#include "ui/map_sim_frame.h" #include "lua/game_engine.h" extern "C" { #include } #include #include #include #include #include enum { ID_PlayBtn = wxID_HIGHEST + 100, ID_ClearBtn, ID_SimTimer }; wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame) EVT_BUTTON(ID_PlayBtn, MapSimFrame::OnPlay) EVT_BUTTON(ID_ClearBtn, MapSimFrame::OnClear) EVT_TIMER(ID_SimTimer, MapSimFrame::OnSimTimer) wxEND_EVENT_TABLE() // Escape a string for safe embedding inside a JS single-quoted string static wxString jsEscape(const std::string &s) { wxString out; for (const unsigned char c : s) { if (c == '\'') out += "\\'"; else if (c == '\\') out += "\\\\"; else if (c == '\n') out += "\\n"; else if (c == '\r') out += "\\r"; else out += static_cast(c); } return out; } MapSimFrame::MapSimFrame(wxWindow *parent, double centerLat, double centerLon, const std::vector &zoneInfos) : wxFrame(parent, wxID_ANY, "GPS-Simulation", wxDefaultPosition, wxSize(900, 700)), m_simTimer(this, ID_SimTimer), m_zoneInfos(zoneInfos) { auto *sizer = new wxBoxSizer(wxVERTICAL); m_webView = wxWebView::New(this, wxID_ANY); sizer->Add(m_webView, 1, wxEXPAND); auto *btnSizer = new wxBoxSizer(wxHORIZONTAL); btnSizer->Add(new wxButton(this, ID_PlayBtn, "▶ Route abspielen"), 0, wxALL, 5); btnSizer->Add(new wxButton(this, ID_ClearBtn, "✕ Wegpunkte löschen"), 0, wxALL, 5); sizer->Add(btnSizer, 0, wxALIGN_CENTER); SetSizer(sizer); // Register JS → C++ message handler (wxWidgets 3.1.5+) m_webView->AddScriptMessageHandler("wx"); m_webView->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &MapSimFrame::OnScriptMessage, this); m_webView->Bind(wxEVT_WEBVIEW_LOADED, &MapSimFrame::OnWebViewLoaded, this); wherigo::GameEngine::getInstance().Bind( wherigo::EVT_GAME_STATE_CHANGED, &MapSimFrame::OnGameStateChanged, this); Bind(wxEVT_CLOSE_WINDOW, &MapSimFrame::OnClose, this); // ── Build Leaflet page ─────────────────────────────────────────────────── wxString html; html << "" "GPS-Sim" "" "" "" "" "" "" "" "
" ""; m_webView->SetPage(html, ""); } // ── JS → C++ message // ────────────────────────────────────────────────────────── static bool parseLatLon(const wxString &json, double &lat, double &lon) { auto extractNum = [&](const wxString &key, double &val) -> bool { int pos = json.Find(key); if (pos == wxNOT_FOUND) return false; pos += static_cast(key.length()); while (pos < static_cast(json.length()) && (json[pos] == ' ' || json[pos] == ':')) ++pos; const int start = pos; while (pos < static_cast(json.length()) && (wxIsdigit(json[pos]) || json[pos] == '.' || json[pos] == '-')) ++pos; return json.Mid(start, pos - start).ToDouble(&val); }; return extractNum("\"lat\"", lat) && extractNum("\"lon\"", lon); } void MapSimFrame::OnScriptMessage(wxWebViewEvent &event) { double lat = 0, lon = 0; if (parseLatLon(event.GetString(), lat, lon)) { AddSimPoint(lat, lon); wxLogDebug("MapSim: waypoint %zu added (%.6f, %.6f)", m_route.size(), lat, lon); } } // ── Route management // ────────────────────────────────────────────────────────── void MapSimFrame::AddSimPoint(double lat, double lon) { m_route.push_back({lat, lon}); } void MapSimFrame::StartSimulation() { if (m_route.empty()) return; m_simIndex = 0; m_simTimer.Start(500); } // ── Button handlers // ─────────────────────────────────────────────────────────── void MapSimFrame::OnPlay(wxCommandEvent &) { if (m_route.empty()) { wxMessageBox("Bitte zuerst Wegpunkte auf der Karte setzen.", "Hinweis", wxOK | wxICON_INFORMATION); return; } m_simIndex = 0; m_simTimer.Start(500); wxLogDebug("MapSim: starting playback of %zu waypoints", m_route.size()); } void MapSimFrame::OnClear(wxCommandEvent &) { m_simTimer.Stop(); m_route.clear(); m_simIndex = 0; // Remove all route markers and the connecting line from the map m_webView->RunScript( "routeMarkers.forEach(function(m){map.removeLayer(m);});" "routeMarkers=[];" "if(routeLine){map.removeLayer(routeLine);routeLine=null;}", nullptr); wxLogDebug("MapSim: route cleared"); } void MapSimFrame::OnSimTimer(wxTimerEvent &) { if (m_simIndex >= m_route.size()) { wxCommandEvent dummy; OnClear(dummy); return; } const auto &pt = m_route[m_simIndex++]; SendPositionToEngine(pt.lat, pt.lon); } // ── Zone sync // ───────────────────────────────────────────────────────────────── void MapSimFrame::syncZonesOnMap() { if (!m_pageLoaded) return; lua_State *L = wherigo::GameEngine::getInstance().getLuaState(); if (!L) return; wxString js = "syncZones(["; bool first = true; lua_getglobal(L, "_G"); lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_istable(L, -1)) { lua_getfield(L, -1, "ClassName"); const bool isZone = lua_isstring(L, -1) && !strcmp(lua_tostring(L, -1), "Zone"); lua_pop(L, 1); if (isZone) { lua_getfield(L, -1, "Active"); const bool active = lua_toboolean(L, -1); lua_pop(L, 1); if (active) { lua_getfield(L, -1, "Name"); const std::string name = lua_isstring(L, -1) ? lua_tostring(L, -1) : ""; lua_pop(L, 1); lua_getfield(L, -1, "OriginalPoint"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "latitude"); lua_getfield(L, -2, "longitude"); const double lat = lua_tonumber(L, -2); const double lon = lua_tonumber(L, -1); lua_pop(L, 2); if (!first) js += ","; js += wxString::Format("{name:'%s',lat:%.8f,lon:%.8f}", jsEscape(name), lat, lon); first = false; } lua_pop(L, 1); // pop OriginalPoint } } } lua_pop(L, 1); } lua_pop(L, 1); // pop _G js += "]);"; m_webView->RunScript(js, nullptr); } void MapSimFrame::OnWebViewLoaded(wxWebViewEvent &) { m_pageLoaded = true; syncZonesOnMap(); } void MapSimFrame::OnGameStateChanged(wxEvent &) { syncZonesOnMap(); } void MapSimFrame::OnClose(wxCloseEvent &event) { wherigo::GameEngine::getInstance().Unbind( wherigo::EVT_GAME_STATE_CHANGED, &MapSimFrame::OnGameStateChanged, this); event.Skip(); } // ── Engine integration // ──────────────────────────────────────────────────────── void MapSimFrame::SendPositionToEngine(double lat, double lon) { wxLogDebug("MapSim: position → engine (%.6f, %.6f)", lat, lon); wherigo::GameEngine::getInstance().updatePlayerPosition(lat, lon, 0.0); m_webView->RunScript( wxString::Format("updatePlayerPos(%.8f,%.8f);", lat, lon), nullptr); }