Skip to content

Commit

Permalink
WIP: Archipelago
Browse files Browse the repository at this point in the history
  • Loading branch information
timoschwarzer committed Aug 27, 2024
1 parent d9ad36e commit cb0311c
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 65 deletions.
5 changes: 5 additions & 0 deletions projects/Randomizer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set(PROJECT_DIR ${WOTWR_PROJECTS_DIR}/${PROJECT_NAME})

set(
SOURCE_FILES
"archipelago/archipelago.cpp"
"archipelago/archipelago_ids.cpp"
"conditions/condition_override.cpp"
"conditions/condition_uber_state.cpp"
"conditions/new_setup_state_override.cpp"
Expand Down Expand Up @@ -210,6 +212,9 @@ set(

set(
PUBLIC_HEADER_FILES
"archipelago/archipelago.h"
"archipelago/archipelago_ids.h"
"archipelago/messages.h"
"conditions/condition_override.h"
"conditions/condition_uber_state.h"
"conditions/new_setup_state_override.h"
Expand Down
80 changes: 80 additions & 0 deletions projects/Randomizer/archipelago/archipelago.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include <Core/events/task.h>
#include <Modloader/modloader.h>
#include <Randomizer/archipelago/archipelago.h>
#include <Randomizer/archipelago/messages.h>
#include <Randomizer/location_data/location.h>

namespace randomizer::archipelago {
ArchipelagoClient::ArchipelagoClient() {
m_websocket.setOnMessageCallback([this](const auto& msg) { on_websocket_message(msg); });
}

void ArchipelagoClient::connect(const std::string_view url, const std::string_view password) {
m_password = password;
m_websocket.stop();
m_websocket.setUrl(std::string(url));
m_websocket.setPingInterval(30);
m_websocket.disableAutomaticReconnection();
m_websocket.start();
m_should_connect = true;
modloader::info("archipelago", "AP client connected.");
}

bool ArchipelagoClient::is_connected() {
return m_websocket.getReadyState() == ix::ReadyState::Open;
}

void ArchipelagoClient::on_websocket_message(ix::WebSocketMessagePtr const& msg) {
switch (msg->type) {
case ix::WebSocketMessageType::Message: {
auto message_string = msg.get()->str;

try {
nlohmann::json message(message_string);
} catch (nlohmann::json::exception e) {
modloader::error("archipelago", std::format("Failed to parse message {}", message_string));
}
break;
}
case ix::WebSocketMessageType::Open: {
modloader::info("archipelago", "Connected to server");
send_message(messages::Connect{
m_password,
"Ori and the Will of the Wisps",
"Player", // TODO
"12345667", // TODO
messages::NetworkVersion{0, 5, 0},
0b101,
{},
false,
});
break;
}
case ix::WebSocketMessageType::Close: {
auto closed_reason = std::format("websocket closed '{}': {}", msg->closeInfo.code, msg->closeInfo.reason.c_str());
modloader::warn("archipelago", closed_reason);

if (m_should_connect) {
// 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);
}
});
}
break;
}
case ix::WebSocketMessageType::Error:
core::events::schedule_task(10.f, [this]() {
if (m_should_connect) {
connect(m_websocket.getUrl(), m_password);
}
});
break;
case ix::WebSocketMessageType::Ping:
case ix::WebSocketMessageType::Pong:
case ix::WebSocketMessageType::Fragment:
break;
}
}
} // namespace randomizer::archipelago
34 changes: 34 additions & 0 deletions projects/Randomizer/archipelago/archipelago.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include <IXWebSocket.h>
#include <nlohmann/json.hpp>
#include <string>

namespace randomizer::archipelago {
// TODO:
// - Add save meta slot for AP
// - Keep track of received items from AP

class ArchipelagoClient {
public:
ArchipelagoClient();
void connect(std::string_view url, std::string_view password);
void disconnect();
bool is_connected();

private:
template<typename Jsonable>
void send_message(const Jsonable& message) {
const nlohmann::json json(message);
m_websocket.send(json.dump());
}

void on_websocket_message(ix::WebSocketMessagePtr const& msg);

bool m_connected = false;
bool m_should_connect = false;
ix::WebSocket m_websocket;
std::string m_password;
int m_last_item_index = 0;
};
} // namespace randomizer::archipelago
56 changes: 56 additions & 0 deletions projects/Randomizer/archipelago/archipelago_ids.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <Randomizer/archipelago/archipelago_ids.h>

