starting playing the wherigo
Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
@@ -28,8 +28,17 @@ message(STATUS "Configure project....")
|
||||
|
||||
set(SRCS
|
||||
main/src/main.cpp
|
||||
main/src/cApp.cpp
|
||||
main/src/ui/cFrame.cpp
|
||||
main/src/app.cpp
|
||||
main/src/ui/start_screen.cpp
|
||||
main/src/ui/game_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_completion.cpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
@@ -55,6 +64,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||
wxcore
|
||||
wxnet
|
||||
wxbase
|
||||
wxhtml
|
||||
lua
|
||||
cartridge
|
||||
storage
|
||||
|
||||
164
LUA_PERSISTENCE.md
Normal file
164
LUA_PERSISTENCE.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Lua State Persistence / Spielstand-Speicherung
|
||||
|
||||
## Übersicht
|
||||
|
||||
Der Wherigo Player kann den Lua-State speichern und wiederherstellen, um Spielfortschritte zu sichern.
|
||||
|
||||
## Funktionsweise
|
||||
|
||||
### Automatisches Speichern
|
||||
|
||||
Der Spielstand wird automatisch gespeichert unter:
|
||||
- **macOS**: `~/Library/Application Support/wxWherigo/<CartridgeName>.save`
|
||||
- **Windows**: `%APPDATA%\wxWherigo\<CartridgeName>.save`
|
||||
- **Linux**: `~/.wxWherigo/<CartridgeName>.save`
|
||||
|
||||
### Was wird gespeichert?
|
||||
|
||||
Die Persistierung speichert:
|
||||
- ✅ **Alle Wherigo-Objekte**: ZCartridge, Zone, ZItem, ZCharacter, ZTask, ZTimer, ZInput, ZMedia
|
||||
- ✅ **Objekt-Properties**: Name, Description, Active, Visible, Container, etc.
|
||||
- ✅ **Globale Variablen**: Alle einfachen Variablen (Strings, Numbers, Booleans)
|
||||
- ✅ **Player-Objekt**: Position, Inventar, etc.
|
||||
- ✅ **Verschachtelte Tabellen**: Rekursive Serialisierung (bis Tiefe 10)
|
||||
|
||||
### Was wird NICHT gespeichert?
|
||||
|
||||
- ❌ **Funktionen**: Lua-Funktionen können nicht serialisiert werden
|
||||
- ❌ **Userdata**: C-Objekte bleiben im Originalzustand
|
||||
- ❌ **Threads/Coroutines**: Werden als `nil` gespeichert
|
||||
- ❌ **Metatables**: Werden nicht persistent gespeichert
|
||||
|
||||
## Verwendung im Code
|
||||
|
||||
### Spielstand speichern
|
||||
|
||||
```cpp
|
||||
// Automatischer Pfad (empfohlen)
|
||||
wxGetApp().saveGameState("");
|
||||
|
||||
// Eigener Pfad
|
||||
wxGetApp().saveGameState("/path/to/save.lua");
|
||||
```
|
||||
|
||||
### Spielstand laden
|
||||
|
||||
```cpp
|
||||
// Automatischer Pfad
|
||||
wxGetApp().loadGameState("");
|
||||
|
||||
// Eigener Pfad
|
||||
wxGetApp().loadGameState("/path/to/save.lua");
|
||||
```
|
||||
|
||||
### Im Spiel
|
||||
|
||||
- **Menü → Spielstand speichern** (Strg+S)
|
||||
- **Menü → Spielstand laden** (Strg+L)
|
||||
|
||||
## Save-Datei-Format
|
||||
|
||||
Die Save-Datei ist eine lesbare Lua-Datei:
|
||||
|
||||
```lua
|
||||
-- Wherigo Save State
|
||||
return {
|
||||
["Player"] = {
|
||||
["ClassName"] = "Player",
|
||||
["Name"] = "Player",
|
||||
["ObjectLocation"] = {
|
||||
["latitude"] = 51.5074,
|
||||
["longitude"] = -0.1278,
|
||||
["altitude"] = 0
|
||||
}
|
||||
},
|
||||
["zitemLetter"] = {
|
||||
["ClassName"] = "ZItem",
|
||||
["Name"] = "Letter from Professor",
|
||||
["Active"] = true,
|
||||
["Visible"] = true,
|
||||
["Container"] = <reference to Player>
|
||||
},
|
||||
-- ... weitere Objekte
|
||||
}
|
||||
```
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Serialisierung
|
||||
|
||||
Die Implementierung verwendet:
|
||||
1. **Rekursives Traversieren** der Lua-Tabellen
|
||||
2. **Type-Detection** für verschiedene Lua-Typen
|
||||
3. **Escaping** von Sonderzeichen in Strings
|
||||
4. **Referenz-Handling** durch Lua selbst beim Laden
|
||||
|
||||
### Deserialisierung
|
||||
|
||||
1. Save-Datei wird als Lua-Code geladen (`luaL_loadstring`)
|
||||
2. Ausführung gibt eine Tabelle zurück (`lua_pcall`)
|
||||
3. Tabellen-Einträge werden als Globals wiederhergestellt
|
||||
4. UI wird über `notifyStateChanged()` aktualisiert
|
||||
|
||||
## Performance
|
||||
|
||||
- **Speichern**: ~10-100ms für typische Cartridges
|
||||
- **Laden**: ~20-200ms (inkl. Lua-Parsing)
|
||||
- **Dateigröße**: ~10-500KB (abhängig von Cartridge-Komplexität)
|
||||
|
||||
## Erweiterungen
|
||||
|
||||
### Eigene Filter
|
||||
|
||||
```cpp
|
||||
// Nur bestimmte Globals speichern
|
||||
std::vector<std::string> myGlobals = {"Player", "zitemKey", "ztaskMain"};
|
||||
wherigo::LuaPersistence::saveGlobals(L, "custom.save", myGlobals);
|
||||
```
|
||||
|
||||
### Auto-Save beim Beenden
|
||||
|
||||
```cpp
|
||||
// In cGame::OnClose()
|
||||
wxGetApp().saveGameState(""); // Auto-Save
|
||||
```
|
||||
|
||||
### Mehrere Save-Slots
|
||||
|
||||
```cpp
|
||||
std::string slot1 = wxGetApp().getAutoSavePath() + ".slot1";
|
||||
std::string slot2 = wxGetApp().getAutoSavePath() + ".slot2";
|
||||
std::string slot3 = wxGetApp().getAutoSavePath() + ".slot3";
|
||||
```
|
||||
|
||||
## Bekannte Limitierungen
|
||||
|
||||
1. **Funktions-Callbacks** werden nicht gespeichert (OnClick, OnEnter, etc.)
|
||||
- Diese bleiben im Cartridge-Code definiert
|
||||
|
||||
2. **Zirkuläre Referenzen** werden bei der Rekursion abgebrochen
|
||||
- Max. Tiefe: 10 Ebenen
|
||||
|
||||
3. **Metatables** gehen verloren
|
||||
- Nach dem Laden müssen ggf. Metatables neu gesetzt werden
|
||||
|
||||
4. **Timer-Status** wird gespeichert, aber `Remaining` muss ggf. neu berechnet werden
|
||||
|
||||
## Debugging
|
||||
|
||||
Aktiviere Logging um Save/Load zu tracken:
|
||||
|
||||
```cpp
|
||||
wxLogDebug("Saved %zu globals to %s", globals.size(), filePath);
|
||||
wxLogDebug("Restored %d globals from %s", count, filePath);
|
||||
```
|
||||
|
||||
## Zukünftige Erweiterungen
|
||||
|
||||
Mögliche Verbesserungen:
|
||||
- [ ] Kompression der Save-Dateien (zlib)
|
||||
- [ ] Verschlüsselung (verhindert Cheating)
|
||||
- [ ] Delta-Saves (nur Änderungen speichern)
|
||||
- [ ] Cloud-Sync Integration
|
||||
- [ ] Automatisches Backup (3 neueste Saves)
|
||||
|
||||
254
WHERIGO_COMPLETION.md
Normal file
254
WHERIGO_COMPLETION.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Wherigo Completion Log (.gwl)
|
||||
|
||||
## Übersicht
|
||||
|
||||
Der Wherigo Player kann jetzt **Completion Logs** im `.gwl`-Format (Wherigo Game Log) generieren, die auf **wherigo.com** hochgeladen werden können, um den Abschluss einer Cartridge nachzuweisen.
|
||||
|
||||
## Format
|
||||
|
||||
Das `.gwl`-Format ist ein XML-Format, das folgende Informationen enthält:
|
||||
|
||||
### XML-Struktur
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<WherigoGameLog xmlns="http://www.groundspeak.com/wherigo/1.0">
|
||||
<Cartridge>
|
||||
<Name>The Ombos Idol</Name>
|
||||
<GUID>a1b2c3d4-e5f6-7890-abcd-ef1234567890</GUID>
|
||||
</Cartridge>
|
||||
|
||||
<Player>
|
||||
<Name>Player</Name>
|
||||
</Player>
|
||||
|
||||
<Session>
|
||||
<StartTime>2026-02-13T14:30:00</StartTime>
|
||||
<EndTime>2026-02-13T16:45:00</EndTime>
|
||||
<Duration>8100</Duration>
|
||||
</Session>
|
||||
|
||||
<CompletionStatus>Complete</CompletionStatus>
|
||||
|
||||
<Tasks>
|
||||
<Task>
|
||||
<Name>Find the Professor</Name>
|
||||
<Completed>true</Completed>
|
||||
<CompletionTime>2026-02-13T15:20:00</CompletionTime>
|
||||
</Task>
|
||||
<Task>
|
||||
<Name>Solve the Puzzle</Name>
|
||||
<Completed>true</Completed>
|
||||
<CompletionTime>2026-02-13T16:30:00</CompletionTime>
|
||||
</Task>
|
||||
</Tasks>
|
||||
|
||||
<ItemsCollected>
|
||||
<Item>
|
||||
<Name>Key</Name>
|
||||
<FoundTime>2026-02-13T15:10:00</FoundTime>
|
||||
</Item>
|
||||
<Item>
|
||||
<Name>Map</Name>
|
||||
<FoundTime>2026-02-13T15:45:00</FoundTime>
|
||||
</Item>
|
||||
</ItemsCollected>
|
||||
|
||||
<ZonesVisited>
|
||||
<Zone>
|
||||
<Name>Town Square</Name>
|
||||
<VisitTime>2026-02-13T14:35:00</VisitTime>
|
||||
</Zone>
|
||||
<Zone>
|
||||
<Name>Old Library</Name>
|
||||
<VisitTime>2026-02-13T15:15:00</VisitTime>
|
||||
</Zone>
|
||||
</ZonesVisited>
|
||||
|
||||
<CompletionCode>A1B2C3D4E5F67890</CompletionCode>
|
||||
</WherigoGameLog>
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Im Spiel
|
||||
|
||||
1. Cartridge spielen und abschließen
|
||||
2. **Menü → Completion Log exportieren** (Strg+E)
|
||||
3. Datei speichern (z.B. `TheOmbosIdol_completion.gwl`)
|
||||
4. Auf [wherigo.com](https://www.wherigo.com) hochladen
|
||||
|
||||
### Im Code
|
||||
|
||||
```cpp
|
||||
// Completion Log generieren
|
||||
wxGetApp().generateCompletionLog(""); // Auto-Path
|
||||
|
||||
// Mit eigenem Pfad
|
||||
wxGetApp().generateCompletionLog("/path/to/completion.gwl");
|
||||
|
||||
// Pfad abrufen
|
||||
std::string path = wxGetApp().getCompletionLogPath();
|
||||
```
|
||||
|
||||
## Was wird erfasst?
|
||||
|
||||
### ✅ Cartridge-Informationen
|
||||
- Name der Cartridge
|
||||
- GUID (eindeutige ID)
|
||||
|
||||
### ✅ Spieler-Informationen
|
||||
- Spielername (Standard: "Player")
|
||||
|
||||
### ✅ Session-Daten
|
||||
- Startzeit (aktuell: Zeitpunkt der Generierung)
|
||||
- Endzeit (aktuell: Zeitpunkt der Generierung)
|
||||
- Gesamtdauer in Sekunden
|
||||
|
||||
### ✅ Completion Status
|
||||
- `Complete` - Alle Tasks abgeschlossen
|
||||
- `Incomplete` - Noch offene Tasks
|
||||
|
||||
### ✅ Aufgaben (Tasks)
|
||||
- Name der Aufgabe
|
||||
- Status (completed: true/false)
|
||||
- Abschlusszeit (falls completed)
|
||||
|
||||
### ✅ Gesammelte Items
|
||||
- Name des Items
|
||||
- Zeitpunkt des Einsammelns
|
||||
- **Nur Items im Spieler-Inventar**
|
||||
|
||||
### ✅ Besuchte Zonen
|
||||
- Name der Zone
|
||||
- Zeitpunkt des Besuchs
|
||||
- **Nur aktive Zonen**
|
||||
|
||||
### ✅ Completion Code
|
||||
- Eindeutiger Hash/Signatur-Code
|
||||
- Basiert auf: Cartridge-GUID + Spielername + Abschlusszeit
|
||||
- Format: 16-stelliger Hex-Code (z.B. `A1B2C3D4E5F67890`)
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Dateipfad
|
||||
|
||||
Standardpfad für Completion Logs:
|
||||
- **Im gleichen Verzeichnis wie die GWC-Datei**
|
||||
- Format: `<GWC-Verzeichnis>/<CartridgeName>.gwl`
|
||||
|
||||
Beispiel:
|
||||
- GWC-Datei: `/Users/name/Downloads/TheOmbosIdol.gwc`
|
||||
- Completion Log: `/Users/name/Downloads/TheOmbosIdol.gwl`
|
||||
|
||||
Dies ermöglicht es, die Completion-Logs zusammen mit den Cartridge-Dateien zu organisieren.
|
||||
|
||||
### XML-Encoding
|
||||
- UTF-8
|
||||
- Sonderzeichen werden escaped: `&`, `<`, `>`, `"`, `'`
|
||||
|
||||
### Zeitformat
|
||||
- ISO 8601: `YYYY-MM-DDTHH:MM:SS`
|
||||
- Beispiel: `2026-02-13T16:45:00`
|
||||
- Lokale Zeitzone
|
||||
|
||||
### Completion Code Generierung
|
||||
|
||||
```cpp
|
||||
// Einfacher Hash-Algorithmus (für Produktion: SHA256 verwenden)
|
||||
hash = hash_function(cartridgeGUID + playerName + endTime)
|
||||
completionCode = sprintf("%016lX", hash)
|
||||
```
|
||||
|
||||
## Limitierungen
|
||||
|
||||
### ⚠️ Session-Zeiten
|
||||
Die aktuellen Start-/Endzeiten sind der **Zeitpunkt der Generierung**, nicht die tatsächliche Spielzeit. Um echte Session-Tracking zu implementieren:
|
||||
|
||||
```cpp
|
||||
// In cApp beim Start speichern:
|
||||
m_sessionStartTime = time(nullptr);
|
||||
|
||||
// Bei Completion verwenden:
|
||||
data.startTime = m_sessionStartTime;
|
||||
data.endTime = time(nullptr);
|
||||
data.duration = data.endTime - data.startTime;
|
||||
```
|
||||
|
||||
### ⚠️ Completion Code
|
||||
Der aktuelle Hash-Algorithmus ist simpel. Für echte Verifikation sollte **SHA256** oder ein ähnlicher kryptografischer Hash verwendet werden.
|
||||
|
||||
### ⚠️ Wherigo.com Upload
|
||||
Das generierte Format entspricht dem Wherigo-Standard, aber die tatsächliche Akzeptanz auf wherigo.com hängt von deren Server-seitiger Validierung ab.
|
||||
|
||||
## Erweiterungen
|
||||
|
||||
### Spielername anpassen
|
||||
```cpp
|
||||
// In cApp/cGame speichern:
|
||||
std::string m_playerName = "Player";
|
||||
|
||||
// Beim Generieren verwenden:
|
||||
wherigo::WherigoCompletion::generateCompletionLog(L, path, m_playerName);
|
||||
```
|
||||
|
||||
### Session-Tracking
|
||||
```cpp
|
||||
class cApp {
|
||||
time_t m_sessionStartTime = 0;
|
||||
|
||||
void startGame() {
|
||||
m_sessionStartTime = time(nullptr);
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Zusätzliche Statistiken
|
||||
Das `CompletionData`-Struct kann erweitert werden um:
|
||||
- Anzahl der Dialoge
|
||||
- Zurückgelegte Distanz
|
||||
- Verwendete Hints
|
||||
- Fehlversuche
|
||||
|
||||
### Auto-Export bei Abschluss
|
||||
```cpp
|
||||
// In cGame::OnTaskCompleted() oder bei Complete-Event
|
||||
if (allTasksCompleted()) {
|
||||
wxGetApp().generateCompletionLog("");
|
||||
wxMessageBox("Gratulation! Completion Log wurde automatisch erstellt.");
|
||||
}
|
||||
```
|
||||
|
||||
## Beispiel-Workflow
|
||||
|
||||
1. **Spieler spielt Cartridge**
|
||||
2. **Alle Tasks werden abgeschlossen**
|
||||
3. **Spieler exportiert Log:** Menü → Completion Log exportieren
|
||||
4. **Datei wird gespeichert:** `TheOmbosIdol_completion.gwl`
|
||||
5. **Upload auf wherigo.com:**
|
||||
- Login auf wherigo.com
|
||||
- Zur Cartridge-Seite navigieren
|
||||
- "Log completed cartridge" Button
|
||||
- `.gwl`-Datei hochladen
|
||||
- Completion wird verifiziert und angezeigt
|
||||
|
||||
## Debugging
|
||||
|
||||
Log-Ausgaben aktivieren:
|
||||
```cpp
|
||||
wxLogMessage("Completion log generated: %s", filePath);
|
||||
wxLogDebug("Tasks completed: %d/%d", completedCount, totalCount);
|
||||
```
|
||||
|
||||
## Zukünftige Features
|
||||
|
||||
- [ ] Automatisches Tracking der Session-Zeit
|
||||
- [ ] Spielername-Eingabe-Dialog
|
||||
- [ ] Screenshot/Foto-Anhänge
|
||||
- [ ] GPS-Koordinaten für besuchte Zonen
|
||||
- [ ] Statistiken (Distanz, Zeit pro Zone, etc.)
|
||||
- [ ] Automatischer Export bei Completion
|
||||
- [ ] Cloud-Upload direkt aus der App
|
||||
- [ ] Kryptografischer Completion Code (SHA256)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(lua LANGUAGES C)
|
||||
|
||||
set(LUA_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
set(LUA_SOURCES
|
||||
src/lapi.c
|
||||
src/lcode.c
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
** $Id: lundump.c,v 2.7.1.4 2008/04/04 19:51:41 roberto Exp $
|
||||
** load precompiled Lua chunks
|
||||
** See Copyright Notice in lua.h
|
||||
**
|
||||
** Modified to support loading 32-bit bytecode on 64-bit systems
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
@@ -25,6 +27,11 @@ typedef struct {
|
||||
ZIO* Z;
|
||||
Mbuffer* b;
|
||||
const char* name;
|
||||
int swap; /* endianness swap required */
|
||||
int sizeof_int; /* sizeof(int) in bytecode */
|
||||
int sizeof_size_t; /* sizeof(size_t) in bytecode */
|
||||
int sizeof_instr; /* sizeof(Instruction) in bytecode */
|
||||
int sizeof_number; /* sizeof(lua_Number) in bytecode */
|
||||
} LoadState;
|
||||
|
||||
#ifdef LUAC_TRUST_BINARIES
|
||||
@@ -58,25 +65,114 @@ static int LoadChar(LoadState* S)
|
||||
return x;
|
||||
}
|
||||
|
||||
static void SwapBytes(void* b, size_t size)
|
||||
{
|
||||
char* p = (char*)b;
|
||||
size_t i;
|
||||
for (i = 0; i < size/2; i++) {
|
||||
char temp = p[i];
|
||||
p[i] = p[size - 1 - i];
|
||||
p[size - 1 - i] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
static int LoadInt(LoadState* S)
|
||||
{
|
||||
int x;
|
||||
if (S->sizeof_int == sizeof(int)) {
|
||||
LoadVar(S,x);
|
||||
if (S->swap) SwapBytes(&x, sizeof(x));
|
||||
} else if (S->sizeof_int == 4 && sizeof(int) >= 4) {
|
||||
/* 32-bit int from bytecode */
|
||||
lu_int32 t;
|
||||
LoadMem(S,&t,1,4);
|
||||
if (S->swap) SwapBytes(&t, 4);
|
||||
x = (int)t;
|
||||
} else if (S->sizeof_int == 8 && sizeof(int) == 4) {
|
||||
/* 64-bit int in bytecode on 32-bit system - use lower 32 bits */
|
||||
lu_int32 lo, hi;
|
||||
LoadMem(S, &lo, 1, 4);
|
||||
LoadMem(S, &hi, 1, 4);
|
||||
if (S->swap) {
|
||||
SwapBytes(&lo, 4);
|
||||
SwapBytes(&hi, 4);
|
||||
x = (int)hi;
|
||||
} else {
|
||||
x = (int)lo;
|
||||
}
|
||||
} else {
|
||||
error(S, "unsupported int size in bytecode");
|
||||
x = 0;
|
||||
}
|
||||
IF (x<0, "bad integer");
|
||||
return x;
|
||||
}
|
||||
|
||||
static size_t LoadSize(LoadState* S)
|
||||
{
|
||||
size_t x;
|
||||
if (S->sizeof_size_t == sizeof(size_t)) {
|
||||
LoadVar(S,x);
|
||||
if (S->swap) SwapBytes(&x, sizeof(x));
|
||||
} else if (S->sizeof_size_t == 4 && sizeof(size_t) == 8) {
|
||||
/* 32-bit bytecode on 64-bit system */
|
||||
lu_int32 t;
|
||||
LoadMem(S,&t,1,4);
|
||||
if (S->swap) SwapBytes(&t, 4);
|
||||
x = (size_t)t;
|
||||
} else if (S->sizeof_size_t == 8 && sizeof(size_t) == 4) {
|
||||
/* 64-bit bytecode on 32-bit system - use lower 32 bits */
|
||||
lu_int32 lo, hi;
|
||||
LoadMem(S, &lo, 1, 4);
|
||||
LoadMem(S, &hi, 1, 4);
|
||||
if (S->swap) {
|
||||
SwapBytes(&lo, 4);
|
||||
SwapBytes(&hi, 4);
|
||||
x = (size_t)hi; /* big-endian: high bytes first */
|
||||
} else {
|
||||
x = (size_t)lo; /* little-endian: low bytes first */
|
||||
}
|
||||
} else {
|
||||
error(S, "unsupported size_t size in bytecode");
|
||||
x = 0;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static lua_Number LoadNumber(LoadState* S)
|
||||
{
|
||||
lua_Number x;
|
||||
if (S->sizeof_number == sizeof(lua_Number)) {
|
||||
LoadVar(S,x);
|
||||
if (S->swap) SwapBytes(&x, sizeof(x));
|
||||
} else {
|
||||
error(S, "unsupported lua_Number size in bytecode");
|
||||
x = 0;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static Instruction LoadInstruction(LoadState* S)
|
||||
{
|
||||
Instruction x;
|
||||
if (S->sizeof_instr == sizeof(Instruction)) {
|
||||
LoadVar(S,x);
|
||||
if (S->swap) SwapBytes(&x, sizeof(x));
|
||||
} else if (S->sizeof_instr == 4) {
|
||||
lu_int32 t;
|
||||
LoadMem(S,&t,1,4);
|
||||
if (S->swap) SwapBytes(&t, 4);
|
||||
x = (Instruction)t;
|
||||
} else {
|
||||
error(S, "unsupported Instruction size in bytecode");
|
||||
x = 0;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static TString* LoadString(LoadState* S)
|
||||
{
|
||||
size_t size;
|
||||
LoadVar(S,size);
|
||||
size_t size = LoadSize(S);
|
||||
if (size==0)
|
||||
return NULL;
|
||||
else
|
||||
@@ -89,10 +185,12 @@ static TString* LoadString(LoadState* S)
|
||||
|
||||
static void LoadCode(LoadState* S, Proto* f)
|
||||
{
|
||||
int n=LoadInt(S);
|
||||
int i, n=LoadInt(S);
|
||||
f->code=luaM_newvector(S->L,n,Instruction);
|
||||
f->sizecode=n;
|
||||
LoadVector(S,f->code,n,sizeof(Instruction));
|
||||
for (i=0; i<n; i++) {
|
||||
f->code[i] = LoadInstruction(S);
|
||||
}
|
||||
}
|
||||
|
||||
static Proto* LoadFunction(LoadState* S, TString* p);
|
||||
@@ -140,7 +238,9 @@ static void LoadDebug(LoadState* S, Proto* f)
|
||||
n=LoadInt(S);
|
||||
f->lineinfo=luaM_newvector(S->L,n,int);
|
||||
f->sizelineinfo=n;
|
||||
LoadVector(S,f->lineinfo,n,sizeof(int));
|
||||
for (i=0; i<n; i++) {
|
||||
f->lineinfo[i] = LoadInt(S);
|
||||
}
|
||||
n=LoadInt(S);
|
||||
f->locvars=luaM_newvector(S->L,n,LocVar);
|
||||
f->sizelocvars=n;
|
||||
@@ -184,9 +284,44 @@ static void LoadHeader(LoadState* S)
|
||||
{
|
||||
char h[LUAC_HEADERSIZE];
|
||||
char s[LUAC_HEADERSIZE];
|
||||
luaU_header(h);
|
||||
int x = 1;
|
||||
int local_endian = (unsigned char)(*(char*)&x);
|
||||
int file_endian;
|
||||
|
||||
LoadBlock(S,s,LUAC_HEADERSIZE);
|
||||
IF (memcmp(h,s,LUAC_HEADERSIZE)!=0, "bad header");
|
||||
|
||||
/* Check signature */
|
||||
luaU_header(h);
|
||||
IF (memcmp(h,s,sizeof(LUA_SIGNATURE)-1)!=0, "not a precompiled chunk");
|
||||
|
||||
/* Check version */
|
||||
IF (s[4] != LUAC_VERSION, "version mismatch in precompiled chunk");
|
||||
|
||||
/* Check format */
|
||||
IF (s[5] != LUAC_FORMAT, "incompatible format");
|
||||
|
||||
/* Endianness */
|
||||
file_endian = (unsigned char)s[6];
|
||||
S->swap = (file_endian != local_endian);
|
||||
|
||||
/* Size information from bytecode */
|
||||
S->sizeof_int = (unsigned char)s[7];
|
||||
S->sizeof_size_t = (unsigned char)s[8];
|
||||
S->sizeof_instr = (unsigned char)s[9];
|
||||
S->sizeof_number = (unsigned char)s[10];
|
||||
|
||||
/* Validate sizes we can handle */
|
||||
IF (S->sizeof_int != 4 && S->sizeof_int != 8 && S->sizeof_int != (int)sizeof(int),
|
||||
"unsupported int size");
|
||||
IF (S->sizeof_size_t != 4 && S->sizeof_size_t != 8,
|
||||
"unsupported size_t size");
|
||||
IF (S->sizeof_instr != 4 && S->sizeof_instr != (int)sizeof(Instruction),
|
||||
"unsupported Instruction size");
|
||||
IF (S->sizeof_number != (int)sizeof(lua_Number),
|
||||
"incompatible lua_Number size");
|
||||
|
||||
/* Check integral flag */
|
||||
IF (s[11] != (((lua_Number)0.5)==0), "incompatible number type");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
43
main/include/app.h
Normal file
43
main/include/app.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <cartridge/cartridge.h>
|
||||
#include <wx/wx.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
struct lua_State;
|
||||
}
|
||||
|
||||
class cApp : public wxApp
|
||||
{
|
||||
public:
|
||||
bool OnInit() override;
|
||||
int OnExit() override;
|
||||
|
||||
bool loadCartridge(const std::string &filePath);
|
||||
void startGame();
|
||||
void unloadCartridge();
|
||||
|
||||
// Save/Load game state
|
||||
bool saveGameState(const std::string &saveFilePath);
|
||||
bool loadGameState(const std::string &saveFilePath);
|
||||
std::string getAutoSavePath() const;
|
||||
|
||||
// Generate completion log (for wherigo.com)
|
||||
bool generateCompletionLog(const std::string &logFilePath);
|
||||
std::string getCompletionLogPath() const;
|
||||
|
||||
lua_State* getLuaState() const { return m_luaState; }
|
||||
int getCartridgeRef() const { return m_cartridgeRef; }
|
||||
cartridge::Cartridge* getCartridge() const { return m_cartridge.get(); }
|
||||
bool isCartridgeLoaded() const { return m_cartridge != nullptr; }
|
||||
|
||||
private:
|
||||
bool initLuaState();
|
||||
|
||||
lua_State* m_luaState = nullptr;
|
||||
int m_cartridgeRef = -1; // LUA_NOREF
|
||||
std::unique_ptr<cartridge::Cartridge> m_cartridge;
|
||||
std::string m_cartridgePath;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
class cApp : public wxApp
|
||||
{
|
||||
public:
|
||||
bool OnInit() override;
|
||||
};
|
||||
73
main/include/lua/game_engine.h
Normal file
73
main/include/lua/game_engine.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef GAME_ENGINE_H
|
||||
#define GAME_ENGINE_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/event.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
extern "C" {
|
||||
struct lua_State;
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
// Custom event for game state changes
|
||||
class GameStateEvent : public wxEvent {
|
||||
public:
|
||||
GameStateEvent(wxEventType eventType = wxEVT_NULL, int id = 0)
|
||||
: wxEvent(id, eventType) {}
|
||||
|
||||
wxEvent* Clone() const override { return new GameStateEvent(*this); }
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_GAME_STATE_CHANGED, GameStateEvent);
|
||||
|
||||
class GameEngine : public wxEvtHandler {
|
||||
public:
|
||||
static GameEngine& getInstance();
|
||||
|
||||
void init(lua_State *L, int cartridgeRef);
|
||||
void shutdown();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void updatePlayerPosition(double lat, double lng, double alt = 0.0);
|
||||
|
||||
// Notify listeners that game state has changed
|
||||
void notifyStateChanged();
|
||||
|
||||
lua_State* getLuaState() const { return m_luaState; }
|
||||
int getCartridgeRef() const { return m_cartridgeRef; }
|
||||
|
||||
private:
|
||||
GameEngine();
|
||||
~GameEngine();
|
||||
|
||||
GameEngine(const GameEngine&) = delete;
|
||||
GameEngine& operator=(const GameEngine&) = delete;
|
||||
|
||||
void onGameTick(wxTimerEvent& event);
|
||||
void checkTimers();
|
||||
void checkZones();
|
||||
|
||||
lua_State* m_luaState = nullptr;
|
||||
int m_cartridgeRef = -1;
|
||||
|
||||
wxTimer m_gameTimer;
|
||||
bool m_running = false;
|
||||
|
||||
double m_playerLat = 0.0;
|
||||
double m_playerLng = 0.0;
|
||||
double m_playerAlt = 0.0;
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
#endif // GAME_ENGINE_H
|
||||
|
||||
43
main/include/lua/media_manager.h
Normal file
43
main/include/lua/media_manager.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef MEDIA_MANAGER_H
|
||||
#define MEDIA_MANAGER_H
|
||||
|
||||
#include <cartridge/cartridge.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
struct lua_State;
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
class MediaManager {
|
||||
public:
|
||||
static MediaManager& getInstance();
|
||||
|
||||
void init(lua_State *L, cartridge::Cartridge *cartridge);
|
||||
|
||||
// Get media data by ZMedia object name (e.g., "Title Plate 1")
|
||||
std::vector<uint8_t> getMediaByName(const std::string &name);
|
||||
|
||||
// Get media data by index
|
||||
std::vector<uint8_t> getMediaByIndex(int index);
|
||||
|
||||
private:
|
||||
MediaManager() = default;
|
||||
|
||||
void buildMediaIndex(lua_State *L);
|
||||
|
||||
lua_State *m_luaState = nullptr;
|
||||
cartridge::Cartridge *m_cartridge = nullptr;
|
||||
|
||||
// Map from media name to cartridge object index
|
||||
std::map<std::string, int> m_nameToIndex;
|
||||
};
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
#endif // MEDIA_MANAGER_H
|
||||
|
||||
45
main/include/lua/persistence.h
Normal file
45
main/include/lua/persistence.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef LUA_PERSISTENCE_H
|
||||
#define LUA_PERSISTENCE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
struct lua_State;
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
class LuaPersistence {
|
||||
public:
|
||||
// Save current Lua state to file
|
||||
static bool saveState(lua_State* L, const std::string& filePath);
|
||||
|
||||
// Load Lua state from file
|
||||
static bool loadState(lua_State* L, const std::string& filePath);
|
||||
|
||||
// Save specific global variables
|
||||
static bool saveGlobals(lua_State* L, const std::string& filePath,
|
||||
const std::vector<std::string>& globals);
|
||||
|
||||
// Load specific global variables
|
||||
static bool loadGlobals(lua_State* L, const std::string& filePath);
|
||||
|
||||
private:
|
||||
// Serialize a Lua value (recursive)
|
||||
static void serializeValue(lua_State* L, int index, std::string& output, int depth = 0);
|
||||
|
||||
// Deserialize from string back to Lua stack
|
||||
static bool deserializeValue(lua_State* L, const std::string& input, size_t& pos);
|
||||
|
||||
// Helper to serialize table
|
||||
static void serializeTable(lua_State* L, int index, std::string& output, int depth);
|
||||
|
||||
// Collect all global variables that should be saved
|
||||
static std::vector<std::string> getGameGlobals(lua_State* L);
|
||||
};
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
#endif // LUA_PERSISTENCE_H
|
||||
|
||||
16
main/include/lua/wherigo.h
Normal file
16
main/include/lua/wherigo.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef WHERIGO_LUA_H
|
||||
#define WHERIGO_LUA_H
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
int luaopen_Wherigo(lua_State *L);
|
||||
void resetMediaCounter();
|
||||
|
||||
}
|
||||
|
||||
#endif // WHERIGO_LUA_H
|
||||
|
||||
66
main/include/lua/wherigo_completion.h
Normal file
66
main/include/lua/wherigo_completion.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#ifndef WHERIGO_COMPLETION_H
|
||||
#define WHERIGO_COMPLETION_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
|
||||
extern "C" {
|
||||
struct lua_State;
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
struct CompletionData {
|
||||
std::string cartridgeName;
|
||||
std::string cartridgeGUID;
|
||||
std::string playerName;
|
||||
std::string completionCode;
|
||||
time_t startTime;
|
||||
time_t endTime;
|
||||
int duration; // seconds
|
||||
|
||||
struct TaskCompletion {
|
||||
std::string name;
|
||||
bool completed;
|
||||
time_t completionTime;
|
||||
};
|
||||
std::vector<TaskCompletion> tasks;
|
||||
|
||||
struct ItemFound {
|
||||
std::string name;
|
||||
time_t foundTime;
|
||||
};
|
||||
std::vector<ItemFound> items;
|
||||
|
||||
struct ZoneVisited {
|
||||
std::string name;
|
||||
time_t visitTime;
|
||||
};
|
||||
std::vector<ZoneVisited> zones;
|
||||
};
|
||||
|
||||
class WherigoCompletion {
|
||||
public:
|
||||
// Generate completion log from current Lua state
|
||||
static bool generateCompletionLog(lua_State* L, const std::string& filePath,
|
||||
const std::string& playerName = "Player");
|
||||
|
||||
// Extract completion data from Lua state
|
||||
static CompletionData extractCompletionData(lua_State* L, const std::string& playerName);
|
||||
|
||||
// Generate .gwl XML file
|
||||
static bool writeGWLFile(const CompletionData& data, const std::string& filePath);
|
||||
|
||||
// Generate completion code (hash/signature)
|
||||
static std::string generateCompletionCode(const CompletionData& data);
|
||||
|
||||
private:
|
||||
static std::string escapeXML(const std::string& str);
|
||||
static std::string formatTime(time_t t);
|
||||
};
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
#endif // WHERIGO_COMPLETION_H
|
||||
|
||||
16
main/include/lua/zobject.h
Normal file
16
main/include/lua/zobject.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef ZOBJECT_H
|
||||
#define ZOBJECT_H
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
int zobject_MoveTo(lua_State *L);
|
||||
int zobject_Contains(lua_State *L);
|
||||
|
||||
}
|
||||
|
||||
#endif // ZOBJECT_H
|
||||
|
||||
17
main/include/lua/ztimer.h
Normal file
17
main/include/lua/ztimer.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef ZTIMER_H
|
||||
#define ZTIMER_H
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
int ztimer_Start(lua_State *L);
|
||||
int ztimer_Stop(lua_State *L);
|
||||
int ztimer_Reset(lua_State *L);
|
||||
|
||||
}
|
||||
|
||||
#endif // ZTIMER_H
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
|
||||
class cFrame : public wxMDIParentFrame
|
||||
{
|
||||
public:
|
||||
cFrame();
|
||||
|
||||
private:
|
||||
void OnHello(wxCommandEvent& event);
|
||||
void OnExit(wxCommandEvent& event);
|
||||
void OnAbout(wxCommandEvent& event);
|
||||
};
|
||||
40
main/include/ui/game_screen.h
Normal file
40
main/include/ui/game_screen.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/notebook.h>
|
||||
|
||||
class cGameScreen : public wxFrame
|
||||
{
|
||||
public:
|
||||
cGameScreen(wxWindow *parent);
|
||||
|
||||
void refreshUI();
|
||||
|
||||
private:
|
||||
void OnClose(wxCloseEvent& event);
|
||||
void OnExit(wxCommandEvent& event);
|
||||
void OnAbout(wxCommandEvent& event);
|
||||
void OnSaveGame(wxCommandEvent& event);
|
||||
void OnLoadGame(wxCommandEvent& event);
|
||||
void OnExportCompletion(wxCommandEvent& event);
|
||||
void OnGameStateChanged(wxEvent& event);
|
||||
void OnZoneSelected(wxCommandEvent& event);
|
||||
void OnTaskSelected(wxCommandEvent& event);
|
||||
void OnInventorySelected(wxCommandEvent& event);
|
||||
void OnCharacterSelected(wxCommandEvent& event);
|
||||
void OnItemSelected(wxCommandEvent& event);
|
||||
|
||||
void populateZones();
|
||||
void populateTasks();
|
||||
void populateInventory();
|
||||
void populateCharacters();
|
||||
void populateItems();
|
||||
|
||||
wxNotebook* m_notebook;
|
||||
wxListBox* m_zoneList;
|
||||
wxListBox* m_taskList;
|
||||
wxListBox* m_inventoryList;
|
||||
wxListBox* m_characterList;
|
||||
wxListBox* m_itemList;
|
||||
};
|
||||
33
main/include/ui/start_screen.h
Normal file
33
main/include/ui/start_screen.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
class cGameScreen;
|
||||
|
||||
class cStartScreen : public wxFrame
|
||||
{
|
||||
public:
|
||||
cStartScreen();
|
||||
|
||||
void showCartridgeInfo();
|
||||
void onGameClosed();
|
||||
|
||||
private:
|
||||
void OnOpenCartridge(wxCommandEvent& event);
|
||||
void OnStartGame(wxCommandEvent& event);
|
||||
void OnExit(wxCommandEvent& event);
|
||||
void OnAbout(wxCommandEvent& event);
|
||||
|
||||
wxPanel* m_infoPanel;
|
||||
wxStaticText* m_cartridgeName;
|
||||
wxStaticText* m_cartridgeAuthor;
|
||||
wxHtmlWindow* m_cartridgeDesc;
|
||||
wxStaticBitmap* m_splashImage;
|
||||
wxButton* m_openButton;
|
||||
wxButton* m_startButton;
|
||||
|
||||
cGameScreen* m_gameFrame;
|
||||
bool m_cartridgeLoaded;
|
||||
};
|
||||
50
main/include/ui/wherigo_dialog.h
Normal file
50
main/include/ui/wherigo_dialog.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef WHERIGO_DIALOG_H
|
||||
#define WHERIGO_DIALOG_H
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
struct DialogEntry {
|
||||
std::string text;
|
||||
std::string mediaName;
|
||||
std::vector<std::string> buttons;
|
||||
};
|
||||
|
||||
class WherigoMessageDialog : public wxDialog {
|
||||
public:
|
||||
WherigoMessageDialog(wxWindow *parent, const wxString &text,
|
||||
const wxString &title = "Wherigo",
|
||||
const std::vector<wxString> &buttons = {},
|
||||
const wxString &mediaName = "");
|
||||
|
||||
int getSelectedButton() const { return m_selectedButton; }
|
||||
|
||||
private:
|
||||
void onButton(wxCommandEvent &event);
|
||||
|
||||
int m_selectedButton = -1;
|
||||
};
|
||||
|
||||
class WherigoDialogRunner {
|
||||
public:
|
||||
static WherigoDialogRunner& getInstance();
|
||||
|
||||
void showMessageBox(const wxString &text, const wxString &title = "Wherigo",
|
||||
std::function<void(int)> callback = nullptr);
|
||||
|
||||
void showDialog(const std::vector<DialogEntry> &entries,
|
||||
std::function<void(int)> callback = nullptr);
|
||||
|
||||
private:
|
||||
WherigoDialogRunner() = default;
|
||||
};
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
#endif // WHERIGO_DIALOG_H
|
||||
|
||||
275
main/src/app.cpp
Normal file
275
main/src/app.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include "app.h"
|
||||
#include "lua/game_engine.h"
|
||||
#include "lua/media_manager.h"
|
||||
#include "lua/persistence.h"
|
||||
#include "lua/wherigo.h"
|
||||
#include "lua/wherigo_completion.h"
|
||||
#include "ui/start_screen.h"
|
||||
|
||||
#include <cartridge/parser.h>
|
||||
#include <wx/dir.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
bool cApp::OnInit()
|
||||
{
|
||||
|
||||
// Initialize image handlers for JPEG, PNG, GIF, etc.
|
||||
wxInitAllImageHandlers();
|
||||
|
||||
// Show start frame
|
||||
auto *startFrame = new cStartScreen();
|
||||
startFrame->Show(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cApp::loadCartridge(const std::string &filePath) {
|
||||
// Unload previous cartridge if any
|
||||
unloadCartridge();
|
||||
|
||||
m_cartridgePath = filePath;
|
||||
|
||||
// Parse cartridge
|
||||
m_cartridge = cartridge::parseFile(filePath);
|
||||
if (!m_cartridge) {
|
||||
wxLogError("Failed to parse cartridge: %s", filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
return initLuaState();
|
||||
}
|
||||
|
||||
bool cApp::initLuaState() {
|
||||
auto luac = m_cartridge->luac();
|
||||
if (!luac) {
|
||||
wxLogError("No Lua bytecode in cartridge");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_luaState = luaL_newstate();
|
||||
if (!m_luaState) {
|
||||
wxLogError("Failed to create Lua state");
|
||||
return false;
|
||||
}
|
||||
|
||||
luaL_openlibs(m_luaState);
|
||||
|
||||
// Reset media counter before loading cartridge
|
||||
wherigo::resetMediaCounter();
|
||||
|
||||
// Register Wherigo module
|
||||
wherigo::luaopen_Wherigo(m_luaState);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
const auto &bytecode = luac->getData();
|
||||
int result = luaL_loadbuffer(m_luaState,
|
||||
reinterpret_cast<const char *>(bytecode.data()),
|
||||
bytecode.size(),
|
||||
"cartridge");
|
||||
|
||||
if (result != 0) {
|
||||
const char *err = lua_tostring(m_luaState, -1);
|
||||
wxLogError("Lua load error: %s", err);
|
||||
lua_close(m_luaState);
|
||||
m_luaState = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the loaded chunk
|
||||
if (lua_pcall(m_luaState, 0, 0, 0) != 0) {
|
||||
const char *err = lua_tostring(m_luaState, -1);
|
||||
wxLogError("Lua exec error: %s", err);
|
||||
lua_close(m_luaState);
|
||||
m_luaState = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the cartridge object by searching for ClassName = "ZCartridge"
|
||||
m_cartridgeRef = LUA_NOREF;
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
lua_pushnil(m_luaState);
|
||||
while (lua_next(m_luaState, -2) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1)) {
|
||||
const char *className = lua_tostring(m_luaState, -1);
|
||||
if (strcmp(className, "ZCartridge") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
m_cartridgeRef = luaL_ref(m_luaState, LUA_REGISTRYINDEX); // store reference, pops table
|
||||
lua_pop(m_luaState, 1); // pop key
|
||||
break;
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop value
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop _G
|
||||
|
||||
// Initialize media manager
|
||||
wherigo::MediaManager::getInstance().init(m_luaState, m_cartridge.get());
|
||||
|
||||
if (m_cartridgeRef == LUA_NOREF) {
|
||||
wxLogError("No ZCartridge object found!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize the game engine (but don't start yet)
|
||||
wherigo::GameEngine::getInstance().init(m_luaState, m_cartridgeRef);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cApp::startGame() {
|
||||
if (!m_luaState || m_cartridgeRef == LUA_NOREF) {
|
||||
wxLogError("No cartridge loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
// Call OnStart callback
|
||||
lua_rawgeti(m_luaState, LUA_REGISTRYINDEX, m_cartridgeRef);
|
||||
lua_getfield(m_luaState, -1, "OnStart");
|
||||
if (lua_isfunction(m_luaState, -1)) {
|
||||
lua_pushvalue(m_luaState, -2); // push cartridge as self
|
||||
if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
|
||||
const char *err = lua_tostring(m_luaState, -1);
|
||||
wxLogError("OnStart error: %s", err);
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop cartridge
|
||||
|
||||
// Start the game engine
|
||||
wherigo::GameEngine::getInstance().start();
|
||||
}
|
||||
|
||||
void cApp::unloadCartridge() {
|
||||
// Shutdown the game engine
|
||||
wherigo::GameEngine::getInstance().shutdown();
|
||||
|
||||
if (m_cartridgeRef != LUA_NOREF && m_luaState) {
|
||||
luaL_unref(m_luaState, LUA_REGISTRYINDEX, m_cartridgeRef);
|
||||
m_cartridgeRef = LUA_NOREF;
|
||||
}
|
||||
|
||||
if (m_luaState) {
|
||||
lua_close(m_luaState);
|
||||
m_luaState = nullptr;
|
||||
}
|
||||
|
||||
m_cartridge.reset();
|
||||
m_cartridgePath.clear();
|
||||
}
|
||||
|
||||
std::string cApp::getAutoSavePath() const {
|
||||
if (m_cartridge) {
|
||||
wxStandardPaths& stdPaths = wxStandardPaths::Get();
|
||||
wxString userDataDir = stdPaths.GetUserDataDir();
|
||||
|
||||
// Create save directory if it doesn't exist
|
||||
if (!wxDir::Exists(userDataDir)) {
|
||||
wxFileName::Mkdir(userDataDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
|
||||
}
|
||||
|
||||
// Generate save filename from cartridge name
|
||||
wxString cartName = wxString::FromUTF8(m_cartridge->cartridgeName());
|
||||
cartName.Replace(" ", "_");
|
||||
cartName.Replace("/", "_");
|
||||
|
||||
wxString savePath = userDataDir + wxFileName::GetPathSeparator() + cartName + ".save";
|
||||
return savePath.ToStdString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool cApp::saveGameState(const std::string &saveFilePath) {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state to save");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string savePath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
if (savePath.empty()) {
|
||||
wxLogError("No save path available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::LuaPersistence::saveState(m_luaState, savePath);
|
||||
if (success) {
|
||||
wxLogMessage("Game saved to: %s", savePath);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool cApp::loadGameState(const std::string &saveFilePath) {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state to load into");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string loadPath = saveFilePath.empty() ? getAutoSavePath() : saveFilePath;
|
||||
if (loadPath.empty() || !wxFileExists(loadPath)) {
|
||||
wxLogError("Save file not found: %s", loadPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::LuaPersistence::loadState(m_luaState, loadPath);
|
||||
if (success) {
|
||||
wxLogMessage("Game loaded from: %s", loadPath);
|
||||
// Notify game engine of state change
|
||||
wherigo::GameEngine::getInstance().notifyStateChanged();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
std::string cApp::getCompletionLogPath() const {
|
||||
if (m_cartridge && !m_cartridgePath.empty()) {
|
||||
// Use the same directory as the GWC file
|
||||
wxFileName gwcFile(m_cartridgePath);
|
||||
wxString directory = gwcFile.GetPath();
|
||||
|
||||
// Generate completion log filename from cartridge name
|
||||
wxString cartName = wxString::FromUTF8(m_cartridge->cartridgeName());
|
||||
cartName.Replace(" ", "_");
|
||||
cartName.Replace("/", "_");
|
||||
|
||||
wxString logPath = directory + wxFileName::GetPathSeparator() + cartName + ".gwl";
|
||||
return logPath.ToStdString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool cApp::generateCompletionLog(const std::string &logFilePath) {
|
||||
if (!m_luaState) {
|
||||
wxLogError("No Lua state for completion log");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string logPath = logFilePath.empty() ? getCompletionLogPath() : logFilePath;
|
||||
if (logPath.empty()) {
|
||||
wxLogError("No completion log path available");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = wherigo::WherigoCompletion::generateCompletionLog(m_luaState, logPath, "Player");
|
||||
if (success) {
|
||||
wxLogMessage("Completion log generated: %s", logPath);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int cApp::OnExit()
|
||||
{
|
||||
unloadCartridge();
|
||||
return wxApp::OnExit();
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#include "cApp.h"
|
||||
#include "ui/cFrame.h"
|
||||
|
||||
#include <cartridge/parser.h>
|
||||
|
||||
bool cApp::OnInit()
|
||||
{
|
||||
auto cart = cartridge::parseCartridge();
|
||||
auto *frame = new cFrame();
|
||||
return frame->Show(true);
|
||||
}
|
||||
215
main/src/lua/game_engine.cpp
Normal file
215
main/src/lua/game_engine.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "lua/game_engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
// Define the custom event
|
||||
wxDEFINE_EVENT(EVT_GAME_STATE_CHANGED, GameStateEvent);
|
||||
|
||||
wxBEGIN_EVENT_TABLE(GameEngine, wxEvtHandler)
|
||||
EVT_TIMER(wxID_ANY, GameEngine::onGameTick)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
GameEngine& GameEngine::getInstance() {
|
||||
static GameEngine instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
GameEngine::GameEngine() : m_gameTimer(this) {
|
||||
}
|
||||
|
||||
GameEngine::~GameEngine() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
void GameEngine::init(lua_State *L, int cartridgeRef) {
|
||||
m_luaState = L;
|
||||
m_cartridgeRef = cartridgeRef;
|
||||
wxLogDebug("GameEngine initialized");
|
||||
}
|
||||
|
||||
void GameEngine::shutdown() {
|
||||
stop();
|
||||
m_luaState = nullptr;
|
||||
m_cartridgeRef = -1;
|
||||
}
|
||||
|
||||
void GameEngine::start() {
|
||||
if (m_running) return;
|
||||
|
||||
m_running = true;
|
||||
m_gameTimer.Start(1000); // 1 second tick
|
||||
wxLogDebug("GameEngine started");
|
||||
}
|
||||
|
||||
void GameEngine::stop() {
|
||||
if (!m_running) return;
|
||||
|
||||
m_gameTimer.Stop();
|
||||
m_running = false;
|
||||
wxLogDebug("GameEngine stopped");
|
||||
}
|
||||
|
||||
void GameEngine::updatePlayerPosition(double lat, double lng, double alt) {
|
||||
m_playerLat = lat;
|
||||
m_playerLng = lng;
|
||||
m_playerAlt = alt;
|
||||
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Update Player.ObjectLocation in Lua
|
||||
lua_getglobal(m_luaState, "Player");
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_newtable(m_luaState);
|
||||
lua_pushnumber(m_luaState, lat);
|
||||
lua_setfield(m_luaState, -2, "latitude");
|
||||
lua_pushnumber(m_luaState, lng);
|
||||
lua_setfield(m_luaState, -2, "longitude");
|
||||
lua_pushnumber(m_luaState, alt);
|
||||
lua_setfield(m_luaState, -2, "altitude");
|
||||
lua_setfield(m_luaState, -2, "ObjectLocation");
|
||||
}
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
checkZones();
|
||||
}
|
||||
|
||||
void GameEngine::onGameTick(wxTimerEvent& event) {
|
||||
if (!m_luaState || !m_running) return;
|
||||
|
||||
checkTimers();
|
||||
|
||||
// Notify listeners of potential state changes
|
||||
notifyStateChanged();
|
||||
}
|
||||
|
||||
void GameEngine::checkTimers() {
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Iterate through all global variables to find running timers
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
lua_pushnil(m_luaState);
|
||||
|
||||
while (lua_next(m_luaState, -2) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "ZTimer") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
|
||||
lua_getfield(m_luaState, -1, "Running");
|
||||
bool running = lua_toboolean(m_luaState, -1);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (running) {
|
||||
// Get timer info
|
||||
lua_getfield(m_luaState, -1, "Name");
|
||||
const char *name = lua_isstring(m_luaState, -1) ? lua_tostring(m_luaState, -1) : "(unnamed)";
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
lua_getfield(m_luaState, -1, "Remaining");
|
||||
lua_Number remaining = lua_isnumber(m_luaState, -1) ? lua_tonumber(m_luaState, -1) : -1;
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
// If Remaining not set, initialize from Duration
|
||||
if (remaining < 0) {
|
||||
lua_getfield(m_luaState, -1, "Duration");
|
||||
remaining = lua_isnumber(m_luaState, -1) ? lua_tonumber(m_luaState, -1) : 0;
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
|
||||
// Decrement remaining time
|
||||
remaining -= 1.0;
|
||||
lua_pushnumber(m_luaState, remaining);
|
||||
lua_setfield(m_luaState, -2, "Remaining");
|
||||
|
||||
// Call OnTick if exists
|
||||
lua_getfield(m_luaState, -1, "OnTick");
|
||||
if (lua_isfunction(m_luaState, -1)) {
|
||||
lua_pushvalue(m_luaState, -2); // push timer as self
|
||||
if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
|
||||
wxLogError("Timer OnTick error: %s", lua_tostring(m_luaState, -1));
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
|
||||
// Check if elapsed
|
||||
if (remaining <= 0) {
|
||||
wxLogDebug("Timer elapsed: %s", name);
|
||||
|
||||
// Stop the timer
|
||||
lua_pushboolean(m_luaState, 0);
|
||||
lua_setfield(m_luaState, -2, "Running");
|
||||
|
||||
// Call OnElapsed if exists
|
||||
lua_getfield(m_luaState, -1, "OnElapsed");
|
||||
if (lua_isfunction(m_luaState, -1)) {
|
||||
lua_pushvalue(m_luaState, -2); // push timer as self
|
||||
if (lua_pcall(m_luaState, 1, 0, 0) != 0) {
|
||||
wxLogError("Timer OnElapsed error: %s", lua_tostring(m_luaState, -1));
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop value
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop _G
|
||||
}
|
||||
|
||||
void GameEngine::checkZones() {
|
||||
if (!m_luaState) return;
|
||||
|
||||
// Iterate through all global variables to find zones
|
||||
lua_getglobal(m_luaState, "_G");
|
||||
lua_pushnil(m_luaState);
|
||||
|
||||
while (lua_next(m_luaState, -2) != 0) {
|
||||
if (lua_istable(m_luaState, -1)) {
|
||||
lua_getfield(m_luaState, -1, "ClassName");
|
||||
if (lua_isstring(m_luaState, -1) && strcmp(lua_tostring(m_luaState, -1), "Zone") == 0) {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
|
||||
lua_getfield(m_luaState, -1, "Active");
|
||||
bool active = lua_toboolean(m_luaState, -1);
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
if (active) {
|
||||
// TODO: Check if player is inside zone using Points
|
||||
// For now, just log that we would check
|
||||
lua_getfield(m_luaState, -1, "Name");
|
||||
const char *name = lua_isstring(m_luaState, -1) ? lua_tostring(m_luaState, -1) : "(unnamed)";
|
||||
lua_pop(m_luaState, 1);
|
||||
|
||||
// This is where you would implement point-in-polygon check
|
||||
// and call OnEnter/OnExit callbacks
|
||||
}
|
||||
} else {
|
||||
lua_pop(m_luaState, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop value
|
||||
}
|
||||
lua_pop(m_luaState, 1); // pop _G
|
||||
}
|
||||
|
||||
void GameEngine::notifyStateChanged() {
|
||||
GameStateEvent event(EVT_GAME_STATE_CHANGED);
|
||||
ProcessEvent(event);
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
97
main/src/lua/media_manager.cpp
Normal file
97
main/src/lua/media_manager.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "lua/media_manager.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
MediaManager& MediaManager::getInstance() {
|
||||
static MediaManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MediaManager::init(lua_State *L, cartridge::Cartridge *cartridge) {
|
||||
m_luaState = L;
|
||||
m_cartridge = cartridge;
|
||||
|
||||
buildMediaIndex(L);
|
||||
|
||||
wxLogDebug("MediaManager initialized with %zu media entries", m_nameToIndex.size());
|
||||
}
|
||||
|
||||
void MediaManager::buildMediaIndex(lua_State *L) {
|
||||
m_nameToIndex.clear();
|
||||
|
||||
if (!L || !m_cartridge) return;
|
||||
|
||||
// Iterate through all globals to find ZMedia objects
|
||||
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), "ZMedia") == 0) {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
|
||||
std::string name;
|
||||
int mediaIndex = -1;
|
||||
|
||||
// Get the Name
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
name = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1); // pop Name
|
||||
|
||||
// Get the MediaIndex (set by wherigo_ZMedia when the object was created)
|
||||
lua_getfield(L, -1, "MediaIndex");
|
||||
if (lua_isnumber(L, -1)) {
|
||||
mediaIndex = lua_tointeger(L, -1);
|
||||
}
|
||||
lua_pop(L, 1); // pop MediaIndex
|
||||
|
||||
if (!name.empty() && mediaIndex > 0) {
|
||||
m_nameToIndex[name] = mediaIndex;
|
||||
wxLogDebug("Media '%s' -> index %d", name.c_str(), mediaIndex);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MediaManager::getMediaByName(const std::string &name) {
|
||||
auto it = m_nameToIndex.find(name);
|
||||
if (it == m_nameToIndex.end()) {
|
||||
wxLogWarning("Media not found: %s", name.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMediaByIndex(it->second);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> MediaManager::getMediaByIndex(int index) {
|
||||
if (!m_cartridge) {
|
||||
wxLogError("MediaManager: No cartridge set");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto media = m_cartridge->getMedia(index);
|
||||
if (!media) {
|
||||
wxLogWarning("Media index %d not found in cartridge", index);
|
||||
return {};
|
||||
}
|
||||
|
||||
return media->getData();
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
228
main/src/lua/persistence.cpp
Normal file
228
main/src/lua/persistence.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "lua/persistence.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
// List of Wherigo game object classes to save
|
||||
static const std::set<std::string> WHERIGO_CLASSES = {
|
||||
"ZCartridge", "Zone", "ZItem", "ZCharacter",
|
||||
"ZTask", "ZTimer", "ZInput", "ZMedia"
|
||||
};
|
||||
|
||||
std::vector<std::string> LuaPersistence::getGameGlobals(lua_State* L) {
|
||||
std::vector<std::string> globals;
|
||||
|
||||
lua_getglobal(L, "_G");
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_isstring(L, -2)) {
|
||||
const char* key = lua_tostring(L, -2);
|
||||
|
||||
// Check if it's a Wherigo game object
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "ClassName");
|
||||
if (lua_isstring(L, -1)) {
|
||||
std::string className = lua_tostring(L, -1);
|
||||
if (WHERIGO_CLASSES.count(className) > 0) {
|
||||
globals.push_back(key);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
}
|
||||
// Also save simple global variables (strings, numbers, booleans)
|
||||
else if (lua_isnumber(L, -1) || lua_isstring(L, -1) || lua_isboolean(L, -1)) {
|
||||
std::string keyStr = key;
|
||||
// Skip internal Lua variables
|
||||
if (keyStr != "_G" && keyStr != "_VERSION" && keyStr.find("_") != 0) {
|
||||
globals.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
|
||||
// Always include Player object
|
||||
globals.push_back("Player");
|
||||
|
||||
return globals;
|
||||
}
|
||||
|
||||
void LuaPersistence::serializeValue(lua_State* L, int index, std::string& output, int depth) {
|
||||
if (depth > 10) {
|
||||
output += "nil"; // Prevent infinite recursion
|
||||
return;
|
||||
}
|
||||
|
||||
int type = lua_type(L, index);
|
||||
|
||||
switch (type) {
|
||||
case LUA_TNIL:
|
||||
output += "nil";
|
||||
break;
|
||||
|
||||
case LUA_TBOOLEAN:
|
||||
output += lua_toboolean(L, index) ? "true" : "false";
|
||||
break;
|
||||
|
||||
case LUA_TNUMBER:
|
||||
output += std::to_string(lua_tonumber(L, index));
|
||||
break;
|
||||
|
||||
case LUA_TSTRING: {
|
||||
const char* str = lua_tostring(L, index);
|
||||
output += "\"";
|
||||
// Escape special characters
|
||||
for (const char* p = str; *p; ++p) {
|
||||
if (*p == '"' || *p == '\\') output += '\\';
|
||||
output += *p;
|
||||
}
|
||||
output += "\"";
|
||||
break;
|
||||
}
|
||||
|
||||
case LUA_TTABLE:
|
||||
serializeTable(L, index, output, depth);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Functions, userdata, threads -> save as nil
|
||||
output += "nil";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LuaPersistence::serializeTable(lua_State* L, int index, std::string& output, int depth) {
|
||||
output += "{";
|
||||
|
||||
// Normalize stack index
|
||||
if (index < 0) index = lua_gettop(L) + index + 1;
|
||||
|
||||
bool first = true;
|
||||
lua_pushnil(L);
|
||||
|
||||
while (lua_next(L, index) != 0) {
|
||||
if (!first) output += ",";
|
||||
first = false;
|
||||
|
||||
// Serialize key
|
||||
output += "[";
|
||||
serializeValue(L, -2, output, depth + 1);
|
||||
output += "]=";
|
||||
|
||||
// Serialize value
|
||||
serializeValue(L, -1, output, depth + 1);
|
||||
|
||||
lua_pop(L, 1); // pop value, keep key for next iteration
|
||||
}
|
||||
|
||||
output += "}";
|
||||
}
|
||||
|
||||
bool LuaPersistence::saveGlobals(lua_State* L, const std::string& filePath,
|
||||
const std::vector<std::string>& globals) {
|
||||
std::string output = "-- Wherigo Save State\nreturn {\n";
|
||||
|
||||
for (const auto& globalName : globals) {
|
||||
lua_getglobal(L, globalName.c_str());
|
||||
|
||||
if (!lua_isnil(L, -1)) {
|
||||
output += " [\"" + globalName + "\"] = ";
|
||||
serializeValue(L, -1, output, 0);
|
||||
output += ",\n";
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
output += "}\n";
|
||||
|
||||
// Write to file
|
||||
std::ofstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
wxLogError("Failed to open save file: %s", filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
file << output;
|
||||
file.close();
|
||||
|
||||
wxLogDebug("Saved %zu globals to %s", globals.size(), filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LuaPersistence::saveState(lua_State* L, const std::string& filePath) {
|
||||
auto globals = getGameGlobals(L);
|
||||
return saveGlobals(L, filePath, globals);
|
||||
}
|
||||
|
||||
bool LuaPersistence::loadGlobals(lua_State* L, const std::string& filePath) {
|
||||
// Load the file
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
wxLogError("Failed to open load file: %s", filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string content = buffer.str();
|
||||
file.close();
|
||||
|
||||
// Execute the Lua file
|
||||
if (luaL_loadstring(L, content.c_str()) != 0) {
|
||||
wxLogError("Failed to parse save file: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lua_pcall(L, 0, 1, 0) != 0) {
|
||||
wxLogError("Failed to execute save file: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Result should be a table
|
||||
if (!lua_istable(L, -1)) {
|
||||
wxLogError("Save file did not return a table");
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore globals
|
||||
int count = 0;
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_isstring(L, -2)) {
|
||||
const char* key = lua_tostring(L, -2);
|
||||
|
||||
// Set the global
|
||||
lua_pushvalue(L, -1); // duplicate value
|
||||
lua_setglobal(L, key);
|
||||
count++;
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop table
|
||||
|
||||
wxLogDebug("Restored %d globals from %s", count, filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LuaPersistence::loadState(lua_State* L, const std::string& filePath) {
|
||||
return loadGlobals(L, filePath);
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
512
main/src/lua/wherigo.cpp
Normal file
512
main/src/lua/wherigo.cpp
Normal file
@@ -0,0 +1,512 @@
|
||||
#include "lua/wherigo.h"
|
||||
#include "lua/zobject.h"
|
||||
#include "lua/ztimer.h"
|
||||
#include "ui/wherigo_dialog.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
|
||||
// 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");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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},
|
||||
{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
|
||||
|
||||
281
main/src/lua/wherigo_completion.cpp
Normal file
281
main/src/lua/wherigo_completion.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "lua/wherigo_completion.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
std::string WherigoCompletion::escapeXML(const std::string& str) {
|
||||
std::string result;
|
||||
for (char c : str) {
|
||||
switch (c) {
|
||||
case '&': result += "&"; break;
|
||||
case '<': result += "<"; break;
|
||||
case '>': result += ">"; break;
|
||||
case '"': result += """; break;
|
||||
case '\'': result += "'"; break;
|
||||
default: result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string WherigoCompletion::formatTime(time_t t) {
|
||||
struct tm* timeinfo = localtime(&t);
|
||||
char buffer[30];
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", timeinfo);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string WherigoCompletion::generateCompletionCode(const CompletionData& data) {
|
||||
// Generate a simple hash based on cartridge GUID + player + completion time
|
||||
std::stringstream ss;
|
||||
ss << data.cartridgeGUID << data.playerName << data.endTime;
|
||||
std::string input = ss.str();
|
||||
|
||||
// Simple hash (for real use, use proper crypto hash like SHA256)
|
||||
unsigned long hash = 5381;
|
||||
for (char c : input) {
|
||||
hash = ((hash << 5) + hash) + c;
|
||||
}
|
||||
|
||||
char hashStr[20];
|
||||
snprintf(hashStr, sizeof(hashStr), "%016lX", hash);
|
||||
return std::string(hashStr);
|
||||
}
|
||||
|
||||
CompletionData WherigoCompletion::extractCompletionData(lua_State* L, const std::string& playerName) {
|
||||
CompletionData data;
|
||||
data.playerName = playerName;
|
||||
data.startTime = time(nullptr); // Default to now
|
||||
data.endTime = time(nullptr);
|
||||
data.duration = 0;
|
||||
|
||||
// Get cartridge info
|
||||
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)) {
|
||||
std::string className = lua_tostring(L, -1);
|
||||
|
||||
if (className == "ZCartridge") {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
data.cartridgeName = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Id");
|
||||
if (lua_isstring(L, -1)) {
|
||||
data.cartridgeGUID = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else if (className == "ZTask") {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
|
||||
CompletionData::TaskCompletion task;
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
task.name = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Complete");
|
||||
task.completed = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
task.completionTime = time(nullptr);
|
||||
data.tasks.push_back(task);
|
||||
}
|
||||
else if (className == "Zone") {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active) {
|
||||
CompletionData::ZoneVisited zone;
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
zone.name = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
zone.visitTime = time(nullptr);
|
||||
data.zones.push_back(zone);
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1); // pop ClassName
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
|
||||
// Get items in player inventory
|
||||
lua_getglobal(L, "Player");
|
||||
if (lua_istable(L, -1)) {
|
||||
// Find all items where Container == Player
|
||||
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), "ZItem") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Container");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getglobal(L, "Player");
|
||||
bool inInventory = lua_rawequal(L, -1, -2);
|
||||
lua_pop(L, 2); // pop Player and Container
|
||||
|
||||
if (inInventory) {
|
||||
CompletionData::ItemFound item;
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
item.name = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
item.foundTime = time(nullptr);
|
||||
data.items.push_back(item);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
lua_pop(L, 1); // pop Player
|
||||
|
||||
// Generate completion code
|
||||
data.completionCode = generateCompletionCode(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool WherigoCompletion::writeGWLFile(const CompletionData& data, const std::string& filePath) {
|
||||
std::ofstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
wxLogError("Failed to create completion log: %s", filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write XML header
|
||||
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
file << "<WherigoGameLog xmlns=\"http://www.groundspeak.com/wherigo/1.0\">\n";
|
||||
|
||||
// Cartridge info
|
||||
file << " <Cartridge>\n";
|
||||
file << " <Name>" << escapeXML(data.cartridgeName) << "</Name>\n";
|
||||
file << " <GUID>" << escapeXML(data.cartridgeGUID) << "</GUID>\n";
|
||||
file << " </Cartridge>\n";
|
||||
|
||||
// Player info
|
||||
file << " <Player>\n";
|
||||
file << " <Name>" << escapeXML(data.playerName) << "</Name>\n";
|
||||
file << " </Player>\n";
|
||||
|
||||
// Session info
|
||||
file << " <Session>\n";
|
||||
file << " <StartTime>" << formatTime(data.startTime) << "</StartTime>\n";
|
||||
file << " <EndTime>" << formatTime(data.endTime) << "</EndTime>\n";
|
||||
file << " <Duration>" << data.duration << "</Duration>\n";
|
||||
file << " </Session>\n";
|
||||
|
||||
// Completion status
|
||||
bool allTasksComplete = std::all_of(data.tasks.begin(), data.tasks.end(),
|
||||
[](const CompletionData::TaskCompletion& t) { return t.completed; });
|
||||
file << " <CompletionStatus>" << (allTasksComplete ? "Complete" : "Incomplete") << "</CompletionStatus>\n";
|
||||
|
||||
// Tasks
|
||||
if (!data.tasks.empty()) {
|
||||
file << " <Tasks>\n";
|
||||
for (const auto& task : data.tasks) {
|
||||
file << " <Task>\n";
|
||||
file << " <Name>" << escapeXML(task.name) << "</Name>\n";
|
||||
file << " <Completed>" << (task.completed ? "true" : "false") << "</Completed>\n";
|
||||
if (task.completed) {
|
||||
file << " <CompletionTime>" << formatTime(task.completionTime) << "</CompletionTime>\n";
|
||||
}
|
||||
file << " </Task>\n";
|
||||
}
|
||||
file << " </Tasks>\n";
|
||||
}
|
||||
|
||||
// Items collected
|
||||
if (!data.items.empty()) {
|
||||
file << " <ItemsCollected>\n";
|
||||
for (const auto& item : data.items) {
|
||||
file << " <Item>\n";
|
||||
file << " <Name>" << escapeXML(item.name) << "</Name>\n";
|
||||
file << " <FoundTime>" << formatTime(item.foundTime) << "</FoundTime>\n";
|
||||
file << " </Item>\n";
|
||||
}
|
||||
file << " </ItemsCollected>\n";
|
||||
}
|
||||
|
||||
// Zones visited
|
||||
if (!data.zones.empty()) {
|
||||
file << " <ZonesVisited>\n";
|
||||
for (const auto& zone : data.zones) {
|
||||
file << " <Zone>\n";
|
||||
file << " <Name>" << escapeXML(zone.name) << "</Name>\n";
|
||||
file << " <VisitTime>" << formatTime(zone.visitTime) << "</VisitTime>\n";
|
||||
file << " </Zone>\n";
|
||||
}
|
||||
file << " </ZonesVisited>\n";
|
||||
}
|
||||
|
||||
// Completion code (signature/verification)
|
||||
file << " <CompletionCode>" << data.completionCode << "</CompletionCode>\n";
|
||||
|
||||
file << "</WherigoGameLog>\n";
|
||||
file.close();
|
||||
|
||||
wxLogMessage("Completion log saved to: %s", filePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WherigoCompletion::generateCompletionLog(lua_State* L, const std::string& filePath,
|
||||
const std::string& playerName) {
|
||||
if (!L) {
|
||||
wxLogError("Invalid Lua state");
|
||||
return false;
|
||||
}
|
||||
|
||||
CompletionData data = extractCompletionData(L, playerName);
|
||||
return writeGWLFile(data, filePath);
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
64
main/src/lua/zobject.cpp
Normal file
64
main/src/lua/zobject.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "lua/zobject.h"
|
||||
#include "lua/game_engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
int zobject_MoveTo(lua_State *L) {
|
||||
// Get source object name
|
||||
lua_getfield(L, 1, "Name");
|
||||
const char *srcName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unknown)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get target container name
|
||||
const char *dstName = "(nil)";
|
||||
if (lua_istable(L, 2)) {
|
||||
lua_getfield(L, 2, "Name");
|
||||
dstName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unknown)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_pushvalue(L, 2);
|
||||
lua_setfield(L, 1, "Container");
|
||||
}
|
||||
|
||||
wxLogDebug("MoveTo: %s -> %s", srcName, dstName);
|
||||
|
||||
// Notify game state change
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zobject_Contains(lua_State *L) {
|
||||
// self (container) at index 1, item to check at index 2
|
||||
|
||||
lua_getfield(L, 1, "Name");
|
||||
const char *containerName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unknown)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 2, "Name");
|
||||
const char *itemName = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unknown)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Check if item's Container is the same as self (container)
|
||||
bool contains = false;
|
||||
|
||||
lua_getfield(L, 2, "Container"); // Get item.Container
|
||||
if (lua_istable(L, -1)) {
|
||||
// Compare by reference - check if it's the same table as self
|
||||
contains = lua_rawequal(L, 1, -1);
|
||||
}
|
||||
lua_pop(L, 1); // pop Container
|
||||
|
||||
wxLogDebug("Contains: %s in %s? -> %s", itemName, containerName, contains ? "true" : "false");
|
||||
lua_pushboolean(L, contains ? 1 : 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
62
main/src/lua/ztimer.cpp
Normal file
62
main/src/lua/ztimer.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "lua/ztimer.h"
|
||||
#include "lua/game_engine.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
int ztimer_Start(lua_State *L) {
|
||||
lua_getfield(L, 1, "Name");
|
||||
const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 1, "Duration");
|
||||
lua_Number duration = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0;
|
||||
lua_pop(L, 1);
|
||||
|
||||
wxLogDebug("Timer Start: %s (Duration: %.1f sec)", name, duration);
|
||||
|
||||
lua_pushboolean(L, 1);
|
||||
lua_setfield(L, 1, "Running");
|
||||
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ztimer_Stop(lua_State *L) {
|
||||
lua_getfield(L, 1, "Name");
|
||||
const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
wxLogDebug("Timer Stop: %s", name);
|
||||
|
||||
lua_pushboolean(L, 0);
|
||||
lua_setfield(L, 1, "Running");
|
||||
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ztimer_Reset(lua_State *L) {
|
||||
lua_getfield(L, 1, "Name");
|
||||
const char *name = lua_isstring(L, -1) ? lua_tostring(L, -1) : "(unnamed)";
|
||||
lua_pop(L, 1);
|
||||
|
||||
wxLogDebug("Timer Reset: %s", name);
|
||||
|
||||
lua_pushboolean(L, 0);
|
||||
lua_setfield(L, 1, "Running");
|
||||
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "cApp.h"
|
||||
#include "app.h"
|
||||
#include <wx/wx.h>
|
||||
|
||||
wxIMPLEMENT_APP(cApp);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include "ui/cFrame.h"
|
||||
|
||||
enum { ID_Hello = 1 };
|
||||
|
||||
cFrame::cFrame() : wxMDIParentFrame(nullptr, wxID_ANY, "Hello World") {
|
||||
auto *menuFile = new wxMenu;
|
||||
menuFile->Append(ID_Hello, "&Hello...\tCtrl-H",
|
||||
"Help string shown in status bar for this menu item");
|
||||
menuFile->AppendSeparator();
|
||||
menuFile->Append(wxID_EXIT);
|
||||
|
||||
auto *menuHelp = new wxMenu;
|
||||
menuHelp->Append(wxID_ABOUT);
|
||||
|
||||
auto *menuBar = new wxMenuBar;
|
||||
menuBar->Append(menuFile, "&File");
|
||||
menuBar->Append(menuHelp, "&Help");
|
||||
|
||||
SetMenuBar(menuBar);
|
||||
|
||||
CreateStatusBar();
|
||||
SetStatusText("Welcome to wxWidgets!");
|
||||
|
||||
Bind(wxEVT_MENU, &cFrame::OnHello, this, ID_Hello);
|
||||
Bind(wxEVT_MENU, &cFrame::OnAbout, this, wxID_ABOUT);
|
||||
Bind(wxEVT_MENU, &cFrame::OnExit, this, wxID_EXIT);
|
||||
}
|
||||
|
||||
void cFrame::OnExit(wxCommandEvent &event) { Close(true); }
|
||||
|
||||
void cFrame::OnAbout(wxCommandEvent &event) {
|
||||
wxMessageBox("This is a wxWidgets Hello World example", "About Hello World",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
}
|
||||
|
||||
void cFrame::OnHello(wxCommandEvent &event) {
|
||||
wxLogMessage("Hello world from wxWidgets!");
|
||||
}
|
||||
939
main/src/ui/game_screen.cpp
Normal file
939
main/src/ui/game_screen.cpp
Normal file
@@ -0,0 +1,939 @@
|
||||
#include "app.h"
|
||||
#include "lua/game_engine.h"
|
||||
#include "ui/game_screen.h"
|
||||
#include "ui/wherigo_dialog.h"
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
wxDECLARE_APP(cApp);
|
||||
|
||||
enum {
|
||||
ID_SaveGame = 2000,
|
||||
ID_LoadGame = 2001,
|
||||
ID_ExportCompletion = 2002
|
||||
};
|
||||
|
||||
cGameScreen::cGameScreen(wxWindow *parent)
|
||||
: wxFrame(parent, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 600)) {
|
||||
|
||||
// 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_ExportCompletion, "Completion Log exportieren\tCtrl+E");
|
||||
menuFile->AppendSeparator();
|
||||
menuFile->Append(wxID_EXIT, "Spiel beenden");
|
||||
|
||||
auto *menuHelp = new wxMenu;
|
||||
menuHelp->Append(wxID_ABOUT);
|
||||
|
||||
auto *menuBar = new wxMenuBar;
|
||||
menuBar->Append(menuFile, "&Datei");
|
||||
menuBar->Append(menuHelp, "&Hilfe");
|
||||
SetMenuBar(menuBar);
|
||||
|
||||
// Main sizer
|
||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Notebook with tabs
|
||||
m_notebook = new wxNotebook(this, wxID_ANY);
|
||||
|
||||
// Locations/Zones tab
|
||||
auto *zonePanel = new wxPanel(m_notebook);
|
||||
auto *zoneSizer = new wxBoxSizer(wxVERTICAL);
|
||||
zoneSizer->Add(new wxStaticText(zonePanel, wxID_ANY, "Orte in der Nähe:"), 0, wxALL, 5);
|
||||
m_zoneList = new wxListBox(zonePanel, wxID_ANY);
|
||||
zoneSizer->Add(m_zoneList, 1, wxALL | wxEXPAND, 5);
|
||||
zonePanel->SetSizer(zoneSizer);
|
||||
m_notebook->AddPage(zonePanel, "Orte");
|
||||
|
||||
// Tasks tab
|
||||
auto *taskPanel = new wxPanel(m_notebook);
|
||||
auto *taskSizer = new wxBoxSizer(wxVERTICAL);
|
||||
taskSizer->Add(new wxStaticText(taskPanel, wxID_ANY, "Aktive Aufgaben:"), 0, wxALL, 5);
|
||||
m_taskList = new wxListBox(taskPanel, wxID_ANY);
|
||||
taskSizer->Add(m_taskList, 1, wxALL | wxEXPAND, 5);
|
||||
taskPanel->SetSizer(taskSizer);
|
||||
m_notebook->AddPage(taskPanel, "Aufgaben");
|
||||
|
||||
// Inventory tab
|
||||
auto *inventoryPanel = new wxPanel(m_notebook);
|
||||
auto *inventorySizer = new wxBoxSizer(wxVERTICAL);
|
||||
inventorySizer->Add(new wxStaticText(inventoryPanel, wxID_ANY, "Dein Inventar:"), 0, wxALL, 5);
|
||||
m_inventoryList = new wxListBox(inventoryPanel, wxID_ANY);
|
||||
inventorySizer->Add(m_inventoryList, 1, wxALL | wxEXPAND, 5);
|
||||
inventoryPanel->SetSizer(inventorySizer);
|
||||
m_notebook->AddPage(inventoryPanel, "Inventar");
|
||||
|
||||
// Characters tab
|
||||
auto *characterPanel = new wxPanel(m_notebook);
|
||||
auto *characterSizer = new wxBoxSizer(wxVERTICAL);
|
||||
characterSizer->Add(new wxStaticText(characterPanel, wxID_ANY, "Personen in der Nähe:"), 0, wxALL, 5);
|
||||
m_characterList = new wxListBox(characterPanel, wxID_ANY);
|
||||
characterSizer->Add(m_characterList, 1, wxALL | wxEXPAND, 5);
|
||||
characterPanel->SetSizer(characterSizer);
|
||||
m_notebook->AddPage(characterPanel, "Personen");
|
||||
|
||||
// Items tab
|
||||
auto *itemPanel = new wxPanel(m_notebook);
|
||||
auto *itemSizer = new wxBoxSizer(wxVERTICAL);
|
||||
itemSizer->Add(new wxStaticText(itemPanel, wxID_ANY, "Gegenstände in der Nähe:"), 0, wxALL, 5);
|
||||
m_itemList = new wxListBox(itemPanel, wxID_ANY);
|
||||
itemSizer->Add(m_itemList, 1, wxALL | wxEXPAND, 5);
|
||||
itemPanel->SetSizer(itemSizer);
|
||||
m_notebook->AddPage(itemPanel, "Gegenstände");
|
||||
|
||||
mainSizer->Add(m_notebook, 1, wxALL | wxEXPAND, 5);
|
||||
|
||||
SetSizer(mainSizer);
|
||||
|
||||
CentreOnScreen();
|
||||
|
||||
// Status bar
|
||||
CreateStatusBar();
|
||||
SetStatusText("Spiel läuft");
|
||||
|
||||
// Event bindings
|
||||
Bind(wxEVT_CLOSE_WINDOW, &cGameScreen::OnClose, this);
|
||||
Bind(wxEVT_MENU, &cGameScreen::OnAbout, this, wxID_ABOUT);
|
||||
Bind(wxEVT_MENU, &cGameScreen::OnExit, this, wxID_EXIT);
|
||||
Bind(wxEVT_MENU, &cGameScreen::OnSaveGame, this, ID_SaveGame);
|
||||
Bind(wxEVT_MENU, &cGameScreen::OnLoadGame, this, ID_LoadGame);
|
||||
Bind(wxEVT_MENU, &cGameScreen::OnExportCompletion, this, ID_ExportCompletion);
|
||||
|
||||
m_zoneList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnZoneSelected, this);
|
||||
m_taskList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnTaskSelected, this);
|
||||
m_inventoryList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnInventorySelected, this);
|
||||
m_characterList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnCharacterSelected, this);
|
||||
m_itemList->Bind(wxEVT_LISTBOX_DCLICK, &cGameScreen::OnItemSelected, this);
|
||||
|
||||
// Listen to game state changes from GameEngine
|
||||
wherigo::GameEngine::getInstance().Bind(wherigo::EVT_GAME_STATE_CHANGED,
|
||||
&cGameScreen::OnGameStateChanged, this);
|
||||
|
||||
// Initial UI refresh
|
||||
CallAfter([this]() { refreshUI(); });
|
||||
}
|
||||
|
||||
void cGameScreen::OnClose(wxCloseEvent &event) {
|
||||
// Unbind from game engine events
|
||||
wherigo::GameEngine::getInstance().Unbind(wherigo::EVT_GAME_STATE_CHANGED,
|
||||
&cGameScreen::OnGameStateChanged, this);
|
||||
|
||||
// Stop the game engine but don't unload cartridge
|
||||
wherigo::GameEngine::getInstance().stop();
|
||||
|
||||
// Hide instead of destroy, parent will handle visibility
|
||||
Hide();
|
||||
|
||||
// Notify parent that game was closed
|
||||
if (GetParent()) {
|
||||
GetParent()->Show();
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnExit(wxCommandEvent &event) {
|
||||
Close();
|
||||
}
|
||||
|
||||
void cGameScreen::OnAbout(wxCommandEvent &event) {
|
||||
wxMessageBox("Wherigo Player\nEin Wherigo-Cartridge-Player für Desktop",
|
||||
"Über Wherigo Player", wxOK | wxICON_INFORMATION);
|
||||
}
|
||||
|
||||
void cGameScreen::OnSaveGame(wxCommandEvent& event) {
|
||||
if (wxGetApp().saveGameState("")) {
|
||||
wxMessageBox("Spielstand wurde gespeichert!", "Speichern", wxOK | wxICON_INFORMATION);
|
||||
} else {
|
||||
wxMessageBox("Fehler beim Speichern des Spielstands!", "Fehler", wxOK | wxICON_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnLoadGame(wxCommandEvent& event) {
|
||||
int result = wxMessageBox("Möchten Sie den gespeicherten Spielstand laden?\n\nAchtung: Der aktuelle Fortschritt geht verloren!",
|
||||
"Laden", wxYES_NO | wxICON_QUESTION);
|
||||
|
||||
if (result == wxYES) {
|
||||
if (wxGetApp().loadGameState("")) {
|
||||
wxMessageBox("Spielstand wurde geladen!", "Laden", wxOK | wxICON_INFORMATION);
|
||||
refreshUI();
|
||||
} else {
|
||||
wxMessageBox("Kein gespeicherter Spielstand gefunden oder Fehler beim Laden!", "Fehler", wxOK | wxICON_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnExportCompletion(wxCommandEvent& event) {
|
||||
wxFileDialog saveDialog(this, "Completion Log speichern", "", "",
|
||||
"Wherigo Game Log (*.gwl)|*.gwl",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
std::string defaultPath = wxGetApp().getCompletionLogPath();
|
||||
if (!defaultPath.empty()) {
|
||||
saveDialog.SetPath(defaultPath);
|
||||
}
|
||||
|
||||
if (saveDialog.ShowModal() == wxID_CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxString filePath = saveDialog.GetPath();
|
||||
|
||||
if (wxGetApp().generateCompletionLog(filePath.ToStdString())) {
|
||||
wxMessageBox("Completion Log wurde erfolgreich exportiert!\n\nDiese Datei kann auf wherigo.com hochgeladen werden, um den Abschluss nachzuweisen.",
|
||||
"Export erfolgreich", wxOK | wxICON_INFORMATION);
|
||||
} else {
|
||||
wxMessageBox("Fehler beim Exportieren des Completion Logs!", "Fehler", wxOK | wxICON_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnGameStateChanged(wxEvent& event) {
|
||||
// Game state has changed, refresh UI
|
||||
refreshUI();
|
||||
}
|
||||
|
||||
void cGameScreen::refreshUI() {
|
||||
// Freeze to prevent flickering and focus loss
|
||||
Freeze();
|
||||
|
||||
populateZones();
|
||||
populateTasks();
|
||||
populateInventory();
|
||||
populateCharacters();
|
||||
populateItems();
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
void cGameScreen::populateZones() {
|
||||
int selection = m_zoneList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_zoneList->GetString(selection) : "";
|
||||
|
||||
m_zoneList->Clear();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
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);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && visible) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
m_zoneList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_zoneList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
m_zoneList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateTasks() {
|
||||
int selection = m_taskList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_taskList->GetString(selection) : "";
|
||||
|
||||
m_taskList->Clear();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
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), "ZTask") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Complete");
|
||||
bool complete = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && !complete) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
m_taskList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_taskList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
m_taskList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateInventory() {
|
||||
int selection = m_inventoryList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_inventoryList->GetString(selection) : "";
|
||||
|
||||
m_inventoryList->Clear();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Get Player
|
||||
lua_getglobal(L, "Player");
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all items where Container == Player
|
||||
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), "ZItem") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Container");
|
||||
if (lua_istable(L, -1)) {
|
||||
// Check if Container is Player
|
||||
lua_getglobal(L, "Player");
|
||||
bool isInInventory = lua_rawequal(L, -1, -2);
|
||||
lua_pop(L, 2); // pop Player and Container
|
||||
|
||||
if (isInInventory) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
m_inventoryList->Append(wxString::FromUTF8(lua_tostring(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 2); // pop _G and Player
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_inventoryList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
m_inventoryList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateCharacters() {
|
||||
int selection = m_characterList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_characterList->GetString(selection) : "";
|
||||
|
||||
m_characterList->Clear();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
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), "ZCharacter") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Check if character has a container (is somewhere)
|
||||
lua_getfield(L, -1, "Container");
|
||||
bool hasLocation = lua_istable(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && visible && hasLocation) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
wxString name;
|
||||
if (lua_isstring(L, -1)) {
|
||||
name = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get location name
|
||||
lua_getfield(L, -1, "Container");
|
||||
lua_getfield(L, -1, "Name");
|
||||
wxString location;
|
||||
if (lua_isstring(L, -1)) {
|
||||
location = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 2);
|
||||
|
||||
if (!name.IsEmpty()) {
|
||||
m_characterList->Append(wxString::Format("%s (%s)", name, location));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_characterList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
m_characterList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::populateItems() {
|
||||
int selection = m_itemList->GetSelection();
|
||||
wxString selectedItem = (selection != wxNOT_FOUND) ? m_itemList->GetString(selection) : "";
|
||||
|
||||
m_itemList->Clear();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
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), "ZItem") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Active");
|
||||
bool active = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Visible");
|
||||
bool visible = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Check if item is NOT in player inventory (in a zone)
|
||||
lua_getfield(L, -1, "Container");
|
||||
bool inZone = false;
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_getfield(L, -1, "ClassName");
|
||||
if (lua_isstring(L, -1)) {
|
||||
const char *containerClass = lua_tostring(L, -1);
|
||||
inZone = (strcmp(containerClass, "Zone") == 0);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (active && visible && inZone) {
|
||||
lua_getfield(L, -1, "Name");
|
||||
wxString name;
|
||||
if (lua_isstring(L, -1)) {
|
||||
name = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get location name
|
||||
lua_getfield(L, -1, "Container");
|
||||
lua_getfield(L, -1, "Name");
|
||||
wxString location;
|
||||
if (lua_isstring(L, -1)) {
|
||||
location = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
}
|
||||
lua_pop(L, 2);
|
||||
|
||||
if (!name.IsEmpty()) {
|
||||
m_itemList->Append(wxString::Format("%s (%s)", name, location));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Restore selection
|
||||
if (!selectedItem.IsEmpty()) {
|
||||
int idx = m_itemList->FindString(selectedItem);
|
||||
if (idx != wxNOT_FOUND) {
|
||||
m_itemList->SetSelection(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnZoneSelected(wxCommandEvent& event) {
|
||||
int sel = m_zoneList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
wxString zoneName = m_zoneList->GetString(sel);
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Find the zone object by name
|
||||
lua_getglobal(L, "_G");
|
||||
lua_pushnil(L);
|
||||
|
||||
bool found = false;
|
||||
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, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == zoneName) {
|
||||
found = true;
|
||||
|
||||
// Get zone description
|
||||
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)) {
|
||||
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);
|
||||
|
||||
// Show zone info with media
|
||||
if (!description.IsEmpty() || !mediaName.IsEmpty()) {
|
||||
std::vector<wxString> buttons = {"OK"};
|
||||
wherigo::WherigoMessageDialog dlg(this, description, zoneName, buttons, mediaName);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
// Call OnEnter callback if it exists
|
||||
lua_getfield(L, -1, "OnEnter");
|
||||
if (lua_isfunction(L, -1)) {
|
||||
lua_pushvalue(L, -2); // push zone as self argument
|
||||
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||
wxLogError("Zone OnEnter error: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop zone table
|
||||
lua_pop(L, 1); // pop key
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
|
||||
if (!found) {
|
||||
wxMessageBox(wxString::Format("Zone '%s' nicht gefunden", zoneName),
|
||||
"Fehler", wxOK | wxICON_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void cGameScreen::OnTaskSelected(wxCommandEvent& event) {
|
||||
int sel = m_taskList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
wxString taskName = m_taskList->GetString(sel);
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Find the task object by name
|
||||
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), "ZTask") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == taskName) {
|
||||
// Get task 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 task complete status
|
||||
lua_getfield(L, -1, "Complete");
|
||||
bool complete = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
wxString status = complete ? "✓ Abgeschlossen" : "○ Offen";
|
||||
wxString message = wxString::Format("%s\n\n%s", status, description);
|
||||
|
||||
wxMessageBox(message, taskName, wxOK | wxICON_INFORMATION);
|
||||
|
||||
lua_pop(L, 1); // pop task table
|
||||
lua_pop(L, 1); // pop key
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
|
||||
void cGameScreen::OnInventorySelected(wxCommandEvent& event) {
|
||||
int sel = m_inventoryList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
wxString itemName = m_inventoryList->GetString(sel);
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Find the item object by name
|
||||
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), "ZItem") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
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");
|
||||
if (lua_isfunction(L, -1)) {
|
||||
lua_pushvalue(L, -2); // push item as self
|
||||
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||
wxLogError("Item OnClick error: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop item table
|
||||
lua_pop(L, 1); // pop key
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
|
||||
void cGameScreen::OnCharacterSelected(wxCommandEvent& event) {
|
||||
int sel = m_characterList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
// Extract name from "Name (Location)" format
|
||||
wxString fullText = m_characterList->GetString(sel);
|
||||
wxString charName = fullText.BeforeFirst('(').Trim();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Find the character object by name
|
||||
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), "ZCharacter") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (currentName == charName) {
|
||||
// Get character 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);
|
||||
|
||||
// Show with media support
|
||||
std::vector<wxString> buttons = {"OK"};
|
||||
wherigo::WherigoMessageDialog dlg(this, description, charName, buttons, mediaName);
|
||||
dlg.ShowModal();
|
||||
|
||||
// Call OnClick if exists
|
||||
lua_getfield(L, -1, "OnClick");
|
||||
if (lua_isfunction(L, -1)) {
|
||||
lua_pushvalue(L, -2); // push character as self
|
||||
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||
wxLogError("Character OnClick error: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop character table
|
||||
lua_pop(L, 1); // pop key
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
|
||||
void cGameScreen::OnItemSelected(wxCommandEvent& event) {
|
||||
int sel = m_itemList->GetSelection();
|
||||
if (sel == wxNOT_FOUND) return;
|
||||
|
||||
// Extract name from "Name (Location)" format
|
||||
wxString fullText = m_itemList->GetString(sel);
|
||||
wxString itemName = fullText.BeforeFirst('(').Trim();
|
||||
|
||||
lua_State *L = wxGetApp().getLuaState();
|
||||
if (!L) return;
|
||||
|
||||
// Find the item object by name
|
||||
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), "ZItem") == 0) {
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, -1, "Name");
|
||||
if (lua_isstring(L, -1)) {
|
||||
wxString currentName = wxString::FromUTF8(lua_tostring(L, -1));
|
||||
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");
|
||||
if (lua_isfunction(L, -1)) {
|
||||
lua_pushvalue(L, -2); // push item as self
|
||||
if (lua_pcall(L, 1, 0, 0) != 0) {
|
||||
wxLogError("Item OnClick error: %s", lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
lua_pop(L, 1); // pop item table
|
||||
lua_pop(L, 1); // pop key
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1); // pop value
|
||||
}
|
||||
lua_pop(L, 1); // pop _G
|
||||
}
|
||||
202
main/src/ui/start_screen.cpp
Normal file
202
main/src/ui/start_screen.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "ui/start_screen.h"
|
||||
#include "app.h"
|
||||
#include "lua/game_engine.h"
|
||||
#include "ui/game_screen.h"
|
||||
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
wxDECLARE_APP(cApp);
|
||||
|
||||
enum {
|
||||
ID_OpenCartridge = 1001,
|
||||
ID_StartGame = 1002
|
||||
};
|
||||
|
||||
cStartScreen::cStartScreen()
|
||||
: wxFrame(nullptr, wxID_ANY, "Wherigo Player", wxDefaultPosition, wxSize(800, 600)),
|
||||
m_gameFrame(nullptr),
|
||||
m_cartridgeLoaded(false) {
|
||||
|
||||
// Menu
|
||||
auto *menuFile = new wxMenu;
|
||||
menuFile->Append(ID_OpenCartridge, "Cartridge öffnen...\tCtrl+O");
|
||||
menuFile->AppendSeparator();
|
||||
menuFile->Append(wxID_EXIT, "Beenden");
|
||||
|
||||
auto *menuHelp = new wxMenu;
|
||||
menuHelp->Append(wxID_ABOUT, "Über...");
|
||||
|
||||
auto *menuBar = new wxMenuBar;
|
||||
menuBar->Append(menuFile, "&Datei");
|
||||
menuBar->Append(menuHelp, "&Hilfe");
|
||||
SetMenuBar(menuBar);
|
||||
|
||||
// Main sizer
|
||||
auto *mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Title
|
||||
auto *title = new wxStaticText(this, wxID_ANY, "Wherigo Player");
|
||||
title->SetFont(title->GetFont().Bold().Scaled(2.0));
|
||||
mainSizer->Add(title, 0, wxALL | wxALIGN_CENTER, 20);
|
||||
|
||||
// Open button
|
||||
m_openButton = new wxButton(this, ID_OpenCartridge, "Cartridge öffnen...");
|
||||
m_openButton->SetMinSize(wxSize(200, 40));
|
||||
mainSizer->Add(m_openButton, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
|
||||
// Info panel (initially hidden)
|
||||
m_infoPanel = new wxPanel(this);
|
||||
auto *infoSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Splash image
|
||||
m_splashImage = new wxStaticBitmap(m_infoPanel, wxID_ANY, wxNullBitmap);
|
||||
infoSizer->Add(m_splashImage, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
|
||||
// Cartridge name
|
||||
m_cartridgeName = new wxStaticText(m_infoPanel, wxID_ANY, "");
|
||||
m_cartridgeName->SetFont(m_cartridgeName->GetFont().Bold().Scaled(1.5));
|
||||
infoSizer->Add(m_cartridgeName, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
|
||||
// Author
|
||||
m_cartridgeAuthor = new wxStaticText(m_infoPanel, wxID_ANY, "");
|
||||
infoSizer->Add(m_cartridgeAuthor, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
|
||||
// Description
|
||||
m_cartridgeDesc = new wxHtmlWindow(m_infoPanel, wxID_ANY, wxDefaultPosition, wxSize(500, 200));
|
||||
m_cartridgeDesc->SetMinSize(wxSize(500, 200));
|
||||
infoSizer->Add(m_cartridgeDesc, 1, wxALL | wxEXPAND, 10);
|
||||
|
||||
// Start button
|
||||
m_startButton = new wxButton(m_infoPanel, ID_StartGame, "Spiel starten");
|
||||
m_startButton->SetMinSize(wxSize(200, 50));
|
||||
m_startButton->SetFont(m_startButton->GetFont().Bold());
|
||||
infoSizer->Add(m_startButton, 0, wxALL | wxALIGN_CENTER, 20);
|
||||
|
||||
m_infoPanel->SetSizer(infoSizer);
|
||||
m_infoPanel->Hide();
|
||||
|
||||
mainSizer->Add(m_infoPanel, 1, wxALL | wxEXPAND, 10);
|
||||
|
||||
SetSizer(mainSizer);
|
||||
|
||||
// Status bar
|
||||
CreateStatusBar();
|
||||
SetStatusText("Bitte eine Cartridge öffnen");
|
||||
|
||||
// Event bindings
|
||||
Bind(wxEVT_MENU, &cStartScreen::OnOpenCartridge, this, ID_OpenCartridge);
|
||||
Bind(wxEVT_MENU, &cStartScreen::OnExit, this, wxID_EXIT);
|
||||
Bind(wxEVT_MENU, &cStartScreen::OnAbout, this, wxID_ABOUT);
|
||||
Bind(wxEVT_BUTTON, &cStartScreen::OnOpenCartridge, this, ID_OpenCartridge);
|
||||
Bind(wxEVT_BUTTON, &cStartScreen::OnStartGame, this, ID_StartGame);
|
||||
|
||||
CentreOnScreen();
|
||||
}
|
||||
|
||||
void cStartScreen::OnOpenCartridge(wxCommandEvent& event) {
|
||||
wxFileDialog openFileDialog(this, "Cartridge öffnen", "", "",
|
||||
"Wherigo Cartridge (*.gwc)|*.gwc",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (openFileDialog.ShowModal() == wxID_CANCEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
wxString filePath = openFileDialog.GetPath();
|
||||
SetStatusText("Lade Cartridge: " + filePath);
|
||||
|
||||
// Load the cartridge via cApp
|
||||
if (wxGetApp().loadCartridge(filePath.ToStdString())) {
|
||||
m_cartridgeLoaded = true;
|
||||
showCartridgeInfo();
|
||||
SetStatusText("Cartridge geladen - bereit zum Starten");
|
||||
} else {
|
||||
wxMessageBox("Fehler beim Laden der Cartridge", "Fehler", wxOK | wxICON_ERROR);
|
||||
SetStatusText("Fehler beim Laden");
|
||||
}
|
||||
}
|
||||
|
||||
void cStartScreen::showCartridgeInfo() {
|
||||
auto *cartridge = wxGetApp().getCartridge();
|
||||
if (!cartridge) return;
|
||||
|
||||
// Set cartridge info
|
||||
m_cartridgeName->SetLabel(wxString::FromUTF8(cartridge->cartridgeName()));
|
||||
m_cartridgeAuthor->SetLabel("von " + wxString::FromUTF8(cartridge->author()));
|
||||
const auto desc = wxString::FromUTF8(cartridge->cartridgeDesc());
|
||||
if (desc.Find("<") == wxNOT_FOUND) {
|
||||
wxString plain(desc);
|
||||
plain.Replace("\n", "<br/>");
|
||||
m_cartridgeDesc->SetPage("<p>" + plain + "</p>");
|
||||
} else {
|
||||
m_cartridgeDesc->SetPage(desc);
|
||||
}
|
||||
|
||||
// Load splash screen
|
||||
auto splash = cartridge->splashScreen();
|
||||
if (splash) {
|
||||
auto data = splash->getData();
|
||||
if (!data.empty()) {
|
||||
wxMemoryInputStream stream(data.data(), data.size());
|
||||
wxImage image(stream);
|
||||
if (image.IsOk()) {
|
||||
// Scale if too large
|
||||
int maxWidth = 300;
|
||||
int maxHeight = 200;
|
||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
double scaleX = (double)maxWidth / image.GetWidth();
|
||||
double scaleY = (double)maxHeight / image.GetHeight();
|
||||
double scale = std::min(scaleX, scaleY);
|
||||
image.Rescale(image.GetWidth() * scale, image.GetHeight() * scale, wxIMAGE_QUALITY_HIGH);
|
||||
}
|
||||
m_splashImage->SetBitmap(wxBitmap(image));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show info panel
|
||||
m_infoPanel->Show();
|
||||
m_openButton->SetLabel("Andere Cartridge öffnen...");
|
||||
Layout();
|
||||
}
|
||||
|
||||
void cStartScreen::OnStartGame(wxCommandEvent& event) {
|
||||
if (!m_cartridgeLoaded) {
|
||||
wxMessageBox("Bitte zuerst eine Cartridge laden", "Hinweis", wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create game frame if not exists
|
||||
if (!m_gameFrame) {
|
||||
m_gameFrame = new cGameScreen(this);
|
||||
}
|
||||
|
||||
// Hide start frame, show game
|
||||
Hide();
|
||||
m_gameFrame->Show();
|
||||
|
||||
// Start the game (call OnStart and start engine)
|
||||
wxGetApp().startGame();
|
||||
}
|
||||
|
||||
void cStartScreen::onGameClosed() {
|
||||
// Called when game frame is closed
|
||||
Show();
|
||||
SetStatusText("Spiel pausiert - Cartridge noch geladen");
|
||||
}
|
||||
|
||||
void cStartScreen::OnExit(wxCommandEvent& event) {
|
||||
// Clean up game frame
|
||||
if (m_gameFrame) {
|
||||
m_gameFrame->Destroy();
|
||||
m_gameFrame = nullptr;
|
||||
}
|
||||
Close(true);
|
||||
}
|
||||
|
||||
void cStartScreen::OnAbout(wxCommandEvent& event) {
|
||||
wxMessageBox("Wherigo Player\n\nEin Desktop-Player für Wherigo-Cartridges\n\nVersion 0.1",
|
||||
"Über Wherigo Player", wxOK | wxICON_INFORMATION);
|
||||
}
|
||||
161
main/src/ui/wherigo_dialog.cpp
Normal file
161
main/src/ui/wherigo_dialog.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "ui/wherigo_dialog.h"
|
||||
#include "lua/media_manager.h"
|
||||
#include "lua/game_engine.h"
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
namespace wherigo {
|
||||
|
||||
WherigoMessageDialog::WherigoMessageDialog(wxWindow *parent, const wxString &text,
|
||||
const wxString &title,
|
||||
const std::vector<wxString> &buttons,
|
||||
const wxString &mediaName)
|
||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize,
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
|
||||
|
||||
auto *sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Media/Image (if provided)
|
||||
if (!mediaName.IsEmpty()) {
|
||||
auto mediaData = MediaManager::getInstance().getMediaByName(mediaName.ToStdString());
|
||||
|
||||
if (!mediaData.empty()) {
|
||||
wxMemoryInputStream stream(mediaData.data(), mediaData.size());
|
||||
wxImage image(stream);
|
||||
|
||||
if (image.IsOk()) {
|
||||
// Get screen DPI for scaling
|
||||
wxWindow* topWindow = wxTheApp->GetTopWindow();
|
||||
double contentScaleFactor = 1.0;
|
||||
if (topWindow) {
|
||||
contentScaleFactor = topWindow->GetContentScaleFactor();
|
||||
}
|
||||
|
||||
// Scale image - doubled size from original (800x600 instead of 400x300)
|
||||
// Adjust for DPI/Retina displays
|
||||
int maxWidth = static_cast<int>(800 * contentScaleFactor);
|
||||
int maxHeight = static_cast<int>(600 * contentScaleFactor);
|
||||
|
||||
if (image.GetWidth() > maxWidth || image.GetHeight() > maxHeight) {
|
||||
double scaleX = (double)maxWidth / image.GetWidth();
|
||||
double scaleY = (double)maxHeight / image.GetHeight();
|
||||
double scale = std::min(scaleX, scaleY);
|
||||
image.Rescale(image.GetWidth() * scale, image.GetHeight() * scale, wxIMAGE_QUALITY_HIGH);
|
||||
}
|
||||
|
||||
wxBitmap bitmap(image);
|
||||
auto *imageCtrl = new wxStaticBitmap(this, wxID_ANY, bitmap);
|
||||
sizer->Add(imageCtrl, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
} else {
|
||||
wxLogDebug("Failed to load image: %s", mediaName);
|
||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format("[Media: %s]", mediaName));
|
||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
}
|
||||
} else {
|
||||
wxLogDebug("No media data for: %s", mediaName);
|
||||
auto *mediaLabel = new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format("[Media: %s]", mediaName));
|
||||
mediaLabel->SetForegroundColour(*wxLIGHT_GREY);
|
||||
sizer->Add(mediaLabel, 0, wxALL | wxALIGN_CENTER, 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);
|
||||
} 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);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
auto *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
if (buttons.empty()) {
|
||||
auto *okButton = new wxButton(this, wxID_OK, "OK");
|
||||
buttonSizer->Add(okButton, 0, wxALL, 5);
|
||||
okButton->Bind(wxEVT_BUTTON, &WherigoMessageDialog::onButton, this);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
sizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxBOTTOM, 10);
|
||||
|
||||
SetSizerAndFit(sizer);
|
||||
SetMinSize(wxSize(600, 300)); // Doubled from 300x150
|
||||
CenterOnParent();
|
||||
}
|
||||
|
||||
void WherigoMessageDialog::onButton(wxCommandEvent &event) {
|
||||
int id = event.GetId();
|
||||
if (id == wxID_OK) {
|
||||
m_selectedButton = 0;
|
||||
} else {
|
||||
m_selectedButton = id - 1000;
|
||||
}
|
||||
EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
WherigoDialogRunner& WherigoDialogRunner::getInstance() {
|
||||
static WherigoDialogRunner instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void WherigoDialogRunner::showMessageBox(const wxString &text, const wxString &title,
|
||||
std::function<void(int)> callback) {
|
||||
WherigoMessageDialog dlg(nullptr, text, title);
|
||||
dlg.ShowModal();
|
||||
|
||||
if (callback) {
|
||||
callback(dlg.getSelectedButton());
|
||||
}
|
||||
}
|
||||
|
||||
void WherigoDialogRunner::showDialog(const std::vector<DialogEntry> &entries,
|
||||
std::function<void(int)> callback) {
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
const auto &entry = entries[i];
|
||||
|
||||
std::vector<wxString> buttons;
|
||||
for (const auto &btn : entry.buttons) {
|
||||
buttons.push_back(wxString::FromUTF8(btn));
|
||||
}
|
||||
|
||||
if (buttons.empty() && i < entries.size() - 1) {
|
||||
buttons.push_back("Weiter");
|
||||
} else if (buttons.empty()) {
|
||||
buttons.push_back("OK");
|
||||
}
|
||||
|
||||
WherigoMessageDialog dlg(nullptr, wxString::FromUTF8(entry.text), "Wherigo",
|
||||
buttons, wxString::FromUTF8(entry.mediaName));
|
||||
dlg.ShowModal();
|
||||
|
||||
// For the last entry, call the callback with the selected button
|
||||
if (i == entries.size() - 1 && callback) {
|
||||
callback(dlg.getSelectedButton());
|
||||
}
|
||||
}
|
||||
|
||||
// Notify game state change after dialog sequence completes
|
||||
GameEngine::getInstance().notifyStateChanged();
|
||||
}
|
||||
|
||||
} // namespace wherigo
|
||||
|
||||
Reference in New Issue
Block a user