diff options
61 files changed, 2700 insertions, 1174 deletions
diff --git a/.gitmodules b/.gitmodules index ebe016f..1a69477 100644 --- a/.gitmodules +++ b/.gitmodules | |||
@@ -16,3 +16,6 @@ | |||
16 | [submodule "vendor/vcpkg"] | 16 | [submodule "vendor/vcpkg"] |
17 | path = vendor/vcpkg | 17 | path = vendor/vcpkg |
18 | url = https://github.com/Microsoft/vcpkg.git | 18 | url = https://github.com/Microsoft/vcpkg.git |
19 | [submodule "vendor/websocketpp"] | ||
20 | path = vendor/websocketpp | ||
21 | url = https://github.com/zaphoyd/websocketpp | ||
diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c94f10..e2444b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -1,5 +1,90 @@ | |||
1 | # lingo-ap-tracker Releases | 1 | # lingo-ap-tracker Releases |
2 | 2 | ||
3 | ## v2.0.2 - 2025-05-24 | ||
4 | |||
5 | - Fixed issue connecting to the Archipelago 0.6.2 RC server. | ||
6 | |||
7 | Download: | ||
8 | [lingo-ap-tracker-v2.0.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.2-win64.zip)<br/> | ||
9 | Source: [v2.0.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.2) | ||
10 | |||
11 | ## v2.0.1 - 2025-04-06 | ||
12 | |||
13 | - The tracker now assumes postgame shuffle is enabled when the flag is not | ||
14 | present in slot data (as is the case with Archipelago 0.6.1 and earlier). | ||
15 | Players who have postgame shuffle disabled will unfortunately see locations | ||
16 | that are not in their world, until this problem can be fixed. | ||
17 | |||
18 | Download: | ||
19 | [lingo-ap-tracker-v2.0.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.1-win64.zip)<br/> | ||
20 | Source: [v2.0.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.1) | ||
21 | |||
22 | ## v2.0.0 - 2025-04-01 | ||
23 | |||
24 | - Compatibility update for Archipelago 0.6.0. | ||
25 | |||
26 | Download: | ||
27 | [lingo-ap-tracker-v2.0.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.0-win64.zip)<br/> | ||
28 | Source: [v2.0.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v2.0.0) | ||
29 | |||
30 | ## v1.0.0 - 2025-03-21 | ||
31 | |||
32 | After almost two years of development, we have finally hit version 1! | ||
33 | |||
34 | - The subway map now uses Kinrah's updated map image, including fully separated | ||
35 | paintings for places like The Wondrous and Orange Tower Sixth Floor, as well | ||
36 | as indicators for the color items. | ||
37 | - Paintings have friendly names now instead of the internal game IDs. They are | ||
38 | also distinguished from regular checks by using an owl icon instead of an eye. | ||
39 | - The tracker is now able to read your solved panel state from the multiworld | ||
40 | (requires v5.3.0 of the client). This obsoletes save file parsing and | ||
41 | receiving panel solves by connecting to the game. | ||
42 | - Numerous optimizations to reachability detection and rendering. | ||
43 | - Added a pane that shows all items received, similar to the web tracker. | ||
44 | - Added a pane that shows your currently revealed painting mapping, as well as a | ||
45 | button that reveals it all immediately. | ||
46 | - Added a pane that shows your slot options. | ||
47 | - Postgame shuffle being disabled is handled properly now, and removed locations | ||
48 | are no longer shown. | ||
49 | - Improved support for high-DPI screens. The tracker should no longer appear | ||
50 | "blurry" on a high-DPI screen, and should also be able to react properly to | ||
51 | being moved between screens with different DPIs. | ||
52 | - The update checker is now able to download and install tracker updates in | ||
53 | place. | ||
54 | |||
55 | Download: | ||
56 | [lingo-ap-tracker-v1.0.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v1.0.0-win64.zip)<br/> | ||
57 | Source: [v1.0.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v1.0.0) | ||
58 | |||
59 | ## v0.12.3 - 2025-03-03 | ||
60 | |||
61 | - Fixed issue with non-ASCII connection details. | ||
62 | |||
63 | Download: | ||
64 | [lingo-ap-tracker-v0.12.3-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.12.3-win64.zip)<br/> | ||
65 | Source: [v0.12.3](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.12.3) | ||
66 | |||
67 | ## v0.12.2 - 2025-02-10 | ||
68 | |||
69 | - Added a scrollbar to the subway map access requirement popups. | ||
70 | - Fixed an issue with a small number of doors having too-strict access | ||
71 | requirements in vanilla or panels mode door shuffle. | ||
72 | |||
73 | Download: | ||
74 | [lingo-ap-tracker-v0.12.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.12.2-win64.zip)<br/> | ||
75 | Source: [v0.12.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.12.2) | ||
76 | |||
77 | ## v0.12.1 - 2025-01-27 | ||
78 | |||
79 | - Fixed sunwarp mapping not showing up on metro map when sunwarp shuffle is | ||
80 | enabled. | ||
81 | - Fixed metro map door requirements sometimes saying you cannot reach areas when | ||
82 | you really can. | ||
83 | |||
84 | Download: | ||
85 | [lingo-ap-tracker-v0.12.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.12.1-win64.zip)<br/> | ||
86 | Source: [v0.12.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.12.1) | ||
87 | |||
3 | ## v0.12.0 - 2024-12-20 | 88 | ## v0.12.0 - 2024-12-20 |
4 | 89 | ||
5 | - The tracker can now connect to a game of Lingo that is running the Archipelago | 90 | - The tracker can now connect to a game of Lingo that is running the Archipelago |
diff --git a/CMakeLists.txt b/CMakeLists.txt index f46c158..ef741fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -1,15 +1,15 @@ | |||
1 | cmake_minimum_required (VERSION 3.1) | 1 | cmake_minimum_required (VERSION 3.20) |
2 | project (lingo_ap_tracker) | 2 | project (lingo_ap_tracker) |
3 | 3 | ||
4 | if (MSVC) | 4 | if (MSVC) |
5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") |
6 | set(CMAKE_WIN32_EXECUTABLE true) | 6 | set(CMAKE_WIN32_EXECUTABLE true) |
7 | set(CMAKE_EXE_LINKER_FLAGS /MANIFEST:NO) | ||
7 | endif(MSVC) | 8 | endif(MSVC) |
8 | 9 | ||
9 | find_package(wxWidgets CONFIG REQUIRED) | 10 | find_package(wxWidgets CONFIG REQUIRED) |
10 | find_package(OpenSSL REQUIRED) | 11 | find_package(OpenSSL REQUIRED) |
11 | find_package(yaml-cpp REQUIRED) | 12 | find_package(yaml-cpp REQUIRED) |
12 | find_package(websocketpp REQUIRED) | ||
13 | find_package(fmt REQUIRED) | 13 | find_package(fmt REQUIRED) |
14 | 14 | ||
15 | include_directories( | 15 | include_directories( |
@@ -18,7 +18,7 @@ include_directories( | |||
18 | vendor/asio/asio/include | 18 | vendor/asio/asio/include |
19 | vendor/nlohmann | 19 | vendor/nlohmann |
20 | vendor/valijson/include | 20 | vendor/valijson/include |
21 | ${websocketpp_INCLUDE_DIRS} | 21 | vendor/websocketpp |
22 | vendor/wswrap/include | 22 | vendor/wswrap/include |
23 | ${yaml-cpp_INCLUDE_DIRS} | 23 | ${yaml-cpp_INCLUDE_DIRS} |
24 | ${OpenSSL_INCLUDE_DIRS} | 24 | ${OpenSSL_INCLUDE_DIRS} |
@@ -32,7 +32,7 @@ include_directories(${SYSTEM_INCLUDE_DIR}) | |||
32 | 32 | ||
33 | link_directories(${openssl_LIBRARY_DIRS}) | 33 | link_directories(${openssl_LIBRARY_DIRS}) |
34 | 34 | ||
35 | add_executable(lingo_ap_tracker | 35 | set(SOURCE_FILES |
36 | "src/main.cpp" | 36 | "src/main.cpp" |
37 | "src/tracker_frame.cpp" | 37 | "src/tracker_frame.cpp" |
38 | "src/tracker_panel.cpp" | 38 | "src/tracker_panel.cpp" |
@@ -48,14 +48,26 @@ 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" | ||
52 | "src/ipc_state.cpp" | 51 | "src/ipc_state.cpp" |
53 | "src/ipc_dialog.cpp" | 52 | "src/ipc_dialog.cpp" |
53 | "src/report_popup.cpp" | ||
54 | "src/updater.cpp" | ||
55 | "src/icons.cpp" | ||
56 | "src/paintings_pane.cpp" | ||
57 | "src/items_pane.cpp" | ||
58 | "src/options_pane.cpp" | ||
59 | "src/log_dialog.cpp" | ||
54 | "vendor/whereami/whereami.c" | 60 | "vendor/whereami/whereami.c" |
55 | ) | 61 | ) |
62 | |||
63 | if (MSVC) | ||
64 | list(APPEND SOURCE_FILES "src/windows.rc") | ||
65 | endif(MSVC) | ||
66 | |||
67 | add_executable(lingo_ap_tracker ${SOURCE_FILES}) | ||
56 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) | 68 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) |
57 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) | 69 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD_REQUIRED ON) |
58 | 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) | 70 | target_link_libraries(lingo_ap_tracker PRIVATE fmt::fmt OpenSSL::SSL OpenSSL::Crypto wx::core wx::base wx::net yaml-cpp::yaml-cpp) |
59 | 71 | ||
60 | set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets") | 72 | set(SRC_DIR "${CMAKE_SOURCE_DIR}/assets") |
61 | set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets") | 73 | set(DST_DIR "${CMAKE_BINARY_DIR}/$<CONFIG>/assets") |
diff --git a/README.md b/README.md index 1fbbc2f..cb3e90d 100644 --- a/README.md +++ b/README.md | |||
@@ -25,3 +25,7 @@ To build the app: | |||
25 | 6. (Optional) Build the application in release mode: `cmake --build --preset=x64-release-preset` | 25 | 6. (Optional) Build the application in release mode: `cmake --build --preset=x64-release-preset` |
26 | 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. | 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. |
28 | |||
29 | ### Using Visual Studio | ||
30 | |||
31 | If you're using Visual Studio on Windows, you can simplify the above process. You can clone the repository directly from Visual Studio, and it will automatically download the submodules as well as configure cmake and build the vcpkg packages. You will still have to do steps 2 and 3 manually. Then, look for the dropdown next to the green arrow and select `lingo_ap_tracker.exe` so that you can build it. You can build in release mode by choosing it from the preset dropdown. | ||
diff --git a/VERSION b/VERSION index 3061e9e..b02d37b 100644 --- a/VERSION +++ b/VERSION | |||
@@ -1 +1 @@ | |||
v0.12.0 \ No newline at end of file | v2.0.2 \ No newline at end of file | ||
diff --git a/VERSION.yaml b/VERSION.yaml new file mode 100644 index 0000000..8f86a39 --- /dev/null +++ b/VERSION.yaml | |||
@@ -0,0 +1,31 @@ | |||
1 | version: v2.0.2 | ||
2 | packages: | ||
3 | win64: | ||
4 | url: "https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v2.0.2-win64.zip" | ||
5 | checksum: "dfb2fce2f5b14c09f4af7e7fcf6478e420a070db9fb23f9f9ad196f2db4b7518" | ||
6 | files: | ||
7 | - fmt.dll | ||
8 | - jpeg62.dll | ||
9 | - libcrypto-3-x64.dll | ||
10 | - liblzma.dll | ||
11 | - libpng16.dll | ||
12 | - libssl-3-x64.dll | ||
13 | - lingo_ap_tracker.exe | ||
14 | - pcre2-16.dll | ||
15 | - tiff.dll | ||
16 | - wxbase32u_net_vc_custom.dll | ||
17 | - wxbase32u_vc_custom.dll | ||
18 | - wxmsw32u_core_vc_custom.dll | ||
19 | - yaml-cpp.dll | ||
20 | - zlib1.dll | ||
21 | - assets/areas.yaml | ||
22 | - assets/checked.png | ||
23 | - assets/checked_owl.png | ||
24 | - assets/ids.yaml | ||
25 | - assets/lingo_map.png | ||
26 | - assets/LL1.yaml | ||
27 | - assets/owl.png | ||
28 | - assets/player.png | ||
29 | - assets/subway.png | ||
30 | - assets/subway.yaml | ||
31 | - assets/unchecked.png | ||
diff --git a/assets/checked_owl.png b/assets/checked_owl.png new file mode 100644 index 0000000..f53ddd3 --- /dev/null +++ b/assets/checked_owl.png | |||
Binary files differ | |||
diff --git a/assets/subway.png b/assets/subway.png index 3860d2c..8128004 100644 --- a/assets/subway.png +++ b/assets/subway.png | |||
Binary files differ | |||
diff --git a/assets/subway.yaml b/assets/subway.yaml index b6f1e1c..8c0df38 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml | |||
@@ -1,123 +1,117 @@ | |||
1 | --- | 1 | --- |
2 | - pos: [1050, 954] | 2 | - pos: [1051, 954] |
3 | room: Starting Room | 3 | room: Starting Room |
4 | door: Back Right Door | 4 | door: Back Right Door |
5 | - pos: [986, 1034] | 5 | - pos: [986, 1034] |
6 | room: Starting Room | 6 | room: Starting Room |
7 | door: Rhyme Room Entrance | 7 | door: Rhyme Room Entrance |
8 | - pos: [990, 956] | 8 | - pos: [996, 964] |
9 | special: starting_room_paintings # Early Color Hallways painting is a hardcoded special case | 9 | special: starting_room_overhead |
10 | paintings: | 10 | painting: arrows_painting |
11 | - arrows_painting | 11 | - pos: [1044, 1012] |
12 | - pos: [905, 841] | 12 | special: early_color_hallways |
13 | painting: color_hallways | ||
14 | - pos: [915, 851] | ||
13 | room: Hedge Maze | 15 | room: Hedge Maze |
14 | door: Painting Shortcut | 16 | door: Painting Shortcut |
15 | paintings: | 17 | painting: garden_painting_tower2 |
16 | - garden_painting_tower2 | ||
17 | entrances: | 18 | entrances: |
18 | - garden_starting | 19 | - garden_starting |
19 | - pos: [1066, 841] | 20 | - pos: [1076, 851] |
20 | room: Courtyard | 21 | room: Courtyard |
21 | door: Painting Shortcut | 22 | door: Painting Shortcut |
22 | paintings: | 23 | painting: flower_painting_8 |
23 | - flower_painting_8 | ||
24 | entrances: | 24 | entrances: |
25 | - flower_starting | 25 | - flower_starting |
26 | - pos: [905, 895] | 26 | - pos: [915, 905] |
27 | room: The Wondrous (Doorknob) | 27 | room: The Wondrous (Doorknob) |
28 | door: Painting Shortcut | 28 | door: Painting Shortcut |
29 | paintings: | 29 | painting: symmetry_painting_a_starter |
30 | - symmetry_painting_a_starter | ||
31 | entrances: | 30 | entrances: |
32 | - symmetry_starting | 31 | - symmetry_starting |
33 | - pos: [1066, 868] | 32 | - pos: [1076, 905] |
34 | room: Outside The Bold | 33 | room: Outside The Bold |
35 | door: Painting Shortcut | 34 | door: Painting Shortcut |
36 | paintings: | 35 | painting: pencil_painting6 |
37 | - pencil_painting6 | ||
38 | entrances: | 36 | entrances: |
39 | - pencil_starting | 37 | - pencil_starting |
40 | - pos: [1066, 895] | 38 | - pos: [1076, 878] |
41 | room: Outside The Undeterred | 39 | room: Outside The Undeterred |
42 | door: Painting Shortcut | 40 | door: Painting Shortcut |
43 | paintings: | 41 | painting: blueman_painting_3 |
44 | - blueman_painting_3 | ||
45 | entrances: | 42 | entrances: |
46 | - blueman_starting | 43 | - blueman_starting |
47 | - pos: [905, 868] | 44 | - pos: [915, 878] |
48 | room: Outside The Agreeable | 45 | room: Outside The Agreeable |
49 | door: Painting Shortcut | 46 | door: Painting Shortcut |
50 | paintings: | 47 | painting: eyes_yellow_painting2 |
51 | - eyes_yellow_painting2 | ||
52 | entrances: | 48 | entrances: |
53 | - street_starting | 49 | - street_starting |
54 | - pos: [1211, 879] | 50 | - pos: [1211, 879] |
55 | room: Hidden Room | 51 | room: Hidden Room |
56 | door: Dead End Door | 52 | door: Dead End Door |
57 | - pos: [1291, 906] | 53 | - pos: [1292, 906] |
58 | room: Hidden Room | 54 | room: Hidden Room |
59 | door: Knight Night Entrance | 55 | door: Knight Night Entrance |
60 | - pos: [1103, 980] | 56 | - pos: [1104, 981] |
61 | room: Hidden Room | 57 | room: Hidden Room |
62 | door: Seeker Entrance | 58 | door: Seeker Entrance |
63 | - pos: [1173, 980] | 59 | - pos: [1174, 981] |
64 | room: Hidden Room | 60 | room: Hidden Room |
65 | door: Rhyme Room Entrance | 61 | door: Rhyme Room Entrance |
66 | - pos: [1116, 939] | 62 | - pos: [1114, 937] |
67 | paintings: | 63 | painting: owl_painting |
68 | - owl_painting | ||
69 | entrances: | 64 | entrances: |
70 | - owl_hidden | 65 | - owl_hidden |
71 | - pos: [986, 793] | 66 | - pos: [986, 793] |
72 | room: Second Room | 67 | room: Second Room |
73 | door: Exit Door | 68 | door: Exit Door |
74 | - pos: [798, 584] | 69 | - pos: [799, 584] |
75 | room: Hub Room | 70 | room: Hub Room |
76 | door: Crossroads Entrance | 71 | door: Crossroads Entrance |
77 | - pos: [932, 665] | 72 | - pos: [933, 665] |
78 | room: Hub Room | 73 | room: Hub Room |
79 | door: Tenacious Entrance | 74 | door: Tenacious Entrance |
80 | - pos: [1361, 578] | 75 | - pos: [1361, 579] |
81 | room: Hub Room | 76 | room: Hub Room |
82 | door: Shortcut to Hedge Maze | 77 | door: Shortcut to Hedge Maze |
83 | - pos: [1312, 841] | 78 | - pos: [1313, 842] |
84 | room: Hub Room | 79 | room: Hub Room |
85 | door: Near RAT Door | 80 | door: Near RAT Door |
86 | - pos: [1371, 729] | 81 | - pos: [1372, 729] |
87 | room: Hub Room | 82 | room: Hub Room |
88 | door: Traveled Entrance | 83 | door: Traveled Entrance |
89 | - pos: [1313, 686] | 84 | - pos: [1312, 696] |
90 | paintings: | 85 | painting: maze_painting |
91 | - maze_painting | ||
92 | exits: | 86 | exits: |
93 | - green_owl | 87 | - green_owl |
94 | - green_numbers | 88 | - green_numbers |
95 | - pos: [1172, 760] | 89 | - pos: [1174, 761] |
96 | sunwarp: | 90 | sunwarp: |
97 | dots: 1 | 91 | dots: 1 |
98 | type: enter | 92 | type: enter |
99 | - pos: [1302, 638] | 93 | - pos: [1302, 638] |
100 | room: Outside The Undeterred | 94 | room: Outside The Undeterred |
101 | door: Fours | 95 | door: Fours |
102 | - pos: [1243, 819] | 96 | - pos: [1243, 820] |
103 | room: Outside The Undeterred | 97 | room: Outside The Undeterred |
104 | door: Fours | 98 | door: Fours |
105 | - pos: [1276, 819] | 99 | - pos: [1276, 820] |
106 | room: Number Hunt | 100 | room: Number Hunt |
107 | door: Eights | 101 | door: Eights |
108 | - pos: [1263, 867] | 102 | - pos: [1253, 889] |
109 | paintings: | 103 | painting: smile_painting_6 |
110 | - smile_painting_6 | ||
111 | entrances: | 104 | entrances: |
112 | - smiley_deadend | 105 | - smiley_deadend |
113 | - pos: [1012, 1086] | 106 | - pos: [1013, 1087] |
114 | sunwarp: | 107 | sunwarp: |
115 | dots: 6 | 108 | dots: 6 |
116 | type: final | 109 | type: final |
117 | - pos: [938, 1002] | 110 | - pos: [948, 1012] |
118 | room: Pilgrim Antechamber | 111 | room: Pilgrim Antechamber |
119 | door: Sun Painting | 112 | door: Sun Painting |
120 | special: sun_painting | 113 | special: sun_painting |
114 | painting: pilgrim_painting2 | ||
121 | - pos: [1053, 1090] | 115 | - pos: [1053, 1090] |
122 | invisible: true | 116 | invisible: true |
123 | special: sun_painting_exit | 117 | special: sun_painting_exit |
@@ -136,7 +130,7 @@ | |||
136 | - pos: [932, 477] | 130 | - pos: [932, 477] |
137 | room: Crossroads | 131 | room: Crossroads |
138 | door: Tenacious Entrance | 132 | door: Tenacious Entrance |
139 | - pos: [638, 477] | 133 | - pos: [509, 477] |
140 | room: Crossroads | 134 | room: Crossroads |
141 | door: Discerning Entrance | 135 | door: Discerning Entrance |
142 | - pos: [905, 290] | 136 | - pos: [905, 290] |
@@ -145,15 +139,14 @@ | |||
145 | - pos: [894, 423] | 139 | - pos: [894, 423] |
146 | room: Crossroads | 140 | room: Crossroads |
147 | door: Words Sword Door | 141 | door: Words Sword Door |
148 | - pos: [632, 643] | 142 | - pos: [718, 557] |
149 | room: Crossroads | 143 | room: Crossroads |
150 | door: Eye Wall | 144 | door: Eye Wall |
151 | - pos: [638, 520] | 145 | - pos: [509, 434] |
152 | room: Crossroads | 146 | room: Crossroads |
153 | door: Roof Access | 147 | door: Roof Access |
154 | - pos: [756, 400] | 148 | - pos: [712, 514] |
155 | paintings: | 149 | painting: smile_painting_4 |
156 | - smile_painting_4 | ||
157 | entrances: | 150 | entrances: |
158 | - smiley_crossroads | 151 | - smiley_crossroads |
159 | - pos: [878, 509] | 152 | - pos: [878, 509] |
@@ -168,13 +161,12 @@ | |||
168 | door: Exit | 161 | door: Exit |
169 | - pos: [986, 290] | 162 | - pos: [986, 290] |
170 | room: Number Hunt | 163 | room: Number Hunt |
171 | door: Eights | 164 | door: Nines |
172 | - pos: [954, 247] | 165 | - pos: [954, 247] |
173 | room: Amen Name Area | 166 | room: Amen Name Area |
174 | door: Exit | 167 | door: Exit |
175 | - pos: [954, 222] | 168 | - pos: [985, 235] |
176 | paintings: | 169 | painting: west_afar |
177 | - west_afar | ||
178 | - pos: [986, 697] | 170 | - pos: [986, 697] |
179 | room: The Tenacious | 171 | room: The Tenacious |
180 | door: Shortcut to Hub Room | 172 | door: Shortcut to Hub Room |
@@ -205,21 +197,19 @@ | |||
205 | - pos: [1216, 525] | 197 | - pos: [1216, 525] |
206 | room: Outside The Agreeable | 198 | room: Outside The Agreeable |
207 | door: Agreeable Entrance | 199 | door: Agreeable Entrance |
208 | - pos: [1138, 287] | 200 | - pos: [1156, 299] |
209 | paintings: | 201 | painting: eyes_yellow_painting |
210 | - eyes_yellow_painting | ||
211 | exits: | 202 | exits: |
212 | - street_starting | 203 | - street_starting |
213 | - pos: [1088, 385] | 204 | - pos: [1088, 385] |
214 | sunwarp: | 205 | sunwarp: |
215 | dots: 6 | 206 | dots: 6 |
216 | type: enter | 207 | type: enter |
217 | - pos: [1195, 450] | 208 | - pos: [1195, 445] |
218 | room: Compass Room | 209 | room: Compass Room |
219 | door: Lookout Entrance | 210 | door: Lookout Entrance |
220 | - pos: [1214, 457] | 211 | - pos: [1226, 481] |
221 | paintings: | 212 | painting: pencil_painting7 |
222 | - pencil_painting7 | ||
223 | entrances: | 213 | entrances: |
224 | - pencil_compass | 214 | - pencil_compass |
225 | - pos: [1196, 417] | 215 | - pos: [1196, 417] |
@@ -248,9 +238,8 @@ | |||
248 | - pos: [1714, 434] | 238 | - pos: [1714, 434] |
249 | room: Hedge Maze | 239 | room: Hedge Maze |
250 | door: Observant Entrance | 240 | door: Observant Entrance |
251 | - pos: [1477, 343] | 241 | - pos: [1526, 401] |
252 | paintings: | 242 | painting: garden_painting_tower |
253 | - garden_painting_tower | ||
254 | exits: | 243 | exits: |
255 | - garden_starting | 244 | - garden_starting |
256 | - pos: [1565, 311] | 245 | - pos: [1565, 311] |
@@ -262,45 +251,48 @@ | |||
262 | - pos: [1414, 209] | 251 | - pos: [1414, 209] |
263 | room: The Observant | 252 | room: The Observant |
264 | door: Backside Door | 253 | door: Backside Door |
265 | - pos: [1624, 188] | 254 | - pos: [1592, 188] |
266 | room: The Observant | 255 | room: The Observant |
267 | door: Stairs | 256 | door: Stairs |
268 | - pos: [1667, 686] | 257 | - pos: [1694, 659] |
269 | room: The Incomparable | 258 | room: The Incomparable |
270 | door: Eight Door | 259 | door: Eight Door |
271 | - pos: [1784, 569] | 260 | - pos: [1799, 583] |
272 | paintings: | 261 | painting: crown_painting |
273 | - crown_painting | ||
274 | exits: | 262 | exits: |
275 | - crown_tower6 | 263 | - crown_tower6 |
276 | - pos: [1653, 717] | 264 | - pos: [1644, 685] |
277 | paintings: | 265 | painting: eight_painting2 |
278 | - eight_painting2 | ||
279 | entrances: | 266 | entrances: |
280 | - eight_alcove | 267 | - eight_alcove |
281 | - pos: [1653, 662] | 268 | - pos: [1660, 664] |
282 | paintings: | 269 | painting: eight_painting |
283 | - eight_painting | ||
284 | exits: | 270 | exits: |
285 | - eight_alcove | 271 | - eight_alcove |
286 | - pos: [697, 1471] | 272 | - pos: [695, 1471] |
287 | room: Orange Tower | 273 | room: Orange Tower |
288 | door: Second Floor | 274 | door: Second Floor |
289 | - pos: [633, 1406] | 275 | tilted: true |
276 | - pos: [631, 1406] | ||
290 | room: Orange Tower | 277 | room: Orange Tower |
291 | door: Third Floor | 278 | door: Third Floor |
292 | - pos: [570, 1343] | 279 | tilted: true |
280 | - pos: [567, 1341] | ||
293 | room: Orange Tower | 281 | room: Orange Tower |
294 | door: Fourth Floor | 282 | door: Fourth Floor |
295 | - pos: [504, 1279] | 283 | tilted: true |
284 | - pos: [502, 1277] | ||
296 | room: Orange Tower | 285 | room: Orange Tower |
297 | door: Fifth Floor | 286 | door: Fifth Floor |
298 | - pos: [440, 1215] | 287 | tilted: true |
288 | - pos: [438, 1213] | ||
299 | room: Orange Tower | 289 | room: Orange Tower |
300 | door: Sixth Floor | 290 | door: Sixth Floor |
301 | - pos: [379, 1153] | 291 | tilted: true |
292 | - pos: [378, 1152] | ||
302 | room: Orange Tower | 293 | room: Orange Tower |
303 | door: Seventh Floor | 294 | door: Seventh Floor |
295 | tilted: true | ||
304 | - pos: [905, 793] | 296 | - pos: [905, 793] |
305 | room: Orange Tower First Floor | 297 | room: Orange Tower First Floor |
306 | door: Shortcut to Hub Room | 298 | door: Shortcut to Hub Room |
@@ -320,7 +312,7 @@ | |||
320 | - pos: [722, 1439] | 312 | - pos: [722, 1439] |
321 | tags: | 313 | tags: |
322 | - tower2_undeterred | 314 | - tower2_undeterred |
323 | - pos: [533, 1375] | 315 | - pos: [549, 1375] |
324 | tags: | 316 | tags: |
325 | - tower3_tower3 | 317 | - tower3_tower3 |
326 | - pos: [662, 1375] | 318 | - pos: [662, 1375] |
@@ -377,10 +369,10 @@ | |||
377 | - pos: [1173, 1248] | 369 | - pos: [1173, 1248] |
378 | room: Orange Tower Third Floor | 370 | room: Orange Tower Third Floor |
379 | door: Rhyme Room Entrance | 371 | door: Rhyme Room Entrance |
380 | - pos: [1270, 1231] | 372 | - pos: [1274, 1253] |
381 | paintings: | 373 | painting: arrows_painting_6 |
382 | - arrows_painting_6 | 374 | - pos: [1293, 1234] |
383 | - flower_painting_5 | 375 | painting: flower_painting_5 |
384 | - pos: [1216, 1216] | 376 | - pos: [1216, 1216] |
385 | sunwarp: | 377 | sunwarp: |
386 | dots: 2 | 378 | dots: 2 |
@@ -396,77 +388,82 @@ | |||
396 | sunwarp: | 388 | sunwarp: |
397 | dots: 5 | 389 | dots: 5 |
398 | type: enter | 390 | type: enter |
399 | - pos: [877, 155] | 391 | - pos: [878, 155] |
400 | room: Number Hunt | 392 | room: Number Hunt |
401 | door: Eights | 393 | door: Eights |
402 | - pos: [844, 134] | 394 | - pos: [862, 144] |
403 | paintings: | 395 | painting: smile_painting_8 |
404 | - smile_painting_8 | ||
405 | entrances: | 396 | entrances: |
406 | - smiley_hotcrusts | 397 | - smiley_hotcrusts |
407 | - pos: [797, 155] | 398 | - pos: [798, 155] |
408 | sunwarp: | 399 | sunwarp: |
409 | dots: 2 | 400 | dots: 2 |
410 | type: enter | 401 | type: enter |
411 | - pos: [679, 985] | 402 | - pos: [680, 986] |
412 | room: Number Hunt | 403 | room: Number Hunt |
413 | door: Nines | 404 | door: Nines |
414 | - pos: [723, 953] | 405 | - pos: [723, 953] |
415 | room: Orange Tower Fifth Floor | 406 | room: Orange Tower Fifth Floor |
416 | door: Welcome Back | 407 | door: Welcome Back |
417 | - pos: [683, 944] | 408 | - pos: [690, 937] |
418 | paintings: | 409 | painting: east_afar |
419 | - east_afar | 410 | - pos: [535, 1205] |
420 | - pos: [548, 1221] | 411 | painting: hi_solved_painting3 |
421 | paintings: | 412 | - pos: [1574, 1424] |
422 | - hi_solved_painting3 | 413 | painting: hi_solved_painting2 |
423 | - pos: [1574, 1425] | 414 | - pos: [316, 1269] |
424 | paintings: | 415 | painting: arrows_painting_10 |
425 | - hi_solved_painting2 | 416 | - pos: [332, 1253] |
426 | - pos: [411, 1186] | 417 | painting: scenery_painting_5d_2 |
427 | paintings: | 418 | - pos: [347, 1237] |
428 | - arrows_painting_10 | 419 | painting: panda_painting_2 |
429 | - owl_painting_3 | ||
430 | - clock_painting | ||
431 | - scenery_painting_5d_2 | ||
432 | - symmetry_painting_b_7 | ||
433 | - panda_painting_2 | ||
434 | - crown_painting2 | ||
435 | - colors_painting2 | ||
436 | - cherry_painting2 | ||
437 | - hi_solved_painting | ||
438 | entrances: | 420 | entrances: |
439 | - owl_tower6 | ||
440 | - clock_tower6 | ||
441 | - panda_tower6 | 421 | - panda_tower6 |
442 | - crown_tower6 | 422 | - pos: [363, 1221] |
443 | - apple_tower6 | 423 | painting: colors_painting2 |
424 | - pos: [380, 1205] | ||
425 | painting: clock_painting | ||
426 | entrances: | ||
427 | - clock_tower6 | ||
428 | - pos: [396, 1221] | ||
429 | painting: hi_solved_painting | ||
444 | exits: | 430 | exits: |
445 | - hi_scientific | 431 | - hi_scientific |
446 | - pos: [349, 1124] | 432 | - pos: [380, 1237] |
447 | paintings: | 433 | painting: crown_painting2 |
448 | - map_painting2 | 434 | entrances: |
435 | - crown_tower6 | ||
436 | - pos: [363, 1253] | ||
437 | painting: owl_painting_3 | ||
438 | entrances: | ||
439 | - owl_tower6 | ||
440 | - pos: [347, 1269] | ||
441 | painting: symmetry_painting_b_7 | ||
442 | - pos: [332, 1285] | ||
443 | painting: cherry_painting2 | ||
444 | entrances: | ||
445 | - apple_tower6 | ||
446 | - pos: [360, 1135] | ||
447 | # TODO: there isn't really a spot for this one | ||
448 | painting: map_painting2 | ||
449 | - pos: [436, 1159] | 449 | - pos: [436, 1159] |
450 | room: Orange Tower Seventh Floor | 450 | room: Orange Tower Seventh Floor |
451 | door: Mastery | 451 | door: Mastery |
452 | - pos: [544, 1159] | 452 | - pos: [535, 1146] |
453 | paintings: | 453 | painting: arrows_painting_11 |
454 | - arrows_painting_11 | ||
455 | - pos: [498, 284] | 454 | - pos: [498, 284] |
456 | room: Courtyard | 455 | room: Courtyard |
457 | door: Green Barrier | 456 | door: Green Barrier |
458 | - pos: [556, 233] | 457 | - pos: [589, 230] |
459 | paintings: | 458 | painting: flower_painting_7 |
460 | - flower_painting_7 | ||
461 | exits: | 459 | exits: |
462 | - flower_starting | 460 | - flower_starting |
463 | - flower_arrow | 461 | - flower_arrow |
464 | - pos: [600, 332] | 462 | - pos: [600, 332] |
465 | room: Number Hunt | 463 | room: Number Hunt |
466 | door: Nines | 464 | door: Nines |
467 | - pos: [579, 350] | 465 | - pos: [589, 369] |
468 | paintings: | 466 | painting: blueman_painting |
469 | - blueman_painting | ||
470 | entrances: | 467 | entrances: |
471 | - blueman_courtyard | 468 | - blueman_courtyard |
472 | - pos: [530, 310] | 469 | - pos: [530, 310] |
@@ -502,9 +499,8 @@ | |||
502 | - pos: [922, 107] | 499 | - pos: [922, 107] |
503 | room: The Colorful (Gray) | 500 | room: The Colorful (Gray) |
504 | door: Progress Door | 501 | door: Progress Door |
505 | - pos: [967, 107] | 502 | - pos: [969, 96] |
506 | paintings: | 503 | painting: arrows_painting_12 |
507 | - arrows_painting_12 | ||
508 | - pos: [878, 954] | 504 | - pos: [878, 954] |
509 | room: Welcome Back Area | 505 | room: Welcome Back Area |
510 | door: Shortcut to Starting Room | 506 | door: Shortcut to Starting Room |
@@ -537,10 +533,10 @@ | |||
537 | - pos: [1737, 1053] | 533 | - pos: [1737, 1053] |
538 | entrances: | 534 | entrances: |
539 | - gallery_wb | 535 | - gallery_wb |
540 | - pos: [1690, 268] | 536 | - pos: [1784, 395] |
541 | entrances: | 537 | entrances: |
542 | - observant_wb | 538 | - observant_wb |
543 | - pos: [250, 604] | 539 | - pos: [122, 350] |
544 | entrances: | 540 | entrances: |
545 | - scientific_wb | 541 | - scientific_wb |
546 | - pos: [1553, 1467] | 542 | - pos: [1553, 1467] |
@@ -549,17 +545,19 @@ | |||
549 | - pos: [1478, 498] | 545 | - pos: [1478, 498] |
550 | room: Owl Hallway | 546 | room: Owl Hallway |
551 | door: Shortcut to Hedge Maze | 547 | door: Shortcut to Hedge Maze |
552 | - pos: [1480, 551] | 548 | - pos: [1467, 535] |
553 | paintings: | 549 | painting: arrows_painting_8 |
554 | - arrows_painting_8 | 550 | - pos: [1467, 562] |
555 | - maze_painting_2 | 551 | painting: maze_painting_2 |
556 | - owl_painting_2 | ||
557 | - clock_painting_4 | ||
558 | entrances: | 552 | entrances: |
559 | - green_owl | 553 | - green_owl |
554 | - pos: [1510, 535] | ||
555 | painting: owl_painting_2 | ||
560 | exits: | 556 | exits: |
561 | - owl_hidden | 557 | - owl_hidden |
562 | - owl_tower6 | 558 | - owl_tower6 |
559 | - pos: [1510, 562] | ||
560 | painting: clock_painting_4 | ||
563 | - pos: [1478, 938] | 561 | - pos: [1478, 938] |
564 | room: Number Hunt | 562 | room: Number Hunt |
565 | door: Sevens | 563 | door: Sevens |
@@ -584,40 +582,38 @@ | |||
584 | - pos: [1511, 841] | 582 | - pos: [1511, 841] |
585 | room: Outside The Initiated | 583 | room: Outside The Initiated |
586 | door: Initiated Entrance | 584 | door: Initiated Entrance |
587 | - pos: [1141, 1441] | 585 | - pos: [1071, 1441] |
588 | room: Orange Tower Third Floor | 586 | room: Orange Tower Third Floor |
589 | door: Orange Barrier | 587 | door: Orange Barrier |
590 | - pos: [1173, 1441] | 588 | - pos: [1104, 1441] |
591 | room: Outside The Initiated | 589 | room: Outside The Initiated |
592 | door: Green Barrier | 590 | door: Green Barrier |
593 | - pos: [1206, 1441] | 591 | - pos: [1137, 1441] |
594 | room: Outside The Initiated | 592 | room: Outside The Initiated |
595 | door: Purple Barrier | 593 | door: Purple Barrier |
596 | - pos: [1189, 1355] | 594 | - pos: [1120, 1355] |
597 | room: Outside The Initiated | 595 | room: Outside The Initiated |
598 | door: Entrance | 596 | door: Entrance |
599 | - pos: [1580, 729] | 597 | - pos: [1580, 729] |
600 | room: The Incomparable | 598 | room: The Incomparable |
601 | door: "Eight Door (Outside The Initiated)" | 599 | door: "Eight Door (Outside The Initiated)" |
602 | - pos: [1530, 938] | 600 | - pos: [1548, 948] |
603 | paintings: | 601 | painting: clock_painting_5 |
604 | - clock_painting_5 | ||
605 | entrances: | 602 | entrances: |
606 | - clock_initiated | 603 | - clock_initiated |
607 | - pos: [1546, 938] | 604 | - pos: [1575, 969] |
608 | paintings: | 605 | painting: arrows_painting_2 |
609 | - clock_painting_2 | 606 | - pos: [1575, 926] |
610 | - arrows_painting_2 | 607 | painting: clock_painting_2 |
611 | exits: | 608 | exits: |
612 | - clock_tower6 | 609 | - clock_tower6 |
613 | - clock_initiated | 610 | - clock_initiated |
614 | - pos: [1579, 813] | 611 | - pos: [1580, 814] |
615 | sunwarp: | 612 | sunwarp: |
616 | dots: 3 | 613 | dots: 3 |
617 | type: exit | 614 | type: exit |
618 | - pos: [1444, 896] | 615 | - pos: [1462, 915] |
619 | paintings: | 616 | painting: smile_painting_1 |
620 | - smile_painting_1 | ||
621 | entrances: | 617 | entrances: |
622 | - smiley_initiated | 618 | - smiley_initiated |
623 | - pos: [1430, 691] | 619 | - pos: [1430, 691] |
@@ -626,18 +622,25 @@ | |||
626 | - pos: [1468, 728] | 622 | - pos: [1468, 728] |
627 | room: The Traveled | 623 | room: The Traveled |
628 | door: Color Hallways Entrance | 624 | door: Color Hallways Entrance |
629 | - pos: [1533, 707] | 625 | - pos: [1535, 644] |
630 | tags: | 626 | tags: |
631 | - red_ch | 627 | - red_ch |
628 | - pos: [1535, 667] | ||
629 | tags: | ||
632 | - blue_ch | 630 | - blue_ch |
631 | - pos: [1535, 689] | ||
632 | tags: | ||
633 | - yellow_ch | 633 | - yellow_ch |
634 | - pos: [1535, 711] | ||
635 | tags: | ||
634 | - green_ch | 636 | - green_ch |
637 | - pos: [1499, 678] | ||
635 | exits: | 638 | exits: |
636 | - early_ch | 639 | - early_ch |
637 | - pos: [1567, 1264] | 640 | - pos: [1567, 1264] |
638 | tags: | 641 | tags: |
639 | - red_ch | 642 | - red_ch |
640 | - pos: [150, 808] | 643 | - pos: [150, 748] |
641 | tags: | 644 | tags: |
642 | - blue_ch | 645 | - blue_ch |
643 | - pos: [626, 371] | 646 | - pos: [626, 371] |
@@ -655,21 +658,21 @@ | |||
655 | - pos: [1468, 1377] | 658 | - pos: [1468, 1377] |
656 | room: Outside The Bold | 659 | room: Outside The Bold |
657 | door: Bold Entrance | 660 | door: Bold Entrance |
658 | - pos: [1425, 1358] | 661 | - pos: [1446, 1344] |
659 | paintings: | 662 | painting: pencil_painting2 |
660 | - pencil_painting2 | ||
661 | - north_missing2 | ||
662 | exits: | 663 | exits: |
663 | - pencil_compass | 664 | - pencil_compass |
664 | - pencil_starting | 665 | - pencil_starting |
665 | - pencil_directional | 666 | - pencil_directional |
667 | - pos: [1580, 1344] | ||
668 | painting: north_missing2 | ||
666 | - pos: [1334, 1419] | 669 | - pos: [1334, 1419] |
667 | room: Outside The Bold | 670 | room: Outside The Bold |
668 | door: Steady Entrance | 671 | door: Steady Entrance |
669 | - pos: [445, 1048] | 672 | - pos: [445, 1048] |
670 | exits: | 673 | exits: |
671 | - undeterred_artistic | 674 | - undeterred_artistic |
672 | - pos: [279, 1071] | 675 | - pos: [273, 1071] |
673 | room: Number Hunt | 676 | room: Number Hunt |
674 | door: Zero Door | 677 | door: Zero Door |
675 | - pos: [338, 1071] | 678 | - pos: [338, 1071] |
@@ -684,17 +687,15 @@ | |||
684 | - pos: [242, 1071] | 687 | - pos: [242, 1071] |
685 | room: Outside The Undeterred | 688 | room: Outside The Undeterred |
686 | door: Undeterred Entrance | 689 | door: Undeterred Entrance |
687 | - pos: [60, 1017] | 690 | - pos: [149, 937] |
688 | paintings: | 691 | painting: blueman_painting_2 |
689 | - blueman_painting_2 | ||
690 | exits: | 692 | exits: |
691 | - blueman_courtyard | 693 | - blueman_courtyard |
692 | - blueman_starting | 694 | - blueman_starting |
693 | - pos: [402, 1012] | 695 | - pos: [412, 1017] |
694 | room: Outside The Undeterred | 696 | room: Outside The Undeterred |
695 | door: Green Painting | 697 | door: Green Painting |
696 | paintings: | 698 | painting: maze_painting_3 |
697 | - maze_painting_3 | ||
698 | entrances: | 699 | entrances: |
699 | - green_numbers | 700 | - green_numbers |
700 | - pos: [134, 1007] | 701 | - pos: [134, 1007] |
@@ -704,7 +705,7 @@ | |||
704 | - pos: [719, 1039] | 705 | - pos: [719, 1039] |
705 | room: Outside The Undeterred | 706 | room: Outside The Undeterred |
706 | door: Challenge Entrance | 707 | door: Challenge Entrance |
707 | - pos: [483, 1039] | 708 | - pos: [456, 1039] |
708 | room: Outside The Undeterred | 709 | room: Outside The Undeterred |
709 | door: Number Hunt | 710 | door: Number Hunt |
710 | - pos: [563, 1071] | 711 | - pos: [563, 1071] |
@@ -725,11 +726,10 @@ | |||
725 | - pos: [525, 1002] | 726 | - pos: [525, 1002] |
726 | room: Number Hunt | 727 | room: Number Hunt |
727 | door: Door to Directional Gallery | 728 | door: Door to Directional Gallery |
728 | - pos: [659, 1014] | 729 | - pos: [572, 1017] |
729 | room: Number Hunt | 730 | room: Number Hunt |
730 | door: Eights | 731 | door: Eights |
731 | paintings: | 732 | painting: smile_painting_5 |
732 | - smile_painting_5 | ||
733 | entrances: | 733 | entrances: |
734 | - smiley_numbers | 734 | - smiley_numbers |
735 | - pos: [557, 953] | 735 | - pos: [557, 953] |
@@ -747,87 +747,67 @@ | |||
747 | - pos: [268, 825] | 747 | - pos: [268, 825] |
748 | room: Directional Gallery | 748 | room: Directional Gallery |
749 | door: Yellow Barrier | 749 | door: Yellow Barrier |
750 | - pos: [231, 681] | 750 | - pos: [214, 563] |
751 | room: Number Hunt | 751 | room: Number Hunt |
752 | door: Sixes | 752 | door: Sixes |
753 | - pos: [242, 980] | 753 | - pos: [242, 980] |
754 | room: Directional Gallery | 754 | room: Directional Gallery |
755 | door: Shortcut to The Undeterred | 755 | door: Shortcut to The Undeterred |
756 | - pos: [351, 927] | 756 | - pos: [364, 942] |
757 | paintings: | 757 | painting: boxes_painting |
758 | - boxes_painting | ||
759 | entrances: | 758 | entrances: |
760 | - lattice_directional | 759 | - lattice_directional |
761 | - pos: [272, 927] | 760 | - pos: [278, 942] |
762 | paintings: | 761 | painting: smile_painting_7 |
763 | - smile_painting_7 | ||
764 | entrances: | 762 | entrances: |
765 | - smiley_directional | 763 | - smiley_directional |
766 | - pos: [214, 822] | 764 | - pos: [278, 803] |
767 | paintings: | 765 | painting: cherry_painting |
768 | - cherry_painting | ||
769 | entrances: | 766 | entrances: |
770 | - apple_directional | 767 | - apple_directional |
771 | - pos: [266, 735] | 768 | - pos: [262, 573] |
772 | room: Number Hunt | 769 | room: Number Hunt |
773 | door: Sixes | 770 | door: Sixes |
774 | paintings: | 771 | painting: pencil_painting3 |
775 | - pencil_painting3 | ||
776 | entrances: | 772 | entrances: |
777 | - pencil_directional | 773 | - pencil_directional |
778 | - pos: [215, 735] | 774 | - pos: [278, 685] |
779 | paintings: | 775 | painting: flower_painting_4 |
780 | - flower_painting_4 | ||
781 | - pos: [626, 851] | 776 | - pos: [626, 851] |
782 | sunwarp: | 777 | sunwarp: |
783 | dots: 6 | 778 | dots: 6 |
784 | type: exit | 779 | type: exit |
785 | - pos: [1141, 1441] | ||
786 | room: Orange Tower Third Floor | ||
787 | door: Orange Barrier | ||
788 | - pos: [1174, 1441] | ||
789 | room: Outside The Initiated | ||
790 | door: Green Barrier | ||
791 | - pos: [1205, 1441] | ||
792 | room: Outside The Initiated | ||
793 | door: Purple Barrier | ||
794 | - pos: [1334, 1377] | 780 | - pos: [1334, 1377] |
795 | room: Color Hunt | 781 | room: Color Hunt |
796 | door: Shortcut to The Steady | 782 | door: Shortcut to The Steady |
797 | - pos: [1280, 1375] | 783 | - pos: [1312, 1333] |
798 | paintings: | 784 | painting: arrows_painting_7 |
799 | - arrows_painting_7 | 785 | - pos: [1226, 1333] |
800 | - pos: [1233, 1321] | ||
801 | room: Outside The Initiated | 786 | room: Outside The Initiated |
802 | door: Entrance | 787 | door: Entrance |
803 | paintings: | 788 | painting: fruitbowl_painting3 |
804 | - fruitbowl_painting3 | 789 | - pos: [1260, 1323] |
805 | - pos: [1290, 1323] | ||
806 | sunwarp: | 790 | sunwarp: |
807 | dots: 5 | 791 | dots: 5 |
808 | type: exit | 792 | type: exit |
809 | - pos: [1189, 1356] | 793 | - pos: [1092, 1333] |
810 | room: Outside The Initiated | 794 | painting: colors_painting |
811 | door: Entrance | ||
812 | - pos: [1154, 1332] | ||
813 | paintings: | ||
814 | - colors_painting | ||
815 | - pos: [1640, 1260] | 795 | - pos: [1640, 1260] |
816 | room: The Bearer | 796 | room: The Bearer |
817 | door: Backside Door | 797 | door: Backside Door |
818 | - pos: [1468, 1287] | 798 | - pos: [1468, 1287] |
819 | room: The Bearer | 799 | room: The Bearer |
820 | door: Entrance | 800 | door: Entrance |
821 | - pos: [1430, 1232] | 801 | - pos: [1431, 1233] |
822 | room: Number Hunt | 802 | room: Number Hunt |
823 | door: Sixes | 803 | door: Sixes |
824 | - pos: [1388, 1152] | 804 | - pos: [1388, 1152] |
825 | room: Bearer Side Area | 805 | room: Bearer Side Area |
826 | door: Shortcut to Tower | 806 | door: Shortcut to Tower |
827 | - pos: [1273, 1442] | 807 | - pos: [1264, 1430] |
828 | paintings: | 808 | painting: pencil_painting5 |
829 | - pencil_painting5 | 809 | - pos: [1291, 1430] |
830 | - pencil_painting4 | 810 | painting: pencil_painting4 |
831 | - pos: [1355, 1092] | 811 | - pos: [1355, 1092] |
832 | room: Knight Night (Final) | 812 | room: Knight Night (Final) |
833 | door: Exit | 813 | door: Exit |
@@ -838,9 +818,8 @@ | |||
838 | # Complex case, because this is also blocked by Knight Night (Final) - Exit | 818 | # Complex case, because this is also blocked by Knight Night (Final) - Exit |
839 | room: Number Hunt | 819 | room: Number Hunt |
840 | door: Sevens | 820 | door: Sevens |
841 | - pos: [1653, 101] | 821 | - pos: [1687, 117] |
842 | paintings: | 822 | painting: smile_painting_9 |
843 | - smile_painting_9 | ||
844 | exits: | 823 | exits: |
845 | - smiley_crossroads | 824 | - smiley_crossroads |
846 | - smiley_deadend | 825 | - smiley_deadend |
@@ -850,88 +829,113 @@ | |||
850 | - smiley_initiated | 829 | - smiley_initiated |
851 | - smiley_gallery | 830 | - smiley_gallery |
852 | - smiley_theysee | 831 | - smiley_theysee |
853 | - pos: [1656, 139] | 832 | - pos: [1677, 161] |
854 | room: The Artistic (Smiley) | 833 | room: The Artistic (Smiley) |
855 | door: Door to Panda | 834 | door: Door to Panda |
856 | - pos: [1711, 140] | 835 | - pos: [1711, 140] |
857 | entrances: | 836 | entrances: |
858 | - undeterred_artistic | 837 | - undeterred_artistic |
859 | - pos: [1653, 169] | 838 | - pos: [1687, 224] |
860 | paintings: | 839 | painting: panda_painting_3 |
861 | - panda_painting_3 | ||
862 | exits: | 840 | exits: |
863 | - panda_tower6 | 841 | - panda_tower6 |
864 | - panda_hallway | 842 | - panda_hallway |
865 | - pos: [1708, 171] | 843 | - pos: [1731, 215] |
866 | room: The Artistic (Panda) | 844 | room: The Artistic (Panda) |
867 | door: Door to Lattice | 845 | door: Door to Lattice |
868 | - pos: [1761, 169] | 846 | - pos: [1794, 224] |
869 | paintings: | 847 | painting: boxes_painting2 |
870 | - boxes_painting2 | ||
871 | exits: | 848 | exits: |
872 | - lattice_directional | 849 | - lattice_directional |
873 | - pos: [1762, 139] | 850 | - pos: [1785, 161] |
874 | room: The Artistic (Lattice) | 851 | room: The Artistic (Lattice) |
875 | door: Door to Apple | 852 | door: Door to Apple |
876 | - pos: [1761, 101] | 853 | - pos: [1794, 117] |
877 | paintings: | 854 | painting: cherry_painting3 |
878 | - cherry_painting3 | ||
879 | exits: | 855 | exits: |
880 | - apple_tower6 | 856 | - apple_tower6 |
881 | - apple_directional | 857 | - apple_directional |
882 | - pos: [1708, 107] | 858 | - pos: [1731, 107] |
883 | room: The Artistic (Apple) | 859 | room: The Artistic (Apple) |
884 | door: Door to Smiley | 860 | door: Door to Smiley |
885 | - pos: [370, 681] | 861 | - pos: [370, 563] |
886 | room: Number Hunt | 862 | room: Number Hunt |
887 | door: Eights | 863 | door: Eights |
888 | - pos: [411, 685] | 864 | - pos: [637, 605] |
889 | paintings: | 865 | painting: eye_painting |
890 | - eye_painting_2 | 866 | entrances: |
891 | - smile_painting_2 | 867 | - crossroads_eyewall |
868 | - pos: [610, 605] | ||
869 | exits: | ||
870 | - crossroads_eyewall | ||
871 | - pos: [417, 573] | ||
872 | painting: eye_painting_2 | ||
873 | exits: | ||
874 | - crossroads_eyewall | ||
875 | - pos: [342, 573] | ||
876 | painting: smile_painting_2 | ||
892 | entrances: | 877 | entrances: |
893 | - smiley_theysee | 878 | - smiley_theysee |
894 | - pos: [310, 750] | 879 | - pos: [311, 750] |
895 | room: The Eyes They See | 880 | room: The Eyes They See |
896 | door: Exit | 881 | door: Exit |
897 | - pos: [334, 798] | 882 | - pos: [348, 803] |
898 | paintings: | 883 | painting: arrows_painting_5 |
899 | - arrows_painting_5 | 884 | - pos: [370, 751] |
900 | - pos: [370, 792] | ||
901 | room: Outside The Wondrous | 885 | room: Outside The Wondrous |
902 | door: Wondrous Entrance | 886 | door: Wondrous Entrance |
903 | - pos: [367, 752] | 887 | - pos: [428, 696] |
904 | paintings: | 888 | painting: symmetry_painting_a_1 |
905 | - symmetry_painting_a_1 | ||
906 | - symmetry_painting_b_1 | ||
907 | - symmetry_painting_a_3 | ||
908 | - symmetry_painting_a_5 | ||
909 | - symmetry_painting_b_4 | ||
910 | - symmetry_painting_a_2 | ||
911 | - symmetry_painting_b_2 | ||
912 | - symmetry_painting_a_6 | ||
913 | - symmetry_painting_b_6 | ||
914 | exits: | 889 | exits: |
915 | - symmetry_starting | 890 | - symmetry_starting |
916 | - pos: [407, 755] | 891 | - symmetry_a_chandelier |
892 | - symmetry_a_table | ||
893 | - pos: [428, 749] | ||
894 | painting: symmetry_painting_b_1 | ||
895 | entrances: | ||
896 | - symmetry_b_table | ||
897 | - pos: [471, 696] | ||
898 | painting: symmetry_painting_a_3 | ||
899 | - pos: [538, 699] | ||
900 | painting: symmetry_painting_a_5 | ||
901 | entrances: | ||
902 | - symmetry_a_chandelier | ||
903 | - pos: [562, 728] | ||
904 | painting: symmetry_painting_b_4 | ||
905 | - pos: [422, 647] | ||
906 | painting: symmetry_painting_a_2 | ||
907 | entrances: | ||
908 | - symmetry_a_table | ||
909 | - pos: [364, 648] | ||
910 | painting: symmetry_painting_b_2 | ||
911 | exits: | ||
912 | - symmetry_b_table | ||
913 | - symmetry_b_fire | ||
914 | - pos: [449, 647] | ||
915 | painting: symmetry_painting_a_6 | ||
916 | - pos: [508, 647] | ||
917 | painting: symmetry_painting_b_6 | ||
918 | entrances: | ||
919 | - symmetry_b_fire | ||
920 | - pos: [558, 665] | ||
921 | room: The Wondrous | ||
922 | door: Exit | ||
923 | - pos: [535, 647] | ||
917 | room: The Wondrous | 924 | room: The Wondrous |
918 | door: Exit | 925 | door: Exit |
919 | paintings: | 926 | painting: arrows_painting_9 |
920 | - arrows_painting_9 | 927 | - pos: [610, 674] |
921 | - pos: [449, 755] | 928 | painting: flower_painting_6 |
922 | paintings: | ||
923 | - flower_painting_6 | ||
924 | entrances: | 929 | entrances: |
925 | - flower_arrow | 930 | - flower_arrow |
926 | - pos: [1101, 222] | 931 | - pos: [1156, 262] |
927 | paintings: | 932 | painting: panda_painting |
928 | - panda_painting | ||
929 | entrances: | 933 | entrances: |
930 | - panda_hallway | 934 | - panda_hallway |
931 | - pos: [1152, 209] | 935 | - pos: [1152, 209] |
932 | room: Hallway Room (1) | 936 | room: Hallway Room (1) |
933 | door: Exit | 937 | door: Exit |
934 | - pos: [1189, 170] | 938 | - pos: [1190, 171] |
935 | room: Hallway Room (2) | 939 | room: Hallway Room (2) |
936 | door: Exit | 940 | door: Exit |
937 | - pos: [1238, 124] | 941 | - pos: [1238, 124] |
@@ -946,16 +950,17 @@ | |||
946 | - pos: [1415, 140] | 950 | - pos: [1415, 140] |
947 | room: Number Hunt | 951 | room: Number Hunt |
948 | door: Nines | 952 | door: Nines |
949 | - pos: [1458, 133] | 953 | - pos: [1424, 85] |
950 | paintings: | 954 | painting: south_afar |
951 | - south_afar | ||
952 | - pos: [826, 1452] | 955 | - pos: [826, 1452] |
953 | room: Outside The Wanderer | 956 | room: Outside The Wanderer |
954 | door: Wanderer Entrance | 957 | door: Wanderer Entrance |
958 | tilted: true | ||
955 | - pos: [763, 1465] | 959 | - pos: [763, 1465] |
956 | room: Outside The Wanderer | 960 | room: Outside The Wanderer |
957 | door: Tower Entrance | 961 | door: Tower Entrance |
958 | - pos: [1655, 1151] | 962 | tilted: true |
963 | - pos: [1656, 1152] | ||
959 | room: Number Hunt | 964 | room: Number Hunt |
960 | door: Eights | 965 | door: Eights |
961 | - pos: [1623, 1044] | 966 | - pos: [1623, 1044] |
@@ -973,15 +978,18 @@ | |||
973 | - pos: [1511, 1119] | 978 | - pos: [1511, 1119] |
974 | room: Art Gallery | 979 | room: Art Gallery |
975 | door: Exit | 980 | door: Exit |
976 | - pos: [1654, 1116] | 981 | - pos: [1730, 1162] |
977 | paintings: | 982 | painting: flower_painting_2 |
978 | - smile_painting_3 | 983 | - pos: [1730, 1189] |
979 | - flower_painting_2 | 984 | painting: map_painting |
980 | - scenery_painting_0a | 985 | - pos: [1698, 1189] |
981 | - map_painting | 986 | painting: smile_painting_3 |
982 | - fruitbowl_painting4 | ||
983 | entrances: | 987 | entrances: |
984 | - smiley_gallery | 988 | - smiley_gallery |
989 | - pos: [1714, 1215] | ||
990 | painting: fruitbowl_painting4 | ||
991 | - pos: [1714, 1242] | ||
992 | painting: scenery_painting_0a | ||
985 | - pos: [1120, 1286] | 993 | - pos: [1120, 1286] |
986 | room: Rhyme Room (Smiley) | 994 | room: Rhyme Room (Smiley) |
987 | door: Door to Target | 995 | door: Door to Target |
@@ -1000,9 +1008,8 @@ | |||
1000 | - pos: [1120, 1195] | 1008 | - pos: [1120, 1195] |
1001 | room: Rhyme Room (Circle) | 1009 | room: Rhyme Room (Circle) |
1002 | door: Door to Smiley | 1010 | door: Door to Smiley |
1003 | - pos: [1118, 1137] | 1011 | - pos: [1130, 1124] |
1004 | paintings: | 1012 | painting: arrows_painting_3 |
1005 | - arrows_painting_3 | ||
1006 | - pos: [1050, 1142] | 1013 | - pos: [1050, 1142] |
1007 | room: Rhyme Room (Looped Square) | 1014 | room: Rhyme Room (Looped Square) |
1008 | door: Door to Circle | 1015 | door: Door to Circle |
@@ -1015,26 +1022,41 @@ | |||
1015 | - pos: [852, 1200] | 1022 | - pos: [852, 1200] |
1016 | room: Rhyme Room (Target) | 1023 | room: Rhyme Room (Target) |
1017 | door: Door to Cross | 1024 | door: Door to Cross |
1018 | - pos: [850, 1138] | 1025 | - pos: [862, 1124] |
1019 | paintings: | 1026 | painting: arrows_painting_4 |
1020 | - arrows_painting_4 | ||
1021 | - pos: [1592, 1442] | 1027 | - pos: [1592, 1442] |
1022 | room: Room Room | 1028 | room: Room Room |
1023 | door: Cellar Exit | 1029 | door: Cellar Exit |
1024 | - pos: [1570, 938] | 1030 | - pos: [1623, 938] |
1025 | room: Outside The Wise | 1031 | room: Outside The Wise |
1026 | door: Wise Entrance | 1032 | door: Wise Entrance |
1027 | - pos: [1653, 935] | 1033 | - pos: [1665, 920] |
1028 | paintings: | 1034 | painting: clock_painting_3 |
1029 | - clock_painting_3 | 1035 | - pos: [241, 348] |
1030 | - pos: [369, 605] | ||
1031 | room: Outside The Scientific | 1036 | room: Outside The Scientific |
1032 | door: Scientific Entrance | 1037 | door: Scientific Entrance |
1033 | - pos: [294, 602] | 1038 | - pos: [176, 326] |
1034 | paintings: | 1039 | painting: hi_solved_painting4 |
1035 | - hi_solved_painting4 | ||
1036 | entrances: | 1040 | entrances: |
1037 | - hi_scientific | 1041 | - hi_scientific |
1038 | - pos: [814, 1001] | 1042 | - pos: [815, 1002] |
1039 | room: Challenge Room | 1043 | room: Challenge Room |
1040 | door: Welcome Door | 1044 | door: Welcome Door |
1045 | - pos: [104, 1208] | ||
1046 | special: color_black | ||
1047 | - pos: [104, 1249] | ||
1048 | special: color_red | ||
1049 | - pos: [104, 1290] | ||
1050 | special: color_yellow | ||
1051 | - pos: [104, 1330] | ||
1052 | special: color_blue | ||
1053 | - pos: [104, 1371] | ||
1054 | special: color_purple | ||
1055 | - pos: [104, 1411] | ||
1056 | special: color_orange | ||
1057 | - pos: [104, 1451] | ||
1058 | special: color_green | ||
1059 | - pos: [104, 1491] | ||
1060 | special: color_brown | ||
1061 | - pos: [104, 1531] | ||
1062 | special: color_gray | ||
diff --git a/src/achievements_pane.cpp b/src/achievements_pane.cpp index 8ec3727..d23c434 100644 --- a/src/achievements_pane.cpp +++ b/src/achievements_pane.cpp | |||
@@ -8,23 +8,24 @@ AchievementsPane::AchievementsPane(wxWindow* parent) | |||
8 | AppendColumn("Achievement"); | 8 | AppendColumn("Achievement"); |
9 | 9 | ||
10 | for (int panel_id : GD_GetAchievementPanels()) { | 10 | for (int panel_id : GD_GetAchievementPanels()) { |
11 | achievement_names_.push_back(GD_GetPanel(panel_id).achievement_name); | 11 | const Panel& panel = GD_GetPanel(panel_id); |
12 | achievements_.emplace_back(panel.achievement_name, panel.solve_index); | ||
12 | } | 13 | } |
13 | 14 | ||
14 | std::sort(std::begin(achievement_names_), std::end(achievement_names_)); | 15 | std::sort(std::begin(achievements_), std::end(achievements_)); |
15 | 16 | ||
16 | for (int i = 0; i < achievement_names_.size(); i++) { | 17 | for (int i = 0; i < achievements_.size(); i++) { |
17 | InsertItem(i, achievement_names_.at(i)); | 18 | InsertItem(i, std::get<0>(achievements_.at(i))); |
18 | } | 19 | } |
19 | 20 | ||
20 | SetColumnWidth(0, wxLIST_AUTOSIZE); | 21 | SetColumnWidth(0, wxLIST_AUTOSIZE_USEHEADER); |
21 | 22 | ||
22 | UpdateIndicators(); | 23 | UpdateIndicators(); |
23 | } | 24 | } |
24 | 25 | ||
25 | void AchievementsPane::UpdateIndicators() { | 26 | void AchievementsPane::UpdateIndicators() { |
26 | for (int i = 0; i < achievement_names_.size(); i++) { | 27 | for (int i = 0; i < achievements_.size(); i++) { |
27 | if (AP_HasAchievement(achievement_names_.at(i))) { | 28 | if (AP_IsPanelSolved(std::get<1>(achievements_.at(i)))) { |
28 | SetItemTextColour(i, *wxBLACK); | 29 | SetItemTextColour(i, *wxBLACK); |
29 | } else { | 30 | } else { |
30 | SetItemTextColour(i, *wxRED); | 31 | SetItemTextColour(i, *wxRED); |
diff --git a/src/achievements_pane.h b/src/achievements_pane.h index ac88cac..941b5e3 100644 --- a/src/achievements_pane.h +++ b/src/achievements_pane.h | |||
@@ -9,6 +9,10 @@ | |||
9 | 9 | ||
10 | #include <wx/listctrl.h> | 10 | #include <wx/listctrl.h> |
11 | 11 | ||
12 | #include <string> | ||
13 | #include <tuple> | ||
14 | #include <vector> | ||
15 | |||
12 | class AchievementsPane : public wxListView { | 16 | class AchievementsPane : public wxListView { |
13 | public: | 17 | public: |
14 | explicit AchievementsPane(wxWindow* parent); | 18 | explicit AchievementsPane(wxWindow* parent); |
@@ -16,7 +20,7 @@ class AchievementsPane : public wxListView { | |||
16 | void UpdateIndicators(); | 20 | void UpdateIndicators(); |
17 | 21 | ||
18 | private: | 22 | private: |
19 | std::vector<std::string> achievement_names_; | 23 | std::vector<std::tuple<std::string, int>> achievements_; // name, solve index |
20 | }; | 24 | }; |
21 | 25 | ||
22 | #endif /* end of include guard: ACHIEVEMENTS_PANE_H_C320D0B8 */ \ No newline at end of file | 26 | #endif /* end of include guard: ACHIEVEMENTS_PANE_H_C320D0B8 */ \ No newline at end of file |
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4ac0cce..8438649 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
@@ -10,6 +10,7 @@ | |||
10 | #include <any> | 10 | #include <any> |
11 | #include <apclient.hpp> | 11 | #include <apclient.hpp> |
12 | #include <apuuid.hpp> | 12 | #include <apuuid.hpp> |
13 | #include <bitset> | ||
13 | #include <chrono> | 14 | #include <chrono> |
14 | #include <exception> | 15 | #include <exception> |
15 | #include <filesystem> | 16 | #include <filesystem> |
@@ -28,8 +29,8 @@ | |||
28 | #include "tracker_state.h" | 29 | #include "tracker_state.h" |
29 | 30 | ||
30 | constexpr int AP_MAJOR = 0; | 31 | constexpr int AP_MAJOR = 0; |
31 | constexpr int AP_MINOR = 4; | 32 | constexpr int AP_MINOR = 6; |
32 | constexpr int AP_REVISION = 5; | 33 | constexpr int AP_REVISION = 1; |
33 | 34 | ||
34 | constexpr const char* CERT_STORE_PATH = "cacert.pem"; | 35 | constexpr const char* CERT_STORE_PATH = "cacert.pem"; |
35 | constexpr int ITEM_HANDLING = 7; // <- all | 36 | constexpr int ITEM_HANDLING = 7; // <- all |
@@ -37,8 +38,24 @@ constexpr int ITEM_HANDLING = 7; // <- all | |||
37 | constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds | 38 | constexpr int CONNECTION_TIMEOUT = 50000; // 50 seconds |
38 | constexpr int CONNECTION_BACKOFF_INTERVAL = 100; | 39 | constexpr int CONNECTION_BACKOFF_INTERVAL = 100; |
39 | 40 | ||
41 | constexpr int PANEL_COUNT = 803; | ||
42 | constexpr int PANEL_BITFIELD_LENGTH = 48; | ||
43 | constexpr int PANEL_BITFIELDS = 17; | ||
44 | |||
40 | namespace { | 45 | namespace { |
41 | 46 | ||
47 | const std::set<long> kNonProgressionItems = { | ||
48 | 444409, // :) | ||
49 | 444575, // The Feeling of Being Lost | ||
50 | 444576, // Wanderlust | ||
51 | 444577, // Empty White Hallways | ||
52 | 444410, // Slowness Trap | ||
53 | 444411, // Iceland Trap | ||
54 | 444412, // Atbash Trap | ||
55 | 444413, // Puzzle Skip | ||
56 | 444680, // Speed Boost | ||
57 | }; | ||
58 | |||
42 | struct APState { | 59 | struct APState { |
43 | // Initialized on main thread | 60 | // Initialized on main thread |
44 | bool initialized = false; | 61 | bool initialized = false; |
@@ -67,10 +84,12 @@ struct APState { | |||
67 | std::set<int64_t> checked_locations; | 84 | std::set<int64_t> checked_locations; |
68 | std::map<std::string, std::any> data_storage; | 85 | std::map<std::string, std::any> data_storage; |
69 | std::optional<std::tuple<int, int>> player_pos; | 86 | std::optional<std::tuple<int, int>> player_pos; |
87 | std::bitset<PANEL_COUNT> solved_panels; | ||
70 | 88 | ||
71 | DoorShuffleMode door_shuffle_mode = kNO_DOORS; | 89 | DoorShuffleMode door_shuffle_mode = kNO_DOORS; |
72 | bool group_doors = false; | 90 | bool group_doors = false; |
73 | bool color_shuffle = false; | 91 | bool color_shuffle = false; |
92 | PanelShuffleMode panel_shuffle_mode = kNO_PANELS; | ||
74 | bool painting_shuffle = false; | 93 | bool painting_shuffle = false; |
75 | int mastery_requirement = 21; | 94 | int mastery_requirement = 21; |
76 | int level_2_requirement = 223; | 95 | int level_2_requirement = 223; |
@@ -82,6 +101,7 @@ struct APState { | |||
82 | bool pilgrimage_allows_paintings = false; | 101 | bool pilgrimage_allows_paintings = false; |
83 | SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; | 102 | SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; |
84 | bool sunwarp_shuffle = false; | 103 | bool sunwarp_shuffle = false; |
104 | bool postgame_shuffle = true; | ||
85 | 105 | ||
86 | std::map<std::string, std::string> painting_mapping; | 106 | std::map<std::string, std::string> painting_mapping; |
87 | std::set<std::string> painting_codomain; | 107 | std::set<std::string> painting_codomain; |
@@ -128,10 +148,12 @@ struct APState { | |||
128 | checked_locations.clear(); | 148 | checked_locations.clear(); |
129 | data_storage.clear(); | 149 | data_storage.clear(); |
130 | player_pos = std::nullopt; | 150 | player_pos = std::nullopt; |
151 | solved_panels.reset(); | ||
131 | victory_data_storage_key.clear(); | 152 | victory_data_storage_key.clear(); |
132 | door_shuffle_mode = kNO_DOORS; | 153 | door_shuffle_mode = kNO_DOORS; |
133 | group_doors = false; | 154 | group_doors = false; |
134 | color_shuffle = false; | 155 | color_shuffle = false; |
156 | panel_shuffle_mode = kNO_PANELS; | ||
135 | painting_shuffle = false; | 157 | painting_shuffle = false; |
136 | painting_mapping.clear(); | 158 | painting_mapping.clear(); |
137 | painting_codomain.clear(); | 159 | painting_codomain.clear(); |
@@ -146,6 +168,7 @@ struct APState { | |||
146 | sunwarp_access = kSUNWARP_ACCESS_NORMAL; | 168 | sunwarp_access = kSUNWARP_ACCESS_NORMAL; |
147 | sunwarp_shuffle = false; | 169 | sunwarp_shuffle = false; |
148 | sunwarp_mapping.clear(); | 170 | sunwarp_mapping.clear(); |
171 | postgame_shuffle = true; | ||
149 | } | 172 | } |
150 | 173 | ||
151 | apclient->set_room_info_handler( | 174 | apclient->set_room_info_handler( |
@@ -200,24 +223,13 @@ struct APState { | |||
200 | return checked_locations.count(location_id); | 223 | return checked_locations.count(location_id); |
201 | } | 224 | } |
202 | 225 | ||
203 | bool HasCheckedHuntPanel(int location_id) { | ||
204 | std::lock_guard state_guard(state_mutex); | ||
205 | |||
206 | std::string key = | ||
207 | fmt::format("{}Hunt|{}", data_storage_prefix, location_id); | ||
208 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | ||
209 | } | ||
210 | |||
211 | bool HasItem(int item_id, int quantity) { | 226 | bool HasItem(int item_id, int quantity) { |
212 | return inventory.count(item_id) && inventory.at(item_id) >= quantity; | 227 | return inventory.count(item_id) && inventory.at(item_id) >= quantity; |
213 | } | 228 | } |
214 | 229 | ||
215 | bool HasAchievement(const std::string& name) { | 230 | bool HasItemSafe(int item_id, int quantity) { |
216 | std::lock_guard state_guard(state_mutex); | 231 | std::lock_guard state_guard(state_mutex); |
217 | 232 | return HasItem(item_id, quantity); | |
218 | std::string key = | ||
219 | fmt::format("{}Achievement|{}", data_storage_prefix, name); | ||
220 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | ||
221 | } | 233 | } |
222 | 234 | ||
223 | const std::set<std::string>& GetCheckedPaintings() { | 235 | const std::set<std::string>& GetCheckedPaintings() { |
@@ -241,7 +253,21 @@ struct APState { | |||
241 | checked_paintings.count(painting_mapping.at(painting_id))); | 253 | checked_paintings.count(painting_mapping.at(painting_id))); |
242 | } | 254 | } |
243 | 255 | ||
244 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | 256 | void RevealPaintings() { |
257 | std::lock_guard state_guard(state_mutex); | ||
258 | |||
259 | std::vector<std::string> paintings; | ||
260 | for (const PaintingExit& painting : GD_GetPaintings()) { | ||
261 | paintings.push_back(painting.internal_id); | ||
262 | } | ||
263 | |||
264 | APClient::DataStorageOperation operation; | ||
265 | operation.operation = "replace"; | ||
266 | operation.value = paintings; | ||
267 | |||
268 | apclient->Set(fmt::format("{}Paintings", data_storage_prefix), "", true, | ||
269 | {operation}); | ||
270 | } | ||
245 | 271 | ||
246 | bool HasReachedGoal() { | 272 | bool HasReachedGoal() { |
247 | std::lock_guard state_guard(state_mutex); | 273 | std::lock_guard state_guard(state_mutex); |
@@ -251,6 +277,12 @@ struct APState { | |||
251 | 30; // CLIENT_GOAL | 277 | 30; // CLIENT_GOAL |
252 | } | 278 | } |
253 | 279 | ||
280 | bool IsPanelSolved(int solve_index) { | ||
281 | std::lock_guard state_guard(state_mutex); | ||
282 | |||
283 | return solved_panels.test(solve_index); | ||
284 | } | ||
285 | |||
254 | private: | 286 | private: |
255 | void Initialize() { | 287 | void Initialize() { |
256 | if (!initialized) { | 288 | if (!initialized) { |
@@ -258,16 +290,8 @@ struct APState { | |||
258 | 290 | ||
259 | std::thread([this]() { Thread(); }).detach(); | 291 | std::thread([this]() { Thread(); }).detach(); |
260 | 292 | ||
261 | for (int panel_id : GD_GetAchievementPanels()) { | 293 | for (int i = 0; i < PANEL_BITFIELDS; i++) { |
262 | tracked_data_storage_keys.push_back(fmt::format( | 294 | tracked_data_storage_keys.push_back(fmt::format("Panels_{}", i)); |
263 | "Achievement|{}", GD_GetPanel(panel_id).achievement_name)); | ||
264 | } | ||
265 | |||
266 | for (const MapArea& map_area : GD_GetMapAreas()) { | ||
267 | for (const Location& location : map_area.locations) { | ||
268 | tracked_data_storage_keys.push_back( | ||
269 | fmt::format("Hunt|{}", location.ap_location_id)); | ||
270 | } | ||
271 | } | 295 | } |
272 | 296 | ||
273 | tracked_data_storage_keys.push_back("PlayerPos"); | 297 | tracked_data_storage_keys.push_back("PlayerPos"); |
@@ -351,7 +375,7 @@ struct APState { | |||
351 | } | 375 | } |
352 | } | 376 | } |
353 | 377 | ||
354 | RefreshTracker(false); | 378 | RefreshTracker(StateUpdate{.cleared_locations = true}); |
355 | } | 379 | } |
356 | 380 | ||
357 | void OnSlotDisconnected() { | 381 | void OnSlotDisconnected() { |
@@ -373,37 +397,59 @@ struct APState { | |||
373 | } | 397 | } |
374 | 398 | ||
375 | void OnItemsReceived(const std::list<APClient::NetworkItem>& items) { | 399 | void OnItemsReceived(const std::list<APClient::NetworkItem>& items) { |
400 | std::vector<ItemState> item_states; | ||
401 | bool progression_items = false; | ||
402 | |||
376 | { | 403 | { |
377 | std::lock_guard state_guard(state_mutex); | 404 | std::lock_guard state_guard(state_mutex); |
378 | 405 | ||
406 | std::map<int64_t, int> index_by_item; | ||
407 | |||
379 | for (const APClient::NetworkItem& item : items) { | 408 | for (const APClient::NetworkItem& item : items) { |
380 | inventory[item.item]++; | 409 | inventory[item.item]++; |
381 | TrackerLog(fmt::format("Item: {}", item.item)); | 410 | TrackerLog(fmt::format("Item: {}", item.item)); |
411 | |||
412 | index_by_item[item.item] = item.index; | ||
413 | |||
414 | if (!kNonProgressionItems.count(item.item)) { | ||
415 | progression_items = true; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | for (const auto& [item_id, item_index] : index_by_item) { | ||
420 | item_states.push_back(ItemState{.name = GD_GetItemName(item_id), | ||
421 | .amount = inventory[item_id], | ||
422 | .index = item_index}); | ||
382 | } | 423 | } |
383 | } | 424 | } |
384 | 425 | ||
385 | RefreshTracker(false); | 426 | RefreshTracker(StateUpdate{.items = item_states, |
427 | .progression_items = progression_items}); | ||
386 | } | 428 | } |
387 | 429 | ||
388 | void OnRetrieved(const std::map<std::string, nlohmann::json>& data) { | 430 | void OnRetrieved(const std::map<std::string, nlohmann::json>& data) { |
431 | StateUpdate state_update; | ||
432 | |||
389 | { | 433 | { |
390 | std::lock_guard state_guard(state_mutex); | 434 | std::lock_guard state_guard(state_mutex); |
391 | 435 | ||
392 | for (const auto& [key, value] : data) { | 436 | for (const auto& [key, value] : data) { |
393 | HandleDataStorage(key, value); | 437 | HandleDataStorage(key, value, state_update); |
394 | } | 438 | } |
395 | } | 439 | } |
396 | 440 | ||
397 | RefreshTracker(false); | 441 | RefreshTracker(state_update); |
398 | } | 442 | } |
399 | 443 | ||
400 | void OnSetReply(const std::string& key, const nlohmann::json& value) { | 444 | void OnSetReply(const std::string& key, const nlohmann::json& value) { |
445 | StateUpdate state_update; | ||
446 | |||
401 | { | 447 | { |
402 | std::lock_guard state_guard(state_mutex); | 448 | std::lock_guard state_guard(state_mutex); |
403 | HandleDataStorage(key, value); | 449 | HandleDataStorage(key, value, state_update); |
404 | } | 450 | } |
405 | 451 | ||
406 | RefreshTracker(false); | 452 | RefreshTracker(state_update); |
407 | } | 453 | } |
408 | 454 | ||
409 | void OnSlotConnected(std::string player, std::string server, | 455 | void OnSlotConnected(std::string player, std::string server, |
@@ -435,6 +481,7 @@ struct APState { | |||
435 | } | 481 | } |
436 | } | 482 | } |
437 | color_shuffle = slot_data["shuffle_colors"].get<int>() == 1; | 483 | color_shuffle = slot_data["shuffle_colors"].get<int>() == 1; |
484 | panel_shuffle_mode = slot_data["shuffle_panels"].get<PanelShuffleMode>(); | ||
438 | painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1; | 485 | painting_shuffle = slot_data["shuffle_paintings"].get<int>() == 1; |
439 | mastery_requirement = slot_data["mastery_achievements"].get<int>(); | 486 | mastery_requirement = slot_data["mastery_achievements"].get<int>(); |
440 | level_2_requirement = slot_data["level_2_requirement"].get<int>(); | 487 | level_2_requirement = slot_data["level_2_requirement"].get<int>(); |
@@ -456,6 +503,9 @@ struct APState { | |||
456 | : kSUNWARP_ACCESS_NORMAL; | 503 | : kSUNWARP_ACCESS_NORMAL; |
457 | sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") && | 504 | sunwarp_shuffle = slot_data.contains("shuffle_sunwarps") && |
458 | slot_data["shuffle_sunwarps"].get<int>() == 1; | 505 | slot_data["shuffle_sunwarps"].get<int>() == 1; |
506 | postgame_shuffle = slot_data.contains("shuffle_postgame") | ||
507 | ? (slot_data["shuffle_postgame"].get<int>() == 1) | ||
508 | : true; | ||
459 | 509 | ||
460 | if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { | 510 | if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { |
461 | painting_mapping.clear(); | 511 | painting_mapping.clear(); |
@@ -497,7 +547,7 @@ struct APState { | |||
497 | } | 547 | } |
498 | 548 | ||
499 | ResetReachabilityRequirements(); | 549 | ResetReachabilityRequirements(); |
500 | RefreshTracker(true); | 550 | RefreshTracker(std::nullopt); |
501 | } | 551 | } |
502 | 552 | ||
503 | void OnSlotRefused(const std::list<std::string>& errors) { | 553 | void OnSlotRefused(const std::list<std::string>& errors) { |
@@ -540,19 +590,40 @@ struct APState { | |||
540 | } | 590 | } |
541 | 591 | ||
542 | // Assumes state mutex is locked. | 592 | // Assumes state mutex is locked. |
543 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 593 | void HandleDataStorage(const std::string& key, const nlohmann::json& value, StateUpdate& state_update) { |
544 | if (value.is_boolean()) { | 594 | if (value.is_boolean()) { |
545 | data_storage[key] = value.get<bool>(); | 595 | data_storage[key] = value.get<bool>(); |
546 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, | 596 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, |
547 | (value.get<bool>() ? "true" : "false"))); | 597 | (value.get<bool>() ? "true" : "false"))); |
598 | |||
548 | } else if (value.is_number()) { | 599 | } else if (value.is_number()) { |
549 | data_storage[key] = value.get<int>(); | 600 | data_storage[key] = value.get<int>(); |
550 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, | 601 | TrackerLog(fmt::format("Data storage {} retrieved as {}", key, |
551 | value.get<int>())); | 602 | value.get<int>())); |
603 | |||
604 | if (key == victory_data_storage_key) { | ||
605 | state_update.cleared_locations = true; | ||
606 | } else if (key.find("Panels_") != std::string::npos) { | ||
607 | int bitfield_num = | ||
608 | std::stoi(key.substr(data_storage_prefix.size() + 7)); | ||
609 | uint64_t bitfield_value = value.get<uint64_t>(); | ||
610 | for (int i = 0; i < PANEL_BITFIELD_LENGTH; i++) { | ||
611 | if ((bitfield_value & (1LL << i)) != 0) { | ||
612 | int solve_index = bitfield_num * PANEL_BITFIELD_LENGTH + i; | ||
613 | |||
614 | if (!solved_panels.test(solve_index)) { | ||
615 | state_update.panels.insert(solve_index); | ||
616 | } | ||
617 | |||
618 | solved_panels.set(solve_index); | ||
619 | } | ||
620 | } | ||
621 | } | ||
552 | } else if (value.is_object()) { | 622 | } else if (value.is_object()) { |
553 | if (key.ends_with("PlayerPos")) { | 623 | if (key.ends_with("PlayerPos")) { |
554 | auto map_value = value.get<std::map<std::string, int>>(); | 624 | auto map_value = value.get<std::map<std::string, int>>(); |
555 | player_pos = std::tuple<int, int>(map_value["x"], map_value["z"]); | 625 | player_pos = std::tuple<int, int>(map_value["x"], map_value["z"]); |
626 | state_update.player_position = true; | ||
556 | } else { | 627 | } else { |
557 | data_storage[key] = value.get<std::map<std::string, int>>(); | 628 | data_storage[key] = value.get<std::map<std::string, int>>(); |
558 | } | 629 | } |
@@ -561,6 +632,7 @@ struct APState { | |||
561 | } else if (value.is_null()) { | 632 | } else if (value.is_null()) { |
562 | if (key.ends_with("PlayerPos")) { | 633 | if (key.ends_with("PlayerPos")) { |
563 | player_pos = std::nullopt; | 634 | player_pos = std::nullopt; |
635 | state_update.player_position = true; | ||
564 | } else { | 636 | } else { |
565 | data_storage.erase(key); | 637 | data_storage.erase(key); |
566 | } | 638 | } |
@@ -572,6 +644,8 @@ struct APState { | |||
572 | if (key.ends_with("Paintings")) { | 644 | if (key.ends_with("Paintings")) { |
573 | data_storage[key] = | 645 | data_storage[key] = |
574 | std::set<std::string>(list_value.begin(), list_value.end()); | 646 | std::set<std::string>(list_value.begin(), list_value.end()); |
647 | state_update.paintings = | ||
648 | std::vector<std::string>(list_value.begin(), list_value.end()); | ||
575 | } else { | 649 | } else { |
576 | data_storage[key] = list_value; | 650 | data_storage[key] = list_value; |
577 | } | 651 | } |
@@ -582,29 +656,34 @@ struct APState { | |||
582 | } | 656 | } |
583 | 657 | ||
584 | // State mutex should NOT be locked. | 658 | // State mutex should NOT be locked. |
585 | void RefreshTracker(bool reset) { | 659 | // nullopt state_update indicates a reset. |
660 | void RefreshTracker(std::optional<StateUpdate> state_update) { | ||
586 | TrackerLog("Refreshing display..."); | 661 | TrackerLog("Refreshing display..."); |
587 | 662 | ||
588 | std::string prev_msg; | 663 | if (!state_update || state_update->progression_items || |
589 | { | 664 | !state_update->paintings.empty()) { |
590 | std::lock_guard state_guard(state_mutex); | 665 | std::string prev_msg; |
666 | { | ||
667 | std::lock_guard state_guard(state_mutex); | ||
591 | 668 | ||
592 | prev_msg = status_message; | 669 | prev_msg = status_message; |
593 | SetStatusMessage(fmt::format("{} Recalculating...", status_message)); | 670 | SetStatusMessage(fmt::format("{} Recalculating...", status_message)); |
594 | } | 671 | } |
595 | 672 | ||
596 | RecalculateReachability(); | 673 | RecalculateReachability(); |
597 | 674 | ||
598 | if (reset) { | 675 | { |
599 | tracker_frame->ResetIndicators(); | 676 | std::lock_guard state_guard(state_mutex); |
600 | } else { | ||
601 | tracker_frame->UpdateIndicators(); | ||
602 | } | ||
603 | 677 | ||
604 | { | 678 | SetStatusMessage(prev_msg); |
605 | std::lock_guard state_guard(state_mutex); | 679 | } |
680 | } | ||
681 | |||
606 | 682 | ||
607 | SetStatusMessage(prev_msg); | 683 | if (!state_update) { |
684 | tracker_frame->ResetIndicators(); | ||
685 | } else { | ||
686 | tracker_frame->UpdateIndicators(*state_update); | ||
608 | } | 687 | } |
609 | } | 688 | } |
610 | 689 | ||
@@ -639,16 +718,12 @@ bool AP_HasCheckedGameLocation(int location_id) { | |||
639 | return GetState().HasCheckedGameLocation(location_id); | 718 | return GetState().HasCheckedGameLocation(location_id); |
640 | } | 719 | } |
641 | 720 | ||
642 | bool AP_HasCheckedHuntPanel(int location_id) { | ||
643 | return GetState().HasCheckedHuntPanel(location_id); | ||
644 | } | ||
645 | |||
646 | bool AP_HasItem(int item_id, int quantity) { | 721 | bool AP_HasItem(int item_id, int quantity) { |
647 | return GetState().HasItem(item_id, quantity); | 722 | return GetState().HasItem(item_id, quantity); |
648 | } | 723 | } |
649 | 724 | ||
650 | std::string AP_GetItemName(int item_id) { | 725 | bool AP_HasItemSafe(int item_id, int quantity) { |
651 | return GetState().GetItemName(item_id); | 726 | return GetState().HasItemSafe(item_id, quantity); |
652 | } | 727 | } |
653 | 728 | ||
654 | DoorShuffleMode AP_GetDoorShuffleMode() { | 729 | DoorShuffleMode AP_GetDoorShuffleMode() { |
@@ -695,6 +770,8 @@ bool AP_IsPaintingChecked(const std::string& painting_id) { | |||
695 | return GetState().IsPaintingChecked(painting_id); | 770 | return GetState().IsPaintingChecked(painting_id); |
696 | } | 771 | } |
697 | 772 | ||
773 | void AP_RevealPaintings() { GetState().RevealPaintings(); } | ||
774 | |||
698 | int AP_GetMasteryRequirement() { | 775 | int AP_GetMasteryRequirement() { |
699 | std::lock_guard state_guard(GetState().state_mutex); | 776 | std::lock_guard state_guard(GetState().state_mutex); |
700 | 777 | ||
@@ -707,6 +784,12 @@ int AP_GetLevel2Requirement() { | |||
707 | return GetState().level_2_requirement; | 784 | return GetState().level_2_requirement; |
708 | } | 785 | } |
709 | 786 | ||
787 | LocationChecks AP_GetLocationsChecks() { | ||
788 | std::lock_guard state_guard(GetState().state_mutex); | ||
789 | |||
790 | return GetState().location_checks; | ||
791 | } | ||
792 | |||
710 | bool AP_IsLocationVisible(int classification) { | 793 | bool AP_IsLocationVisible(int classification) { |
711 | std::lock_guard state_guard(GetState().state_mutex); | 794 | std::lock_guard state_guard(GetState().state_mutex); |
712 | 795 | ||
@@ -734,14 +817,16 @@ bool AP_IsLocationVisible(int classification) { | |||
734 | return (world_state & classification); | 817 | return (world_state & classification); |
735 | } | 818 | } |
736 | 819 | ||
737 | VictoryCondition AP_GetVictoryCondition() { | 820 | PanelShuffleMode AP_GetPanelShuffleMode() { |
738 | std::lock_guard state_guard(GetState().state_mutex); | 821 | std::lock_guard state_guard(GetState().state_mutex); |
739 | 822 | ||
740 | return GetState().victory_condition; | 823 | return GetState().panel_shuffle_mode; |
741 | } | 824 | } |
742 | 825 | ||
743 | bool AP_HasAchievement(const std::string& achievement_name) { | 826 | VictoryCondition AP_GetVictoryCondition() { |
744 | return GetState().HasAchievement(achievement_name); | 827 | std::lock_guard state_guard(GetState().state_mutex); |
828 | |||
829 | return GetState().victory_condition; | ||
745 | } | 830 | } |
746 | 831 | ||
747 | bool AP_HasEarlyColorHallways() { | 832 | bool AP_HasEarlyColorHallways() { |
@@ -784,6 +869,8 @@ std::map<int, SunwarpMapping> AP_GetSunwarpMapping() { | |||
784 | return GetState().sunwarp_mapping; | 869 | return GetState().sunwarp_mapping; |
785 | } | 870 | } |
786 | 871 | ||
872 | bool AP_IsPostgameShuffle() { return GetState().postgame_shuffle; } | ||
873 | |||
787 | bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } | 874 | bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } |
788 | 875 | ||
789 | std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { | 876 | std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { |
@@ -791,3 +878,7 @@ std::optional<std::tuple<int, int>> AP_GetPlayerPosition() { | |||
791 | 878 | ||
792 | return GetState().player_pos; | 879 | return GetState().player_pos; |
793 | } | 880 | } |
881 | |||
882 | bool AP_IsPanelSolved(int solve_index) { | ||
883 | return GetState().IsPanelSolved(solve_index); | ||
884 | } | ||
diff --git a/src/ap_state.h b/src/ap_state.h index 2da0b8e..a757d89 100644 --- a/src/ap_state.h +++ b/src/ap_state.h | |||
@@ -26,6 +26,8 @@ enum LocationChecks { | |||
26 | kPANELSANITY = 2 | 26 | kPANELSANITY = 2 |
27 | }; | 27 | }; |
28 | 28 | ||
29 | enum PanelShuffleMode { kNO_PANELS = 0, kREARRANGE_PANELS = 1 }; | ||
30 | |||
29 | enum SunwarpAccess { | 31 | enum SunwarpAccess { |
30 | kSUNWARP_ACCESS_NORMAL = 0, | 32 | kSUNWARP_ACCESS_NORMAL = 0, |
31 | kSUNWARP_ACCESS_DISABLED = 1, | 33 | kSUNWARP_ACCESS_DISABLED = 1, |
@@ -39,6 +41,12 @@ struct SunwarpMapping { | |||
39 | int exit_index; | 41 | int exit_index; |
40 | }; | 42 | }; |
41 | 43 | ||
44 | struct ItemState { | ||
45 | std::string name; | ||
46 | int amount = 0; | ||
47 | int index = 0; | ||
48 | }; | ||
49 | |||
42 | void AP_SetTrackerFrame(TrackerFrame* tracker_frame); | 50 | void AP_SetTrackerFrame(TrackerFrame* tracker_frame); |
43 | 51 | ||
44 | void AP_Connect(std::string server, std::string player, std::string password); | 52 | void AP_Connect(std::string server, std::string player, std::string password); |
@@ -49,16 +57,11 @@ std::string AP_GetSaveName(); | |||
49 | 57 | ||
50 | bool AP_HasCheckedGameLocation(int location_id); | 58 | bool AP_HasCheckedGameLocation(int location_id); |
51 | 59 | ||
52 | bool AP_HasCheckedHuntPanel(int location_id); | ||
53 | |||
54 | // This doesn't lock the state mutex, for speed, so it must ONLY be called from | 60 | // This doesn't lock the state mutex, for speed, so it must ONLY be called from |
55 | // RecalculateReachability, which is only called from the APState thread anyway. | 61 | // RecalculateReachability, which is only called from the APState thread anyway. |
56 | bool AP_HasItem(int item_id, int quantity = 1); | 62 | bool AP_HasItem(int item_id, int quantity = 1); |
57 | 63 | ||
58 | // This doesn't lock the client mutex because it is ONLY to be called from | 64 | bool AP_HasItemSafe(int item_id, int quantity = 1); |
59 | // RecalculateReachability, which is only called from within a client callback | ||
60 | // anyway. | ||
61 | std::string AP_GetItemName(int item_id); | ||
62 | 65 | ||
63 | DoorShuffleMode AP_GetDoorShuffleMode(); | 66 | DoorShuffleMode AP_GetDoorShuffleMode(); |
64 | 67 | ||
@@ -76,15 +79,19 @@ std::set<std::string> AP_GetCheckedPaintings(); | |||
76 | 79 | ||
77 | bool AP_IsPaintingChecked(const std::string& painting_id); | 80 | bool AP_IsPaintingChecked(const std::string& painting_id); |
78 | 81 | ||
82 | void AP_RevealPaintings(); | ||
83 | |||
79 | int AP_GetMasteryRequirement(); | 84 | int AP_GetMasteryRequirement(); |
80 | 85 | ||
81 | int AP_GetLevel2Requirement(); | 86 | int AP_GetLevel2Requirement(); |
82 | 87 | ||
88 | LocationChecks AP_GetLocationsChecks(); | ||
89 | |||
83 | bool AP_IsLocationVisible(int classification); | 90 | bool AP_IsLocationVisible(int classification); |
84 | 91 | ||
85 | VictoryCondition AP_GetVictoryCondition(); | 92 | PanelShuffleMode AP_GetPanelShuffleMode(); |
86 | 93 | ||
87 | bool AP_HasAchievement(const std::string& achievement_name); | 94 | VictoryCondition AP_GetVictoryCondition(); |
88 | 95 | ||
89 | bool AP_HasEarlyColorHallways(); | 96 | bool AP_HasEarlyColorHallways(); |
90 | 97 | ||
@@ -100,8 +107,12 @@ bool AP_IsSunwarpShuffle(); | |||
100 | 107 | ||
101 | std::map<int, SunwarpMapping> AP_GetSunwarpMapping(); | 108 | std::map<int, SunwarpMapping> AP_GetSunwarpMapping(); |
102 | 109 | ||
110 | bool AP_IsPostgameShuffle(); | ||
111 | |||
103 | bool AP_HasReachedGoal(); | 112 | bool AP_HasReachedGoal(); |
104 | 113 | ||
105 | std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); | 114 | std::optional<std::tuple<int, int>> AP_GetPlayerPosition(); |
106 | 115 | ||
116 | bool AP_IsPanelSolved(int solve_index); | ||
117 | |||
107 | #endif /* end of include guard: AP_STATE_H_664A4180 */ | 118 | #endif /* end of include guard: AP_STATE_H_664A4180 */ |
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 8d6487e..c95e492 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp | |||
@@ -7,6 +7,7 @@ | |||
7 | #include "ap_state.h" | 7 | #include "ap_state.h" |
8 | #include "game_data.h" | 8 | #include "game_data.h" |
9 | #include "global.h" | 9 | #include "global.h" |
10 | #include "icons.h" | ||
10 | #include "tracker_config.h" | 11 | #include "tracker_config.h" |
11 | #include "tracker_panel.h" | 12 | #include "tracker_panel.h" |
12 | #include "tracker_state.h" | 13 | #include "tracker_state.h" |
@@ -15,60 +16,54 @@ AreaPopup::AreaPopup(wxWindow* parent, int area_id) | |||
15 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { | 16 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { |
16 | SetBackgroundStyle(wxBG_STYLE_PAINT); | 17 | SetBackgroundStyle(wxBG_STYLE_PAINT); |
17 | 18 | ||
18 | unchecked_eye_ = | 19 | LoadIcons(); |
19 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | ||
20 | wxBITMAP_TYPE_PNG) | ||
21 | .Scale(32, 32)); | ||
22 | checked_eye_ = wxBitmap( | ||
23 | wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) | ||
24 | .Scale(32, 32)); | ||
25 | 20 | ||
21 | // TODO: This is slow on high-DPI screens. | ||
26 | SetScrollRate(5, 5); | 22 | SetScrollRate(5, 5); |
27 | 23 | ||
28 | SetBackgroundColour(*wxBLACK); | 24 | SetBackgroundColour(*wxBLACK); |
29 | Hide(); | 25 | Hide(); |
30 | 26 | ||
31 | Bind(wxEVT_PAINT, &AreaPopup::OnPaint, this); | 27 | Bind(wxEVT_PAINT, &AreaPopup::OnPaint, this); |
28 | Bind(wxEVT_DPI_CHANGED, &AreaPopup::OnDPIChanged, this); | ||
32 | 29 | ||
33 | UpdateIndicators(); | 30 | ResetIndicators(); |
34 | } | 31 | } |
35 | 32 | ||
36 | void AreaPopup::UpdateIndicators() { | 33 | void AreaPopup::ResetIndicators() { |
34 | indicators_.clear(); | ||
35 | |||
37 | const MapArea& map_area = GD_GetMapArea(area_id_); | 36 | const MapArea& map_area = GD_GetMapArea(area_id_); |
37 | wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); | ||
38 | TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent()); | ||
38 | 39 | ||
39 | // Start calculating extents. | 40 | // Start calculating extents. |
40 | wxMemoryDC mem_dc; | 41 | wxMemoryDC mem_dc; |
41 | mem_dc.SetFont(GetFont().Bold()); | 42 | mem_dc.SetFont(the_font.Bold()); |
42 | wxSize header_extent = mem_dc.GetTextExtent(map_area.name); | 43 | header_extent_ = mem_dc.GetTextExtent(map_area.name); |
43 | 44 | ||
44 | int acc_height = header_extent.GetHeight() + 20; | 45 | int acc_height = header_extent_.GetHeight() + FromDIP(20); |
45 | int col_width = 0; | 46 | int col_width = 0; |
46 | 47 | ||
47 | mem_dc.SetFont(GetFont()); | 48 | mem_dc.SetFont(the_font); |
48 | |||
49 | TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent()); | ||
50 | |||
51 | std::vector<int> real_locations; | ||
52 | 49 | ||
53 | for (int section_id = 0; section_id < map_area.locations.size(); | 50 | for (int section_id = 0; section_id < map_area.locations.size(); |
54 | section_id++) { | 51 | section_id++) { |
55 | const Location& location = map_area.locations.at(section_id); | 52 | const Location& location = map_area.locations.at(section_id); |
56 | 53 | if ((!AP_IsLocationVisible(location.classification) || | |
57 | if (tracker_panel->IsPanelsMode()) { | 54 | IsLocationPostgame(location.ap_location_id)) && |
58 | if (!location.single_panel) { | 55 | !(location.hunt && |
59 | continue; | 56 | GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) && |
60 | } | 57 | !(location.single_panel && |
61 | } else { | 58 | GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS)) { |
62 | if (!AP_IsLocationVisible(location.classification) && | 59 | continue; |
63 | !(location.hunt && GetTrackerConfig().show_hunt_panels)) { | ||
64 | continue; | ||
65 | } | ||
66 | } | 60 | } |
67 | 61 | ||
68 | real_locations.push_back(section_id); | 62 | indicators_.emplace_back(section_id, kLOCATION, acc_height); |
69 | 63 | ||
70 | wxSize item_extent = mem_dc.GetTextExtent(location.name); | 64 | wxSize item_extent = mem_dc.GetTextExtent(location.name); |
71 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | 65 | int item_height = |
66 | std::max(FromDIP(32), item_extent.GetHeight()) + FromDIP(10); | ||
72 | acc_height += item_height; | 67 | acc_height += item_height; |
73 | 68 | ||
74 | if (item_extent.GetWidth() > col_width) { | 69 | if (item_extent.GetWidth() > col_width) { |
@@ -76,11 +71,18 @@ void AreaPopup::UpdateIndicators() { | |||
76 | } | 71 | } |
77 | } | 72 | } |
78 | 73 | ||
79 | if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { | 74 | if (AP_IsPaintingShuffle()) { |
80 | for (int painting_id : map_area.paintings) { | 75 | for (int painting_id : map_area.paintings) { |
76 | if (IsPaintingPostgame(painting_id)) { | ||
77 | continue; | ||
78 | } | ||
79 | |||
80 | indicators_.emplace_back(painting_id, kPAINTING, acc_height); | ||
81 | |||
81 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | 82 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); |
82 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. | 83 | wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); |
83 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | 84 | int item_height = |
85 | std::max(FromDIP(32), item_extent.GetHeight()) + FromDIP(10); | ||
84 | acc_height += item_height; | 86 | acc_height += item_height; |
85 | 87 | ||
86 | if (item_extent.GetWidth() > col_width) { | 88 | if (item_extent.GetWidth() > col_width) { |
@@ -89,80 +91,86 @@ void AreaPopup::UpdateIndicators() { | |||
89 | } | 91 | } |
90 | } | 92 | } |
91 | 93 | ||
92 | int item_width = col_width + 10 + 32; | 94 | int item_width = col_width + FromDIP(10 + 32); |
93 | int full_width = std::max(header_extent.GetWidth(), item_width) + 20; | 95 | full_width_ = std::max(header_extent_.GetWidth(), item_width) + FromDIP(20); |
96 | full_height_ = acc_height; | ||
94 | 97 | ||
95 | Fit(); | 98 | Fit(); |
96 | SetVirtualSize(full_width, acc_height); | 99 | SetVirtualSize(full_width_, full_height_); |
100 | |||
101 | UpdateIndicators(); | ||
102 | } | ||
103 | |||
104 | void AreaPopup::UpdateIndicators() { | ||
105 | const MapArea& map_area = GD_GetMapArea(area_id_); | ||
106 | wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); | ||
107 | TrackerPanel* tracker_panel = dynamic_cast<TrackerPanel*>(GetParent()); | ||
108 | |||
109 | rendered_ = wxBitmap(full_width_, full_height_); | ||
97 | 110 | ||
98 | rendered_ = wxBitmap(full_width, acc_height); | 111 | wxMemoryDC mem_dc; |
99 | mem_dc.SelectObject(rendered_); | 112 | mem_dc.SelectObject(rendered_); |
100 | mem_dc.SetPen(*wxTRANSPARENT_PEN); | 113 | mem_dc.SetPen(*wxTRANSPARENT_PEN); |
101 | mem_dc.SetBrush(*wxBLACK_BRUSH); | 114 | mem_dc.SetBrush(*wxBLACK_BRUSH); |
102 | mem_dc.DrawRectangle({0, 0}, {full_width, acc_height}); | 115 | mem_dc.DrawRectangle({0, 0}, {full_width_, full_height_}); |
103 | 116 | ||
104 | mem_dc.SetFont(GetFont().Bold()); | 117 | mem_dc.SetFont(the_font.Bold()); |
105 | mem_dc.SetTextForeground(*wxWHITE); | 118 | mem_dc.SetTextForeground(*wxWHITE); |
106 | mem_dc.DrawText(map_area.name, | 119 | mem_dc.DrawText(map_area.name, |
107 | {(full_width - header_extent.GetWidth()) / 2, 10}); | 120 | {(full_width_ - header_extent_.GetWidth()) / 2, FromDIP(10)}); |
108 | 121 | ||
109 | int cur_height = header_extent.GetHeight() + 20; | 122 | mem_dc.SetFont(the_font); |
110 | 123 | ||
111 | mem_dc.SetFont(GetFont()); | 124 | for (const IndicatorInfo& indicator : indicators_) { |
125 | switch (indicator.type) { | ||
126 | case kLOCATION: { | ||
127 | const Location& location = map_area.locations.at(indicator.id); | ||
112 | 128 | ||
113 | for (int section_id : real_locations) { | 129 | bool checked = false; |
114 | const Location& location = map_area.locations.at(section_id); | 130 | if (IsLocationWinCondition(location)) { |
131 | checked = AP_HasReachedGoal(); | ||
132 | } else { | ||
133 | checked = AP_HasCheckedGameLocation(location.ap_location_id) || | ||
134 | (location.single_panel && | ||
135 | AP_IsPanelSolved( | ||
136 | GD_GetPanel(*location.single_panel).solve_index)); | ||
137 | } | ||
115 | 138 | ||
116 | bool checked = false; | 139 | const wxBitmap* eye_ptr = checked ? checked_eye_ : unchecked_eye_; |
117 | if (IsLocationWinCondition(location)) { | ||
118 | checked = 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 | } | ||
131 | |||
132 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | ||
133 | 140 | ||
134 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | 141 | mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y}); |
135 | 142 | ||
136 | bool reachable = IsLocationReachable(location.ap_location_id); | 143 | bool reachable = IsLocationReachable(location.ap_location_id); |
137 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | 144 | const wxColour* text_color = reachable ? wxWHITE : wxRED; |
138 | mem_dc.SetTextForeground(*text_color); | 145 | mem_dc.SetTextForeground(*text_color); |
139 | 146 | ||
140 | wxSize item_extent = mem_dc.GetTextExtent(location.name); | 147 | wxSize item_extent = mem_dc.GetTextExtent(location.name); |
141 | mem_dc.DrawText( | 148 | mem_dc.DrawText( |
142 | location.name, | 149 | location.name, |
143 | {10 + 32 + 10, cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); | 150 | {FromDIP(10 + 32 + 10), |
144 | 151 | indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); | |
145 | cur_height += 10 + 32; | ||
146 | } | ||
147 | 152 | ||
148 | if (AP_IsPaintingShuffle() && !tracker_panel->IsPanelsMode()) { | 153 | break; |
149 | for (int painting_id : map_area.paintings) { | 154 | } |
150 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | 155 | case kPAINTING: { |
156 | const PaintingExit& painting = GD_GetPaintingExit(indicator.id); | ||
151 | 157 | ||
152 | bool reachable = IsPaintingReachable(painting_id); | 158 | bool reachable = IsPaintingReachable(indicator.id); |
153 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | 159 | const wxColour* text_color = reachable ? wxWHITE : wxRED; |
154 | mem_dc.SetTextForeground(*text_color); | 160 | mem_dc.SetTextForeground(*text_color); |
155 | 161 | ||
156 | bool checked = reachable && AP_IsPaintingChecked(painting.internal_id); | 162 | bool checked = reachable && AP_IsPaintingChecked(painting.internal_id); |
157 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | 163 | const wxBitmap* eye_ptr = checked ? checked_owl_ : unchecked_owl_; |
158 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | 164 | mem_dc.DrawBitmap(*eye_ptr, {FromDIP(10), indicator.y}); |
159 | 165 | ||
160 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. | 166 | wxSize item_extent = mem_dc.GetTextExtent(painting.display_name); |
161 | mem_dc.DrawText(painting.internal_id, | 167 | mem_dc.DrawText( |
162 | {10 + 32 + 10, | 168 | painting.display_name, |
163 | cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); | 169 | {FromDIP(10 + 32 + 10), |
170 | indicator.y + (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); | ||
164 | 171 | ||
165 | cur_height += 10 + 32; | 172 | break; |
173 | } | ||
166 | } | 174 | } |
167 | } | 175 | } |
168 | } | 176 | } |
@@ -174,3 +182,21 @@ void AreaPopup::OnPaint(wxPaintEvent& event) { | |||
174 | 182 | ||
175 | event.Skip(); | 183 | event.Skip(); |
176 | } | 184 | } |
185 | |||
186 | void AreaPopup::OnDPIChanged(wxDPIChangedEvent& event) { | ||
187 | LoadIcons(); | ||
188 | ResetIndicators(); | ||
189 | |||
190 | event.Skip(); | ||
191 | } | ||
192 | |||
193 | void AreaPopup::LoadIcons() { | ||
194 | unchecked_eye_ = GetTheIconCache().GetIcon("assets/unchecked.png", | ||
195 | FromDIP(wxSize{32, 32})); | ||
196 | checked_eye_ = | ||
197 | GetTheIconCache().GetIcon("assets/checked.png", FromDIP(wxSize{32, 32})); | ||
198 | unchecked_owl_ = | ||
199 | GetTheIconCache().GetIcon("assets/owl.png", FromDIP(wxSize{32, 32})); | ||
200 | checked_owl_ = GetTheIconCache().GetIcon("assets/checked_owl.png", | ||
201 | FromDIP(wxSize{32, 32})); | ||
202 | } | ||
diff --git a/src/area_popup.h b/src/area_popup.h index 00c644d..f8a2355 100644 --- a/src/area_popup.h +++ b/src/area_popup.h | |||
@@ -7,19 +7,53 @@ | |||
7 | #include <wx/wx.h> | 7 | #include <wx/wx.h> |
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | #include <vector> | ||
11 | |||
10 | class AreaPopup : public wxScrolledCanvas { | 12 | class AreaPopup : public wxScrolledCanvas { |
11 | public: | 13 | public: |
12 | AreaPopup(wxWindow* parent, int area_id); | 14 | AreaPopup(wxWindow* parent, int area_id); |
13 | 15 | ||
16 | void ResetIndicators(); | ||
14 | void UpdateIndicators(); | 17 | void UpdateIndicators(); |
15 | 18 | ||
19 | int GetFullWidth() const { return full_width_; } | ||
20 | int GetFullHeight() const { return full_height_; } | ||
21 | |||
16 | private: | 22 | private: |
23 | enum IndicatorType { | ||
24 | kLOCATION, | ||
25 | kPAINTING, | ||
26 | }; | ||
27 | |||
28 | struct IndicatorInfo { | ||
29 | // For locations, the id is an index into the map area's locations list. | ||
30 | // For paintings, it is a real painting id. | ||
31 | int id; | ||
32 | IndicatorType type; | ||
33 | int y; | ||
34 | |||
35 | IndicatorInfo(int id, IndicatorType type, int y) | ||
36 | : id(id), type(type), y(y) {} | ||
37 | }; | ||
38 | |||
17 | void OnPaint(wxPaintEvent& event); | 39 | void OnPaint(wxPaintEvent& event); |
40 | void OnDPIChanged(wxDPIChangedEvent& event); | ||
41 | |||
42 | void LoadIcons(); | ||
18 | 43 | ||
19 | int area_id_; | 44 | int area_id_; |
20 | 45 | ||
21 | wxBitmap unchecked_eye_; | 46 | const wxBitmap* unchecked_eye_; |
22 | wxBitmap checked_eye_; | 47 | const wxBitmap* checked_eye_; |
48 | const wxBitmap* unchecked_owl_; | ||
49 | const wxBitmap* checked_owl_; | ||
50 | |||
51 | int full_width_ = 0; | ||
52 | int full_height_ = 0; | ||
53 | wxSize header_extent_; | ||
54 | |||
55 | std::vector<IndicatorInfo> indicators_; | ||
56 | |||
23 | wxBitmap rendered_; | 57 | wxBitmap rendered_; |
24 | }; | 58 | }; |
25 | 59 | ||
diff --git a/src/connection_dialog.cpp b/src/connection_dialog.cpp index 64fee98..b55a138 100644 --- a/src/connection_dialog.cpp +++ b/src/connection_dialog.cpp | |||
@@ -4,17 +4,21 @@ | |||
4 | 4 | ||
5 | ConnectionDialog::ConnectionDialog() | 5 | ConnectionDialog::ConnectionDialog() |
6 | : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { | 6 | : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { |
7 | server_box_ = | 7 | server_box_ = new wxTextCtrl( |
8 | new wxTextCtrl(this, -1, GetTrackerConfig().connection_details.ap_server, | 8 | this, -1, |
9 | wxDefaultPosition, {300, -1}); | 9 | wxString::FromUTF8(GetTrackerConfig().connection_details.ap_server), |
10 | player_box_ = | 10 | wxDefaultPosition, FromDIP(wxSize{300, -1})); |
11 | new wxTextCtrl(this, -1, GetTrackerConfig().connection_details.ap_player, | 11 | player_box_ = new wxTextCtrl( |
12 | wxDefaultPosition, {300, -1}); | 12 | this, -1, |
13 | wxString::FromUTF8(GetTrackerConfig().connection_details.ap_player), | ||
14 | wxDefaultPosition, FromDIP(wxSize{300, -1})); | ||
13 | password_box_ = new wxTextCtrl( | 15 | password_box_ = new wxTextCtrl( |
14 | this, -1, GetTrackerConfig().connection_details.ap_password, | 16 | this, -1, |
15 | wxDefaultPosition, {300, -1}); | 17 | wxString::FromUTF8(GetTrackerConfig().connection_details.ap_password), |
18 | wxDefaultPosition, FromDIP(wxSize{300, -1})); | ||
16 | 19 | ||
17 | wxFlexGridSizer* form_sizer = new wxFlexGridSizer(2, 10, 10); | 20 | wxFlexGridSizer* form_sizer = |
21 | new wxFlexGridSizer(2, FromDIP(10), FromDIP(10)); | ||
18 | 22 | ||
19 | form_sizer->Add( | 23 | form_sizer->Add( |
20 | new wxStaticText(this, -1, "Server:"), | 24 | new wxStaticText(this, -1, "Server:"), |
@@ -30,17 +34,19 @@ ConnectionDialog::ConnectionDialog() | |||
30 | form_sizer->Add(password_box_, wxSizerFlags().Expand()); | 34 | form_sizer->Add(password_box_, wxSizerFlags().Expand()); |
31 | 35 | ||
32 | history_list_ = new wxListBox(this, -1); | 36 | history_list_ = new wxListBox(this, -1); |
33 | for (const ConnectionDetails& details : GetTrackerConfig().connection_history) { | 37 | for (const ConnectionDetails& details : |
38 | GetTrackerConfig().connection_history) { | ||
34 | wxString display_text; | 39 | wxString display_text; |
35 | display_text << details.ap_player; | 40 | display_text << wxString::FromUTF8(details.ap_player); |
36 | display_text << " ("; | 41 | display_text << " ("; |
37 | display_text << details.ap_server; | 42 | display_text << wxString::FromUTF8(details.ap_server); |
38 | display_text << ")"; | 43 | display_text << ")"; |
39 | 44 | ||
40 | history_list_->Append(display_text); | 45 | history_list_->Append(display_text); |
41 | } | 46 | } |
42 | 47 | ||
43 | history_list_->Bind(wxEVT_LISTBOX, &ConnectionDialog::OnOldConnectionChosen, this); | 48 | history_list_->Bind(wxEVT_LISTBOX, &ConnectionDialog::OnOldConnectionChosen, |
49 | this); | ||
44 | 50 | ||
45 | wxBoxSizer* mid_sizer = new wxBoxSizer(wxHORIZONTAL); | 51 | wxBoxSizer* mid_sizer = new wxBoxSizer(wxHORIZONTAL); |
46 | mid_sizer->Add(form_sizer, wxSizerFlags().Proportion(3).Expand()); | 52 | mid_sizer->Add(form_sizer, wxSizerFlags().Proportion(3).Expand()); |
@@ -52,7 +58,8 @@ ConnectionDialog::ConnectionDialog() | |||
52 | this, -1, "Enter the details to connect to Archipelago."), | 58 | this, -1, "Enter the details to connect to Archipelago."), |
53 | wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); | 59 | wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); |
54 | top_sizer->Add(mid_sizer, wxSizerFlags().DoubleBorder().Expand()); | 60 | top_sizer->Add(mid_sizer, wxSizerFlags().DoubleBorder().Expand()); |
55 | top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Border().Center()); | 61 | top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), |
62 | wxSizerFlags().Border().Center()); | ||
56 | 63 | ||
57 | SetSizerAndFit(top_sizer); | 64 | SetSizerAndFit(top_sizer); |
58 | 65 | ||
@@ -62,9 +69,10 @@ ConnectionDialog::ConnectionDialog() | |||
62 | 69 | ||
63 | void ConnectionDialog::OnOldConnectionChosen(wxCommandEvent& e) { | 70 | void ConnectionDialog::OnOldConnectionChosen(wxCommandEvent& e) { |
64 | if (e.IsSelection()) { | 71 | if (e.IsSelection()) { |
65 | const ConnectionDetails& details = GetTrackerConfig().connection_history.at(e.GetSelection()); | 72 | const ConnectionDetails& details = |
66 | server_box_->SetValue(details.ap_server); | 73 | GetTrackerConfig().connection_history.at(e.GetSelection()); |
67 | player_box_->SetValue(details.ap_player); | 74 | server_box_->SetValue(wxString::FromUTF8(details.ap_server)); |
68 | password_box_->SetValue(details.ap_password); | 75 | player_box_->SetValue(wxString::FromUTF8(details.ap_player)); |
76 | password_box_->SetValue(wxString::FromUTF8(details.ap_password)); | ||
69 | } | 77 | } |
70 | } | 78 | } |
diff --git a/src/connection_dialog.h b/src/connection_dialog.h index 9fe62fd..ec2ee72 100644 --- a/src/connection_dialog.h +++ b/src/connection_dialog.h | |||
@@ -14,12 +14,12 @@ class ConnectionDialog : public wxDialog { | |||
14 | public: | 14 | public: |
15 | ConnectionDialog(); | 15 | ConnectionDialog(); |
16 | 16 | ||
17 | std::string GetServerValue() { return server_box_->GetValue().ToStdString(); } | 17 | std::string GetServerValue() { return server_box_->GetValue().utf8_string(); } |
18 | 18 | ||
19 | std::string GetPlayerValue() { return player_box_->GetValue().ToStdString(); } | 19 | std::string GetPlayerValue() { return player_box_->GetValue().utf8_string(); } |
20 | 20 | ||
21 | std::string GetPasswordValue() { | 21 | std::string GetPasswordValue() { |
22 | return password_box_->GetValue().ToStdString(); | 22 | return password_box_->GetValue().utf8_string(); |
23 | } | 23 | } |
24 | 24 | ||
25 | private: | 25 | private: |
diff --git a/src/game_data.cpp b/src/game_data.cpp index 0ac77af..94b9888 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp | |||
@@ -12,32 +12,6 @@ | |||
12 | 12 | ||
13 | namespace { | 13 | namespace { |
14 | 14 | ||
15 | LingoColor GetColorForString(const std::string &str) { | ||
16 | if (str == "black") { | ||
17 | return LingoColor::kBlack; | ||
18 | } else if (str == "red") { | ||
19 | return LingoColor::kRed; | ||
20 | } else if (str == "blue") { | ||
21 | return LingoColor::kBlue; | ||
22 | } else if (str == "yellow") { | ||
23 | return LingoColor::kYellow; | ||
24 | } else if (str == "orange") { | ||
25 | return LingoColor::kOrange; | ||
26 | } else if (str == "green") { | ||
27 | return LingoColor::kGreen; | ||
28 | } else if (str == "gray") { | ||
29 | return LingoColor::kGray; | ||
30 | } else if (str == "brown") { | ||
31 | return LingoColor::kBrown; | ||
32 | } else if (str == "purple") { | ||
33 | return LingoColor::kPurple; | ||
34 | } else { | ||
35 | TrackerLog(fmt::format("Invalid color: {}", str)); | ||
36 | |||
37 | return LingoColor::kNone; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | struct GameData { | 15 | struct GameData { |
42 | std::vector<Room> rooms_; | 16 | std::vector<Room> rooms_; |
43 | std::vector<Door> doors_; | 17 | std::vector<Door> doors_; |
@@ -55,10 +29,10 @@ struct GameData { | |||
55 | std::map<std::string, int> painting_by_id_; | 29 | std::map<std::string, int> painting_by_id_; |
56 | 30 | ||
57 | std::vector<int> door_definition_order_; | 31 | std::vector<int> door_definition_order_; |
58 | std::vector<int> room_definition_order_; | ||
59 | 32 | ||
60 | std::map<std::string, int> room_by_painting_; | 33 | std::map<std::string, int> room_by_painting_; |
61 | std::map<int, int> room_by_sunwarp_; | 34 | std::map<int, int> room_by_sunwarp_; |
35 | std::map<int, int> panel_by_solve_index_; | ||
62 | 36 | ||
63 | std::vector<int> achievement_panels_; | 37 | std::vector<int> achievement_panels_; |
64 | 38 | ||
@@ -69,6 +43,8 @@ struct GameData { | |||
69 | std::map<std::string, int> subway_item_by_painting_; | 43 | std::map<std::string, int> subway_item_by_painting_; |
70 | std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; | 44 | std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; |
71 | 45 | ||
46 | std::map<int, std::string> item_by_ap_id_; | ||
47 | |||
72 | bool loaded_area_data_ = false; | 48 | bool loaded_area_data_ = false; |
73 | std::set<std::string> malconfigured_areas_; | 49 | std::set<std::string> malconfigured_areas_; |
74 | 50 | ||
@@ -84,7 +60,7 @@ struct GameData { | |||
84 | ids_config["special_items"][color_name]) { | 60 | ids_config["special_items"][color_name]) { |
85 | std::string input_name = color_name; | 61 | std::string input_name = color_name; |
86 | input_name[0] = std::tolower(input_name[0]); | 62 | input_name[0] = std::tolower(input_name[0]); |
87 | ap_id_by_color_[GetColorForString(input_name)] = | 63 | ap_id_by_color_[GetLingoColorForString(input_name)] = |
88 | ids_config["special_items"][color_name].as<int>(); | 64 | ids_config["special_items"][color_name].as<int>(); |
89 | } else { | 65 | } else { |
90 | TrackerLog(fmt::format("Missing AP item ID for color {}", color_name)); | 66 | TrackerLog(fmt::format("Missing AP item ID for color {}", color_name)); |
@@ -101,11 +77,20 @@ struct GameData { | |||
101 | init_color_id("Brown"); | 77 | init_color_id("Brown"); |
102 | init_color_id("Gray"); | 78 | init_color_id("Gray"); |
103 | 79 | ||
80 | if (ids_config["special_items"]) { | ||
81 | for (const auto& special_item_it : ids_config["special_items"]) | ||
82 | { | ||
83 | item_by_ap_id_[special_item_it.second.as<int>()] = | ||
84 | special_item_it.first.as<std::string>(); | ||
85 | } | ||
86 | } | ||
87 | |||
104 | rooms_.reserve(lingo_config.size() * 2); | 88 | rooms_.reserve(lingo_config.size() * 2); |
105 | 89 | ||
90 | std::vector<int> panel_location_ids; | ||
91 | |||
106 | for (const auto &room_it : lingo_config) { | 92 | for (const auto &room_it : lingo_config) { |
107 | int room_id = AddOrGetRoom(room_it.first.as<std::string>()); | 93 | int room_id = AddOrGetRoom(room_it.first.as<std::string>()); |
108 | room_definition_order_.push_back(room_id); | ||
109 | 94 | ||
110 | for (const auto &entrance_it : room_it.second["entrances"]) { | 95 | for (const auto &entrance_it : room_it.second["entrances"]) { |
111 | int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); | 96 | int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); |
@@ -181,12 +166,12 @@ struct GameData { | |||
181 | 166 | ||
182 | if (panel_it.second["colors"]) { | 167 | if (panel_it.second["colors"]) { |
183 | if (panel_it.second["colors"].IsScalar()) { | 168 | if (panel_it.second["colors"].IsScalar()) { |
184 | panels_[panel_id].colors.push_back(GetColorForString( | 169 | panels_[panel_id].colors.push_back(GetLingoColorForString( |
185 | panel_it.second["colors"].as<std::string>())); | 170 | panel_it.second["colors"].as<std::string>())); |
186 | } else { | 171 | } else { |
187 | for (const auto &color_node : panel_it.second["colors"]) { | 172 | for (const auto &color_node : panel_it.second["colors"]) { |
188 | panels_[panel_id].colors.push_back( | 173 | panels_[panel_id].colors.push_back( |
189 | GetColorForString(color_node.as<std::string>())); | 174 | GetLingoColorForString(color_node.as<std::string>())); |
190 | } | 175 | } |
191 | } | 176 | } |
192 | } | 177 | } |
@@ -292,10 +277,11 @@ struct GameData { | |||
292 | ids_config["panels"][rooms_[room_id].name] && | 277 | ids_config["panels"][rooms_[room_id].name] && |
293 | ids_config["panels"][rooms_[room_id].name] | 278 | ids_config["panels"][rooms_[room_id].name] |
294 | [panels_[panel_id].name]) { | 279 | [panels_[panel_id].name]) { |
295 | panels_[panel_id].ap_location_id = | 280 | int location_id = ids_config["panels"][rooms_[room_id].name] |
296 | ids_config["panels"][rooms_[room_id].name] | 281 | [panels_[panel_id].name] |
297 | [panels_[panel_id].name] | 282 | .as<int>(); |
298 | .as<int>(); | 283 | panels_[panel_id].ap_location_id = location_id; |
284 | panel_location_ids.push_back(location_id); | ||
299 | } else { | 285 | } else { |
300 | TrackerLog(fmt::format("Missing AP location ID for panel {} - {}", | 286 | TrackerLog(fmt::format("Missing AP location ID for panel {} - {}", |
301 | rooms_[room_id].name, | 287 | rooms_[room_id].name, |
@@ -361,6 +347,9 @@ struct GameData { | |||
361 | ids_config["doors"][rooms_[room_id].name] | 347 | ids_config["doors"][rooms_[room_id].name] |
362 | [doors_[door_id].name]["item"] | 348 | [doors_[door_id].name]["item"] |
363 | .as<int>(); | 349 | .as<int>(); |
350 | |||
351 | item_by_ap_id_[doors_[door_id].ap_item_id] = | ||
352 | doors_[door_id].item_name; | ||
364 | } else { | 353 | } else { |
365 | TrackerLog(fmt::format("Missing AP item ID for door {} - {}", | 354 | TrackerLog(fmt::format("Missing AP item ID for door {} - {}", |
366 | rooms_[room_id].name, | 355 | rooms_[room_id].name, |
@@ -377,6 +366,9 @@ struct GameData { | |||
377 | doors_[door_id].group_ap_item_id = | 366 | doors_[door_id].group_ap_item_id = |
378 | ids_config["door_groups"][doors_[door_id].group_name] | 367 | ids_config["door_groups"][doors_[door_id].group_name] |
379 | .as<int>(); | 368 | .as<int>(); |
369 | |||
370 | item_by_ap_id_[doors_[door_id].group_ap_item_id] = | ||
371 | doors_[door_id].group_name; | ||
380 | } else { | 372 | } else { |
381 | TrackerLog(fmt::format("Missing AP item ID for door group {}", | 373 | TrackerLog(fmt::format("Missing AP item ID for door group {}", |
382 | doors_[door_id].group_name)); | 374 | doors_[door_id].group_name)); |
@@ -440,21 +432,50 @@ struct GameData { | |||
440 | int panel_door_id = | 432 | int panel_door_id = |
441 | AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name); | 433 | AddOrGetPanelDoor(rooms_[room_id].name, panel_door_name); |
442 | 434 | ||
435 | std::map<std::string, std::vector<std::string>> panel_per_room; | ||
436 | int num_panels = 0; | ||
443 | for (const auto &panel_node : panel_door_it.second["panels"]) { | 437 | for (const auto &panel_node : panel_door_it.second["panels"]) { |
438 | num_panels++; | ||
439 | |||
444 | int panel_id = -1; | 440 | int panel_id = -1; |
445 | 441 | ||
446 | if (panel_node.IsScalar()) { | 442 | if (panel_node.IsScalar()) { |
447 | panel_id = AddOrGetPanel(rooms_[room_id].name, | 443 | panel_id = AddOrGetPanel(rooms_[room_id].name, |
448 | panel_node.as<std::string>()); | 444 | panel_node.as<std::string>()); |
445 | |||
446 | panel_per_room[rooms_[room_id].name].push_back( | ||
447 | panel_node.as<std::string>()); | ||
449 | } else { | 448 | } else { |
450 | panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(), | 449 | panel_id = AddOrGetPanel(panel_node["room"].as<std::string>(), |
451 | panel_node["panel"].as<std::string>()); | 450 | panel_node["panel"].as<std::string>()); |
451 | |||
452 | panel_per_room[panel_node["room"].as<std::string>()].push_back( | ||
453 | panel_node["panel"].as<std::string>()); | ||
452 | } | 454 | } |
453 | 455 | ||
454 | Panel &panel = panels_[panel_id]; | 456 | Panel &panel = panels_[panel_id]; |
455 | panel.panel_door = panel_door_id; | 457 | panel.panel_door = panel_door_id; |
456 | } | 458 | } |
457 | 459 | ||
460 | if (panel_door_it.second["item_name"]) { | ||
461 | panel_doors_[panel_door_id].item_name = | ||
462 | panel_door_it.second["item_name"].as<std::string>(); | ||
463 | } else { | ||
464 | std::vector<std::string> room_strs; | ||
465 | for (const auto &[room_str, panels_str] : panel_per_room) { | ||
466 | room_strs.push_back(fmt::format( | ||
467 | "{} - {}", room_str, hatkirby::implode(panels_str, ", "))); | ||
468 | } | ||
469 | |||
470 | if (num_panels == 1) { | ||
471 | panel_doors_[panel_door_id].item_name = | ||
472 | fmt::format("{} (Panel)", room_strs[0]); | ||
473 | } else { | ||
474 | panel_doors_[panel_door_id].item_name = fmt::format( | ||
475 | "{} (Panels)", hatkirby::implode(room_strs, " and ")); | ||
476 | } | ||
477 | } | ||
478 | |||
458 | if (ids_config["panel_doors"] && | 479 | if (ids_config["panel_doors"] && |
459 | ids_config["panel_doors"][rooms_[room_id].name] && | 480 | ids_config["panel_doors"][rooms_[room_id].name] && |
460 | ids_config["panel_doors"][rooms_[room_id].name] | 481 | ids_config["panel_doors"][rooms_[room_id].name] |
@@ -462,6 +483,9 @@ struct GameData { | |||
462 | panel_doors_[panel_door_id].ap_item_id = | 483 | panel_doors_[panel_door_id].ap_item_id = |
463 | ids_config["panel_doors"][rooms_[room_id].name][panel_door_name] | 484 | ids_config["panel_doors"][rooms_[room_id].name][panel_door_name] |
464 | .as<int>(); | 485 | .as<int>(); |
486 | |||
487 | item_by_ap_id_[panel_doors_[panel_door_id].ap_item_id] = | ||
488 | panel_doors_[panel_door_id].item_name; | ||
465 | } else { | 489 | } else { |
466 | TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}", | 490 | TrackerLog(fmt::format("Missing AP item ID for panel door {} - {}", |
467 | rooms_[room_id].name, panel_door_name)); | 491 | rooms_[room_id].name, panel_door_name)); |
@@ -475,6 +499,9 @@ struct GameData { | |||
475 | ids_config["panel_groups"][panel_group]) { | 499 | ids_config["panel_groups"][panel_group]) { |
476 | panel_doors_[panel_door_id].group_ap_item_id = | 500 | panel_doors_[panel_door_id].group_ap_item_id = |
477 | ids_config["panel_groups"][panel_group].as<int>(); | 501 | ids_config["panel_groups"][panel_group].as<int>(); |
502 | |||
503 | item_by_ap_id_[panel_doors_[panel_door_id].group_ap_item_id] = | ||
504 | panel_group; | ||
478 | } else { | 505 | } else { |
479 | TrackerLog(fmt::format( | 506 | TrackerLog(fmt::format( |
480 | "Missing AP item ID for panel door group {}", panel_group)); | 507 | "Missing AP item ID for panel door group {}", panel_group)); |
@@ -490,6 +517,13 @@ struct GameData { | |||
490 | PaintingExit &painting_exit = paintings_[painting_id]; | 517 | PaintingExit &painting_exit = paintings_[painting_id]; |
491 | painting_exit.room = room_id; | 518 | painting_exit.room = room_id; |
492 | 519 | ||
520 | if (painting["display_name"]) { | ||
521 | painting_exit.display_name = | ||
522 | painting["display_name"].as<std::string>(); | ||
523 | } else { | ||
524 | painting_exit.display_name = painting_exit.internal_id; | ||
525 | } | ||
526 | |||
493 | if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) && | 527 | if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) && |
494 | (!painting["disable"] || !painting["disable"].as<bool>())) { | 528 | (!painting["disable"] || !painting["disable"].as<bool>())) { |
495 | painting_exit.entrance = true; | 529 | painting_exit.entrance = true; |
@@ -531,6 +565,8 @@ struct GameData { | |||
531 | ids_config["progression"][progressive_item_name]) { | 565 | ids_config["progression"][progressive_item_name]) { |
532 | progressive_item_id = | 566 | progressive_item_id = |
533 | ids_config["progression"][progressive_item_name].as<int>(); | 567 | ids_config["progression"][progressive_item_name].as<int>(); |
568 | |||
569 | item_by_ap_id_[progressive_item_id] = progressive_item_name; | ||
534 | } else { | 570 | } else { |
535 | TrackerLog(fmt::format("Missing AP item ID for progressive item {}", | 571 | TrackerLog(fmt::format("Missing AP item ID for progressive item {}", |
536 | progressive_item_name)); | 572 | progressive_item_name)); |
@@ -582,6 +618,21 @@ struct GameData { | |||
582 | } | 618 | } |
583 | } | 619 | } |
584 | 620 | ||
621 | // Determine the panel solve indices from the sorted location IDs. | ||
622 | std::sort(panel_location_ids.begin(), panel_location_ids.end()); | ||
623 | |||
624 | std::map<int, int> solve_index_by_location_id; | ||
625 | for (int i = 0; i < panel_location_ids.size(); i++) { | ||
626 | solve_index_by_location_id[panel_location_ids[i]] = i; | ||
627 | } | ||
628 | |||
629 | for (Panel &panel : panels_) { | ||
630 | if (panel.ap_location_id != -1) { | ||
631 | panel.solve_index = solve_index_by_location_id[panel.ap_location_id]; | ||
632 | panel_by_solve_index_[panel.solve_index] = panel.id; | ||
633 | } | ||
634 | } | ||
635 | |||
585 | map_areas_.reserve(areas_config.size()); | 636 | map_areas_.reserve(areas_config.size()); |
586 | 637 | ||
587 | std::map<std::string, int> fold_areas; | 638 | std::map<std::string, int> fold_areas; |
@@ -602,7 +653,7 @@ struct GameData { | |||
602 | // Only locations for the panels are kept here. | 653 | // Only locations for the panels are kept here. |
603 | std::map<std::string, std::tuple<int, int>> locations_by_name; | 654 | std::map<std::string, std::tuple<int, int>> locations_by_name; |
604 | 655 | ||
605 | for (const Panel &panel : panels_) { | 656 | for (Panel &panel : panels_) { |
606 | int room_id = panel.room; | 657 | int room_id = panel.room; |
607 | std::string room_name = rooms_[room_id].name; | 658 | std::string room_name = rooms_[room_id].name; |
608 | 659 | ||
@@ -618,6 +669,8 @@ struct GameData { | |||
618 | area_name = location_name.substr(0, divider_pos); | 669 | area_name = location_name.substr(0, divider_pos); |
619 | section_name = location_name.substr(divider_pos + 3); | 670 | section_name = location_name.substr(divider_pos + 3); |
620 | } | 671 | } |
672 | } else { | ||
673 | panel.location_name = location_name; | ||
621 | } | 674 | } |
622 | 675 | ||
623 | if (fold_areas.count(area_name)) { | 676 | if (fold_areas.count(area_name)) { |
@@ -716,7 +769,8 @@ struct GameData { | |||
716 | MapArea &map_area = map_areas_[area_id]; | 769 | MapArea &map_area = map_areas_[area_id]; |
717 | 770 | ||
718 | for (int painting_id : room.paintings) { | 771 | for (int painting_id : room.paintings) { |
719 | const PaintingExit &painting_obj = paintings_.at(painting_id); | 772 | PaintingExit &painting_obj = paintings_.at(painting_id); |
773 | painting_obj.map_area = area_id; | ||
720 | if (painting_obj.entrance) { | 774 | if (painting_obj.entrance) { |
721 | map_area.paintings.push_back(painting_id); | 775 | map_area.paintings.push_back(painting_id); |
722 | } | 776 | } |
@@ -724,31 +778,6 @@ struct GameData { | |||
724 | } | 778 | } |
725 | } | 779 | } |
726 | 780 | ||
727 | // As a workaround for a generator bug in 0.5.1, we are going to remove the | ||
728 | // panel door requirement on panels that are defined earlier in the file than | ||
729 | // the panel door is. This results in logic that matches the generator, even | ||
730 | // if it is not true to how the game should work. This will be reverted once | ||
731 | // the logic bug is fixed and released. | ||
732 | // See: https://github.com/ArchipelagoMW/Archipelago/pull/4342 | ||
733 | for (Panel& panel : panels_) { | ||
734 | if (panel.panel_door == -1) { | ||
735 | continue; | ||
736 | } | ||
737 | const PanelDoor &panel_door = panel_doors_[panel.panel_door]; | ||
738 | for (int room_id : room_definition_order_) { | ||
739 | if (room_id == panel_door.room) { | ||
740 | // The panel door was defined first (or at the same time as the panel), | ||
741 | // so we're good. | ||
742 | break; | ||
743 | } else if (room_id == panel.room) { | ||
744 | // The panel was defined first, so we have to pretend the panel door is | ||
745 | // not required for this panel. | ||
746 | panel.panel_door = -1; | ||
747 | break; | ||
748 | } | ||
749 | } | ||
750 | } | ||
751 | |||
752 | // Report errors. | 781 | // Report errors. |
753 | for (const std::string &area : malconfigured_areas_) { | 782 | for (const std::string &area : malconfigured_areas_) { |
754 | TrackerLog(fmt::format("Area data not found for: {}", area)); | 783 | TrackerLog(fmt::format("Area data not found for: {}", area)); |
@@ -768,13 +797,10 @@ struct GameData { | |||
768 | subway_it["door"].as<std::string>()); | 797 | subway_it["door"].as<std::string>()); |
769 | } | 798 | } |
770 | 799 | ||
771 | if (subway_it["paintings"]) { | 800 | if (subway_it["painting"]) { |
772 | for (const auto &painting_it : subway_it["paintings"]) { | 801 | std::string painting_id = subway_it["painting"].as<std::string>(); |
773 | std::string painting_id = painting_it.as<std::string>(); | 802 | subway_item.painting = painting_id; |
774 | 803 | subway_item_by_painting_[painting_id] = subway_item.id; | |
775 | subway_item.paintings.push_back(painting_id); | ||
776 | subway_item_by_painting_[painting_id] = subway_item.id; | ||
777 | } | ||
778 | } | 804 | } |
779 | 805 | ||
780 | if (subway_it["tags"]) { | 806 | if (subway_it["tags"]) { |
@@ -821,6 +847,10 @@ struct GameData { | |||
821 | subway_item.special = subway_it["special"].as<std::string>(); | 847 | subway_item.special = subway_it["special"].as<std::string>(); |
822 | } | 848 | } |
823 | 849 | ||
850 | if (subway_it["tilted"]) { | ||
851 | subway_item.tilted = subway_it["tilted"].as<bool>(); | ||
852 | } | ||
853 | |||
824 | subway_items_.push_back(subway_item); | 854 | subway_items_.push_back(subway_item); |
825 | } | 855 | } |
826 | 856 | ||
@@ -880,7 +910,7 @@ struct GameData { | |||
880 | if (!panel_doors_by_id_.count(full_name)) { | 910 | if (!panel_doors_by_id_.count(full_name)) { |
881 | int panel_door_id = panel_doors_.size(); | 911 | int panel_door_id = panel_doors_.size(); |
882 | panel_doors_by_id_[full_name] = panel_door_id; | 912 | panel_doors_by_id_[full_name] = panel_door_id; |
883 | panel_doors_.push_back({.room = AddOrGetRoom(room)}); | 913 | panel_doors_.push_back({}); |
884 | } | 914 | } |
885 | 915 | ||
886 | return panel_doors_by_id_[full_name]; | 916 | return panel_doors_by_id_[full_name]; |
@@ -953,6 +983,14 @@ const Panel &GD_GetPanel(int panel_id) { | |||
953 | return GetState().panels_.at(panel_id); | 983 | return GetState().panels_.at(panel_id); |
954 | } | 984 | } |
955 | 985 | ||
986 | int GD_GetPanelBySolveIndex(int solve_index) { | ||
987 | return GetState().panel_by_solve_index_.at(solve_index); | ||
988 | } | ||
989 | |||
990 | const std::vector<PaintingExit> &GD_GetPaintings() { | ||
991 | return GetState().paintings_; | ||
992 | } | ||
993 | |||
956 | const PaintingExit &GD_GetPaintingExit(int painting_id) { | 994 | const PaintingExit &GD_GetPaintingExit(int painting_id) { |
957 | return GetState().paintings_.at(painting_id); | 995 | return GetState().paintings_.at(painting_id); |
958 | } | 996 | } |
@@ -995,3 +1033,38 @@ std::optional<int> GD_GetSubwayItemForPainting(const std::string &painting_id) { | |||
995 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { | 1033 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { |
996 | return GetState().subway_item_by_sunwarp_.at(sunwarp); | 1034 | return GetState().subway_item_by_sunwarp_.at(sunwarp); |
997 | } | 1035 | } |
1036 | |||
1037 | std::string GD_GetItemName(int id) { | ||
1038 | auto it = GetState().item_by_ap_id_.find(id); | ||
1039 | if (it != GetState().item_by_ap_id_.end()) { | ||
1040 | return it->second; | ||
1041 | } else { | ||
1042 | return "Unknown"; | ||
1043 | } | ||
1044 | } | ||
1045 | |||
1046 | LingoColor GetLingoColorForString(const std::string &str) { | ||
1047 | if (str == "black") { | ||
1048 | return LingoColor::kBlack; | ||
1049 | } else if (str == "red") { | ||
1050 | return LingoColor::kRed; | ||
1051 | } else if (str == "blue") { | ||
1052 | return LingoColor::kBlue; | ||
1053 | } else if (str == "yellow") { | ||
1054 | return LingoColor::kYellow; | ||
1055 | } else if (str == "orange") { | ||
1056 | return LingoColor::kOrange; | ||
1057 | } else if (str == "green") { | ||
1058 | return LingoColor::kGreen; | ||
1059 | } else if (str == "gray") { | ||
1060 | return LingoColor::kGray; | ||
1061 | } else if (str == "brown") { | ||
1062 | return LingoColor::kBrown; | ||
1063 | } else if (str == "purple") { | ||
1064 | return LingoColor::kPurple; | ||
1065 | } else { | ||
1066 | TrackerLog(fmt::format("Invalid color: {}", str)); | ||
1067 | |||
1068 | return LingoColor::kNone; | ||
1069 | } | ||
1070 | } | ||
diff --git a/src/game_data.h b/src/game_data.h index 31a1e78..ac911e5 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
@@ -57,6 +57,7 @@ struct Panel { | |||
57 | int ap_location_id = -1; | 57 | int ap_location_id = -1; |
58 | bool hunt = false; | 58 | bool hunt = false; |
59 | int panel_door = -1; | 59 | int panel_door = -1; |
60 | int solve_index = -1; | ||
60 | }; | 61 | }; |
61 | 62 | ||
62 | struct ProgressiveRequirement { | 63 | struct ProgressiveRequirement { |
@@ -85,10 +86,10 @@ struct Door { | |||
85 | }; | 86 | }; |
86 | 87 | ||
87 | struct PanelDoor { | 88 | struct PanelDoor { |
88 | int room; | ||
89 | int ap_item_id = -1; | 89 | int ap_item_id = -1; |
90 | int group_ap_item_id = -1; | 90 | int group_ap_item_id = -1; |
91 | std::vector<ProgressiveRequirement> progressives; | 91 | std::vector<ProgressiveRequirement> progressives; |
92 | std::string item_name; | ||
92 | }; | 93 | }; |
93 | 94 | ||
94 | struct Exit { | 95 | struct Exit { |
@@ -102,8 +103,10 @@ struct PaintingExit { | |||
102 | int id; | 103 | int id; |
103 | int room; | 104 | int room; |
104 | std::string internal_id; | 105 | std::string internal_id; |
106 | std::string display_name; | ||
105 | std::optional<int> door; | 107 | std::optional<int> door; |
106 | bool entrance = false; | 108 | bool entrance = false; |
109 | int map_area; | ||
107 | }; | 110 | }; |
108 | 111 | ||
109 | struct Room { | 112 | struct Room { |
@@ -154,8 +157,9 @@ struct SubwayItem { | |||
154 | int id; | 157 | int id; |
155 | int x; | 158 | int x; |
156 | int y; | 159 | int y; |
160 | bool tilted = false; | ||
157 | std::optional<int> door; | 161 | std::optional<int> door; |
158 | std::vector<std::string> paintings; | 162 | std::optional<std::string> painting; |
159 | std::vector<std::string> tags; // 2-way teleports | 163 | std::vector<std::string> tags; // 2-way teleports |
160 | std::vector<std::string> entrances; // teleport entrances | 164 | std::vector<std::string> entrances; // teleport entrances |
161 | std::vector<std::string> exits; // teleport exits | 165 | std::vector<std::string> exits; // teleport exits |
@@ -173,7 +177,9 @@ const std::vector<Door>& GD_GetDoors(); | |||
173 | const Door& GD_GetDoor(int door_id); | 177 | const Door& GD_GetDoor(int door_id); |
174 | int GD_GetDoorByName(const std::string& name); | 178 | int GD_GetDoorByName(const std::string& name); |
175 | const Panel& GD_GetPanel(int panel_id); | 179 | const Panel& GD_GetPanel(int panel_id); |
180 | int GD_GetPanelBySolveIndex(int solve_index); | ||
176 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); | 181 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); |
182 | const std::vector<PaintingExit>& GD_GetPaintings(); | ||
177 | const PaintingExit& GD_GetPaintingExit(int painting_id); | 183 | const PaintingExit& GD_GetPaintingExit(int painting_id); |
178 | int GD_GetPaintingByName(const std::string& name); | 184 | int GD_GetPaintingByName(const std::string& name); |
179 | const std::vector<int>& GD_GetAchievementPanels(); | 185 | const std::vector<int>& GD_GetAchievementPanels(); |
@@ -184,5 +190,8 @@ const std::vector<SubwayItem>& GD_GetSubwayItems(); | |||
184 | const SubwayItem& GD_GetSubwayItem(int id); | 190 | const SubwayItem& GD_GetSubwayItem(int id); |
185 | std::optional<int> GD_GetSubwayItemForPainting(const std::string& painting_id); | 191 | std::optional<int> GD_GetSubwayItemForPainting(const std::string& painting_id); |
186 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); | 192 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); |
193 | std::string GD_GetItemName(int id); | ||
194 | |||
195 | LingoColor GetLingoColorForString(const std::string& str); | ||
187 | 196 | ||
188 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | 197 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ |
diff --git a/src/global.cpp b/src/global.cpp index 1eb3f8d..63f4a19 100644 --- a/src/global.cpp +++ b/src/global.cpp | |||
@@ -26,17 +26,19 @@ std::string GetAbsolutePath(std::string_view path) { | |||
26 | return (GetExecutableDirectory() / path).string(); | 26 | return (GetExecutableDirectory() / path).string(); |
27 | } | 27 | } |
28 | 28 | ||
29 | bool IsLocationWinCondition(const Location& location) { | 29 | std::string GetWinCondition() { |
30 | switch (AP_GetVictoryCondition()) { | 30 | switch (AP_GetVictoryCondition()) { |
31 | case kTHE_END: | 31 | case kTHE_END: |
32 | return location.ap_location_name == | 32 | return "Orange Tower Seventh Floor - THE END"; |
33 | "Orange Tower Seventh Floor - THE END"; | ||
34 | case kTHE_MASTER: | 33 | case kTHE_MASTER: |
35 | return location.ap_location_name == | 34 | return "Orange Tower Seventh Floor - THE MASTER"; |
36 | "Orange Tower Seventh Floor - THE MASTER"; | ||
37 | case kLEVEL_2: | 35 | case kLEVEL_2: |
38 | return location.ap_location_name == "Second Room - LEVEL 2"; | 36 | return "Second Room - LEVEL 2"; |
39 | case kPILGRIMAGE: | 37 | case kPILGRIMAGE: |
40 | return location.ap_location_name == "Pilgrim Antechamber - PILGRIM"; | 38 | return "Pilgrim Antechamber - PILGRIM"; |
41 | } | 39 | } |
42 | } | 40 | } |
41 | |||
42 | bool IsLocationWinCondition(const Location& location) { | ||
43 | return location.ap_location_name == GetWinCondition(); | ||
44 | } | ||
diff --git a/src/global.h b/src/global.h index 31ebde3..bdfa7ae 100644 --- a/src/global.h +++ b/src/global.h | |||
@@ -10,6 +10,8 @@ const std::filesystem::path& GetExecutableDirectory(); | |||
10 | 10 | ||
11 | std::string GetAbsolutePath(std::string_view path); | 11 | std::string GetAbsolutePath(std::string_view path); |
12 | 12 | ||
13 | std::string GetWinCondition(); | ||
14 | |||
13 | bool IsLocationWinCondition(const Location& location); | 15 | bool IsLocationWinCondition(const Location& location); |
14 | 16 | ||
15 | #endif /* end of include guard: GLOBAL_H_44945DBA */ | 17 | #endif /* end of include guard: GLOBAL_H_44945DBA */ |
diff --git a/src/godot_variant.cpp b/src/godot_variant.cpp deleted file mode 100644 index 152b9ef..0000000 --- a/src/godot_variant.cpp +++ /dev/null | |||
@@ -1,84 +0,0 @@ | |||
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 <cstdint> | ||
9 | #include <fstream> | ||
10 | #include <string> | ||
11 | #include <tuple> | ||
12 | #include <variant> | ||
13 | #include <vector> | ||
14 | |||
15 | namespace { | ||
16 | |||
17 | uint16_t ReadUint16(std::basic_istream<char>& stream) { | ||
18 | uint16_t result; | ||
19 | stream.read(reinterpret_cast<char*>(&result), 2); | ||
20 | return result; | ||
21 | } | ||
22 | |||
23 | uint32_t ReadUint32(std::basic_istream<char>& stream) { | ||
24 | uint32_t result; | ||
25 | stream.read(reinterpret_cast<char*>(&result), 4); | ||
26 | return result; | ||
27 | } | ||
28 | |||
29 | GodotVariant ParseVariant(std::basic_istream<char>& stream) { | ||
30 | uint16_t type = ReadUint16(stream); | ||
31 | stream.ignore(2); | ||
32 | |||
33 | switch (type) { | ||
34 | case 1: { | ||
35 | // bool | ||
36 | bool boolval = (ReadUint32(stream) == 1); | ||
37 | return {boolval}; | ||
38 | } | ||
39 | case 15: { | ||
40 | // nodepath | ||
41 | uint32_t name_length = ReadUint32(stream) & 0x7fffffff; | ||
42 | uint32_t subname_length = ReadUint32(stream) & 0x7fffffff; | ||
43 | uint32_t flags = ReadUint32(stream); | ||
44 | |||
45 | std::vector<std::string> result; | ||
46 | for (size_t i = 0; i < name_length + subname_length; i++) { | ||
47 | uint32_t char_length = ReadUint32(stream); | ||
48 | uint32_t padded_length = (char_length % 4 == 0) | ||
49 | ? char_length | ||
50 | : (char_length + 4 - (char_length % 4)); | ||
51 | std::vector<char> next_bytes(padded_length); | ||
52 | stream.read(next_bytes.data(), padded_length); | ||
53 | std::string next_piece; | ||
54 | std::copy(next_bytes.begin(), | ||
55 | std::next(next_bytes.begin(), char_length), | ||
56 | std::back_inserter(next_piece)); | ||
57 | result.push_back(next_piece); | ||
58 | } | ||
59 | |||
60 | return {result}; | ||
61 | } | ||
62 | case 19: { | ||
63 | // array | ||
64 | uint32_t length = ReadUint32(stream) & 0x7fffffff; | ||
65 | std::vector<GodotVariant> result; | ||
66 | for (size_t i = 0; i < length; i++) { | ||
67 | result.push_back(ParseVariant(stream)); | ||
68 | } | ||
69 | return {result}; | ||
70 | } | ||
71 | default: { | ||
72 | // eh | ||
73 | return {std::monostate{}}; | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | } // namespace | ||
79 | |||
80 | GodotVariant ParseGodotFile(std::string filename) { | ||
81 | std::ifstream file_stream(filename, std::ios_base::binary); | ||
82 | file_stream.ignore(4); | ||
83 | return ParseVariant(file_stream); | ||
84 | } | ||
diff --git a/src/godot_variant.h b/src/godot_variant.h deleted file mode 100644 index 620e569..0000000 --- a/src/godot_variant.h +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
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/icons.cpp b/src/icons.cpp new file mode 100644 index 0000000..87ba037 --- /dev/null +++ b/src/icons.cpp | |||
@@ -0,0 +1,22 @@ | |||
1 | #include "icons.h" | ||
2 | |||
3 | #include "global.h" | ||
4 | |||
5 | const wxBitmap* IconCache::GetIcon(const std::string& filename, wxSize size) { | ||
6 | std::tuple<std::string, int, int> key = {filename, size.x, size.y}; | ||
7 | |||
8 | if (!icons_.count(key)) { | ||
9 | icons_[key] = | ||
10 | wxBitmap(wxImage(GetAbsolutePath(filename).c_str(), | ||
11 | wxBITMAP_TYPE_PNG) | ||
12 | .Scale(size.x, size.y)); | ||
13 | } | ||
14 | |||
15 | return &icons_.at(key); | ||
16 | } | ||
17 | |||
18 | static IconCache* ICON_CACHE_INSTANCE = nullptr; | ||
19 | |||
20 | void SetTheIconCache(IconCache* instance) { ICON_CACHE_INSTANCE = instance; } | ||
21 | |||
22 | IconCache& GetTheIconCache() { return *ICON_CACHE_INSTANCE; } | ||
diff --git a/src/icons.h b/src/icons.h new file mode 100644 index 0000000..23dca2a --- /dev/null +++ b/src/icons.h | |||
@@ -0,0 +1,25 @@ | |||
1 | #ifndef ICONS_H_B95159A6 | ||
2 | #define ICONS_H_B95159A6 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <map> | ||
11 | #include <string> | ||
12 | #include <tuple> | ||
13 | |||
14 | class IconCache { | ||
15 | public: | ||
16 | const wxBitmap* GetIcon(const std::string& filename, wxSize size); | ||
17 | |||
18 | private: | ||
19 | std::map<std::tuple<std::string, int, int>, wxBitmap> icons_; | ||
20 | }; | ||
21 | |||
22 | void SetTheIconCache(IconCache* instance); | ||
23 | IconCache& GetTheIconCache(); | ||
24 | |||
25 | #endif /* end of include guard: ICONS_H_B95159A6 */ | ||
diff --git a/src/ipc_dialog.cpp b/src/ipc_dialog.cpp index f17c2d8..6763b7f 100644 --- a/src/ipc_dialog.cpp +++ b/src/ipc_dialog.cpp | |||
@@ -12,13 +12,14 @@ IpcDialog::IpcDialog() : wxDialog(nullptr, wxID_ANY, "Connect to game") { | |||
12 | address_value = GetTrackerConfig().ipc_address; | 12 | address_value = GetTrackerConfig().ipc_address; |
13 | } | 13 | } |
14 | 14 | ||
15 | address_box_ = | 15 | address_box_ = new wxTextCtrl(this, -1, wxString::FromUTF8(address_value), |
16 | new wxTextCtrl(this, -1, address_value, wxDefaultPosition, {300, -1}); | 16 | wxDefaultPosition, FromDIP(wxSize{300, -1})); |
17 | 17 | ||
18 | wxButton* reset_button = new wxButton(this, -1, "Use Default"); | 18 | wxButton* reset_button = new wxButton(this, -1, "Use Default"); |
19 | reset_button->Bind(wxEVT_BUTTON, &IpcDialog::OnResetClicked, this); | 19 | reset_button->Bind(wxEVT_BUTTON, &IpcDialog::OnResetClicked, this); |
20 | 20 | ||
21 | wxFlexGridSizer* form_sizer = new wxFlexGridSizer(3, 10, 10); | 21 | wxFlexGridSizer* form_sizer = |
22 | new wxFlexGridSizer(3, FromDIP(10), FromDIP(10)); | ||
22 | form_sizer->Add( | 23 | form_sizer->Add( |
23 | new wxStaticText(this, -1, "Address:"), | 24 | new wxStaticText(this, -1, "Address:"), |
24 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | 25 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); |
diff --git a/src/ipc_dialog.h b/src/ipc_dialog.h index 1caed01..a8c4512 100644 --- a/src/ipc_dialog.h +++ b/src/ipc_dialog.h | |||
@@ -13,7 +13,7 @@ class IpcDialog : public wxDialog { | |||
13 | public: | 13 | public: |
14 | IpcDialog(); | 14 | IpcDialog(); |
15 | 15 | ||
16 | std::string GetIpcAddress() { return address_box_->GetValue().ToStdString(); } | 16 | std::string GetIpcAddress() { return address_box_->GetValue().utf8_string(); } |
17 | 17 | ||
18 | private: | 18 | private: |
19 | void OnResetClicked(wxCommandEvent& event); | 19 | void OnResetClicked(wxCommandEvent& event); |
diff --git a/src/ipc_state.cpp b/src/ipc_state.cpp index 1f8d286..6e2a440 100644 --- a/src/ipc_state.cpp +++ b/src/ipc_state.cpp | |||
@@ -39,7 +39,6 @@ struct IPCState { | |||
39 | std::string game_ap_user; | 39 | std::string game_ap_user; |
40 | 40 | ||
41 | std::optional<std::tuple<int, int>> player_position; | 41 | std::optional<std::tuple<int, int>> player_position; |
42 | std::set<std::string> solved_panels; | ||
43 | 42 | ||
44 | // Thread state | 43 | // Thread state |
45 | std::unique_ptr<wswrap::WS> ws; | 44 | std::unique_ptr<wswrap::WS> ws; |
@@ -103,12 +102,6 @@ struct IPCState { | |||
103 | return player_position; | 102 | return player_position; |
104 | } | 103 | } |
105 | 104 | ||
106 | std::set<std::string> GetSolvedPanels() { | ||
107 | std::lock_guard state_guard(state_mutex); | ||
108 | |||
109 | return solved_panels; | ||
110 | } | ||
111 | |||
112 | private: | 105 | private: |
113 | void Thread() { | 106 | void Thread() { |
114 | for (;;) { | 107 | for (;;) { |
@@ -134,7 +127,6 @@ struct IPCState { | |||
134 | game_ap_user.clear(); | 127 | game_ap_user.clear(); |
135 | 128 | ||
136 | player_position = std::nullopt; | 129 | player_position = std::nullopt; |
137 | solved_panels.clear(); | ||
138 | 130 | ||
139 | if (address.empty()) { | 131 | if (address.empty()) { |
140 | initialized = false; | 132 | initialized = false; |
@@ -273,7 +265,6 @@ struct IPCState { | |||
273 | 265 | ||
274 | slot_matches = false; | 266 | slot_matches = false; |
275 | player_position = std::nullopt; | 267 | player_position = std::nullopt; |
276 | solved_panels.clear(); | ||
277 | } | 268 | } |
278 | } | 269 | } |
279 | 270 | ||
@@ -313,15 +304,7 @@ struct IPCState { | |||
313 | player_position = | 304 | player_position = |
314 | std::make_tuple<int, int>(msg["position"]["x"], msg["position"]["z"]); | 305 | std::make_tuple<int, int>(msg["position"]["x"], msg["position"]["z"]); |
315 | 306 | ||
316 | tracker_frame->RedrawPosition(); | 307 | tracker_frame->UpdateIndicators(StateUpdate{.player_position = true}); |
317 | } else if (msg["cmd"] == "SolvePanels") { | ||
318 | std::lock_guard state_guard(state_mutex); | ||
319 | |||
320 | for (std::string panel : msg["panels"]) { | ||
321 | solved_panels.insert(std::move(panel)); | ||
322 | } | ||
323 | |||
324 | tracker_frame->UpdateIndicators(kUPDATE_ONLY_PANELS); | ||
325 | } | 308 | } |
326 | } | 309 | } |
327 | 310 | ||
@@ -382,7 +365,3 @@ bool IPC_IsConnected() { return GetState().IsConnected(); } | |||
382 | std::optional<std::tuple<int, int>> IPC_GetPlayerPosition() { | 365 | std::optional<std::tuple<int, int>> IPC_GetPlayerPosition() { |
383 | return GetState().GetPlayerPosition(); | 366 | return GetState().GetPlayerPosition(); |
384 | } | 367 | } |
385 | |||
386 | std::set<std::string> IPC_GetSolvedPanels() { | ||
387 | return GetState().GetSolvedPanels(); | ||
388 | } | ||
diff --git a/src/ipc_state.h b/src/ipc_state.h index 7c9d68d..0e6fa51 100644 --- a/src/ipc_state.h +++ b/src/ipc_state.h | |||
@@ -20,6 +20,4 @@ bool IPC_IsConnected(); | |||
20 | 20 | ||
21 | std::optional<std::tuple<int, int>> IPC_GetPlayerPosition(); | 21 | std::optional<std::tuple<int, int>> IPC_GetPlayerPosition(); |
22 | 22 | ||
23 | std::set<std::string> IPC_GetSolvedPanels(); | ||
24 | |||
25 | #endif /* end of include guard: IPC_STATE_H_6B3B0958 */ | 23 | #endif /* end of include guard: IPC_STATE_H_6B3B0958 */ |
diff --git a/src/items_pane.cpp b/src/items_pane.cpp new file mode 100644 index 0000000..055eec0 --- /dev/null +++ b/src/items_pane.cpp | |||
@@ -0,0 +1,145 @@ | |||
1 | #include "items_pane.h" | ||
2 | |||
3 | #include <map> | ||
4 | |||
5 | namespace { | ||
6 | |||
7 | enum SortInstruction { | ||
8 | SI_NONE = 0, | ||
9 | SI_ASC = 1 << 0, | ||
10 | SI_DESC = 1 << 1, | ||
11 | SI_NAME = 1 << 2, | ||
12 | SI_AMOUNT = 1 << 3, | ||
13 | SI_ORDER = 1 << 4, | ||
14 | }; | ||
15 | |||
16 | inline SortInstruction operator|(SortInstruction lhs, SortInstruction rhs) { | ||
17 | return static_cast<SortInstruction>(static_cast<int>(lhs) | | ||
18 | static_cast<int>(rhs)); | ||
19 | } | ||
20 | |||
21 | template <typename T> | ||
22 | int ItemCompare(const T& lhs, const T& rhs, bool ascending) { | ||
23 | if (lhs < rhs) { | ||
24 | return ascending ? -1 : 1; | ||
25 | } else if (lhs > rhs) { | ||
26 | return ascending ? 1 : -1; | ||
27 | } else { | ||
28 | return 0; | ||
29 | } | ||
30 | } | ||
31 | |||
32 | int wxCALLBACK RowCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { | ||
33 | const ItemState& lhs = *reinterpret_cast<const ItemState*>(item1); | ||
34 | const ItemState& rhs = *reinterpret_cast<const ItemState*>(item2); | ||
35 | SortInstruction instruction = static_cast<SortInstruction>(sortData); | ||
36 | |||
37 | bool ascending = (instruction & SI_ASC) != 0; | ||
38 | if ((instruction & SI_NAME) != 0) { | ||
39 | return ItemCompare(lhs.name, rhs.name, ascending); | ||
40 | } else if ((instruction & SI_AMOUNT) != 0) { | ||
41 | return ItemCompare(lhs.amount, rhs.amount, ascending); | ||
42 | } else if ((instruction & SI_ORDER) != 0) { | ||
43 | return ItemCompare(lhs.index, rhs.index, ascending); | ||
44 | } else { | ||
45 | return 0; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | } // namespace | ||
50 | |||
51 | ItemsPane::ItemsPane(wxWindow* parent) | ||
52 | : wxListView(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, | ||
53 | wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_HRULES) { | ||
54 | AppendColumn("Item", wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE_USEHEADER); | ||
55 | AppendColumn("Amount", wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE_USEHEADER); | ||
56 | AppendColumn("Order", wxLIST_FORMAT_LEFT, wxLIST_AUTOSIZE_USEHEADER); | ||
57 | |||
58 | Bind(wxEVT_LIST_COL_CLICK, &ItemsPane::OnColClick, this); | ||
59 | Bind(wxEVT_DPI_CHANGED, &ItemsPane::OnDPIChanged, this); | ||
60 | } | ||
61 | |||
62 | void ItemsPane::ResetIndicators() { | ||
63 | DeleteAllItems(); | ||
64 | items_.clear(); | ||
65 | } | ||
66 | |||
67 | void ItemsPane::UpdateIndicators(const std::vector<ItemState>& items) { | ||
68 | std::map<std::string, ItemState> items_by_name; | ||
69 | |||
70 | for (const ItemState& item : items) { | ||
71 | items_by_name[item.name] = item; | ||
72 | } | ||
73 | |||
74 | for (int i = 0; i < GetItemCount(); i++) { | ||
75 | std::string item_name = GetItemText(i).utf8_string(); | ||
76 | auto it = items_by_name.find(item_name); | ||
77 | |||
78 | if (it != items_by_name.end()) { | ||
79 | SetItem(i, 1, std::to_string(it->second.amount)); | ||
80 | SetItem(i, 2, std::to_string(it->second.index)); | ||
81 | |||
82 | *reinterpret_cast<ItemState*>(GetItemData(i)) = it->second; | ||
83 | |||
84 | items_by_name.erase(item_name); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | for (const auto& [name, item] : items_by_name) { | ||
89 | int i = InsertItem(GetItemCount(), name); | ||
90 | SetItem(i, 1, std::to_string(item.amount)); | ||
91 | SetItem(i, 2, std::to_string(item.index)); | ||
92 | |||
93 | auto item_ptr = std::make_unique<ItemState>(item); | ||
94 | SetItemPtrData(i, reinterpret_cast<wxUIntPtr>(item_ptr.get())); | ||
95 | items_.push_back(std::move(item_ptr)); | ||
96 | } | ||
97 | |||
98 | SetColumnWidth(0, wxLIST_AUTOSIZE); | ||
99 | SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); | ||
100 | SetColumnWidth(2, wxLIST_AUTOSIZE_USEHEADER); | ||
101 | |||
102 | if (GetSortIndicator() != -1) { | ||
103 | DoSort(GetSortIndicator(), IsAscendingSortIndicator()); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | void ItemsPane::OnColClick(wxListEvent& event) { | ||
108 | int col = event.GetColumn(); | ||
109 | if (col == -1) { | ||
110 | return; | ||
111 | } | ||
112 | |||
113 | bool ascending = GetUpdatedAscendingSortIndicator(col); | ||
114 | |||
115 | DoSort(col, ascending); | ||
116 | } | ||
117 | |||
118 | void ItemsPane::OnDPIChanged(wxDPIChangedEvent& event) { | ||
119 | SetColumnWidth(0, wxLIST_AUTOSIZE); | ||
120 | SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); | ||
121 | SetColumnWidth(2, wxLIST_AUTOSIZE_USEHEADER); | ||
122 | |||
123 | event.Skip(); | ||
124 | } | ||
125 | |||
126 | void ItemsPane::DoSort(int col, bool ascending) { | ||
127 | SortInstruction instruction = SI_NONE; | ||
128 | if (ascending) { | ||
129 | instruction = instruction | SI_ASC; | ||
130 | } else { | ||
131 | instruction = instruction | SI_DESC; | ||
132 | } | ||
133 | |||
134 | if (col == 0) { | ||
135 | instruction = instruction | SI_NAME; | ||
136 | } else if (col == 1) { | ||
137 | instruction = instruction | SI_AMOUNT; | ||
138 | } else if (col == 2) { | ||
139 | instruction = instruction | SI_ORDER; | ||
140 | } | ||
141 | |||
142 | if (SortItems(RowCompare, instruction)) { | ||
143 | ShowSortIndicator(col, ascending); | ||
144 | } | ||
145 | } | ||
diff --git a/src/items_pane.h b/src/items_pane.h new file mode 100644 index 0000000..aa09c49 --- /dev/null +++ b/src/items_pane.h | |||
@@ -0,0 +1,33 @@ | |||
1 | #ifndef ITEMS_PANE_H_EB637EE3 | ||
2 | #define ITEMS_PANE_H_EB637EE3 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <wx/listctrl.h> | ||
11 | |||
12 | #include <memory> | ||
13 | #include <vector> | ||
14 | |||
15 | #include "ap_state.h" | ||
16 | |||
17 | class ItemsPane : public wxListView { | ||
18 | public: | ||
19 | explicit ItemsPane(wxWindow* parent); | ||
20 | |||
21 | void ResetIndicators(); | ||
22 | void UpdateIndicators(const std::vector<ItemState>& items); | ||
23 | |||
24 | private: | ||
25 | void OnColClick(wxListEvent& event); | ||
26 | void OnDPIChanged(wxDPIChangedEvent& event); | ||
27 | |||
28 | void DoSort(int col, bool ascending); | ||
29 | |||
30 | std::vector<std::unique_ptr<ItemState>> items_; | ||
31 | }; | ||
32 | |||
33 | #endif /* end of include guard: ITEMS_PANE_H_EB637EE3 */ | ||
diff --git a/src/log_dialog.cpp b/src/log_dialog.cpp new file mode 100644 index 0000000..3f0a8ad --- /dev/null +++ b/src/log_dialog.cpp | |||
@@ -0,0 +1,37 @@ | |||
1 | #include "log_dialog.h" | ||
2 | |||
3 | #include "logger.h" | ||
4 | |||
5 | wxDEFINE_EVENT(LOG_MESSAGE, wxCommandEvent); | ||
6 | |||
7 | LogDialog::LogDialog(wxWindow* parent) | ||
8 | : wxDialog(parent, wxID_ANY, "Debug Log", wxDefaultPosition, wxDefaultSize, | ||
9 | wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { | ||
10 | SetSize(FromDIP(wxSize{512, 280})); | ||
11 | |||
12 | text_area_ = | ||
13 | new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, | ||
14 | wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP); | ||
15 | text_area_->SetValue(TrackerReadPastLog()); | ||
16 | |||
17 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
18 | top_sizer->Add(text_area_, | ||
19 | wxSizerFlags().DoubleBorder().Expand().Proportion(1)); | ||
20 | |||
21 | SetSizer(top_sizer); | ||
22 | |||
23 | Bind(LOG_MESSAGE, &LogDialog::OnLogMessage, this); | ||
24 | } | ||
25 | |||
26 | void LogDialog::LogMessage(const std::string& message) { | ||
27 | wxCommandEvent* event = new wxCommandEvent(LOG_MESSAGE); | ||
28 | event->SetString(message); | ||
29 | QueueEvent(event); | ||
30 | } | ||
31 | |||
32 | void LogDialog::OnLogMessage(wxCommandEvent& event) { | ||
33 | if (!text_area_->IsEmpty()) { | ||
34 | text_area_->AppendText("\n"); | ||
35 | } | ||
36 | text_area_->AppendText(event.GetString()); | ||
37 | } | ||
diff --git a/src/log_dialog.h b/src/log_dialog.h new file mode 100644 index 0000000..c29251f --- /dev/null +++ b/src/log_dialog.h | |||
@@ -0,0 +1,24 @@ | |||
1 | #ifndef LOG_DIALOG_H_EEFD45B6 | ||
2 | #define LOG_DIALOG_H_EEFD45B6 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | wxDECLARE_EVENT(LOG_MESSAGE, wxCommandEvent); | ||
11 | |||
12 | class LogDialog : public wxDialog { | ||
13 | public: | ||
14 | explicit LogDialog(wxWindow* parent); | ||
15 | |||
16 | void LogMessage(const std::string& message); | ||
17 | |||
18 | private: | ||
19 | void OnLogMessage(wxCommandEvent& event); | ||
20 | |||
21 | wxTextCtrl* text_area_; | ||
22 | }; | ||
23 | |||
24 | #endif /* end of include guard: LOG_DIALOG_H_EEFD45B6 */ | ||
diff --git a/src/logger.cpp b/src/logger.cpp index 09fc331..8a08b58 100644 --- a/src/logger.cpp +++ b/src/logger.cpp | |||
@@ -3,8 +3,10 @@ | |||
3 | #include <chrono> | 3 | #include <chrono> |
4 | #include <fstream> | 4 | #include <fstream> |
5 | #include <mutex> | 5 | #include <mutex> |
6 | #include <sstream> | ||
6 | 7 | ||
7 | #include "global.h" | 8 | #include "global.h" |
9 | #include "log_dialog.h" | ||
8 | 10 | ||
9 | namespace { | 11 | namespace { |
10 | 12 | ||
@@ -14,19 +16,49 @@ class Logger { | |||
14 | 16 | ||
15 | void LogLine(const std::string& text) { | 17 | void LogLine(const std::string& text) { |
16 | std::lock_guard guard(file_mutex_); | 18 | std::lock_guard guard(file_mutex_); |
17 | logfile_ << "[" << std::chrono::system_clock::now() << "] " << text | 19 | std::ostringstream line; |
18 | << std::endl; | 20 | line << "[" << std::chrono::system_clock::now() << "] " << text; |
21 | |||
22 | logfile_ << line.str() << std::endl; | ||
19 | logfile_.flush(); | 23 | logfile_.flush(); |
24 | |||
25 | if (log_dialog_ != nullptr) { | ||
26 | log_dialog_->LogMessage(line.str()); | ||
27 | } | ||
28 | } | ||
29 | |||
30 | std::string GetContents() { | ||
31 | std::lock_guard guard(file_mutex_); | ||
32 | |||
33 | std::ifstream file_in(GetAbsolutePath("debug.log")); | ||
34 | std::ostringstream buffer; | ||
35 | buffer << file_in.rdbuf(); | ||
36 | |||
37 | return buffer.str(); | ||
38 | } | ||
39 | |||
40 | void SetLogDialog(LogDialog* log_dialog) { | ||
41 | std::lock_guard guard(file_mutex_); | ||
42 | log_dialog_ = log_dialog; | ||
20 | } | 43 | } |
21 | 44 | ||
22 | private: | 45 | private: |
23 | std::ofstream logfile_; | 46 | std::ofstream logfile_; |
24 | std::mutex file_mutex_; | 47 | std::mutex file_mutex_; |
48 | LogDialog* log_dialog_ = nullptr; | ||
25 | }; | 49 | }; |
26 | 50 | ||
51 | Logger& GetLogger() { | ||
52 | static Logger* instance = new Logger(); | ||
53 | return *instance; | ||
54 | } | ||
55 | |||
27 | } // namespace | 56 | } // namespace |
28 | 57 | ||
29 | void TrackerLog(std::string text) { | 58 | void TrackerLog(std::string text) { GetLogger().LogLine(text); } |
30 | static Logger* instance = new Logger(); | 59 | |
31 | instance->LogLine(text); | 60 | std::string TrackerReadPastLog() { return GetLogger().GetContents(); } |
61 | |||
62 | void TrackerSetLogDialog(LogDialog* log_dialog) { | ||
63 | GetLogger().SetLogDialog(log_dialog); | ||
32 | } | 64 | } |
diff --git a/src/logger.h b/src/logger.h index a27839f..f669790 100644 --- a/src/logger.h +++ b/src/logger.h | |||
@@ -3,6 +3,12 @@ | |||
3 | 3 | ||
4 | #include <string> | 4 | #include <string> |
5 | 5 | ||
6 | class LogDialog; | ||
7 | |||
6 | void TrackerLog(std::string message); | 8 | void TrackerLog(std::string message); |
7 | 9 | ||
10 | std::string TrackerReadPastLog(); | ||
11 | |||
12 | void TrackerSetLogDialog(LogDialog* log_dialog); | ||
13 | |||
8 | #endif /* end of include guard: LOGGER_H_9BDD07EA */ | 14 | #endif /* end of include guard: LOGGER_H_9BDD07EA */ |
diff --git a/src/main.cpp b/src/main.cpp index 1d7cc9e..574b6df 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
@@ -10,7 +10,7 @@ | |||
10 | 10 | ||
11 | class TrackerApp : public wxApp { | 11 | class TrackerApp : public wxApp { |
12 | public: | 12 | public: |
13 | virtual bool OnInit() { | 13 | virtual bool OnInit() override { |
14 | GetTrackerConfig().Load(); | 14 | GetTrackerConfig().Load(); |
15 | 15 | ||
16 | TrackerFrame *frame = new TrackerFrame(); | 16 | TrackerFrame *frame = new TrackerFrame(); |
diff --git a/src/options_pane.cpp b/src/options_pane.cpp new file mode 100644 index 0000000..844e145 --- /dev/null +++ b/src/options_pane.cpp | |||
@@ -0,0 +1,71 @@ | |||
1 | #include "options_pane.h" | ||
2 | |||
3 | #include "ap_state.h" | ||
4 | |||
5 | namespace { | ||
6 | |||
7 | const char* kDoorShuffleLabels[] = {"None", "Panels", "Doors"}; | ||
8 | const char* kLocationChecksLabels[] = {"Normal", "Reduced", "Insanity"}; | ||
9 | const char* kPanelShuffleLabels[] = {"None", "Rearrange"}; | ||
10 | const char* kVictoryConditionLabels[] = {"The End", "The Master", "Level 2", | ||
11 | "Pilgrimage"}; | ||
12 | const char* kSunwarpAccessLabels[] = {"Normal", "Disabled", "Unlock", | ||
13 | "Individual", "Progressive"}; | ||
14 | |||
15 | void AddRow(wxDataViewListCtrl* list, const std::string& text) { | ||
16 | wxVector<wxVariant> data; | ||
17 | data.push_back(wxVariant{text + ": "}); | ||
18 | data.push_back(wxVariant{""}); | ||
19 | list->AppendItem(data); | ||
20 | } | ||
21 | |||
22 | } // namespace | ||
23 | |||
24 | OptionsPane::OptionsPane(wxWindow* parent) | ||
25 | : wxDataViewListCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, | ||
26 | wxDV_ROW_LINES) { | ||
27 | AppendTextColumn("Name", wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE); | ||
28 | AppendTextColumn("Value"); | ||
29 | AddRow(this, "Shuffle Doors"); | ||
30 | AddRow(this, "Group Doors"); | ||
31 | AddRow(this, "Location Checks"); | ||
32 | AddRow(this, "Shuffle Colors"); | ||
33 | AddRow(this, "Shuffle Panels"); | ||
34 | AddRow(this, "Shuffle Paintings"); | ||
35 | AddRow(this, "Victory Condition"); | ||
36 | AddRow(this, "Early Color Hallways"); | ||
37 | AddRow(this, "Shuffle Postgame"); | ||
38 | AddRow(this, "Enable Pilgrimage"); | ||
39 | AddRow(this, "Pilgrimage Roof Access"); | ||
40 | AddRow(this, "Pilgrimage Paintings"); | ||
41 | AddRow(this, "Sunwarp Access"); | ||
42 | AddRow(this, "Shuffle Sunwarps"); | ||
43 | AddRow(this, "Mastery Achievements"); | ||
44 | AddRow(this, "Level 2 Requirement"); | ||
45 | } | ||
46 | |||
47 | void OptionsPane::OnConnect() { | ||
48 | SetTextValue(kDoorShuffleLabels[static_cast<size_t>(AP_GetDoorShuffleMode())], | ||
49 | 0, 1); | ||
50 | SetTextValue(AP_AreDoorsGrouped() ? "Yes" : "No", 1, 1); | ||
51 | SetTextValue( | ||
52 | kLocationChecksLabels[static_cast<size_t>(AP_GetLocationsChecks())], 2, | ||
53 | 1); | ||
54 | SetTextValue(AP_IsColorShuffle() ? "Yes" : "No", 3, 1); | ||
55 | SetTextValue( | ||
56 | kPanelShuffleLabels[static_cast<size_t>(AP_GetPanelShuffleMode())], 4, 1); | ||
57 | SetTextValue(AP_IsPaintingShuffle() ? "Yes" : "No", 5, 1); | ||
58 | SetTextValue( | ||
59 | kVictoryConditionLabels[static_cast<size_t>(AP_GetVictoryCondition())], 6, | ||
60 | 1); | ||
61 | SetTextValue(AP_HasEarlyColorHallways() ? "Yes" : "No", 7, 1); | ||
62 | SetTextValue(AP_IsPostgameShuffle() ? "Yes" : "No", 8, 1); | ||
63 | SetTextValue(AP_IsPilgrimageEnabled() ? "Yes" : "No", 9, 1); | ||
64 | SetTextValue(AP_DoesPilgrimageAllowRoofAccess() ? "Yes" : "No", 10, 1); | ||
65 | SetTextValue(AP_DoesPilgrimageAllowPaintings() ? "Yes" : "No", 11, 1); | ||
66 | SetTextValue(kSunwarpAccessLabels[static_cast<size_t>(AP_GetSunwarpAccess())], | ||
67 | 12, 1); | ||
68 | SetTextValue(AP_IsSunwarpShuffle() ? "Yes" : "No", 13, 1); | ||
69 | SetTextValue(std::to_string(AP_GetMasteryRequirement()), 14, 1); | ||
70 | SetTextValue(std::to_string(AP_GetLevel2Requirement()), 15, 1); | ||
71 | } | ||
diff --git a/src/options_pane.h b/src/options_pane.h new file mode 100644 index 0000000..e9df9f0 --- /dev/null +++ b/src/options_pane.h | |||
@@ -0,0 +1,19 @@ | |||
1 | #ifndef OPTIONS_PANE_H_026A0EC0 | ||
2 | #define OPTIONS_PANE_H_026A0EC0 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <wx/dataview.h> | ||
11 | |||
12 | class OptionsPane : public wxDataViewListCtrl { | ||
13 | public: | ||
14 | explicit OptionsPane(wxWindow* parent); | ||
15 | |||
16 | void OnConnect(); | ||
17 | }; | ||
18 | |||
19 | #endif /* end of include guard: OPTIONS_PANE_H_026A0EC0 */ | ||
diff --git a/src/paintings_pane.cpp b/src/paintings_pane.cpp new file mode 100644 index 0000000..bf5d71b --- /dev/null +++ b/src/paintings_pane.cpp | |||
@@ -0,0 +1,86 @@ | |||
1 | #include "paintings_pane.h" | ||
2 | |||
3 | #include <fmt/core.h> | ||
4 | #include <wx/dataview.h> | ||
5 | |||
6 | #include <map> | ||
7 | #include <set> | ||
8 | |||
9 | #include "ap_state.h" | ||
10 | #include "game_data.h" | ||
11 | #include "tracker_state.h" | ||
12 | |||
13 | namespace { | ||
14 | |||
15 | std::string GetPaintingDisplayName(const std::string& id) { | ||
16 | const PaintingExit& painting = GD_GetPaintingExit(GD_GetPaintingByName(id)); | ||
17 | const MapArea& map_area = GD_GetMapArea(painting.map_area); | ||
18 | |||
19 | return fmt::format("{} - {}", map_area.name, painting.display_name); | ||
20 | } | ||
21 | |||
22 | } // namespace | ||
23 | |||
24 | PaintingsPane::PaintingsPane(wxWindow* parent) : wxPanel(parent, wxID_ANY) { | ||
25 | wxStaticText* label = new wxStaticText( | ||
26 | this, wxID_ANY, "Shuffled paintings grouped by destination:"); | ||
27 | tree_ctrl_ = new wxDataViewTreeCtrl(this, wxID_ANY); | ||
28 | |||
29 | reveal_btn_ = new wxButton(this, wxID_ANY, "Reveal shuffled paintings"); | ||
30 | reveal_btn_->Disable(); | ||
31 | |||
32 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
33 | top_sizer->Add(label, wxSizerFlags().Border()); | ||
34 | top_sizer->Add(tree_ctrl_, wxSizerFlags().Expand().Proportion(1)); | ||
35 | top_sizer->Add(reveal_btn_, wxSizerFlags().Border().Expand()); | ||
36 | |||
37 | SetSizerAndFit(top_sizer); | ||
38 | |||
39 | tree_ctrl_->Bind(wxEVT_DATAVIEW_ITEM_START_EDITING, | ||
40 | &PaintingsPane::OnStartEditingCell, this); | ||
41 | reveal_btn_->Bind(wxEVT_BUTTON, &PaintingsPane::OnClickRevealPaintings, this); | ||
42 | } | ||
43 | |||
44 | void PaintingsPane::ResetIndicators() { | ||
45 | tree_ctrl_->DeleteAllItems(); | ||
46 | reveal_btn_->Enable(AP_IsPaintingShuffle()); | ||
47 | } | ||
48 | |||
49 | void PaintingsPane::UpdateIndicators(const std::vector<std::string>&) { | ||
50 | // TODO: Optimize this by using the paintings delta. | ||
51 | |||
52 | tree_ctrl_->DeleteAllItems(); | ||
53 | |||
54 | std::map<std::string, std::set<std::string>> grouped_paintings; | ||
55 | |||
56 | for (const auto& [from, to] : AP_GetPaintingMapping()) { | ||
57 | if (IsPaintingReachable(GD_GetPaintingByName(from)) && | ||
58 | AP_IsPaintingChecked(from)) { | ||
59 | grouped_paintings[GetPaintingDisplayName(to)].insert( | ||
60 | GetPaintingDisplayName(from)); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | for (const auto& [to, froms] : grouped_paintings) { | ||
65 | wxDataViewItem tree_branch = | ||
66 | tree_ctrl_->AppendContainer(wxDataViewItem(0), to); | ||
67 | |||
68 | for (const std::string& from : froms) { | ||
69 | tree_ctrl_->AppendItem(tree_branch, from); | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | |||
74 | void PaintingsPane::OnClickRevealPaintings(wxCommandEvent& event) { | ||
75 | if (wxMessageBox("Clicking yes will reveal the mapping between all shuffled " | ||
76 | "paintings. This is usually considered a spoiler, and is " | ||
77 | "likely not allowed during competitions. This action is not " | ||
78 | "reversible. Are you sure you want to proceed?", | ||
79 | "Warning", wxYES_NO | wxICON_WARNING) == wxNO) { | ||
80 | return; | ||
81 | } | ||
82 | |||
83 | AP_RevealPaintings(); | ||
84 | } | ||
85 | |||
86 | void PaintingsPane::OnStartEditingCell(wxDataViewEvent& event) { event.Veto(); } | ||
diff --git a/src/paintings_pane.h b/src/paintings_pane.h new file mode 100644 index 0000000..1d14510 --- /dev/null +++ b/src/paintings_pane.h | |||
@@ -0,0 +1,28 @@ | |||
1 | #ifndef PAINTINGS_PANE_H_815370D2 | ||
2 | #define PAINTINGS_PANE_H_815370D2 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | class wxDataViewEvent; | ||
11 | class wxDataViewTreeCtrl; | ||
12 | |||
13 | class PaintingsPane : public wxPanel { | ||
14 | public: | ||
15 | explicit PaintingsPane(wxWindow* parent); | ||
16 | |||
17 | void ResetIndicators(); | ||
18 | void UpdateIndicators(const std::vector<std::string>& paintings); | ||
19 | |||
20 | private: | ||
21 | void OnClickRevealPaintings(wxCommandEvent& event); | ||
22 | void OnStartEditingCell(wxDataViewEvent& event); | ||
23 | |||
24 | wxDataViewTreeCtrl* tree_ctrl_; | ||
25 | wxButton* reveal_btn_; | ||
26 | }; | ||
27 | |||
28 | #endif /* end of include guard: PAINTINGS_PANE_H_815370D2 */ | ||
diff --git a/src/report_popup.cpp b/src/report_popup.cpp new file mode 100644 index 0000000..703e87f --- /dev/null +++ b/src/report_popup.cpp | |||
@@ -0,0 +1,131 @@ | |||
1 | #include "report_popup.h" | ||
2 | |||
3 | #include <wx/dcbuffer.h> | ||
4 | |||
5 | #include <map> | ||
6 | #include <string> | ||
7 | |||
8 | #include "global.h" | ||
9 | #include "icons.h" | ||
10 | #include "tracker_state.h" | ||
11 | |||
12 | ReportPopup::ReportPopup(wxWindow* parent) | ||
13 | : wxScrolledCanvas(parent, wxID_ANY) { | ||
14 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
15 | |||
16 | LoadIcons(); | ||
17 | |||
18 | // TODO: This is slow on high-DPI screens. | ||
19 | SetScrollRate(5, 5); | ||
20 | |||
21 | SetBackgroundColour(*wxBLACK); | ||
22 | Hide(); | ||
23 | |||
24 | Bind(wxEVT_PAINT, &ReportPopup::OnPaint, this); | ||
25 | Bind(wxEVT_DPI_CHANGED, &ReportPopup::OnDPIChanged, this); | ||
26 | } | ||
27 | |||
28 | void ReportPopup::SetDoorId(int door_id) { | ||
29 | door_id_ = door_id; | ||
30 | |||
31 | ResetIndicators(); | ||
32 | } | ||
33 | |||
34 | void ReportPopup::Reset() { | ||
35 | door_id_ = -1; | ||
36 | } | ||
37 | |||
38 | void ReportPopup::ResetIndicators() { | ||
39 | if (door_id_ == -1) { | ||
40 | return; | ||
41 | } | ||
42 | |||
43 | wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); | ||
44 | const std::map<std::string, bool>& report = GetDoorRequirements(door_id_); | ||
45 | |||
46 | wxMemoryDC mem_dc; | ||
47 | mem_dc.SetFont(the_font); | ||
48 | |||
49 | int acc_height = FromDIP(10); | ||
50 | int col_width = 0; | ||
51 | |||
52 | for (const auto& [text, obtained] : report) { | ||
53 | wxSize item_extent = mem_dc.GetTextExtent(text); | ||
54 | int item_height = | ||
55 | std::max(FromDIP(32), item_extent.GetHeight()) + FromDIP(10); | ||
56 | acc_height += item_height; | ||
57 | |||
58 | if (item_extent.GetWidth() > col_width) { | ||
59 | col_width = item_extent.GetWidth(); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | int item_width = col_width + FromDIP(10 + 32); | ||
64 | full_width_ = item_width + FromDIP(20); | ||
65 | full_height_ = acc_height; | ||
66 | |||
67 | Fit(); | ||
68 | SetVirtualSize(full_width_, full_height_); | ||
69 | |||
70 | UpdateIndicators(); | ||
71 | } | ||
72 | |||
73 | void ReportPopup::UpdateIndicators() { | ||
74 | if (door_id_ == -1) { | ||
75 | return; | ||
76 | } | ||
77 | |||
78 | wxFont the_font = GetFont().Scale(GetDPIScaleFactor()); | ||
79 | const std::map<std::string, bool>& report = GetDoorRequirements(door_id_); | ||
80 | |||
81 | rendered_ = wxBitmap(full_width_, full_height_); | ||
82 | |||
83 | wxMemoryDC mem_dc; | ||
84 | mem_dc.SelectObject(rendered_); | ||
85 | mem_dc.SetPen(*wxTRANSPARENT_PEN); | ||
86 | mem_dc.SetBrush(*wxBLACK_BRUSH); | ||
87 | mem_dc.DrawRectangle({0, 0}, {full_width_, full_height_}); | ||
88 | |||
89 | mem_dc.SetFont(the_font); | ||
90 | |||
91 | int cur_height = FromDIP(10); | ||
92 | |||
93 | for (const auto& [text, obtained] : report) { | ||
94 | const wxBitmap* eye_ptr = obtained ? checked_eye_ : unchecked_eye_; | ||
95 | |||
96 | mem_dc.DrawBitmap(*eye_ptr, wxPoint{FromDIP(10), cur_height}); | ||
97 | |||
98 | mem_dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
99 | wxSize item_extent = mem_dc.GetTextExtent(text); | ||
100 | mem_dc.DrawText( | ||
101 | text, wxPoint{FromDIP(10 + 32 + 10), | ||
102 | cur_height + | ||
103 | (FromDIP(32) - mem_dc.GetFontMetrics().height) / 2}); | ||
104 | |||
105 | cur_height += FromDIP(10 + 32); | ||
106 | } | ||
107 | } | ||
108 | |||
109 | void ReportPopup::OnPaint(wxPaintEvent& event) { | ||
110 | if (door_id_ != -1) { | ||
111 | wxBufferedPaintDC dc(this); | ||
112 | PrepareDC(dc); | ||
113 | dc.DrawBitmap(rendered_, 0, 0); | ||
114 | } | ||
115 | |||
116 | event.Skip(); | ||
117 | } | ||
118 | |||
119 | void ReportPopup::OnDPIChanged(wxDPIChangedEvent& event) { | ||
120 | LoadIcons(); | ||
121 | ResetIndicators(); | ||
122 | |||
123 | event.Skip(); | ||
124 | } | ||
125 | |||
126 | void ReportPopup::LoadIcons() { | ||
127 | unchecked_eye_ = GetTheIconCache().GetIcon("assets/unchecked.png", | ||
128 | FromDIP(wxSize{32, 32})); | ||
129 | checked_eye_ = | ||
130 | GetTheIconCache().GetIcon("assets/checked.png", FromDIP(wxSize{32, 32})); | ||
131 | } | ||
diff --git a/src/report_popup.h b/src/report_popup.h new file mode 100644 index 0000000..bbb0bef --- /dev/null +++ b/src/report_popup.h | |||
@@ -0,0 +1,38 @@ | |||
1 | #ifndef REPORT_POPUP_H_E065BED4 | ||
2 | #define REPORT_POPUP_H_E065BED4 | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | class ReportPopup : public wxScrolledCanvas { | ||
11 | public: | ||
12 | explicit ReportPopup(wxWindow* parent); | ||
13 | |||
14 | void SetDoorId(int door_id); | ||
15 | |||
16 | void Reset(); | ||
17 | |||
18 | void ResetIndicators(); | ||
19 | void UpdateIndicators(); | ||
20 | |||
21 | private: | ||
22 | void OnPaint(wxPaintEvent& event); | ||
23 | void OnDPIChanged(wxDPIChangedEvent& event); | ||
24 | |||
25 | void LoadIcons(); | ||
26 | |||
27 | int door_id_ = -1; | ||
28 | |||
29 | const wxBitmap* unchecked_eye_; | ||
30 | const wxBitmap* checked_eye_; | ||
31 | |||
32 | int full_width_ = 0; | ||
33 | int full_height_ = 0; | ||
34 | |||
35 | wxBitmap rendered_; | ||
36 | }; | ||
37 | |||
38 | #endif /* end of include guard: REPORT_POPUP_H_E065BED4 */ | ||
diff --git a/src/settings_dialog.cpp b/src/settings_dialog.cpp index 0321b5a..95df577 100644 --- a/src/settings_dialog.cpp +++ b/src/settings_dialog.cpp | |||
@@ -3,30 +3,43 @@ | |||
3 | #include "tracker_config.h" | 3 | #include "tracker_config.h" |
4 | 4 | ||
5 | SettingsDialog::SettingsDialog() : wxDialog(nullptr, wxID_ANY, "Settings") { | 5 | SettingsDialog::SettingsDialog() : wxDialog(nullptr, wxID_ANY, "Settings") { |
6 | should_check_for_updates_box_ = new wxCheckBox( | 6 | wxStaticBoxSizer* main_box = |
7 | this, wxID_ANY, "Check for updates when the tracker opens"); | 7 | new wxStaticBoxSizer(wxVERTICAL, this, "General settings"); |
8 | |||
9 | should_check_for_updates_box_ = | ||
10 | new wxCheckBox(main_box->GetStaticBox(), wxID_ANY, | ||
11 | "Check for updates when the tracker opens"); | ||
8 | hybrid_areas_box_ = new wxCheckBox( | 12 | hybrid_areas_box_ = new wxCheckBox( |
9 | this, wxID_ANY, | 13 | main_box->GetStaticBox(), wxID_ANY, |
10 | "Use two colors to show that an area has partial availability"); | 14 | "Use two colors to show that an area has partial availability"); |
11 | show_hunt_panels_box_ = new wxCheckBox(this, wxID_ANY, "Show hunt panels"); | 15 | track_position_box_ = new wxCheckBox(main_box->GetStaticBox(), wxID_ANY, |
16 | "Track player position"); | ||
12 | 17 | ||
13 | should_check_for_updates_box_->SetValue( | 18 | should_check_for_updates_box_->SetValue( |
14 | GetTrackerConfig().should_check_for_updates); | 19 | GetTrackerConfig().should_check_for_updates); |
15 | hybrid_areas_box_->SetValue(GetTrackerConfig().hybrid_areas); | 20 | hybrid_areas_box_->SetValue(GetTrackerConfig().hybrid_areas); |
16 | show_hunt_panels_box_->SetValue(GetTrackerConfig().show_hunt_panels); | 21 | track_position_box_->SetValue(GetTrackerConfig().track_position); |
22 | |||
23 | main_box->Add(should_check_for_updates_box_, wxSizerFlags().Border()); | ||
24 | main_box->AddSpacer(2); | ||
25 | main_box->Add(hybrid_areas_box_, wxSizerFlags().Border()); | ||
26 | main_box->AddSpacer(2); | ||
27 | main_box->Add(track_position_box_, wxSizerFlags().Border()); | ||
28 | |||
29 | const wxString visible_panels_choices[] = {"Only show locations", | ||
30 | "Show locations and hunt panels", | ||
31 | "Show all panels"}; | ||
32 | visible_panels_box_ = | ||
33 | new wxRadioBox(this, wxID_ANY, "Visible panels", wxDefaultPosition, | ||
34 | wxDefaultSize, 3, visible_panels_choices, 1); | ||
35 | visible_panels_box_->SetSelection( | ||
36 | static_cast<int>(GetTrackerConfig().visible_panels)); | ||
17 | 37 | ||
18 | wxBoxSizer* form_sizer = new wxBoxSizer(wxVERTICAL); | 38 | wxBoxSizer* form_sizer = new wxBoxSizer(wxVERTICAL); |
19 | 39 | form_sizer->Add(main_box, wxSizerFlags().Border().Expand()); | |
20 | form_sizer->Add(should_check_for_updates_box_, wxSizerFlags().HorzBorder()); | 40 | form_sizer->Add(visible_panels_box_, wxSizerFlags().Border().Expand()); |
21 | form_sizer->AddSpacer(2); | 41 | form_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), |
22 | 42 | wxSizerFlags().Center().Border()); | |
23 | form_sizer->Add(hybrid_areas_box_, wxSizerFlags().HorzBorder()); | ||
24 | form_sizer->AddSpacer(2); | ||
25 | |||
26 | form_sizer->Add(show_hunt_panels_box_, wxSizerFlags().HorzBorder()); | ||
27 | form_sizer->AddSpacer(2); | ||
28 | |||
29 | form_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center()); | ||
30 | 43 | ||
31 | SetSizerAndFit(form_sizer); | 44 | SetSizerAndFit(form_sizer); |
32 | 45 | ||
diff --git a/src/settings_dialog.h b/src/settings_dialog.h index d7c1ed3..c4dacfa 100644 --- a/src/settings_dialog.h +++ b/src/settings_dialog.h | |||
@@ -7,6 +7,10 @@ | |||
7 | #include <wx/wx.h> | 7 | #include <wx/wx.h> |
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | #include <wx/radiobox.h> | ||
11 | |||
12 | #include "tracker_config.h" | ||
13 | |||
10 | class SettingsDialog : public wxDialog { | 14 | class SettingsDialog : public wxDialog { |
11 | public: | 15 | public: |
12 | SettingsDialog(); | 16 | SettingsDialog(); |
@@ -15,12 +19,17 @@ class SettingsDialog : public wxDialog { | |||
15 | return should_check_for_updates_box_->GetValue(); | 19 | return should_check_for_updates_box_->GetValue(); |
16 | } | 20 | } |
17 | bool GetHybridAreas() const { return hybrid_areas_box_->GetValue(); } | 21 | bool GetHybridAreas() const { return hybrid_areas_box_->GetValue(); } |
18 | bool GetShowHuntPanels() const { return show_hunt_panels_box_->GetValue(); } | 22 | TrackerConfig::VisiblePanels GetVisiblePanels() const { |
23 | return static_cast<TrackerConfig::VisiblePanels>( | ||
24 | visible_panels_box_->GetSelection()); | ||
25 | } | ||
26 | bool GetTrackPosition() const { return track_position_box_->GetValue(); } | ||
19 | 27 | ||
20 | private: | 28 | private: |
21 | wxCheckBox* should_check_for_updates_box_; | 29 | wxCheckBox* should_check_for_updates_box_; |
22 | wxCheckBox* hybrid_areas_box_; | 30 | wxCheckBox* hybrid_areas_box_; |
23 | wxCheckBox* show_hunt_panels_box_; | 31 | wxRadioBox* visible_panels_box_; |
32 | wxCheckBox* track_position_box_; | ||
24 | }; | 33 | }; |
25 | 34 | ||
26 | #endif /* end of include guard: SETTINGS_DIALOG_H_D8635719 */ | 35 | #endif /* end of include guard: SETTINGS_DIALOG_H_D8635719 */ |
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 0a250fb..55ac411 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp | |||
@@ -9,12 +9,28 @@ | |||
9 | #include "ap_state.h" | 9 | #include "ap_state.h" |
10 | #include "game_data.h" | 10 | #include "game_data.h" |
11 | #include "global.h" | 11 | #include "global.h" |
12 | #include "report_popup.h" | ||
12 | #include "tracker_state.h" | 13 | #include "tracker_state.h" |
13 | 14 | ||
14 | constexpr int AREA_ACTUAL_SIZE = 21; | 15 | constexpr int AREA_ACTUAL_SIZE = 21; |
15 | constexpr int OWL_ACTUAL_SIZE = 32; | 16 | constexpr int OWL_ACTUAL_SIZE = 32; |
17 | constexpr int PAINTING_RADIUS = 9; // the actual circles on the map are radius 11 | ||
18 | constexpr int PAINTING_EXIT_RADIUS = 6; | ||
16 | 19 | ||
17 | enum class ItemDrawType { kNone, kBox, kOwl }; | 20 | enum class ItemDrawType { kNone, kBox, kOwl, kOwlExit }; |
21 | |||
22 | namespace { | ||
23 | |||
24 | wxPoint GetSubwayItemMapCenter(const SubwayItem &subway_item) { | ||
25 | if (subway_item.painting) { | ||
26 | return {subway_item.x, subway_item.y}; | ||
27 | } else { | ||
28 | return {subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
29 | subway_item.y + AREA_ACTUAL_SIZE / 2}; | ||
30 | } | ||
31 | } | ||
32 | |||
33 | } // namespace | ||
18 | 34 | ||
19 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 35 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { |
20 | SetBackgroundStyle(wxBG_STYLE_PAINT); | 36 | SetBackgroundStyle(wxBG_STYLE_PAINT); |
@@ -31,14 +47,6 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | |||
31 | return; | 47 | return; |
32 | } | 48 | } |
33 | 49 | ||
34 | unchecked_eye_ = | ||
35 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | ||
36 | wxBITMAP_TYPE_PNG) | ||
37 | .Scale(32, 32)); | ||
38 | checked_eye_ = wxBitmap( | ||
39 | wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) | ||
40 | .Scale(32, 32)); | ||
41 | |||
42 | tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( | 50 | tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( |
43 | quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), | 51 | quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), |
44 | static_cast<float>(map_image_.GetHeight())}); | 52 | static_cast<float>(map_image_.GetHeight())}); |
@@ -57,12 +65,14 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | |||
57 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); | 65 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); |
58 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); | 66 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); |
59 | 67 | ||
60 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); | 68 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, FromDIP(wxPoint{15, 15})); |
61 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); | 69 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); |
62 | 70 | ||
63 | help_button_ = new wxButton(this, wxID_ANY, "Help"); | 71 | help_button_ = new wxButton(this, wxID_ANY, "Help"); |
64 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); | 72 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); |
65 | SetUpHelpButton(); | 73 | SetUpHelpButton(); |
74 | |||
75 | report_popup_ = new ReportPopup(this); | ||
66 | } | 76 | } |
67 | 77 | ||
68 | void SubwayMap::OnConnect() { | 78 | void SubwayMap::OnConnect() { |
@@ -73,11 +83,11 @@ void SubwayMap::OnConnect() { | |||
73 | std::map<std::string, std::vector<int>> exits; | 83 | std::map<std::string, std::vector<int>> exits; |
74 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | 84 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { |
75 | if (AP_HasEarlyColorHallways() && | 85 | if (AP_HasEarlyColorHallways() && |
76 | subway_item.special == "starting_room_paintings") { | 86 | subway_item.special == "early_color_hallways") { |
77 | entrances["early_ch"].push_back(subway_item.id); | 87 | entrances["early_ch"].push_back(subway_item.id); |
78 | } | 88 | } |
79 | 89 | ||
80 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { | 90 | if (AP_IsPaintingShuffle() && subway_item.painting) { |
81 | continue; | 91 | continue; |
82 | } | 92 | } |
83 | 93 | ||
@@ -174,6 +184,8 @@ void SubwayMap::OnConnect() { | |||
174 | } | 184 | } |
175 | 185 | ||
176 | checked_paintings_.clear(); | 186 | checked_paintings_.clear(); |
187 | |||
188 | UpdateIndicators(); | ||
177 | } | 189 | } |
178 | 190 | ||
179 | void SubwayMap::UpdateIndicators() { | 191 | void SubwayMap::UpdateIndicators() { |
@@ -202,6 +214,8 @@ void SubwayMap::UpdateIndicators() { | |||
202 | } | 214 | } |
203 | } | 215 | } |
204 | 216 | ||
217 | report_popup_->UpdateIndicators(); | ||
218 | |||
205 | Redraw(); | 219 | Redraw(); |
206 | } | 220 | } |
207 | 221 | ||
@@ -255,6 +269,9 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
255 | SetZoomPos({zoom_x_, zoom_y_}); | 269 | SetZoomPos({zoom_x_, zoom_y_}); |
256 | 270 | ||
257 | SetUpHelpButton(); | 271 | SetUpHelpButton(); |
272 | |||
273 | zoom_slider_->SetSize(FromDIP(15), FromDIP(15), wxDefaultCoord, | ||
274 | wxDefaultCoord, wxSIZE_AUTO); | ||
258 | } | 275 | } |
259 | 276 | ||
260 | wxBufferedPaintDC dc(this); | 277 | wxBufferedPaintDC dc(this); |
@@ -310,67 +327,6 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
310 | } | 327 | } |
311 | 328 | ||
312 | if (hovered_item_) { | 329 | if (hovered_item_) { |
313 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
314 | // knows when an item has a hover effect. | ||
315 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
316 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
317 | |||
318 | if (subway_door && !GetDoorRequirements(*subway_door).empty()) { | ||
319 | const std::map<std::string, bool> &report = | ||
320 | GetDoorRequirements(*subway_door); | ||
321 | |||
322 | int acc_height = 10; | ||
323 | int col_width = 0; | ||
324 | |||
325 | for (const auto &[text, obtained] : report) { | ||
326 | wxSize item_extent = dc.GetTextExtent(text); | ||
327 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
328 | acc_height += item_height; | ||
329 | |||
330 | if (item_extent.GetWidth() > col_width) { | ||
331 | col_width = item_extent.GetWidth(); | ||
332 | } | ||
333 | } | ||
334 | |||
335 | int item_width = col_width + 10 + 32; | ||
336 | int full_width = item_width + 20; | ||
337 | |||
338 | wxPoint popup_pos = | ||
339 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
340 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
341 | |||
342 | if (popup_pos.x + full_width > GetSize().GetWidth()) { | ||
343 | popup_pos.x = GetSize().GetWidth() - full_width; | ||
344 | } | ||
345 | if (popup_pos.y + acc_height > GetSize().GetHeight()) { | ||
346 | popup_pos.y = GetSize().GetHeight() - acc_height; | ||
347 | } | ||
348 | |||
349 | dc.SetPen(*wxTRANSPARENT_PEN); | ||
350 | dc.SetBrush(*wxBLACK_BRUSH); | ||
351 | dc.DrawRectangle(popup_pos, {full_width, acc_height}); | ||
352 | |||
353 | dc.SetFont(GetFont()); | ||
354 | |||
355 | int cur_height = 10; | ||
356 | |||
357 | for (const auto &[text, obtained] : report) { | ||
358 | wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; | ||
359 | |||
360 | dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); | ||
361 | |||
362 | dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
363 | wxSize item_extent = dc.GetTextExtent(text); | ||
364 | dc.DrawText( | ||
365 | text, | ||
366 | popup_pos + | ||
367 | wxPoint{10 + 32 + 10, | ||
368 | cur_height + (32 - dc.GetFontMetrics().height) / 2}); | ||
369 | |||
370 | cur_height += 10 + 32; | ||
371 | } | ||
372 | } | ||
373 | |||
374 | if (networks_.IsItemInNetwork(*hovered_item_)) { | 330 | if (networks_.IsItemInNetwork(*hovered_item_)) { |
375 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | 331 | dc.SetBrush(*wxTRANSPARENT_BRUSH); |
376 | 332 | ||
@@ -378,10 +334,8 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { | |||
378 | const SubwayItem &item1 = GD_GetSubwayItem(node.entry); | 334 | const SubwayItem &item1 = GD_GetSubwayItem(node.entry); |
379 | const SubwayItem &item2 = GD_GetSubwayItem(node.exit); | 335 | const SubwayItem &item2 = GD_GetSubwayItem(node.exit); |
380 | 336 | ||
381 | wxPoint item1_pos = MapPosToRenderPos( | 337 | wxPoint item1_pos = MapPosToRenderPos(GetSubwayItemMapCenter(item1)); |
382 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | 338 | wxPoint item2_pos = MapPosToRenderPos(GetSubwayItemMapCenter(item2)); |
383 | wxPoint item2_pos = MapPosToRenderPos( | ||
384 | {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); | ||
385 | 339 | ||
386 | int left = std::min(item1_pos.x, item2_pos.x); | 340 | int left = std::min(item1_pos.x, item2_pos.x); |
387 | int top = std::min(item1_pos.y, item2_pos.y); | 341 | int top = std::min(item1_pos.y, item2_pos.y); |
@@ -470,9 +424,7 @@ void SubwayMap::OnMouseMove(wxMouseEvent &event) { | |||
470 | } | 424 | } |
471 | 425 | ||
472 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { | 426 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { |
473 | hovered_item_ = actual_hover_; | 427 | EvaluateHover(); |
474 | |||
475 | Refresh(); | ||
476 | } | 428 | } |
477 | 429 | ||
478 | if (scroll_mode_) { | 430 | if (scroll_mode_) { |
@@ -514,13 +466,11 @@ void SubwayMap::OnMouseClick(wxMouseEvent &event) { | |||
514 | if ((subway_door && !GetDoorRequirements(*subway_door).empty()) || | 466 | if ((subway_door && !GetDoorRequirements(*subway_door).empty()) || |
515 | networks_.IsItemInNetwork(*hovered_item_)) { | 467 | networks_.IsItemInNetwork(*hovered_item_)) { |
516 | if (actual_hover_ != hovered_item_) { | 468 | if (actual_hover_ != hovered_item_) { |
517 | hovered_item_ = actual_hover_; | 469 | EvaluateHover(); |
518 | 470 | ||
519 | if (!hovered_item_) { | 471 | if (!hovered_item_) { |
520 | sticky_hover_ = false; | 472 | sticky_hover_ = false; |
521 | } | 473 | } |
522 | |||
523 | Refresh(); | ||
524 | } else { | 474 | } else { |
525 | sticky_hover_ = !sticky_hover_; | 475 | sticky_hover_ = !sticky_hover_; |
526 | } | 476 | } |
@@ -571,11 +521,12 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) { | |||
571 | "your mouse. Click again to stop.\nHover over a door to see the " | 521 | "your mouse. Click again to stop.\nHover over a door to see the " |
572 | "requirements to open it.\nHover over a warp or active painting to see " | 522 | "requirements to open it.\nHover over a warp or active painting to see " |
573 | "what it is connected to.\nFor one-way connections, there will be a " | 523 | "what it is connected to.\nFor one-way connections, there will be a " |
574 | "circle at the exit.\nIn painting shuffle, paintings that have not " | 524 | "circle at the exit.\nCircles represent paintings.\nA red circle means " |
575 | "yet been checked will not show their connections.\nA green shaded owl " | 525 | "that the painting is locked by a door.\nA blue circle means painting " |
576 | "means that there is a painting entrance there.\nA red shaded owl means " | 526 | "shuffle is enabled and the painting has not been checked yet.\nA black " |
577 | "that there are only painting exits there.\nClick on a door or " | 527 | "circle means the painting is not a warp.\nA green circle means that the " |
578 | "warp to make the popup stick until you click again.", | 528 | "painting is a warp.\nPainting exits will be indicated with an X.\nClick " |
529 | "on a door or warp to make the popup stick until you click again.", | ||
579 | "Subway Map Help"); | 530 | "Subway Map Help"); |
580 | } | 531 | } |
581 | 532 | ||
@@ -592,20 +543,32 @@ void SubwayMap::Redraw() { | |||
592 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | 543 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { |
593 | ItemDrawType draw_type = ItemDrawType::kNone; | 544 | ItemDrawType draw_type = ItemDrawType::kNone; |
594 | const wxBrush *brush_color = wxGREY_BRUSH; | 545 | const wxBrush *brush_color = wxGREY_BRUSH; |
595 | std::optional<wxColour> shade_color; | ||
596 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | 546 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); |
597 | 547 | ||
598 | if (AP_HasEarlyColorHallways() && | 548 | if (AP_HasEarlyColorHallways() && |
599 | subway_item.special == "starting_room_paintings") { | 549 | subway_item.special == "early_color_hallways") { |
600 | draw_type = ItemDrawType::kOwl; | 550 | draw_type = ItemDrawType::kOwl; |
601 | shade_color = wxColour(0, 255, 0, 128); | 551 | brush_color = wxGREEN_BRUSH; |
552 | } else if (subway_item.special == "starting_room_overhead") { | ||
553 | // Do not draw. | ||
554 | } else if (AP_IsColorShuffle() && subway_item.special && | ||
555 | subway_item.special->starts_with("color_")) { | ||
556 | std::string color_name = subway_item.special->substr(6); | ||
557 | LingoColor lingo_color = GetLingoColorForString(color_name); | ||
558 | int color_item_id = GD_GetItemIdForColor(lingo_color); | ||
559 | |||
560 | draw_type = ItemDrawType::kBox; | ||
561 | if (AP_HasItemSafe(color_item_id)) { | ||
562 | brush_color = wxGREEN_BRUSH; | ||
563 | } else { | ||
564 | brush_color = wxRED_BRUSH; | ||
565 | } | ||
602 | } else if (subway_item.special == "sun_painting") { | 566 | } else if (subway_item.special == "sun_painting") { |
603 | if (!AP_IsPilgrimageEnabled()) { | 567 | if (!AP_IsPilgrimageEnabled()) { |
568 | draw_type = ItemDrawType::kOwl; | ||
604 | if (IsDoorOpen(*subway_item.door)) { | 569 | if (IsDoorOpen(*subway_item.door)) { |
605 | draw_type = ItemDrawType::kOwl; | 570 | brush_color = wxGREEN_BRUSH; |
606 | shade_color = wxColour(0, 255, 0, 128); | ||
607 | } else { | 571 | } else { |
608 | draw_type = ItemDrawType::kBox; | ||
609 | brush_color = wxRED_BRUSH; | 572 | brush_color = wxRED_BRUSH; |
610 | } | 573 | } |
611 | } | 574 | } |
@@ -619,41 +582,28 @@ void SubwayMap::Redraw() { | |||
619 | } else { | 582 | } else { |
620 | brush_color = wxRED_BRUSH; | 583 | brush_color = wxRED_BRUSH; |
621 | } | 584 | } |
622 | } else if (!subway_item.paintings.empty()) { | 585 | } else if (subway_item.painting) { |
623 | if (AP_IsPaintingShuffle()) { | 586 | if (subway_door && !IsDoorOpen(*subway_door)) { |
624 | bool has_checked_painting = false; | 587 | draw_type = ItemDrawType::kOwl; |
625 | bool has_unchecked_painting = false; | 588 | brush_color = wxRED_BRUSH; |
626 | bool has_mapped_painting = false; | 589 | } else if (AP_IsPaintingShuffle()) { |
627 | bool has_codomain_painting = false; | 590 | if (!checked_paintings_.count(*subway_item.painting)) { |
628 | |||
629 | for (const std::string &painting_id : subway_item.paintings) { | ||
630 | if (checked_paintings_.count(painting_id)) { | ||
631 | has_checked_painting = true; | ||
632 | |||
633 | if (painting_mapping.count(painting_id)) { | ||
634 | has_mapped_painting = true; | ||
635 | } else if (AP_IsPaintingMappedTo(painting_id)) { | ||
636 | has_codomain_painting = true; | ||
637 | } | ||
638 | } else { | ||
639 | has_unchecked_painting = true; | ||
640 | } | ||
641 | } | ||
642 | |||
643 | if (has_unchecked_painting || has_mapped_painting || | ||
644 | has_codomain_painting) { | ||
645 | draw_type = ItemDrawType::kOwl; | 591 | draw_type = ItemDrawType::kOwl; |
646 | 592 | brush_color = wxBLUE_BRUSH; | |
647 | if (has_checked_painting) { | 593 | } else if (painting_mapping.count(*subway_item.painting)) { |
648 | if (has_mapped_painting) { | 594 | draw_type = ItemDrawType::kOwl; |
649 | shade_color = wxColour(0, 255, 0, 128); | 595 | brush_color = wxGREEN_BRUSH; |
650 | } else { | 596 | } else if (AP_IsPaintingMappedTo(*subway_item.painting)) { |
651 | shade_color = wxColour(255, 0, 0, 128); | 597 | draw_type = ItemDrawType::kOwlExit; |
652 | } | 598 | brush_color = wxGREEN_BRUSH; |
653 | } | ||
654 | } | 599 | } |
655 | } else if (subway_item.HasWarps()) { | 600 | } else if (subway_item.HasWarps()) { |
656 | draw_type = ItemDrawType::kOwl; | 601 | brush_color = wxGREEN_BRUSH; |
602 | if (!subway_item.exits.empty()) { | ||
603 | draw_type = ItemDrawType::kOwlExit; | ||
604 | } else { | ||
605 | draw_type = ItemDrawType::kOwl; | ||
606 | } | ||
657 | } | 607 | } |
658 | } else if (subway_door) { | 608 | } else if (subway_door) { |
659 | draw_type = ItemDrawType::kBox; | 609 | draw_type = ItemDrawType::kBox; |
@@ -673,21 +623,40 @@ void SubwayMap::Redraw() { | |||
673 | if (draw_type == ItemDrawType::kBox) { | 623 | if (draw_type == ItemDrawType::kBox) { |
674 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | 624 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); |
675 | gcdc.SetBrush(*brush_color); | 625 | gcdc.SetBrush(*brush_color); |
676 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | 626 | |
677 | } else if (draw_type == ItemDrawType::kOwl) { | 627 | if (subway_item.tilted) { |
678 | wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( | 628 | constexpr int AREA_TILTED_SIDE = |
679 | real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); | 629 | static_cast<int>(AREA_ACTUAL_SIZE / 1.41421356237); |
680 | gcdc.DrawBitmap(owl_bitmap, real_area_pos); | 630 | const wxPoint poly_points[] = {{AREA_TILTED_SIDE, 0}, |
681 | 631 | {2 * AREA_TILTED_SIDE, AREA_TILTED_SIDE}, | |
682 | if (shade_color) { | 632 | {AREA_TILTED_SIDE, 2 * AREA_TILTED_SIDE}, |
683 | gcdc.SetBrush(wxBrush(*shade_color)); | 633 | {0, AREA_TILTED_SIDE}}; |
634 | gcdc.DrawPolygon(4, poly_points, subway_item.x, subway_item.y); | ||
635 | } else { | ||
684 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | 636 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); |
685 | } | 637 | } |
638 | } else if (draw_type == ItemDrawType::kOwl || draw_type == ItemDrawType::kOwlExit) { | ||
639 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
640 | gcdc.SetBrush(*brush_color); | ||
641 | gcdc.DrawCircle(real_area_pos, PAINTING_RADIUS); | ||
642 | |||
643 | if (draw_type == ItemDrawType::kOwlExit) { | ||
644 | gcdc.DrawLine(subway_item.x - PAINTING_EXIT_RADIUS, | ||
645 | subway_item.y - PAINTING_EXIT_RADIUS, | ||
646 | subway_item.x + PAINTING_EXIT_RADIUS, | ||
647 | subway_item.y + PAINTING_EXIT_RADIUS); | ||
648 | gcdc.DrawLine(subway_item.x + PAINTING_EXIT_RADIUS, | ||
649 | subway_item.y - PAINTING_EXIT_RADIUS, | ||
650 | subway_item.x - PAINTING_EXIT_RADIUS, | ||
651 | subway_item.y + PAINTING_EXIT_RADIUS); | ||
652 | } | ||
686 | } | 653 | } |
687 | } | 654 | } |
688 | } | 655 | } |
689 | 656 | ||
690 | void SubwayMap::SetUpHelpButton() { | 657 | void SubwayMap::SetUpHelpButton() { |
658 | help_button_->SetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, | ||
659 | wxDefaultCoord, wxSIZE_AUTO); | ||
691 | help_button_->SetPosition({ | 660 | help_button_->SetPosition({ |
692 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | 661 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, |
693 | 15, | 662 | 15, |
@@ -723,6 +692,51 @@ void SubwayMap::EvaluateScroll(wxPoint pos) { | |||
723 | SetScrollSpeed(scroll_x, scroll_y); | 692 | SetScrollSpeed(scroll_x, scroll_y); |
724 | } | 693 | } |
725 | 694 | ||
695 | void SubwayMap::EvaluateHover() { | ||
696 | hovered_item_ = actual_hover_; | ||
697 | |||
698 | if (hovered_item_) { | ||
699 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
700 | // knows when an item has a hover effect. | ||
701 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
702 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
703 | |||
704 | if (subway_door && !GetDoorRequirements(*subway_door).empty()) { | ||
705 | report_popup_->SetDoorId(*subway_door); | ||
706 | |||
707 | wxPoint popupPos = | ||
708 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
709 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
710 | |||
711 | report_popup_->SetClientSize( | ||
712 | report_popup_->GetVirtualSize().GetWidth(), | ||
713 | std::min(GetSize().GetHeight(), | ||
714 | report_popup_->GetVirtualSize().GetHeight())); | ||
715 | |||
716 | if (popupPos.x + report_popup_->GetSize().GetWidth() > | ||
717 | GetSize().GetWidth()) { | ||
718 | popupPos.x = GetSize().GetWidth() - report_popup_->GetSize().GetWidth(); | ||
719 | } | ||
720 | if (popupPos.y + report_popup_->GetSize().GetHeight() > | ||
721 | GetSize().GetHeight()) { | ||
722 | popupPos.y = | ||
723 | GetSize().GetHeight() - report_popup_->GetSize().GetHeight(); | ||
724 | } | ||
725 | report_popup_->SetPosition(popupPos); | ||
726 | |||
727 | report_popup_->Show(); | ||
728 | } else { | ||
729 | report_popup_->Reset(); | ||
730 | report_popup_->Hide(); | ||
731 | } | ||
732 | } else { | ||
733 | report_popup_->Reset(); | ||
734 | report_popup_->Hide(); | ||
735 | } | ||
736 | |||
737 | Refresh(); | ||
738 | } | ||
739 | |||
726 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | 740 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { |
727 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | 741 | return {static_cast<int>(pos.x * render_width_ * zoom_ / |
728 | map_image_.GetSize().GetWidth() + | 742 | map_image_.GetSize().GetWidth() + |
@@ -812,6 +826,13 @@ std::optional<int> SubwayMap::GetRealSubwayDoor(const SubwayItem subway_item) { | |||
812 | 826 | ||
813 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | 827 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { |
814 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | 828 | const SubwayItem &subway_item = GD_GetSubwayItem(id); |
815 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | 829 | if (subway_item.painting) { |
816 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | 830 | return {static_cast<float>(subway_item.x) - PAINTING_RADIUS, |
831 | static_cast<float>(subway_item.y) - PAINTING_RADIUS, | ||
832 | PAINTING_RADIUS * 2, PAINTING_RADIUS * 2}; | ||
833 | } else { | ||
834 | return {static_cast<float>(subway_item.x), | ||
835 | static_cast<float>(subway_item.y), AREA_ACTUAL_SIZE, | ||
836 | AREA_ACTUAL_SIZE}; | ||
837 | } | ||
817 | } | 838 | } |
diff --git a/src/subway_map.h b/src/subway_map.h index 6aa31f5..b04c2fd 100644 --- a/src/subway_map.h +++ b/src/subway_map.h | |||
@@ -19,6 +19,8 @@ | |||
19 | #include "game_data.h" | 19 | #include "game_data.h" |
20 | #include "network_set.h" | 20 | #include "network_set.h" |
21 | 21 | ||
22 | class ReportPopup; | ||
23 | |||
22 | class SubwayMap : public wxPanel { | 24 | class SubwayMap : public wxPanel { |
23 | public: | 25 | public: |
24 | SubwayMap(wxWindow *parent); | 26 | SubwayMap(wxWindow *parent); |
@@ -46,6 +48,7 @@ class SubwayMap : public wxPanel { | |||
46 | wxPoint RenderPosToMapPos(wxPoint pos) const; | 48 | wxPoint RenderPosToMapPos(wxPoint pos) const; |
47 | 49 | ||
48 | void EvaluateScroll(wxPoint pos); | 50 | void EvaluateScroll(wxPoint pos); |
51 | void EvaluateHover(); | ||
49 | 52 | ||
50 | void SetZoomPos(wxPoint pos); | 53 | void SetZoomPos(wxPoint pos); |
51 | void SetScrollSpeed(int scroll_x, int scroll_y); | 54 | void SetScrollSpeed(int scroll_x, int scroll_y); |
@@ -55,8 +58,6 @@ class SubwayMap : public wxPanel { | |||
55 | 58 | ||
56 | wxImage map_image_; | 59 | wxImage map_image_; |
57 | wxImage owl_image_; | 60 | wxImage owl_image_; |
58 | wxBitmap unchecked_eye_; | ||
59 | wxBitmap checked_eye_; | ||
60 | 61 | ||
61 | wxBitmap rendered_; | 62 | wxBitmap rendered_; |
62 | int render_x_ = 0; | 63 | int render_x_ = 0; |
@@ -88,6 +89,8 @@ class SubwayMap : public wxPanel { | |||
88 | std::optional<int> actual_hover_; | 89 | std::optional<int> actual_hover_; |
89 | bool sticky_hover_ = false; | 90 | bool sticky_hover_ = false; |
90 | 91 | ||
92 | ReportPopup *report_popup_; | ||
93 | |||
91 | NetworkSet networks_; | 94 | NetworkSet networks_; |
92 | std::set<std::string> checked_paintings_; | 95 | std::set<std::string> checked_paintings_; |
93 | 96 | ||
diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp index 129dbbc..da5d60a 100644 --- a/src/tracker_config.cpp +++ b/src/tracker_config.cpp | |||
@@ -16,7 +16,9 @@ void TrackerConfig::Load() { | |||
16 | asked_to_check_for_updates = file["asked_to_check_for_updates"].as<bool>(); | 16 | asked_to_check_for_updates = file["asked_to_check_for_updates"].as<bool>(); |
17 | should_check_for_updates = file["should_check_for_updates"].as<bool>(); | 17 | should_check_for_updates = file["should_check_for_updates"].as<bool>(); |
18 | hybrid_areas = file["hybrid_areas"].as<bool>(); | 18 | hybrid_areas = file["hybrid_areas"].as<bool>(); |
19 | show_hunt_panels = file["show_hunt_panels"].as<bool>(); | 19 | if (file["show_hunt_panels"] && file["show_hunt_panels"].as<bool>()) { |
20 | visible_panels = kHUNT_PANELS; | ||
21 | } | ||
20 | 22 | ||
21 | if (file["connection_history"]) { | 23 | if (file["connection_history"]) { |
22 | for (const auto& connection : file["connection_history"]) { | 24 | for (const auto& connection : file["connection_history"]) { |
@@ -29,6 +31,9 @@ void TrackerConfig::Load() { | |||
29 | } | 31 | } |
30 | 32 | ||
31 | ipc_address = file["ipc_address"].as<std::string>(); | 33 | ipc_address = file["ipc_address"].as<std::string>(); |
34 | track_position = file["track_position"].as<bool>(); | ||
35 | visible_panels = | ||
36 | static_cast<VisiblePanels>(file["visible_panels"].as<int>()); | ||
32 | } catch (const std::exception&) { | 37 | } catch (const std::exception&) { |
33 | // It's fine if the file can't be loaded. | 38 | // It's fine if the file can't be loaded. |
34 | } | 39 | } |
@@ -42,7 +47,6 @@ void TrackerConfig::Save() { | |||
42 | output["asked_to_check_for_updates"] = asked_to_check_for_updates; | 47 | output["asked_to_check_for_updates"] = asked_to_check_for_updates; |
43 | output["should_check_for_updates"] = should_check_for_updates; | 48 | output["should_check_for_updates"] = should_check_for_updates; |
44 | output["hybrid_areas"] = hybrid_areas; | 49 | output["hybrid_areas"] = hybrid_areas; |
45 | output["show_hunt_panels"] = show_hunt_panels; | ||
46 | 50 | ||
47 | output.remove("connection_history"); | 51 | output.remove("connection_history"); |
48 | for (const ConnectionDetails& details : connection_history) { | 52 | for (const ConnectionDetails& details : connection_history) { |
@@ -55,6 +59,8 @@ void TrackerConfig::Save() { | |||
55 | } | 59 | } |
56 | 60 | ||
57 | output["ipc_address"] = ipc_address; | 61 | output["ipc_address"] = ipc_address; |
62 | output["track_position"] = track_position; | ||
63 | output["visible_panels"] = static_cast<int>(visible_panels); | ||
58 | 64 | ||
59 | std::ofstream filewriter(filename_); | 65 | std::ofstream filewriter(filename_); |
60 | filewriter << output; | 66 | filewriter << output; |
diff --git a/src/tracker_config.h b/src/tracker_config.h index 9244b74..df4105d 100644 --- a/src/tracker_config.h +++ b/src/tracker_config.h | |||
@@ -23,13 +23,20 @@ class TrackerConfig { | |||
23 | 23 | ||
24 | void Save(); | 24 | void Save(); |
25 | 25 | ||
26 | enum VisiblePanels { | ||
27 | kLOCATIONS_ONLY, | ||
28 | kHUNT_PANELS, | ||
29 | kALL_PANELS, | ||
30 | }; | ||
31 | |||
26 | ConnectionDetails connection_details; | 32 | ConnectionDetails connection_details; |
27 | bool asked_to_check_for_updates = false; | 33 | bool asked_to_check_for_updates = false; |
28 | bool should_check_for_updates = false; | 34 | bool should_check_for_updates = false; |
29 | bool hybrid_areas = false; | 35 | bool hybrid_areas = false; |
30 | bool show_hunt_panels = false; | ||
31 | std::deque<ConnectionDetails> connection_history; | 36 | std::deque<ConnectionDetails> connection_history; |
32 | std::string ipc_address; | 37 | std::string ipc_address; |
38 | bool track_position = true; | ||
39 | VisiblePanels visible_panels = kLOCATIONS_ONLY; | ||
33 | 40 | ||
34 | private: | 41 | private: |
35 | std::string filename_; | 42 | std::string filename_; |
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index 587d87b..e8d7ef6 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp | |||
@@ -5,9 +5,11 @@ | |||
5 | #include <wx/choicebk.h> | 5 | #include <wx/choicebk.h> |
6 | #include <wx/filedlg.h> | 6 | #include <wx/filedlg.h> |
7 | #include <wx/notebook.h> | 7 | #include <wx/notebook.h> |
8 | #include <wx/splitter.h> | ||
8 | #include <wx/stdpaths.h> | 9 | #include <wx/stdpaths.h> |
9 | #include <wx/webrequest.h> | 10 | #include <wx/webrequest.h> |
10 | 11 | ||
12 | #include <algorithm> | ||
11 | #include <nlohmann/json.hpp> | 13 | #include <nlohmann/json.hpp> |
12 | #include <sstream> | 14 | #include <sstream> |
13 | 15 | ||
@@ -16,6 +18,11 @@ | |||
16 | #include "connection_dialog.h" | 18 | #include "connection_dialog.h" |
17 | #include "ipc_dialog.h" | 19 | #include "ipc_dialog.h" |
18 | #include "ipc_state.h" | 20 | #include "ipc_state.h" |
21 | #include "items_pane.h" | ||
22 | #include "log_dialog.h" | ||
23 | #include "logger.h" | ||
24 | #include "options_pane.h" | ||
25 | #include "paintings_pane.h" | ||
19 | #include "settings_dialog.h" | 26 | #include "settings_dialog.h" |
20 | #include "subway_map.h" | 27 | #include "subway_map.h" |
21 | #include "tracker_config.h" | 28 | #include "tracker_config.h" |
@@ -44,14 +51,13 @@ enum TrackerFrameIds { | |||
44 | ID_SETTINGS = 3, | 51 | ID_SETTINGS = 3, |
45 | ID_ZOOM_IN = 4, | 52 | ID_ZOOM_IN = 4, |
46 | ID_ZOOM_OUT = 5, | 53 | ID_ZOOM_OUT = 5, |
47 | ID_OPEN_SAVE_FILE = 6, | ||
48 | ID_IPC_CONNECT = 7, | 54 | ID_IPC_CONNECT = 7, |
55 | ID_LOG_DIALOG = 8, | ||
49 | }; | 56 | }; |
50 | 57 | ||
51 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | 58 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); |
52 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | 59 | wxDEFINE_EVENT(STATE_CHANGED, StateChangedEvent); |
53 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | 60 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); |
54 | wxDEFINE_EVENT(REDRAW_POSITION, wxCommandEvent); | ||
55 | wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent); | 61 | wxDEFINE_EVENT(CONNECT_TO_AP, ApConnectEvent); |
56 | 62 | ||
57 | TrackerFrame::TrackerFrame() | 63 | TrackerFrame::TrackerFrame() |
@@ -62,16 +68,22 @@ TrackerFrame::TrackerFrame() | |||
62 | AP_SetTrackerFrame(this); | 68 | AP_SetTrackerFrame(this); |
63 | IPC_SetTrackerFrame(this); | 69 | IPC_SetTrackerFrame(this); |
64 | 70 | ||
71 | SetTheIconCache(&icons_); | ||
72 | |||
73 | updater_ = std::make_unique<Updater>(this); | ||
74 | updater_->Cleanup(); | ||
75 | |||
65 | wxMenu *menuFile = new wxMenu(); | 76 | wxMenu *menuFile = new wxMenu(); |
66 | menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago"); | 77 | menuFile->Append(ID_AP_CONNECT, "&Connect to Archipelago"); |
67 | menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo"); | 78 | menuFile->Append(ID_IPC_CONNECT, "&Connect to Lingo"); |
68 | menuFile->Append(ID_OPEN_SAVE_FILE, "&Open Save Data\tCtrl-O"); | ||
69 | menuFile->Append(ID_SETTINGS, "&Settings"); | 79 | menuFile->Append(ID_SETTINGS, "&Settings"); |
70 | menuFile->Append(wxID_EXIT); | 80 | menuFile->Append(wxID_EXIT); |
71 | 81 | ||
72 | wxMenu *menuView = new wxMenu(); | 82 | wxMenu *menuView = new wxMenu(); |
73 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); | 83 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); |
74 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); | 84 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); |
85 | menuView->AppendSeparator(); | ||
86 | menuView->Append(ID_LOG_DIALOG, "Show Log Window\tCtrl-L"); | ||
75 | 87 | ||
76 | zoom_in_menu_item_->Enable(false); | 88 | zoom_in_menu_item_->Enable(false); |
77 | zoom_out_menu_item_->Enable(false); | 89 | zoom_out_menu_item_->Enable(false); |
@@ -98,30 +110,43 @@ TrackerFrame::TrackerFrame() | |||
98 | ID_CHECK_FOR_UPDATES); | 110 | ID_CHECK_FOR_UPDATES); |
99 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); | 111 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); |
100 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); | 112 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); |
113 | Bind(wxEVT_MENU, &TrackerFrame::OnOpenLogWindow, this, ID_LOG_DIALOG); | ||
101 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); | 114 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); |
102 | Bind(wxEVT_MENU, &TrackerFrame::OnOpenFile, this, ID_OPEN_SAVE_FILE); | 115 | Bind(wxEVT_SPLITTER_SASH_POS_CHANGED, &TrackerFrame::OnSashPositionChanged, |
116 | this); | ||
103 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); | 117 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); |
104 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | 118 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); |
105 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | 119 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); |
106 | Bind(REDRAW_POSITION, &TrackerFrame::OnRedrawPosition, this); | ||
107 | Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this); | 120 | Bind(CONNECT_TO_AP, &TrackerFrame::OnConnectToAp, this); |
108 | 121 | ||
109 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); | 122 | wxSize logicalSize = FromDIP(wxSize(1280, 728)); |
123 | |||
124 | splitter_window_ = new wxSplitterWindow(this, wxID_ANY); | ||
125 | splitter_window_->SetMinimumPaneSize(logicalSize.x / 5); | ||
126 | |||
127 | wxChoicebook *choicebook = new wxChoicebook(splitter_window_, wxID_ANY); | ||
128 | |||
110 | achievements_pane_ = new AchievementsPane(choicebook); | 129 | achievements_pane_ = new AchievementsPane(choicebook); |
111 | choicebook->AddPage(achievements_pane_, "Achievements"); | 130 | choicebook->AddPage(achievements_pane_, "Achievements"); |
112 | 131 | ||
113 | notebook_ = new wxNotebook(this, wxID_ANY); | 132 | items_pane_ = new ItemsPane(choicebook); |
133 | choicebook->AddPage(items_pane_, "Items"); | ||
134 | |||
135 | options_pane_ = new OptionsPane(choicebook); | ||
136 | choicebook->AddPage(options_pane_, "Options"); | ||
137 | |||
138 | paintings_pane_ = new PaintingsPane(choicebook); | ||
139 | choicebook->AddPage(paintings_pane_, "Paintings"); | ||
140 | |||
141 | notebook_ = new wxNotebook(splitter_window_, wxID_ANY); | ||
114 | tracker_panel_ = new TrackerPanel(notebook_); | 142 | tracker_panel_ = new TrackerPanel(notebook_); |
115 | subway_map_ = new SubwayMap(notebook_); | 143 | subway_map_ = new SubwayMap(notebook_); |
116 | notebook_->AddPage(tracker_panel_, "Map"); | 144 | notebook_->AddPage(tracker_panel_, "Map"); |
117 | notebook_->AddPage(subway_map_, "Subway"); | 145 | notebook_->AddPage(subway_map_, "Subway"); |
118 | 146 | ||
119 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); | 147 | splitter_window_->SplitVertically(choicebook, notebook_, logicalSize.x / 4); |
120 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); | ||
121 | top_sizer->Add(notebook_, wxSizerFlags().Expand().Proportion(3)); | ||
122 | 148 | ||
123 | SetSizerAndFit(top_sizer); | 149 | SetSize(logicalSize); |
124 | SetSize(1280, 728); | ||
125 | 150 | ||
126 | if (!GetTrackerConfig().asked_to_check_for_updates) { | 151 | if (!GetTrackerConfig().asked_to_check_for_updates) { |
127 | GetTrackerConfig().asked_to_check_for_updates = true; | 152 | GetTrackerConfig().asked_to_check_for_updates = true; |
@@ -138,7 +163,7 @@ TrackerFrame::TrackerFrame() | |||
138 | } | 163 | } |
139 | 164 | ||
140 | if (GetTrackerConfig().should_check_for_updates) { | 165 | if (GetTrackerConfig().should_check_for_updates) { |
141 | CheckForUpdates(/*manual=*/false); | 166 | updater_->CheckForUpdates(/*invisible=*/true); |
142 | } | 167 | } |
143 | 168 | ||
144 | SetStatusText(GetStatusMessage()); | 169 | SetStatusText(GetStatusMessage()); |
@@ -158,15 +183,8 @@ void TrackerFrame::ResetIndicators() { | |||
158 | QueueEvent(new wxCommandEvent(STATE_RESET)); | 183 | QueueEvent(new wxCommandEvent(STATE_RESET)); |
159 | } | 184 | } |
160 | 185 | ||
161 | void TrackerFrame::UpdateIndicators(UpdateIndicatorsMode mode) { | 186 | void TrackerFrame::UpdateIndicators(StateUpdate state) { |
162 | auto evt = new wxCommandEvent(STATE_CHANGED); | 187 | QueueEvent(new StateChangedEvent(STATE_CHANGED, GetId(), std::move(state))); |
163 | evt->SetInt(static_cast<int>(mode)); | ||
164 | |||
165 | QueueEvent(evt); | ||
166 | } | ||
167 | |||
168 | void TrackerFrame::RedrawPosition() { | ||
169 | QueueEvent(new wxCommandEvent(REDRAW_POSITION)); | ||
170 | } | 188 | } |
171 | 189 | ||
172 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | 190 | void TrackerFrame::OnAbout(wxCommandEvent &event) { |
@@ -231,15 +249,18 @@ void TrackerFrame::OnSettings(wxCommandEvent &event) { | |||
231 | GetTrackerConfig().should_check_for_updates = | 249 | GetTrackerConfig().should_check_for_updates = |
232 | dlg.GetShouldCheckForUpdates(); | 250 | dlg.GetShouldCheckForUpdates(); |
233 | GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas(); | 251 | GetTrackerConfig().hybrid_areas = dlg.GetHybridAreas(); |
234 | GetTrackerConfig().show_hunt_panels = dlg.GetShowHuntPanels(); | 252 | GetTrackerConfig().visible_panels = dlg.GetVisiblePanels(); |
253 | GetTrackerConfig().track_position = dlg.GetTrackPosition(); | ||
235 | GetTrackerConfig().Save(); | 254 | GetTrackerConfig().Save(); |
236 | 255 | ||
237 | UpdateIndicators(); | 256 | UpdateIndicators(StateUpdate{.cleared_locations = true, |
257 | .player_position = true, | ||
258 | .changed_settings = true}); | ||
238 | } | 259 | } |
239 | } | 260 | } |
240 | 261 | ||
241 | void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { | 262 | void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { |
242 | CheckForUpdates(/*manual=*/true); | 263 | updater_->CheckForUpdates(/*invisible=*/false); |
243 | } | 264 | } |
244 | 265 | ||
245 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { | 266 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { |
@@ -254,132 +275,93 @@ void TrackerFrame::OnZoomOut(wxCommandEvent &event) { | |||
254 | } | 275 | } |
255 | } | 276 | } |
256 | 277 | ||
257 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { | 278 | void TrackerFrame::OnOpenLogWindow(wxCommandEvent &event) { |
258 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); | 279 | if (log_dialog_ == nullptr) { |
259 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | 280 | log_dialog_ = new LogDialog(this); |
260 | } | 281 | log_dialog_->Show(); |
282 | TrackerSetLogDialog(log_dialog_); | ||
261 | 283 | ||
262 | void TrackerFrame::OnOpenFile(wxCommandEvent &event) { | 284 | log_dialog_->Bind(wxEVT_CLOSE_WINDOW, &TrackerFrame::OnCloseLogWindow, |
263 | wxFileDialog open_file_dialog( | 285 | this); |
264 | this, "Open Lingo Save File", | 286 | } else { |
265 | fmt::format("{}\\Godot\\app_userdata\\Lingo\\level1_stable", | 287 | log_dialog_->SetFocus(); |
266 | wxStandardPaths::Get().GetUserConfigDir().ToStdString()), | ||
267 | AP_GetSaveName(), "Lingo save file (*.save)|*.save", | ||
268 | wxFD_OPEN | wxFD_FILE_MUST_EXIST); | ||
269 | if (open_file_dialog.ShowModal() == wxID_CANCEL) { | ||
270 | return; | ||
271 | } | 288 | } |
289 | } | ||
272 | 290 | ||
273 | std::string savedata_path = open_file_dialog.GetPath().ToStdString(); | 291 | void TrackerFrame::OnCloseLogWindow(wxCloseEvent& event) { |
292 | TrackerSetLogDialog(nullptr); | ||
293 | log_dialog_ = nullptr; | ||
274 | 294 | ||
275 | if (panels_panel_ == nullptr) { | 295 | event.Skip(); |
276 | panels_panel_ = new TrackerPanel(notebook_); | 296 | } |
277 | notebook_->AddPage(panels_panel_, "Panels"); | ||
278 | } | ||
279 | 297 | ||
280 | notebook_->SetSelection(notebook_->FindPage(panels_panel_)); | 298 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { |
281 | panels_panel_->SetSavedataPath(savedata_path); | 299 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); |
300 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | ||
301 | } | ||
302 | |||
303 | void TrackerFrame::OnSashPositionChanged(wxSplitterEvent& event) { | ||
304 | notebook_->Refresh(); | ||
282 | } | 305 | } |
283 | 306 | ||
284 | void TrackerFrame::OnStateReset(wxCommandEvent &event) { | 307 | void TrackerFrame::OnStateReset(wxCommandEvent &event) { |
285 | tracker_panel_->UpdateIndicators(); | 308 | tracker_panel_->UpdateIndicators(/*reset=*/true); |
286 | achievements_pane_->UpdateIndicators(); | 309 | achievements_pane_->UpdateIndicators(); |
310 | items_pane_->ResetIndicators(); | ||
311 | options_pane_->OnConnect(); | ||
312 | paintings_pane_->ResetIndicators(); | ||
287 | subway_map_->OnConnect(); | 313 | subway_map_->OnConnect(); |
288 | if (panels_panel_ != nullptr) { | ||
289 | notebook_->DeletePage(notebook_->FindPage(panels_panel_)); | ||
290 | panels_panel_ = nullptr; | ||
291 | } | ||
292 | Refresh(); | 314 | Refresh(); |
293 | } | 315 | } |
294 | 316 | ||
295 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | 317 | void TrackerFrame::OnStateChanged(StateChangedEvent &event) { |
296 | UpdateIndicatorsMode mode = static_cast<UpdateIndicatorsMode>(event.GetInt()); | 318 | const StateUpdate &state = event.GetState(); |
297 | 319 | ||
298 | if (mode == kUPDATE_ALL_INDICATORS) { | 320 | bool hunt_panels = false; |
299 | tracker_panel_->UpdateIndicators(); | 321 | if (GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) { |
300 | achievements_pane_->UpdateIndicators(); | 322 | hunt_panels = std::any_of( |
323 | state.panels.begin(), state.panels.end(), [](int solve_index) { | ||
324 | return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)).hunt; | ||
325 | }); | ||
326 | } else if (GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) { | ||
327 | hunt_panels = true; | ||
328 | } | ||
329 | |||
330 | if (!state.items.empty() || !state.paintings.empty() || | ||
331 | state.cleared_locations || hunt_panels) { | ||
332 | // TODO: The only real reason to reset tracker_panel during an active | ||
333 | // connection is if the hunt panels setting changes. If we remove hunt | ||
334 | // panels later, we can get rid of this. | ||
335 | tracker_panel_->UpdateIndicators(/*reset=*/state.changed_settings); | ||
301 | subway_map_->UpdateIndicators(); | 336 | subway_map_->UpdateIndicators(); |
302 | if (panels_panel_ != nullptr) { | ||
303 | panels_panel_->UpdateIndicators(); | ||
304 | } | ||
305 | Refresh(); | 337 | Refresh(); |
306 | } else if (mode == kUPDATE_ONLY_PANELS) { | 338 | } else if (state.player_position && GetTrackerConfig().track_position) { |
307 | if (panels_panel_ == nullptr) { | 339 | if (notebook_->GetSelection() == 0) { |
308 | panels_panel_ = new TrackerPanel(notebook_); | 340 | tracker_panel_->Refresh(); |
309 | panels_panel_->SetPanelsMode(); | ||
310 | notebook_->AddPage(panels_panel_, "Panels"); | ||
311 | } | ||
312 | panels_panel_->UpdateIndicators(); | ||
313 | if (notebook_->GetSelection() == 2) { | ||
314 | Refresh(); | ||
315 | } | 341 | } |
316 | } | 342 | } |
317 | } | ||
318 | 343 | ||
319 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { | 344 | if (std::any_of(state.panels.begin(), state.panels.end(), |
320 | SetStatusText(GetStatusMessage()); | 345 | [](int solve_index) { |
321 | } | 346 | return GD_GetPanel(GD_GetPanelBySolveIndex(solve_index)) |
322 | 347 | .achievement; | |
323 | void TrackerFrame::OnRedrawPosition(wxCommandEvent &event) { | 348 | })) { |
324 | if (notebook_->GetSelection() == 0) { | 349 | achievements_pane_->UpdateIndicators(); |
325 | tracker_panel_->Refresh(); | ||
326 | } else if (notebook_->GetSelection() == 2) { | ||
327 | panels_panel_->Refresh(); | ||
328 | } | 350 | } |
329 | } | ||
330 | |||
331 | void TrackerFrame::OnConnectToAp(ApConnectEvent &event) { | ||
332 | AP_Connect(event.GetServer(), event.GetUser(), event.GetPass()); | ||
333 | } | ||
334 | |||
335 | void TrackerFrame::CheckForUpdates(bool manual) { | ||
336 | wxWebRequest request = wxWebSession::GetDefault().CreateRequest( | ||
337 | this, "https://code.fourisland.com/lingo-ap-tracker/plain/VERSION"); | ||
338 | 351 | ||
339 | if (!request.IsOk()) { | 352 | if (!state.items.empty()) { |
340 | if (manual) { | 353 | items_pane_->UpdateIndicators(state.items); |
341 | wxMessageBox("Could not check for updates.", "Error", | 354 | } |
342 | wxOK | wxICON_ERROR); | ||
343 | } else { | ||
344 | SetStatusText("Could not check for updates."); | ||
345 | } | ||
346 | 355 | ||
347 | return; | 356 | if (!state.paintings.empty()) { |
357 | paintings_pane_->UpdateIndicators(state.paintings); | ||
348 | } | 358 | } |
359 | } | ||
349 | 360 | ||
350 | Bind(wxEVT_WEBREQUEST_STATE, [this, manual](wxWebRequestEvent &evt) { | 361 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { |
351 | if (evt.GetState() == wxWebRequest::State_Completed) { | 362 | SetStatusText(wxString::FromUTF8(GetStatusMessage())); |
352 | std::string response = evt.GetResponse().AsString().ToStdString(); | 363 | } |
353 | |||
354 | Version latest_version(response); | ||
355 | if (kTrackerVersion < latest_version) { | ||
356 | std::ostringstream message_text; | ||
357 | message_text << "There is a newer version of Lingo AP Tracker " | ||
358 | "available. You have " | ||
359 | << kTrackerVersion.ToString() | ||
360 | << ", and the latest version is " | ||
361 | << latest_version.ToString() | ||
362 | << ". Would you like to update?"; | ||
363 | |||
364 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == | ||
365 | wxYES) { | ||
366 | wxLaunchDefaultBrowser( | ||
367 | "https://code.fourisland.com/lingo-ap-tracker/about/" | ||
368 | "CHANGELOG.md"); | ||
369 | } | ||
370 | } else if (manual) { | ||
371 | wxMessageBox("Lingo AP Tracker is up to date!", "Lingo AP Tracker", | ||
372 | wxOK); | ||
373 | } | ||
374 | } else if (evt.GetState() == wxWebRequest::State_Failed) { | ||
375 | if (manual) { | ||
376 | wxMessageBox("Could not check for updates.", "Error", | ||
377 | wxOK | wxICON_ERROR); | ||
378 | } else { | ||
379 | SetStatusText("Could not check for updates."); | ||
380 | } | ||
381 | } | ||
382 | }); | ||
383 | 364 | ||
384 | request.Start(); | 365 | void TrackerFrame::OnConnectToAp(ApConnectEvent &event) { |
366 | AP_Connect(event.GetServer(), event.GetUser(), event.GetPass()); | ||
385 | } | 367 | } |
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index e9fec17..00bbe70 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h | |||
@@ -7,11 +7,24 @@ | |||
7 | #include <wx/wx.h> | 7 | #include <wx/wx.h> |
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | #include <memory> | ||
11 | #include <set> | ||
12 | |||
13 | #include "ap_state.h" | ||
14 | #include "icons.h" | ||
15 | #include "updater.h" | ||
16 | |||
10 | class AchievementsPane; | 17 | class AchievementsPane; |
18 | class ItemsPane; | ||
19 | class LogDialog; | ||
20 | class OptionsPane; | ||
21 | class PaintingsPane; | ||
11 | class SubwayMap; | 22 | class SubwayMap; |
12 | class TrackerPanel; | 23 | class TrackerPanel; |
13 | class wxBookCtrlEvent; | 24 | class wxBookCtrlEvent; |
14 | class wxNotebook; | 25 | class wxNotebook; |
26 | class wxSplitterEvent; | ||
27 | class wxSplitterWindow; | ||
15 | 28 | ||
16 | class ApConnectEvent : public wxEvent { | 29 | class ApConnectEvent : public wxEvent { |
17 | public: | 30 | public: |
@@ -36,17 +49,34 @@ class ApConnectEvent : public wxEvent { | |||
36 | std::string ap_pass_; | 49 | std::string ap_pass_; |
37 | }; | 50 | }; |
38 | 51 | ||
52 | struct StateUpdate { | ||
53 | std::vector<ItemState> items; | ||
54 | bool progression_items = false; | ||
55 | std::vector<std::string> paintings; | ||
56 | bool cleared_locations = false; | ||
57 | std::set<int> panels; | ||
58 | bool player_position = false; | ||
59 | bool changed_settings = false; | ||
60 | }; | ||
61 | |||
62 | class StateChangedEvent : public wxEvent { | ||
63 | public: | ||
64 | StateChangedEvent(wxEventType eventType, int winid, StateUpdate state) | ||
65 | : wxEvent(winid, eventType), state_(std::move(state)) {} | ||
66 | |||
67 | const StateUpdate &GetState() const { return state_; } | ||
68 | |||
69 | virtual wxEvent *Clone() const { return new StateChangedEvent(*this); } | ||
70 | |||
71 | private: | ||
72 | StateUpdate state_; | ||
73 | }; | ||
74 | |||
39 | wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); | 75 | wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); |
40 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); | 76 | wxDECLARE_EVENT(STATE_CHANGED, StateChangedEvent); |
41 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); | 77 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); |
42 | wxDECLARE_EVENT(REDRAW_POSITION, wxCommandEvent); | ||
43 | wxDECLARE_EVENT(CONNECT_TO_AP, ApConnectEvent); | 78 | wxDECLARE_EVENT(CONNECT_TO_AP, ApConnectEvent); |
44 | 79 | ||
45 | enum UpdateIndicatorsMode { | ||
46 | kUPDATE_ALL_INDICATORS = 0, | ||
47 | kUPDATE_ONLY_PANELS = 1, | ||
48 | }; | ||
49 | |||
50 | class TrackerFrame : public wxFrame { | 80 | class TrackerFrame : public wxFrame { |
51 | public: | 81 | public: |
52 | TrackerFrame(); | 82 | TrackerFrame(); |
@@ -55,8 +85,7 @@ class TrackerFrame : public wxFrame { | |||
55 | void UpdateStatusMessage(); | 85 | void UpdateStatusMessage(); |
56 | 86 | ||
57 | void ResetIndicators(); | 87 | void ResetIndicators(); |
58 | void UpdateIndicators(UpdateIndicatorsMode mode = kUPDATE_ALL_INDICATORS); | 88 | void UpdateIndicators(StateUpdate state); |
59 | void RedrawPosition(); | ||
60 | 89 | ||
61 | private: | 90 | private: |
62 | void OnExit(wxCommandEvent &event); | 91 | void OnExit(wxCommandEvent &event); |
@@ -67,25 +96,32 @@ class TrackerFrame : public wxFrame { | |||
67 | void OnCheckForUpdates(wxCommandEvent &event); | 96 | void OnCheckForUpdates(wxCommandEvent &event); |
68 | void OnZoomIn(wxCommandEvent &event); | 97 | void OnZoomIn(wxCommandEvent &event); |
69 | void OnZoomOut(wxCommandEvent &event); | 98 | void OnZoomOut(wxCommandEvent &event); |
99 | void OnOpenLogWindow(wxCommandEvent &event); | ||
100 | void OnCloseLogWindow(wxCloseEvent &event); | ||
70 | void OnChangePage(wxBookCtrlEvent &event); | 101 | void OnChangePage(wxBookCtrlEvent &event); |
71 | void OnOpenFile(wxCommandEvent &event); | 102 | void OnSashPositionChanged(wxSplitterEvent &event); |
72 | 103 | ||
73 | void OnStateReset(wxCommandEvent &event); | 104 | void OnStateReset(wxCommandEvent &event); |
74 | void OnStateChanged(wxCommandEvent &event); | 105 | void OnStateChanged(StateChangedEvent &event); |
75 | void OnStatusChanged(wxCommandEvent &event); | 106 | void OnStatusChanged(wxCommandEvent &event); |
76 | void OnRedrawPosition(wxCommandEvent &event); | ||
77 | void OnConnectToAp(ApConnectEvent &event); | 107 | void OnConnectToAp(ApConnectEvent &event); |
108 | |||
109 | std::unique_ptr<Updater> updater_; | ||
78 | 110 | ||
79 | void CheckForUpdates(bool manual); | 111 | wxSplitterWindow *splitter_window_; |
80 | |||
81 | wxNotebook *notebook_; | 112 | wxNotebook *notebook_; |
82 | TrackerPanel *tracker_panel_; | 113 | TrackerPanel *tracker_panel_; |
83 | AchievementsPane *achievements_pane_; | 114 | AchievementsPane *achievements_pane_; |
115 | ItemsPane *items_pane_; | ||
116 | OptionsPane *options_pane_; | ||
117 | PaintingsPane *paintings_pane_; | ||
84 | SubwayMap *subway_map_; | 118 | SubwayMap *subway_map_; |
85 | TrackerPanel *panels_panel_ = nullptr; | 119 | LogDialog *log_dialog_ = nullptr; |
86 | 120 | ||
87 | wxMenuItem *zoom_in_menu_item_; | 121 | wxMenuItem *zoom_in_menu_item_; |
88 | wxMenuItem *zoom_out_menu_item_; | 122 | wxMenuItem *zoom_out_menu_item_; |
123 | |||
124 | IconCache icons_; | ||
89 | }; | 125 | }; |
90 | 126 | ||
91 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ | 127 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ |
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 04b970c..ddb4df9 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp | |||
@@ -9,7 +9,6 @@ | |||
9 | #include "area_popup.h" | 9 | #include "area_popup.h" |
10 | #include "game_data.h" | 10 | #include "game_data.h" |
11 | #include "global.h" | 11 | #include "global.h" |
12 | #include "godot_variant.h" | ||
13 | #include "ipc_state.h" | 12 | #include "ipc_state.h" |
14 | #include "tracker_config.h" | 13 | #include "tracker_config.h" |
15 | #include "tracker_state.h" | 14 | #include "tracker_state.h" |
@@ -44,58 +43,46 @@ TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | |||
44 | areas_.push_back(area); | 43 | areas_.push_back(area); |
45 | } | 44 | } |
46 | 45 | ||
46 | Resize(); | ||
47 | Redraw(); | 47 | Redraw(); |
48 | 48 | ||
49 | Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); | 49 | Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); |
50 | Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); | 50 | Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); |
51 | } | 51 | } |
52 | 52 | ||
53 | void TrackerPanel::UpdateIndicators() { | 53 | void TrackerPanel::UpdateIndicators(bool reset) { |
54 | if (panels_mode_ && !savedata_path_) { | 54 | if (reset) { |
55 | solved_panels_ = IPC_GetSolvedPanels(); | 55 | for (AreaIndicator &area : areas_) { |
56 | } | 56 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
57 | 57 | ||
58 | for (AreaIndicator &area : areas_) { | 58 | if ((!AP_IsLocationVisible(map_area.classification) || |
59 | area.popup->UpdateIndicators(); | 59 | IsAreaPostgame(area.area_id)) && |
60 | } | 60 | !(map_area.hunt && |
61 | 61 | GetTrackerConfig().visible_panels == TrackerConfig::kHUNT_PANELS) && | |
62 | Redraw(); | 62 | !(map_area.has_single_panel && |
63 | } | 63 | GetTrackerConfig().visible_panels == TrackerConfig::kALL_PANELS) && |
64 | 64 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | |
65 | void TrackerPanel::SetPanelsMode() { panels_mode_ = true; } | 65 | area.active = false; |
66 | 66 | } else { | |
67 | void TrackerPanel::SetSavedataPath(std::string savedata_path) { | 67 | area.active = true; |
68 | if (!savedata_path_) { | 68 | } |
69 | wxButton *refresh_button = | ||
70 | new wxButton(this, wxID_ANY, "Refresh", {15, 15}); | ||
71 | refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this); | ||
72 | } | ||
73 | |||
74 | savedata_path_ = savedata_path; | ||
75 | panels_mode_ = true; | ||
76 | |||
77 | RefreshSavedata(); | ||
78 | } | ||
79 | 69 | ||
80 | void TrackerPanel::RefreshSavedata() { | 70 | area.popup->ResetIndicators(); |
81 | solved_panels_.clear(); | 71 | } |
82 | 72 | ||
83 | GodotVariant godot_variant = ParseGodotFile(*savedata_path_); | 73 | Resize(); |
84 | for (const GodotVariant &panel_node : godot_variant.AsArray()) { | 74 | } else { |
85 | const std::vector<GodotVariant> &fields = panel_node.AsArray(); | 75 | for (AreaIndicator &area : areas_) { |
86 | if (fields[1].AsBool()) { | 76 | area.popup->UpdateIndicators(); |
87 | const std::vector<std::string> &nodepath = fields[0].AsNodePath(); | ||
88 | std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]); | ||
89 | solved_panels_.insert(key); | ||
90 | } | 77 | } |
91 | } | 78 | } |
92 | 79 | ||
93 | UpdateIndicators(); | 80 | Redraw(); |
94 | Refresh(); | ||
95 | } | 81 | } |
96 | 82 | ||
97 | void TrackerPanel::OnPaint(wxPaintEvent &event) { | 83 | void TrackerPanel::OnPaint(wxPaintEvent &event) { |
98 | if (GetSize() != rendered_.GetSize()) { | 84 | if (GetSize() != rendered_.GetSize()) { |
85 | Resize(); | ||
99 | Redraw(); | 86 | Redraw(); |
100 | } | 87 | } |
101 | 88 | ||
@@ -103,10 +90,13 @@ void TrackerPanel::OnPaint(wxPaintEvent &event) { | |||
103 | dc.DrawBitmap(rendered_, 0, 0); | 90 | dc.DrawBitmap(rendered_, 0, 0); |
104 | 91 | ||
105 | std::optional<std::tuple<int, int>> player_position; | 92 | std::optional<std::tuple<int, int>> player_position; |
106 | if (IPC_IsConnected()) { | 93 | if (GetTrackerConfig().track_position) |
107 | player_position = IPC_GetPlayerPosition(); | 94 | { |
108 | } else { | 95 | if (IPC_IsConnected()) { |
109 | player_position = AP_GetPlayerPosition(); | 96 | player_position = IPC_GetPlayerPosition(); |
97 | } else { | ||
98 | player_position = AP_GetPlayerPosition(); | ||
99 | } | ||
110 | } | 100 | } |
111 | 101 | ||
112 | if (player_position.has_value()) { | 102 | if (player_position.has_value()) { |
@@ -142,12 +132,8 @@ void TrackerPanel::OnMouseMove(wxMouseEvent &event) { | |||
142 | event.Skip(); | 132 | event.Skip(); |
143 | } | 133 | } |
144 | 134 | ||
145 | void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) { | 135 | void TrackerPanel::Resize() { |
146 | RefreshSavedata(); | 136 | wxSize panel_size = GetClientSize(); |
147 | } | ||
148 | |||
149 | void TrackerPanel::Redraw() { | ||
150 | wxSize panel_size = GetSize(); | ||
151 | wxSize image_size = map_image_.GetSize(); | 137 | wxSize image_size = map_image_.GetSize(); |
152 | 138 | ||
153 | int final_x = 0; | 139 | int final_x = 0; |
@@ -166,7 +152,7 @@ void TrackerPanel::Redraw() { | |||
166 | final_x = (panel_size.GetWidth() - final_width) / 2; | 152 | final_x = (panel_size.GetWidth() - final_width) / 2; |
167 | } | 153 | } |
168 | 154 | ||
169 | rendered_ = wxBitmap( | 155 | scaled_map_ = wxBitmap( |
170 | map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) | 156 | map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) |
171 | .Size(panel_size, {final_x, final_y}, 0, 0, 0)); | 157 | .Size(panel_size, {final_x, final_y}, 0, 0, 0)); |
172 | 158 | ||
@@ -181,30 +167,61 @@ void TrackerPanel::Redraw() { | |||
181 | wxBitmap(player_image_.Scale(player_width > 0 ? player_width : 1, | 167 | wxBitmap(player_image_.Scale(player_width > 0 ? player_width : 1, |
182 | player_height > 0 ? player_height : 1)); | 168 | player_height > 0 ? player_height : 1)); |
183 | 169 | ||
170 | real_area_size_ = final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); | ||
171 | |||
172 | for (AreaIndicator &area : areas_) { | ||
173 | const MapArea &map_area = GD_GetMapArea(area.area_id); | ||
174 | |||
175 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | ||
176 | final_width / image_size.GetWidth(); | ||
177 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | ||
178 | final_width / image_size.GetWidth(); | ||
179 | |||
180 | area.real_x1 = real_area_x; | ||
181 | area.real_x2 = real_area_x + real_area_size_; | ||
182 | area.real_y1 = real_area_y; | ||
183 | area.real_y2 = real_area_y + real_area_size_; | ||
184 | |||
185 | int popup_x = | ||
186 | final_x + map_area.map_x * final_width / image_size.GetWidth(); | ||
187 | int popup_y = | ||
188 | final_y + map_area.map_y * final_width / image_size.GetWidth(); | ||
189 | |||
190 | area.popup->SetClientSize( | ||
191 | area.popup->GetFullWidth(), | ||
192 | std::min(panel_size.GetHeight(), area.popup->GetFullHeight())); | ||
193 | |||
194 | if (area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { | ||
195 | area.popup->SetSize(area.popup->GetSize().GetWidth(), | ||
196 | panel_size.GetHeight()); | ||
197 | } | ||
198 | |||
199 | if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { | ||
200 | popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); | ||
201 | } | ||
202 | if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { | ||
203 | popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); | ||
204 | } | ||
205 | area.popup->SetPosition({popup_x, popup_y}); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | void TrackerPanel::Redraw() { | ||
210 | rendered_ = scaled_map_; | ||
211 | |||
184 | wxMemoryDC dc; | 212 | wxMemoryDC dc; |
185 | dc.SelectObject(rendered_); | 213 | dc.SelectObject(rendered_); |
186 | 214 | ||
187 | int real_area_size = | ||
188 | final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); | ||
189 | int actual_border_size = | 215 | int actual_border_size = |
190 | real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; | 216 | real_area_size_ * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; |
191 | const wxPoint upper_left_triangle[] = { | 217 | const wxPoint upper_left_triangle[] = { |
192 | {0, 0}, {0, real_area_size}, {real_area_size, 0}}; | 218 | {0, 0}, {0, real_area_size_}, {real_area_size_, 0}}; |
193 | const wxPoint lower_right_triangle[] = {{0, real_area_size - 1}, | 219 | const wxPoint lower_right_triangle[] = {{0, real_area_size_ - 1}, |
194 | {real_area_size - 1, 0}, | 220 | {real_area_size_ - 1, 0}, |
195 | {real_area_size, real_area_size}}; | 221 | {real_area_size_, real_area_size_}}; |
196 | 222 | ||
197 | for (AreaIndicator &area : areas_) { | 223 | for (AreaIndicator &area : areas_) { |
198 | const MapArea &map_area = GD_GetMapArea(area.area_id); | 224 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
199 | if (panels_mode_) { | ||
200 | area.active = map_area.has_single_panel; | ||
201 | } else if (!AP_IsLocationVisible(map_area.classification) && | ||
202 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && | ||
203 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | ||
204 | area.active = false; | ||
205 | } else { | ||
206 | area.active = true; | ||
207 | } | ||
208 | 225 | ||
209 | if (!area.active) { | 226 | if (!area.active) { |
210 | continue; | 227 | continue; |
@@ -216,19 +233,15 @@ void TrackerPanel::Redraw() { | |||
216 | bool has_unchecked = false; | 233 | bool has_unchecked = false; |
217 | if (IsLocationWinCondition(section)) { | 234 | if (IsLocationWinCondition(section)) { |
218 | has_unchecked = !AP_HasReachedGoal(); | 235 | has_unchecked = !AP_HasReachedGoal(); |
219 | } else if (panels_mode_) { | 236 | } else if (AP_IsLocationVisible(section.classification) && |
220 | if (section.single_panel) { | 237 | !IsLocationPostgame(section.ap_location_id)) { |
221 | const Panel &panel = GD_GetPanel(*section.single_panel); | ||
222 | if (panel.non_counting) { | ||
223 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); | ||
224 | } else { | ||
225 | has_unchecked = !GetSolvedPanels().contains(panel.nodepath); | ||
226 | } | ||
227 | } | ||
228 | } else if (AP_IsLocationVisible(section.classification)) { | ||
229 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); | 238 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); |
230 | } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { | 239 | } else if ((section.hunt && GetTrackerConfig().visible_panels == |
231 | has_unchecked = !AP_HasCheckedHuntPanel(section.ap_location_id); | 240 | TrackerConfig::kHUNT_PANELS) || |
241 | (section.single_panel && GetTrackerConfig().visible_panels == | ||
242 | TrackerConfig::kALL_PANELS)) { | ||
243 | has_unchecked = | ||
244 | !AP_IsPanelSolved(GD_GetPanel(*section.single_panel).solve_index); | ||
232 | } | 245 | } |
233 | 246 | ||
234 | if (has_unchecked) { | 247 | if (has_unchecked) { |
@@ -240,8 +253,12 @@ void TrackerPanel::Redraw() { | |||
240 | } | 253 | } |
241 | } | 254 | } |
242 | 255 | ||
243 | if (AP_IsPaintingShuffle() && !panels_mode_) { | 256 | if (AP_IsPaintingShuffle()) { |
244 | for (int painting_id : map_area.paintings) { | 257 | for (int painting_id : map_area.paintings) { |
258 | if (IsPaintingPostgame(painting_id)) { | ||
259 | continue; | ||
260 | } | ||
261 | |||
245 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); | 262 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); |
246 | bool reachable = IsPaintingReachable(painting_id); | 263 | bool reachable = IsPaintingReachable(painting_id); |
247 | if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { | 264 | if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { |
@@ -254,10 +271,8 @@ void TrackerPanel::Redraw() { | |||
254 | } | 271 | } |
255 | } | 272 | } |
256 | 273 | ||
257 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | 274 | int real_area_x = area.real_x1; |
258 | final_width / image_size.GetWidth(); | 275 | int real_area_y = area.real_y1; |
259 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | ||
260 | final_width / image_size.GetWidth(); | ||
261 | 276 | ||
262 | if (has_reachable_unchecked && has_unreachable_unchecked && | 277 | if (has_reachable_unchecked && has_unreachable_unchecked && |
263 | GetTrackerConfig().hybrid_areas) { | 278 | GetTrackerConfig().hybrid_areas) { |
@@ -271,7 +286,7 @@ void TrackerPanel::Redraw() { | |||
271 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); | 286 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); |
272 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | 287 | dc.SetBrush(*wxTRANSPARENT_BRUSH); |
273 | dc.DrawRectangle({real_area_x, real_area_y}, | 288 | dc.DrawRectangle({real_area_x, real_area_y}, |
274 | {real_area_size, real_area_size}); | 289 | {real_area_size_, real_area_size_}); |
275 | 290 | ||
276 | } else { | 291 | } else { |
277 | const wxBrush *brush_color = wxGREY_BRUSH; | 292 | const wxBrush *brush_color = wxGREY_BRUSH; |
@@ -286,30 +301,7 @@ void TrackerPanel::Redraw() { | |||
286 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); | 301 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); |
287 | dc.SetBrush(*brush_color); | 302 | dc.SetBrush(*brush_color); |
288 | dc.DrawRectangle({real_area_x, real_area_y}, | 303 | dc.DrawRectangle({real_area_x, real_area_y}, |
289 | {real_area_size, real_area_size}); | 304 | {real_area_size_, real_area_size_}); |
290 | } | 305 | } |
291 | |||
292 | area.real_x1 = real_area_x; | ||
293 | area.real_x2 = real_area_x + real_area_size; | ||
294 | area.real_y1 = real_area_y; | ||
295 | area.real_y2 = real_area_y + real_area_size; | ||
296 | |||
297 | int popup_x = | ||
298 | final_x + map_area.map_x * final_width / image_size.GetWidth(); | ||
299 | int popup_y = | ||
300 | final_y + map_area.map_y * final_width / image_size.GetWidth(); | ||
301 | |||
302 | area.popup->SetClientSize( | ||
303 | area.popup->GetVirtualSize().GetWidth(), | ||
304 | std::min(panel_size.GetHeight(), | ||
305 | area.popup->GetVirtualSize().GetHeight())); | ||
306 | |||
307 | if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { | ||
308 | popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); | ||
309 | } | ||
310 | if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { | ||
311 | popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); | ||
312 | } | ||
313 | area.popup->SetPosition({popup_x, popup_y}); | ||
314 | } | 306 | } |
315 | } | 307 | } |
diff --git a/src/tracker_panel.h b/src/tracker_panel.h index 822d181..6825843 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h | |||
@@ -17,17 +17,7 @@ class TrackerPanel : public wxPanel { | |||
17 | public: | 17 | public: |
18 | TrackerPanel(wxWindow *parent); | 18 | TrackerPanel(wxWindow *parent); |
19 | 19 | ||
20 | void UpdateIndicators(); | 20 | void UpdateIndicators(bool reset); |
21 | |||
22 | void SetPanelsMode(); | ||
23 | |||
24 | void SetSavedataPath(std::string savedata_path); | ||
25 | |||
26 | bool IsPanelsMode() const { return panels_mode_; } | ||
27 | |||
28 | const std::set<std::string> &GetSolvedPanels() const { | ||
29 | return solved_panels_; | ||
30 | } | ||
31 | 21 | ||
32 | private: | 22 | private: |
33 | struct AreaIndicator { | 23 | struct AreaIndicator { |
@@ -42,14 +32,13 @@ class TrackerPanel : public wxPanel { | |||
42 | 32 | ||
43 | void OnPaint(wxPaintEvent &event); | 33 | void OnPaint(wxPaintEvent &event); |
44 | void OnMouseMove(wxMouseEvent &event); | 34 | void OnMouseMove(wxMouseEvent &event); |
45 | void OnRefreshSavedata(wxCommandEvent &event); | ||
46 | 35 | ||
36 | void Resize(); | ||
47 | void Redraw(); | 37 | void Redraw(); |
48 | 38 | ||
49 | void RefreshSavedata(); | ||
50 | |||
51 | wxImage map_image_; | 39 | wxImage map_image_; |
52 | wxImage player_image_; | 40 | wxImage player_image_; |
41 | wxBitmap scaled_map_; | ||
53 | wxBitmap rendered_; | 42 | wxBitmap rendered_; |
54 | wxBitmap scaled_player_; | 43 | wxBitmap scaled_player_; |
55 | 44 | ||
@@ -57,12 +46,9 @@ class TrackerPanel : public wxPanel { | |||
57 | int offset_y_ = 0; | 46 | int offset_y_ = 0; |
58 | double scale_x_ = 0; | 47 | double scale_x_ = 0; |
59 | double scale_y_ = 0; | 48 | double scale_y_ = 0; |
49 | int real_area_size_ = 0; | ||
60 | 50 | ||
61 | std::vector<AreaIndicator> areas_; | 51 | std::vector<AreaIndicator> areas_; |
62 | |||
63 | bool panels_mode_ = false; | ||
64 | std::optional<std::string> savedata_path_; | ||
65 | std::set<std::string> solved_panels_; | ||
66 | }; | 52 | }; |
67 | 53 | ||
68 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ | 54 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ |
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index eee43e4..bf2725a 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -12,6 +12,7 @@ | |||
12 | 12 | ||
13 | #include "ap_state.h" | 13 | #include "ap_state.h" |
14 | #include "game_data.h" | 14 | #include "game_data.h" |
15 | #include "global.h" | ||
15 | #include "logger.h" | 16 | #include "logger.h" |
16 | 17 | ||
17 | namespace { | 18 | namespace { |
@@ -25,6 +26,7 @@ struct Requirements { | |||
25 | std::set<int> rooms; // maybe | 26 | std::set<int> rooms; // maybe |
26 | bool mastery = false; // maybe | 27 | bool mastery = false; // maybe |
27 | bool panel_hunt = false; // maybe | 28 | bool panel_hunt = false; // maybe |
29 | bool postgame = false; | ||
28 | 30 | ||
29 | void Merge(const Requirements& rhs) { | 31 | void Merge(const Requirements& rhs) { |
30 | if (rhs.disabled) { | 32 | if (rhs.disabled) { |
@@ -45,6 +47,7 @@ struct Requirements { | |||
45 | } | 47 | } |
46 | mastery = mastery || rhs.mastery; | 48 | mastery = mastery || rhs.mastery; |
47 | panel_hunt = panel_hunt || rhs.panel_hunt; | 49 | panel_hunt = panel_hunt || rhs.panel_hunt; |
50 | postgame = postgame || rhs.postgame; | ||
48 | } | 51 | } |
49 | }; | 52 | }; |
50 | 53 | ||
@@ -83,8 +86,6 @@ class RequirementCalculator { | |||
83 | break; | 86 | break; |
84 | } | 87 | } |
85 | } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { | 88 | } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { |
86 | requirements.rooms.insert(door_obj.room); | ||
87 | |||
88 | for (int panel_id : door_obj.panels) { | 89 | for (int panel_id : door_obj.panels) { |
89 | const Requirements& panel_reqs = GetPanel(panel_id); | 90 | const Requirements& panel_reqs = GetPanel(panel_id); |
90 | requirements.Merge(panel_reqs); | 91 | requirements.Merge(panel_reqs); |
@@ -148,6 +149,10 @@ class RequirementCalculator { | |||
148 | } | 149 | } |
149 | } | 150 | } |
150 | 151 | ||
152 | if (panel_obj.location_name == GetWinCondition()) { | ||
153 | requirements.postgame = true; | ||
154 | } | ||
155 | |||
151 | panels_[panel_id] = requirements; | 156 | panels_[panel_id] = requirements; |
152 | } | 157 | } |
153 | 158 | ||
@@ -162,11 +167,17 @@ class RequirementCalculator { | |||
162 | struct TrackerState { | 167 | struct TrackerState { |
163 | std::map<int, bool> reachability; | 168 | std::map<int, bool> reachability; |
164 | std::set<int> reachable_doors; | 169 | std::set<int> reachable_doors; |
170 | std::set<int> solveable_panels; | ||
165 | std::set<int> reachable_paintings; | 171 | std::set<int> reachable_paintings; |
166 | std::mutex reachability_mutex; | 172 | std::mutex reachability_mutex; |
167 | RequirementCalculator requirements; | 173 | RequirementCalculator requirements; |
168 | std::map<int, std::map<std::string, bool>> door_reports; | 174 | std::map<int, std::map<std::string, bool>> door_reports; |
169 | bool pilgrimage_doable = false; | 175 | bool pilgrimage_doable = false; |
176 | |||
177 | // If these are empty, it actually means everything is non-postgame. | ||
178 | std::set<int> non_postgame_areas; | ||
179 | std::set<int> non_postgame_locations; | ||
180 | std::set<int> non_postgame_paintings; | ||
170 | }; | 181 | }; |
171 | 182 | ||
172 | enum Decision { kYes, kNo, kMaybe }; | 183 | enum Decision { kYes, kNo, kMaybe }; |
@@ -181,6 +192,11 @@ class StateCalculator; | |||
181 | struct StateCalculatorOptions { | 192 | struct StateCalculatorOptions { |
182 | int start; | 193 | int start; |
183 | bool pilgrimage = false; | 194 | bool pilgrimage = false; |
195 | |||
196 | // Treats all items as collected and all paintings as checked, but postgame | ||
197 | // areas cannot be reached. | ||
198 | bool postgame_detection = false; | ||
199 | |||
184 | StateCalculator* parent = nullptr; | 200 | StateCalculator* parent = nullptr; |
185 | }; | 201 | }; |
186 | 202 | ||
@@ -191,6 +207,16 @@ class StateCalculator { | |||
191 | explicit StateCalculator(StateCalculatorOptions options) | 207 | explicit StateCalculator(StateCalculatorOptions options) |
192 | : options_(options) {} | 208 | : options_(options) {} |
193 | 209 | ||
210 | void PreloadPanels(const std::set<int>& panels) { | ||
211 | solveable_panels_ = panels; | ||
212 | } | ||
213 | |||
214 | void PreloadDoors(const std::set<int>& doors) { | ||
215 | for (int door_id : doors) { | ||
216 | door_decisions_[door_id] = kYes; | ||
217 | } | ||
218 | } | ||
219 | |||
194 | void Calculate() { | 220 | void Calculate() { |
195 | painting_mapping_ = AP_GetPaintingMapping(); | 221 | painting_mapping_ = AP_GetPaintingMapping(); |
196 | checked_paintings_ = AP_GetCheckedPaintings(); | 222 | checked_paintings_ = AP_GetCheckedPaintings(); |
@@ -236,7 +262,8 @@ class StateCalculator { | |||
236 | 262 | ||
237 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); | 263 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); |
238 | if (painting_mapping_.count(cur_painting.internal_id) && | 264 | if (painting_mapping_.count(cur_painting.internal_id) && |
239 | checked_paintings_.count(cur_painting.internal_id)) { | 265 | (checked_paintings_.count(cur_painting.internal_id) || |
266 | options_.postgame_detection)) { | ||
240 | Exit painting_exit; | 267 | Exit painting_exit; |
241 | PaintingExit target_painting = | 268 | PaintingExit target_painting = |
242 | GD_GetPaintingExit(GD_GetPaintingByName( | 269 | GD_GetPaintingExit(GD_GetPaintingByName( |
@@ -360,6 +387,10 @@ class StateCalculator { | |||
360 | // evaluated. | 387 | // evaluated. |
361 | for (const Door& door : GD_GetDoors()) { | 388 | for (const Door& door : GD_GetDoors()) { |
362 | int discard = IsDoorReachable(door.id); | 389 | int discard = IsDoorReachable(door.id); |
390 | |||
391 | door_report_[door.id] = {}; | ||
392 | discard = AreRequirementsSatisfied( | ||
393 | GetState().requirements.GetDoor(door.id), &door_report_[door.id]); | ||
363 | } | 394 | } |
364 | } | 395 | } |
365 | 396 | ||
@@ -417,43 +448,48 @@ class StateCalculator { | |||
417 | return kNo; | 448 | return kNo; |
418 | } | 449 | } |
419 | 450 | ||
451 | if (reqs.postgame && options_.postgame_detection) { | ||
452 | return kNo; | ||
453 | } | ||
454 | |||
420 | Decision final_decision = kYes; | 455 | Decision final_decision = kYes; |
421 | 456 | ||
422 | for (int door_id : reqs.doors) { | 457 | if (!options_.postgame_detection) { |
423 | const Door& door_obj = GD_GetDoor(door_id); | 458 | for (int door_id : reqs.doors) { |
424 | Decision decision = IsNonGroupedDoorReachable(door_obj); | 459 | const Door& door_obj = GD_GetDoor(door_id); |
460 | Decision decision = IsNonGroupedDoorReachable(door_obj); | ||
425 | 461 | ||
426 | if (report) { | 462 | if (report) { |
427 | (*report)[door_obj.item_name] = (decision == kYes); | 463 | (*report)[door_obj.item_name] = (decision == kYes); |
428 | } | 464 | } |
429 | 465 | ||
430 | if (decision != kYes) { | 466 | if (decision != kYes) { |
431 | final_decision = decision; | 467 | final_decision = decision; |
468 | } | ||
432 | } | 469 | } |
433 | } | ||
434 | 470 | ||
435 | for (int panel_door_id : reqs.panel_doors) { | 471 | for (int panel_door_id : reqs.panel_doors) { |
436 | const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id); | 472 | const PanelDoor& panel_door_obj = GD_GetPanelDoor(panel_door_id); |
437 | Decision decision = IsNonGroupedDoorReachable(panel_door_obj); | 473 | Decision decision = IsNonGroupedDoorReachable(panel_door_obj); |
438 | 474 | ||
439 | if (report) { | 475 | if (report) { |
440 | (*report)[AP_GetItemName(panel_door_obj.ap_item_id)] = | 476 | (*report)[panel_door_obj.item_name] = (decision == kYes); |
441 | (decision == kYes); | 477 | } |
442 | } | ||
443 | 478 | ||
444 | if (decision != kYes) { | 479 | if (decision != kYes) { |
445 | final_decision = decision; | 480 | final_decision = decision; |
481 | } | ||
446 | } | 482 | } |
447 | } | ||
448 | 483 | ||
449 | for (int item_id : reqs.items) { | 484 | for (int item_id : reqs.items) { |
450 | bool has_item = AP_HasItem(item_id); | 485 | bool has_item = AP_HasItem(item_id); |
451 | if (report) { | 486 | if (report) { |
452 | (*report)[AP_GetItemName(item_id)] = has_item; | 487 | (*report)[GD_GetItemName(item_id)] = has_item; |
453 | } | 488 | } |
454 | 489 | ||
455 | if (!has_item) { | 490 | if (!has_item) { |
456 | final_decision = kNo; | 491 | final_decision = kNo; |
492 | } | ||
457 | } | 493 | } |
458 | } | 494 | } |
459 | 495 | ||
@@ -522,14 +558,7 @@ class StateCalculator { | |||
522 | } | 558 | } |
523 | 559 | ||
524 | Decision IsDoorReachable_Helper(int door_id) { | 560 | Decision IsDoorReachable_Helper(int door_id) { |
525 | if (door_report_.count(door_id)) { | 561 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id)); |
526 | door_report_[door_id].clear(); | ||
527 | } else { | ||
528 | door_report_[door_id] = {}; | ||
529 | } | ||
530 | |||
531 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), | ||
532 | &door_report_[door_id]); | ||
533 | } | 562 | } |
534 | 563 | ||
535 | Decision IsDoorReachable(int door_id) { | 564 | Decision IsDoorReachable(int door_id) { |
@@ -661,18 +690,85 @@ class StateCalculator { | |||
661 | } // namespace | 690 | } // namespace |
662 | 691 | ||
663 | void ResetReachabilityRequirements() { | 692 | void ResetReachabilityRequirements() { |
693 | TrackerLog("Resetting tracker state..."); | ||
694 | |||
664 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 695 | std::lock_guard reachability_guard(GetState().reachability_mutex); |
665 | GetState().requirements.Reset(); | 696 | GetState().requirements.Reset(); |
697 | GetState().reachable_doors.clear(); | ||
698 | GetState().solveable_panels.clear(); | ||
699 | |||
700 | if (AP_IsPostgameShuffle()) { | ||
701 | GetState().non_postgame_areas.clear(); | ||
702 | GetState().non_postgame_locations.clear(); | ||
703 | GetState().non_postgame_paintings.clear(); | ||
704 | } else { | ||
705 | StateCalculator postgame_calculator( | ||
706 | {.start = GD_GetRoomByName("Menu"), .postgame_detection = true}); | ||
707 | postgame_calculator.Calculate(); | ||
708 | |||
709 | std::set<int>& non_postgame_areas = GetState().non_postgame_areas; | ||
710 | non_postgame_areas.clear(); | ||
711 | |||
712 | std::set<int>& non_postgame_locations = GetState().non_postgame_locations; | ||
713 | non_postgame_locations.clear(); | ||
714 | |||
715 | const std::set<int>& reachable_rooms = | ||
716 | postgame_calculator.GetReachableRooms(); | ||
717 | const std::set<int>& solveable_panels = | ||
718 | postgame_calculator.GetSolveablePanels(); | ||
719 | |||
720 | for (const MapArea& map_area : GD_GetMapAreas()) { | ||
721 | bool area_reachable = false; | ||
722 | |||
723 | for (const Location& location_section : map_area.locations) { | ||
724 | bool reachable = reachable_rooms.count(location_section.room); | ||
725 | if (reachable) { | ||
726 | for (int panel_id : location_section.panels) { | ||
727 | reachable &= (solveable_panels.count(panel_id) == 1); | ||
728 | } | ||
729 | } | ||
730 | |||
731 | if (!reachable && IsLocationWinCondition(location_section)) { | ||
732 | reachable = true; | ||
733 | } | ||
734 | |||
735 | if (reachable) { | ||
736 | non_postgame_locations.insert(location_section.ap_location_id); | ||
737 | area_reachable = true; | ||
738 | } | ||
739 | } | ||
740 | |||
741 | for (int painting_id : map_area.paintings) { | ||
742 | if (postgame_calculator.GetReachablePaintings().count(painting_id)) { | ||
743 | area_reachable = true; | ||
744 | } | ||
745 | } | ||
746 | |||
747 | if (area_reachable) { | ||
748 | non_postgame_areas.insert(map_area.id); | ||
749 | } | ||
750 | } | ||
751 | |||
752 | GetState().non_postgame_paintings = | ||
753 | postgame_calculator.GetReachablePaintings(); | ||
754 | } | ||
666 | } | 755 | } |
667 | 756 | ||
668 | void RecalculateReachability() { | 757 | void RecalculateReachability() { |
758 | TrackerLog("Calculating reachability..."); | ||
759 | |||
669 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 760 | std::lock_guard reachability_guard(GetState().reachability_mutex); |
670 | 761 | ||
762 | // Receiving items and checking paintings should never remove access to doors | ||
763 | // or panels, so we can preload any doors and panels we already know are | ||
764 | // accessible from previous runs, in order to reduce the work. | ||
671 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); | 765 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); |
766 | state_calculator.PreloadDoors(GetState().reachable_doors); | ||
767 | state_calculator.PreloadPanels(GetState().solveable_panels); | ||
672 | state_calculator.Calculate(); | 768 | state_calculator.Calculate(); |
673 | 769 | ||
674 | const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms(); | 770 | const std::set<int>& reachable_rooms = state_calculator.GetReachableRooms(); |
675 | const std::set<int>& solveable_panels = state_calculator.GetSolveablePanels(); | 771 | std::set<int> solveable_panels = state_calculator.GetSolveablePanels(); |
676 | 772 | ||
677 | std::map<int, bool> new_reachability; | 773 | std::map<int, bool> new_reachability; |
678 | for (const MapArea& map_area : GD_GetMapAreas()) { | 774 | for (const MapArea& map_area : GD_GetMapAreas()) { |
@@ -703,6 +799,7 @@ void RecalculateReachability() { | |||
703 | 799 | ||
704 | std::swap(GetState().reachability, new_reachability); | 800 | std::swap(GetState().reachability, new_reachability); |
705 | std::swap(GetState().reachable_doors, new_reachable_doors); | 801 | std::swap(GetState().reachable_doors, new_reachable_doors); |
802 | std::swap(GetState().solveable_panels, solveable_panels); | ||
706 | std::swap(GetState().reachable_paintings, reachable_paintings); | 803 | std::swap(GetState().reachable_paintings, reachable_paintings); |
707 | std::swap(GetState().door_reports, door_reports); | 804 | std::swap(GetState().door_reports, door_reports); |
708 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); | 805 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); |
@@ -741,3 +838,33 @@ bool IsPilgrimageDoable() { | |||
741 | 838 | ||
742 | return GetState().pilgrimage_doable; | 839 | return GetState().pilgrimage_doable; |
743 | } | 840 | } |
841 | |||
842 | bool IsAreaPostgame(int area_id) { | ||
843 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
844 | |||
845 | if (GetState().non_postgame_areas.empty()) { | ||
846 | return false; | ||
847 | } else { | ||
848 | return !GetState().non_postgame_areas.count(area_id); | ||
849 | } | ||
850 | } | ||
851 | |||
852 | bool IsLocationPostgame(int location_id) { | ||
853 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
854 | |||
855 | if (GetState().non_postgame_locations.empty()) { | ||
856 | return false; | ||
857 | } else { | ||
858 | return !GetState().non_postgame_locations.count(location_id); | ||
859 | } | ||
860 | } | ||
861 | |||
862 | bool IsPaintingPostgame(int painting_id) { | ||
863 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
864 | |||
865 | if (GetState().non_postgame_paintings.empty()) { | ||
866 | return false; | ||
867 | } else { | ||
868 | return !GetState().non_postgame_paintings.count(painting_id); | ||
869 | } | ||
870 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h index a8f155d..8f1002f 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h | |||
@@ -18,4 +18,10 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id); | |||
18 | 18 | ||
19 | bool IsPilgrimageDoable(); | 19 | bool IsPilgrimageDoable(); |
20 | 20 | ||
21 | bool IsAreaPostgame(int area_id); | ||
22 | |||
23 | bool IsLocationPostgame(int location_id); | ||
24 | |||
25 | bool IsPaintingPostgame(int painting_id); | ||
26 | |||
21 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | 27 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ |
diff --git a/src/updater.cpp b/src/updater.cpp new file mode 100644 index 0000000..2b05daf --- /dev/null +++ b/src/updater.cpp | |||
@@ -0,0 +1,309 @@ | |||
1 | #include "updater.h" | ||
2 | |||
3 | #include <fmt/core.h> | ||
4 | #include <openssl/evp.h> | ||
5 | #include <openssl/sha.h> | ||
6 | #include <wx/evtloop.h> | ||
7 | #include <wx/progdlg.h> | ||
8 | #include <wx/webrequest.h> | ||
9 | #include <wx/wfstream.h> | ||
10 | #include <wx/zipstrm.h> | ||
11 | #include <yaml-cpp/yaml.h> | ||
12 | |||
13 | #include <cstdio> | ||
14 | #include <deque> | ||
15 | #include <filesystem> | ||
16 | #include <fstream> | ||
17 | |||
18 | #include "global.h" | ||
19 | #include "logger.h" | ||
20 | #include "version.h" | ||
21 | |||
22 | constexpr const char* kVersionFileUrl = | ||
23 | "https://code.fourisland.com/lingo-ap-tracker/plain/VERSION.yaml"; | ||
24 | constexpr const char* kChangelogUrl = | ||
25 | "https://code.fourisland.com/lingo-ap-tracker/about/CHANGELOG.md"; | ||
26 | |||
27 | namespace { | ||
28 | |||
29 | std::string CalculateStringSha256(const wxString& data) { | ||
30 | unsigned char hash[SHA256_DIGEST_LENGTH]; | ||
31 | EVP_MD_CTX* sha256 = EVP_MD_CTX_new(); | ||
32 | EVP_DigestInit(sha256, EVP_sha256()); | ||
33 | EVP_DigestUpdate(sha256, data.c_str(), data.length()); | ||
34 | EVP_DigestFinal_ex(sha256, hash, nullptr); | ||
35 | EVP_MD_CTX_free(sha256); | ||
36 | |||
37 | char output[65] = {0}; | ||
38 | for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { | ||
39 | snprintf(output + (i * 2), 3, "%02x", hash[i]); | ||
40 | } | ||
41 | |||
42 | return std::string(output); | ||
43 | } | ||
44 | |||
45 | } // namespace | ||
46 | |||
47 | Updater::Updater(wxFrame* parent) : parent_(parent) { | ||
48 | Bind(wxEVT_WEBREQUEST_STATE, &Updater::OnWebRequestState, this); | ||
49 | } | ||
50 | |||
51 | void Updater::Cleanup() { | ||
52 | std::filesystem::path oldDir = GetExecutableDirectory() / "old"; | ||
53 | if (std::filesystem::is_directory(oldDir)) { | ||
54 | std::filesystem::remove_all(oldDir); | ||
55 | } | ||
56 | } | ||
57 | |||
58 | void Updater::CheckForUpdates(bool invisible) { | ||
59 | wxWebRequest versionRequest = | ||
60 | wxWebSession::GetDefault().CreateRequest(this, kVersionFileUrl); | ||
61 | |||
62 | if (invisible) { | ||
63 | update_state_ = UpdateState::GetVersionInvisible; | ||
64 | |||
65 | versionRequest.Start(); | ||
66 | } else { | ||
67 | update_state_ = UpdateState::GetVersionManual; | ||
68 | |||
69 | if (DownloadWithProgress(versionRequest)) { | ||
70 | if (versionRequest.GetState() == wxWebRequest::State_Failed) { | ||
71 | wxMessageBox("Could not check for updates.", "Error", | ||
72 | wxOK | wxICON_ERROR); | ||
73 | } else if (versionRequest.GetState() == wxWebRequest::State_Completed) { | ||
74 | ProcessVersionFile( | ||
75 | versionRequest.GetResponse().AsString().utf8_string()); | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
81 | void Updater::OnWebRequestState(wxWebRequestEvent& evt) { | ||
82 | if (update_state_ == UpdateState::GetVersionInvisible) { | ||
83 | if (evt.GetState() == wxWebRequest::State_Completed) { | ||
84 | ProcessVersionFile(evt.GetResponse().AsString().utf8_string()); | ||
85 | } else if (evt.GetState() == wxWebRequest::State_Failed) { | ||
86 | parent_->SetStatusText("Could not check for updates."); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | void Updater::ProcessVersionFile(std::string data) { | ||
92 | try { | ||
93 | YAML::Node versionInfo = YAML::Load(data); | ||
94 | Version latestVersion(versionInfo["version"].as<std::string>()); | ||
95 | |||
96 | if (kTrackerVersion < latestVersion) { | ||
97 | if (versionInfo["packages"]) { | ||
98 | std::string platformIdentifier; | ||
99 | |||
100 | if (wxPlatformInfo::Get().GetOperatingSystemId() == wxOS_WINDOWS_NT) { | ||
101 | platformIdentifier = "win64"; | ||
102 | } | ||
103 | |||
104 | if (!platformIdentifier.empty() && | ||
105 | versionInfo["packages"][platformIdentifier]) { | ||
106 | wxMessageDialog dialog( | ||
107 | nullptr, | ||
108 | fmt::format("There is a newer version of Lingo AP Tracker " | ||
109 | "available. You have {}, and the latest version is " | ||
110 | "{}. Would you like to update?", | ||
111 | kTrackerVersion.ToString(), latestVersion.ToString()), | ||
112 | "Update available", wxYES_NO | wxCANCEL); | ||
113 | dialog.SetYesNoLabels("Install update", "Open changelog"); | ||
114 | |||
115 | int dlgResult = dialog.ShowModal(); | ||
116 | if (dlgResult == wxID_YES) { | ||
117 | const YAML::Node& packageInfo = | ||
118 | versionInfo["packages"][platformIdentifier]; | ||
119 | std::string packageUrl = packageInfo["url"].as<std::string>(); | ||
120 | std::string packageChecksum = | ||
121 | packageInfo["checksum"].as<std::string>(); | ||
122 | |||
123 | std::vector<std::filesystem::path> packageFiles; | ||
124 | if (packageInfo["files"]) { | ||
125 | for (const YAML::Node& filename : packageInfo["files"]) { | ||
126 | packageFiles.push_back(filename.as<std::string>()); | ||
127 | } | ||
128 | } | ||
129 | |||
130 | std::vector<std::filesystem::path> deletedFiles; | ||
131 | if (packageInfo["deleted_files"]) { | ||
132 | for (const YAML::Node& filename : packageInfo["deleted_files"]) { | ||
133 | deletedFiles.push_back(filename.as<std::string>()); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | InstallUpdate(packageUrl, packageChecksum, packageFiles, | ||
138 | deletedFiles); | ||
139 | } else if (dlgResult == wxID_NO) { | ||
140 | wxLaunchDefaultBrowser(kChangelogUrl); | ||
141 | } | ||
142 | |||
143 | return; | ||
144 | } | ||
145 | } | ||
146 | |||
147 | if (wxMessageBox( | ||
148 | fmt::format("There is a newer version of Lingo AP Tracker " | ||
149 | "available. You have {}, and the latest version is " | ||
150 | "{}. Would you like to update?", | ||
151 | kTrackerVersion.ToString(), latestVersion.ToString()), | ||
152 | "Update available", wxYES_NO) == wxYES) { | ||
153 | wxLaunchDefaultBrowser(kChangelogUrl); | ||
154 | } | ||
155 | } else if (update_state_ == UpdateState::GetVersionManual) { | ||
156 | wxMessageBox("Lingo AP Tracker is up to date!", "Lingo AP Tracker", wxOK); | ||
157 | } | ||
158 | } catch (const std::exception& ex) { | ||
159 | wxMessageBox("Could not check for updates.", "Error", wxOK | wxICON_ERROR); | ||
160 | } | ||
161 | } | ||
162 | |||
163 | void Updater::InstallUpdate(std::string url, std::string checksum, | ||
164 | std::vector<std::filesystem::path> files, | ||
165 | std::vector<std::filesystem::path> deletedFiles) { | ||
166 | update_state_ = UpdateState::GetPackage; | ||
167 | |||
168 | wxWebRequest packageRequest = | ||
169 | wxWebSession::GetDefault().CreateRequest(this, url); | ||
170 | |||
171 | if (!DownloadWithProgress(packageRequest)) { | ||
172 | return; | ||
173 | } | ||
174 | |||
175 | bool download_issue = false; | ||
176 | |||
177 | wxFileName package_path; | ||
178 | package_path.AssignTempFileName(""); | ||
179 | |||
180 | if (!package_path.IsOk()) { | ||
181 | download_issue = true; | ||
182 | } else { | ||
183 | wxFileOutputStream writeOut(package_path.GetFullPath()); | ||
184 | wxString fileData = packageRequest.GetResponse().AsString(); | ||
185 | writeOut.WriteAll(fileData.c_str(), fileData.length()); | ||
186 | |||
187 | std::string downloadedChecksum = CalculateStringSha256(fileData); | ||
188 | if (downloadedChecksum != checksum) { | ||
189 | download_issue = true; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | if (download_issue) { | ||
194 | if (wxMessageBox("There was an issue downloading the update. Would you " | ||
195 | "like to manually download it instead?", | ||
196 | "Error", wxYES_NO | wxICON_ERROR) == wxID_YES) { | ||
197 | wxLaunchDefaultBrowser(kChangelogUrl); | ||
198 | } | ||
199 | return; | ||
200 | } | ||
201 | |||
202 | std::filesystem::path newArea = GetExecutableDirectory(); | ||
203 | std::filesystem::path oldArea = newArea / "old"; | ||
204 | std::set<std::filesystem::path> folders; | ||
205 | std::set<std::filesystem::path> filesToMove; | ||
206 | for (const std::filesystem::path& existingFile : files) { | ||
207 | std::filesystem::path movedPath = oldArea / existingFile; | ||
208 | std::filesystem::path movedDir = movedPath; | ||
209 | movedDir.remove_filename(); | ||
210 | folders.insert(movedDir); | ||
211 | filesToMove.insert(existingFile); | ||
212 | } | ||
213 | for (const std::filesystem::path& existingFile : deletedFiles) { | ||
214 | std::filesystem::path movedPath = oldArea / existingFile; | ||
215 | std::filesystem::path movedDir = movedPath; | ||
216 | movedDir.remove_filename(); | ||
217 | folders.insert(movedDir); | ||
218 | } | ||
219 | |||
220 | for (const std::filesystem::path& newFolder : folders) { | ||
221 | TrackerLog(fmt::format("Creating directory {}", newFolder.string())); | ||
222 | |||
223 | std::filesystem::create_directories(newFolder); | ||
224 | } | ||
225 | |||
226 | for (const std::filesystem::path& existingFile : files) { | ||
227 | std::filesystem::path existingPath = newArea / existingFile; | ||
228 | |||
229 | if (std::filesystem::is_regular_file(existingPath)) { | ||
230 | std::filesystem::path movedPath = oldArea / existingFile; | ||
231 | |||
232 | TrackerLog(fmt::format("Moving {} -> {}", existingPath.string(), | ||
233 | movedPath.string())); | ||
234 | |||
235 | std::filesystem::rename(existingPath, movedPath); | ||
236 | } | ||
237 | } | ||
238 | for (const std::filesystem::path& existingFile : deletedFiles) { | ||
239 | std::filesystem::path existingPath = newArea / existingFile; | ||
240 | |||
241 | if (std::filesystem::is_regular_file(existingPath)) { | ||
242 | std::filesystem::path movedPath = oldArea / existingFile; | ||
243 | |||
244 | TrackerLog(fmt::format("Moving {} -> {}", existingPath.string(), | ||
245 | movedPath.string())); | ||
246 | |||
247 | std::filesystem::rename(existingPath, movedPath); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | wxFileInputStream fileInputStream(package_path.GetFullPath()); | ||
252 | wxZipInputStream zipStream(fileInputStream); | ||
253 | std::unique_ptr<wxZipEntry> zipEntry; | ||
254 | while ((zipEntry = std::unique_ptr<wxZipEntry>(zipStream.GetNextEntry())) != | ||
255 | nullptr) { | ||
256 | if (zipEntry->IsDir()) { | ||
257 | continue; | ||
258 | } | ||
259 | |||
260 | std::filesystem::path archivePath = zipEntry->GetName().utf8_string(); | ||
261 | |||
262 | TrackerLog(fmt::format("Found {} in archive", archivePath.string())); | ||
263 | |||
264 | // Cut off the root folder name | ||
265 | std::filesystem::path subPath; | ||
266 | for (auto it = std::next(archivePath.begin()); it != archivePath.end(); | ||
267 | it++) { | ||
268 | subPath /= *it; | ||
269 | } | ||
270 | |||
271 | std::filesystem::path pastePath = newArea / subPath; | ||
272 | |||
273 | wxFileOutputStream fileOutput(pastePath.string()); | ||
274 | zipStream.Read(fileOutput); | ||
275 | } | ||
276 | |||
277 | if (wxMessageBox( | ||
278 | "Update installed! The tracker must be restarted for the changes to take " | ||
279 | "effect. Do you want to close the tracker?", | ||
280 | "Update installed", wxYES_NO) == wxYES) { | ||
281 | wxExit(); | ||
282 | } | ||
283 | } | ||
284 | |||
285 | bool Updater::DownloadWithProgress(wxWebRequest& request) { | ||
286 | request.Start(); | ||
287 | |||
288 | wxProgressDialog dialog("Checking for updates...", "Checking for updates...", | ||
289 | 100, nullptr, | ||
290 | wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT | | ||
291 | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME); | ||
292 | while (request.GetState() != wxWebRequest::State_Completed && | ||
293 | request.GetState() != wxWebRequest::State_Failed) { | ||
294 | if (request.GetBytesExpectedToReceive() == -1) { | ||
295 | if (!dialog.Pulse()) { | ||
296 | request.Cancel(); | ||
297 | return false; | ||
298 | } | ||
299 | } else { | ||
300 | dialog.SetRange(request.GetBytesExpectedToReceive()); | ||
301 | if (!dialog.Update(request.GetBytesReceived())) { | ||
302 | request.Cancel(); | ||
303 | return false; | ||
304 | } | ||
305 | } | ||
306 | } | ||
307 | |||
308 | return true; | ||
309 | } | ||
diff --git a/src/updater.h b/src/updater.h new file mode 100644 index 0000000..c604a49 --- /dev/null +++ b/src/updater.h | |||
@@ -0,0 +1,46 @@ | |||
1 | #ifndef UPDATER_H_809E7381 | ||
2 | #define UPDATER_H_809E7381 | ||
3 | |||
4 | #include <filesystem> | ||
5 | #include <set> | ||
6 | #include <string> | ||
7 | |||
8 | #include <wx/wxprec.h> | ||
9 | |||
10 | #ifndef WX_PRECOMP | ||
11 | #include <wx/wx.h> | ||
12 | #endif | ||
13 | |||
14 | class wxWebRequest; | ||
15 | class wxWebRequestEvent; | ||
16 | |||
17 | class Updater : public wxEvtHandler { | ||
18 | public: | ||
19 | explicit Updater(wxFrame* parent); | ||
20 | |||
21 | void Cleanup(); | ||
22 | |||
23 | void CheckForUpdates(bool invisible); | ||
24 | |||
25 | private: | ||
26 | enum class UpdateState { | ||
27 | GetVersionInvisible, | ||
28 | GetVersionManual, | ||
29 | GetPackage, | ||
30 | }; | ||
31 | |||
32 | void OnWebRequestState(wxWebRequestEvent& event); | ||
33 | |||
34 | void ProcessVersionFile(std::string data); | ||
35 | |||
36 | void InstallUpdate(std::string url, std::string checksum, | ||
37 | std::vector<std::filesystem::path> files, | ||
38 | std::vector<std::filesystem::path> deletedFiles); | ||
39 | |||
40 | bool DownloadWithProgress(wxWebRequest& request); | ||
41 | |||
42 | wxFrame* parent_; | ||
43 | UpdateState update_state_ = UpdateState::GetVersionInvisible; | ||
44 | }; | ||
45 | |||
46 | #endif /* end of include guard: UPDATER_H_809E7381 */ | ||
diff --git a/src/version.h b/src/version.h index f734f02..3439fda 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, 12, 0); | 39 | constexpr const Version kTrackerVersion = Version(2, 0, 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 |
diff --git a/src/windows.rc b/src/windows.rc new file mode 100644 index 0000000..8ba30ed --- /dev/null +++ b/src/windows.rc | |||
@@ -0,0 +1,3 @@ | |||
1 | #define wxUSE_RC_MANIFEST 1 | ||
2 | #define wxUSE_DPI_AWARE_MANIFEST 2 | ||
3 | #include "wx/msw/wx.rc" | ||
diff --git a/vcpkg.json b/vcpkg.json index e13d228..581a507 100644 --- a/vcpkg.json +++ b/vcpkg.json | |||
@@ -1,6 +1,5 @@ | |||
1 | { | 1 | { |
2 | "dependencies": [ | 2 | "dependencies": [ |
3 | "websocketpp", | ||
4 | "wxwidgets", | 3 | "wxwidgets", |
5 | "openssl", | 4 | "openssl", |
6 | "yaml-cpp", | 5 | "yaml-cpp", |
diff --git a/vendor/vcpkg b/vendor/vcpkg | |||
Subproject 3dd44b931481d7a8e9ba412621fa810232b6628 | Subproject df8bfe519564ae001903e5cdd32af0999531ef7 | ||
diff --git a/vendor/websocketpp b/vendor/websocketpp new file mode 160000 | |||
Subproject 1b11fd301531e6df35a6107c1e8665b1e77a2d8 | |||