3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
6 using System.Collections.Generic;
7 using System.Globalization;
20 private Point prevResolution;
25 private readonly List<GUITickBox> missionTickBoxes =
new List<GUITickBox>();
26 private readonly List<GUITextBlock> missionRewardTexts =
new List<GUITextBlock>();
28 private bool hasMaxMissions;
38 private GUIButton StartButton {
get;
set; }
54 if (campaign.
Map ==
null) {
throw new InvalidOperationException(
"Failed to create campaign UI (campaign map was null)."); }
55 if (campaign.
Map.
CurrentLocation ==
null) {
throw new InvalidOperationException(
"Failed to create campaign UI (current location not set)."); }
62 if (missionList?.Content !=
null)
68 tickBox.Selected = missions.Contains(tickBox.UserData as
Mission);
86 var notificationFrame =
new GUIFrame(
new RectTransform(
new Point(mapContainer.Rect.Width, GUI.IntScale(40)), mapContainer.RectTransform,
Anchor.BottomCenter), style:
"ChatBox");
88 new GUIFrame(
new RectTransform(Vector2.One, mapFrame.RectTransform), style:
"InnerGlow", color: Color.Black * 0.9f)
93 var notificationContainer =
new GUICustomComponent(
new RectTransform(
new Vector2(0.98f, 1.0f), notificationFrame.RectTransform,
Anchor.Center), DrawMapNotifications,
null)
95 HideElementsOutsideFrame =
true
97 var notificationHeader =
new GUIImage(
new RectTransform(
new Vector2(0.1f, 1.0f), notificationFrame.RectTransform,
Anchor.CenterLeft), style:
"GUISlopedHeaderRight");
98 var text =
new GUITextBlock(
new RectTransform(Vector2.One, notificationHeader.RectTransform,
Anchor.Center), TextManager.Get(
"breakingnews"), font: GUIStyle.LargeFont);
99 notificationHeader.RectTransform.MinSize =
new Point((
int)(text.TextSize.X * 1.3f), 0);
103 var crewTab =
new GUIFrame(
new RectTransform(Vector2.One, container.
RectTransform), color: Color.Black * 0.9f);
104 tabs[(int)CampaignMode.InteractionType.Crew] = crewTab;
109 var storeTab =
new GUIFrame(
new RectTransform(Vector2.One, container.
RectTransform), color: Color.Black * 0.9f);
110 tabs[(int)CampaignMode.InteractionType.Store] = storeTab;
115 tabs[(int)CampaignMode.InteractionType.Upgrade] =
new GUIFrame(
new RectTransform(Vector2.One, container.
RectTransform), color: Color.Black * 0.9f);
119 tabs[(int)CampaignMode.InteractionType.PurchaseSub] =
new GUIFrame(
new RectTransform(Vector2.One, container.
RectTransform,
Anchor.TopLeft), color: Color.Black * 0.9f);
121 tabs[(int)CampaignMode.InteractionType.MedicalClinic] =
new GUIFrame(
new RectTransform(Vector2.One, container.
RectTransform), color: Color.Black * 0.9f);
127 { RelativeOffset = new Vector2(0.02f, 0.0f) },
135 SelectTab(CampaignMode.InteractionType.Map);
137 prevResolution =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
140 private GUIFrame CreateDefaultTabContainer(GUIComponent container, Vector2 frameSize,
bool visible =
true)
142 var innerFrame =
new GUIFrame(
new RectTransform(frameSize, container.RectTransform,
Anchor.Center))
146 new GUIFrame(
new RectTransform(innerFrame.Rect.Size - GUIStyle.ItemFrameMargin, innerFrame.RectTransform,
Anchor.Center), style:
null)
148 UserData =
"container"
155 var tabFrame = tabs[(int)tab];
169 private void DrawMapNotifications(SpriteBatch spriteBatch, GUICustomComponent notificationContainer)
174 private void UpdateMap(
float deltaTime, GUICustomComponent mapContainer)
177 if (map ==
null) {
return; }
182 map.Update(
Campaign, deltaTime, mapContainer);
183 foreach (GUITickBox tickBox
in missionTickBoxes)
185 bool disable = hasMaxMissions && !tickBox.Selected;
186 tickBox.Enabled = CampaignMode.AllowedToManageCampaign(
ClientPermissions.ManageMap) && !disable;
187 tickBox.Box.DisabledColor = disable ? tickBox.Box.Color * 0.5f : tickBox.Box.Color * 0.8f;
188 foreach (GUIComponent child
in tickBox.Parent.Parent.Children)
190 if (child is GUITextBlock textBlock)
192 textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor =
193 disable ?
new Color(textBlock.TextColor, 0.5f) : new Color(textBlock.TextColor, 1.0f);
204 submarineSelection?.
Update();
216 if (StartButton !=
null)
226 if (selectedLocation !=
null &&
Campaign?.
Map?.SelectedConnection !=
null)
234 missionTickBoxes.Clear();
235 missionRewardTexts.Clear();
241 locationInfoPanel.
Visible = location !=
null;
244 Location prevSelectedLocation = selectedLocation;
245 float prevMissionListScroll = missionList?.
BarScroll ?? 0.0f;
247 selectedLocation = location;
248 if (location ==
null) {
return; }
250 int padding = GUI.IntScale(20);
255 RelativeSpacing = 0.02f,
260 AutoScaleHorizontal =
true
269 portrait.Draw(sb, customComponent.Rect.Center.ToVector2(), Color.Gray, portrait.size / 2, scale: Math.Max(customComponent.Rect.Width / portrait.size.X, customComponent.Rect.Height / portrait.size.Y));
272 HideElementsOutsideFrame =
true
277 RelativeSpacing = 0.05f
285 TextManager.Get(
"Faction"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
289 TextManager.Get(
"Biome",
"location"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
293 TextManager.Get(
"LevelDifficulty"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft);
294 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 1.0f), difficultyLabel.RectTransform), TextManager.GetWithVariable(
"percentageformat",
"[value]", ((
int)connection.
LevelData.
Difficulty).ToString()), textAlignment: Alignment.CenterRight);
298 var beaconStationContent =
new GUILayoutGroup(
new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
300 var icon =
new GUIImage(
new RectTransform(
new Point((
int)(beaconStationContent.Rect.Height * 1.2f)), beaconStationContent.RectTransform),
301 style, scaleToFit:
true)
308 TextManager.Get(
"submarinetype.beaconstation",
"beaconstationsonarlabel"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
310 Padding = Vector4.Zero,
316 var huntingGroundsContent =
new GUILayoutGroup(
new RectTransform(biomeLabel.RectTransform.NonScaledSize, textContent.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft);
317 var icon =
new GUIImage(
new RectTransform(
new Point((
int)(huntingGroundsContent.Rect.Height * 1.5f)), huntingGroundsContent.RectTransform),
318 "HuntingGrounds", scaleToFit:
true)
322 ToolTip =
RichString.
Rich(TextManager.Get(
"HuntingGroundsTooltip"))
325 TextManager.Get(
"missionname.huntinggrounds"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
327 Padding = Vector4.Zero,
335 Spacing = (int)(5 * GUI.yScale)
340 if (GUI.MouseOn == tickBox) {
return false; }
345 tickBox.
Selected = !tickBox.Selected;
353 if (connection !=
null && connection.
Locations.Contains(currentDisplayLocation))
357 if (!availableMissions.Any()) { availableMissions.Insert(0,
null); }
359 availableMissions.AddRange(location.
AvailableMissions.Where(m => m.Locations[0] == m.Locations[1]));
363 bool isPrevMissionInNextLocation =
false;
364 foreach (
Mission mission
in availableMissions)
366 bool isMissionInNextLocation = mission !=
null && location.
AvailableMissions.Contains(mission);
367 if (isMissionInNextLocation && !isPrevMissionInNextLocation)
370 textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont, wrap:
true)
379 isPrevMissionInNextLocation = isMissionInNextLocation;
389 AbsoluteSpacing = GUI.IntScale(5)
392 var missionName =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), mission?.
Name ?? TextManager.Get(
"NoMission"), font: GUIStyle.SubHeadingFont, wrap:
true);
393 missionName.RectTransform.MinSize =
new Point(0, GUI.IntScale(15));
396 missionTextContent.RectTransform.MinSize = missionName.RectTransform.MinSize =
new Point(0, GUI.IntScale(35));
397 missionTextContent.ChildAnchor =
Anchor.CenterLeft;
402 if (!isMissionInNextLocation)
404 tickBox =
new GUITickBox(
new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor:
Anchor.CenterLeft, scaleBasis:
ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.X, 0) }, label:
string.Empty)
431 UpdateMaxMissions(connection.
OtherLocation(currentDisplayLocation));
440 missionTickBoxes.Add(tickBox);
446 difficultyIndicatorGroup =
new GUILayoutGroup(
new RectTransform(Vector2.One * 0.9f, missionName.RectTransform, anchor:
Anchor.CenterRight, scaleBasis:
ScaleBasis.Smallest) { AbsoluteOffset = new Point((int)missionName.Padding.Z, 0) },
447 isHorizontal:
true, childAnchor:
Anchor.CenterRight)
450 UserData =
"difficulty"
457 Color = difficultyColor,
458 SelectedColor = difficultyColor,
459 HoverColor = difficultyColor
464 float extraPadding = 0;
465 float extraZPadding = difficultyIndicatorGroup !=
null ? mission.
Difficulty.Value * (difficultyIndicatorGroup.
Children.First().Rect.Width + difficultyIndicatorGroup.
AbsoluteSpacing) : 0;
466 missionName.Padding =
new Vector4(missionName.Padding.X + (tickBox?.
Rect.Width ?? 0) * 1.2f + extraPadding,
467 missionName.Padding.Y,
468 missionName.Padding.Z + extraZPadding + extraPadding,
469 missionName.Padding.W);
470 missionName.CalculateHeightFromText();
473 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.0f), missionTextContent.RectTransform) { MinSize = new Point(0, GUI.IntScale(10)) }, style:
null);
479 missionRewardTexts.Add(rewardText);
482 if (!reputationText.IsNullOrEmpty())
488 missionPanel.RectTransform.MinSize =
new Point(0, (
int)(missionTextContent.Children.Sum(c => c.Rect.Height + missionTextContent.AbsoluteSpacing) / missionTextContent.RectTransform.RelativeSize.Y) + GUI.IntScale(0));
493 textBlock.Color = textBlock.SelectedColor = textBlock.HoverColor = Color.Transparent;
494 textBlock.SelectedTextColor = textBlock.HoverTextColor = textBlock.TextColor;
497 missionPanel.OnAddedToGUIUpdateList = (c) =>
499 missionTextContent.Children.ForEach(child => child.
State = c.State);
500 if (missionTextContent.FindChild(
"difficulty", recursive:
true) is
GUILayoutGroup group)
502 group.State = c.State;
506 if (mission != availableMissions.Last())
514 if (prevSelectedLocation == selectedLocation)
516 missionList.
BarScroll = prevMissionListScroll;
521 var destination = connection.
OtherLocation(currentDisplayLocation);
522 UpdateMaxMissions(destination);
526 new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f), buttonArea.RectTransform),
"", font: GUIStyle.SubHeadingFont)
530 int missionCount = 0;
535 return TextManager.AddPunctuation(
':', TextManager.Get(
"Missions"), $
"{missionCount}/{Campaign.Settings.TotalMaxMissionCount}");
540 TextManager.Get(
"StartCampaignButton"), style:
"GUIButtonLarge")
542 OnClicked = (
GUIButton btn,
object obj) =>
547 var noMissionVerification =
new GUIMessageBox(
string.Empty, TextManager.Get(
"nomissionprompt"),
new LocalizedString[] { TextManager.Get(
"yes"), TextManager.Get(
"no") });
548 noMissionVerification.Buttons[0].OnClicked = (btn, userdata) =>
551 noMissionVerification.Close();
554 noMissionVerification.Buttons[1].OnClicked = noMissionVerification.Close;
581 HintManager.OnShowCampaignInterface(tab);
585 for (
int i = 0; i < tabs.Length; i++)
589 tabs[i].
Visible = (int)selectedTab == i;
640 private void UpdateMaxMissions(
Location location)
674 AddBalance(parent,
true, TextManager.Get(
"campaignstore.balance"),
GetTotalBalance);
680 var totalBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get(
"campaignstore.total"),
GetTotalBalance);
681 var bankBalanceContainer = AddBalance(parent, displaySeparateBalances, TextManager.Get(
"crewwallet.bank"),
GetBankBalance);
682 AddBalance(parent,
true, TextManager.Get(
"crewwallet.wallet"),
GetWalletBalance);
683 var playerBalanceElement =
new PlayerBalanceElement(displaySeparateBalances, parent, totalBalanceContainer, bankBalanceContainer);
684 parent.Recalculate();
685 return playerBalanceElement;
691 var rt =
new RectTransform(
new Vector2(balanceContainerWidth, 1.0f), parent.RectTransform)
693 MaxSize =
new Point(GUI.IntScale(GUI.AdjustForTextScale(120)),
int.MaxValue)
697 RelativeSpacing = 0.005f,
701 font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
703 AutoScaleVertical =
true,
707 textColor: Color.White, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopRight)
709 AutoScaleVertical =
true,
711 TextGetter = textGetter
713 return balanceContainer;
719 if (playerBalanceElement is { } balanceElement)
722 if (displaySeparateBalances != balanceElement.DisplaySeparateBalances)
724 balanceElement.TotalBalanceContainer.Visible = displaySeparateBalances;
725 balanceElement.BankBalanceContainer.Visible = displaySeparateBalances;
727 balanceElement.ParentComponent.Recalculate();
730 return playerBalanceElement;
readonly LocalizedString DisplayName
static bool AllowedToManageWallets()
virtual int GetBalance(Client client=null)
int NumberOfMissionsAtLocation(Location location)
MedicalClinic MedicalClinic
bool??????? ShowCampaignUI
virtual Wallet Wallet
Gets the current personal wallet In singleplayer this is the campaign bank and in multiplayer this is...
CampaignSettings Settings
static bool AllowedToManageCampaign(ClientPermissions permissions)
There is a server-side implementation of the method in MultiPlayerCampaign
Location GetCurrentDisplayLocation()
The location that's displayed as the "current one" in the map screen. Normally the current outpost or...
UpgradeStore UpgradeStore
CampaignMode.InteractionType SelectedTab
static ? PlayerBalanceElement AddBalanceElement(GUIComponent elementParent, Vector2 relativeSize)
static ? PlayerBalanceElement UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
static LocalizedString GetBankBalance()
GUIComponent GetTabContainer(CampaignMode.InteractionType tab)
static LocalizedString GetWalletBalance()
CampaignUI(CampaignMode campaign, GUIComponent container)
static LocalizedString GetTotalBalance()
void SelectTab(CampaignMode.InteractionType tab, Character npc=null)
void Update(float deltaTime)
MedicalClinicUI MedicalClinic
void RefreshLocationInfo()
void SelectLocation(Location location, LocationConnection connection)
static LocalizedString GetMoney()
static Character? Controlled
virtual ComponentState State
GUIComponent GetChildByUserData(object obj)
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
OnSelectedHandler OnSelected
Triggers when some element is clicked on the listbox. Note that SelectedData is not set yet when this...
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
delegate LocalizedString TextGetterHandler()
OnSelectedHandler OnSelected
static GameSession?? GameSession
static int GraphicsHeight
static bool IsSingleplayer
IEnumerable< Mission > Missions
The "HR manager" UI, which is used to hire/fire characters and rename crewmates.
readonly float Difficulty
readonly LevelData LevelData
Location OtherLocation(Location location)
IEnumerable< Mission > GetMissionsInConnection(LocationConnection connection)
IEnumerable< Mission > AvailableMissions
void SelectMission(Mission mission)
IEnumerable< Mission > SelectedMissions
void DeselectMission(Mission mission)
LocalizedString DisplayName
Sprite GetPortrait(int randomSeed)
readonly LocalizedString Name
static MapGenerationParams Instance
Action< Location, LocationConnection > OnLocationSelected
void DrawNotifications(SpriteBatch spriteBatch, GUICustomComponent container)
Action< LocationConnection, IEnumerable< Mission > > OnMissionsSelected
LocationConnection SelectedConnection
void SelectLocation(int index)
void Draw(CampaignMode campaign, SpriteBatch spriteBatch, GUICustomComponent mapContainer)
void ResetPendingSub()
Resets pendingSubInfo and forces crush depth to be calculated again for icon displaying purposes
Color GetDifficultyColor()
virtual RichString GetMissionRewardText(Submarine sub)
Returns the full reward text of the mission (e.g. "Reward: 2,000 mk" or "Reward: 500 mk x 2 (out of m...
virtual LocalizedString Name
virtual LocalizedString Description
RichString GetReputationRewardText()
readonly Location[] Locations
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
void EnsureLazyLoaded(bool isAsync=false)
void Update(float deltaTime)
void SelectStore(Character merchant)
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.
void RefreshSubmarineDisplay(bool updateSubs, bool setTransferOptionToTrue=false)
readonly GUILayoutGroup TotalBalanceContainer
PlayerBalanceElement(PlayerBalanceElement element, bool displaySeparateBalances)
readonly bool DisplaySeparateBalances
readonly GUILayoutGroup ParentComponent
PlayerBalanceElement(bool displaySeparateBalances, GUILayoutGroup parentComponent, GUILayoutGroup totalBalanceContainer, GUILayoutGroup bankBalanceContainer)
readonly GUILayoutGroup BankBalanceContainer