84 changes: 57 additions & 27 deletions data/languages/russian.txt
Expand Up @@ -868,7 +868,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== Перед посещением сервера рекомендуем изменить настройки на Ваш вкус.

Please enter your nick name below.
Please enter your nickname below.
== Пожалуйста, ниже укажите свой никнейм.

Destination file already exist
Expand Down Expand Up @@ -1111,18 +1111,6 @@ Check now
Time
== Время

Manual %3d:%02d
== Ручное %3d:%02d

Race %3d:%02d
== Попытка %3d:%02d

Auto %3d:%02d
== Авто %3d:%02d

Replay %3d:%02d
== Повтор %3d:%02d

Follow
== Следить

Expand Down Expand Up @@ -1232,38 +1220,80 @@ Learn
Play
== Играть

Theme
==
Saving ddnet-settings.cfg failed
== Неудачное сохранение ddnet-settings.cfg

File
==
Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
== Отладочный режим включен. Нажмите Ctrl+Shift+D, чтобы выйти.

Existing Player
== Существующий игрок

Your nickname '%s' is already used (%d points). Do you still want to use it?
== Твой никнейм '%s' уже существует (%d поинтов). Вы уверены, что хотите использовать его?

Checking for existing player with your name
== Проверка существования игрока с таким же именем

Theme
== Тема

Demos directory
==
== Папка с демо

Smooth Dynamic Camera
== Плавная Динамическая камера

Themes directory
==
== Папка с темами

Skin Database
==
== База данных скинов

Skins directory
==
== Папка со скинами

Game sound volume
== Громкость игры

Chat sound volume
== Громкость звуков чата

Background music volume
==
== Громкость фоновой музыки

Assets
==
== Графика

Entities
==
Use old chat style
== Использовать старый чат

Highlight
== Подсветка

Use current map as background
== Использовать данную карту как фон

Emoticons
==
== Эмоции

Particles
==
== Частицы

Assets directory
== Папка с графикой

Manual
== Ручное

Race
== Гонка

Auto
== Авто

Replay
== Запись

Entities
==
46 changes: 38 additions & 8 deletions data/languages/serbian.txt
Expand Up @@ -650,12 +650,18 @@ Video name:
Use k key to kill (restart), q to pause and watch other players. See settings for other key binds.
== Tipka K da se ubijes (restart), tipka q da pauziras i gledas druge igrače.

Saving ddnet-settings.cfg failed
==

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
==

Warning
==

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
==

Server best:
==

Expand Down Expand Up @@ -704,7 +710,16 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
==

Please enter your nick name below.
Please enter your nickname below.
==

Existing Player
==

Your nickname '%s' is already used (%d points). Do you still want to use it?
==

Checking for existing player with your name
==

Country / Region
Expand Down Expand Up @@ -767,9 +782,6 @@ Please use a different name
Remove chat
==

File
==

Markers:
==

Expand Down Expand Up @@ -824,6 +836,9 @@ Activate
Save
==

Smooth Dynamic Camera
==

Switch weapon when out of ammo
==

Expand Down Expand Up @@ -977,6 +992,12 @@ Enable highlighted chat sound
Threaded sound loading
==

Game sound volume
==

Chat sound volume
==

Map sound volume
==

Expand Down Expand Up @@ -1007,6 +1028,9 @@ Show score
Show health + ammo
==

Use old chat style
==

Show names in chat in team colors
==

Expand Down Expand Up @@ -1043,6 +1067,9 @@ We will win
Friend message
==

Highlight
==

Friend
==

Expand Down Expand Up @@ -1133,6 +1160,9 @@ Background (regular)
Background (entities)
==

Use current map as background
==

Show tiles layers from BG map
==

Expand Down Expand Up @@ -1212,16 +1242,16 @@ Restart
Time
==

Manual %3d:%02d
Manual
==

Race %3d:%02d
Race
==

Auto %3d:%02d
Auto
==

Replay %3d:%02d
Replay
==

Follow
Expand Down
1,293 changes: 1,293 additions & 0 deletions data/languages/serbian_cyrillic.txt

Large diffs are not rendered by default.

109 changes: 70 additions & 39 deletions data/languages/simplified_chinese.txt
Expand Up @@ -10,6 +10,7 @@
# 2020.8.14 Night_L
# 2020.8.19 TsFreddie
# 2020.8.20 Dan_cao
# 2020.11.12 TsFreddie
##### /authors #####

##### translated strings #####
Expand Down Expand Up @@ -793,13 +794,13 @@ Show other players' key presses
== 显示其他玩家的按键

Background (regular)
== 背景(常规)
== 背景(常规模式)

Background (entities)
== 背景(实体)
== 背景(实体层模式)

Show tiles layers from BG map
== 在背景地图显示图块层
== 显示实体层背景地图的图块层

Try fast HTTP map download first
== 优先尝试用HTTP快速下载地图
Expand Down Expand Up @@ -918,7 +919,7 @@ Refresh Rate
== 刷新率

New random timeout code
== 新随机超时代码
== 随机生成新的超时代码(Timeout代码)

Suicides
== 自杀数
Expand Down Expand Up @@ -1008,21 +1009,12 @@ Time
Show entities
== 实体层显示开关

Race %3d:%02d
== 竞速赛 %3d:%02d

Replay %3d:%02d
== 回放 %3d:%02d

Show all
== 显示所有玩家

Downloading %s:
== 正在下载 %s:

Manual %3d:%02d
== 手动 %3d:%02d

%d new mentions
== %d 条新提示

Expand Down Expand Up @@ -1083,9 +1075,6 @@ Size
Dummy copy
== 分身同步动作

Auto %3d:%02d
== 自动 %3d:%02d

9+ new mentions
== 9+ 条新提示

Expand Down Expand Up @@ -1140,7 +1129,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== 推荐在进入服务器前,先将设置调整到符合你的习惯。

Please enter your nick name below.
Please enter your nickname below.
== 请输入你的昵称。

Render
Expand All @@ -1150,7 +1139,7 @@ Server best:
== 服务器最佳

Learn
== 学习
== 教程

Use high DPI
== 使用高 DPI
Expand Down Expand Up @@ -1189,7 +1178,7 @@ https://wiki.ddnet.tw/
== https://docs.qq.com/doc/DWGFrV0xPRmVWVkla

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
== 宽和高无法被16整除,这可能会导致画面异常
== 材质 %s 的宽和高无法被16整除,这可能会导致画面异常

Warning
== 警告
Expand All @@ -1201,7 +1190,7 @@ Skip the main menu
== 跳过主菜单

Website
== 网站
== 官网

Settings
== 设置
Expand All @@ -1222,53 +1211,95 @@ Server executable not found, can't run server
Editor
== 编辑器

Manual
== 手动

Race
== 竞速

Auto
== 自动

Replay
== 回放

Saving ddnet-settings.cfg failed
== 配置文件 ddnet-settings.cfg 保存失败

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
== 调试模式已启用。按 Ctrl+Shift+D 禁用调试模式。

Existing Player
== 玩家已存在

Your nickname '%s' is already used (%d points). Do you still want to use it?
== 你使用的昵称"%s"在DDNet中有%d分的记录,这可能代表该昵称已经被其他人使用过。确认要使用这个名字吗?

Checking for existing player with your name
== 正在查找该昵称的DDNet记录

Theme
==
== 主题

%d of %d servers
==
== 符合条件 %d / 总共 %d 个服务器

%d of %d server
==
== 符合条件 %d / 总共 %d 个服务器

%d players
==
== %d 在线玩家

%d player
==

File
==
== %d 在线玩家

Demos directory
==
== 回放目录

Smooth Dynamic Camera
== 平滑动态镜头

Themes directory
==
== 主题目录

Download skins
==
== 在线获取皮肤

Skin Database
==
== 皮肤数据库

Skins directory
==
== 皮肤目录

Game sound volume
== 游戏音效音量

Chat sound volume
== 聊天提示音量

Background music volume
==
== 背景音樂音量

Assets
==
== 材质

Use old chat style
== 旧版聊天框

Highlight
== 高亮

Use current map as background
== 使用当前地图作为实体层背景地图

Entities
==
== 实体层

Emoticons
==
== 表情

Particles
==
== 粒子

Assets directory
==
== 材质目录
46 changes: 38 additions & 8 deletions data/languages/slovak.txt
Expand Up @@ -625,6 +625,9 @@ Type:
Successfully saved the replay!
==

Saving ddnet-settings.cfg failed
==

Replay feature is disabled!
==

Expand All @@ -634,6 +637,9 @@ The width or height of texture %s is not divisible by 16, which might cause visu
Warning
==

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
==

Game paused
==

Expand Down Expand Up @@ -697,7 +703,16 @@ 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.
==

Please enter your nick name below.
Please enter your nickname below.
==

Existing Player
==

Your nickname '%s' is already used (%d points). Do you still want to use it?
==

Checking for existing player with your name
==

Country / Region
Expand Down Expand Up @@ -763,9 +778,6 @@ Please use a different name
Remove chat
==

File
==

Markers:
==

Expand Down Expand Up @@ -820,6 +832,9 @@ Activate
Save
==

Smooth Dynamic Camera
==

Switch weapon when out of ammo
==

Expand Down Expand Up @@ -973,6 +988,12 @@ Enable highlighted chat sound
Threaded sound loading
==

Game sound volume
==

Chat sound volume
==

Map sound volume
==

Expand Down Expand Up @@ -1003,6 +1024,9 @@ Show score
Show health + ammo
==

Use old chat style
==

Show names in chat in team colors
==

Expand Down Expand Up @@ -1039,6 +1063,9 @@ We will win
Friend message
==

Highlight
==

Friend
==

Expand Down Expand Up @@ -1129,6 +1156,9 @@ Background (regular)
Background (entities)
==

Use current map as background
==

Show tiles layers from BG map
==

