-
Notifications
You must be signed in to change notification settings - Fork 23
/
BossLogUI.cs
1743 lines (1514 loc) · 74.8 KB
/
BossLogUI.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using BossChecklist.Resources;
using BossChecklist.UIElements;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using ReLogic.Content;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Terraria;
using Terraria.Audio;
using Terraria.GameContent;
using Terraria.GameContent.ItemDropRules;
using Terraria.GameContent.UI.Elements;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader.Config;
using Terraria.ModLoader.UI;
using Terraria.UI;
using Terraria.UI.Chat;
using static BossChecklist.UIElements.BossLogUIElements;
namespace BossChecklist
{
internal enum SubPage {
Records,
SpawnInfo,
LootAndCollectibles
}
internal enum SubCategory {
PreviousAttempt,
FirstVictory,
PersonalBest,
WorldRecord,
None
}
class BossLogUI : UIState {
public OpenLogButton bosslogbutton; // The main button to open the Boss Log
public LogPanel BookArea; // The main panel for the UI. All content is aligned within this area.
public LogPanel PageOne; // left page content panel
public LogPanel PageTwo; // right page content panel
private int BossLogPageNumber;
public const int Page_TableOfContents = -1;
public const int Page_Credits = -2; // The credits page is the last page after all entries, despite being -2
public const int Page_Prompt = -3;
/// <summary>
/// The page number for the client's Boss Log. Changing the page value will automatically update the log to dispaly the selected page.
/// </summary>
public int PageNum {
get => BossLogPageNumber;
set {
if (value == -3) {
OpenProgressionModePrompt();
}
else if (!HiddenEntriesMode) {
UpdateSelectedPage(value, SelectedSubPage);
}
}
}
/// <summary>
/// Gets the EntryInfo of the entry on the selected page. Returns null if not on an entry page.
/// </summary>
public EntryInfo GetLogEntryInfo => PageNum >= 0 ? BossChecklist.bossTracker.SortedEntries[PageNum] : null;
public PersonalRecords GetPlayerRecords => GetLogEntryInfo.IsRecordIndexed(out int recordIndex) ? GetModPlayer.RecordsForWorld?[recordIndex] : null;
public WorldRecord GetWorldRecords => GetLogEntryInfo.IsRecordIndexed(out int recordIndex) ? WorldAssist.WorldRecordsForWorld[recordIndex] : null;
public PlayerAssist GetModPlayer => Main.LocalPlayer.GetModPlayer<PlayerAssist>();
public static bool AltKeyIsDown => Main.keyState.IsKeyDown(Keys.LeftAlt) || Main.keyState.IsKeyDown(Keys.Right);
// Navigation
public NavigationalButton NextPage;
public NavigationalButton PrevPage;
public SubPage SelectedSubPage = SubPage.SpawnInfo;
public SubPageButton recordButton;
public SubPageButton spawnButton;
public SubPageButton lootButton;
// Book Tabs
public LogTab ToCTab; // also used for the filter tab
public LogTab CreditsTab;
public LogTab BossTab;
public LogTab MiniBossTab;
public LogTab EventTab;
public IndicatorPanel AltInteractionsTab;
public IndicatorIcon InteractionIcon;
public IndicatorPanel IndicatorTab;
public List<IndicatorIcon> Indicators;
public FilterIcon FilterPanel; // contains the filter buttons, (not a filter icon, but it works)
public List<FilterIcon> FilterIcons;
public bool filterOpen = false; // when true, the filter panel is visible to the user
// Table of Contents related
public UIList prehardmodeList; // lists for pre-hardmode and hardmode entries
public UIList hardmodeList;
public ProgressBar prehardmodeBar; // progress bars for pre-hardmode and hardmode entries
public ProgressBar hardmodeBar;
public LogScrollbar scrollOne; // scroll bars for table of contents lists (and other elements too)
public LogScrollbar scrollTwo;
public bool barState = false; // when true, hovering over the progress bar will split up the entry percentages by mod instead of entry type
public UIList pageTwoItemList; // Item slot lists that include: Loot tables, spawn item, and collectibles
public Dictionary<string, bool> HiddenEntriesPending = new Dictionary<string, bool>();
private bool hiddenListOpen = false;
public bool HiddenEntriesMode {
get => hiddenListOpen;
set {
hiddenListOpen = value;
if (value is false) {
foreach (KeyValuePair<string, bool> hiddenState in HiddenEntriesPending) {
if (!hiddenState.Value) {
WorldAssist.HiddenEntries.Remove(hiddenState.Key);
}
else if (!WorldAssist.HiddenEntries.Contains(hiddenState.Key)) {
WorldAssist.HiddenEntries.Add(hiddenState.Key);
}
}
SoundEngine.PlaySound(HiddenEntriesPending.Count > 0 ? SoundID.ResearchComplete : SoundID.MenuClose);
HiddenEntriesPending.Clear();
}
else {
SoundEngine.PlaySound(SoundID.MenuOpen);
}
}
}
// Credits related
public static readonly Dictionary<string, string> contributors = new Dictionary<string, string>() {
{ "Jopojelly", "Creator & Owner" },
{ "SheepishShepherd", "Co-Owner & Maintainer"},
{ "direwolf420", "Code Contributor" },
{ "riveren", "Boss Log Sprites"},
{ "Orian", "Early Testing" },
{ "Panini", "Early Server Testing" }
};
// Record page related
public SubCategory RecordSubCategory = SubCategory.PreviousAttempt;
public SubCategory CompareState = SubCategory.None; // Compare record values to one another
public List<NavigationalButton> RecordCategoryButtons;
// Spawn Info page related
public static int SpawnItemSelected = 0;
public static int RecipeSelected = 0;
// Loot page related
public static bool OtherworldUnlocked = false;
// Extra stuff
public const string LangLog = "Mods.BossChecklist.Log";
public static int headNum = -1;
public static readonly Color faded = new Color(128, 128, 128, 128);
public UIImage PromptCheck; // checkmark for the toggle prompt config button
public UIText PageOneTitle;
public UIText PageTwoTitle;
// Boss Log visibiltiy helpers
private bool bossLogVisible;
internal static bool PendingToggleBossLogUI; // Allows toggling boss log visibility from methods not run during UIScale so Main.screenWidth/etc are correct for ResetUIPositioning method
internal static bool PendingConfigChange; // Allows configs to be updated on Log close, when needed
internal List<int> HiddenIndexes;
internal static bool PendingHiddenChange; // Updates Table of Contents hidden entries once the hidden list is closed
private bool PendingPageChange; // Allows changing the page outside of the UIState without causing ordering or drawing issues.
private int PageChangeValue;
public int PendingPageNum {
get => PageChangeValue;
set {
if (!HiddenEntriesMode) {
PageChangeValue = value;
PendingPageChange = true;
}
}
}
/// <summary>
/// Appends or removes UI elements based on the visibility status it is set to.
/// </summary>
public bool BossLogVisible {
get => bossLogVisible;
set {
if (value) {
Append(BookArea);
Append(ToCTab);
Append(FilterPanel);
Append(IndicatorTab);
Append(AltInteractionsTab);
Append(CreditsTab);
Append(BossTab);
Append(MiniBossTab);
Append(EventTab);
Append(PageOne);
Append(PageTwo);
}
else {
RemoveChild(PageTwo);
RemoveChild(PageOne);
RemoveChild(EventTab);
RemoveChild(MiniBossTab);
RemoveChild(BossTab);
RemoveChild(CreditsTab);
RemoveChild(AltInteractionsTab);
RemoveChild(IndicatorTab);
RemoveChild(FilterPanel);
RemoveChild(ToCTab);
RemoveChild(BookArea);
if (PendingConfigChange) {
PendingConfigChange = false;
BossChecklist.SaveConfig(BossChecklist.BossLogConfig);
}
}
bossLogVisible = value;
}
}
/// <summary>
/// Toggles the Boss Log's visibility state. Defaults to visible.
/// </summary>
/// <param name="show">The visibility state desired</param>
public void ToggleBossLog(bool show = true) {
// First, determine if the player has ever opened the Log before
if (show) {
// Mark the player as having opened the Log if they have not been so already
if (!GetModPlayer.hasOpenedTheBossLog) {
GetModPlayer.enteredWorldReset = false; // If opening for the first time, this doesn't need to occur again until the next world reset
// When opening for the first time, open the Progression Mode prompt if enabled. Otherwise, open the Table of Contents.
PageNum = BossChecklist.BossLogConfig.PromptDisabled ? Page_TableOfContents : Page_Prompt;
}
else {
if (GetModPlayer.enteredWorldReset) {
// If the Log has been opened before, check for a world change.
// This is to reset the page from what the user previously had back to the Table of Contents when entering another world.
GetModPlayer.enteredWorldReset = false;
PageNum = Page_TableOfContents;
}
else {
RefreshPageContent(); // Otherwise, just default to the last page selected
}
}
// Update UI Element positioning before marked visible
// This will always occur after adjusting UIScale, since the UI has to be closed in order to open up the menu options
//ResetUIPositioning();
Main.playerInventory = false; // hide the player inventory
}
else if (PageNum >= 0 && GetLogEntryInfo.IsRecordIndexed(out int selectedEntryIndex) && GetModPlayer.hasNewRecord.Length > 0) {
GetModPlayer.hasNewRecord[selectedEntryIndex] = false; // If UI is closed on a new record page, remove the new record from the list
}
else if (PageNum == Page_TableOfContents && HiddenEntriesMode) {
HiddenEntriesPending.Clear(); // closing the hide list in anyway other than the filter button should not save hidden statuses
HiddenEntriesMode = false;
}
BossLogVisible = show; // Setting the state makes the UIElements append/remove making them visible/invisible
}
public override void OnInitialize() {
BossLogResources.PreloadLogAssets();
bosslogbutton = new OpenLogButton(BossLogResources.Button_Book);
bosslogbutton.Left.Set(Main.screenWidth - bosslogbutton.Width.Pixels - 190, 0f);
bosslogbutton.Top.Pixels = Main.screenHeight - bosslogbutton.Height.Pixels - 8;
bosslogbutton.OnLeftClick += (a, b) => ToggleBossLog(true);
BookArea = new LogPanel();
BookArea.Width.Pixels = BossLogResources.Log_BackPanel.Value.Width;
BookArea.Height.Pixels = BossLogResources.Log_BackPanel.Value.Height;
ToCTab = new LogTab(BossLogResources.Log_Tab, BossLogResources.Nav_TableOfContents) {
Id = "TableOfContents"
};
ToCTab.OnLeftClick += (a, b) => UpdateFilterTabPos(true);
BossTab = new LogTab(BossLogResources.Log_Tab, BossLogResources.Nav_Boss) {
Id = "Boss"
};
MiniBossTab = new LogTab(BossLogResources.Log_Tab, BossLogResources.Nav_MiniBoss) {
Id = "MiniBoss"
};
EventTab = new LogTab(BossLogResources.Log_Tab, BossLogResources.Nav_Event) {
Id = "Event"
};
CreditsTab = new LogTab(BossLogResources.Log_Tab, BossLogResources.Nav_Credits) {
Id = "Credits",
Anchor = -2,
hoverText = $"{LangLog}.Tabs.Credits" // hoverText will never change, so initialize it
};
PageOne = new LogPanel() {
Id = "PageOne",
};
PageOne.Width.Pixels = 375;
PageOne.Height.Pixels = 480;
PageOneTitle = new UIText("", 0.6f, true) {
TextColor = Colors.RarityAmber
};
PageOneTitle.Top.Pixels = 18;
PrevPage = new NavigationalButton(BossLogResources.Nav_Prev, true) {
Id = "Previous"
};
PrevPage.Left.Pixels = 8;
PrevPage.Top.Pixels = 416;
PrevPage.OnLeftClick += PageChangerClicked;
prehardmodeList = new UIList();
prehardmodeList.Left.Pixels = 4;
prehardmodeList.Top.Pixels = 44;
prehardmodeList.Width.Pixels = PageOne.Width.Pixels - 60;
prehardmodeList.Height.Pixels = PageOne.Height.Pixels - 136;
prehardmodeList.PaddingTop = 5;
PageTwo = new LogPanel() {
Id = "PageTwo"
};
PageTwo.Width.Pixels = 375;
PageTwo.Height.Pixels = 480;
PageTwoTitle = new UIText("", 0.6f, true) {
TextColor = Colors.RarityAmber
};
PageTwoTitle.Top.Pixels = 18;
pageTwoItemList = new UIList();
FilterPanel = new FilterIcon(BossLogResources.FilterPanel);
FilterIcons = new List<FilterIcon>() {
new FilterIcon(BossLogResources.Nav_Boss) { Id = "Boss" },
new FilterIcon(BossLogResources.Nav_MiniBoss) { Id = "MiniBoss" },
new FilterIcon(BossLogResources.Nav_Event) { Id = "Event" },
new FilterIcon(BossLogResources.RequestItemTexture(ItemID.EchoMonolith)) { Id = "Hidden" },
};
int offsetY = 0;
foreach (FilterIcon icon in FilterIcons) {
icon.check = BossLogResources.Check_Check;
icon.Top.Pixels = offsetY + 15;
icon.Left.Pixels = 25 - (int)(icon.Width.Pixels / 2);
FilterPanel.Append(icon);
offsetY += 34;
}
Indicators = new List<IndicatorIcon>() {
new IndicatorIcon(BossLogResources.Indicator_OnlyBosses) { Id = "OnlyBosses" },
new IndicatorIcon(BossLogResources.Indicator_Manual) { Id = "Manual" },
new IndicatorIcon(BossLogResources.Indicator_Progression) { Id = "Progression" },
};
IndicatorTab = new IndicatorPanel(Indicators.Count) { Id = "Configurations" };
int offsetX = 2;
foreach (IndicatorIcon icon in Indicators) {
icon.Left.Pixels = 10 + offsetX;
icon.Top.Pixels = 8;
IndicatorTab.Append(icon);
offsetX += 20;
}
InteractionIcon = new IndicatorIcon(BossLogResources.Indicator_Interaction);
AltInteractionsTab = new IndicatorPanel(1) { Id = "Interactions" };
InteractionIcon.Left.Pixels = (int)(AltInteractionsTab.Width.Pixels / 2 - InteractionIcon.Width.Pixels / 2);
InteractionIcon.Top.Pixels = 8;
AltInteractionsTab.Append(InteractionIcon);
NextPage = new NavigationalButton(BossLogResources.Nav_Next, true) {
Id = "Next"
};
NextPage.Left.Pixels = PageTwo.Width.Pixels - NextPage.Width.Pixels - 12;
NextPage.Top.Pixels = 416;
NextPage.OnLeftClick += PageChangerClicked;
PageTwo.Append(NextPage);
hardmodeList = new UIList();
hardmodeList.Left.Pixels = 19;
hardmodeList.Top.Pixels = 44;
hardmodeList.Width.Pixels = PageOne.Width.Pixels - 60;
hardmodeList.Height.Pixels = PageOne.Height.Pixels - 136;
hardmodeList.PaddingTop = 5;
recordButton = new SubPageButton(BossLogResources.Nav_SubPage, SubPage.Records);
recordButton.Left.Pixels = (int)PageTwo.Width.Pixels / 2 - BossLogResources.Nav_SubPage.Value.Width / 2;
recordButton.Top.Pixels = 5 + BossLogResources.Nav_SubPage.Value.Height + 10;
recordButton.OnLeftClick += (a, b) => UpdateSelectedPage(PageNum, SubPage.Records);
spawnButton = new SubPageButton(BossLogResources.Nav_SubPage, SubPage.SpawnInfo);
spawnButton.Left.Pixels = (int)PageTwo.Width.Pixels / 2 - BossLogResources.Nav_SubPage.Value.Width - 8;
spawnButton.Top.Pixels = 5;
spawnButton.OnLeftClick += (a, b) => UpdateSelectedPage(PageNum, SubPage.SpawnInfo);
lootButton = new SubPageButton(BossLogResources.Nav_SubPage, SubPage.LootAndCollectibles);
lootButton.Left.Pixels = (int)PageTwo.Width.Pixels / 2 + 8;
lootButton.Top.Pixels = 5;
lootButton.OnLeftClick += (a, b) => UpdateSelectedPage(PageNum, SubPage.LootAndCollectibles);
// Record Type navigation buttons
RecordCategoryButtons = new List<NavigationalButton>();
for (int value = 0; value < 4; value++) {
RecordCategoryButtons.Add(
new NavigationalButton(BossLogResources.Nav_Record_Category[value], true) {
Record_Anchor = (SubCategory)value,
hoverText = $"{LangLog}.Records.Category.{(SubCategory)value}"
}
);
}
// scroll one currently only appears for the table of contents, so its fields can be set here
scrollOne = new LogScrollbar();
scrollOne.SetView(100f, 1000f);
scrollOne.Top.Pixels = 50f;
scrollOne.Left.Pixels = -18;
scrollOne.Height.Set(-24f, 0.75f);
scrollOne.HAlign = 1f;
// scroll two is used in more areas, such as the display spawn info message box, so its fields are set when needed
scrollTwo = new LogScrollbar();
HiddenIndexes = new List<int>();
}
public override void Update(GameTime gameTime) {
if (PendingToggleBossLogUI) {
PendingToggleBossLogUI = false;
ToggleBossLog(!BossLogVisible);
}
if (PendingPageChange) {
PendingPageChange = false;
PageNum = PageChangeValue;
}
this.AddOrRemoveChild(bosslogbutton, Main.playerInventory);
base.Update(gameTime);
}
public TextSnippet hoveredTextSnippet;
public override void Draw(SpriteBatch spriteBatch) {
base.Draw(spriteBatch);
if (hoveredTextSnippet != null) {
hoveredTextSnippet.OnHover();
if (Main.mouseLeft && Main.mouseLeftRelease) {
hoveredTextSnippet.OnClick();
}
hoveredTextSnippet = null;
}
if (headNum != -1) {
EntryInfo entry = BossChecklist.bossTracker.SortedEntries[headNum];
bool isNotProgressed = BossChecklist.BossLogConfig.ProgressiveChecklist && !entry.IsAutoDownedOrMarked && !entry.IsUpNext;
int headOffset = 0;
foreach (Asset<Texture2D> headIcon in entry.headIconTextures()) {
Texture2D headShown = isNotProgressed ? TextureAssets.NpcHead[0].Value : headIcon.Value;
spriteBatch.Draw(headShown, new Vector2(Main.mouseX + 15 + headOffset, Main.mouseY + 15), MaskBoss(entry));
headOffset += headShown.Width + 2;
}
}
}
/// <summary>
/// Resets the positioning of the Boss Log's common UI elements.
/// This includes the main book area, the page areas, and all book tabs.
/// </summary>
private void ResetUIPositioning() {
// Reset the position of the button to make sure it updates with the screen res
BookArea.Left.Pixels = (Main.screenWidth / 2) - (BookArea.Width.Pixels / 2);
BookArea.Top.Pixels = (Main.screenHeight / 2) - (BookArea.Height.Pixels / 2) - 6;
PageOne.Left.Pixels = BookArea.Left.Pixels + 20;
PageOne.Top.Pixels = BookArea.Top.Pixels + 12;
PageTwo.Left.Pixels = BookArea.Left.Pixels - 15 + BookArea.Width.Pixels - PageTwo.Width.Pixels;
PageTwo.Top.Pixels = BookArea.Top.Pixels + 12;
int offsetY = 50;
// ToC/Filter Tab and Credits Tab never flips to the other side, just disappears when on said page
ToCTab.Top.Pixels = BookArea.Top.Pixels + offsetY;
IndicatorTab.Left.Pixels = PageTwo.Left.Pixels + PageTwo.Width.Pixels - 25 - IndicatorTab.Width.Pixels;
IndicatorTab.Top.Pixels = PageTwo.Top.Pixels - IndicatorTab.Height.Pixels - 6;
AltInteractionsTab.Left.Pixels = IndicatorTab.Left.Pixels - AltInteractionsTab.Width.Pixels - 2;
AltInteractionsTab.Top.Pixels = PageTwo.Top.Pixels - AltInteractionsTab.Height.Pixels - 6;
AltInteractionsTab.hoverText = GenerateInteractionHoverText();
UpdateFilterTabPos(false); // Update filter tab visibility
CreditsTab.Left.Pixels = BookArea.Left.Pixels + BookArea.Width.Pixels - 12;
CreditsTab.Top.Pixels = BookArea.Top.Pixels + offsetY + (BossTab.Height.Pixels * 4);
// Reset book tabs Y positioning after BookArea adjusted
BossTab.Top.Pixels = BookArea.Top.Pixels + offsetY + (BossTab.Height.Pixels * 1);
MiniBossTab.Top.Pixels = BookArea.Top.Pixels + offsetY + (BossTab.Height.Pixels * 2);
EventTab.Top.Pixels = BookArea.Top.Pixels + offsetY + (BossTab.Height.Pixels * 3);
CreditsTab.Top.Pixels = BookArea.Top.Pixels + offsetY + (BossTab.Height.Pixels * 4);
// Update the navigation tabs to the proper positions
BossTab.Left.Pixels = BookArea.Left.Pixels + (BossTab.OnLeftSide() ? -20 : BookArea.Width.Pixels - 12);
MiniBossTab.Left.Pixels = BookArea.Left.Pixels + (MiniBossTab.OnLeftSide() ? -20 : BookArea.Width.Pixels - 12);
EventTab.Left.Pixels = BookArea.Left.Pixels + (EventTab.OnLeftSide() ? -20 : BookArea.Width.Pixels - 12);
}
/// <summary>
/// Toggles the visibility state of the filter panel. This can occur when the tab is clicked or if the page is changed.
/// </summary>
/// <param name="tabClicked"></param>
private void UpdateFilterTabPos(bool tabClicked) {
if (tabClicked && PageNum != Page_TableOfContents)
return; // Filter tab position should not change if not on the Table of Contents page when clicked
if (HiddenEntriesMode)
return; // The Hidden List should be confirmed and closed before being able to close the filters tab
if (PageNum != Page_TableOfContents) {
filterOpen = false; // If the page is not on the Table of Contents, the filters tab should be in the closed position
}
else if (tabClicked) {
filterOpen = !filterOpen;
}
if (filterOpen) {
FilterPanel.Top.Pixels = ToCTab.Top.Pixels;
ToCTab.Left.Pixels = BookArea.Left.Pixels - 20 - FilterPanel.Width.Pixels;
FilterPanel.Left.Pixels = ToCTab.Left.Pixels + ToCTab.Width.Pixels;
FilterIcons.ForEach(x => x.UpdateFilterIcon()); // Update filter display state when the filter panel is opened
}
else {
ToCTab.Left.Pixels = BookArea.Left.Pixels - 20;
FilterPanel.Top.Pixels = -5000; // throw offscreen
}
}
/// <summary>
/// While in debug mode, users are able to reset their records of a specific boss by alt and right-clicking the recordnavigation button
/// </summary>
private void ResetStats() {
if (!BossChecklist.BossLogConfig.Debug.EnabledResetOptions || SelectedSubPage != SubPage.Records || GetPlayerRecords is null)
return; // must be on a valid record page and must have the reset records config enabled
if (!AltKeyIsDown)
return; // player must be holding alt
GetPlayerRecords.ResetStats(RecordSubCategory); // TODO: Fix this and make a reset for all records of a boss
RefreshPageContent(); // update page to show changes
}
private void RemovePlayerFromWorldRecord() {
// TODO: Localhost can remove players from record holders list
}
/// <summary>
/// While in debug mode, players will be able to remove obtained items from their player save data using the right-click button on the selected item slot.
/// </summary>
private void RemoveItem(UIMouseEvent evt, UIElement listeningElement) {
if (!BossChecklist.BossLogConfig.Debug.EnabledResetOptions || SelectedSubPage != SubPage.LootAndCollectibles)
return; // do not do anything if the loot page isn't active
if (listeningElement is not LogItemSlot slot || !AltKeyIsDown)
return; // player must be holding alt over an itemslot to remove items
// Note: items removed are removed from ALL boss loot pages retroactively
GetModPlayer.BossItemsCollected.Remove(new ItemDefinition(slot.item.type));
RefreshPageContent(); // update page to show changes
}
/// <summary>
/// Cycles through the spawn items on an entry.
/// Also cycles through recipes for spawn items.
/// </summary>
private void ChangeSpawnItem(UIMouseEvent evt, UIElement listeningElement) {
if (listeningElement is not NavigationalButton button)
return;
string id = button.Id;
if (id == "NextItem") {
SpawnItemSelected++;
RecipeSelected = 0;
}
else if (id == "PrevItem") {
SpawnItemSelected--;
RecipeSelected = 0;
}
else if (id.Contains("CycleItem")) {
int index = id.IndexOf('_');
if (RecipeSelected == Convert.ToInt32(id.Substring(index + 1)) - 1) {
RecipeSelected = 0;
}
else {
RecipeSelected++;
}
}
RefreshPageContent();
}
/// <summary>
/// Sets up the content needed for the Progression Mode prompt, including text boxes and buttons.
/// </summary>
private void OpenProgressionModePrompt() {
BossLogPageNumber = Page_Prompt; // make sure the page number is updated directly (using PageNum will trigger the page set up)
ResetUIPositioning(); // Updates ui elements and tabs to be properly positioned in relation the the new pagenum
PageOne.RemoveAllChildren(); // remove all content from both pages before appending new content for the prompt
PageTwo.RemoveAllChildren();
// create a text box for the progression mode description
FittedTextPanel textBox = new FittedTextPanel($"{LangLog}.ProgressionMode.Description");
textBox.Width.Pixels = PageOne.Width.Pixels - 30;
textBox.Height.Pixels = PageOne.Height.Pixels - 70;
textBox.Left.Pixels = 10;
textBox.Top.Pixels = 60;
PageOne.Append(textBox);
// create buttons for the different progression mode options
LogUIElement[] backdrops = new LogUIElement[] {
new LogUIElement(BossLogResources.Content_PromptSlot.Value),
new LogUIElement(BossLogResources.Content_PromptSlot.Value),
new LogUIElement(BossLogResources.Content_RecordSlot.Value)
};
backdrops[0].OnLeftClick += (a, b) => SelectProgressionModeState(true);
backdrops[1].OnLeftClick += (a, b) => SelectProgressionModeState(false);
backdrops[2].OnLeftClick += (a, b) => DisablePromptMessage();
foreach (LogUIElement backdrop in backdrops) {
backdrop.OnMouseOver += (a, b) => { backdrop.assetColor = BossChecklist.BossLogConfig.BossLogColor;};
backdrop.OnMouseOut += (a, b) => { backdrop.assetColor = Color.White; };
}
backdrops[0].Left.Pixels = PageTwo.Width.Pixels / 2 - BossLogResources.Content_PromptSlot.Value.Width - 10;
backdrops[1].Left.Pixels = PageTwo.Width.Pixels / 2 + 10;
backdrops[2].Left.Pixels = 25;
backdrops[0].Top.Pixels = 125;
backdrops[1].Top.Pixels = 125;
backdrops[2].Top.Pixels = 125 + BossLogResources.Content_PromptSlot.Value.Height + 25;
backdrops[0].hoverText = $"{LangLog}.ProgressionMode.SelectEnable";
backdrops[1].hoverText = $"{LangLog}.ProgressionMode.SelectDisable";
UIImage[] buttons = new UIImage[] {
new UIImage(BossLogResources.Content_ProgressiveOn),
new UIImage(BossLogResources.Content_ProgressiveOff),
new UIImage(BossLogResources.Check_Box)
};
buttons[0].Left.Pixels = backdrops[0].Width.Pixels / 2 - buttons[0].Width.Pixels / 2;
buttons[1].Left.Pixels = backdrops[1].Height.Pixels / 2 - buttons[1].Width.Pixels / 2;
buttons[2].Left.Pixels = 15;
buttons[0].Top.Pixels = backdrops[0].Height.Pixels / 2 - buttons[0].Height.Pixels / 2;
buttons[1].Top.Pixels = backdrops[1].Height.Pixels / 2 - buttons[1].Height.Pixels / 2;
buttons[2].Top.Pixels = backdrops[2].Height.Pixels / 2 - buttons[2].Height.Pixels / 2;
FittedTextPanel textOptions = new FittedTextPanel($"{LangLog}.ProgressionMode.DisablePrompt");
textOptions.Width.Pixels = backdrops[2].Width.Pixels - (buttons[2].Left.Pixels + buttons[2].Width.Pixels + 15);
textOptions.Height.Pixels = backdrops[2].Height.Pixels / 2;
textOptions.Left.Pixels = buttons[2].Left.Pixels + buttons[2].Width.Pixels;
textOptions.Top.Pixels = 5;
textOptions.PaddingTop = 0;
textOptions.PaddingLeft = 15;
backdrops[2].Append(textOptions);
PromptCheck = new UIImage(BossChecklist.BossLogConfig.PromptDisabled ? BossLogResources.Check_Check : BossLogResources.Check_X);
for (int i = 0; i < buttons.Length; i++) {
if (i == backdrops.Length - 1) {
buttons[i].Append(PromptCheck);
}
backdrops[i].Append(buttons[i]);
PageTwo.Append(backdrops[i]);
}
}
/// <summary>
/// Closes the UI and opens the configs to allow the player to customize the Progression Mode options to their liking.
/// </summary>
public void CloseAndConfigure() {
ToggleBossLog(false);
PageNum = Page_TableOfContents;
// A whole bunch of janky code to show the config and scroll down.
try {
IngameFancyUI.CoverNextFrame();
Main.playerInventory = false;
Main.editChest = false;
Main.npcChatText = "";
Main.inFancyUI = true;
var modConfigFieldInfo = Assembly.GetEntryAssembly().GetType("Terraria.ModLoader.UI.Interface").GetField("modConfig", BindingFlags.Static | BindingFlags.NonPublic);
var modConfig = (UIState)modConfigFieldInfo.GetValue(null);
Type UIModConfigType = Assembly.GetEntryAssembly().GetType("Terraria.ModLoader.Config.UI.UIModConfig");
var SetModMethodInfo = UIModConfigType.GetMethod("SetMod", BindingFlags.Instance | BindingFlags.NonPublic);
//Interface.modConfig.SetMod("BossChecklist", BossChecklist.BossLogConfig);
SetModMethodInfo.Invoke(modConfig, new object[] { BossChecklist.instance, BossChecklist.BossLogConfig });
Main.InGameUI.SetState(modConfig);
//private UIList mainConfigList;
//var mainConfigListFieldInfo = UIModConfigType.GetField("mainConfigList", BindingFlags.Instance | BindingFlags.NonPublic);
//UIList mainConfigList = (UIList)mainConfigListFieldInfo.GetValue(modConfig);
var uIScrollbarFieldInfo = UIModConfigType.GetField("uIScrollbar", BindingFlags.Instance | BindingFlags.NonPublic);
UIScrollbar uIScrollbar = (UIScrollbar)uIScrollbarFieldInfo.GetValue(modConfig);
uIScrollbar.GoToBottom();
//mainConfigList.Goto(delegate (UIElement element) {
// if(element is UISortableElement sortableElement && sortableElement.Children.FirstOrDefault() is Terraria.ModLoader.Config.UI.ConfigElement configElement) {
// return configElement.TextDisplayFunction().IndexOf("Test", StringComparison.OrdinalIgnoreCase) != -1;
// }
//});
}
catch (Exception) {
BossChecklist.instance.Logger.Warn("Force opening ModConfig menu failed, code update required");
}
}
/// <summary>
/// Fully enables or disables Progression Mode based on option selected and redirects the player to the Table of Contents.
/// </summary>
private void SelectProgressionModeState(bool enabled) {
BossChecklist.BossLogConfig.ProgressiveChecklist = enabled;
if (BossChecklist.BossLogConfig.ProgressiveChecklist)
BossChecklist.BossLogConfig.DrawNextMark = true;
PendingConfigChange = true; // save the option selected before proceeding
BossChecklist.BossLogConfig.UpdateIndicators();
PageNum = Page_TableOfContents; // switch page to Table of Contents when clicked
}
/// <summary>
/// Toggles whether the prompt will show on future characters. This can still be changed under the configs.
/// </summary>
private void DisablePromptMessage() {
BossChecklist.BossLogConfig.PromptDisabled = !BossChecklist.BossLogConfig.PromptDisabled;
PendingConfigChange = true;
PromptCheck.SetImage(BossChecklist.BossLogConfig.PromptDisabled ? BossLogResources.Check_Check : BossLogResources.Check_X);
}
/// <summary>
/// Handles the logic behind clicking the next/prev navigation buttons, and thus "turning the page".
/// </summary>
private void PageChangerClicked(UIMouseEvent evt, UIElement listeningElement) {
if (listeningElement is not NavigationalButton button)
return;
// Calculate what page the Log needs to update to
List<EntryInfo> BossList = BossChecklist.bossTracker.SortedEntries;
int NewPageValue = PageNum;
if (button.Id == "Next") {
NewPageValue = NewPageValue < BossList.Count - 1 ? NewPageValue + 1 : Page_Credits;
}
else {
NewPageValue = NewPageValue >= 0 ? NewPageValue - 1 : BossList.Count - 1;
}
// Figure out next page based on the new page number value
// If the page is hidden or unavailable, keep moving till its not or until page reaches the end
// Also check for "Only Bosses" navigation
while (NewPageValue >= 0) {
if (NewPageValue >= 0 && BossList[NewPageValue].VisibleOnPageContent())
break;
// same calulation as before, but repeated until a valid page is selected
if (button.Id == "Next") {
NewPageValue = NewPageValue < BossList.Count - 1 ? NewPageValue + 1 : Page_Credits;
}
else {
NewPageValue = NewPageValue >= 0 ? NewPageValue - 1 : BossList.Count - 1;
}
}
PageNum = NewPageValue; // Once a valid page is found, change the page.
}
/// <summary>
/// Updates desired page, subpage, and subcategory when called. Used for buttons that use navigation.
/// </summary>
/// <param name="pageNum">The page you want to switch to.</param>
/// <param name="subPage">The category page you want to set up, which includes record/event data, summoning info, and loot checklist.</param>
/// <param name="subCategory">The alternate category page you want to display. As of now this just applies for the record category page, which includes last attempt, first record, best record, and world record.</param>
private void UpdateSelectedPage(int pageNum, SubPage subPage, SubCategory subCategory = SubCategory.None) {
// Remove new records when navigating from a page with a new record
if (PageNum >= 0 && GetLogEntryInfo.IsRecordIndexed(out int recordIndex))
GetModPlayer.hasNewRecord[recordIndex] = false;
BossLogPageNumber = pageNum; // Directly change the BossLogPageNumber value in order to prevent an infinite loop
// Only on boss pages does updating the category page matter
if (PageNum >= 0) {
if (BossChecklist.BossLogConfig.ProgressiveChecklist && !GetLogEntryInfo.IsAutoDownedOrMarked && subPage == SubPage.LootAndCollectibles) {
// If Progressive Checklist is enabled, and the Loot subpage is trying to be accessed, deny it
// This can also happen if the loot page is already selected and a navigation button is used to access a locked page
if (SelectedSubPage == SubPage.LootAndCollectibles)
SelectedSubPage = SubPage.SpawnInfo;
}
else {
SelectedSubPage = subPage;
}
if (subCategory != SubCategory.None)
RecordSubCategory = subCategory;
}
ToCTab.Anchor = PageNum == Page_TableOfContents ? null : -1; // Update ToC/Filter tab anchor (and hover text)
RefreshPageContent();
}
/// <summary>
/// Restructures all page content and elements without changing the page or subpage.
/// </summary>
public void RefreshPageContent() {
if (PageNum == Page_Prompt) {
OpenProgressionModePrompt();
return; // If the page is somehow the prompt, redirect to the open prompt method
}
BossTab.Anchor = FindNextEntry(EntryType.Boss); // Updates the 'next' entries for the tabs and next checkmark
MiniBossTab.Anchor = FindNextEntry(EntryType.MiniBoss);
EventTab.Anchor = FindNextEntry(EntryType.Event);
ResetUIPositioning(); // Repositions common ui elements when the UI is updated
ResetBothPages(); // Reset the content of both pages before appending new content for the page
// Set up the designated page content
if (PageNum == Page_TableOfContents) {
UpdateTableofContents();
}
else if (PageNum == Page_Credits) {
UpdateCredits();
}
else {
if (SelectedSubPage == SubPage.Records) {
OpenRecord();
}
else if (SelectedSubPage == SubPage.SpawnInfo) {
OpenSpawn();
}
else if (SelectedSubPage == SubPage.LootAndCollectibles) {
OpenLoot();
}
}
}
/// <summary>
/// Updates the hovertext for the key combinations (ex. Alt+Right-Click element) that are available to the current page.
/// </summary>
private string GenerateInteractionHoverText() {
string interactions = null;
string HiddenTexts = LangLog + ".HintTexts";
if (PageNum == Page_TableOfContents) {
if (HiddenEntriesMode) {
interactions =
Language.GetTextValue($"{HiddenTexts}.HideEntry") +
(BossChecklist.BossLogConfig.Debug.EnabledResetOptions ? "\n" + Language.GetTextValue($"{HiddenTexts}.ClearHidden") : "");
}
else {
interactions =
Language.GetTextValue($"{HiddenTexts}.MarkEntry") +
(BossChecklist.BossLogConfig.Debug.EnabledResetOptions ? "\n" + Language.GetTextValue($"{HiddenTexts}.ClearMarked") : "");
}
}
else if (PageNum >= 0 && BossChecklist.BossLogConfig.Debug.EnabledResetOptions) {
if (SelectedSubPage == SubPage.Records && GetLogEntryInfo.type == EntryType.Boss) {
interactions =
Language.GetTextValue($"{HiddenTexts}.ClearAllRecords") + "\n" +
Language.GetTextValue($"{HiddenTexts}.ClearRecord");
}
else if (SelectedSubPage == SubPage.LootAndCollectibles) {
interactions =
Language.GetTextValue($"{HiddenTexts}.RemoveItem") + "\n" +
Language.GetTextValue($"{HiddenTexts}.ClearItems");
}
}
return BossChecklist.BossLogConfig.ShowInteractionTooltips ? interactions : null;
}
/// <summary>
/// Clears page content and replaces navigational elements.
/// </summary>
private void ResetBothPages() {
PageOne.RemoveAllChildren(); // remove all elements from the pages
PageTwo.RemoveAllChildren();
// Replace all of the page's navigational buttons
if (PageNum != Page_Credits) {
PageTwo.Append(NextPage); // Next page button can appear on any page except the Credits
}
if (PageNum != Page_TableOfContents) {
PageOne.Append(PrevPage); // Prev page button can appear on any page except the Table of Contents
}
if (PageNum >= 0) {
if (BossChecklist.BossLogConfig.Debug.AccessInternalNames && GetLogEntryInfo.modSource != "Unknown") {
NavigationalButton keyButton = new NavigationalButton(BossLogResources.Content_BossKey, true) {
Id = "CopyKey",
hoverText = $"{Language.GetTextValue($"{LangLog}.EntryPage.CopyKey")}:\n{GetLogEntryInfo.Key}"
};
keyButton.Left.Pixels = 5;
keyButton.Top.Pixels = 55;
PageOne.Append(keyButton);
}
// Entry pages need to have the category pages set up, but only for entries fully implemented
if (GetLogEntryInfo.modSource != "Unknown") {
PageTwo.Append(recordButton);
PageTwo.Append(spawnButton);
PageTwo.Append(lootButton);
lootButton.isLocked = !GetLogEntryInfo.IsAutoDownedOrMarked;
}
else {
// Old mod calls are no longer supported and will not add entries
// if somehow the entry has an unknown source, make a panel to show something went wrong
UIPanel brokenPanel = new UIPanel();
brokenPanel.Height.Pixels = 160;
brokenPanel.Width.Pixels = 340;
brokenPanel.Top.Pixels = 150;
brokenPanel.Left.Pixels = 3;
PageTwo.Append(brokenPanel);
FittedTextPanel brokenDisplay = new FittedTextPanel($"{LangLog}.EntryPage.LogFeaturesNotAvailable");
brokenDisplay.Height.Pixels = 200;
brokenDisplay.Width.Pixels = 340;
brokenDisplay.Top.Pixels = -12;
brokenDisplay.Left.Pixels = -15;
brokenPanel.Append(brokenDisplay);
}
}
}
/// <summary>
/// Sets up the content needed for the table of contents,
/// including the list of pre-hardmode and hardmode entries and
/// the progress bar of defeated entries.
/// </summary>
private void UpdateTableofContents() {
if (GetModPlayer.hasOpenedTheBossLog is false)
GetModPlayer.hasOpenedTheBossLog = true; // This will only ever happen once per character
prehardmodeList.Clear(); // clear both lists before setting up content
hardmodeList.Clear();
// Pre-Hard Mode List Title
string title = Language.GetTextValue($"{LangLog}.TableOfContents.PreHardmode");
PageOneTitle.SetText(title);
PageOneTitle.Left.Pixels = (int)((PageOne.Width.Pixels / 2) - (FontAssets.DeathText.Value.MeasureString(title).X * 0.6f / 2));
PageOne.Append(PageOneTitle);
// Hard Mode List Title
title = Language.GetTextValue($"{LangLog}.TableOfContents.Hardmode");
PageTwoTitle.SetText(title);
PageTwoTitle.Left.Pixels = (int)((PageTwo.Width.Pixels / 2) - (FontAssets.DeathText.Value.MeasureString(title).X * 0.6f / 2));
PageTwo.Append(PageTwoTitle);
foreach (EntryInfo entry in BossChecklist.bossTracker.SortedEntries) {
entry.hidden = WorldAssist.HiddenEntries.Contains(entry.Key);
if (!entry.VisibleOnChecklist())
continue; // If the boss should not be visible on the Table of Contents, skip the entry in the list
// Setup display name. Show "???" if unavailable and Silhouettes are turned on
string displayName = entry.DisplayName;
if (BossChecklist.BossLogConfig.ProgressiveChecklist && !entry.IsAutoDownedOrMarked && !entry.IsUpNext)
displayName = "???";
// The first entry that isnt downed to have a nextCheck will set off the next check for the rest
// Entries that ARE downed will still be green due to the ordering of colors within the draw method
// Update marked downs. If the boss is actually downed, remove the mark.
if (BossChecklist.BossLogConfig.AutomaticChecklist && entry.MarkedAsDowned) {
displayName += "*";
/*
if (WorldAssist.MarkedEntries.Contains(entry.Key) && entry.downed()) {
WorldAssist.MarkedEntries.Remove(entry.Key); // TODO: move this to a more apprpriate method
Networking.RequestMarkedEntryUpdate(entry.Key, entry.MarkedAsDowned);
}
*/
}
bool allLoot = BossChecklist.BossLogConfig.LootCheckVisibility;
bool allCollect = BossChecklist.BossLogConfig.LootCheckVisibility;
if (BossChecklist.BossLogConfig.LootCheckVisibility) {
// Loop through player saved loot and boss loot to see if every item was obtained
foreach (int loot in entry.lootItemTypes) {
if (loot == entry.TreasureBag)
continue;
int index = entry.loot.FindIndex(x => x.itemId == loot);
if (index != -1 && entry.loot[index].conditions is not null) {
bool isCorruptionLocked = WorldGen.crimson && entry.loot[index].conditions.Any(x => x is Conditions.IsCorruption || x is Conditions.IsCorruptionAndNotExpert);
bool isCrimsonLocked = !WorldGen.crimson && entry.loot[index].conditions.Any(x => x is Conditions.IsCrimson || x is Conditions.IsCrimsonAndNotExpert);
if (isCorruptionLocked || isCrimsonLocked)
continue; // Skips items that are dropped within the opposing world evil
}
Item checkItem = ContentSamples.ItemsByType[loot];
if (!Main.expertMode && (checkItem.expert || checkItem.expertOnly))
continue; // Skip items that are expert exclusive if not in an expert world