latest code update

- app icon
- starting with map view
- code cleanup

Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
2026-02-14 09:43:19 +01:00
parent b7bee804ca
commit 6e29dde558
20 changed files with 639 additions and 170 deletions

2
.gitignore vendored
View File

@@ -111,3 +111,5 @@ CMakeUserPresets.json
# End of https://www.toptal.com/developers/gitignore/api/cmake,clion+all # End of https://www.toptal.com/developers/gitignore/api/cmake,clion+all
cartridges/ cartridges/
assets/icon.iconset/
*.lua

View File

@@ -27,21 +27,25 @@ FetchContent_MakeAvailable(wxWidgets)
message(STATUS "Configure project....") message(STATUS "Configure project....")
set(SRCS set(SRCS
main/src/main.cpp
main/src/app.cpp main/src/app.cpp
main/src/ui/start_screen.cpp main/src/main.cpp
main/src/ui/game_screen.cpp main/src/ui/game_screen.cpp
main/src/ui/map_sim_frame.cpp
main/src/ui/start_screen.cpp
main/src/ui/wherigo_dialog.cpp main/src/ui/wherigo_dialog.cpp
main/src/lua/wherigo.cpp
main/src/lua/zobject.cpp
main/src/lua/ztimer.cpp
main/src/lua/game_engine.cpp main/src/lua/game_engine.cpp
main/src/lua/media_manager.cpp main/src/lua/media_manager.cpp
main/src/lua/persistence.cpp main/src/lua/persistence.cpp
main/src/lua/wherigo.cpp
main/src/lua/wherigo_completion.cpp main/src/lua/wherigo_completion.cpp
main/src/lua/zobject.cpp
main/src/lua/ztimer.cpp
) )
if (APPLE) if (APPLE)
configure_file(${CMAKE_SOURCE_DIR}/assets/Info.plist.in
${CMAKE_BINARY_DIR}/Info.plist @ONLY)
set(MACOSX_BUNDLE_ICON_FILE icon.icns)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SRCS}) add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SRCS})
set_target_properties(${PROJECT_NAME} PROPERTIES set_target_properties(${PROJECT_NAME} PROPERTIES
BUNDLE True BUNDLE True
@@ -49,7 +53,8 @@ if (APPLE)
MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME} MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}
MACOSX_BUNDLE_BUNDLE_VERSION "0.1" MACOSX_BUNDLE_BUNDLE_VERSION "0.1"
MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1" MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1"
#MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/customtemplate.plist.in MACOSX_BUNDLE_ICON_FILE icon.icns
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_BINARY_DIR}/Info.plist
INSTALL_RPATH "@executable_path" INSTALL_RPATH "@executable_path"
) )
else () else ()
@@ -61,14 +66,14 @@ include_directories(main/include)
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
PRIVATE PRIVATE
wxcore wx::net
wxnet wx::html
wxbase wx::webview
wxhtml
lua lua
cartridge cartridge
storage storage
) )
target_include_directories(${PROJECT_NAME} PRIVATE ${wxWidgets_INCLUDE_DIRS})
# Kopiere die .dylib-Dateien ins Bundle # Kopiere die .dylib-Dateien ins Bundle
if (APPLE) if (APPLE)
@@ -78,5 +83,11 @@ if (APPLE)
$<TARGET_FILE:cartridge> $<TARGET_FILE:cartridge>
$<TARGET_FILE:storage> $<TARGET_FILE:storage>
"$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/MacOS/" "$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/MacOS/"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/assets/icon.icns
"$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Resources/icon.icns"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_BINARY_DIR}/Info.plist
"$<TARGET_BUNDLE_DIR:${PROJECT_NAME}>/Contents/Info.plist"
) )
endif () endif ()

22
assets/Info.plist.in Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIconFile</key>
<string>icon.icns</string>
<key>CFBundleName</key>
<string>@CMAKE_PROJECT_NAME@</string>
<key>CFBundleIdentifier</key>
<string>dev.mars3142.@CMAKE_PROJECT_NAME@</string>
<key>CFBundleVersion</key>
<string>@PROJECT_VERSION@</string>
<key>CFBundleShortVersionString</key>
<string>@PROJECT_VERSION@</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
</dict>
</plist>

BIN
assets/icon.icns Normal file

Binary file not shown.

BIN
assets/icon.png LFS Normal file

Binary file not shown.

25
assets/make_icns.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
ICONSET="icon.iconset"
PNG="$1"
if [ -z "$PNG" ]; then
echo "Usage: $0 icon.png"
exit 1
fi
mkdir -p $ICONSET
# Erzeuge alle Größen
sips -z 16 16 "$PNG" --out $ICONSET/icon_16x16.png
sips -z 32 32 "$PNG" --out $ICONSET/icon_16x16@2x.png
sips -z 32 32 "$PNG" --out $ICONSET/icon_32x32.png
sips -z 64 64 "$PNG" --out $ICONSET/icon_32x32@2x.png
sips -z 128 128 "$PNG" --out $ICONSET/icon_128x128.png
sips -z 256 256 "$PNG" --out $ICONSET/icon_128x128@2x.png
sips -z 256 256 "$PNG" --out $ICONSET/icon_256x256.png
sips -z 512 512 "$PNG" --out $ICONSET/icon_256x256@2x.png
sips -z 512 512 "$PNG" --out $ICONSET/icon_512x512.png
sips -z 1024 1024 "$PNG" --out $ICONSET/icon_512x512@2x.png
iconutil -c icns icon.iconset

