3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
21 private GUIListBox hireableList, pendingList, crewList;
22 private GUIFrame characterPreviewFrame;
28 private PlayerBalanceElement? playerBalanceElement;
33 private bool wasReplacingPermanentlyDeadCharacter;
38 private static bool ReplacingPermanentlyDeadCharacter =>
42 private bool hadPermissionToHire;
43 private static bool HasPermissionToHire => ReplacingPermanentlyDeadCharacter ?
47 private Point resolutionWhenCreated;
49 private bool needsHireableRefresh;
51 private enum SortingMethod
63 this.campaignUI = campaignUI;
64 this.parentComponent = parentComponent;
70 "CrewManagement.UpdateLocationView".ToIdentifier(),
71 (locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation,
true, locationChangeInfo.PrevLocation));
73 "CrewManagement.UpdateLocationView".ToIdentifier(), _ => needsHireableRefresh =
true);
75 hadPermissionToHire = HasPermissionToHire;
76 wasReplacingPermanentlyDeadCharacter = ReplacingPermanentlyDeadCharacter;
81 RefreshCrewFrames(hireableList);
82 RefreshCrewFrames(crewList);
83 RefreshCrewFrames(pendingList);
84 if (clearAllButton !=
null) { clearAllButton.
Enabled = HasPermissionToHire; }
85 hadPermissionToHire = HasPermissionToHire;
86 wasReplacingPermanentlyDeadCharacter = ReplacingPermanentlyDeadCharacter;
89 private void RefreshCrewFrames(
GUIListBox listBox)
91 if (listBox ==
null) {
return; }
95 if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo,
true) is GUIButton buyButton)
97 CharacterInfo characterInfo = buyButton.UserData as CharacterInfo;
100 !ReplacingPermanentlyDeadCharacter &&
101 HasPermissionToHire &&
103 foreach (GUITextBlock text
in child.GetAllChildren<GUITextBlock>())
105 text.TextColor =
new Color(text.TextColor, buyButton.Enabled ? 1.0f : 0.6f);
111 private void CreateUI()
113 if (parentComponent.FindChild(c => c.UserData as
string ==
"glow") is GUIComponent glowChild)
115 parentComponent.RemoveChild(glowChild);
117 if (parentComponent.FindChild(c => c.UserData as
string ==
"container") is GUIComponent containerChild)
119 parentComponent.RemoveChild(containerChild);
122 new GUIFrame(
new RectTransform(
new Vector2(1.25f, 1.25f), parentComponent.RectTransform,
Anchor.Center),
123 style:
"OuterGlow", color: Color.Black * 0.7f)
127 new GUIFrame(
new RectTransform(
new Vector2(0.95f), parentComponent.RectTransform, anchor:
Anchor.Center),
130 CanBeFocused =
false,
131 UserData =
"container"
134 int panelMaxWidth = (int)(GUI.xScale * (GUI.HorizontalAspectRatio < 1.4f ? 650 : 560));
135 var availableMainGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform)
137 MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
141 RelativeSpacing = 0.02f
145 var headerGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.75f / 14.0f), availableMainGroup.RectTransform), isHorizontal:
true)
147 RelativeSpacing = 0.005f
149 var imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width;
150 new GUIImage(
new RectTransform(
new Vector2(imageWidth, 1.0f), headerGroup.RectTransform),
"CrewManagementHeaderIcon");
151 new GUITextBlock(
new RectTransform(
new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get(
"campaigncrew.header"), font: GUIStyle.LargeFont)
153 CanBeFocused =
false,
157 var hireablesGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.95f), anchor:
Anchor.Center,
158 parent:
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 13.25f / 14.0f), availableMainGroup.RectTransform)).RectTransform))
160 RelativeSpacing = 0.015f,
164 var sortGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.04f), hireablesGroup.RectTransform), isHorizontal:
true)
166 RelativeSpacing = 0.015f,
169 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), sortGroup.RectTransform), text: TextManager.Get(
"campaignstore.sortby"));
170 sortingDropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.5f, 1.0f), sortGroup.RectTransform), elementCount: 5)
172 OnSelected = (child, userData) =>
174 SortCharacters(hireableList, (SortingMethod)userData);
178 var tag =
"sortingmethod.";
179 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.JobAsc), userData: SortingMethod.JobAsc);
180 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.SkillAsc), userData: SortingMethod.SkillAsc);
181 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.SkillDesc), userData: SortingMethod.SkillDesc);
182 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.PriceAsc), userData: SortingMethod.PriceAsc);
183 sortingDropDown.
AddItem(TextManager.Get(tag + SortingMethod.PriceDesc), userData: SortingMethod.PriceDesc);
185 hireableList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 0.96f),
186 hireablesGroup.RectTransform,
192 var pendingAndCrewMainGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform, anchor:
Anchor.TopRight)
194 MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
198 RelativeSpacing = 0.02f
201 playerBalanceElement = CampaignUI.AddBalanceElement(pendingAndCrewMainGroup,
new Vector2(1.0f, 0.75f / 14.0f));
203 pendingAndCrewPanel =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform)
205 MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
208 var pendingAndCrewGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.9f, 0.95f), anchor:
Anchor.Center,
211 float height = 0.05f;
212 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get(
"campaigncrew.pending"), font: GUIStyle.SubHeadingFont);
213 pendingList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform))
218 var crewHeader =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get(
"campaignmenucrew"), font: GUIStyle.SubHeadingFont);
220 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), crewHeader.RectTransform,
Anchor.CenterRight),
string.Empty, textAlignment: Alignment.CenterRight)
225 return $
"{crewSize}/{CrewManager.MaxCrewSize}";
228 crewList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform))
233 var group =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal:
true);
234 new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), group.RectTransform), TextManager.Get(
"campaignstore.total"));
235 totalBlock =
new GUITextBlock(
new RectTransform(
new Vector2(0.5f, 1.0f), group.RectTransform),
"", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Right)
239 group =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal:
true, childAnchor:
Anchor.TopRight)
241 RelativeSpacing = 0.01f
243 validateHiresButton =
new GUIButton(
new RectTransform(
new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get(
"campaigncrew.validate"))
247 OnClicked = (b, o) =>
ValidateHires(PendingHires, createNetworkEvent:
true)
249 clearAllButton =
new GUIButton(
new RectTransform(
new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get(
"campaignstore.clearall"))
253 Enabled = HasPermissionToHire,
254 OnClicked = (b, o) => RemoveAllPendingHires()
256 GUITextBlock.AutoScaleAndNormalize(validateHiresButton.
TextBlock, clearAllButton.
TextBlock);
258 resolutionWhenCreated =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
261 private void UpdateLocationView(Location location,
bool removePending, Location prevLocation =
null)
263 if (prevLocation !=
null && prevLocation == location && GameMain.NetworkMember !=
null) {
return; }
265 if (characterPreviewFrame !=
null)
268 characterPreviewFrame =
null;
271 if (pendingList !=
null)
275 PendingHires?.Clear();
280 PendingHires?.ForEach(ci => AddPendingHire(ci));
294 if (hireableList ==
null) {
return; }
297 if (hireableCharacters.None())
299 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.2f), hireableList.
Content.
RectTransform), TextManager.Get(
"HireUnavailable"), textAlignment: Alignment.Center)
306 foreach (CharacterInfo c
in hireableCharacters)
308 if (c ==
null || PendingHires.Contains(c)) {
continue; }
312 sortingDropDown.SelectItem(SortingMethod.JobAsc);
319 if (hireManager ==
null) {
return; }
320 int hireVal = hireManager.
AvailableCharacters.Aggregate(0, (curr, hire) => curr + hire.ID);
321 int newVal = availableHires.Aggregate(0, (curr, hire) => curr + hire.ID);
322 if (hireVal != newVal)
337 SortCharacters(crewList, SortingMethod.JobAsc);
341 private void SortCharacters(
GUIListBox list, SortingMethod sortingMethod)
343 if (sortingMethod == SortingMethod.AlphabeticalAsc)
346 CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
347 ((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Name.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Name));
349 else if (sortingMethod == SortingMethod.JobAsc)
351 SortCharacters(list, SortingMethod.AlphabeticalAsc);
353 CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
354 string.Compare(((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Job.Name.Value, ((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Job.Name.Value, StringComparison.Ordinal));
356 else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
358 SortCharacters(list, SortingMethod.AlphabeticalAsc);
360 CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
361 ((InfoSkill)x.GUIComponent.UserData).CharacterInfo.Salary.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.Salary));
364 else if (sortingMethod == SortingMethod.SkillAsc || sortingMethod == SortingMethod.SkillDesc)
366 SortCharacters(list, SortingMethod.AlphabeticalAsc);
368 CompareReputationRequirement(x.GUIComponent, y.GUIComponent) ??
369 ((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
373 int? CompareReputationRequirement(GUIComponent c1, GUIComponent c2)
375 CharacterInfo info1 = ((InfoSkill)c1.UserData).CharacterInfo;
376 CharacterInfo info2 = ((InfoSkill)c2.UserData).CharacterInfo;
377 float requirement1 = EnoughReputationToHire(info1) ? 0 : info1.MinReputationToHire.reputation;
378 float requirement2 = EnoughReputationToHire(info2) ? 0 : info2.MinReputationToHire.reputation;
379 if (MathUtils.NearlyEqual(requirement1, 0.0f) && MathUtils.NearlyEqual(requirement2, 0.0f))
385 return requirement1.CompareTo(requirement2);
390 private readonly
struct InfoSkill
392 public readonly CharacterInfo CharacterInfo;
393 public readonly
float SkillLevel;
395 public InfoSkill(CharacterInfo characterInfo,
float skillLevel)
397 CharacterInfo = characterInfo;
398 SkillLevel = skillLevel;
405 Color? jobColor =
null;
406 if (characterInfo.
Job !=
null)
414 UserData =
new InfoSkill(characterInfo, skill?.
Level ?? 0.0f)
421 float portraitWidth = (0.8f * mainGroup.
Rect.Height) / mainGroup.
Rect.Width;
423 onDraw: (sb, component) => characterInfo.
DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
432 textColor: jobColor, textAlignment: Alignment.BottomLeft)
436 nameBlock.
Text = ToolBox.LimitString(nameBlock.
Text, nameBlock.
Font, nameBlock.
Rect.Width);
439 characterInfo.
Title ?? characterInfo.
Job.
Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft)
443 if (!characterInfo.MinReputationToHire.factionId.IsEmpty)
445 var faction = campaign.
Factions.Find(f => f.Prefab.Identifier == characterInfo.MinReputationToHire.factionId);
448 jobBlock.
TextColor = faction.Prefab.IconColor;
451 var fullJobText = jobBlock.
Text;
452 jobBlock.
Text = ToolBox.LimitString(fullJobText, jobBlock.
Font, jobBlock.
Rect.Width);
453 if (jobBlock.
Text != fullJobText)
455 jobBlock.
ToolTip = fullJobText;
458 float width = 0.6f / 3;
459 if (characterInfo.
Job !=
null && skill !=
null)
462 float iconWidth = (float)skillGroup.
Rect.Height / skillGroup.
Rect.Width;
467 if (jobColor.HasValue) { skillIcon.
Color = jobColor.Value; }
476 if (listBox != crewList)
480 textAlignment: Alignment.Center)
492 if (listBox == hireableList)
496 ToolTip = TextManager.Get(
"hirebutton"),
498 UserData = characterInfo,
499 Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter,
502 hireButton.OnAddedToGUIUpdateList += (
GUIComponent btn) =>
504 if (ReplacingPermanentlyDeadCharacter)
512 btn.ToolTip = TextManager.Get(
"canthiremorecharacters");
516 else if (!btn.Enabled)
518 btn.ToolTip =
string.Empty;
519 btn.Enabled = CanHire(characterInfo);
523 if (ReplacingPermanentlyDeadCharacter)
528 ToolTip = canHire ? TextManager.Get(
"hireandtakecontrol") : TextManager.Get(
"hireandtakecontroldisabled"),
530 UserData = characterInfo,
532 OnClicked = (b, o) =>
538 Client client = gameClient.ConnectedClients.FirstOrDefault(c => c.SessionId == gameClient.SessionId);
543 gameClient.SendTakeOverBotRequest(characterInfo);
544 needsHireableRefresh =
true;
549 takeoverButton.OnAddedToGUIUpdateList += (
GUIComponent btn) =>
551 bool canHireCurrently = ReplacingPermanentlyDeadCharacter && CanHire(characterInfo) && campaign.
CanAffordNewCharacter(characterInfo);
552 btn.ToolTip = TextManager.Get(canHireCurrently ?
"hireandtakecontrol" :
"hireandtakecontroldisabled");
554 btn.Enabled = canHireCurrently;
558 else if (listBox == pendingList)
563 UserData = characterInfo,
564 Enabled = CanHire(characterInfo),
568 else if (listBox == crewList && campaign !=
null)
573 UserData = characterInfo,
575 Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermissionToHire,
576 OnClicked = (btn, obj) =>
579 TextManager.Get(
"FireWarningHeader"),
580 TextManager.GetWithVariable(
"FireWarningText",
"[charactername]", ((
CharacterInfo)obj).Name),
581 new LocalizedString[] { TextManager.Get(
"Yes"), TextManager.Get(
"No") });
583 confirmDialog.Buttons[0].OnClicked = FireCharacter;
584 confirmDialog.Buttons[0].OnClicked += confirmDialog.Close;
585 confirmDialog.Buttons[1].OnClicked = confirmDialog.Close;
591 if (listBox == pendingList || listBox == crewList)
594 nameBlock.
Text = ToolBox.LimitString(nameBlock.
Text, nameBlock.
Font, nameBlock.
Rect.Width);
596 Point size =
new Point((
int)(0.7f * nameBlock.
Rect.Height));
598 size =
new Point(3 * mainGroup.
AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.
Rect.Width, mainGroup.
Rect.Height);
601 Enabled = CanHire(characterInfo),
603 UserData = characterInfo,
604 OnClicked = CreateRenamingComponent
610 if (!HasPermissionToHire) {
return false; }
611 return EnoughReputationToHire(thisCharacterInfo);
617 private bool EnoughReputationToHire(
CharacterInfo characterInfo)
619 if (characterInfo.MinReputationToHire.factionId != Identifier.Empty)
621 if (MathF.Round(campaign.
GetReputation(characterInfo.MinReputationToHire.factionId)) < characterInfo.MinReputationToHire.reputation)
629 private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
631 Pivot pivot = listBox == hireableList ?
Pivot.TopLeft :
Pivot.TopRight;
632 Point absoluteOffset =
new Point(
633 pivot ==
Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5,
634 characterFrame.Rect.Top);
635 Point frameSize =
new Point(GUI.IntScale(300), GUI.IntScale(350));
636 if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize.Y) < 0)
638 pivot = listBox == hireableList ?
Pivot.BottomLeft :
Pivot.BottomRight;
639 absoluteOffset.Y = characterFrame.Rect.Bottom;
641 characterPreviewFrame =
new GUIFrame(
new RectTransform(frameSize, parent: campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Parent.RectTransform, pivot: pivot)
643 AbsoluteOffset = absoluteOffset
644 }, style:
"InnerFrame")
646 UserData = characterInfo
648 GUILayoutGroup mainGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.95f), characterPreviewFrame.
RectTransform, anchor:
Anchor.Center))
650 RelativeSpacing = 0.01f,
655 GUILayoutGroup infoGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal:
true);
656 GUILayoutGroup infoLabelGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch =
true };
657 GUILayoutGroup infoValueGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch =
true };
658 float blockHeight = 1.0f / 4;
659 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(
"name"));
660 GUITextBlock nameBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform),
"");
661 string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
662 nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
664 if (characterInfo.HasSpecifierTags)
666 var menuCategoryVar = characterInfo.Prefab.MenuCategoryVar;
667 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar));
668 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.ReplaceVars($
"[{menuCategoryVar}]")));
670 if (characterInfo.Job is Job job)
672 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(
"tabmenu.job"));
673 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name);
675 if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait)
677 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(
"PersonalityTrait"));
678 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName);
680 infoLabelGroup.Recalculate();
681 infoValueGroup.Recalculate();
683 new GUIImage(
new RectTransform(
new Vector2(1.0f, 0.05f), mainGroup.RectTransform),
"HorizontalLine");
686 GUILayoutGroup skillGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal:
true);
687 GUILayoutGroup skillNameGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 1.0f), skillGroup.RectTransform));
688 GUILayoutGroup skillLevelGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.2f, 1.0f), skillGroup.RectTransform));
689 var characterSkills = characterInfo.Job.GetSkills();
690 blockHeight = 1.0f / characterSkills.Count();
691 foreach (Skill skill
in characterSkills)
693 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get(
"SkillName." + skill.Identifier), font: GUIStyle.SmallFont);
694 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, blockHeight), skillLevelGroup.RectTransform), ((
int)skill.Level).ToString(), textAlignment: Alignment.Right);
697 if (characterInfo.MinReputationToHire.reputation > 0.0f)
699 var repStr = TextManager.GetWithVariables(
700 "campaignstore.reputationrequired",
701 (
"[amount]", ((
int)characterInfo.MinReputationToHire.reputation).ToString()),
702 (
"[faction]", TextManager.Get(
"faction." + characterInfo.MinReputationToHire.factionId).Value));
703 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), mainGroup.RectTransform),
704 repStr, textColor: !EnoughReputationToHire(characterInfo) ? GUIStyle.Orange : GUIStyle.Green,
705 font: GUIStyle.SmallFont, wrap:
true, textAlignment: Alignment.Center);
707 mainGroup.Recalculate();
709 new Point(0, (
int)(mainGroup.Children.Sum(c => c.Rect.Height + mainGroup.Rect.Height * mainGroup.RelativeSpacing) / mainGroup.RectTransform.RelativeSize.Y));
712 private bool SelectCharacter(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
714 if (characterPreviewFrame !=
null && characterPreviewFrame.
UserData != characterInfo)
717 characterPreviewFrame =
null;
720 if (listBox ==
null || characterFrame ==
null || characterInfo ==
null) {
return false; }
722 if (characterPreviewFrame ==
null)
724 CreateCharacterPreviewFrame(listBox, characterFrame, characterInfo);
730 private bool AddPendingHire(CharacterInfo characterInfo,
bool createNetworkMessage =
true)
739 if (!PendingHires.Contains(characterInfo)) { PendingHires.Add(characterInfo); }
741 SortCharacters(pendingList, SortingMethod.JobAsc);
748 private bool RemovePendingHire(CharacterInfo characterInfo,
bool setTotalHireCost =
true,
bool createNetworkMessage =
true)
750 if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); }
755 if (!GameMain.IsMultiplayer) { characterInfo?.ResetName(); }
758 hireableList.
Content.
Children.None(c => c.UserData is InfoSkill userData && userData.
CharacterInfo.GetIdentifierUsingOriginalName() == characterInfo.GetIdentifierUsingOriginalName()))
761 SortCharacters(hireableList, (SortingMethod)sortingDropDown.
SelectedItemData);
765 if (setTotalHireCost) { SetTotalHireCost(); }
770 private bool RemoveAllPendingHires(
bool createNetworkMessage =
true)
772 pendingList.
Content.
Children.ToList().ForEach(c => RemovePendingHire(((InfoSkill)c.UserData).CharacterInfo, setTotalHireCost:
false, createNetworkMessage));
777 private void SetTotalHireCost()
779 if (pendingList ==
null || totalBlock ==
null || validateHiresButton ==
null) {
return; }
780 var infos = pendingList.
Content.
Children.Select(
static c => ((InfoSkill)c.UserData).CharacterInfo).ToArray();
781 int total = HireManager.GetSalaryFor(infos);
782 totalBlock.
Text = TextManager.FormatCurrency(total);
783 bool enoughMoney = campaign ==
null || campaign.
CanAfford(total);
784 totalBlock.
TextColor = enoughMoney ? Color.White : Color.Red;
788 public bool ValidateHires(List<CharacterInfo> hires,
bool takeMoney =
true,
bool createNetworkEvent =
false,
bool createNotification =
true)
790 if (hires ==
null || hires.None()) {
return false; }
792 List<CharacterInfo> nonDuplicateHires =
new List<CharacterInfo>();
793 hires.ForEach(hireInfo =>
795 if (campaign.
CrewManager.
GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
797 nonDuplicateHires.Add(hireInfo);
801 if (nonDuplicateHires.None()) {
return false; }
806 if (!campaign.
CanAfford(total)) {
return false; }
809 bool atLeastOneHired =
false;
814 atLeastOneHired =
true;
825 SelectCharacter(
null,
null,
null);
826 if (createNotification)
829 TextManager.Get(
"newcrewmembers"),
830 TextManager.GetWithVariable(
"crewhiredmessage",
"[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
832 dialog.Buttons[0].OnClicked += dialog.Close;
836 if (createNetworkEvent)
844 private bool CreateRenamingComponent(
GUIButton button,
object userData)
846 if (!HasPermissionToHire || userData is not
CharacterInfo characterInfo) {
return false; }
847 var outerGlowFrame =
new GUIFrame(
new RectTransform(
new Vector2(1.25f, 1.25f), parentComponent.RectTransform,
Anchor.Center),
848 style:
"OuterGlow", color: Color.Black * 0.7f);
849 var frame =
new GUIFrame(
new RectTransform(
new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor:
Anchor.Center)
851 MaxSize = new Point(400, 300).Multiply(GUI.Scale)
853 var layoutGroup =
new GUILayoutGroup(
new RectTransform((frame.Rect.Size - GUIStyle.ItemFrameMargin).Multiply(
new Vector2(0.75f, 1.0f)), frame.RectTransform, anchor:
Anchor.Center), childAnchor:
Anchor.TopCenter)
855 RelativeSpacing = 0.02f,
858 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), layoutGroup.RectTransform), TextManager.Get(
"campaigncrew.givenickname"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center, wrap:
true);
859 var groupElementSize =
new Vector2(1.0f, 0.25f);
860 var nameBox =
new GUITextBox(
new RectTransform(groupElementSize, layoutGroup.RectTransform))
864 new GUIButton(
new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get(
"confirm"))
866 OnClicked = (button, userData) =>
870 parentComponent.RemoveChild(outerGlowFrame);
875 nameBox.Flash(color: Color.Red);
881 new GUIButton(
new RectTransform(groupElementSize, layoutGroup.RectTransform), text: TextManager.Get(
"cancel"))
883 OnClicked = (button, userData) =>
885 parentComponent.RemoveChild(outerGlowFrame);
889 layoutGroup.Recalculate();
895 if (characterInfo ==
null ||
string.IsNullOrEmpty(newName)) {
return false; }
896 if (newName == characterInfo.
Name) {
return false; }
899 SendCrewState(
false, renameCharacter: (characterInfo, newName));
904 if (crewComponent !=
null)
909 SortCharacters(crewList, SortingMethod.JobAsc);
914 if (pendingComponent !=
null)
919 SortCharacters(pendingList, SortingMethod.JobAsc);
931 private bool FireCharacter(
GUIButton button,
object selection)
933 if (selection is not
CharacterInfo characterInfo) {
return false; }
936 SelectCharacter(
null,
null,
null);
956 pendingAndCrewPanel.
Visible = !ReplacingPermanentlyDeadCharacter;
958 if (hadPermissionToHire != HasPermissionToHire ||
959 wasReplacingPermanentlyDeadCharacter != ReplacingPermanentlyDeadCharacter)
964 if (needsHireableRefresh)
966 RefreshCrewFrames(hireableList);
967 if (sortingDropDown?.SelectedItemData !=
null)
969 SortCharacters(hireableList, (SortingMethod)sortingDropDown.
SelectedItemData);
971 needsHireableRefresh =
false;
975 if (highlightedFrame !=
null && highlightedInfo !=
null)
977 if (characterPreviewFrame ==
null || highlightedInfo != characterPreviewFrame.
UserData)
988 else if (component.
Parent !=
null)
990 component = component.
Parent;
996 }
while (listBox ==
null);
1000 SelectCharacter(listBox, highlightedFrame as
GUIFrame, highlightedInfo);
1009 else if (characterPreviewFrame !=
null)
1012 characterPreviewFrame =
null;
1021 if (c.
UserData is InfoSkill highlightedData)
1023 return (c, highlightedData.CharacterInfo);
1031 return FindHighlightedCharacter(c.
Parent);
1039 List<CharacterInfo> oldHires = PendingHires.ToList();
1042 RemovePendingHire(pendingHire, createNetworkMessage:
false);
1044 PendingHires.Clear();
1045 foreach (UInt16 identifier
in characterInfos)
1050 AddPendingHire(match, createNetworkMessage:
false);
1051 System.Diagnostics.Debug.Assert(PendingHires.Contains(match));
1055 DebugConsole.ThrowError(
"Received a hire that doesn't exist.");
1086 bool validRenaming = renameCharacter.info !=
null && !
string.IsNullOrEmpty(renameCharacter.newName);
1097 if (firedCharacter !=
null)
virtual bool TryPurchase(Client client, int price)
bool CanAfford(int cost, Client client=null)
float GetReputation(Identifier factionIdentifier)
bool??????? ShowCampaignUI
IReadOnlyList< Faction > Factions
static bool AllowedToManageCampaign(ClientPermissions permissions)
There is a server-side implementation of the method in MultiPlayerCampaign
bool CanAffordNewCharacter(CharacterInfo characterInfo)
int NewCharacterCost(CharacterInfo characterInfo)
bool TryHireCharacter(Location location, CharacterInfo characterInfo, bool takeMoney=true, Client client=null, bool buyingNewCharacter=false)
static ? PlayerBalanceElement UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
Stores information about the Character that is needed between rounds in the menu etc....
CharacterInfo(Identifier speciesName, string name="", string originalName="", Either< Job, JobPrefab > jobOrJobPrefab=null, int variant=0, Rand.RandSync randSync=Rand.RandSync.Unsynced, Identifier npcIdentifier=default)
Character Character
Note: Can be null.
void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
ushort ID
Unique ID given to character infos in MP. Non-persistent. Used by clients to identify which infos are...
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
IEnumerable< CharacterInfo > GetCharacterInfos()
Note: this only returns AI characters' infos in multiplayer. The infos are used to manage hiring/firi...
void FireCharacter(CharacterInfo characterInfo)
void RenameCharacter(CharacterInfo characterInfo, string newName)
virtual void RemoveChild(GUIComponent child)
virtual void ClearChildren()
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
virtual RichString ToolTip
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIComponent that can be used to render custom content on the UI
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
override void RemoveChild(GUIComponent child)
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
static GameSession?? GameSession
static int GraphicsHeight
static bool IsMultiplayer
static NetworkMember NetworkMember
The "HR manager" UI, which is used to hire/fire characters and rename crewmates.
GUIComponent CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox, bool hideSalary=false)
bool RenameCharacter(CharacterInfo characterInfo, string newName)
void SetPendingHires(List< UInt16 > characterInfos, Location location)
bool ValidateHires(List< CharacterInfo > hires, bool takeMoney=true, bool createNetworkEvent=false, bool createNotification=true)
void SendCrewState(bool updatePending,(CharacterInfo info, string newName) renameCharacter=default, CharacterInfo firedCharacter=null, bool validateHires=false)
Notify the server of crew changes
void SetHireables(Location location, List< CharacterInfo > availableHires)
HRManagerUI(CampaignUI campaignUI, GUIComponent parentComponent)
static int GetSalaryFor(IReadOnlyCollection< CharacterInfo > hires)
List< CharacterInfo > PendingHires
void RenameCharacter(CharacterInfo characterInfo, string newName)
List< CharacterInfo > AvailableCharacters
IEnumerable< Skill > GetSkills()
IEnumerable< CharacterInfo > GetHireableCharacters()
readonly NamedEvent< LocationChangeInfo > OnLocationChanged
From -> To
CharacterInfo CharacterInfo
static readonly NamedEvent< Reputation > OnAnyReputationValueChanged
void WriteString(string val)
void WriteBoolean(bool val)
void WriteUInt16(UInt16 val)