Expand Down Expand Up @@ -1211,16 +1241,16 @@ Restart
Time
==

Manual %3d:%02d
Manual
==

Race %3d:%02d
Race
==

Auto %3d:%02d
Auto
==

Replay %3d:%02d
Replay
==

Follow
Expand Down
125 changes: 78 additions & 47 deletions data/languages/spanish.txt
Expand Up @@ -9,6 +9,7 @@
# Mortcheck 2012-11-03 22:58:43
# FeaRZ 2020-07-07 12:09:00
# Ryozuki 2020-07-09 12:06:00
# Headshot 2020-11-07 12:40:00
##### /authors #####

##### translated strings #####
Expand Down Expand Up @@ -700,7 +701,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== Se recomienda que verifiques la configuración para ajustarla a tu gusto antes de unirte a un servidor.

Please enter your nick name below.
Please enter your nickname below.
== Por favor, introduzca su apodo a continuación.

Destination file already exist
Expand Down Expand Up @@ -1120,18 +1121,6 @@ New random timeout code
Time
== Tiempo

Manual %3d:%02d
== Manual %3d:%02d

Race %3d:%02d
== Carrera %3d:%02d

Auto %3d:%02d
== Auto %3d:%02d

Replay %3d:%02d
== Repetición %3d:%02d

Follow
== Seguir

Expand Down Expand Up @@ -1171,99 +1160,141 @@ Grabs
9+ new mentions
== 9+ nuevas menciones

Saving ddnet-settings.cfg failed
== Error al guardar ddnet-settings.cfg

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
==
== El ancho o alto de la textura %s no es divisible por 16, por lo que puede causar errores visuales.

Warning
==
== Advertencia

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
== Modo debug activado. Pulsa Ctrl+Shift+D para desactivar.

Use k key to kill (restart), q to pause and watch other players. See settings for other key binds.
==
== Usa la tecla K para suicidarte (reiniciar), Q para pausar y ver a otros jugadores. Mira la configuración para otras teclas.

Existing Player
== Este jugador ya existe

Your nickname '%s' is already used (%d points). Do you still want to use it?
== Tu nombre '%s' ya está usado (%d puntos). ¿Deseas seguir usándolo?

Checking for existing player with your name
== Comprobando si ya existe un jugador con tu nombre

Country / Region
==
== País / Región

Speed
==
== Velocidad

Theme
==
== Tema

%d of %d servers
==
== %d de %d servidores

%d of %d server
==
== %d de %d servidor

%d players
==
== %d jugadores

%d player
==

File
==
== %d jugador

Demos directory
==
== Directorio de demos

Smooth Dynamic Camera
== Cámara dinámica suave

Skip the main menu
==
== Saltar el 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úmen de sonido del juego

Chat sound volume
== Volúmen del chat

Background music volume
==
== Volúmen de música de fondo

Assets
==
== Recursos

Use old chat style
== Usar estilo viejo de chat

Highlight
== Resaltado

Client message
==
== Mensaje de 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.tw/
==
== https://wiki.ddnet.tw/

Website
==
== Página web

Settings
==
== Configuración

Stop server
==
== Detener servidor

Run server
==
== Iniciar servidor

Server executable not found, can't run server
==
== Ejecutable del servidor no encontrado. No se puede iniciar el servidor

Editor
==
== Editor

[Start menu]
Play
==
== Jugar

Manual
== Manual

Race
== Carrera

Auto
== Auto

Replay
== Repetición
60 changes: 45 additions & 15 deletions data/languages/swedish.txt
Expand Up @@ -769,7 +769,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== Det är rekomenderad att du kollar igenom inställningarna och ändra dom så de passar dig innan du ansluter dig till en server.

Please enter your nick name below.
Please enter your nickname below.
== Skriv ditt smeknamn nedanför.

Render
Expand Down Expand Up @@ -883,9 +883,6 @@ Show text entities
Show ghost
== Visa spöke

Replay %3d:%02d
== Repris %3d:%02d

1 new mention
== 1 ny nämning

Expand Down Expand Up @@ -937,9 +934,6 @@ Follow
Restart
== Starta om

Manual %3d:%02d
== Manual %3d:%02d

Background (regular)
== Bakgrund (vanlig)

Expand Down Expand Up @@ -1108,9 +1102,6 @@ Gameplay
Loading DDNet Client
== Laddar DDNet Klienten

Auto %3d:%02d
== Auto %3d:%02d

Show HUD
== Visa HUD

Expand All @@ -1126,9 +1117,6 @@ Spree
Show others
== Visa andra

Race %3d:%02d
== Race %3d:%02d

Unfinished map
== Oavslutat bana

Expand Down Expand Up @@ -1229,15 +1217,42 @@ Editor
Play
== Spela

Theme
Manual
== Manual

Race
== Race

Auto
== Auto

Replay
== Repris

Saving ddnet-settings.cfg failed
==

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
==

Existing Player
==

Your nickname '%s' is already used (%d points). Do you still want to use it?
==

File
Checking for existing player with your name
==

Theme
==

Demos directory
==

Smooth Dynamic Camera
==

Themes directory
==

Expand All @@ -1247,12 +1262,27 @@ Skin Database
Skins directory
==

Game sound volume
==

Chat sound volume
==

Background music volume
==

Assets
==

Use old chat style
==

Highlight
==

Use current map as background
==

Entities
==

Expand Down
155 changes: 93 additions & 62 deletions data/languages/traditional_chinese.txt
Expand Up @@ -4,6 +4,7 @@
#modified by:
# 2020.8.19 TsFreddie
# 2020.8.20 Dan_cao
# 2020.11.12 TsFreddie
##### /authors #####

##### translated strings #####
Expand Down Expand Up @@ -207,7 +208,7 @@ General
== 常規

Graphics
== 影象
== 顯示

Grenade
== 榴彈槍
Expand Down Expand Up @@ -793,7 +794,7 @@ Background (entities)
== 背景(實體)

Show tiles layers from BG map
== 在背景地圖顯示圖塊層
== 顯示實體層背景地圖的圖塊層

Try fast HTTP map download first
== 優先嚐試用HTTP高速下載地圖
Expand Down Expand Up @@ -1122,7 +1123,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== 推薦在進入伺服器前,先將設定調整到符合你的習慣。

Please enter your nick name below.
Please enter your nickname below.
== 請輸入你的暱稱。

Render
Expand All @@ -1132,7 +1133,7 @@ Server best:
== 伺服器最佳

Learn
== 學習
== 教程

Use high DPI
== 使用高 DPI
Expand Down Expand Up @@ -1167,102 +1168,132 @@ Use k key to kill (restart), q to pause and watch other players. See settings fo
Country / Region
== 國家/地區

Manual %3d:%02d
== 手動 %3d:%02d

Race %3d:%02d
== 競速賽 %3d:%02d

Auto %3d:%02d
== 自動 %3d:%02d

Replay %3d:%02d
== 回放 %3d:%02d

https://wiki.ddnet.tw/
== https://docs.qq.com/doc/DWGFrV0xPRmVWVkla

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
==
== 材質 %s 的寬和高無法被16整除,這可能會導致畫面異常。

Warning
==
== 警告

Speed
==
== 速度

Skip the main menu
== 跳過主選單

Website
== 網站

Settings
== 設定

[Start menu]
Play
== 開始遊戲

Stop server
== 停止伺服器

Run server
== 開啟伺服器

Server executable not found, can't run server
== 找不到伺服器的可執行檔案,無法開啟

Editor
== 編輯器

Manual
== 手動

Race
== 競速

Auto
== 自動

Replay
== 回放

Saving ddnet-settings.cfg failed
== 配置檔案 ddnet-settings.cfg 儲存失敗

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
== 偵錯模式已啟用。按 Ctrl+Shift+D 禁用偵錯模式。

Existing Player
== 玩家已存在

Your nickname '%s' is already used (%d points). Do you still want to use it?
== 你使用的暱稱"%s"在DDNet中有%d分的記錄,這可能代表這個暱稱已經被其他人使用過。確認要使用這個名字嗎?

Checking for existing player with your name
== 正在查詢該暱稱的DDNet記錄

Theme
==
== 主題

%d of %d servers
==
== 符合條件 %d / 總共 %d 個伺服器

%d of %d server
==
== 符合條件 %d / 總共 %d 個伺服器

%d players
==
== %d 在線玩家

%d player
==

File
==
== %d 在線玩家

Demos directory
==
== 回放目錄

Skip the main menu
==
Smooth Dynamic Camera
== 動態視距平滑化

Themes directory
==
== 主題目錄

Download skins
==
== 在線獲取外觀

Skin Database
==
== 外觀數據庫

Skins directory
==
== 外觀目錄

Game sound volume
== 遊戲音效音量

Chat sound volume
== 聊天提示音量

Background music volume
==
== 背景音樂音量

Assets
==
== 材質

Use old chat style
== 舊版聊天框

Highlight
== 高亮

Use current map as background
== 使用當前地圖作爲實體層背景地圖

Entities
==
== 實體層

Emoticons
==
== 表情

Particles
==
== 粒子

Assets directory
==

Website
==

Settings
==

Stop server
==

Run server
==

Server executable not found, can't run server
==

Editor
==

[Start menu]
Play
==
== 材質目錄
62 changes: 46 additions & 16 deletions data/languages/turkish.txt
Expand Up @@ -127,7 +127,7 @@ Demo details
== Demo ayrıntıları

Demofile: %s
== Demo dosyası
== Demo dosyası: %s

Demos
== Demolar
Expand Down Expand Up @@ -696,7 +696,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.
== Sunuculara bağlanmadan önce ayarları kontrol edip kendine göre düzenlemeni öneriyoruz.

Please enter your nick name below.
Please enter your nickname below.
== Lütfen takma adı girin.

