og-edopro/gframe/server_lobby.cpp

319 lines
12 KiB
C++

#include <nlohmann/json.hpp>
#include <IGUITable.h>
#include <IGUIEditBox.h>
#include <IGUIComboBox.h>
#include <IGUIButton.h>
#include <IGUICheckBox.h>
#include <IGUIWindow.h>
#include <ICursorControl.h>
#include "server_lobby.h"
#include "utils.h"
#include "data_manager.h"
#include "game.h"
#include "duelclient.h"
#include "logging.h"
#include "utils_gui.h"
#include "custom_skin_enum.h"
#include "game_config.h"
#include "address.h"
#include "fmt.h"
#include "curl.h"
namespace ygo {
std::vector<RoomInfo> ServerLobby::roomsVector;
std::vector<ServerInfo> ServerLobby::serversVector;
std::atomic_bool ServerLobby::is_refreshing{ false };
std::atomic_bool ServerLobby::has_refreshed{ false };
static size_t WriteMemoryCallback(char* contents, size_t size, size_t nmemb, void* userp) {
size_t realsize = size * nmemb;
auto buff = static_cast<std::string*>(userp);
buff->append(contents, realsize);
return realsize;
}
void ServerLobby::FillOnlineRooms() {
mainGame->roomListTable->clearRows();
auto& rooms = roomsVector;
std::wstring searchText(Utils::ToUpperNoAccents(mainGame->ebRoomName->getText()));
int searchRules = mainGame->cbFilterRule->getSelected();
int searchBanlist = mainGame->cbFilterBanlist->getSelected();
int bestOf = 0;
int team1 = 0;
int team2 = 0;
try {
bestOf = std::stoi(mainGame->ebOnlineBestOf->getText());
team1 = std::stoi(mainGame->ebOnlineTeam1->getText());
team2 = std::stoi(mainGame->ebOnlineTeam2->getText());
}
catch(...) {}
bool doFilter = searchText.size() || searchRules > 0 || searchBanlist > 0 || bestOf || team1 || team2 || mainGame->btnFilterRelayMode->isPressed();
const auto& normal_room = skin::ROOMLIST_NORMAL_ROOM_VAL;
const auto& custom_room = skin::ROOMLIST_CUSTOM_ROOM_VAL;
const auto& started_room = skin::ROOMLIST_STARTED_ROOM_VAL;
const bool show_password_checked = mainGame->chkShowPassword->isChecked();
const bool show_started_checked = mainGame->chkShowActiveRooms->isChecked();
for(auto& room : rooms) {
if((room.locked && !show_password_checked) || (room.started && !show_started_checked)) {
continue;
}
if(doFilter) {
if(searchText.size()) {
auto res = Utils::ToUpperNoAccents(room.description).find(searchText);
for(auto it = room.players.cbegin(); res == std::wstring::npos && it != room.players.cend(); it++)
res = Utils::ToUpperNoAccents(*it).find(searchText);
if(res == std::wstring::npos)
continue;
}
if(bestOf && room.info.best_of != bestOf)
continue;
if(team1 && room.info.team1 != team1)
continue;
if(team2 && room.info.team2 != team2)
continue;
if(searchRules > 0) {
if(searchRules != room.info.rule + 1)
continue;
}
/*add mutex for banlist access*/
if(searchBanlist > 0) {
if(room.info.lflist != gdeckManager->_lfList[searchBanlist - 1].hash) {
continue;
}
}
if(mainGame->btnFilterRelayMode->isPressed() && !(room.info.duel_flag_low & DUEL_RELAY))
continue;
}
epro::wstringview banlist = L"???";
for(auto& list : gdeckManager->_lfList) {
if(list.hash == room.info.lflist) {
banlist = list.listName;
break;
}
}
irr::gui::IGUITable* roomListTable = mainGame->roomListTable;
int index = roomListTable->getRowCount();
roomListTable->addRow(index);
roomListTable->setCellData(index, 0, room.locked ? (void*)1 : nullptr);
roomListTable->setCellData(index, 1, &room);
roomListTable->setCellText(index, 1, gDataManager->GetSysString(room.info.rule + 1900).data());
roomListTable->setCellText(index, 2, epro::format(L"[{}vs{}]{}{}", room.info.team1, room.info.team2,
(room.info.best_of > 1) ? epro::format(L" (best of {})", room.info.best_of) : L"",
(room.info.duel_flag_low & DUEL_RELAY) ? L" (Relay)" : L"").data());
int rule;
auto duel_flag = (((uint64_t)room.info.duel_flag_low) | ((uint64_t)room.info.duel_flag_high) << 32);
mainGame->GetMasterRule(duel_flag & ~(DUEL_RELAY | DUEL_TCG_SEGOC_NONPUBLIC | DUEL_PSEUDO_SHUFFLE), room.info.forbiddentypes, &rule);
if(rule == 6) {
if(duel_flag == DUEL_MODE_GOAT) {
roomListTable->setCellText(index, 3, L"GOAT");
} else if(duel_flag == DUEL_MODE_RUSH) {
roomListTable->setCellText(index, 3, L"Rush");
} else if(duel_flag == DUEL_MODE_SPEED) {
roomListTable->setCellText(index, 3, L"Speed");
} else
roomListTable->setCellText(index, 3, L"Custom");
} else
roomListTable->setCellText(index, 3, epro::format(L"{}MR {}",
(duel_flag & DUEL_TCG_SEGOC_NONPUBLIC) ? L"TCG " : L"",
(rule == 0) ? 3 : rule).data());
roomListTable->setCellText(index, 4, banlist.data());
std::wstring players;
for(const auto& player : room.players)
players.append(player).append(L", ");
if(players.size())
players.erase(players.size() - 2);
roomListTable->setCellText(index, 5, players.data());
roomListTable->setCellText(index, 6, room.description.data());
roomListTable->setCellText(index, 7, room.started ? gDataManager->GetSysString(1986).data() : gDataManager->GetSysString(1987).data());
static constexpr DeckSizes normal_sizes{ {40,60}, {0,15}, {0,15} };
irr::video::SColor color;
if(room.started)
color = started_room;
else if(rule == 5 && !room.info.no_check_deck_content && room.info.sizes == normal_sizes && !room.info.no_shuffle_deck && room.info.start_lp == 8000 && room.info.start_hand == 5 && room.info.draw_count == 1)
color = normal_room;
else
color = custom_room;
roomListTable->setCellColor(index, 0, color);
roomListTable->setCellColor(index, 1, color);
roomListTable->setCellColor(index, 2, color);
roomListTable->setCellColor(index, 3, color);
roomListTable->setCellColor(index, 4, color);
roomListTable->setCellColor(index, 5, color);
roomListTable->setCellColor(index, 6, color);
roomListTable->setCellColor(index, 7, color);
}
mainGame->roomListTable->setActiveColumn(mainGame->roomListTable->getActiveColumn(), true);
if(!roomsVector.empty() && mainGame->chkShowActiveRooms->isChecked()) {
mainGame->roomListTable->setActiveColumn(7, true);
mainGame->roomListTable->orderRows(-1, irr::gui::EGOM_DESCENDING);
}
GUIUtils::ChangeCursor(mainGame->device.get(), irr::gui::ECI_NORMAL);
mainGame->btnLanRefresh2->setEnabled(true);
mainGame->serverChoice->setEnabled(true);
mainGame->roomListTable->setVisible(true);
has_refreshed = false;
}
void ServerLobby::GetRoomsThread() {
Utils::SetThreadName("RoomlistFetch");
auto selected = mainGame->serverChoice->getSelected();
if (selected < 0) return;
const auto& serverInfo = serversVector[selected];
mainGame->btnLanRefresh2->setEnabled(false);
mainGame->serverChoice->setEnabled(false);
mainGame->roomListTable->setVisible(false);
std::string retrieved_data;
char curl_error_buffer[CURL_ERROR_SIZE];
auto curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, curl_error_buffer);
curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl_handle, CURLOPT_URL, epro::format("{}://{}:{}/api/getrooms", serverInfo.GetProtocolString(), serverInfo.roomaddress, serverInfo.roomlistport).data());
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 60L);
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 15L);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &retrieved_data);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, ygo::Utils::GetUserAgent().data());
curl_easy_setopt(curl_handle, CURLOPT_NOPROXY, "*");
if(gGameConfig->ssl_certificate_path.size() && Utils::FileExists(Utils::ToPathString(gGameConfig->ssl_certificate_path)))
curl_easy_setopt(curl_handle, CURLOPT_CAINFO, gGameConfig->ssl_certificate_path.data());
auto res = curl_easy_perform(curl_handle);
curl_easy_cleanup(curl_handle);
if(res != CURLE_OK) {
if(gGameConfig->logDownloadErrors) {
ErrorLog("Error updating the room list:");
ErrorLog("Curl error: ({}) {} ({})", res, curl_easy_strerror(res), curl_error_buffer);
}
//error
mainGame->PopupMessage(gDataManager->GetSysString(2037));
mainGame->btnLanRefresh2->setEnabled(true);
mainGame->serverChoice->setEnabled(true);
mainGame->roomListTable->setVisible(true);
is_refreshing = false;
has_refreshed = true;
return;
}
roomsVector.clear();
try {
auto j = nlohmann::json::parse(retrieved_data);
if(j.size()) {
#define GET(field, type) obj[field].get<type>()
for(auto& obj : j["rooms"]) {
RoomInfo room;
room.id = GET("roomid", int);
room.name = BufferIO::DecodeUTF8(obj["roomname"].get_ref<std::string&>());
room.description = BufferIO::DecodeUTF8(obj["roomnotes"].get_ref<std::string&>());
room.locked = GET("needpass", bool);
room.started = obj["istart"].get_ref<std::string&>() == "start";
room.info.mode = GET("roommode", int);
room.info.team1 = GET("team1", int);
room.info.team2 = GET("team2", int);
room.info.best_of = GET("best_of", int);
const auto flag = GET("duel_flag", uint64_t);
room.info.duel_flag_low = flag & 0xffffffff;
room.info.duel_flag_high = (flag >> 32) & 0xffffffff;
room.info.forbiddentypes = GET("forbidden_types", int);
room.info.extra_rules = GET("extra_rules", int);
room.info.start_lp = GET("start_lp", int);
room.info.start_hand = GET("start_hand", int);
room.info.draw_count = GET("draw_count", int);
room.info.time_limit = GET("time_limit", int);
room.info.rule = GET("rule", int);
room.info.no_check_deck_content = GET("no_check", bool);
room.info.no_shuffle_deck = GET("no_shuffle", bool) || (flag & DUEL_PSEUDO_SHUFFLE);
room.info.lflist = GET("banlist_hash", int);
room.info.sizes.main.min = GET("main_min", uint16_t);
room.info.sizes.main.max = GET("main_max", uint16_t);
room.info.sizes.extra.min = GET("extra_min", uint16_t);
room.info.sizes.extra.max = GET("extra_max", uint16_t);
room.info.sizes.side.min = GET("side_min", uint16_t);
room.info.sizes.side.max = GET("side_max", uint16_t);
#undef GET
for(auto& obj2 : obj["users"])
room.players.push_back(BufferIO::DecodeUTF8(obj2["name"].get_ref<const std::string&>()));
roomsVector.push_back(std::move(room));
}
}
} catch(const std::exception& e) {
ErrorLog("Exception occurred parsing server rooms: {}", e.what());
}
has_refreshed = true;
is_refreshing = false;
}
bool ServerLobby::IsKnownHost(epro::Host host) {
return std::find_if(serversVector.begin(), serversVector.end(), [&](const ServerInfo& konwn_host) {
return konwn_host.Resolved() == host;
}) != serversVector.end();
}
void ServerLobby::RefreshRooms() {
if(is_refreshing)
return;
is_refreshing = true;
mainGame->roomListTable->clearRows();
GUIUtils::ChangeCursor(mainGame->device.get(), irr::gui::ECI_WAIT);
epro::thread(GetRoomsThread).detach();
}
bool ServerLobby::HasRefreshedRooms() {
return has_refreshed;
}
void ServerLobby::JoinServer(bool host) {
mainGame->ebNickName->setText(mainGame->ebNickNameOnline->getText());
auto selected = mainGame->serverChoice->getSelected();
if (selected < 0) return;
const auto& serverinfo = serversVector[selected].Resolved();
if(serverinfo.address.family == epro::Address::UNK)
return;
if(host) {
if(!DuelClient::StartClient(serverinfo.address, serverinfo.port))
return;
} else {
//client
auto room = static_cast<RoomInfo*>(mainGame->roomListTable->getCellData(mainGame->roomListTable->getSelected(), 1));
if(!room)
return;
if(room->locked) {
if(!mainGame->wRoomPassword->isVisible()) {
mainGame->wRoomPassword->setVisible(true);
return;
}
auto text = mainGame->ebRPName->getText();
if(*text == L'\0')
return;
mainGame->wRoomPassword->setVisible(false);
mainGame->dInfo.secret.pass = text;
} else
mainGame->dInfo.secret.pass.clear();
if(!DuelClient::StartClient(serverinfo.address, serverinfo.port, room->id, false))
return;
}
}
const epro::Host& ServerInfo::Resolved() const {
if(!resolved) {
try {
resolved_address = epro::Host::resolve(address, duelport);
resolved = true;
} catch(const std::exception& e) {
ErrorLog("Exception occurred: {}", e.what());
}
}
return resolved_address;
}
}