#include "tracker_panel.h" #include #include #include #include "ap_state.h" #include "area_popup.h" #include "game_data.h" #include "global.h" #include "godot_variant.h" #include "ipc_state.h" #include "tracker_config.h" #include "tracker_state.h" constexpr int AREA_ACTUAL_SIZE = 64; constexpr int AREA_BORDER_SIZE = 5; constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; constexpr int PLAYER_SIZE = 96; TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { SetBackgroundStyle(wxBG_STYLE_PAINT); map_image_ = wxImage(GetAbsolutePath("assets/lingo_map.png").c_str(), wxBITMAP_TYPE_PNG); if (!map_image_.IsOk()) { return; } player_image_ = wxImage(GetAbsolutePath("assets/player.png").c_str(), wxBITMAP_TYPE_PNG); if (!player_image_.IsOk()) { return; } for (const MapArea &map_area : GD_GetMapAreas()) { AreaIndicator area; area.area_id = map_area.id; area.popup = new AreaPopup(this, map_area.id); area.popup->SetPosition({0, 0}); areas_.push_back(area); } Redraw(); Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); } void TrackerPanel::UpdateIndicators() { for (AreaIndicator &area : areas_) { area.popup->UpdateIndicators(); } Redraw(); } void TrackerPanel::SetSavedataPath(std::string savedata_path) { if (!panels_mode_) { wxButton *refresh_button = new wxButton(this, wxID_ANY, "Refresh", {15, 15}); refresh_button->Bind(wxEVT_BUTTON, &TrackerPanel::OnRefreshSavedata, this); } savedata_path_ = savedata_path; panels_mode_ = true; RefreshSavedata(); } void TrackerPanel::RefreshSavedata() { solved_panels_.clear(); GodotVariant godot_variant = ParseGodotFile(*savedata_path_); for (const GodotVariant &panel_node : godot_variant.AsArray()) { const std::vector &fields = panel_node.AsArray(); if (fields[1].AsBool()) { const std::vector &nodepath = fields[0].AsNodePath(); std::string key = fmt::format("{}/{}", nodepath[3], nodepath[4]); solved_panels_.insert(key); } } UpdateIndicators(); Refresh(); } void TrackerPanel::OnPaint(wxPaintEvent &event) { if (GetSize() != rendered_.GetSize()) { Redraw(); } wxBufferedPaintDC dc(this); dc.DrawBitmap(rendered_, 0, 0); std::optional> player_position; if (IPC_IsConnected()) { player_position = IPC_GetPlayerPosition(); } else { player_position = AP_GetPlayerPosition(); } if (player_position.has_value()) { // 1588, 1194 // 14x14 -> 154x154 double intended_x = 1588.0 + (std::get<0>(*player_position) * (154.0 / 14.0)); double intended_y = 1194.0 + (std::get<1>(*player_position) * (154.0 / 14.0)); int real_x = offset_x_ + scale_x_ * intended_x - scaled_player_.GetWidth() / 2; int real_y = offset_y_ + scale_y_ * intended_y - scaled_player_.GetHeight() / 2; dc.DrawBitmap(scaled_player_, real_x, real_y); } event.Skip(); } void TrackerPanel::OnMouseMove(wxMouseEvent &event) { for (AreaIndicator &area : areas_) { if (area.active && area.real_x1 <= event.GetX() && event.GetX() < area.real_x2 && area.real_y1 <= event.GetY() && event.GetY() < area.real_y2) { area.popup->Show(); } else { area.popup->Hide(); } } event.Skip(); } void TrackerPanel::OnRefreshSavedata(wxCommandEvent &event) { RefreshSavedata(); } void TrackerPanel::Redraw() { wxSize panel_size = GetSize(); wxSize image_size = map_image_.GetSize(); int final_x = 0; int final_y = 0; int final_width = panel_size.GetWidth(); int final_height = panel_size.GetHeight(); if (image_size.GetWidth() * panel_size.GetHeight() > panel_size.GetWidth() * image_size.GetHeight()) { final_height = (panel_size.GetWidth() * image_size.GetHeight()) / image_size.GetWidth(); final_y = (panel_size.GetHeight() - final_height) / 2; } else { final_width = (image_size.GetWidth() * panel_size.GetHeight()) / image_size.GetHeight(); final_x = (panel_size.GetWidth() - final_width) / 2; } rendered_ = wxBitmap( map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) .Size(panel_size, {final_x, final_y}, 0, 0, 0)); offset_x_ = final_x; offset_y_ = final_y; scale_x_ = static_cast(final_width) / image_size.GetWidth(); scale_y_ = static_cast(final_height) / image_size.GetHeight(); int player_width = PLAYER_SIZE * scale_x_; int player_height = PLAYER_SIZE * scale_y_; scaled_player_ = wxBitmap(player_image_.Scale(player_width > 0 ? player_width : 1, player_height > 0 ? player_height : 1)); wxMemoryDC dc; dc.SelectObject(rendered_); int real_area_size = final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); int actual_border_size = real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; const wxPoint upper_left_triangle[] = { {0, 0}, {0, real_area_size}, {real_area_size, 0}}; const wxPoint lower_right_triangle[] = {{0, real_area_size - 1}, {real_area_size - 1, 0}, {real_area_size, real_area_size}}; for (AreaIndicator &area : areas_) { const MapArea &map_area = GD_GetMapArea(area.area_id); if (panels_mode_) { area.active = map_area.has_single_panel; } else if (!AP_IsLocationVisible(map_area.classification) && !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { area.active = false; } else { area.active = true; } if (!area.active) { continue; } bool has_reachable_unchecked = false; bool has_unreachable_unchecked = false; for (const Location §ion : map_area.locations) { bool has_unchecked = false; if (IsLocationWinCondition(section)) { has_unchecked = !AP_HasReachedGoal(); } else if (panels_mode_) { if (section.single_panel) { const Panel &panel = GD_GetPanel(*section.single_panel); if (panel.non_counting) { has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); } else { has_unchecked = !solved_panels_.contains(panel.nodepath); } } } else if (AP_IsLocationVisible(section.classification)) { has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { has_unchecked = !AP_HasCheckedHuntPanel(section.ap_location_id); } if (has_unchecked) { if (IsLocationReachable(section.ap_location_id)) { has_reachable_unchecked = true; } else { has_unreachable_unchecked = true; } } } if (AP_IsPaintingShuffle() && !panels_mode_) { for (int painting_id : map_area.paintings) { const PaintingExit &painting = GD_GetPaintingExit(painting_id); bool reachable = IsPaintingReachable(painting_id); if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { 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)) * final_width / image_size.GetWidth(); if (has_reachable_unchecked && has_unreachable_unchecked && GetTrackerConfig().hybrid_areas) { dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(*wxGREEN_BRUSH); dc.DrawPolygon(3, upper_left_triangle, real_area_x, real_area_y); dc.SetBrush(*wxRED_BRUSH); dc.DrawPolygon(3, lower_right_triangle, real_area_x, real_area_y); dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle({real_area_x, real_area_y}, {real_area_size, real_area_size}); } else { const wxBrush *brush_color = wxGREY_BRUSH; if (has_reachable_unchecked && has_unreachable_unchecked) { brush_color = wxYELLOW_BRUSH; } else if (has_reachable_unchecked) { brush_color = wxGREEN_BRUSH; } else if (has_unreachable_unchecked) { brush_color = wxRED_BRUSH; } dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); dc.SetBrush(*brush_color); dc.DrawRectangle({real_area_x, real_area_y}, {real_area_size, real_area_size}); } area.real_x1 = real_area_x; area.real_x2 = real_area_x + real_area_size; area.real_y1 = real_area_y; area.real_y2 = real_area_y + real_area_size; int popup_x = final_x + map_area.map_x * final_width / image_size.GetWidth(); int popup_y = final_y + map_area.map_y * final_width / image_size.GetWidth(); area.popup->SetClientSize( area.popup->GetVirtualSize().GetWidth(), std::min(panel_size.GetHeight(), area.popup->GetVirtualSize().GetHeight())); if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); } if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); } area.popup->SetPosition({popup_x, popup_y}); } }