Skip to content

Commit

Permalink
WIP: Archipelago
Browse files Browse the repository at this point in the history
  • Loading branch information
timoschwarzer committed Dec 16, 2024
1 parent c82921d commit b7e53f4
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 35 deletions.
3 changes: 3 additions & 0 deletions projects/Randomizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,9 @@ target_link_libraries(
$<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static>
)

find_package(stduuid CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} stduuid)

message(STATUS "Assigning source groups.")

set_vc_structure("${CMAKE_CURRENT_SOURCE_DIR}" "${SOURCE_FILES};${PUBLIC_HEADER_FILES};${MISC_FILES}")
Expand Down
19 changes: 13 additions & 6 deletions projects/Randomizer/archipelago/archipelago.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include <Randomizer/archipelago/archipelago_protocol.h>
#include <Randomizer/archipelago/archipelago_save_meta.h>

#define UUID_SYSTEM_GENERATOR
#include <uuid.h>

namespace randomizer::archipelago {
auto archipelago_save_data = std::make_shared<ArchipelagoSaveData>();

Expand All @@ -17,7 +20,8 @@ namespace randomizer::archipelago {
m_websocket.setOnMessageCallback([this](const auto& msg) { on_websocket_message(msg); });
}

void ArchipelagoClient::connect(const std::string_view url, const std::string_view password) {
void ArchipelagoClient::connect(const std::string_view url, const std::string_view slot_name, const std::string_view password) {
m_slot_name = slot_name;
m_password = password;
m_websocket.stop();
m_websocket.setUrl(std::string(url));
Expand Down Expand Up @@ -52,13 +56,16 @@ namespace randomizer::archipelago {
}
case ix::WebSocketMessageType::Open: {
modloader::info("archipelago", "Connected to server");

const uuids::uuid uuid = uuids::uuid_system_generator{}();

send_message(messages::Connect{
m_password,
"Ori and the Will of the Wisps",
"Player", // TODO
"12345667", // TODO
m_slot_name,
uuids::to_string(uuid),
messages::NetworkVersion{0, 5, 0},
0b101,
0b111,
{},
false,
});
Expand All @@ -72,7 +79,7 @@ namespace randomizer::archipelago {
// If we are in here we did not expect this disconnect, underlying socket will auto reconnect.
core::events::schedule_task(3.f, [this] {
if (m_should_connect && !is_connected()) {
connect(m_websocket.getUrl(), m_password);
connect(m_websocket.getUrl(), m_slot_name, m_password);
}
});
}
Expand All @@ -81,7 +88,7 @@ namespace randomizer::archipelago {
case ix::WebSocketMessageType::Error:
core::events::schedule_task(10.f, [this]() {
if (m_should_connect) {
connect(m_websocket.getUrl(), m_password);
connect(m_websocket.getUrl(), m_slot_name, m_password);
}
});
break;
Expand Down
3 changes: 2 additions & 1 deletion projects/Randomizer/archipelago/archipelago.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace randomizer::archipelago {
class ArchipelagoClient {
public:
ArchipelagoClient();
void connect(std::string_view url, std::string_view password);
void connect(std::string_view url, std::string_view slot_name, std::string_view password);
void disconnect();
bool is_connected() const;

Expand All @@ -31,6 +31,7 @@ namespace randomizer::archipelago {
bool m_connected = false;
bool m_should_connect = false;
ix::WebSocket m_websocket;
std::string m_slot_name; // aka player name
std::string m_password;
int m_last_item_index = 0;
};
Expand Down
62 changes: 51 additions & 11 deletions projects/Randomizer/archipelago/archipelago_ids.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
#include <Modloader/app/types/UberStateCollection.h>
#include <Modloader/modloader.h>
#include <Randomizer/archipelago/archipelago_ids.h>
#include <Randomizer/randomizer.h>

using namespace app::classes;

namespace randomizer::archipelago::ids {
/**
* Since locations store only the first 8 bits of the uber group, we need to keep a
* mapping of the Archipelago IDs to the actual locations.
*/
std::unordered_map<archipelago_id_t, location_data::Location> location_map;

[maybe_unused] auto on_location_collection_loading = event_bus().register_handler(
RandomizerEvent::LocationCollectionLoaded,
EventTiming::Before,
[](auto, auto) {
location_map.clear();
}
);

[maybe_unused] auto on_location_collection_loaded = event_bus().register_handler(
RandomizerEvent::LocationCollectionLoaded,
EventTiming::After,
[](auto, auto) {
for (const auto& location: location_collection().locations()) {
const auto location_id = get_location_id(location);

auto existing_it = location_map.find(location_id);
if (existing_it != location_map.end()) {
modloader::warn(
"archipelago",
std::format(
"Location {} overlaps location {} when converted to an Archipelago ID and thus cannot be used in Archipelago",
location.name,
existing_it->second.name
)
);
continue;
}

location_map.emplace(location_id, location);
}

modloader::info("archipelago", "Built location map");
}
);

archipelago_id_t get_boolean_item_id(int uber_group, int uber_state) {
assert(uber_group <= 0b11111111'11111111);
assert(uber_state <= 0b11111111'11111111);

return BASE_ID
+ (static_cast<archipelago_id_t>(IdType::BooleanItem) << 32)
+ (uber_group << 16)
+ uber_state;
return BASE_ID + (static_cast<archipelago_id_t>(IdType::BooleanItem) << 32) + (uber_group << 16) + uber_state;
}

archipelago_id_t get_resource_item_id(ResourceType type, int16_t value) {
return BASE_ID
+ (static_cast<archipelago_id_t>(IdType::ResourceItem) << 32)
+ (static_cast<archipelago_id_t>(type) << 16)
+ value;
return BASE_ID + (static_cast<archipelago_id_t>(IdType::ResourceItem) << 32) + (static_cast<archipelago_id_t>(type) << 16) + value;
}

archipelago_id_t get_location_id(const location_data::Location& location) {
Expand All @@ -34,18 +74,18 @@ namespace randomizer::archipelago::ids {

switch (id_type) {
case IdType::Location:
return Location {
return Location{
static_cast<int8_t>(id >> 24),
static_cast<int16_t>(id >> 8),
static_cast<int8_t>(id),
};
case IdType::BooleanItem:
return BooleanItem {
return BooleanItem{
static_cast<int16_t>(id >> 16),
static_cast<int16_t>(id),
};
case IdType::ResourceItem:
return ResourceItem {
return ResourceItem{
static_cast<ResourceType>(id >> 16),
static_cast<int16_t>(id),
};
Expand Down
4 changes: 4 additions & 0 deletions projects/Randomizer/seed/legacy_parser/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,8 @@ namespace randomizer::seed::legacy_parser {
}

meta.start_position = position;
} else if (line.starts_with("APSlot:")) {
meta.archipelago_slot_name = trim_copy(line.substr(sizeof("APSlot:")));
}
}
}
Expand Down Expand Up @@ -2130,6 +2132,8 @@ namespace randomizer::seed::legacy_parser {
}

data->info.meta.start_position = position;
} else if (line.starts_with("APSlot:")) {
data->info.meta.archipelago_slot_name = trim_copy(line.substr(sizeof("APSlot:")));
} else if (line.starts_with("timer:")) {
std::vector<std::string> parts;
auto timer_states = line.substr(6);
Expand Down
1 change: 1 addition & 0 deletions projects/Randomizer/seed/seed.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ namespace randomizer::seed {
int world_index = 0;
bool race_mode = false;
GameDifficultySettings game_difficulties;
std::optional<std::string> archipelago_slot_name = std::nullopt;
};

struct SeedInfo {
Expand Down
38 changes: 37 additions & 1 deletion projects/Randomizer/seed/seed_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace randomizer::seed {
}

if (source.starts_with("delayed-file:")) {
return std::make_shared<DebugDelayedFileSeedSource>(source.substr(13));
return std::make_shared<DebugDelayedFileSeedSource>(convert_string_to_wstring(source.substr(13)));
}

if (source.starts_with("server:")) {
Expand All @@ -27,6 +27,32 @@ namespace randomizer::seed {
}
}

if (source.starts_with("archipelago:")) {
const auto options_string = source.substr(12);
std::vector<std::string> options;
split_str(options_string, options, '|');

if (options.size() < 4) {
modloader::error("seed_source", "Archipelago seed sources need to give 4 options: file|host|port|password");
} else {
const auto port_string = options.at(2);

try {
const auto port = std::stoi(port_string);

// TODO: Pipes are not supported as passwords for now since they are almost never used according to Archipelago
return std::make_shared<ArchipelagoSeedSource>(
convert_string_to_wstring(options.at(0)),
options.at(1),
port,
options.at(3)
);
} catch (const std::invalid_argument&) {
modloader::error("seed_source", std::format("Parsing port '{}' failed", port_string));
}
}
}

if (source == "empty") {
return std::make_shared<EmptySeedSource>();
}
Expand All @@ -45,6 +71,16 @@ namespace randomizer::seed {
std::string ServerSeedSource::to_source_string() { return std::format("server:{}", m_multiverse_id); }
std::optional<long> ServerSeedSource::get_multiverse_id() { return m_multiverse_id; }
bool ServerSeedSource::allows_rereading() { return false; }
std::string ArchipelagoSeedSource::get_description() { return "AP: " + FileSeedSource::get_description(); }
std::string ArchipelagoSeedSource::to_source_string() {
return std::format(
"archipelago:{},{},{},{}",
convert_wstring_to_string(m_path.wstring()),
m_host,
m_port,
m_password
);
}

std::pair<SourceStatus, std::optional<std::string>> EmptySeedSource::poll() {
return {SourceStatus::Ready, "// Format Version: 1.0.0"};
Expand Down
36 changes: 20 additions & 16 deletions projects/Randomizer/seed/seed_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,15 @@ namespace randomizer::seed {
virtual bool allows_rereading() = 0;

/** Query custom properties for seed sources */
std::optional<std::variant<std::string, int, float>> get_property(int property) {
return std::nullopt;
}
std::optional<std::variant<std::string, int, float>> get_property(int property) { return std::nullopt; }

std::optional<std::string> get_error() { return m_error; }

protected:
std::optional<std::string> m_error;
};

class FileSeedSource: public SeedSource {
class FileSeedSource : public SeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
Expand All @@ -57,7 +55,7 @@ namespace randomizer::seed {
std::optional<std::string> m_content;
};

class DebugDelayedFileSeedSource: public SeedSource {
class DebugDelayedFileSeedSource : public SeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
Expand All @@ -73,36 +71,41 @@ namespace randomizer::seed {
std::optional<std::string> m_content;
};

class ServerSeedSource: public SeedSource {
class ServerSeedSource : public SeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
std::string to_source_string() override;
std::optional<long> get_multiverse_id() override;
bool allows_rereading() override;

explicit ServerSeedSource(long multiverse_id) : m_multiverse_id(multiverse_id) {};
explicit ServerSeedSource(long multiverse_id) :
m_multiverse_id(multiverse_id) {};

private:
long m_multiverse_id;
};

class ArchipelagoSeedSource: public SeedSource {
class ArchipelagoSeedSource : public FileSeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
std::string to_source_string() override;
std::optional<long> get_multiverse_id() override;
bool allows_rereading() override;

explicit ArchipelagoSeedSource(const std::string host, const int port, const std::string password);
explicit ArchipelagoSeedSource(const std::filesystem::path& path, const std::string& host, const int port, const std::string& password) :
FileSeedSource(path),
m_host(host),
m_port(port),
m_password(password) {}

private:
std::filesystem::path m_path;
std::string m_host;
int m_port;
std::string m_password;
std::optional<std::string> m_content;
};

class EmptySeedSource: public SeedSource {
class EmptySeedSource : public SeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
Expand All @@ -111,15 +114,16 @@ namespace randomizer::seed {
bool allows_rereading() override;
};

class InvalidSeedSource: public SeedSource {
class InvalidSeedSource : public SeedSource {
public:
std::pair<SourceStatus, std::optional<std::string>> poll() override;
std::string get_description() override;
std::string to_source_string() override;
std::optional<long> get_multiverse_id() override;
bool allows_rereading() override;

explicit InvalidSeedSource(const std::string& source_string) : m_source_string(source_string) {
explicit InvalidSeedSource(const std::string& source_string) :
m_source_string(source_string) {
m_error = std::format("Invalid source: {}", source_string);
};

Expand All @@ -129,4 +133,4 @@ namespace randomizer::seed {

void set_server_seed_content(const std::optional<std::string>& content);
std::shared_ptr<SeedSource> parse_source_string(const std::string& source);
}
} // namespace randomizer::seed

0 comments on commit b7e53f4

Please sign in to comment.