namespace randomizer::archipelago::ids {
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;
}

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;
}

archipelago_id_t get_location_id(const location_data::Location& location) {
assert(location.condition.value <= 0b11111111);
assert(location.condition.state.state() <= 0b11111111'11111111);

const archipelago_id_t group = (location.condition.state.group_int() & 0b11111111) << 24;
const archipelago_id_t state = (location.condition.state.state() & 0b11111111'11111111) << 8;
const archipelago_id_t value = static_cast<archipelago_id_t>(location.condition.value);

return BASE_ID + (static_cast<archipelago_id_t>(IdType::Location) << 32) + group + state + value;
}

std::variant<Location, BooleanItem, ResourceItem> get_item(archipelago_id_t id) {
const auto id_type = static_cast<IdType>(id >> 32);

switch (id_type) {
case IdType::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 {
static_cast<int16_t>(id >> 16),
static_cast<int16_t>(id),
};
case IdType::ResourceItem:
return ResourceItem {
static_cast<ResourceType>(id >> 16),
static_cast<int16_t>(id),
};
}

throw std::runtime_error(std::format("Could not process archipelago id {}", id));
}
} // namespace randomizer::archipelago::ids
64 changes: 64 additions & 0 deletions projects/Randomizer/archipelago/archipelago_ids.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma once

#include <Randomizer/location_data/location.h>
#include <variant>

namespace randomizer::archipelago::ids {
using archipelago_id_t = uint64_t;

/**
* Prefix (18 bits): "101111110010101001"
*
* Locations (34 bits): "00", 8 bits group, 16 bits state, 8 bits value (operator is implied, 0 ^= greater, everything else ^= greater or equal)
* Boolean items (34 bits): "01", 16 bits group, 16 bits state
* Resource items (34 bits): "10", 16 bits resource type, 16 bits resource value (spirit light only)
*
* Resource Types:
* 0b001: 100 spirit light
* 0b010: gorlek ore
* 0b011: keystones
* 0b100: shard slots
* 0b101: health fragments
* 0b110: energy fragments
*
* Decimal range: 3363010932375552 - 3363028112244735
*/

constexpr archipelago_id_t BASE_ID = 0b101111110010101001LL << 34; // ori (18 bit)

enum class IdType : int8_t {
Location = 0b00,
BooleanItem = 0b01,
ResourceItem = 0b10,
};

enum class ResourceType : int16_t {
SpiritLight = 0b001,
GorlekOre = 0b010,
Keystone = 0b011,
ShardSlot = 0b100,
HealthFragment = 0b101,
EnergyFragment = 0b110,
};

struct Location {
int8_t uber_group;
int16_t uber_state;
int8_t value;
};

struct BooleanItem {
int16_t uber_group;
int16_t uber_state;
};

struct ResourceItem {
ResourceType type;
int16_t value; // Only used for Spirit Light
};

archipelago_id_t get_boolean_item_id(int uber_group, int uber_state);
archipelago_id_t get_resource_item_id(ResourceType type, archipelago_id_t uid);
archipelago_id_t get_location_id(const location_data::Location& location);
std::variant<Location, BooleanItem, ResourceItem> get_item(archipelago_id_t id);
} // namespace randomizer::archipelago::ids
26 changes: 26 additions & 0 deletions projects/Randomizer/archipelago/messages.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <nlohmann/json.hpp>

namespace randomizer::archipelago::messages {
struct NetworkVersion {
int major;
int minor;
int patch;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NetworkVersion, major, minor, patch);

struct Connect {
std::string password;
std::string game;
std::string name;
std::string uuid;
NetworkVersion version;
int items_handling;
std::vector<std::string> tags;
bool slot_data;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Connect, password, game, name, uuid, version, items_handling, tags, slot_data);
}
Loading

0 comments on commit cb0311c

Please sign in to comment.