mirror of https://github.com/OpenTTD/OpenTTD.git
Feature: opt-in survey when exiting a game
On first start-up, the game will ask if you want to participate in our automated survey. You have to opt-in, and can easily opt-out (via the Options) at any time. When opt-in, whenever you exit a game, a JSON blob will be send to the survey server hosted by OpenTTD. This JSON blob contains information that gives a global picture of the game just played: - What settings were used - How many humans vs AIs - How long the game has been played - Basic information about the OS / CPU All this information is kept very generic, so there is no chance we send private information to our survey server. Nothing in the JSON blob could identify you as a person; it mostly tells about the game played. At any time you can see what the JSON blob includes, by pressing the "Preview Survey Results" button in-game.
This commit is contained in:
parent
021c45c4f6
commit
7634553d22
|
@ -37,10 +37,12 @@ jobs:
|
|||
liballegro4-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfontconfig-dev \
|
||||
libharfbuzz-dev \
|
||||
libicu-dev \
|
||||
liblzma-dev \
|
||||
liblzo2-dev \
|
||||
libsdl2-dev \
|
||||
nlohmann-json3-dev \
|
||||
zlib1g-dev \
|
||||
# EOF
|
||||
echo "::endgroup::"
|
||||
|
|
|
@ -2,6 +2,11 @@ name: Release (Linux)
|
|||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
survey_key:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
|
@ -119,6 +124,7 @@ jobs:
|
|||
cmake ${GITHUB_WORKSPACE} \
|
||||
-DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DOPTION_SURVEY_KEY=${{ inputs.survey_key }} \
|
||||
-DOPTION_PACKAGE_DEPENDENCIES=ON \
|
||||
# EOF
|
||||
echo "::endgroup::"
|
||||
|
|
|
@ -2,6 +2,11 @@ name: Release (MacOS)
|
|||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
survey_key:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
|
@ -104,6 +109,7 @@ jobs:
|
|||
-DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake \
|
||||
-DHOST_BINARY_DIR=${GITHUB_WORKSPACE}/build-host \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DOPTION_SURVEY_KEY=${{ inputs.survey_key }} \
|
||||
# EOF
|
||||
echo "::endgroup::"
|
||||
|
||||
|
@ -124,6 +130,7 @@ jobs:
|
|||
-DCMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake \
|
||||
-DHOST_BINARY_DIR=${GITHUB_WORKSPACE}/build-host \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DOPTION_SURVEY_KEY=${{ inputs.survey_key }} \
|
||||
-DCPACK_BUNDLE_APPLE_CERT_APP=${{ secrets.APPLE_DEVELOPER_CERTIFICATE_ID }} \
|
||||
"-DCPACK_BUNDLE_APPLE_CODESIGN_PARAMETER=--deep -f --options runtime" \
|
||||
-DAPPLE_UNIVERSAL_PACKAGE=1 \
|
||||
|
|
|
@ -11,6 +11,8 @@ on:
|
|||
value: ${{ jobs.source.outputs.trigger_type }}
|
||||
folder:
|
||||
value: ${{ jobs.source.outputs.folder }}
|
||||
survey_key:
|
||||
value: ${{ jobs.source.outputs.survey_key }}
|
||||
|
||||
jobs:
|
||||
source:
|
||||
|
@ -23,6 +25,7 @@ jobs:
|
|||
is_tag: ${{ steps.metadata.outputs.is_tag }}
|
||||
trigger_type: ${{ steps.metadata.outputs.trigger_type }}
|
||||
folder: ${{ steps.metadata.outputs.folder }}
|
||||
survey_key: ${{ steps.survey_key.outputs.survey_key }}
|
||||
|
||||
steps:
|
||||
- name: Checkout (Release)
|
||||
|
@ -146,6 +149,19 @@ jobs:
|
|||
FOLDER_NIGHTLIES: openttd-nightlies
|
||||
FOLDER_BRANCHES: openttd-branches
|
||||
|
||||
- name: Generate survey key
|
||||
id: survey_key
|
||||
run: |
|
||||
PAYLOAD='{"version":"${{ steps.metadata.outputs.version }}","type":"${{ vars.SURVEY_TYPE }}"}'
|
||||
|
||||
echo "${{ secrets.SURVEY_SIGNING_KEY }}" > survey_signing_key.pem
|
||||
SIGNATURE=$(echo -n "${PAYLOAD}" | openssl dgst -sha256 -sign survey_signing_key.pem | base64 -w0)
|
||||
rm -f survey_signing_key.pem
|
||||
|
||||
SURVEY_KEY=$(curl -f -s -X POST -d "${PAYLOAD}" -H "Content-Type: application/json" -H "X-Signature: ${SIGNATURE}" https://survey-participate.openttd.org/create-survey-key/${{ vars.SURVEY_TYPE }})
|
||||
|
||||
echo "survey_key=${SURVEY_KEY}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Remove VCS information
|
||||
run: |
|
||||
rm -rf .git
|
||||
|
|
|
@ -3,6 +3,10 @@ name: Release (Windows)
|
|||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
survey_key:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
is_tag:
|
||||
required: true
|
||||
type: string
|
||||
|
@ -129,6 +133,7 @@ jobs:
|
|||
-DOPTION_USE_NSIS=ON \
|
||||
-DHOST_BINARY_DIR=${GITHUB_WORKSPACE}/build-host \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DOPTION_SURVEY_KEY=${{ inputs.survey_key }} \
|
||||
-DWINDOWS_CERTIFICATE_COMMON_NAME="${WINDOWS_CERTIFICATE_COMMON_NAME}" \
|
||||
# EOF
|
||||
echo "::endgroup::"
|
||||
|
@ -153,6 +158,7 @@ jobs:
|
|||
-DCMAKE_TOOLCHAIN_FILE="c:\vcpkg\scripts\buildsystems\vcpkg.cmake" \
|
||||
-DHOST_BINARY_DIR=${GITHUB_WORKSPACE}/build-host \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DOPTION_SURVEY_KEY=${{ inputs.survey_key }} \
|
||||
-DWINDOWS_CERTIFICATE_COMMON_NAME="${WINDOWS_CERTIFICATE_COMMON_NAME}" \
|
||||
# EOF
|
||||
echo "::endgroup::"
|
||||
|
|
|
@ -38,6 +38,9 @@ jobs:
|
|||
uses: ./.github/workflows/release-linux.yml
|
||||
secrets: inherit
|
||||
|
||||
with:
|
||||
survey_key: ${{ needs.source.outputs.survey_key }}
|
||||
|
||||
macos:
|
||||
name: MacOS
|
||||
needs: source
|
||||
|
@ -45,6 +48,9 @@ jobs:
|
|||
uses: ./.github/workflows/release-macos.yml
|
||||
secrets: inherit
|
||||
|
||||
with:
|
||||
survey_key: ${{ needs.source.outputs.survey_key }}
|
||||
|
||||
windows:
|
||||
name: Windows
|
||||
needs: source
|
||||
|
@ -54,6 +60,7 @@ jobs:
|
|||
|
||||
with:
|
||||
is_tag: ${{ needs.source.outputs.is_tag }}
|
||||
survey_key: ${{ needs.source.outputs.survey_key }}
|
||||
|
||||
windows-store:
|
||||
name: Windows Store
|
||||
|
|
|
@ -71,6 +71,8 @@ function(set_options)
|
|||
if (OPTION_DOCS_ONLY)
|
||||
set(OPTION_TOOLS_ONLY ON PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
option(OPTION_SURVEY_KEY "Survey-key to use for the opt-in survey (empty if you have none)" "")
|
||||
endfunction()
|
||||
|
||||
# Show the values of the generic options.
|
||||
|
@ -84,6 +86,12 @@ function(show_options)
|
|||
message(STATUS "Option Use assert - ${OPTION_USE_ASSERTS}")
|
||||
message(STATUS "Option Use threads - ${OPTION_USE_THREADS}")
|
||||
message(STATUS "Option Use NSIS - ${OPTION_USE_NSIS}")
|
||||
|
||||
if(OPTION_SURVEY_KEY)
|
||||
message(STATUS "Option Survey Key - USED")
|
||||
else()
|
||||
message(STATUS "Option Survey Key - NOT USED")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Add the definitions for the options that are selected.
|
||||
|
@ -104,4 +112,8 @@ function(add_definitions_based_on_options)
|
|||
else()
|
||||
add_definitions(-DNDEBUG)
|
||||
endif()
|
||||
|
||||
if(OPTION_SURVEY_KEY)
|
||||
add_definitions(-DSURVEY_KEY="${OPTION_SURVEY_KEY}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
|
|
@ -183,7 +183,7 @@ struct AIConfigWindow : public Window {
|
|||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (this->selected_slot == INVALID_COMPANY || AIConfig::GetConfig(this->selected_slot) == nullptr) return;
|
||||
|
||||
ShowScriptTextfileWindow((TextfileType)(widget - WID_AIC_TEXTFILE), this->selected_slot);
|
||||
|
@ -284,7 +284,7 @@ struct AIConfigWindow : public Window {
|
|||
this->SetWidgetDisabledState(WID_AIC_MOVE_UP, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot - 1)));
|
||||
this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, this->selected_slot == INVALID_COMPANY || !IsEditable((CompanyID)(this->selected_slot + 1)));
|
||||
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || !AIConfig::GetConfig(this->selected_slot)->GetTextfile(tft, this->selected_slot).has_value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "screenshot.h"
|
||||
#include "gfx_func.h"
|
||||
#include "network/network.h"
|
||||
#include "network/network_survey.h"
|
||||
#include "language.h"
|
||||
#include "fontcache.h"
|
||||
#include "news_gui.h"
|
||||
|
@ -511,6 +512,10 @@ bool CrashLog::MakeCrashLog() const
|
|||
printf("Writing crash screenshot failed.\n\n");
|
||||
}
|
||||
|
||||
if (_game_mode == GM_NORMAL) {
|
||||
_survey.Transmit(NetworkSurveyHandler::Reason::CRASH, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -240,7 +240,7 @@ struct GSConfigWindow : public Window {
|
|||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget >= WID_GSC_TEXTFILE && widget < WID_GSC_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_GSC_TEXTFILE && widget < WID_GSC_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (GameConfig::GetConfig() == nullptr) return;
|
||||
|
||||
ShowScriptTextfileWindow((TextfileType)(widget - WID_GSC_TEXTFILE), (CompanyID)OWNER_DEITY);
|
||||
|
@ -404,7 +404,7 @@ struct GSConfigWindow : public Window {
|
|||
|
||||
this->SetWidgetDisabledState(WID_GSC_CHANGE, (_game_mode == GM_NORMAL) || !IsEditable());
|
||||
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_GSC_TEXTFILE + tft, !GameConfig::GetConfig()->GetTextfile(tft, (CompanyID)OWNER_DEITY).has_value());
|
||||
}
|
||||
this->RebuildVisibleSettings();
|
||||
|
|
|
@ -47,6 +47,7 @@ bool _screen_disable_anim = false; ///< Disable palette animation (important f
|
|||
std::atomic<bool> _exit_game;
|
||||
GameMode _game_mode;
|
||||
SwitchMode _switch_mode; ///< The next mainloop command.
|
||||
std::chrono::steady_clock::time_point _switch_mode_time; ///< The time when the switch mode was requested.
|
||||
PauseMode _pause_mode;
|
||||
Palette _cur_palette;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "genworld.h"
|
||||
#include "network/network_gui.h"
|
||||
#include "network/network_content.h"
|
||||
#include "network/network_survey.h"
|
||||
#include "landscape_type.h"
|
||||
#include "landscape.h"
|
||||
#include "strings_func.h"
|
||||
|
@ -504,7 +505,10 @@ void ShowSelectGameWindow()
|
|||
|
||||
static void AskExitGameCallback(Window *w, bool confirmed)
|
||||
{
|
||||
if (confirmed) _exit_game = true;
|
||||
if (confirmed) {
|
||||
_survey.Transmit(NetworkSurveyHandler::Reason::EXIT, true);
|
||||
_exit_game = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AskExitGame()
|
||||
|
|
|
@ -1041,6 +1041,14 @@ STR_GAME_OPTIONS_GUI_SCALE_3X :3x
|
|||
STR_GAME_OPTIONS_GUI_SCALE_4X :4x
|
||||
STR_GAME_OPTIONS_GUI_SCALE_5X :5x
|
||||
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_FRAME :{BLACK}Automated survey
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY :{BLACK}Participate in automated survey
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_TOOLTIP :{BLACK}When enabled, OpenTTD will transmit a survey when leaving a game
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK :{BLACK}About survey and privacy
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK_TOOLTIP :{BLACK}This opens a browser with more information about the automated survey
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW :{BLACK}Preview survey result
|
||||
STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW_TOOLTIP :{BLACK}Show the survey result of the current running game
|
||||
|
||||
STR_GAME_OPTIONS_GRAPHICS :{BLACK}Graphics
|
||||
|
||||
STR_GAME_OPTIONS_REFRESH_RATE :{BLACK}Display refresh rate
|
||||
|
@ -2402,6 +2410,13 @@ STR_NETWORK_ASK_RELAY_NO :{BLACK}No
|
|||
STR_NETWORK_ASK_RELAY_YES_ONCE :{BLACK}Yes, this once
|
||||
STR_NETWORK_ASK_RELAY_YES_ALWAYS :{BLACK}Yes, don't ask again
|
||||
|
||||
STR_NETWORK_ASK_SURVEY_CAPTION :Participate in automated survey?
|
||||
STR_NETWORK_ASK_SURVEY_TEXT :Would you like to participate in the automated survey?{}OpenTTD will transmit a survey when leaving a game.{}You can change this at any time under "Game Options".
|
||||
STR_NETWORK_ASK_SURVEY_PREVIEW :Preview survey result
|
||||
STR_NETWORK_ASK_SURVEY_LINK :About survey and privacy
|
||||
STR_NETWORK_ASK_SURVEY_NO :No
|
||||
STR_NETWORK_ASK_SURVEY_YES :Yes
|
||||
|
||||
STR_NETWORK_SPECTATORS :Spectators
|
||||
|
||||
# Network set password
|
||||
|
@ -4654,10 +4669,11 @@ STR_TEXTFILE_WRAP_TEXT_TOOLTIP :{BLACK}Wrap the
|
|||
STR_TEXTFILE_VIEW_README :{BLACK}View readme
|
||||
STR_TEXTFILE_VIEW_CHANGELOG :{BLACK}Changelog
|
||||
STR_TEXTFILE_VIEW_LICENCE :{BLACK}Licence
|
||||
###length 3
|
||||
###length 4
|
||||
STR_TEXTFILE_README_CAPTION :{WHITE}{STRING} readme of {RAW_STRING}
|
||||
STR_TEXTFILE_CHANGELOG_CAPTION :{WHITE}{STRING} changelog of {RAW_STRING}
|
||||
STR_TEXTFILE_LICENCE_CAPTION :{WHITE}{STRING} licence of {RAW_STRING}
|
||||
STR_TEXTFILE_SURVEY_RESULT_CAPTION :{WHITE}Preview of survey result
|
||||
|
||||
|
||||
# Vehicle loading indicators
|
||||
|
|
36
src/misc.cpp
36
src/misc.cpp
|
@ -31,9 +31,11 @@
|
|||
#include "town_kdtree.h"
|
||||
#include "viewport_kdtree.h"
|
||||
#include "newgrf_profiling.h"
|
||||
#include "3rdparty/md5/md5.h"
|
||||
|
||||
#include "safeguards.h"
|
||||
|
||||
std::string _savegame_id; ///< Unique ID of the current savegame.
|
||||
|
||||
extern TileIndex _cur_tileloop_tile;
|
||||
extern void MakeNewgameSettingsLive();
|
||||
|
@ -56,6 +58,40 @@ void InitializeCheats();
|
|||
void InitializeNPF();
|
||||
void InitializeOldNames();
|
||||
|
||||
/**
|
||||
* Generate an unique ID.
|
||||
*
|
||||
* It isn't as much of an unique ID as we would like, but our random generator
|
||||
* can only produce 32bit random numbers.
|
||||
* That is why we combine InteractiveRandom with the current (steady) clock.
|
||||
* The first to add a bit of randomness, the second to ensure you can't get
|
||||
* the same unique ID when you run it twice from the same state at different
|
||||
* times.
|
||||
*
|
||||
* This makes it unlikely that two users generate the same ID for different
|
||||
* subjects. But as this is not an UUID, so it can't be ruled out either.
|
||||
*/
|
||||
std::string GenerateUid(std::string_view subject)
|
||||
{
|
||||
auto current_time = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
std::string coding_string = fmt::format("{}{}{}", InteractiveRandom(), current_time, subject);
|
||||
|
||||
Md5 checksum;
|
||||
uint8 digest[16];
|
||||
checksum.Append(coding_string.c_str(), coding_string.length());
|
||||
checksum.Finish(digest);
|
||||
|
||||
return MD5SumToString(digest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an unique savegame ID.
|
||||
*/
|
||||
void GenerateSavegameId()
|
||||
{
|
||||
_savegame_id = GenerateUid("OpenTTD Savegame ID");
|
||||
}
|
||||
|
||||
void InitializeGame(uint size_x, uint size_y, bool reset_date, bool reset_settings)
|
||||
{
|
||||
/* Make sure there isn't any window that can influence anything
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -66,3 +66,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)
|
||||
|
@ -26,6 +27,8 @@ static const uint16 NETWORK_ADMIN_PORT = 3977; ///< The d
|
|||
static const uint16 NETWORK_DEFAULT_DEBUGLOG_PORT = 3982; ///< The default port debug-log is sent to (TCP)
|
||||
|
||||
static const uint16 UDP_MTU = 1460; ///< Number of bytes we can pack in a single UDP packet
|
||||
|
||||
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.
|
||||
|
@ -46,6 +49,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'
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -116,6 +116,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);
|
||||
|
@ -146,8 +147,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());
|
||||
|
||||
|
@ -174,11 +183,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,6 +85,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
|
||||
|
@ -1204,24 +1206,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");
|
||||
}
|
||||
|
||||
class TCPNetworkDebugConnecter : TCPConnecter {
|
||||
|
|
|
@ -790,7 +790,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);
|
||||
|
@ -997,7 +997,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).has_value());
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,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 "../timer/timer.h"
|
||||
#include "../timer/timer_window.h"
|
||||
#include "../timer/timer_game_calendar.h"
|
||||
#include "../textfile_gui.h"
|
||||
|
||||
#include "../widgets/network_widget.h"
|
||||
|
||||
|
@ -2515,3 +2517,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);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,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 {
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
/*
|
||||
* 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 "settings_table.h"
|
||||
#include "network.h"
|
||||
#include "../debug.h"
|
||||
#include "../rev.h"
|
||||
#include "../settings_type.h"
|
||||
#include "../timer/timer_game_tick.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);
|
||||
|
||||
/**
|
||||
* List of all the generic setting tables.
|
||||
*
|
||||
* There are a few tables that are special and not processed like the rest:
|
||||
* - _currency_settings
|
||||
* - _misc_settings
|
||||
* - _company_settings
|
||||
* - _win32_settings
|
||||
* As such, they are not part of this list.
|
||||
*/
|
||||
static auto &GenericSettingTables()
|
||||
{
|
||||
static const SettingTable _generic_setting_tables[] = {
|
||||
_difficulty_settings,
|
||||
_economy_settings,
|
||||
_game_settings,
|
||||
_gui_settings,
|
||||
_linkgraph_settings,
|
||||
_locale_settings,
|
||||
_multimedia_settings,
|
||||
_network_settings,
|
||||
_news_display_settings,
|
||||
_pathfinding_settings,
|
||||
_script_settings,
|
||||
_world_settings,
|
||||
};
|
||||
return _generic_setting_tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 &desc : table) {
|
||||
const SettingDesc *sd = GetSettingDesc(desc);
|
||||
/* Skip any old settings we no longer save/load. */
|
||||
if (!SlIsObjectCurrentlyValid(sd->save.version_from, sd->save.version_to)) continue;
|
||||
|
||||
auto name = sd->GetName();
|
||||
sd->FormatValue(buf, lastof(buf), object);
|
||||
|
||||
survey[name] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert settings to JSON.
|
||||
*
|
||||
* @param survey The JSON object.
|
||||
*/
|
||||
static void SurveySettings(nlohmann::json &survey)
|
||||
{
|
||||
SurveySettingsTable(survey, _misc_settings, nullptr);
|
||||
#if defined(_WIN32) && !defined(DEDICATED)
|
||||
SurveySettingsTable(survey, _win32_settings, nullptr);
|
||||
#endif
|
||||
for (auto &table : GenericSettingTables()) {
|
||||
SurveySettingsTable(survey, table, &_settings_game);
|
||||
}
|
||||
SurveySettingsTable(survey, _currency_settings, &_custom_currency);
|
||||
SurveySettingsTable(survey, _company_settings, &_settings_client.company);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"] = MD5SumToString(c->ident.md5sum);
|
||||
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"] = TimerGameTick::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();
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -925,7 +925,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback {
|
|||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget >= WID_NS_NEWGRF_TEXTFILE && widget < WID_NS_NEWGRF_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_NS_NEWGRF_TEXTFILE && widget < WID_NS_NEWGRF_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (this->active_sel == nullptr && this->avail_sel == nullptr) return;
|
||||
|
||||
ShowNewGRFTextfileWindow((TextfileType)(widget - WID_NS_NEWGRF_TEXTFILE), this->active_sel != nullptr ? this->active_sel : this->avail_sel);
|
||||
|
@ -1286,7 +1286,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback {
|
|||
);
|
||||
|
||||
const GRFConfig *selected_config = (this->avail_sel == nullptr) ? this->active_sel : this->avail_sel;
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_NS_NEWGRF_TEXTFILE + tft, selected_config == nullptr || !selected_config->GetTextfile(tft).has_value());
|
||||
}
|
||||
this->SetWidgetDisabledState(WID_NS_OPEN_URL, selected_config == nullptr || StrEmpty(selected_config->GetURL()));
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
#include "framerate_type.h"
|
||||
#include "industry.h"
|
||||
#include "network/network_gui.h"
|
||||
#include "network/network_survey.h"
|
||||
#include "misc_cmd.h"
|
||||
#include "timer/timer.h"
|
||||
#include "timer/timer_game_calendar.h"
|
||||
|
@ -822,6 +823,7 @@ void HandleExitGameRequest()
|
|||
_exit_game = true;
|
||||
} else if (_settings_client.gui.autosave_on_exit) {
|
||||
DoExitSave();
|
||||
_survey.Transmit(NetworkSurveyHandler::Reason::EXIT, true);
|
||||
_exit_game = true;
|
||||
} else {
|
||||
AskExitGame();
|
||||
|
@ -1036,9 +1038,16 @@ void SwitchToMode(SwitchMode new_mode)
|
|||
/* When we change mode, reset the autosave. */
|
||||
if (new_mode != SM_SAVE_GAME) ChangeAutosaveFrequency(true);
|
||||
|
||||
/* Transmit the survey if we were in normal-mode and not saving. It always means we leaving the current game. */
|
||||
if (_game_mode == GM_NORMAL && new_mode != SM_SAVE_GAME) _survey.Transmit(NetworkSurveyHandler::Reason::LEAVE);
|
||||
|
||||
/* Keep track when we last switch mode. Used for survey, to know how long someone was in a game. */
|
||||
if (new_mode != SM_SAVE_GAME) _switch_mode_time = std::chrono::steady_clock::now();
|
||||
|
||||
switch (new_mode) {
|
||||
case SM_EDITOR: // Switch to scenario editor
|
||||
MakeNewEditorWorld();
|
||||
GenerateSavegameId();
|
||||
break;
|
||||
|
||||
case SM_RELOADGAME: // Reload with what-ever started the game
|
||||
|
@ -1055,11 +1064,13 @@ void SwitchToMode(SwitchMode new_mode)
|
|||
}
|
||||
|
||||
MakeNewGame(false, new_mode == SM_NEWGAME);
|
||||
GenerateSavegameId();
|
||||
break;
|
||||
|
||||
case SM_RESTARTGAME: // Restart --> 'Random game' with current settings
|
||||
case SM_NEWGAME: // New Game --> 'Random game'
|
||||
MakeNewGame(false, new_mode == SM_NEWGAME);
|
||||
GenerateSavegameId();
|
||||
break;
|
||||
|
||||
case SM_LOAD_GAME: { // Load game, Play Scenario
|
||||
|
@ -1083,18 +1094,21 @@ void SwitchToMode(SwitchMode new_mode)
|
|||
case SM_RESTART_HEIGHTMAP: // Load a heightmap and start a new game from it with current settings
|
||||
case SM_START_HEIGHTMAP: // Load a heightmap and start a new game from it
|
||||
MakeNewGame(true, new_mode == SM_START_HEIGHTMAP);
|
||||
GenerateSavegameId();
|
||||
break;
|
||||
|
||||
case SM_LOAD_HEIGHTMAP: // Load heightmap from scenario editor
|
||||
SetLocalCompany(OWNER_NONE);
|
||||
|
||||
GenerateWorld(GWM_HEIGHTMAP, 1 << _settings_game.game_creation.map_x, 1 << _settings_game.game_creation.map_y);
|
||||
GenerateSavegameId();
|
||||
MarkWholeScreenDirty();
|
||||
break;
|
||||
|
||||
case SM_LOAD_SCENARIO: { // Load scenario from scenario editor
|
||||
if (SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, GM_EDITOR, NO_DIRECTORY)) {
|
||||
SetLocalCompany(OWNER_NONE);
|
||||
GenerateSavegameId();
|
||||
_settings_newgame.game_creation.starting_year = TimerGameCalendar::year;
|
||||
/* Cancel the saveload pausing */
|
||||
Command<CMD_PAUSE>::Post(PM_PAUSED_SAVELOAD, false);
|
||||
|
@ -1116,6 +1130,14 @@ void SwitchToMode(SwitchMode new_mode)
|
|||
ShowErrorMessage(STR_WARNING_FALLBACK_SOUNDSET, INVALID_STRING_ID, WL_CRITICAL);
|
||||
BaseSounds::ini_set = BaseSounds::GetUsedSet()->name;
|
||||
}
|
||||
if (_settings_client.network.participate_survey == PS_ASK) {
|
||||
/* No matter how often you go back to the main menu, only ask the first time. */
|
||||
static bool asked_once = false;
|
||||
if (!asked_once) {
|
||||
asked_once = true;
|
||||
ShowNetworkAskSurvey();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SM_SAVE_GAME: // Save game.
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#define OPENTTD_H
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include "core/enum_type.hpp"
|
||||
|
||||
/** Mode which defines the state of the game. */
|
||||
|
@ -53,6 +54,7 @@ enum DisplayOptions {
|
|||
|
||||
extern GameMode _game_mode;
|
||||
extern SwitchMode _switch_mode;
|
||||
extern std::chrono::steady_clock::time_point _switch_mode_time;
|
||||
extern std::atomic<bool> _exit_game;
|
||||
extern bool _save_config;
|
||||
|
||||
|
@ -86,6 +88,7 @@ void HandleExitGameRequest();
|
|||
void SwitchToMode(SwitchMode new_mode);
|
||||
|
||||
bool RequestNewGRFScan(struct NewGRFScanCallback *callback = nullptr);
|
||||
void GenerateSavegameId();
|
||||
|
||||
void OpenBrowser(const char *url);
|
||||
void ChangeAutosaveFrequency(bool reset);
|
||||
|
|
|
@ -7,6 +7,7 @@ add_files(
|
|||
osx_stdafx.h
|
||||
string_osx.cpp
|
||||
string_osx.h
|
||||
survey_osx.cpp
|
||||
CONDITION APPLE
|
||||
)
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ bool IsMonospaceFont(CFStringRef name);
|
|||
|
||||
void MacOSSetThreadName(const char *name);
|
||||
|
||||
uint64 MacOSGetPhysicalMemory();
|
||||
|
||||
|
||||
/** Deleter that calls CFRelease rather than deleting the pointer. */
|
||||
template <typename T> struct CFDeleter {
|
||||
|
|
|
@ -272,3 +272,8 @@ void MacOSSetThreadName(const char *name)
|
|||
[ cur performSelector:@selector(setName:) withObject:[ NSString stringWithUTF8String:name ] ];
|
||||
}
|
||||
}
|
||||
|
||||
uint64 MacOSGetPhysicalMemory()
|
||||
{
|
||||
return [ [ NSProcessInfo processInfo ] physicalMemory ];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 survey_osx.cpp OSX implementation of OS-specific survey information. */
|
||||
|
||||
#ifdef WITH_NLOHMANN_JSON
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "../../3rdparty/fmt/format.h"
|
||||
#include "macos.h"
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
int ver_maj, ver_min, ver_bug;
|
||||
GetMacOSVersion(&ver_maj, &ver_min, &ver_bug);
|
||||
|
||||
const NXArchInfo *arch = NXGetLocalArchInfo();
|
||||
|
||||
json["os"] = "MacOS";
|
||||
json["release"] = fmt::format("{}.{}.{}", ver_maj, ver_min, ver_bug);
|
||||
json["machine"] = arch != nullptr ? arch->description : "unknown";
|
||||
json["min_ver"] = MAC_OS_X_VERSION_MIN_REQUIRED;
|
||||
json["max_ver"] = MAC_OS_X_VERSION_MAX_ALLOWED;
|
||||
|
||||
json["memory"] = MacOSGetPhysicalMemory();
|
||||
}
|
||||
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
|
@ -1,5 +1,6 @@
|
|||
add_files(
|
||||
crashlog_unix.cpp
|
||||
survey_unix.cpp
|
||||
CONDITION UNIX AND NOT APPLE AND NOT OPTION_OS2
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 survey_unix.cpp Unix implementation of OS-specific survey information. */
|
||||
|
||||
#ifdef WITH_NLOHMANN_JSON
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
struct utsname name;
|
||||
if (uname(&name) < 0) {
|
||||
json["os"] = "Unix";
|
||||
return;
|
||||
}
|
||||
|
||||
json["os"] = name.sysname;
|
||||
json["release"] = name.release;
|
||||
json["machine"] = name.machine;
|
||||
json["version"] = name.version;
|
||||
|
||||
long pages = sysconf(_SC_PHYS_PAGES);
|
||||
long page_size = sysconf(_SC_PAGE_SIZE);
|
||||
json["memory"] = pages * page_size;
|
||||
}
|
||||
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
|
@ -4,6 +4,7 @@ add_files(
|
|||
font_win32.h
|
||||
string_uniscribe.cpp
|
||||
string_uniscribe.h
|
||||
survey_win.cpp
|
||||
win32.cpp
|
||||
win32.h
|
||||
CONDITION WIN32
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 survey_win.cpp Windows implementation of OS-specific survey information. */
|
||||
|
||||
#ifdef WITH_NLOHMANN_JSON
|
||||
|
||||
#include "../../stdafx.h"
|
||||
|
||||
#include "../../3rdparty/fmt/format.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <windows.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
void SurveyOS(nlohmann::json &json)
|
||||
{
|
||||
_OSVERSIONINFOA os;
|
||||
os.dwOSVersionInfoSize = sizeof(os);
|
||||
GetVersionExA(&os);
|
||||
|
||||
json["os"] = "Windows";
|
||||
json["release"] = fmt::format("{}.{}.{} ({})", os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.szCSDVersion);
|
||||
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatusEx(&status);
|
||||
|
||||
json["memory"] = status.ullTotalPhys;
|
||||
}
|
||||
|
||||
#endif /* WITH_NLOHMANN_JSON */
|
|
@ -3224,6 +3224,10 @@ bool AfterLoadGame()
|
|||
for (Station *st : Station::Iterate()) UpdateStationAcceptance(st, false);
|
||||
}
|
||||
|
||||
if (IsSavegameVersionBefore(SLV_SAVEGAME_ID)) {
|
||||
GenerateSavegameId();
|
||||
}
|
||||
|
||||
if (IsSavegameVersionBefore(SLV_AI_START_DATE)) {
|
||||
/* For older savegames, we don't now the actual interval; so set it to the newgame value. */
|
||||
_settings_game.difficulty.competitors_interval = _settings_newgame.difficulty.competitors_interval;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
extern TileIndex _cur_tileloop_tile;
|
||||
extern uint16 _disaster_delay;
|
||||
extern byte _trees_tick_ctr;
|
||||
extern std::string _savegame_id;
|
||||
|
||||
/* Keep track of current game position */
|
||||
int _saved_scrollpos_x;
|
||||
|
@ -87,6 +88,7 @@ static const SaveLoad _date_desc[] = {
|
|||
SLEG_VAR("company_tick_counter", _cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32),
|
||||
SLEG_VAR("trees_tick_counter", _trees_tick_ctr, SLE_UINT8),
|
||||
SLEG_CONDVAR("pause_mode", _pause_mode, SLE_UINT8, SLV_4, SL_MAX_VERSION),
|
||||
SLEG_CONDSSTR("id", _savegame_id, SLE_STR, SLV_SAVEGAME_ID, SL_MAX_VERSION),
|
||||
/* For older savegames, we load the current value as the "period"; afterload will set the "fired" and "elapsed". */
|
||||
SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_FILE_U16 | SLE_VAR_U32, SL_MIN_VERSION, SLV_109),
|
||||
SLEG_CONDVAR("next_competitor_start", _new_competitor_timeout.period, SLE_UINT32, SLV_109, SLV_AI_START_DATE),
|
||||
|
|
|
@ -353,6 +353,7 @@ enum SaveLoadVersion : uint16 {
|
|||
SLV_EXTEND_VEHICLE_RANDOM, ///< 310 PR#10701 Extend vehicle random bits.
|
||||
SLV_EXTEND_ENTITY_MAPPING, ///< 311 PR#10672 Extend entity mapping range.
|
||||
SLV_DISASTER_VEH_STATE, ///< 312 PR#10798 Explicit storage of disaster vehicle state.
|
||||
SLV_SAVEGAME_ID, ///< 313 PR#10719 Add an unique ID to every savegame (used to deduplicate surveys).
|
||||
|
||||
SL_MAX_VERSION, ///< Highest possible saveload version
|
||||
};
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
#include "music/music_driver.hpp"
|
||||
#include "gui.h"
|
||||
#include "mixer.h"
|
||||
#include "network/core/config.h"
|
||||
#include "network/network_gui.h"
|
||||
#include "network/network_survey.h"
|
||||
|
||||
|
||||
#include "safeguards.h"
|
||||
|
@ -190,6 +193,8 @@ struct GameOptionsWindow : Window {
|
|||
this->OnInvalidateData(0);
|
||||
|
||||
this->SetTab(WID_GO_TAB_GENERAL);
|
||||
|
||||
if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) this->GetWidget<NWidgetStacked>(WID_GO_SURVEY_SEL)->SetDisplayedPlane(SZSP_NONE);
|
||||
}
|
||||
|
||||
void Close() override
|
||||
|
@ -464,19 +469,19 @@ struct GameOptionsWindow : Window {
|
|||
|
||||
void OnClick(Point pt, int widget, int click_count) override
|
||||
{
|
||||
if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (BaseGraphics::GetUsedSet() == nullptr) return;
|
||||
|
||||
ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
|
||||
return;
|
||||
}
|
||||
if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (BaseSounds::GetUsedSet() == nullptr) return;
|
||||
|
||||
ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
|
||||
return;
|
||||
}
|
||||
if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_END) {
|
||||
if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_CONTENT_END) {
|
||||
if (BaseMusic::GetUsedSet() == nullptr) return;
|
||||
|
||||
ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
|
||||
|
@ -489,6 +494,30 @@ struct GameOptionsWindow : Window {
|
|||
this->SetTab(widget);
|
||||
break;
|
||||
|
||||
case WID_GO_SURVEY_PARTICIPATE_BUTTON:
|
||||
switch (_settings_client.network.participate_survey) {
|
||||
case PS_ASK:
|
||||
case PS_NO:
|
||||
_settings_client.network.participate_survey = PS_YES;
|
||||
break;
|
||||
|
||||
case PS_YES:
|
||||
_settings_client.network.participate_survey = PS_NO;
|
||||
break;
|
||||
}
|
||||
|
||||
this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES);
|
||||
this->SetWidgetDirty(WID_GO_SURVEY_PARTICIPATE_BUTTON);
|
||||
break;
|
||||
|
||||
case WID_GO_SURVEY_LINK_BUTTON:
|
||||
OpenBrowser(NETWORK_SURVEY_DETAILS_LINK.c_str());
|
||||
break;
|
||||
|
||||
case WID_GO_SURVEY_PREVIEW_BUTTON:
|
||||
ShowSurveyResultTextfileWindow();
|
||||
break;
|
||||
|
||||
case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
|
||||
/* try to toggle full-screen on/off */
|
||||
if (!ToggleFullScreen(!_fullscreen)) {
|
||||
|
@ -686,6 +715,7 @@ struct GameOptionsWindow : Window {
|
|||
void OnInvalidateData(int data = 0, bool gui_scope = true) override
|
||||
{
|
||||
if (!gui_scope) return;
|
||||
this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES);
|
||||
this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
|
||||
this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
|
||||
this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
|
||||
|
@ -701,7 +731,7 @@ struct GameOptionsWindow : Window {
|
|||
bool missing_files = BaseGraphics::GetUsedSet()->GetNumMissing() == 0;
|
||||
this->GetWidget<NWidgetCore>(WID_GO_BASE_GRF_STATUS)->SetDataTip(missing_files ? STR_EMPTY : STR_GAME_OPTIONS_BASE_GRF_STATUS, STR_NULL);
|
||||
|
||||
for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
|
||||
for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
|
||||
this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->GetTextfile(tft).has_value());
|
||||
this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || !BaseSounds::GetUsedSet()->GetTextfile(tft).has_value());
|
||||
this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || !BaseMusic::GetUsedSet()->GetTextfile(tft).has_value());
|
||||
|
@ -739,6 +769,20 @@ static const NWidgetPart _nested_game_options_widgets[] = {
|
|||
NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
|
||||
NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_CURRENCY_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0),
|
||||
EndContainer(),
|
||||
|
||||
NWidget(NWID_SELECTION, INVALID_COLOUR, WID_GO_SURVEY_SEL),
|
||||
NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_FRAME, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
|
||||
NWidget(NWID_HORIZONTAL),
|
||||
NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY, STR_NULL),
|
||||
NWidget(NWID_SPACER), SetMinimalSize(1, 0), SetFill(1, 0),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PARTICIPATE_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_TOOLTIP),
|
||||
EndContainer(),
|
||||
NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(7, 0, 7),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PREVIEW_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW_TOOLTIP),
|
||||
NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_LINK_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK_TOOLTIP),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
EndContainer(),
|
||||
|
||||
/* Graphics tab */
|
||||
|
|
|
@ -63,13 +63,20 @@ enum IndustryDensity {
|
|||
ID_END, ///< Number of industry density settings.
|
||||
};
|
||||
|
||||
/** Possible values for "userelayservice" setting. */
|
||||
/** Possible values for "use_relay_service" setting. */
|
||||
enum UseRelayService {
|
||||
URS_NEVER = 0,
|
||||
URS_ASK,
|
||||
URS_ALLOW,
|
||||
};
|
||||
|
||||
/** Possible values for "participate_survey" setting. */
|
||||
enum ParticipateSurvey {
|
||||
PS_ASK = 0,
|
||||
PS_NO,
|
||||
PS_YES,
|
||||
};
|
||||
|
||||
/** Settings related to the difficulty of the game */
|
||||
struct DifficultySettings {
|
||||
byte competitor_start_time; ///< Unused value, used to load old savegames.
|
||||
|
@ -306,7 +313,8 @@ struct NetworkSettings {
|
|||
bool reload_cfg; ///< reload the config file before restarting
|
||||
std::string last_joined; ///< Last joined server
|
||||
bool no_http_content_downloads; ///< do not do content downloads over HTTP
|
||||
UseRelayService use_relay_service; ///< Use relay service?
|
||||
UseRelayService use_relay_service; ///< Use relay service?
|
||||
ParticipateSurvey participate_survey; ///< Participate in the automated survey
|
||||
};
|
||||
|
||||
/** Settings related to the creation of games. */
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
[pre-amble]
|
||||
static constexpr std::initializer_list<const char*> _use_relay_service{"never", "ask", "allow"};
|
||||
static constexpr std::initializer_list<const char*> _participate_survey{"ask", "no", "yes"};
|
||||
|
||||
static const SettingVariant _network_private_settings_table[] = {
|
||||
[post-amble]
|
||||
|
@ -16,6 +17,7 @@ static const SettingVariant _network_private_settings_table[] = {
|
|||
SDTC_BOOL = SDTC_BOOL( $var, $flags, $def, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup),
|
||||
SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup),
|
||||
SDTC_SSTR = SDTC_SSTR( $var, $type, $flags, $def, $length, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup),
|
||||
SDTC_OMANY = SDTC_OMANY( $var, $type, $flags, $def, $max, $full, $str, $strhelp, $strval, $pre_cb, $post_cb, $from, $to, $cat, $extra, $startup),
|
||||
|
||||
[validation]
|
||||
SDTC_OMANY = static_assert($max <= MAX_$type, "Maximum value for $var exceeds storage size");
|
||||
|
@ -90,3 +92,12 @@ str = STR_CONFIG_SETTING_USE_RELAY_SERVICE
|
|||
strhelp = STR_CONFIG_SETTING_USE_RELAY_SERVICE_HELPTEXT
|
||||
strval = STR_CONFIG_SETTING_USE_RELAY_SERVICE_NEVER
|
||||
cat = SC_BASIC
|
||||
|
||||
[SDTC_OMANY]
|
||||
var = network.participate_survey
|
||||
type = SLE_UINT8
|
||||
flags = SF_NOT_IN_SAVE | SF_NO_NETWORK_SYNC
|
||||
def = PS_ASK
|
||||
min = PS_ASK
|
||||
max = PS_YES
|
||||
full = _participate_survey
|
||||
|
|
|
@ -366,8 +366,21 @@ static void Xunzip(byte **bufp, size_t *sizep)
|
|||
if (StrStartsWith(sv_buf, u8"\ufeff")) sv_buf.remove_prefix(3);
|
||||
|
||||
/* Replace any invalid characters with a question-mark. This copies the buf in the process. */
|
||||
this->text = StrMakeValid(sv_buf, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE | SVS_REPLACE_TAB_CR_NL_WITH_SPACE);
|
||||
this->LoadText(sv_buf);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a text into the textfile viewer.
|
||||
*
|
||||
* This will split the text into newlines and stores it for fast drawing.
|
||||
*
|
||||
* @param buf The text to load.
|
||||
*/
|
||||
void TextfileWindow::LoadText(std::string_view buf)
|
||||
{
|
||||
this->text = StrMakeValid(buf, SVS_REPLACE_WITH_QUESTION_MARK | SVS_ALLOW_NEWLINE | SVS_REPLACE_TAB_CR_NL_WITH_SPACE);
|
||||
this->lines.clear();
|
||||
|
||||
/* Split the string on newlines. */
|
||||
std::string_view p(this->text);
|
||||
|
@ -406,7 +419,7 @@ std::optional<std::string> GetTextfile(TextfileType type, Subdirectory dir, cons
|
|||
"changelog",
|
||||
"license",
|
||||
};
|
||||
static_assert(lengthof(prefixes) == TFT_END);
|
||||
static_assert(lengthof(prefixes) == TFT_CONTENT_END);
|
||||
|
||||
std::string_view prefix = prefixes[type];
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ struct TextfileWindow : public Window, MissingGlyphSearcher {
|
|||
|
||||
virtual void LoadTextfile(const std::string &textfile, Subdirectory dir);
|
||||
|
||||
protected:
|
||||
void LoadText(std::string_view buf);
|
||||
|
||||
private:
|
||||
struct Line {
|
||||
int top; ///< Top scroll position.
|
||||
|
|
|
@ -12,13 +12,15 @@
|
|||
|
||||
/** Additional text files accompanying Tar archives */
|
||||
enum TextfileType {
|
||||
TFT_BEGIN,
|
||||
TFT_CONTENT_BEGIN,
|
||||
|
||||
TFT_README = TFT_BEGIN, ///< NewGRF readme
|
||||
TFT_CHANGELOG, ///< NewGRF changelog
|
||||
TFT_LICENSE, ///< NewGRF license
|
||||
TFT_README = TFT_CONTENT_BEGIN, ///< Content readme
|
||||
TFT_CHANGELOG, ///< Content changelog
|
||||
TFT_LICENSE, ///< Content license
|
||||
|
||||
TFT_END,
|
||||
TFT_CONTENT_END, // This marker is used to generate the above three buttons in sequence by various of places in the code.
|
||||
|
||||
TFT_SURVEY_RESULT = TFT_CONTENT_END, ///< Survey result (preview)
|
||||
};
|
||||
DECLARE_POSTFIX_INCREMENT(TextfileType)
|
||||
|
||||
|
|
|
@ -1172,7 +1172,7 @@ NWidgetCore::NWidgetCore(WidgetType tp, Colours colour, uint fill_x, uint fill_y
|
|||
this->widget_data = widget_data;
|
||||
this->tool_tip = tool_tip;
|
||||
this->scrollbar_index = -1;
|
||||
this->text_colour = TC_BLACK;
|
||||
this->text_colour = tp == WWT_CAPTION ? TC_WHITE : TC_BLACK;
|
||||
this->text_size = FS_NORMAL;
|
||||
this->align = SA_CENTER;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ enum AIConfigWidgets {
|
|||
WID_AIC_CONFIGURE, ///< Change AI settings button.
|
||||
WID_AIC_CLOSE, ///< Close window button.
|
||||
WID_AIC_TEXTFILE, ///< Open AI readme, changelog (+1) or license (+2).
|
||||
WID_AIC_CONTENT_DOWNLOAD = WID_AIC_TEXTFILE + TFT_END, ///< Download content button.
|
||||
WID_AIC_CONTENT_DOWNLOAD = WID_AIC_TEXTFILE + TFT_CONTENT_END, ///< Download content button.
|
||||
};
|
||||
|
||||
#endif /* WIDGETS_AI_WIDGET_H */
|
||||
|
|
|
@ -20,7 +20,7 @@ enum GSConfigWidgets {
|
|||
WID_GSC_SCROLLBAR, ///< Scrollbar to scroll through the selected AIs.
|
||||
WID_GSC_CHANGE, ///< Select another Game Script button.
|
||||
WID_GSC_TEXTFILE, ///< Open GS readme, changelog (+1) or license (+2).
|
||||
WID_GSC_CONTENT_DOWNLOAD = WID_GSC_TEXTFILE + TFT_END, ///< Download content button.
|
||||
WID_GSC_CONTENT_DOWNLOAD = WID_GSC_TEXTFILE + TFT_CONTENT_END, ///< Download content button.
|
||||
WID_GSC_ACCEPT, ///< Accept ("Close") button
|
||||
WID_GSC_RESET, ///< Reset button.
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ enum NetworkContentListWidgets {
|
|||
WID_NCL_DETAILS, ///< Panel with content details.
|
||||
WID_NCL_TEXTFILE, ///< Open readme, changelog (+1) or license (+2) of a file in the content window.
|
||||
|
||||
WID_NCL_SELECT_ALL = WID_NCL_TEXTFILE + TFT_END, ///< 'Select all' button.
|
||||
WID_NCL_SELECT_ALL = WID_NCL_TEXTFILE + TFT_CONTENT_END, ///< 'Select all' button.
|
||||
WID_NCL_SELECT_UPDATE, ///< 'Select updates' button.
|
||||
WID_NCL_UNSELECT, ///< 'Unselect all' button.
|
||||
WID_NCL_OPEN_URL, ///< 'Open url' button.
|
||||
|
|
|
@ -119,4 +119,14 @@ enum NetworkAskRelayWidgets {
|
|||
WID_NAR_YES_ALWAYS, ///< "Yes, always" button.
|
||||
};
|
||||
|
||||
/** Widgets of the #NetworkAskSurveyWindow class. */
|
||||
enum NetworkAskSurveyWidgets {
|
||||
WID_NAS_CAPTION, ///< Caption of the window.
|
||||
WID_NAS_TEXT, ///< Text in the window.
|
||||
WID_NAS_PREVIEW, ///< "Preview" button.
|
||||
WID_NAS_LINK, ///< "Details & Privacy" button.
|
||||
WID_NAS_NO, ///< "No" button.
|
||||
WID_NAS_YES, ///< "Yes" button.
|
||||
};
|
||||
|
||||
#endif /* WIDGETS_NETWORK_WIDGET_H */
|
||||
|
|
|
@ -47,7 +47,7 @@ enum NewGRFStateWidgets {
|
|||
WID_NS_NEWGRF_INFO, ///< Panel for Info on selected NewGRF.
|
||||
WID_NS_OPEN_URL, ///< Open URL of NewGRF.
|
||||
WID_NS_NEWGRF_TEXTFILE, ///< Open NewGRF readme, changelog (+1) or license (+2).
|
||||
WID_NS_SET_PARAMETERS = WID_NS_NEWGRF_TEXTFILE + TFT_END, ///< Open Parameters Window for selected NewGRF for editing parameters.
|
||||
WID_NS_SET_PARAMETERS = WID_NS_NEWGRF_TEXTFILE + TFT_CONTENT_END, ///< Open Parameters Window for selected NewGRF for editing parameters.
|
||||
WID_NS_VIEW_PARAMETERS, ///< Open Parameters Window for selected NewGRF for viewing parameters.
|
||||
WID_NS_TOGGLE_PALETTE, ///< Toggle Palette of selected, active NewGRF.
|
||||
WID_NS_APPLY_CHANGES, ///< Apply changes to NewGRF config.
|
||||
|
|
|
@ -28,23 +28,27 @@ enum GameOptionsWidgets {
|
|||
WID_GO_BASE_GRF_DROPDOWN, ///< Use to select a base GRF.
|
||||
WID_GO_BASE_GRF_STATUS, ///< Info about missing files etc.
|
||||
WID_GO_BASE_GRF_TEXTFILE, ///< Open base GRF readme, changelog (+1) or license (+2).
|
||||
WID_GO_BASE_GRF_DESCRIPTION = WID_GO_BASE_GRF_TEXTFILE + TFT_END, ///< Description of selected base GRF.
|
||||
WID_GO_BASE_GRF_DESCRIPTION = WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base GRF.
|
||||
WID_GO_BASE_SFX_DROPDOWN, ///< Use to select a base SFX.
|
||||
WID_GO_TEXT_SFX_VOLUME, ///< Sound effects volume label.
|
||||
WID_GO_BASE_SFX_VOLUME, ///< Change sound effects volume.
|
||||
WID_GO_BASE_SFX_TEXTFILE, ///< Open base SFX readme, changelog (+1) or license (+2).
|
||||
WID_GO_BASE_SFX_DESCRIPTION = WID_GO_BASE_SFX_TEXTFILE + TFT_END, ///< Description of selected base SFX.
|
||||
WID_GO_BASE_SFX_DESCRIPTION = WID_GO_BASE_SFX_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base SFX.
|
||||
WID_GO_BASE_MUSIC_DROPDOWN, ///< Use to select a base music set.
|
||||
WID_GO_TEXT_MUSIC_VOLUME, ///< Music volume label.
|
||||
WID_GO_BASE_MUSIC_VOLUME, ///< Change music volume.
|
||||
WID_GO_BASE_MUSIC_JUKEBOX, ///< Open the jukebox.
|
||||
WID_GO_BASE_MUSIC_STATUS, ///< Info about corrupted files etc.
|
||||
WID_GO_BASE_MUSIC_TEXTFILE, ///< Open base music readme, changelog (+1) or license (+2).
|
||||
WID_GO_BASE_MUSIC_DESCRIPTION = WID_GO_BASE_MUSIC_TEXTFILE + TFT_END, ///< Description of selected base music set.
|
||||
WID_GO_BASE_MUSIC_DESCRIPTION = WID_GO_BASE_MUSIC_TEXTFILE + TFT_CONTENT_END, ///< Description of selected base music set.
|
||||
WID_GO_VIDEO_ACCEL_BUTTON, ///< Toggle for video acceleration.
|
||||
WID_GO_VIDEO_VSYNC_BUTTON, ///< Toggle for video vsync.
|
||||
WID_GO_REFRESH_RATE_DROPDOWN, ///< Dropdown for all available refresh rates.
|
||||
WID_GO_VIDEO_DRIVER_INFO, ///< Label showing details about the current video driver.
|
||||
WID_GO_SURVEY_SEL, ///< Selection to hide survey if no JSON library is compiled in.
|
||||
WID_GO_SURVEY_PARTICIPATE_BUTTON, ///< Toggle for participating in the automated survey.
|
||||
WID_GO_SURVEY_LINK_BUTTON, ///< Button to open browser to go to the survey website.
|
||||
WID_GO_SURVEY_PREVIEW_BUTTON, ///< Button to open a preview window with the survey results
|
||||
};
|
||||
|
||||
/** Widgets of the #GameSettingsWindow class. */
|
||||
|
|
|
@ -483,6 +483,12 @@ enum WindowClass {
|
|||
*/
|
||||
WC_NETWORK_ASK_RELAY,
|
||||
|
||||
/**
|
||||
* Network ask survey window; %Window numbers:
|
||||
* - 0 - #NetworkAskSurveyWidgets
|
||||
*/
|
||||
WC_NETWORK_ASK_SURVEY,
|
||||
|
||||
/**
|
||||
* Chatbox; %Window numbers:
|
||||
* - #DestType = #NetWorkChatWidgets
|
||||
|
|
Loading…
Reference in New Issue