View File

@@ -23,6 +23,4 @@ std::unique_ptr<Cartridge> parseData(const std::vector<uint8_t>& bytes);
std::unique_ptr<Cartridge> parseFile(const std::string& filePath); std::unique_ptr<Cartridge> parseFile(const std::string& filePath);
std::unique_ptr<Cartridge> parseCartridge();
} // namespace cartridge } // namespace cartridge

View File

@@ -57,45 +57,4 @@ std::unique_ptr<Cartridge> parseFile(const std::string &filePath) {
return parseData(*result); return parseData(*result);
} }
std::unique_ptr<Cartridge> parseCartridge() {
auto cartridge = parseFile("/Volumes/Coding/git.mars3142.dev/mars3142/"
"wx_wherigo/cartridges/the_ombos_idol_-_c.gwc");
if (!cartridge) {
std::println(std::cerr, "Cartridge konnte nicht geladen werden.");
return nullptr;
}
const int count = cartridge->mediaCount();
const std::filesystem::path outDir =
"/Volumes/Coding/git.mars3142.dev/mars3142/wx_wherigo/cartridges";
storage::Storage storage;
for (const int i : std::views::iota(0, count)) {
auto media = cartridge->getMedia(i);
if (!media) {
std::println(std::cerr, "Media-Objekt an Index {} ist nullptr.", i);
continue;
}
const auto &data = media->getData();
if (data.empty()) {
std::println(std::cerr, "Media-Daten an Index {} sind leer.", i);
continue;
}
std::string ext = media->getObjectType();
if (ext.empty()) {
std::println(std::cerr, "Media-Extension an Index {} ist leer.", i);
ext = "bin";
}
const std::filesystem::path outFile = outDir / std::format("file{}.{}", i, ext);
if (auto writeResult = storage.writeFile(outFile.string(), data); !writeResult) {
std::println(std::cerr, "Fehler beim Schreiben der Datei: {}", outFile.string());
}
}
return cartridge;
}
} // namespace cartridge } // namespace cartridge

View File

@@ -32,6 +32,7 @@ public:
int getCartridgeRef() const { return m_cartridgeRef; } int getCartridgeRef() const { return m_cartridgeRef; }
cartridge::Cartridge* getCartridge() const { return m_cartridge.get(); } cartridge::Cartridge* getCartridge() const { return m_cartridge.get(); }
bool isCartridgeLoaded() const { return m_cartridge != nullptr; } bool isCartridgeLoaded() const { return m_cartridge != nullptr; }
std::string getCartridgePath() const { return m_cartridgePath; }
private: private:
bool initLuaState(); bool initLuaState();

View File

