about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/ap_state.cpp7
-rw-r--r--src/ap_state.h2
-rw-r--r--src/subway_map.cpp181
-rw-r--r--src/subway_map.h2
-rw-r--r--src/tracker_state.cpp314
-rw-r--r--src/tracker_state.h7
6 files changed, 377 insertions, 136 deletions
diff --git a/src/ap_state.cpp b/src/ap_state.cpp index f245c2b..20245e5 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp
@@ -271,6 +271,7 @@ struct APState {
271 connected = true; 271 connected = true;
272 has_connection_result = true; 272 has_connection_result = true;
273 273
274 ResetReachabilityRequirements();
274 RefreshTracker(true); 275 RefreshTracker(true);
275 276
276 std::list<std::string> corrected_keys; 277 std::list<std::string> corrected_keys;
@@ -447,6 +448,8 @@ struct APState {
447 return ap_id; 448 return ap_id;
448 } 449 }
449 450
451 std::string GetItemName(int id) { return apclient->get_item_name(id); }
452
450 bool HasReachedGoal() { 453 bool HasReachedGoal() {
451 return data_storage.count(victory_data_storage_key) && 454 return data_storage.count(victory_data_storage_key) &&
452 std::any_cast<int>(data_storage.at(victory_data_storage_key)) == 455 std::any_cast<int>(data_storage.at(victory_data_storage_key)) ==
@@ -485,6 +488,10 @@ bool AP_HasItem(int item_id, int quantity) {
485 return GetState().HasItem(item_id, quantity); 488 return GetState().HasItem(item_id, quantity);
486} 489}
487 490
491std::string AP_GetItemName(int item_id) {
492 return GetState().GetItemName(item_id);
493}
494
488DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } 495DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; }
489 496
490bool AP_IsColorShuffle() { return GetState().color_shuffle; } 497bool AP_IsColorShuffle() { return GetState().color_shuffle; }
diff --git a/src/ap_state.h b/src/ap_state.h index 5fbb720..0ae6a25 100644 --- a/src/ap_state.h +++ b/src/ap_state.h
@@ -49,6 +49,8 @@ bool AP_HasCheckedHuntPanel(int location_id);
49 49
50bool AP_HasItem(int item_id, int quantity = 1); 50bool AP_HasItem(int item_id, int quantity = 1);
51 51
52std::string AP_GetItemName(int item_id);
53
52DoorShuffleMode AP_GetDoorShuffleMode(); 54DoorShuffleMode AP_GetDoorShuffleMode();
53 55
54bool AP_IsColorShuffle(); 56bool AP_IsColorShuffle();
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 6070fd5..2e7b36f 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp
@@ -33,6 +33,14 @@ SubwayMap::SubwayMap(wxWindow *parent) : wxPanel(parent, wxID_ANY) {
33 return; 33 return;
34 } 34 }
35 35
36 unchecked_eye_ =
37 wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(),
38 wxBITMAP_TYPE_PNG)
39 .Scale(32, 32));
40 checked_eye_ = wxBitmap(
41 wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG)
42 .Scale(32, 32));
43
36 tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>( 44 tree_ = std::make_unique<quadtree::Quadtree<int, GetItemBox>>(
37 quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()), 45 quadtree::Box<float>{0, 0, static_cast<float>(map_image_.GetWidth()),
38 static_cast<float>(map_image_.GetHeight())}); 46 static_cast<float>(map_image_.GetHeight())});
@@ -113,75 +121,144 @@ void SubwayMap::OnPaint(wxPaintEvent &event) {
113 wxBufferedPaintDC dc(this); 121 wxBufferedPaintDC dc(this);
114 dc.DrawBitmap(rendered_, 0, 0); 122 dc.DrawBitmap(rendered_, 0, 0);
115 123
116 if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) { 124 if (hovered_item_) {
117 dc.SetBrush(*wxTRANSPARENT_BRUSH); 125 const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_);
126 if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) {
127 const std::map<std::string, bool> &report =
128 GetDoorRequirements(*subway_item.door);
118 129
119 for (const auto &[item_id1, item_id2] : 130 int acc_height = 10;
120 networks_.GetNetworkGraph(*hovered_item_)) { 131 int col_width = 0;
121 const SubwayItem &item1 = GD_GetSubwayItem(item_id1);
122 const SubwayItem &item2 = GD_GetSubwayItem(item_id2);
123 132
124 int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; 133 for (const auto &[text, obtained] : report) {
125 int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; 134 wxSize item_extent = dc.GetTextExtent(text);
135 int item_height = std::max(32, item_extent.GetHeight()) + 10;
136 acc_height += item_height;
126 137
127 int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; 138 if (item_extent.GetWidth() > col_width) {
128 int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; 139 col_width = item_extent.GetWidth();
140 }
141 }
142
143 int item_width = col_width + 10 + 32;
144 int full_width = item_width + 20;
145
146 int popup_x = (subway_item.x + AREA_ACTUAL_SIZE / 2) * render_width_ /
147 map_image_.GetWidth() +
148 render_x_;
149 int popup_y = (subway_item.y + AREA_ACTUAL_SIZE / 2) * render_width_ /
150 map_image_.GetWidth() +
151 render_y_;
152
153 if (popup_x + full_width > GetSize().GetWidth()) {
154 popup_x = GetSize().GetWidth() - full_width;
155 }
156 if (popup_y + acc_height > GetSize().GetHeight()) {
157 popup_y = GetSize().GetHeight() - acc_height;
158 }
129 159
130 int left = std::min(item1_x, item2_x); 160 dc.SetPen(*wxTRANSPARENT_PEN);
131 int top = std::min(item1_y, item2_y); 161 dc.SetBrush(*wxBLACK_BRUSH);
132 int right = std::max(item1_x, item2_x); 162 dc.DrawRectangle({popup_x, popup_y}, {full_width, acc_height});
133 int bottom = std::max(item1_y, item2_y);
134 163
135 int halfwidth = right - left; 164 dc.SetFont(GetFont());
136 int halfheight = bottom - top;
137 165
138 if (halfwidth < 4 || halfheight < 4) { 166 int cur_height = 10;
139 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
140 dc.DrawLine(item1_x, item1_y, item2_x, item2_y);
141 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
142 dc.DrawLine(item1_x, item1_y, item2_x, item2_y);
143 } else {
144 int ellipse_x;
145 int ellipse_y;
146 double start;
147 double end;
148 167
149 if (item1_x > item2_x) { 168 for (const auto& [text, obtained] : report) {
150 ellipse_y = top; 169 wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_;
151 170
152 if (item1_y > item2_y) { 171 dc.DrawBitmap(*eye_ptr, {popup_x + 10, popup_y + cur_height});
153 ellipse_x = left - halfwidth;
154 172
155 start = 0; 173 dc.SetTextForeground(obtained ? *wxWHITE : *wxRED);
156 end = 90; 174 wxSize item_extent = dc.GetTextExtent(text);
157 } else { 175 dc.DrawText(
158 ellipse_x = left; 176 text,
177 {popup_x + 10 + 32 + 10,
178 popup_y + cur_height + (32 - dc.GetFontMetrics().height) / 2});
159 179
160 start = 90; 180 cur_height += 10 + 32;
161 end = 180; 181 }
162 } 182 }
183
184 if (networks_.IsItemInNetwork(*hovered_item_)) {
185 dc.SetBrush(*wxTRANSPARENT_BRUSH);
186
187 for (const auto &[item_id1, item_id2] :
188 networks_.GetNetworkGraph(*hovered_item_)) {
189 const SubwayItem &item1 = GD_GetSubwayItem(item_id1);
190 const SubwayItem &item2 = GD_GetSubwayItem(item_id2);
191
192 int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ /
193 map_image_.GetWidth() +
194 render_x_;
195 int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ /
196 map_image_.GetWidth() +
197 render_y_;
198
199 int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ /
200 map_image_.GetWidth() +
201 render_x_;
202 int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ /
203 map_image_.GetWidth() +
204 render_y_;
205
206 int left = std::min(item1_x, item2_x);
207 int top = std::min(item1_y, item2_y);
208 int right = std::max(item1_x, item2_x);
209 int bottom = std::max(item1_y, item2_y);
210
211 int halfwidth = right - left;
212 int halfheight = bottom - top;
213
214 if (halfwidth < 4 || halfheight < 4) {
215 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
216 dc.DrawLine(item1_x, item1_y, item2_x, item2_y);
217 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
218 dc.DrawLine(item1_x, item1_y, item2_x, item2_y);
163 } else { 219 } else {
164 ellipse_y = top - halfheight; 220 int ellipse_x;
221 int ellipse_y;
222 double start;
223 double end;
165 224
166 if (item1_y > item2_y) { 225 if (item1_x > item2_x) {
167 ellipse_x = left - halfwidth; 226 ellipse_y = top;
168 227
169 start = 270; 228 if (item1_y > item2_y) {
170 end = 360; 229 ellipse_x = left - halfwidth;
230
231 start = 0;
232 end = 90;
233 } else {
234 ellipse_x = left;
235
236 start = 90;
237 end = 180;
238 }
171 } else { 239 } else {
172 ellipse_x = left; 240 ellipse_y = top - halfheight;
241
242 if (item1_y > item2_y) {
243 ellipse_x = left - halfwidth;
173 244
174 start = 180; 245 start = 270;
175 end = 270; 246 end = 360;
247 } else {
248 ellipse_x = left;
249
250 start = 180;
251 end = 270;
252 }
176 } 253 }
177 }
178 254
179 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); 255 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4));
180 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, 256 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2,
181 start, end); 257 start, end);
182 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); 258 dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2));
183 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, 259 dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2,
184 start, end); 260 start, end);
261 }
185 } 262 }
186 } 263 }
187 } 264 }
diff --git a/src/subway_map.h b/src/subway_map.h index e5f0bf6..52d07b8 100644 --- a/src/subway_map.h +++ b/src/subway_map.h
@@ -34,6 +34,8 @@ class SubwayMap : public wxPanel {
34 34
35 wxImage map_image_; 35 wxImage map_image_;
36 wxImage owl_image_; 36 wxImage owl_image_;
37 wxBitmap unchecked_eye_;
38 wxBitmap checked_eye_;
37 39
38 wxBitmap rendered_; 40 wxBitmap rendered_;
39 int render_x_ = 0; 41 int render_x_ = 0;
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 5588c7f..ccb3fbd 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp
@@ -12,10 +12,138 @@
12 12
13namespace { 13namespace {
14 14
15struct 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
43class 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 (!AP_IsPilgrimageEnabled() &&
56 door_obj.type == DoorType::kSunPainting) {
57 requirements.items.insert(door_obj.ap_item_id);
58 } else if (door_obj.type == DoorType::kSunwarp) {
59 switch (AP_GetSunwarpAccess()) {
60 case kSUNWARP_ACCESS_NORMAL:
61 // Do nothing.
62 break;
63 case kSUNWARP_ACCESS_DISABLED:
64 requirements.disabled = true;
65 break;
66 case kSUNWARP_ACCESS_UNLOCK:
67 requirements.items.insert(door_obj.group_ap_item_id);
68 break;
69 case kSUNWARP_ACCESS_INDIVIDUAL:
70 case kSUNWARP_ACCESS_PROGRESSIVE:
71 requirements.doors.insert(door_obj.id);
72 break;
73 }
74 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
75 requirements.rooms.insert(door_obj.room);
76
77 for (int panel_id : door_obj.panels) {
78 const Requirements& panel_reqs = GetPanel(panel_id);
79 requirements.Merge(panel_reqs);
80 }
81 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS &&
82 !door_obj.group_name.empty()) {
83 requirements.items.insert(door_obj.group_ap_item_id);
84 } else {
85 requirements.doors.insert(door_obj.id);
86 }
87
88 doors_[door_id] = requirements;
89 }
90
91 return doors_[door_id];
92 }
93
94 const Requirements& GetPanel(int panel_id) {
95 if (!panels_.count(panel_id)) {
96 Requirements requirements;
97 const Panel& panel_obj = GD_GetPanel(panel_id);
98
99 requirements.rooms.insert(panel_obj.room);
100
101 if (panel_obj.name == "THE MASTER") {
102 requirements.mastery = true;
103 }
104
105 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") &&
106 AP_GetLevel2Requirement() > 1) {
107 requirements.panel_hunt = true;
108 }
109
110 for (int room_id : panel_obj.required_rooms) {
111 requirements.rooms.insert(room_id);
112 }
113
114 for (int door_id : panel_obj.required_doors) {
115 const Requirements& door_reqs = GetDoor(door_id);
116 requirements.Merge(door_reqs);
117 }
118
119 for (int panel_id : panel_obj.required_panels) {
120 const Requirements& panel_reqs = GetPanel(panel_id);
121 requirements.Merge(panel_reqs);
122 }
123
124 if (AP_IsColorShuffle()) {
125 for (LingoColor color : panel_obj.colors) {
126 requirements.items.insert(GD_GetItemIdForColor(color));
127 }
128 }
129
130 panels_[panel_id] = requirements;
131 }
132
133 return panels_[panel_id];
134 }
135
136 private:
137 std::map<int, Requirements> doors_;
138 std::map<int, Requirements> panels_;
139};
140
15struct TrackerState { 141struct TrackerState {
16 std::map<int, bool> reachability; 142 std::map<int, bool> reachability;
17 std::set<int> reachable_doors; 143 std::set<int> reachable_doors;
18 std::mutex reachability_mutex; 144 std::mutex reachability_mutex;
145 RequirementCalculator requirements;
146 std::map<int, std::map<std::string, bool>> door_reports;
19}; 147};
20 148
21enum Decision { kYes, kNo, kMaybe }; 149enum Decision { kYes, kNo, kMaybe };
@@ -172,6 +300,10 @@ class StateCalculator {
172 300
173 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; } 301 const std::set<int>& GetSolveablePanels() const { return solveable_panels_; }
174 302
303 const std::map<int, std::map<std::string, bool>>& GetDoorReports() const {
304 return door_report_;
305 }
306
175 private: 307 private:
176 Decision IsNonGroupedDoorReachable(const Door& door_obj) { 308 Decision IsNonGroupedDoorReachable(const Door& door_obj) {
177 bool has_item = AP_HasItem(door_obj.ap_item_id); 309 bool has_item = AP_HasItem(door_obj.ap_item_id);
@@ -188,68 +320,52 @@ class StateCalculator {
188 return has_item ? kYes : kNo; 320 return has_item ? kYes : kNo;
189 } 321 }
190 322
191 Decision IsDoorReachable_Helper(int door_id) { 323 Decision AreRequirementsSatisfied(const Requirements& reqs, std::map<std::string, bool>* report = nullptr) {
192 const Door& door_obj = GD_GetDoor(door_id); 324 if (reqs.disabled) {
193 325 return kNo;
194 if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { 326 }
195 return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo;
196 } else if (door_obj.type == DoorType::kSunwarp) {
197 switch (AP_GetSunwarpAccess()) {
198 case kSUNWARP_ACCESS_NORMAL:
199 return kYes;
200 case kSUNWARP_ACCESS_DISABLED:
201 return kNo;
202 case kSUNWARP_ACCESS_UNLOCK:
203 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
204 case kSUNWARP_ACCESS_INDIVIDUAL:
205 case kSUNWARP_ACCESS_PROGRESSIVE:
206 return IsNonGroupedDoorReachable(door_obj);
207 }
208 } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) {
209 if (!reachable_rooms_.count(door_obj.room)) {
210 return kMaybe;
211 }
212 327
213 for (int panel_id : door_obj.panels) { 328 Decision final_decision = kYes;
214 if (!solveable_panels_.count(panel_id)) {
215 return kMaybe;
216 }
217 }
218 329
219 return kYes; 330 for (int door_id : reqs.doors) {
220 } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && 331 const Door& door_obj = GD_GetDoor(door_id);
221 !door_obj.group_name.empty()) { 332 Decision decision = IsNonGroupedDoorReachable(door_obj);
222 return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo;
223 } else {
224 return IsNonGroupedDoorReachable(door_obj);
225 }
226 }
227 333
228 Decision IsDoorReachable(int door_id) { 334 if (report) {
229 if (options_.parent) { 335 (*report)[door_obj.item_name] = (decision == kYes);
230 return options_.parent->IsDoorReachable(door_id); 336 }
231 }
232 337
233 if (door_decisions_.count(door_id)) { 338 if (decision != kYes) {
234 return door_decisions_.at(door_id); 339 final_decision = decision;
340 }
235 } 341 }
342
343 for (int item_id : reqs.items) {
344 bool has_item = AP_HasItem(item_id);
345 if (report) {
346 (*report)[AP_GetItemName(item_id)] = has_item;
347 }
236 348
237 Decision result = IsDoorReachable_Helper(door_id); 349 if (!has_item) {
238 if (result != kMaybe) { 350 final_decision = kNo;
239 door_decisions_[door_id] = result; 351 }
240 } 352 }
241 353
242 return result; 354 for (int room_id : reqs.rooms) {
243 } 355 bool reachable = reachable_rooms_.count(room_id);
244 356
245 Decision IsPanelReachable(int panel_id) { 357 if (report) {
246 const Panel& panel_obj = GD_GetPanel(panel_id); 358 std::string report_name =
359 "Reach \"" + GD_GetRoom(room_id).name + "\"";
360 (*report)[report_name] = reachable;
361 }
247 362
248 if (!reachable_rooms_.count(panel_obj.room)) { 363 if (!reachable && final_decision != kNo) {
249 return kMaybe; 364 final_decision = kMaybe;
365 }
250 } 366 }
251 367
252 if (panel_obj.name == "THE MASTER") { 368 if (reqs.mastery) {
253 int achievements_accessible = 0; 369 int achievements_accessible = 0;
254 370
255 for (int achieve_id : GD_GetAchievementPanels()) { 371 for (int achieve_id : GD_GetAchievementPanels()) {
@@ -262,12 +378,18 @@ class StateCalculator {
262 } 378 }
263 } 379 }
264 380
265 return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes 381 bool can_mastery =
266 : kMaybe; 382 (achievements_accessible >= AP_GetMasteryRequirement());
383 if (report) {
384 (*report)["Mastery"] = can_mastery;
385 }
386
387 if (can_mastery && final_decision != kNo) {
388 final_decision = kMaybe;
389 }
267 } 390 }
268 391
269 if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && 392 if (reqs.panel_hunt) {
270 AP_GetLevel2Requirement() > 1) {
271 int counting_panels_accessible = 0; 393 int counting_panels_accessible = 0;
272 394
273 for (int solved_panel_id : solveable_panels_) { 395 for (int solved_panel_id : solveable_panels_) {
@@ -278,44 +400,52 @@ class StateCalculator {
278 } 400 }
279 } 401 }
280 402
281 return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) 403 bool can_level2 =
282 ? kYes 404 (counting_panels_accessible >= AP_GetLevel2Requirement() - 1);
283 : kMaybe; 405 if (report) {
406 std::string report_name =
407 std::to_string(AP_GetLevel2Requirement()) + " Panels";
408 (*report)[report_name] = can_level2;
409 }
410
411 if (can_level2 && final_decision != kNo) {
412 final_decision = kMaybe;
413 }
284 } 414 }
285 415
286 for (int room_id : panel_obj.required_rooms) { 416 return final_decision;
287 if (!reachable_rooms_.count(room_id)) { 417 }
288 return kMaybe; 418
289 } 419 Decision IsDoorReachable_Helper(int door_id) {
420 if (door_report_.count(door_id)) {
421 door_report_[door_id].clear();
422 } else {
423 door_report_[door_id] = {};
290 } 424 }
425
426 return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id),
427 &door_report_[door_id]);
428 }
291 429
292 for (int door_id : panel_obj.required_doors) { 430 Decision IsDoorReachable(int door_id) {
293 Decision door_reachable = IsDoorReachable(door_id); 431 if (options_.parent) {
294 if (door_reachable == kNo) { 432 return options_.parent->IsDoorReachable(door_id);
295 const Door& door_obj = GD_GetDoor(door_id);
296 return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS)
297 ? kMaybe
298 : kNo;
299 } else if (door_reachable == kMaybe) {
300 return kMaybe;
301 }
302 } 433 }
303 434
304 for (int panel_id : panel_obj.required_panels) { 435 if (door_decisions_.count(door_id)) {
305 if (!solveable_panels_.count(panel_id)) { 436 return door_decisions_.at(door_id);
306 return kMaybe;
307 }
308 } 437 }
309 438
310 if (AP_IsColorShuffle()) { 439 Decision result = IsDoorReachable_Helper(door_id);
311 for (LingoColor color : panel_obj.colors) { 440 if (result != kMaybe) {
312 if (!AP_HasItem(GD_GetItemIdForColor(color))) { 441 door_decisions_[door_id] = result;
313 return kNo;
314 }
315 }
316 } 442 }
317 443
318 return kYes; 444 return result;
445 }
446
447 Decision IsPanelReachable(int panel_id) {
448 return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id));
319 } 449 }
320 450
321 Decision IsExitUsable(const Exit& room_exit) { 451 Decision IsExitUsable(const Exit& room_exit) {
@@ -401,10 +531,16 @@ class StateCalculator {
401 std::set<int> reachable_rooms_; 531 std::set<int> reachable_rooms_;
402 std::map<int, Decision> door_decisions_; 532 std::map<int, Decision> door_decisions_;
403 std::set<int> solveable_panels_; 533 std::set<int> solveable_panels_;
534 std::map<int, std::map<std::string, bool>> door_report_;
404}; 535};
405 536
406} // namespace 537} // namespace
407 538
539void ResetReachabilityRequirements() {
540 std::lock_guard reachability_guard(GetState().reachability_mutex);
541 GetState().requirements.Reset();
542}
543
408void RecalculateReachability() { 544void RecalculateReachability() {
409 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); 545 StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")});
410 state_calculator.Calculate(); 546 state_calculator.Calculate();
@@ -435,10 +571,14 @@ void RecalculateReachability() {
435 } 571 }
436 } 572 }
437 573
574 std::map<int, std::map<std::string, bool>> door_reports =
575 state_calculator.GetDoorReports();
576
438 { 577 {
439 std::lock_guard reachability_guard(GetState().reachability_mutex); 578 std::lock_guard reachability_guard(GetState().reachability_mutex);
440 std::swap(GetState().reachability, new_reachability); 579 std::swap(GetState().reachability, new_reachability);
441 std::swap(GetState().reachable_doors, new_reachable_doors); 580 std::swap(GetState().reachable_doors, new_reachable_doors);
581 std::swap(GetState().door_reports, door_reports);
442 } 582 }
443} 583}
444 584
@@ -457,3 +597,9 @@ bool IsDoorOpen(int door_id) {
457 597
458 return GetState().reachable_doors.count(door_id); 598 return GetState().reachable_doors.count(door_id);
459} 599}
600
601const std::map<std::string, bool>& GetDoorRequirements(int door_id) {
602 std::lock_guard reachability_guard(GetState().reachability_mutex);
603
604 return GetState().door_reports.at(door_id);
605}
diff --git a/src/tracker_state.h b/src/tracker_state.h index 119b3b5..7acb0f2 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h
@@ -1,10 +1,17 @@
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
7void ResetReachabilityRequirements();
8
4void RecalculateReachability(); 9void RecalculateReachability();
5 10
6bool IsLocationReachable(int location_id); 11bool IsLocationReachable(int location_id);
7 12
8bool IsDoorOpen(int door_id); 13bool IsDoorOpen(int door_id);
9 14
15const std::map<std::string, bool>& GetDoorRequirements(int door_id);
16
10#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ 17#endif /* end of include guard: TRACKER_STATE_H_8639BC90 */