some tweaks
still non playable cartridges Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
+4
-4
@@ -16,16 +16,16 @@ public:
|
||||
int OnExit() override;
|
||||
|
||||
bool loadCartridge(const std::string &filePath);
|
||||
void startGame();
|
||||
void startGame() const;
|
||||
void unloadCartridge();
|
||||
|
||||
// Save/Load game state
|
||||
bool saveGameState(const std::string &saveFilePath);
|
||||
bool loadGameState(const std::string &saveFilePath);
|
||||
bool saveGameState(const std::string &saveFilePath) const;
|
||||
bool loadGameState(const std::string &saveFilePath) const;
|
||||
std::string getAutoSavePath() const;
|
||||
|
||||
// Generate completion log (for wherigo.com)
|
||||
bool generateCompletionLog(const std::string &logFilePath);
|
||||
bool generateCompletionLog(const std::string &logFilePath) const;
|
||||
std::string getCompletionLogPath() const;
|
||||
|
||||
lua_State* getLuaState() const { return m_luaState; }
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
|
||||
// Notify listeners that game state has changed
|
||||
void notifyStateChanged();
|
||||
void rebuildPlayerInventory();
|
||||
|
||||
lua_State* getLuaState() const { return m_luaState; }
|
||||
int getCartridgeRef() const { return m_cartridgeRef; }
|
||||
@@ -64,6 +65,8 @@ private:
|
||||
double m_playerLng = 0.0;
|
||||
double m_playerAlt = 0.0;
|
||||
|
||||
bool m_isProcessing = false;
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
// ── Unit conversion ───────────────────────────────────────────────────────────
|
||||
|
||||
inline double distanceToMeters(double value, const char *units) {
|
||||
if (!units || !strcmp(units,"meters") || !strcmp(units,"metres") || !strcmp(units,"m"))
|
||||
return value;
|
||||
if (!strcmp(units,"kilometers") || !strcmp(units,"kilometres") || !strcmp(units,"km"))
|
||||
return value * 1000.0;
|
||||
if (!strcmp(units,"feet") || !strcmp(units,"ft"))
|
||||
return value * 0.3048;
|
||||
if (!strcmp(units,"miles") || !strcmp(units,"mi"))
|
||||
return value * 1609.344;
|
||||
if (!strcmp(units,"nauticalmiles"))
|
||||
return value * 1852.0;
|
||||
return value;
|
||||
}
|
||||
|
||||
inline double metersToUnit(double meters, const char *units) {
|
||||
if (!units || !strcmp(units,"meters") || !strcmp(units,"metres") || !strcmp(units,"m"))
|
||||
return meters;
|
||||
if (!strcmp(units,"kilometers") || !strcmp(units,"kilometres") || !strcmp(units,"km"))
|
||||
return meters / 1000.0;
|
||||
if (!strcmp(units,"feet") || !strcmp(units,"ft"))
|
||||
return meters / 0.3048;
|
||||
if (!strcmp(units,"miles") || !strcmp(units,"mi"))
|
||||
return meters / 1609.344;
|
||||
if (!strcmp(units,"nauticalmiles"))
|
||||
return meters / 1852.0;
|
||||
return meters;
|
||||
}
|
||||
|
||||
// ── Geodesic calculations ─────────────────────────────────────────────────────
|
||||
|
||||
// Haversine formula → {distance_meters, bearing_degrees [0,360)}
|
||||
inline std::pair<double,double> haversine(double lat1, double lon1,
|
||||
double lat2, double lon2) {
|
||||
constexpr double R = 6371000.0;
|
||||
constexpr double D2R = M_PI / 180.0;
|
||||
const double rl1 = lat1*D2R, rl2 = lat2*D2R;
|
||||
const double dl = (lat2-lat1)*D2R, dl2 = (lon2-lon1)*D2R;
|
||||
const double a = std::sin(dl/2)*std::sin(dl/2) +
|
||||
std::cos(rl1)*std::cos(rl2)*std::sin(dl2/2)*std::sin(dl2/2);
|
||||
const double dist = R * 2.0 * std::asin(std::min(1.0, std::sqrt(a)));
|
||||
const double brg = std::atan2(std::sin(dl2)*std::cos(rl2),
|
||||
std::cos(rl1)*std::sin(rl2) -
|
||||
std::sin(rl1)*std::cos(rl2)*std::cos(dl2));
|
||||
return {dist, std::fmod(brg*(180.0/M_PI)+360.0, 360.0)};
|
||||
}
|
||||
|
||||
// Destination point given start, distance (m), bearing (deg)
|
||||
inline std::pair<double,double> translatePoint(double lat, double lon,
|
||||
double distMeters, double bearingDeg) {
|
||||
constexpr double R = 6371000.0;
|
||||
constexpr double D2R = M_PI / 180.0;
|
||||
constexpr double R2D = 180.0 / M_PI;
|
||||
const double d = distMeters / R;
|
||||
const double b = bearingDeg * D2R;
|
||||
const double r1 = lat * D2R;
|
||||
const double r2 = std::asin(std::sin(r1)*std::cos(d) +
|
||||
std::cos(r1)*std::sin(d)*std::cos(b));
|
||||
const double dl = std::atan2(std::sin(b)*std::sin(d)*std::cos(r1),
|
||||
std::cos(d) - std::sin(r1)*std::sin(r2));
|
||||
return {r2*R2D, lon + dl*R2D};
|
||||
}
|
||||
|
||||
// Ray-casting point-in-polygon. pts = {lat, lon} pairs.
|
||||
inline bool pointInPolygon(double playerLat, double playerLon,
|
||||
const std::vector<std::pair<double,double>>& pts) {
|
||||
const int n = static_cast<int>(pts.size());
|
||||
if (n < 3) return false;
|
||||
bool inside = false;
|
||||
for (int i = 0, j = n-1; i < n; j = i++) {
|
||||
const double lati = pts[i].first, loni = pts[i].second;
|
||||
const double latj = pts[j].first, lonj = pts[j].second;
|
||||
if (((lati > playerLat) != (latj > playerLat)) &&
|
||||
(playerLon < (lonj-loni)*(playerLat-lati)/(latj-lati) + loni))
|
||||
inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
// Distance (m) from point to nearest point on segment; optionally returns that point
|
||||
inline double distToSegment(double plat, double plon,
|
||||
double lat1, double lon1,
|
||||
double lat2, double lon2,
|
||||
double *nearLatOut = nullptr,
|
||||
double *nearLonOut = nullptr) {
|
||||
const double dlat = lat2-lat1, dlon = lon2-lon1;
|
||||
const double len2 = dlat*dlat + dlon*dlon;
|
||||
const double t = (len2 > 1e-12)
|
||||
? std::max(0.0, std::min(1.0,
|
||||
((plat-lat1)*dlat + (plon-lon1)*dlon) / len2))
|
||||
: 0.0;
|
||||
const double nLat = lat1 + t*dlat, nLon = lon1 + t*dlon;
|
||||
if (nearLatOut) *nearLatOut = nLat;
|
||||
if (nearLonOut) *nearLonOut = nLon;
|
||||
return haversine(plat, plon, nLat, nLon).first;
|
||||
}
|
||||
|
||||
// Nearest edge of zone polygon → {distance_m, bearing_deg}
|
||||
inline std::pair<double,double> nearestZoneEdge(
|
||||
double plat, double plon,
|
||||
const std::vector<std::pair<double,double>>& pts) {
|
||||
double minDist = std::numeric_limits<double>::max(), minBrg = 0.0;
|
||||
const int n = static_cast<int>(pts.size());
|
||||
for (int i = 0, j = n-1; i < n; j = i++) {
|
||||
double nLat, nLon;
|
||||
const double d = distToSegment(plat, plon,
|
||||
pts[j].first, pts[j].second, pts[i].first, pts[i].second,
|
||||
&nLat, &nLon);
|
||||
if (d < minDist) {
|
||||
minDist = d;
|
||||
minBrg = haversine(plat, plon, nLat, nLon).second;
|
||||
}
|
||||
}
|
||||
return {minDist, minBrg};
|
||||
}
|
||||
|
||||
// ── Lua table helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
// Push a Distance object (value in meters) with __call metatable
|
||||
inline void pushDistance(lua_State *L, double meters) {
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, meters);
|
||||
lua_setfield(L, -2, "value");
|
||||
lua_pushstring(L, "Distance");
|
||||
lua_setfield(L, -2, "_classname");
|
||||
luaL_getmetatable(L, "Wherigo.Distance");
|
||||
if (lua_istable(L, -1))
|
||||
lua_setmetatable(L, -2);
|
||||
else
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Push a Bearing object (value in degrees [0,360)) with __call metatable
|
||||
inline void pushBearing(lua_State *L, double degrees) {
|
||||
lua_newtable(L);
|
||||
lua_pushnumber(L, std::fmod(degrees+360.0, 360.0));
|
||||
lua_setfield(L, -2, "value");
|
||||
lua_pushstring(L, "Bearing");
|
||||
lua_setfield(L, -2, "_classname");
|
||||
luaL_getmetatable(L, "Wherigo.Bearing");
|
||||
if (lua_istable(L, -1))
|
||||
lua_setmetatable(L, -2);
|
||||
else
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Read zone Points array from Lua table at absolute stack index
|
||||
inline std::vector<std::pair<double,double>> readZonePoints(lua_State *L, int zoneIdx) {
|
||||
std::vector<std::pair<double,double>> pts;
|
||||
lua_getfield(L, zoneIdx, "Points");
|
||||
if (!lua_istable(L, -1)) { lua_pop(L, 1); return pts; }
|
||||
const int n = static_cast<int>(lua_objlen(L, -1));
|
||||
pts.reserve(n);
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "latitude"); double lat = lua_tonumber(L,-1); lua_pop(L,1);
|
||||
lua_getfield(L, -1, "longitude"); double lon = lua_tonumber(L,-1); lua_pop(L,1);
|
||||
pts.push_back({lat, lon});
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return pts;
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
@@ -1,25 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/webview.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
struct SimPoint {
|
||||
double lat;
|
||||
double lon;
|
||||
};
|
||||
|
||||
struct ZoneInfo {
|
||||
double lat;
|
||||
double lon;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class MapSimFrame : public wxFrame {
|
||||
public:
|
||||
MapSimFrame(wxWindow* parent, double centerLat = 53.3, double centerLon = 10.39, const std::vector<std::pair<double, double>>& zoneCoords = {});
|
||||
MapSimFrame(wxWindow* parent,
|
||||
double centerLat = 53.3, double centerLon = 10.39,
|
||||
const std::vector<ZoneInfo>& zoneInfos = {});
|
||||
|
||||
void AddSimPoint(double lat, double lon);
|
||||
void StartSimulation();
|
||||
|
||||
private:
|
||||
wxWebView* m_webView;
|
||||
std::vector<SimPoint> m_route;
|
||||
std::vector<std::pair<double, double>> m_zoneCoords;
|
||||
void OnWebViewEvent(wxWebViewEvent& event);
|
||||
void OnPlay(wxCommandEvent& event);
|
||||
wxWebView* m_webView = nullptr;
|
||||
wxTimer m_simTimer;
|
||||
size_t m_simIndex = 0;
|
||||
|
||||
std::vector<SimPoint> m_route;
|
||||
std::vector<ZoneInfo> m_zoneInfos;
|
||||
bool m_pageLoaded = false;
|
||||
|
||||
void SendPositionToEngine(double lat, double lon);
|
||||
|
||||
void syncZonesOnMap();
|
||||
|
||||
void OnScriptMessage(wxWebViewEvent& event);
|
||||
void OnWebViewLoaded(wxWebViewEvent& event);
|
||||
void OnGameStateChanged(wxEvent& event);
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnPlay(wxCommandEvent& event);
|
||||
void OnClear(wxCommandEvent& event);
|
||||
void OnSimTimer(wxTimerEvent& event);
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
int getSelectedButton() const { return m_selectedButton; }
|
||||
|
||||
private:
|
||||
void onButton(wxCommandEvent &event);
|
||||
void onButton(const wxCommandEvent &event);
|
||||
|
||||
int m_selectedButton = -1;
|
||||
};
|
||||
|
||||
+42
-14
@@ -6,6 +6,11 @@
|
||||
#include "lua/wherigo_completion.h"
|
||||
#include "ui/start_screen.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
#endif
|
||||
|
||||
#include <cartridge/parser.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/filename.h>
|
||||
@@ -26,6 +31,19 @@ bool cApp::OnInit()
|
||||
auto *startFrame = new cStartScreen();
|
||||
startFrame->Show(true);
|
||||
|
||||
#ifdef __APPLE__
|
||||
// Force the app to process itself into the foreground (Raise() alone is not enough on macOS)
|
||||
using MsgFn = id(*)(id, SEL);
|
||||
using ActivateFn = void(*)(id, SEL, BOOL);
|
||||
id nsApp = reinterpret_cast<MsgFn>(objc_msgSend)(
|
||||
reinterpret_cast<id>(objc_getClass("NSApplication")),
|
||||
sel_registerName("sharedApplication"));
|
||||
reinterpret_cast<ActivateFn>(objc_msgSend)(
|
||||
nsApp, sel_registerName("activateIgnoringOtherApps:"), YES);
|
||||
#else
|
||||
startFrame->Raise();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -46,7 +64,7 @@ bool cApp::loadCartridge(const std::string &filePath) {
|
||||
}
|
||||
|
||||
bool cApp::initLuaState() {
|
||||
auto luac = m_cartridge->luac();
|
||||
const auto luac = m_cartridge->luac();
|
||||
if (!luac) {
|
||||
wxLogError("No Lua bytecode in cartridge");
|
||||
return false;
|
||||
@@ -68,7 +86,7 @@ bool cApp::initLuaState() {
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
const auto &bytecode = luac->getData();
|
||||
int result = luaL_loadbuffer(m_luaState,
|
||||
const int result = luaL_loadbuffer(m_luaState,
|
||||
reinterpret_cast<const char *>(bytecode.data()),
|
||||
bytecode.size(),
|
||||
"cartridge");
|
||||
@@ -98,8 +116,8 @@ bool cApp::initLuaState() {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1)) {
|
||||
const char *className = lua_tostring(m_luaState, -1);
|
||||
if (strcmp(className, "ZCartridge") == 0) {
|
||||
if (const char *className = lua_tostring(m_luaState, -1);
|
||||
strcmp(className, "ZCartridge") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
m_cartridgeRef = luaL_ref(m_luaState, LUA_REGISTRYINDEX); // store reference, pops table
|
||||
lua_pop(m_luaState, 1); // pop key
|
||||
@@ -115,6 +133,16 @@ bool cApp::initLuaState() {
|
||||
// Initialize media manager
|
||||
wherigo::MediaManager::getInstance().init(m_luaState, m_cartridge.get());
|
||||
|
||||
// Set Player.CompletionCode from the cartridge binary so Lua can access it
|
||||
lua_getglobal(m_luaState, "Player");
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
std::string cc = m_cartridge->completionCode();
|
||||
if (cc.length() > 15) cc = cc.substr(0, 15);
|
||||
lua_pushstring(m_luaState, cc.c_str());
|
||||
lua_setfield(m_luaState, -2, "CompletionCode");
|
||||
}
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (m_cartridgeRef == LUA_NOREF) {
|
||||
wxLogError("No ZCartridge object found!");
|
||||
return false;
|
||||
@@ -126,7 +154,7 @@ bool cApp::initLuaState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void cApp::startGame() {
|
||||
void cApp::startGame() const {
|
||||
if (!m_luaState || m_cartridgeRef == LUA_NOREF) {
|
||||
wxLogError("No cartridge loaded");
|
||||
return;
|
||||
@@ -190,38 +218,38 @@ std::string cApp::getAutoSavePath() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
bool cApp::saveGameState(const std::string &saveFilePath) {
|
||||
bool cApp::saveGameState(const std::string &saveFilePath) const {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state to save");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
const std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
if (savePath.empty()) {
|
||||
wxLogError("No save path available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
|
||||
const bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
|
||||
if (success) {
|
||||
wxLogMessage("Game saved to: %s", savePath);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool cApp::loadGameState(const std::string &saveFilePath) {
|
||||
bool cApp::loadGameState(const std::string &saveFilePath) const {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state to load into");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
const std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
if (loadPath.empty() || !wxFileExists(loadPath)) {
|
||||
wxLogError("Save file not found: %s", loadPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
|
||||
const bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
|
||||
if (success) {
|
||||
wxLogMessage("Game loaded from: %s", loadPath);
|
||||
// Notify game engine of state change
|
||||
@@ -247,19 +275,19 @@ std::string cApp::getCompletionLogPath() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
bool cApp::generateCompletionLog(const std::string &logFilePath) {
|
||||
bool cApp::generateCompletionLog(const std::string &logFilePath) const {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state for completion log");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
|
||||
const std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
|
||||
if (logPath.empty()) {
|
||||
wxLogError("No completion log path available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
|
||||
const bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
|
||||
if (success) {
|
||||
wxLogMessage("Completion log generated: %s", logPath);
|
||||
}
|
||||
|
||||
+241
-62
@@ -1,15 +1,18 @@
|
||||
#include "lua/game_engine.h"
|
||||
#include "lua/geo_utils.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
// Define the custom event
|
||||
wxDEFINE_EVENT(EVT_GAME_STATE_CHANGED, GameStateEvent);
|
||||
|
||||
wxBEGIN_EVENT_TABLE(GameEngine, wxEvtHandler)
|
||||
@@ -21,12 +24,8 @@ GameEngine& GameEngine::getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
GameEngine::GameEngine() : m_gameTimer(this) {
|
||||
}
|
||||
|
||||
GameEngine::~GameEngine() {
|
||||
shutdown();
|
||||
}
|
||||
GameEngine::GameEngine() : m_gameTimer(this) {}
|
||||
GameEngine::~GameEngine() { shutdown(); }
|
||||
|
||||
void GameEngine::init(lua_State *L, int cartridgeRef) {
|
||||
m_luaState = L;
|
||||
@@ -42,15 +41,13 @@ void GameEngine::shutdown() {
|
||||
|
||||
void GameEngine::start() {
|
||||
if (m_running) return;
|
||||
|
||||
m_running = true;
|
||||
m_gameTimer.Start(1000); // 1 second tick
|
||||
m_gameTimer.Start(1000);
|
||||
wxLogDebug("GameEngine started");
|
||||
}
|
||||
|
||||
void GameEngine::stop() {
|
||||
if (!m_running) return;
|
||||
|
||||
m_gameTimer.Stop();
|
||||
m_running = false;
|
||||
wxLogDebug("GameEngine stopped");
|
||||
@@ -67,93 +64,89 @@ void GameEngine::updatePlayerPosition(double lat, double lng, double alt) {
|
||||
lua_getglobal(m_luaState, "Player");
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_newtable(m_luaState);
|
||||
lua_pushnumber(m_luaState, lat);
|
||||
lua_setfield(m_luaState, -2, "latitude");
|
||||
lua_pushnumber(m_luaState, lng);
|
||||
lua_setfield(m_luaState, -2, "longitude");
|
||||
lua_pushnumber(m_luaState, alt);
|
||||
lua_setfield(m_luaState, -2, "altitude");
|
||||
lua_pushnumber(m_luaState, lat); lua_setfield(m_luaState, -2, "latitude");
|
||||
lua_pushnumber(m_luaState, lng); lua_setfield(m_luaState, -2, "longitude");
|
||||
lua_pushnumber(m_luaState, alt); lua_setfield(m_luaState, -2, "altitude");
|
||||
lua_setfield(m_luaState, -2, "ObjectLocation");
|
||||
}
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
checkZones();
|
||||
if (!m_isProcessing) {
|
||||
m_isProcessing = true;
|
||||
checkZones();
|
||||
notifyStateChanged();
|
||||
m_isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GameEngine::onGameTick(wxTimerEvent& event) {
|
||||
if (!m_luaState || !m_running) return;
|
||||
|
||||
void GameEngine::onGameTick(wxTimerEvent&) {
|
||||
if (!m_luaState || !m_running || m_isProcessing) return;
|
||||
m_isProcessing = true;
|
||||
checkTimers();
|
||||
|
||||
// Notify listeners of potential state changes
|
||||
notifyStateChanged();
|
||||
m_isProcessing = false;
|
||||
}
|
||||
|
||||
// ── Timer processing ──────────────────────────────────────────────────────────
|
||||
|
||||
void GameEngine::checkTimers() {
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Iterate through all global variables to find running timers
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
lua_pushnil(m_luaState);
|
||||
|
||||
while (lua_next(m_luaState, -2) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "ZTimer") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
const bool isTimer = lua_isstring(m_luaState, -1) &&
|
||||
!strcmp(lua_tostring(m_luaState, -1), "ZTimer");
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (isTimer) {
|
||||
lua_getfield(m_luaState, -1, "Running");
|
||||
bool running = lua_toboolean(m_luaState, -1);
|
||||
const bool running = lua_toboolean(m_luaState, -1);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (running) {
|
||||
// Get timer info
|
||||
lua_getfield(m_luaState, -1, "Name");
|
||||
const char *name = lua_isstring(m_luaState, -1) ? lua_tostring(m_luaState, -1) : "(unnamed)";
|
||||
const char *name = lua_isstring(m_luaState,-1) ? lua_tostring(m_luaState,-1) : "(unnamed)";
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
lua_getfield(m_luaState, -1, "Remaining");
|
||||
lua_Number remaining = lua_isnumber(m_luaState, -1) ? lua_tonumber(m_luaState, -1) : -1;
|
||||
double remaining = lua_isnumber(m_luaState,-1) ? lua_tonumber(m_luaState,-1) : -1.0;
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
// If Remaining not set, initialize from Duration
|
||||
if (remaining < 0) {
|
||||
lua_getfield(m_luaState, -1, "Duration");
|
||||
remaining = lua_isnumber(m_luaState, -1) ? lua_tonumber(m_luaState, -1) : 0;
|
||||
remaining = lua_isnumber(m_luaState,-1) ? lua_tonumber(m_luaState,-1) : 0.0;
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
|
||||
// Decrement remaining time
|
||||
remaining -= 1.0;
|
||||
lua_pushnumber(m_luaState, remaining);
|
||||
lua_setfield(m_luaState, -2, "Remaining");
|
||||
|
||||
// Call OnTick if exists
|
||||
lua_getfield(m_luaState, -1, "OnTick");
|
||||
if (lua_isfunction(m_luaState, -1)) {
|
||||
lua_pushvalue(m_luaState, -2); // push timer as self
|
||||
lua_pushvalue(m_luaState, -2);
|
||||
if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
|
||||
wxLogError("Timer OnTick error: %s", lua_tostring(m_luaState, -1));
|
||||
wxLogError("Timer OnTick error: %s", lua_tostring(m_luaState,-1));
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
|
||||
// Check if elapsed
|
||||
if (remaining <= 0) {
|
||||
wxLogDebug("Timer elapsed: %s", name);
|
||||
|
||||
// Stop the timer
|
||||
lua_pushboolean(m_luaState, 0);
|
||||
lua_setfield(m_luaState, -2, "Running");
|
||||
|
||||
// Call OnElapsed if exists
|
||||
lua_getfield(m_luaState, -1, "OnElapsed");
|
||||
if (lua_isfunction(m_luaState, -1)) {
|
||||
lua_pushvalue(m_luaState, -2); // push timer as self
|
||||
lua_pushvalue(m_luaState, -2);
|
||||
if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
|
||||
wxLogError("Timer OnElapsed error: %s", lua_tostring(m_luaState, -1));
|
||||
wxLogError("Timer OnElapsed error: %s", lua_tostring(m_luaState,-1));
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
} else {
|
||||
@@ -161,49 +154,236 @@ void GameEngine::checkTimers() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop value
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop _G
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
|
||||
// ── Zone processing ───────────────────────────────────────────────────────────
|
||||
|
||||
// Fire a named callback on a zone (absolute index zoneIdx). Stack-neutral.
|
||||
static void callZoneCallback(lua_State *L, int zoneIdx,
|
||||
const char *cbName, const char *zoneName) {
|
||||
lua_getfield(L, zoneIdx, cbName);
|
||||
if (lua_isfunction(L, -1)) {
|
||||
wxLogDebug("Zone <%s>: START %s", zoneName, cbName);
|
||||
lua_pushvalue(L, zoneIdx);
|
||||
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||
wxLogError("Zone %s error: %s", cbName, lua_tostring(L,-1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
wxLogDebug("Zone <%s>: END__ %s", zoneName, cbName);
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Process one zone table at absolute stack index. Returns true if state changed.
|
||||
static bool updateZone(lua_State *L, int zoneIdx, double playerLat, double playerLng) {
|
||||
// Zone name (for logging)
|
||||
lua_getfield(L, zoneIdx, "Name");
|
||||
const std::string zoneName = lua_isstring(L,-1) ? lua_tostring(L,-1) : "(unnamed)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Need at least 3 points for a polygon
|
||||
const auto pts = readZonePoints(L, zoneIdx);
|
||||
if (pts.size() < 3) return false;
|
||||
|
||||
// Previous state
|
||||
lua_getfield(L, zoneIdx, "_inside");
|
||||
bool wasInside = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, zoneIdx, "_state");
|
||||
const std::string prevState = lua_isstring(L,-1) ? lua_tostring(L,-1) : "NotInRange";
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Proximity / distance range thresholds
|
||||
double proximityRange = 60.0;
|
||||
lua_getfield(L, zoneIdx, "ProximityRange");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "value");
|
||||
if (lua_isnumber(L,-1)) proximityRange = lua_tonumber(L,-1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
double distanceRange = -0.3048;
|
||||
lua_getfield(L, zoneIdx, "DistanceRange");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "value");
|
||||
if (lua_isnumber(L,-1)) distanceRange = lua_tonumber(L,-1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Current position in zone?
|
||||
bool inside = pointInPolygon(playerLat, playerLng, pts);
|
||||
|
||||
// Distance / bearing to zone boundary
|
||||
double currentDist = 0.0, currentBrg = 0.0;
|
||||
if (!inside) {
|
||||
const auto [d, b] = nearestZoneEdge(playerLat, playerLng, pts);
|
||||
currentDist = d;
|
||||
currentBrg = b;
|
||||
}
|
||||
|
||||
// Update zone fields
|
||||
pushDistance(L, inside ? 0.0 : currentDist);
|
||||
lua_setfield(L, zoneIdx, "CurrentDistance");
|
||||
pushBearing(L, inside ? 0.0 : currentBrg);
|
||||
lua_setfield(L, zoneIdx, "CurrentBearing");
|
||||
|
||||
constexpr double COMFORT = 7.0; // meters hysteresis buffer
|
||||
bool stateChanged = false;
|
||||
|
||||
// ── Handle enter / exit transitions ────────────────────────────────────────
|
||||
if (inside != wasInside) {
|
||||
if (inside) {
|
||||
// Entering the zone
|
||||
if (prevState == "NotInRange")
|
||||
callZoneCallback(L, zoneIdx, "OnDistant", zoneName.c_str());
|
||||
if (prevState != "Proximity" && prevState != "Inside")
|
||||
callZoneCallback(L, zoneIdx, "OnProximity", zoneName.c_str());
|
||||
callZoneCallback(L, zoneIdx, "OnEnter", zoneName.c_str());
|
||||
stateChanged = true;
|
||||
} else if (currentDist > COMFORT) {
|
||||
// Confirmed exit (beyond hysteresis buffer)
|
||||
callZoneCallback(L, zoneIdx, "OnExit", zoneName.c_str());
|
||||
stateChanged = true;
|
||||
} else {
|
||||
// Within comfort zone – stay inside
|
||||
inside = true;
|
||||
}
|
||||
lua_pushboolean(L, inside ? 1 : 0);
|
||||
lua_setfield(L, zoneIdx, "_inside");
|
||||
}
|
||||
|
||||
// ── Determine new state string ──────────────────────────────────────────────
|
||||
std::string newState;
|
||||
if (inside) {
|
||||
newState = "Inside";
|
||||
} else if (currentDist < (proximityRange + COMFORT)) {
|
||||
newState = "Proximity";
|
||||
} else if (distanceRange < 0.0 || currentDist < (distanceRange + COMFORT)) {
|
||||
newState = "Distant";
|
||||
} else {
|
||||
newState = "NotInRange";
|
||||
}
|
||||
|
||||
// Fire state-change callbacks for non-inside transitions
|
||||
if (!inside && newState != prevState) {
|
||||
stateChanged = true;
|
||||
if (prevState == "Inside") {
|
||||
callZoneCallback(L, zoneIdx, "OnProximity", zoneName.c_str());
|
||||
if (newState == "NotInRange")
|
||||
callZoneCallback(L, zoneIdx, "OnDistant", zoneName.c_str());
|
||||
} else if (prevState == "Proximity" && newState == "NotInRange") {
|
||||
callZoneCallback(L, zoneIdx, "OnDistant", zoneName.c_str());
|
||||
} else if (prevState == "NotInRange" && newState == "Proximity") {
|
||||
callZoneCallback(L, zoneIdx, "OnDistant", zoneName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushstring(L, newState.c_str()); lua_setfield(L, zoneIdx, "State");
|
||||
lua_pushstring(L, newState.c_str()); lua_setfield(L, zoneIdx, "_state");
|
||||
|
||||
return stateChanged;
|
||||
}
|
||||
|
||||
void GameEngine::checkZones() {
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Iterate through all global variables to find zones
|
||||
// Collect all active zone references first (to avoid lua_next invalidation)
|
||||
std::vector<int> zoneRefs;
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
lua_pushnil(m_luaState);
|
||||
|
||||
while (lua_next(m_luaState, -2) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "Zone") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
|
||||
const bool isZone = lua_isstring(m_luaState,-1) &&
|
||||
!strcmp(lua_tostring(m_luaState,-1), "Zone");
|
||||
lua_pop(m_luaState, 1);
|
||||
if (isZone) {
|
||||
lua_getfield(m_luaState, -1, "Active");
|
||||
bool active = lua_toboolean(m_luaState, -1);
|
||||
const bool active = lua_toboolean(m_luaState, -1);
|
||||
lua_pop(m_luaState, 1);
|
||||
if (active) {
|
||||
lua_pushvalue(m_luaState, -1);
|
||||
zoneRefs.push_back(luaL_ref(m_luaState, LUA_REGISTRYINDEX));
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
// Process each zone
|
||||
bool anyChanged = false;
|
||||
for (const int ref : zoneRefs) {
|
||||
lua_rawgeti(m_luaState, LUA_REGISTRYINDEX, ref);
|
||||
if (updateZone(m_luaState, lua_gettop(m_luaState), m_playerLat, m_playerLng))
|
||||
anyChanged = true;
|
||||
lua_pop(m_luaState, 1);
|
||||
luaL_unref(m_luaState, LUA_REGISTRYINDEX, ref);
|
||||
}
|
||||
|
||||
if (anyChanged) {
|
||||
rebuildPlayerInventory();
|
||||
notifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void GameEngine::rebuildPlayerInventory() {
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Player table
|
||||
lua_getglobal(m_luaState, "Player");
|
||||
if (!lua_istable(m_luaState, -1)) { lua_pop(m_luaState, 1); return; }
|
||||
const int playerIdx = lua_gettop(m_luaState);
|
||||
|
||||
// New inventory array (only visible items with Container == Player)
|
||||
lua_newtable(m_luaState);
|
||||
const int invIdx = lua_gettop(m_luaState);
|
||||
int count = 0;
|
||||
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
const int gIdx = lua_gettop(m_luaState);
|
||||
lua_pushnil(m_luaState);
|
||||
|
||||
while (lua_next(m_luaState, gIdx) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
const bool isItem = lua_isstring(m_luaState, -1) &&
|
||||
!strcmp(lua_tostring(m_luaState, -1), "ZItem");
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (isItem) {
|
||||
lua_getfield(m_luaState, -1, "Container");
|
||||
const bool inInventory = lua_rawequal(m_luaState, -1, playerIdx);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (active) {
|
||||
// TODO: Check if player is inside zone using Points
|
||||
// For now, just log that we would check
|
||||
lua_getfield(m_luaState, -1, "Name");
|
||||
const char *name = lua_isstring(m_luaState, -1) ? lua_tostring(m_luaState, -1) : "(unnamed)";
|
||||
if (inInventory) {
|
||||
lua_getfield(m_luaState, -1, "Visible");
|
||||
const bool visible = lua_toboolean(m_luaState, -1);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
// This is where you would implement point-in-polygon check
|
||||
// and call OnEnter/OnExit callbacks
|
||||
if (visible) {
|
||||
lua_pushvalue(m_luaState, -1);
|
||||
lua_rawseti(m_luaState, invIdx, ++count);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop value
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop _G
|
||||
|
||||
lua_pushvalue(m_luaState, invIdx);
|
||||
lua_setfield(m_luaState, playerIdx, "Inventory");
|
||||
lua_pop(m_luaState, 2); // pop inventory + Player
|
||||
}
|
||||
|
||||
void GameEngine::notifyStateChanged() {
|
||||
@@ -212,4 +392,3 @@ void GameEngine::notifyStateChanged() {
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ void MediaManager::buildMediaIndex(lua_State *L) {
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MediaManager::getMediaByName(const std::string &name) {
|
||||
auto it = m_nameToIndex.find(name);
|
||||
const auto it = m_nameToIndex.find(name);
|
||||
if (it == m_nameToIndex.end()) {
|
||||
wxLogWarning("Media not found: %s", name.c_str());
|
||||
return {};
|
||||
@@ -84,7 +84,7 @@ std::vector<uint8_t> MediaManager::getMediaByIndex(int index) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto media = m_cartridge->getMedia(index);
|
||||
const auto media = m_cartridge->getMedia(index);
|
||||
if (!media) {
|
||||
wxLogWarning("Media index %d not found in cartridge", index);
|
||||
return {};
|
||||
|
||||
+718
-503
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,7 @@ int zobject_MoveTo(lua_State *L) {
|
||||
|
||||
wxLogDebug("MoveTo: %s -> %s", srcName, dstName);
|
||||
|
||||
// Notify game state change
|
||||
GameEngine::getInstance().rebuildPlayerInventory();
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
|
||||
return 0;
|
||||
|
||||
+100
-89
@@ -37,7 +37,7 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
||||
auto *menuBar = new wxMenuBar;
|
||||
menuBar->Append(menuFile, "&Datei");
|
||||
menuBar->Append(menuHelp, "&Hilfe");
|
||||
SetMenuBar(menuBar);
|
||||
wxFrameBase::SetMenuBar(menuBar);
|
||||
|
||||
// Main sizer
|
||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -45,15 +45,6 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
||||
// Notebook with tabs
|
||||
m_notebook = new wxNotebook(this, wxID_ANY);
|
||||
|
||||
// Locations/Zones tab
|
||||
auto *zonePanel = new wxPanel(m_notebook);
|
||||
auto *zoneSizer = new wxBoxSizer(wxVERTICAL);
|
||||
zoneSizer->Add(new wxStaticText(zonePanel, wxID_ANY, "Orte in der Nähe:"), 0, wxALL, 5);
|
||||
m_zoneList = new wxListBox(zonePanel, wxID_ANY);
|
||||
zoneSizer->Add(m_zoneList, 1, wxALL | wxEXPAND, 5);
|
||||
zonePanel->SetSizer(zoneSizer);
|
||||
m_notebook->AddPage(zonePanel, "Orte");
|
||||
|
||||
// Tasks tab
|
||||
auto *taskPanel = new wxPanel(m_notebook);
|
||||
auto *taskSizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -63,6 +54,15 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
||||
taskPanel->SetSizer(taskSizer);
|
||||
m_notebook->AddPage(taskPanel, "Aufgaben");
|
||||
|
||||
// Locations/Zones tab
|
||||
auto *zonePanel = new wxPanel(m_notebook);
|
||||
auto *zoneSizer = new wxBoxSizer(wxVERTICAL);
|
||||
zoneSizer->Add(new wxStaticText(zonePanel, wxID_ANY, "Orte in der Nähe:"), 0, wxALL, 5);
|
||||
m_zoneList = new wxListBox(zonePanel, wxID_ANY);
|
||||
zoneSizer->Add(m_zoneList, 1, wxALL | wxEXPAND, 5);
|
||||
zonePanel->SetSizer(zoneSizer);
|
||||
m_notebook->AddPage(zonePanel, "Orte");
|
||||
|
||||
// Inventory tab
|
||||
auto *inventoryPanel = new wxPanel(m_notebook);
|
||||
auto *inventorySizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -97,8 +97,8 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
||||
CentreOnScreen();
|
||||
|
||||
// Status bar
|
||||
CreateStatusBar();
|
||||
SetStatusText("Spiel läuft");
|
||||
wxFrameBase::CreateStatusBar();
|
||||
wxFrameBase::SetStatusText("Spiel läuft");
|
||||
|
||||
// Event bindings
|
||||
Bind(wxEVT_CLOSE_WINDOW, &cGameScreen::OnClose, this);
|
||||
@@ -158,7 +158,7 @@ void cGameScreen::OnSaveGame(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::OnLoadGame(wxCommandEvent& event) {
|
||||
int result = wxMessageBox("Möchten Sie den gespeicherten Spielstand laden?\n\nAchtung: Der aktuelle Fortschritt geht verloren!",
|
||||
const int result = wxMessageBox("Möchten Sie den gespeicherten Spielstand laden?\n\nAchtung: Der aktuelle Fortschritt geht verloren!",
|
||||
"Laden", wxYES_NO | wxICON_QUESTION);
|
||||
|
||||
if (result == wxYES) {
|
||||
@@ -185,9 +185,8 @@ void cGameScreen::OnExportCompletion(wxCommandEvent& event) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxString filePath = saveDialog.GetPath();
|
||||
|
||||
if (wxGetApp().generateCompletionLog(filePath.ToStdString())) {
|
||||
if (const wxString filePath = saveDialog.GetPath();
|
||||
wxGetApp().generateCompletionLog(filePath.ToStdString())) {
|
||||
wxMessageBox("Completion Log wurde erfolgreich exportiert!\n\nDiese Datei kann auf wherigo.com hochgeladen werden, um den Abschluss nachzuweisen.",
|
||||
"Export erfolgreich", wxOK | wxICON_INFORMATION);
|
||||
} else {
|
||||
@@ -201,6 +200,9 @@ void cGameScreen::OnGameStateChanged(wxEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::refreshUI() {
|
||||
if (wxGetApp().isCartridgeLoaded())
|
||||
SetTitle(wxString::FromUTF8(wxGetApp().getCartridge()->cartridgeName()));
|
||||
|
||||
// Freeze to prevent flickering and focus loss
|
||||
Freeze();
|
||||
|
||||
@@ -214,8 +216,8 @@ void cGameScreen::refreshUI() {
|
||||
}
|
||||
|
||||
void cGameScreen::populateZones() {
|
||||
int selection = m_zoneList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_zoneList->GetString(selection) : "";
|
||||
const int selection = m_zoneList->GetSelection();
|
||||
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_zoneList->GetString(selection) : "";
|
||||
|
||||
m_zoneList->Clear();
|
||||
|
||||
@@ -232,11 +234,11 @@ void cGameScreen::populateZones() {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
const bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
const bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && visible) {
|
||||
@@ -256,16 +258,15 @@ void cGameScreen::populateZones() {
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_zoneList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
if (const int idx = m_zoneList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||
m_zoneList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateTasks() {
|
||||
int selection = m_taskList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_taskList->GetString(selection) : "";
|
||||
const int selection = m_taskList->GetSelection();
|
||||
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_taskList->GetString(selection) : "";
|
||||
|
||||
m_taskList->Clear();
|
||||
|
||||
@@ -286,7 +287,7 @@ void cGameScreen::populateTasks() {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Complete");
|
||||
bool complete = lua_toboolean(L, -1);
|
||||
const bool complete = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && !complete) {
|
||||
@@ -306,16 +307,15 @@ void cGameScreen::populateTasks() {
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_taskList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
if (const int idx = m_taskList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||
m_taskList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateInventory() {
|
||||
int selection = m_inventoryList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_inventoryList->GetString(selection) : "";
|
||||
const int selection = m_inventoryList->GetSelection();
|
||||
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_inventoryList->GetString(selection) : "";
|
||||
|
||||
m_inventoryList->Clear();
|
||||
|
||||
@@ -343,15 +343,21 @@ void cGameScreen::populateInventory() {
|
||||
if (lua_istable(L, -1)) {
|
||||
// Check if Container is Player
|
||||
lua_getglobal(L, "Player");
|
||||
bool isInInventory = lua_rawequal(L, -1, -2);
|
||||
const bool isInInventory = lua_rawequal(L, -1, -2);
|
||||
lua_pop(L, 2); // pop Player and Container
|
||||
|
||||
if (isInInventory) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
m_inventoryList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
||||
}
|
||||
lua_getfield(L, -1, "Visible");
|
||||
const bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (visible) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
m_inventoryList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
@@ -366,16 +372,16 @@ void cGameScreen::populateInventory() {
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_inventoryList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
if (const int idx = m_inventoryList->FindString(selectedItem);
|
||||
idx != wxNOT_FOUND) {
|
||||
m_inventoryList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateCharacters() {
|
||||
int selection = m_characterList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_characterList->GetString(selection) : "";
|
||||
const int selection = m_characterList->GetSelection();
|
||||
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_characterList->GetString(selection) : "";
|
||||
|
||||
m_characterList->Clear();
|
||||
|
||||
@@ -392,16 +398,16 @@ void cGameScreen::populateCharacters() {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
const bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
const bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Check if character has a container (is somewhere)
|
||||
lua_getfield(L, -1, "Container");
|
||||
bool hasLocation = lua_istable(L, -1);
|
||||
const bool hasLocation = lua_istable(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && visible && hasLocation) {
|
||||
@@ -435,16 +441,16 @@ void cGameScreen::populateCharacters() {
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_characterList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
if (const int idx = m_characterList->FindString(selectedItem);
|
||||
idx != wxNOT_FOUND) {
|
||||
m_characterList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateItems() {
|
||||
int selection = m_itemList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_itemList->GetString(selection) : "";
|
||||
const int selection = m_itemList->GetSelection();
|
||||
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_itemList->GetString(selection) : "";
|
||||
|
||||
m_itemList->Clear();
|
||||
|
||||
@@ -461,11 +467,11 @@ void cGameScreen::populateItems() {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
const bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
const bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Check if item is NOT in player inventory (in a zone)
|
||||
@@ -512,8 +518,7 @@ void cGameScreen::populateItems() {
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_itemList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
if (const int idx = m_itemList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||
m_itemList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
@@ -658,10 +663,10 @@ void cGameScreen::OnTaskSelected(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
||||
int sel = m_inventoryList->GetSelection();
|
||||
const int sel = m_inventoryList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
wxString itemName = m_inventoryList->GetString(sel);
|
||||
const wxString itemName = m_inventoryList->GetString(sel);
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
@@ -712,12 +717,12 @@ void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
||||
int sel = m_characterList->GetSelection();
|
||||
const int sel = m_characterList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
// Extract name from "Name (Location)" format
|
||||
wxString fullText = m_characterList->GetString(sel);
|
||||
wxString charName = fullText.BeforeFirst('(').Trim();
|
||||
const wxString fullText = m_characterList->GetString(sel);
|
||||
const wxString charName = fullText.BeforeFirst('(').Trim();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
@@ -734,13 +739,13 @@ void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
const wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == charName) {
|
||||
// Get character description
|
||||
lua_getfield(L, -1, "Description");
|
||||
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
|
||||
const wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get media if exists
|
||||
@@ -789,12 +794,12 @@ void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
int sel = m_itemList->GetSelection();
|
||||
const int sel = m_itemList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
// Extract name from "Name (Location)" format
|
||||
wxString fullText = m_itemList->GetString(sel);
|
||||
wxString itemName = fullText.BeforeFirst('(').Trim();
|
||||
const wxString fullText = m_itemList->GetString(sel);
|
||||
const wxString itemName = fullText.BeforeFirst('(').Trim();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
@@ -811,13 +816,13 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
const wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == itemName) {
|
||||
// Get item description
|
||||
lua_getfield(L, -1, "Description");
|
||||
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
|
||||
const wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get media if exists
|
||||
@@ -836,7 +841,7 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
lua_getfield(L, -1, "Commands");
|
||||
wxArrayString commands;
|
||||
if (lua_istable(L, -1)) {
|
||||
int n = lua_objlen(L, -1);
|
||||
const auto n = lua_objlen(L, -1);
|
||||
for (int i = 1; i <= n; i++) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (lua_istable(L, -1)) {
|
||||
@@ -860,7 +865,7 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
// Use WherigoMessageDialog to show with media support
|
||||
std::vector<wxString> buttons = {"OK"};
|
||||
const std::vector<wxString> buttons = {"OK"};
|
||||
wherigo::WherigoMessageDialog dlg(this, message, itemName, buttons, mediaName);
|
||||
dlg.ShowModal();
|
||||
|
||||
@@ -893,8 +898,9 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
}
|
||||
|
||||
void cGameScreen::OnMapSim(wxCommandEvent&) {
|
||||
std::vector<std::pair<double, double>> zoneCoords;
|
||||
std::vector<ZoneInfo> zoneInfos;
|
||||
double lat = 53.3, lon = 10.39;
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (L) {
|
||||
lua_getglobal(L, "_G");
|
||||
@@ -903,41 +909,46 @@ void cGameScreen::OnMapSim(wxCommandEvent&) {
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "ClassName");
|
||||
if (lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "Zone") == 0) {
|
||||
lua_pop(L, 1);
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1) || true;
|
||||
const bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1) || true;
|
||||
if (!active) { lua_pop(L, 1); continue; }
|
||||
|
||||
// Read name
|
||||
lua_getfield(L, -1, "Name");
|
||||
std::string zoneName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
|
||||
lua_pop(L, 1);
|
||||
if (active && visible) {
|
||||
lua_getfield(L, -1, "OriginalPoint");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "latitude");
|
||||
lua_getfield(L, -2, "longitude");
|
||||
if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
|
||||
double zlat = lua_tonumber(L, -2);
|
||||
double zlon = lua_tonumber(L, -1);
|
||||
zoneCoords.emplace_back(zlat, zlon);
|
||||
}
|
||||
lua_pop(L, 2);
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Read OriginalPoint (set by Wherigo Builder on each zone)
|
||||
lua_getfield(L, -1, "OriginalPoint");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "latitude");
|
||||
lua_getfield(L, -2, "longitude");
|
||||
if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
|
||||
zoneInfos.push_back({
|
||||
lua_tonumber(L, -2),
|
||||
lua_tonumber(L, -1),
|
||||
zoneName
|
||||
});
|
||||
}
|
||||
lua_pop(L, 1); // pop OriginalPoint
|
||||
lua_pop(L, 2); // pop lat, lon
|
||||
}
|
||||
lua_pop(L, 1); // pop OriginalPoint (table or nil)
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
if (!zoneCoords.empty()) {
|
||||
lat = zoneCoords[0].first;
|
||||
lon = zoneCoords[0].second;
|
||||
|
||||
if (!zoneInfos.empty()) {
|
||||
lat = zoneInfos[0].lat;
|
||||
lon = zoneInfos[0].lon;
|
||||
}
|
||||
auto *mapSim = new MapSimFrame(this, lat, lon, zoneCoords);
|
||||
auto *mapSim = new MapSimFrame(this, lat, lon, zoneInfos);
|
||||
mapSim->Show();
|
||||
}
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
@@ -4,20 +4,17 @@
|
||||
#include "ui/game_screen.h"
|
||||
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/mstream.h>
|
||||
|
||||
wxDECLARE_APP(cApp);
|
||||
|
||||
enum {
|
||||
ID_OpenCartridge = 1001,
|
||||
ID_StartGame = 1002
|
||||
};
|
||||
enum { ID_OpenCartridge = 1001, ID_StartGame = 1002 };
|
||||
|
||||
cStartScreen::cStartScreen()
|
||||
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 800)),
|
||||
m_gameFrame(nullptr),
|
||||
m_cartridgeLoaded(false) {
|
||||
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition,
|
||||
wxSize(800, 800)),
|
||||
m_gameFrame(nullptr), m_cartridgeLoaded(false) {
|
||||
|
||||
// Menu
|
||||
auto *menuFile = new wxMenu;
|
||||
@@ -31,7 +28,7 @@ cStartScreen::cStartScreen()
|
||||
auto *menuBar = new wxMenuBar;
|
||||
menuBar->Append(menuFile, "&Datei");
|
||||
menuBar->Append(menuHelp, "&Hilfe");
|
||||
SetMenuBar(menuBar);
|
||||
wxFrameBase::SetMenuBar(menuBar);
|
||||
|
||||
// Main sizer
|
||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -64,7 +61,8 @@ cStartScreen::cStartScreen()
|
||||
infoSizer->Add(m_cartridgeAuthor, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
|
||||
// Description
|
||||
m_cartridgeDesc = new wxHtmlWindow(m_infoPanel, wxID_ANY, wxDefaultPosition, wxSize(500, 200));
|
||||
m_cartridgeDesc = new wxHtmlWindow(m_infoPanel, wxID_ANY, wxDefaultPosition,
|
||||
wxSize(500, 200));
|
||||
m_cartridgeDesc->SetMinSize(wxSize(500, 200));
|
||||
infoSizer->Add(m_cartridgeDesc, 1, wxALL | wxEXPAND, 10);
|
||||
|
||||
@@ -82,8 +80,8 @@ cStartScreen::cStartScreen()
|
||||
SetSizer(mainSizer);
|
||||
|
||||
// Status bar
|
||||
CreateStatusBar();
|
||||
SetStatusText("Bitte eine Cartridge öffnen");
|
||||
wxFrameBase::CreateStatusBar();
|
||||
wxFrameBase::SetStatusText("Bitte eine Cartridge öffnen");
|
||||
|
||||
// Event bindings
|
||||
Bind(wxEVT_MENU, &cStartScreen::OnOpenCartridge, this, ID_OpenCartridge);
|
||||
@@ -95,7 +93,7 @@ cStartScreen::cStartScreen()
|
||||
CentreOnScreen();
|
||||
}
|
||||
|
||||
void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
||||
void cStartScreen::OnOpenCartridge(wxCommandEvent &event) {
|
||||
wxFileDialog openFileDialog(this, "Cartridge öffnen", "", "",
|
||||
"Wherigo Cartridge (*.gwc)|*.gwc",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
@@ -104,15 +102,19 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxString filePath = openFileDialog.GetPath();
|
||||
SetStatusText("Lade Cartridge: " + filePath);
|
||||
const wxString filePath = openFileDialog.GetPath();
|
||||
wxFrameBase::SetStatusText("Lade Cartridge: " + filePath);
|
||||
|
||||
// Vor dem Laden: Info-Panel zurücksetzen
|
||||
if (m_infoPanel) {
|
||||
if (m_cartridgeName) m_cartridgeName->SetLabel("");
|
||||
if (m_cartridgeAuthor) m_cartridgeAuthor->SetLabel("");
|
||||
if (m_cartridgeDesc) m_cartridgeDesc->SetPage("");
|
||||
if (m_splashImage) m_splashImage->SetBitmap(wxNullBitmap);
|
||||
if (m_cartridgeName)
|
||||
m_cartridgeName->SetLabel("");
|
||||
if (m_cartridgeAuthor)
|
||||
m_cartridgeAuthor->SetLabel("");
|
||||
if (m_cartridgeDesc)
|
||||
m_cartridgeDesc->SetPage("");
|
||||
if (m_splashImage)
|
||||
m_splashImage->SetBitmap(wxNullBitmap);
|
||||
}
|
||||
|
||||
// ggf. alten GameScreen schließen
|
||||
@@ -125,16 +127,18 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
||||
if (wxGetApp().loadCartridge(filePath.ToStdString())) {
|
||||
m_cartridgeLoaded = true;
|
||||
showCartridgeInfo();
|
||||
SetStatusText("Cartridge geladen - bereit zum Starten");
|
||||
wxFrameBase::SetStatusText("Cartridge geladen - bereit zum Starten");
|
||||
} else {
|
||||
wxMessageBox("Fehler beim Laden der Cartridge", "Fehler", wxOK | wxICON_ERROR);
|
||||
SetStatusText("Fehler beim Laden");
|
||||
wxMessageBox("Fehler beim Laden der Cartridge", "Fehler",
|
||||
wxOK | wxICON_ERROR);
|
||||
wxFrameBase::SetStatusText("Fehler beim Laden");
|
||||
}
|
||||
}
|
||||
|
||||
void cStartScreen::showCartridgeInfo() {
|
||||
auto *cartridge = wxGetApp().getCartridge();
|
||||
if (!cartridge) return;
|
||||
if (!cartridge)
|
||||
return;
|
||||
|
||||
// Set cartridge info
|
||||
m_cartridgeName->SetLabel(wxString::FromUTF8(cartridge->cartridgeName()));
|
||||
@@ -149,21 +153,21 @@ void cStartScreen::showCartridgeInfo() {
|
||||
}
|
||||
|
||||
// Load splash screen
|
||||
auto splash = cartridge->splashScreen();
|
||||
if (splash) {
|
||||
auto data = splash->getData();
|
||||
if (const auto splash = cartridge->splashScreen()) {
|
||||
const auto data = splash->getData();
|
||||
if (!data.empty()) {
|
||||
wxMemoryInputStream stream(data.data(), data.size());
|
||||
wxImage image(stream);
|
||||
if (image.IsOk()) {
|
||||
if (wxImage image(stream); image.IsOk()) {
|
||||
// Scale if too large
|
||||
int maxWidth = 300;
|
||||
int maxHeight = 200;
|
||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
double scaleX = (double)maxWidth / image.GetWidth();
|
||||
double scaleY = (double)maxHeight / image.GetHeight();
|
||||
const int maxHeight = 200;
|
||||
if (int maxWidth = 300;
|
||||
image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
double scaleX = static_cast<double>(maxWidth) / image.GetWidth();
|
||||
double scaleY = static_cast<double>(maxHeight) / image.GetHeight();
|
||||
double scale = std::min(scaleX, scaleY);
|
||||
image.Rescale(image.GetWidth() * scale, image.GetHeight() * scale, wxIMAGE_QUALITY_HIGH);
|
||||
image.Rescale(static_cast<int>(image.GetWidth() * scale),
|
||||
static_cast<int>(image.GetHeight() * scale),
|
||||
wxIMAGE_QUALITY_HIGH);
|
||||
}
|
||||
m_splashImage->SetBitmap(wxBitmap(image));
|
||||
}
|
||||
@@ -176,15 +180,18 @@ void cStartScreen::showCartridgeInfo() {
|
||||
Layout();
|
||||
}
|
||||
|
||||
void cStartScreen::OnStartGame(wxCommandEvent& event) {
|
||||
void cStartScreen::OnStartGame(wxCommandEvent &event) {
|
||||
if (!m_cartridgeLoaded) {
|
||||
wxMessageBox("Bitte zuerst eine Cartridge laden", "Hinweis", wxOK | wxICON_INFORMATION);
|
||||
wxMessageBox("Bitte zuerst eine Cartridge laden", "Hinweis",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create game frame if not exists
|
||||
if (!m_gameFrame) {
|
||||
m_gameFrame = new cGameScreen(this);
|
||||
m_gameFrame->SetPosition(GetPosition());
|
||||
m_gameFrame->SetSize(GetSize());
|
||||
}
|
||||
|
||||
// Hide start frame, show game
|
||||
@@ -198,10 +205,10 @@ void cStartScreen::OnStartGame(wxCommandEvent& event) {
|
||||
void cStartScreen::onGameClosed() {
|
||||
// Called when game frame is closed
|
||||
Show();
|
||||
SetStatusText("Spiel pausiert - Cartridge noch geladen");
|
||||
wxFrameBase::SetStatusText("Spiel pausiert - Cartridge noch geladen");
|
||||
}
|
||||
|
||||
void cStartScreen::OnExit(wxCommandEvent& event) {
|
||||
void cStartScreen::OnExit(wxCommandEvent &event) {
|
||||
// Clean up game frame
|
||||
if (m_gameFrame) {
|
||||
m_gameFrame->Destroy();
|
||||
@@ -210,7 +217,8 @@ void cStartScreen::OnExit(wxCommandEvent& event) {
|
||||
Close(true);
|
||||
}
|
||||
|
||||
void cStartScreen::OnAbout(wxCommandEvent& event) {
|
||||
wxMessageBox("Wherigo Player\n\nEin Desktop-Player für Wherigo-Cartridges\n\nVersion 0.1",
|
||||
void cStartScreen::OnAbout(wxCommandEvent &event) {
|
||||
wxMessageBox("Wherigo Player\n\nEin Desktop-Player für "
|
||||
"Wherigo-Cartridges\n\nVersion 0.1",
|
||||
"Über Wherigo Player", wxOK | wxICON_INFORMATION);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
#include "ui/wherigo_dialog.h"
|
||||
#include "lua/media_manager.h"
|
||||
#include "lua/game_engine.h"
|
||||
#include "lua/media_manager.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &text,
|
||||
WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent,
|
||||
const wxString &text,
|
||||
const wxString &title,
|
||||
const std::vector<wxString> &buttons,
|
||||
const wxString &mediaName)
|
||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ) {
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
|
||||
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Media/Image (if provided)
|
||||
if (!mediaName.IsEmpty()) {
|
||||
auto mediaData = MediaManager::getInstance().getMediaByName(mediaName.ToStdString());
|
||||
const auto mediaData =
|
||||
MediaManager::getInstance().getMediaByName(mediaName.ToStdString());
|
||||
|
||||
if (!mediaData.empty()) {
|
||||
wxMemoryInputStream stream(mediaData.data(), mediaData.size());
|
||||
wxImage image(stream);
|
||||
|
||||
if (image.IsOk()) {
|
||||
if (wxImage image(stream); image.IsOk()) {
|
||||
// Get screen DPI for scaling
|
||||
wxWindow* topWindow = wxTheApp->GetTopWindow();
|
||||
const wxWindow *topWindow = wxTheApp->GetTopWindow();
|
||||
double contentScaleFactor = 1.0;
|
||||
if (topWindow) {
|
||||
contentScaleFactor = topWindow->GetContentScaleFactor();
|
||||
@@ -40,14 +41,18 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
||||
|
||||
// Scale image - doubled size from original (800x600 instead of 400x300)
|
||||
// Adjust for DPI/Retina displays
|
||||
int maxWidth = static_cast<int>(800 * contentScaleFactor);
|
||||
int maxHeight = static_cast<int>(600 * contentScaleFactor);
|
||||
const int maxWidth = static_cast<int>(800 * contentScaleFactor);
|
||||
|
||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
double scaleX = (double)maxWidth / image.GetWidth();
|
||||
double scaleY = (double)maxHeight / image.GetHeight();
|
||||
double scale = std::min(scaleX, scaleY);
|
||||
image.Rescale(image.GetWidth() * scale, image.GetHeight() * scale, wxIMAGE_QUALITY_HIGH);
|
||||
if (const int maxHeight = static_cast<int>(600 * contentScaleFactor);
|
||||
image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
const double scaleX =
|
||||
static_cast<double>(maxWidth) / image.GetWidth();
|
||||
const double scaleY =
|
||||
static_cast<double>(maxHeight) / image.GetHeight();
|
||||
const double scale = std::min(scaleX, scaleY);
|
||||
image.Rescale(static_cast<int>(image.GetWidth() * scale),
|
||||
static_cast<int>(image.GetHeight() * scale),
|
||||
wxIMAGE_QUALITY_HIGH);
|
||||
}
|
||||
|
||||
wxBitmap bitmap(image);
|
||||
@@ -55,22 +60,22 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
||||
sizer->Add(imageCtrl, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
} else {
|
||||
wxLogDebug("Failed to load image: %s", mediaName);
|
||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format("[Media: %s]", mediaName));
|
||||
auto *mediaLabel = new wxStaticText(
|
||||
this, wxID_ANY, wxString::Format("[Media: %s]", mediaName));
|
||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
}
|
||||
} else {
|
||||
wxLogDebug("No media data for: %s", mediaName);
|
||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format("[Media: %s]", mediaName));
|
||||
auto *mediaLabel = new wxStaticText(
|
||||
this, wxID_ANY, wxString::Format("[Media: %s]", mediaName));
|
||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper lambda to convert plain text to HTML
|
||||
auto textToHtml = [](const wxString& plainText) -> wxString {
|
||||
auto textToHtml = [](const wxString &plainText) -> wxString {
|
||||
wxString result = plainText;
|
||||
// Check if this looks like HTML (has tags)
|
||||
if (plainText.Find("<") == wxNOT_FOUND) {
|
||||
@@ -104,29 +109,34 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
||||
wxColour fgColor = GetForegroundColour();
|
||||
|
||||
// Helper lambda to create and configure HTML window with auto-sizing
|
||||
auto createHtmlWindow = [this, &sizer, &textToHtml, bgColor, fgColor](const wxString& text) {
|
||||
auto createHtmlWindow = [this, &sizer, &textToHtml, bgColor,
|
||||
fgColor](const wxString &message) {
|
||||
auto *htmlCtrl = new wxHtmlWindow(this, wxID_ANY);
|
||||
htmlCtrl->SetBorders(0);
|
||||
|
||||
wxString htmlContent = wxString::Format(
|
||||
"<html><body style=\"margin:0; padding:5px; background-color:#%02x%02x%02x; color:#%02x%02x%02x; font-family:sans-serif; font-size:11pt; line-height:1.5; user-select:none; -webkit-user-select:none; -moz-user-select:none;\">%s</body></html>",
|
||||
bgColor.Red(), bgColor.Green(), bgColor.Blue(),
|
||||
fgColor.Red(), fgColor.Green(), fgColor.Blue(),
|
||||
textToHtml(text)
|
||||
);
|
||||
const wxString htmlContent = wxString::Format(
|
||||
"<html><body style=\"margin:0; padding:5px; "
|
||||
"background-color:#%02x%02x%02x; color:#%02x%02x%02x; "
|
||||
"font-family:sans-serif; font-size:11pt; line-height:1.5; "
|
||||
"user-select:none; -webkit-user-select:none; "
|
||||
"-moz-user-select:none;\">%s</body></html>",
|
||||
bgColor.Red(), bgColor.Green(), bgColor.Blue(), fgColor.Red(),
|
||||
fgColor.Green(), fgColor.Blue(), textToHtml(message));
|
||||
|
||||
htmlCtrl->SetPage(htmlContent);
|
||||
// htmlCtrl->SetBackgroundColour(bgColor); // Removed as it might not exist or be needed
|
||||
// htmlCtrl->SetBackgroundColour(bgColor); // Removed as it might not exist
|
||||
// or be needed
|
||||
|
||||
// Set a reasonable fixed size - will expand based on content
|
||||
// Width: fixed at 600px for consistent layout
|
||||
// Height: let wxHtmlWindow calculate based on content, with limits
|
||||
int contentWidth = 600;
|
||||
constexpr int contentWidth = 600;
|
||||
|
||||
// Calculate approximate height based on line count (rough estimate)
|
||||
int lineCount = 1;
|
||||
for (size_t i = 0; i < text.length(); i++) {
|
||||
if (text[i] == '\n') lineCount++;
|
||||
for (size_t i = 0; i < message.length(); i++) {
|
||||
if (message[i] == '\n')
|
||||
lineCount++;
|
||||
}
|
||||
|
||||
// Estimate: ~20px per line + 40px padding
|
||||
@@ -182,17 +192,16 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
||||
wxSize minSize = sizer->GetMinSize();
|
||||
|
||||
// Set reasonable dialog size
|
||||
int dialogWidth = std::clamp(minSize.GetWidth() + 40, 500, 800);
|
||||
int dialogHeight = std::clamp(minSize.GetHeight() + 40, 300, 700);
|
||||
const int dialogWidth = std::clamp(minSize.GetWidth() + 40, 500, 800);
|
||||
const int dialogHeight = std::clamp(minSize.GetHeight() + 40, 300, 700);
|
||||
|
||||
SetSize(dialogWidth, dialogHeight);
|
||||
SetMinSize(wxSize(400, 250));
|
||||
wxTopLevelWindowBase::SetMinSize(wxSize(400, 250));
|
||||
CenterOnParent();
|
||||
}
|
||||
|
||||
void WherigoMessageDialog::onButton(wxCommandEvent &event) {
|
||||
int id = event.GetId();
|
||||
if (id == wxID_OK) {
|
||||
void WherigoMessageDialog::onButton(const wxCommandEvent &event) {
|
||||
if (const int id = event.GetId(); id == wxID_OK) {
|
||||
m_selectedButton = 0;
|
||||
} else {
|
||||
m_selectedButton = id - 1000;
|
||||
@@ -200,13 +209,14 @@ void WherigoMessageDialog::onButton(wxCommandEvent &event) {
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
WherigoDialogRunner& WherigoDialogRunner::getInstance() {
|
||||
WherigoDialogRunner &WherigoDialogRunner::getInstance() {
|
||||
static WherigoDialogRunner instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void WherigoDialogRunner::showMessageBox(const wxString &text, const wxString &title,
|
||||
std::function<void(int)> callback) {
|
||||
void WherigoDialogRunner::showMessageBox(const wxString &text,
|
||||
const wxString &title,
|
||||
std::function<void(int)> callback) {
|
||||
WherigoMessageDialog dlg(nullptr, text, title);
|
||||
dlg.ShowModal();
|
||||
|
||||
@@ -216,27 +226,26 @@ void WherigoDialogRunner::showMessageBox(const wxString &text, const wxString &t
|
||||
}
|
||||
|
||||
void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
|
||||
std::function<void(int)> callback) {
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
std::function<void(int)> callback) {
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
const auto &entry = entries[i];
|
||||
|
||||
std::vector<wxString> buttons;
|
||||
buttons.reserve(entry.buttons.size() + 1);
|
||||
for (const auto &btn : entry.buttons) {
|
||||
buttons.push_back(wxString::FromUTF8(btn));
|
||||
buttons.emplace_back(wxString::FromUTF8(btn));
|
||||
}
|
||||
|
||||
if (buttons.empty() && i < entries.size() - 1) {
|
||||
buttons.push_back("Weiter");
|
||||
} else if (buttons.empty()) {
|
||||
buttons.push_back("OK");
|
||||
if (buttons.empty()) {
|
||||
buttons.emplace_back(i + 1 < entries.size() ? "Weiter" : "OK");
|
||||
}
|
||||
|
||||
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(entry.text), "Wherigo",
|
||||
buttons, wxString::FromUTF8(entry.mediaName));
|
||||
buttons, wxString::FromUTF8(entry.mediaName));
|
||||
dlg.ShowModal();
|
||||
|
||||
// For the last entry, call the callback with the selected button
|
||||
if (i == entries.size() - 1 && callback) {
|
||||
if (i + 1 == entries.size() && callback) {
|
||||
callback(dlg.getSelectedButton());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user