#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 #include } #include #include #include #include #include #include #include #include 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(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 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) { lua_Number lat = luaL_checknumber(L, 1); lua_Number lng = luaL_checknumber(L, 2); lua_Number alt = luaL_optnumber(L, 3, 0.0); lua_newtable(L); lua_pushnumber(L, lat); lua_setfield(L, -2, "latitude"); lua_pushnumber(L, lng); lua_setfield(L, -2, "longitude"); lua_pushnumber(L, alt); lua_setfield(L, -2, "altitude"); return 1; } // Wherigo.Distance(value, unit) static int wherigo_Distance(lua_State *L) { lua_Number value = luaL_checknumber(L, 1); const char *unit = luaL_optstring(L, 2, "meters"); lua_newtable(L); lua_pushnumber(L, value); lua_setfield(L, -2, "value"); lua_pushstring(L, unit); lua_setfield(L, -2, "unit"); return 1; } // Wherigo.Zone(cartridge) static int wherigo_Zone(lua_State *L) { lua_newtable(L); lua_pushstring(L, "Zone"); lua_setfield(L, -2, "ClassName"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Active"); lua_pushboolean(L, 1); lua_setfield(L, -2, "Visible"); lua_newtable(L); lua_setfield(L, -2, "Points"); lua_pushcfunction(L, zobject_Contains); lua_setfield(L, -2, "Contains"); return 1; } // Wherigo.ZCharacter(cartridge) static int wherigo_ZCharacter(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZCharacter"); lua_setfield(L, -2, "ClassName"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Active"); lua_pushboolean(L, 1); lua_setfield(L, -2, "Visible"); lua_pushcfunction(L, zobject_MoveTo); lua_setfield(L, -2, "MoveTo"); lua_pushcfunction(L, zobject_Contains); lua_setfield(L, -2, "Contains"); return 1; } // Wherigo.ZItem(cartridge) static int wherigo_ZItem(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZItem"); lua_setfield(L, -2, "ClassName"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Active"); lua_pushboolean(L, 1); lua_setfield(L, -2, "Visible"); lua_pushcfunction(L, zobject_MoveTo); lua_setfield(L, -2, "MoveTo"); lua_pushcfunction(L, zobject_Contains); lua_setfield(L, -2, "Contains"); return 1; } // Wherigo.ZTask(cartridge) static int wherigo_ZTask(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZTask"); lua_setfield(L, -2, "ClassName"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Active"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Complete"); return 1; } // Wherigo.ZTimer(cartridge) static int wherigo_ZTimer(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZTimer"); lua_setfield(L, -2, "ClassName"); lua_pushstring(L, "Countdown"); lua_setfield(L, -2, "Type"); lua_pushnumber(L, 0); lua_setfield(L, -2, "Duration"); lua_pushboolean(L, 0); lua_setfield(L, -2, "Running"); lua_pushcfunction(L, ztimer_Start); lua_setfield(L, -2, "Start"); lua_pushcfunction(L, ztimer_Stop); lua_setfield(L, -2, "Stop"); lua_pushcfunction(L, ztimer_Reset); lua_setfield(L, -2, "Reset"); return 1; } // Wherigo.ZInput(cartridge) static int wherigo_ZInput(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZInput"); lua_setfield(L, -2, "ClassName"); lua_pushstring(L, "Text"); lua_setfield(L, -2, "InputType"); return 1; } // Wherigo.ZMedia(cartridge) static int s_mediaCounter = 0; static int wherigo_ZMedia(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZMedia"); lua_setfield(L, -2, "ClassName"); // Store the media index (1-based, 0 is luac) s_mediaCounter++; lua_pushinteger(L, s_mediaCounter); lua_setfield(L, -2, "MediaIndex"); return 1; } void resetMediaCounter() { s_mediaCounter = 0; } // Wherigo.ZCartridge() static int wherigo_ZCartridge(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZCartridge"); lua_setfield(L, -2, "ClassName"); lua_newtable(L); lua_setfield(L, -2, "AllZObjects"); lua_pushcfunction(L, wherigo_RequestSync); lua_setfield(L, -2, "RequestSync"); return 1; } // Wherigo.ZCommand(cartridge) static int wherigo_ZCommand(lua_State *L) { lua_newtable(L); lua_pushstring(L, "ZCommand"); lua_setfield(L, -2, "ClassName"); lua_pushboolean(L, 1); lua_setfield(L, -2, "Enabled"); return 1; } // Wherigo.Player static int wherigo_Player(lua_State *L) { lua_newtable(L); lua_pushstring(L, "Player"); lua_setfield(L, -2, "ClassName"); lua_pushstring(L, "Player"); lua_setfield(L, -2, "Name"); lua_newtable(L); lua_setfield(L, -2, "Inventory"); return 1; } // Wherigo.MessageBox(table) static int wherigo_MessageBox(lua_State *L) { if (!lua_istable(L, 1)) return 0; // Get Text lua_getfield(L, 1, "Text"); const char *text = lua_isstring(L, -1) ? lua_tostring(L, -1) : ""; lua_pop(L, 1); // Get Buttons std::vector buttons; lua_getfield(L, 1, "Buttons"); 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_isstring(L, -1)) { buttons.push_back(wxString::FromUTF8(lua_tostring(L, -1))); } lua_pop(L, 1); } } lua_pop(L, 1); // Get Callback lua_getfield(L, 1, "Callback"); bool hasCallback = lua_isfunction(L, -1); int callbackRef = LUA_NOREF; if (hasCallback) { callbackRef = luaL_ref(L, LUA_REGISTRYINDEX); } else { lua_pop(L, 1); } wxLogDebug("MessageBox: %s", text); // Show dialog WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(text), "Wherigo", buttons); dlg.ShowModal(); int selected = dlg.getSelectedButton(); // Call callback if exists if (callbackRef != LUA_NOREF) { lua_rawgeti(L, LUA_REGISTRYINDEX, callbackRef); if (buttons.empty()) { lua_pushnil(L); } else if (selected >= 0 && selected < (int)buttons.size()) { lua_pushstring(L, buttons[selected].ToUTF8().data()); } else { lua_pushnil(L); } if (lua_pcall(L, 1, 0, 0) != 0) { wxLogError("MessageBox callback error: %s", lua_tostring(L, -1)); lua_pop(L, 1); } 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; std::vector entries; int n = lua_objlen(L, 1); for (int i = 1; i <= n; i++) { lua_rawgeti(L, 1, i); if (lua_istable(L, -1)) { DialogEntry entry; lua_getfield(L, -1, "Text"); entry.text = lua_isstring(L, -1) ? lua_tostring(L, -1) : ""; lua_pop(L, 1); // Get Media object name lua_getfield(L, -1, "Media"); if (lua_istable(L, -1)) { lua_getfield(L, -1, "Name"); entry.mediaName = lua_isstring(L, -1) ? lua_tostring(L, -1) : ""; lua_pop(L, 1); } lua_pop(L, 1); lua_getfield(L, -1, "Buttons"); if (lua_istable(L, -1)) { int bn = lua_objlen(L, -1); for (int j = 1; j <= bn; j++) { lua_rawgeti(L, -1, j); if (lua_isstring(L, -1)) { entry.buttons.push_back(lua_tostring(L, -1)); } lua_pop(L, 1); } } lua_pop(L, 1); entries.push_back(entry); } lua_pop(L, 1); } wxLogDebug("Dialog with %zu entries", entries.size()); for (const auto &entry : entries) { wxLogDebug(" Text: %s, Media: %s", entry.text.c_str(), entry.mediaName.empty() ? "(none)" : entry.mediaName.c_str()); } // Show dialogs sequentially WherigoDialogRunner::getInstance().showDialog(entries); return 0; } // Wherigo.GetInput(zinput) static int wherigo_GetInput(lua_State *L) { if (lua_istable(L, 1)) { lua_getfield(L, 1, "Name"); const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)"; wxLogDebug("GetInput: %s", name); lua_pop(L, 1); } return 0; } // Wherigo.PlayAudio(zmedia) static int wherigo_PlayAudio(lua_State *L) { if (lua_istable(L, 1)) { lua_getfield(L, 1, "Name"); const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)"; wxLogDebug("PlayAudio: %s", name); lua_pop(L, 1); } return 0; } // Wherigo.ShowScreen(screen, obj) 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 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; } // Wherigo.LogMessage(message, level) static int wherigo_LogMessage(lua_State *L) { const char *message = luaL_checkstring(L, 1); wxLogDebug("Wherigo.LogMessage: %s", message); return 0; } // Wherigo.NoCaseEquals(str1, str2) static int wherigo_NoCaseEquals(lua_State *L) { const char *str1 = luaL_checkstring(L, 1); const char *str2 = luaL_checkstring(L, 2); std::string s1(str1); std::string s2(str2); std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); lua_pushboolean(L, s1 == s2); return 1; } // Wherigo.TranslatePoint(point, distance, bearing) static int wherigo_TranslatePoint(lua_State *L) { // TODO: implement point translation lua_newtable(L); return 1; } // Wherigo.VectorToZone(point, zone) static int wherigo_VectorToZone(lua_State *L) { lua_newtable(L); lua_pushnumber(L, 0); lua_setfield(L, -2, "distance"); lua_pushnumber(L, 0); lua_setfield(L, -2, "bearing"); lua_pushboolean(L, 0); lua_setfield(L, -2, "inside"); return 1; } // Wherigo.VectorToPoint(point1, point2) static int wherigo_VectorToPoint(lua_State *L) { lua_newtable(L); lua_pushnumber(L, 0); lua_setfield(L, -2, "distance"); lua_pushnumber(L, 0); lua_setfield(L, -2, "bearing"); return 1; } // Wherigo.VectorToSegment(point, point1, point2) static int wherigo_VectorToSegment(lua_State *L) { lua_newtable(L); lua_pushnumber(L, 0); lua_setfield(L, -2, "distance"); lua_pushnumber(L, 0); lua_setfield(L, -2, "bearing"); return 1; } 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}, {"RequestSync", wherigo_RequestSync}, {nullptr, nullptr}}; int luaopen_Wherigo(lua_State *L) { luaL_register(L, "Wherigo", wherigo_funcs); // Create Player object wherigo_Player(L); lua_setfield(L, -2, "Player"); // Screen constants lua_pushnumber(L, 0); lua_setfield(L, -2, "MAINSCREEN"); lua_pushnumber(L, 1); lua_setfield(L, -2, "LOCATIONSCREEN"); lua_pushnumber(L, 2); lua_setfield(L, -2, "ITEMSCREEN"); lua_pushnumber(L, 3); lua_setfield(L, -2, "INVENTORYSCREEN"); lua_pushnumber(L, 4); lua_setfield(L, -2, "TASKSCREEN"); lua_pushnumber(L, 5); lua_setfield(L, -2, "DETAILSCREEN"); // Register common functions as globals (as expected by Wherigo Builder) lua_pushcfunction(L, wherigo_ZonePoint); lua_setglobal(L, "ZonePoint"); lua_pushcfunction(L, wherigo_Distance); lua_setglobal(L, "Distance"); // Register Player as global wherigo_Player(L); lua_setglobal(L, "Player"); return 1; } } // namespace wherigo