2733: Implement on-demand skin downloader, remove Lappi's non-free skins (fixes #1166, fixes #2547) r=Learath2 a=def-

Seems to work on first try, I'm somewhat surprised :D

Future ideas:

- I haven't checked how the performance impact is of a new skin joining,
  might want to move the loading to a separate thread?
- Use the skins from download directory instead of redownloading? Might
  want to compare modified time.
- Make all skins load on demand only to save some memory?

Co-authored-by: def <dennis@felsin9.de>
This commit is contained in:
bors[bot] 2020-09-17 13:49:23 +00:00 committed by GitHub
commit 77cb30aec4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 192 additions and 43 deletions

View file

@ -1182,8 +1182,6 @@ set(EXPECTED_DATA
skins/bluestripe.png skins/bluestripe.png
skins/bomb.png skins/bomb.png
skins/brownbear.png skins/brownbear.png
skins/bunny.png
skins/caesar.png
skins/cammo.png skins/cammo.png
skins/cammostripes.png skins/cammostripes.png
skins/chinese_by_whis.png skins/chinese_by_whis.png
@ -1207,7 +1205,6 @@ set(EXPECTED_DATA
skins/demonlimekitty.png skins/demonlimekitty.png
skins/dino.png skins/dino.png
skins/dragon.png skins/dragon.png
skins/emo.png
skins/evil.png skins/evil.png
skins/evilwolfe.png skins/evilwolfe.png
skins/ghost.png skins/ghost.png
@ -1218,7 +1215,6 @@ set(EXPECTED_DATA
skins/greyfox_2.png skins/greyfox_2.png
skins/hammie-chew.png skins/hammie-chew.png
skins/hammie-whis.png skins/hammie-whis.png
skins/hedgehog.png
skins/jeet.png skins/jeet.png
skins/kintaro_2.png skins/kintaro_2.png
skins/kitty_bluestripe.png skins/kitty_bluestripe.png
@ -1237,7 +1233,6 @@ set(EXPECTED_DATA
skins/kitty_warpaint.png skins/kitty_warpaint.png
skins/kitty_x_ninja.png skins/kitty_x_ninja.png
skins/license.txt skins/license.txt
skins/lightbulb.png
skins/limekitty.png skins/limekitty.png
skins/mermydon-coala.png skins/mermydon-coala.png
skins/mermydon.png skins/mermydon.png
@ -1245,9 +1240,7 @@ set(EXPECTED_DATA
skins/musmann.png skins/musmann.png
skins/nanami.png skins/nanami.png
skins/nanas.png skins/nanas.png
skins/napoleon.png
skins/nersif.png skins/nersif.png
skins/nosey.png
skins/oldman.png skins/oldman.png
skins/oldschool.png skins/oldschool.png
skins/penguin.png skins/penguin.png
@ -1255,7 +1248,6 @@ set(EXPECTED_DATA
skins/random.png skins/random.png
skins/redbopp.png skins/redbopp.png
skins/redstripe.png skins/redstripe.png
skins/roman.png
skins/saddo.png skins/saddo.png
skins/santa_bluekitty.png skins/santa_bluekitty.png
skins/santa_bluestripe.png skins/santa_bluestripe.png
@ -1273,8 +1265,6 @@ set(EXPECTED_DATA
skins/santa_twinbop.png skins/santa_twinbop.png
skins/santa_twintri.png skins/santa_twintri.png
skins/santa_warpaint.png skins/santa_warpaint.png
skins/savage.png
skins/tank.png
skins/teerasta.png skins/teerasta.png
skins/toptri.png skins/toptri.png
skins/twinbop.png skins/twinbop.png

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1168,6 +1168,9 @@ Skip the main menu
Max CSVs Max CSVs
== ==
Download skins
==
Borderless window Borderless window
== ==

View file

@ -1217,6 +1217,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
https://wiki.ddnet.tw/ https://wiki.ddnet.tw/
== ==

View file

@ -847,6 +847,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1194,6 +1194,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -853,6 +853,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1206,6 +1206,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -851,6 +851,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1222,6 +1222,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -1225,6 +1225,9 @@ Editor
Menu Menu
== Menü == Menü
Download skins
== Skins herunterladen
Warning Warning
== Warnung == Warnung

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1200,6 +1200,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Skin prefix Skin prefix
== ==

View file

@ -856,6 +856,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -849,6 +849,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -841,6 +841,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1202,6 +1202,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -848,6 +848,9 @@ Show only supported
V-Sync V-Sync
== ==
Download skins
==
may cause delay may cause delay
== ==

View file

@ -1206,6 +1206,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -1031,6 +1031,9 @@ Reset wanted weapon on death
Skip the main menu Skip the main menu
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -856,6 +856,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -636,9 +636,6 @@ Internet
Max demos Max demos
== Максимальное количество демо == Максимальное количество демо
News
==
Join game Join game
== Играть == Играть
@ -843,9 +840,6 @@ Server best:
Personal best: Personal best:
== Ваше лучшее: == Ваше лучшее:
Learn
==
Browser Browser
== Браузер == Браузер
@ -1229,3 +1223,12 @@ Server executable not found, can't run server
Editor Editor
== Редактор == Редактор
News
==
Download skins
==
Learn
==

View file

@ -854,6 +854,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1218,6 +1218,9 @@ Speed
Skip the main menu Skip the main menu
== 跳过主菜单 == 跳过主菜单
Download skins
==
Website Website
== 网站 == 网站

View file

@ -850,6 +850,9 @@ Max CSVs
Dummy settings Dummy settings
== ==
Download skins
==
Vanilla skins only Vanilla skins only
== ==

View file

@ -1207,6 +1207,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -1203,6 +1203,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Client message Client message
== ==

View file

@ -1212,6 +1212,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
Website Website
== ==

View file

@ -1209,6 +1209,9 @@ Speed
Skip the main menu Skip the main menu
== ==
Download skins
==
https://wiki.ddnet.tw/ https://wiki.ddnet.tw/
== ==

View file

@ -1204,6 +1204,9 @@ Markers
Skip the main menu Skip the main menu
== ==
Download skins
==
https://wiki.ddnet.tw/ https://wiki.ddnet.tw/
== ==

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -31,10 +31,6 @@ kitty_warpaint, kitty_x_ninja:
Copyright Ravie Copyright Ravie
CC0 license CC0 license
lightbulb, tank, emo, bunny, hedgehog, napoleon, roman, caesar, nosey, savage:
Copyright Lappi
CC-BY-NC-SA license
demonlimekitty, nanas, nersif: demonlimekitty, nanas, nersif:
Copyright Miper Copyright Miper
CC-BY-SA license CC-BY-SA license

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -452,6 +452,13 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
if(DoButton_CheckBox(&g_Config.m_ClDownloadSkins, Localize("Download skins"), g_Config.m_ClDownloadSkins, &DummyLabel))
{
g_Config.m_ClDownloadSkins ^= 1;
}
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
if(DoButton_CheckBox(&g_Config.m_ClVanillaSkinsOnly, Localize("Vanilla skins only"), g_Config.m_ClVanillaSkinsOnly, &DummyLabel)) if(DoButton_CheckBox(&g_Config.m_ClVanillaSkinsOnly, Localize("Vanilla skins only"), g_Config.m_ClVanillaSkinsOnly, &DummyLabel))
{ {
g_Config.m_ClVanillaSkinsOnly ^= 1; g_Config.m_ClVanillaSkinsOnly ^= 1;

View file

@ -6,9 +6,10 @@
#include <base/math.h> #include <base/math.h>
#include <ctime> #include <ctime>
#include <engine/engine.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/storage.h>
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/storage.h>
#include "skins.h" #include "skins.h"
@ -49,19 +50,25 @@ int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
return 0; return 0;
} }
char aBuf[512]; char aBuf[MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "skins/%s", pName); str_format(aBuf, sizeof(aBuf), "skins/%s", pName);
return pSelf->LoadSkin(aNameWithoutPng, aBuf, DirType);
}
int CSkins::LoadSkin(const char *pName, const char *pPath, int DirType)
{
char aBuf[512];
CImageInfo Info; CImageInfo Info;
if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType)) if(!Graphics()->LoadPNG(&Info, pPath, DirType))
{ {
str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName); str_format(aBuf, sizeof(aBuf), "failed to load skin from %s", pName);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
return 0; return 0;
} }
CSkin Skin; CSkin Skin;
Skin.m_IsVanilla = IsVanillaSkin(aNameWithoutPng); Skin.m_IsVanilla = IsVanillaSkin(pName);
Skin.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); Skin.m_OrgTexture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
int BodySize = 96; // body size int BodySize = 96; // body size
if (BodySize > Info.m_Height) if (BodySize > Info.m_Height)
@ -133,22 +140,21 @@ int CSkins::SkinScan(const char *pName, int IsDir, int DirType, void *pUser)
d[y*Pitch+x*4+2] = v; d[y*Pitch+x*4+2] = v;
} }
Skin.m_ColorTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); Skin.m_ColorTexture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
free(Info.m_pData); free(Info.m_pData);
// set skin data // set skin data
str_copy(Skin.m_aName, aNameWithoutPng, sizeof(Skin.m_aName)); str_copy(Skin.m_aName, pName, sizeof(Skin.m_aName));
if(g_Config.m_Debug) if(g_Config.m_Debug)
{ {
str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName); str_format(aBuf, sizeof(aBuf), "load skin %s", Skin.m_aName);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
} }
pSelf->m_aSkins.add(Skin); m_aSkins.add(Skin);
return 0; return 0;
} }
void CSkins::OnInit() void CSkins::OnInit()
{ {
m_EventSkinPrefix[0] = '\0'; m_EventSkinPrefix[0] = '\0';
@ -203,7 +209,7 @@ int CSkins::Find(const char *pName)
{ {
return -1; return -1;
} }
else if(pSkinPrefix) else if(pSkinPrefix && pSkinPrefix[0])
{ {
char aBuf[64]; char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s_%s", pSkinPrefix, pName); str_format(aBuf, sizeof(aBuf), "%s_%s", pSkinPrefix, pName);
@ -220,8 +226,51 @@ int CSkins::Find(const char *pName)
int CSkins::FindImpl(const char *pName) int CSkins::FindImpl(const char *pName)
{ {
auto r = ::find_binary(m_aSkins.all(), pName); auto r = ::find_binary(m_aSkins.all(), pName);
if(r.empty()) if(!r.empty())
return &r.front() - m_aSkins.base_ptr();
if(str_comp(pName, "default") == 0)
return -1; return -1;
return &r.front() - m_aSkins.base_ptr(); if(!g_Config.m_ClDownloadSkins)
return -1;
if(str_find(pName, "/") != 0)
return -1;
auto d = ::find_binary(m_aDownloadSkins.all(), pName);
if(!d.empty())
{
if(d.front().m_pTask && d.front().m_pTask->State() == HTTP_DONE)
{
char aPath[MAX_PATH_LENGTH];
str_format(aPath, sizeof(aPath), "downloadedskins/%s.png", d.front().m_aName);
Storage()->RenameFile(d.front().m_aPath, aPath, IStorage::TYPE_SAVE);
LoadSkin(d.front().m_aName, aPath, IStorage::TYPE_SAVE);
d.front().m_pTask = nullptr;
}
if(d.front().m_pTask && d.front().m_pTask->State() == HTTP_ERROR)
{
Storage()->RemoveFile(d.front().m_aPath, IStorage::TYPE_SAVE);
d.front().m_pTask = nullptr;
}
return -1;
}
int DefaultIndex = CSkins::Find("default");
CDownloadSkin Skin;
str_copy(Skin.m_aName, pName, sizeof(Skin.m_aName));
Skin.m_IsVanilla = false;
Skin.m_OrgTexture = m_aSkins[DefaultIndex].m_OrgTexture;
Skin.m_ColorTexture = m_aSkins[DefaultIndex].m_ColorTexture;
Skin.m_BloodColor = m_aSkins[DefaultIndex].m_BloodColor;
char aUrl[256];
str_format(aUrl, sizeof(aUrl), "%s%s.png", g_Config.m_ClSkinDownloadUrl, pName);
str_format(Skin.m_aPath, sizeof(Skin.m_aPath), "downloadedskins/%s.%d.tmp", pName, pid());
Skin.m_pTask = std::make_shared<CGetFile>(Storage(), aUrl, Skin.m_aPath, IStorage::TYPE_SAVE, CTimeout{0, 0, 0});
m_pClient->Engine()->AddJob(Skin.m_pTask);
m_aDownloadSkins.add(Skin);
return -1;
} }

View file

@ -2,9 +2,10 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_SKINS_H #ifndef GAME_CLIENT_COMPONENTS_SKINS_H
#define GAME_CLIENT_COMPONENTS_SKINS_H #define GAME_CLIENT_COMPONENTS_SKINS_H
#include <base/vmath.h>
#include <base/color.h> #include <base/color.h>
#include <base/tl/sorted_array.h> #include <base/tl/sorted_array.h>
#include <base/vmath.h>
#include <engine/client/http.h>
#include <game/client/component.h> #include <game/client/component.h>
class CSkins : public CComponent class CSkins : public CComponent
@ -25,6 +26,12 @@ public:
bool operator==(const char *pOther) { return !str_comp(m_aName, pOther); } bool operator==(const char *pOther) { return !str_comp(m_aName, pOther); }
}; };
struct CDownloadSkin : public CSkin
{
std::shared_ptr<CGetFile> m_pTask;
char m_aPath[MAX_PATH_LENGTH];
};
void OnInit(); void OnInit();
int Num(); int Num();
@ -33,8 +40,10 @@ public:
private: private:
sorted_array<CSkin> m_aSkins; sorted_array<CSkin> m_aSkins;
sorted_array<CDownloadSkin> m_aDownloadSkins;
char m_EventSkinPrefix[100]; char m_EventSkinPrefix[100];
int LoadSkin(const char *pName, const char *pPath, int DirType);
int FindImpl(const char *pName); int FindImpl(const char *pName);
static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser); static int SkinScan(const char *pName, int IsDir, int DirType, void *pUser);
}; };

