From 3151ac6274e796f54f2d9269186f1fd2e69f90c3 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 15 May 2024 12:11:00 -0400 Subject: Get checked paintings from server --- src/ap_state.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/ap_state.h') diff --git a/src/ap_state.h b/src/ap_state.h index 6667e0d..5fbb720 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -54,7 +55,9 @@ bool AP_IsColorShuffle(); bool AP_IsPaintingShuffle(); -const std::map AP_GetPaintingMapping(); +const std::map& AP_GetPaintingMapping(); + +const std::set& AP_GetCheckedPaintings(); int AP_GetMasteryRequirement(); -- cgit 1.4.1 From 4d8e36245e8ce43eef9b687a9108fd4c353f756f Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 16 May 2024 17:06:33 -0400 Subject: Added door popups that report requirements --- src/ap_state.cpp | 7 ++ src/ap_state.h | 2 + src/subway_map.cpp | 181 ++++++++++++++++++++--------- src/subway_map.h | 2 + src/tracker_state.cpp | 314 ++++++++++++++++++++++++++++++++++++-------------- src/tracker_state.h | 7 ++ 6 files changed, 377 insertions(+), 136 deletions(-) (limited to 'src/ap_state.h') 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 { connected = true; has_connection_result = true; + ResetReachabilityRequirements(); RefreshTracker(true); std::list corrected_keys; @@ -447,6 +448,8 @@ struct APState { return ap_id; } + std::string GetItemName(int id) { return apclient->get_item_name(id); } + bool HasReachedGoal() { return data_storage.count(victory_data_storage_key) && std::any_cast(data_storage.at(victory_data_storage_key)) == @@ -485,6 +488,10 @@ bool AP_HasItem(int item_id, int quantity) { return GetState().HasItem(item_id, quantity); } +std::string AP_GetItemName(int item_id) { + return GetState().GetItemName(item_id); +} + DoorShuffleMode AP_GetDoorShuffleMode() { return GetState().door_shuffle_mode; } 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); bool AP_HasItem(int item_id, int quantity = 1); +std::string AP_GetItemName(int item_id); + DoorShuffleMode AP_GetDoorShuffleMode(); 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) { return; } + unchecked_eye_ = + wxBitmap(wxImage(GetAbsolutePath("assets/unchecked.png").c_str(), + wxBITMAP_TYPE_PNG) + .Scale(32, 32)); + checked_eye_ = wxBitmap( + wxImage(GetAbsolutePath("assets/checked.png").c_str(), wxBITMAP_TYPE_PNG) + .Scale(32, 32)); + tree_ = std::make_unique>( quadtree::Box{0, 0, static_cast(map_image_.GetWidth()), static_cast(map_image_.GetHeight())}); @@ -113,75 +121,144 @@ void SubwayMap::OnPaint(wxPaintEvent &event) { wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); - if (hovered_item_ && networks_.IsItemInNetwork(*hovered_item_)) { - dc.SetBrush(*wxTRANSPARENT_BRUSH); + if (hovered_item_) { + const SubwayItem &subway_item = GD_GetSubwayItem(*hovered_item_); + if (subway_item.door && !GetDoorRequirements(*subway_item.door).empty()) { + const std::map &report = + GetDoorRequirements(*subway_item.door); - for (const auto &[item_id1, item_id2] : - networks_.GetNetworkGraph(*hovered_item_)) { - const SubwayItem &item1 = GD_GetSubwayItem(item_id1); - const SubwayItem &item2 = GD_GetSubwayItem(item_id2); + int acc_height = 10; + int col_width = 0; - int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; - int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; + for (const auto &[text, obtained] : report) { + wxSize item_extent = dc.GetTextExtent(text); + int item_height = std::max(32, item_extent.GetHeight()) + 10; + acc_height += item_height; - int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_x_; - int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / map_image_.GetWidth() + render_y_; + if (item_extent.GetWidth() > col_width) { + col_width = item_extent.GetWidth(); + } + } + + int item_width = col_width + 10 + 32; + int full_width = item_width + 20; + + int popup_x = (subway_item.x + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_x_; + int popup_y = (subway_item.y + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_y_; + + if (popup_x + full_width > GetSize().GetWidth()) { + popup_x = GetSize().GetWidth() - full_width; + } + if (popup_y + acc_height > GetSize().GetHeight()) { + popup_y = GetSize().GetHeight() - acc_height; + } - int left = std::min(item1_x, item2_x); - int top = std::min(item1_y, item2_y); - int right = std::max(item1_x, item2_x); - int bottom = std::max(item1_y, item2_y); + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(*wxBLACK_BRUSH); + dc.DrawRectangle({popup_x, popup_y}, {full_width, acc_height}); - int halfwidth = right - left; - int halfheight = bottom - top; + dc.SetFont(GetFont()); - if (halfwidth < 4 || halfheight < 4) { - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); - dc.DrawLine(item1_x, item1_y, item2_x, item2_y); - } else { - int ellipse_x; - int ellipse_y; - double start; - double end; + int cur_height = 10; - if (item1_x > item2_x) { - ellipse_y = top; + for (const auto& [text, obtained] : report) { + wxBitmap *eye_ptr = obtained ? &unchecked_eye_ : &checked_eye_; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + dc.DrawBitmap(*eye_ptr, {popup_x + 10, popup_y + cur_height}); - start = 0; - end = 90; - } else { - ellipse_x = left; + dc.SetTextForeground(obtained ? *wxWHITE : *wxRED); + wxSize item_extent = dc.GetTextExtent(text); + dc.DrawText( + text, + {popup_x + 10 + 32 + 10, + popup_y + cur_height + (32 - dc.GetFontMetrics().height) / 2}); - start = 90; - end = 180; - } + cur_height += 10 + 32; + } + } + + if (networks_.IsItemInNetwork(*hovered_item_)) { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + + for (const auto &[item_id1, item_id2] : + networks_.GetNetworkGraph(*hovered_item_)) { + const SubwayItem &item1 = GD_GetSubwayItem(item_id1); + const SubwayItem &item2 = GD_GetSubwayItem(item_id2); + + int item1_x = (item1.x + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_x_; + int item1_y = (item1.y + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_y_; + + int item2_x = (item2.x + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_x_; + int item2_y = (item2.y + AREA_ACTUAL_SIZE / 2) * render_width_ / + map_image_.GetWidth() + + render_y_; + + int left = std::min(item1_x, item2_x); + int top = std::min(item1_y, item2_y); + int right = std::max(item1_x, item2_x); + int bottom = std::max(item1_y, item2_y); + + int halfwidth = right - left; + int halfheight = bottom - top; + + if (halfwidth < 4 || halfheight < 4) { + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); + dc.DrawLine(item1_x, item1_y, item2_x, item2_y); + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); + dc.DrawLine(item1_x, item1_y, item2_x, item2_y); } else { - ellipse_y = top - halfheight; + int ellipse_x; + int ellipse_y; + double start; + double end; - if (item1_y > item2_y) { - ellipse_x = left - halfwidth; + if (item1_x > item2_x) { + ellipse_y = top; - start = 270; - end = 360; + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; + + start = 0; + end = 90; + } else { + ellipse_x = left; + + start = 90; + end = 180; + } } else { - ellipse_x = left; + ellipse_y = top - halfheight; + + if (item1_y > item2_y) { + ellipse_x = left - halfwidth; - start = 180; - end = 270; + start = 270; + end = 360; + } else { + ellipse_x = left; + + start = 180; + end = 270; + } } - } - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); - dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, - start, end); - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); - dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, - start, end); + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4)); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, + start, end); + dc.SetPen(*wxThePenList->FindOrCreatePen(*wxCYAN, 2)); + dc.DrawEllipticArc(ellipse_x, ellipse_y, halfwidth * 2, halfheight * 2, + start, end); + } } } } 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 { wxImage map_image_; wxImage owl_image_; + wxBitmap unchecked_eye_; + wxBitmap checked_eye_; wxBitmap rendered_; 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 @@ namespace { +struct Requirements { + bool disabled = false; + + std::set doors; // non-grouped, handles progressive + std::set items; // all other items + std::set rooms; // maybe + bool mastery = false; // maybe + bool panel_hunt = false; // maybe + + void Merge(const Requirements& rhs) { + if (rhs.disabled) { + return; + } + + for (int id : rhs.doors) { + doors.insert(id); + } + for (int id : rhs.items) { + items.insert(id); + } + for (int id : rhs.rooms) { + rooms.insert(id); + } + mastery = mastery || rhs.mastery; + panel_hunt = panel_hunt || rhs.panel_hunt; + } +}; + +class RequirementCalculator { + public: + void Reset() { + doors_.clear(); + panels_.clear(); + } + + const Requirements& GetDoor(int door_id) { + if (!doors_.count(door_id)) { + Requirements requirements; + const Door& door_obj = GD_GetDoor(door_id); + + if (!AP_IsPilgrimageEnabled() && + door_obj.type == DoorType::kSunPainting) { + requirements.items.insert(door_obj.ap_item_id); + } else if (door_obj.type == DoorType::kSunwarp) { + switch (AP_GetSunwarpAccess()) { + case kSUNWARP_ACCESS_NORMAL: + // Do nothing. + break; + case kSUNWARP_ACCESS_DISABLED: + requirements.disabled = true; + break; + case kSUNWARP_ACCESS_UNLOCK: + requirements.items.insert(door_obj.group_ap_item_id); + break; + case kSUNWARP_ACCESS_INDIVIDUAL: + case kSUNWARP_ACCESS_PROGRESSIVE: + requirements.doors.insert(door_obj.id); + break; + } + } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { + requirements.rooms.insert(door_obj.room); + + for (int panel_id : door_obj.panels) { + const Requirements& panel_reqs = GetPanel(panel_id); + requirements.Merge(panel_reqs); + } + } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && + !door_obj.group_name.empty()) { + requirements.items.insert(door_obj.group_ap_item_id); + } else { + requirements.doors.insert(door_obj.id); + } + + doors_[door_id] = requirements; + } + + return doors_[door_id]; + } + + const Requirements& GetPanel(int panel_id) { + if (!panels_.count(panel_id)) { + Requirements requirements; + const Panel& panel_obj = GD_GetPanel(panel_id); + + requirements.rooms.insert(panel_obj.room); + + if (panel_obj.name == "THE MASTER") { + requirements.mastery = true; + } + + if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && + AP_GetLevel2Requirement() > 1) { + requirements.panel_hunt = true; + } + + for (int room_id : panel_obj.required_rooms) { + requirements.rooms.insert(room_id); + } + + for (int door_id : panel_obj.required_doors) { + const Requirements& door_reqs = GetDoor(door_id); + requirements.Merge(door_reqs); + } + + for (int panel_id : panel_obj.required_panels) { + const Requirements& panel_reqs = GetPanel(panel_id); + requirements.Merge(panel_reqs); + } + + if (AP_IsColorShuffle()) { + for (LingoColor color : panel_obj.colors) { + requirements.items.insert(GD_GetItemIdForColor(color)); + } + } + + panels_[panel_id] = requirements; + } + + return panels_[panel_id]; + } + + private: + std::map doors_; + std::map panels_; +}; + struct TrackerState { std::map reachability; std::set reachable_doors; std::mutex reachability_mutex; + RequirementCalculator requirements; + std::map> door_reports; }; enum Decision { kYes, kNo, kMaybe }; @@ -172,6 +300,10 @@ class StateCalculator { const std::set& GetSolveablePanels() const { return solveable_panels_; } + const std::map>& GetDoorReports() const { + return door_report_; + } + private: Decision IsNonGroupedDoorReachable(const Door& door_obj) { bool has_item = AP_HasItem(door_obj.ap_item_id); @@ -188,68 +320,52 @@ class StateCalculator { return has_item ? kYes : kNo; } - Decision IsDoorReachable_Helper(int door_id) { - const Door& door_obj = GD_GetDoor(door_id); - - if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { - return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; - } else if (door_obj.type == DoorType::kSunwarp) { - switch (AP_GetSunwarpAccess()) { - case kSUNWARP_ACCESS_NORMAL: - return kYes; - case kSUNWARP_ACCESS_DISABLED: - return kNo; - case kSUNWARP_ACCESS_UNLOCK: - return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; - case kSUNWARP_ACCESS_INDIVIDUAL: - case kSUNWARP_ACCESS_PROGRESSIVE: - return IsNonGroupedDoorReachable(door_obj); - } - } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { - if (!reachable_rooms_.count(door_obj.room)) { - return kMaybe; - } + Decision AreRequirementsSatisfied(const Requirements& reqs, std::map* report = nullptr) { + if (reqs.disabled) { + return kNo; + } - for (int panel_id : door_obj.panels) { - if (!solveable_panels_.count(panel_id)) { - return kMaybe; - } - } + Decision final_decision = kYes; - return kYes; - } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && - !door_obj.group_name.empty()) { - return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; - } else { - return IsNonGroupedDoorReachable(door_obj); - } - } + for (int door_id : reqs.doors) { + const Door& door_obj = GD_GetDoor(door_id); + Decision decision = IsNonGroupedDoorReachable(door_obj); - Decision IsDoorReachable(int door_id) { - if (options_.parent) { - return options_.parent->IsDoorReachable(door_id); - } + if (report) { + (*report)[door_obj.item_name] = (decision == kYes); + } - if (door_decisions_.count(door_id)) { - return door_decisions_.at(door_id); + if (decision != kYes) { + final_decision = decision; + } } + + for (int item_id : reqs.items) { + bool has_item = AP_HasItem(item_id); + if (report) { + (*report)[AP_GetItemName(item_id)] = has_item; + } - Decision result = IsDoorReachable_Helper(door_id); - if (result != kMaybe) { - door_decisions_[door_id] = result; + if (!has_item) { + final_decision = kNo; + } } - return result; - } - - Decision IsPanelReachable(int panel_id) { - const Panel& panel_obj = GD_GetPanel(panel_id); + for (int room_id : reqs.rooms) { + bool reachable = reachable_rooms_.count(room_id); + + if (report) { + std::string report_name = + "Reach \"" + GD_GetRoom(room_id).name + "\""; + (*report)[report_name] = reachable; + } - if (!reachable_rooms_.count(panel_obj.room)) { - return kMaybe; + if (!reachable && final_decision != kNo) { + final_decision = kMaybe; + } } - - if (panel_obj.name == "THE MASTER") { + + if (reqs.mastery) { int achievements_accessible = 0; for (int achieve_id : GD_GetAchievementPanels()) { @@ -262,12 +378,18 @@ class StateCalculator { } } - return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes - : kMaybe; + bool can_mastery = + (achievements_accessible >= AP_GetMasteryRequirement()); + if (report) { + (*report)["Mastery"] = can_mastery; + } + + if (can_mastery && final_decision != kNo) { + final_decision = kMaybe; + } } - if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && - AP_GetLevel2Requirement() > 1) { + if (reqs.panel_hunt) { int counting_panels_accessible = 0; for (int solved_panel_id : solveable_panels_) { @@ -278,44 +400,52 @@ class StateCalculator { } } - return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) - ? kYes - : kMaybe; + bool can_level2 = + (counting_panels_accessible >= AP_GetLevel2Requirement() - 1); + if (report) { + std::string report_name = + std::to_string(AP_GetLevel2Requirement()) + " Panels"; + (*report)[report_name] = can_level2; + } + + if (can_level2 && final_decision != kNo) { + final_decision = kMaybe; + } } - for (int room_id : panel_obj.required_rooms) { - if (!reachable_rooms_.count(room_id)) { - return kMaybe; - } + return final_decision; + } + + Decision IsDoorReachable_Helper(int door_id) { + if (door_report_.count(door_id)) { + door_report_[door_id].clear(); + } else { + door_report_[door_id] = {}; } + + return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), + &door_report_[door_id]); + } - for (int door_id : panel_obj.required_doors) { - Decision door_reachable = IsDoorReachable(door_id); - if (door_reachable == kNo) { - const Door& door_obj = GD_GetDoor(door_id); - return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) - ? kMaybe - : kNo; - } else if (door_reachable == kMaybe) { - return kMaybe; - } + Decision IsDoorReachable(int door_id) { + if (options_.parent) { + return options_.parent->IsDoorReachable(door_id); } - for (int panel_id : panel_obj.required_panels) { - if (!solveable_panels_.count(panel_id)) { - return kMaybe; - } + if (door_decisions_.count(door_id)) { + return door_decisions_.at(door_id); } - if (AP_IsColorShuffle()) { - for (LingoColor color : panel_obj.colors) { - if (!AP_HasItem(GD_GetItemIdForColor(color))) { - return kNo; - } - } + Decision result = IsDoorReachable_Helper(door_id); + if (result != kMaybe) { + door_decisions_[door_id] = result; } - return kYes; + return result; + } + + Decision IsPanelReachable(int panel_id) { + return AreRequirementsSatisfied(GetState().requirements.GetPanel(panel_id)); } Decision IsExitUsable(const Exit& room_exit) { @@ -401,10 +531,16 @@ class StateCalculator { std::set reachable_rooms_; std::map door_decisions_; std::set solveable_panels_; + std::map> door_report_; }; } // namespace +void ResetReachabilityRequirements() { + std::lock_guard reachability_guard(GetState().reachability_mutex); + GetState().requirements.Reset(); +} + void RecalculateReachability() { StateCalculator state_calculator({.start = GD_GetRoomByName("Menu")}); state_calculator.Calculate(); @@ -435,10 +571,14 @@ void RecalculateReachability() { } } + std::map> door_reports = + state_calculator.GetDoorReports(); + { std::lock_guard reachability_guard(GetState().reachability_mutex); std::swap(GetState().reachability, new_reachability); std::swap(GetState().reachable_doors, new_reachable_doors); + std::swap(GetState().door_reports, door_reports); } } @@ -457,3 +597,9 @@ bool IsDoorOpen(int door_id) { return GetState().reachable_doors.count(door_id); } + +const std::map& GetDoorRequirements(int door_id) { + std::lock_guard reachability_guard(GetState().reachability_mutex); + + return GetState().door_reports.at(door_id); +} 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 @@ #ifndef TRACKER_STATE_H_8639BC90 #define TRACKER_STATE_H_8639BC90 +#include +#include + +void ResetReachabilityRequirements(); + void RecalculateReachability(); bool IsLocationReachable(int location_id); bool IsDoorOpen(int door_id); +const std::map& GetDoorRequirements(int door_id); + #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ -- cgit 1.4.1 From 13d2a129f6972e6e752da9c9cb686a63d5550517 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 29 May 2024 12:56:29 -0400 Subject: Show unchecked paintings --- src/ap_state.cpp | 12 ++++++++++++ src/ap_state.h | 2 ++ src/area_popup.cpp | 32 ++++++++++++++++++++++++++++++++ src/game_data.cpp | 33 ++++++++++++++++++++++++++------- src/game_data.h | 1 + src/tracker_panel.cpp | 14 ++++++++++++++ src/tracker_state.cpp | 32 +++++++++++++++++--------------- 7 files changed, 104 insertions(+), 22 deletions(-) (limited to 'src/ap_state.h') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 20245e5..0c75eae 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -427,6 +427,14 @@ struct APState { return std::any_cast&>(data_storage.at(key)); } + bool IsPaintingChecked(const std::string& painting_id) { + const auto& checked_paintings = GetCheckedPaintings(); + + return checked_paintings.count(painting_id) || + (painting_mapping.count(painting_id) && + checked_paintings.count(painting_mapping.at(painting_id))); + } + void RefreshTracker(bool reset) { wxLogVerbose("Refreshing display..."); @@ -506,6 +514,10 @@ const std::set& AP_GetCheckedPaintings() { return GetState().GetCheckedPaintings(); } +bool AP_IsPaintingChecked(const std::string& painting_id) { + return GetState().IsPaintingChecked(painting_id); +} + int AP_GetMasteryRequirement() { return GetState().mastery_requirement; } int AP_GetLevel2Requirement() { return GetState().level_2_requirement; } diff --git a/src/ap_state.h b/src/ap_state.h index 0ae6a25..2769bb8 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -61,6 +61,8 @@ const std::map& AP_GetPaintingMapping(); const std::set& AP_GetCheckedPaintings(); +bool AP_IsPaintingChecked(const std::string& painting_id); + int AP_GetMasteryRequirement(); int AP_GetLevel2Requirement(); diff --git a/src/area_popup.cpp b/src/area_popup.cpp index 6e70315..b5c1ccb 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp @@ -65,6 +65,18 @@ void AreaPopup::UpdateIndicators() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + int item_height = std::max(32, item_extent.GetHeight()) + 10; + acc_height += item_height; + + if (item_extent.GetWidth() > col_width) { + col_width = item_extent.GetWidth(); + } + } + } + int item_width = col_width + 10 + 32; int full_width = std::max(header_extent.GetWidth(), item_width) + 20; @@ -109,6 +121,26 @@ void AreaPopup::UpdateIndicators() { cur_height += 10 + 32; } + + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& painting : map_area.paintings) { + bool checked = AP_IsPaintingChecked(painting.id); + wxBitmap* eye_ptr = checked ? &checked_eye_ : &unchecked_eye_; + + mem_dc.DrawBitmap(*eye_ptr, {10, cur_height}); + + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + const wxColour* text_color = reachable ? wxWHITE : wxRED; + mem_dc.SetTextForeground(*text_color); + + wxSize item_extent = mem_dc.GetTextExtent(painting.id); + mem_dc.DrawText(painting.id, + {10 + 32 + 10, + cur_height + (32 - mem_dc.GetFontMetrics().height) / 2}); + + cur_height += 10 + 32; + } + } } void AreaPopup::OnPaint(wxPaintEvent& event) { diff --git a/src/game_data.cpp b/src/game_data.cpp index 7c9564b..4dd69e2 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -284,7 +284,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP location ID for panel %s - %s", - rooms_[room_id].name, panels_[panel_id].name); + rooms_[room_id].name, panels_[panel_id].name); } } } @@ -348,7 +348,7 @@ struct GameData { .as(); } else { wxLogError("Missing AP item ID for door %s - %s", - rooms_[room_id].name, doors_[door_id].name); + rooms_[room_id].name, doors_[door_id].name); } } @@ -422,7 +422,8 @@ struct GameData { std::string painting_id = painting["id"].as(); room_by_painting_[painting_id] = room_id; - if (!painting["exit_only"] || !painting["exit_only"].as()) { + if ((!painting["exit_only"] || !painting["exit_only"].as()) && + (!painting["disable"] || !painting["disable"].as())) { PaintingExit painting_exit; painting_exit.id = painting_id; @@ -594,11 +595,28 @@ struct GameData { } } + for (const Room &room : rooms_) { + std::string area_name = room.name; + if (fold_areas.count(room.name)) { + int fold_area_id = fold_areas[room.name]; + area_name = map_areas_[fold_area_id].name; + } + + if (!room.paintings.empty()) { + int area_id = AddOrGetArea(area_name); + MapArea &map_area = map_areas_[area_id]; + + for (const PaintingExit &painting : room.paintings) { + map_area.paintings.push_back(painting); + } + } + } + // Report errors. for (const std::string &area : malconfigured_areas_) { wxLogError("Area data not found for: %s", area); } - + // Read in subway items. YAML::Node subway_config = YAML::LoadFile(GetAbsolutePath("assets/subway.yaml")); @@ -687,7 +705,8 @@ struct GameData { if (!door_by_id_.count(full_name)) { int door_id = doors_.size(); door_by_id_[full_name] = doors_.size(); - doors_.push_back({.id = door_id, .room = AddOrGetRoom(room), .name = door}); + doors_.push_back( + {.id = door_id, .room = AddOrGetRoom(room), .name = door}); } return door_by_id_[full_name]; @@ -728,7 +747,7 @@ GameData &GetState() { } // namespace -bool SubwaySunwarp::operator<(const SubwaySunwarp& rhs) const { +bool SubwaySunwarp::operator<(const SubwaySunwarp &rhs) const { return std::tie(dots, type) < std::tie(rhs.dots, rhs.type); } @@ -782,7 +801,7 @@ const SubwayItem &GD_GetSubwayItem(int id) { return GetState().subway_items_.at(id); } -int GD_GetSubwayItemForPainting(const std::string& painting_id) { +int GD_GetSubwayItemForPainting(const std::string &painting_id) { #ifndef NDEBUG if (!GetState().subway_item_by_painting_.count(painting_id)) { wxLogError("No subway item for painting %s", painting_id); diff --git a/src/game_data.h b/src/game_data.h index 3afaec3..68ba5e4 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -113,6 +113,7 @@ struct MapArea { int id; std::string name; std::vector locations; + std::vector paintings; int map_x; int map_y; int classification = 0; diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 66bce81..0385f89 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp @@ -171,6 +171,20 @@ void TrackerPanel::Redraw() { } } + if (AP_IsPaintingShuffle()) { + for (const PaintingExit &painting : map_area.paintings) { + if (!AP_IsPaintingChecked(painting.id)) { + bool reachable = painting.door ? IsDoorOpen(*painting.door) : true; + + if (reachable) { + has_reachable_unchecked = true; + } else { + has_unreachable_unchecked = true; + } + } + } + } + int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * final_width / image_size.GetWidth(); 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 869f837..faf74cc 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -15,11 +15,11 @@ namespace { struct Requirements { bool disabled = false; - std::set doors; // non-grouped, handles progressive - std::set items; // all other items - std::set rooms; // maybe - bool mastery = false; // maybe - bool panel_hunt = false; // maybe + std::set doors; // non-grouped, handles progressive + std::set items; // all other items + std::set rooms; // maybe + bool mastery = false; // maybe + bool panel_hunt = false; // maybe void Merge(const Requirements& rhs) { if (rhs.disabled) { @@ -228,7 +228,8 @@ class StateCalculator { if (AP_IsPaintingShuffle()) { for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id)) { + if (AP_GetPaintingMapping().count(out_edge.id) && + AP_GetCheckedPaintings().count(out_edge.id)) { Exit painting_exit; painting_exit.destination_room = GD_GetRoomForPainting( AP_GetPaintingMapping().at(out_edge.id)); @@ -286,7 +287,8 @@ class StateCalculator { panel_boundary = new_panel_boundary; } - // Now that we know the full reachable area, let's make sure all doors are evaluated. + // Now that we know the full reachable area, let's make sure all doors are + // evaluated. for (const Door& door : GD_GetDoors()) { int discard = IsDoorReachable(door.id); } @@ -320,7 +322,8 @@ class StateCalculator { return has_item ? kYes : kNo; } - Decision AreRequirementsSatisfied(const Requirements& reqs, std::map* report = nullptr) { + Decision AreRequirementsSatisfied( + const Requirements& reqs, std::map* report = nullptr) { if (reqs.disabled) { return kNo; } @@ -339,7 +342,7 @@ class StateCalculator { final_decision = decision; } } - + for (int item_id : reqs.items) { bool has_item = AP_HasItem(item_id); if (report) { @@ -353,10 +356,9 @@ class StateCalculator { for (int room_id : reqs.rooms) { bool reachable = reachable_rooms_.count(room_id); - + if (report) { - std::string report_name = - "Reach \"" + GD_GetRoom(room_id).name + "\""; + std::string report_name = "Reach \"" + GD_GetRoom(room_id).name + "\""; (*report)[report_name] = reachable; } @@ -364,7 +366,7 @@ class StateCalculator { final_decision = kMaybe; } } - + if (reqs.mastery) { int achievements_accessible = 0; @@ -407,7 +409,7 @@ class StateCalculator { std::to_string(AP_GetLevel2Requirement()) + " Panels"; (*report)[report_name] = can_level2; } - + if (can_level2 && final_decision != kNo) { final_decision = kMaybe; } @@ -422,7 +424,7 @@ class StateCalculator { } else { door_report_[door_id] = {}; } - + return AreRequirementsSatisfied(GetState().requirements.GetDoor(door_id), &door_report_[door_id]); } -- cgit 1.4.1 From b84a5401359a442b2dff14599f80d47626290fa1 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 6 Jun 2024 14:35:57 -0400 Subject: Shade owls to indicate entrance/exit --- src/ap_state.cpp | 7 +++++++ src/ap_state.h | 2 ++ src/subway_map.cpp | 54 +++++++++++++++++++++++++++++++----------------------- 3 files changed, 40 insertions(+), 23 deletions(-) (limited to 'src/ap_state.h') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 0c75eae..0ce4582 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -70,6 +70,7 @@ struct APState { bool sunwarp_shuffle = false; std::map painting_mapping; + std::set painting_codomain; std::map sunwarp_mapping; void Connect(std::string server, std::string player, std::string password) { @@ -137,6 +138,7 @@ struct APState { color_shuffle = false; painting_shuffle = false; painting_mapping.clear(); + painting_codomain.clear(); mastery_requirement = 21; level_2_requirement = 223; location_checks = kNORMAL_LOCATIONS; @@ -253,6 +255,7 @@ struct APState { for (const auto& mapping_it : slot_data["painting_entrance_to_exit"].items()) { painting_mapping[mapping_it.key()] = mapping_it.value(); + painting_codomain.insert(mapping_it.value()); } } @@ -510,6 +513,10 @@ const std::map& AP_GetPaintingMapping() { return GetState().painting_mapping; } +bool AP_IsPaintingMappedTo(const std::string& painting_id) { + return GetState().painting_codomain.count(painting_id); +} + const std::set& AP_GetCheckedPaintings() { return GetState().GetCheckedPaintings(); } diff --git a/src/ap_state.h b/src/ap_state.h index 2769bb8..7af7395 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -59,6 +59,8 @@ bool AP_IsPaintingShuffle(); const std::map& AP_GetPaintingMapping(); +bool AP_IsPaintingMappedTo(const std::string& painting_id); + const std::set& AP_GetCheckedPaintings(); bool AP_IsPaintingChecked(const std::string& painting_id); diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 0beef76..dde817b 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp @@ -457,7 +457,9 @@ void SubwayMap::OnClickHelp(wxCommandEvent &event) { "your mouse. Click again to stop.\nHover over a door to see the " "requirements to open it.\nHover over a warp or active painting to see " "what it is connected to.\nIn painting shuffle, paintings that have not " - "yet been checked will not show their connections.\nClick on a door or " + "yet been checked will not show their connections.\nA green shaded owl " + "means that there is a painting entrance there.\nA red shaded owl means " + "that there are only painting exits there.\nClick on a door or " "warp to make the popup stick until you click again.", "Subway Map Help"); } @@ -468,28 +470,19 @@ void SubwayMap::Redraw() { wxMemoryDC dc; dc.SelectObject(rendered_); + wxGCDC gcdc(dc); + for (const SubwayItem &subway_item : GD_GetSubwayItems()) { ItemDrawType draw_type = ItemDrawType::kNone; const wxBrush *brush_color = wxGREY_BRUSH; std::optional shade_color; - if (subway_item.door) { - draw_type = ItemDrawType::kBox; - - if (IsDoorOpen(*subway_item.door)) { - if (!subway_item.paintings.empty()) { - draw_type = ItemDrawType::kOwl; - } else { - brush_color = wxGREEN_BRUSH; - } - } else { - brush_color = wxRED_BRUSH; - } - } else if (!subway_item.paintings.empty()) { + if (!subway_item.paintings.empty()) { if (AP_IsPaintingShuffle()) { bool has_checked_painting = false; bool has_unchecked_painting = false; bool has_mapped_painting = false; + bool has_codomain_painting = false; for (const std::string &painting_id : subway_item.paintings) { if (checked_paintings_.count(painting_id)) { @@ -497,26 +490,36 @@ void SubwayMap::Redraw() { if (AP_GetPaintingMapping().count(painting_id)) { has_mapped_painting = true; + } else if (AP_IsPaintingMappedTo(painting_id)) { + has_codomain_painting = true; } } else { has_unchecked_painting = true; } } - if (has_unchecked_painting || has_mapped_painting) { + if (has_unchecked_painting || has_mapped_painting || has_codomain_painting) { draw_type = ItemDrawType::kOwl; - if (has_unchecked_painting) { - if (has_checked_painting) { - shade_color = wxColour(255, 255, 0, 100); + if (has_checked_painting) { + if (has_mapped_painting) { + shade_color = wxColour(0, 255, 0, 128); } else { - shade_color = wxColour(100, 100, 100, 100); + shade_color = wxColour(255, 0, 0, 128); } } } } else if (!subway_item.tags.empty()) { draw_type = ItemDrawType::kOwl; } + } else if (subway_item.door) { + draw_type = ItemDrawType::kBox; + + if (IsDoorOpen(*subway_item.door)) { + brush_color = wxGREEN_BRUSH; + } else { + brush_color = wxRED_BRUSH; + } } wxPoint real_area_pos = {subway_item.x, subway_item.y}; @@ -525,13 +528,18 @@ void SubwayMap::Redraw() { (draw_type == ItemDrawType::kOwl ? OWL_ACTUAL_SIZE : AREA_ACTUAL_SIZE); if (draw_type == ItemDrawType::kBox) { - dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); - dc.SetBrush(*brush_color); - dc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); + gcdc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 1)); + gcdc.SetBrush(*brush_color); + gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); } else if (draw_type == ItemDrawType::kOwl) { wxBitmap owl_bitmap = wxBitmap(owl_image_.Scale( real_area_size, real_area_size, wxIMAGE_QUALITY_BILINEAR)); - dc.DrawBitmap(owl_bitmap, real_area_pos); + gcdc.DrawBitmap(owl_bitmap, real_area_pos); + + if (shade_color) { + gcdc.SetBrush(wxBrush(*shade_color)); + gcdc.DrawRectangle(real_area_pos, {real_area_size, real_area_size}); + } } } } -- cgit 1.4.1