diff options
33 files changed, 3299 insertions, 264 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee737e..37167d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -1,5 +1,59 @@ | |||
1 | # lingo-ap-tracker Releases | 1 | # lingo-ap-tracker Releases |
2 | 2 | ||
3 | ## v0.10.2 - 2024-06-09 | ||
4 | |||
5 | - Fixed intermittent reachability detection issue. | ||
6 | - Increased debug logging. | ||
7 | |||
8 | Download: | ||
9 | [lingo-ap-tracker-v0.10.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.2-win64.zip)<br/> | ||
10 | Source: [v0.10.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.2) | ||
11 | |||
12 | ## v0.10.1 - 2024-06-08 | ||
13 | |||
14 | - Fixed display not updating when game state changes. | ||
15 | - Fixed broken reachability calculation. | ||
16 | |||
17 | Download: | ||
18 | [lingo-ap-tracker-v0.10.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.1-win64.zip)<br/> | ||
19 | Source: [v0.10.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.1) | ||
20 | |||
21 | ## v0.10.0 - 2024-06-06 | ||
22 | |||
23 | - Added a subway map tab to the tracker. This displays the topology of the game, | ||
24 | along with all of the doors, paintings, sunwarps, and regular warps. It can | ||
25 | show you whether doors are open or closed, and you can hover over them to see | ||
26 | what's needed to open them. You can also hover over paintings, sunwarps, and | ||
27 | regular warps to see what they're connected to. Thanks to Kinrah for creating | ||
28 | the image! | ||
29 | - When playing on painting shuffle, the tracker will no longer automatically | ||
30 | take your painting mapping into consideration. Instead, unchecked paintings | ||
31 | will show up in the regular map tab as if they were unchecked locations. | ||
32 | Looking into a painting in-game marks it as checked in the tracker, and if the | ||
33 | painting goes somewhere, the tracker will record that and recalculate your | ||
34 | reachable areas accordingly. v3.1.0 or later of the client is required for | ||
35 | this to work. | ||
36 | |||
37 | Download: | ||
38 | [lingo-ap-tracker-v0.10.0-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.10.0-win64.zip)<br/> | ||
39 | Source: [v0.10.0](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.10.0) | ||
40 | |||
41 | ## v0.9.2 - 2024-06-04 | ||
42 | |||
43 | - Updated apclient and dependencies. | ||
44 | |||
45 | Download: | ||
46 | [lingo-ap-tracker-v0.9.2-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.9.2-win64.zip)<br/> | ||
47 | Source: [v0.9.2](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.9.2) | ||
48 | |||
49 | ## v0.9.1 - 2024-05-15 | ||
50 | |||
51 | - Fixed pilgrimage detection when sunwarp shuffle is on. | ||
52 | |||
53 | Download: | ||
54 | [lingo-ap-tracker-v0.9.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.9.1-win64.zip)<br/> | ||
55 | Source: [v0.9.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.9.1) | ||
56 | |||
3 | ## v0.9.0 - 2024-04-22 | 57 | ## v0.9.0 - 2024-04-22 |
4 | 58 | ||
5 | - Compatibility update for Archipelago 0.4.6 | 59 | - Compatibility update for Archipelago 0.4.6 |
diff --git a/CMakeLists.txt b/CMakeLists.txt index f857096..cd62c55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -22,6 +22,7 @@ include_directories( | |||
22 | ${yaml-cpp_INCLUDE_DIRS} | 22 | ${yaml-cpp_INCLUDE_DIRS} |
23 | ${OpenSSL_INCLUDE_DIRS} | 23 | ${OpenSSL_INCLUDE_DIRS} |
24 | vendor/whereami | 24 | vendor/whereami |
25 | vendor | ||
25 | ) | 26 | ) |
26 | 27 | ||
27 | find_path(SYSTEM_INCLUDE_DIR zlib.h) | 28 | find_path(SYSTEM_INCLUDE_DIR zlib.h) |
@@ -39,11 +40,11 @@ add_executable(lingo_ap_tracker | |||
39 | "src/connection_dialog.cpp" | 40 | "src/connection_dialog.cpp" |
40 | "src/tracker_state.cpp" | 41 | "src/tracker_state.cpp" |
41 | "src/tracker_config.cpp" | 42 | "src/tracker_config.cpp" |
42 | "src/logger.cpp" | ||
43 | "src/achievements_pane.cpp" | 43 | "src/achievements_pane.cpp" |
44 | "src/settings_dialog.cpp" | 44 | "src/settings_dialog.cpp" |
45 | "src/global.cpp" | 45 | "src/global.cpp" |
46 | "src/version.cpp" | 46 | "src/subway_map.cpp" |
47 | "src/network_set.cpp" | ||
47 | "vendor/whereami/whereami.c" | 48 | "vendor/whereami/whereami.c" |
48 | ) | 49 | ) |
49 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) | 50 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) |
diff --git a/README.md b/README.md index 0b928cd..83525dd 100644 --- a/README.md +++ b/README.md | |||
@@ -6,3 +6,7 @@ This app is a tool that helps you keep track of your game state when playing Lin | |||
6 | ## Download | 6 | ## Download |
7 | 7 | ||
8 | Releases of the tracker can be found [on the releases page](https://code.fourisland.com/lingo-ap-tracker/about/CHANGELOG.md). | 8 | Releases of the tracker can be found [on the releases page](https://code.fourisland.com/lingo-ap-tracker/about/CHANGELOG.md). |
9 | |||
10 | ## Acknowledgments | ||
11 | |||
12 | Thanks to Kinrah for making the subway map image! | ||
diff --git a/VERSION b/VERSION index 7965b36..a30852f 100644 --- a/VERSION +++ b/VERSION | |||
@@ -1 +1 @@ | |||
v0.9.0 \ No newline at end of file | v0.10.2 \ No newline at end of file | ||
diff --git a/assets/areas.yaml b/assets/areas.yaml index d38ceb8..cbcf23a 100755 --- a/assets/areas.yaml +++ b/assets/areas.yaml | |||
@@ -67,6 +67,8 @@ | |||
67 | map: [2642, 872] | 67 | map: [2642, 872] |
68 | Eight Room: | 68 | Eight Room: |
69 | fold_into: The Incomparable | 69 | fold_into: The Incomparable |
70 | Eight Alcove: | ||
71 | fold_into: The Incomparable | ||
70 | Orange Tower First Floor: | 72 | Orange Tower First Floor: |
71 | map: [1285, 928] | 73 | map: [1285, 928] |
72 | Color Hunt: | 74 | Color Hunt: |
@@ -83,8 +85,10 @@ | |||
83 | map: [1252, 1259] | 85 | map: [1252, 1259] |
84 | Orange Tower Seventh Floor: | 86 | Orange Tower Seventh Floor: |
85 | map: [1587, 1900] | 87 | map: [1587, 1900] |
86 | Orange Tower Basement: | 88 | Orange Tower Sixth Floor: |
87 | map: [1587, 2000] | 89 | map: [1587, 2000] |
90 | Orange Tower Basement: | ||
91 | map: [1587, 2100] | ||
88 | Courtyard: | 92 | Courtyard: |
89 | map: [863, 387] | 93 | map: [863, 387] |
90 | First Second Third Fourth: | 94 | First Second Third Fourth: |
@@ -224,11 +228,15 @@ | |||
224 | The Eyes They See: | 228 | The Eyes They See: |
225 | map: [955, 933] | 229 | map: [955, 933] |
226 | Far Window: | 230 | Far Window: |
227 | fold_into: The Eyes They See | 231 | fold_into: The Eyes They See |
232 | Wondrous Lobby: | ||
233 | fold_into: The Eyes They See | ||
228 | Outside The Wondrous: | 234 | Outside The Wondrous: |
229 | map: [691, 524] | 235 | map: [691, 524] |
230 | The Wondrous: | 236 | The Wondrous: |
231 | map: [648, 338] | 237 | map: [648, 338] |
238 | The Wondrous (Doorknob): | ||
239 | fold_into: The Wondrous | ||
232 | The Wondrous (Bookcase): | 240 | The Wondrous (Bookcase): |
233 | fold_into: The Wondrous | 241 | fold_into: The Wondrous |
234 | The Wondrous (Chandelier): | 242 | The Wondrous (Chandelier): |
diff --git a/assets/owl.png b/assets/owl.png new file mode 100644 index 0000000..0210303 --- /dev/null +++ b/assets/owl.png | |||
Binary files differ | |||
diff --git a/assets/subway.png b/assets/subway.png new file mode 100644 index 0000000..3860d2c --- /dev/null +++ b/assets/subway.png | |||
Binary files differ | |||
diff --git a/assets/subway.yaml b/assets/subway.yaml new file mode 100644 index 0000000..266b722 --- /dev/null +++ b/assets/subway.yaml | |||
@@ -0,0 +1,1038 @@ | |||
1 | --- | ||
2 | - pos: [1050, 954] | ||
3 | room: Starting Room | ||
4 | door: Back Right Door | ||
5 | - pos: [986, 1034] | ||
6 | room: Starting Room | ||
7 | door: Rhyme Room Entrance | ||
8 | - pos: [990, 956] | ||
9 | special: starting_room_paintings # Early Color Hallways painting is a hardcoded special case | ||
10 | paintings: | ||
11 | - arrows_painting | ||
12 | - pos: [905, 841] | ||
13 | room: Hedge Maze | ||
14 | door: Painting Shortcut | ||
15 | paintings: | ||
16 | - garden_painting_tower2 | ||
17 | tags: | ||
18 | - garden_starting | ||
19 | - pos: [1066, 841] | ||
20 | room: Courtyard | ||
21 | door: Painting Shortcut | ||
22 | paintings: | ||
23 | - flower_painting_8 | ||
24 | tags: | ||
25 | - flower_starting | ||
26 | - pos: [905, 895] | ||
27 | room: The Wondrous (Doorknob) | ||
28 | door: Painting Shortcut | ||
29 | paintings: | ||
30 | - symmetry_painting_a_starter | ||
31 | tags: | ||
32 | - symmetry_starting | ||
33 | - pos: [1066, 868] | ||
34 | room: Outside The Bold | ||
35 | door: Painting Shortcut | ||
36 | paintings: | ||
37 | - pencil_painting6 | ||
38 | tags: | ||
39 | - pencil_starting | ||
40 | - pos: [1066, 895] | ||
41 | room: Outside The Undeterred | ||
42 | door: Painting Shortcut | ||
43 | paintings: | ||
44 | - blueman_painting_3 | ||
45 | tags: | ||
46 | - blueman_starting | ||
47 | - pos: [905, 868] | ||
48 | room: Outside The Agreeable | ||
49 | door: Painting Shortcut | ||
50 | paintings: | ||
51 | - eyes_yellow_painting2 | ||
52 | tags: | ||
53 | - street_starting | ||
54 | - pos: [1211, 879] | ||
55 | room: Hidden Room | ||
56 | door: Dead End Door | ||
57 | - pos: [1291, 906] | ||
58 | room: Hidden Room | ||
59 | door: Knight Night Entrance | ||
60 | - pos: [1103, 980] | ||
61 | room: Hidden Room | ||
62 | door: Seeker Entrance | ||
63 | - pos: [1173, 980] | ||
64 | room: Hidden Room | ||
65 | door: Rhyme Room Entrance | ||
66 | - pos: [1116, 939] | ||
67 | paintings: | ||
68 | - owl_painting | ||
69 | tags: | ||
70 | - owl_hidden | ||
71 | - pos: [986, 793] | ||
72 | room: Second Room | ||
73 | door: Exit Door | ||
74 | - pos: [798, 584] | ||
75 | room: Hub Room | ||
76 | door: Crossroads Entrance | ||
77 | - pos: [932, 665] | ||
78 | room: Hub Room | ||
79 | door: Tenacious Entrance | ||
80 | - pos: [1361, 578] | ||
81 | room: Hub Room | ||
82 | door: Shortcut to Hedge Maze | ||
83 | - pos: [1312, 841] | ||
84 | room: Hub Room | ||
85 | door: Near RAT Door | ||
86 | - pos: [1371, 729] | ||
87 | room: Hub Room | ||
88 | door: Traveled Entrance | ||
89 | - pos: [1313, 686] | ||
90 | paintings: | ||
91 | - maze_painting | ||
92 | tags: | ||
93 | - green_owl | ||
94 | - green_numbers | ||
95 | - pos: [1172, 760] | ||
96 | sunwarp: | ||
97 | dots: 1 | ||
98 | type: enter | ||
99 | - pos: [1302, 638] | ||
100 | room: Outside The Undeterred | ||
101 | door: Fours | ||
102 | - pos: [1243, 819] | ||
103 | room: Outside The Undeterred | ||
104 | door: Fours | ||
105 | - pos: [1276, 819] | ||
106 | room: Outside The Undeterred | ||
107 | door: Eights | ||
108 | - pos: [1263, 867] | ||
109 | paintings: | ||
110 | - smile_painting_6 | ||
111 | tags: | ||
112 | - smiley_deadend | ||
113 | - pos: [1012, 1086] | ||
114 | sunwarp: | ||
115 | dots: 6 | ||
116 | type: final | ||
117 | - pos: [938, 1002] | ||
118 | room: Pilgrim Antechamber | ||
119 | door: Sun Painting | ||
120 | special: sun_painting | ||
121 | - pos: [1053, 1090] | ||
122 | invisible: true | ||
123 | special: sun_painting_exit | ||
124 | - pos: [1077, 1061] | ||
125 | room: Pilgrim Room | ||
126 | door: Shortcut to The Seeker | ||
127 | - pos: [713, 359] | ||
128 | room: Number Hunt | ||
129 | door: Eights | ||
130 | - pos: [932, 348] | ||
131 | room: Crossroads | ||
132 | door: Hollow Hallway | ||
133 | - pos: [798, 290] | ||
134 | room: Crossroads | ||
135 | door: Tower Entrance | ||
136 | - pos: [932, 477] | ||
137 | room: Crossroads | ||
138 | door: Tenacious Entrance | ||
139 | - pos: [638, 477] | ||
140 | room: Crossroads | ||
141 | door: Discerning Entrance | ||
142 | - pos: [905, 290] | ||
143 | room: Crossroads | ||
144 | door: Tower Back Entrance | ||
145 | - pos: [894, 423] | ||
146 | room: Crossroads | ||
147 | door: Words Sword Door | ||
148 | - pos: [632, 643] | ||
149 | room: Crossroads | ||
150 | door: Eye Wall | ||
151 | - pos: [638, 520] | ||
152 | room: Crossroads | ||
153 | door: Roof Access | ||
154 | - pos: [756, 400] | ||
155 | paintings: | ||
156 | - smile_painting_4 | ||
157 | tags: | ||
158 | - smiley_crossroads | ||
159 | - pos: [878, 509] | ||
160 | sunwarp: | ||
161 | dots: 1 | ||
162 | type: exit | ||
163 | - pos: [1056, 344] | ||
164 | room: Lost Area | ||
165 | door: Exit | ||
166 | - pos: [954, 290] | ||
167 | room: Lost Area | ||
168 | door: Exit | ||
169 | - pos: [986, 290] | ||
170 | room: Number Hunt | ||
171 | door: Eights | ||
172 | - pos: [954, 247] | ||
173 | room: Amen Name Area | ||
174 | door: Exit | ||
175 | - pos: [954, 222] | ||
176 | paintings: | ||
177 | - west_afar | ||
178 | - pos: [986, 697] | ||
179 | room: The Tenacious | ||
180 | door: Shortcut to Hub Room | ||
181 | - pos: [1173, 665] | ||
182 | room: Near Far Area | ||
183 | door: Door | ||
184 | - pos: [1173, 622] | ||
185 | room: Warts Straw Area | ||
186 | door: Door | ||
187 | - pos: [1173, 579] | ||
188 | room: Leaf Feel Area | ||
189 | door: Door | ||
190 | - pos: [1173, 333] | ||
191 | room: Outside The Agreeable | ||
192 | door: Purple Barrier | ||
193 | - pos: [1088, 289] | ||
194 | room: Outside The Undeterred | ||
195 | door: Fives | ||
196 | - pos: [1088, 418] | ||
197 | room: Outside The Undeterred | ||
198 | door: Fives | ||
199 | - pos: [1039, 477] | ||
200 | room: Outside The Agreeable | ||
201 | door: Tenacious Entrance | ||
202 | - pos: [1147, 525] | ||
203 | room: Outside The Agreeable | ||
204 | door: Black Door | ||
205 | - pos: [1216, 525] | ||
206 | room: Outside The Agreeable | ||
207 | door: Agreeable Entrance | ||
208 | - pos: [1138, 287] | ||
209 | paintings: | ||
210 | - eyes_yellow_painting | ||
211 | tags: | ||
212 | - street_starting | ||
213 | - pos: [1088, 385] | ||
214 | sunwarp: | ||
215 | dots: 6 | ||
216 | type: enter | ||
217 | - pos: [1195, 450] | ||
218 | room: Compass Room | ||
219 | door: Lookout Entrance | ||
220 | - pos: [1214, 457] | ||
221 | paintings: | ||
222 | - pencil_painting7 | ||
223 | tags: | ||
224 | - pencil_compass | ||
225 | - pos: [1196, 417] | ||
226 | invisible: true | ||
227 | tags: | ||
228 | - agreeable_to_lookout | ||
229 | - pos: [1657, 1392] | ||
230 | room: Room Room | ||
231 | door: Excavation | ||
232 | - pos: [1725, 1441] | ||
233 | invisible: true | ||
234 | tags: | ||
235 | - agreeable_to_lookout | ||
236 | - pos: [1040, 665] | ||
237 | room: Dread Hallway | ||
238 | door: Tenacious Entrance | ||
239 | - pos: [1324, 525] | ||
240 | room: The Agreeable | ||
241 | door: Shortcut to Hedge Maze | ||
242 | - pos: [1484, 392] | ||
243 | room: Hedge Maze | ||
244 | door: Perceptive Entrance | ||
245 | - pos: [1441, 241] | ||
246 | room: Hedge Maze | ||
247 | door: Observant Entrance | ||
248 | - pos: [1714, 434] | ||
249 | room: Hedge Maze | ||
250 | door: Observant Entrance | ||
251 | - pos: [1477, 343] | ||
252 | paintings: | ||
253 | - garden_painting_tower | ||
254 | tags: | ||
255 | - garden_starting | ||
256 | - pos: [1565, 311] | ||
257 | room: The Fearless (First Floor) | ||
258 | door: Second Floor | ||
259 | - pos: [1597, 279] | ||
260 | room: The Fearless (Second Floor) | ||
261 | door: Third Floor | ||
262 | - pos: [1414, 209] | ||
263 | room: The Observant | ||
264 | door: Backside Door | ||
265 | - pos: [1624, 188] | ||
266 | room: The Observant | ||
267 | door: Stairs | ||
268 | - pos: [1667, 686] | ||
269 | room: The Incomparable | ||
270 | door: Eight Door | ||
271 | - pos: [1784, 569] | ||
272 | paintings: | ||
273 | - crown_painting | ||
274 | tags: | ||
275 | - crown_tower6 | ||
276 | - pos: [1653, 717] | ||
277 | paintings: | ||
278 | - eight_painting2 | ||
279 | tags: | ||
280 | - eight_alcove | ||
281 | - pos: [1653, 662] | ||
282 | paintings: | ||
283 | - eight_painting | ||
284 | tags: | ||
285 | - eight_alcove | ||
286 | - pos: [697, 1471] | ||
287 | room: Orange Tower | ||
288 | door: Second Floor | ||
289 | - pos: [633, 1406] | ||
290 | room: Orange Tower | ||
291 | door: Third Floor | ||
292 | - pos: [570, 1343] | ||
293 | room: Orange Tower | ||
294 | door: Fourth Floor | ||
295 | - pos: [504, 1279] | ||
296 | room: Orange Tower | ||
297 | door: Fifth Floor | ||
298 | - pos: [440, 1215] | ||
299 | room: Orange Tower | ||
300 | door: Sixth Floor | ||
301 | - pos: [379, 1153] | ||
302 | room: Orange Tower | ||
303 | door: Seventh Floor | ||
304 | - pos: [905, 793] | ||
305 | room: Orange Tower First Floor | ||
306 | door: Shortcut to Hub Room | ||
307 | - pos: [686, 820] | ||
308 | room: Orange Tower First Floor | ||
309 | door: Salt Pepper Door | ||
310 | - pos: [755, 787] | ||
311 | sunwarp: | ||
312 | dots: 4 | ||
313 | type: enter | ||
314 | - pos: [719, 846] | ||
315 | tags: | ||
316 | - tower1_tower1 | ||
317 | - pos: [681, 1506] | ||
318 | tags: | ||
319 | - tower1_tower1 | ||
320 | - pos: [722, 1439] | ||
321 | tags: | ||
322 | - tower2_undeterred | ||
323 | - pos: [533, 1375] | ||
324 | tags: | ||
325 | - tower3_tower3 | ||
326 | - pos: [662, 1375] | ||
327 | tags: | ||
328 | - tower3_gallery | ||
329 | - pos: [483, 1307] | ||
330 | tags: | ||
331 | - tower4_room | ||
332 | - pos: [598, 1307] | ||
333 | tags: | ||
334 | - tower4_tower4 | ||
335 | - pos: [598, 1287] | ||
336 | tags: | ||
337 | - tower4_courtyard | ||
338 | - pos: [533, 1245] | ||
339 | tags: | ||
340 | - tower5_welcome | ||
341 | - pos: [419, 1245] | ||
342 | tags: | ||
343 | - tower5_cellar | ||
344 | - pos: [419, 1267] | ||
345 | tags: | ||
346 | - tower5_quadruple | ||
347 | - pos: [203, 1014] | ||
348 | tags: | ||
349 | - tower2_undeterred | ||
350 | - pos: [1325, 1191] | ||
351 | tags: | ||
352 | - tower3_tower3 | ||
353 | - pos: [1700, 1021] | ||
354 | tags: | ||
355 | - tower3_gallery | ||
356 | - pos: [1653, 1318] | ||
357 | tags: | ||
358 | - tower4_room | ||
359 | - pos: [918, 222] | ||
360 | tags: | ||
361 | - tower4_tower4 | ||
362 | - pos: [806, 222] | ||
363 | tags: | ||
364 | - tower4_courtyard | ||
365 | - pos: [652, 951] | ||
366 | tags: | ||
367 | - tower5_welcome | ||
368 | - pos: [1553, 1440] | ||
369 | tags: | ||
370 | - tower5_cellar | ||
371 | - pos: [1459, 1119] | ||
372 | tags: | ||
373 | - tower5_quadruple | ||
374 | - pos: [1216, 1280] | ||
375 | room: Orange Tower Third Floor | ||
376 | door: Red Barrier | ||
377 | - pos: [1173, 1248] | ||
378 | room: Orange Tower Third Floor | ||
379 | door: Rhyme Room Entrance | ||
380 | - pos: [1270, 1231] | ||
381 | paintings: | ||
382 | - arrows_painting_6 | ||
383 | - flower_painting_5 | ||
384 | - pos: [1216, 1216] | ||
385 | sunwarp: | ||
386 | dots: 2 | ||
387 | type: exit | ||
388 | - pos: [1253, 1172] | ||
389 | sunwarp: | ||
390 | dots: 3 | ||
391 | type: enter | ||
392 | - pos: [852, 198] | ||
393 | room: Orange Tower Fourth Floor | ||
394 | door: Hot Crusts Door | ||
395 | - pos: [830, 289] | ||
396 | sunwarp: | ||
397 | dots: 5 | ||
398 | type: enter | ||
399 | - pos: [877, 155] | ||
400 | room: Number Hunt | ||
401 | door: Eights | ||
402 | - pos: [844, 134] | ||
403 | paintings: | ||
404 | - smile_painting_8 | ||
405 | tags: | ||
406 | - smiley_hotcrusts | ||
407 | - pos: [797, 155] | ||
408 | sunwarp: | ||
409 | dots: 2 | ||
410 | type: enter | ||
411 | - pos: [679, 985] | ||
412 | room: Number Hunt | ||
413 | door: Nines | ||
414 | - pos: [723, 953] | ||
415 | room: Orange Tower Fifth Floor | ||
416 | door: Welcome Back | ||
417 | - pos: [683, 944] | ||
418 | paintings: | ||
419 | - east_afar | ||
420 | - pos: [548, 1221] | ||
421 | paintings: | ||
422 | - hi_solved_painting3 | ||
423 | - pos: [1574, 1425] | ||
424 | paintings: | ||
425 | - hi_solved_painting2 | ||
426 | - pos: [411, 1186] | ||
427 | paintings: | ||
428 | - arrows_painting_10 | ||
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 | tags: | ||
439 | - owl_tower6 | ||
440 | - clock_tower6 | ||
441 | - panda_tower6 | ||
442 | - crown_tower6 | ||
443 | - apple_tower6 | ||
444 | - hi_scientific | ||
445 | - pos: [349, 1124] | ||
446 | paintings: | ||
447 | - map_painting2 | ||
448 | - pos: [436, 1159] | ||
449 | room: Orange Tower Seventh Floor | ||
450 | door: Mastery | ||
451 | - pos: [544, 1159] | ||
452 | paintings: | ||
453 | - arrows_painting_11 | ||
454 | - pos: [498, 284] | ||
455 | room: Courtyard | ||
456 | door: Green Barrier | ||
457 | - pos: [556, 233] | ||
458 | paintings: | ||
459 | - flower_painting_7 | ||
460 | tags: | ||
461 | - flower_starting | ||
462 | - flower_arrow | ||
463 | - pos: [600, 332] | ||
464 | room: Number Hunt | ||
465 | door: Nines | ||
466 | - pos: [579, 350] | ||
467 | paintings: | ||
468 | - blueman_painting | ||
469 | tags: | ||
470 | - blueman_courtyard | ||
471 | - pos: [530, 310] | ||
472 | room: First Second Third Fourth | ||
473 | door: Backside Door | ||
474 | - pos: [584, 107] | ||
475 | room: The Colorful (White) | ||
476 | door: Progress Door | ||
477 | - pos: [622, 107] | ||
478 | room: The Colorful (Black) | ||
479 | door: Progress Door | ||
480 | - pos: [659, 107] | ||
481 | room: The Colorful (Red) | ||
482 | door: Progress Door | ||
483 | - pos: [697, 107] | ||
484 | room: The Colorful (Yellow) | ||
485 | door: Progress Door | ||
486 | - pos: [734, 107] | ||
487 | room: The Colorful (Blue) | ||
488 | door: Progress Door | ||
489 | - pos: [772, 107] | ||
490 | room: The Colorful (Purple) | ||
491 | door: Progress Door | ||
492 | - pos: [809, 107] | ||
493 | room: The Colorful (Orange) | ||
494 | door: Progress Door | ||
495 | - pos: [847, 107] | ||
496 | room: The Colorful (Green) | ||
497 | door: Progress Door | ||
498 | - pos: [884, 107] | ||
499 | room: The Colorful (Brown) | ||
500 | door: Progress Door | ||
501 | - pos: [922, 107] | ||
502 | room: The Colorful (Gray) | ||
503 | door: Progress Door | ||
504 | - pos: [967, 107] | ||
505 | paintings: | ||
506 | - arrows_painting_12 | ||
507 | - pos: [878, 954] | ||
508 | room: Welcome Back Area | ||
509 | door: Shortcut to Starting Room | ||
510 | - pos: [773, 954] | ||
511 | tags: | ||
512 | - hub_wb | ||
513 | - wondrous_wb | ||
514 | - undeterred_wb | ||
515 | - agreeable_wb | ||
516 | - wanderer_wb | ||
517 | - observant_wb | ||
518 | - gallery_wb | ||
519 | - scientific_wb | ||
520 | - cellar_wb | ||
521 | - pos: [1107, 749] | ||
522 | tags: | ||
523 | - hub_wb | ||
524 | - pos: [408, 817] | ||
525 | tags: | ||
526 | - wondrous_wb | ||
527 | - pos: [281, 1017] | ||
528 | tags: | ||
529 | - undeterred_wb | ||
530 | - pos: [1017, 289] | ||
531 | tags: | ||
532 | - agreeable_wb | ||
533 | - pos: [907, 1385] | ||
534 | tags: | ||
535 | - wanderer_wb | ||
536 | - pos: [1737, 1053] | ||
537 | tags: | ||
538 | - gallery_wb | ||
539 | - pos: [1690, 268] | ||
540 | tags: | ||
541 | - observant_wb | ||
542 | - pos: [250, 604] | ||
543 | tags: | ||
544 | - scientific_wb | ||
545 | - pos: [1553, 1467] | ||
546 | tags: | ||
547 | - cellar_wb | ||
548 | - pos: [1478, 498] | ||
549 | room: Owl Hallway | ||
550 | door: Shortcut to Hedge Maze | ||
551 | - pos: [1480, 551] | ||
552 | paintings: | ||
553 | - arrows_painting_8 | ||
554 | - maze_painting_2 | ||
555 | - owl_painting_2 | ||
556 | - clock_painting_4 | ||
557 | tags: | ||
558 | - green_owl | ||
559 | - owl_hidden | ||
560 | - owl_tower6 | ||
561 | - pos: [1478, 938] | ||
562 | room: Number Hunt | ||
563 | door: Sevens | ||
564 | - pos: [1580, 853] | ||
565 | room: Number Hunt | ||
566 | door: Sevens | ||
567 | - pos: [1478, 905] | ||
568 | room: Number Hunt | ||
569 | door: Eights | ||
570 | - pos: [1452, 841] | ||
571 | room: Number Hunt | ||
572 | door: Nines | ||
573 | - pos: [1420, 841] | ||
574 | room: Outside The Initiated | ||
575 | door: Blue Barrier | ||
576 | - pos: [1479, 1018] | ||
577 | room: Outside The Initiated | ||
578 | door: Orange Barrier | ||
579 | - pos: [1360, 847] | ||
580 | room: Outside The Initiated | ||
581 | door: Shortcut to Hub Room | ||
582 | - pos: [1511, 841] | ||
583 | room: Outside The Initiated | ||
584 | door: Initiated Entrance | ||
585 | - pos: [1141, 1441] | ||
586 | room: Orange Tower Third Floor | ||
587 | door: Orange Barrier | ||
588 | - pos: [1173, 1441] | ||
589 | room: Outside The Initiated | ||
590 | door: Green Barrier | ||
591 | - pos: [1206, 1441] | ||
592 | room: Outside The Initiated | ||
593 | door: Purple Barrier | ||
594 | - pos: [1189, 1355] | ||
595 | room: Outside The Initiated | ||
596 | door: Entrance | ||
597 | - pos: [1580, 729] | ||
598 | room: Outside The Initiated | ||
599 | door: Eight Door | ||
600 | - pos: [1530, 938] | ||
601 | paintings: | ||
602 | - clock_painting_5 | ||
603 | tags: | ||
604 | - clock_initiated | ||
605 | - pos: [1546, 938] | ||
606 | paintings: | ||
607 | - clock_painting_2 | ||
608 | - arrows_painting_2 | ||
609 | tags: | ||
610 | - clock_tower6 | ||
611 | - clock_initiated | ||
612 | - pos: [1579, 813] | ||
613 | sunwarp: | ||
614 | dots: 3 | ||
615 | type: exit | ||
616 | - pos: [1444, 896] | ||
617 | paintings: | ||
618 | - smile_painting_1 | ||
619 | tags: | ||
620 | - smiley_initiated | ||
621 | - pos: [1430, 691] | ||
622 | room: Outside The Undeterred | ||
623 | door: Fours | ||
624 | - pos: [1468, 728] | ||
625 | room: The Traveled | ||
626 | door: Color Hallways Entrance | ||
627 | - pos: [1533, 707] | ||
628 | tags: | ||
629 | - red_ch | ||
630 | - blue_ch | ||
631 | - yellow_ch | ||
632 | - green_ch | ||
633 | - pos: [1567, 1264] | ||
634 | tags: | ||
635 | - red_ch | ||
636 | - pos: [150, 808] | ||
637 | tags: | ||
638 | - blue_ch | ||
639 | - pos: [626, 371] | ||
640 | tags: | ||
641 | - yellow_ch | ||
642 | - pos: [1368, 241] | ||
643 | tags: | ||
644 | - green_ch | ||
645 | - pos: [1543, 1307] | ||
646 | room: Number Hunt | ||
647 | door: Sixes | ||
648 | - pos: [1543, 1388] | ||
649 | room: Number Hunt | ||
650 | door: Nines | ||
651 | - pos: [1468, 1377] | ||
652 | room: Outside The Bold | ||
653 | door: Bold Entrance | ||
654 | - pos: [1425, 1358] | ||
655 | paintings: | ||
656 | - pencil_painting2 | ||
657 | - north_missing2 | ||
658 | tags: | ||
659 | - pencil_compass | ||
660 | - pencil_starting | ||
661 | - pencil_directional | ||
662 | - pos: [1334, 1419] | ||
663 | room: Outside The Bold | ||
664 | door: Steady Entrance | ||
665 | - pos: [445, 1048] | ||
666 | tags: | ||
667 | - undeterred_artistic | ||
668 | - pos: [279, 1071] | ||
669 | room: Number Hunt | ||
670 | door: Zero Door | ||
671 | - pos: [338, 1071] | ||
672 | room: Outside The Undeterred | ||
673 | door: Twos | ||
674 | - pos: [370, 1071] | ||
675 | room: Outside The Undeterred | ||
676 | door: Threes | ||
677 | - pos: [402, 1071] | ||
678 | room: Outside The Undeterred | ||
679 | door: Fours | ||
680 | - pos: [242, 1071] | ||
681 | room: Outside The Undeterred | ||
682 | door: Undeterred Entrance | ||
683 | - pos: [60, 1017] | ||
684 | paintings: | ||
685 | - blueman_painting_2 | ||
686 | tags: | ||
687 | - blueman_courtyard | ||
688 | - blueman_starting | ||
689 | - pos: [60, 970] | ||
690 | special: early_color_hallways | ||
691 | - pos: [402, 1012] | ||
692 | room: Outside The Undeterred | ||
693 | door: Green Painting | ||
694 | paintings: | ||
695 | - maze_painting_3 | ||
696 | tags: | ||
697 | - green_numbers | ||
698 | - pos: [134, 1007] | ||
699 | sunwarp: | ||
700 | dots: 4 | ||
701 | type: exit | ||
702 | - pos: [719, 1039] | ||
703 | room: Outside The Undeterred | ||
704 | door: Challenge Entrance | ||
705 | - pos: [438, 1039] | ||
706 | room: Outside The Undeterred | ||
707 | door: Number Hunt | ||
708 | - pos: [563, 1071] | ||
709 | room: Outside The Undeterred | ||
710 | door: Fives | ||
711 | - pos: [596, 1071] | ||
712 | room: Number Hunt | ||
713 | door: Sixes | ||
714 | - pos: [627, 1071] | ||
715 | room: Number Hunt | ||
716 | door: Sevens | ||
717 | - pos: [659, 1071] | ||
718 | room: Number Hunt | ||
719 | door: Eights | ||
720 | - pos: [692, 1071] | ||
721 | room: Number Hunt | ||
722 | door: Nines | ||
723 | - pos: [525, 1002] | ||
724 | room: Number Hunt | ||
725 | door: Door to Directional Gallery | ||
726 | - pos: [659, 1014] | ||
727 | room: Number Hunt | ||
728 | door: Eights | ||
729 | paintings: | ||
730 | - smile_painting_5 | ||
731 | tags: | ||
732 | - smiley_numbers | ||
733 | - pos: [557, 953] | ||
734 | room: Number Hunt | ||
735 | door: Sevens | ||
736 | - pos: [391, 932] | ||
737 | room: Number Hunt | ||
738 | door: Sixes | ||
739 | - pos: [311, 932] | ||
740 | room: Number Hunt | ||
741 | door: Nines | ||
742 | - pos: [268, 868] | ||
743 | room: Number Hunt | ||
744 | door: Eights | ||
745 | - pos: [268, 825] | ||
746 | room: Directional Gallery | ||
747 | door: Yellow Barrier | ||
748 | - pos: [231, 681] | ||
749 | room: Number Hunt | ||
750 | door: Sixes | ||
751 | - pos: [242, 980] | ||
752 | room: Directional Gallery | ||
753 | door: Shortcut to The Undeterred | ||
754 | - pos: [351, 927] | ||
755 | paintings: | ||
756 | - boxes_painting | ||
757 | tags: | ||
758 | - lattice_directional | ||
759 | - pos: [272, 927] | ||
760 | paintings: | ||
761 | - smile_painting_7 | ||
762 | tags: | ||
763 | - smiley_directional | ||
764 | - pos: [214, 822] | ||
765 | paintings: | ||
766 | - cherry_painting | ||
767 | tags: | ||
768 | - apple_directional | ||
769 | - pos: [266, 735] | ||
770 | room: Number Hunt | ||
771 | door: Sixes | ||
772 | paintings: | ||
773 | - pencil_painting3 | ||
774 | tags: | ||
775 | - pencil_directional | ||
776 | - pos: [215, 735] | ||
777 | paintings: | ||
778 | - flower_painting_4 | ||
779 | - pos: [626, 851] | ||
780 | sunwarp: | ||
781 | dots: 6 | ||
782 | type: exit | ||
783 | - pos: [1141, 1441] | ||
784 | room: Orange Tower Third Floor | ||
785 | door: Orange Barrier | ||
786 | - pos: [1174, 1441] | ||
787 | room: Outside The Initiated | ||
788 | door: Green Barrier | ||
789 | - pos: [1205, 1441] | ||
790 | room: Outside The Initiated | ||
791 | door: Purple Barrier | ||
792 | - pos: [1334, 1377] | ||
793 | room: Color Hunt | ||
794 | door: Shortcut to The Steady | ||
795 | - pos: [1280, 1375] | ||
796 | paintings: | ||
797 | - arrows_painting_7 | ||
798 | - pos: [1233, 1321] | ||
799 | room: Outside The Initiated | ||
800 | door: Entrance | ||
801 | paintings: | ||
802 | - fruitbowl_painting3 | ||
803 | - pos: [1290, 1323] | ||
804 | sunwarp: | ||
805 | dots: 5 | ||
806 | type: exit | ||
807 | - pos: [1189, 1356] | ||
808 | room: Outside The Initiated | ||
809 | door: Entrance | ||
810 | - pos: [1154, 1332] | ||
811 | paintings: | ||
812 | - colors_painting | ||
813 | - pos: [1640, 1260] | ||
814 | room: The Bearer | ||
815 | door: Backside Door | ||
816 | - pos: [1468, 1287] | ||
817 | room: The Bearer | ||
818 | door: Entrance | ||
819 | - pos: [1430, 1232] | ||
820 | room: Number Hunt | ||
821 | door: Sixes | ||
822 | - pos: [1388, 1152] | ||
823 | room: Bearer Side Area | ||
824 | door: Shortcut to Tower | ||
825 | - pos: [1273, 1142] | ||
826 | paintings: | ||
827 | - pencil_painting5 | ||
828 | - pencil_painting4 | ||
829 | - pos: [1355, 1092] | ||
830 | room: Knight Night (Final) | ||
831 | door: Exit | ||
832 | - pos: [1382, 900] | ||
833 | room: Knight Night (Final) | ||
834 | door: Exit | ||
835 | - pos: [1388, 1002] | ||
836 | # Complex case, because this is also blocked by Knight Night (Final) - Exit | ||
837 | room: Number Hunt | ||
838 | door: Sevens | ||
839 | - pos: [1653, 101] | ||
840 | paintings: | ||
841 | - smile_painting_9 | ||
842 | tags: | ||
843 | - smiley_crossroads | ||
844 | - smiley_deadend | ||
845 | - smiley_hotcrusts | ||
846 | - smiley_numbers | ||
847 | - smiley_directional | ||
848 | - smiley_initiated | ||
849 | - smiley_gallery | ||
850 | - smiley_theysee | ||
851 | - pos: [1656, 139] | ||
852 | room: The Artistic (Smiley) | ||
853 | door: Door to Panda | ||
854 | - pos: [1711, 140] | ||
855 | tags: | ||
856 | - undeterred_artistic | ||
857 | - pos: [1653, 169] | ||
858 | paintings: | ||
859 | - panda_painting_3 | ||
860 | tags: | ||
861 | - panda_tower6 | ||
862 | - panda_hallway | ||
863 | - pos: [1708, 171] | ||
864 | room: The Artistic (Panda) | ||
865 | door: Door to Lattice | ||
866 | - pos: [1761, 169] | ||
867 | paintings: | ||
868 | - boxes_painting2 | ||
869 | tags: | ||
870 | - lattice_directional | ||
871 | - pos: [1762, 139] | ||
872 | room: The Artistic (Lattice) | ||
873 | door: Door to Apple | ||
874 | - pos: [1761, 101] | ||
875 | paintings: | ||
876 | - cherry_painting3 | ||
877 | tags: | ||
878 | - apple_tower6 | ||
879 | - apple_directional | ||
880 | - pos: [1708, 107] | ||
881 | room: The Artistic (Apple) | ||
882 | door: Door to Smiley | ||
883 | - pos: [370, 681] | ||
884 | room: Number Hunt | ||
885 | door: Eights | ||
886 | - pos: [411, 685] | ||
887 | paintings: | ||
888 | - eye_painting_2 | ||
889 | - smile_painting_2 | ||
890 | tags: | ||
891 | - smiley_theysee | ||
892 | - pos: [310, 750] | ||
893 | room: The Eyes They See | ||
894 | door: Exit | ||
895 | - pos: [334, 798] | ||
896 | paintings: | ||
897 | - arrows_painting_5 | ||
898 | - pos: [370, 792] | ||
899 | room: Outside The Wondrous | ||
900 | door: Wondrous Entrance | ||
901 | - pos: [367, 752] | ||
902 | paintings: | ||
903 | - symmetry_painting_a_1 | ||
904 | - symmetry_painting_b_1 | ||
905 | - symmetry_painting_a_3 | ||
906 | - symmetry_painting_a_5 | ||
907 | - symmetry_painting_b_4 | ||
908 | - symmetry_painting_a_2 | ||
909 | - symmetry_painting_b_2 | ||
910 | - symmetry_painting_a_6 | ||
911 | - symmetry_painting_b_6 | ||
912 | tags: | ||
913 | - symmetry_starting | ||
914 | - pos: [407, 755] | ||
915 | room: The Wondrous | ||
916 | door: Exit | ||
917 | paintings: | ||
918 | - arrows_painting_9 | ||
919 | - pos: [449, 755] | ||
920 | paintings: | ||
921 | - flower_painting_6 | ||
922 | tags: | ||
923 | - flower_arrow | ||
924 | - pos: [1101, 222] | ||
925 | paintings: | ||
926 | - panda_painting | ||
927 | tags: | ||
928 | - panda_hallway | ||
929 | - pos: [1152, 209] | ||
930 | room: Hallway Room (1) | ||
931 | door: Exit | ||
932 | - pos: [1189, 170] | ||
933 | room: Hallway Room (2) | ||
934 | door: Exit | ||
935 | - pos: [1238, 124] | ||
936 | room: Hallway Room (3) | ||
937 | door: Exit | ||
938 | - pos: [1313, 108] | ||
939 | room: Hallway Room (4) | ||
940 | door: Exit | ||
941 | - pos: [1528, 108] | ||
942 | room: Hallway Room (4) | ||
943 | door: Exit | ||
944 | - pos: [1415, 140] | ||
945 | room: Number Hunt | ||
946 | door: Nines | ||
947 | - pos: [1458, 133] | ||
948 | paintings: | ||
949 | - south_afar | ||
950 | - pos: [826, 1452] | ||
951 | room: Outside The Wanderer | ||
952 | door: Wanderer Entrance | ||
953 | - pos: [763, 1465] | ||
954 | room: Outside The Wanderer | ||
955 | door: Tower Entrance | ||
956 | - pos: [1655, 1151] | ||
957 | room: Number Hunt | ||
958 | door: Eights | ||
959 | - pos: [1623, 1044] | ||
960 | room: Art Gallery | ||
961 | door: Second Floor | ||
962 | - pos: [1623, 1019] | ||
963 | room: Art Gallery | ||
964 | door: Third Floor | ||
965 | - pos: [1623, 991] | ||
966 | room: Art Gallery | ||
967 | door: Fourth Floor | ||
968 | - pos: [1623, 964] | ||
969 | room: Art Gallery | ||
970 | door: Fifth Floor | ||
971 | - pos: [1511, 1119] | ||
972 | room: Art Gallery | ||
973 | door: Exit | ||
974 | - pos: [1654, 1116] | ||
975 | paintings: | ||
976 | - smile_painting_3 | ||
977 | - flower_painting_2 | ||
978 | - scenery_painting_0a | ||
979 | - map_painting | ||
980 | - fruitbowl_painting4 | ||
981 | tags: | ||
982 | - smiley_gallery | ||
983 | - pos: [1120, 1286] | ||
984 | room: Rhyme Room (Smiley) | ||
985 | door: Door to Target | ||
986 | - pos: [1120, 1315] | ||
987 | tags: | ||
988 | - rhyme_smiley_target | ||
989 | - pos: [792, 1137] | ||
990 | tags: | ||
991 | - rhyme_smiley_target | ||
992 | - pos: [895, 1217] | ||
993 | room: Number Hunt | ||
994 | door: Nines | ||
995 | - pos: [938, 1296] | ||
996 | room: Rhyme Room (Cross) | ||
997 | door: Exit | ||
998 | - pos: [1120, 1195] | ||
999 | room: Rhyme Room (Circle) | ||
1000 | door: Door to Smiley | ||
1001 | - pos: [1118, 1137] | ||
1002 | paintings: | ||
1003 | - arrows_painting_3 | ||
1004 | - pos: [1050, 1142] | ||
1005 | room: Rhyme Room (Looped Square) | ||
1006 | door: Door to Circle | ||
1007 | - pos: [987, 1194] | ||
1008 | room: Rhyme Room (Looped Square) | ||
1009 | door: Door to Cross | ||
1010 | - pos: [922, 1142] | ||
1011 | room: Rhyme Room (Looped Square) | ||
1012 | door: Door to Target | ||
1013 | - pos: [852, 1200] | ||
1014 | room: Rhyme Room (Target) | ||
1015 | door: Door to Cross | ||
1016 | - pos: [850, 1138] | ||
1017 | paintings: | ||
1018 | - arrows_painting_4 | ||
1019 | - pos: [1592, 1442] | ||
1020 | room: Room Room | ||
1021 | door: Cellar Exit | ||
1022 | - pos: [1570, 938] | ||
1023 | room: Outside The Wise | ||
1024 | door: Wise Entrance | ||
1025 | - pos: [1653, 935] | ||
1026 | paintings: | ||
1027 | - clock_painting_3 | ||
1028 | - pos: [369, 605] | ||
1029 | room: Outside The Scientific | ||
1030 | door: Scientific Entrance | ||
1031 | - pos: [294, 602] | ||
1032 | paintings: | ||
1033 | - hi_solved_painting4 | ||
1034 | tags: | ||
1035 | - hi_scientific | ||
1036 | - pos: [814, 1001] | ||
1037 | room: Challenge Room | ||
1038 | door: Welcome Door | ||
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 4fd241a..a7565cf 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp | |||
@@ -21,7 +21,6 @@ | |||
21 | #include <tuple> | 21 | #include <tuple> |
22 | 22 | ||
23 | #include "game_data.h" | 23 | #include "game_data.h" |
24 | #include "logger.h" | ||
25 | #include "tracker_frame.h" | 24 | #include "tracker_frame.h" |
26 | #include "tracker_state.h" | 25 | #include "tracker_state.h" |
27 | 26 | ||
@@ -72,11 +71,12 @@ struct APState { | |||
72 | bool sunwarp_shuffle = false; | 71 | bool sunwarp_shuffle = false; |
73 | 72 | ||
74 | std::map<std::string, std::string> painting_mapping; | 73 | std::map<std::string, std::string> painting_mapping; |
74 | std::set<std::string> painting_codomain; | ||
75 | std::map<int, SunwarpMapping> sunwarp_mapping; | 75 | std::map<int, SunwarpMapping> sunwarp_mapping; |
76 | 76 | ||
77 | void Connect(std::string server, std::string player, std::string password) { | 77 | void Connect(std::string server, std::string player, std::string password) { |
78 | if (!initialized) { | 78 | if (!initialized) { |
79 | TrackerLog("Initializing APState..."); | 79 | wxLogVerbose("Initializing APState..."); |
80 | 80 | ||
81 | std::thread([this]() { | 81 | std::thread([this]() { |
82 | for (;;) { | 82 | for (;;) { |
@@ -104,15 +104,16 @@ struct APState { | |||
104 | } | 104 | } |
105 | 105 | ||
106 | tracked_data_storage_keys.push_back("PlayerPos"); | 106 | tracked_data_storage_keys.push_back("PlayerPos"); |
107 | tracked_data_storage_keys.push_back("Paintings"); | ||
107 | 108 | ||
108 | initialized = true; | 109 | initialized = true; |
109 | } | 110 | } |
110 | 111 | ||
111 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | 112 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); |
112 | TrackerLog("Connecting to Archipelago server (" + server + ")..."); | 113 | wxLogStatus("Connecting to Archipelago server (%s)...", server); |
113 | 114 | ||
114 | { | 115 | { |
115 | TrackerLog("Destroying old AP client..."); | 116 | wxLogVerbose("Destroying old AP client..."); |
116 | 117 | ||
117 | std::lock_guard client_guard(client_mutex); | 118 | std::lock_guard client_guard(client_mutex); |
118 | 119 | ||
@@ -139,6 +140,7 @@ struct APState { | |||
139 | color_shuffle = false; | 140 | color_shuffle = false; |
140 | painting_shuffle = false; | 141 | painting_shuffle = false; |
141 | painting_mapping.clear(); | 142 | painting_mapping.clear(); |
143 | painting_codomain.clear(); | ||
142 | mastery_requirement = 21; | 144 | mastery_requirement = 21; |
143 | level_2_requirement = 223; | 145 | level_2_requirement = 223; |
144 | location_checks = kNORMAL_LOCATIONS; | 146 | location_checks = kNORMAL_LOCATIONS; |
@@ -151,16 +153,17 @@ struct APState { | |||
151 | sunwarp_shuffle = false; | 153 | sunwarp_shuffle = false; |
152 | sunwarp_mapping.clear(); | 154 | sunwarp_mapping.clear(); |
153 | 155 | ||
156 | std::mutex connection_mutex; | ||
154 | connected = false; | 157 | connected = false; |
155 | has_connection_result = false; | 158 | has_connection_result = false; |
156 | 159 | ||
157 | apclient->set_room_info_handler([this, player, password]() { | 160 | apclient->set_room_info_handler([this, player, password]() { |
158 | inventory.clear(); | 161 | inventory.clear(); |
159 | 162 | ||
160 | TrackerLog("Connected to Archipelago server. Authenticating as " + | 163 | wxLogStatus("Connected to Archipelago server. Authenticating as %s %s", |
161 | player + | 164 | player, |
162 | (password.empty() ? " without password" | 165 | (password.empty() ? "without password" |
163 | : " with password " + password)); | 166 | : "with password " + password)); |
164 | tracker_frame->SetStatusMessage( | 167 | tracker_frame->SetStatusMessage( |
165 | "Connected to Archipelago server. Authenticating..."); | 168 | "Connected to Archipelago server. Authenticating..."); |
166 | 169 | ||
@@ -172,23 +175,23 @@ struct APState { | |||
172 | [this](const std::list<int64_t>& locations) { | 175 | [this](const std::list<int64_t>& locations) { |
173 | for (const int64_t location_id : locations) { | 176 | for (const int64_t location_id : locations) { |
174 | checked_locations.insert(location_id); | 177 | checked_locations.insert(location_id); |
175 | TrackerLog("Location: " + std::to_string(location_id)); | 178 | wxLogVerbose("Location: %lld", location_id); |
176 | } | 179 | } |
177 | 180 | ||
178 | RefreshTracker(); | 181 | RefreshTracker(false); |
179 | }); | 182 | }); |
180 | 183 | ||
181 | apclient->set_slot_disconnected_handler([this]() { | 184 | apclient->set_slot_disconnected_handler([this]() { |
182 | tracker_frame->SetStatusMessage( | 185 | tracker_frame->SetStatusMessage( |
183 | "Disconnected from Archipelago. Attempting to reconnect..."); | 186 | "Disconnected from Archipelago. Attempting to reconnect..."); |
184 | TrackerLog( | 187 | wxLogStatus( |
185 | "Slot disconnected from Archipelago. Attempting to reconnect..."); | 188 | "Slot disconnected from Archipelago. Attempting to reconnect..."); |
186 | }); | 189 | }); |
187 | 190 | ||
188 | apclient->set_socket_disconnected_handler([this]() { | 191 | apclient->set_socket_disconnected_handler([this]() { |
189 | tracker_frame->SetStatusMessage( | 192 | tracker_frame->SetStatusMessage( |
190 | "Disconnected from Archipelago. Attempting to reconnect..."); | 193 | "Disconnected from Archipelago. Attempting to reconnect..."); |
191 | TrackerLog( | 194 | wxLogStatus( |
192 | "Socket disconnected from Archipelago. Attempting to reconnect..."); | 195 | "Socket disconnected from Archipelago. Attempting to reconnect..."); |
193 | }); | 196 | }); |
194 | 197 | ||
@@ -196,10 +199,10 @@ struct APState { | |||
196 | [this](const std::list<APClient::NetworkItem>& items) { | 199 | [this](const std::list<APClient::NetworkItem>& items) { |
197 | for (const APClient::NetworkItem& item : items) { | 200 | for (const APClient::NetworkItem& item : items) { |
198 | inventory[item.item]++; | 201 | inventory[item.item]++; |
199 | TrackerLog("Item: " + std::to_string(item.item)); | 202 | wxLogVerbose("Item: %lld", item.item); |
200 | } | 203 | } |
201 | 204 | ||
202 | RefreshTracker(); | 205 | RefreshTracker(false); |
203 | }); | 206 | }); |
204 | 207 | ||
205 | apclient->set_retrieved_handler( | 208 | apclient->set_retrieved_handler( |
@@ -208,20 +211,20 @@ struct APState { | |||
208 | HandleDataStorage(key, value); | 211 | HandleDataStorage(key, value); |
209 | } | 212 | } |
210 | 213 | ||
211 | RefreshTracker(); | 214 | RefreshTracker(false); |
212 | }); | 215 | }); |
213 | 216 | ||
214 | apclient->set_set_reply_handler([this](const std::string& key, | 217 | apclient->set_set_reply_handler([this](const std::string& key, |
215 | const nlohmann::json& value, | 218 | const nlohmann::json& value, |
216 | const nlohmann::json&) { | 219 | const nlohmann::json&) { |
217 | HandleDataStorage(key, value); | 220 | HandleDataStorage(key, value); |
218 | RefreshTracker(); | 221 | RefreshTracker(false); |
219 | }); | 222 | }); |
220 | 223 | ||
221 | apclient->set_slot_connected_handler([this]( | 224 | apclient->set_slot_connected_handler([this, &connection_mutex]( |
222 | const nlohmann::json& slot_data) { | 225 | const nlohmann::json& slot_data) { |
223 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 226 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); |
224 | TrackerLog("Connected to Archipelago!"); | 227 | wxLogStatus("Connected to Archipelago!"); |
225 | 228 | ||
226 | data_storage_prefix = | 229 | data_storage_prefix = |
227 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; | 230 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; |
@@ -266,6 +269,7 @@ struct APState { | |||
266 | for (const auto& mapping_it : | 269 | for (const auto& mapping_it : |
267 | slot_data["painting_entrance_to_exit"].items()) { | 270 | slot_data["painting_entrance_to_exit"].items()) { |
268 | painting_mapping[mapping_it.key()] = mapping_it.value(); | 271 | painting_mapping[mapping_it.key()] = mapping_it.value(); |
272 | painting_codomain.insert(mapping_it.value()); | ||
269 | } | 273 | } |
270 | } | 274 | } |
271 | 275 | ||
@@ -281,11 +285,6 @@ struct APState { | |||
281 | } | 285 | } |
282 | } | 286 | } |
283 | 287 | ||
284 | connected = true; | ||
285 | has_connection_result = true; | ||
286 | |||
287 | RefreshTracker(); | ||
288 | |||
289 | std::list<std::string> corrected_keys; | 288 | std::list<std::string> corrected_keys; |
290 | for (const std::string& key : tracked_data_storage_keys) { | 289 | for (const std::string& key : tracked_data_storage_keys) { |
291 | corrected_keys.push_back(data_storage_prefix + key); | 290 | corrected_keys.push_back(data_storage_prefix + key); |
@@ -302,12 +301,23 @@ struct APState { | |||
302 | 301 | ||
303 | apclient->Get(corrected_keys); | 302 | apclient->Get(corrected_keys); |
304 | apclient->SetNotify(corrected_keys); | 303 | apclient->SetNotify(corrected_keys); |
304 | |||
305 | { | ||
306 | std::lock_guard connection_lock(connection_mutex); | ||
307 | if (!has_connection_result) { | ||
308 | connected = true; | ||
309 | has_connection_result = true; | ||
310 | } | ||
311 | } | ||
305 | }); | 312 | }); |
306 | 313 | ||
307 | apclient->set_slot_refused_handler( | 314 | apclient->set_slot_refused_handler( |
308 | [this](const std::list<std::string>& errors) { | 315 | [this, &connection_mutex](const std::list<std::string>& errors) { |
309 | connected = false; | 316 | { |
310 | has_connection_result = true; | 317 | std::lock_guard connection_lock(connection_mutex); |
318 | connected = false; | ||
319 | has_connection_result = true; | ||
320 | } | ||
311 | 321 | ||
312 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 322 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
313 | 323 | ||
@@ -336,7 +346,7 @@ struct APState { | |||
336 | } | 346 | } |
337 | 347 | ||
338 | std::string full_message = hatkirby::implode(error_messages, " "); | 348 | std::string full_message = hatkirby::implode(error_messages, " "); |
339 | TrackerLog(full_message); | 349 | wxLogError(wxString(full_message)); |
340 | 350 | ||
341 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | 351 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); |
342 | }); | 352 | }); |
@@ -346,18 +356,29 @@ struct APState { | |||
346 | int timeout = 5000; // 5 seconds | 356 | int timeout = 5000; // 5 seconds |
347 | int interval = 100; | 357 | int interval = 100; |
348 | int remaining_loops = timeout / interval; | 358 | int remaining_loops = timeout / interval; |
349 | while (!has_connection_result) { | 359 | while (true) { |
350 | if (interval == 0) { | 360 | { |
351 | connected = false; | 361 | std::lock_guard connection_lock(connection_mutex); |
352 | has_connection_result = true; | 362 | if (has_connection_result) { |
363 | break; | ||
364 | } | ||
365 | } | ||
353 | 366 | ||
367 | if (interval == 0) { | ||
354 | DestroyClient(); | 368 | DestroyClient(); |
355 | 369 | ||
356 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 370 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
357 | 371 | wxLogStatus("Timeout while connecting to Archipelago server."); | |
358 | TrackerLog("Timeout while connecting to Archipelago server."); | ||
359 | wxMessageBox("Timeout while connecting to Archipelago server.", | 372 | wxMessageBox("Timeout while connecting to Archipelago server.", |
360 | "Connection failed", wxOK | wxICON_ERROR); | 373 | "Connection failed", wxOK | wxICON_ERROR); |
374 | |||
375 | { | ||
376 | std::lock_guard connection_lock(connection_mutex); | ||
377 | connected = false; | ||
378 | has_connection_result = true; | ||
379 | } | ||
380 | |||
381 | break; | ||
361 | } | 382 | } |
362 | 383 | ||
363 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | 384 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
@@ -366,7 +387,8 @@ struct APState { | |||
366 | } | 387 | } |
367 | 388 | ||
368 | if (connected) { | 389 | if (connected) { |
369 | RefreshTracker(); | 390 | ResetReachabilityRequirements(); |
391 | RefreshTracker(true); | ||
370 | } else { | 392 | } else { |
371 | client_active = false; | 393 | client_active = false; |
372 | } | 394 | } |
@@ -375,12 +397,11 @@ struct APState { | |||
375 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 397 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { |
376 | if (value.is_boolean()) { | 398 | if (value.is_boolean()) { |
377 | data_storage[key] = value.get<bool>(); | 399 | data_storage[key] = value.get<bool>(); |
378 | TrackerLog("Data storage " + key + " retrieved as " + | 400 | wxLogVerbose("Data storage %s retrieved as %s", key, |
379 | (value.get<bool>() ? "true" : "false")); | 401 | (value.get<bool>() ? "true" : "false")); |
380 | } else if (value.is_number()) { | 402 | } else if (value.is_number()) { |
381 | data_storage[key] = value.get<int>(); | 403 | data_storage[key] = value.get<int>(); |
382 | TrackerLog("Data storage " + key + " retrieved as " + | 404 | wxLogVerbose("Data storage %s retrieved as %d", key, value.get<int>()); |
383 | std::to_string(value.get<int>())); | ||
384 | } else if (value.is_object()) { | 405 | } else if (value.is_object()) { |
385 | if (key.ends_with("PlayerPos")) { | 406 | if (key.ends_with("PlayerPos")) { |
386 | auto map_value = value.get<std::map<std::string, int>>(); | 407 | auto map_value = value.get<std::map<std::string, int>>(); |
@@ -389,7 +410,7 @@ struct APState { | |||
389 | data_storage[key] = value.get<std::map<std::string, int>>(); | 410 | data_storage[key] = value.get<std::map<std::string, int>>(); |
390 | } | 411 | } |
391 | 412 | ||
392 | TrackerLog("Data storage " + key + " retrieved as dictionary"); | 413 | wxLogVerbose("Data storage %s retrieved as dictionary", key); |
393 | } else if (value.is_null()) { | 414 | } else if (value.is_null()) { |
394 | if (key.ends_with("PlayerPos")) { | 415 | if (key.ends_with("PlayerPos")) { |
395 | player_pos = std::nullopt; | 416 | player_pos = std::nullopt; |
@@ -397,7 +418,19 @@ struct APState { | |||
397 | data_storage.erase(key); | 418 | data_storage.erase(key); |
398 | } | 419 | } |
399 | 420 | ||
400 | TrackerLog("Data storage " + key + " retrieved as null"); | 421 | wxLogVerbose("Data storage %s retrieved as null", key); |
422 | } else if (value.is_array()) { | ||
423 | auto list_value = value.get<std::vector<std::string>>(); | ||
424 | |||
425 | if (key.ends_with("Paintings")) { | ||
426 | data_storage[key] = | ||
427 | std::set<std::string>(list_value.begin(), list_value.end()); | ||
428 | } else { | ||
429 | data_storage[key] = list_value; | ||
430 | } | ||
431 | |||
432 | wxLogVerbose("Data storage %s retrieved as list: [%s]", key, | ||
433 | hatkirby::implode(list_value, ", ")); | ||
401 | } | 434 | } |
402 | } | 435 | } |
403 | 436 | ||
@@ -420,22 +453,46 @@ struct APState { | |||
420 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 453 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
421 | } | 454 | } |
422 | 455 | ||
423 | void RefreshTracker() { | 456 | const std::set<std::string>& GetCheckedPaintings() { |
424 | TrackerLog("Refreshing display..."); | 457 | std::string key = data_storage_prefix + "Paintings"; |
458 | if (!data_storage.count(key)) { | ||
459 | data_storage[key] = std::set<std::string>(); | ||
460 | } | ||
461 | |||
462 | return std::any_cast<const std::set<std::string>&>(data_storage.at(key)); | ||
463 | } | ||
464 | |||
465 | bool IsPaintingChecked(const std::string& painting_id) { | ||
466 | const auto& checked_paintings = GetCheckedPaintings(); | ||
467 | |||
468 | return checked_paintings.count(painting_id) || | ||
469 | (painting_mapping.count(painting_id) && | ||
470 | checked_paintings.count(painting_mapping.at(painting_id))); | ||
471 | } | ||
472 | |||
473 | void RefreshTracker(bool reset) { | ||
474 | wxLogVerbose("Refreshing display..."); | ||
425 | 475 | ||
426 | RecalculateReachability(); | 476 | RecalculateReachability(); |
427 | tracker_frame->UpdateIndicators(); | 477 | |
478 | if (reset) { | ||
479 | tracker_frame->ResetIndicators(); | ||
480 | } else { | ||
481 | tracker_frame->UpdateIndicators(); | ||
482 | } | ||
428 | } | 483 | } |
429 | 484 | ||
430 | int64_t GetItemId(const std::string& item_name) { | 485 | int64_t GetItemId(const std::string& item_name) { |
431 | int64_t ap_id = apclient->get_item_id(item_name); | 486 | int64_t ap_id = apclient->get_item_id(item_name); |
432 | if (ap_id == APClient::INVALID_NAME_ID) { | 487 | if (ap_id == APClient::INVALID_NAME_ID) { |
433 | TrackerLog("Could not find AP item ID for " + item_name); | 488 | wxLogError("Could not find AP item ID for %s", item_name); |
434 | } | 489 | } |
435 | 490 | ||
436 | return ap_id; | 491 | return ap_id; |
437 | } | 492 | } |
438 | 493 | ||
494 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | ||
495 | |||
439 | bool HasReachedGoal() { | 496 | bool HasReachedGoal() { |
440 | return data_storage.count(victory_data_storage_key) && | 497 | return data_storage.count(victory_data_storage_key) && |
441 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == | 498 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == |
@@ -474,6 +531,10 @@ bool AP_HasItem(int item_id, int quantity) { | |||
474 | return GetState().HasItem(item_id, quantity); | 531 | return GetState().HasItem(item_id, quantity); |
475 | } | 532 | } |
476 | 533 | ||
534 | std::string AP_GetItemName(int item_id) { | ||
535 | return GetState().GetItemName(item_id); | ||
536 | } | ||
537 | |||
477 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 538 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
478 | 539 | ||
479 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } | 540 | bool AP_AreDoorsGrouped() { return GetState().group_doors; } |
@@ -482,10 +543,22 @@ bool AP_IsColorShuffle() { return GetState().color_shuffle; } | |||
482 | 543 | ||
483 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } | 544 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } |
484 | 545 | ||
485 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | 546 | const std::map<std::string, std::string>& AP_GetPaintingMapping() { |
486 | return GetState().painting_mapping; | 547 | return GetState().painting_mapping; |
487 | } | 548 | } |
488 | 549 | ||
550 | bool AP_IsPaintingMappedTo(const std::string& painting_id) { | ||
551 | return GetState().painting_codomain.count(painting_id); | ||
552 | } | ||
553 | |||
554 | const std::set<std::string>& AP_GetCheckedPaintings() { | ||
555 | return GetState().GetCheckedPaintings(); | ||
556 | } | ||
557 | |||
558 | bool AP_IsPaintingChecked(const std::string& painting_id) { | ||
559 | return GetState().IsPaintingChecked(painting_id); | ||
560 | } | ||
561 | |||
489 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } | 562 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } |
490 | 563 | ||
491 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } | 564 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } |
diff --git a/src/ap_state.h b/src/ap_state.h index c514489..190b21f 100644 --- a/src/ap_state.h +++ b/src/ap_state.h | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | #include <map> | 4 | #include <map> |
5 | #include <optional> | 5 | #include <optional> |
6 | #include <set> | ||
6 | #include <string> | 7 | #include <string> |
7 | #include <tuple> | 8 | #include <tuple> |
8 | 9 | ||
@@ -48,6 +49,8 @@ bool AP_HasCheckedHuntPanel(int location_id); | |||
48 | 49 | ||
49 | bool AP_HasItem(int item_id, int quantity = 1); | 50 | bool AP_HasItem(int item_id, int quantity = 1); |
50 | 51 | ||
52 | std::string AP_GetItemName(int item_id); | ||
53 | |||
51 | DoorShuffleMode AP_GetDoorShuffleMode(); | 54 | DoorShuffleMode AP_GetDoorShuffleMode(); |
52 | 55 | ||
53 | bool AP_AreDoorsGrouped(); | 56 | bool AP_AreDoorsGrouped(); |
@@ -56,7 +59,13 @@ bool AP_IsColorShuffle(); | |||
56 | 59 | ||
57 | bool AP_IsPaintingShuffle(); | 60 | bool AP_IsPaintingShuffle(); |
58 | 61 | ||
59 | const std::map<std::string, std::string> AP_GetPaintingMapping(); | 62 | const std::map<std::string, std::string>& AP_GetPaintingMapping(); |
63 | |||
64 | bool AP_IsPaintingMappedTo(const std::string& painting_id); | ||
65 | |||
66 | const std::set<std::string>& AP_GetCheckedPaintings(); | ||
67 | |||
68 | bool AP_IsPaintingChecked(const std::string& painting_id); | ||
60 | 69 | ||
61 | int AP_GetMasteryRequirement(); | 70 | int AP_GetMasteryRequirement(); |
62 | 71 | ||
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 3b5d8d4..58d8897 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp | |||
@@ -1,5 +1,7 @@ | |||
1 | #include "area_popup.h" | 1 | #include "area_popup.h" |
2 | 2 | ||
3 | #include <wx/dcbuffer.h> | ||
4 | |||
3 | #include "ap_state.h" | 5 | #include "ap_state.h" |
4 | #include "game_data.h" | 6 | #include "game_data.h" |
5 | #include "global.h" | 7 | #include "global.h" |
@@ -8,6 +10,8 @@ | |||
8 | 10 | ||
9 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | 11 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) |
10 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { | 12 | : wxScrolledCanvas(parent, wxID_ANY), area_id_(area_id) { |
13 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
14 | |||
11 | unchecked_eye_ = | 15 | unchecked_eye_ = |
12 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | 16 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), |
13 | wxBITMAP_TYPE_PNG) | 17 | wxBITMAP_TYPE_PNG) |
@@ -61,6 +65,19 @@ void AreaPopup::UpdateIndicators() { | |||
61 | } | 65 | } |
62 | } | 66 | } |
63 | 67 | ||
68 | if (AP_IsPaintingShuffle()) { | ||
69 | for (int painting_id : map_area.paintings) { | ||
70 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
71 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with a friendly name. | ||
72 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
73 | acc_height += item_height; | ||
74 | |||
75 | if (item_extent.GetWidth() > col_width) { | ||
76 | col_width = item_extent.GetWidth(); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
64 | int item_width = col_width + 10 + 32; | 81 | int item_width = col_width + 10 + 32; |
65 | int full_width = std::max(header_extent.GetWidth(), item_width) + 20; | 82 | int full_width = std::max(header_extent.GetWidth(), item_width) + 20; |
66 | 83 | ||
@@ -105,10 +122,31 @@ void AreaPopup::UpdateIndicators() { | |||
105 | 122 | ||
106 | cur_height += 10 + 32; | 123 | cur_height += 10 + 32; |
107 | } | 124 | } |
125 | |||
126 | if (AP_IsPaintingShuffle()) { | ||
127 | for (int painting_id : map_area.paintings) { | ||
128 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
129 | bool checked = AP_IsPaintingChecked(painting.internal_id); | ||
130 | wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; | ||
131 | |||
132 | mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); | ||
133 | |||
134 | bool reachable = IsPaintingReachable(painting_id); | ||
135 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | ||
136 | mem_dc.SetTextForeground(*text_color); | ||
137 | |||
138 | wxSize item_extent = mem_dc.GetTextExtent(painting.internal_id); // TODO: Replace with friendly name. | ||
139 | mem_dc.DrawText(painting.internal_id, | ||
140 | {10 + 32 + 10, | ||
141 | cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); | ||
142 | |||
143 | cur_height += 10 + 32; | ||
144 | } | ||
145 | } | ||
108 | } | 146 | } |
109 | 147 | ||
110 | void AreaPopup::OnPaint(wxPaintEvent& event) { | 148 | void AreaPopup::OnPaint(wxPaintEvent& event) { |
111 | wxPaintDC dc(this); | 149 | wxBufferedPaintDC dc(this); |
112 | PrepareDC(dc); | 150 | PrepareDC(dc); |
113 | dc.DrawBitmap(rendered_, 0, 0); | 151 | dc.DrawBitmap(rendered_, 0, 0); |
114 | 152 | ||
diff --git a/src/game_data.cpp b/src/game_data.cpp index 0567623..1ccf511 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp | |||
@@ -1,5 +1,11 @@ | |||
1 | #include "game_data.h" | 1 | #include "game_data.h" |
2 | 2 | ||
3 | #include <wx/wxprec.h> | ||
4 | |||
5 | #ifndef WX_PRECOMP | ||
6 | #include <wx/wx.h> | ||
7 | #endif | ||
8 | |||
3 | #include <hkutil/string.h> | 9 | #include <hkutil/string.h> |
4 | #include <yaml-cpp/yaml.h> | 10 | #include <yaml-cpp/yaml.h> |
5 | 11 | ||
@@ -7,7 +13,6 @@ | |||
7 | #include <sstream> | 13 | #include <sstream> |
8 | 14 | ||
9 | #include "global.h" | 15 | #include "global.h" |
10 | #include "logger.h" | ||
11 | 16 | ||
12 | namespace { | 17 | namespace { |
13 | 18 | ||
@@ -31,9 +36,7 @@ LingoColor GetColorForString(const std::string &str) { | |||
31 | } else if (str == "purple") { | 36 | } else if (str == "purple") { |
32 | return LingoColor::kPurple; | 37 | return LingoColor::kPurple; |
33 | } else { | 38 | } else { |
34 | std::ostringstream errmsg; | 39 | wxLogError("Invalid color: %s", str); |
35 | errmsg << "Invalid color: " << str; | ||
36 | TrackerLog(errmsg.str()); | ||
37 | 40 | ||
38 | return LingoColor::kNone; | 41 | return LingoColor::kNone; |
39 | } | 42 | } |
@@ -45,12 +48,15 @@ struct GameData { | |||
45 | std::vector<Panel> panels_; | 48 | std::vector<Panel> panels_; |
46 | std::vector<PanelDoor> panel_doors_; | 49 | std::vector<PanelDoor> panel_doors_; |
47 | std::vector<MapArea> map_areas_; | 50 | std::vector<MapArea> map_areas_; |
51 | std::vector<SubwayItem> subway_items_; | ||
52 | std::vector<PaintingExit> paintings_; | ||
48 | 53 | ||
49 | std::map<std::string, int> room_by_id_; | 54 | std::map<std::string, int> room_by_id_; |
50 | std::map<std::string, int> door_by_id_; | 55 | std::map<std::string, int> door_by_id_; |
51 | std::map<std::string, int> panel_by_id_; | 56 | std::map<std::string, int> panel_by_id_; |
52 | std::map<std::string, int> panel_doors_by_id_; | 57 | std::map<std::string, int> panel_doors_by_id_; |
53 | std::map<std::string, int> area_by_id_; | 58 | std::map<std::string, int> area_by_id_; |
59 | std::map<std::string, int> painting_by_id_; | ||
54 | 60 | ||
55 | std::vector<int> door_definition_order_; | 61 | std::vector<int> door_definition_order_; |
56 | 62 | ||
@@ -63,6 +69,9 @@ struct GameData { | |||
63 | 69 | ||
64 | std::vector<int> sunwarp_doors_; | 70 | std::vector<int> sunwarp_doors_; |
65 | 71 | ||
72 | std::map<std::string, int> subway_item_by_painting_; | ||
73 | std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; | ||
74 | |||
66 | bool loaded_area_data_ = false; | 75 | bool loaded_area_data_ = false; |
67 | std::set<std::string> malconfigured_areas_; | 76 | std::set<std::string> malconfigured_areas_; |
68 | 77 | ||
@@ -81,9 +90,7 @@ struct GameData { | |||
81 | ap_id_by_color_[GetColorForString(input_name)] = | 90 | ap_id_by_color_[GetColorForString(input_name)] = |
82 | ids_config["special_items"][color_name].as<int>(); | 91 | ids_config["special_items"][color_name].as<int>(); |
83 | } else { | 92 | } else { |
84 | std::ostringstream errmsg; | 93 | wxLogError("Missing AP item ID for color %s", color_name); |
85 | errmsg << "Missing AP item ID for color " << color_name; | ||
86 | TrackerLog(errmsg.str()); | ||
87 | } | 94 | } |
88 | }; | 95 | }; |
89 | 96 | ||
@@ -158,8 +165,9 @@ struct GameData { | |||
158 | } | 165 | } |
159 | default: { | 166 | default: { |
160 | // This shouldn't happen. | 167 | // This shouldn't happen. |
161 | std::cout << "Error reading game data: " << entrance_it | 168 | std::ostringstream formatted; |
162 | << std::endl; | 169 | formatted << entrance_it; |
170 | wxLogError("Error reading game data: %s", formatted.str()); | ||
163 | break; | 171 | break; |
164 | } | 172 | } |
165 | } | 173 | } |
@@ -256,6 +264,11 @@ struct GameData { | |||
256 | achievement_panels_.push_back(panel_id); | 264 | achievement_panels_.push_back(panel_id); |
257 | } | 265 | } |
258 | 266 | ||
267 | if (panel_it.second["location_name"]) { | ||
268 | panels_[panel_id].location_name = | ||
269 | panel_it.second["location_name"].as<std::string>(); | ||
270 | } | ||
271 | |||
259 | if (panel_it.second["hunt"]) { | 272 | if (panel_it.second["hunt"]) { |
260 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); | 273 | panels_[panel_id].hunt = panel_it.second["hunt"].as<bool>(); |
261 | } | 274 | } |
@@ -279,10 +292,8 @@ struct GameData { | |||
279 | [panels_[panel_id].name] | 292 | [panels_[panel_id].name] |
280 | .as<int>(); | 293 | .as<int>(); |
281 | } else { | 294 | } else { |
282 | std::ostringstream errmsg; | 295 | wxLogError("Missing AP location ID for panel %s - %s", |
283 | errmsg << "Missing AP location ID for panel " | 296 | rooms_[room_id].name, panels_[panel_id].name); |
284 | << rooms_[room_id].name << " - " << panels_[panel_id].name; | ||
285 | TrackerLog(errmsg.str()); | ||
286 | } | 297 | } |
287 | } | 298 | } |
288 | } | 299 | } |
@@ -345,10 +356,8 @@ struct GameData { | |||
345 | [doors_[door_id].name]["item"] | 356 | [doors_[door_id].name]["item"] |
346 | .as<int>(); | 357 | .as<int>(); |
347 | } else { | 358 | } else { |
348 | std::ostringstream errmsg; | 359 | wxLogError("Missing AP item ID for door %s - %s", |
349 | errmsg << "Missing AP item ID for door " << rooms_[room_id].name | 360 | rooms_[room_id].name, doors_[door_id].name); |
350 | << " - " << doors_[door_id].name; | ||
351 | TrackerLog(errmsg.str()); | ||
352 | } | 361 | } |
353 | } | 362 | } |
354 | 363 | ||
@@ -362,10 +371,8 @@ struct GameData { | |||
362 | ids_config["door_groups"][doors_[door_id].group_name] | 371 | ids_config["door_groups"][doors_[door_id].group_name] |
363 | .as<int>(); | 372 | .as<int>(); |
364 | } else { | 373 | } else { |
365 | std::ostringstream errmsg; | 374 | wxLogError("Missing AP item ID for door group %s", |
366 | errmsg << "Missing AP item ID for door group " | 375 | doors_[door_id].group_name); |
367 | << doors_[door_id].group_name; | ||
368 | TrackerLog(errmsg.str()); | ||
369 | } | 376 | } |
370 | } | 377 | } |
371 | 378 | ||
@@ -375,13 +382,11 @@ struct GameData { | |||
375 | } else if (!door_it.second["skip_location"] && | 382 | } else if (!door_it.second["skip_location"] && |
376 | !door_it.second["event"]) { | 383 | !door_it.second["event"]) { |
377 | if (has_external_panels) { | 384 | if (has_external_panels) { |
378 | std::ostringstream errmsg; | 385 | wxLogError( |
379 | errmsg | 386 | "%s - %s has panels from other rooms but does not have an " |
380 | << rooms_[room_id].name << " - " << doors_[door_id].name | 387 | "explicit location name and is not marked skip_location or " |
381 | << " has panels from other rooms but does not have an " | 388 | "event", |
382 | "explicit " | 389 | rooms_[room_id].name, doors_[door_id].name); |
383 | "location name and is not marked skip_location or event"; | ||
384 | TrackerLog(errmsg.str()); | ||
385 | } | 390 | } |
386 | 391 | ||
387 | doors_[door_id].location_name = | 392 | doors_[door_id].location_name = |
@@ -401,10 +406,8 @@ struct GameData { | |||
401 | [doors_[door_id].name]["location"] | 406 | [doors_[door_id].name]["location"] |
402 | .as<int>(); | 407 | .as<int>(); |
403 | } else { | 408 | } else { |
404 | std::ostringstream errmsg; | 409 | wxLogError("Missing AP location ID for door %s - %s", |
405 | errmsg << "Missing AP location ID for door " | 410 | rooms_[room_id].name, doors_[door_id].name); |
406 | << rooms_[room_id].name << " - " << doors_[door_id].name; | ||
407 | TrackerLog(errmsg.str()); | ||
408 | } | 411 | } |
409 | } | 412 | } |
410 | 413 | ||
@@ -478,12 +481,14 @@ struct GameData { | |||
478 | 481 | ||
479 | if (room_it.second["paintings"]) { | 482 | if (room_it.second["paintings"]) { |
480 | for (const auto &painting : room_it.second["paintings"]) { | 483 | for (const auto &painting : room_it.second["paintings"]) { |
481 | std::string painting_id = painting["id"].as<std::string>(); | 484 | std::string internal_id = painting["id"].as<std::string>(); |
482 | room_by_painting_[painting_id] = room_id; | 485 | int painting_id = AddOrGetPainting(internal_id); |
486 | PaintingExit &painting_exit = paintings_[painting_id]; | ||
487 | painting_exit.room = room_id; | ||
483 | 488 | ||
484 | if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { | 489 | if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) && |
485 | PaintingExit painting_exit; | 490 | (!painting["disable"] || !painting["disable"].as<bool>())) { |
486 | painting_exit.id = painting_id; | 491 | painting_exit.entrance = true; |
487 | 492 | ||
488 | if (painting["required_door"]) { | 493 | if (painting["required_door"]) { |
489 | std::string rd_room = rooms_[room_id].name; | 494 | std::string rd_room = rooms_[room_id].name; |
@@ -494,9 +499,9 @@ struct GameData { | |||
494 | painting_exit.door = AddOrGetDoor( | 499 | painting_exit.door = AddOrGetDoor( |
495 | rd_room, painting["required_door"]["door"].as<std::string>()); | 500 | rd_room, painting["required_door"]["door"].as<std::string>()); |
496 | } | 501 | } |
497 | |||
498 | rooms_[room_id].paintings.push_back(painting_exit); | ||
499 | } | 502 | } |
503 | |||
504 | rooms_[room_id].paintings.push_back(painting_exit.id); | ||
500 | } | 505 | } |
501 | } | 506 | } |
502 | 507 | ||
@@ -523,10 +528,8 @@ struct GameData { | |||
523 | progressive_item_id = | 528 | progressive_item_id = |
524 | ids_config["progression"][progressive_item_name].as<int>(); | 529 | ids_config["progression"][progressive_item_name].as<int>(); |
525 | } else { | 530 | } else { |
526 | std::ostringstream errmsg; | 531 | wxLogError("Missing AP item ID for progressive item %s", |
527 | errmsg << "Missing AP item ID for progressive item " | 532 | progressive_item_name); |
528 | << progressive_item_name; | ||
529 | TrackerLog(errmsg.str()); | ||
530 | } | 533 | } |
531 | 534 | ||
532 | if (progression_it.second["doors"]) { | 535 | if (progression_it.second["doors"]) { |
@@ -600,8 +603,21 @@ struct GameData { | |||
600 | std::string room_name = rooms_[room_id].name; | 603 | std::string room_name = rooms_[room_id].name; |
601 | 604 | ||
602 | std::string area_name = room_name; | 605 | std::string area_name = room_name; |
603 | if (fold_areas.count(room_name)) { | 606 | std::string section_name = panel.name; |
604 | int fold_area_id = fold_areas[room_name]; | 607 | std::string location_name = room_name + " - " + panel.name; |
608 | |||
609 | if (!panel.location_name.empty()) { | ||
610 | location_name = panel.location_name; | ||
611 | |||
612 | size_t divider_pos = location_name.find(" - "); | ||
613 | if (divider_pos != std::string::npos) { | ||
614 | area_name = location_name.substr(0, divider_pos); | ||
615 | section_name = location_name.substr(divider_pos + 3); | ||
616 | } | ||
617 | } | ||
618 | |||
619 | if (fold_areas.count(area_name)) { | ||
620 | int fold_area_id = fold_areas[area_name]; | ||
605 | area_name = map_areas_[fold_area_id].name; | 621 | area_name = map_areas_[fold_area_id].name; |
606 | } | 622 | } |
607 | 623 | ||
@@ -617,15 +633,15 @@ struct GameData { | |||
617 | MapArea &map_area = map_areas_[area_id]; | 633 | MapArea &map_area = map_areas_[area_id]; |
618 | // room field should be the original room ID | 634 | // room field should be the original room ID |
619 | map_area.locations.push_back( | 635 | map_area.locations.push_back( |
620 | {.name = panel.name, | 636 | {.name = section_name, |
621 | .ap_location_name = room_name + " - " + panel.name, | 637 | .ap_location_name = location_name, |
622 | .ap_location_id = panel.ap_location_id, | 638 | .ap_location_id = panel.ap_location_id, |
623 | .room = panel.room, | 639 | .room = panel.room, |
624 | .panels = {panel.id}, | 640 | .panels = {panel.id}, |
625 | .classification = classification, | 641 | .classification = classification, |
626 | .hunt = panel.hunt}); | 642 | .hunt = panel.hunt}); |
627 | locations_by_name[map_area.locations.back().ap_location_name] = { | 643 | locations_by_name[location_name] = {area_id, |
628 | area_id, map_area.locations.size() - 1}; | 644 | map_area.locations.size() - 1}; |
629 | } | 645 | } |
630 | 646 | ||
631 | for (int door_id : door_definition_order_) { | 647 | for (int door_id : door_definition_order_) { |
@@ -679,11 +695,101 @@ struct GameData { | |||
679 | } | 695 | } |
680 | } | 696 | } |
681 | 697 | ||
698 | for (const Room &room : rooms_) { | ||
699 | std::string area_name = room.name; | ||
700 | if (fold_areas.count(room.name)) { | ||
701 | int fold_area_id = fold_areas[room.name]; | ||
702 | area_name = map_areas_[fold_area_id].name; | ||
703 | } | ||
704 | |||
705 | if (!room.paintings.empty()) { | ||
706 | int area_id = AddOrGetArea(area_name); | ||
707 | MapArea &map_area = map_areas_[area_id]; | ||
708 | |||
709 | for (int painting_id : room.paintings) { | ||
710 | const PaintingExit &painting_obj = paintings_.at(painting_id); | ||
711 | if (painting_obj.entrance) { | ||
712 | map_area.paintings.push_back(painting_id); | ||
713 | } | ||
714 | } | ||
715 | } | ||
716 | } | ||
717 | |||
682 | // Report errors. | 718 | // Report errors. |
683 | for (const std::string &area : malconfigured_areas_) { | 719 | for (const std::string &area : malconfigured_areas_) { |
684 | std::ostringstream errstr; | 720 | wxLogError("Area data not found for: %s", area); |
685 | errstr << "Area data not found for: " << area; | 721 | } |
686 | TrackerLog(errstr.str()); | 722 | |
723 | // Read in subway items. | ||
724 | YAML::Node subway_config = | ||
725 | YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); | ||
726 | for (const auto &subway_it : subway_config) { | ||
727 | SubwayItem subway_item; | ||
728 | subway_item.id = subway_items_.size(); | ||
729 | subway_item.x = subway_it["pos"][0].as<int>(); | ||
730 | subway_item.y = subway_it["pos"][1].as<int>(); | ||
731 | |||
732 | if (subway_it["door"]) { | ||
733 | subway_item.door = AddOrGetDoor(subway_it["room"].as<std::string>(), | ||
734 | subway_it["door"].as<std::string>()); | ||
735 | } | ||
736 | |||
737 | if (subway_it["paintings"]) { | ||
738 | for (const auto &painting_it : subway_it["paintings"]) { | ||
739 | std::string painting_id = painting_it.as<std::string>(); | ||
740 | |||
741 | subway_item.paintings.push_back(painting_id); | ||
742 | subway_item_by_painting_[painting_id] = subway_item.id; | ||
743 | } | ||
744 | } | ||
745 | |||
746 | if (subway_it["tags"]) { | ||
747 | for (const auto &tag_it : subway_it["tags"]) { | ||
748 | subway_item.tags.push_back(tag_it.as<std::string>()); | ||
749 | } | ||
750 | } | ||
751 | |||
752 | if (subway_it["sunwarp"]) { | ||
753 | SubwaySunwarp sunwarp; | ||
754 | sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); | ||
755 | |||
756 | std::string sunwarp_type = | ||
757 | subway_it["sunwarp"]["type"].as<std::string>(); | ||
758 | if (sunwarp_type == "final") { | ||
759 | sunwarp.type = SubwaySunwarpType::kFinal; | ||
760 | } else if (sunwarp_type == "exit") { | ||
761 | sunwarp.type = SubwaySunwarpType::kExit; | ||
762 | } else { | ||
763 | sunwarp.type = SubwaySunwarpType::kEnter; | ||
764 | } | ||
765 | |||
766 | subway_item.sunwarp = sunwarp; | ||
767 | |||
768 | subway_item_by_sunwarp_[sunwarp] = subway_item.id; | ||
769 | |||
770 | subway_item.door = | ||
771 | AddOrGetDoor("Sunwarps", std::to_string(sunwarp.dots) + " Sunwarp"); | ||
772 | } | ||
773 | |||
774 | if (subway_it["special"]) { | ||
775 | subway_item.special = subway_it["special"].as<std::string>(); | ||
776 | } | ||
777 | |||
778 | subway_items_.push_back(subway_item); | ||
779 | } | ||
780 | |||
781 | // Find singleton subway tags. | ||
782 | std::map<std::string, std::set<int>> subway_tags; | ||
783 | for (const SubwayItem &subway_item : subway_items_) { | ||
784 | for (const std::string &tag : subway_item.tags) { | ||
785 | subway_tags[tag].insert(subway_item.id); | ||
786 | } | ||
787 | } | ||
788 | |||
789 | for (const auto &[tag, items] : subway_tags) { | ||
790 | if (items.size() == 1) { | ||
791 | wxLogWarning("Singleton subway item tag: %s", tag); | ||
792 | } | ||
687 | } | 793 | } |
688 | } | 794 | } |
689 | 795 | ||
@@ -700,8 +806,10 @@ struct GameData { | |||
700 | std::string full_name = room + " - " + door; | 806 | std::string full_name = room + " - " + door; |
701 | 807 | ||
702 | if (!door_by_id_.count(full_name)) { | 808 | if (!door_by_id_.count(full_name)) { |
809 | int door_id = doors_.size(); | ||
703 | door_by_id_[full_name] = doors_.size(); | 810 | door_by_id_[full_name] = doors_.size(); |
704 | doors_.push_back({.room = AddOrGetRoom(room), .name = door}); | 811 | doors_.push_back( |
812 | {.id = door_id, .room = AddOrGetRoom(room), .name = door}); | ||
705 | } | 813 | } |
706 | 814 | ||
707 | return door_by_id_[full_name]; | 815 | return door_by_id_[full_name]; |
@@ -745,6 +853,16 @@ struct GameData { | |||
745 | 853 | ||
746 | return area_by_id_[area]; | 854 | return area_by_id_[area]; |
747 | } | 855 | } |
856 | |||
857 | int AddOrGetPainting(std::string internal_id) { | ||
858 | if (!painting_by_id_.count(internal_id)) { | ||
859 | int painting_id = paintings_.size(); | ||
860 | painting_by_id_[internal_id] = painting_id; | ||
861 | paintings_.push_back({.id = painting_id, .internal_id = internal_id}); | ||
862 | } | ||
863 | |||
864 | return painting_by_id_[internal_id]; | ||
865 | } | ||
748 | }; | 866 | }; |
749 | 867 | ||
750 | GameData &GetState() { | 868 | GameData &GetState() { |
@@ -754,6 +872,10 @@ GameData &GetState() { | |||
754 | 872 | ||
755 | } // namespace | 873 | } // namespace |
756 | 874 | ||
875 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { | ||
876 | return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); | ||
877 | } | ||
878 | |||
757 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } | 879 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } |
758 | 880 | ||
759 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } | 881 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } |
@@ -780,8 +902,12 @@ const Panel &GD_GetPanel(int panel_id) { | |||
780 | return GetState().panels_.at(panel_id); | 902 | return GetState().panels_.at(panel_id); |
781 | } | 903 | } |
782 | 904 | ||
783 | int GD_GetRoomForPainting(const std::string &painting_id) { | 905 | const PaintingExit &GD_GetPaintingExit(int painting_id) { |
784 | return GetState().room_by_painting_.at(painting_id); | 906 | return GetState().paintings_.at(painting_id); |
907 | } | ||
908 | |||
909 | int GD_GetPaintingByName(const std::string &name) { | ||
910 | return GetState().painting_by_id_.at(name); | ||
785 | } | 911 | } |
786 | 912 | ||
787 | const std::vector<int> &GD_GetAchievementPanels() { | 913 | const std::vector<int> &GD_GetAchievementPanels() { |
@@ -799,3 +925,24 @@ const std::vector<int> &GD_GetSunwarpDoors() { | |||
799 | int GD_GetRoomForSunwarp(int index) { | 925 | int GD_GetRoomForSunwarp(int index) { |
800 | return GetState().room_by_sunwarp_.at(index); | 926 | return GetState().room_by_sunwarp_.at(index); |
801 | } | 927 | } |
928 | |||
929 | const std::vector<SubwayItem> &GD_GetSubwayItems() { | ||
930 | return GetState().subway_items_; | ||
931 | } | ||
932 | |||
933 | const SubwayItem &GD_GetSubwayItem(int id) { | ||
934 | return GetState().subway_items_.at(id); | ||
935 | } | ||
936 | |||
937 | int GD_GetSubwayItemForPainting(const std::string &painting_id) { | ||
938 | #ifndef NDEBUG | ||
939 | if (!GetState().subway_item_by_painting_.count(painting_id)) { | ||
940 | wxLogError("No subway item for painting %s", painting_id); | ||
941 | } | ||
942 | #endif | ||
943 | return GetState().subway_item_by_painting_.at(painting_id); | ||
944 | } | ||
945 | |||
946 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { | ||
947 | return GetState().subway_item_by_sunwarp_.at(sunwarp); | ||
948 | } | ||
diff --git a/src/game_data.h b/src/game_data.h index 09824d7..aca4c3d 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
@@ -50,6 +50,7 @@ struct Panel { | |||
50 | bool exclude_reduce = false; | 50 | bool exclude_reduce = false; |
51 | bool achievement = false; | 51 | bool achievement = false; |
52 | std::string achievement_name; | 52 | std::string achievement_name; |
53 | std::string location_name; | ||
53 | bool non_counting = false; | 54 | bool non_counting = false; |
54 | int ap_location_id = -1; | 55 | int ap_location_id = -1; |
55 | bool hunt = false; | 56 | bool hunt = false; |
@@ -63,6 +64,7 @@ struct ProgressiveRequirement { | |||
63 | }; | 64 | }; |
64 | 65 | ||
65 | struct Door { | 66 | struct Door { |
67 | int id; | ||
66 | int room; | 68 | int room; |
67 | std::string name; | 69 | std::string name; |
68 | std::string location_name; | 70 | std::string location_name; |
@@ -93,14 +95,17 @@ struct Exit { | |||
93 | }; | 95 | }; |
94 | 96 | ||
95 | struct PaintingExit { | 97 | struct PaintingExit { |
96 | std::string id; | 98 | int id; |
99 | int room; | ||
100 | std::string internal_id; | ||
97 | std::optional<int> door; | 101 | std::optional<int> door; |
102 | bool entrance = false; | ||
98 | }; | 103 | }; |
99 | 104 | ||
100 | struct Room { | 105 | struct Room { |
101 | std::string name; | 106 | std::string name; |
102 | std::vector<Exit> exits; | 107 | std::vector<Exit> exits; |
103 | std::vector<PaintingExit> paintings; | 108 | std::vector<int> paintings; |
104 | std::vector<int> sunwarps; | 109 | std::vector<int> sunwarps; |
105 | std::vector<int> panels; | 110 | std::vector<int> panels; |
106 | }; | 111 | }; |
@@ -119,12 +124,37 @@ struct MapArea { | |||
119 | int id; | 124 | int id; |
120 | std::string name; | 125 | std::string name; |
121 | std::vector<Location> locations; | 126 | std::vector<Location> locations; |
127 | std::vector<int> paintings; | ||
122 | int map_x; | 128 | int map_x; |
123 | int map_y; | 129 | int map_y; |
124 | int classification = 0; | 130 | int classification = 0; |
125 | bool hunt = false; | 131 | bool hunt = false; |
126 | }; | 132 | }; |
127 | 133 | ||
134 | enum class SubwaySunwarpType { | ||
135 | kEnter, | ||
136 | kExit, | ||
137 | kFinal | ||
138 | }; | ||
139 | |||
140 | struct SubwaySunwarp { | ||
141 | int dots; | ||
142 | SubwaySunwarpType type; | ||
143 | |||
144 | bool operator<(const SubwaySunwarp& rhs) const; | ||
145 | }; | ||
146 | |||
147 | struct SubwayItem { | ||
148 | int id; | ||
149 | int x; | ||
150 | int y; | ||
151 | std::optional<int> door; | ||
152 | std::vector<std::string> paintings; | ||
153 | std::vector<std::string> tags; | ||
154 | std::optional<SubwaySunwarp> sunwarp; | ||
155 | std::optional<std::string> special; | ||
156 | }; | ||
157 | |||
128 | const std::vector<MapArea>& GD_GetMapAreas(); | 158 | const std::vector<MapArea>& GD_GetMapAreas(); |
129 | const MapArea& GD_GetMapArea(int id); | 159 | const MapArea& GD_GetMapArea(int id); |
130 | int GD_GetRoomByName(const std::string& name); | 160 | int GD_GetRoomByName(const std::string& name); |
@@ -134,10 +164,15 @@ const Door& GD_GetDoor(int door_id); | |||
134 | int GD_GetDoorByName(const std::string& name); | 164 | int GD_GetDoorByName(const std::string& name); |
135 | const Panel& GD_GetPanel(int panel_id); | 165 | const Panel& GD_GetPanel(int panel_id); |
136 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); | 166 | const PanelDoor& GD_GetPanelDoor(int panel_door_id); |
137 | int GD_GetRoomForPainting(const std::string& painting_id); | 167 | const PaintingExit& GD_GetPaintingExit(int painting_id); |
168 | int GD_GetPaintingByName(const std::string& name); | ||
138 | const std::vector<int>& GD_GetAchievementPanels(); | 169 | const std::vector<int>& GD_GetAchievementPanels(); |
139 | int GD_GetItemIdForColor(LingoColor color); | 170 | int GD_GetItemIdForColor(LingoColor color); |
140 | const std::vector<int>& GD_GetSunwarpDoors(); | 171 | const std::vector<int>& GD_GetSunwarpDoors(); |
141 | int GD_GetRoomForSunwarp(int index); | 172 | int GD_GetRoomForSunwarp(int index); |
173 | const std::vector<SubwayItem>& GD_GetSubwayItems(); | ||
174 | const SubwayItem& GD_GetSubwayItem(int id); | ||
175 | int GD_GetSubwayItemForPainting(const std::string& painting_id); | ||
176 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); | ||
142 | 177 | ||
143 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | 178 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ |
diff --git a/src/logger.cpp b/src/logger.cpp deleted file mode 100644 index 4b722c8..0000000 --- a/src/logger.cpp +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | #include "logger.h" | ||
2 | |||
3 | #include <chrono> | ||
4 | #include <fstream> | ||
5 | #include <mutex> | ||
6 | |||
7 | #include "global.h" | ||
8 | |||
9 | namespace { | ||
10 | |||
11 | class Logger { | ||
12 | public: | ||
13 | Logger() : logfile_(GetAbsolutePath("debug.log")) {} | ||
14 | |||
15 | void LogLine(const std::string& text) { | ||
16 | std::lock_guard guard(file_mutex_); | ||
17 | logfile_ << "[" << std::chrono::system_clock::now() << "] " << text | ||
18 | << std::endl; | ||
19 | logfile_.flush(); | ||
20 | } | ||
21 | |||
22 | private: | ||
23 | std::ofstream logfile_; | ||
24 | std::mutex file_mutex_; | ||
25 | }; | ||
26 | |||
27 | } // namespace | ||
28 | |||
29 | void TrackerLog(const std::string& text) { | ||
30 | static Logger* instance = new Logger(); | ||
31 | instance->LogLine(text); | ||
32 | } | ||
diff --git a/src/logger.h b/src/logger.h deleted file mode 100644 index db9bb49..0000000 --- a/src/logger.h +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | #ifndef LOGGER_H_6E7B9594 | ||
2 | #define LOGGER_H_6E7B9594 | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | void TrackerLog(const std::string& text); | ||
7 | |||
8 | #endif /* end of include guard: LOGGER_H_6E7B9594 */ | ||
diff --git a/src/main.cpp b/src/main.cpp index fe9aceb..b327b25 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
@@ -4,18 +4,41 @@ | |||
4 | #include <wx/wx.h> | 4 | #include <wx/wx.h> |
5 | #endif | 5 | #endif |
6 | 6 | ||
7 | #include <fstream> | ||
8 | |||
9 | #include "global.h" | ||
7 | #include "tracker_config.h" | 10 | #include "tracker_config.h" |
8 | #include "tracker_frame.h" | 11 | #include "tracker_frame.h" |
9 | 12 | ||
13 | static std::ofstream* logfile; | ||
14 | |||
10 | class TrackerApp : public wxApp { | 15 | class TrackerApp : public wxApp { |
11 | public: | 16 | public: |
12 | virtual bool OnInit() { | 17 | virtual bool OnInit() { |
18 | logfile = new std::ofstream(GetAbsolutePath("debug.log")); | ||
19 | wxLog::SetActiveTarget(new wxLogStream(logfile)); | ||
20 | wxLog::SetVerbose(true); | ||
21 | |||
22 | #ifndef NDEBUG | ||
23 | wxLog::SetActiveTarget(new wxLogWindow(nullptr, "Debug Log")); | ||
24 | #endif | ||
25 | |||
13 | GetTrackerConfig().Load(); | 26 | GetTrackerConfig().Load(); |
14 | 27 | ||
15 | TrackerFrame *frame = new TrackerFrame(); | 28 | TrackerFrame *frame = new TrackerFrame(); |
16 | frame->Show(true); | 29 | frame->Show(true); |
17 | return true; | 30 | return true; |
18 | } | 31 | } |
32 | |||
33 | bool OnExceptionInMainLoop() override { | ||
34 | try { | ||
35 | throw; | ||
36 | } catch (const std::exception& ex) { | ||
37 | wxLogError(ex.what()); | ||
38 | } | ||
39 | |||
40 | return false; | ||
41 | } | ||
19 | }; | 42 | }; |
20 | 43 | ||
21 | wxIMPLEMENT_APP(TrackerApp); | 44 | wxIMPLEMENT_APP(TrackerApp); |
diff --git a/src/network_set.cpp b/src/network_set.cpp new file mode 100644 index 0000000..6d2a098 --- /dev/null +++ b/src/network_set.cpp | |||
@@ -0,0 +1,30 @@ | |||
1 | #include "network_set.h" | ||
2 | |||
3 | void NetworkSet::Clear() { | ||
4 | network_by_item_.clear(); | ||
5 | } | ||
6 | |||
7 | void NetworkSet::AddLink(int id1, int id2) { | ||
8 | if (id2 > id1) { | ||
9 | // Make sure id1 < id2 | ||
10 | std::swap(id1, id2); | ||
11 | } | ||
12 | |||
13 | if (!network_by_item_.count(id1)) { | ||
14 | network_by_item_[id1] = {}; | ||
15 | } | ||
16 | if (!network_by_item_.count(id2)) { | ||
17 | network_by_item_[id2] = {}; | ||
18 | } | ||
19 | |||
20 | network_by_item_[id1].insert({id1, id2}); | ||
21 | network_by_item_[id2].insert({id1, id2}); | ||
22 | } | ||
23 | |||
24 | bool NetworkSet::IsItemInNetwork(int id) const { | ||
25 | return network_by_item_.count(id); | ||
26 | } | ||
27 | |||
28 | const std::set<std::pair<int, int>>& NetworkSet::GetNetworkGraph(int id) const { | ||
29 | return network_by_item_.at(id); | ||
30 | } | ||
diff --git a/src/network_set.h b/src/network_set.h new file mode 100644 index 0000000..e6f0c07 --- /dev/null +++ b/src/network_set.h | |||
@@ -0,0 +1,25 @@ | |||
1 | #ifndef NETWORK_SET_H_3036B8E3 | ||
2 | #define NETWORK_SET_H_3036B8E3 | ||
3 | |||
4 | #include <map> | ||
5 | #include <optional> | ||
6 | #include <set> | ||
7 | #include <utility> | ||
8 | #include <vector> | ||
9 | |||
10 | class NetworkSet { | ||
11 | public: | ||
12 | void Clear(); | ||
13 | |||
14 | void AddLink(int id1, int id2); | ||
15 | |||
16 | bool IsItemInNetwork(int id) const; | ||
17 | |||
18 | const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; | ||
19 | |||
20 | private: | ||
21 | |||
22 | std::map<int, std::set<std::pair<int, int>>> network_by_item_; | ||
23 | }; | ||
24 | |||
25 | #endif /* end of include guard: NETWORK_SET_H_3036B8E3 */ | ||
diff --git a/src/subway_map.cpp b/src/subway_map.cpp new file mode 100644 index 0000000..408c4f0 --- /dev/null +++ b/src/subway_map.cpp | |||
@@ -0,0 +1,728 @@ | |||
1 | #include "subway_map.h" | ||
2 | |||
3 | #include <wx/dcbuffer.h> | ||
4 | #include <wx/dcgraph.h> | ||
5 | |||
6 | #include <sstream> | ||
7 | |||
8 | #include "ap_state.h" | ||
9 | #include "game_data.h" | ||
10 | #include "global.h" | ||
11 | #include "tracker_state.h" | ||
12 | |||
13 | constexpr int AREA_ACTUAL_SIZE = 21; | ||
14 | constexpr int OWL_ACTUAL_SIZE = 32; | ||
15 | |||
16 | enum class ItemDrawType { kNone, kBox, kOwl }; | ||
17 | |||
18 | SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | ||
19 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
20 | |||
21 | map_image_ = | ||
22 | wxImage(GetAbsolutePath("assets/subway.png").c_str(), wxBITMAP_TYPE_PNG); | ||
23 | if (!map_image_.IsOk()) { | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | owl_image_ = | ||
28 | wxImage(GetAbsolutePath("assets/owl.png").c_str(), wxBITMAP_TYPE_PNG); | ||
29 | if (!owl_image_.IsOk()) { | ||
30 | return; | ||
31 | } | ||
32 | |||
33 | unchecked_eye_ = | ||
34 | wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), | ||
35 | wxBITMAP_TYPE_PNG) | ||
36 | .Scale(32, 32)); | ||
37 | checked_eye_ = wxBitmap( | ||
38 | wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) | ||
39 | .Scale(32, 32)); | ||
40 | |||
41 | tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( | ||
42 | quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), | ||
43 | static_cast<float>(map_image_.GetHeight())}); | ||
44 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
45 | tree_->add(subway_item.id); | ||
46 | } | ||
47 | |||
48 | Redraw(); | ||
49 | |||
50 | scroll_timer_ = new wxTimer(this); | ||
51 | |||
52 | Bind(wxEVT_PAINT, &SubwayMap::OnPaint, this); | ||
53 | Bind(wxEVT_MOTION, &SubwayMap::OnMouseMove, this); | ||
54 | Bind(wxEVT_MOUSEWHEEL, &SubwayMap::OnMouseScroll, this); | ||
55 | Bind(wxEVT_LEAVE_WINDOW, &SubwayMap::OnMouseLeave, this); | ||
56 | Bind(wxEVT_LEFT_DOWN, &SubwayMap::OnMouseClick, this); | ||
57 | Bind(wxEVT_TIMER, &SubwayMap::OnTimer, this); | ||
58 | |||
59 | zoom_slider_ = new wxSlider(this, wxID_ANY, 0, 0, 8, {15, 15}); | ||
60 | zoom_slider_->Bind(wxEVT_SLIDER, &SubwayMap::OnZoomSlide, this); | ||
61 | |||
62 | help_button_ = new wxButton(this, wxID_ANY, "Help"); | ||
63 | help_button_->Bind(wxEVT_BUTTON, &SubwayMap::OnClickHelp, this); | ||
64 | SetUpHelpButton(); | ||
65 | } | ||
66 | |||
67 | void SubwayMap::OnConnect() { | ||
68 | networks_.Clear(); | ||
69 | |||
70 | std::map<std::string, std::vector<int>> tagged; | ||
71 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
72 | if (AP_HasEarlyColorHallways() && | ||
73 | (subway_item.special == "starting_room_paintings" || | ||
74 | subway_item.special == "early_color_hallways")) { | ||
75 | tagged["early_color_hallways"].push_back(subway_item.id); | ||
76 | } | ||
77 | |||
78 | if (AP_IsPaintingShuffle() && !subway_item.paintings.empty()) { | ||
79 | continue; | ||
80 | } | ||
81 | |||
82 | for (const std::string &tag : subway_item.tags) { | ||
83 | tagged[tag].push_back(subway_item.id); | ||
84 | } | ||
85 | |||
86 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && | ||
87 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | ||
88 | std::ostringstream tag; | ||
89 | tag << "sunwarp" << subway_item.sunwarp->dots; | ||
90 | |||
91 | tagged[tag.str()].push_back(subway_item.id); | ||
92 | } | ||
93 | |||
94 | if (!AP_IsPilgrimageEnabled() && | ||
95 | (subway_item.special == "sun_painting" || | ||
96 | subway_item.special == "sun_painting_exit")) { | ||
97 | tagged["sun_painting"].push_back(subway_item.id); | ||
98 | } | ||
99 | } | ||
100 | |||
101 | if (AP_IsSunwarpShuffle()) { | ||
102 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { | ||
103 | std::ostringstream tag; | ||
104 | tag << "sunwarp" << mapping.dots; | ||
105 | |||
106 | SubwaySunwarp fromWarp; | ||
107 | if (index < 6) { | ||
108 | fromWarp.dots = index + 1; | ||
109 | fromWarp.type = SubwaySunwarpType::kEnter; | ||
110 | } else { | ||
111 | fromWarp.dots = index - 5; | ||
112 | fromWarp.type = SubwaySunwarpType::kExit; | ||
113 | } | ||
114 | |||
115 | SubwaySunwarp toWarp; | ||
116 | if (mapping.exit_index < 6) { | ||
117 | toWarp.dots = mapping.exit_index + 1; | ||
118 | toWarp.type = SubwaySunwarpType::kEnter; | ||
119 | } else { | ||
120 | toWarp.dots = mapping.exit_index - 5; | ||
121 | toWarp.type = SubwaySunwarpType::kExit; | ||
122 | } | ||
123 | |||
124 | tagged[tag.str()].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | ||
125 | tagged[tag.str()].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | for (const auto &[tag, items] : tagged) { | ||
130 | // Pairwise connect all items with the same tag. | ||
131 | for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end(); | ||
132 | tag_it1++) { | ||
133 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | ||
134 | tag_it2++) { | ||
135 | networks_.AddLink(*tag_it1, *tag_it2); | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | |||
140 | checked_paintings_.clear(); | ||
141 | } | ||
142 | |||
143 | void SubwayMap::UpdateIndicators() { | ||
144 | if (AP_IsPaintingShuffle()) { | ||
145 | for (const std::string &painting_id : AP_GetCheckedPaintings()) { | ||
146 | if (!checked_paintings_.count(painting_id)) { | ||
147 | checked_paintings_.insert(painting_id); | ||
148 | |||
149 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
150 | networks_.AddLink(GD_GetSubwayItemForPainting(painting_id), | ||
151 | GD_GetSubwayItemForPainting( | ||
152 | AP_GetPaintingMapping().at(painting_id))); | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
158 | Redraw(); | ||
159 | } | ||
160 | |||
161 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | ||
162 | SubwaySunwarp to_sunwarp) { | ||
163 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | ||
164 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | ||
165 | } | ||
166 | |||
167 | void SubwayMap::Zoom(bool in) { | ||
168 | wxPoint focus_point; | ||
169 | |||
170 | if (mouse_position_) { | ||
171 | focus_point = *mouse_position_; | ||
172 | } else { | ||
173 | focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; | ||
174 | } | ||
175 | |||
176 | if (in) { | ||
177 | if (zoom_ < 3.0) { | ||
178 | SetZoom(zoom_ + 0.25, focus_point); | ||
179 | } | ||
180 | } else { | ||
181 | if (zoom_ > 1.0) { | ||
182 | SetZoom(zoom_ - 0.25, focus_point); | ||
183 | } | ||
184 | } | ||
185 | } | ||
186 | |||
187 | void SubwayMap::OnPaint(wxPaintEvent &event) { | ||
188 | if (GetSize() != rendered_.GetSize()) { | ||
189 | wxSize panel_size = GetSize(); | ||
190 | wxSize image_size = map_image_.GetSize(); | ||
191 | |||
192 | render_x_ = 0; | ||
193 | render_y_ = 0; | ||
194 | render_width_ = panel_size.GetWidth(); | ||
195 | render_height_ = panel_size.GetHeight(); | ||
196 | |||
197 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
198 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
199 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
200 | image_size.GetWidth(); | ||
201 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
202 | } else { | ||
203 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
204 | image_size.GetHeight(); | ||
205 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | ||
206 | } | ||
207 | |||
208 | SetZoomPos({zoom_x_, zoom_y_}); | ||
209 | |||
210 | SetUpHelpButton(); | ||
211 | } | ||
212 | |||
213 | wxBufferedPaintDC dc(this); | ||
214 | dc.SetBackground(*wxWHITE_BRUSH); | ||
215 | dc.Clear(); | ||
216 | |||
217 | { | ||
218 | wxMemoryDC rendered_dc; | ||
219 | rendered_dc.SelectObject(rendered_); | ||
220 | |||
221 | int dst_x; | ||
222 | int dst_y; | ||
223 | int dst_w; | ||
224 | int dst_h; | ||
225 | int src_x; | ||
226 | int src_y; | ||
227 | int src_w; | ||
228 | int src_h; | ||
229 | |||
230 | int zoomed_width = render_width_ * zoom_; | ||
231 | int zoomed_height = render_height_ * zoom_; | ||
232 | |||
233 | if (zoomed_width <= GetSize().GetWidth()) { | ||
234 | dst_x = (GetSize().GetWidth() - zoomed_width) / 2; | ||
235 | dst_w = zoomed_width; | ||
236 | src_x = 0; | ||
237 | src_w = map_image_.GetWidth(); | ||
238 | } else { | ||
239 | dst_x = 0; | ||
240 | dst_w = GetSize().GetWidth(); | ||
241 | src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
242 | src_w = | ||
243 | GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; | ||
244 | } | ||
245 | |||
246 | if (zoomed_height <= GetSize().GetHeight()) { | ||
247 | dst_y = (GetSize().GetHeight() - zoomed_height) / 2; | ||
248 | dst_h = zoomed_height; | ||
249 | src_y = 0; | ||
250 | src_h = map_image_.GetHeight(); | ||
251 | } else { | ||
252 | dst_y = 0; | ||
253 | dst_h = GetSize().GetHeight(); | ||
254 | src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
255 | src_h = | ||
256 | GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; | ||
257 | } | ||
258 | |||
259 | wxGCDC gcdc(dc); | ||
260 | gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); | ||
261 | gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, | ||
262 | src_w, src_h); | ||
263 | } | ||
264 | |||
265 | if (hovered_item_) { | ||
266 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
267 | // knows when an item has a hover effect. | ||
268 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
269 | if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { | ||
270 | const std::map<std::string, bool> &report = | ||
271 | GetDoorRequirements(*subway_item.door); | ||
272 | |||
273 | int acc_height = 10; | ||
274 | int col_width = 0; | ||
275 | |||
276 | for (const auto &[text, obtained] : report) { | ||
277 | wxSize item_extent = dc.GetTextExtent(text); | ||
278 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
279 | acc_height += item_height; | ||
280 | |||
281 | if (item_extent.GetWidth() > col_width) { | ||
282 | col_width = item_extent.GetWidth(); | ||
283 | } | ||
284 | } | ||
285 | |||
286 | int item_width = col_width + 10 + 32; | ||
287 | int full_width = item_width + 20; | ||
288 | |||
289 | wxPoint popup_pos = | ||
290 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
291 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
292 | |||
293 | if (popup_pos.x + full_width > GetSize().GetWidth()) { | ||
294 | popup_pos.x = GetSize().GetWidth() - full_width; | ||
295 | } | ||
296 | if (popup_pos.y + acc_height > GetSize().GetHeight()) { | ||
297 | popup_pos.y = GetSize().GetHeight() - acc_height; | ||
298 | } | ||
299 | |||
300 | dc.SetPen(*wxTRANSPARENT_PEN); | ||
301 | dc.SetBrush(*wxBLACK_BRUSH); | ||
302 | dc.DrawRectangle(popup_pos, {full_width, acc_height}); | ||
303 | |||
304 | dc.SetFont(GetFont()); | ||
305 | |||
306 | int cur_height = 10; | ||
307 | |||
308 | for (const auto &[text, obtained] : report) { | ||
309 | wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; | ||
310 | |||
311 | dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); | ||
312 | |||
313 | dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
314 | wxSize item_extent = dc.GetTextExtent(text); | ||
315 | dc.DrawText( | ||
316 | text, | ||
317 | popup_pos + | ||
318 | wxPoint{10 + 32 + 10, | ||
319 | cur_height + (32 - dc.GetFontMetrics().height) / 2}); | ||
320 | |||
321 | cur_height += 10 + 32; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | if (networks_.IsItemInNetwork(*hovered_item_)) { | ||
326 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
327 | |||
328 | for (const auto &[item_id1, item_id2] : | ||
329 | networks_.GetNetworkGraph(*hovered_item_)) { | ||
330 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | ||
331 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
332 | |||
333 | wxPoint item1_pos = MapPosToRenderPos( | ||
334 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | ||
335 | wxPoint item2_pos = MapPosToRenderPos( | ||
336 | {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); | ||
337 | |||
338 | int left = std::min(item1_pos.x, item2_pos.x); | ||
339 | int top = std::min(item1_pos.y, item2_pos.y); | ||
340 | int right = std::max(item1_pos.x, item2_pos.x); | ||
341 | int bottom = std::max(item1_pos.y, item2_pos.y); | ||
342 | |||
343 | int halfwidth = right - left; | ||
344 | int halfheight = bottom - top; | ||
345 | |||
346 | if (halfwidth < 4 || halfheight < 4) { | ||
347 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
348 | dc.DrawLine(item1_pos, item2_pos); | ||
349 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
350 | dc.DrawLine(item1_pos, item2_pos); | ||
351 | } else { | ||
352 | int ellipse_x; | ||
353 | int ellipse_y; | ||
354 | double start; | ||
355 | double end; | ||
356 | |||
357 | if (item1_pos.x > item2_pos.x) { | ||
358 | ellipse_y = top; | ||
359 | |||
360 | if (item1_pos.y > item2_pos.y) { | ||
361 | ellipse_x = left - halfwidth; | ||
362 | |||
363 | start = 0; | ||
364 | end = 90; | ||
365 | } else { | ||
366 | ellipse_x = left; | ||
367 | |||
368 | start = 90; | ||
369 | end = 180; | ||
370 | } | ||
371 | } else { | ||
372 | ellipse_y = top - halfheight; | ||
373 | |||
374 | if (item1_pos.y > item2_pos.y) { | ||
375 | ellipse_x = left - halfwidth; | ||
376 | |||
377 | start = 270; | ||
378 | end = 360; | ||
379 | } else { | ||
380 | ellipse_x = left; | ||
381 | |||
382 | start = 180; | ||
383 | end = 270; | ||
384 | } | ||
385 | } | ||
386 | |||
387 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
388 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
389 | halfheight * 2, start, end); | ||
390 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
391 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
392 | halfheight * 2, start, end); | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | } | ||
397 | |||
398 | event.Skip(); | ||
399 | } | ||
400 | |||
401 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { | ||
402 | wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); | ||
403 | |||
404 | std::vector<int> hovered = tree_->query( | ||
405 | {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2}); | ||
406 | if (!hovered.empty()) { | ||
407 | actual_hover_= hovered[0]; | ||
408 | } else { | ||
409 | actual_hover_ = std::nullopt; | ||
410 | } | ||
411 | |||
412 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { | ||
413 | hovered_item_ = actual_hover_; | ||
414 | |||
415 | Refresh(); | ||
416 | } | ||
417 | |||
418 | if (scroll_mode_) { | ||
419 | EvaluateScroll(event.GetPosition()); | ||
420 | } | ||
421 | |||
422 | mouse_position_ = event.GetPosition(); | ||
423 | |||
424 | event.Skip(); | ||
425 | } | ||
426 | |||
427 | void SubwayMap::OnMouseScroll(wxMouseEvent &event) { | ||
428 | double new_zoom = zoom_; | ||
429 | if (event.GetWheelRotation() > 0) { | ||
430 | new_zoom = std::min(3.0, zoom_ + 0.25); | ||
431 | } else { | ||
432 | new_zoom = std::max(1.0, zoom_ - 0.25); | ||
433 | } | ||
434 | |||
435 | if (zoom_ != new_zoom) { | ||
436 | SetZoom(new_zoom, event.GetPosition()); | ||
437 | } | ||
438 | |||
439 | event.Skip(); | ||
440 | } | ||
441 | |||
442 | void SubwayMap::OnMouseLeave(wxMouseEvent &event) { | ||
443 | SetScrollSpeed(0, 0); | ||
444 | mouse_position_ = std::nullopt; | ||
445 | } | ||
446 | |||
447 | void SubwayMap::OnMouseClick(wxMouseEvent &event) { | ||
448 | bool finished = false; | ||
449 | |||
450 | if (actual_hover_) { | ||
451 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | ||
452 | if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || | ||
453 | networks_.IsItemInNetwork(*hovered_item_)) { | ||
454 | if (actual_hover_ != hovered_item_) { | ||
455 | hovered_item_ = actual_hover_; | ||
456 | |||
457 | if (!hovered_item_) { | ||
458 | sticky_hover_ = false; | ||
459 | } | ||
460 | |||
461 | Refresh(); | ||
462 | } else { | ||
463 | sticky_hover_ = !sticky_hover_; | ||
464 | } | ||
465 | |||
466 | finished = true; | ||
467 | } | ||
468 | } | ||
469 | |||
470 | if (!finished) { | ||
471 | if (scroll_mode_) { | ||
472 | scroll_mode_ = false; | ||
473 | |||
474 | SetScrollSpeed(0, 0); | ||
475 | |||
476 | SetCursor(wxCURSOR_ARROW); | ||
477 | } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || | ||
478 | event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || | ||
479 | event.GetPosition().y < GetSize().GetHeight() / 6 || | ||
480 | event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { | ||
481 | scroll_mode_ = true; | ||
482 | |||
483 | EvaluateScroll(event.GetPosition()); | ||
484 | |||
485 | SetCursor(wxCURSOR_CROSS); | ||
486 | } else { | ||
487 | sticky_hover_ = false; | ||
488 | } | ||
489 | } | ||
490 | } | ||
491 | |||
492 | void SubwayMap::OnTimer(wxTimerEvent &event) { | ||
493 | SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); | ||
494 | Refresh(); | ||
495 | } | ||
496 | |||
497 | void SubwayMap::OnZoomSlide(wxCommandEvent &event) { | ||
498 | double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); | ||
499 | |||
500 | if (new_zoom != zoom_) { | ||
501 | SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); | ||
502 | } | ||
503 | } | ||
504 | |||
505 | void SubwayMap::OnClickHelp(wxCommandEvent &event) { | ||
506 | wxMessageBox( | ||
507 | "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " | ||
508 | "corner.\nClick on a side of the screen to start panning. It will follow " | ||
509 | "your mouse. Click again to stop.\nHover over a door to see the " | ||
510 | "requirements to open it.\nHover over a warp or active painting to see " | ||
511 | "what it is connected to.\nIn painting shuffle, paintings that have not " | ||
512 | "yet been checked will not show their connections.\nA green shaded owl " | ||
513 | "means that there is a painting entrance there.\nA red shaded owl means " | ||
514 | "that there are only painting exits there.\nClick on a door or " | ||
515 | "warp to make the popup stick until you click again.", | ||
516 | "Subway Map Help"); | ||
517 | } | ||
518 | |||
519 | void SubwayMap::Redraw() { | ||
520 | rendered_ = wxBitmap(map_image_); | ||
521 | |||
522 | wxMemoryDC dc; | ||
523 | dc.SelectObject(rendered_); | ||
524 | |||
525 | wxGCDC gcdc(dc); | ||
526 | |||
527 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
528 | ItemDrawType draw_type = ItemDrawType::kNone; | ||
529 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
530 | std::optional<wxColour> shade_color; | ||
531 | |||
532 | if (AP_HasEarlyColorHallways() && | ||
533 | (subway_item.special == "starting_room_paintings" || | ||
534 | subway_item.special == "early_color_hallways")) { | ||
535 | draw_type = ItemDrawType::kOwl; | ||
536 | |||
537 | if (subway_item.special == "starting_room_paintings") { | ||
538 | shade_color = wxColour(0, 255, 0, 128); | ||
539 | } else { | ||
540 | shade_color = wxColour(255, 0, 0, 128); | ||
541 | } | ||
542 | } else if (subway_item.special == "sun_painting") { | ||
543 | if (!AP_IsPilgrimageEnabled()) { | ||
544 | if (IsDoorOpen(*subway_item.door)) { | ||
545 | draw_type = ItemDrawType::kOwl; | ||
546 | shade_color = wxColour(0, 255, 0, 128); | ||
547 | } else { | ||
548 | draw_type = ItemDrawType::kBox; | ||
549 | brush_color = wxRED_BRUSH; | ||
550 | } | ||
551 | } | ||
552 | } else if (!subway_item.paintings.empty()) { | ||
553 | if (AP_IsPaintingShuffle()) { | ||
554 | bool has_checked_painting = false; | ||
555 | bool has_unchecked_painting = false; | ||
556 | bool has_mapped_painting = false; | ||
557 | bool has_codomain_painting = false; | ||
558 | |||
559 | for (const std::string &painting_id : subway_item.paintings) { | ||
560 | if (checked_paintings_.count(painting_id)) { | ||
561 | has_checked_painting = true; | ||
562 | |||
563 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
564 | has_mapped_painting = true; | ||
565 | } else if (AP_IsPaintingMappedTo(painting_id)) { | ||
566 | has_codomain_painting = true; | ||
567 | } | ||
568 | } else { | ||
569 | has_unchecked_painting = true; | ||
570 | } | ||
571 | } | ||
572 | |||
573 | if (has_unchecked_painting || has_mapped_painting || has_codomain_painting) { | ||
574 | draw_type = ItemDrawType::kOwl; | ||
575 | |||
576 | if (has_checked_painting) { | ||
577 | if (has_mapped_painting) { | ||
578 | shade_color = wxColour(0, 255, 0, 128); | ||
579 | } else { | ||
580 | shade_color = wxColour(255, 0, 0, 128); | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | } else if (!subway_item.tags.empty()) { | ||
585 | draw_type = ItemDrawType::kOwl; | ||
586 | } | ||
587 | } else if (subway_item.door) { | ||
588 | draw_type = ItemDrawType::kBox; | ||
589 | |||
590 | if (IsDoorOpen(*subway_item.door)) { | ||
591 | brush_color = wxGREEN_BRUSH; | ||
592 | } else { | ||
593 | brush_color = wxRED_BRUSH; | ||
594 | } | ||
595 | } | ||
596 | |||
597 | wxPoint real_area_pos = {subway_item.x, subway_item.y}; | ||
598 | |||
599 | int real_area_size = | ||
600 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); | ||
601 | |||
602 | if (draw_type == ItemDrawType::kBox) { | ||
603 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
604 | gcdc.SetBrush(*brush_color); | ||
605 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
606 | } else if (draw_type == ItemDrawType::kOwl) { | ||
607 | wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( | ||
608 | real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); | ||
609 | gcdc.DrawBitmap(owl_bitmap, real_area_pos); | ||
610 | |||
611 | if (shade_color) { | ||
612 | gcdc.SetBrush(wxBrush(*shade_color)); | ||
613 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
614 | } | ||
615 | } | ||
616 | } | ||
617 | } | ||
618 | |||
619 | void SubwayMap::SetUpHelpButton() { | ||
620 | help_button_->SetPosition({ | ||
621 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | ||
622 | 15, | ||
623 | }); | ||
624 | } | ||
625 | |||
626 | void SubwayMap::EvaluateScroll(wxPoint pos) { | ||
627 | int scroll_x; | ||
628 | int scroll_y; | ||
629 | if (pos.x < GetSize().GetWidth() / 9) { | ||
630 | scroll_x = 20; | ||
631 | } else if (pos.x < GetSize().GetWidth() / 6) { | ||
632 | scroll_x = 5; | ||
633 | } else if (pos.x > 8 * GetSize().GetWidth() / 9) { | ||
634 | scroll_x = -20; | ||
635 | } else if (pos.x > 5 * GetSize().GetWidth() / 6) { | ||
636 | scroll_x = -5; | ||
637 | } else { | ||
638 | scroll_x = 0; | ||
639 | } | ||
640 | if (pos.y < GetSize().GetHeight() / 9) { | ||
641 | scroll_y = 20; | ||
642 | } else if (pos.y < GetSize().GetHeight() / 6) { | ||
643 | scroll_y = 5; | ||
644 | } else if (pos.y > 8 * GetSize().GetHeight() / 9) { | ||
645 | scroll_y = -20; | ||
646 | } else if (pos.y > 5 * GetSize().GetHeight() / 6) { | ||
647 | scroll_y = -5; | ||
648 | } else { | ||
649 | scroll_y = 0; | ||
650 | } | ||
651 | |||
652 | SetScrollSpeed(scroll_x, scroll_y); | ||
653 | } | ||
654 | |||
655 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | ||
656 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
657 | map_image_.GetSize().GetWidth() + | ||
658 | zoom_x_), | ||
659 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
660 | map_image_.GetSize().GetWidth() + | ||
661 | zoom_y_)}; | ||
662 | } | ||
663 | |||
664 | wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { | ||
665 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
666 | map_image_.GetSize().GetWidth()), | ||
667 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
668 | map_image_.GetSize().GetWidth())}; | ||
669 | } | ||
670 | |||
671 | wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { | ||
672 | return { | ||
673 | std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() / | ||
674 | render_width_ / zoom_), | ||
675 | 0, map_image_.GetWidth() - 1), | ||
676 | std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() / | ||
677 | render_width_ / zoom_), | ||
678 | 0, map_image_.GetHeight() - 1)}; | ||
679 | } | ||
680 | |||
681 | void SubwayMap::SetZoomPos(wxPoint pos) { | ||
682 | if (render_width_ * zoom_ <= GetSize().GetWidth()) { | ||
683 | zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; | ||
684 | } else { | ||
685 | zoom_x_ = std::clamp( | ||
686 | pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_), | ||
687 | 0); | ||
688 | } | ||
689 | if (render_height_ * zoom_ <= GetSize().GetHeight()) { | ||
690 | zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; | ||
691 | } else { | ||
692 | zoom_y_ = std::clamp( | ||
693 | pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_), | ||
694 | 0); | ||
695 | } | ||
696 | } | ||
697 | |||
698 | void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { | ||
699 | bool should_timer = (scroll_x != 0 || scroll_y != 0); | ||
700 | if (should_timer != scroll_timer_->IsRunning()) { | ||
701 | if (should_timer) { | ||
702 | scroll_timer_->Start(1000 / 60); | ||
703 | } else { | ||
704 | scroll_timer_->Stop(); | ||
705 | } | ||
706 | } | ||
707 | |||
708 | scroll_x_ = scroll_x; | ||
709 | scroll_y_ = scroll_y; | ||
710 | } | ||
711 | |||
712 | void SubwayMap::SetZoom(double zoom, wxPoint static_point) { | ||
713 | wxPoint map_pos = RenderPosToMapPos(static_point); | ||
714 | zoom_ = zoom; | ||
715 | |||
716 | wxPoint virtual_pos = MapPosToVirtualPos(map_pos); | ||
717 | SetZoomPos(-(virtual_pos - static_point)); | ||
718 | |||
719 | Refresh(); | ||
720 | |||
721 | zoom_slider_->SetValue((zoom - 1.0) / 0.25); | ||
722 | } | ||
723 | |||
724 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | ||
725 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | ||
726 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | ||
727 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | ||
728 | } | ||
diff --git a/src/subway_map.h b/src/subway_map.h new file mode 100644 index 0000000..feee8ff --- /dev/null +++ b/src/subway_map.h | |||
@@ -0,0 +1,92 @@ | |||
1 | #ifndef SUBWAY_MAP_H_BD2D843E | ||
2 | #define SUBWAY_MAP_H_BD2D843E | ||
3 | |||
4 | #include <wx/wxprec.h> | ||
5 | |||
6 | #ifndef WX_PRECOMP | ||
7 | #include <wx/wx.h> | ||
8 | #endif | ||
9 | |||
10 | #include <memory> | ||
11 | #include <optional> | ||
12 | #include <set> | ||
13 | #include <string> | ||
14 | #include <vector> | ||
15 | |||
16 | #include <quadtree/Quadtree.h> | ||
17 | |||
18 | #include "game_data.h" | ||
19 | #include "network_set.h" | ||
20 | |||
21 | class SubwayMap : public wxPanel { | ||
22 | public: | ||
23 | SubwayMap(wxWindow *parent); | ||
24 | |||
25 | void OnConnect(); | ||
26 | void UpdateIndicators(); | ||
27 | void UpdateSunwarp(SubwaySunwarp from_sunwarp, SubwaySunwarp to_sunwarp); | ||
28 | void Zoom(bool in); | ||
29 | |||
30 | private: | ||
31 | void OnPaint(wxPaintEvent &event); | ||
32 | void OnMouseMove(wxMouseEvent &event); | ||
33 | void OnMouseScroll(wxMouseEvent &event); | ||
34 | void OnMouseLeave(wxMouseEvent &event); | ||
35 | void OnMouseClick(wxMouseEvent &event); | ||
36 | void OnTimer(wxTimerEvent &event); | ||
37 | void OnZoomSlide(wxCommandEvent &event); | ||
38 | void OnClickHelp(wxCommandEvent &event); | ||
39 | |||
40 | void Redraw(); | ||
41 | void SetUpHelpButton(); | ||
42 | |||
43 | wxPoint MapPosToRenderPos(wxPoint pos) const; | ||
44 | wxPoint MapPosToVirtualPos(wxPoint pos) const; | ||
45 | wxPoint RenderPosToMapPos(wxPoint pos) const; | ||
46 | |||
47 | void EvaluateScroll(wxPoint pos); | ||
48 | |||
49 | void SetZoomPos(wxPoint pos); | ||
50 | void SetScrollSpeed(int scroll_x, int scroll_y); | ||
51 | void SetZoom(double zoom, wxPoint static_point); | ||
52 | |||
53 | wxImage map_image_; | ||
54 | wxImage owl_image_; | ||
55 | wxBitmap unchecked_eye_; | ||
56 | wxBitmap checked_eye_; | ||
57 | |||
58 | wxBitmap rendered_; | ||
59 | int render_x_ = 0; | ||
60 | int render_y_ = 0; | ||
61 | int render_width_ = 1; | ||
62 | int render_height_ = 1; | ||
63 | |||
64 | double zoom_ = 1.0; | ||
65 | int zoom_x_ = 0; // in render space | ||
66 | int zoom_y_ = 0; | ||
67 | |||
68 | bool scroll_mode_ = false; | ||
69 | wxTimer* scroll_timer_; | ||
70 | int scroll_x_ = 0; | ||
71 | int scroll_y_ = 0; | ||
72 | |||
73 | wxSlider *zoom_slider_; | ||
74 | |||
75 | wxButton *help_button_; | ||
76 | |||
77 | std::optional<wxPoint> mouse_position_; | ||
78 | |||
79 | struct GetItemBox { | ||
80 | quadtree::Box<float> operator()(const int &id) const; | ||
81 | }; | ||
82 | |||
83 | std::unique_ptr<quadtree::Quadtree<int, GetItemBox>> tree_; | ||
84 | std::optional<int> hovered_item_; | ||
85 | std::optional<int> actual_hover_; | ||
86 | bool sticky_hover_ = false; | ||
87 | |||
88 | NetworkSet networks_; | ||
89 | std::set<std::string> checked_paintings_; | ||
90 | }; | ||
91 | |||
92 | #endif /* end of include guard: SUBWAY_MAP_H_BD2D843E */ | ||
diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp index d64e0d3..107ae49 100644 --- a/src/tracker_frame.cpp +++ b/src/tracker_frame.cpp | |||
@@ -1,6 +1,8 @@ | |||
1 | #include "tracker_frame.h" | 1 | #include "tracker_frame.h" |
2 | 2 | ||
3 | #include <wx/aboutdlg.h> | ||
3 | #include <wx/choicebk.h> | 4 | #include <wx/choicebk.h> |
5 | #include <wx/notebook.h> | ||
4 | #include <wx/webrequest.h> | 6 | #include <wx/webrequest.h> |
5 | 7 | ||
6 | #include <nlohmann/json.hpp> | 8 | #include <nlohmann/json.hpp> |
@@ -10,6 +12,7 @@ | |||
10 | #include "ap_state.h" | 12 | #include "ap_state.h" |
11 | #include "connection_dialog.h" | 13 | #include "connection_dialog.h" |
12 | #include "settings_dialog.h" | 14 | #include "settings_dialog.h" |
15 | #include "subway_map.h" | ||
13 | #include "tracker_config.h" | 16 | #include "tracker_config.h" |
14 | #include "tracker_panel.h" | 17 | #include "tracker_panel.h" |
15 | #include "version.h" | 18 | #include "version.h" |
@@ -17,9 +20,12 @@ | |||
17 | enum TrackerFrameIds { | 20 | enum TrackerFrameIds { |
18 | ID_CONNECT = 1, | 21 | ID_CONNECT = 1, |
19 | ID_CHECK_FOR_UPDATES = 2, | 22 | ID_CHECK_FOR_UPDATES = 2, |
20 | ID_SETTINGS = 3 | 23 | ID_SETTINGS = 3, |
24 | ID_ZOOM_IN = 4, | ||
25 | ID_ZOOM_OUT = 5, | ||
21 | }; | 26 | }; |
22 | 27 | ||
28 | wxDEFINE_EVENT(STATE_RESET, wxCommandEvent); | ||
23 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | 29 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); |
24 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | 30 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); |
25 | 31 | ||
@@ -35,12 +41,20 @@ TrackerFrame::TrackerFrame() | |||
35 | menuFile->Append(ID_SETTINGS, "&Settings"); | 41 | menuFile->Append(ID_SETTINGS, "&Settings"); |
36 | menuFile->Append(wxID_EXIT); | 42 | menuFile->Append(wxID_EXIT); |
37 | 43 | ||
44 | wxMenu *menuView = new wxMenu(); | ||
45 | zoom_in_menu_item_ = menuView->Append(ID_ZOOM_IN, "Zoom In\tCtrl-+"); | ||
46 | zoom_out_menu_item_ = menuView->Append(ID_ZOOM_OUT, "Zoom Out\tCtrl--"); | ||
47 | |||
48 | zoom_in_menu_item_->Enable(false); | ||
49 | zoom_out_menu_item_->Enable(false); | ||
50 | |||
38 | wxMenu *menuHelp = new wxMenu(); | 51 | wxMenu *menuHelp = new wxMenu(); |
39 | menuHelp->Append(wxID_ABOUT); | 52 | menuHelp->Append(wxID_ABOUT); |
40 | menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); | 53 | menuHelp->Append(ID_CHECK_FOR_UPDATES, "Check for Updates"); |
41 | 54 | ||
42 | wxMenuBar *menuBar = new wxMenuBar(); | 55 | wxMenuBar *menuBar = new wxMenuBar(); |
43 | menuBar->Append(menuFile, "&File"); | 56 | menuBar->Append(menuFile, "&File"); |
57 | menuBar->Append(menuView, "&View"); | ||
44 | menuBar->Append(menuHelp, "&Help"); | 58 | menuBar->Append(menuHelp, "&Help"); |
45 | 59 | ||
46 | SetMenuBar(menuBar); | 60 | SetMenuBar(menuBar); |
@@ -54,18 +68,26 @@ TrackerFrame::TrackerFrame() | |||
54 | Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); | 68 | Bind(wxEVT_MENU, &TrackerFrame::OnSettings, this, ID_SETTINGS); |
55 | Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, | 69 | Bind(wxEVT_MENU, &TrackerFrame::OnCheckForUpdates, this, |
56 | ID_CHECK_FOR_UPDATES); | 70 | ID_CHECK_FOR_UPDATES); |
71 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomIn, this, ID_ZOOM_IN); | ||
72 | Bind(wxEVT_MENU, &TrackerFrame::OnZoomOut, this, ID_ZOOM_OUT); | ||
73 | Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &TrackerFrame::OnChangePage, this); | ||
74 | Bind(STATE_RESET, &TrackerFrame::OnStateReset, this); | ||
57 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | 75 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); |
58 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | 76 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); |
59 | 77 | ||
60 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); | 78 | wxChoicebook *choicebook = new wxChoicebook(this, wxID_ANY); |
61 | achievements_pane_ = new AchievementsPane(this); | 79 | achievements_pane_ = new AchievementsPane(choicebook); |
62 | choicebook->AddPage(achievements_pane_, "Achievements"); | 80 | choicebook->AddPage(achievements_pane_, "Achievements"); |
63 | 81 | ||
64 | tracker_panel_ = new TrackerPanel(this); | 82 | notebook_ = new wxNotebook(this, wxID_ANY); |
83 | tracker_panel_ = new TrackerPanel(notebook_); | ||
84 | subway_map_ = new SubwayMap(notebook_); | ||
85 | notebook_->AddPage(tracker_panel_, "Map"); | ||
86 | notebook_->AddPage(subway_map_, "Subway"); | ||
65 | 87 | ||
66 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); | 88 | wxBoxSizer *top_sizer = new wxBoxSizer(wxHORIZONTAL); |
67 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); | 89 | top_sizer->Add(choicebook, wxSizerFlags().Expand().Proportion(1)); |
68 | top_sizer->Add(tracker_panel_, wxSizerFlags().Expand().Proportion(3)); | 90 | top_sizer->Add(notebook_, wxSizerFlags().Expand().Proportion(3)); |
69 | 91 | ||
70 | SetSizerAndFit(top_sizer); | 92 | SetSizerAndFit(top_sizer); |
71 | SetSize(1280, 728); | 93 | SetSize(1280, 728); |
@@ -96,17 +118,23 @@ void TrackerFrame::SetStatusMessage(std::string message) { | |||
96 | QueueEvent(event); | 118 | QueueEvent(event); |
97 | } | 119 | } |
98 | 120 | ||
121 | void TrackerFrame::ResetIndicators() { | ||
122 | QueueEvent(new wxCommandEvent(STATE_RESET)); | ||
123 | } | ||
124 | |||
99 | void TrackerFrame::UpdateIndicators() { | 125 | void TrackerFrame::UpdateIndicators() { |
100 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); | 126 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); |
101 | } | 127 | } |
102 | 128 | ||
103 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | 129 | void TrackerFrame::OnAbout(wxCommandEvent &event) { |
104 | std::ostringstream message_text; | 130 | wxAboutDialogInfo about_info; |
105 | message_text << "Lingo Archipelago Tracker " << kTrackerVersion | 131 | about_info.SetName("Lingo Archipelago Tracker"); |
106 | << " by hatkirby"; | 132 | about_info.SetVersion(kTrackerVersion.ToString()); |
107 | 133 | about_info.AddDeveloper("hatkirby"); | |
108 | wxMessageBox(message_text.str(), "About lingo-ap-tracker", | 134 | about_info.AddArtist("Brenton Wildes"); |
109 | wxOK | wxICON_INFORMATION); | 135 | about_info.AddArtist("kinrah"); |
136 | |||
137 | wxAboutBox(about_info); | ||
110 | } | 138 | } |
111 | 139 | ||
112 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } | 140 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } |
@@ -122,7 +150,8 @@ void TrackerFrame::OnConnect(wxCommandEvent &event) { | |||
122 | std::deque<ConnectionDetails> new_history; | 150 | std::deque<ConnectionDetails> new_history; |
123 | new_history.push_back(GetTrackerConfig().connection_details); | 151 | new_history.push_back(GetTrackerConfig().connection_details); |
124 | 152 | ||
125 | for (const ConnectionDetails& details : GetTrackerConfig().connection_history) { | 153 | for (const ConnectionDetails &details : |
154 | GetTrackerConfig().connection_history) { | ||
126 | if (details != GetTrackerConfig().connection_details) { | 155 | if (details != GetTrackerConfig().connection_details) { |
127 | new_history.push_back(details); | 156 | new_history.push_back(details); |
128 | } | 157 | } |
@@ -158,9 +187,34 @@ void TrackerFrame::OnCheckForUpdates(wxCommandEvent &event) { | |||
158 | CheckForUpdates(/*manual=*/true); | 187 | CheckForUpdates(/*manual=*/true); |
159 | } | 188 | } |
160 | 189 | ||
190 | void TrackerFrame::OnZoomIn(wxCommandEvent &event) { | ||
191 | if (notebook_->GetSelection() == 1) { | ||
192 | subway_map_->Zoom(true); | ||
193 | } | ||
194 | } | ||
195 | |||
196 | void TrackerFrame::OnZoomOut(wxCommandEvent& event) { | ||
197 | if (notebook_->GetSelection() == 1) { | ||
198 | subway_map_->Zoom(false); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | void TrackerFrame::OnChangePage(wxBookCtrlEvent &event) { | ||
203 | zoom_in_menu_item_->Enable(event.GetSelection() == 1); | ||
204 | zoom_out_menu_item_->Enable(event.GetSelection() == 1); | ||
205 | } | ||
206 | |||
207 | void TrackerFrame::OnStateReset(wxCommandEvent& event) { | ||
208 | tracker_panel_->UpdateIndicators(); | ||
209 | achievements_pane_->UpdateIndicators(); | ||
210 | subway_map_->OnConnect(); | ||
211 | Refresh(); | ||
212 | } | ||
213 | |||
161 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | 214 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { |
162 | tracker_panel_->UpdateIndicators(); | 215 | tracker_panel_->UpdateIndicators(); |
163 | achievements_pane_->UpdateIndicators(); | 216 | achievements_pane_->UpdateIndicators(); |
217 | subway_map_->UpdateIndicators(); | ||
164 | Refresh(); | 218 | Refresh(); |
165 | } | 219 | } |
166 | 220 | ||
@@ -192,8 +246,10 @@ void TrackerFrame::CheckForUpdates(bool manual) { | |||
192 | std::ostringstream message_text; | 246 | std::ostringstream message_text; |
193 | message_text << "There is a newer version of Lingo AP Tracker " | 247 | message_text << "There is a newer version of Lingo AP Tracker " |
194 | "available. You have " | 248 | "available. You have " |
195 | << kTrackerVersion << ", and the latest version is " | 249 | << kTrackerVersion.ToString() |
196 | << latest_version << ". Would you like to update?"; | 250 | << ", and the latest version is " |
251 | << latest_version.ToString() | ||
252 | << ". Would you like to update?"; | ||
197 | 253 | ||
198 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == | 254 | if (wxMessageBox(message_text.str(), "Update available", wxYES_NO) == |
199 | wxYES) { | 255 | wxYES) { |
diff --git a/src/tracker_frame.h b/src/tracker_frame.h index e5bf97e..f7cb3f2 100644 --- a/src/tracker_frame.h +++ b/src/tracker_frame.h | |||
@@ -8,8 +8,12 @@ | |||
8 | #endif | 8 | #endif |
9 | 9 | ||
10 | class AchievementsPane; | 10 | class AchievementsPane; |
11 | class SubwayMap; | ||
11 | class TrackerPanel; | 12 | class TrackerPanel; |
13 | class wxBookCtrlEvent; | ||
14 | class wxNotebook; | ||
12 | 15 | ||
16 | wxDECLARE_EVENT(STATE_RESET, wxCommandEvent); | ||
13 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); | 17 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); |
14 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); | 18 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); |
15 | 19 | ||
@@ -19,6 +23,7 @@ class TrackerFrame : public wxFrame { | |||
19 | 23 | ||
20 | void SetStatusMessage(std::string message); | 24 | void SetStatusMessage(std::string message); |
21 | 25 | ||
26 | void ResetIndicators(); | ||
22 | void UpdateIndicators(); | 27 | void UpdateIndicators(); |
23 | 28 | ||
24 | private: | 29 | private: |
@@ -27,14 +32,23 @@ class TrackerFrame : public wxFrame { | |||
27 | void OnConnect(wxCommandEvent &event); | 32 | void OnConnect(wxCommandEvent &event); |
28 | void OnSettings(wxCommandEvent &event); | 33 | void OnSettings(wxCommandEvent &event); |
29 | void OnCheckForUpdates(wxCommandEvent &event); | 34 | void OnCheckForUpdates(wxCommandEvent &event); |
35 | void OnZoomIn(wxCommandEvent &event); | ||
36 | void OnZoomOut(wxCommandEvent &event); | ||
37 | void OnChangePage(wxBookCtrlEvent &event); | ||
30 | 38 | ||
39 | void OnStateReset(wxCommandEvent &event); | ||
31 | void OnStateChanged(wxCommandEvent &event); | 40 | void OnStateChanged(wxCommandEvent &event); |
32 | void OnStatusChanged(wxCommandEvent &event); | 41 | void OnStatusChanged(wxCommandEvent &event); |
33 | 42 | ||
34 | void CheckForUpdates(bool manual); | 43 | void CheckForUpdates(bool manual); |
35 | 44 | ||
45 | wxNotebook *notebook_; | ||
36 | TrackerPanel *tracker_panel_; | 46 | TrackerPanel *tracker_panel_; |
37 | AchievementsPane *achievements_pane_; | 47 | AchievementsPane *achievements_pane_; |
48 | SubwayMap *subway_map_; | ||
49 | |||
50 | wxMenuItem *zoom_in_menu_item_; | ||
51 | wxMenuItem *zoom_out_menu_item_; | ||
38 | }; | 52 | }; |
39 | 53 | ||
40 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ | 54 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ |
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 5f9f8ea..d60c1b6 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp | |||
@@ -1,5 +1,7 @@ | |||
1 | #include "tracker_panel.h" | 1 | #include "tracker_panel.h" |
2 | 2 | ||
3 | #include <wx/dcbuffer.h> | ||
4 | |||
3 | #include "ap_state.h" | 5 | #include "ap_state.h" |
4 | #include "area_popup.h" | 6 | #include "area_popup.h" |
5 | #include "game_data.h" | 7 | #include "game_data.h" |
@@ -13,6 +15,8 @@ constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; | |||
13 | constexpr int PLAYER_SIZE = 96; | 15 | constexpr int PLAYER_SIZE = 96; |
14 | 16 | ||
15 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | 17 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { |
18 | SetBackgroundStyle(wxBG_STYLE_PAINT); | ||
19 | |||
16 | map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), | 20 | map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), |
17 | wxBITMAP_TYPE_PNG); | 21 | wxBITMAP_TYPE_PNG); |
18 | if (!map_image_.IsOk()) { | 22 | if (!map_image_.IsOk()) { |
@@ -54,7 +58,7 @@ void TrackerPanel::OnPaint(wxPaintEvent &event) { | |||
54 | Redraw(); | 58 | Redraw(); |
55 | } | 59 | } |
56 | 60 | ||
57 | wxPaintDC dc(this); | 61 | wxBufferedPaintDC dc(this); |
58 | dc.DrawBitmap(rendered_, 0, 0); | 62 | dc.DrawBitmap(rendered_, 0, 0); |
59 | 63 | ||
60 | if (AP_GetPlayerPosition().has_value()) { | 64 | if (AP_GetPlayerPosition().has_value()) { |
@@ -139,7 +143,8 @@ void TrackerPanel::Redraw() { | |||
139 | for (AreaIndicator &area : areas_) { | 143 | for (AreaIndicator &area : areas_) { |
140 | const MapArea &map_area = GD_GetMapArea(area.area_id); | 144 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
141 | if (!AP_IsLocationVisible(map_area.classification) && | 145 | if (!AP_IsLocationVisible(map_area.classification) && |
142 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels)) { | 146 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && |
147 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | ||
143 | area.active = false; | 148 | area.active = false; |
144 | continue; | 149 | continue; |
145 | } else { | 150 | } else { |
@@ -167,6 +172,21 @@ void TrackerPanel::Redraw() { | |||
167 | } | 172 | } |
168 | } | 173 | } |
169 | 174 | ||
175 | if (AP_IsPaintingShuffle()) { | ||
176 | for (int painting_id : map_area.paintings) { | ||
177 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); | ||
178 | if (!AP_IsPaintingChecked(painting.internal_id)) { | ||
179 | bool reachable = IsPaintingReachable(painting_id); | ||
180 | |||
181 | if (reachable) { | ||
182 | has_reachable_unchecked = true; | ||
183 | } else { | ||
184 | has_unreachable_unchecked = true; | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | } | ||
189 | |||
170 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | 190 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * |
171 | final_width / image_size.GetWidth(); | 191 | final_width / image_size.GetWidth(); |
172 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | 192 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * |
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 1881513..c475fb7 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -12,9 +12,142 @@ | |||
12 | 12 | ||
13 | namespace { | 13 | namespace { |
14 | 14 | ||
15 | struct Requirements { | ||
16 | bool disabled = false; | ||
17 | |||
18 | std::set<int> doors; // non-grouped, handles progressive | ||
19 | std::set<int> items; // all other items | ||
20 | std::set<int> rooms; // maybe | ||
21 | bool mastery = false; // maybe | ||
22 | bool panel_hunt = false; // maybe | ||
23 | |||
24 | void Merge(const Requirements& rhs) { | ||
25 | if (rhs.disabled) { | ||
26 | return; | ||
27 | } | ||
28 | |||
29 | for (int id : rhs.doors) { | ||
30 | doors.insert(id); | ||
31 | } | ||
32 | for (int id : rhs.items) { | ||
33 | items.insert(id); | ||
34 | } | ||
35 | for (int id : rhs.rooms) { | ||
36 | rooms.insert(id); | ||
37 | } | ||
38 | mastery = mastery || rhs.mastery; | ||
39 | panel_hunt = panel_hunt || rhs.panel_hunt; | ||
40 | } | ||
41 | }; | ||
42 | |||
43 | class RequirementCalculator { | ||
44 | public: | ||
45 | void Reset() { | ||
46 | doors_.clear(); | ||
47 | panels_.clear(); | ||
48 | } | ||
49 | |||
50 | const Requirements& GetDoor(int door_id) { | ||
51 | if (!doors_.count(door_id)) { | ||
52 | Requirements requirements; | ||
53 | const Door& door_obj = GD_GetDoor(door_id); | ||
54 | |||
55 | if (door_obj.type == DoorType::kSunPainting) { | ||
56 | if (!AP_IsPilgrimageEnabled()) { | ||
57 | requirements.items.insert(door_obj.ap_item_id); | ||
58 | } else { | ||
59 | requirements.disabled = true; | ||
60 | } | ||
61 | } else if (door_obj.type == DoorType::kSunwarp) { | ||
62 | switch (AP_GetSunwarpAccess()) { | ||
63 | case kSUNWARP_ACCESS_NORMAL: | ||
64 | // Do nothing. | ||
65 | break; | ||
66 | case kSUNWARP_ACCESS_DISABLED: | ||
67 | requirements.disabled = true; | ||
68 | break; | ||
69 | case kSUNWARP_ACCESS_UNLOCK: | ||
70 | requirements.items.insert(door_obj.group_ap_item_id); | ||
71 | break; | ||
72 | case kSUNWARP_ACCESS_INDIVIDUAL: | ||
73 | case kSUNWARP_ACCESS_PROGRESSIVE: | ||
74 | requirements.doors.insert(door_obj.id); | ||
75 | break; | ||
76 | } | ||
77 | } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { | ||
78 | requirements.rooms.insert(door_obj.room); | ||
79 | |||
80 | for (int panel_id : door_obj.panels) { | ||
81 | const Requirements& panel_reqs = GetPanel(panel_id); | ||
82 | requirements.Merge(panel_reqs); | ||
83 | } | ||
84 | } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && | ||
85 | !door_obj.group_name.empty()) { | ||
86 | requirements.items.insert(door_obj.group_ap_item_id); | ||
87 | } else { | ||
88 | requirements.doors.insert(door_obj.id); | ||
89 | } | ||
90 | |||
91 | doors_[door_id] = requirements; | ||
92 | } | ||
93 | |||
94 | return doors_[door_id]; | ||
95 | } | ||
96 | |||
97 | const Requirements& GetPanel(int panel_id) { | ||
98 | if (!panels_.count(panel_id)) { | ||
99 | Requirements requirements; | ||
100 | const Panel& panel_obj = GD_GetPanel(panel_id); | ||
101 | |||
102 | requirements.rooms.insert(panel_obj.room); | ||
103 | |||
104 | if (panel_obj.name == "THE MASTER") { | ||
105 | requirements.mastery = true; | ||
106 | } | ||
107 | |||
108 | if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && | ||
109 | AP_GetLevel2Requirement() > 1) { | ||
110 | requirements.panel_hunt = true; | ||
111 | } | ||
112 | |||
113 | for (int room_id : panel_obj.required_rooms) { | ||
114 | requirements.rooms.insert(room_id); | ||
115 | } | ||
116 | |||
117 | for (int door_id : panel_obj.required_doors) { | ||
118 | const Requirements& door_reqs = GetDoor(door_id); | ||
119 | requirements.Merge(door_reqs); | ||
120 | } | ||
121 | |||
122 | for (int panel_id : panel_obj.required_panels) { | ||
123 | const Requirements& panel_reqs = GetPanel(panel_id); | ||
124 | requirements.Merge(panel_reqs); | ||
125 | } | ||
126 | |||
127 | if (AP_IsColorShuffle()) { | ||
128 | for (LingoColor color : panel_obj.colors) { | ||
129 | requirements.items.insert(GD_GetItemIdForColor(color)); | ||
130 | } | ||
131 | } | ||
132 | |||
133 | panels_[panel_id] = requirements; | ||
134 | } | ||
135 | |||
136 | return panels_[panel_id]; | ||
137 | } | ||
138 | |||
139 | private: | ||
140 | std::map<int, Requirements> doors_; | ||
141 | std::map<int, Requirements> panels_; | ||
142 | }; | ||
143 | |||
15 | struct TrackerState { | 144 | struct TrackerState { |
16 | std::map<int, bool> reachability; | 145 | std::map<int, bool> reachability; |
146 | std::set<int> reachable_doors; | ||
147 | std::set<int> reachable_paintings; | ||
17 | std::mutex reachability_mutex; | 148 | std::mutex reachability_mutex; |
149 | RequirementCalculator requirements; | ||
150 | std::map<int, std::map<std::string, bool>> door_reports; | ||
18 | }; | 151 | }; |
19 | 152 | ||
20 | enum Decision { kYes, kNo, kMaybe }; | 153 | enum Decision { kYes, kNo, kMaybe }; |
@@ -41,6 +174,7 @@ class StateCalculator { | |||
41 | 174 | ||
42 | void Calculate() { | 175 | void Calculate() { |
43 | std::list<int> panel_boundary; | 176 | std::list<int> panel_boundary; |
177 | std::list<int> painting_boundary; | ||
44 | std::list<Exit> flood_boundary; | 178 | std::list<Exit> flood_boundary; |
45 | flood_boundary.push_back({.destination_room = options_.start}); | 179 | flood_boundary.push_back({.destination_room = options_.start}); |
46 | 180 | ||
@@ -48,6 +182,8 @@ class StateCalculator { | |||
48 | while (reachable_changed) { | 182 | while (reachable_changed) { |
49 | reachable_changed = false; | 183 | reachable_changed = false; |
50 | 184 | ||
185 | std::list<Exit> new_boundary; | ||
186 | |||
51 | std::list<int> new_panel_boundary; | 187 | std::list<int> new_panel_boundary; |
52 | for (int panel_id : panel_boundary) { | 188 | for (int panel_id : panel_boundary) { |
53 | if (solveable_panels_.count(panel_id)) { | 189 | if (solveable_panels_.count(panel_id)) { |
@@ -63,7 +199,33 @@ class StateCalculator { | |||
63 | } | 199 | } |
64 | } | 200 | } |
65 | 201 | ||
66 | std::list<Exit> new_boundary; | 202 | std::list<int> new_painting_boundary; |
203 | for (int painting_id : painting_boundary) { | ||
204 | if (reachable_paintings_.count(painting_id)) { | ||
205 | continue; | ||
206 | } | ||
207 | |||
208 | Decision painting_reachable = IsPaintingReachable(painting_id); | ||
209 | if (painting_reachable == kYes) { | ||
210 | reachable_paintings_.insert(painting_id); | ||
211 | reachable_changed = true; | ||
212 | |||
213 | PaintingExit cur_painting = GD_GetPaintingExit(painting_id); | ||
214 | if (AP_GetPaintingMapping().count(cur_painting.internal_id) && | ||
215 | AP_GetCheckedPaintings().count(cur_painting.internal_id)) { | ||
216 | Exit painting_exit; | ||
217 | PaintingExit target_painting = | ||
218 | GD_GetPaintingExit(GD_GetPaintingByName( | ||
219 | AP_GetPaintingMapping().at(cur_painting.internal_id))); | ||
220 | painting_exit.destination_room = target_painting.room; | ||
221 | |||
222 | new_boundary.push_back(painting_exit); | ||
223 | } | ||
224 | } else if (painting_reachable == kMaybe) { | ||
225 | new_painting_boundary.push_back(painting_id); | ||
226 | } | ||
227 | } | ||
228 | |||
67 | for (const Exit& room_exit : flood_boundary) { | 229 | for (const Exit& room_exit : flood_boundary) { |
68 | if (reachable_rooms_.count(room_exit.destination_room)) { | 230 | if (reachable_rooms_.count(room_exit.destination_room)) { |
69 | continue; | 231 | continue; |
@@ -98,15 +260,8 @@ class StateCalculator { | |||
98 | } | 260 | } |
99 | 261 | ||
100 | if (AP_IsPaintingShuffle()) { | 262 | if (AP_IsPaintingShuffle()) { |
101 | for (const PaintingExit& out_edge : room_obj.paintings) { | 263 | for (int out_edge : room_obj.paintings) { |
102 | if (AP_GetPaintingMapping().count(out_edge.id)) { | 264 | new_painting_boundary.push_back(out_edge); |
103 | Exit painting_exit; | ||
104 | painting_exit.destination_room = GD_GetRoomForPainting( | ||
105 | AP_GetPaintingMapping().at(out_edge.id)); | ||
106 | painting_exit.door = out_edge.door; | ||
107 | |||
108 | new_boundary.push_back(painting_exit); | ||
109 | } | ||
110 | } | 265 | } |
111 | } | 266 | } |
112 | 267 | ||
@@ -155,6 +310,13 @@ class StateCalculator { | |||
155 | 310 | ||
156 | flood_boundary = new_boundary; | 311 | flood_boundary = new_boundary; |
157 | panel_boundary = new_panel_boundary; | 312 | panel_boundary = new_panel_boundary; |
313 | painting_boundary = new_painting_boundary; | ||
314 | } | ||
315 | |||
316 | // Now that we know the full reachable area, let's make sure all doors are | ||
317 | // evaluated. | ||
318 | for (const Door& door : GD_GetDoors()) { | ||
319 | int discard = IsDoorReachable(door.id); | ||
158 | } | 320 | } |
159 | } | 321 | } |
160 | 322 | ||
@@ -166,6 +328,14 @@ class StateCalculator { | |||
166 | 328 | ||
167 | const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } | 329 | const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } |
168 | 330 | ||
331 | const std::set<int>& GetReachablePaintings() const { | ||
332 | return reachable_paintings_; | ||
333 | } | ||
334 | |||
335 | const std::map<int, std::map<std::string, bool>>& GetDoorReports() const { | ||
336 | return door_report_; | ||
337 | } | ||
338 | |||
169 | private: | 339 | private: |
170 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { | 340 | Decision IsNonGroupedDoorReachable(const Door& door_obj) { |
171 | bool has_item = AP_HasItem(door_obj.ap_item_id); | 341 | bool has_item = AP_HasItem(door_obj.ap_item_id); |
@@ -182,68 +352,52 @@ class StateCalculator { | |||
182 | return has_item ? kYes : kNo; | 352 | return has_item ? kYes : kNo; |
183 | } | 353 | } |
184 | 354 | ||
185 | Decision IsDoorReachable_Helper(int door_id) { | 355 | Decision AreRequirementsSatisfied( |
186 | const Door& door_obj = GD_GetDoor(door_id); | 356 | const Requirements& reqs, std::map<std::string, bool>* report = nullptr) { |
187 | 357 | if (reqs.disabled) { | |
188 | if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { | 358 | return kNo; |
189 | return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; | ||
190 | } else if (door_obj.type == DoorType::kSunwarp) { | ||
191 | switch (AP_GetSunwarpAccess()) { | ||
192 | case kSUNWARP_ACCESS_NORMAL: | ||
193 | return kYes; | ||
194 | case kSUNWARP_ACCESS_DISABLED: | ||
195 | return kNo; | ||
196 | case kSUNWARP_ACCESS_UNLOCK: | ||
197 | return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; | ||
198 | case kSUNWARP_ACCESS_INDIVIDUAL: | ||
199 | case kSUNWARP_ACCESS_PROGRESSIVE: | ||
200 | return IsNonGroupedDoorReachable(door_obj); | ||
201 | } | ||
202 | } else if (AP_GetDoorShuffleMode() != kDOORS_MODE || door_obj.skip_item) { | ||
203 | if (!reachable_rooms_.count(door_obj.room)) { | ||
204 | return kMaybe; | ||
205 | } | ||
206 | |||
207 | for (int panel_id : door_obj.panels) { | ||
208 | if (!solveable_panels_.count(panel_id)) { | ||
209 | return kMaybe; | ||
210 | } | ||
211 | } | ||
212 | |||
213 | return kYes; | ||
214 | } else if (AP_GetDoorShuffleMode() == kDOORS_MODE && AP_AreDoorsGrouped() && | ||
215 | !door_obj.group_name.empty()) { | ||
216 | return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; | ||
217 | } else { | ||
218 | return IsNonGroupedDoorReachable(door_obj); | ||
219 | } | 359 | } |
220 | } | ||
221 | 360 | ||
222 | Decision IsDoorReachable(int door_id) { | 361 | Decision final_decision = kYes; |
223 | if (options_.parent) { | ||
224 | return options_.parent->IsDoorReachable(door_id); | ||
225 | } | ||
226 | 362 | ||
227 | if (door_decisions_.count(door_id)) { | 363 | for (int door_id : reqs.doors) { |
228 | return door_decisions_.at(door_id); | 364 | const Door& door_obj = GD_GetDoor(door_id); |
365 | Decision decision = IsNonGroupedDoorReachable(door_obj); | ||
366 | |||
367 | if (report) { | ||
368 | (*report)[door_obj.item_name] = (decision == kYes); | ||
369 | } | ||
370 | |||
371 | if (decision != kYes) { | ||
372 | final_decision = decision; | ||
373 | } | ||
229 | } | 374 | } |
230 | 375 | ||
231 | Decision result = IsDoorReachable_Helper(door_id); | 376 | for (int item_id : reqs.items) { |
232 | if (result != kMaybe) { | 377 | bool has_item = AP_HasItem(item_id); |
233 | door_decisions_[door_id] = result; | 378 | if (report) { |
379 | (*report)[AP_GetItemName(item_id)] = has_item; | ||
380 | } | ||
381 | |||
382 | if (!has_item) { | ||
383 | final_decision = kNo; | ||
384 | } | ||
234 | } | 385 | } |
235 | 386 | ||
236 | return result; | 387 | for (int room_id : reqs.rooms) { |
237 | } | 388 | bool reachable = reachable_rooms_.count(room_id); |
238 | 389 | ||
239 | Decision IsPanelReachable(int panel_id) { | 390 | if (report) { |
240 | const Panel& panel_obj = GD_GetPanel(panel_id); | 391 | std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; |
392 | (*report)[report_name] = reachable; | ||
393 | } | ||
241 | 394 | ||
242 | if (!reachable_rooms_.count(panel_obj.room)) { | 395 | if (!reachable && final_decision != kNo) { |
243 | return kMaybe; | 396 | final_decision = kMaybe; |
397 | } | ||
244 | } | 398 | } |
245 | 399 | ||
246 | if (panel_obj.name == "THE MASTER") { | 400 | if (reqs.mastery) { |
247 | int achievements_accessible = 0; | 401 | int achievements_accessible = 0; |
248 | 402 | ||
249 | for (int achieve_id : GD_GetAchievementPanels()) { | 403 | for (int achieve_id : GD_GetAchievementPanels()) { |
@@ -256,12 +410,18 @@ class StateCalculator { | |||
256 | } | 410 | } |
257 | } | 411 | } |
258 | 412 | ||
259 | return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes | 413 | bool can_mastery = |
260 | : kMaybe; | 414 | (achievements_accessible >= AP_GetMasteryRequirement()); |
415 | if (report) { | ||
416 | (*report)["Mastery"] = can_mastery; | ||
417 | } | ||
418 | |||
419 | if (!can_mastery && final_decision != kNo) { | ||
420 | final_decision = kMaybe; | ||
421 | } | ||
261 | } | 422 | } |
262 | 423 | ||
263 | if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && | 424 | if (reqs.panel_hunt) { |
264 | AP_GetLevel2Requirement() > 1) { | ||
265 | int counting_panels_accessible = 0; | 425 | int counting_panels_accessible = 0; |
266 | 426 | ||
267 | for (int solved_panel_id : solveable_panels_) { | 427 | for (int solved_panel_id : solveable_panels_) { |
@@ -272,41 +432,58 @@ class StateCalculator { | |||
272 | } | 432 | } |
273 | } | 433 | } |
274 | 434 | ||
275 | return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) | 435 | bool can_level2 = |
276 | ? kYes | 436 | (counting_panels_accessible >= AP_GetLevel2Requirement() - 1); |
277 | : kMaybe; | 437 | if (report) { |
278 | } | 438 | std::string report_name = |
439 | std::to_string(AP_GetLevel2Requirement()) + " Panels"; | ||
440 | (*report)[report_name] = can_level2; | ||
441 | } | ||
279 | 442 | ||
280 | for (int room_id : panel_obj.required_rooms) { | 443 | if (!can_level2 && final_decision != kNo) { |
281 | if (!reachable_rooms_.count(room_id)) { | 444 | final_decision = kMaybe; |
282 | return kMaybe; | ||
283 | } | 445 | } |
284 | } | 446 | } |
285 | 447 | ||
286 | for (int door_id : panel_obj.required_doors) { | 448 | return final_decision; |
287 | Decision door_reachable = IsDoorReachable(door_id); | 449 | } |
288 | if (door_reachable == kNo) { | 450 | |
289 | const Door& door_obj = GD_GetDoor(door_id); | 451 | Decision IsDoorReachable_Helper(int door_id) { |
290 | return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) | 452 | if (door_report_.count(door_id)) { |
291 | ? kMaybe | 453 | door_report_[door_id].clear(); |
292 | : kNo; | 454 | } else { |
293 | } else if (door_reachable == kMaybe) { | 455 | door_report_[door_id] = {}; |
294 | return kMaybe; | ||
295 | } | ||
296 | } | 456 | } |
297 | 457 | ||
298 | for (int panel_id : panel_obj.required_panels) { | 458 | return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), |
299 | if (!solveable_panels_.count(panel_id)) { | 459 | &door_report_[door_id]); |
300 | return kMaybe; | 460 | } |
301 | } | 461 | |
462 | Decision IsDoorReachable(int door_id) { | ||
463 | if (options_.parent) { | ||
464 | return options_.parent->IsDoorReachable(door_id); | ||
302 | } | 465 | } |
303 | 466 | ||
304 | if (AP_IsColorShuffle()) { | 467 | if (door_decisions_.count(door_id)) { |
305 | for (LingoColor color : panel_obj.colors) { | 468 | return door_decisions_.at(door_id); |
306 | if (!AP_HasItem(GD_GetItemIdForColor(color))) { | 469 | } |
307 | return kNo; | 470 | |
308 | } | 471 | Decision result = IsDoorReachable_Helper(door_id); |
309 | } | 472 | if (result != kMaybe) { |
473 | door_decisions_[door_id] = result; | ||
474 | } | ||
475 | |||
476 | return result; | ||
477 | } | ||
478 | |||
479 | Decision IsPanelReachable(int panel_id) { | ||
480 | return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); | ||
481 | } | ||
482 | |||
483 | Decision IsPaintingReachable(int painting_id) { | ||
484 | const PaintingExit& painting = GD_GetPaintingExit(painting_id); | ||
485 | if (painting.door) { | ||
486 | return IsDoorReachable(*painting.door); | ||
310 | } | 487 | } |
311 | 488 | ||
312 | if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { | 489 | if (panel_obj.panel_door != -1 && AP_GetDoorShuffleMode() == kPANELS_MODE) { |
@@ -417,11 +594,20 @@ class StateCalculator { | |||
417 | std::set<int> reachable_rooms_; | 594 | std::set<int> reachable_rooms_; |
418 | std::map<int, Decision> door_decisions_; | 595 | std::map<int, Decision> door_decisions_; |
419 | std::set<int> solveable_panels_; | 596 | std::set<int> solveable_panels_; |
597 | std::set<int> reachable_paintings_; | ||
598 | std::map<int, std::map<std::string, bool>> door_report_; | ||
420 | }; | 599 | }; |
421 | 600 | ||
422 | } // namespace | 601 | } // namespace |
423 | 602 | ||
603 | void ResetReachabilityRequirements() { | ||
604 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
605 | GetState().requirements.Reset(); | ||
606 | } | ||
607 | |||
424 | void RecalculateReachability() { | 608 | void RecalculateReachability() { |
609 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
610 | |||
425 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); | 611 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); |
426 | state_calculator.Calculate(); | 612 | state_calculator.Calculate(); |
427 | 613 | ||
@@ -444,10 +630,21 @@ void RecalculateReachability() { | |||
444 | } | 630 | } |
445 | } | 631 | } |
446 | 632 | ||
447 | { | 633 | std::set<int> new_reachable_doors; |
448 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 634 | for (const auto& [door_id, decision] : state_calculator.GetDoorDecisions()) { |
449 | std::swap(GetState().reachability, new_reachability); | 635 | if (decision == kYes) { |
636 | new_reachable_doors.insert(door_id); | ||
637 | } | ||
450 | } | 638 | } |
639 | |||
640 | std::set<int> reachable_paintings = state_calculator.GetReachablePaintings(); | ||
641 | std::map<int, std::map<std::string, bool>> door_reports = | ||
642 | state_calculator.GetDoorReports(); | ||
643 | |||
644 | std::swap(GetState().reachability, new_reachability); | ||
645 | std::swap(GetState().reachable_doors, new_reachable_doors); | ||
646 | std::swap(GetState().reachable_paintings, reachable_paintings); | ||
647 | std::swap(GetState().door_reports, door_reports); | ||
451 | } | 648 | } |
452 | 649 | ||
453 | bool IsLocationReachable(int location_id) { | 650 | bool IsLocationReachable(int location_id) { |
@@ -459,3 +656,21 @@ bool IsLocationReachable(int location_id) { | |||
459 | return false; | 656 | return false; |
460 | } | 657 | } |
461 | } | 658 | } |
659 | |||
660 | bool IsDoorOpen(int door_id) { | ||
661 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
662 | |||
663 | return GetState().reachable_doors.count(door_id); | ||
664 | } | ||
665 | |||
666 | bool IsPaintingReachable(int painting_id) { | ||
667 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
668 | |||
669 | return GetState().reachable_paintings.count(painting_id); | ||
670 | } | ||
671 | |||
672 | const std::map<std::string, bool>& GetDoorRequirements(int door_id) { | ||
673 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
674 | |||
675 | return GetState().door_reports.at(door_id); | ||
676 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h index e73607f..c7857a0 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h | |||
@@ -1,8 +1,19 @@ | |||
1 | #ifndef TRACKER_STATE_H_8639BC90 | 1 | #ifndef TRACKER_STATE_H_8639BC90 |
2 | #define TRACKER_STATE_H_8639BC90 | 2 | #define TRACKER_STATE_H_8639BC90 |
3 | 3 | ||
4 | #include <map> | ||
5 | #include <string> | ||
6 | |||
7 | void ResetReachabilityRequirements(); | ||
8 | |||
4 | void RecalculateReachability(); | 9 | void RecalculateReachability(); |
5 | 10 | ||
6 | bool IsLocationReachable(int location_id); | 11 | bool IsLocationReachable(int location_id); |
7 | 12 | ||
13 | bool IsDoorOpen(int door_id); | ||
14 | |||
15 | bool IsPaintingReachable(int painting_id); | ||
16 | |||
17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); | ||
18 | |||
8 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | 19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ |
diff --git a/src/version.h b/src/version.h index db75351..7aab91b 100644 --- a/src/version.h +++ b/src/version.h | |||
@@ -1,7 +1,7 @@ | |||
1 | #ifndef VERSION_H_C757E53C | 1 | #ifndef VERSION_H_C757E53C |
2 | #define VERSION_H_C757E53C | 2 | #define VERSION_H_C757E53C |
3 | 3 | ||
4 | #include <iostream> | 4 | #include <sstream> |
5 | #include <regex> | 5 | #include <regex> |
6 | 6 | ||
7 | struct Version { | 7 | struct Version { |
@@ -23,6 +23,12 @@ struct Version { | |||
23 | } | 23 | } |
24 | } | 24 | } |
25 | 25 | ||
26 | std::string ToString() const { | ||
27 | std::ostringstream output; | ||
28 | output << "v" << major << "." << minor << "." << revision; | ||
29 | return output.str(); | ||
30 | } | ||
31 | |||
26 | bool operator<(const Version& rhs) const { | 32 | bool operator<(const Version& rhs) const { |
27 | return (major < rhs.major) || | 33 | return (major < rhs.major) || |
28 | (major == rhs.major && | 34 | (major == rhs.major && |
@@ -31,8 +37,6 @@ struct Version { | |||
31 | } | 37 | } |
32 | }; | 38 | }; |
33 | 39 | ||
34 | std::ostream& operator<<(std::ostream& out, const Version& ver); | 40 | constexpr const Version kTrackerVersion = Version(0, 10, 2); |
35 | |||
36 | constexpr const Version kTrackerVersion = Version(0, 9, 0); | ||
37 | 41 | ||
38 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file | 42 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file |
diff --git a/vendor/apclientpp b/vendor/apclientpp | |||
Subproject 8e54ae1bbef99fdbf9f674c79df416e38ca3cf6 | Subproject 6866205eac2c1d1cbb7e64453d8d7150ffed858 | ||
diff --git a/vendor/asio b/vendor/asio | |||
Subproject c465349fa5cd91a64bb369f5131ceacab2c0c1c | Subproject 12e0ce9e0500bf0f247dbd1ae89427265645607 | ||
diff --git a/vendor/quadtree/Box.h b/vendor/quadtree/Box.h new file mode 100644 index 0000000..65182bf --- /dev/null +++ b/vendor/quadtree/Box.h | |||
@@ -0,0 +1,67 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "Vector2.h" | ||
4 | |||
5 | namespace quadtree | ||
6 | { | ||
7 | |||
8 | template<typename T> | ||
9 | class Box | ||
10 | { | ||
11 | public: | ||
12 | T left; | ||
13 | T top; | ||
14 | T width; // Must be positive | ||
15 | T height; // Must be positive | ||
16 | |||
17 | constexpr Box(T Left = 0, T Top = 0, T Width = 0, T Height = 0) noexcept : | ||
18 | left(Left), top(Top), width(Width), height(Height) | ||
19 | { | ||
20 | |||
21 | } | ||
22 | |||
23 | constexpr Box(const Vector2<T>& position, const Vector2<T>& size) noexcept : | ||
24 | left(position.x), top(position.y), width(size.x), height(size.y) | ||
25 | { | ||
26 | |||
27 | } | ||
28 | |||
29 | constexpr T getRight() const noexcept | ||
30 | { | ||
31 | return left + width; | ||
32 | } | ||
33 | |||
34 | constexpr T getBottom() const noexcept | ||
35 | { | ||
36 | return top + height; | ||
37 | } | ||
38 | |||
39 | constexpr Vector2<T> getTopLeft() const noexcept | ||
40 | { | ||
41 | return Vector2<T>(left, top); | ||
42 | } | ||
43 | |||
44 | constexpr Vector2<T> getCenter() const noexcept | ||
45 | { | ||
46 | return Vector2<T>(left + width / 2, top + height / 2); | ||
47 | } | ||
48 | |||
49 | constexpr Vector2<T> getSize() const noexcept | ||
50 | { | ||
51 | return Vector2<T>(width, height); | ||
52 | } | ||
53 | |||
54 | constexpr bool contains(const Box<T>& box) const noexcept | ||
55 | { | ||
56 | return left <= box.left && box.getRight() <= getRight() && | ||
57 | top <= box.top && box.getBottom() <= getBottom(); | ||
58 | } | ||
59 | |||
60 | constexpr bool intersects(const Box<T>& box) const noexcept | ||
61 | { | ||
62 | return !(left >= box.getRight() || getRight() <= box.left || | ||
63 | top >= box.getBottom() || getBottom() <= box.top); | ||
64 | } | ||
65 | }; | ||
66 | |||
67 | } \ No newline at end of file | ||
diff --git a/vendor/quadtree/LICENSE b/vendor/quadtree/LICENSE new file mode 100644 index 0000000..1b226d3 --- /dev/null +++ b/vendor/quadtree/LICENSE | |||
@@ -0,0 +1,21 @@ | |||
1 | MIT License | ||
2 | |||
3 | Copyright (c) 2019 Pierre Vigier | ||
4 | |||
5 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
6 | of this software and associated documentation files (the "Software"), to deal | ||
7 | in the Software without restriction, including without limitation the rights | ||
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
9 | copies of the Software, and to permit persons to whom the Software is | ||
10 | furnished to do so, subject to the following conditions: | ||
11 | |||
12 | The above copyright notice and this permission notice shall be included in all | ||
13 | copies or substantial portions of the Software. | ||
14 | |||
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
21 | SOFTWARE. | ||
diff --git a/vendor/quadtree/Quadtree.h b/vendor/quadtree/Quadtree.h new file mode 100644 index 0000000..06097c5 --- /dev/null +++ b/vendor/quadtree/Quadtree.h | |||
@@ -0,0 +1,315 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include <cassert> | ||
4 | #include <algorithm> | ||
5 | #include <array> | ||
6 | #include <memory> | ||
7 | #include <type_traits> | ||
8 | #include <vector> | ||
9 | #include "Box.h" | ||
10 | |||
11 | namespace quadtree | ||
12 | { | ||
13 | |||
14 | template<typename T, typename GetBox, typename Equal = std::equal_to<T>, typename Float = float> | ||
15 | class Quadtree | ||
16 | { | ||
17 | static_assert(std::is_convertible_v<std::invoke_result_t<GetBox, const T&>, Box<Float>>, | ||
18 | "GetBox must be a callable of signature Box<Float>(const T&)"); | ||
19 | static_assert(std::is_convertible_v<std::invoke_result_t<Equal, const T&, const T&>, bool>, | ||
20 | "Equal must be a callable of signature bool(const T&, const T&)"); | ||
21 | static_assert(std::is_arithmetic_v<Float>); | ||
22 | |||
23 | public: | ||
24 | Quadtree(const Box<Float>& box, const GetBox& getBox = GetBox(), | ||
25 | const Equal& equal = Equal()) : | ||
26 | mBox(box), mRoot(std::make_unique<Node>()), mGetBox(getBox), mEqual(equal) | ||
27 | { | ||
28 | |||
29 | } | ||
30 | |||
31 | void add(const T& value) | ||
32 | { | ||
33 | add(mRoot.get(), 0, mBox, value); | ||
34 | } | ||
35 | |||
36 | void remove(const T& value) | ||
37 | { | ||
38 | remove(mRoot.get(), mBox, value); | ||
39 | } | ||
40 | |||
41 | std::vector<T> query(const Box<Float>& box) const | ||
42 | { | ||
43 | auto values = std::vector<T>(); | ||
44 | query(mRoot.get(), mBox, box, values); | ||
45 | return values; | ||
46 | } | ||
47 | |||
48 | std::vector<std::pair<T, T>> findAllIntersections() const | ||
49 | { | ||
50 | auto intersections = std::vector<std::pair<T, T>>(); | ||
51 | findAllIntersections(mRoot.get(), intersections); | ||
52 | return intersections; | ||
53 | } | ||
54 | |||
55 | Box<Float> getBox() const | ||
56 | { | ||
57 | return mBox; | ||
58 | } | ||
59 | |||
60 | private: | ||
61 | static constexpr auto Threshold = std::size_t(16); | ||
62 | static constexpr auto MaxDepth = std::size_t(8); | ||
63 | |||
64 | struct Node | ||
65 | { | ||
66 | std::array<std::unique_ptr<Node>, 4> children; | ||
67 | std::vector<T> values; | ||
68 | }; | ||
69 | |||
70 | Box<Float> mBox; | ||
71 | std::unique_ptr<Node> mRoot; | ||
72 | GetBox mGetBox; | ||
73 | Equal mEqual; | ||
74 | |||
75 | bool isLeaf(const Node* node) const | ||
76 | { | ||
77 | return !static_cast<bool>(node->children[0]); | ||
78 | } | ||
79 | |||
80 | Box<Float> computeBox(const Box<Float>& box, int i) const | ||
81 | { | ||
82 | auto origin = box.getTopLeft(); | ||
83 | auto childSize = box.getSize() / static_cast<Float>(2); | ||
84 | switch (i) | ||
85 | { | ||
86 | // North West | ||
87 | case 0: | ||
88 | return Box<Float>(origin, childSize); | ||
89 | // Norst East | ||
90 | case 1: | ||
91 | return Box<Float>(Vector2<Float>(origin.x + childSize.x, origin.y), childSize); | ||
92 | // South West | ||
93 | case 2: | ||
94 | return Box<Float>(Vector2<Float>(origin.x, origin.y + childSize.y), childSize); | ||
95 | // South East | ||
96 | case 3: | ||
97 | return Box<Float>(origin + childSize, childSize); | ||
98 | default: | ||
99 | assert(false && "Invalid child index"); | ||
100 | return Box<Float>(); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | int getQuadrant(const Box<Float>& nodeBox, const Box<Float>& valueBox) const | ||
105 | { | ||
106 | auto center = nodeBox.getCenter(); | ||
107 | // West | ||
108 | if (valueBox.getRight() < center.x) | ||
109 | { | ||
110 | // North West | ||
111 | if (valueBox.getBottom() < center.y) | ||
112 | return 0; | ||
113 | // South West | ||
114 | else if (valueBox.top >= center.y) | ||
115 | return 2; | ||
116 | // Not contained in any quadrant | ||
117 | else | ||
118 | return -1; | ||
119 | } | ||
120 | // East | ||
121 | else if (valueBox.left >= center.x) | ||
122 | { | ||
123 | // North East | ||
124 | if (valueBox.getBottom() < center.y) | ||
125 | return 1; | ||
126 | // South East | ||
127 | else if (valueBox.top >= center.y) | ||
128 | return 3; | ||
129 | // Not contained in any quadrant | ||
130 | else | ||
131 | return -1; | ||
132 | } | ||
133 | // Not contained in any quadrant | ||
134 | else | ||
135 | return -1; | ||
136 | } | ||
137 | |||
138 | void add(Node* node, std::size_t depth, const Box<Float>& box, const T& value) | ||
139 | { | ||
140 | assert(node != nullptr); | ||
141 | assert(box.contains(mGetBox(value))); | ||
142 | if (isLeaf(node)) | ||
143 | { | ||
144 | // Insert the value in this node if possible | ||
145 | if (depth >= MaxDepth || node->values.size() < Threshold) | ||
146 | node->values.push_back(value); | ||
147 | // Otherwise, we split and we try again | ||
148 | else | ||
149 | { | ||
150 | split(node, box); | ||
151 | add(node, depth, box, value); | ||
152 | } | ||
153 | } | ||
154 | else | ||
155 | { | ||
156 | auto i = getQuadrant(box, mGetBox(value)); | ||
157 | // Add the value in a child if the value is entirely contained in it | ||
158 | if (i != -1) | ||
159 | add(node->children[static_cast<std::size_t>(i)].get(), depth + 1, computeBox(box, i), value); | ||
160 | // Otherwise, we add the value in the current node | ||
161 | else | ||
162 | node->values.push_back(value); | ||
163 | } | ||
164 | } | ||
165 | |||
166 | void split(Node* node, const Box<Float>& box) | ||
167 | { | ||
168 | assert(node != nullptr); | ||
169 | assert(isLeaf(node) && "Only leaves can be split"); | ||
170 | // Create children | ||
171 | for (auto& child : node->children) | ||
172 | child = std::make_unique<Node>(); | ||
173 | // Assign values to children | ||
174 | auto newValues = std::vector<T>(); // New values for this node | ||
175 | for (const auto& value : node->values) | ||
176 | { | ||
177 | auto i = getQuadrant(box, mGetBox(value)); | ||
178 | if (i != -1) | ||
179 | node->children[static_cast<std::size_t>(i)]->values.push_back(value); | ||
180 | else | ||
181 | newValues.push_back(value); | ||
182 | } | ||
183 | node->values = std::move(newValues); | ||
184 | } | ||
185 | |||
186 | bool remove(Node* node, const Box<Float>& box, const T& value) | ||
187 | { | ||
188 | assert(node != nullptr); | ||
189 | assert(box.contains(mGetBox(value))); | ||
190 | if (isLeaf(node)) | ||
191 | { | ||
192 | // Remove the value from node | ||
193 | removeValue(node, value); | ||
194 | return true; | ||
195 | } | ||
196 | else | ||
197 | { | ||
198 | // Remove the value in a child if the value is entirely contained in it | ||
199 | auto i = getQuadrant(box, mGetBox(value)); | ||
200 | if (i != -1) | ||
201 | { | ||
202 | if (remove(node->children[static_cast<std::size_t>(i)].get(), computeBox(box, i), value)) | ||
203 | return tryMerge(node); | ||
204 | } | ||
205 | // Otherwise, we remove the value from the current node | ||
206 | else | ||
207 | removeValue(node, value); | ||
208 | return false; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | void removeValue(Node* node, const T& value) | ||
213 | { | ||
214 | // Find the value in node->values | ||
215 | auto it = std::find_if(std::begin(node->values), std::end(node->values), | ||
216 | [this, &value](const auto& rhs){ return mEqual(value, rhs); }); | ||
217 | assert(it != std::end(node->values) && "Trying to remove a value that is not present in the node"); | ||
218 | // Swap with the last element and pop back | ||
219 | *it = std::move(node->values.back()); | ||
220 | node->values.pop_back(); | ||
221 | } | ||
222 | |||
223 | bool tryMerge(Node* node) | ||
224 | { | ||
225 | assert(node != nullptr); | ||
226 | assert(!isLeaf(node) && "Only interior nodes can be merged"); | ||
227 | auto nbValues = node->values.size(); | ||
228 | for (const auto& child : node->children) | ||
229 | { | ||
230 | if (!isLeaf(child.get())) | ||
231 | return false; | ||
232 | nbValues += child->values.size(); | ||
233 | } | ||
234 | if (nbValues <= Threshold) | ||
235 | { | ||
236 | node->values.reserve(nbValues); | ||
237 | // Merge the values of all the children | ||
238 | for (const auto& child : node->children) | ||
239 | { | ||
240 | for (const auto& value : child->values) | ||
241 | node->values.push_back(value); | ||
242 | } | ||
243 | // Remove the children | ||
244 | for (auto& child : node->children) | ||
245 | child.reset(); | ||
246 | return true; | ||
247 | } | ||
248 | else | ||
249 | return false; | ||
250 | } | ||
251 | |||
252 | void query(Node* node, const Box<Float>& box, const Box<Float>& queryBox, std::vector<T>& values) const | ||
253 | { | ||
254 | assert(node != nullptr); | ||
255 | assert(queryBox.intersects(box)); | ||
256 | for (const auto& value : node->values) | ||
257 | { | ||
258 | if (queryBox.intersects(mGetBox(value))) | ||
259 | values.push_back(value); | ||
260 | } | ||
261 | if (!isLeaf(node)) | ||
262 | { | ||
263 | for (auto i = std::size_t(0); i < node->children.size(); ++i) | ||
264 | { | ||
265 | auto childBox = computeBox(box, static_cast<int>(i)); | ||
266 | if (queryBox.intersects(childBox)) | ||
267 | query(node->children[i].get(), childBox, queryBox, values); | ||
268 | } | ||
269 | } | ||
270 | } | ||
271 | |||
272 | void findAllIntersections(Node* node, std::vector<std::pair<T, T>>& intersections) const | ||
273 | { | ||
274 | // Find intersections between values stored in this node | ||
275 | // Make sure to not report the same intersection twice | ||
276 | for (auto i = std::size_t(0); i < node->values.size(); ++i) | ||
277 | { | ||
278 | for (auto j = std::size_t(0); j < i; ++j) | ||
279 | { | ||
280 | if (mGetBox(node->values[i]).intersects(mGetBox(node->values[j]))) | ||
281 | intersections.emplace_back(node->values[i], node->values[j]); | ||
282 | } | ||
283 | } | ||
284 | if (!isLeaf(node)) | ||
285 | { | ||
286 | // Values in this node can intersect values in descendants | ||
287 | for (const auto& child : node->children) | ||
288 | { | ||
289 | for (const auto& value : node->values) | ||
290 | findIntersectionsInDescendants(child.get(), value, intersections); | ||
291 | } | ||
292 | // Find intersections in children | ||
293 | for (const auto& child : node->children) | ||
294 | findAllIntersections(child.get(), intersections); | ||
295 | } | ||
296 | } | ||
297 | |||
298 | void findIntersectionsInDescendants(Node* node, const T& value, std::vector<std::pair<T, T>>& intersections) const | ||
299 | { | ||
300 | // Test against the values stored in this node | ||
301 | for (const auto& other : node->values) | ||
302 | { | ||
303 | if (mGetBox(value).intersects(mGetBox(other))) | ||
304 | intersections.emplace_back(value, other); | ||
305 | } | ||
306 | // Test against values stored into descendants of this node | ||
307 | if (!isLeaf(node)) | ||
308 | { | ||
309 | for (const auto& child : node->children) | ||
310 | findIntersectionsInDescendants(child.get(), value, intersections); | ||
311 | } | ||
312 | } | ||
313 | }; | ||
314 | |||
315 | } | ||
diff --git a/vendor/quadtree/Vector2.h b/vendor/quadtree/Vector2.h new file mode 100644 index 0000000..302d73e --- /dev/null +++ b/vendor/quadtree/Vector2.h | |||
@@ -0,0 +1,47 @@ | |||
1 | #pragma once | ||
2 | |||
3 | namespace quadtree | ||
4 | { | ||
5 | |||
6 | template<typename T> | ||
7 | class Vector2 | ||
8 | { | ||
9 | public: | ||
10 | T x; | ||
11 | T y; | ||
12 | |||
13 | constexpr Vector2<T>(T X = 0, T Y = 0) noexcept : x(X), y(Y) | ||
14 | { | ||
15 | |||
16 | } | ||
17 | |||
18 | constexpr Vector2<T>& operator+=(const Vector2<T>& other) noexcept | ||
19 | { | ||
20 | x += other.x; | ||
21 | y += other.y; | ||
22 | return *this; | ||
23 | } | ||
24 | |||
25 | constexpr Vector2<T>& operator/=(T t) noexcept | ||
26 | { | ||
27 | x /= t; | ||
28 | y /= t; | ||
29 | return *this; | ||
30 | } | ||
31 | }; | ||
32 | |||
33 | template<typename T> | ||
34 | constexpr Vector2<T> operator+(Vector2<T> lhs, const Vector2<T>& rhs) noexcept | ||
35 | { | ||
36 | lhs += rhs; | ||
37 | return lhs; | ||
38 | } | ||
39 | |||
40 | template<typename T> | ||
41 | constexpr Vector2<T> operator/(Vector2<T> vec, T t) noexcept | ||
42 | { | ||
43 | vec /= t; | ||
44 | return vec; | ||
45 | } | ||
46 | |||
47 | } | ||
diff --git a/vendor/wswrap b/vendor/wswrap | |||
Subproject 5c64761477bd1df380056e5ad3b20677616664b | Subproject 9b5b11e8dad01c5f5e465b15dfee87dd4138a5f | ||