diff options
-rw-r--r-- | src/ap_state.cpp | 7 | ||||
-rw-r--r-- | src/ap_state.h | 2 | ||||
-rw-r--r-- | src/subway_map.cpp | 181 | ||||
-rw-r--r-- | src/subway_map.h | 2 | ||||
-rw-r--r-- | src/tracker_state.cpp | 314 | ||||
-rw-r--r-- | src/tracker_state.h | 7 |
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 | ||
491 | std::string AP_GetItemName(int item_id) { | ||
492 | return GetState().GetItemName(item_id); | ||
493 | } | ||
494 | |||
488 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } | 495 | DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } |
489 | 496 | ||
490 | bool AP_IsColorShuffle() { return GetState().color_shuffle; } | 497 | bool 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 | ||
50 | bool AP_HasItem(int item_id, int quantity = 1); | 50 | bool AP_HasItem(int item_id, int quantity = 1); |
51 | 51 | ||
52 | std::string AP_GetItemName(int item_id); | ||
53 | |||
52 | DoorShuffleMode AP_GetDoorShuffleMode(); | 54 | DoorShuffleMode AP_GetDoorShuffleMode(); |
53 | 55 | ||
54 | bool AP_IsColorShuffle(); | 56 | bool 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 | ||
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 (!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 | |||
15 | struct TrackerState { | 141 | struct 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 | ||
21 | enum Decision { kYes, kNo, kMaybe }; | 149 | enum 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 | ||
539 | void ResetReachabilityRequirements() { | ||
540 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
541 | GetState().requirements.Reset(); | ||
542 | } | ||
543 | |||
408 | void RecalculateReachability() { | 544 | void 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 | |||
601 | const 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 | |||
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 | ||
8 | bool IsDoorOpen(int door_id); | 13 | bool IsDoorOpen(int door_id); |
9 | 14 | ||
15 | const 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 */ |