@@ -3,6 +3,7 @@
#include <wx/wx.h> #include <wx/wx.h>
#include <wx/listbox.h> #include <wx/listbox.h>
#include <wx/notebook.h> #include <wx/notebook.h>
#include "ui/map_sim_frame.h"
class cGameScreen : public wxFrame class cGameScreen : public wxFrame
{ {
@@ -24,6 +25,7 @@ private:
void OnInventorySelected(wxCommandEvent& event); void OnInventorySelected(wxCommandEvent& event);
void OnCharacterSelected(wxCommandEvent& event); void OnCharacterSelected(wxCommandEvent& event);
void OnItemSelected(wxCommandEvent& event); void OnItemSelected(wxCommandEvent& event);
void OnMapSim(wxCommandEvent& event);
void populateZones(); void populateZones();
void populateTasks(); void populateTasks();

View File

@@ -0,0 +1,25 @@
#pragma once
#include <wx/frame.h>
#include <wx/webview.h>
#include <vector>
#include <utility>
struct SimPoint {
double lat;
double lon;
};
class MapSimFrame : public wxFrame {
public:
MapSimFrame(wxWindow* parent, double centerLat = 53.3, double centerLon = 10.39, const std::vector<std::pair<double, double>>& zoneCoords = {});
void AddSimPoint(double lat, double lon);
void StartSimulation();
private:
wxWebView* m_webView;
std::vector<SimPoint> m_route;
std::vector<std::pair<double, double>> m_zoneCoords;
void OnWebViewEvent(wxWebViewEvent& event);
void OnPlay(wxCommandEvent& event);
void SendPositionToEngine(double lat, double lon);
wxDECLARE_EVENT_TABLE();
};

View File

@@ -3,6 +3,7 @@
#include <wx/wx.h> #include <wx/wx.h>
#include <wx/statbmp.h> #include <wx/statbmp.h>
#include <wx/html/htmlwin.h> #include <wx/html/htmlwin.h>
#include "ui/map_sim_frame.h"
class cGameScreen; class cGameScreen;

View File

@@ -19,7 +19,6 @@ extern "C" {
bool cApp::OnInit() bool cApp::OnInit()
{ {
// Initialize image handlers for JPEG, PNG, GIF, etc. // Initialize image handlers for JPEG, PNG, GIF, etc.
wxInitAllImageHandlers(); wxInitAllImageHandlers();

View File

@@ -9,6 +9,7 @@ extern "C" {
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <set> #include <set>
#include <cstdio>
namespace wherigo { namespace wherigo {
@@ -82,10 +83,42 @@ void LuaPersistence::serializeValue(lua_State* L, int index, std::string& output
case LUA_TSTRING: { case LUA_TSTRING: {
const char* str = lua_tostring(L, index); const char* str = lua_tostring(L, index);
output += "\""; output += "\"";
// Escape special characters // Properly escape all special characters for Lua strings
for (const char* p = str; *p; ++p) { for (const char* p = str; *p; ++p) {
if (*p == '"' || *p == '\\') output += '\\'; unsigned char c = (unsigned char)*p;
output += *p; switch (c) {
case '"':
output += "\\\"";
break;
case '\\':
output += "\\\\";
break;
case '\n':
output += "\\n";
break;
case '\r':
output += "\\r";
break;
case '\t':
output += "\\t";
break;
case '\b':
output += "\\b";
break;
case '\f':
output += "\\f";
break;
default:
// For control characters and high bytes, use decimal escape
if (c < 32 || c >= 127) {
char hex[6];
snprintf(hex, sizeof(hex), "\\%d", c);
output += hex;
} else {
output += c;
}
break;
}
} }
output += "\""; output += "\"";
break; break;
@@ -199,23 +232,120 @@ bool LuaPersistence::loadGlobals(lua_State* L, const std::string& filePath) {
return false; return false;
} }
// Restore globals // Restore globals - but for tables (Wherigo objects), merge instead of replace
// Strategy: For each saved object, find the matching existing object and merge
int count = 0; int count = 0;
lua_pushnil(L); lua_pushnil(L);
while (lua_next(L, -2) != 0) { while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2)) { if (lua_isstring(L, -2) && lua_istable(L, -1)) {
const char* key = lua_tostring(L, -2); const char* savedKey = lua_tostring(L, -2);
wxLogDebug("Restoring global: %s", savedKey);
int stackBefore = lua_gettop(L);
// Set the global // Get the Name field of the saved object (if it exists)
lua_pushvalue(L, -1); // duplicate value lua_getfield(L, -1, "Name");
lua_setglobal(L, key); std::string savedName;
if (lua_isstring(L, -1)) {
savedName = lua_tostring(L, -1);
}
lua_pop(L, 1);
// Try to find matching existing object
bool merged = false;
if (!savedName.empty()) {
// Search for existing object with same Name
lua_getglobal(L, "_G");
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "Name");
if (lua_isstring(L, -1)) {
if (savedName == lua_tostring(L, -1)) {
lua_pop(L, 1); // pop Name
// Found matching object! Merge data
lua_pushnil(L);
while (lua_next(L, -3) != 0) {
if (lua_isstring(L, -2)) {
const char* field = lua_tostring(L, -2);
if (!lua_isfunction(L, -1)) {
lua_pushvalue(L, -1);
lua_setfield(L, -4, field);
}
}
lua_pop(L, 1);
}
merged = true;
lua_pop(L, 2); // pop iterator and object
break;
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lua_pop(L, 1); // pop _G
}
if (!merged) {
// No match found or no Name field - use key matching
lua_getglobal(L, savedKey);
if (lua_istable(L, -1)) {
// Object exists by key - merge it
lua_pushnil(L);
while (lua_next(L, -3) != 0) {
if (lua_isstring(L, -2)) {
const char* field = lua_tostring(L, -2);
if (!lua_isfunction(L, -1)) {
lua_getfield(L, -3, field); // get existing value
if (lua_istable(L, -1) && lua_istable(L, -2)) {
// Both are tables: merge recursively
lua_pushnil(L);
while (lua_next(L, -3) != 0) {
if (lua_isstring(L, -2)) {
const char* subfield = lua_tostring(L, -2);
if (!lua_isfunction(L, -1)) {
lua_pushvalue(L, -1);
lua_setfield(L, -5, subfield); // merge into existing table
}
}
lua_pop(L, 1);
}
lua_pop(L, 1); // pop existing value
} else {
lua_pop(L, 1); // pop existing value
lua_pushvalue(L, -1);
lua_setfield(L, -3, field);
}
}
}
lua_pop(L, 1);
}
lua_pop(L, 1); // pop existing object
merged = true;
} else {
lua_pop(L, 1); // pop nil
}
}
if (!merged) {
// No existing object found - create new global
lua_pushvalue(L, -1);
lua_setglobal(L, savedKey);
}
int stackAfter = lua_gettop(L);
if (stackBefore != stackAfter) {
wxLogError("Stack imbalance detected for global %s: before=%d after=%d", savedKey, stackBefore, stackAfter);
}
count++; count++;
} }
lua_pop(L, 1); // pop value lua_pop(L, 1); // pop value from lua_next
} }
lua_pop(L, 1); // pop table lua_pop(L, 1); // pop table
lua_pop(L, 1); // pop table
wxLogDebug("Restored %d globals from %s", count, filePath); wxLogDebug("Restored %d globals from %s", count, filePath);
return true; return true;
} }

View File

@@ -1,14 +1,19 @@
#include "lua/wherigo.h" #include "lua/wherigo.h"
#include "app.h"
#include "lua/game_engine.h"
#include "lua/zobject.h" #include "lua/zobject.h"
#include "lua/ztimer.h" #include "lua/ztimer.h"
#include "ui/wherigo_dialog.h" #include "ui/wherigo_dialog.h"
extern "C" { extern "C" {
#include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
#include <lua.h>
} }
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/log.h> #include <wx/log.h>
#include <wx/stdpaths.h>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@@ -17,6 +22,63 @@ extern "C" {
namespace wherigo { namespace wherigo {
std::string getCompletionCode(lua_State *L) {
lua_getglobal(L, "Player");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return "";
}
lua_getfield(L, -1, "CompletionCode");
std::string completionCode;
if (lua_isstring(L, -1)) {
completionCode = lua_tostring(L, -1);
if (completionCode.length() > 15) {
completionCode = completionCode.substr(0, 15);
}
}
lua_pop(L, 2);
return completionCode;
}
// Wherigo.RequestSync()
static int wherigo_RequestSync(lua_State *L) {
// RequestSync shows the completion code from the cartridge in a MessageBox
wxLogDebug(
"Wherigo.RequestSync() called - showing completion code from cartridge");
auto completionCode = getCompletionCode(L);
if (completionCode.empty()) {
wxApp *app = wxTheApp;
if (!app) {
wxLogWarning("Cannot sync: no app instance");
return 0;
}
cApp *wherigApp = dynamic_cast<cApp *>(app);
if (!wherigApp || !wherigApp->isCartridgeLoaded()) {
wxLogWarning("Cannot sync: no cartridge loaded");
return 0;
}
auto cartridge = wherigApp->getCartridge();
completionCode = cartridge->completionCode().substr(0, 15);
}
wxString message =
wxString::Format("Completion Code:\n\n%s\n\nEnter this code on "
"wherigo.com to verify your completion.",
completionCode.c_str());
std::vector<wxString> buttons = {"OK"};
wherigo::WherigoMessageDialog dlg(nullptr, message, "Wherigo Completion",
buttons);
dlg.ShowModal();
return 0;
}
// Wherigo.ZonePoint(lat, lng, alt) // Wherigo.ZonePoint(lat, lng, alt)
static int wherigo_ZonePoint(lua_State *L) { static int wherigo_ZonePoint(lua_State *L) {
@@ -131,7 +193,6 @@ static int wherigo_ZTask(lua_State *L) {
return 1; return 1;
} }
// Wherigo.ZTimer(cartridge) // Wherigo.ZTimer(cartridge)
static int wherigo_ZTimer(lua_State *L) { static int wherigo_ZTimer(lua_State *L) {
lua_newtable(L); lua_newtable(L);
@@ -190,9 +251,7 @@ static int wherigo_ZMedia(lua_State *L) {
return 1; return 1;
} }
void resetMediaCounter() { void resetMediaCounter() { s_mediaCounter = 0; }
s_mediaCounter = 0;
}
// Wherigo.ZCartridge() // Wherigo.ZCartridge()
static int wherigo_ZCartridge(lua_State *L) { static int wherigo_ZCartridge(lua_State *L) {
@@ -204,6 +263,9 @@ static int wherigo_ZCartridge(lua_State *L) {
lua_newtable(L); lua_newtable(L);
lua_setfield(L, -2, "AllZObjects"); lua_setfield(L, -2, "AllZObjects");
lua_pushcfunction(L, wherigo_RequestSync);
lua_setfield(L, -2, "RequestSync");
return 1; return 1;
} }
@@ -238,7 +300,8 @@ static int wherigo_Player(lua_State *L) {
// Wherigo.MessageBox(table) // Wherigo.MessageBox(table)
static int wherigo_MessageBox(lua_State *L) { static int wherigo_MessageBox(lua_State *L) {
if (!lua_istable(L, 1)) return 0; if (!lua_istable(L, 1))
return 0;
// Get Text // Get Text
lua_getfield(L, 1, "Text"); lua_getfield(L, 1, "Text");
@@ -273,7 +336,8 @@ static int wherigo_MessageBox(lua_State *L) {
wxLogDebug("MessageBox: %s", text); wxLogDebug("MessageBox: %s", text);
// Show dialog // Show dialog
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo", buttons); WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo",
buttons);
dlg.ShowModal(); dlg.ShowModal();
int selected = dlg.getSelectedButton(); int selected = dlg.getSelectedButton();
@@ -294,12 +358,17 @@ static int wherigo_MessageBox(lua_State *L) {
luaL_unref(L, LUA_REGISTRYINDEX, callbackRef); luaL_unref(L, LUA_REGISTRYINDEX, callbackRef);
} }
// Notify game state change after MessageBox
// This ensures UI updates if state was modified during the MessageBox
GameEngine::getInstance().notifyStateChanged();
return 0; return 0;
} }
// Wherigo.Dialog(table) // Wherigo.Dialog(table)
static int wherigo_Dialog(lua_State *L) { static int wherigo_Dialog(lua_State *L) {
if (!lua_istable(L, 1)) return 0; if (!lua_istable(L, 1))
return 0;
std::vector<DialogEntry> entries; std::vector<DialogEntry> entries;
int n = lua_objlen(L, 1); int n = lua_objlen(L, 1);
@@ -341,7 +410,7 @@ static int wherigo_Dialog(lua_State *L) {
} }
wxLogDebug("Dialog with %zu entries", entries.size()); wxLogDebug("Dialog with %zu entries", entries.size());
for (const auto& entry : entries) { for (const auto &entry : entries) {
wxLogDebug(" Text: %s, Media: %s", entry.text.c_str(), wxLogDebug(" Text: %s, Media: %s", entry.text.c_str(),
entry.mediaName.empty() ? "(none)" : entry.mediaName.c_str()); entry.mediaName.empty() ? "(none)" : entry.mediaName.c_str());
} }
@@ -378,6 +447,45 @@ static int wherigo_PlayAudio(lua_State *L) {
static int wherigo_ShowScreen(lua_State *L) { static int wherigo_ShowScreen(lua_State *L) {
int screen = luaL_optinteger(L, 1, 0); int screen = luaL_optinteger(L, 1, 0);
wxLogDebug("ShowScreen: %d", screen); wxLogDebug("ShowScreen: %d", screen);
// Show details dialog for DETAILSCREEN
if (screen == 5 && lua_gettop(L) >= 2 && lua_istable(L, 2)) {
// Get Name
lua_getfield(L, 2, "Name");
std::string name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "Details";
lua_pop(L, 1);
// Get Description
lua_getfield(L, 2, "Description");
std::string desc = lua_isstring(L, -1) ? lua_tostring(L, -1) : "";
lua_pop(L, 1);
// Get Media name (prefer Media, fallback to Icon)
std::string mediaName;
lua_getfield(L, 2, "Media");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "Name");
if (lua_isstring(L, -1)) {
mediaName = lua_tostring(L, -1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
if (mediaName.empty()) {
lua_getfield(L, 2, "Icon");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "Name");
if (lua_isstring(L, -1)) {
mediaName = lua_tostring(L, -1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
// Show dialog with media if available
std::vector<wxString> buttons = {"OK"};
wherigo::WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(desc.c_str()), wxString::FromUTF8(name.c_str()), buttons, wxString::FromUTF8(mediaName.c_str()));
dlg.ShowModal();
return 0;
}
return 0; return 0;
} }
@@ -465,8 +573,8 @@ static const luaL_Reg wherigo_funcs[] = {
{"VectorToZone", wherigo_VectorToZone}, {"VectorToZone", wherigo_VectorToZone},
{"VectorToPoint", wherigo_VectorToPoint}, {"VectorToPoint", wherigo_VectorToPoint},
{"VectorToSegment", wherigo_VectorToSegment}, {"VectorToSegment", wherigo_VectorToSegment},
{nullptr, nullptr} {"RequestSync", wherigo_RequestSync},
}; {nullptr, nullptr}};
int luaopen_Wherigo(lua_State *L) { int luaopen_Wherigo(lua_State *L) {
luaL_register(L, "Wherigo", wherigo_funcs); luaL_register(L, "Wherigo", wherigo_funcs);
@@ -509,4 +617,3 @@ int luaopen_Wherigo(lua_State *L) {
} }
} // namespace wherigo } // namespace wherigo

View File

@@ -262,7 +262,6 @@ bool WherigoCompletion::writeGWLFile(const CompletionData& data, const std::stri
file << "</WherigoGameLog>\n"; file << "</WherigoGameLog>\n";
file.close(); file.close();
wxLogMessage("Completion log saved to: %s", filePath);
return true; return true;
} }

View File

@@ -2,6 +2,7 @@
#include "lua/game_engine.h" #include "lua/game_engine.h"
#include "ui/game_screen.h" #include "ui/game_screen.h"
#include "ui/wherigo_dialog.h" #include "ui/wherigo_dialog.h"
#include <cartridge/cartridge.h>
extern "C" { extern "C" {
#include <lua.h> #include <lua.h>
@@ -12,17 +13,20 @@ wxDECLARE_APP(cApp);
enum { enum {
ID_SaveGame = 2000, ID_SaveGame = 2000,
ID_LoadGame = 2001, ID_LoadGame = 2001,
ID_ExportCompletion = 2002 ID_ExportCompletion = 2002,
ID_MapView = 2003
}; };
cGameScreen::cGameScreen(wxWindow *parent) cGameScreen::cGameScreen(wxWindow *parent)
: wxFrame(parent, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 600)) { : wxFrame(parent, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 800)) {
// Menu // Menu
auto *menuFile = new wxMenu; auto *menuFile = new wxMenu;
menuFile->Append(ID_SaveGame, "Spielstand speichern\tCtrl+S"); menuFile->Append(ID_SaveGame, "Spielstand speichern\tCtrl+S");
menuFile->Append(ID_LoadGame, "Spielstand laden\tCtrl+L"); menuFile->Append(ID_LoadGame, "Spielstand laden\tCtrl+L");
menuFile->AppendSeparator(); menuFile->AppendSeparator();
menuFile->Append(ID_MapView, "Karten-Simulation..."); // Menüpunkt für Simulation
menuFile->AppendSeparator();
menuFile->Append(ID_ExportCompletion, "Completion Log exportieren\tCtrl+E"); menuFile->Append(ID_ExportCompletion, "Completion Log exportieren\tCtrl+E");
menuFile->AppendSeparator(); menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT, "Spiel beenden"); menuFile->Append(wxID_EXIT, "Spiel beenden");
@@ -103,6 +107,7 @@ cGameScreen::cGameScreen(wxWindow *parent)
Bind(wxEVT_MENU, &cGameScreen::OnSaveGame, this, ID_SaveGame); Bind(wxEVT_MENU, &cGameScreen::OnSaveGame, this, ID_SaveGame);
Bind(wxEVT_MENU, &cGameScreen::OnLoadGame, this, ID_LoadGame); Bind(wxEVT_MENU, &cGameScreen::OnLoadGame, this, ID_LoadGame);
Bind(wxEVT_MENU, &cGameScreen::OnExportCompletion, this, ID_ExportCompletion); Bind(wxEVT_MENU, &cGameScreen::OnExportCompletion, this, ID_ExportCompletion);
Bind(wxEVT_MENU, &cGameScreen::OnMapSim, this, ID_MapView);
m_zoneList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnZoneSelected, this); m_zoneList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnZoneSelected, this);
m_taskList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnTaskSelected, this); m_taskList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnTaskSelected, this);
@@ -542,12 +547,11 @@ void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
if (currentName == zoneName) { if (currentName == zoneName) {
found = true; found = true;
// Get zone description // Zone Info mit Media anzeigen
lua_getfield(L, -1, "Description"); lua_getfield(L, -1, "Description");
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ""; wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : "";
lua_pop(L, 1); lua_pop(L, 1);
// Get media if exists
wxString mediaName; wxString mediaName;
lua_getfield(L, -1, "Media"); lua_getfield(L, -1, "Media");
if (lua_istable(L, -1)) { if (lua_istable(L, -1)) {
@@ -559,7 +563,6 @@ void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
} }
lua_pop(L, 1); lua_pop(L, 1);
// Show zone info with media
if (!description.IsEmpty() || !mediaName.IsEmpty()) { if (!description.IsEmpty() || !mediaName.IsEmpty()) {
std::vector<wxString> buttons = {"OK"}; std::vector<wxString> buttons = {"OK"};
wherigo::WherigoMessageDialog dlg(this, description, zoneName, buttons, mediaName); wherigo::WherigoMessageDialog dlg(this, description, zoneName, buttons, mediaName);
@@ -589,9 +592,9 @@ void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
lua_pop(L, 1); lua_pop(L, 1);
} }
} }
lua_pop(L, 1); // pop value lua_pop(L, 1);
} }
lua_pop(L, 1); // pop _G lua_pop(L, 1);
if (!found) { if (!found) {
wxMessageBox(wxString::Format("Zone '%s' nicht gefunden", zoneName), wxMessageBox(wxString::Format("Zone '%s' nicht gefunden", zoneName),
@@ -624,12 +627,11 @@ void cGameScreen::OnTaskSelected(wxCommandEvent& event) {
lua_pop(L, 1); lua_pop(L, 1);
if (currentName == taskName) { if (currentName == taskName) {
// Get task description // Bestehende MessageBox mit Status und Beschreibung
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"; 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 task complete status
lua_getfield(L, -1, "Complete"); lua_getfield(L, -1, "Complete");
bool complete = lua_toboolean(L, -1); bool complete = lua_toboolean(L, -1);
lua_pop(L, 1); lua_pop(L, 1);
@@ -680,54 +682,6 @@ void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
lua_pop(L, 1); lua_pop(L, 1);
if (currentName == itemName) { if (currentName == itemName) {
// Get item description
lua_getfield(L, -1, "Description");
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
lua_pop(L, 1);
// Get media if exists
wxString mediaName;
lua_getfield(L, -1, "Media");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "Name");
if (lua_isstring(L, -1)) {
mediaName = wxString::FromUTF8(lua_tostring(L, -1));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
// Check for commands
lua_getfield(L, -1, "Commands");
wxArrayString commands;
if (lua_istable(L, -1)) {
int n = lua_objlen(L, -1);
for (int i = 1; i <= n; i++) {
lua_rawgeti(L, -1, i);
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "Text");
if (lua_isstring(L, -1)) {
commands.Add(wxString::FromUTF8(lua_tostring(L, -1)));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
}
lua_pop(L, 1);
wxString message = description;
if (!commands.IsEmpty()) {
message += ("\n\nVerfügbare Aktionen:\n");
for (const auto& cmd : commands) {
message += "" + cmd + "\n";
}
}
// Use WherigoMessageDialog to show with media support
std::vector<wxString> buttons = {"OK"};
wherigo::WherigoMessageDialog dlg(this, message, itemName, buttons, mediaName);
dlg.ShowModal();
// Call OnClick if exists // Call OnClick if exists
lua_getfield(L, -1, "OnClick"); lua_getfield(L, -1, "OnClick");
@@ -937,3 +891,53 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
} }
lua_pop(L, 1); // pop _G lua_pop(L, 1); // pop _G
} }
void cGameScreen::OnMapSim(wxCommandEvent&) {
std::vector<std::pair<double, double>> zoneCoords;
double lat = 53.3, lon = 10.39;
lua_State *L = wxGetApp().getLuaState();
if (L) {
lua_getglobal(L, "_G");
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "ClassName");
if (lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "Zone") == 0) {
lua_pop(L, 1);
lua_getfield(L, -1, "Active");
bool active = lua_toboolean(L, -1) || true;
lua_pop(L, 1);
lua_getfield(L, -1, "Visible");
bool visible = lua_toboolean(L, -1) || true;
lua_pop(L, 1);
if (active && visible) {
lua_getfield(L, -1, "OriginalPoint");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "latitude");
lua_getfield(L, -2, "longitude");
if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
double zlat = lua_tonumber(L, -2);
double zlon = lua_tonumber(L, -1);
zoneCoords.emplace_back(zlat, zlon);
}
lua_pop(L, 2);
} else {
lua_pop(L, 1);
}
lua_pop(L, 1); // pop OriginalPoint
}
} else {
lua_pop(L, 1);
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
if (!zoneCoords.empty()) {
lat = zoneCoords[0].first;
lon = zoneCoords[0].second;
}
auto *mapSim = new MapSimFrame(this, lat, lon, zoneCoords);
mapSim->Show();
}

View File

@@ -0,0 +1,79 @@
#include "ui/map_sim_frame.h"
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/webview.h>
#include <wx/msgdlg.h>
#include <thread>
#include <chrono>
wxBEGIN_EVENT_TABLE(MapSimFrame, wxFrame)
EVT_WEBVIEW_NAVIGATED(wxID_ANY, MapSimFrame::OnWebViewEvent)
EVT_BUTTON(wxID_ANY, MapSimFrame::OnPlay)
wxEND_EVENT_TABLE()
MapSimFrame::MapSimFrame(wxWindow* parent, double centerLat, double centerLon, const std::vector<std::pair<double, double>>& zoneCoords)
: wxFrame(parent, wxID_ANY, "Karten-Simulation", wxDefaultPosition, wxSize(900, 700)), m_zoneCoords(zoneCoords) {
auto* sizer = new wxBoxSizer(wxVERTICAL);
m_webView = wxWebView::New(this, wxID_ANY);
sizer->Add(m_webView, 1, wxEXPAND);
auto* playBtn = new wxButton(this, wxID_ANY, "Simulation starten");
sizer->Add(playBtn, 0, wxALL | wxALIGN_CENTER, 8);
SetSizer(sizer);
// Leaflet-Karte laden (dynamisch zentriert und Marker)
wxString html;
html << "<!DOCTYPE html><html><head><meta charset='utf-8'><title>MapSim</title>"
"<link rel='stylesheet' href='https://unpkg.com/leaflet/dist/leaflet.css'/>"
"<script src='https://unpkg.com/leaflet/dist/leaflet.js'></script>"
"<style>html,body,#map{height:100%%;margin:0;padding:0;}#map{height:600px;}</style>"
"</head><body><div id='map'></div>"
"<script>"
"var map = L.map('map').setView([" << wxString::Format("%.8f, %.8f", centerLat, centerLon) << "], 16);"
"L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 19}).addTo(map);"
"var markers = [];";
// Marker für alle Zonen
for (const auto& z : zoneCoords) {
html << wxString::Format("var m = L.marker([%.8f, %.8f]).addTo(map); markers.push(m);\n", z.first, z.second);
}
html <<
"map.on('click', function(e) {"
" var marker = L.marker(e.latlng).addTo(map);"
" markers.push(marker);"
" window.wx.postMessage(JSON.stringify({lat: e.latlng.lat, lon: e.latlng.lng}));"
"});"
"</script></body></html>";
m_webView->SetPage(html, "");
}
void MapSimFrame::OnWebViewEvent(wxWebViewEvent& event) {
// Empfange Marker-Koordinaten von JS
wxString msg = event.GetString();
double lat = 0, lon = 0;
if (msg.ToDouble(&lat)) {
// Not used, see below
}
// In wxWidgets 3.1+ kann man wxWebView::RegisterHandler für JS->C++ nutzen
// Hier: Marker werden über postMessage als JSON gesendet
// TODO: JSON parsen und AddSimPoint aufrufen
}
void MapSimFrame::AddSimPoint(double lat, double lon) {
m_route.push_back({lat, lon});
}
void MapSimFrame::OnPlay(wxCommandEvent&) {
if (m_route.empty()) {
wxMessageBox("Bitte zuerst Marker setzen.", "Hinweis", wxOK | wxICON_INFORMATION);
return;
}
// Simuliere Bewegung entlang der Route
for (const auto& pt : m_route) {
SendPositionToEngine(pt.lat, pt.lon);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
wxMessageBox("Simulation beendet.", "Info", wxOK | wxICON_INFORMATION);
}
void MapSimFrame::SendPositionToEngine(double lat, double lon) {
// TODO: Engine-Integration: GPS-Position setzen
// Beispiel: wxGetApp().setSimulatedPosition(lat, lon);
}

View File

@@ -15,7 +15,7 @@ enum {
}; };
cStartScreen::cStartScreen() cStartScreen::cStartScreen()
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 600)), : wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 800)),
m_gameFrame(nullptr), m_gameFrame(nullptr),
m_cartridgeLoaded(false) { m_cartridgeLoaded(false) {
@@ -107,6 +107,20 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
wxString filePath = openFileDialog.GetPath(); wxString filePath = openFileDialog.GetPath();
SetStatusText("Lade Cartridge: " + filePath); SetStatusText("Lade Cartridge: " + filePath);
// Vor dem Laden: Info-Panel zurücksetzen
if (m_infoPanel) {
if (m_cartridgeName) m_cartridgeName->SetLabel("");
if (m_cartridgeAuthor) m_cartridgeAuthor->SetLabel("");
if (m_cartridgeDesc) m_cartridgeDesc->SetPage("");
if (m_splashImage) m_splashImage->SetBitmap(wxNullBitmap);
}
// ggf. alten GameScreen schließen
if (m_gameFrame) {
m_gameFrame->Destroy();
m_gameFrame = nullptr;
}
// Load the cartridge via cApp // Load the cartridge via cApp
if (wxGetApp().loadCartridge(filePath.ToStdString())) { if (wxGetApp().loadCartridge(filePath.ToStdString())) {
m_cartridgeLoaded = true; m_cartridgeLoaded = true;

View File

@@ -9,6 +9,7 @@
#include <wx/log.h> #include <wx/log.h>
#include <wx/mstream.h> #include <wx/mstream.h>
#include <wx/image.h> #include <wx/image.h>
#include <wx/html/htmlwin.h>
namespace wherigo { namespace wherigo {
@@ -17,7 +18,7 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
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);
@@ -68,37 +69,124 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
} }
} }
// Helper lambda to convert plain text to HTML
auto textToHtml = [](const wxString& plainText) -> wxString {
wxString result = plainText;
// Check if this looks like HTML (has tags)
if (plainText.Find("<") == wxNOT_FOUND) {
// Plain text - need to be careful with & to avoid breaking HTML entities
// Replace & but preserve HTML entities like &nbsp; &amp; etc.
wxString escaped;
for (size_t i = 0; i < plainText.length(); i++) {
if (plainText[i] == '&') {
// Look ahead to see if this is an HTML entity
size_t end = plainText.find(';', i);
if (end != wxString::npos && end - i < 12) {
// Likely an entity, copy as-is
escaped += plainText.substr(i, end - i + 1);
i = end;
} else {
// Not an entity, escape the &
escaped += "&amp;";
}
} else if (plainText[i] == '\n') {
escaped += "<br/>";
} else {
escaped += plainText[i];
}
}
result = escaped;
}
return result;
};
wxColour bgColor = GetBackgroundColour();
wxColour fgColor = GetForegroundColour();
// Helper lambda to create and configure HTML window with auto-sizing
auto createHtmlWindow = [this, &sizer, &textToHtml, bgColor, fgColor](const wxString& text) {
auto *htmlCtrl = new wxHtmlWindow(this, wxID_ANY);
htmlCtrl->SetBorders(0);
wxString htmlContent = wxString::Format(
"<html><body style=\"margin:0; padding:5px; background-color:#%02x%02x%02x; color:#%02x%02x%02x; font-family:sans-serif; font-size:11pt; line-height:1.5; user-select:none; -webkit-user-select:none; -moz-user-select:none;\">%s</body></html>",
bgColor.Red(), bgColor.Green(), bgColor.Blue(),
fgColor.Red(), fgColor.Green(), fgColor.Blue(),
textToHtml(text)
);
htmlCtrl->SetPage(htmlContent);
// htmlCtrl->SetBackgroundColour(bgColor); // Removed as it might not exist or be needed
// Set a reasonable fixed size - will expand based on content
// Width: fixed at 600px for consistent layout
// Height: let wxHtmlWindow calculate based on content, with limits
int contentWidth = 600;
// Calculate approximate height based on line count (rough estimate)
int lineCount = 1;
for (size_t i = 0; i < text.length(); i++) {
if (text[i] == '\n') lineCount++;
}
// Estimate: ~20px per line + 40px padding
int estimatedHeight = std::max(80, lineCount * 20 + 40);
estimatedHeight = std::clamp(estimatedHeight, 80, 400);
htmlCtrl->SetMinSize(wxSize(contentWidth, estimatedHeight));
sizer->Add(htmlCtrl, 1, wxALL | wxEXPAND, 10);
};
// Text (only show if not just a placeholder like ".") // Text (only show if not just a placeholder like ".")
if (!text.IsEmpty() && text != ".") { if (!text.IsEmpty() && text != ".") {
auto *textCtrl = new wxStaticText(this, wxID_ANY, text); createHtmlWindow(text);
textCtrl->Wrap(800); // Also doubled wrap width
sizer->Add(textCtrl, 1, wxALL | wxEXPAND, 15);
} else if (mediaName.IsEmpty()) { } else if (mediaName.IsEmpty()) {
// No media and empty/placeholder text - show something // No media and empty/placeholder text - show something
auto *textCtrl = new wxStaticText(this, wxID_ANY, text); createHtmlWindow(text);
textCtrl->Wrap(800); // Also doubled wrap width
sizer->Add(textCtrl, 1, wxALL | wxEXPAND, 15);
} }
// Buttons // Buttons
auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL); auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
wxButton *defaultButton = nullptr;
if (buttons.empty()) { if (buttons.empty()) {
auto *okButton = new wxButton(this, wxID_OK, "OK"); auto *okButton = new wxButton(this, wxID_OK, "OK");
buttonSizer->Add(okButton, 0, wxALL, 5); buttonSizer->Add(okButton, 0, wxALL, 5);
okButton->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this); okButton->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this);
defaultButton = okButton;
} else { } else {
for (size_t i = 0; i < buttons.size(); i++) { for (size_t i = 0; i < buttons.size(); i++) {
auto *btn = new wxButton(this, 1000 + i, buttons[i]); auto *btn = new wxButton(this, 1000 + i, buttons[i]);
buttonSizer->Add(btn, 0, wxALL, 5); buttonSizer->Add(btn, 0, wxALL, 5);
btn->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this); btn->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this);
// Set first button as default
if (i == 0) {
defaultButton = btn;
} }
} }
}
// Set the default button (will be activated by Enter)
if (defaultButton) {
defaultButton->SetDefault();
}
sizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxBOTTOM, 10); sizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxBOTTOM, 10);
SetSizerAndFit(sizer); SetSizer(sizer);
SetMinSize(wxSize(600, 300)); // Doubled from 300x150 sizer->Layout();
// Calculate dialog size based on content
wxSize minSize = sizer->GetMinSize();
// Set reasonable dialog size
int dialogWidth = std::clamp(minSize.GetWidth() + 40, 500, 800);
int dialogHeight = std::clamp(minSize.GetHeight() + 40, 300, 700);
SetSize(dialogWidth, dialogHeight);
SetMinSize(wxSize(400, 250));
CenterOnParent(); CenterOnParent();
} }
@@ -151,11 +239,11 @@ void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
if (i == entries.size() - 1 && callback) { if (i == entries.size() - 1 && callback) {
callback(dlg.getSelectedButton()); callback(dlg.getSelectedButton());
} }
}
// Notify game state change after dialog sequence completes // Notify state change after each dialog to ensure UI updates
// This is important for goto events triggered during dialogs
GameEngine::getInstance().notifyStateChanged(); GameEngine::getInstance().notifyStateChanged();
}
} }
} // namespace wherigo } // namespace wherigo