some tweaks
still non playable cartridges Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
@@ -113,3 +113,4 @@ CMakeUserPresets.json
|
|||||||
cartridges/
|
cartridges/
|
||||||
assets/icon.iconset/
|
assets/icon.iconset/
|
||||||
*.lua
|
*.lua
|
||||||
|
*.local.json
|
||||||
|
|||||||
+4
-4
@@ -16,16 +16,16 @@ public:
|
|||||||
int OnExit() override;
|
int OnExit() override;
|
||||||
|
|
||||||
bool loadCartridge(const std::string &filePath);
|
bool loadCartridge(const std::string &filePath);
|
||||||
void startGame();
|
void startGame() const;
|
||||||
void unloadCartridge();
|
void unloadCartridge();
|
||||||
|
|
||||||
// Save/Load game state
|
// Save/Load game state
|
||||||
bool saveGameState(const std::string &saveFilePath);
|
bool saveGameState(const std::string &saveFilePath) const;
|
||||||
bool loadGameState(const std::string &saveFilePath);
|
bool loadGameState(const std::string &saveFilePath) const;
|
||||||
std::string getAutoSavePath() const;
|
std::string getAutoSavePath() const;
|
||||||
|
|
||||||
// Generate completion log (for wherigo.com)
|
// Generate completion log (for wherigo.com)
|
||||||
bool generateCompletionLog(const std::string &logFilePath);
|
bool generateCompletionLog(const std::string &logFilePath) const;
|
||||||
std::string getCompletionLogPath() const;
|
std::string getCompletionLogPath() const;
|
||||||
|
|
||||||
lua_State* getLuaState() const { return m_luaState; }
|
lua_State* getLuaState() const { return m_luaState; }
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public:
|
|||||||
|
|
||||||
// Notify listeners that game state has changed
|
// Notify listeners that game state has changed
|
||||||
void notifyStateChanged();
|
void notifyStateChanged();
|
||||||
|
void rebuildPlayerInventory();
|
||||||
|
|
||||||
lua_State* getLuaState() const { return m_luaState; }
|
lua_State* getLuaState() const { return m_luaState; }
|
||||||
int getCartridgeRef() const { return m_cartridgeRef; }
|
int getCartridgeRef() const { return m_cartridgeRef; }
|
||||||
@@ -64,6 +65,8 @@ private:
|
|||||||
double m_playerLng = 0.0;
|
double m_playerLng = 0.0;
|
||||||
double m_playerAlt = 0.0;
|
double m_playerAlt = 0.0;
|
||||||
|
|
||||||
|
bool m_isProcessing = false;
|
||||||
|
|
||||||
wxDECLARE_EVENT_TABLE();
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <wx/frame.h>
|
#include <wx/frame.h>
|
||||||
|
#include <wx/timer.h>
|
||||||
#include <wx/webview.h>
|
#include <wx/webview.h>
|
||||||
#include <vector>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct SimPoint {
|
struct SimPoint {
|
||||||
double lat;
|
double lat;
|
||||||
double lon;
|
double lon;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ZoneInfo {
|
||||||
|
double lat;
|
||||||
|
double lon;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
class MapSimFrame : public wxFrame {
|
class MapSimFrame : public wxFrame {
|
||||||
public:
|
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 AddSimPoint(double lat, double lon);
|
||||||
void StartSimulation();
|
void StartSimulation();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
wxWebView* m_webView;
|
wxWebView* m_webView = nullptr;
|
||||||
std::vector<SimPoint> m_route;
|
wxTimer m_simTimer;
|
||||||
std::vector<std::pair<double, double>> m_zoneCoords;
|
size_t m_simIndex = 0;
|
||||||
void OnWebViewEvent(wxWebViewEvent& event);
|
|
||||||
void OnPlay(wxCommandEvent& event);
|
std::vector<SimPoint> m_route;
|
||||||
|
std::vector<ZoneInfo> m_zoneInfos;
|
||||||
|
bool m_pageLoaded = false;
|
||||||
|
|
||||||
void SendPositionToEngine(double lat, double lon);
|
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();
|
wxDECLARE_EVENT_TABLE();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public:
|
|||||||
int getSelectedButton() const { return m_selectedButton; }
|
int getSelectedButton() const { return m_selectedButton; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onButton(wxCommandEvent &event);
|
void onButton(const wxCommandEvent &event);
|
||||||
|
|
||||||
int m_selectedButton = -1;
|
int m_selectedButton = -1;
|
||||||
};
|
};
|
||||||
|
|||||||
+42
-14
@@ -6,6 +6,11 @@
|
|||||||
#include "lua/wherigo_completion.h"
|
#include "lua/wherigo_completion.h"
|
||||||
#include "ui/start_screen.h"
|
#include "ui/start_screen.h"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <objc/message.h>
|
||||||
|
#include <objc/objc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cartridge/parser.h>
|
#include <cartridge/parser.h>
|
||||||
#include <wx/dir.h>
|
#include <wx/dir.h>
|
||||||
#include <wx/filename.h>
|
#include <wx/filename.h>
|
||||||
@@ -26,6 +31,19 @@ bool cApp::OnInit()
|
|||||||
auto *startFrame = new cStartScreen();
|
auto *startFrame = new cStartScreen();
|
||||||
startFrame->Show(true);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +64,7 @@ bool cApp::loadCartridge(const std::string &filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool cApp::initLuaState() {
|
bool cApp::initLuaState() {
|
||||||
auto luac = m_cartridge->luac();
|
const auto luac = m_cartridge->luac();
|
||||||
if (!luac) {
|
if (!luac) {
|
||||||
wxLogError("No Lua bytecode in cartridge");
|
wxLogError("No Lua bytecode in cartridge");
|
||||||
return false;
|
return false;
|
||||||
@@ -68,7 +86,7 @@ bool cApp::initLuaState() {
|
|||||||
lua_pop(m_luaState, 1);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
const auto &bytecode = luac->getData();
|
const auto &bytecode = luac->getData();
|
||||||
int result = luaL_loadbuffer(m_luaState,
|
const int result = luaL_loadbuffer(m_luaState,
|
||||||
reinterpret_cast<const char *>(bytecode.data()),
|
reinterpret_cast<const char *>(bytecode.data()),
|
||||||
bytecode.size(),
|
bytecode.size(),
|
||||||
"cartridge");
|
"cartridge");
|
||||||
@@ -98,8 +116,8 @@ bool cApp::initLuaState() {
|
|||||||
if (lua_istable(m_luaState, -1)) {
|
if (lua_istable(m_luaState, -1)) {
|
||||||
lua_getfield(m_luaState, -1, "ClassName");
|
lua_getfield(m_luaState, -1, "ClassName");
|
||||||
if (lua_isstring(m_luaState, -1)) {
|
if (lua_isstring(m_luaState, -1)) {
|
||||||
const char *className = lua_tostring(m_luaState, -1);
|
if (const char *className = lua_tostring(m_luaState, -1);
|
||||||
if (strcmp(className, "ZCartridge") == 0) {
|
strcmp(className, "ZCartridge") == 0) {
|
||||||
lua_pop(m_luaState, 1); // pop ClassName
|
lua_pop(m_luaState, 1); // pop ClassName
|
||||||
m_cartridgeRef = luaL_ref(m_luaState, LUA_REGISTRYINDEX); // store reference, pops table
|
m_cartridgeRef = luaL_ref(m_luaState, LUA_REGISTRYINDEX); // store reference, pops table
|
||||||
lua_pop(m_luaState, 1); // pop key
|
lua_pop(m_luaState, 1); // pop key
|
||||||
@@ -115,6 +133,16 @@ bool cApp::initLuaState() {
|
|||||||
// Initialize media manager
|
// Initialize media manager
|
||||||
wherigo::MediaManager::getInstance().init(m_luaState, m_cartridge.get());
|
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) {
|
if (m_cartridgeRef == LUA_NOREF) {
|
||||||
wxLogError("No ZCartridge object found!");
|
wxLogError("No ZCartridge object found!");
|
||||||
return false;
|
return false;
|
||||||
@@ -126,7 +154,7 @@ bool cApp::initLuaState() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cApp::startGame() {
|
void cApp::startGame() const {
|
||||||
if (!m_luaState || m_cartridgeRef == LUA_NOREF) {
|
if (!m_luaState || m_cartridgeRef == LUA_NOREF) {
|
||||||
wxLogError("No cartridge loaded");
|
wxLogError("No cartridge loaded");
|
||||||
return;
|
return;
|
||||||
@@ -190,38 +218,38 @@ std::string cApp::getAutoSavePath() const {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cApp::saveGameState(const std::string &saveFilePath) {
|
bool cApp::saveGameState(const std::string &saveFilePath) const {
|
||||||
if (!m_luaState) {
|
if (!m_luaState) {
|
||||||
wxLogError("No Lua state to save");
|
wxLogError("No Lua state to save");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
const std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||||
if (savePath.empty()) {
|
if (savePath.empty()) {
|
||||||
wxLogError("No save path available");
|
wxLogError("No save path available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
|
const bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
|
||||||
if (success) {
|
if (success) {
|
||||||
wxLogMessage("Game saved to: %s", savePath);
|
wxLogMessage("Game saved to: %s", savePath);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cApp::loadGameState(const std::string &saveFilePath) {
|
bool cApp::loadGameState(const std::string &saveFilePath) const {
|
||||||
if (!m_luaState) {
|
if (!m_luaState) {
|
||||||
wxLogError("No Lua state to load into");
|
wxLogError("No Lua state to load into");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
const std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||||
if (loadPath.empty() || !wxFileExists(loadPath)) {
|
if (loadPath.empty() || !wxFileExists(loadPath)) {
|
||||||
wxLogError("Save file not found: %s", loadPath);
|
wxLogError("Save file not found: %s", loadPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
|
const bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
|
||||||
if (success) {
|
if (success) {
|
||||||
wxLogMessage("Game loaded from: %s", loadPath);
|
wxLogMessage("Game loaded from: %s", loadPath);
|
||||||
// Notify game engine of state change
|
// Notify game engine of state change
|
||||||
@@ -247,19 +275,19 @@ std::string cApp::getCompletionLogPath() const {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cApp::generateCompletionLog(const std::string &logFilePath) {
|
bool cApp::generateCompletionLog(const std::string &logFilePath) const {
|
||||||
if (!m_luaState) {
|
if (!m_luaState) {
|
||||||
wxLogError("No Lua state for completion log");
|
wxLogError("No Lua state for completion log");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
|
const std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
|
||||||
if (logPath.empty()) {
|
if (logPath.empty()) {
|
||||||
wxLogError("No completion log path available");
|
wxLogError("No completion log path available");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
|
const bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
|
||||||
if (success) {
|
if (success) {
|
||||||
wxLogMessage("Completion log generated: %s", logPath);
|
wxLogMessage("Completion log generated: %s", logPath);
|
||||||
}
|
}
|
||||||
|
|||||||
+241
-62
@@ -1,15 +1,18 @@
|
|||||||
#include "lua/game_engine.h"
|
#include "lua/game_engine.h"
|
||||||
|
#include "lua/geo_utils.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lua.h>
|
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
|
#include <lua.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <wx/log.h>
|
#include <wx/log.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace wherigo {
|
namespace wherigo {
|
||||||
|
|
||||||
// Define the custom event
|
|
||||||
wxDEFINE_EVENT(EVT_GAME_STATE_CHANGED, GameStateEvent);
|
wxDEFINE_EVENT(EVT_GAME_STATE_CHANGED, GameStateEvent);
|
||||||
|
|
||||||
wxBEGIN_EVENT_TABLE(GameEngine, wxEvtHandler)
|
wxBEGIN_EVENT_TABLE(GameEngine, wxEvtHandler)
|
||||||
@@ -21,12 +24,8 @@ GameEngine& GameEngine::getInstance() {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameEngine::GameEngine() : m_gameTimer(this) {
|
GameEngine::GameEngine() : m_gameTimer(this) {}
|
||||||
}
|
GameEngine::~GameEngine() { shutdown(); }
|
||||||
|
|
||||||
GameEngine::~GameEngine() {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameEngine::init(lua_State *L, int cartridgeRef) {
|
void GameEngine::init(lua_State *L, int cartridgeRef) {
|
||||||
m_luaState = L;
|
m_luaState = L;
|
||||||
@@ -42,15 +41,13 @@ void GameEngine::shutdown() {
|
|||||||
|
|
||||||
void GameEngine::start() {
|
void GameEngine::start() {
|
||||||
if (m_running) return;
|
if (m_running) return;
|
||||||
|
|
||||||
m_running = true;
|
m_running = true;
|
||||||
m_gameTimer.Start(1000); // 1 second tick
|
m_gameTimer.Start(1000);
|
||||||
wxLogDebug("GameEngine started");
|
wxLogDebug("GameEngine started");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameEngine::stop() {
|
void GameEngine::stop() {
|
||||||
if (!m_running) return;
|
if (!m_running) return;
|
||||||
|
|
||||||
m_gameTimer.Stop();
|
m_gameTimer.Stop();
|
||||||
m_running = false;
|
m_running = false;
|
||||||
wxLogDebug("GameEngine stopped");
|
wxLogDebug("GameEngine stopped");
|
||||||
@@ -67,93 +64,89 @@ void GameEngine::updatePlayerPosition(double lat, double lng, double alt) {
|
|||||||
lua_getglobal(m_luaState, "Player");
|
lua_getglobal(m_luaState, "Player");
|
||||||
if (lua_istable(m_luaState, -1)) {
|
if (lua_istable(m_luaState, -1)) {
|
||||||
lua_newtable(m_luaState);
|
lua_newtable(m_luaState);
|
||||||
lua_pushnumber(m_luaState, lat);
|
lua_pushnumber(m_luaState, lat); lua_setfield(m_luaState, -2, "latitude");
|
||||||
lua_setfield(m_luaState, -2, "latitude");
|
lua_pushnumber(m_luaState, lng); lua_setfield(m_luaState, -2, "longitude");
|
||||||
lua_pushnumber(m_luaState, lng);
|
lua_pushnumber(m_luaState, alt); lua_setfield(m_luaState, -2, "altitude");
|
||||||
lua_setfield(m_luaState, -2, "longitude");
|
|
||||||
lua_pushnumber(m_luaState, alt);
|
|
||||||
lua_setfield(m_luaState, -2, "altitude");
|
|
||||||
lua_setfield(m_luaState, -2, "ObjectLocation");
|
lua_setfield(m_luaState, -2, "ObjectLocation");
|
||||||
}
|
}
|
||||||
lua_pop(m_luaState, 1);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
checkZones();
|
if (!m_isProcessing) {
|
||||||
|
m_isProcessing = true;
|
||||||
|
checkZones();
|
||||||
|
notifyStateChanged();
|
||||||
|
m_isProcessing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameEngine::onGameTick(wxTimerEvent& event) {
|
void GameEngine::onGameTick(wxTimerEvent&) {
|
||||||
if (!m_luaState || !m_running) return;
|
if (!m_luaState || !m_running || m_isProcessing) return;
|
||||||
|
m_isProcessing = true;
|
||||||
checkTimers();
|
checkTimers();
|
||||||
|
|
||||||
// Notify listeners of potential state changes
|
|
||||||
notifyStateChanged();
|
notifyStateChanged();
|
||||||
|
m_isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Timer processing ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void GameEngine::checkTimers() {
|
void GameEngine::checkTimers() {
|
||||||
if (!m_luaState) return;
|
if (!m_luaState) return;
|
||||||
|
|
||||||
// Iterate through all global variables to find running timers
|
|
||||||
lua_getglobal(m_luaState, "_G");
|
lua_getglobal(m_luaState, "_G");
|
||||||
lua_pushnil(m_luaState);
|
lua_pushnil(m_luaState);
|
||||||
|
|
||||||
while (lua_next(m_luaState, -2) != 0) {
|
while (lua_next(m_luaState, -2) != 0) {
|
||||||
if (lua_istable(m_luaState, -1)) {
|
if (lua_istable(m_luaState, -1)) {
|
||||||
lua_getfield(m_luaState, -1, "ClassName");
|
lua_getfield(m_luaState, -1, "ClassName");
|
||||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "ZTimer") == 0) {
|
const bool isTimer = lua_isstring(m_luaState, -1) &&
|
||||||
lua_pop(m_luaState, 1); // pop ClassName
|
!strcmp(lua_tostring(m_luaState, -1), "ZTimer");
|
||||||
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
|
if (isTimer) {
|
||||||
lua_getfield(m_luaState, -1, "Running");
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
if (running) {
|
if (running) {
|
||||||
// Get timer info
|
|
||||||
lua_getfield(m_luaState, -1, "Name");
|
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_pop(m_luaState, 1);
|
||||||
|
|
||||||
lua_getfield(m_luaState, -1, "Remaining");
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
// If Remaining not set, initialize from Duration
|
|
||||||
if (remaining < 0) {
|
if (remaining < 0) {
|
||||||
lua_getfield(m_luaState, -1, "Duration");
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrement remaining time
|
|
||||||
remaining -= 1.0;
|
remaining -= 1.0;
|
||||||
lua_pushnumber(m_luaState, remaining);
|
lua_pushnumber(m_luaState, remaining);
|
||||||
lua_setfield(m_luaState, -2, "Remaining");
|
lua_setfield(m_luaState, -2, "Remaining");
|
||||||
|
|
||||||
// Call OnTick if exists
|
|
||||||
lua_getfield(m_luaState, -1, "OnTick");
|
lua_getfield(m_luaState, -1, "OnTick");
|
||||||
if (lua_isfunction(m_luaState, -1)) {
|
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) {
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lua_pop(m_luaState, 1);
|
lua_pop(m_luaState, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if elapsed
|
|
||||||
if (remaining <= 0) {
|
if (remaining <= 0) {
|
||||||
wxLogDebug("Timer elapsed: %s", name);
|
wxLogDebug("Timer elapsed: %s", name);
|
||||||
|
|
||||||
// Stop the timer
|
|
||||||
lua_pushboolean(m_luaState, 0);
|
lua_pushboolean(m_luaState, 0);
|
||||||
lua_setfield(m_luaState, -2, "Running");
|
lua_setfield(m_luaState, -2, "Running");
|
||||||
|
|
||||||
// Call OnElapsed if exists
|
|
||||||
lua_getfield(m_luaState, -1, "OnElapsed");
|
lua_getfield(m_luaState, -1, "OnElapsed");
|
||||||
if (lua_isfunction(m_luaState, -1)) {
|
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) {
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
}
|
}
|
||||||
} else {
|
} 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() {
|
void GameEngine::checkZones() {
|
||||||
if (!m_luaState) return;
|
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_getglobal(m_luaState, "_G");
|
||||||
lua_pushnil(m_luaState);
|
lua_pushnil(m_luaState);
|
||||||
|
|
||||||
while (lua_next(m_luaState, -2) != 0) {
|
while (lua_next(m_luaState, -2) != 0) {
|
||||||
if (lua_istable(m_luaState, -1)) {
|
if (lua_istable(m_luaState, -1)) {
|
||||||
lua_getfield(m_luaState, -1, "ClassName");
|
lua_getfield(m_luaState, -1, "ClassName");
|
||||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "Zone") == 0) {
|
const bool isZone = lua_isstring(m_luaState,-1) &&
|
||||||
lua_pop(m_luaState, 1); // pop ClassName
|
!strcmp(lua_tostring(m_luaState,-1), "Zone");
|
||||||
|
lua_pop(m_luaState, 1);
|
||||||
|
if (isZone) {
|
||||||
lua_getfield(m_luaState, -1, "Active");
|
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);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
if (active) {
|
if (inInventory) {
|
||||||
// TODO: Check if player is inside zone using Points
|
lua_getfield(m_luaState, -1, "Visible");
|
||||||
// For now, just log that we would check
|
const bool visible = lua_toboolean(m_luaState, -1);
|
||||||
lua_getfield(m_luaState, -1, "Name");
|
|
||||||
const char *name = lua_isstring(m_luaState, -1) ? lua_tostring(m_luaState, -1) : "(unnamed)";
|
|
||||||
lua_pop(m_luaState, 1);
|
lua_pop(m_luaState, 1);
|
||||||
|
|
||||||
// This is where you would implement point-in-polygon check
|
if (visible) {
|
||||||
// and call OnEnter/OnExit callbacks
|
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 value
|
||||||
}
|
}
|
||||||
lua_pop(m_luaState, 1); // pop _G
|
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() {
|
void GameEngine::notifyStateChanged() {
|
||||||
@@ -212,4 +392,3 @@ void GameEngine::notifyStateChanged() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace wherigo
|
} // namespace wherigo
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ void MediaManager::buildMediaIndex(lua_State *L) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> MediaManager::getMediaByName(const std::string &name) {
|
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()) {
|
if (it == m_nameToIndex.end()) {
|
||||||
wxLogWarning("Media not found: %s", name.c_str());
|
wxLogWarning("Media not found: %s", name.c_str());
|
||||||
return {};
|
return {};
|
||||||
@@ -84,7 +84,7 @@ std::vector<uint8_t> MediaManager::getMediaByIndex(int index) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto media = m_cartridge->getMedia(index);
|
const auto media = m_cartridge->getMedia(index);
|
||||||
if (!media) {
|
if (!media) {
|
||||||
wxLogWarning("Media index %d not found in cartridge", index);
|
wxLogWarning("Media index %d not found in cartridge", index);
|
||||||
return {};
|
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);
|
wxLogDebug("MoveTo: %s -> %s", srcName, dstName);
|
||||||
|
|
||||||
// Notify game state change
|
GameEngine::getInstance().rebuildPlayerInventory();
|
||||||
GameEngine::getInstance().notifyStateChanged();
|
GameEngine::getInstance().notifyStateChanged();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+100
-89
@@ -37,7 +37,7 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
|||||||
auto *menuBar = new wxMenuBar;
|
auto *menuBar = new wxMenuBar;
|
||||||
menuBar->Append(menuFile, "&Datei");
|
menuBar->Append(menuFile, "&Datei");
|
||||||
menuBar->Append(menuHelp, "&Hilfe");
|
menuBar->Append(menuHelp, "&Hilfe");
|
||||||
SetMenuBar(menuBar);
|
wxFrameBase::SetMenuBar(menuBar);
|
||||||
|
|
||||||
// Main sizer
|
// Main sizer
|
||||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
@@ -45,15 +45,6 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
|||||||
// Notebook with tabs
|
// Notebook with tabs
|
||||||
m_notebook = new wxNotebook(this, wxID_ANY);
|
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
|
// Tasks tab
|
||||||
auto *taskPanel = new wxPanel(m_notebook);
|
auto *taskPanel = new wxPanel(m_notebook);
|
||||||
auto *taskSizer = new wxBoxSizer(wxVERTICAL);
|
auto *taskSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
@@ -63,6 +54,15 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
|||||||
taskPanel->SetSizer(taskSizer);
|
taskPanel->SetSizer(taskSizer);
|
||||||
m_notebook->AddPage(taskPanel, "Aufgaben");
|
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
|
// Inventory tab
|
||||||
auto *inventoryPanel = new wxPanel(m_notebook);
|
auto *inventoryPanel = new wxPanel(m_notebook);
|
||||||
auto *inventorySizer = new wxBoxSizer(wxVERTICAL);
|
auto *inventorySizer = new wxBoxSizer(wxVERTICAL);
|
||||||
@@ -97,8 +97,8 @@ cGameScreen::cGameScreen(wxWindow *parent)
|
|||||||
CentreOnScreen();
|
CentreOnScreen();
|
||||||
|
|
||||||
// Status bar
|
// Status bar
|
||||||
CreateStatusBar();
|
wxFrameBase::CreateStatusBar();
|
||||||
SetStatusText("Spiel läuft");
|
wxFrameBase::SetStatusText("Spiel läuft");
|
||||||
|
|
||||||
// Event bindings
|
// Event bindings
|
||||||
Bind(wxEVT_CLOSE_WINDOW, &cGameScreen::OnClose, this);
|
Bind(wxEVT_CLOSE_WINDOW, &cGameScreen::OnClose, this);
|
||||||
@@ -158,7 +158,7 @@ void cGameScreen::OnSaveGame(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::OnLoadGame(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);
|
"Laden", wxYES_NO | wxICON_QUESTION);
|
||||||
|
|
||||||
if (result == wxYES) {
|
if (result == wxYES) {
|
||||||
@@ -185,9 +185,8 @@ void cGameScreen::OnExportCompletion(wxCommandEvent& event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString filePath = saveDialog.GetPath();
|
if (const wxString filePath = saveDialog.GetPath();
|
||||||
|
wxGetApp().generateCompletionLog(filePath.ToStdString())) {
|
||||||
if (wxGetApp().generateCompletionLog(filePath.ToStdString())) {
|
|
||||||
wxMessageBox("Completion Log wurde erfolgreich exportiert!\n\nDiese Datei kann auf wherigo.com hochgeladen werden, um den Abschluss nachzuweisen.",
|
wxMessageBox("Completion Log wurde erfolgreich exportiert!\n\nDiese Datei kann auf wherigo.com hochgeladen werden, um den Abschluss nachzuweisen.",
|
||||||
"Export erfolgreich", wxOK | wxICON_INFORMATION);
|
"Export erfolgreich", wxOK | wxICON_INFORMATION);
|
||||||
} else {
|
} else {
|
||||||
@@ -201,6 +200,9 @@ void cGameScreen::OnGameStateChanged(wxEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::refreshUI() {
|
void cGameScreen::refreshUI() {
|
||||||
|
if (wxGetApp().isCartridgeLoaded())
|
||||||
|
SetTitle(wxString::FromUTF8(wxGetApp().getCartridge()->cartridgeName()));
|
||||||
|
|
||||||
// Freeze to prevent flickering and focus loss
|
// Freeze to prevent flickering and focus loss
|
||||||
Freeze();
|
Freeze();
|
||||||
|
|
||||||
@@ -214,8 +216,8 @@ void cGameScreen::refreshUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::populateZones() {
|
void cGameScreen::populateZones() {
|
||||||
int selection = m_zoneList->GetSelection();
|
const int selection = m_zoneList->GetSelection();
|
||||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_zoneList->GetString(selection) : "";
|
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_zoneList->GetString(selection) : "";
|
||||||
|
|
||||||
m_zoneList->Clear();
|
m_zoneList->Clear();
|
||||||
|
|
||||||
@@ -232,11 +234,11 @@ void cGameScreen::populateZones() {
|
|||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Active");
|
lua_getfield(L, -1, "Active");
|
||||||
bool active = lua_toboolean(L, -1);
|
const bool active = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Visible");
|
lua_getfield(L, -1, "Visible");
|
||||||
bool visible = lua_toboolean(L, -1);
|
const bool visible = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (active && visible) {
|
if (active && visible) {
|
||||||
@@ -256,16 +258,15 @@ void cGameScreen::populateZones() {
|
|||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (!selectedItem.IsEmpty()) {
|
if (!selectedItem.IsEmpty()) {
|
||||||
int idx = m_zoneList->FindString(selectedItem);
|
if (const int idx = m_zoneList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||||
if (idx != wxNOT_FOUND) {
|
|
||||||
m_zoneList->SetSelection(idx);
|
m_zoneList->SetSelection(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::populateTasks() {
|
void cGameScreen::populateTasks() {
|
||||||
int selection = m_taskList->GetSelection();
|
const int selection = m_taskList->GetSelection();
|
||||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_taskList->GetString(selection) : "";
|
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_taskList->GetString(selection) : "";
|
||||||
|
|
||||||
m_taskList->Clear();
|
m_taskList->Clear();
|
||||||
|
|
||||||
@@ -286,7 +287,7 @@ void cGameScreen::populateTasks() {
|
|||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Complete");
|
lua_getfield(L, -1, "Complete");
|
||||||
bool complete = lua_toboolean(L, -1);
|
const bool complete = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (active && !complete) {
|
if (active && !complete) {
|
||||||
@@ -306,16 +307,15 @@ void cGameScreen::populateTasks() {
|
|||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (!selectedItem.IsEmpty()) {
|
if (!selectedItem.IsEmpty()) {
|
||||||
int idx = m_taskList->FindString(selectedItem);
|
if (const int idx = m_taskList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||||
if (idx != wxNOT_FOUND) {
|
|
||||||
m_taskList->SetSelection(idx);
|
m_taskList->SetSelection(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::populateInventory() {
|
void cGameScreen::populateInventory() {
|
||||||
int selection = m_inventoryList->GetSelection();
|
const int selection = m_inventoryList->GetSelection();
|
||||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_inventoryList->GetString(selection) : "";
|
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_inventoryList->GetString(selection) : "";
|
||||||
|
|
||||||
m_inventoryList->Clear();
|
m_inventoryList->Clear();
|
||||||
|
|
||||||
@@ -343,15 +343,21 @@ void cGameScreen::populateInventory() {
|
|||||||
if (lua_istable(L, -1)) {
|
if (lua_istable(L, -1)) {
|
||||||
// Check if Container is Player
|
// Check if Container is Player
|
||||||
lua_getglobal(L, "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
|
lua_pop(L, 2); // pop Player and Container
|
||||||
|
|
||||||
if (isInInventory) {
|
if (isInInventory) {
|
||||||
lua_getfield(L, -1, "Name");
|
lua_getfield(L, -1, "Visible");
|
||||||
if (lua_isstring(L, -1)) {
|
const bool visible = lua_toboolean(L, -1);
|
||||||
m_inventoryList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
|
||||||
}
|
|
||||||
lua_pop(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 {
|
} else {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
@@ -366,16 +372,16 @@ void cGameScreen::populateInventory() {
|
|||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (!selectedItem.IsEmpty()) {
|
if (!selectedItem.IsEmpty()) {
|
||||||
int idx = m_inventoryList->FindString(selectedItem);
|
if (const int idx = m_inventoryList->FindString(selectedItem);
|
||||||
if (idx != wxNOT_FOUND) {
|
idx != wxNOT_FOUND) {
|
||||||
m_inventoryList->SetSelection(idx);
|
m_inventoryList->SetSelection(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::populateCharacters() {
|
void cGameScreen::populateCharacters() {
|
||||||
int selection = m_characterList->GetSelection();
|
const int selection = m_characterList->GetSelection();
|
||||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_characterList->GetString(selection) : "";
|
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_characterList->GetString(selection) : "";
|
||||||
|
|
||||||
m_characterList->Clear();
|
m_characterList->Clear();
|
||||||
|
|
||||||
@@ -392,16 +398,16 @@ void cGameScreen::populateCharacters() {
|
|||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Active");
|
lua_getfield(L, -1, "Active");
|
||||||
bool active = lua_toboolean(L, -1);
|
const bool active = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Visible");
|
lua_getfield(L, -1, "Visible");
|
||||||
bool visible = lua_toboolean(L, -1);
|
const bool visible = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// Check if character has a container (is somewhere)
|
// Check if character has a container (is somewhere)
|
||||||
lua_getfield(L, -1, "Container");
|
lua_getfield(L, -1, "Container");
|
||||||
bool hasLocation = lua_istable(L, -1);
|
const bool hasLocation = lua_istable(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (active && visible && hasLocation) {
|
if (active && visible && hasLocation) {
|
||||||
@@ -435,16 +441,16 @@ void cGameScreen::populateCharacters() {
|
|||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (!selectedItem.IsEmpty()) {
|
if (!selectedItem.IsEmpty()) {
|
||||||
int idx = m_characterList->FindString(selectedItem);
|
if (const int idx = m_characterList->FindString(selectedItem);
|
||||||
if (idx != wxNOT_FOUND) {
|
idx != wxNOT_FOUND) {
|
||||||
m_characterList->SetSelection(idx);
|
m_characterList->SetSelection(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::populateItems() {
|
void cGameScreen::populateItems() {
|
||||||
int selection = m_itemList->GetSelection();
|
const int selection = m_itemList->GetSelection();
|
||||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_itemList->GetString(selection) : "";
|
const wxString selectedItem = (selection != wxNOT_FOUND) ? m_itemList->GetString(selection) : "";
|
||||||
|
|
||||||
m_itemList->Clear();
|
m_itemList->Clear();
|
||||||
|
|
||||||
@@ -461,11 +467,11 @@ void cGameScreen::populateItems() {
|
|||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Active");
|
lua_getfield(L, -1, "Active");
|
||||||
bool active = lua_toboolean(L, -1);
|
const bool active = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_getfield(L, -1, "Visible");
|
lua_getfield(L, -1, "Visible");
|
||||||
bool visible = lua_toboolean(L, -1);
|
const bool visible = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// Check if item is NOT in player inventory (in a zone)
|
// Check if item is NOT in player inventory (in a zone)
|
||||||
@@ -512,8 +518,7 @@ void cGameScreen::populateItems() {
|
|||||||
|
|
||||||
// Restore selection
|
// Restore selection
|
||||||
if (!selectedItem.IsEmpty()) {
|
if (!selectedItem.IsEmpty()) {
|
||||||
int idx = m_itemList->FindString(selectedItem);
|
if (const int idx = m_itemList->FindString(selectedItem); idx != wxNOT_FOUND) {
|
||||||
if (idx != wxNOT_FOUND) {
|
|
||||||
m_itemList->SetSelection(idx);
|
m_itemList->SetSelection(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,10 +663,10 @@ void cGameScreen::OnTaskSelected(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
||||||
int sel = m_inventoryList->GetSelection();
|
const int sel = m_inventoryList->GetSelection();
|
||||||
if (sel == wxNOT_FOUND) return;
|
if (sel == wxNOT_FOUND) return;
|
||||||
|
|
||||||
wxString itemName = m_inventoryList->GetString(sel);
|
const wxString itemName = m_inventoryList->GetString(sel);
|
||||||
|
|
||||||
lua_State *L = wxGetApp().getLuaState();
|
lua_State *L = wxGetApp().getLuaState();
|
||||||
if (!L) return;
|
if (!L) return;
|
||||||
@@ -712,12 +717,12 @@ void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
||||||
int sel = m_characterList->GetSelection();
|
const int sel = m_characterList->GetSelection();
|
||||||
if (sel == wxNOT_FOUND) return;
|
if (sel == wxNOT_FOUND) return;
|
||||||
|
|
||||||
// Extract name from "Name (Location)" format
|
// Extract name from "Name (Location)" format
|
||||||
wxString fullText = m_characterList->GetString(sel);
|
const wxString fullText = m_characterList->GetString(sel);
|
||||||
wxString charName = fullText.BeforeFirst('(').Trim();
|
const wxString charName = fullText.BeforeFirst('(').Trim();
|
||||||
|
|
||||||
lua_State *L = wxGetApp().getLuaState();
|
lua_State *L = wxGetApp().getLuaState();
|
||||||
if (!L) return;
|
if (!L) return;
|
||||||
@@ -734,13 +739,13 @@ void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
|||||||
|
|
||||||
lua_getfield(L, -1, "Name");
|
lua_getfield(L, -1, "Name");
|
||||||
if (lua_isstring(L, -1)) {
|
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);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (currentName == charName) {
|
if (currentName == charName) {
|
||||||
// Get character description
|
// Get character description
|
||||||
lua_getfield(L, -1, "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);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// Get media if exists
|
// Get media if exists
|
||||||
@@ -789,12 +794,12 @@ void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||||
int sel = m_itemList->GetSelection();
|
const int sel = m_itemList->GetSelection();
|
||||||
if (sel == wxNOT_FOUND) return;
|
if (sel == wxNOT_FOUND) return;
|
||||||
|
|
||||||
// Extract name from "Name (Location)" format
|
// Extract name from "Name (Location)" format
|
||||||
wxString fullText = m_itemList->GetString(sel);
|
const wxString fullText = m_itemList->GetString(sel);
|
||||||
wxString itemName = fullText.BeforeFirst('(').Trim();
|
const wxString itemName = fullText.BeforeFirst('(').Trim();
|
||||||
|
|
||||||
lua_State *L = wxGetApp().getLuaState();
|
lua_State *L = wxGetApp().getLuaState();
|
||||||
if (!L) return;
|
if (!L) return;
|
||||||
@@ -811,13 +816,13 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
|||||||
|
|
||||||
lua_getfield(L, -1, "Name");
|
lua_getfield(L, -1, "Name");
|
||||||
if (lua_isstring(L, -1)) {
|
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);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
if (currentName == itemName) {
|
if (currentName == itemName) {
|
||||||
// Get item description
|
// Get item description
|
||||||
lua_getfield(L, -1, "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);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// Get media if exists
|
// Get media if exists
|
||||||
@@ -836,7 +841,7 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
|||||||
lua_getfield(L, -1, "Commands");
|
lua_getfield(L, -1, "Commands");
|
||||||
wxArrayString commands;
|
wxArrayString commands;
|
||||||
if (lua_istable(L, -1)) {
|
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++) {
|
for (int i = 1; i <= n; i++) {
|
||||||
lua_rawgeti(L, -1, i);
|
lua_rawgeti(L, -1, i);
|
||||||
if (lua_istable(L, -1)) {
|
if (lua_istable(L, -1)) {
|
||||||
@@ -860,7 +865,7 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use WherigoMessageDialog to show with media support
|
// 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);
|
wherigo::WherigoMessageDialog dlg(this, message, itemName, buttons, mediaName);
|
||||||
dlg.ShowModal();
|
dlg.ShowModal();
|
||||||
|
|
||||||
@@ -893,8 +898,9 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cGameScreen::OnMapSim(wxCommandEvent&) {
|
void cGameScreen::OnMapSim(wxCommandEvent&) {
|
||||||
std::vector<std::pair<double, double>> zoneCoords;
|
std::vector<ZoneInfo> zoneInfos;
|
||||||
double lat = 53.3, lon = 10.39;
|
double lat = 53.3, lon = 10.39;
|
||||||
|
|
||||||
lua_State *L = wxGetApp().getLuaState();
|
lua_State *L = wxGetApp().getLuaState();
|
||||||
if (L) {
|
if (L) {
|
||||||
lua_getglobal(L, "_G");
|
lua_getglobal(L, "_G");
|
||||||
@@ -903,41 +909,46 @@ void cGameScreen::OnMapSim(wxCommandEvent&) {
|
|||||||
if (lua_istable(L, -1)) {
|
if (lua_istable(L, -1)) {
|
||||||
lua_getfield(L, -1, "ClassName");
|
lua_getfield(L, -1, "ClassName");
|
||||||
if (lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "Zone") == 0) {
|
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");
|
lua_getfield(L, -1, "Active");
|
||||||
bool active = lua_toboolean(L, -1) || true;
|
const bool active = lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
lua_getfield(L, -1, "Visible");
|
if (!active) { lua_pop(L, 1); continue; }
|
||||||
bool visible = lua_toboolean(L, -1) || true;
|
|
||||||
|
// Read name
|
||||||
|
lua_getfield(L, -1, "Name");
|
||||||
|
std::string zoneName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
if (active && visible) {
|
|
||||||
lua_getfield(L, -1, "OriginalPoint");
|
// Read OriginalPoint (set by Wherigo Builder on each zone)
|
||||||
if (lua_istable(L, -1)) {
|
lua_getfield(L, -1, "OriginalPoint");
|
||||||
lua_getfield(L, -1, "latitude");
|
if (lua_istable(L, -1)) {
|
||||||
lua_getfield(L, -2, "longitude");
|
lua_getfield(L, -1, "latitude");
|
||||||
if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
|
lua_getfield(L, -2, "longitude");
|
||||||
double zlat = lua_tonumber(L, -2);
|
if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
|
||||||
double zlon = lua_tonumber(L, -1);
|
zoneInfos.push_back({
|
||||||
zoneCoords.emplace_back(zlat, zlon);
|
lua_tonumber(L, -2),
|
||||||
}
|
lua_tonumber(L, -1),
|
||||||
lua_pop(L, 2);
|
zoneName
|
||||||
} else {
|
});
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
}
|
||||||
lua_pop(L, 1); // pop OriginalPoint
|
lua_pop(L, 2); // pop lat, lon
|
||||||
}
|
}
|
||||||
|
lua_pop(L, 1); // pop OriginalPoint (table or nil)
|
||||||
} else {
|
} 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;
|
if (!zoneInfos.empty()) {
|
||||||
lon = zoneCoords[0].second;
|
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();
|
mapSim->Show();
|
||||||
}
|
}
|
||||||
|
|||||||
+362
-62
@@ -1,79 +1,379 @@
|
|||||||
#include "ui/map_sim_frame.h"
|
#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/button.h>
|
||||||
#include <wx/webview.h>
|
#include <wx/log.h>
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <thread>
|
#include <wx/sizer.h>
|
||||||
#include <chrono>
|
#include <wx/webview.h>
|
||||||
|
|
||||||
|
enum { ID_PlayBtn = wxID_HIGHEST + 100, ID_ClearBtn, ID_SimTimer };
|
||||||
|
|
||||||
wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame)
|
wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame)
|
||||||
EVT_WEBVIEW_NAVIGATED(wxID_ANY, MapSimFrame::OnWebViewEvent)
|
EVT_BUTTON(ID_PlayBtn, MapSimFrame::OnPlay)
|
||||||
EVT_BUTTON(wxID_ANY, MapSimFrame::OnPlay)
|
EVT_BUTTON(ID_ClearBtn, MapSimFrame::OnClear)
|
||||||
wxEND_EVENT_TABLE()
|
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)
|
// Escape a string for safe embedding inside a JS single-quoted string
|
||||||
: wxFrame(parent, wxID_ANY, "Karten-Simulation", wxDefaultPosition, wxSize(900, 700)), m_zoneCoords(zoneCoords) {
|
static wxString jsEscape(const std::string &s) {
|
||||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
wxString out;
|
||||||
m_webView = wxWebView::New(this, wxID_ANY);
|
for (const unsigned char c : s) {
|
||||||
sizer->Add(m_webView, 1, wxEXPAND);
|
if (c == '\'')
|
||||||
auto* playBtn = new wxButton(this, wxID_ANY, "Simulation starten");
|
out += "\\'";
|
||||||
sizer->Add(playBtn, 0, wxALL | wxALIGN_CENTER, 8);
|
else if (c == '\\')
|
||||||
SetSizer(sizer);
|
out += "\\\\";
|
||||||
// Leaflet-Karte laden (dynamisch zentriert und Marker)
|
else if (c == '\n')
|
||||||
wxString html;
|
out += "\\n";
|
||||||
html << "<!DOCTYPE html><html><head><meta charset='utf-8'><title>MapSim</title>"
|
else if (c == '\r')
|
||||||
"<link rel='stylesheet' href='https://unpkg.com/leaflet/dist/leaflet.css'/>"
|
out += "\\r";
|
||||||
"<script src='https://unpkg.com/leaflet/dist/leaflet.js'></script>"
|
else
|
||||||
"<style>html,body,#map{height:100%%;margin:0;padding:0;}#map{height:600px;}</style>"
|
out += static_cast<char>(c);
|
||||||
"</head><body><div id='map'></div>"
|
}
|
||||||
"<script>"
|
return out;
|
||||||
"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, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapSimFrame::OnWebViewEvent(wxWebViewEvent& event) {
|
MapSimFrame::MapSimFrame(wxWindow *parent, double centerLat, double centerLon,
|
||||||
// Empfange Marker-Koordinaten von JS
|
const std::vector<ZoneInfo> &zoneInfos)
|
||||||
wxString msg = event.GetString();
|
: wxFrame(parent, wxID_ANY, "GPS-Simulation", wxDefaultPosition,
|
||||||
double lat = 0, lon = 0;
|
wxSize(900, 700)),
|
||||||
if (msg.ToDouble(&lat)) {
|
m_simTimer(this, ID_SimTimer), m_zoneInfos(zoneInfos) {
|
||||||
// Not used, see below
|
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
}
|
|
||||||
// In wxWidgets 3.1+ kann man wxWebView::RegisterHandler für JS->C++ nutzen
|
m_webView = wxWebView::New(this, wxID_ANY);
|
||||||
// Hier: Marker werden über postMessage als JSON gesendet
|
sizer->Add(m_webView, 1, wxEXPAND);
|
||||||
// TODO: JSON parsen und AddSimPoint aufrufen
|
|
||||||
|
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) {
|
void MapSimFrame::AddSimPoint(double lat, double lon) {
|
||||||
m_route.push_back({lat, lon});
|
m_route.push_back({lat, lon});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapSimFrame::OnPlay(wxCommandEvent&) {
|
void MapSimFrame::StartSimulation() {
|
||||||
if (m_route.empty()) {
|
if (m_route.empty())
|
||||||
wxMessageBox("Bitte zuerst Marker setzen.", "Hinweis", wxOK | wxICON_INFORMATION);
|
return;
|
||||||
return;
|
m_simIndex = 0;
|
||||||
}
|
m_simTimer.Start(500);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 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) {
|
void MapSimFrame::SendPositionToEngine(double lat, double lon) {
|
||||||
// TODO: Engine-Integration: GPS-Position setzen
|
wxLogDebug("MapSim: position → engine (%.6f, %.6f)", lat, lon);
|
||||||
// Beispiel: wxGetApp().setSimulatedPosition(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 "ui/game_screen.h"
|
||||||
|
|
||||||
#include <wx/filedlg.h>
|
#include <wx/filedlg.h>
|
||||||
#include <wx/mstream.h>
|
|
||||||
#include <wx/image.h>
|
#include <wx/image.h>
|
||||||
|
#include <wx/mstream.h>
|
||||||
|
|
||||||
wxDECLARE_APP(cApp);
|
wxDECLARE_APP(cApp);
|
||||||
|
|
||||||
enum {
|
enum { ID_OpenCartridge = 1001, ID_StartGame = 1002 };
|
||||||
ID_OpenCartridge = 1001,
|
|
||||||
ID_StartGame = 1002
|
|
||||||
};
|
|
||||||
|
|
||||||
cStartScreen::cStartScreen()
|
cStartScreen::cStartScreen()
|
||||||
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 800)),
|
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition,
|
||||||
m_gameFrame(nullptr),
|
wxSize(800, 800)),
|
||||||
m_cartridgeLoaded(false) {
|
m_gameFrame(nullptr), m_cartridgeLoaded(false) {
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
auto *menuFile = new wxMenu;
|
auto *menuFile = new wxMenu;
|
||||||
@@ -31,7 +28,7 @@ cStartScreen::cStartScreen()
|
|||||||
auto *menuBar = new wxMenuBar;
|
auto *menuBar = new wxMenuBar;
|
||||||
menuBar->Append(menuFile, "&Datei");
|
menuBar->Append(menuFile, "&Datei");
|
||||||
menuBar->Append(menuHelp, "&Hilfe");
|
menuBar->Append(menuHelp, "&Hilfe");
|
||||||
SetMenuBar(menuBar);
|
wxFrameBase::SetMenuBar(menuBar);
|
||||||
|
|
||||||
// Main sizer
|
// Main sizer
|
||||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||||
@@ -64,7 +61,8 @@ cStartScreen::cStartScreen()
|
|||||||
infoSizer->Add(m_cartridgeAuthor, 0, wxALL | wxALIGN_CENTER, 5);
|
infoSizer->Add(m_cartridgeAuthor, 0, wxALL | wxALIGN_CENTER, 5);
|
||||||
|
|
||||||
// Description
|
// 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));
|
m_cartridgeDesc->SetMinSize(wxSize(500, 200));
|
||||||
infoSizer->Add(m_cartridgeDesc, 1, wxALL | wxEXPAND, 10);
|
infoSizer->Add(m_cartridgeDesc, 1, wxALL | wxEXPAND, 10);
|
||||||
|
|
||||||
@@ -82,8 +80,8 @@ cStartScreen::cStartScreen()
|
|||||||
SetSizer(mainSizer);
|
SetSizer(mainSizer);
|
||||||
|
|
||||||
// Status bar
|
// Status bar
|
||||||
CreateStatusBar();
|
wxFrameBase::CreateStatusBar();
|
||||||
SetStatusText("Bitte eine Cartridge öffnen");
|
wxFrameBase::SetStatusText("Bitte eine Cartridge öffnen");
|
||||||
|
|
||||||
// Event bindings
|
// Event bindings
|
||||||
Bind(wxEVT_MENU, &cStartScreen::OnOpenCartridge, this, ID_OpenCartridge);
|
Bind(wxEVT_MENU, &cStartScreen::OnOpenCartridge, this, ID_OpenCartridge);
|
||||||
@@ -95,7 +93,7 @@ cStartScreen::cStartScreen()
|
|||||||
CentreOnScreen();
|
CentreOnScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
void cStartScreen::OnOpenCartridge(wxCommandEvent &event) {
|
||||||
wxFileDialog openFileDialog(this, "Cartridge öffnen", "", "",
|
wxFileDialog openFileDialog(this, "Cartridge öffnen", "", "",
|
||||||
"Wherigo Cartridge (*.gwc)|*.gwc",
|
"Wherigo Cartridge (*.gwc)|*.gwc",
|
||||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||||
@@ -104,15 +102,19 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString filePath = openFileDialog.GetPath();
|
const wxString filePath = openFileDialog.GetPath();
|
||||||
SetStatusText("Lade Cartridge: " + filePath);
|
wxFrameBase::SetStatusText("Lade Cartridge: " + filePath);
|
||||||
|
|
||||||
// Vor dem Laden: Info-Panel zurücksetzen
|
// Vor dem Laden: Info-Panel zurücksetzen
|
||||||
if (m_infoPanel) {
|
if (m_infoPanel) {
|
||||||
if (m_cartridgeName) m_cartridgeName->SetLabel("");
|
if (m_cartridgeName)
|
||||||
if (m_cartridgeAuthor) m_cartridgeAuthor->SetLabel("");
|
m_cartridgeName->SetLabel("");
|
||||||
if (m_cartridgeDesc) m_cartridgeDesc->SetPage("");
|
if (m_cartridgeAuthor)
|
||||||
if (m_splashImage) m_splashImage->SetBitmap(wxNullBitmap);
|
m_cartridgeAuthor->SetLabel("");
|
||||||
|
if (m_cartridgeDesc)
|
||||||
|
m_cartridgeDesc->SetPage("");
|
||||||
|
if (m_splashImage)
|
||||||
|
m_splashImage->SetBitmap(wxNullBitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ggf. alten GameScreen schließen
|
// ggf. alten GameScreen schließen
|
||||||
@@ -125,16 +127,18 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
|||||||
if (wxGetApp().loadCartridge(filePath.ToStdString())) {
|
if (wxGetApp().loadCartridge(filePath.ToStdString())) {
|
||||||
m_cartridgeLoaded = true;
|
m_cartridgeLoaded = true;
|
||||||
showCartridgeInfo();
|
showCartridgeInfo();
|
||||||
SetStatusText("Cartridge geladen - bereit zum Starten");
|
wxFrameBase::SetStatusText("Cartridge geladen - bereit zum Starten");
|
||||||
} else {
|
} else {
|
||||||
wxMessageBox("Fehler beim Laden der Cartridge", "Fehler", wxOK | wxICON_ERROR);
|
wxMessageBox("Fehler beim Laden der Cartridge", "Fehler",
|
||||||
SetStatusText("Fehler beim Laden");
|
wxOK | wxICON_ERROR);
|
||||||
|
wxFrameBase::SetStatusText("Fehler beim Laden");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cStartScreen::showCartridgeInfo() {
|
void cStartScreen::showCartridgeInfo() {
|
||||||
auto *cartridge = wxGetApp().getCartridge();
|
auto *cartridge = wxGetApp().getCartridge();
|
||||||
if (!cartridge) return;
|
if (!cartridge)
|
||||||
|
return;
|
||||||
|
|
||||||
// Set cartridge info
|
// Set cartridge info
|
||||||
m_cartridgeName->SetLabel(wxString::FromUTF8(cartridge->cartridgeName()));
|
m_cartridgeName->SetLabel(wxString::FromUTF8(cartridge->cartridgeName()));
|
||||||
@@ -149,21 +153,21 @@ void cStartScreen::showCartridgeInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load splash screen
|
// Load splash screen
|
||||||
auto splash = cartridge->splashScreen();
|
if (const auto splash = cartridge->splashScreen()) {
|
||||||
if (splash) {
|
const auto data = splash->getData();
|
||||||
auto data = splash->getData();
|
|
||||||
if (!data.empty()) {
|
if (!data.empty()) {
|
||||||
wxMemoryInputStream stream(data.data(), data.size());
|
wxMemoryInputStream stream(data.data(), data.size());
|
||||||
wxImage image(stream);
|
if (wxImage image(stream); image.IsOk()) {
|
||||||
if (image.IsOk()) {
|
|
||||||
// Scale if too large
|
// Scale if too large
|
||||||
int maxWidth = 300;
|
const int maxHeight = 200;
|
||||||
int maxHeight = 200;
|
if (int maxWidth = 300;
|
||||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||||
double scaleX = (double)maxWidth / image.GetWidth();
|
double scaleX = static_cast<double>(maxWidth) / image.GetWidth();
|
||||||
double scaleY = (double)maxHeight / image.GetHeight();
|
double scaleY = static_cast<double>(maxHeight) / image.GetHeight();
|
||||||
double scale = std::min(scaleX, scaleY);
|
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));
|
m_splashImage->SetBitmap(wxBitmap(image));
|
||||||
}
|
}
|
||||||
@@ -176,15 +180,18 @@ void cStartScreen::showCartridgeInfo() {
|
|||||||
Layout();
|
Layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cStartScreen::OnStartGame(wxCommandEvent& event) {
|
void cStartScreen::OnStartGame(wxCommandEvent &event) {
|
||||||
if (!m_cartridgeLoaded) {
|
if (!m_cartridgeLoaded) {
|
||||||
wxMessageBox("Bitte zuerst eine Cartridge laden", "Hinweis", wxOK | wxICON_INFORMATION);
|
wxMessageBox("Bitte zuerst eine Cartridge laden", "Hinweis",
|
||||||
|
wxOK | wxICON_INFORMATION);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create game frame if not exists
|
// Create game frame if not exists
|
||||||
if (!m_gameFrame) {
|
if (!m_gameFrame) {
|
||||||
m_gameFrame = new cGameScreen(this);
|
m_gameFrame = new cGameScreen(this);
|
||||||
|
m_gameFrame->SetPosition(GetPosition());
|
||||||
|
m_gameFrame->SetSize(GetSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide start frame, show game
|
// Hide start frame, show game
|
||||||
@@ -198,10 +205,10 @@ void cStartScreen::OnStartGame(wxCommandEvent& event) {
|
|||||||
void cStartScreen::onGameClosed() {
|
void cStartScreen::onGameClosed() {
|
||||||
// Called when game frame is closed
|
// Called when game frame is closed
|
||||||
Show();
|
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
|
// Clean up game frame
|
||||||
if (m_gameFrame) {
|
if (m_gameFrame) {
|
||||||
m_gameFrame->Destroy();
|
m_gameFrame->Destroy();
|
||||||
@@ -210,7 +217,8 @@ void cStartScreen::OnExit(wxCommandEvent& event) {
|
|||||||
Close(true);
|
Close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cStartScreen::OnAbout(wxCommandEvent& event) {
|
void cStartScreen::OnAbout(wxCommandEvent &event) {
|
||||||
wxMessageBox("Wherigo Player\n\nEin Desktop-Player für Wherigo-Cartridges\n\nVersion 0.1",
|
wxMessageBox("Wherigo Player\n\nEin Desktop-Player für "
|
||||||
|
"Wherigo-Cartridges\n\nVersion 0.1",
|
||||||
"Über Wherigo Player", wxOK | wxICON_INFORMATION);
|
"Über Wherigo Player", wxOK | wxICON_INFORMATION);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
#include "ui/wherigo_dialog.h"
|
#include "ui/wherigo_dialog.h"
|
||||||
#include "lua/media_manager.h"
|
|
||||||
#include "lua/game_engine.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/button.h>
|
||||||
|
#include <wx/html/htmlwin.h>
|
||||||
|
#include <wx/image.h>
|
||||||
#include <wx/log.h>
|
#include <wx/log.h>
|
||||||
#include <wx/mstream.h>
|
#include <wx/mstream.h>
|
||||||
#include <wx/image.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/html/htmlwin.h>
|
#include <wx/statbmp.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
namespace wherigo {
|
namespace wherigo {
|
||||||
|
|
||||||
WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &text,
|
WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent,
|
||||||
|
const wxString &text,
|
||||||
const wxString &title,
|
const wxString &title,
|
||||||
const std::vector<wxString> &buttons,
|
const std::vector<wxString> &buttons,
|
||||||
const wxString &mediaName)
|
const wxString &mediaName)
|
||||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
|
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
|
||||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ) {
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
|
||||||
|
|
||||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
// Media/Image (if provided)
|
// Media/Image (if provided)
|
||||||
if (!mediaName.IsEmpty()) {
|
if (!mediaName.IsEmpty()) {
|
||||||
auto mediaData = MediaManager::getInstance().getMediaByName(mediaName.ToStdString());
|
const auto mediaData =
|
||||||
|
MediaManager::getInstance().getMediaByName(mediaName.ToStdString());
|
||||||
|
|
||||||
if (!mediaData.empty()) {
|
if (!mediaData.empty()) {
|
||||||
wxMemoryInputStream stream(mediaData.data(), mediaData.size());
|
wxMemoryInputStream stream(mediaData.data(), mediaData.size());
|
||||||
wxImage image(stream);
|
|
||||||
|
|
||||||
if (image.IsOk()) {
|
if (wxImage image(stream); image.IsOk()) {
|
||||||
// Get screen DPI for scaling
|
// Get screen DPI for scaling
|
||||||
wxWindow* topWindow = wxTheApp->GetTopWindow();
|
const wxWindow *topWindow = wxTheApp->GetTopWindow();
|
||||||
double contentScaleFactor = 1.0;
|
double contentScaleFactor = 1.0;
|
||||||
if (topWindow) {
|
if (topWindow) {
|
||||||
contentScaleFactor = topWindow->GetContentScaleFactor();
|
contentScaleFactor = topWindow->GetContentScaleFactor();
|
||||||
@@ -40,14 +41,18 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
|||||||
|
|
||||||
// Scale image - doubled size from original (800x600 instead of 400x300)
|
// Scale image - doubled size from original (800x600 instead of 400x300)
|
||||||
// Adjust for DPI/Retina displays
|
// Adjust for DPI/Retina displays
|
||||||
int maxWidth = static_cast<int>(800 * contentScaleFactor);
|
const int maxWidth = static_cast<int>(800 * contentScaleFactor);
|
||||||
int maxHeight = static_cast<int>(600 * contentScaleFactor);
|
|
||||||
|
|
||||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
if (const int maxHeight = static_cast<int>(600 * contentScaleFactor);
|
||||||
double scaleX = (double)maxWidth / image.GetWidth();
|
image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||||
double scaleY = (double)maxHeight / image.GetHeight();
|
const double scaleX =
|
||||||
double scale = std::min(scaleX, scaleY);
|
static_cast<double>(maxWidth) / image.GetWidth();
|
||||||
image.Rescale(image.GetWidth() * scale, image.GetHeight() * scale, wxIMAGE_QUALITY_HIGH);
|
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);
|
wxBitmap bitmap(image);
|
||||||
@@ -55,22 +60,22 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
|||||||
sizer->Add(imageCtrl, 0, wxALL | wxALIGN_CENTER, 10);
|
sizer->Add(imageCtrl, 0, wxALL | wxALIGN_CENTER, 10);
|
||||||
} else {
|
} else {
|
||||||
wxLogDebug("Failed to load image: %s", mediaName);
|
wxLogDebug("Failed to load image: %s", mediaName);
|
||||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
auto *mediaLabel = new wxStaticText(
|
||||||
wxString::Format("[Media: %s]", mediaName));
|
this, wxID_ANY, wxString::Format("[Media: %s]", mediaName));
|
||||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wxLogDebug("No media data for: %s", mediaName);
|
wxLogDebug("No media data for: %s", mediaName);
|
||||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
auto *mediaLabel = new wxStaticText(
|
||||||
wxString::Format("[Media: %s]", mediaName));
|
this, wxID_ANY, wxString::Format("[Media: %s]", mediaName));
|
||||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper lambda to convert plain text to HTML
|
// Helper lambda to convert plain text to HTML
|
||||||
auto textToHtml = [](const wxString& plainText) -> wxString {
|
auto textToHtml = [](const wxString &plainText) -> wxString {
|
||||||
wxString result = plainText;
|
wxString result = plainText;
|
||||||
// Check if this looks like HTML (has tags)
|
// Check if this looks like HTML (has tags)
|
||||||
if (plainText.Find("<") == wxNOT_FOUND) {
|
if (plainText.Find("<") == wxNOT_FOUND) {
|
||||||
@@ -104,29 +109,34 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
|||||||
wxColour fgColor = GetForegroundColour();
|
wxColour fgColor = GetForegroundColour();
|
||||||
|
|
||||||
// Helper lambda to create and configure HTML window with auto-sizing
|
// 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);
|
auto *htmlCtrl = new wxHtmlWindow(this, wxID_ANY);
|
||||||
htmlCtrl->SetBorders(0);
|
htmlCtrl->SetBorders(0);
|
||||||
|
|
||||||
wxString htmlContent = wxString::Format(
|
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>",
|
"<html><body style=\"margin:0; padding:5px; "
|
||||||
bgColor.Red(), bgColor.Green(), bgColor.Blue(),
|
"background-color:#%02x%02x%02x; color:#%02x%02x%02x; "
|
||||||
fgColor.Red(), fgColor.Green(), fgColor.Blue(),
|
"font-family:sans-serif; font-size:11pt; line-height:1.5; "
|
||||||
textToHtml(text)
|
"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->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
|
// Set a reasonable fixed size - will expand based on content
|
||||||
// Width: fixed at 600px for consistent layout
|
// Width: fixed at 600px for consistent layout
|
||||||
// Height: let wxHtmlWindow calculate based on content, with limits
|
// 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)
|
// Calculate approximate height based on line count (rough estimate)
|
||||||
int lineCount = 1;
|
int lineCount = 1;
|
||||||
for (size_t i = 0; i < text.length(); i++) {
|
for (size_t i = 0; i < message.length(); i++) {
|
||||||
if (text[i] == '\n') lineCount++;
|
if (message[i] == '\n')
|
||||||
|
lineCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estimate: ~20px per line + 40px padding
|
// Estimate: ~20px per line + 40px padding
|
||||||
@@ -182,17 +192,16 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
|||||||
wxSize minSize = sizer->GetMinSize();
|
wxSize minSize = sizer->GetMinSize();
|
||||||
|
|
||||||
// Set reasonable dialog size
|
// Set reasonable dialog size
|
||||||
int dialogWidth = std::clamp(minSize.GetWidth() + 40, 500, 800);
|
const int dialogWidth = std::clamp(minSize.GetWidth() + 40, 500, 800);
|
||||||
int dialogHeight = std::clamp(minSize.GetHeight() + 40, 300, 700);
|
const int dialogHeight = std::clamp(minSize.GetHeight() + 40, 300, 700);
|
||||||
|
|
||||||
SetSize(dialogWidth, dialogHeight);
|
SetSize(dialogWidth, dialogHeight);
|
||||||
SetMinSize(wxSize(400, 250));
|
wxTopLevelWindowBase::SetMinSize(wxSize(400, 250));
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WherigoMessageDialog::onButton(wxCommandEvent &event) {
|
void WherigoMessageDialog::onButton(const wxCommandEvent &event) {
|
||||||
int id = event.GetId();
|
if (const int id = event.GetId(); id == wxID_OK) {
|
||||||
if (id == wxID_OK) {
|
|
||||||
m_selectedButton = 0;
|
m_selectedButton = 0;
|
||||||
} else {
|
} else {
|
||||||
m_selectedButton = id - 1000;
|
m_selectedButton = id - 1000;
|
||||||
@@ -200,13 +209,14 @@ void WherigoMessageDialog::onButton(wxCommandEvent &event) {
|
|||||||
EndModal(wxID_OK);
|
EndModal(wxID_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
WherigoDialogRunner& WherigoDialogRunner::getInstance() {
|
WherigoDialogRunner &WherigoDialogRunner::getInstance() {
|
||||||
static WherigoDialogRunner instance;
|
static WherigoDialogRunner instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WherigoDialogRunner::showMessageBox(const wxString &text, const wxString &title,
|
void WherigoDialogRunner::showMessageBox(const wxString &text,
|
||||||
std::function<void(int)> callback) {
|
const wxString &title,
|
||||||
|
std::function<void(int)> callback) {
|
||||||
WherigoMessageDialog dlg(nullptr, text, title);
|
WherigoMessageDialog dlg(nullptr, text, title);
|
||||||
dlg.ShowModal();
|
dlg.ShowModal();
|
||||||
|
|
||||||
@@ -216,27 +226,26 @@ void WherigoDialogRunner::showMessageBox(const wxString &text, const wxString &t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
|
void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
|
||||||
std::function<void(int)> callback) {
|
std::function<void(int)> callback) {
|
||||||
for (size_t i = 0; i < entries.size(); i++) {
|
for (size_t i = 0; i < entries.size(); ++i) {
|
||||||
const auto &entry = entries[i];
|
const auto &entry = entries[i];
|
||||||
|
|
||||||
std::vector<wxString> buttons;
|
std::vector<wxString> buttons;
|
||||||
|
buttons.reserve(entry.buttons.size() + 1);
|
||||||
for (const auto &btn : entry.buttons) {
|
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) {
|
if (buttons.empty()) {
|
||||||
buttons.push_back("Weiter");
|
buttons.emplace_back(i + 1 < entries.size() ? "Weiter" : "OK");
|
||||||
} else if (buttons.empty()) {
|
|
||||||
buttons.push_back("OK");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(entry.text), "Wherigo",
|
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(entry.text), "Wherigo",
|
||||||
buttons, wxString::FromUTF8(entry.mediaName));
|
buttons, wxString::FromUTF8(entry.mediaName));
|
||||||
dlg.ShowModal();
|
dlg.ShowModal();
|
||||||
|
|
||||||
// For the last entry, call the callback with the selected button
|
// 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());
|
callback(dlg.getSelectedButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user