- app icon - starting with map view - code cleanup Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
620 lines
15 KiB
C++
620 lines
15 KiB
C++
#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 <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>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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) {
|
|
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<wxString> 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<DialogEntry> 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<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;
|
|
}
|
|
|
|
// 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
|