Destination file already exist
Expand Down Expand Up @@ -1125,18 +1125,6 @@ New random timeout code
Time
== Süre

Manual %3d:%02d
== Manual %3d:%02d

Race %3d:%02d
== Yarış %3d:%02d

Auto %3d:%02d
== Otomatik %3d:%02d

Replay %3d:%02d
== Yeniden oynatma %3d:%02d

Follow
== Takip et

Expand Down Expand Up @@ -1176,12 +1164,39 @@ Grabs
9+ new mentions
== 9+ yeni bildirim

Manual
== Manual

Race
== Yarış

Auto
== Otomatik

Replay
== Yeniden oynatma

Saving ddnet-settings.cfg failed
==

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
==

Warning
==

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
==

Existing Player
==

Your nickname '%s' is already used (%d points). Do you still want to use it?
==

Checking for existing player with your name
==

Country / Region
==

Expand All @@ -1203,10 +1218,10 @@ Theme
%d player
==

File
Demos directory
==

Demos directory
Smooth Dynamic Camera
==

Skip the main menu
Expand All @@ -1224,12 +1239,27 @@ Skin Database
Skins directory
==

Game sound volume
==

Chat sound volume
==

Background music volume
==

Assets
==

Use old chat style
==

Highlight
==

Use current map as background
==

Entities
==

Expand Down
62 changes: 46 additions & 16 deletions data/languages/ukrainian.txt
Expand Up @@ -538,7 +538,7 @@ Most importantly communication is key: There is no tutorial so you'll have to ch
It's recommended that you check the settings to adjust them to your liking before joining a server.
== Рекомендується перевірити налаштування, щоб підлаштувати їх до душі перед тим, як приєднатися до сервера.

Please enter your nick name below.
Please enter your nickname below.
== Будь ласка, введіть свій нікнейм нижче

There's an unsaved map in the editor, you might want to save it before you quit the game.
Expand Down Expand Up @@ -1111,18 +1111,6 @@ New random timeout code
Time
== Час

Manual %3d:%02d
== Ручна %3d:%02d

Race %3d:%02d
== Гонка %3d:%02d

Auto %3d:%02d
== Авто %3d:%02d

Replay %3d:%02d
== Повтор %3d:%02d

%s wins!
== %s переміг!

Expand Down Expand Up @@ -1165,15 +1153,42 @@ Grabs
9+ new mentions
== 9+ нових згадок

Manual
== Ручна

Race
== Гонка

Auto
== Авто

Replay
== Повтор

Saving ddnet-settings.cfg failed
==

The width or height of texture %s is not divisible by 16, which might cause visual bugs.
==

Warning
==

Debug mode enabled. Press Ctrl+Shift+D to disable debug mode.
==

Use k key to kill (restart), q to pause and watch other players. See settings for other key binds.
==

Existing Player
==

Your nickname '%s' is already used (%d points). Do you still want to use it?
==

Checking for existing player with your name
==

Country / Region
==

Expand All @@ -1195,15 +1210,15 @@ Theme
%d player
==

File
==

Markers
==

Demos directory
==

Smooth Dynamic Camera
==

Skip the main menu
==

Expand All @@ -1219,12 +1234,27 @@ Skin Database
Skins directory
==

Game sound volume
==

Chat sound volume
==

Background music volume
==

Assets
==

Use old chat style
==

Highlight
==

Use current map as background
==

Entities
==

Expand Down
Binary file modified data/mapres/ddmax_freeze.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions data/shader/primex.frag
@@ -0,0 +1,19 @@
#ifdef TW_TEXTURED
uniform sampler2D gTextureSampler;
#endif

uniform vec4 gVerticesColor;

noperspective in vec2 texCoord;
noperspective in vec4 vertColor;

out vec4 FragClr;
void main()
{
#ifdef TW_TEXTURED
vec4 tex = texture(gTextureSampler, texCoord);
FragClr = tex * vertColor * gVerticesColor;
#else
FragClr = vertColor * gVerticesColor;
#endif
}
28 changes: 28 additions & 0 deletions data/shader/primex.vert
@@ -0,0 +1,28 @@
layout (location = 0) in vec2 inVertex;
layout (location = 1) in vec2 inVertexTexCoord;
layout (location = 2) in vec4 inVertexColor;

uniform mat4x2 gPos;

uniform float gRotation;
uniform vec2 gCenter;

noperspective out vec2 texCoord;
noperspective out vec4 vertColor;

void main()
{
vec2 FinalPos = vec2(inVertex.xy);
if(gRotation != 0.0)
{
float X = FinalPos.x - gCenter.x;
float Y = FinalPos.y - gCenter.y;

FinalPos.x = X * cos(gRotation) - Y * sin(gRotation) + gCenter.x;
FinalPos.y = X * sin(gRotation) + Y * cos(gRotation) + gCenter.y;
}

gl_Position = vec4(gPos * vec4(FinalPos, 0.0, 1.0), 0.0, 1.0);
texCoord = inVertexTexCoord;
vertColor = inVertexColor;
}
4 changes: 3 additions & 1 deletion data/shader/quad.vert
Expand Up @@ -9,6 +9,8 @@ uniform mat4x2 gPos;
uniform vec2 gOffsets[TW_MAX_QUADS];
uniform float gRotations[TW_MAX_QUADS];

uniform int gQuadOffset;