View file

@ -78,15 +78,17 @@ MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG
MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "How long to show the server message of the day") MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT|CFGFLAG_SAVE, "How long to show the server message of the day")
// http map download // http map download
MACRO_CONFIG_STR(ClMapDownloadUrl, cl_map_download_url, 100, "https://maps2.ddnet.tw", CFGFLAG_CLIENT|CFGFLAG_SAVE, "URL to use to download maps (can start with http:// or https://)") MACRO_CONFIG_STR(ClMapDownloadUrl, cl_map_download_url, 100, "https://maps2.ddnet.tw", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download maps (can start with http:// or https://)")
MACRO_CONFIG_INT(ClMapDownloadConnectTimeoutMs, cl_map_download_connect_timeout_ms, 2000, 0, 100000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "HTTP map downloads: timeout for the connect phase in milliseconds (0 to disable)") MACRO_CONFIG_INT(ClMapDownloadConnectTimeoutMs, cl_map_download_connect_timeout_ms, 2000, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: timeout for the connect phase in milliseconds (0 to disable)")
MACRO_CONFIG_INT(ClMapDownloadLowSpeedLimit, cl_map_download_low_speed_limit, 500, 0, 100000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit in bytes per second (0 to disable)") MACRO_CONFIG_INT(ClMapDownloadLowSpeedLimit, cl_map_download_low_speed_limit, 500, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit in bytes per second (0 to disable)")
MACRO_CONFIG_INT(ClMapDownloadLowSpeedTime, cl_map_download_low_speed_time, 5, 0, 100000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit time period (0 to disable)") MACRO_CONFIG_INT(ClMapDownloadLowSpeedTime, cl_map_download_low_speed_time, 5, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "HTTP map downloads: Set low speed limit time period (0 to disable)")
MACRO_CONFIG_STR(ClLanguagefile, cl_languagefile, 255, "", CFGFLAG_CLIENT|CFGFLAG_SAVE, "What language file to use") MACRO_CONFIG_STR(ClLanguagefile, cl_languagefile, 255, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "What language file to use")
MACRO_CONFIG_INT(ClVanillaSkinsOnly, cl_vanilla_skins_only, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Only show skins available in Vanilla Teeworlds") MACRO_CONFIG_STR(ClSkinDownloadUrl, cl_skin_download_url, 100, "https://skins.ddnet.tw/skin/", CFGFLAG_CLIENT | CFGFLAG_SAVE, "URL used to download skins")
MACRO_CONFIG_INT(ClAutoStatboardScreenshot, cl_auto_statboard_screenshot, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Automatically take game over statboard screenshot") MACRO_CONFIG_INT(ClVanillaSkinsOnly, cl_vanilla_skins_only, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Only show skins available in Vanilla Teeworlds")
MACRO_CONFIG_INT(ClAutoStatboardScreenshotMax, cl_auto_statboard_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Maximum number of automatically created statboard screenshots (0 = no limit)") MACRO_CONFIG_INT(ClDownloadSkins, cl_download_skins, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Download skins from cl_skin_download_url on-the-fly")
MACRO_CONFIG_INT(ClAutoStatboardScreenshot, cl_auto_statboard_screenshot, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Automatically take game over statboard screenshot")
MACRO_CONFIG_INT(ClAutoStatboardScreenshotMax, cl_auto_statboard_screenshot_max, 10, 0, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Maximum number of automatically created statboard screenshots (0 = no limit)")
MACRO_CONFIG_INT(ClDefaultZoom, cl_default_zoom, 10, 0, 20, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Default zoom level (10 default, min 0, max 20)") MACRO_CONFIG_INT(ClDefaultZoom, cl_default_zoom, 10, 0, 20, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Default zoom level (10 default, min 0, max 20)")
MACRO_CONFIG_INT(ClSmoothZoomTime, cl_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Time of smooth zoom animation in ms (0 for off)") MACRO_CONFIG_INT(ClSmoothZoomTime, cl_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Time of smooth zoom animation in ms (0 for off)")