diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CHANGELOG.md | 60 | ||||
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rw-r--r-- | README.md | 17 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | assets/subway.yaml | 117 | ||||
-rw-r--r-- | src/ap_state.cpp | 13 | ||||
-rw-r--r-- | src/ap_state.h | 2 | ||||
-rw-r--r-- | src/area_popup.cpp | 48 | ||||
-rw-r--r-- | src/game_data.cpp | 29 | ||||
-rw-r--r-- | src/game_data.h | 10 | ||||
-rw-r--r-- | src/godot_variant.cpp | 83 | ||||
-rw-r--r-- | src/godot_variant.h | 28 | ||||
-rw-r--r-- | src/network_set.cpp | 34 | ||||
-rw-r--r-- | src/network_set.h | 16 | ||||
-rw-r--r-- | src/subway_map.cpp | 136 | ||||
-rw-r--r-- | src/tracker_frame.cpp | 36 | ||||
-rw-r--r-- | src/tracker_frame.h | 2 | ||||
-rw-r--r-- | src/tracker_panel.cpp | 62 | ||||
-rw-r--r-- | src/tracker_panel.h | 19 | ||||
-rw-r--r-- | src/tracker_state.cpp | 75 | ||||
-rw-r--r-- | src/tracker_state.h | 2 | ||||
-rw-r--r-- | src/version.h | 2 |
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 @@ | |||
1 | build/ | 1 | build/ |
2 | builds/ | 2 | builds/ |
3 | assets/LL1.yaml | 3 | assets/LL1.yaml |
4 | assets/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 | |||
8 | Download: | ||
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/> | ||
10 | Source: [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 | |||
24 | Download: | ||
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/> | ||
26 | Source: [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 | |||
35 | Download: | ||
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/> | ||
37 | Source: [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 | |||
45 | Download: | ||
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/> | ||
47 | Source: [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 | |||
59 | Download: | ||
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/> | ||
61 | Source: [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 | ) |
53 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) | 54 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) |
54 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) | 55 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) |
55 | target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto websocketpp::websocketpp wx::core wx::base wx::net yaml-cpp::yaml-cpp) | 56 | target_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 | |||
58 | set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets") | ||
59 | set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets") | ||
60 | |||
61 | add_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 | |||
66 | add_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 | ||
12 | Thanks 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 | |||
18 | To build the app: | ||
19 | |||
20 | 1. Clone the repository including submodules: `git clone --recursive https://code.fourisland.com/lingo-ap-tracker` | ||
21 | 2. Put [LL1.yaml from archipelago](https://github.com/ArchipelagoMW/Archipelago/raw/main/worlds/lingo/data/LL1.yaml) in ./assets | ||
22 | 3. Put [ids.yaml from archipelago](https://github.com/ArchipelagoMW/Archipelago/raw/main/worlds/lingo/data/ids.yaml) in ./assets | ||
23 | 4. Configure the project: `cmake --preset=lingo-ap-tracker-preset` | ||
24 | 5. Build the application in debug mode: `cmake --build --preset=lingo-ap-tracker-preset` | ||
25 | 6. (Optional) Build the application in release mode: `cmake --build --preset=x64-release-preset` | ||
26 | |||
27 | LL1.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 | ||
517 | std::string AP_GetSaveName() { return GetState().save_name; } | ||
518 | |||
510 | bool AP_HasCheckedGameLocation(int location_id) { | 519 | bool 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 | ||
44 | void AP_Connect(std::string server, std::string player, std::string password); | 44 | void AP_Connect(std::string server, std::string player, std::string password); |
45 | 45 | ||
46 | std::string AP_GetSaveName(); | ||
47 | |||
46 | bool AP_HasCheckedGameLocation(int location_id); | 48 | bool AP_HasCheckedGameLocation(int location_id); |
47 | 49 | ||
48 | bool AP_HasCheckedHuntPanel(int location_id); | 50 | bool 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 | ||
11 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | 14 | AreaPopup::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 | ||
807 | bool SubwayItem::HasWarps() const { | ||
808 | return !(this->tags.empty() && this->entrances.empty() && | ||
809 | this->exits.empty()); | ||
810 | } | ||
811 | |||
787 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { | 812 | bool 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 | ||
85 | struct Exit { | 86 | struct 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 | ||
117 | struct MapArea { | 120 | struct 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 | ||
128 | enum class SubwaySunwarpType { | 132 | enum 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 | ||
152 | const std::vector<MapArea>& GD_GetMapAreas(); | 160 | const 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 | |||
14 | namespace { | ||
15 | |||
16 | uint16_t ReadUint16(std::basic_istream<char>& stream) { | ||
17 | uint16_t result; | ||
18 | stream.read(reinterpret_cast<char*>(&result), 2); | ||
19 | return result; | ||
20 | } | ||
21 | |||
22 | uint32_t ReadUint32(std::basic_istream<char>& stream) { | ||
23 | uint32_t result; | ||
24 | stream.read(reinterpret_cast<char*>(&result), 4); | ||
25 | return result; | ||
26 | } | ||
27 | |||
28 | GodotVariant 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 | |||
79 | GodotVariant 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 | |||
8 | struct 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 | |||
26 | GodotVariant 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 | ||
7 | void NetworkSet::AddLink(int id1, int id2) { | 7 | void 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 | |||
25 | void 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 | ||
24 | bool NetworkSet::IsItemInNetwork(int id) const { | 39 | bool NetworkSet::IsItemInNetwork(int id) const { |
25 | return network_by_item_.count(id); | 40 | return network_by_item_.count(id); |
26 | } | 41 | } |
27 | 42 | ||
28 | const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { | 43 | const 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 | |||
47 | bool 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 | ||
10 | struct NetworkNode { | ||
11 | int entry; | ||
12 | int exit; | ||
13 | bool two_way; | ||
14 | |||
15 | bool operator<(const NetworkNode& rhs) const; | ||
16 | }; | ||
17 | |||
10 | class NetworkSet { | 18 | class 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 | ||
17 | enum class ItemDrawType { kNone, kBox, kOwl }; | 17 | enum class ItemDrawType { kNone, kBox, kOwl }; |
18 | 18 | ||
19 | namespace { | ||
20 | |||
21 | std::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 | |||
19 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 41 | SubwayMap::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() { | |||
162 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | 221 | void 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 | ||
168 | void SubwayMap::Zoom(bool in) { | 227 | void 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 | ||
28 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | 32 | wxDEFINE_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 | ||
214 | void 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 | |||
207 | void TrackerFrame::OnStateReset(wxCommandEvent& event) { | 236 | void 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 | ||
60 | void 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 | |||
72 | void 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 | |||
56 | void TrackerPanel::OnPaint(wxPaintEvent &event) { | 89 | void 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 | ||
128 | void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) { | ||
129 | RefreshSavedata(); | ||
130 | } | ||
131 | |||
95 | void TrackerPanel::Redraw() { | 132 | void 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 §ion : map_area.locations) { | 198 | for (const Location §ion : 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 | |||
10 | class AreaPopup; | 14 | class AreaPopup; |
11 | 15 | ||
12 | class TrackerPanel : public wxPanel { | 16 | class 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 | ||
13 | namespace { | 17 | namespace { |
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 | ||
153 | enum Decision { kYes, kNo, kMaybe }; | 158 | enum 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 | ||
628 | bool IsLocationReachable(int location_id) { | 675 | bool 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 | |||
703 | bool 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 | ||
17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); | 17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); |
18 | 18 | ||
19 | bool 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 | ||
39 | constexpr const Version kTrackerVersion = Version(0, 10, 5); | 39 | constexpr 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 |