diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 29f0b6bda5d..4f4a0005087 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -216,3 +216,64 @@ jobs: with: name: ddnet-${{ matrix.os }} path: release/artifacts + + build-android: + runs-on: ubuntu-24.04 + env: + CARGO_HTTP_MULTIPLEXING: false + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Prepare + run: | + sudo apt-get update -y + sudo apt-get install cmake ninja-build openjdk-21-jdk p7zip-full curl glslang-tools openssl + cargo install cargo-ndk + rustup target add armv7-linux-androideabi + rustup target add i686-linux-android + rustup target add aarch64-linux-android + rustup target add x86_64-linux-android + mkdir ~/Android + cd ~/Android + mkdir Sdk + cd Sdk + mkdir ndk + cd ndk + wget --quiet https://dl.google.com/android/repository/android-ndk-r26d-linux.zip + unzip android-ndk-r26d-linux.zip + rm android-ndk-r26d-linux.zip + cd ~/Android/Sdk + mkdir build-tools + cd build-tools + wget --quiet https://dl.google.com/android/repository/build-tools_r30.0.3-linux.zip + unzip build-tools_r30.0.3-linux.zip + rm build-tools_r30.0.3-linux.zip + mv android-11 30.0.3 + cd ~/Android/Sdk + mkdir cmdline-tools + cd cmdline-tools + wget --quiet https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip + unzip commandlinetools-linux-11076708_latest.zip + rm commandlinetools-linux-11076708_latest.zip + mv cmdline-tools latest + yes | latest/bin/sdkmanager --licenses + + - name: Build + env: + TW_KEY_NAME: /home/runner/DDNet.jks + TW_KEY_ALIAS: DDNet-Key + run: | + export TW_KEY_PW="$(openssl rand -base64 32)" + keytool -genkey -v -keystore "$TW_KEY_NAME" -keyalg RSA -keysize 2048 -validity 10000 -alias "$TW_KEY_ALIAS" -storepass "$TW_KEY_PW" -dname "CN=DDNet CI, OU=DDNet, O=DDNet" + mkdir build-android + scripts/android/cmake_android.sh all DDNet org.ddnet.client Release build-android + mkdir artifacts + mv build-android/DDNet.apk artifacts + + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: ddnet-android + path: artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index 21f37220720..30304acb3d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1133,11 +1133,10 @@ set(EXPECTED_DATA audio/wp_switch-02.wv audio/wp_switch-03.wv autoexec_server.cfg + background_noise.png blob.png censorlist.txt communityicons/none.png - console.png - console_bar.png countryflags/AD.png countryflags/AE.png countryflags/AF.png @@ -1409,7 +1408,6 @@ set(EXPECTED_DATA editor/automap/round_tiles.rules editor/automap/water.rules editor/automap/winter_main.rules - editor/background.png editor/checker.png editor/cursor.png editor/cursor_resize.png @@ -1848,6 +1846,7 @@ set(EXPECTED_DATA themes/winter.png themes/winter_day.map themes/winter_night.map + touch_controls.json wordlist.txt ) @@ -2425,6 +2424,8 @@ if(CLIENT) components/tclient/warlist.h components/tooltips.cpp components/tooltips.h + components/touch_controls.cpp + components/touch_controls.h components/voting.cpp components/voting.h gameclient.cpp diff --git a/data/background_noise.png b/data/background_noise.png new file mode 100644 index 00000000000..31af9896303 Binary files /dev/null and b/data/background_noise.png differ diff --git a/data/console.png b/data/console.png deleted file mode 100644 index d4d664715fc..00000000000 Binary files a/data/console.png and /dev/null differ diff --git a/data/console_bar.png b/data/console_bar.png deleted file mode 100644 index 546ce941973..00000000000 Binary files a/data/console_bar.png and /dev/null differ diff --git a/data/editor/background.png b/data/editor/background.png deleted file mode 100644 index 7d83990e6ef..00000000000 Binary files a/data/editor/background.png and /dev/null differ diff --git a/data/languages/arabic.txt b/data/languages/arabic.txt index 9bc3f350205..744c666e9f2 100644 --- a/data/languages/arabic.txt +++ b/data/languages/arabic.txt @@ -1348,7 +1348,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1367,7 +1367,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1505,6 +1511,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1748,7 +1828,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1769,6 +1852,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1933,6 +2019,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/azerbaijani.txt b/data/languages/azerbaijani.txt index b777b359d0d..72fe1c79549 100644 --- a/data/languages/azerbaijani.txt +++ b/data/languages/azerbaijani.txt @@ -7,6 +7,7 @@ # Gokturk 2024-08-29 23:39:00 # Gokturk 2024-09-29 19:24:00 # Gokturk 2024-11-04 11:35:00 +# Gokturk & seishiroon 2024-12-03 16:35:00 ##### /authors ##### ##### translated strings ##### @@ -1159,9 +1160,6 @@ Leak IP No server selected == Heç bir server seçilmədi -Online players (%d) -== Onlayn oyunçular (%d) - Online clanmates (%d) == Onlayn klandaşlar (%d) @@ -1178,9 +1176,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Bu klanı dostlarınızın siyahısından silmək üçün klikləyin. -None -== Yox - Are you sure that you want to remove the player '%s' from your friends list? == '%s' oyunçu dostlarınız siyahısından silmək istədiyinizə əminsiniz? @@ -1483,9 +1478,6 @@ Show local player's key presses Hook collision line == Qarmaq izi -Hook collision line width -== Qarmaq izinin qalınlığı - Hook collision line opacity == Kanca çizgisi opaklığı @@ -1942,4 +1934,108 @@ Eyes == Göz Some fonts could not be loaded. Check the local console for details. -== Bəzi yazı fontları yüklənə bilmir. Ətraflı məlumat üçün əsas konsolu yoxlayın +== Bəzi yazı fontları yüklənə bilmir. Ətraflı məlumat üçün əsas konsolu yoxlayın. + +Online friends (%d) +== Onlayn dostlar (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Aşağıya ad yazaraq və ya oyunçu siyahısından adlarına tıklayaraq dost əlavə edin. + +Add clanmates by entering their clan below and leaving the name blank. +== Klan yoldaşlarınızı klan adını aşağıya yazaraq və ad hissəsini boş buraxaraq əlavə edin. + +Offline friends and clanmates will appear here. +== Oflayn dostlar və klan yoldaşları burada görünəcək. + +Edit touch controls +== Toxunuş idarəetmələrini düzəlt + +Close +== Bağla + +Save changes +== Dəyişiklikləri yadda saxla + +Error saving touch controls +== Toxunuş idarəetmələri saxlanılarkən xəta baş verdi + +Could not save touch controls to file. See local console for details. +== Toxunuş idarəetmələrini fayllara yadda saxlamaq mümkün olmadı. Ətraflı məlumat üçün konsola baxın. + +Unsaved changes +== Yadda saxlanılmamış dəyişikliklər + +Discard changes +== Dəyişiklikləri ləğv et + +Are you sure that you want to discard the current changes to the touch controls? +== Toxunuş idarəetmələrindəki dəyişiklikləri ləğv etmək istədiyinizə əminsinizmi? + +Are you sure that you want to reset the touch controls to default? +== oxunuş idarəetmələrini varsayılan parametrlərə qaytarmaq istədiyinizə əminsinizmi? + +Import from clipboard +== Panodan içəri idxal et + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Toxunuş idarəetmələrini panodan idxal etmək istədiyinizə əminsinizmi? Bu, mövcud idarəetmələrin üzərinə yazılacaq. + +Export to clipboard +== Panoya ixrac et + +Direct touch input while ingame +== Oyun içində olarkən birbaşa toxunuş girişi + +[Direct touch input] +Disabled +== Deaktiv edilib + +[Direct touch input] +Active action +== Aktiv hərəkət + +[Direct touch input] +Aim +== Aim + +[Direct touch input] +Fire +== Atəş + +[Direct touch input] +Hook +== Qarmaq + +Direct touch input while spectating +== İzləmə rejimində olarkən birbaşa toxunuş girişi + +Error loading touch controls +== Toxunuş idarəetmələri yüklənərkən xəta baş verdi + +Could not load touch controls from file. See local console for details. +== Toxunuş idarəetmələrini fayllardan yükləmək mümkün olmadı. Ətraflı məlumat üçün konsola baxın. + +Could not load default touch controls from file. See local console for details. +== Varsayılan toxunuş idarəetmələrini fayllardan yükləmək mümkün olmadı. Ətraflı məlumat üçün konsola baxın. + +Could not load touch controls from clipboard. See local console for details. +== Toxunuş idarəetmələrini panodan yükləmək mümkün olmadı. Ətraflı məlumat üçün konsola baxın. + +Width of your own hook collision line +== Öz qarmağınızın toqquşma xəttinin qalınlığı + +Width of others' hook collision line +== Başqalarının qarmaqlarının toqquşma xəttinin qalınlığı + +Preview 'Hook collisions' being pressed +== ‘Qarmaq toqquşmaları’ basılarkən önizləmə + +Aim +== Aim + +Active: Fire +== Aktiv: Atəş + +Active: Hook +== Aktiv: Qarmaq diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt index b54a463539f..6cb7a8bc800 100644 --- a/data/languages/belarusian.txt +++ b/data/languages/belarusian.txt @@ -1236,9 +1236,6 @@ Hook collision line Show other players' hook collision lines == Паказваць сутыкненні крука іншых гульцоў -Hook collision line width -== Шырыня лініі сутыкнення крука - Hook collision line opacity == Непразрыстасьць лініі сутыкнення крука @@ -1534,9 +1531,6 @@ A demo with this name already exists No server selected == Сервер не абраны -Online players (%d) -== Гульцоў анлайн (%d) - Online clanmates (%d) == Членаў клана анлайн (%d) @@ -1553,9 +1547,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Націсніце, как выдаліць гэты клан са свайго спіса сяброў. -None -== Пуста - Add Clan == Дадаць клан @@ -1894,12 +1885,98 @@ Unable to save the skin with a reserved name No local servers found (ports %d-%d) == +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Dummy is not allowed on this server == Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + [Hertz] Hz == @@ -1907,6 +1984,15 @@ Hz Show client IDs (scoreboard, chat, spectator) == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Basic == @@ -1945,3 +2031,12 @@ Feet [skins] Eyes == + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/bosnian.txt b/data/languages/bosnian.txt index e68a22d938e..dcc4037608a 100644 --- a/data/languages/bosnian.txt +++ b/data/languages/bosnian.txt @@ -1246,7 +1246,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1265,7 +1265,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1412,6 +1418,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1697,7 +1777,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1718,6 +1801,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1940,6 +2026,15 @@ Spree Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/brazilian_portuguese.txt b/data/languages/brazilian_portuguese.txt index 4139e87525a..4c737883940 100644 --- a/data/languages/brazilian_portuguese.txt +++ b/data/languages/brazilian_portuguese.txt @@ -1382,9 +1382,6 @@ Show jumps indicator Hook collision line == Linha de colisão do gancho -Hook collision line width -== Largura da linha de colisão do gancho - Hook collision line opacity == Opacidade da linha de colisão do gancho @@ -1580,9 +1577,6 @@ A demo with this name already exists No server selected == Nenhum serviço selecionado -Online players (%d) -== Jogadores online (%d) - Online clanmates (%d) == Colegas de clã online (%d) @@ -1599,9 +1593,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Clique para remover este clã a partir de sua lista de amigos. -None -== Ninguém - Add Clan == Adicionar clã @@ -1974,3 +1965,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == Algumas fontes não puderam ser carregadas. Verifique o console local para detalhes. + +Online friends (%d) +== Amigos online (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Adicione amigos digitando o nome deles abaixo ou clicando no nome deles na lista de jogadores. + +Add clanmates by entering their clan below and leaving the name blank. +== Adicione companheiros de clã inserindo o clã deles abaixo e deixando o nome em branco. + +Offline friends and clanmates will appear here. +== Amigos offline e companheiros de clã aparecerão aqui. + +Edit touch controls +== Editar controles de toque + +Close +== Fechar + +Save changes +== Salvar alterações + +Error saving touch controls +== Erro ao salvar controles de toque + +Could not save touch controls to file. See local console for details. +== Não foi possível salvar os controles de toque no arquivo. Veja o console local para detalhes. + +Unsaved changes +== Alterações não salvas + +Discard changes +== Descartar alterações + +Are you sure that you want to discard the current changes to the touch controls? +== Tem certeza de que deseja descartar as alterações atuais nos controles de toque? + +Are you sure that you want to reset the touch controls to default? +== Tem certeza de que deseja redefinir os controles de toque para o padrão? + +Import from clipboard +== Importar da área de transferência + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Tem certeza de que deseja importar os controles de toque da área de transferência? Isso substituirá seus controles de toque atuais. + +Export to clipboard +== Exportar para área de transferência + +Direct touch input while ingame +== Entrada de toque direto durante o jogo + +[Direct touch input] +Disabled +== Desabilitada + +[Direct touch input] +Active action +== Ação ativa + +[Direct touch input] +Aim +== Mira + +[Direct touch input] +Fire +== Atirar + +[Direct touch input] +Hook +== Gancho + +Direct touch input while spectating +== Entrada de toque direto durante observação + +Error loading touch controls +== Erro ao carregar controles de toque + +Could not load touch controls from file. See local console for details. +== Não foi possível carregar controles de toque do arquivo. Veja o console local para detalhes. + +Could not load default touch controls from file. See local console for details. +== Não foi possível carregar controles de toque padrão do arquivo. Veja o console local para detalhes. + +Could not load touch controls from clipboard. See local console for details. +== Não foi possível carregar controles de toque da área de transferência. Veja o console local para detalhes. + +Width of your own hook collision line +== Largura de sua própria linha de colisão do gancho + +Width of others' hook collision line +== Largura de linha de colisão do gancho dos outros + +Preview 'Hook collisions' being pressed +== Prévia de 'colisões de gancho' sendo pressionada + +Aim +== Mira + +Active: Fire +== Ativo: atirar + +Active: Hook +== Ativo: Gancho diff --git a/data/languages/bulgarian.txt b/data/languages/bulgarian.txt index 7058e24f937..fcffffdbc78 100644 --- a/data/languages/bulgarian.txt +++ b/data/languages/bulgarian.txt @@ -928,7 +928,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -947,7 +947,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1139,6 +1145,80 @@ Kill Pause == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1589,7 +1669,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1610,6 +1693,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show kill messages == @@ -1928,6 +2014,15 @@ Best Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + 1 new mention == diff --git a/data/languages/catalan.txt b/data/languages/catalan.txt index 1fe4ce1162d..cc2acb78f1f 100644 --- a/data/languages/catalan.txt +++ b/data/languages/catalan.txt @@ -1411,7 +1411,7 @@ Copy info No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1430,7 +1430,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1568,6 +1574,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1769,7 +1849,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1790,6 +1873,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1930,6 +2016,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/chuvash.txt b/data/languages/chuvash.txt index c525505dbc1..ea60ba0661a 100644 --- a/data/languages/chuvash.txt +++ b/data/languages/chuvash.txt @@ -931,7 +931,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -950,7 +950,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1142,6 +1148,80 @@ Kill Pause == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1589,7 +1669,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1610,6 +1693,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show kill messages == @@ -1928,6 +2014,15 @@ Best Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + 1 new mention == diff --git a/data/languages/czech.txt b/data/languages/czech.txt index 1d250ac6dec..670746c328d 100644 --- a/data/languages/czech.txt +++ b/data/languages/czech.txt @@ -1316,9 +1316,6 @@ Leak IP No server selected == Není vybrán žádný server -Online players (%d) -== Online hráči (%d) - Online clanmates (%d) == Online členové klanu (%d) @@ -1335,9 +1332,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Kliknutím odeberete tento klan ze seznamu přátel. -None -== Žádný - Are you sure that you want to remove the player '%s' from your friends list? == Opravdu chcete odstranit hráče '%s' ze seznamu přátel? @@ -1673,9 +1667,6 @@ Same clan color in scoreboard Hook collision line == Čára kolize háku -Hook collision line width -== Šířka čáry kolize háku - Hook collision line opacity == Neprůhlednost čáry kolize háku @@ -1946,3 +1937,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/danish.txt b/data/languages/danish.txt index 86f092c08a9..fc989d26c81 100644 --- a/data/languages/danish.txt +++ b/data/languages/danish.txt @@ -1356,7 +1356,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1375,7 +1375,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1513,6 +1519,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1756,7 +1836,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1777,6 +1860,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1938,6 +2024,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/dutch.txt b/data/languages/dutch.txt index cf3c53a80ea..410adbedee5 100644 --- a/data/languages/dutch.txt +++ b/data/languages/dutch.txt @@ -1454,7 +1454,7 @@ Copy info No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1473,7 +1473,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1611,6 +1617,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1806,7 +1886,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1827,6 +1910,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1949,6 +2035,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/esperanto.txt b/data/languages/esperanto.txt index 2419b375614..13df65b9f95 100644 --- a/data/languages/esperanto.txt +++ b/data/languages/esperanto.txt @@ -881,7 +881,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -900,7 +900,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1089,6 +1095,89 @@ Stop record Record demo == +Edit touch controls +== + +Close +== + +Remote console +== + +Console +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Reset to defaults +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Score limit == @@ -1291,12 +1380,6 @@ Spectate next Spectate previous == -Console -== - -Remote console -== - Screenshot == @@ -1362,9 +1445,6 @@ UI mouse sens. Movement == -Reset to defaults -== - Reset controls == @@ -1623,7 +1703,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1641,6 +1724,9 @@ Nothing hookable Something hookable == +Preview 'Hook collisions' being pressed +== + Show kill messages == @@ -1928,6 +2014,15 @@ Best Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/estonian.txt b/data/languages/estonian.txt index 429e4b09237..a4a0c2afd50 100644 --- a/data/languages/estonian.txt +++ b/data/languages/estonian.txt @@ -1269,9 +1269,6 @@ Copy info No server selected == Serverit pole valitud -Online players (%d) -== Võrgus olevaid mängijaid (%d) - Online clanmates (%d) == Võrgus olevaid klannikaaslasi (%d) @@ -1288,9 +1285,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Klõpsake, et eemaldada see klann oma sõbranimekirjast. -None -== Mitte ühtegi - Are you sure that you want to remove the player '%s' from your friends list? == Kas olete kindel, et soovite eemaldada mängija '%s' oma sõbranimekirjast? @@ -1641,9 +1635,6 @@ Same clan color in scoreboard Hook collision line == Konksu kokkupõrkejoon -Hook collision line width -== Konksu kokkupõrkejoone laius - Hook collision line opacity == Konksu kokkupõrkejoone läbipaistvus @@ -1890,12 +1881,98 @@ Unable to save the skin with a reserved name No local servers found (ports %d-%d) == +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Dummy is not allowed on this server == Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + [Hertz] Hz == @@ -1903,6 +1980,15 @@ Hz Show client IDs (scoreboard, chat, spectator) == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Basic == @@ -1942,5 +2028,14 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + https://wiki.ddnet.org/wiki/Mapping == diff --git a/data/languages/finnish.txt b/data/languages/finnish.txt index b4412c8e1c3..3ac49e14156 100644 --- a/data/languages/finnish.txt +++ b/data/languages/finnish.txt @@ -1271,9 +1271,6 @@ Copy info No server selected == Palvelinta ei ole valittu -Online players (%d) -== Online pelaajat (%d) - Online clanmates (%d) == Online klaanikaverit (%d) @@ -1290,9 +1287,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Klikkaa poistaaksesi tämän klaanin ystävälistaltasi. -None -== Ei mitään - Are you sure that you want to remove the player '%s' from your friends list? == Oletko varma että haluat poistaa pelaajan '%s' ystävälistaltasi? @@ -1670,9 +1664,6 @@ Same clan color in scoreboard Hook collision line == Koukku törmäysviiva -Hook collision line width -== Koukku törmäysviivan paksuus - Hook collision line opacity == Koukku törmäysviivan läpinäkyvyys @@ -1860,12 +1851,98 @@ No local servers found (ports %d-%d) Example of usage == +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Dummy is not allowed on this server == Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + [Hertz] Hz == @@ -1882,6 +1959,15 @@ Show client IDs (scoreboard, chat, spectator) Show only chat messages from team members == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Basic == @@ -1941,5 +2027,14 @@ Spree Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + https://wiki.ddnet.org/wiki/Mapping == diff --git a/data/languages/french.txt b/data/languages/french.txt index 28c148f81c9..71602767201 100644 --- a/data/languages/french.txt +++ b/data/languages/french.txt @@ -27,6 +27,7 @@ # Nouaa 2022-10-25 18:00:00 # Sukya 2023-05-20 19:38:00 # Emilcha 2024-06-09 11:31:00 +# Yubel 2024-12-04 16:17:00 ##### /authors ##### @@ -66,7 +67,7 @@ All == Tout le monde Are you sure that you want to quit? -== Êtes-vous sûrs de vouloir quitter ? +== Êtes-vous sûr de vouloir quitter ? Automatically record demos == Enregistrer les démos automatiquement @@ -153,7 +154,7 @@ Error == Erreur Error loading demo -== Erreur pendant le chargement de la démo +== Erreur lors du chargement de la démo Favorite == Favori @@ -605,7 +606,7 @@ AntiPing: predict other players == AntiPing: prédit le déplacement des autres joueurs Are you sure that you want to disconnect your dummy? -== Êtes vous sûrs de vouloir déconnecter votre dummy ? +== Êtes-vous sûr de vouloir déconnecter votre dummy ? Server best: == Meilleur score du serveur @@ -647,7 +648,7 @@ Show text entities == Afficher le texte des entités Connecting dummy -== Connection du dummy +== Connexion du dummy Render demo == Convertir une démo @@ -683,13 +684,13 @@ System message == Message du système Are you sure that you want to disconnect? -== Êtes-vous sûrs de vouloir vous déconnecter ? +== Êtes-vous sûr de vouloir vous déconnecter ? Deactivate == Désactiver Update failed! Check log… -== La mise à jour a échoué ! Vérifier les logs… +== La mise à jour a échouée ! Vérifiez les logs… %.2f KiB == %.2f Kio @@ -734,7 +735,7 @@ Show only chat messages from friends == Ne montrer que les messages des amis Remove chat -== Desactiver le chat +== Désactiver le chat Pause == Pause @@ -764,7 +765,7 @@ Exclude == Exclure Disconnect Dummy -== Deconnecter le dummy +== Déconnecter le dummy Show clan above name plates == Afficher le clan au dessus du pseudonyme @@ -1140,7 +1141,7 @@ https://ddnet.org/discord == https://ddnet.org/discord Are you sure that you want to disconnect and switch to a different server? -== Êtes vous sur de vouloir vous déconnecter et changer de serveur? +== Êtes-vous sûr de vouloir vous déconnecter et changer de serveur ? Show local player's key presses == Montrer les touches appuyées des autres joueurs @@ -1158,7 +1159,7 @@ Run on join == Exécuter en rejoignant Chat command (e.g. showall 1) -== Commande du chat (ex: showall 1) +== Commande du chat (ex : showall 1) The format of texture %s is not RGBA which will cause visual bugs. == La texture %s n'est pas au format RGBA, ce qui causera des bugs visuels. @@ -1188,13 +1189,13 @@ auto == Automatique When you cross the start line, show a ghost tee replicating the movements of your best time -== Lorsque vous franchissez la ligne de départ, montrez un fantôme reproduisant les mouvements de votre meilleur temps +== Lorsque vous franchissez la ligne de départ, montre un fantôme reproduisant les mouvements de votre meilleur temps Opacity == Opacité Adjust the opacity of entities belonging to other teams, such as tees and nameplates -== Ajustez l'opacité des entités appartenant à d'autres équipes, telles que les tees et les noms des joueurs +== Ajustez l'opacité des entités appartenant à d'autres équipes, telle que les tees et les noms des joueurs Quads are used for background decoration == Les quads sont utilisés pour la décoration de l'arrière-plan @@ -1215,16 +1216,16 @@ Team %d == Équipe %d Position: -== Position: +== Position : Speed: -== Vitesse: +== Vitesse : Angle: -== Angle: +== Angle : Trying to determine UDP connectivity… -== Tentative de connection UDP… +== Tentative de connexion UDP… UDP seems to be filtered. == UDP semble filtré. @@ -1384,9 +1385,6 @@ Name Plate Hook Collisions == Collisions du grappin -Hook collision line width -== Largeur de la ligne de collision du grappin - Hook collision line opacity == Opacité de la ligne de collision du grappin @@ -1438,11 +1436,11 @@ Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in s [Graphics error] Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. -== Mémoire graphique insuffisante. Essayez de retirer des ressources personnalisées (skins, entités, etc.), spécialement celles de haute résolution. +== Mémoire graphique insuffisante. Essayez de retirer des ressources personnalisées (skins, entités, etc.), en particulier celles de haute résolution. [Graphics error] An error during command recording occurred. Try to update your GPU drivers. -== Une erreur pendant l'enregistrement des commandes est survenue. Essayez de mettre à jour vos pilotes GPU. +== Une erreur est survenue pendant l'enregistrement des commandes. Essayez de mettre à jour vos pilotes GPU. [Graphics error] A render command failed. Try to update your GPU drivers. @@ -1471,10 +1469,10 @@ File '%s' already exists, do you want to overwrite it? == Le fichier '%s' existe déjà, voulez-vous l'écraser ? Are you sure that you want to remove the player '%s' from your friends list? -== Êtes-vous sûrs de vouloir retirer le joueur '%s' de votre liste d'amis ? +== Êtes-vous sûr de vouloir retirer le joueur '%s' de votre liste d'amis ? Are you sure that you want to remove the clan '%s' from your friends list? -== Êtes-vous sûrs de vouloir retirer le clan '%s' de votre liste d'amis ? +== Êtes-vous sûr de vouloir retirer le clan '%s' de votre liste d'amis ? Go back one tick == Reculer d'une image @@ -1492,7 +1490,7 @@ Open the directory that contains the demo files == Ouvrir le dossier contenant les fichiers démo Are you sure that you want to delete the demo '%s'? -== Êtes-vous sûrs de vouloir supprimer la démo '%s' ? +== Êtes-vous sûr de vouloir supprimer la démo '%s' ? Unable to delete the demo '%s' == Impossible de supprimer la démo '%s' @@ -1504,7 +1502,7 @@ Open the settings file == Ouvrir le fichier de configuration Open the directory that contains the configuration and user files -== Ouvrir le dossier de configuration et des fichiers personnalisés +== Ouvrir le dossier contenant les fichiers de configuration et d'utilisateurs Open the directory to add custom themes == Ouvrir le dossier pour ajouter un thème personnalisé @@ -1519,7 +1517,7 @@ Reset controls == Réinitialiser les contrôles Are you sure that you want to reset the controls to their defaults? -== Êtes-vous sûrs de vouloir réinitialiser les contrôles aux valeurs par défaut ? +== Êtes-vous sûr de vouloir réinitialiser les contrôles aux valeurs par défaut ? Rifle Laser Outline Color == Couleur fusil laser (contour) @@ -1556,17 +1554,14 @@ Open the directory to add custom assets [Graphics error] Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. -== Impossible d'initialiser l'interface graphique, tu n'as probablement pas installé les pilotes de ta carte graphique. +== Impossible d'initialiser l'interface graphique, vous n'avez probablement pas installé les pilotes de votre carte graphique. Could not save downloaded map. Try manually deleting this file: %s -== Impossible de sauvegarder la carte téléchargé. Essayez de supprimer ce fichier manuellement: %s +== Impossible de sauvegarder la carte téléchargée. Essayez de supprimer ce fichier manuellement: %s Copy info == Copier les infos -Online players (%d) -== Joueurs en ligne (%d) - Online clanmates (%d) == Membres du clan en ligne (%d) @@ -1583,11 +1578,8 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Cliquez pour retirer ce clan de votre liste d'amis -None -== Aucuns - Add Clan -== Ajouter clan +== Ajouter un clan Create a random skin == Créer un skin aléatoire @@ -1599,7 +1591,7 @@ Failed saving the replay! == Erreur durant la sauvegarde du replay ! Saving settings to '%s' failed -== La sauvegarde des paramètres vers '%s' à échoué +== La sauvegarde des paramètres vers '%s' a échouée Error saving settings == Erreur de sauvegarde des paramètres @@ -1641,7 +1633,7 @@ Following == Suivi Loading commands… -== Chargement des commandes +== Chargement des commandes… Multi-View == Multi-vues @@ -1677,7 +1669,7 @@ Continue anyway? == Continuer quand même ? A demo with this name already exists -== Une demo avec ce nom existe déjà +== Une démo avec ce nom existe déjà A folder with this name already exists == Un dossier avec ce nom existe déjà @@ -1788,10 +1780,10 @@ Ghosts directory == Dossier des fantômes Activate all -== Tous activer +== Tout activer Deactivate all -== Tous désactiver +== Tout désactiver Player info change cooldown == Délai de changement des informations du joueur @@ -1866,104 +1858,208 @@ Moved ingame == Déplacé en jeu Could not resolve connect address '%s'. See local console for details. -== +== Impossible de résoudre l'adresse de connexion '%s'. Consultez la console locale pour plus de détails. Connect address error -== +== Erreur d'adresse de connexion Could not connect dummy -== +== Impossible de connecter le dummy Some fonts could not be loaded. Check the local console for details. -== +== Certaines polices n'ont pas pu être chargées. Consultez la console locale pour plus de détails. [Spectating] Following %s -== +== Suivi de %s Save skin -== +== Enregistrer le skin Are you sure you want to save your skin? If a skin with this name already exists, it will be replaced. -== +== Êtes-vous sûr de vouloir enregistrer votre skin ? Si un skin déjà existant possède ce nom, il sera remplacé. Unable to save the skin -== +== Impossible d'enregistrer le skin Unable to save the skin with a reserved name -== +== Impossible d'enregistrer le skin avec un nom réservé No local servers found (ports %d-%d) -== +== Aucun serveur local trouvé (ports %d-%d) Example of usage -== +== Exemple d'utilisation + +Online friends (%d) +== Amis connectés (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Ajoutez des amis en entrant leurs noms ci-dessous ou en cliquant sur leurs noms dans la liste des joueurs. + +Add clanmates by entering their clan below and leaving the name blank. +== Ajoutez des membres du clan en entrant le nom de clan ci-dessous et en laissant le nom de joueur vide. + +Offline friends and clanmates will appear here. +== Les amis hors-lignes et les membres du clan apparaîtront ici. Dummy is not allowed on this server -== +== Le dummy n'est pas autorisé sur ce serveur Please wait… -== +== Veuillez patienter… + +Edit touch controls +== Modifier les contrôles tactiles + +Close +== Fermer + +Save changes +== Enregistrer les modifications + +Error saving touch controls +== Erreur lors de l'enregistrement des contrôles tactiles + +Could not save touch controls to file. See local console for details. +== Impossible d'enregistrer les contrôles tactiles dans le fichier. Consultez la console locale pour plus de détails. + +Unsaved changes +== Modifications non-enregistrées + +Discard changes +== Annuler les modifications + +Are you sure that you want to discard the current changes to the touch controls? +== Êtes-vous sûr de vouloir annuler les modifications actuelles des contrôles tactiles ? + +Are you sure that you want to reset the touch controls to default? +== Êtes-vous sûr de vouloir réinitialiser les contrôles tactiles aux valeurs par défaut ? + +Import from clipboard +== Importer depuis le presse-papiers + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Êtes-vous sûr de vouloir importer les contrôles tactiles depuis le presse-papiers ? Cela va remplacer les contrôles tactiles actuels. + +Export to clipboard +== Exporter au presse-papiers + +Direct touch input while ingame +== Entrée tactile directe en jeu + +[Direct touch input] +Disabled +== Désactivée + +[Direct touch input] +Active action +== Action active + +[Direct touch input] +Aim +== Viser + +[Direct touch input] +Fire +== Tirer + +[Direct touch input] +Hook +== Grappin + +Direct touch input while spectating +== Entrée tactile directe en mode spec. + +Error loading touch controls +== Erreur lors du chargement des contrôles tactiles + +Could not load touch controls from file. See local console for details. +== Impossible de charger les contrôles tactiles depuis le fichier. Consultez la console locale pour plus de détails. + +Could not load default touch controls from file. See local console for details. +== Impossible de charger les contrôles tactiles par défaut depuis le fichier. Consultez la console locale pour plus de détails. + +Could not load touch controls from clipboard. See local console for details. +== Impossible de charger les contrôles tactiles depuis le presse-papiers. Consultez la console locale pour plus de détails. [Hertz] Hz -== +== Hz Show client IDs (scoreboard, chat, spectator) -== +== Afficher les IDs des clients (tableau des scores, chat, spectateur) + +Width of your own hook collision line +== Largeur de la ligne de collision de votre grappin + +Width of others' hook collision line +== Largeur de la ligne de collision du grappin des autres joueurs + +Preview 'Hook collisions' being pressed +== Prévisualiser lorsque 'Collision du grappin' est utilisée Basic -== +== Basique Custom -== +== Personnalisé Are you sure that you want to delete '%s'? -== +== Êtes-vous sûr de vouloir supprimer '%s' ? Delete skin -== +== Supprimer le skin Unable to delete skin -== +== Impossible de supprimer le skin Round %d/%d -== +== Manche %d/%d [Spectators] %d others… -== +== %d autres… [Team and size] %d\n(%d/%d) -== +== %d\n(%d/%d) Team %d (%d/%d) -== +== Équipe %d (%d/%d) [skins] Body -== +== Corps [skins] Marking -== +== Marquage [skins] Decoration -== +== Décoration [skins] Hands -== +== Mains [skins] Feet -== +== Pieds [skins] Eyes -== +== Yeux + +Aim +== Viser + +Active: Fire +== Actif : Tirer + +Active: Hook +== Actif : Grappin https://wiki.ddnet.org/wiki/Mapping -== +== https://wiki.ddnet.org/wiki/Mapping diff --git a/data/languages/galician.txt b/data/languages/galician.txt index d6eb655f1d8..a667fa3494f 100644 --- a/data/languages/galician.txt +++ b/data/languages/galician.txt @@ -1361,9 +1361,6 @@ Show jumps indicator Hook collision line == Liña de colisión do gancho -Hook collision line width -== Ancho da liña de colisión do gancho - Hook collision line opacity == Opacidade da liña de colisión do gancho @@ -1529,9 +1526,6 @@ Copy info Create a random skin == Crear skin aleatoria -Online players (%d) -== Xogadores en línea (%d) - Online clanmates (%d) == Compañeiro de clan en liña (%d) @@ -1548,9 +1542,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Haz clic para quitar este clan da túa lista de amigos. -None -== Ningún - Add Clan == Agregar clan @@ -1710,6 +1701,18 @@ Communities No server selected == +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Server filter == @@ -1791,6 +1794,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1864,6 +1941,15 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1935,6 +2021,15 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/german.txt b/data/languages/german.txt index 5fbea807add..2af1614650e 100644 --- a/data/languages/german.txt +++ b/data/languages/german.txt @@ -90,7 +90,7 @@ Client == Client Colors of the hook collision line, in case of a possible collision with: -== Die Farbe der Hakenkollisionlinie bei einer möglichen Kollision mit: +== Die Farbe der Hakenkollisionslinie bei einer möglichen Kollision mit: Connecting to == Verbinden mit @@ -848,9 +848,6 @@ Hook collision line Hook collision line opacity == Deckkraft der Hakenkollisionslinie -Hook collision line width -== Breite der Hakenkollisionslinie - Fetch Info == Neu laden @@ -1559,9 +1556,6 @@ Open the settings file Create a random skin == Erstelle einen zufälligen Skin -Online players (%d) -== Online-Spieler (%d) - Online clanmates (%d) == Online-Clanmitglieder (%d) @@ -1578,9 +1572,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Klicke um diesen Clan aus deiner Freundesliste zu entfernen. -None -== Keine - Add Clan == Clan hinzufügen @@ -1959,3 +1950,107 @@ https://wiki.ddnet.org/wiki/Mapping Some fonts could not be loaded. Check the local console for details. == Einige Schriften konnten nicht geladen werden. Prüf die lokale Konsole für Details. + +Online friends (%d) +== Online-Freunde (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Freunde hinzufügen durch Eingabe des Namens oder durch Klicken auf ihren Namen in Spielerliste. + +Add clanmates by entering their clan below and leaving the name blank. +== Clanmitglieder hinzufügen durch Eingabe des Clans und Leerlassen des Namens. + +Offline friends and clanmates will appear here. +== Offline-Freunde und Clanmitglieder erscheinen hier. + +Edit touch controls +== Berührungseingaben bearbeiten + +Close +== Schließen + +Save changes +== Änderungen speichern + +Error saving touch controls +== Berührungseingaben konnten nicht gespeichert werden + +Could not save touch controls to file. See local console for details. +== Konnte Berührungseingaben nicht in Datei speichern. Siehe lokale Konsole für Details. + +Unsaved changes +== Ungespeicherte Änderungen + +Discard changes +== Änderungen verwerfen + +Are you sure that you want to discard the current changes to the touch controls? +== Willst du wirklich die aktuellen Änderungen zu Berührungseingaben verwerfen? + +Are you sure that you want to reset the touch controls to default? +== Willst du wirklich die Berührungseingaben zurücksetzen? + +Import from clipboard +== Aus Zwischenablage importieren + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Willst du wirklich Berührungseingaben aus der Zwischenablage importieren? Dies überschreibt deine aktuellen Berührungseingaben. + +Export to clipboard +== In Zwischenablage exportieren + +Direct touch input while ingame +== Direkte Berührungseingaben beim Spielen + +[Direct touch input] +Disabled +== Deaktiviert + +[Direct touch input] +Active action +== Aktive Aktion + +[Direct touch input] +Aim +== Zielen + +[Direct touch input] +Fire +== Feuern + +[Direct touch input] +Hook +== Haken + +Direct touch input while spectating +== Direkte Berührungseingaben beim Zuschauen + +Error loading touch controls +== Konnte Berührungssteuerung nicht laden + +Could not load touch controls from file. See local console for details. +== Konnte Berührungssteuerung nicht von Datei laden. Siehe Details in der lokalen Konsole. + +Could not load default touch controls from file. See local console for details. +== Konnte Standard-Berührungssteuerung nicht von Datei laden. Siehe Details in der lokalen Konsole. + +Could not load touch controls from clipboard. See local console for details. +== Konnte Berührungssteuerung nicht von Zwischenablage laden. Siehe Details in der lokalen Konsole. + +Width of your own hook collision line +== Breite der eigenen Hakenkollisionslinie + +Width of others' hook collision line +== Breite der Hakenkollisionslinie anderer + +Aim +== Zielen + +Active: Fire +== Aktiv: Feuern + +Active: Hook +== Aktiv: Haken + +Preview 'Hook collisions' being pressed +== Vorschau für 'Hakenkollisionen' gedrückt diff --git a/data/languages/greek.txt b/data/languages/greek.txt index cff01a43ebf..131e9131e17 100644 --- a/data/languages/greek.txt +++ b/data/languages/greek.txt @@ -934,7 +934,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -953,7 +953,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1145,6 +1151,80 @@ Kill Pause == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1589,7 +1669,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1610,6 +1693,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show kill messages == @@ -1928,6 +2014,15 @@ Best Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + 1 new mention == diff --git a/data/languages/hungarian.txt b/data/languages/hungarian.txt index f0a848f09da..b584323917d 100644 --- a/data/languages/hungarian.txt +++ b/data/languages/hungarian.txt @@ -1344,9 +1344,6 @@ Opacity of freeze bars inside freeze Hook collision line == Horog érintkező vonal indikátora -Hook collision line width -== Horog érintkező vonal szélessége - Hook collision line opacity == Horog érintkező vonal láthatósága @@ -1690,7 +1687,7 @@ Communities No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1709,7 +1706,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Add Clan @@ -1796,6 +1799,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1869,6 +1946,15 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1937,6 +2023,15 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/italian.txt b/data/languages/italian.txt index 42afef5ff91..f3db9483a50 100644 --- a/data/languages/italian.txt +++ b/data/languages/italian.txt @@ -1185,9 +1185,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Fai click per rimuovere questo clan dall'elenco dei tuoi amici. -None -== Nessuno - Are you sure that you want to remove the player '%s' from your friends list? == Sei sicuro di voler rimuovere il giocatore '%s' dalla tua lista amici? @@ -1300,9 +1297,6 @@ Show local player's key presses Hook collision line == Hook line collisione -Hook collision line width -== Larghezza hook line collisione - Hook collision line opacity == Opacità hook line di collisione @@ -1568,13 +1562,22 @@ Copy info Leak IP == -Online players (%d) +Online friends (%d) == [friends (server browser)] Offline (%d) == +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Server filter == @@ -1704,6 +1707,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1850,9 +1927,18 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1975,5 +2061,14 @@ Eyes FPM == +Aim +== + +Active: Fire +== + +Active: Hook +== + https://wiki.ddnet.org/wiki/Mapping == diff --git a/data/languages/japanese.txt b/data/languages/japanese.txt index 1af876f322b..092639bfea2 100644 --- a/data/languages/japanese.txt +++ b/data/languages/japanese.txt @@ -1382,7 +1382,7 @@ Copy info No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1401,7 +1401,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1539,6 +1545,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1764,7 +1844,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1785,6 +1868,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1937,6 +2023,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/korean.txt b/data/languages/korean.txt index 6c036f9682b..82a1809b780 100644 --- a/data/languages/korean.txt +++ b/data/languages/korean.txt @@ -1359,9 +1359,6 @@ Show jumps indicator Hook collision line == 갈고리 보조선 -Hook collision line width -== 갈고리 보조선 폭 - Hook collision line opacity == 갈고리 보조선 불투명도 @@ -1702,7 +1699,7 @@ Communities No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1721,7 +1718,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Add Clan @@ -1808,6 +1811,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1881,6 +1958,15 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1949,6 +2035,15 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/kyrgyz.txt b/data/languages/kyrgyz.txt index 1bb30df3645..ea457d86281 100644 --- a/data/languages/kyrgyz.txt +++ b/data/languages/kyrgyz.txt @@ -925,7 +925,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -944,7 +944,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1136,6 +1142,80 @@ Kill Pause == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1580,7 +1660,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1601,6 +1684,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show kill messages == @@ -1919,6 +2005,15 @@ Best Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + 1 new mention == diff --git a/data/languages/norwegian.txt b/data/languages/norwegian.txt index eea7f05af6b..d0f4cd4237a 100644 --- a/data/languages/norwegian.txt +++ b/data/languages/norwegian.txt @@ -1357,7 +1357,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1376,7 +1376,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1514,6 +1520,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1757,7 +1837,10 @@ Always show own player's hook collision line Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1778,6 +1861,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1939,6 +2025,15 @@ Eyes Loading sound files == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/persian.txt b/data/languages/persian.txt index bd254c2e8b9..1ba26366d63 100644 --- a/data/languages/persian.txt +++ b/data/languages/persian.txt @@ -1286,9 +1286,6 @@ Hook collision line Show other players' hook collision lines == ﺮﮕﯾﺩ ﻥﺎﻨﮑﯾﺯﺎﺑ ﺏﻼﻗ ﺩﺭﻮﺧﺮﺑ ﻁﻮﻄﺧ ﺶﯾﺎﻤﻧ -Hook collision line width -== ﺏﻼﻗ ﺩﺭﻮﺧﺮﺑ ﻂﺧ ﺽﺮﻋ - Hook collision line opacity == ﺏﻼﻗ ﺩﺭﻮﺧﺮﺑ ﻂﺧ ﺖﯿﻓﺎﻔﺷ @@ -1667,9 +1664,6 @@ Copy info No server selected == ﺖﺳﺍ ﻩﺪﺸﻧ ﺏﺎﺨﺘﻧﺍ ىﺭﻭﺮﺳ ﭻﯿﻫ -Online players (%d) -== (%d) ﻦﯾﻼﻧﺁ ﻥﺎﻨﻜﯾﺯﺎﺑ - Online clanmates (%d) == (%d) ﻦﯾﻼﻧﺁ ىﺎﻫ ﯽﻨﻠﻛ ﻢﻫ @@ -1686,9 +1680,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == .ﺪﯿﻨﻛ ﻚﯿﻠﻛ ﺩﻮﺧ ﻥﺎﺘﺳﻭﺩ ﺖﺴﯿﻟ ﺯﺍ ﻦﻠﻛ ﻦﯾﺍ ﻑﺬﺣ ىﺍﺮﺑ -None -== ﻡﺍﺪﻛ ﭻﯿﻫ - Add Clan == ﻦﻠﻛ ﻥﺩﺮﻛ ﻪﻓﺎﺿﺍ @@ -1941,3 +1932,107 @@ The width of texture %s is not divisible by %d, or the height is not divisible b Some fonts could not be loaded. Check the local console for details. == + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/polish.txt b/data/languages/polish.txt index 03a1e80fe26..e669e128e23 100644 --- a/data/languages/polish.txt +++ b/data/languages/polish.txt @@ -1386,9 +1386,6 @@ Copy info No server selected == Nie wybrano serwera -Online players (%d) -== Dostępni gracze (%d) - Online clanmates (%d) == Dostępni członkowie klanu (%d) @@ -1405,9 +1402,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Kliknij, aby usunąć ten klan z listy znajomych. -None -== Nikt - Are you sure that you want to remove the player '%s' from your friends list? == Jesteś pewny, że chcesz usunąć gracza '%s' z listy znajomych? @@ -1694,9 +1688,6 @@ Always show own player's hook collision line Always show other players' hook collision lines == Zawsze pokazuj linię kolizyjną haka innych -Hook collision line width -== Szerokość linii kolizji haka - Hook collision line opacity == Przezroczystość linii kolizji haka @@ -1948,3 +1939,107 @@ https://wiki.ddnet.org/wiki/Mapping Some fonts could not be loaded. Check the local console for details. == + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/portuguese.txt b/data/languages/portuguese.txt index 851d0809133..83919357ae2 100644 --- a/data/languages/portuguese.txt +++ b/data/languages/portuguese.txt @@ -1182,7 +1182,7 @@ Leak IP No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1201,7 +1201,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1363,6 +1369,80 @@ Kill Pause == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1675,7 +1755,10 @@ Show other players' hook collision lines Always show other players' hook collision lines == -Hook collision line width +Width of your own hook collision line +== + +Width of others' hook collision line == Hook collision line opacity @@ -1696,6 +1779,9 @@ Something hookable A Tee == +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1930,6 +2016,15 @@ Spree Grabs == +Aim +== + +Active: Fire +== + +Active: Hook +== + 1 new mention == diff --git a/data/languages/romanian.txt b/data/languages/romanian.txt index f62b920ad68..610cffe8388 100644 --- a/data/languages/romanian.txt +++ b/data/languages/romanian.txt @@ -9,18 +9,19 @@ # kneekoo 2011-07-01 18:40:49 # kneekoo 2011-07-05 23:34:34 # kneekoo 2012-05-01 02:01:47 +# Sans 2024-12-04 02:19:00 ##### /authors ##### ##### translated strings ##### %ds left -== %ds a ieșit +== %ds rămase %i minute left -== %i minute rămas +== %i minut rămas %i minutes left -== %i minutes rămase +== %i minute rămase %i second left == %i secundă rămasă @@ -47,7 +48,7 @@ All == Toți Are you sure that you want to quit? -== Sigur vrei să ieși? +== Ești sigur că vrei să ieși? Automatically record demos == Înregistrează automat demo-uri @@ -74,10 +75,10 @@ Chat == Chat Clan -== Clanul +== Clan Client -== Clientul +== Client Connecting to == Conectare la @@ -104,13 +105,13 @@ Delete == Șterge Delete demo -== Șterge demonstrația +== Șterge demo-ul Demofile: %s == Fișier demo: %s Demos -== Demo +== Demo-uri Disconnect == Deconectare @@ -128,7 +129,7 @@ Dynamic Camera == Cameră dinamică Emoticon -== Figurine +== Emoticon Error == Eroare @@ -158,7 +159,7 @@ Free-View == Vizualizare liberă Fullscreen -== Ecrat complet +== Ecran complet Game == Joc @@ -185,7 +186,7 @@ Graphics == Grafică Grenade -== Grenade +== Lansator de grenade Hammer == Ciocan @@ -203,10 +204,10 @@ Invalid Demo == Demo nevalid Join blue -== La albaștri +== Intră la albaștri Join red -== La roșii +== Intră la roșii Jump == Salt @@ -236,7 +237,7 @@ Movement == Mișcare Mute when not active -== Opreşte sunetul la inactivate +== Opreşte sunetul când nu este activ Name == Nume @@ -245,7 +246,7 @@ Next weapon == Arma următoare Nickname -== Pseudonim +== Poreclă No == Nu @@ -260,7 +261,7 @@ No servers match your filter criteria == Nici un server nu corespunde criteriilor Ok -== Bine +== Ok Parent Folder == Dosarul părinte @@ -314,13 +315,13 @@ Remove == Șterge Remove friend -== Șterge prietenul +== Șterge prieten Rename demo -== Redenumește demo-ul +== Redenumește demo Reset filter -== Filtru implicit +== Resetează filtrul Score == Scor @@ -329,7 +330,7 @@ Score limit == Limita de scor Scoreboard -== Scoruri +== Tabela de scor Screenshot == Captură de ecran @@ -341,25 +342,25 @@ Server info == Info. server Server not full -== Are locuri libere +== Server-ul nu e plin Shotgun -== Pușcă +== Pușcă de vânatoare Show chat == Afișare chat Show friends only -== Arată prietenii +== Arată doar prietenii Show ingame HUD -== Arată scorul în joc +== Afișează interfața din joc Show name plates -== Arată pseudonimele +== Arată poreclele Show only chat messages from friends -== Arată doar chatul prietenilor +== Arată doar mesaje de la prieteni în chat Sound == Sunet @@ -404,7 +405,7 @@ The audio device couldn't be initialised. == Dispozitivul audio nu a putut fi inițializat. The server is running a non-standard tuning on a pure game type. -== Serverul folosește parametri ne-standard într-un joc standard. +== Serverul folosește parametri non-standard într-un joc standard. Time limit == Timp limită @@ -419,13 +420,13 @@ Type == Tip Unable to rename the demo -== Nu pot redenumi demo-ul +== Nu se poate redenumi demo-ul Use sounds -== Folosește sunetul +== Folosește sunete Use team colors for name plates -== Folosește culorile echipelor pe etichetele de nume +== Folosește culorile echipelor pentru etichetele de nume V-Sync == Sincronizare verticală (V-Sync) @@ -466,7 +467,7 @@ New name: == Nume nou: Sat. -== Saturație +== Sat. Miscellaneous == Diverse @@ -484,7 +485,7 @@ Join game == Intră în joc FSAA samples -== Eșantioane FSAA +== Mostre FSAA Sound volume == Volum sunet @@ -493,10 +494,10 @@ Max Screenshots == Număr maxim de capturi de ecran Laser -== Carabină +== Laser Hue -== Tentă +== Nuanță Record demo == Înreg. demo @@ -520,1431 +521,1526 @@ LAN == Rețea Name plates size -== Dimensiune nume placă +== Dimensiune plăcuțe de nume [Graphics error] Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== +== Eșuat în timpul inițializării. Încercați să schimbați gfx_backend la OpenGL sau Vulkan în settings_ddnet.cfg din dosarul de configurare și încercați din nou. [Graphics error] Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. -== +== Fără memorie video (VRAM). Încercați să eliminați resursele personalizate (costume, entități etc.), în special cele cu rezoluție mare. [Graphics error] An error during command recording occurred. Try to update your GPU drivers. -== +== A apărut o eroare în timpul înregistrării comenzii. Încercați să vă actualizați driverele plăcii grafice (GPU). [Graphics error] A render command failed. Try to update your GPU drivers. -== +== O comandă de randare a eșuat. Încercați să vă actualizați driverele plăcii grafice (GPU). [Graphics error] Submitting the render commands failed. Try to update your GPU drivers. -== +== Trimiterea comenzilor de randare a eșuat. Încercați să vă actualizați driverele plăcii grafice (GPU). [Graphics error] Failed to swap framebuffers. Try to update your GPU drivers. -== +== A eșuat schimbul tamponelor de cadre. Încercați să vă actualizați driverele plăcii grafice (GPU). [Graphics error] Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== +== Eroare necunoscută. Încercați să schimbați gfx_backend la OpenGL sau Vulkan în settings_ddnet.cfg din dosarul de configurare și încercați din nou. [Graphics error] Could not initialize the given graphics backend, reverting to the default backend now. -== +== Nu s-a putut inițializa backend-ul grafic dat, se revine la backend-ul implicit. [Graphics error] Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. -== +== Nu s-a putut inițializa backend-ul grafic dat, probabil pentru că nu ați instalat driverul plăcii grafice integrate. Could not resolve connect address '%s'. See local console for details. -== +== Nu s-a putut rezolva adresa de conectare '%s'. Consultați consola pentru detalii. Connect address error -== +== Eroare adresă de conectare Could not save downloaded map. Try manually deleting this file: %s -== +== Nu s-a putut salva harta descărcată. Încercați să ștergeți manual acest fișier: %s Could not connect dummy -== +== Nu s-a putut conecta manechinul Error playing demo -== +== Eroare la redarea demo-ului Successfully saved the replay! -== +== Reluare salvată cu succes! Failed saving the replay! -== +== A eșuat salvarea reluării! Saving settings to '%s' failed -== +== Salvarea setărilor în '%s' a eșuat Error saving settings -== +== Eroare la salvarea setărilor Replay feature is disabled! -== +== Funcția de reluare este dezactivată! The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +== Lățimea texturii %s nu este divizibilă cu %d, sau înălțimea nu este divizibilă cu %d, ceea ce poate cauza erori vizuale. The format of texture %s is not RGBA which will cause visual bugs. -== +== Formatul texturii %s nu este RGBA, ceea ce va cauza erori vizuale. Preparing demo playback -== +== Se pregătește redarea demo-ului Connected -== +== Conectat Loading map file from storage -== +== Se încarcă fișierul de hartă din stocare Loading demo file from storage -== +== Se încarcă fișierul demo din stocare Some fonts could not be loaded. Check the local console for details. -== +== Unele fonturi nu au putut fi încărcate. Verificați consola pentru detalii. Loading DDNet Client -== +== Se încarcă client-ul DDNet Initializing components -== +== Se inițializează componentele Why are you slowmo replaying to read this? -== +== De ce re-joci în slowmo ca să citești asta? Initializing assets -== +== Se inițializează resursele Initializing map logic -== +== Se inițializează logica hărții Sending initial client info -== +== Se trimit informațiile inițiale despre client Warning -== +== Avertisment Quitting. Please wait… -== +== Ieșire. Așteptați, vă rog… Restarting. Please wait… -== +== Repornire. Așteptați, vă rog… Loading skin files -== +== Se încarcă fișiere costume Search -== +== Caută Searching -== +== Se caută Enter Username -== +== Introduceți numele de utilizator Enter Password -== +== Introduceți parola NOT CONNECTED -== +== NU ESTE CONECTAT Match %d of %d -== +== Corespunde %d din %d No results -== +== Nu există rezultate Lines %d - %d (%s) -== +== Liniile %d - %d (%s) Locked -== +== Blocat Following -== +== Urmărind Loading commands… -== +== Se încarcă comenzile… Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. -== +== Modul de depanare activat. Apăsați Ctrl+Shift+D pentru a dezactiva modul de depanare. Position: -== +== Poziție: Speed: -== +== Viteză: Angle: -== +== Unghi: Multi-View -== +== Multi-vedere [Spectating] Following %s -== +== Urmărind %s Server best: -== +== Record server: Personal best: -== +== Record personal: Team %d -== +== Echipa %d Some map images could not be loaded. Check the local console for details. -== +== Unele imagini ale hărții nu au putut fi încărcate. Verificați consola pentru detalii. Uploading map data to GPU -== +== Se încarcă datele hărții pe placa grafică (GPU) Some map sounds could not be loaded. Check the local console for details. -== +== Unele sunete ale hărții nu au putut fi încărcate. Verificați consola pentru detalii. Loading menu themes -== +== Se încarcă temele meniului Reset -== +== Resetează Press a key… -== +== Apasă o tastă… Settings -== +== Setări Editor -== +== Editor Main menu -== +== Meniul principal Browser -== +== Browser Ghost -== +== Fantomă Reconnect in %d sec -== +== Reconectare în %d sec Rename folder -== +== Redenumește dosarul Render demo -== +== Randează demo Render complete -== +== Randare completă Restart -== +== Repornire Are you sure that you want to restart? -== +== Ești sigur că vrei să repornești? Welcome to DDNet -== +== Bine ai venit pe DDNet DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you. -== +== DDraceNetwork este un joc online cooperativ în care scopul tău și al grupului tău este să ajungeți la linia de sosire a hărții. Ca începător, ar trebui să începi pe serverele Novice, care găzduiesc cele mai ușoare hărți. Ține cont de ping pentru a alege un server aproape de tine. Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. -== +== Folosește tasta k pentru a te "ucide" (reporni), q pentru a pune pauză și a privi alți jucători. Vezi setările pentru alte taste configurabile. It's recommended that you check the settings to adjust them to your liking before joining a server. -== +== Este recomandat să verifici setările pentru a le ajusta după preferințele tale înainte de a intra pe un server. Please enter your nickname below. -== +== Introdu porecla ta mai jos. Existing Player -== +== Jucător existent Your nickname '%s' is already used (%d points). Do you still want to use it? -== +== Porecla ta '%s' este deja folosită (%d puncte). Vrei totuși să o folosești? Checking for existing player with your name -== +== Se verifică existența unui jucător cu numele tău Save skin -== +== Salvează costumul Are you sure you want to save your skin? If a skin with this name already exists, it will be replaced. -== +== Ești sigur că vrei să salvezi costumul? Dacă un costum cu acest nume există deja, acesta va fi înlocuit. There's an unsaved map in the editor, you might want to save it. -== +== Există o hartă nesalvată în editor, poate vrei să o salvezi. Continue anyway? -== +== Continui oricum? A demo with this name already exists -== +== Un demo cu acest nume există deja A folder with this name already exists -== +== Un dosar cu acest nume există deja Unable to rename the folder -== +== Nu se poate redenumi dosarul File '%s' already exists, do you want to overwrite it? -== +== Fișierul '%s' există deja, vrei să-l suprascrii? Replace video -== +== Înlocuiește videoclipul (paused) -== +== (pauză) Speed -== +== Viteză Video name: -== +== Nume videoclip: Videos directory -== +== Dosar videoclipuri Video was saved to '%s' -== +== Videoclipul a fost salvat în '%s' Join Tutorial Server -== +== Intră pe serverul de tutorial Skip Tutorial -== +== Sari peste tutorial Show DDNet map finishes in server browser -== +== Afișează finalizările hărților DDNet în browserul de servere transmits your player name to info.ddnet.org -== +== transmite numele tău de jucător către info.ddnet.org Unable to save the skin -== +== Nu se poate salva costumul Unable to save the skin with a reserved name -== +== Nu se poate salva costumul cu un nume rezervat Trying to determine UDP connectivity… -== +== Se încearcă determinarea conectivității UDP… UDP seems to be filtered. -== +== UDP pare să fie filtrat. UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators. -== +== Adresele IP UDP și TCP par să fie diferite. Încercați să dezactivați VPN-ul, proxy-ul sau acceleratoarele de rețea. No answer from server yet. -== +== Încă nu a răspuns serverul. %d/%d KiB (%.1f KiB/s) -== +== %d/%d KiB (%.1f KiB/s) Getting game info -== +== Se obțin informațiile jocului Requesting to join the game -== +== Se solicită intrarea în joc Theme -== +== Temă Loading menu images -== +== Se încarcă imaginile meniului AFR -== +== AFR ASI -== +== ASI AUS -== +== AUS EUR -== +== EUR NA -== +== NA SA -== +== SA CHN -== +== CHN Getting server list from master server -== +== Se obține lista serverelor de la serverul principal No local servers found (ports %d-%d) -== +== Nu s-au găsit servere locale (porturi %d-%d) Example of usage -== +== Exemplu de utilizare Exclude -== +== Exclude %d of %d servers -== +== %d din %d servere %d of %d server -== +== %d din %d server %d players -== +== %d jucători %d player -== +== %d jucător Are you sure that you want to disconnect and switch to a different server? -== +== Ești sigur că vrei să te deconectezi și să treci la un alt server? No login required -== +== Nu este necesar să te autentifici Filter connecting players -== +== Filtrează jucătorii care se conectează Indicate map finish -== +== Indică finalizarea hărții Unfinished map -== +== Hartă neterminată Countries -== +== Țări Types -== +== Tipuri Communities -== +== Comunități Copy info -== +== Copiază informațiile Leak IP -== +== Expune IP-ul No server selected -== +== Niciun server selectat -Online players (%d) -== +Online friends (%d) +== Prieteni online (%d) Online clanmates (%d) -== +== Membri de clan online (%d) [friends (server browser)] Offline (%d) -== +== Offline (%d) Click to select server. Double click to join your friend. -== +== Fă clic pentru a selecta serverul. Dublu clic pentru a te alătura prietenului tău. Click to remove this player from your friends list. -== +== Fă clic pentru a elimina acest jucător din lista de prieteni. Click to remove this clan from your friends list. -== +== Fă clic pentru a elimina acest clan din lista de prieteni. + +Add friends by entering their name below or by clicking their name in the player list. +== Adaugă prieteni introducând numele lor mai jos sau făcând clic pe numele lor în lista de jucători. + +Add clanmates by entering their clan below and leaving the name blank. +== Adaugă membri de clan introducând numele clanului mai jos și lăsând numele gol. -None -== +Offline friends and clanmates will appear here. +== Prietenii și membrii de clan offline vor apărea aici. Are you sure that you want to remove the player '%s' from your friends list? -== +== Ești sigur că vrei să elimini jucătorul '%s' din lista de prieteni? Are you sure that you want to remove the clan '%s' from your friends list? -== +== Ești sigur că vrei să elimini clanul '%s' din lista de prieteni? Add Clan -== +== Adaugă Clan Server filter -== +== Filtru server Friends -== +== Prieteni Play the current demo -== +== Redă demo-ul curent Pause the current demo -== +== Pune pe pauză demo-ul curent Stop the current demo -== +== Oprește demo-ul curent Go back the specified duration -== +== Mergi înapoi cu durata specificată [Demo player duration] %d min. -== +== %d min. [Demo player duration] %d sec. -== +== %d sec. Change the skip duration -== +== Schimbă durata de sărire Go forward the specified duration -== +== Mergi înainte cu durata specificată Go back one tick -== +== Mergi înapoi un pas Go forward one tick -== +== Mergi înainte un pas Go back one marker -== +== Mergi înapoi un marcaj Go forward one marker -== +== Mergi înainte un marcaj Slow down the demo -== +== Încetinește demo-ul Speed up the demo -== +== Accelerează demo-ul Mark the beginning of a cut (right click to reset) -== +== Marchează începutul unei secțiuni (clic dreapta pentru resetare) Mark the end of a cut (right click to reset) -== +== Marchează sfârșitul unei secțiuni (clic dreapta pentru resetare) Export cut as a separate demo -== +== Exportă secțiunea ca un demo separat Close the demo player -== +== Închide player-ul demo Toggle keyboard shortcuts -== +== Comutează scurtăturile de tastatură Export demo cut -== +== Exportă secțiunea demo Cut interval -== +== Interval de tăiere Cut length -== +== Lungime secțiune Remove chat -== +== Elimină chatul Render cut to video -== +== Randează secțiunea în video Please use a different filename -== +== Folosește un alt nume de fișier File already exists, do you want to overwrite it? -== +== Fișierul există deja, vrei să-l suprascrii? Loading demo files -== +== Se încarcă fișierele demo All combined -== +== Toate combinate Demo -== +== Demo Length -== +== Lungime Date -== +== Dată No demo selected -== +== Niciun demo selectat Folder Link -== +== Link folder Created -== +== Creat Markers -== +== Marcaje Netversion -== +== Versiune rețea Size -== +== Dimensiune [Demo details] map not included -== +== harta nu este inclusă %.2f MiB -== +== %.2f MiB %.2f KiB -== +== %.2f KiB Fetch Info -== +== Preia informații Demos directory -== +== Dosar demo-uri Open the directory that contains the demo files -== +== Deschide dosarul care conține fișierele demo Are you sure that you want to delete the folder '%s'? -== +== Ești sigur că vrei să ștergi folderul '%s'? Are you sure that you want to delete the demo '%s'? -== +== Ești sigur că vrei să ștergi demo-ul '%s'? Delete folder -== +== Șterge folderul Unable to delete the demo '%s' -== +== Nu se poate șterge demo-ul '%s' Unable to delete the folder '%s'. Make sure it's empty first. -== +== Nu se poate șterge folderul '%s'. Asigură-te mai întâi că este gol. Are you sure that you want to disconnect? -== +== Ești sigur că vrei să te deconectezi? Connect Dummy -== +== Conectează manechinul Dummy is not allowed on this server -== +== Manechinul nu este permis pe acest server Please wait… -== +== Te rog așteaptă… Connecting dummy -== +== Se conectează manechinul Disconnect Dummy -== +== Deconectează manechinul Are you sure that you want to disconnect your dummy? -== +== Ești sigur că vrei să deconectezi manechinul? Kill -== +== Ucide Pause -== +== Pauză + +Edit touch controls +== Editează controalele tactile + +Close +== Închide + +Save changes +== Salvează modificările + +Error saving touch controls +== Eroare la salvarea controalelor tactile + +Could not save touch controls to file. See local console for details. +== Nu s-au putut salva controalele tactile în fișier. Verifică consola locală pentru detalii. + +Unsaved changes +== Modificări nesalvate + +Discard changes +== Renunță la modificări + +Are you sure that you want to discard the current changes to the touch controls? +== Ești sigur că vrei să renunți la modificările curente ale controalelor tactile? + +Are you sure that you want to reset the touch controls to default? +== Ești sigur că vrei să resetezi controalele tactile la valorile implicite? + +Import from clipboard +== Importă din clipboard + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Ești sigur că vrei să imporți controalele tactile din clipboard? Acest lucru va suprascrie setările actuale. + +Export to clipboard +== Exportă în clipboard + +Direct touch input while ingame +== Intrare tactilă directă în timpul jocului + +[Direct touch input] +Disabled +== Dezactivat + +[Direct touch input] +Active action +== Acțiune activă + +[Direct touch input] +Aim +== Țintește + +[Direct touch input] +Fire +== Trage + +[Direct touch input] +Hook +== Cârlig + +Direct touch input while spectating +== Intrare tactilă directă în timpul vizionării + +Error loading touch controls +== Eroare la încărcarea controalelor tactile + +Could not load touch controls from file. See local console for details. +== Nu s-au putut încărca controalele tactile din fișier. Verifică consola locală pentru detalii. + +Could not load default touch controls from file. See local console for details. +== Nu s-au putut încărca controalele tactile implicite din fișier. Verifică consola locală pentru detalii. + +Could not load touch controls from clipboard. See local console for details. +== Nu s-au putut încărca controalele tactile din clipboard. Verifică consola locală pentru detalii. Loading… -== +== Se încarcă… Loading ghost files -== +== Se încarcă fișierele fantomă Time -== +== Timp Ghosts directory -== +== Dosar fantome Activate all -== +== Activează toate Deactivate all -== +== Dezactivează toate Deactivate -== +== Dezactivează Activate -== +== Activează Save -== +== Salvează Menu opened. Press Esc key again to close menu. -== +== Meniul a fost deschis. Apasă tasta Esc din nou pentru a închide meniul. Smooth Dynamic Camera -== +== Cameră dinamică lină Switch weapon when out of ammo -== +== Schimbă arma când rămâi fără muniție Skip the main menu -== +== Sari peste meniul principal [Hertz] Hz -== +== Hz Refresh Rate -== +== Rata de refresh Save power by lowering refresh rate (higher input latency) -== +== Economisește energie prin reducerea ratei de refresh (latență mai mare la intrări) Settings file -== +== Fișier de setări Open the settings file -== +== Deschide fișierul de setări Config directory -== +== Dosarul de configurare Open the directory that contains the configuration and user files -== +== Deschide dosarul care conține fișierele de configurare și utilizator Themes directory -== +== Dosar teme Open the directory to add custom themes -== +== Deschide dosarul pentru a adăuga teme personalizate Automatically take statboard screenshot -== +== Fă automat captură de ecran pentru tabela de statistici Automatically create statboard csv -== +== Creează automat fișier CSV pentru tabela de statistici Max CSVs -== +== Număr maxim de fișiere CSV Dummy -== +== Manechin Player info change cooldown -== +== Timp de așteptare pentru schimbarea informațiilor jucătorului Download skins -== +== Descarcă costume Download community skins -== +== Descarcă costume comunitare Vanilla skins only -== +== Doar costume de bază (Vanilla) Fat skins (DDFat) -== +== Costume mari (DDFat) Skin prefix -== +== Prefix costum Create a random skin -== +== Creează un costum aleatoriu Choose default eyes when joining a server -== +== Alege ochii implicați la conectarea pe un server Skin Database -== +== Baza de date a costumelor Skins directory -== +== Dosar costume Open the directory to add custom skins -== +== Deschide dosarul pentru a adăuga costume personalizate Hook collisions -== +== Coliziuni cârlig Zoom in -== +== Mărește Zoom out -== +== Micșorează Default zoom -== +== Zoom implicit Show others -== +== Arată alții Show all -== +== Arată toți Toggle dyncam -== +== Comutează camera dinamică Toggle ghost -== +== Comutează fantoma Converse -== +== Conversație Chat command -== +== Comandă chat Toggle dummy -== +== Comutează manechinul Dummy copy -== +== Copiere manechin Hammerfly dummy -== +== Manechin hammerfly Statboard -== +== Tabela de statistici Lock team -== +== Blochează echipa Show entities -== +== Arată entitățile Show HUD -== +== Arată interfața Enable controller -== +== Activează controlerul Controller -== +== Controler Ingame controller mode -== +== Mod controler în joc [Ingame controller mode] Relative -== +== Relativ [Ingame controller mode] Absolute -== +== Absolut Ingame controller sens. -== +== Sensibilitate controler în joc UI controller sens. -== +== Sensibilitate controler interfață Controller jitter tolerance -== +== Toleranță la zgomot controler No controller found. Plug in a controller. -== +== Nu s-a găsit niciun controler. Conectează un controler. Axis -== +== Axă Status -== +== Status Aim bind -== +== Legare țintire Mouse -== +== Mouse Ingame mouse sens. -== +== Sensibilitate mouse în joc UI mouse sens. -== +== Sensibilitate mouse interfață Reset controls -== +== Resetează controalele Are you sure that you want to reset the controls to their defaults? -== +== Ești sigur că vrei să resetezi controalele la valorile implicite? Cancel -== +== Anulează Windowed -== +== Fereastră Windowed borderless -== +== Fereastră fără margini Windowed fullscreen -== +== Fereastră pe tot ecranul Desktop fullscreen -== +== Ecran complet desktop Screen -== +== Ecran may cause delay -== +== poate cauza întârzieri Allows maps to render with more detail -== +== Permite redarea hărților cu mai multe detalii Use high DPI -== +== Folosește DPI înalt Renderer -== +== Motor grafic default -== +== implicit custom -== +== personalizat Graphics card -== +== Placă grafică auto -== +== automat Enable game sounds -== +== Activează sunetele jocului Enable gun sound -== +== Activează sunetul armelor Enable long pain sound (used when shooting in freeze) -== +== Activează sunetul lung de durere (utilizat când tragi în timp ce ești înghețat) Enable server message sound -== +== Activează sunetul mesajelor serverului Enable regular chat sound -== +== Activează sunetul chat-ului obișnuit Enable team chat sound -== +== Activează sunetul chat-ului de echipă Enable highlighted chat sound -== +== Activează sunetul pentru chat-ul evidențiat Game sound volume -== +== Volumul sunetului jocului Chat sound volume -== +== Volumul sunetului din chat Map sound volume -== +== Volumul sunetelor hărții Background music volume -== +== Volumul muzicii de fundal Tee -== +== Tee Appearance -== +== Apariție DDNet -== +== DDNet Assets -== +== Resurse DDNet Client needs to be restarted to complete update! -== +== Clientul DDNet trebuie repornit pentru a finaliza actualizarea! HUD -== +== Interfață Name Plate -== +== Etichetă de nume Hook Collisions -== +== Coliziuni cârlig Info Messages -== +== Mesaje informative Show health, shields and ammo -== +== Arată viața, scuturile și muniția Show score -== +== Arată scorul Show local time always -== +== Arată ora locală mereu Show votes window after voting -== +== Arată fereastra de voturi după votare DDRace HUD -== +== Interfață DDRace Show client IDs (scoreboard, chat, spectator) -== +== Arată ID-urile de client (tabelă, chat, spectator) Show DDRace HUD -== +== Arată interfața DDRace Show jumps indicator -== +== Arată indicatorul săriturilor Show dummy actions -== +== Arată acțiunile manechinului Show player position -== +== Arată poziția jucătorului Show player speed -== +== Arată viteza jucătorului Show player target angle -== +== Arată unghiul țintei jucătorului Show freeze bars -== +== Arată barele de îngheț Opacity of freeze bars inside freeze -== +== Opacitatea barelor de îngheț în stare de îngheț Always show chat -== +== Arată mereu chat-ul Show names in chat in team colors -== +== Arată numele în chat în culorile echipei Show only chat messages from team members -== +== Arată doar mesajele din chat de la membrii echipei Use old chat style -== +== Folosește stilul vechi de chat Chat font size -== +== Dimensiunea fontului în chat Chat width -== +== Lățimea chat-ului Messages -== +== Mesaje System message -== +== Mesaj de sistem Highlighted message -== +== Mesaj evidențiat Team message -== +== Mesaj de echipă Friend message -== +== Mesaj de prieten Normal message -== +== Mesaj normal Client message -== +== Mesaj client Preview -== +== Previzualizare Show clan above name plates -== +== Arată clanul deasupra etichetelor de nume Clan plates size -== +== Dimensiunea etichetelor de clan Show friend mark (♥) in name plates -== +== Arată semnul prietenilor (♥) pe etichetele de nume Show hook strength icon indicator -== +== Arată indicatorul de putere al cârligului Show hook strength number indicator -== +== Arată indicatorul numeric de putere al cârligului Show other players' key presses -== +== Arată tastele apăsate de alți jucători Show local player's key presses -== +== Arată tastele apăsate de jucătorul local Authed name color in scoreboard -== +== Culoarea numelui autentificat în tabela de scor Same clan color in scoreboard -== +== Culoarea comună a clanului în tabela de scor Hook collision line -== +== Linie de coliziune cârlig Show own player's hook collision line -== +== Arată linia de coliziune a cârligului propriu Always show own player's hook collision line -== +== Arată mereu linia de coliziune a cârligului propriu Show other players' hook collision lines -== +== Arată liniile de coliziune ale cârligelor altor jucători Always show other players' hook collision lines -== +== Arată mereu liniile de coliziune ale cârligelor altor jucători + +Width of your own hook collision line +== Lățimea liniei de coliziune a cârligului propriu -Hook collision line width -== +Width of others' hook collision line +== Lățimea liniei de coliziune a cârligelor altor jucători Hook collision line opacity -== +== Opacitatea liniei de coliziune a cârligului Colors of the hook collision line, in case of a possible collision with: -== +== Culorile liniei de coliziune a cârligului, în cazul unei posibile coliziuni cu: Your movements are not taken into account when calculating the line colors -== +== Mișcările tale nu sunt luate în considerare la calcularea culorilor liniei Nothing hookable -== +== Nimic agățabil Something hookable -== +== Ceva agățabil A Tee -== +== Un Tee + +Preview 'Hook collisions' being pressed +== Previzualizare 'Coliziuni cârlig' în timpul apăsării Show kill messages -== +== Arată mesajele de ucidere Show finish messages -== +== Arată mesajele de finalizare Normal Color -== +== Culoare normală Highlight Color -== +== Culoare evidențiată Weapons -== +== Arme Rifle Laser Outline Color -== +== Culoarea conturului puștii Rifle Laser Inner Color -== +== Culoarea interioară a puștii Shotgun Laser Outline Color -== +== Culoarea conturului laserului puștii de vânătoare Shotgun Laser Inner Color -== +== Culoarea interioară a laserului puștii de vânătoare Entities -== +== Entități Door Laser Outline Color -== +== Culoarea conturului laserului ușii Door Laser Inner Color -== +== Culoarea interioară a laserului ușii Freeze Laser Outline Color -== +== Culoarea conturului laserului de îngheț Freeze Laser Inner Color -== +== Culoarea interioară a laserului de îngheț Set all to Rifle -== +== Setează toate pe Pușcă Save the best demo of each race -== +== Salvează cel mai bun demo al fiecărei curse Enable replays -== +== Activează reluările Default length -== +== Lungime implicită Enable ghost -== +== Activează fantoma When you cross the start line, show a ghost tee replicating the movements of your best time -== +== Când treci linia de start, arată un Tee fantomă care reproduce mișcările celui mai bun timp al tău Show ghost -== +== Arată fantoma Opacity -== +== Opacitate Save ghost -== +== Salvează fantoma Only save improvements -== +== Salvează doar îmbunătățirile Gameplay -== +== Gameplay Overlay entities -== +== Suprapune entitățile Show text entities -== +== Arată entitățile text Adjust the opacity of entities belonging to other teams, such as tees and nameplates -== +== Ajustează opacitatea entităților aparținând altor echipe, cum ar fi Tee-urile și etichetele de nume Show others (own team only) -== +== Arată alții (doar echipa proprie) Show quads -== +== Arată quad-urile Quads are used for background decoration -== +== Quad-urile sunt utilizate pentru decorarea fundalului AntiPing -== +== AntiPing Tries to predict other entities to give a feel of low latency -== +== Încearcă să prezică alte entități pentru a oferi senzația de latență scăzută AntiPing: predict other players -== +== AntiPing: prezice alți jucători AntiPing: predict weapons -== +== AntiPing: prezice armele AntiPing: predict grenade paths -== +== AntiPing: prezice traiectoria grenadelor Background -== +== Fundal Regular background color -== +== Culoare de fundal obișnuită Entities background color -== +== Culoarea de fundal a entităților Use current map as background -== +== Folosește harta curentă ca fundal Show tiles layers from BG map -== +== Arată straturile de țigle din harta de fundal New random timeout code -== +== Cod nou de timeout aleatoriu Run on join -== +== Rulează la conectare Chat command (e.g. showall 1) -== +== Comandă chat (ex: showall 1) Unregister protocol and file extensions -== +== Deregistrează protocolul și extensiile fișierelor DDNet %s is available: -== +== DDNet %s este disponibil: Update now -== +== Actualizează acum Updating… -== +== Se actualizează… DDNet Client updated! -== +== Clientul DDNet a fost actualizat! No updates available -== +== Nu există actualizări disponibile Check now -== +== Verifică acum Basic -== +== Bazic Custom -== +== Personalizat Are you sure that you want to delete '%s'? -== +== Ești sigur că vrei să ștergi '%s'? Delete skin -== +== Șterge costumul Unable to delete skin -== +== Nu se poate șterge costumul Emoticons -== +== Emoticoane Particles -== +== Particule Extras -== +== Extra Loading assets -== +== Se încarcă resursele Assets directory -== +== Dosar resurse Open the directory to add custom assets -== +== Deschide dosarul pentru a adăuga resurse personalizate Discord -== +== Discord https://ddnet.org/discord -== +== https://ddnet.org/discord Learn -== +== Învață https://wiki.ddnet.org/ -== +== https://wiki.ddnet.org/ Tutorial -== +== Tutorial Can't find a Tutorial server -== +== Nu se poate găsi un server de Tutorial Website -== +== Website Stop server -== +== Oprește server Run server -== +== Rulează server Server executable not found, can't run server -== +== Executabilul serverului nu a fost găsit, nu se poate rula serverul [Start menu] Play -== +== Joacă DDNet %s is out! -== +== DDNet %s a fost lansat! Downloading %s: -== +== Descarcă %s: Update failed! Check log… -== +== Actualizarea a eșuat! Verifică jurnalul… Loading race demo files -== +== Se încarcă fișierele demo de cursă Round %d/%d -== +== Runda %d/%d [Spectators] %d others… -== +== %d alți spectatori… Super -== +== Super [Team and size] %d\n(%d/%d) -== +== %d\n(%d/%d) Team %d (%d/%d) -== +== Echipa %d (%d/%d) Manual -== +== Manual Race -== +== Cursă Auto -== +== Automat Replay -== +== Repetă [skins] Body -== +== Corp [skins] Marking -== +== Marcaj [skins] Decoration -== +== Decorație [skins] Hands -== +== Mâini [skins] Feet -== +== Picioare [skins] Eyes -== +== Ochii Loading sound files -== +== Se încarcă fișierele audio Follow -== +== Urmărește Frags -== +== Frags Deaths -== +== Decese Suicides -== +== Sinucideri Ratio -== +== Rata Net -== +== Rețea FPM -== +== FPM Spree -== +== Serii de ucideri Best -== +== Cel mai bun Grabs -== +== Prinderi + +Aim +== Țintă + +Active: Fire +== Activ: Foc + +Active: Hook +== Activ: Cârlig 1 new mention -== +== 1 mențiune nouă %d new mentions -== +== %d mențiuni noi 9+ new mentions -== +== 9+ mențiuni noi Moved ingame -== +== Mutat în joc https://wiki.ddnet.org/wiki/Mapping -== +== https://wiki.ddnet.org/wiki/Mapping diff --git a/data/languages/russian.txt b/data/languages/russian.txt index 85c4f30ab83..1adb0c44d57 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -1366,9 +1366,6 @@ Show jumps indicator Hook collision line == Линия коллизии крюка -Hook collision line width -== Ширина линии - Hook collision line opacity == Прозрачность линии @@ -1561,9 +1558,6 @@ Copy info Create a random skin == Создать случайный скин -Online players (%d) -== Друзья (%d) - Online clanmates (%d) == Участники клана (%d) @@ -1580,9 +1574,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Нажмите, чтобы исключить клан из списка. -None -== Пусто - Add Clan == Добавить клан @@ -1958,3 +1949,107 @@ Feet [skins] Eyes == Глаза + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/serbian.txt b/data/languages/serbian.txt index df9130b9d9f..b5cd34ad48f 100644 --- a/data/languages/serbian.txt +++ b/data/languages/serbian.txt @@ -1294,9 +1294,6 @@ Show local player's key presses Hook collision line == Dodir kuke -Hook collision line width -== Debljina linije za dodir kuke - Hook collision line opacity == Providnost linije za dodir kuke @@ -1440,9 +1437,6 @@ Copy info No server selected == Није изабран сервер -Online players (%d) -== Играчи на мрежи (%d) - Online clanmates (%d) == Чланови клана на мрежи (%d) @@ -1459,9 +1453,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Кликните да бисте уклонили овај клан са листе пријатеља. -None -== Ништа - Are you sure that you want to remove the player '%s' from your friends list? == Да ли сте сигурни да желите да уклоните играча '%s' са листе пријатеља? @@ -1761,6 +1752,18 @@ No login required Communities == +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + Server filter == @@ -1809,6 +1812,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1876,6 +1953,15 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1944,5 +2030,14 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + https://wiki.ddnet.org/wiki/Mapping == diff --git a/data/languages/serbian_cyrillic.txt b/data/languages/serbian_cyrillic.txt index b58eb080d08..c265e5230a9 100644 --- a/data/languages/serbian_cyrillic.txt +++ b/data/languages/serbian_cyrillic.txt @@ -1293,9 +1293,6 @@ Show local player's key presses Hook collision line == Додир куке -Hook collision line width -== Дебљина линије за додир куке - Hook collision line opacity == Провидност линије за додир куке @@ -1573,7 +1570,7 @@ Copy info No server selected == -Online players (%d) +Online friends (%d) == Online clanmates (%d) @@ -1592,7 +1589,13 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == -None +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. == Are you sure that you want to remove the player '%s' from your friends list? @@ -1727,6 +1730,80 @@ Dummy is not allowed on this server Please wait… == +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + Loading… == @@ -1833,6 +1910,15 @@ Always show own player's hook collision line Always show other players' hook collision lines == +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + Show finish messages == @@ -1937,6 +2023,15 @@ Feet Eyes == +Aim +== + +Active: Fire +== + +Active: Hook +== + Moved ingame == diff --git a/data/languages/simplified_chinese.txt b/data/languages/simplified_chinese.txt index cde20f5430c..16c439deba5 100644 --- a/data/languages/simplified_chinese.txt +++ b/data/languages/simplified_chinese.txt @@ -44,6 +44,7 @@ # 2024-08-29 Pioooooo # 2024-09-29 Pioooooo # 2024-11-01 Pioooooo +# 2024-12-03 豆腐渣 & Pioooooo ##### /authors ##### ##### translated strings ##### @@ -1395,9 +1396,6 @@ Show jumps indicator Hook collision line == 钩索辅助线 -Hook collision line width -== 辅助线宽度 - Hook collision line opacity == 辅助线不透明度 @@ -1587,9 +1585,6 @@ Copy info Create a random skin == 随机创造皮肤 -Online players (%d) -== 在线玩家 (%d人) - Online clanmates (%d) == 在线战队成员 (%d人) @@ -1606,9 +1601,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == 单击从你的好友列表移除此战队 -None -== 无 - Add Clan == 添加战队 @@ -1987,3 +1979,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == 未能加载某些字体。检查本地控制台以获取详情。 + +Online friends (%d) +== 在线好友 (%d人) + +Add friends by entering their name below or by clicking their name in the player list. +== 在下方输入好友名称或点击玩家列表中的好友名称即可添加好友 + +Add clanmates by entering their clan below and leaving the name blank. +== 在下方输入战队并留空名称即可添加战队 + +Offline friends and clanmates will appear here. +== 离线好友和战队会显示在这里 + +Edit touch controls +== 编辑触控设置 + +Close +== 关闭 + +Save changes +== 保存更改 + +Error saving touch controls +== 保存触控设置错误 + +Could not save touch controls to file. See local console for details. +== 无法将触控设置保存到文件。检查本地控制台以获取详情。 + +Unsaved changes +== 更改未保存 + +Discard changes +== 放弃更改 + +Are you sure that you want to discard the current changes to the touch controls? +== 确定要放弃当前对触控设置的更改吗? + +Are you sure that you want to reset the touch controls to default? +== 确定要将触控设置重置为默认设置吗? + +Import from clipboard +== 从剪贴板导入 + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== 您确定要从剪贴板导入触控设置吗?这将覆盖当前的触控设置。 + +Export to clipboard +== 导出到剪贴板 + +Direct touch input while ingame +== 游戏时直接触摸操作 + +[Direct touch input] +Disabled +== 禁用 + +[Direct touch input] +Active action +== 当前动作 + +[Direct touch input] +Aim +== 瞄准 + +[Direct touch input] +Fire +== 开火 + +[Direct touch input] +Hook +== 钩索 + +Direct touch input while spectating +== 旁观时直接触摸操作 + +Error loading touch controls +== 加载触控设置错误 + +Could not load touch controls from file. See local console for details. +== 无法从文件加载触控设置。检查本地控制台以获取详情。 + +Could not load default touch controls from file. See local console for details. +== 无法从文件加载默认触控设置。检查本地控制台以获取详情。 + +Could not load touch controls from clipboard. See local console for details. +== 无法从剪贴板加载触控设置。检查本地控制台以获取详情。 + +Width of your own hook collision line +== 自己的钩索辅助线宽度 + +Width of others' hook collision line +== 其他玩家的钩索辅助线宽度 + +Preview 'Hook collisions' being pressed +== 预览按下时的钩索辅助线 + +Aim +== 瞄准 + +Active: Fire +== 当前:开火 + +Active: Hook +== 当前:钩索 diff --git a/data/languages/slovak.txt b/data/languages/slovak.txt index 29dbf781197..934620aa21f 100644 --- a/data/languages/slovak.txt +++ b/data/languages/slovak.txt @@ -889,9 +889,6 @@ Leak IP No server selected == Nie je vybraný žiadny server -Online players (%d) -== Online hráči (%d) - Online clanmates (%d) == Online členovia klanu (%d) @@ -908,9 +905,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Kliknutím odoberiete tento klan zo zoznamu priateľov. -None -== Žiadny - Are you sure that you want to remove the player '%s' from your friends list? == Naozaj chcete odstrániť hráča '%s' zo zoznamu priateľov? @@ -1504,9 +1498,6 @@ Hook collision line Show other players' hook collision lines == Zobraziť čiaru kolízie hákov ostatných hráčov -Hook collision line width -== Šírka čiary kolízie háku - Hook collision line opacity == Nepriehľadnosť čiary kolízie háku @@ -1943,3 +1934,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/spanish.txt b/data/languages/spanish.txt index 1612ed35dc6..ab307ea5821 100644 --- a/data/languages/spanish.txt +++ b/data/languages/spanish.txt @@ -1393,9 +1393,6 @@ Show jumps indicator Hook collision line == Línea de colisión del gancho -Hook collision line width -== Ancho de la línea de colisión del gancho - Hook collision line opacity == Opacidad de la lína de colisión del gancho @@ -1561,9 +1558,6 @@ Copy info Create a random skin == Crear skin aleatoria -Online players (%d) -== Jugadores en línea (%d) - Online clanmates (%d) == Compañeros de clan en línea (%d) @@ -1580,9 +1574,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Haz clic para quitar este clan de tu lista de amigos. -None -== Ninguno - Add Clan == Agregar clan @@ -1961,3 +1952,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == + +Online friends (%d) +== + +Add friends by entering their name below or by clicking their name in the player list. +== + +Add clanmates by entering their clan below and leaving the name blank. +== + +Offline friends and clanmates will appear here. +== + +Edit touch controls +== + +Close +== + +Save changes +== + +Error saving touch controls +== + +Could not save touch controls to file. See local console for details. +== + +Unsaved changes +== + +Discard changes +== + +Are you sure that you want to discard the current changes to the touch controls? +== + +Are you sure that you want to reset the touch controls to default? +== + +Import from clipboard +== + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== + +Export to clipboard +== + +Direct touch input while ingame +== + +[Direct touch input] +Disabled +== + +[Direct touch input] +Active action +== + +[Direct touch input] +Aim +== + +[Direct touch input] +Fire +== + +[Direct touch input] +Hook +== + +Direct touch input while spectating +== + +Error loading touch controls +== + +Could not load touch controls from file. See local console for details. +== + +Could not load default touch controls from file. See local console for details. +== + +Could not load touch controls from clipboard. See local console for details. +== + +Width of your own hook collision line +== + +Width of others' hook collision line +== + +Preview 'Hook collisions' being pressed +== + +Aim +== + +Active: Fire +== + +Active: Hook +== diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt index 1e8e4bc1723..214ea656892 100644 --- a/data/languages/swedish.txt +++ b/data/languages/swedish.txt @@ -7,7 +7,7 @@ # 3edcxzaq1 2020-06-25 00:00:00 # cur.ie 2020-09-28 00:00:00 # simpygirl 2022-02-20 00:00:00 -# furo 2024-11-01 00:00:00 +# furo 2024-12-04 00:00:00 ##### /authors ##### ##### translated strings ##### @@ -1279,9 +1279,6 @@ Copy info No server selected == Ingen server vald -Online players (%d) -== Online spelare (%d) - Online clanmates (%d) == Online klanmedlemmar (%d) @@ -1298,9 +1295,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Klicka för att ta bort denna klan från din kompis lista. -None -== Ingen - Are you sure that you want to remove the player '%s' from your friends list? == Är du säker på att du vill ta bort spelare '%s' från din kompis lista? @@ -1537,9 +1531,6 @@ Opacity of freeze bars inside freeze Hook collision line == Hook kollisions linje -Hook collision line width -== Hook kollisions linje bredd - Hook collision line opacity == Hook kollisions linje opacitet @@ -1945,3 +1936,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == Vissa typsnitt kunde inte laddas in. Se den lokala konsolen för detaljer. + +Online friends (%d) +== Online kompisar (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Lägg till en kompis genom att skriva in deras namn nedanför eller genom att klicka på deras namn i spellistan. + +Add clanmates by entering their clan below and leaving the name blank. +== Lägg till en klanmedlem genom att skriva in deras klan nedanför och lämna namn fältet blankt. + +Offline friends and clanmates will appear here. +== Offline kompisar och klanmedlemmar kommer att synas här. + +Edit touch controls +== Ändra pekskärms kontrollerna + +Close +== Stäng + +Save changes +== Spara ändringar + +Error saving touch controls +== Misslyckades med att spara pekskärms kontrollerna + +Could not save touch controls to file. See local console for details. +== Kunde inte spara pekskärms kontrollerna till en fil. Se den lokala konsolen för detaljer. + +Unsaved changes +== Osparade ändringar + +Discard changes +== Kasta ändringar + +Are you sure that you want to discard the current changes to the touch controls? +== Är du säker att du vill kasta de nuvarande ändringar till pekskärms kontrollerna? + +Are you sure that you want to reset the touch controls to default? +== Är du säker på att du vill nollställa pekskärms kontrollerna till standardinställningarna? + +Import from clipboard +== Importera från urklipp + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Är du säker att du vill importera pekskärms kontrollerna från urklipp? Detta kommer att skriva över dina nuvarande inställningar. + +Export to clipboard +== Exportera till urklipp + +Direct touch input while ingame +== Direkt pekskärms kontroller medan du är i spel + +[Direct touch input] +Disabled +== Inaktiverad + +[Direct touch input] +Active action +== Aktivt läge + +[Direct touch input] +Aim +== Sikta + +[Direct touch input] +Fire +== Skjut + +[Direct touch input] +Hook +== Hook + +Direct touch input while spectating +== Direkt pekskärms kontroller medan du är i åskådarläge + +Error loading touch controls +== Misslyckades att ladda in pekskärms kontrollerna + +Could not load touch controls from file. See local console for details. +== Misslyckades att ladda in pekskärms kontrollerna från fil. Se den lokala konsolen för detaljer. + +Could not load default touch controls from file. See local console for details. +== Misslyckades att ladda in standard pekskärms kontrollerna från fil. Se den lokala konsolen för detaljer. + +Could not load touch controls from clipboard. See local console for details. +== Misslyckades att ladda in pekskärms kontrollerna från urkipp. Se den lokala konsolen för detaljer. + +Width of your own hook collision line +== Bredd av din egna hook kollisions linje + +Width of others' hook collision line +== Bredd av andras hook kollisions linjer + +Preview 'Hook collisions' being pressed +== Förhandsvisning när 'Hook kollisions' är aktivt + +Aim +== Sikta + +Active: Fire +== Läge: Skjut + +Active: Hook +== Läge: Hook diff --git a/data/languages/traditional_chinese.txt b/data/languages/traditional_chinese.txt index b969f1c669b..2326a00cd67 100644 --- a/data/languages/traditional_chinese.txt +++ b/data/languages/traditional_chinese.txt @@ -33,6 +33,7 @@ # 2024-08-29 Pioooooo # 2024-09-29 Pioooooo # 2024-11-01 Pioooooo +# 2024-12-03 豆腐渣 & Pioooooo & By ##### /authors ##### ##### translated strings ##### @@ -1384,9 +1385,6 @@ Show jumps indicator Hook collision line == 鉤索輔助線 -Hook collision line width -== 輔助線寬度 - Hook collision line opacity == 輔助線不透明度 @@ -1576,9 +1574,6 @@ Copy info Create a random skin == 隨機創造外觀 -Online players (%d) -== 在綫玩家 (%d) - Online clanmates (%d) == 在綫戰隊隊友 (%d) @@ -1595,9 +1590,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == 點擊以將此戰隊從好友列表中移除 -None -== 無 - Add Clan == 新增戰隊 @@ -1976,3 +1968,107 @@ Eyes Some fonts could not be loaded. Check the local console for details. == 未能載入某些字體。檢查本機控制台以取得詳情。 + +Online friends (%d) +== 在線好友(%d人) + +Add friends by entering their name below or by clicking their name in the player list. +== 在下方輸入好友名稱或點擊玩家列表中的好友的名稱即可添加好友 + +Add clanmates by entering their clan below and leaving the name blank. +== 在下方輸入戰隊名並留空名稱即可添加戰隊 + +Offline friends and clanmates will appear here. +== 離線好友和戰隊會顯示在這裡 + +Edit touch controls +== 編輯觸控設置 + +Close +== 關閉 + +Save changes +== 保存更改 + +Error saving touch controls +== 保存觸控設置錯誤 + +Could not save touch controls to file. See local console for details. +== 無法將觸控設置保存到文件。檢查本地控制台以獲得詳情 + +Unsaved changes +== 更改未保存 + +Discard changes +== 放棄更改 + +Are you sure that you want to discard the current changes to the touch controls? +== 確定要放棄當前對觸控設置的更改嗎? + +Are you sure that you want to reset the touch controls to default? +== 確定要將觸控設置重置為默認設置嗎? + +Import from clipboard +== 從剪貼簿導入 + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== 確定要從剪貼簿導入觸控設置嗎?這將覆蓋當前的觸控設置。 + +Export to clipboard +== 導出到剪貼簿 + +Direct touch input while ingame +== 遊戲時直接觸摸操作 + +[Direct touch input] +Disabled +== 禁用 + +[Direct touch input] +Active action +== 當前行為 + +[Direct touch input] +Aim +== 瞄準 + +[Direct touch input] +Fire +== 開火 + +[Direct touch input] +Hook +== 鉤索 + +Direct touch input while spectating +== 旁觀時直接觸摸操作 + +Error loading touch controls +== 加載觸控設置錯誤 + +Could not load touch controls from file. See local console for details. +== 無法從文件加載觸控設置。檢查本機控制台以獲取詳情。 + +Could not load default touch controls from file. See local console for details. +== 無法從文件加載默認觸控設置。檢查本機控制台以獲取詳情。 + +Could not load touch controls from clipboard. See local console for details. +== 無法從剪貼板加載觸控設置。檢查本機控制台以獲取詳情。 + +Width of your own hook collision line +== 自己的鉤索輔助線寬度 + +Width of others' hook collision line +== 其他玩家的鉤索輔助線寬度 + +Preview 'Hook collisions' being pressed +== 預覽按下時的鉤索輔助線 + +Aim +== 瞄準 + +Active: Fire +== 當前:開火 + +Active: Hook +== 當前:鉤鎖 diff --git a/data/languages/turkish.txt b/data/languages/turkish.txt index 77f6146d3c7..f68398e0ebe 100644 --- a/data/languages/turkish.txt +++ b/data/languages/turkish.txt @@ -20,6 +20,7 @@ # Gokturk 2024-09-29 19:23:00 # Gokturk 2024-09-29 19:23:00 # Gokturk 2024-11-04 11:32:00 +# Gokturk 2024-12-03 16:00:00 ##### /authors ##### ##### translated strings ##### @@ -1173,9 +1174,6 @@ Leak IP No server selected == Sunucu seçilmedi -Online players (%d) -== Çevrim içi oyuncular (%d) - Online clanmates (%d) == Çevrim içi klan arkadaşları (%d) @@ -1192,9 +1190,6 @@ Click to remove this player from your friends list. Click to remove this clan from your friends list. == Bu klanı arkadaş listesinden silmek için tıkla. -None -== Yok - Are you sure that you want to remove the player '%s' from your friends list? == '%s' oyuncusunu arkadaş listenizden silmek istediğinize emin misiniz? @@ -1497,9 +1492,6 @@ Show local player's key presses Hook collision line == Kanca çizgisi -Hook collision line width -== Kanca çizgisi kalınlığı - Hook collision line opacity == Kanca çizgisi opaklığı @@ -1880,7 +1872,7 @@ https://wiki.ddnet.org/wiki/Mapping == https://wiki.ddnet.org/wiki/Mapping/tr Could not resolve connect address '%s'. See local console for details. -== '%s' Bağlantı adresi çözümlenemedi. Detaylar için yerel konsola bakın +== '%s' bağlantı adresi çözümlenemedi. Detaylar için yerel konsola bakın. Connect address error == Bağlantı adresi hatası @@ -1922,7 +1914,7 @@ Unable to save the skin == Skin kaydedilemiyor Unable to save the skin with a reserved name -== Skin kaydedilemiyor, bu isimde zaten bir skin var. +== Skin kaydedilemiyor bu isimde zaten bir skin var No local servers found (ports %d-%d) == Yerel sunucu bulunamadı (bağlantı noktası %d-%d) @@ -1956,4 +1948,108 @@ Eyes == Göz Some fonts could not be loaded. Check the local console for details. -== Bazı fontlar yüklenemiyor. Detaylar için ana konsolu kontrol edin +== Bazı fontlar yüklenemiyor. Detaylar için konsolu kontrol edin. + +Online friends (%d) +== Çevrim içi arkadaşlar (%d) + +Add friends by entering their name below or by clicking their name in the player list. +== Aşağıya isim girerek veya oyuncu listesinden isme tıklayarak arkadaş ekle. + +Add clanmates by entering their clan below and leaving the name blank. +== Klan arkadaşlarınızı klanlarını aşağıya girerek ve isimlerini boş bırakarak ekleyin. + +Offline friends and clanmates will appear here. +== Çevrim dışı arkadaşlar ve klan arkadaşları burada görünecek. + +Edit touch controls +== Dokunmatik ayarlarını düzelt + +Close +== Kapat + +Save changes +== Değişiklikleri kaydet + +Error saving touch controls +== Dokunmatik ayarları kaydedilirken bir hata oldu + +Could not save touch controls to file. See local console for details. +== Dokunmatik ayarları dosyalara kaydedilemedi. Detaylar için yerel konsola bakın. + +Unsaved changes +== Kaydedilmemiş değişiklikler + +Discard changes +== Değişiklikleri iptal et + +Are you sure that you want to discard the current changes to the touch controls? +== Dokunmatik ayarlarındaki değişiklikleri iptal etmek istediğinden emin misin? + +Are you sure that you want to reset the touch controls to default? +== Dokunmatik ayarlarını varsayılan ayarlarına döndürmek istediğinden emin misin? + +Import from clipboard +== Panodan içeri aktar + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Dokunmatik ayarlarını panodan içeri aktarmak istediğine emin misin? Bu şu anki kontrollerin üzerine yazılacak. + +Export to clipboard +== Panodan dışarı aktar + +Direct touch input while ingame +== Oyun içindeyken doğrudan dokunmatik giriş + +[Direct touch input] +Disabled +== Devre dışı + +[Direct touch input] +Active action +== Aktif aksiyon + +[Direct touch input] +Aim +== Aim + +[Direct touch input] +Fire +== Ateş + +[Direct touch input] +Hook +== Kanca + +Direct touch input while spectating +== Gezinme modundayken dokunarak doğrudan giriş + +Error loading touch controls +== Dokunmatik ayarları yüklenirken bir hata oluştu + +Could not load touch controls from file. See local console for details. +== Dokunmatik ayarları dosyalardan yüklenemedi. Detaylar için konsola bakın. + +Could not load default touch controls from file. See local console for details. +== Varsayılan dokunmatik ayarları dosyalardan yüklenemedi. Detaylar için konsola bakın. + +Could not load touch controls from clipboard. See local console for details. +== Dokunmatik ayarları panodan yüklenemedi. Detaylar için konsola bakın. + +Width of your own hook collision line +== Kendi kancanın çarpışma çizgisi kalınlığı + +Width of others' hook collision line +== Başkalarının kancalarının çarpışma çizgisi kalınlığı + +Preview 'Hook collisions' being pressed +== Önizleme 'Kanca çarpışmaları' gösteriliyor + +Aim +== Aim + +Active: Fire +== Aktif: Ateş etme + +Active: Hook +== Aktif: Kanca diff --git a/data/languages/ukrainian.txt b/data/languages/ukrainian.txt index 07df531f449..38cab31735f 100644 --- a/data/languages/ukrainian.txt +++ b/data/languages/ukrainian.txt @@ -102,15 +102,31 @@ Activate Activate all == Активувати усіх +[Direct touch input] +Active action +== Поточна дія + +Active: Fire +== Поточне: Вогонь + +Active: Hook +== Поточне: Гак + Add == Додати Add Clan == Додати клан +Add clanmates by entering their clan below and leaving the name blank. +== Додайте співклановців, ввівши їх клан нижче і залишивши поле "Нік" пустим. + Add Friend == Додати друга +Add friends by entering their name below or by clicking their name in the player list. +== Додайте друзів, ввівши їх нікнейми нижче або натиснувши на їх нікнейми у списку гравців. + Address == Адреса @@ -120,6 +136,13 @@ Adjust the opacity of entities belonging to other teams, such as tees and namepl AFR == АФР +[Direct touch input] +Aim +== Прицілювання + +Aim +== Прицілювання + Aim bind == Прив’язка @@ -167,40 +190,49 @@ Appearance == Вигляд Are you sure that you want to delete '%s'? -== Ви дійсно хочете видалити '%s'? +== Ви дійсно бажаєте видалити '%s'? Are you sure that you want to delete the demo '%s'? -== Ви дійсно хочете видалити демо '%s'? +== Ви дійсно бажаєте видалити демо '%s'? Are you sure that you want to delete the folder '%s'? -== Ви дійсно хочете видалити теку '%s'? +== Ви дійсно бажаєте видалити теку '%s'? + +Are you sure that you want to discard the current changes to the touch controls? +== Ви дійсно бажаєте скасувати поточні зміни до сенсорного керування? Are you sure that you want to disconnect? -== Ви дійсно хочете від’єднатися? +== Ви дійсно бажаєте від’єднатися? Are you sure that you want to disconnect and switch to a different server? -== Ви дійсно хочете від’єднатися й приєднатися до іншого сервера? +== Ви дійсно бажаєте від’єднатися й приєднатися до іншого сервера? Are you sure that you want to disconnect your dummy? -== Ви дійсно хочете від’єднати свого даммі? +== Ви дійсно бажаєте від’єднати свого даммі? + +Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls. +== Ви дійсно бажаєте імпортувати сенсорне керування з буфера обміну? Це перезапише ваші поточні налаштування. Are you sure that you want to quit? -== Ви дійсно хочете вийти? +== Ви дійсно бажаєте вийти? Are you sure that you want to remove the clan '%s' from your friends list? -== Ви дійсно хочете прибрати клан '%s' зі списку друзів? +== Ви дійсно бажаєте прибрати клан '%s' зі списку друзів? Are you sure that you want to remove the player '%s' from your friends list? -== Ви дійсно хочете прибрати гравця '%s' зі списку друзів? +== Ви дійсно бажаєте прибрати гравця '%s' зі списку друзів? Are you sure that you want to reset the controls to their defaults? -== Ви дійсно хочете скинути налаштування керувань до початкових значень? +== Ви дійсно бажаєте скинути налаштування керувань до початкових значень? + +Are you sure that you want to reset the touch controls to default? +== Ви дійсно бажаєте скинути сенсорне керування до початкових значень? Are you sure that you want to restart? -== Ви дійсно хочете перезапустити? +== Ви дійсно бажаєте перезапустити? Are you sure you want to save your skin? If a skin with this name already exists, it will be replaced. -== Ви дійсно хочете зберегти ваш скін? Якщо скін із цією назвою вже існує, його буде замінено. +== Ви дійсно бажаєте зберегти ваш скін? Якщо скін із цією назвою вже існує, його буде замінено. ASI == АЗІ @@ -332,6 +364,9 @@ Client Client message == Повідомлення клієнта +Close +== Закрити + Close the demo player == Закрити програвач демо @@ -394,12 +429,24 @@ Could not initialize the given graphics backend, reverting to the default backen Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. == Не вдалося ініціалізувати заданий графічний рушій, імовірно, ви не встановили драйвери на вбудовану відеокарту. +Could not load default touch controls from file. See local console for details. +== Не вдалося завантажити стандартне сенсорне керування з файлу. Див. локальну консоль для подробиць. + +Could not load touch controls from clipboard. See local console for details. +== Не вдалося завантажити сенсорне керування з файлу. Див. локальну консоль для подробиць. + +Could not load touch controls from file. See local console for details. +== Не вдалося завантажити сенсорне керування з файлу. Див. локальну консоль для подробиць. + Could not resolve connect address '%s'. See local console for details. == Не вдалося визначити адресу з’єднання '%s'. Див. локальну консоль для подробиць. Could not save downloaded map. Try manually deleting this file: %s == Не вдалося зберегти завантажену мапу. Спробуйте самостійно видалити цей файл: %s +Could not save touch controls to file. See local console for details. +== Не вдалося зберегти сенсорне керування до файлу. Див. локальну консоль для подробиць. + Count players only == Рахувати лише гравців @@ -506,6 +553,19 @@ Demos directory Desktop fullscreen == Робочий стіл на весь екран +Direct touch input while ingame +== Прямий сенсорний ввід у грі + +Direct touch input while spectating +== Прямий сенсорний ввід під час спостерігання + +[Direct touch input] +Disabled +== Вимкнено + +Discard changes +== Скасувати зміни + Disconnect == Від’єднатися @@ -551,6 +611,9 @@ Dummy is not allowed on this server Dynamic Camera == Динамічна камера +Edit touch controls +== Змінити сенсорне керування + Editor == Редактор @@ -608,12 +671,18 @@ Error Error loading demo == Помилка завантаження демо +Error loading touch controls +== Помилка завантаження сенсорного керування + Error playing demo == Помилка відтворення демо Error saving settings == Помилка збереження налаштувань +Error saving touch controls +== Помилка збереження сенсорного керування + EUR == ЄВР @@ -632,6 +701,9 @@ Export cut as a separate demo Export demo cut == Експортувати фрагмент демо +Export to clipboard +== Експортувати до буфера обміну + Extras == Додатково @@ -681,6 +753,10 @@ Filter connecting players Fire == Вогонь +[Direct touch input] +Fire +== Вогонь + Folder == Тека @@ -821,6 +897,10 @@ Highlighted message Hook == Гак +[Direct touch input] +Hook +== Гак + Hook Collisions == Зіткнення гака @@ -830,9 +910,6 @@ Hook collision line Hook collision line opacity == Непрозорість лінії зіткнення гака -Hook collision line width -== Товщина лінії зіткнення гака - Hook collisions == Зіткнення гака @@ -855,6 +932,9 @@ Hue Hz == Гц +Import from clipboard +== Імпортувати з буфера обміну + Indicate map finish == Позначати пройдені мапи @@ -1089,7 +1169,7 @@ Next weapon == Наст. зброя Nickname -== Псевдонім +== Нікнейм No == Ні @@ -1127,9 +1207,6 @@ No servers match your filter criteria No updates available == Немає доступних оновлень -None -== Немає - Normal Color == Колір звичайних повідомлень @@ -1146,14 +1223,17 @@ Nothing hookable Offline (%d) == Не в мережі (%d) +Offline friends and clanmates will appear here. +== Тут з'являться друзі та співклановці не в мережі. + Ok == Гаразд Online clanmates (%d) == Співклановці в мережі (%d) -Online players (%d) -== Гравці в мережі (%d) +Online friends (%d) +== Друзі в мережі (%d) Only save improvements == Зберігати лише покращення @@ -1265,6 +1345,9 @@ Prev. weapon Preview == Передперегляд +Preview 'Hook collisions' being pressed +== "Зіткнення гака" натиснуто + Quads are used for background decoration == Квади використовуються для декорацій @@ -1392,6 +1475,9 @@ Sat. Save == Зберегти +Save changes +== Зберегти зміни + Save ghost == Зберігати привида @@ -1498,7 +1584,7 @@ Show freeze bars == Показувати смугу заморозки Show friend mark (♥) in name plates -== Показувати позначку друга (♥) біля псевдонімів +== Показувати позначку друга (♥) біля ніків Show friends only == Показувати лише з друзями @@ -1534,7 +1620,7 @@ Show local time always == Завжди показувати місцевий час Show name plates -== Показувати псевдоніми +== Показувати ніки Show names in chat in team colors == Показувати імена в чаті в кольорах команди @@ -1826,6 +1912,9 @@ Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.c Unregister protocol and file extensions == Розреєструвати протокол і розширення файлів +Unsaved changes +== Незбережені зміни + Update failed! Check log… == Помилка оновлення! Перевірте журнал… @@ -1913,6 +2002,12 @@ When you cross the start line, show a ghost tee replicating the movements of you Why are you slowmo replaying to read this? == Чому ви переглядаєте це у повторі? +Width of others' hook collision line +== Товщина лінії зіткнення гака інших + +Width of your own hook collision line +== Товщина вашої лінії зіткнення гака + Windowed == У вікні @@ -1932,7 +2027,7 @@ You must restart the game for all settings to take effect. == Щоб налаштування набули чинності, перезапустіть гру. Your nickname '%s' is already used (%d points). Do you still want to use it? -== Ваш псевдонім «%s» вже зайнято (%d балів). Усе ще хочете використовувати його? +== Ваш нікнейм «%s» вже зайнято (%d балів). Усе ще бажаєте використовувати його? Your skin == Ваш скін diff --git a/data/touch_controls.json b/data/touch_controls.json new file mode 100644 index 00000000000..5d4aa431b6c --- /dev/null +++ b/data/touch_controls.json @@ -0,0 +1,311 @@ +{ + "direct-touch-ingame": "action", + "direct-touch-spectate": "aim", + "touch-buttons": [ + { + "x": 0, + "y": 833333, + "w": 200000, + "h": 166667, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "bind", + "label": "Move left", + "label-type": "localized", + "command": "+left" + } + }, + { + "x": 200000, + "y": 833333, + "w": 200000, + "h": 166667, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "bind", + "label": "Move right", + "label-type": "localized", + "command": "+right" + } + }, + { + "x": 100000, + "y": 666667, + "w": 200000, + "h": 166667, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "bind", + "label": "Jump", + "label-type": "localized", + "command": "+jump" + } + }, + { + "x": 116667, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "bind", + "label": "Prev. weapon", + "label-type": "localized", + "command": "+prevweapon" + } + }, + { + "x": 200000, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "bind", + "label": "Next weapon", + "label-type": "localized", + "command": "+nextweapon" + } + }, + { + "x": 16667, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + ], + "behavior": { + "type": "predefined", + "id": "extra-menu", + "number": 1 + } + }, + { + "x": 300000, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + "extra-menu", + "zoom-allowed" + ], + "behavior": { + "type": "bind", + "label": "Zoom out", + "label-type": "localized", + "command": "zoom-" + } + }, + { + "x": 383333, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + "extra-menu", + "zoom-allowed" + ], + "behavior": { + "type": "bind", + "label": "Default zoom", + "label-type": "localized", + "command": "zoom" + } + }, + { + "x": 466666, + "y": 16667, + "w": 83333, + "h": 83333, + "shape": "rect", + "visibilities": [ + "extra-menu", + "zoom-allowed" + ], + "behavior": { + "type": "bind", + "label": "Zoom in", + "label-type": "localized", + "command": "zoom+" + } + }, + { + "x": 16667, + "y": 133333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "extra-menu" + ], + "behavior": { + "type": "bind", + "label": "Scoreboard", + "label-type": "localized", + "command": "+scoreboard" + } + }, + { + "x": 116667, + "y": 133333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "ingame", + "extra-menu" + ], + "behavior": { + "type": "predefined", + "id": "emoticon" + } + }, + { + "x": 116667, + "y": 133333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "-ingame", + "extra-menu" + ], + "behavior": { + "type": "predefined", + "id": "spectate" + } + }, + { + "x": 216667, + "y": 133333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "extra-menu", + "-demo-player" + ], + "behavior": { + "type": "bind", + "label": "Chat", + "label-type": "localized", + "command": "chat all" + } + }, + { + "x": 316667, + "y": 133333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "extra-menu", + "-demo-player" + ], + "behavior": { + "type": "bind", + "label": "Team chat", + "label-type": "localized", + "command": "chat team" + } + }, + { + "x": 16667, + "y": 333333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "extra-menu", + "vote-active", + "-demo-player" + ], + "behavior": { + "type": "bind", + "label": "Vote yes", + "label-type": "localized", + "command": "vote yes" + } + }, + { + "x": 116667, + "y": 333333, + "w": 83333, + "h": 66667, + "shape": "rect", + "visibilities": [ + "extra-menu", + "vote-active", + "-demo-player" + ], + "behavior": { + "type": "bind", + "label": "Vote no", + "label-type": "localized", + "command": "vote no" + } + }, + { + "x": 766667, + "y": 16667, + "w": 100000, + "h": 100000, + "shape": "rect", + "visibilities": [ + "dummy-connected" + ], + "behavior": { + "type": "bind", + "label": "Toggle dummy", + "label-type": "localized", + "command": "toggle cl_dummy 0 1" + } + }, + { + "x": 883333, + "y": 16667, + "w": 100000, + "h": 100000, + "shape": "rect", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "predefined", + "id": "swap-action" + } + }, + { + "x": 755000, + "y": 580000, + "w": 225000, + "h": 400000, + "shape": "circle", + "visibilities": [ + "ingame" + ], + "behavior": { + "type": "predefined", + "id": "joystick-action" + } + } + ] +} diff --git a/datasrc/content.py b/datasrc/content.py index 7cb5fbe1a9e..8eee9776b36 100644 --- a/datasrc/content.py +++ b/datasrc/content.py @@ -245,8 +245,7 @@ def FileList(fmt, num): container.images.Add(Image("cursor", "gui_cursor.png")) container.images.Add(Image("banner", "gui_logo.png")) container.images.Add(image_emoticons) -container.images.Add(Image("console_bg", "console.png")) -container.images.Add(Image("console_bar", "console_bar.png")) +container.images.Add(Image("background_noise", "background_noise.png")) container.images.Add(image_speedup_arrow) container.images.Add(image_guibuttons) container.images.Add(image_guiicons) diff --git a/datasrc/network.py b/datasrc/network.py index 08fa59c5437..d03edce9795 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -4,7 +4,7 @@ from datatypes import Enum, Flags, NetArray, NetBool, NetEvent, NetEventEx, NetIntAny, NetIntRange, NetMessage, NetMessageEx, NetObject, NetObjectEx, NetString, NetStringHalfStrict, NetStringStrict, NetTick Emotes = ["NORMAL", "PAIN", "HAPPY", "SURPRISE", "ANGRY", "BLINK"] -PlayerFlags = ["PLAYING", "IN_MENU", "CHATTING", "SCOREBOARD", "AIM"] +PlayerFlags = ["PLAYING", "IN_MENU", "CHATTING", "SCOREBOARD", "AIM", "SPEC_CAM"] GameFlags = ["TEAMS", "FLAGS"] GameStateFlags = ["GAMEOVER", "SUDDENDEATH", "PAUSED", "RACETIME"] CharacterFlags = ["SOLO", "JETPACK", "COLLISION_DISABLED", "ENDLESS_HOOK", "ENDLESS_JUMP", "SUPER", @@ -540,6 +540,12 @@ NetIntRange("m_Show", 0, 2), ]), + NetMessageEx("Cl_CameraInfo", "camera-info@netmsg.ddnet.org", [ + NetIntAny("m_Zoom"), + NetIntAny("m_Deadzone"), + NetIntAny("m_FollowFactor"), + ]), + NetMessageEx("Sv_TeamsState", "teamsstate@netmsg.ddnet.tw", []), NetMessageEx("Sv_DDRaceTime", "ddrace-time@netmsg.ddnet.tw", [ diff --git a/scripts/android/README.md b/scripts/android/README.md index a34bdc315a9..7bd362affaa 100644 --- a/scripts/android/README.md +++ b/scripts/android/README.md @@ -1,9 +1,8 @@ -Requirements for building for Android -===================================== +Requirements for building for Android on Linux +============================================== - At least 10-15 GiB of free disk space. - First follow the general instructions for setting up https://github.com/ddnet/ddnet for building on Linux. - This guide has only been tested on Linux. - Note: Use a stable version of Rust. Using the nightly version results in linking errors. - Install the Android NDK (version 26) in the same location where Android Studio would unpack it (`~/Android/Sdk/ndk/`): @@ -55,10 +54,6 @@ Requirements for building for Android ```shell sudo apt install openjdk-21-jdk ``` -- Install 7zip for building `ddnet-libs`: - ```shell - sudo apt install p7zip-full - ``` - Install ninja: ```shell sudo apt install ninja-build @@ -72,11 +67,57 @@ Requirements for building for Android brew install coreutils ``` - Build the `ddnet-libs` for Android (see below). Follow all above steps first. + Alternatively, use the precompiled libraries from https://github.com/ddnet/ddnet-libs/. + +Requirements for building for Android on Windows using MSYS2 +============================================================ +- At least 50 GiB of free disk space if you start from scratch. +- First install MSYS2 (https://www.msys2.org/wiki/MSYS2-installation/) as well as all required packages for building DDNet using MSYS2 on Windows. + (There is currently no more detailed guide for this.) +- Install cargo-ndk and add Android targets to rustup to build Rust with the Android NDK: + ```shell + cargo install cargo-ndk + rustup target add armv7-linux-androideabi + rustup target add i686-linux-android + rustup target add aarch64-linux-android + rustup target add x86_64-linux-android + ``` +- Install JDK 21, e.g. from https://adoptium.net/temurin/releases/?package=jdk&os=windows&version=21 +- Install ninja: + ```shell + pacman -S mingw-w64-x86_64-ninja + ``` +- Install curl: + ```shell + pacman -S mingw-w64-x86_64-curl + ``` +- Install coreutils so `nproc` is available: + ```shell + pacman -S coreutils + ``` +- Compiling the libraries is not supported on Windows yet. Use the precompiled libraries from https://github.com/ddnet/ddnet-libs/, + i.e. make sure to also clone the ddnet-libs submodule, or compile the libraries on a separate Linux system. +- Set the `ANDROID_HOME` environment variable to override the location where the Android SDK will be installed, e.g. `C:/Android/SDK`. Make sure to only use forward slashes. +- Install either Android Studio (which includes an SDK manager GUI) from https://developer.android.com/studio or the standalone command-line tools (which include the `sdkmanager` tool) from https://developer.android.com/studio/#command-line-tools-only. +- When using the command-line tools: Ensure the command-line tools are installed at the expected location, so `%ANDROID_HOME%/cmdline-tools/latest/bin` should contain `sdkmanager.bat`. +Accept the licenses using the SDK manager, otherwise the Gradle build will fail if the licenses have not been accepted: + ```shell + yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager.bat --licenses + ``` +- Install the following using the SDK Manager in Android Studio (Tools menu) or the `sdkmanager` command-line tool: + - SDK Platform for API Level 34 + - NDK (Side by side) + - Android SDK Build-Tools (latest version) How to build the `ddnet-libs` for Android ========================================= +- Note: This has only been tested on Linux. +- Install 7-Zip: + ```shell + sudo apt install p7zip-full + ``` - There is a script to automatically download and build all repositories, this requires an active internet connection and can take around 30 minutes: ```shell @@ -94,10 +135,10 @@ How to build the `ddnet-libs` for Android cp -r build-android-libs/ddnet-libs/. ddnet-libs/ ``` - How to build the DDNet client for Android ========================================= +- These steps are identical on Linux and Windows, except on Windows `bash` must be used as terminal and not `cmd.exe` or PowerShell. - Open a terminal inside the `ddnet` project root directory and run the following: ```shell scripts/android/cmake_android.sh @@ -144,3 +185,12 @@ How to build the DDNet client for Android - Note that you should only generate a signing key once (and make backups). Users can only update apps automatically if the same package name and signing key have been used, else they must manually uninstall the old app. + +Common problems and solutions +============================= + +- If the Gradle build fails with errors messages indicating bugs relating to files in the Gradle cache, try to clear the Gradle cache by deleting the contents of the folder `~/.gradle/caches` (`%USERPROFILE%/.gradle/caches` on Windows). +- The Gradle build may show a message that the JDK version could not be determined but this can safely be ignored. +- The Gradle build will fail with errors messages indicating an unsupported class file version if a different version of the JDK is used than specified in `build.gradle`. + When incrementing the supported JDK version, the Gradle version also has to be incremented according to https://docs.gradle.org/current/userguide/compatibility.html. + If you have multiple JDKs installed, you can set the JDK version for Gradle using the property `org.gradle.java.home` in the `gradle.properties` file in your Gradle home directory. diff --git a/scripts/android/cmake_android.sh b/scripts/android/cmake_android.sh index d541250a366..2e91565bc41 100755 --- a/scripts/android/cmake_android.sh +++ b/scripts/android/cmake_android.sh @@ -1,9 +1,17 @@ #!/bin/bash +set -e -# $HOME must be used instead of ~ else cargo-ndk cannot find the folder -export ANDROID_HOME=$HOME/Android/Sdk -MAKEFLAGS=-j$(nproc) -export MAKEFLAGS +# Ensure that binaries from MSYS2 are preferred over Windows-native commands like find and sort which work differently. +PATH="/usr/bin/:$PATH" + +# $ANDROID_HOME can be used-defined, else the default location is used. Important notes: +# - The path must not contain spaces on Windows. +# - $HOME must be used instead of ~ else cargo-ndk cannot find the folder. +ANDROID_HOME="${ANDROID_HOME:-$HOME/Android/Sdk}" +export ANDROID_HOME + +BUILD_FLAGS="${BUILD_FLAGS:--j$(nproc)}" +export BUILD_FLAGS ANDROID_NDK_VERSION="$(cd "$ANDROID_HOME/ndk" && find . -maxdepth 1 | sort -n | tail -1)" ANDROID_NDK_VERSION="${ANDROID_NDK_VERSION:2}" @@ -122,8 +130,6 @@ fi export TW_VERSION_NAME=$ANDROID_VERSION_NAME -printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Building cmake..." - function build_for_type() { cmake \ -H. \ @@ -150,47 +156,32 @@ function build_for_type() { -DVIDEORECORDER=OFF ( cd "${BUILD_FOLDER}/$ANDROID_SUB_BUILD_DIR/$1" || exit 1 - cmake --build . --target game-client + # We want word splitting + # shellcheck disable=SC2086 + cmake --build . --target game-client $BUILD_FLAGS ) } mkdir -p "${BUILD_FOLDER}" if [[ "${ANDROID_BUILD}" == "arm" || "${ANDROID_BUILD}" == "all" ]]; then - build_for_type arm armeabi-v7a armv7-linux-androideabi & - PID_BUILD_ARM=$! + printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Building cmake (arm)..." + build_for_type arm armeabi-v7a armv7-linux-androideabi fi if [[ "${ANDROID_BUILD}" == "arm64" || "${ANDROID_BUILD}" == "all" ]]; then - build_for_type arm64 arm64-v8a aarch64-linux-android & - PID_BUILD_ARM64=$! + printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Building cmake (arm64)..." + build_for_type arm64 arm64-v8a aarch64-linux-android fi if [[ "${ANDROID_BUILD}" == "x86" || "${ANDROID_BUILD}" == "all" ]]; then - build_for_type x86 x86 i686-linux-android & - PID_BUILD_X86=$! + printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Building cmake (x86)..." + build_for_type x86 x86 i686-linux-android fi if [[ "${ANDROID_BUILD}" == "x86_64" || "${ANDROID_BUILD}" == "all" ]]; then - build_for_type x86_64 x86_64 x86_64-linux-android & - PID_BUILD_X86_64=$! -fi - -if [ -n "$PID_BUILD_ARM" ] && ! wait "$PID_BUILD_ARM"; then - printf "${COLOR_RED}%s${COLOR_RESET}\n" "Building for arm failed" - exit 1 -fi -if [ -n "$PID_BUILD_ARM64" ] && ! wait "$PID_BUILD_ARM64"; then - printf "${COLOR_RED}%s${COLOR_RESET}\n" "Building for arm64 failed" - exit 1 -fi -if [ -n "$PID_BUILD_X86" ] && ! wait "$PID_BUILD_X86"; then - printf "${COLOR_RED}%s${COLOR_RESET}\n" "Building for x86 failed" - exit 1 -fi -if [ -n "$PID_BUILD_X86_64" ] && ! wait "$PID_BUILD_X86_64"; then - printf "${COLOR_RED}%s${COLOR_RESET}\n" "Building for x86_64 failed" - exit 1 + printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Building cmake (x86_64)..." + build_for_type x86_64 x86_64 x86_64-linux-android fi printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Copying project files..." @@ -259,14 +250,8 @@ cp ./cacert.pem ./assets/asset_integrity_files/data/cacert.pem || exit 1 printf "${COLOR_CYAN}%s${COLOR_RESET}\n" "Creating integrity index file..." ( cd assets/asset_integrity_files || exit 1 - tmpfile="$(mktemp /tmp/hash_strings.XXX)" - - find data -iname "*" -type f -print0 | while IFS= read -r -d $'\0' file; do - sha_hash=$(sha256sum "$file" | cut -d' ' -f 1) - echo "$file $sha_hash" >> "$tmpfile" - done - + find data -iname "*" -type f -print0 | xargs -0 sha256sum | awk '{gsub(/^\*/, "", $2); print substr($0, index($0, $2)), $1}' > "$tmpfile" full_hash="$(sha256sum "$tmpfile" | cut -d' ' -f 1)" rm -f "integrity.txt" diff --git a/src/engine/client.h b/src/engine/client.h index e65c5a03fcf..bfc0e29eae1 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -393,6 +393,7 @@ class IGameClient : public IInterface virtual int TranslateSnap(class CSnapshot *pSnapDstSix, class CSnapshot *pSnapSrcSeven, int Conn, bool Dummy) = 0; virtual bool CheckNewInput() = 0; + virtual void InitializeLanguage() = 0; }; void SnapshotRemoveExtraProjectileInfo(class CSnapshot *pSnap); diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 8cf54767a11..7e07fb5287a 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -86,7 +86,7 @@ static const ColorRGBA gs_ClientNetworkErrPrintColor{1.0f, 0.25f, 0.25f, 1.0f}; CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta, true, [&]() { UpdateDemoIntraTimers(); }), m_InputtimeMarginGraph(128), - m_GametimeMarginGraph(128), + m_aGametimeMarginGraphs{128, 128}, m_FpsGraph(4096) { m_StateStartTime = time_get(); @@ -645,7 +645,7 @@ void CClient::Connect(const char *pAddress, const char *pPassword) SetState(IClient::STATE_CONNECTING); m_InputtimeMarginGraph.Init(-150.0f, 150.0f); - m_GametimeMarginGraph.Init(-150.0f, 150.0f); + m_aGametimeMarginGraphs[CONN_MAIN].Init(-150.0f, 150.0f); GenerateTimeoutCodes(aConnectAddrs, NumConnectAddrs); } @@ -764,6 +764,8 @@ void CClient::DummyConnect() m_aNetClient[CONN_DUMMY].Connect7(m_aNetClient[CONN_MAIN].ServerAddress(), 1); else m_aNetClient[CONN_DUMMY].Connect(m_aNetClient[CONN_MAIN].ServerAddress(), 1); + + m_aGametimeMarginGraphs[CONN_DUMMY].Init(-150.0f, 150.0f); } void CClient::DummyDisconnect(const char *pReason) @@ -909,7 +911,13 @@ void CClient::DebugRender() { if(m_SnapshotDelta.GetDataRate(i)) { - str_format(aBuffer, sizeof(aBuffer), "%5d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i) / 8, m_SnapshotDelta.GetDataUpdates(i), + str_format( + aBuffer, + sizeof(aBuffer), + "%5d %20s: %8" PRIu64 " %8" PRIu64 " %8" PRIu64, + i, + GameClient()->GetItemName(i), + m_SnapshotDelta.GetDataRate(i) / 8, m_SnapshotDelta.GetDataUpdates(i), (m_SnapshotDelta.GetDataRate(i) / m_SnapshotDelta.GetDataUpdates(i)) / 8); Graphics()->QuadsText(2, 100 + y * 12, 16, aBuffer); y++; @@ -922,14 +930,28 @@ void CClient::DebugRender() int Type = m_aapSnapshots[g_Config.m_ClDummy][IClient::SNAP_CURRENT]->m_pAltSnap->GetExternalItemType(i); if(Type == UUID_INVALID) { - str_format(aBuffer, sizeof(aBuffer), "%5d %20s: %8d %8d %8d", i, "Unknown UUID", m_SnapshotDelta.GetDataRate(i) / 8, m_SnapshotDelta.GetDataUpdates(i), + str_format( + aBuffer, + sizeof(aBuffer), + "%5d %20s: %8" PRIu64 " %8" PRIu64 " %8" PRIu64, + i, + "Unknown UUID", + m_SnapshotDelta.GetDataRate(i) / 8, + m_SnapshotDelta.GetDataUpdates(i), (m_SnapshotDelta.GetDataRate(i) / m_SnapshotDelta.GetDataUpdates(i)) / 8); Graphics()->QuadsText(2, 100 + y * 12, 16, aBuffer); y++; } else if(Type != i) { - str_format(aBuffer, sizeof(aBuffer), "%5d %20s: %8d %8d %8d", Type, GameClient()->GetItemName(Type), m_SnapshotDelta.GetDataRate(i) / 8, m_SnapshotDelta.GetDataUpdates(i), + str_format( + aBuffer, + sizeof(aBuffer), + "%5d %20s: %8" PRIu64 " %8" PRIu64 " %8" PRIu64, + Type, + GameClient()->GetItemName(Type), + m_SnapshotDelta.GetDataRate(i) / 8, + m_SnapshotDelta.GetDataUpdates(i), (m_SnapshotDelta.GetDataRate(i) / m_SnapshotDelta.GetDataUpdates(i)) / 8); Graphics()->QuadsText(2, 100 + y * 12, 16, aBuffer); y++; @@ -954,8 +976,8 @@ void CClient::DebugRender() m_FpsGraph.Render(Graphics(), TextRender(), x, sp * 5, w, h, "FPS"); m_InputtimeMarginGraph.Scale(5 * time_freq()); m_InputtimeMarginGraph.Render(Graphics(), TextRender(), x, sp * 6 + h, w, h, "Prediction Margin"); - m_GametimeMarginGraph.Scale(5 * time_freq()); - m_GametimeMarginGraph.Render(Graphics(), TextRender(), x, sp * 7 + h * 2, w, h, "Gametime Margin"); + m_aGametimeMarginGraphs[g_Config.m_ClDummy].Scale(5 * time_freq()); + m_aGametimeMarginGraphs[g_Config.m_ClDummy].Render(Graphics(), TextRender(), x, sp * 7 + h * 2, w, h, "Gametime Margin"); } } @@ -2079,7 +2101,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket, int Conn, bool Dummy) int64_t Now = m_aGameTime[Conn].Get(time_get()); int64_t TickStart = GameTick * time_freq() / GameTickSpeed(); int64_t TimeLeft = (TickStart - Now) * 1000 / time_freq(); - m_aGameTime[Conn].Update(&m_GametimeMarginGraph, (GameTick - 1) * time_freq() / GameTickSpeed(), TimeLeft, CSmoothTime::ADJUSTDIRECTION_DOWN); + m_aGameTime[Conn].Update(&m_aGametimeMarginGraphs[Conn], (GameTick - 1) * time_freq() / GameTickSpeed(), TimeLeft, CSmoothTime::ADJUSTDIRECTION_DOWN); } if(g_Config.m_ClRunOnJoinConsole && m_aReceivedSnapshots[Conn] > g_Config.m_ClRunOnJoinDelay && !m_aCodeRunAfterJoinConsole[Conn]) { @@ -3033,6 +3055,9 @@ void CClient::Run() Graphics()->Clear(0, 0, 0); Graphics()->Swap(); + // init localization first, making sure all errors during init can be localized + GameClient()->InitializeLanguage(); + // init sound, allowed to fail const bool SoundInitFailed = Sound()->Init() != 0; @@ -4419,7 +4444,7 @@ void CClient::RegisterCommands() m_pConsole->Register("demo_slice_start", "", CFGFLAG_CLIENT, Con_DemoSliceBegin, this, "Mark the beginning of a demo cut"); m_pConsole->Register("demo_slice_end", "", CFGFLAG_CLIENT, Con_DemoSliceEnd, this, "Mark the end of a demo cut"); m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play/pause the current demo"); - m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set current demo speed"); + m_pConsole->Register("demo_speed", "f[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set current demo speed"); m_pConsole->Register("save_replay", "?i[length] ?r[filename]", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of the last defined amount of seconds"); m_pConsole->Register("benchmark_quit", "i[seconds] r[file]", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_BenchmarkQuit, this, "Benchmark frame times for number of seconds to file, then quit"); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index ea95d99ddcf..adcbbc896a7 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -188,7 +188,7 @@ class CClient : public IClient, public CDemoPlayer::IListener // graphs CGraph m_InputtimeMarginGraph; - CGraph m_GametimeMarginGraph; + CGraph m_aGametimeMarginGraphs[NUM_DUMMIES]; CGraph m_FpsGraph; // the game snapshots are modifiable by the game diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 1a7b023eb74..06b841c3c5e 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -1107,16 +1107,6 @@ class CGraphics_Threaded : public IEngineGraphics void DrawRect4(float x, float y, float w, float h, ColorRGBA ColorTopLeft, ColorRGBA ColorTopRight, ColorRGBA ColorBottomLeft, ColorRGBA ColorBottomRight, int Corners, float Rounding) override; void DrawCircle(float CenterX, float CenterY, float Radius, int Segments) override; - const GL_STexCoord *GetCurTextureCoordinates() override - { - return m_aTexture; - } - - const GL_SColor *GetCurColor() override - { - return m_aColor; - } - int CreateQuadContainer(bool AutomaticUpload = true) override; void QuadContainerChangeAutomaticUpload(int ContainerIndex, bool AutomaticUpload) override; void QuadContainerUpload(int ContainerIndex) override; diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index f590ba8c025..cb4ec78b503 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -301,6 +301,14 @@ const std::vector &CInput::TouchFingerStates() const return m_vTouchFingerStates; } +void CInput::ClearTouchDeltas() +{ + for(CTouchFingerState &TouchFingerState : m_vTouchFingerStates) + { + TouchFingerState.m_Delta = vec2(0.0f, 0.0f); + } +} + std::string CInput::GetClipboardText() { char *pClipboardText = SDL_GetClipboardText(); @@ -348,10 +356,7 @@ void CInput::Clear() mem_zero(m_aInputState, sizeof(m_aInputState)); mem_zero(m_aInputCount, sizeof(m_aInputCount)); m_vInputEvents.clear(); - for(CTouchFingerState &TouchFingerState : m_vTouchFingerStates) - { - TouchFingerState.m_Delta = vec2(0.0f, 0.0f); - } + ClearTouchDeltas(); } float CInput::GetUpdateTime() const @@ -565,6 +570,7 @@ void CInput::HandleTouchDownEvent(const SDL_TouchFingerEvent &Event) TouchFingerState.m_Finger.m_FingerId = Event.fingerId; TouchFingerState.m_Position = vec2(Event.x, Event.y); TouchFingerState.m_Delta = vec2(Event.dx, Event.dy); + TouchFingerState.m_PressTime = time_get_nanoseconds(); m_vTouchFingerStates.emplace_back(TouchFingerState); } diff --git a/src/engine/client/input.h b/src/engine/client/input.h index d0098abf1b5..7d491571e68 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -146,6 +146,7 @@ class CInput : public IEngineInput bool NativeMousePressed(int Index) const override; const std::vector &TouchFingerStates() const override; + void ClearTouchDeltas() override; std::string GetClipboardText() override; void SetClipboardText(const char *pText) override; diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 610f46e3082..a4f44119552 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -369,27 +369,12 @@ class IGraphics : public IInterface CQuadItem() {} CQuadItem(float x, float y, float w, float h) : m_X(x), m_Y(y), m_Width(w), m_Height(h) {} - void Set(float x, float y, float w, float h) - { - m_X = x; - m_Y = y; - m_Width = w; - m_Height = h; - } - - CFreeformItem ToFreeForm() const - { - return CFreeformItem(m_X, m_Y, m_X + m_Width, m_Y, m_X, m_Y + m_Height, m_X + m_Width, m_Y + m_Height); - } }; virtual void QuadsDraw(CQuadItem *pArray, int Num) = 0; virtual void QuadsDrawTL(const CQuadItem *pArray, int Num) = 0; virtual void QuadsTex3DDrawTL(const CQuadItem *pArray, int Num) = 0; - virtual const GL_STexCoord *GetCurTextureCoordinates() = 0; - virtual const GL_SColor *GetCurColor() = 0; - virtual int CreateQuadContainer(bool AutomaticUpload = true) = 0; virtual void QuadContainerChangeAutomaticUpload(int ContainerIndex, bool AutomaticUpload) = 0; virtual void QuadContainerUpload(int ContainerIndex) = 0; diff --git a/src/engine/input.h b/src/engine/input.h index 9aa87335826..873423b8085 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -128,6 +129,10 @@ class IInput : public IInterface * @remark This is reset to zero at the end of each frame. */ vec2 m_Delta; + /** + * The time when this finger was first pressed down. + */ + std::chrono::nanoseconds m_PressTime; }; /** * Returns a vector of the states of all touch fingers currently being pressed down on touch devices. @@ -137,6 +142,12 @@ class IInput : public IInterface * @return vector of all touch finger states */ virtual const std::vector &TouchFingerStates() const = 0; + /** + * Must be called after the touch finger states have been used during the client update to ensure that + * touch deltas are only accumulated until the next update. If the touch states are only used during + * rendering, i.e. for user interfaces, then this is called automatically by calling @link Clear @endlink. + */ + virtual void ClearTouchDeltas() = 0; // clipboard virtual std::string GetClipboardText() = 0; diff --git a/src/engine/server.h b/src/engine/server.h index f6d950e5f29..a459f8d8a5e 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -273,7 +273,6 @@ class IServer : public IInterface virtual bool DnsblPending(int ClientId) = 0; virtual bool DnsblBlack(int ClientId) = 0; virtual const char *GetAnnouncementLine() = 0; - virtual void ReadAnnouncementsFile(const char *pFileName) = 0; virtual bool ClientPrevIngame(int ClientId) = 0; virtual const char *GetNetErrorString(int ClientId) = 0; virtual void ResetNetErrorString(int ClientId) = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 19d00781d7e..b2c0e93b67e 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2773,7 +2773,7 @@ int CServer::Run() Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } - ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName); + ReadAnnouncementsFile(); // process pending commands m_pConsole->StoreCommands(false); @@ -3618,6 +3618,12 @@ void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData) } } +void CServer::ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData) +{ + CServer *pThis = static_cast(pUserData); + pThis->ReadAnnouncementsFile(); +} + void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); @@ -3809,7 +3815,7 @@ void CServer::ConchainAnnouncementFileName(IConsole::IResult *pResult, void *pUs pfnCallback(pResult, pCallbackUserData); if(Changed) { - pSelf->ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName); + pSelf->ReadAnnouncementsFile(); } } @@ -3873,6 +3879,8 @@ void CServer::RegisterCommands() Console()->Register("auth_remove", "s[ident]", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, ConAuthRemove, this, "Remove a rcon key"); Console()->Register("auth_list", "", CFGFLAG_SERVER, ConAuthList, this, "List all rcon keys"); + Console()->Register("reload_announcement", "", CFGFLAG_SERVER, ConReloadAnnouncement, this, "Reload the announcements"); + RustVersionRegister(*Console()); Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); @@ -3936,17 +3944,17 @@ void CServer::GetClientAddr(int ClientId, NETADDR *pAddr) const } } -void CServer::ReadAnnouncementsFile(const char *pFileName) +void CServer::ReadAnnouncementsFile() { m_vAnnouncements.clear(); - if(pFileName[0] == '\0') + if(g_Config.m_SvAnnouncementFileName[0] == '\0') return; CLineReader LineReader; - if(!LineReader.OpenFile(m_pStorage->OpenFile(pFileName, IOFLAG_READ, IStorage::TYPE_ALL))) + if(!LineReader.OpenFile(m_pStorage->OpenFile(g_Config.m_SvAnnouncementFileName, IOFLAG_READ, IStorage::TYPE_ALL))) { - dbg_msg("announcements", "failed to open '%s'", pFileName); + log_error("server", "Failed load announcements from '%s'", g_Config.m_SvAnnouncementFileName); return; } while(const char *pLine = LineReader.Get()) @@ -3956,13 +3964,14 @@ void CServer::ReadAnnouncementsFile(const char *pFileName) m_vAnnouncements.emplace_back(pLine); } } + log_info("server", "Loaded %" PRIzu " announcements", m_vAnnouncements.size()); } const char *CServer::GetAnnouncementLine() { if(m_vAnnouncements.empty()) { - return 0; + return nullptr; } else if(m_vAnnouncements.size() == 1) { diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 9e5b2c2e48f..36ba63a42ac 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -411,6 +411,8 @@ class CServer : public IServer static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData); static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData); + static void ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData); + static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainCommandAccessUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); @@ -444,7 +446,7 @@ class CServer : public IServer void GetClientAddr(int ClientId, NETADDR *pAddr) const override; int m_aPrevStates[MAX_CLIENTS]; const char *GetAnnouncementLine() override; - void ReadAnnouncementsFile(const char *pFileName) override; + void ReadAnnouncementsFile(); int *GetIdMap(int ClientId) override; diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 66e3f1c9331..690dc9e9ee3 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -24,6 +24,11 @@ MACRO_CONFIG_INT(ClAntiPingSmooth, cl_antiping_smooth, 0, 0, 1, CFGFLAG_CLIENT | MACRO_CONFIG_INT(ClAntiPingGunfire, cl_antiping_gunfire, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Predict gunfire and show predicted weapon physics (with cl_antiping_grenade 1 and cl_antiping_weapons 1)") MACRO_CONFIG_INT(ClPredictionMargin, cl_prediction_margin, 10, 1, 300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Prediction margin in ms (adds latency, can reduce lag from ping jumps)") MACRO_CONFIG_INT(ClSubTickAiming, cl_sub_tick_aiming, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Send aiming data at sub-tick accuracy") +#if defined(CONF_PLATFORM_ANDROID) +MACRO_CONFIG_INT(ClTouchControls, cl_touch_controls, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable ingame touch controls") +#else +MACRO_CONFIG_INT(ClTouchControls, cl_touch_controls, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable ingame touch controls") +#endif MACRO_CONFIG_INT(ClNameplates, cl_nameplates, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show name plates") MACRO_CONFIG_INT(ClAfkEmote, cl_afk_emote, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show zzz emote next to afk players") diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index 3593929ea68..6ec54c60a41 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -5,14 +5,20 @@ #include "compression.h" #include "packer.h" -void CPacker::Reset() +CAbstractPacker::CAbstractPacker(unsigned char *pBuffer, size_t Size) : + m_pBuffer(pBuffer), + m_BufferSize(Size) +{ +} + +void CAbstractPacker::Reset() { m_Error = false; - m_pCurrent = m_aBuffer; - m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE; + m_pCurrent = m_pBuffer; + m_pEnd = m_pCurrent + m_BufferSize; } -void CPacker::AddInt(int i) +void CAbstractPacker::AddInt(int i) { if(m_Error) return; @@ -26,42 +32,51 @@ void CPacker::AddInt(int i) m_pCurrent = pNext; } -void CPacker::AddString(const char *pStr, int Limit) +void CAbstractPacker::AddString(const char *pStr, int Limit, bool AllowTruncation) { if(m_Error) return; + unsigned char *const pPrevCurrent = m_pCurrent; if(Limit <= 0) { - Limit = PACKER_BUFFER_SIZE; + Limit = m_BufferSize; } - while(*pStr && Limit != 0) + while(*pStr) { int Codepoint = str_utf8_decode(&pStr); if(Codepoint == -1) { Codepoint = 0xfffd; // Unicode replacement character. } - char aGarbage[4]; - int Length = str_utf8_encode(aGarbage, Codepoint); + char aEncoded[4]; + const int Length = str_utf8_encode(aEncoded, Codepoint); + // Limit must ensure space for null termination if desired. if(Limit < Length) { - break; + if(AllowTruncation) + { + break; + } + m_Error = true; + m_pCurrent = pPrevCurrent; + return; } // Ensure space for the null termination. - if(m_pEnd - m_pCurrent < Length + 1) + if(m_pCurrent + Length + 1 > m_pEnd) { m_Error = true; - break; + m_pCurrent = pPrevCurrent; + return; } - Length = str_utf8_encode((char *)m_pCurrent, Codepoint); + mem_copy(m_pCurrent, aEncoded, Length); m_pCurrent += Length; Limit -= Length; } - *m_pCurrent++ = 0; + *m_pCurrent++ = '\0'; } -void CPacker::AddRaw(const void *pData, int Size) +void CAbstractPacker::AddRaw(const void *pData, int Size) { if(m_Error) return; diff --git a/src/engine/shared/packer.h b/src/engine/shared/packer.h index 3ee622f6fd9..d08b74c7bb5 100644 --- a/src/engine/shared/packer.h +++ b/src/engine/shared/packer.h @@ -3,31 +3,53 @@ #ifndef ENGINE_SHARED_PACKER_H #define ENGINE_SHARED_PACKER_H -class CPacker -{ -public: - enum - { - PACKER_BUFFER_SIZE = 1024 * 64 - }; +#include +/** + * Abstract packer implementation. Subclasses must supply the buffer. + */ +class CAbstractPacker +{ private: - unsigned char m_aBuffer[PACKER_BUFFER_SIZE]; + unsigned char *const m_pBuffer; + const size_t m_BufferSize; unsigned char *m_pCurrent; unsigned char *m_pEnd; bool m_Error; +protected: + CAbstractPacker(unsigned char *pBuffer, size_t Size); + public: void Reset(); void AddInt(int i); - void AddString(const char *pStr, int Limit = PACKER_BUFFER_SIZE); + void AddString(const char *pStr, int Limit = 0, bool AllowTruncation = true); void AddRaw(const void *pData, int Size); - int Size() const { return (int)(m_pCurrent - m_aBuffer); } - const unsigned char *Data() const { return m_aBuffer; } + int Size() const { return (int)(m_pCurrent - m_pBuffer); } + const unsigned char *Data() const { return m_pBuffer; } bool Error() const { return m_Error; } }; +/** + * Default packer with buffer for networking. + */ +class CPacker : public CAbstractPacker +{ +public: + enum + { + PACKER_BUFFER_SIZE = 1024 * 2 + }; + CPacker() : + CAbstractPacker(m_aBuffer, sizeof(m_aBuffer)) + { + } + +private: + unsigned char m_aBuffer[PACKER_BUFFER_SIZE]; +}; + class CUnpacker { const unsigned char *m_pStart; diff --git a/src/engine/shared/protocol.h b/src/engine/shared/protocol.h index 363e35c9412..2608a9297ee 100644 --- a/src/engine/shared/protocol.h +++ b/src/engine/shared/protocol.h @@ -127,6 +127,7 @@ enum VERSION_DDNET_MULTI_LASER = 16040, VERSION_DDNET_ENTITY_NETOBJS = 16200, VERSION_DDNET_REDIRECT = 17020, + VERSION_DDNET_PLAYERFLAG_SPEC_CAM = 18090, }; typedef std::bitset CClientMask; diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 99d2b50ccc7..7f9706de930 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -230,7 +230,7 @@ int CSnapshotDelta::DiffItem(const int *pPast, const int *pCurrent, int *pOut, i return Needed; } -void CSnapshotDelta::UndiffItem(const int *pPast, const int *pDiff, int *pOut, int Size, int *pDataRate) +void CSnapshotDelta::UndiffItem(const int *pPast, const int *pDiff, int *pOut, int Size, uint64_t *pDataRate) { while(Size) { @@ -243,7 +243,7 @@ void CSnapshotDelta::UndiffItem(const int *pPast, const int *pDiff, int *pOut, i { unsigned char aBuf[CVariableInt::MAX_BYTES_PACKED]; unsigned char *pEnd = CVariableInt::Pack(aBuf, *pDiff, sizeof(aBuf)); - *pDataRate += (int)(pEnd - (unsigned char *)aBuf) * 8; + *pDataRate += (uint64_t)(pEnd - (unsigned char *)aBuf) * 8; } pOut++; diff --git a/src/engine/shared/snapshot.h b/src/engine/shared/snapshot.h index 7a001221d5d..105f05d1f9b 100644 --- a/src/engine/shared/snapshot.h +++ b/src/engine/shared/snapshot.h @@ -89,18 +89,18 @@ class CSnapshotDelta }; short m_aItemSizes[MAX_NETOBJSIZES]; short m_aItemSizes7[MAX_NETOBJSIZES]; - int m_aSnapshotDataRate[CSnapshot::MAX_TYPE + 1]; - int m_aSnapshotDataUpdates[CSnapshot::MAX_TYPE + 1]; + uint64_t m_aSnapshotDataRate[CSnapshot::MAX_TYPE + 1]; + uint64_t m_aSnapshotDataUpdates[CSnapshot::MAX_TYPE + 1]; CData m_Empty; - static void UndiffItem(const int *pPast, const int *pDiff, int *pOut, int Size, int *pDataRate); + static void UndiffItem(const int *pPast, const int *pDiff, int *pOut, int Size, uint64_t *pDataRate); public: static int DiffItem(const int *pPast, const int *pCurrent, int *pOut, int Size); CSnapshotDelta(); CSnapshotDelta(const CSnapshotDelta &Old); - int GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; } - int GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; } + uint64_t GetDataRate(int Index) const { return m_aSnapshotDataRate[Index]; } + uint64_t GetDataUpdates(int Index) const { return m_aSnapshotDataUpdates[Index]; } void SetStaticsize(int ItemType, size_t Size); void SetStaticsize7(int ItemType, size_t Size); const CData *EmptyDelta() const; diff --git a/src/game/client/component.h b/src/game/client/component.h index 639c8b140ad..9d14615a058 100644 --- a/src/game/client/component.h +++ b/src/game/client/component.h @@ -212,6 +212,14 @@ class CComponent * @param Event The input event. */ virtual bool OnInput(const IInput::CEvent &Event) { return false; } + /** + * Called with all current touch finger states. + * + * @param vTouchFingerStates The touch finger states to be handled. + * + * @return `true` if the component used the touch events, `false` otherwise + */ + virtual bool OnTouchState(const std::vector &vTouchFingerStates) { return false; } }; #endif diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index d57840a0dcc..2d64955bec5 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -35,6 +35,11 @@ CCamera::CCamera() m_WasSpectating = false; m_CameraSmoothing = false; + + m_LastMousePos = vec2(0, 0); + m_DyncamTargetCameraOffset = vec2(0, 0); + mem_zero(m_aDyncamCurrentCameraOffset, sizeof(m_aDyncamCurrentCameraOffset)); + m_DyncamSmoothingSpeedBias = 0.5f; } float CCamera::CameraSmoothingProgress(float CurrentTime) const @@ -89,7 +94,7 @@ void CCamera::ChangeZoom(float Target, int Smoothness) m_Zooming = true; } -void CCamera::OnRender() +void CCamera::UpdateCamera() { if(m_Zooming) { @@ -112,6 +117,58 @@ void CCamera::OnRender() m_Zoom = clamp(m_Zoom, MinZoomLevel(), MaxZoomLevel()); } + if(!ZoomAllowed()) + { + m_ZoomSet = false; + m_Zoom = 1.0f; + m_Zooming = false; + } + else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10) + { + m_ZoomSet = true; + OnReset(); + } + + if(m_pClient->m_Snap.m_SpecInfo.m_Active && !m_pClient->m_Snap.m_SpecInfo.m_UsePosition) + return; + + float DeltaTime = Client()->RenderFrameTime(); + + if(g_Config.m_ClDyncamSmoothness > 0) + { + float CameraSpeed = (1.0f - (g_Config.m_ClDyncamSmoothness / 100.0f)) * 9.5f + 0.5f; + float CameraStabilizingFactor = 1 + g_Config.m_ClDyncamStabilizing / 100.0f; + + m_DyncamSmoothingSpeedBias += CameraSpeed * DeltaTime; + if(g_Config.m_ClDyncam) + { + m_DyncamSmoothingSpeedBias -= length(m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] - m_LastMousePos) * std::log10(CameraStabilizingFactor) * 0.02f; + m_DyncamSmoothingSpeedBias = clamp(m_DyncamSmoothingSpeedBias, 0.5f, CameraSpeed); + } + else + { + m_DyncamSmoothingSpeedBias = maximum(5.0f, CameraSpeed); // make sure toggle back is fast + } + } + + m_DyncamTargetCameraOffset = vec2(0, 0); + vec2 MousePos = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy]; + float l = length(MousePos); + if(l > 0.0001f) // make sure that this isn't 0 + { + float OffsetAmount = maximum(l - Deadzone(), 0.0f) * (FollowFactor() / 100.0f); + m_DyncamTargetCameraOffset = normalize_pre_length(MousePos, l) * OffsetAmount; + } + + m_LastMousePos = MousePos; + if(g_Config.m_ClDyncamSmoothness > 0) + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy] += (m_DyncamTargetCameraOffset - m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy]) * minimum(DeltaTime * m_DyncamSmoothingSpeedBias, 1.0f); + else + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy] = m_DyncamTargetCameraOffset; +} + +void CCamera::OnRender() +{ if(m_CameraSmoothing) { if(!m_pClient->m_Snap.m_SpecInfo.m_Active) @@ -139,18 +196,6 @@ void CCamera::OnRender() } } - if(!ZoomAllowed()) - { - m_ZoomSet = false; - m_Zoom = 1.0f; - m_Zooming = false; - } - else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10) - { - m_ZoomSet = true; - OnReset(); - } - // update camera center if(m_pClient->m_Snap.m_SpecInfo.m_Active && !m_pClient->m_Snap.m_SpecInfo.m_UsePosition) { @@ -172,49 +217,10 @@ void CCamera::OnRender() m_CamType = CAMTYPE_PLAYER; } - float DeltaTime = Client()->RenderFrameTime(); - static vec2 s_LastMousePos(0, 0); - static vec2 s_aCurrentCameraOffset[NUM_DUMMIES] = {vec2(0, 0), vec2(0, 0)}; - static float s_SpeedBias = 0.5f; - - if(g_Config.m_ClDyncamSmoothness > 0) - { - float CameraSpeed = (1.0f - (g_Config.m_ClDyncamSmoothness / 100.0f)) * 9.5f + 0.5f; - float CameraStabilizingFactor = 1 + g_Config.m_ClDyncamStabilizing / 100.0f; - - s_SpeedBias += CameraSpeed * DeltaTime; - if(g_Config.m_ClDyncam) - { - s_SpeedBias -= length(m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] - s_LastMousePos) * std::log10(CameraStabilizingFactor) * 0.02f; - s_SpeedBias = clamp(s_SpeedBias, 0.5f, CameraSpeed); - } - else - { - s_SpeedBias = maximum(5.0f, CameraSpeed); // make sure toggle back is fast - } - } - - vec2 TargetCameraOffset(0, 0); - s_LastMousePos = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy]; - float l = length(s_LastMousePos); - if(l > 0.0001f) // make sure that this isn't 0 - { - float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone; - float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f; - float OffsetAmount = maximum(l - DeadZone, 0.0f) * FollowFactor; - - TargetCameraOffset = normalize(m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy]) * OffsetAmount; - } - - if(g_Config.m_ClDyncamSmoothness > 0) - s_aCurrentCameraOffset[g_Config.m_ClDummy] += (TargetCameraOffset - s_aCurrentCameraOffset[g_Config.m_ClDummy]) * minimum(DeltaTime * s_SpeedBias, 1.0f); - else - s_aCurrentCameraOffset[g_Config.m_ClDummy] = TargetCameraOffset; - if(m_pClient->m_Snap.m_SpecInfo.m_Active) - m_Center = m_pClient->m_Snap.m_SpecInfo.m_Position + s_aCurrentCameraOffset[g_Config.m_ClDummy]; + m_Center = m_pClient->m_Snap.m_SpecInfo.m_Position + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy]; else - m_Center = m_pClient->m_LocalCharacterPos + s_aCurrentCameraOffset[g_Config.m_ClDummy]; + m_Center = m_pClient->m_LocalCharacterPos + m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy]; } if(m_ForceFreeview && m_CamType == CAMTYPE_SPEC) @@ -276,9 +282,9 @@ void CCamera::OnRender() void CCamera::OnConsoleInit() { - Console()->Register("zoom+", "", CFGFLAG_CLIENT, ConZoomPlus, this, "Zoom increase"); - Console()->Register("zoom-", "", CFGFLAG_CLIENT, ConZoomMinus, this, "Zoom decrease"); - Console()->Register("zoom", "?i", CFGFLAG_CLIENT, ConZoom, this, "Change zoom"); + Console()->Register("zoom+", "?f[amount]", CFGFLAG_CLIENT, ConZoomPlus, this, "Zoom increase"); + Console()->Register("zoom-", "?f[amount]", CFGFLAG_CLIENT, ConZoomMinus, this, "Zoom decrease"); + Console()->Register("zoom", "?f", CFGFLAG_CLIENT, ConZoom, this, "Change zoom"); Console()->Register("set_view", "i[x]i[y]", CFGFLAG_CLIENT, ConSetView, this, "Set camera position to x and y in the map"); Console()->Register("set_view_relative", "i[x]i[y]", CFGFLAG_CLIENT, ConSetViewRelative, this, "Set camera position relative to current view in the map"); Console()->Register("goto_switch", "i[number]?i[offset]", CFGFLAG_CLIENT, ConGotoSwitch, this, "View switch found (at offset) with given number"); @@ -299,10 +305,12 @@ void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) if(!pSelf->ZoomAllowed()) return; - pSelf->ScaleZoom(CCamera::ZOOM_STEP); + float ZoomAmount = pResult->NumArguments() ? pResult->GetFloat(0) : 1.0f; + + pSelf->ScaleZoom(std::pow(CCamera::ZOOM_STEP, ZoomAmount)); if(pSelf->GameClient()->m_MultiViewActivated) - pSelf->GameClient()->m_MultiViewPersonalZoom++; + pSelf->GameClient()->m_MultiViewPersonalZoom += ZoomAmount; } void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) { @@ -310,10 +318,13 @@ void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) if(!pSelf->ZoomAllowed()) return; - pSelf->ScaleZoom(1 / CCamera::ZOOM_STEP); + float ZoomAmount = pResult->NumArguments() ? pResult->GetFloat(0) : 1.0f; + ZoomAmount *= -1.0f; + + pSelf->ScaleZoom(std::pow(CCamera::ZOOM_STEP, ZoomAmount)); if(pSelf->GameClient()->m_MultiViewActivated) - pSelf->GameClient()->m_MultiViewPersonalZoom--; + pSelf->GameClient()->m_MultiViewPersonalZoom += ZoomAmount; } void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData) { @@ -322,10 +333,10 @@ void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData) return; float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(0) : g_Config.m_ClDefaultZoom; - pSelf->ChangeZoom(std::pow(CCamera::ZOOM_STEP, TargetLevel - 10), pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime); + pSelf->ChangeZoom(std::pow(CCamera::ZOOM_STEP, TargetLevel - 10.0f), pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime); if(pSelf->GameClient()->m_MultiViewActivated && pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active) - pSelf->GameClient()->m_MultiViewPersonalZoom = 0; + pSelf->GameClient()->m_MultiViewPersonalZoom = TargetLevel - 10.0f; } void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) { @@ -473,3 +484,13 @@ bool CCamera::ZoomAllowed() const GameClient()->m_GameInfo.m_AllowZoom || Client()->State() == IClient::STATE_DEMOPLAYBACK; } + +int CCamera::Deadzone() const +{ + return g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone; +} + +int CCamera::FollowFactor() const +{ + return g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor; +} diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h index 72b42230f91..32d7c3a10c5 100644 --- a/src/game/client/components/camera.h +++ b/src/game/client/components/camera.h @@ -14,6 +14,7 @@ class CCamera : public CComponent { friend class CMenuBackground; +public: enum { CAMTYPE_UNDEFINED = -1, @@ -21,6 +22,7 @@ class CCamera : public CComponent CAMTYPE_PLAYER, }; +private: int m_CamType; vec2 m_aLastPos[NUM_DUMMIES]; vec2 m_PrevCenter; @@ -50,6 +52,9 @@ class CCamera : public CComponent float MinZoomLevel(); float MaxZoomLevel(); + vec2 m_LastMousePos; + float m_DyncamSmoothingSpeedBias; + public: static constexpr float ZOOM_STEP = 0.866025f; @@ -59,6 +64,9 @@ class CCamera : public CComponent float m_Zoom; float m_ZoomSmoothingTarget; + vec2 m_DyncamTargetCameraOffset; + vec2 m_aDyncamCurrentCameraOffset[NUM_DUMMIES]; + CCamera(); virtual int Sizeof() const override { return sizeof(*this); } virtual void OnRender() override; @@ -75,6 +83,12 @@ class CCamera : public CComponent void SetZoom(float Target, int Smoothness); bool ZoomAllowed() const; + int Deadzone() const; + int FollowFactor() const; + int CamType() const { return m_CamType; } + + void UpdateCamera(); + private: static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData); static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 803ff59292b..1d9f4ab45bc 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -918,41 +918,45 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v { CCompletionOptionRenderInfo *pInfo = static_cast(pUser); + ColorRGBA TextColor; if(Index == pInfo->m_WantedCompletion) { - float TextWidth = pInfo->m_pSelf->TextRender()->TextWidth(pInfo->m_Cursor.m_FontSize, pStr, -1, -1.0f); - const CUIRect Rect = {pInfo->m_Cursor.m_X - 2.5f, pInfo->m_Cursor.m_Y - 4.f / 2.f, TextWidth + 5.f, pInfo->m_Cursor.m_FontSize + 4.f}; - Rect.Draw(ColorRGBA(229.0f / 255.0f, 185.0f / 255.0f, 4.0f / 255.0f, 0.85f), IGraphics::CORNER_ALL, pInfo->m_Cursor.m_FontSize / 3.f); + TextColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + const float TextWidth = pInfo->m_pSelf->TextRender()->TextWidth(pInfo->m_Cursor.m_FontSize, pStr); + const CUIRect Rect = {pInfo->m_Cursor.m_X - 2.0f, pInfo->m_Cursor.m_Y - 2.0f, TextWidth + 4.0f, pInfo->m_Cursor.m_FontSize + 4.0f}; + Rect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.85f), IGraphics::CORNER_ALL, 2.0f); // scroll when out of sight const bool MoveLeft = Rect.x - *pInfo->m_pOffsetChange < 0.0f; const bool MoveRight = Rect.x + Rect.w - *pInfo->m_pOffsetChange > pInfo->m_Width; if(MoveLeft && !MoveRight) + { *pInfo->m_pOffsetChange -= -Rect.x + pInfo->m_Width / 4.0f; + } else if(!MoveLeft && MoveRight) + { *pInfo->m_pOffsetChange += Rect.x + Rect.w - pInfo->m_Width + pInfo->m_Width / 4.0f; - - pInfo->m_pSelf->TextRender()->TextColor(0.05f, 0.05f, 0.05f, 1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -1); + } } else { - const char *pMatchStart = str_find_nocase(pStr, pInfo->m_pCurrentCmd); + TextColor = ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f); + } - if(pMatchStart) - { - pInfo->m_pSelf->TextRender()->TextColor(0.5f, 0.5f, 0.5f, 1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, pMatchStart - pStr); - pInfo->m_pSelf->TextRender()->TextColor(229.0f / 255.0f, 185.0f / 255.0f, 4.0f / 255.0f, 1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart, str_length(pInfo->m_pCurrentCmd)); - pInfo->m_pSelf->TextRender()->TextColor(0.5f, 0.5f, 0.5f, 1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart + str_length(pInfo->m_pCurrentCmd), -1); - } - else - { - pInfo->m_pSelf->TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); - pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -1); - } + const char *pMatchStart = str_find_nocase(pStr, pInfo->m_pCurrentCmd); + if(pMatchStart) + { + pInfo->m_pSelf->TextRender()->TextColor(TextColor); + pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, pMatchStart - pStr); + pInfo->m_pSelf->TextRender()->TextColor(1.0f, 0.75f, 0.0f, 1.0f); + pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart, str_length(pInfo->m_pCurrentCmd)); + pInfo->m_pSelf->TextRender()->TextColor(TextColor); + pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pMatchStart + str_length(pInfo->m_pCurrentCmd)); + } + else + { + pInfo->m_pSelf->TextRender()->TextColor(TextColor); + pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr); } pInfo->m_Cursor.m_X += 7.0f; @@ -1029,69 +1033,53 @@ void CGameConsole::OnRender() const float ConsoleHeight = ConsoleHeightScale * MaxConsoleHeight; - Ui()->MapScreen(); + const ColorRGBA ShadowColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f); + const ColorRGBA TransparentColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); + const ColorRGBA aBackgroundColors[NUM_CONSOLETYPES] = {ColorRGBA(0.2f, 0.2f, 0.2f, 0.9f), ColorRGBA(0.4f, 0.2f, 0.2f, 0.9f)}; + const ColorRGBA aBorderColors[NUM_CONSOLETYPES] = {ColorRGBA(0.1f, 0.1f, 0.1f, 0.9f), ColorRGBA(0.2f, 0.1f, 0.1f, 0.9f)}; - // do console shadow - Graphics()->TextureClear(); - Graphics()->QuadsBegin(); - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, 0, 0, 0, 0.5f), - IGraphics::CColorVertex(1, 0, 0, 0, 0.5f), - IGraphics::CColorVertex(2, 0, 0, 0, 0.0f), - IGraphics::CColorVertex(3, 0, 0, 0, 0.0f)}; - Graphics()->SetColorVertex(Array, 4); - IGraphics::CQuadItem QuadItem(0, ConsoleHeight, Screen.w, 10.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); + Ui()->MapScreen(); - // do background - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CONSOLE_BG].m_Id); + // background + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_BACKGROUND_NOISE].m_Id); Graphics()->QuadsBegin(); - Graphics()->SetColor(0.2f, 0.2f, 0.2f, 0.9f); - if(m_ConsoleType == CONSOLETYPE_REMOTE) - Graphics()->SetColor(0.4f, 0.2f, 0.2f, 0.9f); - Graphics()->QuadsSetSubset(0, -ConsoleHeight * 0.075f, Screen.w * 0.075f * 0.5f, 0); - QuadItem = IGraphics::CQuadItem(0, 0, Screen.w, ConsoleHeight); - Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->SetColor(aBackgroundColors[m_ConsoleType]); + Graphics()->QuadsSetSubset(0, 0, Screen.w / 80.0f, ConsoleHeight / 80.0f); + IGraphics::CQuadItem QuadItemBackground(0.0f, 0.0f, Screen.w, ConsoleHeight); + Graphics()->QuadsDrawTL(&QuadItemBackground, 1); Graphics()->QuadsEnd(); - // do small bar shadow + // bottom border Graphics()->TextureClear(); Graphics()->QuadsBegin(); - Array[0] = IGraphics::CColorVertex(0, 0, 0, 0, 0.0f); - Array[1] = IGraphics::CColorVertex(1, 0, 0, 0, 0.0f); - Array[2] = IGraphics::CColorVertex(2, 0, 0, 0, 0.25f); - Array[3] = IGraphics::CColorVertex(3, 0, 0, 0, 0.25f); - - Graphics()->SetColorVertex(Array, 4); - QuadItem = IGraphics::CQuadItem(0, ConsoleHeight - 20, Screen.w, 10); - Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->SetColor(aBorderColors[m_ConsoleType]); + IGraphics::CQuadItem QuadItemBorder(0.0f, ConsoleHeight, Screen.w, 1.0f); + Graphics()->QuadsDrawTL(&QuadItemBorder, 1); Graphics()->QuadsEnd(); - // do the lower bar - Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CONSOLE_BAR].m_Id); + // bottom shadow + Graphics()->TextureClear(); Graphics()->QuadsBegin(); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.9f); - Graphics()->QuadsSetSubset(0, 0.1f, Screen.w * 0.015f, 1 - 0.1f); - QuadItem = IGraphics::CQuadItem(0, ConsoleHeight - 10.0f, Screen.w, 10.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->SetColor4(ShadowColor, ShadowColor, TransparentColor, TransparentColor); + IGraphics::CQuadItem QuadItemShadow(0.0f, ConsoleHeight + 1.0f, Screen.w, 10.0f); + Graphics()->QuadsDrawTL(&QuadItemShadow, 1); Graphics()->QuadsEnd(); { // Get height of 1 line const float LineHeight = TextRender()->TextBoundingBox(FONT_SIZE, " ", -1, -1.0f, LINE_SPACING).m_H; - const float RowHeight = FONT_SIZE * 1.5f; + const float RowHeight = FONT_SIZE * 2.0f; float x = 3; - float y = ConsoleHeight - RowHeight - 27.0f; + float y = ConsoleHeight - RowHeight - 18.0f; const float InitialX = x; const float InitialY = y; // render prompt CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER); + TextRender()->SetCursor(&Cursor, x, y + FONT_SIZE / 2.0f, FONT_SIZE, TEXTFLAG_RENDER); char aPrompt[32]; Prompt(aPrompt); @@ -1165,7 +1153,7 @@ void CGameConsole::OnRender() { pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active } - const CUIRect InputCursorRect = {x, y + FONT_SIZE, 0.0f, 0.0f}; + const CUIRect InputCursorRect = {x, y + FONT_SIZE * 1.5f, 0.0f, 0.0f}; const bool WasChanged = pConsole->m_Input.WasChanged(); const bool WasCursorChanged = pConsole->m_Input.WasCursorChanged(); const bool Changed = WasChanged || WasCursorChanged; @@ -1288,7 +1276,7 @@ void CGameConsole::OnRender() const float YScale = Graphics()->ScreenHeight() / Screen.h; const float CalcOffsetY = LineHeight * std::floor((y - RowHeight) / LineHeight); const float ClipStartY = (y - CalcOffsetY) * YScale; - Graphics()->ClipEnable(0, ClipStartY, Screen.w * XScale, y * YScale - ClipStartY); + Graphics()->ClipEnable(0, ClipStartY, Screen.w * XScale, (y + 2.0f) * YScale - ClipStartY); while(pEntry) { diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index e8ab47900f4..f411d0323d4 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -176,6 +176,7 @@ class CGameConsole : public CComponent { CONSOLETYPE_LOCAL = 0, CONSOLETYPE_REMOTE, + NUM_CONSOLETYPES }; CGameConsole(); diff --git a/src/game/client/components/controls.cpp b/src/game/client/components/controls.cpp index a83e502b871..c3534c81225 100644 --- a/src/game/client/components/controls.cpp +++ b/src/game/client/components/controls.cpp @@ -191,9 +191,12 @@ int CControls::SnapInput(int *pData) if(m_pClient->m_Scoreboard.Active() || g_Config.m_ClPingNameCircle) m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SCOREBOARD; - if(m_pClient->m_Controls.m_aShowHookColl[g_Config.m_ClDummy] && Client()->ServerCapAnyPlayerFlag()) + if(Client()->ServerCapAnyPlayerFlag() && m_pClient->m_Controls.m_aShowHookColl[g_Config.m_ClDummy]) m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_AIM; + if(Client()->ServerCapAnyPlayerFlag() && m_pClient->m_Camera.CamType() == CCamera::CAMTYPE_SPEC) + m_aInputData[g_Config.m_ClDummy].m_PlayerFlags |= PLAYERFLAG_SPEC_CAM; + bool Send = m_aLastData[g_Config.m_ClDummy].m_PlayerFlags != m_aInputData[g_Config.m_ClDummy].m_PlayerFlags; m_aLastData[g_Config.m_ClDummy].m_PlayerFlags = m_aInputData[g_Config.m_ClDummy].m_PlayerFlags; @@ -214,8 +217,6 @@ int CControls::SnapInput(int *pData) { if(g_Config.m_ClImproveMousePrecision && MaxDistance < 1000) // Don't scale if it would reduce precision Pos *= length(Pos) * 1000.0f / (float)MaxDistance; - if(!g_Config.m_ClOldMouseZoom) - Pos *= m_pClient->m_Camera.m_Zoom; } m_aInputData[g_Config.m_ClDummy].m_TargetX = (int)Pos.x; m_aInputData[g_Config.m_ClDummy].m_TargetY = (int)Pos.y; @@ -316,7 +317,7 @@ int CControls::SnapInput(int *pData) Send = Send || m_aInputData[g_Config.m_ClDummy].m_WantedWeapon != m_aLastData[g_Config.m_ClDummy].m_WantedWeapon; Send = Send || m_aInputData[g_Config.m_ClDummy].m_NextWeapon != m_aLastData[g_Config.m_ClDummy].m_NextWeapon; Send = Send || m_aInputData[g_Config.m_ClDummy].m_PrevWeapon != m_aLastData[g_Config.m_ClDummy].m_PrevWeapon; - Send = Send || time_get() > m_LastSendTime + time_freq() / 25; // send at least 10hz + Send = Send || time_get() > m_LastSendTime + time_freq() / 25; // send at least 25 Hz Send = Send || (m_pClient->m_Snap.m_pLocalCharacter && m_pClient->m_Snap.m_pLocalCharacter->m_Weapon == WEAPON_NINJA && (m_aInputData[g_Config.m_ClDummy].m_Direction || m_aInputData[g_Config.m_ClDummy].m_Jump || m_aInputData[g_Config.m_ClDummy].m_Hook)); } @@ -361,11 +362,20 @@ void CControls::OnRender() // update target pos if(m_pClient->m_Snap.m_pGameInfoObj && !m_pClient->m_Snap.m_SpecInfo.m_Active) - m_aTargetPos[g_Config.m_ClDummy] = m_pClient->m_LocalCharacterPos + m_aMousePos[g_Config.m_ClDummy]; + { + // make sure to compensate for smooth dyncam to ensure the cursor stays still in world space if zoomed + vec2 DyncamOffsetDelta = m_pClient->m_Camera.m_DyncamTargetCameraOffset - m_pClient->m_Camera.m_aDyncamCurrentCameraOffset[g_Config.m_ClDummy]; + float Zoom = m_pClient->m_Camera.m_Zoom; + m_aTargetPos[g_Config.m_ClDummy] = m_pClient->m_LocalCharacterPos + m_aMousePos[g_Config.m_ClDummy] - DyncamOffsetDelta + DyncamOffsetDelta / Zoom; + } else if(m_pClient->m_Snap.m_SpecInfo.m_Active && m_pClient->m_Snap.m_SpecInfo.m_UsePosition) + { m_aTargetPos[g_Config.m_ClDummy] = m_pClient->m_Snap.m_SpecInfo.m_Position + m_aMousePos[g_Config.m_ClDummy]; + } else + { m_aTargetPos[g_Config.m_ClDummy] = m_aMousePos[g_Config.m_ClDummy]; + } } bool CControls::OnCursorMove(float x, float y, IInput::ECursorType CursorType) diff --git a/src/game/client/components/controls.h b/src/game/client/components/controls.h index c363ea7b06b..8564d592b07 100644 --- a/src/game/client/components/controls.h +++ b/src/game/client/components/controls.h @@ -12,10 +12,10 @@ class CControls : public CComponent { +public: float GetMinMouseDistance() const; float GetMaxMouseDistance() const; -public: vec2 m_aMousePos[NUM_DUMMIES]; vec2 m_aMousePosOnAction[NUM_DUMMIES]; vec2 m_aTargetPos[NUM_DUMMIES]; diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index e6bde3a941c..603707927ea 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -86,6 +86,8 @@ void CHud::OnInit() { OnReset(); + Graphics()->SetColor(1.0, 1.0, 1.0, 1.0); + m_HudQuadContainerIndex = Graphics()->CreateQuadContainer(false); Graphics()->QuadsSetSubset(0, 0, 1, 1); PrepareAmmoHealthAndArmorQuads(); diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 8e95087f8c8..5d5c3bb762b 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -806,6 +806,8 @@ void CMenus::RenderLoading(const char *pCaption, const char *pContent, int Incre Ui()->RenderProgressBar(ProgressBar, CurLoadRenderCount / (float)m_LoadingState.m_Total); } + Graphics()->SetColor(1.0, 1.0, 1.0, 1.0); + Client()->UpdateAndSwap(); } @@ -1815,7 +1817,7 @@ void CMenus::RenderPopupFullscreen(CUIRect Screen) if(m_pClient->m_Skins7.SaveSkinfile(m_SkinNameInput.GetString(), m_Dummy)) { m_Popup = POPUP_NONE; - m_SkinListNeedsUpdate = true; + m_SkinList7LastRefreshTime = std::nullopt; } else PopupMessage(Localize("Error"), Localize("Unable to save the skin"), Localize("Ok"), POPUP_SAVE_SKIN); diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index f4e57ce346d..254a462bb0c 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -96,8 +97,10 @@ class CMenus : public CComponent void DoJoystickAxisPicker(CUIRect View); void DoJoystickBar(const CUIRect *pRect, float Current, float Tolerance, bool Active); - bool m_SkinListNeedsUpdate = false; + std::optional m_SkinListLastRefreshTime; bool m_SkinListScrollToSelected = false; + std::optional m_SkinList7LastRefreshTime; + std::optional m_SkinPartsList7LastRefreshTime; int m_DirectionQuadContainerIndex; @@ -481,8 +484,12 @@ class CMenus : public CComponent // found in menus_ingame.cpp STextContainerIndex m_MotdTextContainerIndex; void RenderGame(CUIRect MainView); + void RenderTouchControlsEditor(CUIRect MainView); void PopupConfirmDisconnect(); void PopupConfirmDisconnectDummy(); + void PopupConfirmDiscardTouchControlsChanges(); + void PopupConfirmResetTouchControls(); + void PopupConfirmImportTouchControlsClipboard(); void RenderPlayers(CUIRect MainView); void RenderServerInfo(CUIRect MainView); void RenderServerInfoMotd(CUIRect Motd); @@ -588,7 +595,6 @@ class CMenus : public CComponent void UpdateCommunityIcons(); // skin favorite list - bool m_SkinFavoritesChanged = false; std::unordered_set m_SkinFavorites; static void Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData); static void Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData); @@ -644,7 +650,6 @@ class CMenus : public CComponent static CUi::EPopupMenuFunctionResult PopupMapPicker(void *pContext, CUIRect View, bool Active); void SetNeedSendInfo(); - void SetActive(bool Active); void UpdateColors(); IGraphics::CTextureHandle m_TextureBlob; @@ -664,6 +669,8 @@ class CMenus : public CComponent bool IsInit() { return m_IsInit; } bool IsActive() const { return m_MenuActive; } + void SetActive(bool Active); + void KillServer(); virtual void OnInit() override; @@ -671,7 +678,6 @@ class CMenus : public CComponent virtual void OnStateChange(int NewState, int OldState) override; virtual void OnWindowResize() override; - virtual void OnRefreshSkins() override; virtual void OnReset() override; virtual void OnRender() override; virtual bool OnInput(const IInput::CEvent &Event) override; diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index fdc8459e347..beba28fc118 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -40,21 +41,19 @@ using namespace std::chrono_literals; void CMenus::RenderGame(CUIRect MainView) { - CUIRect Button, ButtonBar, ButtonBar2; + CUIRect Button, ButtonBars, ButtonBar, ButtonBar2; bool ShowDDRaceButtons = MainView.w > 855.0f; - MainView.HSplitTop(45.0f, &ButtonBar, &MainView); - ButtonBar.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); - - // button bar - ButtonBar.HSplitTop(10.0f, 0, &ButtonBar); - ButtonBar.HSplitTop(25.0f, &ButtonBar, 0); - ButtonBar.VMargin(10.0f, &ButtonBar); - - ButtonBar.HSplitTop(30.0f, 0, &ButtonBar2); - ButtonBar2.HSplitTop(25.0f, &ButtonBar2, 0); + MainView.HSplitTop(45.0f + (g_Config.m_ClTouchControls ? 35.0f : 0.0f), &ButtonBars, &MainView); + ButtonBars.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); + ButtonBars.Margin(10.0f, &ButtonBars); + ButtonBars.HSplitTop(25.0f, &ButtonBar, &ButtonBars); + if(g_Config.m_ClTouchControls) + { + ButtonBars.HSplitTop(10.0f, nullptr, &ButtonBars); + ButtonBars.HSplitTop(25.0f, &ButtonBar2, &ButtonBars); + } ButtonBar.VSplitRight(120.0f, &ButtonBar, &Button); - static CButtonContainer s_DisconnectButton; if(DoButton_Menu(&s_DisconnectButton, Localize("Disconnect"), 0, &Button)) { @@ -214,6 +213,173 @@ void CMenus::RenderGame(CUIRect MainView) } } } + + if(g_Config.m_ClTouchControls) + { + ButtonBar2.VSplitLeft(200.0f, &Button, &ButtonBar2); + static char s_TouchControlsEditCheckbox; + if(DoButton_CheckBox(&s_TouchControlsEditCheckbox, Localize("Edit touch controls"), GameClient()->m_TouchControls.IsEditingActive(), &Button)) + { + GameClient()->m_TouchControls.SetEditingActive(!GameClient()->m_TouchControls.IsEditingActive()); + } + + ButtonBar2.VSplitRight(80.0f, &ButtonBar2, &Button); + static CButtonContainer s_CloseButton; + if(DoButton_Menu(&s_CloseButton, Localize("Close"), 0, &Button)) + { + SetActive(false); + } + + ButtonBar2.VSplitRight(5.0f, &ButtonBar2, nullptr); + ButtonBar2.VSplitRight(160.0f, &ButtonBar2, &Button); + static CButtonContainer s_RemoveConsoleButton; + if(DoButton_Menu(&s_RemoveConsoleButton, Localize("Remote console"), 0, &Button)) + { + Console()->ExecuteLine("toggle_remote_console"); + } + + ButtonBar2.VSplitRight(5.0f, &ButtonBar2, nullptr); + ButtonBar2.VSplitRight(120.0f, &ButtonBar2, &Button); + static CButtonContainer s_LocalConsoleButton; + if(DoButton_Menu(&s_LocalConsoleButton, Localize("Console"), 0, &Button)) + { + Console()->ExecuteLine("toggle_local_console"); + } + + if(GameClient()->m_TouchControls.IsEditingActive()) + { + CUIRect TouchControlsEditor; + MainView.VMargin((MainView.w - 505.0f) / 2.0f, &TouchControlsEditor); + TouchControlsEditor.HMargin((TouchControlsEditor.h - 230.0f) / 2.0f, &TouchControlsEditor); + RenderTouchControlsEditor(TouchControlsEditor); + } + } +} + +void CMenus::RenderTouchControlsEditor(CUIRect MainView) +{ + CUIRect Label, Button, Row; + MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_ALL, 10.0f); + MainView.Margin(10.0f, &MainView); + + MainView.HSplitTop(25.0f, &Label, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + Ui()->DoLabel(&Label, Localize("Edit touch controls"), 20.0f, TEXTALIGN_MC); + + MainView.HSplitTop(25.0f, &Row, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + + Row.VSplitLeft(240.0f, &Button, &Row); + static CButtonContainer s_SaveConfigurationButton; + if(DoButton_Menu(&s_SaveConfigurationButton, Localize("Save changes"), GameClient()->m_TouchControls.HasEditingChanges() ? 0 : 1, &Button)) + { + if(GameClient()->m_TouchControls.SaveConfigurationToFile()) + { + GameClient()->m_TouchControls.SetEditingChanges(false); + } + else + { + SWarning Warning(Localize("Error saving touch controls"), Localize("Could not save touch controls to file. See local console for details.")); + Warning.m_AutoHide = false; + Client()->AddWarning(Warning); + } + } + + Row.VSplitLeft(5.0f, nullptr, &Row); + Row.VSplitLeft(240.0f, &Button, &Row); + if(GameClient()->m_TouchControls.HasEditingChanges()) + { + TextRender()->TextColor(ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)); + Ui()->DoLabel(&Button, Localize("Unsaved changes"), 14.0f, TEXTALIGN_MC); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + + MainView.HSplitTop(25.0f, &Row, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + + Row.VSplitLeft(240.0f, &Button, &Row); + static CButtonContainer s_DiscardChangesButton; + if(DoButton_Menu(&s_DiscardChangesButton, Localize("Discard changes"), GameClient()->m_TouchControls.HasEditingChanges() ? 0 : 1, &Button)) + { + PopupConfirm(Localize("Discard changes"), + Localize("Are you sure that you want to discard the current changes to the touch controls?"), + Localize("Yes"), Localize("No"), + &CMenus::PopupConfirmDiscardTouchControlsChanges); + } + + Row.VSplitLeft(5.0f, nullptr, &Row); + Row.VSplitLeft(240.0f, &Button, &Row); + static CButtonContainer s_ResetButton; + if(DoButton_Menu(&s_ResetButton, Localize("Reset to defaults"), 0, &Button)) + { + PopupConfirm(Localize("Reset to defaults"), + Localize("Are you sure that you want to reset the touch controls to default?"), + Localize("Yes"), Localize("No"), + &CMenus::PopupConfirmResetTouchControls); + } + + MainView.HSplitTop(25.0f, &Row, &MainView); + MainView.HSplitTop(10.0f, nullptr, &MainView); + + Row.VSplitLeft(240.0f, &Button, &Row); + static CButtonContainer s_ClipboardImportButton; + if(DoButton_Menu(&s_ClipboardImportButton, Localize("Import from clipboard"), 0, &Button)) + { + PopupConfirm(Localize("Import from clipboard"), + Localize("Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls."), + Localize("Yes"), Localize("No"), + &CMenus::PopupConfirmImportTouchControlsClipboard); + } + + Row.VSplitLeft(5.0f, nullptr, &Row); + Row.VSplitLeft(240.0f, &Button, &Row); + static CButtonContainer s_ClipboardExportButton; + if(DoButton_Menu(&s_ClipboardExportButton, Localize("Export to clipboard"), 0, &Button)) + { + GameClient()->m_TouchControls.SaveConfigurationToClipboard(); + } + + MainView.HSplitTop(25.0f, &Label, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + Ui()->DoLabel(&Label, Localize("Settings"), 20.0f, TEXTALIGN_MC); + + MainView.HSplitTop(25.0f, &Row, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + + Row.VSplitLeft(300.0f, &Label, &Row); + Ui()->DoLabel(&Label, Localize("Direct touch input while ingame"), 16.0f, TEXTALIGN_ML); + + Row.VSplitLeft(5.0f, nullptr, &Row); + Row.VSplitLeft(180.0f, &Button, &Row); + const char *apIngameTouchModes[(int)CTouchControls::EDirectTouchIngameMode::NUM_STATES] = {Localize("Disabled", "Direct touch input"), Localize("Active action", "Direct touch input"), Localize("Aim", "Direct touch input"), Localize("Fire", "Direct touch input"), Localize("Hook", "Direct touch input")}; + const CTouchControls::EDirectTouchIngameMode OldDirectTouchIngame = GameClient()->m_TouchControls.DirectTouchIngame(); + static CUi::SDropDownState s_DirectTouchIngameDropDownState; + static CScrollRegion s_DirectTouchIngameDropDownScrollRegion; + s_DirectTouchIngameDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_DirectTouchIngameDropDownScrollRegion; + const CTouchControls::EDirectTouchIngameMode NewDirectTouchIngame = (CTouchControls::EDirectTouchIngameMode)Ui()->DoDropDown(&Button, (int)OldDirectTouchIngame, apIngameTouchModes, std::size(apIngameTouchModes), s_DirectTouchIngameDropDownState); + if(OldDirectTouchIngame != NewDirectTouchIngame) + { + GameClient()->m_TouchControls.SetDirectTouchIngame(NewDirectTouchIngame); + } + + MainView.HSplitTop(25.0f, &Row, &MainView); + MainView.HSplitTop(5.0f, nullptr, &MainView); + + Row.VSplitLeft(300.0f, &Label, &Row); + Ui()->DoLabel(&Label, Localize("Direct touch input while spectating"), 16.0f, TEXTALIGN_ML); + + Row.VSplitLeft(5.0f, nullptr, &Row); + Row.VSplitLeft(180.0f, &Button, &Row); + const char *apSpectateTouchModes[(int)CTouchControls::EDirectTouchSpectateMode::NUM_STATES] = {Localize("Disabled", "Direct touch input"), Localize("Aim", "Direct touch input")}; + const CTouchControls::EDirectTouchSpectateMode OldDirectTouchSpectate = GameClient()->m_TouchControls.DirectTouchSpectate(); + static CUi::SDropDownState s_DirectTouchSpectateDropDownState; + static CScrollRegion s_DirectTouchSpectateDropDownScrollRegion; + s_DirectTouchSpectateDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_DirectTouchSpectateDropDownScrollRegion; + const CTouchControls::EDirectTouchSpectateMode NewDirectTouchSpectate = (CTouchControls::EDirectTouchSpectateMode)Ui()->DoDropDown(&Button, (int)OldDirectTouchSpectate, apSpectateTouchModes, std::size(apSpectateTouchModes), s_DirectTouchSpectateDropDownState); + if(OldDirectTouchSpectate != NewDirectTouchSpectate) + { + GameClient()->m_TouchControls.SetDirectTouchSpectate(NewDirectTouchSpectate); + } } void CMenus::PopupConfirmDisconnect() @@ -227,6 +393,57 @@ void CMenus::PopupConfirmDisconnectDummy() SetActive(false); } +void CMenus::PopupConfirmDiscardTouchControlsChanges() +{ + if(GameClient()->m_TouchControls.LoadConfigurationFromFile(IStorage::TYPE_ALL)) + { + GameClient()->m_TouchControls.SetEditingChanges(false); + } + else + { + SWarning Warning(Localize("Error loading touch controls"), Localize("Could not load touch controls from file. See local console for details.")); + Warning.m_AutoHide = false; + Client()->AddWarning(Warning); + } +} + +void CMenus::PopupConfirmResetTouchControls() +{ + bool Success = false; + for(int StorageType = IStorage::TYPE_SAVE + 1; StorageType < Storage()->NumPaths(); ++StorageType) + { + if(GameClient()->m_TouchControls.LoadConfigurationFromFile(StorageType)) + { + Success = true; + break; + } + } + if(Success) + { + GameClient()->m_TouchControls.SetEditingChanges(true); + } + else + { + SWarning Warning(Localize("Error loading touch controls"), Localize("Could not load default touch controls from file. See local console for details.")); + Warning.m_AutoHide = false; + Client()->AddWarning(Warning); + } +} + +void CMenus::PopupConfirmImportTouchControlsClipboard() +{ + if(GameClient()->m_TouchControls.LoadConfigurationFromClipboard()) + { + GameClient()->m_TouchControls.SetEditingChanges(true); + } + else + { + SWarning Warning(Localize("Error loading touch controls"), Localize("Could not load touch controls from clipboard. See local console for details.")); + Warning.m_AutoHide = false; + Client()->AddWarning(Warning); + } +} + void CMenus::RenderPlayers(CUIRect MainView) { CUIRect Button, Button2, ButtonBar, PlayerList, Player; @@ -1281,6 +1498,10 @@ void CMenus::RenderGhost(CUIRect MainView) void CMenus::RenderIngameHint() { + // With touch controls enabled there is a Close button in the menu and usually no Escape key available. + if(g_Config.m_ClTouchControls) + return; + float Width = 300 * Graphics()->ScreenAspect(); Graphics()->MapScreen(0, 0, Width, 300); TextRender()->TextColor(1, 1, 1, 1); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 7d42ffe7dc5..af8a82ca46b 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -401,11 +401,6 @@ struct CUISkin bool operator==(const char *pOther) const { return !str_comp_nocase(m_pSkin->GetName(), pOther); } }; -void CMenus::OnRefreshSkins() -{ - m_SkinListNeedsUpdate = true; -} - void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData) { auto *pSelf = (CMenus *)pUserData; @@ -417,7 +412,7 @@ void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData) return; } pSelf->m_SkinFavorites.emplace(pStr); - pSelf->m_SkinFavoritesChanged = true; + pSelf->m_SkinListLastRefreshTime = std::nullopt; } void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData) @@ -427,7 +422,7 @@ void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData) if(it != pSelf->m_SkinFavorites.end()) { pSelf->m_SkinFavorites.erase(it); - pSelf->m_SkinFavoritesChanged = true; + pSelf->m_SkinListLastRefreshTime = std::nullopt; } } @@ -740,14 +735,12 @@ void CMenus::RenderSettingsTee(CUIRect MainView) static CListBox s_ListBox; // be nice to the CPU - static std::chrono::nanoseconds s_SkinLastRefreshTime = m_pClient->m_Skins.LastRefreshTime(); - if(m_SkinListNeedsUpdate || m_SkinFavoritesChanged || s_SkinLastRefreshTime != m_pClient->m_Skins.LastRefreshTime()) + if(!m_SkinListLastRefreshTime.has_value() || m_SkinListLastRefreshTime.value() != m_pClient->m_Skins.LastRefreshTime()) { - s_SkinLastRefreshTime = m_pClient->m_Skins.LastRefreshTime(); + m_SkinListLastRefreshTime = m_pClient->m_Skins.LastRefreshTime(); s_vSkinList.clear(); s_vSkinListHelper.clear(); s_vFavoriteSkinListHelper.clear(); - m_SkinFavoritesChanged = false; auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) { // filter quick search @@ -783,7 +776,6 @@ void CMenus::RenderSettingsTee(CUIRect MainView) std::sort(s_vFavoriteSkinListHelper.begin(), s_vFavoriteSkinListHelper.end()); s_vSkinList = s_vFavoriteSkinListHelper; s_vSkinList.insert(s_vSkinList.end(), s_vSkinListHelper.begin(), s_vSkinListHelper.end()); - m_SkinListNeedsUpdate = false; } int RainbowSelectedOld = -1; @@ -847,7 +839,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) { m_SkinFavorites.emplace(pSkinToBeDraw->GetName()); } - m_SkinListNeedsUpdate = true; + m_SkinListLastRefreshTime = std::nullopt; } } } @@ -862,7 +854,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed())) { - m_SkinListNeedsUpdate = true; + m_SkinListLastRefreshTime = std::nullopt; } static CButtonContainer s_SkinDatabaseButton; @@ -3098,7 +3090,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) // ***** Preview +hookcoll pressed toggle ***** RightView.HSplitTop(LineSize, &Button, &RightView); - if(DoButton_CheckBox(&s_HookCollPressed, Localize("Preview \"Hook collisions\" being pressed"), s_HookCollPressed, &Button)) + if(DoButton_CheckBox(&s_HookCollPressed, Localize("Preview 'Hook collisions' being pressed"), s_HookCollPressed, &Button)) s_HookCollPressed = !s_HookCollPressed; } else if(s_CurTab == APPEARANCE_TAB_INFO_MESSAGES) diff --git a/src/game/client/components/menus_settings7.cpp b/src/game/client/components/menus_settings7.cpp index 13c804d3669..9c5cf8751cd 100644 --- a/src/game/client/components/menus_settings7.cpp +++ b/src/game/client/components/menus_settings7.cpp @@ -207,8 +207,8 @@ void CMenus::RenderSettingsTee7(CUIRect MainView) static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); if(Ui()->DoEditBox_Search(&s_SkinFilterInput, &QuickSearch, 14.0f, !Ui()->IsPopupOpen() && m_pClient->m_GameConsole.IsClosed())) { - m_SkinListNeedsUpdate = true; - m_SkinPartListNeedsUpdate = true; + m_SkinList7LastRefreshTime = std::nullopt; + m_SkinPartsList7LastRefreshTime = std::nullopt; } static CButtonContainer s_DirectoryButton; @@ -317,14 +317,11 @@ void CMenus::RenderSkinSelection7(CUIRect MainView) static float s_LastSelectionTime = -10.0f; static std::vector s_vpSkinList; static CListBox s_ListBox; - static size_t s_SkinCount = 0; - const std::vector &vCurrentSkins = GameClient()->m_Skins7.GetSkins(); - if(m_SkinListNeedsUpdate || vCurrentSkins.size() != s_SkinCount) + if(!m_SkinList7LastRefreshTime.has_value() || m_SkinList7LastRefreshTime.value() != m_SkinList7LastRefreshTime) { s_vpSkinList.clear(); - s_SkinCount = vCurrentSkins.size(); - for(const CSkins7::CSkin &Skin : vCurrentSkins) + for(const CSkins7::CSkin &Skin : GameClient()->m_Skins7.GetSkins()) { if((Skin.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0) continue; @@ -333,7 +330,6 @@ void CMenus::RenderSkinSelection7(CUIRect MainView) s_vpSkinList.emplace_back(&Skin); } - m_SkinListNeedsUpdate = false; } m_pSelectedSkin = nullptr; @@ -405,15 +401,12 @@ void CMenus::RenderSkinPartSelection7(CUIRect MainView) { static std::vector s_paList[protocol7::NUM_SKINPARTS]; static CListBox s_ListBox; - static size_t s_aSkinPartCount[protocol7::NUM_SKINPARTS] = {0}; for(int Part = 0; Part < protocol7::NUM_SKINPARTS; Part++) { - const std::vector &vCurrentSkinParts = GameClient()->m_Skins7.GetSkinParts(Part); - if(m_SkinPartListNeedsUpdate || vCurrentSkinParts.size() != s_aSkinPartCount[Part]) + if(!m_SkinList7LastRefreshTime.has_value() || m_SkinList7LastRefreshTime.value() != GameClient()->m_Skins7.LastRefreshTime()) { s_paList[Part].clear(); - s_aSkinPartCount[Part] = vCurrentSkinParts.size(); - for(const CSkins7::CSkinPart &SkinPart : vCurrentSkinParts) + for(const CSkins7::CSkinPart &SkinPart : GameClient()->m_Skins7.GetSkinParts(Part)) { if((SkinPart.m_Flags & CSkins7::SKINFLAG_SPECIAL) != 0) continue; @@ -425,7 +418,6 @@ void CMenus::RenderSkinPartSelection7(CUIRect MainView) } } } - m_SkinPartListNeedsUpdate = false; static int s_OldSelected = -1; s_ListBox.DoBegin(&MainView); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 223329ae858..ec0adb2bd49 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -473,7 +473,7 @@ void CPlayers::RenderPlayer( float AttackTicksPassed = AttackTime * (float)Client()->GameTickSpeed(); float Angle; - if(Local && (!m_pClient->m_Snap.m_SpecInfo.m_Active || m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW) && Client()->State() != IClient::STATE_DEMOPLAYBACK) + if(Local && !m_pClient->m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK) { // just use the direct input if it's the local player we are rendering vec2 Pos = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy]; diff --git a/src/game/client/components/skins7.cpp b/src/game/client/components/skins7.cpp index 9287180f346..494c0f02ceb 100644 --- a/src/game/client/components/skins7.cpp +++ b/src/game/client/components/skins7.cpp @@ -307,6 +307,8 @@ void CSkins7::OnInit() LoadXmasHat(); LoadBotDecoration(); GameClient()->m_Menus.RenderLoading(Localize("Loading DDNet Client"), Localize("Loading skin files"), 0); + + m_LastRefreshTime = time_get_nanoseconds(); } void CSkins7::InitPlaceholderSkinParts() diff --git a/src/game/client/components/skins7.h b/src/game/client/components/skins7.h index 4f37630ea4a..54e3b3dcdda 100644 --- a/src/game/client/components/skins7.h +++ b/src/game/client/components/skins7.h @@ -13,6 +13,7 @@ #include #include +#include #include class CSkins7 : public CComponent @@ -66,6 +67,8 @@ class CSkins7 : public CComponent int Sizeof() const override { return sizeof(*this); } void OnInit() override; + std::chrono::nanoseconds LastRefreshTime() const { return m_LastRefreshTime; } + const std::vector &GetSkins() const; const std::vector &GetSkinParts(int Part) const; const CSkinPart *FindSkinPartOrNullptr(int Part, const char *pName, bool AllowSpecialPart) const; @@ -87,6 +90,7 @@ class CSkins7 : public CComponent private: int m_ScanningPart; + std::chrono::nanoseconds m_LastRefreshTime; std::vector m_avSkinParts[protocol7::NUM_SKINPARTS]; CSkinPart m_aPlaceholderSkinParts[protocol7::NUM_SKINPARTS]; diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index feb41c6a477..e3211196553 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -18,10 +18,17 @@ #include -bool CSpectator::CanChangeSpectator() +bool CSpectator::CanChangeSpectatorId() { - // Don't change SpectatorId when not spectating - return m_pClient->m_Snap.m_SpecInfo.m_Active; + // don't change SpectatorId when not spectating + if(!m_pClient->m_Snap.m_SpecInfo.m_Active) + return false; + + // stop follow mode from changing SpectatorId + if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecId == SPEC_FOLLOW) + return false; + + return true; } void CSpectator::SpectateNext(bool Reverse) @@ -89,7 +96,7 @@ void CSpectator::ConKeySpectator(IConsole::IResult *pResult, void *pUserData) void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData) { CSpectator *pSelf = (CSpectator *)pUserData; - if(!pSelf->CanChangeSpectator()) + if(!pSelf->CanChangeSpectatorId()) return; pSelf->Spectate(pResult->GetInteger(0)); @@ -98,7 +105,7 @@ void CSpectator::ConSpectate(IConsole::IResult *pResult, void *pUserData) void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData) { CSpectator *pSelf = (CSpectator *)pUserData; - if(!pSelf->CanChangeSpectator()) + if(!pSelf->CanChangeSpectatorId()) return; pSelf->SpectateNext(false); @@ -107,7 +114,7 @@ void CSpectator::ConSpectateNext(IConsole::IResult *pResult, void *pUserData) void CSpectator::ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData) { CSpectator *pSelf = (CSpectator *)pUserData; - if(!pSelf->CanChangeSpectator()) + if(!pSelf->CanChangeSpectatorId()) return; pSelf->SpectateNext(true); @@ -116,37 +123,7 @@ void CSpectator::ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData) { CSpectator *pSelf = (CSpectator *)pUserData; - if(!pSelf->CanChangeSpectator()) - return; - - const CGameClient::CSnapState &Snap = pSelf->m_pClient->m_Snap; - int SpectatorId = Snap.m_SpecInfo.m_SpectatorId; - - int NewSpectatorId = -1; - - vec2 CurPosition(pSelf->m_pClient->m_Camera.m_Center); - if(SpectatorId != SPEC_FREEVIEW) - { - const CNetObj_Character &CurCharacter = Snap.m_aCharacters[SpectatorId].m_Cur; - CurPosition.x = CurCharacter.m_X; - CurPosition.y = CurCharacter.m_Y; - } - - int ClosestDistance = std::numeric_limits::max(); - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(i == SpectatorId || !Snap.m_aCharacters[i].m_Active || !Snap.m_apPlayerInfos[i] || Snap.m_apPlayerInfos[i]->m_Team == TEAM_SPECTATORS || (SpectatorId == SPEC_FREEVIEW && i == Snap.m_LocalClientId)) - continue; - const CNetObj_Character &MaybeClosestCharacter = Snap.m_aCharacters[i].m_Cur; - int Distance = distance(CurPosition, vec2(MaybeClosestCharacter.m_X, MaybeClosestCharacter.m_Y)); - if(NewSpectatorId == -1 || Distance < ClosestDistance) - { - NewSpectatorId = i; - ClosestDistance = Distance; - } - } - if(NewSpectatorId > -1) - pSelf->Spectate(NewSpectatorId); + pSelf->SpectateClosest(); } void CSpectator::ConMultiView(IConsole::IResult *pResult, void *pUserData) @@ -643,5 +620,39 @@ void CSpectator::Spectate(int SpectatorId) void CSpectator::SpectateClosest() { - ConSpectateClosest(NULL, this); + if(!CanChangeSpectatorId()) + return; + + const CGameClient::CSnapState &Snap = m_pClient->m_Snap; + int SpectatorId = Snap.m_SpecInfo.m_SpectatorId; + + int NewSpectatorId = -1; + + vec2 CurPosition(m_pClient->m_Camera.m_Center); + if(SpectatorId != SPEC_FREEVIEW) + { + const CNetObj_Character &CurCharacter = Snap.m_aCharacters[SpectatorId].m_Cur; + CurPosition.x = CurCharacter.m_X; + CurPosition.y = CurCharacter.m_Y; + } + + int ClosestDistance = std::numeric_limits::max(); + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(i == SpectatorId || !Snap.m_aCharacters[i].m_Active || !Snap.m_apPlayerInfos[i] || Snap.m_apPlayerInfos[i]->m_Team == TEAM_SPECTATORS) + continue; + + if(Client()->State() != IClient::STATE_DEMOPLAYBACK && i == Snap.m_LocalClientId) + continue; + + const CNetObj_Character &MaybeClosestCharacter = Snap.m_aCharacters[i].m_Cur; + int Distance = distance(CurPosition, vec2(MaybeClosestCharacter.m_X, MaybeClosestCharacter.m_Y)); + if(NewSpectatorId == -1 || Distance < ClosestDistance) + { + NewSpectatorId = i; + ClosestDistance = Distance; + } + } + if(NewSpectatorId > -1) + Spectate(NewSpectatorId); } diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h index 25dddfb60e4..c454c0cf5dc 100644 --- a/src/game/client/components/spectator.h +++ b/src/game/client/components/spectator.h @@ -26,7 +26,7 @@ class CSpectator : public CComponent float m_MultiViewActivateDelay; - bool CanChangeSpectator(); + bool CanChangeSpectatorId(); void SpectateNext(bool Reverse); static void ConKeySpectator(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/components/touch_controls.cpp b/src/game/client/components/touch_controls.cpp new file mode 100644 index 00000000000..07942591658 --- /dev/null +++ b/src/game/client/components/touch_controls.cpp @@ -0,0 +1,1568 @@ +#include "touch_controls.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +// TODO: Add user interface to adjust button layout +// TODO: Add "color" property for touch buttons? +// TODO: Add combined weapon picker button that shows all currently available weapons +// TODO: Add "joystick-aim-relative", a virtual joystick that moves the mouse pointer relatively. And add "aim-relative" ingame direct touch input. +// TODO: Add "choice" predefined behavior which shows a selection popup for 2 or more other behaviors? +// TODO: Support changing labels of menu buttons (or support overriding label for all predefined button behaviors)? + +static constexpr const char *const ACTION_NAMES[] = {Localizable("Aim"), Localizable("Fire"), Localizable("Hook")}; +static constexpr const char *const ACTION_SWAP_NAMES[] = {/* unused */ "", Localizable("Active: Fire"), Localizable("Active: Hook")}; +static constexpr const char *const ACTION_COMMANDS[] = {/* unused */ "", "+fire", "+hook"}; + +static constexpr std::chrono::milliseconds LONG_TOUCH_DURATION = 500ms; +static constexpr std::chrono::milliseconds BIND_REPEAT_INITIAL_DELAY = 250ms; +static constexpr std::chrono::nanoseconds BIND_REPEAT_RATE = std::chrono::nanoseconds(1s) / 15; + +static constexpr const char *const CONFIGURATION_FILENAME = "touch_controls.json"; +static constexpr int BUTTON_SIZE_SCALE = 1000000; +static constexpr int BUTTON_SIZE_MINIMUM = 50000; +static constexpr int BUTTON_SIZE_MAXIMUM = 500000; + +/* This is required for the localization script to find the labels of the default bind buttons specified in the configuration file: +Localizable("Move left") Localizable("Move right") Localizable("Jump") Localizable("Prev. weapon") Localizable("Next weapon") +Localizable("Zoom out") Localizable("Default zoom") Localizable("Zoom in") Localizable("Scoreboard") Localizable("Chat") Localizable("Team chat") +Localizable("Vote yes") Localizable("Vote no") Localizable("Toggle dummy") +*/ + +CTouchControls::CTouchButton::CTouchButton(CTouchControls *pTouchControls) : + m_pTouchControls(pTouchControls), + m_VisibilityCached(false) +{ +} + +CTouchControls::CTouchButton::CTouchButton(CTouchButton &&Other) noexcept : + m_pTouchControls(Other.m_pTouchControls), + m_UnitRect(Other.m_UnitRect), + m_Shape(Other.m_Shape), + m_vVisibilities(Other.m_vVisibilities), + m_pBehavior(std::move(Other.m_pBehavior)), + m_VisibilityCached(false) +{ + Other.m_pTouchControls = nullptr; +} + +CTouchControls::CTouchButton &CTouchControls::CTouchButton::operator=(CTouchButton &&Other) noexcept +{ + m_pTouchControls = Other.m_pTouchControls; + Other.m_pTouchControls = nullptr; + m_UnitRect = Other.m_UnitRect; + m_Shape = Other.m_Shape; + m_vVisibilities = Other.m_vVisibilities; + m_pBehavior = std::move(Other.m_pBehavior); + m_VisibilityCached = false; + return *this; +} + +void CTouchControls::CTouchButton::UpdatePointers() +{ + m_pBehavior->Init(this); +} + +void CTouchControls::CTouchButton::UpdateScreenFromUnitRect() +{ + const vec2 ScreenSize = m_pTouchControls->CalculateScreenSize(); + m_ScreenRect.x = m_UnitRect.m_X * ScreenSize.x / BUTTON_SIZE_SCALE; + m_ScreenRect.y = m_UnitRect.m_Y * ScreenSize.y / BUTTON_SIZE_SCALE; + m_ScreenRect.w = m_UnitRect.m_W * ScreenSize.x / BUTTON_SIZE_SCALE; + m_ScreenRect.h = m_UnitRect.m_H * ScreenSize.y / BUTTON_SIZE_SCALE; + + // Enforce circle shape so the screen rect can be used for mapping the touch input position + if(m_Shape == EButtonShape::CIRCLE) + { + if(m_ScreenRect.h > m_ScreenRect.w) + { + m_ScreenRect.y += (m_ScreenRect.h - m_ScreenRect.w) / 2.0f; + m_ScreenRect.h = m_ScreenRect.w; + } + else if(m_ScreenRect.w > m_ScreenRect.h) + { + m_ScreenRect.x += (m_ScreenRect.w - m_ScreenRect.h) / 2.0f; + m_ScreenRect.w = m_ScreenRect.h; + } + } +} + +void CTouchControls::CTouchButton::UpdateBackgroundCorners() +{ + if(m_Shape != EButtonShape::RECT) + { + m_BackgroundCorners = IGraphics::CORNER_NONE; + return; + } + + // Determine rounded corners based on button layout + m_BackgroundCorners = IGraphics::CORNER_ALL; + + if(m_UnitRect.m_X == 0) + { + m_BackgroundCorners &= ~IGraphics::CORNER_L; + } + if(m_UnitRect.m_X + m_UnitRect.m_W == BUTTON_SIZE_SCALE) + { + m_BackgroundCorners &= ~IGraphics::CORNER_R; + } + if(m_UnitRect.m_Y == 0) + { + m_BackgroundCorners &= ~IGraphics::CORNER_T; + } + if(m_UnitRect.m_Y + m_UnitRect.m_H == BUTTON_SIZE_SCALE) + { + m_BackgroundCorners &= ~IGraphics::CORNER_B; + } + + const auto &&PointInOrOnRect = [](ivec2 Point, CUnitRect Rect) { + return Point.x >= Rect.m_X && Point.x <= Rect.m_X + Rect.m_W && Point.y >= Rect.m_Y && Point.y <= Rect.m_Y + Rect.m_H; + }; + for(const CTouchButton &OtherButton : m_pTouchControls->m_vTouchButtons) + { + if(&OtherButton == this || OtherButton.m_Shape != EButtonShape::RECT) + continue; + // TODO: This does not consider that button visibilities can change independently, also update corners when any visibility changed + const bool ExcludingVisibilities = std::any_of(OtherButton.m_vVisibilities.begin(), OtherButton.m_vVisibilities.end(), [&](const CButtonVisibility &OtherVisibility) { + return std::any_of(m_vVisibilities.begin(), m_vVisibilities.end(), [&](const CButtonVisibility &OurVisibility) { + return OtherVisibility.m_Type == OurVisibility.m_Type && OtherVisibility.m_Parity != OurVisibility.m_Parity; + }); + }); + if(ExcludingVisibilities) + continue; + + if((m_BackgroundCorners & IGraphics::CORNER_TL) && PointInOrOnRect(ivec2(m_UnitRect.m_X, m_UnitRect.m_Y), OtherButton.m_UnitRect)) + { + m_BackgroundCorners &= ~IGraphics::CORNER_TL; + } + if((m_BackgroundCorners & IGraphics::CORNER_TR) && PointInOrOnRect(ivec2(m_UnitRect.m_X + m_UnitRect.m_W, m_UnitRect.m_Y), OtherButton.m_UnitRect)) + { + m_BackgroundCorners &= ~IGraphics::CORNER_TR; + } + if((m_BackgroundCorners & IGraphics::CORNER_BL) && PointInOrOnRect(ivec2(m_UnitRect.m_X, m_UnitRect.m_Y + m_UnitRect.m_H), OtherButton.m_UnitRect)) + { + m_BackgroundCorners &= ~IGraphics::CORNER_BL; + } + if((m_BackgroundCorners & IGraphics::CORNER_BR) && PointInOrOnRect(ivec2(m_UnitRect.m_X + m_UnitRect.m_W, m_UnitRect.m_Y + m_UnitRect.m_H), OtherButton.m_UnitRect)) + { + m_BackgroundCorners &= ~IGraphics::CORNER_BR; + } + if(m_BackgroundCorners == IGraphics::CORNER_NONE) + { + break; + } + } +} + +vec2 CTouchControls::CTouchButton::ClampTouchPosition(vec2 TouchPosition) const +{ + switch(m_Shape) + { + case EButtonShape::RECT: + { + TouchPosition.x = clamp(TouchPosition.x, m_ScreenRect.x, m_ScreenRect.x + m_ScreenRect.w); + TouchPosition.y = clamp(TouchPosition.y, m_ScreenRect.y, m_ScreenRect.y + m_ScreenRect.h); + break; + } + case EButtonShape::CIRCLE: + { + const vec2 Center = m_ScreenRect.Center(); + const float MaxLength = minimum(m_ScreenRect.w, m_ScreenRect.h) / 2.0f; + const vec2 TouchDirection = TouchPosition - Center; + const float Length = length(TouchDirection); + if(Length > MaxLength) + { + TouchPosition = normalize_pre_length(TouchDirection, Length) * MaxLength + Center; + } + break; + } + default: + dbg_assert(false, "Unhandled shape"); + break; + } + return TouchPosition; +} + +bool CTouchControls::CTouchButton::IsInside(vec2 TouchPosition) const +{ + switch(m_Shape) + { + case EButtonShape::RECT: + return m_ScreenRect.Inside(TouchPosition); + case EButtonShape::CIRCLE: + return distance(TouchPosition, m_ScreenRect.Center()) <= minimum(m_ScreenRect.w, m_ScreenRect.h) / 2.0f; + default: + dbg_assert(false, "Unhandled shape"); + return false; + } +} + +void CTouchControls::CTouchButton::UpdateVisibility() +{ + const bool PrevVisibility = m_VisibilityCached; + m_VisibilityCached = m_pTouchControls->m_EditingActive || std::all_of(m_vVisibilities.begin(), m_vVisibilities.end(), [&](CButtonVisibility Visibility) { + return m_pTouchControls->m_aVisibilityFunctions[(int)Visibility.m_Type].m_Function() == Visibility.m_Parity; + }); + if(m_VisibilityCached && !PrevVisibility) + { + m_VisibilityStartTime = time_get_nanoseconds(); + } +} + +bool CTouchControls::CTouchButton::IsVisible() const +{ + return m_VisibilityCached; +} + +// TODO: Optimization: Use text and quad containers for rendering +void CTouchControls::CTouchButton::Render() const +{ + const ColorRGBA ButtonColor = m_pBehavior->IsActive() ? ColorRGBA(0.2f, 0.2f, 0.2f, 0.25f) : ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f); + + switch(m_Shape) + { + case EButtonShape::RECT: + { + m_ScreenRect.Draw(ButtonColor, m_BackgroundCorners, 10.0f); + break; + } + case EButtonShape::CIRCLE: + { + const vec2 Center = m_ScreenRect.Center(); + const float Radius = minimum(m_ScreenRect.w, m_ScreenRect.h) / 2.0f; + m_pTouchControls->Graphics()->TextureClear(); + m_pTouchControls->Graphics()->QuadsBegin(); + m_pTouchControls->Graphics()->SetColor(ButtonColor); + m_pTouchControls->Graphics()->DrawCircle(Center.x, Center.y, Radius, maximum(round_truncate(Radius / 4.0f) & ~1, 32)); + m_pTouchControls->Graphics()->QuadsEnd(); + break; + } + default: + dbg_assert(false, "Unhandled shape"); + break; + } + + const float FontSize = 22.0f; + CButtonLabel LabelData = m_pBehavior->GetLabel(); + CUIRect LabelRect; + m_ScreenRect.Margin(10.0f, &LabelRect); + SLabelProperties LabelProps; + LabelProps.m_MaxWidth = LabelRect.w; + if(LabelData.m_Type == CButtonLabel::EType::ICON) + { + m_pTouchControls->TextRender()->SetFontPreset(EFontPreset::ICON_FONT); + m_pTouchControls->TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); + m_pTouchControls->Ui()->DoLabel(&LabelRect, LabelData.m_pLabel, FontSize, TEXTALIGN_MC, LabelProps); + m_pTouchControls->TextRender()->SetRenderFlags(0); + m_pTouchControls->TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + } + else + { + const char *pLabel = LabelData.m_Type == CButtonLabel::EType::LOCALIZED ? Localize(LabelData.m_pLabel) : LabelData.m_pLabel; + m_pTouchControls->Ui()->DoLabel(&LabelRect, pLabel, FontSize, TEXTALIGN_MC, LabelProps); + } +} + +void CTouchControls::CTouchButton::WriteToConfiguration(CJsonWriter *pWriter) +{ + char aBuf[256]; + + pWriter->BeginObject(); + + pWriter->WriteAttribute("x"); + pWriter->WriteIntValue(m_UnitRect.m_X); + pWriter->WriteAttribute("y"); + pWriter->WriteIntValue(m_UnitRect.m_Y); + pWriter->WriteAttribute("w"); + pWriter->WriteIntValue(m_UnitRect.m_W); + pWriter->WriteAttribute("h"); + pWriter->WriteIntValue(m_UnitRect.m_H); + + pWriter->WriteAttribute("shape"); + pWriter->WriteStrValue(SHAPE_NAMES[(int)m_Shape]); + + pWriter->WriteAttribute("visibilities"); + pWriter->BeginArray(); + for(CButtonVisibility Visibility : m_vVisibilities) + { + str_format(aBuf, sizeof(aBuf), "%s%s", Visibility.m_Parity ? "" : "-", m_pTouchControls->m_aVisibilityFunctions[(int)Visibility.m_Type].m_pId); + pWriter->WriteStrValue(aBuf); + } + pWriter->EndArray(); + + pWriter->WriteAttribute("behavior"); + pWriter->BeginObject(); + m_pBehavior->WriteToConfiguration(pWriter); + pWriter->EndObject(); + + pWriter->EndObject(); +} + +void CTouchControls::CTouchButtonBehavior::Init(CTouchButton *pTouchButton) +{ + m_pTouchButton = pTouchButton; + m_pTouchControls = pTouchButton->m_pTouchControls; +} + +void CTouchControls::CTouchButtonBehavior::Reset() +{ + m_Active = false; +} + +void CTouchControls::CTouchButtonBehavior::SetActive(const IInput::CTouchFingerState &FingerState) +{ + const vec2 ScreenSize = m_pTouchControls->CalculateScreenSize(); + const CUIRect ButtonScreenRect = m_pTouchButton->m_ScreenRect; + const vec2 Position = (m_pTouchButton->ClampTouchPosition(FingerState.m_Position * ScreenSize) - ButtonScreenRect.TopLeft()) / ButtonScreenRect.Size(); + const vec2 Delta = FingerState.m_Delta * ScreenSize / ButtonScreenRect.Size(); + if(!m_Active) + { + m_Active = true; + m_ActivePosition = Position; + m_AccumulatedDelta = Delta; + m_ActivationStartTime = time_get_nanoseconds(); + m_Finger = FingerState.m_Finger; + OnActivate(); + } + else if(m_Finger == FingerState.m_Finger) + { + m_ActivePosition = Position; + m_AccumulatedDelta += Delta; + OnUpdate(); + } + else + { + dbg_assert(false, "Touch button must be inactive or use same finger"); + } +} + +void CTouchControls::CTouchButtonBehavior::SetInactive() +{ + if(m_Active) + { + m_Active = false; + OnDeactivate(); + } +} + +bool CTouchControls::CTouchButtonBehavior::IsActive() const +{ + return m_Active; +} + +bool CTouchControls::CTouchButtonBehavior::IsActive(const IInput::CTouchFinger &Finger) const +{ + return m_Active && m_Finger == Finger; +} + +void CTouchControls::CPredefinedTouchButtonBehavior::WriteToConfiguration(CJsonWriter *pWriter) +{ + pWriter->WriteAttribute("type"); + pWriter->WriteStrValue(BEHAVIOR_TYPE); + + pWriter->WriteAttribute("id"); + pWriter->WriteStrValue(m_pId); +} + +// Ingame menu button: always opens ingame menu. +CTouchControls::CButtonLabel CTouchControls::CIngameMenuTouchButtonBehavior::GetLabel() const +{ + return {CButtonLabel::EType::ICON, "\xEF\x85\x8E"}; +} + +void CTouchControls::CIngameMenuTouchButtonBehavior::OnDeactivate() +{ + m_pTouchControls->GameClient()->m_Menus.SetActive(true); +} + +// Extra menu button: +// - Short press: show/hide additional buttons (toggle extra-menu visibilities) +// - Long press: open ingame menu +CTouchControls::CExtraMenuTouchButtonBehavior::CExtraMenuTouchButtonBehavior(int Number) : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID), + m_Number(Number) +{ + if(m_Number == 0) + { + str_copy(m_aLabel, "\xEF\x83\x89"); + } + else + { + str_format(m_aLabel, sizeof(m_aLabel), "\xEF\x83\x89%d", m_Number + 1); + } +} + +CTouchControls::CButtonLabel CTouchControls::CExtraMenuTouchButtonBehavior::GetLabel() const +{ + if(m_Active && time_get_nanoseconds() - m_ActivationStartTime >= LONG_TOUCH_DURATION) + { + return {CButtonLabel::EType::ICON, "\xEF\x95\x90"}; + } + else + { + return {CButtonLabel::EType::ICON, m_aLabel}; + } +} + +void CTouchControls::CExtraMenuTouchButtonBehavior::OnDeactivate() +{ + if(time_get_nanoseconds() - m_ActivationStartTime >= LONG_TOUCH_DURATION) + { + m_pTouchControls->GameClient()->m_Menus.SetActive(true); + } + else + { + m_pTouchControls->m_aExtraMenuActive[m_Number] = !m_pTouchControls->m_aExtraMenuActive[m_Number]; + } +} + +void CTouchControls::CExtraMenuTouchButtonBehavior::WriteToConfiguration(CJsonWriter *pWriter) +{ + CPredefinedTouchButtonBehavior::WriteToConfiguration(pWriter); + + pWriter->WriteAttribute("number"); + pWriter->WriteIntValue(m_Number + 1); +} + +// Emoticon button: keeps the emoticon HUD open, next touch in emoticon HUD will close it again. +CTouchControls::CButtonLabel CTouchControls::CEmoticonTouchButtonBehavior::GetLabel() const +{ + return {CButtonLabel::EType::LOCALIZED, Localizable("Emoticon")}; +} + +void CTouchControls::CEmoticonTouchButtonBehavior::OnDeactivate() +{ + m_pTouchControls->Console()->ExecuteLineStroked(1, "+emote"); +} + +// Spectate button: keeps the spectate menu open, next touch in spectate menu will close it again. +CTouchControls::CButtonLabel CTouchControls::CSpectateTouchButtonBehavior::GetLabel() const +{ + return {CButtonLabel::EType::LOCALIZED, Localizable("Spectator mode")}; +} + +void CTouchControls::CSpectateTouchButtonBehavior::OnDeactivate() +{ + m_pTouchControls->Console()->ExecuteLineStroked(1, "+spectate"); +} + +// Swap action button: +// - If joystick is currently active with one action: activate the other action. +// - Else: swap active action. +CTouchControls::CButtonLabel CTouchControls::CSwapActionTouchButtonBehavior::GetLabel() const +{ + if(m_ActiveAction != NUM_ACTIONS) + { + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[m_ActiveAction]}; + } + else if(m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior != nullptr && + m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior->ActiveAction() != NUM_ACTIONS) + { + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[m_pTouchControls->NextActiveAction(m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior->ActiveAction())]}; + } + return {CButtonLabel::EType::LOCALIZED, ACTION_SWAP_NAMES[m_pTouchControls->m_ActionSelected]}; +} + +void CTouchControls::CSwapActionTouchButtonBehavior::OnActivate() +{ + if(m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior != nullptr && + m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior->ActiveAction() != NUM_ACTIONS) + { + m_ActiveAction = m_pTouchControls->NextActiveAction(m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior->ActiveAction()); + m_pTouchControls->Console()->ExecuteLineStroked(1, ACTION_COMMANDS[m_ActiveAction]); + } + else + { + m_pTouchControls->m_ActionSelected = m_pTouchControls->NextActiveAction(m_pTouchControls->m_ActionSelected); + } +} + +void CTouchControls::CSwapActionTouchButtonBehavior::OnDeactivate() +{ + if(m_ActiveAction != NUM_ACTIONS) + { + m_pTouchControls->Console()->ExecuteLineStroked(0, ACTION_COMMANDS[m_ActiveAction]); + m_ActiveAction = NUM_ACTIONS; + } +} + +// Use action button: always uses the active action. +CTouchControls::CButtonLabel CTouchControls::CUseActionTouchButtonBehavior::GetLabel() const +{ + if(m_ActiveAction != NUM_ACTIONS) + { + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[m_ActiveAction]}; + } + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[m_pTouchControls->m_ActionSelected]}; +} + +void CTouchControls::CUseActionTouchButtonBehavior::OnActivate() +{ + m_ActiveAction = m_pTouchControls->m_ActionSelected; + m_pTouchControls->Console()->ExecuteLineStroked(1, ACTION_COMMANDS[m_ActiveAction]); +} + +void CTouchControls::CUseActionTouchButtonBehavior::OnDeactivate() +{ + m_pTouchControls->Console()->ExecuteLineStroked(0, ACTION_COMMANDS[m_ActiveAction]); + m_ActiveAction = NUM_ACTIONS; +} + +// Generic joystick button behavior: aim with virtual joystick and use action (defined by subclass). +CTouchControls::CButtonLabel CTouchControls::CJoystickTouchButtonBehavior::GetLabel() const +{ + if(m_ActiveAction != NUM_ACTIONS) + { + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[m_ActiveAction]}; + } + return {CButtonLabel::EType::LOCALIZED, ACTION_NAMES[SelectedAction()]}; +} + +void CTouchControls::CJoystickTouchButtonBehavior::OnActivate() +{ + m_ActiveAction = SelectedAction(); + OnUpdate(); + if(m_ActiveAction != ACTION_AIM) + { + m_pTouchControls->Console()->ExecuteLineStroked(1, ACTION_COMMANDS[m_ActiveAction]); + } +} + +void CTouchControls::CJoystickTouchButtonBehavior::OnDeactivate() +{ + if(m_ActiveAction != ACTION_AIM) + { + m_pTouchControls->Console()->ExecuteLineStroked(0, ACTION_COMMANDS[m_ActiveAction]); + } + m_ActiveAction = NUM_ACTIONS; +} + +void CTouchControls::CJoystickTouchButtonBehavior::OnUpdate() +{ + CControls &Controls = m_pTouchControls->GameClient()->m_Controls; + if(m_pTouchControls->GameClient()->m_Snap.m_SpecInfo.m_Active) + { + vec2 WorldScreenSize; + m_pTouchControls->RenderTools()->CalcScreenParams(m_pTouchControls->Graphics()->ScreenAspect(), m_pTouchControls->GameClient()->m_Camera.m_Zoom, &WorldScreenSize.x, &WorldScreenSize.y); + Controls.m_aMousePos[g_Config.m_ClDummy] += -m_AccumulatedDelta * WorldScreenSize; + Controls.m_aMousePos[g_Config.m_ClDummy].x = clamp(Controls.m_aMousePos[g_Config.m_ClDummy].x, -201.0f * 32, (m_pTouchControls->Collision()->GetWidth() + 201.0f) * 32.0f); + Controls.m_aMousePos[g_Config.m_ClDummy].y = clamp(Controls.m_aMousePos[g_Config.m_ClDummy].y, -201.0f * 32, (m_pTouchControls->Collision()->GetHeight() + 201.0f) * 32.0f); + m_AccumulatedDelta = vec2(0.0f, 0.0f); + } + else + { + const vec2 AbsolutePosition = (m_ActivePosition - vec2(0.5f, 0.5f)) * 2.0f; + Controls.m_aMousePos[g_Config.m_ClDummy] = AbsolutePosition * (Controls.GetMaxMouseDistance() - Controls.GetMinMouseDistance()) + normalize(AbsolutePosition) * Controls.GetMinMouseDistance(); + if(length(Controls.m_aMousePos[g_Config.m_ClDummy]) < 0.001f) + { + Controls.m_aMousePos[g_Config.m_ClDummy].x = 0.001f; + Controls.m_aMousePos[g_Config.m_ClDummy].y = 0.0f; + } + } +} + +// Joystick that uses the active action. Registers itself as the primary joystick. +void CTouchControls::CJoystickActionTouchButtonBehavior::Init(CTouchButton *pTouchButton) +{ + CPredefinedTouchButtonBehavior::Init(pTouchButton); + m_pTouchControls->m_pPrimaryJoystickTouchButtonBehavior = this; +} + +int CTouchControls::CJoystickActionTouchButtonBehavior::SelectedAction() const +{ + return m_pTouchControls->m_ActionSelected; +} + +// Joystick that only aims. +int CTouchControls::CJoystickAimTouchButtonBehavior::SelectedAction() const +{ + return ACTION_AIM; +} + +// Joystick that always uses fire. +int CTouchControls::CJoystickFireTouchButtonBehavior::SelectedAction() const +{ + return ACTION_FIRE; +} + +// Joystick that always uses hook. +int CTouchControls::CJoystickHookTouchButtonBehavior::SelectedAction() const +{ + return ACTION_HOOK; +} + +// Bind button behavior that executes a command like a bind. +CTouchControls::CButtonLabel CTouchControls::CBindTouchButtonBehavior::GetLabel() const +{ + return {m_LabelType, m_Label.c_str()}; +} + +void CTouchControls::CBindTouchButtonBehavior::OnActivate() +{ + m_pTouchControls->Console()->ExecuteLineStroked(1, m_Command.c_str()); + m_Repeating = false; +} + +void CTouchControls::CBindTouchButtonBehavior::OnDeactivate() +{ + m_pTouchControls->Console()->ExecuteLineStroked(0, m_Command.c_str()); +} + +void CTouchControls::CBindTouchButtonBehavior::OnUpdate() +{ + const auto Now = time_get_nanoseconds(); + if(m_Repeating) + { + m_AccumulatedRepeatingTime += Now - m_LastUpdateTime; + m_LastUpdateTime = Now; + if(m_AccumulatedRepeatingTime >= BIND_REPEAT_RATE) + { + m_AccumulatedRepeatingTime -= BIND_REPEAT_RATE; + m_pTouchControls->Console()->ExecuteLineStroked(1, m_Command.c_str()); + } + } + else if(Now - m_ActivationStartTime >= BIND_REPEAT_INITIAL_DELAY) + { + m_Repeating = true; + m_LastUpdateTime = Now; + m_AccumulatedRepeatingTime = 0ns; + } +} + +void CTouchControls::CBindTouchButtonBehavior::WriteToConfiguration(CJsonWriter *pWriter) +{ + pWriter->WriteAttribute("type"); + pWriter->WriteStrValue(BEHAVIOR_TYPE); + + pWriter->WriteAttribute("label"); + pWriter->WriteStrValue(m_Label.c_str()); + + pWriter->WriteAttribute("label-type"); + pWriter->WriteStrValue(LABEL_TYPE_NAMES[(int)m_LabelType]); + + pWriter->WriteAttribute("command"); + pWriter->WriteStrValue(m_Command.c_str()); +} + +// Bind button behavior that switches between executing one of two or more console commands. +CTouchControls::CButtonLabel CTouchControls::CBindToggleTouchButtonBehavior::GetLabel() const +{ + const auto &ActiveCommand = m_vCommands[m_ActiveCommandIndex]; + return {ActiveCommand.m_LabelType, ActiveCommand.m_Label.c_str()}; +} + +void CTouchControls::CBindToggleTouchButtonBehavior::OnActivate() +{ + m_pTouchControls->Console()->ExecuteLine(m_vCommands[m_ActiveCommandIndex].m_Command.c_str()); + m_ActiveCommandIndex = (m_ActiveCommandIndex + 1) % m_vCommands.size(); +} + +void CTouchControls::CBindToggleTouchButtonBehavior::WriteToConfiguration(CJsonWriter *pWriter) +{ + pWriter->WriteAttribute("type"); + pWriter->WriteStrValue(BEHAVIOR_TYPE); + + pWriter->WriteAttribute("commands"); + pWriter->BeginArray(); + + for(const auto &Command : m_vCommands) + { + pWriter->BeginObject(); + + pWriter->WriteAttribute("label"); + pWriter->WriteStrValue(Command.m_Label.c_str()); + + pWriter->WriteAttribute("label-type"); + pWriter->WriteStrValue(LABEL_TYPE_NAMES[(int)Command.m_LabelType]); + + pWriter->WriteAttribute("command"); + pWriter->WriteStrValue(Command.m_Command.c_str()); + + pWriter->EndObject(); + } + + pWriter->EndArray(); +} + +void CTouchControls::OnInit() +{ + InitVisibilityFunctions(); + if(!LoadConfigurationFromFile(IStorage::TYPE_ALL)) + { + Client()->AddWarning(SWarning(Localize("Error loading touch controls"), Localize("Could not load touch controls from file. See local console for details."))); + } +} + +void CTouchControls::OnReset() +{ + ResetButtons(); + m_EditingActive = false; +} + +void CTouchControls::OnWindowResize() +{ + ResetButtons(); + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.UpdateScreenFromUnitRect(); + } +} + +bool CTouchControls::OnTouchState(const std::vector &vTouchFingerStates) +{ + if(!g_Config.m_ClTouchControls) + return false; + if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) + return false; + if(GameClient()->m_Chat.IsActive() || + !GameClient()->m_GameConsole.IsClosed() || + GameClient()->m_Menus.IsActive() || + GameClient()->m_Emoticon.IsActive() || + GameClient()->m_Spectator.IsActive()) + { + ResetButtons(); + return false; + } + + UpdateButtons(vTouchFingerStates); + return true; +} + +void CTouchControls::OnRender() +{ + if(!g_Config.m_ClTouchControls) + return; + if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) + return; + if(GameClient()->m_Chat.IsActive() || + GameClient()->m_Emoticon.IsActive() || + GameClient()->m_Spectator.IsActive()) + { + return; + } + + const vec2 ScreenSize = CalculateScreenSize(); + Graphics()->MapScreen(0.0f, 0.0f, ScreenSize.x, ScreenSize.y); + + RenderButtons(); +} + +bool CTouchControls::LoadConfigurationFromFile(int StorageType) +{ + void *pFileData; + unsigned FileLength; + if(!Storage()->ReadFile(CONFIGURATION_FILENAME, StorageType, &pFileData, &FileLength)) + { + log_error("touch_controls", "Failed to read configuration from '%s'", CONFIGURATION_FILENAME); + return false; + } + + const bool Result = ParseConfiguration(pFileData, FileLength); + free(pFileData); + return Result; +} + +bool CTouchControls::LoadConfigurationFromClipboard() +{ + std::string Clipboard = Input()->GetClipboardText(); + return ParseConfiguration(Clipboard.c_str(), Clipboard.size()); +} + +bool CTouchControls::SaveConfigurationToFile() +{ + IOHANDLE File = Storage()->OpenFile(CONFIGURATION_FILENAME, IOFLAG_WRITE, IStorage::TYPE_SAVE); + if(!File) + { + log_error("touch_controls", "Failed to open '%s' for writing configuration", CONFIGURATION_FILENAME); + return false; + } + + CJsonFileWriter Writer(File); + WriteConfiguration(&Writer); + return true; +} + +void CTouchControls::SaveConfigurationToClipboard() +{ + CJsonStringWriter Writer; + WriteConfiguration(&Writer); + std::string ConfigurationString = Writer.GetOutputString(); + Input()->SetClipboardText(ConfigurationString.c_str()); +} + +void CTouchControls::InitVisibilityFunctions() +{ + m_aVisibilityFunctions[(int)EButtonVisibility::INGAME].m_pId = "ingame"; + m_aVisibilityFunctions[(int)EButtonVisibility::INGAME].m_Function = [&]() { + return !GameClient()->m_Snap.m_SpecInfo.m_Active; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::ZOOM_ALLOWED].m_pId = "zoom-allowed"; + m_aVisibilityFunctions[(int)EButtonVisibility::ZOOM_ALLOWED].m_Function = [&]() { + return GameClient()->m_Camera.ZoomAllowed(); + }; + m_aVisibilityFunctions[(int)EButtonVisibility::VOTE_ACTIVE].m_pId = "vote-active"; + m_aVisibilityFunctions[(int)EButtonVisibility::VOTE_ACTIVE].m_Function = [&]() { + return GameClient()->m_Voting.IsVoting(); + }; + m_aVisibilityFunctions[(int)EButtonVisibility::DUMMY_ALLOWED].m_pId = "dummy-allowed"; + m_aVisibilityFunctions[(int)EButtonVisibility::DUMMY_ALLOWED].m_Function = [&]() { + return Client()->DummyAllowed(); + }; + m_aVisibilityFunctions[(int)EButtonVisibility::DUMMY_CONNECTED].m_pId = "dummy-connected"; + m_aVisibilityFunctions[(int)EButtonVisibility::DUMMY_CONNECTED].m_Function = [&]() { + return Client()->DummyConnected(); + }; + m_aVisibilityFunctions[(int)EButtonVisibility::RCON_AUTHED].m_pId = "rcon-authed"; + m_aVisibilityFunctions[(int)EButtonVisibility::RCON_AUTHED].m_Function = [&]() { + return Client()->RconAuthed(); + }; + m_aVisibilityFunctions[(int)EButtonVisibility::DEMO_PLAYER].m_pId = "demo-player"; + m_aVisibilityFunctions[(int)EButtonVisibility::DEMO_PLAYER].m_Function = [&]() { + return Client()->State() == IClient::STATE_DEMOPLAYBACK; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_1].m_pId = "extra-menu"; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_1].m_Function = [&]() { + return m_aExtraMenuActive[0]; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_2].m_pId = "extra-menu-2"; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_2].m_Function = [&]() { + return m_aExtraMenuActive[1]; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_3].m_pId = "extra-menu-3"; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_3].m_Function = [&]() { + return m_aExtraMenuActive[2]; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_4].m_pId = "extra-menu-4"; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_4].m_Function = [&]() { + return m_aExtraMenuActive[3]; + }; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_5].m_pId = "extra-menu-5"; + m_aVisibilityFunctions[(int)EButtonVisibility::EXTRA_MENU_5].m_Function = [&]() { + return m_aExtraMenuActive[4]; + }; +} + +int CTouchControls::NextActiveAction(int Action) const +{ + switch(Action) + { + case ACTION_FIRE: + return ACTION_HOOK; + case ACTION_HOOK: + return ACTION_FIRE; + default: + dbg_assert(false, "Action invalid for NextActiveAction"); + return NUM_ACTIONS; + } +} + +int CTouchControls::NextDirectTouchAction() const +{ + if(m_pClient->m_Snap.m_SpecInfo.m_Active) + { + switch(m_DirectTouchSpectate) + { + case EDirectTouchSpectateMode::DISABLED: + return NUM_ACTIONS; + case EDirectTouchSpectateMode::AIM: + return ACTION_AIM; + default: + dbg_assert(false, "m_DirectTouchSpectate invalid"); + return NUM_ACTIONS; + } + } + else + { + switch(m_DirectTouchIngame) + { + case EDirectTouchIngameMode::DISABLED: + return NUM_ACTIONS; + case EDirectTouchIngameMode::ACTION: + return m_ActionSelected; + case EDirectTouchIngameMode::AIM: + return ACTION_AIM; + case EDirectTouchIngameMode::FIRE: + return ACTION_FIRE; + case EDirectTouchIngameMode::HOOK: + return ACTION_HOOK; + default: + dbg_assert(false, "m_DirectTouchIngame invalid"); + return NUM_ACTIONS; + } + } +} + +void CTouchControls::UpdateButtons(const std::vector &vTouchFingerStates) +{ + // Update cached button visibilities and store time that buttons become visible. + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.UpdateVisibility(); + } + + const int DirectTouchAction = NextDirectTouchAction(); + const vec2 ScreenSize = CalculateScreenSize(); + + std::vector vRemainingTouchFingerStates = vTouchFingerStates; + + // Remove remaining finger states for fingers which are responsible for active actions + // and release action when the finger responsible for it is not pressed down anymore. + bool GotDirectFingerState = false; // Whether DirectFingerState is valid + IInput::CTouchFingerState DirectFingerState{}; // The finger that will be used to update the mouse position + for(int Action = ACTION_AIM; Action < NUM_ACTIONS; ++Action) + { + if(!m_aDirectTouchActionStates[Action].m_Active) + { + continue; + } + + const auto ActiveFinger = std::find_if(vRemainingTouchFingerStates.begin(), vRemainingTouchFingerStates.end(), [&](const IInput::CTouchFingerState &TouchFingerState) { + return TouchFingerState.m_Finger == m_aDirectTouchActionStates[Action].m_Finger; + }); + if(ActiveFinger == vRemainingTouchFingerStates.end() || DirectTouchAction == NUM_ACTIONS) + { + m_aDirectTouchActionStates[Action].m_Active = false; + if(Action != ACTION_AIM) + { + Console()->ExecuteLineStroked(0, ACTION_COMMANDS[Action]); + } + } + else + { + if(Action == m_DirectTouchLastAction) + { + GotDirectFingerState = true; + DirectFingerState = *ActiveFinger; + } + vRemainingTouchFingerStates.erase(ActiveFinger); + } + } + + // Update touch button states after the active action fingers were removed from the vector + // so that current cursor movement can cross over touch buttons without activating them. + + // Activate visible, inactive buttons with hovered finger. Deactivate previous button being + // activated by the same finger. Touch buttons are only activated if they became visible + // before the respective touch finger was pressed down, to prevent repeatedly activating + // overlapping buttons of excluding visibilities. + for(CTouchButton &TouchButton : m_vTouchButtons) + { + if(!TouchButton.IsVisible() || TouchButton.m_pBehavior->IsActive()) + { + continue; + } + const auto FingerInsideButton = std::find_if(vRemainingTouchFingerStates.begin(), vRemainingTouchFingerStates.end(), [&](const IInput::CTouchFingerState &TouchFingerState) { + return TouchButton.m_VisibilityStartTime < TouchFingerState.m_PressTime && + TouchButton.IsInside(TouchFingerState.m_Position * ScreenSize); + }); + if(FingerInsideButton == vRemainingTouchFingerStates.end()) + { + continue; + } + const auto OtherHoveredTouchButton = std::find_if(m_vTouchButtons.begin(), m_vTouchButtons.end(), [&](const CTouchButton &Button) { + return &Button != &TouchButton && Button.IsVisible() && Button.IsInside(FingerInsideButton->m_Position * ScreenSize); + }); + if(OtherHoveredTouchButton != m_vTouchButtons.end()) + { + // Do not activate any button if multiple overlapping buttons are hovered. + // TODO: Prevent overlapping buttons entirely when parsing the button configuration? + vRemainingTouchFingerStates.erase(FingerInsideButton); + continue; + } + auto PrevActiveTouchButton = std::find_if(m_vTouchButtons.begin(), m_vTouchButtons.end(), [&](const CTouchButton &Button) { + return Button.m_pBehavior->IsActive(FingerInsideButton->m_Finger); + }); + if(PrevActiveTouchButton != m_vTouchButtons.end()) + { + PrevActiveTouchButton->m_pBehavior->SetInactive(); + } + TouchButton.m_pBehavior->SetActive(*FingerInsideButton); + } + + // Deactivate touch buttons only when the respective finger is released, so touch buttons + // are kept active also if the finger is moved outside the button. + for(CTouchButton &TouchButton : m_vTouchButtons) + { + if(!TouchButton.IsVisible()) + { + TouchButton.m_pBehavior->SetInactive(); + continue; + } + if(!TouchButton.m_pBehavior->IsActive()) + { + continue; + } + const auto ActiveFinger = std::find_if(vRemainingTouchFingerStates.begin(), vRemainingTouchFingerStates.end(), [&](const IInput::CTouchFingerState &TouchFingerState) { + return TouchFingerState.m_Finger == TouchButton.m_pBehavior->m_Finger; + }); + if(ActiveFinger == vRemainingTouchFingerStates.end()) + { + TouchButton.m_pBehavior->SetInactive(); + } + else + { + // Update the already active touch button with the current finger state + TouchButton.m_pBehavior->SetActive(*ActiveFinger); + } + } + + // Remove remaining fingers for active buttons after updating the buttons. + for(CTouchButton &TouchButton : m_vTouchButtons) + { + if(!TouchButton.m_pBehavior->IsActive()) + { + continue; + } + const auto ActiveFinger = std::find_if(vRemainingTouchFingerStates.begin(), vRemainingTouchFingerStates.end(), [&](const IInput::CTouchFingerState &TouchFingerState) { + return TouchFingerState.m_Finger == TouchButton.m_pBehavior->m_Finger; + }); + dbg_assert(ActiveFinger != vRemainingTouchFingerStates.end(), "Active button finger not found"); + vRemainingTouchFingerStates.erase(ActiveFinger); + } + + // TODO: Support standard gesture to zoom (enabled separately for ingame and spectator) + + // Activate action if there is an unhandled pressed down finger. + int ActivateAction = NUM_ACTIONS; + if(DirectTouchAction != NUM_ACTIONS && !vRemainingTouchFingerStates.empty() && !m_aDirectTouchActionStates[DirectTouchAction].m_Active) + { + GotDirectFingerState = true; + DirectFingerState = vRemainingTouchFingerStates[0]; + vRemainingTouchFingerStates.erase(vRemainingTouchFingerStates.begin()); + m_aDirectTouchActionStates[DirectTouchAction].m_Active = true; + m_aDirectTouchActionStates[DirectTouchAction].m_Finger = DirectFingerState.m_Finger; + m_DirectTouchLastAction = DirectTouchAction; + ActivateAction = DirectTouchAction; + } + + // Update mouse position based on the finger responsible for the last active action. + if(GotDirectFingerState) + { + const float Zoom = m_pClient->m_Snap.m_SpecInfo.m_Active ? m_pClient->m_Camera.m_Zoom : 1.0f; + vec2 WorldScreenSize; + RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), Zoom, &WorldScreenSize.x, &WorldScreenSize.y); + CControls &Controls = GameClient()->m_Controls; + if(m_pClient->m_Snap.m_SpecInfo.m_Active) + { + Controls.m_aMousePos[g_Config.m_ClDummy] += -DirectFingerState.m_Delta * WorldScreenSize; + Controls.m_aMousePos[g_Config.m_ClDummy].x = clamp(Controls.m_aMousePos[g_Config.m_ClDummy].x, -201.0f * 32, (Collision()->GetWidth() + 201.0f) * 32.0f); + Controls.m_aMousePos[g_Config.m_ClDummy].y = clamp(Controls.m_aMousePos[g_Config.m_ClDummy].y, -201.0f * 32, (Collision()->GetHeight() + 201.0f) * 32.0f); + } + else + { + Controls.m_aMousePos[g_Config.m_ClDummy] = (DirectFingerState.m_Position - vec2(0.5f, 0.5f)) * WorldScreenSize; + } + } + + // Activate action after the mouse position is set. + if(ActivateAction != ACTION_AIM && ActivateAction != NUM_ACTIONS) + { + Console()->ExecuteLineStroked(1, ACTION_COMMANDS[ActivateAction]); + } +} + +void CTouchControls::ResetButtons() +{ + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.m_pBehavior->Reset(); + } + for(CActionState &ActionState : m_aDirectTouchActionStates) + { + ActionState.m_Active = false; + } +} + +void CTouchControls::RenderButtons() +{ + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.UpdateVisibility(); + if(!TouchButton.IsVisible()) + { + continue; + } + TouchButton.Render(); + } +} + +vec2 CTouchControls::CalculateScreenSize() const +{ + const float ScreenHeight = 400.0f * 3.0f; + const float ScreenWidth = ScreenHeight * Graphics()->ScreenAspect(); + return vec2(ScreenWidth, ScreenHeight); +} + +bool CTouchControls::ParseConfiguration(const void *pFileData, unsigned FileLength) +{ + json_settings JsonSettings{}; + char aError[256]; + json_value *pConfiguration = json_parse_ex(&JsonSettings, static_cast(pFileData), FileLength, aError); + + if(pConfiguration == nullptr) + { + log_error("touch_controls", "Failed to parse configuration (invalid json): '%s'", aError); + return false; + } + if(pConfiguration->type != json_object) + { + log_error("touch_controls", "Failed to parse configuration: root must be an object"); + json_value_free(pConfiguration); + return false; + } + + std::optional ParsedDirectTouchIngame = ParseDirectTouchIngameMode(&(*pConfiguration)["direct-touch-ingame"]); + if(!ParsedDirectTouchIngame.has_value()) + { + json_value_free(pConfiguration); + return false; + } + + std::optional ParsedDirectTouchSpectate = ParseDirectTouchSpectateMode(&(*pConfiguration)["direct-touch-spectate"]); + if(!ParsedDirectTouchSpectate.has_value()) + { + json_value_free(pConfiguration); + return false; + } + + const json_value &TouchButtons = (*pConfiguration)["touch-buttons"]; + if(TouchButtons.type != json_array) + { + log_error("touch_controls", "Failed to parse configuration: attribute 'touch-buttons' must specify an array"); + json_value_free(pConfiguration); + return false; + } + + std::vector vParsedTouchButtons; + vParsedTouchButtons.reserve(TouchButtons.u.array.length); + for(unsigned ButtonIndex = 0; ButtonIndex < TouchButtons.u.array.length; ++ButtonIndex) + { + std::optional ParsedButton = ParseButton(&TouchButtons[ButtonIndex]); + if(!ParsedButton.has_value()) + { + log_error("touch_controls", "Failed to parse configuration: could not parse button at index '%d'", ButtonIndex); + json_value_free(pConfiguration); + return false; + } + + vParsedTouchButtons.push_back(std::move(ParsedButton.value())); + } + + // Parsing successful. Apply parsed configuration. + m_DirectTouchIngame = ParsedDirectTouchIngame.value(); + m_DirectTouchSpectate = ParsedDirectTouchSpectate.value(); + + m_pPrimaryJoystickTouchButtonBehavior = nullptr; + m_vTouchButtons = std::move(vParsedTouchButtons); + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.UpdatePointers(); + TouchButton.UpdateScreenFromUnitRect(); + TouchButton.UpdateBackgroundCorners(); + } + + json_value_free(pConfiguration); + + return true; +} + +std::optional CTouchControls::ParseDirectTouchIngameMode(const json_value *pModeValue) +{ + // TODO: Remove json_boolean backwards compatibility + const json_value &DirectTouchIngame = *pModeValue; + if(DirectTouchIngame.type != json_boolean && DirectTouchIngame.type != json_string) + { + log_error("touch_controls", "Failed to parse configuration: attribute 'direct-touch-ingame' must specify a string"); + return {}; + } + if(DirectTouchIngame.type == json_boolean) + { + return DirectTouchIngame.u.boolean ? EDirectTouchIngameMode::ACTION : EDirectTouchIngameMode::DISABLED; + } + EDirectTouchIngameMode ParsedDirectTouchIngame = EDirectTouchIngameMode::NUM_STATES; + for(int CurrentMode = (int)EDirectTouchIngameMode::DISABLED; CurrentMode < (int)EDirectTouchIngameMode::NUM_STATES; ++CurrentMode) + { + if(str_comp(DirectTouchIngame.u.string.ptr, DIRECT_TOUCH_INGAME_MODE_NAMES[CurrentMode]) == 0) + { + ParsedDirectTouchIngame = (EDirectTouchIngameMode)CurrentMode; + break; + } + } + if(ParsedDirectTouchIngame == EDirectTouchIngameMode::NUM_STATES) + { + log_error("touch_controls", "Failed to parse configuration: attribute 'direct-touch-ingame' specifies unknown value '%s'", DirectTouchIngame.u.string.ptr); + return {}; + } + return ParsedDirectTouchIngame; +} + +std::optional CTouchControls::ParseDirectTouchSpectateMode(const json_value *pModeValue) +{ + // TODO: Remove json_boolean backwards compatibility + const json_value &DirectTouchSpectate = *pModeValue; + if(DirectTouchSpectate.type != json_boolean && DirectTouchSpectate.type != json_string) + { + log_error("touch_controls", "Failed to parse configuration: attribute 'direct-touch-spectate' must specify a string"); + return {}; + } + if(DirectTouchSpectate.type == json_boolean) + { + return DirectTouchSpectate.u.boolean ? EDirectTouchSpectateMode::AIM : EDirectTouchSpectateMode::DISABLED; + } + EDirectTouchSpectateMode ParsedDirectTouchSpectate = EDirectTouchSpectateMode::NUM_STATES; + for(int CurrentMode = (int)EDirectTouchSpectateMode::DISABLED; CurrentMode < (int)EDirectTouchSpectateMode::NUM_STATES; ++CurrentMode) + { + if(str_comp(DirectTouchSpectate.u.string.ptr, DIRECT_TOUCH_SPECTATE_MODE_NAMES[CurrentMode]) == 0) + { + ParsedDirectTouchSpectate = (EDirectTouchSpectateMode)CurrentMode; + break; + } + } + if(ParsedDirectTouchSpectate == EDirectTouchSpectateMode::NUM_STATES) + { + log_error("touch_controls", "Failed to parse configuration: attribute 'direct-touch-spectate' specifies unknown value '%s'", DirectTouchSpectate.u.string.ptr); + return {}; + } + return ParsedDirectTouchSpectate; +} + +std::optional CTouchControls::ParseButton(const json_value *pButtonObject) +{ + const json_value &ButtonObject = *pButtonObject; + if(ButtonObject.type != json_object) + { + log_error("touch_controls", "Failed to parse touch button: must be an object"); + return {}; + } + + const auto &&ParsePositionSize = [&](const char *pAttribute, int &ParsedValue, int Min, int Max) { + const json_value &AttributeValue = ButtonObject[pAttribute]; + if(AttributeValue.type != json_integer || !in_range(AttributeValue.u.integer, Min, Max)) + { + log_error("touch_controls", "Failed to parse touch button: attribute '%s' must specify an integer between '%d' and '%d'", pAttribute, Min, Max); + return false; + } + ParsedValue = AttributeValue.u.integer; + return true; + }; + CUnitRect ParsedUnitRect; + if(!ParsePositionSize("w", ParsedUnitRect.m_W, BUTTON_SIZE_MINIMUM, BUTTON_SIZE_MAXIMUM) || + !ParsePositionSize("h", ParsedUnitRect.m_H, BUTTON_SIZE_MINIMUM, BUTTON_SIZE_MAXIMUM)) + { + return {}; + } + if(!ParsePositionSize("x", ParsedUnitRect.m_X, 0, BUTTON_SIZE_SCALE - ParsedUnitRect.m_W) || + !ParsePositionSize("y", ParsedUnitRect.m_Y, 0, BUTTON_SIZE_SCALE - ParsedUnitRect.m_H)) + { + return {}; + } + + const json_value &Shape = ButtonObject["shape"]; + if(Shape.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'shape' must specify a string"); + return {}; + } + EButtonShape ParsedShape = EButtonShape::NUM_SHAPES; + for(int CurrentShape = (int)EButtonShape::RECT; CurrentShape < (int)EButtonShape::NUM_SHAPES; ++CurrentShape) + { + if(str_comp(Shape.u.string.ptr, SHAPE_NAMES[CurrentShape]) == 0) + { + ParsedShape = (EButtonShape)CurrentShape; + break; + } + } + if(ParsedShape == EButtonShape::NUM_SHAPES) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'shape' specifies unknown value '%s'", Shape.u.string.ptr); + return {}; + } + + const json_value &Visibilities = ButtonObject["visibilities"]; + if(Visibilities.type != json_array) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'visibilities' must specify an array"); + return {}; + } + std::vector vParsedVisibilities; + for(unsigned VisibilityIndex = 0; VisibilityIndex < Visibilities.u.array.length; ++VisibilityIndex) + { + const json_value &Visibility = Visibilities[VisibilityIndex]; + if(Visibility.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'visibilities' does not specify string at index '%d'", VisibilityIndex); + return {}; + } + EButtonVisibility ParsedVisibility = EButtonVisibility::NUM_VISIBILITIES; + const bool ParsedParity = Visibility.u.string.ptr[0] != '-'; + const char *pVisibilityString = ParsedParity ? Visibility.u.string.ptr : &Visibility.u.string.ptr[1]; + for(int CurrentVisibility = (int)EButtonVisibility::INGAME; CurrentVisibility < (int)EButtonVisibility::NUM_VISIBILITIES; ++CurrentVisibility) + { + if(str_comp(pVisibilityString, m_aVisibilityFunctions[CurrentVisibility].m_pId) == 0) + { + ParsedVisibility = (EButtonVisibility)CurrentVisibility; + break; + } + } + if(ParsedVisibility == EButtonVisibility::NUM_VISIBILITIES) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'visibilities' specifies unknown value '%s' at index '%d'", pVisibilityString, VisibilityIndex); + return {}; + } + const bool VisibilityAlreadyUsed = std::any_of(vParsedVisibilities.begin(), vParsedVisibilities.end(), [&](CButtonVisibility OtherParsedVisibility) { + return OtherParsedVisibility.m_Type == ParsedVisibility; + }); + if(VisibilityAlreadyUsed) + { + log_error("touch_controls", "Failed to parse touch button: attribute 'visibilities' specifies duplicate value '%s' at '%d'", pVisibilityString, VisibilityIndex); + return {}; + } + vParsedVisibilities.emplace_back(ParsedVisibility, ParsedParity); + } + + std::unique_ptr pParsedBehavior = ParseBehavior(&ButtonObject["behavior"]); + if(pParsedBehavior == nullptr) + { + log_error("touch_controls", "Failed to parse touch button: failed to parse attribute 'behavior' (see details above)"); + return {}; + } + + CTouchButton Button(this); + Button.m_UnitRect = ParsedUnitRect; + Button.m_Shape = ParsedShape; + Button.m_vVisibilities = std::move(vParsedVisibilities); + Button.m_pBehavior = std::move(pParsedBehavior); + return Button; +} + +std::unique_ptr CTouchControls::ParseBehavior(const json_value *pBehaviorObject) +{ + const json_value &BehaviorObject = *pBehaviorObject; + if(BehaviorObject.type != json_object) + { + log_error("touch_controls", "Failed to parse touch button behavior: must be an object"); + return nullptr; + } + + const json_value &BehaviorType = BehaviorObject["type"]; + if(BehaviorType.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior: attribute 'type' must specify a string"); + return nullptr; + } + + if(str_comp(BehaviorType.u.string.ptr, CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE) == 0) + { + return ParsePredefinedBehavior(&BehaviorObject); + } + else if(str_comp(BehaviorType.u.string.ptr, CBindTouchButtonBehavior::BEHAVIOR_TYPE) == 0) + { + return ParseBindBehavior(&BehaviorObject); + } + else if(str_comp(BehaviorType.u.string.ptr, CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE) == 0) + { + return ParseBindToggleBehavior(&BehaviorObject); + } + else + { + log_error("touch_controls", "Failed to parse touch button behavior: attribute 'type' specifies unknown value '%s'", BehaviorType.u.string.ptr); + return nullptr; + } +} + +std::unique_ptr CTouchControls::ParsePredefinedBehavior(const json_value *pBehaviorObject) +{ + const json_value &BehaviorObject = *pBehaviorObject; + const json_value &PredefinedId = BehaviorObject["id"]; + if(PredefinedId.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'id' must specify a string", CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE); + return nullptr; + } + + class CBehaviorFactory + { + public: + const char *m_pId; + std::function(const json_value *pBehaviorObject)> m_Factory; + }; + static const CBehaviorFactory BEHAVIOR_FACTORIES[] = { + {CIngameMenuTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CExtraMenuTouchButtonBehavior::BEHAVIOR_ID, [&](const json_value *pBehavior) { return ParseExtraMenuBehavior(pBehavior); }}, + {CEmoticonTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CSpectateTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CSwapActionTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CUseActionTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CJoystickActionTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CJoystickAimTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CJoystickFireTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}, + {CJoystickHookTouchButtonBehavior::BEHAVIOR_ID, [](const json_value *pBehavior) { return std::make_unique(); }}}; + for(const CBehaviorFactory &BehaviorFactory : BEHAVIOR_FACTORIES) + { + if(str_comp(PredefinedId.u.string.ptr, BehaviorFactory.m_pId) == 0) + { + return BehaviorFactory.m_Factory(&BehaviorObject); + } + } + + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'id' specifies unknown value '%s'", CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE, PredefinedId.u.string.ptr); + return nullptr; +} + +std::unique_ptr CTouchControls::ParseExtraMenuBehavior(const json_value *pBehaviorObject) +{ + const json_value &BehaviorObject = *pBehaviorObject; + const json_value &MenuNumber = BehaviorObject["number"]; + // TODO: Remove json_none backwards compatibility + const int MaxNumber = (int)EButtonVisibility::EXTRA_MENU_5 - (int)EButtonVisibility::EXTRA_MENU_1 + 1; + if(MenuNumber.type != json_none && (MenuNumber.type != json_integer || !in_range(MenuNumber.u.integer, 1, MaxNumber))) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s' and ID '%s': attribute 'number' must specify an integer between '%d' and '%d'", + CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE, CExtraMenuTouchButtonBehavior::BEHAVIOR_ID, 1, MaxNumber); + return nullptr; + } + int ParsedMenuNumber = MenuNumber.type == json_none ? 0 : (MenuNumber.u.integer - 1); + + return std::make_unique(ParsedMenuNumber); +} + +std::unique_ptr CTouchControls::ParseBindBehavior(const json_value *pBehaviorObject) +{ + const json_value &BehaviorObject = *pBehaviorObject; + const json_value &Label = BehaviorObject["label"]; + if(Label.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'label' must specify a string", CBindTouchButtonBehavior::BEHAVIOR_TYPE); + return nullptr; + } + + const json_value &LabelType = BehaviorObject["label-type"]; + if(LabelType.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'label-type' must specify a string", CBindTouchButtonBehavior::BEHAVIOR_TYPE); + return {}; + } + CButtonLabel::EType ParsedLabelType = CButtonLabel::EType::NUM_TYPES; + for(int CurrentType = (int)CButtonLabel::EType::PLAIN; CurrentType < (int)CButtonLabel::EType::NUM_TYPES; ++CurrentType) + { + if(str_comp(LabelType.u.string.ptr, LABEL_TYPE_NAMES[CurrentType]) == 0) + { + ParsedLabelType = (CButtonLabel::EType)CurrentType; + break; + } + } + if(ParsedLabelType == CButtonLabel::EType::NUM_TYPES) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'label-type' specifies unknown value '%s'", CBindTouchButtonBehavior::BEHAVIOR_TYPE, LabelType.u.string.ptr); + return {}; + } + + const json_value &Command = BehaviorObject["command"]; + if(Command.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'command' must specify a string", CBindTouchButtonBehavior::BEHAVIOR_TYPE); + return nullptr; + } + + return std::make_unique(Label.u.string.ptr, ParsedLabelType, Command.u.string.ptr); +} + +std::unique_ptr CTouchControls::ParseBindToggleBehavior(const json_value *pBehaviorObject) +{ + const json_value &CommandsObject = (*pBehaviorObject)["commands"]; + if(CommandsObject.type != json_array || CommandsObject.u.array.length < 2) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': attribute 'commands' must specify an array with at least 2 entries", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE); + return {}; + } + + std::vector vCommands; + vCommands.reserve(CommandsObject.u.array.length); + for(unsigned CommandIndex = 0; CommandIndex < CommandsObject.u.array.length; ++CommandIndex) + { + const json_value &CommandObject = CommandsObject[CommandIndex]; + if(CommandObject.type != json_object) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': failed to parse command at index '%d': attribute 'commands' must specify an array of objects", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE, CommandIndex); + return nullptr; + } + + const json_value &Label = CommandObject["label"]; + if(Label.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': failed to parse command at index '%d': attribute 'label' must specify a string", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE, CommandIndex); + return nullptr; + } + + const json_value &LabelType = CommandObject["label-type"]; + if(LabelType.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': failed to parse command at index '%d': attribute 'label-type' must specify a string", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE, CommandIndex); + return {}; + } + CButtonLabel::EType ParsedLabelType = CButtonLabel::EType::NUM_TYPES; + for(int CurrentType = (int)CButtonLabel::EType::PLAIN; CurrentType < (int)CButtonLabel::EType::NUM_TYPES; ++CurrentType) + { + if(str_comp(LabelType.u.string.ptr, LABEL_TYPE_NAMES[CurrentType]) == 0) + { + ParsedLabelType = (CButtonLabel::EType)CurrentType; + break; + } + } + if(ParsedLabelType == CButtonLabel::EType::NUM_TYPES) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': failed to parse command at index '%d': attribute 'label-type' specifies unknown value '%s'", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE, CommandIndex, LabelType.u.string.ptr); + return {}; + } + + const json_value &Command = CommandObject["command"]; + if(Command.type != json_string) + { + log_error("touch_controls", "Failed to parse touch button behavior of type '%s': failed to parse command at index '%d': attribute 'command' must specify a string", CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE, CommandIndex); + return nullptr; + } + vCommands.emplace_back(Label.u.string.ptr, ParsedLabelType, Command.u.string.ptr); + } + return std::make_unique(std::move(vCommands)); +} + +void CTouchControls::WriteConfiguration(CJsonWriter *pWriter) +{ + pWriter->BeginObject(); + + pWriter->WriteAttribute("direct-touch-ingame"); + pWriter->WriteStrValue(DIRECT_TOUCH_INGAME_MODE_NAMES[(int)m_DirectTouchIngame]); + + pWriter->WriteAttribute("direct-touch-spectate"); + pWriter->WriteStrValue(DIRECT_TOUCH_SPECTATE_MODE_NAMES[(int)m_DirectTouchSpectate]); + + pWriter->WriteAttribute("touch-buttons"); + pWriter->BeginArray(); + for(CTouchButton &TouchButton : m_vTouchButtons) + { + TouchButton.WriteToConfiguration(pWriter); + } + pWriter->EndArray(); + + pWriter->EndObject(); +} diff --git a/src/game/client/components/touch_controls.h b/src/game/client/components/touch_controls.h new file mode 100644 index 00000000000..e13c4a35b87 --- /dev/null +++ b/src/game/client/components/touch_controls.h @@ -0,0 +1,548 @@ +#ifndef GAME_CLIENT_COMPONENTS_TOUCH_CONTROLS_H +#define GAME_CLIENT_COMPONENTS_TOUCH_CONTROLS_H + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +class CJsonWriter; +typedef struct _json_value json_value; + +class CTouchControls : public CComponent +{ +public: + enum class EDirectTouchIngameMode + { + DISABLED, + ACTION, + AIM, + FIRE, + HOOK, + NUM_STATES + }; + enum class EDirectTouchSpectateMode + { + DISABLED, + AIM, + NUM_STATES + }; + + int Sizeof() const override { return sizeof(*this); } + void OnInit() override; + void OnReset() override; + void OnWindowResize() override; + bool OnTouchState(const std::vector &vTouchFingerStates) override; + void OnRender() override; + + bool LoadConfigurationFromFile(int StorageType); + bool LoadConfigurationFromClipboard(); + bool SaveConfigurationToFile(); + void SaveConfigurationToClipboard(); + + EDirectTouchIngameMode DirectTouchIngame() const { return m_DirectTouchIngame; } + void SetDirectTouchIngame(EDirectTouchIngameMode DirectTouchIngame) + { + m_DirectTouchIngame = DirectTouchIngame; + m_EditingChanges = true; + } + EDirectTouchSpectateMode DirectTouchSpectate() const { return m_DirectTouchSpectate; } + void SetDirectTouchSpectate(EDirectTouchSpectateMode DirectTouchSpectate) + { + m_DirectTouchSpectate = DirectTouchSpectate; + m_EditingChanges = true; + } + bool IsEditingActive() const { return m_EditingActive; } + void SetEditingActive(bool EditingActive) { m_EditingActive = EditingActive; } + bool HasEditingChanges() const { return m_EditingChanges; } + void SetEditingChanges(bool EditingChanges) { m_EditingChanges = EditingChanges; } + +private: + static constexpr const char *const DIRECT_TOUCH_INGAME_MODE_NAMES[(int)EDirectTouchIngameMode::NUM_STATES] = {"disabled", "action", "aim", "fire", "hook"}; + static constexpr const char *const DIRECT_TOUCH_SPECTATE_MODE_NAMES[(int)EDirectTouchSpectateMode::NUM_STATES] = {"disabled", "aim"}; + + enum class EButtonShape + { + RECT, + CIRCLE, + NUM_SHAPES + }; + + static constexpr const char *const SHAPE_NAMES[(int)EButtonShape::NUM_SHAPES] = {"rect", "circle"}; + + enum class EButtonVisibility + { + INGAME, + ZOOM_ALLOWED, + VOTE_ACTIVE, + DUMMY_ALLOWED, + DUMMY_CONNECTED, + RCON_AUTHED, + DEMO_PLAYER, + EXTRA_MENU_1, + EXTRA_MENU_2, + EXTRA_MENU_3, + EXTRA_MENU_4, + EXTRA_MENU_5, + NUM_VISIBILITIES + }; + + class CButtonVisibility + { + public: + EButtonVisibility m_Type; + bool m_Parity; + + CButtonVisibility(EButtonVisibility Type, bool Parity) : + m_Type(Type), m_Parity(Parity) {} + }; + + class CButtonVisibilityData + { + public: + const char *m_pId; + std::function m_Function; + }; + + CButtonVisibilityData m_aVisibilityFunctions[(int)EButtonVisibility::NUM_VISIBILITIES]; + + enum + { + ACTION_AIM, + ACTION_FIRE, + ACTION_HOOK, + NUM_ACTIONS + }; + + class CButtonLabel + { + public: + enum class EType + { + /** + * Label is used as is. + */ + PLAIN, + /** + * Label is localized. Only usable for default button labels for which there must be + * corresponding `Localizable`-calls in code and string in the translation files. + */ + LOCALIZED, + /** + * Icon font is used for the label. + */ + ICON, + /** + * Number of label types. + */ + NUM_TYPES + }; + + EType m_Type; + const char *m_pLabel; + }; + + static constexpr const char *const LABEL_TYPE_NAMES[(int)CButtonLabel::EType::NUM_TYPES] = {"plain", "localized", "icon"}; + + class CUnitRect + { + public: + int m_X; + int m_Y; + int m_W; + int m_H; + }; + + class CTouchButtonBehavior; + + class CTouchButton + { + public: + CTouchButton(CTouchControls *pTouchControls); + CTouchButton(CTouchButton &&Other) noexcept; + CTouchButton(const CTouchButton &Other) = delete; + + CTouchButton &operator=(const CTouchButton &Other) = delete; + CTouchButton &operator=(CTouchButton &&Other) noexcept; + + CTouchControls *m_pTouchControls; + + CUnitRect m_UnitRect; + CUIRect m_ScreenRect; + + EButtonShape m_Shape; + int m_BackgroundCorners; // only used with EButtonShape::RECT + + std::vector m_vVisibilities; + std::unique_ptr m_pBehavior; + + bool m_VisibilityCached; + std::chrono::nanoseconds m_VisibilityStartTime; + + void UpdatePointers(); + void UpdateScreenFromUnitRect(); + void UpdateBackgroundCorners(); + + vec2 ClampTouchPosition(vec2 TouchPosition) const; + bool IsInside(vec2 TouchPosition) const; + void UpdateVisibility(); + bool IsVisible() const; + void Render() const; + void WriteToConfiguration(CJsonWriter *pWriter); + }; + + class CTouchButtonBehavior + { + public: + CTouchButton *m_pTouchButton; + CTouchControls *m_pTouchControls; + + bool m_Active; // variables below must only be used when active + IInput::CTouchFinger m_Finger; + vec2 m_ActivePosition; + vec2 m_AccumulatedDelta; + std::chrono::nanoseconds m_ActivationStartTime; + + virtual ~CTouchButtonBehavior() = default; + virtual void Init(CTouchButton *pTouchButton); + + void Reset(); + void SetActive(const IInput::CTouchFingerState &FingerState); + void SetInactive(); + bool IsActive() const; + bool IsActive(const IInput::CTouchFinger &Finger) const; + + virtual CButtonLabel GetLabel() const = 0; + virtual void OnActivate() {} + virtual void OnDeactivate() {} + virtual void OnUpdate() {} + virtual void WriteToConfiguration(CJsonWriter *pWriter) = 0; + }; + + /** + * Abstract class for predefined behaviors. + * + * Subclasses must implemented the concrete behavior and provide the label. + */ + class CPredefinedTouchButtonBehavior : public CTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_TYPE = "predefined"; + + CPredefinedTouchButtonBehavior(const char *pId) : + m_pId(pId) {} + + /** + * Implements the serialization for predefined behaviors. Subclasses + * may override this, but they should call the parent function first. + */ + void WriteToConfiguration(CJsonWriter *pWriter) override; + + private: + const char *m_pId; + }; + + class CIngameMenuTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "ingame-menu"; + + CIngameMenuTouchButtonBehavior() : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID) {} + + CButtonLabel GetLabel() const override; + void OnDeactivate() override; + }; + + class CExtraMenuTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "extra-menu"; + + CExtraMenuTouchButtonBehavior(int Number); + + CButtonLabel GetLabel() const override; + void OnDeactivate() override; + void WriteToConfiguration(CJsonWriter *pWriter) override; + + private: + int m_Number; + char m_aLabel[16]; + }; + + class CEmoticonTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "emoticon"; + + CEmoticonTouchButtonBehavior() : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID) {} + + CButtonLabel GetLabel() const override; + void OnDeactivate() override; + }; + + class CSpectateTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "spectate"; + + CSpectateTouchButtonBehavior() : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID) {} + + CButtonLabel GetLabel() const override; + void OnDeactivate() override; + }; + + class CSwapActionTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "swap-action"; + + CSwapActionTouchButtonBehavior() : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID) {} + + CButtonLabel GetLabel() const override; + void OnActivate() override; + void OnDeactivate() override; + + private: + int m_ActiveAction = NUM_ACTIONS; + }; + + class CUseActionTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "use-action"; + + CUseActionTouchButtonBehavior() : + CPredefinedTouchButtonBehavior(BEHAVIOR_ID) {} + + CButtonLabel GetLabel() const override; + void OnActivate() override; + void OnDeactivate() override; + + private: + int m_ActiveAction = NUM_ACTIONS; + }; + + class CJoystickTouchButtonBehavior : public CPredefinedTouchButtonBehavior + { + public: + CJoystickTouchButtonBehavior(const char *pId) : + CPredefinedTouchButtonBehavior(pId) {} + + CButtonLabel GetLabel() const override; + void OnActivate() override; + void OnDeactivate() override; + void OnUpdate() override; + int ActiveAction() const { return m_ActiveAction; } + virtual int SelectedAction() const = 0; + + private: + int m_ActiveAction = NUM_ACTIONS; + }; + + class CJoystickActionTouchButtonBehavior : public CJoystickTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "joystick-action"; + + CJoystickActionTouchButtonBehavior() : + CJoystickTouchButtonBehavior(BEHAVIOR_ID) {} + + void Init(CTouchButton *pTouchButton) override; + int SelectedAction() const override; + }; + + class CJoystickAimTouchButtonBehavior : public CJoystickTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "joystick-aim"; + + CJoystickAimTouchButtonBehavior() : + CJoystickTouchButtonBehavior(BEHAVIOR_ID) {} + + int SelectedAction() const override; + }; + + class CJoystickFireTouchButtonBehavior : public CJoystickTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "joystick-fire"; + + CJoystickFireTouchButtonBehavior() : + CJoystickTouchButtonBehavior(BEHAVIOR_ID) {} + + int SelectedAction() const override; + }; + + class CJoystickHookTouchButtonBehavior : public CJoystickTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_ID = "joystick-hook"; + + CJoystickHookTouchButtonBehavior() : + CJoystickTouchButtonBehavior(BEHAVIOR_ID) {} + + int SelectedAction() const override; + }; + + /** + * Generic behavior implementation that executes a console command like a bind. + */ + class CBindTouchButtonBehavior : public CTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_TYPE = "bind"; + + CBindTouchButtonBehavior(const char *pLabel, CButtonLabel::EType LabelType, const char *pCommand) : + m_Label(pLabel), + m_LabelType(LabelType), + m_Command(pCommand) {} + + CButtonLabel GetLabel() const override; + void OnActivate() override; + void OnDeactivate() override; + void OnUpdate() override; + void WriteToConfiguration(CJsonWriter *pWriter) override; + + private: + std::string m_Label; + CButtonLabel::EType m_LabelType; + std::string m_Command; + + bool m_Repeating = false; + std::chrono::nanoseconds m_LastUpdateTime; + std::chrono::nanoseconds m_AccumulatedRepeatingTime; + }; + + /** + * Generic behavior implementation that switches between executing one of two or more console commands. + */ + class CBindToggleTouchButtonBehavior : public CTouchButtonBehavior + { + public: + static constexpr const char *const BEHAVIOR_TYPE = "bind-toggle"; + + class CCommand + { + public: + std::string m_Label; + CButtonLabel::EType m_LabelType; + std::string m_Command; + + CCommand(const char *pLabel, CButtonLabel::EType LabelType, const char *pCommand) : + m_Label(pLabel), + m_LabelType(LabelType), + m_Command(pCommand) {} + }; + + CBindToggleTouchButtonBehavior(std::vector &&vCommands) : + m_vCommands(std::move(vCommands)) {} + + CButtonLabel GetLabel() const override; + void OnActivate() override; + void WriteToConfiguration(CJsonWriter *pWriter) override; + + private: + std::vector m_vCommands; + size_t m_ActiveCommandIndex = 0; + }; + + /** + * Mode of direct touch input while ingame. + * + * Saved to the touch controls configuration. + */ + EDirectTouchIngameMode m_DirectTouchIngame = EDirectTouchIngameMode::ACTION; + + /** + * Mode of direct touch input while spectating. + * + * Saved to the touch controls configuration. + */ + EDirectTouchSpectateMode m_DirectTouchSpectate = EDirectTouchSpectateMode::AIM; + + /** + * All touch buttons. + * + * Saved to the touch controls configuration. + */ + std::vector m_vTouchButtons; + + /** + * The activation states of the different extra menus which are toggle by the extra menu button behavior. + */ + bool m_aExtraMenuActive[(int)EButtonVisibility::EXTRA_MENU_5 - (int)EButtonVisibility::EXTRA_MENU_1 + 1] = {false}; + + /** + * The currently selected action which is used for direct touch and is changed and used by some button behaviors. + */ + int m_ActionSelected = ACTION_FIRE; + + /** + * The action that was last activated with direct touch input, which will determine the finger that will + * be used to update the mouse position from direct touch input. + */ + int m_DirectTouchLastAction = ACTION_FIRE; + + class CActionState + { + public: + bool m_Active = false; + IInput::CTouchFinger m_Finger; + }; + + /** + * The states of the different actions for direct touch input. + */ + CActionState m_aDirectTouchActionStates[NUM_ACTIONS]; + + /** + * A pointer to the action joystick, if any exists in the current configuration, or `nullptr` if none. + * This is set by @link CJoystickActionTouchButtonBehavior @endlink when it is initialized and always + * cleared before loading a new touch button configuration. + */ + CJoystickActionTouchButtonBehavior *m_pPrimaryJoystickTouchButtonBehavior; + + /** + * Whether editing mode is currently active. + */ + bool m_EditingActive = false; + + /** + * Whether there are changes to the current configuration in editing mode. + */ + bool m_EditingChanges = false; + + void InitVisibilityFunctions(); + int NextActiveAction(int Action) const; + int NextDirectTouchAction() const; + void UpdateButtons(const std::vector &vTouchFingerStates); + void ResetButtons(); + void RenderButtons(); + vec2 CalculateScreenSize() const; + + bool ParseConfiguration(const void *pFileData, unsigned FileLength); + std::optional ParseDirectTouchIngameMode(const json_value *pModeValue); + std::optional ParseDirectTouchSpectateMode(const json_value *pModeValue); + std::optional ParseButton(const json_value *pButtonObject); + std::unique_ptr ParseBehavior(const json_value *pBehaviorObject); + std::unique_ptr ParsePredefinedBehavior(const json_value *pBehaviorObject); + std::unique_ptr ParseExtraMenuBehavior(const json_value *pBehaviorObject); + std::unique_ptr ParseBindBehavior(const json_value *pBehaviorObject); + std::unique_ptr ParseBindToggleBehavior(const json_value *pBehaviorObject); + void WriteConfiguration(CJsonWriter *pWriter); +}; + +#endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index ce9dacccbfe..e8d15cdee7e 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -154,6 +154,7 @@ void CGameClient::OnConsoleInit() &m_Chat, &m_Broadcast, &m_DebugHud, + &m_TouchControls, &m_Scoreboard, &m_Statboard, &m_Motd, @@ -174,6 +175,7 @@ void CGameClient::OnConsoleInit() &m_Emoticon, &m_Menus, &m_Controls, + &m_TouchControls, &m_Binds}); // add basic console commands @@ -274,6 +276,15 @@ static void GenerateTimeoutCode(char *pTimeoutCode) } } +void CGameClient::InitializeLanguage() +{ + // set the language + g_Localization.LoadIndexfile(Storage(), Console()); + if(g_Config.m_ClShowWelcome) + g_Localization.SelectDefaultLanguage(Console(), g_Config.m_ClLanguagefile, sizeof(g_Config.m_ClLanguagefile)); + g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console()); +} + void CGameClient::OnInit() { const int64_t OnInitStart = time_get(); @@ -320,12 +331,6 @@ void CGameClient::OnInit() str_format(m_aDDNetVersionStr, sizeof(m_aDDNetVersionStr), "%s %s", CLIENT_NAME, GAME_RELEASE_VERSION); } - // set the language - g_Localization.LoadIndexfile(Storage(), Console()); - if(g_Config.m_ClShowWelcome) - g_Localization.SelectDefaultLanguage(Console(), g_Config.m_ClLanguagefile, sizeof(g_Config.m_ClLanguagefile)); - g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console()); - // TODO: this should be different // setup item sizes for(int i = 0; i < NUM_NETOBJTYPES; i++) @@ -453,6 +458,23 @@ void CGameClient::OnUpdate() } } + // handle touch events + const std::vector &vTouchFingerStates = Input()->TouchFingerStates(); + bool TouchHandled = false; + for(auto &pComponent : m_vpInput) + { + if(TouchHandled) + { + // Also update inactive components so they can handle touch fingers being released. + pComponent->OnTouchState({}); + } + else if(pComponent->OnTouchState(vTouchFingerStates)) + { + Input()->ClearTouchDeltas(); + TouchHandled = true; + } + } + // handle key presses Input()->ConsumeEvents([&](const IInput::CEvent &Event) { for(auto &pComponent : m_vpInput) @@ -652,11 +674,14 @@ void CGameClient::OnReset() // m_aTuningList is reset in LoadMapSettings + m_LastShowDistanceZoom = 0.0f; m_LastZoom = 0.0f; m_LastScreenAspect = 0.0f; + m_LastDeadzone = 0.0f; + m_LastFollowFactor = 0.0f; m_LastDummyConnected = false; - m_MultiViewPersonalZoom = 0; + m_MultiViewPersonalZoom = 0.0f; m_MultiViewActivated = false; m_MultiView.m_IsInit = false; @@ -766,6 +791,9 @@ void CGameClient::OnRender() } } + // update camera data prior to CControls::OnRender to allow CControls::m_aTargetPos to compensate using camera data + m_Camera.UpdateCamera(); + // render all systems for(auto &pComponent : m_vpAll) pComponent->OnRender(); @@ -2035,32 +2063,82 @@ void CGameClient::OnNewSnapshot() m_aShowOthers[g_Config.m_ClDummy] = g_Config.m_ClShowOthers; } - float ZoomToSend = m_Camera.m_Zoom; + float ShowDistanceZoom = m_Camera.m_Zoom; + float Zoom = m_Camera.m_Zoom; if(m_Camera.m_Zooming) { if(m_Camera.m_ZoomSmoothingTarget > m_Camera.m_Zoom) // Zooming out - ZoomToSend = m_Camera.m_ZoomSmoothingTarget; - else if(m_Camera.m_ZoomSmoothingTarget < m_Camera.m_Zoom && m_LastZoom > 0) // Zooming in - ZoomToSend = m_LastZoom; + ShowDistanceZoom = m_Camera.m_ZoomSmoothingTarget; + else if(m_Camera.m_ZoomSmoothingTarget < m_Camera.m_Zoom && m_LastShowDistanceZoom > 0) // Zooming in + ShowDistanceZoom = m_LastShowDistanceZoom; + + Zoom = m_Camera.m_ZoomSmoothingTarget; } - if(ZoomToSend != m_LastZoom || Graphics()->ScreenAspect() != m_LastScreenAspect || (Client()->DummyConnected() && !m_LastDummyConnected)) + float Deadzone = m_Camera.Deadzone(); + float FollowFactor = m_Camera.FollowFactor(); + + // initialize dummy vital when first connected + if(Client()->DummyConnected() && !m_LastDummyConnected) + { + { + CNetMsg_Cl_ShowDistance Msg; + float x, y; + RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), ShowDistanceZoom, &x, &y); + Msg.m_X = x; + Msg.m_Y = y; + CMsgPacker Packer(&Msg); + Msg.Pack(&Packer); + Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL); + } + { + CNetMsg_Cl_CameraInfo Msg; + Msg.m_Zoom = round_truncate(Zoom * 1000.f); + Msg.m_Deadzone = Deadzone; + Msg.m_FollowFactor = FollowFactor; + CMsgPacker Packer(&Msg); + Msg.Pack(&Packer); + Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL); + } + } + + // send show distance + if(ShowDistanceZoom != m_LastShowDistanceZoom || Graphics()->ScreenAspect() != m_LastScreenAspect) { CNetMsg_Cl_ShowDistance Msg; float x, y; - RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), ZoomToSend, &x, &y); + RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), ShowDistanceZoom, &x, &y); Msg.m_X = x; Msg.m_Y = y; - Client()->ChecksumData()->m_Zoom = ZoomToSend; + Client()->ChecksumData()->m_Zoom = ShowDistanceZoom; CMsgPacker Packer(&Msg); Msg.Pack(&Packer); - if(ZoomToSend != m_LastZoom) - Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL); - if(Client()->DummyConnected()) + + Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL); + if(Client()->DummyConnected() && m_LastDummyConnected) Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL); - m_LastZoom = ZoomToSend; - m_LastScreenAspect = Graphics()->ScreenAspect(); } + + // send camera info + if(Zoom != m_LastZoom || Deadzone != m_LastDeadzone || FollowFactor != m_LastFollowFactor) + { + CNetMsg_Cl_CameraInfo Msg; + Msg.m_Zoom = round_truncate(Zoom * 1000.f); + Msg.m_Deadzone = Deadzone; + Msg.m_FollowFactor = FollowFactor; + CMsgPacker Packer(&Msg); + Msg.Pack(&Packer); + + Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL); + if(Client()->DummyConnected() && m_LastDummyConnected) + Client()->SendMsg(IClient::CONN_DUMMY, &Packer, MSGFLAG_VITAL); + } + + m_LastShowDistanceZoom = ShowDistanceZoom; + m_LastZoom = Zoom; + m_LastScreenAspect = Graphics()->ScreenAspect(); + m_LastDeadzone = Deadzone; + m_LastFollowFactor = FollowFactor; m_LastDummyConnected = Client()->DummyConnected(); for(auto &pComponent : m_vpAll) @@ -4363,7 +4441,7 @@ float CGameClient::MapValue(float MaxValue, float MinValue, float MaxRange, floa void CGameClient::ResetMultiView() { m_Camera.SetZoom(std::pow(CCamera::ZOOM_STEP, g_Config.m_ClDefaultZoom - 10), g_Config.m_ClSmoothZoomTime); - m_MultiViewPersonalZoom = 0; + m_MultiViewPersonalZoom = 0.0f; m_MultiViewActivated = false; m_MultiView.m_Solo = false; m_MultiView.m_IsInit = false; diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 0634f310201..3c78ee501ba 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -66,6 +66,7 @@ #include "components/tclient/tater.h" #include "components/tclient/verify.h" #include "components/tclient/warlist.h" +#include "components/touch_controls.h" #include "components/voting.h" class CGameInfo @@ -154,6 +155,7 @@ class CGameClient : public IGameClient CSounds m_Sounds; CEmoticon m_Emoticon; CDamageInd m_DamageInd; + CTouchControls m_TouchControls; CVoting m_Voting; CVerify m_Verify; CSpectator m_Spectator; @@ -552,6 +554,7 @@ class CGameClient : public IGameClient virtual void OnFlagGrab(int TeamId); void OnWindowResize() override; + void InitializeLanguage() override; bool m_LanguageChanged = false; void OnLanguageChange(); void HandleLanguageChanged(); @@ -802,7 +805,7 @@ class CGameClient : public IGameClient vec2 GetSmoothPos(int ClientId); vec2 GetFreezePos(int ClientId); int m_MultiViewTeam; - int m_MultiViewPersonalZoom; + float m_MultiViewPersonalZoom; bool m_MultiViewShowHud; bool m_MultiViewActivated; bool m_aMultiViewId[MAX_CLIENTS]; @@ -837,8 +840,11 @@ class CGameClient : public IGameClient CTuningParams m_aTuningList[NUM_TUNEZONES]; CTuningParams *TuningList() { return m_aTuningList; } + float m_LastShowDistanceZoom; float m_LastZoom; float m_LastScreenAspect; + float m_LastDeadzone; + float m_LastFollowFactor; bool m_LastDummyConnected; void HandleMultiView(); diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index e27353b0d23..f2f113f624d 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -358,6 +358,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event) Line = GetString(); } Begin = i + 1; + str_sanitize_cc(Line.data()); m_pfnClipboardLineCallback(Line.c_str()); } } diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 6e2ce1fc1a3..b1c72903283 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1406,6 +1406,20 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) } } +void CEditor::DoToolbarImages(CUIRect ToolBar) +{ + CUIRect ToolBarTop, ToolBarBottom; + ToolBar.HSplitMid(&ToolBarTop, &ToolBarBottom, 5.0f); + + if(m_SelectedImage >= 0 && (size_t)m_SelectedImage < m_Map.m_vpImages.size()) + { + const std::shared_ptr pSelectedImage = m_Map.m_vpImages[m_SelectedImage]; + char aLabel[64]; + str_format(aLabel, sizeof(aLabel), "Size: %" PRIzu " × %" PRIzu, pSelectedImage->m_Width, pSelectedImage->m_Height); + Ui()->DoLabel(&ToolBarBottom, aLabel, 12.0f, TEXTALIGN_ML); + } +} + void CEditor::DoToolbarSounds(CUIRect ToolBar) { CUIRect ToolBarTop, ToolBarBottom; @@ -5275,23 +5289,30 @@ void CEditor::RenderFileDialog() Preview.Margin(10.0f, &Preview); if(m_FilePreviewState == PREVIEW_LOADED) { + CUIRect PreviewLabel, PreviewImage; + Preview.HSplitTop(20.0f, &PreviewLabel, &PreviewImage); + + char aLabel[64]; + str_format(aLabel, sizeof(aLabel), "Size: %d × %d", m_FilePreviewImageWidth, m_FilePreviewImageHeight); + Ui()->DoLabel(&PreviewLabel, aLabel, 12.0f, TEXTALIGN_ML); + int w = m_FilePreviewImageWidth; int h = m_FilePreviewImageHeight; - if(m_FilePreviewImageWidth > Preview.w) + if(m_FilePreviewImageWidth > PreviewImage.w) { - h = m_FilePreviewImageHeight * Preview.w / m_FilePreviewImageWidth; - w = Preview.w; + h = m_FilePreviewImageHeight * PreviewImage.w / m_FilePreviewImageWidth; + w = PreviewImage.w; } - if(h > Preview.h) + if(h > PreviewImage.h) { - w = w * Preview.h / h; - h = Preview.h; + w = w * PreviewImage.h / h; + h = PreviewImage.h; } Graphics()->TextureSet(m_FilePreviewImage); Graphics()->BlendNormal(); Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h); + IGraphics::CQuadItem QuadItem(PreviewImage.x, PreviewImage.y, w, h); Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } @@ -7913,27 +7934,30 @@ void CEditor::Render() } } - float Brightness = 0.25f; + const float BackgroundBrightness = 0.26f; + const float BackgroundScale = 80.0f; if(m_GuiActive) { - RenderBackground(MenuBar, m_BackgroundTexture, 128.0f, Brightness * 0); + RenderBackground(MenuBar, IGraphics::CTextureHandle(), BackgroundScale, 0.0f); MenuBar.Margin(2.0f, &MenuBar); - RenderBackground(ToolBox, m_BackgroundTexture, 128.0f, Brightness); + RenderBackground(ToolBox, g_pData->m_aImages[IMAGE_BACKGROUND_NOISE].m_Id, BackgroundScale, BackgroundBrightness); ToolBox.Margin(2.0f, &ToolBox); - RenderBackground(ToolBar, m_BackgroundTexture, 128.0f, Brightness); + RenderBackground(ToolBar, g_pData->m_aImages[IMAGE_BACKGROUND_NOISE].m_Id, BackgroundScale, BackgroundBrightness); ToolBar.Margin(2.0f, &ToolBar); ToolBar.VSplitLeft(m_ToolBoxWidth, &ModeBar, &ToolBar); - RenderBackground(StatusBar, m_BackgroundTexture, 128.0f, Brightness); + RenderBackground(StatusBar, g_pData->m_aImages[IMAGE_BACKGROUND_NOISE].m_Id, BackgroundScale, BackgroundBrightness); StatusBar.Margin(2.0f, &StatusBar); } // do the toolbar if(m_Mode == MODE_LAYERS) DoToolbarLayers(ToolBar); + else if(m_Mode == MODE_IMAGES) + DoToolbarImages(ToolBar); else if(m_Mode == MODE_SOUNDS) DoToolbarSounds(ToolBar); @@ -8056,7 +8080,7 @@ void CEditor::Render() { if(m_ActiveExtraEditor != EXTRAEDITOR_NONE) { - RenderBackground(ExtraEditor, m_BackgroundTexture, 128.0f, Brightness); + RenderBackground(ExtraEditor, g_pData->m_aImages[IMAGE_BACKGROUND_NOISE].m_Id, BackgroundScale, BackgroundBrightness); ExtraEditor.HMargin(2.0f, &ExtraEditor); ExtraEditor.VSplitRight(2.0f, &ExtraEditor, nullptr); } @@ -8457,7 +8481,6 @@ void CEditor::Init() Component.OnInit(this); m_CheckerTexture = Graphics()->LoadTexture("editor/checker.png", IStorage::TYPE_ALL); - m_BackgroundTexture = Graphics()->LoadTexture("editor/background.png", IStorage::TYPE_ALL); m_aCursorTextures[CURSOR_NORMAL] = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL); m_aCursorTextures[CURSOR_RESIZE_H] = Graphics()->LoadTexture("editor/cursor_resize.png", IStorage::TYPE_ALL); m_aCursorTextures[CURSOR_RESIZE_V] = m_aCursorTextures[CURSOR_RESIZE_H]; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index a32c9d7ca4f..32557f476f7 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -439,7 +439,6 @@ class CEditor : public IEditor } m_CheckerTexture.Invalidate(); - m_BackgroundTexture.Invalidate(); for(auto &CursorTexture : m_aCursorTextures) CursorTexture.Invalidate(); @@ -819,7 +818,6 @@ class CEditor : public IEditor bool m_ColorPipetteActive = false; IGraphics::CTextureHandle m_CheckerTexture; - IGraphics::CTextureHandle m_BackgroundTexture; enum ECursorType { @@ -973,6 +971,7 @@ class CEditor : public IEditor }; void DoMapEditor(CUIRect View); void DoToolbarLayers(CUIRect Toolbar); + void DoToolbarImages(CUIRect Toolbar); void DoToolbarSounds(CUIRect Toolbar); void DoQuad(int LayerIndex, const std::shared_ptr &pLayer, CQuad *pQuad, int Index); void PreparePointDrag(const std::shared_ptr &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex); diff --git a/src/game/editor/editor_server_settings.cpp b/src/game/editor/editor_server_settings.cpp index d91f41ed078..02678474cd5 100644 --- a/src/game/editor/editor_server_settings.cpp +++ b/src/game/editor/editor_server_settings.cpp @@ -325,7 +325,7 @@ void CEditor::DoMapSettingsEditBox(CMapSettingsBackend::CContext *pContext, cons ToolBar.VSplitRight(ToolBar.h, &ToolBar, &Button); // Do the unknown command toggle button - if(DoButton_FontIcon(&Context.m_AllowUnknownCommands, FONT_ICON_QUESTION, Context.m_AllowUnknownCommands, &Button, 0, "Disallow/allow unknown commands", IGraphics::CORNER_R)) + if(DoButton_FontIcon(&Context.m_AllowUnknownCommands, FONT_ICON_QUESTION, Context.m_AllowUnknownCommands, &Button, 0, "Disallow/allow unknown or invalid commands", IGraphics::CORNER_R)) { Context.m_AllowUnknownCommands = !Context.m_AllowUnknownCommands; Context.Update(); @@ -1393,7 +1393,7 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c // Validate argument from the parsed argument of the current setting. // If current setting is not valid, then there are no arguments which results in an error. - char Type = 'u'; // u = unknown, only possible for unknown commands when m_AllowUnknownCommands is true. + char Type = 'u'; // u = unknown if(ArgIndex < CommandArgCount) { SParsedMapSettingArg &Arg = m_pBackend->m_ParsedCommandArgs[m_pCurrentSetting].at(ArgIndex); @@ -1479,10 +1479,6 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c NewArg.m_Error = Error != SCommandParseError::ERROR_NONE || Length == 0 || m_Error.m_Type != SCommandParseError::ERROR_NONE; NewArg.m_ExpectedType = Type; - // Do not emit an error if we allow unknown commands and the current setting is invalid - if(m_AllowUnknownCommands && m_pCurrentSetting == nullptr) - NewArg.m_Error = false; - // Check error and fill the error field with different messages if(Error == SCommandParseError::ERROR_INVALID_VALUE || Error == SCommandParseError::ERROR_UNKNOWN_VALUE || Error == SCommandParseError::ERROR_OUT_OF_RANGE || Error == SCommandParseError::ERROR_INCOMPLETE) { @@ -1524,7 +1520,7 @@ void CMapSettingsBackend::CContext::ParseArgs(const char *pLineInputStr, const c m_Error.m_ArgIndex = ArgIndex; break; } - else if(!m_AllowUnknownCommands) + else { char aFormattedValue[256]; FormatDisplayValue(m_aCommand, aFormattedValue); @@ -1699,7 +1695,7 @@ void CMapSettingsBackend::CContext::UpdatePossibleMatches() } // If there are no matches, then the command is unknown - if(m_vPossibleMatches.empty() && !m_AllowUnknownCommands) + if(m_vPossibleMatches.empty()) { // Fill the error if we do not allow unknown commands char aFormattedValue[256]; @@ -1827,7 +1823,7 @@ void CMapSettingsBackend::CContext::ColorArguments(std::vector if(m_pLineInput && !m_pLineInput->IsEmpty()) { - if(!CommandIsValid() && !m_AllowUnknownCommands && m_CommentOffset != 0) + if(!CommandIsValid() && m_CommentOffset != 0) { // If command is invalid, override color splits with red, but not comment int ErrorLength = m_CommentOffset == -1 ? -1 : m_CommentOffset; @@ -2047,6 +2043,10 @@ bool CMapSettingsBackend::CContext::Valid() const { // Check if the entire setting is valid or not + // We don't need to check whether a command is valid if we allow unknown commands + if(m_AllowUnknownCommands) + return true; + if(m_CommentOffset == 0 || m_aCommand[0] == '\0') return true; // A "comment" setting is considered valid. @@ -2066,9 +2066,7 @@ bool CMapSettingsBackend::CContext::Valid() const } else { - // If we have an invalid setting, then we consider the entire setting as valid if we allow unknown commands - // as we cannot handle them. - return m_AllowUnknownCommands; + return false; } } diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 2bfe8ce84cc..680079c68c9 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -56,7 +56,9 @@ void CGameContext::ConCredits(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "DynamoFox, MilkeeyCat, iMilchshake, SchrodingerZhu,"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - "catseyenebulous, Rei-Tw, Matodor, Emilcha, art0007i & others"); + "catseyenebulous, Rei-Tw, Matodor, Emilcha, art0007i, SollyBunny,"); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "0xfaulty & others"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Based on DDRace by the DDRace developers,"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", @@ -1920,10 +1922,12 @@ void CGameContext::ConTeleCursor(IConsole::IResult *pResult, void *pUserData) return; } + // default to view pos when character is not available vec2 Pos = pPlayer->m_ViewPos; - if(pResult->NumArguments() == 0 && !pPlayer->IsPaused()) + if(pResult->NumArguments() == 0 && !pPlayer->IsPaused() && pPlayer->GetCharacter() && pPlayer->GetCharacter()->IsAlive()) { - Pos += vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + vec2 Target = vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + Pos = pPlayer->m_CameraInfo.ConvertTargetToWorld(pPlayer->GetCharacter()->GetPos(), Target); } else if(pResult->NumArguments() > 0) { diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index 26f817314d7..4b8fd2d2042 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -439,10 +439,12 @@ void CGameContext::ConTeleport(IConsole::IResult *pResult, void *pUserData) if(pChr && pPlayer && pSelf->GetPlayerChar(TeleTo)) { - vec2 Pos = pSelf->m_apPlayers[TeleTo]->m_ViewPos; - if(!pPlayer->IsPaused() && !pResult->NumArguments()) + // default to view pos when character is not available + vec2 Pos = pPlayer->m_ViewPos; + if(pResult->NumArguments() == 0 && !pPlayer->IsPaused() && pChr->IsAlive()) { - Pos += vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + vec2 Target = vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY); + Pos = pPlayer->m_CameraInfo.ConvertTargetToWorld(pChr->GetPos(), Target); } pSelf->Teleport(pChr, Pos); pChr->ResetJumps(); @@ -917,12 +919,6 @@ void CGameContext::ConReloadCensorlist(IConsole::IResult *pResult, void *pUserDa pSelf->ReadCensorList(); } -void CGameContext::ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData) -{ - CGameContext *pSelf = (CGameContext *)pUserData; - pSelf->Server()->ReadAnnouncementsFile(g_Config.m_SvAnnouncementFileName); -} - void CGameContext::ConDumpAntibot(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 6cf0e3d488a..d393c7159fb 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -1386,7 +1386,19 @@ void CCharacter::HandleSkippableTiles(int Index) Collision()->GetFCollisionAt(m_Pos.x - GetProximityRadius() / 3.f, m_Pos.y + GetProximityRadius() / 3.f) == TILE_DEATH) && !m_Core.m_Super && !m_Core.m_Invincible && !(Team() && Teams()->TeeFinished(m_pPlayer->GetCid()))) { - Die(m_pPlayer->GetCid(), WEAPON_WORLD); + if(Team() && Teams()->IsPractice(Team())) + { + Freeze(); + // Rate limit death effects to once per second + if(Server()->Tick() - m_pPlayer->m_DieTick >= Server()->TickSpeed()) + { + m_pPlayer->m_DieTick = Server()->Tick(); + GameServer()->CreateSound(m_Pos, SOUND_PLAYER_DIE, TeamMask()); + GameServer()->CreateDeath(m_Pos, m_pPlayer->GetCid(), TeamMask()); + } + } + else + Die(m_pPlayer->GetCid(), WEAPON_WORLD); return; } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index b957f82f09e..6c319fbac9f 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -1853,13 +1853,18 @@ bool CGameContext::OnClientDDNetVersionKnown(int ClientId) return false; } +bool CheckClientId2(int ClientId) +{ + return ClientId >= 0 && ClientId < MAX_CLIENTS; +} + void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientId) { if(Server()->IsSixup(ClientId) && *pMsgId < OFFSET_UUID) { void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(*pMsgId, pUnpacker); if(!pRawMsg) - return 0; + return nullptr; CPlayer *pPlayer = m_apPlayers[ClientId]; static char s_aRawMsg[1024]; @@ -1870,18 +1875,21 @@ void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientI // Should probably use a placement new to start the lifetime of the object to avoid future weirdness ::CNetMsg_Cl_Say *pMsg = (::CNetMsg_Cl_Say *)s_aRawMsg; - if(pMsg7->m_Target >= 0) + if(pMsg7->m_Mode == protocol7::CHAT_WHISPER) { + if(!CheckClientId2(pMsg7->m_Target) || !Server()->ClientIngame(pMsg7->m_Target)) + return nullptr; if(ProcessSpamProtection(ClientId)) - return 0; + return nullptr; - // Should we maybe recraft the message so that it can go through the usual path? WhisperId(ClientId, pMsg7->m_Target, pMsg7->m_pMessage); - return 0; + return nullptr; + } + else + { + pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM; + pMsg->m_pMessage = pMsg7->m_pMessage; } - - pMsg->m_Team = pMsg7->m_Mode == protocol7::CHAT_TEAM; - pMsg->m_pMessage = pMsg7->m_pMessage; } else if(*pMsgId == protocol7::NETMSGTYPE_CL_STARTINFO) { @@ -1908,7 +1916,7 @@ void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientI protocol7::CNetMsg_Cl_SkinChange *pMsg = (protocol7::CNetMsg_Cl_SkinChange *)pRawMsg; if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay > Server()->Tick()) - return 0; + return nullptr; pPlayer->m_LastChangeInfo = Server()->Tick(); @@ -1927,7 +1935,7 @@ void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientI Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); - return 0; + return nullptr; } else if(*pMsgId == protocol7::NETMSGTYPE_CL_SETSPECTATORMODE) { @@ -1955,7 +1963,6 @@ void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientI str_format(s_aRawMsg + sizeof(*pMsg), sizeof(s_aRawMsg) - sizeof(*pMsg), "/%s %s", pMsg7->m_pName, pMsg7->m_pArguments); pMsg->m_pMessage = s_aRawMsg + sizeof(*pMsg); - dbg_msg("debug", "line='%s'", s_aRawMsg + sizeof(*pMsg)); pMsg->m_Team = 0; *pMsgId = NETMSGTYPE_CL_SAY; @@ -1973,7 +1980,7 @@ void *CGameContext::PreProcessMsg(int *pMsgId, CUnpacker *pUnpacker, int ClientI Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); Console()->ExecuteLine(s_aRawMsg, ClientId, false); Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); - return 0; + return nullptr; } pMsg->m_pValue = pMsg7->m_pValue; @@ -2068,6 +2075,9 @@ void CGameContext::OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId) case NETMSGTYPE_CL_SHOWDISTANCE: OnShowDistanceNetMessage(static_cast(pRawMsg), ClientId); break; + case NETMSGTYPE_CL_CAMERAINFO: + OnCameraInfoNetMessage(static_cast(pRawMsg), ClientId); + break; case NETMSGTYPE_CL_SETSPECTATORMODE: OnSetSpectatorModeNetMessage(static_cast(pRawMsg), ClientId); break; @@ -2148,25 +2158,25 @@ void CGameContext::OnSayNetMessage(const CNetMsg_Cl_Say *pMsg, int ClientId, con if(str_startswith_nocase(pMsg->m_pMessage + 1, "w ")) { char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); + str_copy(aWhisperMsg, pMsg->m_pMessage + 3); Whisper(pPlayer->GetCid(), aWhisperMsg); } else if(str_startswith_nocase(pMsg->m_pMessage + 1, "whisper ")) { char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 9, 256); + str_copy(aWhisperMsg, pMsg->m_pMessage + 9); Whisper(pPlayer->GetCid(), aWhisperMsg); } else if(str_startswith_nocase(pMsg->m_pMessage + 1, "c ")) { char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); + str_copy(aWhisperMsg, pMsg->m_pMessage + 3); Converse(pPlayer->GetCid(), aWhisperMsg); } else if(str_startswith_nocase(pMsg->m_pMessage + 1, "converse ")) { char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 10, 256); + str_copy(aWhisperMsg, pMsg->m_pMessage + 10); Converse(pPlayer->GetCid(), aWhisperMsg); } else @@ -2578,6 +2588,12 @@ void CGameContext::OnShowDistanceNetMessage(const CNetMsg_Cl_ShowDistance *pMsg, pPlayer->m_ShowDistance = vec2(pMsg->m_X, pMsg->m_Y); } +void CGameContext::OnCameraInfoNetMessage(const CNetMsg_Cl_CameraInfo *pMsg, int ClientId) +{ + CPlayer *pPlayer = m_apPlayers[ClientId]; + pPlayer->m_CameraInfo.Write(pMsg); +} + void CGameContext::OnSetSpectatorModeNetMessage(const CNetMsg_Cl_SetSpectatorMode *pMsg, int ClientId) { if(m_World.m_Paused) @@ -3643,7 +3659,6 @@ void CGameContext::OnConsoleInit() Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team"); Console()->Register("hot_reload", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConHotReload, this, "Reload the map while preserving the state of tees and teams"); Console()->Register("reload_censorlist", "", CFGFLAG_SERVER, ConReloadCensorlist, this, "Reload the censorlist"); - Console()->Register("reload_announcement", "", CFGFLAG_SERVER, ConReloadAnnouncement, this, "Reload the announcements"); Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option"); Console()->Register("remove_vote", "r[name]", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option"); @@ -4560,11 +4575,6 @@ void CGameContext::ResetTuning() SendTuningParams(-1); } -bool CheckClientId2(int ClientId) -{ - return ClientId >= 0 && ClientId < MAX_CLIENTS; -} - void CGameContext::Whisper(int ClientId, char *pStr) { if(ProcessSpamProtection(ClientId)) @@ -4572,7 +4582,7 @@ void CGameContext::Whisper(int ClientId, char *pStr) pStr = str_skip_whitespaces(pStr); - char *pName; + const char *pName; int Victim; bool Error = false; @@ -4609,14 +4619,16 @@ void CGameContext::Whisper(int ClientId, char *pStr) if(!Error) { - // write null termination - *pDst = 0; - + *pDst = '\0'; pStr++; for(Victim = 0; Victim < MAX_CLIENTS; Victim++) - if(str_comp(pName, Server()->ClientName(Victim)) == 0) + { + if(Server()->ClientIngame(Victim) && str_comp(pName, Server()->ClientName(Victim)) == 0) + { break; + } + } } } else @@ -4624,20 +4636,23 @@ void CGameContext::Whisper(int ClientId, char *pStr) pName = pStr; while(true) { - if(pStr[0] == 0) + if(pStr[0] == '\0') { Error = true; break; } if(pStr[0] == ' ') { - pStr[0] = 0; + pStr[0] = '\0'; for(Victim = 0; Victim < MAX_CLIENTS; Victim++) - if(str_comp(pName, Server()->ClientName(Victim)) == 0) + { + if(Server()->ClientIngame(Victim) && str_comp(pName, Server()->ClientName(Victim)) == 0) + { break; + } + } pStr[0] = ' '; - if(Victim < MAX_CLIENTS) break; } @@ -4650,7 +4665,7 @@ void CGameContext::Whisper(int ClientId, char *pStr) Error = true; } - *pStr = 0; + *pStr = '\0'; pStr++; if(Error) @@ -4659,7 +4674,7 @@ void CGameContext::Whisper(int ClientId, char *pStr) return; } - if(Victim >= MAX_CLIENTS || !CheckClientId2(Victim)) + if(!CheckClientId2(Victim)) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "No player with name \"%s\" found", pName); @@ -4672,14 +4687,10 @@ void CGameContext::Whisper(int ClientId, char *pStr) void CGameContext::WhisperId(int ClientId, int VictimId, const char *pMessage) { - if(!CheckClientId2(ClientId)) - return; - - if(!CheckClientId2(VictimId)) - return; + dbg_assert(CheckClientId2(ClientId) && m_apPlayers[ClientId] != nullptr, "ClientId invalid"); + dbg_assert(CheckClientId2(VictimId) && m_apPlayers[VictimId] != nullptr, "VictimId invalid"); - if(m_apPlayers[ClientId]) - m_apPlayers[ClientId]->m_LastWhisperTo = VictimId; + m_apPlayers[ClientId]->m_LastWhisperTo = VictimId; char aCensoredMessage[256]; CensorMessage(aCensoredMessage, pMessage, sizeof(aCensoredMessage)); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 85055d7d38e..c5e6b19f83b 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -309,6 +309,7 @@ class CGameContext : public IGameServer void OnShowOthersLegacyNetMessage(const CNetMsg_Cl_ShowOthersLegacy *pMsg, int ClientId); void OnShowOthersNetMessage(const CNetMsg_Cl_ShowOthers *pMsg, int ClientId); void OnShowDistanceNetMessage(const CNetMsg_Cl_ShowDistance *pMsg, int ClientId); + void OnCameraInfoNetMessage(const CNetMsg_Cl_CameraInfo *pMsg, int ClientId); void OnSetSpectatorModeNetMessage(const CNetMsg_Cl_SetSpectatorMode *pMsg, int ClientId); void OnChangeInfoNetMessage(const CNetMsg_Cl_ChangeInfo *pMsg, int ClientId); void OnEmoticonNetMessage(const CNetMsg_Cl_Emoticon *pMsg, int ClientId); @@ -528,7 +529,6 @@ class CGameContext : public IGameServer static void ConUnFreezeHammer(IConsole::IResult *pResult, void *pUserData); static void ConReloadCensorlist(IConsole::IResult *pResult, void *pUserData); - static void ConReloadAnnouncement(IConsole::IResult *pResult, void *pUserData); CCharacter *GetPracticeCharacter(IConsole::IResult *pResult); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 12767386b48..9f280760796 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -146,6 +146,8 @@ void CPlayer::Reset() m_SwapTargetsClientId = -1; m_BirthdayAnnounced = false; m_RescueMode = RESCUEMODE_AUTO; + + m_CameraInfo.Reset(); } static int PlayerFlags_SixToSeven(int Flags) @@ -511,7 +513,7 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *pNewInput) m_NumInputs++; - if(m_pCharacter && !m_Paused) + if(m_pCharacter && !m_Paused && !(pNewInput->m_PlayerFlags & PLAYERFLAG_SPEC_CAM)) m_pCharacter->OnPredictedInput(pNewInput); // Magic number when we can hope that client has successfully identified itself @@ -527,7 +529,7 @@ void CPlayer::OnDirectInput(CNetObj_PlayerInput *pNewInput) AfkTimer(); - if(((!m_pCharacter && m_Team == TEAM_SPECTATORS) || m_Paused) && m_SpectatorId == SPEC_FREEVIEW) + if(((pNewInput->m_PlayerFlags & PLAYERFLAG_SPEC_CAM) || GetClientVersion() < VERSION_DDNET_PLAYERFLAG_SPEC_CAM) && ((!m_pCharacter && m_Team == TEAM_SPECTATORS) || m_Paused) && m_SpectatorId == SPEC_FREEVIEW) m_ViewPos = vec2(pNewInput->m_TargetX, pNewInput->m_TargetY); // check for activity @@ -553,7 +555,7 @@ void CPlayer::OnPredictedEarlyInput(CNetObj_PlayerInput *pNewInput) if(m_PlayerFlags & PLAYERFLAG_CHATTING) return; - if(m_pCharacter && !m_Paused) + if(m_pCharacter && !m_Paused && !(m_PlayerFlags & PLAYERFLAG_SPEC_CAM)) m_pCharacter->OnDirectInput(pNewInput); } @@ -954,3 +956,31 @@ void CPlayer::ProcessScoreResult(CScorePlayerResult &Result) } } } + +vec2 CPlayer::CCameraInfo::ConvertTargetToWorld(vec2 Position, vec2 Target) const +{ + vec2 TargetCameraOffset(0, 0); + float l = length(Target); + + if(l > 0.0001f) // make sure that this isn't 0 + { + float OffsetAmount = maximum(l - m_Deadzone, 0.0f) * (m_FollowFactor / 100.0f); + TargetCameraOffset = normalize_pre_length(Target, l) * OffsetAmount; + } + + return Position + (Target - TargetCameraOffset) * m_Zoom + TargetCameraOffset; +} + +void CPlayer::CCameraInfo::Write(const CNetMsg_Cl_CameraInfo *Msg) +{ + m_Zoom = Msg->m_Zoom / 1000.0f; + m_Deadzone = Msg->m_Deadzone; + m_FollowFactor = Msg->m_FollowFactor; +} + +void CPlayer::CCameraInfo::Reset() +{ + m_Zoom = 1.0f; + m_Deadzone = 0.0f; + m_FollowFactor = 0.0f; +} diff --git a/src/game/server/player.h b/src/game/server/player.h index c281ac5b144..5703a0a765c 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -191,6 +191,19 @@ class CPlayer bool m_SpecTeam; bool m_NinjaJetpack; + // camera info is used sparingly for converting aim target to absolute world coordinates + class CCameraInfo + { + float m_Zoom; + int m_Deadzone; + int m_FollowFactor; + + public: + vec2 ConvertTargetToWorld(vec2 Position, vec2 Target) const; + void Write(const CNetMsg_Cl_CameraInfo *pMsg); + void Reset(); + } m_CameraInfo; + int m_ChatScore; bool m_Moderating; diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index 16c43b29086..c2e67c5f0e7 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -1,12 +1,26 @@ #include "teehistorian.h" #include -#include + #include #include +#include #include + #include +class CTeehistorianPacker : public CAbstractPacker +{ +public: + CTeehistorianPacker() : + CAbstractPacker(m_aBuffer, sizeof(m_aBuffer)) + { + } + +private: + unsigned char m_aBuffer[1024 * 64]; +}; + static const char TEEHISTORIAN_NAME[] = "teehistorian@ddnet.tw"; static const CUuid TEEHISTORIAN_UUID = CalculateUuid(TEEHISTORIAN_NAME); static const char TEEHISTORIAN_VERSION[] = "2"; @@ -215,7 +229,7 @@ void CTeeHistorian::WriteExtra(CUuid Uuid, const void *pData, int DataSize) { EnsureTickWritten(); - CPacker Ex; + CTeehistorianPacker Ex; Ex.Reset(); Ex.AddInt(-TEEHISTORIAN_EX); Ex.AddRaw(&Uuid, sizeof(Uuid)); @@ -277,7 +291,7 @@ void CTeeHistorian::RecordPlayer(int ClientId, const CNetObj_CharacterCore *pCha { EnsureTickWrittenPlayerData(ClientId); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); if(pPrev->m_Alive) { @@ -320,7 +334,7 @@ void CTeeHistorian::RecordDeadPlayer(int ClientId) { EnsureTickWrittenPlayerData(ClientId); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_PLAYER_OLD); Buffer.AddInt(ClientId); @@ -341,7 +355,7 @@ void CTeeHistorian::RecordPlayerTeam(int ClientId, int Team) EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddInt(Team); @@ -363,7 +377,7 @@ void CTeeHistorian::RecordTeamPractice(int Team, bool Practice) EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(Team); Buffer.AddInt(Practice); @@ -392,7 +406,7 @@ void CTeeHistorian::EnsureTickWritten() void CTeeHistorian::WriteTick() { - CPacker TickPacker; + CTeehistorianPacker TickPacker; TickPacker.Reset(); int dt = m_Tick - m_LastWrittenTick - 1; @@ -424,7 +438,7 @@ void CTeeHistorian::BeginInputs() void CTeeHistorian::RecordPlayerInput(int ClientId, uint32_t UniqueClientId, const CNetObj_PlayerInput *pInput) { - CPacker Buffer; + CTeehistorianPacker Buffer; CTeehistorianPlayer *pPrev = &m_aPrevPlayers[ClientId]; CNetObj_PlayerInput DiffInput; @@ -473,7 +487,7 @@ void CTeeHistorian::RecordPlayerMessage(int ClientId, const void *pMsg, int MsgS { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_MESSAGE); Buffer.AddInt(ClientId); @@ -499,7 +513,7 @@ void CTeeHistorian::RecordPlayerJoin(int ClientId, int Protocol) EnsureTickWritten(); { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); if(m_Debug) @@ -510,7 +524,7 @@ void CTeeHistorian::RecordPlayerJoin(int ClientId, int Protocol) WriteExtra(Uuid, Buffer.Data(), Buffer.Size()); } - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_JOIN); Buffer.AddInt(ClientId); @@ -527,7 +541,7 @@ void CTeeHistorian::RecordPlayerRejoin(int ClientId) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); @@ -543,7 +557,7 @@ void CTeeHistorian::RecordPlayerReady(int ClientId) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); @@ -559,7 +573,7 @@ void CTeeHistorian::RecordPlayerDrop(int ClientId, const char *pReason) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_DROP); Buffer.AddInt(ClientId); @@ -577,7 +591,7 @@ void CTeeHistorian::RecordPlayerName(int ClientId, const char *pName) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddString(pName, 0); @@ -594,7 +608,7 @@ void CTeeHistorian::RecordConsoleCommand(int ClientId, int FlagMask, const char { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_CONSOLE_COMMAND); Buffer.AddInt(ClientId); @@ -628,7 +642,7 @@ void CTeeHistorian::RecordPlayerSwap(int ClientId1, int ClientId2) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId1); Buffer.AddInt(ClientId2); @@ -640,7 +654,7 @@ void CTeeHistorian::RecordTeamSaveSuccess(int Team, CUuid SaveId, const char *pT { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(Team); Buffer.AddRaw(&SaveId, sizeof(SaveId)); @@ -660,7 +674,7 @@ void CTeeHistorian::RecordTeamSaveFailure(int Team) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(Team); @@ -676,7 +690,7 @@ void CTeeHistorian::RecordTeamLoadSuccess(int Team, CUuid SaveId, const char *pT { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(Team); Buffer.AddRaw(&SaveId, sizeof(SaveId)); @@ -696,7 +710,7 @@ void CTeeHistorian::RecordTeamLoadFailure(int Team) { EnsureTickWritten(); - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(Team); @@ -723,7 +737,7 @@ void CTeeHistorian::EndTick() void CTeeHistorian::RecordDDNetVersionOld(int ClientId, int DDNetVersion) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddInt(DDNetVersion); @@ -738,7 +752,7 @@ void CTeeHistorian::RecordDDNetVersionOld(int ClientId, int DDNetVersion) void CTeeHistorian::RecordDDNetVersion(int ClientId, CUuid ConnectionId, int DDNetVersion, const char *pDDNetVersionStr) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddRaw(&ConnectionId, sizeof(ConnectionId)); @@ -757,7 +771,7 @@ void CTeeHistorian::RecordDDNetVersion(int ClientId, CUuid ConnectionId, int DDN void CTeeHistorian::RecordAuthInitial(int ClientId, int Level, const char *pAuthName) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddInt(Level); @@ -773,7 +787,7 @@ void CTeeHistorian::RecordAuthInitial(int ClientId, int Level, const char *pAuth void CTeeHistorian::RecordAuthLogin(int ClientId, int Level, const char *pAuthName) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddInt(Level); @@ -789,7 +803,7 @@ void CTeeHistorian::RecordAuthLogin(int ClientId, int Level, const char *pAuthNa void CTeeHistorian::RecordAuthLogout(int ClientId) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); @@ -813,7 +827,7 @@ void CTeeHistorian::RecordAntibot(const void *pData, int DataSize) void CTeeHistorian::RecordPlayerFinish(int ClientId, int TimeTicks) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(ClientId); Buffer.AddInt(TimeTicks); @@ -827,7 +841,7 @@ void CTeeHistorian::RecordPlayerFinish(int ClientId, int TimeTicks) void CTeeHistorian::RecordTeamFinish(int TeamId, int TimeTicks) { - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(TeamId); Buffer.AddInt(TimeTicks); @@ -852,7 +866,7 @@ void CTeeHistorian::Finish() EndTick(); } - CPacker Buffer; + CTeehistorianPacker Buffer; Buffer.Reset(); Buffer.AddInt(-TEEHISTORIAN_FINISH); diff --git a/src/game/version.h b/src/game/version.h index c3812806ce0..e17786c9213 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -3,7 +3,7 @@ #ifndef GAME_VERSION_H #define GAME_VERSION_H #ifndef GAME_RELEASE_VERSION -#define GAME_RELEASE_VERSION "18.8" +#define GAME_RELEASE_VERSION "18.9" #endif // teeworlds @@ -13,7 +13,7 @@ #define GAME_NETVERSION7 "0.7 802f1be60a05665f" // ddnet -#define DDNET_VERSION_NUMBER 18080 +#define DDNET_VERSION_NUMBER 18090 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" #define CLIENT_NAME "TClient" diff --git a/src/mastersrv/src/main.rs b/src/mastersrv/src/main.rs index c53fae69ff9..d19f48c8488 100644 --- a/src/mastersrv/src/main.rs +++ b/src/mastersrv/src/main.rs @@ -1039,7 +1039,7 @@ async fn main() { .and(warp::post()) .and(warp::header::headers_cloned()) .and(warp::addr::remote()) - .and(warp::body::content_length_limit(16 * 1024)) // limit body size to 16 KiB + .and(warp::body::content_length_limit(32 * 1024)) // limit body size to 32 KiB .and(warp::body::bytes()) .map( move |headers: warp::http::HeaderMap, addr: Option, info: bytes::Bytes| { diff --git a/src/test/packer.cpp b/src/test/packer.cpp index 6b4e133698f..90a3a7cad15 100644 --- a/src/test/packer.cpp +++ b/src/test/packer.cpp @@ -5,24 +5,24 @@ #include // pExpected is NULL if an error is expected -static void ExpectAddString5(const char *pString, int Limit, const char *pExpected) +static void ExpectAddString5(const char *pString, int Limit, bool AllowTruncation, const char *pExpected) { static char ZEROS[CPacker::PACKER_BUFFER_SIZE] = {0}; static const int OFFSET = CPacker::PACKER_BUFFER_SIZE - 5; CPacker Packer; Packer.Reset(); Packer.AddRaw(ZEROS, OFFSET); - Packer.AddString(pString, Limit); + Packer.AddString(pString, Limit, AllowTruncation); - EXPECT_EQ(pExpected == 0, Packer.Error()); + EXPECT_EQ(pExpected == 0, Packer.Error()) << "for String='" << pString << "', Limit='" << Limit << "', AllowTruncation='" << AllowTruncation << "'"; if(pExpected) { // Include null termination. int ExpectedLength = str_length(pExpected) + 1; - EXPECT_EQ(ExpectedLength, Packer.Size() - OFFSET); + EXPECT_EQ(ExpectedLength, Packer.Size() - OFFSET) << "for String='" << pString << "', Limit='" << Limit << "', AllowTruncation='" << AllowTruncation << "'"; if(ExpectedLength == Packer.Size() - OFFSET) { - EXPECT_STREQ(pExpected, (const char *)Packer.Data() + OFFSET); + EXPECT_STREQ(pExpected, (const char *)Packer.Data() + OFFSET) << "for String='" << pString << "', Limit='" << Limit << "', AllowTruncation='" << AllowTruncation << "'"; } } } @@ -207,92 +207,121 @@ TEST(Packer, AddExtendedInt) TEST(Packer, AddString) { - ExpectAddString5("", 0, ""); - ExpectAddString5("a", 0, "a"); - ExpectAddString5("abcd", 0, "abcd"); - ExpectAddString5("abcde", 0, 0); + ExpectAddString5("", 0, true, ""); + ExpectAddString5("a", 0, true, "a"); + ExpectAddString5("abcd", 0, true, "abcd"); + ExpectAddString5("abcde", 0, true, nullptr); } TEST(Packer, AddStringLimit) { - ExpectAddString5("", 1, ""); - ExpectAddString5("a", 1, "a"); - ExpectAddString5("aa", 1, "a"); - ExpectAddString5("ä", 1, ""); + ExpectAddString5("", 1, true, ""); + ExpectAddString5("a", 1, true, "a"); + ExpectAddString5("aa", 1, true, "a"); + ExpectAddString5("ä", 1, true, ""); - ExpectAddString5("", 10, ""); - ExpectAddString5("a", 10, "a"); - ExpectAddString5("abcd", 10, "abcd"); - ExpectAddString5("abcde", 10, 0); + ExpectAddString5("", 10, true, ""); + ExpectAddString5("a", 10, true, "a"); + ExpectAddString5("abcd", 10, true, "abcd"); + ExpectAddString5("abcde", 10, true, nullptr); - ExpectAddString5("äöü", 5, "äö"); - ExpectAddString5("äöü", 6, 0); + ExpectAddString5("äöü", 4, true, "äö"); + ExpectAddString5("äöü", 5, true, "äö"); + ExpectAddString5("äöü", 6, true, nullptr); + + ExpectAddString5("", 1, false, ""); + ExpectAddString5("a", 1, false, "a"); + ExpectAddString5("aa", 1, false, nullptr); + ExpectAddString5("ä", 1, false, nullptr); + + ExpectAddString5("", 10, false, ""); + ExpectAddString5("a", 10, false, "a"); + ExpectAddString5("abcd", 10, false, "abcd"); + ExpectAddString5("abcde", 10, false, nullptr); + + ExpectAddString5("äöü", 4, false, nullptr); + ExpectAddString5("äöü", 5, false, nullptr); + ExpectAddString5("äöü", 6, false, nullptr); } TEST(Packer, AddStringBroken) { - ExpectAddString5("\x80", 0, "�"); - ExpectAddString5("\x80\x80", 0, 0); - ExpectAddString5("a\x80", 0, "a�"); + ExpectAddString5("\x80", 0, true, "�"); + ExpectAddString5("\x80\x80", 0, true, nullptr); + ExpectAddString5("a\x80", 0, true, "a�"); ExpectAddString5("\x80" "a", - 0, "�a"); - ExpectAddString5("\x80", 1, ""); - ExpectAddString5("\x80\x80", 3, "�"); - ExpectAddString5("\x80\x80", 5, "�"); - ExpectAddString5("\x80\x80", 6, 0); + 0, true, "�a"); + + ExpectAddString5("\x80", 1, true, ""); + ExpectAddString5("\x80", 3, true, "�"); + ExpectAddString5("\x80\x80", 3, true, "�"); + ExpectAddString5("\x80\x80", 5, true, "�"); + ExpectAddString5("\x80\x80", 6, true, nullptr); + + ExpectAddString5("\x80", 1, false, nullptr); + ExpectAddString5("\x80", 3, false, "�"); + ExpectAddString5("\x80\x80", 3, false, nullptr); + ExpectAddString5("\x80\x80", 5, false, nullptr); + ExpectAddString5("\x80\x80", 6, false, nullptr); } -TEST(Packer, Error) +TEST(Packer, Error1) { char aData[CPacker::PACKER_BUFFER_SIZE]; mem_zero(aData, sizeof(aData)); - { - CPacker Packer; - Packer.Reset(); - EXPECT_EQ(Packer.Error(), false); - Packer.AddRaw(aData, sizeof(aData) - 1); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData) - 1); - Packer.AddInt(1); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData)); - Packer.AddInt(2); - EXPECT_EQ(Packer.Error(), true); - Packer.AddInt(3); - EXPECT_EQ(Packer.Error(), true); - } + CPacker Packer; + Packer.Reset(); + EXPECT_EQ(Packer.Error(), false); + Packer.AddRaw(aData, sizeof(aData) - 1); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData) - 1); + Packer.AddInt(1); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData)); + Packer.AddInt(2); + EXPECT_EQ(Packer.Error(), true); + Packer.AddInt(3); + EXPECT_EQ(Packer.Error(), true); +} - { - CPacker Packer; - Packer.Reset(); - EXPECT_EQ(Packer.Error(), false); - Packer.AddRaw(aData, sizeof(aData) - 1); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData) - 1); - Packer.AddRaw(aData, 1); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData)); - Packer.AddRaw(aData, 1); - EXPECT_EQ(Packer.Error(), true); - Packer.AddRaw(aData, 1); - EXPECT_EQ(Packer.Error(), true); - } +TEST(Packer, Error2) +{ + char aData[CPacker::PACKER_BUFFER_SIZE]; + mem_zero(aData, sizeof(aData)); - { - CPacker Packer; - Packer.Reset(); - EXPECT_EQ(Packer.Error(), false); - Packer.AddRaw(aData, sizeof(aData) - 5); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData) - 5); - Packer.AddString("test"); - EXPECT_EQ(Packer.Error(), false); - EXPECT_EQ(Packer.Size(), sizeof(aData)); - Packer.AddString("test"); - EXPECT_EQ(Packer.Error(), true); - Packer.AddString("test"); - EXPECT_EQ(Packer.Error(), true); - } + CPacker Packer; + Packer.Reset(); + EXPECT_EQ(Packer.Error(), false); + Packer.AddRaw(aData, sizeof(aData) - 1); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData) - 1); + Packer.AddRaw(aData, 1); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData)); + Packer.AddRaw(aData, 1); + EXPECT_EQ(Packer.Error(), true); + Packer.AddRaw(aData, 1); + EXPECT_EQ(Packer.Error(), true); +} + +TEST(Packer, Error3) +{ + char aData[CPacker::PACKER_BUFFER_SIZE]; + mem_zero(aData, sizeof(aData)); + + CPacker Packer; + Packer.Reset(); + EXPECT_EQ(Packer.Error(), false); + Packer.AddRaw(aData, sizeof(aData) - 5); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData) - 5); + Packer.AddString("test"); + EXPECT_EQ(Packer.Error(), false); + EXPECT_EQ(Packer.Size(), sizeof(aData)); + Packer.AddString("test"); + EXPECT_EQ(Packer.Error(), true); + Packer.AddString("test"); + EXPECT_EQ(Packer.Error(), true); }