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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -111,3 +111,5 @@ CMakeUserPresets.json
|
||||
# End of https://www.toptal.com/developers/gitignore/api/cmake,clion+all
|
||||
|
||||
cartridges/
|
||||
assets/icon.iconset/
|
||||
*.lua
|
||||
|
||||
@@ -27,21 +27,25 @@ FetchContent_MakeAvailable(wxWidgets)
|
||||
message(STATUS "Configure project....")
|
||||
|
||||
set(SRCS
|
||||
main/src/main.cpp
|
||||
main/src/app.cpp
|
||||
main/src/ui/start_screen.cpp
|
||||
main/src/main.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/lua/wherigo.cpp
|
||||
main/src/lua/zobject.cpp
|
||||
main/src/lua/ztimer.cpp
|
||||
main/src/lua/game_engine.cpp
|
||||
main/src/lua/media_manager.cpp
|
||||
main/src/lua/persistence.cpp
|
||||
main/src/lua/wherigo.cpp
|
||||
main/src/lua/wherigo_completion.cpp
|
||||
main/src/lua/zobject.cpp
|
||||
main/src/lua/ztimer.cpp
|
||||
)
|
||||
|
||||
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})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
BUNDLE True
|
||||
@@ -49,7 +53,8 @@ if (APPLE)
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${PROJECT_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "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"
|
||||
)
|
||||
else ()
|
||||
@@ -61,14 +66,14 @@ include_directories(main/include)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
wxcore
|
||||
wxnet
|
||||
wxbase
|
||||
wxhtml
|
||||
wx::net
|
||||
wx::html
|
||||
wx::webview
|
||||
lua
|
||||
cartridge
|
||||
storage
|
||||
)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
||||
|
||||
# Kopiere die .dylib-Dateien ins Bundle
|
||||
if (APPLE)
|
||||
@@ -78,5 +83,11 @@ if (APPLE)
|
||||
$<TARGET_FILE:cartridge>
|
||||
$<TARGET_FILE:storage>
|
||||
"$<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 ()
|
||||
|
||||
22
assets/Info.plist.in
Normal file
22
assets/Info.plist.in
Normal 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
BIN
assets/icon.icns
Normal file
Binary file not shown.
BIN
assets/icon.png
LFS
Normal file
BIN
assets/icon.png
LFS
Normal file
Binary file not shown.
25
assets/make_icns.sh
Executable file
25
assets/make_icns.sh
Executable 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
|
||||
@@ -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> parseCartridge();
|
||||
|
||||
} // namespace cartridge
|
||||
|
||||
@@ -57,45 +57,4 @@ std::unique_ptr<Cartridge> parseFile(const std::string &filePath) {
|
||||
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
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
int getCartridgeRef() const { return m_cartridgeRef; }
|
||||
cartridge::Cartridge* getCartridge() const { return m_cartridge.get(); }
|
||||
bool isCartridgeLoaded() const { return m_cartridge != nullptr; }
|
||||
std::string getCartridgePath() const { return m_cartridgePath; }
|
||||
|
||||
private:
|
||||
bool initLuaState();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <wx/wx.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/notebook.h>
|
||||
#include "ui/map_sim_frame.h"
|
||||
|
||||
class cGameScreen : public wxFrame
|
||||
{
|
||||
@@ -24,6 +25,7 @@ private:
|
||||
void OnInventorySelected(wxCommandEvent& event);
|
||||
void OnCharacterSelected(wxCommandEvent& event);
|
||||
void OnItemSelected(wxCommandEvent& event);
|
||||
void OnMapSim(wxCommandEvent& event);
|
||||
|
||||
void populateZones();
|
||||
void populateTasks();
|
||||
|
||||
25
main/include/ui/map_sim_frame.h
Normal file
25
main/include/ui/map_sim_frame.h
Normal 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();
|
||||
};
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <wx/wx.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include "ui/map_sim_frame.h"
|
||||
|
||||
class cGameScreen;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ extern "C" {
|
||||
|
||||
bool cApp::OnInit()
|
||||
{
|
||||
|
||||
// Initialize image handlers for JPEG, PNG, GIF, etc.
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ extern "C" {
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <cstdio>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
@@ -82,10 +83,42 @@ void LuaPersistence::serializeValue(lua_State* L, int index, std::string& output
|
||||
case LUA_TSTRING: {
|
||||
const char* str = lua_tostring(L, index);
|
||||
output += "\"";
|
||||
// Escape special characters
|
||||
// Properly escape all special characters for Lua strings
|
||||
for (const char* p = str; *p; ++p) {
|
||||
if (*p == '"' || *p == '\\') output += '\\';
|
||||
output += *p;
|
||||
unsigned char c = (unsigned char)*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 += "\"";
|
||||
break;
|
||||
@@ -199,23 +232,120 @@ bool LuaPersistence::loadGlobals(lua_State* L, const std::string& filePath) {
|
||||
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;
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_isstring(L, -2)) {
|
||||
const char* key = lua_tostring(L, -2);
|
||||
if (lua_isstring(L, -2) && lua_istable(L, -1)) {
|
||||
const char* savedKey = lua_tostring(L, -2);
|
||||
wxLogDebug("Restoring global: %s", savedKey);
|
||||
int stackBefore = lua_gettop(L);
|
||||
|
||||
// Set the global
|
||||
lua_pushvalue(L, -1); // duplicate value
|
||||
lua_setglobal(L, key);
|
||||
// Get the Name field of the saved object (if it exists)
|
||||
lua_getfield(L, -1, "Name");
|
||||
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++;
|
||||
}
|
||||
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
|
||||
|
||||
wxLogDebug("Restored %d globals from %s", count, filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#include "lua/wherigo.h"
|
||||
#include "app.h"
|
||||
#include "lua/game_engine.h"
|
||||
#include "lua/zobject.h"
|
||||
#include "lua/ztimer.h"
|
||||
#include "ui/wherigo_dialog.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
#include <wx/dir.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -17,6 +22,63 @@ extern "C" {
|
||||
|
||||
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)
|
||||
static int wherigo_ZonePoint(lua_State *L) {
|
||||
@@ -131,7 +193,6 @@ static int wherigo_ZTask(lua_State *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// Wherigo.ZTimer(cartridge)
|
||||
static int wherigo_ZTimer(lua_State *L) {
|
||||
lua_newtable(L);
|
||||
@@ -190,9 +251,7 @@ static int wherigo_ZMedia(lua_State *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void resetMediaCounter() {
|
||||
s_mediaCounter = 0;
|
||||
}
|
||||
void resetMediaCounter() { s_mediaCounter = 0; }
|
||||
|
||||
// Wherigo.ZCartridge()
|
||||
static int wherigo_ZCartridge(lua_State *L) {
|
||||
@@ -204,6 +263,9 @@ static int wherigo_ZCartridge(lua_State *L) {
|
||||
lua_newtable(L);
|
||||
lua_setfield(L, -2, "AllZObjects");
|
||||
|
||||
lua_pushcfunction(L, wherigo_RequestSync);
|
||||
lua_setfield(L, -2, "RequestSync");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -238,7 +300,8 @@ static int wherigo_Player(lua_State *L) {
|
||||
|
||||
// Wherigo.MessageBox(table)
|
||||
static int wherigo_MessageBox(lua_State *L) {
|
||||
if (!lua_istable(L, 1)) return 0;
|
||||
if (!lua_istable(L, 1))
|
||||
return 0;
|
||||
|
||||
// Get Text
|
||||
lua_getfield(L, 1, "Text");
|
||||
@@ -273,7 +336,8 @@ static int wherigo_MessageBox(lua_State *L) {
|
||||
wxLogDebug("MessageBox: %s", text);
|
||||
|
||||
// Show dialog
|
||||
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo", buttons);
|
||||
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo",
|
||||
buttons);
|
||||
dlg.ShowModal();
|
||||
int selected = dlg.getSelectedButton();
|
||||
|
||||
@@ -294,12 +358,17 @@ static int wherigo_MessageBox(lua_State *L) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Wherigo.Dialog(table)
|
||||
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;
|
||||
int n = lua_objlen(L, 1);
|
||||
@@ -341,7 +410,7 @@ static int wherigo_Dialog(lua_State *L) {
|
||||
}
|
||||
|
||||
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(),
|
||||
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) {
|
||||
int screen = luaL_optinteger(L, 1, 0);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -443,30 +551,30 @@ static int wherigo_VectorToSegment(lua_State *L) {
|
||||
}
|
||||
|
||||
static const luaL_Reg wherigo_funcs[] = {
|
||||
{"ZonePoint", wherigo_ZonePoint},
|
||||
{"Distance", wherigo_Distance},
|
||||
{"Zone", wherigo_Zone},
|
||||
{"ZCharacter", wherigo_ZCharacter},
|
||||
{"ZItem", wherigo_ZItem},
|
||||
{"ZTask", wherigo_ZTask},
|
||||
{"ZTimer", wherigo_ZTimer},
|
||||
{"ZInput", wherigo_ZInput},
|
||||
{"ZMedia", wherigo_ZMedia},
|
||||
{"ZCartridge", wherigo_ZCartridge},
|
||||
{"ZCommand", wherigo_ZCommand},
|
||||
{"MessageBox", wherigo_MessageBox},
|
||||
{"Dialog", wherigo_Dialog},
|
||||
{"GetInput", wherigo_GetInput},
|
||||
{"PlayAudio", wherigo_PlayAudio},
|
||||
{"ShowScreen", wherigo_ShowScreen},
|
||||
{"LogMessage", wherigo_LogMessage},
|
||||
{"NoCaseEquals", wherigo_NoCaseEquals},
|
||||
{"TranslatePoint", wherigo_TranslatePoint},
|
||||
{"VectorToZone", wherigo_VectorToZone},
|
||||
{"VectorToPoint", wherigo_VectorToPoint},
|
||||
{"VectorToSegment", wherigo_VectorToSegment},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
{"ZonePoint", wherigo_ZonePoint},
|
||||
{"Distance", wherigo_Distance},
|
||||
{"Zone", wherigo_Zone},
|
||||
{"ZCharacter", wherigo_ZCharacter},
|
||||
{"ZItem", wherigo_ZItem},
|
||||
{"ZTask", wherigo_ZTask},
|
||||
{"ZTimer", wherigo_ZTimer},
|
||||
{"ZInput", wherigo_ZInput},
|
||||
{"ZMedia", wherigo_ZMedia},
|
||||
{"ZCartridge", wherigo_ZCartridge},
|
||||
{"ZCommand", wherigo_ZCommand},
|
||||
{"MessageBox", wherigo_MessageBox},
|
||||
{"Dialog", wherigo_Dialog},
|
||||
{"GetInput", wherigo_GetInput},
|
||||
{"PlayAudio", wherigo_PlayAudio},
|
||||
{"ShowScreen", wherigo_ShowScreen},
|
||||
{"LogMessage", wherigo_LogMessage},
|
||||
{"NoCaseEquals", wherigo_NoCaseEquals},
|
||||
{"TranslatePoint", wherigo_TranslatePoint},
|
||||
{"VectorToZone", wherigo_VectorToZone},
|
||||
{"VectorToPoint", wherigo_VectorToPoint},
|
||||
{"VectorToSegment", wherigo_VectorToSegment},
|
||||
{"RequestSync", wherigo_RequestSync},
|
||||
{nullptr, nullptr}};
|
||||
|
||||
int luaopen_Wherigo(lua_State *L) {
|
||||
luaL_register(L, "Wherigo", wherigo_funcs);
|
||||
@@ -509,4 +617,3 @@ int luaopen_Wherigo(lua_State *L) {
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
|
||||
@@ -262,7 +262,6 @@ bool WherigoCompletion::writeGWLFile(const CompletionData& data, const std::stri
|
||||
file << "</WherigoGameLog>\n";
|
||||
file.close();
|
||||
|
||||
wxLogMessage("Completion log saved to: %s", filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "lua/game_engine.h"
|
||||
#include "ui/game_screen.h"
|
||||
#include "ui/wherigo_dialog.h"
|
||||
#include <cartridge/cartridge.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -12,17 +13,20 @@ wxDECLARE_APP(cApp);
|
||||
enum {
|
||||
ID_SaveGame = 2000,
|
||||
ID_LoadGame = 2001,
|
||||
ID_ExportCompletion = 2002
|
||||
ID_ExportCompletion = 2002,
|
||||
ID_MapView = 2003
|
||||
};
|
||||
|
||||
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
|
||||
auto *menuFile = new wxMenu;
|
||||
menuFile->Append(ID_SaveGame, "Spielstand speichern\tCtrl+S");
|
||||
menuFile->Append(ID_LoadGame, "Spielstand laden\tCtrl+L");
|
||||
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->AppendSeparator();
|
||||
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::OnLoadGame, this, ID_LoadGame);
|
||||
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_taskList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnTaskSelected, this);
|
||||
@@ -542,12 +547,11 @@ void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
|
||||
if (currentName == zoneName) {
|
||||
found = true;
|
||||
|
||||
// Get zone description
|
||||
// Zone Info mit Media anzeigen
|
||||
lua_getfield(L, -1, "Description");
|
||||
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : "";
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get media if exists
|
||||
wxString mediaName;
|
||||
lua_getfield(L, -1, "Media");
|
||||
if (lua_istable(L, -1)) {
|
||||
@@ -559,7 +563,6 @@ void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Show zone info with media
|
||||
if (!description.IsEmpty() || !mediaName.IsEmpty()) {
|
||||
std::vector<wxString> buttons = {"OK"};
|
||||
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); // pop value
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (!found) {
|
||||
wxMessageBox(wxString::Format("Zone '%s' nicht gefunden", zoneName),
|
||||
@@ -624,12 +627,11 @@ void cGameScreen::OnTaskSelected(wxCommandEvent& event) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == taskName) {
|
||||
// Get task description
|
||||
// Bestehende MessageBox mit Status und Beschreibung
|
||||
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 task complete status
|
||||
lua_getfield(L, -1, "Complete");
|
||||
bool complete = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
@@ -680,54 +682,6 @@ void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == itemName) {
|
||||
// Get item description
|
||||
lua_getfield(L, -1, "Description");
|
||||
wxString description = lua_isstring(L, -1) ? wxString::FromUTF8(lua_tostring(L, -1)) : ("Keine Beschreibung verfügbar");
|
||||
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
|
||||
lua_getfield(L, -1, "OnClick");
|
||||
@@ -937,3 +891,53 @@ void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
79
main/src/ui/map_sim_frame.cpp
Normal file
79
main/src/ui/map_sim_frame.cpp
Normal 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);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ enum {
|
||||
};
|
||||
|
||||
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_cartridgeLoaded(false) {
|
||||
|
||||
@@ -107,6 +107,20 @@ void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
||||
wxString filePath = openFileDialog.GetPath();
|
||||
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
|
||||
if (wxGetApp().loadCartridge(filePath.ToStdString())) {
|
||||
m_cartridgeLoaded = true;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <wx/log.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
@@ -17,7 +18,7 @@ WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &tex
|
||||
const std::vector<wxString> &buttons,
|
||||
const wxString &mediaName)
|
||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ) {
|
||||
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -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 & 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 += "&";
|
||||
}
|
||||
} 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 ".")
|
||||
if (!text.IsEmpty() && text != ".") {
|
||||
auto *textCtrl = new wxStaticText(this, wxID_ANY, text);
|
||||
textCtrl->Wrap(800); // Also doubled wrap width
|
||||
sizer->Add(textCtrl, 1, wxALL | wxEXPAND, 15);
|
||||
createHtmlWindow(text);
|
||||
} else if (mediaName.IsEmpty()) {
|
||||
// No media and empty/placeholder text - show something
|
||||
auto *textCtrl = new wxStaticText(this, wxID_ANY, text);
|
||||
textCtrl->Wrap(800); // Also doubled wrap width
|
||||
sizer->Add(textCtrl, 1, wxALL | wxEXPAND, 15);
|
||||
createHtmlWindow(text);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxButton *defaultButton = nullptr;
|
||||
|
||||
if (buttons.empty()) {
|
||||
auto *okButton = new wxButton(this, wxID_OK, "OK");
|
||||
buttonSizer->Add(okButton, 0, wxALL, 5);
|
||||
okButton->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this);
|
||||
defaultButton = okButton;
|
||||
} else {
|
||||
for (size_t i = 0; i < buttons.size(); i++) {
|
||||
auto *btn = new wxButton(this, 1000 + i, buttons[i]);
|
||||
buttonSizer->Add(btn, 0, wxALL, 5);
|
||||
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);
|
||||
|
||||
SetSizerAndFit(sizer);
|
||||
SetMinSize(wxSize(600, 300)); // Doubled from 300x150
|
||||
SetSizer(sizer);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -151,11 +239,11 @@ void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
|
||||
if (i == entries.size() - 1 && callback) {
|
||||
callback(dlg.getSelectedButton());
|
||||
}
|
||||
}
|
||||
|
||||
// Notify game state change after dialog sequence completes
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
// Notify state change after each dialog to ensure UI updates
|
||||
// This is important for goto events triggered during dialogs
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
|
||||
Reference in New Issue
Block a user