about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.md60
-rw-r--r--CMakeLists.txt11
-rw-r--r--README.md17
-rw-r--r--VERSION2
-rw-r--r--assets/subway.yaml117
-rw-r--r--src/ap_state.cpp13
-rw-r--r--src/ap_state.h2
-rw-r--r--src/area_popup.cpp48
-rw-r--r--src/game_data.cpp29
-rw-r--r--src/game_data.h10
-rw-r--r--src/godot_variant.cpp83
-rw-r--r--src/godot_variant.h28
-rw-r--r--src/network_set.cpp34
-rw-r--r--src/network_set.h16
-rw-r--r--src/subway_map.cpp136
-rw-r--r--src/tracker_frame.cpp36
-rw-r--r--src/tracker_frame.h2
-rw-r--r--src/tracker_panel.cpp62
-rw-r--r--src/tracker_panel.h19
-rw-r--r--src/tracker_state.cpp75
-rw-r--r--src/tracker_state.h2
-rw-r--r--src/version.h2
23 files changed, 676 insertions, 129 deletions
diff --git a/.gitignore b/.gitignore index a7cadc7..1ca77eb 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,5 +1,6 @@
1build/ 1build/
2builds/ 2builds/
3assets/LL1.yaml 3assets/LL1.yaml
4assets/ids.yaml
4.DS_Store 5.DS_Store
5.vs 6.vs
diff --git a/CHANGELOG.md b/CHANGELOG.md index cd237c6..256b63d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -1,5 +1,65 @@
1# lingo-ap-tracker Releases 1# lingo-ap-tracker Releases
2 2
3## v0.11.2 - 2024-09-24
4
5- One-way connections on the subway map are now indicated by a circle at the
6 exit. Contributed by art0007i.
7
8Download:
9[lingo-ap-tracker-v0.11.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.11.2-win64.zip)<br/>
10Source: [v0.11.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.11.2)
11
12## v0.11.1 - 2024-07-25
13
14- The Pilgrim Antechamber sunwarp on the subway map now shows all sunwarp
15 connections, and is red if a pilgrimage is not possible.
16- The save analysis panel now uses the remote location status for non-counting
17 panels.
18- Fixed positioning of Outside The Undeterred - Number Hunt door on subway map.
19- Fixed subway map issue when sunwarp shuffle and individual/progressive sunwarp
20 access were combined where the icons on the map would show unshuffled access.
21- Map area indicators now correctly treat unreachable pre-checked paintings as
22 unchecked.
23
24Download:
25[lingo-ap-tracker-v0.11.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.11.1-win64.zip)<br/>
26Source: [v0.11.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.11.1)
27
28## v0.11.0 - 2024-07-19
29
30- Added a savedata analyzer. When connected to a world, the user can open up the
31 Lingo save file associated with the connected world, and a new tab will open
32 up showing unsolved panels that are accessible, even if the world is not a
33 panelsanity world.
34
35Download:
36[lingo-ap-tracker-v0.11.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.11.0-win64.zip)<br/>
37Source: [v0.11.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.11.0)
38
39## v0.10.7 - 2024-07-17
40
41- Fixed issue with pilgrimage detection when sunwarps are shuffled where it
42 would expect you to use sunwarps mid-pilgrimage.
43- Fixed unreachable paintings sometimes being shown as already checked.
44
45Download:
46[lingo-ap-tracker-v0.10.7-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.7-win64.zip)<br/>
47Source: [v0.10.7](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.7)
48
49## v0.10.6 - 2024-07-16
50
51- The status bar now shows the name and server for the connected slot.
52- Fixed an issue with pilgrimage detection when paintings are shuffled and
53 paintings are not allowed for pilgrimage.
54- Fixed an issue with pilgrimage starting from the wrong place when sunwarps are
55 shuffled.
56- Fixed incorrect doors showing for sunwarps on subway map when sunwarps are
57 shuffled.
58
59Download:
60[lingo-ap-tracker-v0.10.6-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.6-win64.zip)<br/>
61Source: [v0.10.6](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.6)
62
3## v0.10.5 - 2024-07-12 63## v0.10.5 - 2024-07-12
4 64
5- Increased length of connection history to 10. 65- Increased length of connection history to 10.
diff --git a/CMakeLists.txt b/CMakeLists.txt index f9f1117..4eef9d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -48,8 +48,19 @@ add_executable(lingo_ap_tracker
48 "src/subway_map.cpp" 48 "src/subway_map.cpp"
49 "src/network_set.cpp" 49 "src/network_set.cpp"
50 "src/logger.cpp" 50 "src/logger.cpp"
51 "src/godot_variant.cpp"
51 "vendor/whereami/whereami.c" 52 "vendor/whereami/whereami.c"
52) 53)
53set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) 54set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20)
54set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) 55set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON)
55target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto websocketpp::websocketpp wx::core wx::base wx::net yaml-cpp::yaml-cpp) 56target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto websocketpp::websocketpp wx::core wx::base wx::net yaml-cpp::yaml-cpp)
57
58set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets")
59set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets")
60
61add_custom_target(copy_assets ALL
62 COMMAND ${CMAKE_COMMAND} -E copy_directory ${SRC_DIR} ${DST_DIR}
63 COMMENT "Copying folder from ${SRC_DIR} to ${DST_DIR}"
64)
65
66add_dependencies(lingo_ap_tracker copy_assets)
diff --git a/README.md b/README.md index 83525dd..1fbbc2f 100644 --- a/README.md +++ b/README.md
@@ -9,4 +9,19 @@ Releases of the tracker can be found [on the releases page](https://code.fourisl
9 9
10## Acknowledgments 10## Acknowledgments
11 11
12Thanks to Kinrah for making the subway map image! 12* Brenton Wildes: Created Lingo, and drew some of the images used in the tracker.
13* Kinrah: Made the subway map image.
14* art0007i: Contributed to the display of the subway map.
15
16## Building
17
18To build the app:
19
201. Clone the repository including submodules: `git clone --recursive https://code.fourisland.com/lingo-ap-tracker`
212. Put [LL1.yaml from archipelago](https://github.com/ArchipelagoMW/Archipelago/raw/main/worlds/lingo/data/LL1.yaml) in ./assets
223. Put [ids.yaml from archipelago](https://github.com/ArchipelagoMW/Archipelago/raw/main/worlds/lingo/data/ids.yaml) in ./assets
234. Configure the project: `cmake --preset=lingo-ap-tracker-preset`
245. Build the application in debug mode: `cmake --build --preset=lingo-ap-tracker-preset`
256. (Optional) Build the application in release mode: `cmake --build --preset=x64-release-preset`
26
27LL1.yaml and ids.yaml sometimes receive breaking changes that need to be kept in sync with the tracker. If the application crashes with an unknown error, try making sure that you are using the right versions of those files. In general, the main branch of the tracker will require config files from the latest Archipelago release. Branches may require config files from Archipelago main, or from pending pull requests.
diff --git a/VERSION b/VERSION index a6eeb03..1554d9b 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@
v0.10.5 \ No newline at end of file v0.11.2 \ No newline at end of file
diff --git a/assets/subway.yaml b/assets/subway.yaml index ede704c..87be8cc 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml
@@ -14,42 +14,42 @@
14 door: Painting Shortcut 14 door: Painting Shortcut
15 paintings: 15 paintings:
16 - garden_painting_tower2 16 - garden_painting_tower2
17 tags: 17 entrances:
18 - garden_starting 18 - garden_starting
19- pos: [1066, 841] 19- pos: [1066, 841]
20 room: Courtyard 20 room: Courtyard
21 door: Painting Shortcut 21 door: Painting Shortcut
22 paintings: 22 paintings:
23 - flower_painting_8 23 - flower_painting_8
24 tags: 24 entrances:
25 - flower_starting 25 - flower_starting
26- pos: [905, 895] 26- pos: [905, 895]
27 room: The Wondrous (Doorknob) 27 room: The Wondrous (Doorknob)
28 door: Painting Shortcut 28 door: Painting Shortcut
29 paintings: 29 paintings:
30 - symmetry_painting_a_starter 30 - symmetry_painting_a_starter
31 tags: 31 entrances:
32 - symmetry_starting 32 - symmetry_starting
33- pos: [1066, 868] 33- pos: [1066, 868]
34 room: Outside The Bold 34 room: Outside The Bold
35 door: Painting Shortcut 35 door: Painting Shortcut
36 paintings: 36 paintings:
37 - pencil_painting6 37 - pencil_painting6
38 tags: 38 entrances:
39 - pencil_starting 39 - pencil_starting
40- pos: [1066, 895] 40- pos: [1066, 895]
41 room: Outside The Undeterred 41 room: Outside The Undeterred
42 door: Painting Shortcut 42 door: Painting Shortcut
43 paintings: 43 paintings:
44 - blueman_painting_3 44 - blueman_painting_3
45 tags: 45 entrances:
46 - blueman_starting 46 - blueman_starting
47- pos: [905, 868] 47- pos: [905, 868]
48 room: Outside The Agreeable 48 room: Outside The Agreeable
49 door: Painting Shortcut 49 door: Painting Shortcut
50 paintings: 50 paintings:
51 - eyes_yellow_painting2 51 - eyes_yellow_painting2
52 tags: 52 entrances:
53 - street_starting 53 - street_starting
54- pos: [1211, 879] 54- pos: [1211, 879]
55 room: Hidden Room 55 room: Hidden Room
@@ -66,7 +66,7 @@
66- pos: [1116, 939] 66- pos: [1116, 939]
67 paintings: 67 paintings:
68 - owl_painting 68 - owl_painting
69 tags: 69 entrances:
70 - owl_hidden 70 - owl_hidden
71- pos: [986, 793] 71- pos: [986, 793]
72 room: Second Room 72 room: Second Room
@@ -89,7 +89,7 @@
89- pos: [1313, 686] 89- pos: [1313, 686]
90 paintings: 90 paintings:
91 - maze_painting 91 - maze_painting
92 tags: 92 exits:
93 - green_owl 93 - green_owl
94 - green_numbers 94 - green_numbers
95- pos: [1172, 760] 95- pos: [1172, 760]
@@ -108,7 +108,7 @@
108- pos: [1263, 867] 108- pos: [1263, 867]
109 paintings: 109 paintings:
110 - smile_painting_6 110 - smile_painting_6
111 tags: 111 entrances:
112 - smiley_deadend 112 - smiley_deadend
113- pos: [1012, 1086] 113- pos: [1012, 1086]
114 sunwarp: 114 sunwarp:
@@ -154,7 +154,7 @@
154- pos: [756, 400] 154- pos: [756, 400]
155 paintings: 155 paintings:
156 - smile_painting_4 156 - smile_painting_4
157 tags: 157 entrances:
158 - smiley_crossroads 158 - smiley_crossroads
159- pos: [878, 509] 159- pos: [878, 509]
160 sunwarp: 160 sunwarp:
@@ -208,7 +208,7 @@
208- pos: [1138, 287] 208- pos: [1138, 287]
209 paintings: 209 paintings:
210 - eyes_yellow_painting 210 - eyes_yellow_painting
211 tags: 211 exits:
212 - street_starting 212 - street_starting
213- pos: [1088, 385] 213- pos: [1088, 385]
214 sunwarp: 214 sunwarp:
@@ -220,7 +220,7 @@
220- pos: [1214, 457] 220- pos: [1214, 457]
221 paintings: 221 paintings:
222 - pencil_painting7 222 - pencil_painting7
223 tags: 223 entrances:
224 - pencil_compass 224 - pencil_compass
225- pos: [1196, 417] 225- pos: [1196, 417]
226 invisible: true 226 invisible: true
@@ -251,7 +251,7 @@
251- pos: [1477, 343] 251- pos: [1477, 343]
252 paintings: 252 paintings:
253 - garden_painting_tower 253 - garden_painting_tower
254 tags: 254 exits:
255 - garden_starting 255 - garden_starting
256- pos: [1565, 311] 256- pos: [1565, 311]
257 room: The Fearless (First Floor) 257 room: The Fearless (First Floor)
@@ -271,17 +271,17 @@
271- pos: [1784, 569] 271- pos: [1784, 569]
272 paintings: 272 paintings:
273 - crown_painting 273 - crown_painting
274 tags: 274 exits:
275 - crown_tower6 275 - crown_tower6
276- pos: [1653, 717] 276- pos: [1653, 717]
277 paintings: 277 paintings:
278 - eight_painting2 278 - eight_painting2
279 tags: 279 entrances:
280 - eight_alcove 280 - eight_alcove
281- pos: [1653, 662] 281- pos: [1653, 662]
282 paintings: 282 paintings:
283 - eight_painting 283 - eight_painting
284 tags: 284 exits:
285 - eight_alcove 285 - eight_alcove
286- pos: [697, 1471] 286- pos: [697, 1471]
287 room: Orange Tower 287 room: Orange Tower
@@ -402,7 +402,7 @@
402- pos: [844, 134] 402- pos: [844, 134]
403 paintings: 403 paintings:
404 - smile_painting_8 404 - smile_painting_8
405 tags: 405 entrances:
406 - smiley_hotcrusts 406 - smiley_hotcrusts
407- pos: [797, 155] 407- pos: [797, 155]
408 sunwarp: 408 sunwarp:
@@ -435,12 +435,13 @@
435 - colors_painting2 435 - colors_painting2
436 - cherry_painting2 436 - cherry_painting2
437 - hi_solved_painting 437 - hi_solved_painting
438 tags: 438 entrances:
439 - owl_tower6 439 - owl_tower6
440 - clock_tower6 440 - clock_tower6
441 - panda_tower6 441 - panda_tower6
442 - crown_tower6 442 - crown_tower6
443 - apple_tower6 443 - apple_tower6
444 exits:
444 - hi_scientific 445 - hi_scientific
445- pos: [349, 1124] 446- pos: [349, 1124]
446 paintings: 447 paintings:
@@ -457,7 +458,7 @@
457- pos: [556, 233] 458- pos: [556, 233]
458 paintings: 459 paintings:
459 - flower_painting_7 460 - flower_painting_7
460 tags: 461 exits:
461 - flower_starting 462 - flower_starting
462 - flower_arrow 463 - flower_arrow
463- pos: [600, 332] 464- pos: [600, 332]
@@ -466,7 +467,7 @@
466- pos: [579, 350] 467- pos: [579, 350]
467 paintings: 468 paintings:
468 - blueman_painting 469 - blueman_painting
469 tags: 470 entrances:
470 - blueman_courtyard 471 - blueman_courtyard
471- pos: [530, 310] 472- pos: [530, 310]
472 room: First Second Third Fourth 473 room: First Second Third Fourth
@@ -508,7 +509,7 @@
508 room: Welcome Back Area 509 room: Welcome Back Area
509 door: Shortcut to Starting Room 510 door: Shortcut to Starting Room
510- pos: [773, 954] 511- pos: [773, 954]
511 tags: 512 exits:
512 - hub_wb 513 - hub_wb
513 - wondrous_wb 514 - wondrous_wb
514 - undeterred_wb 515 - undeterred_wb
@@ -519,31 +520,31 @@
519 - scientific_wb 520 - scientific_wb
520 - cellar_wb 521 - cellar_wb
521- pos: [1107, 749] 522- pos: [1107, 749]
522 tags: 523 entrances:
523 - hub_wb 524 - hub_wb
524- pos: [408, 817] 525- pos: [408, 817]
525 tags: 526 entrances:
526 - wondrous_wb 527 - wondrous_wb
527- pos: [281, 1017] 528- pos: [281, 1017]
528 tags: 529 entrances:
529 - undeterred_wb 530 - undeterred_wb
530- pos: [1017, 289] 531- pos: [1017, 289]
531 tags: 532 entrances:
532 - agreeable_wb 533 - agreeable_wb
533- pos: [907, 1385] 534- pos: [907, 1385]
534 tags: 535 entrances:
535 - wanderer_wb 536 - wanderer_wb
536- pos: [1737, 1053] 537- pos: [1737, 1053]
537 tags: 538 entrances:
538 - gallery_wb 539 - gallery_wb
539- pos: [1690, 268] 540- pos: [1690, 268]
540 tags: 541 entrances:
541 - observant_wb 542 - observant_wb
542- pos: [250, 604] 543- pos: [250, 604]
543 tags: 544 entrances:
544 - scientific_wb 545 - scientific_wb
545- pos: [1553, 1467] 546- pos: [1553, 1467]
546 tags: 547 entrances:
547 - cellar_wb 548 - cellar_wb
548- pos: [1478, 498] 549- pos: [1478, 498]
549 room: Owl Hallway 550 room: Owl Hallway
@@ -554,8 +555,9 @@
554 - maze_painting_2 555 - maze_painting_2
555 - owl_painting_2 556 - owl_painting_2
556 - clock_painting_4 557 - clock_painting_4
557 tags: 558 entrances:
558 - green_owl 559 - green_owl
560 exits:
559 - owl_hidden 561 - owl_hidden
560 - owl_tower6 562 - owl_tower6
561- pos: [1478, 938] 563- pos: [1478, 938]
@@ -600,13 +602,13 @@
600- pos: [1530, 938] 602- pos: [1530, 938]
601 paintings: 603 paintings:
602 - clock_painting_5 604 - clock_painting_5
603 tags: 605 entrances:
604 - clock_initiated 606 - clock_initiated
605- pos: [1546, 938] 607- pos: [1546, 938]
606 paintings: 608 paintings:
607 - clock_painting_2 609 - clock_painting_2
608 - arrows_painting_2 610 - arrows_painting_2
609 tags: 611 exits:
610 - clock_tower6 612 - clock_tower6
611 - clock_initiated 613 - clock_initiated
612- pos: [1579, 813] 614- pos: [1579, 813]
@@ -616,7 +618,7 @@
616- pos: [1444, 896] 618- pos: [1444, 896]
617 paintings: 619 paintings:
618 - smile_painting_1 620 - smile_painting_1
619 tags: 621 entrances:
620 - smiley_initiated 622 - smiley_initiated
621- pos: [1430, 691] 623- pos: [1430, 691]
622 room: Outside The Undeterred 624 room: Outside The Undeterred
@@ -630,6 +632,7 @@
630 - blue_ch 632 - blue_ch
631 - yellow_ch 633 - yellow_ch
632 - green_ch 634 - green_ch
635 exits:
633 - early_ch 636 - early_ch
634- pos: [1567, 1264] 637- pos: [1567, 1264]
635 tags: 638 tags:
@@ -656,7 +659,7 @@
656 paintings: 659 paintings:
657 - pencil_painting2 660 - pencil_painting2
658 - north_missing2 661 - north_missing2
659 tags: 662 exits:
660 - pencil_compass 663 - pencil_compass
661 - pencil_starting 664 - pencil_starting
662 - pencil_directional 665 - pencil_directional
@@ -664,7 +667,7 @@
664 room: Outside The Bold 667 room: Outside The Bold
665 door: Steady Entrance 668 door: Steady Entrance
666- pos: [445, 1048] 669- pos: [445, 1048]
667 tags: 670 exits:
668 - undeterred_artistic 671 - undeterred_artistic
669- pos: [279, 1071] 672- pos: [279, 1071]
670 room: Number Hunt 673 room: Number Hunt
@@ -684,7 +687,7 @@
684- pos: [60, 1017] 687- pos: [60, 1017]
685 paintings: 688 paintings:
686 - blueman_painting_2 689 - blueman_painting_2
687 tags: 690 exits:
688 - blueman_courtyard 691 - blueman_courtyard
689 - blueman_starting 692 - blueman_starting
690- pos: [402, 1012] 693- pos: [402, 1012]
@@ -692,7 +695,7 @@
692 door: Green Painting 695 door: Green Painting
693 paintings: 696 paintings:
694 - maze_painting_3 697 - maze_painting_3
695 tags: 698 entrances:
696 - green_numbers 699 - green_numbers
697- pos: [134, 1007] 700- pos: [134, 1007]
698 sunwarp: 701 sunwarp:
@@ -701,7 +704,7 @@
701- pos: [719, 1039] 704- pos: [719, 1039]
702 room: Outside The Undeterred 705 room: Outside The Undeterred
703 door: Challenge Entrance 706 door: Challenge Entrance
704- pos: [438, 1039] 707- pos: [483, 1039]
705 room: Outside The Undeterred 708 room: Outside The Undeterred
706 door: Number Hunt 709 door: Number Hunt
707- pos: [563, 1071] 710- pos: [563, 1071]
@@ -727,7 +730,7 @@
727 door: Eights 730 door: Eights
728 paintings: 731 paintings:
729 - smile_painting_5 732 - smile_painting_5
730 tags: 733 entrances:
731 - smiley_numbers 734 - smiley_numbers
732- pos: [557, 953] 735- pos: [557, 953]
733 room: Number Hunt 736 room: Number Hunt
@@ -753,24 +756,24 @@
753- pos: [351, 927] 756- pos: [351, 927]
754 paintings: 757 paintings:
755 - boxes_painting 758 - boxes_painting
756 tags: 759 entrances:
757 - lattice_directional 760 - lattice_directional
758- pos: [272, 927] 761- pos: [272, 927]
759 paintings: 762 paintings:
760 - smile_painting_7 763 - smile_painting_7
761 tags: 764 entrances:
762 - smiley_directional 765 - smiley_directional
763- pos: [214, 822] 766- pos: [214, 822]
764 paintings: 767 paintings:
765 - cherry_painting 768 - cherry_painting
766 tags: 769 entrances:
767 - apple_directional 770 - apple_directional
768- pos: [266, 735] 771- pos: [266, 735]
769 room: Number Hunt 772 room: Number Hunt
770 door: Sixes 773 door: Sixes
771 paintings: 774 paintings:
772 - pencil_painting3 775 - pencil_painting3
773 tags: 776 entrances:
774 - pencil_directional 777 - pencil_directional
775- pos: [215, 735] 778- pos: [215, 735]
776 paintings: 779 paintings:
@@ -838,7 +841,7 @@
838- pos: [1653, 101] 841- pos: [1653, 101]
839 paintings: 842 paintings:
840 - smile_painting_9 843 - smile_painting_9
841 tags: 844 exits:
842 - smiley_crossroads 845 - smiley_crossroads
843 - smiley_deadend 846 - smiley_deadend
844 - smiley_hotcrusts 847 - smiley_hotcrusts
@@ -851,12 +854,12 @@
851 room: The Artistic (Smiley) 854 room: The Artistic (Smiley)
852 door: Door to Panda 855 door: Door to Panda
853- pos: [1711, 140] 856- pos: [1711, 140]
854 tags: 857 entrances:
855 - undeterred_artistic 858 - undeterred_artistic
856- pos: [1653, 169] 859- pos: [1653, 169]
857 paintings: 860 paintings:
858 - panda_painting_3 861 - panda_painting_3
859 tags: 862 exits:
860 - panda_tower6 863 - panda_tower6
861 - panda_hallway 864 - panda_hallway
862- pos: [1708, 171] 865- pos: [1708, 171]
@@ -865,7 +868,7 @@
865- pos: [1761, 169] 868- pos: [1761, 169]
866 paintings: 869 paintings:
867 - boxes_painting2 870 - boxes_painting2
868 tags: 871 exits:
869 - lattice_directional 872 - lattice_directional
870- pos: [1762, 139] 873- pos: [1762, 139]
871 room: The Artistic (Lattice) 874 room: The Artistic (Lattice)
@@ -873,7 +876,7 @@
873- pos: [1761, 101] 876- pos: [1761, 101]
874 paintings: 877 paintings:
875 - cherry_painting3 878 - cherry_painting3
876 tags: 879 exits:
877 - apple_tower6 880 - apple_tower6
878 - apple_directional 881 - apple_directional
879- pos: [1708, 107] 882- pos: [1708, 107]
@@ -886,7 +889,7 @@
886 paintings: 889 paintings:
887 - eye_painting_2 890 - eye_painting_2
888 - smile_painting_2 891 - smile_painting_2
889 tags: 892 entrances:
890 - smiley_theysee 893 - smiley_theysee
891- pos: [310, 750] 894- pos: [310, 750]
892 room: The Eyes They See 895 room: The Eyes They See
@@ -908,7 +911,7 @@
908 - symmetry_painting_b_2 911 - symmetry_painting_b_2
909 - symmetry_painting_a_6 912 - symmetry_painting_a_6
910 - symmetry_painting_b_6 913 - symmetry_painting_b_6
911 tags: 914 exits:
912 - symmetry_starting 915 - symmetry_starting
913- pos: [407, 755] 916- pos: [407, 755]
914 room: The Wondrous 917 room: The Wondrous
@@ -918,12 +921,12 @@
918- pos: [449, 755] 921- pos: [449, 755]
919 paintings: 922 paintings:
920 - flower_painting_6 923 - flower_painting_6
921 tags: 924 entrances:
922 - flower_arrow 925 - flower_arrow
923- pos: [1101, 222] 926- pos: [1101, 222]
924 paintings: 927 paintings:
925 - panda_painting 928 - panda_painting
926 tags: 929 entrances:
927 - panda_hallway 930 - panda_hallway
928- pos: [1152, 209] 931- pos: [1152, 209]
929 room: Hallway Room (1) 932 room: Hallway Room (1)
@@ -977,16 +980,16 @@
977 - scenery_painting_0a 980 - scenery_painting_0a
978 - map_painting 981 - map_painting
979 - fruitbowl_painting4 982 - fruitbowl_painting4
980 tags: 983 entrances:
981 - smiley_gallery 984 - smiley_gallery
982- pos: [1120, 1286] 985- pos: [1120, 1286]
983 room: Rhyme Room (Smiley) 986 room: Rhyme Room (Smiley)
984 door: Door to Target 987 door: Door to Target
985- pos: [1120, 1315] 988- pos: [1120, 1315]
986 tags: 989 entrances: # this could be considered 2 way since the subway map has a one way gate at the exit anyway
987 - rhyme_smiley_target 990 - rhyme_smiley_target
988- pos: [792, 1137] 991- pos: [792, 1137]
989 tags: 992 exits:
990 - rhyme_smiley_target 993 - rhyme_smiley_target
991- pos: [895, 1217] 994- pos: [895, 1217]
992 room: Number Hunt 995 room: Number Hunt
@@ -1030,7 +1033,7 @@
1030- pos: [294, 602] 1033- pos: [294, 602]
1031 paintings: 1034 paintings:
1032 - hi_solved_painting4 1035 - hi_solved_painting4
1033 tags: 1036 entrances:
1034 - hi_scientific 1037 - hi_scientific
1035- pos: [814, 1001] 1038- pos: [814, 1001]
1036 room: Challenge Room 1039 room: Challenge Room
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 876fdd8..f8d4ee0 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -52,6 +52,8 @@ struct APState {
52 std::list<std::string> tracked_data_storage_keys; 52 std::list<std::string> tracked_data_storage_keys;
53 std::string victory_data_storage_key; 53 std::string victory_data_storage_key;
54 54
55 std::string save_name;
56
55 std::map<int64_t, int> inventory; 57 std::map<int64_t, int> inventory;
56 std::set<int64_t> checked_locations; 58 std::set<int64_t> checked_locations;
57 std::map<std::string, std::any> data_storage; 59 std::map<std::string, std::any> data_storage;
@@ -131,6 +133,7 @@ struct APState {
131 cert_store); 133 cert_store);
132 } 134 }
133 135
136 save_name.clear();
134 inventory.clear(); 137 inventory.clear();
135 checked_locations.clear(); 138 checked_locations.clear();
136 data_storage.clear(); 139 data_storage.clear();
@@ -221,11 +224,15 @@ struct APState {
221 RefreshTracker(false); 224 RefreshTracker(false);
222 }); 225 });
223 226
224 apclient->set_slot_connected_handler([this, &connection_mutex]( 227 apclient->set_slot_connected_handler([this, player, server,
228 &connection_mutex](
225 const nlohmann::json& slot_data) { 229 const nlohmann::json& slot_data) {
226 tracker_frame->SetStatusMessage("Connected to Archipelago!"); 230 tracker_frame->SetStatusMessage(
231 fmt::format("Connected to Archipelago! ({}@{})", player, server));
227 TrackerLog("Connected to Archipelago!"); 232 TrackerLog("Connected to Archipelago!");
228 233
234 save_name = fmt::format("zzAP_{}_{}.save", apclient->get_seed(),
235 apclient->get_player_number());
229 data_storage_prefix = 236 data_storage_prefix =
230 fmt::format("Lingo_{}_", apclient->get_player_number()); 237 fmt::format("Lingo_{}_", apclient->get_player_number());
231 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); 238 door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>();
@@ -507,6 +514,8 @@ void AP_Connect(std::string server, std::string player, std::string password) {
507 GetState().Connect(server, player, password); 514 GetState().Connect(server, player, password);
508} 515}
509 516
517std::string AP_GetSaveName() { return GetState().save_name; }
518
510bool AP_HasCheckedGameLocation(int location_id) { 519bool AP_HasCheckedGameLocation(int location_id) {
511 return GetState().HasCheckedGameLocation(location_id); 520 return GetState().HasCheckedGameLocation(location_id);
512} 521}
diff --git a/src/ap_state.h b/src/ap_state.h index 7af7395..f8936e5 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -43,6 +43,8 @@ void AP_SetTrackerFrame(TrackerFrame* tracker_frame);
43 43
44void AP_Connect(std::string server, std::string player, std::string password); 44void AP_Connect(std::string server, std::string player, std::string password);
45 45
46std::string AP_GetSaveName();
47
46bool AP_HasCheckedGameLocation(int location_id); 48bool AP_HasCheckedGameLocation(int location_id);
47 49
48bool AP_HasCheckedHuntPanel(int location_id); 50bool AP_HasCheckedHuntPanel(int location_id);
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 58d8897..8d6487e 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp
@@ -2,10 +2,13 @@
2 2
3#include <wx/dcbuffer.h> 3#include <wx/dcbuffer.h>
4 4
5#include <algorithm>
6
5#include "ap_state.h" 7#include "ap_state.h"
6#include "game_data.h" 8#include "game_data.h"
7#include "global.h" 9#include "global.h"
8#include "tracker_config.h" 10#include "tracker_config.h"
11#include "tracker_panel.h"
9#include "tracker_state.h" 12#include "tracker_state.h"
10 13
11AreaPopup::AreaPopup(wxWindow* parent, int area_id) 14AreaPopup::AreaPopup(wxWindow* parent, int area_id)
@@ -43,15 +46,23 @@ void AreaPopup::UpdateIndicators() {
43 46
44 mem_dc.SetFont(GetFont()); 47 mem_dc.SetFont(GetFont());
45 48
49 TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent());
50
46 std::vector<int> real_locations; 51 std::vector<int> real_locations;
47 52
48 for (int section_id = 0; section_id < map_area.locations.size(); 53 for (int section_id = 0; section_id < map_area.locations.size();
49 section_id++) { 54 section_id++) {
50 const Location& location = map_area.locations.at(section_id); 55 const Location& location = map_area.locations.at(section_id);
51 56
52 if (!AP_IsLocationVisible(location.classification) && 57 if (tracker_panel->IsPanelsMode()) {
53 !(location.hunt && GetTrackerConfig().show_hunt_panels)) { 58 if (!location.single_panel) {
54 continue; 59 continue;
60 }
61 } else {
62 if (!AP_IsLocationVisible(location.classification) &&
63 !(location.hunt && GetTrackerConfig().show_hunt_panels)) {
64 continue;
65 }
55 } 66 }
56 67
57 real_locations.push_back(section_id); 68 real_locations.push_back(section_id);
@@ -65,7 +76,7 @@ void AreaPopup::UpdateIndicators() {
65 } 76 }
66 } 77 }
67 78
68 if (AP_IsPaintingShuffle()) { 79 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
69 for (int painting_id : map_area.paintings) { 80 for (int painting_id : map_area.paintings) {
70 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 81 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
71 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. 82 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name.
@@ -102,10 +113,21 @@ void AreaPopup::UpdateIndicators() {
102 for (int section_id : real_locations) { 113 for (int section_id : real_locations) {
103 const Location& location = map_area.locations.at(section_id); 114 const Location& location = map_area.locations.at(section_id);
104 115
105 bool checked = 116 bool checked = false;
106 AP_HasCheckedGameLocation(location.ap_location_id) || 117 if (IsLocationWinCondition(location)) {
107 (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id)) || 118 checked = AP_HasReachedGoal();
108 (IsLocationWinCondition(location) && AP_HasReachedGoal()); 119 } else if (tracker_panel->IsPanelsMode()) {
120 const Panel& panel = GD_GetPanel(*location.single_panel);
121 if (panel.non_counting) {
122 checked = AP_HasCheckedGameLocation(location.ap_location_id);
123 } else {
124 checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath);
125 }
126 } else {
127 checked =
128 AP_HasCheckedGameLocation(location.ap_location_id) ||
129 (location.hunt && AP_HasCheckedHuntPanel(location.ap_location_id));
130 }
109 131
110 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; 132 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
111 133
@@ -123,18 +145,18 @@ void AreaPopup::UpdateIndicators() {
123 cur_height += 10 + 32; 145 cur_height += 10 + 32;
124 } 146 }
125 147
126 if (AP_IsPaintingShuffle()) { 148 if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) {
127 for (int painting_id : map_area.paintings) { 149 for (int painting_id : map_area.paintings) {
128 const PaintingExit& painting = GD_GetPaintingExit(painting_id); 150 const PaintingExit& painting = GD_GetPaintingExit(painting_id);
129 bool checked = AP_IsPaintingChecked(painting.internal_id);
130 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
131
132 mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});
133 151
134 bool reachable = IsPaintingReachable(painting_id); 152 bool reachable = IsPaintingReachable(painting_id);
135 const wxColour* text_color = reachable ? wxWHITE : wxRED; 153 const wxColour* text_color = reachable ? wxWHITE : wxRED;
136 mem_dc.SetTextForeground(*text_color); 154 mem_dc.SetTextForeground(*text_color);
137 155
156 bool checked = reachable && AP_IsPaintingChecked(painting.internal_id);
157 wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_;
158 mem_dc.DrawBitmap(*eye_ptr, {10, cur_height});
159
138 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. 160 wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name.
139 mem_dc.DrawText(painting.internal_id, 161 mem_dc.DrawText(painting.internal_id,
140 {10 + 32 + 10, 162 {10 + 32 + 10,
diff --git a/src/game_data.cpp b/src/game_data.cpp index e75170e..7b805df 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp
@@ -109,6 +109,7 @@ struct GameData {
109 auto process_single_entrance = 109 auto process_single_entrance =
110 [this, room_id, from_room_id](const YAML::Node &option) { 110 [this, room_id, from_room_id](const YAML::Node &option) {
111 Exit exit_obj; 111 Exit exit_obj;
112 exit_obj.source_room = from_room_id;
112 exit_obj.destination_room = room_id; 113 exit_obj.destination_room = room_id;
113 114
114 if (option["door"]) { 115 if (option["door"]) {
@@ -143,7 +144,7 @@ struct GameData {
143 switch (entrance_it.second.Type()) { 144 switch (entrance_it.second.Type()) {
144 case YAML::NodeType::Scalar: { 145 case YAML::NodeType::Scalar: {
145 // This is just "true". 146 // This is just "true".
146 rooms_[from_room_id].exits.push_back({.destination_room = room_id}); 147 rooms_[from_room_id].exits.push_back({.source_room = from_room_id, .destination_room = room_id});
147 break; 148 break;
148 } 149 }
149 case YAML::NodeType::Map: { 150 case YAML::NodeType::Map: {
@@ -264,6 +265,11 @@ struct GameData {
264 panel_it.second["location_name"].as<std::string>(); 265 panel_it.second["location_name"].as<std::string>();
265 } 266 }
266 267
268 if (panel_it.second["id"]) {
269 panels_[panel_id].nodepath =
270 panel_it.second["id"].as<std::string>();
271 }
272
267 if (panel_it.second["hunt"]) { 273 if (panel_it.second["hunt"]) {
268 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); 274 panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>();
269 } 275 }
@@ -563,7 +569,8 @@ struct GameData {
563 .room = panel.room, 569 .room = panel.room,
564 .panels = {panel.id}, 570 .panels = {panel.id},
565 .classification = classification, 571 .classification = classification,
566 .hunt = panel.hunt}); 572 .hunt = panel.hunt,
573 .single_panel = panel.id});
567 locations_by_name[location_name] = {area_id, 574 locations_by_name[location_name] = {area_id,
568 map_area.locations.size() - 1}; 575 map_area.locations.size() - 1};
569 } 576 }
@@ -616,6 +623,7 @@ struct GameData {
616 for (const Location &location : map_area.locations) { 623 for (const Location &location : map_area.locations) {
617 map_area.classification |= location.classification; 624 map_area.classification |= location.classification;
618 map_area.hunt |= location.hunt; 625 map_area.hunt |= location.hunt;
626 map_area.has_single_panel |= location.single_panel.has_value();
619 } 627 }
620 } 628 }
621 629
@@ -673,6 +681,18 @@ struct GameData {
673 } 681 }
674 } 682 }
675 683
684 if (subway_it["entrances"]) {
685 for (const auto &entrance_it : subway_it["entrances"]) {
686 subway_item.entrances.push_back(entrance_it.as<std::string>());
687 }
688 }
689
690 if (subway_it["exits"]) {
691 for (const auto &exit_it : subway_it["exits"]) {
692 subway_item.exits.push_back(exit_it.as<std::string>());
693 }
694 }
695
676 if (subway_it["sunwarp"]) { 696 if (subway_it["sunwarp"]) {
677 SubwaySunwarp sunwarp; 697 SubwaySunwarp sunwarp;
678 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); 698 sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>();
@@ -784,6 +804,11 @@ GameData &GetState() {
784 804
785} // namespace 805} // namespace
786 806
807bool SubwayItem::HasWarps() const {
808 return !(this->tags.empty() && this->entrances.empty() &&
809 this->exits.empty());
810}
811
787bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { 812bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const {
788 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); 813 return std::tie(dots, type) < std::tie(rhs.dots, rhs.type);
789} 814}
diff --git a/src/game_data.h b/src/game_data.h index b787e6f..1f6d247 100644 --- a/src/game_data.h +++ b/src/game_data.h
@@ -43,6 +43,7 @@ struct Panel {
43 int id; 43 int id;
44 int room; 44 int room;
45 std::string name; 45 std::string name;
46 std::string nodepath;
46 std::vector<LingoColor> colors; 47 std::vector<LingoColor> colors;
47 std::vector<int> required_rooms; 48 std::vector<int> required_rooms;
48 std::vector<int> required_doors; 49 std::vector<int> required_doors;
@@ -83,6 +84,7 @@ struct Door {
83}; 84};
84 85
85struct Exit { 86struct Exit {
87 int source_room;
86 int destination_room; 88 int destination_room;
87 std::optional<int> door; 89 std::optional<int> door;
88 EntranceType type = EntranceType::kNormal; 90 EntranceType type = EntranceType::kNormal;
@@ -112,6 +114,7 @@ struct Location {
112 std::vector<int> panels; 114 std::vector<int> panels;
113 int classification = 0; 115 int classification = 0;
114 bool hunt = false; 116 bool hunt = false;
117 std::optional<int> single_panel;
115}; 118};
116 119
117struct MapArea { 120struct MapArea {
@@ -123,6 +126,7 @@ struct MapArea {
123 int map_y; 126 int map_y;
124 int classification = 0; 127 int classification = 0;
125 bool hunt = false; 128 bool hunt = false;
129 bool has_single_panel = false;
126}; 130};
127 131
128enum class SubwaySunwarpType { 132enum class SubwaySunwarpType {
@@ -144,9 +148,13 @@ struct SubwayItem {
144 int y; 148 int y;
145 std::optional<int> door; 149 std::optional<int> door;
146 std::vector<std::string> paintings; 150 std::vector<std::string> paintings;
147 std::vector<std::string> tags; 151 std::vector<std::string> tags; // 2-way teleports
152 std::vector<std::string> entrances; // teleport entrances
153 std::vector<std::string> exits; // teleport exits
148 std::optional<SubwaySunwarp> sunwarp; 154 std::optional<SubwaySunwarp> sunwarp;
149 std::optional<std::string> special; 155 std::optional<std::string> special;
156
157 bool HasWarps() const;
150}; 158};
151 159
152const std::vector<MapArea>& GD_GetMapAreas(); 160const std::vector<MapArea>& GD_GetMapAreas();
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp new file mode 100644 index 0000000..1bc906f --- /dev/null +++ b/src/godot_variant.cpp
@@ -0,0 +1,83 @@
1// Godot save decoder algorithm by Chris Souvey.
2
3#include "godot_variant.h"
4
5#include <algorithm>
6#include <charconv>
7#include <cstddef>
8#include <fstream>
9#include <string>
10#include <tuple>
11#include <variant>
12#include <vector>
13
14namespace {
15
16uint16_t ReadUint16(std::basic_istream<char>& stream) {
17 uint16_t result;
18 stream.read(reinterpret_cast<char*>(&result), 2);
19 return result;
20}
21
22uint32_t ReadUint32(std::basic_istream<char>& stream) {
23 uint32_t result;
24 stream.read(reinterpret_cast<char*>(&result), 4);
25 return result;
26}
27
28GodotVariant ParseVariant(std::basic_istream<char>& stream) {
29 uint16_t type = ReadUint16(stream);
30 stream.ignore(2);
31
32 switch (type) {
33 case 1: {
34 // bool
35 bool boolval = (ReadUint32(stream) == 1);
36 return {boolval};
37 }
38 case 15: {
39 // nodepath
40 uint32_t name_length = ReadUint32(stream) & 0x7fffffff;
41 uint32_t subname_length = ReadUint32(stream) & 0x7fffffff;
42 uint32_t flags = ReadUint32(stream);
43
44 std::vector<std::string> result;
45 for (size_t i = 0; i < name_length + subname_length; i++) {
46 uint32_t char_length = ReadUint32(stream);
47 uint32_t padded_length = (char_length % 4 == 0)
48 ? char_length
49 : (char_length + 4 - (char_length % 4));
50 std::vector<char> next_bytes(padded_length);
51 stream.read(next_bytes.data(), padded_length);
52 std::string next_piece;
53 std::copy(next_bytes.begin(),
54 std::next(next_bytes.begin(), char_length),
55 std::back_inserter(next_piece));
56 result.push_back(next_piece);
57 }
58
59 return {result};
60 }
61 case 19: {
62 // array
63 uint32_t length = ReadUint32(stream) & 0x7fffffff;
64 std::vector<GodotVariant> result;
65 for (size_t i = 0; i < length; i++) {
66 result.push_back(ParseVariant(stream));
67 }
68 return {result};
69 }
70 default: {
71 // eh
72 return {std::monostate{}};
73 }
74 }
75}
76
77} // namespace
78
79GodotVariant ParseGodotFile(std::string filename) {
80 std::ifstream file_stream(filename, std::ios_base::binary);
81 file_stream.ignore(4);
82 return ParseVariant(file_stream);
83}
diff --git a/src/godot_variant.h b/src/godot_variant.h new file mode 100644 index 0000000..620e569 --- /dev/null +++ b/src/godot_variant.h
@@ -0,0 +1,28 @@
1#ifndef GODOT_VARIANT_H_ED7F2EB6
2#define GODOT_VARIANT_H_ED7F2EB6
3
4#include <string>
5#include <variant>
6#include <vector>
7
8struct GodotVariant {
9 using value_type = std::variant<std::monostate, bool, std::vector<std::string>, std::vector<GodotVariant>>;
10
11 value_type value;
12
13 GodotVariant(value_type v) : value(v) {}
14
15 bool AsBool() const { return std::get<bool>(value); }
16
17 const std::vector<std::string>& AsNodePath() const {
18 return std::get<std::vector<std::string>>(value);
19 }
20
21 const std::vector<GodotVariant>& AsArray() const {
22 return std::get<std::vector<GodotVariant>>(value);
23 }
24};
25
26GodotVariant ParseGodotFile(std::string filename);
27
28#endif /* end of include guard: GODOT_VARIANT_H_ED7F2EB6 */
diff --git a/src/network_set.cpp b/src/network_set.cpp index 6d2a098..45911e3 100644 --- a/src/network_set.cpp +++ b/src/network_set.cpp
@@ -4,9 +4,8 @@ void NetworkSet::Clear() {
4 network_by_item_.clear(); 4 network_by_item_.clear();
5} 5}
6 6
7void NetworkSet::AddLink(int id1, int id2) { 7void NetworkSet::AddLink(int id1, int id2, bool two_way) {
8 if (id2 > id1) { 8 if (two_way && id2 > id1) {
9 // Make sure id1 < id2
10 std::swap(id1, id2); 9 std::swap(id1, id2);
11 } 10 }
12 11
@@ -17,14 +16,37 @@ void NetworkSet::AddLink(int id1, int id2) {
17 network_by_item_[id2] = {}; 16 network_by_item_[id2] = {};
18 } 17 }
19 18
20 network_by_item_[id1].insert({id1, id2}); 19 NetworkNode node = {id1, id2, two_way};
21 network_by_item_[id2].insert({id1, id2}); 20
21 network_by_item_[id1].insert(node);
22 network_by_item_[id2].insert(node);
23}
24
25void NetworkSet::AddLinkToNetwork(int network_id, int id1, int id2, bool two_way) {
26 if (two_way && id2 > id1) {
27 std::swap(id1, id2);
28 }
29
30 if (!network_by_item_.count(network_id)) {
31 network_by_item_[network_id] = {};
32 }
33
34 NetworkNode node = {id1, id2, two_way};
35
36 network_by_item_[network_id].insert(node);
22} 37}
23 38
24bool NetworkSet::IsItemInNetwork(int id) const { 39bool NetworkSet::IsItemInNetwork(int id) const {
25 return network_by_item_.count(id); 40 return network_by_item_.count(id);
26} 41}
27 42
28const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { 43const std::set<NetworkNode>& NetworkSet::GetNetworkGraph(int id) const {
29 return network_by_item_.at(id); 44 return network_by_item_.at(id);
30} 45}
46
47bool NetworkNode::operator<(const NetworkNode& rhs) const {
48 if (entry != rhs.entry) return entry < rhs.entry;
49 if (exit != rhs.exit) return exit < rhs.exit;
50 if (two_way != rhs.two_way) return two_way < rhs.two_way;
51 return false;
52}
diff --git a/src/network_set.h b/src/network_set.h index e6f0c07..0f72052 100644 --- a/src/network_set.h +++ b/src/network_set.h
@@ -7,19 +7,29 @@
7#include <utility> 7#include <utility>
8#include <vector> 8#include <vector>
9 9
10struct NetworkNode {
11 int entry;
12 int exit;
13 bool two_way;
14
15 bool operator<(const NetworkNode& rhs) const;
16};
17
10class NetworkSet { 18class NetworkSet {
11 public: 19 public:
12 void Clear(); 20 void Clear();
13 21
14 void AddLink(int id1, int id2); 22 void AddLink(int id1, int id2, bool two_way);
23
24 void AddLinkToNetwork(int network_id, int id1, int id2, bool two_way);
15 25
16 bool IsItemInNetwork(int id) const; 26 bool IsItemInNetwork(int id) const;
17 27
18 const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; 28 const std::set<NetworkNode>& GetNetworkGraph(int id) const;
19 29
20 private: 30 private:
21 31
22 std::map<int, std::set<std::pair<int, int>>> network_by_item_; 32 std::map<int, std::set<NetworkNode>> network_by_item_;
23}; 33};
24 34
25#endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ 35#endif /* end of include guard: NETWORK_SET_H_3036B8E3 */
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index e3b844d..f896693 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp
@@ -16,6 +16,28 @@ constexpr int OWL_ACTUAL_SIZE = 32;
16 16
17enum class ItemDrawType { kNone, kBox, kOwl }; 17enum class ItemDrawType { kNone, kBox, kOwl };
18 18
19namespace {
20
21std::optional<int> GetRealSubwayDoor(const SubwayItem subway_item) {
22 if (AP_IsSunwarpShuffle() && subway_item.sunwarp &&
23 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) {
24 int sunwarp_index = subway_item.sunwarp->dots - 1;
25 if (subway_item.sunwarp->type == SubwaySunwarpType::kExit) {
26 sunwarp_index += 6;
27 }
28
29 for (const auto &[start_index, mapping] : AP_GetSunwarpMapping()) {
30 if (start_index == sunwarp_index || mapping.exit_index == sunwarp_index) {
31 return GD_GetSunwarpDoors().at(mapping.dots - 1);
32 }
33 }
34 }
35
36 return subway_item.door;
37}
38
39} // namespace
40
19SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { 41SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
20 SetBackgroundStyle(wxBG_STYLE_PAINT); 42 SetBackgroundStyle(wxBG_STYLE_PAINT);
21 43
@@ -69,10 +91,12 @@ void SubwayMap::OnConnect() {
69 networks_.Clear(); 91 networks_.Clear();
70 92
71 std::map<std::string, std::vector<int>> tagged; 93 std::map<std::string, std::vector<int>> tagged;
94 std::map<std::string, std::vector<int>> entrances;
95 std::map<std::string, std::vector<int>> exits;
72 for (const SubwayItem &subway_item : GD_GetSubwayItems()) { 96 for (const SubwayItem &subway_item : GD_GetSubwayItems()) {
73 if (AP_HasEarlyColorHallways() && 97 if (AP_HasEarlyColorHallways() &&
74 subway_item.special == "starting_room_paintings") { 98 subway_item.special == "starting_room_paintings") {
75 tagged["early_ch"].push_back(subway_item.id); 99 entrances["early_ch"].push_back(subway_item.id);
76 } 100 }
77 101
78 if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { 102 if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) {
@@ -82,21 +106,40 @@ void SubwayMap::OnConnect() {
82 for (const std::string &tag : subway_item.tags) { 106 for (const std::string &tag : subway_item.tags) {
83 tagged[tag].push_back(subway_item.id); 107 tagged[tag].push_back(subway_item.id);
84 } 108 }
109 for (const std::string &tag : subway_item.entrances) {
110 entrances[tag].push_back(subway_item.id);
111 }
112 for (const std::string &tag : subway_item.exits) {
113 exits[tag].push_back(subway_item.id);
114 }
85 115
86 if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && 116 if (!AP_IsSunwarpShuffle() && subway_item.sunwarp) {
87 subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { 117 std::string tag = fmt::format("sunwarp{}", subway_item.sunwarp->dots);
88 std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots); 118 switch (subway_item.sunwarp->type) {
89 tagged[tag].push_back(subway_item.id); 119 case SubwaySunwarpType::kEnter:
120 entrances[tag].push_back(subway_item.id);
121 break;
122 case SubwaySunwarpType::kExit:
123 exits[tag].push_back(subway_item.id);
124 break;
125 default:
126 break;
127 }
90 } 128 }
91 129
92 if (!AP_IsPilgrimageEnabled() && 130 if (!AP_IsPilgrimageEnabled()) {
93 (subway_item.special == "sun_painting" || 131 if (subway_item.special == "sun_painting") {
94 subway_item.special == "sun_painting_exit")) { 132 entrances["sun_painting"].push_back(subway_item.id);
95 tagged["sun_painting"].push_back(subway_item.id); 133 } else if (subway_item.special == "sun_painting_exit") {
134 exits["sun_painting"].push_back(subway_item.id);
135 }
96 } 136 }
97 } 137 }
98 138
99 if (AP_IsSunwarpShuffle()) { 139 if (AP_IsSunwarpShuffle()) {
140 SubwaySunwarp final_sunwarp{.dots = 6, .type = SubwaySunwarpType::kFinal};
141 int final_sunwarp_item = GD_GetSubwayItemForSunwarp(final_sunwarp);
142
100 for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { 143 for (const auto &[index, mapping] : AP_GetSunwarpMapping()) {
101 std::string tag = fmt::format("sunwarp{}", mapping.dots); 144 std::string tag = fmt::format("sunwarp{}", mapping.dots);
102 145
@@ -118,8 +161,14 @@ void SubwayMap::OnConnect() {
118 toWarp.type = SubwaySunwarpType::kExit; 161 toWarp.type = SubwaySunwarpType::kExit;
119 } 162 }
120 163
121 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); 164 entrances[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp));
122 tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); 165 exits[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp));
166
167 networks_.AddLinkToNetwork(
168 final_sunwarp_item, GD_GetSubwayItemForSunwarp(fromWarp),
169 mapping.dots == 6 ? final_sunwarp_item
170 : GD_GetSubwayItemForSunwarp(toWarp),
171 false);
123 } 172 }
124 } 173 }
125 174
@@ -129,7 +178,17 @@ void SubwayMap::OnConnect() {
129 tag_it1++) { 178 tag_it1++) {
130 for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); 179 for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end();
131 tag_it2++) { 180 tag_it2++) {
132 networks_.AddLink(*tag_it1, *tag_it2); 181 // two links because tags are bi-directional
182 networks_.AddLink(*tag_it1, *tag_it2, true);
183 }
184 }
185 }
186
187 for (const auto &[tag, items] : entrances) {
188 if (!exits.contains(tag)) continue;
189 for (auto exit : exits[tag]) {
190 for (auto entrance : items) {
191 networks_.AddLink(entrance, exit, false);
133 } 192 }
134 } 193 }
135 } 194 }
@@ -149,7 +208,7 @@ void SubwayMap::UpdateIndicators() {
149 AP_GetPaintingMapping().at(painting_id)); 208 AP_GetPaintingMapping().at(painting_id));
150 209
151 if (from_id && to_id) { 210 if (from_id && to_id) {
152 networks_.AddLink(*from_id, *to_id); 211 networks_.AddLink(*from_id, *to_id, false);
153 } 212 }
154 } 213 }
155 } 214 }
@@ -162,7 +221,7 @@ void SubwayMap::UpdateIndicators() {
162void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, 221void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp,
163 SubwaySunwarp to_sunwarp) { 222 SubwaySunwarp to_sunwarp) {
164 networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), 223 networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp),
165 GD_GetSubwayItemForSunwarp(to_sunwarp)); 224 GD_GetSubwayItemForSunwarp(to_sunwarp), false);
166} 225}
167 226
168void SubwayMap::Zoom(bool in) { 227void SubwayMap::Zoom(bool in) {
@@ -267,9 +326,11 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
267 // Note that these requirements are duplicated on OnMouseClick so that it 326 // Note that these requirements are duplicated on OnMouseClick so that it
268 // knows when an item has a hover effect. 327 // knows when an item has a hover effect.
269 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); 328 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_);
270 if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { 329 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
330
331 if (subway_door && !GetDoorRequirements(*subway_door).empty()) {
271 const std::map<std::string, bool> &report = 332 const std::map<std::string, bool> &report =
272 GetDoorRequirements(*subway_item.door); 333 GetDoorRequirements(*subway_door);
273 334
274 int acc_height = 10; 335 int acc_height = 10;
275 int col_width = 0; 336 int col_width = 0;
@@ -326,10 +387,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
326 if (networks_.IsItemInNetwork(*hovered_item_)) { 387 if (networks_.IsItemInNetwork(*hovered_item_)) {
327 dc.SetBrush(*wxTRANSPARENT_BRUSH); 388 dc.SetBrush(*wxTRANSPARENT_BRUSH);
328 389
329 for (const auto &[item_id1, item_id2] : 390 for (const auto node : networks_.GetNetworkGraph(*hovered_item_)) {
330 networks_.GetNetworkGraph(*hovered_item_)) { 391 const SubwayItem &item1 = GD_GetSubwayItem(node.entry);
331 const SubwayItem &item1 = GD_GetSubwayItem(item_id1); 392 const SubwayItem &item2 = GD_GetSubwayItem(node.exit);
332 const SubwayItem &item2 = GD_GetSubwayItem(item_id2);
333 393
334 wxPoint item1_pos = MapPosToRenderPos( 394 wxPoint item1_pos = MapPosToRenderPos(
335 {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); 395 {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2});
@@ -349,6 +409,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
349 dc.DrawLine(item1_pos, item2_pos); 409 dc.DrawLine(item1_pos, item2_pos);
350 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); 410 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
351 dc.DrawLine(item1_pos, item2_pos); 411 dc.DrawLine(item1_pos, item2_pos);
412 if (!node.two_way) {
413 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2));
414 dc.SetBrush(*wxCYAN_BRUSH);
415 dc.DrawCircle(item2_pos, 4);
416 dc.SetBrush(*wxTRANSPARENT_BRUSH);
417 }
352 } else { 418 } else {
353 int ellipse_x; 419 int ellipse_x;
354 int ellipse_y; 420 int ellipse_y;
@@ -391,6 +457,12 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
391 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); 457 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
392 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, 458 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2,
393 halfheight * 2, start, end); 459 halfheight * 2, start, end);
460 if (!node.two_way) {
461 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2));
462 dc.SetBrush(*wxCYAN_BRUSH);
463 dc.DrawCircle(item2_pos, 4);
464 dc.SetBrush(*wxTRANSPARENT_BRUSH);
465 }
394 } 466 }
395 } 467 }
396 } 468 }
@@ -450,7 +522,9 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) {
450 522
451 if (actual_hover_) { 523 if (actual_hover_) {
452 const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); 524 const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_);
453 if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || 525 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
526
527 if ((subway_door && !GetDoorRequirements(*subway_door).empty()) ||
454 networks_.IsItemInNetwork(*hovered_item_)) { 528 networks_.IsItemInNetwork(*hovered_item_)) {
455 if (actual_hover_ != hovered_item_) { 529 if (actual_hover_ != hovered_item_) {
456 hovered_item_ = actual_hover_; 530 hovered_item_ = actual_hover_;
@@ -509,7 +583,8 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) {
509 "corner.\nClick on a side of the screen to start panning. It will follow " 583 "corner.\nClick on a side of the screen to start panning. It will follow "
510 "your mouse. Click again to stop.\nHover over a door to see the " 584 "your mouse. Click again to stop.\nHover over a door to see the "
511 "requirements to open it.\nHover over a warp or active painting to see " 585 "requirements to open it.\nHover over a warp or active painting to see "
512 "what it is connected to.\nIn painting shuffle, paintings that have not " 586 "what it is connected to.\nFor one-way connections, there will be a "
587 "circle at the exit.\nIn painting shuffle, paintings that have not "
513 "yet been checked will not show their connections.\nA green shaded owl " 588 "yet been checked will not show their connections.\nA green shaded owl "
514 "means that there is a painting entrance there.\nA red shaded owl means " 589 "means that there is a painting entrance there.\nA red shaded owl means "
515 "that there are only painting exits there.\nClick on a door or " 590 "that there are only painting exits there.\nClick on a door or "
@@ -529,6 +604,7 @@ void SubwayMap::Redraw() {
529 ItemDrawType draw_type = ItemDrawType::kNone; 604 ItemDrawType draw_type = ItemDrawType::kNone;
530 const wxBrush *brush_color = wxGREY_BRUSH; 605 const wxBrush *brush_color = wxGREY_BRUSH;
531 std::optional<wxColour> shade_color; 606 std::optional<wxColour> shade_color;
607 std::optional<int> subway_door = GetRealSubwayDoor(subway_item);
532 608
533 if (AP_HasEarlyColorHallways() && 609 if (AP_HasEarlyColorHallways() &&
534 subway_item.special == "starting_room_paintings") { 610 subway_item.special == "starting_room_paintings") {
@@ -544,6 +620,16 @@ void SubwayMap::Redraw() {
544 brush_color = wxRED_BRUSH; 620 brush_color = wxRED_BRUSH;
545 } 621 }
546 } 622 }
623 } else if (subway_item.sunwarp &&
624 subway_item.sunwarp->type == SubwaySunwarpType::kFinal &&
625 AP_IsPilgrimageEnabled()) {
626 draw_type = ItemDrawType::kBox;
627
628 if (IsPilgrimageDoable()) {
629 brush_color = wxGREEN_BRUSH;
630 } else {
631 brush_color = wxRED_BRUSH;
632 }
547 } else if (!subway_item.paintings.empty()) { 633 } else if (!subway_item.paintings.empty()) {
548 if (AP_IsPaintingShuffle()) { 634 if (AP_IsPaintingShuffle()) {
549 bool has_checked_painting = false; 635 bool has_checked_painting = false;
@@ -577,13 +663,13 @@ void SubwayMap::Redraw() {
577 } 663 }
578 } 664 }
579 } 665 }
580 } else if (!subway_item.tags.empty()) { 666 } else if (subway_item.HasWarps()) {
581 draw_type = ItemDrawType::kOwl; 667 draw_type = ItemDrawType::kOwl;
582 } 668 }
583 } else if (subway_item.door) { 669 } else if (subway_door) {
584 draw_type = ItemDrawType::kBox; 670 draw_type = ItemDrawType::kBox;
585 671
586 if (IsDoorOpen(*subway_item.door)) { 672 if (IsDoorOpen(*subway_door)) {
587 brush_color = wxGREEN_BRUSH; 673 brush_color = wxGREEN_BRUSH;
588 } else { 674 } else {
589 brush_color = wxRED_BRUSH; 675 brush_color = wxRED_BRUSH;
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 80fd137..b9282f5 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp
@@ -2,9 +2,12 @@
2 2
3#include <wx/aboutdlg.h> 3#include <wx/aboutdlg.h>
4#include <wx/choicebk.h> 4#include <wx/choicebk.h>
5#include <wx/filedlg.h>
5#include <wx/notebook.h> 6#include <wx/notebook.h>
7#include <wx/stdpaths.h>
6#include <wx/webrequest.h> 8#include <wx/webrequest.h>
7 9
10#include <fmt/core.h>
8#include <nlohmann/json.hpp> 11#include <nlohmann/json.hpp>
9#include <sstream> 12#include <sstream>
10 13
@@ -23,6 +26,7 @@ enum TrackerFrameIds {
23 ID_SETTINGS = 3, 26 ID_SETTINGS = 3,
24 ID_ZOOM_IN = 4, 27 ID_ZOOM_IN = 4,
25 ID_ZOOM_OUT = 5, 28 ID_ZOOM_OUT = 5,
29 ID_OPEN_SAVE_FILE = 6,
26}; 30};
27 31
28wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); 32wxDEFINE_EVENT(STATE_RESET, wxCommandEvent);
@@ -38,6 +42,7 @@ TrackerFrame::TrackerFrame()
38 42
39 wxMenu *menuFile = new wxMenu(); 43 wxMenu *menuFile = new wxMenu();
40 menuFile->Append(ID_CONNECT, "&Connect"); 44 menuFile->Append(ID_CONNECT, "&Connect");
45 menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O");
41 menuFile->Append(ID_SETTINGS, "&Settings"); 46 menuFile->Append(ID_SETTINGS, "&Settings");
42 menuFile->Append(wxID_EXIT); 47 menuFile->Append(wxID_EXIT);
43 48
@@ -71,6 +76,7 @@ TrackerFrame::TrackerFrame()
71 Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); 76 Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN);
72 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); 77 Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT);
73 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); 78 Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this);
79 Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE);
74 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); 80 Bind(STATE_RESET, &TrackerFrame::OnStateReset, this);
75 Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); 81 Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this);
76 Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); 82 Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this);
@@ -131,6 +137,7 @@ void TrackerFrame::OnAbout(wxCommandEvent &event) {
131 about_info.SetName("Lingo Archipelago Tracker"); 137 about_info.SetName("Lingo Archipelago Tracker");
132 about_info.SetVersion(kTrackerVersion.ToString()); 138 about_info.SetVersion(kTrackerVersion.ToString());
133 about_info.AddDeveloper("hatkirby"); 139 about_info.AddDeveloper("hatkirby");
140 about_info.AddDeveloper("art0007i");
134 about_info.AddArtist("Brenton Wildes"); 141 about_info.AddArtist("Brenton Wildes");
135 about_info.AddArtist("kinrah"); 142 about_info.AddArtist("kinrah");
136 143
@@ -204,10 +211,36 @@ void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) {
204 zoom_out_menu_item_->Enable(event.GetSelection() == 1); 211 zoom_out_menu_item_->Enable(event.GetSelection() == 1);
205} 212}
206 213
214void TrackerFrame::OnOpenFile(wxCommandEvent& event) {
215 wxFileDialog open_file_dialog(
216 this, "Open Lingo Save File",
217 fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable",
218 wxStandardPaths::Get().GetUserConfigDir().ToStdString()),
219 AP_GetSaveName(), "Lingo save file (*.save)|*.save",
220 wxFD_OPEN | wxFD_FILE_MUST_EXIST);
221 if (open_file_dialog.ShowModal() == wxID_CANCEL) {
222 return;
223 }
224
225 std::string savedata_path = open_file_dialog.GetPath().ToStdString();
226
227 if (panels_panel_ == nullptr) {
228 panels_panel_ = new TrackerPanel(notebook_);
229 notebook_->AddPage(panels_panel_, "Panels");
230 }
231
232 notebook_->SetSelection(notebook_->FindPage(panels_panel_));
233 panels_panel_->SetSavedataPath(savedata_path);
234}
235
207void TrackerFrame::OnStateReset(wxCommandEvent& event) { 236void TrackerFrame::OnStateReset(wxCommandEvent& event) {
208 tracker_panel_->UpdateIndicators(); 237 tracker_panel_->UpdateIndicators();
209 achievements_pane_->UpdateIndicators(); 238 achievements_pane_->UpdateIndicators();
210 subway_map_->OnConnect(); 239 subway_map_->OnConnect();
240 if (panels_panel_ != nullptr) {
241 notebook_->DeletePage(notebook_->FindPage(panels_panel_));
242 panels_panel_ = nullptr;
243 }
211 Refresh(); 244 Refresh();
212} 245}
213 246
@@ -215,6 +248,9 @@ void TrackerFrame::OnStateChanged(wxCommandEvent &event) {
215 tracker_panel_->UpdateIndicators(); 248 tracker_panel_->UpdateIndicators();
216 achievements_pane_->UpdateIndicators(); 249 achievements_pane_->UpdateIndicators();
217 subway_map_->UpdateIndicators(); 250 subway_map_->UpdateIndicators();
251 if (panels_panel_ != nullptr) {
252 panels_panel_->UpdateIndicators();
253 }
218 Refresh(); 254 Refresh();
219} 255}
220 256
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index f7cb3f2..19bd0b3 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h
@@ -35,6 +35,7 @@ class TrackerFrame : public wxFrame {
35 void OnZoomIn(wxCommandEvent &event); 35 void OnZoomIn(wxCommandEvent &event);
36 void OnZoomOut(wxCommandEvent &event); 36 void OnZoomOut(wxCommandEvent &event);
37 void OnChangePage(wxBookCtrlEvent &event); 37 void OnChangePage(wxBookCtrlEvent &event);
38 void OnOpenFile(wxCommandEvent &event);
38 39
39 void OnStateReset(wxCommandEvent &event); 40 void OnStateReset(wxCommandEvent &event);
40 void OnStateChanged(wxCommandEvent &event); 41 void OnStateChanged(wxCommandEvent &event);
@@ -46,6 +47,7 @@ class TrackerFrame : public wxFrame {
46 TrackerPanel *tracker_panel_; 47 TrackerPanel *tracker_panel_;
47 AchievementsPane *achievements_pane_; 48 AchievementsPane *achievements_pane_;
48 SubwayMap *subway_map_; 49 SubwayMap *subway_map_;
50 TrackerPanel *panels_panel_ = nullptr;
49 51
50 wxMenuItem *zoom_in_menu_item_; 52 wxMenuItem *zoom_in_menu_item_;
51 wxMenuItem *zoom_out_menu_item_; 53 wxMenuItem *zoom_out_menu_item_;
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index d60c1b6..27e825a 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp
@@ -1,11 +1,15 @@
1#include "tracker_panel.h" 1#include "tracker_panel.h"
2 2
3#include <fmt/core.h>
3#include <wx/dcbuffer.h> 4#include <wx/dcbuffer.h>
4 5
6#include <algorithm>
7
5#include "ap_state.h" 8#include "ap_state.h"
6#include "area_popup.h" 9#include "area_popup.h"
7#include "game_data.h" 10#include "game_data.h"
8#include "global.h" 11#include "global.h"
12#include "godot_variant.h"
9#include "tracker_config.h" 13#include "tracker_config.h"
10#include "tracker_state.h" 14#include "tracker_state.h"
11 15
@@ -53,6 +57,35 @@ void TrackerPanel::UpdateIndicators() {
53 Redraw(); 57 Redraw();
54} 58}
55 59
60void TrackerPanel::SetSavedataPath(std::string savedata_path) {
61 if (!panels_mode_) {
62 wxButton *refresh_button = new wxButton(this, wxID_ANY, "Refresh", {15, 15});
63 refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this);
64 }
65
66 savedata_path_ = savedata_path;
67 panels_mode_ = true;
68
69 RefreshSavedata();
70}
71
72void TrackerPanel::RefreshSavedata() {
73 solved_panels_.clear();
74
75 GodotVariant godot_variant = ParseGodotFile(*savedata_path_);
76 for (const GodotVariant &panel_node : godot_variant.AsArray()) {
77 const std::vector<GodotVariant> &fields = panel_node.AsArray();
78 if (fields[1].AsBool()) {
79 const std::vector<std::string> &nodepath = fields[0].AsNodePath();
80 std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]);
81 solved_panels_.insert(key);
82 }
83 }
84
85 UpdateIndicators();
86 Refresh();
87}
88
56void TrackerPanel::OnPaint(wxPaintEvent &event) { 89void TrackerPanel::OnPaint(wxPaintEvent &event) {
57 if (GetSize() != rendered_.GetSize()) { 90 if (GetSize() != rendered_.GetSize()) {
58 Redraw(); 91 Redraw();
@@ -92,6 +125,10 @@ void TrackerPanel::OnMouseMove(wxMouseEvent &event) {
92 event.Skip(); 125 event.Skip();
93} 126}
94 127
128void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) {
129 RefreshSavedata();
130}
131
95void TrackerPanel::Redraw() { 132void TrackerPanel::Redraw() {
96 wxSize panel_size = GetSize(); 133 wxSize panel_size = GetSize();
97 wxSize image_size = map_image_.GetSize(); 134 wxSize image_size = map_image_.GetSize();
@@ -142,21 +179,35 @@ void TrackerPanel::Redraw() {
142 179
143 for (AreaIndicator &area : areas_) { 180 for (AreaIndicator &area : areas_) {
144 const MapArea &map_area = GD_GetMapArea(area.area_id); 181 const MapArea &map_area = GD_GetMapArea(area.area_id);
145 if (!AP_IsLocationVisible(map_area.classification) && 182 if (panels_mode_) {
183 area.active = map_area.has_single_panel;
184 } else if (!AP_IsLocationVisible(map_area.classification) &&
146 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && 185 !(map_area.hunt && GetTrackerConfig().show_hunt_panels) &&
147 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { 186 !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) {
148 area.active = false; 187 area.active = false;
149 continue;
150 } else { 188 } else {
151 area.active = true; 189 area.active = true;
152 } 190 }
153 191
192 if (!area.active) {
193 continue;
194 }
195
154 bool has_reachable_unchecked = false; 196 bool has_reachable_unchecked = false;
155 bool has_unreachable_unchecked = false; 197 bool has_unreachable_unchecked = false;
156 for (const Location &section : map_area.locations) { 198 for (const Location &section : map_area.locations) {
157 bool has_unchecked = false; 199 bool has_unchecked = false;
158 if (IsLocationWinCondition(section)) { 200 if (IsLocationWinCondition(section)) {
159 has_unchecked = !AP_HasReachedGoal(); 201 has_unchecked = !AP_HasReachedGoal();
202 } else if (panels_mode_) {
203 if (section.single_panel) {
204 const Panel &panel = GD_GetPanel(*section.single_panel);
205 if (panel.non_counting) {
206 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
207 } else {
208 has_unchecked = !solved_panels_.contains(panel.nodepath);
209 }
210 }
160 } else if (AP_IsLocationVisible(section.classification)) { 211 } else if (AP_IsLocationVisible(section.classification)) {
161 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); 212 has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id);
162 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { 213 } else if (section.hunt && GetTrackerConfig().show_hunt_panels) {
@@ -172,12 +223,11 @@ void TrackerPanel::Redraw() {
172 } 223 }
173 } 224 }
174 225
175 if (AP_IsPaintingShuffle()) { 226 if (AP_IsPaintingShuffle() && !panels_mode_) {
176 for (int painting_id : map_area.paintings) { 227 for (int painting_id : map_area.paintings) {
177 const PaintingExit &painting = GD_GetPaintingExit(painting_id); 228 const PaintingExit &painting = GD_GetPaintingExit(painting_id);
178 if (!AP_IsPaintingChecked(painting.internal_id)) { 229 bool reachable = IsPaintingReachable(painting_id);
179 bool reachable = IsPaintingReachable(painting_id); 230 if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) {
180
181 if (reachable) { 231 if (reachable) {
182 has_reachable_unchecked = true; 232 has_reachable_unchecked = true;
183 } else { 233 } else {
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index 06ec7a0..e1f515d 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h
@@ -7,6 +7,10 @@
7#include <wx/wx.h> 7#include <wx/wx.h>
8#endif 8#endif
9 9
10#include <optional>
11#include <set>
12#include <string>
13
10class AreaPopup; 14class AreaPopup;
11 15
12class TrackerPanel : public wxPanel { 16class TrackerPanel : public wxPanel {
@@ -15,6 +19,14 @@ class TrackerPanel : public wxPanel {
15 19
16 void UpdateIndicators(); 20 void UpdateIndicators();
17 21
22 void SetSavedataPath(std::string savedata_path);
23
24 bool IsPanelsMode() const { return panels_mode_; }
25
26 const std::set<std::string> &GetSolvedPanels() const {
27 return solved_panels_;
28 }
29
18 private: 30 private:
19 struct AreaIndicator { 31 struct AreaIndicator {
20 int area_id = -1; 32 int area_id = -1;
@@ -28,9 +40,12 @@ class TrackerPanel : public wxPanel {
28 40
29 void OnPaint(wxPaintEvent &event); 41 void OnPaint(wxPaintEvent &event);
30 void OnMouseMove(wxMouseEvent &event); 42 void OnMouseMove(wxMouseEvent &event);
43 void OnRefreshSavedata(wxCommandEvent &event);
31 44
32 void Redraw(); 45 void Redraw();
33 46
47 void RefreshSavedata();
48
34 wxImage map_image_; 49 wxImage map_image_;
35 wxImage player_image_; 50 wxImage player_image_;
36 wxBitmap rendered_; 51 wxBitmap rendered_;
@@ -42,6 +57,10 @@ class TrackerPanel : public wxPanel {
42 double scale_y_ = 0; 57 double scale_y_ = 0;
43 58
44 std::vector<AreaIndicator> areas_; 59 std::vector<AreaIndicator> areas_;
60
61 bool panels_mode_ = false;
62 std::optional<std::string> savedata_path_;
63 std::set<std::string> solved_panels_;
45}; 64};
46 65
47#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ 66#endif /* end of include guard: TRACKER_PANEL_H_D675A54D */
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 1a2d116..8d5d904 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -1,5 +1,8 @@
1#include "tracker_state.h" 1#include "tracker_state.h"
2 2
3#include <fmt/core.h>
4#include <hkutil/string.h>
5
3#include <list> 6#include <list>
4#include <map> 7#include <map>
5#include <mutex> 8#include <mutex>
@@ -9,6 +12,7 @@
9 12
10#include "ap_state.h" 13#include "ap_state.h"
11#include "game_data.h" 14#include "game_data.h"
15#include "logger.h"
12 16
13namespace { 17namespace {
14 18
@@ -148,6 +152,7 @@ struct TrackerState {
148 std::mutex reachability_mutex; 152 std::mutex reachability_mutex;
149 RequirementCalculator requirements; 153 RequirementCalculator requirements;
150 std::map<int, std::map<std::string, bool>> door_reports; 154 std::map<int, std::map<std::string, bool>> door_reports;
155 bool pilgrimage_doable = false;
151}; 156};
152 157
153enum Decision { kYes, kNo, kMaybe }; 158enum Decision { kYes, kNo, kMaybe };
@@ -176,7 +181,8 @@ class StateCalculator {
176 std::list<int> panel_boundary; 181 std::list<int> panel_boundary;
177 std::list<int> painting_boundary; 182 std::list<int> painting_boundary;
178 std::list<Exit> flood_boundary; 183 std::list<Exit> flood_boundary;
179 flood_boundary.push_back({.destination_room = options_.start}); 184 flood_boundary.push_back(
185 {.source_room = -1, .destination_room = options_.start});
180 186
181 bool reachable_changed = true; 187 bool reachable_changed = true;
182 while (reachable_changed) { 188 while (reachable_changed) {
@@ -217,7 +223,9 @@ class StateCalculator {
217 PaintingExit target_painting = 223 PaintingExit target_painting =
218 GD_GetPaintingExit(GD_GetPaintingByName( 224 GD_GetPaintingExit(GD_GetPaintingByName(
219 AP_GetPaintingMapping().at(cur_painting.internal_id))); 225 AP_GetPaintingMapping().at(cur_painting.internal_id)));
226 painting_exit.source_room = cur_painting.room;
220 painting_exit.destination_room = target_painting.room; 227 painting_exit.destination_room = target_painting.room;
228 painting_exit.type = EntranceType::kPainting;
221 229
222 new_boundary.push_back(painting_exit); 230 new_boundary.push_back(painting_exit);
223 } 231 }
@@ -244,6 +252,12 @@ class StateCalculator {
244 reachable_rooms_.insert(room_exit.destination_room); 252 reachable_rooms_.insert(room_exit.destination_room);
245 reachable_changed = true; 253 reachable_changed = true;
246 254
255#ifndef NDEBUG
256 std::list<int> room_path = paths_[room_exit.source_room];
257 room_path.push_back(room_exit.destination_room);
258 paths_[room_exit.destination_room] = room_path;
259#endif
260
247 const Room& room_obj = GD_GetRoom(room_exit.destination_room); 261 const Room& room_obj = GD_GetRoom(room_exit.destination_room);
248 for (const Exit& out_edge : room_obj.exits) { 262 for (const Exit& out_edge : room_obj.exits) {
249 if (out_edge.type == EntranceType::kPainting && 263 if (out_edge.type == EntranceType::kPainting &&
@@ -270,32 +284,44 @@ class StateCalculator {
270 if (AP_GetSunwarpMapping().count(index)) { 284 if (AP_GetSunwarpMapping().count(index)) {
271 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); 285 const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index);
272 286
273 Exit sunwarp_exit; 287 new_boundary.push_back(
274 sunwarp_exit.destination_room = 288 {.source_room = room_exit.destination_room,
275 GD_GetRoomForSunwarp(sm.exit_index); 289 .destination_room = GD_GetRoomForSunwarp(sm.exit_index),
276 sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); 290 .door = GD_GetSunwarpDoors().at(sm.dots - 1),
277 291 .type = EntranceType::kSunwarp});
278 new_boundary.push_back(sunwarp_exit);
279 } 292 }
280 } 293 }
281 } 294 }
282 295
283 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { 296 if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") {
284 new_boundary.push_back( 297 new_boundary.push_back(
285 {.destination_room = GD_GetRoomByName("Color Hallways"), 298 {.source_room = room_exit.destination_room,
299 .destination_room = GD_GetRoomByName("Color Hallways"),
286 .type = EntranceType::kPainting}); 300 .type = EntranceType::kPainting});
287 } 301 }
288 302
289 if (AP_IsPilgrimageEnabled()) { 303 if (AP_IsPilgrimageEnabled()) {
290 if (room_obj.name == "Hub Room") { 304 int pilgrimage_start_id = GD_GetRoomByName("Hub Room");
305 if (AP_IsSunwarpShuffle()) {
306 for (const auto& [start_index, mapping] :
307 AP_GetSunwarpMapping()) {
308 if (mapping.dots == 1) {
309 pilgrimage_start_id = GD_GetRoomForSunwarp(start_index);
310 }
311 }
312 }
313
314 if (room_exit.destination_room == pilgrimage_start_id) {
291 new_boundary.push_back( 315 new_boundary.push_back(
292 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 316 {.source_room = room_exit.destination_room,
317 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
293 .type = EntranceType::kPilgrimage}); 318 .type = EntranceType::kPilgrimage});
294 } 319 }
295 } else { 320 } else {
296 if (room_obj.name == "Starting Room") { 321 if (room_obj.name == "Starting Room") {
297 new_boundary.push_back( 322 new_boundary.push_back(
298 {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), 323 {.source_room = room_exit.destination_room,
324 .destination_room = GD_GetRoomByName("Pilgrim Antechamber"),
299 .door = 325 .door =
300 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), 326 GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"),
301 .type = EntranceType::kPainting}); 327 .type = EntranceType::kPainting});
@@ -336,6 +362,21 @@ class StateCalculator {
336 return door_report_; 362 return door_report_;
337 } 363 }
338 364
365 bool IsPilgrimageDoable() const { return pilgrimage_doable_; }
366
367 std::string GetPathToRoom(int room_id) const {
368 if (!paths_.count(room_id)) {
369 return "";
370 }
371
372 const std::list<int>& path = paths_.at(room_id);
373 std::vector<std::string> room_names;
374 for (int room_id : path) {
375 room_names.push_back(GD_GetRoom(room_id).name);
376 }
377 return hatkirby::implode(room_names, " -> ");
378 }
379
339 private: 380 private:
340 Decision IsNonGroupedDoorReachable(const Door& door_obj) { 381 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
341 bool has_item = AP_HasItem(door_obj.ap_item_id); 382 bool has_item = AP_HasItem(door_obj.ap_item_id);
@@ -534,6 +575,8 @@ class StateCalculator {
534 } 575 }
535 } 576 }
536 577
578 pilgrimage_doable_ = true;
579
537 return kYes; 580 return kYes;
538 } 581 }
539 582
@@ -574,6 +617,9 @@ class StateCalculator {
574 std::set<int> solveable_panels_; 617 std::set<int> solveable_panels_;
575 std::set<int> reachable_paintings_; 618 std::set<int> reachable_paintings_;
576 std::map<int, std::map<std::string, bool>> door_report_; 619 std::map<int, std::map<std::string, bool>> door_report_;
620 bool pilgrimage_doable_ = false;
621
622 std::map<int, std::list<int>> paths_;
577}; 623};
578 624
579} // namespace 625} // namespace
@@ -623,6 +669,7 @@ void RecalculateReachability() {
623 std::swap(GetState().reachable_doors, new_reachable_doors); 669 std::swap(GetState().reachable_doors, new_reachable_doors);
624 std::swap(GetState().reachable_paintings, reachable_paintings); 670 std::swap(GetState().reachable_paintings, reachable_paintings);
625 std::swap(GetState().door_reports, door_reports); 671 std::swap(GetState().door_reports, door_reports);
672 GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable();
626} 673}
627 674
628bool IsLocationReachable(int location_id) { 675bool IsLocationReachable(int location_id) {
@@ -652,3 +699,9 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id) {
652 699
653 return GetState().door_reports[door_id]; 700 return GetState().door_reports[door_id];
654} 701}
702
703bool IsPilgrimageDoable() {
704 std::lock_guard reachability_guard(GetState().reachability_mutex);
705
706 return GetState().pilgrimage_doable;
707}
diff --git a/src/tracker_state.h b/src/tracker_state.h index c7857a0..a8f155d 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h
@@ -16,4 +16,6 @@ bool IsPaintingReachable(int painting_id);
16 16
17const std::map<std::string, bool>& GetDoorRequirements(int door_id); 17const std::map<std::string, bool>& GetDoorRequirements(int door_id);
18 18
19bool IsPilgrimageDoable();
20
19#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ 21#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */
diff --git a/src/version.h b/src/version.h index b688e27..ec52f44 100644 --- a/src/version.h +++ b/src/version.h
@@ -36,6 +36,6 @@ struct Version {
36 } 36 }
37}; 37};
38 38
39constexpr const Version kTrackerVersion = Version(0, 10, 5); 39constexpr const Version kTrackerVersion = Version(0, 11, 2);
40 40
41#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file 41#endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file