Merge branch 'master' into jgrpp
# Conflicts: # .github/workflows/ci-build.yml # .github/workflows/release-linux.yml # .github/workflows/release-macos.yml # .github/workflows/release-source.yml # .github/workflows/release.yml # CMakeLists.txt # COMPILING.md # src/ai/ai_core.cpp # src/ai/ai_gui.cpp # src/bridge_gui.cpp # src/company_gui.cpp # src/console_cmds.cpp # src/core/CMakeLists.txt # src/core/smallmap_type.hpp # src/disaster_vehicle.h # src/effectvehicle_base.h # src/fontcache.cpp # src/game/game_core.cpp # src/game/game_gui.cpp # src/gamelog.cpp # src/gamelog_internal.h # src/group_gui.cpp # src/linkgraph/linkgraph.h # src/misc.cpp # src/network/core/config.h # src/network/core/udp.cpp # src/network/network_chat_gui.cpp # src/network/network_content_gui.cpp # src/network/network_gui.cpp # src/newgrf.cpp # src/newgrf_gui.cpp # src/newgrf_profiling.cpp # src/newgrf_profiling.h # src/object_gui.cpp # src/openttd.cpp # src/openttd.h # src/order_gui.cpp # src/os/windows/font_win32.cpp # src/rail_gui.cpp # src/road.cpp # src/road_gui.cpp # src/saveload/afterload.cpp # src/saveload/saveload.h # src/script/api/script_controller.cpp # src/script/api/script_roadtypelist.cpp # src/script/script_config.cpp # src/script/script_config.hpp # src/script/script_instance.cpp # src/script/script_scanner.cpp # src/script/squirrel.cpp # src/script/squirrel_helper.hpp # src/settings_gui.cpp # src/settings_internal.h # src/settings_type.h # src/table/settings/network_private_settings.ini # src/timetable_gui.cpp # src/vehicle.cpp # src/vehicle_base.h # src/window_gui.h
This commit is contained in:
@@ -28,6 +28,8 @@ add_files(
|
||||
network_server.h
|
||||
network_stun.cpp
|
||||
network_stun.h
|
||||
network_survey.cpp
|
||||
network_survey.h
|
||||
network_turn.cpp
|
||||
network_turn.h
|
||||
network_type.h
|
||||
|
||||
@@ -282,7 +282,7 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList *
|
||||
* of course totally unneeded ;) */
|
||||
if (sockets != nullptr) {
|
||||
NetworkAddress address(runp->ai_addr, (int)runp->ai_addrlen);
|
||||
if (sockets->Contains(address)) continue;
|
||||
if (std::any_of(sockets->begin(), sockets->end(), [&address](const auto &p) { return p.second == address; })) continue;
|
||||
}
|
||||
sock = func(runp);
|
||||
if (sock == INVALID_SOCKET) continue;
|
||||
@@ -307,7 +307,7 @@ SOCKET NetworkAddress::Resolve(int family, int socktype, int flags, SocketList *
|
||||
}
|
||||
|
||||
NetworkAddress addr(runp->ai_addr, (int)runp->ai_addrlen);
|
||||
(*sockets)[addr] = sock;
|
||||
(*sockets)[sock] = addr;
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo (ai);
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
#include "config.h"
|
||||
#include "../../company_type.h"
|
||||
#include "../../string_func.h"
|
||||
#include "../../core/smallmap_type.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class NetworkAddress;
|
||||
typedef std::vector<NetworkAddress> NetworkAddressList; ///< Type for a list of addresses.
|
||||
typedef SmallMap<NetworkAddress, SOCKET> SocketList; ///< Type for a mapping between address and socket.
|
||||
using SocketList = std::map<SOCKET, NetworkAddress>; ///< Type for a mapping between address and socket.
|
||||
|
||||
/**
|
||||
* Wrapper for (un)resolved network addresses; there's no reason to transform
|
||||
|
||||
@@ -67,3 +67,13 @@ const char *NetworkContentMirrorUriString()
|
||||
{
|
||||
return GetEnv("OTTD_CONTENT_MIRROR_URI", "https://binaries.openttd.org/bananas");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI string for the survey from the environment variable OTTD_SURVEY_URI,
|
||||
* or when it has not been set a hard coded URI of the production server.
|
||||
* @return The survey's URI string.
|
||||
*/
|
||||
const char *NetworkSurveyUriString()
|
||||
{
|
||||
return GetEnv("OTTD_SURVEY_URI", "https://survey-participate.openttd.org/");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const char *NetworkCoordinatorConnectionString();
|
||||
const char *NetworkStunConnectionString();
|
||||
const char *NetworkContentServerConnectionString();
|
||||
const char *NetworkContentMirrorUriString();
|
||||
const char *NetworkSurveyUriString();
|
||||
|
||||
static const uint16 NETWORK_COORDINATOR_SERVER_PORT = 3976; ///< The default port of the Game Coordinator server (TCP)
|
||||
static const uint16 NETWORK_STUN_SERVER_PORT = 3975; ///< The default port of the STUN server (TCP)
|
||||
@@ -27,6 +28,8 @@ static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The d
|
||||
|
||||
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
|
||||
static const uint16 UDP_MTU_SHORT = 1400; ///< Number of bytes we can pack in a single UDP packet (conservative)
|
||||
|
||||
static const std::string NETWORK_SURVEY_DETAILS_LINK = "https://survey.openttd.org/participate"; ///< Link with more details & privacy statement of the survey.
|
||||
/*
|
||||
* Technically a TCP packet could become 64kiB, however the high bit is kept so it becomes possible in the future
|
||||
* to go to (significantly) larger packets if needed. This would entail a strategy such as employed for UTF-8.
|
||||
@@ -47,6 +50,7 @@ static const uint16 COMPAT_MTU = 1460; ///< Numbe
|
||||
static const byte NETWORK_GAME_ADMIN_VERSION = 3; ///< What version of the admin network do we use?
|
||||
static const byte NETWORK_GAME_INFO_VERSION = 6; ///< What version of game-info do we use?
|
||||
static const byte NETWORK_COORDINATOR_VERSION = 6; ///< What version of game-coordinator-protocol do we use?
|
||||
static const byte NETWORK_SURVEY_VERSION = 1; ///< What version of the survey do we use?
|
||||
|
||||
static const uint NETWORK_NAME_LENGTH = 80; ///< The maximum length of the server name and map name, in bytes including '\0'
|
||||
static const uint NETWORK_COMPANY_NAME_LENGTH = 128; ///< The maximum length of the company name, in bytes including '\0'
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
NetworkSocketHandler() { this->has_quit = false; }
|
||||
|
||||
/** Close the socket when destructing the socket handler */
|
||||
virtual ~NetworkSocketHandler() {}
|
||||
virtual ~NetworkSocketHandler() = default;
|
||||
|
||||
/**
|
||||
* Mark the connection as closed.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../core/alloc_func.hpp"
|
||||
#include "address.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
constexpr int HTTP_429_TOO_MANY_REQUESTS = 429;
|
||||
|
||||
/** Callback for when the HTTP handler has something to tell us. */
|
||||
struct HTTPCallback {
|
||||
/**
|
||||
@@ -40,7 +42,7 @@ struct HTTPCallback {
|
||||
virtual bool IsCancelled() const = 0;
|
||||
|
||||
/** Silentium */
|
||||
virtual ~HTTPCallback() {}
|
||||
virtual ~HTTPCallback() = default;
|
||||
};
|
||||
|
||||
/** Base socket handler for HTTP traffic. */
|
||||
|
||||
@@ -117,6 +117,7 @@ void HttpThread()
|
||||
|
||||
/* Reset to default settings. */
|
||||
curl_easy_reset(curl);
|
||||
curl_slist *headers = nullptr;
|
||||
|
||||
if (_debug_net_level >= 5) {
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
@@ -147,8 +148,16 @@ void HttpThread()
|
||||
|
||||
/* Prepare POST body and URI. */
|
||||
if (!request->data.empty()) {
|
||||
/* When the payload starts with a '{', it is a JSON payload. */
|
||||
if (StrStartsWith(request->data, "{")) {
|
||||
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||
} else {
|
||||
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str());
|
||||
|
||||
@@ -175,11 +184,17 @@ void HttpThread()
|
||||
/* Perform the request. */
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
Debug(net, 1, "HTTP request succeeded");
|
||||
request->callback->OnReceiveData(nullptr, 0);
|
||||
} else {
|
||||
Debug(net, (request->callback->IsCancelled() || _http_thread_exit) ? 1 : 0, "HTTP request failed: {}", curl_easy_strerror(res));
|
||||
long status_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
|
||||
|
||||
/* No need to be verbose about rate limiting. */
|
||||
Debug(net, (request->callback->IsCancelled() || _http_thread_exit || status_code == HTTP_429_TOO_MANY_REQUESTS) ? 1 : 0, "HTTP request failed: status_code: {}, error: {}", status_code, curl_easy_strerror(res));
|
||||
request->callback->OnFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,8 @@ void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
|
||||
|
||||
/* If there is any error, we simply abort the request. */
|
||||
if (status_code >= 400) {
|
||||
Debug(net, 0, "HTTP request failed: status-code {}", status_code);
|
||||
/* No need to be verbose about rate limiting. */
|
||||
Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0, "HTTP request failed: status-code {}", status_code);
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
@@ -242,7 +243,9 @@ void NetworkHTTPRequest::Connect()
|
||||
if (data.empty()) {
|
||||
WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast<DWORD_PTR>(this));
|
||||
} else {
|
||||
WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast<char *>(data.c_str()), static_cast<DWORD>(data.size()), static_cast<DWORD>(data.size()), reinterpret_cast<DWORD_PTR>(this));
|
||||
/* When the payload starts with a '{', it is a JSON payload. */
|
||||
LPCWSTR content_type = StrStartsWith(data, "{") ? L"Content-Type: application/json\r\n" : L"Content-Type: application/x-www-form-urlencoded\r\n";
|
||||
WinHttpSendRequest(this->request, content_type, -1, const_cast<char *>(data.c_str()), static_cast<DWORD>(data.size()), static_cast<DWORD>(data.size()), reinterpret_cast<DWORD_PTR>(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -565,7 +565,7 @@ public:
|
||||
* @param status The reason the connection got closed.
|
||||
*/
|
||||
virtual NetworkRecvStatus CloseConnection(NetworkRecvStatus status) = 0;
|
||||
virtual ~NetworkGameSocketHandler() {}
|
||||
virtual ~NetworkGameSocketHandler() = default;
|
||||
|
||||
/**
|
||||
* Sets the client info for this socket handler.
|
||||
|
||||
@@ -114,7 +114,7 @@ public:
|
||||
|
||||
/* take care of listener port */
|
||||
for (auto &s : sockets) {
|
||||
FD_SET(s.second, &read_fd);
|
||||
FD_SET(s.first, &read_fd);
|
||||
}
|
||||
|
||||
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
|
||||
/* accept clients.. */
|
||||
for (auto &s : sockets) {
|
||||
if (FD_ISSET(s.second, &read_fd)) AcceptClient(s.second);
|
||||
if (FD_ISSET(s.first, &read_fd)) AcceptClient(s.first);
|
||||
}
|
||||
|
||||
/* read stuff from clients */
|
||||
@@ -164,7 +164,7 @@ public:
|
||||
static void CloseListeners()
|
||||
{
|
||||
for (auto &s : sockets) {
|
||||
closesocket(s.second);
|
||||
closesocket(s.first);
|
||||
}
|
||||
sockets.clear();
|
||||
DEBUG(net, 5, "[%s] Closed listeners", Tsocket::GetName());
|
||||
|
||||
@@ -61,7 +61,7 @@ bool NetworkUDPSocketHandler::Listen()
|
||||
void NetworkUDPSocketHandler::CloseSocket()
|
||||
{
|
||||
for (auto &s : this->sockets) {
|
||||
closesocket(s.second);
|
||||
closesocket(s.first);
|
||||
}
|
||||
this->sockets.clear();
|
||||
}
|
||||
@@ -113,20 +113,20 @@ void NetworkUDPSocketHandler::SendPacket(Packet *p, NetworkAddress *recv, bool a
|
||||
NetworkAddress send(*recv);
|
||||
|
||||
/* Not the same type */
|
||||
if (!send.IsFamily(s.first.GetAddress()->ss_family)) continue;
|
||||
if (!send.IsFamily(s.second.GetAddress()->ss_family)) continue;
|
||||
|
||||
p->PrepareToSend();
|
||||
|
||||
if (broadcast) {
|
||||
/* Enable broadcast */
|
||||
unsigned long val = 1;
|
||||
if (setsockopt(s.second, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) {
|
||||
if (setsockopt(s.first, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof(val)) < 0) {
|
||||
DEBUG(net, 1, "Setting broadcast mode failed: %s", NetworkError::GetLast().AsString());
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the buffer */
|
||||
ssize_t res = p->TransferOut<int>(sendto, s.second, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
|
||||
ssize_t res = p->TransferOut<int>(sendto, s.first, 0, (const struct sockaddr *)send.GetAddress(), send.GetAddressLength());
|
||||
DEBUG(net, 7, "sendto(%s)", NetworkAddressDumper().GetAddressAsString(&send));
|
||||
|
||||
/* Check for any errors, but ignore it otherwise */
|
||||
@@ -151,8 +151,8 @@ void NetworkUDPSocketHandler::ReceivePackets()
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
|
||||
/* Try to receive anything */
|
||||
SetNonBlocking(s.second); // Some OSes seem to lose the non-blocking status of the socket
|
||||
ssize_t nbytes = p.TransferIn<int>(recvfrom, s.second, 0, (struct sockaddr *)&client_addr, &client_len);
|
||||
SetNonBlocking(s.first); // Some OSes seem to lose the non-blocking status of the socket
|
||||
ssize_t nbytes = p.TransferIn<int>(recvfrom, s.first, 0, (struct sockaddr *)&client_addr, &client_len);
|
||||
|
||||
/* Did we get the bytes for the base header of the packet? */
|
||||
if (nbytes <= 0) break; // No data, i.e. no packet
|
||||
|
||||
@@ -102,6 +102,8 @@ static_assert((int)NETWORK_COMPANY_NAME_LENGTH == MAX_LENGTH_COMPANY_NAME_CHARS
|
||||
/** The amount of clients connected */
|
||||
byte _network_clients_connected = 0;
|
||||
|
||||
extern std::string GenerateUid(std::string_view subject);
|
||||
|
||||
/**
|
||||
* Return whether there is any client connected or trying to connect at all.
|
||||
* @return whether we have any client activity
|
||||
@@ -1298,24 +1300,7 @@ void NetworkGameLoop()
|
||||
|
||||
static void NetworkGenerateServerId()
|
||||
{
|
||||
Md5 checksum;
|
||||
uint8 digest[16];
|
||||
char hex_output[16 * 2 + 1];
|
||||
char coding_string[NETWORK_NAME_LENGTH];
|
||||
int di;
|
||||
|
||||
seprintf(coding_string, lastof(coding_string), "%d%s", (uint)Random(), "OpenTTD Server ID");
|
||||
|
||||
/* Generate the MD5 hash */
|
||||
checksum.Append((const uint8*)coding_string, strlen(coding_string));
|
||||
checksum.Finish(digest);
|
||||
|
||||
for (di = 0; di < 16; ++di) {
|
||||
seprintf(hex_output + di * 2, lastof(hex_output), "%02x", digest[di]);
|
||||
}
|
||||
|
||||
/* _settings_client.network.network_id is our id */
|
||||
_settings_client.network.network_id = hex_output;
|
||||
_settings_client.network.network_id = GenerateUid("OpenTTD Server ID");
|
||||
}
|
||||
|
||||
std::string BytesToHexString(const byte *data, uint length)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "network.h"
|
||||
#include "network_client.h"
|
||||
#include "network_base.h"
|
||||
#include "../3rdparty/fmt/format.h"
|
||||
|
||||
#include "../widgets/network_chat_widget.h"
|
||||
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
#include <stdarg.h> /* va_list */
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
@@ -326,18 +328,16 @@ struct NetworkChatWindow : public Window {
|
||||
* Find the next item of the list of things that can be auto-completed.
|
||||
* @param item The current indexed item to return. This function can, and most
|
||||
* likely will, alter item, to skip empty items in the arrays.
|
||||
* @return Returns the char that matched to the index.
|
||||
* @return Returns the view that matched to the index.
|
||||
*/
|
||||
const char *ChatTabCompletionNextItem(uint *item)
|
||||
std::optional<std::string> ChatTabCompletionNextItem(uint *item)
|
||||
{
|
||||
static char chat_tab_temp_buffer[64];
|
||||
|
||||
/* First, try clients */
|
||||
if (*item < MAX_CLIENT_SLOTS) {
|
||||
/* Skip inactive clients */
|
||||
for (NetworkClientInfo *ci : NetworkClientInfo::Iterate(*item)) {
|
||||
*item = ci->index;
|
||||
return ci->client_name.c_str();
|
||||
return ci->client_name;
|
||||
}
|
||||
*item = MAX_CLIENT_SLOTS;
|
||||
}
|
||||
@@ -349,12 +349,11 @@ struct NetworkChatWindow : public Window {
|
||||
for (const Town *t : Town::Iterate(*item - MAX_CLIENT_SLOTS)) {
|
||||
/* Get the town-name via the string-system */
|
||||
SetDParam(0, t->index);
|
||||
GetString(chat_tab_temp_buffer, STR_TOWN_NAME, lastof(chat_tab_temp_buffer));
|
||||
return &chat_tab_temp_buffer[0];
|
||||
return GetString(STR_TOWN_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,13 +361,14 @@ struct NetworkChatWindow : public Window {
|
||||
* the word right from that as to complete. It also writes a \0 at the
|
||||
* position of the space (if any). If nothing found, buf is returned.
|
||||
*/
|
||||
static char *ChatTabCompletionFindText(char *buf)
|
||||
static std::string_view ChatTabCompletionFindText(std::string_view &buf)
|
||||
{
|
||||
char *p = strrchr(buf, ' ');
|
||||
if (p == nullptr) return buf;
|
||||
auto it = buf.find_last_of(' ');
|
||||
if (it == std::string_view::npos) return buf;
|
||||
|
||||
*p = '\0';
|
||||
return p + 1;
|
||||
std::string_view res = buf.substr(it + 1);
|
||||
buf.remove_suffix(res.size() + 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,46 +376,44 @@ struct NetworkChatWindow : public Window {
|
||||
*/
|
||||
void ChatTabCompletion()
|
||||
{
|
||||
static char _chat_tab_completion_buf[NETWORK_CHAT_LENGTH];
|
||||
assert(this->message_editbox.text.max_bytes == lengthof(_chat_tab_completion_buf));
|
||||
static std::string _chat_tab_completion_buf;
|
||||
|
||||
Textbuf *tb = &this->message_editbox.text;
|
||||
size_t len, tb_len;
|
||||
uint item;
|
||||
char *tb_buf, *pre_buf;
|
||||
const char *cur_name;
|
||||
uint item = 0;
|
||||
bool second_scan = false;
|
||||
|
||||
item = 0;
|
||||
/* Create views, so we do not need to copy the data for now. */
|
||||
std::string_view pre_buf = _chat_tab_completion_active ? std::string_view(_chat_tab_completion_buf) : std::string_view(tb->buf);
|
||||
std::string_view tb_buf = ChatTabCompletionFindText(pre_buf);
|
||||
|
||||
/* Copy the buffer so we can modify it without damaging the real data */
|
||||
pre_buf = (_chat_tab_completion_active) ? stredup(_chat_tab_completion_buf) : stredup(tb->buf);
|
||||
/*
|
||||
* Comparing pointers of the data, as both "Hi:<tab>" and "Hi: Hi:<tab>" will result in
|
||||
* tb_buf and pre_buf being "Hi:", which would be equal in content but not in context.
|
||||
*/
|
||||
bool begin_of_line = tb_buf.data() == pre_buf.data();
|
||||
|
||||
tb_buf = ChatTabCompletionFindText(pre_buf);
|
||||
tb_len = strlen(tb_buf);
|
||||
|
||||
while ((cur_name = ChatTabCompletionNextItem(&item)) != nullptr) {
|
||||
std::optional<std::string> cur_item;
|
||||
while ((cur_item = ChatTabCompletionNextItem(&item)).has_value()) {
|
||||
std::string_view cur_name = cur_item.value();
|
||||
item++;
|
||||
|
||||
if (_chat_tab_completion_active) {
|
||||
/* We are pressing TAB again on the same name, is there another name
|
||||
* that starts with this? */
|
||||
if (!second_scan) {
|
||||
size_t offset;
|
||||
size_t length;
|
||||
std::string_view view;
|
||||
|
||||
/* If we are completing at the begin of the line, skip the ': ' we added */
|
||||
if (tb_buf == pre_buf) {
|
||||
offset = 0;
|
||||
length = (tb->bytes - 1) - 2;
|
||||
if (begin_of_line) {
|
||||
view = std::string_view(tb->buf, (tb->bytes - 1) - 2);
|
||||
} else {
|
||||
/* Else, find the place we are completing at */
|
||||
offset = strlen(pre_buf) + 1;
|
||||
length = (tb->bytes - 1) - offset;
|
||||
size_t offset = pre_buf.size() + 1;
|
||||
view = std::string_view(tb->buf + offset, (tb->bytes - 1) - offset);
|
||||
}
|
||||
|
||||
/* Compare if we have a match */
|
||||
if (strlen(cur_name) == length && strncmp(cur_name, tb->buf + offset, length) == 0) second_scan = true;
|
||||
if (cur_name == view) second_scan = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -423,21 +421,19 @@ struct NetworkChatWindow : public Window {
|
||||
/* Now any match we make on _chat_tab_completion_buf after this, is perfect */
|
||||
}
|
||||
|
||||
len = strlen(cur_name);
|
||||
if (tb_len < len && StrStartsWith(cur_name, tb_buf)) {
|
||||
if (tb_buf.size() < cur_name.size() && StrStartsWith(cur_name, tb_buf)) {
|
||||
/* Save the data it was before completion */
|
||||
if (!second_scan) seprintf(_chat_tab_completion_buf, lastof(_chat_tab_completion_buf), "%s", tb->buf);
|
||||
if (!second_scan) _chat_tab_completion_buf = tb->buf;
|
||||
_chat_tab_completion_active = true;
|
||||
|
||||
/* Change to the found name. Add ': ' if we are at the start of the line (pretty) */
|
||||
if (pre_buf == tb_buf) {
|
||||
this->message_editbox.text.Print("%s: ", cur_name);
|
||||
if (begin_of_line) {
|
||||
this->message_editbox.text.Assign(fmt::format("{}: ", cur_name));
|
||||
} else {
|
||||
this->message_editbox.text.Print("%s %s", pre_buf, cur_name);
|
||||
this->message_editbox.text.Assign(fmt::format("{} {}", pre_buf, cur_name));
|
||||
}
|
||||
|
||||
this->SetDirty();
|
||||
free(pre_buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -449,7 +445,6 @@ struct NetworkChatWindow : public Window {
|
||||
|
||||
this->SetDirty();
|
||||
}
|
||||
free(pre_buf);
|
||||
}
|
||||
|
||||
Point OnInitialPosition(int16 sm_width, int16 sm_height, int window_number) override
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "../thread.h"
|
||||
#include "../crashlog.h"
|
||||
#include "../core/checksum_func.hpp"
|
||||
#include "../core/alloc_func.hpp"
|
||||
#include "../fileio_func.h"
|
||||
#include "../debug_settings.h"
|
||||
#include "../3rdparty/monocypher/monocypher.h"
|
||||
|
||||
@@ -57,7 +57,7 @@ struct ContentCallback {
|
||||
virtual void OnDownloadComplete(ContentID cid) {}
|
||||
|
||||
/** Silentium */
|
||||
virtual ~ContentCallback() {}
|
||||
virtual ~ContentCallback() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -791,7 +791,7 @@ public:
|
||||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (this->selected == nullptr || this->selected->state != ContentInfo::ALREADY_HERE) return;
|
||||
|
||||
ShowContentTextfileWindow((TextfileType)(widget - WID_NCL_TEXTFILE), this->selected);
|
||||
@@ -800,11 +800,11 @@ public:
|
||||
|
||||
switch (widget) {
|
||||
case WID_NCL_MATRIX: {
|
||||
uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NCL_MATRIX);
|
||||
if (id_v >= this->content.size()) return; // click out of bounds
|
||||
auto it = this->vscroll->GetScrolledItemFromWidget(this->content, pt.y, this, WID_NCL_MATRIX);
|
||||
if (it == this->content.end()) return; // click out of bounds
|
||||
|
||||
this->selected = this->content[id_v];
|
||||
this->list_pos = id_v;
|
||||
this->selected = *it;
|
||||
this->list_pos = it - this->content.begin();
|
||||
|
||||
const NWidgetBase *checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX);
|
||||
if (click_count > 1 || IsInsideBS(pt.x, checkbox->pos_x, checkbox->current_x)) {
|
||||
@@ -998,7 +998,7 @@ public:
|
||||
this->SetWidgetDisabledState(WID_NCL_SELECT_ALL, !show_select_all);
|
||||
this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE, !show_select_upgrade);
|
||||
this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == nullptr || this->selected->url.empty());
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_NCL_TEXTFILE + tft, this->selected == nullptr || this->selected->state != ContentInfo::ALREADY_HERE || this->selected->GetTextfile(tft) == nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "network_content.h"
|
||||
#include "network_server.h"
|
||||
#include "network_coordinator.h"
|
||||
#include "network_survey.h"
|
||||
#include "../gui.h"
|
||||
#include "network_udp.h"
|
||||
#include "../window_func.h"
|
||||
@@ -38,6 +39,7 @@
|
||||
#include "../zoom_func.h"
|
||||
#include "../sprite.h"
|
||||
#include "../settings_internal.h"
|
||||
#include "../textfile_gui.h"
|
||||
|
||||
#include "../widgets/network_widget.h"
|
||||
|
||||
@@ -764,9 +766,9 @@ public:
|
||||
break;
|
||||
|
||||
case WID_NG_MATRIX: { // Show available network games
|
||||
uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NG_MATRIX);
|
||||
this->server = (id_v < this->servers.size()) ? this->servers[id_v] : nullptr;
|
||||
this->list_pos = (server == nullptr) ? SLP_INVALID : id_v;
|
||||
auto it = this->vscroll->GetScrolledItemFromWidget(this->servers, pt.y, this, WID_NG_MATRIX);
|
||||
this->server = (it != this->servers.end()) ? *it : nullptr;
|
||||
this->list_pos = (server == nullptr) ? SLP_INVALID : it - this->servers.begin();
|
||||
this->SetDirty();
|
||||
|
||||
/* FIXME the disabling should go into some InvalidateData, which is called instead of the SetDirty */
|
||||
@@ -1462,7 +1464,7 @@ public:
|
||||
this->height = d.height + WidgetDimensions::scaled.framerect.Vertical();
|
||||
this->width = d.width + WidgetDimensions::scaled.framerect.Horizontal();
|
||||
}
|
||||
virtual ~ButtonCommon() {}
|
||||
virtual ~ButtonCommon() = default;
|
||||
|
||||
/**
|
||||
* OnClick handler for when the button is pressed.
|
||||
@@ -2448,7 +2450,8 @@ struct NetworkAskRelayWindow : public Window {
|
||||
{
|
||||
if (widget == WID_NAR_TEXT) {
|
||||
*size = GetStringBoundingBox(STR_NETWORK_ASK_RELAY_TEXT);
|
||||
size->height = GetStringHeight(STR_NETWORK_ASK_RELAY_TEXT, size->width - WidgetDimensions::scaled.frametext.Horizontal()) + WidgetDimensions::scaled.frametext.Vertical();
|
||||
size->width += WidgetDimensions::scaled.frametext.Horizontal();
|
||||
size->height += WidgetDimensions::scaled.frametext.Vertical();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2534,3 +2537,119 @@ void ShowNetworkAskRelay(const std::string &server_connection_string, const std:
|
||||
Window *parent = GetMainWindow();
|
||||
new NetworkAskRelayWindow(&_network_ask_relay_desc, parent, server_connection_string, relay_connection_string, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Window used for asking if the user wants to participate in the automated survey.
|
||||
*/
|
||||
struct NetworkAskSurveyWindow : public Window {
|
||||
NetworkAskSurveyWindow(WindowDesc *desc, Window *parent) :
|
||||
Window(desc)
|
||||
{
|
||||
this->parent = parent;
|
||||
this->InitNested(0);
|
||||
}
|
||||
|
||||
void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override
|
||||
{
|
||||
if (widget == WID_NAS_TEXT) {
|
||||
*size = GetStringBoundingBox(STR_NETWORK_ASK_SURVEY_TEXT);
|
||||
size->width += WidgetDimensions::scaled.frametext.Horizontal();
|
||||
size->height += WidgetDimensions::scaled.frametext.Vertical();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawWidget(const Rect &r, int widget) const override
|
||||
{
|
||||
if (widget == WID_NAS_TEXT) {
|
||||
DrawStringMultiLine(r.Shrink(WidgetDimensions::scaled.frametext), STR_NETWORK_ASK_SURVEY_TEXT, TC_BLACK, SA_CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
void FindWindowPlacementAndResize(int def_width, int def_height) override
|
||||
{
|
||||
/* Position query window over the calling window, ensuring it's within screen bounds. */
|
||||
this->left = Clamp(parent->left + (parent->width / 2) - (this->width / 2), 0, _screen.width - this->width);
|
||||
this->top = Clamp(parent->top + (parent->height / 2) - (this->height / 2), 0, _screen.height - this->height);
|
||||
this->SetDirty();
|
||||
}
|
||||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
switch (widget) {
|
||||
case WID_NAS_PREVIEW:
|
||||
ShowSurveyResultTextfileWindow();
|
||||
break;
|
||||
|
||||
case WID_NAS_LINK:
|
||||
OpenBrowser(NETWORK_SURVEY_DETAILS_LINK.c_str());
|
||||
break;
|
||||
|
||||
case WID_NAS_NO:
|
||||
_settings_client.network.participate_survey = PS_NO;
|
||||
this->Close();
|
||||
break;
|
||||
|
||||
case WID_NAS_YES:
|
||||
_settings_client.network.participate_survey = PS_YES;
|
||||
this->Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const NWidgetPart _nested_network_ask_survey_widgets[] = {
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_CLOSEBOX, COLOUR_GREY),
|
||||
NWidget(WWT_CAPTION, COLOUR_GREY, WID_NAS_CAPTION), SetDataTip(STR_NETWORK_ASK_SURVEY_CAPTION, STR_NULL),
|
||||
EndContainer(),
|
||||
NWidget(WWT_PANEL, COLOUR_GREY), SetPIP(0, 4, 8),
|
||||
NWidget(WWT_TEXT, COLOUR_GREY, WID_NAS_TEXT), SetAlignment(SA_HOR_CENTER), SetFill(1, 1),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NAS_PREVIEW), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_PREVIEW, STR_NULL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NAS_LINK), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_LINK, STR_NULL),
|
||||
EndContainer(),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 15, 10),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NAS_NO), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_NO, STR_NULL),
|
||||
NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_NAS_YES), SetMinimalSize(71, 12), SetFill(1, 1), SetDataTip(STR_NETWORK_ASK_SURVEY_YES, STR_NULL),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
};
|
||||
|
||||
static WindowDesc _network_ask_survey_desc(
|
||||
WDP_CENTER, nullptr, 0, 0,
|
||||
WC_NETWORK_ASK_SURVEY, WC_NONE,
|
||||
WDF_MODAL,
|
||||
_nested_network_ask_survey_widgets, lengthof(_nested_network_ask_survey_widgets)
|
||||
);
|
||||
|
||||
/**
|
||||
* Show a modal confirmation window with "no" / "preview" / "yes" buttons.
|
||||
*/
|
||||
void ShowNetworkAskSurvey()
|
||||
{
|
||||
/* If we can't send a survey, don't ask the question. */
|
||||
if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) return;
|
||||
|
||||
CloseWindowByClass(WC_NETWORK_ASK_SURVEY);
|
||||
|
||||
Window *parent = GetMainWindow();
|
||||
new NetworkAskSurveyWindow(&_network_ask_survey_desc, parent);
|
||||
}
|
||||
|
||||
/** Window for displaying the textfile of a survey result. */
|
||||
struct SurveyResultTextfileWindow : public TextfileWindow {
|
||||
const GRFConfig *grf_config; ///< View the textfile of this GRFConfig.
|
||||
|
||||
SurveyResultTextfileWindow(TextfileType file_type) : TextfileWindow(file_type)
|
||||
{
|
||||
auto result = _survey.CreatePayload(NetworkSurveyHandler::Reason::PREVIEW, true);
|
||||
this->LoadText(result);
|
||||
this->InvalidateData();
|
||||
}
|
||||
};
|
||||
|
||||
void ShowSurveyResultTextfileWindow()
|
||||
{
|
||||
CloseWindowById(WC_TEXTFILE, TFT_SURVEY_RESULT);
|
||||
new SurveyResultTextfileWindow(TFT_SURVEY_RESULT);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ void ShowNetworkGameWindow();
|
||||
void ShowClientList();
|
||||
void ShowNetworkCompanyPasswordWindow(Window *parent);
|
||||
void ShowNetworkAskRelay(const std::string &server_connection_string, const std::string &relay_connection_string, const std::string &token);
|
||||
|
||||
void ShowNetworkAskSurvey();
|
||||
void ShowSurveyResultTextfileWindow();
|
||||
|
||||
/** Company information stored at the client side */
|
||||
struct NetworkCompanyInfo : NetworkCompanyStats {
|
||||
|
||||
366
src/network/network_survey.cpp
Normal file
366
src/network/network_survey.cpp
Normal file
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_survey.cpp Opt-in survey part of the network protocol. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
#include "network_survey.h"
|
||||
#include "network.h"
|
||||
#include "network_internal.h"
|
||||
#include "../company_base.h"
|
||||
#include "../debug.h"
|
||||
#include "../debug_fmt.h"
|
||||
#include "../rev.h"
|
||||
#include "../settings_type.h"
|
||||
#include "../settings_internal.h"
|
||||
#include "../timer/timer_game_tick.h"
|
||||
#include "../sl/saveload.h"
|
||||
#include "../date_func.h"
|
||||
|
||||
#include "../currency.h"
|
||||
#include "../fontcache.h"
|
||||
#include "../language.h"
|
||||
|
||||
#include "../ai/ai_info.hpp"
|
||||
#include "../game/game.hpp"
|
||||
#include "../game/game_info.hpp"
|
||||
|
||||
#include "../music/music_driver.hpp"
|
||||
#include "../sound/sound_driver.hpp"
|
||||
#include "../video/video_driver.hpp"
|
||||
|
||||
#include "../base_media_base.h"
|
||||
#include "../blitter/factory.hpp"
|
||||
|
||||
#ifdef WITH_NLOHMANN_JSON
|
||||
#include <nlohmann/json.hpp>
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
extern std::string _savegame_id;
|
||||
|
||||
NetworkSurveyHandler _survey = {};
|
||||
|
||||
#ifdef WITH_NLOHMANN_JSON
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurveyHandler::Reason, {
|
||||
{NetworkSurveyHandler::Reason::PREVIEW, "preview"},
|
||||
{NetworkSurveyHandler::Reason::LEAVE, "leave"},
|
||||
{NetworkSurveyHandler::Reason::EXIT, "exit"},
|
||||
{NetworkSurveyHandler::Reason::CRASH, "crash"},
|
||||
})
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM(GRFStatus, {
|
||||
{GRFStatus::GCS_UNKNOWN, "unknown"},
|
||||
{GRFStatus::GCS_DISABLED, "disabled"},
|
||||
{GRFStatus::GCS_NOT_FOUND, "not found"},
|
||||
{GRFStatus::GCS_INITIALISED, "initialised"},
|
||||
{GRFStatus::GCS_ACTIVATED, "activated"},
|
||||
})
|
||||
|
||||
static const std::string _vehicle_type_to_string[] = {
|
||||
"train",
|
||||
"roadveh",
|
||||
"ship",
|
||||
"aircraft",
|
||||
};
|
||||
|
||||
/* Defined in one of the os/ survey files. */
|
||||
extern void SurveyOS(nlohmann::json &json);
|
||||
|
||||
/**
|
||||
* Convert a settings table to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
* @param table The settings table to convert.
|
||||
* @param object The object to get the settings from.
|
||||
*/
|
||||
static void SurveySettingsTable(nlohmann::json &survey, const SettingTable &table, void *object)
|
||||
{
|
||||
char buf[512];
|
||||
for (auto &sd : table) {
|
||||
/* Skip any old settings we no longer save/load. */
|
||||
if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to, sd->save.ext_feature_test)) continue;
|
||||
|
||||
auto name = sd->name;
|
||||
sd->FormatValue(buf, lastof(buf), object);
|
||||
|
||||
survey[name] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert settings to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveySettings(nlohmann::json &survey)
|
||||
{
|
||||
IterateSettingsTables([&](const SettingTable &table, void *object) {
|
||||
SurveySettingsTable(survey, table, object);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert generic OpenTTD information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyOpenTTD(nlohmann::json &survey)
|
||||
{
|
||||
survey["version"] = std::string(_openttd_revision);
|
||||
survey["newgrf_version"] = _openttd_newgrf_version;
|
||||
survey["build_date"] = std::string(_openttd_build_date);
|
||||
survey["bits"] =
|
||||
#ifdef POINTER_IS_64BIT
|
||||
64
|
||||
#else
|
||||
32
|
||||
#endif
|
||||
;
|
||||
survey["endian"] =
|
||||
#if (TTD_ENDIAN == TTD_LITTLE_ENDIAN)
|
||||
"little"
|
||||
#else
|
||||
"big"
|
||||
#endif
|
||||
;
|
||||
survey["dedicated_build"] =
|
||||
#ifdef DEDICATED
|
||||
"yes"
|
||||
#else
|
||||
"no"
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert generic game information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyConfiguration(nlohmann::json &survey)
|
||||
{
|
||||
survey["network"] = _networking ? (_network_server ? "server" : "client") : "no";
|
||||
if (_current_language != nullptr) {
|
||||
std::string_view language_basename(_current_language->file);
|
||||
auto e = language_basename.rfind(PATHSEPCHAR);
|
||||
if (e != std::string::npos) {
|
||||
language_basename = language_basename.substr(e + 1);
|
||||
}
|
||||
|
||||
survey["language"]["filename"] = language_basename;
|
||||
survey["language"]["name"] = _current_language->name;
|
||||
survey["language"]["isocode"] = _current_language->isocode;
|
||||
}
|
||||
if (BlitterFactory::GetCurrentBlitter() != nullptr) {
|
||||
survey["blitter"] = BlitterFactory::GetCurrentBlitter()->GetName();
|
||||
}
|
||||
if (MusicDriver::GetInstance() != nullptr) {
|
||||
survey["music_driver"] = MusicDriver::GetInstance()->GetName();
|
||||
}
|
||||
if (SoundDriver::GetInstance() != nullptr) {
|
||||
survey["sound_driver"] = SoundDriver::GetInstance()->GetName();
|
||||
}
|
||||
if (VideoDriver::GetInstance() != nullptr) {
|
||||
survey["video_driver"] = VideoDriver::GetInstance()->GetName();
|
||||
survey["video_info"] = VideoDriver::GetInstance()->GetInfoString();
|
||||
}
|
||||
if (BaseGraphics::GetUsedSet() != nullptr) {
|
||||
survey["graphics_set"] = fmt::format("{}.{}", BaseGraphics::GetUsedSet()->name, BaseGraphics::GetUsedSet()->version);
|
||||
}
|
||||
if (BaseMusic::GetUsedSet() != nullptr) {
|
||||
survey["music_set"] = fmt::format("{}.{}", BaseMusic::GetUsedSet()->name, BaseMusic::GetUsedSet()->version);
|
||||
}
|
||||
if (BaseSounds::GetUsedSet() != nullptr) {
|
||||
survey["sound_set"] = fmt::format("{}.{}", BaseSounds::GetUsedSet()->name, BaseSounds::GetUsedSet()->version);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert font information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyFont(nlohmann::json &survey)
|
||||
{
|
||||
survey["small"] = FontCache::Get(FS_SMALL)->GetFontName();
|
||||
survey["medium"] = FontCache::Get(FS_NORMAL)->GetFontName();
|
||||
survey["large"] = FontCache::Get(FS_LARGE)->GetFontName();
|
||||
survey["mono"] = FontCache::Get(FS_MONO)->GetFontName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert company information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyCompanies(nlohmann::json &survey)
|
||||
{
|
||||
for (const Company *c : Company::Iterate()) {
|
||||
auto &company = survey[std::to_string(c->index)];
|
||||
if (c->ai_info == nullptr) {
|
||||
company["type"] = "human";
|
||||
} else {
|
||||
company["type"] = "ai";
|
||||
company["script"] = fmt::format("{}.{}", c->ai_info->GetName(), c->ai_info->GetVersion());
|
||||
}
|
||||
|
||||
for (VehicleType type = VEH_BEGIN; type < VEH_COMPANY_END; type++) {
|
||||
uint amount = c->group_all[type].num_vehicle;
|
||||
company["vehicles"][_vehicle_type_to_string[type]] = amount;
|
||||
}
|
||||
|
||||
company["infrastructure"]["road"] = c->infrastructure.GetRoadTotal();
|
||||
company["infrastructure"]["tram"] = c->infrastructure.GetTramTotal();
|
||||
company["infrastructure"]["rail"] = c->infrastructure.GetRailTotal();
|
||||
company["infrastructure"]["signal"] = c->infrastructure.signal;
|
||||
company["infrastructure"]["water"] = c->infrastructure.water;
|
||||
company["infrastructure"]["station"] = c->infrastructure.station;
|
||||
company["infrastructure"]["airport"] = c->infrastructure.airport;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert GRF information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyGrfs(nlohmann::json &survey)
|
||||
{
|
||||
for (GRFConfig *c = _grfconfig; c != nullptr; c = c->next) {
|
||||
auto grfid = fmt::format("{:08x}", BSWAP32(c->ident.grfid));
|
||||
auto &grf = survey[grfid];
|
||||
|
||||
grf["md5sum"] = BytesToHexString(c->ident.md5sum, 16);
|
||||
grf["status"] = c->status;
|
||||
|
||||
if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_UNSET) grf["palette"] = "unset";
|
||||
if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_DOS) grf["palette"] = "dos";
|
||||
if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_WINDOWS) grf["palette"] = "windows";
|
||||
if ((c->palette & GRFP_GRF_MASK) == GRFP_GRF_ANY) grf["palette"] = "any";
|
||||
|
||||
if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_UNSET) grf["blitter"] = "unset";
|
||||
if ((c->palette & GRFP_BLT_MASK) == GRFP_BLT_32BPP) grf["blitter"] = "32bpp";
|
||||
|
||||
grf["is_static"] = HasBit(c->flags, GCF_STATIC);
|
||||
|
||||
std::vector<uint32> parameters;
|
||||
for (int i = 0; i < c->num_params; i++) {
|
||||
parameters.push_back(c->param[i]);
|
||||
}
|
||||
grf["parameters"] = parameters;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert game-script information to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveyGameScript(nlohmann::json &survey)
|
||||
{
|
||||
if (Game::GetInfo() == nullptr) return;
|
||||
|
||||
survey = fmt::format("{}.{}", Game::GetInfo()->GetName(), Game::GetInfo()->GetVersion());
|
||||
}
|
||||
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
||||
|
||||
/**
|
||||
* Create the payload for the survey.
|
||||
*
|
||||
* @param reason The reason for sending the survey.
|
||||
* @param for_preview Whether the payload is meant for preview. This indents the result, and filters out the id/key.
|
||||
* @return std::string The JSON payload as string for the survey.
|
||||
*/
|
||||
std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview)
|
||||
{
|
||||
#ifndef WITH_NLOHMANN_JSON
|
||||
return "";
|
||||
#else
|
||||
nlohmann::json survey;
|
||||
|
||||
survey["schema"] = NETWORK_SURVEY_VERSION;
|
||||
survey["reason"] = reason;
|
||||
survey["id"] = _savegame_id;
|
||||
|
||||
#ifdef SURVEY_KEY
|
||||
/* We censor the key to avoid people trying to be "clever" and use it to send their own surveys. */
|
||||
survey["key"] = for_preview ? "(redacted)" : SURVEY_KEY;
|
||||
#else
|
||||
survey["key"] = "";
|
||||
#endif
|
||||
|
||||
{
|
||||
auto &info = survey["info"];
|
||||
SurveyOS(info["os"]);
|
||||
info["os"]["hardware_concurrency"] = std::thread::hardware_concurrency();
|
||||
|
||||
SurveyOpenTTD(info["openttd"]);
|
||||
SurveyConfiguration(info["configuration"]);
|
||||
SurveyFont(info["font"]);
|
||||
}
|
||||
|
||||
{
|
||||
auto &game = survey["game"];
|
||||
game["ticks"] = _scaled_tick_counter;
|
||||
game["time"] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - _switch_mode_time).count();
|
||||
SurveyCompanies(game["companies"]);
|
||||
SurveySettings(game["settings"]);
|
||||
SurveyGrfs(game["grfs"]);
|
||||
SurveyGameScript(game["game_script"]);
|
||||
}
|
||||
|
||||
/* For preview, we indent with 4 whitespaces to make things more readable. */
|
||||
int indent = for_preview ? 4 : -1;
|
||||
return survey.dump(indent);
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit the survey.
|
||||
*
|
||||
* @param reason The reason for sending the survey.
|
||||
* @param blocking Whether to block until the survey is sent.
|
||||
*/
|
||||
void NetworkSurveyHandler::Transmit(Reason reason, bool blocking)
|
||||
{
|
||||
if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) {
|
||||
Debug(net, 4, "Survey: not possible to send survey; most likely due to missing JSON library at compile-time");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settings_client.network.participate_survey != PS_YES) {
|
||||
Debug(net, 5, "Survey: user is not participating in survey; skipping survey");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(net, 1, "Survey: sending survey results");
|
||||
NetworkHTTPSocketHandler::Connect(NetworkSurveyUriString(), this, this->CreatePayload(reason));
|
||||
|
||||
if (blocking) {
|
||||
std::unique_lock<std::mutex> lock(this->mutex);
|
||||
/* Block no longer than 2 seconds. If we failed to send the survey in that time, so be it. */
|
||||
this->loaded.wait_for(lock, std::chrono::seconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSurveyHandler::OnFailure()
|
||||
{
|
||||
Debug(net, 1, "Survey: failed to send survey results");
|
||||
this->loaded.notify_all();
|
||||
}
|
||||
|
||||
void NetworkSurveyHandler::OnReceiveData(const char *data, size_t length)
|
||||
{
|
||||
if (data == nullptr) {
|
||||
Debug(net, 1, "Survey: survey results sent");
|
||||
this->loaded.notify_all();
|
||||
}
|
||||
}
|
||||
54
src/network/network_survey.h
Normal file
54
src/network/network_survey.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file network_survey.h Part of the network protocol handling opt-in survey. */
|
||||
|
||||
#ifndef NETWORK_SURVEY_H
|
||||
#define NETWORK_SURVEY_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include "core/http.h"
|
||||
|
||||
/**
|
||||
* Socket handler for the survey connection
|
||||
*/
|
||||
class NetworkSurveyHandler : public HTTPCallback {
|
||||
protected:
|
||||
void OnFailure() override;
|
||||
void OnReceiveData(const char *data, size_t length) override;
|
||||
bool IsCancelled() const override { return false; }
|
||||
|
||||
public:
|
||||
enum class Reason {
|
||||
PREVIEW, ///< User is previewing the survey result.
|
||||
LEAVE, ///< User is leaving the game (but not exiting the application).
|
||||
EXIT, ///< User is exiting the application.
|
||||
CRASH, ///< Game crashed.
|
||||
};
|
||||
|
||||
void Transmit(Reason reason, bool blocking = false);
|
||||
std::string CreatePayload(Reason reason, bool for_preview = false);
|
||||
|
||||
constexpr static bool IsSurveyPossible()
|
||||
{
|
||||
#ifndef WITH_NLOHMANN_JSON
|
||||
/* Without JSON library, we cannot send a payload; so we disable the survey. */
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex; ///< Mutex for the condition variable.
|
||||
std::condition_variable loaded; ///< Condition variable to wait for the survey to be sent.
|
||||
};
|
||||
|
||||
extern NetworkSurveyHandler _survey;
|
||||
|
||||
#endif /* NETWORK_SURVEY_H */
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
* @param addresses The addresses to bind on.
|
||||
*/
|
||||
ServerNetworkUDPSocketHandler(NetworkAddressList *addresses) : NetworkUDPSocketHandler(addresses) {}
|
||||
virtual ~ServerNetworkUDPSocketHandler() {}
|
||||
virtual ~ServerNetworkUDPSocketHandler() = default;
|
||||
};
|
||||
|
||||
void ServerNetworkUDPSocketHandler::Receive_CLIENT_FIND_SERVER(Packet *p, NetworkAddress *client_addr)
|
||||
@@ -117,7 +117,7 @@ protected:
|
||||
void Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override;
|
||||
void Receive_EX_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr) override;
|
||||
public:
|
||||
virtual ~ClientNetworkUDPSocketHandler() {}
|
||||
virtual ~ClientNetworkUDPSocketHandler() = default;
|
||||
};
|
||||
|
||||
void ClientNetworkUDPSocketHandler::Receive_SERVER_RESPONSE(Packet *p, NetworkAddress *client_addr)
|
||||
|
||||
Reference in New Issue
Block a user