Previously, skins not found in the `skins` folder were downloaded from the configured URL and saved to the `downloadedskins` folder, but the saved files were never used, leading to wasted downloads and write operations.
Now, if a skin to be downloaded already exists in the `downloadedskins` folder, the modified time of the skin file is determined and included in the HTTP GET request as `If-Modified-Since` header. If the file was not modified on the server since that time, the server will answer with status 304 Not Modified and an empty response body, in which case the existing skin file is loaded. If the skin file was modified, then it will be downloaded like usual. The download will also be retried without including the `If-Modified-Since` header, if the local file is more recent but it failed to be loaded. If a skin could not be downloaded when it should but a file already exists in the `downloadedskins` folder then that file will be loaded instead.
Instead of checking whether the number of skins is different to determine whether to reload the list of skins in the settings menus, the last time that the list of skins was refreshed is now saved and compared, which is more robust. The additional check to refresh the skin list while skins are being downloaded is unnecessary, as any updates to the skin list are now covered by checking the last refresh time.
If the sql statement does not contain placeholders
``NumParameters`` can be empty.
In that case accessing the first element will cause an asan error:
```
runtime error: reference binding to null pointer of type 'st_mysql_bind'
```
Add constant to replace the magic number `24` used for the size of skin names. Skin names in the server info/browser were sized `24 + 1` but the additional byte was unnecessary.
This now shows less errors:
```
valgrind --tool=memcheck --gen-suppressions=all --suppressions=../memcheck.supp --leak-check=full --show-leak-kinds=all ./DDNet-Server
```
There are still 3 errors that might not be false positives.
See #8943
When the datafile is closed, only the `CDataFileReader::Open` and `CDataFileReader::Close` functions may be called. All other functions will now assert instead of returning some default-values if no file is open. The `CDataFileReader::Open` function will now assert if the file is already open instead of implicitly closing the previous one.
The whole idea behind custom sounds was to use them as replacement for
built-in sounds (depending on the gameplay). We have to use the same channels
instead of the MAP (aka 'ambient') channel to have the same volume for both
sets of messages.
Otherwise we have situation of players reporting 'no sound' because they have
`snd_ambient_volume 0` in the configs.
NETMSGTYPE_SV_MAPSOUNDGLOBAL is NETMSGTYPE_SV_SOUNDGLOBAL which uses map
assets as the sounds container. Use the same CSounds::CHN_GLOBAL to make
the sound messages equivalent.
NETEVENTTYPE_MAPSOUNDWORLD is NETEVENTTYPE_SOUNDWORLD which uses map assets
as the sounds container. Use the same CSounds::CHN_WORLD to make the sound
events equivalent.