diff --git a/.gitmodules b/.gitmodules index 1d4d149f..2c54b305 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "lib/lua"] path = lib/lua url = git://github.com/lua/lua +[submodule "lib/apclientpp"] + path = lib/apclientpp + url = https://github.com/black-sliver/apclientpp.git +[submodule "lib/wswrap"] + path = lib/wswrap + url = https://github.com/black-sliver/wswrap.git diff --git a/Makefile b/Makefile index 2d92fe1b..91fd2058 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ SRC = $(wildcard $(SRC_DIR)/*.cpp) \ $(wildcard $(SRC_DIR)/core/*.cpp) \ $(wildcard $(SRC_DIR)/usb2snes/*.cpp) \ $(wildcard $(SRC_DIR)/uat/*.cpp) \ + $(wildcard $(SRC_DIR)/ap/*.cpp) \ + $(wildcard $(LIB_DIR)/wswrap/src/*.cpp) \ $(wildcard $(SRC_DIR)/http/*.cpp) \ $(wildcard $(LIB_DIR)/tinyfiledialogs/*.cpp) #lib/gifdec/gifdec.c @@ -19,8 +21,9 @@ HDR = $(wildcard $(SRC_DIR)/*.h) \ $(wildcard $(SRC_DIR)/core/*.h) \ $(wildcard $(SRC_DIR)/usb2snes/*.h) \ $(wildcard $(SRC_DIR)/uat/*.h) \ + $(wildcard $(SRC_DIR)/ap/*.h) \ $(wildcard $(SRC_DIR)/http/*.h) -INCLUDE_DIRS = -Ilib -Ilib/lua -Ilib/asio/include -DASIO_STANDALONE -Ilib/miniz -Ilib/json/include -Ilib/valijson/include -Ilib/tinyfiledialogs #-Ilib/gifdec +INCLUDE_DIRS = -Ilib -Ilib/lua -Ilib/asio/include -DASIO_STANDALONE -Ilib/miniz -Ilib/json/include -Ilib/valijson/include -Ilib/tinyfiledialogs -Ilib/wswrap/include #-Ilib/gifdec WIN32_INCLUDE_DIRS = -Iwin32-lib/i686/include WIN32_LIB_DIRS = -L./win32-lib/i686/bin -L./win32-lib/i686/lib WIN64_INCLUDE_DIRS = -Iwin32-lib/x86_64/include diff --git a/doc/AUTOTRACKING.md b/doc/AUTOTRACKING.md index 58b8a9fa..7f3bf801 100644 --- a/doc/AUTOTRACKING.md +++ b/doc/AUTOTRACKING.md @@ -7,6 +7,8 @@ as provided by QUsb2Snes, Usb2Snes and SNI * Variables * [UAT Protocol](https://github.com/black-sliver/UAT) +* Archipelago Multiworld + * see [archipelago.gg](https://archipelago.gg) ## Atomicity @@ -111,3 +113,26 @@ ScriptHost:AddVariableWatch("Alchemy", {"acid_rain"}, updateAlchemy) ``` See [examples/uat-example](../examples/uat-example) for a full example. + + +## Archipelago Interface + +This is fully callback-driven. + +Archipelago auto-tracking is enabled by adding "ap" to a variant's flags in +manifest.json and clicking on "AT" when the pack is loaded. + +* when connection to a server is established, Clear handlers are called so the + Lua script can clear all locations and items. +* when an item is received, Item handlers are called +* when a location is checked, Location handlers are called +* when a location is scouted, Scout handlers are called +* when a bounce is received, Bounced handlers are called + +### global Archipelago +* `.PlayerNumber` returns the slot number of the connected player or -1 if not connected +* `:AddClearHandler(name, callback)` called when connecting to a (new) server and state should be cleared +* `:AddItemHandler(name, callback)` called when an item is received; args: index, item_id, item_name +* `:AddLocationHandler(name, callback)` called when a location was checked; args: location_id, location_name +* `:AddScoutHandler(name, callback)` called when a location was scouted; args: location_id, location_name, item_id, item_name, item_player +* `:AddBouncedHandler(name, callback)` called when the server sends a bounce; args: json bounce message as table \ No newline at end of file diff --git a/lib/apclientpp b/lib/apclientpp new file mode 160000 index 00000000..c4a329e0 --- /dev/null +++ b/lib/apclientpp @@ -0,0 +1 @@ +Subproject commit c4a329e0624e0df3b4ce4087df3b1e1b918d8ec2 diff --git a/lib/wswrap b/lib/wswrap new file mode 160000 index 00000000..4be367a2 --- /dev/null +++ b/lib/wswrap @@ -0,0 +1 @@ +Subproject commit 4be367a21a9a0784161759cba38bc4a8639c2254 diff --git a/src/ap/aptracker.cpp b/src/ap/aptracker.cpp new file mode 100644 index 00000000..dd85c5f7 --- /dev/null +++ b/src/ap/aptracker.cpp @@ -0,0 +1,13 @@ +#include "aptracker.h" +#include +#include + + +const std::map APTracker::_errorMessages = { + { "InvalidSlot", "Invalid Slot; please verify that you have connected to the correct world." }, + { "InvalidGame", "Invalid Game; please verify that you connected with the right game to the correct world." }, + { "SlotAlreadyTaken", "Player slot already in use for that team" }, + { "IncompatibleVersion", "Server reported your client version as incompatible" }, + { "InvalidPassword", "Wrong password" }, + { "InvalidItemsHandling", "The item handling flags requested by the client are not supported" } +}; \ No newline at end of file diff --git a/src/ap/aptracker.h b/src/ap/aptracker.h new file mode 100644 index 00000000..ae97898e --- /dev/null +++ b/src/ap/aptracker.h @@ -0,0 +1,229 @@ +#ifndef _AP_APTRACKER_H +#define _AP_APTRACKER_H + +// APTracker wraps APClient to provide a simpler interface to core/AutoTracker +#include +#include +#include +#include +#include +#include "../core/signal.h" +#include "../core/fileutil.h" +#include +#include +#include +#include +#include + + +class APTracker final { + typedef nlohmann::json json; +public: + APTracker(const std::string& appname, const std::string& game="") + : _appname(appname), _game(game) + { + // AP uses an UUID to detect reconnects (replace old connection). If + // stored UUID is older than 60min, the connection should already be + // dropped, so we can generate a new one. + bool newUuid = false; + auto uuidFilename = getConfigPath(_appname, UUID_FILENAME); + struct stat st; + if (stat(uuidFilename.c_str(), &st) == 0) { + time_t elapsed = time(nullptr) - st.st_mtime; + newUuid = elapsed > 3600; + } + if (!newUuid) newUuid = !readFile(uuidFilename, _uuid); + if (!newUuid) newUuid = _uuid.empty(); + if (newUuid) { + // generate a new uuid thread-safe + _uuid.clear(); + struct timespec ts; + unsigned rng; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + rng = (unsigned)ts.tv_sec; + rng ^= (unsigned)ts.tv_nsec; + } else { + rng = (unsigned)time(nullptr); + } + const char uuid_chars[] = "0123456789abcdef"; + for (int n=0; n<16; n++) + _uuid += uuid_chars[rand_r(&rng) % strlen(uuid_chars)]; + writeFile(uuidFilename, _uuid); + } else { + // update mtime of uuid file + utime(uuidFilename.c_str(), nullptr); + } + std::string s; + if (readFile(getConfigPath(_appname, DATAPACKAGE_FILENAME), s)) { + try { + _datapackage = json::parse(s); + } catch (...) { } + } + } + + virtual ~APTracker() + { + disconnect(true); + } + + bool connect(const std::string& uri, const std::string& slot, const std::string& pw) + { + disconnect(true); + + if (uri.empty()) { + fprintf(stderr, "APTracker: uri can't be empty!\n"); + onError.emit(this, "Invalid server"); + return false; + } + if (slot.empty()) { + fprintf(stderr, "APTracker: slot can't be empty!\n"); + onError.emit(this, "Invalid slot name"); + return false; + } + + _ap = new APClient(_uuid, _game, uri); + _ap->set_data_package(_datapackage); + _ap->set_socket_connected_handler([this, slot, pw]() { + auto lock = EventLock(_event); + std::list tags = {"Tracker"}; + if (_game.empty()) tags.push_back("IgnoreGame"); + return _ap->ConnectSlot(slot, pw, 0b111, tags); + }); + _ap->set_slot_refused_handler([this](const std::list& errs) { + auto lock = EventLock(_event); + auto contains = [](const std::list& list, const std::string& element) { + return std::find(list.begin(), list.end(), element) != list.end(); + }; + if (errs.empty()) { + onError.emit(this, "Connection refused, no reason provided."); + goto clean_up; + } + for (const auto& pair : _errorMessages) { + if (contains(errs, pair.first)) { + onError.emit(this, pair.second); + goto clean_up; + } + } + onError.emit(this, "Connection refused: " + errs.front()); + clean_up: + disconnect(); + }); + _ap->set_slot_connected_handler([this]() { + auto lock = EventLock(_event); + onStateChanged.emit(this, _ap->get_state()); + printf("TODO: clean up state\n"); + _itemIndex = 0; + onClear.emit(this); + + // update mtime of uuid file + touchUUID(); + }); + _ap->set_slot_disconnected_handler([this]() { + auto lock = EventLock(_event); + onStateChanged.emit(this, _ap->get_state()); + }); + _ap->set_data_package_changed_handler([this](const json& datapackage) { + _datapackage = datapackage; + writeFile(getConfigPath(_appname, DATAPACKAGE_FILENAME), _datapackage.dump()); + }); + _ap->set_bounced_handler([this](const json& packet) { + auto lock = EventLock(_event); + onBounced.emit(this, packet); + }); + _ap->set_items_received_handler([this](const std::list& items) { + auto lock = EventLock(_event); + for (const auto& item: items) { + if (item.index != _itemIndex) continue; + // TODO: Sync if item.index > _itemIndex + onItem.emit(this, item.index, item.item, _ap->get_item_name(item.item)); + _itemIndex++; + } + }); + _ap->set_location_checked_handler([this](const std::list& locations) { + auto lock = EventLock(_event); + for (int location: locations) { + onLocationChecked.emit(this, location, _ap->get_location_name(location)); + } + }); + _ap->set_location_info_handler([this](const std::list& scouts) { + auto lock = EventLock(_event); + for (const auto& scout: scouts) { + onScout.emit(this, scout.location, _ap->get_location_name(scout.location), + scout.item, _ap->get_item_name(scout.item), scout.player); + } + }); + return true; + } + + void disconnect(bool force=false) + { + if (_event && !force) { + // we can not stop the service while a callback is being executed + _scheduleDisconnect = true; + return; + } + bool wasConnected = _ap && _ap->get_state() != APClient::State::DISCONNECTED; + if (_ap) delete _ap; + _ap = nullptr; + if (wasConnected) + onStateChanged.emit(this, APClient::State::DISCONNECTED); + _scheduleDisconnect = false; + } + + bool poll() + { + if (_event) { + fprintf(stderr, "APTracker: polling from a signal is unsupported\n"); + return false; + } + if (_scheduleDisconnect) disconnect(); + if (_ap) _ap->poll(); + return false; + } + + void touchUUID() + { + // update mtime of the uuid file so we reuse the uuid on next connect + utime(getConfigPath(_appname, UUID_FILENAME).c_str(), nullptr); + } + + int getPlayerNumber() const + { + return _ap ? _ap->get_player_number() : -1; + } + + Signal onError; + Signal onStateChanged; + Signal<> onClear; // called when state has to be cleared + Signal onItem; // index, item, item_name + Signal onScout; // location, location_name, item, item_name, target player + Signal onLocationChecked; // location, location_name + Signal onBounced; // packet + +private: + APClient* _ap = nullptr; + std::string _appname; + std::string _game; + std::string _uuid; + json _datapackage; + int _itemIndex = 0; + std::set _checkedLocations; + std::set _uncheckedLocations; + int _event = 0; + bool _scheduleDisconnect = false; + + static const std::map _errorMessages; + + constexpr static char DATAPACKAGE_FILENAME[] = "ap-datapackage.json"; + constexpr static char UUID_FILENAME[] = "ap-uuid.txt"; + + struct EventLock final + { + EventLock(int& event) : _event(event) { _event++; } + ~EventLock() { _event--; } + private: + int& _event; + }; +}; + +#endif // _AP_APTRACKER_H \ No newline at end of file diff --git a/src/ap/archipelago.cpp b/src/ap/archipelago.cpp new file mode 100644 index 00000000..39c2494c --- /dev/null +++ b/src/ap/archipelago.cpp @@ -0,0 +1,126 @@ +#include "archipelago.h" +#include "../luaglue/luamethod.h" +#include "../luaglue/lua.h" +#include "../luaglue/lua_json.h" + + +const LuaInterface::MethodMap Archipelago::Lua_Methods = { + LUA_METHOD(Archipelago, AddClearHandler, const char*, LuaRef), + LUA_METHOD(Archipelago, AddItemHandler, const char*, LuaRef), + LUA_METHOD(Archipelago, AddLocationHandler, const char*, LuaRef), + LUA_METHOD(Archipelago, AddScoutHandler, const char*, LuaRef), + LUA_METHOD(Archipelago, AddBouncedHandler, const char*, LuaRef), +}; + +Archipelago::Archipelago(lua_State *L, APTracker *ap) + : _L(L), _ap(ap) +{ +} + +Archipelago::~Archipelago() +{ + _L = nullptr; + _ap = nullptr; +} + +bool Archipelago::AddClearHandler(const std::string& name, LuaRef callback) +{ + if (!_ap || !callback.valid()) return false; + int ref = callback.ref; + _ap->onClear += {this, [this, ref, name](void*) { + lua_rawgeti(_L, LUA_REGISTRYINDEX, ref); + if (lua_pcall(_L, 0, 0, 0)) { + const char* err = lua_tostring(_L, -1); + printf("Error calling Archipelago ClearHandler for %s: %s\n", + name.c_str(), err ? err : "Unknown"); + lua_pop(_L, 1); + } + }}; + return true; +} + +bool Archipelago::AddItemHandler(const std::string& name, LuaRef callback) +{ + if (!_ap || !callback.valid()) return false; + int ref = callback.ref; + _ap->onItem += {this, [this, ref, name](void*, int index, int item, const std::string& item_name) { + lua_rawgeti(_L, LUA_REGISTRYINDEX, ref); + Lua(_L).Push((lua_Integer)index); + Lua(_L).Push((lua_Integer)item); + Lua(_L).Push(item_name.c_str()); + if (lua_pcall(_L, 3, 0, 0)) { + const char* err = lua_tostring(_L, -1); + printf("Error calling Archipelago ItemHandler for %s: %s\n", + name.c_str(), err ? err : "Unknown"); + lua_pop(_L, 1); + } + }}; + return true; +} + +bool Archipelago::AddLocationHandler(const std::string& name, LuaRef callback) +{ + if (!_ap || !callback.valid()) return false; + int ref = callback.ref; + _ap->onLocationChecked += {this, [this, ref, name](void*, int location, const std::string& location_name) { + lua_rawgeti(_L, LUA_REGISTRYINDEX, ref); + Lua(_L).Push((lua_Integer)location); + Lua(_L).Push(location_name.c_str()); + if (lua_pcall(_L, 2, 0, 0)) { + const char* err = lua_tostring(_L, -1); + printf("Error calling Archipelago LocationHandler for %s: %s\n", + name.c_str(), err ? err : "Unknown"); + lua_pop(_L, 1); + } + }}; + return true; +} + +bool Archipelago::AddScoutHandler(const std::string& name, LuaRef callback) +{ + if (!_ap || !callback.valid()) return false; + int ref = callback.ref; + _ap->onScout += {this, [this, ref, name](void*, int location, const std::string& location_name, + int item, const std::string& item_name, int player) { + lua_rawgeti(_L, LUA_REGISTRYINDEX, ref); + Lua(_L).Push((lua_Integer)location); + Lua(_L).Push(location_name.c_str()); + Lua(_L).Push((lua_Integer)item); + Lua(_L).Push(item_name.c_str()); + Lua(_L).Push((lua_Integer)player); + if (lua_pcall(_L, 5, 0, 0)) { + const char* err = lua_tostring(_L, -1); + printf("Error calling Archipelago ScoutHandler for %s: %s\n", + name.c_str(), err ? err : "Unknown"); + lua_pop(_L, 1); + } + }}; + return true; +} + +bool Archipelago::AddBouncedHandler(const std::string& name, LuaRef callback) +{ + if (!_ap || !callback.valid()) return false; + int ref = callback.ref; + _ap->onBounced += {this, [this, ref, name](void*, const nlohmann::json& data) { + nlohmann::json j = data; + lua_rawgeti(_L, LUA_REGISTRYINDEX, ref); + json_to_lua(_L, j); + if (lua_pcall(_L, 1, 0, 0)) { + const char* err = lua_tostring(_L, -1); + printf("Error calling Archipelago BouncedHandler for %s: %s\n", + name.c_str(), err ? err : "Unknown"); + lua_pop(_L, 1); + } + }}; + return true; +} + +int Archipelago::Lua_Index(lua_State *L, const char* key) { + if (strcmp(key, "PlayerNumber")==0) { + lua_pushinteger(L, _ap ? _ap->getPlayerNumber() : -1); + return 1; + } + printf("Get Archipelago.%s unknown\n", key); + return 0; +} \ No newline at end of file diff --git a/src/ap/archipelago.h b/src/ap/archipelago.h new file mode 100644 index 00000000..d955c255 --- /dev/null +++ b/src/ap/archipelago.h @@ -0,0 +1,39 @@ +#ifndef _AP_ARCHIPELAGO_H +#define _AP_ARCHIPELAGO_H + +// Lua interface to AutoTracker::_ap + +#include "../luaglue/luainterface.h" +#include "../luaglue/lua_json.h" +#include "../core/autotracker.h" +#include "../ap/aptracker.h" + +class Archipelago; + +class Archipelago : public LuaInterface { + friend class LuaInterface; + +public: + Archipelago(lua_State *L, APTracker *ap); + virtual ~Archipelago(); + + bool AddClearHandler(const std::string& name, LuaRef callback); + bool AddItemHandler(const std::string& name, LuaRef callback); + bool AddLocationHandler(const std::string& name, LuaRef callback); + bool AddScoutHandler(const std::string& name, LuaRef callback); + bool AddBouncedHandler(const std::string& name, LuaRef callback); + +protected: + lua_State *_L; + APTracker *_ap; + +protected: // Lua interface implementation + static constexpr const char Lua_Name[] = "Archipelago"; + static const LuaInterface::MethodMap Lua_Methods; + + virtual int Lua_Index(lua_State *L, const char* key) override; +}; + + +#endif // _AP_ARCHIPELAGO_H + diff --git a/src/core/autotracker.h b/src/core/autotracker.h index f166005f..a579ef55 100644 --- a/src/core/autotracker.h +++ b/src/core/autotracker.h @@ -3,6 +3,7 @@ #include "../usb2snes/usb2snes.h" #include "../uat/uatclient.h" +#include "../ap/aptracker.h" #include "signal.h" #include #include @@ -25,7 +26,29 @@ class AutoTracker final : public LuaInterface{ AutoTracker(const std::string& platform, const std::set& flags, const std::string& name="PopTracker") : _name(name) { - if (strcasecmp(platform.c_str(), "snes")==0) { + if (flags.find("ap") != flags.end()) { + _ap = new APTracker(_name); + _state = State::Disabled; + _ap->onError += {this, [this](void* sender, const std::string& msg) { + if (_state != State::Disabled && _state != State::Disconnected) { + _state = State::Disconnected; + onStateChange.emit(this, _state); + } + if (!msg.empty()) { + onError.emit(this, "AP: " + msg); + } + }}; + _ap->onStateChanged += {this, [this](void* sender, auto state) { + State newstate = + state == APClient::State::SLOT_CONNECTED ? State::ConsoleConnected : + state == APClient::State::ROOM_INFO ? State::BridgeConnected : + State::Disconnected; + if (_state != newstate) { + _state = newstate; + onStateChange.emit(this, _state); + } + }}; + } else if (strcasecmp(platform.c_str(), "snes")==0) { _snes = new USB2SNES(_name); } else if (flags.find("uat") != flags.end()) { _uat = new UATClient(); @@ -93,6 +116,7 @@ class AutoTracker final : public LuaInterface{ Signal onStateChange; Signal<> onDataChange; Signal&> onVariablesChanged; + Signal onError; State getState() const { return _state; } bool doStuff() @@ -142,6 +166,11 @@ class AutoTracker final : public LuaInterface{ return true; } + // AP AUTOTRACKING + if (_ap && _ap->poll()) { + return true; + } + // NO UPDATES return false; } @@ -253,12 +282,24 @@ class AutoTracker final : public LuaInterface{ return {}; } - void enable() + bool enable(const std::string& uri="", const std::string& slot="", const std::string& password="") { - if (_state == State::Disabled && (_snes || _uat)) { + if (_state != State::Disabled) return true; + if (_snes || _uat) { + // snes and uat will auto-connect when polling (doStuff()) _state = State::Disconnected; onStateChange.emit(this, _state); + return true; } + else if (_ap) { + // ap requires explicit connection to a server + if (_ap->connect(uri, slot, password)) { + _state = State::Disconnected; + onStateChange.emit(this, _state); + return true; + } + } + return false; } void disable() @@ -282,13 +323,21 @@ class AutoTracker final : public LuaInterface{ && _uat->getState() != UATClient::State::DISCONNECTING) { _uat->disconnect(websocketpp::close::status::going_away); } + if (_ap) + _ap->disconnect(); onStateChange.emit(this, _state); } + APTracker* getAP() const + { + return _ap; + } + protected: State _state = State::Disconnected; USB2SNES *_snes = nullptr; UATClient *_uat = nullptr; + APTracker *_ap = nullptr; std::string _slot; // selected slot for UAT std::map _vars; // variable store for UAT std::string _name; diff --git a/src/poptracker.cpp b/src/poptracker.cpp index 6890cd19..dc814481 100644 --- a/src/poptracker.cpp +++ b/src/poptracker.cpp @@ -16,6 +16,7 @@ extern "C" { #include "core/log.h" #include #include "http/http.h" +#include "ap/archipelago.h" #ifdef _WIN32 #include #endif @@ -138,6 +139,11 @@ PopTracker::PopTracker(int argc, char** argv) if (_config["export_dir"].is_string()) _exportDir = _config["export_dir"]; + if (_config["at_uri"].is_string()) + _atUri = _config["at_uri"]; + if (_config["at_slot"].is_string()) + _atSlot = _config["at_slot"]; + saveConfig(); _ui = new Ui::Ui(APPNAME, _config["software_renderer"]?true:false); @@ -286,8 +292,31 @@ bool PopTracker::start() auto at = _scriptHost->getAutoTracker(); if (!at) return; if (at->getState() == AutoTracker::State::Disabled) { - at->enable(); - _autoTrackerDisabled = false; + auto flags = _pack->getVariantFlags(); + if (flags.count("ap")) { + // TODO: replace tinyfd by GUI overlay + const char* s; + s = tinyfd_inputBox("PopTracker", "Enter archipelago server:port", _atUri.empty()?"localhost":_atUri.c_str()); + if (!s) return; + std::string uri = s; + s = tinyfd_inputBox("PopTracker", "Enter slot", (_atUri==uri)?_atSlot.c_str():"Player"); + if (!s) return; + std::string slot = s; + s = tinyfd_inputBox("PopTracker", "Enter password", nullptr); + if (!s) return; + std::string password = s; + _atUri = uri; + _atSlot = slot; + _atPassword = password; + if (at->enable(_atUri, _atSlot, _atPassword)) { + _autoTrackerDisabled = false; + _config["at_uri"] = _atUri; + _config["at_slot"] = _atSlot; + } + } else { + if (at->enable()) + _autoTrackerDisabled = false; + } } else if (at->getState() != AutoTracker::State::Disabled) { at->disable(); @@ -502,10 +531,12 @@ void PopTracker::unloadTracker() delete _scriptHost; delete _tracker; delete _pack; + delete _archipelago; _L = nullptr; _tracker = nullptr; _scriptHost = nullptr; _pack = nullptr; + _archipelago = nullptr; } bool PopTracker::loadTracker(const std::string& pack, const std::string& variant, bool loadAutosave) { @@ -541,8 +572,23 @@ bool PopTracker::loadTracker(const std::string& pack, const std::string& variant ScriptHost::Lua_Register(_L); _scriptHost->Lua_Push(_L); lua_setglobal(_L, "ScriptHost"); - if (_scriptHost->getAutoTracker() && _autoTrackerDisabled) - _scriptHost->getAutoTracker()->disable(); + AutoTracker* at = _scriptHost->getAutoTracker(); + if (at) { + if (_autoTrackerDisabled) + at->disable(); + at->onError += {this, [this](void*, const std::string& msg) { + tinyfd_messageBox("PopTracker", msg.c_str(), "ok", "error", 0); + }}; + auto ap = at->getAP(); + if (ap) { + printf("Creating Archipelago Interface...\n"); + _archipelago = new Archipelago(_L, ap); + printf("Registering in Lua...\n"); + Archipelago::Lua_Register(_L); + _archipelago->Lua_Push(_L); + lua_setglobal(_L, "Archipelago"); + } + } printf("Registering classes in Lua...\n"); LuaItem::Lua_Register(_L); // TODO: move this to Tracker or ScriptHost diff --git a/src/poptracker.h b/src/poptracker.h index 16ce49e0..eb4b2eeb 100644 --- a/src/poptracker.h +++ b/src/poptracker.h @@ -12,6 +12,7 @@ #include "core/scripthost.h" #include "core/imagereference.h" #include "core/version.h" +#include "ap/archipelago.h" #include #include @@ -27,6 +28,7 @@ class PopTracker final : public App { lua_State* _L = nullptr; Tracker *_tracker = nullptr; ScriptHost *_scriptHost = nullptr; + Archipelago *_archipelago = nullptr; Pack *_pack = nullptr; ImageReference _imageReference; bool _autoTrackerDisabled = false; @@ -45,6 +47,8 @@ class PopTracker final : public App { bool _newTrackerLoadAutosave = false; std::chrono::steady_clock::time_point _autosaveTimer; + std::string _atUri, _atSlot, _atPassword; + bool loadTracker(const std::string& pack, const std::string& variant, bool loadAutosave=true); bool scheduleLoadTracker(const std::string& pack, const std::string& variant, bool loadAutosave=true); void unloadTracker();