noperspective out vec4 QuadColor;
flat out int QuadIndex;
#ifdef TW_QUAD_TEXTURED
Expand All @@ -19,7 +21,7 @@ void main()
{
vec2 FinalPos = vec2(inVertex.xy);

int TmpQuadIndex = int(gl_VertexID / 4);
int TmpQuadIndex = int(gl_VertexID / 4) - gQuadOffset;

if(gRotations[TmpQuadIndex] != 0.0)
{
Expand Down
13 changes: 0 additions & 13 deletions data/shader/sprite.frag

This file was deleted.

28 changes: 0 additions & 28 deletions data/shader/sprite.vert

This file was deleted.

Binary file modified data/skins/PaladiN.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed data/skins/Terrorist.png
Binary file not shown.
Binary file modified data/themes/autumn_day.map
Binary file not shown.
Binary file added data/themes/newyear.map
Binary file not shown.
Binary file added data/themes/newyear.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 15 additions & 8 deletions datasrc/content.py
Expand Up @@ -320,16 +320,16 @@ def FileList(format, num):
container.sprites.Add(Sprite("weapon_gun_body", set_game, 2,4,4,2))
container.sprites.Add(Sprite("weapon_gun_cursor", set_game, 0,4,2,2))
container.sprites.Add(Sprite("weapon_gun_proj", set_game, 6,4,2,2))
container.sprites.Add(Sprite("weapon_gun_muzzle1", set_game, 8,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle2", set_game, 12,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle3", set_game, 16,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle1", set_game, 8,4,4,2))
container.sprites.Add(Sprite("weapon_gun_muzzle2", set_game, 12,4,4,2))
container.sprites.Add(Sprite("weapon_gun_muzzle3", set_game, 16,4,4,2))

container.sprites.Add(Sprite("weapon_shotgun_body", set_game, 2,6,8,2))
container.sprites.Add(Sprite("weapon_shotgun_cursor", set_game, 0,6,2,2))
container.sprites.Add(Sprite("weapon_shotgun_proj", set_game, 10,6,2,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle1", set_game, 12,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle2", set_game, 16,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle3", set_game, 20,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle1", set_game, 12,6,4,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle2", set_game, 16,6,4,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle3", set_game, 20,6,4,2))

container.sprites.Add(Sprite("weapon_grenade_body", set_game, 2,8,7,2))
container.sprites.Add(Sprite("weapon_grenade_cursor", set_game, 0,8,2,2))
Expand Down Expand Up @@ -498,7 +498,14 @@ def FileList(format, num):
weapon.visual_size.Set(64)
weapon.offsetx.Set(32)
weapon.offsety.Set(4)
weapon.muzzleoffsetx.Set(50)
# the number after the plus sign is the sprite scale, which is calculated for all sprites ( w / sqrt(w² * h²) ) of the additionally added x offset, which is added now,
# since the muzzle image is 32 pixels bigger, devided by 2, because a sprite's position is always at the center of the sprite image itself
# => the offset added, bcs the sprite is bigger now, but should not be shifted to the left
# => 96 / sqrt(64×64+96×96) (the original sprite scale)
# => 64 × original sprite scale (the actual size of the sprite ingame see weapon.visual_size above)
# => (actual image sprite) / 3 (the new sprite is 128 instead of 96, so 4 / 3 times as big(bcs it should look the same as before, not scaled down because of this bigger number), so basically, 1 / 3 of the original size is added)
# => (new sprite width) / 2 (bcs the sprite is at center, only add half of that new extra width)
weapon.muzzleoffsetx.Set(50 + 8.8752)
weapon.muzzleoffsety.Set(6)
container.weapons.gun.base.Set(weapon)
container.weapons.id.Add(weapon)
Expand All @@ -508,7 +515,7 @@ def FileList(format, num):
weapon.visual_size.Set(96)
weapon.offsetx.Set(24)
weapon.offsety.Set(-2)
weapon.muzzleoffsetx.Set(70)
weapon.muzzleoffsetx.Set(70 + 13.3128) # see gun for the number after the plus sign
weapon.muzzleoffsety.Set(6)
container.weapons.shotgun.base.Set(weapon)
container.weapons.id.Add(weapon)
Expand Down
14 changes: 9 additions & 5 deletions datasrc/seven/compile.py
Expand Up @@ -55,18 +55,19 @@ def EmitFlags(names, num):
if "server_content_source" in sys.argv: gen_server_content_source = True

if gen_client_content_header:
print("#ifndef CLIENT_CONTENT_HEADER")
print("#define CLIENT_CONTENT_HEADER")
print("#ifndef CLIENT_CONTENT7_HEADER")
print("#define CLIENT_CONTENT7_HEADER")

if gen_server_content_header:
print("#ifndef SERVER_CONTENT_HEADER")
print("#define SERVER_CONTENT_HEADER")
print("#ifndef SERVER_CONTENT7_HEADER")
print("#define SERVER_CONTENT7_HEADER")


if gen_client_content_header or gen_server_content_header:
# print some includes
print('#include <engine/graphics.h>')
print('#include <engine/sound.h>')
print("namespace client_data7 {")

# emit the type declarations
contentlines = open("datasrc/content.py", "rb").readlines()
Expand All @@ -88,11 +89,13 @@ def EmitFlags(names, num):

if gen_client_content_source or gen_server_content_source:
if gen_client_content_source:
print('#include "client_data.h"')
print('#include "client_data7.h"')
if gen_server_content_source:
print('#include "server_data.h"')
print("namespace client_data7 {")
EmitDefinition(content.container, "datacontainer")
print('CDataContainer *g_pData = &datacontainer;')
print("}")

# NETWORK
if gen_network_header:
Expand Down Expand Up @@ -356,4 +359,5 @@ class CNetObjHandler
print(l)

if gen_client_content_header or gen_server_content_header:
print("}")
print("#endif")
18 changes: 9 additions & 9 deletions datasrc/seven/content.py
Expand Up @@ -173,7 +173,7 @@ def __init__(self):
self.sprites = Array(Sprite())
self.animations = Array(Animation())
self.weapons = Weapons()
self.explosion = Explosion()
#self.explosion = Explosion()

def FileList(format, num):
return [format%(x+1) for x in range(0,num)]
Expand Down Expand Up @@ -373,16 +373,16 @@ def FileList(format, num):
container.sprites.Add(Sprite("weapon_gun_body", set_game, 2,4,4,2))
container.sprites.Add(Sprite("weapon_gun_cursor", set_game, 0,4,2,2))
container.sprites.Add(Sprite("weapon_gun_proj", set_game, 6,4,2,2))
container.sprites.Add(Sprite("weapon_gun_muzzle1", set_game, 8,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle2", set_game, 12,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle3", set_game, 16,4,3,2))
container.sprites.Add(Sprite("weapon_gun_muzzle1", set_game, 8,4,4,2))
container.sprites.Add(Sprite("weapon_gun_muzzle2", set_game, 12,4,4,2))
container.sprites.Add(Sprite("weapon_gun_muzzle3", set_game, 16,4,4,2))

container.sprites.Add(Sprite("weapon_shotgun_body", set_game, 2,6,8,2))
container.sprites.Add(Sprite("weapon_shotgun_cursor", set_game, 0,6,2,2))
container.sprites.Add(Sprite("weapon_shotgun_proj", set_game, 10,6,2,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle1", set_game, 12,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle2", set_game, 16,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle3", set_game, 20,6,3,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle1", set_game, 12,6,4,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle2", set_game, 16,6,4,2))
container.sprites.Add(Sprite("weapon_shotgun_muzzle3", set_game, 20,6,4,2))

container.sprites.Add(Sprite("weapon_grenade_body", set_game, 2,8,7,2))
container.sprites.Add(Sprite("weapon_grenade_cursor", set_game, 0,8,2,2))
Expand Down Expand Up @@ -633,7 +633,7 @@ def FileList(format, num):
weapon.visual_size.Set(64)
weapon.offsetx.Set(32)
weapon.offsety.Set(-4)
weapon.muzzleoffsetx.Set(50)
weapon.muzzleoffsetx.Set(50 + 8.8752) # see gun in 0.6 content.py for the number after the plus sign (TODO: also copy the comment from 0.6 content.py, if it gets removed)
weapon.muzzleoffsety.Set(6)
container.weapons.gun.base.Set(weapon)
container.weapons.id.Add(weapon)
Expand All @@ -644,7 +644,7 @@ def FileList(format, num):
weapon.visual_size.Set(96)
weapon.offsetx.Set(24)
weapon.offsety.Set(-2)
weapon.muzzleoffsetx.Set(70)
weapon.muzzleoffsetx.Set(70 + 13.3128) # see gun in 0.6 content.py for the number after the plus sign (TODO: also copy the comment from 0.6 content.py, if it gets removed)
weapon.muzzleoffsety.Set(6)
container.weapons.shotgun.base.Set(weapon)
container.weapons.id.Add(weapon)
Expand Down
2 changes: 1 addition & 1 deletion license.txt
Expand Up @@ -20,7 +20,7 @@ freely, subject to the following restrictions:

------------------------------------------------------------------------

All content under 'data' except the font, language & skin files,
All content under 'data' except the assets, font, language & skin files,
(which have their own licenses):
Apache 2.0 for the 'Icon.tff' file:
Copyright Google
Expand Down
10 changes: 10 additions & 0 deletions other/ddnet.appdata.xml
Expand Up @@ -37,6 +37,16 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release date="2020-12-29" version="15.2.5"/>
<release date="2020-12-16" version="15.2.4"/>
<release date="2020-11-19" version="15.2.3"/>
<release date="2020-11-17" version="15.2.2"/>
<release date="2020-11-17" version="15.2.1"/>
<release date="2020-11-17" version="15.2"/>
<release date="2020-10-15" version="15.1.3"/>
<release date="2020-10-12" version="15.1.2"/>
<release date="2020-10-11" version="15.1.1"/>
<release date="2020-10-11" version="15.1"/>
<release date="2020-09-23" version="15.0.5"/>
<release date="2020-09-20" version="15.0.4"/>
<release date="2020-09-18" version="15.0.3"/>
Expand Down
101 changes: 53 additions & 48 deletions scripts/languages/copy_fix.py
Expand Up @@ -3,55 +3,60 @@
import os
import sys

os.chdir(os.path.dirname(__file__) + "/../..")
def copy_fix(infile, delete_unused, append_missing, delete_empty):
content = open(infile).readlines()
trans = twlang.translations(infile)
if delete_unused or append_missing:
local = twlang.localizes()
if append_missing:
supported = []
for tran, (start, expr, end) in trans.items():
if delete_unused and tran not in local:
content[start:end] = [None]*(end-start)
if append_missing and tran in local:
if expr or (not expr and delete_empty):
supported.append(local.index(tran))
else:
content[start:end] = [None]*(end-start)
if delete_empty and not expr:
content[start:end] = [None]*(end-start)
content = [line for line in content if line != None]
if append_missing:
missing = [index for index in range(len(local)) if index not in supported]
if missing:
if content[-1] != "\n":
content.append("\n")
for i, miss in enumerate(missing):
if local[miss][1] != "":
content.append("["+local[miss][1]+"]\n")
content.append(local[miss][0]+"\n== \n\n")
content[-1] = content[-1][:-1]
return "".join(content)

if len(sys.argv) < 3:
print("usage: python copy_fix.py <infile> <outfile> [--delete-unused] [--append-missing] [--delete-empty]")
sys.exit()
infile = sys.argv[1]
outfile = sys.argv[2]
args = sys.argv[3:]
delete_unused = False
append_missing = False
delete_empty = False
for arg in args:
if arg == "--delete-unused":
delete_unused = True
elif arg == "--append-missing":
append_missing = True
elif arg == "--delete-empty":
delete_empty = True
else:
print("No such argument '"+arg+"'.")
sys.exit()
if __name__ == '__main__':
os.chdir(os.path.dirname(__file__) + "/../..")

content = open(infile).readlines()
trans = twlang.translations(infile)
if delete_unused or append_missing:
local = twlang.localizes()
if append_missing:
supported = []
for tran, (start, expr, end) in trans.items():
if delete_unused and tran not in local:
content[start:end] = [None]*(end-start)
if append_missing and tran in local:
if expr or (not expr and delete_empty):
supported.append(local.index(tran))
if len(sys.argv) < 3:
print("usage: python copy_fix.py <infile> <outfile> [--delete-unused] [--append-missing] [--delete-empty]")
sys.exit()
infile = sys.argv[1]
outfile = sys.argv[2]
args = sys.argv[3:]
delete_unused = False
append_missing = False
delete_empty = False
for arg in args:
if arg == "--delete-unused":
delete_unused = True
elif arg == "--append-missing":
append_missing = True
elif arg == "--delete-empty":
delete_empty = True
else:
content[start:end] = [None]*(end-start)
if delete_empty and not expr:
content[start:end] = [None]*(end-start)
content = [line for line in content if line != None]
if append_missing:
missing = [index for index in range(len(local)) if index not in supported]
if missing:
if content[-1] != "\n":
content.append("\n")
for i, miss in enumerate(missing):
if local[miss][1] != "":
content.append("["+local[miss][1]+"]\n")
content.append(local[miss][0]+"\n== \n\n")
content[-1] = content[-1][:-1]
print("No such argument '"+arg+"'.")
sys.exit()

content = copy_fix(infile, delete_unused, append_missing, delete_empty)

open(outfile, "w").write("".join(content))
print("Successfully created '"+outfile+"'.")
open(outfile, "w").write("".join(content))
print("Successfully created '" + outfile + "'.")
2 changes: 1 addition & 1 deletion scripts/languages/readme.txt
Expand Up @@ -38,7 +38,7 @@ $ ./find_unchanged.py ../spanish.txt

To update all languages:

$ for i in data/languages/*.txt; do [ "${i:t}" != "license.txt" ] && [ "${i:t}" != "index.txt" ] && scripts/languages/copy_fix.py $i $i.$$.tmp --delete-unused --append-missing && mv $i.$$.tmp $i; done
$ ./update_all.py

To get a statistic of how complete the translation is:

Expand Down
23 changes: 21 additions & 2 deletions scripts/languages/twlang.py
Expand Up @@ -8,6 +8,21 @@ def __init__(self, message, filename, line):
super(LanguageDecodeError, self).__init__(error)


# Taken from https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
cfmt = '''\
( # start of capture group 1
% # literal "%"
(?: # first option
(?:[-+0 #]{0,5}) # optional flags
(?:\d+|\*)? # width
(?:\.(?:\d+|\*))? # precision
(?:h|l|ll|w|I|I32|I64)? # size
[cCdiouxXeEfgGaAnpsSZ] # type
) | # OR
%%) # literal "%%"
'''


def decode(fileobj, elements_per_key):
data = {}
current_context = ""
Expand All @@ -30,7 +45,11 @@ def decode(fileobj, elements_per_key):
if len(data[current_key]) >= 1+elements_per_key:
raise LanguageDecodeError("Wrong number of elements per key", fileobj.name, index)
if current_key:
data[current_key].extend([line[3:]])
original = current_key[0]
translation = line[3:]
if translation and [m.group(1) for m in re.finditer(cfmt, original, flags=re.X)] != [m.group(1) for m in re.finditer(cfmt, translation, flags=re.X)]:
raise LanguageDecodeError("Non-matching formatting string", fileobj.name, index)
data[current_key].extend([translation])
else:
raise LanguageDecodeError("Element before key given", fileobj.name, index)
else:
Expand Down Expand Up @@ -68,7 +87,7 @@ def check_folder(path):

def languages():
index = decode(open("data/languages/index.txt"), 2)
langs = {"data/languages/"+key+".txt" : [key]+elements for key, elements in index.items()}
langs = {"data/languages/"+key[0]+".txt" : [key[0]]+elements for key, elements in index.items()}
return langs


Expand Down
10 changes: 10 additions & 0 deletions scripts/languages/update_all.py
@@ -0,0 +1,10 @@
#!/usr/bin/env python3
import os
from copy_fix import copy_fix
import twlang

os.chdir(os.path.dirname(__file__) + "/../..")

for lang in twlang.languages():
content = copy_fix(lang, delete_unused=True, append_missing=True, delete_empty=False)
open(lang, "w").write(content)
9 changes: 9 additions & 0 deletions scripts/wordlist.py
@@ -0,0 +1,9 @@
print("#ifndef GENERATED_WORDLIST_H")
print("#define GENERATED_WORDLIST_H")
print("const char g_aFallbackWordlist[][32] = {")
with open("data/wordlist.txt") as f:
for line in f:
word = line.strip().split("\t")[1]
print("\t\"%s\", " % word)
print("};")
print("#endif // GENERATED_WORDLIST_H")
77 changes: 64 additions & 13 deletions src/base/system.c
Expand Up @@ -803,6 +803,7 @@ LOCK lock_create(void)
if(result != 0)
{
dbg_msg("lock", "init failed: %d", result);
free(lock);
return 0;
}
#elif defined(CONF_FAMILY_WINDOWS)
Expand Down Expand Up @@ -1029,7 +1030,9 @@ static void netaddr_to_sockaddr_in6(const NETADDR *src, struct sockaddr_in6 *des

static void sockaddr_to_netaddr(const struct sockaddr *src, NETADDR *dst)
{
if(src->sa_family == AF_INET)
// Filled by accept, clang-analyzer probably can't tell because of the
// (struct sockaddr *) cast.
if(src->sa_family == AF_INET) // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult)
{
mem_zero(dst, sizeof(NETADDR));
dst->type = NETTYPE_IPV4;
Expand Down Expand Up @@ -1140,7 +1143,7 @@ int net_host_lookup(const char *hostname, NETADDR *addr, int types)
if(priv_net_extract(hostname, host, sizeof(host), &port))
return -1;

dbg_msg("host lookup", "host='%s' port=%d %d", host, port, types);
dbg_msg("host_lookup", "host='%s' port=%d %d", host, port, types);

mem_zero(&hints, sizeof(hints));

Expand Down Expand Up @@ -1925,7 +1928,7 @@ int net_unix_send(UNIXSOCKET sock, UNIXSOCKETADDR *addr, void *data, int size)

void net_unix_set_addr(UNIXSOCKETADDR *addr, const char *path)
{
mem_zero(addr, sizeof(addr));
mem_zero(addr, sizeof(*addr));
addr->sun_family = AF_UNIX;
str_copy(addr->sun_path, path, sizeof(addr->sun_path));
}
Expand Down Expand Up @@ -2061,7 +2064,7 @@ int fs_storage_path(const char *appname, char *path, int max)
#else
snprintf(path, max, "%s/.%s", home, appname);
for(i = strlen(home) + 2; path[i]; i++)
path[i] = tolower(path[i]);
path[i] = tolower((unsigned char)path[i]);
#endif

return 0;
Expand Down Expand Up @@ -2299,6 +2302,11 @@ int time_season(void)
time(&time_data);
time_info = localtime(&time_data);

if((time_info->tm_mon == 11 && time_info->tm_mday == 31) || (time_info->tm_mon == 0 && time_info->tm_mday == 1))
{
return SEASON_NEWYEAR;
}

switch(time_info->tm_mon)
{
case 11:
Expand Down Expand Up @@ -2690,13 +2698,8 @@ int str_utf8_dist_buffer(const char *a_utf8, const char *b_utf8, int *buf, int b
dbg_assert(buf_len >= 2 * (a_utf8_len + 1 + b_utf8_len + 1), "buffer too small");
if(a_utf8_len > b_utf8_len)
{
int tmp1 = a_utf8_len;
const char *tmp2 = a_utf8;

a_utf8_len = b_utf8_len;
a_utf8 = b_utf8;

b_utf8_len = tmp1;
b_utf8 = tmp2;
}
a = buf;
Expand All @@ -2712,7 +2715,7 @@ const char *str_find_nocase(const char *haystack, const char *needle)
{
const char *a = haystack;
const char *b = needle;
while(*a && *b && tolower(*a) == tolower(*b))
while(*a && *b && tolower((unsigned char)*a) == tolower((unsigned char)*b))
{
a++;
b++;
Expand Down Expand Up @@ -2850,6 +2853,54 @@ void str_timestamp(char *buffer, int buffer_size)
#pragma GCC diagnostic pop
#endif

int str_time(int64 centisecs, int format, char *buffer, int buffer_size)
{
const int sec = 100;
const int min = 60 * sec;
const int hour = 60 * min;
const int day = 24 * hour;

if(buffer_size <= 0)
return -1;

if(centisecs < 0)
centisecs = 0;

buffer[0] = 0;

switch(format)
{
case TIME_DAYS:
if(centisecs >= day)
return str_format(buffer, buffer_size, "%lldd %02lld:%02lld:%02lld", centisecs / day,
(centisecs % day) / hour, (centisecs % hour) / min, (centisecs % min) / sec);
// fall through
case TIME_HOURS:
if(centisecs >= hour)
return str_format(buffer, buffer_size, "%02lld:%02lld:%02lld", centisecs / hour,
(centisecs % hour) / min, (centisecs % min) / sec);
// fall through
case TIME_MINS:
return str_format(buffer, buffer_size, "%02lld:%02lld", centisecs / min,
(centisecs % min) / sec);
case TIME_HOURS_CENTISECS:
if(centisecs >= hour)
return str_format(buffer, buffer_size, "%02lld:%02lld:%02lld.%02lld", centisecs / hour,
(centisecs % hour) / min, (centisecs % min) / sec, centisecs % sec);
// fall through
case TIME_MINS_CENTISECS:
return str_format(buffer, buffer_size, "%02lld:%02lld.%02lld", centisecs / min,
(centisecs % min) / sec, centisecs % sec);
}

return -1;
}

int str_time_float(float secs, int format, char *buffer, int buffer_size)
{
return str_time((int64)(secs * 100.0), format, buffer, buffer_size);
}

void str_escape(char **dst, const char *src, const char *end)
{
while(*src && *dst + 1 < end)
Expand Down Expand Up @@ -2965,11 +3016,11 @@ const char *str_utf8_find_nocase(const char *haystack, const char *needle)
int str_utf8_isspace(int code)
{
return code <= 0x0020 || code == 0x0085 || code == 0x00A0 ||
code == 0x034F || code == 0x1680 || code == 0x180E ||
code == 0x034F || code == 0x1160 || code == 0x1680 || code == 0x180E ||
(code >= 0x2000 && code <= 0x200F) || (code >= 0x2028 && code <= 0x202F) ||
(code >= 0x205F && code <= 0x2064) || (code >= 0x206A && code <= 0x206F) ||
code == 0x2800 || code == 0x3000 ||
(code >= 0xFE00 && code <= 0xFE0F) || code == 0xFEFF ||
code == 0x2800 || code == 0x3000 || code == 0x3164 ||
(code >= 0xFE00 && code <= 0xFE0F) || code == 0xFEFF || code == 0xFFA0 ||
(code >= 0xFFF9 && code <= 0xFFFC);
}

Expand Down
32 changes: 31 additions & 1 deletion src/base/system.h
Expand Up @@ -629,7 +629,8 @@ enum
SEASON_SPRING = 0,
SEASON_SUMMER,
SEASON_AUTUMN,
SEASON_WINTER
SEASON_WINTER,
SEASON_NEWYEAR
};

/*
Expand Down Expand Up @@ -1500,6 +1501,35 @@ void str_timestamp_ex(time_t time, char *buffer, int buffer_size, const char *fo
#define FORMAT_SPACE "%Y-%m-%d %H:%M:%S"
#define FORMAT_NOSPACE "%Y-%m-%d_%H-%M-%S"

enum
{
TIME_DAYS,
TIME_HOURS,
TIME_MINS,
TIME_HOURS_CENTISECS,
TIME_MINS_CENTISECS,
};

/*
Function: str_times
Formats a time string.
Parameters:
centisecs - Time in centiseconds, minimum value clamped to 0
format - Format of the time string, see enum above, for example TIME_DAYS
buffer - Pointer to a buffer that shall receive the time stamp string.
buffer_size - Size of the buffer.
Returns:
Number of bytes written, -1 on invalid format or buffer_size <= 0
Remarks:
- Guarantees that buffer string will contain zero-termination, assuming
buffer_size > 0.
*/
int str_time(int64 centisecs, int format, char *buffer, int buffer_size);
int str_time_float(float secs, int format, char *buffer, int buffer_size);

/*
Function: str_escape
Escapes \ and " characters in a string.
Expand Down
5 changes: 3 additions & 2 deletions src/base/tl/algorithm.h
Expand Up @@ -4,6 +4,7 @@
#define BASE_TL_ALGORITHM_H

#include "base/tl/range.h"
#include <algorithm>
#include <functional>

/*
Expand Down Expand Up @@ -67,7 +68,7 @@ R find_linear(R range, T value)
template<class R, class T>
R find_binary(R range, T value)
{
range = partition_linear(range, value);
range = partition_binary(range, value);
if(range.empty())
return range;
if(range.front() == value)
Expand Down Expand Up @@ -108,7 +109,7 @@ void sort_quick(R range)
template<class R>
void sort(R range)
{
sort_bubble(range);
std::sort(&range.front(), &range.back() + 1);
}

template<class R>
Expand Down
3 changes: 2 additions & 1 deletion src/base/tl/array.h
Expand Up @@ -3,6 +3,7 @@
#ifndef BASE_TL_ARRAY_H
#define BASE_TL_ARRAY_H

#include "base/math.h"
#include "base/tl/allocator.h"
#include "base/tl/range.h"

Expand Down Expand Up @@ -318,7 +319,7 @@ class array : private ALLOCATOR

void alloc(int new_len)
{
list_size = new_len;
list_size = maximum(1, new_len);
T *new_list = ALLOCATOR::alloc_array(list_size);

int end = num_elements < list_size ? num_elements : list_size;
Expand Down
1 change: 1 addition & 0 deletions src/base/tl/sorted_array.h
Expand Up @@ -46,6 +46,7 @@ class sorted_array : public array<T, ALLOCATOR>
Returns a sorted range that contains the whole array.
*/
range all() { return range(parent::list, parent::list + parent::num_elements); }
range all() const { return range(parent::list, parent::list + parent::num_elements); }
};

#endif // TL_FILE_SORTED_ARRAY_HPP
24 changes: 16 additions & 8 deletions src/base/vmath.h
Expand Up @@ -107,23 +107,31 @@ inline vector2_base<T> normalize(const vector2_base<T> &v)
return vector2_base<T>(v.x * l, v.y * l);
}

template<typename T>
inline vector2_base<T> normalize_pre_length(const vector2_base<T> &v, T len)
{
return vector2_base<T>(v.x / len, v.y / len);
}

typedef vector2_base<float> vec2;
typedef vector2_base<bool> bvec2;
typedef vector2_base<int> ivec2;

template<typename T>
inline vector2_base<T> closest_point_on_line(vector2_base<T> line_point0, vector2_base<T> line_point1, vector2_base<T> target_point)
inline bool closest_point_on_line(vector2_base<T> line_point0, vector2_base<T> line_point1, vector2_base<T> target_point, vector2_base<T> &out_pos)
{
vector2_base<T> c = target_point - line_point0;
vector2_base<T> v = (line_point1 - line_point0);
v = normalize(v);
T d = length(line_point0 - line_point1);
T t = dot(v, c) / d;
return mix(line_point0, line_point1, clamp(t, (T)0, (T)1));
/*
if (t < 0) t = 0;
if (t > 1.0f) return 1.0f;
return t;*/
if(d > 0)
{
v = normalize_pre_length<T>(v, d);
T t = dot(v, c) / d;
out_pos = mix(line_point0, line_point1, clamp(t, (T)0, (T)1));
return true;
}
else
return false;
}

// ------------------------------------
Expand Down
20 changes: 13 additions & 7 deletions src/engine/client.h
Expand Up @@ -6,6 +6,7 @@

#include "graphics.h"
#include "message.h"
#include <base/hash.h>
#include <engine/friends.h>

struct SWarning;
Expand All @@ -17,6 +18,8 @@ enum
RECORDER_RACE = 2,
RECORDER_REPLAYS = 3,
RECORDER_MAX = 4,

NUM_DUMMIES = 2,
};

typedef bool (*CLIENTFUNC_FILTER)(const void *pData, int DataSize, void *pUser);
Expand All @@ -29,13 +32,13 @@ class IClient : public IInterface
int m_State;

// quick access to time variables
int m_PrevGameTick[2];
int m_CurGameTick[2];
float m_GameIntraTick[2];
float m_GameTickTime[2];
int m_PrevGameTick[NUM_DUMMIES];
int m_CurGameTick[NUM_DUMMIES];
float m_GameIntraTick[NUM_DUMMIES];
float m_GameTickTime[NUM_DUMMIES];

int m_PredTick[2];
float m_PredIntraTick[2];
int m_PredTick[NUM_DUMMIES];
float m_PredIntraTick[NUM_DUMMIES];

float m_LocalTime;
float m_RenderFrameTime;
Expand All @@ -46,6 +49,7 @@ class IClient : public IInterface

public:
char m_aNews[3000];
int m_Points;
int64 m_ReconnectTime;

class CSnapItem
Expand Down Expand Up @@ -193,7 +197,8 @@ class IClient : public IInterface

virtual const char *GetCurrentMap() = 0;
virtual const char *GetCurrentMapPath() = 0;
virtual unsigned GetMapCrc() = 0;
virtual SHA256_DIGEST GetCurrentMapSha256() = 0;
virtual unsigned GetCurrentMapCrc() = 0;

virtual int GetCurrentRaceTime() = 0;

Expand Down Expand Up @@ -253,6 +258,7 @@ class IGameClient : public IInterface
virtual void OnDummyDisconnect() = 0;
virtual void Echo(const char *pString) = 0;
virtual bool CanDisplayWarning() = 0;
virtual bool IsDisplayingWarning() = 0;
};

extern IGameClient *CreateGameClient();
Expand Down
451 changes: 323 additions & 128 deletions src/engine/client/backend_sdl.cpp

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions src/engine/client/backend_sdl.h
Expand Up @@ -4,6 +4,7 @@
#include "SDL.h"
#include "SDL_opengl.h"

#include "blocklist_driver.h"
#include "graphics_threaded.h"

#include <base/tl/threading.h>
Expand Down Expand Up @@ -103,7 +104,7 @@ class CGLSLPrimitiveProgram;
class CGLSLQuadProgram;
class CGLSLTileProgram;
class CGLSLTextProgram;
class CGLSLSpriteProgram;
class CGLSLPrimitiveExProgram;
class CGLSLSpriteMultipleProgram;

// takes care of opengl related rendering
Expand Down Expand Up @@ -206,7 +207,7 @@ class CCommandProcessorFragment_OpenGL
virtual void Cmd_RenderText(const CCommandBuffer::SCommand_RenderText *pCommand) {}
virtual void Cmd_RenderTextStream(const CCommandBuffer::SCommand_RenderTextStream *pCommand) {}
virtual void Cmd_RenderQuadContainer(const CCommandBuffer::SCommand_RenderQuadContainer *pCommand) {}
virtual void Cmd_RenderQuadContainerAsSprite(const CCommandBuffer::SCommand_RenderQuadContainerAsSprite *pCommand) {}
virtual void Cmd_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) {}
virtual void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) {}

