diff options
Diffstat (limited to 'src/tracker_state.cpp')
-rw-r--r-- | src/tracker_state.cpp | 400 |
1 files changed, 308 insertions, 92 deletions
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 | } | ||