diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af6c7fc513..b8ed7afef76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1404,6 +1404,7 @@ set(EXPECTED_DATA countryflags/XCA.png countryflags/XEN.png countryflags/XEU.png + countryflags/XGL.png countryflags/XNI.png countryflags/XSC.png countryflags/XWA.png @@ -1480,6 +1481,7 @@ set(EXPECTED_DATA languages/esperanto.txt languages/finnish.txt languages/french.txt + languages/galician.txt languages/german.txt languages/greek.txt languages/hungarian.txt @@ -1889,6 +1891,7 @@ set_src(BASE GLOB_RECURSE src/base chillerbot/curses_colors.h chillerbot/dbg_print.h chillerbot/terminalui_logger.h + color.cpp color.h curses.h detect.h diff --git a/data/countryflags/XGL.png b/data/countryflags/XGL.png new file mode 100644 index 00000000000..5860a2c759d Binary files /dev/null and b/data/countryflags/XGL.png differ diff --git a/data/countryflags/index.txt b/data/countryflags/index.txt index f4b6df3442d..8af04d58e0a 100644 --- a/data/countryflags/index.txt +++ b/data/countryflags/index.txt @@ -24,6 +24,9 @@ XEU XCA == 906 +XGL +== 907 + #south sudan, non official code# SS == 737 diff --git a/data/languages/arabic.txt b/data/languages/arabic.txt index 476a3a06b41..f8a62b093c9 100644 --- a/data/languages/arabic.txt +++ b/data/languages/arabic.txt @@ -71,12 +71,6 @@ Clan Client == ﺖﻨﻳﻼﻛ -Close -== ﻕﻼﻏﺍ - -Connect -== ﻝﺎﺼﺗﺍ - Connecting to == ﻰﻟﺍ ﻝﺎﺼﺗﺍ @@ -747,9 +741,6 @@ Switch weapon when out of ammo Grabs == ﺕﺎﻜﺴﻣ -Select a name -== ﻢﺳﺍ ﺭﺎﻴﺘﺧﺍ - Automatically take game over screenshot == ﺔﺷﺎﺸﻟﺍ ﺔﻄﻘﻟ ﻰﻠﻋ ﺍًﻴﺋﺎﻘﻠﺗ ﺔﺒﻌﻠﻟﺍ ﺬﺧﺃ @@ -762,9 +753,6 @@ Gameplay Laser == ﺭﺰﻴﻟ -Old mouse mode -== ﻢﻳﺪﻘﻟﺍ ﺱﻭﺎﻤﻟﺍ ﻊﺿﻭ - Follow == ﻊﺒﺗﺃ @@ -963,8 +951,8 @@ Replace video 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 ، ﻦﻴﺋﺪﺘﺒﻤﻟﺍ ﺕﺍﺮﻓﺮﻴﺳ ﻰﻠﻋ ﺃﺪﺒﺗ ﻥﺃ ﺐﺠﻳ ، ﺍًﺪﻳﺪﺟ ﺍًﻣﺩﺎﻗ ﻚﺘﻔﺼﺑ .ﺏﺎﻤﻟﺍ ﻰﻠﻋ ﺔﻳﺎﻬﻨﻟﺍ ﻂﺧ ﻰﻟﺇ ﺕﻼﻤﺤﻤﻟﺍ ﻦﻣ ﻚﺘﻋﻮﻤﺠﻣﻭ ﺖﻧﺃ ﻚﻟﻮﺻﻭ ﻮﻫ ﻑﺪﻬﻟﺍ ﻥﻮﻜﻳ ﺚﻴﺣ ﺖﻧﺮﺘﻧﻹﺍ ﺮﺒﻋ ﺔﻴﻧﻭﺎﻌﺗ ﺔﺒﻌﻟ ﻲﻫ -Default length: %d -== ﻲﺿﺍﺮﺘﻓﻻﺍ ﻝﻮﻄﻟﺍ: %d +Default length +== ﻲﺿﺍﺮﺘﻓﻻﺍ ﻝﻮﻄﻟﺍ Pause == ﺔﺒﻗﺍﺮﻣ @@ -1257,7 +1245,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1296,9 +1284,6 @@ CHN Getting server list from master server == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1308,6 +1293,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1360,10 +1348,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1375,9 +1363,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1438,10 +1438,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1452,9 +1452,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1467,7 +1464,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1476,9 +1473,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1488,15 +1482,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1524,7 +1518,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt index 00db6654a3f..b707d747e64 100644 --- a/data/languages/belarusian.txt +++ b/data/languages/belarusian.txt @@ -3,6 +3,7 @@ # arionwt1997 #modified by: # Chill & PoKeMoN 2023-03-31 16:00:00 +# Chill & PoKeMoN 2023-07-02 00:54:00 # ##### /authors ##### @@ -77,12 +78,6 @@ Clan Client == Кліент -Close -== Выйсці - -Connect -== Падлучыцца - Connecting to == Падлучэнне да @@ -827,9 +822,6 @@ Exclude %d player == %d гулец -Refreshing... -== Абнаўленне... - Are you sure that you want to disconnect and switch to a different server? == Вы ўпэўненыя, што хочаце адлучыцца і пераключыцца на іншы сервер? @@ -860,9 +852,6 @@ Are you sure that you want to remove the player '%s' from your friends list? Are you sure that you want to remove the clan '%s' from your friends list? == Вы ўпэўненыя што хочаце выдаліць клан '%s' са спіса вашых сяброў? -Select a name -== Выберыце імя - Please use a different name == Калі ласка, выкарыстайце іншае імя @@ -893,12 +882,6 @@ Slow down the demo Speed up the demo == Паскорыць дэма -Mark the beginning of a cut -== Пазначыць пачатак адрэзка - -Mark the end of a cut -== Пазначыць канец адрэзка - Export cut as a separate demo == Экспартаваць адрэзак як асобнае дэма @@ -1130,13 +1113,6 @@ Chat command Enable controller == Уключыць кантролер -Controller %d: %s -== Кантролер %d: %s - -Click to cycle through all available controllers. -== Націсніце каб праглядзець усе дасяжныя кантролеры. - -[Ingame controller mode] Relative == Адносны @@ -1159,18 +1135,12 @@ Controller jitter tolerance No controller found. Plug in a controller. == Кантролер не знойдзены. Падключыце кантролер. -Device -== Прылада - Status == Статус Aim bind == Вось прыцэла -Controller Axis #%d -== Вось кантролера #%d - Mouse == Мыш @@ -1225,9 +1195,6 @@ default custom == карыстацкі -Graphics cards -== Відэакарты - auto == аўта @@ -1453,8 +1420,8 @@ Save the best demo of each race Enable replays == Уключыць запісы -Default length: %d -== Станд. працягласць: %d +Default length +== Станд. працягласць When you cross the start line, show a ghost tee replicating the movements of your best time == Пры перасячэнні стартавай лініі, паказваць прывід Tee, які паўтарае ваш лепшы час @@ -1507,9 +1474,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == АнтыПінг: прадказывать траекторыі гранат -Old mouse mode -== Рэжым старой мышы - Background == Фон @@ -1680,35 +1644,65 @@ Grabs == Зах. 1 new mention -== 1 новая згадка +== 1 згадка %d new mentions -== %d новых згадак +== %d згадак 9+ new mentions -== 9+ новых згадак +== 9+ згадак + +A demo with this name already exists +== Дэма з такой назвай ужо існуе + +No server selected +== Сервер не абраны Online players (%d) -== +== Гульцоў анлайн (%d) Online clanmates (%d) -== +== Членаў клана анлайн (%d) [friends (server browser)] Offline (%d) -== +== Афлайн (%d) Click to select server. Double click to join your friend. -== +== Націсніце, как абраць сервер. Націсніце двойчы, каб далучыцца да вашага сябра. Click to remove this player from your friends list. -== +== Націсніце, как выдаліць гэтага гульца са свайго спіса сяброў. Click to remove this clan from your friends list. -== +== Націсніце, как выдаліць гэты клан са свайго спіса сяброў. None -== +== Пуста Add Clan -== +== Дадаць клан + +Mark the beginning of a cut (right click to reset) +== Пазначыць пачатак адрэзка (правая кнопка мышы для скіду) + +Mark the end of a cut (right click to reset) +== Пазначыць канец адрэзка (правая кнопка мышы для скіду) + +Close the demo player +== Закрыць прайгравальнік дэма + +Export demo cut +== Экспартаваць адрэзак дэма + +Cut interval +== Інтэрвал адрэзка + +Cut length +== Працягласць адрэзка + +Axis +== Вось + +Graphics card +== Відэакарта diff --git a/data/languages/bosnian.txt b/data/languages/bosnian.txt index 3dea4115438..b5320cb44dc 100644 --- a/data/languages/bosnian.txt +++ b/data/languages/bosnian.txt @@ -79,12 +79,6 @@ Clan Client == Klijent -Close -== Zatvori - -Connect -== Konektuj - Connecting to == Konektovanje na @@ -700,9 +694,6 @@ Update now Restart == Restart -Select a name -== Izaberite ime - Please use a different name == Molimo izaberite drugo ime @@ -913,8 +904,8 @@ Client message Save the best demo of each race == Sačuvaj najbolji demo-snimak svake trke -Default length: %d -== Zadana dužina: %d +Default length +== Zadana dužina Enable replays == Omogući replay @@ -955,9 +946,6 @@ Show other players' hook collision lines Show other players' key presses == Prikaži tipke koje ostali igrači pritiskaju -Old mouse mode -== Stari način miša - Show tiles layers from BG map == Prikaži slojeve od pozadinske mape @@ -1129,7 +1117,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1186,9 +1174,6 @@ Getting server list from master server %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1198,6 +1183,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1250,10 +1238,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1265,9 +1253,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1358,10 +1358,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1372,9 +1372,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1387,7 +1384,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1396,9 +1393,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1408,15 +1402,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1444,7 +1438,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/brazilian_portuguese.txt b/data/languages/brazilian_portuguese.txt index f630b370bca..cbfb96c983c 100644 --- a/data/languages/brazilian_portuguese.txt +++ b/data/languages/brazilian_portuguese.txt @@ -26,6 +26,7 @@ # Rafael Fontenelle 2022-10-25 11:22:00 # Rafael Fontenelle 2023-01-09 10:33:00 # Rafael Fontenelle 2023-03-26 19:11:00 +# Rafael Fontenelle 2023-07-07 12:11:00 ##### /authors ##### ##### translated strings ##### @@ -99,12 +100,6 @@ Clan Client == Cliente -Close -== Fechar - -Connect -== Conectar - Connecting to == Conectando a @@ -601,9 +596,6 @@ HUD Show names in chat in team colors == Mostrar nomes no chat com cores do time -Select a name -== Selecione um nome - Netversion: == Netversion: @@ -649,9 +641,6 @@ Gameplay Restart == Reiniciar -Old mouse mode -== Modo antigo de mouse - Round == Turno @@ -1000,8 +989,8 @@ Converse Successfully saved the replay! == A reprodução foi salva com sucesso -Default length: %d -== Duração padrão: %d +Default length +== Duração padrão Replay feature is disabled! == O Recurso de reprodução está desabilitado! @@ -1247,9 +1236,6 @@ https://ddnet.org/discord Are you sure that you want to disconnect and switch to a different server? == Tem certeza que deseja desconectar e trocar para um servidor diferente? -Refreshing... -== Atualizando... - Kill Messages == Mensagens de kill @@ -1283,9 +1269,6 @@ default custom == personalizado -Graphics cards -== Placas de vídeo - auto == auto @@ -1358,13 +1341,6 @@ Download community skins Enable controller == Habilitar controle -Controller %d: %s -== Controle %d: %s - -Click to cycle through all available controllers. -== Clique para trocar entre todos os controles disponíveis. - -[Ingame controller mode] Relative == Relativo @@ -1384,18 +1360,12 @@ UI controller sens. Controller jitter tolerance == Tolerância jitter do controle -Device -== Dispositivo - Status == Status Aim bind == Associação de mira -Controller Axis #%d -== Eixo #%d do controle - Mouse == Mouse @@ -1555,12 +1525,6 @@ Slow down the demo Speed up the demo == Acelerar a demo -Mark the beginning of a cut -== Marcar o início de um corte - -Mark the end of a cut -== Marcar o fim de um corte - Export cut as a separate demo == Exportar corte como uma demo separada @@ -1699,7 +1663,7 @@ 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. -== Não foi possível inicializar o backend de vídeo fornecido, isso provavelmente porque você não instalou o driver da placa de vídeo +== Não foi possível inicializar o backend de vídeo fornecido, isso provavelmente porque você não instalou o driver da placa de vídeo. Could not save downloaded map. Try manually deleting this file: %s == Não foi possível salvar o mapa baixado. Tente excluir manualmente este arquivo: %s @@ -1710,27 +1674,57 @@ Copy info Create a random skin == Criar uma skin aleatória +A demo with this name already exists +== Uma demo com este nome já existe + +No server selected +== Nenhum serviço selecionado + Online players (%d) -== +== Jogadores online (%d) Online clanmates (%d) -== +== Colegas de clã online (%d) [friends (server browser)] Offline (%d) -== +== Offline (%d) Click to select server. Double click to join your friend. -== +== Clique para selecionar o servidor. Clique duplo para se juntar a seus amigos. Click to remove this player from your friends list. -== +== Clique para remover este jogador de sua lista de amigos. 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ã + +Mark the beginning of a cut (right click to reset) +== Marcar o início de um corte (clique direito para redefinir) + +Mark the end of a cut (right click to reset) +== Marcar o fim de um corte (clique direito para redefinir) + +Close the demo player +== Fechar o reprodutor de demo + +Export demo cut +== Exportar corte de demo + +Cut interval +== Intervalo de corte + +Cut length +== Comprimento do corte + +Axis +== Eixo + +Graphics card +== Placa de vídeo diff --git a/data/languages/bulgarian.txt b/data/languages/bulgarian.txt index 862fb530450..a322b5e2c9b 100644 --- a/data/languages/bulgarian.txt +++ b/data/languages/bulgarian.txt @@ -76,12 +76,6 @@ Clan Client == Клиент -Close -== Затвори - -Connect -== Впиши - Connecting to == Свързвам се с @@ -753,7 +747,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -828,9 +822,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -855,6 +846,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -886,18 +880,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -919,10 +901,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -934,9 +916,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1159,10 +1162,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1173,9 +1176,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1188,7 +1188,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1197,9 +1197,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1209,15 +1206,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1233,10 +1230,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1254,7 +1251,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1482,7 +1479,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1500,10 +1497,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1536,9 +1533,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/catalan.txt b/data/languages/catalan.txt index 32e32db9c6c..8cca1acc18d 100644 --- a/data/languages/catalan.txt +++ b/data/languages/catalan.txt @@ -72,12 +72,6 @@ Clan Client == Client -Close -== Tancar - -Connect -== Connectar - Connecting to == T'estàs unint a @@ -639,9 +633,6 @@ Show votes window after voting HUD == HUD -Select a name -== Selecciona un nom - Enable team chat sound == Activar el so del xat d'equip @@ -669,9 +660,6 @@ Gameplay Restart == Reiniciar -Old mouse mode -== Mode antic del ratolí - Browser == Navegador @@ -996,8 +984,8 @@ Show HUD Use high DPI == Fer servir alt DPI -Default length: %d -== Llargada predeterminada: %d +Default length +== Llargada predeterminada Enable replays == Activar les repeticions @@ -1083,9 +1071,6 @@ Getting server list from master server %d player == %d jugador -Refreshing... -== Actualitzant... - Leak IP == Fer la teva ip pública @@ -1255,9 +1240,6 @@ default custom == personalitzat -Graphics cards -== Tarjetes gràfiques - auto == auto @@ -1371,7 +1353,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1383,6 +1365,9 @@ Loading menu images Copy info == +No server selected +== + Online players (%d) == @@ -1435,10 +1420,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1450,9 +1435,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1504,10 +1501,10 @@ Open the directory to add custom skins Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1518,9 +1515,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1533,7 +1527,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1542,9 +1536,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1554,18 +1545,21 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Allows maps to render with more detail == +Graphics card +== + Appearance == diff --git a/data/languages/chuvash.txt b/data/languages/chuvash.txt index 219012dbc70..4c1edeff6ae 100644 --- a/data/languages/chuvash.txt +++ b/data/languages/chuvash.txt @@ -76,12 +76,6 @@ Clan Client == Клиент -Close -== Хупма - -Connect -== Çых - Connecting to == Çыхăну @@ -756,7 +750,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -831,9 +825,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -858,6 +849,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -889,18 +883,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -922,10 +904,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -937,9 +919,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1159,10 +1162,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1173,9 +1176,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1188,7 +1188,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1197,9 +1197,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1209,15 +1206,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1233,10 +1230,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1254,7 +1251,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1482,7 +1479,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1500,10 +1497,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1536,9 +1533,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/czech.txt b/data/languages/czech.txt index 3b20557692a..335505ca821 100644 --- a/data/languages/czech.txt +++ b/data/languages/czech.txt @@ -79,12 +79,6 @@ Clan Client == Klient -Close -== Zavřít - -Connect -== Připojit - Connecting to == Připojuji na @@ -684,9 +678,6 @@ Update now Restart == Restartovat -Select a name -== Vyberte jméno - Please use a different name == Použijte jiné jméno @@ -909,8 +900,8 @@ Normal message Save the best demo of each race == Uložte si nejlepší demo každého závodu -Default length: %d -== Výchozí délka: %d +Default length +== Výchozí délka Enable replays == Povolit záznamy @@ -957,9 +948,6 @@ Show other players' hook collision lines Show other players' key presses == Zobrazit stisknutí kláves ostatních hráčů -Old mouse mode -== Starý režim myši - Show tiles layers from BG map == Zobrazit vrstvy dlaždic z mapy BG @@ -1267,7 +1255,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1306,9 +1294,6 @@ CHN Getting server list from master server == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1318,6 +1303,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1370,10 +1358,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1385,9 +1373,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1448,10 +1448,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1462,9 +1462,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1477,7 +1474,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1486,9 +1483,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1498,15 +1492,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1534,7 +1528,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/danish.txt b/data/languages/danish.txt index ef0974a0af8..54e9bb9cd3f 100644 --- a/data/languages/danish.txt +++ b/data/languages/danish.txt @@ -77,12 +77,6 @@ Clan Client == Klient -Close -== Luk - -Connect -== Tilslut - Connecting to == Forbinder til @@ -713,9 +707,6 @@ Countries Types == Typer -Select a name -== Vælg et navn - Please use a different name == Brug et andet navn @@ -977,8 +968,8 @@ Client message Save the best demo of each race == Gem den bedste demo af hvert løb -Default length: %d -== Standardlængde: %d +Default length +== Standardlængde Enable replays == Aktivér gentagelser @@ -1025,9 +1016,6 @@ Show other players' hook collision lines Show other players' key presses == Vis andre spillers tastetryk -Old mouse mode -== Gammel musetilstand - Use current map as background == Brug det aktuelle kort som baggrund @@ -1265,7 +1253,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1304,9 +1292,6 @@ CHN Getting server list from master server == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1316,6 +1301,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1368,10 +1356,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1383,9 +1371,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1446,10 +1446,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1460,9 +1460,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1475,7 +1472,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1484,9 +1481,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1496,15 +1490,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1532,7 +1526,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/dutch.txt b/data/languages/dutch.txt index 908d05cb4b8..367dc3ebebc 100644 --- a/data/languages/dutch.txt +++ b/data/languages/dutch.txt @@ -89,12 +89,6 @@ Clan Client == Client -Close -== Sluiten - -Connect -== Verbinden - Connecting to == Verbinden met @@ -707,9 +701,6 @@ Update now Restart == Herstarten -Select a name -== Selecteer een naam - Please use a different name == Gebruik a.u.b. een andere naam @@ -931,8 +922,8 @@ Normal message Save the best demo of each race == Sla de beste demo op van elke race -Default length: %d -== Standaard lengte: %d +Default length +== Standaard lengte Enable replays == Replays aanzetten @@ -979,9 +970,6 @@ Show other players' hook collision lines Show other players' key presses == Laat toetsaanslagen van andere spelers zien -Old mouse mode -== Oude muis modus - Show tiles layers from BG map == Laat tegellagen van achtergrondmap zien @@ -1126,9 +1114,6 @@ Getting server list from master server %d player == %d speler -Refreshing... -== Aan het verversen... - Leak IP == Lek IP @@ -1195,9 +1180,6 @@ default custom == custom -Graphics cards -== Grafische kaart - auto == Automatisch @@ -1417,7 +1399,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1429,6 +1411,9 @@ Loading menu images Copy info == +No server selected +== + Online players (%d) == @@ -1481,10 +1466,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1496,9 +1481,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1544,10 +1541,10 @@ Open the directory to add custom skins Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1558,9 +1555,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1573,7 +1567,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1582,9 +1576,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1594,15 +1585,18 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + +Graphics card +== + Appearance == diff --git a/data/languages/esperanto.txt b/data/languages/esperanto.txt index 5f6ba074924..206c80cbfb2 100644 --- a/data/languages/esperanto.txt +++ b/data/languages/esperanto.txt @@ -255,12 +255,6 @@ Server address: Refresh == Aktualigi -Refreshing... -== Aktualas... - -Connect -== Konektiĝi - Count players only == Nur nombri ludantojn @@ -294,18 +288,12 @@ Add Friend Filter == Filtri -Select a name -== Elekti nomon - Please use a different name == Bonvolu uzi malsaman nomon Remove chat == Malmontri la diskutejon -Close -== Fermi - Folder == Dosierujo @@ -727,7 +715,7 @@ Use k key to kill (restart), q to pause and watch other players. See settings fo It's recommended that you check the settings to adjust them to your liking before joining a server. == -Cancel +A demo with this name already exists == Unable to rename the demo @@ -799,6 +787,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -836,9 +827,6 @@ Add Clan Info == -File already exists, do you want to overwrite it? -== - Play the current demo == @@ -860,10 +848,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -875,12 +863,27 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == Demofile: %s == +Export demo cut +== + +Cut interval +== + +Cut length +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1178,10 +1181,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1192,9 +1195,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1207,7 +1207,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1216,18 +1216,12 @@ Status Aim bind == -Controller Axis #%d -== - Ingame mouse sens. == UI mouse sens. == -Controller -== - Movement == @@ -1240,6 +1234,9 @@ Reset controls Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Voting == @@ -1291,7 +1288,7 @@ Renderer custom == -Graphics cards +Graphics card == Play background music @@ -1519,7 +1516,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1537,10 +1534,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1573,9 +1570,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/finnish.txt b/data/languages/finnish.txt index b677efe633d..58a966e79f1 100644 --- a/data/languages/finnish.txt +++ b/data/languages/finnish.txt @@ -78,12 +78,6 @@ Clan Client == Asiakasohjelma -Close -== Sulje - -Connect -== Yhdistä - Connecting to == Yhdistetään @@ -745,9 +739,6 @@ Types Leak IP == Vuoda IP -Select a name -== Valitse nimi - Please use a different name == Ole hyvä ja käytä toista nimeä @@ -991,8 +982,8 @@ Preview Save the best demo of each race == Tallenna jokaisen kentän paras demo -Default length: %d -== Oletuspituus: %d +Default length +== Oletuspituus Enable replays == Luo uudelleentoistoja @@ -1030,9 +1021,6 @@ Show other players' hook collision lines Show other players' key presses == Näytä muiden pelaajien näppäinpainallukset -Old mouse mode -== Vanha hiiritila - Background == Tausta @@ -1249,7 +1237,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1264,15 +1252,15 @@ Skip Tutorial Loading menu images == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == Copy info == +No server selected +== + Online players (%d) == @@ -1325,10 +1313,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1340,9 +1328,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1427,10 +1427,10 @@ Show entities Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1441,9 +1441,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1456,7 +1453,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1465,9 +1462,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1477,15 +1471,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Windowed fullscreen == @@ -1501,7 +1495,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/french.txt b/data/languages/french.txt index a3911bdbb4c..d5edc5832be 100644 --- a/data/languages/french.txt +++ b/data/languages/french.txt @@ -100,12 +100,6 @@ Clan Client == Client -Close -== Fermer - -Connect -== Se connecter - Connecting to == Connexion à @@ -688,9 +682,6 @@ Are you sure that you want to disconnect your dummy? Reload == Recharger -Old mouse mode -== Ancien mode de souris - Server best: == Meilleur score du serveur @@ -916,8 +907,8 @@ Show others Unfinished map == Carte non terminée -Default length: %d -== Durée par défaut : %d +Default length +== Durée par défaut Highlighted message == Message en surbrillance @@ -946,9 +937,6 @@ Ghost Show kill messages == Afficher les messages d'élimination -Select a name -== Choisissez un pseudonyme - Markers == Marqueurs @@ -1253,9 +1241,6 @@ 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? -Refreshing... -== Actualisation... - Show local player's key presses == Montrer les touches appuyées des autres joueurs @@ -1301,9 +1286,6 @@ default custom == Personnalisé -Graphics cards -== Carte graphique - auto == Automatique @@ -1361,13 +1343,6 @@ Download community skins Enable controller == Activer la manette -Controller %d: %s -== Manette %d: %s - -Click to cycle through all available controllers. -== Cliquez pour essayer toutes les manettes disponibles. - -[Ingame controller mode] Relative == Relatif @@ -1387,18 +1362,12 @@ UI controller sens. Controller jitter tolerance == Tolérance gigue de la manette -Device -== Manette - Status == Statut Aim bind == Touche visée -Controller Axis #%d -== Axe manette #%d - Mouse == Souris @@ -1558,12 +1527,6 @@ Slow down the demo Speed up the demo == Accélérer la démo -Mark the beginning of a cut -== Marquer le début d'une coupe - -Mark the end of a cut -== Marquer la fin d'une coupe - Export cut as a separate demo == Exporter la coupe comme une démo séparée @@ -1737,3 +1700,33 @@ Add Clan Create a random skin == Créer un skin aléatoire + +A demo with this name already exists +== + +No server selected +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/data/languages/galician.txt b/data/languages/galician.txt new file mode 100644 index 00000000000..2112aae3619 --- /dev/null +++ b/data/languages/galician.txt @@ -0,0 +1,1719 @@ +##### authors ##### +# based on Spanish translation (2023-07-05) +# +# originally created by: +# TormentaDeFacha and Mercadona (2023-07-05) +##### /authors ##### + +##### translated strings ##### + +%ds left +== %ds restantes + +%i minute left +== %i minuto restante + +%i minutes left +== %i minutos restantes + +%i second left +== %i segundo restante + +%i seconds left +== %i segundos restantes + +%s wins! +== ¡%s gana! + +-Page %d- +== -Páxina %d- + +Abort +== Cancelar + +Add +== Añadir + +Add Friend +== Añadir amigo + +Address +== Dirección + +All +== Todos + +Are you sure that you want to quit? +== Estás seguro de que queres saír? + +Automatically record demos +== Gravar demos automaticamente + +Automatically take game over screenshot +== Captura de pantalla ao final da partida + +Blue team +== Equipo azul + +Blue team wins! +== O equipo azul gana! + +Body +== Corpo + +Call vote +== Votar + +Change settings +== Cambiar configuración + +Chat +== Chat + +Clan +== Clan + +Client +== Cliente + +Close +== Pechar + +Connect +== Conectar + +Connecting to +== Conectando con + +Connection Problems... +== Problemas de conexión... + +Console +== Consola + +Controls +== Controis + +Count players only +== Só contar xogadores + +Current +== Actual + +Custom colors +== Cores personalizadas + +Delete +== Borrar + +Delete demo +== Borrar demo + +Demo details +== Detalles de la demo + +Demofile: %s +== Arquivo: %s + +Demos +== Demos + +Disconnect +== Desconectar + +Disconnected +== Desconectado + +Downloading map +== Descargando mapa + +Draw! +== Empate! + +Dynamic Camera +== Cámara dinámica + +Emoticon +== Emoticon + +Error +== Erro + +Error loading demo +== Erro ao cargar a demo + +Favorite +== Favorito + +Favorites +== Favoritos + +Feet +== Pés + +Filter +== Filtro + +Fire +== Disparar + +Folder +== Cartafol + +Force vote +== Forzar + +Free-View +== Vista libre + +Friends +== Amigos + +Fullscreen +== Pantalla completa + +Game +== Xogo + +Game info +== Información do xogo + +Game over +== Fin da partida + +Game paused +== Xogo en Pausa + +Game type +== Modo + +Game types: +== Tipos de Xogo: + +General +== Xeneral + +Graphics +== Gráficos + +Grenade +== Granada + +Hammer +== Martelo + +Has people playing +== Hai xente xogando + +High Detail +== Máis detalles (HD) + +Hook +== Gancho + +Invalid Demo +== Demo inválida + +Join blue +== Unirse ao azul + +Join red +== Unirse ao vermello + +Jump +== Saltar + +Kick player +== Expulsar xogador + +Language +== Idioma + +MOTD +== MOTD + +Map +== Mapa + +Move left +== Mover á esquerda + +Move player to spectators +== Mover xogador a espectadores + +Move right +== Mover á dereita + +Movement +== Movemento + +Mute when not active +== Silenciar se non está activo + +Name +== Nome + +Next weapon +== Arma seguinte + +Nickname +== Alcume + +No +== No + +No password +== Sen contrasinal + +No servers found +== Ningún servidor atopado + +No servers match your filter criteria +== Ningún servidor corresponde aos criterios de filtrado + +Ok +== Aceptar + +Open +== Abrir + +Parent Folder +== Directorio pai + +Password +== Contrasinal + +Password incorrect +== Contrasinal incorreta + +Ping +== Ping + +Pistol +== Pistola + +[Demo browser] +Play +== Reproducir + +Play background music +== Reproducir música de fondo + +Player +== Xogador + +Player country: +== País do xogador + +Player options +== Opcións de xogador + +Players +== Xogadores + +Please balance teams! +== Por favor, equilibrade os equipos! + +Prev. weapon +== Arma anterior + +Quit +== Saír + +Reason: +== Motivo: + +Red team +== Equipo vermello + +Red team wins! +== O equipo vermello gana! + +Refresh +== Actualizar + +Remote console +== Consola remota + +Remove +== Eliminar + +Remove friend +== Eliminar amigo + +Rename +== Renombrar + +Rename demo +== Renombrar demo + +Reset filter +== Restablecer filtro + +Sample rate +== Frecuencia de mostraxe + +Score +== Puntos + +Score limit +== Límite puntos + +Scoreboard +== Puntuaxe + +Screenshot +== Captura de pantalla + +Server address: +== IP do servidor: + +Server details +== Detalles do servidor + +Server filter +== Filtro do servidor + +Server info +== Servidor + +Server not full +== Servidor sen encher + +Shotgun +== Escopeta + +Show chat +== Mostrar chat + +Show friends only +== Só mostrar amigos + +Show ingame HUD +== Mostrar HUD durante o xogo + +Show name plates +== Mostrar alcumes + +Show only chat messages from friends +== Recibir mensaxes só de amigos + +Sound +== Son + +Sound error +== Erro de son + +Spectate +== Asistir + +Spectate next +== Observar seguinte + +Spectate previous +== Observar anterior + +Spectator mode +== Modo espectador + +Spectators +== Espectadores + +Stop record +== Deter gravación + +Strict gametype filter +== Filtro de tipo de xogo estrito + +Sudden Death +== Morte súbita + +Switch weapon on pickup +== Cambiar á arma recollida + +Team +== Equipo + +Team chat +== En equipo + +The audio device couldn't be initialised. +== O dispositivo de audio non puido ser inicializado. + +The server is running a non-standard tuning on a pure game type. +== O servidor está a executar unha configuración non estándar nun tipo de xogo puro. + +There's an unsaved map in the editor, you might want to save it before you quit the game. +== Tes un mapa sen gardar no editor, quizá queiras gardalo antes de saír. + +Time limit +== Tempo límite + +Time limit: %d min +== Tempo límite: %d minutos + +Try again +== Tentar de novo + +Type +== Tipo + +Unable to rename the demo +== Non se puido renombrar a demo + +Use sounds +== Usar sons + +Use team colors for name plates +== Usar a cor de equipo nos alcumes + +V-Sync +== V-Sync + +Version +== Versión + +Vote command: +== Comando de votación: + +Vote description: +== Descrición da votación: + +Vote no +== Votar no + +Vote yes +== Votar sí + +Voting +== Votación + +Warmup +== Quecemento + +Weapon +== Arma + +Yes +== Sí + +You must restart the game for all settings to take effect. +== Debes reiniciar o xogo para que os cambios teñan efecto. + +New name: +== Novo nome: + +Sat. +== Sat. + +Miscellaneous +== Miscelánea + +Internet +== Internet + +Max demos +== Número máximo de demos + +News +== Noticias + +Join game +== Unirse + +FSAA samples +== Muestras FSAA + +Sound volume +== Volume do son + +Created: +== Creado: + +Max Screenshots +== Número máximo de capturas + +Length: +== Lonxitude: + +Laser +== Láser + +Netversion: +== Versión Net: + +Map: +== Mapa: + +Info +== Información + +Hue +== Matiz + +Record demo +== Grabar demo + +Your skin +== O teu skin + +Size: +== Tamaño: + +Reset to defaults +== Restablecer por defecto + +Quit anyway? +== Saír de todos os xeitos? + +Version: +== Versión: + +Round +== Rolda + +Lht. +== Lum. + +UI Color +== Cor de menú + +Crc: +== Crc: + +Alpha +== Transp. + +LAN +== LAN + +Name plates size +== Tamaño da fonte dos alcumes + +Type: +== Tipo: + +Successfully saved the replay! +== Repetición gardada con éxito! + +Replay feature is disabled! +== A función de repetición está desactivada! + +Server best: +== Mellor do servidor: + +Personal best: +== Mellor persoal: + +Learn +== Aprender + +Browser +== Navegador + +Ghost +== Pantasma + +Loading DDNet Client +== Cargando o cliente DDNet + +Reconnect in %d sec +== Reconectando en %d seg + +Render demo +== Renderizar demo + +Replace video +== Substituír vídeo + +File already exists, do you want to overwrite it? +== O arquivo xa existe, queres sobrescribirlo? + +Are you sure that you want to disconnect? +== Estás seguro de que queres desconectarche? + +Disconnect Dummy +== Desconectar Dummy + +Are you sure that you want to disconnect your dummy? +== Estás seguro de que queres desconectar o teu dummy? + +Welcome to DDNet +== Benvido a 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 é un xogo cooperativo en liña onde o obxectivo é que ti e o voso grupo de tees cheguen á meta do mapa. Como recentemente chegado, deberías comezar nos servidores Novice, que aloxan os mapas máis fáciles. Considera o ping para elixir un servidor próximo + +It's recommended that you check the settings to adjust them to your liking before joining a server. +== Recoméndase que verifiques a configuración para axustala ao teu gusto antes de unirche a un servidor. + +Please enter your nickname below. +== Por favor, introduza o seu alcume a continuación. + +Video name: +== Nome do vídeo: + +Show DDNet map finishes in server browser +== Marcar os mapas DDNet acabados no navegador de servidores + +transmits your player name to info.ddnet.org +== transmite o teu nome de xogador a info.ddnet.org + +Search +== Buscar + +Exclude +== Excluír + +Filter connecting players +== Filtrar xogadores conectándose + +Indicate map finish +== Indicar mapa acabado + +Unfinished map +== Mapa inacabado + +Countries +== Países + +Types +== Tipos + +DDNet %s is out! +== DDNet %s xa esta dispoñible! + +Downloading %s: +== Descargando %s: + +Update failed! Check log... +== Actualización errada! Comproba o rexistro... + +DDNet Client updated! +== Cliente DDNet actualizado! + +Update now +== Actualizar agora + +Restart +== Reiniciar + +Select a name +== Selecciona un nome + +Please use a different name +== Por favor usa un nome diferente + +Remove chat +== Eliminar chat + +Markers: +== Marcadores: + +%.2f MiB +== %.2f MiB + +%.2f KiB +== %.2f KiB + +Demo +== Demo + +Markers +== Marcadores + +Length +== Lonxitude + +Date +== Data + +Fetch Info +== Obter info. + +Render +== Renderizar + +Connecting dummy +== Conectando dummy + +Connect Dummy +== Conectar Dummy + +Reload +== Recargar + +Deactivate +== Desactivar + +Activate +== Activar + +Save +== Gardar + +Switch weapon when out of ammo +== Cambiar de arma cando se acabe a munición + +Show clan above name plates +== Mostrar o clan por encima dos nomes + +Clan plates size +== Tamaño da fonte do clan + +Refresh Rate +== Frecuencia de actualización + +Automatically take statboard screenshot +== Tomar automaticamente a captura de pantalla do Statboard + +Automatically create statboard csv +== Crear automaticamente o csv do Statboard + +Max CSVs +== CSV máx + +Dummy settings +== Config. del Dummy + +Vanilla skins only +== Só skins Vanilla + +Fat skins (DDFat) +== Skins Obesas (DDFat) + +Skin prefix +== Prefixo de skin + +Hook collisions +== Colisións do gancho + +Pause +== Pausa + +Kill +== Matar + +Zoom in +== Achegar + +Zoom out +== Afastar + +Default zoom +== Zoom predeterm. + +Show others +== Mostrar outros + +Show all +== Mostrar todo + +Toggle dyncam +== Alt. cámara dinámica + +Toggle dummy +== Alternar dummy + +Toggle ghost +== Alternar pantasma + +Dummy copy +== Copiar dummy + +Hammerfly dummy +== Hammerfly dummy + +Converse +== Conversar + +Statboard +== Statboard + +Lock team +== Bloquear equipo + +Show entities +== Mostrar entidades + +Show HUD +== Mostrar HUD + +may cause delay +== pode causar atraso + +Screen +== Pantalla + +Use high DPI +== Usar DPI alto + +Enable game sounds +== Habilitar sons do xogo + +Enable gun sound +== Habilitar son de pistola + +Enable long pain sound (used when shooting in freeze) +== Habilitar o son de dor prolongada (úsase ao disparar estando conxelado) + +Enable server message sound +== Habilitar o son para mensaxes do servidor + +Enable regular chat sound +== Habilitar son do chat normal + +Enable team chat sound +== Habilitar o son do chat do equipo + +Enable highlighted chat sound +== Habilitar son de chat resaltado + +Threaded sound loading +== Carga de son nun fío + +Map sound volume +== Vol. do son do mapa + +HUD +== Aparencia + +DDNet +== DDNet + +DDNet Client needs to be restarted to complete update! +== Utilizar o marcador DDRace + +Use DDRace Scoreboard +== Utilizar o marcador DDRace + +Show score +== Mostrar puntaje + +Show names in chat in team colors +== Mostrar nomes con cores de equipo no chat + +Show kill messages +== Mostrar mensaxes de morte + +Show votes window after voting +== Mostrar xanela de votos despois de votar + +Messages +== Menxaxes + +System message +== Menxaxe do sistema + +Reset +== Reiniciar + +Highlighted message +== Mensaxe resaltada + +Team message +== Mensaxe do equipo + +Friend message +== Mensaxe dun amigo + +Normal message +== Menxaxe normal + +Save the best demo of each race +== Gardar o mellor demo de cada carreira + +Default length: %d +== Lonxitude predeterminada: %d + +Enable replays +== Habilitar repeticiones + +Show ghost +== Mostrar pantasma + +Save ghost +== Guardar pantasma + +Gameplay +== Gameplay + +Overlay entities +== Entidades superpostas + +Size +== Tamaño + +Show text entities +== Mostrar entidades de texto + +Show others (own team only) +== Mostrar outros (solo equipo propio) + +Show quads +== Mostrar quads + +AntiPing +== AntiPing + +AntiPing: predict other players +== AntiPing: predicir outros xogadores + +AntiPing: predict weapons +== AntiPing: predicir armas + +AntiPing: predict grenade paths +== AntiPing: predicir percorrido de granadas + +Show other players' hook collision lines +== Mostrar as liñas de colisión de gancho doutros xogadores + +Show other players' key presses +== Mostrar as pulsacións de teclas doutros xogadores + +Old mouse mode +== Modo de mouse vello + +Show tiles layers from BG map +== Mostrar capas de tiles do mapa de fondo + +DDNet %s is available: +== DDNet %s está dispoñible: + +Updating... +== Actualizando... + +No updates available +== Non hai actualizacións dispoñibles + +Check now +== Revisalo agora + +New random timeout code +== Novo código de timeout aleatorio + +Time +== Tempo + +Follow +== Seguir + +Frags +== Asasinatos + +Deaths +== Mortes + +Suicides +== Suicidios + +Ratio +== K/D + +Net +== Ventaxa + +FPM +== APM + +Spree +== Racha + +Best +== Mellor + +Grabs +== Bandeiras + +1 new mention +== 1 nova mención + +%d new mentions +== %d novas mencións + +9+ new mentions +== 9+ novas mencións + +Saving ddnet-settings.cfg failed +== Erro ao gardar ddnet-settings.cfg + +Warning +== Advertencia + +Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. +== Modo debug activado. Pulsa Ctrl+Shift+D para desactivarlo. + +Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. +== Usa a tecla K para suicidarche (reiniciar), Q para pausar e ver a outros xogadores. Mira a configuración para outras teclas. + +Existing Player +== Este xogador xa existe + +Your nickname '%s' is already used (%d points). Do you still want to use it? +== O teu nome '%s' xa está usado (%d puntos). Desexas seguir usándoo? + +Checking for existing player with your name +== Comprobando se xa existe un xogador co teu nome + +Speed +== Velocidade + +Theme +== Tema + +%d of %d servers +== %d dos %d servidores + +%d of %d server +== %d do %d servidor + +%d players +== %d xogadores + +%d player +== %d xogador + +Demos directory +== Directorio de demos + +Smooth Dynamic Camera +== Cámara dinámica suave + +Skip the main menu +== Saltar ao menú principal + +Themes directory +== Directorio de temas + +Download skins +== Descargar skins + +Skin Database +== Base de datos de skins + +Skins directory +== Directorio de skins + +Game sound volume +== Vol. do sonido do xogo + +Chat sound volume +== Vol. do sonido do chat + +Background music volume +== Vol. da música de fondo + +Assets +== Recursos + +Use old chat style +== Usar estilo antigo do chat + +Client message +== Mensaxe do cliente + +Use current map as background +== Usar mapa actual como fondo + +Entities +== Entidades + +Emoticons +== Emoticones + +Particles +== Partículas + +Assets directory +== Directorio de recursos + +https://wiki.ddnet.org/ +== https://wiki.ddnet.org/wiki/Main_Page/es + +Website +== Páxina web + +Settings +== Configuración + +Stop server +== Deter servidor + +Run server +== Iniciar servidor + +Server executable not found, can't run server +== Executable do servidor non atopado. Non se pode iniciar o servidor + +Editor +== Editor + +[Start menu] +Play +== Xogar + +Manual +== Manual + +Race +== Carreira + +Auto +== Auto + +Replay +== Repetición + +The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. +== O ancho da textura %s non é divisible por %d, ou o alto non é divisible por %d, o que pode causar erros visuais. + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== NA + +SA +== SA + +CHN +== CHN + +Getting server list from master server +== Obtendo lista de servidores do servidor mestre + +Leak IP +== Filtrar tu IP + +Chat command +== Comando de chat + +Dummy +== Dummy + +Windowed +== En xanela + +Windowed borderless +== Xanela sen bordos + +Desktop fullscreen +== Pantalla completa de escritorio + +Preview +== Vista previa + +Background +== Fondo + +Entities Background color +== Cor de fondo de entidades + +Regular Background Color +== Cor de fondo regular + +Discord +== Discord + +https://ddnet.org/discord +== https://ddnet.org/discord + +Are you sure that you want to disconnect and switch to a different server? +== Seguro que queres desconectarche e cambiar de servidor? + +Refreshing... +== Actualizando... + +Kill Messages +== Mensaxes de morte + +Show local player's key presses +== Mostrar as teclas presionadas polo xogador local + +Settings file +== Arquivo de configuraciones + +Config directory +== Directorio de configuración + +Run on join +== Ejec. ao entrar + +Chat command (e.g. showall 1) +== Comando de chat (Ex. showall 1) + +The format of texture %s is not RGBA which will cause visual bugs. +== O formato da textura %s non é RGBA, o que pode causar erros visuais. + +Join Tutorial Server +== Ir a un servidor Titorial + +Skip Tutorial +== Saltar T`itorial + +Windowed fullscreen +== Xanela a pantalla completa + +Renderer +== Renderizador + +default +== por defecto + +custom +== personalizado + +Graphics cards +== Tarxetas de vídeo + +auto +== auto. + +Tutorial +== Titorial + +Can't find a Tutorial server +== Non se puido atopar un servidor Titorial + +Toggle to edit your dummy settings +== Actívao para editar a configuración do téu monicreque + +Choose default eyes when joining a server +== Escolle os ollos predeterminados ao unirche a un servidor + +Allows maps to render with more detail +== Permite que os mapas teñan máis detalles + +When you cross the start line, show a ghost tee replicating the movements of your best time +== Cando cruzas a liña de inicio, móstrase un tee pantasma replicando os movementos do teu mellor tempo + +Opacity +== Opacidade + +Quads are used for background decoration +== As quads úsanse para a decoración do fondo + +Tries to predict other entities to give a feel of low latency +== Tenta predicir a outras entidades para dar unha sensación de baixa latencia + +Super +== Súper + +Team %d +== Equipo %d + +Position: +== Posición: + +Speed: +== Velocidade: + +Angle: +== Ángulo: + +Trying to determine UDP connectivity... +== Tratando de determinar a conexión UDP... + +UDP seems to be filtered. +== UDP parece estar filtrado. + +UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators. +== As direccións IP TCP e UDP parecen ser diferentes. Tenta desactivar calquera VPN, proxy ou acelerador de rede. + +No answer from server yet. +== Aínda non hai resposta do servidor. + +Download community skins +== Descargar skins da comunidade + +Enable controller +== Habilitar mando + +Controller %d: %s +== Mando %d: %s + +Click to cycle through all available controllers. +== Click aquí para percorrer todos os mandos dispoñibles. + +[Ingame controller mode] +Relative +== Relativo + +[Ingame controller mode] +Absolute +== Absoluto + +Ingame controller mode +== Modo do mando + +Ingame controller sens. +== Sens. do mando do xogo + +UI controller sens. +== Sens. do mando do menú + +Controller jitter tolerance +== Tolerancia á fluctuación do mando + +Device +== Dispositivo + +Status +== Estado + +Aim bind +== Apuntar + +Controller Axis #%d +== Eje #%d do mando + +Mouse +== Ratón + +Ingame mouse sens. +== Sens. do ratón no xogo + +UI mouse sens. +== Sens. do ratón no menú + +Controller +== Mando + +Show dummy actions +== Mostrar acciones del dummy + +Show freeze bars +== Mostrar barras do freeze + +Show player position +== Mostrar a posición do xogador + +Show player speed +== Mostrar a velocidade do xogador + +Show player target angle +== Mostrar o ángulo do obxectivo do xogador + +Adjust the opacity of entities belonging to other teams, such as tees and nameplates +== Axusta a opacidade de entidades pertencentes a outros equipos, como os tees e os nomes. + +Opacity of freeze bars inside freeze +== Opacidade das barras de freeze dentro do freeze + +Normal Color +== Cor normal + +Highlight Color +== Cor resaltada + +Extras +== Extras + +Preparing demo playback +== Preparando a reprodución da demo + +Connected +== Conectado + +Loading map file from storage +== Cargando arquivo de mapa desde o almacenamento + +Why are you slowmo replaying to read this? +== Por que o reproduces a cámara lenta para ler isto? + +Initializing components +== Inicializando compoñentes + +Initializing assets +== Inicializando recursos + +Initializing map logic +== Inicializando lóxica de mapa + +Sending initial client info +== Enviando información inicial do cliente + +Uploading map data to GPU +== Cargando datos do mapa á GPU + +Getting game info +== Obtendo información da partida + +Requesting to join the game +== Solicitando unión á partida + +Loading menu images +== Cargando imaxes do menú + +Play the current demo +== Reproducir a demo actual + +Pause the current demo +== Pausar a demo actual + +Stop the current demo +== Deter a demo actual + +Slow down the demo +== Retardar a demo + +Speed up the demo +== Acelerar a demo + +Mark the beginning of a cut +== Marca o inicio dun corte + +Mark the end of a cut +== Marca o final dun corte + +Export cut as a separate demo +== Exportar corte como unha demo diferente + +Toggle keyboard shortcuts +== Alternar atallos de teclado + +Loading demo files +== Cargando arquivos de demos + +Menu opened. Press Esc key again to close menu. +== Menú aberto. Presiona a tecla Esc outra vez para pechalo. + +Loading ghost files +== Cargando arquivos de pantasma + +Loading skin files +== Cargando arquivos de skins + +Appearance +== Aparencia + +Name Plate +== Nomes + +Hook Collisions +== Colisións do gancho + +Show health, shields and ammo +== Mostrar saúde, escudos e munición + +DDRace HUD +== HUD DDRace + +Show client IDs in scoreboard +== Mostrar IDs de cliente na táboa de puntuación + +Show DDRace HUD +== Mostrar o HUD DDRace + +Show jumps indicator +== Mostrar indicador de saltos + +Show hook strength indicator +== Mostrar indicador de forza do gancho + +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 + +Colors of the hook collision line, in case of a possible collision with: +== Cores da liña de colisión do gancho, no posible caso de que choque con: + +Your movements are not taken into account when calculating the line colors +== Os teus movementos non contan ao calcular as cores da liña + +Nothing hookable +== Nada enganchable + +Something hookable +== Algo enganchable + +A Tee +== Un Tee + +Weapons +== Armas + +Rifle Laser Outline Color +== Cor externa do Láser de Rifle + +Rifle Laser Inner Color +== Cor interna do Láser de Rifle + +Shotgun Laser Outline Color +== Cor externa do Láser da Escopeta + +Shotgun Laser Inner Color +== Cor interna do Láser da Escopeta + +Door Laser Outline Color +== Cor externa das Puertas Láser + +Door Laser Inner Color +== Cor interna das Puertas Láser + +Freeze Laser Outline Color +== Cor externa do Láser Congelante + +Freeze Laser Inner Color +== Cor interna do Láser Congelante + +Set all to Rifle +== Usar todos como o Rifle + +Loading assets +== Cargando recursos + +Loading race demo files +== Cargando arquivos de demos de carreiras + +Loading sound files +== Cargando arquivos de son + +Cancel +== Cancelar + +File '%s' already exists, do you want to overwrite it? +== O arquivo '%s' xa existe, queres sobrescribirlo? + +Are you sure that you want to remove the player '%s' from your friends list? +== Estás seguro de que queres quitar ao xogador '%s' da túa lista de amigos? + +Are you sure that you want to remove the clan '%s' from your friends list? +== Estás seguro de que queres quitar ao clan '%s' da túa lista de amigos? + +Go back one tick +== Retroceder un tick + +Go forward one tick +== Avanzar un tick + +Go back one marker +== Retroceder un marcador + +Go forward one marker +== Avanzar un marcador + +Are you sure that you want to delete the demo '%s'? +== Estás seguro de que queres eliminar a demo '%s'? + +Unable to delete the demo '%s' +== Non se puido eliminar a demo '%s' + +Reset controls +== Restablecer controis + +Are you sure that you want to reset the controls to their defaults? +== Estás seguro de que queres restablecer aos controis predeterminados? + +[Graphics error] + +[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. +== Erro durante a iniciación. Tenta cambiar gfx_backend a OpenGL ou Vulkan desde settings_ddnet.cfg no cartafol de configuracións e téntao outra vez. + +[Graphics error] +Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. +== A VRAM encheuse. Tenta quitar recursos personalizados (skins, entidades etc.)/ etc.), especialmente os de resolución alta. + +[Graphics error] +An error during command recording occurred. Try to update your GPU drivers. +== Ocorreu un erro durante a gravación de comandos. Tenta actualizar os controladores do teu GPU. + +[Graphics error] +A render command failed. Try to update your GPU drivers. +== Un comando de renderización fallou. Tenta actualizar os controladores do teu GPU. + +[Graphics error] +Submitting the render commands failed. Try to update your GPU drivers. +== Erro ao enviar os comandos de renderización. Tenta actualizar os controladores do teu GPU. + +[Graphics error] +Failed to swap framebuffers. Try to update your GPU drivers. +== Erro ao intercambiar búferes de fotogramas. Tenta actualizar os controladores do teu 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. +== Erro descoñecido. Tenta cambiar gfx_backend a OpenGL ou Vulkan desde settings_ddnet.cfg no directorio de configuracións e téntao outra vez. + +[Graphics error] +Could not initialize the given graphics backend, reverting to the default backend now. +== Non se puido inicializar o backend de gráficos dado, volvendo ao backend predeterminado. + +Open the directory that contains the demo files +== Abrir o cartafol que contén os arquivos de demo + +Save power by lowering refresh rate (higher input latency) +== Aforra enerxía ao reducir a frecuencia de actualización (maior latencia de entrada) + +Open the settings file +== Abrir o arquivo de configuracións + +Open the directory that contains the configuration and user files +== Abrir o cartafol que contén as configuracións e arquivos do usuario + +Open the directory to add custom themes +== Abrir o cartafol para agregar temas personalizados + +Open the directory to add custom skins +== Abrir o cartafol para agregar skins personalizadas + +No controller found. Plug in a controller. +== Non se atopou un mando. Conecta un. + +Unregister protocol and file extensions +== Anular rexistro de protocolo e extensións de arquivo + +Open the directory to add custom assets +== Abrir o cartafol para agregar recursos personalizados + +[Graphics error] +Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. +== No se pudo inizializar el backend de gráficos dado, probablemente debido a que los controladores de la tarjeta gráfica integrada no están instalados. + +Could not save downloaded map. Try manually deleting this file: %s +== Non se puido almacenar o mapa descargado. Tenta eliminar este arquivo manualmente: %s + +Copy info +== Copiar información + +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) + +[friends (server browser)] +Offline (%d) +== Desconectados (%d) + +Click to select server. Double click to join your friend. +== Haz clic para seleccionar o servidor. Dobre clic para unirche ao teu amigo. + +Click to remove this player from your friends list. +== Haz clic para quitar este xogador da túa lista de amigos. + +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 diff --git a/data/languages/german.txt b/data/languages/german.txt index d93b2464cba..d070c01ba0f 100644 --- a/data/languages/german.txt +++ b/data/languages/german.txt @@ -91,15 +91,9 @@ Clan Client == Client -Close -== Schließ. - Colors of the hook collision line, in case of a possible collision with: == Die Farbe der Hakenkollisionlinie bei einer möglichen Kollision mit: -Connect -== Verbinden - Connecting to == Verbinden mit @@ -791,9 +785,6 @@ Switch weapon when out of ammo Grabs == Griffe -Select a name -== Namen wählen - Automatically take game over screenshot == Automatisches Bildschirmfoto am Spielende @@ -806,9 +797,6 @@ Gameplay Laser == Laser -Old mouse mode -== Alter Mausmodus - Follow == Folgen @@ -1019,8 +1007,8 @@ Replace video 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 ist ein kooperatives Onlinespiel mit dem Ziel für dich und deine Gruppe von Tees das Ziel der Karte zu erreichen. Als Anfänger solltest du auf den Novice-Servern anfangen, auf denen die einfachsten Karten sind. Achte auf den Ping, um einen Server in deiner Nähe zu finden. -Default length: %d -== Standardlänge: %d +Default length +== Standardlänge Pause == Pause @@ -1284,9 +1272,6 @@ A Tee Are you sure that you want to disconnect and switch to a different server? == Bist du sicher, dass du die Verbindung trennen und zu einem anderen Server wechseln möchtest? -Refreshing... -== Aktualisieren... - Kill Messages == Kill-Nachrichten @@ -1326,9 +1311,6 @@ default custom == eigene -Graphics cards -== Grafikkarte - auto == Auto @@ -1392,9 +1374,6 @@ No answer from server yet. Download community skins == Community-Skins runterladen -Device -== Gerät - Status == Status @@ -1428,13 +1407,6 @@ UI mouse sens. Enable controller == Controller aktivieren -Controller %d: %s -== Controller %d: %s - -Click to cycle through all available controllers. -== Klicke um durch die verfügbaren Controller zu wechseln. - -[Ingame controller mode] Relative == Relativ @@ -1454,9 +1426,6 @@ UI controller sens. Controller jitter tolerance == Jitter-Toleranz des Controllers -Controller Axis #%d -== Controller-Axe #%d - Controller == Controller @@ -1583,12 +1552,6 @@ Freeze Laser Outline Color Freeze Laser Inner Color == Innenfarbe Einfrier-Laser -Mark the beginning of a cut -== Den Anfang eines Schnitts markieren - -Mark the end of a cut -== Das Ende eines Schnitts markieren - Menu opened. Press Esc key again to close menu. == Menü geöffnet. Erneut Esc-Taste drücken um Menü zu schließen. @@ -1727,3 +1690,33 @@ None Add Clan == Clan hinzufügen + +A demo with this name already exists +== Eine Demo mit diesem Namen existiert bereits + +No server selected +== Kein Server ausgewählt + +Mark the beginning of a cut (right click to reset) +== Den Anfang eines Schnitts markieren (Rechtsklick zum Resetten) + +Mark the end of a cut (right click to reset) +== Das Ende eines Schnitts markieren (Rechtsklick zum Resetten) + +Close the demo player +== Den Demo-Player schließen + +Export demo cut +== Demo-Schnitt exportieren + +Cut interval +== Intervall des Schnitts + +Cut length +== Länge des Schnitts + +Axis +== Achse + +Graphics card +== Grafikkarte diff --git a/data/languages/greek.txt b/data/languages/greek.txt index 7764ebfc018..177ec06ead0 100644 --- a/data/languages/greek.txt +++ b/data/languages/greek.txt @@ -76,12 +76,6 @@ Clan Client == Πελάτης -Close -== Κλείσιμο - -Connect -== Σύνδεση - Connecting to == Σύνδεση με @@ -759,7 +753,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -834,9 +828,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -861,6 +852,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -892,18 +886,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -925,10 +907,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -940,9 +922,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1162,10 +1165,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1176,9 +1179,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1191,7 +1191,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1200,9 +1200,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1212,15 +1209,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1236,10 +1233,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1257,7 +1254,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1482,7 +1479,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1500,10 +1497,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1536,9 +1533,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/hungarian.txt b/data/languages/hungarian.txt index a2a527d192b..f0ed34540ac 100644 --- a/data/languages/hungarian.txt +++ b/data/languages/hungarian.txt @@ -78,12 +78,6 @@ Clan Client == Kliens -Close -== Bezárás - -Connect -== Csatlakozás - Connecting to == Csatlakozás... @@ -622,9 +616,6 @@ Spectate next Show names in chat in team colors == Csapatszínben jelennek meg a játékosok a chatben -Select a name -== Válassz nevet - Enable team chat sound == Csapat chat hang engedélyezése @@ -655,9 +646,6 @@ Restart Game paused == Játék megállítva -Old mouse mode -== Régi egér mód - Browser == Böngésző @@ -970,8 +958,8 @@ Toggle dyncam %d new mentions == %d új értesítés -Default length: %d -== Átlagos hossz: %d +Default length +== Átlagos hossz Show HUD == HUD mutatása @@ -1217,9 +1205,6 @@ SA CHN == KíNA -Refreshing... -== Frissítés... - Kill Messages == Halálüzenetek @@ -1277,9 +1262,6 @@ default custom == egyedi -Graphics cards -== Videókártyák - auto == automatikus @@ -1337,13 +1319,6 @@ Download community skins Enable controller == Kontroller használata -Controller %d: %s -== Kontroller %d: %s - -Click to cycle through all available controllers. -== Kattints, hogy váltogass az elérhető kontrollerek között. - -[Ingame controller mode] Relative == Relatív @@ -1363,15 +1338,9 @@ UI controller sens. Controller jitter tolerance == Kontroller jitter tolerancia -Device -== Eszköz - Status == Állapot -Controller Axis #%d -== Kontroller Tengely #%d - Mouse == Egér @@ -1569,37 +1538,12 @@ File '%s' already exists, do you want to overwrite it? Copy info == Infó Másolása -Online players (%d) -== - -Online clanmates (%d) -== - -[friends (server browser)] -Offline (%d) -== - -Click to select server. Double click to join your friend. -== - -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? == Biztos, hogy ki szeretnéd törölni '%s' Játékost a barátlistádból? Are you sure that you want to remove the clan '%s' from your friends list? == Biztos, hogy ki szeretnéd törölni '%s' Klánt a barátlistádból? -Add Clan -== - Play the current demo == Jelenlegi Demó Lejátszása @@ -1621,12 +1565,6 @@ Slow down the demo Speed up the demo == Demó Felgyorsítása -Mark the beginning of a cut -== Jelöld be a vágás elejét - -Mark the end of a cut -== Jelöld be a vágás végét - Export cut as a separate demo == Exportáld a vágást, mint külön Demó @@ -1713,3 +1651,58 @@ Unregister protocol and file extensions Open the directory to add custom assets == Megnyitni az egyedi Képek helyét + +A demo with this name already exists +== + +No server selected +== + +Online players (%d) +== + +Online clanmates (%d) +== + +[friends (server browser)] +Offline (%d) +== + +Click to select server. Double click to join your friend. +== + +Click to remove this player from your friends list. +== + +Click to remove this clan from your friends list. +== + +None +== + +Add Clan +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/data/languages/index.txt b/data/languages/index.txt index 67b6619d125..59fff2a4cba 100644 --- a/data/languages/index.txt +++ b/data/languages/index.txt @@ -70,6 +70,11 @@ french == 250 == fr +galician +== Galego +== 907 +== gl + german == Deutsch == 276 diff --git a/data/languages/italian.txt b/data/languages/italian.txt index 8cf495c8990..63f6e065bf8 100644 --- a/data/languages/italian.txt +++ b/data/languages/italian.txt @@ -81,12 +81,6 @@ Clan Client == Client -Close -== Chiudi - -Connect -== Connetti - Connecting to == Connessione a @@ -763,9 +757,6 @@ Countries Types == Tipi -Select a name -== Seleziona un nome - Please use a different name == Per favore usa un nome differente @@ -1009,8 +1000,8 @@ Client message Save the best demo of each race == Salva la miglior demo di ogni partita -Default length: %d -== Lunghezza predefinita: %d +Default length +== Lunghezza predefinita Enable replays == Abilita i replay @@ -1054,9 +1045,6 @@ Show other players' hook collision lines Show other players' key presses == Mostra i tasti degli altri giocatori -Old mouse mode -== Modalità mouse vecchio - Use current map as background == Usa la mappa attuale come sfondo @@ -1283,7 +1271,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1322,9 +1310,6 @@ CHN Getting server list from master server == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1334,6 +1319,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1386,10 +1374,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1401,9 +1389,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1476,10 +1476,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1490,9 +1490,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1505,7 +1502,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1514,9 +1511,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1526,15 +1520,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1562,7 +1556,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/japanese.txt b/data/languages/japanese.txt index 5783830fb74..64dcd37372f 100644 --- a/data/languages/japanese.txt +++ b/data/languages/japanese.txt @@ -76,12 +76,6 @@ Clan Client == クライアント -Close -== 閉じる - -Connect -== 接続 - Connecting to == 接続先 @@ -721,9 +715,6 @@ Types Leak IP == IP を漏洩する -Select a name -== 名前をつけて保存 - Please use a different name == 別の名前をつけてください @@ -1003,8 +994,8 @@ Preview Save the best demo of each race == レースの最高記録のデモを保存 -Default length: %d -== デフォルト長さ:%D +Default length +== デフォルト長さ Enable replays == リプレイ機能 @@ -1051,9 +1042,6 @@ Show other players' hook collision lines Show other players' key presses == 他のプレーヤーの押したキーを表示 -Old mouse mode -== 古いマウスモード - Background == 背景 @@ -1303,7 +1291,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1339,15 +1327,15 @@ SA CHN == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == Copy info == +No server selected +== + Online players (%d) == @@ -1400,10 +1388,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1415,9 +1403,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1475,10 +1475,10 @@ Open the directory to add custom skins Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1489,9 +1489,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1504,7 +1501,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1513,9 +1510,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1525,15 +1519,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Windowed fullscreen == @@ -1549,7 +1543,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/korean.txt b/data/languages/korean.txt index aaca2af0b44..1a61c272388 100644 --- a/data/languages/korean.txt +++ b/data/languages/korean.txt @@ -88,12 +88,6 @@ Clan Client == 클라이언트 -Close -== 닫기 - -Connect -== 연결 - Connecting to == 연결 중 @@ -721,9 +715,6 @@ Exclude %d player == %d 플레이어 -Refreshing... -== 새로고침 중... - Filter connecting players == 접속 중인 플레이어 제외 @@ -742,9 +733,6 @@ Types Leak IP == IP 노출하기 -Select a name -== 다른 이름으로 저장 - Please use a different name == 다른 이름을 사용해 주십시오 @@ -1030,8 +1018,8 @@ Preview Save the best demo of each race == 레이스 최고 기록의 데모를 저장 -Default length: %d -== 기본 길이: %d +Default length +== 기본 길이 Enable replays == 리플레이 활성화 @@ -1081,9 +1069,6 @@ Show other players' key presses Show local player's key presses == 주변 플레이어의 키 입력 표시 -Old mouse mode -== 구식 마우스 모드 - Background == 배경 @@ -1280,9 +1265,6 @@ default custom == 커스텀 -Graphics cards -== 그래픽 카드 - auto == 자동 @@ -1349,13 +1331,6 @@ Download community skins Enable controller == 컨트롤러 활성화 -Controller %d: %s -== 컨트롤러 %d: %s - -Click to cycle through all available controllers. -== 사용가능한 컨트롤러로 전환하려면 클릭하십시오. - -[Ingame controller mode] Relative == 상대적 @@ -1375,18 +1350,12 @@ UI controller sens. Controller jitter tolerance == 컨트롤러 신호 오차 허용 범위 -Device -== 장치 - Status == 상태 Aim bind == 조준 바인드 -Controller Axis #%d -== 컨트롤러 축 - Mouse == 마우스 @@ -1546,12 +1515,6 @@ Slow down the demo Speed up the demo == 데모 속도 높이기 -Mark the beginning of a cut -== 영상 자르기의 시작 부분 - -Mark the end of a cut -== 영상 자르기의 끝 부분 - Export cut as a separate demo == 자른 부분을 다른 이름으로 저장 @@ -1701,6 +1664,12 @@ Copy info Create a random skin == 무작위 스킨 생성 +A demo with this name already exists +== + +No server selected +== + Online players (%d) == @@ -1725,3 +1694,27 @@ None Add Clan == + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/data/languages/kyrgyz.txt b/data/languages/kyrgyz.txt index 02b10ba619a..b355047095c 100644 --- a/data/languages/kyrgyz.txt +++ b/data/languages/kyrgyz.txt @@ -72,12 +72,6 @@ Clan Client == Клиент -Close -== Чыгуу - -Connect -== Туташуу - Connecting to == Туташтырылууда @@ -750,7 +744,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -825,9 +819,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -852,6 +843,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -883,18 +877,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -916,10 +898,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -931,9 +913,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1153,10 +1156,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1167,9 +1170,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1182,7 +1182,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1191,9 +1191,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1203,15 +1200,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1227,10 +1224,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1248,7 +1245,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1473,7 +1470,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1491,10 +1488,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1527,9 +1524,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/norwegian.txt b/data/languages/norwegian.txt index 89ce646d482..241125bc4eb 100644 --- a/data/languages/norwegian.txt +++ b/data/languages/norwegian.txt @@ -78,12 +78,6 @@ Clan Client == Klient -Close -== Lukk - -Connect -== Koble til - Connecting to == Kobler til @@ -696,9 +690,6 @@ Update now Restart == Restart -Select a name -== Velg et navn - Please use a different name == Vennligst velg et annet navn @@ -921,8 +912,8 @@ Normal message Save the best demo of each race == Lagre beste demo for hvert løp -Default length: %d -== Standard lengde: %d +Default length +== Standard lengde Enable replays == Aktiver opptak @@ -969,9 +960,6 @@ Show other players' hook collision lines Show other players' key presses == Vis andre spilleres tastetrykk -Old mouse mode -== Gammel musemodus - Show tiles layers from BG map == Vis tile-lag fra BG-bane @@ -1266,7 +1254,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1305,9 +1293,6 @@ CHN Getting server list from master server == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1317,6 +1302,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1369,10 +1357,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1384,9 +1372,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1447,10 +1447,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1461,9 +1461,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1476,7 +1473,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1485,9 +1482,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1497,15 +1491,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1533,7 +1527,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/persian.txt b/data/languages/persian.txt index 5c6d77df85b..e625eb6d057 100644 --- a/data/languages/persian.txt +++ b/data/languages/persian.txt @@ -76,12 +76,6 @@ Clan Client == ﺖﻨﯾﻼﻛ -Close -== ﻦﺘﺴﺑ - -Connect -== ﻝﺎﺼﺗﺍ - Connecting to == ﻪﺑ ﻥﺪﺷ ﻞﺻﻭ @@ -478,9 +472,6 @@ Server address: Refresh == ﯼﺯﺎﺳ ﻩﺯﺎﺗ -Refreshing... -== ... ﯼﺯﺎﺳ ﻩﺯﺎﺗ ﻝﺎﺣ ﺭﺩ - Server filter == ﺭﻭﺮﺳ ﺮﺘﻠﯿﻓ @@ -526,9 +517,6 @@ Remove Info == ﺕﺎﻋﻼﻃﺍ -Select a name -== ﻡﺎﻧ ﮏﯾ ﺏﺎﺨﺘﻧﺍ - Please use a different name == ﻦﮐ ﻩﺩﺎﻔﺘﺳﺍ ﺕﻭﺎﻔﺘﻣ ﻡﺎﻧ ﮏﯾ ﺯﺍ ﺎﻔﻄﻟ @@ -1052,12 +1040,6 @@ Slow down the demo Speed up the demo == ﻮﻣﺩ ﺖﻋﺮﺳ ﻥﺩﺮﮐ ﺩﺎﯾﺯ -Mark the beginning of a cut -== ﺵﺮﺑ ﻉﻭﺮﺷ ﻥﺩﺯ ﺖﻣﻼﻋ - -Mark the end of a cut -== ﺵﺮﺑ ﻥﺎﯾﺎﭘ ﻥﺩﺯ ﺖﻣﻼﻋ - Export cut as a separate demo == ﻪﻧﺎﮔﺍﺪﺟ ﻮﻣﺩ ﻥﺍﻮﻨﻋ ﻪﺑ ﺵﺮﺑ ﻥﺩﺮﮐ ﺝﺭﺎﺧ @@ -1184,10 +1166,6 @@ Statboard Enable controller == ﻦﮐ ﻝﺎﻌﻓ ﺍﺭ ﺮﻟﺮﺘﻨﮐ -Click to cycle through all available controllers. -== ﺪﯿﻨﮐ ﮏﯿﻠﮐ ﺩﻮﺟﻮﻣ ﯼﺎﻫﺮﻟﺮﺘﻨﮐ ﻡﺎﻤﺗ ﺭﺩ ﺶﺧﺮﭼ ﯼﺍﺮﺑ - -[Ingame controller mode] Relative == ﯽﺒﺴﻧ [ﯼﺯﺎﺑ ﺮﻟﺮﺘﻨﮐ ﺖﻟﺎﺣ] @@ -1210,9 +1188,6 @@ Controller jitter tolerance No controller found. Plug in a controller. == ﺪﯿﻨﮐ ﻞﺻﻭ ﺮﻟﺮﺘﻨﮐ ﮏﯾ .ﺪﺸﻧ ﺍﺪﯿﭘ ﯼﺮﻟﺮﺘﻨﮐ ﭻﯿﻫ -Device -== ﻩﺎﮕﺘﺳﺩ - Status == ﺖﯿﻌﺿﻭ @@ -1273,9 +1248,6 @@ default custom == ﯽﺷﺭﺎﻔﺳ -Graphics cards -== ﮏﯿﻓﺍﺮﮔ ﯼﺎﻫ ﺕﺭﺎﮐ - auto == ﺭﺎﮐﺩﻮﺧ @@ -1477,8 +1449,8 @@ Set all to Rifle Save the best demo of each race == ﻪﻘﺑﺎﺴﻣ ﺮﻫ ﻮﻣﺩ ﻦﯾﺮﺘﻬﺑ ﻩﺮﯿﺧﺫ -Default length: %d -== %d :ﺽﺮﻓ‌ﺶﯿﭘ ﻝﻮﻃ +Default length +== ﺽﺮﻓ‌ﺶﯿﭘ ﻝﻮﻃ When you cross the start line, show a ghost tee replicating the movements of your best time == ﺪﻨﮐ ﯽﻣ ﺭﺍﺮﮑﺗ ﺍﺭ ﺎﻤﺷ ﻥﺎﻣﺯ ﻦﯾﺮﺘﻬﺑ ﺕﺎﮐﺮﺣ ﻪﮐ ﻩﺪﺑ ﺶﯾﺎﻤﻧ ﺍﺭ ﺡﻭﺭ ،ﺪﯿﻨﮐ ﯽﻣ ﺭﻮﺒﻋ ﻉﻭﺮﺷ ﻂﺧ ﺯﺍ ﻪﮐ ﯽﻣﺎﮕﻨﻫ @@ -1522,9 +1494,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == ﺎﻫ ﮏﺠﻧﺭﺎﻧ ﺮﯿﺴﻣ ﯽﻨﯿﺑ‌ﺶﯿﭘ AntiPing: -Old mouse mode -== ﯽﻤﯾﺪﻗ ﺱﻮﻣ ﺖﻟﺎﺣ - Entities Background color == ﺎﻫ ﺖﯾﺩﻮﺟﻮﻣ ﻪﻨﯿﻣﺯﺲﭘ ﮓﻧﺭ @@ -1662,9 +1631,15 @@ The format of texture %s is not RGBA which will cause visual bugs. Initializing map logic == +A demo with this name already exists +== + Copy info == +No server selected +== + Online players (%d) == @@ -1690,13 +1665,31 @@ None Add Clan == +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + Create a random skin == -Controller %d: %s +Axis == -Controller Axis #%d +Graphics card == Unregister protocol and file extensions diff --git a/data/languages/polish.txt b/data/languages/polish.txt index ae96e759e87..4d561e1afe4 100644 --- a/data/languages/polish.txt +++ b/data/languages/polish.txt @@ -80,12 +80,6 @@ Clan Client == Klient -Close -== Zamknij - -Connect -== Połącz - Connecting to == Łączenie z @@ -650,9 +644,6 @@ Default zoom Overlay entities == Pokazuj entities -Select a name -== Wybierz nazwę - Switch weapon when out of ammo == Zmień broń, gdy skończy się amunicja @@ -893,9 +884,6 @@ Toggle ghost Replace video == Zamień wideo -Old mouse mode -== Tryb starej myszki - Lock team == Zablokuj drużynę @@ -947,8 +935,8 @@ Markers: %d new mentions == %d razy wspomniano o tobie -Default length: %d -== Domyślna długość: %d +Default length +== Domyślna długość Refresh Rate == Częstotliwość próbkowania @@ -1203,9 +1191,6 @@ CHN Getting server list from master server == Pobieranie listy serwerów z serwera głównego -Refreshing... -== Odświeżanie - Leak IP == Ujawnij IP @@ -1251,9 +1236,6 @@ default custom == niestandardowy -Graphics cards -== Karty graficzne - auto == automatyczny @@ -1409,7 +1391,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1421,6 +1403,9 @@ Loading menu images Copy info == +No server selected +== + Online players (%d) == @@ -1473,10 +1458,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1488,9 +1473,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1536,10 +1533,10 @@ Open the directory to add custom skins Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1550,9 +1547,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1565,7 +1559,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1574,9 +1568,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1586,15 +1577,18 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + +Graphics card +== + Appearance == diff --git a/data/languages/portuguese.txt b/data/languages/portuguese.txt index 3ef2ba904ea..56cb5774179 100644 --- a/data/languages/portuguese.txt +++ b/data/languages/portuguese.txt @@ -80,12 +80,6 @@ Clan Client == Cliente -Close -== Fechar - -Connect -== Ligar - Connecting to == A ligar ao endereço @@ -639,9 +633,6 @@ HUD Show names in chat in team colors == Mostrar nomes no chat com cores da equipa -Select a name -== Escolha um nome - Enable team chat sound == Ativar o som do chat de equipa @@ -666,9 +657,6 @@ Gameplay Restart == Reiniciar -Old mouse mode -== Modo de rato antigo - Browser == Navegador @@ -855,9 +843,6 @@ Theme %d player == %d jogador -Refreshing... -== A atualizar... - Date == Data @@ -1078,7 +1063,7 @@ DDraceNetwork is a cooperative online game where the goal is for you and your gr Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1138,6 +1123,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1190,10 +1178,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1205,9 +1193,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1358,10 +1358,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1372,9 +1372,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1387,7 +1384,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1396,9 +1393,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1408,15 +1402,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Windowed fullscreen == @@ -1438,7 +1432,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1579,7 +1573,7 @@ Set all to Rifle Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time diff --git a/data/languages/romanian.txt b/data/languages/romanian.txt index d150a2d8647..7461085f310 100644 --- a/data/languages/romanian.txt +++ b/data/languages/romanian.txt @@ -82,12 +82,6 @@ Clan Client == Clientul -Close -== Închide - -Connect -== Conectează - Connecting to == Conectare la @@ -765,7 +759,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -840,9 +834,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -867,6 +858,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -898,18 +892,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -931,10 +913,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -946,9 +928,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1168,10 +1171,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1182,9 +1185,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1197,7 +1197,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1206,9 +1206,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1218,15 +1215,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1242,10 +1239,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1263,7 +1260,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1488,7 +1485,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1506,10 +1503,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1542,9 +1539,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/russian.txt b/data/languages/russian.txt index 5a13e063c8f..149824672ca 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -89,12 +89,6 @@ Clan Client == Клиент -Close -== Закрыть - -Connect -== Подключиться - Connecting to == Подключение к @@ -666,9 +660,6 @@ Show ghost No updates available == Нет доступных обновлений -Select a name -== Выберите имя - Show other players' hook collision lines == Показывать коллизии крюка других игроков @@ -684,9 +675,6 @@ Show others Game paused == Пауза -Old mouse mode -== Режим старой мыши - Team message == Сообщение от команды @@ -942,8 +930,8 @@ Friend message Save the best demo of each race == Сохранять лучшее демо каждой карты -Default length: %d -== Станд. длина: %d +Default length +== Станд. длина Enable replays == Включить записи @@ -1024,13 +1012,13 @@ Grabs == Захватов 1 new mention -== 1 новое упоминание +== 1 упоминание %d new mentions -== %d новых упоминаний +== %d упоминаний 9+ new mentions -== 9+ новых упоминаний +== 9+ упоминаний Client message == Клиентское сообщение @@ -1237,9 +1225,6 @@ Desktop fullscreen Are you sure that you want to disconnect and switch to a different server? == Вы уверены, что хотите отключиться и перейти на другой сервер? -Refreshing... -== Обновление... - Kill Messages == Сообщения о смерти @@ -1273,9 +1258,6 @@ default custom == перс. -Graphics cards -== Видеокарты - auto == авто @@ -1324,13 +1306,6 @@ Choose default eyes when joining a server Enable controller == Включить контроллер -Controller %d: %s -== Контроллер %d: %s - -Click to cycle through all available controllers. -== Нажмите для переключения между всеми доступными контроллерами. - -[Ingame controller mode] Relative == Относительный @@ -1350,18 +1325,12 @@ UI controller sens. Controller jitter tolerance == Устойчивость к движению контроллера -Device -== Устройство - Status == Состояние Aim bind == Привязка оси -Controller Axis #%d -== Ось контроллера #%d - Mouse == Мышь @@ -1545,14 +1514,8 @@ Slow down the demo Speed up the demo == Ускорить демо -Mark the beginning of a cut -== Отметить начало участка - -Mark the end of a cut -== Отметить конец участка - Export cut as a separate demo -== Экспортировать участок отдельно +== Экспортировать отрезок отдельно Toggle keyboard shortcuts == Включить горячие клавиши @@ -1722,3 +1685,33 @@ None Add Clan == Добавить клан + +A demo with this name already exists +== Демо с таким именем уже существует + +No server selected +== Не выбран сервер + +Mark the beginning of a cut (right click to reset) +== Отметить начало отрезка (щелкните правой кнопкой мыши, чтобы сбросить) + +Mark the end of a cut (right click to reset) +== Отметить конец отрезка (щелкните правой кнопкой мыши, чтобы сбросить) + +Close the demo player +== Закрыть проигрыватель демок + +Export demo cut +== Экспортировать отрезок демки + +Cut interval +== Интервал отрезка + +Cut length +== Длина отрезка + +Axis +== Ось + +Graphics card +== Видеокарта diff --git a/data/languages/serbian.txt b/data/languages/serbian.txt index 41a777e2903..45b5d5a2efc 100644 --- a/data/languages/serbian.txt +++ b/data/languages/serbian.txt @@ -80,12 +80,6 @@ Clan Client == Klijent -Close -== Zatvori - -Connect -== Poveži se - Connecting to == Povezujem se na @@ -721,9 +715,6 @@ Countries Types == Tipovi -Select a name -== Izaberite ime - Please use a different name == Izaberite drugo ime @@ -956,8 +947,8 @@ Client message Save the best demo of each race == Sačuvajte najbolji snimak svake trke -Default length: %d -== Podrazumevana dužina: %d +Default length +== Podrazumevana dužina Enable replays == Omogući ponovljene reprodukcije @@ -1001,9 +992,6 @@ Show other players' hook collision lines Show other players' key presses == Prikaži pritiske tastera drugih igrača -Old mouse mode -== Stari mod miša - Show tiles layers from BG map == Prikaži slojeve pločica sa BG mape @@ -1236,9 +1224,6 @@ CHN Getting server list from master server == Dobijam listu servera sa master servera -Refreshing... -== Osvežavanje - Leak IP == Provali adresu @@ -1275,13 +1260,6 @@ Chat command Enable controller == Uključi kontroler -Controller %d: %s -== Kontroler %d: %s - -Click to cycle through all available controllers. -== Klikni da vidiš sve slobodne kontroleres - -[Ingame controller mode] Relative == Relativno @@ -1301,18 +1279,12 @@ UI controller sens. Controller jitter tolerance == Tolerancija kontrolera -Device -== Uređaj - Status == Status Aim bind == Nišan -Controller Axis #%d -== Stik %d. - Mouse == Miš @@ -1352,9 +1324,6 @@ default custom == svoje -Graphics cards -== Grafička kartica - auto == auto @@ -1561,7 +1530,7 @@ Could not save downloaded map. Try manually deleting this file: %s Initializing components == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1573,6 +1542,9 @@ transmits your player name to info.ddnet.org Copy info == +No server selected +== + Online players (%d) == @@ -1625,10 +1597,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1640,9 +1612,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Open the directory that contains the demo files == @@ -1676,12 +1660,21 @@ Open the directory to add custom skins No controller found. Plug in a controller. == +Axis +== + Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + +Graphics card +== + Weapons == diff --git a/data/languages/serbian_cyrillic.txt b/data/languages/serbian_cyrillic.txt index bd687b6529c..798d5786799 100644 --- a/data/languages/serbian_cyrillic.txt +++ b/data/languages/serbian_cyrillic.txt @@ -76,12 +76,6 @@ Clan Client == Клијент -Close -== Затвори - -Connect -== Повежи се - Connecting to == Повезујем се на @@ -720,9 +714,6 @@ Countries Types == Типови -Select a name -== Изаберите име - Please use a different name == Изаберите друго име @@ -955,8 +946,8 @@ Client message Save the best demo of each race == Сачувајте најбољи снимак сваке трке -Default length: %d -== Подразумевана дужина: %d +Default length +== Подразумевана дужина Enable replays == Омогући поновљене репродукције @@ -1000,9 +991,6 @@ Show other players' hook collision lines Show other players' key presses == Прикажи притиске тастера других играча -Old mouse mode -== Стари мод миша - Show tiles layers from BG map == Прикажи слојеве плочица са BG мапе @@ -1235,9 +1223,6 @@ CHN Getting server list from master server == Добијам листу сервера са мастер сервера -Refreshing... -== Освежавање - Leak IP == Провали адресу @@ -1274,13 +1259,6 @@ Chat command Enable controller == Укључи контролер -Controller %d: %s -== Контролер %d: %s - -Click to cycle through all available controllers. -== Кликни да видиш све слободне контролерес - -[Ingame controller mode] Relative == Релативно @@ -1300,18 +1278,12 @@ UI controller sens. Controller jitter tolerance == Толеранција контролера -Device -== Уређај - Status == Статус Aim bind == Нишан -Controller Axis #%d -== Стик %d. - Mouse == Миш @@ -1351,9 +1323,6 @@ default custom == своје -Graphics cards -== Графичка картица - auto == ауто @@ -1560,7 +1529,7 @@ Could not save downloaded map. Try manually deleting this file: %s Initializing components == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1569,6 +1538,9 @@ File '%s' already exists, do you want to overwrite it? Copy info == +No server selected +== + Online players (%d) == @@ -1621,10 +1593,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1636,9 +1608,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Open the directory that contains the demo files == @@ -1672,12 +1656,21 @@ Open the directory to add custom skins No controller found. Plug in a controller. == +Axis +== + Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + +Graphics card +== + Weapons == diff --git a/data/languages/simplified_chinese.txt b/data/languages/simplified_chinese.txt index 5376d9bad8a..3e4e368aba6 100644 --- a/data/languages/simplified_chinese.txt +++ b/data/languages/simplified_chinese.txt @@ -106,12 +106,6 @@ Clan Client == 客户端 -Close -== 关闭 - -Connect -== 连接 - Connecting to == 正在连接到 @@ -805,9 +799,6 @@ Grabs DDNet == DDNet -Select a name -== 另存为 - Deaths == 死亡数 @@ -817,9 +808,6 @@ Please use a different name Restart == 重新开始 -Old mouse mode -== 旧版鼠标模式 - Follow == 跟随 @@ -1004,8 +992,8 @@ Show HUD Hammerfly dummy == 分身 Hammerfly 开关 -Default length: %d -== 默认时长: %d +Default length +== 默认时长 Toggle ghost == 影子记录开关 @@ -1260,9 +1248,6 @@ Desktop fullscreen Are you sure that you want to disconnect and switch to a different server? == 你确定要断开此服务器连接并进入其他服务器吗? -Refreshing... -== 正在刷新... - Kill Messages == 击杀消息 @@ -1302,9 +1287,6 @@ default custom == 自定义 -Graphics cards -== 显卡 - auto == 自动 @@ -1371,13 +1353,6 @@ Download community skins Enable controller == 启用控制器 -Controller %d: %s -== 控制器 %d: %s - -Click to cycle through all available controllers. -== 点击以切换其他控制器 - -[Ingame controller mode] Relative == 相对 @@ -1397,18 +1372,12 @@ UI controller sens. Controller jitter tolerance == 摇杆死区 -Device -== 输入设备 - Status == 状态 Aim bind == 操作轴绑定 -Controller Axis #%d -== 摇杆 #%d - Mouse == 鼠标 @@ -1568,12 +1537,6 @@ Slow down the demo Speed up the demo == 加快播放速度 -Mark the beginning of a cut -== 标记裁剪起点 - -Mark the end of a cut -== 标记裁剪终点 - Export cut as a separate demo == 另存为新回放文件 @@ -1747,3 +1710,33 @@ None Add Clan == 添加战队 + +A demo with this name already exists +== + +No server selected +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/data/languages/slovak.txt b/data/languages/slovak.txt index 37ba68ed036..5afdba76a9f 100644 --- a/data/languages/slovak.txt +++ b/data/languages/slovak.txt @@ -76,12 +76,6 @@ Clan Client == Klient -Close -== Zavrieť - -Connect -== Pripojiť - Connecting to == Pripojujem sa k @@ -756,7 +750,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -831,9 +825,6 @@ Exclude %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -858,6 +849,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -889,18 +883,6 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == -Select a name -== - -Please use a different name -== - -File already exists, do you want to overwrite it? -== - -Remove chat -== - Play the current demo == @@ -922,10 +904,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -937,9 +919,30 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + +Remove chat +== + +Please use a different name +== + +File already exists, do you want to overwrite it? +== + Loading demo files == @@ -1159,10 +1162,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1173,9 +1176,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1188,7 +1188,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1197,9 +1197,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1209,15 +1206,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1233,10 +1230,10 @@ Windowed fullscreen Desktop fullscreen == -may cause delay +Screen == -Screen +may cause delay == Allows maps to render with more detail @@ -1254,7 +1251,7 @@ default custom == -Graphics cards +Graphics card == auto @@ -1482,7 +1479,7 @@ Save the best demo of each race Enable replays == -Default length: %d +Default length == When you cross the start line, show a ghost tee replicating the movements of your best time @@ -1500,10 +1497,10 @@ Gameplay Overlay entities == -Size +Show text entities == -Show text entities +Size == Opacity @@ -1536,9 +1533,6 @@ AntiPing: predict weapons AntiPing: predict grenade paths == -Old mouse mode -== - Background == diff --git a/data/languages/spanish.txt b/data/languages/spanish.txt index b8b13715d0c..a191ba6f7c2 100644 --- a/data/languages/spanish.txt +++ b/data/languages/spanish.txt @@ -21,6 +21,7 @@ # Deëivid! 2023-01-05 21:05:00 # Deëivid! 2023-02-26 00:35:00 # Deëivid! 2023-05-22 19:54:00 +# Deëivid! 2023-07-01 22:52:00 ##### /authors ##### ##### translated strings ##### @@ -94,12 +95,6 @@ Clan Client == Cliente -Close -== Cerrar - -Connect -== Conectar - Connecting to == Conectando con @@ -715,9 +710,6 @@ Update now Restart == Reiniciar -Select a name -== Selecciona un nombre - Please use a different name == Por favor usa un nombre diferente @@ -937,8 +929,8 @@ Normal message Save the best demo of each race == Guardar la mejor demo de cada carrera -Default length: %d -== Longitud predeterminada: %d +Default length +== Longitud predeterminada Enable replays == Habilitar repeticiones @@ -985,9 +977,6 @@ Show other players' hook collision lines Show other players' key presses == Mostrar las pulsaciones de teclas de otros jugadores -Old mouse mode -== Modo de mouse viejo - Show tiles layers from BG map == Mostrar capas de tiles del mapa de fondo @@ -1244,9 +1233,6 @@ https://ddnet.org/discord Are you sure that you want to disconnect and switch to a different server? == ¿Seguro que quieres desconectarte y cambiar de servidor? -Refreshing... -== Actualizando... - Kill Messages == Mensajes de Muerte @@ -1286,9 +1272,6 @@ default custom == personalizado -Graphics cards -== Tarjetas de video - auto == auto. @@ -1352,13 +1335,6 @@ Download community skins Enable controller == Habilitar mando -Controller %d: %s -== Mando %d: %s - -Click to cycle through all available controllers. -== Click acá para recorrer todos los mandos disponibles. - -[Ingame controller mode] Relative == Relativo @@ -1378,18 +1354,12 @@ UI controller sens. Controller jitter tolerance == Tolerancia a la fluctuación del mando -Device -== Dispositivo - Status == Estado Aim bind == Apuntar -Controller Axis #%d -== Eje #%d del mando - Mouse == Ratón @@ -1483,12 +1453,6 @@ Slow down the demo Speed up the demo == Acelerar la demo -Mark the beginning of a cut -== Marca el inicio de un corte - -Mark the end of a cut -== Marca el final de un corte - Export cut as a separate demo == Exportar corte como una demo diferente @@ -1731,3 +1695,33 @@ None Add Clan == Agregar clan + +A demo with this name already exists +== Ya existe una demo con este nombre + +No server selected +== Ningún servidor seleccionado + +Mark the beginning of a cut (right click to reset) +== Marcar el inicio de un corte (clic derecho para restablecer) + +Mark the end of a cut (right click to reset) +== Marcar el final de un corte (clic derecho para restablecer) + +Close the demo player +== Cerrar el reproductor de demos + +Export demo cut +== Exportar corte de la demo + +Cut interval +== Intervalo del corte + +Cut length +== Largo del corte + +Axis +== Eje + +Graphics card +== Tarjeta gráfica diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt index fd6db6bc640..9b2e4fccda9 100644 --- a/data/languages/swedish.txt +++ b/data/languages/swedish.txt @@ -80,12 +80,6 @@ Clan Client == Klient -Close -== Stäng - -Connect -== Anslut - Connecting to == Ansluter till @@ -594,9 +588,6 @@ Vanilla skins only New random timeout code == Ny slumpad timeout kod -Select a name -== Välj ett namn - Enable long pain sound (used when shooting in freeze) == Aktivera ett långt ont ljud (använt vid sjutning i freeze) @@ -660,8 +651,8 @@ Save ghost Browser == Bläddraren -Default length: %d -== Standard längd: %d +Default length +== Standard längd Switch weapon when out of ammo == Byt vapen vid slut av ammunition @@ -969,9 +960,6 @@ Automatically take statboard screenshot Automatically create statboard csv == Automatiskt skapa poänglista csv -Old mouse mode -== Gammal mus metod - Enable regular chat sound == Aktivera vanligt chatt ljud @@ -1147,9 +1135,6 @@ CHN Getting server list from master server == Hämtar server lista från master server -Refreshing... -== Ladder om... - Leak IP == Läck IP @@ -1353,7 +1338,7 @@ Getting game info Requesting to join the game == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1371,6 +1356,9 @@ Loading menu images Copy info == +No server selected +== + Online players (%d) == @@ -1423,10 +1411,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1438,9 +1426,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1492,10 +1492,10 @@ Open the directory to add custom skins Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1506,9 +1506,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1521,7 +1518,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1530,9 +1527,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1542,15 +1536,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Allows maps to render with more detail == @@ -1563,7 +1557,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/traditional_chinese.txt b/data/languages/traditional_chinese.txt index 012d155523d..c95ed8cb0b1 100644 --- a/data/languages/traditional_chinese.txt +++ b/data/languages/traditional_chinese.txt @@ -95,12 +95,6 @@ Clan Client == 客戶端 -Close -== 關閉 - -Connect -== 連線 - Connecting to == 正在連線到 @@ -290,9 +284,6 @@ Enable team chat sound Enable highlighted chat sound == 啟用被提及時的提示音 -Threaded sound loading -== 啟用多執行緒音訊載入 - Name == 名稱 @@ -396,9 +387,6 @@ Rename demo Reset filter == 重置過濾器 -Sample rate -== 取樣率 - Score == 分數 @@ -800,9 +788,6 @@ Grabs DDNet == DDNet -Select a name -== 另存為 - Deaths == 死亡數 @@ -812,9 +797,6 @@ Please use a different name Restart == 重新開始 -Old mouse mode -== 舊版滑鼠模式 - Follow == 跟隨 @@ -999,8 +981,8 @@ Show HUD Hammerfly dummy == 分身Hammerfly開關 -Default length: %d -== 預設長度: %d +Default length +== 預設長度 Toggle ghost == 影子記錄開關 @@ -1255,9 +1237,6 @@ https://ddnet.org/discord Are you sure that you want to disconnect and switch to a different server? == 你確定要中斷此伺服器并嘗試加入其他伺服器嗎? -Refreshing... -== 正在重整... - Kill Messages == 擊殺訊息 @@ -1297,9 +1276,6 @@ default custom == 自定義 -Graphics cards -== 顯示卡 - auto == 自動 @@ -1366,13 +1342,6 @@ Download community skins Enable controller == 啓用控制器 -Controller %d: %s -== 控制器 %d: %s - -Click to cycle through all available controllers. -== 點擊以切換其他控制器 - -[Ingame controller mode] Relative == 相對 @@ -1392,18 +1361,12 @@ UI controller sens. Controller jitter tolerance == 搖桿錯位容錯 -Device -== 輸入設備 - Status == 狀態 Aim bind == 操作軸綁定 -Controller Axis #%d -== 搖桿 #%d - Mouse == 滑鼠 @@ -1563,12 +1526,6 @@ Slow down the demo Speed up the demo == 加快播放速度 -Mark the beginning of a cut -== 標記裁剪起點 - -Mark the end of a cut -== 標記裁剪終點 - Export cut as a separate demo == 另存爲新回放檔案 @@ -1742,3 +1699,33 @@ None Add Clan == 新增戰隊 + +A demo with this name already exists +== + +No server selected +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/data/languages/turkish.txt b/data/languages/turkish.txt index dd1271381eb..c5b790f775d 100644 --- a/data/languages/turkish.txt +++ b/data/languages/turkish.txt @@ -81,12 +81,6 @@ Clan Client == İstemci -Close -== Kapat - -Connect -== Bağlan - Connecting to == Bağlanılıyor @@ -702,9 +696,6 @@ Update now Restart == Yeniden başlat -Select a name -== Takma ad seç - Please use a different name == Lütfen farklı takma ad kullanın @@ -930,8 +921,8 @@ Client message Save the best demo of each race == Her yarış için en iyi demoyu kaydet -Default length: %d -== Uzunluk: %d +Default length +== Uzunluk Enable replays == Tekrar oynatmaları etkinleştir @@ -978,9 +969,6 @@ Show other players' hook collision lines Show other players' key presses == Diğer oyuncuların tuş basışlarını göster -Old mouse mode -== Eski mouse modu - Show tiles layers from BG map == Yapı katmanlarını göster @@ -1178,7 +1166,7 @@ Your nickname '%s' is already used (%d points). Do you still want to use it? Checking for existing player with your name == -Cancel +A demo with this name already exists == File '%s' already exists, do you want to overwrite it? @@ -1235,9 +1223,6 @@ Getting server list from master server %d player == -Refreshing... -== - Are you sure that you want to disconnect and switch to a different server? == @@ -1247,6 +1232,9 @@ Copy info Leak IP == +No server selected +== + Online players (%d) == @@ -1299,10 +1287,10 @@ Slow down the demo Speed up the demo == -Mark the beginning of a cut +Mark the beginning of a cut (right click to reset) == -Mark the end of a cut +Mark the end of a cut (right click to reset) == Export cut as a separate demo @@ -1314,9 +1302,21 @@ Go back one marker Go forward one marker == +Close the demo player +== + Toggle keyboard shortcuts == +Export demo cut +== + +Cut interval +== + +Cut length +== + Loading demo files == @@ -1398,10 +1398,10 @@ Chat command Enable controller == -Controller %d: %s +Controller == -Click to cycle through all available controllers. +Ingame controller mode == [Ingame controller mode] @@ -1412,9 +1412,6 @@ Relative Absolute == -Ingame controller mode -== - Ingame controller sens. == @@ -1427,7 +1424,7 @@ Controller jitter tolerance No controller found. Plug in a controller. == -Device +Axis == Status @@ -1436,9 +1433,6 @@ Status Aim bind == -Controller Axis #%d -== - Mouse == @@ -1448,15 +1442,15 @@ Ingame mouse sens. UI mouse sens. == -Controller -== - Reset controls == Are you sure that you want to reset the controls to their defaults? == +Cancel +== + Dummy == @@ -1484,7 +1478,7 @@ default custom == -Graphics cards +Graphics card == auto diff --git a/data/languages/ukrainian.txt b/data/languages/ukrainian.txt index 5c6a48b1f8d..90bacdb211f 100644 --- a/data/languages/ukrainian.txt +++ b/data/languages/ukrainian.txt @@ -37,12 +37,6 @@ Call vote Chat == Чат -Close -== Зачинити - -Connect -== Під’єднатись - Connecting to == Підключення до @@ -182,7 +176,7 @@ Movement == Переміщення Mute when not active -== Глушити звуки, коли гра неактивна +== Відключити звуки, коли гра неактивна Name == Ім'я @@ -215,7 +209,7 @@ Password == Пароль Password incorrect -== неправильний пароль +== Неправильний пароль Ping == Пінг @@ -234,7 +228,7 @@ Players == Гравці Please balance teams! -== Збалансуйте команди! +== Будь ласка збалансуйте команди! Prev. weapon == Попер. зброя @@ -303,10 +297,10 @@ Stop record == Зупинити запис Sudden Death -== Швидка смерть +== Раптова смерть Switch weapon on pickup -== Перемикати зброю при підборі +== Взяти в руку підібравшу зброю Team == Команда @@ -333,7 +327,7 @@ Use sounds == Використовувати звуки V-Sync -== V-Sync +== Вертикальна синхронізація (V-Sync) Version == Версія @@ -482,7 +476,7 @@ Disconnect Dummy == Відключити Dummy Are you sure that you want to disconnect your dummy? -== Ви впевнені, що хочете відключити свого Dummy? +== Ви впевнені, що хочете відключити свого даммі? Welcome to DDNet == Ласкаво просимо в DDNet @@ -527,10 +521,10 @@ Video name: == Назва відео Show DDNet map finishes in server browser -== Показувати завершені карти DDNet в браузері сервера +== Показувати пройдені карти DDNet в браузері сервера transmits your player name to info.ddnet.org -== передає Ваш нікнейм на info.ddnet.org +== передає ваш нікнейм на info.ddnet.org Search == Пошук @@ -601,9 +595,6 @@ Update now Restart == Перезавантажити -Select a name -== Оберіть ім'я - Please use a different name == Будь ласка, використовуйте інше ім’я @@ -776,10 +767,10 @@ Kill == Самогубство Zoom in -== Збільшити +== Приблизити камеру Zoom out -== Зменшити +== Віддалити камеру Default zoom == Стандартний масштаб @@ -797,7 +788,7 @@ Toggle dummy == Переключити Даммі Toggle ghost -== Переключити примару +== Переключити тінь Dummy copy == Повторення рухів Даммі @@ -887,7 +878,7 @@ Show kill messages == Показувати список вбивств Show votes window after voting -== Показувати голосування після вашого голосу +== Відображати вікно голосування після вашого голосу Messages == Повідомлення @@ -916,8 +907,8 @@ Client message Save the best demo of each race == Зберігати краще демо кожної гонки -Default length: %d -== Стандартна довжина: %d +Default length +== Стандартна довжина Enable replays == Включити повтори @@ -941,7 +932,7 @@ Show text entities == Текстові текстури Show others (own team only) -== Завжди показувати тільки вашу команду +== Показати інших гравців (Лише для комманди) Show quads == Показувати quads @@ -964,9 +955,6 @@ Show other players' hook collision lines Show other players' key presses == Показувати натискання кнопок інших гравців -Old mouse mode -== Режим старої мишки - Show tiles layers from BG map == Показувати шари з тайлами з фонової карти @@ -974,7 +962,7 @@ DDNet %s is available: == Доступний новий DDNet %s: Updating... -== Оновлення ... +== Оновлення... No updates available == Немає доступних оновлень @@ -1043,7 +1031,7 @@ Replay == Повтор Saving ddnet-settings.cfg failed -== Збереження налаштувань невдалося +== Збереження налаштувань ddnet-settings.cfg невдалося The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == Ширина або висота текстури %s не поділяється на %d, що може викликати візуальні помилки @@ -1111,9 +1099,6 @@ Getting server list from master server %d player == %d Гравець -Refreshing... -== Оновлення... - Leak IP == Стійке IP @@ -1151,7 +1136,7 @@ Windowed == Віконний Windowed borderless -== Віконний без меж +== Віконний без рамок Desktop fullscreen == Повний робочий стіл @@ -1310,7 +1295,7 @@ Speed: == Швидкість: Angle: -== Кут +== Кут: Team %d == Команда %d @@ -1328,7 +1313,7 @@ UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or netwo == Здається, що IP-адреси UDP і TCP відрізняються. Спробуйте вимкнути VPN, проксі або мережеві прискорювачі. No answer from server yet. -== Сервер ще не відповів +== Сервер ще не відповів... Getting game info == Отримання ігрової інформації @@ -1371,7 +1356,7 @@ Click to remove this player from your friends list. == Натисніть, щоб видалити цього гравця зі списку друзів. Click to remove this clan from your friends list. -== Натисніть, щоб видалити цей клан зі свого списку друзів. +== Натисніть, щоб видалити цей клан зі свого списку. None == Нічого @@ -1406,12 +1391,6 @@ Slow down the demo Speed up the demo == Прискорити димо -Mark the beginning of a cut -== Позначити початок вирізу - -Mark the end of a cut -== Позначити кінець вирізу - Export cut as a separate demo == Експортувати виріз як окреме демо @@ -1458,7 +1437,7 @@ Open the directory that contains the configuration and user files == Відкрити каталог, який містить конфігураційні та користувацькі файли Open the directory to add custom themes -== Відкрити каталог, щоб додати власні теми +== Відкрити каталог, щоб додати власні фони Loading skin files == Завантаження файлів скінів @@ -1481,13 +1460,6 @@ Open the directory to add custom skins Enable controller == Увімкнути контролер -Controller %d: %s -== Контролер %d: %s - -Click to cycle through all available controllers. -== Натисніть, щоб переглянути всі активні контролери. - -[Ingame controller mode] Relative == Relative @@ -1510,18 +1482,12 @@ Controller jitter tolerance No controller found. Plug in a controller. == Контролер не знайдено. Підключіть контролер. -Device -== Пристрій - Status == Стан Aim bind == Прив'язка до цілі -Controller Axis #%d -== Ось контролера #%d - Mouse == Мишка @@ -1555,9 +1521,6 @@ default custom == користувацькі -Graphics cards -== Відеокарти - auto == авто @@ -1640,34 +1603,34 @@ Weapons == Зброя Rifle Laser Outline Color -== Колір контуру лазера +== Колір лазера Rifle Laser Inner Color -== Внутрішній колір лазера +== Колір обводки лазера Shotgun Laser Outline Color -== Колір контуру дробовика +== Колір лазера дробовика Shotgun Laser Inner Color -== Внутрішній колір дробовика +== Колір обводки лазера дробовика Door Laser Outline Color -== Колір контуру дверей +== Колір лазера-дверей Door Laser Inner Color -== Внутрішній колір дверей +== Колір обводки лазера-дверей Freeze Laser Outline Color -== Колір контуру фріз-лазерів +== Колір фріз-лазера Freeze Laser Inner Color -== Внутрішній колір фріз-дверей +== Колір обводки фріз-лазера Set all to Rifle -== Поставити все на колір зброї +== Всі кольори на колір лезера When you cross the start line, show a ghost tee replicating the movements of your best time -== При перетинанні лінію старту, відображати tee-привида, який буде повторювати рухи свого найкращого часу +== При перетинанні лінію старту, відображати tee-привида, який буде повторювати рухи вашого найкращого часу Opacity == Непрозорість @@ -1713,3 +1676,33 @@ Super Loading sound files == Завантаження звукових файлів + +A demo with this name already exists +== + +No server selected +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Close the demo player +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Axis +== + +Graphics card +== diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index 151a2884ad1..f2b3da5e2a0 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -175,13 +175,14 @@ $tool ../DDNet \ player_name client1; cl_download_skins 0; gfx_fullscreen 0; + snd_enable 0; logfile client1.log; $client_args connect localhost:$port" > stdout_client1.txt 2> stderr_client1.txt || fail client1 "$?" & if [ "$arg_valgrind_memcheck" == "1" ]; then - wait_for_fifo client1.fifo 120 - sleep 20 + wait_for_fifo client1.fifo 180 + sleep 30 else wait_for_fifo client1.fifo 50 sleep 1 @@ -198,13 +199,14 @@ $tool ../DDNet \ player_name client2; cl_download_skins 0; gfx_fullscreen 0; + snd_enable 0; logfile client2.log; $client_args connect localhost:$port" > stdout_client2.txt 2> stderr_client2.txt || fail client2 "$?" & if [ "$arg_valgrind_memcheck" == "1" ]; then - wait_for_fifo client2.fifo 120 - sleep 20 + wait_for_fifo client2.fifo 180 + sleep 30 else wait_for_fifo client2.fifo 50 sleep 2 diff --git a/src/base/color.cpp b/src/base/color.cpp new file mode 100644 index 00000000000..81edd0c61c4 --- /dev/null +++ b/src/base/color.cpp @@ -0,0 +1,48 @@ +#include "color.h" +#include "system.h" + +template +std::optional color_parse(const char *pStr) +{ + if(!str_isallnum_hex(pStr)) + return {}; + + const unsigned Num = str_toulong_base(pStr, 16); + + T Color; + switch(str_length(pStr)) + { + case 3: + Color.x = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; + Color.y = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; + Color.z = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; + Color.a = 1.0f; + return Color; + + case 4: + Color.x = (((Num >> 12) & 0x0F) + ((Num >> 8) & 0xF0)) / 255.0f; + Color.y = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; + Color.z = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; + Color.a = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; + return Color; + + case 6: + Color.x = ((Num >> 16) & 0xFF) / 255.0f; + Color.y = ((Num >> 8) & 0xFF) / 255.0f; + Color.z = ((Num >> 0) & 0xFF) / 255.0f; + Color.a = 1.0f; + return Color; + + case 8: + Color.x = ((Num >> 24) & 0xFF) / 255.0f; + Color.y = ((Num >> 16) & 0xFF) / 255.0f; + Color.z = ((Num >> 8) & 0xFF) / 255.0f; + Color.a = ((Num >> 0) & 0xFF) / 255.0f; + return Color; + + default: + return {}; + } +} + +template std::optional color_parse(const char *); diff --git a/src/base/color.h b/src/base/color.h index e767a063769..0bf867901e6 100644 --- a/src/base/color.h +++ b/src/base/color.h @@ -5,6 +5,8 @@ #include #include +#include + /* Title: Color handling */ @@ -114,12 +116,40 @@ class color4_base return (Alpha ? ((unsigned)round_to_int(a * 255.0f) << 24) : 0) + ((unsigned)round_to_int(x * 255.0f) << 16) + ((unsigned)round_to_int(y * 255.0f) << 8) + (unsigned)round_to_int(z * 255.0f); } + unsigned PackAlphaLast(bool Alpha = true) const + { + if(Alpha) + return ((unsigned)round_to_int(x * 255.0f) << 24) + ((unsigned)round_to_int(y * 255.0f) << 16) + ((unsigned)round_to_int(z * 255.0f) << 8) + (unsigned)round_to_int(a * 255.0f); + return ((unsigned)round_to_int(x * 255.0f) << 16) + ((unsigned)round_to_int(y * 255.0f) << 8) + (unsigned)round_to_int(z * 255.0f); + } + DerivedT WithAlpha(float alpha) const { DerivedT col(static_cast(*this)); col.a = alpha; return col; } + + template + static UnpackT UnpackAlphaLast(unsigned Color, bool Alpha = true) + { + UnpackT Result; + if(Alpha) + { + Result.x = ((Color >> 24) & 0xFF) / 255.0f; + Result.y = ((Color >> 16) & 0xFF) / 255.0f; + Result.z = ((Color >> 8) & 0xFF) / 255.0f; + Result.a = ((Color >> 0) & 0xFF) / 255.0f; + } + else + { + Result.x = ((Color >> 16) & 0xFF) / 255.0f; + Result.y = ((Color >> 8) & 0xFF) / 255.0f; + Result.z = ((Color >> 0) & 0xFF) / 255.0f; + Result.a = 1.0f; + } + return Result; + } }; class ColorHSLA : public color4_base @@ -262,4 +292,7 @@ T color_invert(const T &col) return T(1.0f - col.x, 1.0f - col.y, 1.0f - col.z, 1.0f - col.a); } +template +std::optional color_parse(const char *pStr); + #endif diff --git a/src/base/system.cpp b/src/base/system.cpp index 124f8fbd88f..4c9fc81e388 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -2405,6 +2405,39 @@ char *fs_getcwd(char *buffer, int buffer_size) #endif } +const char *fs_filename(const char *path) +{ + for(const char *filename = path + str_length(path); filename >= path; --filename) + { + if(filename[0] == '/' || filename[0] == '\\') + return filename + 1; + } + return path; +} + +void fs_split_file_extension(const char *filename, char *name, size_t name_size, char *extension, size_t extension_size) +{ + dbg_assert(name != nullptr || extension != nullptr, "name or extension parameter required"); + dbg_assert(name == nullptr || name_size > 0, "name_size invalid"); + dbg_assert(extension == nullptr || extension_size > 0, "extension_size invalid"); + + const char *last_dot = str_rchr(filename, '.'); + if(last_dot == nullptr || last_dot == filename) + { + if(extension != nullptr) + extension[0] = '\0'; + if(name != nullptr) + str_copy(name, filename, name_size); + } + else + { + if(extension != nullptr) + str_copy(extension, last_dot + 1, extension_size); + if(name != nullptr) + str_truncate(name, name_size, filename, last_dot - filename); + } +} + int fs_parent_dir(char *path) { char *parent = 0; @@ -3439,6 +3472,17 @@ int str_isallnum(const char *str) return 1; } +int str_isallnum_hex(const char *str) +{ + while(*str) + { + if(!(*str >= '0' && *str <= '9') && !(*str >= 'a' && *str <= 'f') && !(*str >= 'A' && *str <= 'F')) + return 0; + str++; + } + return 1; +} + int str_toint(const char *str) { return str_toint_base(str, 10); diff --git a/src/base/system.h b/src/base/system.h index e0bc2c36126..549cf06bf99 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2026,6 +2026,39 @@ int fs_chdir(const char *path); */ char *fs_getcwd(char *buffer, int buffer_size); +/** + * Gets the name of a file or folder specified by a path, + * i.e. the last segment of the path. + * + * @ingroup Filesystem + * + * @param path Path from which to retrieve the filename. + * + * @return Filename of the path. + * + * @remark Supports forward and backward slashes as path segment separator. + * @remark No distinction between files and folders is being made. + * @remark The strings are treated as zero-terminated strings. + */ +const char *fs_filename(const char *path); + +/** + * Splits a filename into name (without extension) and file extension. + * + * @ingroup Filesystem + * + * @param filename The filename to split. + * @param name Buffer that will receive the name without extension, may be nullptr. + * @param name_size Size of the name buffer (ignored if name is nullptr). + * @param extension Buffer that will receive the extension, may be nullptr. + * @param extension_size Size of the extension buffer (ignored if extension is nullptr). + * + * @remark Does NOT handle forward and backward slashes. + * @remark No distinction between files and folders is being made. + * @remark The strings are treated as zero-terminated strings. + */ +void fs_split_file_extension(const char *filename, char *name, size_t name_size, char *extension = nullptr, size_t extension_size = 0); + /** * Get the parent directory of a directory. * @@ -2186,7 +2219,11 @@ float str_tofloat(const char *str); int str_isspace(char c); char str_uppercase(char c); + int str_isallnum(const char *str); + +int str_isallnum_hex(const char *str); + unsigned str_quickhash(const char *str); enum diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index f7e68556a3a..c0a6235d730 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2924,18 +2924,18 @@ void CClient::Update() if(State() == IClient::STATE_ONLINE) { - if(!m_lpEditJobs.empty()) + if(!m_EditJobs.empty()) { - std::shared_ptr e = m_lpEditJobs.front(); - if(e->Status() == IJob::STATE_DONE) + std::shared_ptr pJob = m_EditJobs.front(); + if(pJob->Status() == IJob::STATE_DONE) { - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "Successfully saved the replay to %s!", e->Destination()); + char aBuf[IO_MAX_PATH_LENGTH + 64]; + str_format(aBuf, sizeof(aBuf), "Successfully saved the replay to %s!", pJob->Destination()); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", aBuf); GameClient()->Echo(Localize("Successfully saved the replay!")); - m_lpEditJobs.pop_front(); + m_EditJobs.pop_front(); } } } @@ -3389,8 +3389,15 @@ void CClient::Run() else if(g_Config.m_ClRefreshRate) { SleepTimeInNanoSeconds = (std::chrono::nanoseconds(1s) / (int64_t)g_Config.m_ClRefreshRate) - (Now - LastTime); - if(SleepTimeInNanoSeconds > 0ns) - net_socket_read_wait(m_aNetClient[CONN_MAIN].m_Socket, SleepTimeInNanoSeconds); + auto SleepTimeInNanoSecondsInner = SleepTimeInNanoSeconds; + auto NowInner = Now; + while((SleepTimeInNanoSecondsInner / std::chrono::nanoseconds(1us).count()) > 0ns) + { + net_socket_read_wait(m_aNetClient[CONN_MAIN].m_Socket, SleepTimeInNanoSecondsInner); + auto NowInnerCalc = time_get_nanoseconds(); + SleepTimeInNanoSecondsInner -= (NowInnerCalc - NowInner); + NowInner = NowInnerCalc; + } Slept = true; } if(Slept) @@ -3857,7 +3864,7 @@ void CClient::SaveReplay(const int Length, const char *pFilename) // Create a job to do this slicing in background because it can be a bit long depending on the file size std::shared_ptr pDemoEditTask = std::make_shared(GameClient()->NetVersion(), &m_SnapshotDelta, m_pStorage, pSrc, aFilename, StartTick, EndTick); Engine()->AddJob(pDemoEditTask); - m_lpEditJobs.push_back(pDemoEditTask); + m_EditJobs.push_back(pDemoEditTask); // And we restart the recorder DemoRecorder_StartReplayRecorder(); diff --git a/src/engine/client/client.h b/src/engine/client/client.h index d35809e263c..71fb4f7379f 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -3,7 +3,7 @@ #ifndef ENGINE_CLIENT_CLIENT_H #define ENGINE_CLIENT_CLIENT_H -#include +#include #include #include @@ -246,7 +246,7 @@ class CClient : public IClient, public CDemoPlayer::IListener CSnapshotDelta m_SnapshotDelta; - std::list> m_lpEditJobs; + std::deque> m_EditJobs; // bool m_CanReceiveServerCapabilities; @@ -277,9 +277,6 @@ class CClient : public IClient, public CDemoPlayer::IListener class CHostLookup m_VersionServeraddr; } m_VersionInfo; - static void GraphicsThreadProxy(void *pThis) { ((CClient *)pThis)->GraphicsThread(); } - void GraphicsThread(); - std::vector m_vWarnings; CFifo m_Fifo; diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index ffc4e9d6d5e..b1ce3c0e105 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -275,6 +275,11 @@ static void SdlCallback(void *pUnused, Uint8 *pStream, int Len) #endif } +CSound::CSound() : + m_SoundEnabled(false), m_Device(0), m_pGraphics(nullptr), m_pStorage(nullptr) +{ +} + int CSound::Init() { m_SoundEnabled = false; @@ -576,7 +581,7 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) return SampleID; } -int CSound::LoadOpus(const char *pFilename) +int CSound::LoadOpus(const char *pFilename, int StorageType) { // don't waste memory on sound when we are stress testing #ifdef CONF_DEBUG @@ -600,7 +605,7 @@ int CSound::LoadOpus(const char *pFilename) void *pData; unsigned DataSize; - if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, &pData, &DataSize)) + if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize)) { dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); return -1; @@ -618,7 +623,7 @@ int CSound::LoadOpus(const char *pFilename) return SampleID; } -int CSound::LoadWV(const char *pFilename) +int CSound::LoadWV(const char *pFilename, int StorageType) { // don't waste memory on sound when we are stress testing #ifdef CONF_DEBUG @@ -642,7 +647,7 @@ int CSound::LoadWV(const char *pFilename) void *pData; unsigned DataSize; - if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, &pData, &DataSize)) + if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize)) { dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename); return -1; diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index d082bca2287..207d52df54b 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -28,15 +28,16 @@ class CSound : public IEngineSound static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize); public: + CSound(); int Init() override; int Update() override; void Shutdown() override; bool IsSoundEnabled() override { return m_SoundEnabled; } - int LoadWV(const char *pFilename) override; + int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; - int LoadOpus(const char *pFilename) override; + int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; void UnloadSample(int SampleID) override; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 7737233c8b6..8b1377a657a 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1858,7 +1858,7 @@ static inline int GetCacheIndex(int Type, bool SendClient) CServer::CCache::CCache() { - m_Cache.clear(); + m_vCache.clear(); } CServer::CCache::~CCache() @@ -1873,12 +1873,12 @@ CServer::CCache::CCacheChunk::CCacheChunk(const void *pData, int Size) void CServer::CCache::AddChunk(const void *pData, int Size) { - m_Cache.emplace_back(pData, Size); + m_vCache.emplace_back(pData, Size); } void CServer::CCache::Clear() { - m_Cache.clear(); + m_vCache.clear(); } void CServer::CacheServerInfo(CCache *pCache, int Type, bool SendClients) @@ -2178,12 +2178,12 @@ void CServer::SendServerInfo(const NETADDR *pAddr, int Token, int Type, bool Sen Packet.m_Address = *pAddr; Packet.m_Flags = NETSENDFLAG_CONNLESS; - for(const auto &Chunk : pCache->m_Cache) + for(const auto &Chunk : pCache->m_vCache) { p.Reset(); if(Type == SERVERINFO_EXTENDED) { - if(&Chunk == &pCache->m_Cache.front()) + if(&Chunk == &pCache->m_vCache.front()) p.AddRaw(SERVERBROWSE_INFO_EXTENDED, sizeof(SERVERBROWSE_INFO_EXTENDED)); else p.AddRaw(SERVERBROWSE_INFO_EXTENDED_MORE, sizeof(SERVERBROWSE_INFO_EXTENDED_MORE)); @@ -2222,7 +2222,7 @@ void CServer::GetServerInfoSixup(CPacker *pPacker, int Token, bool SendClients) SendClients = SendClients && Token != -1; - CCache::CCacheChunk &FirstChunk = m_aSixupServerInfoCache[SendClients].m_Cache.front(); + CCache::CCacheChunk &FirstChunk = m_aSixupServerInfoCache[SendClients].m_vCache.front(); pPacker->AddRaw(FirstChunk.m_vData.data(), FirstChunk.m_vData.size()); } diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 2f8d6cffd26..8e5285e63d6 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -350,11 +350,12 @@ class CServer : public IServer public: CCacheChunk(const void *pData, int Size); CCacheChunk(const CCacheChunk &) = delete; + CCacheChunk(CCacheChunk &&) = default; std::vector m_vData; }; - std::list m_Cache; + std::vector m_vCache; CCache(); ~CCache(); diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index bcc30303141..d59902f6dbc 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -55,44 +55,7 @@ ColorHSLA CConsole::CResult::GetColor(unsigned Index, bool Light) const } else if(*pStr == '$') // Hex RGB/RGBA { - ColorRGBA Rgba = ColorRGBA(0, 0, 0, 1); - const int Len = str_length(pStr); - if(Len == 4) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; - Rgba.g = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; - Rgba.b = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; - } - else if(Len == 5) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = (((Num >> 12) & 0x0F) + ((Num >> 8) & 0xF0)) / 255.0f; - Rgba.g = (((Num >> 8) & 0x0F) + ((Num >> 4) & 0xF0)) / 255.0f; - Rgba.b = (((Num >> 4) & 0x0F) + ((Num >> 0) & 0xF0)) / 255.0f; - Rgba.a = (((Num >> 0) & 0x0F) + ((Num << 4) & 0xF0)) / 255.0f; - } - else if(Len == 7) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = ((Num >> 16) & 0xFF) / 255.0f; - Rgba.g = ((Num >> 8) & 0xFF) / 255.0f; - Rgba.b = ((Num >> 0) & 0xFF) / 255.0f; - } - else if(Len == 9) - { - const unsigned Num = str_toulong_base(pStr + 1, 16); - Rgba.r = ((Num >> 24) & 0xFF) / 255.0f; - Rgba.g = ((Num >> 16) & 0xFF) / 255.0f; - Rgba.b = ((Num >> 8) & 0xFF) / 255.0f; - Rgba.a = ((Num >> 0) & 0xFF) / 255.0f; - } - else - { - return ColorHSLA(0, 0, 0); - } - - return color_cast(Rgba); + return color_cast(color_parse(pStr + 1).value_or(ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f))); } else if(!str_comp_nocase(pStr, "red")) return ColorHSLA(0.0f / 6.0f, 1, .5f); diff --git a/src/engine/shared/datafile.cpp b/src/engine/shared/datafile.cpp index 5b75771af85..c0f668487b5 100644 --- a/src/engine/shared/datafile.cpp +++ b/src/engine/shared/datafile.cpp @@ -525,16 +525,35 @@ CDataFileWriter::CDataFileWriter() CDataFileWriter::~CDataFileWriter() { + if(m_File) + { + io_close(m_File); + m_File = 0; + } + free(m_pItemTypes); m_pItemTypes = nullptr; - for(int i = 0; i < m_NumItems; i++) - free(m_pItems[i].m_pData); - for(int i = 0; i < m_NumDatas; ++i) - free(m_pDatas[i].m_pCompressedData); - free(m_pItems); - m_pItems = nullptr; - free(m_pDatas); - m_pDatas = nullptr; + + if(m_pItems) + { + for(int i = 0; i < m_NumItems; i++) + { + free(m_pItems[i].m_pData); + } + free(m_pItems); + m_pItems = nullptr; + } + + if(m_pDatas) + { + for(int i = 0; i < m_NumDatas; ++i) + { + free(m_pDatas[i].m_pUncompressedData); + free(m_pDatas[i].m_pCompressedData); + } + free(m_pDatas); + m_pDatas = nullptr; + } } bool CDataFileWriter::OpenFile(class IStorage *pStorage, const char *pFilename, int StorageType) @@ -636,21 +655,12 @@ int CDataFileWriter::AddData(int Size, void *pData, int CompressionLevel) dbg_assert(m_NumDatas < 1024, "too much data"); CDataInfo *pInfo = &m_pDatas[m_NumDatas]; - unsigned long s = compressBound(Size); - void *pCompData = malloc(s); // temporary buffer that we use during compression - - int Result = compress2((Bytef *)pCompData, &s, (Bytef *)pData, Size, CompressionLevel); - if(Result != Z_OK) - { - dbg_msg("datafile", "compression error %d", Result); - dbg_assert(0, "zlib error"); - } - + pInfo->m_pUncompressedData = malloc(Size); + mem_copy(pInfo->m_pUncompressedData, pData, Size); pInfo->m_UncompressedSize = Size; - pInfo->m_CompressedSize = (int)s; - pInfo->m_pCompressedData = malloc(pInfo->m_CompressedSize); - mem_copy(pInfo->m_pCompressedData, pCompData, pInfo->m_CompressedSize); - free(pCompData); + pInfo->m_pCompressedData = nullptr; + pInfo->m_CompressedSize = 0; + pInfo->m_CompressionLevel = CompressionLevel; m_NumDatas++; return m_NumDatas - 1; @@ -672,15 +682,31 @@ int CDataFileWriter::AddDataSwapped(int Size, void *pData) #endif } -int CDataFileWriter::Finish() +void CDataFileWriter::Finish() { - if(!m_File) - return 1; + dbg_assert((bool)m_File, "file not open"); // we should now write this file! if(DEBUG) dbg_msg("datafile", "writing"); + // Compress data. This takes the majority of the time when saving a datafile, + // so it's delayed until the end so it can be off-loaded to another thread. + for(int i = 0; i < m_NumDatas; i++) + { + unsigned long CompressedSize = compressBound(m_pDatas[i].m_UncompressedSize); + m_pDatas[i].m_pCompressedData = malloc(CompressedSize); + const int Result = compress2((Bytef *)m_pDatas[i].m_pCompressedData, &CompressedSize, (Bytef *)m_pDatas[i].m_pUncompressedData, m_pDatas[i].m_UncompressedSize, m_pDatas[i].m_CompressionLevel); + m_pDatas[i].m_CompressedSize = CompressedSize; + free(m_pDatas[i].m_pUncompressedData); + m_pDatas[i].m_pUncompressedData = nullptr; + if(Result != Z_OK) + { + dbg_msg("datafile", "compression error %d", Result); + dbg_assert(false, "zlib error"); + } + } + // calculate sizes int ItemSize = 0; for(int i = 0; i < m_NumItems; i++) @@ -851,5 +877,4 @@ int CDataFileWriter::Finish() if(DEBUG) dbg_msg("datafile", "done"); - return 0; } diff --git a/src/engine/shared/datafile.h b/src/engine/shared/datafile.h index c124812b47b..abc5af217b7 100644 --- a/src/engine/shared/datafile.h +++ b/src/engine/shared/datafile.h @@ -58,9 +58,11 @@ class CDataFileWriter { struct CDataInfo { + void *m_pUncompressedData; int m_UncompressedSize; - int m_CompressedSize; void *m_pCompressedData; + int m_CompressedSize; + int m_CompressionLevel; }; struct CItemInfo @@ -103,14 +105,31 @@ class CDataFileWriter public: CDataFileWriter(); + CDataFileWriter(CDataFileWriter &&Other) : + m_NumItems(Other.m_NumItems), + m_NumDatas(Other.m_NumDatas), + m_NumItemTypes(Other.m_NumItemTypes), + m_NumExtendedItemTypes(Other.m_NumExtendedItemTypes) + { + m_File = Other.m_File; + Other.m_File = 0; + m_pItemTypes = Other.m_pItemTypes; + Other.m_pItemTypes = nullptr; + m_pItems = Other.m_pItems; + Other.m_pItems = nullptr; + m_pDatas = Other.m_pDatas; + Other.m_pDatas = nullptr; + mem_copy(m_aExtendedItemTypes, Other.m_aExtendedItemTypes, sizeof(m_aExtendedItemTypes)); + } ~CDataFileWriter(); + void Init(); bool OpenFile(class IStorage *pStorage, const char *pFilename, int StorageType = IStorage::TYPE_SAVE); bool Open(class IStorage *pStorage, const char *pFilename, int StorageType = IStorage::TYPE_SAVE); int AddData(int Size, void *pData, int CompressionLevel = Z_DEFAULT_COMPRESSION); int AddDataSwapped(int Size, void *pData); int AddItem(int Type, int ID, int Size, void *pData); - int Finish(); + void Finish(); }; #endif diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp index df2ad3d5212..8125e8ab91e 100644 --- a/src/engine/shared/filecollection.cpp +++ b/src/engine/shared/filecollection.cpp @@ -207,7 +207,7 @@ void CFileCollection::AddEntry(int64_t Timestamp) } else { - char aBuf[512]; + char aBuf[IO_MAX_PATH_LENGTH]; char aTimestring[TIMESTAMP_LENGTH]; BuildTimestring(m_aTimestamps[0], aTimestring); @@ -261,7 +261,7 @@ int CFileCollection::RemoveCallback(const char *pFilename, int IsDir, int Storag if(Timestamp == pThis->m_Remove) { - char aBuf[512]; + char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "%s/%s", pThis->m_aPath, pFilename); pThis->m_pStorage->RemoveFile(aBuf, IStorage::TYPE_SAVE); pThis->m_Remove = -1; diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 4eedb16c7f5..2a51c9bc316 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -1,11 +1,14 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include "linereader.h" #include #include + #include +#include #include +#include + #ifdef CONF_PLATFORM_HAIKU #include #endif @@ -68,6 +71,7 @@ class CStorage : public IStorage CreateFolder("screenshots/auto", TYPE_SAVE); CreateFolder("screenshots/auto/stats", TYPE_SAVE); CreateFolder("maps", TYPE_SAVE); + CreateFolder("maps/auto", TYPE_SAVE); CreateFolder("mapres", TYPE_SAVE); CreateFolder("downloadedmaps", TYPE_SAVE); CreateFolder("skins", TYPE_SAVE); @@ -312,14 +316,38 @@ class CStorage : public IStorage m_aBinarydir[0] = '\0'; } + int NumPaths() const override + { + return m_NumPaths; + } + + struct SListDirectoryInfoUniqueCallbackData + { + FS_LISTDIR_CALLBACK_FILEINFO m_pfnDelegate; + void *m_pDelegateUser; + std::unordered_set m_Seen; + }; + + static int ListDirectoryInfoUniqueCallback(const CFsFileInfo *pInfo, int IsDir, int Type, void *pUser) + { + SListDirectoryInfoUniqueCallbackData *pData = static_cast(pUser); + auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pInfo->m_pName); + if(InsertionTookPlace) + return pData->m_pfnDelegate(pInfo, IsDir, Type, pData->m_pDelegateUser); + return 0; + } + void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override { char aBuffer[IO_MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { + SListDirectoryInfoUniqueCallbackData Data; + Data.m_pfnDelegate = pfnCallback; + Data.m_pDelegateUser = pUser; // list all available directories for(int i = TYPE_SAVE; i < m_NumPaths; ++i) - fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); + fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryInfoUniqueCallback, i, &Data); } else if(Type >= TYPE_SAVE && Type < m_NumPaths) { @@ -332,14 +360,33 @@ class CStorage : public IStorage } } + struct SListDirectoryUniqueCallbackData + { + FS_LISTDIR_CALLBACK m_pfnDelegate; + void *m_pDelegateUser; + std::unordered_set m_Seen; + }; + + static int ListDirectoryUniqueCallback(const char *pName, int IsDir, int Type, void *pUser) + { + SListDirectoryUniqueCallbackData *pData = static_cast(pUser); + auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pName); + if(InsertionTookPlace) + return pData->m_pfnDelegate(pName, IsDir, Type, pData->m_pDelegateUser); + return 0; + } + void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override { char aBuffer[IO_MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { + SListDirectoryUniqueCallbackData Data; + Data.m_pfnDelegate = pfnCallback; + Data.m_pDelegateUser = pUser; // list all available directories for(int i = TYPE_SAVE; i < m_NumPaths; ++i) - fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); + fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryUniqueCallback, i, &Data); } else if(Type >= TYPE_SAVE && Type < m_NumPaths) { diff --git a/src/engine/sound.h b/src/engine/sound.h index 79a73cdbde0..658c6f64fad 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -6,6 +6,7 @@ #include "kernel.h" #include +#include class ISound : public IInterface { @@ -63,8 +64,8 @@ class ISound : public IInterface virtual bool IsSoundEnabled() = 0; - virtual int LoadWV(const char *pFilename) = 0; - virtual int LoadOpus(const char *pFilename) = 0; + virtual int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0; + virtual int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0; virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual void UnloadSample(int SampleID) = 0; diff --git a/src/engine/storage.h b/src/engine/storage.h index 19d4a18e04e..ddabded156b 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -42,6 +42,8 @@ class IStorage : public IInterface STORAGETYPE_CLIENT, }; + virtual int NumPaths() const = 0; + virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0; virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0; diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp index db721200603..b4d4526258f 100644 --- a/src/game/client/components/binds.cpp +++ b/src/game/client/components/binds.cpp @@ -255,7 +255,7 @@ void CBinds::OnConsoleInit() if(pConfigManager) pConfigManager->RegisterCallback(ConfigSaveCallback, this); - Console()->Register("bind", "s[key] r[command]", CFGFLAG_CLIENT, ConBind, this, "Bind key to execute the command"); + Console()->Register("bind", "s[key] ?r[command]", CFGFLAG_CLIENT, ConBind, this, "Bind key to execute a command or view keybindings"); Console()->Register("binds", "?s[key]", CFGFLAG_CLIENT, ConBinds, this, "Print command executed by this keybinding or all binds"); Console()->Register("unbind", "s[key]", CFGFLAG_CLIENT, ConUnbind, this, "Unbind key"); Console()->Register("unbindall", "", CFGFLAG_CLIENT, ConUnbindAll, this, "Unbind all keys"); @@ -279,6 +279,20 @@ void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData) return; } + if(pResult->NumArguments() == 1) + { + char aBuf[256]; + const char *pKeyName = pResult->GetString(0); + + if(!pBinds->m_aapKeyBindings[Modifier][KeyID]) + str_format(aBuf, sizeof(aBuf), "%s (%d) is not bound", pKeyName, KeyID); + else + str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, KeyID, pBinds->m_aapKeyBindings[Modifier][KeyID]); + + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); + return; + } + pBinds->Bind(KeyID, pResult->GetString(1), false, Modifier); } diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index 199e134ec89..80af33cbb66 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -31,7 +31,7 @@ float CCamera::ZoomProgress(float CurrentTime) const void CCamera::ScaleZoom(float Factor) { float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom; - ChangeZoom(CurrentTarget * Factor); + ChangeZoom(CurrentTarget * Factor, g_Config.m_ClSmoothZoomTime); } float CCamera::MaxZoomLevel() @@ -44,7 +44,7 @@ float CCamera::MinZoomLevel() return 0.01f; } -void CCamera::ChangeZoom(float Target) +void CCamera::ChangeZoom(float Target, int Smoothness) { if(Target > MaxZoomLevel() || Target < MinZoomLevel()) { @@ -64,7 +64,7 @@ void CCamera::ChangeZoom(float Target) m_ZoomSmoothingTarget = Target; m_ZoomSmoothing = CCubicBezier::With(Current, Derivative, 0, m_ZoomSmoothingTarget); m_ZoomSmoothingStart = Now; - m_ZoomSmoothingEnd = Now + (float)g_Config.m_ClSmoothZoomTime / 1000; + m_ZoomSmoothingEnd = Now + (float)Smoothness / 1000; m_Zooming = true; } @@ -198,6 +198,9 @@ void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { pSelf->ScaleZoom(ZoomStep); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom++; } } void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) @@ -206,12 +209,19 @@ void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { pSelf->ScaleZoom(1 / ZoomStep); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom--; } } void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData) { + CCamera *pSelf = (CCamera *)pUserData; float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(0) : g_Config.m_ClDefaultZoom; - ((CCamera *)pUserData)->ChangeZoom(std::pow(ZoomStep, TargetLevel - 10)); + pSelf->ChangeZoom(std::pow(ZoomStep, TargetLevel - 10), g_Config.m_ClSmoothZoomTime); + + if(pSelf->GameClient()->m_MultiViewActivated) + pSelf->GameClient()->m_MultiViewPersonalZoom = 0; } void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) { @@ -221,3 +231,8 @@ void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) clamp(pResult->GetInteger(0) * 32.0f, 200.0f, pSelf->Collision()->GetWidth() * 32 - 200.0f), clamp(pResult->GetInteger(1) * 32.0f, 200.0f, pSelf->Collision()->GetWidth() * 32 - 200.0f)); } + +void CCamera::SetZoom(float Target, int Smoothness) +{ + ChangeZoom(Target, Smoothness); +} diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h index 257a9cec688..c52e9a49204 100644 --- a/src/game/client/components/camera.h +++ b/src/game/client/components/camera.h @@ -30,7 +30,7 @@ class CCamera : public CComponent float m_ZoomSmoothingEnd; void ScaleZoom(float Factor); - void ChangeZoom(float Target); + void ChangeZoom(float Target, int Smoothness); float ZoomProgress(float CurrentTime) const; float MinZoomLevel(); @@ -52,6 +52,8 @@ class CCamera : public CComponent virtual void OnConsoleInit() override; virtual void OnReset() override; + void SetZoom(float Target, int Smoothness); + private: static void ConZoomPlus(IConsole::IResult *pResult, void *pUserData); static void ConZoomMinus(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index c0f8a80a03d..47e2015cc15 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -106,7 +106,7 @@ void CHud::RenderGameTimer() } else if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME) { - //The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer + // The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer Time = (Client()->GameTick(g_Config.m_ClDummy) + m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) / Client()->GameTickSpeed(); } else @@ -750,20 +750,16 @@ void CHud::PreparePlayerStateQuads() m_AirjumpEmptyOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10); // Quads for displaying weapons - float ScaleX, ScaleY; - const float HudWeaponScale = 0.25f; - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponHammerOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleY * HudWeaponScale); - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GUN].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponGunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleY * HudWeaponScale); - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponShotgunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleY * HudWeaponScale); - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponGrenadeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleY * HudWeaponScale); - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_LASER].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponLaserOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleY * HudWeaponScale); - RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_pSpriteBody, ScaleX, ScaleY); - m_WeaponNinjaOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleY * HudWeaponScale); + for(int Weapon = 0; Weapon < NUM_WEAPONS; ++Weapon) + { + const CDataWeaponspec &WeaponSpec = g_pData->m_Weapons.m_aId[Weapon]; + float ScaleX, ScaleY; + RenderTools()->GetSpriteScale(WeaponSpec.m_pSpriteBody, ScaleX, ScaleY); + constexpr float HudWeaponScale = 0.25f; + float Width = WeaponSpec.m_VisualSize * ScaleX * HudWeaponScale; + float Height = WeaponSpec.m_VisualSize * ScaleY * HudWeaponScale; + m_aWeaponOffset[Weapon] = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, Width, Height); + } // Quads for displaying capabilities m_EndlessJumpOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f); @@ -879,90 +875,36 @@ void CHud::RenderPlayerState(const int ClientID) (GameClient()->m_GameInfo.m_HudAmmo && g_Config.m_ClShowhudHealthAmmo ? 12 : 0)); // render weapons - if(pCharacter->m_aWeapons[WEAPON_HAMMER].m_Got) - { - if(pPlayer->m_Weapon != WEAPON_HAMMER) - { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - } - x -= 3; - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupHammer); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponHammerOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - x += 16; - } - if(pCharacter->m_aWeapons[WEAPON_GUN].m_Got) - { - if(pPlayer->m_Weapon != WEAPON_GUN) - { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - } - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGun); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGunOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - x += 12; - } - if(pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got) - { - if(pPlayer->m_Weapon != WEAPON_SHOTGUN) - { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - } - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupShotgun); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponShotgunOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - x += 12; - } - if(pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got) - { - if(pPlayer->m_Weapon != WEAPON_GRENADE) - { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - } - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGrenade); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGrenadeOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - x += 12; - } - if(pCharacter->m_aWeapons[WEAPON_LASER].m_Got) { - if(pPlayer->m_Weapon != WEAPON_LASER) + constexpr float aWeaponWidth[NUM_WEAPONS] = {16, 12, 12, 12, 12, 12}; + constexpr float aWeaponInitialOffset[NUM_WEAPONS] = {-3, -4, -1, -1, -2, -4}; + bool InitialOffsetAdded = false; + for(int Weapon = 0; Weapon < NUM_WEAPONS; ++Weapon) { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); - } - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupLaser); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponLaserOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - x += 12; - } - if(pCharacter->m_aWeapons[WEAPON_NINJA].m_Got) - { - if(pPlayer->m_Weapon != WEAPON_NINJA) - { - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + if(!pCharacter->m_aWeapons[Weapon].m_Got) + continue; + if(!InitialOffsetAdded) + { + x += aWeaponInitialOffset[Weapon]; + InitialOffsetAdded = true; + } + if(pPlayer->m_Weapon != Weapon) + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f); + Graphics()->QuadsSetRotation(pi * 7 / 4); + Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpritePickupWeapons[Weapon]); + Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aWeaponOffset[Weapon], x, y); + Graphics()->QuadsSetRotation(0); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + x += aWeaponWidth[Weapon]; } - Graphics()->QuadsSetRotation(pi * 7 / 4); - Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupNinja); - Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponNinjaOffset, x, y); - Graphics()->QuadsSetRotation(0); - Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); - - const int Max = g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000; - float NinjaProgress = clamp(pCharacter->m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000 - Client()->GameTick(g_Config.m_ClDummy), 0, Max) / (float)Max; - if(NinjaProgress > 0.0f && m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + if(pCharacter->m_aWeapons[WEAPON_NINJA].m_Got) { - x += 12; - RenderNinjaBarPos(x, y - 12, 6.f, 24.f, NinjaProgress); + const int Max = g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000; + float NinjaProgress = clamp(pCharacter->m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000 - Client()->GameTick(g_Config.m_ClDummy), 0, Max) / (float)Max; + if(NinjaProgress > 0.0f && m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo) + { + RenderNinjaBarPos(x, y - 12, 6.f, 24.f, NinjaProgress); + } } } @@ -1474,7 +1416,7 @@ void CHud::RenderSpectatorHud() // draw the text char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View")); + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), GameClient()->m_MultiViewActivated ? Localize("Multi-View") : m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View")); TextRender()->Text(m_Width - 174.0f, m_Height - 15.0f + (15.f - 8.f) / 2.f, 8.0f, aBuf, -1.0f); } @@ -1527,7 +1469,11 @@ void CHud::OnRender() { RenderAmmoHealthAndArmor(&m_pClient->m_Snap.m_aCharacters[SpectatorID].m_Cur); } - if(SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && g_Config.m_ClShowhudDDRace && GameClient()->m_GameInfo.m_HudDDRace) + if(SpectatorID != SPEC_FREEVIEW && + m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && + g_Config.m_ClShowhudDDRace && + (!GameClient()->m_MultiViewActivated || GameClient()->m_MultiViewShowHud) && + GameClient()->m_GameInfo.m_HudDDRace) { RenderPlayerState(SpectatorID); } diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h index 16f5f257efa..f292dd0b1c0 100644 --- a/src/game/client/components/hud.h +++ b/src/game/client/components/hud.h @@ -111,12 +111,7 @@ class CHud : public CComponent int m_FlagOffset; int m_AirjumpOffset; int m_AirjumpEmptyOffset; - int m_WeaponHammerOffset; - int m_WeaponGunOffset; - int m_WeaponShotgunOffset; - int m_WeaponGrenadeOffset; - int m_WeaponLaserOffset; - int m_WeaponNinjaOffset; + int m_aWeaponOffset[NUM_WEAPONS]; int m_EndlessJumpOffset; int m_EndlessHookOffset; int m_JetpackOffset; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 400905b3743..6997e747ee7 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -70,6 +70,7 @@ CMenus::CMenus() m_ShowStart = true; str_copy(m_aCurrentDemoFolder, "demos"); + m_DemolistStorageType = IStorage::TYPE_ALL; m_DemoPlayerState = DEMOPLAYER_NONE; m_Dummy = false; @@ -1160,6 +1161,7 @@ int CMenus::Render() { pTitle = m_aPopupTitle; pExtraText = m_aPopupMessage; + TopAlign = true; } else if(m_Popup == POPUP_CONNECTING) { @@ -1551,9 +1553,9 @@ int CMenus::Render() } else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) { - str_copy(g_Config.m_UiDemoSelected, m_DemoRenameInput.GetString()); - if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) - g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; + str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString()); + if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) + m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; DemolistPopulate(); DemolistOnUpdate(false); } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 3ee253c07f3..56ded196b41 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -199,7 +199,7 @@ class CMenus : public CComponent NUM_BUTTONS }; char m_aPopupTitle[128]; - char m_aPopupMessage[256]; + char m_aPopupMessage[IO_MAX_PATH_LENGTH + 256]; struct { char m_aLabel[64]; @@ -259,6 +259,7 @@ class CMenus : public CComponent char m_aFilename[IO_MAX_PATH_LENGTH]; char m_aName[IO_MAX_PATH_LENGTH]; bool m_IsDir; + bool m_IsLink; int m_StorageType; time_t m_Date; @@ -318,6 +319,7 @@ class CMenus : public CComponent }; char m_aCurrentDemoFolder[IO_MAX_PATH_LENGTH]; + char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH]; CLineInputBuffered m_DemoRenameInput; CLineInputBuffered m_DemoSliceInput; CLineInputBuffered m_DemoRenderInput; @@ -325,6 +327,7 @@ class CMenus : public CComponent bool m_DemolistSelectedIsDir; bool m_DemolistSelectedReveal = false; int m_DemolistStorageType; + bool m_DemolistMultipleStorages = false; int m_Speed = 4; std::chrono::nanoseconds m_DemoPopulateStartTime{0}; diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index fa80d524963..cdd3ada0336 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -205,7 +205,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); TextRender()->TextColor(TextColor); TextRender()->TextOutlineColor(TextOutlineColor); - UI()->DoLabelStreamed(UIRect, pRect, pText, FontSize, TextAlign, -1.0f); + UI()->DoLabelStreamed(UIRect, pRect, pText, FontSize, TextAlign); TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetRenderFlags(0); @@ -250,6 +250,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) continue; } + const float FontSize = 12.0f; for(int c = 0; c < NumCols; c++) { CUIRect Button; @@ -285,19 +286,21 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } else if(ID == COL_NAME) { - float FontSize = 12.0f; + SLabelProperties Props; + Props.m_MaxWidth = Button.w; + Props.m_StopAtEnd = true; + Props.m_EnableWidthCheck = false; bool Printed = false; - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME)) - Printed = PrintHighlighted(pItem->m_aName, [this, pItem, FontSize, &Button](const char *pFilteredStr, const int FilterLen) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Button.w, true, (int)(pFilteredStr - pItem->m_aName)); + Printed = PrintHighlighted(pItem->m_aName, [this, pItem, FontSize, Props, &Button](const char *pFilteredStr, const int FilterLen) { + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aName)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Button.w, true, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor); TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Button.w, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor); }); if(!Printed) - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Button.w, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props); } else if(ID == COL_MAP) { @@ -314,18 +317,21 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } } - float FontSize = 12.0f; + SLabelProperties Props; + Props.m_MaxWidth = Button.w; + Props.m_StopAtEnd = true; + Props.m_EnableWidthCheck = false; bool Printed = false; if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME)) - Printed = PrintHighlighted(pItem->m_aMap, [this, pItem, FontSize, &Button](const char *pFilteredStr, const int FilterLen) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Button.w, true, (int)(pFilteredStr - pItem->m_aMap)); + Printed = PrintHighlighted(pItem->m_aMap, [this, pItem, FontSize, Props, &Button](const char *pFilteredStr, const int FilterLen) { + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aMap)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Button.w, true, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor); TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Button.w, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor); }); if(!Printed) - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Button.w, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props); } else if(ID == COL_PLAYERS) { @@ -350,8 +356,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem)); if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER)) TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_MR, -1.0f, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_MR); TextRender()->TextColor(1, 1, 1, 1); } else if(ID == COL_PING) @@ -364,20 +369,20 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) TextRender()->TextColor(rgb); } - float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_MR, -1.0f, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_MR); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); } else if(ID == COL_VERSION) { const char *pVersion = pItem->m_aVersion; - float FontSize = 12.0f; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_MR, -1.0f, false); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_MR); } else if(ID == COL_GAMETYPE) { - float FontSize = 12.0f; - + SLabelProperties Props; + Props.m_MaxWidth = Button.w; + Props.m_StopAtEnd = true; + Props.m_EnableWidthCheck = false; if(g_Config.m_UiColorizeGametype) { ColorHSLA hsl = ColorHSLA(1.0f, 1.0f, 1.0f); @@ -405,11 +410,11 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) ColorRGBA rgb = color_cast(hsl); TextRender()->TextColor(rgb); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Button.w, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Props); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); } else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Button.w, true); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Props); } } } @@ -456,6 +461,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) CUIRect SearchAndInfo, ServerAddr, ConnectButtons; SearchInfoAndAddr.HSplitTop(40.0f, &SearchAndInfo, &ServerAddr); ServersAndConnect.HSplitTop(35.0f, &Status3, &ConnectButtons); + ConnectButtons.HSplitTop(5.0f, nullptr, &ConnectButtons); CUIRect QuickSearch, QuickExclude; SearchAndInfo.HSplitTop(20.f, &QuickSearch, &QuickExclude); @@ -553,10 +559,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) // button area CUIRect ButtonRefresh, ButtonConnect; - ConnectButtons.VSplitMid(&ButtonRefresh, &ButtonConnect); - ButtonRefresh.HSplitTop(5.0f, NULL, &ButtonRefresh); - ButtonConnect.HSplitTop(5.0f, NULL, &ButtonConnect); - ButtonConnect.VSplitLeft(5.0f, NULL, &ButtonConnect); + ConnectButtons.VSplitMid(&ButtonRefresh, &ButtonConnect, 5.0f); // refresh button { @@ -1642,7 +1645,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) void CMenus::PopupConfirmRemoveFriend() { - m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->Name(), m_pRemoveFriend->Clan()); + m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->FriendState() == IFriends::FRIEND_PLAYER ? m_pRemoveFriend->Name() : "", m_pRemoveFriend->Clan()); FriendlistOnUpdate(); Client()->ServerBrowserUpdate(); m_pRemoveFriend = nullptr; diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index a353bfd4cdc..bbe0a36ba0b 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -89,6 +89,8 @@ void CMenus::HandleDemoSeeking(float PositionToSeek, float TimeToSeek) m_pClient->m_SuppressEvents = false; m_pClient->m_MapLayersBackGround.EnvelopeUpdate(); m_pClient->m_MapLayersForeGround.EnvelopeUpdate(); + if(!DemoPlayer()->BaseInfo()->m_Paused && PositionToSeek == 1.0f) + DemoPlayer()->Pause(); } } @@ -375,14 +377,14 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) if(Input()->ShiftIsPressed()) { AmountSeek = s_PrevAmount + (AmountSeek - s_PrevAmount) * 0.05f; - if(AmountSeek > 0.0f && AmountSeek < 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.0001f) + if(AmountSeek >= 0.0f && AmountSeek <= 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.0001f) { PositionToSeek = AmountSeek; } } else { - if(AmountSeek > 0.0f && AmountSeek < 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.001f) + if(AmountSeek >= 0.0f && AmountSeek <= 1.0f && absolute(s_PrevAmount - AmountSeek) >= 0.001f) { s_PrevAmount = AmountSeek; PositionToSeek = AmountSeek; @@ -482,25 +484,35 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) // slice begin button ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_SliceBeginButton; - if(DoButton_FontIcon(&s_SliceBeginButton, FONT_ICON_RIGHT_FROM_BRACKET, 0, &Button, IGraphics::CORNER_ALL)) + const int SliceBeginButtonResult = DoButton_FontIcon(&s_SliceBeginButton, FONT_ICON_RIGHT_FROM_BRACKET, 0, &Button, IGraphics::CORNER_ALL); + if(SliceBeginButtonResult == 1) { Client()->DemoSliceBegin(); if(CurrentTick > (g_Config.m_ClDemoSliceEnd - pInfo->m_FirstTick)) g_Config.m_ClDemoSliceEnd = -1; } - GameClient()->m_Tooltips.DoToolTip(&s_SliceBeginButton, &Button, Localize("Mark the beginning of a cut")); + else if(SliceBeginButtonResult == 2) + { + g_Config.m_ClDemoSliceBegin = -1; + } + GameClient()->m_Tooltips.DoToolTip(&s_SliceBeginButton, &Button, Localize("Mark the beginning of a cut (right click to reset)")); // slice end button ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_SliceEndButton; - if(DoButton_FontIcon(&s_SliceEndButton, FONT_ICON_RIGHT_TO_BRACKET, 0, &Button, IGraphics::CORNER_ALL)) + const int SliceEndButtonResult = DoButton_FontIcon(&s_SliceEndButton, FONT_ICON_RIGHT_TO_BRACKET, 0, &Button, IGraphics::CORNER_ALL); + if(SliceEndButtonResult == 1) { Client()->DemoSliceEnd(); if(CurrentTick < (g_Config.m_ClDemoSliceBegin - pInfo->m_FirstTick)) g_Config.m_ClDemoSliceBegin = -1; } - GameClient()->m_Tooltips.DoToolTip(&s_SliceEndButton, &Button, Localize("Mark the end of a cut")); + else if(SliceEndButtonResult == 2) + { + g_Config.m_ClDemoSliceEnd = -1; + } + GameClient()->m_Tooltips.DoToolTip(&s_SliceEndButton, &Button, Localize("Mark the end of a cut (right click to reset)")); // slice save button ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); @@ -525,6 +537,8 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_OneMarkerBackButton; if(DoButton_FontIcon(&s_OneMarkerBackButton, FONT_ICON_BACKWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) + { + PositionToSeek = 0.0f; for(int i = pInfo->m_NumTimelineMarkers - 1; i >= 0; i--) { if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) < CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) @@ -532,8 +546,8 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) PositionToSeek = (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; break; } - PositionToSeek = 0.0f; } + } GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerBackButton, &Button, Localize("Go back one marker")); // one marker forward @@ -541,6 +555,8 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_OneMarkerForwardButton; if(DoButton_FontIcon(&s_OneMarkerForwardButton, FONT_ICON_FORWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) + { + PositionToSeek = 1.0f; for(int i = 0; i < pInfo->m_NumTimelineMarkers; i++) { if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) > CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) @@ -548,8 +564,8 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) PositionToSeek = (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; break; } - PositionToSeek = 1.0f; } + } GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerForwardButton, &Button, Localize("Go forward one marker")); // close button @@ -708,9 +724,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) { char aPath[IO_MAX_PATH_LENGTH]; str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); - str_copy(g_Config.m_UiDemoSelected, m_DemoSliceInput.GetString()); - if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) - g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; + str_copy(m_aCurrentDemoSelectionName, m_DemoSliceInput.GetString()); + if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) + m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; m_DemoPlayerState = DEMOPLAYER_NONE; Client()->DemoSlice(aPath, CMenus::DemoFilterChat, &s_RemoveChat); DemolistPopulate(); @@ -725,7 +741,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) { CMenus *pSelf = (CMenus *)pUser; - if(str_comp(pInfo->m_pName, ".") == 0 || (str_comp(pInfo->m_pName, "..") == 0 && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0) || (!IsDir && !str_endswith(pInfo->m_pName, ".demo"))) + if(str_comp(pInfo->m_pName, ".") == 0 || + (str_comp(pInfo->m_pName, "..") == 0 && (pSelf->m_aCurrentDemoFolder[0] == '\0' || (!pSelf->m_DemolistMultipleStorages && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0))) || + (!IsDir && !str_endswith(pInfo->m_pName, ".demo"))) { return 0; } @@ -746,6 +764,7 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora Item.m_Date = pInfo->m_TimeModified; } Item.m_IsDir = IsDir != 0; + Item.m_IsLink = false; Item.m_StorageType = StorageType; pSelf->m_vDemos.push_back(Item); @@ -760,21 +779,66 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora void CMenus::DemolistPopulate() { m_vDemos.clear(); - if(!str_comp(m_aCurrentDemoFolder, "demos")) - m_DemolistStorageType = IStorage::TYPE_ALL; - m_DemoPopulateStartTime = time_get_nanoseconds(); - Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); - if(g_Config.m_BrDemoFetchInfo) - FetchAllHeaders(); + int NumStoragesWithDemos = 0; + for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType) + { + if(Storage()->FolderExists("demos", StorageType)) + { + NumStoragesWithDemos++; + } + } + m_DemolistMultipleStorages = NumStoragesWithDemos > 1; - std::stable_sort(m_vDemos.begin(), m_vDemos.end()); + if(m_aCurrentDemoFolder[0] == '\0') + { + { + CDemoItem Item; + str_copy(Item.m_aFilename, "demos"); + str_copy(Item.m_aName, Localize("All combined")); + Item.m_InfosLoaded = false; + Item.m_Valid = false; + Item.m_Date = 0; + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = IStorage::TYPE_ALL; + m_vDemos.push_back(Item); + } + + for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType) + { + if(Storage()->FolderExists("demos", StorageType)) + { + CDemoItem Item; + str_copy(Item.m_aFilename, "demos"); + Storage()->GetCompletePath(StorageType, "demos", Item.m_aName, sizeof(Item.m_aName)); + str_append(Item.m_aName, "/", sizeof(Item.m_aName)); + Item.m_InfosLoaded = false; + Item.m_Valid = false; + Item.m_Date = 0; + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + m_vDemos.push_back(Item); + } + } + } + else + { + m_DemoPopulateStartTime = time_get_nanoseconds(); + Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); + + if(g_Config.m_BrDemoFetchInfo) + FetchAllHeaders(); + + std::stable_sort(m_vDemos.begin(), m_vDemos.end()); + } } void CMenus::DemolistOnUpdate(bool Reset) { if(Reset) - g_Config.m_UiDemoSelected[0] = '\0'; + m_aCurrentDemoSelectionName[0] = '\0'; else { bool Found = false; @@ -784,7 +848,7 @@ void CMenus::DemolistOnUpdate(bool Reset) { SelectedIndex++; - if(str_comp(g_Config.m_UiDemoSelected, Item.m_aName) == 0) + if(str_comp(m_aCurrentDemoSelectionName, Item.m_aName) == 0) { Found = true; break; @@ -797,7 +861,6 @@ void CMenus::DemolistOnUpdate(bool Reset) m_DemolistSelectedIndex = Reset ? !m_vDemos.empty() ? 0 : -1 : m_DemolistSelectedIndex >= (int)m_vDemos.size() ? m_vDemos.size() - 1 : m_DemolistSelectedIndex; - m_DemolistSelectedIsDir = m_DemolistSelectedIndex < 0 ? false : m_vDemos[m_DemolistSelectedIndex].m_IsDir; m_DemolistSelectedReveal = true; } @@ -838,7 +901,9 @@ void CMenus::RenderDemoList(CUIRect MainView) CDemoItem &Item = m_vDemos[m_DemolistSelectedIndex]; if(str_comp(Item.m_aFilename, "..") == 0) str_copy(aFooterLabel, Localize("Parent Folder")); - else if(m_DemolistSelectedIsDir) + else if(m_vDemos[m_DemolistSelectedIndex].m_IsLink) + str_copy(aFooterLabel, Localize("Folder Link")); + else if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) str_copy(aFooterLabel, Localize("Folder")); else if(!FetchHeader(Item)) str_copy(aFooterLabel, Localize("Invalid Demo")); @@ -1009,7 +1074,6 @@ void CMenus::RenderDemoList(CUIRect MainView) if(i + 1 < NumCols) { - //Cols[i].flags |= SPACER; Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); } } @@ -1075,7 +1139,7 @@ void CMenus::RenderDemoList(CUIRect MainView) FileIcon.x += 2.0f; const char *pIconType; - if(str_comp(Item.m_aFilename, "..") == 0) + if(Item.m_IsLink || str_comp(Item.m_aFilename, "..") == 0) pIconType = FONT_ICON_FOLDER_TREE; else if(Item.m_IsDir) pIconType = FONT_ICON_FOLDER; @@ -1088,7 +1152,9 @@ void CMenus::RenderDemoList(CUIRect MainView) TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->TextColor(IconColor); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); + TextRender()->SetRenderFlags(0); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetCurFont(nullptr); @@ -1140,7 +1206,7 @@ void CMenus::RenderDemoList(CUIRect MainView) { m_DemolistSelectedIndex = NewSelected; if(m_DemolistSelectedIndex >= 0) - str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName); + str_copy(m_aCurrentDemoSelectionName, m_vDemos[m_DemolistSelectedIndex].m_aName); DemolistOnUpdate(false); } @@ -1159,22 +1225,41 @@ void CMenus::RenderDemoList(CUIRect MainView) } static CButtonContainer s_PlayButton; - if(DoButton_Menu(&s_PlayButton, m_DemolistSelectedIsDir ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed() && m_DemoPlayerState == DEMOPLAYER_NONE)) + if(DoButton_Menu(&s_PlayButton, (m_DemolistSelectedIndex >= 0 && m_vDemos[m_DemolistSelectedIndex].m_IsDir) ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed())) { if(m_DemolistSelectedIndex >= 0) { - if(m_DemolistSelectedIsDir) // folder + if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) // folder { - if(str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0) // parent folder - fs_parent_dir(m_aCurrentDemoFolder); + const bool ParentFolder = str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0; + if(ParentFolder) // parent folder + { + str_copy(m_aCurrentDemoSelectionName, fs_filename(m_aCurrentDemoFolder)); + str_append(m_aCurrentDemoSelectionName, "/"); + if(fs_parent_dir(m_aCurrentDemoFolder)) + { + m_aCurrentDemoFolder[0] = '\0'; + if(m_DemolistStorageType == IStorage::TYPE_ALL) + { + m_aCurrentDemoSelectionName[0] = '\0'; // will select first list item + } + else + { + Storage()->GetCompletePath(m_DemolistStorageType, "demos", m_aCurrentDemoSelectionName, sizeof(m_aCurrentDemoSelectionName)); + str_append(m_aCurrentDemoSelectionName, "/"); + } + } + } else // sub folder { - str_append(m_aCurrentDemoFolder, "/"); + if(m_aCurrentDemoFolder[0] != '\0') + str_append(m_aCurrentDemoFolder, "/"); + else + m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType; str_append(m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); - m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType; } DemolistPopulate(); - DemolistOnUpdate(true); + DemolistOnUpdate(!ParentFolder); } else // file { @@ -1196,8 +1281,7 @@ void CMenus::RenderDemoList(CUIRect MainView) if(DoButton_Menu(&s_DirectoryButtonID, Localize("Demos directory"), 0, &DirectoryButton)) { char aBuf[IO_MAX_PATH_LENGTH]; - Storage()->GetCompletePath(IStorage::TYPE_SAVE, "demos", aBuf, sizeof(aBuf)); - Storage()->CreateFolder("demos", IStorage::TYPE_SAVE); + Storage()->GetCompletePath(m_DemolistSelectedIndex >= 0 ? m_vDemos[m_DemolistSelectedIndex].m_StorageType : IStorage::TYPE_SAVE, m_aCurrentDemoFolder[0] == '\0' ? "demos" : m_aCurrentDemoFolder, aBuf, sizeof(aBuf)); if(!open_file(aBuf)) { dbg_msg("menus", "couldn't open file '%s'", aBuf); @@ -1205,43 +1289,34 @@ void CMenus::RenderDemoList(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory that contains the demo files")); - if(!m_DemolistSelectedIsDir) + if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir) { static CButtonContainer s_DeleteButton; if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed())) { - if(m_DemolistSelectedIndex >= 0) - { - char aBuf[128 + IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); - PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); - return; - } + char aBuf[128 + IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); + PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); + return; } static CButtonContainer s_RenameButton; if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) { - if(m_DemolistSelectedIndex >= 0) - { - m_Popup = POPUP_RENAME_DEMO; - m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); - UI()->SetActiveItem(&m_DemoRenameInput); - return; - } + m_Popup = POPUP_RENAME_DEMO; + m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenameInput); + return; } #if defined(CONF_VIDEORECORDER) static CButtonContainer s_RenderButton; if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed())) { - if(m_DemolistSelectedIndex >= 0) - { - m_Popup = POPUP_RENDER_DEMO; - m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); - UI()->SetActiveItem(&m_DemoRenderInput); - return; - } + m_Popup = POPUP_RENDER_DEMO; + m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenderInput); + return; } #endif } diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 7cde85350dd..108e0f45d23 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -149,6 +149,17 @@ void CSpectator::ConSpectateClosest(IConsole::IResult *pResult, void *pUserData) pSelf->Spectate(NewSpectatorID); } +void CSpectator::ConMultiView(IConsole::IResult *pResult, void *pUserData) +{ + CSpectator *pSelf = (CSpectator *)pUserData; + int Input = pResult->GetInteger(0); + + if(Input == -1) + std::fill(std::begin(pSelf->GameClient()->m_aMultiViewId), std::end(pSelf->GameClient()->m_aMultiViewId), false); // remove everyone from multiview + else if(Input < MAX_CLIENTS && Input >= 0) + pSelf->GameClient()->m_aMultiViewId[Input] = !pSelf->GameClient()->m_aMultiViewId[Input]; // activate or deactivate one player from multiview +} + CSpectator::CSpectator() { OnReset(); @@ -162,6 +173,7 @@ void CSpectator::OnConsoleInit() Console()->Register("spectate_next", "", CFGFLAG_CLIENT, ConSpectateNext, this, "Spectate the next player"); Console()->Register("spectate_previous", "", CFGFLAG_CLIENT, ConSpectatePrevious, this, "Spectate the previous player"); Console()->Register("spectate_closest", "", CFGFLAG_CLIENT, ConSpectateClosest, this, "Spectate the closest player"); + Console()->Register("spectate_multiview", "i[id]", CFGFLAG_CLIENT, ConMultiView, this, "Add/remove Client-IDs to spectate them exclusivly (-1 to reset)"); } bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType) @@ -186,7 +198,12 @@ void CSpectator::OnRender() if(m_WasActive) { if(m_SelectedSpectatorID != NO_SELECTION) - Spectate(m_SelectedSpectatorID); + { + if(m_SelectedSpectatorID != MULTI_VIEW) + Spectate(m_SelectedSpectatorID); + + GameClient()->m_MultiViewActivated = m_SelectedSpectatorID == MULTI_VIEW; + } m_WasActive = false; } return; @@ -213,6 +230,7 @@ void CSpectator::OnRender() float TeeSizeMod = 1.0f; float RoundRadius = 30.0f; bool Selected = false; + bool MultiViewSelected = false; int TotalPlayers = 0; int PerLine = 8; float BoxMove = -10.0f; @@ -253,34 +271,48 @@ void CSpectator::OnRender() if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecID == SPEC_FREEVIEW) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorID == SPEC_FREEVIEW)) { - Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + } + + if(GameClient()->m_MultiViewActivated) + { + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); } if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientID >= 0 && m_pClient->m_DemoSpecID == SPEC_FOLLOW) { - Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 310.0f), Height / 2.0f - 280.0f, 270.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); + Graphics()->DrawRect(Width / 2.0f - (ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), Height / 2.0f - 280.0f, ((ObjWidth * 2.0f) / 3.0f) - 40.0f, 60.0f, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 20.0f); } - if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 290 + 10.0f) && + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) { m_SelectedSpectatorID = SPEC_FREEVIEW; Selected = true; } TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f); - TextRender()->Text(Width / 2.0f - (ObjWidth - 60.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Free-View"), -1.0f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Free-View"), -1.0f); + + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && + m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) + { + m_SelectedSpectatorID = MULTI_VIEW; + MultiViewSelected = true; + } + TextRender()->TextColor(1.0f, 1.0f, 1.0f, MultiViewSelected ? 1.0f : 0.5f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f / 3.0f), Height / 2.0f - 280.f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Multi-View"), -1.0f); if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_LocalClientID >= 0) { Selected = false; - if(m_SelectorMouse.x > -(ObjWidth - 290.0f) && m_SelectorMouse.x <= -(ObjWidth - 590.0f) && + if(m_SelectorMouse.x >= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) && m_SelectorMouse.x <= -(ObjWidth - 20.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f) + ((ObjWidth * 2.0f) / 3.0f) - 40.0f && m_SelectorMouse.y >= -280.0f && m_SelectorMouse.y <= -220.0f) { m_SelectedSpectatorID = SPEC_FOLLOW; Selected = true; } TextRender()->TextColor(1.0f, 1.0f, 1.0f, Selected ? 1.0f : 0.5f); - TextRender()->Text(Width / 2.0f - (ObjWidth - 350.0f), Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Follow"), -1.0f); + TextRender()->Text(Width / 2.0f - (ObjWidth - 40.0f) + (ObjWidth * 2.0f * 2.0f / 3.0f), Height / 2.0f - 280.0f + (60.f - BigFontSize) / 2.f, BigFontSize, Localize("Follow"), -1.0f); } float x = -(ObjWidth - 35.0f), y = StartY; diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h index ff1deac00b3..711d697a64c 100644 --- a/src/game/client/components/spectator.h +++ b/src/game/client/components/spectator.h @@ -10,6 +10,7 @@ class CSpectator : public CComponent { enum { + MULTI_VIEW = -4, NO_SELECTION = -3, }; @@ -30,6 +31,7 @@ class CSpectator : public CComponent static void ConSpectateNext(IConsole::IResult *pResult, void *pUserData); static void ConSpectatePrevious(IConsole::IResult *pResult, void *pUserData); static void ConSpectateClosest(IConsole::IResult *pResult, void *pUserData); + static void ConMultiView(IConsole::IResult *pResult, void *pUserData); public: CSpectator(); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index d551f6bd0d5..6825f165bb1 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -659,7 +659,11 @@ void CGameClient::UpdatePositions() // spectator position if(m_Snap.m_SpecInfo.m_Active) { - if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) + if(m_MultiViewActivated) + { + HandleMultiView(); + } + else if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_DemoSpecID != SPEC_FOLLOW && m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW) { m_Snap.m_SpecInfo.m_Position = mix( vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_Y), @@ -678,11 +682,28 @@ void CGameClient::UpdatePositions() } } + if(!m_MultiViewActivated && m_MultiView.m_IsInit) + ResetMultiView(); + UpdateRenderedCharacters(); } void CGameClient::OnRender() { + // check if multi view got activated + if(!m_MultiView.m_IsInit && m_MultiViewActivated) + { + int TeamId = 0; + if(m_Snap.m_SpecInfo.m_SpectatorID >= 0) + TeamId = m_Teams.Team(m_Snap.m_SpecInfo.m_SpectatorID); + + if(!InitMultiViewFromFreeview(TeamId)) + { + dbg_msg("MultiView", "No players found to spectate"); + m_MultiViewActivated = false; + } + } + // update the local character and spectate position UpdatePositions(); @@ -898,6 +919,18 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm pChar->ResetPrediction(); m_GameWorld.ReleaseHooked(pMsg->m_Victim); } + + // if we are spectating a static id set (team 0) and somebody killed, we remove him from the list + if(IsMultiViewIdSet() && m_aMultiViewId[pMsg->m_Victim] && !m_aClients[pMsg->m_Victim].m_Spec) + { + // is multi view even activated and we are not spectating a solo guy + if(m_MultiViewActivated && !m_MultiView.m_Solo && m_MultiView.m_Team == 0) + m_aMultiViewId[pMsg->m_Victim] = false; + + // if everyone of a team killed, we have no ids to spectate anymore, so we disable multi view + if(!IsMultiViewIdSet()) + m_MultiViewActivated = false; + } } } @@ -1734,7 +1767,17 @@ void CGameClient::OnNewSnapshot() m_aDDRaceMsgSent[i] = true; } - if(m_aShowOthers[g_Config.m_ClDummy] == SHOW_OTHERS_NOT_SET || m_aShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers) + if(m_MultiViewActivated) + { + // dont show other teams while spectating in multi view + CNetMsg_Cl_ShowOthers Msg; + Msg.m_Show = SHOW_OTHERS_ONLY_TEAM; + Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); + + // update state + m_aShowOthers[g_Config.m_ClDummy] = SHOW_OTHERS_ONLY_TEAM; + } + else if(m_aShowOthers[g_Config.m_ClDummy] == SHOW_OTHERS_NOT_SET || m_aShowOthers[g_Config.m_ClDummy] != g_Config.m_ClShowOthers) { { CNetMsg_Cl_ShowOthers Msg; @@ -2190,7 +2233,7 @@ void CGameClient::SendInfo(bool Start) Msg.m_ColorFeet = g_Config.m_ClPlayerColorFeet; CMsgPacker Packer(&Msg); Msg.Pack(&Packer); - Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL); + Client()->SendMsg(IClient::CONN_MAIN, &Packer, MSGFLAG_VITAL | MSGFLAG_FLUSH); m_aCheckInfo[0] = -1; } else @@ -3433,3 +3476,214 @@ void CGameClient::SnapCollectEntities() m_vSnapEntities.push_back({Ent.m_Item, Ent.m_pData, pDataEx}); } } + +void CGameClient::HandleMultiView() +{ + bool IsTeamZero = IsMultiViewIdSet(); + bool Init = false; + int AmountPlayers = 0; + vec2 Minpos, Maxpos; + float TmpVel = 0.0f; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + // look at players who are vanished + if(m_MultiView.m_aVanish[i]) + { + // not in freeze anymore and the delay is over + if(m_MultiView.m_aLastFreeze[i] + 6.0f <= Client()->LocalTime() && m_aClients[i].m_FreezeEnd == 0) + { + m_MultiView.m_aVanish[i] = false; + m_MultiView.m_aLastFreeze[i] = 0.0f; + } + } + + // we look at team 0 and the player is not in the spec list + if(IsTeamZero && !m_aMultiViewId[i]) + continue; + + // player is vanished + if(m_MultiView.m_aVanish[i]) + continue; + + // the player is not in the team we are spectating + if(m_Teams.Team(i) != m_MultiView.m_Team) + continue; + + vec2 PlayerPos; + if(m_Snap.m_aCharacters[i].m_Active) + PlayerPos = vec2(m_aClients[i].m_RenderPos.x, m_aClients[i].m_RenderPos.y); + else if(m_aClients[i].m_Spec) // tee is in spec + PlayerPos = m_aClients[i].m_SpecChar; + else + continue; + + // player is far away and frozen + if(distance(m_MultiView.m_OldPos, PlayerPos) > 1100 && m_aClients[i].m_FreezeEnd != 0) + { + // check if the player is frozen for more than 3 seconds, if so vanish him + if(m_MultiView.m_aLastFreeze[i] == 0.0f) + m_MultiView.m_aLastFreeze[i] = Client()->LocalTime(); + else if(m_MultiView.m_aLastFreeze[i] + 3.0f <= Client()->LocalTime()) + m_MultiView.m_aVanish[i] = true; + } + else if(m_MultiView.m_aLastFreeze[i] != 0) + m_MultiView.m_aLastFreeze[i] = 0; + + // set the minimum and maximum position + if(!Init) + { + Minpos = PlayerPos; + Maxpos = PlayerPos; + Init = true; + } + else + { + Minpos.x = std::min(Minpos.x, PlayerPos.x); + Maxpos.x = std::max(Maxpos.x, PlayerPos.x); + Minpos.y = std::min(Minpos.y, PlayerPos.y); + Maxpos.y = std::max(Maxpos.y, PlayerPos.y); + } + + // sum up the velocity of all players we are spectating + const CNetObj_Character &CurrentCharacter = m_Snap.m_aCharacters[i].m_Cur; + TmpVel += (length(vec2(CurrentCharacter.m_VelX / 256.0f, CurrentCharacter.m_VelY / 256.0f)) * 50) / 32.0f; + AmountPlayers++; + } + + // if we have found no players, we disable multi view + if(AmountPlayers == 0) + { + m_MultiViewActivated = false; + return; + } + + vec2 TargetPos = vec2((Minpos.x + Maxpos.x) / 2.0f, (Minpos.y + Maxpos.y) / 2.0f); + // dont hide the position hud if its only one player + m_MultiViewShowHud = AmountPlayers == 1; + // get the average velocity + float AvgVel = clamp(TmpVel / AmountPlayers ? TmpVel / (float)AmountPlayers : 0.0f, 0.0f, 1000.0f); + + if(m_MultiView.m_OldPersonalZoom == m_MultiViewPersonalZoom) + m_Camera.SetZoom(CalculateMultiViewZoom(Minpos, Maxpos, AvgVel), g_Config.m_ClMultiViewZoomSmoothness); + else + m_Camera.SetZoom(CalculateMultiViewZoom(Minpos, Maxpos, AvgVel), 50); + + m_Snap.m_SpecInfo.m_Position = m_MultiView.m_OldPos + ((TargetPos - m_MultiView.m_OldPos) * CalculateMultiViewMultiplier(TargetPos)); + m_MultiView.m_OldPos = m_Snap.m_SpecInfo.m_Position; + m_Snap.m_SpecInfo.m_UsePosition = true; +} + +bool CGameClient::InitMultiViewFromFreeview(int Team) +{ + float Width, Height; + CleanMultiViewIds(); + m_MultiView.m_IsInit = true; + + // get the current view coordinates + RenderTools()->CalcScreenParams(Graphics()->ScreenAspect(), m_Camera.m_Zoom, &Width, &Height); + vec2 AxisX = vec2(m_Camera.m_Center.x - (Width / 2), m_Camera.m_Center.x + (Width / 2)); + vec2 AxisY = vec2(m_Camera.m_Center.y - (Height / 2), m_Camera.m_Center.y + (Height / 2)); + + m_MultiView.m_Team = Team; + + if(Team > 0) + return true; // spectating a team, not necessary to search the players in view + else + { + int Count = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + vec2 PlayerPos; + + // get the position of the player + if(m_Snap.m_aCharacters[i].m_Active) + PlayerPos = vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y); + else if(m_aClients[i].m_Spec) + PlayerPos = m_aClients[i].m_SpecChar; + else + continue; + + // player isnt in the correct team + if(m_Teams.Team(i) != Team) + continue; + + if(PlayerPos.x != 0 && PlayerPos.y != 0) + { + // is the player in view + if(PlayerPos.x > AxisX.x && PlayerPos.x < AxisX.y && PlayerPos.y > AxisY.x && PlayerPos.y < AxisY.y) + { + m_aMultiViewId[i] = true; + Count++; + } + } + } + + // we are spectating only one player + m_MultiView.m_Solo = Count == 1; + + // found players to spectate + return Count > 0; + } +} + +float CGameClient::CalculateMultiViewMultiplier(vec2 CameraPos) +{ + float MaxCameraDist = 200.0f; + float MinCameraDist = 20.0f; + float MaxVel = 0.1f; + float MinVel = 0.007f; + + float CurrentCameraDistance = distance(m_MultiView.m_OldPos, CameraPos); + + return clamp(MapValue(MaxCameraDist, MinCameraDist, MaxVel, MinVel, CurrentCameraDistance), MinVel, 1.0f); +} + +float CGameClient::CalculateMultiViewZoom(vec2 MinPos, vec2 MaxPos, float Vel) +{ + float Ratio = Graphics()->ScreenAspect(); + float ZoomX = 0.0f, ZoomY; + + // only calc two axis if the aspect ratio is not 1:1 + if(Ratio != 1.0f) + ZoomX = (0.001309f - 0.000328 * Ratio) * (MaxPos.x - MinPos.x) + (0.741413f - 0.032959 * Ratio); + + // calculate the according zoom with linear function + ZoomY = 0.001309f * (MaxPos.y - MinPos.y) + 0.741413f; + // choose the highest zoom + float Zoom = std::max(ZoomX, ZoomY); + // zoom out to maximum 10 percent of the current zoom for 70 velocity + float Diff = clamp(MapValue(70.0f, 15.0f, Zoom * 0.10f, 0.0f, Vel), 0.0f, Zoom * 0.10f); + // zoom should stay inbetween 1.1 and 20.0 + Zoom = clamp(Zoom + Diff, 1.1f, 20.0f); + // add the user preference + Zoom -= (Zoom * 0.075f) * m_MultiViewPersonalZoom; + m_MultiView.m_OldPersonalZoom = m_MultiViewPersonalZoom; + + return Zoom; +} + +float CGameClient::MapValue(float MaxValue, float MinValue, float MaxRange, float MinRange, float Value) +{ + return (MaxRange - MinRange) / (MaxValue - MinValue) * (Value - MinValue) + MinRange; +} + +void CGameClient::ResetMultiView() +{ + m_MultiView.m_Solo = false; + m_MultiView.m_IsInit = false; + m_MultiViewActivated = false; + m_MultiViewPersonalZoom = 0; +} + +void CGameClient::CleanMultiViewIds() +{ + std::fill(std::begin(m_aMultiViewId), std::end(m_aMultiViewId), false); + std::fill(std::begin(m_MultiView.m_aLastFreeze), std::end(m_MultiView.m_aLastFreeze), 0.0f); + std::fill(std::begin(m_MultiView.m_aVanish), std::end(m_MultiView.m_aVanish), false); +} + +bool CGameClient::IsMultiViewIdSet() +{ + return std::any_of(std::begin(m_aMultiViewId), std::end(m_aMultiViewId), [](bool IsSet) { return IsSet; }); +} diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index c64e9340538..5efc06dbb52 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -731,6 +731,11 @@ class CGameClient : public IGameClient const std::vector &SnapEntities() { return m_vSnapEntities; } + int m_MultiViewPersonalZoom; + bool m_MultiViewShowHud; + bool m_MultiViewActivated; + bool m_aMultiViewId[MAX_CLIENTS]; + private: std::vector m_vSnapEntities; void SnapCollectEntities(); @@ -759,6 +764,28 @@ class CGameClient : public IGameClient float m_LastZoom; float m_LastScreenAspect; bool m_LastDummyConnected; + + void ResetMultiView(); + void HandleMultiView(); + bool IsMultiViewIdSet(); + void CleanMultiViewIds(); + bool InitMultiViewFromFreeview(int Team); + float CalculateMultiViewMultiplier(vec2 CameraPos); + float CalculateMultiViewZoom(vec2 MinPos, vec2 MaxPos, float Vel); + float MapValue(float MaxValue, float MinValue, float MaxRange, float MinRange, float Value); + + struct SMultiView + { + bool m_Solo; + bool m_IsInit; + bool m_aVanish[MAX_CLIENTS]; + vec2 m_OldPos; + int m_Team; + int m_OldPersonalZoom; + float m_aLastFreeze[MAX_CLIENTS]; + }; + + SMultiView m_MultiView; }; ColorRGBA CalculateNameColor(ColorHSLA TextColorHSL); diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp index f05978fa658..058c0253661 100644 --- a/src/game/client/prediction/entities/character.cpp +++ b/src/game/client/prediction/entities/character.cpp @@ -493,7 +493,7 @@ void CCharacter::GiveNinja() m_Core.m_aWeapons[WEAPON_NINJA].m_Ammo = -1; if(m_Core.m_ActiveWeapon != WEAPON_NINJA) m_LastWeapon = m_Core.m_ActiveWeapon; - m_Core.m_ActiveWeapon = WEAPON_NINJA; + SetActiveWeapon(WEAPON_NINJA); } void CCharacter::OnPredictedInput(CNetObj_PlayerInput *pNewInput) @@ -1029,9 +1029,9 @@ void CCharacter::DDRacePostCoreTick() HandleSkippableTiles(CurrentIndex); // handle Anti-Skip tiles - std::list Indices = Collision()->GetMapIndices(m_PrevPos, m_Pos); - if(!Indices.empty()) - for(int Index : Indices) + std::vector vIndices = Collision()->GetMapIndices(m_PrevPos, m_Pos); + if(!vIndices.empty()) + for(int Index : vIndices) HandleTiles(Index); else { @@ -1341,7 +1341,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende // in most cases the reload timer can be determined from the last attack tick // (this is only needed for autofire weapons to prevent the predicted reload timer from desyncing) - if(IsLocal && m_Core.m_ActiveWeapon != WEAPON_HAMMER) + if(IsLocal && m_Core.m_ActiveWeapon != WEAPON_HAMMER && !m_Core.m_aWeapons[WEAPON_NINJA].m_Got) { if(maximum(m_LastTuneZoneTick, m_LastWeaponSwitchTick) + GameWorld()->GameTickSpeed() < GameWorld()->GameTick()) { diff --git a/src/game/client/prediction/entities/pickup.cpp b/src/game/client/prediction/entities/pickup.cpp index 5e417e420d0..86f66e55cb3 100644 --- a/src/game/client/prediction/entities/pickup.cpp +++ b/src/game/client/prediction/entities/pickup.cpp @@ -7,6 +7,8 @@ #include #include +static constexpr int gs_PickupPhysSize = 14; + void CPickup::Tick() { Move(); @@ -143,7 +145,7 @@ void CPickup::Move() } CPickup::CPickup(CGameWorld *pGameWorld, int ID, const CPickupData *pPickup) : - CEntity(pGameWorld, CGameWorld::ENTTYPE_PICKUP) + CEntity(pGameWorld, CGameWorld::ENTTYPE_PICKUP, vec2(0, 0), gs_PickupPhysSize) { m_Pos = pPickup->m_Pos; m_Type = pPickup->m_Type; diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index 7ae7ffc0b15..63d250c2250 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -287,10 +287,9 @@ CCharacter *CGameWorld::IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, v return pClosest; } -std::list CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis) +std::vector CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis) { - std::list listOfChars; - + std::vector vpCharacters; CCharacter *pChr = (CCharacter *)FindFirst(CGameWorld::ENTTYPE_CHARACTER); for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) { @@ -303,11 +302,11 @@ std::list CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 float Len = distance(pChr->m_Pos, IntersectPos); if(Len < pChr->m_ProximityRadius + Radius) { - listOfChars.push_back(pChr); + vpCharacters.push_back(pChr); } } } - return listOfChars; + return vpCharacters; } void CGameWorld::ReleaseHooked(int ClientID) diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h index 79d855dc6c4..696956f8cb3 100644 --- a/src/game/client/prediction/gameworld.h +++ b/src/game/client/prediction/gameworld.h @@ -7,6 +7,7 @@ #include #include +#include class CCollision; class CCharacter; @@ -49,7 +50,7 @@ class CGameWorld // DDRace void ReleaseHooked(int ClientID); - std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); + std::vector IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); int m_GameTick; int m_GameTickSpeed; diff --git a/src/game/client/race.cpp b/src/game/client/race.cpp index e52e441ff13..2a68f0479ff 100644 --- a/src/game/client/race.cpp +++ b/src/game/client/race.cpp @@ -76,9 +76,9 @@ bool CRaceHelper::IsStart(CGameClient *pClient, vec2 Prev, vec2 Pos) } else { - std::list Indices = pCollision->GetMapIndices(Prev, Pos); - if(!Indices.empty()) - for(int &Indice : Indices) + std::vector vIndices = pCollision->GetMapIndices(Prev, Pos); + if(!vIndices.empty()) + for(int &Indice : vIndices) { if(pCollision->GetTileIndex(Indice) == TILE_START) return true; diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index df1626ccd0b..70a26100696 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -580,24 +580,33 @@ struct SCursorAndBoundingBox static SCursorAndBoundingBox CalcFontSizeCursorHeightAndBoundingBox(ITextRender *pTextRender, const char *pText, int Flags, float &Size, float MaxWidth, const SLabelProperties &LabelProps) { + const float MinFontSize = 5.0f; + const float MaxTextWidth = LabelProps.m_MaxWidth != -1.0f ? LabelProps.m_MaxWidth : MaxWidth; + const int FlagsWithoutStop = Flags & ~(TEXTFLAG_STOP_AT_END | TEXTFLAG_ELLIPSIS_AT_END); + const float MaxTextWidthWithoutStop = Flags == FlagsWithoutStop ? LabelProps.m_MaxWidth : -1.0f; + float TextBoundingHeight = 0.0f; float TextHeight = 0.0f; int LineCount = 0; - float MaxTextWidth = LabelProps.m_MaxWidth != -1 ? LabelProps.m_MaxWidth : MaxWidth; STextSizeProperties TextSizeProps{}; TextSizeProps.m_pHeight = &TextHeight; TextSizeProps.m_pMaxCharacterHeightInLine = &TextBoundingHeight; TextSizeProps.m_pLineCount = &LineCount; - float TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, TextSizeProps); - while(TextWidth > MaxTextWidth + 0.001f) + + float TextWidth; + do { - if(!LabelProps.m_EnableWidthCheck) - break; - if(Size < 4.0f) + Size = maximum(Size, MinFontSize); + // Only consider stop-at-end and ellipsis-at-end when minimum font size reached or font scaling disabled + if((Size == MinFontSize || !LabelProps.m_EnableWidthCheck) && Flags != FlagsWithoutStop) + TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, TextSizeProps); + else + TextWidth = pTextRender->TextWidth(Size, pText, -1, MaxTextWidthWithoutStop, FlagsWithoutStop, TextSizeProps); + if(TextWidth <= MaxTextWidth + 0.001f || !LabelProps.m_EnableWidthCheck || Size == MinFontSize) break; - Size -= 1.0f; - TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, TextSizeProps); - } + Size--; + } while(true); + SCursorAndBoundingBox Res{}; Res.m_TextSize = vec2(TextWidth, TextHeight); Res.m_BiggestCharacterHeight = TextBoundingHeight; @@ -605,6 +614,17 @@ static SCursorAndBoundingBox CalcFontSizeCursorHeightAndBoundingBox(ITextRender return Res; } +static int GetFlagsForLabelProperties(const SLabelProperties &LabelProps, const CTextCursor *pReadCursor) +{ + if(pReadCursor != nullptr) + return pReadCursor->m_Flags & ~TEXTFLAG_RENDER; + + int Flags = 0; + Flags |= LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; + Flags |= LabelProps.m_EllipsisAtEnd ? TEXTFLAG_ELLIPSIS_AT_END : 0; + return Flags; +} + vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, const float *pBiggestCharHeight) { vec2 Cursor(pRect->x, pRect->y); @@ -634,7 +654,7 @@ vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align, c void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps) { - const int Flags = LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; + const int Flags = GetFlagsForLabelProperties(LabelProps, nullptr); const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); const vec2 CursorPos = CalcAlignedCursorPos(pRect, TextBounds.m_TextSize, Align, TextBounds.m_LineCount == 1 ? &TextBounds.m_BiggestCharacterHeight : nullptr); @@ -646,7 +666,7 @@ void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) { - const int Flags = pReadCursor ? (pReadCursor->m_Flags & ~TEXTFLAG_RENDER) : LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; + const int Flags = GetFlagsForLabelProperties(LabelProps, pReadCursor); const SCursorAndBoundingBox TextBounds = CalcFontSizeCursorHeightAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); CTextCursor Cursor; @@ -671,7 +691,7 @@ void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, cons RectEl.m_Cursor = Cursor; } -void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth, bool StopAtEnd, int StrLen, const CTextCursor *pReadCursor) +void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) { bool NeedsRecreate = false; bool ColorChanged = RectEl.m_TextColor != TextRender()->GetTextColor() || RectEl.m_TextOutlineColor != TextRender()->GetTextOutlineColor(); @@ -714,10 +734,7 @@ void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRe TmpRect.w = pRect->w; TmpRect.h = pRect->h; - SLabelProperties Props; - Props.m_MaxWidth = MaxWidth; - Props.m_StopAtEnd = StopAtEnd; - DoLabel(RectEl, &TmpRect, pText, Size, TEXTALIGN_TL, Props, StrLen, pReadCursor); + DoLabel(RectEl, &TmpRect, pText, Size, TEXTALIGN_TL, LabelProps, StrLen, pReadCursor); } ColorRGBA ColorText(RectEl.m_TextColor); @@ -1711,32 +1728,15 @@ CUI::EPopupMenuFunctionResult CUI::PopupColorPicker(void *pContext, CUIRect View PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f, A / 255.0f); - const auto RotateByteLeft = [pColorPicker](unsigned Num) { - if(pColorPicker->m_Alpha) - { - // ARGB -> RGBA (internal -> displayed) - return ((Num & 0xFF000000u) >> 24) | (Num << 8); - } - return Num; - }; - const auto RotateByteRight = [pColorPicker](unsigned Num) { - if(pColorPicker->m_Alpha) - { - // RGBA -> ARGB (displayed -> internal) - return ((Num & 0xFFu) << 24) | (Num >> 8); - } - return Num; - }; - SValueSelectorProperties Props; Props.m_UseScroll = false; Props.m_IsHex = true; Props.m_HexPrefix = pColorPicker->m_Alpha ? 8 : 6; - const unsigned Hex = RotateByteLeft(color_cast(PickerColorHSV).Pack(pColorPicker->m_Alpha)); + const unsigned Hex = color_cast(PickerColorHSV).PackAlphaLast(pColorPicker->m_Alpha); const unsigned NewHex = pUI->DoValueSelector(&pColorPicker->m_aValueSelectorIds[4], &HexRect, "Hex:", Hex, 0, pColorPicker->m_Alpha ? 0xFFFFFFFFll : 0xFFFFFFll, Props); if(Hex != NewHex) { - PickerColorHSV = color_cast(ColorRGBA(RotateByteRight(NewHex), pColorPicker->m_Alpha)); + PickerColorHSV = color_cast(ColorRGBA::UnpackAlphaLast(NewHex, pColorPicker->m_Alpha)); if(!pColorPicker->m_Alpha) PickerColorHSV.a = A / 255.0f; } diff --git a/src/game/client/ui.h b/src/game/client/ui.h index bf20f900ca5..dbd498b6bb7 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -193,6 +193,7 @@ struct SLabelProperties { float m_MaxWidth = -1; bool m_StopAtEnd = false; + bool m_EllipsisAtEnd = false; bool m_EnableWidthCheck = true; }; @@ -488,7 +489,7 @@ class CUI void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}); void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); - void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, bool StopAtEnd = false, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); + void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); diff --git a/src/game/collision.cpp b/src/game/collision.cpp index 4d2f6560a4f..35718b5897a 100644 --- a/src/game/collision.cpp +++ b/src/game/collision.cpp @@ -880,9 +880,9 @@ int CCollision::GetMapIndex(vec2 Pos) const return -1; } -std::list CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) const +std::vector CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) const { - std::list Indices; + std::vector vIndices; float d = distance(PrevPos, Pos); int End(d + 1); if(!d) @@ -893,11 +893,11 @@ std::list CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxInd if(TileExists(Index)) { - Indices.push_back(Index); - return Indices; + vIndices.push_back(Index); + return vIndices; } else - return Indices; + return vIndices; } else { @@ -911,14 +911,14 @@ std::list CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxInd int Index = Ny * m_Width + Nx; if(TileExists(Index) && LastIndex != Index) { - if(MaxIndices && Indices.size() > MaxIndices) - return Indices; - Indices.push_back(Index); + if(MaxIndices && vIndices.size() > MaxIndices) + return vIndices; + vIndices.push_back(Index); LastIndex = Index; } } - return Indices; + return vIndices; } } diff --git a/src/game/collision.h b/src/game/collision.h index 2f7908df8b2..a8ae749c348 100644 --- a/src/game/collision.h +++ b/src/game/collision.h @@ -6,7 +6,7 @@ #include #include -#include +#include enum { @@ -73,7 +73,7 @@ class CCollision int Entity(int x, int y, int Layer) const; int GetPureMapIndex(float x, float y) const; int GetPureMapIndex(vec2 Pos) const { return GetPureMapIndex(Pos.x, Pos.y); } - std::list GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices = 0) const; + std::vector GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices = 0) const; int GetMapIndex(vec2 Pos) const; bool TileExists(int Index) const; bool TileExistsNext(int Index) const; diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp index 4b8b481d53c..d339a26010d 100644 --- a/src/game/editor/auto_map.cpp +++ b/src/game/editor/auto_map.cpp @@ -470,7 +470,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO for(int x = 0; x < pLayer->m_Width; x++) { CTile *pTile = &(pLayer->m_pTiles[y * pLayer->m_Width + x]); - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); for(size_t i = 0; i < pRun->m_vIndexRules.size(); ++i) { diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 8d39d10d71b..b102c7cbf78 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -73,6 +74,11 @@ bool CEditor::IsVanillaImage(const char *pImage) return std::any_of(std::begin(VANILLA_IMAGES), std::end(VANILLA_IMAGES), [pImage](const char *pVanillaImage) { return str_comp(pImage, pVanillaImage) == 0; }); } +static const char *FILETYPE_EXTENSIONS[CEditor::NUM_FILETYPES] = { + ".map", + ".png", + ".opus"}; + const void *CEditor::ms_pUiGotContext; enum @@ -207,7 +213,7 @@ void CLayerGroup::Render() void CLayerGroup::AddLayer(CLayer *pLayer) { - m_pMap->m_Modified = true; + m_pMap->OnModify(); m_vpLayers.push_back(pLayer); } @@ -217,7 +223,7 @@ void CLayerGroup::DeleteLayer(int Index) return; delete m_vpLayers[Index]; m_vpLayers.erase(m_vpLayers.begin() + Index); - m_pMap->m_Modified = true; + m_pMap->OnModify(); } void CLayerGroup::DuplicateLayer(int Index) @@ -228,7 +234,7 @@ void CLayerGroup::DuplicateLayer(int Index) auto *pDup = m_vpLayers[Index]->Duplicate(); m_vpLayers.insert(m_vpLayers.begin() + Index + 1, pDup); - m_pMap->m_Modified = true; + m_pMap->OnModify(); } void CLayerGroup::GetSize(float *pWidth, float *pHeight) const @@ -252,7 +258,7 @@ int CLayerGroup::SwapLayers(int Index0, int Index1) return Index0; if(Index0 == Index1) return Index0; - m_pMap->m_Modified = true; + m_pMap->OnModify(); std::swap(m_vpLayers[Index0], m_vpLayers[Index1]); return Index1; } @@ -443,7 +449,12 @@ int CEditor::DoButton_MenuItem(const void *pID, const char *pText, int Checked, CUIRect Rect; pRect->VMargin(5.0f, &Rect); - UI()->DoLabel(&Rect, pText, 10.0f, TEXTALIGN_ML); + + SLabelProperties Props; + Props.m_MaxWidth = Rect.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&Rect, pText, 10.0f, TEXTALIGN_ML, Props); + return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } @@ -457,7 +468,15 @@ int CEditor::DoButton_Tab(const void *pID, const char *pText, int Checked, const int CEditor::DoButton_Ex(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Flags, const char *pToolTip, int Corners, float FontSize) { pRect->Draw(GetButtonColor(pID, Checked), Corners, 3.0f); - UI()->DoLabel(pRect, pText, FontSize, TEXTALIGN_MC); + + CUIRect Rect; + pRect->VMargin(pRect->w > 20.0f ? 5.0f : 0.0f, &Rect); + + SLabelProperties Props; + Props.m_MaxWidth = Rect.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&Rect, pText, FontSize, TEXTALIGN_MC, Props); + return DoButton_Editor_Common(pID, pText, Checked, pRect, Flags, pToolTip); } @@ -492,7 +511,13 @@ int CEditor::DoButton_DraggableEx(const void *pID, const char *pText, int Checke { pRect->Draw(GetButtonColor(pID, Checked), Corners, 3.0f); - UI()->DoLabel(pRect, pText, FontSize, TEXTALIGN_MC); + CUIRect Rect; + pRect->VMargin(pRect->w > 20.0f ? 5.0f : 0.0f, &Rect); + + SLabelProperties Props; + Props.m_MaxWidth = Rect.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&Rect, pText, FontSize, TEXTALIGN_MC, Props); if(UI()->MouseInside(pRect)) { @@ -798,7 +823,7 @@ bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUse CEditor *pEditor = (CEditor *)pUser; if(pEditor->Load(pFileName, StorageType)) { - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; + pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder || (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentLink && str_comp(pEditor->m_aFileDialogCurrentLink, "themes") == 0)); pEditor->m_Dialog = DIALOG_NONE; return true; } @@ -827,8 +852,10 @@ bool CEditor::CallbackAppendMap(const char *pFileName, int StorageType, void *pU bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser) { + dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE"); + CEditor *pEditor = static_cast(pUser); - char aBuf[1024]; + char aBuf[IO_MAX_PATH_LENGTH]; // add map extension if(!str_endswith(pFileName, ".map")) { @@ -836,25 +863,37 @@ bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse pFileName = aBuf; } + // Save map to specified file if(pEditor->Save(pFileName)) { str_copy(pEditor->m_aFileName, pFileName); - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; + pEditor->m_ValidSaveFilename = true; pEditor->m_Map.m_Modified = false; - pEditor->m_Dialog = DIALOG_NONE; - return true; } else { pEditor->ShowFileDialogError("Failed to save map to file '%s'.", pFileName); return false; } + + // Also update autosave if it's older than half the configured autosave interval, so we also have periodic backups. + const float Time = pEditor->Client()->GlobalTime(); + if(g_Config.m_EdAutosaveInterval > 0 && pEditor->m_Map.m_LastSaveTime < Time && Time - pEditor->m_Map.m_LastSaveTime > 30 * g_Config.m_EdAutosaveInterval) + { + if(!pEditor->PerformAutosave()) + return false; + } + + pEditor->m_Dialog = DIALOG_NONE; + return true; } bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser) { + dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE"); + CEditor *pEditor = static_cast(pUser); - char aBuf[1024]; + char aBuf[IO_MAX_PATH_LENGTH]; // add map extension if(!str_endswith(pFileName, ".map")) { @@ -864,7 +903,6 @@ bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void * if(pEditor->Save(pFileName)) { - pEditor->m_Map.m_Modified = false; pEditor->m_Dialog = DIALOG_NONE; return true; } @@ -875,7 +913,7 @@ bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void * } } -void CEditor::DoToolbar(CUIRect ToolBar) +void CEditor::DoToolbarLayers(CUIRect ToolBar) { const bool ModPressed = Input()->ModifierIsPressed(); const bool ShiftPressed = Input()->ShiftIsPressed(); @@ -1268,6 +1306,41 @@ void CEditor::DoToolbar(CUIRect ToolBar) } } +void CEditor::DoToolbarSounds(CUIRect ToolBar) +{ + CUIRect ToolBarTop, ToolBarBottom, Button; + ToolBar.HSplitMid(&ToolBarTop, &ToolBarBottom, 5.0f); + + if(m_SelectedSound >= 0 && (size_t)m_SelectedSound < m_Map.m_vpSounds.size()) + { + const CEditorSound *pSelectedSound = m_Map.m_vpSounds[m_SelectedSound]; + + // play/stop button + { + ToolBarBottom.VSplitLeft(ToolBarBottom.h, &Button, &ToolBarBottom); + static int s_PlayStopButton; + if(DoButton_FontIcon(&s_PlayStopButton, Sound()->IsPlaying(pSelectedSound->m_SoundID) ? FONT_ICON_STOP : FONT_ICON_PLAY, 0, &Button, 0, "Play/stop audio preview", IGraphics::CORNER_ALL) || + (m_Dialog == DIALOG_NONE && m_EditBoxActive == 0 && Input()->KeyPress(KEY_SPACE))) + { + if(Sound()->IsPlaying(pSelectedSound->m_SoundID)) + Sound()->Stop(pSelectedSound->m_SoundID); + else + Sound()->Play(CSounds::CHN_GUI, pSelectedSound->m_SoundID, 0); + } + } + + // duration + { + ToolBarBottom.VSplitLeft(5.0f, nullptr, &ToolBarBottom); + char aDuration[32]; + char aDurationLabel[64]; + str_time_float(Sound()->GetSampleDuration(pSelectedSound->m_SoundID), TIME_HOURS, aDuration, sizeof(aDuration)); + str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration); + UI()->DoLabel(&ToolBarBottom, aDurationLabel, 12.0f, TEXTALIGN_ML); + } + } +} + static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation) { int x = pPoint->x - pCenter->x; @@ -1489,7 +1562,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) if(m_vSelectedLayers.size() == 1) { UI()->DisableMouseLock(); - m_Map.m_Modified = true; + m_Map.OnModify(); DeleteSelectedQuads(); } s_Operation = OP_NONE; @@ -1688,7 +1761,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) m_SelectedQuadIndex = FindSelectedQuadIndex(QuadIndex); static SPopupMenuId s_PopupPointId; - UI()->DoPopupMenu(&s_PopupPointId, UI()->MouseX(), UI()->MouseY(), 120, 150, this, PopupPoint); + UI()->DoPopupMenu(&s_PopupPointId, UI()->MouseX(), UI()->MouseY(), 120, 75, this, PopupPoint); } UI()->SetActiveItem(nullptr); } @@ -3029,23 +3102,6 @@ void CEditor::DoMapEditor(CUIRect View) m_ChillerEditor.DoMapEditor(); } -float CEditor::ScaleFontSize(char *pText, int TextSize, float FontSize, int Width) -{ - while(TextRender()->TextWidth(FontSize, pText, -1, -1.0f) > Width) - { - if(FontSize > 6.0f) - { - FontSize--; - } - else - { - pText[str_length(pText) - 4] = '\0'; - str_append(pText, "…", TextSize); - } - } - return FontSize; -} - int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color) { int Change = -1; @@ -3135,53 +3191,44 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(pProps[i].m_Type == PROPTYPE_COLOR) { - static const char *s_apTexts[4] = {"R", "G", "B", "A"}; - static const int s_aShift[] = {24, 16, 8, 0}; - int NewColor = 0; - - // extra space - CUIRect ColorBox, ColorSlots; + const ColorRGBA ColorPick = ColorRGBA( + ((pProps[i].m_Value >> 24) & 0xff) / 255.0f, + ((pProps[i].m_Value >> 16) & 0xff) / 255.0f, + ((pProps[i].m_Value >> 8) & 0xff) / 255.0f, + (pProps[i].m_Value & 0xff) / 255.0f); - pToolBox->HSplitTop(3.0f * 13.0f, &Slot, pToolBox); - Slot.VSplitMid(&ColorBox, &ColorSlots); - ColorBox.HSplitTop(8.0f, nullptr, &ColorBox); - ColorBox.VMargin(12.0f, &ColorBox); - ColorBox.HMargin((ColorBox.h - ColorBox.w) / 2.0f, &ColorBox); + CUIRect ColorRect; + Shifter.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(&pIDs[i])), IGraphics::CORNER_ALL, 5.0f); + Shifter.Margin(1.0f, &ColorRect); + ColorRect.Draw(ColorPick, IGraphics::CORNER_ALL, ColorRect.h / 2.0f); - for(int c = 0; c < 4; c++) + static CUI::SColorPickerPopupContext s_ColorPickerPopupContext; + const int ButtonResult = DoButton_Editor_Common(&pIDs[i], nullptr, 0, &Shifter, 0, "Click to show the color picker. Shift+rightclick to copy color to clipboard. Shift+leftclick to paste color from clipboard."); + if(Input()->ShiftIsPressed()) { - int v = (pProps[i].m_Value >> s_aShift[c]) & 0xff; - NewColor |= UiDoValueSelector(((char *)&pIDs[i]) + c, &Shifter, s_apTexts[c], v, 0, 255, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.") << s_aShift[c]; - - if(c != 3) + if(ButtonResult == 1) + { + const char *pClipboard = Input()->GetClipboardText(); + if(*pClipboard == '#' || *pClipboard == '$') // ignore leading # (web color format) and $ (console color format) + ++pClipboard; + if(str_isallnum_hex(pClipboard)) + { + std::optional ParsedColor = color_parse(pClipboard); + if(ParsedColor) + { + *pNewVal = ParsedColor.value().PackAlphaLast(); + Change = i; + } + } + } + else if(ButtonResult == 2) { - ColorSlots.HSplitTop(13.0f, &Shifter, &ColorSlots); - Shifter.HMargin(1.0f, &Shifter); + char aClipboard[9]; + str_format(aClipboard, sizeof(aClipboard), "%08X", ColorPick.PackAlphaLast()); + Input()->SetClipboardText(aClipboard); } } - - // hex - pToolBox->HSplitTop(13.0f, &Slot, pToolBox); - Slot.VSplitMid(nullptr, &Shifter); - Shifter.HMargin(1.0f, &Shifter); - - int NewColorHex = pProps[i].m_Value & 0xff; - NewColorHex |= UiDoValueSelector(((char *)&pIDs[i] - 1), &Shifter, "", (pProps[i].m_Value >> 8) & 0xFFFFFF, 0, 0xFFFFFF, 1, 1.0f, "Use left mouse button to drag and change the color value. Hold shift to be more precise. Rightclick to edit as text.", false, true) << 8; - - // color picker - ColorRGBA ColorPick = ColorRGBA( - ((pProps[i].m_Value >> s_aShift[0]) & 0xff) / 255.0f, - ((pProps[i].m_Value >> s_aShift[1]) & 0xff) / 255.0f, - ((pProps[i].m_Value >> s_aShift[2]) & 0xff) / 255.0f, - 1.0f); - - static int s_ColorPicker; - if(UI()->HotItem() == &s_ColorPicker) - ColorBox.Margin(-2.0f, &ColorBox); - ColorBox.Draw(ColorPick, IGraphics::CORNER_ALL, 3.0f); - - static CUI::SColorPickerPopupContext s_ColorPickerPopupContext; - if(DoButton_Editor_Common(&s_ColorPicker, nullptr, 0, &ColorBox, 0, "Click to show the color picker.")) + else if(ButtonResult > 0) { s_ColorPickerPopupContext.m_HsvaColor = color_cast(ColorPick); s_ColorPickerPopupContext.m_Alpha = true; @@ -3190,30 +3237,23 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * else if(UI()->IsPopupOpen(&s_ColorPickerPopupContext)) { ColorRGBA c = color_cast(s_ColorPickerPopupContext.m_HsvaColor); - NewColor = ((int)(c.r * 255.0f) & 0xff) << 24 | ((int)(c.g * 255.0f) & 0xff) << 16 | ((int)(c.b * 255.0f) & 0xff) << 8 | (pProps[i].m_Value & 0xff); - } - - if(NewColor != pProps[i].m_Value) - { - *pNewVal = NewColor; - Change = i; - } - else if(NewColorHex != pProps[i].m_Value) - { - *pNewVal = NewColorHex; - Change = i; + const int NewColor = ((int)(c.r * 255.0f) & 0xff) << 24 | ((int)(c.g * 255.0f) & 0xff) << 16 | ((int)(c.b * 255.0f) & 0xff) << 8 | ((int)(c.a * 255.0f) & 0xff); + if(NewColor != pProps[i].m_Value) + { + *pNewVal = NewColor; + Change = i; + } } } else if(pProps[i].m_Type == PROPTYPE_IMAGE) { - char aBuf[64]; + const char *pName; if(pProps[i].m_Value < 0) - str_copy(aBuf, "None"); + pName = "None"; else - str_copy(aBuf, m_Map.m_vpImages[pProps[i].m_Value]->m_aName); + pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName; - float FontSize = ScaleFontSize(aBuf, sizeof(aBuf), 10.0f, Shifter.w); - if(DoButton_Ex(&pIDs[i], aBuf, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL, FontSize)) + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) PopupSelectImageInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); int r = PopupSelectImageResult(); @@ -3258,14 +3298,13 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(pProps[i].m_Type == PROPTYPE_SOUND) { - char aBuf[64]; + const char *pName; if(pProps[i].m_Value < 0) - str_copy(aBuf, "None"); + pName = "None"; else - str_copy(aBuf, m_Map.m_vpSounds[pProps[i].m_Value]->m_aName); + pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName; - float FontSize = ScaleFontSize(aBuf, sizeof(aBuf), 10.0f, Shifter.w); - if(DoButton_Ex(&pIDs[i], aBuf, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL, FontSize)) + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) PopupSelectSoundInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); int r = PopupSelectSoundResult(); @@ -3277,14 +3316,13 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER) { - char aBuf[64]; + const char *pName; if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size()) - str_copy(aBuf, "None"); + pName = "None"; else - str_copy(aBuf, m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value)); + pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(pProps[i].m_Value); - float FontSize = ScaleFontSize(aBuf, sizeof(aBuf), 10.0f, Shifter.w); - if(DoButton_Ex(&pIDs[i], aBuf, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL, FontSize)) + if(DoButton_Ex(&pIDs[i], pName, 0, &Shifter, 0, nullptr, IGraphics::CORNER_ALL)) PopupSelectConfigAutoMapInvoke(pProps[i].m_Value, UI()->MouseX(), UI()->MouseY()); int r = PopupSelectConfigAutoMapResult(); @@ -3469,14 +3507,11 @@ void CEditor::RenderLayers(CUIRect LayersBox) m_Map.m_vpGroups[g]->m_Visible = !m_Map.m_vpGroups[g]->m_Visible; str_format(aBuf, sizeof(aBuf), "#%d %s", g, m_Map.m_vpGroups[g]->m_aName); - float FontSize = 10.0f; - while(TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f) > Slot.w && FontSize >= 7.0f) - FontSize--; bool Clicked; bool Abrupted; if(int Result = DoButton_DraggableEx(&m_Map.m_vpGroups[g], aBuf, g == m_SelectedGroup, &Slot, &Clicked, &Abrupted, - BUTTON_CONTEXT, m_Map.m_vpGroups[g]->m_Collapse ? "Select group. Shift click to select all layers. Double click to expand." : "Select group. Shift click to select all layers. Double click to collapse.", IGraphics::CORNER_R, FontSize)) + BUTTON_CONTEXT, m_Map.m_vpGroups[g]->m_Collapse ? "Select group. Shift click to select all layers. Double click to expand." : "Select group. Shift click to select all layers. Double click to collapse.", IGraphics::CORNER_R)) { if(s_Operation == OP_NONE) { @@ -3615,14 +3650,8 @@ void CEditor::RenderLayers(CUIRect LayersBox) CLayerSounds *pSounds = (CLayerSounds *)m_Map.m_vpGroups[g]->m_vpLayers[i]; str_copy(aBuf, pSounds->m_Sound >= 0 ? m_Map.m_vpSounds[pSounds->m_Sound]->m_aName : "Sounds"); } - if(str_length(aBuf) > 11) - str_format(aBuf, sizeof(aBuf), "%.8s…", aBuf); } - float FontSize = 10.0f; - while(TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f) > Button.w && FontSize >= 7.0f) - FontSize--; - int Checked = IsLayerSelected ? 1 : 0; if(m_Map.m_vpGroups[g]->m_vpLayers[i]->IsEntitiesLayer()) { @@ -3632,7 +3661,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) bool Clicked; bool Abrupted; if(int Result = DoButton_DraggableEx(m_Map.m_vpGroups[g]->m_vpLayers[i], aBuf, Checked, &Button, &Clicked, &Abrupted, - BUTTON_CONTEXT, "Select layer. Shift click to select multiple.", IGraphics::CORNER_R, FontSize)) + BUTTON_CONTEXT, "Select layer. Shift click to select multiple.", IGraphics::CORNER_R)) { if(s_Operation == OP_NONE) { @@ -3711,7 +3740,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) else s_LayerPopupContext.m_vpLayers.clear(); - UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 320, &s_LayerPopupContext, PopupLayer); + UI()->DoPopupMenu(&s_LayerPopupContext, UI()->MouseX(), UI()->MouseY(), 120, 270, &s_LayerPopupContext, PopupLayer); } s_Operation = OP_NONE; @@ -3784,7 +3813,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) m_SelectedGroup = GroupAfterDraggedLayer - 1; m_vSelectedLayers.clear(); m_vSelectedQuads.clear(); - m_Map.m_Modified = true; + m_Map.OnModify(); } } @@ -3801,7 +3830,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) m_Map.m_vpGroups.insert(InsertPosition, pSelectedGroup); m_SelectedGroup = InsertPosition - m_Map.m_vpGroups.begin(); - m_Map.m_Modified = true; + m_Map.OnModify(); } if(MoveLayers || MoveGroup) @@ -4361,12 +4390,8 @@ void CEditor::RenderImagesList(CUIRect ToolBox) } } - float FontSize = 10.0f; - while(TextRender()->TextWidth(FontSize, m_Map.m_vpImages[i]->m_aName, -1, -1.0f) > Slot.w) - FontSize--; - if(int Result = DoButton_Ex(&m_Map.m_vpImages[i], m_Map.m_vpImages[i]->m_aName, Selected, &Slot, - BUTTON_CONTEXT, "Select image.", IGraphics::CORNER_ALL, FontSize)) + BUTTON_CONTEXT, "Select image.", IGraphics::CORNER_ALL)) { m_SelectedImage = i; @@ -4481,12 +4506,8 @@ void CEditor::RenderSounds(CUIRect ToolBox) if(!SoundUsed) Selected += 2; // Sound is unused - float FontSize = 10.0f; - while(TextRender()->TextWidth(FontSize, m_Map.m_vpSounds[i]->m_aName, -1, -1.0f) > Slot.w) - FontSize--; - if(int Result = DoButton_Ex(&m_Map.m_vpSounds[i], m_Map.m_vpSounds[i]->m_aName, Selected, &Slot, - BUTTON_CONTEXT, "Select sound.", IGraphics::CORNER_ALL, FontSize)) + BUTTON_CONTEXT, "Select sound.", IGraphics::CORNER_ALL)) { m_SelectedSound = i; @@ -4527,7 +4548,7 @@ static int EditorListdirCallback(const CFsFileInfo *pInfo, int IsDir, int Storag { CEditor *pEditor = (CEditor *)pUser; if((pInfo->m_pName[0] == '.' && (pInfo->m_pName[1] == 0 || - (pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))) || + (pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (pEditor->m_FileDialogShowingRoot || (!pEditor->m_FileDialogMultipleStorages && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))))) || (!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && !str_endswith(pInfo->m_pName, ".map")) || (pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && !str_endswith(pInfo->m_pName, ".png")) || (pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && !str_endswith(pInfo->m_pName, ".opus"))))) @@ -4577,7 +4598,7 @@ void CEditor::RenderFileDialog() // GUI coordsys UI()->MapScreen(); CUIRect View = *UI()->Screen(); - CUIRect Preview; + CUIRect Preview = {0.0f, 0.0f, 0.0f, 0.0f}; float Width = View.w, Height = View.h; View.Draw(ColorRGBA(0, 0, 0, 0.25f), 0, 0); @@ -4596,57 +4617,60 @@ void CEditor::RenderFileDialog() View.HSplitBottom(14.0f, &View, &FileBox); FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox); View.HSplitBottom(10.0f, &View, nullptr); // some spacing - if(m_FileDialogFileType == CEditor::FILETYPE_IMG) + if(m_FileDialogFileType == CEditor::FILETYPE_IMG || m_FileDialogFileType == CEditor::FILETYPE_SOUND) View.VSplitMid(&View, &Preview); - // title - CUIRect ButtonTimeModified, ButtonFileName; - Title.VSplitRight(10.0f, &Title, nullptr); - Title.VSplitRight(90.0f, &Title, &ButtonTimeModified); - Title.VSplitRight(10.0f, &Title, nullptr); - Title.VSplitRight(90.0f, &Title, &ButtonFileName); - Title.VSplitRight(10.0f, &Title, nullptr); - - const char *aSortIndicator[3] = {"▼", "", "▲"}; - - static int s_ButtonTimeModified = 0; - char aBufLabelButtonTimeModified[64]; - str_format(aBufLabelButtonTimeModified, sizeof(aBufLabelButtonTimeModified), "Time modified %s", aSortIndicator[m_SortByTimeModified + 1]); - if(DoButton_Editor(&s_ButtonTimeModified, aBufLabelButtonTimeModified, 0, &ButtonTimeModified, 0, "Sort by time modified")) + // title bar + if(!m_FileDialogShowingRoot) { - if(m_SortByTimeModified == 1) - { - m_SortByTimeModified = -1; - } - else if(m_SortByTimeModified == -1) - { - m_SortByTimeModified = 0; - } - else - { - m_SortByTimeModified = 1; - } + CUIRect ButtonTimeModified, ButtonFileName; + Title.VSplitRight(10.0f, &Title, nullptr); + Title.VSplitRight(90.0f, &Title, &ButtonTimeModified); + Title.VSplitRight(10.0f, &Title, nullptr); + Title.VSplitRight(90.0f, &Title, &ButtonFileName); + Title.VSplitRight(10.0f, &Title, nullptr); - RefreshFilteredFileList(); - } + const char *aSortIndicator[3] = {"▼", "", "▲"}; - static int s_ButtonFileName = 0; - char aBufLabelButtonFilename[64]; - str_format(aBufLabelButtonFilename, sizeof(aBufLabelButtonFilename), "Filename %s", aSortIndicator[m_SortByFilename + 1]); - if(DoButton_Editor(&s_ButtonFileName, aBufLabelButtonFilename, 0, &ButtonFileName, 0, "Sort by file name")) - { - if(m_SortByFilename == 1) + static int s_ButtonTimeModified = 0; + char aBufLabelButtonTimeModified[64]; + str_format(aBufLabelButtonTimeModified, sizeof(aBufLabelButtonTimeModified), "Time modified %s", aSortIndicator[m_SortByTimeModified + 1]); + if(DoButton_Editor(&s_ButtonTimeModified, aBufLabelButtonTimeModified, 0, &ButtonTimeModified, 0, "Sort by time modified")) { - m_SortByFilename = -1; - m_SortByTimeModified = 0; + if(m_SortByTimeModified == 1) + { + m_SortByTimeModified = -1; + } + else if(m_SortByTimeModified == -1) + { + m_SortByTimeModified = 0; + } + else + { + m_SortByTimeModified = 1; + } + + RefreshFilteredFileList(); } - else + + static int s_ButtonFileName = 0; + char aBufLabelButtonFilename[64]; + str_format(aBufLabelButtonFilename, sizeof(aBufLabelButtonFilename), "Filename %s", aSortIndicator[m_SortByFilename + 1]); + if(DoButton_Editor(&s_ButtonFileName, aBufLabelButtonFilename, 0, &ButtonFileName, 0, "Sort by file name")) { - m_SortByFilename = 1; - m_SortByTimeModified = 0; - } + if(m_SortByFilename == 1) + { + m_SortByFilename = -1; + m_SortByTimeModified = 0; + } + else + { + m_SortByFilename = 1; + m_SortByTimeModified = 0; + } - RefreshFilteredFileList(); + RefreshFilteredFileList(); + } } Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); @@ -4654,13 +4678,24 @@ void CEditor::RenderFileDialog() UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, TEXTALIGN_ML); // pathbox - char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH]; - if(m_FilesSelectedIndex != -1) + if(m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType >= IStorage::TYPE_SAVE) + { + char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH]; Storage()->GetCompletePath(m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath)); - else - aPath[0] = 0; - str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); - UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML); + str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); + UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML); + } + + const auto &&UpdateFileNameInput = [this]() { + if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) + { + char aNameWithoutExt[IO_MAX_PATH_LENGTH]; + fs_split_file_extension(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, aNameWithoutExt, sizeof(aNameWithoutExt)); + m_FileDialogFileNameInput.Set(aNameWithoutExt); + } + else + m_FileDialogFileNameInput.Clear(); + }; // filebox static CListBox s_ListBox; @@ -4734,12 +4769,9 @@ void CEditor::RenderFileDialog() str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName); else m_aFilesSelectedName[0] = '\0'; - if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); - else - m_FileDialogFileNameInput.Clear(); + UpdateFileNameInput(); s_ListBox.ScrollToSelected(); - m_PreviewImageState = PREVIEWIMAGE_UNLOADED; + m_FilePreviewState = PREVIEW_UNLOADED; } } @@ -4747,46 +4779,97 @@ void CEditor::RenderFileDialog() if(m_FilesSelectedIndex > -1) { - if(m_FileDialogFileType == CEditor::FILETYPE_IMG && m_PreviewImageState == PREVIEWIMAGE_UNLOADED && m_FilesSelectedIndex > -1) + if(m_FilePreviewState == PREVIEW_UNLOADED) { - if(str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png")) + if(m_FileDialogFileType == CEditor::FILETYPE_IMG && str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png")) { char aBuffer[IO_MAX_PATH_LENGTH]; str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); - if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL)) + if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType)) { Graphics()->UnloadTexture(&m_FilePreviewImage); m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, 0); Graphics()->FreePNG(&m_FilePreviewImageInfo); - m_PreviewImageState = PREVIEWIMAGE_LOADED; + m_FilePreviewState = PREVIEW_LOADED; } else { - m_PreviewImageState = PREVIEWIMAGE_ERROR; + m_FilePreviewState = PREVIEW_ERROR; } } + else if(m_FileDialogFileType == CEditor::FILETYPE_SOUND && str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".opus")) + { + char aBuffer[IO_MAX_PATH_LENGTH]; + str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); + Sound()->UnloadSample(m_FilePreviewSound); + m_FilePreviewSound = Sound()->LoadOpus(aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType); + m_FilePreviewState = m_FilePreviewSound == -1 ? PREVIEW_ERROR : PREVIEW_LOADED; + } } - if(m_FileDialogFileType == CEditor::FILETYPE_IMG && m_PreviewImageState == PREVIEWIMAGE_LOADED) + + if(m_FileDialogFileType == CEditor::FILETYPE_IMG) { - int w = m_FilePreviewImageInfo.m_Width; - int h = m_FilePreviewImageInfo.m_Height; - if(m_FilePreviewImageInfo.m_Width > Preview.w) // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult) + Preview.Margin(10.0f, &Preview); + if(m_FilePreviewState == PREVIEW_LOADED) { - h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width; - w = Preview.w; + int w = m_FilePreviewImageInfo.m_Width; + int h = m_FilePreviewImageInfo.m_Height; + if(m_FilePreviewImageInfo.m_Width > Preview.w) + { + h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width; + w = Preview.w; + } + if(h > Preview.h) + { + w = w * Preview.h / h; + h = Preview.h; + } + + Graphics()->TextureSet(m_FilePreviewImage); + Graphics()->BlendNormal(); + Graphics()->QuadsBegin(); + IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); } - if(h > Preview.h) + else if(m_FilePreviewState == PREVIEW_ERROR) { - w = w * Preview.h / h; - h = Preview.h; + SLabelProperties Props; + Props.m_MaxWidth = Preview.w; + UI()->DoLabel(&Preview, "Failed to load the image (check the local console for details).", 12.0f, TEXTALIGN_TL, Props); } + } + else if(m_FileDialogFileType == CEditor::FILETYPE_SOUND) + { + Preview.Margin(10.0f, &Preview); + if(m_FilePreviewState == PREVIEW_LOADED) + { + CUIRect Button; + Preview.HSplitTop(20.0f, &Preview, nullptr); + Preview.VSplitLeft(Preview.h, &Button, &Preview); + Preview.VSplitLeft(Preview.h / 4.0f, nullptr, &Preview); - Graphics()->TextureSet(m_FilePreviewImage); - Graphics()->BlendNormal(); - Graphics()->QuadsBegin(); - IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); + static int s_PlayStopButton; + if(DoButton_FontIcon(&s_PlayStopButton, Sound()->IsPlaying(m_FilePreviewSound) ? FONT_ICON_STOP : FONT_ICON_PLAY, 0, &Button, 0, "Play/stop audio preview", IGraphics::CORNER_ALL)) + { + if(Sound()->IsPlaying(m_FilePreviewSound)) + Sound()->Stop(m_FilePreviewSound); + else + Sound()->Play(CSounds::CHN_GUI, m_FilePreviewSound, 0); + } + + char aDuration[32]; + char aDurationLabel[64]; + str_time_float(Sound()->GetSampleDuration(m_FilePreviewSound), TIME_HOURS, aDuration, sizeof(aDuration)); + str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration); + UI()->DoLabel(&Preview, aDurationLabel, 12.0f, TEXTALIGN_ML); + } + else if(m_FilePreviewState == PREVIEW_ERROR) + { + SLabelProperties Props; + Props.m_MaxWidth = Preview.w; + UI()->DoLabel(&Preview, "Failed to load the sound (check the local console for details). Make sure you enabled sounds in the settings.", 12.0f, TEXTALIGN_TL, Props); + } } } @@ -4802,6 +4885,7 @@ void CEditor::RenderFileDialog() Item.m_Rect.VSplitLeft(Item.m_Rect.h, &FileIcon, &Button); Button.VSplitLeft(5.0f, nullptr, &Button); Button.VSplitRight(100.0f, &Button, &TimeModified); + Button.VSplitRight(5.0f, &Button, nullptr); const char *pIconType; if(!m_vpFilteredFileList[i]->m_IsDir) @@ -4823,17 +4907,22 @@ void CEditor::RenderFileDialog() } else { - if(str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0) + if(m_vpFilteredFileList[i]->m_IsLink || str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0) pIconType = FONT_ICON_FOLDER_TREE; else pIconType = FONT_ICON_FOLDER; } TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); + TextRender()->SetRenderFlags(0); TextRender()->SetCurFont(nullptr); - UI()->DoLabel(&Button, m_vpFilteredFileList[i]->m_aName, 10.0f, TEXTALIGN_ML); + SLabelProperties Props; + Props.m_MaxWidth = Button.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&Button, m_vpFilteredFileList[i]->m_aName, 10.0f, TEXTALIGN_ML, Props); if(!m_vpFilteredFileList[i]->m_IsLink && str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") != 0) { @@ -4849,13 +4938,10 @@ void CEditor::RenderFileDialog() m_FilesSelectedIndex = NewSelection; str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName); const bool WasChanged = m_FileDialogFileNameInput.WasChanged(); - if(!m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); - else - m_FileDialogFileNameInput.Clear(); + UpdateFileNameInput(); if(!WasChanged) // ensure that changed flag is not set if it wasn't previously set, as this would reset the selection after DoEditBox is called m_FileDialogFileNameInput.WasChanged(); // this clears the changed flag - m_PreviewImageState = PREVIEWIMAGE_UNLOADED; + m_FilePreviewState = PREVIEW_UNLOADED; } const float ButtonSpacing = ButtonBar.w > 600.0f ? 40.0f : 10.0f; @@ -4870,16 +4956,39 @@ void CEditor::RenderFileDialog() CUIRect Button; ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); - bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir; + const bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir; if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || s_ListBox.WasItemActivated()) { if(IsDir) // folder { m_FileDialogFilterInput.Clear(); - if(str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0) // parent folder + const bool ParentFolder = str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0; + if(ParentFolder) // parent folder { + str_copy(m_aFilesSelectedName, fs_filename(m_pFileDialogPath)); + str_append(m_aFilesSelectedName, "/"); if(fs_parent_dir(m_pFileDialogPath)) - m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link + { + if(str_comp(m_pFileDialogPath, m_aFileDialogCurrentFolder) == 0) + { + m_FileDialogShowingRoot = true; + if(m_FileDialogStorageType == IStorage::TYPE_ALL) + { + m_aFilesSelectedName[0] = '\0'; // will select first list item + } + else + { + Storage()->GetCompletePath(m_FileDialogStorageType, m_pFileDialogPath, m_aFilesSelectedName, sizeof(m_aFilesSelectedName)); + str_append(m_aFilesSelectedName, "/"); + } + } + else + { + m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link + str_copy(m_aFilesSelectedName, m_aFileDialogCurrentLink); + str_append(m_aFilesSelectedName, "/"); + } + } } else // sub folder { @@ -4894,29 +5003,26 @@ void CEditor::RenderFileDialog() str_copy(aTemp, m_pFileDialogPath); str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); } + if(m_FileDialogShowingRoot) + m_FileDialogStorageType = m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType; + m_FileDialogShowingRoot = false; } - FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : - m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType); - if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); - else - m_FileDialogFileNameInput.Clear(); + FilelistPopulate(m_FileDialogStorageType, ParentFolder); + UpdateFileNameInput(); } else // file { + const int StorageType = m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType; str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_FileDialogFileNameInput.GetString()); - if(!str_comp(m_pFileDialogButtonText, "Save")) + if(!str_endswith(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType])) + str_append(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType]); + if(!str_comp(m_pFileDialogButtonText, "Save") && Storage()->FileExists(m_aFileSaveName, StorageType)) { - if(Storage()->FileExists(m_aFileSaveName, IStorage::TYPE_SAVE)) - { - m_PopupEventType = POPEVENT_SAVE; - m_PopupEventActivated = true; - } - else if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); + m_PopupEventType = m_pfnFileDialogFunc == &CallbackSaveCopyMap ? POPEVENT_SAVE_COPY : POPEVENT_SAVE; + m_PopupEventActivated = true; } else if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); + m_pfnFileDialogFunc(m_aFileSaveName, StorageType, m_pFileDialogUser); } } @@ -4934,9 +5040,11 @@ void CEditor::RenderFileDialog() ButtonBar.VSplitRight(90.0f, &ButtonBar, &Button); if(DoButton_Editor(&s_ShowDirectoryButton, "Show directory", 0, &Button, 0, "Open the current directory in the file browser")) { - if(!open_file(aPath)) + char aOpenPath[IO_MAX_PATH_LENGTH]; + Storage()->GetCompletePath(m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : IStorage::TYPE_SAVE, m_pFileDialogPath, aOpenPath, sizeof(aOpenPath)); + if(!open_file(aOpenPath)) { - ShowFileDialogError("Failed to open the directory '%s'.", aPath); + ShowFileDialogError("Failed to open the directory '%s'.", aOpenPath); } } @@ -4977,7 +5085,7 @@ void CEditor::RenderFileDialog() else s_ConfirmDeletePopupContext.Reset(); - if(g_Config.m_ClSaveMapInfo && m_FileDialogStorageType == IStorage::TYPE_SAVE) + if(g_Config.m_ClSaveMapInfo && !m_FileDialogShowingRoot && m_FileDialogStorageType == IStorage::TYPE_SAVE) { ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, nullptr)) @@ -5002,7 +5110,8 @@ void CEditor::RefreshFilteredFileList() m_vpFilteredFileList.push_back(&Item); } } - SortFilteredFileList(); + if(!m_FileDialogShowingRoot) + SortFilteredFileList(); if(!m_vpFilteredFileList.empty()) { if(m_aFilesSelectedName[0]) @@ -5030,18 +5139,66 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection) { m_FileDialogLastPopulatedStorageType = StorageType; m_vCompleteFileList.clear(); - if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps")) - { - CFilelistItem Item; - str_copy(Item.m_aFilename, "downloadedmaps"); - str_copy(Item.m_aName, "downloadedmaps/"); - Item.m_IsDir = true; - Item.m_IsLink = true; - Item.m_StorageType = IStorage::TYPE_SAVE; - Item.m_TimeModified = 0; - m_vCompleteFileList.push_back(Item); - } - Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this); + if(m_FileDialogShowingRoot) + { + { + CFilelistItem Item; + str_copy(Item.m_aFilename, m_pFileDialogPath); + str_copy(Item.m_aName, "All combined"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = IStorage::TYPE_ALL; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + + for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType) + { + if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, m_pFileDialogPath); + Storage()->GetCompletePath(CheckStorageType, m_pFileDialogPath, Item.m_aName, sizeof(Item.m_aName)); + str_append(Item.m_aName, "/", sizeof(Item.m_aName)); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = CheckStorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + } + } + else + { + // Add links for downloadedmaps and themes + if(!str_comp(m_pFileDialogPath, "maps")) + { + if(str_comp(m_pFileDialogButtonText, "Save") != 0 && Storage()->FolderExists("downloadedmaps", StorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, "downloadedmaps"); + str_copy(Item.m_aName, "downloadedmaps/"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + + if(Storage()->FolderExists("themes", StorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, "themes"); + str_copy(Item.m_aName, "themes/"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + } + Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this); + } RefreshFilteredFileList(); if(!KeepSelection) { @@ -5051,15 +5208,32 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection) else m_aFilesSelectedName[0] = '\0'; } - m_PreviewImageState = PREVIEWIMAGE_UNLOADED; + m_FilePreviewState = PREVIEW_UNLOADED; } void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, const char *pBasePath, const char *pDefaultName, bool (*pfnFunc)(const char *pFileName, int StorageType, void *pUser), void *pUser) { - UI()->ClosePopupMenus(); m_FileDialogStorageType = StorageType; + if(m_FileDialogStorageType == IStorage::TYPE_ALL) + { + int NumStoragedWithFolder = 0; + for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType) + { + if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType)) + { + NumStoragedWithFolder++; + } + } + m_FileDialogMultipleStorages = NumStoragedWithFolder > 1; + } + else + { + m_FileDialogMultipleStorages = false; + } + + UI()->ClosePopupMenus(); m_pFileDialogTitle = pTitle; m_pFileDialogButtonText = pButtonText; m_pfnFileDialogFunc = pfnFunc; @@ -5070,8 +5244,9 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle m_aFileDialogCurrentLink[0] = 0; m_pFileDialogPath = m_aFileDialogCurrentFolder; m_FileDialogFileType = FileType; - m_PreviewImageState = PREVIEWIMAGE_UNLOADED; + m_FilePreviewState = PREVIEW_UNLOADED; m_FileDialogOpening = true; + m_FileDialogShowingRoot = false; if(pDefaultName) m_FileDialogFileNameInput.Set(pDefaultName); @@ -5148,8 +5323,8 @@ void CEditor::RenderStatusbar(CUIRect View) m_ShowServerSettingsEditor = false; } - View.VSplitRight(100.0f, &View, &Button); - Button.VSplitRight(10.0f, &Button, nullptr); + View.VSplitRight(10.0f, &View, nullptr); + View.VSplitRight(90.0f, &View, &Button); static int s_SettingsButton = 0; if(DoButton_Editor(&s_SettingsButton, "Server settings", m_ShowServerSettingsEditor, &Button, 0, "Toggles the server settings editor.")) { @@ -5165,10 +5340,11 @@ void CEditor::RenderStatusbar(CUIRect View) else str_copy(aBuf, m_pTooltip); - float FontSize = ScaleFontSize(aBuf, sizeof(aBuf), 10.0f, View.w); + View.VSplitRight(10.0f, &View, nullptr); SLabelProperties Props; Props.m_MaxWidth = View.w; - UI()->DoLabel(&View, aBuf, FontSize, TEXTALIGN_ML, Props); + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&View, aBuf, 10.0f, TEXTALIGN_ML, Props); } } @@ -5256,7 +5432,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_NewSoundButton = 0; if(DoButton_Editor(&s_NewSoundButton, "Sound+", 0, &Button, 0, "Creates a new sound envelope")) { - m_Map.m_Modified = true; + m_Map.OnModify(); pNewEnv = m_Map.NewEnvelope(1); } @@ -5265,7 +5441,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_New4dButton = 0; if(DoButton_Editor(&s_New4dButton, "Color+", 0, &Button, 0, "Creates a new color envelope")) { - m_Map.m_Modified = true; + m_Map.OnModify(); pNewEnv = m_Map.NewEnvelope(4); } @@ -5274,7 +5450,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) static int s_New2dButton = 0; if(DoButton_Editor(&s_New2dButton, "Pos.+", 0, &Button, 0, "Creates a new position envelope")) { - m_Map.m_Modified = true; + m_Map.OnModify(); pNewEnv = m_Map.NewEnvelope(3); } @@ -5385,7 +5561,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_NameInput.SetBuffer(pEnvelope->m_aName, sizeof(pEnvelope->m_aName)); if(DoEditBox(&s_NameInput, &Button, 10.0f, IGraphics::CORNER_ALL, "The name of the selected envelope")) { - m_Map.m_Modified = true; + m_Map.OnModify(); } } } @@ -5490,7 +5666,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) pEnvelope->AddPoint(Time, f2fx(Channels.r), f2fx(Channels.g), f2fx(Channels.b), f2fx(Channels.a)); - m_Map.m_Modified = true; + m_Map.OnModify(); } m_ShowEnvelopePreview = SHOWENV_SELECTED; @@ -5669,7 +5845,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) m_SelectedQuadEnvelope = m_SelectedEnvelope; m_ShowEnvelopePreview = SHOWENV_SELECTED; m_SelectedEnvelopePoint = i; - m_Map.m_Modified = true; + m_Map.OnModify(); } ColorMod = 100.0f; @@ -5699,7 +5875,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) } pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + i); - m_Map.m_Modified = true; + m_Map.OnModify(); } m_ShowEnvelopePreview = SHOWENV_SELECTED; @@ -6005,6 +6181,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar) MenuBar.VSplitRight(20.0f, &MenuBar, &Close); Close.VSplitLeft(5.0f, nullptr, &Close); MenuBar.VSplitLeft(MenuBar.w * 0.6f, &MenuBar, &Info); + MenuBar.VSplitRight(5.0f, &MenuBar, nullptr); if(m_Map.m_Modified) { @@ -6019,7 +6196,10 @@ void CEditor::RenderMenubar(CUIRect MenuBar) char aBuf[IO_MAX_PATH_LENGTH + 32]; str_format(aBuf, sizeof(aBuf), "File: %s", m_aFileName); - UI()->DoLabel(&MenuBar, aBuf, 10.0f, TEXTALIGN_ML); + SLabelProperties Props; + Props.m_MaxWidth = MenuBar.w; + Props.m_EllipsisAtEnd = true; + UI()->DoLabel(&MenuBar, aBuf, 10.0f, TEXTALIGN_ML, Props); char aTimeStr[6]; str_timestamp_format(aTimeStr, sizeof(aTimeStr), "%H:%M"); @@ -6178,7 +6358,9 @@ void CEditor::Render() // do the toolbar if(m_Mode == MODE_LAYERS) - DoToolbar(ToolBar); + DoToolbarLayers(ToolBar); + else if(m_Mode == MODE_SOUNDS) + DoToolbarSounds(ToolBar); if(m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) { @@ -6242,7 +6424,7 @@ void CEditor::Render() } } - // ctrl+shift+alt+s to save as + // ctrl+shift+alt+s to save copy if(Input()->KeyPress(KEY_S) && ModPressed && ShiftPressed && AltPressed) InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveCopyMap, this); // ctrl+shift+s to save as @@ -6306,6 +6488,9 @@ void CEditor::Render() } } + RenderPressedKeys(View); + RenderSavingIndicator(View); + if(m_Dialog == DIALOG_FILE) { static int s_NullUiTarget = 0; @@ -6340,51 +6525,63 @@ void CEditor::Render() if(m_GuiActive) RenderStatusbar(StatusBar); - // - if(g_Config.m_EdShowkeys) - { - UI()->MapScreen(); - CTextCursor Cursor; - TextRender()->SetCursor(&Cursor, View.x + 10, View.y + View.h - 24 - 10, 24.0f, TEXTFLAG_RENDER); + RenderMousePointer(); +} - int NKeys = 0; - for(int i = 0; i < KEY_LAST; i++) +void CEditor::RenderPressedKeys(CUIRect View) +{ + if(!g_Config.m_EdShowkeys) + return; + + UI()->MapScreen(); + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, View.x + 10, View.y + View.h - 24 - 10, 24.0f, TEXTFLAG_RENDER); + + int NKeys = 0; + for(int i = 0; i < KEY_LAST; i++) + { + if(Input()->KeyIsPressed(i)) { - if(Input()->KeyIsPressed(i)) - { - if(NKeys) - TextRender()->TextEx(&Cursor, " + ", -1); - TextRender()->TextEx(&Cursor, Input()->KeyName(i), -1); - NKeys++; - } + if(NKeys) + TextRender()->TextEx(&Cursor, " + ", -1); + TextRender()->TextEx(&Cursor, Input()->KeyName(i), -1); + NKeys++; } } +} - if(m_ShowMousePointer) - { - // render butt ugly mouse cursor - float mx = UI()->MouseX(); - float my = UI()->MouseY(); - Graphics()->WrapClamp(); - Graphics()->TextureSet(m_CursorTexture); - Graphics()->QuadsBegin(); - if(ms_pUiGotContext == UI()->HotItem()) - Graphics()->SetColor(1, 0, 0, 1); - IGraphics::CQuadItem QuadItem(mx, my, 16.0f, 16.0f); - Graphics()->QuadsDrawTL(&QuadItem, 1); - Graphics()->QuadsEnd(); - Graphics()->WrapNormal(); - } +void CEditor::RenderSavingIndicator(CUIRect View) +{ + if(m_WriterFinishJobs.empty()) + return; + + UI()->MapScreen(); + CUIRect Label; + View.Margin(20.0f, &Label); + UI()->DoLabel(&Label, "Saving…", 24.0f, TEXTALIGN_BR); +} + +void CEditor::RenderMousePointer() +{ + if(!m_ShowMousePointer) + return; + + Graphics()->WrapClamp(); + Graphics()->TextureSet(m_CursorTexture); + Graphics()->QuadsBegin(); + if(ms_pUiGotContext == UI()->HotItem()) + Graphics()->SetColor(1, 0, 0, 1); + IGraphics::CQuadItem QuadItem(UI()->MouseX(), UI()->MouseY(), 16.0f, 16.0f); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + Graphics()->WrapNormal(); } void CEditor::Reset(bool CreateDefault) { + UI()->ClosePopupMenus(); m_Map.Clean(); - //delete undo file - char aBuffer[1024]; - m_pStorage->GetCompletePath(IStorage::TYPE_SAVE, "editor/", aBuffer, sizeof(aBuffer)); - mem_zero(m_apSavedBrushes, sizeof m_apSavedBrushes); // create default layers @@ -6417,6 +6614,10 @@ void CEditor::Reset(bool CreateDefault) m_MouseDeltaWy = 0; m_Map.m_Modified = false; + m_Map.m_ModifiedAuto = false; + m_Map.m_LastModifiedTime = -1.0f; + m_Map.m_LastSaveTime = Client()->GlobalTime(); + m_Map.m_LastAutosaveUpdateTime = -1.0f; m_ShowEnvelopePreview = SHOWENV_NONE; m_ShiftBy = 1; @@ -6535,12 +6736,19 @@ void CEditor::Goto(float X, float Y) m_WorldOffsetY = Y * 32; } +void CEditorMap::OnModify() +{ + m_Modified = true; + m_ModifiedAuto = true; + m_LastModifiedTime = m_pEditor->Client()->GlobalTime(); +} + void CEditorMap::DeleteEnvelope(int Index) { if(Index < 0 || Index >= (int)m_vpEnvelopes.size()) return; - m_Modified = true; + OnModify(); VisitEnvelopeReferences([Index](int &ElementIndex) { if(ElementIndex == Index) @@ -6561,7 +6769,7 @@ void CEditorMap::SwapEnvelopes(int Index0, int Index1) if(Index0 == Index1) return; - m_Modified = true; + OnModify(); VisitEnvelopeReferences([Index0, Index1](int &ElementIndex) { if(ElementIndex == Index0) @@ -6641,6 +6849,7 @@ void CEditorMap::Clean() m_pGameGroup = nullptr; m_Modified = false; + m_ModifiedAuto = false; m_pTeleLayer = nullptr; m_pSpeedupLayer = nullptr; @@ -6740,6 +6949,7 @@ void CEditor::Init() m_pClient = Kernel()->RequestInterface(); m_pConfig = Kernel()->RequestInterface()->Values(); m_pConsole = Kernel()->RequestInterface(); + m_pEngine = Kernel()->RequestInterface(); m_pGraphics = Kernel()->RequestInterface(); m_pTextRender = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); @@ -6766,7 +6976,6 @@ void CEditor::Init() m_Brush.m_pMap = &m_Map; Reset(false); - m_Map.m_Modified = false; m_ChillerEditor.Init(this); @@ -6816,77 +7025,184 @@ void CEditor::PlaceBorderTiles() pT->m_pTiles[i].m_Index = 1; } -void CEditor::OnUpdate() +void CEditor::HandleCursorMovement() { - CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI + static float s_MouseX = 0.0f; + static float s_MouseY = 0.0f; - if(!m_EditorWasUsedBefore) + float MouseRelX = 0.0f, MouseRelY = 0.0f; + IInput::ECursorType CursorType = Input()->CursorRelative(&MouseRelX, &MouseRelY); + if(CursorType != IInput::CURSOR_NONE) + UI()->ConvertMouseMove(&MouseRelX, &MouseRelY, CursorType); + + m_MouseDeltaX += MouseRelX; + m_MouseDeltaY += MouseRelY; + + if(!UI()->CheckMouseLock()) { - m_EditorWasUsedBefore = true; - Reset(); + s_MouseX = clamp(s_MouseX + MouseRelX, 0.0f, Graphics()->WindowWidth()); + s_MouseY = clamp(s_MouseY + MouseRelY, 0.0f, Graphics()->WindowHeight()); } - // handle cursor movement + // update positions for ui, but only update ui when rendering + m_MouseX = UI()->Screen()->w * ((float)s_MouseX / Graphics()->WindowWidth()); + m_MouseY = UI()->Screen()->h * ((float)s_MouseY / Graphics()->WindowHeight()); + + // fix correct world x and y + CLayerGroup *pGroup = GetSelectedGroup(); + if(pGroup) { - static float s_MouseX = 0.0f; - static float s_MouseY = 0.0f; + float aPoints[4]; + pGroup->Mapping(aPoints); - float MouseRelX = 0.0f, MouseRelY = 0.0f; - IInput::ECursorType CursorType = Input()->CursorRelative(&MouseRelX, &MouseRelY); - if(CursorType != IInput::CURSOR_NONE) - UI()->ConvertMouseMove(&MouseRelX, &MouseRelY, CursorType); + float WorldWidth = aPoints[2] - aPoints[0]; + float WorldHeight = aPoints[3] - aPoints[1]; - m_MouseDeltaX += MouseRelX; - m_MouseDeltaY += MouseRelY; + m_MouseWScale = WorldWidth / Graphics()->WindowWidth(); - if(!UI()->CheckMouseLock()) - { - s_MouseX = clamp(s_MouseX + MouseRelX, 0.0f, Graphics()->WindowWidth()); - s_MouseY = clamp(s_MouseY + MouseRelY, 0.0f, Graphics()->WindowHeight()); - } + m_MouseWorldX = aPoints[0] + WorldWidth * (s_MouseX / Graphics()->WindowWidth()); + m_MouseWorldY = aPoints[1] + WorldHeight * (s_MouseY / Graphics()->WindowHeight()); + m_MouseDeltaWx = m_MouseDeltaX * (WorldWidth / Graphics()->WindowWidth()); + m_MouseDeltaWy = m_MouseDeltaY * (WorldHeight / Graphics()->WindowHeight()); + } + else + { + m_MouseWorldX = 0.0f; + m_MouseWorldY = 0.0f; + } - // update positions for ui, but only update ui when rendering - m_MouseX = UI()->Screen()->w * ((float)s_MouseX / Graphics()->WindowWidth()); - m_MouseY = UI()->Screen()->h * ((float)s_MouseY / Graphics()->WindowHeight()); + for(CLayerGroup *pGameGroup : m_Map.m_vpGroups) + { + if(!pGameGroup->m_GameGroup) + continue; - // fix correct world x and y - CLayerGroup *pGroup = GetSelectedGroup(); - if(pGroup) - { - float aPoints[4]; - pGroup->Mapping(aPoints); + float aPoints[4]; + pGameGroup->Mapping(aPoints); - float WorldWidth = aPoints[2] - aPoints[0]; - float WorldHeight = aPoints[3] - aPoints[1]; + float WorldWidth = aPoints[2] - aPoints[0]; + float WorldHeight = aPoints[3] - aPoints[1]; - m_MouseWScale = WorldWidth / Graphics()->WindowWidth(); + m_MouseWorldNoParaX = aPoints[0] + WorldWidth * (s_MouseX / Graphics()->WindowWidth()); + m_MouseWorldNoParaY = aPoints[1] + WorldHeight * (s_MouseY / Graphics()->WindowHeight()); + } +} - m_MouseWorldX = aPoints[0] + WorldWidth * (s_MouseX / Graphics()->WindowWidth()); - m_MouseWorldY = aPoints[1] + WorldHeight * (s_MouseY / Graphics()->WindowHeight()); - m_MouseDeltaWx = m_MouseDeltaX * (WorldWidth / Graphics()->WindowWidth()); - m_MouseDeltaWy = m_MouseDeltaY * (WorldHeight / Graphics()->WindowHeight()); - } - else +void CEditor::HandleAutosave() +{ + const float Time = Client()->GlobalTime(); + const float LastAutosaveUpdateTime = m_Map.m_LastAutosaveUpdateTime; + m_Map.m_LastAutosaveUpdateTime = Time; + + if(g_Config.m_EdAutosaveInterval == 0) + return; // autosave disabled + if(!m_Map.m_ModifiedAuto || m_Map.m_LastModifiedTime < 0.0f) + return; // no unsaved changes + + // Add time to autosave timer if the editor was disabled for more than 10 seconds, + // to prevent autosave from immediately activating when the editor is activated + // after being deactivated for some time. + if(LastAutosaveUpdateTime >= 0.0f && Time - LastAutosaveUpdateTime > 10.0f) + { + m_Map.m_LastSaveTime += Time - LastAutosaveUpdateTime; + } + + // Check if autosave timer has expired. + if(m_Map.m_LastSaveTime >= Time || Time - m_Map.m_LastSaveTime < 60 * g_Config.m_EdAutosaveInterval) + return; + + // Wait for 5 seconds of no modification before saving, to prevent autosave + // from immediately activating when a map is first modified or while user is + // modifying the map, but don't delay the autosave for more than 1 minute. + if(Time - m_Map.m_LastModifiedTime < 5.0f && Time - m_Map.m_LastSaveTime < 60 * (g_Config.m_EdAutosaveInterval + 1)) + return; + + PerformAutosave(); +} + +bool CEditor::PerformAutosave() +{ + char aDate[20]; + char aAutosavePath[IO_MAX_PATH_LENGTH]; + str_timestamp(aDate, sizeof(aDate)); + char aFileNameNoExt[IO_MAX_PATH_LENGTH]; + if(m_aFileName[0] == '\0') + { + str_copy(aFileNameNoExt, "unnamed"); + } + else + { + const char *pFileName = fs_filename(m_aFileName); + str_truncate(aFileNameNoExt, sizeof(aFileNameNoExt), pFileName, str_length(pFileName) - str_length(".map")); + } + str_format(aAutosavePath, sizeof(aAutosavePath), "maps/auto/%s_%s.map", aFileNameNoExt, aDate); + + m_Map.m_LastSaveTime = Client()->GlobalTime(); + if(Save(aAutosavePath)) + { + m_Map.m_ModifiedAuto = false; + // Clean up autosaves + if(g_Config.m_EdAutosaveMax) { - m_MouseWorldX = 0.0f; - m_MouseWorldY = 0.0f; + CFileCollection AutosavedMaps; + AutosavedMaps.Init(Storage(), "maps/auto", aFileNameNoExt, ".map", g_Config.m_EdAutosaveMax); } + return true; + } + else + { + ShowFileDialogError("Failed to automatically save map to file '%s'.", aAutosavePath); + return false; + } +} - for(CLayerGroup *pGameGroup : m_Map.m_vpGroups) - { - if(!pGameGroup->m_GameGroup) - continue; +void CEditor::HandleWriterFinishJobs() +{ + if(m_WriterFinishJobs.empty()) + return; - float aPoints[4]; - pGameGroup->Mapping(aPoints); + std::shared_ptr pJob = m_WriterFinishJobs.front(); + if(pJob->Status() != IJob::STATE_DONE) + return; - float WorldWidth = aPoints[2] - aPoints[0]; - float WorldHeight = aPoints[3] - aPoints[1]; + char aBuf[IO_MAX_PATH_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "saving '%s' done", pJob->GetFileName()); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", aBuf); + + // send rcon.. if we can + if(Client()->RconAuthed()) + { + CServerInfo CurrentServerInfo; + Client()->GetServerInfo(&CurrentServerInfo); + NETADDR ServerAddr = Client()->ServerAddress(); + const unsigned char aIpv4Localhost[16] = {127, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const unsigned char aIpv6Localhost[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; - m_MouseWorldNoParaX = aPoints[0] + WorldWidth * (s_MouseX / Graphics()->WindowWidth()); - m_MouseWorldNoParaY = aPoints[1] + WorldHeight * (s_MouseY / Graphics()->WindowHeight()); + // and if we're on localhost + if(!mem_comp(ServerAddr.ip, aIpv4Localhost, sizeof(aIpv4Localhost)) || !mem_comp(ServerAddr.ip, aIpv6Localhost, sizeof(aIpv6Localhost))) + { + char aMapName[128]; + IStorage::StripPathAndExtension(pJob->GetFileName(), aMapName, sizeof(aMapName)); + if(!str_comp(aMapName, CurrentServerInfo.m_aMap)) + Client()->Rcon("reload"); } } + + m_WriterFinishJobs.pop_front(); +} + +void CEditor::OnUpdate() +{ + CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI + + if(!m_EditorWasUsedBefore) + { + m_EditorWasUsedBefore = true; + Reset(); + } + + HandleCursorMovement(); + HandleAutosave(); + HandleWriterFinishJobs(); } void CEditor::OnRender() @@ -6935,8 +7251,15 @@ void CEditor::OnRender() void CEditor::LoadCurrentMap() { - Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL); - m_ValidSaveFilename = true; + if(Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_SAVE)) + { + m_ValidSaveFilename = true; + } + else + { + Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL); + m_ValidSaveFilename = false; + } CGameClient *pGameClient = (CGameClient *)Kernel()->RequestInterface(); vec2 Center = pGameClient->m_Camera.m_Center; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 12d4fb62520..ef77c929fd7 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -12,13 +12,17 @@ #include #include +#include #include +#include +#include #include "auto_map.h" #include "chillerbot/chillereditor.h" #include +#include #include #include @@ -317,7 +321,6 @@ class CEditorMap public: CEditor *m_pEditor; - bool m_Modified; CEditorMap() { @@ -329,6 +332,13 @@ class CEditorMap Clean(); } + bool m_Modified; // unsaved changes in manual save + bool m_ModifiedAuto; // unsaved changes in autosave + float m_LastModifiedTime; + float m_LastSaveTime; + float m_LastAutosaveUpdateTime; + void OnModify(); + std::vector m_vpGroups; std::vector m_vpImages; std::vector m_vpEnvelopes; @@ -372,7 +382,7 @@ class CEditorMap CEnvelope *NewEnvelope(int Channels) { - m_Modified = true; + OnModify(); CEnvelope *pEnv = new CEnvelope(Channels); m_vpEnvelopes.push_back(pEnv); return pEnv; @@ -385,7 +395,7 @@ class CEditorMap CLayerGroup *NewGroup() { - m_Modified = true; + OnModify(); CLayerGroup *pGroup = new CLayerGroup; pGroup->m_pMap = this; m_vpGroups.push_back(pGroup); @@ -400,7 +410,7 @@ class CEditorMap return Index0; if(Index0 == Index1) return Index0; - m_Modified = true; + OnModify(); std::swap(m_vpGroups[Index0], m_vpGroups[Index1]); return Index1; } @@ -409,28 +419,28 @@ class CEditorMap { if(Index < 0 || Index >= (int)m_vpGroups.size()) return; - m_Modified = true; + OnModify(); delete m_vpGroups[Index]; m_vpGroups.erase(m_vpGroups.begin() + Index); } void ModifyImageIndex(INDEX_MODIFY_FUNC pfnFunc) { - m_Modified = true; + OnModify(); for(auto &pGroup : m_vpGroups) pGroup->ModifyImageIndex(pfnFunc); } void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) { - m_Modified = true; + OnModify(); for(auto &pGroup : m_vpGroups) pGroup->ModifyEnvelopeIndex(pfnFunc); } void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) { - m_Modified = true; + OnModify(); for(auto &pGroup : m_vpGroups) pGroup->ModifySoundIndex(pfnFunc); } @@ -439,8 +449,8 @@ class CEditorMap void CreateDefault(IGraphics::CTextureHandle EntitiesTexture); // io - bool Save(class IStorage *pStorage, const char *pFilename); - bool Load(class IStorage *pStorage, const char *pFilename, int StorageType); + bool Save(const char *pFilename); + bool Load(const char *pFilename, int StorageType); // DDRace @@ -687,16 +697,37 @@ class CLayerGame : public CLayerTiles CUI::EPopupMenuFunctionResult RenderProperties(CUIRect *pToolbox) override; }; +class CDataFileWriterFinishJob : public IJob +{ + char m_aFileName[IO_MAX_PATH_LENGTH]; + CDataFileWriter m_Writer; + + void Run() override + { + m_Writer.Finish(); + } + +public: + CDataFileWriterFinishJob(const char *pFileName, CDataFileWriter &&Writer) : + m_Writer(std::move(Writer)) + { + str_copy(m_aFileName, pFileName); + } + + const char *GetFileName() const { return m_aFileName; } +}; + class CEditor : public IEditor { - class IInput *m_pInput; - class IClient *m_pClient; - class CConfig *m_pConfig; - class IConsole *m_pConsole; - class IGraphics *m_pGraphics; - class ITextRender *m_pTextRender; - class ISound *m_pSound; - class IStorage *m_pStorage; + class IInput *m_pInput = nullptr; + class IClient *m_pClient = nullptr; + class CConfig *m_pConfig = nullptr; + class IConsole *m_pConsole = nullptr; + class IEngine *m_pEngine = nullptr; + class IGraphics *m_pGraphics = nullptr; + class ITextRender *m_pTextRender = nullptr; + class ISound *m_pSound = nullptr; + class IStorage *m_pStorage = nullptr; CRenderTools m_RenderTools; CUI m_UI; CChillerEditor m_ChillerEditor; @@ -713,11 +744,11 @@ class CEditor : public IEditor int GetTextureUsageFlag(); - enum EPreviewImageState + enum EPreviewState { - PREVIEWIMAGE_UNLOADED, - PREVIEWIMAGE_LOADED, - PREVIEWIMAGE_ERROR, + PREVIEW_UNLOADED, + PREVIEW_LOADED, + PREVIEW_ERROR, }; public: @@ -725,6 +756,7 @@ class CEditor : public IEditor class IClient *Client() { return m_pClient; } class CConfig *Config() { return m_pConfig; } class IConsole *Console() { return m_pConsole; } + class IEngine *Engine() { return m_pEngine; } class IGraphics *Graphics() { return m_pGraphics; } class ISound *Sound() { return m_pSound; } class ITextRender *TextRender() { return m_pTextRender; } @@ -735,12 +767,6 @@ class CEditor : public IEditor CEditor() : m_TilesetPicker(16, 16) { - m_pInput = nullptr; - m_pClient = nullptr; - m_pGraphics = nullptr; - m_pTextRender = nullptr; - m_pSound = nullptr; - m_EntitiesTexture.Invalidate(); m_FrontTexture.Invalidate(); m_TeleTexture.Invalidate(); @@ -778,7 +804,8 @@ class CEditor : public IEditor m_FilesSelectedIndex = -1; m_FilePreviewImage.Invalidate(); - m_PreviewImageState = PREVIEWIMAGE_UNLOADED; + m_FilePreviewSound = -1; + m_FilePreviewState = PREVIEW_UNLOADED; m_SelectEntitiesImage = "DDNet"; @@ -851,6 +878,11 @@ class CEditor : public IEditor void UpdateMentions() override { m_Mentions++; } void ResetMentions() override { m_Mentions = 0; } + void HandleCursorMovement(); + void HandleAutosave(); + bool PerformAutosave(); + void HandleWriterFinishJobs(); + CLayerGroup *m_apSavedBrushes[10]; void RefreshFilteredFileList(); @@ -868,6 +900,10 @@ class CEditor : public IEditor void LoadCurrentMap(); void Render(); + void RenderPressedKeys(CUIRect View); + void RenderSavingIndicator(CUIRect View); + void RenderMousePointer(); + void ResetMenuBackgroundPositions(); std::vector GetSelectedQuads(); @@ -882,7 +918,6 @@ class CEditor : public IEditor bool IsQuadSelected(int Index) const; int FindSelectedQuadIndex(int Index) const; - float ScaleFontSize(char *pText, int TextSize, float FontSize, int Width); int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); int m_Mode; @@ -906,6 +941,7 @@ class CEditor : public IEditor POPEVENT_LOADCURRENT, POPEVENT_NEW, POPEVENT_SAVE, + POPEVENT_SAVE_COPY, POPEVENT_LARGELAYER, POPEVENT_PREVENTUNUSEDTILES, POPEVENT_IMAGEDIV16, @@ -928,6 +964,7 @@ class CEditor : public IEditor FILETYPE_MAP, FILETYPE_IMG, FILETYPE_SOUND, + NUM_FILETYPES }; int m_FileDialogStorageType; @@ -943,10 +980,14 @@ class CEditor : public IEditor CLineInputBuffered m_FileDialogFilterInput; char *m_pFileDialogPath; int m_FileDialogFileType; + bool m_FileDialogMultipleStorages = false; + bool m_FileDialogShowingRoot = false; int m_FilesSelectedIndex; CLineInputBuffered m_FileDialogNewFolderNameInput; + IGraphics::CTextureHandle m_FilePreviewImage; - EPreviewImageState m_PreviewImageState; + int m_FilePreviewSound; + EPreviewState m_FilePreviewState; CImageInfo m_FilePreviewImageInfo; bool m_FileDialogOpening; @@ -968,6 +1009,8 @@ class CEditor : public IEditor return true; if(str_comp(pRhs->m_aFilename, "..") == 0) return false; + if(pLhs->m_IsLink != pRhs->m_IsLink) + return pLhs->m_IsLink; if(pLhs->m_IsDir != pRhs->m_IsDir) return pLhs->m_IsDir; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) < 0; @@ -979,6 +1022,8 @@ class CEditor : public IEditor return true; if(str_comp(pRhs->m_aFilename, "..") == 0) return false; + if(pLhs->m_IsLink != pRhs->m_IsLink) + return pLhs->m_IsLink; if(pLhs->m_IsDir != pRhs->m_IsDir) return pLhs->m_IsDir; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) > 0; @@ -1117,6 +1162,8 @@ class CEditor : public IEditor static const void *ms_pUiGotContext; CEditorMap m_Map; + std::deque> m_WriterFinishJobs; + int m_ShiftBy; static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser); @@ -1212,7 +1259,8 @@ class CEditor : public IEditor void DoSoundSource(CSoundSource *pSource, int Index); void DoMapEditor(CUIRect View); - void DoToolbar(CUIRect Toolbar); + void DoToolbarLayers(CUIRect Toolbar); + void DoToolbarSounds(CUIRect Toolbar); void DoQuad(CQuad *pQuad, int Index); ColorRGBA GetButtonColor(const void *pID, int Checked); diff --git a/src/game/editor/io.cpp b/src/game/editor/io.cpp index 08bf4c4f64d..14f7ecefa8e 100644 --- a/src/game/editor/io.cpp +++ b/src/game/editor/io.cpp @@ -35,16 +35,20 @@ struct CSoundSource_DEPRECATED bool CEditor::Save(const char *pFilename) { - return m_Map.Save(Kernel()->RequestInterface(), pFilename); + // Check if file with this name is already being saved at the moment + if(std::any_of(std::begin(m_WriterFinishJobs), std::end(m_WriterFinishJobs), [pFilename](const std::shared_ptr &Job) { return str_comp(pFilename, Job->GetFileName()) == 0; })) + return false; + + return m_Map.Save(pFilename); } -bool CEditorMap::Save(class IStorage *pStorage, const char *pFileName) +bool CEditorMap::Save(const char *pFileName) { - char aBuf[256]; + char aBuf[IO_MAX_PATH_LENGTH + 64]; str_format(aBuf, sizeof(aBuf), "saving to '%s'...", pFileName); m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); CDataFileWriter df; - if(!df.Open(pStorage, pFileName)) + if(!df.Open(m_pEditor->Storage(), pFileName)) { str_format(aBuf, sizeof(aBuf), "failed to open file '%s'...", pFileName); m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); @@ -375,27 +379,9 @@ bool CEditorMap::Save(class IStorage *pStorage, const char *pFileName) free(pPoints); // finish the data file - df.Finish(); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving done"); - - // send rcon.. if we can - if(m_pEditor->Client()->RconAuthed()) - { - CServerInfo CurrentServerInfo; - m_pEditor->Client()->GetServerInfo(&CurrentServerInfo); - NETADDR ServerAddr = m_pEditor->Client()->ServerAddress(); - const unsigned char aIpv4Localhost[16] = {127, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - const unsigned char aIpv6Localhost[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; - - // and if we're on localhost - if(!mem_comp(ServerAddr.ip, aIpv4Localhost, sizeof(aIpv4Localhost)) || !mem_comp(ServerAddr.ip, aIpv6Localhost, sizeof(aIpv6Localhost))) - { - char aMapName[128]; - IStorage::StripPathAndExtension(pFileName, aMapName, sizeof(aMapName)); - if(!str_comp(aMapName, CurrentServerInfo.m_aMap)) - m_pEditor->Client()->Rcon("reload"); - } - } + std::shared_ptr pWriterFinishJob = std::make_shared(pFileName, std::move(df)); + m_pEditor->Engine()->AddJob(pWriterFinishJob); + m_pEditor->m_WriterFinishJobs.push_back(pWriterFinishJob); return true; } @@ -403,7 +389,7 @@ bool CEditorMap::Save(class IStorage *pStorage, const char *pFileName) bool CEditor::Load(const char *pFileName, int StorageType) { Reset(); - bool Result = m_Map.Load(Kernel()->RequestInterface(), pFileName, StorageType); + bool Result = m_Map.Load(pFileName, StorageType); if(Result) { str_copy(m_aFileName, pFileName); @@ -419,10 +405,10 @@ bool CEditor::Load(const char *pFileName, int StorageType) return Result; } -bool CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int StorageType) +bool CEditorMap::Load(const char *pFileName, int StorageType) { CDataFileReader DataFile; - if(!DataFile.Open(pStorage, pFileName, StorageType)) + if(!DataFile.Open(m_pEditor->Storage(), pFileName, StorageType)) return false; Clean(); @@ -492,7 +478,7 @@ bool CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Stora if(pItem->m_External) { - char aBuf[256]; + char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "mapres/%s.png", pName); // load external @@ -554,11 +540,11 @@ bool CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Stora if(pItem->m_External) { - char aBuf[256]; + char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "mapres/%s.opus", pName); // load external - if(pStorage->ReadFile(pName, IStorage::TYPE_ALL, &pSound->m_pData, &pSound->m_DataSize)) + if(m_pEditor->Storage()->ReadFile(pName, IStorage::TYPE_ALL, &pSound->m_pData, &pSound->m_DataSize)) { pSound->m_SoundID = m_pEditor->Sound()->LoadOpusFromMem(pSound->m_pData, pSound->m_DataSize, true); } @@ -980,6 +966,9 @@ bool CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Stora return false; m_Modified = false; + m_ModifiedAuto = false; + m_LastModifiedTime = -1.0f; + m_LastSaveTime = m_pEditor->Client()->GlobalTime(); return true; } @@ -995,7 +984,7 @@ bool CEditor::Append(const char *pFileName, int StorageType) CEditorMap NewMap; NewMap.m_pEditor = this; - if(!NewMap.Load(Kernel()->RequestInterface(), pFileName, StorageType)) + if(!NewMap.Load(pFileName, StorageType)) return false; // modify indices diff --git a/src/game/editor/layer_quads.cpp b/src/game/editor/layer_quads.cpp index 585ca776ad7..27dc484f4f0 100644 --- a/src/game/editor/layer_quads.cpp +++ b/src/game/editor/layer_quads.cpp @@ -37,7 +37,7 @@ void CLayerQuads::Render(bool QuadPicker) CQuad *CLayerQuads::NewQuad(int x, int y, int Width, int Height) { - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); m_vQuads.emplace_back(); CQuad *pQuad = &m_vQuads[m_vQuads.size() - 1]; @@ -153,7 +153,7 @@ void CLayerQuads::BrushPlace(CLayer *pBrush, float wx, float wy) m_vQuads.push_back(n); } - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } void CLayerQuads::BrushFlipX() @@ -163,7 +163,7 @@ void CLayerQuads::BrushFlipX() std::swap(Quad.m_aPoints[0], Quad.m_aPoints[1]); std::swap(Quad.m_aPoints[2], Quad.m_aPoints[3]); } - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } void CLayerQuads::BrushFlipY() @@ -173,7 +173,7 @@ void CLayerQuads::BrushFlipY() std::swap(Quad.m_aPoints[0], Quad.m_aPoints[2]); std::swap(Quad.m_aPoints[1], Quad.m_aPoints[3]); } - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } void Rotate(vec2 *pCenter, vec2 *pPoint, float Rotation) @@ -236,7 +236,7 @@ CUI::EPopupMenuFunctionResult CLayerQuads::RenderProperties(CUIRect *pToolBox) int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); if(Prop != -1) { - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } if(Prop == PROP_IMAGE) @@ -277,7 +277,7 @@ int CLayerQuads::SwapQuads(int Index0, int Index1) return Index0; if(Index0 == Index1) return Index0; - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); std::swap(m_vQuads[Index0], m_vQuads[Index1]); return Index1; } diff --git a/src/game/editor/layer_sounds.cpp b/src/game/editor/layer_sounds.cpp index 1c236aa5471..1f20f1defcd 100644 --- a/src/game/editor/layer_sounds.cpp +++ b/src/game/editor/layer_sounds.cpp @@ -101,7 +101,7 @@ void CLayerSounds::Render(bool Tileset) CSoundSource *CLayerSounds::NewSource(int x, int y) { - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); m_vSources.emplace_back(); CSoundSource *pSource = &m_vSources[m_vSources.size() - 1]; @@ -179,7 +179,7 @@ void CLayerSounds::BrushPlace(CLayer *pBrush, float wx, float wy) m_vSources.push_back(n); } - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) @@ -200,7 +200,7 @@ CUI::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox) int Prop = m_pEditor->DoProperties(pToolBox, aProps, s_aIds, &NewVal); if(Prop != -1) { - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); } if(Prop == PROP_SOUND) diff --git a/src/game/editor/layer_tiles.cpp b/src/game/editor/layer_tiles.cpp index cbbbeed9599..8c1a047c49f 100644 --- a/src/game/editor/layer_tiles.cpp +++ b/src/game/editor/layer_tiles.cpp @@ -775,8 +775,6 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox) } } } - - return CUI::POPUP_CLOSE_CURRENT; } } @@ -1079,7 +1077,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderCommonProperties(SCommonPropSta void CLayerTiles::FlagModified(int x, int y, int w, int h) { - m_pEditor->m_Map.m_Modified = true; + m_pEditor->m_Map.OnModify(); if(m_Seed != 0 && m_AutoMapperConfig != -1 && m_AutoAutoMap && m_Image >= 0) { m_pEditor->m_Map.m_vpImages[m_Image]->m_AutoMapper.ProceedLocalized(this, m_AutoMapperConfig, m_Seed, x, y, w, h); @@ -1296,7 +1294,7 @@ void CLayerTele::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) if(!Destructive && GetTile(fx, fy).m_Index) continue; - const int SrcIndex = (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index))) @@ -1551,7 +1549,7 @@ void CLayerSpeedup::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) if(!Destructive && GetTile(fx, fy).m_Index) continue; - const int SrcIndex = (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset @@ -1846,7 +1844,7 @@ void CLayerSwitch::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) if(!Destructive && GetTile(fx, fy).m_Index) continue; - const int SrcIndex = (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index))) @@ -2094,7 +2092,7 @@ void CLayerTune::FillSelection(bool Empty, CLayer *pBrush, CUIRect Rect) if(!Destructive && GetTile(fx, fy).m_Index) continue; - const int SrcIndex = (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); const int TgtIndex = fy * m_Width + fx; if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index))) diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 67d514c4fe7..7584f5a9727 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -366,7 +366,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, if(!Found) { pGameLayer->m_pTiles[y * pGameLayer->m_Width + x].m_Index = TILE_AIR; - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } } } @@ -512,7 +512,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, static CLineInput s_NameInput; s_NameInput.SetBuffer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName)); if(pEditor->DoEditBox(&s_NameInput, &Button, 10.0f)) - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } enum @@ -557,7 +557,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } if(Prop == PROP_ORDER) @@ -683,7 +683,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, static CLineInput s_NameInput; s_NameInput.SetBuffer(pCurrentLayer->m_aName, sizeof(pCurrentLayer->m_aName)); if(pEditor->DoEditBox(&s_NameInput, &EditBox, 10.0f)) - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } // spacing if any button was rendered @@ -717,7 +717,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } if(Prop == PROP_ORDER) @@ -762,7 +762,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b { if(pLayer) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); pEditor->DeleteSelectedQuads(); } return CUI::POPUP_CLOSE_CURRENT; @@ -802,7 +802,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b pQuad->m_aPoints[2].y = Top + Height; pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Top + Height; - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } return CUI::POPUP_CLOSE_CURRENT; } @@ -821,7 +821,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b pQuad->m_aPoints[k].x = 1000.0f * (pQuad->m_aPoints[k].x / 1000); pQuad->m_aPoints[k].y = 1000.0f * (pQuad->m_aPoints[k].y / 1000); } - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } return CUI::POPUP_CLOSE_CURRENT; } @@ -859,7 +859,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b pQuad->m_aPoints[2].y = Bottom; pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Bottom; - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } return CUI::POPUP_CLOSE_CURRENT; } @@ -904,7 +904,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x; @@ -988,7 +988,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, CLayerSounds *pLayer = (CLayerSounds *)pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS); if(pLayer) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); pLayer->m_vSources.erase(pLayer->m_vSources.begin() + pEditor->m_SelectedSource); pEditor->m_SelectedSource--; } @@ -1062,7 +1062,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } if(Prop == PROP_POS_X) @@ -1145,7 +1145,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, Prop = pEditor->DoProperties(&View, aCircleProps, s_aCircleIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } if(Prop == PROP_CIRCLE_RADIUS) @@ -1176,7 +1176,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, Prop = pEditor->DoProperties(&View, aRectangleProps, s_aRectangleIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } if(Prop == PROP_RECTANGLE_WIDTH) @@ -1199,6 +1199,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, { CEditor *pEditor = static_cast(pContext); std::vector vpQuads = pEditor->GetSelectedQuads(); + if(!in_range(pEditor->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) + return CUI::POPUP_CLOSE_CURRENT; CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex]; enum @@ -1236,7 +1238,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop != -1) { - pEditor->m_Map.m_Modified = true; + pEditor->m_Map.OnModify(); } for(auto &pQuad : vpQuads) @@ -1589,7 +1591,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, pTitle = "New map"; pMessage = "The map contains unsaved data, you might want to save it before you create a new map.\n\nContinue anyway?"; } - else if(pEditor->m_PopupEventType == POPEVENT_SAVE) + else if(pEditor->m_PopupEventType == POPEVENT_SAVE || pEditor->m_PopupEventType == POPEVENT_SAVE_COPY) { pTitle = "Save map"; pMessage = "The file already exists.\n\nDo you want to overwrite the map?"; @@ -1677,6 +1679,11 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, CallbackSaveMap(pEditor->m_aFileSaveName, IStorage::TYPE_SAVE, pEditor); return CUI::POPUP_CLOSE_CURRENT; } + else if(pEditor->m_PopupEventType == POPEVENT_SAVE_COPY) + { + CallbackSaveCopyMap(pEditor->m_aFileSaveName, IStorage::TYPE_SAVE, pEditor); + return CUI::POPUP_CLOSE_CURRENT; + } else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES) { pEditor->PlaceBorderTiles(); @@ -1848,6 +1855,8 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUI { CEditor *pEditor = static_cast(pContext); + const int PreviousSelected = s_GametileOpSelected; + CUIRect Button; for(size_t i = 0; i < std::size(s_apGametileOpButtonNames); ++i) { @@ -1857,7 +1866,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUI s_GametileOpSelected = i; } - return CUI::POPUP_KEEP_OPEN; + return s_GametileOpSelected == PreviousSelected ? CUI::POPUP_KEEP_OPEN : CUI::POPUP_CLOSE_CURRENT; } void CEditor::PopupSelectGametileOpInvoke(float x, float y) diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 8b037d423eb..629adf951f4 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -45,7 +45,9 @@ void CGameContext::ConCredits(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "sjrc6, Cellegen, srdante, Nouaa, Voxel, luk51,"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", - "Vy0x2, Avolicious, louis, Marmare314 & others."); + "Vy0x2, Avolicious, louis, Marmare314, hus3h,"); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "ArijanJ & others"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Based on DDRace by the DDRace developers,"); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", @@ -750,6 +752,11 @@ void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Need to have started the map to swap with a player."); return; } + if(pSelf->m_World.m_Core.m_apCharacters[pResult->m_ClientID] == nullptr || pSelf->m_World.m_Core.m_apCharacters[TargetClientId] == nullptr) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "You and the other player must not be paused."); + return; + } bool SwapPending = pSwapPlayer->m_SwapTargetsClientID != pResult->m_ClientID; if(SwapPending) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 1d40933a724..27365d79fe2 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -2094,10 +2094,10 @@ void CCharacter::DDRacePostCoreTick() return; // handle Anti-Skip tiles - std::list Indices = Collision()->GetMapIndices(m_PrevPos, m_Pos); - if(!Indices.empty()) + std::vector vIndices = Collision()->GetMapIndices(m_PrevPos, m_Pos); + if(!vIndices.empty()) { - for(int &Index : Indices) + for(int &Index : vIndices) { HandleTiles(Index); if(!m_Alive) diff --git a/src/game/server/entities/light.cpp b/src/game/server/entities/light.cpp index f351745c28d..957b8715b3f 100644 --- a/src/game/server/entities/light.cpp +++ b/src/game/server/entities/light.cpp @@ -28,11 +28,10 @@ CLight::CLight(CGameWorld *pGameWorld, vec2 Pos, float Rotation, int Length, bool CLight::HitCharacter() { - std::list HitCharacters = - GameServer()->m_World.IntersectedCharacters(m_Pos, m_To, 0.0f, 0); - if(HitCharacters.empty()) + std::vector vpHitCharacters = GameServer()->m_World.IntersectedCharacters(m_Pos, m_To, 0.0f, 0); + if(vpHitCharacters.empty()) return false; - for(auto *pChar : HitCharacters) + for(auto *pChar : vpHitCharacters) { if(m_Layer == LAYER_SWITCH && m_Number > 0 && !Switchers()[m_Number].m_aStatus[pChar->Team()]) continue; diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index adc53a933e9..7ea14b34200 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -399,10 +399,9 @@ CCharacter *CGameWorld::ClosestCharacter(vec2 Pos, float Radius, const CEntity * return pClosest; } -std::list CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis) +std::vector CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis) { - std::list listOfChars; - + std::vector vpCharacters; CCharacter *pChr = (CCharacter *)FindFirst(CGameWorld::ENTTYPE_CHARACTER); for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) { @@ -416,11 +415,11 @@ std::list CGameWorld::IntersectedCharacters(vec2 Pos0, vec2 if(Len < pChr->m_ProximityRadius + Radius) { pChr->m_Intersection = IntersectPos; - listOfChars.push_back(pChr); + vpCharacters.push_back(pChr); } } } - return listOfChars; + return vpCharacters; } void CGameWorld::ReleaseHooked(int ClientID) diff --git a/src/game/server/gameworld.h b/src/game/server/gameworld.h index 9cec9ae3187..120a550a1ed 100644 --- a/src/game/server/gameworld.h +++ b/src/game/server/gameworld.h @@ -5,7 +5,7 @@ #include -#include +#include class CEntity; class CCharacter; @@ -165,7 +165,7 @@ class CGameWorld Returns: Returns list with all Characters on line. */ - std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); + std::vector IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); }; #endif diff --git a/src/game/variables.h b/src/game/variables.h index 04dd9165fd6..f0bc7e951b6 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -152,6 +152,10 @@ MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGF MACRO_CONFIG_INT(ClDyncamSmoothness, cl_dyncam_smoothness, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Transition amount of the camera movement, 0=instant, 100=slow and smooth") MACRO_CONFIG_INT(ClDyncamStabilizing, cl_dyncam_stabilizing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Amount of camera slowdown during fast cursor movement. High value can cause delay in camera movement") +MACRO_CONFIG_INT(ClMultiViewZoomSmoothness, cl_multi_view_zoom_smoothness, 1300, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Set the smoothness of the multi view zoom (in ms, higher = slower)") + +MACRO_CONFIG_INT(EdAutosaveInterval, ed_autosave_interval, 10, 0, 240, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interval in minutes at which a copy of the current editor map is automatically saved to the 'auto' folder (0 for off)") +MACRO_CONFIG_INT(EdAutosaveMax, ed_autosave_max, 10, 0, 1000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum number of autosaves that are kept per map name (0 = no limit)") MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation in the editor in ms (0 for off)") MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target") @@ -200,7 +204,6 @@ MACRO_CONFIG_COL(UiColor, ui_color, 0xE4A046AF, CFGFLAG_CLIENT | CFGFLAG_SAVE | MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping") MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype") -MACRO_CONFIG_STR(UiDemoSelected, ui_demo_selected, 256, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Selected demo file") MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting") MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news") diff --git a/src/game/version.h b/src/game/version.h index ea765e26179..9b0cdee50e7 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -3,11 +3,11 @@ #ifndef GAME_VERSION_H #define GAME_VERSION_H #ifndef GAME_RELEASE_VERSION -#define GAME_RELEASE_VERSION "17.0.3" +#define GAME_RELEASE_VERSION "17.1" #endif #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" -#define CLIENT_VERSIONNR 17003 +#define CLIENT_VERSIONNR 17010 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" #endif diff --git a/src/test/fs.cpp b/src/test/fs.cpp index efe9236ffbd..806eac2f47c 100644 --- a/src/test/fs.cpp +++ b/src/test/fs.cpp @@ -3,6 +3,51 @@ #include +TEST(Filesystem, Filename) +{ + EXPECT_STREQ(fs_filename(""), ""); + EXPECT_STREQ(fs_filename("a"), "a"); + EXPECT_STREQ(fs_filename("abc"), "abc"); + EXPECT_STREQ(fs_filename("a/b"), "b"); + EXPECT_STREQ(fs_filename("a/b/c"), "c"); + EXPECT_STREQ(fs_filename("aaaaa/bbbb/ccc"), "ccc"); + EXPECT_STREQ(fs_filename("aaaaa\\bbbb\\ccc"), "ccc"); + EXPECT_STREQ(fs_filename("aaaaa/bbbb\\ccc"), "ccc"); + EXPECT_STREQ(fs_filename("aaaaa\\bbbb/ccc"), "ccc"); +} + +TEST(Filesystem, SplitFileExtension) +{ + char aName[IO_MAX_PATH_LENGTH]; + char aExt[IO_MAX_PATH_LENGTH]; + + fs_split_file_extension("", aName, sizeof(aName), aExt, sizeof(aExt)); + EXPECT_STREQ(aName, ""); + EXPECT_STREQ(aExt, ""); + + fs_split_file_extension("name.ext", aName, sizeof(aName), aExt, sizeof(aExt)); + EXPECT_STREQ(aName, "name"); + EXPECT_STREQ(aExt, "ext"); + + fs_split_file_extension("name.ext", aName, sizeof(aName)); // extension parameter is optional + EXPECT_STREQ(aName, "name"); + + fs_split_file_extension("name.ext", nullptr, 0, aExt, sizeof(aExt)); // name parameter is optional + EXPECT_STREQ(aExt, "ext"); + + fs_split_file_extension("archive.tar.gz", aName, sizeof(aName), aExt, sizeof(aExt)); + EXPECT_STREQ(aName, "archive.tar"); + EXPECT_STREQ(aExt, "gz"); + + fs_split_file_extension("no_dot", aName, sizeof(aName), aExt, sizeof(aExt)); + EXPECT_STREQ(aName, "no_dot"); + EXPECT_STREQ(aExt, ""); + + fs_split_file_extension(".dot_first", aName, sizeof(aName), aExt, sizeof(aExt)); + EXPECT_STREQ(aName, ".dot_first"); + EXPECT_STREQ(aExt, ""); +} + TEST(Filesystem, CreateCloseDelete) { CTestInfo Info;