public:
Expand Down Expand Up @@ -317,7 +318,8 @@ class CCommandProcessorFragment_OpenGL3_3 : public CCommandProcessorFragment_Ope
CGLSLQuadProgram *m_pQuadProgram;
CGLSLQuadProgram *m_pQuadProgramTextured;
CGLSLTextProgram *m_pTextProgram;
CGLSLSpriteProgram *m_pSpriteProgram;
CGLSLPrimitiveExProgram *m_pPrimitiveExProgram;
CGLSLPrimitiveExProgram *m_pPrimitiveExProgramTextured;
CGLSLSpriteMultipleProgram *m_pSpriteProgramMultiple;

GLuint m_LastProgramID;
Expand Down Expand Up @@ -388,7 +390,7 @@ class CCommandProcessorFragment_OpenGL3_3 : public CCommandProcessorFragment_Ope
void Cmd_RenderText(const CCommandBuffer::SCommand_RenderText *pCommand) override;
void Cmd_RenderTextStream(const CCommandBuffer::SCommand_RenderTextStream *pCommand) override;
void Cmd_RenderQuadContainer(const CCommandBuffer::SCommand_RenderQuadContainer *pCommand) override;
void Cmd_RenderQuadContainerAsSprite(const CCommandBuffer::SCommand_RenderQuadContainerAsSprite *pCommand) override;
void Cmd_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) override;
void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) override;

