diff options
| author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-06 15:54:41 -0400 |
|---|---|---|
| committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-06-06 15:54:41 -0400 |
| commit | 8ddab49cc13d809ca75dcd7f645661a3d3cb05c4 (patch) | |
| tree | ba1e5f3237dbb7cdc939c35e193f5e6e46845a77 | |
| parent | ac38dd0a5c394eefc39b7a8cf7b96762f18c8b31 (diff) | |
| parent | 6f5287b3921c843a6b322ccbdfcbef00a8f16980 (diff) | |
| download | lingo-ap-tracker-8ddab49cc13d809ca75dcd7f645661a3d3cb05c4.tar.gz lingo-ap-tracker-8ddab49cc13d809ca75dcd7f645661a3d3cb05c4.tar.bz2 lingo-ap-tracker-8ddab49cc13d809ca75dcd7f645661a3d3cb05c4.zip | |
Merge branch 'subway'
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rwxr-xr-x | assets/areas.yaml | 12 | ||||
| -rw-r--r-- | assets/owl.png | bin | 0 -> 439 bytes | |||
| -rw-r--r-- | assets/subway.png | bin | 0 -> 184533 bytes | |||
| -rw-r--r-- | assets/subway.yaml | 1041 | ||||
| -rw-r--r-- | src/ap_state.cpp | 120 | ||||
| -rw-r--r-- | src/ap_state.h | 11 | ||||
| -rw-r--r-- | src/area_popup.cpp | 40 | ||||
| -rw-r--r-- | src/game_data.cpp | 221 | ||||
| -rw-r--r-- | src/game_data.h | 39 | ||||
| -rw-r--r-- | src/logger.cpp | 32 | ||||
| -rw-r--r-- | src/logger.h | 8 | ||||
| -rw-r--r-- | src/main.cpp | 23 | ||||
| -rw-r--r-- | src/network_set.cpp | 30 | ||||
| -rw-r--r-- | src/network_set.h | 25 | ||||
| -rw-r--r-- | src/subway_map.cpp | 700 | ||||
| -rw-r--r-- | src/subway_map.h | 92 | ||||
| -rw-r--r-- | src/tracker_frame.cpp | 82 | ||||
| -rw-r--r-- | src/tracker_frame.h | 14 | ||||
| -rw-r--r-- | src/tracker_panel.cpp | 24 | ||||
| -rw-r--r-- | src/tracker_state.cpp | 400 | ||||
| -rw-r--r-- | src/tracker_state.h | 11 | ||||
| -rw-r--r-- | src/version.h | 12 | ||||
| -rw-r--r-- | vendor/quadtree/Box.h | 67 | ||||
| -rw-r--r-- | vendor/quadtree/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/quadtree/Quadtree.h | 315 | ||||
| -rw-r--r-- | vendor/quadtree/Vector2.h | 47 |
27 files changed, 3150 insertions, 241 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index a6f6342..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,10 +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/subway_map.cpp" | ||
| 47 | "src/network_set.cpp" | ||
| 46 | "vendor/whereami/whereami.c" | 48 | "vendor/whereami/whereami.c" |
| 47 | ) | 49 | ) |
| 48 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) | 50 | set_property(TARGET lingo_ap_tracker PROPERTY CXX_STANDARD 20) |
| 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..8f13f09 --- /dev/null +++ b/assets/subway.yaml | |||
| @@ -0,0 +1,1041 @@ | |||
| 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: [1652, 1393] | ||
| 1020 | room: Room Room | ||
| 1021 | door: Excavation | ||
| 1022 | - pos: [1592, 1442] | ||
| 1023 | room: Room Room | ||
| 1024 | door: Cellar Exit | ||
| 1025 | - pos: [1570, 938] | ||
| 1026 | room: Outside The Wise | ||
| 1027 | door: Wise Entrance | ||
| 1028 | - pos: [1653, 935] | ||
| 1029 | paintings: | ||
| 1030 | - clock_painting_3 | ||
| 1031 | - pos: [369, 605] | ||
| 1032 | room: Outside The Scientific | ||
| 1033 | door: Scientific Entrance | ||
| 1034 | - pos: [294, 602] | ||
| 1035 | paintings: | ||
| 1036 | - hi_solved_painting4 | ||
| 1037 | tags: | ||
| 1038 | - hi_scientific | ||
| 1039 | - pos: [814, 1001] | ||
| 1040 | room: Challenge Room | ||
| 1041 | door: Welcome Door | ||
| diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 8feb78b..0ce4582 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 | ||
| @@ -71,11 +70,12 @@ struct APState { | |||
| 71 | bool sunwarp_shuffle = false; | 70 | bool sunwarp_shuffle = false; |
| 72 | 71 | ||
| 73 | std::map<std::string, std::string> painting_mapping; | 72 | std::map<std::string, std::string> painting_mapping; |
| 73 | std::set<std::string> painting_codomain; | ||
| 74 | std::map<int, SunwarpMapping> sunwarp_mapping; | 74 | std::map<int, SunwarpMapping> sunwarp_mapping; |
| 75 | 75 | ||
| 76 | void Connect(std::string server, std::string player, std::string password) { | 76 | void Connect(std::string server, std::string player, std::string password) { |
| 77 | if (!initialized) { | 77 | if (!initialized) { |
| 78 | TrackerLog("Initializing APState..."); | 78 | wxLogVerbose("Initializing APState..."); |
| 79 | 79 | ||
| 80 | std::thread([this]() { | 80 | std::thread([this]() { |
| 81 | for (;;) { | 81 | for (;;) { |
| @@ -103,15 +103,16 @@ struct APState { | |||
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | tracked_data_storage_keys.push_back("PlayerPos"); | 105 | tracked_data_storage_keys.push_back("PlayerPos"); |
| 106 | tracked_data_storage_keys.push_back("Paintings"); | ||
| 106 | 107 | ||
| 107 | initialized = true; | 108 | initialized = true; |
| 108 | } | 109 | } |
| 109 | 110 | ||
| 110 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | 111 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); |
| 111 | TrackerLog("Connecting to Archipelago server (" + server + ")..."); | 112 | wxLogStatus("Connecting to Archipelago server (%s)...", server); |
| 112 | 113 | ||
| 113 | { | 114 | { |
| 114 | TrackerLog("Destroying old AP client..."); | 115 | wxLogVerbose("Destroying old AP client..."); |
| 115 | 116 | ||
| 116 | std::lock_guard client_guard(client_mutex); | 117 | std::lock_guard client_guard(client_mutex); |
| 117 | 118 | ||
| @@ -137,6 +138,7 @@ struct APState { | |||
| 137 | color_shuffle = false; | 138 | color_shuffle = false; |
| 138 | painting_shuffle = false; | 139 | painting_shuffle = false; |
| 139 | painting_mapping.clear(); | 140 | painting_mapping.clear(); |
| 141 | painting_codomain.clear(); | ||
| 140 | mastery_requirement = 21; | 142 | mastery_requirement = 21; |
| 141 | level_2_requirement = 223; | 143 | level_2_requirement = 223; |
| 142 | location_checks = kNORMAL_LOCATIONS; | 144 | location_checks = kNORMAL_LOCATIONS; |
| @@ -155,10 +157,10 @@ struct APState { | |||
| 155 | apclient->set_room_info_handler([this, player, password]() { | 157 | apclient->set_room_info_handler([this, player, password]() { |
| 156 | inventory.clear(); | 158 | inventory.clear(); |
| 157 | 159 | ||
| 158 | TrackerLog("Connected to Archipelago server. Authenticating as " + | 160 | wxLogStatus("Connected to Archipelago server. Authenticating as %s %s", |
| 159 | player + | 161 | player, |
| 160 | (password.empty() ? " without password" | 162 | (password.empty() ? "without password" |
| 161 | : " with password " + password)); | 163 | : "with password " + password)); |
| 162 | tracker_frame->SetStatusMessage( | 164 | tracker_frame->SetStatusMessage( |
| 163 | "Connected to Archipelago server. Authenticating..."); | 165 | "Connected to Archipelago server. Authenticating..."); |
| 164 | 166 | ||
| @@ -170,23 +172,23 @@ struct APState { | |||
| 170 | [this](const std::list<int64_t>& locations) { | 172 | [this](const std::list<int64_t>& locations) { |
| 171 | for (const int64_t location_id : locations) { | 173 | for (const int64_t location_id : locations) { |
| 172 | checked_locations.insert(location_id); | 174 | checked_locations.insert(location_id); |
| 173 | TrackerLog("Location: " + std::to_string(location_id)); | 175 | wxLogVerbose("Location: %lld", location_id); |
| 174 | } | 176 | } |
| 175 | 177 | ||
| 176 | RefreshTracker(); | 178 | RefreshTracker(false); |
| 177 | }); | 179 | }); |
| 178 | 180 | ||
| 179 | apclient->set_slot_disconnected_handler([this]() { | 181 | apclient->set_slot_disconnected_handler([this]() { |
| 180 | tracker_frame->SetStatusMessage( | 182 | tracker_frame->SetStatusMessage( |
| 181 | "Disconnected from Archipelago. Attempting to reconnect..."); | 183 | "Disconnected from Archipelago. Attempting to reconnect..."); |
| 182 | TrackerLog( | 184 | wxLogStatus( |
| 183 | "Slot disconnected from Archipelago. Attempting to reconnect..."); | 185 | "Slot disconnected from Archipelago. Attempting to reconnect..."); |
| 184 | }); | 186 | }); |
| 185 | 187 | ||
| 186 | apclient->set_socket_disconnected_handler([this]() { | 188 | apclient->set_socket_disconnected_handler([this]() { |
| 187 | tracker_frame->SetStatusMessage( | 189 | tracker_frame->SetStatusMessage( |
| 188 | "Disconnected from Archipelago. Attempting to reconnect..."); | 190 | "Disconnected from Archipelago. Attempting to reconnect..."); |
| 189 | TrackerLog( | 191 | wxLogStatus( |
| 190 | "Socket disconnected from Archipelago. Attempting to reconnect..."); | 192 | "Socket disconnected from Archipelago. Attempting to reconnect..."); |
| 191 | }); | 193 | }); |
| 192 | 194 | ||
| @@ -194,10 +196,10 @@ struct APState { | |||
| 194 | [this](const std::list<APClient::NetworkItem>& items) { | 196 | [this](const std::list<APClient::NetworkItem>& items) { |
| 195 | for (const APClient::NetworkItem& item : items) { | 197 | for (const APClient::NetworkItem& item : items) { |
| 196 | inventory[item.item]++; | 198 | inventory[item.item]++; |
| 197 | TrackerLog("Item: " + std::to_string(item.item)); | 199 | wxLogVerbose("Item: %lld", item.item); |
| 198 | } | 200 | } |
| 199 | 201 | ||
| 200 | RefreshTracker(); | 202 | RefreshTracker(false); |
| 201 | }); | 203 | }); |
| 202 | 204 | ||
| 203 | apclient->set_retrieved_handler( | 205 | apclient->set_retrieved_handler( |
| @@ -206,20 +208,20 @@ struct APState { | |||
| 206 | HandleDataStorage(key, value); | 208 | HandleDataStorage(key, value); |
| 207 | } | 209 | } |
| 208 | 210 | ||
| 209 | RefreshTracker(); | 211 | RefreshTracker(false); |
| 210 | }); | 212 | }); |
| 211 | 213 | ||
| 212 | apclient->set_set_reply_handler([this](const std::string& key, | 214 | apclient->set_set_reply_handler([this](const std::string& key, |
| 213 | const nlohmann::json& value, | 215 | const nlohmann::json& value, |
| 214 | const nlohmann::json&) { | 216 | const nlohmann::json&) { |
| 215 | HandleDataStorage(key, value); | 217 | HandleDataStorage(key, value); |
| 216 | RefreshTracker(); | 218 | RefreshTracker(false); |
| 217 | }); | 219 | }); |
| 218 | 220 | ||
| 219 | apclient->set_slot_connected_handler([this]( | 221 | apclient->set_slot_connected_handler([this]( |
| 220 | const nlohmann::json& slot_data) { | 222 | const nlohmann::json& slot_data) { |
| 221 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | 223 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); |
| 222 | TrackerLog("Connected to Archipelago!"); | 224 | wxLogStatus("Connected to Archipelago!"); |
| 223 | 225 | ||
| 224 | data_storage_prefix = | 226 | data_storage_prefix = |
| 225 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; | 227 | "Lingo_" + std::to_string(apclient->get_player_number()) + "_"; |
| @@ -253,6 +255,7 @@ struct APState { | |||
| 253 | for (const auto& mapping_it : | 255 | for (const auto& mapping_it : |
| 254 | slot_data["painting_entrance_to_exit"].items()) { | 256 | slot_data["painting_entrance_to_exit"].items()) { |
| 255 | painting_mapping[mapping_it.key()] = mapping_it.value(); | 257 | painting_mapping[mapping_it.key()] = mapping_it.value(); |
| 258 | painting_codomain.insert(mapping_it.value()); | ||
| 256 | } | 259 | } |
| 257 | } | 260 | } |
| 258 | 261 | ||
| @@ -271,7 +274,8 @@ struct APState { | |||
| 271 | connected = true; | 274 | connected = true; |
| 272 | has_connection_result = true; | 275 | has_connection_result = true; |
| 273 | 276 | ||
| 274 | RefreshTracker(); | 277 | ResetReachabilityRequirements(); |
| 278 | RefreshTracker(true); | ||
| 275 | 279 | ||
| 276 | std::list<std::string> corrected_keys; | 280 | std::list<std::string> corrected_keys; |
| 277 | for (const std::string& key : tracked_data_storage_keys) { | 281 | for (const std::string& key : tracked_data_storage_keys) { |
| @@ -323,7 +327,7 @@ struct APState { | |||
| 323 | } | 327 | } |
| 324 | 328 | ||
| 325 | std::string full_message = hatkirby::implode(error_messages, " "); | 329 | std::string full_message = hatkirby::implode(error_messages, " "); |
| 326 | TrackerLog(full_message); | 330 | wxLogError(wxString(full_message)); |
| 327 | 331 | ||
| 328 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | 332 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); |
| 329 | }); | 333 | }); |
| @@ -341,8 +345,7 @@ struct APState { | |||
| 341 | DestroyClient(); | 345 | DestroyClient(); |
| 342 | 346 | ||
| 343 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | 347 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); |
| 344 | 348 | wxLogStatus("Timeout while connecting to Archipelago server."); | |
| 345 | TrackerLog("Timeout while connecting to Archipelago server."); | ||
| 346 | wxMessageBox("Timeout while connecting to Archipelago server.", | 349 | wxMessageBox("Timeout while connecting to Archipelago server.", |
| 347 | "Connection failed", wxOK | wxICON_ERROR); | 350 | "Connection failed", wxOK | wxICON_ERROR); |
| 348 | } | 351 | } |
| @@ -353,7 +356,7 @@ struct APState { | |||
| 353 | } | 356 | } |
| 354 | 357 | ||
| 355 | if (connected) { | 358 | if (connected) { |
| 356 | RefreshTracker(); | 359 | RefreshTracker(false); |
| 357 | } else { | 360 | } else { |
| 358 | client_active = false; | 361 | client_active = false; |
| 359 | } | 362 | } |
| @@ -362,12 +365,11 @@ struct APState { | |||
| 362 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { | 365 | void HandleDataStorage(const std::string& key, const nlohmann::json& value) { |
| 363 | if (value.is_boolean()) { | 366 | if (value.is_boolean()) { |
| 364 | data_storage[key] = value.get<bool>(); | 367 | data_storage[key] = value.get<bool>(); |
| 365 | TrackerLog("Data storage " + key + " retrieved as " + | 368 | wxLogVerbose("Data storage %s retrieved as %s", key, |
| 366 | (value.get<bool>() ? "true" : "false")); | 369 | (value.get<bool>() ? "true" : "false")); |
| 367 | } else if (value.is_number()) { | 370 | } else if (value.is_number()) { |
| 368 | data_storage[key] = value.get<int>(); | 371 | data_storage[key] = value.get<int>(); |
| 369 | TrackerLog("Data storage " + key + " retrieved as " + | 372 | wxLogVerbose("Data storage %s retrieved as %d", key, value.get<int>()); |
| 370 | std::to_string(value.get<int>())); | ||
| 371 | } else if (value.is_object()) { | 373 | } else if (value.is_object()) { |
| 372 | if (key.ends_with("PlayerPos")) { | 374 | if (key.ends_with("PlayerPos")) { |
| 373 | auto map_value = value.get<std::map<std::string, int>>(); | 375 | auto map_value = value.get<std::map<std::string, int>>(); |
| @@ -376,7 +378,7 @@ struct APState { | |||
| 376 | data_storage[key] = value.get<std::map<std::string, int>>(); | 378 | data_storage[key] = value.get<std::map<std::string, int>>(); |
| 377 | } | 379 | } |
| 378 | 380 | ||
| 379 | TrackerLog("Data storage " + key + " retrieved as dictionary"); | 381 | wxLogVerbose("Data storage %s retrieved as dictionary", key); |
| 380 | } else if (value.is_null()) { | 382 | } else if (value.is_null()) { |
| 381 | if (key.ends_with("PlayerPos")) { | 383 | if (key.ends_with("PlayerPos")) { |
| 382 | player_pos = std::nullopt; | 384 | player_pos = std::nullopt; |
| @@ -384,7 +386,19 @@ struct APState { | |||
| 384 | data_storage.erase(key); | 386 | data_storage.erase(key); |
| 385 | } | 387 | } |
| 386 | 388 | ||
| 387 | TrackerLog("Data storage " + key + " retrieved as null"); | 389 | wxLogVerbose("Data storage %s retrieved as null", key); |
| 390 | } else if (value.is_array()) { | ||
| 391 | auto list_value = value.get<std::vector<std::string>>(); | ||
| 392 | |||
| 393 | if (key.ends_with("Paintings")) { | ||
| 394 | data_storage[key] = | ||
| 395 | std::set<std::string>(list_value.begin(), list_value.end()); | ||
| 396 | } else { | ||
| 397 | data_storage[key] = list_value; | ||
| 398 | } | ||
| 399 | |||
| 400 | wxLogVerbose("Data storage %s retrieved as list: [%s]", key, | ||
| 401 | hatkirby::implode(list_value, ", ")); | ||
| 388 | } | 402 | } |
| 389 | } | 403 | } |
| 390 | 404 | ||
| @@ -407,22 +421,46 @@ struct APState { | |||
| 407 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); | 421 | return data_storage.count(key) && std::any_cast<bool>(data_storage.at(key)); |
| 408 | } | 422 | } |
| 409 | 423 | ||
| 410 | void RefreshTracker() { | 424 | const std::set<std::string>& GetCheckedPaintings() { |
| 411 | TrackerLog("Refreshing display..."); | 425 | std::string key = data_storage_prefix + "Paintings"; |
| 426 | if (!data_storage.count(key)) { | ||
| 427 | data_storage[key] = std::set<std::string>(); | ||
| 428 | } | ||
| 429 | |||
| 430 | return std::any_cast<const std::set<std::string>&>(data_storage.at(key)); | ||
| 431 | } | ||
| 432 | |||
| 433 | bool IsPaintingChecked(const std::string& painting_id) { | ||
| 434 | const auto& checked_paintings = GetCheckedPaintings(); | ||
| 435 | |||
| 436 | return checked_paintings.count(painting_id) || | ||
| 437 | (painting_mapping.count(painting_id) && | ||
| 438 | checked_paintings.count(painting_mapping.at(painting_id))); | ||
| 439 | } | ||
| 440 | |||
| 441 | void RefreshTracker(bool reset) { | ||
| 442 | wxLogVerbose("Refreshing display..."); | ||
| 412 | 443 | ||
| 413 | RecalculateReachability(); | 444 | RecalculateReachability(); |
| 414 | tracker_frame->UpdateIndicators(); | 445 | |
| 446 | if (reset) { | ||
| 447 | tracker_frame->ResetIndicators(); | ||
| 448 | } else { | ||
| 449 | tracker_frame->UpdateIndicators(); | ||
| 450 | } | ||
| 415 | } | 451 | } |
| 416 | 452 | ||
| 417 | int64_t GetItemId(const std::string& item_name) { | 453 | int64_t GetItemId(const std::string& item_name) { |
| 418 | int64_t ap_id = apclient->get_item_id(item_name); | 454 | int64_t ap_id = apclient->get_item_id(item_name); |
| 419 | if (ap_id == APClient::INVALID_NAME_ID) { | 455 | if (ap_id == APClient::INVALID_NAME_ID) { |
| 420 | TrackerLog("Could not find AP item ID for " + item_name); | 456 | wxLogError("Could not find AP item ID for %s", item_name); |
| 421 | } | 457 | } |
| 422 | 458 | ||
| 423 | return ap_id; | 459 | return ap_id; |
| 424 | } | 460 | } |
| 425 | 461 | ||
| 462 | std::string GetItemName(int id) { return apclient->get_item_name(id); } | ||
| 463 | |||
| 426 | bool HasReachedGoal() { | 464 | bool HasReachedGoal() { |
| 427 | return data_storage.count(victory_data_storage_key) && | 465 | return data_storage.count(victory_data_storage_key) && |
| 428 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == | 466 | std::any_cast<int>(data_storage.at(victory_data_storage_key)) == |
| @@ -461,16 +499,32 @@ bool AP_HasItem(int item_id, int quantity) { | |||
| 461 | return GetState().HasItem(item_id, quantity); | 499 | return GetState().HasItem(item_id, quantity); |
| 462 | } | 500 | } |
| 463 | 501 | ||
| 502 | std::string AP_GetItemName(int item_id) { | ||
| 503 | return GetState().GetItemName(item_id); | ||
| 504 | } | ||
| 505 | |||
| 464 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 506 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
| 465 | 507 | ||
| 466 | bool AP_IsColorShuffle() { return GetState().color_shuffle; } | 508 | bool AP_IsColorShuffle() { return GetState().color_shuffle; } |
| 467 | 509 | ||
| 468 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } | 510 | bool AP_IsPaintingShuffle() { return GetState().painting_shuffle; } |
| 469 | 511 | ||
| 470 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | 512 | const std::map<std::string, std::string>& AP_GetPaintingMapping() { |
| 471 | return GetState().painting_mapping; | 513 | return GetState().painting_mapping; |
| 472 | } | 514 | } |
| 473 | 515 | ||
| 516 | bool AP_IsPaintingMappedTo(const std::string& painting_id) { | ||
| 517 | return GetState().painting_codomain.count(painting_id); | ||
| 518 | } | ||
| 519 | |||
| 520 | const std::set<std::string>& AP_GetCheckedPaintings() { | ||
| 521 | return GetState().GetCheckedPaintings(); | ||
| 522 | } | ||
| 523 | |||
| 524 | bool AP_IsPaintingChecked(const std::string& painting_id) { | ||
| 525 | return GetState().IsPaintingChecked(painting_id); | ||
| 526 | } | ||
| 527 | |||
| 474 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } | 528 | int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } |
| 475 | 529 | ||
| 476 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } | 530 | int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } |
| diff --git a/src/ap_state.h b/src/ap_state.h index 6667e0d..7af7395 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,13 +49,21 @@ 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_IsColorShuffle(); | 56 | bool AP_IsColorShuffle(); |
| 54 | 57 | ||
| 55 | bool AP_IsPaintingShuffle(); | 58 | bool AP_IsPaintingShuffle(); |
| 56 | 59 | ||
| 57 | const std::map<std::string, std::string> AP_GetPaintingMapping(); | 60 | const std::map<std::string, std::string>& AP_GetPaintingMapping(); |
| 61 | |||
| 62 | bool AP_IsPaintingMappedTo(const std::string& painting_id); | ||
| 63 | |||
| 64 | const std::set<std::string>& AP_GetCheckedPaintings(); | ||
| 65 | |||
| 66 | bool AP_IsPaintingChecked(const std::string& painting_id); | ||
| 58 | 67 | ||
| 59 | int AP_GetMasteryRequirement(); | 68 | int AP_GetMasteryRequirement(); |
| 60 | 69 | ||
| 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 be31b8f..5776c6c 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 | } |
| @@ -44,11 +47,14 @@ struct GameData { | |||
| 44 | std::vector<Door> doors_; | 47 | std::vector<Door> doors_; |
| 45 | std::vector<Panel> panels_; | 48 | std::vector<Panel> panels_; |
| 46 | std::vector<MapArea> map_areas_; | 49 | std::vector<MapArea> map_areas_; |
| 50 | std::vector<SubwayItem> subway_items_; | ||
| 51 | std::vector<PaintingExit> paintings_; | ||
| 47 | 52 | ||
| 48 | std::map<std::string, int> room_by_id_; | 53 | std::map<std::string, int> room_by_id_; |
| 49 | std::map<std::string, int> door_by_id_; | 54 | std::map<std::string, int> door_by_id_; |
| 50 | std::map<std::string, int> panel_by_id_; | 55 | std::map<std::string, int> panel_by_id_; |
| 51 | std::map<std::string, int> area_by_id_; | 56 | std::map<std::string, int> area_by_id_; |
| 57 | std::map<std::string, int> painting_by_id_; | ||
| 52 | 58 | ||
| 53 | std::vector<int> door_definition_order_; | 59 | std::vector<int> door_definition_order_; |
| 54 | 60 | ||
| @@ -61,6 +67,9 @@ struct GameData { | |||
| 61 | 67 | ||
| 62 | std::vector<int> sunwarp_doors_; | 68 | std::vector<int> sunwarp_doors_; |
| 63 | 69 | ||
| 70 | std::map<std::string, int> subway_item_by_painting_; | ||
| 71 | std::map<SubwaySunwarp, int> subway_item_by_sunwarp_; | ||
| 72 | |||
| 64 | bool loaded_area_data_ = false; | 73 | bool loaded_area_data_ = false; |
| 65 | std::set<std::string> malconfigured_areas_; | 74 | std::set<std::string> malconfigured_areas_; |
| 66 | 75 | ||
| @@ -79,9 +88,7 @@ struct GameData { | |||
| 79 | ap_id_by_color_[GetColorForString(input_name)] = | 88 | ap_id_by_color_[GetColorForString(input_name)] = |
| 80 | ids_config["special_items"][color_name].as<int>(); | 89 | ids_config["special_items"][color_name].as<int>(); |
| 81 | } else { | 90 | } else { |
| 82 | std::ostringstream errmsg; | 91 | wxLogError("Missing AP item ID for color %s", color_name); |
| 83 | errmsg << "Missing AP item ID for color " << color_name; | ||
| 84 | TrackerLog(errmsg.str()); | ||
| 85 | } | 92 | } |
| 86 | }; | 93 | }; |
| 87 | 94 | ||
| @@ -156,8 +163,9 @@ struct GameData { | |||
| 156 | } | 163 | } |
| 157 | default: { | 164 | default: { |
| 158 | // This shouldn't happen. | 165 | // This shouldn't happen. |
| 159 | std::cout << "Error reading game data: " << entrance_it | 166 | std::ostringstream formatted; |
| 160 | << std::endl; | 167 | formatted << entrance_it; |
| 168 | wxLogError("Error reading game data: %s", formatted.str()); | ||
| 161 | break; | 169 | break; |
| 162 | } | 170 | } |
| 163 | } | 171 | } |
| @@ -282,10 +290,8 @@ struct GameData { | |||
| 282 | [panels_[panel_id].name] | 290 | [panels_[panel_id].name] |
| 283 | .as<int>(); | 291 | .as<int>(); |
| 284 | } else { | 292 | } else { |
| 285 | std::ostringstream errmsg; | 293 | wxLogError("Missing AP location ID for panel %s - %s", |
| 286 | errmsg << "Missing AP location ID for panel " | 294 | rooms_[room_id].name, panels_[panel_id].name); |
| 287 | << rooms_[room_id].name << " - " << panels_[panel_id].name; | ||
| 288 | TrackerLog(errmsg.str()); | ||
| 289 | } | 295 | } |
| 290 | } | 296 | } |
| 291 | } | 297 | } |
| @@ -348,10 +354,8 @@ struct GameData { | |||
| 348 | [doors_[door_id].name]["item"] | 354 | [doors_[door_id].name]["item"] |
| 349 | .as<int>(); | 355 | .as<int>(); |
| 350 | } else { | 356 | } else { |
| 351 | std::ostringstream errmsg; | 357 | wxLogError("Missing AP item ID for door %s - %s", |
| 352 | errmsg << "Missing AP item ID for door " << rooms_[room_id].name | 358 | rooms_[room_id].name, doors_[door_id].name); |
| 353 | << " - " << doors_[door_id].name; | ||
| 354 | TrackerLog(errmsg.str()); | ||
| 355 | } | 359 | } |
| 356 | } | 360 | } |
| 357 | 361 | ||
| @@ -365,10 +369,8 @@ struct GameData { | |||
| 365 | ids_config["door_groups"][doors_[door_id].group_name] | 369 | ids_config["door_groups"][doors_[door_id].group_name] |
| 366 | .as<int>(); | 370 | .as<int>(); |
| 367 | } else { | 371 | } else { |
| 368 | std::ostringstream errmsg; | 372 | wxLogError("Missing AP item ID for door group %s", |
| 369 | errmsg << "Missing AP item ID for door group " | 373 | doors_[door_id].group_name); |
| 370 | << doors_[door_id].group_name; | ||
| 371 | TrackerLog(errmsg.str()); | ||
| 372 | } | 374 | } |
| 373 | } | 375 | } |
| 374 | 376 | ||
| @@ -378,13 +380,11 @@ struct GameData { | |||
| 378 | } else if (!door_it.second["skip_location"] && | 380 | } else if (!door_it.second["skip_location"] && |
| 379 | !door_it.second["event"]) { | 381 | !door_it.second["event"]) { |
| 380 | if (has_external_panels) { | 382 | if (has_external_panels) { |
| 381 | std::ostringstream errmsg; | 383 | wxLogError( |
| 382 | errmsg | 384 | "%s - %s has panels from other rooms but does not have an " |
| 383 | << rooms_[room_id].name << " - " << doors_[door_id].name | 385 | "explicit location name and is not marked skip_location or " |
| 384 | << " has panels from other rooms but does not have an " | 386 | "event", |
| 385 | "explicit " | 387 | rooms_[room_id].name, doors_[door_id].name); |
| 386 | "location name and is not marked skip_location or event"; | ||
| 387 | TrackerLog(errmsg.str()); | ||
| 388 | } | 388 | } |
| 389 | 389 | ||
| 390 | doors_[door_id].location_name = | 390 | doors_[door_id].location_name = |
| @@ -404,10 +404,8 @@ struct GameData { | |||
| 404 | [doors_[door_id].name]["location"] | 404 | [doors_[door_id].name]["location"] |
| 405 | .as<int>(); | 405 | .as<int>(); |
| 406 | } else { | 406 | } else { |
| 407 | std::ostringstream errmsg; | 407 | wxLogError("Missing AP location ID for door %s - %s", |
| 408 | errmsg << "Missing AP location ID for door " | 408 | rooms_[room_id].name, doors_[door_id].name); |
| 409 | << rooms_[room_id].name << " - " << doors_[door_id].name; | ||
| 410 | TrackerLog(errmsg.str()); | ||
| 411 | } | 409 | } |
| 412 | } | 410 | } |
| 413 | 411 | ||
| @@ -428,12 +426,13 @@ struct GameData { | |||
| 428 | 426 | ||
| 429 | if (room_it.second["paintings"]) { | 427 | if (room_it.second["paintings"]) { |
| 430 | for (const auto &painting : room_it.second["paintings"]) { | 428 | for (const auto &painting : room_it.second["paintings"]) { |
| 431 | std::string painting_id = painting["id"].as<std::string>(); | 429 | std::string internal_id = painting["id"].as<std::string>(); |
| 432 | room_by_painting_[painting_id] = room_id; | ||
| 433 | 430 | ||
| 434 | if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { | 431 | if ((!painting["exit_only"] || !painting["exit_only"].as<bool>()) && |
| 435 | PaintingExit painting_exit; | 432 | (!painting["disable"] || !painting["disable"].as<bool>())) { |
| 436 | painting_exit.id = painting_id; | 433 | int painting_id = AddOrGetPainting(internal_id); |
| 434 | PaintingExit &painting_exit = paintings_[painting_id]; | ||
| 435 | painting_exit.room = room_id; | ||
| 437 | 436 | ||
| 438 | if (painting["required_door"]) { | 437 | if (painting["required_door"]) { |
| 439 | std::string rd_room = rooms_[room_id].name; | 438 | std::string rd_room = rooms_[room_id].name; |
| @@ -445,7 +444,7 @@ struct GameData { | |||
| 445 | rd_room, painting["required_door"]["door"].as<std::string>()); | 444 | rd_room, painting["required_door"]["door"].as<std::string>()); |
| 446 | } | 445 | } |
| 447 | 446 | ||
| 448 | rooms_[room_id].paintings.push_back(painting_exit); | 447 | rooms_[room_id].paintings.push_back(painting_exit.id); |
| 449 | } | 448 | } |
| 450 | } | 449 | } |
| 451 | } | 450 | } |
| @@ -473,10 +472,8 @@ struct GameData { | |||
| 473 | progressive_item_id = | 472 | progressive_item_id = |
| 474 | ids_config["progression"][progressive_item_name].as<int>(); | 473 | ids_config["progression"][progressive_item_name].as<int>(); |
| 475 | } else { | 474 | } else { |
| 476 | std::ostringstream errmsg; | 475 | wxLogError("Missing AP item ID for progressive item %s", |
| 477 | errmsg << "Missing AP item ID for progressive item " | 476 | progressive_item_name); |
| 478 | << progressive_item_name; | ||
| 479 | TrackerLog(errmsg.str()); | ||
| 480 | } | 477 | } |
| 481 | 478 | ||
| 482 | int index = 1; | 479 | int index = 1; |
| @@ -618,11 +615,98 @@ struct GameData { | |||
| 618 | } | 615 | } |
| 619 | } | 616 | } |
| 620 | 617 | ||
| 618 | for (const Room &room : rooms_) { | ||
| 619 | std::string area_name = room.name; | ||
| 620 | if (fold_areas.count(room.name)) { | ||
| 621 | int fold_area_id = fold_areas[room.name]; | ||
| 622 | area_name = map_areas_[fold_area_id].name; | ||
| 623 | } | ||
| 624 | |||
| 625 | if (!room.paintings.empty()) { | ||
| 626 | int area_id = AddOrGetArea(area_name); | ||
| 627 | MapArea &map_area = map_areas_[area_id]; | ||
| 628 | |||
| 629 | for (int painting_id : room.paintings) { | ||
| 630 | map_area.paintings.push_back(painting_id); | ||
| 631 | } | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 621 | // Report errors. | 635 | // Report errors. |
| 622 | for (const std::string &area : malconfigured_areas_) { | 636 | for (const std::string &area : malconfigured_areas_) { |
| 623 | std::ostringstream errstr; | 637 | wxLogError("Area data not found for: %s", area); |
| 624 | errstr << "Area data not found for: " << area; | 638 | } |
| 625 | TrackerLog(errstr.str()); | 639 | |
| 640 | // Read in subway items. | ||
| 641 | YAML::Node subway_config = | ||
| 642 | YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); | ||
| 643 | for (const auto &subway_it : subway_config) { | ||
| 644 | SubwayItem subway_item; | ||
| 645 | subway_item.id = subway_items_.size(); | ||
| 646 | subway_item.x = subway_it["pos"][0].as<int>(); | ||
| 647 | subway_item.y = subway_it["pos"][1].as<int>(); | ||
| 648 | |||
| 649 | if (subway_it["door"]) { | ||
| 650 | subway_item.door = AddOrGetDoor(subway_it["room"].as<std::string>(), | ||
| 651 | subway_it["door"].as<std::string>()); | ||
| 652 | } | ||
| 653 | |||
| 654 | if (subway_it["paintings"]) { | ||
| 655 | for (const auto &painting_it : subway_it["paintings"]) { | ||
| 656 | std::string painting_id = painting_it.as<std::string>(); | ||
| 657 | |||
| 658 | subway_item.paintings.push_back(painting_id); | ||
| 659 | subway_item_by_painting_[painting_id] = subway_item.id; | ||
| 660 | } | ||
| 661 | } | ||
| 662 | |||
| 663 | if (subway_it["tags"]) { | ||
| 664 | for (const auto &tag_it : subway_it["tags"]) { | ||
| 665 | subway_item.tags.push_back(tag_it.as<std::string>()); | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | if (subway_it["sunwarp"]) { | ||
| 670 | SubwaySunwarp sunwarp; | ||
| 671 | sunwarp.dots = subway_it["sunwarp"]["dots"].as<int>(); | ||
| 672 | |||
| 673 | std::string sunwarp_type = | ||
| 674 | subway_it["sunwarp"]["type"].as<std::string>(); | ||
| 675 | if (sunwarp_type == "final") { | ||
| 676 | sunwarp.type = SubwaySunwarpType::kFinal; | ||
| 677 | } else if (sunwarp_type == "exit") { | ||
| 678 | sunwarp.type = SubwaySunwarpType::kExit; | ||
| 679 | } else { | ||
| 680 | sunwarp.type = SubwaySunwarpType::kEnter; | ||
| 681 | } | ||
| 682 | |||
| 683 | subway_item.sunwarp = sunwarp; | ||
| 684 | |||
| 685 | subway_item_by_sunwarp_[sunwarp] = subway_item.id; | ||
| 686 | |||
| 687 | subway_item.door = | ||
| 688 | AddOrGetDoor("Sunwarps", std::to_string(sunwarp.dots) + " Sunwarp"); | ||
| 689 | } | ||
| 690 | |||
| 691 | if (subway_it["special"]) { | ||
| 692 | subway_item.special = subway_it["special"].as<std::string>(); | ||
| 693 | } | ||
| 694 | |||
| 695 | subway_items_.push_back(subway_item); | ||
| 696 | } | ||
| 697 | |||
| 698 | // Find singleton subway tags. | ||
| 699 | std::map<std::string, std::set<int>> subway_tags; | ||
| 700 | for (const SubwayItem &subway_item : subway_items_) { | ||
| 701 | for (const std::string &tag : subway_item.tags) { | ||
| 702 | subway_tags[tag].insert(subway_item.id); | ||
| 703 | } | ||
| 704 | } | ||
| 705 | |||
| 706 | for (const auto &[tag, items] : subway_tags) { | ||
| 707 | if (items.size() == 1) { | ||
| 708 | wxLogWarning("Singleton subway item tag: %s", tag); | ||
| 709 | } | ||
| 626 | } | 710 | } |
| 627 | } | 711 | } |
| 628 | 712 | ||
| @@ -639,8 +723,10 @@ struct GameData { | |||
| 639 | std::string full_name = room + " - " + door; | 723 | std::string full_name = room + " - " + door; |
| 640 | 724 | ||
| 641 | if (!door_by_id_.count(full_name)) { | 725 | if (!door_by_id_.count(full_name)) { |
| 726 | int door_id = doors_.size(); | ||
| 642 | door_by_id_[full_name] = doors_.size(); | 727 | door_by_id_[full_name] = doors_.size(); |
| 643 | doors_.push_back({.room = AddOrGetRoom(room), .name = door}); | 728 | doors_.push_back( |
| 729 | {.id = door_id, .room = AddOrGetRoom(room), .name = door}); | ||
| 644 | } | 730 | } |
| 645 | 731 | ||
| 646 | return door_by_id_[full_name]; | 732 | return door_by_id_[full_name]; |
| @@ -672,6 +758,16 @@ struct GameData { | |||
| 672 | 758 | ||
| 673 | return area_by_id_[area]; | 759 | return area_by_id_[area]; |
| 674 | } | 760 | } |
| 761 | |||
| 762 | int AddOrGetPainting(std::string internal_id) { | ||
| 763 | if (!painting_by_id_.count(internal_id)) { | ||
| 764 | int painting_id = paintings_.size(); | ||
| 765 | painting_by_id_[internal_id] = painting_id; | ||
| 766 | paintings_.push_back({.id = painting_id, .internal_id = internal_id}); | ||
| 767 | } | ||
| 768 | |||
| 769 | return painting_by_id_[internal_id]; | ||
| 770 | } | ||
| 675 | }; | 771 | }; |
| 676 | 772 | ||
| 677 | GameData &GetState() { | 773 | GameData &GetState() { |
| @@ -681,6 +777,10 @@ GameData &GetState() { | |||
| 681 | 777 | ||
| 682 | } // namespace | 778 | } // namespace |
| 683 | 779 | ||
| 780 | bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { | ||
| 781 | return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); | ||
| 782 | } | ||
| 783 | |||
| 684 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } | 784 | const std::vector<MapArea> &GD_GetMapAreas() { return GetState().map_areas_; } |
| 685 | 785 | ||
| 686 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } | 786 | const MapArea &GD_GetMapArea(int id) { return GetState().map_areas_.at(id); } |
| @@ -703,8 +803,12 @@ const Panel &GD_GetPanel(int panel_id) { | |||
| 703 | return GetState().panels_.at(panel_id); | 803 | return GetState().panels_.at(panel_id); |
| 704 | } | 804 | } |
| 705 | 805 | ||
| 706 | int GD_GetRoomForPainting(const std::string &painting_id) { | 806 | const PaintingExit &GD_GetPaintingExit(int painting_id) { |
| 707 | return GetState().room_by_painting_.at(painting_id); | 807 | return GetState().paintings_.at(painting_id); |
| 808 | } | ||
| 809 | |||
| 810 | int GD_GetPaintingByName(const std::string &name) { | ||
| 811 | return GetState().painting_by_id_.at(name); | ||
| 708 | } | 812 | } |
| 709 | 813 | ||
| 710 | const std::vector<int> &GD_GetAchievementPanels() { | 814 | const std::vector<int> &GD_GetAchievementPanels() { |
| @@ -722,3 +826,24 @@ const std::vector<int> &GD_GetSunwarpDoors() { | |||
| 722 | int GD_GetRoomForSunwarp(int index) { | 826 | int GD_GetRoomForSunwarp(int index) { |
| 723 | return GetState().room_by_sunwarp_.at(index); | 827 | return GetState().room_by_sunwarp_.at(index); |
| 724 | } | 828 | } |
| 829 | |||
| 830 | const std::vector<SubwayItem> &GD_GetSubwayItems() { | ||
| 831 | return GetState().subway_items_; | ||
| 832 | } | ||
| 833 | |||
| 834 | const SubwayItem &GD_GetSubwayItem(int id) { | ||
| 835 | return GetState().subway_items_.at(id); | ||
| 836 | } | ||
| 837 | |||
| 838 | int GD_GetSubwayItemForPainting(const std::string &painting_id) { | ||
| 839 | #ifndef NDEBUG | ||
| 840 | if (!GetState().subway_item_by_painting_.count(painting_id)) { | ||
| 841 | wxLogError("No subway item for painting %s", painting_id); | ||
| 842 | } | ||
| 843 | #endif | ||
| 844 | return GetState().subway_item_by_painting_.at(painting_id); | ||
| 845 | } | ||
| 846 | |||
| 847 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp &sunwarp) { | ||
| 848 | return GetState().subway_item_by_sunwarp_.at(sunwarp); | ||
| 849 | } | ||
| diff --git a/src/game_data.h b/src/game_data.h index f3edaa2..a5d5699 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
| @@ -63,6 +63,7 @@ struct ProgressiveRequirement { | |||
| 63 | }; | 63 | }; |
| 64 | 64 | ||
| 65 | struct Door { | 65 | struct Door { |
| 66 | int id; | ||
| 66 | int room; | 67 | int room; |
| 67 | std::string name; | 68 | std::string name; |
| 68 | std::string location_name; | 69 | std::string location_name; |
| @@ -87,14 +88,16 @@ struct Exit { | |||
| 87 | }; | 88 | }; |
| 88 | 89 | ||
| 89 | struct PaintingExit { | 90 | struct PaintingExit { |
| 90 | std::string id; | 91 | int id; |
| 92 | int room; | ||
| 93 | std::string internal_id; | ||
| 91 | std::optional<int> door; | 94 | std::optional<int> door; |
| 92 | }; | 95 | }; |
| 93 | 96 | ||
| 94 | struct Room { | 97 | struct Room { |
| 95 | std::string name; | 98 | std::string name; |
| 96 | std::vector<Exit> exits; | 99 | std::vector<Exit> exits; |
| 97 | std::vector<PaintingExit> paintings; | 100 | std::vector<int> paintings; |
| 98 | std::vector<int> sunwarps; | 101 | std::vector<int> sunwarps; |
| 99 | std::vector<int> panels; | 102 | std::vector<int> panels; |
| 100 | }; | 103 | }; |
| @@ -113,12 +116,37 @@ struct MapArea { | |||
| 113 | int id; | 116 | int id; |
| 114 | std::string name; | 117 | std::string name; |
| 115 | std::vector<Location> locations; | 118 | std::vector<Location> locations; |
| 119 | std::vector<int> paintings; | ||
| 116 | int map_x; | 120 | int map_x; |
| 117 | int map_y; | 121 | int map_y; |
| 118 | int classification = 0; | 122 | int classification = 0; |
| 119 | bool hunt = false; | 123 | bool hunt = false; |
| 120 | }; | 124 | }; |
| 121 | 125 | ||
| 126 | enum class SubwaySunwarpType { | ||
| 127 | kEnter, | ||
| 128 | kExit, | ||
| 129 | kFinal | ||
| 130 | }; | ||
| 131 | |||
| 132 | struct SubwaySunwarp { | ||
| 133 | int dots; | ||
| 134 | SubwaySunwarpType type; | ||
| 135 | |||
| 136 | bool operator<(const SubwaySunwarp& rhs) const; | ||
| 137 | }; | ||
| 138 | |||
| 139 | struct SubwayItem { | ||
| 140 | int id; | ||
| 141 | int x; | ||
| 142 | int y; | ||
| 143 | std::optional<int> door; | ||
| 144 | std::vector<std::string> paintings; | ||
| 145 | std::vector<std::string> tags; | ||
| 146 | std::optional<SubwaySunwarp> sunwarp; | ||
| 147 | std::optional<std::string> special; | ||
| 148 | }; | ||
| 149 | |||
| 122 | const std::vector<MapArea>& GD_GetMapAreas(); | 150 | const std::vector<MapArea>& GD_GetMapAreas(); |
| 123 | const MapArea& GD_GetMapArea(int id); | 151 | const MapArea& GD_GetMapArea(int id); |
| 124 | int GD_GetRoomByName(const std::string& name); | 152 | int GD_GetRoomByName(const std::string& name); |
| @@ -127,10 +155,15 @@ const std::vector<Door>& GD_GetDoors(); | |||
| 127 | const Door& GD_GetDoor(int door_id); | 155 | const Door& GD_GetDoor(int door_id); |
| 128 | int GD_GetDoorByName(const std::string& name); | 156 | int GD_GetDoorByName(const std::string& name); |
| 129 | const Panel& GD_GetPanel(int panel_id); | 157 | const Panel& GD_GetPanel(int panel_id); |
| 130 | int GD_GetRoomForPainting(const std::string& painting_id); | 158 | const PaintingExit& GD_GetPaintingExit(int painting_id); |
| 159 | int GD_GetPaintingByName(const std::string& name); | ||
| 131 | const std::vector<int>& GD_GetAchievementPanels(); | 160 | const std::vector<int>& GD_GetAchievementPanels(); |
| 132 | int GD_GetItemIdForColor(LingoColor color); | 161 | int GD_GetItemIdForColor(LingoColor color); |
| 133 | const std::vector<int>& GD_GetSunwarpDoors(); | 162 | const std::vector<int>& GD_GetSunwarpDoors(); |
| 134 | int GD_GetRoomForSunwarp(int index); | 163 | int GD_GetRoomForSunwarp(int index); |
| 164 | const std::vector<SubwayItem>& GD_GetSubwayItems(); | ||
| 165 | const SubwayItem& GD_GetSubwayItem(int id); | ||
| 166 | int GD_GetSubwayItemForPainting(const std::string& painting_id); | ||
| 167 | int GD_GetSubwayItemForSunwarp(const SubwaySunwarp& sunwarp); | ||
| 135 | 168 | ||
| 136 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | 169 | #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..abe6626 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 | |||
| 21 | #ifndef NDEBUG | ||
| 22 | wxLog::SetVerbose(true); | ||
| 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..8364714 --- /dev/null +++ b/src/subway_map.cpp | |||
| @@ -0,0 +1,700 @@ | |||
| 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 | for (const auto &[tag, items] : tagged) { | ||
| 102 | // Pairwise connect all items with the same tag. | ||
| 103 | for (auto tag_it1 = items.begin(); std::next(tag_it1) != items.end(); | ||
| 104 | tag_it1++) { | ||
| 105 | for (auto tag_it2 = std::next(tag_it1); tag_it2 != items.end(); | ||
| 106 | tag_it2++) { | ||
| 107 | networks_.AddLink(*tag_it1, *tag_it2); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | checked_paintings_.clear(); | ||
| 113 | } | ||
| 114 | |||
| 115 | void SubwayMap::UpdateIndicators() { | ||
| 116 | if (AP_IsPaintingShuffle()) { | ||
| 117 | for (const std::string &painting_id : AP_GetCheckedPaintings()) { | ||
| 118 | if (!checked_paintings_.count(painting_id)) { | ||
| 119 | checked_paintings_.insert(painting_id); | ||
| 120 | |||
| 121 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
| 122 | networks_.AddLink(GD_GetSubwayItemForPainting(painting_id), | ||
| 123 | GD_GetSubwayItemForPainting( | ||
| 124 | AP_GetPaintingMapping().at(painting_id))); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | Redraw(); | ||
| 131 | } | ||
| 132 | |||
| 133 | void SubwayMap::UpdateSunwarp(SubwaySunwarp from_sunwarp, | ||
| 134 | SubwaySunwarp to_sunwarp) { | ||
| 135 | networks_.AddLink(GD_GetSubwayItemForSunwarp(from_sunwarp), | ||
| 136 | GD_GetSubwayItemForSunwarp(to_sunwarp)); | ||
| 137 | } | ||
| 138 | |||
| 139 | void SubwayMap::Zoom(bool in) { | ||
| 140 | wxPoint focus_point; | ||
| 141 | |||
| 142 | if (mouse_position_) { | ||
| 143 | focus_point = *mouse_position_; | ||
| 144 | } else { | ||
| 145 | focus_point = {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}; | ||
| 146 | } | ||
| 147 | |||
| 148 | if (in) { | ||
| 149 | if (zoom_ < 3.0) { | ||
| 150 | SetZoom(zoom_ + 0.25, focus_point); | ||
| 151 | } | ||
| 152 | } else { | ||
| 153 | if (zoom_ > 1.0) { | ||
| 154 | SetZoom(zoom_ - 0.25, focus_point); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void SubwayMap::OnPaint(wxPaintEvent &event) { | ||
| 160 | if (GetSize() != rendered_.GetSize()) { | ||
| 161 | wxSize panel_size = GetSize(); | ||
| 162 | wxSize image_size = map_image_.GetSize(); | ||
| 163 | |||
| 164 | render_x_ = 0; | ||
| 165 | render_y_ = 0; | ||
| 166 | render_width_ = panel_size.GetWidth(); | ||
| 167 | render_height_ = panel_size.GetHeight(); | ||
| 168 | |||
| 169 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
| 170 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
| 171 | render_height_ = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
| 172 | image_size.GetWidth(); | ||
| 173 | render_y_ = (panel_size.GetHeight() - render_height_) / 2; | ||
| 174 | } else { | ||
| 175 | render_width_ = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
| 176 | image_size.GetHeight(); | ||
| 177 | render_x_ = (panel_size.GetWidth() - render_width_) / 2; | ||
| 178 | } | ||
| 179 | |||
| 180 | SetZoomPos({zoom_x_, zoom_y_}); | ||
| 181 | |||
| 182 | SetUpHelpButton(); | ||
| 183 | } | ||
| 184 | |||
| 185 | wxBufferedPaintDC dc(this); | ||
| 186 | dc.SetBackground(*wxWHITE_BRUSH); | ||
| 187 | dc.Clear(); | ||
| 188 | |||
| 189 | { | ||
| 190 | wxMemoryDC rendered_dc; | ||
| 191 | rendered_dc.SelectObject(rendered_); | ||
| 192 | |||
| 193 | int dst_x; | ||
| 194 | int dst_y; | ||
| 195 | int dst_w; | ||
| 196 | int dst_h; | ||
| 197 | int src_x; | ||
| 198 | int src_y; | ||
| 199 | int src_w; | ||
| 200 | int src_h; | ||
| 201 | |||
| 202 | int zoomed_width = render_width_ * zoom_; | ||
| 203 | int zoomed_height = render_height_ * zoom_; | ||
| 204 | |||
| 205 | if (zoomed_width <= GetSize().GetWidth()) { | ||
| 206 | dst_x = (GetSize().GetWidth() - zoomed_width) / 2; | ||
| 207 | dst_w = zoomed_width; | ||
| 208 | src_x = 0; | ||
| 209 | src_w = map_image_.GetWidth(); | ||
| 210 | } else { | ||
| 211 | dst_x = 0; | ||
| 212 | dst_w = GetSize().GetWidth(); | ||
| 213 | src_x = -zoom_x_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 214 | src_w = | ||
| 215 | GetSize().GetWidth() * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 216 | } | ||
| 217 | |||
| 218 | if (zoomed_height <= GetSize().GetHeight()) { | ||
| 219 | dst_y = (GetSize().GetHeight() - zoomed_height) / 2; | ||
| 220 | dst_h = zoomed_height; | ||
| 221 | src_y = 0; | ||
| 222 | src_h = map_image_.GetHeight(); | ||
| 223 | } else { | ||
| 224 | dst_y = 0; | ||
| 225 | dst_h = GetSize().GetHeight(); | ||
| 226 | src_y = -zoom_y_ * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 227 | src_h = | ||
| 228 | GetSize().GetHeight() * map_image_.GetWidth() / render_width_ / zoom_; | ||
| 229 | } | ||
| 230 | |||
| 231 | wxGCDC gcdc(dc); | ||
| 232 | gcdc.GetGraphicsContext()->SetInterpolationQuality(wxINTERPOLATION_GOOD); | ||
| 233 | gcdc.StretchBlit(dst_x, dst_y, dst_w, dst_h, &rendered_dc, src_x, src_y, | ||
| 234 | src_w, src_h); | ||
| 235 | } | ||
| 236 | |||
| 237 | if (hovered_item_) { | ||
| 238 | // Note that these requirements are duplicated on OnMouseClick so that it | ||
| 239 | // knows when an item has a hover effect. | ||
| 240 | const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); | ||
| 241 | if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { | ||
| 242 | const std::map<std::string, bool> &report = | ||
| 243 | GetDoorRequirements(*subway_item.door); | ||
| 244 | |||
| 245 | int acc_height = 10; | ||
| 246 | int col_width = 0; | ||
| 247 | |||
| 248 | for (const auto &[text, obtained] : report) { | ||
| 249 | wxSize item_extent = dc.GetTextExtent(text); | ||
| 250 | int item_height = std::max(32, item_extent.GetHeight()) + 10; | ||
| 251 | acc_height += item_height; | ||
| 252 | |||
| 253 | if (item_extent.GetWidth() > col_width) { | ||
| 254 | col_width = item_extent.GetWidth(); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | int item_width = col_width + 10 + 32; | ||
| 259 | int full_width = item_width + 20; | ||
| 260 | |||
| 261 | wxPoint popup_pos = | ||
| 262 | MapPosToRenderPos({subway_item.x + AREA_ACTUAL_SIZE / 2, | ||
| 263 | subway_item.y + AREA_ACTUAL_SIZE / 2}); | ||
| 264 | |||
| 265 | if (popup_pos.x + full_width > GetSize().GetWidth()) { | ||
| 266 | popup_pos.x = GetSize().GetWidth() - full_width; | ||
| 267 | } | ||
| 268 | if (popup_pos.y + acc_height > GetSize().GetHeight()) { | ||
| 269 | popup_pos.y = GetSize().GetHeight() - acc_height; | ||
| 270 | } | ||
| 271 | |||
| 272 | dc.SetPen(*wxTRANSPARENT_PEN); | ||
| 273 | dc.SetBrush(*wxBLACK_BRUSH); | ||
| 274 | dc.DrawRectangle(popup_pos, {full_width, acc_height}); | ||
| 275 | |||
| 276 | dc.SetFont(GetFont()); | ||
| 277 | |||
| 278 | int cur_height = 10; | ||
| 279 | |||
| 280 | for (const auto &[text, obtained] : report) { | ||
| 281 | wxBitmap *eye_ptr = obtained ? &checked_eye_ : &unchecked_eye_; | ||
| 282 | |||
| 283 | dc.DrawBitmap(*eye_ptr, popup_pos + wxPoint{10, cur_height}); | ||
| 284 | |||
| 285 | dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); | ||
| 286 | wxSize item_extent = dc.GetTextExtent(text); | ||
| 287 | dc.DrawText( | ||
| 288 | text, | ||
| 289 | popup_pos + | ||
| 290 | wxPoint{10 + 32 + 10, | ||
| 291 | cur_height + (32 - dc.GetFontMetrics().height) / 2}); | ||
| 292 | |||
| 293 | cur_height += 10 + 32; | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | if (networks_.IsItemInNetwork(*hovered_item_)) { | ||
| 298 | dc.SetBrush(*wxTRANSPARENT_BRUSH); | ||
| 299 | |||
| 300 | for (const auto &[item_id1, item_id2] : | ||
| 301 | networks_.GetNetworkGraph(*hovered_item_)) { | ||
| 302 | const SubwayItem &item1 = GD_GetSubwayItem(item_id1); | ||
| 303 | const SubwayItem &item2 = GD_GetSubwayItem(item_id2); | ||
| 304 | |||
| 305 | wxPoint item1_pos = MapPosToRenderPos( | ||
| 306 | {item1.x + AREA_ACTUAL_SIZE / 2, item1.y + AREA_ACTUAL_SIZE / 2}); | ||
| 307 | wxPoint item2_pos = MapPosToRenderPos( | ||
| 308 | {item2.x + AREA_ACTUAL_SIZE / 2, item2.y + AREA_ACTUAL_SIZE / 2}); | ||
| 309 | |||
| 310 | int left = std::min(item1_pos.x, item2_pos.x); | ||
| 311 | int top = std::min(item1_pos.y, item2_pos.y); | ||
| 312 | int right = std::max(item1_pos.x, item2_pos.x); | ||
| 313 | int bottom = std::max(item1_pos.y, item2_pos.y); | ||
| 314 | |||
| 315 | int halfwidth = right - left; | ||
| 316 | int halfheight = bottom - top; | ||
| 317 | |||
| 318 | if (halfwidth < 4 || halfheight < 4) { | ||
| 319 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
| 320 | dc.DrawLine(item1_pos, item2_pos); | ||
| 321 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 322 | dc.DrawLine(item1_pos, item2_pos); | ||
| 323 | } else { | ||
| 324 | int ellipse_x; | ||
| 325 | int ellipse_y; | ||
| 326 | double start; | ||
| 327 | double end; | ||
| 328 | |||
| 329 | if (item1_pos.x > item2_pos.x) { | ||
| 330 | ellipse_y = top; | ||
| 331 | |||
| 332 | if (item1_pos.y > item2_pos.y) { | ||
| 333 | ellipse_x = left - halfwidth; | ||
| 334 | |||
| 335 | start = 0; | ||
| 336 | end = 90; | ||
| 337 | } else { | ||
| 338 | ellipse_x = left; | ||
| 339 | |||
| 340 | start = 90; | ||
| 341 | end = 180; | ||
| 342 | } | ||
| 343 | } else { | ||
| 344 | ellipse_y = top - halfheight; | ||
| 345 | |||
| 346 | if (item1_pos.y > item2_pos.y) { | ||
| 347 | ellipse_x = left - halfwidth; | ||
| 348 | |||
| 349 | start = 270; | ||
| 350 | end = 360; | ||
| 351 | } else { | ||
| 352 | ellipse_x = left; | ||
| 353 | |||
| 354 | start = 180; | ||
| 355 | end = 270; | ||
| 356 | } | ||
| 357 | } | ||
| 358 | |||
| 359 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); | ||
| 360 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
| 361 | halfheight * 2, start, end); | ||
| 362 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); | ||
| 363 | dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, | ||
| 364 | halfheight * 2, start, end); | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | event.Skip(); | ||
| 371 | } | ||
| 372 | |||
| 373 | void SubwayMap::OnMouseMove(wxMouseEvent &event) { | ||
| 374 | wxPoint mouse_pos = RenderPosToMapPos(event.GetPosition()); | ||
| 375 | |||
| 376 | std::vector<int> hovered = tree_->query( | ||
| 377 | {static_cast<float>(mouse_pos.x), static_cast<float>(mouse_pos.y), 2, 2}); | ||
| 378 | if (!hovered.empty()) { | ||
| 379 | actual_hover_= hovered[0]; | ||
| 380 | } else { | ||
| 381 | actual_hover_ = std::nullopt; | ||
| 382 | } | ||
| 383 | |||
| 384 | if (!sticky_hover_ && actual_hover_ != hovered_item_) { | ||
| 385 | hovered_item_ = actual_hover_; | ||
| 386 | |||
| 387 | Refresh(); | ||
| 388 | } | ||
| 389 | |||
| 390 | if (scroll_mode_) { | ||
| 391 | EvaluateScroll(event.GetPosition()); | ||
| 392 | } | ||
| 393 | |||
| 394 | mouse_position_ = event.GetPosition(); | ||
| 395 | |||
| 396 | event.Skip(); | ||
| 397 | } | ||
| 398 | |||
| 399 | void SubwayMap::OnMouseScroll(wxMouseEvent &event) { | ||
| 400 | double new_zoom = zoom_; | ||
| 401 | if (event.GetWheelRotation() > 0) { | ||
| 402 | new_zoom = std::min(3.0, zoom_ + 0.25); | ||
| 403 | } else { | ||
| 404 | new_zoom = std::max(1.0, zoom_ - 0.25); | ||
| 405 | } | ||
| 406 | |||
| 407 | if (zoom_ != new_zoom) { | ||
| 408 | SetZoom(new_zoom, event.GetPosition()); | ||
| 409 | } | ||
| 410 | |||
| 411 | event.Skip(); | ||
| 412 | } | ||
| 413 | |||
| 414 | void SubwayMap::OnMouseLeave(wxMouseEvent &event) { | ||
| 415 | SetScrollSpeed(0, 0); | ||
| 416 | mouse_position_ = std::nullopt; | ||
| 417 | } | ||
| 418 | |||
| 419 | void SubwayMap::OnMouseClick(wxMouseEvent &event) { | ||
| 420 | bool finished = false; | ||
| 421 | |||
| 422 | if (actual_hover_) { | ||
| 423 | const SubwayItem &subway_item = GD_GetSubwayItem(*actual_hover_); | ||
| 424 | if ((subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) || | ||
| 425 | networks_.IsItemInNetwork(*hovered_item_)) { | ||
| 426 | if (actual_hover_ != hovered_item_) { | ||
| 427 | hovered_item_ = actual_hover_; | ||
| 428 | |||
| 429 | if (!hovered_item_) { | ||
| 430 | sticky_hover_ = false; | ||
| 431 | } | ||
| 432 | |||
| 433 | Refresh(); | ||
| 434 | } else { | ||
| 435 | sticky_hover_ = !sticky_hover_; | ||
| 436 | } | ||
| 437 | |||
| 438 | finished = true; | ||
| 439 | } | ||
| 440 | } | ||
| 441 | |||
| 442 | if (!finished) { | ||
| 443 | if (scroll_mode_) { | ||
| 444 | scroll_mode_ = false; | ||
| 445 | |||
| 446 | SetScrollSpeed(0, 0); | ||
| 447 | |||
| 448 | SetCursor(wxCURSOR_ARROW); | ||
| 449 | } else if (event.GetPosition().x < GetSize().GetWidth() / 6 || | ||
| 450 | event.GetPosition().x > 5 * GetSize().GetWidth() / 6 || | ||
| 451 | event.GetPosition().y < GetSize().GetHeight() / 6 || | ||
| 452 | event.GetPosition().y > 5 * GetSize().GetHeight() / 6) { | ||
| 453 | scroll_mode_ = true; | ||
| 454 | |||
| 455 | EvaluateScroll(event.GetPosition()); | ||
| 456 | |||
| 457 | SetCursor(wxCURSOR_CROSS); | ||
| 458 | } else { | ||
| 459 | sticky_hover_ = false; | ||
| 460 | } | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | void SubwayMap::OnTimer(wxTimerEvent &event) { | ||
| 465 | SetZoomPos({zoom_x_ + scroll_x_, zoom_y_ + scroll_y_}); | ||
| 466 | Refresh(); | ||
| 467 | } | ||
| 468 | |||
| 469 | void SubwayMap::OnZoomSlide(wxCommandEvent &event) { | ||
| 470 | double new_zoom = 1.0 + 0.25 * zoom_slider_->GetValue(); | ||
| 471 | |||
| 472 | if (new_zoom != zoom_) { | ||
| 473 | SetZoom(new_zoom, {GetSize().GetWidth() / 2, GetSize().GetHeight() / 2}); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | void SubwayMap::OnClickHelp(wxCommandEvent &event) { | ||
| 478 | wxMessageBox( | ||
| 479 | "Zoom in/out using the mouse wheel, Ctrl +/-, or the slider in the " | ||
| 480 | "corner.\nClick on a side of the screen to start panning. It will follow " | ||
| 481 | "your mouse. Click again to stop.\nHover over a door to see the " | ||
| 482 | "requirements to open it.\nHover over a warp or active painting to see " | ||
| 483 | "what it is connected to.\nIn painting shuffle, paintings that have not " | ||
| 484 | "yet been checked will not show their connections.\nA green shaded owl " | ||
| 485 | "means that there is a painting entrance there.\nA red shaded owl means " | ||
| 486 | "that there are only painting exits there.\nClick on a door or " | ||
| 487 | "warp to make the popup stick until you click again.", | ||
| 488 | "Subway Map Help"); | ||
| 489 | } | ||
| 490 | |||
| 491 | void SubwayMap::Redraw() { | ||
| 492 | rendered_ = wxBitmap(map_image_); | ||
| 493 | |||
| 494 | wxMemoryDC dc; | ||
| 495 | dc.SelectObject(rendered_); | ||
| 496 | |||
| 497 | wxGCDC gcdc(dc); | ||
| 498 | |||
| 499 | for (const SubwayItem &subway_item : GD_GetSubwayItems()) { | ||
| 500 | ItemDrawType draw_type = ItemDrawType::kNone; | ||
| 501 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
| 502 | std::optional<wxColour> shade_color; | ||
| 503 | |||
| 504 | if (AP_HasEarlyColorHallways() && | ||
| 505 | (subway_item.special == "starting_room_paintings" || | ||
| 506 | subway_item.special == "early_color_hallways")) { | ||
| 507 | draw_type = ItemDrawType::kOwl; | ||
| 508 | |||
| 509 | if (subway_item.special == "starting_room_paintings") { | ||
| 510 | shade_color = wxColour(0, 255, 0, 128); | ||
| 511 | } else { | ||
| 512 | shade_color = wxColour(255, 0, 0, 128); | ||
| 513 | } | ||
| 514 | } else if (subway_item.special == "sun_painting") { | ||
| 515 | if (!AP_IsPilgrimageEnabled()) { | ||
| 516 | if (IsDoorOpen(*subway_item.door)) { | ||
| 517 | draw_type = ItemDrawType::kOwl; | ||
| 518 | shade_color = wxColour(0, 255, 0, 128); | ||
| 519 | } else { | ||
| 520 | draw_type = ItemDrawType::kBox; | ||
| 521 | brush_color = wxRED_BRUSH; | ||
| 522 | } | ||
| 523 | } | ||
| 524 | } else if (!subway_item.paintings.empty()) { | ||
| 525 | if (AP_IsPaintingShuffle()) { | ||
| 526 | bool has_checked_painting = false; | ||
| 527 | bool has_unchecked_painting = false; | ||
| 528 | bool has_mapped_painting = false; | ||
| 529 | bool has_codomain_painting = false; | ||
| 530 | |||
| 531 | for (const std::string &painting_id : subway_item.paintings) { | ||
| 532 | if (checked_paintings_.count(painting_id)) { | ||
| 533 | has_checked_painting = true; | ||
| 534 | |||
| 535 | if (AP_GetPaintingMapping().count(painting_id)) { | ||
| 536 | has_mapped_painting = true; | ||
| 537 | } else if (AP_IsPaintingMappedTo(painting_id)) { | ||
| 538 | has_codomain_painting = true; | ||
| 539 | } | ||
| 540 | } else { | ||
| 541 | has_unchecked_painting = true; | ||
| 542 | } | ||
| 543 | } | ||
| 544 | |||
| 545 | if (has_unchecked_painting || has_mapped_painting || has_codomain_painting) { | ||
| 546 | draw_type = ItemDrawType::kOwl; | ||
| 547 | |||
| 548 | if (has_checked_painting) { | ||
| 549 | if (has_mapped_painting) { | ||
| 550 | shade_color = wxColour(0, 255, 0, 128); | ||
| 551 | } else { | ||
| 552 | shade_color = wxColour(255, 0, 0, 128); | ||
| 553 | } | ||
| 554 | } | ||
| 555 | } | ||
| 556 | } else if (!subway_item.tags.empty()) { | ||
| 557 | draw_type = ItemDrawType::kOwl; | ||
| 558 | } | ||
| 559 | } else if (subway_item.door) { | ||
| 560 | draw_type = ItemDrawType::kBox; | ||
| 561 | |||
| 562 | if (IsDoorOpen(*subway_item.door)) { | ||
| 563 | brush_color = wxGREEN_BRUSH; | ||
| 564 | } else { | ||
| 565 | brush_color = wxRED_BRUSH; | ||
| 566 | } | ||
| 567 | } | ||
| 568 | |||
| 569 | wxPoint real_area_pos = {subway_item.x, subway_item.y}; | ||
| 570 | |||
| 571 | int real_area_size = | ||
| 572 | (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); | ||
| 573 | |||
| 574 | if (draw_type == ItemDrawType::kBox) { | ||
| 575 | gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); | ||
| 576 | gcdc.SetBrush(*brush_color); | ||
| 577 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
| 578 | } else if (draw_type == ItemDrawType::kOwl) { | ||
| 579 | wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( | ||
| 580 | real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); | ||
| 581 | gcdc.DrawBitmap(owl_bitmap, real_area_pos); | ||
| 582 | |||
| 583 | if (shade_color) { | ||
| 584 | gcdc.SetBrush(wxBrush(*shade_color)); | ||
| 585 | gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); | ||
| 586 | } | ||
| 587 | } | ||
| 588 | } | ||
| 589 | } | ||
| 590 | |||
| 591 | void SubwayMap::SetUpHelpButton() { | ||
| 592 | help_button_->SetPosition({ | ||
| 593 | GetSize().GetWidth() - help_button_->GetSize().GetWidth() - 15, | ||
| 594 | 15, | ||
| 595 | }); | ||
| 596 | } | ||
| 597 | |||
| 598 | void SubwayMap::EvaluateScroll(wxPoint pos) { | ||
| 599 | int scroll_x; | ||
| 600 | int scroll_y; | ||
| 601 | if (pos.x < GetSize().GetWidth() / 9) { | ||
| 602 | scroll_x = 20; | ||
| 603 | } else if (pos.x < GetSize().GetWidth() / 6) { | ||
| 604 | scroll_x = 5; | ||
| 605 | } else if (pos.x > 8 * GetSize().GetWidth() / 9) { | ||
| 606 | scroll_x = -20; | ||
| 607 | } else if (pos.x > 5 * GetSize().GetWidth() / 6) { | ||
| 608 | scroll_x = -5; | ||
| 609 | } else { | ||
| 610 | scroll_x = 0; | ||
| 611 | } | ||
| 612 | if (pos.y < GetSize().GetHeight() / 9) { | ||
| 613 | scroll_y = 20; | ||
| 614 | } else if (pos.y < GetSize().GetHeight() / 6) { | ||
| 615 | scroll_y = 5; | ||
| 616 | } else if (pos.y > 8 * GetSize().GetHeight() / 9) { | ||
| 617 | scroll_y = -20; | ||
| 618 | } else if (pos.y > 5 * GetSize().GetHeight() / 6) { | ||
| 619 | scroll_y = -5; | ||
| 620 | } else { | ||
| 621 | scroll_y = 0; | ||
| 622 | } | ||
| 623 | |||
| 624 | SetScrollSpeed(scroll_x, scroll_y); | ||
| 625 | } | ||
| 626 | |||
| 627 | wxPoint SubwayMap::MapPosToRenderPos(wxPoint pos) const { | ||
| 628 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
| 629 | map_image_.GetSize().GetWidth() + | ||
| 630 | zoom_x_), | ||
| 631 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
| 632 | map_image_.GetSize().GetWidth() + | ||
| 633 | zoom_y_)}; | ||
| 634 | } | ||
| 635 | |||
| 636 | wxPoint SubwayMap::MapPosToVirtualPos(wxPoint pos) const { | ||
| 637 | return {static_cast<int>(pos.x * render_width_ * zoom_ / | ||
| 638 | map_image_.GetSize().GetWidth()), | ||
| 639 | static_cast<int>(pos.y * render_width_ * zoom_ / | ||
| 640 | map_image_.GetSize().GetWidth())}; | ||
| 641 | } | ||
| 642 | |||
| 643 | wxPoint SubwayMap::RenderPosToMapPos(wxPoint pos) const { | ||
| 644 | return { | ||
| 645 | std::clamp(static_cast<int>((pos.x - zoom_x_) * map_image_.GetWidth() / | ||
| 646 | render_width_ / zoom_), | ||
| 647 | 0, map_image_.GetWidth() - 1), | ||
| 648 | std::clamp(static_cast<int>((pos.y - zoom_y_) * map_image_.GetWidth() / | ||
| 649 | render_width_ / zoom_), | ||
| 650 | 0, map_image_.GetHeight() - 1)}; | ||
| 651 | } | ||
| 652 | |||
| 653 | void SubwayMap::SetZoomPos(wxPoint pos) { | ||
| 654 | if (render_width_ * zoom_ <= GetSize().GetWidth()) { | ||
| 655 | zoom_x_ = (GetSize().GetWidth() - render_width_ * zoom_) / 2; | ||
| 656 | } else { | ||
| 657 | zoom_x_ = std::clamp( | ||
| 658 | pos.x, GetSize().GetWidth() - static_cast<int>(render_width_ * zoom_), | ||
| 659 | 0); | ||
| 660 | } | ||
| 661 | if (render_height_ * zoom_ <= GetSize().GetHeight()) { | ||
| 662 | zoom_y_ = (GetSize().GetHeight() - render_height_ * zoom_) / 2; | ||
| 663 | } else { | ||
| 664 | zoom_y_ = std::clamp( | ||
| 665 | pos.y, GetSize().GetHeight() - static_cast<int>(render_height_ * zoom_), | ||
| 666 | 0); | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | void SubwayMap::SetScrollSpeed(int scroll_x, int scroll_y) { | ||
| 671 | bool should_timer = (scroll_x != 0 || scroll_y != 0); | ||
| 672 | if (should_timer != scroll_timer_->IsRunning()) { | ||
| 673 | if (should_timer) { | ||
| 674 | scroll_timer_->Start(1000 / 60); | ||
| 675 | } else { | ||
| 676 | scroll_timer_->Stop(); | ||
| 677 | } | ||
| 678 | } | ||
| 679 | |||
| 680 | scroll_x_ = scroll_x; | ||
| 681 | scroll_y_ = scroll_y; | ||
| 682 | } | ||
| 683 | |||
| 684 | void SubwayMap::SetZoom(double zoom, wxPoint static_point) { | ||
| 685 | wxPoint map_pos = RenderPosToMapPos(static_point); | ||
| 686 | zoom_ = zoom; | ||
| 687 | |||
| 688 | wxPoint virtual_pos = MapPosToVirtualPos(map_pos); | ||
| 689 | SetZoomPos(-(virtual_pos - static_point)); | ||
| 690 | |||
| 691 | Refresh(); | ||
| 692 | |||
| 693 | zoom_slider_->SetValue((zoom - 1.0) / 0.25); | ||
| 694 | } | ||
| 695 | |||
| 696 | quadtree::Box<float> SubwayMap::GetItemBox::operator()(const int &id) const { | ||
| 697 | const SubwayItem &subway_item = GD_GetSubwayItem(id); | ||
| 698 | return {static_cast<float>(subway_item.x), static_cast<float>(subway_item.y), | ||
| 699 | AREA_ACTUAL_SIZE, AREA_ACTUAL_SIZE}; | ||
| 700 | } | ||
| 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 640a159..66e7751 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() == kNO_DOORS || 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() == kSIMPLE_DOORS && | ||
| 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 | return kYes; | 489 | return kYes; |
| @@ -395,10 +572,17 @@ class StateCalculator { | |||
| 395 | std::set<int> reachable_rooms_; | 572 | std::set<int> reachable_rooms_; |
| 396 | std::map<int, Decision> door_decisions_; | 573 | std::map<int, Decision> door_decisions_; |
| 397 | std::set<int> solveable_panels_; | 574 | std::set<int> solveable_panels_; |
| 575 | std::set<int> reachable_paintings_; | ||
| 576 | std::map<int, std::map<std::string, bool>> door_report_; | ||
| 398 | }; | 577 | }; |
| 399 | 578 | ||
| 400 | } // namespace | 579 | } // namespace |
| 401 | 580 | ||
| 581 | void ResetReachabilityRequirements() { | ||
| 582 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
| 583 | GetState().requirements.Reset(); | ||
| 584 | } | ||
| 585 | |||
| 402 | void RecalculateReachability() { | 586 | void RecalculateReachability() { |
| 403 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); | 587 | StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); |
| 404 | state_calculator.Calculate(); | 588 | state_calculator.Calculate(); |
| @@ -422,9 +606,23 @@ void RecalculateReachability() { | |||
| 422 | } | 606 | } |
| 423 | } | 607 | } |
| 424 | 608 | ||
| 609 | std::set<int> new_reachable_doors; | ||
| 610 | for (const auto& [door_id, decision] : state_calculator.GetDoorDecisions()) { | ||
| 611 | if (decision == kYes) { | ||
| 612 | new_reachable_doors.insert(door_id); | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | std::set<int> reachable_paintings = state_calculator.GetReachablePaintings(); | ||
| 617 | std::map<int, std::map<std::string, bool>> door_reports = | ||
| 618 | state_calculator.GetDoorReports(); | ||
| 619 | |||
| 425 | { | 620 | { |
| 426 | std::lock_guard reachability_guard(GetState().reachability_mutex); | 621 | std::lock_guard reachability_guard(GetState().reachability_mutex); |
| 427 | std::swap(GetState().reachability, new_reachability); | 622 | std::swap(GetState().reachability, new_reachability); |
| 623 | std::swap(GetState().reachable_doors, new_reachable_doors); | ||
| 624 | std::swap(GetState().reachable_paintings, reachable_paintings); | ||
| 625 | std::swap(GetState().door_reports, door_reports); | ||
| 428 | } | 626 | } |
| 429 | } | 627 | } |
| 430 | 628 | ||
| @@ -437,3 +635,21 @@ bool IsLocationReachable(int location_id) { | |||
| 437 | return false; | 635 | return false; |
| 438 | } | 636 | } |
| 439 | } | 637 | } |
| 638 | |||
| 639 | bool IsDoorOpen(int door_id) { | ||
| 640 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
| 641 | |||
| 642 | return GetState().reachable_doors.count(door_id); | ||
| 643 | } | ||
| 644 | |||
| 645 | bool IsPaintingReachable(int painting_id) { | ||
| 646 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
| 647 | |||
| 648 | return GetState().reachable_paintings.count(painting_id); | ||
| 649 | } | ||
| 650 | |||
| 651 | const std::map<std::string, bool>& GetDoorRequirements(int door_id) { | ||
| 652 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
| 653 | |||
| 654 | return GetState().door_reports[door_id]; | ||
| 655 | } | ||
| 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 0ccd2c7..4b13d42 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,10 +37,6 @@ struct Version { | |||
| 31 | } | 37 | } |
| 32 | }; | 38 | }; |
| 33 | 39 | ||
| 34 | std::ostream& operator<<(std::ostream& out, const Version& ver) { | ||
| 35 | return out << "v" << ver.major << "." << ver.minor << "." << ver.revision; | ||
| 36 | } | ||
| 37 | |||
| 38 | constexpr const Version kTrackerVersion = Version(0, 9, 2); | 40 | constexpr const Version kTrackerVersion = Version(0, 9, 2); |
| 39 | 41 | ||
| 40 | #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/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 | } | ||
