some tweaks
still non playable cartridges Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
+362
-62
@@ -1,79 +1,379 @@
|
||||
#include "ui/map_sim_frame.h"
|
||||
#include <wx/sizer.h>
|
||||
#include "lua/game_engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/webview.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/webview.h>
|
||||
|
||||
enum { ID_PlayBtn = wxID_HIGHEST + 100, ID_ClearBtn, ID_SimTimer };
|
||||
|
||||
wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame)
|
||||
EVT_WEBVIEW_NAVIGATED(wxID_ANY, MapSimFrame::OnWebViewEvent)
|
||||
EVT_BUTTON(wxID_ANY, MapSimFrame::OnPlay)
|
||||
wxEND_EVENT_TABLE()
|
||||
EVT_BUTTON(ID_PlayBtn, MapSimFrame::OnPlay)
|
||||
EVT_BUTTON(ID_ClearBtn, MapSimFrame::OnClear)
|
||||
EVT_TIMER(ID_SimTimer, MapSimFrame::OnSimTimer) wxEND_EVENT_TABLE()
|
||||
|
||||
MapSimFrame::MapSimFrame(wxWindow* parent, double centerLat, double centerLon, const std::vector<std::pair<double, double>>& zoneCoords)
|
||||
: wxFrame(parent, wxID_ANY, "Karten-Simulation", wxDefaultPosition, wxSize(900, 700)), m_zoneCoords(zoneCoords) {
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_webView = wxWebView::New(this, wxID_ANY);
|
||||
sizer->Add(m_webView, 1, wxEXPAND);
|
||||
auto* playBtn = new wxButton(this, wxID_ANY, "Simulation starten");
|
||||
sizer->Add(playBtn, 0, wxALL | wxALIGN_CENTER, 8);
|
||||
SetSizer(sizer);
|
||||
// Leaflet-Karte laden (dynamisch zentriert und Marker)
|
||||
wxString html;
|
||||
html << "<!DOCTYPE html><html><head><meta charset='utf-8'><title>MapSim</title>"
|
||||
"<link rel='stylesheet' href='https://unpkg.com/leaflet/dist/leaflet.css'/>"
|
||||
"<script src='https://unpkg.com/leaflet/dist/leaflet.js'></script>"
|
||||
"<style>html,body,#map{height:100%%;margin:0;padding:0;}#map{height:600px;}</style>"
|
||||
"</head><body><div id='map'></div>"
|
||||
"<script>"
|
||||
"var map = L.map('map').setView([" << wxString::Format("%.8f, %.8f", centerLat, centerLon) << "], 16);"
|
||||
"L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 19}).addTo(map);"
|
||||
"var markers = [];";
|
||||
// Marker für alle Zonen
|
||||
for (const auto& z : zoneCoords) {
|
||||
html << wxString::Format("var m = L.marker([%.8f, %.8f]).addTo(map); markers.push(m);\n", z.first, z.second);
|
||||
}
|
||||
html <<
|
||||
"map.on('click', function(e) {"
|
||||
" var marker = L.marker(e.latlng).addTo(map);"
|
||||
" markers.push(marker);"
|
||||
" window.wx.postMessage(JSON.stringify({lat: e.latlng.lat, lon: e.latlng.lng}));"
|
||||
"});"
|
||||
"</script></body></html>";
|
||||
m_webView->SetPage(html, "");
|
||||
// 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<char>(c);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void MapSimFrame::OnWebViewEvent(wxWebViewEvent& event) {
|
||||
// Empfange Marker-Koordinaten von JS
|
||||
wxString msg = event.GetString();
|
||||
double lat = 0, lon = 0;
|
||||
if (msg.ToDouble(&lat)) {
|
||||
// Not used, see below
|
||||
}
|
||||
// In wxWidgets 3.1+ kann man wxWebView::RegisterHandler für JS->C++ nutzen
|
||||
// Hier: Marker werden über postMessage als JSON gesendet
|
||||
// TODO: JSON parsen und AddSimPoint aufrufen
|
||||
MapSimFrame::MapSimFrame(wxWindow *parent, double centerLat, double centerLon,
|
||||
const std::vector<ZoneInfo> &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 << "<!DOCTYPE html><html><head>"
|
||||
"<meta charset='utf-8'><title>GPS-Sim</title>"
|
||||
"<link rel='stylesheet' "
|
||||
"href='https://unpkg.com/leaflet/dist/leaflet.css'/>"
|
||||
"<link rel='stylesheet' "
|
||||
"href='https://unpkg.com/leaflet.markercluster/dist/"
|
||||
"MarkerCluster.css'/>"
|
||||
"<link rel='stylesheet' "
|
||||
"href='https://unpkg.com/leaflet.markercluster/dist/"
|
||||
"MarkerCluster.Default.css'/>"
|
||||
"<script src='https://unpkg.com/leaflet/dist/leaflet.js'></script>"
|
||||
"<script "
|
||||
"src='https://unpkg.com/leaflet.markercluster/dist/"
|
||||
"leaflet.markercluster.js'></script>"
|
||||
"<style>"
|
||||
" html,body,#map{height:100%;margin:0;padding:0;}"
|
||||
" .zone-dot{"
|
||||
" width:14px;height:14px;border-radius:50%;"
|
||||
" background:#e44;border:2px solid #900;"
|
||||
" box-shadow:0 1px 3px rgba(0,0,0,.4);"
|
||||
" }"
|
||||
" .zone-cluster{"
|
||||
" background:#e44;border:3px solid #900;border-radius:50%;"
|
||||
" color:#fff;font-weight:bold;font-size:13px;"
|
||||
" display:flex;align-items:center;justify-content:center;"
|
||||
" box-shadow:0 2px 5px rgba(0,0,0,.4);"
|
||||
" }"
|
||||
" .zone-label{"
|
||||
" background:transparent;border:none;font-weight:bold;"
|
||||
" font-size:12px;color:#900;"
|
||||
" text-shadow:0 0 3px #fff,0 0 3px #fff,0 0 3px #fff;"
|
||||
" white-space:nowrap;pointer-events:none;"
|
||||
" }"
|
||||
"</style>"
|
||||
"</head><body>"
|
||||
"<div id='map' style='height:calc(100vh - 50px);'></div>"
|
||||
"<script>"
|
||||
<< wxString::Format("var map=L.map('map').setView([%.8f,%.8f],16);",
|
||||
centerLat, centerLon)
|
||||
<< "L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',"
|
||||
" {maxZoom:19,attribution:'© OpenStreetMap'}).addTo(map);"
|
||||
"var routeMarkers=[];"
|
||||
"var routeLine=null;"
|
||||
|
||||
// Zone cluster group with custom cluster icon
|
||||
"var zoneCluster=L.markerClusterGroup({"
|
||||
" maxClusterRadius:60,"
|
||||
" iconCreateFunction:function(c){"
|
||||
" var n=c.getChildCount();"
|
||||
" var s=n<10?32:n<100?38:44;"
|
||||
" return L.divIcon({"
|
||||
" html:'<div class=\"zone-cluster\" "
|
||||
"style=\"width:'+s+'px;height:'+s+'px;\">'+n+'</div>',"
|
||||
" className:'',"
|
||||
" iconSize:[s,s],iconAnchor:[s/2,s/2]"
|
||||
" });"
|
||||
" }"
|
||||
"});";
|
||||
|
||||
html << "map.addLayer(zoneCluster);"
|
||||
|
||||
// Dynamic zone sync: adds new active zones, removes deactivated ones
|
||||
"var zoneMarkerMap={};"
|
||||
"function syncZones(zones){"
|
||||
" var active={};"
|
||||
" zones.forEach(function(z){"
|
||||
" active[z.name]=true;"
|
||||
" if(!zoneMarkerMap[z.name]){"
|
||||
" var zm=L.marker([z.lat,z.lon],{"
|
||||
" icon:L.divIcon({"
|
||||
" html:'<div class=\"zone-dot\"></div>',"
|
||||
" className:'',"
|
||||
" iconSize:[14,14],iconAnchor:[7,7]"
|
||||
" })"
|
||||
" });"
|
||||
" zm.bindTooltip(z.name,{"
|
||||
" permanent:true,direction:'top',"
|
||||
" offset:[0,-10],className:'zone-label'"
|
||||
" });"
|
||||
" zoneCluster.addLayer(zm);"
|
||||
" zoneMarkerMap[z.name]=zm;"
|
||||
" }"
|
||||
" });"
|
||||
" Object.keys(zoneMarkerMap).forEach(function(name){"
|
||||
" if(!active[name]){"
|
||||
" zoneCluster.removeLayer(zoneMarkerMap[name]);"
|
||||
" delete zoneMarkerMap[name];"
|
||||
" }"
|
||||
" });"
|
||||
"}"
|
||||
|
||||
// Player position marker (hidden until first position update)
|
||||
"var playerMarker=null;"
|
||||
"function updatePlayerPos(lat,lon){"
|
||||
" if(!playerMarker){"
|
||||
" playerMarker=L.marker([lat,lon],{"
|
||||
" icon:L.divIcon({"
|
||||
" html:'<div style=\"width:18px;height:18px;border-radius:50%;"
|
||||
" background:#0077cc;border:3px solid #fff;"
|
||||
" box-shadow:0 0 0 2px #0077cc,0 2px 6px "
|
||||
"rgba(0,0,0,.5);\"></div>',"
|
||||
" className:'',"
|
||||
" iconSize:[18,18],iconAnchor:[9,9]"
|
||||
" })"
|
||||
" }).addTo(map);"
|
||||
" "
|
||||
"playerMarker.bindTooltip('Spieler',{permanent:false,direction:'top'}"
|
||||
");"
|
||||
" } else {"
|
||||
" playerMarker.setLatLng([lat,lon]);"
|
||||
" }"
|
||||
" map.panTo([lat,lon]);"
|
||||
"}"
|
||||
|
||||
// Click handler: add numbered route waypoint (NOT in cluster)
|
||||
"function updateLine(){"
|
||||
" if(routeLine){map.removeLayer(routeLine);routeLine=null;}"
|
||||
" if(routeMarkers.length>1){"
|
||||
" var lls=routeMarkers.map(function(m){return m.getLatLng();});"
|
||||
" "
|
||||
"routeLine=L.polyline(lls,{color:'#0077cc',weight:2,dashArray:'6,4'})"
|
||||
".addTo(map);"
|
||||
" }"
|
||||
"}"
|
||||
"map.on('click',function(e){"
|
||||
" var n=routeMarkers.length+1;"
|
||||
" var m=L.marker(e.latlng,{"
|
||||
" icon:L.divIcon({"
|
||||
" html:'<div "
|
||||
"style=\"background:#0077cc;color:#fff;border-radius:50%;width:20px;"
|
||||
" "
|
||||
"height:20px;line-height:20px;text-align:center;font-size:11px;"
|
||||
" font-weight:bold;border:2px solid "
|
||||
"#005299;\">'+n+'</div>',"
|
||||
" className:'',"
|
||||
" iconSize:[20,20],iconAnchor:[10,10]"
|
||||
" })"
|
||||
" }).addTo(map);"
|
||||
" routeMarkers.push(m);"
|
||||
" updateLine();"
|
||||
" "
|
||||
"window.wx.postMessage(JSON.stringify({lat:e.latlng.lat,lon:e.latlng."
|
||||
"lng}));"
|
||||
"});"
|
||||
"</script></body></html>";
|
||||
|
||||
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<int>(key.length());
|
||||
while (pos < static_cast<int>(json.length()) &&
|
||||
(json[pos] == ' ' || json[pos] == ':'))
|
||||
++pos;
|
||||
const int start = pos;
|
||||
while (pos < static_cast<int>(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});
|
||||
m_route.push_back({lat, lon});
|
||||
}
|
||||
|
||||
void MapSimFrame::OnPlay(wxCommandEvent&) {
|
||||
if (m_route.empty()) {
|
||||
wxMessageBox("Bitte zuerst Marker setzen.", "Hinweis", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
// Simuliere Bewegung entlang der Route
|
||||
for (const auto& pt : m_route) {
|
||||
SendPositionToEngine(pt.lat, pt.lon);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
wxMessageBox("Simulation beendet.", "Info", wxOK | wxICON_INFORMATION);
|
||||
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) {
|
||||
// TODO: Engine-Integration: GPS-Position setzen
|
||||
// Beispiel: wxGetApp().setSimulatedPosition(lat, 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user