diff -Nru ddnet-15.3.2/autoexec_server.cfg ddnet-15.5.4/autoexec_server.cfg --- ddnet-15.3.2/autoexec_server.cfg 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/autoexec_server.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,208 +0,0 @@ -# -# autoexec_server.cfg -# -# See https://ddnet.tw/settingscommands/ for all available settings. -# Everything following a # is considered a comment and ignored by the server. -# When an option can be enabled or disabled, it's enabled with 1, disabled with 0. -# -# SEE CUSTOM CONFIG AT THE END TO PREVENT DDNET UPDATES FROM OVERWRITING YOUR SETTINGS - - - -# GENERAL OPTIONS -# --------------- - -# Server port (only port range 8303-8310 show up in LAN tab, -# defaults to 0 to automatically select free port in range 8303-8310) -#sv_port 8303 - -# Server name -sv_name "My DDNet server" - -# Password for joining the server, empty for no password -password "" - -# rcon (F2) passwords for admin. If you don't set one, a random one will be -# created and shown in the terminal window of the server. -sv_rcon_password "" - -# rcon (F2) password for moderator. If you don't set one, none exists. -sv_rcon_mod_password "" - -# rcon (F2) password for helper. If you don't set one, none exists. -sv_rcon_helper_password "" - -# Map to start server with -sv_map "Sunny Side Up" - -# Whether this is a test server and rcon cheats are allowed. Also indicated in -# the server type, which is: -# - "DDraceNetwork" for 0 (no cheats) and -# - "TestDDraceNetwo" for 1 (cheats) -sv_test_cmds 1 - -# Register server (make it public) -sv_register 0 - - - -# ADVANCED OPTIONS -# ---------------- - -# File where server log will be stored -logfile "autoexec_server.log" - -# Folder where map records will be saved -sv_score_folder "records" - -# Max players on server -sv_max_clients 64 - -# Max players with the same IP address -sv_max_clients_per_ip 4 - -# Tournament mode - when enabled players joins the server as spectator -sv_tournament_mode 0 - -# Whether players can pause their character and make it disappear with the /spec command -sv_pauseable 0 - -# Allow /rescue (also /r) command so players can teleport themselves out of freeze -sv_rescue 1 -# Number of seconds between two rescues -sv_rescue_delay 5 - -# Enable ranks after rcon cheats have been used -sv_rank_cheats 1 - - - -# SERVER CUSTOMIZATION -# -------------------- - -# Message on chat displayed when joining -sv_welcome "Welcome to my server!" - -# File which will have the announcements (each one in new line) -sv_announcement_filename "announcement.txt" - -# Number of minutes before next announcement will be displayed (from the announcement file) -sv_announcement_interval 120 - -# Whether announcements will be displayed in their order or chosen randomly -sv_announcement_random 1 - -# Message of the day to display when joining the server (use "\n" to create new line) -sv_motd "Testserver with DDraceNetwork Features!\nDon't forget to check server rules by using /rules" - -# Use default DDRace rules -sv_ddrace_rules 1 - -# Own rules (up to 10 lines) -sv_rules_line1 "" -sv_rules_line2 "" -sv_rules_line3 "" - -# Reset physics tunes after a map change -sv_tune_reset 1 - -# Reset DDRace tunes after a map change -sv_ddrace_tune_reset 1 - -# Use a config file to execute whenever a map is changed -sv_reset_file "reset.cfg" - - - -# CUSTOM VOTES -# ------------ - -# Format: add_vote "[vote name]" "[command 1]; [command 2]; [command 3]; [...]" -# Example: add_vote "Close server" "sv_name Private DDNet server; password My secret password" -# -# To create empty line in votes just use space in name of vote and command -# "info". Every "empty line" vote should have different number of spaces in its -# name, because each vote text has to be unique. -# Example: add_vote " " "info" -# -# You can learn more about tunes on http://ddnet.tw/settingscommands/#tunings - -add_map_votes -add_vote " " "info" -add_vote "Option: Normal gravity" "tune gravity 0.50" -add_vote "Option: Moon gravity" "tune gravity 0.25" -add_vote "Option: No gravity" "tune gravity 0.00" -add_vote "  " "info" -add_vote "Option: Reset server" "exec autoexec_server.cfg" -add_vote "Option: Shutdown server" "shutdown" - - - -# ADDITIONAL COMMANDS PERMISSIONS -# ------------------------------- - -# You can see all commands which are accessible for specific authentication-levels by using "access_status" -# Format: access_status [0: admin, 1: moderator, 2: helper or 3: user] -# -# Format: access_level [command] [0: admin, 1: moderator, 2: helper or 3: user] -# Where 0 means only accessible for admin, 1 gives access to moderator and 2 gives access to helper -# Example: mod_command ban 1 - -# Non-default commands to which moderators and helpers will have access -access_level left 2 -access_level right 2 -access_level up 2 -access_level down 2 -access_level super 2 -access_level unsuper 2 -access_level tele 2 -access_level totele 2 -access_level totelecp 2 -access_level logout 2 -access_level ninja 2 -access_level grenade 2 -access_level shotgun 2 -access_level laser 2 -access_level weapons 2 -access_level unweapons 2 -access_level unlaser 2 -access_level unshotgun 2 -access_level ungrenade 2 -access_level unsolo 2 -access_level undeep 2 -access_level status 2 - -# commands for moderators only -access_level ban 1 -access_level unban 1 -access_level ban_range 1 -access_level unban_range 1 -access_level unban_all 1 -access_level bans 1 -access_level bans_save 1 -access_level kick 1 -access_level force_vote 1 -access_level moderate 1 - - - -# SPECIAL BROADCAST-SUGGESTION FOR PLAYERS -# ---------------------------------------- - -# Broadcast to display for players without DDNet client -sv_client_suggestion "Get DDNet client from DDNet.tw to use all features on DDNet!" - -# Broadcast to display for players with a very old version of DDNet client -sv_client_suggestion_old "Your DDNet client is old, update it on DDNet.tw!" - -# Broadcast to display for players with known botting client -sv_client_suggestion_bot "Your client has bots and can be remotely controlled!\nPlease use another client like DDNet client from DDNet.tw" - - - -# CUSTOM CONFIG -# ------------- - -# if you do not want updates to overwrite your settings create a -# file called myServerconfig.cfg and put your config there -exec myServerconfig.cfg diff -Nru ddnet-15.3.2/cmake/FindFFMPEG.cmake ddnet-15.5.4/cmake/FindFFMPEG.cmake --- ddnet-15.3.2/cmake/FindFFMPEG.cmake 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/cmake/FindFFMPEG.cmake 2021-06-20 09:38:48.000000000 +0000 @@ -6,7 +6,7 @@ pkg_check_modules(PC_SWSCALE libswscale) pkg_check_modules(PC_SWRESAMPLE libswresample) if(TARGET_OS STREQUAL "linux") - pkg_check_modules(PC_X264 libx264) + pkg_search_module(PC_X264 libx264 x264) endif() endif() diff -Nru ddnet-15.3.2/cmake/FindMySQL.cmake ddnet-15.5.4/cmake/FindMySQL.cmake --- ddnet-15.3.2/cmake/FindMySQL.cmake 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/cmake/FindMySQL.cmake 2021-06-20 09:38:48.000000000 +0000 @@ -39,30 +39,13 @@ set_extra_dirs_lib(MYSQL mysql) find_library(MYSQL_LIBRARY NAMES "mysqlclient" "mysqlclient_r" "mariadbclient" - HINTS ${HINTS_MYSQL_LIBDIR} ${MYSQL_CONFIG_LIBRARY_PATH} - PATHS ${PATHS_MYSQL_LIBDIR} + HINTS ${MYSQL_CONFIG_LIBRARY_PATH} ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} ) set_extra_dirs_include(MYSQL mysql "${MYSQL_LIBRARY}") find_path(MYSQL_INCLUDEDIR NAMES "mysql.h" - HINTS ${HINTS_MYSQL_INCLUDEDIR} ${MYSQL_CONFIG_INCLUDE_DIR} - PATHS ${PATHS_MYSQL_INCLUDEDIR} - ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} -) - -set_extra_dirs_lib(MYSQL_CPPCONN mysql) -find_library(MYSQL_CPPCONN_LIBRARY - NAMES "mysqlcppconn" "mysqlcppconn-static" - HINTS ${HINTS_MYSQL_CPPCONN_LIBDIR} ${MYSQL_CONFIG_LIBRARY_PATH} - PATHS ${PATHS_MYSQL_CPPCONN_LIBDIR} - ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} -) -set_extra_dirs_include(MYSQL_CPPCONN mysql "${MYSQL_CPPCONN_LIBRARY}") -find_path(MYSQL_CPPCONN_INCLUDEDIR - NAMES "mysql_connection.h" - HINTS ${HINTS_MYSQL_CPPCONN_INCLUDEDIR} ${MYSQL_CONFIG_INCLUDE_DIR} - PATHS ${PATHS_MYSQL_CPPCONN_INCLUDEDIR} + HINTS ${MYSQL_CONFIG_INCLUDE_DIR} ${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH} ) @@ -70,10 +53,8 @@ find_package_handle_standard_args(MySQL DEFAULT_MSG MYSQL_LIBRARY MYSQL_INCLUDEDIR) if(MYSQL_FOUND) - is_bundled(MYSQL_BUNDLED "${MYSQL_LIBRARY}") - - set(MYSQL_LIBRARIES ${MYSQL_LIBRARY} ${MYSQL_CPPCONN_LIBRARY}) - set(MYSQL_INCLUDE_DIRS ${MYSQL_INCLUDEDIR} ${MYSQL_CPPCONN_INCLUDEDIR}) + set(MYSQL_LIBRARIES ${MYSQL_LIBRARY}) + set(MYSQL_INCLUDE_DIRS ${MYSQL_INCLUDEDIR}) mark_as_advanced(MYSQL_INCLUDEDIR MYSQL_LIBRARY) endif() diff -Nru ddnet-15.3.2/CMakeLists.txt ddnet-15.5.4/CMakeLists.txt --- ddnet-15.3.2/CMakeLists.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/CMakeLists.txt 2021-06-20 09:38:48.000000000 +0000 @@ -60,6 +60,8 @@ set(TARGET_OS "linux") elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(TARGET_OS "mac") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Haiku") + set(TARGET_OS "haiku") endif() include(CheckCCompilerFlag) @@ -155,7 +157,7 @@ add_c_compiler_flag_if_supported(OUR_FLAGS -fcolor-diagnostics) endif() -if(NOT MSVC) +if(NOT MSVC AND NOT HAIKU) if(CMAKE_VERSION VERSION_LESS 3.1 OR TARGET_OS STREQUAL "mac") check_cxx_compiler_flag(-std=gnu++11 FLAG_SUPPORTED_std_gnu__11) if(FLAG_SUPPORTED_std_gnu__11) @@ -171,6 +173,9 @@ # -fstack-protector-all doesn't work on MinGW. add_c_compiler_flag_if_supported(OUR_FLAGS -fstack-protector-all) + # Disable exceptions as DDNet does not use them. + add_c_compiler_flag_if_supported(OUR_FLAGS -fno-exceptions) + # Inaccurate floating point numbers cause problems on mingw-w64-gcc when # compiling for x86, might cause problems elsewhere. So don't store floats # in registers but keep them at higher accuracy. @@ -209,7 +214,7 @@ #add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN "-Wuseless-cast") endif() -if(NOT MSVC) +if(NOT MSVC AND NOT HAIKU) check_c_compiler_flag("-O2;-Wp,-Werror;-D_FORTIFY_SOURCE=2" DEFINE_FORTIFY_SOURCE) # Some distributions define _FORTIFY_SOURCE by themselves. endif() @@ -535,11 +540,17 @@ find_library(OPENGL OpenGL) find_library(SECURITY Security) set(PLATFORM_CLIENT - src/osx/notifications.mm - src/osxlaunch/client.m + src/macos/notifications.mm + src/macoslaunch/client.m ) set(PLATFORM_CLIENT_LIBS ${COCOA} ${OPENGL}) set(PLATFORM_LIBS ${CARBON} ${SECURITY}) +elseif(TARGET_OS STREQUAL "haiku") + set(PLATFORM_CLIENT) + find_package(OpenGL) + set(PLATFORM_LIBS GL network) + set(PLATFORM_CLIENT_LIBS ${OPENGL_gl_LIBRARY}) + set(PLATFORM_CLIENT_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR}) else() find_package(Notify) find_package(OpenGL) @@ -1497,6 +1508,7 @@ server.h serverbrowser.h sound.h + sqlite.h steam.h storage.h textrender.h @@ -1560,7 +1572,8 @@ protocol_ex_msgs.h ringbuffer.cpp ringbuffer.h - serverbrowser.cpp + serverinfo.cpp + serverinfo.h snapshot.cpp snapshot.h storage.cpp @@ -1581,8 +1594,6 @@ collision.h ddracechat.h ddracecommands.h - extrainfo.cpp - extrainfo.h gamecore.cpp gamecore.h layers.cpp @@ -1636,6 +1647,7 @@ # Libraries set(LIBS ${CRYPTO_LIBRARIES} + ${SQLite3_LIBRARIES} ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS} @@ -1677,7 +1689,24 @@ add_library(${TARGET_STEAMAPI} ${STEAMAPI_KIND} ${STEAMAPI_SRC}) list(APPEND TARGETS_OWN ${TARGET_STEAMAPI}) - set_src(ENGINE_CLIENT GLOB src/engine/client + set_src(ENGINE_CLIENT GLOB_RECURSE src/engine/client + backend/glsl_shader_compiler.cpp + backend/glsl_shader_compiler.h + backend/opengl/backend_opengl.cpp + backend/opengl/backend_opengl.h + backend/opengl/backend_opengl3.cpp + backend/opengl/backend_opengl3.h + backend/opengl/opengl_sl.cpp + backend/opengl/opengl_sl.h + backend/opengl/opengl_sl_program.cpp + backend/opengl/opengl_sl_program.h + backend/opengles/backend_opengles.cpp + backend/opengles/backend_opengles.h + backend/opengles/backend_opengles3.cpp + backend/opengles/backend_opengles3.h + backend/opengles/gles_class_defines.h + backend/opengles/opengles_sl.cpp + backend/opengles/opengles_sl_program.cpp backend_sdl.cpp backend_sdl.h blocklist_driver.cpp @@ -1691,6 +1720,7 @@ friends.h ghost.cpp ghost.h + graphics_defines.h graphics_threaded.cpp graphics_threaded.h http.cpp @@ -1700,14 +1730,15 @@ keynames.h notifications.cpp notifications.h - opengl_sl.cpp - opengl_sl.h - opengl_sl_program.cpp - opengl_sl_program.h serverbrowser.cpp serverbrowser.h + serverbrowser_http.cpp + serverbrowser_http.h + serverbrowser_ping_cache.cpp + serverbrowser_ping_cache.h sound.cpp sound.h + sqlite.cpp steam.cpp text.cpp updater.cpp @@ -1807,6 +1838,8 @@ prediction/entity.h prediction/gameworld.cpp prediction/gameworld.h + projectile_data.cpp + projectile_data.h race.cpp race.h render.cpp @@ -1815,6 +1848,8 @@ skin.h ui.cpp ui.h + ui_ex.cpp + ui_ex.h ) set_src(GAME_EDITOR GLOB src/game/editor auto_map.cpp @@ -1887,7 +1922,7 @@ # Target set(TARGET_CLIENT ${CLIENT_EXECUTABLE}) - add_executable(${TARGET_CLIENT} + add_executable(${TARGET_CLIENT} WIN32 ${CLIENT_SRC} ${CLIENT_ICON} ${CLIENT_MANIFEST} @@ -1897,6 +1932,10 @@ ) target_link_libraries(${TARGET_CLIENT} ${LIBS_CLIENT}) + if(MSVC) + target_link_options(${TARGET_CLIENT} PRIVATE /ENTRY:mainCRTStartup) + endif() + target_include_directories(${TARGET_CLIENT} PRIVATE ${CURL_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIRS} @@ -1964,9 +2003,7 @@ databases/connection_pool.cpp databases/connection_pool.h databases/mysql.cpp - databases/mysql.h databases/sqlite.cpp - databases/sqlite.h name_ban.cpp name_ban.h register.cpp @@ -2012,7 +2049,6 @@ gamecontroller.h gamemodes/DDRace.cpp gamemodes/DDRace.h - gamemodes/gamemode.h gameworld.cpp gameworld.h player.cpp @@ -2051,7 +2087,6 @@ set(LIBS_SERVER ${LIBS} ${MYSQL_LIBRARIES} - ${SQLite3_LIBRARIES} ${TARGET_ANTIBOT} ${MINIUPNPC_LIBRARIES} # Add pthreads (on non-Windows) at the end, so that other libraries can depend @@ -2073,7 +2108,7 @@ list(APPEND TARGETS_LINK ${TARGET_SERVER}) if(TARGET_OS AND TARGET_OS STREQUAL "mac") - set(SERVER_LAUNCHER_SRC src/osxlaunch/server.mm) + set(SERVER_LAUNCHER_SRC src/macoslaunch/server.mm) set(TARGET_SERVER_LAUNCHER ${TARGET_SERVER}-Launcher) add_executable(${TARGET_SERVER_LAUNCHER} ${SERVER_LAUNCHER_SRC}) target_link_libraries(${TARGET_SERVER_LAUNCHER} ${COCOA}) @@ -2174,8 +2209,13 @@ json.cpp mapbugs.cpp name_ban.cpp + netaddr.cpp packer.cpp prng.cpp + secure_random.cpp + serverbrowser.cpp + serverinfo.cpp + sorted_array.cpp str.cpp strip_path_and_extension.cpp teehistorian.cpp @@ -2188,6 +2228,15 @@ set(TESTS_EXTRA src/engine/client/blocklist_driver.cpp src/engine/client/blocklist_driver.h + src/engine/client/http.cpp + src/engine/client/http.h + src/engine/client/serverbrowser.cpp + src/engine/client/serverbrowser.h + src/engine/client/serverbrowser_http.cpp + src/engine/client/serverbrowser_http.h + src/engine/client/serverbrowser_ping_cache.cpp + src/engine/client/serverbrowser_ping_cache.h + src/engine/client/sqlite.cpp src/engine/server/name_ban.cpp src/engine/server/name_ban.h src/game/server/teehistorian.cpp @@ -2202,8 +2251,8 @@ $ ${DEPS} ) - target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${GTEST_LIBRARIES}) - target_include_directories(${TARGET_TESTRUNNER} PRIVATE ${GTEST_INCLUDE_DIRS}) + target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${CURL_LIBRARIES} ${GTEST_LIBRARIES}) + target_include_directories(${TARGET_TESTRUNNER} PRIVATE ${CURL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER}) list(APPEND TARGETS_LINK ${TARGET_TESTRUNNER}) @@ -2332,7 +2381,6 @@ set(CPACK_SOURCE_FILES CMakeLists.txt README.md - autoexec_server.cfg cmake/ data/ datasrc/ @@ -2376,7 +2424,6 @@ set(CPACK_FILES license.txt storage.cfg - autoexec_server.cfg ${COPY_FILES} ) if(TARGET_OS STREQUAL "windows") Binary files /tmp/tmpsgiiycv7/JBPrrl94ZS/ddnet-15.3.2/data/assets/entities/comfort/ddnet.png and /tmp/tmpsgiiycv7/EShcP_Mk2k/ddnet-15.5.4/data/assets/entities/comfort/ddnet.png differ diff -Nru ddnet-15.3.2/data/autoexec_server.cfg ddnet-15.5.4/data/autoexec_server.cfg --- ddnet-15.3.2/data/autoexec_server.cfg 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/data/autoexec_server.cfg 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,208 @@ +# +# autoexec_server.cfg +# +# See https://ddnet.tw/settingscommands/ for all available settings. +# Everything following a # is considered a comment and ignored by the server. +# When an option can be enabled or disabled, it's enabled with 1, disabled with 0. +# +# SEE CUSTOM CONFIG AT THE END TO PREVENT DDNET UPDATES FROM OVERWRITING YOUR SETTINGS + + + +# GENERAL OPTIONS +# --------------- + +# Server port (only port range 8303-8310 show up in LAN tab, +# defaults to 0 to automatically select free port in range 8303-8310) +#sv_port 8303 + +# Server name +sv_name "My DDNet server" + +# Password for joining the server, empty for no password +password "" + +# rcon (F2) passwords for admin. If you don't set one, a random one will be +# created and shown in the terminal window of the server. +sv_rcon_password "" + +# rcon (F2) password for moderator. If you don't set one, none exists. +sv_rcon_mod_password "" + +# rcon (F2) password for helper. If you don't set one, none exists. +sv_rcon_helper_password "" + +# Map to start server with +sv_map "Sunny Side Up" + +# Whether this is a test server and rcon cheats are allowed. Also indicated in +# the server type, which is: +# - "DDraceNetwork" for 0 (no cheats) and +# - "TestDDraceNetwo" for 1 (cheats) +sv_test_cmds 1 + +# Register server (make it public) +sv_register 0 + + + +# ADVANCED OPTIONS +# ---------------- + +# File where server log will be stored +logfile "autoexec_server.log" + +# Folder where map records will be saved +sv_score_folder "records" + +# Max players on server +sv_max_clients 64 + +# Max players with the same IP address +sv_max_clients_per_ip 4 + +# Tournament mode - when enabled players joins the server as spectator +sv_tournament_mode 0 + +# Whether players can pause their character and make it disappear with the /spec command +sv_pauseable 0 + +# Allow /rescue (also /r) command so players can teleport themselves out of freeze +sv_rescue 1 +# Number of seconds between two rescues +sv_rescue_delay 5 + +# Enable ranks after rcon cheats have been used +sv_rank_cheats 1 + + + +# SERVER CUSTOMIZATION +# -------------------- + +# Message on chat displayed when joining +sv_welcome "Welcome to my server!" + +# File which will have the announcements (each one in new line) +sv_announcement_filename "announcement.txt" + +# Number of minutes before next announcement will be displayed (from the announcement file) +sv_announcement_interval 120 + +# Whether announcements will be displayed in their order or chosen randomly +sv_announcement_random 1 + +# Message of the day to display when joining the server (use "\n" to create new line) +sv_motd "Testserver with DDraceNetwork Features!\nDon't forget to check server rules by using /rules" + +# Use default DDRace rules +sv_ddrace_rules 1 + +# Own rules (up to 10 lines) +sv_rules_line1 "" +sv_rules_line2 "" +sv_rules_line3 "" + +# Reset physics tunes after a map change +sv_tune_reset 1 + +# Reset DDRace tunes after a map change +sv_ddrace_tune_reset 1 + +# Use a config file to execute whenever a map is changed +sv_reset_file "reset.cfg" + + + +# CUSTOM VOTES +# ------------ + +# Format: add_vote "[vote name]" "[command 1]; [command 2]; [command 3]; [...]" +# Example: add_vote "Close server" "sv_name Private DDNet server; password My secret password" +# +# To create empty line in votes just use space in name of vote and command +# "info". Every "empty line" vote should have different number of spaces in its +# name, because each vote text has to be unique. +# Example: add_vote " " "info" +# +# You can learn more about tunes on http://ddnet.tw/settingscommands/#tunings + +add_map_votes +add_vote " " "info" +add_vote "Option: Normal gravity" "tune gravity 0.50" +add_vote "Option: Moon gravity" "tune gravity 0.25" +add_vote "Option: No gravity" "tune gravity 0.00" +add_vote "  " "info" +add_vote "Option: Reset server" "exec autoexec_server.cfg" +add_vote "Option: Shutdown server" "shutdown" + + + +# ADDITIONAL COMMANDS PERMISSIONS +# ------------------------------- + +# You can see all commands which are accessible for specific authentication-levels by using "access_status" +# Format: access_status [0: admin, 1: moderator, 2: helper or 3: user] +# +# Format: access_level [command] [0: admin, 1: moderator, 2: helper or 3: user] +# Where 0 means only accessible for admin, 1 gives access to moderator and 2 gives access to helper +# Example: mod_command ban 1 + +# Non-default commands to which moderators and helpers will have access +access_level left 2 +access_level right 2 +access_level up 2 +access_level down 2 +access_level super 2 +access_level unsuper 2 +access_level tele 2 +access_level totele 2 +access_level totelecp 2 +access_level logout 2 +access_level ninja 2 +access_level grenade 2 +access_level shotgun 2 +access_level laser 2 +access_level weapons 2 +access_level unweapons 2 +access_level unlaser 2 +access_level unshotgun 2 +access_level ungrenade 2 +access_level unsolo 2 +access_level undeep 2 +access_level status 2 + +# commands for moderators only +access_level ban 1 +access_level unban 1 +access_level ban_range 1 +access_level unban_range 1 +access_level unban_all 1 +access_level bans 1 +access_level bans_save 1 +access_level kick 1 +access_level force_vote 1 +access_level moderate 1 + + + +# SPECIAL BROADCAST-SUGGESTION FOR PLAYERS +# ---------------------------------------- + +# Broadcast to display for players without DDNet client +sv_client_suggestion "Get DDNet client from DDNet.tw to use all features on DDNet!" + +# Broadcast to display for players with a very old version of DDNet client +sv_client_suggestion_old "Your DDNet client is old, update it on DDNet.tw!" + +# Broadcast to display for players with known botting client +sv_client_suggestion_bot "Your client has bots and can be remotely controlled!\nPlease use another client like DDNet client from DDNet.tw" + + + +# CUSTOM CONFIG +# ------------- + +# if you do not want updates to overwrite your settings create a +# file called myServerconfig.cfg and put your config there +exec myServerconfig.cfg diff -Nru ddnet-15.3.2/data/languages/arabic.txt ddnet-15.5.4/data/languages/arabic.txt --- ddnet-15.3.2/data/languages/arabic.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/arabic.txt 2021-06-20 09:38:48.000000000 +0000 @@ -345,9 +345,6 @@ Refresh == ﺚﻳﺪﺤﺗ -Refreshing master servers -== ﺕﺍﺮﻓﺮﻴﺴﻟﺍ ﻞﻴﻤﺤﺗ ﺓﺩﺎﻋﺍ - Remote console == ﻦﻣﺩﻻﺍ ﻞﺴﻧﻮﻛ @@ -411,9 +408,6 @@ Show only chat messages from friends == ﺀﺎﻗﺪﺻﻻﺍ ﻦﻣ ﻂﻘﻓ ﻞﺋﺎﺳﺮﻟﺍ ﺭﺎﻬﻇﺍ -Show only supported -== ﻂﻘﻓ ﻡﻮﻋﺪﻣ ﺽﺮﻋ - Skins == ﺕﺎﻨﻜﺴﻟﺍ @@ -861,9 +855,6 @@ Max CSVs == Max CSVs -Borderless window -== ﺩﻭﺪﺣ ﻼﺑ ﺓﺬﻓﺎﻧ - Best == ﻞﻀﻓﺍ @@ -903,9 +894,6 @@ Indicate map finish == ﺔﻗﺎﻄﺒﻟﺍ ﺭﺎﺼﺘﻧﺍ ﺭﺎﻬﻇﺇ -Use OpenGL 3.3 (experimental) -== ( ﺔﻳﻮﻘﻟﺍ ﺓﺰﻬﺟﻼﻟ ) OpenGL 3.3 ﻡﺍﺪﺨﺘﺳﺍ - File already exists, do you want to overwrite it? == ﻪﻗﻮﻓ ﺔﺑﺎﺘﻜﻟﺍ ﺪﻳﺮﺗ ﻞﻫ ، ﻞﻌﻔﻟﺎﺑ ﺩﻮﺟﻮﻣ ﻒﻠﻤﻟﺍ @@ -1237,18 +1225,51 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == -45° aim +AFR +== + +ASI +== + +AUS +== + +EUR == -Deepfly on +NA == -Deepfly off +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Sample rate == @@ -1261,6 +1282,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1275,3 +1308,9 @@ DDNet %s is available: == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/belarusian.txt ddnet-15.5.4/data/languages/belarusian.txt --- ddnet-15.3.2/data/languages/belarusian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/belarusian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -347,9 +347,6 @@ Refresh == Абнавіць -Refreshing master servers -== Абнаўленне спісу майстар-сервераў - Remote console == Кансоль сервера @@ -413,9 +410,6 @@ Show name plates == Паказваць нікі гульцоў -Show only supported -== Паказваць толькі падтрымоўваныя дазволы экрана - Skins == Скіны @@ -739,6 +733,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -772,6 +790,9 @@ Types == +Leak IP +== + Select a name == @@ -943,22 +964,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -967,7 +988,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1054,6 +1075,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1186,6 +1219,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/bosnian.txt ddnet-15.5.4/data/languages/bosnian.txt --- ddnet-15.3.2/data/languages/bosnian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/bosnian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -350,9 +350,6 @@ Refresh == Osvježi -Refreshing master servers -== Osvježavam glavne servere - Remote console == Udaljena konzola @@ -416,9 +413,6 @@ Show name plates == Prikaži imena igrača -Show only supported -== Prikaži samo podržane - Skins == Skinovi @@ -901,9 +895,6 @@ Screen == Ekran -Use OpenGL 3.3 (experimental) -== Koristi OpenGL 3.3 (eksperimentalno) - Preinit VBO (iGPUs only) == Inicijalizuj VBO (samo iGPU-ovi) @@ -1117,6 +1108,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -1129,6 +1144,9 @@ %d player == +Leak IP +== + %.2f MiB == @@ -1159,19 +1177,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1201,6 +1222,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1237,6 +1270,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/languages/brazilian_portuguese.txt ddnet-15.5.4/data/languages/brazilian_portuguese.txt --- ddnet-15.3.2/data/languages/brazilian_portuguese.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/brazilian_portuguese.txt 2021-06-20 09:38:48.000000000 +0000 @@ -20,6 +20,7 @@ # Rafael Fontenelle 2020-08-27 12:38:07 # Rafael Fontenelle 2020-09-20 11:00:38 # Rafael Fontenelle 2021-02-13 07:00:00 +# Rafael Fontenelle 2021-06-10 12:00:00 ##### /authors ##### ##### translated strings ##### @@ -367,9 +368,6 @@ Refresh == Atualizar -Refreshing master servers -== Atualizando servidores mestres - Remote console == Console remoto @@ -436,9 +434,6 @@ Show only chat messages from friends == Mostrar apenas mensagens de amigos -Show only supported -== Mostrar apenas modos suportados - Skins == Skins @@ -547,9 +542,6 @@ You must restart the game for all settings to take effect. == Você deve reiniciar o jogo para que todas as alterações tenham efeito. -Borderless window -== Janela sem borda - Demo == Demo @@ -931,9 +923,6 @@ Multiple texture units (disable for MacOS) == Várias unidades de textura (desabilite para macOS) -Use OpenGL 3.3 (experimental) -== Usar OpenGL 3.3 (experimental) - UI mouse s. == Sens. mouse de UI @@ -1262,15 +1251,6 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == A largura da textura %s não é divisível por %d ou a altura não é divisível por %d, o que pode causar erros visuais. -45° aim -== Mirar em 45º - -Deepfly on -== Ativar deepfly - -Deepfly off -== Desativar deepfly - Dummy == Dummy @@ -1294,3 +1274,63 @@ Regular Background Color == Cor de fundo comum + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== NA + +SA +== SA + +CHN +== CHN + +Getting server list from master server +== Obtendo lista de servidores do servidor mestre + +Leak IP +== Vazar IP + +Chat command +== Comando de chat + +Windowed +== Janela + +Windowed borderless +== Janela sem borda + +Desktop fullscreen +== Tela cheia de desktop + +Use modern OpenGL +== Usar OpenGL moderno + +Hookline +== Linha do gancho + +No hit +== Sem acerto + +Hookable +== Enganchável + +Tee +== Tee + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord diff -Nru ddnet-15.3.2/data/languages/bulgarian.txt ddnet-15.5.4/data/languages/bulgarian.txt --- ddnet-15.3.2/data/languages/bulgarian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/bulgarian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -347,9 +347,6 @@ Refresh == Обнови -Refreshing master servers -== Обновновявам мастър-сървърите - Remote console == Сървър Конзола @@ -413,9 +410,6 @@ Show name plates == Показвай лентите с имената -Show only supported -== Показвай само съвместимите - Skins == Модели @@ -736,6 +730,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -769,6 +787,9 @@ Types == +Leak IP +== + Select a name == @@ -943,22 +964,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -967,7 +988,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1054,6 +1075,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1186,6 +1219,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/catalan.txt ddnet-15.5.4/data/languages/catalan.txt --- ddnet-15.3.2/data/languages/catalan.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/catalan.txt 2021-06-20 09:38:48.000000000 +0000 @@ -345,9 +345,6 @@ Refresh == Actualitzar -Refreshing master servers -== Actualitzant servidors principals - Remote console == Consola remota @@ -414,9 +411,6 @@ Show only chat messages from friends == Rebre només missatges d'amics -Show only supported -== Veure únicament modes compatibles - Skins == Skins @@ -525,9 +519,6 @@ You must restart the game for all settings to take effect. == Has de reiniciar el joc perquè els canvis tinguin efecte. -Borderless window -== Pantalla sense finestra - Demo == Repetició @@ -909,9 +900,6 @@ Multiple texture units (disable for MacOS) == Multiple texture units (disable for MacOS) -Use OpenGL 3.3 (experimental) -== Utilitzar OpenGL 3.3 (experimental) - UI mouse s. == UI mouse s. @@ -1149,6 +1137,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -1161,6 +1173,9 @@ %d player == +Leak IP +== + Demos directory == @@ -1182,16 +1197,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1215,6 +1236,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1248,6 +1281,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/languages/chuvash.txt ddnet-15.5.4/data/languages/chuvash.txt --- ddnet-15.3.2/data/languages/chuvash.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/chuvash.txt 2021-06-20 09:38:48.000000000 +0000 @@ -413,9 +413,6 @@ Show name plates == Вылякансен ят кăтартма -Show only supported -== Ĕçлекен экран режимĕсем анчах - Skins == Сăнсем diff -Nru ddnet-15.3.2/data/languages/czech.txt ddnet-15.5.4/data/languages/czech.txt --- ddnet-15.3.2/data/languages/czech.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/czech.txt 2021-06-20 09:38:48.000000000 +0000 @@ -346,9 +346,6 @@ Refresh == Obnovit -Refreshing master servers -== Aktualizování hlavních serverů - Remote console == Vzdálená konzola @@ -412,9 +409,6 @@ Show name plates == Zobrazovat jména hráčů -Show only supported -== Zobrazit pouze podporované - Skins == Vzhledy @@ -888,18 +882,12 @@ UI mouse s. == UI mouse s. -Borderless window -== Okno bez okrajů - may cause delay == může způsobit zpoždění Screen == Obrazovka -Use OpenGL 3.3 (experimental) -== Použít OpenGL 3.3 (experimentální) - Preinit VBO (iGPUs only) == Preinit VBO (pouze iGPU) @@ -1250,18 +1238,51 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == -45° aim +AFR +== + +ASI +== + +AUS +== + +EUR == -Deepfly on +NA == -Deepfly off +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Show client IDs == @@ -1271,6 +1292,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1282,3 +1315,9 @@ Regular Background Color == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/danish.txt ddnet-15.5.4/data/languages/danish.txt --- ddnet-15.3.2/data/languages/danish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/danish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -348,9 +348,6 @@ Refresh == Opdater -Refreshing master servers -== Opdaterer masterservere - Remote console == Serverkonsol @@ -414,9 +411,6 @@ Show name plates == Vis navneskilte -Show only supported -== Vis kun understøttede - Skins == Udseende @@ -941,18 +935,12 @@ UI mouse s. == UI mus s. -Borderless window -== Kantløs vindue - may cause delay == kan forårsage forsinkelse Screen == Skærm -Use OpenGL 3.3 (experimental) -== Brug OpenGL 3.3 (eksperimentel) - Preinit VBO (iGPUs only) == Præinit VBO (iGPUs alene) @@ -1248,18 +1236,51 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == -45° aim +AFR +== + +ASI +== + +AUS +== + +EUR == -Deepfly on +NA == -Deepfly off +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Show client IDs == @@ -1269,6 +1290,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1280,3 +1313,9 @@ Regular Background Color == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/dutch.txt ddnet-15.5.4/data/languages/dutch.txt --- ddnet-15.3.2/data/languages/dutch.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/dutch.txt 2021-06-20 09:38:48.000000000 +0000 @@ -359,9 +359,6 @@ Refresh == Vernieuwen -Refreshing master servers -== Masterservers vernieuwen - Remote console == Remote console @@ -425,9 +422,6 @@ Show name plates == Laat naamplaat zien -Show only supported -== Laat alleen compatibele servers zien - Skins == Skins @@ -909,18 +903,12 @@ UI mouse s. == UI mouse s. -Borderless window -== Borderless window - may cause delay == kan voor vertraging zorgen Screen == Scherm -Use OpenGL 3.3 (experimental) -== Gebruik OpgenGL 3.3 (experimenteel) - Preinit VBO (iGPUs only) == Preinit VBO (alleen iGPUs) @@ -1161,6 +1149,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -1173,6 +1185,9 @@ %d player == +Leak IP +== + Demos directory == @@ -1194,16 +1209,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1227,6 +1248,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1260,6 +1293,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/languages/finnish.txt ddnet-15.5.4/data/languages/finnish.txt --- ddnet-15.3.2/data/languages/finnish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/finnish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -348,9 +348,6 @@ Refresh == Päivitä -Refreshing master servers -== Päivitetään isäntäpalvelimia - Remote console == Etäkonsoli @@ -414,9 +411,6 @@ Show name plates == Näytä nimikyltit -Show only supported -== Näytä vain tuetut - Skins == Ulkonäkö @@ -740,6 +734,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -773,6 +791,9 @@ Types == +Leak IP +== + Select a name == @@ -944,22 +965,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -968,7 +989,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1055,6 +1076,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1187,6 +1220,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/french.txt ddnet-15.5.4/data/languages/french.txt --- ddnet-15.3.2/data/languages/french.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/french.txt 2021-06-20 09:38:48.000000000 +0000 @@ -17,6 +17,8 @@ # SunnyPistache & Pipou 2020-07-09 21:09:00 # SunnyPistache & Pipou 2020-10-03 09:29:00 # SunnyPistache & Pipou 2020-11-07 11:30:00 +# Chairn 2021-05-31 20:00:00 +# T tee 2021-06-01 00:39:00 ##### /authors ##### ##### translated strings ##### @@ -70,7 +72,7 @@ == Enregistrer les démos automatiquement Automatically take game over screenshot -== Prends une capture d'écran de fin de partie automatiquement +== Prendre une capture d'écran de fin de partie automatiquement Blue team == Équipe bleue @@ -361,9 +363,6 @@ Refresh == Rafraîchir -Refreshing master servers -== Rafraîchissement des serveurs maîtres - Remote console == Console serveur @@ -427,9 +426,6 @@ Show name plates == Afficher les pseudonymes -Show only supported -== N'afficher que les résolutions supportées - Skins == Skins @@ -652,7 +648,7 @@ == Activer les sons du jeu DDNet Client needs to be restarted to complete update! -== Le client DDNet a besoin d'être redémarré pour finir la MAJ! +== Le client DDNet a besoin d'être redémarré pour finir la MAJ ! Toggle dummy == Basculer vers le dummy @@ -673,7 +669,7 @@ == Skins enrobés (DDFat) Automatically take statboard screenshot -== Prends une capture d'écran du tableau des stats automatiquement +== Prendre une capture d'écran du tableau des stats automatiquement Clan plates size == Taille du clan @@ -727,7 +723,7 @@ == AntiPing: prédit le déplacement des autres joueurs Are you sure that you want to disconnect your dummy? -== Êtes vous sûr de vouloir déconnecter votre dummy? +== Êtes vous sûr de vouloir déconnecter votre dummy ? Reload == Recharger @@ -744,9 +740,6 @@ Skin prefix == Préfixe de skin -Use OpenGL 3.3 (experimental) -== Utiliser OpenGL 3.3 (expérimental) - Date == Date @@ -817,13 +810,13 @@ == Config. du dummy Are you sure that you want to disconnect? -== Êtes-vous sûr de vouloir vous déconnecter? +== Êtes-vous sûr de vouloir vous déconnecter ? Deactivate == Désactiver Update failed! Check log... -== La mise à jour a échoué! Vérifier les logs... +== La mise à jour a échoué ! Vérifier les logs... %.2f KiB == %.2f Kio @@ -835,7 +828,7 @@ == Chargement de sons enfilés File already exists, do you want to overwrite it? -== Ce fichier existe déjà, voulez-vous l'écraser? +== Ce fichier existe déjà, voulez-vous l'écraser ? Grabs == Grappins @@ -849,9 +842,6 @@ Converse == Converser -Borderless window -== Mode fenêtré sans bordure - Hook collisions == Collision du grappin @@ -901,7 +891,7 @@ == Démo Map sound volume -== Volume des sons de la map +== Volume des sons de la carte Time == Temps @@ -1108,13 +1098,13 @@ == Reconnecte dans %d sec DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you. -== DDRaceNetwork est un jeu coopératif en ligne dont l'objectif est, pour vous ainsi que votre groupe de tees, d'atteindre la ligne d'arrivée d'une map. En tant que nouveau joueur, il est préférable que vous commenciez sur les serveurs Novice, qui sont constitués des cartes les plus simples. Prenez en compte le ping afin de choisir un serveur proche de chez vous. +== DDRaceNetwork est un jeu coopératif en ligne dont l'objectif est, pour vous ainsi que votre groupe de tees, d'atteindre la ligne d'arrivée d'une carte. En tant que nouveau joueur, il est préférable que vous commenciez sur les serveurs Novice, qui sont constitués des cartes les plus simples. Prenez en compte le ping afin de choisir un serveur proche de chez vous. The maps contain freeze, which temporarily make a tee unable to move. You have to work together to get through these parts. -== Les maps contiennent du freeze (gèle), ce qui vous empêche temporairement de bouger. Vous devez travailler en équipe pour passer ces parties. +== Les cartes contiennent du freeze (gel), ce qui vous empêche temporairement de bouger. Vous devez travailler en équipe pour passer ces parties. The mouse wheel changes weapons. Hammer (left mouse) can be used to hit other tees and wake them up from being frozen. -== La molette de la souris permet de changer d'arme. Le marteau (clic gauche) peut être utilisé pour frapper les autres tees ce qui les unfreeze (dégivre). +== La molette de la souris permet de changer d'arme. Le marteau (clic gauche) peut être utilisé pour frapper les autres tees ce qui les unfreeze (dégèle). Hook (right mouse) can be used to swing through the map and to hook other tees to you. == Le grappin (clic droit) peut être utilisé pour se balancer tout le long de la carte en attrappant les murs et peut aussi permettre d'attrapper les autres Tees. @@ -1144,7 +1134,7 @@ == Appuyez sur "k" pour mourir (recommencer) et "a" pour vous mettre en pause afin de regarder les autres joueurs. Allez dans les configurations pour voir davantage de raccourcis. Country / Region -== Pays / Region +== Pays / Région Speed == Vitesse @@ -1204,7 +1194,7 @@ == Émoticônes Particles -== Particles +== Particules Assets directory == Répertoire des assets @@ -1271,37 +1261,88 @@ == Replay The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +== La largeure de la texture %s n'est pas divisible par %d, ou la hauteur n'est pas divisible par %d, ce qui peut causer des bugs visuels. + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== AN + +SA +== AS + +CHN +== CHN -45° aim -== +Getting server list from master server +== Obtenir la liste des serveurs depuis le serveur maître -Deepfly on -== +Leak IP +== Fuiter l'IP -Deepfly off -== +Chat command +== Commande de chat Dummy -== +== Dummy + +Windowed +== Fenêtré + +Windowed borderless +== Fenêtré sans bordure + +Desktop fullscreen +== Plein écran + +Use modern OpenGL +== Utiliser OpenGL moderne Show client IDs -== +== Montrer les IDs Laser Outline Color -== +== Couleur de la bordure du laser Laser Inner Color -== +== Couleur du laser + +Hookline +== Ligne de visée + +No hit +== Pas de frappe + +Hookable +== Accrochable + +Tee +== Tee Preview -== +== Prévisualisation Background -== +== Arrière-plan Entities Background color -== +== Couleur de l'arrière-plan des entités Regular Background Color -== +== Couleur de l'arrière-plan + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord diff -Nru ddnet-15.3.2/data/languages/german.txt ddnet-15.5.4/data/languages/german.txt --- ddnet-15.3.2/data/languages/german.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/german.txt 2021-06-20 09:38:48.000000000 +0000 @@ -364,9 +364,6 @@ Refresh == Aktualisieren -Refreshing master servers -== Aktualisiere Masterserver - Remote console == Remote-Konsole @@ -433,9 +430,6 @@ Show only chat messages from friends == Nur Nachrichten von Freunden zeigen -Show only supported -== Nur Unterstützte anzeigen - Skins == Skins @@ -886,9 +880,6 @@ Max CSVs == Maximale CSVs -Borderless window -== Randloses Fenster - Best == Beste @@ -928,9 +919,6 @@ Indicate map finish == Kartensieg anzeigen -Use OpenGL 3.3 (experimental) -== OpenGL 3.3 benutzen (experimentell) - File already exists, do you want to overwrite it? == Datei existiert bereits, willst du sie überschreiben? @@ -1262,15 +1250,6 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == Die Breite von Textur %s ist nicht durch %d teilbar, oder die Höhe nicht durch %d teilbar, was Visuelle Fehler verursachen könnte. -45° aim -== 45°-Zielen - -Deepfly on -== Deepfly an - -Deepfly off -== Deepfly aus - Dummy == Dummy @@ -1294,3 +1273,63 @@ Regular Background Color == Normale Hintergrundfarbe + +Chat command +== Chat-Befehl + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== NA + +SA +== SA + +CHN +== CHN + +Getting server list from master server +== Hole Serverliste vom Masterserver + +Leak IP +== IP leaken + +Windowed +== Fenster + +Windowed borderless +== Fenster ohne Rand + +Desktop fullscreen +== Desktop-Vollbild + +Use modern OpenGL +== Modernes OpenGL benutzen + +Hookline +== Hooklinie + +No hit +== Kein Treffer + +Hookable +== Hookbar + +Tee +== Tee diff -Nru ddnet-15.3.2/data/languages/greek.txt ddnet-15.5.4/data/languages/greek.txt --- ddnet-15.3.2/data/languages/greek.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/greek.txt 2021-06-20 09:38:48.000000000 +0000 @@ -419,9 +419,6 @@ Show only chat messages from friends == Προβολή μηνυμάτων μόνο από φίλους -Show only supported -== Προβολή υποστηριζόμενων μόνο - Skins == Στολές diff -Nru ddnet-15.3.2/data/languages/hungarian.txt ddnet-15.5.4/data/languages/hungarian.txt --- ddnet-15.3.2/data/languages/hungarian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/hungarian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -306,7 +306,7 @@ [Demo browser] Play -== Játék +== Lejátszás Player == Játékos @@ -338,9 +338,6 @@ Refresh == Újratöltés -Refreshing master servers -== Mester szerverek frissítése - Remote console == Távoli konzol @@ -404,11 +401,8 @@ Show name plates == Név táblák mutatása -Show only supported -== Csak támogatott mutatása - Skins -== Skinek +== Karakterek Sound == Hang @@ -562,7 +556,7 @@ == Demó felvétele Your skin -== Te skined +== Te Karaktered Size: == Méret: @@ -607,7 +601,7 @@ == Típus: Dummy settings -== Dummy beállításai +== Másolat Beállításai AntiPing == Antiping @@ -790,7 +784,7 @@ == Öngyilkosságok Player country: -== Játékos országa +== Játékos országa: Net == Net @@ -816,9 +810,6 @@ Max CSVs == Maximum CSV-k -Borderless window -== Ablak mód - %.2f KiB == %.2f KiB @@ -918,9 +909,6 @@ Multiple texture units (disable for MacOS) == Több textúra egységek (kapcsolja ki MacOS esetén) -Use OpenGL 3.3 (experimental) -== OpenGL 3.3 használata (Haladó) - UI mouse s. == UI egér sebesség @@ -934,7 +922,7 @@ == Mentés Vanilla skins only -== Csak alap skinek +== Csak vanilla skinek Date == Idő @@ -949,7 +937,7 @@ == újratöltés Hammerfly dummy -== Dummy Kalapács +== Másolat Kalapács Downloading %s: == Letöltés %s: @@ -970,10 +958,10 @@ == Videó neve: Connect Dummy -== Dummy Csatlakoztatása +== Másolat Csatlakoztatása Disconnect Dummy -== Dummy Lecsatlakoztatása +== Másolat Lecsatlakoztatása Filter connecting players == Csatlakozók szűrése @@ -982,7 +970,7 @@ == Nézetváltás Toggle dummy -== Dummy irányítása +== Másolat irányítása Fetch Info == Minden infó @@ -1051,7 +1039,7 @@ == A kiválasztott helynél ez létezik Server best: -== Legjobb szerver: +== Szerver legjobb: Markers: == Jelölések @@ -1066,10 +1054,10 @@ == A pályákon fagyasztó részeket találtok, melyek ideiglenesen blokkolják a játékos mozgását. Együtt kell dolgoznod a pályarészekkel az átjutáshoz. The mouse wheel changes weapons. Hammer (left mouse) can be used to hit other tees and wake them up from being frozen. -== A görgő megváltoztatja a fegyvereket. A kalapács (bal egérgomb) segítségével más Tee-ket megüthetsz és ütésekkel kiolvadhatnak a fagyásból. +== A görgő megváltoztatja a fegyvereket. A kalapács (bal egérgomb) segítségével más Játékosokat megüthetsz és ütésekkel kiolvadhatnak a fagyásból. Hook (right mouse) can be used to swing through the map and to hook other tees to you. -== A horoggal (jobb egérgomb) megkapaszkodhatsz néhány platformon és más Tee-ket is megragadhatsz vele. +== A horoggal (jobb egérgomb) megkapaszkodhatsz néhány platformon és más Játékosokat is megragadhatsz vele. Most importantly communication is key: There is no tutorial so you'll have to chat (t key) with other players to learn the basics and tricks of the game. == A legfontosabb kulcs a sikerhez: Nincs oktatóprogram, így mindenki magától tanulja meg más játékosok által. (T gombbal tudsz Chattelni) @@ -1153,25 +1141,25 @@ == Demók Helye Smooth Dynamic Camera -== Egyenletesen dinamikus kamera +== Egyenletes dinamikus kamera Skip the main menu -== főmenű kihagyása +== Főmenű kihagyása Themes directory == Témák Helye Download skins -== Skinek letöltése +== Karakterek letöltése Skin prefix == Karakter prefix Skin Database -== Több skin +== Karakter Adatbázis Skins directory -== Skinek Helye +== Karakterek Helye Game sound volume == Játék Hangerő @@ -1186,7 +1174,7 @@ == Kinézet Use old chat style -== régi chat stílus használata +== Régi chat stílus használata Client message == Kliens Üzenet @@ -1204,7 +1192,7 @@ == Hangulatjelek Particles -== effektek +== Effektek Assets directory == Kinézet Helye @@ -1229,7 +1217,7 @@ [Start menu] Play -== Játszás +== Játék Manual == Manuális @@ -1244,39 +1232,90 @@ == Visszajátszás The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. +== A Struktúra Szélessége %s nem osztható %d al, vagy a magasság nem osztható %d al, ami vizuális hibákhoz vezethet. + +AFR == -45° aim +ASI == -Deepfly on +AUS == -Deepfly off +EUR == -Dummy +NA == -Show client IDs +SA == -Laser Outline Color +CHN == +Getting server list from master server +== Szerver Lista Lekérése + +Leak IP +== IP Mutatása + +Chat command +== Chat Parancs + +Dummy +== Másolat + +Windowed +== Ablak + +Windowed borderless +== Keretnélküli Ablak + +Desktop fullscreen +== Asztalos Teljesképernyő + +Use modern OpenGL +== Modern OpenGL Használata + +Show client IDs +== Kliens ID-k Mutatása + +Laser Outline Color +== Lézer Körvonal Szín + Laser Inner Color -== +== Lézer Belső Szín + +Hookline +== Horogvonal + +No hit +== Nincs Találat + +Hookable +== Horgolható + +Tee +== Játékos Találat Preview -== +== Előnézet Background -== +== Háttér Entities Background color -== +== Entitás Háttér Szín Regular Background Color +== Átlagos Háttér Szín + +Discord +== + +https://ddnet.tw/discord == https://wiki.ddnet.tw/ diff -Nru ddnet-15.3.2/data/languages/italian.txt ddnet-15.5.4/data/languages/italian.txt --- ddnet-15.3.2/data/languages/italian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/italian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -352,9 +352,6 @@ Refresh == Aggiorna -Refreshing master servers -== Aggiornamento server principali - Remote console == Console remota @@ -418,9 +415,6 @@ Show name plates == Mostra nomi -Show only supported -== Mostra solo supportati - Skins == Skin @@ -973,18 +967,12 @@ UI mouse s. == Sensibilità mouse nei menu -Borderless window -== Finestra senza bordi - may cause delay == Può esserci ritardo Screen == Schermo -Use OpenGL 3.3 (experimental) -== Usa OpenGL 3.3 (sperimentale) - Preinit VBO (iGPUs only) == Preinit VBO (solo iGPU) @@ -1266,6 +1254,33 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + Markers: == @@ -1278,16 +1293,22 @@ Converse == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Show client IDs @@ -1299,6 +1320,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1314,5 +1347,11 @@ Regular Background Color == +Discord +== + +https://ddnet.tw/discord +== + FPM == diff -Nru ddnet-15.3.2/data/languages/japanese.txt ddnet-15.5.4/data/languages/japanese.txt --- ddnet-15.3.2/data/languages/japanese.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/japanese.txt 2021-06-20 09:38:48.000000000 +0000 @@ -2,13 +2,13 @@ #originally created by: # orange #modified by: -# +# 2021-06-04 Eana Hufwe ##### /authors ##### ##### translated strings ##### %ds left -== 残り %ds +== 残り %d 秒 %i minute left == 残り %i 分 @@ -26,7 +26,7 @@ == %s の勝ち! -Page %d- -== -%d ページ- +== -ページ %d- Abort == 中止 @@ -68,10 +68,10 @@ == 体 Call vote -== 投票を開始する +== 投票作成 Change settings -== 設定を変える +== 設定変更 Chat == チャット @@ -125,7 +125,7 @@ == デモファイル: %s Demos -== デモ +== デモ録画 Disconnect == 切断 @@ -140,10 +140,10 @@ == 引き分け! Dynamic Camera -== ダイナミックカメラ +== 動的カメラ Emoticon -== エモティコン +== 絵文字 Enter == Enter @@ -181,9 +181,6 @@ Friends == 友達 -Fullscreen -== フルスクリーン - Game == ゲーム @@ -215,7 +212,7 @@ == プレイヤーがいるサーバー High Detail -== High Detail表示 +== 高解像度マップを表示 Hook == フック @@ -245,7 +242,7 @@ == マップ Maximum ping: -== Pingの最大値: +== Ping の最大値: Mouse sens. == マウス感度 @@ -309,22 +306,22 @@ [Demo browser] Play -== プレイ +== 再生 Play background music -== BGMを再生する +== BGM Player == プレイヤー Player country: -== 国籍: +== 所在地: Player options == プレイヤー設定 Players -== プレイヤー +== プレーヤー Please balance teams! == チーム人数が偏っています! @@ -347,11 +344,8 @@ Refresh == 更新 -Refreshing master servers -== サーバーリスト更新 - Remote console -== リモコン +== リモートコンソール Remove == 削除 @@ -384,10 +378,10 @@ == スクリーンショット Server address: -== IPアドレス: +== IP アドレス: Server details -== サーバーの詳細 +== サーバー情報 Server filter == サーバーフィルタ @@ -402,20 +396,17 @@ == ショットガン Show chat -== チャットを見る +== チャットを表示 Show friends only == 友達がいるサーバー Show ingame HUD -== ゲーム内でHUDを表示 +== ゲーム内で HUD を表示 Show name plates == 名前を表示 -Show only supported -== 対応している項目のみを表示 - Skins == スキン @@ -426,7 +417,7 @@ == サウンドエラー Spectate -== 観戦 +== 観戦席 Spectate next == 次の人を見る @@ -462,16 +453,16 @@ == オーディオデバイスの初期化に失敗しました。 The server is running a non-standard tuning on a pure game type. -== サーバーがMODを導入しているようです。 +== サーバーが MOD を導入しているようです。 There's an unsaved map in the editor, you might want to save it before you quit the game. -== 保存されていないデータがありますが、よろしいですか。 +== 保存されていないデータがありますが、保存せずに閉じますか。 Time limit -== 残り時間 +== 時間上限 Time limit: %d min -== 残り %d 分 +== 時間上限: %d 分 Try again == もう一度 @@ -486,7 +477,7 @@ == デモの名前を変更できません。 Use sounds -== 音を鳴らす +== サウンド全般を使用 Use team colors for name plates == 名前表示にチームカラーを使う @@ -527,7 +518,7 @@ ##### needs translation ##### New name: -== 新しい名前: +== 新しいファイル名: Max demos == デモの最大数 @@ -551,7 +542,7 @@ == Crc: FSAA samples -== FSAAサンプル数 +== FSAA サンプル数 Sat. == 彩度 @@ -563,7 +554,7 @@ == 音量 Created: -== 作成: +== 作成日時: Record demo == デモを録画 @@ -572,13 +563,13 @@ == その他 Netversion: -== Netversion: +== 通信バージョン: Info == 情報 UI Color -== UIカラー +== UI カラー Max Screenshots == スクリーンショットの最大数 @@ -596,7 +587,7 @@ == 初期設定に戻す Laser -== ライフル +== レーザー Display Modes == 画面モード @@ -623,659 +614,707 @@ == タイプ: Successfully saved the replay! -== +== リプレイ保存完了しました! Saving ddnet-settings.cfg failed -== +== ddnet-settings.cfg 保存失敗 Replay feature is disabled! -== +== リプレイ機能はオフにしました! The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +== テクスチ %s の幅は %d で割り切れなません、もしくは高さは %d で割り切れません。この場合、画面バグが発生する可能性があります。 Warning -== +== 注意 Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. -== +== デバッグモードをオンにしました。オフにするには、Ctrl+Shift+D を押してください。 Game paused -== +== 一時停止しました Server best: -== +== サーバー最高記録: Personal best: -== +== 個人最高記録: Reset -== +== リセット Browser -== +== サーバー Ghost -== +== ゴースト Loading DDNet Client -== +== DDNet Client 読み込み中 Reconnect in %d sec -== +== 再接続まであと %d 秒 Render demo -== +== デモを描画 Replace video -== +== 動画を変更 File already exists, do you want to overwrite it? -== +== ファイルはすでに存在しています、上書きしますか? Are you sure that you want to disconnect? -== +== 接続を切断しますか? Disconnect Dummy -== +== ダミーの接続を切断 Are you sure that you want to disconnect your dummy? -== +== ダミーへの接続を切断しますか? Welcome to DDNet -== +== DDNet へようこそ DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you. -== +== DDraceNetwork は、協力型のオンラインゲームです。チーム中の Tee と協力して、マップ上のゴールを目指しましょう!初心者の方は、最も簡単なマップを使っている Novice サーバーから始めることをおすすめします。サーバー選ぶ時は ping を考慮して、近くのサーバーを選んでください。 The maps contain freeze, which temporarily make a tee unable to move. You have to work together to get through these parts. -== +== マップには、一時的に Tee が動けなくなる「フリーズ」が存在します。みんなで協力して、フリーズを乗り切る必要があります。 The mouse wheel changes weapons. Hammer (left mouse) can be used to hit other tees and wake them up from being frozen. -== +== マウスのスクロールで武器を変えます。ハンマー(左ボタン)で他の Tee を叩き、凍結していた Tee を起こすことができます。 Hook (right mouse) can be used to swing through the map and to hook other tees to you. -== +== フック(右ボタン)で地図上でスイングしたり、他の Tee を自分にフックすることができます。 Most importantly communication is key: There is no tutorial so you'll have to chat (t key) with other players to learn the basics and tricks of the game. -== +== 最も重要なのはコミュニケーションです。チュートリアルはありませんので、ゲームの基本やコツを学ぶためには、他のプレイヤーとチャットする必要があります。T ボタンを押すと、チャット画面が開きます。 Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. -== +== K ボタンでキル(やり直す)、Q ボタンで一時停止して観戦することができます。他のキーバインドは設定画面を参照してください。 It's recommended that you check the settings to adjust them to your liking before joining a server. -== +== サーバーに参加する前に、設定を確認して、自分の好みに合わせて、キーバインドを調整することをお勧めします。 Please enter your nickname below. -== +== ハンドルネームを入力してください。 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 -== +== 既存のプレーヤーの中でハンドルネームを検索しています。 Country / Region -== +== 国・地域 Destination file already exist -== +== 保存先のファイルはすでに存在します Speed -== +== 速度 Video name: -== +== 動画の名前 Show DDNet map finishes in server browser -== +== サーバー・ブラウザーで完成した DDNet マップを表示する transmits your player name to info2.ddnet.tw -== +== ハンドルネームを info2.ddnet.tw に送信します Theme +== テーマ + +AFR == -Search +ASI == -Exclude +AUS == -%d of %d servers +EUR == -%d of %d server +NA == -%d players +SA == -%d player +CHN == +Getting server list from master server +== マスターサーバーからサーバーリストを取得しています + +Search +== 検索キーワード + +Exclude +== 除外キーワード + +%d of %d servers +== %d / %d コのサーバー + +%d of %d server +== %d / %d コのサーバー + +%d players +== %d 人のプレーヤー + +%d player +== %d 人のプレーヤー + Filter connecting players -== +== 接続中のプレーヤーをフィルタする Indicate map finish -== +== 完成したマップをハイライトする Unfinished map -== +== 未完成のマップ Countries -== +== 国 Types -== +== タイプ + +Leak IP +== IP を漏洩する Select a name -== +== 名前をつけて保存 Please use a different name -== +== 別の名前をつけてください Remove chat -== +== チャット履歴を削除 Markers: -== +== ブックマーク: %.2f MiB -== +== %.2f MiB %.2f KiB -== +== %.2f KiB Demo -== +== デモ録画 Markers -== +== ブックマーク Length -== +== 長さ Date -== +== 日付 Fetch Info -== +== デモ情報 Demos directory -== +== デモ・ディレクトリー Render -== +== 描画 Connecting dummy -== +== ダミーを接続中 Connect Dummy -== +== ダミーに接続 Kill -== +== キル Pause -== +== 観察 Reload -== +== 更新 Deactivate -== +== 無効にする Activate -== +== 有効にする Save -== +== 保存 Smooth Dynamic Camera -== +== 滑らかな動的カメラ Switch weapon when out of ammo -== +== 弾薬切れの時に武器を切り替え Reset wanted weapon on death -== +== 死亡時に武器をデフォルトにリセット Show only chat messages from friends -== +== 友たちからのメッセージのみを表示 Show clan above name plates -== +== 名前の上に所属クランを表示 Clan plates size -== +== クラン表示サイズ Skip the main menu -== +== メインメニューをスキップ Refresh Rate -== +== リフレッシュ・レート Show console window -== +== コンソールを表示 Themes directory -== +== テーマ・ディレクトリー Automatically take statboard screenshot -== +== リザルト画面で自動スクショ Automatically create statboard csv -== +== リザルト CSV ファイルを自動生成 Max CSVs -== +== 最大 CSV ファイル数 Dummy settings -== +== ダミー設定 Download skins -== +== スキンをダウンロード Vanilla skins only -== +== オリジナルスキンのみ Fat skins (DDFat) -== +== まるいスキン(DDFat) Skin prefix -== +== スキンの接頭辞 Skin Database -== +== スキン・データーベース Skins directory -== +== スキン・ディレクトリー Hook collisions -== +== フックライン Zoom in -== +== 拡大 Zoom out -== +== 縮小 Default zoom -== +== デフォルト倍率 Show others -== +== 他のプレイヤーを表示 Show all -== +== すべてのプレイヤーを表示 Toggle dyncam -== +== 動的カメラ切り替え Toggle dummy -== +== ダミー切り替え Toggle ghost -== +== ゴースト切り替え Dummy copy -== +== ダミーシンクロ Hammerfly dummy -== +== ハンマーフライモード Converse -== +== 送信先を引き継ぎ Statboard -== +== リザルト画面 Lock team -== +== チームをロック Show entities -== +== オブジェを表示 Show HUD -== - -45° aim -== +== HUD を表示 -Deepfly on -== - -Deepfly off -== +Chat command +== チャットコマンド UI mouse s. -== +== メイン画面マウス感度 Dummy -== +== ダミー -Borderless window -== +Windowed +== ウィンドウモード + +Windowed borderless +== ウィンドウモード・枠なし + +Desktop fullscreen +== フルスクリーン + +Fullscreen +== 独占フルスクリーン may cause delay -== +== 遅延する可能性あり Screen -== +== 画面 -Use OpenGL 3.3 (experimental) -== +Use modern OpenGL +== 最新の OpenGL を使用 Preinit VBO (iGPUs only) -== +== VBO を事前初期化(統合型グラフィックのみ) Multiple texture units (disable for MacOS) -== +== 多重テクスチャユニット(macOS 以外) Use high DPI -== +== 高 DPI 対応モード Enable game sounds -== +== 音声 Enable gun sound -== +== 銃声 SFX Enable long pain sound (used when shooting in freeze) -== +== フリーズ時の叫び Enable server message sound -== +== サーバーメッセージ受信音 Enable regular chat sound -== +== 普通チャット受信音 Enable team chat sound -== +== チームチャット受信音 Enable highlighted chat sound -== +== メンション・メッセージ受信音 Threaded sound loading -== +== マルチスレッド音声読み込み Game sound volume -== +== ゲーム SFX 音量 Chat sound volume -== +== 受信音音量 Map sound volume -== +== マップ SFX 音量 Background music volume -== +== BGM 音量 HUD -== +== HUD DDNet -== +== DDNet Assets -== +== 素材 DDNet Client needs to be restarted to complete update! -== +== アップデートを完了するには、DDNet クライアントの再起動が必要です。 Use DDRace Scoreboard -== +== DDRace スゴアボードを使用 Show client IDs -== +== クライアント ID を表示 Show score -== +== スコアを表示 Show health + ammo -== +== HP と弾薬を表示 Show names in chat in team colors -== +== チームの色でハンドルネームを表示 Show kill messages -== +== キル情報を表示 Show votes window after voting -== +== 投票完了後にウィンドウを閉じない Laser Outline Color -== +== レーザー枠の色 Laser Inner Color -== +== レーザーの色 + +Hookline +== フックライン + +No hit +== ヒットなし + +Hookable +== フック可能 + +Tee +== Tee Use old chat style -== +== 古いチャットボックスを使用 Messages -== +== メッセージ System message -== +== システムメッセージ Highlighted message -== +== メンションメッセージ Team message -== +== チームメッセージ Friend message -== +== 友たちからのメッセージ Normal message -== +== 普通のメッセージ Client message -== +== クライアントのメッセージ Preview -== +== プレビュー Save the best demo of each race -== +== レースの最高記録のデモを保存 Default length: %d -== +== デフォルト長さ:%D Enable replays -== +== リプレイ機能 Show ghost -== +== ゴーストを表示 Save ghost -== +== ゴーストを保存 Gameplay -== +== ゲーム Overlay entities -== +== 重ねオブジェ Size -== +== サイズ Show text entities -== +== 文字オブジェを表示 Show others (own team only) -== +== 同チームの他のプレーヤーを表示 Show quads -== +== 背景を表示 AntiPing -== +== AntiPing AntiPing: predict other players -== +== AntiPing: 他のプレーヤーを予測 AntiPing: predict weapons -== +== AntiPing: 武器を予測 AntiPing: predict grenade paths -== +== AntiPing: 手榴弾の軌跡を予測 Show other players' hook collision lines -== +== 他のプレーヤーのフックラインを表示 Show other players' key presses -== +== 他のプレーヤーの押したキーを表示 Old mouse mode -== +== 古いマウスモード Background -== +== 背景 Entities Background color -== +== オブジェの背景色 Use current map as background -== +== 現在のマップを背景にする Show tiles layers from BG map -== +== 背景マップのタイル層を表示 Regular Background Color -== +== 普通の背景色 Try fast HTTP map download first -== +== マップのダウンロードは HTTP を優先にする New random timeout code -== +== ランダムにタイムアウト・コードを生成 DDNet %s is available: -== +== DDNet %s がリリースされました。 Update now -== +== 今すぐ更新 Updating... -== +== 更新中… DDNet Client updated! -== +== DDNet Client が更新しました! No updates available -== +== 新しいバージョンはありません Check now -== +== 更新を確認 Entities -== +== オブジェ Emoticons -== +== 絵文字 Particles -== +== 粒子 Assets directory -== +== 素材ディレクトリー + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord Learn -== +== チュートリアル https://wiki.ddnet.tw/ -== +== https://wiki.ddnet.tw/ Website -== +== ウェブ Settings -== +== 設定 Stop server -== +== サーバーを停止 Run server -== +== サーバーを起動 Server executable not found, can't run server -== +== サーバープログラムが見つかりません。サーバー起動できません。 Editor -== +== エディター [Start menu] Play -== +== スタート DDNet %s is out! -== +== DDNet %s がリリースされました! Downloading %s: -== +== %s をダウンロード中: Update failed! Check log... -== +== 更新に失敗しました!ログを確認してください。 Restart -== +== やり直す Time -== +== 時間 Manual -== +== 手動 Race -== +== レース Auto -== +== 自動 Replay -== +== リプレー Follow -== +== フォロー Frags -== +== キル Deaths -== +== 総キル Suicides -== +== 自殺 Ratio -== +== キル率 Net -== +== 純キル FPM -== +== キル/分 Spree -== +== コンボ Best -== +== 最高 Grabs -== +== 旗取り 1 new mention -== +== 1 件の新しいメンション %d new mentions -== +== %d 件の新しいメンション 9+ new mentions -== +== 9+ 件の新しいメンション diff -Nru ddnet-15.3.2/data/languages/korean.txt ddnet-15.5.4/data/languages/korean.txt --- ddnet-15.3.2/data/languages/korean.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/korean.txt 2021-06-20 09:38:48.000000000 +0000 @@ -337,9 +337,6 @@ Refresh == 새로고침 -Refreshing master servers -== 마스터 서버를 새로 고치는 중 - Remote console == 원격 콘솔 @@ -403,9 +400,6 @@ Show name plates == 이름 표시 -Show only supported -== 지원하는 해상도 표시 - Skins == 스킨 @@ -738,6 +732,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -771,6 +789,9 @@ Types == +Leak IP +== + Select a name == @@ -942,22 +963,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -966,7 +987,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1053,6 +1074,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1185,6 +1218,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/kyrgyz.txt ddnet-15.5.4/data/languages/kyrgyz.txt --- ddnet-15.3.2/data/languages/kyrgyz.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/kyrgyz.txt 2021-06-20 09:38:48.000000000 +0000 @@ -406,9 +406,6 @@ Refresh == Жаңылоо -Refreshing master servers -== Мастер-серверлер тизмесин жаңылоо - Remote console == Алыскы консоль @@ -484,9 +481,6 @@ Show name plates == Оюнчулардын аттарын көрсөтүү -Show only supported -== Колдолгонду гана көрсөтүү - Size: == Өлчөмү: @@ -733,6 +727,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -766,6 +784,9 @@ Types == +Leak IP +== + Select a name == @@ -934,22 +955,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -958,7 +979,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1045,6 +1066,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1177,6 +1210,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/norwegian.txt ddnet-15.5.4/data/languages/norwegian.txt --- ddnet-15.3.2/data/languages/norwegian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/norwegian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -349,9 +349,6 @@ Refresh == Oppdater -Refreshing master servers -== Opdaterer master-servere - Remote console == Server-konsoll @@ -415,9 +412,6 @@ Show name plates == Vis navneskilt -Show only supported -== Vis kompatible - Skins == Utseender @@ -900,18 +894,12 @@ UI mouse s. == UI museh. -Borderless window -== Rammeløst vindu - may cause delay == kan gi forsinkelse Screen == Skjerm -Use OpenGL 3.3 (experimental) -== Bruk OpenGL 3.3 (eksperimentell) - Preinit VBO (iGPUs only) == Preinit. VBO (kun iGPU) @@ -1249,18 +1237,51 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == -45° aim +AFR +== + +ASI +== + +AUS +== + +EUR == -Deepfly on +NA == -Deepfly off +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Show client IDs == @@ -1270,6 +1291,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1281,3 +1314,9 @@ Regular Background Color == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/persian.txt ddnet-15.5.4/data/languages/persian.txt --- ddnet-15.3.2/data/languages/persian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/persian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -409,10 +409,31 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + Type == -Refreshing master servers +Getting server list from master server == Search @@ -475,6 +496,9 @@ Server details == +Leak IP +== + Scoreboard == @@ -763,13 +787,7 @@ Show HUD == -45° aim -== - -Deepfly on -== - -Deepfly off +Chat command == UI mouse s. @@ -790,13 +808,16 @@ Miscellaneous == -Show only supported +Display Modes == -Display Modes +Windowed == -Borderless window +Windowed borderless +== + +Desktop fullscreen == V-Sync @@ -811,7 +832,7 @@ FSAA samples == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -928,6 +949,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1060,6 +1093,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/polish.txt ddnet-15.5.4/data/languages/polish.txt --- ddnet-15.3.2/data/languages/polish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/polish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -350,9 +350,6 @@ Refresh == Odśwież -Refreshing master servers -== Odświeżanie głównych serwerów - Remote console == Zdalna konsola @@ -416,9 +413,6 @@ Show name plates == Pokazuj nicki -Show only supported -== Pokazuj tylko wspierane - Skins == Motywy @@ -814,9 +808,6 @@ Destination file already exist == Plik docelowy już istnieje -Use OpenGL 3.3 (experimental) -== Użyj OpenGL 3.3 (eksperymentalne) - Reload == Przeładuj @@ -1033,9 +1024,6 @@ Are you sure that you want to disconnect? == Czy jesteś pewien, że chcesz opuścić serwer? -Borderless window -== Okno bez ramek - Successfully saved the replay! == Pomyślnie zapisano powtórkę! @@ -1250,18 +1238,51 @@ The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. == -45° aim +AFR +== + +ASI +== + +AUS +== + +EUR == -Deepfly on +NA == -Deepfly off +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Show client IDs == @@ -1271,6 +1292,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Preview == @@ -1282,3 +1315,9 @@ Regular Background Color == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/portuguese.txt ddnet-15.5.4/data/languages/portuguese.txt --- ddnet-15.3.2/data/languages/portuguese.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/portuguese.txt 2021-06-20 09:38:48.000000000 +0000 @@ -70,9 +70,6 @@ Body == Corpo -Borderless window -== Janela S/ Bordas - Call vote == Votação @@ -362,9 +359,6 @@ Refresh == Atualizar -Refreshing master servers -== A Atualizar servidores - Remote console == Remote console @@ -437,9 +431,6 @@ Show only chat messages from friends == Mostrar apenas mensagens dos amigos no chat -Show only supported -== Mostrar apenas suportado - Skins == Skins @@ -935,6 +926,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -956,6 +971,9 @@ Unfinished map == +Leak IP +== + Markers: == @@ -1076,25 +1094,28 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen == may cause delay == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1133,6 +1154,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1202,6 +1235,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/romanian.txt ddnet-15.5.4/data/languages/romanian.txt --- ddnet-15.3.2/data/languages/romanian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/romanian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -356,9 +356,6 @@ Refresh == Reîncarcă -Refreshing master servers -== Reîncarcă serverele principale - Remote console == Consolă server @@ -425,9 +422,6 @@ Show only chat messages from friends == Arată doar chatul prietenilor -Show only supported -== Arată doar modurile suportate - Skins == Costume @@ -748,6 +742,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -781,6 +799,9 @@ Types == +Leak IP +== + Select a name == @@ -949,22 +970,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -973,7 +994,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1060,6 +1081,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1192,6 +1225,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/russian.txt ddnet-15.5.4/data/languages/russian.txt --- ddnet-15.3.2/data/languages/russian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/russian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -11,6 +11,8 @@ # gerdoe 2020-07-01 22:04:21 # gerdoe 2020-09-14 20:53:53 # gerdoe 2020-09-17 22:49:22 +# Vy0x2 2021-06-08 15:48:25 +# Anime.pdf 2021-06-13 13:08:50 ##### /authors ##### ##### translated strings ##### @@ -61,7 +63,7 @@ == Вы уверены, что хотите удалить игрока из друзей? Automatically record demos -== Автоматически записывать демо +== Записывать демо Automatically take game over screenshot == Делать снимок результатов игры @@ -91,7 +93,7 @@ == Клиент Close -== Выход +== Закрыть Compatible version == Совместимая версия @@ -127,7 +129,7 @@ == Удалить демо Demo details -== Детали демо +== Сведения демо Demofile: %s == Демо: %s @@ -160,7 +162,7 @@ == Ошибка Error loading demo -== ошибка при загрузке демо +== Ошибка при загрузке демо Favorite == Избранный @@ -175,7 +177,7 @@ == Фильтр Fire -== Выстрел +== Стрелять Folder == Папка @@ -205,7 +207,7 @@ == Тип игры Game types: -== Тип игры: +== Типы игры: General == Основные @@ -229,7 +231,7 @@ == Крюк Invalid Demo -== Недопустимое демо +== Нерабочее демо Join blue == За синих @@ -241,7 +243,7 @@ == Прыжок Kick player -== Забанить игрока +== Выгнать игрока Language == Язык @@ -307,7 +309,7 @@ == Пароль Password incorrect -== Пароль +== Неверный пароль Ping == Пинг @@ -320,7 +322,7 @@ == Просмотр Play background music -== Играть фоновую музыку +== Включить фоновую музыку Player == Игрок @@ -355,9 +357,6 @@ Refresh == Обновить -Refreshing master servers -== Обновление списка мастер-серверов - Remote console == Консоль сервера @@ -380,7 +379,7 @@ == Частота Score -== Очки +== Счёт Score limit == Лимит очков @@ -392,10 +391,10 @@ == Снимок Server address: -== Адрес сервера +== Адрес сервера: Server details -== Детали сервера +== Сведения сервера Server filter == Фильтр серверов @@ -421,9 +420,6 @@ Show name plates == Показывать имена игроков -Show only supported -== Показывать только поддерживаемые разрешения экрана - Skins == Скины @@ -431,16 +427,16 @@ == Звук Sound error -== Звуковая ошибка +== Ошибка звука Spectate == Наблюдать Spectate next -== Наблюдать след. +== Наблюдать за след. Spectate previous -== Наблюдать пред. +== Наблюдать за пред. Spectator mode == Наблюдатель @@ -455,13 +451,13 @@ == Строгий фильтр р-мов Sudden Death -== Быстрая смерть +== Внезапная смерть Switch weapon on pickup == Переключать оружие при подборе Switch weapon when out of ammo -== Переключать пустое оружие +== Переключать оружие без патронов Reset wanted weapon on death == Сбрасывать выбор оружия при смерти @@ -473,7 +469,7 @@ == Показывать клан Automatically take statboard screenshot -== Автоматически делать скрин результатов +== Делать снимок результатов Show console window == Показывать консоль @@ -485,13 +481,13 @@ == Командный чат The audio device couldn't be initialised. -== Аудио устройство не может быть инициализировано +== Аудио устройство не может быть инициализировано. The server is running a non-standard tuning on a pure game type. == Сервер запущен с нестандартными настройками на стандартном типе игры. There's an unsaved map in the editor, you might want to save it before you quit the game. -== Есть несохранённая карта в редакторе. Вы можете сохранить её перед тем, как выйти. +== Вы не сохранили карту в редакторе. Можете сохранить её перед тем, как выйти. Time limit == Лимит времени @@ -515,7 +511,7 @@ == Использовать звуки Use team colors for name plates -== Командные цвета для имен игроков +== Использовать командные цвета имён V-Sync == V-Sync @@ -574,9 +570,6 @@ Threaded sound loading == Фоновая загрузка музыки -Borderless window -== Без рамки - Use DDRace Scoreboard == Использовать DDRace табло @@ -584,16 +577,16 @@ == Показывать сообщения о смерти Show score -== Показывать лидеров +== Показывать табло счета Show health + ammo -== Показывать здоровье + патроны +== Показывать здоровье и патроны Show names in chat in team colors -== Выделять цветом сообщения игроков в командном чате +== Выделять сообщения в командном чате Show votes window after voting -== Не скрывать меню голосования после вашего выбора +== Показывать голосование после выбора Reset == Сброс @@ -614,7 +607,7 @@ == Типы New name: -== Новое имя +== Новое имя: Sat. == Контр. @@ -632,7 +625,7 @@ == Играть FSAA samples -== Сэмплов FSAA +== FSAA сэмплов Sound volume == Громкость звука @@ -644,10 +637,10 @@ == Максимальное кол-во снимков Length: -== Длина +== Длина: Laser -== Бластер +== Лазер Netversion: == Версия: @@ -677,7 +670,7 @@ == Все равно выйти? Display Modes -== Разрешение экрана +== Разрешения экрана Version: == Версия: @@ -701,13 +694,13 @@ == LAN Name plates size -== Размер +== Размер имён Type: == Тип: Show ghost -== Показать тень +== Показывать тень No updates available == Нет доступных обновлений @@ -728,16 +721,16 @@ == Показывать остальных Game paused -== Игра приостановлена +== Пауза Old mouse mode -== Режим старой мышки +== Режим старой мыши Team message == Сообщение от команды Automatically create statboard csv -== Автоматически создавать statboard CSV +== Записывать результаты в CSV Are you sure that you want to disconnect? == Вы уверены, что хотите отключиться? @@ -788,10 +781,10 @@ == Функция повтора отключена! Server best: -== Лучшее сервера: +== Рекорд сервера: Personal best: -== Ваше лучшее: +== Ваш рекорд: Browser == Браузер @@ -818,25 +811,25 @@ == Добро пожаловать в DDNet DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you. -== DDraceNetwork кооперативная онлайн-игра, где Вашей главной целью и целью вашей команды является достижением финиша карты. Как новенький, Вы должны начать свой путь на Novice серверах, на которых стоят лёгкие карты. Также учитывайте пинг и выбирайте ближайший к вам сервер. +== DDraceNetwork - кооперативная онлайн-игра, где Вашей главной целью и целью вашей команды является достижение конца карты. Как новенький, Вы должны начать свой путь на Novice, на которых есть лёгкие карты. Также учитывайте пинг и выбирайте ближайший к вам сервер. The maps contain freeze, which temporarily make a tee unable to move. You have to work together to get through these parts. -== Карта содержит фриз-тайлы, которые временно лишают вас возможности двигаться. Вы должны работать сообща, чтобы пройти такие уровни. +== Карта содержит фриз, который временно лишает вас возможности двигаться. Вы должны работать сообща, чтобы пройти такие места. The mouse wheel changes weapons. Hammer (left mouse) can be used to hit other tees and wake them up from being frozen. -== Колесо мыши меняет оружие. Молотком (ЛКМ) можно ударить по другим и разбудить их от замерзания. +== Колесо мыши меняет оружие. Молотком (ЛКМ) можно ударить по другим и расфризить их. Hook (right mouse) can be used to swing through the map and to hook other tees to you. -== Крюк (ПКМ) используется для перемещения по карте, а также для притягивания игроков к вам. +== Крюк (ПКМ) используется для перемещения по карте, а также для притягивания игроков к себе. Most importantly communication is key: There is no tutorial so you'll have to chat (t key) with other players to learn the basics and tricks of the game. -== Самое главное, общение ключ к успеху: Здесь нет туториалов, собственно, ты будешь общаться (кнопка Т) с остальными игроками, чтобы обучиться базовым вещам и трюкам игры. +== Самое главное, общение - ключ к успеху: здесь нет туториалов, поэтому тебе придется общаться (кнопка Т) с остальными игроками, чтобы обучиться базовым вещам и трюкам в игре. It's recommended that you check the settings to adjust them to your liking before joining a server. == Перед посещением сервера рекомендуем изменить настройки на Ваш вкус. Please enter your nickname below. -== Пожалуйста, укажите ниже свой никнейм. +== Пожалуйста, ниже укажите ваш псевдоним. Destination file already exist == Конечный файл уже существует @@ -848,7 +841,7 @@ == Показывать в браузере статус завершения карты transmits your player name to info2.ddnet.tw -== передает Ваш никнейм на info2.ddnet.tw +== передает ваш псевдоним на info2.ddnet.tw Exclude == Исключить @@ -857,7 +850,7 @@ == Только подключившиеся Indicate map finish -== Показывать финиши карт +== Выделять финишированные карты Unfinished map == Нефинишированная карта @@ -869,7 +862,7 @@ == Скачивание %s: Update failed! Check log... -== Обновление прошло неудачно! Проверьте логи... +== Провал. Проверьте логи... Restart == Рестарт @@ -893,7 +886,7 @@ == Показать Render -== Начать рендер +== Рендер Connect Dummy == Подключить дамми @@ -911,7 +904,7 @@ == Сохранить Clan plates size -== Размер панели клана +== Размер клана Refresh Rate == Частота обн. @@ -944,19 +937,19 @@ == Отдалить Default zoom -== Станд. масштаб +== Ст. масштаб Show all == Показывать всех Toggle dyncam -== Смен. дин. камеры +== Смена дин. камеры Toggle dummy -== Сменить ти +== Смена Tee Toggle ghost -== Вкл./выкл. призрака +== Переключить тень Dummy copy == Повт. движений @@ -977,7 +970,7 @@ == Показать тайлы Show HUD -== Показавать HUD +== Показывать HUD UI mouse s. == Чувств. мыши в меню @@ -988,9 +981,6 @@ Screen == Экран -Use OpenGL 3.3 (experimental) -== Использовать OpenGL 3.3 (тестируется) - Preinit VBO (iGPUs only) == Преинициал-ть VBO (только iGPU) @@ -1013,7 +1003,7 @@ == Сообщение от друга Save the best demo of each race -== Сохранять лучшее демо каждой попытки +== Сохранять лучшее демо каждой карты Default length: %d == Станд. длина: %d @@ -1025,7 +1015,7 @@ == Игровой процесс Overlay entities -== Наложить тайлы +== Наложение Size == Размер @@ -1034,28 +1024,28 @@ == Показывать текст Show others (own team only) -== Всегда показывать вашу команду +== Показывать только вашу команду Show quads == Показывать квады AntiPing -== AntiPing +== Антипинг AntiPing: predict other players -== AntiPing: предсказывать других игроков +== Предсказывать положение игроков AntiPing: predict weapons -== AntiPing: предсказывать снаряды +== Предсказывать оружия AntiPing: predict grenade paths -== AntiPing: предсказывать пути гранат +== Предсказывать траекторию гранат Show other players' key presses -== Показывать нажатия других игроков +== Показывать действия игроков Show tiles layers from BG map -== Показывать слои с тайлами из фоновой карты +== Показывать тайловые слои фона Try fast HTTP map download first == Быстро загружать карты по HTTP @@ -1109,13 +1099,13 @@ == 9+ новых упоминаний Client message -== Сообщение от клиента +== Клиентское сообщение Warning == Предупреждение Use k key to kill (restart), q to pause and watch other players. See settings for other key binds. -== Используйте кнопку k, чтобы возродиться, q для паузы и наблюдением за остальными игроками. Проверьте настройки для остальных кнопок. +== Используйте кнопку k, чтобы возродиться, q для паузы и наблюдения за остальными игроками. Проверьте настройки остальных кнопок. Country / Region == Страна / Регион @@ -1179,16 +1169,16 @@ == Неудачное сохранение ddnet-settings.cfg Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. -== Отладочный режим включен. Нажмите Ctrl+Shift+D, чтобы выйти. +== Отладка включена. Нажмите Ctrl+Shift+D, чтобы выйти. Existing Player == Существующий игрок Your nickname '%s' is already used (%d points). Do you still want to use it? -== Твой никнейм '%s' уже существует (%d поинтов). Вы уверены, что хотите использовать его? +== Ваш псевдоним '%s' уже существует (%d поинтов). Вы уверены, что хотите использовать его? Checking for existing player with your name -== Проверка существования игрока с таким же именем +== Проверка на наличие игрока с таким же именем Theme == Тема @@ -1197,7 +1187,7 @@ == Папка с демо Smooth Dynamic Camera -== Плавная Динамическая камера +== Плавная дин. камера Themes directory == Папка с темами @@ -1212,7 +1202,7 @@ == Громкость игры Chat sound volume -== Громкость звуков чата +== Громкость чата Background music volume == Громкость фоновой музыки @@ -1227,7 +1217,7 @@ == Использовать данную карту как фон Emoticons -== Значки-эмоции +== Эмоции Particles == Частицы @@ -1251,37 +1241,88 @@ == Тайлы The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== - -45° aim -== - -Deepfly on -== - -Deepfly off -== +== Ширина текстуры %s не делится на %d или же его высота не делится на %d, что может повлиять на графику. Dummy -== +== Дамми Show client IDs -== +== Показывать ID Laser Outline Color -== +== Цвет обводки лазера Laser Inner Color -== +== Цвет лазера Preview -== +== Превью Background -== +== Фон Entities Background color -== +== Цвет фона при наложении Regular Background Color +== Стандартный цвет фона + +Chat command +== Чат-команда + +Discord +== Дискорд + +https://ddnet.tw/discord +== https://ddnet.tw/discord + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== NA + +SA +== SA + +CHN +== CHN + +Getting server list from master server +== Получение списка серверов от мастер-сервера + +Leak IP +== Передавать IP + +Windowed +== Оконный + +Windowed borderless +== Оконный без рамки + +Desktop fullscreen +== Полноэкранный + +Use modern OpenGL +== Использовать новый OpenGL + +Hookline == + +No hit +== + +Hookable +== + +Tee +== Tee diff -Nru ddnet-15.3.2/data/languages/serbian_cyrillic.txt ddnet-15.5.4/data/languages/serbian_cyrillic.txt --- ddnet-15.3.2/data/languages/serbian_cyrillic.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/serbian_cyrillic.txt 2021-06-20 09:38:48.000000000 +0000 @@ -343,9 +343,6 @@ Refresh == Освежи -Refreshing master servers -== Освежам мастер сервере - Remote console == Удаљена конзола @@ -412,9 +409,6 @@ Show name plates == Прикажи плочице са именима -Show only supported -== Прикажи само подржане - Skins == Теме @@ -525,9 +519,6 @@ ##### needs translation ##### -Borderless window -== Прозор без ивица - Demo == Снимак @@ -943,9 +934,6 @@ may cause delay == може проузроковати кашњење -Use OpenGL 3.3 (experimental) -== Користи OpenGL 3.3 (експериментално) - Preinit VBO (iGPUs only) == Preinit VBO (само у iGPUs) @@ -1229,21 +1217,54 @@ Checking for existing player with your name == -Smooth Dynamic Camera +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA == -45° aim +CHN == -Deepfly on +Getting server list from master server +== + +Leak IP +== + +Smooth Dynamic Camera == -Deepfly off +Chat command == Dummy == +Windowed +== + +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL +== + Game sound volume == @@ -1262,6 +1283,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1279,3 +1312,9 @@ Regular Background Color == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/serbian.txt ddnet-15.5.4/data/languages/serbian.txt --- ddnet-15.3.2/data/languages/serbian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/serbian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -350,9 +350,6 @@ Refresh == Osveži -Refreshing master servers -== Osvežavam master servere - Remote console == Udaljena konzola @@ -416,9 +413,6 @@ Show name plates == Prikaži imena igrača -Show only supported -== Prikaži samo podržane - Skins == Skinovi @@ -743,6 +737,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -776,6 +794,9 @@ Types == +Leak IP +== + Select a name == @@ -947,22 +968,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -971,7 +992,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1058,6 +1079,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1190,6 +1223,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/languages/simplified_chinese.txt ddnet-15.5.4/data/languages/simplified_chinese.txt --- ddnet-15.3.2/data/languages/simplified_chinese.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/simplified_chinese.txt 2021-06-20 09:38:48.000000000 +0000 @@ -11,6 +11,8 @@ # 2020.8.19 TsFreddie # 2020.8.20 Dan_cao # 2020.11.12 TsFreddie +# 2021.5.27 Momo +# 2021.6.1 TsFreddie ##### /authors ##### ##### translated strings ##### @@ -190,7 +192,7 @@ == 好友 Fullscreen -== 全屏幕 +== 独占全屏 Game == 游戏 @@ -344,7 +346,7 @@ [Demo browser] Play -== 编辑器 +== 播放 Play background music == 播放背景音乐 @@ -362,7 +364,7 @@ == 玩家选项 Players -== 玩家数 +== 玩家 Please balance teams! == 队伍人数不平衡! @@ -385,9 +387,6 @@ Refresh == 刷新 -Refreshing master servers -== 正在刷新主服务器 - Remote console == 远程控制台 @@ -454,9 +453,6 @@ Show only chat messages from friends == 只显示好友消息 -Show only supported -== 只显示支持的分辨率 - Skins == 皮肤 @@ -577,9 +573,6 @@ You must restart the game for all settings to take effect. == 你需要重启游戏来使某些设置生效。 -Borderless window -== 无边框窗口 - Demo == 回放 @@ -761,13 +754,13 @@ == 默认缩放 AntiPing: predict other players -== AntiPing:预测其他玩家 +== 延迟补偿:预测其他玩家 AntiPing: predict weapons -== AntiPing:预测武器 +== 延迟补偿:预测武器 AntiPing: predict grenade paths -== AntiPing:预测榴弹路径 +== 延迟补偿:预测榴弹路径 Show other players' hook collision lines == 显示其他玩家的钩索辅助线 @@ -826,7 +819,7 @@ ##### Ratio的算法是击杀除以死亡次数 ##### AntiPing -== AntiPing +== 延迟补偿 Countries == 国家 @@ -874,7 +867,7 @@ == 击杀数 Enable long pain sound (used when shooting in freeze) -== 启用长时间痛苦的声音(在冻结时使用) +== 启用冻结呼救声 %.2f MiB == %.2f MiB @@ -934,9 +927,6 @@ Multiple texture units (disable for MacOS) == 多重纹理单元 (MacOS不可用) -Use OpenGL 3.3 (experimental) -== 使用 OpenGL 3.3 (实验性) - File already exists, do you want to overwrite it? == 文件已存在,是否覆盖? @@ -974,7 +964,7 @@ == 正在下载 %s: %d new mentions -== %d 条新提示 +== %d 条新提及 Toggle dummy == 切换分身 @@ -1004,7 +994,7 @@ == 视野放大 1 new mention -== 1 条新提示 +== 1 条新提及 Update failed! Check log... == 更新失败!请检查日志... @@ -1034,7 +1024,7 @@ == 分身同步动作 9+ new mentions -== 9+ 条新提示 +== 9+ 条新提及 Statboard == 统计板 @@ -1256,38 +1246,89 @@ Assets directory == 材质目录 -The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +https://ddnet.tw/discord +== http://chat.teeworlds.cn/ -45° aim -== +Discord +== 开黑啦 -Deepfly on -== +The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. +== 材质 %s 的宽度无法被 %d 整除,或者高度无法被 %d 整除,这可能会导致视觉错误。 -Deepfly off -== +Chat command +== 聊天框指令(/) Dummy -== +== 分身 Show client IDs -== +== 显示客户端ID Laser Outline Color -== +== 激光边框颜色 Laser Inner Color -== +== 激光颜色 Preview -== +== 预览 Background -== +== 背景 Entities Background color -== +== 实体层背景颜色 Regular Background Color -== +== 背景层背景颜色 + +AFR +== 非洲 + +ASI +== 亚洲 + +AUS +== 澳洲 + +EUR +== 欧洲 + +NA +== 北美 + +SA +== 南美 + +CHN +== 中国 + +Getting server list from master server +== 正在从主服务器获取服务器列表 + +Leak IP +== 暴露IP + +Windowed +== 窗口化 + +Windowed borderless +== 无边框窗口 + +Desktop fullscreen +== 无边框全屏 + +Use modern OpenGL +== 使用现代OpenGL + +Hookline +== 钩索辅助线 + +No hit +== 未命中 + +Hookable +== 可钩墙壁 + +Tee +== 其他玩家 diff -Nru ddnet-15.3.2/data/languages/slovak.txt ddnet-15.5.4/data/languages/slovak.txt --- ddnet-15.3.2/data/languages/slovak.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/slovak.txt 2021-06-20 09:38:48.000000000 +0000 @@ -347,9 +347,6 @@ Refresh == Obnoviť -Refreshing master servers -== Obnovujem master servery - Remote console == Vzdialená konzola @@ -413,9 +410,6 @@ Show name plates == Zobrazovať menovky -Show only supported -== Zobraziť len podporované - Skins == Skiny @@ -739,6 +733,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + Search == @@ -772,6 +790,9 @@ Types == +Leak IP +== + Select a name == @@ -943,22 +964,22 @@ Show HUD == -45° aim +Chat command == -Deepfly on +UI mouse s. == -Deepfly off +Dummy == -UI mouse s. +Windowed == -Dummy +Windowed borderless == -Borderless window +Desktop fullscreen == may cause delay @@ -967,7 +988,7 @@ Screen == -Use OpenGL 3.3 (experimental) +Use modern OpenGL == Preinit VBO (iGPUs only) @@ -1054,6 +1075,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1186,6 +1219,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + Learn == diff -Nru ddnet-15.3.2/data/languages/spanish.txt ddnet-15.5.4/data/languages/spanish.txt --- ddnet-15.3.2/data/languages/spanish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/spanish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -10,6 +10,7 @@ # FeaRZ 2020-07-07 12:09:00 # Ryozuki 2020-07-09 12:06:00 # Headshot 2020-11-07 12:40:00 +# Headshot 2021-05-30 17:00:00 ##### /authors ##### ##### translated strings ##### @@ -357,9 +358,6 @@ Refresh == Actualizar -Refreshing master servers -== Actualizando servidores maestros - Remote console == Consola remota @@ -426,9 +424,6 @@ Show only chat messages from friends == Recibir mensages solo de amigos -Show only supported -== Mostrar únicamente modos soportados - Skins == Skins @@ -615,7 +610,7 @@ == Ronda Lht. -== Luminosidad +== Lum. UI Color == Color de menú @@ -905,18 +900,12 @@ UI mouse s. == UI mouse s. -Borderless window -== Ventana sin bordes - may cause delay == puede causar retraso Screen == Pantalla -Use OpenGL 3.3 (experimental) -== Usar OpenGL 3.3 (experimental) - Preinit VBO (iGPUs only) == Preinit VBO (solo iGPU) @@ -1252,37 +1241,88 @@ == Repetición The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +== El ancho de la textura %s no es divisible por %d, o el alto no es divisible por %d, lo que puede causar errores visuales. + +AFR +== AFR + +ASI +== ASI + +AUS +== AUS + +EUR +== EUR + +NA +== NA + +SA +== SA + +CHN +== CHN -45° aim -== +Getting server list from master server +== Obteniendo lista de servidores del servidor maestro -Deepfly on -== +Leak IP +== Filtrar tu IP -Deepfly off -== +Chat command +== Comando de chat Dummy -== +== Dummy + +Windowed +== En ventana + +Windowed borderless +== Ventana sin bordes + +Desktop fullscreen +== Pantalla completa + +Use modern OpenGL +== Usar OpenGL moderno Show client IDs -== +== Mostrar IDs de clientes Laser Outline Color -== +== Color de contorno del laser Laser Inner Color -== +== Color interior del laser + +Hookline +== Linea del gancho + +No hit +== Sin golpes + +Hookable +== Enganchable + +Tee +== Tee Preview -== +== Vista previa Background -== +== Fondo Entities Background color -== +== Color de fondo de entidades Regular Background Color -== +== Color de fondo regular + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord diff -Nru ddnet-15.3.2/data/languages/swedish.txt ddnet-15.5.4/data/languages/swedish.txt --- ddnet-15.3.2/data/languages/swedish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/swedish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -350,9 +350,6 @@ Refresh == Uppdatera -Refreshing master servers -== Uppdaterar huvudservrar - Remote console == Serverkonsol @@ -416,9 +413,6 @@ Show name plates == Visa namnskyltar -Show only supported -== Visa endast upplösningar som stöds - Skins == Utseende @@ -838,9 +832,6 @@ Search == Sök -Borderless window -== Gränslös fönster - FPM == FPM @@ -1087,9 +1078,6 @@ Show names in chat in team colors == Visa namn in chatten med lag färger -Use OpenGL 3.3 (experimental) -== Använd OpenGL 3.3 (experimentell) - Update now == Updatera nu @@ -1205,6 +1193,33 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + +Leak IP +== + Demos directory == @@ -1220,16 +1235,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1253,6 +1274,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1282,3 +1315,9 @@ Assets directory == + +Discord +== + +https://ddnet.tw/discord +== diff -Nru ddnet-15.3.2/data/languages/traditional_chinese.txt ddnet-15.5.4/data/languages/traditional_chinese.txt --- ddnet-15.3.2/data/languages/traditional_chinese.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/traditional_chinese.txt 2021-06-20 09:38:48.000000000 +0000 @@ -5,6 +5,7 @@ # 2020.8.19 TsFreddie # 2020.8.20 Dan_cao # 2020.11.12 TsFreddie +# 2021.6.1 TsFreddie ##### /authors ##### ##### translated strings ##### @@ -184,7 +185,7 @@ == 好友 Fullscreen -== 全螢幕 +== 獨占全螢幕 Game == 遊戲 @@ -356,7 +357,7 @@ == 玩家選項 Players -== 玩家數 +== 玩家 Please balance teams! == 隊伍人數不平衡! @@ -379,9 +380,6 @@ Refresh == 重新整理 -Refreshing master servers -== 正在重新整理主伺服器 - Remote console == 遠端控制檯 @@ -448,9 +446,6 @@ Show only chat messages from friends == 只顯示好友訊息 -Show only supported -== 只顯示支援的解析度 - Skins == 外觀 @@ -571,9 +566,6 @@ You must restart the game for all settings to take effect. == 你需要重啟遊戲來使某些設定生效。 -Borderless window -== 無邊框視窗 - Demo == 回放 @@ -868,7 +860,7 @@ == 擊殺數 Enable long pain sound (used when shooting in freeze) -== 啟用長時間痛苦的聲音(在凍結時使用) +== 啟用在凍結時的呼救聲 %.2f MiB == %.2f MiB @@ -877,7 +869,7 @@ == 重新整理率 New random timeout code -== 新隨機超時程式碼 +== 隨機生成新的超時還原碼 Suicides == 自殺數 @@ -928,9 +920,6 @@ Multiple texture units (disable for MacOS) == 多重紋理單元 (MacOS不可用) -Use OpenGL 3.3 (experimental) -== 使用 OpenGL 3.3 (實驗性) - File already exists, do you want to overwrite it? == 檔案已存在,是否覆蓋? @@ -968,7 +957,7 @@ == 正在下載 %s: %d new mentions -== %d 條新提示 +== %d 條新提及 Toggle dummy == 切換分身 @@ -998,7 +987,7 @@ == 視距放大 1 new mention -== 1 條新提示 +== 1 條新提及 Update failed! Check log... == 更新失敗!請檢查日誌... @@ -1028,7 +1017,7 @@ == 分身同步動作 9+ new mentions -== 9+ 條新提示 +== 9+ 條新提及 Statboard == 統計板 @@ -1233,7 +1222,7 @@ == 材質 Use old chat style -== 舊版聊天框 +== 原始聊天框 Use current map as background == 使用當前地圖作爲實體層背景地圖 @@ -1251,37 +1240,88 @@ == 材質目錄 The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== +== 紋理 %s 的寬度無法被 %d 整除,或者高度無法被 %d 整除,這可能會導致視覺錯誤。 + +AFR +== 非洲 + +ASI +== 亞洲 + +AUS +== 澳洲 + +EUR +== 歐洲 + +NA +== 北美 + +SA +== 南美 + +CHN +== 中國 -45° aim -== +Getting server list from master server +== 正在從主伺服器獲取伺服器列表 -Deepfly on -== +Leak IP +== 洩露IP -Deepfly off -== +Chat command +== 聊天框指令 Dummy -== +== 分身 + +Windowed +== 視窗化 + +Windowed borderless +== 無邊框視窗 + +Desktop fullscreen +== 無邊框全螢幕 + +Use modern OpenGL +== 使用現代OpenGL Show client IDs -== +== 顯示客戶端ID Laser Outline Color -== +== 鐳射線邊框顏色 Laser Inner Color -== +== 鐳射線顏色 + +Hookline +== 鉤索輔助線 + +No hit +== 沒有擊中 + +Hookable +== 可鉤牆壁 + +Tee +== 其他玩家 Preview -== +== 預覽 Background -== +== 背景 Entities Background color -== +== 實體層背景顏色 Regular Background Color -== +== 背景層背景顏色 + +Discord +== Discord + +https://ddnet.tw/discord +== https://ddnet.tw/discord diff -Nru ddnet-15.3.2/data/languages/turkish.txt ddnet-15.5.4/data/languages/turkish.txt --- ddnet-15.3.2/data/languages/turkish.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/turkish.txt 2021-06-20 09:38:48.000000000 +0000 @@ -349,9 +349,6 @@ Refresh == Yenile -Refreshing master servers -== Ana sunucu yenileniyor - Remote console == Uzak Konsol @@ -415,9 +412,6 @@ Show name plates == Oyuncu isimlerini göster -Show only supported -== Desteklenmeyen çözünürlükleri gizle - Skins == Skinler @@ -906,18 +900,12 @@ UI mouse s. == Arayüzde mouse duy. -Borderless window -== Kenarsız pencere - may cause delay == gecikmelere sebep olabilir Screen == Ekran -Use OpenGL 3.3 (experimental) -== OpenGL 3.3 Kullan (deneysel) - Preinit VBO (iGPUs only) == Preinit VBO (sadece iGPUs) @@ -1164,6 +1152,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -1176,6 +1188,9 @@ %d player == +Leak IP +== + Demos directory == @@ -1197,16 +1212,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1230,6 +1251,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1260,6 +1293,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/languages/ukrainian.txt ddnet-15.5.4/data/languages/ukrainian.txt --- ddnet-15.3.2/data/languages/ukrainian.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/languages/ukrainian.txt 2021-06-20 09:38:48.000000000 +0000 @@ -269,9 +269,6 @@ Refresh == Оновити -Refreshing master servers -== Оновлення списку майстер-сервера - Remote console == Консоль сервера @@ -311,9 +308,6 @@ Show name plates == Показувати ніки гравців -Show only supported -== Тільки підтримувані режими - Skins == Скіни @@ -889,18 +883,12 @@ UI mouse s. == Чутлив. миші в меню -Borderless window -== Безмежне вікно - may cause delay == може спричинити затримку Screen == Екран -Use OpenGL 3.3 (experimental) -== OpenGL 3.3 (експериментально) - Preinit VBO (iGPUs only) == Preinit VBO (iGPUs only) @@ -1156,6 +1144,30 @@ Theme == +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Getting server list from master server +== + %d of %d servers == @@ -1168,6 +1180,9 @@ %d player == +Leak IP +== + Markers == @@ -1192,16 +1207,22 @@ Skins directory == -45° aim +Chat command == -Deepfly on +Dummy == -Deepfly off +Windowed == -Dummy +Windowed borderless +== + +Desktop fullscreen +== + +Use modern OpenGL == Game sound volume @@ -1225,6 +1246,18 @@ Laser Inner Color == +Hookline +== + +No hit +== + +Hookable +== + +Tee +== + Use old chat style == @@ -1255,6 +1288,12 @@ Assets directory == +Discord +== + +https://ddnet.tw/discord +== + https://wiki.ddnet.tw/ == diff -Nru ddnet-15.3.2/data/shader/text.frag ddnet-15.5.4/data/shader/text.frag --- ddnet-15.3.2/data/shader/text.frag 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/shader/text.frag 2021-06-20 09:38:48.000000000 +0000 @@ -10,12 +10,12 @@ out vec4 FragClr; void main() { - vec4 textColor = gVertColor * outVertColor * texture(gTextSampler, texCoord); - vec4 textOutlineTex = gVertOutlineColor * texture(gTextOutlineSampler, texCoord); - + vec4 textColor = gVertColor * outVertColor * vec4(1.0, 1.0, 1.0, texture(gTextSampler, texCoord).r); + vec4 textOutlineTex = gVertOutlineColor * vec4(1.0, 1.0, 1.0, texture(gTextOutlineSampler, texCoord).r); + // ratio between the two textures float OutlineBlend = (1.0 - textColor.a); - + // since the outline is always black, or even if it has decent colors, it can be just added to the actual color // without loosing any or too much color diff -Nru ddnet-15.3.2/data/shader/tile.vert ddnet-15.5.4/data/shader/tile.vert --- ddnet-15.3.2/data/shader/tile.vert 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/data/shader/tile.vert 2021-06-20 09:38:48.000000000 +0000 @@ -24,14 +24,14 @@ vec4 VertPos = vec4(inVertex, 0.0, 1.0); int XCount = gl_InstanceID - (int(gl_InstanceID/gJumpIndex) * gJumpIndex); int YCount = (int(gl_InstanceID/gJumpIndex)); - VertPos.x += gOffset.x + gDir.x * XCount; - VertPos.y += gOffset.y + gDir.y * YCount; + VertPos.x += gOffset.x + gDir.x * float(XCount); + VertPos.y += gOffset.y + gDir.y * float(YCount); gl_Position = vec4(gPos * VertPos, 0.0, 1.0); #elif defined(TW_TILE_BORDER_LINE) vec4 VertPos = vec4(inVertex.x + gOffset.x, inVertex.y + gOffset.y, 0.0, 1.0); - VertPos.x += gDir.x * gl_InstanceID; - VertPos.y += gDir.y * gl_InstanceID; + VertPos.x += gDir.x * float(gl_InstanceID); + VertPos.y += gDir.y * float(gl_InstanceID); gl_Position = vec4(gPos * VertPos, 0.0, 1.0); #else diff -Nru ddnet-15.3.2/datasrc/compile.py ddnet-15.5.4/datasrc/compile.py --- ddnet-15.3.2/datasrc/compile.py 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/datasrc/compile.py 2021-06-20 09:38:48.000000000 +0000 @@ -148,15 +148,15 @@ CNetObjHandler(); int ValidateObj(int Type, void *pData, int Size); - const char *GetObjName(int Type); - int GetObjSize(int Type); - int NumObjCorrections(); - const char *CorrectedObjOn(); + const char *GetObjName(int Type) const; + int GetObjSize(int Type) const; + int NumObjCorrections() const; + const char *CorrectedObjOn() const; - const char *GetMsgName(int Type); + const char *GetMsgName(int Type) const; void *SecureUnpackMsg(int Type, CUnpacker *pUnpacker); bool TeeHistorianRecordMsg(int Type); - const char *FailedMsgOn(); + const char *FailedMsgOn() const; }; """) @@ -180,9 +180,9 @@ lines += ['\tm_NumObjCorrections = 0;'] lines += ['}'] lines += [''] - lines += ['int CNetObjHandler::NumObjCorrections() { return m_NumObjCorrections; }'] - lines += ['const char *CNetObjHandler::CorrectedObjOn() { return m_pObjCorrectedOn; }'] - lines += ['const char *CNetObjHandler::FailedMsgOn() { return m_pMsgFailedOn; }'] + lines += ['int CNetObjHandler::NumObjCorrections() const { return m_NumObjCorrections; }'] + lines += ['const char *CNetObjHandler::CorrectedObjOn() const { return m_pObjCorrectedOn; }'] + lines += ['const char *CNetObjHandler::FailedMsgOn() const { return m_pMsgFailedOn; }'] lines += [''] lines += [''] lines += [''] @@ -219,14 +219,14 @@ lines += ['};'] lines += [''] - lines += ['const char *CNetObjHandler::GetObjName(int Type)'] + lines += ['const char *CNetObjHandler::GetObjName(int Type) const'] lines += ['{'] lines += ['\tif(Type < 0 || Type >= NUM_NETOBJTYPES) return "(out of range)";'] lines += ['\treturn ms_apObjNames[Type];'] lines += ['}'] lines += [''] - lines += ['int CNetObjHandler::GetObjSize(int Type)'] + lines += ['int CNetObjHandler::GetObjSize(int Type) const'] lines += ['{'] lines += ['\tif(Type < 0 || Type >= NUM_NETOBJTYPES) return 0;'] lines += ['\treturn ms_aObjSizes[Type];'] @@ -234,7 +234,7 @@ lines += [''] - lines += ['const char *CNetObjHandler::GetMsgName(int Type)'] + lines += ['const char *CNetObjHandler::GetMsgName(int Type) const'] lines += ['{'] lines += ['\tif(Type < 0 || Type >= NUM_NETMSGTYPES) return "(out of range)";'] lines += ['\treturn ms_apMsgNames[Type];'] diff -Nru ddnet-15.3.2/datasrc/network.py ddnet-15.5.4/datasrc/network.py --- ddnet-15.3.2/datasrc/network.py 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/datasrc/network.py 2021-06-20 09:38:48.000000000 +0000 @@ -27,6 +27,10 @@ "ALLOW_X_SKINS", "GAMETYPE_CITY", "GAMETYPE_FDDRACE", "ENTITIES_FDDRACE", ] ExPlayerFlags = ["AFK", "PAUSED", "SPEC"] +ProjectileFlags = ["CLIENTID_BIT{}".format(i) for i in range(8)] + [ + "NO_OWNER", "IS_DDNET", "BOUNCE_HORIZONTAL", "BOUNCE_VERTICAL", + "EXPLOSIVE", "FREEZE", +] Emoticons = ["OOP", "EXCLAMATION", "HEARTS", "DROP", "DOTDOT", "MUSIC", "SORRY", "GHOST", "SUSHI", "SPLATTEE", "DEVILTEE", "ZOMG", "ZZZ", "WTF", "EYES", "QUESTION"] @@ -83,6 +87,7 @@ Flags("GAMEINFOFLAG", GameInfoFlags), Flags("GAMEINFOFLAG2", GameInfoFlags2), Flags("EXPLAYERFLAG", ExPlayerFlags), + Flags("PROJECTILEFLAG", ProjectileFlags), ] Objects = [ @@ -247,6 +252,17 @@ NetIntAny("m_Flags2"), ], validate_size=False), + # The code assumes that this has the same in-memory representation as + # the Projectile net object. + NetObjectEx("DDNetProjectile", "projectile@netobj.ddnet.tw", [ + NetIntAny("m_X"), + NetIntAny("m_Y"), + NetIntAny("m_Angle"), + NetIntAny("m_Data"), + NetIntRange("m_Type", 0, 'NUM_WEAPONS-1'), + NetTick("m_StartTick"), + ]), + ## Events NetEvent("Common", [ @@ -409,22 +425,22 @@ NetStringStrict("m_Reason"), ], teehistorian=False), - NetMessage("Cl_IsDDNet", []), + NetMessage("Cl_IsDDNetLegacy", []), - NetMessage("Sv_DDRaceTime", [ + NetMessage("Sv_DDRaceTimeLegacy", [ NetIntAny("m_Time"), NetIntAny("m_Check"), NetIntRange("m_Finish", 0, 1), ]), - NetMessage("Sv_Record", [ + NetMessage("Sv_RecordLegacy", [ NetIntAny("m_ServerTimeBest"), NetIntAny("m_PlayerTimeBest"), ]), NetMessage("Unused", []), - NetMessage("Sv_TeamsState", []), + NetMessage("Sv_TeamsStateLegacy", []), # deprecated, use showothers@netmsg.ddnet.tw instead NetMessage("Cl_ShowOthersLegacy", [ @@ -444,4 +460,17 @@ NetMessageEx("Cl_ShowOthers", "showothers@netmsg.ddnet.tw", [ NetIntRange("m_Show", 0, 2), ]), + + NetMessageEx("Sv_TeamsState", "teamsstate@netmsg.ddnet.tw", []), + + NetMessageEx("Sv_DDRaceTime", "ddrace-time@netmsg.ddnet.tw", [ + NetIntAny("m_Time"), + NetIntAny("m_Check"), + NetIntRange("m_Finish", 0, 1), + ]), + + NetMessageEx("Sv_Record", "record@netmsg.ddnet.tw", [ + NetIntAny("m_ServerTimeBest"), + NetIntAny("m_PlayerTimeBest"), + ]), ] diff -Nru ddnet-15.3.2/datasrc/seven/network.py ddnet-15.5.4/datasrc/seven/network.py --- ddnet-15.3.2/datasrc/seven/network.py 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/datasrc/seven/network.py 2021-06-20 09:38:48.000000000 +0000 @@ -6,7 +6,7 @@ Votes = Enum("VOTE", ["UNKNOWN", "START_OP", "START_KICK", "START_SPEC", "END_ABORT", "END_PASS", "END_FAIL"]) # todo 0.8: add RUN_OP, RUN_KICK, RUN_SPEC; rem UNKNOWN ChatModes = Enum("CHAT", ["NONE", "ALL", "TEAM", "WHISPER"]) -PlayerFlags = Flags("PLAYERFLAG", ["ADMIN", "CHATTING", "SCOREBOARD", "READY", "DEAD", "WATCHING", "BOT"]) +PlayerFlags = Flags("PLAYERFLAG", ["ADMIN", "CHATTING", "SCOREBOARD", "READY", "DEAD", "WATCHING", "BOT", "AIM"]) GameFlags = Flags("GAMEFLAG", ["TEAMS", "FLAGS", "SURVIVAL", "RACE"]) GameStateFlags = Flags("GAMESTATEFLAG", ["WARMUP", "SUDDENDEATH", "ROUNDOVER", "GAMEOVER", "PAUSED", "STARTCOUNTDOWN"]) CoreEventFlags = Flags("COREEVENTFLAG", ["GROUND_JUMP", "AIR_JUMP", "HOOK_ATTACH_PLAYER", "HOOK_ATTACH_GROUND", "HOOK_HIT_NOHOOK"]) diff -Nru ddnet-15.3.2/debian/changelog ddnet-15.5.4/debian/changelog --- ddnet-15.3.2/debian/changelog 2021-02-28 08:30:45.000000000 +0000 +++ ddnet-15.5.4/debian/changelog 2021-10-05 15:55:37.000000000 +0000 @@ -1,10 +1,16 @@ -ddnet (15.3.2-1ubuntu1) hirsute; urgency=low +ddnet (15.5.4-1ubuntu1) impish; urgency=medium - * Merge from Debian unstable. Remaining changes: - - Lower optimization level on s390x to fix a build failure - - allow stderr because of s390x warnings + * Allow stderr to make warnings not fatal in autopkgtests on armhf and + ppc64el (see: #995742) - -- Gianfranco Costamagna Sun, 28 Feb 2021 09:30:45 +0100 + -- Gianfranco Costamagna Tue, 05 Oct 2021 17:55:37 +0200 + +ddnet (15.5.4-1) unstable; urgency=medium + + * New upstream release + * Bump Standards-Version to 4.6.0 + + -- Yangfl Thu, 30 Sep 2021 11:45:10 +0800 ddnet (15.3.2-1) unstable; urgency=medium @@ -14,28 +20,12 @@ -- Yangfl Sun, 21 Feb 2021 22:46:10 +0800 -ddnet (15.1.3-1ubuntu1) hirsute; urgency=low - - * Merge from Debian unstable. Remaining changes: - - Lower optimization level on s390x to fix a build failure - - allow stderr because of s390x warnings - - -- Gianfranco Costamagna Thu, 29 Oct 2020 08:46:01 +0100 - ddnet (15.1.3-1) unstable; urgency=medium * New upstream release -- Yangfl Sat, 17 Oct 2020 14:28:41 +0000 -ddnet (14.1-1ubuntu1) groovy; urgency=low - - * Merge from Debian unstable. Remaining changes: - - Lower optimization level on s390x to fix a build failure - - allow stderr because of s390x warnings - - -- Gianfranco Costamagna Thu, 02 Jul 2020 15:04:07 +0200 - ddnet (14.1-1) unstable; urgency=medium * New upstream release @@ -43,14 +33,6 @@ -- Yangfl Wed, 01 Jul 2020 12:29:20 +0800 -ddnet (13.0.1-1ubuntu1) groovy; urgency=low - - * Merge from Debian unstable. Remaining changes: - - Lower optimization level on s390x to fix a build failure - - allow stderr because of s390x warnings - - -- Gianfranco Costamagna Mon, 27 Apr 2020 15:38:40 +0200 - ddnet (13.0.1-1) unstable; urgency=medium * New upstream release @@ -59,13 +41,6 @@ -- Yangfl Fri, 10 Apr 2020 10:32:55 +0800 -ddnet (12.9.2-1ubuntu1) focal; urgency=medium - - * Lower optimization level on s390x to fix a build failure - * allow stderr because of s390x warnings - - -- Gianfranco Costamagna Wed, 26 Feb 2020 14:45:06 +0100 - ddnet (12.9.2-1) unstable; urgency=medium * New upstream release diff -Nru ddnet-15.3.2/debian/control ddnet-15.5.4/debian/control --- ddnet-15.3.2/debian/control 2021-02-27 20:46:27.000000000 +0000 +++ ddnet-15.5.4/debian/control 2021-09-30 03:45:10.000000000 +0000 @@ -27,7 +27,7 @@ libnotify-dev, libsqlite3-dev, Rules-Requires-Root: no -Standards-Version: 4.5.1 +Standards-Version: 4.6.0 Homepage: https://ddnet.tw/ Vcs-Git: https://salsa.debian.org/games-team/ddnet.git Vcs-Browser: https://salsa.debian.org/games-team/ddnet diff -Nru ddnet-15.3.2/debian/patches/use-system-libjsonparser.patch ddnet-15.5.4/debian/patches/use-system-libjsonparser.patch --- ddnet-15.3.2/debian/patches/use-system-libjsonparser.patch 2021-02-27 20:46:27.000000000 +0000 +++ ddnet-15.5.4/debian/patches/use-system-libjsonparser.patch 2021-09-30 03:45:10.000000000 +0000 @@ -1,8 +1,8 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index 725b7c11d..72807c97c 100644 +index 01bcfdd84..cc71e6666 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -595,7 +595,7 @@ set_src(DEP_MD5_SRC GLOB src/engine/external/md5 md5.c md5.h) +@@ -636,7 +636,7 @@ set_src(DEP_MD5_SRC GLOB src/engine/external/md5 md5.c md5.h) add_library(md5 EXCLUDE_FROM_ALL OBJECT ${DEP_MD5_SRC}) list(APPEND TARGETS_DEP json md5) @@ -11,7 +11,7 @@ set(DEP_MD5) if(NOT CRYPTO_FOUND) set(DEP_MD5 $) -@@ -1593,6 +1593,7 @@ set(LIBS +@@ -1651,6 +1651,7 @@ set(LIBS ${WEBSOCKETS_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_LIBS} @@ -20,7 +20,7 @@ # on it. ${CMAKE_THREAD_LIBS_INIT} diff --git a/src/engine/client/http.cpp b/src/engine/client/http.cpp -index be8cdc5d5..704a0d6c4 100644 +index 487750913..35b9be47f 100644 --- a/src/engine/client/http.cpp +++ b/src/engine/client/http.cpp @@ -2,7 +2,7 @@ @@ -33,33 +33,33 @@ #include #include diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp -index c2b46044b..3668544f7 100644 +index 022fde37f..a5e8e6e85 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp -@@ -20,7 +20,7 @@ +@@ -27,7 +27,7 @@ #include -#include +#include - #include "serverbrowser.h" class SortWrap + { diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h -index ec35a6e75..ab1d57c13 100644 +index 125d16d71..f506442bf 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h -@@ -4,7 +4,7 @@ - #define ENGINE_CLIENT_SERVERBROWSER_H - +@@ -6,7 +6,7 @@ + #include #include + #include -#include +#include #include #include #include diff --git a/src/engine/client/updater.cpp b/src/engine/client/updater.cpp -index 0004c85bd..56cd89c9f 100644 +index 6557ec2ad..b1fd1ebeb 100644 --- a/src/engine/client/updater.cpp +++ b/src/engine/client/updater.cpp @@ -2,7 +2,7 @@ diff -Nru ddnet-15.3.2/debian/rules ddnet-15.5.4/debian/rules --- ddnet-15.3.2/debian/rules 2021-02-27 20:46:27.000000000 +0000 +++ ddnet-15.5.4/debian/rules 2021-09-30 03:45:10.000000000 +0000 @@ -15,9 +15,6 @@ export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic endif -ifeq ($(DEB_HOST_ARCH),s390x) - export DEB_CXXFLAGS_MAINT_APPEND = -Wall -pedantic -O1 -endif %: dh $@ diff -Nru ddnet-15.3.2/.github/workflows/build.yaml ddnet-15.5.4/.github/workflows/build.yaml --- ddnet-15.3.2/.github/workflows/build.yaml 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/.github/workflows/build.yaml 2021-06-20 09:38:48.000000000 +0000 @@ -63,7 +63,7 @@ - name: Prepare Linux (fancy) if: contains(matrix.os, 'ubuntu') && matrix.fancy run: | - sudo apt-get install libboost-dev libmariadbclient-dev libmysqlcppconn-dev libwebsockets-dev -y + sudo apt-get install libmariadbclient-dev libwebsockets-dev -y - name: Prepare MacOS if: contains(matrix.os, 'macOS') @@ -80,11 +80,11 @@ cd debug ${{ matrix.cmake-path }}cmake --version ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Debug -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. .. - ${{ matrix.cmake-path }}cmake --build . --config Debug ${{ matrix.build-args }} --target everything + ${{ matrix.cmake-path }}cmake --build . --config Debug --target everything ${{ matrix.build-args }} - name: Test debug run: | cd debug - ${{ matrix.cmake-path }}cmake --build . --config Debug ${{ matrix.build-args }} --target run_tests + ${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests ${{ matrix.build-args }} ./DDNet-Server shutdown - name: Build in release mode @@ -93,11 +93,11 @@ mkdir release cd release ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Release -Werror=dev -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. .. - ${{ matrix.cmake-path }}cmake --build . --config Release ${{ matrix.build-args }} --target everything + ${{ matrix.cmake-path }}cmake --build . --config Release --target everything ${{ matrix.build-args }} - name: Test release run: | cd release - ${{ matrix.cmake-path }}cmake --build . --config Release ${{ matrix.build-args }} --target run_tests + ${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests ${{ matrix.build-args }} ./DDNet-Server shutdown - name: Build in release mode with debug info and all features on @@ -107,18 +107,18 @@ mkdir fancy cd fancy ${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DANTIBOT=ON -DMYSQL=ON -DWEBSOCKETS=ON .. - ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo ${{ matrix.build-args }} --target everything + ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target everything ${{ matrix.build-args }} - name: Test fancy if: matrix.fancy run: | cd release - ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo ${{ matrix.build-args }} --target run_tests + ${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests ${{ matrix.build-args }} ./DDNet-Server shutdown - name: Package run: | cd release - ${{ matrix.cmake-path }}cmake --build . --config Release ${{ matrix.build-args }} --target package_default + ${{ matrix.cmake-path }}cmake --build . --config Release --target package_default ${{ matrix.build-args }} mkdir artifacts mv ${{ matrix.package-file }} artifacts diff -Nru ddnet-15.3.2/license.txt ddnet-15.5.4/license.txt --- ddnet-15.3.2/license.txt 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/license.txt 2021-06-20 09:38:48.000000000 +0000 @@ -1,6 +1,6 @@ Teeworlds Copyright (C) 2007-2014 Magnus Auvinen DDRace Copyright (C) 2010-2011 Shereef Marzouk -DDNet Copyright (C) 2013-2020 Dennis Felsing +DDNet Copyright (C) 2013-2021 Dennis Felsing This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -21,7 +21,8 @@ ------------------------------------------------------------------------ All content under 'data' except the assets, font, language & skin files, -(which have their own licenses): +(which have their own licenses) are released under +CC-BY-SA 3.0 (http://creativecommons.org/licenses/by-sa/3.0/). Apache 2.0 for the 'Icon.tff' file: Copyright Google diff -Nru ddnet-15.3.2/other/ddnet.appdata.xml ddnet-15.5.4/other/ddnet.appdata.xml --- ddnet-15.3.2/other/ddnet.appdata.xml 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/other/ddnet.appdata.xml 2021-06-20 09:38:48.000000000 +0000 @@ -37,6 +37,7 @@ intense + diff -Nru ddnet-15.3.2/README.md ddnet-15.5.4/README.md --- ddnet-15.3.2/README.md 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/README.md 2021-06-20 09:38:48.000000000 +0000 @@ -30,11 +30,11 @@ You can install the required libraries on your system, `touch CMakeLists.txt` and CMake will use the system-wide libraries by default. You can install all required dependencies and CMake on Debian or Ubuntu like this: - sudo apt install build-essential cmake git libcurl4-openssl-dev libssl-dev libfreetype6-dev libglew-dev libnotify-dev libogg-dev libopus-dev libopusfile-dev libpnglite-dev libsdl2-dev libsqlite3-dev libwavpack-dev python + sudo apt install build-essential cmake git libcurl4-openssl-dev libssl-dev libfreetype6-dev libglew-dev libnotify-dev libogg-dev libopus-dev libopusfile-dev libpnglite-dev libsdl2-dev libsqlite3-dev libwavpack-dev python google-mock Or on Arch Linux like this: - sudo pacman -S --needed base-devel cmake curl freetype2 git glew libnotify opusfile python sdl2 sqlite wavpack + sudo pacman -S --needed base-devel cmake curl freetype2 git glew libnotify opusfile python sdl2 sqlite wavpack gmock There is an [AUR package for pnglite](https://aur.archlinux.org/packages/pnglite/). For instructions on installing it, see [AUR packages installation instructions on ArchWiki](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages). diff -Nru ddnet-15.3.2/scripts/dmg.py ddnet-15.5.4/scripts/dmg.py --- ddnet-15.3.2/scripts/dmg.py 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/scripts/dmg.py 2021-06-20 09:38:48.000000000 +0000 @@ -4,6 +4,7 @@ import shlex import subprocess import tempfile +import time ConfigDmgtools = namedtuple('Config', 'dmg hfsplus newfs_hfs verbose') ConfigHdiutil = namedtuple('Config', 'hdiutil verbose') @@ -36,10 +37,10 @@ def _dmg(self, *args): self._check_call((self.config.dmg,) + args) - def _create_hfs(self, hfs, volume_name, size): + def _create_hfs(self, hfs_fd, hfs, volume_name, size): if self.config.verbose >= 1: print("TRUNCATING {} to {} bytes".format(hfs, size)) - with open(hfs, 'wb') as f: + with os.fdopen(hfs_fd, 'wb') as f: f.truncate(size) self._mkfs_hfs('-v', volume_name, hfs) @@ -55,8 +56,8 @@ def create(self, dmg, volume_name, directory, symlinks): input_size = sum(os.stat(os.path.join(path, f)).st_size for path, dirs, files in os.walk(directory) for f in files) output_size = max(input_size * 2, 1024**2) - hfs = tempfile.mktemp(prefix=dmg + '.', suffix='.hfs') - self._create_hfs(hfs, volume_name, output_size) + hfs_fd, hfs = tempfile.mkstemp(prefix=dmg + '.', suffix='.hfs') + self._create_hfs(hfs_fd, hfs, volume_name, output_size) self._add(hfs, directory) for target, link_name in symlinks: self._symlink(hfs, target, link_name) @@ -72,7 +73,16 @@ def create(self, dmg, volume_name, directory, symlinks): if symlinks: raise NotImplementedError("symlinks are not yet implemented") - self._hdiutil('create', '-volname', volume_name, '-srcdir', directory, dmg) + for i in range(5): + try: + self._hdiutil('create', '-volname', volume_name, '-srcdir', directory, dmg) + except subprocess.CalledProcessError as e: + if i == 4: + raise e + print("Retrying hdiutil create") + time.sleep(5) + else: + break def main(): p = argparse.ArgumentParser(description="Manipulate dmg archives") diff -Nru ddnet-15.3.2/scripts/fix_style.py ddnet-15.5.4/scripts/fix_style.py --- ddnet-15.3.2/scripts/fix_style.py 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/scripts/fix_style.py 2021-06-20 09:38:48.000000000 +0000 @@ -26,11 +26,27 @@ return [filename for filename in filenames if any(filename.endswith(ext) for ext in ".c .cpp .h".split())] +def find_clang_format(version): + for binary in ( + "clang-format", + "clang-format-{}".format(version), + "/opt/clang-format-static/clang-format-{}".format(version)): + try: + out = subprocess.check_output([binary, "--version"]) + except FileNotFoundError: + continue + if "clang-format version {}.".format(version) in out.decode("utf-8"): + return binary + print("Found no clang-format {}".format(version)) + sys.exit(-1) + +clang_format_bin = find_clang_format(10) + def reformat(filenames): - subprocess.check_call(["clang-format", "-i"] + filenames) + subprocess.check_call([clang_format_bin, "-i"] + filenames) def warn(filenames): - return subprocess.call(["clang-format", "-Werror", "--dry-run"] + filenames) + return subprocess.call([clang_format_bin, "-Werror", "--dry-run"] + filenames) def main(): p = argparse.ArgumentParser(description="Check and fix style of changed files") diff -Nru ddnet-15.3.2/src/base/detect.h ddnet-15.5.4/src/base/detect.h --- ddnet-15.3.2/src/base/detect.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/detect.h 2021-06-20 09:38:48.000000000 +0000 @@ -51,6 +51,7 @@ #define CONF_FAMILY_STRING "unix" #define CONF_PLATFORM_LINUX 1 #define PLATFORM_STRING "linux" +#define CONF_BACKEND_OPENGL_ES3 1 #endif #if defined(__GNU__) || defined(__gnu__) @@ -63,8 +64,8 @@ #if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__) #define CONF_FAMILY_UNIX 1 #define CONF_FAMILY_STRING "unix" -#define CONF_PLATFORM_MACOSX 1 -#define PLATFORM_STRING "macosx" +#define CONF_PLATFORM_MACOS 1 +#define PLATFORM_STRING "macos" #endif #if defined(__sun) @@ -82,6 +83,13 @@ #define PLATFORM_STRING "beos" #endif +#if defined(__HAIKU__) +#define CONF_FAMILY_UNIX 1 +#define CONF_FAMILY_STRING "unix" +#define CONF_PLATFORM_HAIKU 1 +#define CONF_PLATFORM_STRING "haiku" +#endif + /* use gcc endianness definitions when available */ #if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun) #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) diff -Nru ddnet-15.3.2/src/base/system.c ddnet-15.5.4/src/base/system.c --- ddnet-15.3.2/src/base/system.c 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/system.c 2021-06-20 09:38:48.000000000 +0000 @@ -35,7 +35,7 @@ #include -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) // some lock and pthread functions are already defined in headers // included from Carbon.h // this prevents having duplicate definitions of those @@ -177,10 +177,16 @@ char u16[4] = {0}; if(codepoint < 0) + { + free(wide); return; + } if(str_utf16le_encode(u16, codepoint) != 2) + { + free(wide); return; + } mem_copy(&wide[wlen], u16, 2); } @@ -188,6 +194,7 @@ console = GetStdHandle(STD_OUTPUT_HANDLE); WriteConsoleW(console, wide, wlen, NULL, NULL); WriteConsoleA(console, "\n", 1, NULL, NULL); + free(wide); } #endif @@ -882,7 +889,7 @@ void sphore_wait(SEMAPHORE *sem) { WaitForSingleObject((HANDLE)*sem, INFINITE); } void sphore_signal(SEMAPHORE *sem) { ReleaseSemaphore((HANDLE)*sem, 1, NULL); } void sphore_destroy(SEMAPHORE *sem) { CloseHandle((HANDLE)*sem); } -#elif defined(CONF_PLATFORM_MACOSX) +#elif defined(CONF_PLATFORM_MACOS) void sphore_init(SEMAPHORE *sem) { char aBuf[64]; @@ -935,7 +942,7 @@ { static int64 last = 0; { -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) static int got_timebase = 0; mach_timebase_info_data_t timebase; uint64 time; @@ -986,7 +993,7 @@ int64 time_freq(void) { -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) return 1000000000; #elif defined(CONF_FAMILY_UNIX) return 1000000; @@ -1082,6 +1089,73 @@ return net_addr_comp(&ta, &tb); } +void net_addr_str_v6(const unsigned short ip[8], int port, char *buffer, int buffer_size) +{ + int longest_seq_len = 0; + int longest_seq_start = -1; + int w = 0; + int i; + { + int seq_len = 0; + int seq_start = -1; + // Determine longest sequence of zeros. + for(i = 0; i < 8 + 1; i++) + { + if(seq_start != -1) + { + if(i == 8 || ip[i] != 0) + { + if(longest_seq_len < seq_len) + { + longest_seq_len = seq_len; + longest_seq_start = seq_start; + } + seq_len = 0; + seq_start = -1; + } + else + { + seq_len += 1; + } + } + else + { + if(i != 8 && ip[i] == 0) + { + seq_start = i; + seq_len = 1; + } + } + } + } + if(longest_seq_len <= 1) + { + longest_seq_len = 0; + longest_seq_start = -1; + } + w += str_format(buffer + w, buffer_size - w, "["); + for(i = 0; i < 8; i++) + { + if(longest_seq_start <= i && i < longest_seq_start + longest_seq_len) + { + if(i == longest_seq_start) + { + w += str_format(buffer + w, buffer_size - w, "::"); + } + } + else + { + char *colon = i == 0 || i == longest_seq_start + longest_seq_len ? "" : ":"; + w += str_format(buffer + w, buffer_size - w, "%s%x", colon, ip[i]); + } + } + w += str_format(buffer + w, buffer_size - w, "]"); + if(port >= 0) + { + str_format(buffer + w, buffer_size - w, ":%d", port); + } +} + void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port) { if(addr->type == NETTYPE_IPV4 || addr->type == NETTYPE_WEBSOCKET_IPV4) @@ -1093,15 +1167,18 @@ } else if(addr->type == NETTYPE_IPV6) { - if(add_port != 0) - str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", - (addr->ip[0] << 8) | addr->ip[1], (addr->ip[2] << 8) | addr->ip[3], (addr->ip[4] << 8) | addr->ip[5], (addr->ip[6] << 8) | addr->ip[7], - (addr->ip[8] << 8) | addr->ip[9], (addr->ip[10] << 8) | addr->ip[11], (addr->ip[12] << 8) | addr->ip[13], (addr->ip[14] << 8) | addr->ip[15], - addr->port); - else - str_format(string, max_length, "[%x:%x:%x:%x:%x:%x:%x:%x]", - (addr->ip[0] << 8) | addr->ip[1], (addr->ip[2] << 8) | addr->ip[3], (addr->ip[4] << 8) | addr->ip[5], (addr->ip[6] << 8) | addr->ip[7], - (addr->ip[8] << 8) | addr->ip[9], (addr->ip[10] << 8) | addr->ip[11], (addr->ip[12] << 8) | addr->ip[13], (addr->ip[14] << 8) | addr->ip[15]); + int port = -1; + unsigned short ip[8]; + int i; + if(add_port) + { + port = addr->port; + } + for(i = 0; i < 8; i++) + { + ip[i] = (addr->ip[i * 2] << 8) | (addr->ip[i * 2 + 1]); + } + net_addr_str_v6(ip, port, string, max_length); } else str_format(string, max_length, "unknown type %d", addr->type); @@ -1280,6 +1357,10 @@ if(parse_uint16(&addr->port, &str)) return -1; } + else + { + addr->port = 0; + } } else return -1; @@ -2061,13 +2142,18 @@ return 0; #else char *home = getenv("HOME"); -#if !defined(CONF_PLATFORM_MACOSX) +#if !defined(CONF_PLATFORM_MACOS) int i; #endif if(!home) return -1; -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_HAIKU) + str_format(path, max, "%s/config/settings/%s", home, appname); + return 0; +#endif + +#if defined(CONF_PLATFORM_MACOS) snprintf(path, max, "%s/Library/Application Support/%s", home, appname); #else snprintf(path, max, "%s/.%s", home, appname); @@ -2106,6 +2192,11 @@ return 0; return -1; #else +#ifdef CONF_PLATFORM_HAIKU + struct stat st; + if(stat(path, &st) == 0) + return 0; +#endif if(mkdir(path, 0755) == 0) return 0; if(errno == EEXIST) @@ -2114,6 +2205,19 @@ #endif } +int fs_removedir(const char *path) +{ +#if defined(CONF_FAMILY_WINDOWS) + if(_rmdir(path) == 0) + return 0; + return -1; +#else + if(rmdir(path) == 0) + return 0; + return -1; +#endif +} + int fs_is_dir(const char *path) { #if defined(CONF_FAMILY_WINDOWS) @@ -2192,9 +2296,11 @@ int fs_remove(const char *filename) { - if(remove(filename) != 0) - return 1; - return 0; +#if defined(CONF_FAMILY_WINDOWS) + return _unlink(filename) != 0; +#else + return unlink(filename) != 0; +#endif } int fs_rename(const char *oldname, const char *newname) @@ -3010,8 +3116,8 @@ int str_utf8_isspace(int code) { - return code <= 0x0020 || code == 0x0085 || code == 0x00A0 || - code == 0x034F || code == 0x1160 || code == 0x1680 || code == 0x180E || + return code <= 0x0020 || code == 0x0085 || code == 0x00A0 || code == 0x034F || + code == 0x115F || 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 == 0x3164 || @@ -3517,6 +3623,34 @@ return (int)(i % RAND_MAX); } +// From https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2. +static unsigned int find_next_power_of_two_minus_one(unsigned int n) +{ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 4; + n |= n >> 16; + return n; +} + +int secure_rand_below(int below) +{ + unsigned int mask = find_next_power_of_two_minus_one(below); + dbg_assert(below > 0, "below must be positive"); + while(1) + { + unsigned int n; + secure_random_fill(&n, sizeof(n)); + n &= mask; + if((int)n < below) + { + return n; + } + } +} + #if defined(__cplusplus) } #endif diff -Nru ddnet-15.3.2/src/base/system.h ddnet-15.5.4/src/base/system.h --- ddnet-15.3.2/src/base/system.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/system.h 2021-06-20 09:38:48.000000000 +0000 @@ -611,7 +611,7 @@ /* Group: Semaphores */ #if defined(CONF_FAMILY_WINDOWS) typedef void *SEMAPHORE; -#elif defined(CONF_PLATFORM_MACOSX) +#elif defined(CONF_PLATFORM_MACOS) #include typedef sem_t *SEMAPHORE; #elif defined(CONF_FAMILY_UNIX) @@ -631,7 +631,11 @@ /* if compiled with -pedantic-errors it will complain about long not being a C90 thing. */ +#ifdef CONF_PLATFORM_HAIKU +#include +#else __extension__ typedef long long int64; +#endif __extension__ typedef unsigned long long uint64; #else typedef long long int64; @@ -1635,6 +1639,21 @@ int fs_makedir(const char *path); /* + Function: fs_removedir + Removes a directory + + Parameters: + path - Directory to remove + + Returns: + Returns 0 on success. Negative value on failure. + + Remarks: + Cannot remove a non-empty directory. +*/ +int fs_removedir(const char *path); + +/* Function: fs_makedir_rec_for Recursively create directories for a file @@ -1655,7 +1674,7 @@ Remarks: - Returns ~/.appname on UNIX based systems - - Returns ~/Library/Applications Support/appname on Mac OS X + - Returns ~/Library/Applications Support/appname on macOS - Returns %APPDATA%/Appname on Windows based systems */ int fs_storage_path(const char *appname, char *path, int max); @@ -1720,6 +1739,7 @@ Remarks: - The strings are treated as zero-terminated strings. + - Returns an error if the path specifies a directory name. */ int fs_remove(const char *filename); @@ -2189,6 +2209,16 @@ */ int secure_rand(void); +/* + Function: secure_rand_below + Returns a random nonnegative integer below the given number, + with a uniform distribution. + + Parameters: + below - Upper limit (exclusive) of integers to return. +*/ +int secure_rand_below(int below); + #ifdef __cplusplus } #endif diff -Nru ddnet-15.3.2/src/base/tl/algorithm.h ddnet-15.5.4/src/base/tl/algorithm.h --- ddnet-15.3.2/src/base/tl/algorithm.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/tl/algorithm.h 2021-06-20 09:38:48.000000000 +0000 @@ -109,7 +109,7 @@ template void sort(R range) { - std::sort(&range.front(), &range.back() + 1); + std::stable_sort(&range.front(), &range.back() + 1); } template diff -Nru ddnet-15.3.2/src/base/tl/array.h ddnet-15.5.4/src/base/tl/array.h --- ddnet-15.3.2/src/base/tl/array.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/tl/array.h 2021-06-20 09:38:48.000000000 +0000 @@ -280,7 +280,7 @@ Function: memusage Returns how much memory this dynamic array is using */ - int memusage() + int memusage() const { return sizeof(array) + sizeof(T) * list_size; } @@ -303,7 +303,7 @@ Function: all Returns a range that contains the whole array. */ - range all() { return range(list, list + num_elements); } + range all() const { return range(list, list + num_elements); } protected: void incsize() diff -Nru ddnet-15.3.2/src/base/tl/sorted_array.h ddnet-15.5.4/src/base/tl/sorted_array.h --- ddnet-15.3.2/src/base/tl/sorted_array.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/base/tl/sorted_array.h 2021-06-20 09:38:48.000000000 +0000 @@ -38,7 +38,8 @@ void sort_range() { - sort(all()); + if(parent::size() > 0) + sort(all()); } /* diff -Nru ddnet-15.3.2/src/engine/client/backend/glsl_shader_compiler.cpp ddnet-15.5.4/src/engine/client/backend/glsl_shader_compiler.cpp --- ddnet-15.3.2/src/engine/client/backend/glsl_shader_compiler.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/glsl_shader_compiler.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,244 @@ +#include "glsl_shader_compiler.h" + +#include +#include + +CGLSLCompiler::CGLSLCompiler(int OpenGLVersionMajor, int OpenGLVersionMinor, int OpenGLVersionPatch, bool IsOpenGLES, float TextureLODBias) +{ + m_OpenGLVersionMajor = OpenGLVersionMajor; + m_OpenGLVersionMinor = OpenGLVersionMinor; + m_OpenGLVersionPatch = OpenGLVersionPatch; + + m_IsOpenGLES = IsOpenGLES; + + m_TextureLODBias = TextureLODBias; + + m_HasTextureArray = false; + m_TextureReplaceType = 0; +} + +void CGLSLCompiler::AddDefine(const std::string &DefineName, const std::string &DefineValue) +{ + m_Defines.emplace_back(SGLSLCompilerDefine(DefineName, DefineValue)); +} + +void CGLSLCompiler::AddDefine(const char *pDefineName, const char *pDefineValue) +{ + AddDefine(std::string(pDefineName), std::string(pDefineValue)); +} + +void CGLSLCompiler::ClearDefines() +{ + m_Defines.clear(); +} + +void CGLSLCompiler::ParseLine(std::string &Line, const char *pReadLine, EGLSLShaderCompilerType Type) +{ + EBackendType BackendType = m_IsOpenGLES ? BACKEND_TYPE_OPENGL_ES : BACKEND_TYPE_OPENGL; + bool IsNewOpenGL = (BackendType == BACKEND_TYPE_OPENGL ? (m_OpenGLVersionMajor >= 4 || (m_OpenGLVersionMajor == 3 && m_OpenGLVersionMinor == 3)) : m_OpenGLVersionMajor >= 3); + if(!IsNewOpenGL) + { + const char *pBuff = pReadLine; + char aTmpStr[1024]; + size_t TmpStrSize = 0; + while(*pBuff) + { + while(*pBuff && str_isspace(*pBuff)) + { + Line.append(1, *pBuff); + ++pBuff; + } + + while(*pBuff && !str_isspace(*pBuff) && *pBuff != '(' && *pBuff != '.') + { + aTmpStr[TmpStrSize++] = *pBuff; + ++pBuff; + } + + if(TmpStrSize > 0) + { + aTmpStr[TmpStrSize] = 0; + TmpStrSize = 0; + if(str_comp(aTmpStr, "layout") == 0) + { + //search for ' in' + while(*pBuff && (*pBuff != ' ' || (*(pBuff + 1) && *(pBuff + 1) != 'i') || *(pBuff + 2) != 'n')) + { + ++pBuff; + } + + if(*pBuff == ' ' && *(pBuff + 1) && *(pBuff + 1) == 'i' && *(pBuff + 2) == 'n') + { + pBuff += 3; + Line.append("attribute"); + Line.append(pBuff); + return; + } + else + { + dbg_msg("shadercompiler", "Fix shader for older OpenGL versions."); + } + } + else if(str_comp(aTmpStr, "noperspective") == 0 || str_comp(aTmpStr, "smooth") == 0 || str_comp(aTmpStr, "flat") == 0) + { + //search for 'in' or 'out' + while(*pBuff && ((*pBuff != 'i' || *(pBuff + 1) != 'n') && (*pBuff != 'o' || (*(pBuff + 1) && *(pBuff + 1) != 'u') || *(pBuff + 2) != 't'))) + { + ++pBuff; + } + + bool Found = false; + if(*pBuff) + { + if(*pBuff == 'i' && *(pBuff + 1) == 'n') + { + pBuff += 2; + Found = true; + } + else if(*pBuff == 'o' && *(pBuff + 1) && *(pBuff + 1) == 'u' && *(pBuff + 2) == 't') + { + pBuff += 3; + Found = true; + } + } + + if(!Found) + { + dbg_msg("shadercompiler", "Fix shader for older OpenGL versions."); + } + + Line.append("varying"); + Line.append(pBuff); + return; + } + else if(str_comp(aTmpStr, "out") == 0 || str_comp(aTmpStr, "in") == 0) + { + if(Type == GLSL_SHADER_COMPILER_TYPE_FRAGMENT && str_comp(aTmpStr, "out") == 0) + return; + Line.append("varying"); + Line.append(pBuff); + return; + } + else if(str_comp(aTmpStr, "FragClr") == 0) + { + Line.append("gl_FragColor"); + Line.append(pBuff); + return; + } + else if(str_comp(aTmpStr, "texture") == 0) + { + if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D) + Line.append("texture2D"); + else if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D) + Line.append("texture3D"); + else if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY) + Line.append("texture2DArray"); + std::string RestLine; + ParseLine(RestLine, pBuff, Type); + Line.append(RestLine); + return; + } + else + { + Line.append(aTmpStr); + } + } + + if(*pBuff) + { + Line.append(1, *pBuff); + ++pBuff; + } + } + } + else + { + if(BackendType == BACKEND_TYPE_OPENGL_ES) + { + const char *pBuff = pReadLine; + char aTmpStr[1024]; + size_t TmpStrSize = 0; + while(*pBuff) + { + while(*pBuff && str_isspace(*pBuff)) + { + Line.append(1, *pBuff); + ++pBuff; + } + + while(*pBuff && !str_isspace(*pBuff) && *pBuff != '(' && *pBuff != '.') + { + aTmpStr[TmpStrSize++] = *pBuff; + ++pBuff; + } + + if(TmpStrSize > 0) + { + aTmpStr[TmpStrSize] = 0; + TmpStrSize = 0; + + if(str_comp(aTmpStr, "noperspective") == 0) + { + Line.append("smooth"); + Line.append(pBuff); + return; + } + // since GLES doesnt support texture LOD bias as global state, use the shader function instead(since GLES 3.0 uses shaders only anyway) + else if(str_comp(aTmpStr, "texture") == 0) + { + Line.append("texture"); + // check opening and closing brackets to find the end + int CurBrackets = 1; + while(*pBuff && *pBuff != '(') + { + Line.append(1, *pBuff); + + ++pBuff; + } + + if(*pBuff) + { + Line.append(1, *pBuff); + ++pBuff; + } + + while(*pBuff) + { + if(*pBuff == '(') + ++CurBrackets; + if(*pBuff == ')') + --CurBrackets; + + if(CurBrackets == 0) + { + // found end + Line.append(std::string(", ") + std::to_string(m_TextureLODBias) + ")"); + ++pBuff; + break; + } + else + Line.append(1, *pBuff); + ++pBuff; + } + + Line.append(pBuff); + + return; + } + else + { + Line.append(aTmpStr); + } + } + + if(*pBuff) + { + Line.append(1, *pBuff); + ++pBuff; + } + } + } + else + Line = pReadLine; + } +} diff -Nru ddnet-15.3.2/src/engine/client/backend/glsl_shader_compiler.h ddnet-15.5.4/src/engine/client/backend/glsl_shader_compiler.h --- ddnet-15.3.2/src/engine/client/backend/glsl_shader_compiler.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/glsl_shader_compiler.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,60 @@ +#ifndef ENGINE_CLIENT_BACKEND_GLSL_SHADER_COMPILER_H +#define ENGINE_CLIENT_BACKEND_GLSL_SHADER_COMPILER_H + +#include +#include + +enum EGLSLShaderCompilerType +{ + GLSL_SHADER_COMPILER_TYPE_VERTEX = 0, + GLSL_SHADER_COMPILER_TYPE_FRAGMENT, +}; + +class CGLSLCompiler +{ +private: + friend class CGLSL; + + struct SGLSLCompilerDefine + { + SGLSLCompilerDefine(const std::string &DefineName, const std::string &DefineValue) + { + m_DefineName = DefineName; + m_DefineValue = DefineValue; + } + std::string m_DefineName; + std::string m_DefineValue; + }; + + std::vector m_Defines; + + int m_OpenGLVersionMajor; + int m_OpenGLVersionMinor; + int m_OpenGLVersionPatch; + + bool m_IsOpenGLES; + + float m_TextureLODBias; + + bool m_HasTextureArray; + int m_TextureReplaceType; // @see EGLSLCompilerTextureReplaceType +public: + CGLSLCompiler(int OpenGLVersionMajor, int OpenGLVersionMinor, int OpenGLVersionPatch, bool IsOpenGLES, float TextureLODBias); + void SetHasTextureArray(bool TextureArray) { m_HasTextureArray = TextureArray; } + void SetTextureReplaceType(int TextureReplaceType) { m_TextureReplaceType = TextureReplaceType; } + + void AddDefine(const std::string &DefineName, const std::string &DefineValue); + void AddDefine(const char *pDefineName, const char *pDefineValue); + void ClearDefines(); + + void ParseLine(std::string &Line, const char *pReadLine, EGLSLShaderCompilerType Type); + + enum EGLSLCompilerTextureReplaceType + { + GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D = 0, + GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D, + GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY, + }; +}; + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl3.cpp ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl3.cpp --- ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl3.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl3.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,1571 @@ +#include "backend_opengl3.h" + +#include + +#ifndef BACKEND_AS_OPENGL_ES +#include +#else +#include +#endif + +#include +#include + +#include + +#include + +// ------------ CCommandProcessorFragment_OpenGL3_3 +int CCommandProcessorFragment_OpenGL3_3::TexFormatToNewOpenGLFormat(int TexFormat) +{ + if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) + return GL_RGB; + if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) + return GL_RED; + if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) + return GL_RGBA; + return GL_RGBA; +} + +void CCommandProcessorFragment_OpenGL3_3::UseProgram(CGLSLTWProgram *pProgram) +{ + if(m_LastProgramID != pProgram->GetProgramID()) + { + pProgram->UseProgram(); + m_LastProgramID = pProgram->GetProgramID(); + } +} + +void CCommandProcessorFragment_OpenGL3_3::InitPrimExProgram(CGLSLPrimitiveExProgram *pProgram, CGLSLCompiler *pCompiler, IStorage *pStorage, bool Textured, bool Rotationless) +{ + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + if(Textured) + pCompiler->AddDefine("TW_TEXTURED", ""); + if(Rotationless) + pCompiler->AddDefine("TW_ROTATIONLESS", ""); + PrimitiveVertexShader.LoadShader(pCompiler, pStorage, "shader/primex.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(pCompiler, pStorage, "shader/primex.frag", GL_FRAGMENT_SHADER); + if(Textured || Rotationless) + pCompiler->ClearDefines(); + + pProgram->CreateProgram(); + pProgram->AddShader(&PrimitiveVertexShader); + pProgram->AddShader(&PrimitiveFragmentShader); + pProgram->LinkProgram(); + + UseProgram(pProgram); + + pProgram->m_LocPos = pProgram->GetUniformLoc("gPos"); + pProgram->m_LocTextureSampler = pProgram->GetUniformLoc("gTextureSampler"); + pProgram->m_LocRotation = pProgram->GetUniformLoc("gRotation"); + pProgram->m_LocCenter = pProgram->GetUniformLoc("gCenter"); + pProgram->m_LocVertciesColor = pProgram->GetUniformLoc("gVerticesColor"); + + pProgram->SetUniform(pProgram->m_LocRotation, 0.0f); + float Center[2] = {0.f, 0.f}; + pProgram->SetUniformVec2(pProgram->m_LocCenter, 1, Center); +} + +bool CCommandProcessorFragment_OpenGL3_3::Cmd_Init(const SCommand_Init *pCommand) +{ + if(!InitOpenGL(pCommand)) + return false; + + m_OpenGLTextureLodBIAS = g_Config.m_GfxOpenGLTextureLODBIAS; + + m_UseMultipleTextureUnits = g_Config.m_GfxEnableTextureUnitOptimization; + if(!m_UseMultipleTextureUnits) + { + glActiveTexture(GL_TEXTURE0); + } + + m_Has2DArrayTextures = true; + m_Has2DArrayTexturesAsExtension = false; + m_2DArrayTarget = GL_TEXTURE_2D_ARRAY; + m_Has3DTextures = false; + m_HasMipMaps = true; + m_HasNPOTTextures = true; + m_HasShaders = true; + + m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage; + m_pTextureMemoryUsage->store(0, std::memory_order_relaxed); + m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; + m_LastClipEnable = false; + m_pPrimitiveProgram = new CGLSLPrimitiveProgram; + m_pPrimitiveProgramTextured = new CGLSLPrimitiveProgram; + m_pTileProgram = new CGLSLTileProgram; + m_pTileProgramTextured = new CGLSLTileProgram; + m_pPrimitive3DProgram = new CGLSLPrimitiveProgram; + m_pPrimitive3DProgramTextured = new CGLSLPrimitiveProgram; + m_pBorderTileProgram = new CGLSLTileProgram; + m_pBorderTileProgramTextured = new CGLSLTileProgram; + m_pBorderTileLineProgram = new CGLSLTileProgram; + m_pBorderTileLineProgramTextured = new CGLSLTileProgram; + m_pQuadProgram = new CGLSLQuadProgram; + m_pQuadProgramTextured = new CGLSLQuadProgram; + m_pTextProgram = new CGLSLTextProgram; + m_pPrimitiveExProgram = new CGLSLPrimitiveExProgram; + m_pPrimitiveExProgramTextured = new CGLSLPrimitiveExProgram; + m_pPrimitiveExProgramRotationless = new CGLSLPrimitiveExProgram; + m_pPrimitiveExProgramTexturedRotationless = new CGLSLPrimitiveExProgram; + m_pSpriteProgramMultiple = new CGLSLSpriteMultipleProgram; + m_LastProgramID = 0; + + CGLSLCompiler ShaderCompiler(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch, m_IsOpenGLES, m_OpenGLTextureLodBIAS / 1000.0f); + + GLint CapVal; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &CapVal); + + m_MaxQuadsAtOnce = minimum(((CapVal - 20) / (3 * 4)), m_MaxQuadsPossible); + + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.frag", GL_FRAGMENT_SHADER); + + m_pPrimitiveProgram->CreateProgram(); + m_pPrimitiveProgram->AddShader(&PrimitiveVertexShader); + m_pPrimitiveProgram->AddShader(&PrimitiveFragmentShader); + m_pPrimitiveProgram->LinkProgram(); + + UseProgram(m_pPrimitiveProgram); + + m_pPrimitiveProgram->m_LocPos = m_pPrimitiveProgram->GetUniformLoc("gPos"); + m_pPrimitiveProgram->m_LocTextureSampler = m_pPrimitiveProgram->GetUniformLoc("gTextureSampler"); + } + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + ShaderCompiler.AddDefine("TW_TEXTURED", ""); + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pPrimitiveProgramTextured->CreateProgram(); + m_pPrimitiveProgramTextured->AddShader(&PrimitiveVertexShader); + m_pPrimitiveProgramTextured->AddShader(&PrimitiveFragmentShader); + m_pPrimitiveProgramTextured->LinkProgram(); + + UseProgram(m_pPrimitiveProgramTextured); + + m_pPrimitiveProgramTextured->m_LocPos = m_pPrimitiveProgramTextured->GetUniformLoc("gPos"); + m_pPrimitiveProgramTextured->m_LocTextureSampler = m_pPrimitiveProgramTextured->GetUniformLoc("gTextureSampler"); + } + + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + ShaderCompiler.AddDefine("TW_MODERN_GL", ""); + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pPrimitive3DProgram->CreateProgram(); + m_pPrimitive3DProgram->AddShader(&PrimitiveVertexShader); + m_pPrimitive3DProgram->AddShader(&PrimitiveFragmentShader); + m_pPrimitive3DProgram->LinkProgram(); + + UseProgram(m_pPrimitive3DProgram); + + m_pPrimitive3DProgram->m_LocPos = m_pPrimitive3DProgram->GetUniformLoc("gPos"); + } + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + ShaderCompiler.AddDefine("TW_MODERN_GL", ""); + ShaderCompiler.AddDefine("TW_TEXTURED", ""); + if(!pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.AddDefine("TW_3D_TEXTURED", ""); + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pPrimitive3DProgramTextured->CreateProgram(); + m_pPrimitive3DProgramTextured->AddShader(&PrimitiveVertexShader); + m_pPrimitive3DProgramTextured->AddShader(&PrimitiveFragmentShader); + m_pPrimitive3DProgramTextured->LinkProgram(); + + UseProgram(m_pPrimitive3DProgramTextured); + + m_pPrimitive3DProgramTextured->m_LocPos = m_pPrimitive3DProgramTextured->GetUniformLoc("gPos"); + m_pPrimitive3DProgramTextured->m_LocTextureSampler = m_pPrimitive3DProgramTextured->GetUniformLoc("gTextureSampler"); + } + + { + CGLSL VertexShader; + CGLSL FragmentShader; + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + + m_pTileProgram->CreateProgram(); + m_pTileProgram->AddShader(&VertexShader); + m_pTileProgram->AddShader(&FragmentShader); + m_pTileProgram->LinkProgram(); + + UseProgram(m_pTileProgram); + + m_pTileProgram->m_LocPos = m_pTileProgram->GetUniformLoc("gPos"); + m_pTileProgram->m_LocColor = m_pTileProgram->GetUniformLoc("gVertColor"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pTileProgramTextured->CreateProgram(); + m_pTileProgramTextured->AddShader(&VertexShader); + m_pTileProgramTextured->AddShader(&FragmentShader); + m_pTileProgramTextured->LinkProgram(); + + UseProgram(m_pTileProgramTextured); + + m_pTileProgramTextured->m_LocPos = m_pTileProgramTextured->GetUniformLoc("gPos"); + m_pTileProgramTextured->m_LocTextureSampler = m_pTileProgramTextured->GetUniformLoc("gTextureSampler"); + m_pTileProgramTextured->m_LocColor = m_pTileProgramTextured->GetUniformLoc("gVertColor"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_BORDER", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pBorderTileProgram->CreateProgram(); + m_pBorderTileProgram->AddShader(&VertexShader); + m_pBorderTileProgram->AddShader(&FragmentShader); + m_pBorderTileProgram->LinkProgram(); + + UseProgram(m_pBorderTileProgram); + + m_pBorderTileProgram->m_LocPos = m_pBorderTileProgram->GetUniformLoc("gPos"); + m_pBorderTileProgram->m_LocColor = m_pBorderTileProgram->GetUniformLoc("gVertColor"); + m_pBorderTileProgram->m_LocOffset = m_pBorderTileProgram->GetUniformLoc("gOffset"); + m_pBorderTileProgram->m_LocDir = m_pBorderTileProgram->GetUniformLoc("gDir"); + m_pBorderTileProgram->m_LocJumpIndex = m_pBorderTileProgram->GetUniformLoc("gJumpIndex"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_BORDER", ""); + ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pBorderTileProgramTextured->CreateProgram(); + m_pBorderTileProgramTextured->AddShader(&VertexShader); + m_pBorderTileProgramTextured->AddShader(&FragmentShader); + m_pBorderTileProgramTextured->LinkProgram(); + + UseProgram(m_pBorderTileProgramTextured); + + m_pBorderTileProgramTextured->m_LocPos = m_pBorderTileProgramTextured->GetUniformLoc("gPos"); + m_pBorderTileProgramTextured->m_LocTextureSampler = m_pBorderTileProgramTextured->GetUniformLoc("gTextureSampler"); + m_pBorderTileProgramTextured->m_LocColor = m_pBorderTileProgramTextured->GetUniformLoc("gVertColor"); + m_pBorderTileProgramTextured->m_LocOffset = m_pBorderTileProgramTextured->GetUniformLoc("gOffset"); + m_pBorderTileProgramTextured->m_LocDir = m_pBorderTileProgramTextured->GetUniformLoc("gDir"); + m_pBorderTileProgramTextured->m_LocJumpIndex = m_pBorderTileProgramTextured->GetUniformLoc("gJumpIndex"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_BORDER_LINE", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pBorderTileLineProgram->CreateProgram(); + m_pBorderTileLineProgram->AddShader(&VertexShader); + m_pBorderTileLineProgram->AddShader(&FragmentShader); + m_pBorderTileLineProgram->LinkProgram(); + + UseProgram(m_pBorderTileLineProgram); + + m_pBorderTileLineProgram->m_LocPos = m_pBorderTileLineProgram->GetUniformLoc("gPos"); + m_pBorderTileLineProgram->m_LocColor = m_pBorderTileLineProgram->GetUniformLoc("gVertColor"); + m_pBorderTileLineProgram->m_LocOffset = m_pBorderTileLineProgram->GetUniformLoc("gOffset"); + m_pBorderTileLineProgram->m_LocDir = m_pBorderTileLineProgram->GetUniformLoc("gDir"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_BORDER_LINE", ""); + ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pBorderTileLineProgramTextured->CreateProgram(); + m_pBorderTileLineProgramTextured->AddShader(&VertexShader); + m_pBorderTileLineProgramTextured->AddShader(&FragmentShader); + m_pBorderTileLineProgramTextured->LinkProgram(); + + UseProgram(m_pBorderTileLineProgramTextured); + + m_pBorderTileLineProgramTextured->m_LocPos = m_pBorderTileLineProgramTextured->GetUniformLoc("gPos"); + m_pBorderTileLineProgramTextured->m_LocTextureSampler = m_pBorderTileLineProgramTextured->GetUniformLoc("gTextureSampler"); + m_pBorderTileLineProgramTextured->m_LocColor = m_pBorderTileLineProgramTextured->GetUniformLoc("gVertColor"); + m_pBorderTileLineProgramTextured->m_LocOffset = m_pBorderTileLineProgramTextured->GetUniformLoc("gOffset"); + m_pBorderTileLineProgramTextured->m_LocDir = m_pBorderTileLineProgramTextured->GetUniformLoc("gDir"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_MAX_QUADS", std::to_string(m_MaxQuadsAtOnce).c_str()); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pQuadProgram->CreateProgram(); + m_pQuadProgram->AddShader(&VertexShader); + m_pQuadProgram->AddShader(&FragmentShader); + m_pQuadProgram->LinkProgram(); + + UseProgram(m_pQuadProgram); + + m_pQuadProgram->m_LocPos = m_pQuadProgram->GetUniformLoc("gPos"); + m_pQuadProgram->m_LocColors = m_pQuadProgram->GetUniformLoc("gVertColors"); + m_pQuadProgram->m_LocRotations = m_pQuadProgram->GetUniformLoc("gRotations"); + m_pQuadProgram->m_LocOffsets = m_pQuadProgram->GetUniformLoc("gOffsets"); + m_pQuadProgram->m_LocQuadOffset = m_pQuadProgram->GetUniformLoc("gQuadOffset"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_QUAD_TEXTURED", ""); + ShaderCompiler.AddDefine("TW_MAX_QUADS", std::to_string(m_MaxQuadsAtOnce).c_str()); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pQuadProgramTextured->CreateProgram(); + m_pQuadProgramTextured->AddShader(&VertexShader); + m_pQuadProgramTextured->AddShader(&FragmentShader); + m_pQuadProgramTextured->LinkProgram(); + + UseProgram(m_pQuadProgramTextured); + + m_pQuadProgramTextured->m_LocPos = m_pQuadProgramTextured->GetUniformLoc("gPos"); + m_pQuadProgramTextured->m_LocTextureSampler = m_pQuadProgramTextured->GetUniformLoc("gTextureSampler"); + m_pQuadProgramTextured->m_LocColors = m_pQuadProgramTextured->GetUniformLoc("gVertColors"); + m_pQuadProgramTextured->m_LocRotations = m_pQuadProgramTextured->GetUniformLoc("gRotations"); + m_pQuadProgramTextured->m_LocOffsets = m_pQuadProgramTextured->GetUniformLoc("gOffsets"); + m_pQuadProgramTextured->m_LocQuadOffset = m_pQuadProgramTextured->GetUniformLoc("gQuadOffset"); + } + { + CGLSL VertexShader; + CGLSL FragmentShader; + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/text.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/text.frag", GL_FRAGMENT_SHADER); + + m_pTextProgram->CreateProgram(); + m_pTextProgram->AddShader(&VertexShader); + m_pTextProgram->AddShader(&FragmentShader); + m_pTextProgram->LinkProgram(); + + UseProgram(m_pTextProgram); + + m_pTextProgram->m_LocPos = m_pTextProgram->GetUniformLoc("gPos"); + m_pTextProgram->m_LocTextureSampler = -1; + m_pTextProgram->m_LocTextSampler = m_pTextProgram->GetUniformLoc("gTextSampler"); + m_pTextProgram->m_LocTextOutlineSampler = m_pTextProgram->GetUniformLoc("gTextOutlineSampler"); + m_pTextProgram->m_LocColor = m_pTextProgram->GetUniformLoc("gVertColor"); + m_pTextProgram->m_LocOutlineColor = m_pTextProgram->GetUniformLoc("gVertOutlineColor"); + m_pTextProgram->m_LocTextureSize = m_pTextProgram->GetUniformLoc("gTextureSize"); + } + InitPrimExProgram(m_pPrimitiveExProgram, &ShaderCompiler, pCommand->m_pStorage, false, false); + InitPrimExProgram(m_pPrimitiveExProgramTextured, &ShaderCompiler, pCommand->m_pStorage, true, false); + InitPrimExProgram(m_pPrimitiveExProgramRotationless, &ShaderCompiler, pCommand->m_pStorage, false, true); + InitPrimExProgram(m_pPrimitiveExProgramTexturedRotationless, &ShaderCompiler, pCommand->m_pStorage, true, true); + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/spritemulti.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/spritemulti.frag", GL_FRAGMENT_SHADER); + + m_pSpriteProgramMultiple->CreateProgram(); + m_pSpriteProgramMultiple->AddShader(&PrimitiveVertexShader); + m_pSpriteProgramMultiple->AddShader(&PrimitiveFragmentShader); + m_pSpriteProgramMultiple->LinkProgram(); + + UseProgram(m_pSpriteProgramMultiple); + + m_pSpriteProgramMultiple->m_LocPos = m_pSpriteProgramMultiple->GetUniformLoc("gPos"); + m_pSpriteProgramMultiple->m_LocTextureSampler = m_pSpriteProgramMultiple->GetUniformLoc("gTextureSampler"); + m_pSpriteProgramMultiple->m_LocRSP = m_pSpriteProgramMultiple->GetUniformLoc("gRSP[0]"); + m_pSpriteProgramMultiple->m_LocCenter = m_pSpriteProgramMultiple->GetUniformLoc("gCenter"); + m_pSpriteProgramMultiple->m_LocVertciesColor = m_pSpriteProgramMultiple->GetUniformLoc("gVerticesColor"); + + float Center[2] = {0.f, 0.f}; + m_pSpriteProgramMultiple->SetUniformVec2(m_pSpriteProgramMultiple->m_LocCenter, 1, Center); + } + + m_LastStreamBuffer = 0; + + glGenBuffers(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawBufferID); + glGenVertexArrays(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawVertexID); + glGenBuffers(1, &m_PrimitiveDrawBufferIDTex3D); + glGenVertexArrays(1, &m_PrimitiveDrawVertexIDTex3D); + + m_UsePreinitializedVertexBuffer = g_Config.m_GfxUsePreinitBuffer; + + for(int i = 0; i < MAX_STREAM_BUFFER_COUNT; ++i) + { + glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferID[i]); + glBindVertexArray(m_PrimitiveDrawVertexID[i]); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertex), 0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertex), (void *)(sizeof(float) * 2)); + glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(CCommandBuffer::SVertex), (void *)(sizeof(float) * 4)); + + if(m_UsePreinitializedVertexBuffer) + glBufferData(GL_ARRAY_BUFFER, sizeof(CCommandBuffer::SVertex) * CCommandBuffer::MAX_VERTICES, NULL, GL_STREAM_DRAW); + + m_LastIndexBufferBound[i] = 0; + } + + glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferIDTex3D); + glBindVertexArray(m_PrimitiveDrawVertexIDTex3D); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertexTex3DStream), 0); + glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(CCommandBuffer::SVertexTex3DStream), (void *)(sizeof(float) * 2)); + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertexTex3DStream), (void *)(sizeof(float) * 2 + sizeof(unsigned char) * 4)); + + if(m_UsePreinitializedVertexBuffer) + glBufferData(GL_ARRAY_BUFFER, sizeof(CCommandBuffer::SVertexTex3DStream) * CCommandBuffer::MAX_VERTICES, NULL, GL_STREAM_DRAW); + + //query the image max size only once + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize); + + //query maximum of allowed textures + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &m_MaxTextureUnits); + m_TextureSlotBoundToUnit.resize(m_MaxTextureUnits); + for(int i = 0; i < m_MaxTextureUnits; ++i) + { + m_TextureSlotBoundToUnit[i].m_TextureSlot = -1; + m_TextureSlotBoundToUnit[i].m_Is2DArray = false; + } + + glBindVertexArray(0); + glGenBuffers(1, &m_QuadDrawIndexBufferID); + glBindBuffer(GL_COPY_WRITE_BUFFER, m_QuadDrawIndexBufferID); + + unsigned int Indices[CCommandBuffer::MAX_VERTICES / 4 * 6]; + int Primq = 0; + for(int i = 0; i < CCommandBuffer::MAX_VERTICES / 4 * 6; i += 6) + { + Indices[i] = Primq; + Indices[i + 1] = Primq + 1; + Indices[i + 2] = Primq + 2; + Indices[i + 3] = Primq; + Indices[i + 4] = Primq + 2; + Indices[i + 5] = Primq + 3; + Primq += 4; + } + glBufferData(GL_COPY_WRITE_BUFFER, sizeof(unsigned int) * CCommandBuffer::MAX_VERTICES / 4 * 6, Indices, GL_STATIC_DRAW); + + m_CurrentIndicesInBuffer = CCommandBuffer::MAX_VERTICES / 4 * 6; + + m_Textures.resize(CCommandBuffer::MAX_TEXTURES); + + m_ClearColor.r = m_ClearColor.g = m_ClearColor.b = -1.f; + + // fix the alignment to allow even 1byte changes, e.g. for alpha components + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + return true; +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Shutdown(const SCommand_Shutdown *pCommand) +{ + glUseProgram(0); + + m_pPrimitiveProgram->DeleteProgram(); + m_pPrimitiveProgramTextured->DeleteProgram(); + m_pBorderTileProgram->DeleteProgram(); + m_pBorderTileProgramTextured->DeleteProgram(); + m_pBorderTileLineProgram->DeleteProgram(); + m_pBorderTileLineProgramTextured->DeleteProgram(); + m_pQuadProgram->DeleteProgram(); + m_pQuadProgramTextured->DeleteProgram(); + m_pTileProgram->DeleteProgram(); + m_pTileProgramTextured->DeleteProgram(); + m_pPrimitive3DProgram->DeleteProgram(); + m_pPrimitive3DProgramTextured->DeleteProgram(); + m_pTextProgram->DeleteProgram(); + m_pPrimitiveExProgram->DeleteProgram(); + m_pPrimitiveExProgramTextured->DeleteProgram(); + m_pPrimitiveExProgramRotationless->DeleteProgram(); + m_pPrimitiveExProgramTexturedRotationless->DeleteProgram(); + m_pSpriteProgramMultiple->DeleteProgram(); + + //clean up everything + delete m_pPrimitiveProgram; + delete m_pPrimitiveProgramTextured; + delete m_pBorderTileProgram; + delete m_pBorderTileProgramTextured; + delete m_pBorderTileLineProgram; + delete m_pBorderTileLineProgramTextured; + delete m_pQuadProgram; + delete m_pQuadProgramTextured; + delete m_pTileProgram; + delete m_pTileProgramTextured; + delete m_pPrimitive3DProgram; + delete m_pPrimitive3DProgramTextured; + delete m_pTextProgram; + delete m_pPrimitiveExProgram; + delete m_pPrimitiveExProgramTextured; + delete m_pPrimitiveExProgramRotationless; + delete m_pPrimitiveExProgramTexturedRotationless; + delete m_pSpriteProgramMultiple; + + glBindVertexArray(0); + glDeleteBuffers(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawBufferID); + glDeleteBuffers(1, &m_QuadDrawIndexBufferID); + glDeleteVertexArrays(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawVertexID); + glDeleteBuffers(1, &m_PrimitiveDrawBufferIDTex3D); + glDeleteVertexArrays(1, &m_PrimitiveDrawVertexIDTex3D); + + for(int i = 0; i < (int)m_Textures.size(); ++i) + { + DestroyTexture(i); + } + + for(size_t i = 0; i < m_BufferContainers.size(); ++i) + { + DestroyBufferContainer(i); + } + + m_BufferContainers.clear(); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) +{ + if(m_UseMultipleTextureUnits) + { + int Slot = pCommand->m_Slot % m_MaxTextureUnits; + //just tell, that we using this texture now + IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); + glActiveTexture(GL_TEXTURE0 + Slot); + glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler); + } + + glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); + + void *pTexData = pCommand->m_pData; + int Width = pCommand->m_Width; + int Height = pCommand->m_Height; + int X = pCommand->m_X; + int Y = pCommand->m_Y; + if(m_Textures[pCommand->m_Slot].m_RescaleCount > 0) + { + for(int i = 0; i < m_Textures[pCommand->m_Slot].m_RescaleCount; ++i) + { + Width >>= 1; + Height >>= 1; + + X /= 2; + Y /= 2; + } + + void *pTmpData = Resize(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); + free(pTexData); + pTexData = pTmpData; + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, X, Y, Width, Height, + TexFormatToNewOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pTexData); + free(pTexData); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) +{ + int Slot = 0; + if(m_UseMultipleTextureUnits) + { + Slot = pCommand->m_Slot % m_MaxTextureUnits; + IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); + glActiveTexture(GL_TEXTURE0 + Slot); + } + glBindTexture(GL_TEXTURE_2D, 0); + glBindSampler(Slot, 0); + m_TextureSlotBoundToUnit[Slot].m_TextureSlot = -1; + m_TextureSlotBoundToUnit[Slot].m_Is2DArray = false; + DestroyTexture(pCommand->m_Slot); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) +{ + int Width = pCommand->m_Width; + int Height = pCommand->m_Height; + void *pTexData = pCommand->m_pData; + + if(pCommand->m_Slot >= (int)m_Textures.size()) + m_Textures.resize(m_Textures.size() * 2); + + // resample if needed + int RescaleCount = 0; + if(pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGBA || pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGB || pCommand->m_Format == CCommandBuffer::TEXFORMAT_ALPHA) + { + if(Width > m_MaxTexSize || Height > m_MaxTexSize) + { + do + { + Width >>= 1; + Height >>= 1; + ++RescaleCount; + } while(Width > m_MaxTexSize || Height > m_MaxTexSize); + + void *pTmpData = Resize(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); + free(pTexData); + pTexData = pTmpData; + } + } + m_Textures[pCommand->m_Slot].m_Width = Width; + m_Textures[pCommand->m_Slot].m_Height = Height; + m_Textures[pCommand->m_Slot].m_RescaleCount = RescaleCount; + + int Oglformat = TexFormatToNewOpenGLFormat(pCommand->m_Format); + int StoreOglformat = TexFormatToNewOpenGLFormat(pCommand->m_StoreFormat); + if(StoreOglformat == GL_RED) + StoreOglformat = GL_R8; + + int Slot = 0; + if(m_UseMultipleTextureUnits) + { + Slot = pCommand->m_Slot % m_MaxTextureUnits; + //just tell, that we using this texture now + IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); + glActiveTexture(GL_TEXTURE0 + Slot); + m_TextureSlotBoundToUnit[Slot].m_TextureSlot = -1; + m_TextureSlotBoundToUnit[Slot].m_Is2DArray = false; + } + + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex); + glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); + + glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler); + glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler); + } + + if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_NOMIPMAPS) + { + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + } + } + else + { + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + +#ifndef BACKEND_AS_OPENGL_ES + if(m_OpenGLTextureLodBIAS != 0 && !m_IsOpenGLES) + glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); +#endif + + //prevent mipmap display bugs, when zooming out far + if(Width >= 1024 && Height >= 1024) + { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5.f); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 5); + } + glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + glGenerateMipmap(GL_TEXTURE_2D); + } + + if((pCommand->m_Flags & (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE | CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER)) != 0) + { + glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex2DArray); + glBindTexture(GL_TEXTURE_2D_ARRAY, m_Textures[pCommand->m_Slot].m_Tex2DArray); + + glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler2DArray); + glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler2DArray); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); + +#ifndef BACKEND_AS_OPENGL_ES + if(m_OpenGLTextureLodBIAS != 0 && !m_IsOpenGLES) + glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); +#endif + + int ImageColorChannels = TexFormatToImageColorChannelCount(pCommand->m_Format); + + uint8_t *p3DImageData = NULL; + + bool IsSingleLayer = (pCommand->m_Flags & CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER) != 0; + + if(!IsSingleLayer) + p3DImageData = (uint8_t *)malloc((size_t)ImageColorChannels * Width * Height); + int Image3DWidth, Image3DHeight; + + int ConvertWidth = Width; + int ConvertHeight = Height; + + if(!IsSingleLayer) + { + if(ConvertWidth == 0 || (ConvertWidth % 16) != 0 || ConvertHeight == 0 || (ConvertHeight % 16) != 0) + { + dbg_msg("gfx", "3D/2D array texture was resized"); + int NewWidth = maximum(HighestBit(ConvertWidth), 16); + int NewHeight = maximum(HighestBit(ConvertHeight), 16); + uint8_t *pNewTexData = (uint8_t *)Resize(ConvertWidth, ConvertHeight, NewWidth, NewHeight, pCommand->m_Format, (const uint8_t *)pTexData); + + ConvertWidth = NewWidth; + ConvertHeight = NewHeight; + + free(pTexData); + pTexData = pNewTexData; + } + } + + if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) + { + if(IsSingleLayer) + { + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + } + else + { + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); + } + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + } + + if(!IsSingleLayer) + free(p3DImageData); + } + } + + // This is the initial value for the wrap modes + m_Textures[pCommand->m_Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; + + // calculate memory usage + m_Textures[pCommand->m_Slot].m_MemSize = Width * Height * pCommand->m_PixelSize; + while(Width > 2 && Height > 2) + { + Width >>= 1; + Height >>= 1; + m_Textures[pCommand->m_Slot].m_MemSize += Width * Height * pCommand->m_PixelSize; + } + m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_Textures[pCommand->m_Slot].m_MemSize, std::memory_order_relaxed); + + free(pTexData); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) +{ + if(pCommand->m_Color.r != m_ClearColor.r || pCommand->m_Color.g != m_ClearColor.g || pCommand->m_Color.b != m_ClearColor.b) + { + glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); + m_ClearColor = pCommand->m_Color; + } + glClear(GL_COLOR_BUFFER_BIT); +} + +void CCommandProcessorFragment_OpenGL3_3::UploadStreamBufferData(unsigned int PrimitiveType, const void *pVertices, size_t VertSize, unsigned int PrimitiveCount, bool AsTex3D) +{ + int Count = 0; + switch(PrimitiveType) + { + case CCommandBuffer::PRIMTYPE_LINES: + Count = PrimitiveCount * 2; + break; + case CCommandBuffer::PRIMTYPE_TRIANGLES: + Count = PrimitiveCount * 3; + break; + case CCommandBuffer::PRIMTYPE_QUADS: + Count = PrimitiveCount * 4; + break; + default: + return; + }; + + if(AsTex3D) + glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferIDTex3D); + else + glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferID[m_LastStreamBuffer]); + + if(!m_UsePreinitializedVertexBuffer) + glBufferData(GL_ARRAY_BUFFER, VertSize * Count, pVertices, GL_STREAM_DRAW); + else + { + // This is better for some iGPUs. Probably due to not initializing a new buffer in the system memory again and again...(driver dependent) + void *pData = glMapBufferRange(GL_ARRAY_BUFFER, 0, VertSize * Count, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); + + mem_copy(pData, pVertices, VertSize * Count); + + glUnmapBuffer(GL_ARRAY_BUFFER); + } +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) +{ + CGLSLTWProgram *pProgram = m_pPrimitiveProgram; + if(IsTexturedState(pCommand->m_State)) + pProgram = m_pPrimitiveProgramTextured; + UseProgram(pProgram); + SetState(pCommand->m_State, pProgram); + + UploadStreamBufferData(pCommand->m_PrimType, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertex), pCommand->m_PrimCount); + + glBindVertexArray(m_PrimitiveDrawVertexID[m_LastStreamBuffer]); + + switch(pCommand->m_PrimType) + { + // We don't support GL_QUADS due to core profile + case CCommandBuffer::PRIMTYPE_LINES: + glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); + break; + case CCommandBuffer::PRIMTYPE_TRIANGLES: + glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); + break; + case CCommandBuffer::PRIMTYPE_QUADS: + if(m_LastIndexBufferBound[m_LastStreamBuffer] != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + m_LastIndexBufferBound[m_LastStreamBuffer] = m_QuadDrawIndexBufferID; + } + glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount * 6, GL_UNSIGNED_INT, 0); + break; + default: + dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); + }; + + m_LastStreamBuffer = (m_LastStreamBuffer + 1 >= MAX_STREAM_BUFFER_COUNT ? 0 : m_LastStreamBuffer + 1); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) +{ + CGLSLPrimitiveProgram *pProg = m_pPrimitive3DProgram; + if(IsTexturedState(pCommand->m_State)) + pProg = m_pPrimitive3DProgramTextured; + UseProgram(pProg); + SetState(pCommand->m_State, pProg, true); + + UploadStreamBufferData(pCommand->m_PrimType, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertexTex3DStream), pCommand->m_PrimCount, true); + + glBindVertexArray(m_PrimitiveDrawVertexIDTex3D); + + switch(pCommand->m_PrimType) + { + // We don't support GL_QUADS due to core profile + case CCommandBuffer::PRIMTYPE_LINES: + glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); + break; + case CCommandBuffer::PRIMTYPE_QUADS: + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount * 6, GL_UNSIGNED_INT, 0); + break; + default: + dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); + }; +} + +void CCommandProcessorFragment_OpenGL3_3::DestroyBufferContainer(int Index, bool DeleteBOs) +{ + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID != 0) + glDeleteVertexArrays(1, &BufferContainer.m_VertArrayID); + + // all buffer objects can deleted automatically, so the program doesn't need to deal with them (e.g. causing crashes because of driver bugs) + if(DeleteBOs) + { + for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) + { + int VertBufferID = BufferContainer.m_ContainerInfo.m_Attributes[i].m_VertBufferBindingIndex; + if(VertBufferID != -1) + { + for(auto &Attribute : BufferContainer.m_ContainerInfo.m_Attributes) + { + // set all equal ids to zero to not double delete + if(VertBufferID == Attribute.m_VertBufferBindingIndex) + { + Attribute.m_VertBufferBindingIndex = -1; + } + } + + glDeleteBuffers(1, &m_BufferObjectIndices[VertBufferID]); + } + } + } + + BufferContainer.m_LastIndexBufferBound = 0; + BufferContainer.m_ContainerInfo.m_Attributes.clear(); +} + +void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesCount) +{ + if(NewIndicesCount <= m_CurrentIndicesInBuffer) + return; + unsigned int AddCount = NewIndicesCount - m_CurrentIndicesInBuffer; + unsigned int *Indices = new unsigned int[AddCount]; + int Primq = (m_CurrentIndicesInBuffer / 6) * 4; + for(unsigned int i = 0; i < AddCount; i += 6) + { + Indices[i] = Primq; + Indices[i + 1] = Primq + 1; + Indices[i + 2] = Primq + 2; + Indices[i + 3] = Primq; + Indices[i + 4] = Primq + 2; + Indices[i + 5] = Primq + 3; + Primq += 4; + } + + glBindBuffer(GL_COPY_READ_BUFFER, m_QuadDrawIndexBufferID); + GLuint NewIndexBufferID; + glGenBuffers(1, &NewIndexBufferID); + glBindBuffer(GL_COPY_WRITE_BUFFER, NewIndexBufferID); + GLsizeiptr size = sizeof(unsigned int); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)NewIndicesCount * size, NULL, GL_STATIC_DRAW); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, (GLsizeiptr)m_CurrentIndicesInBuffer * size); + glBufferSubData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)m_CurrentIndicesInBuffer * size, (GLsizeiptr)AddCount * size, Indices); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + + glDeleteBuffers(1, &m_QuadDrawIndexBufferID); + m_QuadDrawIndexBufferID = NewIndexBufferID; + + for(unsigned int &i : m_LastIndexBufferBound) + i = 0; + for(auto &BufferContainer : m_BufferContainers) + { + BufferContainer.m_LastIndexBufferBound = 0; + } + + m_CurrentIndicesInBuffer = NewIndicesCount; + delete[] Indices; +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + //create necessary space + if((size_t)Index >= m_BufferObjectIndices.size()) + { + for(int i = m_BufferObjectIndices.size(); i < Index + 1; ++i) + { + m_BufferObjectIndices.push_back(0); + } + } + + GLuint VertBufferID = 0; + + glGenBuffers(1, &VertBufferID); + glBindBuffer(GL_COPY_WRITE_BUFFER, VertBufferID); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); + + m_BufferObjectIndices[Index] = VertBufferID; + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + + glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[Index]); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + + glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[Index]); + glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pOffset), (GLsizeiptr)(pCommand->m_DataSize), pUploadData); + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) +{ + int WriteIndex = pCommand->m_WriteBufferIndex; + int ReadIndex = pCommand->m_ReadBufferIndex; + + glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[WriteIndex]); + glBindBuffer(GL_COPY_READ_BUFFER, m_BufferObjectIndices[ReadIndex]); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_pReadOffset), (GLsizeiptr)(pCommand->m_pWriteOffset), (GLsizeiptr)pCommand->m_CopySize); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) +{ + int Index = pCommand->m_BufferIndex; + + glDeleteBuffers(1, &m_BufferObjectIndices[Index]); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //create necessary space + if((size_t)Index >= m_BufferContainers.size()) + { + for(int i = m_BufferContainers.size(); i < Index + 1; ++i) + { + SBufferContainer Container; + Container.m_ContainerInfo.m_Stride = 0; + m_BufferContainers.push_back(Container); + } + } + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + glGenVertexArrays(1, &BufferContainer.m_VertArrayID); + glBindVertexArray(BufferContainer.m_VertArrayID); + + BufferContainer.m_LastIndexBufferBound = 0; + + for(int i = 0; i < pCommand->m_AttrCount; ++i) + { + glEnableVertexAttribArray((GLuint)i); + + glBindBuffer(GL_ARRAY_BUFFER, m_BufferObjectIndices[pCommand->m_Attributes[i].m_VertBufferBindingIndex]); + + SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; + + if(Attr.m_FuncType == 0) + glVertexAttribPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, (GLboolean)Attr.m_Normalized, pCommand->m_Stride, Attr.m_pOffset); + else if(Attr.m_FuncType == 1) + glVertexAttribIPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, pCommand->m_Stride, Attr.m_pOffset); + + BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); + } + + BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) +{ + SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; + + glBindVertexArray(BufferContainer.m_VertArrayID); + + //disable all old attributes + for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) + { + glDisableVertexAttribArray((GLuint)i); + } + BufferContainer.m_ContainerInfo.m_Attributes.clear(); + + for(int i = 0; i < pCommand->m_AttrCount; ++i) + { + glEnableVertexAttribArray((GLuint)i); + + glBindBuffer(GL_ARRAY_BUFFER, m_BufferObjectIndices[pCommand->m_Attributes[i].m_VertBufferBindingIndex]); + SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; + if(Attr.m_FuncType == 0) + glVertexAttribPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, Attr.m_Normalized, pCommand->m_Stride, Attr.m_pOffset); + else if(Attr.m_FuncType == 1) + glVertexAttribIPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, pCommand->m_Stride, Attr.m_pOffset); + + BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); + } + + BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) +{ + DestroyBufferContainer(pCommand->m_BufferContainerIndex, pCommand->m_DestroyAllBO); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) +{ + if(pCommand->m_RequiredIndicesNum > m_CurrentIndicesInBuffer) + AppendIndices(pCommand->m_RequiredIndicesNum); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + CGLSLTileProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pBorderTileProgramTextured; + } + else + pProgram = m_pBorderTileProgram; + UseProgram(pProgram); + + SetState(pCommand->m_State, pProgram, true); + pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); + + pProgram->SetUniformVec2(pProgram->m_LocOffset, 1, (float *)&pCommand->m_Offset); + pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float *)&pCommand->m_Dir); + pProgram->SetUniform(pProgram->m_LocJumpIndex, (int)pCommand->m_JumpIndex); + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + CGLSLTileProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pBorderTileLineProgramTextured; + } + else + pProgram = m_pBorderTileLineProgram; + UseProgram(pProgram); + + SetState(pCommand->m_State, pProgram, true); + pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); + pProgram->SetUniformVec2(pProgram->m_LocOffset, 1, (float *)&pCommand->m_Offset); + pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float *)&pCommand->m_Dir); + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_IndexDrawNum, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + if(pCommand->m_IndicesDrawNum == 0) + { + return; //nothing to draw + } + + CGLSLTileProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pTileProgramTextured; + } + else + pProgram = m_pTileProgram; + + UseProgram(pProgram); + + SetState(pCommand->m_State, pProgram, true); + pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) + { + glDrawElements(GL_TRIANGLES, pCommand->m_pDrawCount[i], GL_UNSIGNED_INT, pCommand->m_pIndicesOffsets[i]); + } +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + if(pCommand->m_QuadNum == 0) + { + return; //nothing to draw + } + + CGLSLQuadProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pQuadProgramTextured; + } + else + pProgram = m_pQuadProgram; + + UseProgram(pProgram); + SetState(pCommand->m_State, pProgram); + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + + int QuadsLeft = pCommand->m_QuadNum; + size_t QuadOffset = 0; + // the extra offset is not related to the information from the command, but an actual offset in the buffer + size_t QuadOffsetExtra = pCommand->m_QuadOffset; + + vec4 aColors[m_MaxQuadsPossible]; + vec2 aOffsets[m_MaxQuadsPossible]; + float aRotations[m_MaxQuadsPossible]; + + while(QuadsLeft > 0) + { + int ActualQuadCount = minimum(QuadsLeft, m_MaxQuadsAtOnce); + + for(size_t i = 0; i < (size_t)ActualQuadCount; ++i) + { + mem_copy(&aColors[i], pCommand->m_pQuadInfo[i + QuadOffset].m_aColor, sizeof(vec4)); + mem_copy(&aOffsets[i], pCommand->m_pQuadInfo[i + QuadOffset].m_aOffsets, sizeof(vec2)); + mem_copy(&aRotations[i], &pCommand->m_pQuadInfo[i + QuadOffset].m_Rotation, sizeof(float)); + } + + pProgram->SetUniformVec4(pProgram->m_LocColors, ActualQuadCount, (float *)aColors); + pProgram->SetUniformVec2(pProgram->m_LocOffsets, ActualQuadCount, (float *)aOffsets); + pProgram->SetUniform(pProgram->m_LocRotations, ActualQuadCount, (float *)aRotations); + pProgram->SetUniform(pProgram->m_LocQuadOffset, (int)(QuadOffset + QuadOffsetExtra)); + glDrawElements(GL_TRIANGLES, ActualQuadCount * 6, GL_UNSIGNED_INT, (void *)((QuadOffset + QuadOffsetExtra) * 6 * sizeof(unsigned int))); + + QuadsLeft -= ActualQuadCount; + QuadOffset += (size_t)ActualQuadCount; + } +} + +void CCommandProcessorFragment_OpenGL3_3::RenderText(const CCommandBuffer::SState &State, int DrawNum, int TextTextureIndex, int TextOutlineTextureIndex, int TextureSize, const float *pTextColor, const float *pTextOutlineColor) +{ + if(DrawNum == 0) + { + return; //nothing to draw + } + + UseProgram(m_pTextProgram); + + int SlotText = 0; + int SlotTextOutline = 0; + + if(m_UseMultipleTextureUnits) + { + SlotText = TextTextureIndex % m_MaxTextureUnits; + SlotTextOutline = TextOutlineTextureIndex % m_MaxTextureUnits; + if(SlotText == SlotTextOutline) + SlotTextOutline = (TextOutlineTextureIndex + 1) % m_MaxTextureUnits; + + if(!IsAndUpdateTextureSlotBound(SlotText, TextTextureIndex)) + { + glActiveTexture(GL_TEXTURE0 + SlotText); + glBindTexture(GL_TEXTURE_2D, m_Textures[TextTextureIndex].m_Tex); + glBindSampler(SlotText, m_Textures[TextTextureIndex].m_Sampler); + } + if(!IsAndUpdateTextureSlotBound(SlotTextOutline, TextOutlineTextureIndex)) + { + glActiveTexture(GL_TEXTURE0 + SlotTextOutline); + glBindTexture(GL_TEXTURE_2D, m_Textures[TextOutlineTextureIndex].m_Tex); + glBindSampler(SlotTextOutline, m_Textures[TextOutlineTextureIndex].m_Sampler); + } + } + else + { + SlotText = 0; + SlotTextOutline = 1; + glBindTexture(GL_TEXTURE_2D, m_Textures[TextTextureIndex].m_Tex); + glBindSampler(SlotText, m_Textures[TextTextureIndex].m_Sampler); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_Textures[TextOutlineTextureIndex].m_Tex); + glBindSampler(SlotTextOutline, m_Textures[TextOutlineTextureIndex].m_Sampler); + glActiveTexture(GL_TEXTURE0); + } + + if(m_pTextProgram->m_LastTextSampler != SlotText) + { + m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextSampler, SlotText); + m_pTextProgram->m_LastTextSampler = SlotText; + } + + if(m_pTextProgram->m_LastTextOutlineSampler != SlotTextOutline) + { + m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextOutlineSampler, SlotTextOutline); + m_pTextProgram->m_LastTextOutlineSampler = SlotTextOutline; + } + + SetState(State, m_pTextProgram); + + if(m_pTextProgram->m_LastTextureSize != TextureSize) + { + m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextureSize, (float)TextureSize); + m_pTextProgram->m_LastTextureSize = TextureSize; + } + + if(m_pTextProgram->m_LastOutlineColor[0] != pTextOutlineColor[0] || m_pTextProgram->m_LastOutlineColor[1] != pTextOutlineColor[1] || m_pTextProgram->m_LastOutlineColor[2] != pTextOutlineColor[2] || m_pTextProgram->m_LastOutlineColor[3] != pTextOutlineColor[3]) + { + m_pTextProgram->SetUniformVec4(m_pTextProgram->m_LocOutlineColor, 1, (float *)pTextOutlineColor); + m_pTextProgram->m_LastOutlineColor[0] = pTextOutlineColor[0]; + m_pTextProgram->m_LastOutlineColor[1] = pTextOutlineColor[1]; + m_pTextProgram->m_LastOutlineColor[2] = pTextOutlineColor[2]; + m_pTextProgram->m_LastOutlineColor[3] = pTextOutlineColor[3]; + } + + if(m_pTextProgram->m_LastColor[0] != pTextColor[0] || m_pTextProgram->m_LastColor[1] != pTextColor[1] || m_pTextProgram->m_LastColor[2] != pTextColor[2] || m_pTextProgram->m_LastColor[3] != pTextColor[3]) + { + m_pTextProgram->SetUniformVec4(m_pTextProgram->m_LocColor, 1, (float *)pTextColor); + m_pTextProgram->m_LastColor[0] = pTextColor[0]; + m_pTextProgram->m_LastColor[1] = pTextColor[1]; + m_pTextProgram->m_LastColor[2] = pTextColor[2]; + m_pTextProgram->m_LastColor[3] = pTextColor[3]; + } + + glDrawElements(GL_TRIANGLES, DrawNum, GL_UNSIGNED_INT, (void *)(0)); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderText(const CCommandBuffer::SCommand_RenderText *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + + RenderText(pCommand->m_State, pCommand->m_DrawNum, pCommand->m_TextTextureIndex, pCommand->m_TextOutlineTextureIndex, pCommand->m_TextureSize, pCommand->m_aTextColor, pCommand->m_aTextOutlineColor); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTextStream(const CCommandBuffer::SCommand_RenderTextStream *pCommand) +{ + if(pCommand->m_PrimCount == 0) + { + return; //nothing to draw + } + + UploadStreamBufferData(CCommandBuffer::PRIMTYPE_QUADS, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertex), pCommand->m_PrimCount); + + glBindVertexArray(m_PrimitiveDrawVertexID[m_LastStreamBuffer]); + if(m_LastIndexBufferBound[m_LastStreamBuffer] != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + m_LastIndexBufferBound[m_LastStreamBuffer] = m_QuadDrawIndexBufferID; + } + + float aTextColor[4] = {1.f, 1.f, 1.f, 1.f}; + + RenderText(pCommand->m_State, pCommand->m_PrimCount * 6, pCommand->m_TextTextureIndex, pCommand->m_TextOutlineTextureIndex, pCommand->m_TextureSize, aTextColor, pCommand->m_aTextOutlineColor); + + m_LastStreamBuffer = (m_LastStreamBuffer + 1 >= MAX_STREAM_BUFFER_COUNT ? 0 : m_LastStreamBuffer + 1); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainer(const CCommandBuffer::SCommand_RenderQuadContainer *pCommand) +{ + if(pCommand->m_DrawNum == 0) + { + return; //nothing to draw + } + + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + + CGLSLTWProgram *pProgram = m_pPrimitiveProgram; + if(IsTexturedState(pCommand->m_State)) + pProgram = m_pPrimitiveProgramTextured; + UseProgram(pProgram); + SetState(pCommand->m_State, pProgram); + + glDrawElements(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) +{ + if(pCommand->m_DrawNum == 0) + { + return; //nothing to draw + } + + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + + CGLSLPrimitiveExProgram *pProgram = m_pPrimitiveExProgramRotationless; + if(IsTexturedState(pCommand->m_State)) + { + if(pCommand->m_Rotation != 0.0f) + pProgram = m_pPrimitiveExProgramTextured; + else + pProgram = m_pPrimitiveExProgramTexturedRotationless; + } + else + { + if(pCommand->m_Rotation != 0.0f) + pProgram = m_pPrimitiveExProgram; + } + + UseProgram(pProgram); + SetState(pCommand->m_State, pProgram); + + if(pCommand->m_Rotation != 0.0f && (pProgram->m_LastCenter[0] != pCommand->m_Center.x || pProgram->m_LastCenter[1] != pCommand->m_Center.y)) + { + pProgram->SetUniformVec2(pProgram->m_LocCenter, 1, (float *)&pCommand->m_Center); + pProgram->m_LastCenter[0] = pCommand->m_Center.x; + pProgram->m_LastCenter[1] = pCommand->m_Center.y; + } + + if(pProgram->m_LastRotation != pCommand->m_Rotation) + { + pProgram->SetUniform(pProgram->m_LocRotation, pCommand->m_Rotation); + pProgram->m_LastRotation = pCommand->m_Rotation; + } + + if(pProgram->m_LastVertciesColor[0] != pCommand->m_VertexColor.r || pProgram->m_LastVertciesColor[1] != pCommand->m_VertexColor.g || pProgram->m_LastVertciesColor[2] != pCommand->m_VertexColor.b || pProgram->m_LastVertciesColor[3] != pCommand->m_VertexColor.a) + { + pProgram->SetUniformVec4(pProgram->m_LocVertciesColor, 1, (float *)&pCommand->m_VertexColor); + pProgram->m_LastVertciesColor[0] = pCommand->m_VertexColor.r; + pProgram->m_LastVertciesColor[1] = pCommand->m_VertexColor.g; + pProgram->m_LastVertciesColor[2] = pCommand->m_VertexColor.b; + pProgram->m_LastVertciesColor[3] = pCommand->m_VertexColor.a; + } + + glDrawElements(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset); +} + +void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) +{ + if(pCommand->m_DrawNum == 0 || pCommand->m_DrawCount == 0) + { + return; //nothing to draw + } + + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + if(BufferContainer.m_VertArrayID == 0) + return; + + glBindVertexArray(BufferContainer.m_VertArrayID); + if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) + { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); + BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; + } + + UseProgram(m_pSpriteProgramMultiple); + SetState(pCommand->m_State, m_pSpriteProgramMultiple); + + if((m_pSpriteProgramMultiple->m_LastCenter[0] != pCommand->m_Center.x || m_pSpriteProgramMultiple->m_LastCenter[1] != pCommand->m_Center.y)) + { + m_pSpriteProgramMultiple->SetUniformVec2(m_pSpriteProgramMultiple->m_LocCenter, 1, (float *)&pCommand->m_Center); + m_pSpriteProgramMultiple->m_LastCenter[0] = pCommand->m_Center.x; + m_pSpriteProgramMultiple->m_LastCenter[1] = pCommand->m_Center.y; + } + + if(m_pSpriteProgramMultiple->m_LastVertciesColor[0] != pCommand->m_VertexColor.r || m_pSpriteProgramMultiple->m_LastVertciesColor[1] != pCommand->m_VertexColor.g || m_pSpriteProgramMultiple->m_LastVertciesColor[2] != pCommand->m_VertexColor.b || m_pSpriteProgramMultiple->m_LastVertciesColor[3] != pCommand->m_VertexColor.a) + { + m_pSpriteProgramMultiple->SetUniformVec4(m_pSpriteProgramMultiple->m_LocVertciesColor, 1, (float *)&pCommand->m_VertexColor); + m_pSpriteProgramMultiple->m_LastVertciesColor[0] = pCommand->m_VertexColor.r; + m_pSpriteProgramMultiple->m_LastVertciesColor[1] = pCommand->m_VertexColor.g; + m_pSpriteProgramMultiple->m_LastVertciesColor[2] = pCommand->m_VertexColor.b; + m_pSpriteProgramMultiple->m_LastVertciesColor[3] = pCommand->m_VertexColor.a; + } + + int DrawCount = pCommand->m_DrawCount; + size_t RenderOffset = 0; + + // 4 for the center (always use vec4) and 16 for the matrix(just to be sure), 4 for the sampler and vertex color + const int RSPCount = 256 - 4 - 16 - 8; + + while(DrawCount > 0) + { + int UniformCount = (DrawCount > RSPCount ? RSPCount : DrawCount); + + m_pSpriteProgramMultiple->SetUniformVec4(m_pSpriteProgramMultiple->m_LocRSP, UniformCount, (float *)(pCommand->m_pRenderInfo + RenderOffset)); + + glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset, UniformCount); + + RenderOffset += RSPCount; + DrawCount -= RSPCount; + } +} diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl3.h ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl3.h --- ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl3.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl3.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,118 @@ +// This file can be included several times. +#if(!defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H)) || \ + (defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H_AS_ES)) + +#if !defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H) +#define ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H +#endif + +#if defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H_AS_ES) +#define ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL3_H_AS_ES +#endif + +#include + +#include "backend_opengl.h" + +#define MAX_STREAM_BUFFER_COUNT 30 + +// takes care of opengl 3.3+ related rendering +class CCommandProcessorFragment_OpenGL3_3 : public CCommandProcessorFragment_OpenGL3 +{ +protected: + bool m_UsePreinitializedVertexBuffer; + + int m_MaxQuadsAtOnce; + static const int m_MaxQuadsPossible = 256; + + CGLSLPrimitiveProgram *m_pPrimitiveProgram; + CGLSLPrimitiveProgram *m_pPrimitiveProgramTextured; + CGLSLTileProgram *m_pBorderTileProgram; + CGLSLTileProgram *m_pBorderTileProgramTextured; + CGLSLTileProgram *m_pBorderTileLineProgram; + CGLSLTileProgram *m_pBorderTileLineProgramTextured; + CGLSLQuadProgram *m_pQuadProgram; + CGLSLQuadProgram *m_pQuadProgramTextured; + CGLSLTextProgram *m_pTextProgram; + CGLSLPrimitiveExProgram *m_pPrimitiveExProgram; + CGLSLPrimitiveExProgram *m_pPrimitiveExProgramTextured; + CGLSLPrimitiveExProgram *m_pPrimitiveExProgramRotationless; + CGLSLPrimitiveExProgram *m_pPrimitiveExProgramTexturedRotationless; + CGLSLSpriteMultipleProgram *m_pSpriteProgramMultiple; + + TWGLuint m_LastProgramID; + + TWGLuint m_PrimitiveDrawVertexID[MAX_STREAM_BUFFER_COUNT]; + TWGLuint m_PrimitiveDrawVertexIDTex3D; + TWGLuint m_PrimitiveDrawBufferID[MAX_STREAM_BUFFER_COUNT]; + TWGLuint m_PrimitiveDrawBufferIDTex3D; + + TWGLuint m_LastIndexBufferBound[MAX_STREAM_BUFFER_COUNT]; + + int m_LastStreamBuffer; + + TWGLuint m_QuadDrawIndexBufferID; + unsigned int m_CurrentIndicesInBuffer; + + void DestroyBufferContainer(int Index, bool DeleteBOs = true); + + void AppendIndices(unsigned int NewIndicesCount); + + struct SBufferContainer + { + SBufferContainer() : + m_VertArrayID(0), m_LastIndexBufferBound(0) {} + TWGLuint m_VertArrayID; + TWGLuint m_LastIndexBufferBound; + SBufferContainerInfo m_ContainerInfo; + }; + std::vector m_BufferContainers; + + std::vector m_BufferObjectIndices; + + CCommandBuffer::SColorf m_ClearColor; + + void InitPrimExProgram(CGLSLPrimitiveExProgram *pProgram, class CGLSLCompiler *pCompiler, class IStorage *pStorage, bool Textured, bool Rotationless); + + static int TexFormatToNewOpenGLFormat(int TexFormat); + bool IsNewApi() override { return true; } + + void UseProgram(CGLSLTWProgram *pProgram); + void UploadStreamBufferData(unsigned int PrimitiveType, const void *pVertices, size_t VertSize, unsigned int PrimitiveCount, bool AsTex3D = false); + void RenderText(const CCommandBuffer::SState &State, int DrawNum, int TextTextureIndex, int TextOutlineTextureIndex, int TextureSize, const float *pTextColor, const float *pTextOutlineColor); + + bool Cmd_Init(const SCommand_Init *pCommand) override; + void Cmd_Shutdown(const SCommand_Shutdown *pCommand) override; + void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) override; + void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) override; + void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) override; + void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) override; + void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) override; + void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) override; + + void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) override; + void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) override; + void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) override; + void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) override; + void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) override; + + void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) override; + void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) override; + void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) override; + void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) override; + + void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) override; + void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) override; + void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) override; + void Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) override; + 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_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) override; + void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) override; + +public: + CCommandProcessorFragment_OpenGL3_3() = default; +}; + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl.cpp ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl.cpp --- ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,2332 @@ +#include "backend_opengl.h" + +#include + +#include +#include + +#include + +#include + +#ifndef BACKEND_AS_OPENGL_ES +#include +#else +#include +#define GL_TEXTURE_2D_ARRAY_EXT GL_TEXTURE_2D_ARRAY +// GLES doesnt support GL_QUADS, but the code is also never executed +#define GL_QUADS GL_TRIANGLES +#ifndef CONF_BACKEND_OPENGL_ES3 +#include +#define glOrtho glOrthof +#else +#define BACKEND_GL_MODERN_API 1 +#endif +#endif + +// ------------ CCommandProcessorFragment_OpenGL +void CCommandProcessorFragment_OpenGL::Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand) +{ + glViewport(pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height); +} + +void CCommandProcessorFragment_OpenGL::Cmd_Finish(const CCommandBuffer::SCommand_Finish *pCommand) +{ + glFinish(); +} + +bool CCommandProcessorFragment_OpenGL::Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, int ImageColorChannelCount, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight) +{ + Target3DImageWidth = ImageWidth / SplitCountWidth; + Target3DImageHeight = ImageHeight / SplitCountHeight; + + size_t FullImageWidth = (size_t)ImageWidth * ImageColorChannelCount; + + for(int Y = 0; Y < SplitCountHeight; ++Y) + { + for(int X = 0; X < SplitCountWidth; ++X) + { + for(int Y3D = 0; Y3D < Target3DImageHeight; ++Y3D) + { + int DepthIndex = X + Y * SplitCountWidth; + + size_t TargetImageFullWidth = (size_t)Target3DImageWidth * ImageColorChannelCount; + size_t TargetImageFullSize = (size_t)TargetImageFullWidth * Target3DImageHeight; + ptrdiff_t ImageOffset = (ptrdiff_t)(((size_t)Y * FullImageWidth * (size_t)Target3DImageHeight) + ((size_t)Y3D * FullImageWidth) + ((size_t)X * TargetImageFullWidth)); + ptrdiff_t TargetImageOffset = (ptrdiff_t)(TargetImageFullSize * (size_t)DepthIndex + ((size_t)Y3D * TargetImageFullWidth)); + mem_copy(((uint8_t *)pTarget3DImageData) + TargetImageOffset, ((uint8_t *)pImageBuffer) + (ptrdiff_t)(ImageOffset), TargetImageFullWidth); + } + } + } + + return true; +} + +int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat) +{ + if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) + return GL_RGB; + if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) + return GL_ALPHA; + if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) + return GL_RGBA; + return GL_RGBA; +} + +int CCommandProcessorFragment_OpenGL::TexFormatToImageColorChannelCount(int TexFormat) +{ + if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) + return 3; + if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) + return 1; + if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) + return 4; + return 4; +} + +void *CCommandProcessorFragment_OpenGL::Resize(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData) +{ + int Bpp = TexFormatToImageColorChannelCount(Format); + + return ResizeImage((const uint8_t *)pData, Width, Height, NewWidth, NewHeight, Bpp); +} + +bool CCommandProcessorFragment_OpenGL::IsTexturedState(const CCommandBuffer::SState &State) +{ + return State.m_Texture >= 0 && State.m_Texture < (int)m_Textures.size(); +} + +void CCommandProcessorFragment_OpenGL::SetState(const CCommandBuffer::SState &State, bool Use2DArrayTextures) +{ +#ifndef BACKEND_GL_MODERN_API + // blend + switch(State.m_BlendMode) + { + case CCommandBuffer::BLEND_NONE: + glDisable(GL_BLEND); + break; + case CCommandBuffer::BLEND_ALPHA: + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case CCommandBuffer::BLEND_ADDITIVE: + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + default: + dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode); + }; + m_LastBlendMode = State.m_BlendMode; + + // clip + if(State.m_ClipEnable) + { + glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH); + glEnable(GL_SCISSOR_TEST); + m_LastClipEnable = true; + } + else if(m_LastClipEnable) + { + // Don't disable it always + glDisable(GL_SCISSOR_TEST); + m_LastClipEnable = false; + } + + glDisable(GL_TEXTURE_2D); + if(!m_HasShaders) + { + if(m_Has3DTextures) + glDisable(GL_TEXTURE_3D); + if(m_Has2DArrayTextures) + { + glDisable(m_2DArrayTarget); + } + } + + if(m_HasShaders && IsNewApi()) + { + glBindSampler(0, 0); + } + + // texture + if(IsTexturedState(State)) + { + if(!Use2DArrayTextures) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); + + if(m_Textures[State.m_Texture].m_LastWrapMode != State.m_WrapMode) + { + switch(State.m_WrapMode) + { + case CCommandBuffer::WRAP_REPEAT: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + break; + case CCommandBuffer::WRAP_CLAMP: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + break; + default: + dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode); + }; + m_Textures[State.m_Texture].m_LastWrapMode = State.m_WrapMode; + } + } + else + { + if(m_Has2DArrayTextures) + { + if(!m_HasShaders) + glEnable(m_2DArrayTarget); + glBindTexture(m_2DArrayTarget, m_Textures[State.m_Texture].m_Tex2DArray); + } + else if(m_Has3DTextures) + { + if(!m_HasShaders) + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, m_Textures[State.m_Texture].m_Tex2DArray); + } + else + { + dbg_msg("opengl", "Error: this call should not happen."); + } + } + } + + // screen mapping + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(State.m_ScreenTL.x, State.m_ScreenBR.x, State.m_ScreenBR.y, State.m_ScreenTL.y, -10.0f, 10.f); +#endif +} + +static void ParseVersionString(EBackendType BackendType, const char *pStr, int &VersionMajor, int &VersionMinor, int &VersionPatch) +{ + if(pStr) + { + // if backend is GLES, it starts with "OpenGL ES " or OpenGL ES-CM for older contexts, rest is the same + if(BackendType == BACKEND_TYPE_OPENGL_ES) + { + int StrLenGLES = str_length("OpenGL ES "); + int StrLenGLESCM = str_length("OpenGL ES-CM "); + if(str_comp_num(pStr, "OpenGL ES ", StrLenGLES) == 0) + pStr += StrLenGLES; + else if(str_comp_num(pStr, "OpenGL ES-CM ", StrLenGLESCM) == 0) + pStr += StrLenGLESCM; + } + + char aCurNumberStr[32]; + size_t CurNumberStrLen = 0; + size_t TotalNumbersPassed = 0; + int aNumbers[3] = {0}; + bool LastWasNumber = false; + while(*pStr && TotalNumbersPassed < 3) + { + if(*pStr >= '0' && *pStr <= '9') + { + aCurNumberStr[CurNumberStrLen++] = (char)*pStr; + LastWasNumber = true; + } + else if(LastWasNumber && (*pStr == '.' || *pStr == ' ' || *pStr == '\0')) + { + int CurNumber = 0; + if(CurNumberStrLen > 0) + { + aCurNumberStr[CurNumberStrLen] = 0; + CurNumber = str_toint(aCurNumberStr); + aNumbers[TotalNumbersPassed++] = CurNumber; + CurNumberStrLen = 0; + } + + LastWasNumber = false; + + if(*pStr != '.') + break; + } + else + { + break; + } + + ++pStr; + } + + VersionMajor = aNumbers[0]; + VersionMinor = aNumbers[1]; + VersionPatch = aNumbers[2]; + } +} + +#ifndef BACKEND_AS_OPENGL_ES +static const char *GetGLErrorName(GLenum Type) +{ + if(Type == GL_DEBUG_TYPE_ERROR) + return "ERROR"; + else if(Type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) + return "DEPRECATED BEHAVIOR"; + else if(Type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) + return "UNDEFINED BEHAVIOR"; + else if(Type == GL_DEBUG_TYPE_PORTABILITY) + return "PORTABILITY"; + else if(Type == GL_DEBUG_TYPE_PERFORMANCE) + return "PERFORMANCE"; + else if(Type == GL_DEBUG_TYPE_OTHER) + return "OTHER"; + else if(Type == GL_DEBUG_TYPE_MARKER) + return "MARKER"; + else if(Type == GL_DEBUG_TYPE_PUSH_GROUP) + return "PUSH_GROUP"; + else if(Type == GL_DEBUG_TYPE_POP_GROUP) + return "POP_GROUP"; + return "UNKNOWN"; +}; + +static const char *GetGLSeverity(GLenum Type) +{ + if(Type == GL_DEBUG_SEVERITY_HIGH) + return "high"; // All OpenGL Errors, shader compilation/linking errors, or highly-dangerous undefined behavior + else if(Type == GL_DEBUG_SEVERITY_MEDIUM) + return "medium"; // Major performance warnings, shader compilation/linking warnings, or the use of deprecated functionality + else if(Type == GL_DEBUG_SEVERITY_LOW) + return "low"; // Redundant state change performance warning, or unimportant undefined behavior + else if(Type == GL_DEBUG_SEVERITY_NOTIFICATION) + return "notification"; // Anything that isn't an error or performance issue. + + return "unknown"; +} + +static void GLAPIENTRY +GfxOpenGLMessageCallback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar *message, + const void *userParam) +{ + dbg_msg("gfx", "[%s] (importance: %s) %s", GetGLErrorName(type), GetGLSeverity(severity), message); +} +#endif + +bool CCommandProcessorFragment_OpenGL::InitOpenGL(const SCommand_Init *pCommand) +{ + m_IsOpenGLES = pCommand->m_RequestedBackend == BACKEND_TYPE_OPENGL_ES; + + const char *pVendorString = (const char *)glGetString(GL_VENDOR); + dbg_msg("opengl", "Vendor string: %s", pVendorString); + + // check what this context can do + const char *pVersionString = (const char *)glGetString(GL_VERSION); + dbg_msg("opengl", "Version string: %s", pVersionString); + + const char *pRendererString = (const char *)glGetString(GL_RENDERER); + + str_copy(pCommand->m_pVendorString, pVendorString, gs_GPUInfoStringSize); + str_copy(pCommand->m_pVersionString, pVersionString, gs_GPUInfoStringSize); + str_copy(pCommand->m_pRendererString, pRendererString, gs_GPUInfoStringSize); + + // parse version string + ParseVersionString(pCommand->m_RequestedBackend, pVersionString, pCommand->m_pCapabilities->m_ContextMajor, pCommand->m_pCapabilities->m_ContextMinor, pCommand->m_pCapabilities->m_ContextPatch); + + *pCommand->m_pInitError = 0; + + int BlocklistMajor = -1, BlocklistMinor = -1, BlocklistPatch = -1; + bool RequiresWarning = false; + const char *pErrString = ParseBlocklistDriverVersions(pVendorString, pVersionString, BlocklistMajor, BlocklistMinor, BlocklistPatch, RequiresWarning); + //if the driver is buggy, and the requested GL version is the default, fallback + if(pErrString != NULL && pCommand->m_RequestedMajor == 3 && pCommand->m_RequestedMinor == 0 && pCommand->m_RequestedPatch == 0) + { + // if not already in the error state, set the GL version + if(g_Config.m_GfxDriverIsBlocked == 0) + { + // fallback to known good GL version + pCommand->m_pCapabilities->m_ContextMajor = BlocklistMajor; + pCommand->m_pCapabilities->m_ContextMinor = BlocklistMinor; + pCommand->m_pCapabilities->m_ContextPatch = BlocklistPatch; + + // set backend error string + if(RequiresWarning) + *pCommand->m_pErrStringPtr = pErrString; + *pCommand->m_pInitError = -2; + + g_Config.m_GfxDriverIsBlocked = 1; + } + } + // if the driver was in a blocked error state, but is not anymore, reset all config variables + else if(pErrString == NULL && g_Config.m_GfxDriverIsBlocked == 1) + { + pCommand->m_pCapabilities->m_ContextMajor = 3; + pCommand->m_pCapabilities->m_ContextMinor = 0; + pCommand->m_pCapabilities->m_ContextPatch = 0; + + // tell the caller to reinitialize the context + *pCommand->m_pInitError = -2; + + g_Config.m_GfxDriverIsBlocked = 0; + } + + int MajorV = pCommand->m_pCapabilities->m_ContextMajor; + + if(pCommand->m_RequestedBackend == BACKEND_TYPE_OPENGL) + { +#ifndef BACKEND_AS_OPENGL_ES + int MinorV = pCommand->m_pCapabilities->m_ContextMinor; + if(*pCommand->m_pInitError == 0) + { + if(MajorV < pCommand->m_RequestedMajor) + { + *pCommand->m_pInitError = -2; + } + else if(MajorV == pCommand->m_RequestedMajor) + { + if(MinorV < pCommand->m_RequestedMinor) + { + *pCommand->m_pInitError = -2; + } + else if(MinorV == pCommand->m_RequestedMinor) + { + int PatchV = pCommand->m_pCapabilities->m_ContextPatch; + if(PatchV < pCommand->m_RequestedPatch) + { + *pCommand->m_pInitError = -2; + } + } + } + } + + if(*pCommand->m_pInitError == 0) + { + MajorV = pCommand->m_RequestedMajor; + MinorV = pCommand->m_RequestedMinor; + + pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension = false; + pCommand->m_pCapabilities->m_NPOTTextures = true; + + if(MajorV >= 4 || (MajorV == 3 && MinorV == 3)) + { + pCommand->m_pCapabilities->m_TileBuffering = true; + pCommand->m_pCapabilities->m_QuadBuffering = true; + pCommand->m_pCapabilities->m_TextBuffering = true; + pCommand->m_pCapabilities->m_QuadContainerBuffering = true; + pCommand->m_pCapabilities->m_ShaderSupport = true; + + pCommand->m_pCapabilities->m_MipMapping = true; + pCommand->m_pCapabilities->m_3DTextures = true; + pCommand->m_pCapabilities->m_2DArrayTextures = true; + } + else if(MajorV == 3) + { + pCommand->m_pCapabilities->m_MipMapping = true; + // check for context native 2D array texture size + pCommand->m_pCapabilities->m_3DTextures = false; + pCommand->m_pCapabilities->m_2DArrayTextures = false; + pCommand->m_pCapabilities->m_ShaderSupport = true; + + int TextureLayers = 0; + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &TextureLayers); + if(TextureLayers >= 256) + { + pCommand->m_pCapabilities->m_2DArrayTextures = true; + } + + int Texture3DSize = 0; + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &Texture3DSize); + if(Texture3DSize >= 256) + { + pCommand->m_pCapabilities->m_3DTextures = true; + } + + if(!pCommand->m_pCapabilities->m_3DTextures && !pCommand->m_pCapabilities->m_2DArrayTextures) + { + *pCommand->m_pInitError = -2; + pCommand->m_pCapabilities->m_ContextMajor = 1; + pCommand->m_pCapabilities->m_ContextMinor = 5; + pCommand->m_pCapabilities->m_ContextPatch = 0; + } + + pCommand->m_pCapabilities->m_TileBuffering = pCommand->m_pCapabilities->m_2DArrayTextures || pCommand->m_pCapabilities->m_3DTextures; + pCommand->m_pCapabilities->m_QuadBuffering = false; + pCommand->m_pCapabilities->m_TextBuffering = false; + pCommand->m_pCapabilities->m_QuadContainerBuffering = false; + } + else if(MajorV == 2) + { + pCommand->m_pCapabilities->m_MipMapping = true; + // check for context extension: 2D array texture and its max size + pCommand->m_pCapabilities->m_3DTextures = false; + pCommand->m_pCapabilities->m_2DArrayTextures = false; + + pCommand->m_pCapabilities->m_ShaderSupport = false; + if(MinorV >= 1) + pCommand->m_pCapabilities->m_ShaderSupport = true; + + int Texture3DSize = 0; + glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &Texture3DSize); + if(Texture3DSize >= 256) + { + pCommand->m_pCapabilities->m_3DTextures = true; + } + + // check for array texture extension + if(pCommand->m_pCapabilities->m_ShaderSupport && GLEW_EXT_texture_array) + { + int TextureLayers = 0; + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &TextureLayers); + if(TextureLayers >= 256) + { + pCommand->m_pCapabilities->m_2DArrayTextures = true; + pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension = true; + } + } + + pCommand->m_pCapabilities->m_TileBuffering = pCommand->m_pCapabilities->m_2DArrayTextures || pCommand->m_pCapabilities->m_3DTextures; + pCommand->m_pCapabilities->m_QuadBuffering = false; + pCommand->m_pCapabilities->m_TextBuffering = false; + pCommand->m_pCapabilities->m_QuadContainerBuffering = false; + + if(GLEW_ARB_texture_non_power_of_two || pCommand->m_GlewMajor > 2) + pCommand->m_pCapabilities->m_NPOTTextures = true; + else + { + pCommand->m_pCapabilities->m_NPOTTextures = false; + } + + if(!pCommand->m_pCapabilities->m_NPOTTextures || (!pCommand->m_pCapabilities->m_3DTextures && !pCommand->m_pCapabilities->m_2DArrayTextures)) + { + *pCommand->m_pInitError = -2; + pCommand->m_pCapabilities->m_ContextMajor = 1; + pCommand->m_pCapabilities->m_ContextMinor = 5; + pCommand->m_pCapabilities->m_ContextPatch = 0; + } + } + else if(MajorV < 2) + { + pCommand->m_pCapabilities->m_TileBuffering = false; + pCommand->m_pCapabilities->m_QuadBuffering = false; + pCommand->m_pCapabilities->m_TextBuffering = false; + pCommand->m_pCapabilities->m_QuadContainerBuffering = false; + pCommand->m_pCapabilities->m_ShaderSupport = false; + + pCommand->m_pCapabilities->m_MipMapping = false; + pCommand->m_pCapabilities->m_3DTextures = false; + pCommand->m_pCapabilities->m_2DArrayTextures = false; + pCommand->m_pCapabilities->m_NPOTTextures = false; + } + } +#endif + } + else if(pCommand->m_RequestedBackend == BACKEND_TYPE_OPENGL_ES) + { + if(MajorV < 3) + { + pCommand->m_pCapabilities->m_TileBuffering = false; + pCommand->m_pCapabilities->m_QuadBuffering = false; + pCommand->m_pCapabilities->m_TextBuffering = false; + pCommand->m_pCapabilities->m_QuadContainerBuffering = false; + pCommand->m_pCapabilities->m_ShaderSupport = false; + + pCommand->m_pCapabilities->m_MipMapping = false; + pCommand->m_pCapabilities->m_3DTextures = false; + pCommand->m_pCapabilities->m_2DArrayTextures = false; + pCommand->m_pCapabilities->m_NPOTTextures = false; + } + else + { + pCommand->m_pCapabilities->m_TileBuffering = true; + pCommand->m_pCapabilities->m_QuadBuffering = true; + pCommand->m_pCapabilities->m_TextBuffering = true; + pCommand->m_pCapabilities->m_QuadContainerBuffering = true; + pCommand->m_pCapabilities->m_ShaderSupport = true; + + pCommand->m_pCapabilities->m_MipMapping = true; + pCommand->m_pCapabilities->m_3DTextures = true; + pCommand->m_pCapabilities->m_2DArrayTextures = true; + pCommand->m_pCapabilities->m_NPOTTextures = true; + } + } + + if(*pCommand->m_pInitError != -2) + { + // set some default settings + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + +#ifndef BACKEND_GL_MODERN_API + if(!IsNewApi()) + { + glAlphaFunc(GL_GREATER, 0); + glEnable(GL_ALPHA_TEST); + } +#endif + + glDepthMask(0); + +#ifndef BACKEND_AS_OPENGL_ES + if(g_Config.m_DbgGfx) + { + if(GLEW_KHR_debug || GLEW_ARB_debug_output) + { + // During init, enable debug output + if(GLEW_KHR_debug) + { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(GfxOpenGLMessageCallback, 0); + } + else if(GLEW_ARB_debug_output) + { + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); + glDebugMessageCallbackARB(GfxOpenGLMessageCallback, 0); + } + dbg_msg("gfx", "Enabled OpenGL debug mode"); + } + else + dbg_msg("gfx", "Requested OpenGL debug mode, but the driver does not support the required extension"); + } +#endif + + return true; + } + else + return false; +} + +bool CCommandProcessorFragment_OpenGL::Cmd_Init(const SCommand_Init *pCommand) +{ + if(!InitOpenGL(pCommand)) + return false; + + m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage; + m_pTextureMemoryUsage->store(0, std::memory_order_relaxed); + m_MaxTexSize = -1; + + m_OpenGLTextureLodBIAS = 0; + + m_Has2DArrayTextures = pCommand->m_pCapabilities->m_2DArrayTextures; + if(pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension) + { + m_Has2DArrayTexturesAsExtension = true; + m_2DArrayTarget = GL_TEXTURE_2D_ARRAY_EXT; + } + else + { + m_Has2DArrayTexturesAsExtension = false; + m_2DArrayTarget = GL_TEXTURE_2D_ARRAY; + } + + m_Has3DTextures = pCommand->m_pCapabilities->m_3DTextures; + m_HasMipMaps = pCommand->m_pCapabilities->m_MipMapping; + m_HasNPOTTextures = pCommand->m_pCapabilities->m_NPOTTextures; + + m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; + m_LastClipEnable = false; + + return true; +} + +void CCommandProcessorFragment_OpenGL::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) +{ + glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); + + void *pTexData = pCommand->m_pData; + int Width = pCommand->m_Width; + int Height = pCommand->m_Height; + int X = pCommand->m_X; + int Y = pCommand->m_Y; + + if(!m_HasNPOTTextures) + { + float ResizeW = m_Textures[pCommand->m_Slot].m_ResizeWidth; + float ResizeH = m_Textures[pCommand->m_Slot].m_ResizeHeight; + if(ResizeW > 0 && ResizeH > 0) + { + int ResizedW = (int)(Width * ResizeW); + int ResizedH = (int)(Height * ResizeH); + + void *pTmpData = Resize(Width, Height, ResizedW, ResizedH, pCommand->m_Format, static_cast(pTexData)); + free(pTexData); + pTexData = pTmpData; + + Width = ResizedW; + Height = ResizedH; + } + } + + if(m_Textures[pCommand->m_Slot].m_RescaleCount > 0) + { + int OldWidth = Width; + int OldHeight = Height; + for(int i = 0; i < m_Textures[pCommand->m_Slot].m_RescaleCount; ++i) + { + Width >>= 1; + Height >>= 1; + + X /= 2; + Y /= 2; + } + + void *pTmpData = Resize(OldWidth, OldHeight, Width, Height, pCommand->m_Format, static_cast(pTexData)); + free(pTexData); + pTexData = pTmpData; + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, X, Y, Width, Height, + TexFormatToOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pTexData); + free(pTexData); +} + +void CCommandProcessorFragment_OpenGL::DestroyTexture(int Slot) +{ + m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) - m_Textures[Slot].m_MemSize, std::memory_order_relaxed); + + if(m_Textures[Slot].m_Tex != 0) + { + glDeleteTextures(1, &m_Textures[Slot].m_Tex); + } + + if(m_Textures[Slot].m_Tex2DArray != 0) + { + glDeleteTextures(1, &m_Textures[Slot].m_Tex2DArray); + } + + if(IsNewApi()) + { + if(m_Textures[Slot].m_Sampler != 0) + { + glDeleteSamplers(1, &m_Textures[Slot].m_Sampler); + } + if(m_Textures[Slot].m_Sampler2DArray != 0) + { + glDeleteSamplers(1, &m_Textures[Slot].m_Sampler2DArray); + } + } + + m_Textures[Slot].m_Tex = 0; + m_Textures[Slot].m_Sampler = 0; + m_Textures[Slot].m_Tex2DArray = 0; + m_Textures[Slot].m_Sampler2DArray = 0; + m_Textures[Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; +} + +void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) +{ + DestroyTexture(pCommand->m_Slot); +} + +void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) +{ +#ifndef BACKEND_GL_MODERN_API + int Width = pCommand->m_Width; + int Height = pCommand->m_Height; + void *pTexData = pCommand->m_pData; + + if(m_MaxTexSize == -1) + { + // fix the alignment to allow even 1byte changes, e.g. for alpha components + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize); + } + + if(pCommand->m_Slot >= (int)m_Textures.size()) + m_Textures.resize(m_Textures.size() * 2); + + m_Textures[pCommand->m_Slot].m_ResizeWidth = -1.f; + m_Textures[pCommand->m_Slot].m_ResizeHeight = -1.f; + + if(!m_HasNPOTTextures) + { + int PowerOfTwoWidth = HighestBit(Width); + int PowerOfTwoHeight = HighestBit(Height); + if(Width != PowerOfTwoWidth || Height != PowerOfTwoHeight) + { + void *pTmpData = Resize(Width, Height, PowerOfTwoWidth, PowerOfTwoHeight, pCommand->m_Format, static_cast(pTexData)); + free(pTexData); + pTexData = pTmpData; + + m_Textures[pCommand->m_Slot].m_ResizeWidth = (float)PowerOfTwoWidth / (float)Width; + m_Textures[pCommand->m_Slot].m_ResizeHeight = (float)PowerOfTwoHeight / (float)Height; + + Width = PowerOfTwoWidth; + Height = PowerOfTwoHeight; + } + } + + int RescaleCount = 0; + if(pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGBA || pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGB || pCommand->m_Format == CCommandBuffer::TEXFORMAT_ALPHA) + { + int OldWidth = Width; + int OldHeight = Height; + bool NeedsResize = false; + + if(Width > m_MaxTexSize || Height > m_MaxTexSize) + { + do + { + Width >>= 1; + Height >>= 1; + ++RescaleCount; + } while(Width > m_MaxTexSize || Height > m_MaxTexSize); + NeedsResize = true; + } + + if(NeedsResize) + { + void *pTmpData = Resize(OldWidth, OldHeight, Width, Height, pCommand->m_Format, static_cast(pTexData)); + free(pTexData); + pTexData = pTmpData; + } + } + m_Textures[pCommand->m_Slot].m_Width = Width; + m_Textures[pCommand->m_Slot].m_Height = Height; + m_Textures[pCommand->m_Slot].m_RescaleCount = RescaleCount; + + int Oglformat = TexFormatToOpenGLFormat(pCommand->m_Format); + int StoreOglformat = TexFormatToOpenGLFormat(pCommand->m_StoreFormat); + + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex); + glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); + } + + if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_NOMIPMAPS || !m_HasMipMaps) + { + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + } + } + else + { + if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); + +#ifndef BACKEND_AS_OPENGL_ES + if(m_OpenGLTextureLodBIAS != 0 && !m_IsOpenGLES) + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); +#endif + + glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + } + + int Flag2DArrayTexture = (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE | CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER); + int Flag3DTexture = (CCommandBuffer::TEXFLAG_TO_3D_TEXTURE | CCommandBuffer::TEXFLAG_TO_3D_TEXTURE_SINGLE_LAYER); + if((pCommand->m_Flags & (Flag2DArrayTexture | Flag3DTexture)) != 0) + { + bool Is3DTexture = (pCommand->m_Flags & Flag3DTexture) != 0; + + glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex2DArray); + + GLenum Target = GL_TEXTURE_3D; + + if(Is3DTexture) + { + Target = GL_TEXTURE_3D; + } + else + { + Target = m_2DArrayTarget; + } + + glBindTexture(Target, m_Textures[pCommand->m_Slot].m_Tex2DArray); + + if(IsNewApi()) + { + glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler2DArray); + glBindSampler(0, m_Textures[pCommand->m_Slot].m_Sampler2DArray); + } + + glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if(Is3DTexture) + { + glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if(IsNewApi()) + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(Target, GL_GENERATE_MIPMAP, GL_TRUE); + if(IsNewApi()) + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + + glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); + +#ifndef BACKEND_AS_OPENGL_ES + if(m_OpenGLTextureLodBIAS != 0 && !m_IsOpenGLES) + glTexParameterf(Target, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); +#endif + + if(IsNewApi()) + { + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); + +#ifndef BACKEND_AS_OPENGL_ES + if(m_OpenGLTextureLodBIAS != 0 && !m_IsOpenGLES) + glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); +#endif + + glBindSampler(0, 0); + } + + int ImageColorChannels = TexFormatToImageColorChannelCount(pCommand->m_Format); + + uint8_t *p3DImageData = NULL; + + bool IsSingleLayer = (pCommand->m_Flags & (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER | CCommandBuffer::TEXFLAG_TO_3D_TEXTURE_SINGLE_LAYER)) != 0; + + if(!IsSingleLayer) + p3DImageData = (uint8_t *)malloc((size_t)ImageColorChannels * Width * Height); + int Image3DWidth, Image3DHeight; + + int ConvertWidth = Width; + int ConvertHeight = Height; + + if(!IsSingleLayer) + { + if(ConvertWidth == 0 || (ConvertWidth % 16) != 0 || ConvertHeight == 0 || (ConvertHeight % 16) != 0) + { + dbg_msg("gfx", "3D/2D array texture was resized"); + int NewWidth = maximum(HighestBit(ConvertWidth), 16); + int NewHeight = maximum(HighestBit(ConvertHeight), 16); + uint8_t *pNewTexData = (uint8_t *)Resize(ConvertWidth, ConvertHeight, NewWidth, NewHeight, pCommand->m_Format, (const uint8_t *)pTexData); + + ConvertWidth = NewWidth; + ConvertHeight = NewHeight; + + free(pTexData); + pTexData = pNewTexData; + } + } + + if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) + { + if(IsSingleLayer) + { + glTexImage3D(Target, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + } + else + { + glTexImage3D(Target, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); + } + } + + if(!IsSingleLayer) + free(p3DImageData); + } + } + + // This is the initial value for the wrap modes + m_Textures[pCommand->m_Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; + + // calculate memory usage + m_Textures[pCommand->m_Slot].m_MemSize = Width * Height * pCommand->m_PixelSize; + while(Width > 2 && Height > 2) + { + Width >>= 1; + Height >>= 1; + m_Textures[pCommand->m_Slot].m_MemSize += Width * Height * pCommand->m_PixelSize; + } + m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_Textures[pCommand->m_Slot].m_MemSize, std::memory_order_relaxed); + + free(pTexData); +#endif +} + +void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) +{ + glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) +{ +#ifndef BACKEND_GL_MODERN_API + SetState(pCommand->m_State); + + glVertexPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices); + glTexCoordPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices + sizeof(float) * 2); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices + sizeof(float) * 4); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + switch(pCommand->m_PrimType) + { + case CCommandBuffer::PRIMTYPE_QUADS: +#ifndef BACKEND_AS_OPENGL_ES + glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount * 4); +#endif + break; + case CCommandBuffer::PRIMTYPE_LINES: + glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); + break; + case CCommandBuffer::PRIMTYPE_TRIANGLES: + glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); + break; + default: + dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); + }; +#endif +} + +void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand) +{ + // fetch image data + GLint aViewport[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_VIEWPORT, aViewport); + + int w = aViewport[2]; + int h = aViewport[3]; + + // we allocate one more row to use when we are flipping the texture + unsigned char *pPixelData = (unsigned char *)malloc((size_t)w * (h + 1) * 4); + unsigned char *pTempRow = pPixelData + w * h * 4; + + // fetch the pixels + GLint Alignment; + glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pPixelData); + glPixelStorei(GL_PACK_ALIGNMENT, Alignment); + + // flip the pixel because opengl works from bottom left corner + for(int y = 0; y < h / 2; y++) + { + mem_copy(pTempRow, pPixelData + y * w * 4, w * 4); + mem_copy(pPixelData + y * w * 4, pPixelData + (h - y - 1) * w * 4, w * 4); + mem_copy(pPixelData + (h - y - 1) * w * 4, pTempRow, w * 4); + for(int x = 0; x < w; x++) + { + pPixelData[y * w * 4 + x * 4 + 3] = 255; + pPixelData[(h - y - 1) * w * 4 + x * 4 + 3] = 255; + } + } + + // fill in the information + pCommand->m_pImage->m_Width = w; + pCommand->m_pImage->m_Height = h; + pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGBA; + pCommand->m_pImage->m_pData = pPixelData; +} + +CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL() +{ + m_Textures.resize(CCommandBuffer::MAX_TEXTURES); + m_HasShaders = false; +} + +bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) +{ + switch(pBaseCommand->m_Cmd) + { + case CCommandProcessorFragment_OpenGL::CMD_INIT: + Cmd_Init(static_cast(pBaseCommand)); + break; + case CCommandProcessorFragment_OpenGL::CMD_SHUTDOWN: + Cmd_Shutdown(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_TEXTURE_CREATE: + Cmd_Texture_Create(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_TEXTURE_DESTROY: + Cmd_Texture_Destroy(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_TEXTURE_UPDATE: + Cmd_Texture_Update(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_CLEAR: + Cmd_Clear(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_RENDER: + Cmd_Render(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_RENDER_TEX3D: + Cmd_RenderTex3D(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_SCREENSHOT: + Cmd_Screenshot(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_UPDATE_VIEWPORT: + Cmd_Update_Viewport(static_cast(pBaseCommand)); + break; + case CCommandBuffer::CMD_FINISH: + Cmd_Finish(static_cast(pBaseCommand)); + break; + + case CCommandBuffer::CMD_CREATE_BUFFER_OBJECT: Cmd_CreateBufferObject(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_UPDATE_BUFFER_OBJECT: Cmd_UpdateBufferObject(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RECREATE_BUFFER_OBJECT: Cmd_RecreateBufferObject(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_COPY_BUFFER_OBJECT: Cmd_CopyBufferObject(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_DELETE_BUFFER_OBJECT: Cmd_DeleteBufferObject(static_cast(pBaseCommand)); break; + + case CCommandBuffer::CMD_CREATE_BUFFER_CONTAINER: Cmd_CreateBufferContainer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_UPDATE_BUFFER_CONTAINER: Cmd_UpdateBufferContainer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_DELETE_BUFFER_CONTAINER: Cmd_DeleteBufferContainer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_INDICES_REQUIRED_NUM_NOTIFY: Cmd_IndicesRequiredNumNotify(static_cast(pBaseCommand)); break; + + case CCommandBuffer::CMD_RENDER_TILE_LAYER: Cmd_RenderTileLayer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_BORDER_TILE: Cmd_RenderBorderTile(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_BORDER_TILE_LINE: Cmd_RenderBorderTileLine(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_QUAD_LAYER: Cmd_RenderQuadLayer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_TEXT: Cmd_RenderText(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_TEXT_STREAM: Cmd_RenderTextStream(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER: Cmd_RenderQuadContainer(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_EX: Cmd_RenderQuadContainerEx(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_SPRITE_MULTIPLE: Cmd_RenderQuadContainerAsSpriteMultiple(static_cast(pBaseCommand)); break; + default: return false; + } + + return true; +} + +// ------------ CCommandProcessorFragment_OpenGL2 + +void CCommandProcessorFragment_OpenGL2::UseProgram(CGLSLTWProgram *pProgram) +{ + pProgram->UseProgram(); +} + +bool CCommandProcessorFragment_OpenGL2::IsAndUpdateTextureSlotBound(int IDX, int Slot, bool Is2DArray) +{ + if(m_TextureSlotBoundToUnit[IDX].m_TextureSlot == Slot && m_TextureSlotBoundToUnit[IDX].m_Is2DArray == Is2DArray) + return true; + else + { + //the texture slot uses this index now + m_TextureSlotBoundToUnit[IDX].m_TextureSlot = Slot; + m_TextureSlotBoundToUnit[IDX].m_Is2DArray = Is2DArray; + return false; + } +} + +void CCommandProcessorFragment_OpenGL2::SetState(const CCommandBuffer::SState &State, CGLSLTWProgram *pProgram, bool Use2DArrayTextures) +{ + if(m_LastBlendMode == CCommandBuffer::BLEND_NONE) + { + m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + if(State.m_BlendMode != m_LastBlendMode && State.m_BlendMode != CCommandBuffer::BLEND_NONE) + { + // blend + switch(State.m_BlendMode) + { + case CCommandBuffer::BLEND_NONE: + // We don't really need this anymore + //glDisable(GL_BLEND); + break; + case CCommandBuffer::BLEND_ALPHA: + //glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case CCommandBuffer::BLEND_ADDITIVE: + //glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + default: + dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode); + }; + + m_LastBlendMode = State.m_BlendMode; + } + + // clip + if(State.m_ClipEnable) + { + glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH); + glEnable(GL_SCISSOR_TEST); + m_LastClipEnable = true; + } + else if(m_LastClipEnable) + { + // Don't disable it always + glDisable(GL_SCISSOR_TEST); + m_LastClipEnable = false; + } + + if(!IsNewApi()) + { + glDisable(GL_TEXTURE_2D); + if(!m_HasShaders) + { + if(m_Has3DTextures) + glDisable(GL_TEXTURE_3D); + if(m_Has2DArrayTextures) + { + glDisable(m_2DArrayTarget); + } + } + } + + // texture + if(IsTexturedState(State)) + { + int Slot = 0; + if(m_UseMultipleTextureUnits) + { + Slot = State.m_Texture % m_MaxTextureUnits; + if(!IsAndUpdateTextureSlotBound(Slot, State.m_Texture, Use2DArrayTextures)) + { + glActiveTexture(GL_TEXTURE0 + Slot); + if(!Use2DArrayTextures) + { + glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); + if(IsNewApi()) + glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler); + } + else + { + glBindTexture(GL_TEXTURE_2D_ARRAY, m_Textures[State.m_Texture].m_Tex2DArray); + if(IsNewApi()) + glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); + } + } + } + else + { + Slot = 0; + if(!Use2DArrayTextures) + { + if(!IsNewApi() && !m_HasShaders) + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); + if(IsNewApi()) + glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler); + } + else + { + if(!m_Has2DArrayTextures) + { + if(!IsNewApi() && !m_HasShaders) + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, m_Textures[State.m_Texture].m_Tex2DArray); + if(IsNewApi()) + glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); + } + else + { + if(!IsNewApi() && !m_HasShaders) + glEnable(m_2DArrayTarget); + glBindTexture(m_2DArrayTarget, m_Textures[State.m_Texture].m_Tex2DArray); + if(IsNewApi()) + glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); + } + } + } + + if(pProgram->m_LastTextureSampler != Slot) + { + pProgram->SetUniform(pProgram->m_LocTextureSampler, Slot); + pProgram->m_LastTextureSampler = Slot; + } + + if(m_Textures[State.m_Texture].m_LastWrapMode != State.m_WrapMode && !Use2DArrayTextures) + { + switch(State.m_WrapMode) + { + case CCommandBuffer::WRAP_REPEAT: + if(IsNewApi()) + { + glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_S, GL_REPEAT); + glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + break; + case CCommandBuffer::WRAP_CLAMP: + if(IsNewApi()) + { + glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + break; + default: + dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode); + }; + m_Textures[State.m_Texture].m_LastWrapMode = State.m_WrapMode; + } + } + + if(pProgram->m_LastScreen[0] != State.m_ScreenTL.x || pProgram->m_LastScreen[1] != State.m_ScreenTL.y || pProgram->m_LastScreen[2] != State.m_ScreenBR.x || pProgram->m_LastScreen[3] != State.m_ScreenBR.y) + { + pProgram->m_LastScreen[0] = State.m_ScreenTL.x; + pProgram->m_LastScreen[1] = State.m_ScreenTL.y; + pProgram->m_LastScreen[2] = State.m_ScreenBR.x; + pProgram->m_LastScreen[3] = State.m_ScreenBR.y; + // screen mapping + // orthographic projection matrix + // the z coordinate is the same for every vertex, so just ignore the z coordinate and set it in the shaders + float m[2 * 4] = { + 2.f / (State.m_ScreenBR.x - State.m_ScreenTL.x), 0, 0, -((State.m_ScreenBR.x + State.m_ScreenTL.x) / (State.m_ScreenBR.x - State.m_ScreenTL.x)), + 0, (2.f / (State.m_ScreenTL.y - State.m_ScreenBR.y)), 0, -((State.m_ScreenTL.y + State.m_ScreenBR.y) / (State.m_ScreenTL.y - State.m_ScreenBR.y)), + //0, 0, -(2.f/(9.f)), -((11.f)/(9.f)), + //0, 0, 0, 1.0f + }; + + // transpose bcs of column-major order of opengl + glUniformMatrix4x2fv(pProgram->m_LocPos, 1, true, (float *)&m); + } +} + +#ifndef BACKEND_GL_MODERN_API +bool CCommandProcessorFragment_OpenGL2::DoAnalyzeStep(size_t StepN, size_t CheckCount, size_t VerticesCount, uint8_t aFakeTexture[], size_t SingleImageSize) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + int Slot = 0; + if(m_HasShaders) + { + CGLSLTWProgram *pProgram = m_pPrimitive3DProgramTextured; + if(StepN == 1) + pProgram = m_pTileProgramTextured; + UseProgram(pProgram); + + pProgram->SetUniform(pProgram->m_LocTextureSampler, Slot); + + if(StepN == 1) + { + float aColor[4] = {1.f, 1.f, 1.f, 1.f}; + pProgram->SetUniformVec4(((CGLSLTileProgram *)pProgram)->m_LocColor, 1, aColor); + } + + float m[2 * 4] = { + 1, 0, 0, 0, + 0, 1, 0, 0}; + + // transpose bcs of column-major order of opengl + glUniformMatrix4x2fv(pProgram->m_LocPos, 1, true, (float *)&m); + } + else + { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(-1, 1, -1, 1, -10.0f, 10.f); + } + + GLuint BufferID = 0; + if(StepN == 1 && m_HasShaders) + { + glGenBuffers(1, &BufferID); + glBindBuffer(GL_ARRAY_BUFFER, BufferID); + glBufferData(GL_ARRAY_BUFFER, VerticesCount * sizeof((m_aStreamVertices[0])), m_aStreamVertices, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof((m_aStreamVertices[0])), 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof((m_aStreamVertices[0])), (GLvoid *)(sizeof(vec4) + sizeof(vec2))); + } + else + { + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); + glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); + glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); + } + + glDrawArrays(GL_QUADS, 0, VerticesCount); + + if(StepN == 1 && m_HasShaders) + { + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &BufferID); + } + else + { + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + + if(m_HasShaders) + { + glUseProgram(0); + } + + glFinish(); + + GLint aViewport[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_VIEWPORT, aViewport); + + int w = aViewport[2]; + int h = aViewport[3]; + + size_t PixelDataSize = (size_t)w * h * 3; + if(PixelDataSize == 0) + return false; + uint8_t *pPixelData = (uint8_t *)malloc(PixelDataSize); + + // fetch the pixels + GLint Alignment; + glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); + glPixelStorei(GL_PACK_ALIGNMENT, Alignment); + + // now analyse the image data + bool CheckFailed = false; + int WidthTile = w / 16; + int HeightTile = h / 16; + int StartX = WidthTile / 2; + int StartY = HeightTile / 2; + for(size_t d = 0; d < CheckCount; ++d) + { + int CurX = (int)d % 16; + int CurY = (int)d / 16; + + int CheckX = StartX + CurX * WidthTile; + int CheckY = StartY + CurY * HeightTile; + + ptrdiff_t OffsetPixelData = (CheckY * (w * 3)) + (CheckX * 3); + ptrdiff_t OffsetFakeTexture = SingleImageSize * d; + OffsetPixelData = clamp(OffsetPixelData, 0, (ptrdiff_t)PixelDataSize); + OffsetFakeTexture = clamp(OffsetFakeTexture, 0, (ptrdiff_t)(SingleImageSize * CheckCount)); + uint8_t *pPixel = pPixelData + OffsetPixelData; + uint8_t *pPixelTex = aFakeTexture + OffsetFakeTexture; + for(size_t i = 0; i < 3; ++i) + { + if((pPixel[i] < pPixelTex[i] - 25) || (pPixel[i] > pPixelTex[i] + 25)) + { + CheckFailed = true; + break; + } + } + } + + free(pPixelData); + return !CheckFailed; +} + +bool CCommandProcessorFragment_OpenGL2::IsTileMapAnalysisSucceeded() +{ + glClearColor(0, 0, 0, 1); + + // create fake texture 1024x1024 + const size_t ImageWidth = 1024; + const size_t ImageHeight = 1024; + uint8_t *pFakeTexture = (uint8_t *)malloc(sizeof(uint8_t) * ImageWidth * ImageHeight * 4); + // fill by colors stepping by 50 => (255 / 50 ~ 5) => 5 times 3(color channels) = 5 ^ 3 = 125 possibilities to check + size_t CheckCount = 5 * 5 * 5; + // always fill 4 pixels of the texture, so the sampling is accurate + int aCurColor[4] = {25, 25, 25, 255}; + const size_t SingleImageWidth = 64; + const size_t SingleImageHeight = 64; + size_t SingleImageSize = SingleImageWidth * SingleImageHeight * 4; + for(size_t d = 0; d < CheckCount; ++d) + { + uint8_t *pCurFakeTexture = pFakeTexture + (ptrdiff_t)(SingleImageSize * d); + + uint8_t aCurColorUint8[SingleImageWidth * SingleImageHeight * 4]; + for(size_t y = 0; y < SingleImageHeight; ++y) + { + for(size_t x = 0; x < SingleImageWidth; ++x) + { + for(size_t i = 0; i < 4; ++i) + { + aCurColorUint8[(y * SingleImageWidth * 4) + (x * 4) + i] = (uint8_t)aCurColor[i]; + } + } + } + mem_copy(pCurFakeTexture, aCurColorUint8, sizeof(aCurColorUint8)); + + aCurColor[2] += 50; + if(aCurColor[2] > 225) + { + aCurColor[2] -= 250; + aCurColor[1] += 50; + } + if(aCurColor[1] > 225) + { + aCurColor[1] -= 250; + aCurColor[0] += 50; + } + if(aCurColor[0] > 225) + { + break; + } + } + + // upload the texture + GLuint FakeTexture; + glGenTextures(1, &FakeTexture); + + GLenum Target = GL_TEXTURE_3D; + if(m_Has2DArrayTextures) + { + Target = m_2DArrayTarget; + } + + glBindTexture(Target, FakeTexture); + glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if(!m_Has2DArrayTextures) + { + glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else + { + glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(Target, GL_GENERATE_MIPMAP, GL_TRUE); + } + + glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); + + glTexImage3D(Target, 0, GL_RGBA, ImageWidth / 16, ImageHeight / 16, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, pFakeTexture); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_SCISSOR_TEST); + + if(!m_HasShaders) + { + glDisable(GL_TEXTURE_2D); + if(m_Has3DTextures) + glDisable(GL_TEXTURE_3D); + if(m_Has2DArrayTextures) + { + glDisable(m_2DArrayTarget); + } + + if(!m_Has2DArrayTextures) + { + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, FakeTexture); + } + else + { + glEnable(m_2DArrayTarget); + glBindTexture(m_2DArrayTarget, FakeTexture); + } + } + + static_assert(sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0]) >= 256 * 4, "Keep the number of stream vertices >= 256 * 4."); + + size_t VertexCount = 0; + for(size_t i = 0; i < CheckCount; ++i) + { + float XPos = (float)(i % 16); + float YPos = (float)(i / 16); + + GL_SVertexTex3D *pVertex = &m_aStreamVertices[VertexCount++]; + GL_SVertexTex3D *pVertexBefore = pVertex; + pVertex->m_Pos.x = XPos / 16.f; + pVertex->m_Pos.y = YPos / 16.f; + pVertex->m_Color.r = 1; + pVertex->m_Color.g = 1; + pVertex->m_Color.b = 1; + pVertex->m_Color.a = 1; + pVertex->m_Tex.u = 0; + pVertex->m_Tex.v = 0; + + pVertex = &m_aStreamVertices[VertexCount++]; + pVertex->m_Pos.x = XPos / 16.f + 1.f / 16.f; + pVertex->m_Pos.y = YPos / 16.f; + pVertex->m_Color.r = 1; + pVertex->m_Color.g = 1; + pVertex->m_Color.b = 1; + pVertex->m_Color.a = 1; + pVertex->m_Tex.u = 1; + pVertex->m_Tex.v = 0; + + pVertex = &m_aStreamVertices[VertexCount++]; + pVertex->m_Pos.x = XPos / 16.f + 1.f / 16.f; + pVertex->m_Pos.y = YPos / 16.f + 1.f / 16.f; + pVertex->m_Color.r = 1; + pVertex->m_Color.g = 1; + pVertex->m_Color.b = 1; + pVertex->m_Color.a = 1; + pVertex->m_Tex.u = 1; + pVertex->m_Tex.v = 1; + + pVertex = &m_aStreamVertices[VertexCount++]; + pVertex->m_Pos.x = XPos / 16.f; + pVertex->m_Pos.y = YPos / 16.f + 1.f / 16.f; + pVertex->m_Color.r = 1; + pVertex->m_Color.g = 1; + pVertex->m_Color.b = 1; + pVertex->m_Color.a = 1; + pVertex->m_Tex.u = 0; + pVertex->m_Tex.v = 1; + + for(size_t n = 0; n < 4; ++n) + { + pVertexBefore[n].m_Pos.x *= 2; + pVertexBefore[n].m_Pos.x -= 1; + pVertexBefore[n].m_Pos.y *= 2; + pVertexBefore[n].m_Pos.y -= 1; + if(m_Has2DArrayTextures) + { + pVertexBefore[n].m_Tex.w = i; + } + else + { + pVertexBefore[n].m_Tex.w = (i + 0.5f) / 256.f; + } + } + } + + //everything build up, now do the analyze steps + bool NoError = DoAnalyzeStep(0, CheckCount, VertexCount, pFakeTexture, SingleImageSize); + if(NoError && m_HasShaders) + NoError &= DoAnalyzeStep(1, CheckCount, VertexCount, pFakeTexture, SingleImageSize); + + glDeleteTextures(1, &FakeTexture); + free(pFakeTexture); + + return NoError; +} + +bool CCommandProcessorFragment_OpenGL2::Cmd_Init(const SCommand_Init *pCommand) +{ + if(!CCommandProcessorFragment_OpenGL::Cmd_Init(pCommand)) + return false; + + m_OpenGLTextureLodBIAS = g_Config.m_GfxOpenGLTextureLODBIAS; + + m_HasShaders = pCommand->m_pCapabilities->m_ShaderSupport; + + bool HasAllFunc = true; +#ifndef BACKEND_AS_OPENGL_ES + if(m_HasShaders) + { + HasAllFunc &= (glUniformMatrix4x2fv != NULL) && (glGenBuffers != NULL); + HasAllFunc &= (glBindBuffer != NULL) && (glBufferData != NULL); + HasAllFunc &= (glEnableVertexAttribArray != NULL) && (glVertexAttribPointer != NULL); + HasAllFunc &= (glDisableVertexAttribArray != NULL) && (glDeleteBuffers != NULL); + HasAllFunc &= (glUseProgram != NULL) && (glTexImage3D != NULL); + HasAllFunc &= (glBindAttribLocation != NULL) && (glTexImage3D != NULL); + HasAllFunc &= (glBufferSubData != NULL) && (glGetUniformLocation != NULL); + HasAllFunc &= (glUniform1i != NULL) && (glUniform1f != NULL); + HasAllFunc &= (glUniform1ui != NULL) && (glUniform1i != NULL); + HasAllFunc &= (glUniform1fv != NULL) && (glUniform2fv != NULL); + HasAllFunc &= (glUniform4fv != NULL) && (glGetAttachedShaders != NULL); + HasAllFunc &= (glGetProgramInfoLog != NULL) && (glGetProgramiv != NULL); + HasAllFunc &= (glLinkProgram != NULL) && (glDetachShader != NULL); + HasAllFunc &= (glAttachShader != NULL) && (glDeleteProgram != NULL); + HasAllFunc &= (glCreateProgram != NULL) && (glShaderSource != NULL); + HasAllFunc &= (glCompileShader != NULL) && (glGetShaderiv != NULL); + HasAllFunc &= (glGetShaderInfoLog != NULL) && (glDeleteShader != NULL); + HasAllFunc &= (glCreateShader != NULL); + } +#endif + + bool AnalysisCorrect = true; + if(HasAllFunc) + { + if(m_HasShaders) + { + m_pTileProgram = new CGLSLTileProgram; + m_pTileProgramTextured = new CGLSLTileProgram; + m_pPrimitive3DProgram = new CGLSLPrimitiveProgram; + m_pPrimitive3DProgramTextured = new CGLSLPrimitiveProgram; + + CGLSLCompiler ShaderCompiler(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch, m_IsOpenGLES, m_OpenGLTextureLodBIAS / 1000.0f); + ShaderCompiler.SetHasTextureArray(pCommand->m_pCapabilities->m_2DArrayTextures); + + if(pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); + else + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); + + m_pPrimitive3DProgram->CreateProgram(); + m_pPrimitive3DProgram->AddShader(&PrimitiveVertexShader); + m_pPrimitive3DProgram->AddShader(&PrimitiveFragmentShader); + m_pPrimitive3DProgram->LinkProgram(); + + UseProgram(m_pPrimitive3DProgram); + + m_pPrimitive3DProgram->m_LocPos = m_pPrimitive3DProgram->GetUniformLoc("gPos"); + } + + if(pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); + else + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); + { + CGLSL PrimitiveVertexShader; + CGLSL PrimitiveFragmentShader; + ShaderCompiler.AddDefine("TW_TEXTURED", ""); + if(!pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.AddDefine("TW_3D_TEXTURED", ""); + PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); + PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pPrimitive3DProgramTextured->CreateProgram(); + m_pPrimitive3DProgramTextured->AddShader(&PrimitiveVertexShader); + m_pPrimitive3DProgramTextured->AddShader(&PrimitiveFragmentShader); + m_pPrimitive3DProgramTextured->LinkProgram(); + + UseProgram(m_pPrimitive3DProgramTextured); + + m_pPrimitive3DProgramTextured->m_LocPos = m_pPrimitive3DProgramTextured->GetUniformLoc("gPos"); + m_pPrimitive3DProgramTextured->m_LocTextureSampler = m_pPrimitive3DProgramTextured->GetUniformLoc("gTextureSampler"); + } + if(pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); + else + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); + { + CGLSL VertexShader; + CGLSL FragmentShader; + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + + m_pTileProgram->CreateProgram(); + m_pTileProgram->AddShader(&VertexShader); + m_pTileProgram->AddShader(&FragmentShader); + + glBindAttribLocation(m_pTileProgram->GetProgramID(), 0, "inVertex"); + + m_pTileProgram->LinkProgram(); + + UseProgram(m_pTileProgram); + + m_pTileProgram->m_LocPos = m_pTileProgram->GetUniformLoc("gPos"); + m_pTileProgram->m_LocColor = m_pTileProgram->GetUniformLoc("gVertColor"); + } + if(pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); + else + ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); + { + CGLSL VertexShader; + CGLSL FragmentShader; + ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); + if(!pCommand->m_pCapabilities->m_2DArrayTextures) + ShaderCompiler.AddDefine("TW_TILE_3D_TEXTURED", ""); + VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); + FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); + ShaderCompiler.ClearDefines(); + + m_pTileProgramTextured->CreateProgram(); + m_pTileProgramTextured->AddShader(&VertexShader); + m_pTileProgramTextured->AddShader(&FragmentShader); + + glBindAttribLocation(m_pTileProgram->GetProgramID(), 0, "inVertex"); + glBindAttribLocation(m_pTileProgram->GetProgramID(), 1, "inVertexTexCoord"); + + m_pTileProgramTextured->LinkProgram(); + + UseProgram(m_pTileProgramTextured); + + m_pTileProgramTextured->m_LocPos = m_pTileProgramTextured->GetUniformLoc("gPos"); + m_pTileProgramTextured->m_LocTextureSampler = m_pTileProgramTextured->GetUniformLoc("gTextureSampler"); + m_pTileProgramTextured->m_LocColor = m_pTileProgramTextured->GetUniformLoc("gVertColor"); + } + + glUseProgram(0); + } + + if(g_Config.m_Gfx3DTextureAnalysisDone == 0) + { + AnalysisCorrect = IsTileMapAnalysisSucceeded(); + if(AnalysisCorrect) + { + g_Config.m_Gfx3DTextureAnalysisDone = 1; + } + } + } + + if(!AnalysisCorrect || !HasAllFunc) + { + // downgrade to opengl 1.5 + *pCommand->m_pInitError = -2; + pCommand->m_pCapabilities->m_ContextMajor = 1; + pCommand->m_pCapabilities->m_ContextMinor = 5; + pCommand->m_pCapabilities->m_ContextPatch = 0; + + return false; + } + + return true; +} + +void CCommandProcessorFragment_OpenGL2::Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) +{ + if(m_HasShaders) + { + CGLSLPrimitiveProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pPrimitive3DProgramTextured; + } + else + pProgram = m_pPrimitive3DProgram; + + UseProgram(pProgram); + + SetState(pCommand->m_State, pProgram, true); + } + else + { + CCommandProcessorFragment_OpenGL::SetState(pCommand->m_State, true); + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(pCommand->m_pVertices[0]), pCommand->m_pVertices); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(pCommand->m_pVertices[0]), (uint8_t *)pCommand->m_pVertices + (ptrdiff_t)(sizeof(vec2))); + glTexCoordPointer(3, GL_FLOAT, sizeof(pCommand->m_pVertices[0]), (uint8_t *)pCommand->m_pVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(unsigned char) * 4)); + + switch(pCommand->m_PrimType) + { + case CCommandBuffer::PRIMTYPE_QUADS: + glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount * 4); + break; + case CCommandBuffer::PRIMTYPE_TRIANGLES: + glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); + break; + default: + dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); + }; + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + if(m_HasShaders) + { + glUseProgram(0); + } +} + +void CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + //create necessary space + if((size_t)Index >= m_BufferObjectIndices.size()) + { + for(int i = m_BufferObjectIndices.size(); i < Index + 1; ++i) + { + m_BufferObjectIndices.push_back(SBufferObject(0)); + } + } + + GLuint VertBufferID = 0; + + if(m_HasShaders) + { + glGenBuffers(1, &VertBufferID); + glBindBuffer(GL_COPY_WRITE_BUFFER, VertBufferID); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } + + SBufferObject &BufferObject = m_BufferObjectIndices[Index]; + BufferObject.m_BufferObjectID = VertBufferID; + BufferObject.m_DataSize = pCommand->m_DataSize; + BufferObject.m_pData = malloc(pCommand->m_DataSize); + if(pUploadData) + mem_copy(BufferObject.m_pData, pUploadData, pCommand->m_DataSize); + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + SBufferObject &BufferObject = m_BufferObjectIndices[Index]; + + if(m_HasShaders) + { + glBindBuffer(GL_COPY_WRITE_BUFFER, BufferObject.m_BufferObjectID); + glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } + + BufferObject.m_DataSize = pCommand->m_DataSize; + if(BufferObject.m_pData) + free(BufferObject.m_pData); + BufferObject.m_pData = malloc(pCommand->m_DataSize); + if(pUploadData) + mem_copy(BufferObject.m_pData, pUploadData, pCommand->m_DataSize); + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) +{ + void *pUploadData = pCommand->m_pUploadData; + int Index = pCommand->m_BufferIndex; + SBufferObject &BufferObject = m_BufferObjectIndices[Index]; + + if(m_HasShaders) + { + glBindBuffer(GL_COPY_WRITE_BUFFER, BufferObject.m_BufferObjectID); + glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pOffset), (GLsizeiptr)(pCommand->m_DataSize), pUploadData); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } + + if(pUploadData) + mem_copy(((uint8_t *)BufferObject.m_pData) + (ptrdiff_t)pCommand->m_pOffset, pUploadData, pCommand->m_DataSize); + + if(pCommand->m_DeletePointer) + free(pUploadData); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) +{ + int WriteIndex = pCommand->m_WriteBufferIndex; + int ReadIndex = pCommand->m_ReadBufferIndex; + + SBufferObject &ReadBufferObject = m_BufferObjectIndices[ReadIndex]; + SBufferObject &WriteBufferObject = m_BufferObjectIndices[WriteIndex]; + + mem_copy(((uint8_t *)WriteBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pWriteOffset, ((uint8_t *)ReadBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pReadOffset, pCommand->m_CopySize); + + if(m_HasShaders) + { + glBindBuffer(GL_COPY_WRITE_BUFFER, WriteBufferObject.m_BufferObjectID); + glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pWriteOffset), (GLsizeiptr)(pCommand->m_CopySize), ((uint8_t *)WriteBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pWriteOffset); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); + } +} + +void CCommandProcessorFragment_OpenGL2::Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) +{ + int Index = pCommand->m_BufferIndex; + SBufferObject &BufferObject = m_BufferObjectIndices[Index]; + + if(m_HasShaders) + { + glDeleteBuffers(1, &BufferObject.m_BufferObjectID); + } + + if(BufferObject.m_pData) + { + free(BufferObject.m_pData); + BufferObject.m_pData = NULL; + } +} + +void CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //create necessary space + if((size_t)Index >= m_BufferContainers.size()) + { + for(int i = m_BufferContainers.size(); i < Index + 1; ++i) + { + SBufferContainer Container; + Container.m_ContainerInfo.m_Stride = 0; + m_BufferContainers.push_back(Container); + } + } + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + + for(int i = 0; i < pCommand->m_AttrCount; ++i) + { + SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; + BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); + } + + BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; +} + +void CCommandProcessorFragment_OpenGL2::Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) +{ + SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; + + BufferContainer.m_ContainerInfo.m_Attributes.clear(); + + for(int i = 0; i < pCommand->m_AttrCount; ++i) + { + SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; + BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); + } + + BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; +} + +void CCommandProcessorFragment_OpenGL2::Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) +{ + SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; + + if(pCommand->m_DestroyAllBO) + { + for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) + { + int VertBufferID = BufferContainer.m_ContainerInfo.m_Attributes[i].m_VertBufferBindingIndex; + if(VertBufferID != -1) + { + for(auto &Attribute : BufferContainer.m_ContainerInfo.m_Attributes) + { + // set all equal ids to zero to not double delete + if(VertBufferID == Attribute.m_VertBufferBindingIndex) + { + Attribute.m_VertBufferBindingIndex = -1; + } + } + + if(m_HasShaders) + { + glDeleteBuffers(1, &m_BufferObjectIndices[VertBufferID].m_BufferObjectID); + } + + if(m_BufferObjectIndices[VertBufferID].m_pData) + { + free(m_BufferObjectIndices[VertBufferID].m_pData); + m_BufferObjectIndices[VertBufferID].m_pData = NULL; + } + } + } + } + + BufferContainer.m_ContainerInfo.m_Attributes.clear(); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) +{ +} + +void CCommandProcessorFragment_OpenGL2::RenderBorderTileEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int DrawNum, const float *pOffset, const float *pDir, int JumpIndex) +{ + if(m_HasShaders) + { + CGLSLPrimitiveProgram *pProgram = NULL; + if(IsTexturedState(State)) + { + pProgram = m_pPrimitive3DProgramTextured; + } + else + pProgram = m_pPrimitive3DProgram; + + UseProgram(pProgram); + + SetState(State, pProgram, true); + } + else + { + CCommandProcessorFragment_OpenGL::SetState(State, true); + } + + bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; + + SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); + glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); + if(IsTextured) + glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); + + size_t VertexCount = 0; + for(size_t i = 0; i < DrawNum; ++i) + { + GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pBuffOffset)) / (6 * sizeof(unsigned int))) * 4); + size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); + size_t CurBufferOffset = (RealOffset)*SingleVertSize; + + for(size_t n = 0; n < 4; ++n) + { + int XCount = i - (int(i / JumpIndex) * JumpIndex); + int YCount = (int(i / JumpIndex)); + + ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); + vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); + + GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; + mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); + mem_copy(&Vertex.m_Color, pColor, sizeof(vec4)); + if(IsTextured) + { + vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); + mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); + } + + Vertex.m_Pos.x += pOffset[0] + pDir[0] * XCount; + Vertex.m_Pos.y += pOffset[1] + pDir[1] * YCount; + + if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) + { + glDrawArrays(GL_QUADS, 0, VertexCount); + VertexCount = 0; + } + } + } + if(VertexCount > 0) + glDrawArrays(GL_QUADS, 0, VertexCount); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + if(m_HasShaders) + { + glUseProgram(0); + } +} + +void CCommandProcessorFragment_OpenGL2::RenderBorderTileLineEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int IndexDrawNum, unsigned int DrawNum, const float *pOffset, const float *pDir) +{ + if(m_HasShaders) + { + CGLSLPrimitiveProgram *pProgram = NULL; + if(IsTexturedState(State)) + { + pProgram = m_pPrimitive3DProgramTextured; + } + else + pProgram = m_pPrimitive3DProgram; + + UseProgram(pProgram); + + SetState(State, pProgram, true); + } + else + { + CCommandProcessorFragment_OpenGL::SetState(State, true); + } + + bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; + + SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); + glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); + if(IsTextured) + glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); + + size_t VertexCount = 0; + for(size_t i = 0; i < DrawNum; ++i) + { + GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pBuffOffset)) / (6 * sizeof(unsigned int))) * 4); + size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); + size_t CurBufferOffset = (RealOffset)*SingleVertSize; + size_t VerticesPerLine = (size_t)IndexDrawNum / 6; + + for(size_t n = 0; n < 4 * (size_t)VerticesPerLine; ++n) + { + ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); + vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); + + GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; + mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); + mem_copy(&Vertex.m_Color, pColor, sizeof(vec4)); + if(IsTextured) + { + vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); + mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); + } + + Vertex.m_Pos.x += pOffset[0] + pDir[0] * i; + Vertex.m_Pos.y += pOffset[1] + pDir[1] * i; + + if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) + { + glDrawArrays(GL_QUADS, 0, VertexCount); + VertexCount = 0; + } + } + } + if(VertexCount > 0) + glDrawArrays(GL_QUADS, 0, VertexCount); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + if(m_HasShaders) + { + glUseProgram(0); + } +} + +void CCommandProcessorFragment_OpenGL2::Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + + RenderBorderTileEmulation(BufferContainer, pCommand->m_State, (float *)&pCommand->m_Color, pCommand->m_pIndicesOffset, pCommand->m_DrawNum, pCommand->m_Offset, pCommand->m_Dir, pCommand->m_JumpIndex); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + + RenderBorderTileLineEmulation(BufferContainer, pCommand->m_State, (float *)&pCommand->m_Color, pCommand->m_pIndicesOffset, pCommand->m_IndexDrawNum, pCommand->m_DrawNum, pCommand->m_Offset, pCommand->m_Dir); +} + +void CCommandProcessorFragment_OpenGL2::Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) +{ + int Index = pCommand->m_BufferContainerIndex; + //if space not there return + if((size_t)Index >= m_BufferContainers.size()) + return; + + SBufferContainer &BufferContainer = m_BufferContainers[Index]; + + if(pCommand->m_IndicesDrawNum == 0) + { + return; //nothing to draw + } + + if(m_HasShaders) + { + CGLSLTileProgram *pProgram = NULL; + if(IsTexturedState(pCommand->m_State)) + { + pProgram = m_pTileProgramTextured; + } + else + pProgram = m_pTileProgram; + + UseProgram(pProgram); + + SetState(pCommand->m_State, pProgram, true); + pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); + } + else + { + CCommandProcessorFragment_OpenGL::SetState(pCommand->m_State, true); + } + + bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; + + SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; + if(m_HasShaders) + glBindBuffer(GL_ARRAY_BUFFER, BufferObject.m_BufferObjectID); + + if(!m_HasShaders) + { + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + } + + if(m_HasShaders) + { + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, false, BufferContainer.m_ContainerInfo.m_Stride, BufferContainer.m_ContainerInfo.m_Attributes[0].m_pOffset); + if(IsTextured) + { + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, false, BufferContainer.m_ContainerInfo.m_Stride, BufferContainer.m_ContainerInfo.m_Attributes[1].m_pOffset); + } + + for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) + { + size_t RealDrawCount = (pCommand->m_pDrawCount[i] / 6) * 4; + GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pCommand->m_pIndicesOffsets[i])) / (6 * sizeof(unsigned int))) * 4); + glDrawArrays(GL_QUADS, RealOffset, RealDrawCount); + } + } + else + { + glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); + glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); + if(IsTextured) + glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); + + size_t VertexCount = 0; + for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) + { + size_t RealDrawCount = (pCommand->m_pDrawCount[i] / 6) * 4; + GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pCommand->m_pIndicesOffsets[i])) / (6 * sizeof(unsigned int))) * 4); + size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); + size_t CurBufferOffset = RealOffset * SingleVertSize; + + for(size_t n = 0; n < RealDrawCount; ++n) + { + ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); + vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); + GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; + mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); + mem_copy(&Vertex.m_Color, &pCommand->m_Color, sizeof(vec4)); + if(IsTextured) + { + vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); + mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); + } + + if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) + { + glDrawArrays(GL_QUADS, 0, VertexCount); + VertexCount = 0; + } + } + } + if(VertexCount > 0) + glDrawArrays(GL_QUADS, 0, VertexCount); + } + + if(!m_HasShaders) + { + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + if(IsTextured) + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + else + { + glDisableVertexAttribArray(0); + if(IsTextured) + glDisableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); + } +} + +#ifdef BACKEND_GL_MODERN_API +#undef BACKEND_GL_MODERN_API +#endif + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl.h ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl.h --- ddnet-15.3.2/src/engine/client/backend/opengl/backend_opengl.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/backend_opengl.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,219 @@ +// This file can be included several times. +#if(!defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H)) || \ + (defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H_AS_ES)) + +#if !defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H) +#define ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H +#endif + +#if defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H_AS_ES) +#define ENGINE_CLIENT_BACKEND_OPENGL_BACKEND_OPENGL_H_AS_ES +#endif + +#include + +class CGLSLProgram; +class CGLSLTWProgram; +class CGLSLPrimitiveProgram; +class CGLSLQuadProgram; +class CGLSLTileProgram; +class CGLSLTextProgram; +class CGLSLPrimitiveExProgram; +class CGLSLSpriteMultipleProgram; + +#if defined(BACKEND_AS_OPENGL_ES) && defined(CONF_BACKEND_OPENGL_ES3) +#define BACKEND_GL_MODERN_API 1 +#endif + +// takes care of opengl related rendering +class CCommandProcessorFragment_OpenGL : public CCommandProcessorFragment_OpenGLBase +{ +protected: + struct CTexture + { + CTexture() : + m_Tex(0), m_Tex2DArray(0), m_Sampler(0), m_Sampler2DArray(0), m_LastWrapMode(CCommandBuffer::WRAP_REPEAT), m_MemSize(0), m_Width(0), m_Height(0), m_RescaleCount(0), m_ResizeWidth(0), m_ResizeHeight(0) + { + } + + TWGLuint m_Tex; + TWGLuint m_Tex2DArray; //or 3D texture as fallback + TWGLuint m_Sampler; + TWGLuint m_Sampler2DArray; //or 3D texture as fallback + int m_LastWrapMode; + + int m_MemSize; + + int m_Width; + int m_Height; + int m_RescaleCount; + float m_ResizeWidth; + float m_ResizeHeight; + }; + std::vector m_Textures; + std::atomic *m_pTextureMemoryUsage; + + TWGLint m_MaxTexSize; + + bool m_Has2DArrayTextures; + bool m_Has2DArrayTexturesAsExtension; + TWGLenum m_2DArrayTarget; + bool m_Has3DTextures; + bool m_HasMipMaps; + bool m_HasNPOTTextures; + + bool m_HasShaders; + int m_LastBlendMode; //avoid all possible opengl state changes + bool m_LastClipEnable; + + int m_OpenGLTextureLodBIAS; + + bool m_IsOpenGLES; + +protected: + bool IsTexturedState(const CCommandBuffer::SState &State); + static bool Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, int ImageColorChannelCount, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight); + + bool InitOpenGL(const SCommand_Init *pCommand); + + void SetState(const CCommandBuffer::SState &State, bool Use2DArrayTexture = false); + virtual bool IsNewApi() { return false; } + void DestroyTexture(int Slot); + + static int TexFormatToOpenGLFormat(int TexFormat); + static int TexFormatToImageColorChannelCount(int TexFormat); + static void *Resize(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData); + + virtual bool Cmd_Init(const SCommand_Init *pCommand); + virtual void Cmd_Shutdown(const SCommand_Shutdown *pCommand) {} + virtual void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand); + virtual void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand); + virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand); + virtual void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand); + virtual void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand); + virtual void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) {} + virtual void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand); + + virtual void Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand); + virtual void Cmd_Finish(const CCommandBuffer::SCommand_Finish *pCommand); + + virtual void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) {} + virtual void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) {} + virtual void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) {} + virtual void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) {} + virtual void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) {} + + virtual void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) {} + virtual void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) {} + virtual void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) {} + virtual void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) {} + + virtual void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) {} + virtual void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) {} + virtual void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) {} + virtual void Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) {} + 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_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) {} + virtual void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) {} + +public: + CCommandProcessorFragment_OpenGL(); + virtual ~CCommandProcessorFragment_OpenGL() = default; + + virtual bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand); +}; + +class CCommandProcessorFragment_OpenGL2 : public CCommandProcessorFragment_OpenGL +{ + struct SBufferContainer + { + SBufferContainer() {} + SBufferContainerInfo m_ContainerInfo; + }; + std::vector m_BufferContainers; + + GL_SVertexTex3D m_aStreamVertices[1024 * 4]; + + struct SBufferObject + { + SBufferObject(TWGLuint BufferObjectID) : + m_BufferObjectID(BufferObjectID) + { + m_pData = NULL; + m_DataSize = 0; + } + TWGLuint m_BufferObjectID; + void *m_pData; + size_t m_DataSize; + }; + + std::vector m_BufferObjectIndices; + +#ifndef BACKEND_GL_MODERN_API + bool DoAnalyzeStep(size_t StepN, size_t CheckCount, size_t VerticesCount, uint8_t aFakeTexture[], size_t SingleImageSize); + bool IsTileMapAnalysisSucceeded(); + + void RenderBorderTileEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int DrawNum, const float *pOffset, const float *pDir, int JumpIndex); + void RenderBorderTileLineEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int IndexDrawNum, unsigned int DrawNum, const float *pOffset, const float *pDir); +#endif + + void UseProgram(CGLSLTWProgram *pProgram); + +protected: + void SetState(const CCommandBuffer::SState &State, CGLSLTWProgram *pProgram, bool Use2DArrayTextures = false); + +#ifndef BACKEND_GL_MODERN_API + bool Cmd_Init(const SCommand_Init *pCommand) override; + + void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) override; + + void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) override; + void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) override; + void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) override; + void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) override; + void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) override; + + void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) override; + void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) override; + void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) override; + void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) override; + + void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) override; + void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) override; + void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) override; +#endif + + CGLSLTileProgram *m_pTileProgram; + CGLSLTileProgram *m_pTileProgramTextured; + CGLSLPrimitiveProgram *m_pPrimitive3DProgram; + CGLSLPrimitiveProgram *m_pPrimitive3DProgramTextured; + + bool m_UseMultipleTextureUnits; + + TWGLint m_MaxTextureUnits; + + struct STextureBound + { + int m_TextureSlot; + bool m_Is2DArray; + }; + std::vector m_TextureSlotBoundToUnit; //the texture index generated by loadtextureraw is stored in an index calculated by max texture units + + bool IsAndUpdateTextureSlotBound(int IDX, int Slot, bool Is2DArray = false); + +public: + CCommandProcessorFragment_OpenGL2() : + CCommandProcessorFragment_OpenGL(), m_UseMultipleTextureUnits(false) {} +}; + +class CCommandProcessorFragment_OpenGL3 : public CCommandProcessorFragment_OpenGL2 +{ +}; + +#if defined(BACKEND_AS_OPENGL_ES) && defined(CONF_BACKEND_OPENGL_ES3) +#undef BACKEND_GL_MODERN_API +#endif + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl.cpp ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl.cpp --- ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,159 @@ +#include "opengl_sl.h" +#include +#include +#include +#include +#include + +#include + +#include + +#ifndef BACKEND_AS_OPENGL_ES +#include +#else +#include +#endif + +bool CGLSL::LoadShader(CGLSLCompiler *pCompiler, IStorage *pStorage, const char *pFile, int Type) +{ + if(m_IsLoaded) + return true; + IOHANDLE f = pStorage->OpenFile(pFile, IOFLAG_READ, IStorage::TYPE_ALL); + + std::vector Lines; + if(f) + { + EBackendType BackendType = pCompiler->m_IsOpenGLES ? BACKEND_TYPE_OPENGL_ES : BACKEND_TYPE_OPENGL; + bool IsNewOpenGL = (BackendType == BACKEND_TYPE_OPENGL ? (pCompiler->m_OpenGLVersionMajor >= 4 || (pCompiler->m_OpenGLVersionMajor == 3 && pCompiler->m_OpenGLVersionMinor == 3)) : pCompiler->m_OpenGLVersionMajor >= 3); + std::string GLShaderStringPostfix = std::string(" core\r\n"); + if(BackendType == BACKEND_TYPE_OPENGL_ES) + GLShaderStringPostfix = std::string(" es\r\n"); + //add compiler specific values + if(IsNewOpenGL) + Lines.push_back(std::string("#version ") + std::string(std::to_string(pCompiler->m_OpenGLVersionMajor)) + std::string(std::to_string(pCompiler->m_OpenGLVersionMinor)) + std::string(std::to_string(pCompiler->m_OpenGLVersionPatch)) + GLShaderStringPostfix); + else + { + if(pCompiler->m_OpenGLVersionMajor == 3) + { + if(pCompiler->m_OpenGLVersionMinor == 0) + Lines.push_back(std::string("#version 130 \r\n")); + if(pCompiler->m_OpenGLVersionMinor == 1) + Lines.push_back(std::string("#version 140 \r\n")); + if(pCompiler->m_OpenGLVersionMinor == 2) + Lines.push_back(std::string("#version 150 \r\n")); + } + else if(pCompiler->m_OpenGLVersionMajor == 2) + { + if(pCompiler->m_OpenGLVersionMinor == 0) + Lines.push_back(std::string("#version 110 \r\n")); + if(pCompiler->m_OpenGLVersionMinor == 1) + Lines.push_back(std::string("#version 120 \r\n")); + } + } + + if(BackendType == BACKEND_TYPE_OPENGL_ES) + { + if(Type == GL_FRAGMENT_SHADER) + { + Lines.push_back("precision highp float; \r\n"); + Lines.push_back("precision highp sampler2D; \r\n"); + Lines.push_back("precision highp sampler3D; \r\n"); + Lines.push_back("precision highp samplerCube; \r\n"); + Lines.push_back("precision highp samplerCubeShadow; \r\n"); + Lines.push_back("precision highp sampler2DShadow; \r\n"); + Lines.push_back("precision highp sampler2DArray; \r\n"); + Lines.push_back("precision highp sampler2DArrayShadow; \r\n"); + } + } + + for(CGLSLCompiler::SGLSLCompilerDefine &Define : pCompiler->m_Defines) + { + Lines.push_back(std::string("#define ") + Define.m_DefineName + std::string(" ") + Define.m_DefineValue + std::string("\r\n")); + } + + if(Type == GL_FRAGMENT_SHADER && !IsNewOpenGL && pCompiler->m_OpenGLVersionMajor <= 3 && pCompiler->m_HasTextureArray) + { + Lines.push_back(std::string("#extension GL_EXT_texture_array : enable\r\n")); + } + + CLineReader LineReader; + LineReader.Init(f); + char *ReadLine = NULL; + while((ReadLine = LineReader.Get())) + { + std::string Line; + pCompiler->ParseLine(Line, ReadLine, Type == GL_FRAGMENT_SHADER ? GLSL_SHADER_COMPILER_TYPE_FRAGMENT : GLSL_SHADER_COMPILER_TYPE_VERTEX); + Line.append("\r\n"); + Lines.push_back(Line); + } + io_close(f); + + const char **ShaderCode = new const char *[Lines.size()]; + + for(size_t i = 0; i < Lines.size(); ++i) + { + ShaderCode[i] = Lines[i].c_str(); + } + + TWGLuint shader = glCreateShader(Type); + + glShaderSource(shader, Lines.size(), ShaderCode, NULL); + glCompileShader(shader); + + delete[] ShaderCode; + + int CompilationStatus; + glGetShaderiv(shader, GL_COMPILE_STATUS, &CompilationStatus); + + if(CompilationStatus == GL_FALSE) + { + char buff[3000]; + + TWGLint maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + glGetShaderInfoLog(shader, maxLength, &maxLength, buff); + + dbg_msg("glsl", "%s: %s", pFile, buff); + glDeleteShader(shader); + return false; + } + m_Type = Type; + m_IsLoaded = true; + + m_ShaderID = shader; + + return true; + } + else + return false; +} + +void CGLSL::DeleteShader() +{ + if(!IsLoaded()) + return; + m_IsLoaded = false; + glDeleteShader(m_ShaderID); +} + +bool CGLSL::IsLoaded() +{ + return m_IsLoaded; +} + +TWGLuint CGLSL::GetShaderID() +{ + return m_ShaderID; +} + +CGLSL::CGLSL() +{ + m_IsLoaded = false; +} + +CGLSL::~CGLSL() +{ + DeleteShader(); +} diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl.h ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl.h --- ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,40 @@ +// This file can be included several times. +#if(!defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H)) || \ + (defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H_AS_ES)) + +#if !defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H) +#define ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H +#endif + +#if defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H_AS_ES) +#define ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_H_AS_ES +#endif + +#include + +#include + +#include +#include + +class CGLSLCompiler; + +class CGLSL +{ +public: + bool LoadShader(CGLSLCompiler *pCompiler, class IStorage *pStorage, const char *pFile, int Type); + void DeleteShader(); + + bool IsLoaded(); + TWGLuint GetShaderID(); + + CGLSL(); + virtual ~CGLSL(); + +private: + TWGLuint m_ShaderID; + int m_Type; + bool m_IsLoaded; +}; + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl_program.cpp ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl_program.cpp --- ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl_program.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl_program.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,142 @@ +#include "opengl_sl_program.h" +#include "opengl_sl.h" +#include + +#ifndef BACKEND_AS_OPENGL_ES +#include +#else +#include +#endif + +void CGLSLProgram::CreateProgram() +{ + m_ProgramID = glCreateProgram(); +} + +void CGLSLProgram::DeleteProgram() +{ + if(!m_IsLinked) + return; + m_IsLinked = false; + glDeleteProgram(m_ProgramID); +} + +bool CGLSLProgram::AddShader(CGLSL *pShader) +{ + if(pShader->IsLoaded()) + { + glAttachShader(m_ProgramID, pShader->GetShaderID()); + return true; + } + return false; +} + +void CGLSLProgram::DetachShader(CGLSL *pShader) +{ + if(pShader->IsLoaded()) + { + DetachShaderByID(pShader->GetShaderID()); + } +} + +void CGLSLProgram::DetachShaderByID(TWGLuint ShaderID) +{ + glDetachShader(m_ProgramID, ShaderID); +} + +void CGLSLProgram::LinkProgram() +{ + glLinkProgram(m_ProgramID); + int LinkStatus; + glGetProgramiv(m_ProgramID, GL_LINK_STATUS, &LinkStatus); + m_IsLinked = LinkStatus == GL_TRUE; + if(!m_IsLinked) + { + char sInfoLog[1024]; + char sFinalMessage[1536]; + int iLogLength; + glGetProgramInfoLog(m_ProgramID, 1024, &iLogLength, sInfoLog); + str_format(sFinalMessage, 1536, "Error! Shader program wasn't linked! The linker returned:\n\n%s", sInfoLog); + dbg_msg("glslprogram", "%s", sFinalMessage); + } + + //detach all shaders attached to this program + DetachAllShaders(); +} + +void CGLSLProgram::DetachAllShaders() +{ + TWGLuint aShaders[100]; + GLsizei ReturnedCount = 0; + while(1) + { + glGetAttachedShaders(m_ProgramID, 100, &ReturnedCount, aShaders); + + if(ReturnedCount > 0) + { + for(GLsizei i = 0; i < ReturnedCount; ++i) + { + DetachShaderByID(aShaders[i]); + } + } + + if(ReturnedCount < 100) + break; + } +} + +void CGLSLProgram::SetUniformVec4(int Loc, int Count, const float *pValues) +{ + glUniform4fv(Loc, Count, pValues); +} + +void CGLSLProgram::SetUniformVec2(int Loc, int Count, const float *pValues) +{ + glUniform2fv(Loc, Count, pValues); +} + +void CGLSLProgram::SetUniform(int Loc, int Count, const float *pValues) +{ + glUniform1fv(Loc, Count, pValues); +} + +void CGLSLProgram::SetUniform(int Loc, const int Value) +{ + glUniform1i(Loc, Value); +} + +void CGLSLProgram::SetUniform(int Loc, const float Value) +{ + glUniform1f(Loc, Value); +} + +void CGLSLProgram::SetUniform(int Loc, const bool Value) +{ + glUniform1i(Loc, (int)Value); +} + +int CGLSLProgram::GetUniformLoc(const char *Name) +{ + return glGetUniformLocation(m_ProgramID, Name); +} + +void CGLSLProgram::UseProgram() +{ + if(m_IsLinked) + glUseProgram(m_ProgramID); +} + +TWGLuint CGLSLProgram::GetProgramID() +{ + return m_ProgramID; +} + +CGLSLProgram::CGLSLProgram() +{ + m_IsLinked = false; +} + +CGLSLProgram::~CGLSLProgram() +{ + DeleteProgram(); +} diff -Nru ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl_program.h ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl_program.h --- ddnet-15.3.2/src/engine/client/backend/opengl/opengl_sl_program.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengl/opengl_sl_program.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,161 @@ +// This file can be included several times. +#if(!defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H)) || \ + (defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H_AS_ES)) + +#if !defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H) +#define ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H +#endif + +#if defined(BACKEND_AS_OPENGL_ES) && !defined(ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H_AS_ES) +#define ENGINE_CLIENT_BACKEND_OPENGL_OPENGL_SL_PROGRAM_H_AS_ES +#endif + +#include + +#include + +class CGLSL; + +class CGLSLProgram +{ +public: + void CreateProgram(); + void DeleteProgram(); + + bool AddShader(CGLSL *pShader); + + void LinkProgram(); + void UseProgram(); + TWGLuint GetProgramID(); + + void DetachShader(CGLSL *pShader); + void DetachShaderByID(TWGLuint ShaderID); + void DetachAllShaders(); + + //Support various types + void SetUniformVec2(int Loc, int Count, const float *pValue); + void SetUniformVec4(int Loc, int Count, const float *pValue); + void SetUniform(int Loc, const int Value); + void SetUniform(int Loc, const bool Value); + void SetUniform(int Loc, const float Value); + void SetUniform(int Loc, int Count, const float *pValues); + + //for performance reason we do not use SetUniform with using strings... save the Locations of the variables instead + int GetUniformLoc(const char *Name); + + CGLSLProgram(); + virtual ~CGLSLProgram(); + +protected: + TWGLuint m_ProgramID; + bool m_IsLinked; +}; + +class CGLSLTWProgram : public CGLSLProgram +{ +public: + CGLSLTWProgram() : + m_LocPos(-1), m_LocTextureSampler(-1), m_LastTextureSampler(-1), m_LastIsTextured(-1) + { + m_LastScreen[0] = m_LastScreen[1] = m_LastScreen[2] = m_LastScreen[3] = -1.f; + } + + int m_LocPos; + int m_LocTextureSampler; + + int m_LastTextureSampler; + int m_LastIsTextured; + float m_LastScreen[4]; +}; + +class CGLSLTextProgram : public CGLSLTWProgram +{ +public: + CGLSLTextProgram() : + CGLSLTWProgram() + { + m_LastColor[0] = m_LastColor[1] = m_LastColor[2] = m_LastColor[3] = -1.f; + m_LastOutlineColor[0] = m_LastOutlineColor[1] = m_LastOutlineColor[2] = m_LastOutlineColor[3] = -1.f; + m_LastTextSampler = m_LastTextOutlineSampler = -1; + m_LastTextureSize = -1; + } + + int m_LocColor; + int m_LocOutlineColor; + int m_LocTextSampler; + int m_LocTextOutlineSampler; + int m_LocTextureSize; + + float m_LastColor[4]; + float m_LastOutlineColor[4]; + int m_LastTextSampler; + int m_LastTextOutlineSampler; + int m_LastTextureSize; +}; + +class CGLSLPrimitiveProgram : public CGLSLTWProgram +{ +public: +}; + +class CGLSLPrimitiveExProgram : public CGLSLTWProgram +{ +public: + CGLSLPrimitiveExProgram() : + CGLSLTWProgram() + { + m_LastRotation = 0.f; + m_LastCenter[0] = m_LastCenter[1] = 0.f; + m_LastVertciesColor[0] = m_LastVertciesColor[1] = m_LastVertciesColor[2] = m_LastVertciesColor[3] = -1.f; + } + + int m_LocRotation; + int m_LocCenter; + int m_LocVertciesColor; + + float m_LastRotation; + float m_LastCenter[2]; + float m_LastVertciesColor[4]; +}; + +class CGLSLSpriteMultipleProgram : public CGLSLTWProgram +{ +public: + CGLSLSpriteMultipleProgram() : + CGLSLTWProgram() + { + m_LastCenter[0] = m_LastCenter[1] = 0.f; + m_LastVertciesColor[0] = m_LastVertciesColor[1] = m_LastVertciesColor[2] = m_LastVertciesColor[3] = -1.f; + } + + int m_LocRSP; + int m_LocCenter; + int m_LocVertciesColor; + + float m_LastCenter[2]; + float m_LastVertciesColor[4]; +}; + +class CGLSLQuadProgram : public CGLSLTWProgram +{ +public: + int m_LocColors; + int m_LocOffsets; + int m_LocRotations; + int m_LocQuadOffset; +}; + +class CGLSLTileProgram : public CGLSLTWProgram +{ +public: + CGLSLTileProgram() : + m_LocColor(-1), m_LocOffset(-1), m_LocDir(-1), m_LocNum(-1), m_LocJumpIndex(-1) {} + + int m_LocColor; + int m_LocOffset; + int m_LocDir; + int m_LocNum; + int m_LocJumpIndex; +}; + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles3.cpp ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles3.cpp --- ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles3.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles3.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,17 @@ +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) + +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles3.h ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles3.h --- ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles3.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles3.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,23 @@ +#ifndef ENGINE_CLIENT_BACKEND_OPENGLES_BACKEND_OPENGLES3_H +#define ENGINE_CLIENT_BACKEND_OPENGLES_BACKEND_OPENGLES3_H + +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) +#include + +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles.cpp ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles.cpp --- ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,17 @@ +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) + +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles.h ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles.h --- ddnet-15.3.2/src/engine/client/backend/opengles/backend_opengles.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/backend_opengles.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,23 @@ +#ifndef ENGINE_CLIENT_BACKEND_OPENGLES_BACKEND_OPENGLES_H +#define ENGINE_CLIENT_BACKEND_OPENGLES_BACKEND_OPENGLES_H + +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) +#include + +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/gles_class_defines.h ddnet-15.5.4/src/engine/client/backend/opengles/gles_class_defines.h --- ddnet-15.3.2/src/engine/client/backend/opengles/gles_class_defines.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/gles_class_defines.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,36 @@ +#ifndef ENGINE_CLIENT_BACKEND_OPENGLES_GLES_CLASS_DEFINES_H +#define ENGINE_CLIENT_BACKEND_OPENGLES_GLES_CLASS_DEFINES_H +#undef ENGINE_CLIENT_BACKEND_OPENGLES_GLES_CLASS_DEFINES_H +#endif + +#ifdef GLES_CLASS_DEFINES_DO_DEFINE +#define CCommandProcessorFragment_OpenGL3_3 CCommandProcessorFragment_OpenGLES3 +#define CCommandProcessorFragment_OpenGL3 CCommandProcessorFragment_OpenGLES3Wrapper +#define CCommandProcessorFragment_OpenGL2 CCommandProcessorFragment_OpenGLES2 +#define CCommandProcessorFragment_OpenGL CCommandProcessorFragment_OpenGLES + +#define CGLSL CGLSL_ES +#define CGLSLProgram CGLSL_ESProgram +#define CGLSLTWProgram CGLSL_ESTWProgram +#define CGLSLTextProgram CGLSL_ESTextProgram +#define CGLSLPrimitiveProgram CGLSL_ESPrimitiveProgram +#define CGLSLPrimitiveExProgram CGLSL_ESPrimitiveExProgram +#define CGLSLSpriteMultipleProgram CGLSL_ESSpriteMultipleProgram +#define CGLSLQuadProgram CGLSL_ESQuadProgram +#define CGLSLTileProgram CGLSL_ESTileProgram +#else +#undef CCommandProcessorFragment_OpenGL3_3 +#undef CCommandProcessorFragment_OpenGL3 +#undef CCommandProcessorFragment_OpenGL2 +#undef CCommandProcessorFragment_OpenGL + +#undef CGLSL +#undef CGLSLProgram +#undef CGLSLTWProgram +#undef CGLSLTextProgram +#undef CGLSLPrimitiveProgram +#undef CGLSLPrimitiveExProgram +#undef CGLSLSpriteMultipleProgram +#undef CGLSLQuadProgram +#undef CGLSLTileProgram +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/opengles_sl.cpp ddnet-15.5.4/src/engine/client/backend/opengles/opengles_sl.cpp --- ddnet-15.3.2/src/engine/client/backend/opengles/opengles_sl.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/opengles_sl.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,16 @@ +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend/opengles/opengles_sl_program.cpp ddnet-15.5.4/src/engine/client/backend/opengles/opengles_sl_program.cpp --- ddnet-15.3.2/src/engine/client/backend/opengles/opengles_sl_program.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend/opengles/opengles_sl_program.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,16 @@ +#include + +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) +#define GLES_CLASS_DEFINES_DO_DEFINE +#include "gles_class_defines.h" +#undef GLES_CLASS_DEFINES_DO_DEFINE + +#define BACKEND_AS_OPENGL_ES 1 + +#include + +#undef BACKEND_AS_OPENGL_ES + +#include "gles_class_defines.h" + +#endif diff -Nru ddnet-15.3.2/src/engine/client/backend_sdl.cpp ddnet-15.5.4/src/engine/client/backend_sdl.cpp --- ddnet-15.3.2/src/engine/client/backend_sdl.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend_sdl.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -6,17 +6,22 @@ #define WINVER 0x0501 #endif +#ifndef CONF_BACKEND_OPENGL_ES #include +#endif + #include #include "SDL.h" -#include "SDL_opengl.h" + #include "SDL_syswm.h" #include #include #include #include +#include "SDL_hints.h" + #if defined(SDL_VIDEO_DRIVER_X11) #include #include @@ -31,10 +36,16 @@ #endif #include "backend_sdl.h" -#include "graphics_threaded.h" -#include "opengl_sl.h" -#include "opengl_sl_program.h" +#if !defined(CONF_BACKEND_OPENGL_ES) +#include "backend/opengl/backend_opengl3.h" +#endif + +#if defined(CONF_BACKEND_OPENGL_ES3) || defined(CONF_BACKEND_OPENGL_ES) +#include "backend/opengles/backend_opengles3.h" +#endif + +#include "graphics_threaded.h" #include @@ -74,7 +85,7 @@ pThis->m_Activity.Wait(); if(pThis->m_pBuffer) { -#ifdef CONF_PLATFORM_MACOSX +#ifdef CONF_PLATFORM_MACOS CAutoreleasePool AutoreleasePool; #endif pThis->m_pProcessor->RunBuffer(pThis->m_pBuffer); @@ -131,33 +142,6 @@ m_BufferDone.Wait(); } -static bool Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, int ImageColorChannelCount, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight) -{ - Target3DImageWidth = ImageWidth / SplitCountWidth; - Target3DImageHeight = ImageHeight / SplitCountHeight; - - size_t FullImageWidth = (size_t)ImageWidth * ImageColorChannelCount; - - for(int Y = 0; Y < SplitCountHeight; ++Y) - { - for(int X = 0; X < SplitCountWidth; ++X) - { - for(int Y3D = 0; Y3D < Target3DImageHeight; ++Y3D) - { - int DepthIndex = X + Y * SplitCountWidth; - - size_t TargetImageFullWidth = (size_t)Target3DImageWidth * ImageColorChannelCount; - size_t TargetImageFullSize = (size_t)TargetImageFullWidth * Target3DImageHeight; - ptrdiff_t ImageOffset = (ptrdiff_t)(((size_t)Y * FullImageWidth * (size_t)Target3DImageHeight) + ((size_t)Y3D * FullImageWidth) + ((size_t)X * TargetImageFullWidth)); - ptrdiff_t TargetImageOffset = (ptrdiff_t)(TargetImageFullSize * (size_t)DepthIndex + ((size_t)Y3D * TargetImageFullWidth)); - mem_copy(((uint8_t *)pTarget3DImageData) + TargetImageOffset, ((uint8_t *)pImageBuffer) + (ptrdiff_t)(ImageOffset), TargetImageFullWidth); - } - } - } - - return true; -} - // ------------ CCommandProcessorFragment_General void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand) @@ -177,4146 +161,527 @@ return true; } -// ------------ CCommandProcessorFragment_OpenGL - -int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat) +// ------------ CCommandProcessorFragment_SDL +void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand) { - if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) - return GL_RGB; - if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) - return GL_ALPHA; - if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) - return GL_RGBA; - return GL_RGBA; + m_GLContext = pCommand->m_GLContext; + m_pWindow = pCommand->m_pWindow; + SDL_GL_MakeCurrent(m_pWindow, m_GLContext); } -int CCommandProcessorFragment_OpenGL::TexFormatToImageColorChannelCount(int TexFormat) +void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand) { - if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) - return 3; - if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) - return 1; - if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) - return 4; - return 4; + SDL_GL_MakeCurrent(NULL, NULL); } -void *CCommandProcessorFragment_OpenGL::Resize(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData) +void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand) { - int Bpp = TexFormatToImageColorChannelCount(Format); - - return ResizeImage((const uint8_t *)pData, Width, Height, NewWidth, NewHeight, Bpp); + SDL_GL_SwapWindow(m_pWindow); } -bool CCommandProcessorFragment_OpenGL::IsTexturedState(const CCommandBuffer::SState &State) +void CCommandProcessorFragment_SDL::Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand) { - return State.m_Texture >= 0 && State.m_Texture < (int)m_Textures.size(); + *pCommand->m_pRetOk = SDL_GL_SetSwapInterval(pCommand->m_VSync) == 0; } -void CCommandProcessorFragment_OpenGL::SetState(const CCommandBuffer::SState &State, bool Use2DArrayTextures) +void CCommandProcessorFragment_SDL::Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand) { - // blend - switch(State.m_BlendMode) - { - case CCommandBuffer::BLEND_NONE: - glDisable(GL_BLEND); - break; - case CCommandBuffer::BLEND_ALPHA: - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case CCommandBuffer::BLEND_ADDITIVE: - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - default: - dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode); - }; - m_LastBlendMode = State.m_BlendMode; - - // clip - if(State.m_ClipEnable) - { - glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH); - glEnable(GL_SCISSOR_TEST); - m_LastClipEnable = true; - } - else if(m_LastClipEnable) - { - // Don't disable it always - glDisable(GL_SCISSOR_TEST); - m_LastClipEnable = false; - } - - glDisable(GL_TEXTURE_2D); - if(!m_HasShaders) - { - if(m_Has3DTextures) - glDisable(GL_TEXTURE_3D); - if(m_Has2DArrayTextures) - { - glDisable(m_2DArrayTarget); - } - } - - if(m_HasShaders && IsNewApi()) - { - glBindSampler(0, 0); - } + SDL_DisplayMode mode; + int maxModes = SDL_GetNumDisplayModes(pCommand->m_Screen), + numModes = 0; - // texture - if(IsTexturedState(State)) + for(int i = -1; i < maxModes; i++) { - if(!Use2DArrayTextures) + if(i == -1) { - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); - - if(m_Textures[State.m_Texture].m_LastWrapMode != State.m_WrapMode) + if(SDL_GetDesktopDisplayMode(pCommand->m_Screen, &mode) < 0) { - switch(State.m_WrapMode) - { - case CCommandBuffer::WRAP_REPEAT: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - break; - case CCommandBuffer::WRAP_CLAMP: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - break; - default: - dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode); - }; - m_Textures[State.m_Texture].m_LastWrapMode = State.m_WrapMode; + dbg_msg("gfx", "unable to get display mode: %s", SDL_GetError()); + continue; } } else { - if(m_Has2DArrayTextures) - { - if(!m_HasShaders) - glEnable(m_2DArrayTarget); - glBindTexture(m_2DArrayTarget, m_Textures[State.m_Texture].m_Tex2DArray); - } - else if(m_Has3DTextures) + if(SDL_GetDisplayMode(pCommand->m_Screen, i, &mode) < 0) { - if(!m_HasShaders) - glEnable(GL_TEXTURE_3D); - glBindTexture(GL_TEXTURE_3D, m_Textures[State.m_Texture].m_Tex2DArray); + dbg_msg("gfx", "unable to get display mode: %s", SDL_GetError()); + continue; } - else + } + + bool AlreadyFound = false; + for(int j = 0; j < numModes; j++) + { + if(pCommand->m_pModes[j].m_CanvasWidth == mode.w && pCommand->m_pModes[j].m_CanvasHeight == mode.h) { - dbg_msg("opengl", "Error: this call should not happen."); + AlreadyFound = true; + break; } } - } - - // screen mapping - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(State.m_ScreenTL.x, State.m_ScreenBR.x, State.m_ScreenBR.y, State.m_ScreenTL.y, -10.0f, 10.f); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Init(const SCommand_Init *pCommand) -{ - m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage; - m_pTextureMemoryUsage->store(0, std::memory_order_relaxed); - m_MaxTexSize = -1; + if(AlreadyFound) + continue; - m_OpenGLTextureLodBIAS = 0; + if(mode.w > pCommand->m_MaxWindowWidth || mode.h > pCommand->m_MaxWindowHeight) + continue; - m_Has2DArrayTextures = pCommand->m_pCapabilities->m_2DArrayTextures; - if(pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension) - { - m_Has2DArrayTexturesAsExtension = true; - m_2DArrayTarget = GL_TEXTURE_2D_ARRAY_EXT; - } - else - { - m_Has2DArrayTexturesAsExtension = false; - m_2DArrayTarget = GL_TEXTURE_2D_ARRAY; + pCommand->m_pModes[numModes].m_CanvasWidth = mode.w * pCommand->m_HiDPIScale; + pCommand->m_pModes[numModes].m_CanvasHeight = mode.h * pCommand->m_HiDPIScale; + pCommand->m_pModes[numModes].m_WindowWidth = mode.w; + pCommand->m_pModes[numModes].m_WindowHeight = mode.h; + pCommand->m_pModes[numModes].m_Red = 8; + pCommand->m_pModes[numModes].m_Green = 8; + pCommand->m_pModes[numModes].m_Blue = 8; + numModes++; } - - m_Has3DTextures = pCommand->m_pCapabilities->m_3DTextures; - m_HasMipMaps = pCommand->m_pCapabilities->m_MipMapping; - m_HasNPOTTextures = pCommand->m_pCapabilities->m_NPOTTextures; - - m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; - m_LastClipEnable = false; + *pCommand->m_pNumModes = numModes; } -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) +CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL() { - glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); - - void *pTexData = pCommand->m_pData; - int Width = pCommand->m_Width; - int Height = pCommand->m_Height; - int X = pCommand->m_X; - int Y = pCommand->m_Y; - - if(!m_HasNPOTTextures) - { - float ResizeW = m_Textures[pCommand->m_Slot].m_ResizeWidth; - float ResizeH = m_Textures[pCommand->m_Slot].m_ResizeHeight; - if(ResizeW > 0 && ResizeH > 0) - { - int ResizedW = (int)(Width * ResizeW); - int ResizedH = (int)(Height * ResizeH); - - void *pTmpData = Resize(Width, Height, ResizedW, ResizedH, pCommand->m_Format, static_cast(pTexData)); - free(pTexData); - pTexData = pTmpData; - - Width = ResizedW; - Height = ResizedH; - } - } - - if(m_Textures[pCommand->m_Slot].m_RescaleCount > 0) - { - int OldWidth = Width; - int OldHeight = Height; - for(int i = 0; i < m_Textures[pCommand->m_Slot].m_RescaleCount; ++i) - { - Width >>= 1; - Height >>= 1; - - X /= 2; - Y /= 2; - } - - void *pTmpData = Resize(OldWidth, OldHeight, Width, Height, pCommand->m_Format, static_cast(pTexData)); - free(pTexData); - pTexData = pTmpData; - } - - glTexSubImage2D(GL_TEXTURE_2D, 0, X, Y, Width, Height, - TexFormatToOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pTexData); - free(pTexData); } -void CCommandProcessorFragment_OpenGL::DestroyTexture(int Slot) +bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) { - m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) - m_Textures[Slot].m_MemSize, std::memory_order_relaxed); - - if(m_Textures[Slot].m_Tex != 0) - { - glDeleteTextures(1, &m_Textures[Slot].m_Tex); - } - - if(m_Textures[Slot].m_Tex2DArray != 0) - { - glDeleteTextures(1, &m_Textures[Slot].m_Tex2DArray); - } - - if(IsNewApi()) + switch(pBaseCommand->m_Cmd) { - if(m_Textures[Slot].m_Sampler != 0) - { - glDeleteSamplers(1, &m_Textures[Slot].m_Sampler); - } - if(m_Textures[Slot].m_Sampler2DArray != 0) - { - glDeleteSamplers(1, &m_Textures[Slot].m_Sampler2DArray); - } + case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_VSYNC: Cmd_VSync(static_cast(pBaseCommand)); break; + case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast(pBaseCommand)); break; + case CMD_INIT: Cmd_Init(static_cast(pBaseCommand)); break; + case CMD_SHUTDOWN: Cmd_Shutdown(static_cast(pBaseCommand)); break; + default: return false; } - m_Textures[Slot].m_Tex = 0; - m_Textures[Slot].m_Sampler = 0; - m_Textures[Slot].m_Tex2DArray = 0; - m_Textures[Slot].m_Sampler2DArray = 0; - m_Textures[Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; + return true; } -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) -{ - DestroyTexture(pCommand->m_Slot); -} +// ------------ CCommandProcessor_SDL_OpenGL -void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) +void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer) { - int Width = pCommand->m_Width; - int Height = pCommand->m_Height; - void *pTexData = pCommand->m_pData; - - if(m_MaxTexSize == -1) - { - // fix the alignment to allow even 1byte changes, e.g. for alpha components - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize); - } - - if(pCommand->m_Slot >= (int)m_Textures.size()) - m_Textures.resize(m_Textures.size() * 2); - - m_Textures[pCommand->m_Slot].m_ResizeWidth = -1.f; - m_Textures[pCommand->m_Slot].m_ResizeHeight = -1.f; - - if(!m_HasNPOTTextures) - { - int PowerOfTwoWidth = HighestBit(Width); - int PowerOfTwoHeight = HighestBit(Height); - if(Width != PowerOfTwoWidth || Height != PowerOfTwoHeight) - { - void *pTmpData = Resize(Width, Height, PowerOfTwoWidth, PowerOfTwoHeight, pCommand->m_Format, static_cast(pTexData)); - free(pTexData); - pTexData = pTmpData; - - m_Textures[pCommand->m_Slot].m_ResizeWidth = (float)PowerOfTwoWidth / (float)Width; - m_Textures[pCommand->m_Slot].m_ResizeHeight = (float)PowerOfTwoHeight / (float)Height; - - Width = PowerOfTwoWidth; - Height = PowerOfTwoHeight; - } - } - - int RescaleCount = 0; - if(pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGBA || pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGB || pCommand->m_Format == CCommandBuffer::TEXFORMAT_ALPHA) + for(CCommandBuffer::SCommand *pCommand = pBuffer->Head(); pCommand; pCommand = pCommand->m_pNext) { - int OldWidth = Width; - int OldHeight = Height; - bool NeedsResize = false; - - if(Width > m_MaxTexSize || Height > m_MaxTexSize) - { - do - { - Width >>= 1; - Height >>= 1; - ++RescaleCount; - } while(Width > m_MaxTexSize || Height > m_MaxTexSize); - NeedsResize = true; - } - else if(pCommand->m_Format != CCommandBuffer::TEXFORMAT_ALPHA && (Width > 16 && Height > 16 && (pCommand->m_Flags & CCommandBuffer::TEXFLAG_QUALITY) == 0)) - { - Width >>= 1; - Height >>= 1; - ++RescaleCount; - NeedsResize = true; - } + if(m_pOpenGL->RunCommand(pCommand)) + continue; - if(NeedsResize) - { - void *pTmpData = Resize(OldWidth, OldHeight, Width, Height, pCommand->m_Format, static_cast(pTexData)); - free(pTexData); - pTexData = pTmpData; - } - } - m_Textures[pCommand->m_Slot].m_Width = Width; - m_Textures[pCommand->m_Slot].m_Height = Height; - m_Textures[pCommand->m_Slot].m_RescaleCount = RescaleCount; + if(m_SDL.RunCommand(pCommand)) + continue; - int Oglformat = TexFormatToOpenGLFormat(pCommand->m_Format); - int StoreOglformat = TexFormatToOpenGLFormat(pCommand->m_StoreFormat); + if(m_General.RunCommand(pCommand)) + continue; - if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_COMPRESSED) - { - switch(StoreOglformat) - { - case GL_RGB: StoreOglformat = GL_COMPRESSED_RGB_ARB; break; - case GL_ALPHA: StoreOglformat = GL_COMPRESSED_ALPHA_ARB; break; - case GL_RGBA: StoreOglformat = GL_COMPRESSED_RGBA_ARB; break; - default: StoreOglformat = GL_COMPRESSED_RGBA_ARB; - } + dbg_msg("gfx", "unknown command %d", pCommand->m_Cmd); } +} - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) - { - glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex); - glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); - } +CCommandProcessor_SDL_OpenGL::CCommandProcessor_SDL_OpenGL(EBackendType BackendType, int OpenGLMajor, int OpenGLMinor, int OpenGLPatch) +{ + m_BackendType = BackendType; - if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_NOMIPMAPS || !m_HasMipMaps) - { - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - } - else + if(BackendType == BACKEND_TYPE_OPENGL_ES) { - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) +#if defined(CONF_BACKEND_OPENGL_ES) || defined(CONF_BACKEND_OPENGL_ES3) + if(OpenGLMajor < 3) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); - if(m_OpenGLTextureLodBIAS != 0) - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + m_pOpenGL = new CCommandProcessorFragment_OpenGLES(); } - - int Flag2DArrayTexture = (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE | CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER); - int Flag3DTexture = (CCommandBuffer::TEXFLAG_TO_3D_TEXTURE | CCommandBuffer::TEXFLAG_TO_3D_TEXTURE_SINGLE_LAYER); - if((pCommand->m_Flags & (Flag2DArrayTexture | Flag3DTexture)) != 0) + else { - bool Is3DTexture = (pCommand->m_Flags & Flag3DTexture) != 0; - - glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex2DArray); - - GLenum Target = GL_TEXTURE_3D; - - if(Is3DTexture) - { - Target = GL_TEXTURE_3D; - } - else - { - Target = m_2DArrayTarget; - } - - glBindTexture(Target, m_Textures[pCommand->m_Slot].m_Tex2DArray); - - if(IsNewApi()) - { - glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler2DArray); - glBindSampler(0, m_Textures[pCommand->m_Slot].m_Sampler2DArray); - } - - glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if(Is3DTexture) - { - glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if(IsNewApi()) - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - else - { - glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(Target, GL_GENERATE_MIPMAP, GL_TRUE); - if(IsNewApi()) - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - - glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - if(m_OpenGLTextureLodBIAS != 0) - glTexParameterf(Target, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); - - if(IsNewApi()) - { - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - if(m_OpenGLTextureLodBIAS != 0) - glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); - - glBindSampler(0, 0); - } - - int ImageColorChannels = TexFormatToImageColorChannelCount(pCommand->m_Format); - - uint8_t *p3DImageData = NULL; - - bool IsSingleLayer = (pCommand->m_Flags & (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER | CCommandBuffer::TEXFLAG_TO_3D_TEXTURE_SINGLE_LAYER)) != 0; - - if(!IsSingleLayer) - p3DImageData = (uint8_t *)malloc((size_t)ImageColorChannels * Width * Height); - int Image3DWidth, Image3DHeight; - - int ConvertWidth = Width; - int ConvertHeight = Height; - - if(!IsSingleLayer) - { - if(ConvertWidth == 0 || (ConvertWidth % 16) != 0 || ConvertHeight == 0 || (ConvertHeight % 16) != 0) - { - dbg_msg("gfx", "3D/2D array texture was resized"); - int NewWidth = maximum(HighestBit(ConvertWidth), 16); - int NewHeight = maximum(HighestBit(ConvertHeight), 16); - uint8_t *pNewTexData = (uint8_t *)Resize(ConvertWidth, ConvertHeight, NewWidth, NewHeight, pCommand->m_Format, (const uint8_t *)pTexData); - - ConvertWidth = NewWidth; - ConvertHeight = NewHeight; - - free(pTexData); - pTexData = pNewTexData; - } - } - - if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) - { - if(IsSingleLayer) - { - glTexImage3D(Target, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - else - { - glTexImage3D(Target, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); - } - - /*if(StoreOglformat == GL_R8) - { - //Bind the texture 2D. - GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; - glTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - }*/ - } - - if(!IsSingleLayer) - free(p3DImageData); + m_pOpenGL = new CCommandProcessorFragment_OpenGLES3(); } +#endif } - - // This is the initial value for the wrap modes - m_Textures[pCommand->m_Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; - - // calculate memory usage - m_Textures[pCommand->m_Slot].m_MemSize = Width * Height * pCommand->m_PixelSize; - while(Width > 2 && Height > 2) - { - Width >>= 1; - Height >>= 1; - m_Textures[pCommand->m_Slot].m_MemSize += Width * Height * pCommand->m_PixelSize; - } - m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_Textures[pCommand->m_Slot].m_MemSize, std::memory_order_relaxed); - - free(pTexData); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) -{ - glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) -{ - SetState(pCommand->m_State); - - glVertexPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices); - glTexCoordPointer(2, GL_FLOAT, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices + sizeof(float) * 2); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(CCommandBuffer::SVertex), (char *)pCommand->m_pVertices + sizeof(float) * 4); - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - switch(pCommand->m_PrimType) - { - case CCommandBuffer::PRIMTYPE_QUADS: - glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount * 4); - break; - case CCommandBuffer::PRIMTYPE_LINES: - glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); - break; - case CCommandBuffer::PRIMTYPE_TRIANGLES: - glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); - break; - default: - dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); - }; -} - -void CCommandProcessorFragment_OpenGL::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand) -{ - // fetch image data - GLint aViewport[4] = {0, 0, 0, 0}; - glGetIntegerv(GL_VIEWPORT, aViewport); - - int w = aViewport[2]; - int h = aViewport[3]; - - // we allocate one more row to use when we are flipping the texture - unsigned char *pPixelData = (unsigned char *)malloc((size_t)w * (h + 1) * 3); - unsigned char *pTempRow = pPixelData + w * h * 3; - - // fetch the pixels - GLint Alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); - glPixelStorei(GL_PACK_ALIGNMENT, Alignment); - - // flip the pixel because opengl works from bottom left corner - for(int y = 0; y < h / 2; y++) - { - mem_copy(pTempRow, pPixelData + y * w * 3, w * 3); - mem_copy(pPixelData + y * w * 3, pPixelData + (h - y - 1) * w * 3, w * 3); - mem_copy(pPixelData + (h - y - 1) * w * 3, pTempRow, w * 3); - } - - // fill in the information - pCommand->m_pImage->m_Width = w; - pCommand->m_pImage->m_Height = h; - pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGB; - pCommand->m_pImage->m_pData = pPixelData; -} - -CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL() -{ - m_Textures.resize(CCommandBuffer::MAX_TEXTURES); - m_HasShaders = false; -} - -bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) -{ - switch(pBaseCommand->m_Cmd) - { - case CCommandProcessorFragment_OpenGL::CMD_INIT: - Cmd_Init(static_cast(pBaseCommand)); - break; - case CCommandProcessorFragment_OpenGL::CMD_SHUTDOWN: - Cmd_Shutdown(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_TEXTURE_CREATE: - Cmd_Texture_Create(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_TEXTURE_DESTROY: - Cmd_Texture_Destroy(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_TEXTURE_UPDATE: - Cmd_Texture_Update(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_CLEAR: - Cmd_Clear(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_RENDER: - Cmd_Render(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_RENDER_TEX3D: - Cmd_RenderTex3D(static_cast(pBaseCommand)); - break; - case CCommandBuffer::CMD_SCREENSHOT: - Cmd_Screenshot(static_cast(pBaseCommand)); - break; - - case CCommandBuffer::CMD_CREATE_BUFFER_OBJECT: Cmd_CreateBufferObject(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_UPDATE_BUFFER_OBJECT: Cmd_UpdateBufferObject(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RECREATE_BUFFER_OBJECT: Cmd_RecreateBufferObject(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_COPY_BUFFER_OBJECT: Cmd_CopyBufferObject(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_DELETE_BUFFER_OBJECT: Cmd_DeleteBufferObject(static_cast(pBaseCommand)); break; - - case CCommandBuffer::CMD_CREATE_BUFFER_CONTAINER: Cmd_CreateBufferContainer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_UPDATE_BUFFER_CONTAINER: Cmd_UpdateBufferContainer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_DELETE_BUFFER_CONTAINER: Cmd_DeleteBufferContainer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_INDICES_REQUIRED_NUM_NOTIFY: Cmd_IndicesRequiredNumNotify(static_cast(pBaseCommand)); break; - - case CCommandBuffer::CMD_RENDER_TILE_LAYER: Cmd_RenderTileLayer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_BORDER_TILE: Cmd_RenderBorderTile(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_BORDER_TILE_LINE: Cmd_RenderBorderTileLine(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_QUAD_LAYER: Cmd_RenderQuadLayer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_TEXT: Cmd_RenderText(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_TEXT_STREAM: Cmd_RenderTextStream(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER: Cmd_RenderQuadContainer(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_EX: Cmd_RenderQuadContainerEx(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_SPRITE_MULTIPLE: Cmd_RenderQuadContainerAsSpriteMultiple(static_cast(pBaseCommand)); break; - default: return false; - } - - return true; -} - -// ------------ CCommandProcessorFragment_OpenGL2 - -void CCommandProcessorFragment_OpenGL2::UseProgram(CGLSLTWProgram *pProgram) -{ - pProgram->UseProgram(); -} - -bool CCommandProcessorFragment_OpenGL2::IsAndUpdateTextureSlotBound(int IDX, int Slot, bool Is2DArray) -{ - if(m_TextureSlotBoundToUnit[IDX].m_TextureSlot == Slot && m_TextureSlotBoundToUnit[IDX].m_Is2DArray == Is2DArray) - return true; - else - { - //the texture slot uses this index now - m_TextureSlotBoundToUnit[IDX].m_TextureSlot = Slot; - m_TextureSlotBoundToUnit[IDX].m_Is2DArray = Is2DArray; - return false; - } -} - -void CCommandProcessorFragment_OpenGL2::SetState(const CCommandBuffer::SState &State, CGLSLTWProgram *pProgram, bool Use2DArrayTextures) -{ - if(m_LastBlendMode == CCommandBuffer::BLEND_NONE) - { - m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - if(State.m_BlendMode != m_LastBlendMode && State.m_BlendMode != CCommandBuffer::BLEND_NONE) - { - // blend - switch(State.m_BlendMode) - { - case CCommandBuffer::BLEND_NONE: - // We don't really need this anymore - //glDisable(GL_BLEND); - break; - case CCommandBuffer::BLEND_ALPHA: - //glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case CCommandBuffer::BLEND_ADDITIVE: - //glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - default: - dbg_msg("render", "unknown blendmode %d\n", State.m_BlendMode); - }; - - m_LastBlendMode = State.m_BlendMode; - } - - // clip - if(State.m_ClipEnable) - { - glScissor(State.m_ClipX, State.m_ClipY, State.m_ClipW, State.m_ClipH); - glEnable(GL_SCISSOR_TEST); - m_LastClipEnable = true; - } - else if(m_LastClipEnable) - { - // Don't disable it always - glDisable(GL_SCISSOR_TEST); - m_LastClipEnable = false; - } - - if(!IsNewApi()) - { - glDisable(GL_TEXTURE_2D); - if(!m_HasShaders) - { - if(m_Has3DTextures) - glDisable(GL_TEXTURE_3D); - if(m_Has2DArrayTextures) - { - glDisable(m_2DArrayTarget); - } - } - } - - // texture - if(IsTexturedState(State)) - { - int Slot = 0; - if(m_UseMultipleTextureUnits) - { - Slot = State.m_Texture % m_MaxTextureUnits; - if(!IsAndUpdateTextureSlotBound(Slot, State.m_Texture, Use2DArrayTextures)) - { - glActiveTexture(GL_TEXTURE0 + Slot); - if(!Use2DArrayTextures) - { - glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); - if(IsNewApi()) - glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler); - } - else - { - glBindTexture(GL_TEXTURE_2D_ARRAY, m_Textures[State.m_Texture].m_Tex2DArray); - if(IsNewApi()) - glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); - } - } - } - else - { - Slot = 0; - if(!Use2DArrayTextures) - { - if(!IsNewApi() && !m_HasShaders) - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, m_Textures[State.m_Texture].m_Tex); - if(IsNewApi()) - glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler); - } - else - { - if(!m_Has2DArrayTextures) - { - if(!IsNewApi() && !m_HasShaders) - glEnable(GL_TEXTURE_3D); - glBindTexture(GL_TEXTURE_3D, m_Textures[State.m_Texture].m_Tex2DArray); - if(IsNewApi()) - glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); - } - else - { - if(!IsNewApi() && !m_HasShaders) - glEnable(m_2DArrayTarget); - glBindTexture(m_2DArrayTarget, m_Textures[State.m_Texture].m_Tex2DArray); - if(IsNewApi()) - glBindSampler(Slot, m_Textures[State.m_Texture].m_Sampler2DArray); - } - } - } - - if(pProgram->m_LastTextureSampler != Slot) - { - pProgram->SetUniform(pProgram->m_LocTextureSampler, Slot); - pProgram->m_LastTextureSampler = Slot; - } - - if(m_Textures[State.m_Texture].m_LastWrapMode != State.m_WrapMode && !Use2DArrayTextures) - { - switch(State.m_WrapMode) - { - case CCommandBuffer::WRAP_REPEAT: - if(IsNewApi()) - { - glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_S, GL_REPEAT); - glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_T, GL_REPEAT); - } - break; - case CCommandBuffer::WRAP_CLAMP: - if(IsNewApi()) - { - glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glSamplerParameteri(m_Textures[State.m_Texture].m_Sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - break; - default: - dbg_msg("render", "unknown wrapmode %d\n", State.m_WrapMode); - }; - m_Textures[State.m_Texture].m_LastWrapMode = State.m_WrapMode; - } - } - - if(pProgram->m_LastScreen[0] != State.m_ScreenTL.x || pProgram->m_LastScreen[1] != State.m_ScreenTL.y || pProgram->m_LastScreen[2] != State.m_ScreenBR.x || pProgram->m_LastScreen[3] != State.m_ScreenBR.y) - { - pProgram->m_LastScreen[0] = State.m_ScreenTL.x; - pProgram->m_LastScreen[1] = State.m_ScreenTL.y; - pProgram->m_LastScreen[2] = State.m_ScreenBR.x; - pProgram->m_LastScreen[3] = State.m_ScreenBR.y; - // screen mapping - // orthographic projection matrix - // the z coordinate is the same for every vertex, so just ignore the z coordinate and set it in the shaders - float m[2 * 4] = { - 2.f / (State.m_ScreenBR.x - State.m_ScreenTL.x), 0, 0, -((State.m_ScreenBR.x + State.m_ScreenTL.x) / (State.m_ScreenBR.x - State.m_ScreenTL.x)), - 0, (2.f / (State.m_ScreenTL.y - State.m_ScreenBR.y)), 0, -((State.m_ScreenTL.y + State.m_ScreenBR.y) / (State.m_ScreenTL.y - State.m_ScreenBR.y)), - //0, 0, -(2.f/(9.f)), -((11.f)/(9.f)), - //0, 0, 0, 1.0f - }; - - // transpose bcs of column-major order of opengl - glUniformMatrix4x2fv(pProgram->m_LocPos, 1, true, (float *)&m); - } -} - -bool CCommandProcessorFragment_OpenGL2::DoAnalyzeStep(size_t StepN, size_t CheckCount, size_t VerticesCount, uint8_t aFakeTexture[], size_t SingleImageSize) -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - int Slot = 0; - if(m_HasShaders) - { - CGLSLTWProgram *pProgram = m_pPrimitive3DProgramTextured; - if(StepN == 1) - pProgram = m_pTileProgramTextured; - UseProgram(pProgram); - - pProgram->SetUniform(pProgram->m_LocTextureSampler, Slot); - - if(StepN == 1) - { - float aColor[4] = {1.f, 1.f, 1.f, 1.f}; - pProgram->SetUniformVec4(((CGLSLTileProgram *)pProgram)->m_LocColor, 1, aColor); - } - - float m[2 * 4] = { - 1, 0, 0, 0, - 0, 1, 0, 0}; - - // transpose bcs of column-major order of opengl - glUniformMatrix4x2fv(pProgram->m_LocPos, 1, true, (float *)&m); - } - else - { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(-1, 1, -1, 1, -10.0f, 10.f); - } - - GLuint BufferID = 0; - if(StepN == 1 && m_HasShaders) - { - glGenBuffers(1, &BufferID); - glBindBuffer(GL_ARRAY_BUFFER, BufferID); - glBufferData(GL_ARRAY_BUFFER, VerticesCount * sizeof((m_aStreamVertices[0])), m_aStreamVertices, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof((m_aStreamVertices[0])), 0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, false, sizeof((m_aStreamVertices[0])), (GLvoid *)(sizeof(vec4) + sizeof(vec2))); - } - else - { - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); - glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); - glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); - } - - glDrawArrays(GL_QUADS, 0, VerticesCount); - - if(StepN == 1 && m_HasShaders) - { - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &BufferID); - } - else - { - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } - - if(m_HasShaders) - { - glUseProgram(0); - } - - glFinish(); - - GLint aViewport[4] = {0, 0, 0, 0}; - glGetIntegerv(GL_VIEWPORT, aViewport); - - int w = aViewport[2]; - int h = aViewport[3]; - - size_t PixelDataSize = (size_t)w * h * 3; - if(PixelDataSize == 0) - return false; - uint8_t *pPixelData = (uint8_t *)malloc(PixelDataSize); - - // fetch the pixels - GLint Alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); - glPixelStorei(GL_PACK_ALIGNMENT, Alignment); - - // now analyse the image data - bool CheckFailed = false; - int WidthTile = w / 16; - int HeightTile = h / 16; - int StartX = WidthTile / 2; - int StartY = HeightTile / 2; - for(size_t d = 0; d < CheckCount; ++d) - { - int CurX = (int)d % 16; - int CurY = (int)d / 16; - - int CheckX = StartX + CurX * WidthTile; - int CheckY = StartY + CurY * HeightTile; - - ptrdiff_t OffsetPixelData = (CheckY * (w * 3)) + (CheckX * 3); - ptrdiff_t OffsetFakeTexture = SingleImageSize * d; - OffsetPixelData = clamp(OffsetPixelData, 0, (ptrdiff_t)PixelDataSize); - OffsetFakeTexture = clamp(OffsetFakeTexture, 0, (ptrdiff_t)(SingleImageSize * CheckCount)); - uint8_t *pPixel = pPixelData + OffsetPixelData; - uint8_t *pPixelTex = aFakeTexture + OffsetFakeTexture; - for(size_t i = 0; i < 3; ++i) - { - if((pPixel[i] < pPixelTex[i] - 25) || (pPixel[i] > pPixelTex[i] + 25)) - { - CheckFailed = true; - break; - } - } - } - - free(pPixelData); - return !CheckFailed; -} - -bool CCommandProcessorFragment_OpenGL2::IsTileMapAnalysisSucceeded() -{ - glClearColor(0, 0, 0, 1); - - // create fake texture 1024x1024 - const size_t ImageWidth = 1024; - const size_t ImageHeight = 1024; - uint8_t *pFakeTexture = (uint8_t *)malloc(sizeof(uint8_t) * ImageWidth * ImageHeight * 4); - // fill by colors stepping by 50 => (255 / 50 ~ 5) => 5 times 3(color channels) = 5 ^ 3 = 125 possibilities to check - size_t CheckCount = 5 * 5 * 5; - // always fill 4 pixels of the texture, so the sampling is accurate - int aCurColor[4] = {25, 25, 25, 255}; - const size_t SingleImageWidth = 64; - const size_t SingleImageHeight = 64; - size_t SingleImageSize = SingleImageWidth * SingleImageHeight * 4; - for(size_t d = 0; d < CheckCount; ++d) - { - uint8_t *pCurFakeTexture = pFakeTexture + (ptrdiff_t)(SingleImageSize * d); - - uint8_t aCurColorUint8[SingleImageWidth * SingleImageHeight * 4]; - for(size_t y = 0; y < SingleImageHeight; ++y) - { - for(size_t x = 0; x < SingleImageWidth; ++x) - { - for(size_t i = 0; i < 4; ++i) - { - aCurColorUint8[(y * SingleImageWidth * 4) + (x * 4) + i] = (uint8_t)aCurColor[i]; - } - } - } - mem_copy(pCurFakeTexture, aCurColorUint8, sizeof(aCurColorUint8)); - - aCurColor[2] += 50; - if(aCurColor[2] > 225) - { - aCurColor[2] -= 250; - aCurColor[1] += 50; - } - if(aCurColor[1] > 225) - { - aCurColor[1] -= 250; - aCurColor[0] += 50; - } - if(aCurColor[0] > 225) - { - break; - } - } - - // upload the texture - GLuint FakeTexture; - glGenTextures(1, &FakeTexture); - - GLenum Target = GL_TEXTURE_3D; - if(m_Has2DArrayTextures) - { - Target = m_2DArrayTarget; - } - - glBindTexture(Target, FakeTexture); - glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - if(!m_Has2DArrayTextures) - { - glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - else - { - glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(Target, GL_GENERATE_MIPMAP, GL_TRUE); - } - - glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - - glTexImage3D(Target, 0, GL_RGBA, ImageWidth / 16, ImageHeight / 16, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, pFakeTexture); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_SCISSOR_TEST); - - if(!m_HasShaders) - { - glDisable(GL_TEXTURE_2D); - if(m_Has3DTextures) - glDisable(GL_TEXTURE_3D); - if(m_Has2DArrayTextures) - { - glDisable(m_2DArrayTarget); - } - - if(!m_Has2DArrayTextures) - { - glEnable(GL_TEXTURE_3D); - glBindTexture(GL_TEXTURE_3D, FakeTexture); - } - else - { - glEnable(m_2DArrayTarget); - glBindTexture(m_2DArrayTarget, FakeTexture); - } - } - - static_assert(sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0]) >= 256 * 4, "Keep the number of stream vertices >= 256 * 4."); - - size_t VertexCount = 0; - for(size_t i = 0; i < CheckCount; ++i) - { - float XPos = (float)(i % 16); - float YPos = (float)(i / 16); - - GL_SVertexTex3D *pVertex = &m_aStreamVertices[VertexCount++]; - GL_SVertexTex3D *pVertexBefore = pVertex; - pVertex->m_Pos.x = XPos / 16.f; - pVertex->m_Pos.y = YPos / 16.f; - pVertex->m_Color.r = 1; - pVertex->m_Color.g = 1; - pVertex->m_Color.b = 1; - pVertex->m_Color.a = 1; - pVertex->m_Tex.u = 0; - pVertex->m_Tex.v = 0; - - pVertex = &m_aStreamVertices[VertexCount++]; - pVertex->m_Pos.x = XPos / 16.f + 1.f / 16.f; - pVertex->m_Pos.y = YPos / 16.f; - pVertex->m_Color.r = 1; - pVertex->m_Color.g = 1; - pVertex->m_Color.b = 1; - pVertex->m_Color.a = 1; - pVertex->m_Tex.u = 1; - pVertex->m_Tex.v = 0; - - pVertex = &m_aStreamVertices[VertexCount++]; - pVertex->m_Pos.x = XPos / 16.f + 1.f / 16.f; - pVertex->m_Pos.y = YPos / 16.f + 1.f / 16.f; - pVertex->m_Color.r = 1; - pVertex->m_Color.g = 1; - pVertex->m_Color.b = 1; - pVertex->m_Color.a = 1; - pVertex->m_Tex.u = 1; - pVertex->m_Tex.v = 1; - - pVertex = &m_aStreamVertices[VertexCount++]; - pVertex->m_Pos.x = XPos / 16.f; - pVertex->m_Pos.y = YPos / 16.f + 1.f / 16.f; - pVertex->m_Color.r = 1; - pVertex->m_Color.g = 1; - pVertex->m_Color.b = 1; - pVertex->m_Color.a = 1; - pVertex->m_Tex.u = 0; - pVertex->m_Tex.v = 1; - - for(size_t n = 0; n < 4; ++n) - { - pVertexBefore[n].m_Pos.x *= 2; - pVertexBefore[n].m_Pos.x -= 1; - pVertexBefore[n].m_Pos.y *= 2; - pVertexBefore[n].m_Pos.y -= 1; - if(m_Has2DArrayTextures) - { - pVertexBefore[n].m_Tex.w = i; - } - else - { - pVertexBefore[n].m_Tex.w = (i + 0.5f) / 256.f; - } - } - } - - //everything build up, now do the analyze steps - bool NoError = DoAnalyzeStep(0, CheckCount, VertexCount, pFakeTexture, SingleImageSize); - if(NoError && m_HasShaders) - NoError &= DoAnalyzeStep(1, CheckCount, VertexCount, pFakeTexture, SingleImageSize); - - glDeleteTextures(1, &FakeTexture); - free(pFakeTexture); - - return NoError; -} - -void CCommandProcessorFragment_OpenGL2::Cmd_Init(const SCommand_Init *pCommand) -{ - CCommandProcessorFragment_OpenGL::Cmd_Init(pCommand); - - m_OpenGLTextureLodBIAS = g_Config.m_GfxOpenGLTextureLODBIAS; - - m_HasShaders = pCommand->m_pCapabilities->m_ShaderSupport; - - bool HasAllFunc = true; - if(m_HasShaders) - { - HasAllFunc &= (glUniformMatrix4x2fv != NULL) && (glGenBuffers != NULL); - HasAllFunc &= (glBindBuffer != NULL) && (glBufferData != NULL); - HasAllFunc &= (glEnableVertexAttribArray != NULL) && (glVertexAttribPointer != NULL); - HasAllFunc &= (glDisableVertexAttribArray != NULL) && (glDeleteBuffers != NULL); - HasAllFunc &= (glUseProgram != NULL) && (glTexImage3D != NULL); - HasAllFunc &= (glBindAttribLocation != NULL) && (glTexImage3D != NULL); - HasAllFunc &= (glBufferSubData != NULL) && (glGetUniformLocation != NULL); - HasAllFunc &= (glUniform1i != NULL) && (glUniform1f != NULL); - HasAllFunc &= (glUniform1ui != NULL) && (glUniform1i != NULL); - HasAllFunc &= (glUniform1fv != NULL) && (glUniform2fv != NULL); - HasAllFunc &= (glUniform4fv != NULL) && (glGetAttachedShaders != NULL); - HasAllFunc &= (glGetProgramInfoLog != NULL) && (glGetProgramiv != NULL); - HasAllFunc &= (glLinkProgram != NULL) && (glDetachShader != NULL); - HasAllFunc &= (glAttachShader != NULL) && (glDeleteProgram != NULL); - HasAllFunc &= (glCreateProgram != NULL) && (glShaderSource != NULL); - HasAllFunc &= (glCompileShader != NULL) && (glGetShaderiv != NULL); - HasAllFunc &= (glGetShaderInfoLog != NULL) && (glDeleteShader != NULL); - HasAllFunc &= (glCreateShader != NULL); - } - - bool AnalysisCorrect = true; - if(HasAllFunc) - { - if(m_HasShaders) - { - m_pTileProgram = new CGLSLTileProgram; - m_pTileProgramTextured = new CGLSLTileProgram; - m_pPrimitive3DProgram = new CGLSLPrimitiveProgram; - m_pPrimitive3DProgramTextured = new CGLSLPrimitiveProgram; - - CGLSLCompiler ShaderCompiler(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); - ShaderCompiler.SetHasTextureArray(pCommand->m_pCapabilities->m_2DArrayTextures); - - if(pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); - else - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); - - m_pPrimitive3DProgram->CreateProgram(); - m_pPrimitive3DProgram->AddShader(&PrimitiveVertexShader); - m_pPrimitive3DProgram->AddShader(&PrimitiveFragmentShader); - m_pPrimitive3DProgram->LinkProgram(); - - UseProgram(m_pPrimitive3DProgram); - - m_pPrimitive3DProgram->m_LocPos = m_pPrimitive3DProgram->GetUniformLoc("gPos"); - } - - if(pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); - else - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - ShaderCompiler.AddDefine("TW_TEXTURED", ""); - if(!pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.AddDefine("TW_3D_TEXTURED", ""); - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pPrimitive3DProgramTextured->CreateProgram(); - m_pPrimitive3DProgramTextured->AddShader(&PrimitiveVertexShader); - m_pPrimitive3DProgramTextured->AddShader(&PrimitiveFragmentShader); - m_pPrimitive3DProgramTextured->LinkProgram(); - - UseProgram(m_pPrimitive3DProgramTextured); - - m_pPrimitive3DProgramTextured->m_LocPos = m_pPrimitive3DProgramTextured->GetUniformLoc("gPos"); - m_pPrimitive3DProgramTextured->m_LocTextureSampler = m_pPrimitive3DProgramTextured->GetUniformLoc("gTextureSampler"); - } - if(pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); - else - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); - { - CGLSL VertexShader; - CGLSL FragmentShader; - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - - m_pTileProgram->CreateProgram(); - m_pTileProgram->AddShader(&VertexShader); - m_pTileProgram->AddShader(&FragmentShader); - - glBindAttribLocation(m_pTileProgram->GetProgramID(), 0, "inVertex"); - - m_pTileProgram->LinkProgram(); - - UseProgram(m_pTileProgram); - - m_pTileProgram->m_LocPos = m_pTileProgram->GetUniformLoc("gPos"); - m_pTileProgram->m_LocColor = m_pTileProgram->GetUniformLoc("gVertColor"); - } - if(pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY); - else - ShaderCompiler.SetTextureReplaceType(CGLSLCompiler::GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D); - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); - if(!pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.AddDefine("TW_TILE_3D_TEXTURED", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pTileProgramTextured->CreateProgram(); - m_pTileProgramTextured->AddShader(&VertexShader); - m_pTileProgramTextured->AddShader(&FragmentShader); - - glBindAttribLocation(m_pTileProgram->GetProgramID(), 0, "inVertex"); - glBindAttribLocation(m_pTileProgram->GetProgramID(), 1, "inVertexTexCoord"); - - m_pTileProgramTextured->LinkProgram(); - - UseProgram(m_pTileProgramTextured); - - m_pTileProgramTextured->m_LocPos = m_pTileProgramTextured->GetUniformLoc("gPos"); - m_pTileProgramTextured->m_LocTextureSampler = m_pTileProgramTextured->GetUniformLoc("gTextureSampler"); - m_pTileProgramTextured->m_LocColor = m_pTileProgramTextured->GetUniformLoc("gVertColor"); - } - - glUseProgram(0); - } - - if(g_Config.m_Gfx3DTextureAnalysisDone == 0) - { - AnalysisCorrect = IsTileMapAnalysisSucceeded(); - if(AnalysisCorrect) - { - g_Config.m_Gfx3DTextureAnalysisDone = 1; - } - } - } - - if(!AnalysisCorrect || !HasAllFunc) - { - // downgrade to opengl 1.5 - *pCommand->m_pInitError = -2; - pCommand->m_pCapabilities->m_ContextMajor = 1; - pCommand->m_pCapabilities->m_ContextMinor = 5; - pCommand->m_pCapabilities->m_ContextPatch = 0; - } -} - -void CCommandProcessorFragment_OpenGL2::Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) -{ - if(m_HasShaders) - { - CGLSLPrimitiveProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pPrimitive3DProgramTextured; - } - else - pProgram = m_pPrimitive3DProgram; - - UseProgram(pProgram); - - SetState(pCommand->m_State, pProgram, true); - } - else - { - CCommandProcessorFragment_OpenGL::SetState(pCommand->m_State, true); - } - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glVertexPointer(2, GL_FLOAT, sizeof(pCommand->m_pVertices[0]), pCommand->m_pVertices); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(pCommand->m_pVertices[0]), (uint8_t *)pCommand->m_pVertices + (ptrdiff_t)(sizeof(vec2))); - glTexCoordPointer(3, GL_FLOAT, sizeof(pCommand->m_pVertices[0]), (uint8_t *)pCommand->m_pVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(unsigned char) * 4)); - - switch(pCommand->m_PrimType) - { - case CCommandBuffer::PRIMTYPE_QUADS: - glDrawArrays(GL_QUADS, 0, pCommand->m_PrimCount * 4); - break; - case CCommandBuffer::PRIMTYPE_TRIANGLES: - glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); - break; - default: - dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); - }; - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - if(m_HasShaders) - { - glUseProgram(0); - } -} - -void CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - //create necessary space - if((size_t)Index >= m_BufferObjectIndices.size()) - { - for(int i = m_BufferObjectIndices.size(); i < Index + 1; ++i) - { - m_BufferObjectIndices.push_back(SBufferObject(0)); - } - } - - GLuint VertBufferID = 0; - - if(m_HasShaders) - { - glGenBuffers(1, &VertBufferID); - glBindBuffer(GL_COPY_WRITE_BUFFER, VertBufferID); - glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } - - SBufferObject &BufferObject = m_BufferObjectIndices[Index]; - BufferObject.m_BufferObjectID = VertBufferID; - BufferObject.m_DataSize = pCommand->m_DataSize; - BufferObject.m_pData = malloc(pCommand->m_DataSize); - if(pUploadData) - mem_copy(BufferObject.m_pData, pUploadData, pCommand->m_DataSize); - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - SBufferObject &BufferObject = m_BufferObjectIndices[Index]; - - if(m_HasShaders) - { - glBindBuffer(GL_COPY_WRITE_BUFFER, BufferObject.m_BufferObjectID); - glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } - - BufferObject.m_DataSize = pCommand->m_DataSize; - if(BufferObject.m_pData) - free(BufferObject.m_pData); - BufferObject.m_pData = malloc(pCommand->m_DataSize); - if(pUploadData) - mem_copy(BufferObject.m_pData, pUploadData, pCommand->m_DataSize); - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - SBufferObject &BufferObject = m_BufferObjectIndices[Index]; - - if(m_HasShaders) - { - glBindBuffer(GL_COPY_WRITE_BUFFER, BufferObject.m_BufferObjectID); - glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pOffset), (GLsizeiptr)(pCommand->m_DataSize), pUploadData); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } - - if(pUploadData) - mem_copy(((uint8_t *)BufferObject.m_pData) + (ptrdiff_t)pCommand->m_pOffset, pUploadData, pCommand->m_DataSize); - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) -{ - int WriteIndex = pCommand->m_WriteBufferIndex; - int ReadIndex = pCommand->m_ReadBufferIndex; - - SBufferObject &ReadBufferObject = m_BufferObjectIndices[ReadIndex]; - SBufferObject &WriteBufferObject = m_BufferObjectIndices[WriteIndex]; - - mem_copy(((uint8_t *)WriteBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pWriteOffset, ((uint8_t *)ReadBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pReadOffset, pCommand->m_CopySize); - - if(m_HasShaders) - { - glBindBuffer(GL_COPY_WRITE_BUFFER, WriteBufferObject.m_BufferObjectID); - glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pWriteOffset), (GLsizeiptr)(pCommand->m_CopySize), ((uint8_t *)WriteBufferObject.m_pData) + (ptrdiff_t)pCommand->m_pWriteOffset); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - } -} - -void CCommandProcessorFragment_OpenGL2::Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) -{ - int Index = pCommand->m_BufferIndex; - SBufferObject &BufferObject = m_BufferObjectIndices[Index]; - - if(m_HasShaders) - { - glDeleteBuffers(1, &BufferObject.m_BufferObjectID); - } - - if(BufferObject.m_pData) - { - free(BufferObject.m_pData); - BufferObject.m_pData = NULL; - } -} - -void CCommandProcessorFragment_OpenGL2::Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //create necessary space - if((size_t)Index >= m_BufferContainers.size()) - { - for(int i = m_BufferContainers.size(); i < Index + 1; ++i) - { - SBufferContainer Container; - Container.m_ContainerInfo.m_Stride = 0; - m_BufferContainers.push_back(Container); - } - } - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - - for(int i = 0; i < pCommand->m_AttrCount; ++i) - { - SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; - BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); - } - - BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; -} - -void CCommandProcessorFragment_OpenGL2::Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) -{ - SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; - - BufferContainer.m_ContainerInfo.m_Attributes.clear(); - - for(int i = 0; i < pCommand->m_AttrCount; ++i) - { - SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; - BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); - } - - BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; -} - -void CCommandProcessorFragment_OpenGL2::Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) -{ - SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; - - if(pCommand->m_DestroyAllBO) - { - for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) - { - int VertBufferID = BufferContainer.m_ContainerInfo.m_Attributes[i].m_VertBufferBindingIndex; - if(VertBufferID != -1) - { - for(auto &Attribute : BufferContainer.m_ContainerInfo.m_Attributes) - { - // set all equal ids to zero to not double delete - if(VertBufferID == Attribute.m_VertBufferBindingIndex) - { - Attribute.m_VertBufferBindingIndex = -1; - } - } - - if(m_HasShaders) - { - glDeleteBuffers(1, &m_BufferObjectIndices[VertBufferID].m_BufferObjectID); - } - - if(m_BufferObjectIndices[VertBufferID].m_pData) - { - free(m_BufferObjectIndices[VertBufferID].m_pData); - m_BufferObjectIndices[VertBufferID].m_pData = NULL; - } - } - } - } - - BufferContainer.m_ContainerInfo.m_Attributes.clear(); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) -{ -} - -void CCommandProcessorFragment_OpenGL2::RenderBorderTileEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int DrawNum, const float *pOffset, const float *pDir, int JumpIndex) -{ - if(m_HasShaders) - { - CGLSLPrimitiveProgram *pProgram = NULL; - if(IsTexturedState(State)) - { - pProgram = m_pPrimitive3DProgramTextured; - } - else - pProgram = m_pPrimitive3DProgram; - - UseProgram(pProgram); - - SetState(State, pProgram, true); - } - else - { - CCommandProcessorFragment_OpenGL::SetState(State, true); - } - - bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; - - SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); - glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); - if(IsTextured) - glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); - - size_t VertexCount = 0; - for(size_t i = 0; i < DrawNum; ++i) - { - GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pBuffOffset)) / (6 * sizeof(unsigned int))) * 4); - size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); - size_t CurBufferOffset = (RealOffset)*SingleVertSize; - - for(size_t n = 0; n < 4; ++n) - { - int XCount = i - (int(i / JumpIndex) * JumpIndex); - int YCount = (int(i / JumpIndex)); - - ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); - vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); - - GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; - mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); - mem_copy(&Vertex.m_Color, pColor, sizeof(vec4)); - if(IsTextured) - { - vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); - mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); - } - - Vertex.m_Pos.x += pOffset[0] + pDir[0] * XCount; - Vertex.m_Pos.y += pOffset[1] + pDir[1] * YCount; - - if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) - { - glDrawArrays(GL_QUADS, 0, VertexCount); - VertexCount = 0; - } - } - } - if(VertexCount > 0) - glDrawArrays(GL_QUADS, 0, VertexCount); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - if(m_HasShaders) - { - glUseProgram(0); - } -} - -void CCommandProcessorFragment_OpenGL2::RenderBorderTileLineEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int IndexDrawNum, unsigned int DrawNum, const float *pOffset, const float *pDir) -{ - if(m_HasShaders) - { - CGLSLPrimitiveProgram *pProgram = NULL; - if(IsTexturedState(State)) - { - pProgram = m_pPrimitive3DProgramTextured; - } - else - pProgram = m_pPrimitive3DProgram; - - UseProgram(pProgram); - - SetState(State, pProgram, true); - } - else - { - CCommandProcessorFragment_OpenGL::SetState(State, true); - } - - bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; - - SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); - glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); - if(IsTextured) - glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); - - size_t VertexCount = 0; - for(size_t i = 0; i < DrawNum; ++i) - { - GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pBuffOffset)) / (6 * sizeof(unsigned int))) * 4); - size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); - size_t CurBufferOffset = (RealOffset)*SingleVertSize; - size_t VerticesPerLine = (size_t)IndexDrawNum / 6; - - for(size_t n = 0; n < 4 * (size_t)VerticesPerLine; ++n) - { - ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); - vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); - - GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; - mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); - mem_copy(&Vertex.m_Color, pColor, sizeof(vec4)); - if(IsTextured) - { - vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); - mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); - } - - Vertex.m_Pos.x += pOffset[0] + pDir[0] * i; - Vertex.m_Pos.y += pOffset[1] + pDir[1] * i; - - if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) - { - glDrawArrays(GL_QUADS, 0, VertexCount); - VertexCount = 0; - } - } - } - if(VertexCount > 0) - glDrawArrays(GL_QUADS, 0, VertexCount); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - if(m_HasShaders) - { - glUseProgram(0); - } -} - -void CCommandProcessorFragment_OpenGL2::Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - - RenderBorderTileEmulation(BufferContainer, pCommand->m_State, (float *)&pCommand->m_Color, pCommand->m_pIndicesOffset, pCommand->m_DrawNum, pCommand->m_Offset, pCommand->m_Dir, pCommand->m_JumpIndex); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - - RenderBorderTileLineEmulation(BufferContainer, pCommand->m_State, (float *)&pCommand->m_Color, pCommand->m_pIndicesOffset, pCommand->m_IndexDrawNum, pCommand->m_DrawNum, pCommand->m_Offset, pCommand->m_Dir); -} - -void CCommandProcessorFragment_OpenGL2::Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - - if(pCommand->m_IndicesDrawNum == 0) - { - return; //nothing to draw - } - - if(m_HasShaders) - { - CGLSLTileProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pTileProgramTextured; - } - else - pProgram = m_pTileProgram; - - UseProgram(pProgram); - - SetState(pCommand->m_State, pProgram, true); - pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); - } - else - { - CCommandProcessorFragment_OpenGL::SetState(pCommand->m_State, true); - } - - bool IsTextured = BufferContainer.m_ContainerInfo.m_Attributes.size() == 2; - - SBufferObject &BufferObject = m_BufferObjectIndices[(size_t)BufferContainer.m_ContainerInfo.m_Attributes[0].m_VertBufferBindingIndex]; - if(m_HasShaders) - glBindBuffer(GL_ARRAY_BUFFER, BufferObject.m_BufferObjectID); - - if(!m_HasShaders) - { - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - } - - if(m_HasShaders) - { - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, false, BufferContainer.m_ContainerInfo.m_Stride, BufferContainer.m_ContainerInfo.m_Attributes[0].m_pOffset); - if(IsTextured) - { - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, false, BufferContainer.m_ContainerInfo.m_Stride, BufferContainer.m_ContainerInfo.m_Attributes[1].m_pOffset); - } - - for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) - { - size_t RealDrawCount = (pCommand->m_pDrawCount[i] / 6) * 4; - GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pCommand->m_pIndicesOffsets[i])) / (6 * sizeof(unsigned int))) * 4); - glDrawArrays(GL_QUADS, RealOffset, RealDrawCount); - } - } - else - { - glVertexPointer(2, GL_FLOAT, sizeof(m_aStreamVertices[0]), m_aStreamVertices); - glColorPointer(4, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2))); - if(IsTextured) - glTexCoordPointer(3, GL_FLOAT, sizeof(m_aStreamVertices[0]), (uint8_t *)m_aStreamVertices + (ptrdiff_t)(sizeof(vec2) + sizeof(vec4))); - - size_t VertexCount = 0; - for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) - { - size_t RealDrawCount = (pCommand->m_pDrawCount[i] / 6) * 4; - GLint RealOffset = (GLint)((((size_t)(uintptr_t)(pCommand->m_pIndicesOffsets[i])) / (6 * sizeof(unsigned int))) * 4); - size_t SingleVertSize = (sizeof(vec2) + (IsTextured ? sizeof(vec3) : 0)); - size_t CurBufferOffset = RealOffset * SingleVertSize; - - for(size_t n = 0; n < RealDrawCount; ++n) - { - ptrdiff_t VertOffset = (ptrdiff_t)(CurBufferOffset + (n * SingleVertSize)); - vec2 *pPos = (vec2 *)((uint8_t *)BufferObject.m_pData + VertOffset); - GL_SVertexTex3D &Vertex = m_aStreamVertices[VertexCount++]; - mem_copy(&Vertex.m_Pos, pPos, sizeof(vec2)); - mem_copy(&Vertex.m_Color, &pCommand->m_Color, sizeof(vec4)); - if(IsTextured) - { - vec3 *pTex = (vec3 *)((uint8_t *)BufferObject.m_pData + VertOffset + (ptrdiff_t)sizeof(vec2)); - mem_copy(&Vertex.m_Tex, pTex, sizeof(vec3)); - } - - if(VertexCount >= sizeof(m_aStreamVertices) / sizeof(m_aStreamVertices[0])) - { - glDrawArrays(GL_QUADS, 0, VertexCount); - VertexCount = 0; - } - } - } - if(VertexCount > 0) - glDrawArrays(GL_QUADS, 0, VertexCount); - } - - if(!m_HasShaders) - { - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - - if(IsTextured) - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } - else - { - glDisableVertexAttribArray(0); - if(IsTextured) - glDisableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glUseProgram(0); - } -} - -// ------------ CCommandProcessorFragment_OpenGL3_3 -int CCommandProcessorFragment_OpenGL3_3::TexFormatToNewOpenGLFormat(int TexFormat) -{ - if(TexFormat == CCommandBuffer::TEXFORMAT_RGB) - return GL_RGB; - if(TexFormat == CCommandBuffer::TEXFORMAT_ALPHA) - return GL_RED; - if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) - return GL_RGBA; - return GL_RGBA; -} - -void CCommandProcessorFragment_OpenGL3_3::UseProgram(CGLSLTWProgram *pProgram) -{ - if(m_LastProgramID != pProgram->GetProgramID()) - { - pProgram->UseProgram(); - m_LastProgramID = pProgram->GetProgramID(); - } -} - -void CCommandProcessorFragment_OpenGL3_3::InitPrimExProgram(CGLSLPrimitiveExProgram *pProgram, CGLSLCompiler *pCompiler, IStorage *pStorage, bool Textured, bool Rotationless) -{ - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - if(Textured) - pCompiler->AddDefine("TW_TEXTURED", ""); - if(Rotationless) - pCompiler->AddDefine("TW_ROTATIONLESS", ""); - PrimitiveVertexShader.LoadShader(pCompiler, pStorage, "shader/primex.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(pCompiler, pStorage, "shader/primex.frag", GL_FRAGMENT_SHADER); - if(Textured || Rotationless) - pCompiler->ClearDefines(); - - pProgram->CreateProgram(); - pProgram->AddShader(&PrimitiveVertexShader); - pProgram->AddShader(&PrimitiveFragmentShader); - pProgram->LinkProgram(); - - UseProgram(pProgram); - - pProgram->m_LocPos = pProgram->GetUniformLoc("gPos"); - pProgram->m_LocTextureSampler = pProgram->GetUniformLoc("gTextureSampler"); - pProgram->m_LocRotation = pProgram->GetUniformLoc("gRotation"); - pProgram->m_LocCenter = pProgram->GetUniformLoc("gCenter"); - pProgram->m_LocVertciesColor = pProgram->GetUniformLoc("gVerticesColor"); - - pProgram->SetUniform(pProgram->m_LocRotation, 0.0f); - float Center[2] = {0.f, 0.f}; - pProgram->SetUniformVec2(pProgram->m_LocCenter, 1, Center); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Init(const SCommand_Init *pCommand) -{ - m_OpenGLTextureLodBIAS = g_Config.m_GfxOpenGLTextureLODBIAS; - - m_UseMultipleTextureUnits = g_Config.m_GfxEnableTextureUnitOptimization; - if(!m_UseMultipleTextureUnits) - { - glActiveTexture(GL_TEXTURE0); - } - - m_Has2DArrayTextures = true; - m_Has2DArrayTexturesAsExtension = false; - m_2DArrayTarget = GL_TEXTURE_2D_ARRAY; - m_Has3DTextures = false; - m_HasMipMaps = true; - m_HasNPOTTextures = true; - m_HasShaders = true; - - m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage; - m_pTextureMemoryUsage->store(0, std::memory_order_relaxed); - m_LastBlendMode = CCommandBuffer::BLEND_ALPHA; - m_LastClipEnable = false; - m_pPrimitiveProgram = new CGLSLPrimitiveProgram; - m_pPrimitiveProgramTextured = new CGLSLPrimitiveProgram; - m_pTileProgram = new CGLSLTileProgram; - m_pTileProgramTextured = new CGLSLTileProgram; - m_pPrimitive3DProgram = new CGLSLPrimitiveProgram; - m_pPrimitive3DProgramTextured = new CGLSLPrimitiveProgram; - m_pBorderTileProgram = new CGLSLTileProgram; - m_pBorderTileProgramTextured = new CGLSLTileProgram; - m_pBorderTileLineProgram = new CGLSLTileProgram; - m_pBorderTileLineProgramTextured = new CGLSLTileProgram; - m_pQuadProgram = new CGLSLQuadProgram; - m_pQuadProgramTextured = new CGLSLQuadProgram; - m_pTextProgram = new CGLSLTextProgram; - m_pPrimitiveExProgram = new CGLSLPrimitiveExProgram; - m_pPrimitiveExProgramTextured = new CGLSLPrimitiveExProgram; - m_pPrimitiveExProgramRotationless = new CGLSLPrimitiveExProgram; - m_pPrimitiveExProgramTexturedRotationless = new CGLSLPrimitiveExProgram; - m_pSpriteProgramMultiple = new CGLSLSpriteMultipleProgram; - m_LastProgramID = 0; - - CGLSLCompiler ShaderCompiler(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); - - GLint CapVal; - glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &CapVal); - ; - m_MaxQuadsAtOnce = minimum(((CapVal - 20) / (3 * 4)), m_MaxQuadsPossible); - - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.frag", GL_FRAGMENT_SHADER); - - m_pPrimitiveProgram->CreateProgram(); - m_pPrimitiveProgram->AddShader(&PrimitiveVertexShader); - m_pPrimitiveProgram->AddShader(&PrimitiveFragmentShader); - m_pPrimitiveProgram->LinkProgram(); - - UseProgram(m_pPrimitiveProgram); - - m_pPrimitiveProgram->m_LocPos = m_pPrimitiveProgram->GetUniformLoc("gPos"); - m_pPrimitiveProgram->m_LocTextureSampler = m_pPrimitiveProgram->GetUniformLoc("gTextureSampler"); - } - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - ShaderCompiler.AddDefine("TW_TEXTURED", ""); - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/prim.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pPrimitiveProgramTextured->CreateProgram(); - m_pPrimitiveProgramTextured->AddShader(&PrimitiveVertexShader); - m_pPrimitiveProgramTextured->AddShader(&PrimitiveFragmentShader); - m_pPrimitiveProgramTextured->LinkProgram(); - - UseProgram(m_pPrimitiveProgramTextured); - - m_pPrimitiveProgramTextured->m_LocPos = m_pPrimitiveProgramTextured->GetUniformLoc("gPos"); - m_pPrimitiveProgramTextured->m_LocTextureSampler = m_pPrimitiveProgramTextured->GetUniformLoc("gTextureSampler"); - } - - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - ShaderCompiler.AddDefine("TW_MODERN_GL", ""); - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pPrimitive3DProgram->CreateProgram(); - m_pPrimitive3DProgram->AddShader(&PrimitiveVertexShader); - m_pPrimitive3DProgram->AddShader(&PrimitiveFragmentShader); - m_pPrimitive3DProgram->LinkProgram(); - - UseProgram(m_pPrimitive3DProgram); - - m_pPrimitive3DProgram->m_LocPos = m_pPrimitive3DProgram->GetUniformLoc("gPos"); - } - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - ShaderCompiler.AddDefine("TW_MODERN_GL", ""); - ShaderCompiler.AddDefine("TW_TEXTURED", ""); - if(!pCommand->m_pCapabilities->m_2DArrayTextures) - ShaderCompiler.AddDefine("TW_3D_TEXTURED", ""); - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/pipeline.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pPrimitive3DProgramTextured->CreateProgram(); - m_pPrimitive3DProgramTextured->AddShader(&PrimitiveVertexShader); - m_pPrimitive3DProgramTextured->AddShader(&PrimitiveFragmentShader); - m_pPrimitive3DProgramTextured->LinkProgram(); - - UseProgram(m_pPrimitive3DProgramTextured); - - m_pPrimitive3DProgramTextured->m_LocPos = m_pPrimitive3DProgramTextured->GetUniformLoc("gPos"); - m_pPrimitive3DProgramTextured->m_LocTextureSampler = m_pPrimitive3DProgramTextured->GetUniformLoc("gTextureSampler"); - } - - { - CGLSL VertexShader; - CGLSL FragmentShader; - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - - m_pTileProgram->CreateProgram(); - m_pTileProgram->AddShader(&VertexShader); - m_pTileProgram->AddShader(&FragmentShader); - m_pTileProgram->LinkProgram(); - - UseProgram(m_pTileProgram); - - m_pTileProgram->m_LocPos = m_pTileProgram->GetUniformLoc("gPos"); - m_pTileProgram->m_LocColor = m_pTileProgram->GetUniformLoc("gVertColor"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pTileProgramTextured->CreateProgram(); - m_pTileProgramTextured->AddShader(&VertexShader); - m_pTileProgramTextured->AddShader(&FragmentShader); - m_pTileProgramTextured->LinkProgram(); - - UseProgram(m_pTileProgramTextured); - - m_pTileProgramTextured->m_LocPos = m_pTileProgramTextured->GetUniformLoc("gPos"); - m_pTileProgramTextured->m_LocTextureSampler = m_pTileProgramTextured->GetUniformLoc("gTextureSampler"); - m_pTileProgramTextured->m_LocColor = m_pTileProgramTextured->GetUniformLoc("gVertColor"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_BORDER", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pBorderTileProgram->CreateProgram(); - m_pBorderTileProgram->AddShader(&VertexShader); - m_pBorderTileProgram->AddShader(&FragmentShader); - m_pBorderTileProgram->LinkProgram(); - - UseProgram(m_pBorderTileProgram); - - m_pBorderTileProgram->m_LocPos = m_pBorderTileProgram->GetUniformLoc("gPos"); - m_pBorderTileProgram->m_LocColor = m_pBorderTileProgram->GetUniformLoc("gVertColor"); - m_pBorderTileProgram->m_LocOffset = m_pBorderTileProgram->GetUniformLoc("gOffset"); - m_pBorderTileProgram->m_LocDir = m_pBorderTileProgram->GetUniformLoc("gDir"); - m_pBorderTileProgram->m_LocJumpIndex = m_pBorderTileProgram->GetUniformLoc("gJumpIndex"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_BORDER", ""); - ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pBorderTileProgramTextured->CreateProgram(); - m_pBorderTileProgramTextured->AddShader(&VertexShader); - m_pBorderTileProgramTextured->AddShader(&FragmentShader); - m_pBorderTileProgramTextured->LinkProgram(); - - UseProgram(m_pBorderTileProgramTextured); - - m_pBorderTileProgramTextured->m_LocPos = m_pBorderTileProgramTextured->GetUniformLoc("gPos"); - m_pBorderTileProgramTextured->m_LocTextureSampler = m_pBorderTileProgramTextured->GetUniformLoc("gTextureSampler"); - m_pBorderTileProgramTextured->m_LocColor = m_pBorderTileProgramTextured->GetUniformLoc("gVertColor"); - m_pBorderTileProgramTextured->m_LocOffset = m_pBorderTileProgramTextured->GetUniformLoc("gOffset"); - m_pBorderTileProgramTextured->m_LocDir = m_pBorderTileProgramTextured->GetUniformLoc("gDir"); - m_pBorderTileProgramTextured->m_LocJumpIndex = m_pBorderTileProgramTextured->GetUniformLoc("gJumpIndex"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_BORDER_LINE", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pBorderTileLineProgram->CreateProgram(); - m_pBorderTileLineProgram->AddShader(&VertexShader); - m_pBorderTileLineProgram->AddShader(&FragmentShader); - m_pBorderTileLineProgram->LinkProgram(); - - UseProgram(m_pBorderTileLineProgram); - - m_pBorderTileLineProgram->m_LocPos = m_pBorderTileLineProgram->GetUniformLoc("gPos"); - m_pBorderTileLineProgram->m_LocColor = m_pBorderTileLineProgram->GetUniformLoc("gVertColor"); - m_pBorderTileLineProgram->m_LocOffset = m_pBorderTileLineProgram->GetUniformLoc("gOffset"); - m_pBorderTileLineProgram->m_LocDir = m_pBorderTileLineProgram->GetUniformLoc("gDir"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_TILE_BORDER_LINE", ""); - ShaderCompiler.AddDefine("TW_TILE_TEXTURED", ""); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/tile.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pBorderTileLineProgramTextured->CreateProgram(); - m_pBorderTileLineProgramTextured->AddShader(&VertexShader); - m_pBorderTileLineProgramTextured->AddShader(&FragmentShader); - m_pBorderTileLineProgramTextured->LinkProgram(); - - UseProgram(m_pBorderTileLineProgramTextured); - - m_pBorderTileLineProgramTextured->m_LocPos = m_pBorderTileLineProgramTextured->GetUniformLoc("gPos"); - m_pBorderTileLineProgramTextured->m_LocTextureSampler = m_pBorderTileLineProgramTextured->GetUniformLoc("gTextureSampler"); - m_pBorderTileLineProgramTextured->m_LocColor = m_pBorderTileLineProgramTextured->GetUniformLoc("gVertColor"); - m_pBorderTileLineProgramTextured->m_LocOffset = m_pBorderTileLineProgramTextured->GetUniformLoc("gOffset"); - m_pBorderTileLineProgramTextured->m_LocDir = m_pBorderTileLineProgramTextured->GetUniformLoc("gDir"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_MAX_QUADS", std::to_string(m_MaxQuadsAtOnce).c_str()); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pQuadProgram->CreateProgram(); - m_pQuadProgram->AddShader(&VertexShader); - m_pQuadProgram->AddShader(&FragmentShader); - m_pQuadProgram->LinkProgram(); - - UseProgram(m_pQuadProgram); - - m_pQuadProgram->m_LocPos = m_pQuadProgram->GetUniformLoc("gPos"); - m_pQuadProgram->m_LocColors = m_pQuadProgram->GetUniformLoc("gVertColors"); - m_pQuadProgram->m_LocRotations = m_pQuadProgram->GetUniformLoc("gRotations"); - m_pQuadProgram->m_LocOffsets = m_pQuadProgram->GetUniformLoc("gOffsets"); - m_pQuadProgram->m_LocQuadOffset = m_pQuadProgram->GetUniformLoc("gQuadOffset"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - ShaderCompiler.AddDefine("TW_QUAD_TEXTURED", ""); - ShaderCompiler.AddDefine("TW_MAX_QUADS", std::to_string(m_MaxQuadsAtOnce).c_str()); - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/quad.frag", GL_FRAGMENT_SHADER); - ShaderCompiler.ClearDefines(); - - m_pQuadProgramTextured->CreateProgram(); - m_pQuadProgramTextured->AddShader(&VertexShader); - m_pQuadProgramTextured->AddShader(&FragmentShader); - m_pQuadProgramTextured->LinkProgram(); - - UseProgram(m_pQuadProgramTextured); - - m_pQuadProgramTextured->m_LocPos = m_pQuadProgramTextured->GetUniformLoc("gPos"); - m_pQuadProgramTextured->m_LocTextureSampler = m_pQuadProgramTextured->GetUniformLoc("gTextureSampler"); - m_pQuadProgramTextured->m_LocColors = m_pQuadProgramTextured->GetUniformLoc("gVertColors"); - m_pQuadProgramTextured->m_LocRotations = m_pQuadProgramTextured->GetUniformLoc("gRotations"); - m_pQuadProgramTextured->m_LocOffsets = m_pQuadProgramTextured->GetUniformLoc("gOffsets"); - m_pQuadProgramTextured->m_LocQuadOffset = m_pQuadProgramTextured->GetUniformLoc("gQuadOffset"); - } - { - CGLSL VertexShader; - CGLSL FragmentShader; - VertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/text.vert", GL_VERTEX_SHADER); - FragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/text.frag", GL_FRAGMENT_SHADER); - - m_pTextProgram->CreateProgram(); - m_pTextProgram->AddShader(&VertexShader); - m_pTextProgram->AddShader(&FragmentShader); - m_pTextProgram->LinkProgram(); - - UseProgram(m_pTextProgram); - - m_pTextProgram->m_LocPos = m_pTextProgram->GetUniformLoc("gPos"); - m_pTextProgram->m_LocTextureSampler = -1; - m_pTextProgram->m_LocTextSampler = m_pTextProgram->GetUniformLoc("gTextSampler"); - m_pTextProgram->m_LocTextOutlineSampler = m_pTextProgram->GetUniformLoc("gTextOutlineSampler"); - m_pTextProgram->m_LocColor = m_pTextProgram->GetUniformLoc("gVertColor"); - m_pTextProgram->m_LocOutlineColor = m_pTextProgram->GetUniformLoc("gVertOutlineColor"); - m_pTextProgram->m_LocTextureSize = m_pTextProgram->GetUniformLoc("gTextureSize"); - } - InitPrimExProgram(m_pPrimitiveExProgram, &ShaderCompiler, pCommand->m_pStorage, false, false); - InitPrimExProgram(m_pPrimitiveExProgramTextured, &ShaderCompiler, pCommand->m_pStorage, true, false); - InitPrimExProgram(m_pPrimitiveExProgramRotationless, &ShaderCompiler, pCommand->m_pStorage, false, true); - InitPrimExProgram(m_pPrimitiveExProgramTexturedRotationless, &ShaderCompiler, pCommand->m_pStorage, true, true); - { - CGLSL PrimitiveVertexShader; - CGLSL PrimitiveFragmentShader; - PrimitiveVertexShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/spritemulti.vert", GL_VERTEX_SHADER); - PrimitiveFragmentShader.LoadShader(&ShaderCompiler, pCommand->m_pStorage, "shader/spritemulti.frag", GL_FRAGMENT_SHADER); - - m_pSpriteProgramMultiple->CreateProgram(); - m_pSpriteProgramMultiple->AddShader(&PrimitiveVertexShader); - m_pSpriteProgramMultiple->AddShader(&PrimitiveFragmentShader); - m_pSpriteProgramMultiple->LinkProgram(); - - UseProgram(m_pSpriteProgramMultiple); - - m_pSpriteProgramMultiple->m_LocPos = m_pSpriteProgramMultiple->GetUniformLoc("gPos"); - m_pSpriteProgramMultiple->m_LocTextureSampler = m_pSpriteProgramMultiple->GetUniformLoc("gTextureSampler"); - m_pSpriteProgramMultiple->m_LocRSP = m_pSpriteProgramMultiple->GetUniformLoc("gRSP[0]"); - m_pSpriteProgramMultiple->m_LocCenter = m_pSpriteProgramMultiple->GetUniformLoc("gCenter"); - m_pSpriteProgramMultiple->m_LocVertciesColor = m_pSpriteProgramMultiple->GetUniformLoc("gVerticesColor"); - - float Center[2] = {0.f, 0.f}; - m_pSpriteProgramMultiple->SetUniformVec2(m_pSpriteProgramMultiple->m_LocCenter, 1, Center); - } - - m_LastStreamBuffer = 0; - - glGenBuffers(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawBufferID); - glGenVertexArrays(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawVertexID); - glGenBuffers(1, &m_PrimitiveDrawBufferIDTex3D); - glGenVertexArrays(1, &m_PrimitiveDrawVertexIDTex3D); - - m_UsePreinitializedVertexBuffer = g_Config.m_GfxUsePreinitBuffer; - - for(int i = 0; i < MAX_STREAM_BUFFER_COUNT; ++i) - { - glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferID[i]); - glBindVertexArray(m_PrimitiveDrawVertexID[i]); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertex), 0); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertex), (void *)(sizeof(float) * 2)); - glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(CCommandBuffer::SVertex), (void *)(sizeof(float) * 4)); - - if(m_UsePreinitializedVertexBuffer) - glBufferData(GL_ARRAY_BUFFER, sizeof(CCommandBuffer::SVertex) * CCommandBuffer::MAX_VERTICES, NULL, GL_STREAM_DRAW); - - m_LastIndexBufferBound[i] = 0; - } - - glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferIDTex3D); - glBindVertexArray(m_PrimitiveDrawVertexIDTex3D); - glEnableVertexAttribArray(0); - glEnableVertexAttribArray(1); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertexTex3DStream), 0); - glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(CCommandBuffer::SVertexTex3DStream), (void *)(sizeof(float) * 2)); - glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(CCommandBuffer::SVertexTex3DStream), (void *)(sizeof(float) * 2 + sizeof(unsigned char) * 4)); - - if(m_UsePreinitializedVertexBuffer) - glBufferData(GL_ARRAY_BUFFER, sizeof(CCommandBuffer::SVertexTex3DStream) * CCommandBuffer::MAX_VERTICES, NULL, GL_STREAM_DRAW); - - //query the image max size only once - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_MaxTexSize); - - //query maximum of allowed textures - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &m_MaxTextureUnits); - m_TextureSlotBoundToUnit.resize(m_MaxTextureUnits); - for(int i = 0; i < m_MaxTextureUnits; ++i) - { - m_TextureSlotBoundToUnit[i].m_TextureSlot = -1; - m_TextureSlotBoundToUnit[i].m_Is2DArray = false; - } - - glBindVertexArray(0); - glGenBuffers(1, &m_QuadDrawIndexBufferID); - glBindBuffer(GL_COPY_WRITE_BUFFER, m_QuadDrawIndexBufferID); - - unsigned int Indices[CCommandBuffer::MAX_VERTICES / 4 * 6]; - int Primq = 0; - for(int i = 0; i < CCommandBuffer::MAX_VERTICES / 4 * 6; i += 6) - { - Indices[i] = Primq; - Indices[i + 1] = Primq + 1; - Indices[i + 2] = Primq + 2; - Indices[i + 3] = Primq; - Indices[i + 4] = Primq + 2; - Indices[i + 5] = Primq + 3; - Primq += 4; - } - glBufferData(GL_COPY_WRITE_BUFFER, sizeof(unsigned int) * CCommandBuffer::MAX_VERTICES / 4 * 6, Indices, GL_STATIC_DRAW); - - m_CurrentIndicesInBuffer = CCommandBuffer::MAX_VERTICES / 4 * 6; - - m_Textures.resize(CCommandBuffer::MAX_TEXTURES); - - m_ClearColor.r = m_ClearColor.g = m_ClearColor.b = -1.f; - - // fix the alignment to allow even 1byte changes, e.g. for alpha components - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Shutdown(const SCommand_Shutdown *pCommand) -{ - glUseProgram(0); - - m_pPrimitiveProgram->DeleteProgram(); - m_pPrimitiveProgramTextured->DeleteProgram(); - m_pBorderTileProgram->DeleteProgram(); - m_pBorderTileProgramTextured->DeleteProgram(); - m_pBorderTileLineProgram->DeleteProgram(); - m_pBorderTileLineProgramTextured->DeleteProgram(); - m_pQuadProgram->DeleteProgram(); - m_pQuadProgramTextured->DeleteProgram(); - m_pTileProgram->DeleteProgram(); - m_pTileProgramTextured->DeleteProgram(); - m_pPrimitive3DProgram->DeleteProgram(); - m_pPrimitive3DProgramTextured->DeleteProgram(); - m_pTextProgram->DeleteProgram(); - m_pPrimitiveExProgram->DeleteProgram(); - m_pPrimitiveExProgramTextured->DeleteProgram(); - m_pPrimitiveExProgramRotationless->DeleteProgram(); - m_pPrimitiveExProgramTexturedRotationless->DeleteProgram(); - m_pSpriteProgramMultiple->DeleteProgram(); - - //clean up everything - delete m_pPrimitiveProgram; - delete m_pPrimitiveProgramTextured; - delete m_pBorderTileProgram; - delete m_pBorderTileProgramTextured; - delete m_pBorderTileLineProgram; - delete m_pBorderTileLineProgramTextured; - delete m_pQuadProgram; - delete m_pQuadProgramTextured; - delete m_pTileProgram; - delete m_pTileProgramTextured; - delete m_pPrimitive3DProgram; - delete m_pPrimitive3DProgramTextured; - delete m_pTextProgram; - delete m_pPrimitiveExProgram; - delete m_pPrimitiveExProgramTextured; - delete m_pPrimitiveExProgramRotationless; - delete m_pPrimitiveExProgramTexturedRotationless; - delete m_pSpriteProgramMultiple; - - glBindVertexArray(0); - glDeleteBuffers(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawBufferID); - glDeleteBuffers(1, &m_QuadDrawIndexBufferID); - glDeleteVertexArrays(MAX_STREAM_BUFFER_COUNT, m_PrimitiveDrawVertexID); - glDeleteBuffers(1, &m_PrimitiveDrawBufferIDTex3D); - glDeleteVertexArrays(1, &m_PrimitiveDrawVertexIDTex3D); - - for(int i = 0; i < (int)m_Textures.size(); ++i) - { - DestroyTexture(i); - } - - for(size_t i = 0; i < m_BufferContainers.size(); ++i) - { - DestroyBufferContainer(i); - } - - m_BufferContainers.clear(); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) -{ - if(m_UseMultipleTextureUnits) - { - int Slot = pCommand->m_Slot % m_MaxTextureUnits; - //just tell, that we using this texture now - IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); - glActiveTexture(GL_TEXTURE0 + Slot); - glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler); - } - - glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); - - void *pTexData = pCommand->m_pData; - int Width = pCommand->m_Width; - int Height = pCommand->m_Height; - int X = pCommand->m_X; - int Y = pCommand->m_Y; - if(m_Textures[pCommand->m_Slot].m_RescaleCount > 0) - { - for(int i = 0; i < m_Textures[pCommand->m_Slot].m_RescaleCount; ++i) - { - Width >>= 1; - Height >>= 1; - - X /= 2; - Y /= 2; - } - - void *pTmpData = Resize(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); - free(pTexData); - pTexData = pTmpData; - } - - glTexSubImage2D(GL_TEXTURE_2D, 0, X, Y, Width, Height, - TexFormatToNewOpenGLFormat(pCommand->m_Format), GL_UNSIGNED_BYTE, pTexData); - free(pTexData); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) -{ - int Slot = 0; - if(m_UseMultipleTextureUnits) - { - Slot = pCommand->m_Slot % m_MaxTextureUnits; - IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); - glActiveTexture(GL_TEXTURE0 + Slot); - } - glBindTexture(GL_TEXTURE_2D, 0); - glBindSampler(Slot, 0); - m_TextureSlotBoundToUnit[Slot].m_TextureSlot = -1; - m_TextureSlotBoundToUnit[Slot].m_Is2DArray = false; - DestroyTexture(pCommand->m_Slot); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) -{ - int Width = pCommand->m_Width; - int Height = pCommand->m_Height; - void *pTexData = pCommand->m_pData; - - if(pCommand->m_Slot >= (int)m_Textures.size()) - m_Textures.resize(m_Textures.size() * 2); - - // resample if needed - int RescaleCount = 0; - if(pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGBA || pCommand->m_Format == CCommandBuffer::TEXFORMAT_RGB || pCommand->m_Format == CCommandBuffer::TEXFORMAT_ALPHA) - { - if(Width > m_MaxTexSize || Height > m_MaxTexSize) - { - do - { - Width >>= 1; - Height >>= 1; - ++RescaleCount; - } while(Width > m_MaxTexSize || Height > m_MaxTexSize); - - void *pTmpData = Resize(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); - free(pTexData); - pTexData = pTmpData; - } - else if(pCommand->m_Format != CCommandBuffer::TEXFORMAT_ALPHA && (Width > 16 && Height > 16 && (pCommand->m_Flags & CCommandBuffer::TEXFLAG_QUALITY) == 0)) - { - Width >>= 1; - Height >>= 1; - ++RescaleCount; - - void *pTmpData = Resize(pCommand->m_Width, pCommand->m_Height, Width, Height, pCommand->m_Format, static_cast(pCommand->m_pData)); - free(pTexData); - pTexData = pTmpData; - } - } - m_Textures[pCommand->m_Slot].m_Width = Width; - m_Textures[pCommand->m_Slot].m_Height = Height; - m_Textures[pCommand->m_Slot].m_RescaleCount = RescaleCount; - - int Oglformat = TexFormatToNewOpenGLFormat(pCommand->m_Format); - int StoreOglformat = TexFormatToNewOpenGLFormat(pCommand->m_StoreFormat); - - if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_COMPRESSED) - { - switch(StoreOglformat) - { - case GL_RGB: StoreOglformat = GL_COMPRESSED_RGB; break; - // COMPRESSED_ALPHA is deprecated, so use different single channel format. - case GL_RED: StoreOglformat = GL_COMPRESSED_RED; break; - case GL_RGBA: StoreOglformat = GL_COMPRESSED_RGBA; break; - default: StoreOglformat = GL_COMPRESSED_RGBA; - } - } - int Slot = 0; - if(m_UseMultipleTextureUnits) - { - Slot = pCommand->m_Slot % m_MaxTextureUnits; - //just tell, that we using this texture now - IsAndUpdateTextureSlotBound(Slot, pCommand->m_Slot); - glActiveTexture(GL_TEXTURE0 + Slot); - m_TextureSlotBoundToUnit[Slot].m_TextureSlot = -1; - m_TextureSlotBoundToUnit[Slot].m_Is2DArray = false; - } - - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) - { - glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex); - glBindTexture(GL_TEXTURE_2D, m_Textures[pCommand->m_Slot].m_Tex); - - glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler); - glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler); - } - - if(Oglformat == GL_RED) - { - //Bind the texture 2D. - GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - StoreOglformat = GL_R8; - } - - if(pCommand->m_Flags & CCommandBuffer::TEXFLAG_NOMIPMAPS) - { - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - } - else - { - if((pCommand->m_Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) - { - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - if(m_OpenGLTextureLodBIAS != 0) - glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); - //prevent mipmap display bugs, when zooming out far - if(Width >= 1024 && Height >= 1024) - { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5.f); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 5); - } - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - glGenerateMipmap(GL_TEXTURE_2D); - } - - if((pCommand->m_Flags & (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE | CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER)) != 0) - { - glGenTextures(1, &m_Textures[pCommand->m_Slot].m_Tex2DArray); - glBindTexture(GL_TEXTURE_2D_ARRAY, m_Textures[pCommand->m_Slot].m_Tex2DArray); - - glGenSamplers(1, &m_Textures[pCommand->m_Slot].m_Sampler2DArray); - glBindSampler(Slot, m_Textures[pCommand->m_Slot].m_Sampler2DArray); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glSamplerParameteri(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - if(m_OpenGLTextureLodBIAS != 0) - glSamplerParameterf(m_Textures[pCommand->m_Slot].m_Sampler2DArray, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); - - int ImageColorChannels = TexFormatToImageColorChannelCount(pCommand->m_Format); - - uint8_t *p3DImageData = NULL; - - bool IsSingleLayer = (pCommand->m_Flags & CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER) != 0; - - if(!IsSingleLayer) - p3DImageData = (uint8_t *)malloc((size_t)ImageColorChannels * Width * Height); - int Image3DWidth, Image3DHeight; - - int ConvertWidth = Width; - int ConvertHeight = Height; - - if(!IsSingleLayer) - { - if(ConvertWidth == 0 || (ConvertWidth % 16) != 0 || ConvertHeight == 0 || (ConvertHeight % 16) != 0) - { - dbg_msg("gfx", "3D/2D array texture was resized"); - int NewWidth = maximum(HighestBit(ConvertWidth), 16); - int NewHeight = maximum(HighestBit(ConvertHeight), 16); - uint8_t *pNewTexData = (uint8_t *)Resize(ConvertWidth, ConvertHeight, NewWidth, NewHeight, pCommand->m_Format, (const uint8_t *)pTexData); - - ConvertWidth = NewWidth; - ConvertHeight = NewHeight; - - free(pTexData); - pTexData = pNewTexData; - } - } - - if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) - { - if(IsSingleLayer) - { - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); - } - else - { - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); - } - glGenerateMipmap(GL_TEXTURE_2D_ARRAY); - - if(StoreOglformat == GL_R8) - { - //Bind the texture 2D. - GLint swizzleMask[] = {GL_ONE, GL_ONE, GL_ONE, GL_RED}; - glTexParameteriv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - } - - if(!IsSingleLayer) - free(p3DImageData); - } - } - - // This is the initial value for the wrap modes - m_Textures[pCommand->m_Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; - - // calculate memory usage - m_Textures[pCommand->m_Slot].m_MemSize = Width * Height * pCommand->m_PixelSize; - while(Width > 2 && Height > 2) - { - Width >>= 1; - Height >>= 1; - m_Textures[pCommand->m_Slot].m_MemSize += Width * Height * pCommand->m_PixelSize; - } - m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_Textures[pCommand->m_Slot].m_MemSize, std::memory_order_relaxed); - - free(pTexData); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) -{ - if(pCommand->m_Color.r != m_ClearColor.r || pCommand->m_Color.g != m_ClearColor.g || pCommand->m_Color.b != m_ClearColor.b) - { - glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); - m_ClearColor = pCommand->m_Color; - } - glClear(GL_COLOR_BUFFER_BIT); -} - -void CCommandProcessorFragment_OpenGL3_3::UploadStreamBufferData(unsigned int PrimitiveType, const void *pVertices, size_t VertSize, unsigned int PrimitiveCount, bool AsTex3D) -{ - int Count = 0; - switch(PrimitiveType) - { - case CCommandBuffer::PRIMTYPE_LINES: - Count = PrimitiveCount * 2; - break; - case CCommandBuffer::PRIMTYPE_TRIANGLES: - Count = PrimitiveCount * 3; - break; - case CCommandBuffer::PRIMTYPE_QUADS: - Count = PrimitiveCount * 4; - break; - default: - return; - }; - - if(AsTex3D) - glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferIDTex3D); - else - glBindBuffer(GL_ARRAY_BUFFER, m_PrimitiveDrawBufferID[m_LastStreamBuffer]); - - if(!m_UsePreinitializedVertexBuffer) - glBufferData(GL_ARRAY_BUFFER, VertSize * Count, pVertices, GL_STREAM_DRAW); - else - { - // This is better for some iGPUs. Probably due to not initializing a new buffer in the system memory again and again...(driver dependent) - void *pData = glMapBufferRange(GL_ARRAY_BUFFER, 0, VertSize * Count, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT); - - mem_copy(pData, pVertices, VertSize * Count); - - glUnmapBuffer(GL_ARRAY_BUFFER); - } -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) -{ - CGLSLTWProgram *pProgram = m_pPrimitiveProgram; - if(IsTexturedState(pCommand->m_State)) - pProgram = m_pPrimitiveProgramTextured; - UseProgram(pProgram); - SetState(pCommand->m_State, pProgram); - - UploadStreamBufferData(pCommand->m_PrimType, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertex), pCommand->m_PrimCount); - - glBindVertexArray(m_PrimitiveDrawVertexID[m_LastStreamBuffer]); - - switch(pCommand->m_PrimType) - { - // We don't support GL_QUADS due to core profile - case CCommandBuffer::PRIMTYPE_LINES: - glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); - break; - case CCommandBuffer::PRIMTYPE_TRIANGLES: - glDrawArrays(GL_TRIANGLES, 0, pCommand->m_PrimCount * 3); - break; - case CCommandBuffer::PRIMTYPE_QUADS: - if(m_LastIndexBufferBound[m_LastStreamBuffer] != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - m_LastIndexBufferBound[m_LastStreamBuffer] = m_QuadDrawIndexBufferID; - } - glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount * 6, GL_UNSIGNED_INT, 0); - break; - default: - dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); - }; - - m_LastStreamBuffer = (m_LastStreamBuffer + 1 >= MAX_STREAM_BUFFER_COUNT ? 0 : m_LastStreamBuffer + 1); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) -{ - CGLSLPrimitiveProgram *pProg = m_pPrimitive3DProgram; - if(IsTexturedState(pCommand->m_State)) - pProg = m_pPrimitive3DProgramTextured; - UseProgram(pProg); - SetState(pCommand->m_State, pProg, true); - - UploadStreamBufferData(pCommand->m_PrimType, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertexTex3DStream), pCommand->m_PrimCount, true); - - glBindVertexArray(m_PrimitiveDrawVertexIDTex3D); - - switch(pCommand->m_PrimType) - { - // We don't support GL_QUADS due to core profile - case CCommandBuffer::PRIMTYPE_LINES: - glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount * 2); - break; - case CCommandBuffer::PRIMTYPE_QUADS: - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount * 6, GL_UNSIGNED_INT, 0); - break; - default: - dbg_msg("render", "unknown primtype %d\n", pCommand->m_PrimType); - }; -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand) -{ - // fetch image data - GLint aViewport[4] = {0, 0, 0, 0}; - glGetIntegerv(GL_VIEWPORT, aViewport); - - int w = aViewport[2]; - int h = aViewport[3]; - - // we allocate one more row to use when we are flipping the texture - unsigned char *pPixelData = (unsigned char *)malloc((size_t)w * (h + 1) * 3); - unsigned char *pTempRow = pPixelData + w * h * 3; - - // fetch the pixels - GLint Alignment; - glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pPixelData); - glPixelStorei(GL_PACK_ALIGNMENT, Alignment); - - // flip the pixel because opengl works from bottom left corner - for(int y = 0; y < h / 2; y++) - { - mem_copy(pTempRow, pPixelData + y * w * 3, w * 3); - mem_copy(pPixelData + y * w * 3, pPixelData + (h - y - 1) * w * 3, w * 3); - mem_copy(pPixelData + (h - y - 1) * w * 3, pTempRow, w * 3); - } - - // fill in the information - pCommand->m_pImage->m_Width = w; - pCommand->m_pImage->m_Height = h; - pCommand->m_pImage->m_Format = CImageInfo::FORMAT_RGB; - pCommand->m_pImage->m_pData = pPixelData; -} - -void CCommandProcessorFragment_OpenGL3_3::DestroyBufferContainer(int Index, bool DeleteBOs) -{ - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID != 0) - glDeleteVertexArrays(1, &BufferContainer.m_VertArrayID); - - // all buffer objects can deleted automatically, so the program doesn't need to deal with them (e.g. causing crashes because of driver bugs) - if(DeleteBOs) - { - for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) - { - int VertBufferID = BufferContainer.m_ContainerInfo.m_Attributes[i].m_VertBufferBindingIndex; - if(VertBufferID != -1) - { - for(auto &Attribute : BufferContainer.m_ContainerInfo.m_Attributes) - { - // set all equal ids to zero to not double delete - if(VertBufferID == Attribute.m_VertBufferBindingIndex) - { - Attribute.m_VertBufferBindingIndex = -1; - } - } - - glDeleteBuffers(1, &m_BufferObjectIndices[VertBufferID]); - } - } - } - - BufferContainer.m_LastIndexBufferBound = 0; - BufferContainer.m_ContainerInfo.m_Attributes.clear(); -} - -void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesCount) -{ - if(NewIndicesCount <= m_CurrentIndicesInBuffer) - return; - unsigned int AddCount = NewIndicesCount - m_CurrentIndicesInBuffer; - unsigned int *Indices = new unsigned int[AddCount]; - int Primq = (m_CurrentIndicesInBuffer / 6) * 4; - for(unsigned int i = 0; i < AddCount; i += 6) - { - Indices[i] = Primq; - Indices[i + 1] = Primq + 1; - Indices[i + 2] = Primq + 2; - Indices[i + 3] = Primq; - Indices[i + 4] = Primq + 2; - Indices[i + 5] = Primq + 3; - Primq += 4; - } - - glBindBuffer(GL_COPY_READ_BUFFER, m_QuadDrawIndexBufferID); - GLuint NewIndexBufferID; - glGenBuffers(1, &NewIndexBufferID); - glBindBuffer(GL_COPY_WRITE_BUFFER, NewIndexBufferID); - GLsizeiptr size = sizeof(unsigned int); - glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)NewIndicesCount * size, NULL, GL_STATIC_DRAW); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, (GLsizeiptr)m_CurrentIndicesInBuffer * size); - glBufferSubData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)m_CurrentIndicesInBuffer * size, (GLsizeiptr)AddCount * size, Indices); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - - glDeleteBuffers(1, &m_QuadDrawIndexBufferID); - m_QuadDrawIndexBufferID = NewIndexBufferID; - - for(unsigned int &i : m_LastIndexBufferBound) - i = 0; - for(auto &BufferContainer : m_BufferContainers) - { - BufferContainer.m_LastIndexBufferBound = 0; - } - - m_CurrentIndicesInBuffer = NewIndicesCount; - delete[] Indices; -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - //create necessary space - if((size_t)Index >= m_BufferObjectIndices.size()) - { - for(int i = m_BufferObjectIndices.size(); i < Index + 1; ++i) - { - m_BufferObjectIndices.push_back(0); - } - } - - GLuint VertBufferID = 0; - - glGenBuffers(1, &VertBufferID); - glBindBuffer(GL_COPY_WRITE_BUFFER, VertBufferID); - glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); - - m_BufferObjectIndices[Index] = VertBufferID; - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - - glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[Index]); - glBufferData(GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_DataSize), pUploadData, GL_STATIC_DRAW); - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) -{ - void *pUploadData = pCommand->m_pUploadData; - int Index = pCommand->m_BufferIndex; - - glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[Index]); - glBufferSubData(GL_COPY_WRITE_BUFFER, (GLintptr)(pCommand->m_pOffset), (GLsizeiptr)(pCommand->m_DataSize), pUploadData); - - if(pCommand->m_DeletePointer) - free(pUploadData); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) -{ - int WriteIndex = pCommand->m_WriteBufferIndex; - int ReadIndex = pCommand->m_ReadBufferIndex; - - glBindBuffer(GL_COPY_WRITE_BUFFER, m_BufferObjectIndices[WriteIndex]); - glBindBuffer(GL_COPY_READ_BUFFER, m_BufferObjectIndices[ReadIndex]); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, (GLsizeiptr)(pCommand->m_pReadOffset), (GLsizeiptr)(pCommand->m_pWriteOffset), (GLsizeiptr)pCommand->m_CopySize); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) -{ - int Index = pCommand->m_BufferIndex; - - glDeleteBuffers(1, &m_BufferObjectIndices[Index]); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //create necessary space - if((size_t)Index >= m_BufferContainers.size()) - { - for(int i = m_BufferContainers.size(); i < Index + 1; ++i) - { - SBufferContainer Container; - Container.m_ContainerInfo.m_Stride = 0; - m_BufferContainers.push_back(Container); - } - } - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - glGenVertexArrays(1, &BufferContainer.m_VertArrayID); - glBindVertexArray(BufferContainer.m_VertArrayID); - - BufferContainer.m_LastIndexBufferBound = 0; - - for(int i = 0; i < pCommand->m_AttrCount; ++i) - { - glEnableVertexAttribArray((GLuint)i); - - glBindBuffer(GL_ARRAY_BUFFER, m_BufferObjectIndices[pCommand->m_Attributes[i].m_VertBufferBindingIndex]); - - SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; - - if(Attr.m_FuncType == 0) - glVertexAttribPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, (GLboolean)Attr.m_Normalized, pCommand->m_Stride, Attr.m_pOffset); - else if(Attr.m_FuncType == 1) - glVertexAttribIPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, pCommand->m_Stride, Attr.m_pOffset); - - BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); - } - - BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) -{ - SBufferContainer &BufferContainer = m_BufferContainers[pCommand->m_BufferContainerIndex]; - - glBindVertexArray(BufferContainer.m_VertArrayID); - - //disable all old attributes - for(size_t i = 0; i < BufferContainer.m_ContainerInfo.m_Attributes.size(); ++i) - { - glDisableVertexAttribArray((GLuint)i); - } - BufferContainer.m_ContainerInfo.m_Attributes.clear(); - - for(int i = 0; i < pCommand->m_AttrCount; ++i) - { - glEnableVertexAttribArray((GLuint)i); - - glBindBuffer(GL_ARRAY_BUFFER, m_BufferObjectIndices[pCommand->m_Attributes[i].m_VertBufferBindingIndex]); - SBufferContainerInfo::SAttribute &Attr = pCommand->m_Attributes[i]; - if(Attr.m_FuncType == 0) - glVertexAttribPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, Attr.m_Normalized, pCommand->m_Stride, Attr.m_pOffset); - else if(Attr.m_FuncType == 1) - glVertexAttribIPointer((GLuint)i, Attr.m_DataTypeCount, Attr.m_Type, pCommand->m_Stride, Attr.m_pOffset); - - BufferContainer.m_ContainerInfo.m_Attributes.push_back(Attr); - } - - BufferContainer.m_ContainerInfo.m_Stride = pCommand->m_Stride; -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) -{ - DestroyBufferContainer(pCommand->m_BufferContainerIndex, pCommand->m_DestroyAllBO); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) -{ - if(pCommand->m_RequiredIndicesNum > m_CurrentIndicesInBuffer) - AppendIndices(pCommand->m_RequiredIndicesNum); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - CGLSLTileProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pBorderTileProgramTextured; - } - else - pProgram = m_pBorderTileProgram; - UseProgram(pProgram); - - SetState(pCommand->m_State, pProgram, true); - pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); - - pProgram->SetUniformVec2(pProgram->m_LocOffset, 1, (float *)&pCommand->m_Offset); - pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float *)&pCommand->m_Dir); - pProgram->SetUniform(pProgram->m_LocJumpIndex, (int)pCommand->m_JumpIndex); - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - CGLSLTileProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pBorderTileLineProgramTextured; - } - else - pProgram = m_pBorderTileLineProgram; - UseProgram(pProgram); - - SetState(pCommand->m_State, pProgram, true); - pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); - pProgram->SetUniformVec2(pProgram->m_LocOffset, 1, (float *)&pCommand->m_Offset); - pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float *)&pCommand->m_Dir); - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_IndexDrawNum, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - if(pCommand->m_IndicesDrawNum == 0) - { - return; //nothing to draw - } - - CGLSLTileProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pTileProgramTextured; - } - else - pProgram = m_pTileProgram; - - UseProgram(pProgram); - - SetState(pCommand->m_State, pProgram, true); - pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float *)&pCommand->m_Color); - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - for(int i = 0; i < pCommand->m_IndicesDrawNum; ++i) - { - glDrawElements(GL_TRIANGLES, pCommand->m_pDrawCount[i], GL_UNSIGNED_INT, pCommand->m_pIndicesOffsets[i]); - } -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - if(pCommand->m_QuadNum == 0) - { - return; //nothing to draw - } - - CGLSLQuadProgram *pProgram = NULL; - if(IsTexturedState(pCommand->m_State)) - { - pProgram = m_pQuadProgramTextured; - } - else - pProgram = m_pQuadProgram; - - UseProgram(pProgram); - SetState(pCommand->m_State, pProgram); - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - - int QuadsLeft = pCommand->m_QuadNum; - size_t QuadOffset = 0; - // the extra offset is not related to the information from the command, but an actual offset in the buffer - size_t QuadOffsetExtra = pCommand->m_QuadOffset; - - vec4 aColors[m_MaxQuadsPossible]; - vec2 aOffsets[m_MaxQuadsPossible]; - float aRotations[m_MaxQuadsPossible]; - - while(QuadsLeft > 0) - { - int ActualQuadCount = minimum(QuadsLeft, m_MaxQuadsAtOnce); - - for(size_t i = 0; i < (size_t)ActualQuadCount; ++i) - { - mem_copy(&aColors[i], pCommand->m_pQuadInfo[i + QuadOffset].m_aColor, sizeof(vec4)); - mem_copy(&aOffsets[i], pCommand->m_pQuadInfo[i + QuadOffset].m_aOffsets, sizeof(vec2)); - mem_copy(&aRotations[i], &pCommand->m_pQuadInfo[i + QuadOffset].m_Rotation, sizeof(float)); - } - - pProgram->SetUniformVec4(pProgram->m_LocColors, ActualQuadCount, (float *)aColors); - pProgram->SetUniformVec2(pProgram->m_LocOffsets, ActualQuadCount, (float *)aOffsets); - pProgram->SetUniform(pProgram->m_LocRotations, ActualQuadCount, (float *)aRotations); - pProgram->SetUniform(pProgram->m_LocQuadOffset, (int)(QuadOffset + QuadOffsetExtra)); - glDrawElements(GL_TRIANGLES, ActualQuadCount * 6, GL_UNSIGNED_INT, (void *)((QuadOffset + QuadOffsetExtra) * 6 * sizeof(unsigned int))); - - QuadsLeft -= ActualQuadCount; - QuadOffset += (size_t)ActualQuadCount; - } -} - -void CCommandProcessorFragment_OpenGL3_3::RenderText(const CCommandBuffer::SState &State, int DrawNum, int TextTextureIndex, int TextOutlineTextureIndex, int TextureSize, const float *pTextColor, const float *pTextOutlineColor) -{ - if(DrawNum == 0) - { - return; //nothing to draw - } - - UseProgram(m_pTextProgram); - - int SlotText = 0; - int SlotTextOutline = 0; - - if(m_UseMultipleTextureUnits) - { - SlotText = TextTextureIndex % m_MaxTextureUnits; - SlotTextOutline = TextOutlineTextureIndex % m_MaxTextureUnits; - if(SlotText == SlotTextOutline) - SlotTextOutline = (TextOutlineTextureIndex + 1) % m_MaxTextureUnits; - - if(!IsAndUpdateTextureSlotBound(SlotText, TextTextureIndex)) - { - glActiveTexture(GL_TEXTURE0 + SlotText); - glBindTexture(GL_TEXTURE_2D, m_Textures[TextTextureIndex].m_Tex); - glBindSampler(SlotText, m_Textures[TextTextureIndex].m_Sampler); - } - if(!IsAndUpdateTextureSlotBound(SlotTextOutline, TextOutlineTextureIndex)) - { - glActiveTexture(GL_TEXTURE0 + SlotTextOutline); - glBindTexture(GL_TEXTURE_2D, m_Textures[TextOutlineTextureIndex].m_Tex); - glBindSampler(SlotTextOutline, m_Textures[TextOutlineTextureIndex].m_Sampler); - } - } - else - { - SlotText = 0; - SlotTextOutline = 1; - glBindTexture(GL_TEXTURE_2D, m_Textures[TextTextureIndex].m_Tex); - glBindSampler(SlotText, m_Textures[TextTextureIndex].m_Sampler); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m_Textures[TextOutlineTextureIndex].m_Tex); - glBindSampler(SlotTextOutline, m_Textures[TextOutlineTextureIndex].m_Sampler); - glActiveTexture(GL_TEXTURE0); - } - - if(m_pTextProgram->m_LastTextSampler != SlotText) - { - m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextSampler, SlotText); - m_pTextProgram->m_LastTextSampler = SlotText; - } - - if(m_pTextProgram->m_LastTextOutlineSampler != SlotTextOutline) - { - m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextOutlineSampler, SlotTextOutline); - m_pTextProgram->m_LastTextOutlineSampler = SlotTextOutline; - } - - SetState(State, m_pTextProgram); - - if(m_pTextProgram->m_LastTextureSize != TextureSize) - { - m_pTextProgram->SetUniform(m_pTextProgram->m_LocTextureSize, (float)TextureSize); - m_pTextProgram->m_LastTextureSize = TextureSize; - } - - if(m_pTextProgram->m_LastOutlineColor[0] != pTextOutlineColor[0] || m_pTextProgram->m_LastOutlineColor[1] != pTextOutlineColor[1] || m_pTextProgram->m_LastOutlineColor[2] != pTextOutlineColor[2] || m_pTextProgram->m_LastOutlineColor[3] != pTextOutlineColor[3]) - { - m_pTextProgram->SetUniformVec4(m_pTextProgram->m_LocOutlineColor, 1, (float *)pTextOutlineColor); - m_pTextProgram->m_LastOutlineColor[0] = pTextOutlineColor[0]; - m_pTextProgram->m_LastOutlineColor[1] = pTextOutlineColor[1]; - m_pTextProgram->m_LastOutlineColor[2] = pTextOutlineColor[2]; - m_pTextProgram->m_LastOutlineColor[3] = pTextOutlineColor[3]; - } - - if(m_pTextProgram->m_LastColor[0] != pTextColor[0] || m_pTextProgram->m_LastColor[1] != pTextColor[1] || m_pTextProgram->m_LastColor[2] != pTextColor[2] || m_pTextProgram->m_LastColor[3] != pTextColor[3]) - { - m_pTextProgram->SetUniformVec4(m_pTextProgram->m_LocColor, 1, (float *)pTextColor); - m_pTextProgram->m_LastColor[0] = pTextColor[0]; - m_pTextProgram->m_LastColor[1] = pTextColor[1]; - m_pTextProgram->m_LastColor[2] = pTextColor[2]; - m_pTextProgram->m_LastColor[3] = pTextColor[3]; - } - - glDrawElements(GL_TRIANGLES, DrawNum, GL_UNSIGNED_INT, (void *)(0)); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderText(const CCommandBuffer::SCommand_RenderText *pCommand) -{ - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - - RenderText(pCommand->m_State, pCommand->m_DrawNum, pCommand->m_TextTextureIndex, pCommand->m_TextOutlineTextureIndex, pCommand->m_TextureSize, pCommand->m_aTextColor, pCommand->m_aTextOutlineColor); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderTextStream(const CCommandBuffer::SCommand_RenderTextStream *pCommand) -{ - if(pCommand->m_PrimCount == 0) - { - return; //nothing to draw - } - - UploadStreamBufferData(CCommandBuffer::PRIMTYPE_QUADS, pCommand->m_pVertices, sizeof(CCommandBuffer::SVertex), pCommand->m_PrimCount); - - glBindVertexArray(m_PrimitiveDrawVertexID[m_LastStreamBuffer]); - if(m_LastIndexBufferBound[m_LastStreamBuffer] != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - m_LastIndexBufferBound[m_LastStreamBuffer] = m_QuadDrawIndexBufferID; - } - - float aTextColor[4] = {1.f, 1.f, 1.f, 1.f}; - - RenderText(pCommand->m_State, pCommand->m_PrimCount * 6, pCommand->m_TextTextureIndex, pCommand->m_TextOutlineTextureIndex, pCommand->m_TextureSize, aTextColor, pCommand->m_aTextOutlineColor); - - m_LastStreamBuffer = (m_LastStreamBuffer + 1 >= MAX_STREAM_BUFFER_COUNT ? 0 : m_LastStreamBuffer + 1); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainer(const CCommandBuffer::SCommand_RenderQuadContainer *pCommand) -{ - if(pCommand->m_DrawNum == 0) - { - return; //nothing to draw - } - - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - - CGLSLTWProgram *pProgram = m_pPrimitiveProgram; - if(IsTexturedState(pCommand->m_State)) - pProgram = m_pPrimitiveProgramTextured; - UseProgram(pProgram); - SetState(pCommand->m_State, pProgram); - - glDrawElements(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) -{ - if(pCommand->m_DrawNum == 0) - { - return; //nothing to draw - } - - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - - CGLSLPrimitiveExProgram *pProgram = m_pPrimitiveExProgramRotationless; - if(IsTexturedState(pCommand->m_State)) - { - if(pCommand->m_Rotation != 0.0f) - pProgram = m_pPrimitiveExProgramTextured; - else - pProgram = m_pPrimitiveExProgramTexturedRotationless; - } - else - { - if(pCommand->m_Rotation != 0.0f) - pProgram = m_pPrimitiveExProgram; - } - - UseProgram(pProgram); - SetState(pCommand->m_State, pProgram); - - if(pCommand->m_Rotation != 0.0f && (pProgram->m_LastCenter[0] != pCommand->m_Center.x || pProgram->m_LastCenter[1] != pCommand->m_Center.y)) - { - pProgram->SetUniformVec2(pProgram->m_LocCenter, 1, (float *)&pCommand->m_Center); - pProgram->m_LastCenter[0] = pCommand->m_Center.x; - pProgram->m_LastCenter[1] = pCommand->m_Center.y; - } - - if(pProgram->m_LastRotation != pCommand->m_Rotation) - { - pProgram->SetUniform(pProgram->m_LocRotation, pCommand->m_Rotation); - pProgram->m_LastRotation = pCommand->m_Rotation; - } - - if(pProgram->m_LastVertciesColor[0] != pCommand->m_VertexColor.r || pProgram->m_LastVertciesColor[1] != pCommand->m_VertexColor.g || pProgram->m_LastVertciesColor[2] != pCommand->m_VertexColor.b || pProgram->m_LastVertciesColor[3] != pCommand->m_VertexColor.a) - { - pProgram->SetUniformVec4(pProgram->m_LocVertciesColor, 1, (float *)&pCommand->m_VertexColor); - pProgram->m_LastVertciesColor[0] = pCommand->m_VertexColor.r; - pProgram->m_LastVertciesColor[1] = pCommand->m_VertexColor.g; - pProgram->m_LastVertciesColor[2] = pCommand->m_VertexColor.b; - pProgram->m_LastVertciesColor[3] = pCommand->m_VertexColor.a; - } - - glDrawElements(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset); -} - -void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) -{ - if(pCommand->m_DrawNum == 0 || pCommand->m_DrawCount == 0) - { - return; //nothing to draw - } - - int Index = pCommand->m_BufferContainerIndex; - //if space not there return - if((size_t)Index >= m_BufferContainers.size()) - return; - - SBufferContainer &BufferContainer = m_BufferContainers[Index]; - if(BufferContainer.m_VertArrayID == 0) - return; - - glBindVertexArray(BufferContainer.m_VertArrayID); - if(BufferContainer.m_LastIndexBufferBound != m_QuadDrawIndexBufferID) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); - BufferContainer.m_LastIndexBufferBound = m_QuadDrawIndexBufferID; - } - - UseProgram(m_pSpriteProgramMultiple); - SetState(pCommand->m_State, m_pSpriteProgramMultiple); - - if((m_pSpriteProgramMultiple->m_LastCenter[0] != pCommand->m_Center.x || m_pSpriteProgramMultiple->m_LastCenter[1] != pCommand->m_Center.y)) - { - m_pSpriteProgramMultiple->SetUniformVec2(m_pSpriteProgramMultiple->m_LocCenter, 1, (float *)&pCommand->m_Center); - m_pSpriteProgramMultiple->m_LastCenter[0] = pCommand->m_Center.x; - m_pSpriteProgramMultiple->m_LastCenter[1] = pCommand->m_Center.y; - } - - if(m_pSpriteProgramMultiple->m_LastVertciesColor[0] != pCommand->m_VertexColor.r || m_pSpriteProgramMultiple->m_LastVertciesColor[1] != pCommand->m_VertexColor.g || m_pSpriteProgramMultiple->m_LastVertciesColor[2] != pCommand->m_VertexColor.b || m_pSpriteProgramMultiple->m_LastVertciesColor[3] != pCommand->m_VertexColor.a) - { - m_pSpriteProgramMultiple->SetUniformVec4(m_pSpriteProgramMultiple->m_LocVertciesColor, 1, (float *)&pCommand->m_VertexColor); - m_pSpriteProgramMultiple->m_LastVertciesColor[0] = pCommand->m_VertexColor.r; - m_pSpriteProgramMultiple->m_LastVertciesColor[1] = pCommand->m_VertexColor.g; - m_pSpriteProgramMultiple->m_LastVertciesColor[2] = pCommand->m_VertexColor.b; - m_pSpriteProgramMultiple->m_LastVertciesColor[3] = pCommand->m_VertexColor.a; - } - - int DrawCount = pCommand->m_DrawCount; - size_t RenderOffset = 0; - - // 4 for the center (always use vec4) and 16 for the matrix(just to be sure), 4 for the sampler and vertex color - const int RSPCount = 256 - 4 - 16 - 8; - - while(DrawCount > 0) - { - int UniformCount = (DrawCount > RSPCount ? RSPCount : DrawCount); - - m_pSpriteProgramMultiple->SetUniformVec4(m_pSpriteProgramMultiple->m_LocRSP, UniformCount, (float *)(pCommand->m_pRenderInfo + RenderOffset)); - - glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_DrawNum, GL_UNSIGNED_INT, pCommand->m_pOffset, UniformCount); - - RenderOffset += RSPCount; - DrawCount -= RSPCount; - } -} - -// ------------ CCommandProcessorFragment_SDL - -static void ParseVersionString(const char *pStr, int &VersionMajor, int &VersionMinor, int &VersionPatch) -{ - if(pStr) - { - char aCurNumberStr[32]; - size_t CurNumberStrLen = 0; - size_t TotalNumbersPassed = 0; - int aNumbers[3] = {0}; - bool LastWasNumber = false; - while(*pStr && TotalNumbersPassed < 3) - { - if(*pStr >= '0' && *pStr <= '9') - { - aCurNumberStr[CurNumberStrLen++] = (char)*pStr; - LastWasNumber = true; - } - else if(LastWasNumber && (*pStr == '.' || *pStr == ' ' || *pStr == '\0')) - { - int CurNumber = 0; - if(CurNumberStrLen > 0) - { - aCurNumberStr[CurNumberStrLen] = 0; - CurNumber = str_toint(aCurNumberStr); - aNumbers[TotalNumbersPassed++] = CurNumber; - CurNumberStrLen = 0; - } - - LastWasNumber = false; - - if(*pStr != '.') - break; - } - else - { - break; - } - - ++pStr; - } - - VersionMajor = aNumbers[0]; - VersionMinor = aNumbers[1]; - VersionPatch = aNumbers[2]; - } -} - -static const char *GetGLErrorName(GLenum Type) -{ - if(Type == GL_DEBUG_TYPE_ERROR) - return "ERROR"; - else if(Type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) - return "DEPRECATED BEHAVIOR"; - else if(Type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) - return "UNDEFINED BEHAVIOR"; - else if(Type == GL_DEBUG_TYPE_PORTABILITY) - return "PORTABILITY"; - else if(Type == GL_DEBUG_TYPE_PERFORMANCE) - return "PERFORMANCE"; - else if(Type == GL_DEBUG_TYPE_OTHER) - return "OTHER"; - else if(Type == GL_DEBUG_TYPE_MARKER) - return "MARKER"; - else if(Type == GL_DEBUG_TYPE_PUSH_GROUP) - return "PUSH_GROUP"; - else if(Type == GL_DEBUG_TYPE_POP_GROUP) - return "POP_GROUP"; - return "UNKNOWN"; -}; - -static const char *GetGLSeverity(GLenum Type) -{ - if(Type == GL_DEBUG_SEVERITY_HIGH) - return "high"; // All OpenGL Errors, shader compilation/linking errors, or highly-dangerous undefined behavior - else if(Type == GL_DEBUG_SEVERITY_MEDIUM) - return "medium"; // Major performance warnings, shader compilation/linking warnings, or the use of deprecated functionality - else if(Type == GL_DEBUG_SEVERITY_LOW) - return "low"; // Redundant state change performance warning, or unimportant undefined behavior - else if(Type == GL_DEBUG_SEVERITY_NOTIFICATION) - return "notification"; // Anything that isn't an error or performance issue. - - return "unknown"; -} - -static void GLAPIENTRY -GfxOpenGLMessageCallback(GLenum source, - GLenum type, - GLuint id, - GLenum severity, - GLsizei length, - const GLchar *message, - const void *userParam) -{ - dbg_msg("gfx", "[%s] (importance: %s) %s", GetGLErrorName(type), GetGLSeverity(severity), message); -} - -void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand) -{ - m_GLContext = pCommand->m_GLContext; - m_pWindow = pCommand->m_pWindow; - SDL_GL_MakeCurrent(m_pWindow, m_GLContext); - - // set some default settings - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - - glAlphaFunc(GL_GREATER, 0); - glEnable(GL_ALPHA_TEST); - glDepthMask(0); - - if(g_Config.m_DbgGfx) - { - if(GLEW_KHR_debug || GLEW_ARB_debug_output) - { - // During init, enable debug output - if(GLEW_KHR_debug) - { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallback(GfxOpenGLMessageCallback, 0); - } - else if(GLEW_ARB_debug_output) - { - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); - glDebugMessageCallbackARB(GfxOpenGLMessageCallback, 0); - } - dbg_msg("gfx", "Enabled OpenGL debug mode"); - } - else - dbg_msg("gfx", "Requested OpenGL debug mode, but the driver does not support the required extension"); - } - - const char *pVendorString = (const char *)glGetString(GL_VENDOR); - dbg_msg("opengl", "Vendor string: %s", pVendorString); - - // check what this context can do - const char *pVersionString = (const char *)glGetString(GL_VERSION); - dbg_msg("opengl", "Version string: %s", pVersionString); - // parse version string - ParseVersionString(pVersionString, pCommand->m_pCapabilities->m_ContextMajor, pCommand->m_pCapabilities->m_ContextMinor, pCommand->m_pCapabilities->m_ContextPatch); - - *pCommand->m_pInitError = 0; - - int BlocklistMajor = -1, BlocklistMinor = -1, BlocklistPatch = -1; - const char *pErrString = ParseBlocklistDriverVersions(pVendorString, pVersionString, BlocklistMajor, BlocklistMinor, BlocklistPatch); - //if the driver is buggy, and the requested GL version is the default, fallback - if(pErrString != NULL && pCommand->m_RequestedMajor == 3 && pCommand->m_RequestedMinor == 0 && pCommand->m_RequestedPatch == 0) - { - // if not already in the error state, set the GL version - if(g_Config.m_GfxDriverIsBlocked == 0) - { - // fallback to known good GL version - pCommand->m_pCapabilities->m_ContextMajor = BlocklistMajor; - pCommand->m_pCapabilities->m_ContextMinor = BlocklistMinor; - pCommand->m_pCapabilities->m_ContextPatch = BlocklistPatch; - - // set backend error string - *pCommand->m_pErrStringPtr = pErrString; - *pCommand->m_pInitError = -2; - - g_Config.m_GfxDriverIsBlocked = 1; - } - } - // if the driver was in a blocked error state, but is not anymore, reset all config variables - else if(pErrString == NULL && g_Config.m_GfxDriverIsBlocked == 1) - { - pCommand->m_pCapabilities->m_ContextMajor = 3; - pCommand->m_pCapabilities->m_ContextMinor = 0; - pCommand->m_pCapabilities->m_ContextPatch = 0; - - // tell the caller to reinitialize the context - *pCommand->m_pInitError = -2; - - g_Config.m_GfxDriverIsBlocked = 0; - } - - int MajorV = pCommand->m_pCapabilities->m_ContextMajor; - int MinorV = pCommand->m_pCapabilities->m_ContextMinor; - - if(*pCommand->m_pInitError == 0) - { - if(MajorV < pCommand->m_RequestedMajor) - { - *pCommand->m_pInitError = -2; - } - else if(MajorV == pCommand->m_RequestedMajor) - { - if(MinorV < pCommand->m_RequestedMinor) - { - *pCommand->m_pInitError = -2; - } - else if(MinorV == pCommand->m_RequestedMinor) - { - int PatchV = pCommand->m_pCapabilities->m_ContextPatch; - if(PatchV < pCommand->m_RequestedPatch) - { - *pCommand->m_pInitError = -2; - } - } - } - } - - if(*pCommand->m_pInitError == 0) - { - MajorV = pCommand->m_RequestedMajor; - MinorV = pCommand->m_RequestedMinor; - - pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension = false; - pCommand->m_pCapabilities->m_NPOTTextures = true; - - if(MajorV >= 4 || (MajorV == 3 && MinorV == 3)) - { - pCommand->m_pCapabilities->m_TileBuffering = true; - pCommand->m_pCapabilities->m_QuadBuffering = true; - pCommand->m_pCapabilities->m_TextBuffering = true; - pCommand->m_pCapabilities->m_QuadContainerBuffering = true; - pCommand->m_pCapabilities->m_ShaderSupport = true; - - pCommand->m_pCapabilities->m_MipMapping = true; - pCommand->m_pCapabilities->m_3DTextures = true; - pCommand->m_pCapabilities->m_2DArrayTextures = true; - } - else if(MajorV == 3) - { - pCommand->m_pCapabilities->m_MipMapping = true; - // check for context native 2D array texture size - pCommand->m_pCapabilities->m_3DTextures = false; - pCommand->m_pCapabilities->m_2DArrayTextures = false; - pCommand->m_pCapabilities->m_ShaderSupport = true; - - int TextureLayers = 0; - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &TextureLayers); - if(TextureLayers >= 256) - { - pCommand->m_pCapabilities->m_2DArrayTextures = true; - } - - int Texture3DSize = 0; - glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &Texture3DSize); - if(Texture3DSize >= 256) - { - pCommand->m_pCapabilities->m_3DTextures = true; - } - - if(!pCommand->m_pCapabilities->m_3DTextures && !pCommand->m_pCapabilities->m_2DArrayTextures) - { - *pCommand->m_pInitError = -2; - pCommand->m_pCapabilities->m_ContextMajor = 1; - pCommand->m_pCapabilities->m_ContextMinor = 5; - pCommand->m_pCapabilities->m_ContextPatch = 0; - } - - pCommand->m_pCapabilities->m_TileBuffering = pCommand->m_pCapabilities->m_2DArrayTextures || pCommand->m_pCapabilities->m_3DTextures; - pCommand->m_pCapabilities->m_QuadBuffering = false; - pCommand->m_pCapabilities->m_TextBuffering = false; - pCommand->m_pCapabilities->m_QuadContainerBuffering = false; - } - else if(MajorV == 2) - { - pCommand->m_pCapabilities->m_MipMapping = true; - // check for context extension: 2D array texture and its max size - pCommand->m_pCapabilities->m_3DTextures = false; - pCommand->m_pCapabilities->m_2DArrayTextures = false; - - pCommand->m_pCapabilities->m_ShaderSupport = false; - if(MinorV >= 1) - pCommand->m_pCapabilities->m_ShaderSupport = true; - - int Texture3DSize = 0; - glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &Texture3DSize); - if(Texture3DSize >= 256) - { - pCommand->m_pCapabilities->m_3DTextures = true; - } - - // check for array texture extension - if(pCommand->m_pCapabilities->m_ShaderSupport && GLEW_EXT_texture_array) - { - int TextureLayers = 0; - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &TextureLayers); - if(TextureLayers >= 256) - { - pCommand->m_pCapabilities->m_2DArrayTextures = true; - pCommand->m_pCapabilities->m_2DArrayTexturesAsExtension = true; - } - } - - pCommand->m_pCapabilities->m_TileBuffering = pCommand->m_pCapabilities->m_2DArrayTextures || pCommand->m_pCapabilities->m_3DTextures; - pCommand->m_pCapabilities->m_QuadBuffering = false; - pCommand->m_pCapabilities->m_TextBuffering = false; - pCommand->m_pCapabilities->m_QuadContainerBuffering = false; - - if(GLEW_ARB_texture_non_power_of_two || pCommand->m_GlewMajor > 2) - pCommand->m_pCapabilities->m_NPOTTextures = true; - else - { - pCommand->m_pCapabilities->m_NPOTTextures = false; - } - - if(!pCommand->m_pCapabilities->m_NPOTTextures || (!pCommand->m_pCapabilities->m_3DTextures && !pCommand->m_pCapabilities->m_2DArrayTextures)) - { - *pCommand->m_pInitError = -2; - pCommand->m_pCapabilities->m_ContextMajor = 1; - pCommand->m_pCapabilities->m_ContextMinor = 5; - pCommand->m_pCapabilities->m_ContextPatch = 0; - } - } - else if(MajorV < 2) - { - pCommand->m_pCapabilities->m_TileBuffering = false; - pCommand->m_pCapabilities->m_QuadBuffering = false; - pCommand->m_pCapabilities->m_TextBuffering = false; - pCommand->m_pCapabilities->m_QuadContainerBuffering = false; - pCommand->m_pCapabilities->m_ShaderSupport = false; - - pCommand->m_pCapabilities->m_MipMapping = false; - pCommand->m_pCapabilities->m_3DTextures = false; - pCommand->m_pCapabilities->m_2DArrayTextures = false; - pCommand->m_pCapabilities->m_NPOTTextures = false; - } - } -} - -void CCommandProcessorFragment_SDL::Cmd_Update_Viewport(const SCommand_Update_Viewport *pCommand) -{ - glViewport(pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height); -} - -void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand) -{ - SDL_GL_MakeCurrent(NULL, NULL); -} - -void CCommandProcessorFragment_SDL::Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand) -{ - SDL_GL_SwapWindow(m_pWindow); - - if(pCommand->m_Finish) - glFinish(); -} - -void CCommandProcessorFragment_SDL::Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand) -{ - *pCommand->m_pRetOk = SDL_GL_SetSwapInterval(pCommand->m_VSync) == 0; -} - -void CCommandProcessorFragment_SDL::Cmd_Resize(const CCommandBuffer::SCommand_Resize *pCommand) -{ - glViewport(0, 0, pCommand->m_Width, pCommand->m_Height); -} - -void CCommandProcessorFragment_SDL::Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand) -{ - SDL_DisplayMode mode; - int maxModes = SDL_GetNumDisplayModes(pCommand->m_Screen), - numModes = 0; - - for(int i = 0; i < maxModes; i++) - { - if(SDL_GetDisplayMode(pCommand->m_Screen, i, &mode) < 0) - { - dbg_msg("gfx", "unable to get display mode: %s", SDL_GetError()); - continue; - } - - bool AlreadyFound = false; - for(int j = 0; j < numModes; j++) - { - if(pCommand->m_pModes[j].m_Width == mode.w && pCommand->m_pModes[j].m_Height == mode.h) - { - AlreadyFound = true; - break; - } - } - if(AlreadyFound) - continue; - - pCommand->m_pModes[numModes].m_Width = mode.w; - pCommand->m_pModes[numModes].m_Height = mode.h; - pCommand->m_pModes[numModes].m_Red = 8; - pCommand->m_pModes[numModes].m_Green = 8; - pCommand->m_pModes[numModes].m_Blue = 8; - numModes++; - } - *pCommand->m_pNumModes = numModes; -} - -CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL() -{ -} - -bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) -{ - switch(pBaseCommand->m_Cmd) - { - case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_VSYNC: Cmd_VSync(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_RESIZE: Cmd_Resize(static_cast(pBaseCommand)); break; - case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast(pBaseCommand)); break; - case CMD_INIT: Cmd_Init(static_cast(pBaseCommand)); break; - case CMD_SHUTDOWN: Cmd_Shutdown(static_cast(pBaseCommand)); break; - case CMD_UPDATE_VIEWPORT: Cmd_Update_Viewport(static_cast(pBaseCommand)); break; - default: return false; - } - - return true; -} - -// ------------ CCommandProcessor_SDL_OpenGL - -void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer) -{ - for(CCommandBuffer::SCommand *pCommand = pBuffer->Head(); pCommand; pCommand = pCommand->m_pNext) - { - if(m_pOpenGL->RunCommand(pCommand)) - continue; - - if(m_SDL.RunCommand(pCommand)) - continue; - - if(m_General.RunCommand(pCommand)) - continue; - - dbg_msg("gfx", "unknown command %d", pCommand->m_Cmd); - } -} - -CCommandProcessor_SDL_OpenGL::CCommandProcessor_SDL_OpenGL(int OpenGLMajor, int OpenGLMinor, int OpenGLPatch) -{ - if(OpenGLMajor < 2) - { - m_pOpenGL = new CCommandProcessorFragment_OpenGL(); - } - if(OpenGLMajor == 2) - { - m_pOpenGL = new CCommandProcessorFragment_OpenGL2(); - } - if(OpenGLMajor == 3 && OpenGLMinor == 0) - { - m_pOpenGL = new CCommandProcessorFragment_OpenGL3(); - } - else if((OpenGLMajor == 3 && OpenGLMinor == 3) || OpenGLMajor >= 4) - { - m_pOpenGL = new CCommandProcessorFragment_OpenGL3_3(); - } -} - -CCommandProcessor_SDL_OpenGL::~CCommandProcessor_SDL_OpenGL() -{ - delete m_pOpenGL; -} - -// ------------ CGraphicsBackend_SDL_OpenGL - -static void GetGlewVersion(int &GlewMajor, int &GlewMinor, int &GlewPatch) -{ -#ifdef GLEW_VERSION_4_6 - if(GLEW_VERSION_4_6) - { - GlewMajor = 4; - GlewMinor = 6; - GlewPatch = 0; - return; - } -#endif - if(GLEW_VERSION_4_5) - { - GlewMajor = 4; - GlewMinor = 5; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_4_4) - { - GlewMajor = 4; - GlewMinor = 4; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_4_3) - { - GlewMajor = 4; - GlewMinor = 3; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_4_2) - { - GlewMajor = 4; - GlewMinor = 2; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_4_1) - { - GlewMajor = 4; - GlewMinor = 1; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_4_0) - { - GlewMajor = 4; - GlewMinor = 0; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_3_3) - { - GlewMajor = 3; - GlewMinor = 3; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_3_0) - { - GlewMajor = 3; - GlewMinor = 0; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_2_1) - { - GlewMajor = 2; - GlewMinor = 1; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_2_0) - { - GlewMajor = 2; - GlewMinor = 0; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_1_5) - { - GlewMajor = 1; - GlewMinor = 5; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_1_4) - { - GlewMajor = 1; - GlewMinor = 4; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_1_3) - { - GlewMajor = 1; - GlewMinor = 3; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_1_2_1) - { - GlewMajor = 1; - GlewMinor = 2; - GlewPatch = 1; - return; - } - if(GLEW_VERSION_1_2) - { - GlewMajor = 1; - GlewMinor = 2; - GlewPatch = 0; - return; - } - if(GLEW_VERSION_1_1) + else if(BackendType == BACKEND_TYPE_OPENGL) { - GlewMajor = 1; - GlewMinor = 1; - GlewPatch = 0; - return; +#if !defined(CONF_BACKEND_OPENGL_ES) + if(OpenGLMajor < 2) + { + m_pOpenGL = new CCommandProcessorFragment_OpenGL(); + } + if(OpenGLMajor == 2) + { + m_pOpenGL = new CCommandProcessorFragment_OpenGL2(); + } + if(OpenGLMajor == 3 && OpenGLMinor == 0) + { + m_pOpenGL = new CCommandProcessorFragment_OpenGL3(); + } + else if((OpenGLMajor == 3 && OpenGLMinor == 3) || OpenGLMajor >= 4) + { + m_pOpenGL = new CCommandProcessorFragment_OpenGL3_3(); + } +#endif } } -static int IsVersionSupportedGlew(int VersionMajor, int VersionMinor, int VersionPatch, int GlewMajor, int GlewMinor, int GlewPatch) +CCommandProcessor_SDL_OpenGL::~CCommandProcessor_SDL_OpenGL() { - int InitError = 0; - if(VersionMajor >= 4 && GlewMajor < 4) - { - InitError = -1; - } - else if(VersionMajor >= 3 && GlewMajor < 3) - { - InitError = -1; - } - else if(VersionMajor == 3 && GlewMajor == 3) + delete m_pOpenGL; +} + +// ------------ CGraphicsBackend_SDL_OpenGL + +static bool BackendInitGlew(EBackendType BackendType, int &GlewMajor, int &GlewMinor, int &GlewPatch) +{ + if(BackendType == BACKEND_TYPE_OPENGL) { - if(VersionMinor >= 3 && GlewMinor < 3) +#ifndef CONF_BACKEND_OPENGL_ES + //support graphic cards that are pretty old(and linux) + glewExperimental = GL_TRUE; + if(GLEW_OK != glewInit()) + return false; + +#ifdef GLEW_VERSION_4_6 + if(GLEW_VERSION_4_6) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 6; + GlewPatch = 0; + return true; } - if(VersionMinor >= 2 && GlewMinor < 2) +#endif + if(GLEW_VERSION_4_5) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 5; + GlewPatch = 0; + return true; } - if(VersionMinor >= 1 && GlewMinor < 1) + if(GLEW_VERSION_4_4) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 4; + GlewPatch = 0; + return true; } - if(VersionMinor >= 0 && GlewMinor < 0) + if(GLEW_VERSION_4_3) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 3; + GlewPatch = 0; + return true; } - } - else if(VersionMajor >= 2 && GlewMajor < 2) - { - InitError = -1; - } - else if(VersionMajor == 2 && GlewMajor == 2) - { - if(VersionMinor >= 1 && GlewMinor < 1) + if(GLEW_VERSION_4_2) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 2; + GlewPatch = 0; + return true; } - if(VersionMinor >= 0 && GlewMinor < 0) + if(GLEW_VERSION_4_1) { - InitError = -1; + GlewMajor = 4; + GlewMinor = 1; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_4_0) + { + GlewMajor = 4; + GlewMinor = 0; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_3_3) + { + GlewMajor = 3; + GlewMinor = 3; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_3_0) + { + GlewMajor = 3; + GlewMinor = 0; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_2_1) + { + GlewMajor = 2; + GlewMinor = 1; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_2_0) + { + GlewMajor = 2; + GlewMinor = 0; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_1_5) + { + GlewMajor = 1; + GlewMinor = 5; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_1_4) + { + GlewMajor = 1; + GlewMinor = 4; + GlewPatch = 0; + return true; } + if(GLEW_VERSION_1_3) + { + GlewMajor = 1; + GlewMinor = 3; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_1_2_1) + { + GlewMajor = 1; + GlewMinor = 2; + GlewPatch = 1; + return true; + } + if(GLEW_VERSION_1_2) + { + GlewMajor = 1; + GlewMinor = 2; + GlewPatch = 0; + return true; + } + if(GLEW_VERSION_1_1) + { + GlewMajor = 1; + GlewMinor = 1; + GlewPatch = 0; + return true; + } +#endif } - else if(VersionMajor >= 1 && GlewMajor < 1) + else if(BackendType == BACKEND_TYPE_OPENGL_ES) { - InitError = -1; + // just assume the version we need + GlewMajor = 3; + GlewMinor = 0; + GlewPatch = 0; + + return true; } - else if(VersionMajor == 1 && GlewMajor == 1) + + return true; +} + +static int IsVersionSupportedGlew(EBackendType BackendType, int VersionMajor, int VersionMinor, int VersionPatch, int GlewMajor, int GlewMinor, int GlewPatch) +{ + int InitError = 0; + + if(BackendType == BACKEND_TYPE_OPENGL) { - if(VersionMinor >= 5 && GlewMinor < 5) + if(VersionMajor >= 4 && GlewMajor < 4) { InitError = -1; } - if(VersionMinor >= 4 && GlewMinor < 4) + else if(VersionMajor >= 3 && GlewMajor < 3) { InitError = -1; } - if(VersionMinor >= 3 && GlewMinor < 3) + else if(VersionMajor == 3 && GlewMajor == 3) { - InitError = -1; + if(VersionMinor >= 3 && GlewMinor < 3) + { + InitError = -1; + } + if(VersionMinor >= 2 && GlewMinor < 2) + { + InitError = -1; + } + if(VersionMinor >= 1 && GlewMinor < 1) + { + InitError = -1; + } + if(VersionMinor >= 0 && GlewMinor < 0) + { + InitError = -1; + } } - if(VersionMinor >= 2 && GlewMinor < 2) + else if(VersionMajor >= 2 && GlewMajor < 2) { InitError = -1; } - else if(VersionMinor == 2 && GlewMinor == 2) + else if(VersionMajor == 2 && GlewMajor == 2) { - if(VersionPatch >= 1 && GlewPatch < 1) + if(VersionMinor >= 1 && GlewMinor < 1) { InitError = -1; } - if(VersionPatch >= 0 && GlewPatch < 0) + if(VersionMinor >= 0 && GlewMinor < 0) { InitError = -1; } } - if(VersionMinor >= 1 && GlewMinor < 1) + else if(VersionMajor >= 1 && GlewMajor < 1) { InitError = -1; } - if(VersionMinor >= 0 && GlewMinor < 0) + else if(VersionMajor == 1 && GlewMajor == 1) { - InitError = -1; + if(VersionMinor >= 5 && GlewMinor < 5) + { + InitError = -1; + } + if(VersionMinor >= 4 && GlewMinor < 4) + { + InitError = -1; + } + if(VersionMinor >= 3 && GlewMinor < 3) + { + InitError = -1; + } + if(VersionMinor >= 2 && GlewMinor < 2) + { + InitError = -1; + } + else if(VersionMinor == 2 && GlewMinor == 2) + { + if(VersionPatch >= 1 && GlewPatch < 1) + { + InitError = -1; + } + if(VersionPatch >= 0 && GlewPatch < 0) + { + InitError = -1; + } + } + if(VersionMinor >= 1 && GlewMinor < 1) + { + InitError = -1; + } + if(VersionMinor >= 0 && GlewMinor < 0) + { + InitError = -1; + } } } return InitError; } +EBackendType CGraphicsBackend_SDL_OpenGL::DetectBackend() +{ +#ifndef CONF_BACKEND_OPENGL_ES +#ifdef CONF_BACKEND_OPENGL_ES3 + const char *pEnvDriver = getenv("DDNET_DRIVER"); + if(pEnvDriver && str_comp(pEnvDriver, "GLES") == 0) + return BACKEND_TYPE_OPENGL_ES; + else + return BACKEND_TYPE_OPENGL; +#else + return BACKEND_TYPE_OPENGL; +#endif +#else + return BACKEND_TYPE_OPENGL_ES; +#endif +} + +void CGraphicsBackend_SDL_OpenGL::ClampDriverVersion(EBackendType BackendType) +{ + if(BackendType == BACKEND_TYPE_OPENGL) + { + //clamp the versions to existing versions(only for OpenGL major <= 3) + if(g_Config.m_GfxOpenGLMajor == 1) + { + g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 1, 5); + if(g_Config.m_GfxOpenGLMinor == 2) + g_Config.m_GfxOpenGLPatch = clamp(g_Config.m_GfxOpenGLPatch, 0, 1); + else + g_Config.m_GfxOpenGLPatch = 0; + } + else if(g_Config.m_GfxOpenGLMajor == 2) + { + g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 0, 1); + g_Config.m_GfxOpenGLPatch = 0; + } + else if(g_Config.m_GfxOpenGLMajor == 3) + { + g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 0, 3); + if(g_Config.m_GfxOpenGLMinor < 3) + g_Config.m_GfxOpenGLMinor = 0; + g_Config.m_GfxOpenGLPatch = 0; + } + } + else if(BackendType == BACKEND_TYPE_OPENGL_ES) + { +#if !defined(CONF_BACKEND_OPENGL_ES3) + // Make sure GLES is set to 1.0 (which is equivalent to OpenGL 1.3), if its not set to >= 3.0(which is equivalent to OpenGL 3.3) + if(g_Config.m_GfxOpenGLMajor < 3) + { + g_Config.m_GfxOpenGLMajor = 1; + g_Config.m_GfxOpenGLMinor = 0; + g_Config.m_GfxOpenGLPatch = 0; + + // GLES also doesnt know GL_QUAD + g_Config.m_GfxQuadAsTriangle = 1; + } +#else + g_Config.m_GfxOpenGLMajor = 3; + g_Config.m_GfxOpenGLMinor = 0; + g_Config.m_GfxOpenGLPatch = 0; +#endif + } +} + +bool CGraphicsBackend_SDL_OpenGL::IsModernAPI(EBackendType BackendType) +{ + if(BackendType == BACKEND_TYPE_OPENGL) + return (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4; + else if(BackendType == BACKEND_TYPE_OPENGL_ES) + return g_Config.m_GfxOpenGLMajor >= 3; + + return false; +} + +void CGraphicsBackend_SDL_OpenGL::GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch) +{ + if(m_BackendType == BACKEND_TYPE_OPENGL) + { + if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_LEGACY) + { + Major = 1; + Minor = 4; + Patch = 0; + } + else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_DEFAULT) + { + Major = 3; + Minor = 0; + Patch = 0; + } + else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_MODERN) + { + Major = 3; + Minor = 3; + Patch = 0; + } + } + else if(m_BackendType == BACKEND_TYPE_OPENGL_ES) + { + if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_LEGACY) + { + Major = 1; + Minor = 0; + Patch = 0; + } + else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_DEFAULT) + { + Major = 3; + Minor = 0; + Patch = 0; + } + else if(DriverAgeType == GRAPHICS_DRIVER_AGE_TYPE_MODERN) + { + Major = 3; + Minor = 0; + Patch = 0; + } + } +} + int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, IStorage *pStorage) { // print sdl version @@ -4339,115 +704,30 @@ } } - SDL_ClearError(); - const char *pErr = NULL; + m_BackendType = DetectBackend(); - // Query default values, since they are platform dependent - static bool s_InitDefaultParams = false; - static int s_SDLGLContextProfileMask, s_SDLGLContextMajorVersion, s_SDLGLContextMinorVersion; - static bool s_TriedOpenGL3Context = false; - - if(!s_InitDefaultParams) - { - SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &s_SDLGLContextProfileMask); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &s_SDLGLContextMajorVersion); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &s_SDLGLContextMinorVersion); - s_InitDefaultParams = true; - } - - //clamp the versions to existing versions(only for OpenGL major <= 3) - if(g_Config.m_GfxOpenGLMajor == 1) - { - g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 1, 5); - if(g_Config.m_GfxOpenGLMinor == 2) - g_Config.m_GfxOpenGLPatch = clamp(g_Config.m_GfxOpenGLPatch, 0, 1); - else - g_Config.m_GfxOpenGLPatch = 0; - } - else if(g_Config.m_GfxOpenGLMajor == 2) - { - g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 0, 1); - g_Config.m_GfxOpenGLPatch = 0; - } - else if(g_Config.m_GfxOpenGLMajor == 3) - { - g_Config.m_GfxOpenGLMinor = clamp(g_Config.m_GfxOpenGLMinor, 0, 3); - if(g_Config.m_GfxOpenGLMinor < 3) - g_Config.m_GfxOpenGLMinor = 0; - g_Config.m_GfxOpenGLPatch = 0; - } + ClampDriverVersion(m_BackendType); - // if OpenGL3 context was tried to be created, but failed, we have to restore the old context attributes - bool IsNewOpenGL = (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4; - if(s_TriedOpenGL3Context && !IsNewOpenGL) - { - s_TriedOpenGL3Context = false; + m_UseNewOpenGL = IsModernAPI(m_BackendType); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, s_SDLGLContextProfileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, s_SDLGLContextMajorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, s_SDLGLContextMinorVersion); - } + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, g_Config.m_GfxOpenGLMajor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, g_Config.m_GfxOpenGLMinor); + dbg_msg("gfx", "Created OpenGL %zu.%zu context.", (size_t)g_Config.m_GfxOpenGLMajor, (size_t)g_Config.m_GfxOpenGLMinor); - m_UseNewOpenGL = false; - if(IsNewOpenGL) + if(m_BackendType == BACKEND_TYPE_OPENGL) { - s_TriedOpenGL3Context = true; - - if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) == 0) + if(g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 0) { - pErr = SDL_GetError(); - if(pErr[0] != '\0') - { - dbg_msg("gfx", "Using old OpenGL context, because an error occurred while trying to use OpenGL context %zu.%zu: %s.", (size_t)g_Config.m_GfxOpenGLMajor, (size_t)g_Config.m_GfxOpenGLMinor, pErr); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, s_SDLGLContextProfileMask); - } - else - { - if(SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, g_Config.m_GfxOpenGLMajor) == 0 && SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, g_Config.m_GfxOpenGLMinor) == 0) - { - pErr = SDL_GetError(); - if(pErr[0] != '\0') - { - dbg_msg("gfx", "Using old OpenGL context, because an error occurred while trying to use OpenGL context %zu.%zu: %s.", (size_t)g_Config.m_GfxOpenGLMajor, (size_t)g_Config.m_GfxOpenGLMinor, pErr); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, s_SDLGLContextMajorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, s_SDLGLContextMinorVersion); - } - else - { - m_UseNewOpenGL = true; - int vMaj, vMin; - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &vMaj); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &vMin); - dbg_msg("gfx", "Using OpenGL version %d.%d.", vMaj, vMin); - } - } - else - { - dbg_msg("gfx", "Couldn't create OpenGL %zu.%zu context.", (size_t)g_Config.m_GfxOpenGLMajor, (size_t)g_Config.m_GfxOpenGLMinor); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, s_SDLGLContextMajorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, s_SDLGLContextMinorVersion); - } - } + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); } - else + else if(m_UseNewOpenGL) { - //set default attributes - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, s_SDLGLContextProfileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, s_SDLGLContextMajorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, s_SDLGLContextMinorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); } } - //if non standard opengl, set it - else if(s_SDLGLContextMajorVersion != g_Config.m_GfxOpenGLMajor || s_SDLGLContextMinorVersion != g_Config.m_GfxOpenGLMinor) + else if(m_BackendType == BACKEND_TYPE_OPENGL_ES) { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, g_Config.m_GfxOpenGLMajor); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, g_Config.m_GfxOpenGLMinor); - dbg_msg("gfx", "Created OpenGL %zu.%zu context.", (size_t)g_Config.m_GfxOpenGLMajor, (size_t)g_Config.m_GfxOpenGLMinor); - - if(g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 0) - { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); - } + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); } // set screen @@ -4476,11 +756,14 @@ dbg_msg("gfx", "unable to get desktop resolution: %s", SDL_GetError()); return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_SDL_SCREEN_RESOLUTION_REQUEST_FAILED; } + + bool IsDesktopChanged = *pDesktopWidth == 0 || *pDesktopHeight == 0 || *pDesktopWidth != DisplayMode.w || *pDesktopHeight != DisplayMode.h; + *pDesktopWidth = DisplayMode.w; *pDesktopHeight = DisplayMode.h; - // use desktop resolution as default resolution - if(*pWidth == 0 || *pHeight == 0) + // use desktop resolution as default resolution, clamp resolution if users's display is smaller than we remembered + if(*pWidth == 0 || *pHeight == 0 || (IsDesktopChanged && (*pWidth > *pDesktopWidth || *pHeight > *pDesktopHeight))) { *pWidth = *pDesktopWidth; *pHeight = *pDesktopHeight; @@ -4520,6 +803,10 @@ else SdlFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } + else if(Flags & (IGraphicsBackend::INITFLAG_DESKTOP_FULLSCREEN)) + { + SdlFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } // set gl attributes SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -4556,27 +843,33 @@ if(m_GLContext == NULL) { + SDL_DestroyWindow(m_pWindow); dbg_msg("gfx", "unable to create OpenGL context: %s", SDL_GetError()); return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_OPENGL_CONTEXT_FAILED; } - //support graphic cards that are pretty old(and linux) - glewExperimental = GL_TRUE; - if(GLEW_OK != glewInit()) - return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_UNKNOWN; - int GlewMajor = 0; int GlewMinor = 0; int GlewPatch = 0; - GetGlewVersion(GlewMajor, GlewMinor, GlewPatch); + if(!BackendInitGlew(m_BackendType, GlewMajor, GlewMinor, GlewPatch)) + { + SDL_GL_DeleteContext(m_GLContext); + SDL_DestroyWindow(m_pWindow); + return EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_UNKNOWN; + } int InitError = 0; const char *pErrorStr = NULL; - InitError = IsVersionSupportedGlew(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch, GlewMajor, GlewMinor, GlewPatch); + InitError = IsVersionSupportedGlew(m_BackendType, g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch, GlewMajor, GlewMinor, GlewPatch); + + // SDL_GL_GetDrawableSize reports HiDPI resolution even with SDL_WINDOW_ALLOW_HIGHDPI not set, which is wrong + if(SdlFlags & SDL_WINDOW_ALLOW_HIGHDPI) + SDL_GL_GetDrawableSize(m_pWindow, pCurrentWidth, pCurrentHeight); + else + SDL_GetWindowSize(m_pWindow, pCurrentWidth, pCurrentHeight); - SDL_GL_GetDrawableSize(m_pWindow, pCurrentWidth, pCurrentHeight); SDL_GL_SetSwapInterval(Flags & IGraphicsBackend::INITFLAG_VSYNC ? 1 : 0); SDL_GL_MakeCurrent(NULL, NULL); @@ -4594,7 +887,7 @@ } // start the command processor - m_pProcessor = new CCommandProcessor_SDL_OpenGL(g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); + m_pProcessor = new CCommandProcessor_SDL_OpenGL(m_BackendType, g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); StartProcessor(m_pProcessor); mem_zero(m_aErrorString, sizeof(m_aErrorString) / sizeof(m_aErrorString[0])); @@ -4605,50 +898,50 @@ CCommandProcessorFragment_SDL::SCommand_Init CmdSDL; CmdSDL.m_pWindow = m_pWindow; CmdSDL.m_GLContext = m_GLContext; - CmdSDL.m_pCapabilities = &m_Capabilites; - CmdSDL.m_RequestedMajor = g_Config.m_GfxOpenGLMajor; - CmdSDL.m_RequestedMinor = g_Config.m_GfxOpenGLMinor; - CmdSDL.m_RequestedPatch = g_Config.m_GfxOpenGLPatch; - CmdSDL.m_GlewMajor = GlewMajor; - CmdSDL.m_GlewMinor = GlewMinor; - CmdSDL.m_GlewPatch = GlewPatch; - CmdSDL.m_pInitError = &InitError; - CmdSDL.m_pErrStringPtr = &pErrorStr; - CmdBuffer.AddCommand(CmdSDL); + CmdBuffer.AddCommandUnsafe(CmdSDL); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); if(InitError == 0) { - CCommandProcessorFragment_OpenGL::SCommand_Init CmdOpenGL; + CCommandProcessorFragment_OpenGLBase::SCommand_Init CmdOpenGL; CmdOpenGL.m_pTextureMemoryUsage = &m_TextureMemoryUsage; CmdOpenGL.m_pStorage = pStorage; CmdOpenGL.m_pCapabilities = &m_Capabilites; CmdOpenGL.m_pInitError = &InitError; - CmdBuffer.AddCommand(CmdOpenGL); + CmdOpenGL.m_RequestedMajor = g_Config.m_GfxOpenGLMajor; + CmdOpenGL.m_RequestedMinor = g_Config.m_GfxOpenGLMinor; + CmdOpenGL.m_RequestedPatch = g_Config.m_GfxOpenGLPatch; + CmdOpenGL.m_GlewMajor = GlewMajor; + CmdOpenGL.m_GlewMinor = GlewMinor; + CmdOpenGL.m_GlewPatch = GlewPatch; + CmdOpenGL.m_pErrStringPtr = &pErrorStr; + CmdOpenGL.m_pVendorString = m_aVendorString; + CmdOpenGL.m_pVersionString = m_aVersionString; + CmdOpenGL.m_pRendererString = m_aRendererString; + CmdOpenGL.m_RequestedBackend = m_BackendType; + CmdBuffer.AddCommandUnsafe(CmdOpenGL); + RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); + } - if(InitError == -2) + if(InitError != 0) + { + if(InitError != -2) { - CCommandProcessorFragment_OpenGL::SCommand_Shutdown CmdGL; - CmdBuffer.AddCommand(CmdGL); + // shutdown the context, as it might have been initialized + CCommandProcessorFragment_OpenGLBase::SCommand_Shutdown CmdGL; + CmdBuffer.AddCommandUnsafe(CmdGL); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); - - g_Config.m_GfxOpenGLMajor = 1; - g_Config.m_GfxOpenGLMinor = 5; - g_Config.m_GfxOpenGLPatch = 0; } - } - if(InitError != 0) - { CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd; - CmdBuffer.AddCommand(Cmd); + CmdBuffer.AddCommandUnsafe(Cmd); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); @@ -4695,13 +988,13 @@ //TODO: current problem is, that the opengl driver knows about the scaled display, //so the viewport cannot be adjusted for resolutions, that are higher than allowed by the display driver - CCommandProcessorFragment_SDL::SCommand_Update_Viewport CmdSDL; + CCommandBuffer::SCommand_Update_Viewport CmdSDL; CmdSDL.m_X = 0; CmdSDL.m_Y = 0; CmdSDL.m_Width = CurrentDisplayMode.w; CmdSDL.m_Height = CurrentDisplayMode.h; - CmdBuffer.AddCommand(CmdSDL); + CmdBuffer.AddCommandUnsafe(CmdSDL); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); @@ -4716,14 +1009,14 @@ { // issue a shutdown command CCommandBuffer CmdBuffer(1024, 512); - CCommandProcessorFragment_OpenGL::SCommand_Shutdown CmdGL; - CmdBuffer.AddCommand(CmdGL); + CCommandProcessorFragment_OpenGLBase::SCommand_Shutdown CmdGL; + CmdBuffer.AddCommandUnsafe(CmdGL); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); CCommandProcessorFragment_SDL::SCommand_Shutdown Cmd; - CmdBuffer.AddCommand(Cmd); + CmdBuffer.AddCommandUnsafe(Cmd); RunBuffer(&CmdBuffer); WaitForIdle(); CmdBuffer.Reset(); @@ -4755,18 +1048,28 @@ // TODO: SDL } -bool CGraphicsBackend_SDL_OpenGL::Fullscreen(bool State) +void CGraphicsBackend_SDL_OpenGL::SetWindowParams(int FullscreenMode, bool IsBorderless) { -#if defined(CONF_PLATFORM_MACOSX) // Todo SDL: remove this when fixed (game freezes when losing focus in fullscreen) - return SDL_SetWindowFullscreen(m_pWindow, State ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0) == 0; + if(FullscreenMode > 0) + { + if(FullscreenMode == 1) + { +#if defined(CONF_PLATFORM_MACOS) || defined(CONF_PLATFORM_HAIKU) // Todo SDL: remove this when fixed (game freezes when losing focus in fullscreen) + SDL_SetWindowFullscreen(m_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); #else - return SDL_SetWindowFullscreen(m_pWindow, State ? SDL_WINDOW_FULLSCREEN : 0) == 0; + SDL_SetWindowFullscreen(m_pWindow, SDL_WINDOW_FULLSCREEN); #endif -} - -void CGraphicsBackend_SDL_OpenGL::SetWindowBordered(bool State) -{ - SDL_SetWindowBordered(m_pWindow, SDL_bool(State)); + } + else if(FullscreenMode == 2) + { + SDL_SetWindowFullscreen(m_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); + } + } + else + { + SDL_SetWindowFullscreen(m_pWindow, 0); + SDL_SetWindowBordered(m_pWindow, SDL_bool(!IsBorderless)); + } } bool CGraphicsBackend_SDL_OpenGL::SetWindowScreen(int Index) @@ -4836,22 +1139,23 @@ desc.dwTimeout = 0; FlashWindowEx(&desc); -#elif defined(SDL_VIDEO_DRIVER_X11) && !defined(CONF_PLATFORM_MACOSX) - Display *dpy = info.info.x11.display; - Window win = info.info.x11.window; - - // Old hints - XWMHints *wmhints; - wmhints = XAllocWMHints(); - wmhints->flags = XUrgencyHint; - XSetWMHints(dpy, win, wmhints); - XFree(wmhints); - - // More modern way of notifying - static Atom demandsAttention = XInternAtom(dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", true); - static Atom wmState = XInternAtom(dpy, "_NET_WM_STATE", true); - XChangeProperty(dpy, win, wmState, XA_ATOM, 32, PropModeReplace, - (unsigned char *)&demandsAttention, 1); +#elif defined(SDL_VIDEO_DRIVER_X11) && !defined(CONF_PLATFORM_MACOS) + Display *pX11Dpy = info.info.x11.display; + Window X11Win = info.info.x11.window; + + static Atom s_DemandsAttention = XInternAtom(pX11Dpy, "_NET_WM_STATE_DEMANDS_ATTENTION", true); + static Atom s_WmState = XInternAtom(pX11Dpy, "_NET_WM_STATE", true); + + XEvent SndNtfyEvent = {ClientMessage}; + SndNtfyEvent.xclient.window = X11Win; + SndNtfyEvent.xclient.message_type = s_WmState; + SndNtfyEvent.xclient.format = 32; + SndNtfyEvent.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + SndNtfyEvent.xclient.data.l[1] = s_DemandsAttention; + SndNtfyEvent.xclient.data.l[2] = 0; + SndNtfyEvent.xclient.data.l[3] = 1; // normal application + SndNtfyEvent.xclient.data.l[4] = 0; + XSendEvent(pX11Dpy, XDefaultRootWindow(pX11Dpy), False, SubstructureNotifyMask | SubstructureRedirectMask, &SndNtfyEvent); #endif } diff -Nru ddnet-15.3.2/src/engine/client/backend_sdl.h ddnet-15.5.4/src/engine/client/backend_sdl.h --- ddnet-15.3.2/src/engine/client/backend_sdl.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/backend_sdl.h 2021-06-20 09:38:48.000000000 +0000 @@ -2,7 +2,10 @@ #define ENGINE_CLIENT_BACKEND_SDL_H #include "SDL.h" -#include "SDL_opengl.h" + +#include + +#include "graphics_defines.h" #include "blocklist_driver.h" #include "graphics_threaded.h" @@ -11,7 +14,7 @@ #include -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) #include class CAutoreleasePool @@ -79,6 +82,12 @@ bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand); }; +enum EBackendType +{ + BACKEND_TYPE_OPENGL = 0, + BACKEND_TYPE_OPENGL_ES, +}; + struct SBackendCapabilites { bool m_TileBuffering; @@ -98,73 +107,26 @@ int m_ContextPatch; }; -class CGLSLProgram; -class CGLSLTWProgram; -class CGLSLPrimitiveProgram; -class CGLSLQuadProgram; -class CGLSLTileProgram; -class CGLSLTextProgram; -class CGLSLPrimitiveExProgram; -class CGLSLSpriteMultipleProgram; - -// takes care of opengl related rendering -class CCommandProcessorFragment_OpenGL +// takes care of sdl related commands +class CCommandProcessorFragment_SDL { -protected: - struct CTexture - { - CTexture() : - m_Tex(0), m_Tex2DArray(0), m_Sampler(0), m_Sampler2DArray(0), m_LastWrapMode(CCommandBuffer::WRAP_REPEAT), m_MemSize(0), m_Width(0), m_Height(0), m_RescaleCount(0), m_ResizeWidth(0), m_ResizeHeight(0) - { - } - - GLuint m_Tex; - GLuint m_Tex2DArray; //or 3D texture as fallback - GLuint m_Sampler; - GLuint m_Sampler2DArray; //or 3D texture as fallback - int m_LastWrapMode; - - int m_MemSize; - - int m_Width; - int m_Height; - int m_RescaleCount; - float m_ResizeWidth; - float m_ResizeHeight; - }; - std::vector m_Textures; - std::atomic *m_pTextureMemoryUsage; - - GLint m_MaxTexSize; - - bool m_Has2DArrayTextures; - bool m_Has2DArrayTexturesAsExtension; - GLenum m_2DArrayTarget; - bool m_Has3DTextures; - bool m_HasMipMaps; - bool m_HasNPOTTextures; - - bool m_HasShaders; - int m_LastBlendMode; //avoid all possible opengl state changes - bool m_LastClipEnable; - - int m_OpenGLTextureLodBIAS; + // SDL stuff + SDL_Window *m_pWindow; + SDL_GLContext m_GLContext; public: enum { - CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_OPENGL, - CMD_SHUTDOWN = CMD_INIT + 1, + CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL, + CMD_SHUTDOWN, }; struct SCommand_Init : public CCommandBuffer::SCommand { SCommand_Init() : SCommand(CMD_INIT) {} - class IStorage *m_pStorage; - std::atomic *m_pTextureMemoryUsage; - SBackendCapabilites *m_pCapabilities; - int *m_pInitError; + SDL_Window *m_pWindow; + SDL_GLContext m_GLContext; }; struct SCommand_Shutdown : public CCommandBuffer::SCommand @@ -173,319 +135,81 @@ SCommand(CMD_SHUTDOWN) {} }; -protected: - bool IsTexturedState(const CCommandBuffer::SState &State); - void SetState(const CCommandBuffer::SState &State, bool Use2DArrayTexture = false); - virtual bool IsNewApi() { return false; } - void DestroyTexture(int Slot); - - static int TexFormatToOpenGLFormat(int TexFormat); - static int TexFormatToImageColorChannelCount(int TexFormat); - static void *Resize(int Width, int Height, int NewWidth, int NewHeight, int Format, const unsigned char *pData); - - virtual void Cmd_Init(const SCommand_Init *pCommand); - virtual void Cmd_Shutdown(const SCommand_Shutdown *pCommand) {} - virtual void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand); - virtual void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand); - virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand); - virtual void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand); - virtual void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand); - virtual void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) {} - virtual void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand); - - virtual void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) {} - virtual void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) {} - virtual void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) {} - virtual void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) {} - virtual void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) {} - - virtual void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) {} - virtual void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) {} - virtual void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) {} - virtual void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) {} - - virtual void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) {} - virtual void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) {} - virtual void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) {} - virtual void Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) {} - 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_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) {} - virtual void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) {} +private: + void Cmd_Init(const SCommand_Init *pCommand); + void Cmd_Shutdown(const SCommand_Shutdown *pCommand); + void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand); + void Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand); + void Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand); public: - CCommandProcessorFragment_OpenGL(); - virtual ~CCommandProcessorFragment_OpenGL() = default; + CCommandProcessorFragment_SDL(); bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand); }; -class CCommandProcessorFragment_OpenGL2 : public CCommandProcessorFragment_OpenGL +class CCommandProcessorFragment_OpenGLBase { - struct SBufferContainer - { - SBufferContainer() {} - SBufferContainerInfo m_ContainerInfo; - }; - std::vector m_BufferContainers; - - GL_SVertexTex3D m_aStreamVertices[1024 * 4]; - - struct SBufferObject - { - SBufferObject(GLuint BufferObjectID) : - m_BufferObjectID(BufferObjectID) - { - m_pData = NULL; - m_DataSize = 0; - } - GLuint m_BufferObjectID; - void *m_pData; - size_t m_DataSize; - }; - - std::vector m_BufferObjectIndices; - - bool DoAnalyzeStep(size_t StepN, size_t CheckCount, size_t VerticesCount, uint8_t aFakeTexture[], size_t SingleImageSize); - bool IsTileMapAnalysisSucceeded(); - - void RenderBorderTileEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int DrawNum, const float *pOffset, const float *pDir, int JumpIndex); - void RenderBorderTileLineEmulation(SBufferContainer &BufferContainer, const CCommandBuffer::SState &State, const float *pColor, const char *pBuffOffset, unsigned int IndexDrawNum, unsigned int DrawNum, const float *pOffset, const float *pDir); - - void UseProgram(CGLSLTWProgram *pProgram); - -protected: - void SetState(const CCommandBuffer::SState &State, CGLSLTWProgram *pProgram, bool Use2DArrayTextures = false); - - void Cmd_Init(const SCommand_Init *pCommand) override; - - void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) override; - - void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) override; - void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) override; - void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) override; - void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) override; - void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) override; - - void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) override; - void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) override; - void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) override; - void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) override; - - void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) override; - void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) override; - void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) override; - - CGLSLTileProgram *m_pTileProgram; - CGLSLTileProgram *m_pTileProgramTextured; - CGLSLPrimitiveProgram *m_pPrimitive3DProgram; - CGLSLPrimitiveProgram *m_pPrimitive3DProgramTextured; - - bool m_UseMultipleTextureUnits; - - GLint m_MaxTextureUnits; - - struct STextureBound - { - int m_TextureSlot; - bool m_Is2DArray; - }; - std::vector m_TextureSlotBoundToUnit; //the texture index generated by loadtextureraw is stored in an index calculated by max texture units - - bool IsAndUpdateTextureSlotBound(int IDX, int Slot, bool Is2DArray = false); - public: - CCommandProcessorFragment_OpenGL2() : - CCommandProcessorFragment_OpenGL(), m_UseMultipleTextureUnits(false) {} -}; - -class CCommandProcessorFragment_OpenGL3 : public CCommandProcessorFragment_OpenGL2 -{ -}; + virtual ~CCommandProcessorFragment_OpenGLBase() = default; + virtual bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand) = 0; -#define MAX_STREAM_BUFFER_COUNT 30 - -// takes care of opengl 3.3+ related rendering -class CCommandProcessorFragment_OpenGL3_3 : public CCommandProcessorFragment_OpenGL3 -{ - bool m_UsePreinitializedVertexBuffer; - - int m_MaxQuadsAtOnce; - static const int m_MaxQuadsPossible = 256; - - CGLSLPrimitiveProgram *m_pPrimitiveProgram; - CGLSLPrimitiveProgram *m_pPrimitiveProgramTextured; - CGLSLTileProgram *m_pBorderTileProgram; - CGLSLTileProgram *m_pBorderTileProgramTextured; - CGLSLTileProgram *m_pBorderTileLineProgram; - CGLSLTileProgram *m_pBorderTileLineProgramTextured; - CGLSLQuadProgram *m_pQuadProgram; - CGLSLQuadProgram *m_pQuadProgramTextured; - CGLSLTextProgram *m_pTextProgram; - CGLSLPrimitiveExProgram *m_pPrimitiveExProgram; - CGLSLPrimitiveExProgram *m_pPrimitiveExProgramTextured; - CGLSLPrimitiveExProgram *m_pPrimitiveExProgramRotationless; - CGLSLPrimitiveExProgram *m_pPrimitiveExProgramTexturedRotationless; - CGLSLSpriteMultipleProgram *m_pSpriteProgramMultiple; - - GLuint m_LastProgramID; - - GLuint m_PrimitiveDrawVertexID[MAX_STREAM_BUFFER_COUNT]; - GLuint m_PrimitiveDrawVertexIDTex3D; - GLuint m_PrimitiveDrawBufferID[MAX_STREAM_BUFFER_COUNT]; - GLuint m_PrimitiveDrawBufferIDTex3D; - - GLuint m_LastIndexBufferBound[MAX_STREAM_BUFFER_COUNT]; - - int m_LastStreamBuffer; - - GLuint m_QuadDrawIndexBufferID; - unsigned int m_CurrentIndicesInBuffer; - - void DestroyBufferContainer(int Index, bool DeleteBOs = true); - - void AppendIndices(unsigned int NewIndicesCount); - - struct SBufferContainer - { - SBufferContainer() : - m_VertArrayID(0), m_LastIndexBufferBound(0) {} - GLuint m_VertArrayID; - GLuint m_LastIndexBufferBound; - SBufferContainerInfo m_ContainerInfo; - }; - std::vector m_BufferContainers; - - std::vector m_BufferObjectIndices; - - CCommandBuffer::SColorf m_ClearColor; - - void InitPrimExProgram(CGLSLPrimitiveExProgram *pProgram, class CGLSLCompiler *pCompiler, class IStorage *pStorage, bool Textured, bool Rotationless); - -protected: - static int TexFormatToNewOpenGLFormat(int TexFormat); - bool IsNewApi() override { return true; } - - void UseProgram(CGLSLTWProgram *pProgram); - void UploadStreamBufferData(unsigned int PrimitiveType, const void *pVertices, size_t VertSize, unsigned int PrimitiveCount, bool AsTex3D = false); - void RenderText(const CCommandBuffer::SState &State, int DrawNum, int TextTextureIndex, int TextOutlineTextureIndex, int TextureSize, const float *pTextColor, const float *pTextOutlineColor); - - void Cmd_Init(const SCommand_Init *pCommand) override; - void Cmd_Shutdown(const SCommand_Shutdown *pCommand) override; - void Cmd_Texture_Update(const CCommandBuffer::SCommand_Texture_Update *pCommand) override; - void Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand) override; - void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) override; - void Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) override; - void Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) override; - void Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand) override; - void Cmd_Screenshot(const CCommandBuffer::SCommand_Screenshot *pCommand) override; - - void Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand) override; - void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) override; - void Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand) override; - void Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand) override; - void Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand) override; - - void Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand) override; - void Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand) override; - void Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand) override; - void Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand) override; - - void Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand) override; - void Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand) override; - void Cmd_RenderBorderTileLine(const CCommandBuffer::SCommand_RenderBorderTileLine *pCommand) override; - void Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand) override; - 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_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand) override; - void Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand) override; - -public: - CCommandProcessorFragment_OpenGL3_3() = default; -}; - -// takes care of sdl related commands -class CCommandProcessorFragment_SDL -{ - // SDL stuff - SDL_Window *m_pWindow; - SDL_GLContext m_GLContext; - -public: enum { - CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL, - CMD_UPDATE_VIEWPORT, - CMD_SHUTDOWN, + CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_OPENGL, + CMD_SHUTDOWN = CMD_INIT + 1, }; struct SCommand_Init : public CCommandBuffer::SCommand { SCommand_Init() : SCommand(CMD_INIT) {} - SDL_Window *m_pWindow; - SDL_GLContext m_GLContext; + class IStorage *m_pStorage; + std::atomic *m_pTextureMemoryUsage; SBackendCapabilites *m_pCapabilities; + int *m_pInitError; const char **m_pErrStringPtr; - int *m_pInitError; + char *m_pVendorString; + char *m_pVersionString; + char *m_pRendererString; int m_RequestedMajor; int m_RequestedMinor; int m_RequestedPatch; + EBackendType m_RequestedBackend; + int m_GlewMajor; int m_GlewMinor; int m_GlewPatch; }; - struct SCommand_Update_Viewport : public CCommandBuffer::SCommand - { - SCommand_Update_Viewport() : - SCommand(CMD_UPDATE_VIEWPORT) {} - int m_X; - int m_Y; - int m_Width; - int m_Height; - }; - struct SCommand_Shutdown : public CCommandBuffer::SCommand { SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {} }; - -private: - void Cmd_Init(const SCommand_Init *pCommand); - void Cmd_Update_Viewport(const SCommand_Update_Viewport *pCommand); - void Cmd_Shutdown(const SCommand_Shutdown *pCommand); - void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand); - void Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand); - void Cmd_Resize(const CCommandBuffer::SCommand_Resize *pCommand); - void Cmd_VideoModes(const CCommandBuffer::SCommand_VideoModes *pCommand); - -public: - CCommandProcessorFragment_SDL(); - - bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand); }; // command processor impelementation, uses the fragments to combine into one processor class CCommandProcessor_SDL_OpenGL : public CGraphicsBackend_Threaded::ICommandProcessor { - CCommandProcessorFragment_OpenGL *m_pOpenGL; + CCommandProcessorFragment_OpenGLBase *m_pOpenGL; CCommandProcessorFragment_SDL m_SDL; CCommandProcessorFragment_General m_General; + EBackendType m_BackendType; + public: - CCommandProcessor_SDL_OpenGL(int OpenGLMajor, int OpenGLMinor, int OpenGLPatch); + CCommandProcessor_SDL_OpenGL(EBackendType BackendType, int OpenGLMajor, int OpenGLMinor, int OpenGLPatch); virtual ~CCommandProcessor_SDL_OpenGL(); virtual void RunBuffer(CCommandBuffer *pBuffer); }; +static constexpr size_t gs_GPUInfoStringSize = 256; + // graphics backend implemented with SDL and OpenGL class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded { @@ -497,10 +221,18 @@ SBackendCapabilites m_Capabilites; + char m_aVendorString[gs_GPUInfoStringSize] = {}; + char m_aVersionString[gs_GPUInfoStringSize] = {}; + char m_aRendererString[gs_GPUInfoStringSize] = {}; + bool m_UseNewOpenGL; + EBackendType m_BackendType; char m_aErrorString[256]; + static EBackendType DetectBackend(); + static void ClampDriverVersion(EBackendType BackendType); + 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(); @@ -511,8 +243,7 @@ virtual void Minimize(); virtual void Maximize(); - virtual bool Fullscreen(bool State); - virtual void SetWindowBordered(bool State); // on=true/off=false + virtual void SetWindowParams(int FullscreenMode, bool IsBorderless); virtual bool SetWindowScreen(int Index); virtual int GetWindowScreen(); virtual int WindowActive(); @@ -522,6 +253,8 @@ virtual void GetViewportSize(int &w, int &h); virtual void NotifyWindow(); + virtual void GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch); + virtual bool IsConfigModernAPI() { return IsModernAPI(m_BackendType); } virtual bool IsNewOpenGL() { return m_UseNewOpenGL; } virtual bool HasTileBuffering() { return m_Capabilites.m_TileBuffering; } virtual bool HasQuadBuffering() { return m_Capabilites.m_QuadBuffering; } @@ -536,6 +269,23 @@ return NULL; } + + virtual const char *GetVendorString() + { + return m_aVendorString; + } + + virtual const char *GetVersionString() + { + return m_aVersionString; + } + + virtual const char *GetRendererString() + { + return m_aRendererString; + } + + static bool IsModernAPI(EBackendType BackendType); }; #endif // ENGINE_CLIENT_BACKEND_SDL_H diff -Nru ddnet-15.3.2/src/engine/client/blocklist_driver.cpp ddnet-15.5.4/src/engine/client/blocklist_driver.cpp --- ddnet-15.3.2/src/engine/client/blocklist_driver.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/blocklist_driver.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -21,24 +21,37 @@ } }; +enum EBackendDriverBlockListType +{ + BACKEND_DRIVER_BLOCKLIST_TYPE_VERSION = 0, + BACKEND_DRIVER_BLOCKLIST_TYPE_VENDOR, +}; + /* TODO: generalize it more for other drivers / vendors */ struct SBackEndDriverBlockList { + EBackendDriverBlockListType m_BlockListType; + SVersion m_VersionMin; SVersion m_VersionMax; + const char *m_pVendorName; + // the OpenGL version, that is supported int m_AllowedMajor; int m_AllowedMinor; int m_AllowedPatch; const char *m_pReason; + + bool m_DisplayReason; + const char *m_pOSName; }; static SBackEndDriverBlockList gs_aBlockList[] = { - {{26, 20, 100, 7800}, {27, 20, 100, 8853}, 2, 0, 0, "This Intel driver version can cause crashes, please update it to a newer version."}}; + {BACKEND_DRIVER_BLOCKLIST_TYPE_VENDOR, {26, 20, 100, 7800}, {27, 20, 100, 8853}, "Intel", 2, 0, 0, "This Intel driver version can cause crashes, please update it to a newer version.", false, "windows"}}; -const char *ParseBlocklistDriverVersions(const char *pVendorStr, const char *pVersionStr, int &BlocklistMajor, int &BlocklistMinor, int &BlocklistPatch) +const char *ParseBlocklistDriverVersions(const char *pVendorStr, const char *pVersionStr, int &BlocklistMajor, int &BlocklistMinor, int &BlocklistPatch, bool &RequiresWarning) { if(str_find_nocase(pVendorStr, "Intel") == NULL) return NULL; @@ -64,12 +77,32 @@ for(const auto &BlockListItem : gs_aBlockList) { - if(BlockListItem.m_VersionMin <= Version && Version <= BlockListItem.m_VersionMax) + if(str_comp(BlockListItem.m_pOSName, CONF_FAMILY_STRING) == 0) { - BlocklistMajor = BlockListItem.m_AllowedMajor; - BlocklistMinor = BlockListItem.m_AllowedMinor; - BlocklistPatch = BlockListItem.m_AllowedPatch; - return BlockListItem.m_pReason; + bool DriverBlocked = false; + if(BlockListItem.m_BlockListType == BACKEND_DRIVER_BLOCKLIST_TYPE_VENDOR) + { + if(str_find_nocase(pVendorStr, BlockListItem.m_pVendorName) != NULL) + { + DriverBlocked = true; + } + } + else if(BlockListItem.m_BlockListType == BACKEND_DRIVER_BLOCKLIST_TYPE_VERSION) + { + if(BlockListItem.m_VersionMin <= Version && Version <= BlockListItem.m_VersionMax) + { + DriverBlocked = true; + } + } + + if(DriverBlocked) + { + RequiresWarning = BlockListItem.m_DisplayReason; + BlocklistMajor = BlockListItem.m_AllowedMajor; + BlocklistMinor = BlockListItem.m_AllowedMinor; + BlocklistPatch = BlockListItem.m_AllowedPatch; + return BlockListItem.m_pReason; + } } } diff -Nru ddnet-15.3.2/src/engine/client/blocklist_driver.h ddnet-15.5.4/src/engine/client/blocklist_driver.h --- ddnet-15.3.2/src/engine/client/blocklist_driver.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/blocklist_driver.h 2021-06-20 09:38:48.000000000 +0000 @@ -1,6 +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); +const char *ParseBlocklistDriverVersions(const char *pVendorStr, const char *pVersionStr, int &BlocklistMajor, int &BlocklistMinor, int &BlocklistPatch, bool &RequiresWarning); #endif // ENGINE_CLIENT_BLOCKLIST_DRIVER_H diff -Nru ddnet-15.3.2/src/engine/client/client.cpp ddnet-15.5.4/src/engine/client/client.cpp --- ddnet-15.3.2/src/engine/client/client.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/client.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,6 @@ #include #include -#include #include #include @@ -78,6 +76,9 @@ #undef main #endif +static const ColorRGBA ClientNetworkPrintColor{0.7f, 1, 0.7f, 1.0f}; +static const ColorRGBA ClientNetworkErrPrintColor{1.0f, 0.25f, 0.25f, 1.0f}; + void CGraph::Init(float Min, float Max) { m_Min = Min; @@ -334,6 +335,12 @@ m_Points = -1; m_CurrentServerInfoRequestTime = -1; + m_CurrentServerPingInfoType = -1; + m_CurrentServerPingBasicToken = -1; + m_CurrentServerPingToken = -1; + mem_zero(&m_CurrentServerPingUuid, sizeof(m_CurrentServerPingUuid)); + m_CurrentServerCurrentPingTime = -1; + m_CurrentServerNextPingTime = -1; m_CurrentInput[0] = 0; m_CurrentInput[1] = 0; @@ -479,7 +486,7 @@ SendMsg(&Msg, MSGFLAG_VITAL); } -bool CClient::ConnectionProblems() +bool CClient::ConnectionProblems() const { return m_NetClient[g_Config.m_ClDummy].GotProblems() != 0; } @@ -544,13 +551,13 @@ } } -const char *CClient::LatestVersion() +const char *CClient::LatestVersion() const { return m_aVersionStr; } // TODO: OPT: do this a lot smarter! -int *CClient::GetInput(int Tick, int IsDummy) +int *CClient::GetInput(int Tick, int IsDummy) const { int Best = -1; const int d = IsDummy ^ g_Config.m_ClDummy; @@ -565,7 +572,7 @@ return 0; } -int *CClient::GetDirectInput(int Tick, int IsDummy) +int *CClient::GetDirectInput(int Tick, int IsDummy) const { const int d = IsDummy ^ g_Config.m_ClDummy; for(int i = 0; i < 200; i++) @@ -654,6 +661,7 @@ OnEnterGame(); ServerInfoRequest(); // fresh one for timeout protection + m_CurrentServerNextPingTime = time_get() + time_freq() / 2; m_aTimeoutCodeSent[0] = false; m_aTimeoutCodeSent[1] = false; } @@ -710,7 +718,7 @@ str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ClientNetworkPrintColor); bool is_websocket = false; if(strncmp(m_aServerAddressStr, "ws://", 5) == 0) { @@ -737,6 +745,7 @@ else str_copy(m_Password, pPassword, sizeof(m_Password)); + m_CanReceiveServerCapabilities = true; // Deregister Rcon commands from last connected server, might not have called // DisconnectWithReason if the server was shut down m_RconAuthed[0] = 0; @@ -765,7 +774,7 @@ { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason ? pReason : "unknown"); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ClientNetworkPrintColor); // stop demo playback and recorder m_DemoPlayer.Stop(); @@ -774,13 +783,18 @@ // m_RconAuthed[0] = 0; - m_CanReceiveServerCapabilities = true; m_ServerSentCapabilities = false; m_UseTempRconCommands = 0; m_pConsole->DeregisterTempAll(); m_NetClient[CLIENT_MAIN].Disconnect(pReason); SetState(IClient::STATE_OFFLINE); m_pMap->Unload(); + m_CurrentServerPingInfoType = -1; + m_CurrentServerPingBasicToken = -1; + m_CurrentServerPingToken = -1; + mem_zero(&m_CurrentServerPingUuid, sizeof(m_CurrentServerPingUuid)); + m_CurrentServerCurrentPingTime = -1; + m_CurrentServerNextPingTime = -1; // disable all downloads m_MapdownloadChunk = 0; @@ -900,7 +914,7 @@ return 0; } -void CClient::GetServerInfo(CServerInfo *pServerInfo) +void CClient::GetServerInfo(CServerInfo *pServerInfo) const { mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); @@ -916,13 +930,13 @@ int CClient::LoadData() { - m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE); + m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); return 1; } // --- -void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) +void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const { CSnapshotItem *i; dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); @@ -933,7 +947,7 @@ return (void *)i->Data(); } -int CClient::SnapItemSize(int SnapID, int Index) +int CClient::SnapItemSize(int SnapID, int Index) const { dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); return m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index); @@ -954,7 +968,7 @@ } } -void *CClient::SnapFindItem(int SnapID, int Type, int ID) +void *CClient::SnapFindItem(int SnapID, int Type, int ID) const { // TODO: linear search. should be fixed. int i; @@ -971,7 +985,7 @@ return 0x0; } -int CClient::SnapNumItems(int SnapID) +int CClient::SnapNumItems(int SnapID) const { dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); if(!m_aSnapshots[g_Config.m_ClDummy][SnapID]) @@ -1084,7 +1098,7 @@ SetState(IClient::STATE_QUITTING); } -const char *CClient::PlayerName() +const char *CClient::PlayerName() const { if(g_Config.m_PlayerName[0]) { @@ -1097,7 +1111,7 @@ return "nameless tee"; } -const char *CClient::DummyName() +const char *CClient::DummyName() const { if(g_Config.m_ClDummyName[0]) { @@ -1114,13 +1128,14 @@ } if(pBase) { - str_format(m_aDummyNameBuf, sizeof(m_aDummyNameBuf), "[D] %s", pBase); - return m_aDummyNameBuf; + static char aDummyNameBuf[16]; + str_format(aDummyNameBuf, sizeof(aDummyNameBuf), "[D] %s", pBase); + return aDummyNameBuf; } return "brainless tee"; } -const char *CClient::ErrorString() +const char *CClient::ErrorString() const { return m_NetClient[CLIENT_MAIN].ErrorString(); } @@ -1248,105 +1263,8 @@ return pError; } -bool CClient::PlayerScoreNameLess(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1) -{ - 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; - if(Score0 == -9999) - Score0 = INT_MIN; - if(Score1 == -9999) - Score1 = INT_MIN; - - if(Score0 > Score1) - return true; - if(Score0 < Score1) - return false; - return str_comp_nocase(p0.m_aName, p1.m_aName) < 0; -} - void CClient::ProcessConnlessPacket(CNetChunk *pPacket) { - //server count from master server - if(pPacket->m_DataSize == (int)sizeof(SERVERBROWSE_COUNT) + 2 && mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) - { - unsigned char *pP = (unsigned char *)pPacket->m_pData; - pP += sizeof(SERVERBROWSE_COUNT); - int ServerCount = ((*pP) << 8) | *(pP + 1); - int ServerID = -1; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) - { - if(!m_pMasterServer->IsValid(i)) - continue; - NETADDR tmp = m_pMasterServer->GetAddr(i); - if(net_addr_comp(&pPacket->m_Address, &tmp) == 0) - { - ServerID = i; - break; - } - } - if(ServerCount > -1 && ServerID != -1) - { - m_pMasterServer->SetCount(ServerID, ServerCount); - if(g_Config.m_Debug) - dbg_msg("mastercount", "server %d got %d servers", ServerID, ServerCount); - } - } - // server list from master server - if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && - mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) - { - // check for valid master server address - bool Valid = false; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i) - { - if(m_pMasterServer->IsValid(i)) - { - NETADDR Addr = m_pMasterServer->GetAddr(i); - if(net_addr_comp(&pPacket->m_Address, &Addr) == 0) - { - Valid = true; - break; - } - } - } - if(!Valid) - return; - - int Size = pPacket->m_DataSize - sizeof(SERVERBROWSE_LIST); - int Num = Size / sizeof(CMastersrvAddr); - CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char *)pPacket->m_pData + sizeof(SERVERBROWSE_LIST)); - for(int i = 0; i < Num; i++) - { - NETADDR Addr; - - static unsigned char s_IPV4Mapping[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF}; - - // copy address - if(!mem_comp(s_IPV4Mapping, pAddrs[i].m_aIp, sizeof(s_IPV4Mapping))) - { - mem_zero(&Addr, sizeof(Addr)); - Addr.type = NETTYPE_IPV4; - Addr.ip[0] = pAddrs[i].m_aIp[12]; - Addr.ip[1] = pAddrs[i].m_aIp[13]; - Addr.ip[2] = pAddrs[i].m_aIp[14]; - Addr.ip[3] = pAddrs[i].m_aIp[15]; - } - else - { - Addr.type = NETTYPE_IPV6; - mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip)); - } - Addr.port = (pAddrs[i].m_aPort[0] << 8) | pAddrs[i].m_aPort[1]; - - m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0); - } - } - // server info if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO)) { @@ -1527,19 +1445,9 @@ if(!Up.Error() || IgnoreError) { - std::sort(Info.m_aClients, Info.m_aClients + Info.m_NumReceivedClients, PlayerScoreNameLess); - if(!DuplicatedPacket && (!pEntry || !pEntry->m_GotInfo || SavedType >= pEntry->m_Info.m_Type)) { m_ServerBrowser.Set(*pFrom, IServerBrowser::SET_TOKEN, Token, &Info); - pEntry = m_ServerBrowser.Find(*pFrom); - - if(SavedType == SERVERINFO_VANILLA && Is64Player(&Info) && pEntry) - { - pEntry->m_Request64Legacy = true; - // Force a quick update. - m_ServerBrowser.RequestImpl64(pEntry->m_Addr, pEntry); - } } // Player info is irrelevant for the client (while connected), @@ -1558,6 +1466,30 @@ m_CurrentServerInfo.m_NetAddr = m_ServerAddress; m_CurrentServerInfoRequestTime = -1; } + + bool ValidPong = false; + if(!m_ServerCapabilities.m_PingEx && m_CurrentServerCurrentPingTime >= 0 && SavedType >= m_CurrentServerPingInfoType) + { + if(RawType == SERVERINFO_VANILLA) + { + ValidPong = Token == m_CurrentServerPingBasicToken; + } + else if(RawType == SERVERINFO_EXTENDED) + { + ValidPong = Token == m_CurrentServerPingToken; + } + } + if(ValidPong) + { + int LatencyMs = (time_get() - m_CurrentServerCurrentPingTime) * 1000 / time_freq(); + m_ServerBrowser.SetCurrentServerPing(m_ServerAddress, LatencyMs); + m_CurrentServerPingInfoType = SavedType; + m_CurrentServerCurrentPingTime = -1; + + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "got pong from current server, latency=%dms", LatencyMs); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + } } } @@ -1605,10 +1537,20 @@ DDNet = Flags & SERVERCAPFLAG_DDNET; } Result.m_ChatTimeoutCode = DDNet; + Result.m_AnyPlayerFlag = DDNet; + Result.m_PingEx = false; if(Version >= 1) { Result.m_ChatTimeoutCode = Flags & SERVERCAPFLAG_CHATTIMEOUTCODE; } + if(Version >= 2) + { + Result.m_AnyPlayerFlag = Flags & SERVERCAPFLAG_ANYPLAYERFLAG; + } + if(Version >= 3) + { + Result.m_PingEx = Flags & SERVERCAPFLAG_PINGEX; + } return Result; } @@ -1806,6 +1748,35 @@ CMsgPacker Msg(NETMSG_PING_REPLY, true); SendMsg(&Msg, 0); } + else if(Msg == NETMSG_PINGEX) + { + CUuid *pID = (CUuid *)Unpacker.GetRaw(sizeof(*pID)); + if(Unpacker.Error()) + { + return; + } + CMsgPacker Msg(NETMSG_PONGEX, true); + Msg.AddRaw(pID, sizeof(*pID)); + SendMsg(&Msg, MSGFLAG_FLUSH); + } + else if(Msg == NETMSG_PONGEX) + { + CUuid *pID = (CUuid *)Unpacker.GetRaw(sizeof(*pID)); + if(Unpacker.Error()) + { + return; + } + if(m_ServerCapabilities.m_PingEx && m_CurrentServerCurrentPingTime >= 0 && *pID == m_CurrentServerPingUuid) + { + int LatencyMs = (time_get() - m_CurrentServerCurrentPingTime) * 1000 / time_freq(); + m_ServerBrowser.SetCurrentServerPing(m_ServerAddress, LatencyMs); + m_CurrentServerCurrentPingTime = -1; + + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "got pong from current server, latency=%dms", LatencyMs); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + } + } else if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_ADD) { if(!g_Config.m_ClDummy) @@ -2024,7 +1995,7 @@ // for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; mem_copy(aExtraInfoRemoved, pTmpBuffer3, SnapSize); - SnapshotRemoveExtraInfo(aExtraInfoRemoved); + SnapshotRemoveExtraProjectileInfo(aExtraInfoRemoved); // add snapshot to demo for(auto &DemoRecorder : m_DemoRecorder) @@ -2559,7 +2530,7 @@ Disconnect(); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient[CLIENT_MAIN].ErrorString()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ClientNetworkErrPrintColor); } if(State() != IClient::STATE_OFFLINE && State() < IClient::STATE_QUITTING && m_DummyConnected && @@ -2568,14 +2539,14 @@ DummyDisconnect(0); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "offline dummy error='%s'", m_NetClient[CLIENT_DUMMY].ErrorString()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ClientNetworkErrPrintColor); } // if(State() == IClient::STATE_CONNECTING && m_NetClient[CLIENT_MAIN].State() == NETSTATE_ONLINE) { // we switched to online - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info", ClientNetworkPrintColor); SetState(IClient::STATE_LOADING); SendInfo(); } @@ -2837,6 +2808,33 @@ m_ServerBrowser.RequestCurrentServer(m_ServerAddress); m_CurrentServerInfoRequestTime = time_get() + time_freq() * 2; } + + // periodically ping server + if(State() == IClient::STATE_ONLINE && + m_CurrentServerNextPingTime >= 0 && + time_get() > m_CurrentServerNextPingTime) + { + int64 Now = time_get(); + int64 Freq = time_freq(); + + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "pinging current server%s", !m_ServerCapabilities.m_PingEx ? ", using fallback via server info" : ""); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + + m_CurrentServerPingUuid = RandomUuid(); + if(!m_ServerCapabilities.m_PingEx) + { + m_ServerBrowser.RequestCurrentServerWithRandomToken(m_ServerAddress, &m_CurrentServerPingBasicToken, &m_CurrentServerPingToken); + } + else + { + CMsgPacker Msg(NETMSG_PINGEX, true); + Msg.AddRaw(&m_CurrentServerPingUuid, sizeof(m_CurrentServerPingUuid)); + SendMsg(&Msg, MSGFLAG_FLUSH); + } + m_CurrentServerCurrentPingTime = Now; + m_CurrentServerNextPingTime = Now + 600 * Freq; // ping every 10 minutes + } } m_LastDummy = (bool)g_Config.m_ClDummy; @@ -2922,9 +2920,6 @@ } } - // update the maser server registry - MasterServer()->Update(); - // update the server browser m_ServerBrowser.Update(m_ResortServerBrowser); m_ResortServerBrowser = false; @@ -2945,7 +2940,8 @@ if(m_ReconnectTime > 0 && time_get() > m_ReconnectTime) { - Connect(m_aServerAddressStr); + if(State() != STATE_ONLINE) + Connect(m_aServerAddressStr); m_ReconnectTime = 0; } } @@ -2974,7 +2970,6 @@ m_pGameClient = Kernel()->RequestInterface(); m_pInput = Kernel()->RequestInterface(); m_pMap = Kernel()->RequestInterface(); - m_pMasterServer = Kernel()->RequestInterface(); m_pConfigManager = Kernel()->RequestInterface(); m_pConfig = m_pConfigManager->Values(); #if defined(CONF_AUTOUPDATE) @@ -3090,9 +3085,6 @@ // init the input Input()->Init(); - // start refreshing addresses while we load - MasterServer()->RefreshAddresses(m_NetClient[CLIENT_MAIN].NetType()); - // init the editor m_pEditor->Init(); @@ -3111,10 +3103,11 @@ } GameClient()->OnInit(); + m_ServerBrowser.OnInit(); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion()); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA{0.7f, 0.7f, 1, 1.0f}); // connect to the server if wanted /* @@ -3478,6 +3471,12 @@ pSelf->DummyDisconnect(0); } +void CClient::Con_DummyResetInput(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->GameClient()->DummyResetInput(); +} + void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData) { CClient *pSelf = (CClient *)pUserData; @@ -3639,8 +3638,18 @@ { CClient *pSelf = (CClient *)pUserData; NETADDR Addr; - if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) - pSelf->m_ServerBrowser.AddFavorite(Addr); + if(net_addr_from_str(&Addr, pResult->GetString(0)) != 0) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "invalid address '%s'", pResult->GetString(0)); + pSelf->m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + return; + } + pSelf->m_ServerBrowser.AddFavorite(Addr); + if(pResult->NumArguments() > 1 && str_find(pResult->GetString(1), "allow_ping")) + { + pSelf->m_ServerBrowser.FavoriteAllowPing(Addr, true); + } } void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData) @@ -3982,10 +3991,10 @@ // Todo SDL: remove this when fixed (changing screen when in fullscreen is bugged) if(g_Config.m_GfxFullscreen) { - ToggleFullscreen(); + SetWindowParams(0, g_Config.m_GfxBorderless); if(Graphics()->SetWindowScreen(Index)) g_Config.m_GfxScreen = Index; - ToggleFullscreen(); + SetWindowParams(g_Config.m_GfxFullscreen, g_Config.m_GfxBorderless); } else { @@ -4006,10 +4015,11 @@ pfnCallback(pResult, pCallbackUserData); } -void CClient::ToggleFullscreen() +void CClient::SetWindowParams(int FullscreenMode, bool IsBorderless) { - if(Graphics()->Fullscreen(g_Config.m_GfxFullscreen ^ 1)) - g_Config.m_GfxFullscreen ^= 1; + g_Config.m_GfxFullscreen = clamp(FullscreenMode, 0, 2); + g_Config.m_GfxBorderless = (int)IsBorderless; + Graphics()->SetWindowParams(FullscreenMode, IsBorderless); } void CClient::ConchainFullscreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) @@ -4018,25 +4028,19 @@ if(pSelf->Graphics() && pResult->NumArguments()) { if(g_Config.m_GfxFullscreen != pResult->GetInteger(0)) - pSelf->ToggleFullscreen(); + pSelf->SetWindowParams(pResult->GetInteger(0), g_Config.m_GfxBorderless); } else pfnCallback(pResult, pCallbackUserData); } -void CClient::ToggleWindowBordered() -{ - g_Config.m_GfxBorderless ^= 1; - Graphics()->SetWindowBordered(!g_Config.m_GfxBorderless); -} - void CClient::ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { CClient *pSelf = (CClient *)pUserData; if(pSelf->Graphics() && pResult->NumArguments()) { if(!g_Config.m_GfxFullscreen && (g_Config.m_GfxBorderless != pResult->GetInteger(0))) - pSelf->ToggleWindowBordered(); + pSelf->SetWindowParams(g_Config.m_GfxFullscreen, !g_Config.m_GfxBorderless); } else pfnCallback(pResult, pCallbackUserData); @@ -4080,11 +4084,20 @@ Kernel()->RequestInterface()->SetDefaultFont(pDefaultFont); } + + char aBuff[1024]; + if(!pDefaultFont) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load font. filename='%s'", pFontFile); + { + str_format(aBuff, sizeof(aBuff) / sizeof(aBuff[0]), "failed to load font. filename='%s'", pFontFile); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", aBuff); + } if(!LoadedFallbackFont) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load the fallback font. filename='%s'", pFallbackFontFile); + { + str_format(aBuff, sizeof(aBuff) / sizeof(aBuff[0]), "failed to load the fallback font. filename='%s'", pFallbackFontFile); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", aBuff); + } } void CClient::Notify(const char *pTitle, const char *pMessage) @@ -4158,8 +4171,9 @@ m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording"); m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map"); - m_pConsole->Register("dummy_connect", "", CFGFLAG_CLIENT, Con_DummyConnect, this, "connect dummy"); - m_pConsole->Register("dummy_disconnect", "", CFGFLAG_CLIENT, Con_DummyDisconnect, this, "disconnect dummy"); + m_pConsole->Register("dummy_connect", "", CFGFLAG_CLIENT, Con_DummyConnect, this, "Connect dummy"); + m_pConsole->Register("dummy_disconnect", "", CFGFLAG_CLIENT, Con_DummyDisconnect, this, "Disconnect dummy"); + m_pConsole->Register("dummy_reset", "", CFGFLAG_CLIENT, Con_DummyResetInput, this, "Reset dummy"); m_pConsole->Register("quit", "", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); m_pConsole->Register("exit", "", CFGFLAG_CLIENT | CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); @@ -4181,7 +4195,7 @@ m_pConsole->Register("record", "?r[file]", CFGFLAG_CLIENT, Con_Record, this, "Record to the file"); m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording"); m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker"); - m_pConsole->Register("add_favorite", "r[host|ip]", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite"); + m_pConsole->Register("add_favorite", "s[host|ip] ?s['allow_ping']", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite"); m_pConsole->Register("remove_favorite", "r[host|ip]", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites"); m_pConsole->Register("demo_slice_start", "", CFGFLAG_CLIENT, Con_DemoSliceBegin, this, ""); m_pConsole->Register("demo_slice_end", "", CFGFLAG_CLIENT, Con_DemoSliceEnd, this, ""); @@ -4251,7 +4265,7 @@ Upstream latency */ -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) extern "C" int TWMain(int argc, const char **argv) // ignore_convention #else int main(int argc, const char **argv) // ignore_convention @@ -4265,10 +4279,12 @@ if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention { Silent = true; + } + else if(str_comp("-c", argv[i]) == 0 || str_comp("--console", argv[i]) == 0) // ignore_convention + { #if defined(CONF_FAMILY_WINDOWS) - FreeConsole(); + AllocConsole(); #endif - break; } } @@ -4285,7 +4301,7 @@ pClient->RegisterInterfaces(); // create the components - IEngine *pEngine = CreateEngine("DDNet", Silent, 1); + IEngine *pEngine = CreateEngine("DDNet", Silent, 2); IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention IConfigManager *pConfigManager = CreateConfigManager(); @@ -4293,7 +4309,6 @@ IEngineInput *pEngineInput = CreateEngineInput(); IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); IEngineMap *pEngineMap = CreateEngineMap(); - IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); IDiscord *pDiscord = CreateDiscord(); ISteam *pSteam = CreateSteam(); @@ -4322,9 +4337,6 @@ RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // IEngineMap RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMasterServer); // IEngineMasterServer - RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer), false); - RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor(), false); RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient(), false); RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); @@ -4343,8 +4355,6 @@ pEngine->Init(); pConfigManager->Init(); pConsole->Init(); - pEngineMasterServer->Init(); - pEngineMasterServer->Load(); // register all console commands pClient->RegisterCommands(); @@ -4362,6 +4372,11 @@ pConsole->ExecuteFile(CONFIG_FILE); } +#if defined(CONF_FAMILY_WINDOWS) + if(g_Config.m_ClShowConsole) + AllocConsole(); +#endif + // execute autoexec file File = pStorage->OpenFile(AUTOEXEC_CLIENT_FILE, IOFLAG_READ, IStorage::TYPE_ALL); if(File) @@ -4403,11 +4418,6 @@ pClient->Engine()->InitLogfile(); -#if defined(CONF_FAMILY_WINDOWS) - if(!g_Config.m_ClShowConsole) - FreeConsole(); -#endif - // run the client dbg_msg("client", "starting..."); pClient->Run(); @@ -4431,22 +4441,22 @@ // DDRace -const char *CClient::GetCurrentMap() +const char *CClient::GetCurrentMap() const { return m_aCurrentMap; } -const char *CClient::GetCurrentMapPath() +const char *CClient::GetCurrentMapPath() const { return m_aCurrentMapPath; } -SHA256_DIGEST CClient::GetCurrentMapSha256() +SHA256_DIGEST CClient::GetCurrentMapSha256() const { return m_pMap->Sha256(); } -unsigned CClient::GetCurrentMapCrc() +unsigned CClient::GetCurrentMapCrc() const { return m_pMap->Crc(); } diff -Nru ddnet-15.3.2/src/engine/client/client.h ddnet-15.5.4/src/engine/client/client.h --- ddnet-15.3.2/src/engine/client/client.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/client.h 2021-06-20 09:38:48.000000000 +0000 @@ -79,6 +79,8 @@ { public: bool m_ChatTimeoutCode; + bool m_AnyPlayerFlag; + bool m_PingEx; }; class CClient : public IClient, public CDemoPlayer::IListener @@ -98,7 +100,6 @@ IUpdater *m_pUpdater; IDiscord *m_pDiscord; ISteam *m_pSteam; - IEngineMasterServer *m_pMasterServer; enum { @@ -196,8 +197,6 @@ char m_aDDNetInfoTmp[64]; std::shared_ptr m_pDDNetInfoTask; - char m_aDummyNameBuf[16]; - // time CSmoothTime m_GameTime[NUM_DUMMIES]; CSmoothTime m_PredictedTime; @@ -244,6 +243,13 @@ class CServerInfo m_CurrentServerInfo; int64 m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info + int m_CurrentServerPingInfoType; + int m_CurrentServerPingBasicToken; + int m_CurrentServerPingToken; + CUuid m_CurrentServerPingUuid; + int64 m_CurrentServerCurrentPingTime; // >= 0 request running + int64 m_CurrentServerNextPingTime; // >= 0 should request + // version info struct CVersionInfo { @@ -277,7 +283,6 @@ IEngineInput *Input() { return m_pInput; } IEngineSound *Sound() { return m_pSound; } IGameClient *GameClient() { return m_pGameClient; } - IEngineMasterServer *MasterServer() { return m_pMasterServer; } IConfigManager *ConfigManager() { return m_pConfigManager; } CConfig *Config() { return m_pConfig; } IStorage *Storage() { return m_pStorage; } @@ -296,25 +301,25 @@ void SendReady(); void SendMapRequest(); - virtual bool RconAuthed() { return m_RconAuthed[g_Config.m_ClDummy] != 0; } - virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; } + virtual bool RconAuthed() const { return m_RconAuthed[g_Config.m_ClDummy] != 0; } + virtual bool UseTempRconCommands() const { return m_UseTempRconCommands != 0; } void RconAuth(const char *pName, const char *pPassword); virtual void Rcon(const char *pCmd); - virtual bool ConnectionProblems(); + virtual bool ConnectionProblems() const; - virtual bool SoundInitFailed() { return m_SoundInitFailed; } + virtual bool SoundInitFailed() const { return m_SoundInitFailed; } - virtual IGraphics::CTextureHandle GetDebugFont() { return m_DebugFont; } + virtual IGraphics::CTextureHandle GetDebugFont() const { return m_DebugFont; } void DirectInput(int *pInput, int Size); void SendInput(); // TODO: OPT: do this a lot smarter! - virtual int *GetInput(int Tick, int IsDummy); - virtual int *GetDirectInput(int Tick, int IsDummy); + virtual int *GetInput(int Tick, int IsDummy) const; + virtual int *GetDirectInput(int Tick, int IsDummy) const; - const char *LatestVersion(); + const char *LatestVersion() const; // ------ state handling ----- void SetState(int s); @@ -334,7 +339,7 @@ int m_DummyConnected; int m_LastDummyConnectTime; - virtual void GetServerInfo(CServerInfo *pServerInfo); + virtual void GetServerInfo(CServerInfo *pServerInfo) const; void ServerInfoRequest(); int LoadData(); @@ -342,11 +347,11 @@ // --- int GetPredictionTime(); - void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem); - int SnapItemSize(int SnapID, int Index); + void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const; + int SnapItemSize(int SnapID, int Index) const; void SnapInvalidateItem(int SnapID, int Index); - void *SnapFindItem(int SnapID, int Type, int ID); - int SnapNumItems(int SnapID); + void *SnapFindItem(int SnapID, int Type, int ID) const; + int SnapNumItems(int SnapID) const; void SnapSetStaticsize(int ItemType, int Size); void Render(); @@ -355,15 +360,13 @@ virtual void Restart(); virtual void Quit(); - virtual const char *PlayerName(); - virtual const char *DummyName(); - virtual const char *ErrorString(); + virtual const char *PlayerName() const; + virtual const char *DummyName() const; + virtual const char *ErrorString() const; 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 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); void ProcessServerPacket(CNetChunk *pPacket); @@ -378,9 +381,9 @@ void FinishDDNetInfo(); void LoadDDNetInfo(); - virtual const char *MapDownloadName() { return m_aMapdownloadName; } - virtual int MapDownloadAmount() { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); } - virtual int MapDownloadTotalsize() { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); } + virtual const char *MapDownloadName() const { return m_aMapdownloadName; } + virtual int MapDownloadAmount() const { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); } + virtual int MapDownloadTotalsize() const { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); } void PumpNetwork(); @@ -401,6 +404,7 @@ static void Con_DummyConnect(IConsole::IResult *pResult, void *pUserData); static void Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData); + static void Con_DummyResetInput(IConsole::IResult *pResult, void *pUserData); static void Con_Quit(IConsole::IResult *pResult, void *pUserData); static void Con_DemoPlay(IConsole::IResult *pResult, void *pUserData); @@ -466,12 +470,11 @@ void HandleMapPath(const char *pPath); // gfx - void SwitchWindowScreen(int Index); - void ToggleFullscreen(); - void ToggleWindowBordered(); - void ToggleWindowVSync(); - void LoadFont(); - void Notify(const char *pTitle, const char *pMessage); + virtual void SwitchWindowScreen(int Index); + virtual void SetWindowParams(int FullscreenMode, bool IsBorderless); + virtual void ToggleWindowVSync(); + virtual void LoadFont(); + virtual void Notify(const char *pTitle, const char *pMessage); void BenchmarkQuit(int Seconds, const char *pFilename); // DDRace @@ -481,10 +484,10 @@ virtual int GetCurrentRaceTime(); - virtual const char *GetCurrentMap(); - virtual const char *GetCurrentMapPath(); - virtual SHA256_DIGEST GetCurrentMapSha256(); - virtual unsigned GetCurrentMapCrc(); + virtual const char *GetCurrentMap() const; + virtual const char *GetCurrentMapPath() const; + virtual SHA256_DIGEST GetCurrentMapSha256() const; + virtual unsigned GetCurrentMapCrc() const; virtual void RaceRecord_Start(const char *pFilename); virtual void RaceRecord_Stop(); @@ -495,7 +498,7 @@ virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser); virtual void SaveReplay(const int Length); - virtual bool EditorHasUnsavedData() { return m_pEditor->HasUnsavedData(); } + virtual bool EditorHasUnsavedData() const { return m_pEditor->HasUnsavedData(); } virtual IFriends *Foes() { return &m_Foes; } diff -Nru ddnet-15.3.2/src/engine/client/friends.cpp ddnet-15.5.4/src/engine/client/friends.cpp --- ddnet-15.3.2/src/engine/client/friends.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/friends.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -153,7 +153,8 @@ for(int i = 0; i < m_NumFriends; ++i) { str_format(aBuf, sizeof(aBuf), "Name: %s, Clan: %s", m_aFriends[i].m_aName, m_aFriends[i].m_aClan); - pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_Foes ? "foes" : "friends", aBuf, true); + + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, m_Foes ? "foes" : "friends", aBuf, color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor))); } } } diff -Nru ddnet-15.3.2/src/engine/client/ghost.cpp ddnet-15.5.4/src/engine/client/ghost.cpp --- ddnet-15.3.2/src/engine/client/ghost.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/ghost.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -11,6 +11,8 @@ static const unsigned char gs_CurVersion = 6; static const int gs_NumTicksOffset = 93; +static const ColorRGBA gs_GhostPrintColor{0.6f, 0.6f, 0.6f, 1.0f}; + CGhostRecorder::CGhostRecorder() { m_File = 0; @@ -31,7 +33,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for ghost recording", pFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf, gs_GhostPrintColor); return -1; } @@ -50,7 +52,7 @@ char aBuf[256]; str_format(aBuf, sizeof(aBuf), "ghost recording to '%s'", pFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", aBuf, gs_GhostPrintColor); return 0; } @@ -135,7 +137,7 @@ if(!m_File) return -1; - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", "Stopped ghost recording"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "ghost_recorder", "Stopped ghost recording", gs_GhostPrintColor); FlushChunk(); diff -Nru ddnet-15.3.2/src/engine/client/graphics_defines.h ddnet-15.5.4/src/engine/client/graphics_defines.h --- ddnet-15.3.2/src/engine/client/graphics_defines.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/graphics_defines.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,12 @@ +#ifndef ENGINE_CLIENT_GRAPHICS_DEFINES_H +#define ENGINE_CLIENT_GRAPHICS_DEFINES_H + +#include +#include + +typedef uint32_t TWGLuint; +typedef int32_t TWGLint; +typedef uint32_t TWGLenum; +typedef uint8_t TWGLubyte; + +#endif diff -Nru ddnet-15.3.2/src/engine/client/graphics_threaded.cpp ddnet-15.5.4/src/engine/client/graphics_threaded.cpp --- ddnet-15.3.2/src/engine/client/graphics_threaded.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/graphics_threaded.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -33,31 +33,31 @@ #include "graphics_threaded.h" static CVideoMode g_aFakeModes[] = { - {8192, 4320, 8, 8, 8}, {7680, 4320, 8, 8, 8}, {5120, 2880, 8, 8, 8}, - {4096, 2160, 8, 8, 8}, {3840, 2160, 8, 8, 8}, {2560, 1440, 8, 8, 8}, - {2048, 1536, 8, 8, 8}, {1920, 2400, 8, 8, 8}, {1920, 1440, 8, 8, 8}, - {1920, 1200, 8, 8, 8}, {1920, 1080, 8, 8, 8}, {1856, 1392, 8, 8, 8}, - {1800, 1440, 8, 8, 8}, {1792, 1344, 8, 8, 8}, {1680, 1050, 8, 8, 8}, - {1600, 1200, 8, 8, 8}, {1600, 1000, 8, 8, 8}, {1440, 1050, 8, 8, 8}, - {1440, 900, 8, 8, 8}, {1400, 1050, 8, 8, 8}, {1368, 768, 8, 8, 8}, - {1280, 1024, 8, 8, 8}, {1280, 960, 8, 8, 8}, {1280, 800, 8, 8, 8}, - {1280, 768, 8, 8, 8}, {1152, 864, 8, 8, 8}, {1024, 768, 8, 8, 8}, - {1024, 600, 8, 8, 8}, {800, 600, 8, 8, 8}, {768, 576, 8, 8, 8}, - {720, 400, 8, 8, 8}, {640, 480, 8, 8, 8}, {400, 300, 8, 8, 8}, - {320, 240, 8, 8, 8}, - - {8192, 4320, 5, 6, 5}, {7680, 4320, 5, 6, 5}, {5120, 2880, 5, 6, 5}, - {4096, 2160, 5, 6, 5}, {3840, 2160, 5, 6, 5}, {2560, 1440, 5, 6, 5}, - {2048, 1536, 5, 6, 5}, {1920, 2400, 5, 6, 5}, {1920, 1440, 5, 6, 5}, - {1920, 1200, 5, 6, 5}, {1920, 1080, 5, 6, 5}, {1856, 1392, 5, 6, 5}, - {1800, 1440, 5, 6, 5}, {1792, 1344, 5, 6, 5}, {1680, 1050, 5, 6, 5}, - {1600, 1200, 5, 6, 5}, {1600, 1000, 5, 6, 5}, {1440, 1050, 5, 6, 5}, - {1440, 900, 5, 6, 5}, {1400, 1050, 5, 6, 5}, {1368, 768, 5, 6, 5}, - {1280, 1024, 5, 6, 5}, {1280, 960, 5, 6, 5}, {1280, 800, 5, 6, 5}, - {1280, 768, 5, 6, 5}, {1152, 864, 5, 6, 5}, {1024, 768, 5, 6, 5}, - {1024, 600, 5, 6, 5}, {800, 600, 5, 6, 5}, {768, 576, 5, 6, 5}, - {720, 400, 5, 6, 5}, {640, 480, 5, 6, 5}, {400, 300, 5, 6, 5}, - {320, 240, 5, 6, 5}}; + {8192, 4320, 8192, 4320, 8, 8, 8}, {7680, 4320, 7680, 4320, 8, 8, 8}, {5120, 2880, 5120, 2880, 8, 8, 8}, + {4096, 2160, 4096, 2160, 8, 8, 8}, {3840, 2160, 3840, 2160, 8, 8, 8}, {2560, 1440, 2560, 1440, 8, 8, 8}, + {2048, 1536, 2048, 1536, 8, 8, 8}, {1920, 2400, 1920, 2400, 8, 8, 8}, {1920, 1440, 1920, 1440, 8, 8, 8}, + {1920, 1200, 1920, 1200, 8, 8, 8}, {1920, 1080, 1920, 1080, 8, 8, 8}, {1856, 1392, 1856, 1392, 8, 8, 8}, + {1800, 1440, 1800, 1440, 8, 8, 8}, {1792, 1344, 1792, 1344, 8, 8, 8}, {1680, 1050, 1680, 1050, 8, 8, 8}, + {1600, 1200, 1600, 1200, 8, 8, 8}, {1600, 1000, 1600, 1000, 8, 8, 8}, {1440, 1050, 1440, 1050, 8, 8, 8}, + {1440, 900, 1440, 900, 8, 8, 8}, {1400, 1050, 1400, 1050, 8, 8, 8}, {1368, 768, 1368, 768, 8, 8, 8}, + {1280, 1024, 1280, 1024, 8, 8, 8}, {1280, 960, 1280, 960, 8, 8, 8}, {1280, 800, 1280, 800, 8, 8, 8}, + {1280, 768, 1280, 768, 8, 8, 8}, {1152, 864, 1152, 864, 8, 8, 8}, {1024, 768, 1024, 768, 8, 8, 8}, + {1024, 600, 1024, 600, 8, 8, 8}, {800, 600, 800, 600, 8, 8, 8}, {768, 576, 768, 576, 8, 8, 8}, + {720, 400, 720, 400, 8, 8, 8}, {640, 480, 640, 480, 8, 8, 8}, {400, 300, 400, 300, 8, 8, 8}, + {320, 240, 320, 240, 8, 8, 8}, + + {8192, 4320, 8192, 4320, 5, 6, 5}, {7680, 4320, 7680, 4320, 5, 6, 5}, {5120, 2880, 5120, 2880, 5, 6, 5}, + {4096, 2160, 4096, 2160, 5, 6, 5}, {3840, 2160, 3840, 2160, 5, 6, 5}, {2560, 1440, 2560, 1440, 5, 6, 5}, + {2048, 1536, 2048, 1536, 5, 6, 5}, {1920, 2400, 1920, 2400, 5, 6, 5}, {1920, 1440, 1920, 1440, 5, 6, 5}, + {1920, 1200, 1920, 1200, 5, 6, 5}, {1920, 1080, 1920, 1080, 5, 6, 5}, {1856, 1392, 1856, 1392, 5, 6, 5}, + {1800, 1440, 1800, 1440, 5, 6, 5}, {1792, 1344, 1792, 1344, 5, 6, 5}, {1680, 1050, 1680, 1050, 5, 6, 5}, + {1600, 1200, 1600, 1200, 5, 6, 5}, {1600, 1000, 1600, 1000, 5, 6, 5}, {1440, 1050, 1440, 1050, 5, 6, 5}, + {1440, 900, 1440, 900, 5, 6, 5}, {1400, 1050, 1400, 1050, 5, 6, 5}, {1368, 768, 1368, 768, 5, 6, 5}, + {1280, 1024, 1280, 1024, 5, 6, 5}, {1280, 960, 1280, 960, 5, 6, 5}, {1280, 800, 1280, 800, 5, 6, 5}, + {1280, 768, 1280, 768, 5, 6, 5}, {1152, 864, 1152, 864, 5, 6, 5}, {1024, 768, 1024, 768, 5, 6, 5}, + {1024, 600, 1024, 600, 5, 6, 5}, {800, 600, 800, 600, 5, 6, 5}, {768, 576, 768, 576, 5, 6, 5}, + {720, 400, 720, 400, 5, 6, 5}, {640, 480, 640, 480, 5, 6, 5}, {400, 300, 400, 300, 5, 6, 5}, + {320, 240, 320, 240, 5, 6, 5}}; void CGraphics_Threaded::FlushVertices(bool KeepVertices) { @@ -272,7 +272,8 @@ CCommandBuffer::SCommand_Texture_Destroy Cmd; Cmd.m_Slot = Index; - m_pCommandBuffer->AddCommand(Cmd); + AddCmd( + Cmd, [] { return true; }, "failed to unload texture."); m_TextureIndices[Index] = m_FirstFreeTexture; m_FirstFreeTexture = Index; @@ -325,13 +326,8 @@ mem_copy(pTmpData, pData, MemSize); Cmd.m_pData = pTmpData; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - // kick command buffer and try again - KickCommandBuffer(); - m_pCommandBuffer->AddCommand(Cmd); - } + AddCmd( + Cmd, [] { return true; }, "failed to load raw sub texture."); return 0; } @@ -460,10 +456,6 @@ Cmd.m_Flags = 0; if(Flags & IGraphics::TEXLOAD_NOMIPMAPS) Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NOMIPMAPS; - if(g_Config.m_GfxTextureCompressionOld && ((Flags & IGraphics::TEXLOAD_NO_COMPRESSION) == 0)) - Cmd.m_Flags |= CCommandBuffer::TEXFLAG_COMPRESSED; - if(g_Config.m_GfxTextureQualityOld || Flags & TEXLOAD_NORESAMPLE) - Cmd.m_Flags |= CCommandBuffer::TEXFLAG_QUALITY; if((Flags & IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE) != 0) Cmd.m_Flags |= CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE; if((Flags & IGraphics::TEXLOAD_TO_3D_TEXTURE) != 0) @@ -481,13 +473,8 @@ mem_copy(pTmpData, pData, MemSize); Cmd.m_pData = pTmpData; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - // kick command buffer and try again - KickCommandBuffer(); - m_pCommandBuffer->AddCommand(Cmd); - } + AddCmd( + Cmd, [] { return true; }, "failed to load raw texture."); return CreateTextureHandle(Tex); } @@ -669,7 +656,8 @@ CCommandBuffer::SCommand_Screenshot Cmd; Cmd.m_pImage = &Image; - m_pCommandBuffer->AddCommand(Cmd); + AddCmd( + Cmd, [] { return true; }, "failed to take screenshot."); // kick the buffer and wait for the result KickCommandBuffer(); @@ -688,9 +676,9 @@ // save png char aBuf[256]; str_format(aBuf, sizeof(aBuf), "saved screenshot to '%s'", aWholePath); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf, ColorRGBA{1.0f, 0.6f, 0.3f, 1.0f}); png_open_file_write(&Png, aWholePath); // ignore_convention - png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR, (unsigned char *)Image.m_pData); // ignore_convention + png_set_data(&Png, Image.m_Width, Image.m_Height, 8, PNG_TRUECOLOR_ALPHA, (unsigned char *)Image.m_pData); // ignore_convention png_close_file(&Png); // ignore_convention free(Image.m_pData); @@ -710,7 +698,8 @@ Cmd.m_Color.g = g; Cmd.m_Color.b = b; Cmd.m_Color.a = 0; - m_pCommandBuffer->AddCommand(Cmd); + AddCmd( + Cmd, [] { return true; }, "failed to clear graphics."); } void CGraphics_Threaded::QuadsBegin() @@ -1126,26 +1115,21 @@ Cmd.m_pIndicesOffsets = (char **)Data; Cmd.m_pDrawCount = (unsigned int *)(((char *)Data) + (sizeof(char *) * NumIndicesOffet)); - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Data = m_pCommandBuffer->AllocData((sizeof(char *) + sizeof(unsigned int)) * NumIndicesOffet); + if(Data == 0x0) + { + dbg_msg("graphics", "failed to allocate data for vertices"); + return false; + } + Cmd.m_pIndicesOffsets = (char **)Data; + Cmd.m_pDrawCount = (unsigned int *)(((char *)Data) + (sizeof(char *) * NumIndicesOffet)); + return true; + }, + "failed to allocate memory for render command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Data = m_pCommandBuffer->AllocData((sizeof(char *) + sizeof(unsigned int)) * NumIndicesOffet); - if(Data == 0x0) - { - dbg_msg("graphics", "failed to allocate data for vertices"); - return; - } - Cmd.m_pIndicesOffsets = (char **)Data; - Cmd.m_pDrawCount = (unsigned int *)(((char *)Data) + (sizeof(char *) * NumIndicesOffet)); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render command"); - return; - } + return; } mem_copy(Cmd.m_pIndicesOffsets, pOffsets, sizeof(char *) * NumIndicesOffet); @@ -1174,16 +1158,10 @@ Cmd.m_Dir[1] = pDir[1]; // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for render command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render command"); - return; - } + return; } } @@ -1207,16 +1185,10 @@ Cmd.m_Dir[1] = pDir[1]; // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for render command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render command"); - return; - } + return; } } @@ -1237,23 +1209,19 @@ return; // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_pQuadInfo = (SQuadRenderInfo *)m_pCommandBuffer->AllocData(QuadNum * sizeof(SQuadRenderInfo)); + if(Cmd.m_pQuadInfo == 0x0) + { + dbg_msg("graphics", "failed to allocate data for the quad info"); + return false; + } + return true; + }, + "failed to allocate memory for render quad command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pQuadInfo = (SQuadRenderInfo *)m_pCommandBuffer->AllocData(QuadNum * sizeof(SQuadRenderInfo)); - if(Cmd.m_pQuadInfo == 0x0) - { - dbg_msg("graphics", "failed to allocate data for the quad info"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render quad command"); - return; - } + return; } mem_copy(Cmd.m_pQuadInfo, pQuadInfo, sizeof(SQuadRenderInfo) * QuadNum); @@ -1274,17 +1242,10 @@ mem_copy(Cmd.m_aTextColor, pTextColor, sizeof(Cmd.m_aTextColor)); mem_copy(Cmd.m_aTextOutlineColor, pTextoutlineColor, sizeof(Cmd.m_aTextOutlineColor)); - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for render text command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render text command"); - return; - } + return; } } @@ -1490,17 +1451,10 @@ Cmd.m_pOffset = (void *)(QuadOffset * 6 * sizeof(unsigned int)); Cmd.m_BufferContainerIndex = Container.m_QuadBufferContainerIndex; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for render quad container")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render quad container"); - return; - } + return; } } else @@ -1573,17 +1527,10 @@ Cmd.m_Center.x = Quad.m_aVertices[0].m_Pos.x + (Quad.m_aVertices[1].m_Pos.x - Quad.m_aVertices[0].m_Pos.x) / 2.f; Cmd.m_Center.y = Quad.m_aVertices[0].m_Pos.y + (Quad.m_aVertices[2].m_Pos.y - Quad.m_aVertices[0].m_Pos.y) / 2.f; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for render quad container extended")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render quad container extended"); - return; - } + return; } } else @@ -1713,24 +1660,19 @@ } } - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_pRenderInfo = (IGraphics::SRenderSpriteInfo *)m_pCommandBuffer->AllocData(sizeof(IGraphics::SRenderSpriteInfo) * DrawCount); + if(Cmd.m_pRenderInfo == 0x0) + { + dbg_msg("graphics", "failed to allocate data for render info"); + return false; + } + return true; + }, + "failed to allocate memory for render quad container sprite")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pRenderInfo = (IGraphics::SRenderSpriteInfo *)m_pCommandBuffer->AllocData(sizeof(IGraphics::SRenderSpriteInfo) * DrawCount); - if(Cmd.m_pRenderInfo == 0x0) - { - dbg_msg("graphics", "failed to allocate data for render info"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for render quad container sprite"); - return; - } + return; } mem_copy(Cmd.m_pRenderInfo, pRenderInfo, sizeof(IGraphics::SRenderSpriteInfo) * DrawCount); @@ -1788,16 +1730,10 @@ { Cmd.m_pUploadData = pUploadData; - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for update buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for update buffer object command"); - return -1; - } + return -1; } } else @@ -1808,40 +1744,31 @@ if(Cmd.m_pUploadData == NULL) return -1; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); + if(Cmd.m_pUploadData == 0x0) + { + dbg_msg("graphics", "failed to allocate data for upload data"); + return false; + } + return true; + }, + "failed to allocate memory for create buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); - if(Cmd.m_pUploadData == 0x0) - { - dbg_msg("graphics", "failed to allocate data for upload data"); - return -1; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for create buffer object command"); - return -1; - } + return -1; } + mem_copy(Cmd.m_pUploadData, pUploadData, UploadDataSize); } else { Cmd.m_pUploadData = NULL; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for create buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for create buffer object command"); - return -1; - } + return -1; } // update the buffer instead @@ -1872,16 +1799,10 @@ { Cmd.m_pUploadData = pUploadData; - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for recreate buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for recreate buffer object command"); - return; - } + return; } } else @@ -1892,24 +1813,19 @@ if(Cmd.m_pUploadData == NULL) return; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); + if(Cmd.m_pUploadData == 0x0) + { + dbg_msg("graphics", "failed to allocate data for upload data"); + return false; + } + return true; + }, + "failed to allocate memory for recreate buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); - if(Cmd.m_pUploadData == 0x0) - { - dbg_msg("graphics", "failed to allocate data for upload data"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for recreate buffer object command"); - return; - } + return; } mem_copy(Cmd.m_pUploadData, pUploadData, UploadDataSize); @@ -1917,16 +1833,11 @@ else { Cmd.m_pUploadData = NULL; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for update buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for update buffer object command"); - return; - } + return; } // update the buffer instead @@ -1956,16 +1867,10 @@ { Cmd.m_pUploadData = pUploadData; - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for update buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for update buffer object command"); - return; - } + return; } } else @@ -1974,24 +1879,19 @@ if(Cmd.m_pUploadData == NULL) return; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); + if(Cmd.m_pUploadData == 0x0) + { + dbg_msg("graphics", "failed to allocate data for upload data"); + return false; + } + return true; + }, + "failed to allocate memory for update buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_pUploadData = m_pCommandBuffer->AllocData(UploadDataSize); - if(Cmd.m_pUploadData == 0x0) - { - dbg_msg("graphics", "failed to allocate data for upload data"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for update buffer object command"); - return; - } + return; } mem_copy(Cmd.m_pUploadData, pUploadData, UploadDataSize); @@ -2007,17 +1907,10 @@ Cmd.m_pReadOffset = ReadOffset; Cmd.m_CopySize = CopyDataSize; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for copy buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for copy buffer object command"); - return; - } + return; } } @@ -2029,17 +1922,10 @@ CCommandBuffer::SCommand_DeleteBufferObject Cmd; Cmd.m_BufferIndex = BufferIndex; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for delete buffer object command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for delete buffer object command"); - return; - } + return; } // also clear the buffer object index @@ -2071,24 +1957,19 @@ if(Cmd.m_Attributes == NULL) return -1; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_Attributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); + if(Cmd.m_Attributes == 0x0) + { + dbg_msg("graphics", "failed to allocate data for upload data"); + return false; + } + return true; + }, + "failed to allocate memory for create buffer container command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_Attributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); - if(Cmd.m_Attributes == 0x0) - { - dbg_msg("graphics", "failed to allocate data for upload data"); - return -1; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for create buffer container command"); - return -1; - } + return -1; } mem_copy(Cmd.m_Attributes, &pContainerInfo->m_Attributes[0], Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); @@ -2107,17 +1988,10 @@ Cmd.m_BufferContainerIndex = ContainerIndex; Cmd.m_DestroyAllBO = DestroyAllBO; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for delete buffer container command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for delete buffer container command"); - return; - } + return; } if(DestroyAllBO) @@ -2158,24 +2032,19 @@ if(Cmd.m_Attributes == NULL) return; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [&] { + Cmd.m_Attributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); + if(Cmd.m_Attributes == 0x0) + { + dbg_msg("graphics", "failed to allocate data for upload data"); + return false; + } + return true; + }, + "failed to allocate memory for update buffer container command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Cmd.m_Attributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); - if(Cmd.m_Attributes == 0x0) - { - dbg_msg("graphics", "failed to allocate data for upload data"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for update buffer container command"); - return; - } + return; } mem_copy(Cmd.m_Attributes, &pContainerInfo->m_Attributes[0], Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute)); @@ -2190,17 +2059,10 @@ CCommandBuffer::SCommand_IndicesRequiredNumNotify Cmd; Cmd.m_RequiredIndicesNum = RequiredIndicesCount; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Cmd)) + if(!AddCmd( + Cmd, [] { return true; }, "failed to allocate memory for indcies required count notify command")) { - // kick command buffer and try again - KickCommandBuffer(); - - if(!m_pCommandBuffer->AddCommand(Cmd)) - { - dbg_msg("graphics", "failed to allocate memory for indcies required count notify command"); - return; - } + return; } } @@ -2210,8 +2072,10 @@ if(g_Config.m_GfxBorderless) Flags |= IGraphicsBackend::INITFLAG_BORDERLESS; - if(g_Config.m_GfxFullscreen) + if(g_Config.m_GfxFullscreen == 1) Flags |= IGraphicsBackend::INITFLAG_FULLSCREEN; + else if(g_Config.m_GfxFullscreen == 2) + Flags |= IGraphicsBackend::INITFLAG_DESKTOP_FULLSCREEN; if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC; if(g_Config.m_GfxHighdpi) @@ -2219,7 +2083,7 @@ if(g_Config.m_GfxResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE; - int r = m_pBackend->Init("DDNet Client", &g_Config.m_GfxScreen, &g_Config.m_GfxScreenWidth, &g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags, &m_DesktopScreenWidth, &m_DesktopScreenHeight, &m_ScreenWidth, &m_ScreenHeight, m_pStorage); + int r = m_pBackend->Init("DDNet Client", &g_Config.m_GfxScreen, &g_Config.m_GfxScreenWidth, &g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags, &g_Config.m_GfxDesktopWidth, &g_Config.m_GfxDesktopHeight, &m_ScreenWidth, &m_ScreenHeight, m_pStorage); AddBackEndWarningIfExists(); m_IsNewOpenGL = m_pBackend->IsNewOpenGL(); m_OpenGLTileBufferingEnabled = m_IsNewOpenGL || m_pBackend->HasTileBuffering(); @@ -2227,6 +2091,7 @@ m_OpenGLQuadContainerBufferingEnabled = m_IsNewOpenGL || m_pBackend->HasQuadContainerBuffering(); m_OpenGLTextBufferingEnabled = m_IsNewOpenGL || (m_OpenGLQuadContainerBufferingEnabled && m_pBackend->HasTextBuffering()); m_OpenGLHasTextureArrays = m_IsNewOpenGL || m_pBackend->Has2DTextureArrays(); + m_ScreenHiDPIScale = m_ScreenWidth / (float)g_Config.m_GfxScreenWidth; return r; } @@ -2375,6 +2240,12 @@ if(InitWindow() != 0) return -1; + for(auto &FakeMode : g_aFakeModes) + { + FakeMode.m_WindowWidth = FakeMode.m_CanvasWidth / m_ScreenHiDPIScale; + FakeMode.m_WindowHeight = FakeMode.m_CanvasHeight / m_ScreenHiDPIScale; + } + // create command buffers for(auto &pCommandBuffer : m_apCommandBuffers) pCommandBuffer = new CCommandBuffer(CMD_BUFFER_CMD_BUFFER_SIZE, CMD_BUFFER_DATA_BUFFER_SIZE); @@ -2387,7 +2258,20 @@ 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff}; - m_InvalidTexture = LoadTextureRaw(4, 4, CImageInfo::FORMAT_RGBA, s_aNullTextureData, CImageInfo::FORMAT_RGBA, TEXLOAD_NORESAMPLE); + m_InvalidTexture = LoadTextureRaw(4, 4, CImageInfo::FORMAT_RGBA, s_aNullTextureData, CImageInfo::FORMAT_RGBA, 0); + + ColorRGBA GPUInfoPrintColor{0.6f, 0.5f, 1.0f, 1.0f}; + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "GPU vendor: %s", GetVendorString()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gfx", aBuf, GPUInfoPrintColor); + + str_format(aBuf, sizeof(aBuf), "GPU renderer: %s", GetRendererString()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gfx", aBuf, GPUInfoPrintColor); + + str_format(aBuf, sizeof(aBuf), "GPU version: %s", GetVersionString()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gfx", aBuf, GPUInfoPrintColor); + return 0; } @@ -2419,14 +2303,9 @@ m_pBackend->Maximize(); } -bool CGraphics_Threaded::Fullscreen(bool State) -{ - return m_pBackend->Fullscreen(State); -} - -void CGraphics_Threaded::SetWindowBordered(bool State) +void CGraphics_Threaded::SetWindowParams(int FullscreenMode, bool IsBorderless) { - m_pBackend->SetWindowBordered(State); + m_pBackend->SetWindowParams(FullscreenMode, IsBorderless); } bool CGraphics_Threaded::SetWindowScreen(int Index) @@ -2441,15 +2320,12 @@ return; #endif - if(m_DesktopScreenWidth == w && m_DesktopScreenHeight == h) + if(WindowWidth() == w && WindowHeight() == h) return; if(SetWindowSize) m_pBackend->ResizeWindow(w, h); - m_DesktopScreenWidth = w; - m_DesktopScreenHeight = h; - m_pBackend->GetViewportSize(m_ScreenWidth, m_ScreenHeight); // adjust the viewport to only allow certain aspect ratios @@ -2458,10 +2334,17 @@ if(m_ScreenWidth > 21 * m_ScreenHeight / 9) m_ScreenWidth = 21 * m_ScreenHeight / 9; - CCommandBuffer::SCommand_Resize Cmd; + CCommandBuffer::SCommand_Update_Viewport Cmd; + Cmd.m_X = 0; + Cmd.m_Y = 0; Cmd.m_Width = m_ScreenWidth; Cmd.m_Height = m_ScreenHeight; - m_pCommandBuffer->AddCommand(Cmd); + + if(!AddCmd( + Cmd, [] { return true; }, "failed to add resize command")) + { + return; + } // kick the command buffer KickCommandBuffer(); @@ -2535,10 +2418,25 @@ m_DoScreenshot = false; } - // add swap command - CCommandBuffer::SCommand_Swap Cmd; - Cmd.m_Finish = g_Config.m_GfxFinish; - m_pCommandBuffer->AddCommand(Cmd); + { + // add swap command + CCommandBuffer::SCommand_Swap Cmd; + if(!AddCmd( + Cmd, [] { return true; }, "failed to add swap command")) + { + return; + } + } + + if(g_Config.m_GfxFinish) + { + CCommandBuffer::SCommand_Finish Cmd; + if(!AddCmd( + Cmd, [] { return true; }, "failed to add finish command")) + { + return; + } + } // kick the command buffer KickCommandBuffer(); @@ -2554,7 +2452,12 @@ CCommandBuffer::SCommand_VSync Cmd; Cmd.m_VSync = State ? 1 : 0; Cmd.m_pRetOk = &RetOk; - m_pCommandBuffer->AddCommand(Cmd); + + if(!AddCmd( + Cmd, [] { return true; }, "failed to add vsync command")) + { + return false; + } // kick the command buffer KickCommandBuffer(); @@ -2567,10 +2470,15 @@ { CCommandBuffer::SCommand_Signal Cmd; Cmd.m_pSemaphore = pSemaphore; - m_pCommandBuffer->AddCommand(Cmd); + + if(!AddCmd( + Cmd, [] { return true; }, "failed to add signal command")) + { + return; + } } -bool CGraphics_Threaded::IsIdle() +bool CGraphics_Threaded::IsIdle() const { return m_pBackend->IsIdle(); } @@ -2591,9 +2499,24 @@ } } +const char *CGraphics_Threaded::GetVendorString() +{ + return m_pBackend->GetVendorString(); +} + +const char *CGraphics_Threaded::GetVersionString() +{ + return m_pBackend->GetVersionString(); +} + +const char *CGraphics_Threaded::GetRendererString() +{ + return m_pBackend->GetRendererString(); +} + int CGraphics_Threaded::GetVideoModes(CVideoMode *pModes, int MaxModes, int Screen) { - if(g_Config.m_GfxDisplayAllModes) + if(g_Config.m_GfxDisplayAllVideoModes) { int Count = sizeof(g_aFakeModes) / sizeof(CVideoMode); mem_copy(pModes, g_aFakeModes, sizeof(g_aFakeModes)); @@ -2611,8 +2534,16 @@ Cmd.m_pModes = pModes; Cmd.m_MaxModes = MaxModes; Cmd.m_pNumModes = &NumModes; + Cmd.m_HiDPIScale = m_ScreenHiDPIScale; + Cmd.m_MaxWindowWidth = g_Config.m_GfxDesktopWidth; + Cmd.m_MaxWindowHeight = g_Config.m_GfxDesktopHeight; Cmd.m_Screen = Screen; - m_pCommandBuffer->AddCommand(Cmd); + + if(!AddCmd( + Cmd, [] { return true; }, "failed to add video mode command")) + { + return 0; + } // kick the buffer and wait for the result and return it KickCommandBuffer(); diff -Nru ddnet-15.3.2/src/engine/client/graphics_threaded.h ddnet-15.5.4/src/engine/client/graphics_threaded.h --- ddnet-15.3.2/src/engine/client/graphics_threaded.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/graphics_threaded.h 2021-06-20 09:38:48.000000000 +0000 @@ -54,8 +54,8 @@ } unsigned char *DataPtr() { return m_pData; } - unsigned DataSize() { return m_Size; } - unsigned DataUsed() { return m_Used; } + unsigned DataSize() const { return m_Size; } + unsigned DataUsed() const { return m_Used; } }; public: @@ -119,12 +119,13 @@ // swap CMD_SWAP, + CMD_FINISH, // misc CMD_VSYNC, CMD_SCREENSHOT, CMD_VIDEOMODES, - CMD_RESIZE, + CMD_UPDATE_VIEWPORT, }; @@ -136,8 +137,6 @@ TEXFORMAT_ALPHA, TEXFLAG_NOMIPMAPS = 1, - TEXFLAG_COMPRESSED = 2, - TEXFLAG_QUALITY = 4, TEXFLAG_TO_3D_TEXTURE = (1 << 3), TEXFLAG_TO_2D_ARRAY_TEXTURE = (1 << 4), TEXFLAG_TO_3D_TEXTURE_SINGLE_LAYER = (1 << 5), @@ -498,6 +497,9 @@ CVideoMode *m_pModes; // processor will fill this in int m_MaxModes; // maximum of modes the processor can write to the m_pModes int *m_pNumModes; // processor will write to this pointer + int m_HiDPIScale; + int m_MaxWindowWidth; + int m_MaxWindowHeight; int m_Screen; }; @@ -505,8 +507,12 @@ { SCommand_Swap() : SCommand(CMD_SWAP) {} + }; - int m_Finish; + struct SCommand_Finish : public SCommand + { + SCommand_Finish() : + SCommand(CMD_FINISH) {} }; struct SCommand_VSync : public SCommand @@ -518,11 +524,13 @@ bool *m_pRetOk; }; - struct SCommand_Resize : public SCommand + struct SCommand_Update_Viewport : public SCommand { - SCommand_Resize() : - SCommand(CMD_RESIZE) {} + SCommand_Update_Viewport() : + SCommand(CMD_UPDATE_VIEWPORT) {} + int m_X; + int m_Y; int m_Width; int m_Height; }; @@ -581,7 +589,7 @@ } template - bool AddCommand(const T &Command) + bool AddCommandUnsafe(const T &Command) { // make sure that we don't do something stupid like ->AddCommand(&Cmd); (void)static_cast(&Command); @@ -640,6 +648,7 @@ INITFLAG_RESIZABLE = 1 << 2, INITFLAG_BORDERLESS = 1 << 3, INITFLAG_HIGHDPI = 1 << 4, + INITFLAG_DESKTOP_FULLSCREEN = 1 << 5, }; virtual ~IGraphicsBackend() {} @@ -653,8 +662,7 @@ virtual void Minimize() = 0; virtual void Maximize() = 0; - virtual bool Fullscreen(bool State) = 0; - virtual void SetWindowBordered(bool State) = 0; + virtual void SetWindowParams(int FullscreenMode, bool IsBorderless) = 0; virtual bool SetWindowScreen(int Index) = 0; virtual int GetWindowScreen() = 0; virtual int WindowActive() = 0; @@ -668,6 +676,9 @@ virtual bool IsIdle() const = 0; virtual void WaitForIdle() = 0; + virtual void GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch) {} + // checks if the current values of the config are a graphics modern API + virtual bool IsConfigModernAPI() { return false; } virtual bool IsNewOpenGL() { return false; } virtual bool HasTileBuffering() { return false; } virtual bool HasQuadBuffering() { return false; } @@ -675,6 +686,10 @@ virtual bool HasQuadContainerBuffering() { return false; } virtual bool Has2DTextureArrays() { return false; } virtual const char *GetErrorString() { return NULL; } + + virtual const char *GetVendorString() = 0; + virtual const char *GetVersionString() = 0; + virtual const char *GetRendererString() = 0; }; class CGraphics_Threaded : public IEngineGraphics @@ -803,6 +818,26 @@ } } + template + bool AddCmd(TName &Cmd, TFunc &&FailFunc, const char *pFailStr) + { + if(!m_pCommandBuffer->AddCommandUnsafe(Cmd)) + { + // kick command buffer and try again + KickCommandBuffer(); + + if(!FailFunc()) + return false; + + if(!m_pCommandBuffer->AddCommandUnsafe(Cmd)) + { + dbg_msg("graphics", "%s", pFailStr); + return false; + } + } + return true; + } + void KickCommandBuffer(); void AddBackEndWarningIfExists(); @@ -1065,24 +1100,20 @@ Command.m_PrimType = PrimType; Command.m_PrimCount = PrimCount; - // check if we have enough free memory in the commandbuffer - if(!m_pCommandBuffer->AddCommand(Command)) + if( + !AddCmd( + Command, [&] { + Command.m_pVertices = (decltype(Command.m_pVertices))m_pCommandBuffer->AllocData(VertSize * NumVerts); + if(Command.m_pVertices == NULL) + { + dbg_msg("graphics", "failed to allocate data for vertices"); + return false; + } + return true; + }, + "failed to allocate memory for render command")) { - // kick command buffer and try again - KickCommandBuffer(); - - Command.m_pVertices = (decltype(Command.m_pVertices))m_pCommandBuffer->AllocData(VertSize * NumVerts); - if(Command.m_pVertices == NULL) - { - dbg_msg("graphics", "failed to allocate data for vertices"); - return; - } - - if(!m_pCommandBuffer->AddCommand(Command)) - { - dbg_msg("graphics", "failed to allocate memory for render command"); - return; - } + return; } } @@ -1112,8 +1143,7 @@ int GetNumScreens() const override; void Minimize() override; void Maximize() override; - bool Fullscreen(bool State) override; - void SetWindowBordered(bool State) override; + void SetWindowParams(int FullscreenMode, bool IsBorderless) override; bool SetWindowScreen(int Index) override; void Resize(int w, int h, bool SetWindowSize = false) override; void AddWindowResizeListener(WINDOW_RESIZE_FUNC pFunc, void *pUser) override; @@ -1135,21 +1165,27 @@ int GetVideoModes(CVideoMode *pModes, int MaxModes, int Screen) override; - virtual int GetDesktopScreenWidth() { return m_DesktopScreenWidth; } - virtual int GetDesktopScreenHeight() { return m_DesktopScreenHeight; } + virtual int GetDesktopScreenWidth() const { return g_Config.m_GfxScreenWidth; } + virtual int GetDesktopScreenHeight() const { return g_Config.m_GfxScreenHeight; } // synchronization void InsertSignal(CSemaphore *pSemaphore) override; - bool IsIdle() override; + bool IsIdle() const override; void WaitForIdle() override; SWarning *GetCurWarning() override; + void GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch) override { m_pBackend->GetDriverVersion(DriverAgeType, Major, Minor, Patch); } + bool IsConfigModernAPI() override { return m_pBackend->IsConfigModernAPI(); } bool IsTileBufferingEnabled() override { return m_OpenGLTileBufferingEnabled; } bool IsQuadBufferingEnabled() override { return m_OpenGLQuadBufferingEnabled; } bool IsTextBufferingEnabled() override { return m_OpenGLTextBufferingEnabled; } bool IsQuadContainerBufferingEnabled() override { return m_OpenGLQuadContainerBufferingEnabled; } bool HasTextureArrays() override { return m_OpenGLHasTextureArrays; } + + const char *GetVendorString() override; + const char *GetVersionString() override; + const char *GetRendererString() override; }; extern IGraphicsBackend *CreateGraphicsBackend(); diff -Nru ddnet-15.3.2/src/engine/client/http.cpp ddnet-15.5.4/src/engine/client/http.cpp --- ddnet-15.3.2/src/engine/client/http.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/http.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -140,6 +140,7 @@ curl_easy_setopt(pHandle, CURLOPT_URL, m_aUrl); curl_easy_setopt(pHandle, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(pHandle, CURLOPT_USERAGENT, GAME_NAME " " GAME_RELEASE_VERSION " (" CONF_PLATFORM_STRING "; " CONF_ARCH_STRING ")"); + curl_easy_setopt(pHandle, CURLOPT_ACCEPT_ENCODING, ""); // Use any compression algorithm supported by libcurl. curl_easy_setopt(pHandle, CURLOPT_WRITEDATA, this); curl_easy_setopt(pHandle, CURLOPT_WRITEFUNCTION, WriteCallback); @@ -185,8 +186,24 @@ return pTask->m_Abort ? -1 : 0; } -CGet::CGet(const char *pUrl, CTimeout Timeout) : - CRequest(pUrl, Timeout), +CHead::CHead(const char *pUrl, CTimeout Timeout, bool LogProgress) : + CRequest(pUrl, Timeout, LogProgress) +{ +} + +CHead::~CHead() +{ +} + +bool CHead::AfterInit(void *pCurl) +{ + CURL *pHandle = pCurl; + curl_easy_setopt(pHandle, CURLOPT_NOBODY, 1L); + return true; +} + +CGet::CGet(const char *pUrl, CTimeout Timeout, bool LogProgress) : + CRequest(pUrl, Timeout, LogProgress), m_BufferSize(0), m_BufferLength(0), m_pBuffer(NULL) diff -Nru ddnet-15.3.2/src/engine/client/http.h ddnet-15.5.4/src/engine/client/http.h --- ddnet-15.3.2/src/engine/client/http.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/http.h 2021-06-20 09:38:48.000000000 +0000 @@ -66,6 +66,16 @@ void Abort() { m_Abort = true; } }; +class CHead : public CRequest +{ + virtual size_t OnData(char *pData, size_t DataSize) { return DataSize; } + virtual bool AfterInit(void *pCurl); + +public: + CHead(const char *pUrl, CTimeout Timeout, bool LogProgress = true); + ~CHead(); +}; + class CGet : public CRequest { virtual size_t OnData(char *pData, size_t DataSize); @@ -75,7 +85,7 @@ unsigned char *m_pBuffer; public: - CGet(const char *pUrl, CTimeout Timeout); + CGet(const char *pUrl, CTimeout Timeout, bool LogProgress = true); ~CGet(); size_t ResultSize() const diff -Nru ddnet-15.3.2/src/engine/client/input.cpp ddnet-15.5.4/src/engine/client/input.cpp --- ddnet-15.3.2/src/engine/client/input.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/input.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -89,8 +89,6 @@ m_InputGrabbed = 1; SDL_SetRelativeMouseMode(SDL_TRUE); Graphics()->SetWindowGrab(true); - // We should do this call to reset relative mouse position after alt+tab - SDL_GetRelativeMouseState(0x0, 0x0); } int CInput::MouseDoubleClick() @@ -336,6 +334,8 @@ MouseModeRelative(); m_MouseFocus = true; IgnoreKeys = true; + // We should do this call to reset relative mouse position after alt+tab + SDL_GetRelativeMouseState(0x0, 0x0); break; case SDL_WINDOWEVENT_FOCUS_LOST: m_MouseFocus = false; @@ -347,7 +347,7 @@ m_InputGrabbed = true; } break; -#if defined(CONF_PLATFORM_MACOSX) // Todo: remove this when fixed in SDL +#if defined(CONF_PLATFORM_MACOS) // Todo: remove this when fixed in SDL case SDL_WINDOWEVENT_MAXIMIZED: MouseModeAbsolute(); MouseModeRelative(); diff -Nru ddnet-15.3.2/src/engine/client/notifications.cpp ddnet-15.5.4/src/engine/client/notifications.cpp --- ddnet-15.3.2/src/engine/client/notifications.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/notifications.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -2,8 +2,8 @@ #include -#if defined(CONF_PLATFORM_MACOSX) -// Code is in src/osx/notification.mm. +#if defined(CONF_PLATFORM_MACOS) +// Code is in src/macos/notification.mm. #elif defined(CONF_FAMILY_UNIX) #include void NotificationsInit() diff -Nru ddnet-15.3.2/src/engine/client/opengl_sl.cpp ddnet-15.5.4/src/engine/client/opengl_sl.cpp --- ddnet-15.3.2/src/engine/client/opengl_sl.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/opengl_sl.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,279 +0,0 @@ -#include "opengl_sl.h" -#include -#include -#include -#include -#include - -bool CGLSL::LoadShader(CGLSLCompiler *pCompiler, IStorage *pStorage, const char *pFile, int Type) -{ - if(m_IsLoaded) - return true; - IOHANDLE f = pStorage->OpenFile(pFile, IOFLAG_READ, IStorage::TYPE_ALL); - - std::vector Lines; - if(f) - { - bool IsNewOpenGL = pCompiler->m_OpenGLVersionMajor >= 4 || (pCompiler->m_OpenGLVersionMajor == 3 && pCompiler->m_OpenGLVersionMinor == 3); - //add compiler specific values - if(IsNewOpenGL) - Lines.push_back(std::string("#version ") + std::string(std::to_string(pCompiler->m_OpenGLVersionMajor)) + std::string(std::to_string(pCompiler->m_OpenGLVersionMinor)) + std::string(std::to_string(pCompiler->m_OpenGLVersionPatch)) + std::string(" core\r\n")); - else - { - if(pCompiler->m_OpenGLVersionMajor == 3) - { - if(pCompiler->m_OpenGLVersionMinor == 0) - Lines.push_back(std::string("#version 130 \r\n")); - if(pCompiler->m_OpenGLVersionMinor == 1) - Lines.push_back(std::string("#version 140 \r\n")); - if(pCompiler->m_OpenGLVersionMinor == 2) - Lines.push_back(std::string("#version 150 \r\n")); - } - else if(pCompiler->m_OpenGLVersionMajor == 2) - { - if(pCompiler->m_OpenGLVersionMinor == 0) - Lines.push_back(std::string("#version 110 \r\n")); - if(pCompiler->m_OpenGLVersionMinor == 1) - Lines.push_back(std::string("#version 120 \r\n")); - } - } - - for(CGLSLCompiler::SGLSLCompilerDefine &Define : pCompiler->m_Defines) - { - Lines.push_back(std::string("#define ") + Define.m_DefineName + std::string(" ") + Define.m_DefineValue + std::string("\r\n")); - } - - if(Type == GL_FRAGMENT_SHADER && !IsNewOpenGL && pCompiler->m_OpenGLVersionMajor <= 3 && pCompiler->m_HasTextureArray) - { - Lines.push_back(std::string("#extension GL_EXT_texture_array : enable\r\n")); - } - - CLineReader LineReader; - LineReader.Init(f); - char *ReadLine = NULL; - while((ReadLine = LineReader.Get())) - { - std::string Line; - pCompiler->ParseLine(Line, ReadLine, Type); - Line.append("\r\n"); - Lines.push_back(Line); - } - io_close(f); - - const char **ShaderCode = new const char *[Lines.size()]; - - for(size_t i = 0; i < Lines.size(); ++i) - { - ShaderCode[i] = Lines[i].c_str(); - } - - GLuint shader = glCreateShader(Type); - - glShaderSource(shader, Lines.size(), ShaderCode, NULL); - glCompileShader(shader); - - delete[] ShaderCode; - - int CompilationStatus; - glGetShaderiv(shader, GL_COMPILE_STATUS, &CompilationStatus); - - if(CompilationStatus == GL_FALSE) - { - char buff[3000]; - - GLint maxLength = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - glGetShaderInfoLog(shader, maxLength, &maxLength, buff); - - dbg_msg("glsl", "%s: %s", pFile, buff); - glDeleteShader(shader); - return false; - } - m_Type = Type; - m_IsLoaded = true; - - m_ShaderID = shader; - - return true; - } - else - return false; -} - -void CGLSL::DeleteShader() -{ - if(!IsLoaded()) - return; - m_IsLoaded = false; - glDeleteShader(m_ShaderID); -} - -bool CGLSL::IsLoaded() -{ - return m_IsLoaded; -} - -GLuint CGLSL::GetShaderID() -{ - return m_ShaderID; -} - -CGLSL::CGLSL() -{ - m_IsLoaded = false; -} - -CGLSL::~CGLSL() -{ - DeleteShader(); -} - -CGLSLCompiler::CGLSLCompiler(int OpenGLVersionMajor, int OpenGLVersionMinor, int OpenGLVersionPatch) -{ - m_OpenGLVersionMajor = OpenGLVersionMajor; - m_OpenGLVersionMinor = OpenGLVersionMinor; - m_OpenGLVersionPatch = OpenGLVersionPatch; - - m_HasTextureArray = false; - m_TextureReplaceType = 0; -} - -void CGLSLCompiler::AddDefine(const std::string &DefineName, const std::string &DefineValue) -{ - m_Defines.emplace_back(SGLSLCompilerDefine(DefineName, DefineValue)); -} - -void CGLSLCompiler::AddDefine(const char *pDefineName, const char *pDefineValue) -{ - AddDefine(std::string(pDefineName), std::string(pDefineValue)); -} - -void CGLSLCompiler::ClearDefines() -{ - m_Defines.clear(); -} - -void CGLSLCompiler::ParseLine(std::string &Line, const char *pReadLine, int Type) -{ - bool IsNewOpenGL = m_OpenGLVersionMajor >= 4 || (m_OpenGLVersionMajor == 3 && m_OpenGLVersionMinor == 3); - if(!IsNewOpenGL) - { - const char *pBuff = pReadLine; - char aTmpStr[1024]; - size_t TmpStrSize = 0; - while(*pBuff) - { - while(*pBuff && str_isspace(*pBuff)) - { - Line.append(1, *pBuff); - ++pBuff; - } - - while(*pBuff && !str_isspace(*pBuff) && *pBuff != '(' && *pBuff != '.') - { - aTmpStr[TmpStrSize++] = *pBuff; - ++pBuff; - } - - if(TmpStrSize > 0) - { - aTmpStr[TmpStrSize] = 0; - TmpStrSize = 0; - if(str_comp(aTmpStr, "layout") == 0) - { - //search for ' in' - while(*pBuff && (*pBuff != ' ' || (*(pBuff + 1) && *(pBuff + 1) != 'i') || *(pBuff + 2) != 'n')) - { - ++pBuff; - } - - if(*pBuff == ' ' && *(pBuff + 1) && *(pBuff + 1) == 'i' && *(pBuff + 2) == 'n') - { - pBuff += 3; - Line.append("attribute"); - Line.append(pBuff); - return; - } - else - { - dbg_msg("shadercompiler", "Fix shader for older OpenGL versions."); - } - } - else if(str_comp(aTmpStr, "noperspective") == 0 || str_comp(aTmpStr, "smooth") == 0 || str_comp(aTmpStr, "flat") == 0) - { - //search for 'in' or 'out' - while(*pBuff && ((*pBuff != 'i' || *(pBuff + 1) != 'n') && (*pBuff != 'o' || (*(pBuff + 1) && *(pBuff + 1) != 'u') || *(pBuff + 2) != 't'))) - { - ++pBuff; - } - - bool Found = false; - if(*pBuff) - { - if(*pBuff == 'i' && *(pBuff + 1) == 'n') - { - pBuff += 2; - Found = true; - } - else if(*pBuff == 'o' && *(pBuff + 1) && *(pBuff + 1) == 'u' && *(pBuff + 2) == 't') - { - pBuff += 3; - Found = true; - } - } - - if(!Found) - { - dbg_msg("shadercompiler", "Fix shader for older OpenGL versions."); - } - - Line.append("varying"); - Line.append(pBuff); - return; - } - else if(str_comp(aTmpStr, "out") == 0 || str_comp(aTmpStr, "in") == 0) - { - if(Type == GL_FRAGMENT_SHADER && str_comp(aTmpStr, "out") == 0) - return; - Line.append("varying"); - Line.append(pBuff); - return; - } - else if(str_comp(aTmpStr, "FragClr") == 0) - { - Line.append("gl_FragColor"); - Line.append(pBuff); - return; - } - else if(str_comp(aTmpStr, "texture") == 0) - { - if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D) - Line.append("texture2D"); - else if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D) - Line.append("texture3D"); - else if(m_TextureReplaceType == GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY) - Line.append("texture2DArray"); - std::string RestLine; - ParseLine(RestLine, pBuff, Type); - Line.append(RestLine); - return; - } - else - { - Line.append(aTmpStr); - } - } - - if(*pBuff) - { - Line.append(1, *pBuff); - ++pBuff; - } - } - } - else - { - Line = pReadLine; - } -} diff -Nru ddnet-15.3.2/src/engine/client/opengl_sl.h ddnet-15.5.4/src/engine/client/opengl_sl.h --- ddnet-15.3.2/src/engine/client/opengl_sl.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/opengl_sl.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,71 +0,0 @@ -#ifndef ENGINE_CLIENT_OPENGL_SL_H -#define ENGINE_CLIENT_OPENGL_SL_H - -#include -#include -#include - -class CGLSLCompiler; - -class CGLSL -{ -public: - bool LoadShader(CGLSLCompiler *pCompiler, class IStorage *pStorage, const char *pFile, int Type); - void DeleteShader(); - - bool IsLoaded(); - GLuint GetShaderID(); - - CGLSL(); - virtual ~CGLSL(); - -private: - GLuint m_ShaderID; - int m_Type; - bool m_IsLoaded; -}; - -class CGLSLCompiler -{ -private: - friend class CGLSL; - - struct SGLSLCompilerDefine - { - SGLSLCompilerDefine(const std::string &DefineName, const std::string &DefineValue) - { - m_DefineName = DefineName; - m_DefineValue = DefineValue; - } - std::string m_DefineName; - std::string m_DefineValue; - }; - - std::vector m_Defines; - - int m_OpenGLVersionMajor; - int m_OpenGLVersionMinor; - int m_OpenGLVersionPatch; - - bool m_HasTextureArray; - int m_TextureReplaceType; // @see EGLSLCompilerTextureReplaceType -public: - CGLSLCompiler(int OpenGLVersionMajor, int OpenGLVersionMinor, int OpenGLVersionPatch); - void SetHasTextureArray(bool TextureArray) { m_HasTextureArray = TextureArray; } - void SetTextureReplaceType(int TextureReplaceType) { m_TextureReplaceType = TextureReplaceType; } - - void AddDefine(const std::string &DefineName, const std::string &DefineValue); - void AddDefine(const char *pDefineName, const char *pDefineValue); - void ClearDefines(); - - void ParseLine(std::string &Line, const char *pReadLine, int Type); - - enum EGLSLCompilerTextureReplaceType - { - GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D = 0, - GLSL_COMPILER_TEXTURE_REPLACE_TYPE_3D, - GLSL_COMPILER_TEXTURE_REPLACE_TYPE_2D_ARRAY, - }; -}; - -#endif // ENGINE_CLIENT_OPENGL_SL_H diff -Nru ddnet-15.3.2/src/engine/client/opengl_sl_program.cpp ddnet-15.5.4/src/engine/client/opengl_sl_program.cpp --- ddnet-15.3.2/src/engine/client/opengl_sl_program.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/opengl_sl_program.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,141 +0,0 @@ -#include "opengl_sl_program.h" -#include "opengl_sl.h" -#include - -void CGLSLProgram::CreateProgram() -{ - m_ProgramID = glCreateProgram(); -} - -void CGLSLProgram::DeleteProgram() -{ - if(!m_IsLinked) - return; - m_IsLinked = false; - glDeleteProgram(m_ProgramID); -} - -bool CGLSLProgram::AddShader(CGLSL *pShader) -{ - if(pShader->IsLoaded()) - { - glAttachShader(m_ProgramID, pShader->GetShaderID()); - return true; - } - return false; -} - -void CGLSLProgram::DetachShader(CGLSL *pShader) -{ - if(pShader->IsLoaded()) - { - DetachShaderByID(pShader->GetShaderID()); - } -} - -void CGLSLProgram::DetachShaderByID(GLuint ShaderID) -{ - glDetachShader(m_ProgramID, ShaderID); -} - -void CGLSLProgram::LinkProgram() -{ - glLinkProgram(m_ProgramID); - int LinkStatus; - glGetProgramiv(m_ProgramID, GL_LINK_STATUS, &LinkStatus); - m_IsLinked = LinkStatus == GL_TRUE; - if(!m_IsLinked) - { - char sInfoLog[1024]; - char sFinalMessage[1536]; - int iLogLength; - glGetProgramInfoLog(m_ProgramID, 1024, &iLogLength, sInfoLog); - str_format(sFinalMessage, 1536, "Error! Shader program wasn't linked! The linker returned:\n\n%s", sInfoLog); - dbg_msg("glslprogram", "%s", sFinalMessage); - } - - //detach all shaders attached to this program - DetachAllShaders(); -} - -void CGLSLProgram::DetachAllShaders() -{ - GLuint aShaders[100]; - GLsizei ReturnedCount = 0; - while(1) - { - glGetAttachedShaders(m_ProgramID, 100, &ReturnedCount, aShaders); - - if(ReturnedCount > 0) - { - for(GLsizei i = 0; i < ReturnedCount; ++i) - { - DetachShaderByID(aShaders[i]); - } - } - - if(ReturnedCount < 100) - break; - } -} - -void CGLSLProgram::SetUniformVec4(int Loc, int Count, const float *pValues) -{ - glUniform4fv(Loc, Count, pValues); -} - -void CGLSLProgram::SetUniformVec2(int Loc, int Count, const float *pValues) -{ - glUniform2fv(Loc, Count, pValues); -} - -void CGLSLProgram::SetUniform(int Loc, int Count, const float *pValues) -{ - glUniform1fv(Loc, Count, pValues); -} - -void CGLSLProgram::SetUniform(int Loc, const int Value) -{ - glUniform1i(Loc, Value); -} - -void CGLSLProgram::SetUniform(int Loc, const unsigned int Value) -{ - glUniform1ui(Loc, Value); -} - -void CGLSLProgram::SetUniform(int Loc, const float Value) -{ - glUniform1f(Loc, Value); -} - -void CGLSLProgram::SetUniform(int Loc, const bool Value) -{ - glUniform1i(Loc, (int)Value); -} - -int CGLSLProgram::GetUniformLoc(const char *Name) -{ - return glGetUniformLocation(m_ProgramID, Name); -} - -void CGLSLProgram::UseProgram() -{ - if(m_IsLinked) - glUseProgram(m_ProgramID); -} - -GLuint CGLSLProgram::GetProgramID() -{ - return m_ProgramID; -} - -CGLSLProgram::CGLSLProgram() -{ - m_IsLinked = false; -} - -CGLSLProgram::~CGLSLProgram() -{ - DeleteProgram(); -} diff -Nru ddnet-15.3.2/src/engine/client/opengl_sl_program.h ddnet-15.5.4/src/engine/client/opengl_sl_program.h --- ddnet-15.3.2/src/engine/client/opengl_sl_program.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/opengl_sl_program.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,151 +0,0 @@ -#ifndef ENGINE_CLIENT_OPENGL_SL_PROGRAM_H -#define ENGINE_CLIENT_OPENGL_SL_PROGRAM_H - -#include - -class CGLSL; - -class CGLSLProgram -{ -public: - void CreateProgram(); - void DeleteProgram(); - - bool AddShader(CGLSL *pShader); - - void LinkProgram(); - void UseProgram(); - GLuint GetProgramID(); - - void DetachShader(CGLSL *pShader); - void DetachShaderByID(GLuint ShaderID); - void DetachAllShaders(); - - //Support various types - void SetUniformVec2(int Loc, int Count, const float *pValue); - void SetUniformVec4(int Loc, int Count, const float *pValue); - void SetUniform(int Loc, const int Value); - void SetUniform(int Loc, const unsigned int Value); - void SetUniform(int Loc, const bool Value); - void SetUniform(int Loc, const float Value); - void SetUniform(int Loc, int Count, const float *pValues); - - //for performance reason we do not use SetUniform with using strings... save the Locations of the variables instead - int GetUniformLoc(const char *Name); - - CGLSLProgram(); - virtual ~CGLSLProgram(); - -protected: - GLuint m_ProgramID; - bool m_IsLinked; -}; - -class CGLSLTWProgram : public CGLSLProgram -{ -public: - CGLSLTWProgram() : - m_LocPos(-1), m_LocTextureSampler(-1), m_LastTextureSampler(-1), m_LastIsTextured(-1) - { - m_LastScreen[0] = m_LastScreen[1] = m_LastScreen[2] = m_LastScreen[3] = -1.f; - } - - int m_LocPos; - int m_LocTextureSampler; - - int m_LastTextureSampler; - int m_LastIsTextured; - float m_LastScreen[4]; -}; - -class CGLSLTextProgram : public CGLSLTWProgram -{ -public: - CGLSLTextProgram() : - CGLSLTWProgram() - { - m_LastColor[0] = m_LastColor[1] = m_LastColor[2] = m_LastColor[3] = -1.f; - m_LastOutlineColor[0] = m_LastOutlineColor[1] = m_LastOutlineColor[2] = m_LastOutlineColor[3] = -1.f; - m_LastTextSampler = m_LastTextOutlineSampler = -1; - m_LastTextureSize = -1; - } - - int m_LocColor; - int m_LocOutlineColor; - int m_LocTextSampler; - int m_LocTextOutlineSampler; - int m_LocTextureSize; - - float m_LastColor[4]; - float m_LastOutlineColor[4]; - int m_LastTextSampler; - int m_LastTextOutlineSampler; - int m_LastTextureSize; -}; - -class CGLSLPrimitiveProgram : public CGLSLTWProgram -{ -public: -}; - -class CGLSLPrimitiveExProgram : public CGLSLTWProgram -{ -public: - CGLSLPrimitiveExProgram() : - CGLSLTWProgram() - { - m_LastRotation = 0.f; - m_LastCenter[0] = m_LastCenter[1] = 0.f; - m_LastVertciesColor[0] = m_LastVertciesColor[1] = m_LastVertciesColor[2] = m_LastVertciesColor[3] = -1.f; - } - - int m_LocRotation; - int m_LocCenter; - int m_LocVertciesColor; - - float m_LastRotation; - float m_LastCenter[2]; - float m_LastVertciesColor[4]; -}; - -class CGLSLSpriteMultipleProgram : public CGLSLTWProgram -{ -public: - CGLSLSpriteMultipleProgram() : - CGLSLTWProgram() - { - m_LastCenter[0] = m_LastCenter[1] = 0.f; - m_LastVertciesColor[0] = m_LastVertciesColor[1] = m_LastVertciesColor[2] = m_LastVertciesColor[3] = -1.f; - } - - int m_LocRSP; - int m_LocCenter; - int m_LocVertciesColor; - - float m_LastCenter[2]; - float m_LastVertciesColor[4]; -}; - -class CGLSLQuadProgram : public CGLSLTWProgram -{ -public: - int m_LocColors; - int m_LocOffsets; - int m_LocRotations; - int m_LocQuadOffset; -}; - -class CGLSLTileProgram : public CGLSLTWProgram -{ -public: - CGLSLTileProgram() : - m_LocColor(-1), m_LocOffset(-1), m_LocDir(-1), m_LocNum(-1), m_LocJumpIndex(-1) {} - - int m_LocColor; - int m_LocOffset; - int m_LocDir; - int m_LocNum; - int m_LocJumpIndex; -}; - -#endif // ENGINE_CLIENT_OPENGL_SL_PROGRAM_H diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser.cpp ddnet-15.5.4/src/engine/client/serverbrowser.cpp --- ddnet-15.3.2/src/engine/client/serverbrowser.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,6 +1,12 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include // sort TODO: remove this +#include "serverbrowser.h" + +#include "serverbrowser_http.h" +#include "serverbrowser_ping_cache.h" + +#include +#include #include #include @@ -11,18 +17,18 @@ #include #include #include +#include #include #include +#include #include -#include #include #include #include -#include "serverbrowser.h" class SortWrap { typedef bool (CServerBrowser::*SortFunc)(int, int) const; @@ -37,7 +43,6 @@ CServerBrowser::CServerBrowser() { - m_pMasterServer = 0; m_ppServerlist = 0; m_pSortedServerlist = 0; @@ -49,8 +54,6 @@ m_pLastReqServer = 0; m_NumRequests = 0; - m_NeedRefresh = 0; - m_NumSortedServers = 0; m_NumSortedServersCapacity = 0; m_NumServers = 0; @@ -80,18 +83,94 @@ if(m_pDDNetInfo) json_value_free(m_pDDNetInfo); + + delete m_pHttp; + m_pHttp = nullptr; + delete m_pPingCache; + m_pPingCache = nullptr; } void CServerBrowser::SetBaseInfo(class CNetClient *pClient, const char *pNetVersion) { m_pNetClient = pClient; str_copy(m_aNetVersion, pNetVersion, sizeof(m_aNetVersion)); - m_pMasterServer = Kernel()->RequestInterface(); m_pConsole = Kernel()->RequestInterface(); + m_pEngine = Kernel()->RequestInterface(); m_pFriends = Kernel()->RequestInterface(); + m_pStorage = Kernel()->RequestInterface(); IConfigManager *pConfigManager = Kernel()->RequestInterface(); if(pConfigManager) pConfigManager->RegisterCallback(ConfigSaveCallback, this); + m_pPingCache = CreateServerBrowserPingCache(m_pConsole, m_pStorage); + + RegisterCommands(); +} + +void CServerBrowser::OnInit() +{ + m_pHttp = CreateServerBrowserHttp(m_pEngine, m_pConsole, m_pStorage, g_Config.m_BrCachedBestServerinfoUrl); +} + +void CServerBrowser::RegisterCommands() +{ + m_pConsole->Register("leak_ip_address_to_all_servers", "", CFGFLAG_CLIENT, Con_LeakIpAddress, this, "Leaks your IP address to all servers by pinging each of them, also acquiring the latency in the process"); +} + +void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData) +{ + CServerBrowser *pThis = (CServerBrowser *)pUserData; + + std::vector aSortedServers; + // Sort servers by IP address, ignoring port. + class CAddrComparer + { + public: + CServerBrowser *m_pThis; + bool operator()(int i, int j) + { + NETADDR Addr1 = m_pThis->m_ppServerlist[i]->m_Addr; + NETADDR Addr2 = m_pThis->m_ppServerlist[j]->m_Addr; + Addr1.port = 0; + Addr2.port = 0; + return net_addr_comp(&Addr1, &Addr2) < 0; + } + }; + aSortedServers.reserve(pThis->m_NumServers); + for(int i = 0; i < pThis->m_NumServers; i++) + { + aSortedServers.push_back(i); + } + std::sort(aSortedServers.begin(), aSortedServers.end(), CAddrComparer{pThis}); + + // Group the servers into those with same IP address (but differing + // port). + NETADDR Addr; + int Start = -1; + for(int i = 0; i <= (int)aSortedServers.size(); i++) + { + NETADDR NextAddr; + if(i < (int)aSortedServers.size()) + { + NextAddr = pThis->m_ppServerlist[aSortedServers[i]]->m_Addr; + NextAddr.port = 0; + } + bool New = Start == -1 || i == (int)aSortedServers.size() || net_addr_comp(&Addr, &NextAddr) != 0; + if(Start != -1 && New) + { + int Chosen = Start + secure_rand_below(i - Start); + CServerEntry *pChosen = pThis->m_ppServerlist[aSortedServers[Chosen]]; + pChosen->m_RequestIgnoreInfo = true; + pThis->QueueRequest(pChosen); + char aAddr[NETADDR_MAXSTRSIZE]; + net_addr_str(&pChosen->m_Addr, aAddr, sizeof(aAddr), true); + dbg_msg("serverbrowse/dbg", "queuing ping request for %s", aAddr); + } + if(i < (int)aSortedServers.size() && New) + { + Start = i; + Addr = NextAddr; + } + } } const CServerInfo *CServerBrowser::SortedGet(int Index) const @@ -330,15 +409,12 @@ void SetFilteredPlayers(const CServerInfo &Item) { - if(g_Config.m_BrFilterSpectators) - Item.m_NumFilteredPlayers = Item.m_NumPlayers; - else - Item.m_NumFilteredPlayers = Item.m_NumClients; + Item.m_NumFilteredPlayers = g_Config.m_BrFilterSpectators ? Item.m_NumPlayers : Item.m_NumClients; if(g_Config.m_BrFilterConnectingPlayers) { for(const auto &Client : Item.m_aClients) { - if(str_comp(Client.m_aName, "(connecting)") == 0 && Client.m_aClan[0] == '\0' && Client.m_Country == -1 && Client.m_Score == 0) + if((!g_Config.m_BrFilterSpectators || Client.m_Player) && str_comp(Client.m_aName, "(connecting)") == 0 && Client.m_aClan[0] == '\0') Item.m_NumFilteredPlayers--; } } @@ -429,29 +505,58 @@ pEntry->m_Info.m_Favorite = Fav; pEntry->m_Info.m_Official = Off; pEntry->m_Info.m_NetAddr = pEntry->m_Addr; + net_addr_str(&pEntry->m_Info.m_NetAddr, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aAddress), 1); - // all these are just for nice compatibility - if(pEntry->m_Info.m_aGameType[0] == '0' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "DM", sizeof(pEntry->m_Info.m_aGameType)); - else if(pEntry->m_Info.m_aGameType[0] == '1' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "TDM", sizeof(pEntry->m_Info.m_aGameType)); - else if(pEntry->m_Info.m_aGameType[0] == '2' && pEntry->m_Info.m_aGameType[1] == 0) - str_copy(pEntry->m_Info.m_aGameType, "CTF", sizeof(pEntry->m_Info.m_aGameType)); - - /*if(!request) + class CPlayerScoreNameLess { - pEntry->m_Info.latency = (time_get()-pEntry->request_time)*1000/time_freq(); - RemoveRequest(pEntry); - }*/ + public: + bool operator()(const CServerInfo::CClient &p0, const CServerInfo::CClient &p1) + { + 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; + if(Score0 == -9999) + Score0 = INT_MIN; + if(Score1 == -9999) + Score1 = INT_MIN; + + if(Score0 > Score1) + return true; + if(Score0 < Score1) + return false; + return str_comp_nocase(p0.m_aName, p1.m_aName) < 0; + } + }; + + std::sort(pEntry->m_Info.m_aClients, pEntry->m_Info.m_aClients + Info.m_NumReceivedClients, CPlayerScoreNameLess()); pEntry->m_GotInfo = 1; } +void CServerBrowser::SetLatency(NETADDR Addr, int Latency) +{ + Addr.port = 0; + for(CServerEntry *pEntry = m_aServerlistIp[Addr.ip[0]]; pEntry; pEntry = pEntry->m_pNextIp) + { + NETADDR Other = pEntry->m_Addr; + Other.port = 0; + if(net_addr_comp(&Addr, &Other) == 0 && pEntry->m_GotInfo) + { + pEntry->m_Info.m_Latency = Latency; + pEntry->m_Info.m_LatencyIsEstimated = false; + } + } + m_pPingCache->CachePing(Addr, Latency); +} + CServerBrowser::CServerEntry *CServerBrowser::Add(const NETADDR &Addr) { int Hash = Addr.ip[0]; CServerEntry *pEntry = 0; - int i; // create new pEntry pEntry = (CServerEntry *)m_ServerlistHeap.Allocate(sizeof(CServerEntry)); @@ -467,14 +572,7 @@ str_copy(pEntry->m_Info.m_aName, pEntry->m_Info.m_aAddress, sizeof(pEntry->m_Info.m_aName)); // check if it's a favorite - for(i = 0; i < m_NumFavoriteServers; i++) - { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - { - pEntry->m_Info.m_Favorite = true; - break; - } - } + pEntry->m_Info.m_Favorite = IsFavorite(Addr); // check if it's an official server for(auto &Network : m_aNetworks) @@ -523,7 +621,6 @@ { if(m_ServerlistType != IServerBrowser::TYPE_INTERNET) return; - m_LastPacketTick = 0; if(!Find(Addr)) { pEntry = Add(Addr); @@ -563,6 +660,18 @@ QueueRequest(pEntry); } } + else if(Type == IServerBrowser::SET_HTTPINFO) + { + if(!pEntry) + { + pEntry = Add(Addr); + } + if(pEntry) + { + SetInfo(pEntry, *pInfo); + pEntry->m_RequestIgnoreInfo = true; + } + } else if(Type == IServerBrowser::SET_TOKEN) { int BasicToken = Token; @@ -608,13 +717,47 @@ } } - SetInfo(pEntry, *pInfo); if(m_ServerlistType == IServerBrowser::TYPE_LAN) + { + SetInfo(pEntry, *pInfo); pEntry->m_Info.m_Latency = minimum(static_cast((time_get() - m_BroadcastTime) * 1000 / time_freq()), 999); + if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo)) + { + pEntry->m_Request64Legacy = true; + // Force a quick update. + RequestImpl64(pEntry->m_Addr, pEntry); + } + } else if(pEntry->m_RequestTime > 0) { - pEntry->m_Info.m_Latency = minimum(static_cast((time_get() - pEntry->m_RequestTime) * 1000 / time_freq()), 999); + if(!pEntry->m_RequestIgnoreInfo) + { + SetInfo(pEntry, *pInfo); + } + + int Latency = minimum(static_cast((time_get() - pEntry->m_RequestTime) * 1000 / time_freq()), 999); + if(!pEntry->m_RequestIgnoreInfo) + { + pEntry->m_Info.m_Latency = Latency; + } + else + { + char aAddr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddr, sizeof(aAddr), true); + dbg_msg("serverbrowse/dbg", "received ping response from %s", aAddr); + SetLatency(Addr, Latency); + } pEntry->m_RequestTime = -1; // Request has been answered + + if(!pEntry->m_RequestIgnoreInfo) + { + if(pInfo->m_Type == SERVERINFO_VANILLA && Is64Player(pInfo)) + { + pEntry->m_Request64Legacy = true; + // Force a quick update. + RequestImpl64(pEntry->m_Addr, pEntry); + } + } } RemoveRequest(pEntry); } @@ -671,78 +814,15 @@ if(g_Config.m_Debug) m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client_srvbrowse", "broadcasting for servers"); } - else if(Type == IServerBrowser::TYPE_INTERNET) - m_NeedRefresh = 1; - else if(Type == IServerBrowser::TYPE_FAVORITES) + else if(Type == IServerBrowser::TYPE_FAVORITES || Type == IServerBrowser::TYPE_INTERNET || Type == IServerBrowser::TYPE_DDNET || Type == IServerBrowser::TYPE_KOG) { - for(int i = 0; i < m_NumFavoriteServers; i++) - Set(m_aFavoriteServers[i], IServerBrowser::SET_FAV_ADD, -1, 0); - } - else if(Type == IServerBrowser::TYPE_DDNET) - { - // remove unknown elements of exclude list - CountryFilterClean(NETWORK_DDNET); - TypeFilterClean(NETWORK_DDNET); - - int MaxServers = 0; - for(int i = 0; i < m_aNetworks[NETWORK_DDNET].m_NumCountries; i++) - { - CNetworkCountry *pCntr = &m_aNetworks[NETWORK_DDNET].m_aCountries[i]; - MaxServers = maximum(MaxServers, pCntr->m_NumServers); - } - - for(int g = 0; g < MaxServers; g++) - { - for(int i = 0; i < m_aNetworks[NETWORK_DDNET].m_NumCountries; i++) - { - CNetworkCountry *pCntr = &m_aNetworks[NETWORK_DDNET].m_aCountries[i]; - - // check for filter - if(DDNetFiltered(g_Config.m_BrFilterExcludeCountries, pCntr->m_aName)) - continue; - - if(g >= pCntr->m_NumServers) - continue; - - if(!DDNetFiltered(g_Config.m_BrFilterExcludeTypes, pCntr->m_aTypes[g])) - Set(pCntr->m_aServers[g], IServerBrowser::SET_DDNET_ADD, -1, 0); - } - } - } - else if(Type == IServerBrowser::TYPE_KOG) - { - // remove unknown elements of exclude list - CountryFilterClean(NETWORK_KOG); - TypeFilterClean(NETWORK_KOG); - - int MaxServers = 0; - for(int i = 0; i < m_aNetworks[NETWORK_KOG].m_NumCountries; i++) - { - CNetworkCountry *pCntr = &m_aNetworks[NETWORK_KOG].m_aCountries[i]; - MaxServers = maximum(MaxServers, pCntr->m_NumServers); - } - - for(int g = 0; g < MaxServers; g++) - { - for(int i = 0; i < m_aNetworks[NETWORK_KOG].m_NumCountries; i++) - { - CNetworkCountry *pCntr = &m_aNetworks[NETWORK_KOG].m_aCountries[i]; - - // check for filter - if(DDNetFiltered(g_Config.m_BrFilterExcludeCountriesKoG, pCntr->m_aName)) - continue; - - if(g >= pCntr->m_NumServers) - continue; - - if(!DDNetFiltered(g_Config.m_BrFilterExcludeTypesKoG, pCntr->m_aTypes[g])) - Set(pCntr->m_aServers[g], IServerBrowser::SET_KOG_ADD, -1, 0); - } - } + m_pHttp->Refresh(); + m_pPingCache->Load(); + m_RefreshingHttp = true; } } -void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const +void CServerBrowser::RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) const { unsigned char Buffer[sizeof(SERVERBROWSE_GETINFO) + 1]; CNetChunk Packet; @@ -757,6 +837,23 @@ } int Token = GenerateToken(Addr); + if(RandomToken) + { + int AvoidBasicToken = GetBasicToken(Token); + do + { + secure_random_fill(&Token, sizeof(Token)); + Token &= 0xffffff; + } while(GetBasicToken(Token) == AvoidBasicToken); + } + if(pToken) + { + *pToken = Token; + } + if(pBasicToken) + { + *pBasicToken = GetBasicToken(Token); + } mem_copy(Buffer, SERVERBROWSE_GETINFO, sizeof(SERVERBROWSE_GETINFO)); Buffer[sizeof(SERVERBROWSE_GETINFO)] = GetBasicToken(Token); @@ -807,122 +904,287 @@ void CServerBrowser::RequestCurrentServer(const NETADDR &Addr) const { - RequestImpl(Addr, 0); + RequestImpl(Addr, nullptr, nullptr, nullptr, false); } -void CServerBrowser::Update(bool ForceResort) +void CServerBrowser::RequestCurrentServerWithRandomToken(const NETADDR &Addr, int *pBasicToken, int *pToken) const { - int64 Timeout = time_freq(); - int64 Now = time_get(); - int Count; - CServerEntry *pEntry, *pNext; + RequestImpl(Addr, nullptr, pBasicToken, pToken, true); +} - // do server list requests - if(m_NeedRefresh && !m_pMasterServer->IsRefreshing()) - { - NETADDR Addr; - CNetChunk Packet; - int i = 0; +void CServerBrowser::SetCurrentServerPing(const NETADDR &Addr, int Ping) +{ + SetLatency(Addr, std::min(Ping, 999)); +} - m_NeedRefresh = 0; - m_MasterServerCount = -1; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETCOUNT); - Packet.m_pData = SERVERBROWSE_GETCOUNT; +void ServerBrowserFillEstimatedLatency(int OwnLocation, const IServerBrowserPingCache::CEntry *pEntries, int NumEntries, int *pIndex, NETADDR Addr, CServerInfo *pInfo) +{ + Addr.port = 0; + while(*pIndex < NumEntries && net_addr_comp(&pEntries[*pIndex].m_Addr, &Addr) < 0) + { + *pIndex += 1; + } + if(*pIndex >= NumEntries || net_addr_comp(&pEntries[*pIndex].m_Addr, &Addr) != 0) + { + pInfo->m_LatencyIsEstimated = true; + pInfo->m_Latency = CServerInfo::EstimateLatency(OwnLocation, pInfo->m_Location); + return; + } + pInfo->m_LatencyIsEstimated = false; + pInfo->m_Latency = pEntries[*pIndex].m_Ping; +} - for(i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) +void CServerBrowser::UpdateFromHttp() +{ + const IServerBrowserPingCache::CEntry *pPingEntries; + int NumPingEntries; + m_pPingCache->GetPingCache(&pPingEntries, &NumPingEntries); + int OwnLocation; + if(str_comp(g_Config.m_BrLocation, "auto") == 0) + { + OwnLocation = m_OwnLocation; + } + else + { + if(CServerInfo::ParseLocation(&OwnLocation, g_Config.m_BrLocation)) { - if(!m_pMasterServer->IsValid(i)) - continue; - - Addr = m_pMasterServer->GetAddr(i); - m_pMasterServer->SetCount(i, -1); - Packet.m_Address = Addr; - m_pNetClient->Send(&Packet); - if(g_Config.m_Debug) - { - dbg_msg("client_srvbrowse", "count-request sent to %d", i); - } + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "cannot parse br_location: '%s'", g_Config.m_BrLocation); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse", aBuf); } } - //Check if all server counts arrived - if(m_MasterServerCount == -1) + int NumServers = m_pHttp->NumServers(); + int NumLegacyServers = m_pHttp->NumLegacyServers(); + if(m_ServerlistType != IServerBrowser::TYPE_INTERNET) { - m_MasterServerCount = 0; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + class CWantedAddr { - if(!m_pMasterServer->IsValid(i)) - continue; - int Count = m_pMasterServer->GetCount(i); - if(Count == -1) + public: + NETADDR m_Addr; + bool m_FallbackToPing; + bool m_Got; + }; + std::vector aWantedAddresses; + int LegacySetType; + if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES) + { + for(int i = 0; i < m_NumFavoriteServers; i++) { - /* ignore Server - m_MasterServerCount = -1; - return; - // we don't have the required server information - */ + aWantedAddresses.push_back(CWantedAddr{m_aFavoriteServers[i], m_aFavoriteServersAllowPing[i], false}); } - else - m_MasterServerCount += Count; + LegacySetType = IServerBrowser::SET_FAV_ADD; } - //request Server-List - NETADDR Addr; - CNetChunk Packet; - mem_zero(&Packet, sizeof(Packet)); - Packet.m_ClientID = -1; - Packet.m_Flags = NETSENDFLAG_CONNLESS; - Packet.m_DataSize = sizeof(SERVERBROWSE_GETLIST); - Packet.m_pData = SERVERBROWSE_GETLIST; - - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + else { - if(!m_pMasterServer->IsValid(i)) - continue; + int Network; + char *pExcludeCountries; + char *pExcludeTypes; + switch(m_ServerlistType) + { + case IServerBrowser::TYPE_DDNET: + Network = NETWORK_DDNET; + LegacySetType = IServerBrowser::SET_DDNET_ADD; + pExcludeCountries = g_Config.m_BrFilterExcludeCountries; + pExcludeTypes = g_Config.m_BrFilterExcludeTypes; + break; + case IServerBrowser::TYPE_KOG: + Network = NETWORK_KOG; + LegacySetType = IServerBrowser::SET_KOG_ADD; + pExcludeCountries = g_Config.m_BrFilterExcludeCountriesKoG; + pExcludeTypes = g_Config.m_BrFilterExcludeTypesKoG; + break; + default: + dbg_assert(0, "invalid network"); + return; + } + // remove unknown elements of exclude list + CountryFilterClean(Network); + TypeFilterClean(Network); - Addr = m_pMasterServer->GetAddr(i); - Packet.m_Address = Addr; - m_pNetClient->Send(&Packet); + int MaxServers = 0; + for(int i = 0; i < m_aNetworks[Network].m_NumCountries; i++) + { + CNetworkCountry *pCntr = &m_aNetworks[Network].m_aCountries[i]; + MaxServers = maximum(MaxServers, pCntr->m_NumServers); + } + + for(int g = 0; g < MaxServers; g++) + { + for(int i = 0; i < m_aNetworks[Network].m_NumCountries; i++) + { + CNetworkCountry *pCntr = &m_aNetworks[Network].m_aCountries[i]; + + // check for filter + if(DDNetFiltered(pExcludeCountries, pCntr->m_aName)) + continue; + + if(g >= pCntr->m_NumServers) + continue; + + if(DDNetFiltered(pExcludeTypes, pCntr->m_aTypes[g])) + continue; + aWantedAddresses.push_back(CWantedAddr{pCntr->m_aServers[g], false, false}); + } + } } - if(g_Config.m_Debug) + std::vector aSortedServers; + std::vector aSortedLegacyServers; + aSortedServers.reserve(NumServers); + for(int i = 0; i < NumServers; i++) { - dbg_msg("client_srvbrowse", "servercount: %d, requesting server list", m_MasterServerCount); + aSortedServers.push_back(i); } - m_LastPacketTick = 0; - } - else if(m_MasterServerCount > -1) - { - m_MasterServerCount = 0; - for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + aSortedLegacyServers.reserve(NumLegacyServers); + for(int i = 0; i < NumLegacyServers; i++) + { + aSortedLegacyServers.push_back(i); + } + + class CWantedAddrComparer + { + public: + bool operator()(const CWantedAddr &a, const CWantedAddr &b) + { + return net_addr_comp(&a.m_Addr, &b.m_Addr) < 0; + } + }; + class CAddrComparer + { + public: + IServerBrowserHttp *m_pHttp; + bool operator()(int i, int j) + { + return net_addr_comp(&m_pHttp->ServerAddress(i), &m_pHttp->ServerAddress(j)) < 0; + } + }; + class CLegacyAddrComparer + { + public: + IServerBrowserHttp *m_pHttp; + bool operator()(int i, int j) + { + return net_addr_comp(&m_pHttp->LegacyServer(i), &m_pHttp->LegacyServer(j)) < 0; + } + }; + + std::sort(aWantedAddresses.begin(), aWantedAddresses.end(), CWantedAddrComparer()); + std::sort(aSortedServers.begin(), aSortedServers.end(), CAddrComparer{m_pHttp}); + std::sort(aSortedLegacyServers.begin(), aSortedLegacyServers.end(), CLegacyAddrComparer{m_pHttp}); + + unsigned i = 0; + unsigned j = 0; + int p = 0; + while(i < aWantedAddresses.size() && j < aSortedServers.size()) + { + int Cmp = net_addr_comp(&aWantedAddresses[i].m_Addr, &m_pHttp->ServerAddress(aSortedServers[j])); + if(Cmp != 0) + { + if(Cmp < 0) + { + i++; + } + else + { + j++; + } + continue; + } + aWantedAddresses[i].m_Got = true; + NETADDR Addr; + CServerInfo Info; + m_pHttp->Server(aSortedServers[j], &Addr, &Info); + ServerBrowserFillEstimatedLatency(OwnLocation, pPingEntries, NumPingEntries, &p, Addr, &Info); + Info.m_HasRank = HasRank(Info.m_aMap); + Set(Addr, IServerBrowser::SET_HTTPINFO, -1, &Info); + i++; + j++; + } + i = 0; + j = 0; + while(i < aWantedAddresses.size() && j < aSortedLegacyServers.size()) { - if(!m_pMasterServer->IsValid(i)) + int Cmp = net_addr_comp(&aWantedAddresses[i].m_Addr, &m_pHttp->LegacyServer(aSortedLegacyServers[j])); + if(Cmp != 0) + { + if(Cmp < 0) + { + i++; + } + else + { + j++; + } continue; - int Count = m_pMasterServer->GetCount(i); - if(Count == -1) + } + aWantedAddresses[i].m_Got = true; + Set(m_pHttp->LegacyServer(aSortedLegacyServers[j]), LegacySetType, -1, nullptr); + i++; + j++; + } + for(const CWantedAddr &Wanted : aWantedAddresses) + { + if(!Wanted.m_Got) { - /* ignore Server - m_MasterServerCount = -1; - return; - // we don't have the required server information - */ + if(Wanted.m_FallbackToPing) + { + Set(Wanted.m_Addr, LegacySetType, -1, nullptr); + } + else + { + // Also add favorites we're not allowed to ping. + if(LegacySetType == IServerBrowser::SET_FAV_ADD && !Find(Wanted.m_Addr)) + { + Add(Wanted.m_Addr); + } + } } - else - m_MasterServerCount += Count; } - //if(g_Config.m_Debug) - //{ - // dbg_msg("client_srvbrowse", "ServerCount2: %d", m_MasterServerCount); - //} + return; + } + int p = 0; + for(int i = 0; i < NumServers; i++) + { + NETADDR Addr; + CServerInfo Info; + m_pHttp->Server(i, &Addr, &Info); + ServerBrowserFillEstimatedLatency(OwnLocation, pPingEntries, NumPingEntries, &p, Addr, &Info); + Info.m_HasRank = HasRank(Info.m_aMap); + Set(Addr, IServerBrowser::SET_HTTPINFO, -1, &Info); } - if(m_MasterServerCount > m_NumRequests + m_LastPacketTick) + for(int i = 0; i < NumLegacyServers; i++) { - ++m_LastPacketTick; - return; //wait for more packets + NETADDR Addr = m_pHttp->LegacyServer(i); + Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, nullptr); } - pEntry = m_pFirstReqServer; - Count = 0; +} + +void CServerBrowser::Update(bool ForceResort) +{ + int64 Timeout = time_freq(); + int64 Now = time_get(); + + const char *pHttpBestUrl; + if(!m_pHttp->GetBestUrl(&pHttpBestUrl) && pHttpBestUrl != m_pHttpPrevBestUrl) + { + str_copy(g_Config.m_BrCachedBestServerinfoUrl, pHttpBestUrl, sizeof(g_Config.m_BrCachedBestServerinfoUrl)); + m_pHttpPrevBestUrl = pHttpBestUrl; + } + + m_pHttp->Update(); + + if(m_ServerlistType != TYPE_LAN && m_RefreshingHttp && !m_pHttp->IsRefreshing()) + { + m_RefreshingHttp = false; + UpdateFromHttp(); + // TODO: move this somewhere else + if(m_Sorthash != SortHash() || ForceResort) + Sort(); + return; + } + + CServerEntry *pEntry = m_pFirstReqServer; + int Count = 0; while(1) { if(!pEntry) // no more entries @@ -941,7 +1203,7 @@ if(pEntry->m_Request64Legacy) RequestImpl64(pEntry->m_Addr, pEntry); else - RequestImpl(pEntry->m_Addr, pEntry); + RequestImpl(pEntry->m_Addr, pEntry, nullptr, nullptr, false); } Count++; @@ -972,7 +1234,7 @@ { if(!pEntry) // no more entries break; - pNext = pEntry->m_pNextReq; + CServerEntry *pNext = pEntry->m_pNextReq; RemoveRequest(pEntry); //release request pEntry = pNext; } @@ -986,16 +1248,33 @@ } } -bool CServerBrowser::IsFavorite(const NETADDR &Addr) const +int CServerBrowser::FindFavorite(const NETADDR &Addr) const { // search for the address - int i; - for(i = 0; i < m_NumFavoriteServers; i++) + for(int i = 0; i < m_NumFavoriteServers; i++) { if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - return true; + return i; } - return false; + return -1; +} + +bool CServerBrowser::GotInfo(const NETADDR &Addr) const +{ + CServerEntry *pEntry = ((CServerBrowser *)this)->Find(Addr); + return pEntry && pEntry->m_GotInfo; +} + +bool CServerBrowser::IsFavorite(const NETADDR &Addr) const +{ + return FindFavorite(Addr) >= 0; +} + +bool CServerBrowser::IsFavoritePingAllowed(const NETADDR &Addr) const +{ + int i = FindFavorite(Addr); + dbg_assert(i >= 0, "invalid favorite"); + return i >= 0 && m_aFavoriteServersAllowPing[i]; } void CServerBrowser::AddFavorite(const NETADDR &Addr) @@ -1006,14 +1285,15 @@ return; // make sure that we don't already have the server in our list - for(int i = 0; i < m_NumFavoriteServers; i++) + if(IsFavorite(Addr)) { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - return; + return; } // add the server to the list - m_aFavoriteServers[m_NumFavoriteServers++] = Addr; + m_aFavoriteServers[m_NumFavoriteServers] = Addr; + m_aFavoriteServersAllowPing[m_NumFavoriteServers] = false; + m_NumFavoriteServers++; pEntry = Find(Addr); if(pEntry) pEntry->m_Info.m_Favorite = true; @@ -1028,25 +1308,27 @@ } } -void CServerBrowser::RemoveFavorite(const NETADDR &Addr) +void CServerBrowser::FavoriteAllowPing(const NETADDR &Addr, bool AllowPing) { - int i; - CServerEntry *pEntry; + int i = FindFavorite(Addr); + dbg_assert(i >= 0, "invalid favorite"); + m_aFavoriteServersAllowPing[i] = AllowPing; +} - for(i = 0; i < m_NumFavoriteServers; i++) +void CServerBrowser::RemoveFavorite(const NETADDR &Addr) +{ + int i = FindFavorite(Addr); + if(i < 0) { - if(net_addr_comp(&Addr, &m_aFavoriteServers[i]) == 0) - { - mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i + 1], sizeof(NETADDR) * (m_NumFavoriteServers - (i + 1))); - m_NumFavoriteServers--; - - pEntry = Find(Addr); - if(pEntry) - pEntry->m_Info.m_Favorite = false; - - return; - } + return; } + mem_move(&m_aFavoriteServers[i], &m_aFavoriteServers[i + 1], sizeof(NETADDR) * (m_NumFavoriteServers - (i + 1))); + mem_move(&m_aFavoriteServersAllowPing[i], &m_aFavoriteServersAllowPing[i + 1], sizeof(bool) * (m_NumFavoriteServers - (i + 1))); + m_NumFavoriteServers--; + + CServerEntry *pEntry = Find(Addr); + if(pEntry) + pEntry->m_Info.m_Favorite = false; } void CServerBrowser::LoadDDNetServers() @@ -1193,9 +1475,7 @@ void CServerBrowser::LoadDDNetInfoJson() { - IStorage *pStorage = Kernel()->RequestInterface(); - IOHANDLE File = pStorage->OpenFile(DDNET_INFO, IOFLAG_READ, IStorage::TYPE_SAVE); - + IOHANDLE File = m_pStorage->OpenFile(DDNET_INFO, IOFLAG_READ, IStorage::TYPE_SAVE); if(!File) return; @@ -1224,6 +1504,18 @@ json_value_free(m_pDDNetInfo); m_pDDNetInfo = 0; } + + m_OwnLocation = CServerInfo::LOC_UNKNOWN; + if(m_pDDNetInfo) + { + const json_value &Location = (*m_pDDNetInfo)["location"]; + if(Location.type != json_string || CServerInfo::ParseLocation(&m_OwnLocation, Location)) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "cannot parse location from info.sjon: '%s'", (const char *)Location); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse", aBuf); + } + } } const json_value *CServerBrowser::LoadDDNetInfo() @@ -1231,15 +1523,8 @@ LoadDDNetInfoJson(); LoadDDNetServers(); - if(m_NumServers == 0) - { - Refresh(m_ServerlistType); - } - else - { - RecheckOfficial(); - LoadDDNetRanks(); - } + RecheckOfficial(); + LoadDDNetRanks(); return m_pDDNetInfo; } @@ -1249,9 +1534,9 @@ return m_pFirstReqServer != 0; } -bool CServerBrowser::IsRefreshingMasters() const +bool CServerBrowser::IsGettingServerlist() const { - return m_pMasterServer->IsRefreshing(); + return m_pHttp->IsRefreshing(); } int CServerBrowser::LoadingProgression() const @@ -1273,7 +1558,17 @@ for(int i = 0; i < pSelf->m_NumFavoriteServers; i++) { net_addr_str(&pSelf->m_aFavoriteServers[i], aAddrStr, sizeof(aAddrStr), true); - str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr); + if(!pSelf->m_aFavoriteServersAllowPing[i]) + { + str_format(aBuffer, sizeof(aBuffer), "add_favorite %s", aAddrStr); + } + else + { + // Add quotes to the first parameter for backward + // compatibility with versions that took a `r` console + // parameter. + str_format(aBuffer, sizeof(aBuffer), "add_favorite \"%s\" allow_ping", aAddrStr); + } pConfigManager->WriteLine(aBuffer); } } @@ -1358,3 +1653,113 @@ str_copy(pExcludeTypes, aNewList, sizeof(g_Config.m_BrFilterExcludeTypes)); } + +int CServerInfo::EstimateLatency(int Loc1, int Loc2) +{ + if(Loc1 == LOC_UNKNOWN || Loc2 == LOC_UNKNOWN) + { + return 999; + } + if(Loc1 != Loc2) + { + return 199; + } + return 99; +} +bool CServerInfo::ParseLocation(int *pResult, const char *pString) +{ + *pResult = LOC_UNKNOWN; + int Length = str_length(pString); + if(Length < 2) + { + return true; + } + // ISO continent code. Allow antarctica, but treat it as unknown. + static const char LOCATIONS[][6] = { + "an", // LOC_UNKNOWN + "af", // LOC_AFRICA + "as", // LOC_ASIA + "oc", // LOC_AUSTRALIA + "eu", // LOC_EUROPE + "na", // LOC_NORTH_AMERICA + "sa", // LOC_SOUTH_AMERICA + "as:cn", // LOC_CHINA + }; + for(int i = sizeof(LOCATIONS) / sizeof(LOCATIONS[0]) - 1; i >= 0; i--) + { + if(str_startswith(pString, LOCATIONS[i])) + { + *pResult = i; + return false; + } + } + return true; +} + +bool IsVanilla(const CServerInfo *pInfo) +{ + return !str_comp(pInfo->m_aGameType, "DM") || !str_comp(pInfo->m_aGameType, "TDM") || !str_comp(pInfo->m_aGameType, "CTF"); +} + +bool IsCatch(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "catch"); +} + +bool IsInsta(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "idm") || str_find_nocase(pInfo->m_aGameType, "itdm") || str_find_nocase(pInfo->m_aGameType, "ictf"); +} + +bool IsFNG(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "fng"); +} + +bool IsRace(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "race") || str_find_nocase(pInfo->m_aGameType, "fastcap"); +} + +bool IsFastCap(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "fastcap"); +} + +bool IsBlockInfectionZ(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "blockz") || + str_find_nocase(pInfo->m_aGameType, "infectionz"); +} + +bool IsBlockWorlds(const CServerInfo *pInfo) +{ + return (str_comp_nocase_num(pInfo->m_aGameType, "bw ", 4) == 0) || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0); +} + +bool IsCity(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "city"); +} + +bool IsDDRace(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "ddrace") || str_find_nocase(pInfo->m_aGameType, "mkrace"); +} + +bool IsDDNet(const CServerInfo *pInfo) +{ + return str_find_nocase(pInfo->m_aGameType, "ddracenet") || str_find_nocase(pInfo->m_aGameType, "ddnet"); +} + +// other + +bool Is64Player(const CServerInfo *pInfo) +{ + return str_find(pInfo->m_aGameType, "64") || str_find(pInfo->m_aName, "64") || IsDDNet(pInfo); +} + +bool IsPlus(const CServerInfo *pInfo) +{ + return str_find(pInfo->m_aGameType, "+"); +} diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser.h ddnet-15.5.4/src/engine/client/serverbrowser.h --- ddnet-15.3.2/src/engine/client/serverbrowser.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser.h 2021-06-20 09:38:48.000000000 +0000 @@ -3,13 +3,18 @@ #ifndef ENGINE_CLIENT_SERVERBROWSER_H #define ENGINE_CLIENT_SERVERBROWSER_H +#include #include +#include #include #include #include #include #include +class IServerBrowserHttp; +class IServerBrowserPingCache; + class CServerBrowser : public IServerBrowser { public: @@ -18,6 +23,7 @@ public: NETADDR m_Addr; int64 m_RequestTime; + bool m_RequestIgnoreInfo; int m_GotInfo; bool m_Request64Legacy; CServerInfo m_Info; @@ -79,7 +85,7 @@ // interface functions void Refresh(int Type); bool IsRefreshing() const; - bool IsRefreshingMasters() const; + bool IsGettingServerlist() const; int LoadingProgression() const; int NumServers() const { return m_NumServers; } @@ -97,8 +103,11 @@ int NumSortedServers() const { return m_NumSortedServers; } const CServerInfo *SortedGet(int Index) const; + bool GotInfo(const NETADDR &Addr) const; bool IsFavorite(const NETADDR &Addr) const; + bool IsFavoritePingAllowed(const NETADDR &Addr) const; void AddFavorite(const NETADDR &Addr); + void FavoriteAllowPing(const NETADDR &Addr, bool AllowPing); void RemoveFavorite(const NETADDR &Addr); void LoadDDNetRanks(); @@ -124,8 +133,11 @@ void Update(bool ForceResort); void Set(const NETADDR &Addr, int Type, int Token, const CServerInfo *pInfo); void RequestCurrentServer(const NETADDR &Addr) const; + void RequestCurrentServerWithRandomToken(const NETADDR &Addr, int *pBasicToken, int *pToken) const; + void SetCurrentServerPing(const NETADDR &Addr, int Ping); void SetBaseInfo(class CNetClient *pClient, const char *pNetVersion); + void OnInit(); void RequestImpl64(const NETADDR &Addr, CServerEntry *pEntry) const; void QueueRequest(CServerEntry *pEntry); @@ -134,19 +146,27 @@ private: CNetClient *m_pNetClient; - IMasterServer *m_pMasterServer; class IConsole *m_pConsole; + class IEngine *m_pEngine; class IFriends *m_pFriends; + class IStorage *m_pStorage; char m_aNetVersion[128]; + bool m_RefreshingHttp = false; + IServerBrowserHttp *m_pHttp = nullptr; + IServerBrowserPingCache *m_pPingCache = nullptr; + const char *m_pHttpPrevBestUrl = nullptr; + CHeap m_ServerlistHeap; CServerEntry **m_ppServerlist; int *m_pSortedServerlist; NETADDR m_aFavoriteServers[MAX_FAVORITES]; + bool m_aFavoriteServersAllowPing[MAX_FAVORITES]; int m_NumFavoriteServers; CNetwork m_aNetworks[NUM_NETWORKS]; + int m_OwnLocation = CServerInfo::LOC_UNKNOWN; json_value *m_pDDNetInfo; @@ -155,13 +175,10 @@ CServerEntry *m_pFirstReqServer; // request list CServerEntry *m_pLastReqServer; int m_NumRequests; - int m_MasterServerCount; //used instead of g_Config.br_max_requests to get more servers int m_CurrentMaxRequests; - int m_LastPacketTick; - int m_NeedRefresh; int m_NumSortedServers; @@ -180,6 +197,8 @@ bool m_SortOnNextUpdate; + int FindFavorite(const NETADDR &Addr) const; + int GenerateToken(const NETADDR &Addr) const; static int GetBasicToken(int Token); static int GetExtraToken(int Token); @@ -198,13 +217,18 @@ void Sort(); int SortHash() const; + void UpdateFromHttp(); CServerEntry *Add(const NETADDR &Addr); void RemoveRequest(CServerEntry *pEntry); - void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry) const; + void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) const; + + void RegisterCommands(); + static void Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData); void SetInfo(CServerEntry *pEntry, const CServerInfo &Info); + void SetLatency(const NETADDR Addr, int Latency); static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData); }; diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser_http.cpp ddnet-15.5.4/src/engine/client/serverbrowser_http.cpp --- ddnet-15.3.2/src/engine/client/serverbrowser_http.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser_http.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,477 @@ +#include "serverbrowser_http.h" + +#include "http.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +class CChooseMaster +{ +public: + typedef bool (*VALIDATOR)(json_value *pJson); + + enum + { + MAX_URLS = 16, + }; + CChooseMaster(IEngine *pEngine, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex); + virtual ~CChooseMaster() {} + + bool GetBestUrl(const char **pBestUrl) const; + void Reset(); + bool IsRefreshing() const { return m_pJob && m_pJob->Status() != IJob::STATE_DONE; } + void Refresh(); + +private: + int GetBestIndex() const; + + class CData + { + public: + std::atomic_int m_BestIndex{-1}; + // Constant after construction. + VALIDATOR m_pfnValidator; + int m_NumUrls; + char m_aaUrls[MAX_URLS][256]; + }; + class CJob : public IJob + { + std::shared_ptr m_pData; + virtual void Run(); + + public: + CJob(std::shared_ptr pData) : + m_pData(std::move(pData)) {} + virtual ~CJob() {} + }; + + IEngine *m_pEngine; + int m_PreviousBestIndex; + std::shared_ptr m_pData; + std::shared_ptr m_pJob; +}; + +CChooseMaster::CChooseMaster(IEngine *pEngine, VALIDATOR pfnValidator, const char **ppUrls, int NumUrls, int PreviousBestIndex) : + m_pEngine(pEngine), + m_PreviousBestIndex(PreviousBestIndex) +{ + dbg_assert(NumUrls >= 0, "no master URLs"); + dbg_assert(NumUrls <= MAX_URLS, "too many master URLs"); + dbg_assert(PreviousBestIndex >= -1, "previous best index negative and not -1"); + dbg_assert(PreviousBestIndex < NumUrls, "previous best index too high"); + m_pData = std::make_shared(); + m_pData->m_pfnValidator = pfnValidator; + m_pData->m_NumUrls = NumUrls; + for(int i = 0; i < m_pData->m_NumUrls; i++) + { + str_copy(m_pData->m_aaUrls[i], ppUrls[i], sizeof(m_pData->m_aaUrls[i])); + } +} + +int CChooseMaster::GetBestIndex() const +{ + int BestIndex = m_pData->m_BestIndex.load(); + if(BestIndex >= 0) + { + return BestIndex; + } + else + { + return m_PreviousBestIndex; + } +} + +bool CChooseMaster::GetBestUrl(const char **ppBestUrl) const +{ + int Index = GetBestIndex(); + if(Index < 0) + { + *ppBestUrl = nullptr; + return true; + } + *ppBestUrl = m_pData->m_aaUrls[Index]; + return false; +} + +void CChooseMaster::Reset() +{ + m_PreviousBestIndex = -1; + m_pData->m_BestIndex.store(-1); +} + +void CChooseMaster::Refresh() +{ + m_pEngine->AddJob(m_pJob = std::make_shared(m_pData)); +} + +void CChooseMaster::CJob::Run() +{ + // Check masters in a random order. + int aRandomized[MAX_URLS] = {0}; + for(int i = 0; i < m_pData->m_NumUrls; i++) + { + aRandomized[i] = i; + } + // https://en.wikipedia.org/w/index.php?title=Fisher%E2%80%93Yates_shuffle&oldid=1002922479#The_modern_algorithm + // The equivalent version. + for(int i = 0; i <= m_pData->m_NumUrls - 2; i++) + { + int j = i + secure_rand_below(m_pData->m_NumUrls - i); + std::swap(aRandomized[i], aRandomized[j]); + } + // Do a HEAD request to ensure that a connection is established and + // then do a GET request to check how fast we can get the server list. + // + // 10 seconds connection timeout, lower than 8KB/s for 10 seconds to + // fail. + CTimeout Timeout{10000, 8000, 10}; + int aTimeMs[MAX_URLS]; + for(int i = 0; i < m_pData->m_NumUrls; i++) + { + aTimeMs[i] = -1; + const char *pUrl = m_pData->m_aaUrls[aRandomized[i]]; + CHead Head(pUrl, Timeout, false); + IEngine::RunJobBlocking(&Head); + if(Head.State() != HTTP_DONE) + { + continue; + } + int64 StartTime = time_get(); + CGet Get(pUrl, Timeout, false); + IEngine::RunJobBlocking(&Get); + int Time = (time_get() - StartTime) * 1000 / time_freq(); + if(Get.State() != HTTP_DONE) + { + continue; + } + json_value *pJson = Get.ResultJson(); + if(!pJson) + { + continue; + } + bool ParseFailure = m_pData->m_pfnValidator(pJson); + json_value_free(pJson); + if(ParseFailure) + { + continue; + } + dbg_msg("serverbrowse_http", "found master, url='%s' time=%dms", pUrl, Time); + aTimeMs[i] = Time; + } + // Determine index of the minimum time. + int BestIndex = -1; + int BestTime = 0; + for(int i = 0; i < m_pData->m_NumUrls; i++) + { + if(aTimeMs[i] < 0) + { + continue; + } + if(BestIndex == -1 || aTimeMs[i] < BestTime) + { + BestTime = aTimeMs[i]; + BestIndex = aRandomized[i]; + } + } + if(BestIndex == -1) + { + dbg_msg("serverbrowse_http", "WARNING: no usable masters found"); + return; + } + dbg_msg("serverbrowse_http", "determined best master, url='%s' time=%dms", m_pData->m_aaUrls[BestIndex], BestTime); + m_pData->m_BestIndex.store(BestIndex); +} + +class CServerBrowserHttp : public IServerBrowserHttp +{ +public: + CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, const char **ppUrls, int NumUrls, int PreviousBestIndex); + virtual ~CServerBrowserHttp() {} + void Update(); + bool IsRefreshing() { return m_State != STATE_DONE; } + void Refresh(); + bool GetBestUrl(const char **pBestUrl) const { return m_pChooseMaster->GetBestUrl(pBestUrl); } + + int NumServers() const + { + return m_aServers.size(); + } + const NETADDR &ServerAddress(int Index) const + { + return m_aServers[Index].m_Addr; + } + void Server(int Index, NETADDR *pAddr, CServerInfo *pInfo) const + { + const CEntry &Entry = m_aServers[Index]; + *pAddr = Entry.m_Addr; + *pInfo = Entry.m_Info; + } + int NumLegacyServers() const + { + return m_aLegacyServers.size(); + } + const NETADDR &LegacyServer(int Index) const + { + return m_aLegacyServers[Index]; + } + +private: + enum + { + STATE_DONE, + STATE_WANTREFRESH, + STATE_REFRESHING, + }; + + class CEntry + { + public: + NETADDR m_Addr; + CServerInfo m_Info; + }; + + static bool Validate(json_value *pJson); + static bool Parse(json_value *pJson, std::vector *paServers, std::vector *paLegacyServers); + + IEngine *m_pEngine; + IConsole *m_pConsole; + + int m_State = STATE_DONE; + std::shared_ptr m_pGetServers; + std::unique_ptr m_pChooseMaster; + + std::vector m_aServers; + std::vector m_aLegacyServers; +}; + +CServerBrowserHttp::CServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, const char **ppUrls, int NumUrls, int PreviousBestIndex) : + m_pEngine(pEngine), + m_pConsole(pConsole), + m_pChooseMaster(new CChooseMaster(pEngine, Validate, ppUrls, NumUrls, PreviousBestIndex)) +{ + m_pChooseMaster->Refresh(); +} +void CServerBrowserHttp::Update() +{ + if(m_State == STATE_WANTREFRESH) + { + const char *pBestUrl; + if(m_pChooseMaster->GetBestUrl(&pBestUrl)) + { + if(!m_pChooseMaster->IsRefreshing()) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_http", "no working serverlist URL found"); + m_State = STATE_DONE; + } + return; + } + // 10 seconds connection timeout, lower than 8KB/s for 10 seconds to fail. + CTimeout Timeout{10000, 8000, 10}; + m_pEngine->AddJob(m_pGetServers = std::make_shared(pBestUrl, Timeout)); + m_State = STATE_REFRESHING; + } + else if(m_State == STATE_REFRESHING) + { + if(m_pGetServers->State() == HTTP_QUEUED || m_pGetServers->State() == HTTP_RUNNING) + { + return; + } + m_State = STATE_DONE; + std::shared_ptr pGetServers = nullptr; + std::swap(m_pGetServers, pGetServers); + + bool Success = true; + json_value *pJson = pGetServers->ResultJson(); + Success = Success && pJson; + Success = Success && !Parse(pJson, &m_aServers, &m_aLegacyServers); + json_value_free(pJson); + if(!Success) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_http", "failed getting serverlist, trying to find best URL"); + m_pChooseMaster->Reset(); + m_pChooseMaster->Refresh(); + } + } +} +void CServerBrowserHttp::Refresh() +{ + if(m_State == STATE_WANTREFRESH) + { + m_pChooseMaster->Refresh(); + } + if(m_State == STATE_DONE) + m_State = STATE_WANTREFRESH; + Update(); +} +bool ServerbrowserParseUrl(NETADDR *pOut, const char *pUrl) +{ + char aHost[128]; + const char *pRest = str_startswith(pUrl, "tw-0.6+udp://"); + if(!pRest) + { + return true; + } + int Length = str_length(pRest); + int Start = 0; + int End = Length; + for(int i = 0; i < Length; i++) + { + if(pRest[i] == '@') + { + if(Start != 0) + { + // Two at signs. + return true; + } + Start = i + 1; + } + else if(pRest[i] == '/' || pRest[i] == '?' || pRest[i] == '#') + { + End = i; + break; + } + } + str_truncate(aHost, sizeof(aHost), pRest + Start, End - Start); + if(net_addr_from_str(pOut, aHost)) + { + return true; + } + return false; +} +bool CServerBrowserHttp::Validate(json_value *pJson) +{ + std::vector aServers; + std::vector aLegacyServers; + return Parse(pJson, &aServers, &aLegacyServers); +} +bool CServerBrowserHttp::Parse(json_value *pJson, std::vector *paServers, std::vector *paLegacyServers) +{ + std::vector aServers; + std::vector aLegacyServers; + + const json_value &Json = *pJson; + const json_value &Servers = Json["servers"]; + const json_value &LegacyServers = Json["servers_legacy"]; + if(Servers.type != json_array || (LegacyServers.type != json_array && LegacyServers.type != json_none)) + { + return true; + } + for(unsigned int i = 0; i < Servers.u.array.length; i++) + { + const json_value &Server = Servers[i]; + const json_value &Addresses = Server["addresses"]; + const json_value &Info = Server["info"]; + const json_value &Location = Server["location"]; + int ParsedLocation = CServerInfo::LOC_UNKNOWN; + CServerInfo2 ParsedInfo; + if(Addresses.type != json_array || (Location.type != json_string && Location.type != json_none)) + { + return true; + } + if(Location.type == json_string) + { + if(CServerInfo::ParseLocation(&ParsedLocation, Location)) + { + return true; + } + } + if(CServerInfo2::FromJson(&ParsedInfo, &Info)) + { + //dbg_msg("dbg/serverbrowser", "skipped due to info, i=%d", i); + // Only skip the current server on parsing + // failure; the server info is "user input" by + // the game server and can be set to arbitrary + // values. + continue; + } + CServerInfo SetInfo = ParsedInfo; + SetInfo.m_Location = ParsedLocation; + for(unsigned int a = 0; a < Addresses.u.array.length; a++) + { + const json_value &Address = Addresses[a]; + if(Address.type != json_string) + { + return true; + } + // TODO: Address address handling :P + NETADDR ParsedAddr; + if(ServerbrowserParseUrl(&ParsedAddr, Addresses[a])) + { + //dbg_msg("dbg/serverbrowser", "unknown address, i=%d a=%d", i, a); + // Skip unknown addresses. + continue; + } + aServers.push_back({ParsedAddr, SetInfo}); + } + } + if(LegacyServers.type == json_array) + { + for(unsigned int i = 0; i < LegacyServers.u.array.length; i++) + { + const json_value &Address = LegacyServers[i]; + NETADDR ParsedAddr; + if(Address.type != json_string || net_addr_from_str(&ParsedAddr, Address)) + { + return true; + } + aLegacyServers.push_back(ParsedAddr); + } + } + *paServers = aServers; + *paLegacyServers = aLegacyServers; + return false; +} + +static const char *DEFAULT_SERVERLIST_URLS[] = { + "https://master1.ddnet.tw/ddnet/15/servers.json", + "https://master2.ddnet.tw/ddnet/15/servers.json", + "https://master3.ddnet.tw/ddnet/15/servers.json", + "https://master4.ddnet.tw/ddnet/15/servers.json", +}; + +IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, const char *pPreviousBestUrl) +{ + char aaUrls[CChooseMaster::MAX_URLS][256]; + const char *apUrls[CChooseMaster::MAX_URLS] = {0}; + const char **ppUrls = apUrls; + int NumUrls = 0; + IOHANDLE File = pStorage->OpenFile("ddnet-serverlist-urls.cfg", IOFLAG_READ, IStorage::TYPE_ALL); + if(File) + { + CLineReader Lines; + Lines.Init(File); + while(NumUrls < CChooseMaster::MAX_URLS) + { + const char *pLine = Lines.Get(); + if(!pLine) + { + break; + } + str_copy(aaUrls[NumUrls], pLine, sizeof(aaUrls[NumUrls])); + apUrls[NumUrls] = aaUrls[NumUrls]; + NumUrls += 1; + } + } + if(NumUrls == 0) + { + ppUrls = DEFAULT_SERVERLIST_URLS; + NumUrls = sizeof(DEFAULT_SERVERLIST_URLS) / sizeof(DEFAULT_SERVERLIST_URLS[0]); + } + int PreviousBestIndex = -1; + for(int i = 0; i < NumUrls; i++) + { + if(str_comp(ppUrls[i], pPreviousBestUrl) == 0) + { + PreviousBestIndex = i; + break; + } + } + return new CServerBrowserHttp(pEngine, pConsole, ppUrls, NumUrls, PreviousBestIndex); +} diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser_http.h ddnet-15.5.4/src/engine/client/serverbrowser_http.h --- ddnet-15.3.2/src/engine/client/serverbrowser_http.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser_http.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,30 @@ +#ifndef ENGINE_CLIENT_SERVERBROWSER_HTTP_H +#define ENGINE_CLIENT_SERVERBROWSER_HTTP_H +#include + +class CServerInfo; +class IConsole; +class IEngine; +class IStorage; + +class IServerBrowserHttp +{ +public: + virtual ~IServerBrowserHttp() {} + + virtual void Update() = 0; + + virtual bool IsRefreshing() = 0; + virtual void Refresh() = 0; + + virtual bool GetBestUrl(const char **pBestUrl) const = 0; + + virtual int NumServers() const = 0; + virtual const NETADDR &ServerAddress(int Index) const = 0; + virtual void Server(int Index, NETADDR *pAddr, CServerInfo *pInfo) const = 0; + virtual int NumLegacyServers() const = 0; + virtual const NETADDR &LegacyServer(int Index) const = 0; +}; + +IServerBrowserHttp *CreateServerBrowserHttp(IEngine *pEngine, IConsole *pConsole, IStorage *pStorage, const char *pPreviousBestUrl); +#endif // ENGINE_CLIENT_SERVERBROWSER_HTTP_H diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser_ping_cache.cpp ddnet-15.5.4/src/engine/client/serverbrowser_ping_cache.cpp --- ddnet-15.3.2/src/engine/client/serverbrowser_ping_cache.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser_ping_cache.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,207 @@ +#include "serverbrowser_ping_cache.h" + +#include +#include + +#include + +#include +#include +#include + +class CServerBrowserPingCache : public IServerBrowserPingCache +{ +public: + CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage); + virtual ~CServerBrowserPingCache() {} + + void Load(); + + void CachePing(NETADDR Addr, int Ping); + void GetPingCache(const CEntry **ppEntries, int *pNumEntries); + +private: + IConsole *m_pConsole; + + CSqlite m_pDisk; + CSqliteStmt m_pLoadStmt; + CSqliteStmt m_pStoreStmt; + + std::vector m_aEntries; + std::vector m_aNewEntries; +}; + +CServerBrowserPingCache::CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage) : + m_pConsole(pConsole) +{ + m_pDisk = SqliteOpen(pConsole, pStorage, "ddnet-cache.sqlite3"); + if(!m_pDisk) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_ping_cache", "failed to open ddnet-cache.sqlite3"); + return; + } + sqlite3 *pSqlite = m_pDisk.get(); + static const char TABLE[] = "CREATE TABLE IF NOT EXISTS server_pings (ip_address TEXT PRIMARY KEY NOT NULL, ping INTEGER NOT NULL, utc_timestamp TEXT NOT NULL)"; + if(SQLITE_HANDLE_ERROR(sqlite3_exec(pSqlite, TABLE, nullptr, nullptr, nullptr))) + { + m_pDisk = nullptr; + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_ping_cache", "failed to create server_pings table"); + return; + } + m_pLoadStmt = SqlitePrepare(pConsole, pSqlite, "SELECT ip_address, ping FROM server_pings"); + m_pStoreStmt = SqlitePrepare(pConsole, pSqlite, "INSERT OR REPLACE INTO server_pings (ip_address, ping, utc_timestamp) VALUES (?, ?, datetime('now'))"); +} + +void CServerBrowserPingCache::Load() +{ + if(m_pDisk) + { + int PrevNewEntriesSize = m_aNewEntries.size(); + + sqlite3 *pSqlite = m_pDisk.get(); + IConsole *pConsole = m_pConsole; + bool Error = false; + bool WarnedForBadAddress = false; + Error = Error || !m_pLoadStmt; + while(!Error) + { + int StepResult = SQLITE_HANDLE_ERROR(sqlite3_step(m_pLoadStmt.get())); + if(StepResult == SQLITE_DONE) + { + break; + } + else if(StepResult == SQLITE_ROW) + { + const char *pIpAddress = (const char *)sqlite3_column_text(m_pLoadStmt.get(), 0); + int Ping = sqlite3_column_int(m_pLoadStmt.get(), 1); + NETADDR Addr; + if(net_addr_from_str(&Addr, pIpAddress)) + { + if(!WarnedForBadAddress) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "invalid address: %s", pIpAddress); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_ping_cache", aBuf); + WarnedForBadAddress = true; + } + continue; + } + m_aNewEntries.push_back(CEntry{Addr, Ping}); + } + else + { + Error = true; + } + } + if(Error) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_ping_cache", "failed to load ping cache"); + m_aNewEntries.resize(PrevNewEntriesSize); + } + } +} + +void CServerBrowserPingCache::CachePing(NETADDR Addr, int Ping) +{ + Addr.port = 0; + m_aNewEntries.push_back(CEntry{Addr, Ping}); + if(m_pDisk) + { + sqlite3 *pSqlite = m_pDisk.get(); + IConsole *pConsole = m_pConsole; + char aAddr[NETADDR_MAXSTRSIZE]; + net_addr_str(&Addr, aAddr, sizeof(aAddr), false); + + bool Error = false; + Error = Error || !m_pStoreStmt; + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_reset(m_pStoreStmt.get())) != SQLITE_OK; + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_text(m_pStoreStmt.get(), 1, aAddr, -1, SQLITE_STATIC)) != SQLITE_OK; + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_int(m_pStoreStmt.get(), 2, Ping)) != SQLITE_OK; + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_step(m_pStoreStmt.get())) != SQLITE_DONE; + if(Error) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowse_ping_cache", "failed to store ping"); + } + } +} + +void CServerBrowserPingCache::GetPingCache(const CEntry **ppEntries, int *pNumEntries) +{ + if(!m_aNewEntries.empty()) + { + class CAddrComparer + { + public: + bool operator()(const CEntry &a, const CEntry &b) + { + return net_addr_comp(&a.m_Addr, &b.m_Addr) < 0; + } + }; + std::vector aOldEntries; + std::swap(m_aEntries, aOldEntries); + + // Remove duplicates, keeping newer ones. + std::stable_sort(m_aNewEntries.begin(), m_aNewEntries.end(), CAddrComparer()); + { + unsigned To = 0; + for(unsigned int From = 0; From < m_aNewEntries.size(); From++) + { + if(To < From) + { + m_aNewEntries[To] = m_aNewEntries[From]; + } + if(From + 1 >= m_aNewEntries.size() || + net_addr_comp(&m_aNewEntries[From].m_Addr, &m_aNewEntries[From + 1].m_Addr) != 0) + { + To++; + } + } + m_aNewEntries.resize(To); + } + // Only keep the new entries where there are duplicates. + m_aEntries.reserve(m_aNewEntries.size() + aOldEntries.size()); + { + unsigned i = 0; + unsigned j = 0; + while(i < aOldEntries.size() && j < m_aNewEntries.size()) + { + int Cmp = net_addr_comp(&aOldEntries[i].m_Addr, &m_aNewEntries[j].m_Addr); + if(Cmp != 0) + { + if(Cmp < 0) + { + m_aEntries.push_back(aOldEntries[i]); + i++; + } + else + { + m_aEntries.push_back(m_aNewEntries[j]); + j++; + } + } + else + { + // Ignore the old element if we have both. + i++; + } + } + // Add the remaining elements. + for(; i < aOldEntries.size(); i++) + { + m_aEntries.push_back(aOldEntries[i]); + } + for(; j < m_aNewEntries.size(); j++) + { + m_aEntries.push_back(m_aNewEntries[j]); + } + } + m_aNewEntries.clear(); + } + *ppEntries = m_aEntries.data(); + *pNumEntries = m_aEntries.size(); +} + +IServerBrowserPingCache *CreateServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage) +{ + return new CServerBrowserPingCache(pConsole, pStorage); +} diff -Nru ddnet-15.3.2/src/engine/client/serverbrowser_ping_cache.h ddnet-15.5.4/src/engine/client/serverbrowser_ping_cache.h --- ddnet-15.3.2/src/engine/client/serverbrowser_ping_cache.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/serverbrowser_ping_cache.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,29 @@ +#ifndef ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H +#define ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H +#include + +class IConsole; +class IStorage; + +class IServerBrowserPingCache +{ +public: + class CEntry + { + public: + NETADDR m_Addr; + int m_Ping; + }; + + virtual ~IServerBrowserPingCache() {} + + virtual void Load() = 0; + + virtual void CachePing(NETADDR Addr, int Ping) = 0; + // The returned list is sorted by address, the addresses don't have a + // port. + virtual void GetPingCache(const CEntry **ppEntries, int *pNumEntries) = 0; +}; + +IServerBrowserPingCache *CreateServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage); +#endif // ENGINE_CLIENT_SERVERBROWSER_PING_CACHE_H diff -Nru ddnet-15.3.2/src/engine/client/sqlite.cpp ddnet-15.5.4/src/engine/client/sqlite.cpp --- ddnet-15.3.2/src/engine/client/sqlite.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/sqlite.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,60 @@ +#include +#include +#include + +#include + +void CSqliteDeleter::operator()(sqlite3 *pSqlite) +{ + sqlite3_close(pSqlite); +} + +void CSqliteStmtDeleter::operator()(sqlite3_stmt *pStmt) +{ + sqlite3_finalize(pStmt); +} + +int SqliteHandleError(IConsole *pConsole, int Error, sqlite3 *pSqlite, const char *pContext) +{ + if(Error != SQLITE_OK && Error != SQLITE_DONE && Error != SQLITE_ROW) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "%s at %s", sqlite3_errmsg(pSqlite), pContext); + pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "sqlite3", aBuf); + } + return Error; +} + +CSqlite SqliteOpen(IConsole *pConsole, IStorage *pStorage, const char *pPath) +{ + char aFullPath[MAX_PATH_LENGTH]; + pStorage->GetCompletePath(IStorage::TYPE_SAVE, pPath, aFullPath, sizeof(aFullPath)); + sqlite3 *pSqlite = nullptr; + const bool ErrorOpening = SQLITE_HANDLE_ERROR(sqlite3_open(aFullPath, &pSqlite)) != SQLITE_OK; + // Even on error, the database is initialized and needs to be freed. + // Except on allocation failure, but then it'll be nullptr which is + // also fine. + CSqlite pResult{pSqlite}; + if(ErrorOpening) + { + return nullptr; + } + bool Error = false; + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_exec(pSqlite, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr)); + Error = Error || SQLITE_HANDLE_ERROR(sqlite3_exec(pSqlite, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr)); + if(Error) + { + return nullptr; + } + return pResult; +} + +CSqliteStmt SqlitePrepare(IConsole *pConsole, sqlite3 *pSqlite, const char *pStatement) +{ + sqlite3_stmt *pTemp; + if(SQLITE_HANDLE_ERROR(sqlite3_prepare_v2(pSqlite, pStatement, -1, &pTemp, nullptr))) + { + return nullptr; + } + return CSqliteStmt{pTemp}; +} diff -Nru ddnet-15.3.2/src/engine/client/steam.cpp ddnet-15.5.4/src/engine/client/steam.cpp --- ddnet-15.3.2/src/engine/client/steam.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/steam.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -34,7 +34,7 @@ if(pConnect[0]) { NETADDR Connect; - if(net_addr_from_str(&Connect, pConnect) == 0) + if(net_host_lookup(pConnect, &Connect, NETTYPE_ALL) == 0) { m_ConnectAddr = Connect; m_GotConnectAddr = true; diff -Nru ddnet-15.3.2/src/engine/client/text.cpp ddnet-15.5.4/src/engine/client/text.cpp --- ddnet-15.3.2/src/engine/client/text.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/text.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -877,8 +877,8 @@ FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - int ActualX = (int)(pCursor->m_X * FakeToScreenX); - int ActualY = (int)(pCursor->m_Y * FakeToScreenY); + int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); + int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); CursorX = ActualX / FakeToScreenX; CursorY = ActualY / FakeToScreenY; @@ -1004,8 +1004,8 @@ DrawY += Size; if((m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } ++LineCount; if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) @@ -1082,8 +1082,8 @@ DrawY += Size; if((m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } GotNewLine = 1; GotNewLineLast = 1; @@ -1149,8 +1149,8 @@ FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - int ActualX = (int)(pCursor->m_X * FakeToScreenX); - int ActualY = (int)(pCursor->m_Y * FakeToScreenY); + int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); + int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); TextContainer.m_AlignedStartX = ActualX / FakeToScreenX; TextContainer.m_AlignedStartY = ActualY / FakeToScreenY; @@ -1228,8 +1228,8 @@ FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - int ActualX = (int)(pCursor->m_X * FakeToScreenX); - int ActualY = (int)(pCursor->m_Y * FakeToScreenY); + int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); + int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); CursorX = ActualX / FakeToScreenX; CursorY = ActualY / FakeToScreenY; @@ -1329,8 +1329,8 @@ DrawY += Size; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } ++LineCount; if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) @@ -1438,8 +1438,8 @@ DrawY += Size; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } GotNewLine = 1; GotNewLineLast = 1; @@ -1627,8 +1627,8 @@ DrawY += Size; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } ++LineCount; if(TextContainer.m_MaxLines > 0 && LineCount > TextContainer.m_MaxLines) @@ -1686,8 +1686,8 @@ DrawY += Size; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) { - DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign - DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; + DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign + DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; } ++LineCount; } @@ -1818,8 +1818,8 @@ { float FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); float FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); - int ActualX = (int)((TextContainer.m_X + X) * FakeToScreenX); - int ActualY = (int)((TextContainer.m_Y + Y) * FakeToScreenY); + int ActualX = (int)(((TextContainer.m_X + X) * FakeToScreenX) + 0.5f); + int ActualY = (int)(((TextContainer.m_Y + Y) * FakeToScreenY) + 0.5f); float AlignedX = ActualX / FakeToScreenX; float AlignedY = ActualY / FakeToScreenY; X = AlignedX - TextContainer.m_AlignedStartX; diff -Nru ddnet-15.3.2/src/engine/client/updater.cpp ddnet-15.5.4/src/engine/client/updater.cpp --- ddnet-15.3.2/src/engine/client/updater.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/updater.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -157,7 +157,7 @@ #endif #if !defined(CONF_PLATFORM_LINUX) - if(!str_comp_nocase(pFile + len - 4, ".so")) + if(!str_comp_nocase(pFile + len - 3, ".so")) return Success; #endif diff -Nru ddnet-15.3.2/src/engine/client/video.cpp ddnet-15.5.4/src/engine/client/video.cpp --- ddnet-15.3.2/src/engine/client/video.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/video.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -6,11 +6,18 @@ #include "video.h" +#ifndef CONF_BACKEND_OPENGL_ES +#include +#else +#include +#endif + // This code is mostly stolen from https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/muxing.c #define STREAM_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */ const size_t FORMAT_NCHANNELS = 3; +const size_t FORMAT_GL_NCHANNELS = 4; LOCK g_WriteLock = 0; CVideo::CVideo(CGraphics_Threaded *pGraphics, IStorage *pStorage, IConsole *pConsole, int Width, int Height, const char *pName) : @@ -95,7 +102,8 @@ m_pFormat = m_pFormatContext->oformat; size_t NVals = FORMAT_NCHANNELS * m_Width * m_Height; - m_pPixels = (uint8_t *)malloc(NVals * sizeof(GLubyte)); + size_t GLNVals = FORMAT_GL_NCHANNELS * m_Width * m_Height; + m_pPixels = (uint8_t *)malloc(GLNVals * sizeof(TWGLubyte)); m_pRGB = (uint8_t *)malloc(NVals * sizeof(uint8_t)); /* Add the audio and video streams using the default format codecs @@ -215,7 +223,7 @@ { if(m_NextFrame && m_Recording) { - // #ifdef CONF_PLATFORM_MACOSX + // #ifdef CONF_PLATFORM_MACOS // CAutoreleasePool AutoreleasePool; // #endif m_Vseq += 1; @@ -243,7 +251,7 @@ { if(m_Recording) { - // #ifdef CONF_PLATFORM_MACOSX + // #ifdef CONF_PLATFORM_MACOS // CAutoreleasePool AutoreleasePool; // #endif @@ -370,13 +378,13 @@ GLint Alignment; glGetIntegerv(GL_PACK_ALIGNMENT, &Alignment); glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, m_Width, m_Height, GL_RGB, GL_UNSIGNED_BYTE, m_pPixels); + glReadPixels(0, 0, m_Width, m_Height, GL_RGBA, GL_UNSIGNED_BYTE, m_pPixels); glPixelStorei(GL_PACK_ALIGNMENT, Alignment); for(int i = 0; i < m_Height; i++) { for(int j = 0; j < m_Width; j++) { - size_t CurGL = FORMAT_NCHANNELS * (m_Width * (m_Height - i - 1) + j); + size_t CurGL = FORMAT_GL_NCHANNELS * (m_Width * (m_Height - i - 1) + j); size_t CurRGB = FORMAT_NCHANNELS * (m_Width * i + j); for(int k = 0; k < (int)FORMAT_NCHANNELS; k++) m_pRGB[CurRGB + k] = m_pPixels[CurGL + k]; @@ -669,23 +677,27 @@ { int RetRecv = 0; - AVPacket Packet = {0}; + AVPacket *pPacket = av_packet_alloc(); + if(pPacket == nullptr) + { + dbg_msg("video_recorder", "Failed allocating packet"); + return; + } - av_init_packet(&Packet); - Packet.data = 0; - Packet.size = 0; + pPacket->data = 0; + pPacket->size = 0; avcodec_send_frame(pStream->pEnc, pStream->pFrame); do { - RetRecv = avcodec_receive_packet(pStream->pEnc, &Packet); + RetRecv = avcodec_receive_packet(pStream->pEnc, pPacket); if(!RetRecv) { /* rescale output packet timestamp values from codec to stream timebase */ - av_packet_rescale_ts(&Packet, pStream->pEnc->time_base, pStream->pSt->time_base); - Packet.stream_index = pStream->pSt->index; + av_packet_rescale_ts(pPacket, pStream->pEnc->time_base, pStream->pSt->time_base); + pPacket->stream_index = pStream->pSt->index; - if(int Ret = av_interleaved_write_frame(m_pFormatContext, &Packet)) + if(int Ret = av_interleaved_write_frame(m_pFormatContext, pPacket)) { char aBuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(Ret, aBuf, sizeof(aBuf)); @@ -700,6 +712,8 @@ { dbg_msg("video_recorder", "Error encoding frame, error: %d", RetRecv); } + + av_packet_free(&pPacket); } void CVideo::FinishFrames(OutputStream *pStream) @@ -707,24 +721,28 @@ dbg_msg("video_recorder", "------------"); int RetRecv = 0; - AVPacket Packet = {0}; + AVPacket *pPacket = av_packet_alloc(); + if(pPacket == nullptr) + { + dbg_msg("video_recorder", "Failed allocating packet"); + return; + } - av_init_packet(&Packet); - Packet.data = 0; - Packet.size = 0; + pPacket->data = 0; + pPacket->size = 0; avcodec_send_frame(pStream->pEnc, 0); do { - RetRecv = avcodec_receive_packet(pStream->pEnc, &Packet); + RetRecv = avcodec_receive_packet(pStream->pEnc, pPacket); if(!RetRecv) { /* rescale output packet timestamp values from codec to stream timebase */ //if(pStream->pSt->codec->codec_type == AVMEDIA_TYPE_AUDIO) - av_packet_rescale_ts(&Packet, pStream->pEnc->time_base, pStream->pSt->time_base); - Packet.stream_index = pStream->pSt->index; + av_packet_rescale_ts(pPacket, pStream->pEnc->time_base, pStream->pSt->time_base); + pPacket->stream_index = pStream->pSt->index; - if(int Ret = av_interleaved_write_frame(m_pFormatContext, &Packet)) + if(int Ret = av_interleaved_write_frame(m_pFormatContext, pPacket)) { char aBuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(Ret, aBuf, sizeof(aBuf)); @@ -739,6 +757,8 @@ { dbg_msg("video_recorder", "failed to finish recoding, error: %d", RetRecv); } + + av_packet_free(&pPacket); } void CVideo::CloseStream(OutputStream *pStream) diff -Nru ddnet-15.3.2/src/engine/client/video.h ddnet-15.5.4/src/engine/client/video.h --- ddnet-15.3.2/src/engine/client/video.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client/video.h 2021-06-20 09:38:48.000000000 +0000 @@ -1,21 +1,9 @@ #ifndef ENGINE_CLIENT_VIDEO_H #define ENGINE_CLIENT_VIDEO_H -#if defined(__ANDROID__) -#define GL_GLEXT_PROTOTYPES -#include -#include -#include -#define glOrtho glOrthof -#else -#include "SDL_opengl.h" - -#if defined(CONF_PLATFORM_MACOSX) -#include "OpenGL/glu.h" -#else -#include "GL/glu.h" -#endif -#endif +#include + +#include "graphics_defines.h" extern "C" { #include @@ -26,8 +14,6 @@ #include }; -#include - #include #include #define ALEN 2048 @@ -116,7 +102,7 @@ bool m_HasAudio; - GLubyte *m_pPixels; + TWGLubyte *m_pPixels; OutputStream m_VideoStream; OutputStream m_AudioStream; diff -Nru ddnet-15.3.2/src/engine/client.h ddnet-15.5.4/src/engine/client.h --- ddnet-15.3.2/src/engine/client.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/client.h 2021-06-20 09:38:48.000000000 +0000 @@ -125,8 +125,7 @@ // gfx virtual void SwitchWindowScreen(int Index) = 0; - virtual void ToggleFullscreen() = 0; - virtual void ToggleWindowBordered() = 0; + virtual void SetWindowParams(int FullscreenMode, bool IsBorderless) = 0; virtual void ToggleWindowVSync() = 0; virtual void LoadFont() = 0; virtual void Notify(const char *pTitle, const char *pMessage) = 0; @@ -135,22 +134,22 @@ virtual void EnterGame() = 0; // - virtual const char *MapDownloadName() = 0; - virtual int MapDownloadAmount() = 0; - virtual int MapDownloadTotalsize() = 0; + virtual const char *MapDownloadName() const = 0; + virtual int MapDownloadAmount() const = 0; + virtual int MapDownloadTotalsize() const = 0; // input - virtual int *GetInput(int Tick, int IsDummy = 0) = 0; - virtual int *GetDirectInput(int Tick, int IsDummy = 0) = 0; + virtual int *GetInput(int Tick, int IsDummy = 0) const = 0; + virtual int *GetDirectInput(int Tick, int IsDummy = 0) const = 0; // remote console virtual void RconAuth(const char *pUsername, const char *pPassword) = 0; - virtual bool RconAuthed() = 0; - virtual bool UseTempRconCommands() = 0; + virtual bool RconAuthed() const = 0; + virtual bool UseTempRconCommands() const = 0; virtual void Rcon(const char *pLine) = 0; // server info - virtual void GetServerInfo(class CServerInfo *pServerInfo) = 0; + virtual void GetServerInfo(class CServerInfo *pServerInfo) const = 0; virtual int GetPredictionTime() = 0; @@ -163,10 +162,10 @@ }; // TODO: Refactor: should redo this a bit i think, too many virtual calls - virtual int SnapNumItems(int SnapID) = 0; - virtual void *SnapFindItem(int SnapID, int Type, int ID) = 0; - virtual void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem) = 0; - virtual int SnapItemSize(int SnapID, int Index) = 0; + virtual int SnapNumItems(int SnapID) const = 0; + virtual void *SnapFindItem(int SnapID, int Type, int ID) const = 0; + virtual void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem) const = 0; + virtual int SnapItemSize(int SnapID, int Index) const = 0; virtual void SnapInvalidateItem(int SnapID, int Index) = 0; virtual void SnapSetStaticsize(int ItemType, int Size) = 0; @@ -184,22 +183,22 @@ } // - virtual const char *PlayerName() = 0; - virtual const char *DummyName() = 0; - virtual const char *ErrorString() = 0; - virtual const char *LatestVersion() = 0; - virtual bool ConnectionProblems() = 0; + virtual const char *PlayerName() const = 0; + virtual const char *DummyName() const = 0; + virtual const char *ErrorString() const = 0; + virtual const char *LatestVersion() const = 0; + virtual bool ConnectionProblems() const = 0; - virtual bool SoundInitFailed() = 0; + virtual bool SoundInitFailed() const = 0; - virtual IGraphics::CTextureHandle GetDebugFont() = 0; // TODO: remove this function + virtual IGraphics::CTextureHandle GetDebugFont() const = 0; // TODO: remove this function //DDRace - virtual const char *GetCurrentMap() = 0; - virtual const char *GetCurrentMapPath() = 0; - virtual SHA256_DIGEST GetCurrentMapSha256() = 0; - virtual unsigned GetCurrentMapCrc() = 0; + virtual const char *GetCurrentMap() const = 0; + virtual const char *GetCurrentMapPath() const = 0; + virtual SHA256_DIGEST GetCurrentMapSha256() const = 0; + virtual unsigned GetCurrentMapCrc() const = 0; virtual int GetCurrentRaceTime() = 0; @@ -212,7 +211,7 @@ virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser) = 0; virtual void RequestDDNetInfo() = 0; - virtual bool EditorHasUnsavedData() = 0; + virtual bool EditorHasUnsavedData() const = 0; virtual void GenerateTimeoutSeed() = 0; @@ -250,17 +249,20 @@ virtual void SendDummyInfo(bool Start) = 0; virtual int GetLastRaceTick() = 0; - virtual const char *GetItemName(int Type) = 0; - virtual const char *Version() = 0; - virtual const char *NetVersion() = 0; - virtual int DDNetVersion() = 0; - virtual const char *DDNetVersionStr() = 0; + virtual const char *GetItemName(int Type) const = 0; + virtual const char *Version() const = 0; + virtual const char *NetVersion() const = 0; + virtual int DDNetVersion() const = 0; + virtual const char *DDNetVersionStr() const = 0; virtual void OnDummyDisconnect() = 0; + virtual void DummyResetInput() = 0; virtual void Echo(const char *pString) = 0; virtual bool CanDisplayWarning() = 0; virtual bool IsDisplayingWarning() = 0; }; +void SnapshotRemoveExtraProjectileInfo(unsigned char *pData); + extern IGameClient *CreateGameClient(); #endif diff -Nru ddnet-15.3.2/src/engine/console.h ddnet-15.5.4/src/engine/console.h --- ddnet-15.3.2/src/engine/console.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/console.h 2021-06-20 09:38:48.000000000 +0000 @@ -76,7 +76,7 @@ }; typedef void (*FTeeHistorianCommandCallback)(int ClientID, int FlagMask, const char *pCmd, IResult *pResult, void *pUser); - typedef void (*FPrintCallback)(const char *pStr, void *pUser, bool Highlighted); + typedef void (*FPrintCallback)(const char *pStr, void *pUser, ColorRGBA PrintColor); typedef void (*FPossibleCallback)(const char *pCmd, void *pUser); typedef void (*FCommandCallback)(IResult *pResult, void *pUserData); typedef void (*FChainCommandCallback)(IResult *pResult, void *pUserData, FCommandCallback pfnCallback, void *pCallbackUserData); @@ -103,7 +103,7 @@ virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData) = 0; virtual void SetPrintOutputLevel(int Index, int OutputLevel) = 0; virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr) = 0; - virtual void Print(int Level, const char *pFrom, const char *pStr, bool Highlighted = false) = 0; + virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = {1, 1, 1, 1}) = 0; virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser) = 0; virtual void SetAccessLevel(int AccessLevel) = 0; diff -Nru ddnet-15.3.2/src/engine/editor.h ddnet-15.5.4/src/engine/editor.h --- ddnet-15.3.2/src/engine/editor.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/editor.h 2021-06-20 09:38:48.000000000 +0000 @@ -11,7 +11,7 @@ virtual ~IEditor() {} virtual void Init() = 0; virtual void UpdateAndRender() = 0; - virtual bool HasUnsavedData() = 0; + virtual bool HasUnsavedData() const = 0; virtual int Load(const char *pFilename, int StorageType) = 0; virtual int Save(const char *pFilename) = 0; virtual void UpdateMentions() = 0; diff -Nru ddnet-15.3.2/src/engine/engine.h ddnet-15.5.4/src/engine/engine.h --- ddnet-15.3.2/src/engine/engine.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/engine.h 2021-06-20 09:38:48.000000000 +0000 @@ -32,8 +32,10 @@ virtual void Init() = 0; virtual void InitLogfile() = 0; virtual void AddJob(std::shared_ptr pJob) = 0; + static void RunJobBlocking(IJob *pJob); }; extern IEngine *CreateEngine(const char *pAppname, bool Silent, int Jobs); +extern IEngine *CreateTestEngine(const char *pAppname, int Jobs); #endif diff -Nru ddnet-15.3.2/src/engine/external/json-parser/json.h ddnet-15.5.4/src/engine/external/json-parser/json.h --- ddnet-15.3.2/src/engine/external/json-parser/json.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/external/json-parser/json.h 2021-06-20 09:38:48.000000000 +0000 @@ -226,6 +226,12 @@ }; } + /* DDNet additions */ + inline operator int () const + { + return (json_int_t) *this; + } + inline operator bool () const { if (type != json_boolean) diff -Nru ddnet-15.3.2/src/engine/graphics.h ddnet-15.5.4/src/engine/graphics.h --- ddnet-15.3.2/src/engine/graphics.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/graphics.h 2021-06-20 09:38:48.000000000 +0000 @@ -93,7 +93,8 @@ class CVideoMode { public: - int m_Width, m_Height; + int m_CanvasWidth, m_CanvasHeight; + int m_WindowWidth, m_WindowHeight; int m_Red, m_Green, m_Blue; }; @@ -147,6 +148,13 @@ GL_STexCoord3D m_Tex; }; +enum EGraphicsDriverAgeType +{ + GRAPHICS_DRIVER_AGE_TYPE_LEGACY = 0, + GRAPHICS_DRIVER_AGE_TYPE_DEFAULT, + GRAPHICS_DRIVER_AGE_TYPE_MODERN, +}; + typedef void (*WINDOW_RESIZE_FUNC)(void *pUser); namespace client_data7 { @@ -159,16 +167,11 @@ protected: int m_ScreenWidth; int m_ScreenHeight; - int m_DesktopScreenWidth; - int m_DesktopScreenHeight; + float m_ScreenHiDPIScale; public: - /* Constants: Texture Loading Flags - TEXLOAD_NORESAMPLE - Prevents the texture from any resampling - */ enum { - TEXLOAD_NORESAMPLE = 1 << 0, TEXLOAD_NOMIPMAPS = 1 << 1, TEXLOAD_NO_COMPRESSION = 1 << 2, TEXLOAD_TO_3D_TEXTURE = (1 << 3), @@ -195,9 +198,11 @@ int ScreenWidth() const { return m_ScreenWidth; } int ScreenHeight() const { return m_ScreenHeight; } float ScreenAspect() const { return (float)ScreenWidth() / (float)ScreenHeight(); } + float ScreenHiDPIScale() const { return m_ScreenHiDPIScale; } + int WindowWidth() const { return m_ScreenWidth / m_ScreenHiDPIScale; } + int WindowHeight() const { return m_ScreenHeight / m_ScreenHiDPIScale; } - virtual bool Fullscreen(bool State) = 0; - virtual void SetWindowBordered(bool State) = 0; + virtual void SetWindowParams(int FullscreenMode, bool IsBorderless) = 0; virtual bool SetWindowScreen(int Index) = 0; virtual bool SetVSync(bool State) = 0; virtual int GetWindowScreen() = 0; @@ -270,12 +275,18 @@ virtual void UpdateBufferContainer(int ContainerIndex, struct SBufferContainerInfo *pContainerInfo) = 0; virtual void IndicesNumRequiredNotify(unsigned int RequiredIndicesCount) = 0; + virtual void GetDriverVersion(EGraphicsDriverAgeType DriverAgeType, int &Major, int &Minor, int &Patch) = 0; + virtual bool IsConfigModernAPI() = 0; virtual bool IsTileBufferingEnabled() = 0; virtual bool IsQuadBufferingEnabled() = 0; virtual bool IsTextBufferingEnabled() = 0; virtual bool IsQuadContainerBufferingEnabled() = 0; virtual bool HasTextureArrays() = 0; + virtual const char *GetVendorString() = 0; + virtual const char *GetVersionString() = 0; + virtual const char *GetRendererString() = 0; + struct CLineItem { float m_X0, m_Y0, m_X1, m_Y1; @@ -375,7 +386,7 @@ // synchronization virtual void InsertSignal(class CSemaphore *pSemaphore) = 0; - virtual bool IsIdle() = 0; + virtual bool IsIdle() const = 0; virtual void WaitForIdle() = 0; virtual void SetWindowGrab(bool Grab) = 0; diff -Nru ddnet-15.3.2/src/engine/input.h ddnet-15.5.4/src/engine/input.h --- ddnet-15.3.2/src/engine/input.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/input.h 2021-06-20 09:38:48.000000000 +0000 @@ -58,6 +58,9 @@ return m_aInputEvents[Index]; } + CEvent *GetEventsRaw() { return m_aInputEvents; } + int *GetEventCountRaw() { return &m_NumEvents; } + // keys virtual bool KeyIsPressed(int Key) const = 0; virtual bool KeyPress(int Key, bool CheckCounter = false) const = 0; diff -Nru ddnet-15.3.2/src/engine/masterserver.h ddnet-15.5.4/src/engine/masterserver.h --- ddnet-15.3.2/src/engine/masterserver.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/masterserver.h 2021-06-20 09:38:48.000000000 +0000 @@ -21,12 +21,12 @@ virtual int RefreshAddresses(int Nettype) = 0; virtual void Update() = 0; - virtual int IsRefreshing() = 0; - virtual NETADDR GetAddr(int Index) = 0; + virtual bool IsRefreshing() const = 0; + virtual NETADDR GetAddr(int Index) const = 0; virtual void SetCount(int Index, int Count) = 0; - virtual int GetCount(int Index) = 0; - virtual const char *GetName(int Index) = 0; - virtual bool IsValid(int Index) = 0; + virtual int GetCount(int Index) const = 0; + virtual const char *GetName(int Index) const = 0; + virtual bool IsValid(int Index) const = 0; }; class IEngineMasterServer : public IMasterServer diff -Nru ddnet-15.3.2/src/engine/server/authmanager.cpp ddnet-15.5.4/src/engine/server/authmanager.cpp --- ddnet-15.3.2/src/engine/server/authmanager.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/authmanager.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -80,7 +80,7 @@ return m_aKeys.size(); } -int CAuthManager::FindKey(const char *pIdent) +int CAuthManager::FindKey(const char *pIdent) const { for(int i = 0; i < m_aKeys.size(); i++) if(!str_comp(m_aKeys[i].m_aIdent, pIdent)) @@ -89,28 +89,28 @@ return -1; } -bool CAuthManager::CheckKey(int Slot, const char *pPw) +bool CAuthManager::CheckKey(int Slot, const char *pPw) const { if(Slot < 0 || Slot >= m_aKeys.size()) return false; return m_aKeys[Slot].m_Pw == HashPassword(pPw, m_aKeys[Slot].m_aSalt); } -int CAuthManager::DefaultKey(int AuthLevel) +int CAuthManager::DefaultKey(int AuthLevel) const { if(AuthLevel < 0 || AuthLevel > AUTHED_ADMIN) return 0; return m_aDefault[AUTHED_ADMIN - AuthLevel]; } -int CAuthManager::KeyLevel(int Slot) +int CAuthManager::KeyLevel(int Slot) const { if(Slot < 0 || Slot >= m_aKeys.size()) return false; return m_aKeys[Slot].m_Level; } -const char *CAuthManager::KeyIdent(int Slot) +const char *CAuthManager::KeyIdent(int Slot) const { if(Slot < 0 || Slot >= m_aKeys.size()) return NULL; @@ -157,12 +157,12 @@ m_aDefault[Index] = AddKey(IDENTS[Index], pPw, Level); } -bool CAuthManager::IsGenerated() +bool CAuthManager::IsGenerated() const { return m_Generated; } -int CAuthManager::NumNonDefaultKeys() +int CAuthManager::NumNonDefaultKeys() const { int DefaultCount = (m_aDefault[0] >= 0) + (m_aDefault[1] >= 0) + (m_aDefault[2] >= 0); return m_aKeys.size() - DefaultCount; diff -Nru ddnet-15.3.2/src/engine/server/authmanager.h ddnet-15.5.4/src/engine/server/authmanager.h --- ddnet-15.3.2/src/engine/server/authmanager.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/authmanager.h 2021-06-20 09:38:48.000000000 +0000 @@ -37,17 +37,17 @@ int AddKeyHash(const char *pIdent, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel); int AddKey(const char *pIdent, const char *pPw, int AuthLevel); int RemoveKey(int Slot); // Returns the old key slot that is now in the named one. - int FindKey(const char *pIdent); - bool CheckKey(int Slot, const char *pPw); - int DefaultKey(int AuthLevel); - int KeyLevel(int Slot); - const char *KeyIdent(int Slot); + int FindKey(const char *pIdent) const; + bool CheckKey(int Slot, const char *pPw) const; + int DefaultKey(int AuthLevel) const; + int KeyLevel(int Slot) const; + const char *KeyIdent(int Slot) const; void UpdateKeyHash(int Slot, MD5_DIGEST Hash, const unsigned char *pSalt, int AuthLevel); void UpdateKey(int Slot, const char *pPw, int AuthLevel); void ListKeys(FListCallback pfnListCallbac, void *pUser); void AddDefaultKey(int Level, const char *pPw); - bool IsGenerated(); - int NumNonDefaultKeys(); + bool IsGenerated() const; + int NumNonDefaultKeys() const; }; #endif //ENGINE_SERVER_AUTHMANAGER_H diff -Nru ddnet-15.3.2/src/engine/server/databases/connection.h ddnet-15.5.4/src/engine/server/databases/connection.h --- ddnet-15.3.2/src/engine/server/databases/connection.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/connection.h 2021-06-20 09:38:48.000000000 +0000 @@ -38,19 +38,17 @@ // Get Median Map Time from l.Map virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const = 0; - enum Status - { - IN_USE, - SUCCESS, - FAILURE, - }; // tries to allocate the connection from the pool established - virtual Status Connect() = 0; + // + // returns true on failure + virtual bool Connect(char *pError, int ErrorSize) = 0; // has to be called to return the connection back to the pool virtual void Disconnect() = 0; // ? for Placeholders, connection has to be established, can overwrite previous prepared statements - virtual void PrepareStatement(const char *pStmt) = 0; + // + // returns true on failure + virtual bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize) = 0; // PrepareStatement has to be called beforehand, virtual void BindString(int Idx, const char *pString) = 0; @@ -60,23 +58,27 @@ // Print expanded sql statement virtual void Print() = 0; + // executes the query and returns if a result row exists and selects it // when called multiple times the next row is selected - virtual bool Step() = 0; + // + // returns true on failure + virtual bool Step(bool *pEnd, char *pError, int ErrorSize) = 0; // executes the query and returns the number of rows affected by the update/insert/delete - // FIXME(2020-01-20): change function to AffectedRows() when moved to c-api of MySQL - virtual int ExecuteUpdate() = 0; - - virtual bool IsNull(int Col) const = 0; - virtual float GetFloat(int Col) const = 0; - virtual int GetInt(int Col) const = 0; + // + // returns true on failure + virtual bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) = 0; + + virtual bool IsNull(int Col) = 0; + virtual float GetFloat(int Col) = 0; + virtual int GetInt(int Col) = 0; // ensures that the string is null terminated - virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0; + virtual void GetString(int Col, char *pBuffer, int BufferSize) = 0; // returns number of bytes read into the buffer - virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0; + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) = 0; // SQL statements, that can't be abstracted, has side effects to the result - virtual void AddPoints(const char *pPlayer, int Points) = 0; + virtual bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) = 0; private: char m_aPrefix[64]; @@ -89,4 +91,18 @@ void FormatCreatePoints(char *aBuf, unsigned int BufferSize); }; +int MysqlInit(); +void MysqlUninit(); + +IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup); +// Returns nullptr if MySQL support is not compiled in. +IDbConnection *CreateMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup); + #endif // ENGINE_SERVER_DATABASES_CONNECTION_H diff -Nru ddnet-15.3.2/src/engine/server/databases/connection_pool.cpp ddnet-15.5.4/src/engine/server/databases/connection_pool.cpp --- ddnet-15.3.2/src/engine/server/databases/connection_pool.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/connection_pool.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -2,10 +2,6 @@ #include "connection.h" #include -#if defined(CONF_SQL) -#include -#endif -#include // helper struct to hold thread data struct CSqlExecData @@ -206,39 +202,26 @@ bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pData, bool Failure) { - if(pConnection->Connect() != IDbConnection::SUCCESS) - return false; - bool Success = false; - try - { - switch(pData->m_Mode) - { - case CSqlExecData::READ_ACCESS: - if(pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get())) - Success = true; - break; - case CSqlExecData::WRITE_ACCESS: - if(pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), Failure)) - Success = true; - break; - } - } -#if defined(CONF_SQL) - catch(sql::SQLException &e) - { - dbg_msg("sql", "%s MySQL Error: %s", pData->m_pName, e.what()); - } -#endif - catch(std::runtime_error &e) + char aError[256] = "error message not initialized"; + if(pConnection->Connect(aError, sizeof(aError))) { - dbg_msg("sql", "%s SQLite Error: %s", pData->m_pName, e.what()); + dbg_msg("sql", "failed connecting to db: %s", aError); + return false; } - catch(...) + bool Success = false; + switch(pData->m_Mode) { - dbg_msg("sql", "%s Unexpected exception caught", pData->m_pName); + case CSqlExecData::READ_ACCESS: + Success = !pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get(), aError, sizeof(aError)); + break; + case CSqlExecData::WRITE_ACCESS: + Success = !pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), Failure, aError, sizeof(aError)); + break; } pConnection->Disconnect(); if(!Success) - dbg_msg("sql", "%s failed", pData->m_pName); + { + dbg_msg("sql", "%s failed: %s", pData->m_pName, aError); + } return Success; } diff -Nru ddnet-15.3.2/src/engine/server/databases/connection_pool.h ddnet-15.5.4/src/engine/server/databases/connection_pool.h --- ddnet-15.3.2/src/engine/server/databases/connection_pool.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/connection_pool.h 2021-06-20 09:38:48.000000000 +0000 @@ -40,8 +40,9 @@ ~CDbConnectionPool(); CDbConnectionPool &operator=(const CDbConnectionPool &) = delete; - typedef bool (*FRead)(IDbConnection *, const ISqlData *); - typedef bool (*FWrite)(IDbConnection *, const ISqlData *, bool); + // Returns false on success. + typedef bool (*FRead)(IDbConnection *, const ISqlData *, char *pError, int ErrorSize); + typedef bool (*FWrite)(IDbConnection *, const ISqlData *, bool, char *pError, int ErrorSize); enum Mode { diff -Nru ddnet-15.3.2/src/engine/server/databases/mysql.cpp ddnet-15.5.4/src/engine/server/databases/mysql.cpp --- ddnet-15.3.2/src/engine/server/databases/mysql.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/mysql.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,17 +1,144 @@ -#include "mysql.h" +#include "connection.h" + +#if defined(CONF_SQL) +#include #include #include -#if defined(CONF_SQL) -#include -#include -#include -#include -#endif -#include +#include +#include +#include + +enum +{ + MYSQLSTATE_UNINITIALIZED, + MYSQLSTATE_INITIALIZED, + MYSQLSTATE_SHUTTINGDOWN, +}; + +std::atomic_int g_MysqlState = {MYSQLSTATE_UNINITIALIZED}; +std::atomic_int g_MysqlNumConnections; + +int MysqlInit() +{ + dbg_assert(mysql_thread_safe(), "MySQL library without thread safety"); + dbg_assert(g_MysqlState == MYSQLSTATE_UNINITIALIZED, "double MySQL initialization"); + if(mysql_library_init(0, nullptr, nullptr)) + { + return 1; + } + int Uninitialized = MYSQLSTATE_UNINITIALIZED; + bool Swapped = g_MysqlState.compare_exchange_strong(Uninitialized, MYSQLSTATE_INITIALIZED); + (void)Swapped; + dbg_assert(Swapped, "MySQL double initialization"); + return 0; +} + +void MysqlUninit() +{ + int Initialized = MYSQLSTATE_INITIALIZED; + bool Swapped = g_MysqlState.compare_exchange_strong(Initialized, MYSQLSTATE_SHUTTINGDOWN); + (void)Swapped; + dbg_assert(Swapped, "double MySQL free or free without initialization"); + int Counter = g_MysqlNumConnections; + if(Counter != 0) + { + dbg_msg("mysql", "can't deinitialize, connections remaining: %d", Counter); + return; + } + mysql_library_end(); +} + +class CMysqlConnection : public IDbConnection +{ +public: + CMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup); + virtual ~CMysqlConnection(); + virtual void Print(IConsole *pConsole, const char *Mode); + + virtual CMysqlConnection *Copy(); + + virtual const char *BinaryCollate() const { return "utf8mb4_bin"; } + virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); + virtual const char *InsertTimestampAsUtc() const { return "?"; } + virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; } + virtual const char *InsertIgnore() const { return "INSERT IGNORE"; }; + virtual const char *Random() const { return "RAND()"; }; + virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; + + virtual bool Connect(char *pError, int ErrorSize); + virtual void Disconnect(); + + virtual bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize); + + virtual void BindString(int Idx, const char *pString); + virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); + virtual void BindInt(int Idx, int Value); + virtual void BindFloat(int Idx, float Value); + + virtual void Print() {} + virtual bool Step(bool *pEnd, char *pError, int ErrorSize); + virtual bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize); + + virtual bool IsNull(int Col); + virtual float GetFloat(int Col); + virtual int GetInt(int Col); + virtual void GetString(int Col, char *pBuffer, int BufferSize); + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize); + + virtual bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize); + +private: + class CStmtDeleter + { + public: + void operator()(MYSQL_STMT *pStmt) const; + }; + + char m_aErrorDetail[128]; + void StoreErrorMysql(const char *pContext); + void StoreErrorStmt(const char *pContext); + bool ConnectImpl(); + bool PrepareAndExecuteStatement(const char *pStmt); + //static void DeleteResult(MYSQL_RES *pResult); + + union UParameterExtra + { + int i; + unsigned long ul; + float f; + }; + + bool m_NewQuery = false; + bool m_HaveConnection = false; + MYSQL m_Mysql; + std::unique_ptr m_pStmt = nullptr; + std::vector m_aStmtParameters; + std::vector m_aStmtParameterExtras; + + // copy of config vars + char m_aDatabase[64]; + char m_aUser[64]; + char m_aPass[64]; + char m_aIp[64]; + int m_Port; + bool m_Setup; -CLock CMysqlConnection::m_SqlDriverLock; + std::atomic_bool m_InUse; +}; + +void CMysqlConnection::CStmtDeleter::operator()(MYSQL_STMT *pStmt) const +{ + mysql_stmt_close(pStmt); +} CMysqlConnection::CMysqlConnection( const char *pDatabase, @@ -22,24 +149,52 @@ int Port, bool Setup) : IDbConnection(pPrefix), -#if defined(CONF_SQL) - m_NewQuery(false), -#endif m_Port(Port), m_Setup(Setup), m_InUse(false) { + g_MysqlNumConnections += 1; + dbg_assert(g_MysqlState == MYSQLSTATE_INITIALIZED, "MySQL library not in initialized state"); + + mem_zero(m_aErrorDetail, sizeof(m_aErrorDetail)); + mem_zero(&m_Mysql, sizeof(m_Mysql)); + mysql_init(&m_Mysql); + str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase)); str_copy(m_aUser, pUser, sizeof(m_aUser)); str_copy(m_aPass, pPass, sizeof(m_aPass)); str_copy(m_aIp, pIp, sizeof(m_aIp)); -#ifndef CONF_SQL - dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time"); -#endif } CMysqlConnection::~CMysqlConnection() { + mysql_close(&m_Mysql); + g_MysqlNumConnections -= 1; +} + +void CMysqlConnection::StoreErrorMysql(const char *pContext) +{ + str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:mysql:%d): %s", pContext, mysql_errno(&m_Mysql), mysql_error(&m_Mysql)); +} + +void CMysqlConnection::StoreErrorStmt(const char *pContext) +{ + str_format(m_aErrorDetail, sizeof(m_aErrorDetail), "(%s:stmt:%d): %s", pContext, mysql_stmt_errno(m_pStmt.get()), mysql_stmt_error(m_pStmt.get())); +} + +bool CMysqlConnection::PrepareAndExecuteStatement(const char *pStmt) +{ + if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt))) + { + StoreErrorStmt("prepare"); + return true; + } + if(mysql_stmt_execute(m_pStmt.get())) + { + StoreErrorStmt("execute"); + return true; + } + return false; } void CMysqlConnection::Print(IConsole *pConsole, const char *Mode) @@ -61,121 +216,113 @@ str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp); } -IDbConnection::Status CMysqlConnection::Connect() +bool CMysqlConnection::Connect(char *pError, int ErrorSize) { -#if defined(CONF_SQL) if(m_InUse.exchange(true)) - return Status::IN_USE; + { + dbg_assert(0, "Tried connecting while the connection is in use"); + } m_NewQuery = true; - if(m_pConnection != nullptr) + if(ConnectImpl()) { - try - { - // Connect to specific database - m_pConnection->setSchema(m_aDatabase); - return Status::SUCCESS; - } - catch(sql::SQLException &e) - { - dbg_msg("sql", "MySQL Error: %s", e.what()); - } - catch(const std::exception &ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.what()); - } - catch(const std::string &ex) - { - dbg_msg("sql", "MySQL Error: %s", ex.c_str()); - } - catch(...) - { - dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); - } - - dbg_msg("sql", "FAILURE: SQL connection failed, trying to reconnect"); + str_copy(pError, m_aErrorDetail, ErrorSize); + m_InUse.store(false); + return true; } + return false; +} - try +bool CMysqlConnection::ConnectImpl() +{ + if(m_HaveConnection) { - m_pConnection.reset(); - m_pPreparedStmt.reset(); - m_pResults.reset(); - - sql::ConnectOptionsMap connection_properties; - connection_properties["hostName"] = sql::SQLString(m_aIp); - connection_properties["port"] = m_Port; - connection_properties["userName"] = sql::SQLString(m_aUser); - connection_properties["password"] = sql::SQLString(m_aPass); - connection_properties["OPT_CONNECT_TIMEOUT"] = 60; - connection_properties["OPT_READ_TIMEOUT"] = 60; - connection_properties["OPT_WRITE_TIMEOUT"] = 120; - connection_properties["OPT_RECONNECT"] = true; - connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4"); - connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4"); - - // Create connection - { - CScopeLock GlobalLockScope(&m_SqlDriverLock); - sql::Driver *pDriver = get_driver_instance(); - m_pConnection.reset(pDriver->connect(connection_properties)); - } - - // Create Statement - m_pStmt = std::unique_ptr(m_pConnection->createStatement()); - - // Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough - m_pStmt->execute("SET CHARACTER SET utf8mb4;"); - - if(m_Setup) + if(m_pStmt && mysql_stmt_free_result(m_pStmt.get())) { - char aBuf[1024]; - // create database - str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase); - m_pStmt->execute(aBuf); - // Connect to specific database - m_pConnection->setSchema(m_aDatabase); - FormatCreateRace(aBuf, sizeof(aBuf)); - m_pStmt->execute(aBuf); - FormatCreateTeamrace(aBuf, sizeof(aBuf), "VARBINARY(16)"); - m_pStmt->execute(aBuf); - FormatCreateMaps(aBuf, sizeof(aBuf)); - m_pStmt->execute(aBuf); - FormatCreateSaves(aBuf, sizeof(aBuf)); - m_pStmt->execute(aBuf); - FormatCreatePoints(aBuf, sizeof(aBuf)); - m_pStmt->execute(aBuf); - m_Setup = false; + StoreErrorStmt("free_result"); + dbg_msg("mysql", "can't free last result %s", m_aErrorDetail); } - else + if(!mysql_select_db(&m_Mysql, m_aDatabase)) { - // Connect to specific database - m_pConnection->setSchema(m_aDatabase); + // Success. + return false; } - dbg_msg("sql", "sql connection established"); - return Status::SUCCESS; + StoreErrorMysql("select_db"); + dbg_msg("mysql", "ping error, trying to reconnect %s", m_aErrorDetail); + mysql_close(&m_Mysql); + mem_zero(&m_Mysql, sizeof(m_Mysql)); + mysql_init(&m_Mysql); } - catch(sql::SQLException &e) + + m_pStmt = nullptr; + unsigned int OptConnectTimeout = 60; + unsigned int OptReadTimeout = 60; + unsigned int OptWriteTimeout = 120; + my_bool OptReconnect = true; + mysql_options(&m_Mysql, MYSQL_OPT_CONNECT_TIMEOUT, &OptConnectTimeout); + mysql_options(&m_Mysql, MYSQL_OPT_READ_TIMEOUT, &OptReadTimeout); + mysql_options(&m_Mysql, MYSQL_OPT_WRITE_TIMEOUT, &OptWriteTimeout); + mysql_options(&m_Mysql, MYSQL_OPT_RECONNECT, &OptReconnect); + mysql_options(&m_Mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4"); + + if(!mysql_real_connect(&m_Mysql, m_aIp, m_aUser, m_aPass, nullptr, m_Port, nullptr, CLIENT_IGNORE_SIGPIPE)) { - dbg_msg("sql", "MySQL Error: %s", e.what()); + StoreErrorMysql("real_connect"); + return true; } - catch(const std::exception &ex) + m_HaveConnection = true; + + m_pStmt = std::unique_ptr(mysql_stmt_init(&m_Mysql)); + + // Apparently MYSQL_SET_CHARSET_NAME is not enough + if(PrepareAndExecuteStatement("SET CHARACTER SET utf8mb4")) { - dbg_msg("sql", "MySQL Error: %s", ex.what()); + return true; } - catch(const std::string &ex) + + if(m_Setup) { - dbg_msg("sql", "MySQL Error: %s", ex.c_str()); + char aCreateDatabase[1024]; + // create database + str_format(aCreateDatabase, sizeof(aCreateDatabase), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase); + if(PrepareAndExecuteStatement(aCreateDatabase)) + { + return true; + } } - catch(...) + + // Connect to specific database + if(mysql_select_db(&m_Mysql, m_aDatabase)) { - dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector"); + StoreErrorMysql("select_db"); + return true; } - m_InUse.store(false); -#endif - dbg_msg("sql", "FAILURE: sql connection failed"); - return Status::FAILURE; + if(m_Setup) + { + char aCreateRace[1024]; + char aCreateTeamrace[1024]; + char aCreateMaps[1024]; + char aCreateSaves[1024]; + char aCreatePoints[1024]; + FormatCreateRace(aCreateRace, sizeof(aCreateRace)); + FormatCreateTeamrace(aCreateTeamrace, sizeof(aCreateTeamrace), "VARBINARY(16)"); + FormatCreateMaps(aCreateMaps, sizeof(aCreateMaps)); + FormatCreateSaves(aCreateSaves, sizeof(aCreateSaves)); + FormatCreatePoints(aCreatePoints, sizeof(aCreatePoints)); + + if(PrepareAndExecuteStatement(aCreateRace) || + PrepareAndExecuteStatement(aCreateTeamrace) || + PrepareAndExecuteStatement(aCreateMaps) || + PrepareAndExecuteStatement(aCreateSaves) || + PrepareAndExecuteStatement(aCreatePoints)) + { + return true; + } + m_Setup = false; + } + dbg_msg("mysql", "connection established"); + return false; } void CMysqlConnection::Disconnect() @@ -183,120 +330,294 @@ m_InUse.store(false); } -void CMysqlConnection::PrepareStatement(const char *pStmt) +bool CMysqlConnection::PrepareStatement(const char *pStmt, char *pError, int ErrorSize) { -#if defined(CONF_SQL) - m_pPreparedStmt.reset(m_pConnection->prepareStatement(pStmt)); + if(mysql_stmt_prepare(m_pStmt.get(), pStmt, str_length(pStmt))) + { + StoreErrorStmt("prepare"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } m_NewQuery = true; -#endif + unsigned NumParameters = mysql_stmt_param_count(m_pStmt.get()); + m_aStmtParameters.resize(NumParameters); + m_aStmtParameterExtras.resize(NumParameters); + mem_zero(&m_aStmtParameters[0], sizeof(m_aStmtParameters[0]) * m_aStmtParameters.size()); + mem_zero(&m_aStmtParameterExtras[0], sizeof(m_aStmtParameterExtras[0]) * m_aStmtParameterExtras.size()); + return false; } void CMysqlConnection::BindString(int Idx, const char *pString) { -#if defined(CONF_SQL) - m_pPreparedStmt->setString(Idx, pString); m_NewQuery = true; -#endif + Idx -= 1; + dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds"); + + int Length = str_length(pString); + m_aStmtParameterExtras[Idx].ul = Length; + MYSQL_BIND *pParam = &m_aStmtParameters[Idx]; + pParam->buffer_type = MYSQL_TYPE_STRING; + pParam->buffer = (void *)pString; + pParam->buffer_length = Length + 1; + pParam->length = &m_aStmtParameterExtras[Idx].ul; + pParam->is_null = nullptr; + pParam->is_unsigned = false; + pParam->error = nullptr; } void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size) { -#if defined(CONF_SQL) - // copy blob into string - auto Blob = std::string(pBlob, pBlob + Size); - m_pPreparedStmt->setString(Idx, Blob); m_NewQuery = true; -#endif + Idx -= 1; + dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds"); + + m_aStmtParameterExtras[Idx].ul = Size; + MYSQL_BIND *pParam = &m_aStmtParameters[Idx]; + pParam->buffer_type = MYSQL_TYPE_BLOB; + pParam->buffer = pBlob; + pParam->buffer_length = Size; + pParam->length = &m_aStmtParameterExtras[Idx].ul; + pParam->is_null = nullptr; + pParam->is_unsigned = false; + pParam->error = nullptr; } void CMysqlConnection::BindInt(int Idx, int Value) { -#if defined(CONF_SQL) - m_pPreparedStmt->setInt(Idx, Value); m_NewQuery = true; -#endif + Idx -= 1; + dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds"); + + m_aStmtParameterExtras[Idx].i = Value; + MYSQL_BIND *pParam = &m_aStmtParameters[Idx]; + pParam->buffer_type = MYSQL_TYPE_LONG; + pParam->buffer = &m_aStmtParameterExtras[Idx].i; + pParam->buffer_length = sizeof(m_aStmtParameterExtras[Idx].i); + pParam->length = nullptr; + pParam->is_null = nullptr; + pParam->is_unsigned = false; + pParam->error = nullptr; } void CMysqlConnection::BindFloat(int Idx, float Value) { -#if defined(CONF_SQL) - m_pPreparedStmt->setDouble(Idx, (double)Value); m_NewQuery = true; -#endif + Idx -= 1; + dbg_assert(0 <= Idx && Idx < (int)m_aStmtParameters.size(), "index out of bounds"); + + m_aStmtParameterExtras[Idx].f = Value; + MYSQL_BIND *pParam = &m_aStmtParameters[Idx]; + pParam->buffer_type = MYSQL_TYPE_FLOAT; + pParam->buffer = &m_aStmtParameterExtras[Idx].f; + pParam->buffer_length = sizeof(m_aStmtParameterExtras[Idx].i); + pParam->length = nullptr; + pParam->is_null = nullptr; + pParam->is_unsigned = false; + pParam->error = nullptr; } -bool CMysqlConnection::Step() +bool CMysqlConnection::Step(bool *pEnd, char *pError, int ErrorSize) { -#if defined(CONF_SQL) if(m_NewQuery) { m_NewQuery = false; - m_pResults.reset(m_pPreparedStmt->executeQuery()); + if(mysql_stmt_bind_param(m_pStmt.get(), &m_aStmtParameters[0])) + { + StoreErrorStmt("bind_param"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } + if(mysql_stmt_execute(m_pStmt.get())) + { + StoreErrorStmt("execute"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } } - return m_pResults->next(); -#else + int Result = mysql_stmt_fetch(m_pStmt.get()); + if(Result == 1) + { + StoreErrorStmt("fetch"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } + *pEnd = (Result == MYSQL_NO_DATA); + // `Result` is now either `MYSQL_DATA_TRUNCATED` (which we ignore, we + // fetch our columns in a different way) or `0` aka success. return false; -#endif } -int CMysqlConnection::ExecuteUpdate() +bool CMysqlConnection::ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) { -#if defined(CONF_SQL) if(m_NewQuery) { m_NewQuery = false; - return m_pPreparedStmt->executeUpdate(); + if(mysql_stmt_bind_param(m_pStmt.get(), &m_aStmtParameters[0])) + { + StoreErrorStmt("bind_param"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } + if(mysql_stmt_execute(m_pStmt.get())) + { + StoreErrorStmt("execute"); + str_copy(pError, m_aErrorDetail, ErrorSize); + return true; + } + *pNumUpdated = mysql_stmt_affected_rows(m_pStmt.get()); + return false; } -#endif - return -1; + str_copy(pError, "tried to execute update without query", ErrorSize); + return true; } -bool CMysqlConnection::IsNull(int Col) const +bool CMysqlConnection::IsNull(int Col) { -#if defined(CONF_SQL) - return m_pResults->isNull(Col); -#else - return false; -#endif + Col -= 1; + + MYSQL_BIND Bind; + my_bool IsNull; + mem_zero(&Bind, sizeof(Bind)); + Bind.buffer_type = MYSQL_TYPE_NULL; + Bind.buffer = nullptr; + Bind.buffer_length = 0; + Bind.length = nullptr; + Bind.is_null = &IsNull; + Bind.is_unsigned = false; + Bind.error = nullptr; + if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0)) + { + StoreErrorStmt("fetch_column:null"); + dbg_msg("mysql", "error fetching column %s", m_aErrorDetail); + dbg_assert(0, "error in IsNull"); + } + return IsNull; } -float CMysqlConnection::GetFloat(int Col) const +float CMysqlConnection::GetFloat(int Col) { -#if defined(CONF_SQL) - return (float)m_pResults->getDouble(Col); -#else - return 0.0; -#endif + Col -= 1; + + MYSQL_BIND Bind; + float Value; + my_bool IsNull; + mem_zero(&Bind, sizeof(Bind)); + Bind.buffer_type = MYSQL_TYPE_FLOAT; + Bind.buffer = &Value; + Bind.buffer_length = sizeof(Value); + Bind.length = nullptr; + Bind.is_null = &IsNull; + Bind.is_unsigned = false; + Bind.error = nullptr; + if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0)) + { + StoreErrorStmt("fetch_column:float"); + dbg_msg("mysql", "error fetching column %s", m_aErrorDetail); + dbg_assert(0, "error in GetFloat"); + } + if(IsNull) + { + dbg_assert(0, "error getting float: NULL"); + } + return Value; } -int CMysqlConnection::GetInt(int Col) const +int CMysqlConnection::GetInt(int Col) { -#if defined(CONF_SQL) - return m_pResults->getInt(Col); -#else - return 0; -#endif + Col -= 1; + + MYSQL_BIND Bind; + int Value; + my_bool IsNull; + mem_zero(&Bind, sizeof(Bind)); + Bind.buffer_type = MYSQL_TYPE_LONG; + Bind.buffer = &Value; + Bind.buffer_length = sizeof(Value); + Bind.length = nullptr; + Bind.is_null = &IsNull; + Bind.is_unsigned = false; + Bind.error = nullptr; + if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0)) + { + StoreErrorStmt("fetch_column:int"); + dbg_msg("mysql", "error fetching column %s", m_aErrorDetail); + dbg_assert(0, "error in GetInt"); + } + if(IsNull) + { + dbg_assert(0, "error getting int: NULL"); + } + return Value; } -void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const +void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) { -#if defined(CONF_SQL) - auto String = m_pResults->getString(Col); - str_copy(pBuffer, String.c_str(), BufferSize); -#endif + Col -= 1; + + for(int i = 0; i < BufferSize; i++) + { + pBuffer[i] = 'a'; + } + + MYSQL_BIND Bind; + unsigned long Length; + my_bool IsNull; + my_bool Error; + mem_zero(&Bind, sizeof(Bind)); + Bind.buffer_type = MYSQL_TYPE_STRING; + Bind.buffer = pBuffer; + Bind.buffer_length = BufferSize; + Bind.length = &Length; + Bind.is_null = &IsNull; + Bind.is_unsigned = false; + Bind.error = &Error; + if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0)) + { + StoreErrorStmt("fetch_column:string"); + dbg_msg("mysql", "error fetching column %s", m_aErrorDetail); + dbg_assert(0, "error in GetString"); + } + if(IsNull) + { + dbg_assert(0, "error getting string: NULL"); + } + if(Error) + { + dbg_assert(0, "error getting string: truncation occured"); + } } -int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const +int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) { -#if defined(CONF_SQL) - auto *Blob = m_pResults->getBlob(Col); - Blob->read((char *)pBuffer, BufferSize); - int NumRead = Blob->gcount(); - delete Blob; - return NumRead; -#else - return 0; -#endif + Col -= 1; + + MYSQL_BIND Bind; + unsigned long Length; + my_bool IsNull; + my_bool Error; + mem_zero(&Bind, sizeof(Bind)); + Bind.buffer_type = MYSQL_TYPE_BLOB; + Bind.buffer = pBuffer; + Bind.buffer_length = BufferSize; + Bind.length = &Length; + Bind.is_null = &IsNull; + Bind.is_unsigned = false; + Bind.error = &Error; + if(mysql_stmt_fetch_column(m_pStmt.get(), &Bind, Col, 0)) + { + StoreErrorStmt("fetch_column:blob"); + dbg_msg("mysql", "error fetching column %s", m_aErrorDetail); + dbg_assert(0, "error in GetBlob"); + } + if(IsNull) + { + dbg_assert(0, "error getting blob: NULL"); + } + if(Error) + { + dbg_assert(0, "error getting blob: truncation occured"); + } + return Length; } const char *CMysqlConnection::MedianMapTime(char *pBuffer, int BufferSize) const @@ -311,17 +632,57 @@ return pBuffer; } -void CMysqlConnection::AddPoints(const char *pPlayer, int Points) +bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_points(Name, Points) " "VALUES (?, ?) " - "ON DUPLICATE KEY UPDATE Points=Points+?;", + "ON DUPLICATE KEY UPDATE Points=Points+?", GetPrefix()); - PrepareStatement(aBuf); + if(PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } BindString(1, pPlayer); BindInt(2, Points); BindInt(3, Points); - Step(); + int NumUpdated; + if(ExecuteUpdate(&NumUpdated, pError, ErrorSize)) + { + return true; + } + return false; } + +IDbConnection *CreateMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup) +{ + return new CMysqlConnection(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup); +} +#else +int MysqlInit() +{ + return 0; +} +void MysqlUninit() +{ +} +IDbConnection *CreateMysqlConnection( + const char *pDatabase, + const char *pPrefix, + const char *pUser, + const char *pPass, + const char *pIp, + int Port, + bool Setup) +{ + return nullptr; +} +#endif diff -Nru ddnet-15.3.2/src/engine/server/databases/mysql.h ddnet-15.5.4/src/engine/server/databases/mysql.h --- ddnet-15.3.2/src/engine/server/databases/mysql.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/mysql.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -#ifndef ENGINE_SERVER_DATABASES_MYSQL_H -#define ENGINE_SERVER_DATABASES_MYSQL_H - -#include -#include -#include - -class CLock; -namespace sql { -class Connection; -class PreparedStatement; -class ResultSet; -class Statement; -} /* namespace sql */ - -class CMysqlConnection : public IDbConnection -{ -public: - CMysqlConnection( - const char *pDatabase, - const char *pPrefix, - const char *pUser, - const char *pPass, - const char *pIp, - int Port, - bool Setup); - virtual ~CMysqlConnection(); - virtual void Print(IConsole *pConsole, const char *Mode); - - virtual CMysqlConnection *Copy(); - - virtual const char *BinaryCollate() const { return "utf8mb4_bin"; } - virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); - virtual const char *InsertTimestampAsUtc() const { return "?"; } - virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; } - virtual const char *InsertIgnore() const { return "INSERT IGNORE"; }; - virtual const char *Random() const { return "RAND()"; }; - virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; - - virtual Status Connect(); - virtual void Disconnect(); - - virtual void PrepareStatement(const char *pStmt); - - virtual void BindString(int Idx, const char *pString); - virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); - virtual void BindInt(int Idx, int Value); - virtual void BindFloat(int Idx, float Value); - - virtual void Print() {} - virtual bool Step(); - virtual int ExecuteUpdate(); - - virtual bool IsNull(int Col) const; - virtual float GetFloat(int Col) const; - virtual int GetInt(int Col) const; - virtual void GetString(int Col, char *pBuffer, int BufferSize) const; - virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; - - virtual void AddPoints(const char *pPlayer, int Points); - -private: -#if defined(CONF_SQL) - std::unique_ptr m_pConnection; - std::unique_ptr m_pPreparedStmt; - std::unique_ptr m_pStmt; - std::unique_ptr m_pResults; - bool m_NewQuery; -#endif - - // copy of config vars - char m_aDatabase[64]; - char m_aUser[64]; - char m_aPass[64]; - char m_aIp[64]; - int m_Port; - bool m_Setup; - - std::atomic_bool m_InUse; - static CLock m_SqlDriverLock; -}; - -#endif // ENGINE_SERVER_DATABASES_MYSQL_H diff -Nru ddnet-15.3.2/src/engine/server/databases/sqlite.cpp ddnet-15.5.4/src/engine/server/databases/sqlite.cpp --- ddnet-15.3.2/src/engine/server/databases/sqlite.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/sqlite.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,10 +1,69 @@ -#include "sqlite.h" +#include "connection.h" + +#include #include #include -#include -#include +#include + +class CSqliteConnection : public IDbConnection +{ +public: + CSqliteConnection(const char *pFilename, bool Setup); + virtual ~CSqliteConnection(); + virtual void Print(IConsole *pConsole, const char *Mode); + + virtual CSqliteConnection *Copy(); + + virtual const char *BinaryCollate() const { return "BINARY"; } + virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); + virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; } + virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; } + virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; }; + virtual const char *Random() const { return "RANDOM()"; }; + virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; + + virtual bool Connect(char *pError, int ErrorSize); + virtual void Disconnect(); + + virtual bool PrepareStatement(const char *pStmt, char *pError, int ErrorSize); + + virtual void BindString(int Idx, const char *pString); + virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); + virtual void BindInt(int Idx, int Value); + virtual void BindFloat(int Idx, float Value); + + virtual void Print(); + virtual bool Step(bool *pEnd, char *pError, int ErrorSize); + virtual bool ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize); + + virtual bool IsNull(int Col); + virtual float GetFloat(int Col); + virtual int GetInt(int Col); + virtual void GetString(int Col, char *pBuffer, int BufferSize); + // passing a negative buffer size is undefined behavior + virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize); + + virtual bool AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize); + +private: + // copy of config vars + char m_aFilename[512]; + bool m_Setup; + + sqlite3 *m_pDb; + sqlite3_stmt *m_pStmt; + bool m_Done; // no more rows available for Step + // returns false, if the query succeeded + bool Execute(const char *pQuery, char *pError, int ErrorSize); + + // returns true if an error was formatted + bool FormatError(int Result, char *pError, int ErrorSize); + void AssertNoError(int Result); + + std::atomic_bool m_InUse; +}; CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) : IDbConnection("record"), @@ -44,19 +103,23 @@ return new CSqliteConnection(m_aFilename, m_Setup); } -IDbConnection::Status CSqliteConnection::Connect() +bool CSqliteConnection::Connect(char *pError, int ErrorSize) { if(m_InUse.exchange(true)) - return Status::IN_USE; + { + dbg_assert(0, "Tried connecting while the connection is in use"); + } if(m_pDb != nullptr) - return Status::SUCCESS; + { + return false; + } int Result = sqlite3_open(m_aFilename, &m_pDb); if(Result != SQLITE_OK) { - dbg_msg("sql", "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb)); - return Status::FAILURE; + str_format(pError, ErrorSize, "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb)); + return true; } // wait for database to unlock so we don't have to handle SQLITE_BUSY errors @@ -66,23 +129,23 @@ { char aBuf[1024]; FormatCreateRace(aBuf, sizeof(aBuf)); - if(!Execute(aBuf)) - return Status::FAILURE; + if(Execute(aBuf, pError, ErrorSize)) + return true; FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB"); - if(!Execute(aBuf)) - return Status::FAILURE; + if(Execute(aBuf, pError, ErrorSize)) + return true; FormatCreateMaps(aBuf, sizeof(aBuf)); - if(!Execute(aBuf)) - return Status::FAILURE; + if(Execute(aBuf, pError, ErrorSize)) + return true; FormatCreateSaves(aBuf, sizeof(aBuf)); - if(!Execute(aBuf)) - return Status::FAILURE; + if(Execute(aBuf, pError, ErrorSize)) + return true; FormatCreatePoints(aBuf, sizeof(aBuf)); - if(!Execute(aBuf)) - return Status::FAILURE; + if(Execute(aBuf, pError, ErrorSize)) + return true; m_Setup = false; } - return Status::SUCCESS; + return false; } void CSqliteConnection::Disconnect() @@ -93,7 +156,7 @@ m_InUse.store(false); } -void CSqliteConnection::PrepareStatement(const char *pStmt) +bool CSqliteConnection::PrepareStatement(const char *pStmt, char *pError, int ErrorSize) { if(m_pStmt != nullptr) sqlite3_finalize(m_pStmt); @@ -104,35 +167,39 @@ -1, // pStmt can be any length &m_pStmt, NULL); - ExceptionOnError(Result); + if(FormatError(Result, pError, ErrorSize)) + { + return true; + } m_Done = false; + return false; } void CSqliteConnection::BindString(int Idx, const char *pString) { int Result = sqlite3_bind_text(m_pStmt, Idx, pString, -1, NULL); - ExceptionOnError(Result); + AssertNoError(Result); m_Done = false; } void CSqliteConnection::BindBlob(int Idx, unsigned char *pBlob, int Size) { int Result = sqlite3_bind_blob(m_pStmt, Idx, pBlob, Size, NULL); - ExceptionOnError(Result); + AssertNoError(Result); m_Done = false; } void CSqliteConnection::BindInt(int Idx, int Value) { int Result = sqlite3_bind_int(m_pStmt, Idx, Value); - ExceptionOnError(Result); + AssertNoError(Result); m_Done = false; } void CSqliteConnection::BindFloat(int Idx, float Value) { int Result = sqlite3_bind_double(m_pStmt, Idx, (double)Value); - ExceptionOnError(Result); + AssertNoError(Result); m_Done = false; } @@ -151,54 +218,68 @@ } } -bool CSqliteConnection::Step() +bool CSqliteConnection::Step(bool *pEnd, char *pError, int ErrorSize) { if(m_Done) + { + *pEnd = true; return false; + } int Result = sqlite3_step(m_pStmt); if(Result == SQLITE_ROW) { - return true; + *pEnd = false; + return false; } else if(Result == SQLITE_DONE) { m_Done = true; + *pEnd = true; return false; } else { - ExceptionOnError(Result); + if(FormatError(Result, pError, ErrorSize)) + { + return true; + } } + *pEnd = true; return false; } -int CSqliteConnection::ExecuteUpdate() +bool CSqliteConnection::ExecuteUpdate(int *pNumUpdated, char *pError, int ErrorSize) { - Step(); - return sqlite3_changes(m_pDb); + bool End; + if(Step(&End, pError, ErrorSize)) + { + return true; + } + *pNumUpdated = sqlite3_changes(m_pDb); + return false; } -bool CSqliteConnection::IsNull(int Col) const +bool CSqliteConnection::IsNull(int Col) { return sqlite3_column_type(m_pStmt, Col - 1) == SQLITE_NULL; } -float CSqliteConnection::GetFloat(int Col) const +float CSqliteConnection::GetFloat(int Col) { return (float)sqlite3_column_double(m_pStmt, Col - 1); } -int CSqliteConnection::GetInt(int Col) const +int CSqliteConnection::GetInt(int Col) { return sqlite3_column_int(m_pStmt, Col - 1); } -void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) const +void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) { str_copy(pBuffer, (const char *)sqlite3_column_text(m_pStmt, Col - 1), BufferSize); } -int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const +int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) { int Size = sqlite3_column_bytes(m_pStmt, Col - 1); Size = minimum(Size, BufferSize); @@ -223,27 +304,40 @@ return pBuffer; } -bool CSqliteConnection::Execute(const char *pQuery) +bool CSqliteConnection::Execute(const char *pQuery, char *pError, int ErrorSize) { char *pErrorMsg; int Result = sqlite3_exec(m_pDb, pQuery, NULL, NULL, &pErrorMsg); if(Result != SQLITE_OK) { - dbg_msg("sql", "error executing query: '%s'", pErrorMsg); + str_format(pError, ErrorSize, "error executing query: '%s'", pErrorMsg); sqlite3_free(pErrorMsg); + return true; } - return Result == SQLITE_OK; + return false; } -void CSqliteConnection::ExceptionOnError(int Result) +bool CSqliteConnection::FormatError(int Result, char *pError, int ErrorSize) { if(Result != SQLITE_OK) { - throw std::runtime_error(sqlite3_errmsg(m_pDb)); + str_copy(pError, sqlite3_errmsg(m_pDb), ErrorSize); + return true; + } + return false; +} + +void CSqliteConnection::AssertNoError(int Result) +{ + char aBuf[128]; + if(FormatError(Result, aBuf, sizeof(aBuf))) + { + dbg_msg("sqlite", "unexpected sqlite error: %s", aBuf); + dbg_assert(0, "sqlite error"); } } -void CSqliteConnection::AddPoints(const char *pPlayer, int Points) +bool CSqliteConnection::AddPoints(const char *pPlayer, int Points, char *pError, int ErrorSize) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), @@ -251,9 +345,22 @@ "VALUES (?, ?) " "ON CONFLICT(Name) DO UPDATE SET Points=Points+?;", GetPrefix()); - PrepareStatement(aBuf); + if(PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } BindString(1, pPlayer); BindInt(2, Points); BindInt(3, Points); - Step(); + bool End; + if(Step(&End, pError, ErrorSize)) + { + return true; + } + return false; +} + +IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup) +{ + return new CSqliteConnection(pFilename, Setup); } diff -Nru ddnet-15.3.2/src/engine/server/databases/sqlite.h ddnet-15.5.4/src/engine/server/databases/sqlite.h --- ddnet-15.3.2/src/engine/server/databases/sqlite.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/databases/sqlite.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,66 +0,0 @@ -#ifndef ENGINE_SERVER_DATABASES_SQLITE_H -#define ENGINE_SERVER_DATABASES_SQLITE_H - -#include "connection.h" -#include - -struct sqlite3; -struct sqlite3_stmt; - -class CSqliteConnection : public IDbConnection -{ -public: - CSqliteConnection(const char *pFilename, bool Setup); - virtual ~CSqliteConnection(); - virtual void Print(IConsole *pConsole, const char *Mode); - - virtual CSqliteConnection *Copy(); - - virtual const char *BinaryCollate() const { return "BINARY"; } - virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize); - virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; } - virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; } - virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; }; - virtual const char *Random() const { return "RANDOM()"; }; - virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const; - - virtual Status Connect(); - virtual void Disconnect(); - - virtual void PrepareStatement(const char *pStmt); - - virtual void BindString(int Idx, const char *pString); - virtual void BindBlob(int Idx, unsigned char *pBlob, int Size); - virtual void BindInt(int Idx, int Value); - virtual void BindFloat(int Idx, float Value); - - virtual void Print(); - virtual bool Step(); - virtual int ExecuteUpdate(); - - virtual bool IsNull(int Col) const; - virtual float GetFloat(int Col) const; - virtual int GetInt(int Col) const; - virtual void GetString(int Col, char *pBuffer, int BufferSize) const; - // passing a negative buffer size is undefined behavior - virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const; - - virtual void AddPoints(const char *pPlayer, int Points); - -private: - // copy of config vars - char m_aFilename[512]; - bool m_Setup; - - sqlite3 *m_pDb; - sqlite3_stmt *m_pStmt; - bool m_Done; // no more rows available for Step - // returns true, if the query succeeded - bool Execute(const char *pQuery); - - void ExceptionOnError(int Result); - - std::atomic_bool m_InUse; -}; - -#endif // ENGINE_SERVER_DATABASES_SQLITE_H diff -Nru ddnet-15.3.2/src/engine/server/server.cpp ddnet-15.5.4/src/engine/server/server.cpp --- ddnet-15.3.2/src/engine/server/server.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/server.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -3,6 +3,8 @@ #define _WIN32_WINNT 0x0501 +#include "server.h" + #include #include @@ -32,22 +34,18 @@ // DDRace #include -#include #include #include +#include "databases/connection.h" +#include "databases/connection_pool.h" #include "register.h" -#include "server.h" #if defined(CONF_FAMILY_WINDOWS) #define WIN32_LEAN_AND_MEAN #include #endif -#include -#include -#include - CSnapIDPool::CSnapIDPool() { Reset(); @@ -229,7 +227,7 @@ CServerBan *pThis = static_cast(pUser); const char *pStr = pResult->GetString(0); - int Minutes = pResult->NumArguments() > 1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30; + int Minutes = pResult->NumArguments() > 1 ? clamp(pResult->GetInteger(1), 0, 525600) : 30; const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(2) : "No reason given"; if(str_isallnum(pStr)) @@ -540,12 +538,12 @@ m_RconClientID = ClientID; } -int CServer::GetAuthedState(int ClientID) +int CServer::GetAuthedState(int ClientID) const { return m_aClients[ClientID].m_Authed; } -const char *CServer::GetAuthName(int ClientID) +const char *CServer::GetAuthName(int ClientID) const { int Key = m_aClients[ClientID].m_AuthKey; if(Key == -1) @@ -555,7 +553,7 @@ return m_AuthManager.KeyIdent(Key); } -int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) +int CServer::GetClientInfo(int ClientID, CClientInfo *pInfo) const { dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid"); dbg_assert(pInfo != 0, "info can not be null"); @@ -592,13 +590,13 @@ } } -void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size) +void CServer::GetClientAddr(int ClientID, char *pAddrStr, int Size) const { if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) net_addr_str(m_NetServer.ClientAddr(ClientID), pAddrStr, Size, false); } -const char *CServer::ClientName(int ClientID) +const char *CServer::ClientName(int ClientID) const { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return "(invalid)"; @@ -608,7 +606,7 @@ return "(connecting)"; } -const char *CServer::ClientClan(int ClientID) +const char *CServer::ClientClan(int ClientID) const { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return ""; @@ -618,7 +616,7 @@ return ""; } -int CServer::ClientCountry(int ClientID) +int CServer::ClientCountry(int ClientID) const { if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State == CServer::CClient::STATE_EMPTY) return -1; @@ -628,12 +626,12 @@ return -1; } -bool CServer::ClientIngame(int ClientID) +bool CServer::ClientIngame(int ClientID) const { return ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CServer::CClient::STATE_INGAME; } -bool CServer::ClientAuthed(int ClientID) +bool CServer::ClientAuthed(int ClientID) const { return ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_Authed; } @@ -648,10 +646,10 @@ return m_NetServer.MaxClients(); } -int CServer::ClientCount() +int CServer::ClientCount() const { int ClientCount = 0; - for(auto &Client : m_aClients) + for(const auto &Client : m_aClients) { if(Client.m_State != CClient::STATE_EMPTY) { @@ -662,7 +660,7 @@ return ClientCount; } -int CServer::DistinctClientCount() +int CServer::DistinctClientCount() const { NETADDR aAddresses[MAX_CLIENTS]; for(int i = 0; i < MAX_CLIENTS; i++) @@ -697,43 +695,46 @@ { int MsgId = pMsg->m_MsgID; Packer.Reset(); - if(MsgId < OFFSET_UUID) + + if(Sixup && !pMsg->m_NoTranslate) { - if(Sixup && !pMsg->m_NoTranslate) + if(pMsg->m_System) { - if(pMsg->m_System) - { - if(MsgId >= NETMSG_MAP_CHANGE && MsgId <= NETMSG_MAP_DATA) - ; - else if(MsgId >= NETMSG_CON_READY && MsgId <= NETMSG_INPUTTIMING) - MsgId += 1; - else if(MsgId == NETMSG_RCON_LINE) - MsgId = 13; - else if(MsgId >= NETMSG_AUTH_CHALLANGE && MsgId <= NETMSG_AUTH_RESULT) - MsgId += 4; - else if(MsgId >= NETMSG_PING && MsgId <= NETMSG_ERROR) - MsgId += 4; - else if(MsgId >= NETMSG_RCON_CMD_ADD && MsgId <= NETMSG_RCON_CMD_REM) - MsgId -= 11; - else - { - dbg_msg("net", "DROP send sys %d", MsgId); - return true; - } - } + if(MsgId >= OFFSET_UUID) + ; + else if(MsgId >= NETMSG_MAP_CHANGE && MsgId <= NETMSG_MAP_DATA) + ; + else if(MsgId >= NETMSG_CON_READY && MsgId <= NETMSG_INPUTTIMING) + MsgId += 1; + else if(MsgId == NETMSG_RCON_LINE) + MsgId = 13; + else if(MsgId >= NETMSG_AUTH_CHALLANGE && MsgId <= NETMSG_AUTH_RESULT) + MsgId += 4; + else if(MsgId >= NETMSG_PING && MsgId <= NETMSG_ERROR) + MsgId += 4; + else if(MsgId >= NETMSG_RCON_CMD_ADD && MsgId <= NETMSG_RCON_CMD_REM) + MsgId -= 11; else { - if(MsgId >= 0) - MsgId = Msg_SixToSeven(MsgId); - - if(MsgId < 0) - return true; + dbg_msg("net", "DROP send sys %d", MsgId); + return true; } } + else + { + if(MsgId >= 0 && MsgId < OFFSET_UUID) + MsgId = Msg_SixToSeven(MsgId); + + if(MsgId < 0) + return true; + } + } + if(MsgId < OFFSET_UUID) + { Packer.AddInt((MsgId << 1) | (pMsg->m_System ? 1 : 0)); } - else if(!Sixup) + else { Packer.AddInt((0 << 1) | (pMsg->m_System ? 1 : 0)); // NETMSG_EX, NETMSGTYPE_EX g_UuidManager.PackUuid(MsgId, &Packer); @@ -839,12 +840,8 @@ GameServer()->OnSnap(-1); SnapshotSize = m_SnapshotBuilder.Finish(aData); - // for antiping: if the projectile netobjects contains extra data, this is removed and the original content restored before recording demo - unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; - mem_copy(aExtraInfoRemoved, aData, SnapshotSize); - SnapshotRemoveExtraInfo(aExtraInfoRemoved); // write snapshot - m_aDemoRecorder[MAX_CLIENTS].RecordSnapshot(Tick(), aExtraInfoRemoved, SnapshotSize); + m_aDemoRecorder[MAX_CLIENTS].RecordSnapshot(Tick(), aData, SnapshotSize); } // create snapshots for all clients @@ -884,12 +881,8 @@ if(m_aDemoRecorder[i].IsRecording()) { - // for antiping: if the projectile netobjects contains extra data, this is removed and the original content restored before recording demo - unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; - mem_copy(aExtraInfoRemoved, aData, SnapshotSize); - SnapshotRemoveExtraInfo(aExtraInfoRemoved); // write snapshot - m_aDemoRecorder[i].RecordSnapshot(Tick(), aExtraInfoRemoved, SnapshotSize); + m_aDemoRecorder[i].RecordSnapshot(Tick(), aData, SnapshotSize); } Crc = pData->Crc(); @@ -1145,7 +1138,7 @@ { CMsgPacker Msg(NETMSG_CAPABILITIES, true); Msg.AddInt(SERVERCAP_CURVERSION); // version - Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE); // flags + Msg.AddInt(SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX); // flags SendMsg(&Msg, MSGFLAG_VITAL, ClientID); } @@ -1226,7 +1219,7 @@ SendMsg(&Msg, MSGFLAG_VITAL, ClientID); } -void CServer::SendRconLineAuthed(const char *pLine, void *pUser, bool Highlighted) +void CServer::SendRconLineAuthed(const char *pLine, void *pUser, ColorRGBA PrintColor) { CServer *pThis = (CServer *)pUser; static volatile int s_ReentryGuard = 0; @@ -1312,7 +1305,7 @@ Msg += 11; else if(Msg >= 18 && Msg <= 28) Msg = NETMSG_READY + Msg - 18; - else + else if(Msg < OFFSET_UUID) return -1; } @@ -1471,7 +1464,7 @@ } else if(Msg == NETMSG_READY) { - if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_CONNECTING) + if((pPacket->m_Flags & NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_CONNECTING)) { char aAddrStr[NETADDR_MAXSTRSIZE]; net_addr_str(m_NetServer.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true); @@ -1479,8 +1472,15 @@ char aBuf[256]; str_format(aBuf, sizeof(aBuf), "player is ready. ClientID=%d addr=<{%s}> secure=%s", ClientID, aAddrStr, m_NetServer.HasSecurityToken(ClientID) ? "yes" : "no"); Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "server", aBuf); + + void *pPersistentData = 0; + if(m_aClients[ClientID].m_HasPersistentData) + { + pPersistentData = m_aClients[ClientID].m_pPersistentData; + m_aClients[ClientID].m_HasPersistentData = false; + } m_aClients[ClientID].m_State = CClient::STATE_READY; - GameServer()->OnClientConnected(ClientID); + GameServer()->OnClientConnected(ClientID, pPersistentData); } SendConnectionReady(ClientID); @@ -1699,6 +1699,17 @@ CMsgPacker Msg(NETMSG_PING_REPLY, true); SendMsg(&Msg, 0, ClientID); } + else if(Msg == NETMSG_PINGEX) + { + CUuid *pID = (CUuid *)Unpacker.GetRaw(sizeof(*pID)); + if(Unpacker.Error()) + { + return; + } + CMsgPacker Msg(NETMSG_PONGEX, true); + Msg.AddRaw(pID, sizeof(*pID)); + SendMsg(&Msg, MSGFLAG_FLUSH, ClientID); + } else { if(g_Config.m_Debug) @@ -1855,6 +1866,9 @@ ADD_INT(p, g_Config.m_Password[0] ? SERVER_FLAG_PASSWORD : 0); int MaxClients = m_NetServer.MaxClients(); + // How many clients the used serverinfo protocol supports, has to be tracked + // separately to make sure we don't subtract the reserved slots from it + int MaxClientsProtocol = MAX_CLIENTS; if(Type == SERVERINFO_VANILLA || Type == SERVERINFO_INGAME) { if(ClientCount >= VANILLA_MAX_CLIENTS) @@ -1864,16 +1878,15 @@ else ClientCount = VANILLA_MAX_CLIENTS; } - if(MaxClients > VANILLA_MAX_CLIENTS) - MaxClients = VANILLA_MAX_CLIENTS; + MaxClientsProtocol = VANILLA_MAX_CLIENTS; if(PlayerCount > ClientCount) PlayerCount = ClientCount; } ADD_INT(p, PlayerCount); // num players - ADD_INT(p, maximum(MaxClients - maximum(g_Config.m_SvSpectatorSlots, g_Config.m_SvReservedSlots), PlayerCount)); // max players + ADD_INT(p, minimum(MaxClientsProtocol, maximum(MaxClients - maximum(g_Config.m_SvSpectatorSlots, g_Config.m_SvReservedSlots), PlayerCount))); // max players ADD_INT(p, ClientCount); // num clients - ADD_INT(p, maximum(MaxClients - g_Config.m_SvReservedSlots, ClientCount)); // max clients + ADD_INT(p, minimum(MaxClientsProtocol, maximum(MaxClients - g_Config.m_SvReservedSlots, ClientCount))); // max clients if(Type == SERVERINFO_EXTENDED) p.AddString("", 0); // extra info, reserved @@ -2240,7 +2253,7 @@ m_Econ.Update(); } -char *CServer::GetMapName() +char *CServer::GetMapName() const { // get the name of the map without his path char *pMapShortName = &g_Config.m_SvMap[0]; @@ -2355,6 +2368,15 @@ m_PrintCBIndex = Console()->RegisterPrintCallback(g_Config.m_ConsoleOutputLevel, SendRconLineAuthed, this); + { + int Size = GameServer()->PersistentClientDataSize(); + for(auto &Client : m_aClients) + { + Client.m_HasPersistentData = false; + Client.m_pPersistentData = malloc(Size); + } + } + // load map if(!LoadMap(g_Config.m_SvMap)) { @@ -2364,7 +2386,7 @@ if(g_Config.m_SvSqliteFile[0] != '\0') { - auto pSqlServers = std::unique_ptr(new CSqliteConnection( + auto pSqlServers = std::unique_ptr(CreateSqliteConnection( g_Config.m_SvSqliteFile, true)); if(g_Config.m_SvUseSQL) @@ -2373,7 +2395,7 @@ } else { - auto pCopy = std::unique_ptr(pSqlServers->Copy()); + auto pCopy = std::unique_ptr(pSqlServers->Copy()); DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ); DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE); } @@ -2464,6 +2486,16 @@ if(LoadMap(g_Config.m_SvMap)) { // new map loaded + + // ask the game to for the data it wants to persist past a map change + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_aClients[i].m_State == CClient::STATE_INGAME) + { + m_aClients[i].m_HasPersistentData = GameServer()->OnClientDataPersist(i, m_aClients[i].m_pPersistentData); + } + } + GameServer()->OnShutdown(); for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) @@ -2472,7 +2504,9 @@ continue; SendMap(ClientID); + bool HasPersistentData = m_aClients[ClientID].m_HasPersistentData; m_aClients[ClientID].Reset(); + m_aClients[ClientID].m_HasPersistentData = HasPersistentData; m_aClients[ClientID].m_State = CClient::STATE_CONNECTING; } @@ -2663,6 +2697,11 @@ m_UPnP.Shutdown(); #endif + for(auto &Client : m_aClients) + { + free(Client.m_pPersistentData); + } + return ErrorShutdown(); } @@ -3172,14 +3211,20 @@ bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true; - auto pSqlServers = std::unique_ptr(new CMysqlConnection( + auto pSqlServers = std::unique_ptr(CreateMysqlConnection( pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), SetUpDb)); + if(!pSqlServers) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support"); + return; + } + char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d", + "Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d", ReadOnly ? "Read" : "Write", pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(5), pResult->GetInteger(6)); @@ -3489,6 +3534,11 @@ dbg_msg("secure", "could not initialize secure RNG"); return -1; } + if(MysqlInit() != 0) + { + dbg_msg("mysql", "failed to initialize MySQL library"); + return -1; + } CServer *pServer = CreateServer(); IKernel *pKernel = IKernel::Create(); @@ -3562,6 +3612,8 @@ dbg_msg("server", "starting..."); int Ret = pServer->Run(); + MysqlUninit(); + // free delete pKernel; @@ -3570,7 +3622,7 @@ // DDRace -void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) +void CServer::GetClientAddr(int ClientID, NETADDR *pAddr) const { if(ClientID >= 0 && ClientID < MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_INGAME) { diff -Nru ddnet-15.3.2/src/engine/server/server.h ddnet-15.5.4/src/engine/server/server.h --- ddnet-15.3.2/src/engine/server/server.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server/server.h 2021-06-20 09:38:48.000000000 +0000 @@ -178,6 +178,9 @@ const IConsole::CCommandInfo *m_pRconCmdToSend; + bool m_HasPersistentData; + void *m_pPersistentData; + void Reset(); // DDRace @@ -284,21 +287,21 @@ int Init(); void SetRconCID(int ClientID); - int GetAuthedState(int ClientID); - const char *GetAuthName(int ClientID); + int GetAuthedState(int ClientID) const; + const char *GetAuthName(int ClientID) const; void GetMapInfo(char *pMapName, int MapNameSize, int *pMapSize, SHA256_DIGEST *pMapSha256, int *pMapCrc); - int GetClientInfo(int ClientID, CClientInfo *pInfo); + int GetClientInfo(int ClientID, CClientInfo *pInfo) const; void SetClientDDNetVersion(int ClientID, int DDNetVersion); - void GetClientAddr(int ClientID, char *pAddrStr, int Size); - const char *ClientName(int ClientID); - const char *ClientClan(int ClientID); - int ClientCountry(int ClientID); - bool ClientIngame(int ClientID); - bool ClientAuthed(int ClientID); + void GetClientAddr(int ClientID, char *pAddrStr, int Size) const; + const char *ClientName(int ClientID) const; + const char *ClientClan(int ClientID) const; + int ClientCountry(int ClientID) const; + bool ClientIngame(int ClientID) const; + bool ClientAuthed(int ClientID) const; int Port() const; int MaxClients() const; - int ClientCount(); - int DistinctClientCount(); + int ClientCount() const; + int DistinctClientCount() const; virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID); @@ -316,7 +319,7 @@ void SendMapData(int ClientID, int Chunk); void SendConnectionReady(int ClientID); void SendRconLine(int ClientID, const char *pLine); - static void SendRconLineAuthed(const char *pLine, void *pUser, bool Highlighted = false); + static void SendRconLineAuthed(const char *pLine, void *pUser, ColorRGBA PrintColor = {1, 1, 1, 1}); void SendRconCmdAdd(const IConsole::CCommandInfo *pCommandInfo, int ClientID); void SendRconCmdRem(const IConsole::CCommandInfo *pCommandInfo, int ClientID); @@ -360,7 +363,7 @@ void PumpNetwork(bool PacketWaiting); - char *GetMapName(); + char *GetMapName() const; int LoadMap(const char *pMapName); void SaveDemo(int ClientID, float Time); @@ -423,7 +426,7 @@ // DDRace - void GetClientAddr(int ClientID, NETADDR *pAddr); + void GetClientAddr(int ClientID, NETADDR *pAddr) const; int m_aPrevStates[MAX_CLIENTS]; const char *GetAnnouncementLine(char const *pFileName); unsigned m_AnnouncementLastLine; diff -Nru ddnet-15.3.2/src/engine/serverbrowser.h ddnet-15.5.4/src/engine/serverbrowser.h --- ddnet-15.3.2/src/engine/serverbrowser.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/serverbrowser.h 2021-06-20 09:38:48.000000000 +0000 @@ -11,15 +11,26 @@ #define DDNET_INFO "ddnet-info.json" -/* - Structure: CServerInfo -*/ class CServerInfo { public: - /* - Structure: CInfoClient - */ + enum + { + LOC_UNKNOWN = 0, + LOC_AFRICA, + LOC_ASIA, + LOC_AUSTRALIA, + LOC_EUROPE, + LOC_NORTH_AMERICA, + LOC_SOUTH_AMERICA, + // Special case China because it has an exceptionally bad + // connection to the outside due to the Great Firewall of + // China: + // https://en.wikipedia.org/w/index.php?title=Great_Firewall&oldid=1019589632 + LOC_CHINA, + NUM_LOCS, + }; + class CClient { public: @@ -50,6 +61,8 @@ int m_Flags; bool m_Favorite; bool m_Official; + int m_Location; + bool m_LatencyIsEstimated; int m_Latency; // in ms int m_HasRank; char m_aGameType[16]; @@ -63,6 +76,9 @@ mutable int m_NumFilteredPlayers; mutable CUIElement *m_pUIElement; + + static int EstimateLatency(int Loc1, int Loc2); + static bool ParseLocation(int *pResult, const char *pString); }; bool IsVanilla(const CServerInfo *pInfo); @@ -114,6 +130,7 @@ SET_DDNET_ADD, SET_KOG_ADD, SET_TOKEN, + SET_HTTPINFO, NETWORK_DDNET = 0, NETWORK_KOG = 1, @@ -121,8 +138,8 @@ }; virtual void Refresh(int Type) = 0; + virtual bool IsGettingServerlist() const = 0; virtual bool IsRefreshing() const = 0; - virtual bool IsRefreshingMasters() const = 0; virtual int LoadingProgression() const = 0; virtual int NumServers() const = 0; @@ -133,8 +150,11 @@ virtual int NumSortedServers() const = 0; virtual const CServerInfo *SortedGet(int Index) const = 0; + virtual bool GotInfo(const NETADDR &Addr) const = 0; virtual bool IsFavorite(const NETADDR &Addr) const = 0; + virtual bool IsFavoritePingAllowed(const NETADDR &Addr) const = 0; virtual void AddFavorite(const NETADDR &Addr) = 0; + virtual void FavoriteAllowPing(const NETADDR &Addr, bool AllowPing) = 0; virtual void RemoveFavorite(const NETADDR &Addr) = 0; virtual int NumCountries(int Network) = 0; diff -Nru ddnet-15.3.2/src/engine/server.h ddnet-15.5.4/src/engine/server.h --- ddnet-15.3.2/src/engine/server.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/server.h 2021-06-20 09:38:48.000000000 +0000 @@ -43,16 +43,16 @@ virtual int Port() const = 0; virtual int MaxClients() const = 0; - virtual int ClientCount() = 0; - virtual int DistinctClientCount() = 0; - virtual const char *ClientName(int ClientID) = 0; - virtual const char *ClientClan(int ClientID) = 0; - virtual int ClientCountry(int ClientID) = 0; - virtual bool ClientIngame(int ClientID) = 0; - virtual bool ClientAuthed(int ClientID) = 0; - virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) = 0; + virtual int ClientCount() const = 0; + virtual int DistinctClientCount() const = 0; + virtual const char *ClientName(int ClientID) const = 0; + virtual const char *ClientClan(int ClientID) const = 0; + virtual int ClientCountry(int ClientID) const = 0; + virtual bool ClientIngame(int ClientID) const = 0; + virtual bool ClientAuthed(int ClientID) const = 0; + virtual int GetClientInfo(int ClientID, CClientInfo *pInfo) const = 0; virtual void SetClientDDNetVersion(int ClientID, int DDNetVersion) = 0; - virtual void GetClientAddr(int ClientID, char *pAddrStr, int Size) = 0; + virtual void GetClientAddr(int ClientID, char *pAddrStr, int Size) const = 0; virtual void RestrictRconOutput(int ClientID) = 0; virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientID) = 0; @@ -209,8 +209,8 @@ RCON_CID_VOTE = -2, }; virtual void SetRconCID(int ClientID) = 0; - virtual int GetAuthedState(int ClientID) = 0; - virtual const char *GetAuthName(int ClientID) = 0; + virtual int GetAuthedState(int ClientID) const = 0; + virtual const char *GetAuthName(int ClientID) const = 0; virtual void Kick(int ClientID, const char *pReason) = 0; virtual void Ban(int ClientID, int Seconds, const char *pReason) = 0; @@ -224,7 +224,7 @@ virtual void StopRecord(int ClientID) = 0; virtual bool IsRecording(int ClientID) = 0; - virtual void GetClientAddr(int ClientID, NETADDR *pAddr) = 0; + virtual void GetClientAddr(int ClientID, NETADDR *pAddr) const = 0; virtual int *GetIdMap(int ClientID) = 0; @@ -243,7 +243,7 @@ virtual void SendMsgRaw(int ClientID, const void *pData, int Size, int Flags) = 0; - virtual char *GetMapName() = 0; + virtual char *GetMapName() const = 0; virtual bool IsSixup(int ClientID) const = 0; }; @@ -267,25 +267,42 @@ virtual void OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) = 0; - virtual void OnClientConnected(int ClientID) = 0; + // Called before map reload, for any data that the game wants to + // persist to the next map. + // + // Has the size of the return value of `PersistentClientDataSize()`. + // + // Returns whether the game should be supplied with the data when the + // client connects for the next map. + virtual bool OnClientDataPersist(int ClientID, void *pData) = 0; + + // Called when a client connects. + // + // If it is reconnecting to the game after a map change, the + // `pPersistentData` point is nonnull and contains the data the game + // previously stored. + virtual void OnClientConnected(int ClientID, void *pPersistentData) = 0; + virtual void OnClientEnter(int ClientID) = 0; virtual void OnClientDrop(int ClientID, const char *pReason) = 0; virtual void OnClientDirectInput(int ClientID, void *pInput) = 0; virtual void OnClientPredictedInput(int ClientID, void *pInput) = 0; virtual void OnClientPredictedEarlyInput(int ClientID, void *pInput) = 0; - virtual bool IsClientReady(int ClientID) = 0; - virtual bool IsClientPlayer(int ClientID) = 0; + virtual bool IsClientReady(int ClientID) const = 0; + virtual bool IsClientPlayer(int ClientID) const = 0; + + virtual int PersistentClientDataSize() const = 0; - virtual CUuid GameUuid() = 0; - virtual const char *GameType() = 0; - virtual const char *Version() = 0; - virtual const char *NetVersion() = 0; + virtual CUuid GameUuid() const = 0; + virtual const char *GameType() const = 0; + virtual const char *Version() const = 0; + virtual const char *NetVersion() const = 0; // DDRace virtual void OnSetAuthed(int ClientID, int Level) = 0; - virtual bool PlayerExists(int ClientID) = 0; + virtual bool PlayerExists(int ClientID) const = 0; virtual void OnClientEngineJoin(int ClientID, bool Sixup) = 0; virtual void OnClientEngineDrop(int ClientID, const char *pReason) = 0; diff -Nru ddnet-15.3.2/src/engine/shared/config_variables.h ddnet-15.5.4/src/engine/shared/config_variables.h --- ddnet-15.3.2/src/engine/shared/config_variables.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/config_variables.h 2021-06-20 09:38:48.000000000 +0000 @@ -63,6 +63,8 @@ MACRO_CONFIG_STR(BrFilterExcludeCountries, br_filter_exclude_countries, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out DDNet servers by country") MACRO_CONFIG_STR(BrFilterExcludeTypes, br_filter_exclude_types, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out DDNet servers by type (mod)") MACRO_CONFIG_INT(BrIndicateFinished, br_indicate_finished, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show whether you have finished a DDNet map (transmits your player name to info2.ddnet.tw/info)") +MACRO_CONFIG_STR(BrLocation, br_location, 16, "auto", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Override location for ping estimation, available: auto, af, as, as:cn, eu, na, oc, sa (Automatic, Africa, Asia, China, Europe, North America, Oceania/Australia, South America") +MACRO_CONFIG_STR(BrCachedBestServerinfoUrl, br_cached_best_serverinfo_url, 256, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Do not set this variable, instead create a serverlist_urls.cfg next to settings_ddnet.cfg to specify all possible serverlist URLs") MACRO_CONFIG_STR(BrFilterExcludeCountriesKoG, br_filter_exclude_countries_kog, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out kog servers by country") MACRO_CONFIG_STR(BrFilterExcludeTypesKoG, br_filter_exclude_types_kog, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out kog servers by type (mod)") @@ -98,12 +100,14 @@ MACRO_CONFIG_INT(GfxScreen, gfx_screen, 0, 0, 15, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Screen index") MACRO_CONFIG_INT(GfxScreenWidth, gfx_screen_width, 0, 0, 0, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Screen resolution width") MACRO_CONFIG_INT(GfxScreenHeight, gfx_screen_height, 0, 0, 0, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Screen resolution height") -#if !defined(CONF_PLATFORM_MACOSX) +MACRO_CONFIG_INT(GfxDesktopWidth, gfx_desktop_height, 0, 0, 0, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Desktop resolution width for detecting display changes (not recommended to change manually)") +MACRO_CONFIG_INT(GfxDesktopHeight, gfx_desktop_height, 0, 0, 0, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Desktop resolution height for detecting display changes (not recommended to change manually)") +#if !defined(CONF_PLATFORM_MACOS) MACRO_CONFIG_INT(GfxBorderless, gfx_borderless, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Borderless window (not to be used with fullscreen)") -MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Fullscreen") +MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 1, 0, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Set fullscreen mode: 0=no fullscreen, 1=pure fullscreen, 2=desktop fullscreen") #else MACRO_CONFIG_INT(GfxBorderless, gfx_borderless, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Borderless window (not to be used with fullscreen)") -MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Fullscreen") +MACRO_CONFIG_INT(GfxFullscreen, gfx_fullscreen, 0, 0, 2, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Set fullscreen mode: 0=no fullscreen, 1=pure fullscreen, 2=desktop fullscreen") #endif MACRO_CONFIG_INT(GfxHighdpi, gfx_highdpi, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable high-dpi") MACRO_CONFIG_INT(GfxAlphabits, gfx_alphabits, 0, 0, 0, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Alpha bits for framebuffer (fullscreen only)") @@ -111,9 +115,7 @@ //MACRO_CONFIG_INT(GfxClear, gfx_clear, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Clear screen before rendering") MACRO_CONFIG_INT(GfxVsync, gfx_vsync, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Vertical sync (may cause delay)") MACRO_CONFIG_INT(GfxResizable, gfx_resizable, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enables window resizing") -MACRO_CONFIG_INT(GfxDisplayAllModes, gfx_display_all_modes, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "") -MACRO_CONFIG_INT(GfxTextureCompressionOld, gfx_texture_compression_old, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Use texture compression") -MACRO_CONFIG_INT(GfxTextureQualityOld, gfx_texture_quality_old, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(GfxDisplayAllVideoModes, gfx_display_all_video_modes, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show all video modes") MACRO_CONFIG_INT(GfxHighDetail, gfx_high_detail, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "High detail") MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 16, CFGFLAG_SAVE | CFGFLAG_CLIENT, "FSAA Samples") MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Screen refresh rate") @@ -123,7 +125,6 @@ MACRO_CONFIG_INT(GfxAsyncRenderOld, gfx_asyncrender_old, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Do rendering async from the the update") MACRO_CONFIG_INT(GfxTuneOverlay, gfx_tune_overlay, 20, 1, 100, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Stop rendering text overlay in tuning zone in editor: high value = less details = more speed") MACRO_CONFIG_INT(GfxQuadAsTriangle, gfx_quad_as_triangle, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Render quads as triangles (fixes quad coloring on some GPUs)") -MACRO_CONFIG_INT(GfxShowWarnings, gfx_show_warnings, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Render gfx warnings to screen") MACRO_CONFIG_INT(InpMousesens, inp_mousesens, 200, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Mouse sensitivity") MACRO_CONFIG_INT(InpMouseOld, inp_mouseold, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Use old mouse mode (warp mouse instead of raw input)") @@ -154,6 +155,7 @@ MACRO_CONFIG_STR(SvDnsblKey, sv_dnsbl_key, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Optional Authentication Key for the specified DNSBL provider") MACRO_CONFIG_INT(SvDnsblVote, sv_dnsbl_vote, 0, 0, 1, CFGFLAG_SERVER, "Block votes by blacklisted addresses") MACRO_CONFIG_INT(SvDnsblBan, sv_dnsbl_ban, 0, 0, 1, CFGFLAG_SERVER, "Automatically ban blacklisted addresses") +MACRO_CONFIG_INT(SvDnsblChat, sv_dnsbl_chat, 0, 0, 1, CFGFLAG_SERVER, "Don't allow chat from blacklisted addresses") MACRO_CONFIG_INT(SvRconVote, sv_rcon_vote, 0, 0, 1, CFGFLAG_SERVER, "Only allow authed clients to call votes") MACRO_CONFIG_INT(SvPlayerDemoRecord, sv_player_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos for each player") @@ -230,7 +232,10 @@ MACRO_CONFIG_STR(SvRegionName, sv_region_name, 5, "UNK", CFGFLAG_SERVER, "Server region. Used for regional bans") MACRO_CONFIG_STR(SvSqlServerName, sv_sql_servername, 5, "UNK", CFGFLAG_SERVER, "SQL Server name that is inserted into record table") MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables savegames (/save and /load)") -MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame") +MACRO_CONFIG_INT(SvSaveSwapGamesDelay, sv_saveswapgames_delay, 30, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame or before swapping") +MACRO_CONFIG_INT(SvSaveSwapGamesPenalty, sv_saveswapgames_penalty, 60, 0, 10000, CFGFLAG_SERVER, "Penalty in seconds for saving or swapping position") +MACRO_CONFIG_INT(SvSwapTimeout, sv_swap_timeout, 30, 0, 10000, CFGFLAG_SERVER, "Timeout in seconds before option to swap expires") +MACRO_CONFIG_INT(SvSwap, sv_swap, 0, 0, 1, CFGFLAG_SERVER, "Enable /swap") MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables MySQL backend instead of SQLite backend (sv_sqlite_file is still used as fallback write server when no MySQL server is reachable)") MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player") MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet-server.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server") @@ -328,6 +333,7 @@ MACRO_CONFIG_INT(SvDefaultTimerType, sv_default_timer_type, 0, 0, 3, CFGFLAG_SERVER, "Default way of displaying time either game/round timer or broadcast. 0 = game/round timer, 1 = broadcast, 2 = 0+1, 3 = none") // these might need some fine tuning +MACRO_CONFIG_INT(SvChatInitialDelay, sv_chat_initial_delay, 0, 0, 360, CFGFLAG_SERVER, "The time in seconds before the first message can be sent") MACRO_CONFIG_INT(SvChatPenalty, sv_chat_penalty, 250, 50, 1000, CFGFLAG_SERVER, "chat score will be increased by this on every message, and decremented by 1 on every tick.") MACRO_CONFIG_INT(SvChatThreshold, sv_chat_threshold, 1000, 50, 10000, CFGFLAG_SERVER, "if chats core exceeds this, the player will be muted for sv_spam_mute_duration seconds") MACRO_CONFIG_INT(SvSpamMuteDuration, sv_spam_mute_duration, 60, 0, 3600, CFGFLAG_SERVER, "how many seconds to mute, if player triggers mute on spam. 0 = off") @@ -360,6 +366,10 @@ MACRO_CONFIG_INT(ClShowHookCollOther, cl_show_hook_coll_other, 1, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show other players' hook collision line (2 to always show)") MACRO_CONFIG_INT(ClShowHookCollOwn, cl_show_hook_coll_own, 1, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show own players' hook collision line (2 to always show)") +MACRO_CONFIG_COL(ClHookCollColorNoColl, cl_hook_coll_color_no_coll, 65407, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies the color of a hookline that hits nothing.") +MACRO_CONFIG_COL(ClHookCollColorHookableColl, cl_hook_coll_color_hookable_coll, 6401973, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies the color of a hookline that hits hookable tiles.") +MACRO_CONFIG_COL(ClHookCollColorTeeColl, cl_hook_coll_color_tee_coll, 2817919, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies the color of a hookline that hits tees.") + MACRO_CONFIG_INT(ClChatTeamColors, cl_chat_teamcolors, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show names in chat in team colors") MACRO_CONFIG_INT(ClChatReset, cl_chat_reset, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Reset chat when pressing escape") MACRO_CONFIG_INT(ClChatOld, cl_chat_old, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Old chat style: No tee, no background"); @@ -395,7 +405,7 @@ MACRO_CONFIG_INT(Gfx3DTextureAnalysisDone, gfx_3d_texture_analysis_done, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Analyzed, if sampling 3D/2D array textures was correct") MACRO_CONFIG_INT(GfxDriverIsBlocked, gfx_driver_is_blocked, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "If 1, the current driver is in a blocked error state.") -#if !defined(CONF_PLATFORM_MACOSX) +#if !defined(CONF_PLATFORM_MACOS) MACRO_CONFIG_INT(GfxEnableTextureUnitOptimization, gfx_enable_texture_unit_optimization, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Use multiple texture units, instead of only one.") #else MACRO_CONFIG_INT(GfxEnableTextureUnitOptimization, gfx_enable_texture_unit_optimization, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Use multiple texture units, instead of only one.") diff -Nru ddnet-15.3.2/src/engine/shared/console.cpp ddnet-15.5.4/src/engine/shared/console.cpp --- ddnet-15.3.2/src/engine/shared/console.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/console.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -313,7 +313,7 @@ return pBuf; } -void CConsole::Print(int Level, const char *pFrom, const char *pStr, bool Highlighted) +void CConsole::Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor) { dbg_msg(pFrom, "%s", pStr); char aBuf[1024]; @@ -322,7 +322,7 @@ { if(Level <= m_aPrintCB[i].m_OutputLevel && m_aPrintCB[i].m_pfnPrintCallback) { - m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata, Highlighted); + m_aPrintCB[i].m_pfnPrintCallback(aBuf, m_aPrintCB[i].m_pPrintCallbackUserdata, PrintColor); } } } diff -Nru ddnet-15.3.2/src/engine/shared/console.h ddnet-15.5.4/src/engine/shared/console.h --- ddnet-15.3.2/src/engine/shared/console.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/console.h 2021-06-20 09:38:48.000000000 +0000 @@ -218,7 +218,7 @@ virtual int RegisterPrintCallback(int OutputLevel, FPrintCallback pfnPrintCallback, void *pUserData); virtual void SetPrintOutputLevel(int Index, int OutputLevel); virtual char *Format(char *pBuf, int Size, const char *pFrom, const char *pStr); - virtual void Print(int Level, const char *pFrom, const char *pStr, bool Highlighted = false); + virtual void Print(int Level, const char *pFrom, const char *pStr, ColorRGBA PrintColor = {1, 1, 1, 1}); virtual void SetTeeHistorianCommandCallback(FTeeHistorianCommandCallback pfnCallback, void *pUser); void SetAccessLevel(int AccessLevel) { m_AccessLevel = clamp(AccessLevel, (int)(ACCESS_LEVEL_ADMIN), (int)(ACCESS_LEVEL_USER)); } diff -Nru ddnet-15.3.2/src/engine/shared/datafile.cpp ddnet-15.5.4/src/engine/shared/datafile.cpp --- ddnet-15.3.2/src/engine/shared/datafile.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/datafile.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -15,7 +15,6 @@ enum { OFFSET_UUID_TYPE = 0x8000, - ITEMTYPE_EX = 0xffff, }; struct CItemEx @@ -278,7 +277,7 @@ return true; } -int CDataFileReader::NumData() +int CDataFileReader::NumData() const { if(!m_pDataFile) { @@ -395,7 +394,7 @@ m_pDataFile->m_ppDataPtrs[Index] = 0x0; } -int CDataFileReader::GetItemSize(int Index) +int CDataFileReader::GetItemSize(int Index) const { if(!m_pDataFile) return 0; @@ -519,7 +518,7 @@ return GetItem(Index, 0, 0); } -int CDataFileReader::NumItems() +int CDataFileReader::NumItems() const { if(!m_pDataFile) return 0; @@ -542,7 +541,7 @@ return true; } -SHA256_DIGEST CDataFileReader::Sha256() +SHA256_DIGEST CDataFileReader::Sha256() const { if(!m_pDataFile) { @@ -556,14 +555,14 @@ return m_pDataFile->m_Sha256; } -unsigned CDataFileReader::Crc() +unsigned CDataFileReader::Crc() const { if(!m_pDataFile) return 0xFFFFFFFF; return m_pDataFile->m_Crc; } -int CDataFileReader::MapSize() +int CDataFileReader::MapSize() const { if(!m_pDataFile) return 0; diff -Nru ddnet-15.3.2/src/engine/shared/datafile.h ddnet-15.5.4/src/engine/shared/datafile.h --- ddnet-15.3.2/src/engine/shared/datafile.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/datafile.h 2021-06-20 09:38:48.000000000 +0000 @@ -10,6 +10,11 @@ #include +enum +{ + ITEMTYPE_EX = 0xffff, +}; + // raw datafile access class CDataFileReader { @@ -35,17 +40,17 @@ int GetDataSize(int Index); void UnloadData(int Index); void *GetItem(int Index, int *pType, int *pID); - int GetItemSize(int Index); + int GetItemSize(int Index) const; void GetType(int Type, int *pStart, int *pNum); int FindItemIndex(int Type, int ID); void *FindItem(int Type, int ID); - int NumItems(); - int NumData(); + int NumItems() const; + int NumData() const; void Unload(); - SHA256_DIGEST Sha256(); - unsigned Crc(); - int MapSize(); + SHA256_DIGEST Sha256() const; + unsigned Crc() const; + int MapSize() const; IOHANDLE File(); }; diff -Nru ddnet-15.3.2/src/engine/shared/demo.cpp ddnet-15.5.4/src/engine/shared/demo.cpp --- ddnet-15.3.2/src/engine/shared/demo.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/demo.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -28,6 +28,8 @@ static const int s_LengthOffset = 152; static const int s_NumMarkersOffset = 176; +static const ColorRGBA gs_DemoPrintColor{0.7f, 0.7f, 0.7f, 1.0f}; + CDemoRecorder::CDemoRecorder(class CSnapshotDelta *pSnapshotDelta, bool NoMapData) { m_File = 0; @@ -55,7 +57,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Unable to open '%s' for recording", pFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); } return -1; } @@ -111,7 +113,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Unable to open mapfile '%s'", pMap); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); } return -1; } @@ -179,7 +181,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Recording to '%s'", pFilename); - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", aBuf, gs_DemoPrintColor); } m_File = DemoFile; str_copy(m_aCurrentFilename, pFilename, sizeof(m_aCurrentFilename)); @@ -377,7 +379,7 @@ io_close(m_File); m_File = 0; if(m_pConsole) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Stopped recording", gs_DemoPrintColor); return 0; } @@ -398,7 +400,7 @@ m_aTimelineMarkers[m_NumTimelineMarkers++] = m_LastTickMarker; if(m_pConsole) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Added timeline marker"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demo_recorder", "Added timeline marker", gs_DemoPrintColor); } CDemoPlayer::CDemoPlayer(class CSnapshotDelta *pSnapshotDelta) @@ -529,7 +531,7 @@ } // copy all the frames to an array instead for fast access - m_pKeyFrames = (CKeyFrame *)calloc(std::max(m_Info.m_SeekablePoints, 1), sizeof(CKeyFrame)); + m_pKeyFrames = (CKeyFrame *)calloc(maximum(m_Info.m_SeekablePoints, 1), sizeof(CKeyFrame)); for(pCurrentKey = pFirstKey, i = 0; pCurrentKey; pCurrentKey = pCurrentKey->m_pNext, i++) m_pKeyFrames[i] = pCurrentKey->m_Frame; diff -Nru ddnet-15.3.2/src/engine/shared/econ.cpp ddnet-15.5.4/src/engine/shared/econ.cpp --- ddnet-15.3.2/src/engine/shared/econ.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/econ.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -36,7 +36,7 @@ return 0; } -void CEcon::SendLineCB(const char *pLine, void *pUserData, bool Highlighted) +void CEcon::SendLineCB(const char *pLine, void *pUserData, ColorRGBA PrintColor) { static_cast(pUserData)->Send(-1, pLine); } diff -Nru ddnet-15.3.2/src/engine/shared/econ.h ddnet-15.5.4/src/engine/shared/econ.h --- ddnet-15.3.2/src/engine/shared/econ.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/econ.h 2021-06-20 09:38:48.000000000 +0000 @@ -38,7 +38,7 @@ int m_PrintCBIndex; int m_UserClientID; - static void SendLineCB(const char *pLine, void *pUserData, bool Highlighted); + static void SendLineCB(const char *pLine, void *pUserData, ColorRGBA PrintColor = {1, 1, 1, 1}); static void ConchainEconOutputLevelUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConLogout(IConsole::IResult *pResult, void *pUserData); diff -Nru ddnet-15.3.2/src/engine/shared/engine.cpp ddnet-15.5.4/src/engine/shared/engine.cpp --- ddnet-15.3.2/src/engine/shared/engine.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/engine.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -53,25 +53,28 @@ } } - CEngine(const char *pAppname, bool Silent, int Jobs) + CEngine(bool Test, const char *pAppname, bool Silent, int Jobs) { - if(!Silent) - dbg_logger_stdout(); - dbg_logger_debugger(); + if(!Test) + { + if(!Silent) + dbg_logger_stdout(); + dbg_logger_debugger(); - // - dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); + // + dbg_msg("engine", "running on %s-%s-%s", CONF_FAMILY_STRING, CONF_PLATFORM_STRING, CONF_ARCH_STRING); #ifdef CONF_ARCH_ENDIAN_LITTLE - dbg_msg("engine", "arch is little endian"); + dbg_msg("engine", "arch is little endian"); #elif defined(CONF_ARCH_ENDIAN_BIG) - dbg_msg("engine", "arch is big endian"); + dbg_msg("engine", "arch is big endian"); #else - dbg_msg("engine", "unknown endian"); + dbg_msg("engine", "unknown endian"); #endif - // init the network - net_init(); - CNetBase::Init(); + // init the network + net_init(); + CNetBase::Init(); + } m_JobPool.Init(Jobs); @@ -104,4 +107,10 @@ } }; -IEngine *CreateEngine(const char *pAppname, bool Silent, int Jobs) { return new CEngine(pAppname, Silent, Jobs); } +void IEngine::RunJobBlocking(IJob *pJob) +{ + CJobPool::RunBlocking(pJob); +} + +IEngine *CreateEngine(const char *pAppname, bool Silent, int Jobs) { return new CEngine(false, pAppname, Silent, Jobs); } +IEngine *CreateTestEngine(const char *pAppname, int Jobs) { return new CEngine(true, pAppname, true, Jobs); } diff -Nru ddnet-15.3.2/src/engine/shared/fifo.cpp ddnet-15.5.4/src/engine/shared/fifo.cpp --- ddnet-15.3.2/src/engine/shared/fifo.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/fifo.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -19,36 +19,40 @@ if(pFifoFile[0] == '\0') return; + str_copy(m_aFilename, pFifoFile, sizeof(m_aFilename)); m_Flag = Flag; - mkfifo(pFifoFile, 0600); + mkfifo(m_aFilename, 0600); struct stat Attribute; - stat(pFifoFile, &Attribute); + stat(m_aFilename, &Attribute); if(!S_ISFIFO(Attribute.st_mode)) { - dbg_msg("fifo", "'%s' is not a fifo, removing", pFifoFile); - fs_remove(pFifoFile); - mkfifo(pFifoFile, 0600); - stat(pFifoFile, &Attribute); + dbg_msg("fifo", "'%s' is not a fifo, removing", m_aFilename); + fs_remove(m_aFilename); + mkfifo(m_aFilename, 0600); + stat(m_aFilename, &Attribute); if(!S_ISFIFO(Attribute.st_mode)) { - dbg_msg("fifo", "can't remove file '%s', quitting", pFifoFile); + dbg_msg("fifo", "can't remove file '%s', quitting", m_aFilename); exit(2); } } - m_File = open(pFifoFile, O_RDONLY | O_NONBLOCK); + m_File = open(m_aFilename, O_RDONLY | O_NONBLOCK); if(m_File < 0) - dbg_msg("fifo", "can't open file '%s'", pFifoFile); + dbg_msg("fifo", "can't open file '%s'", m_aFilename); } void CFifo::Shutdown() { if(m_File >= 0) + { close(m_File); + fs_remove(m_aFilename); + } } void CFifo::Update() diff -Nru ddnet-15.3.2/src/engine/shared/fifo.h ddnet-15.5.4/src/engine/shared/fifo.h --- ddnet-15.3.2/src/engine/shared/fifo.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/fifo.h 2021-06-20 09:38:48.000000000 +0000 @@ -8,6 +8,7 @@ class CFifo { IConsole *m_pConsole; + char m_aFilename[MAX_PATH_LENGTH]; int m_Flag; int m_File; diff -Nru ddnet-15.3.2/src/engine/shared/image_manipulation.cpp ddnet-15.5.4/src/engine/shared/image_manipulation.cpp --- ddnet-15.3.2/src/engine/shared/image_manipulation.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/image_manipulation.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -61,7 +61,10 @@ for(int x = 0; x < w; x++, m += BPP) { for(int i = 0; i < BPP - 1; ++i) - pDest[m + i] = pSrc[m + i]; + { + if(pDest[m + 3] == 0) + pDest[m + i] = pSrc[m + i]; + } } } } diff -Nru ddnet-15.3.2/src/engine/shared/jobs.cpp ddnet-15.5.4/src/engine/shared/jobs.cpp --- ddnet-15.3.2/src/engine/shared/jobs.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/jobs.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -75,9 +75,7 @@ // do the job if we have one if(pJob) { - pJob->m_Status = IJob::STATE_RUNNING; - pJob->Run(); - pJob->m_Status = IJob::STATE_DONE; + RunBlocking(pJob.get()); } } } @@ -104,3 +102,10 @@ lock_unlock(m_Lock); sphore_signal(&m_Semaphore); } + +void CJobPool::RunBlocking(IJob *pJob) +{ + pJob->m_Status = IJob::STATE_RUNNING; + pJob->Run(); + pJob->m_Status = IJob::STATE_DONE; +} diff -Nru ddnet-15.3.2/src/engine/shared/jobs.h ddnet-15.5.4/src/engine/shared/jobs.h --- ddnet-15.3.2/src/engine/shared/jobs.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/jobs.h 2021-06-20 09:38:48.000000000 +0000 @@ -59,5 +59,6 @@ void Init(int NumThreads); void Add(std::shared_ptr pJob); + static void RunBlocking(IJob *pJob); }; #endif diff -Nru ddnet-15.3.2/src/engine/shared/masterserver.cpp ddnet-15.5.4/src/engine/shared/masterserver.cpp --- ddnet-15.3.2/src/engine/shared/masterserver.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/masterserver.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -97,12 +97,12 @@ } } - virtual int IsRefreshing() + virtual bool IsRefreshing() const { return m_State != STATE_READY; } - virtual NETADDR GetAddr(int Index) + virtual NETADDR GetAddr(int Index) const { return m_aMasterServers[Index].m_Addr; } @@ -112,17 +112,17 @@ m_aMasterServers[Index].m_Count = Count; } - virtual int GetCount(int Index) + virtual int GetCount(int Index) const { return m_aMasterServers[Index].m_Count; } - virtual const char *GetName(int Index) + virtual const char *GetName(int Index) const { return m_aMasterServers[Index].m_aHostname; } - virtual bool IsValid(int Index) + virtual bool IsValid(int Index) const { return m_aMasterServers[Index].m_Valid; } diff -Nru ddnet-15.3.2/src/engine/shared/netban.cpp ddnet-15.5.4/src/engine/shared/netban.cpp --- ddnet-15.3.2/src/engine/shared/netban.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/netban.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -434,7 +434,7 @@ CNetBan *pThis = static_cast(pUser); const char *pStr = pResult->GetString(0); - int Minutes = pResult->NumArguments() > 1 ? clamp(pResult->GetInteger(1), 0, 44640) : 30; + int Minutes = pResult->NumArguments() > 1 ? clamp(pResult->GetInteger(1), 0, 525600) : 30; const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(2) : "No reason given"; NETADDR Addr; @@ -450,7 +450,7 @@ const char *pStr1 = pResult->GetString(0); const char *pStr2 = pResult->GetString(1); - int Minutes = pResult->NumArguments() > 2 ? clamp(pResult->GetInteger(2), 0, 44640) : 30; + int Minutes = pResult->NumArguments() > 2 ? clamp(pResult->GetInteger(2), 0, 525600) : 30; const char *pReason = pResult->NumArguments() > 3 ? pResult->GetString(3) : "No reason given"; CNetRange Range; diff -Nru ddnet-15.3.2/src/engine/shared/netban.h ddnet-15.5.4/src/engine/shared/netban.h --- ddnet-15.3.2/src/engine/shared/netban.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/netban.h 2021-06-20 09:38:48.000000000 +0000 @@ -235,7 +235,7 @@ str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason); } else - str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason); + str_format(pBuf, BuffSize, "%s (%s)", aBuf, pBan->m_Info.m_aReason); } #endif diff -Nru ddnet-15.3.2/src/engine/shared/network_client.cpp ddnet-15.5.4/src/engine/shared/network_client.cpp --- ddnet-15.3.2/src/engine/shared/network_client.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/network_client.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -143,14 +143,14 @@ return m_Connection.Flush(); } -int CNetClient::GotProblems() +int CNetClient::GotProblems() const { if(time_get() - m_Connection.LastRecvTime() > time_freq()) return 1; return 0; } -const char *CNetClient::ErrorString() +const char *CNetClient::ErrorString() const { return m_Connection.ErrorString(); } diff -Nru ddnet-15.3.2/src/engine/shared/network.h ddnet-15.5.4/src/engine/shared/network.h --- ddnet-15.3.2/src/engine/shared/network.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/network.h 2021-06-20 09:38:48.000000000 +0000 @@ -455,8 +455,8 @@ // error and state int NetType() const { return m_Socket.type; } int State(); - int GotProblems(); - const char *ErrorString(); + int GotProblems() const; + const char *ErrorString() const; bool SecurityTokenUnknown() { return m_Connection.SecurityToken() == NET_SECURITY_TOKEN_UNKNOWN; } }; diff -Nru ddnet-15.3.2/src/engine/shared/protocol_ex.h ddnet-15.5.4/src/engine/shared/protocol_ex.h --- ddnet-15.3.2/src/engine/shared/protocol_ex.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/protocol_ex.h 2021-06-20 09:38:48.000000000 +0000 @@ -20,9 +20,11 @@ UNPACKMESSAGE_OK, UNPACKMESSAGE_ANSWER, - SERVERCAP_CURVERSION = 1, + SERVERCAP_CURVERSION = 3, SERVERCAPFLAG_DDNET = 1 << 0, SERVERCAPFLAG_CHATTIMEOUTCODE = 1 << 1, + SERVERCAPFLAG_ANYPLAYERFLAG = 1 << 2, + SERVERCAPFLAG_PINGEX = 1 << 3, }; void RegisterUuids(class CUuidManager *pManager); diff -Nru ddnet-15.3.2/src/engine/shared/protocol_ex_msgs.h ddnet-15.5.4/src/engine/shared/protocol_ex_msgs.h --- ddnet-15.3.2/src/engine/shared/protocol_ex_msgs.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/protocol_ex_msgs.h 2021-06-20 09:38:48.000000000 +0000 @@ -27,3 +27,5 @@ UUID(NETMSG_MAP_DETAILS, "map-details@ddnet.tw") UUID(NETMSG_CAPABILITIES, "capabilities@ddnet.tw") UUID(NETMSG_CLIENTVER, "clientver@ddnet.tw") +UUID(NETMSG_PINGEX, "ping@ddnet.tw") +UUID(NETMSG_PONGEX, "pong@ddnet.tw") diff -Nru ddnet-15.3.2/src/engine/shared/protocol.h ddnet-15.5.4/src/engine/shared/protocol.h --- ddnet-15.3.2/src/engine/shared/protocol.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/protocol.h 2021-06-20 09:38:48.000000000 +0000 @@ -114,6 +114,7 @@ VERSION_DDNET_FIREDELAY_TUNE = 701, VERSION_DDNET_UPDATER_FIXED = 707, VERSION_DDNET_GAMETICK = 10042, + VERSION_DDNET_MSG_LEGACY = 15040, }; #endif diff -Nru ddnet-15.3.2/src/engine/shared/serverbrowser.cpp ddnet-15.5.4/src/engine/shared/serverbrowser.cpp --- ddnet-15.3.2/src/engine/shared/serverbrowser.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/serverbrowser.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -#include -#include - -// gametypes - -bool IsVanilla(const CServerInfo *pInfo) -{ - return !str_comp(pInfo->m_aGameType, "DM") || !str_comp(pInfo->m_aGameType, "TDM") || !str_comp(pInfo->m_aGameType, "CTF"); -} - -bool IsCatch(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "catch"); -} - -bool IsInsta(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "idm") || str_find_nocase(pInfo->m_aGameType, "itdm") || str_find_nocase(pInfo->m_aGameType, "ictf"); -} - -bool IsFNG(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "fng"); -} - -bool IsRace(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "race") || IsFastCap(pInfo) || IsDDRace(pInfo); -} - -bool IsFastCap(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "fastcap"); -} - -bool IsDDRace(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "ddrace") || str_find_nocase(pInfo->m_aGameType, "mkrace") || IsDDNet(pInfo); -} - -bool IsBlockInfectionZ(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "blockz") || - str_find_nocase(pInfo->m_aGameType, "infectionz"); -} - -bool IsBlockWorlds(const CServerInfo *pInfo) -{ - return (str_comp_nocase_num(pInfo->m_aGameType, "bw ", 4) == 0) || (str_comp_nocase(pInfo->m_aGameType, "bw") == 0); -} - -bool IsCity(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "city"); -} - -bool IsDDNet(const CServerInfo *pInfo) -{ - return str_find_nocase(pInfo->m_aGameType, "ddracenet") || str_find_nocase(pInfo->m_aGameType, "ddnet") || IsBlockInfectionZ(pInfo); -} - -// other - -bool Is64Player(const CServerInfo *pInfo) -{ - return str_find(pInfo->m_aGameType, "64") || str_find(pInfo->m_aName, "64") || IsDDNet(pInfo) || IsBlockInfectionZ(pInfo) || IsBlockWorlds(pInfo); -} - -bool IsPlus(const CServerInfo *pInfo) -{ - return str_find(pInfo->m_aGameType, "+"); -} diff -Nru ddnet-15.3.2/src/engine/shared/serverinfo.cpp ddnet-15.5.4/src/engine/shared/serverinfo.cpp --- ddnet-15.3.2/src/engine/shared/serverinfo.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/serverinfo.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,189 @@ +#include "serverinfo.h" + +#include "json.h" +#include +#include + +static bool IsAllowedHex(char c) +{ + static const char ALLOWED[] = "0123456789abcdefABCDEF"; + for(int i = 0; i < (int)sizeof(ALLOWED) - 1; i++) + { + if(c == ALLOWED[i]) + { + return true; + } + } + return false; +} + +bool ParseCrc(unsigned int *pResult, const char *pString) +{ + if(str_length(pString) != 8) + { + return true; + } + for(int i = 0; i < 8; i++) + { + if(!IsAllowedHex(pString[i])) + { + return true; + } + } + return sscanf(pString, "%08x", pResult) != 1; +} + +bool CServerInfo2::FromJson(CServerInfo2 *pOut, const json_value *pJson) +{ + bool Result = FromJsonRaw(pOut, pJson); + if(Result) + { + return Result; + } + return pOut->Validate(); +} + +bool CServerInfo2::Validate() const +{ + bool Error = false; + Error = Error || m_MaxClients < m_MaxPlayers; + Error = Error || m_NumClients < m_NumPlayers; + Error = Error || m_MaxClients < m_NumClients; + Error = Error || m_MaxPlayers < m_NumPlayers; + return Error; +} + +bool CServerInfo2::FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson) +{ + mem_zero(pOut, sizeof(*pOut)); + bool Error; + + const json_value &ServerInfo = *pJson; + const json_value &MaxClients = ServerInfo["max_clients"]; + const json_value &MaxPlayers = ServerInfo["max_players"]; + const json_value &Passworded = ServerInfo["passworded"]; + const json_value &GameType = ServerInfo["game_type"]; + const json_value &Name = ServerInfo["name"]; + const json_value &MapName = ServerInfo["map"]["name"]; + const json_value &Version = ServerInfo["version"]; + const json_value &Clients = ServerInfo["clients"]; + Error = false; + Error = Error || MaxClients.type != json_integer; + Error = Error || MaxPlayers.type != json_integer; + Error = Error || Passworded.type != json_boolean; + Error = Error || GameType.type != json_string; + Error = Error || Name.type != json_string; + Error = Error || MapName.type != json_string; + Error = Error || Version.type != json_string; + Error = Error || Clients.type != json_array; + if(Error) + { + return true; + } + pOut->m_MaxClients = MaxClients; + pOut->m_MaxPlayers = MaxPlayers; + pOut->m_Passworded = Passworded; + str_copy(pOut->m_aGameType, GameType, sizeof(pOut->m_aGameType)); + str_copy(pOut->m_aName, Name, sizeof(pOut->m_aName)); + str_copy(pOut->m_aMapName, MapName, sizeof(pOut->m_aMapName)); + str_copy(pOut->m_aVersion, Version, sizeof(pOut->m_aVersion)); + + pOut->m_NumClients = 0; + pOut->m_NumPlayers = 0; + for(unsigned i = 0; i < Clients.u.array.length; i++) + { + const json_value &Client = Clients[i]; + const json_value &Name = Client["name"]; + const json_value &Clan = Client["clan"]; + const json_value &Country = Client["country"]; + const json_value &Score = Client["score"]; + const json_value &IsPlayer = Client["is_player"]; + Error = false; + Error = Error || Name.type != json_string; + Error = Error || Clan.type != json_string; + Error = Error || Country.type != json_integer; + Error = Error || Score.type != json_integer; + Error = Error || IsPlayer.type != json_boolean; + if(Error) + { + return true; + } + if(i < MAX_CLIENTS) + { + CClient *pClient = &pOut->m_aClients[i]; + str_copy(pClient->m_aName, Name, sizeof(pClient->m_aName)); + str_copy(pClient->m_aClan, Clan, sizeof(pClient->m_aClan)); + pClient->m_Country = Country; + pClient->m_Score = Score; + pClient->m_IsPlayer = IsPlayer; + } + + pOut->m_NumClients++; + if((bool)IsPlayer) + { + pOut->m_NumPlayers++; + } + } + return false; +} + +bool CServerInfo2::operator==(const CServerInfo2 &Other) const +{ + bool Unequal; + Unequal = false; + Unequal = Unequal || m_MaxClients != Other.m_MaxClients; + Unequal = Unequal || m_NumClients != Other.m_NumClients; + Unequal = Unequal || m_MaxPlayers != Other.m_MaxPlayers; + Unequal = Unequal || m_NumPlayers != Other.m_NumPlayers; + Unequal = Unequal || m_Passworded != Other.m_Passworded; + Unequal = Unequal || str_comp(m_aGameType, Other.m_aGameType) != 0; + Unequal = Unequal || str_comp(m_aName, Other.m_aName) != 0; + Unequal = Unequal || str_comp(m_aMapName, Other.m_aMapName) != 0; + Unequal = Unequal || str_comp(m_aVersion, Other.m_aVersion) != 0; + if(Unequal) + { + return false; + } + for(int i = 0; i < m_NumClients; i++) + { + Unequal = false; + Unequal = Unequal || str_comp(m_aClients[i].m_aName, Other.m_aClients[i].m_aName) != 0; + Unequal = Unequal || str_comp(m_aClients[i].m_aClan, Other.m_aClients[i].m_aClan) != 0; + Unequal = Unequal || m_aClients[i].m_Country != Other.m_aClients[i].m_Country; + Unequal = Unequal || m_aClients[i].m_Score != Other.m_aClients[i].m_Score; + Unequal = Unequal || m_aClients[i].m_IsPlayer != Other.m_aClients[i].m_IsPlayer; + if(Unequal) + { + return false; + } + } + return true; +} + +CServerInfo2::operator CServerInfo() const +{ + CServerInfo Result = {0}; + Result.m_MaxClients = m_MaxClients; + Result.m_NumClients = m_NumClients; + Result.m_MaxPlayers = m_MaxPlayers; + Result.m_NumPlayers = m_NumPlayers; + Result.m_Flags = m_Passworded ? SERVER_FLAG_PASSWORD : 0; + str_copy(Result.m_aGameType, m_aGameType, sizeof(Result.m_aGameType)); + str_copy(Result.m_aName, m_aName, sizeof(Result.m_aName)); + str_copy(Result.m_aMap, m_aMapName, sizeof(Result.m_aMap)); + str_copy(Result.m_aVersion, m_aVersion, sizeof(Result.m_aVersion)); + + for(int i = 0; i < std::min(m_NumClients, (int)MAX_CLIENTS); i++) + { + str_copy(Result.m_aClients[i].m_aName, m_aClients[i].m_aName, sizeof(Result.m_aClients[i].m_aName)); + str_copy(Result.m_aClients[i].m_aClan, m_aClients[i].m_aClan, sizeof(Result.m_aClients[i].m_aClan)); + Result.m_aClients[i].m_Country = m_aClients[i].m_Country; + Result.m_aClients[i].m_Score = m_aClients[i].m_Score; + Result.m_aClients[i].m_Player = m_aClients[i].m_IsPlayer; + } + + Result.m_NumReceivedClients = std::min(m_NumClients, (int)MAX_CLIENTS); + Result.m_Latency = -1; + + return Result; +} diff -Nru ddnet-15.3.2/src/engine/shared/serverinfo.h ddnet-15.5.4/src/engine/shared/serverinfo.h --- ddnet-15.3.2/src/engine/shared/serverinfo.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/serverinfo.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,45 @@ +#ifndef ENGINE_SHARED_SERVERINFO_H +#define ENGINE_SHARED_SERVERINFO_H + +#include "protocol.h" + +typedef struct _json_value json_value; +class CServerInfo; + +class CServerInfo2 +{ +public: + class CClient + { + public: + char m_aName[MAX_NAME_LENGTH]; + char m_aClan[MAX_CLAN_LENGTH]; + int m_Country; + int m_Score; + bool m_IsPlayer; + }; + + CClient m_aClients[MAX_CLIENTS]; + int m_MaxClients; + int m_NumClients; // Indirectly serialized. + int m_MaxPlayers; + int m_NumPlayers; // Not serialized. + bool m_Passworded; + char m_aGameType[16]; + char m_aName[64]; + char m_aMapName[32]; + char m_aVersion[32]; + + bool operator==(const CServerInfo2 &Other) const; + bool operator!=(const CServerInfo2 &Other) const { return !(*this == Other); } + static bool FromJson(CServerInfo2 *pOut, const json_value *pJson); + static bool FromJsonRaw(CServerInfo2 *pOut, const json_value *pJson); + bool Validate() const; + void ToJson(char *pBuffer, int BufferSize) const; + + operator CServerInfo() const; +}; + +bool ParseCrc(unsigned int *pResult, const char *pString); + +#endif // ENGINE_SHARED_SERVERINFO_H diff -Nru ddnet-15.3.2/src/engine/shared/snapshot.cpp ddnet-15.5.4/src/engine/shared/snapshot.cpp --- ddnet-15.3.2/src/engine/shared/snapshot.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/snapshot.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -9,19 +9,19 @@ // CSnapshot -CSnapshotItem *CSnapshot::GetItem(int Index) +CSnapshotItem *CSnapshot::GetItem(int Index) const { return (CSnapshotItem *)(DataStart() + Offsets()[Index]); } -int CSnapshot::GetItemSize(int Index) +int CSnapshot::GetItemSize(int Index) const { if(Index == m_NumItems - 1) return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem); return (Offsets()[Index + 1] - Offsets()[Index]) - sizeof(CSnapshotItem); } -int CSnapshot::GetItemType(int Index) +int CSnapshot::GetItemType(int Index) const { int InternalType = GetItem(Index)->Type(); if(InternalType < OFFSET_UUID_TYPE) @@ -48,7 +48,7 @@ return g_UuidManager.LookupUuid(Uuid); } -int CSnapshot::GetItemIndex(int Key) +int CSnapshot::GetItemIndex(int Key) const { // TODO: OPT: this should not be a linear search. very bad for(int i = 0; i < m_NumItems; i++) diff -Nru ddnet-15.3.2/src/engine/shared/snapshot.h ddnet-15.5.4/src/engine/shared/snapshot.h --- ddnet-15.3.2/src/engine/shared/snapshot.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/snapshot.h 2021-06-20 09:38:48.000000000 +0000 @@ -13,9 +13,9 @@ int m_TypeAndID; int *Data() { return (int *)(this + 1); } - int Type() { return m_TypeAndID >> 16; } - int ID() { return m_TypeAndID & 0xffff; } - int Key() { return m_TypeAndID; } + int Type() const { return m_TypeAndID >> 16; } + int ID() const { return m_TypeAndID & 0xffff; } + int Key() const { return m_TypeAndID; } }; class CSnapshot @@ -42,10 +42,10 @@ m_NumItems = 0; } int NumItems() const { return m_NumItems; } - CSnapshotItem *GetItem(int Index); - int GetItemSize(int Index); - int GetItemIndex(int Key); - int GetItemType(int Index); + CSnapshotItem *GetItem(int Index) const; + int GetItemSize(int Index) const; + int GetItemIndex(int Key) const; + int GetItemType(int Index) const; unsigned Crc(); void DebugDump(); diff -Nru ddnet-15.3.2/src/engine/shared/storage.cpp ddnet-15.5.4/src/engine/shared/storage.cpp --- ddnet-15.3.2/src/engine/shared/storage.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/shared/storage.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -6,6 +6,10 @@ #include #include +#ifdef CONF_PLATFORM_HAIKU +#include +#endif + class CStorage : public IStorage { public: @@ -193,6 +197,9 @@ // 3) check for usable path in argv[0] { +#ifdef CONF_PLATFORM_HAIKU + pArgv0 = realpath(pArgv0, NULL); +#endif unsigned int Pos = ~0U; for(unsigned i = 0; pArgv0[i]; i++) if(pArgv0[i] == '/' || pArgv0[i] == '\\') @@ -211,6 +218,9 @@ } } } +#ifdef CONF_PLATFORM_HAIKU + free((void *)pArgv0); +#endif #if defined(CONF_FAMILY_UNIX) // 4) check for all default locations @@ -269,7 +279,7 @@ } else { -#if defined(CONF_PLATFORM_MACOSX) +#if defined(CONF_PLATFORM_MACOS) str_append(m_aBinarydir, "/../../../DDNet-Server.app/Contents/MacOS", sizeof(m_aBinarydir)); str_format(aBuf, sizeof(aBuf), "%s/" PLAT_SERVER_EXEC, m_aBinarydir); IOHANDLE File = io_open(aBuf, IOFLAG_READ); @@ -584,7 +594,10 @@ str_copy(pBuffer, pExtractedName, Length); } -IStorage *CreateStorage(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) { return CStorage::Create(pApplicationName, StorageType, NumArgs, ppArguments); } +IStorage *CreateStorage(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments) +{ + return CStorage::Create(pApplicationName, StorageType, NumArgs, ppArguments); +} IStorage *CreateLocalStorage() { @@ -600,3 +613,13 @@ } return pStorage; } +IStorage *CreateTempStorage(const char *pDirectory) +{ + CStorage *pStorage = new CStorage(); + if(!pStorage) + { + return nullptr; + } + pStorage->AddPath(pDirectory); + return pStorage; +} diff -Nru ddnet-15.3.2/src/engine/sqlite.h ddnet-15.5.4/src/engine/sqlite.h --- ddnet-15.3.2/src/engine/sqlite.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/engine/sqlite.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,28 @@ +#ifndef ENGINE_SQLITE_H +#define ENGINE_SQLITE_H +#include + +typedef struct sqlite3 sqlite3; +typedef struct sqlite3_stmt sqlite3_stmt; +class IConsole; +class IStorage; + +class CSqliteDeleter +{ +public: + void operator()(sqlite3 *pSqlite); +}; +class CSqliteStmtDeleter +{ +public: + void operator()(sqlite3_stmt *pStmt); +}; +typedef std::unique_ptr CSqlite; +typedef std::unique_ptr CSqliteStmt; + +int SqliteHandleError(IConsole *pConsole, int Error, sqlite3 *pSqlite, const char *pContext); +#define SQLITE_HANDLE_ERROR(x) SqliteHandleError(pConsole, x, &*pSqlite, #x) + +CSqlite SqliteOpen(IConsole *pConsole, IStorage *pStorage, const char *pPath); +CSqliteStmt SqlitePrepare(IConsole *pConsole, sqlite3 *pSqlite, const char *pStatement); +#endif // ENGINE_SQLITE_H diff -Nru ddnet-15.3.2/src/engine/storage.h ddnet-15.5.4/src/engine/storage.h --- ddnet-15.3.2/src/engine/storage.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/engine/storage.h 2021-06-20 09:38:48.000000000 +0000 @@ -44,5 +44,6 @@ extern IStorage *CreateStorage(const char *pApplicationName, int StorageType, int NumArgs, const char **ppArguments); extern IStorage *CreateLocalStorage(); +extern IStorage *CreateTempStorage(const char *pDirectory); #endif diff -Nru ddnet-15.3.2/src/game/client/components/binds.cpp ddnet-15.5.4/src/game/client/components/binds.cpp --- ddnet-15.3.2/src/game/client/components/binds.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/binds.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -4,6 +4,8 @@ #include #include +static const ColorRGBA gs_BindPrintColor{1.0f, 1.0f, 0.8f, 1.0f}; + bool CBinds::CBindsSpecial::OnInput(IInput::CEvent Event) { // only handle F and composed F binds @@ -11,13 +13,24 @@ { int Mask = m_pBinds->GetModifierMask(Input()); + // Look for a composed bind bool ret = false; - for(int Mod = 0; Mod < MODIFIER_COMBINATION_COUNT; Mod++) + for(int Mod = 1; Mod < MODIFIER_COMBINATION_COUNT; Mod++) { - if(Mask & (1 << Mod) && m_pBinds->m_aapKeyBindings[Mod][Event.m_Key]) + if(Mask == Mod && m_pBinds->m_aapKeyBindings[Mod][Event.m_Key]) + { m_pBinds->GetConsole()->ExecuteLineStroked(Event.m_Flags & IInput::FLAG_PRESS, m_pBinds->m_aapKeyBindings[Mod][Event.m_Key]); + ret = true; + } + } + + // Look for a non composed bind + if(!ret && m_pBinds->m_aapKeyBindings[0][Event.m_Key]) + { + m_pBinds->GetConsole()->ExecuteLineStroked(Event.m_Flags & IInput::FLAG_PRESS, m_pBinds->m_aapKeyBindings[0][Event.m_Key]); ret = true; } + return ret; } @@ -68,7 +81,7 @@ str_copy(m_aapKeyBindings[Modifier][KeyID], pStr, Size); str_format(aBuf, sizeof(aBuf), "bound %s%s (%d) = %s", GetKeyBindModifiersName(Modifier), Input()->KeyName(KeyID), KeyID, m_aapKeyBindings[Modifier][KeyID]); } - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); } int CBinds::GetModifierMask(IInput *i) @@ -274,7 +287,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "key %s not found", pBindStr); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); return; } @@ -294,7 +307,7 @@ if(!id) { str_format(aBuf, sizeof(aBuf), "key '%s' not found", pKeyName); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); } else { @@ -303,7 +316,7 @@ else str_format(aBuf, sizeof(aBuf), "%s (%d) = %s", pKeyName, id, pBinds->m_aapKeyBindings[Modifier][id]); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); } } else if(pResult->NumArguments() == 0) @@ -317,7 +330,7 @@ continue; str_format(aBuf, sizeof(aBuf), "%s%s (%d) = %s", GetKeyBindModifiersName(i), pBinds->Input()->KeyName(j), j, pBinds->m_aapKeyBindings[i][j]); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); } } } @@ -334,7 +347,7 @@ { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "key %s not found", pKeyName); - pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf); + pBinds->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "binds", aBuf, gs_BindPrintColor); return; } @@ -469,8 +482,6 @@ Bind(KEY_S, "+showhookcoll", FreeOnly); Bind(KEY_X, "toggle cl_dummy 0 1", FreeOnly); Bind(KEY_H, "toggle cl_dummy_hammer 0 1", FreeOnly); - Bind(KEY_UP, "bind mouse1 \"+fire; +toggle cl_dummy_hammer 1 0\"", FreeOnly); - Bind(KEY_DOWN, "bind mouse1 \"+fire\"", FreeOnly); Bind(KEY_SLASH, "+show_chat; chat all /", FreeOnly); Bind(KEY_PAGEDOWN, "toggle cl_show_quads 0 1", FreeOnly); Bind(KEY_PAGEUP, "toggle cl_overlay_entities 0 100", FreeOnly); diff -Nru ddnet-15.3.2/src/game/client/components/broadcast.cpp ddnet-15.5.4/src/game/client/components/broadcast.cpp --- ddnet-15.3.2/src/game/client/components/broadcast.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/broadcast.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -57,7 +57,7 @@ aBuf[ii] = '\0'; ii = 0; if(aBuf[0]) - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, true); + m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor))); } else { @@ -67,7 +67,7 @@ } aBuf[ii] = '\0'; if(aBuf[0]) - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, true); + m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "broadcast", aBuf, color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor))); } } } diff -Nru ddnet-15.3.2/src/game/client/components/chat.cpp ddnet-15.5.4/src/game/client/components/chat.cpp --- ddnet-15.3.2/src/game/client/components/chat.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/chat.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -663,11 +663,44 @@ bool Highlighted = false; char *p = const_cast(pLine); + bool IsTeamLine = Team == 1; + bool IsWhisperLine = Team >= 2; + // Only empty string left if(*p == 0) return; - bool IgnoreLine = false; + auto &&FChatMsgCheckAndPrint = [=](CLine *pLine) { + if(pLine->m_ClientID < 0) // server or client message + { + if(Client()->State() != IClient::STATE_DEMOPLAYBACK) + StoreSave(pLine->m_aText); + } + + char aBuf[1024]; + str_format(aBuf, sizeof(aBuf), "%s%s%s", pLine->m_aName, pLine->m_ClientID >= 0 ? ": " : "", pLine->m_aText); + + ColorRGBA ChatLogColor{1, 1, 1, 1}; + if(pLine->m_Highlighted) + { + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor)); + } + else + { + if(pLine->m_Friend && g_Config.m_ClMessageFriend) + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageFriendColor)); + else if(pLine->m_Team) + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageTeamColor)); + else if(pLine->m_ClientID == -1) // system + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageSystemColor)); + else if(pLine->m_ClientID == -2) // client + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageClientColor)); + else // regular message + ChatLogColor = color_cast(ColorHSLA(g_Config.m_ClMessageColor)); + } + + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, pLine->m_Whisper ? "whisper" : (pLine->m_Team ? "teamchat" : "chat"), aBuf, ChatLogColor); + }; while(*p) { @@ -686,7 +719,7 @@ CLine *pCurrentLine = &m_aLines[m_CurrentLine]; // If it's a client message, m_aText will have ": " prepended so we have to work around it. - if(pCurrentLine->m_Team == Team && pCurrentLine->m_ClientID == ClientID && str_comp(pCurrentLine->m_aText, pLine) == 0) + if(pCurrentLine->m_Team == IsTeamLine && pCurrentLine->m_Whisper == IsWhisperLine && pCurrentLine->m_ClientID == ClientID && str_comp(pCurrentLine->m_aText, pLine) == 0) { pCurrentLine->m_TimesRepeated++; if(pCurrentLine->m_TextContainerIndex != -1) @@ -699,11 +732,9 @@ pCurrentLine->m_Time = time(); pCurrentLine->m_YOffset[0] = -1.f; pCurrentLine->m_YOffset[1] = -1.f; - // Can't return here because we still want to log the message to console, - // even if we ignore it in chat. We will set the new line, fill it out - // totally, but then in the end revert back m_CurrentLine after writing - // the message to console. - IgnoreLine = true; + + FChatMsgCheckAndPrint(pCurrentLine); + return; } m_CurrentLine = (m_CurrentLine + 1) % MAX_LINES; @@ -714,7 +745,8 @@ pCurrentLine->m_YOffset[0] = -1.0f; pCurrentLine->m_YOffset[1] = -1.0f; pCurrentLine->m_ClientID = ClientID; - pCurrentLine->m_Team = Team; + pCurrentLine->m_Team = IsTeamLine; + pCurrentLine->m_Whisper = IsWhisperLine; pCurrentLine->m_NameColor = -2; if(pCurrentLine->m_TextContainerIndex != -1) @@ -748,13 +780,10 @@ pCurrentLine->m_Highlighted = Highlighted; - if(ClientID < 0) // server or client message + if(pCurrentLine->m_ClientID < 0) // server or client message { str_copy(pCurrentLine->m_aName, "*** ", sizeof(pCurrentLine->m_aName)); str_format(pCurrentLine->m_aText, sizeof(pCurrentLine->m_aText), "%s", pLine); - - if(Client()->State() != IClient::STATE_DEMOPLAYBACK) - StoreSave(pCurrentLine->m_aText); } else { @@ -774,7 +803,6 @@ str_format(pCurrentLine->m_aName, sizeof(pCurrentLine->m_aName), "→ %s", m_pClient->m_aClients[ClientID].m_aName); pCurrentLine->m_NameColor = TEAM_BLUE; pCurrentLine->m_Highlighted = false; - pCurrentLine->m_Team = 0; Highlighted = false; } else if(Team == 3) // whisper recv @@ -782,7 +810,6 @@ str_format(pCurrentLine->m_aName, sizeof(pCurrentLine->m_aName), "← %s", m_pClient->m_aClients[ClientID].m_aName); pCurrentLine->m_NameColor = TEAM_RED; pCurrentLine->m_Highlighted = true; - pCurrentLine->m_Team = 0; Highlighted = true; } else @@ -815,15 +842,7 @@ } } - char aBuf[1024]; - str_format(aBuf, sizeof(aBuf), "%s: %s", pCurrentLine->m_aName, pCurrentLine->m_aText); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, Team >= 2 ? "whisper" : (pCurrentLine->m_Team ? "teamchat" : "chat"), aBuf, Highlighted); - } - - if(IgnoreLine) - { - m_CurrentLine = (m_CurrentLine + MAX_LINES - 1) % MAX_LINES; - return; + FChatMsgCheckAndPrint(pCurrentLine); } // play sound @@ -877,21 +896,17 @@ void CChat::RefindSkins() { - for(int i = 0; i < MAX_LINES; i++) + for(auto &Line : m_aLines) { - int r = ((m_CurrentLine - i) + MAX_LINES) % MAX_LINES; - if(m_aLines[r].m_TextContainerIndex == -1) - continue; - - if(m_aLines[r].m_HasRenderTee) + if(Line.m_HasRenderTee) { - const CSkin *pSkin = m_pClient->m_pSkins->Get(m_pClient->m_pSkins->Find(m_aLines[r].m_aSkinName)); - if(m_aLines[r].m_CustomColoredSkin) - m_aLines[r].m_RenderSkin = pSkin->m_ColorableSkin; + const CSkin *pSkin = m_pClient->m_pSkins->Get(m_pClient->m_pSkins->Find(Line.m_aSkinName)); + if(Line.m_CustomColoredSkin) + Line.m_RenderSkin = pSkin->m_ColorableSkin; else - m_aLines[r].m_RenderSkin = pSkin->m_OriginalSkin; + Line.m_RenderSkin = pSkin->m_OriginalSkin; - m_aLines[r].m_RenderSkinMetrics = pSkin->m_Metrics; + Line.m_RenderSkinMetrics = pSkin->m_Metrics; } } } diff -Nru ddnet-15.3.2/src/game/client/components/chat.h ddnet-15.5.4/src/game/client/components/chat.h --- ddnet-15.3.2/src/game/client/components/chat.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/chat.h 2021-06-20 09:38:48.000000000 +0000 @@ -25,7 +25,8 @@ int64 m_Time; float m_YOffset[2]; int m_ClientID; - int m_Team; + bool m_Team; + bool m_Whisper; int m_NameColor; char m_aName[64]; char m_aText[512]; diff -Nru ddnet-15.3.2/src/game/client/components/console.cpp ddnet-15.5.4/src/game/client/components/console.cpp --- ddnet-15.3.2/src/game/client/components/console.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/console.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -337,7 +337,7 @@ } } -void CGameConsole::CInstance::PrintLine(const char *pLine, bool Highlighted) +void CGameConsole::CInstance::PrintLine(const char *pLine, ColorRGBA PrintColor) { int Len = str_length(pLine); @@ -346,7 +346,7 @@ CBacklogEntry *pEntry = m_Backlog.Allocate(sizeof(CBacklogEntry) + Len); pEntry->m_YOffset = -1.0f; - pEntry->m_Highlighted = Highlighted; + pEntry->m_PrintColor = PrintColor; mem_copy(pEntry->m_aText, pLine, Len); pEntry->m_aText[Len] = 0; } @@ -639,8 +639,6 @@ } } - ColorRGBA rgb = color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor)); - // render log (actual page, wrap lines) CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last(); float OffsetY = 0.0f; @@ -650,10 +648,7 @@ { while(pEntry) { - if(pEntry->m_Highlighted) - TextRender()->TextColor(rgb); - else - TextRender()->TextColor(1, 1, 1, 1); + TextRender()->TextColor(pEntry->m_PrintColor); // get y offset (calculate it if we haven't yet) if(pEntry->m_YOffset < 0.0f) @@ -826,9 +821,9 @@ ((CGameConsole *)pUserData)->Dump(CONSOLETYPE_REMOTE); } -void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData, bool Highlighted) +void CGameConsole::ClientConsolePrintCallback(const char *pStr, void *pUserData, ColorRGBA PrintColor) { - ((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr, Highlighted); + ((CGameConsole *)pUserData)->m_LocalConsole.PrintLine(pStr, PrintColor); } void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData) diff -Nru ddnet-15.3.2/src/game/client/components/console.h ddnet-15.5.4/src/game/client/components/console.h --- ddnet-15.3.2/src/game/client/components/console.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/console.h 2021-06-20 09:38:48.000000000 +0000 @@ -22,7 +22,7 @@ struct CBacklogEntry { float m_YOffset; - bool m_Highlighted; + ColorRGBA m_PrintColor; char m_aText[1]; }; CStaticRingBuffer m_Backlog; @@ -61,7 +61,7 @@ void ExecuteLine(const char *pLine); void OnInput(IInput::CEvent Event); - void PrintLine(const char *pLine, bool Highlighted = false); + void PrintLine(const char *pLine, ColorRGBA PrintColor = {1, 1, 1, 1}); const char *GetString() const { return m_Input.GetString(); } static void PossibleCommandsCompleteCallback(const char *pStr, void *pUser); @@ -85,7 +85,7 @@ void Dump(int Type); static void PossibleCommandsRenderCallback(const char *pStr, void *pUser); - static void ClientConsolePrintCallback(const char *pStr, void *pUserData, bool Highlighted); + static void ClientConsolePrintCallback(const char *pStr, void *pUserData, ColorRGBA PrintColor = {1, 1, 1, 1}); static void ConToggleLocalConsole(IConsole::IResult *pResult, void *pUserData); static void ConToggleRemoteConsole(IConsole::IResult *pResult, void *pUserData); static void ConClearLocalConsole(IConsole::IResult *pResult, void *pUserData); diff -Nru ddnet-15.3.2/src/game/client/components/controls.cpp ddnet-15.5.4/src/game/client/components/controls.cpp --- ddnet-15.3.2/src/game/client/components/controls.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/controls.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -17,6 +17,8 @@ #include #include +#include + #include "controls.h" enum @@ -198,10 +200,6 @@ static CInputState s_State = {this, &m_ShowHookColl[0], &m_ShowHookColl[1]}; Console()->Register("+showhookcoll", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Show Hook Collision"); } - { - static CInputState s_State = {this, &m_ResetDummy[0], &m_ResetDummy[1]}; - Console()->Register("+resetdummy", "", CFGFLAG_CLIENT, ConKeyInputState, (void *)&s_State, "Reset Dummy"); - } { static CInputSet s_Set = {this, &m_InputData[0].m_WantedWeapon, &m_InputData[1].m_WantedWeapon, 1}; @@ -341,19 +339,6 @@ pDummyInput->m_Hook = g_Config.m_ClDummyHook; } - if(m_ResetDummy[g_Config.m_ClDummy]) - { - ResetInput(!g_Config.m_ClDummy); - m_InputData[!g_Config.m_ClDummy].m_Hook = 0; - - CNetObj_PlayerInput *pDummyInput = &m_pClient->m_DummyInput; - pDummyInput->m_Hook = m_InputData[!g_Config.m_ClDummy].m_Hook; - pDummyInput->m_Jump = m_InputData[!g_Config.m_ClDummy].m_Jump; - pDummyInput->m_Direction = m_InputData[!g_Config.m_ClDummy].m_Jump; - - pDummyInput->m_Fire = m_InputData[!g_Config.m_ClDummy].m_Fire; - } - // stress testing #ifdef CONF_DEBUG if(g_Config.m_DbgStress) @@ -589,9 +574,17 @@ float MinDistance = g_Config.m_ClDyncam ? g_Config.m_ClDyncamMinDistance : g_Config.m_ClMouseMinDistance; float MouseMin = MinDistance; - if(length(m_MousePos[g_Config.m_ClDummy]) < MouseMin) - m_MousePos[g_Config.m_ClDummy] = normalize(m_MousePos[g_Config.m_ClDummy]) * MouseMin; - if(length(m_MousePos[g_Config.m_ClDummy]) > MouseMax) - m_MousePos[g_Config.m_ClDummy] = normalize(m_MousePos[g_Config.m_ClDummy]) * MouseMax; + float MDistance = length(m_MousePos[g_Config.m_ClDummy]); + if(MDistance < 0.001f) + { + m_MousePos[g_Config.m_ClDummy].x = 0.001f; + m_MousePos[g_Config.m_ClDummy].y = 0; + MDistance = 0.001f; + } + if(MDistance < MouseMin) + m_MousePos[g_Config.m_ClDummy] = normalize_pre_length(m_MousePos[g_Config.m_ClDummy], MDistance) * MouseMin; + MDistance = length(m_MousePos[g_Config.m_ClDummy]); + if(MDistance > MouseMax) + m_MousePos[g_Config.m_ClDummy] = normalize_pre_length(m_MousePos[g_Config.m_ClDummy], MDistance) * MouseMax; } } diff -Nru ddnet-15.3.2/src/game/client/components/controls.h ddnet-15.5.4/src/game/client/components/controls.h --- ddnet-15.3.2/src/game/client/components/controls.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/controls.h 2021-06-20 09:38:48.000000000 +0000 @@ -29,7 +29,6 @@ int m_InputDirectionLeft[NUM_DUMMIES]; int m_InputDirectionRight[NUM_DUMMIES]; int m_ShowHookColl[NUM_DUMMIES]; - int m_ResetDummy[NUM_DUMMIES]; int m_LastDummy; int m_OtherFire; diff -Nru ddnet-15.3.2/src/game/client/components/hud.cpp ddnet-15.5.4/src/game/client/components/hud.cpp --- ddnet-15.3.2/src/game/client/components/hud.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/hud.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -173,6 +173,8 @@ float Whole = 300 * Graphics()->ScreenAspect(); float StartY = 229.0f; + const float ScoreSingleBoxHeight = 18.0f; + bool ForceScoreInfoInit = !m_aScoreInfo[0].m_Initialized || !m_aScoreInfo[1].m_Initialized; m_aScoreInfo[0].m_Initialized = m_aScoreInfo[1].m_Initialized = true; @@ -215,7 +217,7 @@ Graphics()->SetColor(1.0f, 0.0f, 0.0f, 0.25f); else Graphics()->SetColor(0.0f, 0.0f, 1.0f, 0.25f); - m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split, 18.0f, 5.0f, CUI::CORNER_L); + m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); } Graphics()->TextureClear(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -282,10 +284,15 @@ } // draw tee of the flag holder - CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo; - Info.m_Size = 18.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1, 0), - vec2(Whole - ScoreWidthMax - Info.m_Size / 2 - Split, StartY + 1.0f + Info.m_Size / 2 + t * 20)); + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[ID].m_RenderInfo; + TeeInfo.m_Size = ScoreSingleBoxHeight; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(Whole - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } } StartY += 8.0f; @@ -391,7 +398,7 @@ Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f); else Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.25f); - m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split - PosSize, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split + PosSize, 18.0f, 5.0f, CUI::CORNER_L); + m_aScoreInfo[t].m_RoundRectQuadContainerIndex = RenderTools()->CreateRoundRectQuadContainer(Whole - ScoreWidthMax - ImageSize - 2 * Split - PosSize, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split + PosSize, ScoreSingleBoxHeight, 5.0f, CUI::CORNER_L); } Graphics()->TextureClear(); Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -446,10 +453,15 @@ } // draw tee - CTeeRenderInfo Info = m_pClient->m_aClients[ID].m_RenderInfo; - Info.m_Size = 18.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1, 0), - vec2(Whole - ScoreWidthMax - Info.m_Size / 2 - Split, StartY + 1.0f + Info.m_Size / 2 + t * 20)); + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[ID].m_RenderInfo; + TeeInfo.m_Size = ScoreSingleBoxHeight; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(Whole - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } } else @@ -859,7 +871,7 @@ void CHud::OnMessage(int MsgType, void *pRawMsg) { - if(MsgType == NETMSGTYPE_SV_DDRACETIME) + if(MsgType == NETMSGTYPE_SV_DDRACETIME || MsgType == NETMSGTYPE_SV_DDRACETIMELEGACY) { m_DDRaceTimeReceived = true; @@ -877,12 +889,12 @@ m_CheckpointTick = Client()->GameTick(g_Config.m_ClDummy); } } - else if(MsgType == NETMSGTYPE_SV_RECORD) + else if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY) { CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; // NETMSGTYPE_SV_RACETIME on old race servers - if(GameClient()->m_GameInfo.m_DDRaceRecordMessage) + if(MsgType == NETMSGTYPE_SV_RECORDLEGACY && GameClient()->m_GameInfo.m_DDRaceRecordMessage) { m_DDRaceTimeReceived = true; @@ -896,7 +908,7 @@ m_CheckpointTick = Client()->GameTick(g_Config.m_ClDummy); } } - else if(GameClient()->m_GameInfo.m_RaceRecordMessage) + else if(MsgType == NETMSGTYPE_SV_RECORD || GameClient()->m_GameInfo.m_RaceRecordMessage) { m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100; m_PlayerRecord[g_Config.m_ClDummy] = (float)pMsg->m_PlayerTimeBest / 100; diff -Nru ddnet-15.3.2/src/game/client/components/items.cpp ddnet-15.5.4/src/game/client/components/items.cpp --- ddnet-15.3.2/src/game/client/components/items.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/items.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -8,9 +8,9 @@ #include #include +#include #include #include -#include #include #include @@ -22,27 +22,28 @@ m_NumExtraProjectiles = 0; } -void CItems::RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID) +void CItems::RenderProjectile(const CProjectileData *pCurrent, int ItemID) { int CurWeapon = clamp(pCurrent->m_Type, 0, NUM_WEAPONS - 1); // get positions float Curvature = 0; float Speed = 0; + CTuningParams Tuning = GameClient()->GetTunes(pCurrent->m_TuneZone); if(CurWeapon == WEAPON_GRENADE) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; + Curvature = Tuning.m_GrenadeCurvature; + Speed = Tuning.m_GrenadeSpeed; } else if(CurWeapon == WEAPON_SHOTGUN) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; + Curvature = Tuning.m_ShotgunCurvature; + Speed = Tuning.m_ShotgunSpeed; } else if(CurWeapon == WEAPON_GUN) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; + Curvature = Tuning.m_GunCurvature; + Speed = Tuning.m_GunSpeed; } bool LocalPlayerInGame = false; @@ -54,29 +55,23 @@ if(m_pClient->m_Snap.m_pGameInfoObj && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED)) s_LastGameTickTime = Client()->GameTickTime(g_Config.m_ClDummy); + bool IsOtherTeam = (pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(pCurrent->m_Owner)); + float Ct; - if(m_pClient->Predict() && m_pClient->AntiPingGrenade() && LocalPlayerInGame && !(Client()->State() == IClient::STATE_DEMOPLAYBACK)) + if(m_pClient->Predict() && m_pClient->AntiPingGrenade() && LocalPlayerInGame && !IsOtherTeam) Ct = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - 1 - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)SERVER_TICK_SPEED; else Ct = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)SERVER_TICK_SPEED + s_LastGameTickTime; if(Ct < 0) return; // projectile haven't been shot yet - vec2 StartPos; - vec2 StartVel; - - ExtractInfo(pCurrent, &StartPos, &StartVel); - - vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct); - vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, Ct - 0.001f); + vec2 Pos = CalcPos(pCurrent->m_StartPos, pCurrent->m_StartVel, Curvature, Speed, Ct); + vec2 PrevPos = CalcPos(pCurrent->m_StartPos, pCurrent->m_StartVel, Curvature, Speed, Ct - 0.001f); float Alpha = 1.f; - if(UseExtraInfo(pCurrent)) + if(IsOtherTeam) { - int Owner; - ExtractExtraInfo(pCurrent, &Owner, 0, 0, 0); - if(Owner >= 0 && m_pClient->IsOtherTeam(Owner)) - Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; + Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; } vec2 Vel = Pos - PrevPos; @@ -241,6 +236,8 @@ RGB = color_cast(ColorHSLA(g_Config.m_ClLaserInnerColor)); ColorRGBA InnerColor(RGB.r, RGB.g, RGB.b, 1.0f); + int TuneZone = GameClient()->m_GameWorld.m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(From)) : 0; + vec2 Dir; if(Len > 0) { @@ -252,7 +249,7 @@ else Ticks = (float)(Client()->GameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->IntraGameTick(g_Config.m_ClDummy); float Ms = (Ticks / 50.0f) * 1000.0f; - float a = Ms / m_pClient->m_Tuning[g_Config.m_ClDummy].m_LaserBounceDelay; + float a = Ms / m_pClient->GetTunes(TuneZone).m_LaserBounceDelay; a = clamp(a, 0.0f, 1.0f); float Ia = 1 - a; @@ -310,11 +307,7 @@ { for(auto *pProj = (CProjectile *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->NextEntity()) { - CNetObj_Projectile Data; - if(pProj->m_Type != WEAPON_SHOTGUN || pProj->m_Explosive || pProj->m_Freeze) - pProj->FillExtraInfo(&Data); - else - pProj->FillInfo(&Data); + CProjectileData Data = pProj->GetData(); RenderProjectile(&Data, pProj->ID()); } for(auto *pLaser = (CLaser *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_LASER); pLaser; pLaser = (CLaser *)pLaser->NextEntity()) @@ -327,12 +320,15 @@ } for(auto *pPickup = (CPickup *)GameClient()->m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PICKUP); pPickup; pPickup = (CPickup *)pPickup->NextEntity()) { - if(auto *pPrev = (CPickup *)GameClient()->m_PrevPredictedWorld.GetEntity(pPickup->ID(), CGameWorld::ENTTYPE_PICKUP)) + if(pPickup->InDDNetTile()) { - CNetObj_Pickup Data, Prev; - pPickup->FillInfo(&Data); - pPrev->FillInfo(&Prev); - RenderPickup(&Prev, &Data, true); + if(auto *pPrev = (CPickup *)GameClient()->m_PrevPredictedWorld.GetEntity(pPickup->ID(), CGameWorld::ENTTYPE_PICKUP)) + { + CNetObj_Pickup Data, Prev; + pPickup->FillInfo(&Data); + pPrev->FillInfo(&Prev); + RenderPickup(&Prev, &Data, true); + } } } } @@ -343,29 +339,44 @@ IClient::CSnapItem Item; const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item); - if(Item.m_Type == NETOBJTYPE_PROJECTILE) + if(Item.m_Type == NETOBJTYPE_PROJECTILE || Item.m_Type == NETOBJTYPE_DDNETPROJECTILE) { + CProjectileData Data; + if(Item.m_Type == NETOBJTYPE_PROJECTILE) + { + Data = ExtractProjectileInfo((const CNetObj_Projectile *)pData, &GameClient()->m_GameWorld); + } + else + { + Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pData, &GameClient()->m_GameWorld); + } if(UsePredicted) { if(auto *pProj = (CProjectile *)GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData)) { + bool IsOtherTeam = m_pClient->IsOtherTeam(pProj->GetOwner()); if(pProj->m_LastRenderTick <= 0 && (pProj->m_Type != WEAPON_SHOTGUN || (!pProj->m_Freeze && !pProj->m_Explosive)) // skip ddrace shotgun bullets && (pProj->m_Type == WEAPON_SHOTGUN || fabs(length(pProj->m_Direction) - 1.f) < 0.02) // workaround to skip grenades on ball mod - && (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal) // skip locally predicted projectiles + && (pProj->GetOwner() < 0 || !GameClient()->m_aClients[pProj->GetOwner()].m_IsPredictedLocal || IsOtherTeam) // skip locally predicted projectiles && !Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID)) { - ReconstructSmokeTrail((const CNetObj_Projectile *)pData, Item.m_ID, pProj->m_DestroyTick); + ReconstructSmokeTrail(&Data, pProj->m_DestroyTick); } pProj->m_LastRenderTick = Client()->GameTick(g_Config.m_ClDummy); - continue; + if(!IsOtherTeam) + continue; } } - RenderProjectile((const CNetObj_Projectile *)pData, Item.m_ID); + RenderProjectile(&Data, Item.m_ID); } else if(Item.m_Type == NETOBJTYPE_PICKUP) { if(UsePredicted) - continue; + { + auto *pPickup = (CPickup *)GameClient()->m_GameWorld.FindMatch(Item.m_ID, Item.m_Type, pData); + if(pPickup && pPickup->InDDNetTile()) + continue; + } const void *pPrev = Client()->SnapFindItem(IClient::SNAP_PREV, Item.m_Type, Item.m_ID); if(pPrev) RenderPickup((const CNetObj_Pickup *)pPrev, (const CNetObj_Pickup *)pData); @@ -409,7 +420,10 @@ m_NumExtraProjectiles--; } else if(!UsePredicted) - RenderProjectile(&m_aExtraProjectiles[i], 0); + { + CProjectileData Data = ExtractProjectileInfo(&m_aExtraProjectiles[i], &GameClient()->m_GameWorld); + RenderProjectile(&Data, 0); + } } Graphics()->QuadsSetRotation(0); @@ -484,7 +498,7 @@ } } -void CItems::ReconstructSmokeTrail(const CNetObj_Projectile *pCurrent, int ItemID, int DestroyTick) +void CItems::ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyTick) { bool LocalPlayerInGame = false; @@ -498,20 +512,22 @@ // get positions float Curvature = 0; float Speed = 0; + CTuningParams Tuning = GameClient()->GetTunes(pCurrent->m_TuneZone); + if(pCurrent->m_Type == WEAPON_GRENADE) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GrenadeSpeed; + Curvature = Tuning.m_GrenadeCurvature; + Speed = Tuning.m_GrenadeSpeed; } else if(pCurrent->m_Type == WEAPON_SHOTGUN) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_ShotgunSpeed; + Curvature = Tuning.m_ShotgunCurvature; + Speed = Tuning.m_ShotgunSpeed; } else if(pCurrent->m_Type == WEAPON_GUN) { - Curvature = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunCurvature; - Speed = m_pClient->m_Tuning[g_Config.m_ClDummy].m_GunSpeed; + Curvature = Tuning.m_GunCurvature; + Speed = Tuning.m_GunSpeed; } float Pt = ((float)(Client()->PredGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) + Client()->PredIntraGameTick(g_Config.m_ClDummy)) / (float)SERVER_TICK_SPEED; @@ -520,18 +536,10 @@ float Gt = (Client()->PrevGameTick(g_Config.m_ClDummy) - pCurrent->m_StartTick) / (float)SERVER_TICK_SPEED + Client()->GameTickTime(g_Config.m_ClDummy); - vec2 StartPos; - vec2 StartVel; - - ExtractInfo(pCurrent, &StartPos, &StartVel); - float Alpha = 1.f; - if(UseExtraInfo(pCurrent)) + if(pCurrent->m_ExtraInfo && pCurrent->m_Owner >= 0 && m_pClient->IsOtherTeam(pCurrent->m_Owner)) { - int Owner; - ExtractExtraInfo(pCurrent, &Owner, 0, 0, 0); - if(Owner >= 0 && m_pClient->IsOtherTeam(Owner)) - Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; + Alpha = g_Config.m_ClShowOthersAlpha / 100.0f; } float T = Pt; @@ -543,8 +551,8 @@ for(int i = 1 + (int)(Gt / Step); i < (int)(T / Step); i++) { float t = Step * (float)i + 0.4f * Step * (frandom() - 0.5f); - vec2 Pos = CalcPos(StartPos, StartVel, Curvature, Speed, t); - vec2 PrevPos = CalcPos(StartPos, StartVel, Curvature, Speed, t - 0.001f); + vec2 Pos = CalcPos(pCurrent->m_StartPos, pCurrent->m_StartVel, Curvature, Speed, t); + vec2 PrevPos = CalcPos(pCurrent->m_StartPos, pCurrent->m_StartVel, Curvature, Speed, t - 0.001f); vec2 Vel = Pos - PrevPos; float TimePassed = Pt - t; if(Pt - MinTrailSpan > 0.01f) diff -Nru ddnet-15.3.2/src/game/client/components/items.h ddnet-15.5.4/src/game/client/components/items.h --- ddnet-15.3.2/src/game/client/components/items.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/items.h 2021-06-20 09:38:48.000000000 +0000 @@ -4,6 +4,8 @@ #define GAME_CLIENT_COMPONENTS_ITEMS_H #include +class CProjectileData; + class CItems : public CComponent { enum @@ -14,7 +16,7 @@ CNetObj_Projectile m_aExtraProjectiles[MAX_EXTRA_PROJECTILES]; int m_NumExtraProjectiles; - void RenderProjectile(const CNetObj_Projectile *pCurrent, int ItemID); + void RenderProjectile(const CProjectileData *pCurrent, int ItemID); void RenderPickup(const CNetObj_Pickup *pPrev, const CNetObj_Pickup *pCurrent, bool IsPredicted = false); void RenderFlag(const CNetObj_Flag *pPrev, const CNetObj_Flag *pCurrent, const CNetObj_GameData *pPrevGameData, const CNetObj_GameData *pCurGameData); void RenderLaser(const struct CNetObj_Laser *pCurrent, bool IsPredicted = false); @@ -28,7 +30,7 @@ void AddExtraProjectile(CNetObj_Projectile *pProj); - void ReconstructSmokeTrail(const CNetObj_Projectile *pCurrent, int ItemID, int DestroyTick); + void ReconstructSmokeTrail(const CProjectileData *pCurrent, int DestroyTick); }; #endif diff -Nru ddnet-15.3.2/src/game/client/components/killmessages.cpp ddnet-15.5.4/src/game/client/components/killmessages.cpp --- ddnet-15.3.2/src/game/client/components/killmessages.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/killmessages.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -221,7 +221,17 @@ } if(m_aKillmsgs[r].m_VictimID >= 0) - RenderTools()->RenderTee(CAnimState::GetIdle(), &m_aKillmsgs[r].m_VictimRenderInfo, EMOTE_PAIN, vec2(-1, 0), vec2(x, y + 28)); + { + CTeeRenderInfo TeeInfo = m_aKillmsgs[r].m_VictimRenderInfo; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(x, y + 46.0f / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_PAIN, vec2(-1, 0), TeeRenderPos); + } + x -= 32.0f; // render weapon @@ -254,8 +264,19 @@ // render killer tee x -= 24.0f; + if(m_aKillmsgs[r].m_KillerID >= 0) - RenderTools()->RenderTee(CAnimState::GetIdle(), &m_aKillmsgs[r].m_KillerRenderInfo, EMOTE_ANGRY, vec2(1, 0), vec2(x, y + 28)); + { + CTeeRenderInfo TeeInfo = m_aKillmsgs[r].m_KillerRenderInfo; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(x, y + 46.0f / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_ANGRY, vec2(1, 0), TeeRenderPos); + } + x -= 32.0f; // render killer name diff -Nru ddnet-15.3.2/src/game/client/components/menus_browser.cpp ddnet-15.5.4/src/game/client/components/menus_browser.cpp --- ddnet-15.3.2/src/game/client/components/menus_browser.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_browser.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -31,6 +31,27 @@ static const int g_OffsetColPing = g_OffsetColPlayers + 3; static const int g_OffsetColVersion = g_OffsetColPing + 3; +void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo *pInfo) +{ + if(!pInfo->m_LatencyIsEstimated) + { + str_format(pBuffer, BufferLength, "%d", pInfo->m_Latency); + return; + } + static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = { + "", // LOC_UNKNOWN + "AFR", // LOC_AFRICA // Localize("AFR") + "ASI", // LOC_ASIA // Localize("ASI") + "AUS", // LOC_AUSTRALIA // Localize("AUS") + "EUR", // LOC_EUROPE // Localize("EUR") + "NA", // LOC_NORTH_AMERICA // Localize("NA") + "SA", // LOC_SOUTH_AMERICA // Localize("SA") + "CHN", // LOC_CHINA // Localize("CHN") + }; + dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range"); + str_format(pBuffer, BufferLength, "%s", Localize(LOCATION_NAMES[pInfo->m_Location])); +} + void CMenus::RenderServerbrowserServerList(CUIRect View) { CUIRect Headers; @@ -157,8 +178,8 @@ { CUIRect MsgBox = View; - if(m_ActivePage == PAGE_INTERNET && ServerBrowser()->IsRefreshingMasters()) - UI()->DoLabelScaled(&MsgBox, Localize("Refreshing master servers"), 16.0f, 0); + if(ServerBrowser()->IsGettingServerlist()) + UI()->DoLabelScaled(&MsgBox, Localize("Getting server list from master server"), 16.0f, 0); else if(!ServerBrowser()->NumServers()) UI()->DoLabelScaled(&MsgBox, Localize("No servers found"), 16.0f, 0); else if(ServerBrowser()->NumServers() && !NumServers) @@ -405,7 +426,7 @@ } else if(ID == COL_PING) { - str_format(aTemp, sizeof(aTemp), "%i", pItem->m_Latency); + FormatServerbrowserPing(aTemp, sizeof(aTemp), pItem); if(g_Config.m_UiColorizePing) { ColorRGBA rgb = color_cast(ColorHSLA((300.0f - clamp(pItem->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f)); @@ -606,13 +627,13 @@ ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); else if(g_Config.m_UiPage == PAGE_DDNET) { - // start a new serverlist request + // start a new server list request Client()->RequestDDNetInfo(); ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); } else if(g_Config.m_UiPage == PAGE_KOG) { - // start a new serverlist request + // start a new server list request Client()->RequestDDNetInfo(); ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); } @@ -1017,14 +1038,34 @@ { CUIRect Button; ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button); - Button.VSplitLeft(5.0f, 0, &Button); + CUIRect ButtonAddFav; + CUIRect ButtonLeakIp; + Button.VSplitMid(&ButtonAddFav, &ButtonLeakIp); + ButtonAddFav.VSplitLeft(5.0f, 0, &ButtonAddFav); static int s_AddFavButton = 0; - if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &Button)) + static int s_LeakIpButton = 0; + if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &ButtonAddFav)) { if(pSelectedServer->m_Favorite) + { ServerBrowser()->RemoveFavorite(pSelectedServer->m_NetAddr); + } else + { ServerBrowser()->AddFavorite(pSelectedServer->m_NetAddr); + if(g_Config.m_UiPage == PAGE_LAN) + { + ServerBrowser()->FavoriteAllowPing(pSelectedServer->m_NetAddr, true); + } + } + } + if(pSelectedServer->m_Favorite) + { + bool IpLeak = ServerBrowser()->IsFavoritePingAllowed(pSelectedServer->m_NetAddr); + if(DoButton_CheckBox(&s_LeakIpButton, Localize("Leak IP"), IpLeak, &ButtonLeakIp)) + { + ServerBrowser()->FavoriteAllowPing(pSelectedServer->m_NetAddr, !IpLeak); + } } } @@ -1048,7 +1089,7 @@ TextRender()->TextEx(&Cursor, pSelectedServer->m_aGameType, -1); char aTemp[16]; - str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_Latency); + FormatServerbrowserPing(aTemp, sizeof(aTemp), pSelectedServer); RightColumn.HSplitTop(15.0f, &Row, &RightColumn); TextRender()->SetCursor(&Cursor, Row.x, Row.y + (15.f - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Row.w; @@ -1066,14 +1107,14 @@ for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++) { - CListboxItem Item = UiDoListboxNextItem(&i); + CListboxItem Item = UiDoListboxNextItem(&pSelectedServer->m_aClients[i]); if(!Item.m_Visible) continue; CUIRect Name, Clan, Score, Flag; Item.m_Rect.HSplitTop(25.0f, &Name, &Item.m_Rect); - if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name)) + if(UiLogicGetCurrentClickedItem() == i) { if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER) m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan); diff -Nru ddnet-15.3.2/src/game/client/components/menus.cpp ddnet-15.5.4/src/game/client/components/menus.cpp --- ddnet-15.3.2/src/game/client/components/menus.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -610,251 +610,7 @@ int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) { - int Inside = UI()->MouseInside(pRect); - bool ReturnValue = false; - bool UpdateOffset = false; - static int s_AtIndex = 0; - static bool s_DoScroll = false; - static float s_ScrollStart = 0.0f; - - FontSize *= UI()->Scale(); - - if(UI()->LastActiveItem() == pID) - { - int Len = str_length(pStr); - if(Len == 0) - s_AtIndex = 0; - - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V)) - { - const char *Text = Input()->GetClipboardText(); - if(Text) - { - int Offset = str_length(pStr); - int CharsLeft = StrSize - Offset; - char *pCur = pStr + Offset; - str_utf8_copy(pCur, Text, CharsLeft); - for(int i = 0; i < CharsLeft; i++) - { - if(pCur[i] == 0) - break; - else if(pCur[i] == '\r') - pCur[i] = ' '; - else if(pCur[i] == '\n') - pCur[i] = ' '; - } - s_AtIndex = str_length(pStr); - ReturnValue = true; - } - } - - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C)) - { - Input()->SetClipboardText(pStr); - } - - /* TODO: Doesn't work, SetClipboardText doesn't retain the string quickly enough? - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_X)) - { - Input()->SetClipboardText(pStr); - pStr[0] = '\0'; - s_AtIndex = 0; - ReturnValue = true; - } - */ - - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_U)) - { - pStr[0] = '\0'; - s_AtIndex = 0; - ReturnValue = true; - } - - if(Inside && UI()->MouseButton(0)) - { - s_DoScroll = true; - s_ScrollStart = UI()->MouseX(); - int MxRel = (int)(UI()->MouseX() - pRect->x); - - for(int i = 1; i <= Len; i++) - { - if(TextRender()->TextWidth(0, FontSize, pStr, i, std::numeric_limits::max()) - *Offset > MxRel) - { - s_AtIndex = i - 1; - break; - } - - if(i == Len) - s_AtIndex = Len; - } - } - else if(!UI()->MouseButton(0)) - s_DoScroll = false; - else if(s_DoScroll) - { - // do scrolling - if(UI()->MouseX() < pRect->x && s_ScrollStart - UI()->MouseX() > 10.0f) - { - s_AtIndex = maximum(0, s_AtIndex - 1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - else if(UI()->MouseX() > pRect->x + pRect->w && UI()->MouseX() - s_ScrollStart > 10.0f) - { - s_AtIndex = minimum(Len, s_AtIndex + 1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - } - - for(int i = 0; i < m_NumInputEvents; i++) - { - Len = str_length(pStr); - int NumChars = Len; - ReturnValue |= CLineInput::Manipulate(m_aInputEvents[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); - } - } - - bool JustGotActive = false; - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - s_AtIndex = minimum(s_AtIndex, str_length(pStr)); - s_DoScroll = false; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - if(UI()->LastActiveItem() != pID) - JustGotActive = true; - UI()->SetActiveItem(pID); - } - } - - if(Inside) - { - UI()->SetHotItem(pID); - } - - CUIRect Textbox = *pRect; - RenderTools()->DrawUIRect(&Textbox, ColorRGBA(1, 1, 1, 0.5f), Corners, 3.0f); - Textbox.VMargin(2.0f, &Textbox); - Textbox.HMargin(2.0f, &Textbox); - - const char *pDisplayStr = pStr; - char aStars[128]; - - if(Hidden) - { - unsigned s = str_length(pDisplayStr); - if(s >= sizeof(aStars)) - s = sizeof(aStars) - 1; - for(unsigned int i = 0; i < s; ++i) - aStars[i] = '*'; - aStars[s] = 0; - pDisplayStr = aStars; - } - - char aDispEditingText[128 + IInput::INPUT_TEXT_SIZE + 2] = {0}; - int DispCursorPos = s_AtIndex; - if(UI()->LastActiveItem() == pID && Input()->GetIMEEditingTextLength() > -1) - { - int EditingTextCursor = Input()->GetEditingCursor(); - str_copy(aDispEditingText, pDisplayStr, sizeof(aDispEditingText)); - char aEditingText[IInput::INPUT_TEXT_SIZE + 2]; - if(Hidden) - { - // Do not show editing text in password field - str_copy(aEditingText, "[*]", sizeof(aEditingText)); - EditingTextCursor = 1; - } - else - { - str_format(aEditingText, sizeof(aEditingText), "[%s]", Input()->GetIMEEditingText()); - } - int NewTextLen = str_length(aEditingText); - int CharsLeft = (int)sizeof(aDispEditingText) - str_length(aDispEditingText) - 1; - int FillCharLen = minimum(NewTextLen, CharsLeft); - for(int i = str_length(aDispEditingText) - 1; i >= s_AtIndex; i--) - aDispEditingText[i + FillCharLen] = aDispEditingText[i]; - for(int i = 0; i < FillCharLen; i++) - aDispEditingText[s_AtIndex + i] = aEditingText[i]; - DispCursorPos = s_AtIndex + EditingTextCursor + 1; - pDisplayStr = aDispEditingText; - UpdateOffset = true; - } - - if(pDisplayStr[0] == '\0') - { - pDisplayStr = pEmptyText; - TextRender()->TextColor(1, 1, 1, 0.75f); - } - - DispCursorPos = minimum(DispCursorPos, str_length(pDisplayStr)); - - // check if the text has to be moved - if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || m_NumInputEvents)) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, DispCursorPos, std::numeric_limits::max()); - if(w - *Offset > Textbox.w) - { - // move to the left - float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1, std::numeric_limits::max()); - do - { - *Offset += minimum(wt - *Offset - Textbox.w, Textbox.w / 3); - } while(w - *Offset > Textbox.w + 0.0001f); - } - else if(w - *Offset < 0.0f) - { - // move to the right - do - { - *Offset = maximum(0.0f, *Offset - Textbox.w / 3); - } while(w - *Offset < -0.0001f); - } - } - UI()->ClipEnable(pRect); - Textbox.x -= *Offset; - - UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); - - TextRender()->TextColor(1, 1, 1, 1); - - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - float OnePixelWidth = ((ScreenX1 - ScreenX0) / Graphics()->ScreenWidth()); - - // render the cursor - if(UI()->LastActiveItem() == pID && !JustGotActive) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, DispCursorPos, std::numeric_limits::max()); - Textbox.x += w; - - if((2 * time_get() / time_freq()) % 2) - { - Graphics()->TextureClear(); - Graphics()->QuadsBegin(); - Graphics()->SetColor(0, 0, 0, 0.3f); - IGraphics::CQuadItem CursorTBack(Textbox.x - (OnePixelWidth * 2.0f) / 2.0f, Textbox.y, OnePixelWidth * 2 * 2.0f, Textbox.h); - Graphics()->QuadsDrawTL(&CursorTBack, 1); - Graphics()->SetColor(1, 1, 1, 1); - IGraphics::CQuadItem CursorT(Textbox.x, Textbox.y + OnePixelWidth * 1.5f, OnePixelWidth * 2.0f, Textbox.h - OnePixelWidth * 1.5f * 2); - Graphics()->QuadsDrawTL(&CursorT, 1); - Graphics()->QuadsEnd(); - } - - Input()->SetEditingPosition(Textbox.x, Textbox.y + FontSize); - } - - UI()->ClipDisable(); - - return ReturnValue; + return m_UIEx.DoEditBox(pID, pRect, pStr, StrSize, FontSize, Offset, Hidden, Corners, pEmptyText); } int CMenus::DoClearableEditBox(void *pID, void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) @@ -940,11 +696,11 @@ return ReturnValue; } -float CMenus::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current) +float CMenus::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current, bool ColorPickerSlider, ColorRGBA *pColorInner) { CUIRect Handle; static float OffsetX; - pRect->VSplitLeft(33, &Handle, 0); + pRect->VSplitLeft(ColorPickerSlider ? 8 : 33, &Handle, 0); Handle.x += (pRect->w - Handle.w) * Current; @@ -982,19 +738,35 @@ UI()->SetHotItem(pID); // render - CUIRect Rail; - pRect->HMargin(5.0f, &Rail); - RenderTools()->DrawUIRect(&Rail, ColorRGBA(1, 1, 1, 0.25f), 0, 0.0f); - - CUIRect Slider = Handle; - Slider.h = Rail.y - Slider.y; - RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_T, 2.5f); - Slider.y = Rail.y + Rail.h; - RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_B, 2.5f); - - Slider = Handle; - Slider.Margin(5.0f, &Slider); - RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f * ButtonColorMul(pID)), CUI::CORNER_ALL, 2.5f); + if(!ColorPickerSlider) + { + CUIRect Rail; + pRect->HMargin(5.0f, &Rail); + RenderTools()->DrawUIRect(&Rail, ColorRGBA(1, 1, 1, 0.25f), 0, 0.0f); + + CUIRect Slider = Handle; + Slider.h = Rail.y - Slider.y; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_T, 2.5f); + Slider.y = Rail.y + Rail.h; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_B, 2.5f); + + Slider = Handle; + Slider.Margin(5.0f, &Slider); + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1, 1, 1, 0.25f * ButtonColorMul(pID)), CUI::CORNER_ALL, 2.5f); + } + else + { + CUIRect Slider = Handle; + float MarginW = 4.0f; + float MarginH = 9.0f; + Slider.x -= MarginW / 2; + Slider.y -= MarginH / 2; + Slider.w += MarginW; + Slider.h += MarginH; + RenderTools()->DrawUIRect(&Slider, ColorRGBA{0.15f, 0.15f, 0.15f, 1.0f}, CUI::CORNER_ALL, 5.0f); + Slider.Margin(2, &Slider); + RenderTools()->DrawUIRect(&Slider, *pColorInner, CUI::CORNER_ALL, 3.0f); + } return ReturnValue; } @@ -1253,7 +1025,10 @@ Box.VSplitLeft(4.0f, 0, &Box); static int s_CallVoteButton = 0; if(DoButton_MenuTab(&s_CallVoteButton, Localize("Call vote"), m_ActivePage == PAGE_CALLVOTE, &Button, CUI::CORNER_TR)) + { NewPage = PAGE_CALLVOTE; + m_ControlPageOpening = true; + } } TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); @@ -1413,6 +1188,8 @@ if(g_Config.m_ClSkipStartMenu) m_ShowStart = false; + m_UIEx.Init(UI(), Kernel(), RenderTools(), m_aInputEvents, &m_NumInputEvents); + m_RefreshButton.Init(UI()); m_ConnectButton.Init(UI()); @@ -1421,6 +1198,11 @@ Console()->Chain("add_friend", ConchainFriendlistUpdate, this); Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); + Console()->Chain("cl_assets_entities", ConchainAssetsEntities, this); + Console()->Chain("cl_asset_game", ConchainAssetGame, this); + Console()->Chain("cl_asset_emoticons", ConchainAssetEmoticons, this); + Console()->Chain("cl_asset_particles", ConchainAssetParticles, this); + m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); // setup load amount @@ -3168,6 +2950,13 @@ bool CMenus::HandleListInputs(const CUIRect &View, float &ScrollValue, const float ScrollAmount, int *pScrollOffset, const float ElemHeight, int &SelectedIndex, const int NumElems) { + if(NumElems == 0) + { + ScrollValue = 0; + SelectedIndex = 0; + return false; + } + int NewIndex = -1; int Num = (int)(View.h / ElemHeight); int ScrollNum = maximum(NumElems - Num, 0); @@ -3185,7 +2974,7 @@ } ScrollValue = clamp(ScrollValue, 0.0f, 1.0f); - SelectedIndex = clamp(SelectedIndex, 0, NumElems); + SelectedIndex = clamp(SelectedIndex, 0, NumElems - 1); for(int i = 0; i < m_NumInputEvents; i++) { diff -Nru ddnet-15.3.2/src/game/client/components/menus_demo.cpp ddnet-15.5.4/src/game/client/components/menus_demo.cpp --- ddnet-15.3.2/src/game/client/components/menus_demo.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_demo.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -169,15 +169,18 @@ if(m_pClient->m_pGameConsole->IsClosed() && m_DemoPlayerState == DEMOPLAYER_NONE && g_Config.m_ClDemoKeyboardShortcuts) { // increase/decrease speed - if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) || Input()->KeyPress(KEY_UP)) + if(!Input()->KeyIsPressed(KEY_LSHIFT) && !Input()->KeyIsPressed(KEY_RSHIFT)) { - DemoPlayer()->SetSpeedIndex(+1); - LastSpeedChange = time_get(); - } - else if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) || Input()->KeyPress(KEY_DOWN)) - { - DemoPlayer()->SetSpeedIndex(-1); - LastSpeedChange = time_get(); + if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) || Input()->KeyPress(KEY_UP)) + { + DemoPlayer()->SetSpeedIndex(+1); + LastSpeedChange = time_get(); + } + else if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) || Input()->KeyPress(KEY_DOWN)) + { + DemoPlayer()->SetSpeedIndex(-1); + LastSpeedChange = time_get(); + } } // pause/unpause @@ -620,7 +623,18 @@ CListboxItem Item = UiDoListboxNextRow(); - if(Item.m_Visible && UI()->DoButtonLogic(pId, "", gs_ListBoxSelectedIndex == gs_ListBoxItemIndex, &Item.m_HitRect)) + CUIRect HitRect = Item.m_HitRect; + + if(HitRect.y < gs_ListBoxOriginalView.y) + { + float TmpDiff = gs_ListBoxOriginalView.y - HitRect.y; + HitRect.y = gs_ListBoxOriginalView.y; + HitRect.h -= TmpDiff; + } + + HitRect.h = minimum(HitRect.h, (gs_ListBoxOriginalView.y + gs_ListBoxOriginalView.h) - HitRect.y); + + if(Item.m_Visible && UI()->DoButtonLogic(pId, "", gs_ListBoxSelectedIndex == gs_ListBoxItemIndex, &HitRect)) { gs_ListBoxClicked = true; gs_ListBoxNewSelected = ThisItemIndex; @@ -696,7 +710,7 @@ r.Margin(1.5f, &r); RenderTools()->DrawUIRect(&r, ColorRGBA(1, 1, 1, 0.5f), CUI::CORNER_ALL, 4.0f); } - else if(UI()->MouseInside(&Item.m_Rect) && !NoHoverEffects) + else if(UI()->MouseInside(&HitRect) && !NoHoverEffects) { CUIRect r = Item.m_Rect; r.Margin(1.5f, &r); @@ -718,6 +732,14 @@ return gs_ListBoxNewSelected; } +int CMenus::UiLogicGetCurrentClickedItem() +{ + if(gs_ListBoxClicked) + return gs_ListBoxNewSelected; + else + return -1; +} + int CMenus::DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser) { CMenus *pSelf = (CMenus *)pUser; @@ -1232,7 +1254,8 @@ } } - if(DoButton_Menu(&DirectoryButton, Localize("Demos directory"), 0, &DirectoryButton)) + static int s_DirectoryButtonID = 0; + if(DoButton_Menu(&s_DirectoryButtonID, Localize("Demos directory"), 0, &DirectoryButton)) { char aBuf[MAX_PATH_LENGTH]; char aBufFull[MAX_PATH_LENGTH + 7]; diff -Nru ddnet-15.3.2/src/game/client/components/menus.h ddnet-15.5.4/src/game/client/components/menus.h --- ddnet-15.3.2/src/game/client/components/menus.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus.h 2021-06-20 09:38:48.000000000 +0000 @@ -15,6 +15,7 @@ #include #include +#include #include struct CServerProcess @@ -70,6 +71,8 @@ char m_aLocalStringHelper[1024]; + CUIEx m_UIEx; + float ButtonColorMulActive() { return 0.5f; } float ButtonColorMulHot() { return 1.5f; } float ButtonColorMulDefault() { return 1.0f; } @@ -108,7 +111,7 @@ //static int ui_do_edit_box(void *id, const CUIRect *rect, char *str, unsigned str_size, float font_size, bool hidden=false); float DoScrollbarV(const void *pID, const CUIRect *pRect, float Current); - float DoScrollbarH(const void *pID, const CUIRect *pRect, float Current); + float DoScrollbarH(const void *pID, const CUIRect *pRect, float Current, bool ColorPickerSlider = false, ColorRGBA *pColorInner = NULL); void DoButton_KeySelect(const void *pID, const char *pText, int Checked, const CUIRect *pRect); int DoKeyReader(void *pID, const CUIRect *pRect, int Key, int Modifier, int *NewModifier); @@ -220,6 +223,8 @@ CListboxItem UiDoListboxNextRow(); int UiDoListboxEnd(float *pScrollValue, bool *pItemActivated, bool *pListBoxActive = 0); + int UiLogicGetCurrentClickedItem(); + //static void demolist_listdir_callback(const char *name, int is_dir, void *user); //static void demolist_list_callback(const CUIRect *rect, int index, void *user); @@ -268,6 +273,11 @@ static int EmoticonsScan(const char *pName, int IsDir, int DirType, void *pUser); static int ParticlesScan(const char *pName, int IsDir, int DirType, void *pUser); + static void ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainAssetGame(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainAssetParticles(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + void ClearCustomItems(int CurTab); int m_MenuPage; @@ -349,6 +359,7 @@ int m_CallvoteSelectedPlayer; char m_aCallvoteReason[VOTE_REASON_LENGTH]; char m_aFilterString[25]; + bool m_ControlPageOpening; // demo enum @@ -687,7 +698,9 @@ void RenderSettingsDDNet(CUIRect MainView); void RenderSettingsHUD(CUIRect MainView); ColorHSLA RenderHSLColorPicker(const CUIRect *pRect, unsigned int *pColor, bool Alpha); - ColorHSLA RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha = false); + ColorHSLA RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha = false, bool ClampedLight = false); + + int RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelection, const void **pIDs, const char **pStr, int PickNum, const void *pID, float &ScrollVal); CServerProcess m_ServerProcess; }; diff -Nru ddnet-15.3.2/src/game/client/components/menus_ingame.cpp ddnet-15.5.4/src/game/client/components/menus_ingame.cpp --- ddnet-15.3.2/src/game/client/components/menus_ingame.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_ingame.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -293,9 +293,16 @@ // player info Player.VSplitLeft(28.0f, &Button, &Player); - CTeeRenderInfo Info = m_pClient->m_aClients[Index].m_RenderInfo; - Info.m_Size = Button.h; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Button.x + Button.h / 2, Button.y + Button.h / 2)); + + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[Index].m_RenderInfo; + TeeInfo.m_Size = Button.h; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(Button.x + Button.h / 2, Button.y + Button.h / 2 + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); Player.HSplitTop(1.5f, 0, &Player); Player.VSplitMid(&Player, &Button); @@ -589,10 +596,17 @@ if(Item.m_Visible) { - CTeeRenderInfo Info = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo; - Info.m_Size = Item.m_Rect.h; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, EMOTE_NORMAL, vec2(1, 0), vec2(Item.m_Rect.x + Item.m_Rect.h / 2, Item.m_Rect.y + Item.m_Rect.h / 2)); - Item.m_Rect.x += Info.m_Size; + CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo; + TeeInfo.m_Size = Item.m_Rect.h; + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(Item.m_Rect.x + Item.m_Rect.h / 2, Item.m_Rect.y + Item.m_Rect.h / 2 + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); + + Item.m_Rect.x += TeeInfo.m_Size; UI()->DoLabelScaled(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, -1); } } @@ -667,8 +681,11 @@ static int s_ClearButton = 0; static float Offset = 0.0f; //static char aFilterString[25]; - if(Input()->KeyPress(KEY_F) && (Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL))) + if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && (Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL)))) + { UI()->SetActiveItem(&m_aFilterString); + m_ControlPageOpening = false; + } if(DoClearableEditBox(&m_aFilterString, &s_ClearButton, &QuickSearch, m_aFilterString, sizeof(m_aFilterString), 14.0f, &Offset, false, CUI::CORNER_ALL, Localize("Search"))) { // TODO: Implement here @@ -1138,7 +1155,7 @@ GhostlistPopulate(); } - if(s_SelectedIndex >= m_lGhosts.size()) + if(s_SelectedIndex == -1 || s_SelectedIndex >= m_lGhosts.size()) return; CGhostItem *pGhost = &m_lGhosts[s_SelectedIndex]; diff -Nru ddnet-15.3.2/src/game/client/components/menus_settings_assets.cpp ddnet-15.5.4/src/game/client/components/menus_settings_assets.cpp --- ddnet-15.3.2/src/game/client/components/menus_settings_assets.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_settings_assets.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -249,18 +249,30 @@ } } m_EntitiesList.clear(); + + // reload current entities + m_pClient->m_pMapimages->ChangeEntitiesPath(g_Config.m_ClAssetsEntites); } else if(CurTab == 1) { ClearAssetList(m_GameList, Graphics()); + + // reload current game skin + GameClient()->LoadGameSkin(g_Config.m_ClAssetGame); } else if(CurTab == 2) { ClearAssetList(m_EmoticonList, Graphics()); + + // reload current emoticons skin + GameClient()->LoadEmoticonsSkin(g_Config.m_ClAssetEmoticons); } else if(CurTab == 3) { ClearAssetList(m_ParticlesList, Graphics()); + + // reload current particles skin + GameClient()->LoadParticlesSkin(g_Config.m_ClAssetParticles); } s_InitCustomList[CurTab] = true; } @@ -310,13 +322,15 @@ Page2Tab.VSplitLeft(TabsW / 4, &Page2Tab, &Page3Tab); Page3Tab.VSplitLeft(TabsW / 4, &Page3Tab, &Page4Tab); - if(DoButton_MenuTab((void *)&Page1Tab, Localize("Entities"), s_CurCustomTab == 0, &Page1Tab, 5, NULL, NULL, NULL, NULL, 4)) + static int s_aPageTabs[4] = {}; + + if(DoButton_MenuTab((void *)&s_aPageTabs[0], Localize("Entities"), s_CurCustomTab == 0, &Page1Tab, 5, NULL, NULL, NULL, NULL, 4)) s_CurCustomTab = 0; - if(DoButton_MenuTab((void *)&Page2Tab, Localize("Game"), s_CurCustomTab == 1, &Page2Tab, 0, NULL, NULL, NULL, NULL, 4)) + if(DoButton_MenuTab((void *)&s_aPageTabs[1], Localize("Game"), s_CurCustomTab == 1, &Page2Tab, 0, NULL, NULL, NULL, NULL, 4)) s_CurCustomTab = 1; - if(DoButton_MenuTab((void *)&Page3Tab, Localize("Emoticons"), s_CurCustomTab == 2, &Page3Tab, 0, NULL, NULL, NULL, NULL, 4)) + if(DoButton_MenuTab((void *)&s_aPageTabs[2], Localize("Emoticons"), s_CurCustomTab == 2, &Page3Tab, 0, NULL, NULL, NULL, NULL, 4)) s_CurCustomTab = 2; - if(DoButton_MenuTab((void *)&Page4Tab, Localize("Particles"), s_CurCustomTab == 3, &Page4Tab, 10, NULL, NULL, NULL, NULL, 4)) + if(DoButton_MenuTab((void *)&s_aPageTabs[3], Localize("Particles"), s_CurCustomTab == 3, &Page4Tab, 10, NULL, NULL, NULL, NULL, 4)) s_CurCustomTab = 3; if(s_CurCustomTab == 0) @@ -517,7 +531,8 @@ DirectoryButton.VSplitRight(175.0f, 0, &DirectoryButton); DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &ReloadButton); DirectoryButton.VSplitRight(10.0f, &DirectoryButton, 0); - if(DoButton_Menu(&DirectoryButton, Localize("Assets directory"), 0, &DirectoryButton)) + static int s_AssetsDirID = 0; + if(DoButton_Menu(&s_AssetsDirID, Localize("Assets directory"), 0, &DirectoryButton)) { char aBuf[MAX_PATH_LENGTH]; char aBufFull[MAX_PATH_LENGTH + 7]; @@ -541,10 +556,71 @@ TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); - if(DoButton_Menu(&ReloadButton, "\xEE\x97\x95", 0, &ReloadButton, NULL, 15, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f), 0)) + static int s_AssetsReloadBtnID = 0; + if(DoButton_Menu(&s_AssetsReloadBtnID, "\xEE\x97\x95", 0, &ReloadButton, NULL, 15, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f), 0)) { ClearCustomItems(s_CurCustomTab); } TextRender()->SetRenderFlags(0); TextRender()->SetCurFont(NULL); } + +void CMenus::ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CMenus *pThis = (CMenus *)pUserData; + if(pResult->NumArguments() == 1) + { + const char *pArg = pResult->GetString(0); + if(str_comp(pArg, g_Config.m_ClAssetsEntites) != 0) + { + pThis->m_pClient->m_pMapimages->ChangeEntitiesPath(pArg); + } + } + + pfnCallback(pResult, pCallbackUserData); +} + +void CMenus::ConchainAssetGame(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CMenus *pThis = (CMenus *)pUserData; + if(pResult->NumArguments() == 1) + { + const char *pArg = pResult->GetString(0); + if(str_comp(pArg, g_Config.m_ClAssetGame) != 0) + { + pThis->GameClient()->LoadGameSkin(pArg); + } + } + + pfnCallback(pResult, pCallbackUserData); +} + +void CMenus::ConchainAssetParticles(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CMenus *pThis = (CMenus *)pUserData; + if(pResult->NumArguments() == 1) + { + const char *pArg = pResult->GetString(0); + if(str_comp(pArg, g_Config.m_ClAssetParticles) != 0) + { + pThis->GameClient()->LoadParticlesSkin(pArg); + } + } + + pfnCallback(pResult, pCallbackUserData); +} + +void CMenus::ConchainAssetEmoticons(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CMenus *pThis = (CMenus *)pUserData; + if(pResult->NumArguments() == 1) + { + const char *pArg = pResult->GetString(0); + if(str_comp(pArg, g_Config.m_ClAssetEmoticons) != 0) + { + pThis->GameClient()->LoadEmoticonsSkin(pArg); + } + } + + pfnCallback(pResult, pCallbackUserData); +} diff -Nru ddnet-15.3.2/src/game/client/components/menus_settings.cpp ddnet-15.5.4/src/game/client/components/menus_settings.cpp --- ddnet-15.3.2/src/game/client/components/menus_settings.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_settings.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -271,7 +271,8 @@ RenderThemeSelection(Left); DirectoryButton.HSplitTop(5.0f, 0, &DirectoryButton); - if(DoButton_Menu(&DirectoryButton, Localize("Themes directory"), 0, &DirectoryButton)) + static int s_ThemesButtonID = 0; + if(DoButton_Menu(&s_ThemesButtonID, Localize("Themes directory"), 0, &DirectoryButton)) { char aBuf[MAX_PATH_LENGTH]; char aBufFull[MAX_PATH_LENGTH + 7]; @@ -442,6 +443,21 @@ } } +struct CUISkin +{ + const CSkin *m_pSkin; + + CUISkin() : + m_pSkin(nullptr) {} + CUISkin(const CSkin *pSkin) : + m_pSkin(pSkin) {} + + bool operator<(const CUISkin &Other) const { return str_comp_nocase(m_pSkin->m_aName, Other.m_pSkin->m_aName) < 0; } + + bool operator<(const char *pOther) const { return str_comp_nocase(m_pSkin->m_aName, pOther) < 0; } + bool operator==(const char *pOther) const { return !str_comp_nocase(m_pSkin->m_aName, pOther); } +}; + void CMenus::RenderSettingsTee(CUIRect MainView) { CUIRect Button, Label, Button2, Dummy, DummyLabel, SkinList, QuickSearch, QuickSearchClearButton, SkinDB, SkinPrefix, SkinPrefixLabel, DirectoryButton, RefreshButton; @@ -549,10 +565,14 @@ MainView.HSplitTop(50.0f, &Label, &MainView); Label.VSplitLeft(230.0f, &Label, 0); - RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1, 0), vec2(Label.x + 30.0f, Label.y + 28.0f)); + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &OwnSkinInfo, OffsetToMid); + vec2 TeeRenderPos(Label.x + 30.0f, Label.y + Label.h / 2.0f + OffsetToMid.y); + RenderTools()->RenderTee(pIdleState, &OwnSkinInfo, 0, vec2(1, 0), TeeRenderPos); Label.VSplitLeft(70.0f, 0, &Label); Label.HMargin(15.0f, &Label); - //UI()->DoLabelScaled(&Label, Skin, 14.0f, -1, 150.0f); + // UI()->DoLabelScaled(&Label, Skin, 14.0f, -1, 150.0f); static float s_OffsetSkin = 0.0f; static int s_ClearButton = 0; if(DoClearableEditBox(Skin, &s_ClearButton, &Label, Skin, sizeof(g_Config.m_ClPlayerSkin), 14.0f, &s_OffsetSkin, false, CUI::CORNER_ALL, "default")) @@ -564,7 +584,8 @@ MainView.HSplitTop(20.0f, 0, &MainView); MainView.HSplitTop(20.0f, &Button, &MainView); Button.VSplitMid(&Button, &Button2); - if(DoButton_CheckBox(&ColorBody, Localize("Custom colors"), *UseCustomColor, &Button)) + static int s_CustomColorID = 0; + if(DoButton_CheckBox(&s_CustomColorID, Localize("Custom colors"), *UseCustomColor, &Button)) { *UseCustomColor = *UseCustomColor ? 0 : 1; SetNeedSendInfo(); @@ -590,7 +611,7 @@ aRects[i].HSplitTop(2.5f, 0, &aRects[i]); unsigned PrevColor = *paColors[i]; - RenderHSLScrollbars(&aRects[i], paColors[i]); + RenderHSLScrollbars(&aRects[i], paColors[i], false, true); if(PrevColor != *paColors[i]) { @@ -602,7 +623,7 @@ // skin selector MainView.HSplitTop(20.0f, 0, &MainView); MainView.HSplitTop(230.0f, &SkinList, &MainView); - static sorted_array s_paSkinList; + static sorted_array s_paSkinList; static int s_SkinCount = 0; static float s_ScrollValue = 0.0f; if(s_InitSkinlist || m_pClient->m_pSkins->Num() != s_SkinCount) @@ -627,7 +648,7 @@ if(s == 0) continue; - s_paSkinList.add_unsorted(s); + s_paSkinList.add(CUISkin(s)); } s_InitSkinlist = false; s_SkinCount = m_pClient->m_pSkins->Num(); @@ -637,12 +658,12 @@ UiDoListboxStart(&s_InitSkinlist, &SkinList, 50.0f, Localize("Skins"), "", s_paSkinList.size(), 4, OldSelected, s_ScrollValue); for(int i = 0; i < s_paSkinList.size(); ++i) { - const CSkin *s = s_paSkinList[i]; + const CSkin *s = s_paSkinList[i].m_pSkin; if(str_comp(s->m_aName, Skin) == 0) OldSelected = i; - CListboxItem Item = UiDoListboxNextItem(s_paSkinList[i], OldSelected == i); + CListboxItem Item = UiDoListboxNextItem(s_paSkinList[i].m_pSkin, OldSelected == i); char aBuf[128]; if(Item.m_Visible) { @@ -653,8 +674,9 @@ Info.m_ColorableRenderSkin = s->m_ColorableSkin; Info.m_SkinMetrics = s->m_Metrics; - Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top - RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, 0, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x + 30, Item.m_Rect.y + Item.m_Rect.h / 2)); + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid); + TeeRenderPos = vec2(Item.m_Rect.x + 30, Item.m_Rect.y + Item.m_Rect.h / 2 + OffsetToMid.y); + RenderTools()->RenderTee(pIdleState, &Info, 0, vec2(1.0f, 0.0f), TeeRenderPos); Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect); str_format(aBuf, sizeof(aBuf), "%s", s->m_aName); @@ -675,7 +697,7 @@ const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); if(OldSelected != NewSelected) { - mem_copy(Skin, s_paSkinList[NewSelected]->m_aName, sizeof(g_Config.m_ClPlayerSkin)); + mem_copy(Skin, s_paSkinList[NewSelected].m_pSkin->m_aName, sizeof(g_Config.m_ClPlayerSkin)); SetNeedSendInfo(); } @@ -704,7 +726,8 @@ SkinDB.VSplitLeft(150.0f, &SkinDB, &DirectoryButton); SkinDB.HSplitTop(5.0f, 0, &SkinDB); - if(DoButton_Menu(&SkinDB, Localize("Skin Database"), 0, &SkinDB)) + static int s_SkinDBDirID = 0; + if(DoButton_Menu(&s_SkinDBDirID, Localize("Skin Database"), 0, &SkinDB)) { if(!open_link("https://ddnet.tw/skins/")) { @@ -716,7 +739,8 @@ DirectoryButton.VSplitRight(175.0f, 0, &DirectoryButton); DirectoryButton.VSplitRight(25.0f, &DirectoryButton, &RefreshButton); DirectoryButton.VSplitRight(10.0f, &DirectoryButton, 0); - if(DoButton_Menu(&DirectoryButton, Localize("Skins directory"), 0, &DirectoryButton)) + static int s_DirectoryButtonID = 0; + if(DoButton_Menu(&s_DirectoryButtonID, Localize("Skins directory"), 0, &DirectoryButton)) { char aBuf[MAX_PATH_LENGTH]; char aBufFull[MAX_PATH_LENGTH + 7]; @@ -731,7 +755,8 @@ TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); - if(DoButton_Menu(&RefreshButton, "\xEE\x97\x95", 0, &RefreshButton, NULL, 15, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f), 0)) + static int s_SkinRefreshButtonID = 0; + if(DoButton_Menu(&s_SkinRefreshButtonID, "\xEE\x97\x95", 0, &RefreshButton, NULL, 15, 5, 0, vec4(1.0f, 1.0f, 1.0f, 0.75f), vec4(1, 1, 1, 0.5f), 0)) { m_pClient->m_pSkins->Refresh(); s_InitSkinlist = true; @@ -760,7 +785,6 @@ {"Fire", "+fire", 0, 0}, {"Hook", "+hook", 0, 0}, {"Hook collisions", "+showhookcoll", 0, 0}, - {"45° aim", "+toggle cl_mouse_max_distance 2 400; +toggle inp_mousesens 1 200", 0, 0}, {"Pause", "say /pause", 0, 0}, {"Kill", "kill", 0, 0}, {"Zoom in", "zoom+", 0, 0}, @@ -791,8 +815,6 @@ {"Toggle dummy", "toggle cl_dummy 0 1", 0, 0}, {"Dummy copy", "toggle cl_dummy_copy_moves 0 1", 0, 0}, {"Hammerfly dummy", "toggle cl_dummy_hammer 0 1", 0, 0}, - {"Deepfly on", "bind mouse1 \"+fire; +toggle cl_dummy_hammer 1 0\"", 0, 0}, - {"Deepfly off", "bind mouse1 \"+fire\"", 0, 0}, {"Emoticon", "+emote", 0, 0}, {"Spectator mode", "+spectate", 0, 0}, @@ -818,8 +840,7 @@ Localize("Chat");Localize("Team chat");Localize("Converse");Localize("Show chat");Localize("Emoticon"); Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console"); Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Statboard"); - Localize("Lock team");Localize("Show entities");Localize("Show HUD");Localize("45° aim"); - Localize("Chat command";Localize("Deepfly on");Localize("Deepfly off"); + Localize("Lock team");Localize("Show entities");Localize("Show HUD");Localize("Chat command"); */ void CMenus::UiDoGetButtons(int Start, int Stop, CUIRect View, CUIRect ScopeView) @@ -882,18 +903,20 @@ static int s_ControlsList = 0; static int s_SelectedControl = -1; static float s_ScrollValue = 0; - int OldSelected = s_SelectedControl; - UiDoListboxStart(&s_ControlsList, &MainView, 500.0f, Localize("Controls"), "", 1, 1, s_SelectedControl, s_ScrollValue); + static int s_OldSelected = 0; + // Hacky values: Size of 10.0f per item for smoother scrolling, 72 elements + // fits the current size of controls settings + UiDoListboxStart(&s_ControlsList, &MainView, 10.0f, Localize("Controls"), "", 72, 1, s_SelectedControl, s_ScrollValue); CUIRect MovementSettings, WeaponSettings, VotingSettings, ChatSettings, DummySettings, MiscSettings, ResetButton; - CListboxItem Item = UiDoListboxNextItem(&OldSelected, false, false, true); + CListboxItem Item = UiDoListboxNextItem(&s_OldSelected, false, false, true); Item.m_Rect.HSplitTop(10.0f, 0, &Item.m_Rect); Item.m_Rect.VSplitMid(&MovementSettings, &VotingSettings); // movement settings { MovementSettings.VMargin(5.0f, &MovementSettings); - MovementSettings.HSplitTop(510.0f, &MovementSettings, &WeaponSettings); + MovementSettings.HSplitTop(445.0f, &MovementSettings, &WeaponSettings); RenderTools()->DrawUIRect(&MovementSettings, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f); MovementSettings.VMargin(10.0f, &MovementSettings); @@ -927,20 +950,20 @@ MovementSettings.HSplitTop(20.0f, 0, &MovementSettings); } - UiDoGetButtons(0, 18, MovementSettings, MainView); + UiDoGetButtons(0, 15, MovementSettings, MainView); } // weapon settings { WeaponSettings.HSplitTop(10.0f, 0, &WeaponSettings); - WeaponSettings.HSplitTop(145.0f, &WeaponSettings, &ResetButton); + WeaponSettings.HSplitTop(190.0f, &WeaponSettings, &ResetButton); RenderTools()->DrawUIRect(&WeaponSettings, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f); WeaponSettings.VMargin(10.0f, &WeaponSettings); TextRender()->Text(0, WeaponSettings.x, WeaponSettings.y + (14.0f + 5.0f + 10.0f - 14.0f * UI()->Scale()) / 2.f, 14.0f * UI()->Scale(), Localize("Weapon"), -1.0f); WeaponSettings.HSplitTop(14.0f + 5.0f + 10.0f, 0, &WeaponSettings); - UiDoGetButtons(18, 23, WeaponSettings, MainView); + UiDoGetButtons(15, 22, WeaponSettings, MainView); } // defaults @@ -966,7 +989,7 @@ TextRender()->Text(0, VotingSettings.x, VotingSettings.y + (14.0f + 5.0f + 10.0f - 14.0f * UI()->Scale()) / 2.f, 14.0f * UI()->Scale(), Localize("Voting"), -1.0f); VotingSettings.HSplitTop(14.0f + 5.0f + 10.0f, 0, &VotingSettings); - UiDoGetButtons(23, 25, VotingSettings, MainView); + UiDoGetButtons(22, 24, VotingSettings, MainView); } // chat settings @@ -979,20 +1002,20 @@ TextRender()->Text(0, ChatSettings.x, ChatSettings.y + (14.0f + 5.0f + 10.0f - 14.0f * UI()->Scale()) / 2.f, 14.0f * UI()->Scale(), Localize("Chat"), -1.0f); ChatSettings.HSplitTop(14.0f + 5.0f + 10.0f, 0, &ChatSettings); - UiDoGetButtons(25, 30, ChatSettings, MainView); + UiDoGetButtons(24, 29, ChatSettings, MainView); } // dummy settings { DummySettings.HSplitTop(10.0f, 0, &DummySettings); - DummySettings.HSplitTop(145.0f, &DummySettings, &MiscSettings); + DummySettings.HSplitTop(100.0f, &DummySettings, &MiscSettings); RenderTools()->DrawUIRect(&DummySettings, ColorRGBA(1, 1, 1, 0.25f), CUI::CORNER_ALL, 10.0f); DummySettings.VMargin(10.0f, &DummySettings); TextRender()->Text(0, DummySettings.x, DummySettings.y + (14.0f + 5.0f + 10.0f - 14.0f * UI()->Scale()) / 2.f, 14.0f * UI()->Scale(), Localize("Dummy"), -1.0f); DummySettings.HSplitTop(14.0f + 5.0f + 10.0f, 0, &DummySettings); - UiDoGetButtons(30, 35, DummySettings, MainView); + UiDoGetButtons(29, 32, DummySettings, MainView); } // misc settings @@ -1005,12 +1028,49 @@ TextRender()->Text(0, MiscSettings.x, MiscSettings.y + (14.0f + 5.0f + 10.0f - 14.0f * UI()->Scale()) / 2.f, 14.0f * UI()->Scale(), Localize("Miscellaneous"), -1.0f); MiscSettings.HSplitTop(14.0f + 5.0f + 10.0f, 0, &MiscSettings); - UiDoGetButtons(35, 47, MiscSettings, MainView); + UiDoGetButtons(32, 44, MiscSettings, MainView); } UiDoListboxEnd(&s_ScrollValue, 0); } +int CMenus::RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelection, const void **pIDs, const char **pStr, int PickNum, const void *pID, float &ScrollVal) +{ + if(CurDropDownState != 0) + { + CUIRect ListRect; + pRect->HSplitTop(24.0f * PickNum, &ListRect, pRect); + char aBuf[1024]; + UiDoListboxStart(&pID, &ListRect, 24.0f, "", aBuf, PickNum, 1, CurSelection, ScrollVal); + for(int i = 0; i < PickNum; ++i) + { + CListboxItem Item = UiDoListboxNextItem(pIDs[i], CurSelection == i); + if(Item.m_Visible) + { + str_format(aBuf, sizeof(aBuf), "%s", pStr[i]); + UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, 0); + } + } + bool ClickedItem = false; + int NewIndex = UiDoListboxEnd(&ScrollVal, NULL, &ClickedItem); + if(ClickedItem) + { + CurDropDownState = 0; + return NewIndex; + } + else + return CurSelection; + } + else + { + CUIRect Button; + pRect->HSplitTop(24.0f, &Button, pRect); + if(DoButton_MenuTab(pID, CurSelection > -1 ? pStr[CurSelection] : "", 0, &Button, CUI::CORNER_ALL, NULL, NULL, NULL, NULL, 4.0f)) + CurDropDownState = 1; + return CurSelection; + } +} + void CMenus::RenderSettingsGraphics(CUIRect MainView) { CUIRect Button, Label; @@ -1025,7 +1085,7 @@ static int s_GfxColorDepth = g_Config.m_GfxColorDepth; static int s_GfxVsync = g_Config.m_GfxVsync; static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples; - static int s_GfxOpenGLVersion = (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4; + static int s_GfxOpenGLVersion = Graphics()->IsConfigModernAPI(); static int s_GfxEnableTextureUnitOptimization = g_Config.m_GfxEnableTextureUnitOptimization; static int s_GfxUsePreinitBuffer = g_Config.m_GfxUsePreinitBuffer; static int s_GfxHighdpi = g_Config.m_GfxHighdpi; @@ -1034,14 +1094,6 @@ MainView.VSplitLeft(350.0f, &MainView, &ModeList); MainView.VSplitLeft(340.0f, &MainView, 0); - // draw allmodes switch - ModeList.HSplitTop(20, &Button, &ModeList); - if(DoButton_CheckBox(&g_Config.m_GfxDisplayAllModes, Localize("Show only supported"), g_Config.m_GfxDisplayAllModes ^ 1, &Button)) - { - g_Config.m_GfxDisplayAllModes ^= 1; - s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen); - } - // display mode list static float s_ScrollValue = 0; int OldSelected = -1; @@ -1053,8 +1105,8 @@ { const int Depth = s_aModes[i].m_Red + s_aModes[i].m_Green + s_aModes[i].m_Blue > 16 ? 24 : 16; if(g_Config.m_GfxColorDepth == Depth && - g_Config.m_GfxScreenWidth == s_aModes[i].m_Width && - g_Config.m_GfxScreenHeight == s_aModes[i].m_Height) + g_Config.m_GfxScreenWidth == s_aModes[i].m_WindowWidth && + g_Config.m_GfxScreenHeight == s_aModes[i].m_WindowHeight) { OldSelected = i; } @@ -1062,8 +1114,8 @@ CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i); if(Item.m_Visible) { - int G = gcd(s_aModes[i].m_Width, s_aModes[i].m_Height); - str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth, s_aModes[i].m_Width / G, s_aModes[i].m_Height / G); + int G = gcd(s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight); + str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight, Depth, s_aModes[i].m_CanvasWidth / G, s_aModes[i].m_CanvasHeight / G); UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, -1); } } @@ -1073,22 +1125,34 @@ { const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16; g_Config.m_GfxColorDepth = Depth; - g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_Width; - g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_Height; + g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_WindowWidth; + g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_WindowHeight; Graphics()->Resize(g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight, true); } // switches - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxBorderless, Localize("Borderless window"), g_Config.m_GfxBorderless, &Button)) - { - Client()->ToggleWindowBordered(); - } - - MainView.HSplitTop(20.0f, &Button, &MainView); - if(DoButton_CheckBox(&g_Config.m_GfxFullscreen, Localize("Fullscreen"), g_Config.m_GfxFullscreen, &Button)) - { - Client()->ToggleFullscreen(); + static float s_ScrollValueDrop = 0; + static const int s_NumWindowMode = 4; + static int s_aWindowModeIDs[s_NumWindowMode]; + const void *aWindowModeIDs[s_NumWindowMode]; + for(int i = 0; i < s_NumWindowMode; ++i) + aWindowModeIDs[i] = &s_aWindowModeIDs[i]; + static int s_WindowModeDropDownState = 0; + const char *pWindowModes[] = {Localize("Windowed"), Localize("Windowed borderless"), Localize("Desktop fullscreen"), Localize("Fullscreen")}; + + OldSelected = (g_Config.m_GfxFullscreen ? (g_Config.m_GfxFullscreen == 1 ? 3 : 2) : (g_Config.m_GfxBorderless ? 1 : 0)); + + const int NewWindowMode = RenderDropDown(s_WindowModeDropDownState, &MainView, OldSelected, aWindowModeIDs, pWindowModes, s_NumWindowMode, &s_NumWindowMode, s_ScrollValueDrop); + if(OldSelected != NewWindowMode) + { + if(NewWindowMode == 0) + Client()->SetWindowParams(0, false); + else if(NewWindowMode == 1) + Client()->SetWindowParams(0, true); + else if(NewWindowMode == 2) + Client()->SetWindowParams(2, false); + else if(NewWindowMode == 3) + Client()->SetWindowParams(1, false); } MainView.HSplitTop(20.0f, &Button, &MainView); @@ -1134,22 +1198,19 @@ g_Config.m_GfxHighDetail ^= 1; MainView.HSplitTop(20.0f, &Button, &MainView); - bool IsNewOpenGL = (g_Config.m_GfxOpenGLMajor == 3 && g_Config.m_GfxOpenGLMinor == 3) || g_Config.m_GfxOpenGLMajor >= 4; - if(DoButton_CheckBox(&g_Config.m_GfxOpenGLMajor, Localize("Use OpenGL 3.3 (experimental)"), IsNewOpenGL, &Button)) + bool IsNewOpenGL = Graphics()->IsConfigModernAPI(); + + if(DoButton_CheckBox(&g_Config.m_GfxOpenGLMajor, Localize("Use modern OpenGL"), IsNewOpenGL, &Button)) { CheckSettings = true; if(IsNewOpenGL) { - g_Config.m_GfxOpenGLMajor = 3; - g_Config.m_GfxOpenGLMinor = 0; - g_Config.m_GfxOpenGLPatch = 0; + Graphics()->GetDriverVersion(GRAPHICS_DRIVER_AGE_TYPE_DEFAULT, g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); IsNewOpenGL = false; } else { - g_Config.m_GfxOpenGLMajor = 3; - g_Config.m_GfxOpenGLMinor = 3; - g_Config.m_GfxOpenGLPatch = 0; + Graphics()->GetDriverVersion(GRAPHICS_DRIVER_AGE_TYPE_MODERN, g_Config.m_GfxOpenGLMajor, g_Config.m_GfxOpenGLMinor, g_Config.m_GfxOpenGLPatch); IsNewOpenGL = true; } } @@ -1628,23 +1689,339 @@ return HSLColor; } -ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha) +ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, bool ClampedLight) { ColorHSLA Color(*pColor, Alpha); - CUIRect Button, Label; + CUIRect Preview, Button, Label; char aBuf[32]; float *paComponent[] = {&Color.h, &Color.s, &Color.l, &Color.a}; const char *aLabels[] = {Localize("Hue"), Localize("Sat."), Localize("Lht."), Localize("Alpha")}; + float SizePerEntry = 20.0f; + float MarginPerEntry = 5.0f; + + float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - 40.0f; + pRect->VSplitLeft(40.0f, &Preview, pRect); + Preview.HSplitTop(OffY / 2.0f, NULL, &Preview); + Preview.HSplitTop(40.0f, &Preview, NULL); + + Graphics()->TextureClear(); + { + const float SizeBorder = 5.0f; + ColorRGBA SetColorRGBA{0.15f, 0.15f, 0.15f, 1}; + Graphics()->SetColor(SetColorRGBA); + int TmpCont = RenderTools()->CreateRoundRectQuadContainer(Preview.x - SizeBorder / 2.0f, Preview.y - SizeBorder / 2.0f, Preview.w + SizeBorder, Preview.h + SizeBorder, 4.0f + SizeBorder / 2.0f, CUI::CORNER_ALL); + Graphics()->RenderQuadContainer(TmpCont, -1); + Graphics()->DeleteQuadContainer(TmpCont); + } + ColorHSLA RenderColorHSLA{Color.r, Color.g, Color.b, Color.a}; + if(ClampedLight) + RenderColorHSLA = RenderColorHSLA.UnclampLighting(); + ColorRGBA SetColorRGBA = color_cast(RenderColorHSLA); + Graphics()->SetColor(SetColorRGBA); + int TmpCont = RenderTools()->CreateRoundRectQuadContainer(Preview.x, Preview.y, Preview.w, Preview.h, 4.0f, CUI::CORNER_ALL); + Graphics()->RenderQuadContainer(TmpCont, -1); + Graphics()->DeleteQuadContainer(TmpCont); + + auto &&RenderHSLColorsRect = [&](CUIRect *pColorRect) { + Graphics()->TextureClear(); + Graphics()->TrianglesBegin(); + + float CurXOff = pColorRect->x; + float SizeColor = pColorRect->w / 6; + + // red to yellow + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 1, 0, 0, 1), + IGraphics::CColorVertex(1, 1, 1, 0, 1), + IGraphics::CColorVertex(2, 1, 0, 0, 1), + IGraphics::CColorVertex(3, 1, 1, 0, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + // yellow to green + CurXOff += SizeColor; + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 1, 1, 0, 1), + IGraphics::CColorVertex(1, 0, 1, 0, 1), + IGraphics::CColorVertex(2, 1, 1, 0, 1), + IGraphics::CColorVertex(3, 0, 1, 0, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + CurXOff += SizeColor; + // green to turquoise + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 0, 1, 0, 1), + IGraphics::CColorVertex(1, 0, 1, 1, 1), + IGraphics::CColorVertex(2, 0, 1, 0, 1), + IGraphics::CColorVertex(3, 0, 1, 1, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + CurXOff += SizeColor; + // turquoise to blue + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 0, 1, 1, 1), + IGraphics::CColorVertex(1, 0, 0, 1, 1), + IGraphics::CColorVertex(2, 0, 1, 1, 1), + IGraphics::CColorVertex(3, 0, 0, 1, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + CurXOff += SizeColor; + // blue to purple + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 0, 0, 1, 1), + IGraphics::CColorVertex(1, 1, 0, 1, 1), + IGraphics::CColorVertex(2, 0, 0, 1, 1), + IGraphics::CColorVertex(3, 1, 0, 1, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + CurXOff += SizeColor; + // purple to red + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, 1, 0, 1, 1), + IGraphics::CColorVertex(1, 1, 0, 0, 1), + IGraphics::CColorVertex(2, 1, 0, 1, 1), + IGraphics::CColorVertex(3, 1, 0, 0, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + Graphics()->TrianglesEnd(); + }; + + auto &&RenderHSLSatRect = [&](CUIRect *pColorRect, ColorRGBA &CurColor) { + Graphics()->TextureClear(); + Graphics()->TrianglesBegin(); + + float CurXOff = pColorRect->x; + float SizeColor = pColorRect->w; + + ColorHSLA RightColor = color_cast(CurColor); + ColorHSLA LeftColor = color_cast(CurColor); + + LeftColor.g = 0; + RightColor.g = 1; + + ColorRGBA RightColorRGBA = color_cast(RightColor); + ColorRGBA LeftColorRGBA = color_cast(LeftColor); + + // saturation + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), + IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + Graphics()->TrianglesEnd(); + }; + + auto &&RenderHSLLightRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorSat) { + Graphics()->TextureClear(); + Graphics()->TrianglesBegin(); + + float CurXOff = pColorRect->x; + float SizeColor = pColorRect->w / (ClampedLight ? 1.0f : 2.0f); + + ColorHSLA RightColor = color_cast(CurColorSat); + ColorHSLA LeftColor = color_cast(CurColorSat); + + LeftColor.b = ColorHSLA::DARKEST_LGT; + RightColor.b = 1; + + ColorRGBA RightColorRGBA = color_cast(RightColor); + ColorRGBA LeftColorRGBA = color_cast(LeftColor); + + if(!ClampedLight) + CurXOff += SizeColor; + + // light + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), + IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + if(!ClampedLight) + { + CurXOff -= SizeColor; + LeftColor.b = 0; + RightColor.b = ColorHSLA::DARKEST_LGT; + + ColorRGBA RightColorRGBA = color_cast(RightColor); + ColorRGBA LeftColorRGBA = color_cast(LeftColor); + + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), + IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), + IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + Graphics()->TrianglesEnd(); + }; + + auto &&RenderHSLAlphaRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorFull) { + Graphics()->TextureClear(); + Graphics()->TrianglesBegin(); + + float CurXOff = pColorRect->x; + float SizeColor = pColorRect->w; + + ColorHSLA RightColor = color_cast(CurColorFull); + ColorHSLA LeftColor = color_cast(CurColorFull); + + LeftColor.a = 0; + RightColor.a = 1; + + ColorRGBA RightColorRGBA = color_cast(RightColor); + ColorRGBA LeftColorRGBA = color_cast(LeftColor); + + // alpha + { + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), + IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a), + IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), + IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a)}; + Graphics()->SetColorVertex(Array, 4); + + IGraphics::CFreeformItem Freeform( + CurXOff, pColorRect->y, + CurXOff + SizeColor, pColorRect->y, + CurXOff, pColorRect->y + pColorRect->h, + CurXOff + SizeColor, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + } + + Graphics()->TrianglesEnd(); + }; + for(int i = 0; i < 3 + Alpha; i++) { - pRect->HSplitTop(20.0f, &Button, pRect); + pRect->HSplitTop(SizePerEntry, &Button, pRect); + pRect->HSplitTop(MarginPerEntry, NULL, pRect); Button.VSplitLeft(10.0f, 0, &Button); Button.VSplitLeft(100.0f, &Label, &Button); - Button.HMargin(2.0f, &Button); + + RenderTools()->DrawUIRect(&Button, ColorRGBA{0.15f, 0.15f, 0.15f, 1.0f}, CUI::CORNER_ALL, 1.0f); + + Button.Margin(2.0f, &Button); str_format(aBuf, sizeof(aBuf), "%s: %03d", aLabels[i], (int)(*paComponent[i] * 255)); UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); - *paComponent[i] = DoScrollbarH(&((char *)pColor)[i], &Button, *paComponent[i]); + + ColorHSLA CurColorPureHSLA{RenderColorHSLA.r, 1, 0.5f, 1}; + ColorRGBA CurColorPure = color_cast(CurColorPureHSLA); + ColorRGBA ColorInner{1, 1, 1, 0.25f}; + + if(i == 0) + { + ColorInner = CurColorPure; + RenderHSLColorsRect(&Button); + } + else if(i == 1) + { + RenderHSLSatRect(&Button, CurColorPure); + ColorInner = color_cast(ColorHSLA{CurColorPureHSLA.r, *paComponent[1], CurColorPureHSLA.b, 1}); + } + else if(i == 2) + { + ColorRGBA CurColorSat = color_cast(ColorHSLA{CurColorPureHSLA.r, *paComponent[1], 0.5f, 1}); + RenderHSLLightRect(&Button, CurColorSat); + float LightVal = *paComponent[2]; + if(ClampedLight) + LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); + ColorInner = color_cast(ColorHSLA{CurColorPureHSLA.r, *paComponent[1], LightVal, 1}); + } + else if(i == 3) + { + ColorRGBA CurColorFull = color_cast(ColorHSLA{CurColorPureHSLA.r, *paComponent[1], *paComponent[2], 1}); + RenderHSLAlphaRect(&Button, CurColorFull); + float LightVal = *paComponent[2]; + if(ClampedLight) + LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); + ColorInner = color_cast(ColorHSLA{CurColorPureHSLA.r, *paComponent[1], LightVal, *paComponent[3]}); + } + + *paComponent[i] = DoScrollbarH(&((char *)pColor)[i], &Button, *paComponent[i], true, &ColorInner); } *pColor = Color.Pack(Alpha); @@ -1701,6 +2078,27 @@ DoLaserPreview(&Section, LaserOutlineColor, LaserInnerColor); + HUD.HSplitTop(25.0f, 0x0, &HUD); + HUD.HSplitTop(20.0f, &SectionTwo, &HUD); + + UI()->DoLabelScaled(&SectionTwo, Localize("Hookline"), 20.0f, -1); + + HUD.HSplitTop(5.0f, 0x0, &HUD); + HUD.HSplitTop(25.0f, &SectionTwo, &HUD); + + static int HookCollNoCollResetID, HookCollHookableCollResetID, HookCollTeeCollResetID; + DoLine_ColorPicker(&HookCollNoCollResetID, 25.0f, 194.0f, 13.0f, 5.0f, &SectionTwo, Localize("No hit"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false); + + HUD.HSplitTop(5.0f, 0x0, &HUD); + HUD.HSplitTop(25.0f, &SectionTwo, &HUD); + + DoLine_ColorPicker(&HookCollHookableCollResetID, 25.0f, 194.0f, 13.0f, 5.0f, &SectionTwo, Localize("Hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false); + + HUD.HSplitTop(5.0f, 0x0, &HUD); + HUD.HSplitTop(25.0f, &SectionTwo, &HUD); + + DoLine_ColorPicker(&HookCollTeeCollResetID, 25.0f, 194.0f, 13.0f, 5.0f, &SectionTwo, Localize("Tee"), &g_Config.m_ClHookCollColorTeeColl, ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f), false); + // ***** Chat ***** // if(DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_ClChatOld, Localize("Use old chat style"), &g_Config.m_ClChatOld, &Chat, LineMargin)) @@ -2040,7 +2438,8 @@ Left.HSplitTop(20.0f, &Button, &Left); bool ShowOwnTeam = g_Config.m_ClShowOthers == 2; - if(DoButton_CheckBox(&ShowOwnTeam, Localize("Show others (own team only)"), ShowOwnTeam, &Button)) + static int s_ShowOwnTeamID = 0; + if(DoButton_CheckBox(&s_ShowOwnTeamID, Localize("Show others (own team only)"), ShowOwnTeam, &Button)) { g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != 2 ? 2 : 0; } @@ -2147,7 +2546,8 @@ Left.HSplitTop(20.0f, &Button, &Left); bool UseCurrentMap = str_comp(g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0; - if(DoButton_CheckBox(&UseCurrentMap, Localize("Use current map as background"), UseCurrentMap, &Button)) + static int s_UseCurrentMapID = 0; + if(DoButton_CheckBox(&s_UseCurrentMapID, Localize("Use current map as background"), UseCurrentMap, &Button)) { if(UseCurrentMap) g_Config.m_ClBackgroundEntities[0] = '\0'; diff -Nru ddnet-15.3.2/src/game/client/components/menus_start.cpp ddnet-15.5.4/src/game/client/components/menus_start.cpp --- ddnet-15.3.2/src/game/client/components/menus_start.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/menus_start.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -37,9 +37,9 @@ ExtMenu.HSplitBottom(20.0f, &ExtMenu, &Button); static int s_DiscordButton; - if(DoButton_Menu(&s_DiscordButton, "Discord", 0, &Button, 0, CUI::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) + if(DoButton_Menu(&s_DiscordButton, Localize("Discord"), 0, &Button, 0, CUI::CORNER_ALL, 5.0f, 0.0f, vec4(0.0f, 0.0f, 0.0f, 0.5f), vec4(0.0f, 0.0f, 0.0f, 0.25f))) { - if(!open_link("https://ddnet.tw/discord")) + if(!open_link(Localize("https://ddnet.tw/discord"))) { dbg_msg("menus", "couldn't open link"); } diff -Nru ddnet-15.3.2/src/game/client/components/motd.cpp ddnet-15.5.4/src/game/client/components/motd.cpp --- ddnet-15.3.2/src/game/client/components/motd.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/motd.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -80,14 +80,14 @@ if(g_Config.m_ClPrintMotd && m_aServerMotd[k] == '\n') { m_aServerMotd[k] = '\0'; - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, true); + m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor))); m_aServerMotd[k] = '\n'; pLast = m_aServerMotd + k + 1; } } m_aServerMotd[sizeof(m_aServerMotd) - 1] = '\0'; if(g_Config.m_ClPrintMotd && *pLast != '\0') - m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, true); + m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, color_cast(ColorHSLA(g_Config.m_ClMessageHighlightColor))); if(m_aServerMotd[0] && g_Config.m_ClMotdTime) m_ServerMotdTime = time() + time_freq() * g_Config.m_ClMotdTime; diff -Nru ddnet-15.3.2/src/game/client/components/players.cpp ddnet-15.5.4/src/game/client/components/players.cpp --- ddnet-15.3.2/src/game/client/components/players.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/players.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -26,6 +26,8 @@ #include "players.h" +#include + void CPlayers::RenderHand(CTeeRenderInfo *pInfo, vec2 CenterPos, vec2 Dir, float AngleOffset, vec2 PostRotOffset, float Alpha) { vec2 HandPos = CenterPos + Dir; @@ -293,7 +295,7 @@ vec2 FinishPos = InitPos + ExDirection * (m_pClient->m_Tuning[g_Config.m_ClDummy].m_HookLength - 42.0f); Graphics()->LinesBegin(); - ColorRGBA HookCollColor(1.0f, 0.0f, 0.0f); + ColorRGBA HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorNoColl)); float PhysSize = 28.0f; @@ -321,13 +323,13 @@ { if(Hit != TILE_NOHOOK) { - HookCollColor = ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f); + HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorHookableColl)); } } if(m_pClient->IntersectCharacter(OldPos, FinishPos, FinishPos, ClientID) != -1) { - HookCollColor = ColorRGBA(1.0f, 1.0f, 0.0f); + HookCollColor = color_cast(ColorHSLA(g_Config.m_ClHookCollColorTeeColl)); break; } diff -Nru ddnet-15.3.2/src/game/client/components/scoreboard.cpp ddnet-15.5.4/src/game/client/components/scoreboard.cpp --- ddnet-15.3.2/src/game/client/components/scoreboard.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/scoreboard.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -46,7 +46,7 @@ void CScoreboard::OnMessage(int MsgType, void *pRawMsg) { - if(MsgType == NETMSGTYPE_SV_RECORD) + if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY) { CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg; m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100; @@ -172,6 +172,8 @@ else if(Team == -8) upper24 = true; + bool IsTeamplayTeam = Team > TEAM_SPECTATORS; + if(Team < -1) Team = 0; @@ -254,7 +256,6 @@ } // calculate measurements - x += 10.0f; float LineHeight = 60.0f; float TeeSizeMod = 1.0f; float Spacing = 16.0f; @@ -291,14 +292,20 @@ RoundRadius = 15.0f; } - float ScoreOffset = x + 10.0f, ScoreLength = TextRender()->TextWidth(0, FontSize, "00:00:00", -1, -1.0f); + float ScoreOffset = x + 10.0f + 10.0f, ScoreLength = TextRender()->TextWidth(0, FontSize, "00:00:00", -1, -1.0f); + if(IsTeamplayTeam) + ScoreLength = TextRender()->TextWidth(0, FontSize, "99999", -1, -1.0f); float TeeOffset = ScoreOffset + ScoreLength + 15.0f, TeeLength = 60 * TeeSizeMod; float NameOffset = TeeOffset + TeeLength, NameLength = 300.0f - TeeLength; - float PingOffset = x + 610.0f, PingLength = 65.0f; - float CountryOffset = PingOffset - (LineHeight - Spacing - TeeSizeMod * 5.0f) * 2.0f, CountryLength = (LineHeight - Spacing - TeeSizeMod * 5.0f) * 2.0f; - float ClanOffset = x + 360.0f, ClanLength = 240.0f - CountryLength; + float CountryLength = (LineHeight - Spacing - TeeSizeMod * 5.0f) * 2.0f; + float PingLength = 65.0f; + float PingOffset = x + w - PingLength - 10.0f - 10.0f; + float CountryOffset = PingOffset - CountryLength; + float ClanLength = w - ((NameOffset - x) + NameLength) - (w - (CountryOffset - x)); + float ClanOffset = CountryOffset - ClanLength; // render headlines + x += 10.0f; y += 50.0f; float HeadlineFontsize = 22.0f; const char *pScore = (m_pClient->m_GameInfo.m_TimeScore && g_Config.m_ClDDRaceScoreBoard) ? Localize("Time") : Localize("Score"); @@ -308,7 +315,7 @@ TextRender()->Text(0, NameOffset, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Name"), -1.0f); tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Clan"), -1, -1.0f); - TextRender()->Text(0, ClanOffset + ClanLength / 2 - tw / 2, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Clan"), -1.0f); + TextRender()->Text(0, ClanOffset + (ClanLength - tw) / 2, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Clan"), -1.0f); tw = TextRender()->TextWidth(0, HeadlineFontsize, Localize("Ping"), -1, -1.0f); TextRender()->Text(0, PingOffset + PingLength - tw, y + (HeadlineFontsize * 2.f - HeadlineFontsize) / 2.f, HeadlineFontsize, Localize("Ping"), -1.0f); @@ -451,7 +458,12 @@ // avatar CTeeRenderInfo TeeInfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo; TeeInfo.m_Size *= TeeSizeMod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(TeeOffset + TeeLength / 2, y + LineHeight / 2)); + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(TeeOffset + TeeLength / 2, y + LineHeight / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); // name TextRender()->SetCursor(&Cursor, NameOffset, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); @@ -491,8 +503,8 @@ else TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - tw = TextRender()->TextWidth(nullptr, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1, -1.0f); - TextRender()->SetCursor(&Cursor, ClanOffset + ClanLength / 2 - tw / 2, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); + tw = minimum(TextRender()->TextWidth(nullptr, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1, -1.0f), ClanLength); + TextRender()->SetCursor(&Cursor, ClanOffset + (ClanLength - tw) / 2, y + (LineHeight - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = ClanLength; TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1); @@ -609,7 +621,8 @@ Graphics()->MapScreen(0, 0, Width, Height); - float w = 700.0f; + float w = 750.0f; + float ExtraWidthSingle = 20.0f; if(m_pClient->m_Snap.m_pGameInfoObj) { @@ -632,7 +645,8 @@ } else { - RenderScoreboard(Width / 2 - w / 2, 150.0f, w, 0, 0); + w += ExtraWidthSingle; + RenderScoreboard(Width / 2 - w / 2, 150.0f, w, -2, 0); } } else @@ -664,6 +678,9 @@ TextRender()->Text(0, Width / 2 - w / 2, 39, 86.0f, aText, -1.0f); } + //decrease width, because team games use additional offsets + w -= 10.0f; + int NumPlayers = maximum(m_pClient->m_Snap.m_aTeamSize[TEAM_RED], m_pClient->m_Snap.m_aTeamSize[TEAM_BLUE]); RenderScoreboard(Width / 2 - w - 5.0f, 150.0f, w, TEAM_RED, pRedClanName ? pRedClanName : Localize("Red team"), NumPlayers); RenderScoreboard(Width / 2 + 5.0f, 150.0f, w, TEAM_BLUE, pBlueClanName ? pBlueClanName : Localize("Blue team"), NumPlayers); diff -Nru ddnet-15.3.2/src/game/client/components/skins.cpp ddnet-15.5.4/src/game/client/components/skins.cpp --- ddnet-15.3.2/src/game/client/components/skins.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/skins.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -167,13 +167,15 @@ int BodyOutlineGridPixelsWidth = (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridx); int BodyOutlineGridPixelsHeight = (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_pSet->m_Gridy); - int BodyOutlineSize = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_H * BodyOutlineGridPixelsHeight; + int BodyOutlineWidth = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_W * BodyOutlineGridPixelsWidth; + int BodyOutlineHeight = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_H * BodyOutlineGridPixelsHeight; int BodyOutlineOffsetX = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_X * BodyOutlineGridPixelsWidth; int BodyOutlineOffsetY = g_pData->m_aSprites[SPRITE_TEE_BODY_OUTLINE].m_Y * BodyOutlineGridPixelsHeight; - int BodySize = g_pData->m_aSprites[SPRITE_TEE_BODY].m_H * (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy); // body size - if(BodySize > Info.m_Height) + int BodyWidth = g_pData->m_aSprites[SPRITE_TEE_BODY].m_W * (Info.m_Width / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridx); // body width + int BodyHeight = g_pData->m_aSprites[SPRITE_TEE_BODY].m_H * (Info.m_Height / g_pData->m_aSprites[SPRITE_TEE_BODY].m_pSet->m_Gridy); // body height + if(BodyWidth > Info.m_Width || BodyHeight > Info.m_Height) return 0; unsigned char *d = (unsigned char *)Info.m_pData; int Pitch = Info.m_Width * 4; @@ -181,8 +183,8 @@ // dig out blood color { int aColors[3] = {0}; - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) + for(int y = 0; y < BodyHeight; y++) + for(int x = 0; x < BodyWidth; x++) { uint8_t AlphaValue = d[y * Pitch + x * 4 + 3]; if(AlphaValue > 128) @@ -198,10 +200,10 @@ Skin.m_BloodColor = ColorRGBA(0, 0, 0, 1); } - CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, 0, 0, BodySize, BodySize); + CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, 0, 0, BodyWidth, BodyHeight); // body outline metrics - CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, BodyOutlineOffsetX, BodyOutlineOffsetY, BodyOutlineSize, BodyOutlineSize); + CheckMetrics(Skin.m_Metrics.m_Body, d, Pitch, BodyOutlineOffsetX, BodyOutlineOffsetY, BodyOutlineWidth, BodyOutlineHeight); // get feet size CheckMetrics(Skin.m_Metrics.m_Feet, d, Pitch, FeetOffsetX, FeetOffsetY, FeetWidth, FeetHeight); @@ -226,8 +228,8 @@ int NewWeight = 192; // find most common frequence - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) + for(int y = 0; y < BodyHeight; y++) + for(int x = 0; x < BodyWidth; x++) { if(d[y * Pitch + x * 4 + 3] > 128) Freq[d[y * Pitch + x * 4]]++; @@ -242,8 +244,8 @@ // reorder int InvOrgWeight = 255 - OrgWeight; int InvNewWeight = 255 - NewWeight; - for(int y = 0; y < BodySize; y++) - for(int x = 0; x < BodySize; x++) + for(int y = 0; y < BodyHeight; y++) + for(int x = 0; x < BodyWidth; x++) { int v = d[y * Pitch + x * 4]; if(v <= OrgWeight && OrgWeight == 0) diff -Nru ddnet-15.3.2/src/game/client/components/spectator.cpp ddnet-15.5.4/src/game/client/components/spectator.cpp --- ddnet-15.3.2/src/game/client/components/spectator.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/spectator.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -413,7 +413,13 @@ CTeeRenderInfo TeeInfo = m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_RenderInfo; TeeInfo.m_Size *= TeeSizeMod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(Width / 2.0f + x + 20.0f, Height / 2.0f + y + 20.0f), TeeAlpha); + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + vec2 TeeRenderPos(Width / 2.0f + x + 20.0f, Height / 2.0f + y + BoxMove + LineHeight / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos, TeeAlpha); if(m_pClient->m_aClients[m_pClient->m_Snap.m_paInfoByDDTeamName[i]->m_ClientID].m_Friend) { diff -Nru ddnet-15.3.2/src/game/client/components/statboard.cpp ddnet-15.5.4/src/game/client/components/statboard.cpp --- ddnet-15.3.2/src/game/client/components/statboard.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/components/statboard.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -156,9 +156,9 @@ } } - // Dirty hack. Do not show scoreboard if there are more than 16 players - // remove as soon as support of more than 16 players is required - if(NumPlayers > 16) + // Dirty hack. Do not show scoreboard if there are more than 32 players + // remove as soon as support of more than 32 players is required + if(NumPlayers > 32) return; //clear motd if it is active @@ -248,14 +248,20 @@ float FontSize = 24.0f; float LineHeight = 50.0f; float TeeSizemod = 0.8f; - float TeeOffset = 0.0f; + float ContentLineOffset = LineHeight * 0.05f; - if(NumPlayers > 14) + if(NumPlayers > 16) + { + FontSize = 20.0f; + LineHeight = 22.0f; + TeeSizemod = 0.34f; + ContentLineOffset = 0; + } + else if(NumPlayers > 14) { FontSize = 24.0f; LineHeight = 40.0f; TeeSizemod = 0.7f; - TeeOffset = -5.0f; } for(int j = 0; j < NumPlayers; j++) @@ -269,13 +275,19 @@ Graphics()->TextureClear(); Graphics()->QuadsBegin(); Graphics()->SetColor(1, 1, 1, 0.25f); - RenderTools()->DrawRoundRect(x, y, StatboardContentWidth - 20, LineHeight * 0.95f, 17.0f); + RenderTools()->DrawRoundRect(x - 10, y + ContentLineOffset / 2, StatboardContentWidth, LineHeight - ContentLineOffset, 0); Graphics()->QuadsEnd(); } CTeeRenderInfo Teeinfo = m_pClient->m_aClients[pInfo->m_ClientID].m_RenderInfo; Teeinfo.m_Size *= TeeSizemod; - RenderTools()->RenderTee(CAnimState::GetIdle(), &Teeinfo, EMOTE_NORMAL, vec2(1, 0), vec2(x + 28, y + 28 + TeeOffset)); + + CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Teeinfo, OffsetToMid); + vec2 TeeRenderPos(x + Teeinfo.m_Size / 2, y + LineHeight / 2.0f + OffsetToMid.y); + + RenderTools()->RenderTee(pIdleState, &Teeinfo, EMOTE_NORMAL, vec2(1, 0), TeeRenderPos); char aBuf[128]; CTextCursor Cursor; diff -Nru ddnet-15.3.2/src/game/client/gameclient.cpp ddnet-15.5.4/src/game/client/gameclient.cpp --- ddnet-15.3.2/src/game/client/gameclient.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/gameclient.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -26,7 +26,6 @@ #include "race.h" #include "render.h" -#include #include #include @@ -113,11 +112,11 @@ CGameClient::CStack::CStack() { m_Num = 0; } void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_Num++] = pComponent; } -const char *CGameClient::Version() { return GAME_VERSION; } -const char *CGameClient::NetVersion() { return GAME_NETVERSION; } -int CGameClient::DDNetVersion() { return CLIENT_VERSIONNR; } -const char *CGameClient::DDNetVersionStr() { return m_aDDNetVersionStr; } -const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); } +const char *CGameClient::Version() const { return GAME_VERSION; } +const char *CGameClient::NetVersion() const { return GAME_NETVERSION; } +int CGameClient::DDNetVersion() const { return CLIENT_VERSIONNR; } +const char *CGameClient::DDNetVersionStr() const { return m_aDDNetVersionStr; } +const char *CGameClient::GetItemName(int Type) const { return m_NetObjHandler.GetObjName(Type); } void CGameClient::OnConsoleInit() { @@ -541,6 +540,15 @@ m_LastNewPredictedTick[0] = -1; m_LastNewPredictedTick[1] = -1; + m_LocalTuneZone[0] = 0; + m_LocalTuneZone[1] = 0; + + m_ExpectingTuningForZone[0] = -1; + m_ExpectingTuningForZone[1] = -1; + + m_ReceivedTuning[0] = false; + m_ReceivedTuning[1] = false; + InvalidateSnapshot(); for(auto &Client : m_aClients) @@ -654,25 +662,10 @@ // update the local character and spectate position UpdatePositions(); - // display gfx warnings - if(g_Config.m_GfxShowWarnings == 1) - { - SWarning *pWarning = Graphics()->GetCurWarning(); - if(pWarning != NULL) - { - if(m_pMenus->CanDisplayWarning()) - { - m_pMenus->PopupWarning(Localize("Warning"), pWarning->m_aWarningMsg, "Ok", 10000000); - pWarning->m_WasShown = true; - } - } - } - - // display client warnings - SWarning *pWarning = Client()->GetCurWarning(); - if(pWarning != NULL) + // display gfx & client warnings + for(SWarning *pWarning : {Graphics()->GetCurWarning(), Client()->GetCurWarning()}) { - if(m_pMenus->CanDisplayWarning()) + if(pWarning != NULL && m_pMenus->CanDisplayWarning()) { m_pMenus->PopupWarning(Localize("Warning"), pWarning->m_aWarningMsg, "Ok", 10000000); pWarning->m_WasShown = true; @@ -798,6 +791,7 @@ m_ServerMode = SERVERMODE_PURE; + m_ReceivedTuning[IsDummy ? !g_Config.m_ClDummy : g_Config.m_ClDummy] = true; // apply new tuning m_Tuning[IsDummy ? !g_Config.m_ClDummy : g_Config.m_ClDummy] = NewTuning; return; @@ -862,7 +856,7 @@ g_GameClient.m_pSounds->Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f); } } - else if(MsgId == NETMSGTYPE_SV_TEAMSSTATE) + else if(MsgId == NETMSGTYPE_SV_TEAMSSTATE || MsgId == NETMSGTYPE_SV_TEAMSSTATELEGACY) { unsigned int i; @@ -1619,7 +1613,7 @@ if(!m_DDRaceMsgSent[0] && m_Snap.m_pLocalInfo) { - CMsgPacker Msg(NETMSGTYPE_CL_ISDDNET, false); + CMsgPacker Msg(NETMSGTYPE_CL_ISDDNETLEGACY, false); Msg.AddInt(CLIENT_VERSIONNR); Client()->SendMsgY(&Msg, MSGFLAG_VITAL, 0); m_DDRaceMsgSent[0] = true; @@ -1627,7 +1621,7 @@ if(!m_DDRaceMsgSent[1] && m_Snap.m_pLocalInfo && Client()->DummyConnected()) { - CMsgPacker Msg(NETMSGTYPE_CL_ISDDNET, false); + CMsgPacker Msg(NETMSGTYPE_CL_ISDDNETLEGACY, false); Msg.AddInt(CLIENT_VERSIONNR); Client()->SendMsgY(&Msg, MSGFLAG_VITAL, 1); m_DDRaceMsgSent[1] = true; @@ -1645,13 +1639,15 @@ m_ShowOthers[g_Config.m_ClDummy] = g_Config.m_ClShowOthers; } - float ZoomToSend = m_pCamera->m_ZoomSmoothingTarget == .0 ? m_pCamera->m_Zoom // Initial - : - m_pCamera->m_ZoomSmoothingTarget > m_pCamera->m_Zoom ? m_pCamera->m_ZoomSmoothingTarget // Zooming out - : - (m_pCamera->m_ZoomSmoothingTarget < m_pCamera->m_Zoom && m_LastZoom > 0) ? m_LastZoom // Zooming in - : - m_pCamera->m_Zoom; // Not zooming + float ZoomToSend = m_pCamera->m_Zoom; + if(m_pCamera->m_ZoomSmoothingTarget != .0) + { + if(m_pCamera->m_ZoomSmoothingTarget > m_pCamera->m_Zoom) // Zooming out + ZoomToSend = m_pCamera->m_ZoomSmoothingTarget; + else if(m_pCamera->m_ZoomSmoothingTarget < m_pCamera->m_Zoom && m_LastZoom > 0) // Zooming in + ZoomToSend = m_LastZoom; + } + if(ZoomToSend != m_LastZoom || Graphics()->ScreenAspect() != m_LastScreenAspect || (Client()->DummyConnected() && !m_LastDummyConnected)) { CNetMsg_Cl_ShowDistance Msg; @@ -1673,10 +1669,10 @@ m_pGhost->OnNewSnapshot(); m_pRaceDemo->OnNewSnapshot(); - // detect air jump for unpredicted players + // detect air jump for other players for(int i = 0; i < MAX_CLIENTS; i++) if(m_Snap.m_aCharacters[i].m_Active && (m_Snap.m_aCharacters[i].m_Cur.m_Jumped & 2) && !(m_Snap.m_aCharacters[i].m_Prev.m_Jumped & 2)) - if(!Predict() || (!AntiPingPlayers() && i != m_Snap.m_LocalClientID)) + if(!Predict() || (i != m_Snap.m_LocalClientID && (!AntiPingPlayers() || i != m_PredictedDummyID))) { vec2 Pos = mix(vec2(m_Snap.m_aCharacters[i].m_Prev.m_X, m_Snap.m_aCharacters[i].m_Prev.m_Y), vec2(m_Snap.m_aCharacters[i].m_Cur.m_X, m_Snap.m_aCharacters[i].m_Cur.m_Y), @@ -1729,12 +1725,20 @@ bool Dummy = g_Config.m_ClDummy ^ m_IsDummySwapping; m_PredictedWorld.CopyWorld(&m_GameWorld); - // don't predict inactive players + // don't predict inactive players, or entities from other teams for(int i = 0; i < MAX_CLIENTS; i++) if(CCharacter *pChar = m_PredictedWorld.GetCharacterByID(i)) - if(!m_Snap.m_aCharacters[i].m_Active && pChar->m_SnapTicks > 10) + if((!m_Snap.m_aCharacters[i].m_Active && pChar->m_SnapTicks > 10) || IsOtherTeam(i)) pChar->Destroy(); + CProjectile *pProjNext = 0; + for(CProjectile *pProj = (CProjectile *)m_PredictedWorld.FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = pProjNext) + { + pProjNext = (CProjectile *)pProj->TypeNext(); + if(IsOtherTeam(pProj->GetOwner())) + m_PredictedWorld.RemoveEntity(pProj); + } + CCharacter *pLocalChar = m_PredictedWorld.GetCharacterByID(m_Snap.m_LocalClientID); if(!pLocalChar) return; @@ -1813,6 +1817,17 @@ m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos); } } + + // check if we want to trigger predicted airjump for dummy + if(AntiPingPlayers() && pDummyChar && Tick > m_LastNewPredictedTick[!Dummy]) + { + m_LastNewPredictedTick[!Dummy] = Tick; + vec2 Pos = pDummyChar->Core()->m_Pos; + int Events = pDummyChar->Core()->m_TriggeredEvents; + if(g_Config.m_ClPredict) + if(Events & COREEVENT_AIR_JUMP) + m_pEffects->AirJump(Pos); + } } // detect mispredictions of other players and make corrections smoother when possible @@ -2207,6 +2222,8 @@ void CGameClient::UpdatePrediction() { + m_GameWorld.m_WorldConfig.m_UseTuneZones = m_GameInfo.m_PredictDDRaceTiles; + if(!m_Snap.m_pLocalCharacter) { if(CCharacter *pLocalChar = m_GameWorld.GetCharacterByID(m_Snap.m_LocalClientID)) @@ -2226,17 +2243,68 @@ m_GameWorld.m_WorldConfig.m_IsSolo = !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_HasExtendedData && !m_Tuning[g_Config.m_ClDummy].m_PlayerCollision && !m_Tuning[g_Config.m_ClDummy].m_PlayerHooking; // update the tuning/tunezone at the local character position with the latest tunings received before the new snapshot - int TuneZone = Collision()->IsTune(Collision()->GetMapIndex(vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y))); - if(!TuneZone || !m_GameWorld.m_WorldConfig.m_PredictTiles) - m_GameWorld.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + vec2 LocalCharPos = vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y); + m_GameWorld.m_Core.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + + int TuneZone = 0; + if(m_GameWorld.m_WorldConfig.m_UseTuneZones) + { + TuneZone = Collision()->IsTune(Collision()->GetMapIndex(LocalCharPos)); + + if(TuneZone != m_LocalTuneZone[g_Config.m_ClDummy]) + { + // our tunezone changed, expecting tuning message + m_LocalTuneZone[g_Config.m_ClDummy] = m_ExpectingTuningForZone[g_Config.m_ClDummy] = TuneZone; + m_ExpectingTuningSince[g_Config.m_ClDummy] = 0; + } + + if(m_ExpectingTuningForZone[g_Config.m_ClDummy] >= 0) + { + if(m_ReceivedTuning[g_Config.m_ClDummy]) + { + dbg_msg("tunezone", "got tuning for zone %d", m_ExpectingTuningForZone[g_Config.m_ClDummy]); + m_GameWorld.TuningList()[m_ExpectingTuningForZone[g_Config.m_ClDummy]] = m_Tuning[g_Config.m_ClDummy]; + m_ReceivedTuning[g_Config.m_ClDummy] = false; + m_ExpectingTuningForZone[g_Config.m_ClDummy] = -1; + } + else if(m_ExpectingTuningSince[g_Config.m_ClDummy] >= 5) + { + // if we are expecting tuning for more than 10 snaps (less than a quarter of a second) + // it is probably dropped or it was received out of order + // or applied to another tunezone. + // we need to fallback to current tuning to fix ourselves. + m_ExpectingTuningForZone[g_Config.m_ClDummy] = -1; + m_ExpectingTuningSince[g_Config.m_ClDummy] = 0; + m_ReceivedTuning[g_Config.m_ClDummy] = false; + dbg_msg("tunezone", "the tuning was missed"); + } + else + { + // if we are expecting tuning and have not received one yet. + // do not update any tuning, so we don't apply it to the wrong tunezone. + dbg_msg("tunezone", "waiting for tuning for zone %d", m_ExpectingTuningForZone[g_Config.m_ClDummy]); + m_ExpectingTuningSince[g_Config.m_ClDummy]++; + } + } + else + { + // if we have processed what we need, and the tuning is still wrong due to out of order messege + // fix our tuning by using the current one + m_GameWorld.TuningList()[TuneZone] = m_Tuning[g_Config.m_ClDummy]; + m_ExpectingTuningSince[g_Config.m_ClDummy] = 0; + m_ReceivedTuning[g_Config.m_ClDummy] = false; + } + } else - m_GameWorld.TuningList()[TuneZone] = m_GameWorld.m_Core.m_Tuning[g_Config.m_ClDummy] = m_Tuning[g_Config.m_ClDummy]; + { + m_GameWorld.TuningList()[0] = m_Tuning[g_Config.m_ClDummy]; + } // if ddnetcharacter is available, ignore server-wide tunings for hook and collision if(m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_HasExtendedData) { - m_GameWorld.m_Tuning[g_Config.m_ClDummy].m_PlayerCollision = 1; - m_GameWorld.m_Tuning[g_Config.m_ClDummy].m_PlayerHooking = 1; + m_GameWorld.m_Core.m_Tuning[g_Config.m_ClDummy].m_PlayerCollision = 1; + m_GameWorld.m_Core.m_Tuning[g_Config.m_ClDummy].m_PlayerHooking = 1; } // restore characters from previously saved ones if they temporarily left the snapshot @@ -2350,6 +2418,7 @@ m_Snap.m_aCharacters[i].m_HasExtendedData ? &m_Snap.m_aCharacters[i].m_ExtendedData : 0, GameTeam, IsLocal); } + for(int Index = 0; Index < Num; Index++) { IClient::CSnapItem Item; @@ -2383,7 +2452,7 @@ Client()->IntraGameTick(g_Config.m_ClDummy)); vec2 Pos = UnpredPos; - if(Predict() && (i == m_Snap.m_LocalClientID || AntiPingPlayers())) + if(Predict() && (i == m_Snap.m_LocalClientID || (AntiPingPlayers() && !IsOtherTeam(i)))) { m_aClients[i].m_Predicted.Write(&m_aClients[i].m_RenderCur); m_aClients[i].m_PrevPredicted.Write(&m_aClients[i].m_RenderPrev); @@ -2997,6 +3066,21 @@ pfnCallback(pResult, pCallbackUserData); } +void CGameClient::DummyResetInput() +{ + if(!Client()->DummyConnected()) + return; + + if((m_DummyInput.m_Fire & 1) != 0) + m_DummyInput.m_Fire++; + + m_pControls->ResetInput(!g_Config.m_ClDummy); + m_pControls->m_InputData[!g_Config.m_ClDummy].m_Hook = 0; + m_pControls->m_InputData[!g_Config.m_ClDummy].m_Fire = m_DummyInput.m_Fire; + + m_DummyInput = m_pControls->m_InputData[!g_Config.m_ClDummy]; +} + bool CGameClient::CanDisplayWarning() { return m_pMenus->CanDisplayWarning(); diff -Nru ddnet-15.3.2/src/game/client/gameclient.h ddnet-15.5.4/src/game/client/gameclient.h --- ddnet-15.3.2/src/game/client/gameclient.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/gameclient.h 2021-06-20 09:38:48.000000000 +0000 @@ -238,6 +238,10 @@ }; CSnapState m_Snap; + int m_LocalTuneZone[2]; + bool m_ReceivedTuning[2]; + int m_ExpectingTuningForZone[2]; + int m_ExpectingTuningSince[2]; // client data struct CClientData @@ -383,11 +387,11 @@ void OnLanguageChange(); - virtual const char *GetItemName(int Type); - virtual const char *Version(); - virtual const char *NetVersion(); - virtual int DDNetVersion(); - virtual const char *DDNetVersionStr(); + virtual const char *GetItemName(int Type) const; + virtual const char *Version() const; + virtual const char *NetVersion() const; + virtual int DDNetVersion() const; + virtual const char *DDNetVersionStr() const; // actions // TODO: move these @@ -446,11 +450,13 @@ bool AntiPingGunfire() { return AntiPingGrenade() && AntiPingWeapons() && g_Config.m_ClAntiPingGunfire; } bool Predict() { return g_Config.m_ClPredict && !(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) && !m_Snap.m_SpecInfo.m_Active && Client()->State() != IClient::STATE_DEMOPLAYBACK && m_Snap.m_pLocalCharacter; } bool PredictDummy() { return g_Config.m_ClPredictDummy && Client()->DummyConnected() && m_Snap.m_LocalClientID >= 0 && m_PredictedDummyID >= 0 && !m_aClients[m_PredictedDummyID].m_Paused; } + CTuningParams GetTunes(int i) { return m_aTuningList[i]; } CGameWorld m_GameWorld; CGameWorld m_PredictedWorld; CGameWorld m_PrevPredictedWorld; + void DummyResetInput(); void Echo(const char *pString); bool IsOtherTeam(int ClientID); bool CanDisplayWarning(); diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/character.cpp ddnet-15.5.4/src/game/client/prediction/entities/character.cpp --- ddnet-15.3.2/src/game/client/prediction/entities/character.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/character.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -356,7 +356,10 @@ // if we Hit anything, we have to wait for the reload if(Hits) - m_ReloadTimer = GameWorld()->GameTickSpeed() / 3; + { + float FireDelay = GetTuning(m_TuneZone)->m_HammerHitFireDelay; + m_ReloadTimer = FireDelay * GameWorld()->GameTickSpeed() / 1000; + } } break; @@ -861,9 +864,11 @@ void CCharacter::HandleTuneLayer() { int CurrentIndex = Collision()->GetMapIndex(m_Pos); - SetTuneZone(GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(CurrentIndex) : 0); + SetTuneZone(GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(CurrentIndex) : 0); - m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore + if(m_IsLocal) + m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore if the character is local + m_Core.m_Tuning = *GetTuning(m_TuneZone); } void CCharacter::DDRaceTick() @@ -1000,6 +1005,7 @@ CEntity(pGameWorld, CGameWorld::ENTTYPE_CHARACTER) { m_ID = ID; + m_IsLocal = false; m_LastWeapon = WEAPON_HAMMER; m_QueuedWeapon = -1; @@ -1069,6 +1075,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended, bool IsLocal) { m_Core.Read((CNetObj_CharacterCore *)pChar); + m_IsLocal = IsLocal; if(pExtended) { @@ -1210,7 +1217,7 @@ m_LastSnapWeapon = pChar->m_Weapon; m_Alive = true; - SetTuneZone(GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0); + SetTuneZone(GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0); // set the current weapon if(pChar->m_Weapon != WEAPON_NINJA) diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/character.h ddnet-15.5.4/src/game/client/prediction/entities/character.h --- ddnet-15.3.2/src/game/client/prediction/entities/character.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/character.h 2021-06-20 09:38:48.000000000 +0000 @@ -64,6 +64,7 @@ bool IsAlive() { return m_Alive; } bool m_Alive; + bool m_IsLocal; CTeamsCore *TeamsCore(); bool Freeze(int Time); @@ -197,6 +198,8 @@ void DDRacePostCoreTick(); void HandleTuneLayer(); + CTuningParams *CharacterTuning(); + int m_StrongWeakID; int m_LastWeaponSwitchTick; diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/laser.cpp ddnet-15.5.4/src/game/client/prediction/entities/laser.cpp --- ddnet-15.3.2/src/game/client/prediction/entities/laser.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/laser.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -20,7 +20,7 @@ m_TelePos = vec2(0, 0); m_WasTele = false; m_Type = Type; - m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; + m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; GameWorld()->InsertEntity(this); DoBounce(); } @@ -67,7 +67,7 @@ if(m_Energy < 0) { - GameWorld()->DestroyEntity(this); + m_MarkedForDestroy = true; return; } m_PrevPos = m_Pos; @@ -158,7 +158,7 @@ m_From.x = pLaser->m_FromX; m_From.y = pLaser->m_FromY; m_EvalTick = pLaser->m_StartTick; - m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; + m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; m_Owner = -2; m_Energy = GetTuning(m_TuneZone)->m_LaserReach; if(pGameWorld->m_WorldConfig.m_IsFNG && m_Energy < 10.f) diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/pickup.cpp ddnet-15.5.4/src/game/client/prediction/entities/pickup.cpp --- ddnet-15.3.2/src/game/client/prediction/entities/pickup.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/pickup.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -77,8 +77,13 @@ int index = Collision()->IsMover(m_Pos.x, m_Pos.y, &Flags); if(index) { + m_IsCoreActive = true; m_Core = Collision()->CpSpeed(index, Flags); } + else + { + m_IsCoreActive = false; + } m_Pos += m_Core; } } @@ -91,6 +96,7 @@ m_Type = pPickup->m_Type; m_Subtype = pPickup->m_Subtype; m_Core = vec2(0.f, 0.f); + m_IsCoreActive = false; m_ID = ID; m_Layer = LAYER_GAME; m_Number = 0; diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/pickup.h ddnet-15.5.4/src/game/client/prediction/entities/pickup.h --- ddnet-15.3.2/src/game/client/prediction/entities/pickup.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/pickup.h 2021-06-20 09:38:48.000000000 +0000 @@ -15,6 +15,7 @@ CPickup(CGameWorld *pGameWorld, int ID, CNetObj_Pickup *pPickup); void FillInfo(CNetObj_Pickup *pPickup); bool Match(CPickup *pPickup); + bool InDDNetTile() { return m_IsCoreActive; } private: int m_Type; @@ -24,6 +25,7 @@ void Move(); vec2 m_Core; + bool m_IsCoreActive; }; #endif diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/projectile.cpp ddnet-15.5.4/src/game/client/prediction/entities/projectile.cpp --- ddnet-15.3.2/src/game/client/prediction/entities/projectile.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/projectile.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,6 +1,7 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "projectile.h" +#include #include #include @@ -34,7 +35,7 @@ m_Number = Number; m_Freeze = Freeze; - m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; + m_TuneZone = GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; GameWorld()->InsertEntity(this); } @@ -122,10 +123,10 @@ } else if(m_Type == WEAPON_GUN) { - GameWorld()->DestroyEntity(this); + m_MarkedForDestroy = true; } else if(!m_Freeze) - GameWorld()->DestroyEntity(this); + m_MarkedForDestroy = true; } if(m_LifeSpan == -1) { @@ -139,7 +140,7 @@ GameWorld()->CreateExplosion(ColPos, m_Owner, m_Type, m_Owner == -1, (!pOwnerChar ? -1 : pOwnerChar->Team()), (m_Owner != -1) ? TeamMask : -1LL); } - GameWorld()->DestroyEntity(this); + m_MarkedForDestroy = true; } } @@ -150,21 +151,28 @@ m_Bouncing = Value; } -CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CNetObj_Projectile *pProj) : +CProjectile::CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj) : CEntity(pGameWorld, CGameWorld::ENTTYPE_PROJECTILE) { - ExtractInfo(pProj, &m_Pos, &m_Direction); - if(UseExtraInfo(pProj)) - ExtractExtraInfo(pProj, &m_Owner, &m_Explosive, &m_Bouncing, &m_Freeze); + m_Pos = pProj->m_StartPos; + m_Direction = pProj->m_StartVel; + if(pProj->m_ExtraInfo) + { + m_Owner = pProj->m_Owner; + m_Explosive = pProj->m_Explosive; + m_Bouncing = pProj->m_Bouncing; + m_Freeze = pProj->m_Freeze; + } else { m_Owner = -1; - m_Bouncing = m_Freeze = 0; + m_Bouncing = 0; + m_Freeze = 0; m_Explosive = (pProj->m_Type == WEAPON_GRENADE) && (fabs(1.0f - length(m_Direction)) < 0.015f); } m_Type = pProj->m_Type; m_StartTick = pProj->m_StartTick; - m_TuneZone = GameWorld()->m_WorldConfig.m_PredictTiles ? Collision()->IsTune(Collision()->GetMapIndex(m_Pos)) : 0; + m_TuneZone = pProj->m_TuneZone; int Lifetime = 20 * GameWorld()->GameTickSpeed(); m_SoundImpact = -1; @@ -181,45 +189,20 @@ m_ID = ID; } -void CProjectile::FillInfo(CNetObj_Projectile *pProj) +CProjectileData CProjectile::GetData() const { - pProj->m_X = (int)m_Pos.x; - pProj->m_Y = (int)m_Pos.y; - pProj->m_VelX = (int)(m_Direction.x * 100.0f); - pProj->m_VelY = (int)(m_Direction.y * 100.0f); - pProj->m_StartTick = m_StartTick; - pProj->m_Type = m_Type; -} - -void CProjectile::FillExtraInfo(CNetObj_Projectile *pProj) -{ - const int MaxPos = 0x7fffffff / 100; - if(abs((int)m_Pos.y) + 1 >= MaxPos || abs((int)m_Pos.x) + 1 >= MaxPos) - { - //If the modified data would be too large to fit in an integer, send normal data instead - FillInfo(pProj); - return; - } - //Send additional/modified info, by modifiying the fields of the netobj - float Angle = -atan2f(m_Direction.x, m_Direction.y); - - int Data = 0; - Data |= (abs(m_Owner) & 255) << 0; - if(m_Owner < 0) - Data |= 1 << 8; - Data |= 1 << 9; //This bit tells the client to use the extra info - Data |= (m_Bouncing & 3) << 10; - if(m_Explosive) - Data |= 1 << 12; - if(m_Freeze) - Data |= 1 << 13; - - pProj->m_X = (int)(m_Pos.x * 100.0f); - pProj->m_Y = (int)(m_Pos.y * 100.0f); - pProj->m_VelX = (int)(Angle * 1000000.0f); - pProj->m_VelY = Data; - pProj->m_StartTick = m_StartTick; - pProj->m_Type = m_Type; + CProjectileData Result; + Result.m_StartPos = m_Pos; + Result.m_StartVel = m_Direction; + Result.m_Type = m_Type; + Result.m_StartTick = m_StartTick; + Result.m_ExtraInfo = true; + Result.m_Owner = m_Owner; + Result.m_Explosive = m_Explosive; + Result.m_Bouncing = m_Bouncing; + Result.m_Freeze = m_Freeze; + Result.m_TuneZone = m_TuneZone; + return Result; } bool CProjectile::Match(CProjectile *pProj) diff -Nru ddnet-15.3.2/src/game/client/prediction/entities/projectile.h ddnet-15.5.4/src/game/client/prediction/entities/projectile.h --- ddnet-15.3.2/src/game/client/prediction/entities/projectile.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/entities/projectile.h 2021-06-20 09:38:48.000000000 +0000 @@ -5,7 +5,8 @@ #include "character.h" #include -#include + +class CProjectileData; class CProjectile : public CEntity { @@ -28,18 +29,17 @@ int Number = 0); vec2 GetPos(float Time); - void FillInfo(CNetObj_Projectile *pProj); + CProjectileData GetData() const; virtual void Tick(); bool Match(CProjectile *pProj); void SetBouncing(int Value); - void FillExtraInfo(CNetObj_Projectile *pProj); const vec2 &GetDirection() { return m_Direction; } const int &GetOwner() { return m_Owner; } const int &GetStartTick() { return m_StartTick; } - CProjectile(CGameWorld *pGameWorld, int ID, CNetObj_Projectile *pProj); + CProjectile(CGameWorld *pGameWorld, int ID, CProjectileData *pProj); private: vec2 m_Direction; diff -Nru ddnet-15.3.2/src/game/client/prediction/gameworld.cpp ddnet-15.5.4/src/game/client/prediction/gameworld.cpp --- ddnet-15.3.2/src/game/client/prediction/gameworld.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/gameworld.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -9,6 +9,7 @@ #include "entity.h" #include #include +#include #include ////////////////////////////////////////////////// @@ -120,11 +121,6 @@ } } -void CGameWorld::DestroyEntity(CEntity *pEnt) -{ - pEnt->m_MarkedForDestroy = true; -} - void CGameWorld::RemoveEntity(CEntity *pEnt) { // not in the list @@ -288,7 +284,7 @@ CTuningParams *CGameWorld::Tuning() { - return &m_Tuning[g_Config.m_ClDummy]; + return &m_Core.m_Tuning[g_Config.m_ClDummy]; } CEntity *CGameWorld::GetEntity(int ID, int EntType) @@ -368,9 +364,18 @@ void CGameWorld::NetObjAdd(int ObjID, int ObjType, const void *pObjData) { - if(ObjType == NETOBJTYPE_PROJECTILE && m_WorldConfig.m_PredictWeapons) + if((ObjType == NETOBJTYPE_PROJECTILE || ObjType == NETOBJTYPE_DDNETPROJECTILE) && m_WorldConfig.m_PredictWeapons) { - CProjectile NetProj = CProjectile(this, ObjID, (CNetObj_Projectile *)pObjData); + CProjectileData Data; + if(ObjType == NETOBJTYPE_PROJECTILE) + { + Data = ExtractProjectileInfo((const CNetObj_Projectile *)pObjData, this); + } + else + { + Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pObjData, this); + } + CProjectile NetProj = CProjectile(this, ObjID, &Data); if(NetProj.m_Type != WEAPON_SHOTGUN && fabs(length(NetProj.m_Direction) - 1.f) > 0.02f) // workaround to skip grenades on ball mod return; @@ -385,7 +390,7 @@ return; } } - if(!UseExtraInfo((CNetObj_Projectile *)pObjData)) + if(!Data.m_ExtraInfo) { // try to match the newly received (unrecognized) projectile with a locally fired one for(CProjectile *pProj = (CProjectile *)FindFirst(CGameWorld::ENTTYPE_PROJECTILE); pProj; pProj = (CProjectile *)pProj->TypeNext()) @@ -521,7 +526,6 @@ for(int i = 0; i < 2; i++) { m_Core.m_Tuning[i] = pFrom->m_Core.m_Tuning[i]; - m_Tuning[i] = pFrom->m_Tuning[i]; } m_pTuningList = pFrom->m_pTuningList; m_Teams = pFrom->m_Teams; @@ -570,7 +574,25 @@ switch(ObjType) { case NETOBJTYPE_CHARACTER: FindType(ENTTYPE_CHARACTER, CCharacter, CNetObj_Character); - case NETOBJTYPE_PROJECTILE: FindType(ENTTYPE_PROJECTILE, CProjectile, CNetObj_Projectile); + case NETOBJTYPE_PROJECTILE: + case NETOBJTYPE_DDNETPROJECTILE: + { + CProjectileData Data; + if(ObjType == NETOBJTYPE_PROJECTILE) + { + Data = ExtractProjectileInfo((const CNetObj_Projectile *)pObjData, this); + } + else + { + Data = ExtractProjectileInfoDDNet((const CNetObj_DDNetProjectile *)pObjData, this); + } + CProjectile *pEnt = (CProjectile *)GetEntity(ObjID, ENTTYPE_PROJECTILE); + if(pEnt && CProjectile(this, ObjID, &Data).Match(pEnt)) + { + return pEnt; + } + return 0; + } case NETOBJTYPE_LASER: FindType(ENTTYPE_LASER, CLaser, CNetObj_Laser); case NETOBJTYPE_PICKUP: FindType(ENTTYPE_PICKUP, CPickup, CNetObj_Pickup); } diff -Nru ddnet-15.3.2/src/game/client/prediction/gameworld.h ddnet-15.5.4/src/game/client/prediction/gameworld.h --- ddnet-15.3.2/src/game/client/prediction/gameworld.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/prediction/gameworld.h 2021-06-20 09:38:48.000000000 +0000 @@ -37,12 +37,9 @@ class CCharacter *IntersectCharacter(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CCharacter *pNotThis = 0, int CollideWith = -1, class CCharacter *pThisOnly = 0); void InsertEntity(CEntity *pEntity, bool Last = false); void RemoveEntity(CEntity *pEntity); - void DestroyEntity(CEntity *pEntity); void Tick(); // DDRace - - std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CEntity *pNotThis); void ReleaseHooked(int ClientID); std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, class CEntity *pNotThis = 0); @@ -74,6 +71,7 @@ bool m_PredictWeapons; bool m_PredictDDRace; bool m_IsSolo; + bool m_UseTuneZones; } m_WorldConfig; bool m_IsValidCopy; @@ -89,10 +87,9 @@ CEntity *FindMatch(int ObjID, int ObjType, const void *pObjData); void Clear(); - CTuningParams m_Tuning[2]; CTuningParams *m_pTuningList; CTuningParams *TuningList() { return m_pTuningList; } - CTuningParams *GetTuning(int i) { return i == 0 ? Tuning() : &TuningList()[i]; } + CTuningParams *GetTuning(int i) { return &TuningList()[i]; } private: void RemoveEntities(); diff -Nru ddnet-15.3.2/src/game/client/projectile_data.cpp ddnet-15.5.4/src/game/client/projectile_data.cpp --- ddnet-15.3.2/src/game/client/projectile_data.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/game/client/projectile_data.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,83 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#include "projectile_data.h" + +#include +#include +#include +#include + +bool UseProjectileExtraInfo(const CNetObj_Projectile *pProj) +{ + return pProj->m_VelY >= 0 && (pProj->m_VelY & PROJECTILEFLAG_IS_DDNET) != 0; +} + +CProjectileData ExtractProjectileInfo(const CNetObj_Projectile *pProj, CGameWorld *pGameWorld) +{ + if(UseProjectileExtraInfo(pProj)) + { + CNetObj_DDNetProjectile Proj; + mem_copy(&Proj, pProj, sizeof(Proj)); + return ExtractProjectileInfoDDNet(&Proj, pGameWorld); + } + + CProjectileData Result = {vec2(0, 0)}; + Result.m_StartPos.x = pProj->m_X; + Result.m_StartPos.y = pProj->m_Y; + Result.m_StartVel.x = pProj->m_VelX / 100.0f; + Result.m_StartVel.y = pProj->m_VelY / 100.0f; + Result.m_Type = pProj->m_Type; + Result.m_StartTick = pProj->m_StartTick; + Result.m_ExtraInfo = false; + Result.m_Owner = -1; + Result.m_TuneZone = pGameWorld && pGameWorld->m_WorldConfig.m_UseTuneZones ? pGameWorld->Collision()->IsTune(pGameWorld->Collision()->GetMapIndex(Result.m_StartPos)) : 0; + return Result; +} + +CProjectileData ExtractProjectileInfoDDNet(const CNetObj_DDNetProjectile *pProj, CGameWorld *pGameWorld) +{ + CProjectileData Result = {vec2(0, 0)}; + + Result.m_StartPos.x = pProj->m_X / 100.0f; + Result.m_StartPos.y = pProj->m_Y / 100.0f; + float Angle = pProj->m_Angle / 1000000.0f; + Result.m_StartVel.x = sin(-Angle); + Result.m_StartVel.y = cos(-Angle); + Result.m_Type = pProj->m_Type; + Result.m_StartTick = pProj->m_StartTick; + + Result.m_ExtraInfo = true; + Result.m_Owner = pProj->m_Data & 255; + if((pProj->m_Data & PROJECTILEFLAG_NO_OWNER) & 1) + { + Result.m_Owner = -1; + } + // PROJECTILEFLAG_BOUNCE_HORIZONTAL, PROJECTILEFLAG_BOUNCE_VERTICAL + Result.m_Bouncing = (pProj->m_Data >> 10) & 3; + Result.m_Explosive = pProj->m_Data & PROJECTILEFLAG_EXPLOSIVE; + Result.m_Freeze = pProj->m_Data & PROJECTILEFLAG_FREEZE; + Result.m_TuneZone = pGameWorld && pGameWorld->m_WorldConfig.m_UseTuneZones ? pGameWorld->Collision()->IsTune(pGameWorld->Collision()->GetMapIndex(Result.m_StartPos)) : 0; + return Result; +} + +void SnapshotRemoveExtraProjectileInfo(unsigned char *pData) +{ + CSnapshot *pSnap = (CSnapshot *)pData; + for(int Index = 0; Index < pSnap->NumItems(); Index++) + { + CSnapshotItem *pItem = pSnap->GetItem(Index); + if(pItem->Type() == NETOBJTYPE_PROJECTILE) + { + CNetObj_Projectile *pProj = (CNetObj_Projectile *)((void *)pItem->Data()); + if(UseProjectileExtraInfo(pProj)) + { + CProjectileData Data = ExtractProjectileInfo(pProj, nullptr); + pProj->m_X = Data.m_StartPos.x; + pProj->m_Y = Data.m_StartPos.y; + pProj->m_VelX = (int)(Data.m_StartVel.x * 100.0f); + pProj->m_VelY = (int)(Data.m_StartVel.y * 100.0f); + } + } + } +} diff -Nru ddnet-15.3.2/src/game/client/projectile_data.h ddnet-15.5.4/src/game/client/projectile_data.h --- ddnet-15.3.2/src/game/client/projectile_data.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/game/client/projectile_data.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,31 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef GAME_CLIENT_PROJECTILE_DATA_H +#define GAME_CLIENT_PROJECTILE_DATA_H + +#include + +struct CNetObj_Projectile; +struct CNetObj_DDNetProjectile; + +class CProjectileData +{ +public: + vec2 m_StartPos; + vec2 m_StartVel; + int m_Type; + int m_StartTick; + bool m_ExtraInfo; + // The rest is only set if m_ExtraInfo is true. + int m_Owner; + bool m_Explosive; + int m_Bouncing; + bool m_Freeze; + // TuneZone is introduced locally + int m_TuneZone; +}; + +CProjectileData ExtractProjectileInfo(const CNetObj_Projectile *pProj, class CGameWorld *pGameWorld); +CProjectileData ExtractProjectileInfoDDNet(const CNetObj_DDNetProjectile *pProj, class CGameWorld *pGameWorld); + +#endif // GAME_CLIENT_PROJECTILE_DATA_H diff -Nru ddnet-15.3.2/src/game/client/skin.h ddnet-15.5.4/src/game/client/skin.h --- ddnet-15.3.2/src/game/client/skin.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/skin.h 2021-06-20 09:38:48.000000000 +0000 @@ -118,15 +118,6 @@ SSkinMetricVariable m_Body; SSkinMetricVariable m_Feet; - int m_FeetWidth; - int m_FeetHeight; - int m_FeetOffsetX; - int m_FeetOffsetY; - - // these can be used to normalize the metrics - int m_FeetMaxWidth; - int m_FeetMaxHeight; - void Reset() { m_Body.Reset(); @@ -140,10 +131,10 @@ }; SSkinMetrics m_Metrics; - bool operator<(const CSkin &Other) const { return str_comp_nocase(m_aName, Other.m_aName) < 0; } + bool operator<(const CSkin &Other) const { return str_comp(m_aName, Other.m_aName) < 0; } - bool operator<(const char *pOther) const { return str_comp_nocase(m_aName, pOther) < 0; } - bool operator==(const char *pOther) const { return !str_comp_nocase(m_aName, pOther); } + bool operator<(const char *pOther) const { return str_comp(m_aName, pOther) < 0; } + bool operator==(const char *pOther) const { return !str_comp(m_aName, pOther); } }; #endif diff -Nru ddnet-15.3.2/src/game/client/ui.cpp ddnet-15.5.4/src/game/client/ui.cpp --- ddnet-15.3.2/src/game/client/ui.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/ui.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -118,14 +118,14 @@ return 0; } -int CUI::MouseInside(const CUIRect *r) +int CUI::MouseInside(const CUIRect *r) const { if(m_MouseX >= r->x && m_MouseX < r->x + r->w && m_MouseY >= r->y && m_MouseY < r->y + r->h) return 1; return 0; } -void CUI::ConvertMouseMove(float *x, float *y) +void CUI::ConvertMouseMove(float *x, float *y) const { float Fac = (float)(g_Config.m_UiMousesens) / g_Config.m_InpMousesens; *x = *x * Fac; @@ -156,7 +156,7 @@ g_Config.m_UiScale = (int)(s * 100.0f); } -float CUI::Scale() +float CUI::Scale() const { return g_Config.m_UiScale / 100.0f; } diff -Nru ddnet-15.3.2/src/game/client/ui_ex.cpp ddnet-15.5.4/src/game/client/ui_ex.cpp --- ddnet-15.3.2/src/game/client/ui_ex.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/game/client/ui_ex.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,277 @@ +#include "ui_ex.h" + +#include +#include + +#include +#include + +#include + +CUIEx::CUIEx(CUI *pUI, IKernel *pKernel, CRenderTools *pRenderTools, IInput::CEvent *pInputEventsArray, int *pInputEventCount) +{ + Init(pUI, pKernel, pRenderTools, pInputEventsArray, pInputEventCount); +} + +void CUIEx::Init(CUI *pUI, IKernel *pKernel, CRenderTools *pRenderTools, IInput::CEvent *pInputEventsArray, int *pInputEventCount) +{ + m_pUI = pUI; + m_pKernel = pKernel; + m_pRenderTools = pRenderTools; + m_pInputEventsArray = pInputEventsArray; + m_pInputEventCount = pInputEventCount; + + m_pInput = Kernel()->RequestInterface(); + m_pGraphics = Kernel()->RequestInterface(); + m_pTextRender = Kernel()->RequestInterface(); +} + +int CUIEx::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) +{ + int Inside = UI()->MouseInside(pRect); + bool ReturnValue = false; + bool UpdateOffset = false; + static int s_AtIndex = 0; + static bool s_DoScroll = false; + static float s_ScrollStart = 0.0f; + + FontSize *= UI()->Scale(); + + if(UI()->LastActiveItem() == pID) + { + int Len = str_length(pStr); + if(Len == 0) + s_AtIndex = 0; + + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V)) + { + const char *Text = Input()->GetClipboardText(); + if(Text) + { + int Offset = str_length(pStr); + int CharsLeft = StrSize - Offset; + char *pCur = pStr + Offset; + str_utf8_copy(pCur, Text, CharsLeft); + for(int i = 0; i < CharsLeft; i++) + { + if(pCur[i] == 0) + break; + else if(pCur[i] == '\r') + pCur[i] = ' '; + else if(pCur[i] == '\n') + pCur[i] = ' '; + } + s_AtIndex = str_length(pStr); + ReturnValue = true; + } + } + + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C)) + { + Input()->SetClipboardText(pStr); + } + + /* TODO: Doesn't work, SetClipboardText doesn't retain the string quickly enough? + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_X)) + { + Input()->SetClipboardText(pStr); + pStr[0] = '\0'; + s_AtIndex = 0; + ReturnValue = true; + } + */ + + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_U)) + { + pStr[0] = '\0'; + s_AtIndex = 0; + ReturnValue = true; + } + + if(Inside && UI()->MouseButton(0)) + { + s_DoScroll = true; + s_ScrollStart = UI()->MouseX(); + int MxRel = (int)(UI()->MouseX() - pRect->x); + + for(int i = 1; i <= Len; i++) + { + if(TextRender()->TextWidth(0, FontSize, pStr, i, std::numeric_limits::max()) - *Offset > MxRel) + { + s_AtIndex = i - 1; + break; + } + + if(i == Len) + s_AtIndex = Len; + } + } + else if(!UI()->MouseButton(0)) + s_DoScroll = false; + else if(s_DoScroll) + { + // do scrolling + if(UI()->MouseX() < pRect->x && s_ScrollStart - UI()->MouseX() > 10.0f) + { + s_AtIndex = maximum(0, s_AtIndex - 1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + else if(UI()->MouseX() > pRect->x + pRect->w && UI()->MouseX() - s_ScrollStart > 10.0f) + { + s_AtIndex = minimum(Len, s_AtIndex + 1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + } + + for(int i = 0; i < *m_pInputEventCount; i++) + { + Len = str_length(pStr); + int NumChars = Len; + ReturnValue |= CLineInput::Manipulate(m_pInputEventsArray[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); + } + } + + bool JustGotActive = false; + + if(UI()->ActiveItem() == pID) + { + if(!UI()->MouseButton(0)) + { + s_AtIndex = minimum(s_AtIndex, str_length(pStr)); + s_DoScroll = false; + UI()->SetActiveItem(0); + } + } + else if(UI()->HotItem() == pID) + { + if(UI()->MouseButton(0)) + { + if(UI()->LastActiveItem() != pID) + JustGotActive = true; + UI()->SetActiveItem(pID); + } + } + + if(Inside) + { + UI()->SetHotItem(pID); + } + + CUIRect Textbox = *pRect; + RenderTools()->DrawUIRect(&Textbox, ColorRGBA(1, 1, 1, 0.5f), Corners, 3.0f); + Textbox.VMargin(2.0f, &Textbox); + Textbox.HMargin(2.0f, &Textbox); + + const char *pDisplayStr = pStr; + char aStars[128]; + + if(Hidden) + { + unsigned s = str_length(pDisplayStr); + if(s >= sizeof(aStars)) + s = sizeof(aStars) - 1; + for(unsigned int i = 0; i < s; ++i) + aStars[i] = '*'; + aStars[s] = 0; + pDisplayStr = aStars; + } + + char aDispEditingText[128 + IInput::INPUT_TEXT_SIZE + 2] = {0}; + int DispCursorPos = s_AtIndex; + if(UI()->LastActiveItem() == pID && Input()->GetIMEEditingTextLength() > -1) + { + int EditingTextCursor = Input()->GetEditingCursor(); + str_copy(aDispEditingText, pDisplayStr, sizeof(aDispEditingText)); + char aEditingText[IInput::INPUT_TEXT_SIZE + 2]; + if(Hidden) + { + // Do not show editing text in password field + str_copy(aEditingText, "[*]", sizeof(aEditingText)); + EditingTextCursor = 1; + } + else + { + str_format(aEditingText, sizeof(aEditingText), "[%s]", Input()->GetIMEEditingText()); + } + int NewTextLen = str_length(aEditingText); + int CharsLeft = (int)sizeof(aDispEditingText) - str_length(aDispEditingText) - 1; + int FillCharLen = minimum(NewTextLen, CharsLeft); + for(int i = str_length(aDispEditingText) - 1; i >= s_AtIndex; i--) + aDispEditingText[i + FillCharLen] = aDispEditingText[i]; + for(int i = 0; i < FillCharLen; i++) + aDispEditingText[s_AtIndex + i] = aEditingText[i]; + DispCursorPos = s_AtIndex + EditingTextCursor + 1; + pDisplayStr = aDispEditingText; + UpdateOffset = true; + } + + if(pDisplayStr[0] == '\0') + { + pDisplayStr = pEmptyText; + TextRender()->TextColor(1, 1, 1, 0.75f); + } + + DispCursorPos = minimum(DispCursorPos, str_length(pDisplayStr)); + + // check if the text has to be moved + if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || *m_pInputEventCount)) + { + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, DispCursorPos, std::numeric_limits::max()); + if(w - *Offset > Textbox.w) + { + // move to the left + float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1, std::numeric_limits::max()); + do + { + *Offset += minimum(wt - *Offset - Textbox.w, Textbox.w / 3); + } while(w - *Offset > Textbox.w + 0.0001f); + } + else if(w - *Offset < 0.0f) + { + // move to the right + do + { + *Offset = maximum(0.0f, *Offset - Textbox.w / 3); + } while(w - *Offset < -0.0001f); + } + } + UI()->ClipEnable(pRect); + Textbox.x -= *Offset; + + UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); + + TextRender()->TextColor(1, 1, 1, 1); + + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + float OnePixelWidth = ((ScreenX1 - ScreenX0) / Graphics()->ScreenWidth()); + + // render the cursor + if(UI()->LastActiveItem() == pID && !JustGotActive) + { + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, DispCursorPos, std::numeric_limits::max()); + Textbox.x += w; + + if((2 * time_get() / time_freq()) % 2) + { + Graphics()->TextureClear(); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0, 0, 0, 0.3f); + float PosToMid = (Textbox.h - FontSize) / 2.0f; + IGraphics::CQuadItem CursorTBack(Textbox.x - (OnePixelWidth * 2.0f) / 2.0f, Textbox.y + PosToMid, OnePixelWidth * 2 * 2.0f, FontSize); + Graphics()->QuadsDrawTL(&CursorTBack, 1); + Graphics()->SetColor(1, 1, 1, 1); + IGraphics::CQuadItem CursorT(Textbox.x, Textbox.y + PosToMid + OnePixelWidth * 1.5f, OnePixelWidth * 2.0f, FontSize - OnePixelWidth * 1.5f * 2); + Graphics()->QuadsDrawTL(&CursorT, 1); + Graphics()->QuadsEnd(); + } + + Input()->SetEditingPosition(Textbox.x, Textbox.y + FontSize); + } + + UI()->ClipDisable(); + + return ReturnValue; +} diff -Nru ddnet-15.3.2/src/game/client/ui_ex.h ddnet-15.5.4/src/game/client/ui_ex.h --- ddnet-15.3.2/src/game/client/ui_ex.h 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/game/client/ui_ex.h 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,45 @@ +#ifndef GAME_CLIENT_UI_EX_H +#define GAME_CLIENT_UI_EX_H + +#include +#include +#include +#include + +class IInput; +class ITextRender; +class IKernel; +class IGraphics; + +class CRenderTools; + +class CUIEx +{ + CUI *m_pUI; + IInput *m_pInput; + ITextRender *m_pTextRender; + IKernel *m_pKernel; + IGraphics *m_pGraphics; + CRenderTools *m_pRenderTools; + + IInput::CEvent *m_pInputEventsArray; + int *m_pInputEventCount; + +protected: + CUI *UI() { return m_pUI; } + IInput *Input() { return m_pInput; } + ITextRender *TextRender() { return m_pTextRender; } + IKernel *Kernel() { return m_pKernel; } + IGraphics *Graphics() { return m_pGraphics; } + CRenderTools *RenderTools() { return m_pRenderTools; } + +public: + CUIEx(CUI *pUI, IKernel *pKernel, CRenderTools *pRenderTools, IInput::CEvent *pInputEventsArray, int *pInputEventCount); + CUIEx() {} + + void Init(CUI *pUI, IKernel *pKernel, CRenderTools *pRenderTools, IInput::CEvent *pInputEventsArray, int *pInputEventCount); + + int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden = false, int Corners = CUI::CORNER_ALL, const char *pEmptyText = ""); +}; + +#endif diff -Nru ddnet-15.3.2/src/game/client/ui.h ddnet-15.5.4/src/game/client/ui.h --- ddnet-15.3.2/src/game/client/ui.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/client/ui.h 2021-06-20 09:38:48.000000000 +0000 @@ -202,8 +202,8 @@ m_pGraphics = pGraphics; m_pTextRender = pTextRender; } - class IGraphics *Graphics() { return m_pGraphics; } - class ITextRender *TextRender() { return m_pTextRender; } + class IGraphics *Graphics() const { return m_pGraphics; } + class ITextRender *TextRender() const { return m_pTextRender; } CUI(); ~CUI(); @@ -241,8 +241,8 @@ float MouseWorldX() const { return m_MouseWorldX; } float MouseWorldY() const { return m_MouseWorldY; } int MouseButton(int Index) const { return (m_MouseButtons >> Index) & 1; } - int MouseButtonClicked(int Index) { return MouseButton(Index) && !((m_LastMouseButtons >> Index) & 1); } - int MouseButtonReleased(int Index) { return ((m_LastMouseButtons >> Index) & 1) && !MouseButton(Index); } + int MouseButtonClicked(int Index) const { return MouseButton(Index) && !((m_LastMouseButtons >> Index) & 1); } + int MouseButtonReleased(int Index) const { return ((m_LastMouseButtons >> Index) & 1) && !MouseButton(Index); } void SetHotItem(const void *pID) { m_pBecommingHotItem = pID; } void SetActiveItem(const void *pID) @@ -257,8 +257,8 @@ const void *ActiveItem() const { return m_pActiveItem; } const void *LastActiveItem() const { return m_pLastActiveItem; } - int MouseInside(const CUIRect *pRect); - void ConvertMouseMove(float *x, float *y); + int MouseInside(const CUIRect *pRect) const; + void ConvertMouseMove(float *x, float *y) const; CUIRect *Screen(); float PixelSize(); @@ -267,7 +267,7 @@ // TODO: Refactor: Redo UI scaling void SetScale(float s); - float Scale(); + float Scale() const; int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect); int DoButtonLogic(const void *pID, const char *pText /* TODO: Refactor: Remove */, int Checked, const CUIRect *pRect); diff -Nru ddnet-15.3.2/src/game/collision.cpp ddnet-15.5.4/src/game/collision.cpp --- ddnet-15.3.2/src/game/collision.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/collision.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -293,7 +293,7 @@ return Restrictions; } -int CCollision::GetTile(int x, int y) +int CCollision::GetTile(int x, int y) const { if(!m_pTiles) return 0; @@ -308,7 +308,7 @@ } // TODO: rewrite this smarter! -int CCollision::IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) +int CCollision::IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const { float Distance = distance(Pos0, Pos1); int End(Distance + 1); @@ -339,7 +339,7 @@ return 0; } -int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) +int CCollision::IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const { float Distance = distance(Pos0, Pos1); int End(Distance + 1); @@ -396,7 +396,7 @@ return 0; } -int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) +int CCollision::IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const { float Distance = distance(Pos0, Pos1); int End(Distance + 1); @@ -442,7 +442,7 @@ } // TODO: OPT: rewrite this smarter! -void CCollision::MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) +void CCollision::MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const { if(pBounces) *pBounces = 0; @@ -480,7 +480,7 @@ } } -bool CCollision::TestBox(vec2 Pos, vec2 Size) +bool CCollision::TestBox(vec2 Pos, vec2 Size) const { Size *= 0.5f; if(CheckPoint(Pos.x - Size.x, Pos.y - Size.y)) @@ -494,7 +494,7 @@ return false; } -void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity) +void CCollision::MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity) const { // do the move vec2 Pos = *pInoutPos; @@ -583,13 +583,13 @@ m_pSwitchers = 0; } -int CCollision::IsSolid(int x, int y) +int CCollision::IsSolid(int x, int y) const { int index = GetTile(x, y); return index == TILE_SOLID || index == TILE_NOHOOK; } -bool CCollision::IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1) +bool CCollision::IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1) const { int pos = GetPureMapIndex(x, y); if(m_pFront && (m_pFront[pos].m_Index == TILE_THROUGH_ALL || m_pFront[pos].m_Index == TILE_THROUGH_CUT)) @@ -602,7 +602,7 @@ return false; } -bool CCollision::IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1) +bool CCollision::IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1) const { int pos = GetPureMapIndex(x, y); if(m_pTiles[pos].m_Index == TILE_THROUGH_ALL || (m_pFront && m_pFront[pos].m_Index == TILE_THROUGH_ALL)) @@ -617,7 +617,7 @@ return false; } -int CCollision::IsWallJump(int Index) +int CCollision::IsWallJump(int Index) const { if(Index < 0) return 0; @@ -625,17 +625,17 @@ return m_pTiles[Index].m_Index == TILE_WALLJUMP; } -int CCollision::IsNoLaser(int x, int y) +int CCollision::IsNoLaser(int x, int y) const { return (CCollision::GetTile(x, y) == TILE_NOLASER); } -int CCollision::IsFNoLaser(int x, int y) +int CCollision::IsFNoLaser(int x, int y) const { return (CCollision::GetFTile(x, y) == TILE_NOLASER); } -int CCollision::IsTeleport(int Index) +int CCollision::IsTeleport(int Index) const { if(Index < 0 || !m_pTele) return 0; @@ -646,7 +646,7 @@ return 0; } -int CCollision::IsEvilTeleport(int Index) +int CCollision::IsEvilTeleport(int Index) const { if(Index < 0) return 0; @@ -659,7 +659,7 @@ return 0; } -int CCollision::IsCheckTeleport(int Index) +int CCollision::IsCheckTeleport(int Index) const { if(Index < 0) return 0; @@ -672,7 +672,7 @@ return 0; } -int CCollision::IsCheckEvilTeleport(int Index) +int CCollision::IsCheckEvilTeleport(int Index) const { if(Index < 0) return 0; @@ -685,7 +685,7 @@ return 0; } -int CCollision::IsTCheckpoint(int Index) +int CCollision::IsTCheckpoint(int Index) const { if(Index < 0) return 0; @@ -699,7 +699,7 @@ return 0; } -int CCollision::IsTeleportWeapon(int Index) +int CCollision::IsTeleportWeapon(int Index) const { if(Index < 0 || !m_pTele) return 0; @@ -710,7 +710,7 @@ return 0; } -int CCollision::IsTeleportHook(int Index) +int CCollision::IsTeleportHook(int Index) const { if(Index < 0 || !m_pTele) return 0; @@ -721,7 +721,7 @@ return 0; } -int CCollision::IsSpeedup(int Index) +int CCollision::IsSpeedup(int Index) const { if(Index < 0 || !m_pSpeedup) return 0; @@ -732,7 +732,7 @@ return 0; } -int CCollision::IsTune(int Index) +int CCollision::IsTune(int Index) const { if(Index < 0 || !m_pTune) return 0; @@ -743,7 +743,7 @@ return 0; } -void CCollision::GetSpeedup(int Index, vec2 *Dir, int *Force, int *MaxSpeed) +void CCollision::GetSpeedup(int Index, vec2 *Dir, int *Force, int *MaxSpeed) const { if(Index < 0 || !m_pSpeedup) return; @@ -754,7 +754,7 @@ *MaxSpeed = m_pSpeedup[Index].m_MaxSpeed; } -int CCollision::IsSwitch(int Index) +int CCollision::IsSwitch(int Index) const { if(Index < 0 || !m_pSwitch) return 0; @@ -765,7 +765,7 @@ return 0; } -int CCollision::GetSwitchNumber(int Index) +int CCollision::GetSwitchNumber(int Index) const { if(Index < 0 || !m_pSwitch) return 0; @@ -776,7 +776,7 @@ return 0; } -int CCollision::GetSwitchDelay(int Index) +int CCollision::GetSwitchDelay(int Index) const { if(Index < 0 || !m_pSwitch) return 0; @@ -787,7 +787,7 @@ return 0; } -int CCollision::IsMover(int x, int y, int *pFlags) +int CCollision::IsMover(int x, int y, int *pFlags) const { int Nx = clamp(x / 32, 0, m_Width - 1); int Ny = clamp(y / 32, 0, m_Height - 1); @@ -801,7 +801,7 @@ return 0; } -vec2 CCollision::CpSpeed(int Index, int Flags) +vec2 CCollision::CpSpeed(int Index, int Flags) const { if(Index < 0) return vec2(0, 0); @@ -834,14 +834,14 @@ return target; } -int CCollision::GetPureMapIndex(float x, float y) +int CCollision::GetPureMapIndex(float x, float y) const { int Nx = clamp(round_to_int(x) / 32, 0, m_Width - 1); int Ny = clamp(round_to_int(y) / 32, 0, m_Height - 1); return Ny * m_Width + Nx; } -bool CCollision::TileExists(int Index) +bool CCollision::TileExists(int Index) const { if(Index < 0) return false; @@ -863,7 +863,7 @@ return TileExistsNext(Index); } -bool CCollision::TileExistsNext(int Index) +bool CCollision::TileExistsNext(int Index) const { if(Index < 0) return false; @@ -905,7 +905,7 @@ return false; } -int CCollision::GetMapIndex(vec2 Pos) +int CCollision::GetMapIndex(vec2 Pos) const { int Nx = clamp((int)Pos.x / 32, 0, m_Width - 1); int Ny = clamp((int)Pos.y / 32, 0, m_Height - 1); @@ -917,7 +917,7 @@ return -1; } -std::list CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) +std::list CCollision::GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices) const { std::list Indices; float d = distance(PrevPos, Pos); @@ -963,7 +963,7 @@ } } -vec2 CCollision::GetPos(int Index) +vec2 CCollision::GetPos(int Index) const { if(Index < 0) return vec2(0, 0); @@ -973,40 +973,40 @@ return vec2(x * 32 + 16, y * 32 + 16); } -int CCollision::GetTileIndex(int Index) +int CCollision::GetTileIndex(int Index) const { if(Index < 0) return 0; return m_pTiles[Index].m_Index; } -int CCollision::GetFTileIndex(int Index) +int CCollision::GetFTileIndex(int Index) const { if(Index < 0 || !m_pFront) return 0; return m_pFront[Index].m_Index; } -int CCollision::GetTileFlags(int Index) +int CCollision::GetTileFlags(int Index) const { if(Index < 0) return 0; return m_pTiles[Index].m_Flags; } -int CCollision::GetFTileFlags(int Index) +int CCollision::GetFTileFlags(int Index) const { if(Index < 0 || !m_pFront) return 0; return m_pFront[Index].m_Flags; } -int CCollision::GetIndex(int Nx, int Ny) +int CCollision::GetIndex(int Nx, int Ny) const { return m_pTiles[Ny * m_Width + Nx].m_Index; } -int CCollision::GetIndex(vec2 PrevPos, vec2 Pos) +int CCollision::GetIndex(vec2 PrevPos, vec2 Pos) const { float Distance = distance(PrevPos, Pos); @@ -1043,14 +1043,14 @@ return -1; } -int CCollision::GetFIndex(int Nx, int Ny) +int CCollision::GetFIndex(int Nx, int Ny) const { if(!m_pFront) return 0; return m_pFront[Ny * m_Width + Nx].m_Index; } -int CCollision::GetFTile(int x, int y) +int CCollision::GetFTile(int x, int y) const { if(!m_pFront) return 0; @@ -1062,7 +1062,7 @@ return 0; } -int CCollision::Entity(int x, int y, int Layer) +int CCollision::Entity(int x, int y, int Layer) const { if((0 > x || x >= m_Width) || (0 > y || y >= m_Height)) { @@ -1133,14 +1133,14 @@ m_pDoor[Ny * m_Width + Nx].m_Number = Number; } -int CCollision::GetDTileIndex(int Index) +int CCollision::GetDTileIndex(int Index) const { if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index) return 0; return m_pDoor[Index].m_Index; } -int CCollision::GetDTileNumber(int Index) +int CCollision::GetDTileNumber(int Index) const { if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index) return 0; @@ -1149,7 +1149,7 @@ return 0; } -int CCollision::GetDTileFlags(int Index) +int CCollision::GetDTileFlags(int Index) const { if(!m_pDoor || Index < 0 || !m_pDoor[Index].m_Index) return 0; @@ -1188,7 +1188,7 @@ } } -int CCollision::IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) +int CCollision::IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const { float d = distance(Pos0, Pos1); vec2 Last = Pos0; @@ -1219,7 +1219,7 @@ return 0; } -int CCollision::IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) +int CCollision::IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const { float d = distance(Pos0, Pos1); vec2 Last = Pos0; @@ -1248,7 +1248,7 @@ return 0; } -int CCollision::IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) +int CCollision::IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const { float d = distance(Pos0, Pos1); vec2 Last = Pos0; @@ -1279,7 +1279,7 @@ return 0; } -int CCollision::IsCheckpoint(int Index) +int CCollision::IsCheckpoint(int Index) const { if(Index < 0) return -1; @@ -1290,7 +1290,7 @@ return -1; } -int CCollision::IsFCheckpoint(int Index) +int CCollision::IsFCheckpoint(int Index) const { if(Index < 0 || !m_pFront) return -1; diff -Nru ddnet-15.3.2/src/game/collision.h ddnet-15.5.4/src/game/collision.h --- ddnet-15.3.2/src/game/collision.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/collision.h 2021-06-20 09:38:48.000000000 +0000 @@ -33,17 +33,17 @@ ~CCollision(); void Init(class CLayers *pLayers); void FillAntibot(CAntibotMapData *pMapData); - bool CheckPoint(float x, float y) { return IsSolid(round_to_int(x), round_to_int(y)); } - bool CheckPoint(vec2 Pos) { return CheckPoint(Pos.x, Pos.y); } - int GetCollisionAt(float x, float y) { return GetTile(round_to_int(x), round_to_int(y)); } - int GetWidth() { return m_Width; }; - int GetHeight() { return m_Height; }; - int IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision); - int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr); - int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr); - void MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces); - void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity); - bool TestBox(vec2 Pos, vec2 Size); + bool CheckPoint(float x, float y) const { return IsSolid(round_to_int(x), round_to_int(y)); } + bool CheckPoint(vec2 Pos) const { return CheckPoint(Pos.x, Pos.y); } + int GetCollisionAt(float x, float y) const { return GetTile(round_to_int(x), round_to_int(y)); } + int GetWidth() const { return m_Width; } + int GetHeight() const { return m_Height; } + int IntersectLine(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const; + int IntersectLineTeleWeapon(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const; + int IntersectLineTeleHook(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision, int *pTeleNr) const; + void MovePoint(vec2 *pInoutPos, vec2 *pInoutVel, float Elasticity, int *pBounces) const; + void MoveBox(vec2 *pInoutPos, vec2 *pInoutVel, vec2 Size, float Elasticity) const; + bool TestBox(vec2 Pos, vec2 Size) const; // DDRace @@ -51,16 +51,16 @@ void SetCollisionAt(float x, float y, int id); void SetDTile(float x, float y, bool State); void SetDCollisionAt(float x, float y, int Type, int Flags, int Number); - int GetDTileIndex(int Index); - int GetDTileFlags(int Index); - int GetDTileNumber(int Index); - int GetFCollisionAt(float x, float y) { return GetFTile(round_to_int(x), round_to_int(y)); } - int IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision); - int IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision); - int IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision); - int GetIndex(int x, int y); - int GetIndex(vec2 PrevPos, vec2 Pos); - int GetFIndex(int x, int y); + int GetDTileIndex(int Index) const; + int GetDTileFlags(int Index) const; + int GetDTileNumber(int Index) const; + int GetFCollisionAt(float x, float y) const { return GetFTile(round_to_int(x), round_to_int(y)); } + int IntersectNoLaser(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const; + int IntersectNoLaserNW(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const; + int IntersectAir(vec2 Pos0, vec2 Pos1, vec2 *pOutCollision, vec2 *pOutBeforeCollision) const; + int GetIndex(int x, int y) const; + int GetIndex(vec2 PrevPos, vec2 Pos) const; + int GetFIndex(int x, int y) const; int GetMoveRestrictions(CALLBACK_SWITCHACTIVE pfnSwitchActive, void *pUser, vec2 Pos, float Distance = 18.0f, int OverrideCenterTileIndex = -1); int GetMoveRestrictions(vec2 Pos, float Distance = 18.0f) @@ -68,47 +68,47 @@ return GetMoveRestrictions(0, 0, Pos, Distance); } - int GetTile(int x, int y); - int GetFTile(int x, int y); - int Entity(int x, int y, int Layer); - int GetPureMapIndex(float x, float y); - int GetPureMapIndex(vec2 Pos) { return GetPureMapIndex(Pos.x, Pos.y); } - std::list GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices = 0); - int GetMapIndex(vec2 Pos); - bool TileExists(int Index); - bool TileExistsNext(int Index); - vec2 GetPos(int Index); - int GetTileIndex(int Index); - int GetFTileIndex(int Index); - int GetTileFlags(int Index); - int GetFTileFlags(int Index); - int IsTeleport(int Index); - int IsEvilTeleport(int Index); - int IsCheckTeleport(int Index); - int IsCheckEvilTeleport(int Index); - int IsTeleportWeapon(int Index); - int IsTeleportHook(int Index); - int IsTCheckpoint(int Index); - int IsSpeedup(int Index); - int IsTune(int Index); - void GetSpeedup(int Index, vec2 *Dir, int *Force, int *MaxSpeed); - int IsSwitch(int Index); - int GetSwitchNumber(int Index); - int GetSwitchDelay(int Index); - - int IsSolid(int x, int y); - bool IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1); - bool IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1); - int IsWallJump(int Index); - int IsNoLaser(int x, int y); - int IsFNoLaser(int x, int y); + int GetTile(int x, int y) const; + int GetFTile(int x, int y) const; + int Entity(int x, int y, int Layer) const; + int GetPureMapIndex(float x, float y) const; + int GetPureMapIndex(vec2 Pos) const { return GetPureMapIndex(Pos.x, Pos.y); } + std::list GetMapIndices(vec2 PrevPos, vec2 Pos, unsigned MaxIndices = 0) const; + int GetMapIndex(vec2 Pos) const; + bool TileExists(int Index) const; + bool TileExistsNext(int Index) const; + vec2 GetPos(int Index) const; + int GetTileIndex(int Index) const; + int GetFTileIndex(int Index) const; + int GetTileFlags(int Index) const; + int GetFTileFlags(int Index) const; + int IsTeleport(int Index) const; + int IsEvilTeleport(int Index) const; + int IsCheckTeleport(int Index) const; + int IsCheckEvilTeleport(int Index) const; + int IsTeleportWeapon(int Index) const; + int IsTeleportHook(int Index) const; + int IsTCheckpoint(int Index) const; + int IsSpeedup(int Index) const; + int IsTune(int Index) const; + void GetSpeedup(int Index, vec2 *Dir, int *Force, int *MaxSpeed) const; + int IsSwitch(int Index) const; + int GetSwitchNumber(int Index) const; + int GetSwitchDelay(int Index) const; + + int IsSolid(int x, int y) const; + bool IsThrough(int x, int y, int xoff, int yoff, vec2 pos0, vec2 pos1) const; + bool IsHookBlocker(int x, int y, vec2 pos0, vec2 pos1) const; + int IsWallJump(int Index) const; + int IsNoLaser(int x, int y) const; + int IsFNoLaser(int x, int y) const; - int IsCheckpoint(int Index); - int IsFCheckpoint(int Index); + int IsCheckpoint(int Index) const; + int IsFCheckpoint(int Index) const; - int IsMover(int x, int y, int *pFlags); + int IsMover(int x, int y, int *pFlags) const; - vec2 CpSpeed(int index, int Flags = 0); + vec2 CpSpeed(int index, int Flags = 0) const; class CTeleTile *TeleLayer() { return m_pTele; } class CSwitchTile *SwitchLayer() { return m_pSwitch; } diff -Nru ddnet-15.3.2/src/game/ddracechat.h ddnet-15.5.4/src/game/ddracechat.h --- ddnet-15.3.2/src/game/ddracechat.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/ddracechat.h 2021-06-20 09:38:48.000000000 +0000 @@ -27,18 +27,22 @@ CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)") CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s") CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank") +CHAT_COMMAND("swap", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConSwap, this, "Request to swap your tee with another team member") CHAT_COMMAND("save", "?r[code]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConSave, this, "Save team with code r.") CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves") CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name") + CHAT_COMMAND("rankteam", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)") CHAT_COMMAND("teamrank", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamRank, this, "Shows the team rank of player with name r (your team rank by default)") + CHAT_COMMAND("rank", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConRank, this, "Shows the rank of player with name r (your rank by default)") CHAT_COMMAND("top5team", "?s[player name] ?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamTop5, this, "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)") CHAT_COMMAND("teamtop5", "?s[player name] ?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTeamTop5, this, "Shows five team ranks of the ladder or of a player beginning with rank i (1 by default, -1 for worst)") -CHAT_COMMAND("top5", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop5, this, "Shows five ranks of the ladder beginning with rank i (1 by default, -1 for worst)") +CHAT_COMMAND("top", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop, this, "Shows the top ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)") +CHAT_COMMAND("top5", "?i[rank to start with]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTop, this, "Shows the top ranks of the global and regional ladder beginning with rank i (1 by default, -1 for worst)") CHAT_COMMAND("times", "?s[player name] ?i[number of times to skip]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTimes, this, "/times ?s?i shows last 5 times of the server or of a player beginning with name s starting with time i (i = 1 by default, -1 for first)") CHAT_COMMAND("points", "?r[player name]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConPoints, this, "Shows the global points of a player beginning with name r (your rank by default)") -CHAT_COMMAND("top5points", "?i[number]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTopPoints, this, "Shows five points of the global point ladder beginning with rank i (1 by default, -1 for worst)") +CHAT_COMMAND("top5points", "?i[number]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConTopPoints, this, "Shows five points of the global point ladder beginning with rank i (1 by default)") CHAT_COMMAND("team", "?i[id]", CFGFLAG_CHAT | CFGFLAG_SERVER, ConJoinTeam, this, "Lets you join team i (shows your team if left blank)") CHAT_COMMAND("lock", "?i['0'|'1']", CFGFLAG_CHAT | CFGFLAG_SERVER, ConLockTeam, this, "Toggle team lock so no one else can join and so the team restarts when a player dies. /lock 0 to unlock, /lock 1 to lock.") diff -Nru ddnet-15.3.2/src/game/editor/auto_map.cpp ddnet-15.5.4/src/game/editor/auto_map.cpp --- ddnet-15.3.2/src/game/editor/auto_map.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/editor/auto_map.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -397,26 +397,18 @@ int UpdateToX = clamp(X + Width + 3 * pConf->m_EndX, 0, pLayer->m_Width); int UpdateToY = clamp(Y + Height + 3 * pConf->m_EndY, 0, pLayer->m_Height); - CLayerTiles *pUpdateLayer; - if(UpdateFromX != 0 || UpdateFromY != 0 || UpdateToX != pLayer->m_Width || UpdateToY != pLayer->m_Width) - { // Needs a layer to work on - pUpdateLayer = new CLayerTiles(UpdateToX - UpdateFromX, UpdateToY - UpdateFromY); + CLayerTiles *pUpdateLayer = new CLayerTiles(UpdateToX - UpdateFromX, UpdateToY - UpdateFromY); - for(int y = UpdateFromY; y < UpdateToY; y++) + for(int y = UpdateFromY; y < UpdateToY; y++) + { + for(int x = UpdateFromX; x < UpdateToX; x++) { - for(int x = UpdateFromX; x < UpdateToX; x++) - { - CTile *in = &pLayer->m_pTiles[y * pLayer->m_Width + x]; - CTile *out = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX]; - out->m_Index = in->m_Index; - out->m_Flags = in->m_Flags; - } + CTile *in = &pLayer->m_pTiles[y * pLayer->m_Width + x]; + CTile *out = &pUpdateLayer->m_pTiles[(y - UpdateFromY) * pUpdateLayer->m_Width + x - UpdateFromX]; + out->m_Index = in->m_Index; + out->m_Flags = in->m_Flags; } } - else - { - pUpdateLayer = pLayer; - } Proceed(pUpdateLayer, ConfigID, Seed, UpdateFromX, UpdateFromY); @@ -431,8 +423,7 @@ } } - if(pUpdateLayer) - delete pUpdateLayer; + delete pUpdateLayer; } void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedOffsetX, int SeedOffsetY) diff -Nru ddnet-15.3.2/src/game/editor/editor.cpp ddnet-15.5.4/src/game/editor/editor.cpp --- ddnet-15.3.2/src/game/editor/editor.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/editor/editor.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -223,7 +223,7 @@ m_pMap->m_UndoModified++; } -void CLayerGroup::GetSize(float *w, float *h) +void CLayerGroup::GetSize(float *w, float *h) const { *w = 0; *h = 0; @@ -326,171 +326,9 @@ return ReturnValue; } -// copied from gc_menu.cpp, should be more generalized -//extern int ui_do_edit_box(void *id, const CUIRect *rect, char *str, int str_size, float font_size, bool hidden=false); int CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners) { - int Inside = UI()->MouseInside(pRect); - bool ReturnValue = false; - bool UpdateOffset = false; - static int s_AtIndex = 0; - static bool s_DoScroll = false; - static float s_ScrollStart = 0.0f; - - FontSize *= UI()->Scale(); - - if(UI()->LastActiveItem() == pID) - { - m_EditBoxActive = 2; - int Len = str_length(pStr); - if(Len == 0) - s_AtIndex = 0; - - if(Inside && UI()->MouseButton(0)) - { - s_DoScroll = true; - s_ScrollStart = UI()->MouseX(); - int MxRel = (int)(UI()->MouseX() - pRect->x); - - for(int i = 1; i <= Len; i++) - { - if(TextRender()->TextWidth(0, FontSize, pStr, i, std::numeric_limits::max()) - *Offset > MxRel) - { - s_AtIndex = i - 1; - break; - } - - if(i == Len) - s_AtIndex = Len; - } - } - else if(!UI()->MouseButton(0)) - s_DoScroll = false; - else if(s_DoScroll) - { - // do scrolling - if(UI()->MouseX() < pRect->x && s_ScrollStart - UI()->MouseX() > 10.0f) - { - s_AtIndex = maximum(0, s_AtIndex - 1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - else if(UI()->MouseX() > pRect->x + pRect->w && UI()->MouseX() - s_ScrollStart > 10.0f) - { - s_AtIndex = minimum(Len, s_AtIndex + 1); - s_ScrollStart = UI()->MouseX(); - UpdateOffset = true; - } - } - - for(int i = 0; i < Input()->NumEvents(); i++) - { - Len = str_length(pStr); - int NumChars = Len; - ReturnValue |= CLineInput::Manipulate(Input()->GetEvent(i), pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); - } - - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V)) - { - const char *pClipboardText = Input()->GetClipboardText(); - if(pClipboardText) - { - str_append(pStr, pClipboardText, StrSize); - str_sanitize_cc(pStr); - s_AtIndex = str_length(pStr); - ReturnValue = true; - } - } - - if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C) && pStr[0] != '\0') - { - Input()->SetClipboardText(pStr); - } - } - - bool JustGotActive = false; - - if(UI()->ActiveItem() == pID) - { - if(!UI()->MouseButton(0)) - { - s_AtIndex = minimum(s_AtIndex, str_length(pStr)); - s_DoScroll = false; - UI()->SetActiveItem(0); - } - } - else if(UI()->HotItem() == pID) - { - if(UI()->MouseButton(0)) - { - if(UI()->LastActiveItem() != pID) - JustGotActive = true; - UI()->SetActiveItem(pID); - } - } - - if(Inside) - UI()->SetHotItem(pID); - - CUIRect Textbox = *pRect; - RenderTools()->DrawUIRect(&Textbox, ColorRGBA(1, 1, 1, 0.5f), Corners, 3.0f); - Textbox.VMargin(2.0f, &Textbox); - - const char *pDisplayStr = pStr; - char aStars[128]; - - if(Hidden) - { - unsigned s = str_length(pStr); - if(s >= sizeof(aStars)) - s = sizeof(aStars) - 1; - for(unsigned int i = 0; i < s; ++i) - aStars[i] = '*'; - aStars[s] = 0; - pDisplayStr = aStars; - } - - // check if the text has to be moved - if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || Input()->NumEvents())) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex, std::numeric_limits::max()); - if(w - *Offset > Textbox.w) - { - // move to the left - float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1, std::numeric_limits::max()); - do - { - *Offset += minimum(wt - *Offset - Textbox.w, Textbox.w / 3); - } while(w - *Offset > Textbox.w + 0.0001f); - } - else if(w - *Offset < 0.0f) - { - // move to the right - do - { - *Offset = maximum(0.0f, *Offset - Textbox.w / 3); - } while(w - *Offset < -0.0001f); - } - } - UI()->ClipEnable(pRect); - Textbox.x -= *Offset; - - UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1, std::numeric_limits::max()); - - // render the cursor - if(UI()->LastActiveItem() == pID && !JustGotActive) - { - float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex, std::numeric_limits::max()); - Textbox = *pRect; - Textbox.VSplitLeft(2.0f, 0, &Textbox); - Textbox.x += (w - *Offset - TextRender()->TextWidth(0, FontSize, "|", -1, std::numeric_limits::max()) / 2); - - if((2 * time_get() / time_freq()) % 2) // make it blink - UI()->DoLabel(&Textbox, "|", FontSize, -1, std::numeric_limits::max()); - } - UI()->ClipDisable(); - - return ReturnValue; + return m_UIEx.DoEditBox(pID, pRect, pStr, StrSize, FontSize, Offset, Hidden, Corners); } float CEditor::ButtonColorMul(const void *pID) @@ -888,14 +726,14 @@ return Current; } -CLayerGroup *CEditor::GetSelectedGroup() +CLayerGroup *CEditor::GetSelectedGroup() const { if(m_SelectedGroup >= 0 && m_SelectedGroup < m_Map.m_lGroups.size()) return m_Map.m_lGroups[m_SelectedGroup]; return 0x0; } -CLayer *CEditor::GetSelectedLayer(int Index) +CLayer *CEditor::GetSelectedLayer(int Index) const { CLayerGroup *pGroup = GetSelectedGroup(); if(!pGroup) @@ -911,7 +749,7 @@ return 0x0; } -CLayer *CEditor::GetSelectedLayerType(int Index, int Type) +CLayer *CEditor::GetSelectedLayerType(int Index, int Type) const { CLayer *p = GetSelectedLayer(Index); if(p && p->m_Type == Type) @@ -973,7 +811,7 @@ } } -bool CEditor::IsQuadSelected(int Index) +bool CEditor::IsQuadSelected(int Index) const { for(int i = 0; i < m_lSelectedQuads.size(); ++i) if(m_lSelectedQuads[i] == Index) @@ -981,7 +819,7 @@ return false; } -int CEditor::FindSelectedQuadIndex(int Index) +int CEditor::FindSelectedQuadIndex(int Index) const { for(int i = 0; i < m_lSelectedQuads.size(); ++i) if(m_lSelectedQuads[i] == Index) @@ -1159,7 +997,8 @@ TB_Top.VSplitLeft(5.0f, 0, &TB_Top); TB_Top.VSplitLeft(45.0f, &Button, &TB_Top); - if(DoButton_Editor(&Button, "Entities", 0, &Button, 0, "Choose game layer entities image for different gametypes")) + static int s_EntitiesButtonID = 0; + if(DoButton_Editor(&s_EntitiesButtonID, "Entities", 0, &Button, 0, "Choose game layer entities image for different gametypes")) { m_SelectEntitiesFiles.clear(); Storage()->ListDirectory(IStorage::TYPE_ALL, "editor/entities", EntitiesListdirCallback, this); @@ -3811,18 +3650,20 @@ pImg->m_External = 0; return 1; } + View.HSplitTop(5.0f, &Slot, &View); + View.HSplitTop(12.0f, &Slot, &View); } - else + else if(IsVanillaImage(pImg->m_aName)) { if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, 0, "Removes the image from the map file.")) { pImg->m_External = 1; return 1; } + View.HSplitTop(5.0f, &Slot, &View); + View.HSplitTop(12.0f, &Slot, &View); } - View.HSplitTop(5.0f, &Slot, &View); - View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, 0, "Replaces the image with a new one")) { pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Replace Image", "Replace", "mapres", "", ReplaceImage, pEditor); @@ -4038,7 +3879,15 @@ static int s_PopupImageID = 0; if(Result == 2) - UiInvokePopupMenu(&s_PopupImageID, 0, UI()->MouseX(), UI()->MouseY(), 120, 60, PopupImage); + { + CEditorImage *pImg = m_Map.m_lImages[m_SelectedImage]; + int Height; + if(pImg->m_External || IsVanillaImage(pImg->m_aName)) + Height = 60; + else + Height = 43; + UiInvokePopupMenu(&s_PopupImageID, 0, UI()->MouseX(), UI()->MouseY(), 120, Height, PopupImage); + } } ToolBox.HSplitTop(2.0f, 0, &ToolBox); @@ -4497,7 +4346,7 @@ if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL)) { - m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, IGraphics::TEXLOAD_NORESAMPLE); + m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, 0); CImageInfo DummyInfo = m_FilePreviewImageInfo; m_FilePreviewImageInfo.m_pData = NULL; Graphics()->FreePNG(&DummyInfo); @@ -4841,7 +4690,7 @@ { char aBuffer[1024]; str_format(aBuffer, sizeof(aBuffer), "editor/undo_%i.png", m_lUndoSteps[HoveredIndex].m_FileNum); - m_lUndoSteps[HoveredIndex].m_PreviewImage = Graphics()->LoadTexture(aBuffer, IStorage::TYPE_SAVE, CImageInfo::FORMAT_RGB, IGraphics::TEXLOAD_NORESAMPLE); + m_lUndoSteps[HoveredIndex].m_PreviewImage = Graphics()->LoadTexture(aBuffer, IStorage::TYPE_SAVE, CImageInfo::FORMAT_RGB, 0); m_lUndoSteps[HoveredIndex].m_PreviewImageIsLoaded = true; } if(m_lUndoSteps[HoveredIndex].m_PreviewImageIsLoaded) @@ -4856,7 +4705,7 @@ } } -bool CEditor::IsEnvelopeUsed(int EnvelopeIndex) +bool CEditor::IsEnvelopeUsed(int EnvelopeIndex) const { for(int i = 0; i < m_Map.m_lGroups.size(); i++) { @@ -5984,8 +5833,14 @@ } } + // ctrl+shift+alt+s to save as + if(Input()->KeyPress(KEY_S) && CtrlPressed && ShiftPressed && AltPressed) + InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveCopyMap, this); + // ctrl+shift+s to save as + else if(Input()->KeyPress(KEY_S) && CtrlPressed && ShiftPressed) + InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this); // ctrl+s to save - if(Input()->KeyPress(KEY_S) && CtrlPressed) + else if(Input()->KeyPress(KEY_S) && CtrlPressed) { if(m_aFileName[0] && m_ValidSaveFilename) { @@ -5998,14 +5853,6 @@ else InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this); } - - // ctrl+shift+s to save as - if(Input()->KeyPress(KEY_S) && CtrlPressed && ShiftPressed) - InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveMap, this); - - // ctrl+shift+alt+s to save as - if(Input()->KeyPress(KEY_S) && CtrlPressed && ShiftPressed && AltPressed) - InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_MAP, "Save map", "Save", "maps", "", CallbackSaveCopyMap, this); } if(m_GuiActive) @@ -6197,7 +6044,7 @@ m_LastUndoUpdateTime = time_get(); } -int CEditor::GetLineDistance() +int CEditor::GetLineDistance() const { int LineDistance = 512; @@ -6358,6 +6205,8 @@ m_UI.SetGraphics(m_pGraphics, m_pTextRender); m_Map.m_pEditor = this; + m_UIEx.Init(UI(), Kernel(), RenderTools(), Input()->GetEventsRaw(), Input()->GetEventCountRaw()); + m_CheckerTexture = Graphics()->LoadTexture("editor/checker.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); m_BackgroundTexture = Graphics()->LoadTexture("editor/background.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); m_CursorTexture = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); @@ -6446,7 +6295,7 @@ // handle mouse movement float mx, my, Mwx, Mwy; - float rx, ry; + float rx = 0, ry = 0; { Input()->MouseRelative(&rx, &ry); UI()->ConvertMouseMove(&rx, &ry); diff -Nru ddnet-15.3.2/src/game/editor/editor.h ddnet-15.5.4/src/game/editor/editor.h --- ddnet-15.3.2/src/game/editor/editor.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/editor/editor.h 2021-06-20 09:38:48.000000000 +0000 @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -106,7 +107,7 @@ Resort(); } - float EndTime() + float EndTime() const { if(m_lPoints.size()) return m_lPoints[m_lPoints.size() - 1].m_Time * (1.0f / 1000.0f); @@ -204,7 +205,7 @@ void MapScreen(); void Mapping(float *pPoints); - void GetSize(float *w, float *h); + void GetSize(float *w, float *h) const; void DeleteLayer(int Index); int SwapLayers(int Index0, int Index1); @@ -639,6 +640,7 @@ class IStorage *m_pStorage; CRenderTools m_RenderTools; CUI m_UI; + CUIEx m_UIEx; public: class IInput *Input() { return m_pInput; }; @@ -754,7 +756,7 @@ virtual void Init(); virtual void UpdateAndRender(); - virtual bool HasUnsavedData() { return m_Map.m_Modified; } + virtual bool HasUnsavedData() const { return m_Map.m_Modified; } virtual void UpdateMentions() { m_Mentions++; } virtual void ResetMentions() { m_Mentions = 0; } @@ -791,15 +793,15 @@ void Render(); array GetSelectedQuads(); - CLayer *GetSelectedLayerType(int Index, int Type); - CLayer *GetSelectedLayer(int Index); - CLayerGroup *GetSelectedGroup(); + CLayer *GetSelectedLayerType(int Index, int Type) const; + CLayer *GetSelectedLayer(int Index) const; + CLayerGroup *GetSelectedGroup() const; CSoundSource *GetSelectedSource(); void SelectLayer(int LayerIndex, int GroupIndex = -1); void SelectQuad(int Index); void DeleteSelectedQuads(); - bool IsQuadSelected(int Index); - int FindSelectedQuadIndex(int Index); + bool IsQuadSelected(int Index) const; + int FindSelectedQuadIndex(int Index) const; float ScaleFontSize(char *pText, int TextSize, float FontSize, int Width); int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); @@ -1041,7 +1043,7 @@ static void AddImage(const char *pFilename, int StorageType, void *pUser); static void AddSound(const char *pFileName, int StorageType, void *pUser); - bool IsEnvelopeUsed(int EnvelopeIndex); + bool IsEnvelopeUsed(int EnvelopeIndex) const; void RenderImages(CUIRect Toolbox, CUIRect View); void RenderLayers(CUIRect Toolbox, CUIRect View); @@ -1058,9 +1060,9 @@ void AddFileDialogEntry(int Index, CUIRect *pView); void SelectGameLayer(); void SortImages(); - const char *Explain(int Tile, int Layer); + static const char *Explain(int Tile, int Layer); - int GetLineDistance(); + int GetLineDistance() const; void ZoomMouseTarget(float ZoomFactor); static ColorHSVA ms_PickerColor; diff -Nru ddnet-15.3.2/src/game/editor/io.cpp ddnet-15.5.4/src/game/editor/io.cpp --- ddnet-15.3.2/src/game/editor/io.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/editor/io.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -270,7 +270,7 @@ Size += str_length(m_lSettings[i].m_aCommand) + 1; } - char *pSettings = (char *)malloc(std::max(Size, 1)); + char *pSettings = (char *)malloc(maximum(Size, 1)); char *pNext = pSettings; for(int i = 0; i < m_lSettings.size(); i++) { @@ -536,7 +536,7 @@ // save points int TotalSize = sizeof(CEnvPoint) * PointCount; - CEnvPoint *pPoints = (CEnvPoint *)calloc(std::max(PointCount, 1), sizeof(*pPoints)); + CEnvPoint *pPoints = (CEnvPoint *)calloc(maximum(PointCount, 1), sizeof(*pPoints)); PointCount = 0; for(int e = 0; e < m_lEnvelopes.size(); e++) diff -Nru ddnet-15.3.2/src/game/extrainfo.cpp ddnet-15.5.4/src/game/extrainfo.cpp --- ddnet-15.3.2/src/game/extrainfo.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/extrainfo.cpp 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ - -#include "extrainfo.h" -#include -#include -#include - -bool UseExtraInfo(const CNetObj_Projectile *pProj) -{ - bool ExtraInfoFlag = ((abs(pProj->m_VelY) & (1 << 9)) != 0); - return ExtraInfoFlag; -} - -void ExtractInfo(const CNetObj_Projectile *pProj, vec2 *StartPos, vec2 *StartVel) -{ - if(!UseExtraInfo(pProj)) - { - StartPos->x = pProj->m_X; - StartPos->y = pProj->m_Y; - StartVel->x = pProj->m_VelX / 100.0f; - StartVel->y = pProj->m_VelY / 100.0f; - } - else - { - StartPos->x = pProj->m_X / 100.0f; - StartPos->y = pProj->m_Y / 100.0f; - float Angle = pProj->m_VelX / 1000000.0f; - StartVel->x = sin(-Angle); - StartVel->y = cos(-Angle); - } -} - -void ExtractExtraInfo(const CNetObj_Projectile *pProj, int *Owner, bool *Explosive, int *Bouncing, bool *Freeze) -{ - int Data = pProj->m_VelY; - if(Owner) - { - *Owner = Data & 255; - if((Data >> 8) & 1) - *Owner = -(*Owner); - } - if(Bouncing) - *Bouncing = (Data >> 10) & 3; - if(Explosive) - *Explosive = (Data >> 12) & 1; - if(Freeze) - *Freeze = (Data >> 13) & 1; -} - -void SnapshotRemoveExtraInfo(unsigned char *pData) -{ - CSnapshot *pSnap = (CSnapshot *)pData; - for(int Index = 0; Index < pSnap->NumItems(); Index++) - { - CSnapshotItem *pItem = pSnap->GetItem(Index); - if(pItem->Type() == NETOBJTYPE_PROJECTILE) - { - CNetObj_Projectile *pProj = (CNetObj_Projectile *)((void *)pItem->Data()); - if(UseExtraInfo(pProj)) - { - vec2 Pos; - vec2 Vel; - ExtractInfo(pProj, &Pos, &Vel); - pProj->m_X = Pos.x; - pProj->m_Y = Pos.y; - pProj->m_VelX = (int)(Vel.x * 100.0f); - pProj->m_VelY = (int)(Vel.y * 100.0f); - } - } - } -} diff -Nru ddnet-15.3.2/src/game/extrainfo.h ddnet-15.5.4/src/game/extrainfo.h --- ddnet-15.3.2/src/game/extrainfo.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/extrainfo.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ -/* If you are missing that file, acquire a complete release at teeworlds.com. */ -#ifndef GAME_EXTRAINFO_H -#define GAME_EXTRAINFO_H - -#include - -#include - -bool UseExtraInfo(const CNetObj_Projectile *pProj); -void ExtractInfo(const CNetObj_Projectile *pProj, vec2 *StartPos, vec2 *StartVel); -void ExtractExtraInfo(const CNetObj_Projectile *pProj, int *Owner, bool *Explosive, int *Bouncing, bool *Freeze); -void SnapshotRemoveExtraInfo(unsigned char *pData); - -#endif diff -Nru ddnet-15.3.2/src/game/gamecore.cpp ddnet-15.5.4/src/game/gamecore.cpp --- ddnet-15.3.2/src/game/gamecore.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/gamecore.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -19,7 +19,7 @@ return true; } -bool CTuningParams::Get(int Index, float *pValue) +bool CTuningParams::Get(int Index, float *pValue) const { if(Index < 0 || Index >= Num()) return false; @@ -35,7 +35,7 @@ return false; } -bool CTuningParams::Get(const char *pName, float *pValue) +bool CTuningParams::Get(const char *pName, float *pValue) const { for(int i = 0; i < Num(); i++) if(str_comp_nocase(pName, ms_apNames[i]) == 0) @@ -64,6 +64,9 @@ m_pTeams = pTeams; m_Id = -1; + + // fail safe, if core's tuning didn't get updated at all, just fallback to world tuning. + m_Tuning = m_pWorld->m_Tuning[g_Config.m_ClDummy]; Reset(); } @@ -122,11 +125,11 @@ vec2 TargetDirection = normalize(vec2(m_Input.m_TargetX, m_Input.m_TargetY)); - m_Vel.y += m_pWorld->m_Tuning[g_Config.m_ClDummy].m_Gravity; + m_Vel.y += m_Tuning.m_Gravity; - float MaxSpeed = Grounded ? m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GroundControlSpeed : m_pWorld->m_Tuning[g_Config.m_ClDummy].m_AirControlSpeed; - float Accel = Grounded ? m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GroundControlAccel : m_pWorld->m_Tuning[g_Config.m_ClDummy].m_AirControlAccel; - float Friction = Grounded ? m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GroundFriction : m_pWorld->m_Tuning[g_Config.m_ClDummy].m_AirFriction; + float MaxSpeed = Grounded ? m_Tuning.m_GroundControlSpeed : m_Tuning.m_AirControlSpeed; + float Accel = Grounded ? m_Tuning.m_GroundControlAccel : m_Tuning.m_AirControlAccel; + float Friction = Grounded ? m_Tuning.m_GroundFriction : m_Tuning.m_AirFriction; // handle input if(UseInput) @@ -153,14 +156,14 @@ if(Grounded) { m_TriggeredEvents |= COREEVENT_GROUND_JUMP; - m_Vel.y = -m_pWorld->m_Tuning[g_Config.m_ClDummy].m_GroundJumpImpulse; + m_Vel.y = -m_Tuning.m_GroundJumpImpulse; m_Jumped |= 1; m_JumpedTotal = 1; } else if(!(m_Jumped & 2)) { m_TriggeredEvents |= COREEVENT_AIR_JUMP; - m_Vel.y = -m_pWorld->m_Tuning[g_Config.m_ClDummy].m_AirJumpImpulse; + m_Vel.y = -m_Tuning.m_AirJumpImpulse; m_Jumped |= 3; m_JumpedTotal++; } @@ -178,7 +181,7 @@ m_HookPos = m_Pos + TargetDirection * PhysSize * 1.5f; m_HookDir = TargetDirection; m_HookedPlayer = -1; - m_HookTick = SERVER_TICK_SPEED * (1.25f - m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookDuration); + m_HookTick = SERVER_TICK_SPEED * (1.25f - m_Tuning.m_HookDuration); m_TriggeredEvents |= COREEVENT_HOOK_LAUNCH; } } @@ -226,11 +229,11 @@ } else if(m_HookState == HOOK_FLYING) { - vec2 NewPos = m_HookPos + m_HookDir * m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookFireSpeed; - if((!m_NewHook && distance(m_Pos, NewPos) > m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookLength) || (m_NewHook && distance(m_HookTeleBase, NewPos) > m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookLength)) + vec2 NewPos = m_HookPos + m_HookDir * m_Tuning.m_HookFireSpeed; + if((!m_NewHook && distance(m_Pos, NewPos) > m_Tuning.m_HookLength) || (m_NewHook && distance(m_HookTeleBase, NewPos) > m_Tuning.m_HookLength)) { m_HookState = HOOK_RETRACT_START; - NewPos = m_Pos + normalize(NewPos - m_Pos) * m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookLength; + NewPos = m_Pos + normalize(NewPos - m_Pos) * m_Tuning.m_HookLength; m_pReset = true; } @@ -255,7 +258,7 @@ } // Check against other players first - if(this->m_Hook && m_pWorld && m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking) + if(this->m_Hook && m_pWorld && m_Tuning.m_PlayerHooking) { float Distance = 0.0f; for(int i = 0; i < MAX_CLIENTS; i++) @@ -295,7 +298,7 @@ m_HookState = HOOK_RETRACT_START; } - if(GoingThroughTele && m_pTeleOuts && m_pTeleOuts->size() && (*m_pTeleOuts)[teleNr - 1].size()) + if(GoingThroughTele && m_pWorld && m_pTeleOuts && m_pTeleOuts->size() && (*m_pTeleOuts)[teleNr - 1].size()) { m_TriggeredEvents = 0; m_HookedPlayer = -1; @@ -315,7 +318,7 @@ if(m_HookState == HOOK_GRABBED) { - if(m_HookedPlayer != -1) + if(m_HookedPlayer != -1 && m_pWorld) { CCharacterCore *pCharCore = m_pWorld->m_apCharacters[m_HookedPlayer]; if(pCharCore && m_Id != -1 && m_pTeams->CanKeepHook(m_Id, pCharCore->m_Id)) @@ -336,7 +339,7 @@ // don't do this hook rutine when we are hook to a player if(m_HookedPlayer == -1 && distance(m_HookPos, m_Pos) > 46.0f) { - vec2 HookVel = normalize(m_HookPos - m_Pos) * m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookDragAccel; + vec2 HookVel = normalize(m_HookPos - m_Pos) * m_Tuning.m_HookDragAccel; // the hook as more power to drag you up then down. // this makes it easier to get on top of an platform if(HookVel.y > 0) @@ -352,13 +355,13 @@ vec2 NewVel = m_Vel + HookVel; // check if we are under the legal limit for the hook - if(length(NewVel) < m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookDragSpeed || length(NewVel) < length(m_Vel)) + if(length(NewVel) < m_Tuning.m_HookDragSpeed || length(NewVel) < length(m_Vel)) m_Vel = NewVel; // no problem. apply } // release hook (max default hook time is 1.25 s) m_HookTick++; - if(m_HookedPlayer != -1 && (m_HookTick > SERVER_TICK_SPEED + SERVER_TICK_SPEED / 5 || !m_pWorld->m_apCharacters[m_HookedPlayer])) + if(m_HookedPlayer != -1 && (m_HookTick > SERVER_TICK_SPEED + SERVER_TICK_SPEED / 5 || (m_pWorld && !m_pWorld->m_apCharacters[m_HookedPlayer]))) { m_HookedPlayer = -1; m_HookState = HOOK_RETRACTED; @@ -389,7 +392,7 @@ { vec2 Dir = normalize(m_Pos - pCharCore->m_Pos); - bool CanCollide = (m_Super || pCharCore->m_Super) || (pCharCore->m_Collision && m_Collision && !m_NoCollision && !pCharCore->m_NoCollision && m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision); + bool CanCollide = (m_Super || pCharCore->m_Super) || (pCharCore->m_Collision && m_Collision && !m_NoCollision && !pCharCore->m_NoCollision && m_Tuning.m_PlayerCollision); if(CanCollide && Distance < PhysSize * 1.25f && Distance > 0.0f) { @@ -406,12 +409,12 @@ } // handle hook influence - if(m_Hook && m_HookedPlayer == i && m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerHooking) + if(m_Hook && m_HookedPlayer == i && m_Tuning.m_PlayerHooking) { if(Distance > PhysSize * 1.50f) // TODO: fix tweakable variable { - float Accel = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookDragAccel * (Distance / m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookLength); - float DragSpeed = m_pWorld->m_Tuning[g_Config.m_ClDummy].m_HookDragSpeed; + float Accel = m_Tuning.m_HookDragAccel * (Distance / m_Tuning.m_HookLength); + float DragSpeed = m_Tuning.m_HookDragSpeed; vec2 Temp; // add force to the hooked player @@ -440,7 +443,7 @@ void CCharacterCore::Move() { - float RampValue = VelocityRamp(length(m_Vel) * 50, m_pWorld->m_Tuning[g_Config.m_ClDummy].m_VelrampStart, m_pWorld->m_Tuning[g_Config.m_ClDummy].m_VelrampRange, m_pWorld->m_Tuning[g_Config.m_ClDummy].m_VelrampCurvature); + float RampValue = VelocityRamp(length(m_Vel) * 50, m_Tuning.m_VelrampStart, m_Tuning.m_VelrampRange, m_Tuning.m_VelrampCurvature); m_Vel.x = m_Vel.x * RampValue; @@ -462,7 +465,7 @@ m_Vel.x = m_Vel.x * (1.0f / RampValue); - if(m_pWorld && (m_Super || (m_pWorld->m_Tuning[g_Config.m_ClDummy].m_PlayerCollision && m_Collision && !m_NoCollision && !m_Solo))) + if(m_pWorld && (m_Super || (m_Tuning.m_PlayerCollision && m_Collision && !m_NoCollision && !m_Solo))) { // check player collision float Distance = distance(m_Pos, NewPos); diff -Nru ddnet-15.3.2/src/game/gamecore.h ddnet-15.5.4/src/game/gamecore.h --- ddnet-15.3.2/src/game/gamecore.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/gamecore.h 2021-06-20 09:38:48.000000000 +0000 @@ -61,8 +61,8 @@ } bool Set(int Index, float Value); bool Set(const char *pName, float Value); - bool Get(int Index, float *pValue); - bool Get(const char *pName, float *pValue); + bool Get(int Index, float *pValue) const; + bool Get(const char *pName, float *pValue) const; }; inline void StrToInts(int *pInts, int Num, const char *pStr) @@ -265,6 +265,7 @@ bool m_HasTelegunLaser; int m_FreezeEnd; bool m_DeepFrozen; + CTuningParams m_Tuning; private: CTeamsCore *m_pTeams; diff -Nru ddnet-15.3.2/src/game/server/ddracechat.cpp ddnet-15.5.4/src/game/server/ddracechat.cpp --- ddnet-15.3.2/src/game/server/ddracechat.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/ddracechat.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -423,7 +423,7 @@ } } -void CGameContext::ConTop5(IConsole::IResult *pResult, void *pUserData) +void CGameContext::ConTop(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; if(!CheckClientID(pResult->m_ClientID)) @@ -431,15 +431,15 @@ if(g_Config.m_SvHideScore) { - pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "top5", - "Showing the top 5 is not allowed on this server."); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "top", + "Showing the top is not allowed on this server."); return; } if(pResult->NumArguments() > 0) - pSelf->Score()->ShowTop5(pResult->m_ClientID, pResult->GetInteger(0)); + pSelf->Score()->ShowTop(pResult->m_ClientID, pResult->GetInteger(0)); else - pSelf->Score()->ShowTop5(pResult->m_ClientID); + pSelf->Score()->ShowTop(pResult->m_ClientID); } void CGameContext::ConTimes(IConsole::IResult *pResult, void *pUserData) @@ -674,6 +674,90 @@ } } +void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + const char *pName = pResult->GetString(0); + + if(!CheckClientID(pResult->m_ClientID)) + return; + + CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID]; + if(!pPlayer) + return; + + if(pSelf->ProcessSpamProtection(pResult->m_ClientID)) + return; + + if(!g_Config.m_SvSwap) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "print", + "Swap is disabled on this server."); + return; + } + + CGameTeams &Teams = ((CGameControllerDDRace *)pSelf->m_pController)->m_Teams; + + int Team = Teams.m_Core.Team(pResult->m_ClientID); + + if(Team < TEAM_FLOCK || (Team == TEAM_FLOCK && g_Config.m_SvTeam != 3) || Team >= TEAM_SUPER) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "print", + "Join a team to use swap feature, which means you can swap positions with each other."); + return; + } + + int TargetClientId = -1; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(pSelf->m_apPlayers[i] && !str_comp(pName, pSelf->Server()->ClientName(i))) + { + TargetClientId = i; + break; + } + } + + if(TargetClientId < 0) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "swap", "Player not found"); + return; + } + + if(TargetClientId == pResult->m_ClientID) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "swap", "Can't swap with yourself"); + return; + } + + int TargetTeam = Teams.m_Core.Team(TargetClientId); + if(TargetTeam != Team) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "swap", "Player is on a different team"); + return; + } + + if(!Teams.IsStarted(Team)) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "swap", "Need to have started the map to swap with a player."); + return; + } + + CPlayer *pSwapPlayer = pSelf->m_apPlayers[TargetClientId]; + + bool SwapPending = pSwapPlayer->m_SwapTargetsClientID != pResult->m_ClientID; + if(SwapPending) + { + Teams.RequestTeamSwap(pPlayer, pSwapPlayer, Team); + return; + } + + Teams.SwapTeamCharacters(pPlayer, pSwapPlayer, Team); +} + void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; @@ -950,7 +1034,7 @@ { int Team = pResult->GetInteger(0); - if(pPlayer->m_Last_Team + (int64_t)pSelf->Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > pSelf->Server()->Tick()) + if(pPlayer->m_Last_Team + (int64)pSelf->Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > pSelf->Server()->Tick()) { pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "join", "You can\'t change teams that fast!"); diff -Nru ddnet-15.3.2/src/game/server/ddracecommands.cpp ddnet-15.5.4/src/game/server/ddracecommands.cpp --- ddnet-15.3.2/src/game/server/ddracecommands.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/ddracecommands.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -756,7 +756,7 @@ return; CSaveTeam SavedTeam(pSelf->m_pController); - int Result = SavedTeam.save(pPlayer->GetTeam()); + int Result = SavedTeam.Save(pPlayer->GetTeam()); if(CSaveTeam::HandleSaveError(Result, pResult->m_ClientID, pSelf)) return; diff -Nru ddnet-15.3.2/src/game/server/entities/character.cpp ddnet-15.5.4/src/game/server/entities/character.cpp --- ddnet-15.3.2/src/game/server/entities/character.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/character.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -463,7 +463,14 @@ // if we Hit anything, we have to wait for the reload if(Hits) - m_ReloadTimer = Server()->TickSpeed() / 3; + { + float FireDelay; + if(!m_TuneZone) + FireDelay = GameServer()->Tuning()->m_HammerHitFireDelay; + else + FireDelay = GameServer()->TuningList()[m_TuneZone].m_HammerHitFireDelay; + m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000; + } } break; @@ -737,6 +744,14 @@ mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput)); } +void CCharacter::ResetHook() +{ + m_Core.m_HookedPlayer = -1; + m_Core.m_HookState = HOOK_RETRACTED; + m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + m_Core.m_HookPos = m_Core.m_Pos; +} + void CCharacter::ResetInput() { m_Input.m_Direction = 0; @@ -1437,7 +1452,7 @@ { CCharacter *pThis = (CCharacter *)pUser; CCollision *pCollision = pThis->GameServer()->Collision(); - return pCollision->m_pSwitchers && pCollision->m_pSwitchers[Number].m_Status[pThis->Team()] && pThis->Team() != TEAM_SUPER; + return pCollision->m_pSwitchers && pThis->Team() != TEAM_SUPER && pCollision->m_pSwitchers[Number].m_Status[pThis->Team()]; } void CCharacter::HandleTiles(int Index) @@ -1886,10 +1901,7 @@ m_Core.m_Pos = (*m_pTeleOuts)[z - 1][TeleOut]; if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; - m_Core.m_HookPos = m_Core.m_Pos; + ResetHook(); } if(g_Config.m_SvTeleportLoseWeapons) ResetPickups(); @@ -1908,11 +1920,8 @@ if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + ResetHook(); GameWorld()->ReleaseHooked(GetPlayer()->GetCID()); - m_Core.m_HookPos = m_Core.m_Pos; } if(g_Config.m_SvTeleportLoseWeapons) { @@ -1936,11 +1945,8 @@ if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + ResetHook(); GameWorld()->ReleaseHooked(GetPlayer()->GetCID()); - m_Core.m_HookPos = m_Core.m_Pos; } return; @@ -1955,11 +1961,8 @@ if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + ResetHook(); GameWorld()->ReleaseHooked(GetPlayer()->GetCID()); - m_Core.m_HookPos = m_Core.m_Pos; } } return; @@ -1978,10 +1981,7 @@ if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; - m_Core.m_HookPos = m_Core.m_Pos; + ResetHook(); } return; @@ -1995,10 +1995,7 @@ if(!g_Config.m_SvTeleportHoldHook) { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; - m_Core.m_HookPos = m_Core.m_Pos; + ResetHook(); } } return; @@ -2012,9 +2009,9 @@ m_TuneZone = GameServer()->Collision()->IsTune(CurrentIndex); if(m_TuneZone) - m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = GameServer()->TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore + m_Core.m_Tuning = GameServer()->TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore else - m_Core.m_pWorld->m_Tuning[g_Config.m_ClDummy] = *GameServer()->Tuning(); + m_Core.m_Tuning = *GameServer()->Tuning(); if(m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time { @@ -2071,7 +2068,7 @@ void CCharacter::SetRescue() { - m_RescueTee.save(this); + m_RescueTee.Save(this); m_SetSavePos = true; } @@ -2275,9 +2272,8 @@ if(m_Core.m_HookedPlayer != -1) // Keeping hook would allow cheats { - m_Core.m_HookedPlayer = -1; - m_Core.m_HookState = HOOK_RETRACTED; - m_Core.m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; + ResetHook(); + GameWorld()->ReleaseHooked(GetPlayer()->GetCID()); } } else @@ -2335,16 +2331,16 @@ { if(m_SetSavePos && !m_Super) { - if(m_LastRescue + (int64_t)g_Config.m_SvRescueDelay * Server()->TickSpeed() > Server()->Tick()) + if(m_LastRescue + (int64)g_Config.m_SvRescueDelay * Server()->TickSpeed() > Server()->Tick()) { char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "You have to wait %d seconds until you can rescue yourself", (int)((m_LastRescue + (int64_t)g_Config.m_SvRescueDelay * Server()->TickSpeed() - Server()->Tick()) / Server()->TickSpeed())); + str_format(aBuf, sizeof(aBuf), "You have to wait %d seconds until you can rescue yourself", (int)((m_LastRescue + (int64)g_Config.m_SvRescueDelay * Server()->TickSpeed() - Server()->Tick()) / Server()->TickSpeed())); GameServer()->SendChatTarget(GetPlayer()->GetCID(), aBuf); return; } float StartTime = m_StartTime; - m_RescueTee.load(this, Team()); + m_RescueTee.Load(this, Team()); // Don't load these from saved tee: m_Core.m_Vel = vec2(0, 0); m_Core.m_HookState = HOOK_IDLE; diff -Nru ddnet-15.3.2/src/game/server/entities/character.h ddnet-15.5.4/src/game/server/entities/character.h --- ddnet-15.3.2/src/game/server/entities/character.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/character.h 2021-06-20 09:38:48.000000000 +0000 @@ -54,6 +54,7 @@ void OnPredictedInput(CNetObj_PlayerInput *pNewInput); void OnDirectInput(CNetObj_PlayerInput *pNewInput); + void ResetHook(); void ResetInput(); void FireWeapon(); diff -Nru ddnet-15.3.2/src/game/server/entities/door.cpp ddnet-15.5.4/src/game/server/entities/door.cpp --- ddnet-15.3.2/src/game/server/entities/door.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/door.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -83,23 +83,26 @@ if(Char == 0) return; - if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick)) - return; - if(Char->Team() == TEAM_SUPER) { pObj->m_FromX = (int)m_Pos.x; pObj->m_FromY = (int)m_Pos.y; } - else if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) - { - pObj->m_FromX = (int)m_To.x; - pObj->m_FromY = (int)m_To.y; - } else { - pObj->m_FromX = (int)m_Pos.x; - pObj->m_FromY = (int)m_Pos.y; + if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && !GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()] && (!Tick)) + return; + + if(Char->IsAlive() && GameServer()->Collision()->m_NumSwitchers > 0 && GameServer()->Collision()->m_pSwitchers[m_Number].m_Status[Char->Team()]) + { + pObj->m_FromX = (int)m_To.x; + pObj->m_FromY = (int)m_To.y; + } + else + { + pObj->m_FromX = (int)m_Pos.x; + pObj->m_FromY = (int)m_Pos.y; + } } pObj->m_StartTick = Server()->Tick(); } diff -Nru ddnet-15.3.2/src/game/server/entities/dragger.cpp ddnet-15.5.4/src/game/server/entities/dragger.cpp --- ddnet-15.3.2/src/game/server/entities/dragger.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/dragger.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -135,7 +135,7 @@ void CDragger::Reset() { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } void CDragger::Tick() diff -Nru ddnet-15.3.2/src/game/server/entities/gun.cpp ddnet-15.5.4/src/game/server/entities/gun.cpp --- ddnet-15.3.2/src/game/server/entities/gun.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/gun.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -89,7 +89,7 @@ void CGun::Reset() { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } void CGun::Tick() diff -Nru ddnet-15.3.2/src/game/server/entities/laser.cpp ddnet-15.5.4/src/game/server/entities/laser.cpp --- ddnet-15.3.2/src/game/server/entities/laser.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/laser.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -78,7 +78,7 @@ if(m_Energy < 0) { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return; } m_PrevPos = m_Pos; @@ -225,7 +225,7 @@ void CLaser::Reset() { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } void CLaser::Tick() diff -Nru ddnet-15.3.2/src/game/server/entities/light.cpp ddnet-15.5.4/src/game/server/entities/light.cpp --- ddnet-15.3.2/src/game/server/entities/light.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/light.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -76,7 +76,7 @@ void CLight::Reset() { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } void CLight::Tick() diff -Nru ddnet-15.3.2/src/game/server/entities/plasma.cpp ddnet-15.5.4/src/game/server/entities/plasma.cpp --- ddnet-15.3.2/src/game/server/entities/plasma.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/plasma.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -40,7 +40,7 @@ if(m_Explosive) GameServer()->CreateExplosion(m_Pos, -1, WEAPON_GRENADE, true, m_ResponsibleTeam, Hit->Teams()->TeamMask(m_ResponsibleTeam)); - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return true; } @@ -52,7 +52,7 @@ void CPlasma::Reset() { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } void CPlasma::Tick() diff -Nru ddnet-15.3.2/src/game/server/entities/projectile.cpp ddnet-15.5.4/src/game/server/entities/projectile.cpp --- ddnet-15.3.2/src/game/server/entities/projectile.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/projectile.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ void CProjectile::Reset() { if(m_LifeSpan > -2) - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; } vec2 CProjectile::GetPos(float Time) @@ -143,7 +144,7 @@ } else if(m_Owner >= 0 && (m_Type != WEAPON_GRENADE || g_Config.m_SvDestroyBulletsOnDeath)) { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return; } @@ -231,14 +232,14 @@ else if(m_Type == WEAPON_GUN) { GameServer()->CreateDamageInd(CurPos, -atan2(m_Direction.x, m_Direction.y), 10, (m_Owner != -1) ? TeamMask : -1LL); - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return; } else { if(!m_Freeze) { - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return; } } @@ -261,7 +262,7 @@ GameServer()->CreateSound(ColPos, m_SoundImpact, (m_Owner != -1) ? TeamMask : -1LL); } - GameServer()->m_World.DestroyEntity(this); + m_MarkedForDestroy = true; return; } @@ -319,13 +320,27 @@ if(m_Owner != -1 && !CmaskIsSet(TeamMask, SnappingClient)) return; - CNetObj_Projectile *pProj = static_cast(Server()->SnapNewItem(NETOBJTYPE_PROJECTILE, GetID(), sizeof(CNetObj_Projectile))); - if(pProj) + int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR; + + CNetObj_DDNetProjectile DDNetProjectile; + if(SnappingClientVersion >= VERSION_DDNET_ANTIPING_PROJECTILE && FillExtraInfo(&DDNetProjectile)) { - if(SnappingClient > -1 && GameServer()->m_apPlayers[SnappingClient] && GameServer()->m_apPlayers[SnappingClient]->GetClientVersion() >= VERSION_DDNET_ANTIPING_PROJECTILE) - FillExtraInfo(pProj); - else - FillInfo(pProj); + int Type = SnappingClientVersion < VERSION_DDNET_MSG_LEGACY ? (int)NETOBJTYPE_PROJECTILE : NETOBJTYPE_DDNETPROJECTILE; + void *pProj = Server()->SnapNewItem(Type, GetID(), sizeof(DDNetProjectile)); + if(!pProj) + { + return; + } + mem_copy(pProj, &DDNetProjectile, sizeof(DDNetProjectile)); + } + else + { + CNetObj_Projectile *pProj = static_cast(Server()->SnapNewItem(NETOBJTYPE_PROJECTILE, GetID(), sizeof(CNetObj_Projectile))); + if(!pProj) + { + return; + } + FillInfo(pProj); } } @@ -336,14 +351,13 @@ m_Bouncing = Value; } -void CProjectile::FillExtraInfo(CNetObj_Projectile *pProj) +bool CProjectile::FillExtraInfo(CNetObj_DDNetProjectile *pProj) { const int MaxPos = 0x7fffffff / 100; if(abs((int)m_Pos.y) + 1 >= MaxPos || abs((int)m_Pos.x) + 1 >= MaxPos) { //If the modified data would be too large to fit in an integer, send normal data instead - FillInfo(pProj); - return; + return false; } //Send additional/modified info, by modifiying the fields of the netobj float Angle = -atan2f(m_Direction.x, m_Direction.y); @@ -351,18 +365,21 @@ int Data = 0; Data |= (abs(m_Owner) & 255) << 0; if(m_Owner < 0) - Data |= 1 << 8; - Data |= 1 << 9; //This bit tells the client to use the extra info + Data |= PROJECTILEFLAG_NO_OWNER; + //This bit tells the client to use the extra info + Data |= PROJECTILEFLAG_IS_DDNET; + // PROJECTILEFLAG_BOUNCE_HORIZONTAL, PROJECTILEFLAG_BOUNCE_VERTICAL Data |= (m_Bouncing & 3) << 10; if(m_Explosive) - Data |= 1 << 12; + Data |= PROJECTILEFLAG_EXPLOSIVE; if(m_Freeze) - Data |= 1 << 13; + Data |= PROJECTILEFLAG_FREEZE; pProj->m_X = (int)(m_Pos.x * 100.0f); pProj->m_Y = (int)(m_Pos.y * 100.0f); - pProj->m_VelX = (int)(Angle * 1000000.0f); - pProj->m_VelY = Data; + pProj->m_Angle = (int)(Angle * 1000000.0f); + pProj->m_Data = Data; pProj->m_StartTick = m_StartTick; pProj->m_Type = m_Type; + return true; } diff -Nru ddnet-15.3.2/src/game/server/entities/projectile.h ddnet-15.5.4/src/game/server/entities/projectile.h --- ddnet-15.3.2/src/game/server/entities/projectile.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entities/projectile.h 2021-06-20 09:38:48.000000000 +0000 @@ -49,7 +49,7 @@ public: void SetBouncing(int Value); - void FillExtraInfo(CNetObj_Projectile *pProj); + bool FillExtraInfo(CNetObj_DDNetProjectile *pProj); }; #endif diff -Nru ddnet-15.3.2/src/game/server/entity.h ddnet-15.5.4/src/game/server/entity.h --- ddnet-15.3.2/src/game/server/entity.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/entity.h 2021-06-20 09:38:48.000000000 +0000 @@ -34,12 +34,11 @@ */ float m_ProximityRadius; +protected: /* State */ bool m_MarkedForDestroy; public: // TODO: Maybe make protected - /* State */ - /* Variable: m_Pos Contains the current posititon of the entity. @@ -67,10 +66,6 @@ CEntity *TypePrev() { return m_pPrevTypeEntity; } const vec2 &GetPos() const { return m_Pos; } float GetProximityRadius() const { return m_ProximityRadius; } - bool IsMarkedForDestroy() const { return m_MarkedForDestroy; } - - /* Setters */ - void MarkForDestroy() { m_MarkedForDestroy = true; } /* Other functions */ diff -Nru ddnet-15.3.2/src/game/server/gamecontext.cpp ddnet-15.5.4/src/game/server/gamecontext.cpp --- ddnet-15.3.2/src/game/server/gamecontext.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamecontext.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -617,6 +617,14 @@ void CGameContext::SendVoteStatus(int ClientID, int Total, int Yes, int No) { + if(ClientID == -1) + { + for(int i = 0; i < MAX_CLIENTS; ++i) + if(Server()->ClientIngame(i)) + SendVoteStatus(i, Total, Yes, No); + return; + } + if(Total > VANILLA_MAX_CLIENTS && m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE) { Yes = float(Yes) * VANILLA_MAX_CLIENTS / float(Total); @@ -981,9 +989,7 @@ else if(m_VoteUpdate) { m_VoteUpdate = false; - for(int i = 0; i < MAX_CLIENTS; ++i) - if(Server()->ClientIngame(i)) - SendVoteStatus(i, Total, Yes, No); + SendVoteStatus(-1, Total, Yes, No); } } } @@ -1179,15 +1185,7 @@ void CGameContext::OnClientEnter(int ClientID) { - //world.insert_entity(&players[client_id]); - m_apPlayers[ClientID]->Respawn(); - // init the player - Score()->PlayerData(ClientID)->Reset(); - m_apPlayers[ClientID]->m_Score = Score()->PlayerData(ClientID)->m_BestTime ? Score()->PlayerData(ClientID)->m_BestTime : -9999; - - // Can't set score here as LoadScore() is threaded, run it in - // LoadScoreThreaded() instead - Score()->LoadPlayerData(ClientID); + m_pController->OnPlayerConnect(m_apPlayers[ClientID]); if(Server()->IsSixup(ClientID)) { @@ -1246,26 +1244,17 @@ if(!Server()->ClientPrevIngame(ClientID)) { + if(g_Config.m_SvWelcome[0] != 0) + SendChatTarget(ClientID, g_Config.m_SvWelcome); + IServer::CClientInfo Info; Server()->GetClientInfo(ClientID, &Info); if(Info.m_GotDDNetVersion) { - OnClientDDNetVersionKnown(ClientID); + if(OnClientDDNetVersionKnown(ClientID)) + return; // kicked } - char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), m_pController->GetTeamName(m_apPlayers[ClientID]->GetTeam())); - SendChat(-1, CGameContext::CHAT_ALL, aBuf, -1, CHAT_SIX); - - SendChatTarget(ClientID, "DDraceNetwork Mod. Version: " GAME_VERSION); - SendChatTarget(ClientID, "please visit DDNet.tw or say /info and make sure to read our /rules"); - - if(g_Config.m_SvWelcome[0] != 0) - SendChatTarget(ClientID, g_Config.m_SvWelcome); - str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' team=%d", ClientID, Server()->ClientName(ClientID), m_apPlayers[ClientID]->GetTeam()); - - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); - if(g_Config.m_SvShowOthersDefault > 0) { if(g_Config.m_SvShowOthers) @@ -1343,8 +1332,26 @@ } } -void CGameContext::OnClientConnected(int ClientID) +bool CGameContext::OnClientDataPersist(int ClientID, void *pData) { + CPersistentClientData *pPersistent = (CPersistentClientData *)pData; + if(!m_apPlayers[ClientID]) + { + return false; + } + pPersistent->m_IsSpectator = m_apPlayers[ClientID]->GetTeam() == TEAM_SPECTATORS; + return true; +} + +void CGameContext::OnClientConnected(int ClientID, void *pData) +{ + CPersistentClientData *pPersistentData = (CPersistentClientData *)pData; + bool Spec = false; + if(pPersistentData) + { + Spec = pPersistentData->m_IsSpectator; + } + { bool Empty = true; for(auto &pPlayer : m_apPlayers) @@ -1362,7 +1369,7 @@ } // Check which team the player should be on - const int StartTeam = g_Config.m_SvTournamentMode ? TEAM_SPECTATORS : m_pController->GetAutoTeam(ClientID); + const int StartTeam = (Spec || g_Config.m_SvTournamentMode) ? TEAM_SPECTATORS : m_pController->GetAutoTeam(ClientID); if(!m_apPlayers[ClientID]) m_apPlayers[ClientID] = new(ClientID) CPlayer(this, ClientID, StartTeam); @@ -1440,7 +1447,7 @@ } } -void CGameContext::OnClientDDNetVersionKnown(int ClientID) +bool CGameContext::OnClientDDNetVersionKnown(int ClientID) { IServer::CClientInfo Info; Server()->GetClientInfo(ClientID, &Info); @@ -1459,6 +1466,13 @@ } } + // Autoban known bot versions. + if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(ClientVersion)) + { + Server()->Kick(ClientID, "unsupported client"); + return true; + } + CPlayer *pPlayer = m_apPlayers[ClientID]; if(ClientVersion >= VERSION_DDNET_GAMETICK) pPlayer->m_TimerType = g_Config.m_SvDefaultTimerType; @@ -1479,16 +1493,13 @@ // Tell known bot clients that they're botting and we know it. if(((ClientVersion >= 15 && ClientVersion < 100) || ClientVersion == 502) && g_Config.m_SvClientSuggestionBot[0] != '\0') SendBroadcast(g_Config.m_SvClientSuggestionBot, ClientID); - // Autoban known bot versions. - if(g_Config.m_SvBannedVersions[0] != '\0' && IsVersionBanned(ClientVersion)) - { - Server()->Kick(ClientID, "unsupported client"); - } + + return false; } void *CGameContext::PreProcessMsg(int *MsgID, CUnpacker *pUnpacker, int ClientID) { - if(Server()->IsSixup(ClientID)) + if(Server()->IsSixup(ClientID) && *MsgID < OFFSET_UUID) { void *pRawMsg = m_NetObjHandler7.SecureUnpackMsg(*MsgID, pUnpacker); if(!pRawMsg) @@ -2123,7 +2134,7 @@ SendBroadcast(aBuf, ClientID); } } - else if(MsgID == NETMSGTYPE_CL_ISDDNET) + else if(MsgID == NETMSGTYPE_CL_ISDDNETLEGACY) { IServer::CClientInfo Info; Server()->GetClientInfo(ClientID, &Info); @@ -3357,7 +3368,7 @@ { for(int i = 0; i < g_Config.m_DbgDummies; i++) { - OnClientConnected(MAX_CLIENTS - i - 1); + OnClientConnected(MAX_CLIENTS - i - 1, 0); } } #endif @@ -3593,20 +3604,20 @@ m_Events.Clear(); } -bool CGameContext::IsClientReady(int ClientID) +bool CGameContext::IsClientReady(int ClientID) const { return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReady ? true : false; } -bool CGameContext::IsClientPlayer(int ClientID) +bool CGameContext::IsClientPlayer(int ClientID) const { - return m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetTeam() == TEAM_SPECTATORS ? false : true; + return m_apPlayers[ClientID] && m_apPlayers[ClientID]->GetTeam() != TEAM_SPECTATORS; } -CUuid CGameContext::GameUuid() { return m_GameUuid; } -const char *CGameContext::GameType() { return m_pController && m_pController->m_pGameType ? m_pController->m_pGameType : ""; } -const char *CGameContext::Version() { return GAME_VERSION; } -const char *CGameContext::NetVersion() { return GAME_NETVERSION; } +CUuid CGameContext::GameUuid() const { return m_GameUuid; } +const char *CGameContext::GameType() const { return m_pController && m_pController->m_pGameType ? m_pController->m_pGameType : ""; } +const char *CGameContext::Version() const { return GAME_VERSION; } +const char *CGameContext::NetVersion() const { return GAME_NETVERSION; } IGameServer *CreateGameServer() { return new CGameContext; } @@ -3624,14 +3635,14 @@ if(*pLine == '[') do pLine++; - while((pLine - 2 < pLineOrig || *(pLine - 2) != ':') && *pLine != 0); //remove the category (e.g. [Console]: No Such Command) + while((pLine - 2 < pLineOrig || *(pLine - 2) != ':') && *pLine != 0); // remove the category (e.g. [Console]: No Such Command) pSelf->SendChat(-1, CHAT_ALL, pLine); ReentryGuard--; } -void CGameContext::SendChatResponse(const char *pLine, void *pUser, bool Highlighted) +void CGameContext::SendChatResponse(const char *pLine, void *pUser, ColorRGBA PrintColor) { CGameContext *pSelf = (CGameContext *)pUser; int ClientID = pSelf->m_ChatResponseTargetID; @@ -3711,10 +3722,15 @@ void CGameContext::SendRecord(int ClientID) { - CNetMsg_Sv_Record RecordsMsg; - RecordsMsg.m_PlayerTimeBest = Score()->PlayerData(ClientID)->m_BestTime * 100.0f; - RecordsMsg.m_ServerTimeBest = m_pController->m_CurrentRecord * 100.0f; //TODO: finish this - Server()->SendPackMsg(&RecordsMsg, MSGFLAG_VITAL, ClientID); + CNetMsg_Sv_Record Msg; + CNetMsg_Sv_RecordLegacy MsgLegacy; + MsgLegacy.m_PlayerTimeBest = Msg.m_PlayerTimeBest = Score()->PlayerData(ClientID)->m_BestTime * 100.0f; + MsgLegacy.m_ServerTimeBest = Msg.m_ServerTimeBest = m_pController->m_CurrentRecord * 100.0f; //TODO: finish this + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); + if(!Server()->IsSixup(ClientID) && GetClientVersion(ClientID) < VERSION_DDNET_MSG_LEGACY) + { + Server()->SendPackMsg(&MsgLegacy, MSGFLAG_VITAL, ClientID); + } } int CGameContext::ProcessSpamProtection(int ClientID) @@ -3723,16 +3739,27 @@ return 0; if(g_Config.m_SvSpamprotection && m_apPlayers[ClientID]->m_LastChat && m_apPlayers[ClientID]->m_LastChat + Server()->TickSpeed() * g_Config.m_SvChatDelay > Server()->Tick()) return 1; + else if(g_Config.m_SvDnsblChat && Server()->DnsblBlack(ClientID)) + { + SendChatTarget(ClientID, "Players are not allowed to chat from VPNs at this time"); + return 1; + } else m_apPlayers[ClientID]->m_LastChat = Server()->Tick(); + NETADDR Addr; Server()->GetClientAddr(ClientID, &Addr); - int Muted = 0; - for(int i = 0; i < m_NumMutes && !Muted; i++) + int Muted = 0; + if(m_apPlayers[ClientID]->m_JoinTick > m_NonEmptySince + 10 * Server()->TickSpeed()) + Muted = (m_apPlayers[ClientID]->m_JoinTick + Server()->TickSpeed() * g_Config.m_SvChatInitialDelay - Server()->Tick()) / Server()->TickSpeed(); + if(Muted <= 0) { - if(!net_addr_comp_noport(&Addr, &m_aMutes[i].m_Addr)) - Muted = (m_aMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); + for(int i = 0; i < m_NumMutes && Muted <= 0; i++) + { + if(!net_addr_comp_noport(&Addr, &m_aMutes[i].m_Addr)) + Muted = (m_aMutes[i].m_Expire - Server()->Tick()) / Server()->TickSpeed(); + } } if(Muted > 0) @@ -4018,16 +4045,16 @@ SendChatTarget(ClientID, aBuf); } -int CGameContext::GetClientVersion(int ClientID) +int CGameContext::GetClientVersion(int ClientID) const { IServer::CClientInfo Info = {0}; Server()->GetClientInfo(ClientID, &Info); return Info.m_DDNetVersion; } -bool CGameContext::PlayerModerating() +bool CGameContext::PlayerModerating() const { - for(auto &pPlayer : m_apPlayers) + for(const auto &pPlayer : m_apPlayers) { if(pPlayer && pPlayer->m_Moderating) return true; diff -Nru ddnet-15.3.2/src/game/server/gamecontext.h ddnet-15.5.4/src/game/server/gamecontext.h --- ddnet-15.3.2/src/game/server/gamecontext.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamecontext.h 2021-06-20 09:38:48.000000000 +0000 @@ -21,14 +21,6 @@ #include -#ifdef _MSC_VER -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int64 int64; -typedef unsigned __int64 uint64; -#else -#include -#endif /* Tick Game Context (CGameContext::tick) @@ -131,6 +123,11 @@ bool m_Resetting; + struct CPersistentClientData + { + bool m_IsSpectator; + }; + public: IServer *Server() const { return m_pServer; } CConfig *Config() { return m_pConfig; } @@ -254,7 +251,8 @@ void CensorMessage(char *pCensoredMessage, const char *pMessage, int Size); virtual void OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID); - virtual void OnClientConnected(int ClientID); + virtual bool OnClientDataPersist(int ClientID, void *pData); + virtual void OnClientConnected(int ClientID, void *pData); virtual void OnClientEnter(int ClientID); virtual void OnClientDrop(int ClientID, const char *pReason); virtual void OnClientDirectInput(int ClientID, void *pInput); @@ -264,26 +262,27 @@ virtual void OnClientEngineJoin(int ClientID, bool Sixup); virtual void OnClientEngineDrop(int ClientID, const char *pReason); - virtual bool IsClientReady(int ClientID); - virtual bool IsClientPlayer(int ClientID); - - virtual CUuid GameUuid(); - virtual const char *GameType(); - virtual const char *Version(); - virtual const char *NetVersion(); + virtual bool IsClientReady(int ClientID) const; + virtual bool IsClientPlayer(int ClientID) const; + virtual int PersistentClientDataSize() const { return sizeof(CPersistentClientData); } + + virtual CUuid GameUuid() const; + virtual const char *GameType() const; + virtual const char *Version() const; + virtual const char *NetVersion() const; // DDRace - void OnClientDDNetVersionKnown(int ClientID); + bool OnClientDDNetVersionKnown(int ClientID); virtual void FillAntibot(CAntibotRoundData *pData); int ProcessSpamProtection(int ClientID); int GetDDRaceTeam(int ClientID); // Describes the time when the first player joined the server. int64 m_NonEmptySince; int64 m_LastMapVote; - int GetClientVersion(int ClientID); - bool PlayerExists(int ClientID) { return m_apPlayers[ClientID]; }; + int GetClientVersion(int ClientID) const; + bool PlayerExists(int ClientID) const { return m_apPlayers[ClientID]; } // Returns true if someone is actively moderating. - bool PlayerModerating(); + bool PlayerModerating() const; void ForceVote(int EnforcerID, bool Success); // Checks if player can vote and notify them about the reason @@ -347,7 +346,7 @@ static void ConToggleSpecVoted(IConsole::IResult *pResult, void *pUserData); static void ConForcePause(IConsole::IResult *pResult, void *pUserData); static void ConTeamTop5(IConsole::IResult *pResult, void *pUserData); - static void ConTop5(IConsole::IResult *pResult, void *pUserData); + static void ConTop(IConsole::IResult *pResult, void *pUserData); static void ConTimes(IConsole::IResult *pResult, void *pUserData); static void ConPoints(IConsole::IResult *pResult, void *pUserData); static void ConTopPoints(IConsole::IResult *pResult, void *pUserData); @@ -357,6 +356,7 @@ static void ConMapInfo(IConsole::IResult *pResult, void *pUserData); static void ConTimeout(IConsole::IResult *pResult, void *pUserData); static void ConPractice(IConsole::IResult *pResult, void *pUserData); + static void ConSwap(IConsole::IResult *pResult, void *pUserData); static void ConSave(IConsole::IResult *pResult, void *pUserData); static void ConLoad(IConsole::IResult *pResult, void *pUserData); static void ConMap(IConsole::IResult *pResult, void *pUserData); @@ -449,7 +449,7 @@ inline bool IsSpecVote() const { return m_VoteType == VOTE_TYPE_SPECTATE; }; void SendRecord(int ClientID); - static void SendChatResponse(const char *pLine, void *pUser, bool Highlighted = false); + static void SendChatResponse(const char *pLine, void *pUser, ColorRGBA PrintColor = {1, 1, 1, 1}); static void SendChatResponseAll(const char *pLine, void *pUser); virtual void OnSetAuthed(int ClientID, int Level); virtual bool PlayerCollision(); diff -Nru ddnet-15.3.2/src/game/server/gamecontroller.cpp ddnet-15.5.4/src/game/server/gamecontroller.cpp --- ddnet-15.3.2/src/game/server/gamecontroller.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamecontroller.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -384,6 +384,19 @@ return false; } +void IGameController::OnPlayerConnect(CPlayer *pPlayer) +{ + int ClientID = pPlayer->GetCID(); + pPlayer->Respawn(); + + if(!Server()->ClientPrevIngame(ClientID)) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "team_join player='%d:%s' team=%d", ClientID, Server()->ClientName(ClientID), pPlayer->GetTeam()); + GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); + } +} + void IGameController::OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason) { pPlayer->OnDisconnect(); diff -Nru ddnet-15.3.2/src/game/server/gamecontroller.h ddnet-15.5.4/src/game/server/gamecontroller.h --- ddnet-15.3.2/src/game/server/gamecontroller.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamecontroller.h 2021-06-20 09:38:48.000000000 +0000 @@ -6,16 +6,6 @@ #include #include -class CDoor; -#if !defined(_MSC_VER) || _MSC_VER >= 1600 -#include -#else -typedef __int32 int32_t; -typedef unsigned __int32 uint32_t; -typedef __int64 int64; -typedef unsigned __int64 uint64; -#endif - /* Class: Game Controller Controls the main game logic. Keeping track of team and player score, @@ -115,6 +105,7 @@ */ virtual bool OnEntity(int Index, vec2 Pos, int Layer, int Flags, int Number = 0); + virtual void OnPlayerConnect(class CPlayer *pPlayer); virtual void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason); void OnReset(); diff -Nru ddnet-15.3.2/src/game/server/gamemodes/DDRace.cpp ddnet-15.5.4/src/game/server/gamemodes/DDRace.cpp --- ddnet-15.3.2/src/game/server/gamemodes/DDRace.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamemodes/DDRace.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,18 +1,22 @@ /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ /* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */ #include "DDRace.h" -#include "gamemode.h" + #include #include #include #include #include #include +#include + +#define GAME_TYPE_NAME "DDraceNetwork" +#define TEST_TYPE_NAME "TestDDraceNetwork" CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) : IGameController(pGameServer), m_Teams(pGameServer), m_pInitResult(nullptr) { - m_pGameType = g_Config.m_SvTestingCommands ? TEST_NAME : GAME_NAME; + m_pGameType = g_Config.m_SvTestingCommands ? TEST_TYPE_NAME : GAME_TYPE_NAME; InitTeleporter(); } @@ -22,6 +26,11 @@ // Nothing to clean } +CScore *CGameControllerDDRace::Score() +{ + return GameServer()->Score(); +} + void CGameControllerDDRace::OnCharacterSpawn(CCharacter *pChr) { IGameController::OnCharacterSpawn(pChr); @@ -112,6 +121,30 @@ } } +void CGameControllerDDRace::OnPlayerConnect(CPlayer *pPlayer) +{ + IGameController::OnPlayerConnect(pPlayer); + int ClientID = pPlayer->GetCID(); + + // init the player + Score()->PlayerData(ClientID)->Reset(); + pPlayer->m_Score = Score()->PlayerData(ClientID)->m_BestTime ? Score()->PlayerData(ClientID)->m_BestTime : -9999; + + // Can't set score here as LoadScore() is threaded, run it in + // LoadScoreThreaded() instead + Score()->LoadPlayerData(ClientID); + + if(!Server()->ClientPrevIngame(ClientID)) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "'%s' entered and joined the %s", Server()->ClientName(ClientID), GetTeamName(pPlayer->GetTeam())); + GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, -1, CGameContext::CHAT_SIX); + + GameServer()->SendChatTarget(ClientID, "DDraceNetwork Mod. Version: " GAME_VERSION); + GameServer()->SendChatTarget(ClientID, "please visit DDNet.tw or say /info and make sure to read our /rules"); + } +} + void CGameControllerDDRace::OnPlayerDisconnect(CPlayer *pPlayer, const char *pReason) { int ClientID = pPlayer->GetCID(); diff -Nru ddnet-15.3.2/src/game/server/gamemodes/DDRace.h ddnet-15.5.4/src/game/server/gamemodes/DDRace.h --- ddnet-15.3.2/src/game/server/gamemodes/DDRace.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamemodes/DDRace.h 2021-06-20 09:38:48.000000000 +0000 @@ -15,9 +15,12 @@ CGameControllerDDRace(class CGameContext *pGameServer); ~CGameControllerDDRace(); + CScore *Score(); + void OnCharacterSpawn(class CCharacter *pChr) override; void HandleCharacterTiles(class CCharacter *pChr, int MapIndex) override; + void OnPlayerConnect(class CPlayer *pPlayer) override; void OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason) override; void Tick() override; diff -Nru ddnet-15.3.2/src/game/server/gamemodes/gamemode.h ddnet-15.5.4/src/game/server/gamemodes/gamemode.h --- ddnet-15.3.2/src/game/server/gamemodes/gamemode.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gamemodes/gamemode.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ -/* This is used for all NONE Official Builds of DDRace, if you changed the source in anyway change this to something other than DDRace */ -#ifndef GAME_SERVER_GAMEMODES_GAMEMODE_H -#define GAME_SERVER_GAMEMODES_GAMEMODE_H - -#define GAME_NAME "DDraceNetwork" -#define TEST_NAME "TestDDraceNetwork" -#endif // GAME_SERVER_GAMEMODES_GAMEMODE_H diff -Nru ddnet-15.3.2/src/game/server/gameworld.cpp ddnet-15.5.4/src/game/server/gameworld.cpp --- ddnet-15.3.2/src/game/server/gameworld.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gameworld.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -81,11 +81,6 @@ m_apFirstEntityTypes[pEnt->m_ObjType] = pEnt; } -void CGameWorld::DestroyEntity(CEntity *pEnt) -{ - pEnt->m_MarkedForDestroy = true; -} - void CGameWorld::RemoveEntity(CEntity *pEnt) { // not in the list diff -Nru ddnet-15.3.2/src/game/server/gameworld.h ddnet-15.5.4/src/game/server/gameworld.h --- ddnet-15.3.2/src/game/server/gameworld.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/gameworld.h 2021-06-20 09:38:48.000000000 +0000 @@ -123,15 +123,6 @@ void RemoveEntity(CEntity *pEntity); /* - Function: destroy_entity - Destroys an entity in the world. - - Arguments: - entity - Entity to destroy - */ - void DestroyEntity(CEntity *pEntity); - - /* Function: snap Calls snap on all the entities in the world to create the snapshot. @@ -151,8 +142,6 @@ void Tick(); // DDRace - - std::list IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, vec2 &NewPos, class CEntity *pNotThis); void ReleaseHooked(int ClientID); /* diff -Nru ddnet-15.3.2/src/game/server/player.cpp ddnet-15.5.4/src/game/server/player.cpp --- ddnet-15.3.2/src/game/server/player.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/player.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -138,6 +138,7 @@ m_NotEligibleForFinish = false; m_EligibleForFinishCheck = 0; m_VotedForPractice = false; + m_SwapTargetsClientID = -1; } static int PlayerFlags_SevenToSix(int Flags) @@ -147,6 +148,8 @@ Six |= PLAYERFLAG_CHATTING; if(Flags & protocol7::PLAYERFLAG_SCOREBOARD) Six |= PLAYERFLAG_SCOREBOARD; + if(Flags & protocol7::PLAYERFLAG_AIM) + Six |= PLAYERFLAG_AIM; return Six; } @@ -335,7 +338,7 @@ pClientInfo->m_ColorBody = m_TeeInfos.m_ColorBody; pClientInfo->m_ColorFeet = m_TeeInfos.m_ColorFeet; - int ClientVersion = GetClientVersion(); + int SnappingClientVersion = SnappingClient >= 0 ? GameServer()->GetClientVersion(SnappingClient) : CLIENT_VERSIONNR; int Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID]; int Score = abs(m_Score) * -1; @@ -351,11 +354,11 @@ pPlayerInfo->m_Latency = Latency; pPlayerInfo->m_Score = Score; - pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || ClientVersion >= VERSION_DDNET_OLD)); + pPlayerInfo->m_Local = (int)(m_ClientID == SnappingClient && (m_Paused != PAUSE_PAUSED || SnappingClientVersion >= VERSION_DDNET_OLD)); pPlayerInfo->m_ClientID = id; - pPlayerInfo->m_Team = (ClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS; + pPlayerInfo->m_Team = (SnappingClientVersion < VERSION_DDNET_OLD || m_Paused != PAUSE_PAUSED || m_ClientID != SnappingClient) && m_Paused < PAUSE_SPEC ? m_Team : TEAM_SPECTATORS; - if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && ClientVersion < VERSION_DDNET_OLD) + if(m_ClientID == SnappingClient && m_Paused == PAUSE_PAUSED && SnappingClientVersion < VERSION_DDNET_OLD) pPlayerInfo->m_Team = TEAM_SPECTATORS; } else @@ -365,6 +368,8 @@ return; pPlayerInfo->m_PlayerFlags = PlayerFlags_SixToSeven(m_PlayerFlags); + if(SnappingClientVersion >= VERSION_DDRACE && (m_PlayerFlags & PLAYERFLAG_AIM)) + pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_AIM; if(Server()->ClientAuthed(m_ClientID)) pPlayerInfo->m_PlayerFlags |= protocol7::PLAYERFLAG_ADMIN; @@ -689,6 +694,7 @@ m_WeakHookSpawn = false; m_Spawning = false; m_pCharacter = new(m_ClientID) CCharacter(&GameServer()->m_World); + m_ViewPos = SpawnPos; m_pCharacter->Spawn(this, SpawnPos); GameServer()->CreatePlayerSpawn(SpawnPos, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID)); @@ -801,7 +807,7 @@ bool CPlayer::CanOverrideDefaultEmote() const { - return m_LastEyeEmote == 0 || m_LastEyeEmote + (int64_t)g_Config.m_SvEyeEmoteChangeDelay * Server()->TickSpeed() < Server()->Tick(); + return m_LastEyeEmote == 0 || m_LastEyeEmote + (int64)g_Config.m_SvEyeEmoteChangeDelay * Server()->TickSpeed() < Server()->Tick(); } void CPlayer::ProcessPause() @@ -838,12 +844,13 @@ case PAUSE_NONE: if(m_pCharacter->IsPaused()) // First condition might be unnecessary { - if(!Force && m_LastPause && m_LastPause + (int64_t)g_Config.m_SvSpecFrequency * Server()->TickSpeed() > Server()->Tick()) + if(!Force && m_LastPause && m_LastPause + (int64)g_Config.m_SvSpecFrequency * Server()->TickSpeed() > Server()->Tick()) { GameServer()->SendChatTarget(m_ClientID, "Can't /spec that quickly."); return m_Paused; // Do not update state. Do not collect $200 } m_pCharacter->Pause(false); + m_ViewPos = m_pCharacter->m_Pos; GameServer()->CreatePlayerSpawn(m_pCharacter->m_Pos, GameServer()->m_pController->GetMaskForPlayerWorldEvent(m_ClientID)); } // fall-thru @@ -929,13 +936,21 @@ } break; case CScorePlayerResult::ALL: + { + bool PrimaryMessage = true; for(auto &aMessage : Result.m_Data.m_aaMessages) { if(aMessage[0] == 0) break; - GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aMessage, m_ClientID); + + if(GameServer()->ProcessSpamProtection(m_ClientID) && PrimaryMessage) + break; + + GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aMessage, -1); + PrimaryMessage = false; } break; + } case CScorePlayerResult::BROADCAST: if(Result.m_Data.m_Broadcast[0] != 0) GameServer()->SendBroadcast(Result.m_Data.m_Broadcast, -1); diff -Nru ddnet-15.3.2/src/game/server/player.h ddnet-15.5.4/src/game/server/player.h --- ddnet-15.3.2/src/game/server/player.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/player.h 2021-06-20 09:38:48.000000000 +0000 @@ -210,6 +210,7 @@ bool m_NotEligibleForFinish; int64 m_EligibleForFinishCheck; bool m_VotedForPractice; + int m_SwapTargetsClientID; //Client ID of the swap target for the given player }; #endif diff -Nru ddnet-15.3.2/src/game/server/save.cpp ddnet-15.5.4/src/game/server/save.cpp --- ddnet-15.3.2/src/game/server/save.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/save.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -17,7 +17,7 @@ { } -void CSaveTee::save(CCharacter *pChr) +void CSaveTee::Save(CCharacter *pChr) { m_ClientID = pChr->m_pPlayer->GetCID(); str_copy(m_aName, pChr->Server()->ClientName(m_ClientID), sizeof(m_aName)); @@ -55,7 +55,7 @@ m_TuneZoneOld = pChr->m_TuneZoneOld; if(pChr->m_StartTime) - m_Time = pChr->Server()->Tick() - pChr->m_StartTime; + m_Time = pChr->Server()->Tick() - pChr->m_StartTime + g_Config.m_SvSaveSwapGamesPenalty * pChr->Server()->TickSpeed(); else m_Time = 0; @@ -108,7 +108,7 @@ FormatUuid(pChr->GameServer()->GameUuid(), m_aGameUuid, sizeof(m_aGameUuid)); } -void CSaveTee::load(CCharacter *pChr, int Team) +void CSaveTee::Load(CCharacter *pChr, int Team) { pChr->m_pPlayer->Pause(m_Paused, true); @@ -209,9 +209,6 @@ char *CSaveTee::GetString(const CSaveTeam *pTeam) { - // Add time penalty of 60 seconds (only to the database) - int Time = m_Time + 60 * SERVER_TICK_SPEED; - int HookedPlayer = -1; if(m_HookedPlayer != -1) { @@ -268,7 +265,7 @@ m_LastWeapon, m_QueuedWeapon, // tee states m_SuperJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeTick, m_DeepFreeze, m_EndlessHook, - m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, Time, + m_DDRaceState, m_Hit, m_Collision, m_TuneZone, m_TuneZoneOld, m_Hook, m_Time, (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y, m_TeleCheckpoint, m_LastPenalty, (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y, @@ -415,7 +412,7 @@ delete[] m_pSavedTees; } -int CSaveTeam::save(int Team) +int CSaveTeam::Save(int Team) { if(g_Config.m_SvTeam == 3 || (Team > 0 && Team < MAX_CLIENTS)) { @@ -447,7 +444,7 @@ continue; if(m_MembersCount == j) return 3; - m_pSavedTees[j].save(p); + m_pSavedTees[j].Save(p); j++; } if(m_MembersCount != j) @@ -498,7 +495,7 @@ return true; } -void CSaveTeam::load(int Team, bool KeepCurrentWeakStrong) +void CSaveTeam::Load(int Team, bool KeepCurrentWeakStrong) { CGameTeams *pTeams = &(((CGameControllerDDRace *)m_pController)->m_Teams); @@ -513,7 +510,7 @@ if(m_pController->GameServer()->m_apPlayers[ClientID] && pTeams->m_Core.Team(ClientID) == Team) { CCharacter *pChr = MatchCharacter(m_pSavedTees[i].GetClientID(), i, KeepCurrentWeakStrong); - m_pSavedTees[i].load(pChr, Team); + m_pSavedTees[i].Load(pChr, Team); } } diff -Nru ddnet-15.3.2/src/game/server/save.h ddnet-15.5.4/src/game/server/save.h --- ddnet-15.3.2/src/game/server/save.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/save.h 2021-06-20 09:38:48.000000000 +0000 @@ -15,8 +15,8 @@ public: CSaveTee(); ~CSaveTee(); - void save(CCharacter *pchr); - void load(CCharacter *pchr, int Team); + void Save(CCharacter *pchr); + void Load(CCharacter *pchr, int Team); char *GetString(const CSaveTeam *pTeam); int FromString(const char *String); void LoadHookedPlayer(const CSaveTeam *pTeam); @@ -119,8 +119,8 @@ int FromString(const char *String); // returns true if a team can load, otherwise writes a nice error Message in pMessage bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen); - int save(int Team); - void load(int Team, bool KeepCurrentWeakStrong); + int Save(int Team); + void Load(int Team, bool KeepCurrentWeakStrong); CSaveTee *m_pSavedTees; // returns true if an error occured diff -Nru ddnet-15.3.2/src/game/server/score.cpp ddnet-15.5.4/src/game/server/score.cpp --- ddnet-15.3.2/src/game/server/score.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/score.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -66,20 +66,29 @@ mem_zero(&m_TeamID.m_aData, sizeof(m_TeamID)); } -bool CTeamrank::NextSqlResult(IDbConnection *pSqlServer) +bool CTeamrank::NextSqlResult(IDbConnection *pSqlServer, bool *pEnd, char *pError, int ErrorSize) { pSqlServer->GetBlob(1, m_TeamID.m_aData, sizeof(m_TeamID.m_aData)); pSqlServer->GetString(2, m_aaNames[0], sizeof(m_aaNames[0])); m_NumNames = 1; - while(pSqlServer->Step()) + bool End = false; + while(!pSqlServer->Step(&End, pError, ErrorSize) && !End) { CUuid TeamID; pSqlServer->GetBlob(1, TeamID.m_aData, sizeof(TeamID.m_aData)); if(m_TeamID != TeamID) - return true; + { + *pEnd = false; + return false; + } pSqlServer->GetString(2, m_aaNames[m_NumNames], sizeof(m_aaNames[m_NumNames])); m_NumNames++; } + if(!End) + { + return true; + } + *pEnd = true; return false; } @@ -105,7 +114,7 @@ } void CScore::ExecPlayerThread( - bool (*pFuncPtr)(IDbConnection *, const ISqlData *), + bool (*pFuncPtr)(IDbConnection *, const ISqlData *, char *pError, int ErrorSize), const char *pThreadName, int ClientID, const char *pName, @@ -117,6 +126,7 @@ auto Tmp = std::unique_ptr(new CSqlPlayerRequest(pResult)); str_copy(Tmp->m_Name, pName, sizeof(Tmp->m_Name)); str_copy(Tmp->m_Map, g_Config.m_SvMap, sizeof(Tmp->m_Map)); + str_copy(Tmp->m_Server, g_Config.m_SvSqlServerName, sizeof(Tmp->m_Server)); str_copy(Tmp->m_RequestingPlayer, Server()->ClientName(ClientID), sizeof(Tmp->m_RequestingPlayer)); Tmp->m_Offset = Offset; @@ -128,7 +138,7 @@ CPlayer *pPlayer = GameServer()->m_apPlayers[ClientID]; if(pPlayer == 0) return true; - if(pPlayer->m_LastSQLQuery + (int64_t)g_Config.m_SvSqlQueriesDelay * Server()->TickSpeed() >= Server()->Tick()) + if(pPlayer->m_LastSQLQuery + (int64)g_Config.m_SvSqlQueriesDelay * Server()->TickSpeed() >= Server()->Tick()) return true; pPlayer->m_LastSQLQuery = Server()->Tick(); return false; @@ -190,7 +200,7 @@ m_pPool->Execute(Init, std::move(Tmp), "load best time"); } -bool CScore::Init(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::Init(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlInitData *pData = dynamic_cast(pGameData); CScoreInitResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -200,13 +210,23 @@ str_format(aBuf, sizeof(aBuf), "SELECT Time FROM %s_race WHERE Map=? ORDER BY `Time` ASC LIMIT 1;", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) + { pResult->m_CurrentRecord = pSqlServer->GetFloat(1); + } - return true; + return false; } void CScore::LoadPlayerData(int ClientID) @@ -215,7 +235,7 @@ } // update stuff -bool CScore::LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -232,11 +252,19 @@ "ORDER BY Time ASC " "LIMIT 1;", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_RequestingPlayer); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { // get the best time float Time = pSqlServer->GetFloat(1); @@ -259,10 +287,17 @@ "FROM %s_race " "WHERE Name = ?", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_RequestingPlayer); - if(pSqlServer->Step() && !pSqlServer->IsNull(2)) + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End && !pSqlServer->IsNull(2)) { char aCurrent[TIMESTAMP_STR_LENGTH]; pSqlServer->GetString(1, aCurrent, sizeof(aCurrent)); @@ -273,7 +308,7 @@ if(sscanf(aCurrent, "%d-%d-%d", &CurrentYear, &CurrentMonth, &CurrentDay) == 3 && sscanf(aStamp, "%d-%d-%d", &StampYear, &StampMonth, &StampDay) == 3 && CurrentMonth == StampMonth && CurrentDay == StampDay) pResult->m_Data.m_Info.m_Birthday = CurrentYear - StampYear; } - return true; + return false; } void CScore::MapVote(int ClientID, const char *MapName) @@ -283,7 +318,7 @@ ExecPlayerThread(MapVoteThread, "map vote", ClientID, MapName, 0); } -bool CScore::MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -308,12 +343,20 @@ " LENGTH(Map), Map " "LIMIT 1;", pSqlServer->GetPrefix(), pSqlServer->CollateNocase()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, aFuzzyMap); pSqlServer->BindString(2, pData->m_Name); pSqlServer->BindString(3, aMapPrefix); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { pResult->SetVariant(CScorePlayerResult::MAP_VOTE); auto *MapVote = &pResult->m_Data.m_MapVote; @@ -333,7 +376,7 @@ "Example: /map %%castle for \"Out of Castle\"", pData->m_Name); } - return true; + return false; } void CScore::MapInfo(int ClientID, const char *MapName) @@ -343,7 +386,7 @@ ExecPlayerThread(MapInfoThread, "map info", ClientID, MapName, 0); } -bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -386,13 +429,21 @@ aTimestamp, aCurrentTimestamp, aTimestamp, pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->CollateNocase()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_RequestingPlayer); pSqlServer->BindString(2, aFuzzyMap); pSqlServer->BindString(3, pData->m_Name); pSqlServer->BindString(4, aMapPrefix); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { char aMap[MAX_MAP_LENGTH]; pSqlServer->GetString(1, aMap, sizeof(aMap)); @@ -404,10 +455,10 @@ int Stars = pSqlServer->GetInt(5); int Finishes = pSqlServer->GetInt(6); int Finishers = pSqlServer->GetInt(7); - float Median = pSqlServer->GetInt(8); + float Median = !pSqlServer->IsNull(8) ? pSqlServer->GetInt(8) : -1.0f; int Stamp = pSqlServer->GetInt(9); int Ago = pSqlServer->GetInt(10); - float OwnTime = pSqlServer->GetFloat(11); + float OwnTime = !pSqlServer->IsNull(11) ? pSqlServer->GetFloat(11) : -1.0f; char aAgoString[40] = "\0"; char aReleasedString[60] = "\0"; @@ -458,7 +509,7 @@ str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), "No map like \"%s\" found.", pData->m_Name); } - return true; + return false; } void CScore::SaveScore(int ClientID, float Time, const char *pTimestamp, float CpTime[NUM_CHECKPOINTS], bool NotEligible) @@ -484,7 +535,7 @@ m_pPool->ExecuteWrite(SaveScoreThread, std::move(Tmp), "save score"); } -bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +bool CScore::SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize) { const CSqlScoreData *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -495,22 +546,40 @@ str_format(aBuf, sizeof(aBuf), "SELECT COUNT(*) AS NumFinished FROM %s_race WHERE Map=? AND Name=? ORDER BY time ASC LIMIT 1;", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Name); - pSqlServer->Step(); + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } int NumFinished = pSqlServer->GetInt(1); if(NumFinished == 0) { str_format(aBuf, sizeof(aBuf), "SELECT Points FROM %s_maps WHERE Map=?", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { int Points = pSqlServer->GetInt(1); - pSqlServer->AddPoints(pData->m_Name, Points); + if(pSqlServer->AddPoints(pData->m_Name, Points, pError, ErrorSize)) + { + return true; + } str_format(paMessages[0], sizeof(paMessages[0]), "You earned %d point%s for finishing this map!", Points, Points == 1 ? "" : "s"); @@ -540,15 +609,22 @@ pData->m_aCpCurrent[18], pData->m_aCpCurrent[19], pData->m_aCpCurrent[20], pData->m_aCpCurrent[21], pData->m_aCpCurrent[22], pData->m_aCpCurrent[23], pData->m_aCpCurrent[24]); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Name); pSqlServer->BindString(3, pData->m_aTimestamp); pSqlServer->BindString(4, g_Config.m_SvSqlServerName); pSqlServer->BindString(5, pData->m_GameUuid); pSqlServer->Print(); - pSqlServer->Step(); - return true; + int NumInserted; + if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize)) + { + return true; + } + return false; } void CScore::SaveTeamScore(int *aClientIDs, unsigned int Size, float Time, const char *pTimestamp) @@ -573,7 +649,7 @@ m_pPool->ExecuteWrite(SaveTeamScoreThread, std::move(Tmp), "save team score"); } -bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +bool CScore::SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize) { const CSqlTeamScoreData *pData = dynamic_cast(pGameData); @@ -594,20 +670,31 @@ ") as l INNER JOIN %s_teamrace AS r ON l.ID = r.ID " "ORDER BY l.ID, Name COLLATE %s;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->BinaryCollate()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_aNames[0]); bool FoundTeam = false; float Time; CTeamrank Teamrank; - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { - bool SearchTeam = true; - while(SearchTeam) + bool SearchTeamEnd = false; + while(!SearchTeamEnd) { Time = pSqlServer->GetFloat(3); - SearchTeam = Teamrank.NextSqlResult(pSqlServer); + if(Teamrank.NextSqlResult(pSqlServer, &SearchTeamEnd, pError, ErrorSize)) + { + return true; + } if(Teamrank.SamePlayers(&aNames)) { FoundTeam = true; @@ -623,12 +710,19 @@ str_format(aBuf, sizeof(aBuf), "UPDATE %s_teamrace SET Time=%.2f, Timestamp=?, DDNet7=false, GameID=? WHERE ID = ?;", pSqlServer->GetPrefix(), pData->m_Time); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_aTimestamp); pSqlServer->BindString(2, pData->m_GameUuid); pSqlServer->BindBlob(3, Teamrank.m_TeamID.m_aData, sizeof(Teamrank.m_TeamID.m_aData)); pSqlServer->Print(); - pSqlServer->Step(); + int NumUpdated; + if(pSqlServer->ExecuteUpdate(&NumUpdated, pError, ErrorSize)) + { + return true; + } } } else @@ -642,17 +736,24 @@ "VALUES (?, ?, %s, %.2f, ?, ?, false);", pSqlServer->InsertIgnore(), pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc(), pData->m_Time); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_aNames[i]); pSqlServer->BindString(3, pData->m_aTimestamp); pSqlServer->BindBlob(4, GameID.m_aData, sizeof(GameID.m_aData)); pSqlServer->BindString(5, pData->m_GameUuid); pSqlServer->Print(); - pSqlServer->Step(); + int NumInserted; + if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize)) + { + return true; + } } } - return true; + return false; } void CScore::ShowRank(int ClientID, const char *pName) @@ -662,30 +763,69 @@ ExecPlayerThread(ShowRankThread, "show rank", ClientID, pName, 0); } -bool CScore::ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); + char aServerLike[16]; + str_format(aServerLike, sizeof(aServerLike), "%%%s%%", pData->m_Server); + // check sort method char aBuf[600]; - str_format(aBuf, sizeof(aBuf), "SELECT Rank, Time, PercentRank " "FROM (" " SELECT RANK() OVER w AS Rank, PERCENT_RANK() OVER w as PercentRank, Name, MIN(Time) AS Time " " FROM %s_race " " WHERE Map = ? " + " AND Server LIKE ?" " GROUP BY Name " " WINDOW w AS (ORDER BY Time)" ") as a " "WHERE Name = ?;", pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); - pSqlServer->BindString(2, pData->m_Name); + pSqlServer->BindString(2, aServerLike); + pSqlServer->BindString(3, pData->m_Name); + + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + + char aRegionalRank[16]; + if(End) + { + str_copy(aRegionalRank, "unranked", sizeof(aRegionalRank)); + } + else + { + str_format(aRegionalRank, sizeof(aRegionalRank), "rank %d", pSqlServer->GetInt(1)); + } + + const char *pAny = "%"; + + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pAny); + pSqlServer->BindString(3, pData->m_Name); + + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } - if(pSqlServer->Step()) + if(!End) { int Rank = pSqlServer->GetInt(1); float Time = pSqlServer->GetFloat(2); @@ -700,9 +840,23 @@ else { pResult->m_MessageKind = CScorePlayerResult::ALL; - str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), - "%d. %s Time: %s, better than %d%%, requested by %s", - Rank, pData->m_Name, aBuf, BetterThanPercent, pData->m_RequestingPlayer); + + if(str_comp_nocase(pData->m_RequestingPlayer, pData->m_Name) == 0) + { + str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), + "%s - %s - better than %d%%", + pData->m_Name, aBuf, BetterThanPercent); + } + else + { + str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), + "%s - %s - better than %d%% - requested by %s", + pData->m_Name, aBuf, BetterThanPercent, pData->m_RequestingPlayer); + } + + str_format(pResult->m_Data.m_aaMessages[1], sizeof(pResult->m_Data.m_aaMessages[1]), + "Global rank %d - %s %s", + Rank, pData->m_Server, aRegionalRank); } } else @@ -710,7 +864,7 @@ str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), "%s is not ranked", pData->m_Name); } - return true; + return false; } void CScore::ShowTeamRank(int ClientID, const char *pName) @@ -720,7 +874,7 @@ ExecPlayerThread(ShowTeamRankThread, "show team rank", ClientID, pName, 0); } -bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -745,12 +899,20 @@ ") AS l ON TeamRank.ID = l.ID " "INNER JOIN %s_teamrace AS r ON l.ID = r.ID ", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Map); pSqlServer->BindString(3, pData->m_Name); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { float Time = pSqlServer->GetFloat(3); str_time_float(Time, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); @@ -758,7 +920,10 @@ // CEIL and FLOOR are not supported in SQLite int BetterThanPercent = std::floor(100.0 - 100.0 * pSqlServer->GetFloat(5)); CTeamrank Teamrank; - Teamrank.NextSqlResult(pSqlServer); + if(Teamrank.NextSqlResult(pSqlServer, &End, pError, ErrorSize)) + { + return true; + } char aFormattedNames[512] = ""; for(unsigned int Name = 0; Name < Teamrank.m_NumNames; Name++) @@ -789,61 +954,120 @@ str_format(pResult->m_Data.m_aaMessages[0], sizeof(pResult->m_Data.m_aaMessages[0]), "%s has no team ranks", pData->m_Name); } - return true; + return false; } -void CScore::ShowTop5(int ClientID, int Offset) +void CScore::ShowTop(int ClientID, int Offset) { if(RateLimitPlayer(ClientID)) return; - ExecPlayerThread(ShowTop5Thread, "show top5", ClientID, "", Offset); + ExecPlayerThread(ShowTopThread, "show top5", ClientID, "", Offset); } -bool CScore::ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowTopThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); int LimitStart = maximum(abs(pData->m_Offset) - 1, 0); const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; + const char *pAny = "%"; // check sort method char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "SELECT Name, Time, Rank " + "SELECT Name, Time, Rank, Server " "FROM (" - " SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time " + " SELECT RANK() OVER w AS Rank, Name, MIN(Time) AS Time, Server " " FROM %s_race " " WHERE Map = ? " + " AND Server LIKE ? " " GROUP BY Name " " WINDOW w AS (ORDER BY Time)" ") as a " "ORDER BY Rank %s " - "LIMIT %d, 5;", + "LIMIT %d, ?;", pSqlServer->GetPrefix(), pOrder, LimitStart); - pSqlServer->PrepareStatement(aBuf); + + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, pAny); + pSqlServer->BindInt(3, 5); - // show top5 - str_copy(pResult->m_Data.m_aaMessages[0], "----------- Top 5 -----------", sizeof(pResult->m_Data.m_aaMessages[0])); + // show top + int Line = 0; + str_copy(pResult->m_Data.m_aaMessages[Line], "------------ Global Top ------------", sizeof(pResult->m_Data.m_aaMessages[Line])); + Line++; - int Line = 1; - while(pSqlServer->Step()) + char aTime[32]; + bool End = false; + bool HasLocal = false; + + while(!pSqlServer->Step(&End, pError, ErrorSize) && !End) { char aName[MAX_NAME_LENGTH]; pSqlServer->GetString(1, aName, sizeof(aName)); float Time = pSqlServer->GetFloat(2); - str_time_float(Time, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); + str_time_float(Time, TIME_HOURS_CENTISECS, aTime, sizeof(aTime)); int Rank = pSqlServer->GetInt(3); str_format(pResult->m_Data.m_aaMessages[Line], sizeof(pResult->m_Data.m_aaMessages[Line]), - "%d. %s Time: %s", Rank, aName, aBuf); + "%d. %s Time: %s", Rank, aName, aTime); + + char aRecordServer[6]; + pSqlServer->GetString(4, aRecordServer, sizeof(aRecordServer)); + + HasLocal = HasLocal || str_comp(aRecordServer, pData->m_Server) == 0; + Line++; } - str_copy(pResult->m_Data.m_aaMessages[Line], "-------------------------------", sizeof(pResult->m_Data.m_aaMessages[Line])); - return true; + if(!HasLocal) + { + char aServerLike[16]; + str_format(aServerLike, sizeof(aServerLike), "%%%s%%", pData->m_Server); + + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } + pSqlServer->BindString(1, pData->m_Map); + pSqlServer->BindString(2, aServerLike); + pSqlServer->BindInt(3, 3); + + str_format(pResult->m_Data.m_aaMessages[Line], sizeof(pResult->m_Data.m_aaMessages[Line]), + "------------ %s Top ------------", pData->m_Server); + Line++; + + // show top + while(!pSqlServer->Step(&End, pError, ErrorSize) && !End) + { + char aName[MAX_NAME_LENGTH]; + pSqlServer->GetString(1, aName, sizeof(aName)); + float Time = pSqlServer->GetFloat(2); + str_time_float(Time, TIME_HOURS_CENTISECS, aTime, sizeof(aTime)); + int Rank = pSqlServer->GetInt(3); + str_format(pResult->m_Data.m_aaMessages[Line], sizeof(pResult->m_Data.m_aaMessages[Line]), + "%d. %s Time: %s", Rank, aName, aTime); + Line++; + } + } + else + { + str_copy(pResult->m_Data.m_aaMessages[Line], "---------------------------------------", + sizeof(pResult->m_Data.m_aaMessages[Line])); + } + + if(!End) + { + return true; + } + + return false; } void CScore::ShowTeamTop5(int ClientID, int Offset) @@ -853,7 +1077,7 @@ ExecPlayerThread(ShowTeamTop5Thread, "show team top5", ClientID, "", Offset); } -bool CScore::ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -882,14 +1106,22 @@ "INNER JOIN %s_teamrace as r ON l2.ID = r.ID " "ORDER BY Rank %s, r.ID, Name ASC;", pSqlServer->GetPrefix(), pOrder, LimitStart, pSqlServer->GetPrefix(), pOrder); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); // show teamtop5 int Line = 0; str_copy(paMessages[Line++], "------- Team Top 5 -------", sizeof(paMessages[Line])); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { for(Line = 1; Line < 6; Line++) // print { @@ -909,9 +1141,12 @@ str_append(aNames, ", ", sizeof(aNames)); else if(i == TeamSize - 2) str_append(aNames, " & ", sizeof(aNames)); - if(!pSqlServer->Step()) + if(pSqlServer->Step(&Last, pError, ErrorSize)) + { + return true; + } + if(Last) { - Last = true; break; } } @@ -926,7 +1161,7 @@ } str_copy(paMessages[Line], "-------------------------------", sizeof(paMessages[Line])); - return true; + return false; } void CScore::ShowTeamTop5(int ClientID, const char *pName, int Offset) @@ -936,7 +1171,7 @@ ExecPlayerThread(ShowPlayerTeamTop5Thread, "show team top5 player", ClientID, pName, Offset); } -bool CScore::ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -966,12 +1201,20 @@ "INNER JOIN %s_teamrace AS r ON l.ID = r.ID " "ORDER BY Time %s, l.ID ", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pOrder, LimitStart, pSqlServer->GetPrefix(), pOrder); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Map); pSqlServer->BindString(3, pData->m_Name); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { // show teamtop5 int Line = 0; @@ -983,7 +1226,11 @@ str_time_float(Time, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); int Rank = pSqlServer->GetInt(4); CTeamrank Teamrank; - bool Last = !Teamrank.NextSqlResult(pSqlServer); + bool Last; + if(Teamrank.NextSqlResult(pSqlServer, &Last, pError, ErrorSize)) + { + return true; + } char aFormattedNames[512] = ""; for(unsigned int Name = 0; Name < Teamrank.m_NumNames; Name++) @@ -1013,7 +1260,7 @@ else str_format(paMessages[0], sizeof(paMessages[0]), "%s has no team ranks in the specified range", pData->m_Name); } - return true; + return false; } void CScore::ShowTimes(int ClientID, int Offset) @@ -1030,7 +1277,7 @@ ExecPlayerThread(ShowTimesThread, "show times", ClientID, pName, Offset); } -bool CScore::ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1047,14 +1294,17 @@ if(pData->m_Name[0] != '\0') // last 5 times of a player { str_format(aBuf, sizeof(aBuf), - "SELECT Time, (%s-%s) as Ago, %s as Stamp " + "SELECT Time, (%s-%s) as Ago, %s as Stamp, Server " "FROM %s_race " "WHERE Map = ? AND Name = ? " "ORDER BY Timestamp %s " "LIMIT ?, 5;", aCurrentTimestamp, aTimestamp, aTimestamp, pSqlServer->GetPrefix(), pOrder); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, pData->m_Name); pSqlServer->BindInt(3, LimitStart); @@ -1062,24 +1312,32 @@ else // last 5 times of server { str_format(aBuf, sizeof(aBuf), - "SELECT Time, (%s-%s) as Ago, %s as Stamp, Name " + "SELECT Time, (%s-%s) as Ago, %s as Stamp, Server, Name " "FROM %s_race " "WHERE Map = ? " "ORDER BY Timestamp %s " "LIMIT ?, 5;", aCurrentTimestamp, aTimestamp, aTimestamp, pSqlServer->GetPrefix(), pOrder); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindInt(2, LimitStart); } // show top5 - if(!pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) { - str_copy(paMessages[0], "There are no times in the specified range", sizeof(paMessages[0])); return true; } + if(End) + { + str_copy(paMessages[0], "There are no times in the specified range", sizeof(paMessages[0])); + return false; + } str_copy(paMessages[0], "------------- Last Times -------------", sizeof(paMessages[0])); int Line = 1; @@ -1090,6 +1348,11 @@ str_time_float(Time, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); int Ago = pSqlServer->GetInt(2); int Stamp = pSqlServer->GetInt(3); + char aServer[5]; + pSqlServer->GetString(4, aServer, sizeof(aServer)); + char aServerFormatted[8] = "\0"; + if(str_comp(aServer, "UNK") != 0) + str_format(aServerFormatted, sizeof(aServerFormatted), "[%s] ", aServer); char aAgoString[40] = "\0"; sqlstr::AgoTimeToString(Ago, aAgoString, sizeof(aAgoString)); @@ -1098,31 +1361,35 @@ { if(Stamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s, don't know how long ago", aBuf); + "%s%s, don't know how long ago", aServerFormatted, aBuf); else str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s ago, %s", aAgoString, aBuf); + "%s%s ago, %s", aServerFormatted, aAgoString, aBuf); } else // last 5 times of the server { char aName[MAX_NAME_LENGTH]; - pSqlServer->GetString(4, aName, sizeof(aName)); + pSqlServer->GetString(5, aName, sizeof(aName)); if(Stamp == 0) // stamp is 00:00:00 cause it's an old entry from old times where there where no stamps yet { str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s, %s, don't know when", aName, aBuf); + "%s%s, %s, don't know when", aServerFormatted, aName, aBuf); } else { str_format(paMessages[Line], sizeof(paMessages[Line]), - "%s, %s ago, %s", aName, aAgoString, aBuf); + "%s%s, %s ago, %s", aServerFormatted, aName, aAgoString, aBuf); } } Line++; - } while(pSqlServer->Step()); + } while(!pSqlServer->Step(&End, pError, ErrorSize) && !End); + if(!End) + { + return true; + } str_copy(paMessages[Line], "----------------------------------------------------", sizeof(paMessages[Line])); - return true; + return false; } void CScore::ShowPoints(int ClientID, const char *pName) @@ -1132,7 +1399,7 @@ ExecPlayerThread(ShowPointsThread, "show points", ClientID, pName, 0); } -bool CScore::ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1146,11 +1413,19 @@ ")) as Rank, Points, Name " "FROM %s_points WHERE Name = ?;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Name); pSqlServer->BindString(2, pData->m_Name); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { int Rank = pSqlServer->GetInt(1); int Count = pSqlServer->GetInt(2); @@ -1166,7 +1441,7 @@ str_format(paMessages[0], sizeof(paMessages[0]), "%s has not collected any points so far", pData->m_Name); } - return true; + return false; } void CScore::ShowTopPoints(int ClientID, int Offset) @@ -1176,34 +1451,37 @@ ExecPlayerThread(ShowTopPointsThread, "show top points", ClientID, "", Offset); } -bool CScore::ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); auto *paMessages = pResult->m_Data.m_aaMessages; - int LimitStart = maximum(abs(pData->m_Offset) - 1, 0); - const char *pOrder = pData->m_Offset >= 0 ? "ASC" : "DESC"; + int LimitStart = maximum(pData->m_Offset - 1, 0); char aBuf[512]; str_format(aBuf, sizeof(aBuf), - "SELECT Rank, Points, Name " + "SELECT RANK() OVER (ORDER BY a.Points DESC) as Rank, Points, Name " "FROM (" - " SELECT RANK() OVER w AS Rank, Points, Name " + " SELECT Points, Name " " FROM %s_points " - " WINDOW w as (ORDER BY Points DESC)" + " ORDER BY Points DESC LIMIT ?" ") as a " - "ORDER BY Rank %s " "LIMIT ?, 5;", - pSqlServer->GetPrefix(), pOrder); - pSqlServer->PrepareStatement(aBuf); - pSqlServer->BindInt(1, LimitStart); + pSqlServer->GetPrefix()); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } + pSqlServer->BindInt(1, LimitStart + 5); + pSqlServer->BindInt(2, LimitStart); // show top points str_copy(paMessages[0], "-------- Top Points --------", sizeof(paMessages[0])); + bool End = false; int Line = 1; - while(pSqlServer->Step()) + while(!pSqlServer->Step(&End, pError, ErrorSize) && !End) { int Rank = pSqlServer->GetInt(1); int Points = pSqlServer->GetInt(2); @@ -1213,9 +1491,13 @@ "%d. %s Points: %d", Rank, aName, Points); Line++; } + if(!End) + { + return true; + } str_copy(paMessages[Line], "-------------------------------", sizeof(paMessages[Line])); - return true; + return false; } void CScore::RandomMap(int ClientID, int Stars) @@ -1232,7 +1514,7 @@ m_pPool->Execute(RandomMapThread, std::move(Tmp), "random map"); } -bool CScore::RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); CScoreRandomMapResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1245,7 +1527,10 @@ "WHERE Server = ? AND Map != ? AND Stars = ? " "ORDER BY %s LIMIT 1;", pSqlServer->GetPrefix(), pSqlServer->Random()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindInt(3, pData->m_Stars); } else @@ -1255,12 +1540,20 @@ "WHERE Server = ? AND Map != ? " "ORDER BY %s LIMIT 1;", pSqlServer->GetPrefix(), pSqlServer->Random()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } } pSqlServer->BindString(1, pData->m_ServerType); pSqlServer->BindString(2, pData->m_CurrentMap); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { pSqlServer->GetString(1, pResult->m_Map, sizeof(pResult->m_Map)); } @@ -1268,7 +1561,7 @@ { str_copy(pResult->m_aMessage, "No maps found on this server!", sizeof(pResult->m_aMessage)); } - return true; + return false; } void CScore::RandomUnfinishedMap(int ClientID, int Stars) @@ -1285,7 +1578,7 @@ m_pPool->Execute(RandomUnfinishedMapThread, std::move(Tmp), "random unfinished map"); } -bool CScore::RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlRandomMapRequest *pData = dynamic_cast(pGameData); CScoreRandomMapResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1303,7 +1596,10 @@ ") ORDER BY %s " "LIMIT 1;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->Random()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_ServerType); pSqlServer->BindString(2, pData->m_CurrentMap); pSqlServer->BindInt(3, pData->m_Stars); @@ -1321,13 +1617,21 @@ ") ORDER BY %s " "LIMIT 1;", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->Random()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_ServerType); pSqlServer->BindString(2, pData->m_CurrentMap); pSqlServer->BindString(3, pData->m_RequestingPlayer); } - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { pSqlServer->GetString(1, pResult->m_Map, sizeof(pResult->m_Map)); } @@ -1335,7 +1639,7 @@ { str_copy(pResult->m_aMessage, "You have no more unfinished maps on this server!", sizeof(pResult->m_aMessage)); } - return true; + return false; } void CScore::SaveTeam(int ClientID, const char *Code, const char *Server) @@ -1349,7 +1653,7 @@ auto SaveResult = std::make_shared(ClientID, pController); SaveResult->m_SaveID = RandomUuid(); - int Result = SaveResult->m_SavedTeam.save(Team); + int Result = SaveResult->m_SavedTeam.Save(Team); if(CSaveTeam::HandleSaveError(Result, ClientID, GameServer())) return; pController->m_Teams.SetSaving(Team, SaveResult); @@ -1366,7 +1670,7 @@ m_pPool->ExecuteWrite(SaveTeamThread, std::move(Tmp), "save team"); } -bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +bool CScore::SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize) { const CSqlTeamSave *pData = dynamic_cast(pGameData); CScoreSaveResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1377,6 +1681,7 @@ char *pSaveState = pResult->m_SavedTeam.GetString(); char aBuf[65536]; + dbg_msg("score/dbg", "code=%s failure=%d", pData->m_Code, (int)Failure); bool UseGeneratedCode = pData->m_Code[0] == '\0' || Failure; bool Retry = false; @@ -1394,14 +1699,21 @@ "%s INTO %s_saves(Savegame, Map, Code, Timestamp, Server, SaveID, DDNet7) " "VALUES (?, ?, ?, CURRENT_TIMESTAMP, ?, ?, false)", pSqlServer->InsertIgnore(), pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pSaveState); pSqlServer->BindString(2, pData->m_Map); pSqlServer->BindString(3, Code); pSqlServer->BindString(4, pData->m_Server); pSqlServer->BindString(5, aSaveID); pSqlServer->Print(); - int NumInserted = pSqlServer->ExecuteUpdate(); + int NumInserted; + if(pSqlServer->ExecuteUpdate(&NumInserted, pError, ErrorSize)) + { + return true; + } if(NumInserted == 1) { if(!Failure) @@ -1453,7 +1765,7 @@ pResult->m_Status = CScoreSaveResult::SAVE_FAILED; str_copy(pResult->m_aMessage, "This save-code already exists", sizeof(pResult->m_aMessage)); } - return true; + return false; } void CScore::LoadTeam(const char *Code, int ClientID) @@ -1496,19 +1808,12 @@ m_pPool->ExecuteWrite(LoadTeamThread, std::move(Tmp), "load team"); } -bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure) +bool CScore::LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize) { const CSqlTeamLoad *pData = dynamic_cast(pGameData); CScoreSaveResult *pResult = dynamic_cast(pGameData->m_pResult.get()); pResult->m_Status = CScoreSaveResult::LOAD_FAILED; - char aSaveLike[128] = ""; - str_append(aSaveLike, "%\n", sizeof(aSaveLike)); - sqlstr::EscapeLike(aSaveLike + str_length(aSaveLike), - pData->m_RequestingPlayer, - sizeof(aSaveLike) - str_length(aSaveLike)); - str_append(aSaveLike, "\t%", sizeof(aSaveLike)); - char aCurrentTimestamp[512]; pSqlServer->ToUnixTimestamp("CURRENT_TIMESTAMP", aCurrentTimestamp, sizeof(aCurrentTimestamp)); char aTimestamp[512]; @@ -1518,27 +1823,25 @@ str_format(aBuf, sizeof(aBuf), "SELECT Savegame, %s-%s AS Ago, SaveID " "FROM %s_saves " - "where Code = ? AND Map = ? AND DDNet7 = false AND Savegame LIKE ?;", + "where Code = ? AND Map = ? AND DDNet7 = false;", aCurrentTimestamp, aTimestamp, pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Code); pSqlServer->BindString(2, pData->m_Map); - pSqlServer->BindString(3, aSaveLike); - if(!pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) { - str_copy(pResult->m_aMessage, "No such savegame for this map", sizeof(pResult->m_aMessage)); return true; } - - int Since = pSqlServer->GetInt(2); - if(Since < g_Config.m_SvSaveGamesDelay) + if(End) { - str_format(pResult->m_aMessage, sizeof(pResult->m_aMessage), - "You have to wait %d seconds until you can load this savegame", - g_Config.m_SvSaveGamesDelay - Since); - return true; + str_copy(pResult->m_aMessage, "No such savegame for this map", sizeof(pResult->m_aMessage)); + return false; } pResult->m_SaveID = UUID_NO_SAVE_ID; @@ -1549,7 +1852,7 @@ if(ParseUuid(&pResult->m_SaveID, aSaveID) || pResult->m_SaveID == UUID_NO_SAVE_ID) { str_copy(pResult->m_aMessage, "Unable to load savegame: SaveID corrupted", sizeof(pResult->m_aMessage)); - return true; + return false; } } @@ -1560,7 +1863,31 @@ if(Num != 0) { str_copy(pResult->m_aMessage, "Unable to load savegame: data corrupted", sizeof(pResult->m_aMessage)); - return true; + return false; + } + + bool Found = false; + for(int i = 0; i < pResult->m_SavedTeam.GetMembersCount(); i++) + { + if(str_comp(pResult->m_SavedTeam.m_pSavedTees[i].GetName(), pData->m_RequestingPlayer) == 0) + { + Found = true; + break; + } + } + if(!Found) + { + str_copy(pResult->m_aMessage, "You don't belong to this team", sizeof(pResult->m_aMessage)); + return false; + } + + int Since = pSqlServer->GetInt(2); + if(Since < g_Config.m_SvSaveSwapGamesDelay) + { + str_format(pResult->m_aMessage, sizeof(pResult->m_aMessage), + "You have to wait %d seconds until you can load this savegame", + g_Config.m_SvSaveSwapGamesDelay - Since); + return false; } bool CanLoad = pResult->m_SavedTeam.MatchPlayers( @@ -1568,14 +1895,17 @@ pResult->m_aMessage, sizeof(pResult->m_aMessage)); if(!CanLoad) - return true; + return false; str_format(aBuf, sizeof(aBuf), "DELETE FROM %s_saves " "WHERE Code = ? AND Map = ? AND SaveID %s;", pSqlServer->GetPrefix(), pResult->m_SaveID != UUID_NO_SAVE_ID ? "= ?" : "IS NULL"); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Code); pSqlServer->BindString(2, pData->m_Map); char aUuid[UUID_MAXSTRSIZE]; @@ -1585,17 +1915,21 @@ pSqlServer->BindString(3, aUuid); } pSqlServer->Print(); - int NumDeleted = pSqlServer->ExecuteUpdate(); + int NumDeleted; + if(pSqlServer->ExecuteUpdate(&NumDeleted, pError, ErrorSize)) + { + return true; + } if(NumDeleted != 1) { str_copy(pResult->m_aMessage, "Unable to load savegame: loaded on a different server", sizeof(pResult->m_aMessage)); - return true; + return false; } pResult->m_Status = CScoreSaveResult::LOAD_SUCCESS; str_copy(pResult->m_aMessage, "Loading successfully done", sizeof(pResult->m_aMessage)); - return true; + return false; } void CScore::GetSaves(int ClientID) @@ -1605,7 +1939,7 @@ ExecPlayerThread(GetSavesThread, "get saves", ClientID, "", 0); } -bool CScore::GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData) +bool CScore::GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize) { const CSqlPlayerRequest *pData = dynamic_cast(pGameData); CScorePlayerResult *pResult = dynamic_cast(pGameData->m_pResult.get()); @@ -1630,11 +1964,19 @@ "WHERE Map = ? AND Savegame LIKE ?;", aCurrentTimestamp, aMaxTimestamp, pSqlServer->GetPrefix()); - pSqlServer->PrepareStatement(aBuf); + if(pSqlServer->PrepareStatement(aBuf, pError, ErrorSize)) + { + return true; + } pSqlServer->BindString(1, pData->m_Map); pSqlServer->BindString(2, aSaveLike); - if(pSqlServer->Step()) + bool End; + if(pSqlServer->Step(&End, pError, ErrorSize)) + { + return true; + } + if(!End) { int NumSaves = pSqlServer->GetInt(1); int Ago = pSqlServer->GetInt(2); @@ -1652,5 +1994,5 @@ NumSaves, NumSaves == 1 ? "" : "s", pData->m_Map, aLastSavedString); } - return true; + return false; } diff -Nru ddnet-15.3.2/src/game/server/score.h ddnet-15.5.4/src/game/server/score.h --- ddnet-15.3.2/src/game/server/score.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/score.h 2021-06-20 09:38:48.000000000 +0000 @@ -31,7 +31,7 @@ enum { - MAX_MESSAGES = 7, + MAX_MESSAGES = 10, }; enum Variant @@ -167,6 +167,7 @@ char m_RequestingPlayer[MAX_NAME_LENGTH]; // relevant for /top5 kind of requests int m_Offset; + char m_Server[5]; }; struct CSqlRandomMapRequest : ISqlData @@ -267,8 +268,11 @@ // // was executed and that the result line of the first team member is already selected. // Afterwards the team member of the next team is selected. - // Returns if another team can be extracted - bool NextSqlResult(IDbConnection *pSqlServer); + // + // Returns true on SQL failure + // + // if another team can be extracted + bool NextSqlResult(IDbConnection *pSqlServer, bool *pEnd, char *pError, int ErrorSize); bool SamePlayers(const std::vector *aSortedNames); }; @@ -278,29 +282,29 @@ CPlayerData m_aPlayerData[MAX_CLIENTS]; CDbConnectionPool *m_pPool; - static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); - static bool RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - - static bool LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData); - static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData); + static bool RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + + static bool LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowTopThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); + static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize); - static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); - static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); + static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize); + static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize); - static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); - static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure); + static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize); + static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize); CGameContext *GameServer() const { return m_pGameServer; } IServer *Server() const { return m_pServer; } @@ -315,7 +319,7 @@ std::shared_ptr NewSqlPlayerResult(int ClientID); // Creates for player database requests void ExecPlayerThread( - bool (*pFuncPtr)(IDbConnection *, const ISqlData *), + bool (*pFuncPtr)(IDbConnection *, const ISqlData *, char *pError, int ErrorSize), const char *pThreadName, int ClientID, const char *pName, @@ -337,7 +341,7 @@ void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp); - void ShowTop5(int ClientID, int Offset = 1); + void ShowTop(int ClientID, int Offset = 1); void ShowRank(int ClientID, const char *pName); void ShowTeamTop5(int ClientID, int Offset = 1); diff -Nru ddnet-15.3.2/src/game/server/teams.cpp ddnet-15.5.4/src/game/server/teams.cpp --- ddnet-15.3.2/src/game/server/teams.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/teams.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -19,12 +19,31 @@ for(int i = 0; i < MAX_CLIENTS; ++i) { m_TeamState[i] = TEAMSTATE_EMPTY; + m_TeamLocked[i] = false; m_TeeFinished[i] = false; m_LastChat[i] = 0; - m_TeamLocked[i] = false; + m_pSaveTeamResult[i] = nullptr; + m_Invited[i] = 0; m_Practice[i] = false; - m_pSaveTeamResult[i] = nullptr; + m_LastSwap[i] = 0; + } +} + +void CGameTeams::ResetRoundState(int Team) +{ + ResetInvited(Team); + ResetSwitchers(Team); + m_LastSwap[Team] = 0; + + m_Practice[Team] = 0; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->m_VotedForPractice = false; + GameServer()->m_apPlayers[i]->m_SwapTargetsClientID = -1; + } } } @@ -268,13 +287,23 @@ void CGameTeams::SetForceCharacterTeam(int ClientID, int Team) { - if(Team != m_Core.Team(ClientID)) - ForceLeaveTeam(ClientID); - else - m_TeeFinished[ClientID] = false; - + m_TeeFinished[ClientID] = false; int OldTeam = m_Core.Team(ClientID); + if(Team != OldTeam && (OldTeam != TEAM_FLOCK || g_Config.m_SvTeam == 3) && OldTeam != TEAM_SUPER && m_TeamState[OldTeam] != TEAMSTATE_EMPTY) + { + bool NoElseInOldTeam = Count(OldTeam) <= 1; + if(NoElseInOldTeam) + { + m_TeamState[OldTeam] = TEAMSTATE_EMPTY; + + // unlock team when last player leaves + SetTeamLock(OldTeam, false); + ResetRoundState(OldTeam); + // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves + } + } + m_Core.Team(ClientID, Team); if(OldTeam != Team) @@ -284,7 +313,10 @@ SendTeamsState(LoopClientID); if(GetPlayer(ClientID)) + { GetPlayer(ClientID)->m_VotedForPractice = false; + GetPlayer(ClientID)->m_SwapTargetsClientID = -1; + } } if(Team != TEAM_SUPER && (m_TeamState[Team] == TEAMSTATE_EMPTY || m_TeamLocked[Team])) @@ -296,32 +328,6 @@ } } -void CGameTeams::ForceLeaveTeam(int ClientID) -{ - m_TeeFinished[ClientID] = false; - - if((m_Core.Team(ClientID) != TEAM_FLOCK || g_Config.m_SvTeam == 3) && m_Core.Team(ClientID) != TEAM_SUPER && m_TeamState[m_Core.Team(ClientID)] != TEAMSTATE_EMPTY) - { - bool NoOneInOldTeam = true; - for(int i = 0; i < MAX_CLIENTS; ++i) - if(i != ClientID && m_Core.Team(ClientID) == m_Core.Team(i)) - { - NoOneInOldTeam = false; // all good exists someone in old team - break; - } - if(NoOneInOldTeam) - { - m_TeamState[m_Core.Team(ClientID)] = TEAMSTATE_EMPTY; - - // unlock team when last player leaves - SetTeamLock(m_Core.Team(ClientID), false); - ResetInvited(m_Core.Team(ClientID)); - m_Practice[m_Core.Team(ClientID)] = false; - // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves - } - } -} - int CGameTeams::Count(int Team) const { if(Team == TEAM_SUPER) @@ -423,15 +429,24 @@ if(g_Config.m_SvTeam == 3) return; - if(!m_pGameContext->m_apPlayers[ClientID] || m_pGameContext->m_apPlayers[ClientID]->GetClientVersion() <= VERSION_DDRACE) + if(!m_pGameContext->m_apPlayers[ClientID]) return; CMsgPacker Msg(NETMSGTYPE_SV_TEAMSSTATE); + CMsgPacker MsgLegacy(NETMSGTYPE_SV_TEAMSSTATELEGACY); for(unsigned i = 0; i < MAX_CLIENTS; i++) + { Msg.AddInt(m_Core.Team(i)); + MsgLegacy.AddInt(m_Core.Team(i)); + } Server()->SendMsg(&Msg, MSGFLAG_VITAL, ClientID); + int ClientVersion = m_pGameContext->m_apPlayers[ClientID]->GetClientVersion(); + if(!Server()->IsSixup(ClientID) && VERSION_DDRACE < ClientVersion && ClientVersion < VERSION_DDNET_MSG_LEGACY) + { + Server()->SendMsg(&MsgLegacy, MSGFLAG_VITAL, ClientID); + } } int CGameTeams::GetDDRaceState(CPlayer *Player) @@ -628,20 +643,24 @@ } } - if(Player->GetClientVersion() >= VERSION_DDRACE) { CNetMsg_Sv_DDRaceTime Msg; - Msg.m_Time = (int)(Time * 100.0f); - Msg.m_Check = 0; - Msg.m_Finish = 1; + CNetMsg_Sv_DDRaceTimeLegacy MsgLegacy; + MsgLegacy.m_Time = Msg.m_Time = (int)(Time * 100.0f); + MsgLegacy.m_Check = Msg.m_Check = 0; + MsgLegacy.m_Finish = Msg.m_Finish = 1; if(pData->m_BestTime) { float Diff = (Time - pData->m_BestTime) * 100; - Msg.m_Check = (int)Diff; + MsgLegacy.m_Check = Msg.m_Check = (int)Diff; } Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); + if(!Server()->IsSixup(ClientID) && VERSION_DDRACE <= Player->GetClientVersion() && Player->GetClientVersion() < VERSION_DDNET_MSG_LEGACY) + { + Server()->SendPackMsg(&MsgLegacy, MSGFLAG_VITAL, ClientID); + } } { @@ -662,6 +681,96 @@ } } +void CGameTeams::RequestTeamSwap(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team) +{ + if(!pPlayer || !pTargetPlayer) + return; + + char aBuf[512]; + if(pPlayer->m_SwapTargetsClientID == pTargetPlayer->GetCID()) + { + str_format(aBuf, sizeof(aBuf), + "%s has already requested to swap with %s.", + Server()->ClientName(pPlayer->GetCID()), Server()->ClientName(pTargetPlayer->GetCID())); + + GameServer()->SendChatTeam(Team, aBuf); + return; + } + + str_format(aBuf, sizeof(aBuf), + "%s has requested to swap with %s. Please wait %d seconds then type /swap %s.", + Server()->ClientName(pPlayer->GetCID()), Server()->ClientName(pTargetPlayer->GetCID()), g_Config.m_SvSaveSwapGamesDelay, Server()->ClientName(pPlayer->GetCID())); + + GameServer()->SendChatTeam(Team, aBuf); + + pPlayer->m_SwapTargetsClientID = pTargetPlayer->GetCID(); + m_LastSwap[Team] = Server()->Tick(); +} + +void CGameTeams::SwapTeamCharacters(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team) +{ + if(!pPlayer || !pTargetPlayer) + return; + + char aBuf[128]; + + int Since = (Server()->Tick() - m_LastSwap[Team]) / Server()->TickSpeed(); + if(Since < g_Config.m_SvSaveSwapGamesDelay) + { + str_format(aBuf, sizeof(aBuf), + "You have to wait %d seconds until you can swap.", + g_Config.m_SvSaveSwapGamesDelay - Since); + + GameServer()->SendChatTeam(Team, aBuf); + + return; + } + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->m_SwapTargetsClientID = -1; + } + } + + int TimeoutAfterDelay = g_Config.m_SvSaveSwapGamesDelay + g_Config.m_SvSwapTimeout; + if(Since >= TimeoutAfterDelay) + { + str_format(aBuf, sizeof(aBuf), + "Your swap request timed out %d seconds ago. Use /swap again to re-initiate it.", + Since - g_Config.m_SvSwapTimeout); + + GameServer()->SendChatTeam(Team, aBuf); + + return; + } + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) + { + GameServer()->m_apPlayers[i]->GetCharacter()->ResetHook(); + GameServer()->m_World.ReleaseHooked(i); + } + } + + CSaveTee PrimarySavedTee; + PrimarySavedTee.Save(pPlayer->GetCharacter()); + + CSaveTee SecondarySavedTee; + SecondarySavedTee.Save(pTargetPlayer->GetCharacter()); + + PrimarySavedTee.Load(pTargetPlayer->GetCharacter(), Team); + SecondarySavedTee.Load(pPlayer->GetCharacter(), Team); + + str_format(aBuf, sizeof(aBuf), + "%s has swapped with %s.", + Server()->ClientName(pPlayer->GetCID()), Server()->ClientName(pTargetPlayer->GetCID())); + + GameServer()->SendChatTeam(Team, aBuf); +} + void CGameTeams::ProcessSaveTeam() { for(int Team = 0; Team < MAX_CLIENTS; Team++) @@ -704,7 +813,7 @@ if(Count(Team) > 0) { // load weak/strong order to prevent switching weak/strong while saving - m_pSaveTeamResult[Team]->m_SavedTeam.load(Team, false); + m_pSaveTeamResult[Team]->m_SavedTeam.Load(Team, false); } break; case CScoreSaveResult::LOAD_SUCCESS: @@ -719,7 +828,7 @@ if(Count(Team) > 0) { // keep current weak/strong order as on some maps there is no other way of switching - m_pSaveTeamResult[Team]->m_SavedTeam.load(Team, true); + m_pSaveTeamResult[Team]->m_SavedTeam.Load(Team, true); } char aSaveID[UUID_MAXSTRSIZE]; FormatUuid(m_pSaveTeamResult[Team]->m_SaveID, aSaveID, UUID_MAXSTRSIZE); @@ -764,12 +873,10 @@ return; bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME; - if(g_Config.m_SvTeam == 3) + if(g_Config.m_SvTeam == 3 && Team != TEAM_SUPER) { ChangeTeamState(Team, CGameTeams::TEAMSTATE_OPEN); - ResetSwitchers(Team); - m_Practice[Team] = false; - GameServer()->m_apPlayers[ClientID]->m_VotedForPractice = false; + ResetRoundState(Team); } else if(Locked) { @@ -846,8 +953,7 @@ if(g_Config.m_SvTeam == 3) { ChangeTeamState(Team, CGameTeams::TEAMSTATE_OPEN); - ResetSwitchers(Team); - m_Practice[Team] = false; + ResetRoundState(Team); } else { diff -Nru ddnet-15.3.2/src/game/server/teams.h ddnet-15.5.4/src/game/server/teams.h --- ddnet-15.3.2/src/game/server/teams.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/server/teams.h 2021-06-20 09:38:48.000000000 +0000 @@ -17,6 +17,7 @@ uint64 m_Invited[MAX_CLIENTS]; bool m_Practice[MAX_CLIENTS]; std::shared_ptr m_pSaveTeamResult[MAX_CLIENTS]; + uint64 m_LastSwap[MAX_CLIENTS]; class CGameContext *m_pGameContext; @@ -73,10 +74,9 @@ // need to be very careful using this method. SERIOUSLY... void SetForceCharacterTeam(int ClientID, int Team); - void SetForceCharacterNewTeam(int ClientID, int Team); - void ForceLeaveTeam(int ClientID); void Reset(); + void ResetRoundState(int Team); void ResetSwitchers(int Team); void SendTeamsState(int ClientID); @@ -94,6 +94,8 @@ void SetCpActive(CPlayer *Player, int CpActive); void KillSavedTeam(int ClientID, int Team); void ResetSavedTeam(int ClientID, int Team); + void RequestTeamSwap(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team); + void SwapTeamCharacters(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team); void ProcessSaveTeam(); bool TeeFinished(int ClientID) @@ -119,6 +121,11 @@ return m_Invited[Team] & 1LL << ClientID; } + bool IsStarted(int Team) + { + return m_TeamState[Team] == CGameTeams::TEAMSTATE_STARTED; + } + void SetFinished(int ClientID, bool finished) { m_TeeFinished[ClientID] = finished; diff -Nru ddnet-15.3.2/src/game/tuning.h ddnet-15.5.4/src/game/tuning.h --- ddnet-15.3.2/src/game/tuning.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/tuning.h 2021-06-20 09:38:48.000000000 +0000 @@ -52,9 +52,10 @@ MACRO_TUNING_PARAM(HammerStrength, hammer_strength, 1.0f, "Hammer strength") MACRO_TUNING_PARAM(HookDuration, hook_duration, 1.25f, "Hook duration") -MACRO_TUNING_PARAM(HammerFireDelay, hammer_fire_delay, 125, "Delay of hammering") +MACRO_TUNING_PARAM(HammerFireDelay, hammer_fire_delay, 125, "Delay of hammering (when hitting nothing)") MACRO_TUNING_PARAM(GunFireDelay, gun_fire_delay, 125, "Delay of firing gun") MACRO_TUNING_PARAM(ShotgunFireDelay, shotgun_fire_delay, 500, "Delay of firing shotgun") MACRO_TUNING_PARAM(GrenadeFireDelay, grenade_fire_delay, 500, "Delay of firing grenade") MACRO_TUNING_PARAM(LaserFireDelay, laser_fire_delay, 800, "Delay of firing laser laser") MACRO_TUNING_PARAM(NinjaFireDelay, ninja_fire_delay, 800, "Delay of firing ninja") +MACRO_TUNING_PARAM(HammerHitFireDelay, hammer_hit_fire_delay, 320, "Delay of hammering (when hitting another tee)") diff -Nru ddnet-15.3.2/src/game/variables.h ddnet-15.5.4/src/game/variables.h --- ddnet-15.3.2/src/game/variables.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/variables.h 2021-06-20 09:38:48.000000000 +0000 @@ -60,17 +60,17 @@ MACRO_CONFIG_INT(ClWarningTeambalance, cl_warning_teambalance, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Warn about team balance") -MACRO_CONFIG_INT(ClMouseDeadzone, cl_mouse_deadzone, 0, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") -MACRO_CONFIG_INT(ClMouseFollowfactor, cl_mouse_followfactor, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") -MACRO_CONFIG_INT(ClMouseMaxDistance, cl_mouse_max_distance, 400, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") -MACRO_CONFIG_INT(ClMouseMinDistance, cl_mouse_min_distance, 0, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") +MACRO_CONFIG_INT(ClMouseDeadzone, cl_mouse_deadzone, 0, 0, 3000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Deadzone for the camera to follow the cursor") +MACRO_CONFIG_INT(ClMouseFollowfactor, cl_mouse_followfactor, 0, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Factor for the camera to follow the cursor") +MACRO_CONFIG_INT(ClMouseMaxDistance, cl_mouse_max_distance, 400, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum cursor distance") +MACRO_CONFIG_INT(ClMouseMinDistance, cl_mouse_min_distance, 0, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Minimum cursor distance") MACRO_CONFIG_INT(ClDyncam, cl_dyncam, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable dyncam") -MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum dynamic camera distance") -MACRO_CONFIG_INT(ClDyncamMinDistance, cl_dyncam_min_distance, 0, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Minimum dynamic camera distance") +MACRO_CONFIG_INT(ClDyncamMaxDistance, cl_dyncam_max_distance, 1000, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Maximum dynamic camera cursor distance") +MACRO_CONFIG_INT(ClDyncamMinDistance, cl_dyncam_min_distance, 0, 0, 2000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Minimum dynamic camera cursor distance") MACRO_CONFIG_INT(ClDyncamMousesens, cl_dyncam_mousesens, 0, 0, 100000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Mouse sens used when dyncam is toggled on") -MACRO_CONFIG_INT(ClDyncamDeadzone, cl_dyncam_deadzone, 300, 1, 1300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Dynamic camera dead zone") -MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Dynamic camera follow factor") +MACRO_CONFIG_INT(ClDyncamDeadzone, cl_dyncam_deadzone, 300, 1, 1300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Deadzone for the dynamic camera to follow the cursor") +MACRO_CONFIG_INT(ClDyncamFollowFactor, cl_dyncam_follow_factor, 60, 0, 200, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Factor for the dynamic camera to follow the cursor") MACRO_CONFIG_INT(ClDyncamSmoothness, cl_dyncam_smoothness, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Transition amount of the camera movement, 0=instant, 100=slow and smooth") MACRO_CONFIG_INT(ClDyncamStabilizing, cl_dyncam_stabilizing, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Amount of camera slowdown during fast cursor movement. High value can cause delay in camera movement") @@ -138,10 +138,10 @@ MACRO_CONFIG_INT(ClDummyCopyMoves, cl_dummy_copy_moves, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy should copy your moves") // more controlable dummy command -MACRO_CONFIG_INT(ClDummyControl, cl_dummy_control, 0, 0, 1, CFGFLAG_CLIENT, "Whether can you control dummy at the same time") -MACRO_CONFIG_INT(ClDummyJump, cl_dummy_jump, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is jumping") -MACRO_CONFIG_INT(ClDummyFire, cl_dummy_fire, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is firing") -MACRO_CONFIG_INT(ClDummyHook, cl_dummy_hook, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is hooking") +MACRO_CONFIG_INT(ClDummyControl, cl_dummy_control, 0, 0, 1, CFGFLAG_CLIENT, "Whether can you control dummy at the same time (cl_dummy_jump, cl_dummy_fire, cl_dummy_hook)") +MACRO_CONFIG_INT(ClDummyJump, cl_dummy_jump, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is jumping (requires cl_dummy_control 1)") +MACRO_CONFIG_INT(ClDummyFire, cl_dummy_fire, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is firing (requires cl_dummy_control 1)") +MACRO_CONFIG_INT(ClDummyHook, cl_dummy_hook, 0, 0, 1, CFGFLAG_CLIENT, "Whether dummy is hooking (requires cl_dummy_control 1)") // start menu MACRO_CONFIG_INT(ClShowStartMenuImages, cl_show_start_menu_images, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show start menu images") diff -Nru ddnet-15.3.2/src/game/version.h ddnet-15.5.4/src/game/version.h --- ddnet-15.3.2/src/game/version.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/game/version.h 2021-06-20 09:38:48.000000000 +0000 @@ -3,11 +3,11 @@ #ifndef GAME_VERSION_H #define GAME_VERSION_H #ifndef GAME_RELEASE_VERSION -#define GAME_RELEASE_VERSION "15.3.2" +#define GAME_RELEASE_VERSION "15.5.4" #endif #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" #define GAME_NAME "DDNet" -#define CLIENT_VERSIONNR 15032 +#define CLIENT_VERSIONNR 15054 extern const char *GIT_SHORTREV_HASH; #endif diff -Nru ddnet-15.3.2/src/macos/notifications.mm ddnet-15.5.4/src/macos/notifications.mm --- ddnet-15.3.2/src/macos/notifications.mm 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/macos/notifications.mm 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,26 @@ +#import + +#import +#import +#import + +void NotificationsInit() +{ +} +void NotificationsUninit() +{ +} +void NotificationsNotify(const char *pTitle, const char *pMessage) +{ + NSString* pNsTitle = [NSString stringWithCString:pTitle encoding:NSUTF8StringEncoding]; + NSString* pNsMsg = [NSString stringWithCString:pMessage encoding:NSUTF8StringEncoding]; + + NSUserNotification *pNotification = [[NSUserNotification alloc] autorelease]; + pNotification.title = pNsTitle; + pNotification.informativeText = pNsMsg; + pNotification.soundName = NSUserNotificationDefaultSoundName; + + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:pNotification]; + + [NSApp requestUserAttention:NSInformationalRequest]; // use NSCriticalRequest to annoy the user (doesn't stop bouncing) +} diff -Nru ddnet-15.3.2/src/macoslaunch/client.m ddnet-15.5.4/src/macoslaunch/client.m --- ddnet-15.3.2/src/macoslaunch/client.m 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/macoslaunch/client.m 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,23 @@ +#import +#include + +extern int TWMain(int argc, const char **argv); + +int main(int argc, const char **argv) +{ + BOOL FinderLaunch = argc >= 2 && !str_comp_num(argv[1], "-psn", 4); + + NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; + if(!resourcePath) + return -1; + + [[NSFileManager defaultManager] changeCurrentDirectoryPath:resourcePath]; + + if(FinderLaunch) + { + const char *paArgv[2] = { argv[0], NULL }; + return TWMain(1, paArgv); + } + else + return TWMain(argc, argv); +} diff -Nru ddnet-15.3.2/src/macoslaunch/server.mm ddnet-15.5.4/src/macoslaunch/server.mm --- ddnet-15.3.2/src/macoslaunch/server.mm 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/macoslaunch/server.mm 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,108 @@ +#import + +@interface ServerView : NSTextView +{ + NSTask *task; + NSFileHandle *file; +} +- (void)listenTo: (NSTask *)t; +@end + +@implementation ServerView +- (void)listenTo: (NSTask *)t +{ + NSPipe *pipe; + task = t; + pipe = [NSPipe pipe]; + [task setStandardOutput: pipe]; + file = [pipe fileHandleForReading]; + + [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(outputNotification:) name: NSFileHandleReadCompletionNotification object: file]; + + [file readInBackgroundAndNotify]; +} + +- (void) outputNotification: (NSNotification *) notification +{ + NSData *data = [[[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] retain]; + NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString: string]; + + [[self textStorage] appendAttributedString: attrstr]; + int length = [[self textStorage] length]; + NSRange range = NSMakeRange(length, 0); + [self scrollRangeToVisible: range]; + + [attrstr release]; + [string release]; + [file readInBackgroundAndNotify]; +} + +-(void)windowWillClose:(NSNotification *)notification +{ + [task terminate]; + [NSApp terminate:self]; +} +@end + +void runServer() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSApp = [NSApplication sharedApplication]; + NSBundle *mainBundle = [NSBundle mainBundle]; + NSTask *task; + task = [[NSTask alloc] init]; + [task setCurrentDirectoryPath: [mainBundle resourcePath]]; + + // get a server config + NSOpenPanel *openDlg = [NSOpenPanel openPanel]; + [openDlg setCanChooseFiles:YES]; + + if([openDlg runModal] != NSOKButton) + return; + + NSString *filename = [[openDlg URL] path]; + NSArray *arguments = [NSArray arrayWithObjects: @"-f", filename, nil]; + + // run server + NSWindow *window; + ServerView *view; + NSRect graphicsRect; + + graphicsRect = NSMakeRect(100.0, 1000.0, 600.0, 400.0); + + window = [[NSWindow alloc] + initWithContentRect: graphicsRect + styleMask: NSTitledWindowMask + | NSClosableWindowMask + | NSMiniaturizableWindowMask + backing: NSBackingStoreBuffered + defer: NO]; + + [window setTitle: @"DDNet Server"]; + + view = [[[ServerView alloc] initWithFrame: graphicsRect] autorelease]; + [view setEditable: NO]; + [view setRulerVisible: YES]; + + [window setContentView: view]; + [window setDelegate: (id)view]; + [window makeKeyAndOrderFront: nil]; + + [view listenTo: task]; + [task setLaunchPath: [mainBundle pathForAuxiliaryExecutable: @"DDNet-Server"]]; + [task setArguments: arguments]; + [task launch]; + [NSApp run]; + [task terminate]; + + [NSApp release]; + [pool release]; +} + +int main(int argc, char **argv) +{ + runServer(); + + return 0; +} diff -Nru ddnet-15.3.2/src/macoslaunch/server_mysql.m ddnet-15.5.4/src/macoslaunch/server_mysql.m --- ddnet-15.3.2/src/macoslaunch/server_mysql.m 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/macoslaunch/server_mysql.m 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,112 @@ +#import + +@interface ServerView : NSTextView +{ + NSTask *task; + NSFileHandle *file; +} +- (void)listenTo: (NSTask*)t; +@end + +@implementation ServerView +- (void)listenTo: (NSTask*)t; +{ + NSPipe *pipe; + task = t; + pipe = [NSPipe pipe]; + [task setStandardOutput: pipe]; + file = [pipe fileHandleForReading]; + + [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(outputNotification:) name: NSFileHandleReadCompletionNotification object: file]; + + [file readInBackgroundAndNotify]; +} + +- (void) outputNotification: (NSNotification *) notification +{ + NSData *data = [[[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] retain]; + NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; + NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString: string]; + + [[self textStorage] appendAttributedString: attrstr]; + int length = [[self textStorage] length]; + NSRange range = NSMakeRange(length, 0); + [self scrollRangeToVisible: range]; + + [attrstr release]; + [string release]; + [file readInBackgroundAndNotify]; +} + +-(void)windowWillClose:(NSNotification *)notification +{ + [task terminate]; + [NSApp terminate:self]; +} +@end + +void runServer() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSApp = [NSApplication sharedApplication]; + NSBundle* mainBundle = [NSBundle mainBundle]; + NSTask *task; + task = [[NSTask alloc] init]; + [task setCurrentDirectoryPath: [mainBundle resourcePath]]; + + // get a server config + NSOpenPanel* openDlg = [NSOpenPanel openPanel]; + [openDlg setCanChooseFiles:YES]; + + if([openDlg runModalForDirectory:nil file:nil] != NSOKButton) + return; + + NSArray* filenames = [openDlg filenames]; + if([filenames count] != 1) + return; + + NSString* filename = [filenames objectAtIndex: 0]; + NSArray* arguments = [NSArray arrayWithObjects: @"-f", filename, nil]; + + // run server + NSWindow *window; + ServerView *view; + NSRect graphicsRect; + + graphicsRect = NSMakeRect(100.0, 1000.0, 600.0, 400.0); + + window = [[NSWindow alloc] + initWithContentRect: graphicsRect + styleMask: NSTitledWindowMask + | NSClosableWindowMask + | NSMiniaturizableWindowMask + backing: NSBackingStoreBuffered + defer: NO]; + + [window setTitle: @"DDNet Server"]; + + view = [[[ServerView alloc] initWithFrame: graphicsRect] autorelease]; + [view setEditable: NO]; + [view setRulerVisible: YES]; + + [window setContentView: view]; + [window setDelegate: view]; + [window makeKeyAndOrderFront: nil]; + + [view listenTo: task]; + [task setLaunchPath: [mainBundle pathForAuxiliaryExecutable: @"DDNet-Server_sql"]]; + [task setArguments: arguments]; + [task launch]; + [NSApp run]; + [task terminate]; + + [NSApp release]; + [pool release]; +} + +int main (int argc, char **argv) +{ + runServer(); + + return 0; +} diff -Nru ddnet-15.3.2/src/osx/notifications.mm ddnet-15.5.4/src/osx/notifications.mm --- ddnet-15.3.2/src/osx/notifications.mm 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/osx/notifications.mm 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -#import - -#import -#import -#import - -void NotificationsInit() -{ -} -void NotificationsUninit() -{ -} -void NotificationsNotify(const char *pTitle, const char *pMessage) -{ - NSString* pNsTitle = [NSString stringWithCString:pTitle encoding:NSUTF8StringEncoding]; - NSString* pNsMsg = [NSString stringWithCString:pMessage encoding:NSUTF8StringEncoding]; - - NSUserNotification *pNotification = [[NSUserNotification alloc] autorelease]; - pNotification.title = pNsTitle; - pNotification.informativeText = pNsMsg; - pNotification.soundName = NSUserNotificationDefaultSoundName; - - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:pNotification]; - - [NSApp requestUserAttention:NSInformationalRequest]; // use NSCriticalRequest to annoy the user (doesn't stop bouncing) -} diff -Nru ddnet-15.3.2/src/osxlaunch/client.m ddnet-15.5.4/src/osxlaunch/client.m --- ddnet-15.3.2/src/osxlaunch/client.m 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/osxlaunch/client.m 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -#import -#include - -extern int TWMain(int argc, const char **argv); - -int main(int argc, const char **argv) -{ - BOOL FinderLaunch = argc >= 2 && !str_comp_num(argv[1], "-psn", 4); - - NSString *resourcePath = [[NSBundle mainBundle] resourcePath]; - if(!resourcePath) - return -1; - - [[NSFileManager defaultManager] changeCurrentDirectoryPath:resourcePath]; - - if(FinderLaunch) - { - const char *paArgv[2] = { argv[0], NULL }; - return TWMain(1, paArgv); - } - else - return TWMain(argc, argv); -} diff -Nru ddnet-15.3.2/src/osxlaunch/server.mm ddnet-15.5.4/src/osxlaunch/server.mm --- ddnet-15.3.2/src/osxlaunch/server.mm 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/osxlaunch/server.mm 1970-01-01 00:00:00.000000000 +0000 @@ -1,108 +0,0 @@ -#import - -@interface ServerView : NSTextView -{ - NSTask *task; - NSFileHandle *file; -} -- (void)listenTo: (NSTask *)t; -@end - -@implementation ServerView -- (void)listenTo: (NSTask *)t -{ - NSPipe *pipe; - task = t; - pipe = [NSPipe pipe]; - [task setStandardOutput: pipe]; - file = [pipe fileHandleForReading]; - - [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(outputNotification:) name: NSFileHandleReadCompletionNotification object: file]; - - [file readInBackgroundAndNotify]; -} - -- (void) outputNotification: (NSNotification *) notification -{ - NSData *data = [[[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] retain]; - NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; - NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString: string]; - - [[self textStorage] appendAttributedString: attrstr]; - int length = [[self textStorage] length]; - NSRange range = NSMakeRange(length, 0); - [self scrollRangeToVisible: range]; - - [attrstr release]; - [string release]; - [file readInBackgroundAndNotify]; -} - --(void)windowWillClose:(NSNotification *)notification -{ - [task terminate]; - [NSApp terminate:self]; -} -@end - -void runServer() -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSApp = [NSApplication sharedApplication]; - NSBundle *mainBundle = [NSBundle mainBundle]; - NSTask *task; - task = [[NSTask alloc] init]; - [task setCurrentDirectoryPath: [mainBundle resourcePath]]; - - // get a server config - NSOpenPanel *openDlg = [NSOpenPanel openPanel]; - [openDlg setCanChooseFiles:YES]; - - if([openDlg runModal] != NSOKButton) - return; - - NSString *filename = [[openDlg URL] path]; - NSArray *arguments = [NSArray arrayWithObjects: @"-f", filename, nil]; - - // run server - NSWindow *window; - ServerView *view; - NSRect graphicsRect; - - graphicsRect = NSMakeRect(100.0, 1000.0, 600.0, 400.0); - - window = [[NSWindow alloc] - initWithContentRect: graphicsRect - styleMask: NSTitledWindowMask - | NSClosableWindowMask - | NSMiniaturizableWindowMask - backing: NSBackingStoreBuffered - defer: NO]; - - [window setTitle: @"DDNet Server"]; - - view = [[[ServerView alloc] initWithFrame: graphicsRect] autorelease]; - [view setEditable: NO]; - [view setRulerVisible: YES]; - - [window setContentView: view]; - [window setDelegate: (id)view]; - [window makeKeyAndOrderFront: nil]; - - [view listenTo: task]; - [task setLaunchPath: [mainBundle pathForAuxiliaryExecutable: @"DDNet-Server"]]; - [task setArguments: arguments]; - [task launch]; - [NSApp run]; - [task terminate]; - - [NSApp release]; - [pool release]; -} - -int main(int argc, char **argv) -{ - runServer(); - - return 0; -} diff -Nru ddnet-15.3.2/src/osxlaunch/server_mysql.m ddnet-15.5.4/src/osxlaunch/server_mysql.m --- ddnet-15.3.2/src/osxlaunch/server_mysql.m 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/osxlaunch/server_mysql.m 1970-01-01 00:00:00.000000000 +0000 @@ -1,112 +0,0 @@ -#import - -@interface ServerView : NSTextView -{ - NSTask *task; - NSFileHandle *file; -} -- (void)listenTo: (NSTask*)t; -@end - -@implementation ServerView -- (void)listenTo: (NSTask*)t; -{ - NSPipe *pipe; - task = t; - pipe = [NSPipe pipe]; - [task setStandardOutput: pipe]; - file = [pipe fileHandleForReading]; - - [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(outputNotification:) name: NSFileHandleReadCompletionNotification object: file]; - - [file readInBackgroundAndNotify]; -} - -- (void) outputNotification: (NSNotification *) notification -{ - NSData *data = [[[notification userInfo] objectForKey: NSFileHandleNotificationDataItem] retain]; - NSString *string = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding]; - NSAttributedString *attrstr = [[NSAttributedString alloc] initWithString: string]; - - [[self textStorage] appendAttributedString: attrstr]; - int length = [[self textStorage] length]; - NSRange range = NSMakeRange(length, 0); - [self scrollRangeToVisible: range]; - - [attrstr release]; - [string release]; - [file readInBackgroundAndNotify]; -} - --(void)windowWillClose:(NSNotification *)notification -{ - [task terminate]; - [NSApp terminate:self]; -} -@end - -void runServer() -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSApp = [NSApplication sharedApplication]; - NSBundle* mainBundle = [NSBundle mainBundle]; - NSTask *task; - task = [[NSTask alloc] init]; - [task setCurrentDirectoryPath: [mainBundle resourcePath]]; - - // get a server config - NSOpenPanel* openDlg = [NSOpenPanel openPanel]; - [openDlg setCanChooseFiles:YES]; - - if([openDlg runModalForDirectory:nil file:nil] != NSOKButton) - return; - - NSArray* filenames = [openDlg filenames]; - if([filenames count] != 1) - return; - - NSString* filename = [filenames objectAtIndex: 0]; - NSArray* arguments = [NSArray arrayWithObjects: @"-f", filename, nil]; - - // run server - NSWindow *window; - ServerView *view; - NSRect graphicsRect; - - graphicsRect = NSMakeRect(100.0, 1000.0, 600.0, 400.0); - - window = [[NSWindow alloc] - initWithContentRect: graphicsRect - styleMask: NSTitledWindowMask - | NSClosableWindowMask - | NSMiniaturizableWindowMask - backing: NSBackingStoreBuffered - defer: NO]; - - [window setTitle: @"DDNet Server"]; - - view = [[[ServerView alloc] initWithFrame: graphicsRect] autorelease]; - [view setEditable: NO]; - [view setRulerVisible: YES]; - - [window setContentView: view]; - [window setDelegate: view]; - [window makeKeyAndOrderFront: nil]; - - [view listenTo: task]; - [task setLaunchPath: [mainBundle pathForAuxiliaryExecutable: @"DDNet-Server_sql"]]; - [task setArguments: arguments]; - [task launch]; - [NSApp run]; - [task terminate]; - - [NSApp release]; - [pool release]; -} - -int main (int argc, char **argv) -{ - runServer(); - - return 0; -} diff -Nru ddnet-15.3.2/src/test/blocklist_driver.cpp ddnet-15.5.4/src/test/blocklist_driver.cpp --- ddnet-15.3.2/src/test/blocklist_driver.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/test/blocklist_driver.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -1,37 +1,54 @@ #include +#include + #include TEST(BlocklistDriver, Valid1) { +#ifdef CONF_FAMILY_WINDOWS int Major = -1, Minor = -1, Patch = -1; - EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7810", Major, Minor, Patch), "This Intel driver version can cause crashes, please update it to a newer version."); + bool WarningReq = false; + EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7810", Major, Minor, Patch, WarningReq), "This Intel driver version can cause crashes, please update it to a newer version."); EXPECT_EQ(Major, 2); EXPECT_EQ(Minor, 0); EXPECT_EQ(Patch, 0); +#endif } TEST(BlocklistDriver, Valid2) { +#ifdef CONF_FAMILY_WINDOWS int Major = -1, Minor = -1, Patch = -1; - EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7926", Major, Minor, Patch), "This Intel driver version can cause crashes, please update it to a newer version."); + bool WarningReq = false; + EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7926", Major, Minor, Patch, WarningReq), "This Intel driver version can cause crashes, please update it to a newer version."); EXPECT_EQ(Major, 2); EXPECT_EQ(Minor, 0); EXPECT_EQ(Patch, 0); +#endif } TEST(BlocklistDriver, Valid3) { +#ifdef CONF_FAMILY_WINDOWS int Major = -1, Minor = -1, Patch = -1; - EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7985", Major, Minor, Patch), "This Intel driver version can cause crashes, please update it to a newer version."); + bool WarningReq = false; + EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7985", Major, Minor, Patch, WarningReq), "This Intel driver version can cause crashes, please update it to a newer version."); EXPECT_EQ(Major, 2); EXPECT_EQ(Minor, 0); EXPECT_EQ(Patch, 0); +#endif } TEST(BlocklistDriver, Invalid) { int Major, Minor, Patch; - EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 25.20.100.7810", Major, Minor, Patch), NULL); - EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7799", Major, Minor, Patch), NULL); + bool WarningReq = false; +#ifdef CONF_FAMILY_WINDOWS + EXPECT_STREQ(ParseBlocklistDriverVersions("AMD", "Build 25.20.100.7810", Major, Minor, Patch, WarningReq), NULL); + EXPECT_STREQ(ParseBlocklistDriverVersions("NVIDIA", "Build 26.20.100.7799", Major, Minor, Patch, WarningReq), NULL); +#else + EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7985", Major, Minor, Patch, WarningReq), NULL); + EXPECT_STREQ(ParseBlocklistDriverVersions("Intel", "Build 26.20.100.7799", Major, Minor, Patch, WarningReq), NULL); +#endif } diff -Nru ddnet-15.3.2/src/test/fs.cpp ddnet-15.5.4/src/test/fs.cpp --- ddnet-15.3.2/src/test/fs.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/test/fs.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -12,3 +12,39 @@ EXPECT_FALSE(io_close(File)); EXPECT_FALSE(fs_remove(Info.m_aFilename)); } + +TEST(Filesystem, CreateDeleteDirectory) +{ + CTestInfo Info; + char aFilename[128]; + str_format(aFilename, sizeof(aFilename), "%s/test.txt", Info.m_aFilename); + + EXPECT_FALSE(fs_makedir(Info.m_aFilename)); + IOHANDLE File = io_open(aFilename, IOFLAG_WRITE); + ASSERT_TRUE(File); + EXPECT_FALSE(io_close(File)); + + // Directory removal fails if there are any files left in the directory. + EXPECT_TRUE(fs_removedir(Info.m_aFilename)); + + EXPECT_FALSE(fs_remove(aFilename)); + EXPECT_FALSE(fs_removedir(Info.m_aFilename)); +} + +TEST(Filesystem, CantDeleteDirectoryWithRemove) +{ + CTestInfo Info; + EXPECT_FALSE(fs_makedir(Info.m_aFilename)); + EXPECT_TRUE(fs_remove(Info.m_aFilename)); // Cannot remove directory with file removal function. + EXPECT_FALSE(fs_removedir(Info.m_aFilename)); +} + +TEST(Filesystem, CantDeleteFileWithRemoveDirectory) +{ + CTestInfo Info; + IOHANDLE File = io_open(Info.m_aFilename, IOFLAG_WRITE); + ASSERT_TRUE(File); + EXPECT_FALSE(io_close(File)); + EXPECT_TRUE(fs_removedir(Info.m_aFilename)); // Cannot remove file with directory removal function. + EXPECT_FALSE(fs_remove(Info.m_aFilename)); +} diff -Nru ddnet-15.3.2/src/test/jobs.cpp ddnet-15.5.4/src/test/jobs.cpp --- ddnet-15.3.2/src/test/jobs.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/test/jobs.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -23,6 +23,10 @@ { m_Pool.Add(std::move(pJob)); } + void RunBlocking(IJob *pJob) + { + CJobPool::RunBlocking(pJob); + } }; class CJob : public IJob @@ -44,6 +48,15 @@ Add(std::make_shared([] {})); } +TEST_F(Jobs, RunBlocking) +{ + int Result = 0; + CJob Job([&] { Result = 1; }); + EXPECT_EQ(Result, 0); + RunBlocking(&Job); + EXPECT_EQ(Result, 1); +} + TEST_F(Jobs, Wait) { SEMAPHORE sphore; diff -Nru ddnet-15.3.2/src/test/netaddr.cpp ddnet-15.5.4/src/test/netaddr.cpp --- ddnet-15.3.2/src/test/netaddr.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/test/netaddr.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,89 @@ +#include + +#include + +TEST(NetAddr, FromStr) +{ + NETADDR Addr; + char aBuf1[NETADDR_MAXSTRSIZE]; + char aBuf2[NETADDR_MAXSTRSIZE]; + + EXPECT_FALSE(net_addr_from_str(&Addr, "127.0.0.1")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "127.0.0.1:0"); + EXPECT_STREQ(aBuf2, "127.0.0.1"); + + EXPECT_FALSE(net_addr_from_str(&Addr, "1.2.3.4:5678")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "1.2.3.4:5678"); + EXPECT_STREQ(aBuf2, "1.2.3.4"); + + EXPECT_FALSE(net_addr_from_str(&Addr, "[::1]")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "[::1]:0"); + EXPECT_STREQ(aBuf2, "[::1]"); + + EXPECT_FALSE(net_addr_from_str(&Addr, "[0123:4567:89ab:cdef:1:2:3:4]:5678")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "[123:4567:89ab:cdef:1:2:3:4]:5678"); + EXPECT_STREQ(aBuf2, "[123:4567:89ab:cdef:1:2:3:4]"); +} + +TEST(NetAddr, StrV6) +{ + NETADDR Addr; + char aBuf1[NETADDR_MAXSTRSIZE]; + char aBuf2[NETADDR_MAXSTRSIZE]; + + // Test vectors from RFC 5952 section 4: + // https://tools.ietf.org/html/rfc5952#section-4 + // 4.1 Handling Leading Zeros in a 16-Bit Field + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:0db8::0001]:1")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "[2001:db8::1]:1"); + EXPECT_STREQ(aBuf2, "[2001:db8::1]"); + + // 4.2.1 Shorten as Much as Possible + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:db8:0:0:0:0:2:1]")); + net_addr_str(&Addr, aBuf1, sizeof(aBuf1), true); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf1, "[2001:db8::2:1]:0"); + EXPECT_STREQ(aBuf2, "[2001:db8::2:1]"); + + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:db8::0:1]")); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf2, "[2001:db8::1]"); + + // 4.2.2 Handling One 16-Bit 0 Field + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:db8::1:1:1:1:1]")); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf2, "[2001:db8:0:1:1:1:1:1]"); + + // 4.2.3 Choice in Placement of "::" + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:0:0:1:0:0:0:1]")); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf2, "[2001:0:0:1::1]"); + + EXPECT_FALSE(net_addr_from_str(&Addr, "[2001:db8:0:0:1:0:0:1]")); + net_addr_str(&Addr, aBuf2, sizeof(aBuf2), false); + EXPECT_STREQ(aBuf2, "[2001:db8::1:0:0:1]"); +} + +TEST(NetAddr, FromStrInvalid) +{ + NETADDR Addr; + EXPECT_TRUE(net_addr_from_str(&Addr, "127.0.0.")); + //EXPECT_TRUE(net_addr_from_str(&Addr, "127.0.0.1a")); + EXPECT_TRUE(net_addr_from_str(&Addr, "1.1")); + EXPECT_TRUE(net_addr_from_str(&Addr, "[::1")); + EXPECT_TRUE(net_addr_from_str(&Addr, "[::")); + EXPECT_TRUE(net_addr_from_str(&Addr, "127.0.0.1:")); + EXPECT_TRUE(net_addr_from_str(&Addr, "[::]:")); + //EXPECT_TRUE(net_addr_from_str(&Addr, "127.0.0.1:1a")); + EXPECT_TRUE(net_addr_from_str(&Addr, "[::]:c")); +} diff -Nru ddnet-15.3.2/src/test/secure_random.cpp ddnet-15.5.4/src/test/secure_random.cpp --- ddnet-15.3.2/src/test/secure_random.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/test/secure_random.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,35 @@ +#include "test.h" +#include + +#include + +TEST(SecureRandom, Fill) +{ + unsigned int Bits = 0; + while(~Bits) + { + unsigned int Random; + secure_random_fill(&Random, sizeof(Random)); + Bits |= Random; + } +} + +TEST(SecureRandom, Below1) +{ + EXPECT_EQ(secure_rand_below(1), 0); +} + +TEST(SecureRandom, Below) +{ + int BOUNDS[] = {2, 3, 4, 5, 10, 100, 127, 128, 129}; + for(unsigned i = 0; i < sizeof(BOUNDS) / sizeof(BOUNDS[0]); i++) + { + int Below = BOUNDS[i]; + for(int j = 0; j < 10; j++) + { + int Random = secure_rand_below(Below); + EXPECT_GE(Random, 0); + EXPECT_LT(Random, Below); + } + } +} diff -Nru ddnet-15.3.2/src/test/serverbrowser.cpp ddnet-15.5.4/src/test/serverbrowser.cpp --- ddnet-15.3.2/src/test/serverbrowser.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/test/serverbrowser.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,106 @@ +#include + +#include +#include +#include +#include +#include +#include + +TEST(ServerBrowser, PingCache) +{ + CTestInfo Info; + IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); + IStorage *pStorage = Info.CreateTestStorage(); + IServerBrowserPingCache *pPingCache = CreateServerBrowserPingCache(pConsole, pStorage); + + const IServerBrowserPingCache::CEntry *pEntries; + int NumEntries; + + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 0); + + pPingCache->Load(); + + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 0); + + NETADDR Localhost4, Localhost4Tw, Localhost6, Localhost6Tw; + ASSERT_FALSE(net_addr_from_str(&Localhost4, "127.0.0.1")); + ASSERT_FALSE(net_addr_from_str(&Localhost4Tw, "127.0.0.1:8303")); + ASSERT_FALSE(net_addr_from_str(&Localhost6, "[::1]")); + ASSERT_FALSE(net_addr_from_str(&Localhost6Tw, "[::1]:8304")); + EXPECT_LT(net_addr_comp(&Localhost4, &Localhost6), 0); + + // Newer pings overwrite older. + pPingCache->CachePing(Localhost4Tw, 123); + pPingCache->CachePing(Localhost4Tw, 234); + pPingCache->CachePing(Localhost4Tw, 345); + pPingCache->CachePing(Localhost4Tw, 456); + pPingCache->CachePing(Localhost4Tw, 567); + pPingCache->CachePing(Localhost4Tw, 678); + pPingCache->CachePing(Localhost4Tw, 789); + pPingCache->CachePing(Localhost4Tw, 890); + pPingCache->CachePing(Localhost4Tw, 901); + pPingCache->CachePing(Localhost4Tw, 135); + pPingCache->CachePing(Localhost4Tw, 246); + pPingCache->CachePing(Localhost4Tw, 357); + pPingCache->CachePing(Localhost4Tw, 468); + pPingCache->CachePing(Localhost4Tw, 579); + pPingCache->CachePing(Localhost4Tw, 680); + pPingCache->CachePing(Localhost4Tw, 791); + pPingCache->CachePing(Localhost4Tw, 802); + pPingCache->CachePing(Localhost4Tw, 913); + + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 1); + if(NumEntries >= 1) + { + EXPECT_TRUE(net_addr_comp(&pEntries[0].m_Addr, &Localhost4) == 0); + EXPECT_EQ(pEntries[0].m_Ping, 913); + } + + pPingCache->CachePing(Localhost4Tw, 234); + pPingCache->CachePing(Localhost6Tw, 345); + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 2); + if(NumEntries >= 2) + { + EXPECT_TRUE(net_addr_comp(&pEntries[0].m_Addr, &Localhost4) == 0); + EXPECT_TRUE(net_addr_comp(&pEntries[1].m_Addr, &Localhost6) == 0); + EXPECT_EQ(pEntries[0].m_Ping, 234); + EXPECT_EQ(pEntries[1].m_Ping, 345); + } + + // Port doesn't matter for overwriting. + pPingCache->CachePing(Localhost4, 1337); + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 2); + if(NumEntries >= 2) + { + EXPECT_TRUE(net_addr_comp(&pEntries[0].m_Addr, &Localhost4) == 0); + EXPECT_TRUE(net_addr_comp(&pEntries[1].m_Addr, &Localhost6) == 0); + EXPECT_EQ(pEntries[0].m_Ping, 1337); + EXPECT_EQ(pEntries[1].m_Ping, 345); + } + + delete pPingCache; + pPingCache = CreateServerBrowserPingCache(pConsole, pStorage); + + // Persistence. + pPingCache->Load(); + pPingCache->GetPingCache(&pEntries, &NumEntries); + EXPECT_EQ(NumEntries, 2); + if(NumEntries >= 2) + { + EXPECT_TRUE(net_addr_comp(&pEntries[0].m_Addr, &Localhost4) == 0); + EXPECT_TRUE(net_addr_comp(&pEntries[1].m_Addr, &Localhost6) == 0); + EXPECT_EQ(pEntries[0].m_Ping, 1337); + EXPECT_EQ(pEntries[1].m_Ping, 345); + } + + delete pPingCache; + delete pStorage; + + Info.DeleteTestStorageFilesOnSuccess(); +} diff -Nru ddnet-15.3.2/src/test/serverinfo.cpp ddnet-15.5.4/src/test/serverinfo.cpp --- ddnet-15.3.2/src/test/serverinfo.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/test/serverinfo.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,55 @@ +#include + +#include +#include + +#include + +TEST(ServerInfo, ParseLocation) +{ + int Result; + EXPECT_TRUE(CServerInfo::ParseLocation(&Result, "xx")); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "an")); + EXPECT_EQ(Result, CServerInfo::LOC_UNKNOWN); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "af")); + EXPECT_EQ(Result, CServerInfo::LOC_AFRICA); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "eu-n")); + EXPECT_EQ(Result, CServerInfo::LOC_EUROPE); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "na")); + EXPECT_EQ(Result, CServerInfo::LOC_NORTH_AMERICA); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "sa")); + EXPECT_EQ(Result, CServerInfo::LOC_SOUTH_AMERICA); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "as:e")); + EXPECT_EQ(Result, CServerInfo::LOC_ASIA); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "as:cn")); + EXPECT_EQ(Result, CServerInfo::LOC_CHINA); + EXPECT_FALSE(CServerInfo::ParseLocation(&Result, "oc")); + EXPECT_EQ(Result, CServerInfo::LOC_AUSTRALIA); +} + +static unsigned int ParseCrcOrDeadbeef(const char *pString) +{ + unsigned int Result; + if(ParseCrc(&Result, pString)) + { + Result = 0xdeadbeef; + } + return Result; +} + +TEST(ServerInfo, Crc) +{ + EXPECT_EQ(ParseCrcOrDeadbeef("00000000"), 0); + EXPECT_EQ(ParseCrcOrDeadbeef("00000001"), 1); + EXPECT_EQ(ParseCrcOrDeadbeef("12345678"), 0x12345678); + EXPECT_EQ(ParseCrcOrDeadbeef("9abcdef0"), 0x9abcdef0); + + EXPECT_EQ(ParseCrcOrDeadbeef(""), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("a"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("x"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("ç"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("😢"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("0"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("000000000"), 0xdeadbeef); + EXPECT_EQ(ParseCrcOrDeadbeef("00000000x"), 0xdeadbeef); +} diff -Nru ddnet-15.3.2/src/test/sorted_array.cpp ddnet-15.5.4/src/test/sorted_array.cpp --- ddnet-15.3.2/src/test/sorted_array.cpp 1970-01-01 00:00:00.000000000 +0000 +++ ddnet-15.5.4/src/test/sorted_array.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -0,0 +1,9 @@ +#include + +#include + +TEST(SortedArray, SortEmptyRange) +{ + sorted_array x; + x.sort_range(); +} diff -Nru ddnet-15.3.2/src/test/test.cpp ddnet-15.5.4/src/test/test.cpp --- ddnet-15.3.2/src/test/test.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/test/test.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -2,6 +2,9 @@ #include #include +#include + +#include CTestInfo::CTestInfo() { @@ -11,9 +14,106 @@ pTestInfo->test_case_name(), pTestInfo->name(), pid()); } +IStorage *CTestInfo::CreateTestStorage() +{ + bool Error = fs_makedir(m_aFilename); + EXPECT_FALSE(Error); + if(Error) + { + return nullptr; + } + return CreateTempStorage(m_aFilename); +} + +class CTestInfoPath +{ +public: + bool m_IsDirectory; + char m_aData[MAX_PATH_LENGTH]; + + bool operator<(const CTestInfoPath &Other) const + { + if(m_IsDirectory != Other.m_IsDirectory) + { + return m_IsDirectory < Other.m_IsDirectory; + } + return str_comp(m_aData, Other.m_aData) < 0; + } +}; + +class CTestCollectData +{ +public: + char m_aCurrentDir[MAX_PATH_LENGTH]; + std::vector *m_paEntries; +}; + +int TestCollect(const char *pName, int IsDir, int Unused, void *pUser) +{ + CTestCollectData *pData = (CTestCollectData *)pUser; + + if(str_comp(pName, ".") == 0 || str_comp(pName, "..") == 0) + { + return 0; + } + + CTestInfoPath Path; + Path.m_IsDirectory = IsDir; + str_format(Path.m_aData, sizeof(Path.m_aData), "%s/%s", pData->m_aCurrentDir, pName); + pData->m_paEntries->push_back(Path); + if(Path.m_IsDirectory) + { + CTestCollectData DataRecursive; + str_copy(DataRecursive.m_aCurrentDir, Path.m_aData, sizeof(DataRecursive.m_aCurrentDir)); + DataRecursive.m_paEntries = pData->m_paEntries; + fs_listdir(DataRecursive.m_aCurrentDir, TestCollect, 0, &DataRecursive); + } + return 0; +} + +void CTestInfo::DeleteTestStorageFilesOnSuccess() +{ + if(::testing::Test::HasFailure()) + { + return; + } + std::vector aEntries; + CTestCollectData Data; + str_copy(Data.m_aCurrentDir, m_aFilename, sizeof(Data.m_aCurrentDir)); + Data.m_paEntries = &aEntries; + fs_listdir(Data.m_aCurrentDir, TestCollect, 0, &Data); + + CTestInfoPath Path; + Path.m_IsDirectory = true; + str_copy(Path.m_aData, Data.m_aCurrentDir, sizeof(Path.m_aData)); + aEntries.push_back(Path); + + // Sorts directories after files. + std::sort(aEntries.begin(), aEntries.end()); + + // Don't delete too many files. + ASSERT_LE(aEntries.size(), 10); + for(auto &Entry : aEntries) + { + if(Entry.m_IsDirectory) + { + ASSERT_FALSE(fs_removedir(Entry.m_aData)); + } + else + { + ASSERT_FALSE(fs_remove(Entry.m_aData)); + } + } +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); net_init(); + if(secure_random_init()) + { + fprintf(stderr, "random init failed\n"); + return 1; + } return RUN_ALL_TESTS(); } diff -Nru ddnet-15.3.2/src/test/test.h ddnet-15.5.4/src/test/test.h --- ddnet-15.3.2/src/test/test.h 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/test/test.h 2021-06-20 09:38:48.000000000 +0000 @@ -1,9 +1,13 @@ #ifndef TEST_TEST_H #define TEST_TEST_H +class IStorage; + class CTestInfo { public: CTestInfo(); + IStorage *CreateTestStorage(); + void DeleteTestStorageFilesOnSuccess(); char m_aFilename[64]; }; #endif // TEST_TEST_H diff -Nru ddnet-15.3.2/src/tools/map_convert_07.cpp ddnet-15.5.4/src/tools/map_convert_07.cpp --- ddnet-15.3.2/src/tools/map_convert_07.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/tools/map_convert_07.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -218,6 +218,12 @@ pItem = g_DataReader.GetItem(Index, &Type, &ID); Size = g_DataReader.GetItemSize(Index); + // filter ITEMTYPE_EX items, they will be automatically added again + if(Type == ITEMTYPE_EX) + { + continue; + } + Success &= CheckImageDimensions(pItem, Type, pSourceFileName); pItem = ReplaceImageItem(pItem, Type, &NewImageItem); diff -Nru ddnet-15.3.2/src/tools/map_optimize.cpp ddnet-15.5.4/src/tools/map_optimize.cpp --- ddnet-15.3.2/src/tools/map_optimize.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/tools/map_optimize.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -146,6 +146,12 @@ { pPtr = DataFile.GetItem(Index, &Type, &ID); Size = DataFile.GetItemSize(Index); + + // filter ITEMTYPE_EX items, they will be automatically added again + if(Type == ITEMTYPE_EX) + { + continue; + } // for all layers, check if it uses a image and set the corresponding flag if(Type == MAPITEMTYPE_LAYER) { diff -Nru ddnet-15.3.2/src/tools/map_replace_image.cpp ddnet-15.5.4/src/tools/map_replace_image.cpp --- ddnet-15.3.2/src/tools/map_replace_image.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/tools/map_replace_image.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -156,6 +156,13 @@ CMapItemImage NewImageItem; pItem = g_DataReader.GetItem(Index, &Type, &ID); Size = g_DataReader.GetItemSize(Index); + + // filter ITEMTYPE_EX items, they will be automatically added again + if(Type == ITEMTYPE_EX) + { + continue; + } + pItem = ReplaceImageItem(pItem, Type, pImageName, pImageFile, &NewImageItem); if(!pItem) return -1; diff -Nru ddnet-15.3.2/src/tools/map_resave.cpp ddnet-15.5.4/src/tools/map_resave.cpp --- ddnet-15.3.2/src/tools/map_resave.cpp 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/src/tools/map_resave.cpp 2021-06-20 09:38:48.000000000 +0000 @@ -28,6 +28,13 @@ { pPtr = DataFile.GetItem(Index, &Type, &ID); Size = DataFile.GetItemSize(Index); + + // filter ITEMTYPE_EX items, they will be automatically added again + if(Type == ITEMTYPE_EX) + { + continue; + } + df.AddItem(Type, ID, Size, pPtr); } diff -Nru ddnet-15.3.2/storage.cfg ddnet-15.5.4/storage.cfg --- ddnet-15.3.2/storage.cfg 2021-02-20 22:07:40.000000000 +0000 +++ ddnet-15.5.4/storage.cfg 2021-06-20 09:38:48.000000000 +0000 @@ -7,7 +7,7 @@ # There are 3 special paths available: # $USERDIR # - ~/.appname on UNIX based systems -# - ~/Library/Applications Support/appname on Mac OS X +# - ~/Library/Applications Support/appname on macOS # - %APPDATA%/Appname on Windows based systems # $DATADIR # - the 'data' directory which is part of an official