public:
Expand Down Expand Up @@ -418,6 +420,8 @@ class CCommandProcessorFragment_SDL
SDL_GLContext m_GLContext;
SBackendCapabilites *m_pCapabilities;

const char **m_pErrStringPtr;

int *m_pInitError;

int m_RequestedMajor;
Expand Down Expand Up @@ -486,6 +490,8 @@ class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded

bool m_UseNewOpenGL;

char m_aErrorString[256];

public:
virtual int Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, class IStorage *pStorage);
virtual int Shutdown();
Expand All @@ -511,6 +517,14 @@ class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded
virtual bool HasTextBuffering() { return m_Capabilites.m_TextBuffering; }
virtual bool HasQuadContainerBuffering() { return m_Capabilites.m_QuadContainerBuffering; }
virtual bool Has2DTextureArrays() { return m_Capabilites.m_2DArrayTextures; }

virtual const char *GetErrorString()
{
if(m_aErrorString[0] != '\0')
return m_aErrorString;

return NULL;
}
};

#endif // ENGINE_CLIENT_BACKEND_SDL_H
77 changes: 77 additions & 0 deletions src/engine/client/blocklist_driver.cpp
@@ -0,0 +1,77 @@
#include "blocklist_driver.h"

#include <base/system.h>

#define VERSION_PARTS 4

struct SVersion
{
int m_Parts[VERSION_PARTS];

bool operator<=(const SVersion &Other) const
{
for(int i = 0; i < VERSION_PARTS; i++)
{
if(m_Parts[i] < Other.m_Parts[i])
return true;
if(m_Parts[i] > Other.m_Parts[i])
return false;
}
return true;
}
};

/* TODO: generalize it more for other drivers / vendors */
struct SBackEndDriverBlockList
{
SVersion m_VersionMin;
SVersion m_VersionMax;

// the OpenGL version, that is supported
int m_AllowedMajor;
int m_AllowedMinor;
int m_AllowedPatch;

const char *m_pReason;
};

static SBackEndDriverBlockList gs_aBlockList[] = {
{{26, 20, 100, 7800}, {26, 20, 100, 7999}, 2, 0, 0, "This Intel driver version can cause crashes, please update it to a newer version."}};

const char *ParseBlocklistDriverVersions(const char *pVendorStr, const char *pVersionStr, int &BlocklistMajor, int &BlocklistMinor, int &BlocklistPatch)
{
if(str_find_nocase(pVendorStr, "Intel") == NULL)
return NULL;

const char *pVersionStrStart = str_find_nocase(pVersionStr, "Build ");
if(pVersionStrStart == NULL)
return NULL;

// ignore "Build ", after that, it should directly start with the driver version
pVersionStrStart += (ptrdiff_t)str_length("Build ");

char aVersionStrHelper[512]; // the size is random, but shouldn't be too small probably

SVersion Version;
for(int &VersionPart : Version.m_Parts)
{
pVersionStrStart = str_next_token(pVersionStrStart, ".", aVersionStrHelper, sizeof(aVersionStrHelper));
if(pVersionStrStart == NULL)
return NULL;

VersionPart = str_toint(aVersionStrHelper);
}

for(const auto &BlockListItem : gs_aBlockList)
{
if(BlockListItem.m_VersionMin <= Version && Version <= BlockListItem.m_VersionMax)
{
BlocklistMajor = BlockListItem.m_AllowedMajor;
BlocklistMinor = BlockListItem.m_AllowedMinor;
BlocklistPatch = BlockListItem.m_AllowedPatch;
return BlockListItem.m_pReason;
}
}

return NULL;
}
6 changes: 6 additions & 0 deletions src/engine/client/blocklist_driver.h
@@ -0,0 +1,6 @@
#ifndef ENGINE_CLIENT_BLOCKLIST_DRIVER_H
#define ENGINE_CLIENT_BLOCKLIST_DRIVER_H

const char *ParseBlocklistDriverVersions(const char *pVendorStr, const char *pVersionStr, int &BlocklistMajor, int &BlocklistMinor, int &BlocklistPatch);

#endif // ENGINE_CLIENT_BLOCKLIST_DRIVER_H
136 changes: 76 additions & 60 deletions src/engine/client/client.cpp
Expand Up @@ -6,7 +6,6 @@
#include <new>

#include <stdarg.h>
#include <stdlib.h> // qsort
#include <tuple>

#include <base/hash_ctxt.h>
Expand Down Expand Up @@ -42,7 +41,6 @@
#include <engine/shared/demo.h>
#include <engine/shared/fifo.h>
#include <engine/shared/filecollection.h>
#include <engine/shared/ghost.h>
#include <engine/shared/json.h>
#include <engine/shared/network.h>
#include <engine/shared/packer.h>
Expand Down Expand Up @@ -265,8 +263,8 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD
CClient::CClient() :
m_DemoPlayer(&m_SnapshotDelta)
{
for(int i = 0; i < RECORDER_MAX; i++)
m_DemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta);
for(auto &DemoRecorder : m_DemoRecorder)
DemoRecorder = CDemoRecorder(&m_SnapshotDelta);

m_pEditor = 0;
m_pInput = 0;
Expand Down Expand Up @@ -330,6 +328,7 @@ CClient::CClient() :
str_format(m_aDDNetInfoTmp, sizeof(m_aDDNetInfoTmp), DDNET_INFO ".%d.tmp", pid());
m_pDDNetInfoTask = NULL;
m_aNews[0] = '\0';
m_Points = -1;

m_CurrentServerInfoRequestTime = -1;

Expand Down Expand Up @@ -404,9 +403,9 @@ int CClient::SendMsg(CMsgPacker *pMsg, int Flags)

if(Flags & MSGFLAG_RECORD)
{
for(int i = 0; i < RECORDER_MAX; i++)
if(m_DemoRecorder[i].IsRecording())
m_DemoRecorder[i].RecordMessage(Packet.m_pData, Packet.m_DataSize);
for(auto &i : m_DemoRecorder)
if(i.IsRecording())
i.RecordMessage(Packet.m_pData, Packet.m_DataSize);
}

if(!(Flags & MSGFLAG_NOSEND))
Expand Down Expand Up @@ -502,7 +501,7 @@ void CClient::SendInput()

bool Force = false;
// fetch input
for(int Dummy = 0; Dummy < 2; Dummy++)
for(int Dummy = 0; Dummy < NUM_DUMMIES; Dummy++)
{
if(!m_DummyConnected && Dummy != 0)
{
Expand Down Expand Up @@ -1166,8 +1165,8 @@ const char *CClient::LoadMap(const char *pName, const char *pFilename, SHA256_DI
return s_aErrorMsg;
}

// get the crc of the map
if(m_pMap->Crc() != WantedCrc)
// Only check CRC if we don't have the secure SHA256.
if(!pWantedSha256 && m_pMap->Crc() != WantedCrc)
{
str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", s_aErrorMsg);
Expand Down Expand Up @@ -1197,15 +1196,17 @@ const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedS
char aWanted[256];
char aWantedSha256[SHA256_MAXSTRSIZE];

aWanted[0] = 0;

if(pWantedSha256)
{
sha256_str(*pWantedSha256, aWantedSha256, sizeof(aWantedSha256));
str_format(aWanted, sizeof(aWanted), " sha256=%s", aWantedSha256);
str_format(aWanted, sizeof(aWanted), "sha256=%s", aWantedSha256);
}
else
{
str_format(aWanted, sizeof(aWanted), "crc=%08x", WantedCrc);
}

str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted%s crc=%08x", pMapName, aWanted, WantedCrc);
str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted %s", pMapName, aWanted);
m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
SetState(IClient::STATE_LOADING);

Expand All @@ -1218,14 +1219,15 @@ const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedS
// try the downloaded maps
if(pWantedSha256)
{
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x_%s.map", pMapName, WantedCrc, aWantedSha256);
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%s.map", pMapName, aWantedSha256);
}
else
{
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return pError;
}

// try the downloaded maps folder without appending the sha256
str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc);
if(!pError)
return pError;
Expand All @@ -1239,27 +1241,25 @@ const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedS
return pError;
}

int CClient::PlayerScoreNameComp(const void *a, const void *b)
bool CClient::PlayerScoreNameLess(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1)
{
CServerInfo::CClient *p0 = (CServerInfo::CClient *)a;
CServerInfo::CClient *p1 = (CServerInfo::CClient *)b;
if(p0->m_Player && !p1->m_Player)
return -1;
if(!p0->m_Player && p1->m_Player)
return 1;
if(p0.m_Player && !p1.m_Player)
return true;
if(!p0.m_Player && p1.m_Player)
return false;

int Score0 = p0->m_Score;
int Score1 = p1->m_Score;
int Score0 = p0.m_Score;
int Score1 = p1.m_Score;
if(Score0 == -9999)
Score0 = INT_MIN;
if(Score1 == -9999)
Score1 = INT_MIN;

if(Score0 > Score1)
return -1;
return true;
if(Score0 < Score1)
return 1;
return str_comp_nocase(p0->m_aName, p1->m_aName);
return false;
return str_comp_nocase(p0.m_aName, p1.m_aName) < 0;
}

void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
Expand Down Expand Up @@ -1520,7 +1520,7 @@ void CClient::ProcessServerInfo(int RawType, NETADDR *pFrom, const void *pData,

if(!Up.Error() || IgnoreError)
{
qsort(Info.m_aClients, Info.m_NumReceivedClients, sizeof(*Info.m_aClients), PlayerScoreNameComp);
std::sort(Info.m_aClients, Info.m_aClients + Info.m_NumReceivedClients, PlayerScoreNameLess);

if(!DuplicatedPacket && (!pEntry || !pEntry->m_GotInfo || SavedType >= pEntry->m_Info.m_Type))
{
Expand Down Expand Up @@ -1569,28 +1569,23 @@ bool CClient::ShouldSendChatTimeoutCodeHeuristic()

static void FormatMapDownloadFilename(const char *pName, SHA256_DIGEST *pSha256, int Crc, bool Temp, char *pBuffer, int BufferSize)
{
char aSha256[SHA256_MAXSTRSIZE + 1];
aSha256[0] = 0;
char aHash[SHA256_MAXSTRSIZE];
if(pSha256)
{
aSha256[0] = '_';
sha256_str(*pSha256, aSha256 + 1, sizeof(aSha256) - 1);
sha256_str(*pSha256, aHash, sizeof(aHash));
}
else
{
str_format(aHash, sizeof(aHash), "%08x", Crc);
}

if(Temp)
{
str_format(pBuffer, BufferSize, "%s_%08x%s.map.%d.tmp",
pName,
Crc,
aSha256,
pid());
str_format(pBuffer, BufferSize, "%s_%s.map.%d.tmp", pName, aHash, pid());
}
else
{
str_format(pBuffer, BufferSize, "%s_%08x%s.map",
pName,
Crc,
aSha256);
str_format(pBuffer, BufferSize, "%s_%s.map", pName, aHash);
}
}

Expand Down Expand Up @@ -1875,7 +1870,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
int GameTick = Unpacker.GetInt();
int DeltaTick = GameTick - Unpacker.GetInt();
int PartSize = 0;
int Crc = 0;
unsigned int Crc = 0;
int CompleteSize = 0;
const char *pData = 0;

Expand Down Expand Up @@ -2024,12 +2019,12 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
SnapshotRemoveExtraInfo(aExtraInfoRemoved);

// add snapshot to demo
for(int i = 0; i < RECORDER_MAX; i++)
for(auto &DemoRecorder : m_DemoRecorder)
{
if(m_DemoRecorder[i].IsRecording())
if(DemoRecorder.IsRecording())
{
// write snapshot
m_DemoRecorder[i].RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize);
DemoRecorder.RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize);
}
}

Expand Down Expand Up @@ -2096,9 +2091,9 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket)
if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 || Msg == NETMSGTYPE_SV_EXTRAPROJECTILE)
{
// game message
for(int i = 0; i < RECORDER_MAX; i++)
if(m_DemoRecorder[i].IsRecording())
m_DemoRecorder[i].RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
for(auto &DemoRecorder : m_DemoRecorder)
if(DemoRecorder.IsRecording())
DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);

GameClient()->OnMessage(Msg, &Unpacker);
}
Expand Down Expand Up @@ -2163,7 +2158,7 @@ void CClient::ProcessServerPacketDummy(CNetChunk *pPacket)
int GameTick = Unpacker.GetInt();
int DeltaTick = GameTick - Unpacker.GetInt();
int PartSize = 0;
int Crc = 0;
unsigned int Crc = 0;
int CompleteSize = 0;
const char *pData = 0;

Expand Down Expand Up @@ -2527,13 +2522,17 @@ void CClient::LoadDDNetInfo()

str_copy(m_aNews, pNewsString, sizeof(m_aNews));
}

const json_value *pPoints = json_object_get(pDDNetInfo, "points");
if(pPoints->type == json_integer)
m_Points = pPoints->u.integer;
}

void CClient::PumpNetwork()
{
for(int i = 0; i < NUM_CLIENTS; i++)
for(auto &NetClient : m_NetClient)
{
m_NetClient[i].Update();
NetClient.Update();
}

if(State() != IClient::STATE_DEMOPLAYBACK)
Expand Down Expand Up @@ -2880,7 +2879,6 @@ void CClient::Update()
FinishDDNetInfo();
else if(m_pDDNetInfoTask->State() == HTTP_ERROR)
{
m_Warnings.emplace_back(SWarning(Localize("Downloading ddnet-info.json failed")));
Storage()->RemoveFile(m_aDDNetInfoTmp, IStorage::TYPE_SAVE);
ResetDDNetInfo();
}
Expand Down Expand Up @@ -3033,6 +3031,10 @@ void CClient::Run()
}
}

// make sure the first frame just clears everything to prevent undesired colors when waiting for io
Graphics()->Clear(0, 0, 0);
Graphics()->Swap();

// init sound, allowed to fail
m_SoundInitFailed = Sound()->Init() != 0;

Expand All @@ -3054,12 +3056,12 @@ void CClient::Run()
mem_zero(&BindAddr, sizeof(BindAddr));
BindAddr.type = NETTYPE_ALL;
}
for(int i = 0; i < NUM_CLIENTS; i++)
for(auto &NetClient : m_NetClient)
{
do
{
BindAddr.port = (secure_rand() % 64511) + 1024;
} while(!m_NetClient[i].Open(BindAddr, 0));
} while(!NetClient.Open(BindAddr, 0));
}
}

Expand Down Expand Up @@ -3327,7 +3329,7 @@ void CClient::Run()
s_SavedConfig = true;
}

if(m_Warnings.empty() && GameClient()->CanDisplayWarning())
if(m_Warnings.empty() && !GameClient()->IsDisplayingWarning())
break;
}

Expand Down Expand Up @@ -3704,6 +3706,13 @@ const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
{
int Crc;
const char *pError;

IOHANDLE File = Storage()->OpenFile(pFilename, IOFLAG_READ, StorageType);
if(!File)
return "error opening demo file";

io_close(File);

Disconnect();
m_NetClient[CLIENT_MAIN].ResetErrorString();

Expand Down Expand Up @@ -3778,7 +3787,9 @@ const char *CClient::DemoPlayer_Render(const char *pFilename, int StorageType, c
void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
{
CClient *pSelf = (CClient *)pUserData;
pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
const char *pError = pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
if(pError)
pSelf->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_player", pError);
}

void CClient::Con_DemoPlay(IConsole::IResult *pResult, void *pUserData)
Expand Down Expand Up @@ -4372,7 +4383,12 @@ const char *CClient::GetCurrentMapPath()
return m_aCurrentMapPath;
}

unsigned CClient::GetMapCrc()
SHA256_DIGEST CClient::GetCurrentMapSha256()
{
return m_pMap->Sha256();
}

unsigned CClient::GetCurrentMapCrc()
{
return m_pMap->Crc();
}
Expand Down
33 changes: 17 additions & 16 deletions src/engine/client/client.h
Expand Up @@ -10,6 +10,7 @@
#include <engine/client.h>
#include <engine/client/demoedit.h>
#include <engine/client/friends.h>
#include <engine/client/ghost.h>
#include <engine/client/http.h>
#include <engine/client/serverbrowser.h>
#include <engine/client/updater.h>
Expand All @@ -22,7 +23,6 @@
#include <engine/shared/config.h>
#include <engine/shared/demo.h>
#include <engine/shared/fifo.h>
#include <engine/shared/ghost.h>
#include <engine/shared/network.h>
#include <engine/sound.h>
#include <engine/steam.h>
Expand Down Expand Up @@ -104,7 +104,7 @@ class CClient : public IClient, public CDemoPlayer::IListener

enum
{
CLIENT_MAIN,
CLIENT_MAIN = 0,
CLIENT_DUMMY,
CLIENT_CONTACT,
NUM_CLIENTS,
Expand All @@ -125,7 +125,7 @@ class CClient : public IClient, public CDemoPlayer::IListener

CUuid m_ConnectionID;

unsigned m_SnapshotParts[2];
unsigned m_SnapshotParts[NUM_DUMMIES];
int64 m_LocalStartTime;

IGraphics::CTextureHandle m_DebugFont;
Expand All @@ -145,9 +145,9 @@ class CClient : public IClient, public CDemoPlayer::IListener
bool m_SoundInitFailed;
bool m_ResortServerBrowser;

int m_AckGameTick[2];
int m_CurrentRecvTick[2];
int m_RconAuthed[2];
int m_AckGameTick[NUM_DUMMIES];
int m_CurrentRecvTick[NUM_DUMMIES];
int m_RconAuthed[NUM_DUMMIES];
char m_RconPassword[32];
int m_UseTempRconCommands;
char m_Password[32];
Expand All @@ -163,8 +163,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
char m_aCurrentMap[MAX_PATH_LENGTH];
char m_aCurrentMapPath[MAX_PATH_LENGTH];

char m_aTimeoutCodes[2][32];
bool m_aTimeoutCodeSent[2];
char m_aTimeoutCodes[NUM_DUMMIES][32];
bool m_aTimeoutCodeSent[NUM_DUMMIES];
bool m_GenerateTimeoutSeed;

//
Expand Down Expand Up @@ -195,7 +195,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
char m_aDummyNameBuf[16];

// time
CSmoothTime m_GameTime[2];
CSmoothTime m_GameTime[NUM_DUMMIES];
CSmoothTime m_PredictedTime;

// input
Expand All @@ -205,9 +205,9 @@ class CClient : public IClient, public CDemoPlayer::IListener
int m_Tick; // the tick that the input is for
int64 m_PredictedTime; // prediction latency when we sent this input
int64 m_Time;
} m_aInputs[2][200];
} m_aInputs[NUM_DUMMIES][200];

int m_CurrentInput[2];
int m_CurrentInput[NUM_DUMMIES];
bool m_LastDummy;
bool m_DummySendConnInfo;

Expand All @@ -217,10 +217,10 @@ class CClient : public IClient, public CDemoPlayer::IListener
CGraph m_FpsGraph;

// the game snapshots are modifiable by the game
class CSnapshotStorage m_SnapshotStorage[2];
CSnapshotStorage::CHolder *m_aSnapshots[2][NUM_SNAPSHOT_TYPES];
class CSnapshotStorage m_SnapshotStorage[NUM_DUMMIES];
CSnapshotStorage::CHolder *m_aSnapshots[NUM_DUMMIES][NUM_SNAPSHOT_TYPES];

int m_ReceivedSnapshots[2];
int m_ReceivedSnapshots[NUM_DUMMIES];
char m_aSnapshotIncomingData[CSnapshot::MAX_SIZE];

class CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES];
Expand Down Expand Up @@ -352,7 +352,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc);
const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc);

static int PlayerScoreNameComp(const void *a, const void *b);
static bool PlayerScoreNameLess(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1);

void ProcessConnlessPacket(CNetChunk *pPacket);
void ProcessServerInfo(int Type, NETADDR *pFrom, const void *pData, int DataSize);
Expand Down Expand Up @@ -471,7 +471,8 @@ class CClient : public IClient, public CDemoPlayer::IListener

virtual const char *GetCurrentMap();
virtual const char *GetCurrentMapPath();
virtual unsigned GetMapCrc();
virtual SHA256_DIGEST GetCurrentMapSha256();
virtual unsigned GetCurrentMapCrc();

virtual void RaceRecord_Start(const char *pFilename);
virtual void RaceRecord_Stop();
Expand Down