diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c962f8761..00c64b6ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,8 @@ on: jobs: build-cmake: runs-on: ${{ matrix.os }} + env: + CARGO_HTTP_MULTIPLEXING: false strategy: fail-fast: false matrix: diff --git a/.github/workflows/clang-sanitizer.yml b/.github/workflows/clang-sanitizer.yml index 59873154e..2a000d6d7 100644 --- a/.github/workflows/clang-sanitizer.yml +++ b/.github/workflows/clang-sanitizer.yml @@ -11,6 +11,8 @@ on: jobs: check-clang-san: runs-on: ubuntu-latest + env: + CARGO_HTTP_MULTIPLEXING: false steps: - uses: actions/checkout@v3 with: @@ -24,14 +26,17 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build with ASan and UBSan run: | + mkdir clang-sanitizer + cd clang-sanitizer export CC=clang export CXX=clang++ export CXXFLAGS="-fsanitize=address,undefined -fsanitize-recover=address,undefined -fno-omit-frame-pointer" export CFLAGS="-fsanitize=address,undefined -fsanitize-recover=address,undefined -fno-omit-frame-pointer" - cmake -DCMAKE_BUILD_TYPE=Debug -DHEADLESS_CLIENT=ON -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. . + cmake -DCMAKE_BUILD_TYPE=Debug -DHEADLESS_CLIENT=ON -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. .. make -j"$(nproc)" - name: Run server and headless client with ASan and UBSan run: | + cd clang-sanitizer export UBSAN_OPTIONS=suppressions=./ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0 export ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=1:halt_on_errors=0 export LSAN_OPTIONS=suppressions=./lsan.supp @@ -44,6 +49,7 @@ jobs: fi - name: Run unit tests with ASan and UBSan run: | + cd clang-sanitizer cmake --build . --config Debug --target run_cxx_tests # Rust tests work locally, but still not in CI, even with the same directory if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)" @@ -53,6 +59,7 @@ jobs: fi - name: Run integration tests with ASan and UBSan run: | + cd clang-sanitizer make run_integration_tests if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)" then diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index d3af9560d..9c4fe0ce5 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -11,6 +11,8 @@ on: jobs: check-clang-tidy: runs-on: ubuntu-latest + env: + CARGO_HTTP_MULTIPLEXING: false steps: - uses: actions/checkout@v3 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6f2c1276d..439760f8f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,6 +18,8 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + env: + CARGO_HTTP_MULTIPLEXING: false strategy: fail-fast: false diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5ff381de6..77a23ab3c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,6 +10,8 @@ on: jobs: rustdoc: runs-on: ubuntu-latest + env: + CARGO_HTTP_MULTIPLEXING: false steps: - uses: actions/checkout@v2 - name: Cache Rust dependencies @@ -28,6 +30,8 @@ jobs: cargo-deny: runs-on: ubuntu-latest + env: + CARGO_HTTP_MULTIPLEXING: false strategy: matrix: checks: diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e18f2634..b1b9b96c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12...3.19.1) -if(CMAKE_VERSION VERSION_LESS 3.12) - cmake_policy(VERSION ${CMAKE_VERSION}) -endif() +cmake_minimum_required(VERSION 3.12...3.27.4) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE INTERNAL "Minimum macOS deployment version") if(CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.15) @@ -26,16 +23,6 @@ else() endif() # Extra support for CMake pre-3.0 -if(NOT POLICY CMP0048) - set(PROJECT_VERSION_MAJOR ${VERSION_MAJOR}) - set(PROJECT_VERSION_MINOR ${VERSION_MINOR}) - set(PROJECT_VERSION_PATCH ${VERSION_PATCH}) - if(VERSION_PATCH STREQUAL "0") - set(PROJECT_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}) - else() - set(PROJECT_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) - endif() -endif() if(VERSION_PATCH STREQUAL "0") project(DDNet VERSION ${VERSION_MAJOR}.${VERSION_MINOR}) else() @@ -165,26 +152,19 @@ endif() set(DBG $,$>) if(IPO) - if(CMAKE_VERSION VERSION_GREATER 3.9) - include(CheckIPOSupported) - check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output) - if(ipo_supported) - message(STATUS "IPO is enabled") - set(ENABLE_IPO TRUE) - else() - message(WARNING "IPO is not supported: ${ipo_output}") - endif() + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output) + if(ipo_supported) + message(STATUS "IPO is enabled") + set(ENABLE_IPO TRUE) else() - message(WARNING "IPO enablement requires CMake 3.9+") + message(WARNING "IPO is not supported: ${ipo_output}") endif() endif() -if(CMAKE_VERSION VERSION_LESS 3.0) - configure_file(src/game/version.h vd.h) -else() - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS - src/game/version.h - ) +if(NOT "${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + # Remove version.h generated by previous build code + file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/src/game/version.h) endif() set(SERVER_EXECUTABLE DDNet-Server CACHE STRING "Name of the built server executable") @@ -290,7 +270,7 @@ if(NOT MSVC AND NOT HAIKU) endif() endif() - if(CMAKE_VERSION VERSION_LESS 3.1 OR TARGET_OS STREQUAL "mac") + if(TARGET_OS STREQUAL "mac") add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN -std=gnu++17) endif() @@ -330,12 +310,6 @@ if(NOT MSVC AND NOT HAIKU) endif() add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN -Wall) - if(CMAKE_VERSION VERSION_GREATER 3.3 OR CMAKE_VERSION VERSION_EQUAL 3.3) - add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN - $<$:-Wdeclaration-after-statement> - -Wdeclaration-after-statement - ) - endif() add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN -Wextra) add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN -Wno-psabi) # parameter passing for argument of type ‘__gnu_cxx::__normal_iterator*, std::vector, std::allocator > > >’ changed in GCC 7.1 add_cxx_compiler_flag_if_supported(OUR_FLAGS_OWN -Wno-unused-parameter) @@ -387,28 +361,25 @@ function(set_glob VAR GLOBBING EXTS DIRECTORY) # ... if(NOT FILES STREQUAL GLOB_RESULT) message(AUTHOR_WARNING "${VAR} does not contain every file from directory ${DIRECTORY}") set(LIST_BUT_NOT_GLOB) - if(POLICY CMP0057) - cmake_policy(SET CMP0057 NEW) - foreach(file ${FILES}) - if(NOT file IN_LIST GLOB_RESULT) - list(APPEND LIST_BUT_NOT_GLOB ${file}) - endif() - endforeach() - if(LIST_BUT_NOT_GLOB) - message(AUTHOR_WARNING "Entries only present in ${VAR}: ${LIST_BUT_NOT_GLOB}") + foreach(file ${FILES}) + if(NOT file IN_LIST GLOB_RESULT) + list(APPEND LIST_BUT_NOT_GLOB ${file}) endif() - set(GLOB_BUT_NOT_LIST) - foreach(file ${GLOB_RESULT}) - if(NOT file IN_LIST FILES) - list(APPEND GLOB_BUT_NOT_LIST ${file}) - endif() - endforeach() - if(GLOB_BUT_NOT_LIST) - message(AUTHOR_WARNING "Entries only present in ${DIRECTORY}: ${GLOB_BUT_NOT_LIST}") - endif() - if(NOT LIST_BUT_NOT_GLOB AND NOT GLOB_BUT_NOT_LIST) - message(AUTHOR_WARNING "${VAR} is not alphabetically sorted") + endforeach() + if(LIST_BUT_NOT_GLOB) + message(AUTHOR_WARNING "Entries only present in ${VAR}: ${LIST_BUT_NOT_GLOB}") + endif() + set(GLOB_BUT_NOT_LIST) + foreach(file ${GLOB_RESULT}) + if(NOT file IN_LIST FILES) + list(APPEND GLOB_BUT_NOT_LIST ${file}) endif() + endforeach() + if(GLOB_BUT_NOT_LIST) + message(AUTHOR_WARNING "Entries only present in ${DIRECTORY}: ${GLOB_BUT_NOT_LIST}") + endif() + if(NOT LIST_BUT_NOT_GLOB AND NOT GLOB_BUT_NOT_LIST) + message(AUTHOR_WARNING "${VAR} is not alphabetically sorted") endif() endif() @@ -424,19 +395,11 @@ set(CHECKSUM_SRC) function(set_own_rpath TARGET) if(NOT TARGET_OS STREQUAL "windows" AND NOT TARGET_OS STREQUAL "mac") - if(CMAKE_VERSION VERSION_GREATER 3.8 OR CMAKE_VERSION VERSION_EQUAL 3.8) - set_property(TARGET ${TARGET} PROPERTY BUILD_RPATH "$ORIGIN") - endif() + set_property(TARGET ${TARGET} PROPERTY BUILD_RPATH "$ORIGIN") set_property(TARGET ${TARGET} PROPERTY INSTALL_RPATH "$ORIGIN/../lib/ddnet") endif() endfunction() -if(NOT TARGET_OS STREQUAL "windows" AND NOT TARGET_OS STREQUAL "mac" AND CMAKE_VERSION VERSION_LESS 3.8) - if((CLIENT AND (STEAM OR DISCORD_DYNAMIC)) OR ANTIBOT) - message(STATUS "Can't set BUILD_RPATH in CMake before 3.8, pass -Wl,-rpath,'$ORIGIN' manually if you wish to emulate this. Or just install a newer version of CMake...") - endif() -endif() - ######################################################################## # INITIALIZE TARGET LISTS ######################################################################## @@ -834,9 +797,6 @@ if(NOT(GTEST_FOUND) AND DOWNLOAD_GTEST) set(GTEST_LIBRARIES gtest gmock) set(GTEST_INCLUDE_DIRS) - if(CMAKE_VERSION VERSION_LESS 2.8.11) - set(GTEST_INCLUDE_DIRS "${gtest_SOURCE_DIR}/include" "${gmock_SOURCE_DIR}/include") - endif() endif() endif() endif() @@ -1222,8 +1182,11 @@ set(EXPECTED_DATA countryflags/EG.png countryflags/EH.png countryflags/ER.png + countryflags/ES-CT.png + countryflags/ES-GA.png countryflags/ES.png countryflags/ET.png + countryflags/EU.png countryflags/FI.png countryflags/FJ.png countryflags/FK.png @@ -1231,6 +1194,10 @@ set(EXPECTED_DATA countryflags/FO.png countryflags/FR.png countryflags/GA.png + countryflags/GB-ENG.png + countryflags/GB-NIR.png + countryflags/GB-SCT.png + countryflags/GB-WLS.png countryflags/GB.png countryflags/GD.png countryflags/GE.png @@ -1396,13 +1363,6 @@ set(EXPECTED_DATA countryflags/VU.png countryflags/WF.png countryflags/WS.png - countryflags/XCA.png - countryflags/XEN.png - countryflags/XEU.png - countryflags/XGL.png - countryflags/XNI.png - countryflags/XSC.png - countryflags/XWA.png countryflags/YE.png countryflags/ZA.png countryflags/ZM.png @@ -1411,14 +1371,25 @@ set(EXPECTED_DATA countryflags/index.txt debug_font.png editor/audio_source.png + editor/automap/basic_freeze.rules + editor/automap/ddmax_freeze.rules + editor/automap/ddnet_tiles.rules + editor/automap/ddnet_walls.rules + editor/automap/desert_main.rules + editor/automap/fadeout.rules + editor/automap/generic_clear.rules + editor/automap/generic_unhookable.rules + editor/automap/generic_unhookable_0.7.rules + editor/automap/grass_main.rules + editor/automap/grass_main_0.7.rules + editor/automap/jungle_main.rules + editor/automap/jungle_midground.rules + editor/automap/round_tiles.rules + editor/automap/water.rules + editor/automap/winter_main.rules editor/background.png - editor/basic_freeze.rules editor/checker.png editor/cursor.png - editor/ddmax_freeze.rules - editor/ddnet_tiles.rules - editor/ddnet_walls.rules - editor/desert_main.rules editor/entities/DDNet.png editor/entities/FNG.png editor/entities/Race.png @@ -1431,23 +1402,12 @@ set(EXPECTED_DATA editor/entities_clear/fng.png editor/entities_clear/race.png editor/entities_clear/vanilla.png - editor/fadeout.rules editor/front.png - editor/generic_clear.rules - editor/generic_unhookable.rules - editor/generic_unhookable_0.7.rules - editor/grass_main.rules - editor/grass_main_0.7.rules - editor/jungle_main.rules - editor/jungle_midground.rules - editor/round_tiles.rules editor/speed_arrow.png editor/speedup.png editor/switch.png editor/tele.png editor/tune.png - editor/water.rules - editor/winter_main.rules emoticons.png extras.png fonts/DejaVuSans.ttf @@ -2019,6 +1979,7 @@ set_src(ENGINE_GFX GLOB src/engine/gfx image_manipulation.h ) set_src(GAME_SHARED GLOB src/game + alloc.h collision.cpp collision.h ddracechat.h @@ -2303,7 +2264,7 @@ if(CLIENT) ui_scrollregion.cpp ui_scrollregion.h ) - set_src(GAME_EDITOR GLOB src/game/editor + set_src(GAME_EDITOR GLOB_RECURSE src/game/editor auto_map.cpp auto_map.h component.cpp @@ -2311,20 +2272,32 @@ if(CLIENT) editor.cpp editor.h explanations.cpp - io.cpp - layer_game.cpp - layer_quads.cpp - layer_sounds.cpp - layer_tiles.cpp map_grid.cpp map_grid.h map_view.cpp map_view.h + mapitems/image.cpp + mapitems/image.h + mapitems/layer_front.cpp + mapitems/layer_game.cpp + mapitems/layer_group.cpp + mapitems/layer_quads.cpp + mapitems/layer_sounds.cpp + mapitems/layer_speedup.cpp + mapitems/layer_switch.cpp + mapitems/layer_tele.cpp + mapitems/layer_tiles.cpp + mapitems/layer_tune.cpp + mapitems/map.cpp + mapitems/map_io.cpp + mapitems/sound.cpp + mapitems/sound.h popups.cpp proof_mode.cpp proof_mode.h smooth_value.cpp smooth_value.h + tileart.cpp ) set(GAME_GENERATED_CLIENT src/game/generated/checksum.cpp @@ -2508,7 +2481,6 @@ if(SERVER) upnp.h ) set_src(GAME_SERVER GLOB_RECURSE src/game/server - alloc.h ddracechat.cpp ddracecommands.cpp entities/character.cpp @@ -3078,8 +3050,6 @@ endif() if(DEV) # Don't generate CPack targets. -elseif(CMAKE_VERSION VERSION_LESS 3.6 OR CMAKE_VERSION VERSION_EQUAL 3.6) - message(WARNING "Cannot create CPack targets, CMake version too old. Use CMake 3.6 or newer.") else() set(EXTRA_ARGS DESTINATION ${CPACK_PACKAGE_FILE_NAME} COMPONENT portable EXCLUDE_FROM_ALL) install(TARGETS ${CPACK_TARGETS} ${EXTRA_ARGS}) @@ -3309,11 +3279,9 @@ foreach(target ${TARGETS_LINK}) endforeach() foreach(target ${TARGETS_OWN}) - if((CMAKE_VERSION VERSION_GREATER 3.1 OR CMAKE_VERSION VERSION_EQUAL 3.1)) - set_property(TARGET ${target} PROPERTY CXX_STANDARD 17) - set_property(TARGET ${target} PROPERTY CXX_STANDARD_REQUIRED ON) - set_property(TARGET ${target} PROPERTY CXX_EXTENSIONS OFF) - endif() + set_property(TARGET ${target} PROPERTY CXX_STANDARD 17) + set_property(TARGET ${target} PROPERTY CXX_STANDARD_REQUIRED ON) + set_property(TARGET ${target} PROPERTY CXX_EXTENSIONS OFF) if(MSVC) target_compile_options(${target} PRIVATE /wd4244) # Possible loss of data (float -> int, int -> float, etc.). @@ -3377,9 +3345,6 @@ foreach(target ${TARGETS_OWN}) target_compile_definitions(${target} PRIVATE CONF_DISCORD_DYNAMIC) endif() endif() - if(VERSION) - target_compile_definitions(${target} PRIVATE GAME_RELEASE_VERSION="${VERSION}") - endif() if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") target_compile_definitions(${target} PRIVATE CONF_WEBASM) endif() diff --git a/data/countryflags/XCA.png b/data/countryflags/ES-CT.png similarity index 100% rename from data/countryflags/XCA.png rename to data/countryflags/ES-CT.png diff --git a/data/countryflags/XGL.png b/data/countryflags/ES-GA.png similarity index 100% rename from data/countryflags/XGL.png rename to data/countryflags/ES-GA.png diff --git a/data/countryflags/XEU.png b/data/countryflags/EU.png similarity index 100% rename from data/countryflags/XEU.png rename to data/countryflags/EU.png diff --git a/data/countryflags/XEN.png b/data/countryflags/GB-ENG.png similarity index 100% rename from data/countryflags/XEN.png rename to data/countryflags/GB-ENG.png diff --git a/data/countryflags/XNI.png b/data/countryflags/GB-NIR.png similarity index 100% rename from data/countryflags/XNI.png rename to data/countryflags/GB-NIR.png diff --git a/data/countryflags/XSC.png b/data/countryflags/GB-SCT.png similarity index 100% rename from data/countryflags/XSC.png rename to data/countryflags/GB-SCT.png diff --git a/data/countryflags/XWA.png b/data/countryflags/GB-WLS.png similarity index 100% rename from data/countryflags/XWA.png rename to data/countryflags/GB-WLS.png diff --git a/data/countryflags/GP.png b/data/countryflags/GP.png index bcd3c2478..33a02c06c 100644 Binary files a/data/countryflags/GP.png and b/data/countryflags/GP.png differ diff --git a/data/countryflags/MN.png b/data/countryflags/MN.png index 2ef72106d..976c44c4d 100644 Binary files a/data/countryflags/MN.png and b/data/countryflags/MN.png differ diff --git a/data/countryflags/MQ.png b/data/countryflags/MQ.png index fbab048eb..8d7681326 100644 Binary files a/data/countryflags/MQ.png and b/data/countryflags/MQ.png differ diff --git a/data/countryflags/MW.png b/data/countryflags/MW.png index 3694622c7..669f4da1e 100644 Binary files a/data/countryflags/MW.png and b/data/countryflags/MW.png differ diff --git a/data/countryflags/PY.png b/data/countryflags/PY.png index 7e34caaae..f4334643d 100644 Binary files a/data/countryflags/PY.png and b/data/countryflags/PY.png differ diff --git a/data/countryflags/RE.png b/data/countryflags/RE.png index eccf14f8f..d6e57ee55 100644 Binary files a/data/countryflags/RE.png and b/data/countryflags/RE.png differ diff --git a/data/countryflags/index.txt b/data/countryflags/index.txt index 8af04d58e..6ea9d4e12 100644 --- a/data/countryflags/index.txt +++ b/data/countryflags/index.txt @@ -6,30 +6,30 @@ default == -1 -XEN +##### ISO 3166-2 subdivisions ##### + +GB-ENG == 901 -XNI +GB-NIR == 902 -XSC +GB-SCT == 903 -XWA +GB-WLS == 904 -XEU -== 905 - -XCA +ES-CT == 906 -XGL +ES-GA == 907 -#south sudan, non official code# -SS -== 737 +##### ISO 3166/MA exceptional reservations ##### + +EU +== 905 ##### ISO 3166-1 based ##### @@ -648,6 +648,9 @@ SB SO == 706 +SS +== 737 + ZA == 710 diff --git a/data/editor/basic_freeze.rules b/data/editor/automap/basic_freeze.rules similarity index 100% rename from data/editor/basic_freeze.rules rename to data/editor/automap/basic_freeze.rules diff --git a/data/editor/ddmax_freeze.rules b/data/editor/automap/ddmax_freeze.rules similarity index 100% rename from data/editor/ddmax_freeze.rules rename to data/editor/automap/ddmax_freeze.rules diff --git a/data/editor/ddnet_tiles.rules b/data/editor/automap/ddnet_tiles.rules similarity index 100% rename from data/editor/ddnet_tiles.rules rename to data/editor/automap/ddnet_tiles.rules diff --git a/data/editor/ddnet_walls.rules b/data/editor/automap/ddnet_walls.rules similarity index 100% rename from data/editor/ddnet_walls.rules rename to data/editor/automap/ddnet_walls.rules diff --git a/data/editor/desert_main.rules b/data/editor/automap/desert_main.rules similarity index 100% rename from data/editor/desert_main.rules rename to data/editor/automap/desert_main.rules diff --git a/data/editor/fadeout.rules b/data/editor/automap/fadeout.rules similarity index 100% rename from data/editor/fadeout.rules rename to data/editor/automap/fadeout.rules diff --git a/data/editor/generic_clear.rules b/data/editor/automap/generic_clear.rules similarity index 91% rename from data/editor/generic_clear.rules rename to data/editor/automap/generic_clear.rules index 4dd8849b3..033fb5bf4 100644 --- a/data/editor/generic_clear.rules +++ b/data/editor/automap/generic_clear.rules @@ -1,448 +1,448 @@ -[Random Blocks] - -Index 1 - -Index 1 XFLIP -Random 40 - -Index 1 YFLIP -Random 40 - -Index 1 YFLIP XFLIP -Random 40 - -Index 2 -Random 40 - -Index 2 XFLIP -Random 40 - -Index 2 YFLIP -Random 40 - -Index 2 YFLIP XFLIP -Random 40 - -Index 3 XFLIP -Random 40 - -Index 3 YFLIP -Random 40 - -Index 3 YFLIP XFLIP -Random 40 - -Index 4 -Random 40 - -Index 4 XFLIP -Random 40 - -Index 4 YFLIP -Random 40 - -Index 4 YFLIP XFLIP -Random 40 - -Index 5 -Random 40 - -Index 5 XFLIP -Random 40 - -Index 5 YFLIP -Random 40 - -Index 5 YFLIP XFLIP -Random 40 - -Index 6 -Random 40 - -Index 6 XFLIP -Random 40 - -Index 6 YFLIP -Random 40 - -Index 6 YFLIP XFLIP -Random 40 - -Index 64 -Random 40 - -Index 64 XFLIP -Random 40 - -Index 64 YFLIP -Random 40 - -Index 64 YFLIP XFLIP -Random 40 - -Index 65 -Random 40 - -Index 65 XFLIP -Random 40 - -Index 65 YFLIP -Random 40 - -Index 65 YFLIP XFLIP -Random 40 - -Index 66 -Random 40 - -Index 66 XFLIP -Random 40 - -Index 66 YFLIP -Random 40 - -Index 66 YFLIP XFLIP -Random 40 - -Index 67 -Random 40 - -Index 67 XFLIP -Random 40 - -Index 67 YFLIP -Random 40 - -Index 67 YFLIP XFLIP -Random 40 - -#random 2x2 -Index 19 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 100 - - -#random 3x3 -Index 16 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - - -NewRun - -#Remove overlaps -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -2 -2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -1 -2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 0 -2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 1 -2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 2 -2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -2 -1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -1 -1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 0 -1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 1 -1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 2 -1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -2 0 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -1 0 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 1 0 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 2 0 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -2 1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -1 1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 0 1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 1 1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 2 1 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -2 2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos -1 2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 0 2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 1 2 INDEX 16 OR 19 - -Index 1 -Pos 0 0 INDEX 16 OR 19 -Pos 2 2 INDEX 16 OR 19 - -NewRun - -#Fill tiles -Index 20 -Pos -1 0 INDEX 19 -Index 35 -Pos 0 -1 INDEX 19 -Index 36 -Pos -1 -1 INDEX 19 - -Index 17 -Pos -1 0 INDEX 16 -Index 18 -Pos -2 0 INDEX 16 -Index 32 -Pos 0 -1 INDEX 16 -Index 33 -Pos -1 -1 INDEX 16 -Index 34 -Pos -2 -1 INDEX 16 -Index 48 -Pos 0 -2 INDEX 16 -Index 49 -Pos -1 -2 INDEX 16 -Index 50 -Pos -2 -2 INDEX 16 - - - -[Random small Blocks] - -Index 1 - -Index 1 XFLIP -Random 40 - -Index 1 YFLIP -Random 40 - -Index 1 YFLIP XFLIP -Random 40 - -Index 2 -Random 40 - -Index 2 XFLIP -Random 40 - -Index 2 YFLIP -Random 40 - -Index 2 YFLIP XFLIP -Random 40 - -Index 3 XFLIP -Random 40 - -Index 3 YFLIP -Random 40 - -Index 3 YFLIP XFLIP -Random 40 - -Index 4 -Random 40 - -Index 4 XFLIP -Random 40 - -Index 4 YFLIP -Random 40 - -Index 4 YFLIP XFLIP -Random 40 - -Index 5 -Random 40 - -Index 5 XFLIP -Random 40 - -Index 5 YFLIP -Random 40 - -Index 5 YFLIP XFLIP -Random 40 - -Index 6 -Random 40 - -Index 6 XFLIP -Random 40 - -Index 6 YFLIP -Random 40 - -Index 6 YFLIP XFLIP -Random 40 - -Index 64 -Random 40 - -Index 64 XFLIP -Random 40 - -Index 64 YFLIP -Random 40 - -Index 64 YFLIP XFLIP -Random 40 - -Index 65 -Random 40 - -Index 65 XFLIP -Random 40 - -Index 65 YFLIP -Random 40 - -Index 65 YFLIP XFLIP -Random 40 - -Index 66 -Random 40 - -Index 66 XFLIP -Random 40 - -Index 66 YFLIP -Random 40 - -Index 66 YFLIP XFLIP -Random 40 - -Index 67 -Random 40 - -Index 67 XFLIP -Random 40 - -Index 67 YFLIP -Random 40 - -Index 67 YFLIP XFLIP -Random 40 - - - -[Random Decoration] - -Index 7 - -Index 7 XFLIP -Random 18 - -Index 7 YFLIP -Random 18 - -Index 7 YFLIP XFLIP -Random 18 - -Index 21 -Random 18 - -Index 21 XFLIP -Random 18 - -Index 21 YFLIP -Random 18 - -Index 21 YFLIP XFLIP -Random 18 - -Index 23 -Random 18 - -Index 23 XFLIP -Random 18 - -Index 23 YFLIP -Random 18 - -Index 23 YFLIP XFLIP -Random 18 - -Index 51 -Random 18 - -Index 51 XFLIP -Random 18 - -Index 51 YFLIP -Random 18 - -Index 51 YFLIP XFLIP -Random 18 - -Index 68 -Random 18 +[Random Blocks] + +Index 1 + +Index 1 XFLIP +Random 40 + +Index 1 YFLIP +Random 40 + +Index 1 YFLIP XFLIP +Random 40 + +Index 2 +Random 40 + +Index 2 XFLIP +Random 40 + +Index 2 YFLIP +Random 40 + +Index 2 YFLIP XFLIP +Random 40 + +Index 3 XFLIP +Random 40 + +Index 3 YFLIP +Random 40 + +Index 3 YFLIP XFLIP +Random 40 + +Index 4 +Random 40 + +Index 4 XFLIP +Random 40 + +Index 4 YFLIP +Random 40 + +Index 4 YFLIP XFLIP +Random 40 + +Index 5 +Random 40 + +Index 5 XFLIP +Random 40 + +Index 5 YFLIP +Random 40 + +Index 5 YFLIP XFLIP +Random 40 + +Index 6 +Random 40 + +Index 6 XFLIP +Random 40 + +Index 6 YFLIP +Random 40 + +Index 6 YFLIP XFLIP +Random 40 + +Index 64 +Random 40 + +Index 64 XFLIP +Random 40 + +Index 64 YFLIP +Random 40 + +Index 64 YFLIP XFLIP +Random 40 + +Index 65 +Random 40 + +Index 65 XFLIP +Random 40 + +Index 65 YFLIP +Random 40 + +Index 65 YFLIP XFLIP +Random 40 + +Index 66 +Random 40 + +Index 66 XFLIP +Random 40 + +Index 66 YFLIP +Random 40 + +Index 66 YFLIP XFLIP +Random 40 + +Index 67 +Random 40 + +Index 67 XFLIP +Random 40 + +Index 67 YFLIP +Random 40 + +Index 67 YFLIP XFLIP +Random 40 + +#random 2x2 +Index 19 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 100 + + +#random 3x3 +Index 16 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + + +NewRun + +#Remove overlaps +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -2 -2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -1 -2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 0 -2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 1 -2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 2 -2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -2 -1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -1 -1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 0 -1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 1 -1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 2 -1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -2 0 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -1 0 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 1 0 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 2 0 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -2 1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -1 1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 0 1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 1 1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 2 1 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -2 2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos -1 2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 0 2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 1 2 INDEX 16 OR 19 + +Index 1 +Pos 0 0 INDEX 16 OR 19 +Pos 2 2 INDEX 16 OR 19 + +NewRun + +#Fill tiles +Index 20 +Pos -1 0 INDEX 19 +Index 35 +Pos 0 -1 INDEX 19 +Index 36 +Pos -1 -1 INDEX 19 + +Index 17 +Pos -1 0 INDEX 16 +Index 18 +Pos -2 0 INDEX 16 +Index 32 +Pos 0 -1 INDEX 16 +Index 33 +Pos -1 -1 INDEX 16 +Index 34 +Pos -2 -1 INDEX 16 +Index 48 +Pos 0 -2 INDEX 16 +Index 49 +Pos -1 -2 INDEX 16 +Index 50 +Pos -2 -2 INDEX 16 + + + +[Random small Blocks] + +Index 1 + +Index 1 XFLIP +Random 40 + +Index 1 YFLIP +Random 40 + +Index 1 YFLIP XFLIP +Random 40 + +Index 2 +Random 40 + +Index 2 XFLIP +Random 40 + +Index 2 YFLIP +Random 40 + +Index 2 YFLIP XFLIP +Random 40 + +Index 3 XFLIP +Random 40 + +Index 3 YFLIP +Random 40 + +Index 3 YFLIP XFLIP +Random 40 + +Index 4 +Random 40 + +Index 4 XFLIP +Random 40 + +Index 4 YFLIP +Random 40 + +Index 4 YFLIP XFLIP +Random 40 + +Index 5 +Random 40 + +Index 5 XFLIP +Random 40 + +Index 5 YFLIP +Random 40 + +Index 5 YFLIP XFLIP +Random 40 + +Index 6 +Random 40 + +Index 6 XFLIP +Random 40 + +Index 6 YFLIP +Random 40 + +Index 6 YFLIP XFLIP +Random 40 + +Index 64 +Random 40 + +Index 64 XFLIP +Random 40 + +Index 64 YFLIP +Random 40 + +Index 64 YFLIP XFLIP +Random 40 + +Index 65 +Random 40 + +Index 65 XFLIP +Random 40 + +Index 65 YFLIP +Random 40 + +Index 65 YFLIP XFLIP +Random 40 + +Index 66 +Random 40 + +Index 66 XFLIP +Random 40 + +Index 66 YFLIP +Random 40 + +Index 66 YFLIP XFLIP +Random 40 + +Index 67 +Random 40 + +Index 67 XFLIP +Random 40 + +Index 67 YFLIP +Random 40 + +Index 67 YFLIP XFLIP +Random 40 + + + +[Random Decoration] + +Index 7 + +Index 7 XFLIP +Random 18 + +Index 7 YFLIP +Random 18 + +Index 7 YFLIP XFLIP +Random 18 + +Index 21 +Random 18 + +Index 21 XFLIP +Random 18 + +Index 21 YFLIP +Random 18 + +Index 21 YFLIP XFLIP +Random 18 + +Index 23 +Random 18 + +Index 23 XFLIP +Random 18 + +Index 23 YFLIP +Random 18 + +Index 23 YFLIP XFLIP +Random 18 + +Index 51 +Random 18 + +Index 51 XFLIP +Random 18 + +Index 51 YFLIP +Random 18 + +Index 51 YFLIP XFLIP +Random 18 + +Index 68 +Random 18 diff --git a/data/editor/generic_unhookable.rules b/data/editor/automap/generic_unhookable.rules similarity index 91% rename from data/editor/generic_unhookable.rules rename to data/editor/automap/generic_unhookable.rules index e6673d9cd..a32b9677a 100644 --- a/data/editor/generic_unhookable.rules +++ b/data/editor/automap/generic_unhookable.rules @@ -1,1778 +1,1778 @@ -[Random Silver] - -Index 16 - -Index 1 -Random 32 - -Index 1 XFLIP -Random 32 - -Index 1 YFLIP -Random 32 - -Index 1 YFLIP XFLIP -Random 32 - -Index 2 -Random 32 - -Index 2 XFLIP -Random 32 - -Index 2 YFLIP -Random 32 - -Index 2 YFLIP XFLIP -Random 32 - -Index 16 XFLIP -Random 32 - -Index 16 YFLIP -Random 32 - -Index 16 YFLIP XFLIP -Random 32 - -Index 17 -Random 32 - -Index 17 XFLIP -Random 32 - -Index 17 YFLIP -Random 32 - -Index 17 YFLIP XFLIP -Random 32 - -Index 18 -Random 32 - -Index 18 XFLIP -Random 32 - -Index 18 YFLIP -Random 32 - -Index 18 YFLIP XFLIP -Random 32 - -Index 32 -Random 32 - -Index 32 XFLIP -Random 32 - -Index 32 YFLIP -Random 32 - -Index 32 YFLIP XFLIP -Random 32 - -Index 33 -Random 32 - -Index 33 XFLIP -Random 32 - -Index 33 YFLIP -Random 32 - -Index 33 YFLIP XFLIP -Random 32 - -Index 34 -Random 32 - -Index 34 XFLIP -Random 32 - -Index 34 YFLIP -Random 32 - -Index 34 YFLIP XFLIP -Random 32 - -#random 2x2 -Index 3 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 5 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 80 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 67 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -NewRun - -#Remove overlaps -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -2 -2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -1 -2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 0 -2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 1 -2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 2 -2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -2 -1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -1 -1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 0 -1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 1 -1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 2 -1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -2 0 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -1 0 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 1 0 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 2 0 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -2 1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -1 1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 0 1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 1 1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 2 1 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -2 2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos -1 2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 0 2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 1 2 INDEX 3 OR 5 OR 80 OR 67 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 -Pos 2 2 INDEX 3 OR 5 OR 80 OR 67 - -NewRun - -#Fill tiles -Index 4 -Pos -1 0 INDEX 3 -Index 19 -Pos 0 -1 INDEX 3 -Index 20 -Pos -1 -1 INDEX 3 - -Index 6 -Pos -1 0 INDEX 5 -Index 21 -Pos 0 -1 INDEX 5 -Index 22 -Pos -1 -1 INDEX 5 - -Index 81 -Pos -1 0 INDEX 80 -Index 82 -Pos -2 0 INDEX 80 -Index 96 -Pos 0 -1 INDEX 80 -Index 97 -Pos -1 -1 INDEX 80 -Index 98 -Pos -2 -1 INDEX 80 -Index 112 -Pos 0 -2 INDEX 80 -Index 113 -Pos -1 -2 INDEX 80 -Index 114 -Pos -2 -2 INDEX 80 - -Index 68 -Pos -1 0 INDEX 67 -Index 69 -Pos -2 0 INDEX 67 -Index 83 -Pos 0 -1 INDEX 67 -Index 84 -Pos -1 -1 INDEX 67 -Index 85 -Pos -2 -1 INDEX 67 - - - -[Random Gold] - -Index 23 - -Index 8 -Random 32 - -Index 8 XFLIP -Random 32 - -Index 8 YFLIP -Random 32 - -Index 8 YFLIP XFLIP -Random 32 - -Index 9 -Random 32 - -Index 9 XFLIP -Random 32 - -Index 9 YFLIP -Random 32 - -Index 9 YFLIP XFLIP -Random 32 - -Index 23 XFLIP -Random 32 - -Index 23 YFLIP -Random 32 - -Index 23 YFLIP XFLIP -Random 32 - -Index 24 -Random 32 - -Index 24 XFLIP -Random 32 - -Index 24 YFLIP -Random 32 - -Index 24 YFLIP XFLIP -Random 32 - -Index 25 -Random 32 - -Index 25 XFLIP -Random 32 - -Index 25 YFLIP -Random 32 - -Index 25 YFLIP XFLIP -Random 32 - -Index 39 -Random 32 - -Index 39 XFLIP -Random 32 - -Index 39 YFLIP -Random 32 - -Index 39 YFLIP XFLIP -Random 32 - -Index 40 -Random 32 - -Index 40 XFLIP -Random 32 - -Index 40 YFLIP -Random 32 - -Index 40 YFLIP XFLIP -Random 32 - -Index 41 -Random 32 - -Index 41 XFLIP -Random 32 - -Index 41 YFLIP -Random 32 - -Index 41 YFLIP XFLIP -Random 32 - -#random 2x2 -Index 10 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 12 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 87 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 74 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -NewRun - -#Remove overlaps -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -2 -2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -1 -2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 0 -2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 1 -2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 2 -2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -2 -1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -1 -1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 0 -1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 1 -1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 2 -1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -2 0 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -1 0 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 1 0 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 2 0 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -2 1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -1 1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 0 1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 1 1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 2 1 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -2 2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos -1 2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 0 2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 1 2 INDEX 10 OR 12 OR 87 OR 74 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 -Pos 2 2 INDEX 10 OR 12 OR 87 OR 74 - -NewRun - -#Fill tiles -Index 11 -Pos -1 0 INDEX 10 -Index 26 -Pos 0 -1 INDEX 10 -Index 27 -Pos -1 -1 INDEX 10 - -Index 13 -Pos -1 0 INDEX 12 -Index 28 -Pos 0 -1 INDEX 12 -Index 29 -Pos -1 -1 INDEX 12 - -Index 88 -Pos -1 0 INDEX 87 -Index 89 -Pos -2 0 INDEX 87 -Index 103 -Pos 0 -1 INDEX 87 -Index 104 -Pos -1 -1 INDEX 87 -Index 105 -Pos -2 -1 INDEX 87 -Index 119 -Pos 0 -2 INDEX 87 -Index 120 -Pos -1 -2 INDEX 87 -Index 121 -Pos -2 -2 INDEX 87 - -Index 75 -Pos -1 0 INDEX 74 -Index 76 -Pos -2 0 INDEX 74 -Index 90 -Pos 0 -1 INDEX 74 -Index 91 -Pos -1 -1 INDEX 74 -Index 92 -Pos -2 -1 INDEX 74 - - - -[Random Bronze] - -Index 144 - -Index 129 -Random 32 - -Index 129 XFLIP -Random 32 - -Index 129 YFLIP -Random 32 - -Index 129 YFLIP XFLIP -Random 32 - -Index 130 -Random 32 - -Index 130 XFLIP -Random 32 - -Index 130 YFLIP -Random 32 - -Index 130 YFLIP XFLIP -Random 32 - -Index 144 XFLIP -Random 32 - -Index 144 YFLIP -Random 32 - -Index 144 YFLIP XFLIP -Random 32 - -Index 145 -Random 32 - -Index 145 XFLIP -Random 32 - -Index 145 YFLIP -Random 32 - -Index 145 YFLIP XFLIP -Random 32 - -Index 146 -Random 32 - -Index 146 XFLIP -Random 32 - -Index 146 YFLIP -Random 32 - -Index 146 YFLIP XFLIP -Random 32 - -Index 160 -Random 32 - -Index 160 XFLIP -Random 32 - -Index 160 YFLIP -Random 32 - -Index 160 YFLIP XFLIP -Random 32 - -Index 161 -Random 32 - -Index 161 XFLIP -Random 32 - -Index 161 YFLIP -Random 32 - -Index 161 YFLIP XFLIP -Random 32 - -Index 162 -Random 32 - -Index 162 XFLIP -Random 32 - -Index 162 YFLIP -Random 32 - -Index 162 YFLIP XFLIP -Random 32 - -#random 2x2 -Index 131 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 133 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 208 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 195 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -NewRun - -#Remove overlaps -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -2 -2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -1 -2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 0 -2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 1 -2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 2 -2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -2 -1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -1 -1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 0 -1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 1 -1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 2 -1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -2 0 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -1 0 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 1 0 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 2 0 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -2 1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -1 1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 0 1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 1 1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 2 1 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -2 2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos -1 2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 0 2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 1 2 INDEX 131 OR 133 OR 208 OR 195 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 -Pos 2 2 INDEX 131 OR 133 OR 208 OR 195 - -NewRun - -#Fill tiles -Index 132 -Pos -1 0 INDEX 131 -Index 147 -Pos 0 -1 INDEX 131 -Index 148 -Pos -1 -1 INDEX 131 - -Index 134 -Pos -1 0 INDEX 133 -Index 149 -Pos 0 -1 INDEX 133 -Index 150 -Pos -1 -1 INDEX 133 - -Index 209 -Pos -1 0 INDEX 208 -Index 210 -Pos -2 0 INDEX 208 -Index 224 -Pos 0 -1 INDEX 208 -Index 225 -Pos -1 -1 INDEX 208 -Index 226 -Pos -2 -1 INDEX 208 -Index 240 -Pos 0 -2 INDEX 208 -Index 241 -Pos -1 -2 INDEX 208 -Index 242 -Pos -2 -2 INDEX 208 - -Index 196 -Pos -1 0 INDEX 195 -Index 197 -Pos -2 0 INDEX 195 -Index 211 -Pos 0 -1 INDEX 195 -Index 212 -Pos -1 -1 INDEX 195 -Index 213 -Pos -2 -1 INDEX 195 - - - -[Silver/Gold-Mix] - -#Silver -Index 16 - -Index 1 -Random 64 - -Index 1 XFLIP -Random 64 - -Index 1 YFLIP -Random 64 - -Index 1 YFLIP XFLIP -Random 64 - -Index 2 -Random 64 - -Index 2 XFLIP -Random 64 - -Index 2 YFLIP -Random 64 - -Index 2 YFLIP XFLIP -Random 64 - -Index 16 XFLIP -Random 64 - -Index 16 YFLIP -Random 64 - -Index 16 YFLIP XFLIP -Random 64 - -Index 17 -Random 64 - -Index 17 XFLIP -Random 64 - -Index 17 YFLIP -Random 64 - -Index 17 YFLIP XFLIP -Random 64 - -Index 18 -Random 64 - -Index 18 XFLIP -Random 64 - -Index 18 YFLIP -Random 64 - -Index 18 YFLIP XFLIP -Random 64 - -Index 32 -Random 64 - -Index 32 XFLIP -Random 64 - -Index 32 YFLIP -Random 64 - -Index 32 YFLIP XFLIP -Random 64 - -Index 33 -Random 64 - -Index 33 XFLIP -Random 64 - -Index 33 YFLIP -Random 64 - -Index 33 YFLIP XFLIP -Random 64 - -Index 34 -Random 64 - -Index 34 XFLIP -Random 64 - -Index 34 YFLIP -Random 64 - -Index 34 YFLIP XFLIP -Random 64 - -#Gold - -Index 8 -Random 64 - -Index 8 XFLIP -Random 64 - -Index 8 YFLIP -Random 64 - -Index 8 YFLIP XFLIP -Random 64 - -Index 9 -Random 64 - -Index 9 XFLIP -Random 64 - -Index 9 YFLIP -Random 64 - -Index 9 YFLIP XFLIP -Random 64 - -Index 23 -Random 64 - -Index 23 XFLIP -Random 64 - -Index 23 YFLIP -Random 64 - -Index 23 YFLIP XFLIP -Random 64 - -Index 24 -Random 64 - -Index 24 XFLIP -Random 64 - -Index 24 YFLIP -Random 64 - -Index 24 YFLIP XFLIP -Random 64 - -Index 25 -Random 64 - -Index 25 XFLIP -Random 64 - -Index 25 YFLIP -Random 64 - -Index 25 YFLIP XFLIP -Random 64 - -Index 39 -Random 64 - -Index 39 XFLIP -Random 64 - -Index 39 YFLIP -Random 64 - -Index 39 YFLIP XFLIP -Random 64 - -Index 40 -Random 64 - -Index 40 XFLIP -Random 64 - -Index 40 YFLIP -Random 64 - -Index 40 YFLIP XFLIP -Random 64 - -Index 41 -Random 64 - -Index 41 XFLIP -Random 64 - -Index 41 YFLIP -Random 64 - -Index 41 YFLIP XFLIP -Random 64 - - - -[Copper/Silver-Mix] - -#Copper -Index 144 - -Index 129 -Random 64 - -Index 129 XFLIP -Random 64 - -Index 129 YFLIP -Random 64 - -Index 129 YFLIP XFLIP -Random 64 - -Index 130 -Random 64 - -Index 130 XFLIP -Random 64 - -Index 130 YFLIP -Random 64 - -Index 130 YFLIP XFLIP -Random 64 - -Index 144 XFLIP -Random 64 - -Index 144 YFLIP -Random 64 - -Index 144 YFLIP XFLIP -Random 64 - -Index 145 -Random 64 - -Index 145 XFLIP -Random 64 - -Index 145 YFLIP -Random 64 - -Index 145 YFLIP XFLIP -Random 64 - -Index 146 -Random 64 - -Index 146 XFLIP -Random 64 - -Index 146 YFLIP -Random 64 - -Index 146 YFLIP XFLIP -Random 64 - -Index 160 -Random 64 - -Index 160 XFLIP -Random 64 - -Index 160 YFLIP -Random 64 - -Index 160 YFLIP XFLIP -Random 64 - -Index 161 -Random 64 - -Index 161 XFLIP -Random 64 - -Index 161 YFLIP -Random 64 - -Index 161 YFLIP XFLIP -Random 64 - -Index 162 -Random 64 - -Index 162 XFLIP -Random 64 - -Index 162 YFLIP -Random 64 - -Index 162 YFLIP XFLIP -Random 64 - -#Silver - -Index 1 -Random 64 - -Index 1 XFLIP -Random 64 - -Index 1 YFLIP -Random 64 - -Index 1 YFLIP XFLIP -Random 64 - -Index 2 -Random 64 - -Index 2 XFLIP -Random 64 - -Index 2 YFLIP -Random 64 - -Index 2 YFLIP XFLIP -Random 64 - -Index 16 -Random 64 - -Index 16 XFLIP -Random 64 - -Index 16 YFLIP -Random 64 - -Index 16 YFLIP XFLIP -Random 64 - -Index 17 -Random 64 - -Index 17 XFLIP -Random 64 - -Index 17 YFLIP -Random 64 - -Index 17 YFLIP XFLIP -Random 64 - -Index 18 -Random 64 - -Index 18 XFLIP -Random 64 - -Index 18 YFLIP -Random 64 - -Index 18 YFLIP XFLIP -Random 64 - -Index 32 -Random 64 - -Index 32 XFLIP -Random 64 - -Index 32 YFLIP -Random 64 - -Index 32 YFLIP XFLIP -Random 64 - -Index 33 -Random 64 - -Index 33 XFLIP -Random 64 - -Index 33 YFLIP -Random 64 - -Index 33 YFLIP XFLIP -Random 64 - -Index 34 -Random 64 - -Index 34 XFLIP -Random 64 - -Index 34 YFLIP -Random 64 - -Index 34 YFLIP XFLIP -Random 64 - - - -[Gold/Copper-Mix] - -#Gold -Index 23 - -Index 8 -Random 64 - -Index 8 XFLIP -Random 64 - -Index 8 YFLIP -Random 64 - -Index 8 YFLIP XFLIP -Random 64 - -Index 9 -Random 64 - -Index 9 XFLIP -Random 64 - -Index 9 YFLIP -Random 64 - -Index 9 YFLIP XFLIP -Random 64 - -Index 23 XFLIP -Random 64 - -Index 23 YFLIP -Random 64 - -Index 23 YFLIP XFLIP -Random 64 - -Index 24 -Random 64 - -Index 24 XFLIP -Random 64 - -Index 24 YFLIP -Random 64 - -Index 24 YFLIP XFLIP -Random 64 - -Index 25 -Random 64 - -Index 25 XFLIP -Random 64 - -Index 25 YFLIP -Random 64 - -Index 25 YFLIP XFLIP -Random 64 - -Index 39 -Random 64 - -Index 39 XFLIP -Random 64 - -Index 39 YFLIP -Random 64 - -Index 39 YFLIP XFLIP -Random 64 - -Index 40 -Random 64 - -Index 40 XFLIP -Random 64 - -Index 40 YFLIP -Random 64 - -Index 40 YFLIP XFLIP -Random 64 - -Index 41 -Random 64 - -Index 41 XFLIP -Random 64 - -Index 41 YFLIP -Random 64 - -Index 41 YFLIP XFLIP -Random 64 - -#Copper - -Index 129 -Random 64 - -Index 129 XFLIP -Random 64 - -Index 129 YFLIP -Random 64 - -Index 129 YFLIP XFLIP -Random 64 - -Index 130 -Random 64 - -Index 130 XFLIP -Random 64 - -Index 130 YFLIP -Random 64 - -Index 130 YFLIP XFLIP -Random 64 - -Index 144 -Random 64 - -Index 144 XFLIP -Random 64 - -Index 144 YFLIP -Random 64 - -Index 144 YFLIP XFLIP -Random 64 - -Index 145 -Random 64 - -Index 145 XFLIP -Random 64 - -Index 145 YFLIP -Random 64 - -Index 145 YFLIP XFLIP -Random 64 - -Index 146 -Random 64 - -Index 146 XFLIP -Random 64 - -Index 146 YFLIP -Random 64 - -Index 146 YFLIP XFLIP -Random 64 - -Index 160 -Random 64 - -Index 160 XFLIP -Random 64 - -Index 160 YFLIP -Random 64 - -Index 160 YFLIP XFLIP -Random 64 - -Index 161 -Random 64 - -Index 161 XFLIP -Random 64 - -Index 161 YFLIP -Random 64 - -Index 161 YFLIP XFLIP -Random 64 - -Index 162 -Random 64 - -Index 162 XFLIP -Random 64 - -Index 162 YFLIP -Random 64 - -Index 162 YFLIP XFLIP -Random 64 - - - -[Mix All] - -#Silver -Index 16 - -Index 1 -Random 96 - -Index 1 XFLIP -Random 96 - -Index 1 YFLIP -Random 96 - -Index 1 YFLIP XFLIP -Random 96 - -Index 2 -Random 96 - -Index 2 XFLIP -Random 96 - -Index 2 YFLIP -Random 96 - -Index 2 YFLIP XFLIP -Random 96 - -Index 16 XFLIP -Random 96 - -Index 16 YFLIP -Random 96 - -Index 16 YFLIP XFLIP -Random 96 - -Index 17 -Random 96 - -Index 17 XFLIP -Random 96 - -Index 17 YFLIP -Random 96 - -Index 17 YFLIP XFLIP -Random 96 - -Index 18 -Random 96 - -Index 18 XFLIP -Random 96 - -Index 18 YFLIP -Random 96 - -Index 18 YFLIP XFLIP -Random 96 - -Index 32 -Random 96 - -Index 32 XFLIP -Random 96 - -Index 32 YFLIP -Random 96 - -Index 32 YFLIP XFLIP -Random 96 - -Index 33 -Random 96 - -Index 33 XFLIP -Random 96 - -Index 33 YFLIP -Random 96 - -Index 33 YFLIP XFLIP -Random 96 - -Index 34 -Random 96 - -Index 34 XFLIP -Random 96 - -Index 34 YFLIP -Random 96 - -Index 34 YFLIP XFLIP -Random 96 - -#Gold - -Index 8 -Random 96 - -Index 8 XFLIP -Random 96 - -Index 8 YFLIP -Random 96 - -Index 8 YFLIP XFLIP -Random 96 - -Index 9 -Random 96 - -Index 9 XFLIP -Random 96 - -Index 9 YFLIP -Random 96 - -Index 9 YFLIP XFLIP -Random 96 - -Index 23 -Random 96 - -Index 23 XFLIP -Random 96 - -Index 23 YFLIP -Random 96 - -Index 23 YFLIP XFLIP -Random 96 - -Index 24 -Random 96 - -Index 24 XFLIP -Random 96 - -Index 24 YFLIP -Random 96 - -Index 24 YFLIP XFLIP -Random 96 - -Index 25 -Random 96 - -Index 25 XFLIP -Random 96 - -Index 25 YFLIP -Random 96 - -Index 25 YFLIP XFLIP -Random 96 - -Index 39 -Random 96 - -Index 39 XFLIP -Random 96 - -Index 39 YFLIP -Random 96 - -Index 39 YFLIP XFLIP -Random 96 - -Index 40 -Random 96 - -Index 40 XFLIP -Random 96 - -Index 40 YFLIP -Random 96 - -Index 40 YFLIP XFLIP -Random 96 - -Index 41 -Random 96 - -Index 41 XFLIP -Random 96 - -Index 41 YFLIP -Random 96 - -Index 41 YFLIP XFLIP -Random 96 - -#Copper - -Index 129 -Random 96 - -Index 129 XFLIP -Random 96 - -Index 129 YFLIP -Random 96 - -Index 129 YFLIP XFLIP -Random 96 - -Index 130 -Random 96 - -Index 130 XFLIP -Random 96 - -Index 130 YFLIP -Random 96 - -Index 130 YFLIP XFLIP -Random 96 - -Index 144 -Random 96 - -Index 144 XFLIP -Random 96 - -Index 144 YFLIP -Random 96 - -Index 144 YFLIP XFLIP -Random 96 - -Index 145 -Random 96 - -Index 145 XFLIP -Random 96 - -Index 145 YFLIP -Random 96 - -Index 145 YFLIP XFLIP -Random 96 - -Index 146 -Random 96 - -Index 146 XFLIP -Random 96 - -Index 146 YFLIP -Random 96 - -Index 146 YFLIP XFLIP -Random 96 - -Index 160 -Random 96 - -Index 160 XFLIP -Random 96 - -Index 160 YFLIP -Random 96 - -Index 160 YFLIP XFLIP -Random 96 - -Index 161 -Random 96 - -Index 161 XFLIP -Random 96 - -Index 161 YFLIP -Random 96 - -Index 161 YFLIP XFLIP -Random 96 - -Index 162 -Random 96 - -Index 162 XFLIP -Random 96 - -Index 162 YFLIP -Random 96 - -Index 162 YFLIP XFLIP -Random 96 +[Random Silver] + +Index 16 + +Index 1 +Random 32 + +Index 1 XFLIP +Random 32 + +Index 1 YFLIP +Random 32 + +Index 1 YFLIP XFLIP +Random 32 + +Index 2 +Random 32 + +Index 2 XFLIP +Random 32 + +Index 2 YFLIP +Random 32 + +Index 2 YFLIP XFLIP +Random 32 + +Index 16 XFLIP +Random 32 + +Index 16 YFLIP +Random 32 + +Index 16 YFLIP XFLIP +Random 32 + +Index 17 +Random 32 + +Index 17 XFLIP +Random 32 + +Index 17 YFLIP +Random 32 + +Index 17 YFLIP XFLIP +Random 32 + +Index 18 +Random 32 + +Index 18 XFLIP +Random 32 + +Index 18 YFLIP +Random 32 + +Index 18 YFLIP XFLIP +Random 32 + +Index 32 +Random 32 + +Index 32 XFLIP +Random 32 + +Index 32 YFLIP +Random 32 + +Index 32 YFLIP XFLIP +Random 32 + +Index 33 +Random 32 + +Index 33 XFLIP +Random 32 + +Index 33 YFLIP +Random 32 + +Index 33 YFLIP XFLIP +Random 32 + +Index 34 +Random 32 + +Index 34 XFLIP +Random 32 + +Index 34 YFLIP +Random 32 + +Index 34 YFLIP XFLIP +Random 32 + +#random 2x2 +Index 3 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 5 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 80 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 67 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +NewRun + +#Remove overlaps +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -2 -2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -1 -2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 0 -2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 1 -2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 2 -2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -2 -1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -1 -1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 0 -1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 1 -1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 2 -1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -2 0 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -1 0 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 1 0 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 2 0 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -2 1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -1 1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 0 1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 1 1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 2 1 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -2 2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos -1 2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 0 2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 1 2 INDEX 3 OR 5 OR 80 OR 67 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 +Pos 2 2 INDEX 3 OR 5 OR 80 OR 67 + +NewRun + +#Fill tiles +Index 4 +Pos -1 0 INDEX 3 +Index 19 +Pos 0 -1 INDEX 3 +Index 20 +Pos -1 -1 INDEX 3 + +Index 6 +Pos -1 0 INDEX 5 +Index 21 +Pos 0 -1 INDEX 5 +Index 22 +Pos -1 -1 INDEX 5 + +Index 81 +Pos -1 0 INDEX 80 +Index 82 +Pos -2 0 INDEX 80 +Index 96 +Pos 0 -1 INDEX 80 +Index 97 +Pos -1 -1 INDEX 80 +Index 98 +Pos -2 -1 INDEX 80 +Index 112 +Pos 0 -2 INDEX 80 +Index 113 +Pos -1 -2 INDEX 80 +Index 114 +Pos -2 -2 INDEX 80 + +Index 68 +Pos -1 0 INDEX 67 +Index 69 +Pos -2 0 INDEX 67 +Index 83 +Pos 0 -1 INDEX 67 +Index 84 +Pos -1 -1 INDEX 67 +Index 85 +Pos -2 -1 INDEX 67 + + + +[Random Gold] + +Index 23 + +Index 8 +Random 32 + +Index 8 XFLIP +Random 32 + +Index 8 YFLIP +Random 32 + +Index 8 YFLIP XFLIP +Random 32 + +Index 9 +Random 32 + +Index 9 XFLIP +Random 32 + +Index 9 YFLIP +Random 32 + +Index 9 YFLIP XFLIP +Random 32 + +Index 23 XFLIP +Random 32 + +Index 23 YFLIP +Random 32 + +Index 23 YFLIP XFLIP +Random 32 + +Index 24 +Random 32 + +Index 24 XFLIP +Random 32 + +Index 24 YFLIP +Random 32 + +Index 24 YFLIP XFLIP +Random 32 + +Index 25 +Random 32 + +Index 25 XFLIP +Random 32 + +Index 25 YFLIP +Random 32 + +Index 25 YFLIP XFLIP +Random 32 + +Index 39 +Random 32 + +Index 39 XFLIP +Random 32 + +Index 39 YFLIP +Random 32 + +Index 39 YFLIP XFLIP +Random 32 + +Index 40 +Random 32 + +Index 40 XFLIP +Random 32 + +Index 40 YFLIP +Random 32 + +Index 40 YFLIP XFLIP +Random 32 + +Index 41 +Random 32 + +Index 41 XFLIP +Random 32 + +Index 41 YFLIP +Random 32 + +Index 41 YFLIP XFLIP +Random 32 + +#random 2x2 +Index 10 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 12 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 87 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 74 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +NewRun + +#Remove overlaps +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -2 -2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -1 -2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 0 -2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 1 -2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 2 -2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -2 -1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -1 -1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 0 -1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 1 -1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 2 -1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -2 0 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -1 0 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 1 0 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 2 0 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -2 1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -1 1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 0 1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 1 1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 2 1 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -2 2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos -1 2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 0 2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 1 2 INDEX 10 OR 12 OR 87 OR 74 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 +Pos 2 2 INDEX 10 OR 12 OR 87 OR 74 + +NewRun + +#Fill tiles +Index 11 +Pos -1 0 INDEX 10 +Index 26 +Pos 0 -1 INDEX 10 +Index 27 +Pos -1 -1 INDEX 10 + +Index 13 +Pos -1 0 INDEX 12 +Index 28 +Pos 0 -1 INDEX 12 +Index 29 +Pos -1 -1 INDEX 12 + +Index 88 +Pos -1 0 INDEX 87 +Index 89 +Pos -2 0 INDEX 87 +Index 103 +Pos 0 -1 INDEX 87 +Index 104 +Pos -1 -1 INDEX 87 +Index 105 +Pos -2 -1 INDEX 87 +Index 119 +Pos 0 -2 INDEX 87 +Index 120 +Pos -1 -2 INDEX 87 +Index 121 +Pos -2 -2 INDEX 87 + +Index 75 +Pos -1 0 INDEX 74 +Index 76 +Pos -2 0 INDEX 74 +Index 90 +Pos 0 -1 INDEX 74 +Index 91 +Pos -1 -1 INDEX 74 +Index 92 +Pos -2 -1 INDEX 74 + + + +[Random Bronze] + +Index 144 + +Index 129 +Random 32 + +Index 129 XFLIP +Random 32 + +Index 129 YFLIP +Random 32 + +Index 129 YFLIP XFLIP +Random 32 + +Index 130 +Random 32 + +Index 130 XFLIP +Random 32 + +Index 130 YFLIP +Random 32 + +Index 130 YFLIP XFLIP +Random 32 + +Index 144 XFLIP +Random 32 + +Index 144 YFLIP +Random 32 + +Index 144 YFLIP XFLIP +Random 32 + +Index 145 +Random 32 + +Index 145 XFLIP +Random 32 + +Index 145 YFLIP +Random 32 + +Index 145 YFLIP XFLIP +Random 32 + +Index 146 +Random 32 + +Index 146 XFLIP +Random 32 + +Index 146 YFLIP +Random 32 + +Index 146 YFLIP XFLIP +Random 32 + +Index 160 +Random 32 + +Index 160 XFLIP +Random 32 + +Index 160 YFLIP +Random 32 + +Index 160 YFLIP XFLIP +Random 32 + +Index 161 +Random 32 + +Index 161 XFLIP +Random 32 + +Index 161 YFLIP +Random 32 + +Index 161 YFLIP XFLIP +Random 32 + +Index 162 +Random 32 + +Index 162 XFLIP +Random 32 + +Index 162 YFLIP +Random 32 + +Index 162 YFLIP XFLIP +Random 32 + +#random 2x2 +Index 131 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 133 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 208 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 195 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +NewRun + +#Remove overlaps +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -2 -2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -1 -2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 0 -2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 1 -2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 2 -2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -2 -1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -1 -1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 0 -1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 1 -1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 2 -1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -2 0 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -1 0 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 1 0 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 2 0 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -2 1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -1 1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 0 1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 1 1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 2 1 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -2 2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos -1 2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 0 2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 1 2 INDEX 131 OR 133 OR 208 OR 195 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 +Pos 2 2 INDEX 131 OR 133 OR 208 OR 195 + +NewRun + +#Fill tiles +Index 132 +Pos -1 0 INDEX 131 +Index 147 +Pos 0 -1 INDEX 131 +Index 148 +Pos -1 -1 INDEX 131 + +Index 134 +Pos -1 0 INDEX 133 +Index 149 +Pos 0 -1 INDEX 133 +Index 150 +Pos -1 -1 INDEX 133 + +Index 209 +Pos -1 0 INDEX 208 +Index 210 +Pos -2 0 INDEX 208 +Index 224 +Pos 0 -1 INDEX 208 +Index 225 +Pos -1 -1 INDEX 208 +Index 226 +Pos -2 -1 INDEX 208 +Index 240 +Pos 0 -2 INDEX 208 +Index 241 +Pos -1 -2 INDEX 208 +Index 242 +Pos -2 -2 INDEX 208 + +Index 196 +Pos -1 0 INDEX 195 +Index 197 +Pos -2 0 INDEX 195 +Index 211 +Pos 0 -1 INDEX 195 +Index 212 +Pos -1 -1 INDEX 195 +Index 213 +Pos -2 -1 INDEX 195 + + + +[Silver/Gold-Mix] + +#Silver +Index 16 + +Index 1 +Random 64 + +Index 1 XFLIP +Random 64 + +Index 1 YFLIP +Random 64 + +Index 1 YFLIP XFLIP +Random 64 + +Index 2 +Random 64 + +Index 2 XFLIP +Random 64 + +Index 2 YFLIP +Random 64 + +Index 2 YFLIP XFLIP +Random 64 + +Index 16 XFLIP +Random 64 + +Index 16 YFLIP +Random 64 + +Index 16 YFLIP XFLIP +Random 64 + +Index 17 +Random 64 + +Index 17 XFLIP +Random 64 + +Index 17 YFLIP +Random 64 + +Index 17 YFLIP XFLIP +Random 64 + +Index 18 +Random 64 + +Index 18 XFLIP +Random 64 + +Index 18 YFLIP +Random 64 + +Index 18 YFLIP XFLIP +Random 64 + +Index 32 +Random 64 + +Index 32 XFLIP +Random 64 + +Index 32 YFLIP +Random 64 + +Index 32 YFLIP XFLIP +Random 64 + +Index 33 +Random 64 + +Index 33 XFLIP +Random 64 + +Index 33 YFLIP +Random 64 + +Index 33 YFLIP XFLIP +Random 64 + +Index 34 +Random 64 + +Index 34 XFLIP +Random 64 + +Index 34 YFLIP +Random 64 + +Index 34 YFLIP XFLIP +Random 64 + +#Gold + +Index 8 +Random 64 + +Index 8 XFLIP +Random 64 + +Index 8 YFLIP +Random 64 + +Index 8 YFLIP XFLIP +Random 64 + +Index 9 +Random 64 + +Index 9 XFLIP +Random 64 + +Index 9 YFLIP +Random 64 + +Index 9 YFLIP XFLIP +Random 64 + +Index 23 +Random 64 + +Index 23 XFLIP +Random 64 + +Index 23 YFLIP +Random 64 + +Index 23 YFLIP XFLIP +Random 64 + +Index 24 +Random 64 + +Index 24 XFLIP +Random 64 + +Index 24 YFLIP +Random 64 + +Index 24 YFLIP XFLIP +Random 64 + +Index 25 +Random 64 + +Index 25 XFLIP +Random 64 + +Index 25 YFLIP +Random 64 + +Index 25 YFLIP XFLIP +Random 64 + +Index 39 +Random 64 + +Index 39 XFLIP +Random 64 + +Index 39 YFLIP +Random 64 + +Index 39 YFLIP XFLIP +Random 64 + +Index 40 +Random 64 + +Index 40 XFLIP +Random 64 + +Index 40 YFLIP +Random 64 + +Index 40 YFLIP XFLIP +Random 64 + +Index 41 +Random 64 + +Index 41 XFLIP +Random 64 + +Index 41 YFLIP +Random 64 + +Index 41 YFLIP XFLIP +Random 64 + + + +[Copper/Silver-Mix] + +#Copper +Index 144 + +Index 129 +Random 64 + +Index 129 XFLIP +Random 64 + +Index 129 YFLIP +Random 64 + +Index 129 YFLIP XFLIP +Random 64 + +Index 130 +Random 64 + +Index 130 XFLIP +Random 64 + +Index 130 YFLIP +Random 64 + +Index 130 YFLIP XFLIP +Random 64 + +Index 144 XFLIP +Random 64 + +Index 144 YFLIP +Random 64 + +Index 144 YFLIP XFLIP +Random 64 + +Index 145 +Random 64 + +Index 145 XFLIP +Random 64 + +Index 145 YFLIP +Random 64 + +Index 145 YFLIP XFLIP +Random 64 + +Index 146 +Random 64 + +Index 146 XFLIP +Random 64 + +Index 146 YFLIP +Random 64 + +Index 146 YFLIP XFLIP +Random 64 + +Index 160 +Random 64 + +Index 160 XFLIP +Random 64 + +Index 160 YFLIP +Random 64 + +Index 160 YFLIP XFLIP +Random 64 + +Index 161 +Random 64 + +Index 161 XFLIP +Random 64 + +Index 161 YFLIP +Random 64 + +Index 161 YFLIP XFLIP +Random 64 + +Index 162 +Random 64 + +Index 162 XFLIP +Random 64 + +Index 162 YFLIP +Random 64 + +Index 162 YFLIP XFLIP +Random 64 + +#Silver + +Index 1 +Random 64 + +Index 1 XFLIP +Random 64 + +Index 1 YFLIP +Random 64 + +Index 1 YFLIP XFLIP +Random 64 + +Index 2 +Random 64 + +Index 2 XFLIP +Random 64 + +Index 2 YFLIP +Random 64 + +Index 2 YFLIP XFLIP +Random 64 + +Index 16 +Random 64 + +Index 16 XFLIP +Random 64 + +Index 16 YFLIP +Random 64 + +Index 16 YFLIP XFLIP +Random 64 + +Index 17 +Random 64 + +Index 17 XFLIP +Random 64 + +Index 17 YFLIP +Random 64 + +Index 17 YFLIP XFLIP +Random 64 + +Index 18 +Random 64 + +Index 18 XFLIP +Random 64 + +Index 18 YFLIP +Random 64 + +Index 18 YFLIP XFLIP +Random 64 + +Index 32 +Random 64 + +Index 32 XFLIP +Random 64 + +Index 32 YFLIP +Random 64 + +Index 32 YFLIP XFLIP +Random 64 + +Index 33 +Random 64 + +Index 33 XFLIP +Random 64 + +Index 33 YFLIP +Random 64 + +Index 33 YFLIP XFLIP +Random 64 + +Index 34 +Random 64 + +Index 34 XFLIP +Random 64 + +Index 34 YFLIP +Random 64 + +Index 34 YFLIP XFLIP +Random 64 + + + +[Gold/Copper-Mix] + +#Gold +Index 23 + +Index 8 +Random 64 + +Index 8 XFLIP +Random 64 + +Index 8 YFLIP +Random 64 + +Index 8 YFLIP XFLIP +Random 64 + +Index 9 +Random 64 + +Index 9 XFLIP +Random 64 + +Index 9 YFLIP +Random 64 + +Index 9 YFLIP XFLIP +Random 64 + +Index 23 XFLIP +Random 64 + +Index 23 YFLIP +Random 64 + +Index 23 YFLIP XFLIP +Random 64 + +Index 24 +Random 64 + +Index 24 XFLIP +Random 64 + +Index 24 YFLIP +Random 64 + +Index 24 YFLIP XFLIP +Random 64 + +Index 25 +Random 64 + +Index 25 XFLIP +Random 64 + +Index 25 YFLIP +Random 64 + +Index 25 YFLIP XFLIP +Random 64 + +Index 39 +Random 64 + +Index 39 XFLIP +Random 64 + +Index 39 YFLIP +Random 64 + +Index 39 YFLIP XFLIP +Random 64 + +Index 40 +Random 64 + +Index 40 XFLIP +Random 64 + +Index 40 YFLIP +Random 64 + +Index 40 YFLIP XFLIP +Random 64 + +Index 41 +Random 64 + +Index 41 XFLIP +Random 64 + +Index 41 YFLIP +Random 64 + +Index 41 YFLIP XFLIP +Random 64 + +#Copper + +Index 129 +Random 64 + +Index 129 XFLIP +Random 64 + +Index 129 YFLIP +Random 64 + +Index 129 YFLIP XFLIP +Random 64 + +Index 130 +Random 64 + +Index 130 XFLIP +Random 64 + +Index 130 YFLIP +Random 64 + +Index 130 YFLIP XFLIP +Random 64 + +Index 144 +Random 64 + +Index 144 XFLIP +Random 64 + +Index 144 YFLIP +Random 64 + +Index 144 YFLIP XFLIP +Random 64 + +Index 145 +Random 64 + +Index 145 XFLIP +Random 64 + +Index 145 YFLIP +Random 64 + +Index 145 YFLIP XFLIP +Random 64 + +Index 146 +Random 64 + +Index 146 XFLIP +Random 64 + +Index 146 YFLIP +Random 64 + +Index 146 YFLIP XFLIP +Random 64 + +Index 160 +Random 64 + +Index 160 XFLIP +Random 64 + +Index 160 YFLIP +Random 64 + +Index 160 YFLIP XFLIP +Random 64 + +Index 161 +Random 64 + +Index 161 XFLIP +Random 64 + +Index 161 YFLIP +Random 64 + +Index 161 YFLIP XFLIP +Random 64 + +Index 162 +Random 64 + +Index 162 XFLIP +Random 64 + +Index 162 YFLIP +Random 64 + +Index 162 YFLIP XFLIP +Random 64 + + + +[Mix All] + +#Silver +Index 16 + +Index 1 +Random 96 + +Index 1 XFLIP +Random 96 + +Index 1 YFLIP +Random 96 + +Index 1 YFLIP XFLIP +Random 96 + +Index 2 +Random 96 + +Index 2 XFLIP +Random 96 + +Index 2 YFLIP +Random 96 + +Index 2 YFLIP XFLIP +Random 96 + +Index 16 XFLIP +Random 96 + +Index 16 YFLIP +Random 96 + +Index 16 YFLIP XFLIP +Random 96 + +Index 17 +Random 96 + +Index 17 XFLIP +Random 96 + +Index 17 YFLIP +Random 96 + +Index 17 YFLIP XFLIP +Random 96 + +Index 18 +Random 96 + +Index 18 XFLIP +Random 96 + +Index 18 YFLIP +Random 96 + +Index 18 YFLIP XFLIP +Random 96 + +Index 32 +Random 96 + +Index 32 XFLIP +Random 96 + +Index 32 YFLIP +Random 96 + +Index 32 YFLIP XFLIP +Random 96 + +Index 33 +Random 96 + +Index 33 XFLIP +Random 96 + +Index 33 YFLIP +Random 96 + +Index 33 YFLIP XFLIP +Random 96 + +Index 34 +Random 96 + +Index 34 XFLIP +Random 96 + +Index 34 YFLIP +Random 96 + +Index 34 YFLIP XFLIP +Random 96 + +#Gold + +Index 8 +Random 96 + +Index 8 XFLIP +Random 96 + +Index 8 YFLIP +Random 96 + +Index 8 YFLIP XFLIP +Random 96 + +Index 9 +Random 96 + +Index 9 XFLIP +Random 96 + +Index 9 YFLIP +Random 96 + +Index 9 YFLIP XFLIP +Random 96 + +Index 23 +Random 96 + +Index 23 XFLIP +Random 96 + +Index 23 YFLIP +Random 96 + +Index 23 YFLIP XFLIP +Random 96 + +Index 24 +Random 96 + +Index 24 XFLIP +Random 96 + +Index 24 YFLIP +Random 96 + +Index 24 YFLIP XFLIP +Random 96 + +Index 25 +Random 96 + +Index 25 XFLIP +Random 96 + +Index 25 YFLIP +Random 96 + +Index 25 YFLIP XFLIP +Random 96 + +Index 39 +Random 96 + +Index 39 XFLIP +Random 96 + +Index 39 YFLIP +Random 96 + +Index 39 YFLIP XFLIP +Random 96 + +Index 40 +Random 96 + +Index 40 XFLIP +Random 96 + +Index 40 YFLIP +Random 96 + +Index 40 YFLIP XFLIP +Random 96 + +Index 41 +Random 96 + +Index 41 XFLIP +Random 96 + +Index 41 YFLIP +Random 96 + +Index 41 YFLIP XFLIP +Random 96 + +#Copper + +Index 129 +Random 96 + +Index 129 XFLIP +Random 96 + +Index 129 YFLIP +Random 96 + +Index 129 YFLIP XFLIP +Random 96 + +Index 130 +Random 96 + +Index 130 XFLIP +Random 96 + +Index 130 YFLIP +Random 96 + +Index 130 YFLIP XFLIP +Random 96 + +Index 144 +Random 96 + +Index 144 XFLIP +Random 96 + +Index 144 YFLIP +Random 96 + +Index 144 YFLIP XFLIP +Random 96 + +Index 145 +Random 96 + +Index 145 XFLIP +Random 96 + +Index 145 YFLIP +Random 96 + +Index 145 YFLIP XFLIP +Random 96 + +Index 146 +Random 96 + +Index 146 XFLIP +Random 96 + +Index 146 YFLIP +Random 96 + +Index 146 YFLIP XFLIP +Random 96 + +Index 160 +Random 96 + +Index 160 XFLIP +Random 96 + +Index 160 YFLIP +Random 96 + +Index 160 YFLIP XFLIP +Random 96 + +Index 161 +Random 96 + +Index 161 XFLIP +Random 96 + +Index 161 YFLIP +Random 96 + +Index 161 YFLIP XFLIP +Random 96 + +Index 162 +Random 96 + +Index 162 XFLIP +Random 96 + +Index 162 YFLIP +Random 96 + +Index 162 YFLIP XFLIP +Random 96 diff --git a/data/editor/generic_unhookable_0.7.rules b/data/editor/automap/generic_unhookable_0.7.rules similarity index 92% rename from data/editor/generic_unhookable_0.7.rules rename to data/editor/automap/generic_unhookable_0.7.rules index d7e6d6237..8e6335a88 100644 --- a/data/editor/generic_unhookable_0.7.rules +++ b/data/editor/automap/generic_unhookable_0.7.rules @@ -1,2138 +1,2138 @@ -[Random Silver] - -Index 16 - -Index 1 -Random 40 - -Index 1 XFLIP -Random 40 - -Index 1 YFLIP -Random 40 - -Index 1 YFLIP XFLIP -Random 40 - -Index 2 -Random 40 - -Index 2 XFLIP -Random 40 - -Index 2 YFLIP -Random 40 - -Index 2 YFLIP XFLIP -Random 40 - -Index 16 XFLIP -Random 40 - -Index 16 YFLIP -Random 40 - -Index 16 YFLIP XFLIP -Random 40 - -Index 17 -Random 40 - -Index 17 XFLIP -Random 40 - -Index 17 YFLIP -Random 40 - -Index 17 YFLIP XFLIP -Random 40 - -Index 18 -Random 40 - -Index 18 XFLIP -Random 40 - -Index 18 YFLIP -Random 40 - -Index 18 YFLIP XFLIP -Random 40 - -Index 32 -Random 40 - -Index 32 XFLIP -Random 40 - -Index 32 YFLIP -Random 40 - -Index 32 YFLIP XFLIP -Random 40 - -Index 33 -Random 40 - -Index 33 XFLIP -Random 40 - -Index 33 YFLIP -Random 40 - -Index 33 YFLIP XFLIP -Random 40 - -Index 34 -Random 40 - -Index 34 XFLIP -Random 40 - -Index 34 YFLIP -Random 40 - -Index 34 YFLIP XFLIP -Random 40 - -Index 38 -Random 40 - -Index 38 XFLIP -Random 40 - -Index 38 YFLIP -Random 40 - -Index 38 YFLIP XFLIP -Random 40 - -Index 54 -Random 40 - -Index 54 XFLIP -Random 40 - -Index 54 YFLIP -Random 40 - -Index 54 YFLIP XFLIP -Random 40 - -#random 2x2 -Index 3 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 5 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 80 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 67 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -Index 99 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -NewRun - -#Remove overlaps -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -2 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -1 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 0 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 1 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 2 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -2 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -1 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 0 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 1 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 2 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -2 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -1 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 1 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 2 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -2 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -1 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 0 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 1 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 2 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -2 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos -1 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 0 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 1 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -Index 16 -Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 -Pos 2 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 - -NewRun - -#Fill tiles -Index 4 -Pos -1 0 INDEX 3 -Index 19 -Pos 0 -1 INDEX 3 -Index 20 -Pos -1 -1 INDEX 3 - -Index 6 -Pos -1 0 INDEX 5 -Index 21 -Pos 0 -1 INDEX 5 -Index 22 -Pos -1 -1 INDEX 5 - -Index 81 -Pos -1 0 INDEX 80 -Index 82 -Pos -2 0 INDEX 80 -Index 96 -Pos 0 -1 INDEX 80 -Index 97 -Pos -1 -1 INDEX 80 -Index 98 -Pos -2 -1 INDEX 80 -Index 112 -Pos 0 -2 INDEX 80 -Index 113 -Pos -1 -2 INDEX 80 -Index 114 -Pos -2 -2 INDEX 80 - -Index 68 -Pos -1 0 INDEX 67 -Index 69 -Pos -2 0 INDEX 67 -Index 83 -Pos 0 -1 INDEX 67 -Index 84 -Pos -1 -1 INDEX 67 -Index 85 -Pos -2 -1 INDEX 67 - -Index 100 -Pos -1 0 INDEX 99 -Index 101 -Pos -2 0 INDEX 99 -Index 115 -Pos 0 -1 INDEX 99 -Index 116 -Pos -1 -1 INDEX 99 -Index 117 -Pos -2 -1 INDEX 99 - - - -[Random Gold] - -Index 23 - -Index 8 -Random 40 - -Index 8 XFLIP -Random 40 - -Index 8 YFLIP -Random 40 - -Index 8 YFLIP XFLIP -Random 40 - -Index 9 -Random 40 - -Index 9 XFLIP -Random 40 - -Index 9 YFLIP -Random 40 - -Index 9 YFLIP XFLIP -Random 40 - -Index 23 XFLIP -Random 40 - -Index 23 YFLIP -Random 40 - -Index 23 YFLIP XFLIP -Random 40 - -Index 24 -Random 40 - -Index 24 XFLIP -Random 40 - -Index 24 YFLIP -Random 40 - -Index 24 YFLIP XFLIP -Random 40 - -Index 25 -Random 40 - -Index 25 XFLIP -Random 40 - -Index 25 YFLIP -Random 40 - -Index 25 YFLIP XFLIP -Random 40 - -Index 39 -Random 40 - -Index 39 XFLIP -Random 40 - -Index 39 YFLIP -Random 40 - -Index 39 YFLIP XFLIP -Random 40 - -Index 40 -Random 40 - -Index 40 XFLIP -Random 40 - -Index 40 YFLIP -Random 40 - -Index 40 YFLIP XFLIP -Random 40 - -Index 41 -Random 40 - -Index 41 XFLIP -Random 40 - -Index 41 YFLIP -Random 40 - -Index 41 YFLIP XFLIP -Random 40 - -Index 45 -Random 40 - -Index 45 XFLIP -Random 40 - -Index 45 YFLIP -Random 40 - -Index 45 YFLIP XFLIP -Random 40 - -Index 61 -Random 40 - -Index 61 XFLIP -Random 40 - -Index 61 YFLIP -Random 40 - -Index 61 YFLIP XFLIP -Random 40 - -#random 2x2 -Index 10 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 12 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 87 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 74 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -Index 106 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -NewRun - -#Remove overlaps -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -2 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -1 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 0 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 1 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 2 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -2 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -1 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 0 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 1 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 2 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -2 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -1 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 1 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 2 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -2 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -1 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 0 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 1 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 2 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -2 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos -1 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 0 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 1 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -Index 23 -Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 -Pos 2 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 - -NewRun - -#Fill tiles -Index 11 -Pos -1 0 INDEX 10 -Index 26 -Pos 0 -1 INDEX 10 -Index 27 -Pos -1 -1 INDEX 10 - -Index 13 -Pos -1 0 INDEX 12 -Index 28 -Pos 0 -1 INDEX 12 -Index 29 -Pos -1 -1 INDEX 12 - -Index 88 -Pos -1 0 INDEX 87 -Index 89 -Pos -2 0 INDEX 87 -Index 103 -Pos 0 -1 INDEX 87 -Index 104 -Pos -1 -1 INDEX 87 -Index 105 -Pos -2 -1 INDEX 87 -Index 119 -Pos 0 -2 INDEX 87 -Index 120 -Pos -1 -2 INDEX 87 -Index 121 -Pos -2 -2 INDEX 87 - -Index 75 -Pos -1 0 INDEX 74 -Index 76 -Pos -2 0 INDEX 74 -Index 90 -Pos 0 -1 INDEX 74 -Index 91 -Pos -1 -1 INDEX 74 -Index 92 -Pos -2 -1 INDEX 74 - -Index 107 -Pos -1 0 INDEX 106 -Index 108 -Pos -2 0 INDEX 106 -Index 122 -Pos 0 -1 INDEX 106 -Index 123 -Pos -1 -1 INDEX 106 -Index 124 -Pos -2 -1 INDEX 106 - - - -[Random Bronze] - -Index 144 - -Index 129 -Random 40 - -Index 129 XFLIP -Random 40 - -Index 129 YFLIP -Random 40 - -Index 129 YFLIP XFLIP -Random 40 - -Index 130 -Random 40 - -Index 130 XFLIP -Random 40 - -Index 130 YFLIP -Random 40 - -Index 130 YFLIP XFLIP -Random 40 - -Index 144 XFLIP -Random 40 - -Index 144 YFLIP -Random 40 - -Index 144 YFLIP XFLIP -Random 40 - -Index 145 -Random 40 - -Index 145 XFLIP -Random 40 - -Index 145 YFLIP -Random 40 - -Index 145 YFLIP XFLIP -Random 40 - -Index 146 -Random 40 - -Index 146 XFLIP -Random 40 - -Index 146 YFLIP -Random 40 - -Index 146 YFLIP XFLIP -Random 40 - -Index 160 -Random 40 - -Index 160 XFLIP -Random 40 - -Index 160 YFLIP -Random 40 - -Index 160 YFLIP XFLIP -Random 40 - -Index 161 -Random 40 - -Index 161 XFLIP -Random 40 - -Index 161 YFLIP -Random 40 - -Index 161 YFLIP XFLIP -Random 40 - -Index 162 -Random 40 - -Index 162 XFLIP -Random 40 - -Index 162 YFLIP -Random 40 - -Index 162 YFLIP XFLIP -Random 40 - -Index 166 -Random 40 - -Index 166 XFLIP -Random 40 - -Index 166 YFLIP -Random 40 - -Index 166 YFLIP XFLIP -Random 40 - -Index 182 -Random 40 - -Index 182 XFLIP -Random 40 - -Index 182 YFLIP -Random 40 - -Index 182 YFLIP XFLIP -Random 40 - -#random 2x2 -Index 131 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -Index 133 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 2 0 NOTINDEX -1 -Random 50 - -#random 3x3 -Index 208 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 3 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 75 - -#random 3x2 -Index 195 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -Index 227 -Pos 0 0 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 0 NOTINDEX -1 -Pos 0 -1 NOTINDEX -1 -Pos 0 2 NOTINDEX -1 -Pos 3 0 NOTINDEX -1 -Random 100 - -NewRun - -#Remove overlaps -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -2 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -1 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 0 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 1 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 2 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -2 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -1 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 0 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 1 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 2 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -2 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -1 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 1 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 2 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -2 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -1 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 0 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 1 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 2 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -2 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos -1 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 0 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 1 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -Index 144 -Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 -Pos 2 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 - -NewRun - -#Fill tiles -Index 132 -Pos -1 0 INDEX 131 -Index 147 -Pos 0 -1 INDEX 131 -Index 148 -Pos -1 -1 INDEX 131 - -Index 134 -Pos -1 0 INDEX 133 -Index 149 -Pos 0 -1 INDEX 133 -Index 150 -Pos -1 -1 INDEX 133 - -Index 209 -Pos -1 0 INDEX 208 -Index 210 -Pos -2 0 INDEX 208 -Index 224 -Pos 0 -1 INDEX 208 -Index 225 -Pos -1 -1 INDEX 208 -Index 226 -Pos -2 -1 INDEX 208 -Index 240 -Pos 0 -2 INDEX 208 -Index 241 -Pos -1 -2 INDEX 208 -Index 242 -Pos -2 -2 INDEX 208 - -Index 196 -Pos -1 0 INDEX 195 -Index 197 -Pos -2 0 INDEX 195 -Index 211 -Pos 0 -1 INDEX 195 -Index 212 -Pos -1 -1 INDEX 195 -Index 213 -Pos -2 -1 INDEX 195 - -Index 228 -Pos -1 0 INDEX 227 -Index 229 -Pos -2 0 INDEX 227 -Index 243 -Pos 0 -1 INDEX 227 -Index 244 -Pos -1 -1 INDEX 227 -Index 245 -Pos -2 -1 INDEX 227 - - - -[Silver/Gold-Mix] - -#Silver -Index 16 - -Index 1 -Random 80 - -Index 1 XFLIP -Random 80 - -Index 1 YFLIP -Random 80 - -Index 1 YFLIP XFLIP -Random 80 - -Index 2 -Random 80 - -Index 2 XFLIP -Random 80 - -Index 2 YFLIP -Random 80 - -Index 2 YFLIP XFLIP -Random 80 - -Index 16 XFLIP -Random 80 - -Index 16 YFLIP -Random 80 - -Index 16 YFLIP XFLIP -Random 80 - -Index 17 -Random 80 - -Index 17 XFLIP -Random 80 - -Index 17 YFLIP -Random 80 - -Index 17 YFLIP XFLIP -Random 80 - -Index 18 -Random 80 - -Index 18 XFLIP -Random 80 - -Index 18 YFLIP -Random 80 - -Index 18 YFLIP XFLIP -Random 80 - -Index 32 -Random 80 - -Index 32 XFLIP -Random 80 - -Index 32 YFLIP -Random 80 - -Index 32 YFLIP XFLIP -Random 80 - -Index 33 -Random 80 - -Index 33 XFLIP -Random 80 - -Index 33 YFLIP -Random 80 - -Index 33 YFLIP XFLIP -Random 80 - -Index 34 -Random 80 - -Index 34 XFLIP -Random 80 - -Index 34 YFLIP -Random 80 - -Index 34 YFLIP XFLIP -Random 80 - -Index 38 -Random 80 - -Index 38 XFLIP -Random 80 - -Index 38 YFLIP -Random 80 - -Index 38 YFLIP XFLIP -Random 80 - -Index 54 -Random 80 - -Index 54 XFLIP -Random 80 - -Index 54 YFLIP -Random 80 - -Index 54 YFLIP XFLIP -Random 80 - -#Gold - -Index 8 -Random 80 - -Index 8 XFLIP -Random 80 - -Index 8 YFLIP -Random 80 - -Index 8 YFLIP XFLIP -Random 80 - -Index 9 -Random 80 - -Index 9 XFLIP -Random 80 - -Index 9 YFLIP -Random 80 - -Index 9 YFLIP XFLIP -Random 80 - -Index 23 -Random 80 - -Index 23 XFLIP -Random 80 - -Index 23 YFLIP -Random 80 - -Index 23 YFLIP XFLIP -Random 80 - -Index 24 -Random 80 - -Index 24 XFLIP -Random 80 - -Index 24 YFLIP -Random 80 - -Index 24 YFLIP XFLIP -Random 80 - -Index 25 -Random 80 - -Index 25 XFLIP -Random 80 - -Index 25 YFLIP -Random 80 - -Index 25 YFLIP XFLIP -Random 80 - -Index 39 -Random 80 - -Index 39 XFLIP -Random 80 - -Index 39 YFLIP -Random 80 - -Index 39 YFLIP XFLIP -Random 80 - -Index 40 -Random 80 - -Index 40 XFLIP -Random 80 - -Index 40 YFLIP -Random 80 - -Index 40 YFLIP XFLIP -Random 80 - -Index 41 -Random 80 - -Index 41 XFLIP -Random 80 - -Index 41 YFLIP -Random 80 - -Index 41 YFLIP XFLIP -Random 80 - -Index 45 -Random 80 - -Index 45 XFLIP -Random 80 - -Index 45 YFLIP -Random 80 - -Index 45 YFLIP XFLIP -Random 80 - -Index 61 -Random 80 - -Index 61 XFLIP -Random 80 - -Index 61 YFLIP -Random 80 - -Index 61 YFLIP XFLIP -Random 80 - - - -[Copper/Silver-Mix] - -#Copper -Index 144 - -Index 129 -Random 80 - -Index 129 XFLIP -Random 80 - -Index 129 YFLIP -Random 80 - -Index 129 YFLIP XFLIP -Random 80 - -Index 130 -Random 80 - -Index 130 XFLIP -Random 80 - -Index 130 YFLIP -Random 80 - -Index 130 YFLIP XFLIP -Random 80 - -Index 144 XFLIP -Random 80 - -Index 144 YFLIP -Random 80 - -Index 144 YFLIP XFLIP -Random 80 - -Index 145 -Random 80 - -Index 145 XFLIP -Random 80 - -Index 145 YFLIP -Random 80 - -Index 145 YFLIP XFLIP -Random 80 - -Index 146 -Random 80 - -Index 146 XFLIP -Random 80 - -Index 146 YFLIP -Random 80 - -Index 146 YFLIP XFLIP -Random 80 - -Index 160 -Random 80 - -Index 160 XFLIP -Random 80 - -Index 160 YFLIP -Random 80 - -Index 160 YFLIP XFLIP -Random 80 - -Index 161 -Random 80 - -Index 161 XFLIP -Random 80 - -Index 161 YFLIP -Random 80 - -Index 161 YFLIP XFLIP -Random 80 - -Index 162 -Random 80 - -Index 162 XFLIP -Random 80 - -Index 162 YFLIP -Random 80 - -Index 162 YFLIP XFLIP -Random 80 - -Index 166 -Random 80 - -Index 166 XFLIP -Random 80 - -Index 166 YFLIP -Random 80 - -Index 166 YFLIP XFLIP -Random 80 - -Index 182 -Random 80 - -Index 182 XFLIP -Random 80 - -Index 182 YFLIP -Random 80 - -Index 182 YFLIP XFLIP -Random 80 - -#Silver - -Index 1 -Random 80 - -Index 1 XFLIP -Random 80 - -Index 1 YFLIP -Random 80 - -Index 1 YFLIP XFLIP -Random 80 - -Index 2 -Random 80 - -Index 2 XFLIP -Random 80 - -Index 2 YFLIP -Random 80 - -Index 2 YFLIP XFLIP -Random 80 - -Index 16 -Random 80 - -Index 16 XFLIP -Random 80 - -Index 16 YFLIP -Random 80 - -Index 16 YFLIP XFLIP -Random 80 - -Index 17 -Random 80 - -Index 17 XFLIP -Random 80 - -Index 17 YFLIP -Random 80 - -Index 17 YFLIP XFLIP -Random 80 - -Index 18 -Random 80 - -Index 18 XFLIP -Random 80 - -Index 18 YFLIP -Random 80 - -Index 18 YFLIP XFLIP -Random 80 - -Index 32 -Random 80 - -Index 32 XFLIP -Random 80 - -Index 32 YFLIP -Random 80 - -Index 32 YFLIP XFLIP -Random 80 - -Index 33 -Random 80 - -Index 33 XFLIP -Random 80 - -Index 33 YFLIP -Random 80 - -Index 33 YFLIP XFLIP -Random 80 - -Index 34 -Random 80 - -Index 34 XFLIP -Random 80 - -Index 34 YFLIP -Random 80 - -Index 34 YFLIP XFLIP -Random 80 - -Index 38 -Random 80 - -Index 38 XFLIP -Random 80 - -Index 38 YFLIP -Random 80 - -Index 38 YFLIP XFLIP -Random 80 - -Index 54 -Random 80 - -Index 54 XFLIP -Random 80 - -Index 54 YFLIP -Random 80 - -Index 54 YFLIP XFLIP -Random 80 - - - -[Gold/Copper-Mix] - -#Gold -Index 23 - -Index 8 -Random 80 - -Index 8 XFLIP -Random 80 - -Index 8 YFLIP -Random 80 - -Index 8 YFLIP XFLIP -Random 80 - -Index 9 -Random 80 - -Index 9 XFLIP -Random 80 - -Index 9 YFLIP -Random 80 - -Index 9 YFLIP XFLIP -Random 80 - -Index 23 XFLIP -Random 80 - -Index 23 YFLIP -Random 80 - -Index 23 YFLIP XFLIP -Random 80 - -Index 24 -Random 80 - -Index 24 XFLIP -Random 80 - -Index 24 YFLIP -Random 80 - -Index 24 YFLIP XFLIP -Random 80 - -Index 25 -Random 80 - -Index 25 XFLIP -Random 80 - -Index 25 YFLIP -Random 80 - -Index 25 YFLIP XFLIP -Random 80 - -Index 39 -Random 80 - -Index 39 XFLIP -Random 80 - -Index 39 YFLIP -Random 80 - -Index 39 YFLIP XFLIP -Random 80 - -Index 40 -Random 80 - -Index 40 XFLIP -Random 80 - -Index 40 YFLIP -Random 80 - -Index 40 YFLIP XFLIP -Random 80 - -Index 41 -Random 80 - -Index 41 XFLIP -Random 80 - -Index 41 YFLIP -Random 80 - -Index 41 YFLIP XFLIP -Random 80 - -Index 45 -Random 80 - -Index 45 XFLIP -Random 80 - -Index 45 YFLIP -Random 80 - -Index 45 YFLIP XFLIP -Random 80 - -Index 61 -Random 80 - -Index 61 XFLIP -Random 80 - -Index 61 YFLIP -Random 80 - -Index 61 YFLIP XFLIP -Random 80 - -#Copper - -Index 129 -Random 80 - -Index 129 XFLIP -Random 80 - -Index 129 YFLIP -Random 80 - -Index 129 YFLIP XFLIP -Random 80 - -Index 130 -Random 80 - -Index 130 XFLIP -Random 80 - -Index 130 YFLIP -Random 80 - -Index 130 YFLIP XFLIP -Random 80 - -Index 144 -Random 80 - -Index 144 XFLIP -Random 80 - -Index 144 YFLIP -Random 80 - -Index 144 YFLIP XFLIP -Random 80 - -Index 145 -Random 80 - -Index 145 XFLIP -Random 80 - -Index 145 YFLIP -Random 80 - -Index 145 YFLIP XFLIP -Random 80 - -Index 146 -Random 80 - -Index 146 XFLIP -Random 80 - -Index 146 YFLIP -Random 80 - -Index 146 YFLIP XFLIP -Random 80 - -Index 160 -Random 80 - -Index 160 XFLIP -Random 80 - -Index 160 YFLIP -Random 80 - -Index 160 YFLIP XFLIP -Random 80 - -Index 161 -Random 80 - -Index 161 XFLIP -Random 80 - -Index 161 YFLIP -Random 80 - -Index 161 YFLIP XFLIP -Random 80 - -Index 162 -Random 80 - -Index 162 XFLIP -Random 80 - -Index 162 YFLIP -Random 80 - -Index 162 YFLIP XFLIP -Random 80 - -Index 166 -Random 80 - -Index 166 XFLIP -Random 80 - -Index 166 YFLIP -Random 80 - -Index 166 YFLIP XFLIP -Random 80 - -Index 182 -Random 80 - -Index 182 XFLIP -Random 80 - -Index 182 YFLIP -Random 80 - -Index 182 YFLIP XFLIP -Random 80 - - - -[Mix All] - -#Silver -Index 16 - -Index 1 -Random 120 - -Index 1 XFLIP -Random 120 - -Index 1 YFLIP -Random 120 - -Index 1 YFLIP XFLIP -Random 120 - -Index 2 -Random 120 - -Index 2 XFLIP -Random 120 - -Index 2 YFLIP -Random 120 - -Index 2 YFLIP XFLIP -Random 120 - -Index 16 XFLIP -Random 120 - -Index 16 YFLIP -Random 120 - -Index 16 YFLIP XFLIP -Random 120 - -Index 17 -Random 120 - -Index 17 XFLIP -Random 120 - -Index 17 YFLIP -Random 120 - -Index 17 YFLIP XFLIP -Random 120 - -Index 18 -Random 120 - -Index 18 XFLIP -Random 120 - -Index 18 YFLIP -Random 120 - -Index 18 YFLIP XFLIP -Random 120 - -Index 32 -Random 120 - -Index 32 XFLIP -Random 120 - -Index 32 YFLIP -Random 120 - -Index 32 YFLIP XFLIP -Random 120 - -Index 33 -Random 120 - -Index 33 XFLIP -Random 120 - -Index 33 YFLIP -Random 120 - -Index 33 YFLIP XFLIP -Random 120 - -Index 34 -Random 120 - -Index 34 XFLIP -Random 120 - -Index 34 YFLIP -Random 120 - -Index 34 YFLIP XFLIP -Random 120 - -Index 38 -Random 120 - -Index 38 XFLIP -Random 120 - -Index 38 YFLIP -Random 120 - -Index 38 YFLIP XFLIP -Random 120 - -Index 54 -Random 120 - -Index 54 XFLIP -Random 120 - -Index 54 YFLIP -Random 120 - -Index 54 YFLIP XFLIP -Random 120 - -#Gold - -Index 8 -Random 120 - -Index 8 XFLIP -Random 120 - -Index 8 YFLIP -Random 120 - -Index 8 YFLIP XFLIP -Random 120 - -Index 9 -Random 120 - -Index 9 XFLIP -Random 120 - -Index 9 YFLIP -Random 120 - -Index 9 YFLIP XFLIP -Random 120 - -Index 23 -Random 120 - -Index 23 XFLIP -Random 120 - -Index 23 YFLIP -Random 120 - -Index 23 YFLIP XFLIP -Random 120 - -Index 24 -Random 120 - -Index 24 XFLIP -Random 120 - -Index 24 YFLIP -Random 120 - -Index 24 YFLIP XFLIP -Random 120 - -Index 25 -Random 120 - -Index 25 XFLIP -Random 120 - -Index 25 YFLIP -Random 120 - -Index 25 YFLIP XFLIP -Random 120 - -Index 39 -Random 120 - -Index 39 XFLIP -Random 120 - -Index 39 YFLIP -Random 120 - -Index 39 YFLIP XFLIP -Random 120 - -Index 40 -Random 120 - -Index 40 XFLIP -Random 120 - -Index 40 YFLIP -Random 120 - -Index 40 YFLIP XFLIP -Random 120 - -Index 41 -Random 120 - -Index 41 XFLIP -Random 120 - -Index 41 YFLIP -Random 120 - -Index 41 YFLIP XFLIP -Random 120 - -Index 45 -Random 120 - -Index 45 XFLIP -Random 120 - -Index 45 YFLIP -Random 120 - -Index 45 YFLIP XFLIP -Random 120 - -Index 61 -Random 120 - -Index 61 XFLIP -Random 120 - -Index 61 YFLIP -Random 120 - -Index 61 YFLIP XFLIP -Random 120 - -#Copper - -Index 129 -Random 120 - -Index 129 XFLIP -Random 120 - -Index 129 YFLIP -Random 120 - -Index 129 YFLIP XFLIP -Random 120 - -Index 130 -Random 120 - -Index 130 XFLIP -Random 120 - -Index 130 YFLIP -Random 120 - -Index 130 YFLIP XFLIP -Random 120 - -Index 144 -Random 120 - -Index 144 XFLIP -Random 120 - -Index 144 YFLIP -Random 120 - -Index 144 YFLIP XFLIP -Random 120 - -Index 145 -Random 120 - -Index 145 XFLIP -Random 120 - -Index 145 YFLIP -Random 120 - -Index 145 YFLIP XFLIP -Random 120 - -Index 146 -Random 120 - -Index 146 XFLIP -Random 120 - -Index 146 YFLIP -Random 120 - -Index 146 YFLIP XFLIP -Random 120 - -Index 160 -Random 120 - -Index 160 XFLIP -Random 120 - -Index 160 YFLIP -Random 120 - -Index 160 YFLIP XFLIP -Random 120 - -Index 161 -Random 120 - -Index 161 XFLIP -Random 120 - -Index 161 YFLIP -Random 120 - -Index 161 YFLIP XFLIP -Random 120 - -Index 162 -Random 120 - -Index 162 XFLIP -Random 120 - -Index 162 YFLIP -Random 120 - -Index 162 YFLIP XFLIP -Random 120 - -Index 166 -Random 120 - -Index 166 XFLIP -Random 120 - -Index 166 YFLIP -Random 120 - -Index 166 YFLIP XFLIP -Random 120 - -Index 182 -Random 120 - -Index 182 XFLIP -Random 120 - -Index 182 YFLIP -Random 120 - -Index 182 YFLIP XFLIP -Random 120 +[Random Silver] + +Index 16 + +Index 1 +Random 40 + +Index 1 XFLIP +Random 40 + +Index 1 YFLIP +Random 40 + +Index 1 YFLIP XFLIP +Random 40 + +Index 2 +Random 40 + +Index 2 XFLIP +Random 40 + +Index 2 YFLIP +Random 40 + +Index 2 YFLIP XFLIP +Random 40 + +Index 16 XFLIP +Random 40 + +Index 16 YFLIP +Random 40 + +Index 16 YFLIP XFLIP +Random 40 + +Index 17 +Random 40 + +Index 17 XFLIP +Random 40 + +Index 17 YFLIP +Random 40 + +Index 17 YFLIP XFLIP +Random 40 + +Index 18 +Random 40 + +Index 18 XFLIP +Random 40 + +Index 18 YFLIP +Random 40 + +Index 18 YFLIP XFLIP +Random 40 + +Index 32 +Random 40 + +Index 32 XFLIP +Random 40 + +Index 32 YFLIP +Random 40 + +Index 32 YFLIP XFLIP +Random 40 + +Index 33 +Random 40 + +Index 33 XFLIP +Random 40 + +Index 33 YFLIP +Random 40 + +Index 33 YFLIP XFLIP +Random 40 + +Index 34 +Random 40 + +Index 34 XFLIP +Random 40 + +Index 34 YFLIP +Random 40 + +Index 34 YFLIP XFLIP +Random 40 + +Index 38 +Random 40 + +Index 38 XFLIP +Random 40 + +Index 38 YFLIP +Random 40 + +Index 38 YFLIP XFLIP +Random 40 + +Index 54 +Random 40 + +Index 54 XFLIP +Random 40 + +Index 54 YFLIP +Random 40 + +Index 54 YFLIP XFLIP +Random 40 + +#random 2x2 +Index 3 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 5 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 80 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 67 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +Index 99 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +NewRun + +#Remove overlaps +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -2 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -1 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 0 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 1 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 2 -2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -2 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -1 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 0 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 1 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 2 -1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -2 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -1 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 1 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 2 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -2 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -1 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 0 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 1 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 2 1 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -2 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos -1 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 0 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 1 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +Index 16 +Pos 0 0 INDEX 3 OR 5 OR 80 OR 67 OR 99 +Pos 2 2 INDEX 3 OR 5 OR 80 OR 67 OR 99 + +NewRun + +#Fill tiles +Index 4 +Pos -1 0 INDEX 3 +Index 19 +Pos 0 -1 INDEX 3 +Index 20 +Pos -1 -1 INDEX 3 + +Index 6 +Pos -1 0 INDEX 5 +Index 21 +Pos 0 -1 INDEX 5 +Index 22 +Pos -1 -1 INDEX 5 + +Index 81 +Pos -1 0 INDEX 80 +Index 82 +Pos -2 0 INDEX 80 +Index 96 +Pos 0 -1 INDEX 80 +Index 97 +Pos -1 -1 INDEX 80 +Index 98 +Pos -2 -1 INDEX 80 +Index 112 +Pos 0 -2 INDEX 80 +Index 113 +Pos -1 -2 INDEX 80 +Index 114 +Pos -2 -2 INDEX 80 + +Index 68 +Pos -1 0 INDEX 67 +Index 69 +Pos -2 0 INDEX 67 +Index 83 +Pos 0 -1 INDEX 67 +Index 84 +Pos -1 -1 INDEX 67 +Index 85 +Pos -2 -1 INDEX 67 + +Index 100 +Pos -1 0 INDEX 99 +Index 101 +Pos -2 0 INDEX 99 +Index 115 +Pos 0 -1 INDEX 99 +Index 116 +Pos -1 -1 INDEX 99 +Index 117 +Pos -2 -1 INDEX 99 + + + +[Random Gold] + +Index 23 + +Index 8 +Random 40 + +Index 8 XFLIP +Random 40 + +Index 8 YFLIP +Random 40 + +Index 8 YFLIP XFLIP +Random 40 + +Index 9 +Random 40 + +Index 9 XFLIP +Random 40 + +Index 9 YFLIP +Random 40 + +Index 9 YFLIP XFLIP +Random 40 + +Index 23 XFLIP +Random 40 + +Index 23 YFLIP +Random 40 + +Index 23 YFLIP XFLIP +Random 40 + +Index 24 +Random 40 + +Index 24 XFLIP +Random 40 + +Index 24 YFLIP +Random 40 + +Index 24 YFLIP XFLIP +Random 40 + +Index 25 +Random 40 + +Index 25 XFLIP +Random 40 + +Index 25 YFLIP +Random 40 + +Index 25 YFLIP XFLIP +Random 40 + +Index 39 +Random 40 + +Index 39 XFLIP +Random 40 + +Index 39 YFLIP +Random 40 + +Index 39 YFLIP XFLIP +Random 40 + +Index 40 +Random 40 + +Index 40 XFLIP +Random 40 + +Index 40 YFLIP +Random 40 + +Index 40 YFLIP XFLIP +Random 40 + +Index 41 +Random 40 + +Index 41 XFLIP +Random 40 + +Index 41 YFLIP +Random 40 + +Index 41 YFLIP XFLIP +Random 40 + +Index 45 +Random 40 + +Index 45 XFLIP +Random 40 + +Index 45 YFLIP +Random 40 + +Index 45 YFLIP XFLIP +Random 40 + +Index 61 +Random 40 + +Index 61 XFLIP +Random 40 + +Index 61 YFLIP +Random 40 + +Index 61 YFLIP XFLIP +Random 40 + +#random 2x2 +Index 10 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 12 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 87 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 74 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +Index 106 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +NewRun + +#Remove overlaps +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -2 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -1 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 0 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 1 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 2 -2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -2 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -1 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 0 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 1 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 2 -1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -2 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -1 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 1 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 2 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -2 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -1 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 0 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 1 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 2 1 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -2 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos -1 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 0 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 1 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +Index 23 +Pos 0 0 INDEX 10 OR 12 OR 87 OR 74 OR 106 +Pos 2 2 INDEX 10 OR 12 OR 87 OR 74 OR 106 + +NewRun + +#Fill tiles +Index 11 +Pos -1 0 INDEX 10 +Index 26 +Pos 0 -1 INDEX 10 +Index 27 +Pos -1 -1 INDEX 10 + +Index 13 +Pos -1 0 INDEX 12 +Index 28 +Pos 0 -1 INDEX 12 +Index 29 +Pos -1 -1 INDEX 12 + +Index 88 +Pos -1 0 INDEX 87 +Index 89 +Pos -2 0 INDEX 87 +Index 103 +Pos 0 -1 INDEX 87 +Index 104 +Pos -1 -1 INDEX 87 +Index 105 +Pos -2 -1 INDEX 87 +Index 119 +Pos 0 -2 INDEX 87 +Index 120 +Pos -1 -2 INDEX 87 +Index 121 +Pos -2 -2 INDEX 87 + +Index 75 +Pos -1 0 INDEX 74 +Index 76 +Pos -2 0 INDEX 74 +Index 90 +Pos 0 -1 INDEX 74 +Index 91 +Pos -1 -1 INDEX 74 +Index 92 +Pos -2 -1 INDEX 74 + +Index 107 +Pos -1 0 INDEX 106 +Index 108 +Pos -2 0 INDEX 106 +Index 122 +Pos 0 -1 INDEX 106 +Index 123 +Pos -1 -1 INDEX 106 +Index 124 +Pos -2 -1 INDEX 106 + + + +[Random Bronze] + +Index 144 + +Index 129 +Random 40 + +Index 129 XFLIP +Random 40 + +Index 129 YFLIP +Random 40 + +Index 129 YFLIP XFLIP +Random 40 + +Index 130 +Random 40 + +Index 130 XFLIP +Random 40 + +Index 130 YFLIP +Random 40 + +Index 130 YFLIP XFLIP +Random 40 + +Index 144 XFLIP +Random 40 + +Index 144 YFLIP +Random 40 + +Index 144 YFLIP XFLIP +Random 40 + +Index 145 +Random 40 + +Index 145 XFLIP +Random 40 + +Index 145 YFLIP +Random 40 + +Index 145 YFLIP XFLIP +Random 40 + +Index 146 +Random 40 + +Index 146 XFLIP +Random 40 + +Index 146 YFLIP +Random 40 + +Index 146 YFLIP XFLIP +Random 40 + +Index 160 +Random 40 + +Index 160 XFLIP +Random 40 + +Index 160 YFLIP +Random 40 + +Index 160 YFLIP XFLIP +Random 40 + +Index 161 +Random 40 + +Index 161 XFLIP +Random 40 + +Index 161 YFLIP +Random 40 + +Index 161 YFLIP XFLIP +Random 40 + +Index 162 +Random 40 + +Index 162 XFLIP +Random 40 + +Index 162 YFLIP +Random 40 + +Index 162 YFLIP XFLIP +Random 40 + +Index 166 +Random 40 + +Index 166 XFLIP +Random 40 + +Index 166 YFLIP +Random 40 + +Index 166 YFLIP XFLIP +Random 40 + +Index 182 +Random 40 + +Index 182 XFLIP +Random 40 + +Index 182 YFLIP +Random 40 + +Index 182 YFLIP XFLIP +Random 40 + +#random 2x2 +Index 131 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +Index 133 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 2 0 NOTINDEX -1 +Random 50 + +#random 3x3 +Index 208 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 3 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 75 + +#random 3x2 +Index 195 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +Index 227 +Pos 0 0 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 0 NOTINDEX -1 +Pos 0 -1 NOTINDEX -1 +Pos 0 2 NOTINDEX -1 +Pos 3 0 NOTINDEX -1 +Random 100 + +NewRun + +#Remove overlaps +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -2 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -1 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 0 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 1 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 2 -2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -2 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -1 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 0 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 1 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 2 -1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -2 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -1 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 1 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 2 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -2 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -1 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 0 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 1 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 2 1 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -2 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos -1 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 0 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 1 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +Index 144 +Pos 0 0 INDEX 131 OR 133 OR 208 OR 195 OR 227 +Pos 2 2 INDEX 131 OR 133 OR 208 OR 195 OR 227 + +NewRun + +#Fill tiles +Index 132 +Pos -1 0 INDEX 131 +Index 147 +Pos 0 -1 INDEX 131 +Index 148 +Pos -1 -1 INDEX 131 + +Index 134 +Pos -1 0 INDEX 133 +Index 149 +Pos 0 -1 INDEX 133 +Index 150 +Pos -1 -1 INDEX 133 + +Index 209 +Pos -1 0 INDEX 208 +Index 210 +Pos -2 0 INDEX 208 +Index 224 +Pos 0 -1 INDEX 208 +Index 225 +Pos -1 -1 INDEX 208 +Index 226 +Pos -2 -1 INDEX 208 +Index 240 +Pos 0 -2 INDEX 208 +Index 241 +Pos -1 -2 INDEX 208 +Index 242 +Pos -2 -2 INDEX 208 + +Index 196 +Pos -1 0 INDEX 195 +Index 197 +Pos -2 0 INDEX 195 +Index 211 +Pos 0 -1 INDEX 195 +Index 212 +Pos -1 -1 INDEX 195 +Index 213 +Pos -2 -1 INDEX 195 + +Index 228 +Pos -1 0 INDEX 227 +Index 229 +Pos -2 0 INDEX 227 +Index 243 +Pos 0 -1 INDEX 227 +Index 244 +Pos -1 -1 INDEX 227 +Index 245 +Pos -2 -1 INDEX 227 + + + +[Silver/Gold-Mix] + +#Silver +Index 16 + +Index 1 +Random 80 + +Index 1 XFLIP +Random 80 + +Index 1 YFLIP +Random 80 + +Index 1 YFLIP XFLIP +Random 80 + +Index 2 +Random 80 + +Index 2 XFLIP +Random 80 + +Index 2 YFLIP +Random 80 + +Index 2 YFLIP XFLIP +Random 80 + +Index 16 XFLIP +Random 80 + +Index 16 YFLIP +Random 80 + +Index 16 YFLIP XFLIP +Random 80 + +Index 17 +Random 80 + +Index 17 XFLIP +Random 80 + +Index 17 YFLIP +Random 80 + +Index 17 YFLIP XFLIP +Random 80 + +Index 18 +Random 80 + +Index 18 XFLIP +Random 80 + +Index 18 YFLIP +Random 80 + +Index 18 YFLIP XFLIP +Random 80 + +Index 32 +Random 80 + +Index 32 XFLIP +Random 80 + +Index 32 YFLIP +Random 80 + +Index 32 YFLIP XFLIP +Random 80 + +Index 33 +Random 80 + +Index 33 XFLIP +Random 80 + +Index 33 YFLIP +Random 80 + +Index 33 YFLIP XFLIP +Random 80 + +Index 34 +Random 80 + +Index 34 XFLIP +Random 80 + +Index 34 YFLIP +Random 80 + +Index 34 YFLIP XFLIP +Random 80 + +Index 38 +Random 80 + +Index 38 XFLIP +Random 80 + +Index 38 YFLIP +Random 80 + +Index 38 YFLIP XFLIP +Random 80 + +Index 54 +Random 80 + +Index 54 XFLIP +Random 80 + +Index 54 YFLIP +Random 80 + +Index 54 YFLIP XFLIP +Random 80 + +#Gold + +Index 8 +Random 80 + +Index 8 XFLIP +Random 80 + +Index 8 YFLIP +Random 80 + +Index 8 YFLIP XFLIP +Random 80 + +Index 9 +Random 80 + +Index 9 XFLIP +Random 80 + +Index 9 YFLIP +Random 80 + +Index 9 YFLIP XFLIP +Random 80 + +Index 23 +Random 80 + +Index 23 XFLIP +Random 80 + +Index 23 YFLIP +Random 80 + +Index 23 YFLIP XFLIP +Random 80 + +Index 24 +Random 80 + +Index 24 XFLIP +Random 80 + +Index 24 YFLIP +Random 80 + +Index 24 YFLIP XFLIP +Random 80 + +Index 25 +Random 80 + +Index 25 XFLIP +Random 80 + +Index 25 YFLIP +Random 80 + +Index 25 YFLIP XFLIP +Random 80 + +Index 39 +Random 80 + +Index 39 XFLIP +Random 80 + +Index 39 YFLIP +Random 80 + +Index 39 YFLIP XFLIP +Random 80 + +Index 40 +Random 80 + +Index 40 XFLIP +Random 80 + +Index 40 YFLIP +Random 80 + +Index 40 YFLIP XFLIP +Random 80 + +Index 41 +Random 80 + +Index 41 XFLIP +Random 80 + +Index 41 YFLIP +Random 80 + +Index 41 YFLIP XFLIP +Random 80 + +Index 45 +Random 80 + +Index 45 XFLIP +Random 80 + +Index 45 YFLIP +Random 80 + +Index 45 YFLIP XFLIP +Random 80 + +Index 61 +Random 80 + +Index 61 XFLIP +Random 80 + +Index 61 YFLIP +Random 80 + +Index 61 YFLIP XFLIP +Random 80 + + + +[Copper/Silver-Mix] + +#Copper +Index 144 + +Index 129 +Random 80 + +Index 129 XFLIP +Random 80 + +Index 129 YFLIP +Random 80 + +Index 129 YFLIP XFLIP +Random 80 + +Index 130 +Random 80 + +Index 130 XFLIP +Random 80 + +Index 130 YFLIP +Random 80 + +Index 130 YFLIP XFLIP +Random 80 + +Index 144 XFLIP +Random 80 + +Index 144 YFLIP +Random 80 + +Index 144 YFLIP XFLIP +Random 80 + +Index 145 +Random 80 + +Index 145 XFLIP +Random 80 + +Index 145 YFLIP +Random 80 + +Index 145 YFLIP XFLIP +Random 80 + +Index 146 +Random 80 + +Index 146 XFLIP +Random 80 + +Index 146 YFLIP +Random 80 + +Index 146 YFLIP XFLIP +Random 80 + +Index 160 +Random 80 + +Index 160 XFLIP +Random 80 + +Index 160 YFLIP +Random 80 + +Index 160 YFLIP XFLIP +Random 80 + +Index 161 +Random 80 + +Index 161 XFLIP +Random 80 + +Index 161 YFLIP +Random 80 + +Index 161 YFLIP XFLIP +Random 80 + +Index 162 +Random 80 + +Index 162 XFLIP +Random 80 + +Index 162 YFLIP +Random 80 + +Index 162 YFLIP XFLIP +Random 80 + +Index 166 +Random 80 + +Index 166 XFLIP +Random 80 + +Index 166 YFLIP +Random 80 + +Index 166 YFLIP XFLIP +Random 80 + +Index 182 +Random 80 + +Index 182 XFLIP +Random 80 + +Index 182 YFLIP +Random 80 + +Index 182 YFLIP XFLIP +Random 80 + +#Silver + +Index 1 +Random 80 + +Index 1 XFLIP +Random 80 + +Index 1 YFLIP +Random 80 + +Index 1 YFLIP XFLIP +Random 80 + +Index 2 +Random 80 + +Index 2 XFLIP +Random 80 + +Index 2 YFLIP +Random 80 + +Index 2 YFLIP XFLIP +Random 80 + +Index 16 +Random 80 + +Index 16 XFLIP +Random 80 + +Index 16 YFLIP +Random 80 + +Index 16 YFLIP XFLIP +Random 80 + +Index 17 +Random 80 + +Index 17 XFLIP +Random 80 + +Index 17 YFLIP +Random 80 + +Index 17 YFLIP XFLIP +Random 80 + +Index 18 +Random 80 + +Index 18 XFLIP +Random 80 + +Index 18 YFLIP +Random 80 + +Index 18 YFLIP XFLIP +Random 80 + +Index 32 +Random 80 + +Index 32 XFLIP +Random 80 + +Index 32 YFLIP +Random 80 + +Index 32 YFLIP XFLIP +Random 80 + +Index 33 +Random 80 + +Index 33 XFLIP +Random 80 + +Index 33 YFLIP +Random 80 + +Index 33 YFLIP XFLIP +Random 80 + +Index 34 +Random 80 + +Index 34 XFLIP +Random 80 + +Index 34 YFLIP +Random 80 + +Index 34 YFLIP XFLIP +Random 80 + +Index 38 +Random 80 + +Index 38 XFLIP +Random 80 + +Index 38 YFLIP +Random 80 + +Index 38 YFLIP XFLIP +Random 80 + +Index 54 +Random 80 + +Index 54 XFLIP +Random 80 + +Index 54 YFLIP +Random 80 + +Index 54 YFLIP XFLIP +Random 80 + + + +[Gold/Copper-Mix] + +#Gold +Index 23 + +Index 8 +Random 80 + +Index 8 XFLIP +Random 80 + +Index 8 YFLIP +Random 80 + +Index 8 YFLIP XFLIP +Random 80 + +Index 9 +Random 80 + +Index 9 XFLIP +Random 80 + +Index 9 YFLIP +Random 80 + +Index 9 YFLIP XFLIP +Random 80 + +Index 23 XFLIP +Random 80 + +Index 23 YFLIP +Random 80 + +Index 23 YFLIP XFLIP +Random 80 + +Index 24 +Random 80 + +Index 24 XFLIP +Random 80 + +Index 24 YFLIP +Random 80 + +Index 24 YFLIP XFLIP +Random 80 + +Index 25 +Random 80 + +Index 25 XFLIP +Random 80 + +Index 25 YFLIP +Random 80 + +Index 25 YFLIP XFLIP +Random 80 + +Index 39 +Random 80 + +Index 39 XFLIP +Random 80 + +Index 39 YFLIP +Random 80 + +Index 39 YFLIP XFLIP +Random 80 + +Index 40 +Random 80 + +Index 40 XFLIP +Random 80 + +Index 40 YFLIP +Random 80 + +Index 40 YFLIP XFLIP +Random 80 + +Index 41 +Random 80 + +Index 41 XFLIP +Random 80 + +Index 41 YFLIP +Random 80 + +Index 41 YFLIP XFLIP +Random 80 + +Index 45 +Random 80 + +Index 45 XFLIP +Random 80 + +Index 45 YFLIP +Random 80 + +Index 45 YFLIP XFLIP +Random 80 + +Index 61 +Random 80 + +Index 61 XFLIP +Random 80 + +Index 61 YFLIP +Random 80 + +Index 61 YFLIP XFLIP +Random 80 + +#Copper + +Index 129 +Random 80 + +Index 129 XFLIP +Random 80 + +Index 129 YFLIP +Random 80 + +Index 129 YFLIP XFLIP +Random 80 + +Index 130 +Random 80 + +Index 130 XFLIP +Random 80 + +Index 130 YFLIP +Random 80 + +Index 130 YFLIP XFLIP +Random 80 + +Index 144 +Random 80 + +Index 144 XFLIP +Random 80 + +Index 144 YFLIP +Random 80 + +Index 144 YFLIP XFLIP +Random 80 + +Index 145 +Random 80 + +Index 145 XFLIP +Random 80 + +Index 145 YFLIP +Random 80 + +Index 145 YFLIP XFLIP +Random 80 + +Index 146 +Random 80 + +Index 146 XFLIP +Random 80 + +Index 146 YFLIP +Random 80 + +Index 146 YFLIP XFLIP +Random 80 + +Index 160 +Random 80 + +Index 160 XFLIP +Random 80 + +Index 160 YFLIP +Random 80 + +Index 160 YFLIP XFLIP +Random 80 + +Index 161 +Random 80 + +Index 161 XFLIP +Random 80 + +Index 161 YFLIP +Random 80 + +Index 161 YFLIP XFLIP +Random 80 + +Index 162 +Random 80 + +Index 162 XFLIP +Random 80 + +Index 162 YFLIP +Random 80 + +Index 162 YFLIP XFLIP +Random 80 + +Index 166 +Random 80 + +Index 166 XFLIP +Random 80 + +Index 166 YFLIP +Random 80 + +Index 166 YFLIP XFLIP +Random 80 + +Index 182 +Random 80 + +Index 182 XFLIP +Random 80 + +Index 182 YFLIP +Random 80 + +Index 182 YFLIP XFLIP +Random 80 + + + +[Mix All] + +#Silver +Index 16 + +Index 1 +Random 120 + +Index 1 XFLIP +Random 120 + +Index 1 YFLIP +Random 120 + +Index 1 YFLIP XFLIP +Random 120 + +Index 2 +Random 120 + +Index 2 XFLIP +Random 120 + +Index 2 YFLIP +Random 120 + +Index 2 YFLIP XFLIP +Random 120 + +Index 16 XFLIP +Random 120 + +Index 16 YFLIP +Random 120 + +Index 16 YFLIP XFLIP +Random 120 + +Index 17 +Random 120 + +Index 17 XFLIP +Random 120 + +Index 17 YFLIP +Random 120 + +Index 17 YFLIP XFLIP +Random 120 + +Index 18 +Random 120 + +Index 18 XFLIP +Random 120 + +Index 18 YFLIP +Random 120 + +Index 18 YFLIP XFLIP +Random 120 + +Index 32 +Random 120 + +Index 32 XFLIP +Random 120 + +Index 32 YFLIP +Random 120 + +Index 32 YFLIP XFLIP +Random 120 + +Index 33 +Random 120 + +Index 33 XFLIP +Random 120 + +Index 33 YFLIP +Random 120 + +Index 33 YFLIP XFLIP +Random 120 + +Index 34 +Random 120 + +Index 34 XFLIP +Random 120 + +Index 34 YFLIP +Random 120 + +Index 34 YFLIP XFLIP +Random 120 + +Index 38 +Random 120 + +Index 38 XFLIP +Random 120 + +Index 38 YFLIP +Random 120 + +Index 38 YFLIP XFLIP +Random 120 + +Index 54 +Random 120 + +Index 54 XFLIP +Random 120 + +Index 54 YFLIP +Random 120 + +Index 54 YFLIP XFLIP +Random 120 + +#Gold + +Index 8 +Random 120 + +Index 8 XFLIP +Random 120 + +Index 8 YFLIP +Random 120 + +Index 8 YFLIP XFLIP +Random 120 + +Index 9 +Random 120 + +Index 9 XFLIP +Random 120 + +Index 9 YFLIP +Random 120 + +Index 9 YFLIP XFLIP +Random 120 + +Index 23 +Random 120 + +Index 23 XFLIP +Random 120 + +Index 23 YFLIP +Random 120 + +Index 23 YFLIP XFLIP +Random 120 + +Index 24 +Random 120 + +Index 24 XFLIP +Random 120 + +Index 24 YFLIP +Random 120 + +Index 24 YFLIP XFLIP +Random 120 + +Index 25 +Random 120 + +Index 25 XFLIP +Random 120 + +Index 25 YFLIP +Random 120 + +Index 25 YFLIP XFLIP +Random 120 + +Index 39 +Random 120 + +Index 39 XFLIP +Random 120 + +Index 39 YFLIP +Random 120 + +Index 39 YFLIP XFLIP +Random 120 + +Index 40 +Random 120 + +Index 40 XFLIP +Random 120 + +Index 40 YFLIP +Random 120 + +Index 40 YFLIP XFLIP +Random 120 + +Index 41 +Random 120 + +Index 41 XFLIP +Random 120 + +Index 41 YFLIP +Random 120 + +Index 41 YFLIP XFLIP +Random 120 + +Index 45 +Random 120 + +Index 45 XFLIP +Random 120 + +Index 45 YFLIP +Random 120 + +Index 45 YFLIP XFLIP +Random 120 + +Index 61 +Random 120 + +Index 61 XFLIP +Random 120 + +Index 61 YFLIP +Random 120 + +Index 61 YFLIP XFLIP +Random 120 + +#Copper + +Index 129 +Random 120 + +Index 129 XFLIP +Random 120 + +Index 129 YFLIP +Random 120 + +Index 129 YFLIP XFLIP +Random 120 + +Index 130 +Random 120 + +Index 130 XFLIP +Random 120 + +Index 130 YFLIP +Random 120 + +Index 130 YFLIP XFLIP +Random 120 + +Index 144 +Random 120 + +Index 144 XFLIP +Random 120 + +Index 144 YFLIP +Random 120 + +Index 144 YFLIP XFLIP +Random 120 + +Index 145 +Random 120 + +Index 145 XFLIP +Random 120 + +Index 145 YFLIP +Random 120 + +Index 145 YFLIP XFLIP +Random 120 + +Index 146 +Random 120 + +Index 146 XFLIP +Random 120 + +Index 146 YFLIP +Random 120 + +Index 146 YFLIP XFLIP +Random 120 + +Index 160 +Random 120 + +Index 160 XFLIP +Random 120 + +Index 160 YFLIP +Random 120 + +Index 160 YFLIP XFLIP +Random 120 + +Index 161 +Random 120 + +Index 161 XFLIP +Random 120 + +Index 161 YFLIP +Random 120 + +Index 161 YFLIP XFLIP +Random 120 + +Index 162 +Random 120 + +Index 162 XFLIP +Random 120 + +Index 162 YFLIP +Random 120 + +Index 162 YFLIP XFLIP +Random 120 + +Index 166 +Random 120 + +Index 166 XFLIP +Random 120 + +Index 166 YFLIP +Random 120 + +Index 166 YFLIP XFLIP +Random 120 + +Index 182 +Random 120 + +Index 182 XFLIP +Random 120 + +Index 182 YFLIP +Random 120 + +Index 182 YFLIP XFLIP +Random 120 diff --git a/data/editor/grass_main.rules b/data/editor/automap/grass_main.rules similarity index 100% rename from data/editor/grass_main.rules rename to data/editor/automap/grass_main.rules diff --git a/data/editor/grass_main_0.7.rules b/data/editor/automap/grass_main_0.7.rules similarity index 100% rename from data/editor/grass_main_0.7.rules rename to data/editor/automap/grass_main_0.7.rules diff --git a/data/editor/jungle_main.rules b/data/editor/automap/jungle_main.rules similarity index 100% rename from data/editor/jungle_main.rules rename to data/editor/automap/jungle_main.rules diff --git a/data/editor/jungle_midground.rules b/data/editor/automap/jungle_midground.rules similarity index 93% rename from data/editor/jungle_midground.rules rename to data/editor/automap/jungle_midground.rules index 30303553c..4dadbafc4 100644 --- a/data/editor/jungle_midground.rules +++ b/data/editor/automap/jungle_midground.rules @@ -1,1468 +1,1468 @@ -[Raw] - -#clear -Index 0 -Pos 0 0 INDEX 211 OR 214 OR 217 OR 218 OR 219 OR 224 - -NewRun - -Index 35 - - -[Small] - -#clear -Index 0 -Pos 0 0 INDEX 211 OR 214 OR 217 OR 218 OR 219 OR 224 - -NewRun - -Index 35 - -#top -Index 200 -Pos 0 -1 EMPTY - -#right -Index 200 ROTATE -Pos 1 0 EMPTY - -#bottom -Index 200 XFLIP YFLIP -Pos 0 1 EMPTY - -#left -Index 200 ROTATE XFLIP YFLIP -Pos -1 0 EMPTY - -#corner top-right -Index 199 -Pos 0 -1 EMPTY -Pos 1 0 EMPTY - -#corner top-left -Index 199 ROTATE XFLIP YFLIP -Pos 0 -1 EMPTY -Pos -1 0 EMPTY - -#corner bottom-left -Index 199 XFLIP YFLIP -Pos 0 1 EMPTY -Pos -1 0 EMPTY - -#corner bottom-right -Index 199 ROTATE -Pos 0 1 EMPTY -Pos 1 0 EMPTY - -#inside corner top-right -Index 215 XFLIP YFLIP -Pos -1 1 EMPTY -Pos -1 0 FULL -Pos 0 1 FULL - -#inside corner top-left -Index 215 ROTATE -Pos 1 1 EMPTY -Pos 1 0 FULL -Pos 0 1 FULL - -#inside corner bottom-left -Index 215 -Pos 1 -1 EMPTY -Pos 1 0 FULL -Pos 0 -1 FULL - -#inside corner bottom-right -Index 215 ROTATE XFLIP YFLIP -Pos -1 -1 EMPTY -Pos -1 0 FULL -Pos 0 -1 FULL - -NewRun - -#corner top-left 3x3 -Index 160 -Pos 0 0 INDEX 199 -Pos 1 -1 EMPTY -Pos 2 -1 EMPTY -Pos -1 1 EMPTY -Pos -1 2 EMPTY -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos 3 1 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 FULL -Pos 0 3 FULL -Pos 1 3 FULL - -NewRun - -#corner top-right 3x3 -Index 160 ROTATE -Pos 0 0 INDEX 199 -Pos -1 -1 EMPTY -Pos -2 -1 EMPTY -Pos 1 1 EMPTY -Pos 1 2 EMPTY -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos 0 1 FULL -Pos -1 1 FULL -Pos -2 1 FULL -Pos -3 1 FULL -Pos 0 2 FULL -Pos -1 2 FULL -Pos -2 2 FULL -Pos 0 3 FULL -Pos -1 3 FULL -Pos -4 0 NOTINDEX 160 -Pos -3 0 NOTINDEX 160 - -NewRun - -#corner bottom-left 3x3 -Index 160 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos 1 1 EMPTY -Pos 2 1 EMPTY -Pos -1 -1 EMPTY -Pos -1 -2 EMPTY -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos 0 -1 FULL -Pos 1 -1 FULL -Pos 2 -1 FULL -Pos 3 -1 FULL -Pos 0 -2 FULL -Pos 1 -2 FULL -Pos 2 -2 FULL -Pos 0 -3 FULL -Pos 1 -3 FULL -Pos 0 -4 NOTINDEX 160 -Pos 0 -3 NOTINDEX 160 - -NewRun - -#corner bottom-right 3x3 -Index 160 XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos -1 1 EMPTY -Pos -2 1 EMPTY -Pos 1 -1 EMPTY -Pos 1 -2 EMPTY -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos 0 -1 FULL -Pos -1 -1 FULL -Pos -2 -1 FULL -Pos -3 -1 FULL -Pos 0 -2 FULL -Pos -1 -2 FULL -Pos -2 -2 FULL -Pos 0 -3 FULL -Pos -1 -3 FULL -Pos -4 0 NOTINDEX 160 -Pos -3 0 NOTINDEX 160 -Pos 0 -4 NOTINDEX 160 -Pos 0 -3 NOTINDEX 160 - -NewRun - -#fill top-left -Index 161 -Pos -1 0 INDEX 160 -Pos 1 0 FULL -Pos 0 -1 EMPTY - -Index 162 -Pos -2 0 INDEX 160 -Pos -1 0 FULL -Pos 0 -1 EMPTY - -Index 176 -Pos 0 -1 INDEX 160 -Pos 0 1 FULL -Pos -1 0 EMPTY - -Index 177 -Pos -1 -1 INDEX 160 -Pos 0 -1 FULL -Pos -1 0 FULL - -Index 192 -Pos 0 -2 INDEX 160 -Pos 0 -1 FULL -Pos -1 0 EMPTY - -#fill top-right -Index 176 ROTATE -Pos 1 0 INDEX 160 -Pos -1 0 FULL -Pos 0 -1 EMPTY - -Index 192 ROTATE -Pos 2 0 INDEX 160 -Pos 1 0 FULL -Pos 0 -1 EMPTY - -Index 161 ROTATE -Pos 0 -1 INDEX 160 -Pos 0 1 FULL -Pos 1 0 EMPTY - -Index 177 ROTATE -Pos 1 -1 INDEX 160 -Pos 0 -1 FULL -Pos 1 0 FULL - -Index 162 ROTATE -Pos 0 -2 INDEX 160 -Pos 0 -1 FULL -Pos 1 0 EMPTY - -#fill bottom-left -Index 176 ROTATE XFLIP YFLIP -Pos -1 0 INDEX 160 -Pos 1 0 FULL -Pos 0 1 EMPTY - -Index 192 ROTATE XFLIP YFLIP -Pos -2 0 INDEX 160 -Pos -1 0 FULL -Pos 0 1 EMPTY - -Index 161 ROTATE XFLIP YFLIP -Pos 0 1 INDEX 160 -Pos 0 -1 FULL -Pos -1 0 EMPTY - -Index 177 ROTATE XFLIP YFLIP -Pos -1 1 INDEX 160 -Pos 0 1 FULL -Pos -1 0 FULL - -Index 162 ROTATE XFLIP YFLIP -Pos 0 2 INDEX 160 -Pos 0 1 FULL -Pos -1 0 EMPTY - -#fill bottom-right -Index 161 XFLIP YFLIP -Pos 1 0 INDEX 160 -Pos -1 0 FULL -Pos 0 1 EMPTY - -Index 162 XFLIP YFLIP -Pos 2 0 INDEX 160 -Pos 1 0 FULL -Pos 0 1 EMPTY - -Index 176 XFLIP YFLIP -Pos 0 1 INDEX 160 -Pos 0 -1 FULL -Pos 1 0 EMPTY - -Index 177 XFLIP YFLIP -Pos 1 1 INDEX 160 -Pos 0 1 FULL -Pos 1 0 FULL - -Index 192 XFLIP YFLIP -Pos 0 2 INDEX 160 -Pos 0 1 FULL -Pos 1 0 EMPTY - -NewRun - -#corner top-right 4x3 -Index 165 -Pos 0 0 INDEX 199 -Pos 1 1 INDEX 199 -Pos -1 -1 EMPTY -Pos -2 -1 EMPTY -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos -3 1 FULL -Pos -2 1 FULL -Pos -1 1 FULL -Pos 0 1 FULL -Pos -1 2 FULL -Pos 0 2 FULL -Pos 1 2 FULL -Pos 2 2 EMPTY -Pos 0 3 FULL -Pos 1 3 FULL -Pos -2 0 INDEX 200 -Pos 1 2 INDEX 200 - -NewRun - -#corner top-left 4x3 -Index 165 XFLIP -Pos 0 0 INDEX 199 -Pos -1 1 INDEX 199 -Pos 1 -1 EMPTY -Pos 2 -1 EMPTY -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos 3 1 FULL -Pos 2 1 FULL -Pos 1 1 FULL -Pos 0 1 FULL -Pos 1 2 FULL -Pos 0 2 FULL -Pos -1 2 FULL -Pos -2 2 EMPTY -Pos 0 3 FULL -Pos -1 3 FULL -Pos 2 0 INDEX 200 -Pos -1 2 INDEX 200 -Pos 4 0 NOTINDEX 165 -Pos 3 0 NOTINDEX 165 - -NewRun - -#corner bottom-left 4x3 -Index 165 XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos -1 -1 INDEX 199 -Pos 1 1 EMPTY -Pos 2 1 EMPTY -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos 3 -1 FULL -Pos 2 -1 FULL -Pos 1 -1 FULL -Pos 0 -1 FULL -Pos 1 -2 FULL -Pos 0 -2 FULL -Pos -1 -2 FULL -Pos -2 -2 EMPTY -Pos 0 -3 FULL -Pos -1 -3 FULL -Pos 2 0 INDEX 200 -Pos -1 -2 INDEX 200 -Pos 0 -4 NOTINDEX 165 - -NewRun - -#corner bottom-right 4x3 -Index 165 YFLIP -Pos 0 0 INDEX 199 -Pos 1 -1 INDEX 199 -Pos -1 1 EMPTY -Pos -2 1 EMPTY -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos -3 -1 FULL -Pos -2 -1 FULL -Pos -1 -1 FULL -Pos 0 -1 FULL -Pos -1 -2 FULL -Pos 0 -2 FULL -Pos 1 -2 FULL -Pos 2 -2 EMPTY -Pos 0 -3 FULL -Pos 1 -3 FULL -Pos -2 0 INDEX 200 -Pos 1 -2 INDEX 200 -Pos -4 0 NOTINDEX 165 -Pos -3 0 NOTINDEX 165 -Pos 0 -4 NOTINDEX 165 - -NewRun - -#fill top-right 4x3 -Index 164 -Pos 1 0 INDEX 165 -Pos 0 -1 EMPTY -Pos -1 0 FULL - -Index 163 -Pos 2 0 INDEX 165 -Pos 0 -1 EMPTY -Pos -1 0 FULL - -Index 181 -Pos 0 -1 INDEX 165 -Pos 1 -1 EMPTY -Pos 1 0 FULL - -Index 182 -Pos -1 -1 INDEX 165 -Pos -1 0 FULL -Pos 1 0 EMPTY - -Index 198 -Pos -1 -2 INDEX 165 -Pos 0 -1 FULL -Pos 1 0 EMPTY - -#fill top-left 4x3 -Index 164 XFLIP -Pos -1 0 INDEX 165 -Pos 0 -1 EMPTY -Pos 1 0 FULL - -Index 163 XFLIP -Pos -2 0 INDEX 165 -Pos 0 -1 EMPTY -Pos 1 0 FULL - -Index 181 XFLIP -Pos 0 -1 INDEX 165 -Pos -1 -1 EMPTY -Pos -1 0 FULL - -Index 182 XFLIP -Pos 1 -1 INDEX 165 -Pos 1 0 FULL -Pos -1 0 EMPTY - -Index 198 XFLIP -Pos 1 -2 INDEX 165 -Pos 0 -1 FULL -Pos -1 0 EMPTY - -#fill bottom-left 4x3 -Index 164 XFLIP YFLIP -Pos -1 0 INDEX 165 -Pos 0 1 EMPTY -Pos 1 0 FULL - -Index 163 XFLIP YFLIP -Pos -2 0 INDEX 165 -Pos 0 1 EMPTY -Pos 1 0 FULL - -Index 181 XFLIP YFLIP -Pos 0 1 INDEX 165 -Pos -1 1 EMPTY -Pos -1 0 FULL - -Index 182 XFLIP YFLIP -Pos 1 1 INDEX 165 -Pos 1 0 FULL -Pos -1 0 EMPTY - -Index 198 XFLIP YFLIP -Pos 1 2 INDEX 165 -Pos 0 1 FULL -Pos -1 0 EMPTY - -#fill bottom-right 4x3 -Index 164 YFLIP -Pos 1 0 INDEX 165 -Pos 0 1 EMPTY -Pos -1 0 FULL - -Index 163 YFLIP -Pos 2 0 INDEX 165 -Pos 0 1 EMPTY -Pos -1 0 FULL - -Index 181 YFLIP -Pos 0 1 INDEX 165 -Pos 1 1 EMPTY -Pos 1 0 FULL - -Index 182 YFLIP -Pos -1 1 INDEX 165 -Pos -1 0 FULL -Pos 1 0 EMPTY - -Index 198 YFLIP -Pos -1 2 INDEX 165 -Pos 0 1 FULL -Pos 1 0 EMPTY - -NewRun - -#corner top-left 3x4 -Index 165 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos 1 -1 INDEX 199 -Pos 2 -2 EMPTY -Pos 2 -1 FULL -Pos 3 -1 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos -1 1 EMPTY -Pos 0 1 FULL -Pos 1 1 FULL -Pos 2 1 FULL -Pos -1 2 EMPTY -Pos 0 2 FULL -Pos 1 2 FULL -Pos 0 3 FULL -Pos 1 3 FULL -Pos 0 2 INDEX 200 -Pos 2 -1 INDEX 200 - -NewRun - -#corner top-right 3x4 -Index 165 ROTATE XFLIP -Pos 0 0 INDEX 199 -Pos -1 -1 INDEX 199 -Pos -2 -2 EMPTY -Pos -2 -1 FULL -Pos -3 -1 FULL -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos 1 1 EMPTY -Pos 0 1 FULL -Pos -1 1 FULL -Pos -2 1 FULL -Pos 1 2 EMPTY -Pos 0 2 FULL -Pos -1 2 FULL -Pos 0 3 FULL -Pos -1 3 FULL -Pos 0 2 INDEX 200 -Pos -2 -1 INDEX 200 - -NewRun - -#corner bottom-right 3x4 -Index 165 ROTATE -Pos 0 0 INDEX 199 -Pos -1 1 INDEX 199 -Pos -2 2 EMPTY -Pos -2 1 FULL -Pos -3 1 FULL -Pos -1 0 FULL -Pos -2 0 FULL -Pos -3 0 FULL -Pos 1 -1 EMPTY -Pos 0 -1 FULL -Pos -1 -1 FULL -Pos -2 -1 FULL -Pos 1 -2 EMPTY -Pos 0 -2 FULL -Pos -1 -2 FULL -Pos 0 -3 FULL -Pos -1 -3 FULL -Pos 0 -2 INDEX 200 -Pos -2 1 INDEX 200 -Pos 0 -4 NOTINDEX 165 -Pos 0 -3 NOTINDEX 165 - -NewRun - -#corner bottom-left 3x4 -Index 165 ROTATE YFLIP -Pos 0 0 INDEX 199 -Pos 1 1 INDEX 199 -Pos 2 2 EMPTY -Pos 2 1 FULL -Pos 3 1 FULL -Pos 1 0 FULL -Pos 2 0 FULL -Pos 3 0 FULL -Pos -1 -1 EMPTY -Pos 0 -1 FULL -Pos 1 -1 FULL -Pos 2 -1 FULL -Pos -1 -2 EMPTY -Pos 0 -2 FULL -Pos 1 -2 FULL -Pos 0 -3 FULL -Pos 1 -3 FULL -Pos 0 -2 INDEX 200 -Pos 2 1 INDEX 200 -Pos 0 -4 NOTINDEX 165 -Pos 0 -3 NOTINDEX 165 - -NewRun - -#fill top-left 3x4 -Index 164 ROTATE XFLIP YFLIP -Pos 0 -1 INDEX 165 -Pos -1 0 EMPTY -Pos 0 1 FULL - -Index 163 ROTATE XFLIP YFLIP -Pos 0 -2 INDEX 165 -Pos -1 0 EMPTY -Pos 0 1 FULL - -Index 181 ROTATE XFLIP YFLIP -Pos -1 0 INDEX 165 -Pos -1 -1 EMPTY -Pos 0 -1 FULL - -Index 182 ROTATE XFLIP YFLIP -Pos -1 1 INDEX 165 -Pos 0 1 FULL -Pos 0 -1 EMPTY - -Index 198 ROTATE XFLIP YFLIP -Pos -2 1 INDEX 165 -Pos -1 0 FULL -Pos 0 -1 EMPTY - -#fill top-right 3x4 -Index 164 ROTATE XFLIP -Pos 0 -1 INDEX 165 -Pos 1 0 EMPTY -Pos 0 1 FULL - -Index 163 ROTATE XFLIP -Pos 0 -2 INDEX 165 -Pos 1 0 EMPTY -Pos 0 1 FULL - -Index 181 ROTATE XFLIP -Pos 1 0 INDEX 165 -Pos 1 -1 EMPTY -Pos 0 -1 FULL - -Index 182 ROTATE XFLIP -Pos 1 1 INDEX 165 -Pos 0 1 FULL -Pos 0 -1 EMPTY - -Index 198 ROTATE XFLIP -Pos 2 1 INDEX 165 -Pos 1 0 FULL -Pos 0 -1 EMPTY - -#fill bottom-right 3x4 -Index 164 ROTATE -Pos 0 1 INDEX 165 -Pos 1 0 EMPTY -Pos 0 -1 FULL - -Index 163 ROTATE -Pos 0 2 INDEX 165 -Pos 1 0 EMPTY -Pos 0 -1 FULL - -Index 181 ROTATE -Pos 1 0 INDEX 165 -Pos 1 1 EMPTY -Pos 0 1 FULL - -Index 182 ROTATE -Pos 1 -1 INDEX 165 -Pos 0 -1 FULL -Pos 0 1 EMPTY - -Index 198 ROTATE -Pos 2 -1 INDEX 165 -Pos 1 0 FULL -Pos 0 1 EMPTY - -#fill bottom-left 3x4 -Index 164 ROTATE YFLIP -Pos 0 1 INDEX 165 -Pos -1 0 EMPTY -Pos 0 -1 FULL - -Index 163 ROTATE YFLIP -Pos 0 2 INDEX 165 -Pos -1 0 EMPTY -Pos 0 -1 FULL - -Index 181 ROTATE YFLIP -Pos -1 0 INDEX 165 -Pos -1 1 EMPTY -Pos 0 1 FULL - -Index 182 ROTATE YFLIP -Pos -1 -1 INDEX 165 -Pos 0 -1 FULL -Pos 0 1 EMPTY - -Index 198 ROTATE YFLIP -Pos -2 -1 INDEX 165 -Pos -1 0 FULL -Pos 0 1 EMPTY - -NewRun - -#dead end top -Index 227 XFLIP YFLIP -Pos -1 0 INDEX 215 -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 215 -Pos -1 1 INDEX 200 -Pos 0 1 EMPTY -Pos 1 1 INDEX 200 - -#dead end right -Index 227 ROTATE XFLIP YFLIP -Pos 0 -1 INDEX 215 -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 215 -Pos -1 -1 INDEX 200 -Pos -1 0 EMPTY -Pos -1 1 INDEX 200 - -#dead end bottom -Index 227 -Pos -1 0 INDEX 215 -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 215 -Pos -1 -1 INDEX 200 -Pos 0 -1 EMPTY -Pos 1 -1 INDEX 200 - -#dead end left -Index 227 ROTATE -Pos 0 -1 INDEX 215 -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 215 -Pos 1 -1 INDEX 200 -Pos 1 0 EMPTY -Pos 1 1 INDEX 200 - -NewRun - -#fill dead end top -Index 226 XFLIP YFLIP -Pos -1 0 INDEX 227 -Pos 0 1 INDEX 200 -Pos 1 0 FULL -Pos 0 -1 FULL - -Index 228 XFLIP YFLIP -Pos 1 0 INDEX 227 -Pos 0 1 INDEX 200 -Pos -1 0 FULL -Pos 0 1 FULL - -Index 210 XFLIP YFLIP -Pos -1 -1 INDEX 227 -Pos -1 0 EMPTY - -Index 211 XFLIP YFLIP -Pos 0 0 EMPTY -Pos 0 -1 INDEX 227 -Pos 0 1 EMPTY - -Index 212 XFLIP YFLIP -Pos 1 -1 INDEX 227 -Pos 1 0 EMPTY - -#fill dead end right -Index 226 ROTATE XFLIP YFLIP -Pos 0 -1 INDEX 227 -Pos -1 0 INDEX 200 -Pos 0 1 FULL -Pos 1 0 FULL - -Index 228 ROTATE XFLIP YFLIP -Pos 0 1 INDEX 227 -Pos -1 0 INDEX 200 -Pos 0 -1 FULL -Pos 1 0 FULL - -Index 210 ROTATE XFLIP YFLIP -Pos 1 -1 INDEX 227 -Pos 0 -1 EMPTY - -Index 211 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 0 INDEX 227 -Pos -1 0 EMPTY - -Index 212 ROTATE XFLIP YFLIP -Pos 1 1 INDEX 227 -Pos 0 1 EMPTY - -#fill dead end bottom -Index 226 -Pos 1 0 INDEX 227 -Pos 0 -1 INDEX 200 -Pos -1 0 FULL -Pos 0 1 FULL - -Index 228 -Pos -1 0 INDEX 227 -Pos 0 -1 INDEX 200 -Pos 1 0 FULL -Pos 0 1 FULL - -Index 210 -Pos 1 1 INDEX 227 -Pos 1 0 EMPTY - -Index 211 -Pos 0 0 EMPTY -Pos 0 1 INDEX 227 -Pos 0 -1 EMPTY - -Index 212 -Pos -1 1 INDEX 227 -Pos -1 0 EMPTY - -#fill dead end left -Index 226 ROTATE -Pos 0 1 INDEX 227 -Pos 1 0 INDEX 200 -Pos 0 -1 FULL -Pos -1 0 FULL - -Index 228 ROTATE -Pos 0 -1 INDEX 227 -Pos 1 0 INDEX 200 -Pos 0 1 FULL -Pos -1 0 FULL - -Index 210 ROTATE -Pos -1 1 INDEX 227 -Pos 0 1 EMPTY - -Index 211 ROTATE -Pos 0 0 EMPTY -Pos -1 0 INDEX 227 -Pos 1 0 EMPTY - -Index 212 ROTATE -Pos -1 -1 INDEX 227 -Pos 0 -1 EMPTY - -NewRun - -#s-curve bottom-left 1 -Index 209 XFLIP YFLIP -Pos 0 0 INDEX 215 -Pos 0 -1 INDEX 200 -Pos 1 0 INDEX 199 -Pos 1 -1 EMPTY - -#s-curve bottom-left 1 alt -Index 229 -Pos 0 0 INDEX 215 -Pos 0 -1 INDEX 200 -Pos 1 0 INDEX 199 -Pos 1 -1 EMPTY -Random 2 - -NewRun - -#s-curve bottom-left 2 -Index 209 ROTATE YFLIP -Pos 0 0 INDEX 215 -Pos 1 0 INDEX 200 -Pos 0 -1 INDEX 199 -Pos 1 -1 EMPTY - -#s-curve bottom-left 2 alt -Index 229 ROTATE XFLIP -Pos 0 0 INDEX 215 -Pos 1 0 INDEX 200 -Pos 0 -1 INDEX 199 -Pos 1 -1 EMPTY -Random 2 - -NewRun - -#s-curve top-left 1 -Index 209 XFLIP -Pos 0 0 INDEX 215 -Pos 0 1 INDEX 200 -Pos 1 0 INDEX 199 -Pos 1 1 EMPTY -Pos 0 2 NOTINDEX 209 OR 229 - -#s-curve top-left 1 alt -Index 229 YFLIP -Pos 0 0 INDEX 215 -Pos 0 1 INDEX 200 -Pos 1 0 INDEX 199 -Pos 1 1 EMPTY -Pos 0 2 NOTINDEX 209 OR 229 -Random 2 - -NewRun - -#s-curve top-left 2 -Index 209 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 215 -Pos 1 0 INDEX 200 -Pos 0 1 INDEX 199 -Pos 1 1 EMPTY - -#s-curve top-left 2 alt -Index 229 ROTATE -Pos 0 0 INDEX 215 -Pos 1 0 INDEX 200 -Pos 0 1 INDEX 199 -Pos 1 1 EMPTY -Random 2 - -NewRun - -#s-curve top-right 1 -Index 209 -Pos 0 0 INDEX 215 -Pos 0 1 INDEX 200 -Pos -1 0 INDEX 199 -Pos -1 1 EMPTY - -#s-curve top-right 1 alt -Index 229 XFLIP YFLIP -Pos 0 0 INDEX 215 -Pos 0 1 INDEX 200 -Pos -1 0 INDEX 199 -Pos -1 1 EMPTY -Random 2 - -NewRun - -#s-curve top-right 2 -Index 209 ROTATE XFLIP -Pos 0 0 INDEX 215 -Pos -1 0 INDEX 200 -Pos 0 1 INDEX 199 -Pos -1 1 EMPTY -Pos -2 0 NOTINDEX 209 OR 229 - -#s-curve top-right 2 alt -Index 229 ROTATE YFLIP -Pos 0 0 INDEX 215 -Pos -1 0 INDEX 200 -Pos 0 1 INDEX 199 -Pos -1 1 EMPTY -Pos -2 0 NOTINDEX 209 OR 229 -Random 2 - -NewRun - -#s-curve bottom-right 1 -Index 209 YFLIP -Pos 0 0 INDEX 215 -Pos 0 -1 INDEX 200 -Pos -1 0 INDEX 199 -Pos -1 -1 EMPTY -Pos 0 -2 NOTINDEX 209 OR 229 - -#s-curve bottom-right 1 alt -Index 229 XFLIP -Pos 0 0 INDEX 215 -Pos 0 -1 INDEX 200 -Pos -1 0 INDEX 199 -Pos -1 -1 EMPTY -Pos 0 -2 NOTINDEX 209 OR 229 -Random 2 - -NewRun - -#s-curve bottom-right 2 -Index 209 ROTATE -Pos 0 0 INDEX 215 -Pos -1 0 INDEX 200 -Pos 0 -1 INDEX 199 -Pos -1 -1 EMPTY -Pos -2 0 NOTINDEX 209 OR 229 - -#s-curve bottom-right 2 alt -Index 229 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 215 -Pos -1 0 INDEX 200 -Pos 0 -1 INDEX 199 -Pos -1 -1 EMPTY -Pos -2 0 NOTINDEX 209 OR 229 -Random 2 - -NewRun - -#fill s-curve bottom-left 1 -Index 225 XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 209 -Pos 1 0 EMPTY - -Index 208 XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos -1 0 INDEX 209 -Pos 0 -1 EMPTY - -Index 224 XFLIP YFLIP -Pos 0 0 EMPTY -Pos -1 1 INDEX 209 -Pos -1 0 INDEX 200 - -#fill s-curve bottom-left 1 alt -Index 213 -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 229 -Pos 1 0 EMPTY - -Index 230 -Pos 0 0 INDEX 199 -Pos -1 0 INDEX 229 -Pos 0 -1 EMPTY - -Index 214 -Pos 0 0 EMPTY -Pos -1 1 INDEX 229 -Pos -1 0 INDEX 200 - -#fill s-curve bottom-left 2 -Index 225 ROTATE YFLIP -Pos 0 0 INDEX 200 -Pos -1 0 INDEX 209 -Pos 0 -1 EMPTY - -Index 208 ROTATE YFLIP -Pos 0 0 INDEX 199 -Pos 0 1 INDEX 209 -Pos 1 0 EMPTY - -Index 224 ROTATE YFLIP -Pos 0 0 EMPTY -Pos -1 1 INDEX 209 -Pos 0 1 INDEX 200 - -#fill s-curve bottom-left 2 alt -Index 213 ROTATE XFLIP -Pos 0 0 INDEX 200 -Pos -1 0 INDEX 229 -Pos 0 -1 EMPTY - -Index 230 ROTATE XFLIP -Pos 0 0 INDEX 199 -Pos 0 1 INDEX 229 -Pos 1 0 EMPTY - -Index 214 ROTATE XFLIP -Pos 0 0 EMPTY -Pos -1 1 INDEX 229 -Pos 0 1 INDEX 200 - -#fill s-curve top-left 1 -Index 225 XFLIP -Pos 0 0 INDEX 200 -Pos 0 -1 INDEX 209 -Pos 1 0 EMPTY - -Index 208 XFLIP -Pos 0 0 INDEX 199 -Pos -1 0 INDEX 209 -Pos 0 1 EMPTY - -Index 224 XFLIP -Pos 0 0 EMPTY -Pos -1 -1 INDEX 209 -Pos -1 0 INDEX 200 - -#fill s-curve top-left 1 alt -Index 213 YFLIP -Pos 0 0 INDEX 200 -Pos 0 -1 INDEX 229 -Pos 1 0 EMPTY - -Index 230 YFLIP -Pos 0 0 INDEX 199 -Pos -1 0 INDEX 229 -Pos 0 1 EMPTY - -Index 214 YFLIP -Pos 0 0 EMPTY -Pos -1 -1 INDEX 229 -Pos -1 0 INDEX 200 - -#fill s-curve top-left 2 -Index 225 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos -1 0 INDEX 209 -Pos 0 1 EMPTY - -Index 208 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos 0 -1 INDEX 209 -Pos 1 0 EMPTY - -Index 224 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos -1 -1 INDEX 209 -Pos 0 -1 INDEX 200 - -#fill s-curve top-left 2 alt -Index 213 ROTATE -Pos 0 0 INDEX 200 -Pos -1 0 INDEX 229 -Pos 0 1 EMPTY - -Index 230 ROTATE -Pos 0 0 INDEX 199 -Pos 0 -1 INDEX 229 -Pos 1 0 EMPTY - -Index 214 ROTATE -Pos 0 0 EMPTY -Pos -1 -1 INDEX 229 -Pos 0 -1 INDEX 200 - -#fill s-curve top-right 1 -Index 225 -Pos 0 0 INDEX 200 -Pos 0 -1 INDEX 209 -Pos -1 0 EMPTY - -Index 208 -Pos 0 0 INDEX 199 -Pos 1 0 INDEX 209 -Pos 0 1 EMPTY - -Index 224 -Pos 0 0 EMPTY -Pos 1 -1 INDEX 209 -Pos 1 0 INDEX 200 - -#fill s-curve top-right 1 alt -Index 213 XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos 0 -1 INDEX 229 -Pos -1 0 EMPTY - -Index 230 XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos 1 0 INDEX 229 -Pos 0 1 EMPTY - -Index 214 XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 -1 INDEX 229 -Pos 1 0 INDEX 200 - -#fill s-curve top-right 2 -Index 225 ROTATE XFLIP -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 209 -Pos 0 1 EMPTY - -Index 208 ROTATE XFLIP -Pos 0 0 INDEX 199 -Pos 0 -1 INDEX 209 -Pos -1 0 EMPTY - -Index 224 ROTATE XFLIP -Pos 0 0 EMPTY -Pos 1 -1 INDEX 209 -Pos 0 -1 INDEX 200 - -#fill s-curve top-right 2 alt -Index 213 ROTATE YFLIP -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 229 -Pos 0 1 EMPTY - -Index 230 ROTATE YFLIP -Pos 0 0 INDEX 199 -Pos 0 -1 INDEX 229 -Pos -1 0 EMPTY - -Index 214 ROTATE YFLIP -Pos 0 0 EMPTY -Pos 1 -1 INDEX 229 -Pos 0 -1 INDEX 200 - -#fill s-curve bottom-right 1 -Index 225 YFLIP -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 209 -Pos -1 0 EMPTY - -Index 208 YFLIP -Pos 0 0 INDEX 199 -Pos 1 0 INDEX 209 -Pos 0 -1 EMPTY - -Index 224 YFLIP -Pos 0 0 EMPTY -Pos 1 1 INDEX 209 -Pos 1 0 INDEX 200 - -#fill s-curve bottom-right 1 alt -Index 213 XFLIP -Pos 0 0 INDEX 200 -Pos 0 1 INDEX 229 -Pos -1 0 EMPTY - -Index 230 XFLIP -Pos 0 0 INDEX 199 -Pos 1 0 INDEX 229 -Pos 0 -1 EMPTY - -Index 214 XFLIP -Pos 0 0 EMPTY -Pos 1 1 INDEX 229 -Pos 1 0 INDEX 200 - -#fill s-curve bottom-right 2 -Index 225 ROTATE -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 209 -Pos 0 -1 EMPTY - -Index 208 ROTATE -Pos 0 0 INDEX 199 -Pos 0 1 INDEX 209 -Pos -1 0 EMPTY - -Index 224 ROTATE -Pos 0 0 EMPTY -Pos 1 1 INDEX 209 -Pos 0 1 INDEX 200 - -#fill s-curve bottom-right 2 alt -Index 213 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos 1 0 INDEX 229 -Pos 0 -1 EMPTY - -Index 230 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 199 -Pos 0 1 INDEX 229 -Pos -1 0 EMPTY - -Index 214 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 1 INDEX 229 -Pos 0 1 INDEX 200 - - -NewRun - -#top long -Index 233 -Pos 0 0 INDEX 200 -Pos 0 -1 EMPTY -Pos 0 -2 EMPTY -Pos -1 0 FULL -Pos 1 0 INDEX 200 -Pos 2 0 INDEX 200 -Random 5 - -#right long -Index 233 ROTATE -Pos 0 0 INDEX 200 -Pos 1 0 EMPTY -Pos 2 0 EMPTY -Pos 0 -1 FULL -Pos 0 1 INDEX 200 -Pos 0 2 INDEX 200 -Random 5 - -#bottom long -Index 233 XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos 0 1 EMPTY -Pos 0 2 EMPTY -Pos 1 0 FULL -Pos -1 0 INDEX 200 -Pos -2 0 INDEX 200 -Random 5 - -#left long -Index 233 ROTATE XFLIP YFLIP -Pos 0 0 INDEX 200 -Pos -1 0 EMPTY -Pos -2 0 EMPTY -Pos 0 1 FULL -Pos 0 -1 INDEX 200 -Pos 0 -2 INDEX 200 -Random 5 - - -NewRun - -#top overlap -Index 200 -Pos 0 0 233 -Pos 0 -1 EMPTY -Pos 0 0 233 -Pos -1 0 INDEX 233 - -#top overlap 2 -Index 200 -Pos 0 0 233 -Pos 0 -1 EMPTY -Pos 0 0 INDEX 233 -Pos -2 0 INDEX 233 - -#right overlap -Index 200 ROTATE -Pos 0 0 233 -Pos 1 0 EMPTY -Pos 0 0 INDEX 233 -Pos 0 -1 INDEX 233 - -#right overlap 2 -Index 200 ROTATE -Pos 0 0 233 -Pos 1 0 EMPTY -Pos 0 0 INDEX 233 -Pos 0 -2 INDEX 233 - -#bottom overlap -Index 200 XFLIP YFLIP -Pos 0 0 233 -Pos 0 1 EMPTY -Pos 0 0 INDEX 233 -Pos 1 0 INDEX 233 - -#bottom overlap 2 -Index 200 XFLIP YFLIP -Pos 0 0 233 -Pos 0 1 EMPTY -Pos 0 0 INDEX 233 -Pos 2 0 INDEX 233 - -#left overlap -Index 200 ROTATE XFLIP YFLIP -Pos 0 0 233 -Pos -1 0 EMPTY -Pos 0 0 INDEX 233 -Pos 0 1 INDEX 233 - -#left overlap 2 -Index 200 ROTATE XFLIP YFLIP -Pos 0 0 233 -Pos -1 0 EMPTY -Pos 0 0 INDEX 233 -Pos 0 2 INDEX 233 - -NewRun - -#fill top long 1 -Index 234 -Pos -1 0 INDEX 233 -Pos 0 -1 EMPTY - -#fill top long 2 -Index 235 -Pos -2 0 INDEX 233 -Pos -1 0 INDEX 200 -Pos 0 -1 EMPTY -Pos 1 0 FULL - -#fill right long 1 -Index 234 ROTATE -Pos 0 -1 INDEX 233 -Pos 1 0 EMPTY - -#fill right long 2 -Index 235 ROTATE -Pos 0 -2 INDEX 233 -Pos 0 -1 INDEX 200 -Pos 1 0 EMPTY -Pos 0 1 FULL - -#fill bottom long 1 -Index 234 XFLIP YFLIP -Pos 1 0 INDEX 233 -Pos 0 1 EMPTY - -#fill bottom long 2 -Index 235 XFLIP YFLIP -Pos 2 0 INDEX 233 -Pos 1 0 INDEX 200 -Pos 0 1 EMPTY -Pos -1 0 FULL - -#fill left long 1 -Index 234 ROTATE XFLIP YFLIP -Pos 0 1 INDEX 233 -Pos -1 0 EMPTY - -#fill left long 2 -Index 235 ROTATE XFLIP YFLIP -Pos 0 2 INDEX 233 -Pos 0 1 INDEX 200 -Pos -1 0 EMPTY -Pos 0 -1 FULL - -NewRun - -#fill top long 3 -Index 217 -Pos 0 0 EMPTY -Pos 0 1 INDEX 233 -Pos 0 2 FULL - -#fill top long 4 -Index 218 -Pos 0 0 EMPTY -Pos 0 1 INDEX 234 -Pos 0 2 FULL - -#fill top long 5 -Index 219 -Pos 0 0 EMPTY -Pos 0 1 INDEX 235 -Pos 0 2 FULL - -#fill right long 3 -Index 217 ROTATE -Pos 0 0 EMPTY -Pos -1 0 INDEX 233 -Pos -2 0 FULL - -#fill right long 4 -Index 218 ROTATE -Pos 0 0 EMPTY -Pos -1 0 INDEX 234 -Pos -2 0 FULL - -#fill right long 5 -Index 219 ROTATE -Pos 0 0 EMPTY -Pos -1 0 INDEX 235 -Pos -2 0 FULL - -#fill bottom long 3 -Index 217 XFLIP YFLIP -Pos 0 0 EMPTY -Pos 0 -1 INDEX 233 -Pos 0 -2 FULL - -#fill bottom long 4 -Index 218 XFLIP YFLIP -Pos 0 0 EMPTY -Pos 0 -1 INDEX 234 -Pos 0 -2 FULL - -#fill bottom long 5 -Index 219 XFLIP YFLIP -Pos 0 0 EMPTY -Pos 0 -1 INDEX 235 -Pos 0 -2 FULL - -#fill left long 3 -Index 217 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 0 INDEX 233 -Pos 2 0 FULL - -#fill left long 4 -Index 218 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 0 INDEX 234 -Pos 2 0 FULL - -#fill left long 5 -Index 219 ROTATE XFLIP YFLIP -Pos 0 0 EMPTY -Pos 1 0 INDEX 235 -Pos 2 0 FULL +[Raw] + +#clear +Index 0 +Pos 0 0 INDEX 211 OR 214 OR 217 OR 218 OR 219 OR 224 + +NewRun + +Index 35 + + +[Small] + +#clear +Index 0 +Pos 0 0 INDEX 211 OR 214 OR 217 OR 218 OR 219 OR 224 + +NewRun + +Index 35 + +#top +Index 200 +Pos 0 -1 EMPTY + +#right +Index 200 ROTATE +Pos 1 0 EMPTY + +#bottom +Index 200 XFLIP YFLIP +Pos 0 1 EMPTY + +#left +Index 200 ROTATE XFLIP YFLIP +Pos -1 0 EMPTY + +#corner top-right +Index 199 +Pos 0 -1 EMPTY +Pos 1 0 EMPTY + +#corner top-left +Index 199 ROTATE XFLIP YFLIP +Pos 0 -1 EMPTY +Pos -1 0 EMPTY + +#corner bottom-left +Index 199 XFLIP YFLIP +Pos 0 1 EMPTY +Pos -1 0 EMPTY + +#corner bottom-right +Index 199 ROTATE +Pos 0 1 EMPTY +Pos 1 0 EMPTY + +#inside corner top-right +Index 215 XFLIP YFLIP +Pos -1 1 EMPTY +Pos -1 0 FULL +Pos 0 1 FULL + +#inside corner top-left +Index 215 ROTATE +Pos 1 1 EMPTY +Pos 1 0 FULL +Pos 0 1 FULL + +#inside corner bottom-left +Index 215 +Pos 1 -1 EMPTY +Pos 1 0 FULL +Pos 0 -1 FULL + +#inside corner bottom-right +Index 215 ROTATE XFLIP YFLIP +Pos -1 -1 EMPTY +Pos -1 0 FULL +Pos 0 -1 FULL + +NewRun + +#corner top-left 3x3 +Index 160 +Pos 0 0 INDEX 199 +Pos 1 -1 EMPTY +Pos 2 -1 EMPTY +Pos -1 1 EMPTY +Pos -1 2 EMPTY +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos 3 1 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 FULL +Pos 0 3 FULL +Pos 1 3 FULL + +NewRun + +#corner top-right 3x3 +Index 160 ROTATE +Pos 0 0 INDEX 199 +Pos -1 -1 EMPTY +Pos -2 -1 EMPTY +Pos 1 1 EMPTY +Pos 1 2 EMPTY +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos 0 1 FULL +Pos -1 1 FULL +Pos -2 1 FULL +Pos -3 1 FULL +Pos 0 2 FULL +Pos -1 2 FULL +Pos -2 2 FULL +Pos 0 3 FULL +Pos -1 3 FULL +Pos -4 0 NOTINDEX 160 +Pos -3 0 NOTINDEX 160 + +NewRun + +#corner bottom-left 3x3 +Index 160 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos 1 1 EMPTY +Pos 2 1 EMPTY +Pos -1 -1 EMPTY +Pos -1 -2 EMPTY +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos 0 -1 FULL +Pos 1 -1 FULL +Pos 2 -1 FULL +Pos 3 -1 FULL +Pos 0 -2 FULL +Pos 1 -2 FULL +Pos 2 -2 FULL +Pos 0 -3 FULL +Pos 1 -3 FULL +Pos 0 -4 NOTINDEX 160 +Pos 0 -3 NOTINDEX 160 + +NewRun + +#corner bottom-right 3x3 +Index 160 XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos -1 1 EMPTY +Pos -2 1 EMPTY +Pos 1 -1 EMPTY +Pos 1 -2 EMPTY +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos 0 -1 FULL +Pos -1 -1 FULL +Pos -2 -1 FULL +Pos -3 -1 FULL +Pos 0 -2 FULL +Pos -1 -2 FULL +Pos -2 -2 FULL +Pos 0 -3 FULL +Pos -1 -3 FULL +Pos -4 0 NOTINDEX 160 +Pos -3 0 NOTINDEX 160 +Pos 0 -4 NOTINDEX 160 +Pos 0 -3 NOTINDEX 160 + +NewRun + +#fill top-left +Index 161 +Pos -1 0 INDEX 160 +Pos 1 0 FULL +Pos 0 -1 EMPTY + +Index 162 +Pos -2 0 INDEX 160 +Pos -1 0 FULL +Pos 0 -1 EMPTY + +Index 176 +Pos 0 -1 INDEX 160 +Pos 0 1 FULL +Pos -1 0 EMPTY + +Index 177 +Pos -1 -1 INDEX 160 +Pos 0 -1 FULL +Pos -1 0 FULL + +Index 192 +Pos 0 -2 INDEX 160 +Pos 0 -1 FULL +Pos -1 0 EMPTY + +#fill top-right +Index 176 ROTATE +Pos 1 0 INDEX 160 +Pos -1 0 FULL +Pos 0 -1 EMPTY + +Index 192 ROTATE +Pos 2 0 INDEX 160 +Pos 1 0 FULL +Pos 0 -1 EMPTY + +Index 161 ROTATE +Pos 0 -1 INDEX 160 +Pos 0 1 FULL +Pos 1 0 EMPTY + +Index 177 ROTATE +Pos 1 -1 INDEX 160 +Pos 0 -1 FULL +Pos 1 0 FULL + +Index 162 ROTATE +Pos 0 -2 INDEX 160 +Pos 0 -1 FULL +Pos 1 0 EMPTY + +#fill bottom-left +Index 176 ROTATE XFLIP YFLIP +Pos -1 0 INDEX 160 +Pos 1 0 FULL +Pos 0 1 EMPTY + +Index 192 ROTATE XFLIP YFLIP +Pos -2 0 INDEX 160 +Pos -1 0 FULL +Pos 0 1 EMPTY + +Index 161 ROTATE XFLIP YFLIP +Pos 0 1 INDEX 160 +Pos 0 -1 FULL +Pos -1 0 EMPTY + +Index 177 ROTATE XFLIP YFLIP +Pos -1 1 INDEX 160 +Pos 0 1 FULL +Pos -1 0 FULL + +Index 162 ROTATE XFLIP YFLIP +Pos 0 2 INDEX 160 +Pos 0 1 FULL +Pos -1 0 EMPTY + +#fill bottom-right +Index 161 XFLIP YFLIP +Pos 1 0 INDEX 160 +Pos -1 0 FULL +Pos 0 1 EMPTY + +Index 162 XFLIP YFLIP +Pos 2 0 INDEX 160 +Pos 1 0 FULL +Pos 0 1 EMPTY + +Index 176 XFLIP YFLIP +Pos 0 1 INDEX 160 +Pos 0 -1 FULL +Pos 1 0 EMPTY + +Index 177 XFLIP YFLIP +Pos 1 1 INDEX 160 +Pos 0 1 FULL +Pos 1 0 FULL + +Index 192 XFLIP YFLIP +Pos 0 2 INDEX 160 +Pos 0 1 FULL +Pos 1 0 EMPTY + +NewRun + +#corner top-right 4x3 +Index 165 +Pos 0 0 INDEX 199 +Pos 1 1 INDEX 199 +Pos -1 -1 EMPTY +Pos -2 -1 EMPTY +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos -3 1 FULL +Pos -2 1 FULL +Pos -1 1 FULL +Pos 0 1 FULL +Pos -1 2 FULL +Pos 0 2 FULL +Pos 1 2 FULL +Pos 2 2 EMPTY +Pos 0 3 FULL +Pos 1 3 FULL +Pos -2 0 INDEX 200 +Pos 1 2 INDEX 200 + +NewRun + +#corner top-left 4x3 +Index 165 XFLIP +Pos 0 0 INDEX 199 +Pos -1 1 INDEX 199 +Pos 1 -1 EMPTY +Pos 2 -1 EMPTY +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos 3 1 FULL +Pos 2 1 FULL +Pos 1 1 FULL +Pos 0 1 FULL +Pos 1 2 FULL +Pos 0 2 FULL +Pos -1 2 FULL +Pos -2 2 EMPTY +Pos 0 3 FULL +Pos -1 3 FULL +Pos 2 0 INDEX 200 +Pos -1 2 INDEX 200 +Pos 4 0 NOTINDEX 165 +Pos 3 0 NOTINDEX 165 + +NewRun + +#corner bottom-left 4x3 +Index 165 XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos -1 -1 INDEX 199 +Pos 1 1 EMPTY +Pos 2 1 EMPTY +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos 3 -1 FULL +Pos 2 -1 FULL +Pos 1 -1 FULL +Pos 0 -1 FULL +Pos 1 -2 FULL +Pos 0 -2 FULL +Pos -1 -2 FULL +Pos -2 -2 EMPTY +Pos 0 -3 FULL +Pos -1 -3 FULL +Pos 2 0 INDEX 200 +Pos -1 -2 INDEX 200 +Pos 0 -4 NOTINDEX 165 + +NewRun + +#corner bottom-right 4x3 +Index 165 YFLIP +Pos 0 0 INDEX 199 +Pos 1 -1 INDEX 199 +Pos -1 1 EMPTY +Pos -2 1 EMPTY +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos -3 -1 FULL +Pos -2 -1 FULL +Pos -1 -1 FULL +Pos 0 -1 FULL +Pos -1 -2 FULL +Pos 0 -2 FULL +Pos 1 -2 FULL +Pos 2 -2 EMPTY +Pos 0 -3 FULL +Pos 1 -3 FULL +Pos -2 0 INDEX 200 +Pos 1 -2 INDEX 200 +Pos -4 0 NOTINDEX 165 +Pos -3 0 NOTINDEX 165 +Pos 0 -4 NOTINDEX 165 + +NewRun + +#fill top-right 4x3 +Index 164 +Pos 1 0 INDEX 165 +Pos 0 -1 EMPTY +Pos -1 0 FULL + +Index 163 +Pos 2 0 INDEX 165 +Pos 0 -1 EMPTY +Pos -1 0 FULL + +Index 181 +Pos 0 -1 INDEX 165 +Pos 1 -1 EMPTY +Pos 1 0 FULL + +Index 182 +Pos -1 -1 INDEX 165 +Pos -1 0 FULL +Pos 1 0 EMPTY + +Index 198 +Pos -1 -2 INDEX 165 +Pos 0 -1 FULL +Pos 1 0 EMPTY + +#fill top-left 4x3 +Index 164 XFLIP +Pos -1 0 INDEX 165 +Pos 0 -1 EMPTY +Pos 1 0 FULL + +Index 163 XFLIP +Pos -2 0 INDEX 165 +Pos 0 -1 EMPTY +Pos 1 0 FULL + +Index 181 XFLIP +Pos 0 -1 INDEX 165 +Pos -1 -1 EMPTY +Pos -1 0 FULL + +Index 182 XFLIP +Pos 1 -1 INDEX 165 +Pos 1 0 FULL +Pos -1 0 EMPTY + +Index 198 XFLIP +Pos 1 -2 INDEX 165 +Pos 0 -1 FULL +Pos -1 0 EMPTY + +#fill bottom-left 4x3 +Index 164 XFLIP YFLIP +Pos -1 0 INDEX 165 +Pos 0 1 EMPTY +Pos 1 0 FULL + +Index 163 XFLIP YFLIP +Pos -2 0 INDEX 165 +Pos 0 1 EMPTY +Pos 1 0 FULL + +Index 181 XFLIP YFLIP +Pos 0 1 INDEX 165 +Pos -1 1 EMPTY +Pos -1 0 FULL + +Index 182 XFLIP YFLIP +Pos 1 1 INDEX 165 +Pos 1 0 FULL +Pos -1 0 EMPTY + +Index 198 XFLIP YFLIP +Pos 1 2 INDEX 165 +Pos 0 1 FULL +Pos -1 0 EMPTY + +#fill bottom-right 4x3 +Index 164 YFLIP +Pos 1 0 INDEX 165 +Pos 0 1 EMPTY +Pos -1 0 FULL + +Index 163 YFLIP +Pos 2 0 INDEX 165 +Pos 0 1 EMPTY +Pos -1 0 FULL + +Index 181 YFLIP +Pos 0 1 INDEX 165 +Pos 1 1 EMPTY +Pos 1 0 FULL + +Index 182 YFLIP +Pos -1 1 INDEX 165 +Pos -1 0 FULL +Pos 1 0 EMPTY + +Index 198 YFLIP +Pos -1 2 INDEX 165 +Pos 0 1 FULL +Pos 1 0 EMPTY + +NewRun + +#corner top-left 3x4 +Index 165 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos 1 -1 INDEX 199 +Pos 2 -2 EMPTY +Pos 2 -1 FULL +Pos 3 -1 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos -1 1 EMPTY +Pos 0 1 FULL +Pos 1 1 FULL +Pos 2 1 FULL +Pos -1 2 EMPTY +Pos 0 2 FULL +Pos 1 2 FULL +Pos 0 3 FULL +Pos 1 3 FULL +Pos 0 2 INDEX 200 +Pos 2 -1 INDEX 200 + +NewRun + +#corner top-right 3x4 +Index 165 ROTATE XFLIP +Pos 0 0 INDEX 199 +Pos -1 -1 INDEX 199 +Pos -2 -2 EMPTY +Pos -2 -1 FULL +Pos -3 -1 FULL +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos 1 1 EMPTY +Pos 0 1 FULL +Pos -1 1 FULL +Pos -2 1 FULL +Pos 1 2 EMPTY +Pos 0 2 FULL +Pos -1 2 FULL +Pos 0 3 FULL +Pos -1 3 FULL +Pos 0 2 INDEX 200 +Pos -2 -1 INDEX 200 + +NewRun + +#corner bottom-right 3x4 +Index 165 ROTATE +Pos 0 0 INDEX 199 +Pos -1 1 INDEX 199 +Pos -2 2 EMPTY +Pos -2 1 FULL +Pos -3 1 FULL +Pos -1 0 FULL +Pos -2 0 FULL +Pos -3 0 FULL +Pos 1 -1 EMPTY +Pos 0 -1 FULL +Pos -1 -1 FULL +Pos -2 -1 FULL +Pos 1 -2 EMPTY +Pos 0 -2 FULL +Pos -1 -2 FULL +Pos 0 -3 FULL +Pos -1 -3 FULL +Pos 0 -2 INDEX 200 +Pos -2 1 INDEX 200 +Pos 0 -4 NOTINDEX 165 +Pos 0 -3 NOTINDEX 165 + +NewRun + +#corner bottom-left 3x4 +Index 165 ROTATE YFLIP +Pos 0 0 INDEX 199 +Pos 1 1 INDEX 199 +Pos 2 2 EMPTY +Pos 2 1 FULL +Pos 3 1 FULL +Pos 1 0 FULL +Pos 2 0 FULL +Pos 3 0 FULL +Pos -1 -1 EMPTY +Pos 0 -1 FULL +Pos 1 -1 FULL +Pos 2 -1 FULL +Pos -1 -2 EMPTY +Pos 0 -2 FULL +Pos 1 -2 FULL +Pos 0 -3 FULL +Pos 1 -3 FULL +Pos 0 -2 INDEX 200 +Pos 2 1 INDEX 200 +Pos 0 -4 NOTINDEX 165 +Pos 0 -3 NOTINDEX 165 + +NewRun + +#fill top-left 3x4 +Index 164 ROTATE XFLIP YFLIP +Pos 0 -1 INDEX 165 +Pos -1 0 EMPTY +Pos 0 1 FULL + +Index 163 ROTATE XFLIP YFLIP +Pos 0 -2 INDEX 165 +Pos -1 0 EMPTY +Pos 0 1 FULL + +Index 181 ROTATE XFLIP YFLIP +Pos -1 0 INDEX 165 +Pos -1 -1 EMPTY +Pos 0 -1 FULL + +Index 182 ROTATE XFLIP YFLIP +Pos -1 1 INDEX 165 +Pos 0 1 FULL +Pos 0 -1 EMPTY + +Index 198 ROTATE XFLIP YFLIP +Pos -2 1 INDEX 165 +Pos -1 0 FULL +Pos 0 -1 EMPTY + +#fill top-right 3x4 +Index 164 ROTATE XFLIP +Pos 0 -1 INDEX 165 +Pos 1 0 EMPTY +Pos 0 1 FULL + +Index 163 ROTATE XFLIP +Pos 0 -2 INDEX 165 +Pos 1 0 EMPTY +Pos 0 1 FULL + +Index 181 ROTATE XFLIP +Pos 1 0 INDEX 165 +Pos 1 -1 EMPTY +Pos 0 -1 FULL + +Index 182 ROTATE XFLIP +Pos 1 1 INDEX 165 +Pos 0 1 FULL +Pos 0 -1 EMPTY + +Index 198 ROTATE XFLIP +Pos 2 1 INDEX 165 +Pos 1 0 FULL +Pos 0 -1 EMPTY + +#fill bottom-right 3x4 +Index 164 ROTATE +Pos 0 1 INDEX 165 +Pos 1 0 EMPTY +Pos 0 -1 FULL + +Index 163 ROTATE +Pos 0 2 INDEX 165 +Pos 1 0 EMPTY +Pos 0 -1 FULL + +Index 181 ROTATE +Pos 1 0 INDEX 165 +Pos 1 1 EMPTY +Pos 0 1 FULL + +Index 182 ROTATE +Pos 1 -1 INDEX 165 +Pos 0 -1 FULL +Pos 0 1 EMPTY + +Index 198 ROTATE +Pos 2 -1 INDEX 165 +Pos 1 0 FULL +Pos 0 1 EMPTY + +#fill bottom-left 3x4 +Index 164 ROTATE YFLIP +Pos 0 1 INDEX 165 +Pos -1 0 EMPTY +Pos 0 -1 FULL + +Index 163 ROTATE YFLIP +Pos 0 2 INDEX 165 +Pos -1 0 EMPTY +Pos 0 -1 FULL + +Index 181 ROTATE YFLIP +Pos -1 0 INDEX 165 +Pos -1 1 EMPTY +Pos 0 1 FULL + +Index 182 ROTATE YFLIP +Pos -1 -1 INDEX 165 +Pos 0 -1 FULL +Pos 0 1 EMPTY + +Index 198 ROTATE YFLIP +Pos -2 -1 INDEX 165 +Pos -1 0 FULL +Pos 0 1 EMPTY + +NewRun + +#dead end top +Index 227 XFLIP YFLIP +Pos -1 0 INDEX 215 +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 215 +Pos -1 1 INDEX 200 +Pos 0 1 EMPTY +Pos 1 1 INDEX 200 + +#dead end right +Index 227 ROTATE XFLIP YFLIP +Pos 0 -1 INDEX 215 +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 215 +Pos -1 -1 INDEX 200 +Pos -1 0 EMPTY +Pos -1 1 INDEX 200 + +#dead end bottom +Index 227 +Pos -1 0 INDEX 215 +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 215 +Pos -1 -1 INDEX 200 +Pos 0 -1 EMPTY +Pos 1 -1 INDEX 200 + +#dead end left +Index 227 ROTATE +Pos 0 -1 INDEX 215 +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 215 +Pos 1 -1 INDEX 200 +Pos 1 0 EMPTY +Pos 1 1 INDEX 200 + +NewRun + +#fill dead end top +Index 226 XFLIP YFLIP +Pos -1 0 INDEX 227 +Pos 0 1 INDEX 200 +Pos 1 0 FULL +Pos 0 -1 FULL + +Index 228 XFLIP YFLIP +Pos 1 0 INDEX 227 +Pos 0 1 INDEX 200 +Pos -1 0 FULL +Pos 0 1 FULL + +Index 210 XFLIP YFLIP +Pos -1 -1 INDEX 227 +Pos -1 0 EMPTY + +Index 211 XFLIP YFLIP +Pos 0 0 EMPTY +Pos 0 -1 INDEX 227 +Pos 0 1 EMPTY + +Index 212 XFLIP YFLIP +Pos 1 -1 INDEX 227 +Pos 1 0 EMPTY + +#fill dead end right +Index 226 ROTATE XFLIP YFLIP +Pos 0 -1 INDEX 227 +Pos -1 0 INDEX 200 +Pos 0 1 FULL +Pos 1 0 FULL + +Index 228 ROTATE XFLIP YFLIP +Pos 0 1 INDEX 227 +Pos -1 0 INDEX 200 +Pos 0 -1 FULL +Pos 1 0 FULL + +Index 210 ROTATE XFLIP YFLIP +Pos 1 -1 INDEX 227 +Pos 0 -1 EMPTY + +Index 211 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 0 INDEX 227 +Pos -1 0 EMPTY + +Index 212 ROTATE XFLIP YFLIP +Pos 1 1 INDEX 227 +Pos 0 1 EMPTY + +#fill dead end bottom +Index 226 +Pos 1 0 INDEX 227 +Pos 0 -1 INDEX 200 +Pos -1 0 FULL +Pos 0 1 FULL + +Index 228 +Pos -1 0 INDEX 227 +Pos 0 -1 INDEX 200 +Pos 1 0 FULL +Pos 0 1 FULL + +Index 210 +Pos 1 1 INDEX 227 +Pos 1 0 EMPTY + +Index 211 +Pos 0 0 EMPTY +Pos 0 1 INDEX 227 +Pos 0 -1 EMPTY + +Index 212 +Pos -1 1 INDEX 227 +Pos -1 0 EMPTY + +#fill dead end left +Index 226 ROTATE +Pos 0 1 INDEX 227 +Pos 1 0 INDEX 200 +Pos 0 -1 FULL +Pos -1 0 FULL + +Index 228 ROTATE +Pos 0 -1 INDEX 227 +Pos 1 0 INDEX 200 +Pos 0 1 FULL +Pos -1 0 FULL + +Index 210 ROTATE +Pos -1 1 INDEX 227 +Pos 0 1 EMPTY + +Index 211 ROTATE +Pos 0 0 EMPTY +Pos -1 0 INDEX 227 +Pos 1 0 EMPTY + +Index 212 ROTATE +Pos -1 -1 INDEX 227 +Pos 0 -1 EMPTY + +NewRun + +#s-curve bottom-left 1 +Index 209 XFLIP YFLIP +Pos 0 0 INDEX 215 +Pos 0 -1 INDEX 200 +Pos 1 0 INDEX 199 +Pos 1 -1 EMPTY + +#s-curve bottom-left 1 alt +Index 229 +Pos 0 0 INDEX 215 +Pos 0 -1 INDEX 200 +Pos 1 0 INDEX 199 +Pos 1 -1 EMPTY +Random 2 + +NewRun + +#s-curve bottom-left 2 +Index 209 ROTATE YFLIP +Pos 0 0 INDEX 215 +Pos 1 0 INDEX 200 +Pos 0 -1 INDEX 199 +Pos 1 -1 EMPTY + +#s-curve bottom-left 2 alt +Index 229 ROTATE XFLIP +Pos 0 0 INDEX 215 +Pos 1 0 INDEX 200 +Pos 0 -1 INDEX 199 +Pos 1 -1 EMPTY +Random 2 + +NewRun + +#s-curve top-left 1 +Index 209 XFLIP +Pos 0 0 INDEX 215 +Pos 0 1 INDEX 200 +Pos 1 0 INDEX 199 +Pos 1 1 EMPTY +Pos 0 2 NOTINDEX 209 OR 229 + +#s-curve top-left 1 alt +Index 229 YFLIP +Pos 0 0 INDEX 215 +Pos 0 1 INDEX 200 +Pos 1 0 INDEX 199 +Pos 1 1 EMPTY +Pos 0 2 NOTINDEX 209 OR 229 +Random 2 + +NewRun + +#s-curve top-left 2 +Index 209 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 215 +Pos 1 0 INDEX 200 +Pos 0 1 INDEX 199 +Pos 1 1 EMPTY + +#s-curve top-left 2 alt +Index 229 ROTATE +Pos 0 0 INDEX 215 +Pos 1 0 INDEX 200 +Pos 0 1 INDEX 199 +Pos 1 1 EMPTY +Random 2 + +NewRun + +#s-curve top-right 1 +Index 209 +Pos 0 0 INDEX 215 +Pos 0 1 INDEX 200 +Pos -1 0 INDEX 199 +Pos -1 1 EMPTY + +#s-curve top-right 1 alt +Index 229 XFLIP YFLIP +Pos 0 0 INDEX 215 +Pos 0 1 INDEX 200 +Pos -1 0 INDEX 199 +Pos -1 1 EMPTY +Random 2 + +NewRun + +#s-curve top-right 2 +Index 209 ROTATE XFLIP +Pos 0 0 INDEX 215 +Pos -1 0 INDEX 200 +Pos 0 1 INDEX 199 +Pos -1 1 EMPTY +Pos -2 0 NOTINDEX 209 OR 229 + +#s-curve top-right 2 alt +Index 229 ROTATE YFLIP +Pos 0 0 INDEX 215 +Pos -1 0 INDEX 200 +Pos 0 1 INDEX 199 +Pos -1 1 EMPTY +Pos -2 0 NOTINDEX 209 OR 229 +Random 2 + +NewRun + +#s-curve bottom-right 1 +Index 209 YFLIP +Pos 0 0 INDEX 215 +Pos 0 -1 INDEX 200 +Pos -1 0 INDEX 199 +Pos -1 -1 EMPTY +Pos 0 -2 NOTINDEX 209 OR 229 + +#s-curve bottom-right 1 alt +Index 229 XFLIP +Pos 0 0 INDEX 215 +Pos 0 -1 INDEX 200 +Pos -1 0 INDEX 199 +Pos -1 -1 EMPTY +Pos 0 -2 NOTINDEX 209 OR 229 +Random 2 + +NewRun + +#s-curve bottom-right 2 +Index 209 ROTATE +Pos 0 0 INDEX 215 +Pos -1 0 INDEX 200 +Pos 0 -1 INDEX 199 +Pos -1 -1 EMPTY +Pos -2 0 NOTINDEX 209 OR 229 + +#s-curve bottom-right 2 alt +Index 229 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 215 +Pos -1 0 INDEX 200 +Pos 0 -1 INDEX 199 +Pos -1 -1 EMPTY +Pos -2 0 NOTINDEX 209 OR 229 +Random 2 + +NewRun + +#fill s-curve bottom-left 1 +Index 225 XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 209 +Pos 1 0 EMPTY + +Index 208 XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos -1 0 INDEX 209 +Pos 0 -1 EMPTY + +Index 224 XFLIP YFLIP +Pos 0 0 EMPTY +Pos -1 1 INDEX 209 +Pos -1 0 INDEX 200 + +#fill s-curve bottom-left 1 alt +Index 213 +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 229 +Pos 1 0 EMPTY + +Index 230 +Pos 0 0 INDEX 199 +Pos -1 0 INDEX 229 +Pos 0 -1 EMPTY + +Index 214 +Pos 0 0 EMPTY +Pos -1 1 INDEX 229 +Pos -1 0 INDEX 200 + +#fill s-curve bottom-left 2 +Index 225 ROTATE YFLIP +Pos 0 0 INDEX 200 +Pos -1 0 INDEX 209 +Pos 0 -1 EMPTY + +Index 208 ROTATE YFLIP +Pos 0 0 INDEX 199 +Pos 0 1 INDEX 209 +Pos 1 0 EMPTY + +Index 224 ROTATE YFLIP +Pos 0 0 EMPTY +Pos -1 1 INDEX 209 +Pos 0 1 INDEX 200 + +#fill s-curve bottom-left 2 alt +Index 213 ROTATE XFLIP +Pos 0 0 INDEX 200 +Pos -1 0 INDEX 229 +Pos 0 -1 EMPTY + +Index 230 ROTATE XFLIP +Pos 0 0 INDEX 199 +Pos 0 1 INDEX 229 +Pos 1 0 EMPTY + +Index 214 ROTATE XFLIP +Pos 0 0 EMPTY +Pos -1 1 INDEX 229 +Pos 0 1 INDEX 200 + +#fill s-curve top-left 1 +Index 225 XFLIP +Pos 0 0 INDEX 200 +Pos 0 -1 INDEX 209 +Pos 1 0 EMPTY + +Index 208 XFLIP +Pos 0 0 INDEX 199 +Pos -1 0 INDEX 209 +Pos 0 1 EMPTY + +Index 224 XFLIP +Pos 0 0 EMPTY +Pos -1 -1 INDEX 209 +Pos -1 0 INDEX 200 + +#fill s-curve top-left 1 alt +Index 213 YFLIP +Pos 0 0 INDEX 200 +Pos 0 -1 INDEX 229 +Pos 1 0 EMPTY + +Index 230 YFLIP +Pos 0 0 INDEX 199 +Pos -1 0 INDEX 229 +Pos 0 1 EMPTY + +Index 214 YFLIP +Pos 0 0 EMPTY +Pos -1 -1 INDEX 229 +Pos -1 0 INDEX 200 + +#fill s-curve top-left 2 +Index 225 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos -1 0 INDEX 209 +Pos 0 1 EMPTY + +Index 208 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos 0 -1 INDEX 209 +Pos 1 0 EMPTY + +Index 224 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos -1 -1 INDEX 209 +Pos 0 -1 INDEX 200 + +#fill s-curve top-left 2 alt +Index 213 ROTATE +Pos 0 0 INDEX 200 +Pos -1 0 INDEX 229 +Pos 0 1 EMPTY + +Index 230 ROTATE +Pos 0 0 INDEX 199 +Pos 0 -1 INDEX 229 +Pos 1 0 EMPTY + +Index 214 ROTATE +Pos 0 0 EMPTY +Pos -1 -1 INDEX 229 +Pos 0 -1 INDEX 200 + +#fill s-curve top-right 1 +Index 225 +Pos 0 0 INDEX 200 +Pos 0 -1 INDEX 209 +Pos -1 0 EMPTY + +Index 208 +Pos 0 0 INDEX 199 +Pos 1 0 INDEX 209 +Pos 0 1 EMPTY + +Index 224 +Pos 0 0 EMPTY +Pos 1 -1 INDEX 209 +Pos 1 0 INDEX 200 + +#fill s-curve top-right 1 alt +Index 213 XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos 0 -1 INDEX 229 +Pos -1 0 EMPTY + +Index 230 XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos 1 0 INDEX 229 +Pos 0 1 EMPTY + +Index 214 XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 -1 INDEX 229 +Pos 1 0 INDEX 200 + +#fill s-curve top-right 2 +Index 225 ROTATE XFLIP +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 209 +Pos 0 1 EMPTY + +Index 208 ROTATE XFLIP +Pos 0 0 INDEX 199 +Pos 0 -1 INDEX 209 +Pos -1 0 EMPTY + +Index 224 ROTATE XFLIP +Pos 0 0 EMPTY +Pos 1 -1 INDEX 209 +Pos 0 -1 INDEX 200 + +#fill s-curve top-right 2 alt +Index 213 ROTATE YFLIP +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 229 +Pos 0 1 EMPTY + +Index 230 ROTATE YFLIP +Pos 0 0 INDEX 199 +Pos 0 -1 INDEX 229 +Pos -1 0 EMPTY + +Index 214 ROTATE YFLIP +Pos 0 0 EMPTY +Pos 1 -1 INDEX 229 +Pos 0 -1 INDEX 200 + +#fill s-curve bottom-right 1 +Index 225 YFLIP +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 209 +Pos -1 0 EMPTY + +Index 208 YFLIP +Pos 0 0 INDEX 199 +Pos 1 0 INDEX 209 +Pos 0 -1 EMPTY + +Index 224 YFLIP +Pos 0 0 EMPTY +Pos 1 1 INDEX 209 +Pos 1 0 INDEX 200 + +#fill s-curve bottom-right 1 alt +Index 213 XFLIP +Pos 0 0 INDEX 200 +Pos 0 1 INDEX 229 +Pos -1 0 EMPTY + +Index 230 XFLIP +Pos 0 0 INDEX 199 +Pos 1 0 INDEX 229 +Pos 0 -1 EMPTY + +Index 214 XFLIP +Pos 0 0 EMPTY +Pos 1 1 INDEX 229 +Pos 1 0 INDEX 200 + +#fill s-curve bottom-right 2 +Index 225 ROTATE +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 209 +Pos 0 -1 EMPTY + +Index 208 ROTATE +Pos 0 0 INDEX 199 +Pos 0 1 INDEX 209 +Pos -1 0 EMPTY + +Index 224 ROTATE +Pos 0 0 EMPTY +Pos 1 1 INDEX 209 +Pos 0 1 INDEX 200 + +#fill s-curve bottom-right 2 alt +Index 213 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos 1 0 INDEX 229 +Pos 0 -1 EMPTY + +Index 230 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 199 +Pos 0 1 INDEX 229 +Pos -1 0 EMPTY + +Index 214 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 1 INDEX 229 +Pos 0 1 INDEX 200 + + +NewRun + +#top long +Index 233 +Pos 0 0 INDEX 200 +Pos 0 -1 EMPTY +Pos 0 -2 EMPTY +Pos -1 0 FULL +Pos 1 0 INDEX 200 +Pos 2 0 INDEX 200 +Random 5 + +#right long +Index 233 ROTATE +Pos 0 0 INDEX 200 +Pos 1 0 EMPTY +Pos 2 0 EMPTY +Pos 0 -1 FULL +Pos 0 1 INDEX 200 +Pos 0 2 INDEX 200 +Random 5 + +#bottom long +Index 233 XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos 0 1 EMPTY +Pos 0 2 EMPTY +Pos 1 0 FULL +Pos -1 0 INDEX 200 +Pos -2 0 INDEX 200 +Random 5 + +#left long +Index 233 ROTATE XFLIP YFLIP +Pos 0 0 INDEX 200 +Pos -1 0 EMPTY +Pos -2 0 EMPTY +Pos 0 1 FULL +Pos 0 -1 INDEX 200 +Pos 0 -2 INDEX 200 +Random 5 + + +NewRun + +#top overlap +Index 200 +Pos 0 0 233 +Pos 0 -1 EMPTY +Pos 0 0 233 +Pos -1 0 INDEX 233 + +#top overlap 2 +Index 200 +Pos 0 0 233 +Pos 0 -1 EMPTY +Pos 0 0 INDEX 233 +Pos -2 0 INDEX 233 + +#right overlap +Index 200 ROTATE +Pos 0 0 233 +Pos 1 0 EMPTY +Pos 0 0 INDEX 233 +Pos 0 -1 INDEX 233 + +#right overlap 2 +Index 200 ROTATE +Pos 0 0 233 +Pos 1 0 EMPTY +Pos 0 0 INDEX 233 +Pos 0 -2 INDEX 233 + +#bottom overlap +Index 200 XFLIP YFLIP +Pos 0 0 233 +Pos 0 1 EMPTY +Pos 0 0 INDEX 233 +Pos 1 0 INDEX 233 + +#bottom overlap 2 +Index 200 XFLIP YFLIP +Pos 0 0 233 +Pos 0 1 EMPTY +Pos 0 0 INDEX 233 +Pos 2 0 INDEX 233 + +#left overlap +Index 200 ROTATE XFLIP YFLIP +Pos 0 0 233 +Pos -1 0 EMPTY +Pos 0 0 INDEX 233 +Pos 0 1 INDEX 233 + +#left overlap 2 +Index 200 ROTATE XFLIP YFLIP +Pos 0 0 233 +Pos -1 0 EMPTY +Pos 0 0 INDEX 233 +Pos 0 2 INDEX 233 + +NewRun + +#fill top long 1 +Index 234 +Pos -1 0 INDEX 233 +Pos 0 -1 EMPTY + +#fill top long 2 +Index 235 +Pos -2 0 INDEX 233 +Pos -1 0 INDEX 200 +Pos 0 -1 EMPTY +Pos 1 0 FULL + +#fill right long 1 +Index 234 ROTATE +Pos 0 -1 INDEX 233 +Pos 1 0 EMPTY + +#fill right long 2 +Index 235 ROTATE +Pos 0 -2 INDEX 233 +Pos 0 -1 INDEX 200 +Pos 1 0 EMPTY +Pos 0 1 FULL + +#fill bottom long 1 +Index 234 XFLIP YFLIP +Pos 1 0 INDEX 233 +Pos 0 1 EMPTY + +#fill bottom long 2 +Index 235 XFLIP YFLIP +Pos 2 0 INDEX 233 +Pos 1 0 INDEX 200 +Pos 0 1 EMPTY +Pos -1 0 FULL + +#fill left long 1 +Index 234 ROTATE XFLIP YFLIP +Pos 0 1 INDEX 233 +Pos -1 0 EMPTY + +#fill left long 2 +Index 235 ROTATE XFLIP YFLIP +Pos 0 2 INDEX 233 +Pos 0 1 INDEX 200 +Pos -1 0 EMPTY +Pos 0 -1 FULL + +NewRun + +#fill top long 3 +Index 217 +Pos 0 0 EMPTY +Pos 0 1 INDEX 233 +Pos 0 2 FULL + +#fill top long 4 +Index 218 +Pos 0 0 EMPTY +Pos 0 1 INDEX 234 +Pos 0 2 FULL + +#fill top long 5 +Index 219 +Pos 0 0 EMPTY +Pos 0 1 INDEX 235 +Pos 0 2 FULL + +#fill right long 3 +Index 217 ROTATE +Pos 0 0 EMPTY +Pos -1 0 INDEX 233 +Pos -2 0 FULL + +#fill right long 4 +Index 218 ROTATE +Pos 0 0 EMPTY +Pos -1 0 INDEX 234 +Pos -2 0 FULL + +#fill right long 5 +Index 219 ROTATE +Pos 0 0 EMPTY +Pos -1 0 INDEX 235 +Pos -2 0 FULL + +#fill bottom long 3 +Index 217 XFLIP YFLIP +Pos 0 0 EMPTY +Pos 0 -1 INDEX 233 +Pos 0 -2 FULL + +#fill bottom long 4 +Index 218 XFLIP YFLIP +Pos 0 0 EMPTY +Pos 0 -1 INDEX 234 +Pos 0 -2 FULL + +#fill bottom long 5 +Index 219 XFLIP YFLIP +Pos 0 0 EMPTY +Pos 0 -1 INDEX 235 +Pos 0 -2 FULL + +#fill left long 3 +Index 217 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 0 INDEX 233 +Pos 2 0 FULL + +#fill left long 4 +Index 218 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 0 INDEX 234 +Pos 2 0 FULL + +#fill left long 5 +Index 219 ROTATE XFLIP YFLIP +Pos 0 0 EMPTY +Pos 1 0 INDEX 235 +Pos 2 0 FULL diff --git a/data/editor/round_tiles.rules b/data/editor/automap/round_tiles.rules similarity index 100% rename from data/editor/round_tiles.rules rename to data/editor/automap/round_tiles.rules diff --git a/data/editor/water.rules b/data/editor/automap/water.rules similarity index 100% rename from data/editor/water.rules rename to data/editor/automap/water.rules diff --git a/data/editor/winter_main.rules b/data/editor/automap/winter_main.rules similarity index 100% rename from data/editor/winter_main.rules rename to data/editor/automap/winter_main.rules diff --git a/data/languages/arabic.txt b/data/languages/arabic.txt index 635ce0fba..66c7f76c9 100644 --- a/data/languages/arabic.txt +++ b/data/languages/arabic.txt @@ -140,9 +140,6 @@ Favorites Feet == ﺔﻣﺰﺟ -Filter -== ﺔﻴﻔﺼﺗ - Fire == ﺭﺎﻧ @@ -155,9 +152,6 @@ Force vote Free-View == ﺮﺤﻟﺍ ﺮﻈﻨﻤﻟﺍ -Friends -== ﺀﺎﻗﺪﺻﻻﺍ - Fullscreen == ﻞﻣﺎﻜﻟﺍ ﺔﺷﺎﺸﻟﺍ ﻊﺿﻭ @@ -354,12 +348,6 @@ Screenshot Server address: == ﺮﻓﺮﻴﺴﻟﺍ ﻥﺍﻮﻨﻋ: -Server details -== ﺮﻓﺮﻴﺴﻟﺍ ﻞﻴﺻﺎﻔﺗ - -Server filter -== ﺮﻓﺮﻴﺴﻟﺍ ﺔﻴﻔﺼﺗ - Server info == ﺮﻓﺮﻴﺴﻟﺍ ﺕﺎﻣﻮﻠﻌﻣ @@ -528,9 +516,6 @@ Netversion: Map: == ﺏﺎﻤﻟﺍ: -Info -== ﺕﺎﻣﻮﻠﻌﻣ - Hue == ﻥﻮﻟ @@ -1355,12 +1340,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1376,12 +1384,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1397,6 +1399,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1448,10 +1453,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt index a07eb5f60..4f94ab744 100644 --- a/data/languages/belarusian.txt +++ b/data/languages/belarusian.txt @@ -147,9 +147,6 @@ Favorites Feet == Ногі -Filter -== Фільтр - Fire == Стрэл @@ -162,9 +159,6 @@ Force vote Free-View == Вольны агляд -Friends -== Сябры - Fullscreen == Поўнаэкранны рэжым @@ -358,12 +352,6 @@ Screenshot Server address: == Адрас сервера: -Server details -== Дэталі сервера - -Server filter -== Фільтр сервераў - Server info == Інфармацыя @@ -532,9 +520,6 @@ Netversion: Map: == Карта: -Info -== Інфа - Hue == Адценне @@ -1727,6 +1712,26 @@ Unable to rename the folder (paused) == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== + All combined == diff --git a/data/languages/bosnian.txt b/data/languages/bosnian.txt index 4238ef8c7..87cd5b513 100644 --- a/data/languages/bosnian.txt +++ b/data/languages/bosnian.txt @@ -148,9 +148,6 @@ Favorites Feet == Stopala -Filter -== Filter - Fire == Pucanje @@ -163,9 +160,6 @@ Force vote Free-View == Slobodan pogled -Friends -== Prijatelji - Fullscreen == Čitav ekran @@ -359,12 +353,6 @@ Screenshot Server address: == Adresa servera: -Server details -== Podaci o serveru - -Server filter -== Filter servera - Server info == O Serveru @@ -535,9 +523,6 @@ Netversion: Map: == Mapa: -Info -== Info - Hue == Nijans. @@ -1247,12 +1232,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1268,12 +1276,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1289,6 +1291,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1361,10 +1366,10 @@ Open the directory to add custom themes Max CSVs == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins diff --git a/data/languages/brazilian_portuguese.txt b/data/languages/brazilian_portuguese.txt index e168a9f4f..fa08983a5 100644 --- a/data/languages/brazilian_portuguese.txt +++ b/data/languages/brazilian_portuguese.txt @@ -170,9 +170,6 @@ Favorites Feet == Pés -Filter -== Filtro - Fire == Atirar @@ -185,9 +182,6 @@ Force vote Free-View == Visualização livre -Friends -== Amigos - Fullscreen == Tela cheia @@ -384,12 +378,6 @@ Screenshot Server address: == Endereço: -Server details -== Detalhes do servidor - -Server filter -== Filtro de servidores - Server info == Servidor @@ -576,9 +564,6 @@ Show ghost Clan plates size == Tamanho da placa do clã -Info -== Info - No updates available == Nenhuma atualização disponível @@ -1767,3 +1752,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == Movido no jogo + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/bulgarian.txt b/data/languages/bulgarian.txt index 758e6dd96..9a195ba72 100644 --- a/data/languages/bulgarian.txt +++ b/data/languages/bulgarian.txt @@ -145,9 +145,6 @@ Favorites Feet == Крака -Filter -== Филтър - Fire == Стрелба @@ -160,9 +157,6 @@ Force vote Free-View == Свободен Изглед -Friends -== Приятели - Fullscreen == Цял Екран @@ -356,12 +350,6 @@ Screenshot Server address: == Адрес на сървъра: -Server details -== Детайли за Сървъра - -Server filter -== Филтър на сървъра - Server info == Инфо @@ -529,9 +517,6 @@ Netversion: Map: == Карта: -Info -== Инфо - Hue == Оттенък @@ -908,12 +893,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -929,12 +937,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -953,6 +955,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1103,10 +1108,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1160,10 +1165,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1172,9 +1183,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Spectate previous == @@ -1190,9 +1198,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/catalan.txt b/data/languages/catalan.txt index cb6b27649..20d5a1fae 100644 --- a/data/languages/catalan.txt +++ b/data/languages/catalan.txt @@ -140,9 +140,6 @@ Favorites Feet == Peus -Filter -== Filtre - Fire == Disparar @@ -155,9 +152,6 @@ Force vote Free-View == Vista lliure -Friends -== Amics - Fullscreen == Pantalla completa @@ -354,12 +348,6 @@ Screenshot Server address: == IP del servidor: -Server details -== Detalls del servidor - -Server filter -== Filtre del servidor - Server info == Servidor @@ -540,9 +528,6 @@ Netversion: Map: == Mapa: -Info -== Informació - Hue == Tonalitat @@ -1427,12 +1412,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1448,12 +1456,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1469,6 +1471,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1514,10 +1519,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/chuvash.txt b/data/languages/chuvash.txt index 4a88c4c89..60072a571 100644 --- a/data/languages/chuvash.txt +++ b/data/languages/chuvash.txt @@ -145,9 +145,6 @@ Favorites Feet == Урасем -Filter -== Фильтр - Fire == Пĕрӳ @@ -160,9 +157,6 @@ Force vote Free-View == Ирĕклĕ обзор -Friends -== Юлташсем - Fullscreen == Тулли экран @@ -356,12 +350,6 @@ Screenshot Server address: == Сервер адресĕ -Server details -== Сервер тĕплĕсем - -Server filter -== Серверсен фильтр - Server info == Пĕлтерӳ @@ -535,9 +523,6 @@ Miscellaneous Netversion: == Версия: -Info -== Пĕлтерӳ - UI Color == Интерфейс тĕсĕ @@ -911,12 +896,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -932,12 +940,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -956,6 +958,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1106,10 +1111,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1163,10 +1168,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1175,9 +1186,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1190,9 +1198,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/czech.txt b/data/languages/czech.txt index b4d037cd6..f4dede85f 100644 --- a/data/languages/czech.txt +++ b/data/languages/czech.txt @@ -148,9 +148,6 @@ Favorites Feet == Nohy -Filter -== Filtr - Fire == Střelba @@ -163,9 +160,6 @@ Force vote Free-View == Volný pohled -Friends -== Přátelé - Fullscreen == Celá obrazovka @@ -355,12 +349,6 @@ Screenshot Server address: == Adresa serveru: -Server details -== Detaily serveru - -Server filter -== Filtr serverů - Server info == Informace @@ -531,9 +519,6 @@ Netversion: Map: == Mapa: -Info -== Info - Hue == Tón @@ -1365,12 +1350,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1386,12 +1394,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1407,6 +1409,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1458,10 +1463,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/danish.txt b/data/languages/danish.txt index 5acece968..245e92827 100644 --- a/data/languages/danish.txt +++ b/data/languages/danish.txt @@ -146,9 +146,6 @@ Favorites Feet == Fødder -Filter -== Filter - Fire == Skyd @@ -161,9 +158,6 @@ Force vote Free-View == Free-View -Friends -== Venner - Fullscreen == Fuldskærm @@ -357,12 +351,6 @@ Screenshot Server address: == Serveradresse -Server details -== Serverdetaljer - -Server filter -== Serverfilter - Server info == Serverinfo @@ -533,9 +521,6 @@ Netversion: Map: == Bane -Info -== Info - Hue == Farve @@ -1363,12 +1348,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1384,12 +1392,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1405,6 +1407,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1456,10 +1461,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/dutch.txt b/data/languages/dutch.txt index f07acc9dc..127cd5bff 100644 --- a/data/languages/dutch.txt +++ b/data/languages/dutch.txt @@ -158,9 +158,6 @@ Favorites Feet == Voeten -Filter -== Filter - Fire == Schieten @@ -173,9 +170,6 @@ Force vote Free-View == Vrij bewegen -Friends -== Vrienden - Fullscreen == Volledig scherm @@ -369,12 +363,6 @@ Screenshot Server address: == Serveradres: -Server details -== Serverdetails - -Server filter -== Serverfilter - Server info == Serverinfo @@ -545,9 +533,6 @@ Netversion: Map: == Kaart: -Info -== Info - Hue == Tint @@ -1473,12 +1458,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1494,12 +1502,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1515,6 +1517,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == diff --git a/data/languages/esperanto.txt b/data/languages/esperanto.txt index d63c9d2f2..ad1adc17c 100644 --- a/data/languages/esperanto.txt +++ b/data/languages/esperanto.txt @@ -273,9 +273,6 @@ Countries Favorite == Favorati -Friends -== Amikoj - Name == Nomo @@ -285,9 +282,6 @@ Clan Add Friend == Aldoni amikon -Filter -== Filtri - Please use a different name == Bonvolu uzi malsaman nomon @@ -767,9 +761,6 @@ The server is running a non-standard tuning on a pure game type. Loading menu images == -Server filter -== - Has people playing == @@ -797,9 +788,6 @@ Types Reset filter == -Server details -== - Copy info == @@ -843,9 +831,6 @@ Remove friend Add Clan == -Info -== - Play the current demo == @@ -855,12 +840,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -876,12 +884,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -900,6 +902,9 @@ Cut interval Cut length == +Render cut to video +== + File already exists, do you want to overwrite it? == @@ -1086,15 +1091,15 @@ Automatically create statboard csv Max CSVs == +Toggle to edit your dummy settings +== + Loading skin files == Your skin == -Toggle to edit your dummy settings -== - Download skins == @@ -1143,18 +1148,9 @@ Default zoom Toggle dyncam == -Toggle dummy -== - Toggle ghost == -Dummy copy -== - -Hammerfly dummy -== - Shotgun == @@ -1173,6 +1169,18 @@ Team chat Converse == +Chat command +== + +Toggle dummy +== + +Dummy copy +== + +Hammerfly dummy +== + Emoticon == @@ -1209,9 +1217,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/finnish.txt b/data/languages/finnish.txt index e1e6538f5..c114de532 100644 --- a/data/languages/finnish.txt +++ b/data/languages/finnish.txt @@ -147,9 +147,6 @@ Favorites Feet == Jalat -Filter -== Suotimet - Fire == Ammu @@ -162,9 +159,6 @@ Force vote Free-View == Vapaa näkymä -Friends -== Ystävät - Fullscreen == Koko näyttö @@ -358,12 +352,6 @@ Screenshot Server address: == Palvelimen osoite: -Server details -== Palvelimen yksityiskohdat - -Server filter -== Palvelinsuotimet - Server info == Palvelintiedot @@ -532,9 +520,6 @@ Netversion: Map: == Kenttä: -Info -== Info - Hue == Värisävy @@ -1320,12 +1305,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1341,12 +1349,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1362,6 +1364,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1422,10 +1427,10 @@ Automatically create statboard csv Max CSVs == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins @@ -1443,15 +1448,15 @@ Create a random skin Open the directory to add custom skins == +Converse +== + Dummy copy == Hammerfly dummy == -Converse -== - Statboard == diff --git a/data/languages/french.txt b/data/languages/french.txt index ef1125be4..05623a484 100644 --- a/data/languages/french.txt +++ b/data/languages/french.txt @@ -169,9 +169,6 @@ Favorites Feet == Pieds -Filter -== Filtre - Fire == Tirer @@ -184,9 +181,6 @@ Force vote Free-View == Vue libre -Friends -== Amis - Fullscreen == Plein écran @@ -380,12 +374,6 @@ Screenshot Server address: == Adresse du serveur : -Server details -== Détails du serveur - -Server filter -== Filtres du serveur - Server info == Info. serveur @@ -556,9 +544,6 @@ Netversion: Map: == Carte : -Info -== Info. - Hue == Teinte @@ -1727,6 +1712,23 @@ Unable to rename the folder No server selected == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Mark the beginning of a cut (right click to reset) == @@ -1745,6 +1747,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/galician.txt b/data/languages/galician.txt index aee7e9697..fbeece3c7 100644 --- a/data/languages/galician.txt +++ b/data/languages/galician.txt @@ -145,9 +145,6 @@ Favorites Feet == Pés -Filter -== Filtro - Fire == Disparar @@ -160,9 +157,6 @@ Force vote Free-View == Vista libre -Friends -== Amigos - Fullscreen == Pantalla completa @@ -359,12 +353,6 @@ Screenshot Server address: == IP do servidor: -Server details -== Detalles do servidor - -Server filter -== Filtro do servidor - Server info == Servidor @@ -536,9 +524,6 @@ Netversion: Map: == Mapa: -Info -== Información - Hue == Matiz @@ -1698,6 +1683,23 @@ Unable to rename the folder No server selected == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Mark the beginning of a cut (right click to reset) == @@ -1716,6 +1718,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/german.txt b/data/languages/german.txt index 5059fbe0c..1d9b74204 100644 --- a/data/languages/german.txt +++ b/data/languages/german.txt @@ -163,9 +163,6 @@ Favorites Feet == Füße -Filter -== Filter - Fire == Feuern @@ -178,9 +175,6 @@ Force vote Free-View == Freie Ansicht -Friends -== Freunde - Fullscreen == Vollbild @@ -377,12 +371,6 @@ Screenshot Server address: == Serveradresse: -Server details -== Serverdetails - -Server filter -== Filter - Server info == Serverinfo @@ -566,9 +554,6 @@ Netversion: Map: == Karte: -Info -== Info - Hue == Farb. @@ -1760,3 +1745,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == Im Spiel bewegt + +Go back the specified duration +== Gesetzte Dauer zurück + +[Demo player duration] +%d min. +== %d Min. + +[Demo player duration] +%d sec. +== %d Sek. + +Change the skip duration +== Überspring-Dauer ändern + +Go forward the specified duration +== Gesetzte Dauer vorwärts + +Render cut to video +== Schnitt zu Video rendern diff --git a/data/languages/greek.txt b/data/languages/greek.txt index df3bc468e..60cd82208 100644 --- a/data/languages/greek.txt +++ b/data/languages/greek.txt @@ -145,9 +145,6 @@ Favorites Feet == Πόδια -Filter -== Φίλτρο - Fire == Πυρ @@ -160,9 +157,6 @@ Force vote Free-View == Ελεύθερη Όψη -Friends -== Φίλοι - Fullscreen == Πλήρης οθόνη @@ -359,12 +353,6 @@ Screenshot Server address: == Διεύθ/ση διακομιστή: -Server details -== Λεπτομέριες διακομιστή - -Server filter -== Φίλτρο διακομιστών - Server info == Πληροφορίες @@ -538,9 +526,6 @@ Netversion: Map: == Χάρτης: -Info -== Πληροφορίες - Hue == Απόχρωση @@ -914,12 +899,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -935,12 +943,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -959,6 +961,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1109,10 +1114,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1166,10 +1171,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1178,9 +1189,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1193,9 +1201,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/hungarian.txt b/data/languages/hungarian.txt index 06de9910e..247fbf76d 100644 --- a/data/languages/hungarian.txt +++ b/data/languages/hungarian.txt @@ -144,9 +144,6 @@ Favorites Feet == Láb -Filter -== Szűrő - Fire == Tűz @@ -159,9 +156,6 @@ Force vote Free-View == Szabad nézet -Friends -== Barátok - Fullscreen == Teljesképernyő @@ -349,12 +343,6 @@ Screenshot Server address: == Szerver címe: -Server details -== Szerver részletei - -Server filter -== Szerver szűrő - Server info == Szerver infó @@ -511,9 +499,6 @@ Netversion: Map: == Pálya: -Info -== Infó - Miscellaneous == Egyéb @@ -1703,6 +1688,23 @@ None Add Clan == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Mark the beginning of a cut (right click to reset) == @@ -1721,6 +1723,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/italian.txt b/data/languages/italian.txt index ea3fb2d78..ab5717f5d 100644 --- a/data/languages/italian.txt +++ b/data/languages/italian.txt @@ -152,9 +152,6 @@ Favorites Feet == Piedi -Filter -== Filtro - Fire == Fuoco @@ -167,9 +164,6 @@ Force vote Free-View == Visione libera -Friends -== Amici - Fullscreen == Schermo intero @@ -363,12 +357,6 @@ Screenshot Server address: == Indirizzo server: -Server details -== Dettagli server - -Server filter -== Filtro server - Server info == Info server @@ -536,9 +524,6 @@ Netversion: Map: == Mappa: -Info -== Info - Hue == Tinta @@ -1167,51 +1152,6 @@ Grabs 9+ new mentions == +9 nuove menzioni -[Graphics error] -Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== - -[Graphics error] -Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. -== - -[Graphics error] -An error during command recording occurred. Try to update your GPU drivers. -== - -[Graphics error] -A render command failed. Try to update your GPU drivers. -== - -[Graphics error] -Submitting the render commands failed. Try to update your GPU drivers. -== - -[Graphics error] -Failed to swap framebuffers. Try to update your GPU drivers. -== - -[Graphics error] -Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== - -[Graphics error] -Could not initialize the given graphics backend, reverting to the default backend now. -== - -[Graphics error] -Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. -== - -Could not save downloaded map. Try manually deleting this file: %s -== - -The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== - -The format of texture %s is not RGBA which will cause visual bugs. -== - Preparing demo playback == Preparando la riproduzione della demo @@ -1221,9 +1161,6 @@ Connected Loading map file from storage == Caricando il file mappa dalla memoria -Why are you slowmo replaying to read this? -== - Initializing components == Inizializzando i componenti @@ -1302,55 +1239,18 @@ Join Tutorial Server Skip Tutorial == Salta Tutorial -Loading menu images -== - -AFR -== - -ASI -== - -AUS -== - -EUR -== - -NA -== - -SA -== - -CHN -== - Getting server list from master server == Ottenere l'elenco dei server dal server principale Are you sure that you want to disconnect and switch to a different server? == Sei sicuro di voler disconnetterti e passare a un altro server? -Copy info -== - -Leak IP -== - No server selected == Nessun server selezionato -Online players (%d) -== - Online clanmates (%d) == Compagni di clan online -[friends (server browser)] -Offline (%d) -== - Click to select server. Double click to join your friend. == Fare click per selezionare il server. Fai doppio click per unirti al tuo amico. @@ -1372,123 +1272,9 @@ Are you sure that you want to remove the clan '%s' from your friends list? Add Clan == Aggiungi Clan -Play the current demo -== - -Pause the current demo -== - -Stop the current demo -== - -Go back one tick -== - -Go forward one tick -== - -Slow down the demo -== - -Speed up the demo -== - -Mark the beginning of a cut (right click to reset) -== - -Mark the end of a cut (right click to reset) -== - -Export cut as a separate demo -== - -Go back one marker -== - -Go forward one marker -== - -Close the demo player -== - -Toggle keyboard shortcuts -== - -Export demo cut -== - -Cut interval -== - -Cut length -== - -Loading demo files -== - -All combined -== - -Folder Link -== - -Markers: -== - -%.2f MiB -== - -%.2f KiB -== - -Open the directory that contains the demo files -== - -Are you sure that you want to delete the folder '%s'? -== - -Are you sure that you want to delete the demo '%s'? -== - Delete folder == Cancella cartella -Unable to delete the demo '%s' -== - -Unable to delete the folder '%s'. Make sure it's empty first. -== - -Loading ghost files -== - -Menu opened. Press Esc key again to close menu. -== - -Save power by lowering refresh rate (higher input latency) -== - -Settings file -== - -Open the settings file -== - -Config directory -== - -Open the directory that contains the configuration and user files -== - -Open the directory to add custom themes -== - -Loading skin files -== - -Toggle to edit your dummy settings -== - Download community skins == Scarica skin comunitá @@ -1501,18 +1287,9 @@ Create a random skin Open the directory to add custom skins == Apri la cartella per aggiungere una skin custom -Converse -== - -Chat command -== - Enable controller == Abilitá controller -Controller -== - Ingame controller mode == Modalitá controller in gioco @@ -1520,19 +1297,6 @@ Ingame controller mode Relative == Relativo -[Ingame controller mode] -Absolute -== - -Ingame controller sens. -== - -UI controller sens. -== - -Controller jitter tolerance -== - No controller found. Plug in a controller. == Nessun controller trovato. Collega un controller. @@ -1542,12 +1306,6 @@ Axis Status == Stato -Aim bind -== - -Mouse -== - Ingame mouse sens. == Sens. mouse in gioco @@ -1563,9 +1321,6 @@ Are you sure that you want to reset the controls to their defaults? Cancel == Cancella -Dummy -== - Windowed == Finestra @@ -1578,42 +1333,15 @@ Windowed fullscreen Desktop fullscreen == Desktop schermo intero -Allows maps to render with more detail -== - -Renderer -== - -default -== - -custom -== - Graphics card == Scheda grafica -auto -== - -Appearance -== - -Name Plate -== - Hook Collisions == Collisione Hook -Kill Messages -== - Show health, shields and ammo == Mostra vita, scudi e munizioni -DDRace HUD -== - Show client IDs in scoreboard == Mostra gli ID clienti nella scoreboard @@ -1671,51 +1399,12 @@ Nothing hookable Something hookable == Qualcosa hookabile -A Tee -== - -Normal Color -== - -Highlight Color -== - Weapons == Armi -Rifle Laser Outline Color -== - -Rifle Laser Inner Color -== - -Shotgun Laser Outline Color -== - -Shotgun Laser Inner Color -== - -Door Laser Outline Color -== - -Door Laser Inner Color -== - -Freeze Laser Outline Color -== - -Freeze Laser Inner Color -== - -Set all to Rifle -== - When you cross the start line, show a ghost tee replicating the movements of your best time == Quando attraversi la linea di partenza, mostra una maglietta fantasma che riproduce i movimenti del tuo miglior tempo -Overlay entities -== - Opacity == Opacitá @@ -1746,15 +1435,343 @@ Chat command (e.g. showall 1) Unregister protocol and file extensions == Protocollo ed estensioni file non registrati -Extras -== - Loading assets == Caricando assets Open the directory to add custom assets == Apri la cartella per aggiungere assets custom +Can't find a Tutorial server +== Impossibile trovare un Server Tutorial + +Loading race demo files +== Caricando i file della demo della gara + +Loading sound files +== Caricando file musica + +Moved ingame +== Spostato in gioco + +[Graphics error] +Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. +== + +[Graphics error] +Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. +== + +[Graphics error] +An error during command recording occurred. Try to update your GPU drivers. +== + +[Graphics error] +A render command failed. Try to update your GPU drivers. +== + +[Graphics error] +Submitting the render commands failed. Try to update your GPU drivers. +== + +[Graphics error] +Failed to swap framebuffers. Try to update your GPU drivers. +== + +[Graphics error] +Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. +== + +[Graphics error] +Could not initialize the given graphics backend, reverting to the default backend now. +== + +[Graphics error] +Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. +== + +Could not save downloaded map. Try manually deleting this file: %s +== + +The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. +== + +The format of texture %s is not RGBA which will cause visual bugs. +== + +Why are you slowmo replaying to read this? +== + +Loading menu images +== + +AFR +== + +ASI +== + +AUS +== + +EUR +== + +NA +== + +SA +== + +CHN +== + +Copy info +== + +Leak IP +== + +Online players (%d) +== + +[friends (server browser)] +Offline (%d) +== + +Play the current demo +== + +Pause the current demo +== + +Stop the current demo +== + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Go back one tick +== + +Go forward one tick +== + +Go back one marker +== + +Go forward one marker +== + +Slow down the demo +== + +Speed up the demo +== + +Mark the beginning of a cut (right click to reset) +== + +Mark the end of a cut (right click to reset) +== + +Export cut as a separate demo +== + +Close the demo player +== + +Toggle keyboard shortcuts +== + +Export demo cut +== + +Cut interval +== + +Cut length +== + +Render cut to video +== + +Loading demo files +== + +All combined +== + +Folder Link +== + +Markers: +== + +%.2f MiB +== + +%.2f KiB +== + +Open the directory that contains the demo files +== + +Are you sure that you want to delete the folder '%s'? +== + +Are you sure that you want to delete the demo '%s'? +== + +Unable to delete the demo '%s' +== + +Unable to delete the folder '%s'. Make sure it's empty first. +== + +Loading ghost files +== + +Menu opened. Press Esc key again to close menu. +== + +Save power by lowering refresh rate (higher input latency) +== + +Settings file +== + +Open the settings file +== + +Config directory +== + +Open the directory that contains the configuration and user files +== + +Open the directory to add custom themes +== + +Toggle to edit your dummy settings +== + +Loading skin files +== + +Converse +== + +Chat command +== + +Controller +== + +[Ingame controller mode] +Absolute +== + +Ingame controller sens. +== + +UI controller sens. +== + +Controller jitter tolerance +== + +Aim bind +== + +Mouse +== + +Dummy +== + +Allows maps to render with more detail +== + +Renderer +== + +default +== + +custom +== + +auto +== + +Appearance +== + +Name Plate +== + +Kill Messages +== + +DDRace HUD +== + +A Tee +== + +Normal Color +== + +Highlight Color +== + +Rifle Laser Outline Color +== + +Rifle Laser Inner Color +== + +Shotgun Laser Outline Color +== + +Shotgun Laser Inner Color +== + +Door Laser Outline Color +== + +Door Laser Inner Color +== + +Freeze Laser Outline Color +== + +Freeze Laser Inner Color +== + +Set all to Rifle +== + +Overlay entities +== + +Extras +== + Discord == @@ -1764,20 +1781,8 @@ https://ddnet.org/discord Tutorial == -Can't find a Tutorial server -== Impossibile trovare un Server Tutorial - -Loading race demo files -== Caricando i file della demo della gara - Super == -Loading sound files -== Caricando file musica - FPM == - -Moved ingame -== Spostato in gioco diff --git a/data/languages/japanese.txt b/data/languages/japanese.txt index 4e9459ab8..d9f48df32 100644 --- a/data/languages/japanese.txt +++ b/data/languages/japanese.txt @@ -145,9 +145,6 @@ Favorites Feet == 足 -Filter -== フィルタ - Fire == 射撃 @@ -160,9 +157,6 @@ Force vote Free-View == 自由視点 -Friends -== 友達 - Game == ゲーム @@ -353,12 +347,6 @@ Screenshot Server address: == IP アドレス: -Server details -== サーバー情報 - -Server filter -== サーバーフィルタ - Server info == サーバー情報 @@ -532,9 +520,6 @@ Miscellaneous Netversion: == 通信バージョン: -Info -== 情報 - UI Color == UI カラー @@ -1395,12 +1380,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1416,12 +1424,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1437,6 +1439,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1488,10 +1493,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/korean.txt b/data/languages/korean.txt index 469462054..057614242 100644 --- a/data/languages/korean.txt +++ b/data/languages/korean.txt @@ -154,9 +154,6 @@ Favorites Feet == 발 -Filter -== 필터 - Fire == 발사 @@ -169,9 +166,6 @@ Force vote Free-View == 자유 시점 -Friends -== 친구 - Fullscreen == 전체화면 @@ -359,12 +353,6 @@ Screenshot Server address: == 서버 주소: -Server details -== 서버 세부정보 - -Server filter -== 서버 필터 - Server info == 서버 정보 @@ -550,9 +538,6 @@ Miscellaneous Netversion: == 통신 버전: -Info -== 정보 - UI Color == UI 색상 @@ -1715,6 +1700,23 @@ None Add Clan == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Mark the beginning of a cut (right click to reset) == @@ -1733,6 +1735,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/kyrgyz.txt b/data/languages/kyrgyz.txt index 70744b19d..d7a46067a 100644 --- a/data/languages/kyrgyz.txt +++ b/data/languages/kyrgyz.txt @@ -150,9 +150,6 @@ Favorites Feet == Бут -Filter -== Фильтр - Fire == Атуу @@ -165,9 +162,6 @@ Force vote Free-View == Эркин сереп -Friends -== Достор - Fullscreen == Толук экран @@ -210,9 +204,6 @@ Hook Hue == Түсү -Info -== Маалымат - Internet == Интернет @@ -424,12 +415,6 @@ Screenshot Server address: == Сервер дареги: -Server details -== Сервер деталдары - -Server filter -== Сервер фильтри - Server info == Маалымат @@ -905,12 +890,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -926,12 +934,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -950,6 +952,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1100,10 +1105,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1157,10 +1162,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1169,9 +1180,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1184,9 +1192,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/norwegian.txt b/data/languages/norwegian.txt index 77e1a890c..4860f940f 100644 --- a/data/languages/norwegian.txt +++ b/data/languages/norwegian.txt @@ -147,9 +147,6 @@ Favorites Feet == Føtter -Filter -== Filter - Fire == Skyt @@ -162,9 +159,6 @@ Force vote Free-View == Fri-visning -Friends -== Venner - Fullscreen == Fullskjerm @@ -358,12 +352,6 @@ Screenshot Server address: == Serveradresse: -Server details -== Serverdetaljer - -Server filter -== Serverfilter - Server info == Serverinfo @@ -534,9 +522,6 @@ Netversion: Map: == Bane: -Info -== Info - Hue == Farge @@ -1364,12 +1349,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1385,12 +1393,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1406,6 +1408,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1457,10 +1462,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download community skins diff --git a/data/languages/persian.txt b/data/languages/persian.txt index 255193ba1..46cee0d68 100644 --- a/data/languages/persian.txt +++ b/data/languages/persian.txt @@ -145,9 +145,6 @@ Favorites Feet == ﺎﭘ -Filter -== ﺮﺘﻠﯿﻓ - Fire == ﻚﯿﻠﺷ @@ -160,9 +157,6 @@ Force vote Free-View == ﺩﺍﺯﺁ-ﺪﯾﺩ -Friends -== ﻥﺎﺘﺳﻭﺩ - Fullscreen == ﻞﻣﺎﻛ ى ﻪﺤﻔﺻ @@ -472,9 +466,6 @@ Server address: Refresh == ﯼﺯﺎﺳ ﻩﺯﺎﺗ -Server filter -== ﺭﻭﺮﺳ ﺮﺘﻠﯿﻓ - Server not full == ﺪﺷﺎﺒﻧ ﺮﭘ ﺭﻭﺮﺳ @@ -505,18 +496,12 @@ Types Reset filter == ﺮﺘﻠﯿﻓ ﻥﺩﺮﮐ ﺖﺴﯾﺭ -Server details -== ﺭﻭﺮﺳ ﺕﺎﻋﻼﻃﺍ - Scoreboard == ﺕﺍﺯﺎﯿﺘﻣﺍ ﯼﻮﻠﺑﺎﺗ Remove == ﻥﺩﺮﮐ کﺎﭘ -Info -== ﺕﺎﻋﻼﻃﺍ - Please use a different name == ﻦﮐ ﻩﺩﺎﻔﺘﺳﺍ ﺕﻭﺎﻔﺘﻣ ﻡﺎﻧ ﮏﯾ ﺯﺍ ﺎﻔﻄﻟ @@ -1685,6 +1670,23 @@ None Add Clan == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Mark the beginning of a cut (right click to reset) == @@ -1703,6 +1705,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/polish.txt b/data/languages/polish.txt index 8ea6538ad..ecbee3356 100644 --- a/data/languages/polish.txt +++ b/data/languages/polish.txt @@ -149,9 +149,6 @@ Favorites Feet == Stopy -Filter -== Filtr - Fire == Strzał @@ -164,9 +161,6 @@ Force vote Free-View == Wolna kamera -Friends -== Znajomi - Fullscreen == Pełny ekran @@ -360,12 +354,6 @@ Screenshot Server address: == Adres serwera: -Server details -== Szczegóły serwera - -Server filter -== Filtr serwerów - Server info == Info serwera @@ -551,9 +539,6 @@ Netversion: Map: == Mapa: -Info -== Info - Hue == Kolor @@ -1465,12 +1450,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1486,12 +1494,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1507,6 +1509,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == diff --git a/data/languages/portuguese.txt b/data/languages/portuguese.txt index ce8cca8cf..871cbad50 100644 --- a/data/languages/portuguese.txt +++ b/data/languages/portuguese.txt @@ -152,9 +152,6 @@ Favorites Feet == Pés -Filter -== Filtro - Fire == Disparar @@ -167,9 +164,6 @@ Force vote Free-View == Vista Livre -Friends -== Amigos - Fullscreen == Ecrã inteiro @@ -375,12 +369,6 @@ Screenshot Server address: == Endereço do servidor: -Server details -== Detalhes do servidor - -Server filter -== Filtro de servidores - Server info == Info de servidor @@ -549,9 +537,6 @@ Map: FSAA samples == Amostras FSAA -Info -== Info - Miscellaneous == Diversos @@ -1183,12 +1168,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1204,12 +1212,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1225,6 +1227,9 @@ Cut interval Cut length == +Render cut to video +== + Loading demo files == @@ -1309,10 +1314,10 @@ Open the directory that contains the configuration and user files Open the directory to add custom themes == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1357,10 +1362,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1369,9 +1380,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1384,9 +1392,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/romanian.txt b/data/languages/romanian.txt index b446bcd93..599baffa5 100644 --- a/data/languages/romanian.txt +++ b/data/languages/romanian.txt @@ -151,9 +151,6 @@ Favorites Feet == Picioare -Filter -== Filtre - Fire == Foc @@ -166,9 +163,6 @@ Force vote Free-View == Vizualizare liberă -Friends -== Prieteni - Fullscreen == Ecrat complet @@ -365,12 +359,6 @@ Screenshot Server address: == Adresă server: -Server details -== Detalii server - -Server filter -== Filtru servere: - Server info == Info. server @@ -544,9 +532,6 @@ Netversion: Map: == Harta: -Info -== Informații - Hue == Tentă @@ -920,12 +905,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -941,12 +949,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -965,6 +967,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1115,10 +1120,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1172,10 +1177,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1184,9 +1195,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1199,9 +1207,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/russian.txt b/data/languages/russian.txt index e1dfbe068..a0d3642ed 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -159,9 +159,6 @@ Favorites Feet == Ноги -Filter -== Фильтр - Fire == Стрелять @@ -174,9 +171,6 @@ Force vote Free-View == Свободный обзор -Friends -== Друзья - Fullscreen == Полноэкранный(настр.) @@ -370,12 +364,6 @@ Screenshot Server address: == Адрес сервера: -Server details -== Сведения сервера - -Server filter -== Фильтр серверов - Server info == Информация @@ -607,9 +595,6 @@ Netversion: Map: == Карта: -Info -== Инфо - Hue == Оттен. @@ -1756,3 +1741,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == Перемещены в игре + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/serbian.txt b/data/languages/serbian.txt index 565b6ae6c..9c0c40541 100644 --- a/data/languages/serbian.txt +++ b/data/languages/serbian.txt @@ -149,9 +149,6 @@ Favorites Feet == Stopala -Filter -== Filter - Fire == Pucaj @@ -164,9 +161,6 @@ Force vote Free-View == Slobodan pregled -Friends -== Prijatelji - Fullscreen == Preko celog ekrana @@ -356,12 +350,6 @@ Screenshot Server address: == Adresa servera: -Server details -== Detalji o serveru - -Server filter -== Filter servera - Server info == O serveru @@ -562,9 +550,6 @@ Netversion: Map: == Mapa -Info -== Info - Hue == Nijansa @@ -1507,178 +1492,169 @@ A render command failed. Try to update your GPU drivers. Submitting the render commands failed. Try to update your GPU drivers. == Неуспешно слање рендерних команди. Покушајте ажурирати драјвере за вашу графичку картицу. -[Graphics error] -Failed to swap framebuffers. Try to update your GPU drivers. -== - -[Graphics error] -Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== Неуспешно замена бафера фреймова. Покушајте ажурирати драјвере за вашу графичку картицу. - -[Graphics error] Could not initialize the given graphics backend, reverting to the default backend now. -== +== Није било могуће иницијализовати дати графички позадину, сада се враћамо на подразумевану позадину. [Graphics error] Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. -== +== Није било могуће иницијализовати дати графички позадину, највероватније зато што нисте инсталирали драјвер за интегрисану графичку картицу. Could not save downloaded map. Try manually deleting this file: %s -== +== Није могуће сачувати преузету мапу. Покушајте ручно обрисати овај фајл: %s. Initializing components == Иницијализација компоненти Quitting. Please wait… -== Излазим. Молим вас да почекате… +== Излазим. Молим вас да почекате... Restarting. Please wait… -== Поновно покрећем се. Молим вас да почекате… +== Поновно покрећем се. Молим вас да почекате... Multi-View -== +== Мулти-поглед Rename folder -== +== Преименујте фасциклу A demo with this name already exists -== +== Демо са овим именом већ постоји. A folder with this name already exists -== +== Фасцикла са овим именом већ постоји. Unable to rename the folder -== +== Није могуће преименовати фасциклу. File '%s' already exists, do you want to overwrite it? -== +== Датотека '%s' већ постоји, желите ли да је препишете? (paused) -== +== (паузирано) transmits your player name to info.ddnet.org -== +== преноси ваше играчко име на info.ddnet.org Copy info -== +== Копирај информације No server selected -== +== Није изабран сервер Online players (%d) -== +== Играчи на мрежи (%d) Online clanmates (%d) -== +== Чланови клана на мрежи (%d) [friends (server browser)] Offline (%d) -== +== Изван мреже (%d) Click to select server. Double click to join your friend. -== +== Кликните да бисте изабрали сервер. Дупли клик за придруживање пријатељу. Click to remove this player from your friends list. -== +== Кликните да бисте уклонили овог играча са листе пријатеља. Click to remove this clan from your friends list. -== +== Кликните да бисте уклонили овај клан са листе пријатеља. None -== +== Ништа Are you sure that you want to remove the player '%s' from your friends list? -== +== Да ли сте сигурни да желите да уклоните играча '%s' са листе пријатеља? Are you sure that you want to remove the clan '%s' from your friends list? -== +== Да ли сте сигурни да желите да уклоните клан '%s' са листе пријатеља? Add Clan -== +== Додај клан Play the current demo -== +== Пусти тренутни демо запис Pause the current demo -== +== Паузирај тренутни демо запис Stop the current demo -== +== Заустави тренутни демо запис Go back one tick -== +== Иди назад један корак Go forward one tick -== +== Иди напред један корак Slow down the demo -== +== Успори демо запис Speed up the demo -== +== Убрзај демо запис Mark the beginning of a cut (right click to reset) -== +== Обележи почетак сечења (десни клик за ресетовање) Mark the end of a cut (right click to reset) -== +== Обележи крај сечења (десни клик за ресетовање) Export cut as a separate demo -== +== Извоз сечења као засебног демо записа Go back one marker -== +== Иди назад један маркер Go forward one marker -== +== Иди напред један маркер Close the demo player -== +== Затвори репродуктор демо записа Toggle keyboard shortcuts -== +== Пребациванје тастатурних пречица Export demo cut -== +== Извоз демо сечења Cut interval -== +== Интервал сечења Cut length -== +== Дужина сечења All combined -== +== Све комбиновано Folder Link -== +== Веза до фасцикле Open the directory that contains the demo files -== +== Отвори директоријум који садржи демо фајлове Are you sure that you want to delete the folder '%s'? -== +== Да ли сте сигурни да желите да обришете фасциклу '%s'? Are you sure that you want to delete the demo '%s'? -== +== Да ли сте сигурни да желите да обришете демо запис '%s'? Delete folder -== +== Обриши фасциклу Unable to delete the demo '%s' -== +== Није могуће обрисати демо запис '%s' Unable to delete the folder '%s'. Make sure it's empty first. -== +== Није могуће обрисати фасциклу '%s'. Проверите прво да ли је празна. Menu opened. Press Esc key again to close menu. -== +== Мени је отворен. Поново притисните тастер Esc да бисте затворили мени. Save power by lowering refresh rate (higher input latency) -== +== Уштедите струју смањивањем стопе освежавања (већа улазна латенција) Open the settings file -== +== Отворите датотеку са подешавањима Open the directory that contains the configuration and user files == Отворите директоријум који садржи конфигурационе и корисничке датотеке. @@ -1747,4 +1723,32 @@ Open the directory to add custom assets == Отворите директоријум за додавање прилагођених ресурса Moved ingame +== Померено у игри + +[Graphics error] +Failed to swap framebuffers. Try to update your GPU drivers. +== + +[Graphics error] +Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. +== + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video == diff --git a/data/languages/serbian_cyrillic.txt b/data/languages/serbian_cyrillic.txt index 97f7586a1..07f602eda 100644 --- a/data/languages/serbian_cyrillic.txt +++ b/data/languages/serbian_cyrillic.txt @@ -145,9 +145,6 @@ Favorites Feet == Стопала -Filter -== Филтер - Fire == Пуцај @@ -160,9 +157,6 @@ Force vote Free-View == Слободан преглед -Friends -== Пријатељи - Fullscreen == Преко целог екрана @@ -352,12 +346,6 @@ Screenshot Server address: == Адреса сервера: -Server details -== Детаљи о серверу - -Server filter -== Филтер сервера - Server info == О серверу @@ -558,9 +546,6 @@ Netversion: Map: == Мапа -Info -== Инфо - Hue == Нијанса @@ -1601,12 +1586,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -1622,12 +1630,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -1643,6 +1645,9 @@ Cut interval Cut length == +Render cut to video +== + All combined == diff --git a/data/languages/simplified_chinese.txt b/data/languages/simplified_chinese.txt index ff783ed84..c3016e9d2 100644 --- a/data/languages/simplified_chinese.txt +++ b/data/languages/simplified_chinese.txt @@ -177,9 +177,6 @@ Favorites Feet == 脚 -Filter -== 筛选 - Fire == 开火 @@ -192,9 +189,6 @@ Force vote Free-View == 自由视角 -Friends -== 好友 - Fullscreen == 独占全屏 @@ -415,12 +409,6 @@ Screenshot Server address: == 服务器地址: -Server details -== 服务器详情 - -Server filter -== 服务器筛选 - Server info == 服务器信息 @@ -616,9 +604,6 @@ Netversion: Map: == 地图: -Info -== 信息 - Hue == 色调 @@ -1780,3 +1765,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == 游戏内移动 + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/slovak.txt b/data/languages/slovak.txt index fb8fc15c1..98fb93d3a 100644 --- a/data/languages/slovak.txt +++ b/data/languages/slovak.txt @@ -145,9 +145,6 @@ Favorites Feet == Nohy -Filter -== Filter - Fire == Streľba @@ -160,9 +157,6 @@ Force vote Free-View == Voľná Kamera -Friends -== Priatelia - Fullscreen == Celá obrazovka @@ -356,12 +350,6 @@ Screenshot Server address: == Adresa servera: -Server details -== Detaily servera - -Server filter -== Filter serverov - Server info == Informácie @@ -532,9 +520,6 @@ Netversion: Map: == Mapa: -Info -== Info - Hue == Hue @@ -911,12 +896,35 @@ Pause the current demo Stop the current demo == +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + Go back one tick == Go forward one tick == +Go back one marker +== + +Go forward one marker +== + Slow down the demo == @@ -932,12 +940,6 @@ Mark the end of a cut (right click to reset) Export cut as a separate demo == -Go back one marker -== - -Go forward one marker -== - Close the demo player == @@ -956,6 +958,9 @@ Cut length Remove chat == +Render cut to video +== + Please use a different name == @@ -1106,10 +1111,10 @@ Max CSVs Dummy settings == -Loading skin files +Toggle to edit your dummy settings == -Toggle to edit your dummy settings +Loading skin files == Download skins @@ -1163,10 +1168,16 @@ Show all Toggle dyncam == -Toggle dummy +Toggle ghost == -Toggle ghost +Converse +== + +Chat command +== + +Toggle dummy == Dummy copy @@ -1175,9 +1186,6 @@ Dummy copy Hammerfly dummy == -Converse -== - Statboard == @@ -1190,9 +1198,6 @@ Show entities Show HUD == -Chat command -== - Enable controller == diff --git a/data/languages/spanish.txt b/data/languages/spanish.txt index 593fa8238..5746d9ee8 100644 --- a/data/languages/spanish.txt +++ b/data/languages/spanish.txt @@ -164,9 +164,6 @@ Favorites Feet == Pies -Filter -== Filtro - Fire == Disparar @@ -179,9 +176,6 @@ Force vote Free-View == Vista libre -Friends -== Amigos - Fullscreen == Pantalla completa @@ -378,12 +372,6 @@ Screenshot Server address: == IP del servidor: -Server details -== Detalles del servidor - -Server filter -== Filtro del servidor - Server info == Servidor @@ -557,9 +545,6 @@ Netversion: Map: == Mapa: -Info -== Información - Hue == Matiz @@ -1763,3 +1748,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == Movido dentro del juego + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/swedish.txt b/data/languages/swedish.txt index 11c16d3ab..808c9b73d 100644 --- a/data/languages/swedish.txt +++ b/data/languages/swedish.txt @@ -7,6 +7,7 @@ # 3edcxzaq1 2020-06-25 00:00:00 # cur.ie 2020-09-28 00:00:00 # simpygirl 2022-02-20 00:00:00 +# furo 2023-08-29 00:00:00 ##### /authors ##### ##### translated strings ##### @@ -149,9 +150,6 @@ Favorites Feet == Fötter -Filter -== Filter - Fire == Skjuta @@ -164,9 +162,6 @@ Force vote Free-View == Friläge -Friends -== Kompisar - Fullscreen == Fullskärm @@ -207,7 +202,7 @@ Hook == Hook Invalid Demo -== Ogiltigt deo +== Ogiltig demo Join blue == Spela i blått @@ -343,7 +338,7 @@ Rename demo == Byt namn på demo Reset filter -== Återställ filter +== Nollställ filter Score == Poäng @@ -360,12 +355,6 @@ Screenshot Server address: == Serveradress -Server details -== Serverdetaljer - -Server filter -== Serverfilter - Server info == Serverinfo @@ -415,7 +404,7 @@ Strict gametype filter == Strikt speltypsfilter Sudden Death -== Plötslig död +== Sudden Death Switch weapon on pickup == Byt vapen vid anskaffning @@ -433,7 +422,7 @@ The server is running a non-standard tuning on a pure game type. == Denna server kör inte standardinställningar på en reserverad speltyp. There's an unsaved map in the editor, you might want to save it before you quit the game. -== Det finns en osparad bana i redigeraden, du kanske vill spara den innan du avslutar. +== Det finns en osparad bana i redigeraren, du kanske vill spara den innan du avslutar. Time limit == Tidsbegränsning @@ -534,9 +523,6 @@ Netversion: Map: == Bana -Info -== Info - Hue == Nyans @@ -550,7 +536,7 @@ Size: == Storlek: Reset to defaults -== Återställ till standard +== Nollställ till standard Quit anyway? == Avsluta i alla fall? @@ -607,7 +593,7 @@ Show kill messages == Visa döds meddelanden Reset -== Återställ +== Nollställ DDNet == DDNet @@ -643,7 +629,7 @@ Reconnect in %d sec == Återkopplar om %d sekunder Successfully saved the replay! -== Lyckades med att spara repris +== Lyckades med att spara repris! Save ghost == Spara spöken @@ -676,13 +662,13 @@ Show votes window after voting == Visa röstnings fönster efter röstning DDNet Client needs to be restarted to complete update! -== DDNet Klienten behöves startas om för att genomföra updateringen! +== DDNet Klienten behövs startas om för att genomföra uppdateringen! Kill == Dö Personal best: -== Personligs bästa: +== Personligt bästa: Show DDNet map finishes in server browser == Visa DDNet bana avklarningar i server bläddraren @@ -706,7 +692,7 @@ Render == Rendera Are you sure that you want to disconnect? -== Är du säker att du vill koppla ifrån? +== Är du säker på att du vill koppla ifrån? Grabs == Grabs @@ -808,7 +794,7 @@ Date == Datum Show other players' hook collision lines -== Visa andra spelares hook kollision linor +== Visa andra spelares hook kollisions linjer Fetch Info == Hämta Info @@ -874,7 +860,7 @@ Show only chat messages from friends == Visa bara chatt meddelanden från vänner DDNet Client updated! -== DDNet Klienten updaterades! +== DDNet Klienten uppdaterades! Converse == Konversera @@ -916,7 +902,7 @@ Best == Bäst Updating... -== Updaterar... +== Uppdaterar... Clan plates size == Klanskylt storlek @@ -925,7 +911,7 @@ Size == Storlek Save the best demo of each race -== Spara the bästa demon av varje race +== Spara den bästa demot av varje race Frags == Frags @@ -997,7 +983,7 @@ Show names in chat in team colors == Visa namn in chatten med lagets färger Update now -== Updatera nu +== Uppdatera nu Toggle ghost == Växla spöke @@ -1009,7 +995,7 @@ Team message == Lag meddelande File already exists, do you want to overwrite it? -== Filen finns redan, vill du skriv över den? +== Filen finns redan, vill du skriva över den? Hook collisions == Hook kollisioner @@ -1069,7 +1055,7 @@ Server executable not found, can't run server == Server exekveringsfil hittades ej, kan ej starta servern Editor -== Editor +== Redigeraren [Start menu] Play @@ -1091,7 +1077,7 @@ Saving ddnet-settings.cfg failed == Det gick inte att spara ddnet-settings.cfg The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== Bredden på texturen %s är inte delbar med %d, eller höjden är inte delbar med %d, vilket kan orsaka visuella buggar. +== Bredden på texturen %s är inte delbar med %d, eller är höjden inte delbar med %d, vilket kan orsaka visuella buggar. Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. == Felsökningsläge är aktiverad, Klicka Ctrl+Shift+D för att stänga av felsökningsläge. @@ -1106,7 +1092,7 @@ Checking for existing player with your name == Söker efter en befintlig spelare med ditt namn Are you sure that you want to disconnect and switch to a different server? -== Är du säker att du vill koppla ifrån och byta server? +== Är du säker på att du vill koppla ifrån och byta server? Theme == Tema @@ -1223,7 +1209,7 @@ Entities == Entities Emoticons -== Emoticons +== Känsloikoner Particles == Partiklar @@ -1239,510 +1225,530 @@ https://ddnet.org/discord [Graphics error] Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== +== Misslyckades under uppstart. Testa att ändra gfx_backend till OpenGL eller Vulkan i settings_ddnet.cfg i konfig nappen och försök igen. [Graphics error] Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. -== +== Slut på VRAM. Testa att ta bort assets (skins, entities, etc.), speciellt dem i hög upplösning. [Graphics error] An error during command recording occurred. Try to update your GPU drivers. -== +== Ett fel uppstod under "command recording". Testa att uppdatera ditt grafikkorts drivrutiner. [Graphics error] A render command failed. Try to update your GPU drivers. -== +== Ett "render command" misslyckades. Testa att uppdatera ditt grafikkorts drivrutiner. [Graphics error] Submitting the render commands failed. Try to update your GPU drivers. -== +== Inskickning av "render commands" misslyckades. Testa att uppdatera ditt grafikkorts drivrutiner. [Graphics error] Failed to swap framebuffers. Try to update your GPU drivers. -== +== Misslyckades att "swap framebuffers". Testa att uppdatera ditt grafikkorts drivrutiner. [Graphics error] Unknown error. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== +== Okänt fel. Testa att ändra gfx_backend till OpenGL eller Vulkan i settings_ddnet.cfg i konfig nappen och försök igen. [Graphics error] Could not initialize the given graphics backend, reverting to the default backend now. -== +== Kunde inte starta den angivna grafikbackend, återgår till standard backend nu. [Graphics error] Could not initialize the given graphics backend, this is probably because you didn't install the driver of the integrated graphics card. -== +== Kunde inte starta den angivna grafikbackend, detta beror någ på att du inte har installerad drivrutinerna för ditt integrerade grafikkort. Could not save downloaded map. Try manually deleting this file: %s -== +== Kunde inte spara nedladdade bana. Testa att manuellt ta bort denna fil: %s The format of texture %s is not RGBA which will cause visual bugs. -== +== Formatet av texturn %s är inte RGBA, vilket kommer att orsaka visuella buggar. Preparing demo playback -== +== Förbreder för demo uppspelning Connected -== +== Ansluten Loading map file from storage -== +== Laddar bana fil från lagring Why are you slowmo replaying to read this? -== +== Varför försöker du att läsa detta? Initializing components -== +== Initierar komponenter Initializing assets -== +== Initierar assets Initializing map logic -== +== Initierar bana logik Sending initial client info -== +== Skickar första klient info Quitting. Please wait… -== +== Lämnar. Vänligen vänta… Restarting. Please wait… -== +== Startar om. Vänligen vänta… Position: -== +== Position Speed: -== +== Hastighet: Angle: -== +== Vinkel: Multi-View -== +== Multi-Vy Team %d -== +== Lag %d Uploading map data to GPU -== +== Laddar upp bana data till grafikkortet Trying to determine UDP connectivity... -== +== Försöker att bestämma UDP anslutning... UDP seems to be filtered. -== +== UDP verkar vara filtrerad. UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators. -== +== UDP och TCP IP adresser verkar vara olika. Testa att stänga av VPN, proxy eller nätverksacceleratorer. No answer from server yet. -== +== Inget svar från servern än. Getting game info -== +== Hämtar spel info Requesting to join the game -== +== Begär att få ansluta till spelet. Rename folder -== +== Döp om mapp. A demo with this name already exists -== +== Ett demo med detta namn finns redan A folder with this name already exists -== +== En mapp finns redan med detta namn Unable to rename the folder -== +== Misslyckades att döpa om mappen File '%s' already exists, do you want to overwrite it? -== +== Fil '%s' finns redan, vill du skriva över den? (paused) -== +== (pausad) Join Tutorial Server -== +== Anslut till Tutorial Skip Tutorial -== +== Skippa Tutorial Loading menu images -== +== Laddar meny bilder Copy info -== +== Kopiera info No server selected -== +== Ingen server vald Online players (%d) -== +== Online spelare (%d) Online clanmates (%d) -== +== Online klanmedlemmar (%d) [friends (server browser)] Offline (%d) -== +== Offline (%d) Click to select server. Double click to join your friend. -== +== Klicka för att välja server. Dubbel klicka för att ansluta till din kamrat. Click to remove this player from your friends list. -== +== Klicka för att ta bort denna spelare från din kompis lista. Click to remove this clan from your friends list. -== +== Klicka för att ta bort denna klan från din kompis lista. None -== +== Ingen Are you sure that you want to remove the player '%s' from your friends list? -== +== Är du säker på att du vill ta bort spelare '%s' från din kompis lista? Are you sure that you want to remove the clan '%s' from your friends list? -== +== Är du säker på att du vill ta bort klanen '%s' från din kompis lista? Add Clan -== +== Lägg till klan Play the current demo -== +== Spela demo Pause the current demo -== +== Pausa demo Stop the current demo -== +== Stoppa demo Go back one tick -== +== Gå tillbaka en tick Go forward one tick -== +== Gå framåt en tick Slow down the demo -== +== Sakta ner demot Speed up the demo -== +== Snabba up demot Mark the beginning of a cut (right click to reset) -== +== Markera start av snittet (höger klicka för att nollställa) Mark the end of a cut (right click to reset) -== +== Markera slutet av snittet (höger klicka för att nollställa) Export cut as a separate demo -== +== Exportera snitt till en seperat demo fil Go back one marker -== +== Gå tillbaka en markör Go forward one marker -== +== Gå framåt en markör Close the demo player -== +== Stäng demo spelaren Toggle keyboard shortcuts -== +== Växla kortkommandon Export demo cut -== +== Exportera demo snitt Cut interval -== +== Snitt interval Cut length -== +== Snitt längd Loading demo files -== +== Laddar demo filer All combined -== +== Alla kombinerade Folder Link -== +== Mapp länk Open the directory that contains the demo files -== +== Öppna mappen som innehåller demo filerna Are you sure that you want to delete the folder '%s'? -== +== Är du säker på att du vill ta bort mappen '%s'? Are you sure that you want to delete the demo '%s'? -== +== Är du säker på att du vill ta bort demot '%s'? Delete folder -== +== Ta bort mapp Unable to delete the demo '%s' -== +== Misslyckades att ta bort demot '%s' Unable to delete the folder '%s'. Make sure it's empty first. -== +== Misslyckades att ta bort mappen '%s'. Den måste vara tom först. Loading ghost files -== +== Laddar spök filer Menu opened. Press Esc key again to close menu. -== +== Meny öppnad. Klicka Esc igen för att stänga menyn. Save power by lowering refresh rate (higher input latency) -== +== Spara batteri genom att sänka uppdateringsfrekvensen (högre inmatnings latens) Open the settings file -== +== Öppna inställningsfil Open the directory that contains the configuration and user files -== +== Öppna mappen som innehåller konfigurationen och användarfiler Open the directory to add custom themes -== +== Öppna mappen för att lägga till egna teman Loading skin files -== +== Laddar skin filer Toggle to edit your dummy settings -== +== Växla till att ändra dina dummy inställningar Download community skins -== +== Ladda ner community skins Choose default eyes when joining a server -== +== Välj standard ögon när du ansluter till en server Create a random skin -== +== Skapa ett slumpad skin Open the directory to add custom skins -== +== Öppna mappen för att lägga till egna skins Enable controller -== +== Aktivera kontroller Controller -== +== Kontroller Ingame controller mode -== +== Kontroller läge under spel [Ingame controller mode] Relative -== +== Relativ [Ingame controller mode] Absolute -== +== Absolut Ingame controller sens. -== +== Kontroller känslighet i spelet. UI controller sens. -== +== Kontroller känslighet i menyer. Controller jitter tolerance -== +== Kontroller skaka tolerans No controller found. Plug in a controller. -== +== Ingen kontroller hittad. Anslut en kontroller. Axis -== +== Axel Status -== +== Status Aim bind -== +== Aim bind Mouse -== +== Mus Ingame mouse sens. -== +== Mus känslighet i spelet. UI mouse sens. -== +== Mus känslighet i menyer. Reset controls -== +== Nollställ kontrollerna Are you sure that you want to reset the controls to their defaults? -== +== Är du säker på att du vill nollställa kontrollerna till standard? Cancel -== +== Avbryt Allows maps to render with more detail -== +== Tillåt banor att visa mer detaljer Renderer -== +== Renderer default -== +== standard custom -== +== egna Graphics card -== +== Grafikkort auto -== +== auto Appearance -== +== Utseende Name Plate -== +== Namnskylt Hook Collisions -== +== Hook kollisioner Show health, shields and ammo -== +== Visa hälsa, sköldar och ammunition DDRace HUD -== +== DDRace HUD Show client IDs in scoreboard -== +== Visa klient ID i poänglista Show DDRace HUD -== +== Visa DDRace HUD Show jumps indicator -== +== Visa hopp indikator Show dummy actions -== +== Visa dummy actions Show player position -== +== Visa spelarens position Show player speed -== +== Visa spelarens hastighet Show player target angle -== +== Visa spelarens vinkel Show freeze bars -== +== Visa freeze bars Opacity of freeze bars inside freeze -== +== Opacitet av freeze bars i freeze Show hook strength indicator -== +== Visa hook styrka indikator Hook collision line -== +== Hook kollisions linje Hook collision line width -== +== Hook kollisions linje bredd Hook collision line opacity -== +== Hook kollisions linje opacitet Colors of the hook collision line, in case of a possible collision with: -== +== Färger av hook kollisions linje, ifall det finns en kollision med: Your movements are not taken into account when calculating the line colors -== +== Dina rörelser tas inte med i beräkningen av linjefärgerna Nothing hookable -== +== Inget hookable Something hookable -== +== Något hookable A Tee -== +== En Tee Normal Color -== +== Normal Färg Highlight Color -== +== Betonad Färg Weapons -== +== Vapen Rifle Laser Outline Color -== +== Gevär Laser Kontur Färg Rifle Laser Inner Color -== +== Gevär Laser Inre Färg Shotgun Laser Outline Color -== +== Hagelgevär Laser Kontur Färg Shotgun Laser Inner Color -== +== Hagelgevär Inre Färg Door Laser Outline Color -== +== Dörr Laser Kontur Färg Door Laser Inner Color -== +== Dörr Laser Inre Färg Freeze Laser Outline Color -== +== Freeze Laser Kontur Färg Freeze Laser Inner Color -== +== Freeze Laser Inre Färg Set all to Rifle -== +== Sätt alla till Gevär When you cross the start line, show a ghost tee replicating the movements of your best time -== +== När du passera start linjen, visa en spök tee som visar rörelsen av din bästa tid Opacity -== +== Opacitet Adjust the opacity of entities belonging to other teams, such as tees and nameplates -== +== Justera opaciteten av entities som tillhör ett annat lag, som t.ex. tees och namnskyltar Quads are used for background decoration -== +== Quads används till bakgrunds decorationer Tries to predict other entities to give a feel of low latency -== +== Försöker att predicera andra entities för att ge känslan av låg latens. Unregister protocol and file extensions -== +== Avregistera protokoll och filtillägg Extras -== +== Extras Loading assets -== +== Laddar assets Open the directory to add custom assets -== +== Öppna mappen för att lägga till egna assets Tutorial -== +== Tutorial Can't find a Tutorial server -== +== Kunde inte hitta en Tutorial server Loading race demo files -== +== Laddar race demo filer Super -== +== Super Loading sound files -== +== Laddar ljud filer Moved ingame -== +== Förflyttades i spelet + +Render cut to video +== Rendera snitt till video + +Go back the specified duration +== Gå tillbaka den angivna tiden + +[Demo player duration] +%d min. +== %d min. + +[Demo player duration] +%d sec. +== %d sek. + +Change the skip duration +== Ändra tiden för framspolning + +Go forward the specified duration +== Gå framåt den angivna tiden diff --git a/data/languages/traditional_chinese.txt b/data/languages/traditional_chinese.txt index 844f03e2e..eef29643e 100644 --- a/data/languages/traditional_chinese.txt +++ b/data/languages/traditional_chinese.txt @@ -166,9 +166,6 @@ Favorites Feet == 腳 -Filter -== 過濾器 - Fire == 開火 @@ -181,9 +178,6 @@ Force vote Free-View == 自由視角 -Friends -== 好友 - Fullscreen == 獨占全螢幕 @@ -404,12 +398,6 @@ Screenshot Server address: == 伺服器地址: -Server details -== 伺服器詳細資訊 - -Server filter -== 伺服器過濾器 - Server info == 伺服器資訊 @@ -605,9 +593,6 @@ Netversion: Map: == 地圖: -Info -== 資訊 - Hue == 色調 @@ -1769,3 +1754,23 @@ Unable to delete the folder '%s'. Make sure it's empty first. Moved ingame == 遊戲內移動 + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/turkish.txt b/data/languages/turkish.txt index bfaced9e3..c433123a5 100644 --- a/data/languages/turkish.txt +++ b/data/languages/turkish.txt @@ -8,7 +8,8 @@ # Learath2 2012-01-01 22:54:29 # ardadem 2020-08-20 00:00:00 # ardadem 2020-08-22 00:00:00 -# h-kaan 2023-08-23 15:57:36 +# h-kaan 2023-08-23 15:57:36 +# h-kaan 2023-08-30 01:52:04 ##### /authors ##### ##### translated strings ##### @@ -151,9 +152,6 @@ Favorites Feet == Ayak -Filter -== Filtre - Fire == Ateş @@ -166,9 +164,6 @@ Force vote Free-View == Serbest Bakış -Friends -== Arkadaşlar - Fullscreen == Tam ekran @@ -359,12 +354,6 @@ Screenshot Server address: == Sunucu adresi: -Server details -== Sunucucu detayları - -Server filter -== Filtreler - Server info == Hakkında @@ -533,9 +522,6 @@ Netversion: Map: == Harita: -Info -== Bilgi - Hue == Renk @@ -1130,13 +1116,13 @@ Debug mode enabled. Press Ctrl+Shift+D to disable debug mode. == Hata ayıklama modu etkinleştirildi. Devre dışı bırakmak için Ctrl+Shift+D tuşlarına basın. Position: -== Pozisyon +== Pozisyon: Speed: -== Hız +== Hız: Angle: -== Açı +== Açı: Multi-View == Çoklu İzle @@ -1154,7 +1140,7 @@ UDP seems to be filtered. == UDP filtrelenmiş görünüyor. UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators. -== UPD ve TCP IP adresleri farklı görünüyor. Proxy, VPN veya ağ hızlandırıcılarını devre dışı bırakmayı deneyin. +== UDP ve TCP IP adresleri farklı görünüyor. Proxy, VPN veya ağ hızlandırıcılarını devre dışı bırakmayı deneyin. No answer from server yet. == Henüz sunucudan cevap alınamadı. @@ -1172,7 +1158,7 @@ Existing Player == Var olan oyuncu Your nickname '%s' is already used (%d points). Do you still want to use it? -== Takma adınız '%s' başkası tarafından kullanılıyor (%d points). Yine de kullanmak istiyor musunuz? +== Takma adınız '%s' başkası tarafından kullanılıyor (%d puan). Yine de kullanmak istiyor musunuz? Checking for existing player with your name == Adınıza sahip diğer oyuncular aranıyor @@ -1238,7 +1224,7 @@ Getting server list from master server == %2$d sunucunun %1$d tanesi %d players -== %d oyuncular +== %d oyuncu %d player == %d oyuncu @@ -1308,13 +1294,13 @@ Speed up the demo == Demoyu hızlandır Mark the beginning of a cut (right click to reset) -== Bir kesimin başlangıcını işaretleyin (sıfırlamak için sağ tıkla) +== Bir kesitin başlangıcını işaretleyin (sıfırlamak için sağ tıkla) Mark the end of a cut (right click to reset) -== Bir kesimin bitişini işaretleyin (sıfırlamak için sağ tıkla) +== Bir kesitin bitişini işaretleyin (sıfırlamak için sağ tıkla) Export cut as a separate demo -== Kesimi ayrı bir demo olarak dışarı aktar +== Kesiti ayrı bir demo olarak dışarı aktar Go back one marker == Bir işaret geri git @@ -1329,13 +1315,13 @@ Toggle keyboard shortcuts == Klavye kısayollarını kullan Export demo cut -== Kesimi dışarı aktar +== Kesiti dışarı aktar Cut interval -== Kesim aralığı +== Kesit aralığı Cut length -== Kesim uzunluğu +== Kesit uzunluğu Loading demo files == Demo dosyaları yükleniyor @@ -1472,10 +1458,10 @@ Mouse == Fare Ingame mouse sens. -== Oyun içi fare hassasiyeti +== Oyun hassasiyeti UI mouse sens. -== Arayüz fare hassasiyeti +== Arayüz hassasiyeti Reset controls == Kontrolleri sıfırla @@ -1556,7 +1542,7 @@ Show DDRace HUD == DDRace HUD göster Show jumps indicator -== Zıplama göstergesini göster +== Kaç zıplama kaldığını göster Show dummy actions == Dummy aksiyonlarını göster @@ -1749,4 +1735,24 @@ Loading sound files == Ses dosyaları yükleniyor Moved ingame -== Hareket edildi \ No newline at end of file +== Hareket edildi + +Go back the specified duration +== + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/languages/ukrainian.txt b/data/languages/ukrainian.txt index a9b19dbdc..8f22bef86 100644 --- a/data/languages/ukrainian.txt +++ b/data/languages/ukrainian.txt @@ -1749,3 +1749,20 @@ Go forward the specified duration Render cut to video == Відтворити вирізку відео + +[Demo player duration] +%d min. +== + +[Demo player duration] +%d sec. +== + +Change the skip duration +== + +Go forward the specified duration +== + +Render cut to video +== diff --git a/data/skins/default.png b/data/skins/default.png index f167ddeb3..6c881d76d 100644 Binary files a/data/skins/default.png and b/data/skins/default.png differ diff --git a/datasrc/network.py b/datasrc/network.py index 6175cf16f..6d5f18309 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -26,7 +26,7 @@ GameInfoFlags = [ ] GameInfoFlags2 = [ "ALLOW_X_SKINS", "GAMETYPE_CITY", "GAMETYPE_FDDRACE", "ENTITIES_FDDRACE", "HUD_HEALTH_ARMOR", "HUD_AMMO", - "HUD_DDRACE", "NO_WEAK_HOOK" + "HUD_DDRACE", "NO_WEAK_HOOK", "NO_SKIN_CHANGE_FOR_FROZEN" ] ExPlayerFlags = ["AFK", "PAUSED", "SPEC"] LegacyProjectileFlags = [f"CLIENTID_BIT{i}" for i in range(8)] + [ @@ -71,7 +71,7 @@ enum enum { - GAMEINFO_CURVERSION=8, + GAMEINFO_CURVERSION=9, }; ''' @@ -549,4 +549,8 @@ Messages = [ NetIntRange("m_Team", 0, 'MAX_CLIENTS-1'), NetIntRange("m_First", -1, 'MAX_CLIENTS-1'), ]), + + NetMessageEx("Sv_YourVote", "yourvote@netmsg.ddnet.org", [ + NetIntRange("m_Voted", -1, 1), + ]), ] diff --git a/scripts/send_named_pipe.ps1 b/scripts/send_named_pipe.ps1 index fa659deb3..f3dcfe86c 100644 --- a/scripts/send_named_pipe.ps1 +++ b/scripts/send_named_pipe.ps1 @@ -4,7 +4,7 @@ # The second argument is the message to send. if ($args.length -lt 2) { Write-Output "Usage: ./send_named_pipe.ps1 [message] ... [message]" - return + exit -1 } $Wrapper = [pscustomobject]@{ @@ -18,16 +18,24 @@ $Wrapper = [pscustomobject]@{ Reader = $null Writer = $null } -$Wrapper.Pipe.Connect(5000) -if (!$?) { - return +try { + $Wrapper.Pipe.Connect(5000) + $Wrapper.Reader = New-Object System.IO.StreamReader($Wrapper.Pipe) + $Wrapper.Writer = New-Object System.IO.StreamWriter($Wrapper.Pipe) + $Wrapper.Writer.AutoFlush = $true + for ($i = 1; $i -lt $args.length; $i++) { + $Wrapper.Writer.WriteLine($args[$i]) + } + # Wait for pipe contents to be read. + $Wrapper.Pipe.WaitForPipeDrain() + # Dispose the pipe, which also calls Flush and Close. + $Wrapper.Pipe.Dispose() + # Explicity set error level 0 for success, as otherwise the current error level is kept. + exit 0 +} catch [TimeoutException] { + Write-Output "Timeout connecting to pipe" + exit 1 +} catch [System.IO.IOException] { + Write-Output "Broken pipe" + exit 2 } -$Wrapper.Reader = New-Object System.IO.StreamReader($Wrapper.Pipe) -$Wrapper.Writer = New-Object System.IO.StreamWriter($Wrapper.Pipe) -$Wrapper.Writer.AutoFlush = $true -for ($i = 1; $i -lt $args.length; $i++) { - $Wrapper.Writer.WriteLine($args[$i]) -} -# We need to wait because the lines will not be written if we close the pipe immediately -Start-Sleep -Seconds 1.5 -$Wrapper.Pipe.Close() diff --git a/src/base/logger.h b/src/base/logger.h index e3f89a01a..1324d84d1 100644 --- a/src/base/logger.h +++ b/src/base/logger.h @@ -103,7 +103,7 @@ public: */ virtual void GlobalFinish() {} /** - * Notifies thte logger of a changed `m_Filter`. + * Notifies the logger of a changed `m_Filter`. */ virtual void OnFilterChange() {} }; diff --git a/src/base/rust.rs b/src/base/rust.rs index 540bd27ed..b666f1181 100644 --- a/src/base/rust.rs +++ b/src/base/rust.rs @@ -11,7 +11,7 @@ use std::str; /// /// Callbacks in C are usually represented by a function pointer and some /// "userdata" pointer that is also passed to the function pointer. This allows -/// to hand data to the callback. This type represents such a userdata poiner. +/// to hand data to the callback. This type represents such a userdata pointer. /// /// It is `unsafe` to convert the `UserPtr` back to its original pointer using /// [`UserPtr::cast`] because its lifetime and type information was lost. diff --git a/src/base/system.cpp b/src/base/system.cpp index 079286342..89a09f68c 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -2209,16 +2209,12 @@ int net_would_block() #endif } -int net_init() +void net_init() { #if defined(CONF_FAMILY_WINDOWS) - WSADATA wsaData; - int err = WSAStartup(MAKEWORD(1, 1), &wsaData); - dbg_assert(err == 0, "network initialization failed."); - return err == 0 ? 0 : 1; + WSADATA wsa_data; + dbg_assert(WSAStartup(MAKEWORD(1, 1), &wsa_data) == 0, "network initialization failed."); #endif - - return 0; } #if defined(CONF_FAMILY_UNIX) diff --git a/src/base/system.h b/src/base/system.h index 98490f1ed..c1f01bfe3 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -879,11 +879,9 @@ typedef struct sockaddr_un UNIXSOCKETADDR; * * @ingroup Network-General * - * @return 0 on success. - * * @remark You must call this function before using any other network functions. */ -int net_init(); +void net_init(); /* Function: net_host_lookup diff --git a/src/engine/client.h b/src/engine/client.h index 33a8d2cc4..7d75fea3a 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -167,7 +167,7 @@ public: #if defined(CONF_VIDEORECORDER) virtual const char *DemoPlayer_Render(const char *pFilename, int StorageType, const char *pVideoName, int SpeedIndex, bool StartPaused = false) = 0; #endif - virtual void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) = 0; + virtual void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder, bool Verbose = false) = 0; virtual void DemoRecorder_HandleAutoStart() = 0; virtual void DemoRecorder_Stop(int Recorder, bool RemoveFile = false) = 0; virtual class IDemoRecorder *DemoRecorder(int Recorder) = 0; diff --git a/src/engine/client/backend/backend_base.cpp b/src/engine/client/backend/backend_base.cpp index 498217a9b..9e2ad5c4d 100644 --- a/src/engine/client/backend/backend_base.cpp +++ b/src/engine/client/backend/backend_base.cpp @@ -1,24 +1,17 @@ #include "backend_base.h" #include -size_t CCommandProcessorFragment_GLBase::TexFormatToImageColorChannelCount(int TexFormat) -{ - if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) - return 4; - return 4; -} - void *CCommandProcessorFragment_GLBase::Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP) { return ResizeImage((const uint8_t *)pData, Width, Height, NewWidth, NewHeight, BPP); } -bool CCommandProcessorFragment_GLBase::Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, int ImageColorChannelCount, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight) +bool CCommandProcessorFragment_GLBase::Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, size_t PixelSize, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight) { Target3DImageWidth = ImageWidth / SplitCountWidth; Target3DImageHeight = ImageHeight / SplitCountHeight; - size_t FullImageWidth = (size_t)ImageWidth * ImageColorChannelCount; + const size_t FullImageWidth = (size_t)ImageWidth * PixelSize; for(int Y = 0; Y < SplitCountHeight; ++Y) { @@ -28,7 +21,7 @@ bool CCommandProcessorFragment_GLBase::Texture2DTo3D(void *pImageBuffer, int Ima { int DepthIndex = X + Y * SplitCountWidth; - size_t TargetImageFullWidth = (size_t)Target3DImageWidth * ImageColorChannelCount; + size_t TargetImageFullWidth = (size_t)Target3DImageWidth * PixelSize; 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)); diff --git a/src/engine/client/backend/backend_base.h b/src/engine/client/backend/backend_base.h index 5d389ac76..25af6a684 100644 --- a/src/engine/client/backend/backend_base.h +++ b/src/engine/client/backend/backend_base.h @@ -84,12 +84,11 @@ protected: SGFXErrorContainer m_Error; SGFXWarningContainer m_Warning; - static size_t TexFormatToImageColorChannelCount(int TexFormat); static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP); - static bool Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, int ImageColorChannelCount, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight); + static bool Texture2DTo3D(void *pImageBuffer, int ImageWidth, int ImageHeight, size_t PixelSize, int SplitCountWidth, int SplitCountHeight, void *pTarget3DImageData, int &Target3DImageWidth, int &Target3DImageHeight); - virtual bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) = 0; + virtual bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) = 0; public: virtual ~CCommandProcessorFragment_GLBase() = default; diff --git a/src/engine/client/backend/null/backend_null.h b/src/engine/client/backend/null/backend_null.h index 97641d9bc..dd7670367 100644 --- a/src/engine/client/backend/null/backend_null.h +++ b/src/engine/client/backend/null/backend_null.h @@ -5,7 +5,7 @@ class CCommandProcessorFragment_Null : public CCommandProcessorFragment_GLBase { - bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) override { return false; }; + bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) override { return false; }; ERunCommandReturnTypes RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override; bool Cmd_Init(const SCommand_Init *pCommand); virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand); diff --git a/src/engine/client/backend/opengl/backend_opengl.cpp b/src/engine/client/backend/opengl/backend_opengl.cpp index dc31e349c..cb0ef4824 100644 --- a/src/engine/client/backend/opengl/backend_opengl.cpp +++ b/src/engine/client/backend/opengl/backend_opengl.cpp @@ -42,11 +42,6 @@ void CCommandProcessorFragment_OpenGL::Cmd_Update_Viewport(const CCommandBuffer: glViewport(pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height); } -void CCommandProcessorFragment_OpenGL::Cmd_Finish(const CCommandBuffer::SCommand_Finish *pCommand) -{ - glFinish(); -} - int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat) { if(TexFormat == CCommandBuffer::TEXFORMAT_RGBA) @@ -54,7 +49,7 @@ int CCommandProcessorFragment_OpenGL::TexFormatToOpenGLFormat(int TexFormat) return GL_RGBA; } -size_t CCommandProcessorFragment_OpenGL::GLFormatToImageColorChannelCount(int GLFormat) +size_t CCommandProcessorFragment_OpenGL::GLFormatToPixelSize(int GLFormat) { switch(GLFormat) { @@ -283,7 +278,7 @@ GfxOpenGLMessageCallback(GLenum Source, } #endif -bool CCommandProcessorFragment_OpenGL::GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) +bool CCommandProcessorFragment_OpenGL::GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) { if(m_CanvasWidth == 0 || m_CanvasHeight == 0) { @@ -319,7 +314,7 @@ bool CCommandProcessorFragment_OpenGL::InitOpenGL(const SCommand_Init *pCommand) m_IsOpenGLES = pCommand->m_RequestedBackend == BACKEND_TYPE_OPENGL_ES; TGLBackendReadPresentedImageData &ReadPresentedImgDataFunc = *pCommand->m_pReadPresentedImageDataFunc; - ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); }; + ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); }; const char *pVendorString = (const char *)glGetString(GL_VENDOR); dbg_msg("opengl", "Vendor string: %s", pVendorString); @@ -649,7 +644,7 @@ void CCommandProcessorFragment_OpenGL::TextureUpdate(int Slot, int X, int Y, int int ResizedW = (int)(Width * ResizeW); int ResizedH = (int)(Height * ResizeH); - void *pTmpData = Resize(static_cast(pTexData), Width, Height, ResizedW, ResizedH, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), Width, Height, ResizedW, ResizedH, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; @@ -671,7 +666,7 @@ void CCommandProcessorFragment_OpenGL::TextureUpdate(int Slot, int X, int Y, int Y /= 2; } - void *pTmpData = Resize(static_cast(pTexData), OldWidth, OldHeight, Width, Height, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), OldWidth, OldHeight, Width, Height, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; } @@ -723,7 +718,7 @@ void CCommandProcessorFragment_OpenGL::Cmd_Texture_Destroy(const CCommandBuffer: DestroyTexture(pCommand->m_Slot); } -void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int Height, int PixelSize, int GLFormat, int GLStoreFormat, int Flags, void *pTexData) +void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int Height, int GLFormat, int GLStoreFormat, int Flags, void *pTexData) { #ifndef BACKEND_GL_MODERN_API @@ -746,7 +741,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He int PowerOfTwoHeight = HighestBit(Height); if(Width != PowerOfTwoWidth || Height != PowerOfTwoHeight) { - void *pTmpData = Resize(static_cast(pTexData), Width, Height, PowerOfTwoWidth, PowerOfTwoHeight, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), Width, Height, PowerOfTwoWidth, PowerOfTwoHeight, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; @@ -778,7 +773,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He if(NeedsResize) { - void *pTmpData = Resize(static_cast(pTexData), OldWidth, OldHeight, Width, Height, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), OldWidth, OldHeight, Width, Height, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; } @@ -787,8 +782,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He m_vTextures[Slot].m_Height = Height; m_vTextures[Slot].m_RescaleCount = RescaleCount; - int Oglformat = GLFormat; - int StoreOglformat = GLStoreFormat; + const size_t PixelSize = GLFormatToPixelSize(GLFormat); if((Flags & CCommandBuffer::TEXFLAG_NO_2D_TEXTURE) == 0) { @@ -802,7 +796,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He { 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); + glTexImage2D(GL_TEXTURE_2D, 0, GLStoreFormat, Width, Height, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); } } else @@ -818,7 +812,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He 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); + glTexImage2D(GL_TEXTURE_2D, 0, GLStoreFormat, Width, Height, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); } int Flag2DArrayTexture = (CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE | CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER); @@ -886,14 +880,12 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He glBindSampler(0, 0); } - int ImageColorChannels = GLFormatToImageColorChannelCount(GLFormat); - uint8_t *p3DImageData = NULL; bool IsSingleLayer = (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); + p3DImageData = (uint8_t *)malloc((size_t)Width * Height * PixelSize); int Image3DWidth, Image3DHeight; int ConvertWidth = Width; @@ -906,7 +898,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He 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((const uint8_t *)pTexData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, GLFormatToImageColorChannelCount(GLFormat)); + uint8_t *pNewTexData = (uint8_t *)Resize((const uint8_t *)pTexData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, GLFormatToPixelSize(GLFormat)); ConvertWidth = NewWidth; ConvertHeight = NewHeight; @@ -916,15 +908,15 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He } } - if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) + if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, PixelSize, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) { if(IsSingleLayer) { - glTexImage3D(Target, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + glTexImage3D(Target, 0, GLStoreFormat, ConvertWidth, ConvertHeight, 1, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); } else { - glTexImage3D(Target, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); + glTexImage3D(Target, 0, GLStoreFormat, Image3DWidth, Image3DHeight, 256, 0, GLFormat, GL_UNSIGNED_BYTE, p3DImageData); } } @@ -937,12 +929,12 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He m_vTextures[Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; // calculate memory usage - m_vTextures[Slot].m_MemSize = Width * Height * PixelSize; + m_vTextures[Slot].m_MemSize = (size_t)Width * Height * PixelSize; while(Width > 2 && Height > 2) { Width >>= 1; Height >>= 1; - m_vTextures[Slot].m_MemSize += Width * Height * PixelSize; + m_vTextures[Slot].m_MemSize += (size_t)Width * Height * PixelSize; } m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_vTextures[Slot].m_MemSize, std::memory_order_relaxed); @@ -952,7 +944,7 @@ void CCommandProcessorFragment_OpenGL::TextureCreate(int Slot, int Width, int He void CCommandProcessorFragment_OpenGL::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) { - TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, pCommand->m_PixelSize, TexFormatToOpenGLFormat(pCommand->m_Format), TexFormatToOpenGLFormat(pCommand->m_StoreFormat), pCommand->m_Flags, pCommand->m_pData); + TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, TexFormatToOpenGLFormat(pCommand->m_Format), TexFormatToOpenGLFormat(pCommand->m_StoreFormat), pCommand->m_Flags, pCommand->m_pData); } void CCommandProcessorFragment_OpenGL::Cmd_TextTexture_Update(const CCommandBuffer::SCommand_TextTexture_Update *pCommand) @@ -968,16 +960,24 @@ void CCommandProcessorFragment_OpenGL::Cmd_TextTextures_Destroy(const CCommandBu void CCommandProcessorFragment_OpenGL::Cmd_TextTextures_Create(const CCommandBuffer::SCommand_TextTextures_Create *pCommand) { - void *pTextData = pCommand->m_pTextData; - void *pTextOutlineData = pCommand->m_pTextOutlineData; - TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, 1, GL_ALPHA, GL_ALPHA, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTextData); - TextureCreate(pCommand->m_SlotOutline, pCommand->m_Width, pCommand->m_Height, 1, GL_ALPHA, GL_ALPHA, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTextOutlineData); + TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, GL_ALPHA, GL_ALPHA, CCommandBuffer::TEXFLAG_NOMIPMAPS, pCommand->m_pTextData); + TextureCreate(pCommand->m_SlotOutline, pCommand->m_Width, pCommand->m_Height, GL_ALPHA, GL_ALPHA, CCommandBuffer::TEXFLAG_NOMIPMAPS, pCommand->m_pTextOutlineData); } void CCommandProcessorFragment_OpenGL::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) { + // if clip is still active, force disable it for clearing, enable it again afterwards + bool ClipWasEnabled = m_LastClipEnable; + if(ClipWasEnabled) + { + glDisable(GL_SCISSOR_TEST); + } glClearColor(pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if(ClipWasEnabled) + { + glEnable(GL_SCISSOR_TEST); + } } void CCommandProcessorFragment_OpenGL::Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand) @@ -1102,9 +1102,6 @@ ERunCommandReturnTypes CCommandProcessorFragment_OpenGL::RunCommand(const CComma 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; diff --git a/src/engine/client/backend/opengl/backend_opengl.h b/src/engine/client/backend/opengl/backend_opengl.h index c21f27479..3b2c844b3 100644 --- a/src/engine/client/backend/opengl/backend_opengl.h +++ b/src/engine/client/backend/opengl/backend_opengl.h @@ -80,13 +80,13 @@ protected: virtual bool IsNewApi() { return false; } void DestroyTexture(int Slot); - bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) override; + bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) override; static int TexFormatToOpenGLFormat(int TexFormat); - static size_t GLFormatToImageColorChannelCount(int GLFormat); + static size_t GLFormatToPixelSize(int GLFormat); void TextureUpdate(int Slot, int X, int Y, int Width, int Height, int GLFormat, void *pTexData); - void TextureCreate(int Slot, int Width, int Height, int PixelSize, int GLFormat, int GLStoreFormat, int Flags, void *pTexData); + void TextureCreate(int Slot, int Width, int Height, int GLFormat, int GLStoreFormat, int Flags, void *pTexData); virtual bool Cmd_Init(const SCommand_Init *pCommand); virtual void Cmd_Shutdown(const SCommand_Shutdown *pCommand) {} @@ -102,7 +102,6 @@ protected: virtual void Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *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) { dbg_assert(false, "Call of unsupported Cmd_CreateBufferObject"); } virtual void Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand) { dbg_assert(false, "Call of unsupported Cmd_RecreateBufferObject"); } diff --git a/src/engine/client/backend/opengl/backend_opengl3.cpp b/src/engine/client/backend/opengl/backend_opengl3.cpp index 46b9185d4..e251329ed 100644 --- a/src/engine/client/backend/opengl/backend_opengl3.cpp +++ b/src/engine/client/backend/opengl/backend_opengl3.cpp @@ -555,7 +555,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureUpdate(int Slot, int X, int Y, Y /= 2; } - void *pTmpData = Resize(static_cast(pTexData), Width, Height, Width, Height, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), Width, Height, Width, Height, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; } @@ -577,7 +577,7 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Destroy(const CCommandBuff DestroyTexture(pCommand->m_Slot); } -void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int Height, int PixelSize, int GLFormat, int GLStoreFormat, int Flags, void *pTexData) +void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int Height, int GLFormat, int GLStoreFormat, int Flags, void *pTexData) { if(Slot >= (int)m_vTextures.size()) m_vTextures.resize(m_vTextures.size() * 2); @@ -595,7 +595,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int ++RescaleCount; } while(Width > m_MaxTexSize || Height > m_MaxTexSize); - void *pTmpData = Resize(static_cast(pTexData), Width, Height, Width, Height, GLFormatToImageColorChannelCount(GLFormat)); + void *pTmpData = Resize(static_cast(pTexData), Width, Height, Width, Height, GLFormatToPixelSize(GLFormat)); free(pTexData); pTexData = pTmpData; } @@ -604,10 +604,9 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int m_vTextures[Slot].m_Height = Height; m_vTextures[Slot].m_RescaleCount = RescaleCount; - int Oglformat = GLFormat; - int StoreOglformat = GLStoreFormat; - if(StoreOglformat == GL_RED) - StoreOglformat = GL_R8; + if(GLStoreFormat == GL_RED) + GLStoreFormat = GL_R8; + const size_t PixelSize = GLFormatToPixelSize(GLFormat); int SamplerSlot = 0; @@ -628,7 +627,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(m_vTextures[Slot].m_Sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(m_vTextures[Slot].m_Sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, StoreOglformat, Width, Height, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + glTexImage2D(GL_TEXTURE_2D, 0, GLStoreFormat, Width, Height, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); } } else @@ -649,7 +648,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int 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); + glTexImage2D(GL_TEXTURE_2D, 0, GLStoreFormat, Width, Height, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); glGenerateMipmap(GL_TEXTURE_2D); } @@ -671,14 +670,12 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int glSamplerParameterf(m_vTextures[Slot].m_Sampler2DArray, GL_TEXTURE_LOD_BIAS, ((GLfloat)m_OpenGLTextureLodBIAS / 1000.0f)); #endif - int ImageColorChannels = GLFormatToImageColorChannelCount(GLFormat); - uint8_t *p3DImageData = NULL; bool IsSingleLayer = (Flags & CCommandBuffer::TEXFLAG_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER) != 0; if(!IsSingleLayer) - p3DImageData = (uint8_t *)malloc((size_t)ImageColorChannels * Width * Height); + p3DImageData = (uint8_t *)malloc((size_t)Width * Height * PixelSize); int Image3DWidth, Image3DHeight; int ConvertWidth = Width; @@ -691,7 +688,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int 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((const uint8_t *)pTexData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, GLFormatToImageColorChannelCount(GLFormat)); + uint8_t *pNewTexData = (uint8_t *)Resize((const uint8_t *)pTexData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, GLFormatToPixelSize(GLFormat)); ConvertWidth = NewWidth; ConvertHeight = NewHeight; @@ -701,15 +698,15 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int } } - if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) + if(IsSingleLayer || (Texture2DTo3D(pTexData, ConvertWidth, ConvertHeight, PixelSize, 16, 16, p3DImageData, Image3DWidth, Image3DHeight))) { if(IsSingleLayer) { - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, ConvertWidth, ConvertHeight, 1, 0, Oglformat, GL_UNSIGNED_BYTE, pTexData); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GLStoreFormat, ConvertWidth, ConvertHeight, 1, 0, GLFormat, GL_UNSIGNED_BYTE, pTexData); } else { - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, StoreOglformat, Image3DWidth, Image3DHeight, 256, 0, Oglformat, GL_UNSIGNED_BYTE, p3DImageData); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GLStoreFormat, Image3DWidth, Image3DHeight, 256, 0, GLFormat, GL_UNSIGNED_BYTE, p3DImageData); } glGenerateMipmap(GL_TEXTURE_2D_ARRAY); } @@ -723,12 +720,12 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int m_vTextures[Slot].m_LastWrapMode = CCommandBuffer::WRAP_REPEAT; // calculate memory usage - m_vTextures[Slot].m_MemSize = Width * Height * PixelSize; + m_vTextures[Slot].m_MemSize = (size_t)Width * Height * PixelSize; while(Width > 2 && Height > 2) { Width >>= 1; Height >>= 1; - m_vTextures[Slot].m_MemSize += Width * Height * PixelSize; + m_vTextures[Slot].m_MemSize += (size_t)Width * Height * PixelSize; } m_pTextureMemoryUsage->store(m_pTextureMemoryUsage->load(std::memory_order_relaxed) + m_vTextures[Slot].m_MemSize, std::memory_order_relaxed); @@ -737,7 +734,7 @@ void CCommandProcessorFragment_OpenGL3_3::TextureCreate(int Slot, int Width, int void CCommandProcessorFragment_OpenGL3_3::Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand) { - TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, pCommand->m_PixelSize, TexFormatToOpenGLFormat(pCommand->m_Format), TexFormatToOpenGLFormat(pCommand->m_StoreFormat), pCommand->m_Flags, pCommand->m_pData); + TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, TexFormatToOpenGLFormat(pCommand->m_Format), TexFormatToOpenGLFormat(pCommand->m_StoreFormat), pCommand->m_Flags, pCommand->m_pData); } void CCommandProcessorFragment_OpenGL3_3::Cmd_TextTexture_Update(const CCommandBuffer::SCommand_TextTexture_Update *pCommand) @@ -753,20 +750,28 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_TextTextures_Destroy(const CComman void CCommandProcessorFragment_OpenGL3_3::Cmd_TextTextures_Create(const CCommandBuffer::SCommand_TextTextures_Create *pCommand) { - void *pTextData = pCommand->m_pTextData; - void *pTextOutlineData = pCommand->m_pTextOutlineData; - TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, 1, GL_RED, GL_RED, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTextData); - TextureCreate(pCommand->m_SlotOutline, pCommand->m_Width, pCommand->m_Height, 1, GL_RED, GL_RED, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTextOutlineData); + TextureCreate(pCommand->m_Slot, pCommand->m_Width, pCommand->m_Height, GL_RED, GL_RED, CCommandBuffer::TEXFLAG_NOMIPMAPS, pCommand->m_pTextData); + TextureCreate(pCommand->m_SlotOutline, pCommand->m_Width, pCommand->m_Height, GL_RED, GL_RED, CCommandBuffer::TEXFLAG_NOMIPMAPS, pCommand->m_pTextOutlineData); } void CCommandProcessorFragment_OpenGL3_3::Cmd_Clear(const CCommandBuffer::SCommand_Clear *pCommand) { + // if clip is still active, force disable it for clearing, enable it again afterwards + bool ClipWasEnabled = m_LastClipEnable; + if(ClipWasEnabled) + { + glDisable(GL_SCISSOR_TEST); + } 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); + if(ClipWasEnabled) + { + glEnable(GL_SCISSOR_TEST); + } } void CCommandProcessorFragment_OpenGL3_3::UploadStreamBufferData(unsigned int PrimitiveType, const void *pVertices, size_t VertSize, unsigned int PrimitiveCount, bool AsTex3D) diff --git a/src/engine/client/backend/opengl/backend_opengl3.h b/src/engine/client/backend/opengl/backend_opengl3.h index 1c56a0e20..4f839564d 100644 --- a/src/engine/client/backend/opengl/backend_opengl3.h +++ b/src/engine/client/backend/opengl/backend_opengl3.h @@ -83,7 +83,7 @@ protected: void RenderText(const CCommandBuffer::SState &State, int DrawNum, int TextTextureIndex, int TextOutlineTextureIndex, int TextureSize, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor); void TextureUpdate(int Slot, int X, int Y, int Width, int Height, int GLFormat, void *pTexData); - void TextureCreate(int Slot, int Width, int Height, int PixelSize, int GLFormat, int GLStoreFormat, int Flags, void *pTexData); + void TextureCreate(int Slot, int Width, int Height, int GLFormat, int GLStoreFormat, int Flags, void *pTexData); bool Cmd_Init(const SCommand_Init *pCommand) override; void Cmd_Shutdown(const SCommand_Shutdown *pCommand) override; diff --git a/src/engine/client/backend/vulkan/backend_vulkan.cpp b/src/engine/client/backend/vulkan/backend_vulkan.cpp index b5c10d5d7..81b0d65a5 100644 --- a/src/engine/client/backend/vulkan/backend_vulkan.cpp +++ b/src/engine/client/backend/vulkan/backend_vulkan.cpp @@ -1102,7 +1102,7 @@ protected: bool m_CanAssert = false; /** - * After an error occured, the rendering stop as soon as possible + * After an error occurred, the rendering stop as soon as possible * Always stop the current code execution after a call to this function (e.g. return false) */ void SetError(EGFXErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr) @@ -1282,7 +1282,6 @@ protected: m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_SPRITE_MULTIPLE)] = {true, [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadContainerAsSpriteMultiple_FillExecuteBuffer(ExecBuffer, static_cast(pBaseCommand)); }, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadContainerAsSpriteMultiple(static_cast(pBaseCommand), ExecBuffer); }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_SWAP)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Swap(static_cast(pBaseCommand)); }}; - m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_FINISH)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Finish(static_cast(pBaseCommand)); }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_VSYNC)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_VSync(static_cast(pBaseCommand)); }}; m_aCommandCallbacks[CommandBufferCMDOff(CCommandBuffer::CMD_MULTISAMPLING)] = {false, [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_MultiSampling(static_cast(pBaseCommand)); }}; @@ -1386,7 +1385,7 @@ protected: } } - [[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData, bool FlipImgData, bool ResetAlpha) + [[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData, bool FlipImgData, bool ResetAlpha) { bool IsB8G8R8A8 = m_VKSurfFormat.format == VK_FORMAT_B8G8R8A8_UNORM; bool UsesRGBALikeFormat = m_VKSurfFormat.format == VK_FORMAT_R8G8B8A8_UNORM || IsB8G8R8A8; @@ -1397,7 +1396,7 @@ protected: Height = Viewport.height; Format = CImageInfo::FORMAT_RGBA; - size_t ImageTotalSize = (size_t)Width * Height * 4; + const size_t ImageTotalSize = (size_t)Width * Height * CImageInfo::PixelSize(Format); uint8_t *pResImageData; if(!PreparePresentedImageDataImage(pResImageData, Width, Height)) @@ -1553,7 +1552,7 @@ protected: } } - [[nodiscard]] bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) override + [[nodiscard]] bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) override { return GetPresentedImageDataImpl(Width, Height, Format, vDstData, false, false); } @@ -2510,7 +2509,7 @@ protected: * TEXTURES ************************/ - size_t VulkanFormatToImageColorChannelCount(VkFormat Format) + size_t VulkanFormatToPixelSize(VkFormat Format) { if(Format == VK_FORMAT_R8G8B8_UNORM) return 3; @@ -2521,9 +2520,9 @@ protected: return 4; } - [[nodiscard]] bool UpdateTexture(size_t TextureSlot, VkFormat Format, void *&pData, int64_t XOff, int64_t YOff, size_t Width, size_t Height, size_t ColorChannelCount) + [[nodiscard]] bool UpdateTexture(size_t TextureSlot, VkFormat Format, void *&pData, int64_t XOff, int64_t YOff, size_t Width, size_t Height) { - size_t ImageSize = Width * Height * ColorChannelCount; + const size_t ImageSize = Width * Height * VulkanFormatToPixelSize(Format); SMemoryBlock StagingBuffer; if(!GetStagingBufferImage(StagingBuffer, pData, ImageSize)) return false; @@ -2541,7 +2540,7 @@ protected: YOff /= 2; } - void *pTmpData = Resize((const uint8_t *)pData, Width, Height, Width, Height, VulkanFormatToImageColorChannelCount(Format)); + void *pTmpData = Resize((const uint8_t *)pData, Width, Height, Width, Height, VulkanFormatToPixelSize(Format)); free(pData); pData = pTmpData; } @@ -2571,14 +2570,13 @@ protected: int Slot, int Width, int Height, - int PixelSize, VkFormat Format, VkFormat StoreFormat, int Flags, void *&pData) { size_t ImageIndex = (size_t)Slot; - int ImageColorChannels = VulkanFormatToImageColorChannelCount(Format); + const size_t PixelSize = VulkanFormatToPixelSize(Format); while(ImageIndex >= m_vTextures.size()) { @@ -2596,7 +2594,7 @@ protected: ++RescaleCount; } while((size_t)Width > m_MaxTextureSize || (size_t)Height > m_MaxTextureSize); - void *pTmpData = Resize((const uint8_t *)(pData), Width, Height, Width, Height, ImageColorChannels); + void *pTmpData = Resize((const uint8_t *)(pData), Width, Height, Width, Height, PixelSize); free(pData); pData = pTmpData; } @@ -2654,7 +2652,7 @@ protected: dbg_msg("vulkan", "3D/2D array texture was resized"); int NewWidth = maximum(HighestBit(ConvertWidth), 16); int NewHeight = maximum(HighestBit(ConvertHeight), 16); - uint8_t *pNewTexData = (uint8_t *)Resize((const uint8_t *)pData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, ImageColorChannels); + uint8_t *pNewTexData = (uint8_t *)Resize((const uint8_t *)pData, ConvertWidth, ConvertHeight, NewWidth, NewHeight, PixelSize); ConvertWidth = NewWidth; ConvertHeight = NewHeight; @@ -2668,8 +2666,8 @@ protected: bool Needs3DTexDel = false; if(!Is2DTextureSingleLayer) { - p3DTexData = malloc((size_t)ImageColorChannels * ConvertWidth * ConvertHeight); - if(!Texture2DTo3D(pData, ConvertWidth, ConvertHeight, ImageColorChannels, 16, 16, p3DTexData, Image3DWidth, Image3DHeight)) + p3DTexData = malloc((size_t)PixelSize * ConvertWidth * ConvertHeight); + if(!Texture2DTo3D(pData, ConvertWidth, ConvertHeight, PixelSize, 16, 16, p3DTexData, Image3DWidth, Image3DHeight)) { free(p3DTexData); p3DTexData = nullptr; @@ -6467,7 +6465,7 @@ public: Buffer.m_pRawCommand = pBaseCommand; Buffer.m_ThreadIndex = 0; - if(m_CurCommandInPipe + 1 == m_CommandsInPipe && Buffer.m_Command != CCommandBuffer::CMD_FINISH) + if(m_CurCommandInPipe + 1 == m_CommandsInPipe) { m_LastCommandsInPipeThreadIndex = std::numeric_limits::max(); } @@ -6493,7 +6491,7 @@ public: Ret = CallbackObj.m_CMDIsHandled; if(!CallbackObj.m_CommandCB(pBaseCommand, Buffer)) { - // an error occured, stop this command and ignore all further commands + // an error occurred, stop this command and ignore all further commands return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_ERROR; } } @@ -6582,7 +6580,7 @@ public: m_MultiSamplingCount = (g_Config.m_GfxFsaaSamples & 0xFFFFFFFE); // ignore the uneven bit, only even multi sampling works TGLBackendReadPresentedImageData &ReadPresentedImgDataFunc = *pCommand->m_pReadPresentedImageDataFunc; - ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); }; + ReadPresentedImgDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector &vDstData) { return GetPresentedImageData(Width, Height, Format, vDstData); }; m_pWindow = pCommand->m_pWindow; @@ -6657,7 +6655,7 @@ public: void *pData = pCommand->m_pData; - if(!UpdateTexture(IndexTex, VK_FORMAT_B8G8R8A8_UNORM, pData, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height, TexFormatToImageColorChannelCount(pCommand->m_Format))) + if(!UpdateTexture(IndexTex, VK_FORMAT_B8G8R8A8_UNORM, pData, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height)) return false; free(pData); @@ -6682,13 +6680,12 @@ public: int Slot = pCommand->m_Slot; int Width = pCommand->m_Width; int Height = pCommand->m_Height; - int PixelSize = pCommand->m_PixelSize; int Format = pCommand->m_Format; int StoreFormat = pCommand->m_StoreFormat; int Flags = pCommand->m_Flags; void *pData = pCommand->m_pData; - if(!CreateTextureCMD(Slot, Width, Height, PixelSize, TextureFormatToVulkanFormat(Format), TextureFormatToVulkanFormat(StoreFormat), Flags, pData)) + if(!CreateTextureCMD(Slot, Width, Height, TextureFormatToVulkanFormat(Format), TextureFormatToVulkanFormat(StoreFormat), Flags, pData)) return false; free(pData); @@ -6706,9 +6703,9 @@ public: void *pTmpData = pCommand->m_pTextData; void *pTmpData2 = pCommand->m_pTextOutlineData; - if(!CreateTextureCMD(Slot, Width, Height, 1, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTmpData)) + if(!CreateTextureCMD(Slot, Width, Height, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTmpData)) return false; - if(!CreateTextureCMD(SlotOutline, Width, Height, 1, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTmpData2)) + if(!CreateTextureCMD(SlotOutline, Width, Height, VK_FORMAT_R8_UNORM, VK_FORMAT_R8_UNORM, CCommandBuffer::TEXFLAG_NOMIPMAPS, pTmpData2)) return false; if(!CreateNewTextDescriptorSets(Slot, SlotOutline)) @@ -6741,7 +6738,7 @@ public: void *pData = pCommand->m_pData; - if(!UpdateTexture(IndexTex, VK_FORMAT_R8_UNORM, pData, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height, 1)) + if(!UpdateTexture(IndexTex, VK_FORMAT_R8_UNORM, pData, pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height)) return false; free(pData); @@ -6815,7 +6812,7 @@ public: uint32_t Width; uint32_t Height; - uint32_t Format; + CImageInfo::EImageFormat Format; if(GetPresentedImageDataImpl(Width, Height, Format, m_vScreenshotHelper, false, true)) { size_t ImgSize = (size_t)Width * (size_t)Height * (size_t)4; @@ -6828,7 +6825,7 @@ public: } pCommand->m_pImage->m_Width = (int)Width; pCommand->m_pImage->m_Height = (int)Height; - pCommand->m_pImage->m_Format = (int)Format; + pCommand->m_pImage->m_Format = Format; return true; } @@ -6921,12 +6918,6 @@ public: return true; } - [[nodiscard]] bool Cmd_Finish(const CCommandBuffer::SCommand_Finish *pCommand) - { - // just ignore it with vulkan - return true; - } - [[nodiscard]] bool Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand) { return NextFrame(); @@ -7659,7 +7650,7 @@ public: { if(!m_aCommandCallbacks[CommandBufferCMDOff(NextCmd.m_Command)].m_CommandCB(NextCmd.m_pRawCommand, NextCmd)) { - // an error occured, the thread will not continue execution + // an error occurred, the thread will not continue execution HasErrorFromCmd = true; break; } diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index eaac7b945..c3e0e0ee4 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -505,7 +505,7 @@ void CClient::RconAuth(const char *pName, const char *pPassword) CMsgPacker Msg(NETMSG_RCON_AUTH, true); Msg.AddString(pName, 32); - Msg.AddString(pPassword, 32); + Msg.AddString(pPassword, 128); Msg.AddInt(1); SendMsgActive(&Msg, MSGFLAG_VITAL); } @@ -974,7 +974,7 @@ void CClient::ServerInfoRequest() void CClient::LoadDebugFont() { - m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); + m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL); } // --- @@ -3120,7 +3120,7 @@ void CClient::Run() // handle pending map edits if(m_aCmdEditMap[0]) { - int Result = m_pEditor->Load(m_aCmdEditMap, IStorage::TYPE_ALL_OR_ABSOLUTE); + int Result = m_pEditor->HandleMapDrop(m_aCmdEditMap, IStorage::TYPE_ALL_OR_ABSOLUTE); if(Result) g_Config.m_ClEditor = true; else @@ -3376,12 +3376,6 @@ void CClient::Run() else LastTime = Now; - if(g_Config.m_DbgHitch) - { - std::this_thread::sleep_for(g_Config.m_DbgHitch * 1ms); - g_Config.m_DbgHitch = 0; - } - // update local and global time m_LocalTime = (time_get() - m_LocalStartTime) / (float)time_freq(); m_GlobalTime = (time_get() - m_GlobalStartTime) / (float)time_freq(); @@ -3957,10 +3951,13 @@ void CClient::Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData) pSelf->m_DemoPlayer.SetSpeed(pResult->GetFloat(0)); } -void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) +void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder, bool Verbose) { if(State() != IClient::STATE_ONLINE) - m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); + { + if(Verbose) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); + } else { char aFilename[IO_MAX_PATH_LENGTH]; @@ -4038,9 +4035,9 @@ void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) { CClient *pSelf = (CClient *)pUserData; if(pResult->NumArguments()) - pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL); + pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL, true); else - pSelf->DemoRecorder_Start(pSelf->m_aCurrentMap, true, RECORDER_MANUAL); + pSelf->DemoRecorder_Start(pSelf->m_aCurrentMap, true, RECORDER_MANUAL, true); } void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData) @@ -4629,7 +4626,7 @@ int main(int argc, const char **argv) pClient->GetGPUInfoString(aGPUInfo); char aMessage[768]; str_format(aMessage, sizeof(aMessage), - "An assertion error occured. Please write down or take a screenshot of the following information and report this error.\n" + "An assertion error occurred. Please write down or take a screenshot of the following information and report this error.\n" "Please also share the assert log which you should find in the 'dumps' folder in your config directory.\n\n" "%s\n\n" "Platform: %s\n" diff --git a/src/engine/client/client.h b/src/engine/client/client.h index a57dd5fce..cc2bbef3f 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -164,9 +164,9 @@ class CClient : public IClient, public CDemoPlayer::IListener int m_aCurrentRecvTick[NUM_DUMMIES]; int m_aRconAuthed[NUM_DUMMIES]; char m_aRconUsername[32]; - char m_aRconPassword[32]; + char m_aRconPassword[128]; int m_UseTempRconCommands; - char m_aPassword[32]; + char m_aPassword[128]; bool m_SendPassword; bool m_ButtonRender = false; @@ -482,7 +482,7 @@ public: void RegisterCommands(); const char *DemoPlayer_Play(const char *pFilename, int StorageType) override; - void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) override; + void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder, bool Verbose = false) override; void DemoRecorder_HandleAutoStart() override; void DemoRecorder_StartReplayRecorder(); void DemoRecorder_Stop(int Recorder, bool RemoveFile = false) override; diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 91263817e..474c593a8 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -305,27 +305,17 @@ int CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex) return 0; } -static int ImageFormatToPixelSize(int Format) -{ - switch(Format) - { - case CImageInfo::FORMAT_RGB: return 3; - case CImageInfo::FORMAT_SINGLE_COMPONENT: return 1; - default: return 4; - } -} - -static bool ConvertToRGBA(uint8_t *pDest, const uint8_t *pSrc, size_t SrcWidth, size_t SrcHeight, int SrcFormat) +static bool ConvertToRGBA(uint8_t *pDest, const uint8_t *pSrc, size_t SrcWidth, size_t SrcHeight, CImageInfo::EImageFormat SrcFormat) { if(SrcFormat == CImageInfo::FORMAT_RGBA) { - mem_copy(pDest, pSrc, SrcWidth * SrcHeight * 4); + mem_copy(pDest, pSrc, SrcWidth * SrcHeight * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA)); return true; } else { - size_t SrcChannelCount = ImageFormatToPixelSize(SrcFormat); - size_t DstChannelCount = 4; + const size_t SrcChannelCount = CImageInfo::PixelSize(SrcFormat); + const size_t DstChannelCount = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); for(size_t Y = 0; Y < SrcHeight; ++Y) { for(size_t X = 0; X < SrcWidth; ++X) @@ -333,12 +323,12 @@ static bool ConvertToRGBA(uint8_t *pDest, const uint8_t *pSrc, size_t SrcWidth, size_t ImgOffsetSrc = (Y * SrcWidth * SrcChannelCount) + (X * SrcChannelCount); size_t ImgOffsetDest = (Y * SrcWidth * DstChannelCount) + (X * DstChannelCount); size_t CopySize = SrcChannelCount; - if(SrcChannelCount == 3) + if(SrcFormat == CImageInfo::FORMAT_RGB) { mem_copy(&pDest[ImgOffsetDest], &pSrc[ImgOffsetSrc], CopySize); pDest[ImgOffsetDest + 3] = 255; } - else if(SrcChannelCount == 1) + else if(SrcFormat == CImageInfo::FORMAT_SINGLE_COMPONENT) { pDest[ImgOffsetDest + 0] = 255; pDest[ImgOffsetDest + 1] = 255; @@ -351,7 +341,7 @@ static bool ConvertToRGBA(uint8_t *pDest, const uint8_t *pSrc, size_t SrcWidth, } } -int CGraphics_Threaded::LoadTextureRawSub(CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, int Format, const void *pData) +int CGraphics_Threaded::LoadTextureRawSub(CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData) { dbg_assert(TextureID.IsValid(), "Invalid texture handle used with LoadTextureRawSub."); @@ -364,7 +354,7 @@ int CGraphics_Threaded::LoadTextureRawSub(CTextureHandle TextureID, int x, int y Cmd.m_Format = CCommandBuffer::TEXFORMAT_RGBA; // calculate memory usage - const size_t MemSize = Width * Height * 4; + const size_t MemSize = Width * Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); // copy texture data void *pTmpData = malloc(MemSize); @@ -377,7 +367,7 @@ int CGraphics_Threaded::LoadTextureRawSub(CTextureHandle TextureID, int x, int y IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTextureImpl(CImageInfo &FromImageInfo, int x, int y, size_t w, size_t h) { - const size_t PixelSize = ImageFormatToPixelSize(FromImageInfo.m_Format); + const size_t PixelSize = FromImageInfo.PixelSize(); m_vSpriteHelper.resize(w * h * PixelSize); CopyTextureFromTextureBufferSub(m_vSpriteHelper.data(), w, h, (uint8_t *)FromImageInfo.m_pData, FromImageInfo.m_Width, FromImageInfo.m_Height, PixelSize, x, y, w, h); return LoadTextureRaw(w, h, FromImageInfo.m_Format, m_vSpriteHelper.data(), FromImageInfo.m_Format, 0); @@ -410,13 +400,13 @@ bool CGraphics_Threaded::IsImageSubFullyTransparent(CImageInfo &FromImageInfo, i if(FromImageInfo.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA) { uint8_t *pImgData = (uint8_t *)FromImageInfo.m_pData; - int bpp = ImageFormatToPixelSize(FromImageInfo.m_Format); + const size_t PixelSize = FromImageInfo.PixelSize(); for(int iy = 0; iy < h; ++iy) { for(int ix = 0; ix < w; ++ix) { - int RealOffset = (x + ix) * bpp + (y + iy) * bpp * FromImageInfo.m_Width; - if(pImgData[RealOffset + (bpp - 1)] > 0) + const size_t RealOffset = (x + ix) * PixelSize + (y + iy) * PixelSize * FromImageInfo.m_Width; + if(pImgData[RealOffset + (PixelSize - 1)] > 0) return false; } } @@ -438,7 +428,7 @@ bool CGraphics_Threaded::IsSpriteTextureFullyTransparent(CImageInfo &FromImageIn return IsImageSubFullyTransparent(FromImageInfo, x, y, w, h); } -IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_t Height, int Format, const void *pData, int StoreFormat, int Flags, const char *pTexName) +IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName) { // don't waste memory on texture if we are stress testing #ifdef CONF_DEBUG @@ -472,7 +462,6 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_ Cmd.m_Slot = TextureHandle.Id(); Cmd.m_Width = Width; Cmd.m_Height = Height; - Cmd.m_PixelSize = 4; Cmd.m_Format = CCommandBuffer::TEXFORMAT_RGBA; Cmd.m_StoreFormat = CCommandBuffer::TEXFORMAT_RGBA; @@ -492,7 +481,7 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_ Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NO_2D_TEXTURE; // copy texture data - const size_t MemSize = Width * Height * Cmd.m_PixelSize; + const size_t MemSize = Width * Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); void *pTmpData = malloc(MemSize); if(!ConvertToRGBA((uint8_t *)pTmpData, (const uint8_t *)pData, Width, Height, Format)) { @@ -505,17 +494,14 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_ } // simple uncompressed RGBA loaders -IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) +IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int Flags) { dbg_assert(pFilename[0] != '\0', "Cannot load texture from file with empty filename"); // would cause Valgrind to crash otherwise CImageInfo Img; if(LoadPNG(&Img, pFilename, StorageType)) { - if(StoreFormat == CImageInfo::FORMAT_AUTO) - StoreFormat = Img.m_Format; - - IGraphics::CTextureHandle ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, StoreFormat, Flags, pFilename); + IGraphics::CTextureHandle ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, Flags, pFilename); free(Img.m_pData); if(ID.Id() != m_InvalidTexture.Id() && g_Config.m_Debug) dbg_msg("graphics/texture", "loaded %s", pFilename); @@ -696,15 +682,7 @@ bool CGraphics_Threaded::CheckImageDivisibility(const char *pFileName, CImageInf NewWidth = (NewHeight / DivY) * DivX; } - int ColorChannelCount = 4; - if(Img.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT) - ColorChannelCount = 1; - else if(Img.m_Format == CImageInfo::FORMAT_RGB) - ColorChannelCount = 3; - else if(Img.m_Format == CImageInfo::FORMAT_RGBA) - ColorChannelCount = 4; - - uint8_t *pNewImg = ResizeImage((uint8_t *)Img.m_pData, Img.m_Width, Img.m_Height, NewWidth, NewHeight, ColorChannelCount); + uint8_t *pNewImg = ResizeImage((uint8_t *)Img.m_pData, Img.m_Width, Img.m_Height, NewWidth, NewHeight, Img.PixelSize()); free(Img.m_pData); Img.m_pData = pNewImg; Img.m_Width = NewWidth; @@ -734,23 +712,23 @@ bool CGraphics_Threaded::IsImageFormatRGBA(const char *pFileName, CImageInfo &Im return true; } -void CGraphics_Threaded::CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t ColorChannelCount, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) +void CGraphics_Threaded::CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t PixelSize, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) { for(size_t Y = 0; Y < SubCopyHeight; ++Y) { - const size_t ImgOffset = ((SubOffsetY + Y) * FullWidth * ColorChannelCount) + (SubOffsetX * ColorChannelCount); - const size_t CopySize = SubCopyWidth * ColorChannelCount; + const size_t ImgOffset = ((SubOffsetY + Y) * FullWidth * PixelSize) + (SubOffsetX * PixelSize); + const size_t CopySize = SubCopyWidth * PixelSize; mem_copy(&pDestBuffer[ImgOffset], &pSourceBuffer[ImgOffset], CopySize); } } -void CGraphics_Threaded::CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t ColorChannelCount, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) +void CGraphics_Threaded::CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t PixelSize, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) { for(size_t Y = 0; Y < SrcSubCopyHeight; ++Y) { - const size_t SrcImgOffset = ((SrcSubOffsetY + Y) * SrcWidth * ColorChannelCount) + (SrcSubOffsetX * ColorChannelCount); - const size_t DstImgOffset = (Y * DestWidth * ColorChannelCount); - const size_t CopySize = SrcSubCopyWidth * ColorChannelCount; + const size_t SrcImgOffset = ((SrcSubOffsetY + Y) * SrcWidth * PixelSize) + (SrcSubOffsetX * PixelSize); + const size_t DstImgOffset = (Y * DestWidth * PixelSize); + const size_t CopySize = SrcSubCopyWidth * PixelSize; mem_copy(&pDestBuffer[DstImgOffset], &pSourceBuffer[SrcImgOffset], CopySize); } } @@ -2671,7 +2649,7 @@ int CGraphics_Threaded::Init() } const int TextureLoadFlags = HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; m_InvalidTexture.Invalidate(); - m_InvalidTexture = LoadTextureRaw(InvalidTextureDimension, InvalidTextureDimension, CImageInfo::FORMAT_RGBA, aNullTextureData, CImageInfo::FORMAT_RGBA, TextureLoadFlags); + m_InvalidTexture = LoadTextureRaw(InvalidTextureDimension, InvalidTextureDimension, CImageInfo::FORMAT_RGBA, aNullTextureData, TextureLoadFlags); } ColorRGBA GPUInfoPrintColor{0.6f, 0.5f, 1.0f, 1.0f}; @@ -2939,12 +2917,6 @@ void CGraphics_Threaded::Swap() AddCmd(Cmd); } - if(g_Config.m_GfxFinish) - { - CCommandBuffer::SCommand_Finish Cmd; - AddCmd(Cmd); - } - // kick the command buffer KickCommandBuffer(); // TODO: Remove when https://github.com/libsdl-org/SDL/issues/5203 is fixed diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 807a203ef..7a3932684 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -126,7 +126,6 @@ public: // swap CMD_SWAP, - CMD_FINISH, // misc CMD_MULTISAMPLING, @@ -495,12 +494,6 @@ public: SCommand(CMD_SWAP) {} }; - struct SCommand_Finish : public SCommand - { - SCommand_Finish() : - SCommand(CMD_FINISH) {} - }; - struct SCommand_VSync : public SCommand { SCommand_VSync() : @@ -542,7 +535,6 @@ public: size_t m_Width; size_t m_Height; - int m_PixelSize; int m_Format; int m_StoreFormat; int m_Flags; @@ -974,8 +966,8 @@ public: IGraphics::CTextureHandle FindFreeTextureIndex(); void FreeTextureIndex(CTextureHandle *pIndex); int UnloadTexture(IGraphics::CTextureHandle *pIndex) override; - IGraphics::CTextureHandle LoadTextureRaw(size_t Width, size_t Height, int Format, const void *pData, int StoreFormat, int Flags, const char *pTexName = NULL) override; - int LoadTextureRawSub(IGraphics::CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, int Format, const void *pData) override; + IGraphics::CTextureHandle LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName = nullptr) override; + int LoadTextureRawSub(IGraphics::CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData) override; IGraphics::CTextureHandle InvalidTexture() const override; bool LoadTextTextures(size_t Width, size_t Height, CTextureHandle &TextTexture, CTextureHandle &TextOutlineTexture, void *pTextData, void *pTextOutlineData) override; @@ -990,15 +982,15 @@ public: bool IsSpriteTextureFullyTransparent(CImageInfo &FromImageInfo, struct client_data7::CDataSprite *pSprite) override; // simple uncompressed RGBA loaders - IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) override; + IGraphics::CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) override; int LoadPNG(CImageInfo *pImg, const char *pFilename, int StorageType) override; void FreePNG(CImageInfo *pImg) override; bool CheckImageDivisibility(const char *pFileName, CImageInfo &Img, int DivX, int DivY, bool AllowResize) override; bool IsImageFormatRGBA(const char *pFileName, CImageInfo &Img) override; - void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t ColorChannelCount, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) override; - void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t ColorChannelCount, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) override; + void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t PixelSize, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) override; + void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t PixelSize, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) override; bool ScreenshotDirect(); diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index cf948df4f..155e69677 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -113,7 +113,7 @@ class CAtlas /** * Sections with a smaller width or height will not be created * when cutting larger sections, to prevent collecting many - * small, mostly unuseable sections. + * small, mostly unusable sections. */ static constexpr size_t MIN_SECTION_DIMENSION = 6; @@ -227,7 +227,7 @@ public: } // We don't iterate sections in the map with increasing width and height at the same time, - // because it's slower and doesn't noticable increase the atlas utilization. + // because it's slower and doesn't noticeable increase the atlas utilization. } // Check vector for larger section @@ -261,7 +261,7 @@ public: } } while(SectionIndex > 0); if(SmallestLossIndex == m_vSections.size()) - return false; // No useable section found in vector + return false; // No usable section found in vector // Use the section with the smallest loss const SSection Section = m_vSections[SmallestLossIndex]; @@ -729,7 +729,7 @@ public: return vec2(0.0f, 0.0f); } - void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) + void UploadEntityLayerText(void *pTexBuff, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) { if(FontSize < 1) return; @@ -777,11 +777,11 @@ public: { const int ImgOffX = clamp(x + OffX + WidthLastChars, x, (x + TexSubWidth) - 1); const int ImgOffY = clamp(y + OffY, y, (y + TexSubHeight) - 1); - const size_t ImageOffset = ImgOffY * (TexWidth * ImageColorChannelCount) + ImgOffX * ImageColorChannelCount; + const size_t ImageOffset = ImgOffY * (TexWidth * PixelSize) + ImgOffX * PixelSize; const size_t GlyphOffset = OffY * pBitmap->width + OffX; - for(size_t i = 0; i < ImageColorChannelCount; ++i) + for(size_t i = 0; i < PixelSize; ++i) { - if(i != ImageColorChannelCount - 1) + if(i != PixelSize - 1) { *(pImageBuff + ImageOffset + i) = 255; } @@ -1574,7 +1574,11 @@ public: float LastCharX = DrawX; float LastCharWidth = 0; + // Returns true if line was started const auto &&StartNewLine = [&]() { + if(pCursor->m_MaxLines > 0 && LineCount >= pCursor->m_MaxLines) + return false; + DrawX = pCursor->m_StartX; DrawY += pCursor->m_AlignedFontSize; if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) @@ -1587,6 +1591,7 @@ public: LastCharX = DrawX; LastCharWidth = 0; ++LineCount; + return true; }; if(pCursor->m_CalculateSelectionMode != TEXT_CURSOR_SELECTION_MODE_NONE || pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE) @@ -1606,7 +1611,7 @@ public: bool GotNewLine = false; bool GotNewLineLast = false; - while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines) && pCurrent != pEllipsis) + while(pCurrent < pEnd && pCurrent != pEllipsis) { bool NewLine = false; const char *pBatchEnd = pEnd; @@ -1667,8 +1672,7 @@ public: if((pCursor->m_Flags & TEXTFLAG_DISALLOW_NEWLINE) == 0) { pLastGlyph = nullptr; - StartNewLine(); - if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) + if(!StartNewLine()) break; continue; } @@ -1859,7 +1863,8 @@ public: if(NewLine) { - StartNewLine(); + if(!StartNewLine()) + break; GotNewLine = true; GotNewLineLast = true; } @@ -2139,9 +2144,9 @@ public: return TextContainer.m_BoundingBox; } - void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) override + void UploadEntityLayerText(void *pTexBuff, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) override { - m_pGlyphMap->UploadEntityLayerText(pTexBuff, ImageColorChannelCount, TexWidth, TexHeight, TexSubWidth, TexSubHeight, pText, Length, x, y, FontSize); + m_pGlyphMap->UploadEntityLayerText(pTexBuff, PixelSize, TexWidth, TexHeight, TexSubWidth, TexSubHeight, pText, Length, x, y, FontSize); } int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) const override diff --git a/src/engine/client/updater.cpp b/src/engine/client/updater.cpp index 716fe057c..16a03048b 100644 --- a/src/engine/client/updater.cpp +++ b/src/engine/client/updater.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include // system using std::map; diff --git a/src/engine/client/video.cpp b/src/engine/client/video.cpp index 0db67bd08..8fe839a8f 100644 --- a/src/engine/client/video.cpp +++ b/src/engine/client/video.cpp @@ -585,7 +585,7 @@ void CVideo::ReadRGBFromGL(size_t ThreadIndex) { uint32_t Width; uint32_t Height; - uint32_t Format; + CImageInfo::EImageFormat Format; m_pGraphics->GetReadPresentedImageDataFuncUnsafe()(Width, Height, Format, m_vPixelHelper[ThreadIndex]); } diff --git a/src/engine/console.rs b/src/engine/console.rs index 44cafdfe1..055c0802f 100644 --- a/src/engine/console.rs +++ b/src/engine/console.rs @@ -512,7 +512,7 @@ mod ffi { /// Used as a last parameter in [`IConsole::Print`]. /// /// It is treated as "no color" in the console code, meaning that the default -/// color of the output medium isn't overriden. +/// color of the output medium isn't overridden. #[allow(non_upper_case_globals)] pub const gs_ConsoleDefaultColor: ColorRGBA = ColorRGBA { r: 1.0, diff --git a/src/engine/editor.h b/src/engine/editor.h index 290e81898..9acefc5d4 100644 --- a/src/engine/editor.h +++ b/src/engine/editor.h @@ -15,6 +15,7 @@ public: virtual void OnActivate() = 0; virtual void OnWindowResize() = 0; virtual bool HasUnsavedData() const = 0; + virtual bool HandleMapDrop(const char *pFilename, int StorageType) = 0; virtual bool Load(const char *pFilename, int StorageType) = 0; virtual bool Save(const char *pFilename) = 0; virtual void UpdateMentions() = 0; diff --git a/src/engine/gfx/image_loader.cpp b/src/engine/gfx/image_loader.cpp index e9ad79ec4..bad7d9c69 100644 --- a/src/engine/gfx/image_loader.cpp +++ b/src/engine/gfx/image_loader.cpp @@ -17,14 +17,14 @@ struct SLibPNGWarningItem { SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr); pUserStruct->m_pByteLoader->m_Err = -1; - dbg_msg("libpng", "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg); + dbg_msg("png", "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg); std::longjmp(pUserStruct->m_Buf, 1); } static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg) { SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr); - dbg_msg("libpng", "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg); + dbg_msg("png", "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg); } static bool FileMatchesImageType(SImageByteBuffer &ByteLoader) @@ -80,7 +80,7 @@ static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo) { if(pPNGInfo != nullptr) png_destroy_info_struct(pPNGStruct, &pPNGInfo); - png_destroy_read_struct(&pPNGStruct, NULL, NULL); + png_destroy_read_struct(&pPNGStruct, nullptr, nullptr); } static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo) @@ -131,23 +131,19 @@ static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo) bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, int &Width, int &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat) { - png_infop pPNGInfo = nullptr; - int ColorType; - png_byte BitDepth; - int ColorChannelCount; - int BytesInRow; - Height = 0; - png_bytepp pRowPointers = nullptr; SLibPNGWarningItem UserErrorStruct = {&ByteLoader, pFileName, {}}; - png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if(pPNGStruct == NULL) + if(pPNGStruct == nullptr) { dbg_msg("png", "libpng internal failure: png_create_read_struct failed."); return false; } + png_infop pPNGInfo = nullptr; + png_bytepp pRowPointers = nullptr; + Height = 0; // ensure this is not undefined for the error handler if(setjmp(UserErrorStruct.m_Buf)) { if(pRowPointers != nullptr) @@ -165,9 +161,9 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn pPNGInfo = png_create_info_struct(pPNGStruct); - if(pPNGInfo == NULL) + if(pPNGInfo == nullptr) { - png_destroy_read_struct(&pPNGStruct, NULL, NULL); + png_destroy_read_struct(&pPNGStruct, nullptr, nullptr); dbg_msg("png", "libpng internal failure: png_create_info_struct failed."); return false; } @@ -175,6 +171,7 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn if(!FileMatchesImageType(ByteLoader)) { LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + dbg_msg("png", "file does not match image type."); return false; } @@ -189,13 +186,14 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn if(ByteLoader.m_Err != 0) { LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + dbg_msg("png", "byte loader error."); return false; } Width = png_get_image_width(pPNGStruct, pPNGInfo); Height = png_get_image_height(pPNGStruct, pPNGInfo); - ColorType = png_get_color_type(pPNGStruct, pPNGInfo); - BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo); + const int ColorType = png_get_color_type(pPNGStruct, pPNGInfo); + const png_byte BitDepth = png_get_bit_depth(pPNGStruct, pPNGInfo); PngliteIncompatible = PngliteIncompatibility(pPNGStruct, pPNGInfo); if(BitDepth == 16) @@ -227,8 +225,8 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn png_read_update_info(pPNGStruct, pPNGInfo); - ColorChannelCount = LibPNGGetColorChannelCount(ColorType); - BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo); + const int ColorChannelCount = LibPNGGetColorChannelCount(ColorType); + const int BytesInRow = png_get_rowbytes(pPNGStruct, pPNGInfo); if(BytesInRow == Width * ColorChannelCount) { @@ -255,6 +253,7 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn if(ByteLoader.m_Err != 0) { LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + dbg_msg("png", "byte loader error."); return false; } @@ -263,11 +262,12 @@ bool LoadPNG(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIn else { LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo); + dbg_msg("png", "bytes in row incorrect."); return false; } png_destroy_info_struct(pPNGStruct, &pPNGInfo); - png_destroy_read_struct(&pPNGStruct, NULL, NULL); + png_destroy_read_struct(&pPNGStruct, nullptr, nullptr); return true; } @@ -304,9 +304,9 @@ static int ImageLoaderHelperFormatToColorChannel(EImageFormat Format) bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, int Width, int Height) { - png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if(pPNGStruct == NULL) + if(pPNGStruct == nullptr) { dbg_msg("png", "libpng internal failure: png_create_write_struct failed."); return false; @@ -314,9 +314,9 @@ bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuff png_infop pPNGInfo = png_create_info_struct(pPNGStruct); - if(pPNGInfo == NULL) + if(pPNGInfo == nullptr) { - png_destroy_read_struct(&pPNGStruct, NULL, NULL); + png_destroy_read_struct(&pPNGStruct, nullptr, nullptr); dbg_msg("png", "libpng internal failure: png_create_info_struct failed."); return false; } @@ -361,7 +361,7 @@ bool SavePNG(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuff delete[](pRowPointers); png_destroy_info_struct(pPNGStruct, &pPNGInfo); - png_destroy_write_struct(&pPNGStruct, NULL); + png_destroy_write_struct(&pPNGStruct, nullptr); return true; } diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 77a9da40b..d73800974 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -66,29 +66,54 @@ struct SGraphicTileTexureCoords class CImageInfo { public: - enum + enum EImageFormat { - FORMAT_AUTO = -1, + FORMAT_ERROR = -1, FORMAT_RGB = 0, FORMAT_RGBA = 1, FORMAT_SINGLE_COMPONENT = 2, }; - /* Variable: width - Contains the width of the image */ - int m_Width; + /** + * Contains the width of the image + */ + int m_Width = 0; - /* Variable: height - Contains the height of the image */ - int m_Height; + /** + * Contains the height of the image + */ + int m_Height = 0; - /* Variable: format - Contains the format of the image. See for more information. */ - int m_Format; + /** + * Contains the format of the image. + * + * @see EImageFormat + */ + EImageFormat m_Format = FORMAT_ERROR; - /* Variable: data - Pointer to the image data. */ - void *m_pData; + /** + * Pointer to the image data. + */ + void *m_pData = nullptr; + + static size_t PixelSize(EImageFormat Format) + { + dbg_assert(Format != FORMAT_ERROR, "Format invalid"); + static const size_t s_aSizes[] = {3, 4, 1}; + return s_aSizes[(int)Format]; + } + + size_t PixelSize() const + { + return PixelSize(m_Format); + } + + static EImageFormat ImageFormatFromInt(int Format) + { + if(Format < (int)FORMAT_RGB || Format > (int)FORMAT_SINGLE_COMPONENT) + return FORMAT_ERROR; + return (EImageFormat)Format; + } }; /* @@ -207,7 +232,7 @@ namespace client_data7 { struct CDataSprite; // NOLINT(bugprone-forward-declaration-namespace) } -typedef std::function &vDstData)> TGLBackendReadPresentedImageData; +typedef std::function &vDstData)> TGLBackendReadPresentedImageData; class IGraphics : public IInterface { @@ -307,15 +332,15 @@ public: virtual bool IsImageFormatRGBA(const char *pFileName, CImageInfo &Img) = 0; // destination and source buffer require to have the same width and height - virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t ColorChannelCount, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) = 0; + virtual void CopyTextureBufferSub(uint8_t *pDestBuffer, uint8_t *pSourceBuffer, size_t FullWidth, size_t FullHeight, size_t PixelSize, size_t SubOffsetX, size_t SubOffsetY, size_t SubCopyWidth, size_t SubCopyHeight) = 0; // destination width must be equal to the subwidth of the source - virtual void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t ColorChannelCount, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) = 0; + virtual void CopyTextureFromTextureBufferSub(uint8_t *pDestBuffer, size_t DestWidth, size_t DestHeight, uint8_t *pSourceBuffer, size_t SrcWidth, size_t SrcHeight, size_t PixelSize, size_t SrcSubOffsetX, size_t SrcSubOffsetY, size_t SrcSubCopyWidth, size_t SrcSubCopyHeight) = 0; virtual int UnloadTexture(CTextureHandle *pIndex) = 0; - virtual CTextureHandle LoadTextureRaw(size_t Width, size_t Height, int Format, const void *pData, int StoreFormat, int Flags, const char *pTexName = nullptr) = 0; - virtual int LoadTextureRawSub(CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, int Format, const void *pData) = 0; - virtual CTextureHandle LoadTexture(const char *pFilename, int StorageType, int StoreFormat, int Flags) = 0; + virtual CTextureHandle LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName = nullptr) = 0; + virtual int LoadTextureRawSub(CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData) = 0; + virtual CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) = 0; virtual CTextureHandle InvalidTexture() const = 0; virtual void TextureSet(CTextureHandle Texture) = 0; void TextureClear() { TextureSet(CTextureHandle()); } diff --git a/src/engine/server.h b/src/engine/server.h index 9983e84b1..e3ae46f30 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -277,10 +277,14 @@ class IGameServer : public IInterface MACRO_INTERFACE("gameserver", 0) protected: public: - virtual void OnInit() = 0; + // `pPersistentData` may be null if this is the first time `IGameServer` + // is instantiated. + virtual void OnInit(const void *pPersistentData) = 0; virtual void OnConsoleInit() = 0; virtual void OnMapChange(char *pNewMapName, int MapNameSize) = 0; - virtual void OnShutdown() = 0; + // `pPersistentData` may be null if this is the last time `IGameServer` + // is destroyed. + virtual void OnShutdown(void *pPersistentData) = 0; virtual void OnTick() = 0; virtual void OnPreSnap() = 0; @@ -315,6 +319,7 @@ public: virtual bool IsClientReady(int ClientID) const = 0; virtual bool IsClientPlayer(int ClientID) const = 0; + virtual int PersistentDataSize() const = 0; virtual int PersistentClientDataSize() const = 0; virtual CUuid GameUuid() const = 0; diff --git a/src/engine/server/register.cpp b/src/engine/server/register.cpp index b5f8f9362..74aaf7df5 100644 --- a/src/engine/server/register.cpp +++ b/src/engine/server/register.cpp @@ -600,6 +600,11 @@ void CRegister::OnConfigChange() m_aProtocolEnabled[PROTOCOL_TW7_IPV6] = false; m_aProtocolEnabled[PROTOCOL_TW7_IPV4] = false; } + if(m_pConfig->m_SvIpv4Only) + { + m_aProtocolEnabled[PROTOCOL_TW6_IPV6] = false; + m_aProtocolEnabled[PROTOCOL_TW7_IPV6] = false; + } m_NumExtraHeaders = 0; const char *pRegisterExtra = m_pConfig->m_SvRegisterExtra; char aHeader[128]; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 99d3fc4d0..aeff8a1ab 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -368,6 +368,7 @@ CServer::~CServer() free(Client.m_pPersistentData); } } + free(m_pPersistentData); delete m_pRegister; delete m_pConnectionPool; @@ -761,7 +762,7 @@ int CServer::GetClientVersion(int ClientID) const { // Assume latest client version for server demos if(ClientID == SERVER_DEMO_CLIENT) - return CLIENT_VERSIONNR; + return DDNET_VERSION_NUMBER; CClientInfo Info; if(GetClientInfo(ClientID, &Info)) @@ -2636,6 +2637,7 @@ int CServer::Run() Client.m_pPersistentData = malloc(Size); } } + m_pPersistentData = malloc(GameServer()->PersistentDataSize()); // load map if(!LoadMap(Config()->m_SvMap)) @@ -2704,7 +2706,7 @@ int CServer::Run() Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); Antibot()->Init(); - GameServer()->OnInit(); + GameServer()->OnInit(nullptr); if(ErrorShutdown()) { m_RunServer = STOPPING; @@ -2762,7 +2764,7 @@ int CServer::Run() } } - GameServer()->OnShutdown(); + GameServer()->OnShutdown(m_pPersistentData); for(int ClientID = 0; ClientID < MAX_CLIENTS; ClientID++) { @@ -2780,7 +2782,7 @@ int CServer::Run() m_CurrentGameTick = MIN_TICK; m_ServerInfoFirstRequest = 0; Kernel()->ReregisterInterface(GameServer()); - GameServer()->OnInit(); + GameServer()->OnInit(m_pPersistentData); if(ErrorShutdown()) { break; @@ -2989,7 +2991,7 @@ int CServer::Run() m_Fifo.Shutdown(); - GameServer()->OnShutdown(); + GameServer()->OnShutdown(nullptr); m_pMap->Unload(); DbPool()->OnShutdown(); @@ -3808,6 +3810,7 @@ void CServer::RegisterCommands() Console()->Chain("sv_name", ConchainSpecialInfoupdate, this); Console()->Chain("password", ConchainSpecialInfoupdate, this); + Console()->Chain("sv_spectator_slots", ConchainSpecialInfoupdate, this); Console()->Chain("sv_max_clients_per_ip", ConchainMaxclientsperipUpdate, this); Console()->Chain("access_level", ConchainCommandAccessUpdate, this); diff --git a/src/engine/server/server.h b/src/engine/server/server.h index e1d915bea..3d0e3e873 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -248,6 +248,7 @@ public: int m_RconAuthLevel; int m_PrintCBIndex; char m_aShutdownReason[128]; + void *m_pPersistentData; enum { diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 28ccf2e45..5a25e08c5 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -131,13 +131,6 @@ public: TYPE_DDNET = 4, TYPE_KOG = 5, - SET_MASTER_ADD = 1, - SET_FAV_ADD, - SET_DDNET_ADD, - SET_KOG_ADD, - SET_TOKEN, - SET_HTTPINFO, - NETWORK_DDNET = 0, NETWORK_KOG = 1, NUM_NETWORKS, diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index 2a2f05f10..5f7c8eedb 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -10,7 +10,7 @@ MACRO_CONFIG_STR(PlayerName, player_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT MACRO_CONFIG_STR(PlayerClan, player_clan, 12, "", CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Clan of the player") MACRO_CONFIG_INT(PlayerCountry, player_country, -1, -1, 1000, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_INSENSITIVE, "Country of the player") -MACRO_CONFIG_STR(Password, password, 32, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Password to the server") +MACRO_CONFIG_STR(Password, password, 128, "", CFGFLAG_CLIENT | CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Password to the server") MACRO_CONFIG_INT(Events, events, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT | CFGFLAG_SERVER, "Enable triggering of events, (eye emotes on some holidays in server, christmas skins in client).") MACRO_CONFIG_STR(SteamName, steam_name, 16, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Last seen name of the Steam profile") @@ -24,7 +24,7 @@ MACRO_CONFIG_INT(ConsoleEnableColors, console_enable_colors, 1, 0, 1, CFGFLAG_SA MACRO_CONFIG_INT(ClSaveSettings, cl_save_settings, 1, 0, 1, CFGFLAG_CLIENT, "Write the settings file on exit") MACRO_CONFIG_INT(ClRefreshRate, cl_refresh_rate, 0, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Refresh rate for updating the game (in Hz)") MACRO_CONFIG_INT(ClRefreshRateInactive, cl_refresh_rate_inactive, 120, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Refresh rate for updating the game when the window is inactive (in Hz)") -MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(ClEditor, cl_editor, 0, 0, 1, CFGFLAG_CLIENT, "Open the map editor") MACRO_CONFIG_INT(ClEditorDilate, cl_editor_dilate, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Automatically dilates embedded images") MACRO_CONFIG_STR(ClSkinFilterString, cl_skin_filter_string, 25, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Skin filtering string") @@ -40,7 +40,7 @@ MACRO_CONFIG_INT(ClPrintBroadcasts, cl_print_broadcasts, 1, 0, 1, CFGFLAG_CLIENT MACRO_CONFIG_INT(ClPrintMotd, cl_print_motd, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Print motd to console") MACRO_CONFIG_INT(ClFriendsIgnoreClan, cl_friends_ignore_clan, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Ignore clan tag when searching for friends") -MACRO_CONFIG_STR(ClAssetsEntites, cl_assets_entities, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset/assets for entities") +MACRO_CONFIG_STR(ClAssetsEntities, cl_assets_entities, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset/assets for entities") MACRO_CONFIG_STR(ClAssetGame, cl_asset_game, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for game") MACRO_CONFIG_STR(ClAssetEmoticons, cl_asset_emoticons, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for emoticons") MACRO_CONFIG_STR(ClAssetParticles, cl_asset_particles, 50, "default", CFGFLAG_SAVE | CFGFLAG_CLIENT, "The asset for particles") @@ -89,7 +89,7 @@ MACRO_CONFIG_INT(SndGameSoundVolume, snd_game_volume, 30, 0, 100, CFGFLAG_SAVE | MACRO_CONFIG_INT(SndMapSoundVolume, snd_ambient_volume, 30, 0, 100, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Map Sound sound volume") MACRO_CONFIG_INT(SndBackgroundMusicVolume, snd_background_music_volume, 30, 0, 100, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Background music sound volume") -MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(SndNonactiveMute, snd_nonactive_mute, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Mute sounds when window is not active") MACRO_CONFIG_INT(SndGame, snd_game, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable game sounds") MACRO_CONFIG_INT(SndGun, snd_gun, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable gun sound") MACRO_CONFIG_INT(SndLongPain, snd_long_pain, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Enable long pain sound (used when shooting in freeze)") @@ -121,12 +121,11 @@ MACRO_CONFIG_INT(GfxColorDepth, gfx_color_depth, 24, 16, 24, CFGFLAG_SAVE | CFGF MACRO_CONFIG_INT(GfxVsync, gfx_vsync, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Vertical sync (may cause delay)") 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, 64, CFGFLAG_SAVE | CFGFLAG_CLIENT, "FSAA Samples") +MACRO_CONFIG_INT(GfxFsaaSamples, gfx_fsaa_samples, 0, 0, 64, CFGFLAG_SAVE | CFGFLAG_CLIENT, "FSAA samples (may cause delay)") MACRO_CONFIG_INT(GfxRefreshRate, gfx_refresh_rate, 0, 0, 10000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Screen refresh rate") -MACRO_CONFIG_INT(GfxFinish, gfx_finish, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "") MACRO_CONFIG_INT(GfxBackgroundRender, gfx_backgroundrender, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Render graphics when window is in background") MACRO_CONFIG_INT(GfxTextOverlay, gfx_text_overlay, 10, 1, 100, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Stop rendering textoverlay in editor or with entities: high value = less details = more speed") -MACRO_CONFIG_INT(GfxAsyncRenderOld, gfx_asyncrender_old, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Do rendering async from the the update") +MACRO_CONFIG_INT(GfxAsyncRenderOld, gfx_asyncrender_old, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "During an update cycle, skip the render cycle, if the render cycle would need to wait for the previous render cycle to finish") 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(InpMousesens, inp_mousesens, 200, 1, 100000, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Mouse sensitivity") @@ -160,9 +159,9 @@ MACRO_CONFIG_INT(SvHighBandwidth, sv_high_bandwidth, 0, 0, 1, CFGFLAG_SERVER, "U MACRO_CONFIG_STR(SvRegister, sv_register, 16, "1", CFGFLAG_SERVER, "Register server with master server for public listing, can also accept a comma-separated list of protocols to register on, like 'ipv4,ipv6'") MACRO_CONFIG_STR(SvRegisterExtra, sv_register_extra, 256, "", CFGFLAG_SERVER, "Extra headers to send to the register endpoint, comma separated 'Header: Value' pairs") MACRO_CONFIG_STR(SvRegisterUrl, sv_register_url, 128, "https://master1.ddnet.org/ddnet/15/register", CFGFLAG_SERVER, "Masterserver URL to register to") -MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password (full access)") -MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for moderators (limited access)") -MACRO_CONFIG_STR(SvRconHelperPassword, sv_rcon_helper_password, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for helpers (limited access)") +MACRO_CONFIG_STR(SvRconPassword, sv_rcon_password, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password (full access)") +MACRO_CONFIG_STR(SvRconModPassword, sv_rcon_mod_password, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for moderators (limited access)") +MACRO_CONFIG_STR(SvRconHelperPassword, sv_rcon_helper_password, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "Remote console password for helpers (limited access)") MACRO_CONFIG_INT(SvRconMaxTries, sv_rcon_max_tries, 30, 0, 100, CFGFLAG_SERVER, "Maximum number of tries for remote console authentication") MACRO_CONFIG_INT(SvRconBantime, sv_rcon_bantime, 5, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if remote console authentication fails. 0 makes it just use kick") MACRO_CONFIG_INT(SvAutoDemoRecord, sv_auto_demo_record, 0, 0, 1, CFGFLAG_SERVER, "Automatically record demos") @@ -186,7 +185,7 @@ MACRO_CONFIG_INT(SvSkillLevel, sv_skill_level, 1, SERVERINFO_LEVEL_MIN, SERVERIN MACRO_CONFIG_STR(EcBindaddr, ec_bindaddr, 128, "localhost", CFGFLAG_ECON, "Address to bind the external console to. Anything but 'localhost' is dangerous") MACRO_CONFIG_INT(EcPort, ec_port, 0, 0, 0, CFGFLAG_ECON, "Port to use for the external console") -MACRO_CONFIG_STR(EcPassword, ec_password, 32, "", CFGFLAG_ECON, "External console password") +MACRO_CONFIG_STR(EcPassword, ec_password, 128, "", CFGFLAG_ECON, "External console password") MACRO_CONFIG_INT(EcBantime, ec_bantime, 0, 0, 1440, CFGFLAG_ECON, "The time a client gets banned if econ authentication fails. 0 just closes the connection") MACRO_CONFIG_INT(EcAuthTimeout, ec_auth_timeout, 30, 1, 120, CFGFLAG_ECON, "Time in seconds before the the econ authentication times out") MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_ECON, "Adjusts the amount of information in the external console") @@ -194,7 +193,6 @@ MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_ECON, "Adjusts MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Debug mode") MACRO_CONFIG_INT(DbgCurl, dbg_curl, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Debug curl") MACRO_CONFIG_INT(DbgGraphs, dbg_graphs, 0, 0, 1, CFGFLAG_CLIENT, "Performance graphs") -MACRO_CONFIG_INT(DbgHitch, dbg_hitch, 0, 0, 0, CFGFLAG_SERVER, "Hitch warnings") MACRO_CONFIG_INT(DbgGfx, dbg_gfx, 0, 0, 4, CFGFLAG_CLIENT, "Show graphic library warnings and errors, if the GPU supports it (0: none, 1: minimal, 2: affects performance, 3: verbose, 4: all)") #ifdef CONF_DEBUG MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SERVER, "Stress systems (Debug build only)") @@ -206,7 +204,7 @@ MACRO_CONFIG_INT(HttpAllowInsecure, http_allow_insecure, 0, 0, 1, CFGFLAG_CLIENT // DDRace MACRO_CONFIG_STR(SvWelcome, sv_welcome, 64, "", CFGFLAG_SERVER, "Message that will be displayed to players who join the server") MACRO_CONFIG_INT(SvReservedSlots, sv_reserved_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "The number of slots that are reserved for special players") -MACRO_CONFIG_STR(SvReservedSlotsPass, sv_reserved_slots_pass, 32, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "The password that is required to use a reserved slot") +MACRO_CONFIG_STR(SvReservedSlotsPass, sv_reserved_slots_pass, 128, "", CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC, "The password that is required to use a reserved slot") MACRO_CONFIG_INT(SvReservedSlotsAuthLevel, sv_reserved_slots_auth_level, 1, 1, 4, CFGFLAG_SERVER, "Minimum rcon auth level needed to use a reserved slot. 4 = rcon auth disabled") MACRO_CONFIG_INT(SvHit, sv_hit, 1, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Whether players can hammer/grenade/laser each other or not") MACRO_CONFIG_INT(SvEndlessDrag, sv_endless_drag, 0, 0, 1, CFGFLAG_SERVER | CFGFLAG_GAME, "Turns endless hooking on/off") diff --git a/src/engine/shared/console.cpp b/src/engine/shared/console.cpp index 9ceb75ed6..eda02ee33 100644 --- a/src/engine/shared/console.cpp +++ b/src/engine/shared/console.cpp @@ -543,7 +543,7 @@ void CConsole::ExecuteLineStroked(int Stroke, const char *pStr, int ClientID, bo // ends at the first whitespace, which breaks for unknown commands (filenames) containing spaces. if(!m_pfnUnknownCommandCallback(pStr, m_pUnknownCommandUserdata)) { - char aBuf[256]; + char aBuf[512 + 32]; str_format(aBuf, sizeof(aBuf), "No such command: %s.", Result.m_pCommand); Print(OUTPUT_LEVEL_STANDARD, "chatresp", aBuf); } @@ -881,7 +881,7 @@ void CConsole::TraverseChain(FCommandCallback *ppfnCallback, void **ppUserData) void CConsole::ConToggle(IConsole::IResult *pResult, void *pUser) { CConsole *pConsole = static_cast(pUser); - char aBuf[128] = {0}; + char aBuf[512 + 32] = {0}; CCommand *pCommand = pConsole->FindCommand(pResult->GetString(0), pConsole->m_FlagMask); if(pCommand) { @@ -933,7 +933,7 @@ void CConsole::ConToggle(IConsole::IResult *pResult, void *pUser) void CConsole::ConToggleStroke(IConsole::IResult *pResult, void *pUser) { CConsole *pConsole = static_cast(pUser); - char aBuf[128] = {0}; + char aBuf[512 + 32] = {0}; CCommand *pCommand = pConsole->FindCommand(pResult->GetString(1), pConsole->m_FlagMask); if(pCommand) { diff --git a/src/engine/shared/fifo.cpp b/src/engine/shared/fifo.cpp index 7ed0b296b..af2b49f09 100644 --- a/src/engine/shared/fifo.cpp +++ b/src/engine/shared/fifo.cpp @@ -154,7 +154,13 @@ void CFifo::Update() if(!PeekNamedPipe(m_pPipe, NULL, 0, NULL, &BytesAvailable, NULL)) { const DWORD LastError = GetLastError(); - if(LastError != ERROR_BAD_PIPE) // pipe not connected, not an error + if(LastError == ERROR_BROKEN_PIPE) + { + // Pipe was disconnected from the other side, either immediately + // after connecting or after reading the previous message. + DisconnectNamedPipe(m_pPipe); + } + else { const std::string ErrorMsg = windows_format_system_message(LastError); dbg_msg("fifo", "failed to peek at pipe '%s' (%ld %s)", m_aFilename, LastError, ErrorMsg.c_str()); diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 9a96ca056..e9bef6407 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -91,6 +91,8 @@ MAYBE_UNUSED static const char *FONT_ICON_GEAR = "\xEF\x80\x93"; MAYBE_UNUSED static const char *FONT_ICON_PEN_TO_SQUARE = "\xEF\x81\x84"; MAYBE_UNUSED static const char *FONT_ICON_CLAPPERBOARD = "\xEE\x84\xB1"; MAYBE_UNUSED static const char *FONT_ICON_EARTH_AMERICAS = "\xEF\x95\xBD"; +MAYBE_UNUSED static const char *FONT_ICON_LIST_UL = "\xEF\x83\x8A"; +MAYBE_UNUSED static const char *FONT_ICON_INFO = "\xEF\x84\xA9"; MAYBE_UNUSED static const char *FONT_ICON_SLASH = "\xEF\x9C\x95"; MAYBE_UNUSED static const char *FONT_ICON_PLAY = "\xEF\x81\x8B"; @@ -98,6 +100,8 @@ MAYBE_UNUSED static const char *FONT_ICON_PAUSE = "\xEF\x81\x8C"; MAYBE_UNUSED static const char *FONT_ICON_STOP = "\xEF\x81\x8D"; MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_LEFT = "\xEF\x81\x93"; MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_RIGHT = "\xEF\x81\x94"; +MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_UP = "\xEF\x81\xB7"; +MAYBE_UNUSED static const char *FONT_ICON_CHEVRON_DOWN = "\xEF\x81\xB8"; MAYBE_UNUSED static const char *FONT_ICON_BACKWARD = "\xEF\x81\x8A"; MAYBE_UNUSED static const char *FONT_ICON_FORWARD = "\xEF\x81\x8E"; MAYBE_UNUSED static const char *FONT_ICON_RIGHT_FROM_BRACKET = "\xEF\x8B\xB5"; @@ -105,6 +109,8 @@ MAYBE_UNUSED static const char *FONT_ICON_RIGHT_TO_BRACKET = "\xEF\x8B\xB6"; MAYBE_UNUSED static const char *FONT_ICON_ARROW_UP_RIGHT_FROM_SQUARE = "\xEF\x82\x8E"; MAYBE_UNUSED static const char *FONT_ICON_BACKWARD_STEP = "\xEF\x81\x88"; MAYBE_UNUSED static const char *FONT_ICON_FORWARD_STEP = "\xEF\x81\x91"; +MAYBE_UNUSED static const char *FONT_ICON_BACKWARD_FAST = "\xEF\x81\x89"; +MAYBE_UNUSED static const char *FONT_ICON_FORWARD_FAST = "\xEF\x81\x90"; MAYBE_UNUSED static const char *FONT_ICON_KEYBOARD = "\xE2\x8C\xA8"; MAYBE_UNUSED static const char *FONT_ICON_ELLIPSIS = "\xEF\x85\x81"; @@ -281,7 +287,7 @@ public: virtual STextBoundingBox GetBoundingBoxTextContainer(STextContainerIndex TextContainerIndex) = 0; - virtual void UploadEntityLayerText(void *pTexBuff, size_t ImageColorChannelCount, int TexWidth, int TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) = 0; + virtual void UploadEntityLayerText(void *pTexBuff, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TexSubWidth, int TexSubHeight, const char *pText, int Length, float x, float y, int FontSize) = 0; virtual int AdjustFontSize(const char *pText, int TextLength, int MaxSize, int MaxWidth) const = 0; virtual float GetGlyphOffsetX(int FontSize, char TextCharacter) const = 0; virtual int CalculateTextWidth(const char *pText, int TextLength, int FontWidth, int FontSize) const = 0; diff --git a/src/game/server/alloc.h b/src/game/alloc.h similarity index 97% rename from src/game/server/alloc.h rename to src/game/alloc.h index 6ec1301c1..b4a76a9bd 100644 --- a/src/game/server/alloc.h +++ b/src/game/alloc.h @@ -1,7 +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. */ -#ifndef GAME_SERVER_ALLOC_H -#define GAME_SERVER_ALLOC_H +#ifndef GAME_ALLOC_H +#define GAME_ALLOC_H #include diff --git a/src/game/client/components/camera.cpp b/src/game/client/components/camera.cpp index 839486b9b..3937b42b9 100644 --- a/src/game/client/components/camera.cpp +++ b/src/game/client/components/camera.cpp @@ -13,8 +13,6 @@ #include -const float ZoomStep = 0.866025f; - CCamera::CCamera() { m_CamType = CAMTYPE_UNDEFINED; @@ -195,7 +193,7 @@ void CCamera::OnConsoleInit() void CCamera::OnReset() { - m_Zoom = std::pow(ZoomStep, g_Config.m_ClDefaultZoom - 10); + m_Zoom = std::pow(CCamera::ZOOM_STEP, g_Config.m_ClDefaultZoom - 10); m_Zooming = false; } @@ -204,7 +202,7 @@ void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData) CCamera *pSelf = (CCamera *)pUserData; if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { - pSelf->ScaleZoom(ZoomStep); + pSelf->ScaleZoom(CCamera::ZOOM_STEP); if(pSelf->GameClient()->m_MultiViewActivated) pSelf->GameClient()->m_MultiViewPersonalZoom++; @@ -215,7 +213,7 @@ void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData) CCamera *pSelf = (CCamera *)pUserData; if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK) { - pSelf->ScaleZoom(1 / ZoomStep); + pSelf->ScaleZoom(1 / CCamera::ZOOM_STEP); if(pSelf->GameClient()->m_MultiViewActivated) pSelf->GameClient()->m_MultiViewPersonalZoom--; @@ -225,9 +223,9 @@ void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData) { CCamera *pSelf = (CCamera *)pUserData; float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(0) : g_Config.m_ClDefaultZoom; - pSelf->ChangeZoom(std::pow(ZoomStep, TargetLevel - 10), pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime); + pSelf->ChangeZoom(std::pow(CCamera::ZOOM_STEP, TargetLevel - 10), pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime); - if(pSelf->GameClient()->m_MultiViewActivated) + if(pSelf->GameClient()->m_MultiViewActivated && pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active) pSelf->GameClient()->m_MultiViewPersonalZoom = 0; } void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData) diff --git a/src/game/client/components/camera.h b/src/game/client/components/camera.h index 9cfc8bf8d..ae8798d86 100644 --- a/src/game/client/components/camera.h +++ b/src/game/client/components/camera.h @@ -37,6 +37,8 @@ class CCamera : public CComponent float MaxZoomLevel(); public: + static constexpr float ZOOM_STEP = 0.866025f; + vec2 m_Center; bool m_ZoomSet; bool m_Zooming; diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 708ac04a1..17791ed96 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -11,12 +11,10 @@ #include #include -#include - -#include #include #include #include +#include #include #include "chat.h" @@ -819,6 +817,10 @@ void CChat::RefindSkins() Line.m_RenderSkinMetrics = pSkin->m_Metrics; } + else + { + Line.m_RenderSkin.Reset(); + } } } @@ -830,13 +832,9 @@ void CChat::OnPrepareLines() float ScreenRatio = Graphics()->ScreenAspect(); - bool IsScoreBoardOpen = m_pClient->m_Scoreboard.Active() && (ScreenRatio > 1.7f); // only assume scoreboard when screen ratio is widescreen(something around 16:9) - - bool ForceRecreate = IsScoreBoardOpen != m_PrevScoreBoardShowed; - bool ShowLargeArea = m_Show || g_Config.m_ClShowChat == 2; - - ForceRecreate |= ShowLargeArea != m_PrevShowChat; - + const bool IsScoreBoardOpen = m_pClient->m_Scoreboard.Active() && (ScreenRatio > 1.7f); // only assume scoreboard when screen ratio is widescreen(something around 16:9) + const bool ShowLargeArea = m_Show || (m_Mode != MODE_NONE && g_Config.m_ClShowChat == 1) || g_Config.m_ClShowChat == 2; + const bool ForceRecreate = IsScoreBoardOpen != m_PrevScoreBoardShowed || ShowLargeArea != m_PrevShowChat; m_PrevScoreBoardShowed = IsScoreBoardOpen; m_PrevShowChat = ShowLargeArea; @@ -979,7 +977,7 @@ void CChat::OnPrepareLines() else if(m_aLines[r].m_NameColor == TEAM_SPECTATORS) NameColor = ColorRGBA(0.75f, 0.5f, 0.75f, 1.f); else if(m_aLines[r].m_ClientID >= 0 && g_Config.m_ClChatTeamColors && m_pClient->m_Teams.Team(m_aLines[r].m_ClientID)) - NameColor = color_cast(ColorHSLA(m_pClient->m_Teams.Team(m_aLines[r].m_ClientID) / 64.0f, 1.0f, 0.75f)); + NameColor = m_pClient->GetDDTeamColor(m_pClient->m_Teams.Team(m_aLines[r].m_ClientID), 0.75f); else NameColor = ColorRGBA(0.8f, 0.8f, 0.8f, 1.f); @@ -1037,7 +1035,7 @@ void CChat::OnPrepareLines() TextRender()->UploadTextContainer(m_aLines[r].m_TextContainerIndex); } - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->TextColor(TextRender()->DefaultTextColor()); } void CChat::OnRender() @@ -1186,9 +1184,9 @@ void CChat::OnRender() RenderTools()->RenderTee(pIdleState, &RenderInfo, EMOTE_NORMAL, vec2(1, 0.1f), TeeRenderPos, Blend); } - ColorRGBA TextOutline(0.f, 0.f, 0.f, 0.3f * Blend); - ColorRGBA Text(1.f, 1.f, 1.f, Blend); - TextRender()->RenderTextContainer(m_aLines[r].m_TextContainerIndex, Text, TextOutline, 0, (y + RealMsgPaddingY / 2.0f) - m_aLines[r].m_TextYOffset); + const ColorRGBA TextColor = TextRender()->DefaultTextColor().WithMultipliedAlpha(Blend); + const ColorRGBA TextOutlineColor = TextRender()->DefaultTextOutlineColor().WithMultipliedAlpha(Blend); + TextRender()->RenderTextContainer(m_aLines[r].m_TextContainerIndex, TextColor, TextOutlineColor, 0, (y + RealMsgPaddingY / 2.0f) - m_aLines[r].m_TextYOffset); } } } diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index a26d2df10..fd097e054 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -346,7 +346,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) // find the current command { - char aBuf[128]; + char aBuf[512]; StrCopyUntilSpace(aBuf, sizeof(aBuf), GetString()); const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask, m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands()); @@ -367,9 +367,6 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) void CGameConsole::CInstance::PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) { - if(Len > 255) - Len = 255; - m_BacklogLock.lock(); CBacklogEntry *pEntry = m_Backlog.Allocate(sizeof(CBacklogEntry) + Len); pEntry->m_YOffset = -1.0f; @@ -694,7 +691,7 @@ void CGameConsole::OnRender() if(NumArguments <= 0 && pConsole->m_IsCommand) { - char aBuf[512]; + char aBuf[1024]; str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_pCommandHelp); TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); TextRender()->TextColor(0.75f, 0.75f, 0.75f, 1); @@ -729,8 +726,9 @@ void CGameConsole::OnRender() { TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, FontSize, 0); Cursor.m_LineWidth = Screen.w - 10; + Cursor.m_MaxLines = 10; TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); - pEntry->m_YOffset = Cursor.m_Y + Cursor.m_AlignedFontSize + LineOffset; + pEntry->m_YOffset = Cursor.Height() + LineOffset; } OffsetY += pEntry->m_YOffset; @@ -751,6 +749,7 @@ void CGameConsole::OnRender() { TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = Screen.w - 10.0f; + Cursor.m_MaxLines = 10; Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; Cursor.m_PressMouse = pConsole->m_MousePress; Cursor.m_ReleaseMouse = pConsole->m_MouseRelease; diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp index f99b27490..1f28a4bc5 100644 --- a/src/game/client/components/countryflags.cpp +++ b/src/game/client/components/countryflags.cpp @@ -75,7 +75,7 @@ void CCountryFlags::LoadCountryflagsIndexfile() CCountryFlag CountryFlag; CountryFlag.m_CountryCode = CountryCode; str_copy(CountryFlag.m_aCountryCodeString, aOrigin); - CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0, aOrigin); + CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0, aOrigin); Graphics()->FreePNG(&Info); if(g_Config.m_Debug) diff --git a/src/game/client/components/ghost.cpp b/src/game/client/components/ghost.cpp index 1a14e91a0..e1f007c8a 100644 --- a/src/game/client/components/ghost.cpp +++ b/src/game/client/components/ghost.cpp @@ -675,15 +675,19 @@ void CGhost::RefindSkin() if(!Ghost.Empty()) { IntsToStr(&Ghost.m_Skin.m_Skin0, 6, aSkinName); + CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo; if(aSkinName[0] != '\0') { - CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo; - const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName); pRenderInfo->m_OriginalRenderSkin = pSkin->m_OriginalSkin; pRenderInfo->m_ColorableRenderSkin = pSkin->m_ColorableSkin; pRenderInfo->m_SkinMetrics = pSkin->m_Metrics; } + else + { + pRenderInfo->m_OriginalRenderSkin.Reset(); + pRenderInfo->m_ColorableRenderSkin.Reset(); + } } } if(!m_CurGhost.Empty()) diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 86bfca2fa..2ae8832c6 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -598,11 +598,19 @@ void CHud::RenderVoting() str_format(aBuf, sizeof(aBuf), "%s - %s", aKey, Localize("Vote yes")); Base.y += Base.h; Base.h = 12.0f; + if(m_pClient->m_Voting.TakenChoice() == 1) + TextRender()->TextColor(ColorRGBA(0.2f, 0.9f, 0.2f, 0.85f)); UI()->DoLabel(&Base, aBuf, 6.0f, TEXTALIGN_ML); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + m_pClient->m_Binds.GetKey("vote no", aKey, sizeof(aKey)); str_format(aBuf, sizeof(aBuf), "%s - %s", Localize("Vote no"), aKey); + if(m_pClient->m_Voting.TakenChoice() == -1) + TextRender()->TextColor(ColorRGBA(0.9f, 0.2f, 0.2f, 0.85f)); UI()->DoLabel(&Base, aBuf, 6.0f, TEXTALIGN_MR); + + TextRender()->TextColor(TextRender()->DefaultTextColor()); } void CHud::RenderCursor() diff --git a/src/game/client/components/killmessages.cpp b/src/game/client/components/killmessages.cpp index 3f4881912..7c4a69af3 100644 --- a/src/game/client/components/killmessages.cpp +++ b/src/game/client/components/killmessages.cpp @@ -278,21 +278,18 @@ void CKillMessages::OnRender() float x = StartX; - ColorRGBA TColor(1.f, 1.f, 1.f, 1.f); - ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f); - // render victim name x -= m_aKillmsgs[r].m_VictimTextWidth; + ColorRGBA TextColor; if(m_aKillmsgs[r].m_VictimID >= 0 && g_Config.m_ClChatTeamColors && m_aKillmsgs[r].m_VictimDDTeam) - { - TColor = color_cast(ColorHSLA(m_aKillmsgs[r].m_VictimDDTeam / 64.0f, 1.0f, 0.75f)); - TColor.a = 1.f; - } + TextColor = m_pClient->GetDDTeamColor(m_aKillmsgs[r].m_VictimDDTeam, 0.75f); + else + TextColor = TextRender()->DefaultTextColor(); CreateKillmessageNamesIfNotCreated(m_aKillmsgs[r]); if(m_aKillmsgs[r].m_VictimTextContainerIndex.Valid()) - TextRender()->RenderTextContainer(m_aKillmsgs[r].m_VictimTextContainerIndex, TColor, TOutlineColor, x, y + (46.f - 36.f) / 2.f); + TextRender()->RenderTextContainer(m_aKillmsgs[r].m_VictimTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); // render victim tee x -= 24.0f; @@ -380,7 +377,7 @@ void CKillMessages::OnRender() x -= m_aKillmsgs[r].m_KillerTextWidth; if(m_aKillmsgs[r].m_KillerTextContainerIndex.Valid()) - TextRender()->RenderTextContainer(m_aKillmsgs[r].m_KillerTextContainerIndex, TColor, TOutlineColor, x, y + (46.f - 36.f) / 2.f); + TextRender()->RenderTextContainer(m_aKillmsgs[r].m_KillerTextContainerIndex, TextColor, TextRender()->DefaultTextOutlineColor(), x, y + (46.f - 36.f) / 2.f); } y += 46.0f; @@ -397,6 +394,7 @@ void CKillMessages::RefindSkins() if(m_aKillmsgs[r].m_KillerID >= 0) { + m_aKillmsgs[r].m_KillerRenderInfo = {}; const CGameClient::CClientData &Client = GameClient()->m_aClients[m_aKillmsgs[r].m_KillerID]; if(Client.m_aSkinName[0] != '\0') m_aKillmsgs[r].m_KillerRenderInfo = Client.m_RenderInfo; diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp index 0076eb88e..75fd54813 100644 --- a/src/game/client/components/mapimages.cpp +++ b/src/game/client/components/mapimages.cpp @@ -46,11 +46,11 @@ void CMapImages::OnInit() { InitOverlayTextures(); - if(str_comp(g_Config.m_ClAssetsEntites, "default") == 0) + if(str_comp(g_Config.m_ClAssetsEntities, "default") == 0) str_copy(m_aEntitiesPath, "editor/entities_clear"); else { - str_format(m_aEntitiesPath, sizeof(m_aEntitiesPath), "assets/entities/%s", g_Config.m_ClAssetsEntites); + str_format(m_aEntitiesPath, sizeof(m_aEntitiesPath), "assets/entities/%s", g_Config.m_ClAssetsEntities); } } @@ -106,13 +106,13 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap) { int LoadFlag = (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 1) != 0) ? TextureLoadFlag : 0) | (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 2) != 0) ? 0 : (Graphics()->IsTileBufferingEnabled() ? IGraphics::TEXLOAD_NO_2D_TEXTURE : 0)); const CMapItemImage_v2 *pImg = (CMapItemImage_v2 *)pMap->GetItem(Start + i); - const int Format = pImg->m_Version < CMapItemImage_v2::CURRENT_VERSION ? CImageInfo::FORMAT_RGBA : pImg->m_Format; + const CImageInfo::EImageFormat Format = pImg->m_Version < CMapItemImage_v2::CURRENT_VERSION ? CImageInfo::FORMAT_RGBA : CImageInfo::ImageFormatFromInt(pImg->m_Format); if(pImg->m_External) { char aPath[IO_MAX_PATH_LENGTH]; char *pName = (char *)pMap->GetData(pImg->m_ImageName); str_format(aPath, sizeof(aPath), "mapres/%s.png", pName); - m_aTextures[i] = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, LoadFlag); + m_aTextures[i] = Graphics()->LoadTexture(aPath, IStorage::TYPE_ALL, LoadFlag); pMap->UnloadData(pImg->m_ImageName); } else if(Format != CImageInfo::FORMAT_RGBA) @@ -126,7 +126,7 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap) char *pName = (char *)pMap->GetData(pImg->m_ImageName); char aTexName[128]; str_format(aTexName, sizeof(aTexName), "%s %s", "embedded:", pName); - m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, Format, pData, CImageInfo::FORMAT_RGBA, LoadFlag, aTexName); + m_aTextures[i] = Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, Format, pData, LoadFlag, aTexName); pMap->UnloadData(pImg->m_ImageName); pMap->UnloadData(pImg->m_ImageData); } @@ -241,15 +241,8 @@ IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType Entit if(ImagePNGLoaded && ImgInfo.m_Width > 0 && ImgInfo.m_Height > 0) { - int ColorChannelCount = 4; - if(ImgInfo.m_Format == CImageInfo::FORMAT_SINGLE_COMPONENT) - ColorChannelCount = 1; - else if(ImgInfo.m_Format == CImageInfo::FORMAT_RGB) - ColorChannelCount = 3; - else if(ImgInfo.m_Format == CImageInfo::FORMAT_RGBA) - ColorChannelCount = 4; - - int BuildImageSize = ColorChannelCount * ImgInfo.m_Width * ImgInfo.m_Height; + const size_t PixelSize = ImgInfo.PixelSize(); + const size_t BuildImageSize = (size_t)ImgInfo.m_Width * ImgInfo.m_Height * PixelSize; uint8_t *pTmpImgData = (uint8_t *)ImgInfo.m_pData; uint8_t *pBuildImgData = (uint8_t *)malloc(BuildImageSize); @@ -318,11 +311,11 @@ IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType Entit int CopyHeight = ImgInfo.m_Height / 16; if(ValidTile) { - Graphics()->CopyTextureBufferSub(pBuildImgData, pTmpImgData, ImgInfo.m_Width, ImgInfo.m_Height, ColorChannelCount, (size_t)X * CopyWidth, (size_t)Y * CopyHeight, CopyWidth, CopyHeight); + Graphics()->CopyTextureBufferSub(pBuildImgData, pTmpImgData, ImgInfo.m_Width, ImgInfo.m_Height, PixelSize, (size_t)X * CopyWidth, (size_t)Y * CopyHeight, CopyWidth, CopyHeight); } } - m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][n] = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, pBuildImgData, ImgInfo.m_Format, TextureLoadFlag, aPath); + m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][n] = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, pBuildImgData, TextureLoadFlag, aPath); } else { @@ -331,7 +324,7 @@ IGraphics::CTextureHandle CMapImages::GetEntities(EMapImageEntityLayerType Entit // set everything transparent mem_zero(pBuildImgData, BuildImageSize); - m_TransparentTexture = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, pBuildImgData, ImgInfo.m_Format, TextureLoadFlag, aPath); + m_TransparentTexture = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, pBuildImgData, TextureLoadFlag, aPath); } m_aaEntitiesTextures[(EntitiesModType * 2) + (int)EntitiesAreMasked][n] = m_TransparentTexture; } @@ -351,7 +344,7 @@ IGraphics::CTextureHandle CMapImages::GetSpeedupArrow() if(!m_SpeedupArrowIsLoaded) { int TextureLoadFlag = (Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE_SINGLE_LAYER : IGraphics::TEXLOAD_TO_3D_TEXTURE_SINGLE_LAYER) | IGraphics::TEXLOAD_NO_2D_TEXTURE; - m_SpeedupArrowTexture = Graphics()->LoadTexture(g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, TextureLoadFlag); + m_SpeedupArrowTexture = Graphics()->LoadTexture(g_pData->m_aImages[IMAGE_SPEEDUP_ARROW].m_pFilename, IStorage::TYPE_ALL, TextureLoadFlag); m_SpeedupArrowIsLoaded = true; } @@ -428,20 +421,24 @@ int CMapImages::GetTextureScale() IGraphics::CTextureHandle CMapImages::UploadEntityLayerText(int TextureSize, int MaxWidth, int YOffset) { - void *pMem = calloc(1024 * 1024 * 4, 1); + const size_t Width = 1024; + const size_t Height = 1024; + const size_t PixelSize = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); - UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 0); - UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 1); - UpdateEntityLayerText(pMem, 4, 1024, 1024, TextureSize, MaxWidth, YOffset, 2, 255); + void *pMem = calloc(Width * Height * PixelSize, 1); - int TextureLoadFlag = (Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE) | IGraphics::TEXLOAD_NO_2D_TEXTURE; - IGraphics::CTextureHandle Texture = Graphics()->LoadTextureRaw(1024, 1024, CImageInfo::FORMAT_RGBA, pMem, CImageInfo::FORMAT_RGBA, TextureLoadFlag); + UpdateEntityLayerText(pMem, PixelSize, Width, Height, TextureSize, MaxWidth, YOffset, 0); + UpdateEntityLayerText(pMem, PixelSize, Width, Height, TextureSize, MaxWidth, YOffset, 1); + UpdateEntityLayerText(pMem, PixelSize, Width, Height, TextureSize, MaxWidth, YOffset, 2, 255); + + const int TextureLoadFlag = (Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE) | IGraphics::TEXLOAD_NO_2D_TEXTURE; + IGraphics::CTextureHandle Texture = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_RGBA, pMem, TextureLoadFlag); free(pMem); return Texture; } -void CMapImages::UpdateEntityLayerText(void *pTexBuffer, int ImageColorChannelCount, int TexWidth, int TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber) +void CMapImages::UpdateEntityLayerText(void *pTexBuffer, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber) { char aBuf[4]; int DigitsCount = NumbersPower + 1; @@ -468,7 +465,7 @@ void CMapImages::UpdateEntityLayerText(void *pTexBuffer, int ImageColorChannelCo int ApproximateTextWidth = TextRender()->CalculateTextWidth(aBuf, DigitsCount, 0, UniversalSuitableFontSize); int XOffSet = (MaxWidth - clamp(ApproximateTextWidth, 0, MaxWidth)) / 2; - TextRender()->UploadEntityLayerText(pTexBuffer, ImageColorChannelCount, TexWidth, TexHeight, (TexWidth / 16) - XOffSet, (TexHeight / 16) - YOffset, aBuf, DigitsCount, x + XOffSet, y + YOffset, UniversalSuitableFontSize); + TextRender()->UploadEntityLayerText(pTexBuffer, PixelSize, TexWidth, TexHeight, (TexWidth / 16) - XOffSet, (TexHeight / 16) - YOffset, aBuf, DigitsCount, x + XOffSet, y + YOffset, UniversalSuitableFontSize); } } diff --git a/src/game/client/components/mapimages.h b/src/game/client/components/mapimages.h index 28c88e8b2..cdd8bbfba 100644 --- a/src/game/client/components/mapimages.h +++ b/src/game/client/components/mapimages.h @@ -86,7 +86,7 @@ private: void InitOverlayTextures(); IGraphics::CTextureHandle UploadEntityLayerText(int TextureSize, int MaxWidth, int YOffset); - void UpdateEntityLayerText(void *pTexBuffer, int ImageColorChannelCount, int TexWidth, int TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber = -1); + void UpdateEntityLayerText(void *pTexBuffer, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber = -1); }; #endif diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index f88b0489f..5d074e5d2 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -448,10 +448,10 @@ int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Che return DoButton_CheckBox_Common(pID, pText, aBuf, pRect); } -int CMenus::DoKeyReader(void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination) +int CMenus::DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination) { // process - static void *pGrabbedID = 0; + static const void *pGrabbedID = 0; static bool MouseReleased = true; static int s_ButtonUsed = 0; const bool Inside = UI()->MouseHovered(pRect); @@ -864,7 +864,7 @@ void CMenus::OnInit() Console()->Chain("cl_asset_hud", ConchainAssetHud, this); Console()->Chain("cl_asset_extras", ConchainAssetExtras, this); - m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); + m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL); // setup load amount const int NumMenuImages = 5; @@ -1299,7 +1299,10 @@ int CMenus::Render() if(UseIpLabel) { - UI()->DoLabel(&Part, Client()->ConnectAddressString(), FontSize, TEXTALIGN_MC); + SLabelProperties IpLabelProps; + IpLabelProps.m_MaxWidth = Part.w; + IpLabelProps.m_EllipsisAtEnd = true; + UI()->DoLabel(&Part, Client()->ConnectAddressString(), FontSize, TEXTALIGN_MC, IpLabelProps); Box.HSplitTop(20.f, &Part, &Box); Box.HSplitTop(24.f, &Part, &Box); } @@ -2173,10 +2176,9 @@ int CMenus::MenuImageScan(const char *pName, int IsDir, int DirType, void *pUser MenuImage.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); unsigned char *pData = (unsigned char *)Info.m_pData; - //int Pitch = Info.m_Width*4; // create colorless version - int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3; + const size_t Step = Info.PixelSize(); // make the texture gray scale for(int i = 0; i < Info.m_Width * Info.m_Height; i++) diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index b51313b3e..51bc1210b 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -71,7 +71,7 @@ class CMenus : public CComponent int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect); void DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect); - int DoKeyReader(void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination); + int DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination); void DoSettingsControlsButtons(int Start, int Stop, CUIRect View); @@ -409,8 +409,6 @@ protected: std::vector m_avFriends[NUM_FRIEND_TYPES]; const CFriendItem *m_pRemoveFriend = nullptr; - void FriendlistOnUpdate(); - // found in menus.cpp int Render(); #if defined(CONF_VIDEORECORDER) @@ -454,11 +452,19 @@ protected: // found in menus_browser.cpp int m_SelectedIndex; bool m_ServerBrowserShouldRevealSelection; - void RenderServerbrowserServerList(CUIRect View); + void RenderServerbrowserServerList(CUIRect View, bool &WasListboxItemActivated); + void RenderServerbrowserStatusBox(CUIRect StatusBox, bool WasListboxItemActivated); void Connect(const char *pAddress); void PopupConfirmSwitchServer(); - void RenderServerbrowserServerDetail(CUIRect View); void RenderServerbrowserFilters(CUIRect View); + void RenderServerbrowserDDNetFilter(CUIRect View, + char *pFilterExclude, int FilterExcludeSize, + float ItemHeight, int MaxItems, int ItemsPerRow, + CScrollRegion &ScrollRegion, std::vector &vItemIds, + const std::function &GetItemName, + const std::function &RenderItem); + void RenderServerbrowserCountriesFilter(CUIRect View, int Network); + void RenderServerbrowserTypesFilter(CUIRect View, int Network); struct SPopupCountrySelectionContext { CMenus *m_pMenus; @@ -466,11 +472,17 @@ protected: bool m_New; }; static CUI::EPopupMenuFunctionResult PopupCountrySelection(void *pContext, CUIRect View, bool Active); + void RenderServerbrowserInfo(CUIRect View); + void RenderServerbrowserInfoScoreboard(CUIRect View, const CServerInfo *pSelectedServer); void RenderServerbrowserFriends(CUIRect View); + void FriendlistOnUpdate(); void PopupConfirmRemoveFriend(); + void RenderServerbrowserTabBar(CUIRect TabBar); + void RenderServerbrowserToolBox(CUIRect ToolBox); void RenderServerbrowser(CUIRect MainView); template bool PrintHighlighted(const char *pName, F &&PrintFn); + CTeeRenderInfo GetTeeRenderInfo(vec2 Size, const char *pSkinName, bool CustomSkinColors, int CustomSkinColorBody, int CustomSkinColorFeet) const; static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); @@ -583,6 +595,9 @@ public: SMALL_TAB_EDITOR, SMALL_TAB_DEMOBUTTON, SMALL_TAB_SERVER, + SMALL_TAB_BROWSER_FILTER, + SMALL_TAB_BROWSER_INFO, + SMALL_TAB_BROWSER_FRIENDS, SMALL_TAB_LENGTH, }; diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index e5dff168b..b2a0cd23b 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -23,21 +22,14 @@ using namespace FontIcons; -static const int gs_OffsetColFlagLock = 2; -static const int gs_OffsetColFav = gs_OffsetColFlagLock + 3; -static const int gs_OffsetColOff = gs_OffsetColFav + 3; -static const int gs_OffsetColName = gs_OffsetColOff + 3; -static const int gs_OffsetColGameType = gs_OffsetColName + 3; -static const int gs_OffsetColMap = gs_OffsetColGameType + 3; -static const int gs_OffsetColPlayers = gs_OffsetColMap + 3; -static const int gs_OffsetColPing = gs_OffsetColPlayers + 3; -static const int gs_OffsetColVersion = gs_OffsetColPing + 3; +static const ColorRGBA gs_HighlightedTextColor = ColorRGBA(0.4f, 0.4f, 1.0f, 1.0f); -void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo *pInfo) +template +static void FormatServerbrowserPing(char (&aBuffer)[N], const CServerInfo *pInfo) { if(!pInfo->m_LatencyIsEstimated) { - str_from_int(pInfo->m_Latency, pBuffer, BufferLength); + str_from_int(pInfo->m_Latency, aBuffer); return; } static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = { @@ -51,30 +43,60 @@ void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo Localizable("CHN"), // LOC_CHINA }; dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range"); - str_copy(pBuffer, Localize(LOCATION_NAMES[pInfo->m_Location]), BufferLength); + str_copy(aBuffer, Localize(LOCATION_NAMES[pInfo->m_Location])); } -void CMenus::RenderServerbrowserServerList(CUIRect View) +static ColorRGBA GetPingTextColor(int Latency) { + return color_cast(ColorHSLA((300.0f - clamp(Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f)); +} + +static ColorRGBA GetGametypeTextColor(const char *pGametype) +{ + ColorHSLA HslaColor; + if(str_comp(pGametype, "DM") == 0 || str_comp(pGametype, "TDM") == 0 || str_comp(pGametype, "CTF") == 0) + HslaColor = ColorHSLA(0.33f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "catch")) + HslaColor = ColorHSLA(0.17f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "idm") || str_find_nocase(pGametype, "itdm") || str_find_nocase(pGametype, "ictf") || str_find_nocase(pGametype, "f-ddrace")) + HslaColor = ColorHSLA(0.0f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "fng")) + HslaColor = ColorHSLA(0.83f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "gores")) + HslaColor = ColorHSLA(0.525f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "BW")) + HslaColor = ColorHSLA(0.05f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "ddracenet") || str_find_nocase(pGametype, "ddnet") || str_find_nocase(pGametype, "0xf")) + HslaColor = ColorHSLA(0.58f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "ddrace") || str_find_nocase(pGametype, "mkrace")) + HslaColor = ColorHSLA(0.75f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "race") || str_find_nocase(pGametype, "fastcap")) + HslaColor = ColorHSLA(0.46f, 1.0f, 0.75f); + else if(str_find_nocase(pGametype, "s-ddr")) + HslaColor = ColorHSLA(1.0f, 1.0f, 0.7f); + else + HslaColor = ColorHSLA(1.0f, 1.0f, 1.0f); + return color_cast(HslaColor); +} + +void CMenus::RenderServerbrowserServerList(CUIRect View, bool &WasListboxItemActivated) +{ + static CListBox s_ListBox; + CUIRect Headers; - CUIRect Status; - View.HSplitTop(ms_ListheaderHeight, &Headers, &View); - View.HSplitBottom(65.0f, &View, &Status); + Headers.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_T, 5.0f); + Headers.VSplitRight(s_ListBox.ScrollbarWidthMax(), &Headers, nullptr); + View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_NONE, 0.0f); - // split of the scrollbar - Headers.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 5.0f); - Headers.VSplitRight(20.0f, &Headers, 0); - - struct CColumn + struct SColumn { int m_ID; int m_Sort; - CLocConstString m_Caption; + const char *m_pCaption; int m_Direction; float m_Width; CUIRect m_Rect; - CUIRect m_Spacer; }; enum @@ -87,23 +109,39 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) COL_MAP, COL_PLAYERS, COL_PING, - COL_VERSION, + + UI_ELEM_LOCK_ICON = 0, + UI_ELEM_FAVORITE_ICON, + UI_ELEM_OFFICIAL_ICON_1, + UI_ELEM_OFFICIAL_ICON_2, + UI_ELEM_NAME_1, + UI_ELEM_NAME_2, + UI_ELEM_NAME_3, + UI_ELEM_GAMETYPE, + UI_ELEM_MAP_1, + UI_ELEM_MAP_2, + UI_ELEM_MAP_3, + UI_ELEM_FINISH_ICON, + UI_ELEM_PLAYERS, + UI_ELEM_FRIEND_ICON, + UI_ELEM_PING, + NUM_UI_ELEMS, }; - CColumn s_aCols[] = { - {-1, -1, " ", -1, 2.0f, {0}, {0}}, - {COL_FLAG_LOCK, -1, " ", -1, 14.0f, {0}, {0}}, - {COL_FLAG_FAV, -1, " ", -1, 14.0f, {0}, {0}}, - {COL_FLAG_OFFICIAL, -1, " ", -1, 14.0f, {0}, {0}}, - {COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 50.0f, {0}, {0}}, // Localize - these strings are localized within CLocConstString - {COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, Localizable("Type"), 1, 50.0f, {0}, {0}}, - {COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 120.0f + (Headers.w - 480) / 8, {0}, {0}}, - {COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 85.0f, {0}, {0}}, - {-1, -1, " ", 1, 10.0f, {0}, {0}}, - {COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, {0}, {0}}, + static SColumn s_aCols[] = { + {-1, -1, "", -1, 2.0f, {0}}, + {COL_FLAG_LOCK, -1, "", -1, 14.0f, {0}}, + {COL_FLAG_FAV, -1, "", -1, 14.0f, {0}}, + {COL_FLAG_OFFICIAL, -1, "", -1, 14.0f, {0}}, + {COL_NAME, IServerBrowser::SORT_NAME, Localizable("Name"), 0, 50.0f, {0}}, + {COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, Localizable("Type"), 1, 50.0f, {0}}, + {COL_MAP, IServerBrowser::SORT_MAP, Localizable("Map"), 1, 120.0f + (Headers.w - 480) / 8, {0}}, + {COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, Localizable("Players"), 1, 85.0f, {0}}, + {-1, -1, "", 1, 10.0f, {0}}, + {COL_PING, IServerBrowser::SORT_PING, Localizable("Ping"), 1, 40.0f, {0}}, }; - int NumCols = std::size(s_aCols); + const int NumCols = std::size(s_aCols); // do layout for(int i = 0; i < NumCols; i++) @@ -114,7 +152,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) if(i + 1 < NumCols) { - Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); + Headers.VSplitLeft(2.0f, nullptr, &Headers); } } } @@ -124,67 +162,51 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) if(s_aCols[i].m_Direction == 1) { Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect); - Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer); + Headers.VSplitRight(2.0f, &Headers, nullptr); } } - for(int i = 0; i < NumCols; i++) + for(auto &Col : s_aCols) { - if(s_aCols[i].m_Direction == 0) - s_aCols[i].m_Rect = Headers; + if(Col.m_Direction == 0) + Col.m_Rect = Headers; } const bool PlayersOrPing = (g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS || g_Config.m_BrSort == IServerBrowser::SORT_PING); // do headers - for(int i = 0; i < NumCols; i++) + for(const auto &Col : s_aCols) { - int Checked = g_Config.m_BrSort == s_aCols[i].m_Sort; - if(PlayersOrPing && g_Config.m_BrSortOrder == 2 && (s_aCols[i].m_Sort == IServerBrowser::SORT_NUMPLAYERS || s_aCols[i].m_Sort == IServerBrowser::SORT_PING)) + int Checked = g_Config.m_BrSort == Col.m_Sort; + if(PlayersOrPing && g_Config.m_BrSortOrder == 2 && (Col.m_Sort == IServerBrowser::SORT_NUMPLAYERS || Col.m_Sort == IServerBrowser::SORT_PING)) Checked = 2; - if(DoButton_GridHeader(s_aCols[i].m_Caption, Localize(s_aCols[i].m_Caption), Checked, &s_aCols[i].m_Rect)) + if(DoButton_GridHeader(&Col.m_ID, Localize(Col.m_pCaption), Checked, &Col.m_Rect)) { - if(s_aCols[i].m_Sort != -1) + if(Col.m_Sort != -1) { - if(g_Config.m_BrSort == s_aCols[i].m_Sort) - { - if(PlayersOrPing) - g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 3; - else - g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 2; - } + if(g_Config.m_BrSort == Col.m_Sort) + g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % (PlayersOrPing ? 3 : 2); else g_Config.m_BrSortOrder = 0; - g_Config.m_BrSort = s_aCols[i].m_Sort; + g_Config.m_BrSort = Col.m_Sort; } } } - View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0); - - int NumServers = ServerBrowser()->NumSortedServers(); + const int NumServers = ServerBrowser()->NumSortedServers(); // display important messages in the middle of the screen so no // users misses it { - CUIRect MsgBox = View; - if(!ServerBrowser()->NumServers() && ServerBrowser()->IsGettingServerlist()) - UI()->DoLabel(&MsgBox, Localize("Getting server list from master server"), 16.0f, TEXTALIGN_MC); + UI()->DoLabel(&View, Localize("Getting server list from master server"), 16.0f, TEXTALIGN_MC); else if(!ServerBrowser()->NumServers()) - UI()->DoLabel(&MsgBox, Localize("No servers found"), 16.0f, TEXTALIGN_MC); + UI()->DoLabel(&View, Localize("No servers found"), 16.0f, TEXTALIGN_MC); else if(ServerBrowser()->NumServers() && !NumServers) - UI()->DoLabel(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_MC); + UI()->DoLabel(&View, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_MC); } - if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB)) - { - const int Direction = Input()->ShiftIsPressed() ? -1 : 1; - g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + Direction) % 3; - } - - static CListBox s_ListBox; s_ListBox.SetActive(!UI()->IsPopupOpen()); s_ListBox.DoStart(ms_ListheaderHeight, NumServers, 1, 3, -1, &View, false); @@ -195,10 +217,8 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } m_SelectedIndex = -1; - auto RenderBrowserIcons = [this](CUIElement::SUIElementRect &UIRect, CUIRect *pRect, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, const char *pText, int TextAlign, bool SmallFont = false) { - float FontSize = 14.0f; - if(SmallFont) - FontSize = 6.0f; + const auto &&RenderBrowserIcons = [this](CUIElement::SUIElementRect &UIRect, CUIRect *pRect, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, const char *pText, int TextAlign, bool SmallFont = false) { + const float FontSize = SmallFont ? 6.0f : 14.0f; TextRender()->SetFontPreset(EFontPreset::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); TextRender()->TextColor(TextColor); @@ -210,16 +230,13 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); }; - int NumPlayers = 0; for(int i = 0; i < NumServers; i++) { const CServerInfo *pItem = ServerBrowser()->SortedGet(i); - NumPlayers += pItem->m_NumFilteredPlayers; if(pItem->m_pUIElement == nullptr) { - const int UIRectCount = 2 + (COL_VERSION + 1) * 3; - pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount); + pItem->m_pUIElement = UI()->GetNewUIElement(NUM_UI_ELEMS); } const CListboxItem ListItem = s_ListBox.DoNextItem(pItem, str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0); @@ -250,37 +267,36 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } const float FontSize = 12.0f; - for(int c = 0; c < NumCols; c++) + char aTemp[64]; + for(const auto &Col : s_aCols) { CUIRect Button; - char aTemp[64]; - Button.x = s_aCols[c].m_Rect.x; + Button.x = Col.m_Rect.x; Button.y = ListItem.m_Rect.y; Button.h = ListItem.m_Rect.h; - Button.w = s_aCols[c].m_Rect.w; - - int ID = s_aCols[c].m_ID; + Button.w = Col.m_Rect.w; + const int ID = Col.m_ID; if(ID == COL_FLAG_LOCK) { if(pItem->m_Flags & SERVER_FLAG_PASSWORD) { - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 0), &Button, {0.75f, 0.75f, 0.75f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_LOCK, TEXTALIGN_MC); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_LOCK_ICON), &Button, ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f), TextRender()->DefaultTextOutlineColor(), FONT_ICON_LOCK, TEXTALIGN_MC); } } else if(ID == COL_FLAG_FAV) { if(pItem->m_Favorite != TRISTATE::NONE) { - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 0), &Button, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_FAVORITE_ICON), &Button, ColorRGBA(0.94f, 0.4f, 0.4f, 1.0f), TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC); } } else if(ID == COL_FLAG_OFFICIAL) { if(pItem->m_Official && g_Config.m_UiPage != PAGE_DDNET && g_Config.m_UiPage != PAGE_KOG) { - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 0), &Button, {0.4f, 0.7f, 0.94f, 1}, {0.0f, 0.0f, 0.0f, 1.0f}, FONT_ICON_CERTIFICATE, TEXTALIGN_MC); - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 1), &Button, {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, FONT_ICON_CHECK, TEXTALIGN_MC, true); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_OFFICIAL_ICON_1), &Button, ColorRGBA(0.4f, 0.7f, 0.94f, 1.0f), ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f), FONT_ICON_CERTIFICATE, TEXTALIGN_MC); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_OFFICIAL_ICON_2), &Button, ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f), ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f), FONT_ICON_CHECK, TEXTALIGN_MC, true); } } else if(ID == COL_NAME) @@ -291,90 +307,15 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) Props.m_EnableWidthCheck = false; bool Printed = false; if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME)) - Printed = PrintHighlighted(pItem->m_aName, [this, pItem, FontSize, Props, &Button](const char *pFilteredStr, const int FilterLen) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aName)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor); - TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor); + Printed = PrintHighlighted(pItem->m_aName, [&](const char *pFilteredStr, const int FilterLen) { + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_NAME_1), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aName)); + TextRender()->TextColor(gs_HighlightedTextColor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_NAME_2), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(UI_ELEM_NAME_1)->m_Cursor); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_NAME_3), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(UI_ELEM_NAME_2)->m_Cursor); }); if(!Printed) - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props); - } - else if(ID == COL_MAP) - { - if(g_Config.m_UiPage == PAGE_DDNET) - { - CUIRect Icon; - Button.VMargin(4.0f, &Button); - Button.VSplitLeft(Button.h, &Icon, &Button); - Icon.Margin(2.0f, &Icon); - - if(g_Config.m_BrIndicateFinished && pItem->m_HasRank == 1) - { - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 1), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), FONT_ICON_FLAG_CHECKERED, TEXTALIGN_MC); - } - } - - SLabelProperties Props; - Props.m_MaxWidth = Button.w; - Props.m_StopAtEnd = true; - Props.m_EnableWidthCheck = false; - bool Printed = false; - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME)) - Printed = PrintHighlighted(pItem->m_aMap, [this, pItem, FontSize, Props, &Button](const char *pFilteredStr, const int FilterLen) { - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aMap)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor); - TextRender()->TextColor(1, 1, 1, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor); - }); - if(!Printed) - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props); - } - else if(ID == COL_PLAYERS) - { - CUIRect Icon, IconText; - Button.VMargin(2.0f, &Button); - if(pItem->m_FriendState != IFriends::FRIEND_NO) - { - Button.VSplitRight(50.0f, &Icon, &Button); - Icon.Margin(2.0f, &Icon); - Icon.HSplitBottom(6.0f, 0, &IconText); - RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 1), &Icon, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC); - if(FriendsOnServer > 1) - { - char aBufFriendsOnServer[64]; - str_from_int(FriendsOnServer, aBufFriendsOnServer); - TextRender()->TextColor(0.94f, 0.8f, 0.8f, 1); - UI()->DoLabel(&IconText, aBufFriendsOnServer, 10.0f, TEXTALIGN_MC); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1); - } - } - - str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem)); - if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER)) - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_MR); - TextRender()->TextColor(1, 1, 1, 1); - } - else if(ID == COL_PING) - { - Button.VMargin(4.0f, &Button); - 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)); - TextRender()->TextColor(rgb); - } - - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_MR); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - } - else if(ID == COL_VERSION) - { - const char *pVersion = pItem->m_aVersion; - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_MR); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_NAME_1), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Props); } else if(ID == COL_GAMETYPE) { @@ -384,36 +325,77 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) Props.m_EnableWidthCheck = false; if(g_Config.m_UiColorizeGametype) { - ColorHSLA hsl = ColorHSLA(1.0f, 1.0f, 1.0f); - - if(str_comp(pItem->m_aGameType, "DM") == 0 || str_comp(pItem->m_aGameType, "TDM") == 0 || str_comp(pItem->m_aGameType, "CTF") == 0) - hsl = ColorHSLA(0.33f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "catch")) - hsl = ColorHSLA(0.17f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "idm") || str_find_nocase(pItem->m_aGameType, "itdm") || str_find_nocase(pItem->m_aGameType, "ictf") || str_find_nocase(pItem->m_aGameType, "f-ddrace")) - hsl = ColorHSLA(0.00f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "fng")) - hsl = ColorHSLA(0.83f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "gores")) - hsl = ColorHSLA(0.525f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "BW")) - hsl = ColorHSLA(0.050f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "ddracenet") || str_find_nocase(pItem->m_aGameType, "ddnet") || str_find_nocase(pItem->m_aGameType, "0xf")) - hsl = ColorHSLA(0.58f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "ddrace") || str_find_nocase(pItem->m_aGameType, "mkrace")) - hsl = ColorHSLA(0.75f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "race") || str_find_nocase(pItem->m_aGameType, "fastcap")) - hsl = ColorHSLA(0.46f, 1.0f, 0.75f); - else if(str_find_nocase(pItem->m_aGameType, "s-ddr")) - hsl = ColorHSLA(1.0f, 1.0f, 0.70f); - - ColorRGBA rgb = color_cast(hsl); - TextRender()->TextColor(rgb); - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Props); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->TextColor(GetGametypeTextColor(pItem->m_aGameType)); } - else - UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Props); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_GAMETYPE), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Props); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + else if(ID == COL_MAP) + { + if(g_Config.m_UiPage == PAGE_DDNET) + { + CUIRect Icon; + Button.VMargin(4.0f, &Button); + Button.VSplitLeft(Button.h, &Icon, &Button); + if(g_Config.m_BrIndicateFinished && pItem->m_HasRank == 1) + { + Icon.Margin(2.0f, &Icon); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_FINISH_ICON), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), FONT_ICON_FLAG_CHECKERED, TEXTALIGN_MC); + } + } + + SLabelProperties Props; + Props.m_MaxWidth = Button.w; + Props.m_StopAtEnd = true; + Props.m_EnableWidthCheck = false; + bool Printed = false; + if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME)) + Printed = PrintHighlighted(pItem->m_aMap, [&](const char *pFilteredStr, const int FilterLen) { + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_MAP_1), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props, (int)(pFilteredStr - pItem->m_aMap)); + TextRender()->TextColor(gs_HighlightedTextColor); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_MAP_2), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Props, FilterLen, &pItem->m_pUIElement->Rect(UI_ELEM_MAP_1)->m_Cursor); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_MAP_3), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Props, -1, &pItem->m_pUIElement->Rect(UI_ELEM_MAP_2)->m_Cursor); + }); + if(!Printed) + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_MAP_1), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Props); + } + else if(ID == COL_PLAYERS) + { + Button.VMargin(2.0f, &Button); + if(pItem->m_FriendState != IFriends::FRIEND_NO) + { + CUIRect Icon; + Button.VSplitRight(50.0f, &Icon, &Button); + Icon.Margin(2.0f, &Icon); + RenderBrowserIcons(*pItem->m_pUIElement->Rect(UI_ELEM_FRIEND_ICON), &Icon, ColorRGBA(0.94f, 0.4f, 0.4f, 1.0f), TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC); + if(FriendsOnServer > 1) + { + str_from_int(FriendsOnServer, aTemp); + TextRender()->TextColor(0.94f, 0.8f, 0.8f, 1.0f); + UI()->DoLabel(&Icon, aTemp, 9.0f, TEXTALIGN_MC); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + } + + str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem)); + if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER)) + { + TextRender()->TextColor(gs_HighlightedTextColor); + } + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_PLAYERS), &Button, aTemp, FontSize, TEXTALIGN_MR); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } + else if(ID == COL_PING) + { + Button.VMargin(4.0f, &Button); + FormatServerbrowserPing(aTemp, pItem); + if(g_Config.m_UiColorizePing) + { + TextRender()->TextColor(GetPingTextColor(pItem->m_Latency)); + } + UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(UI_ELEM_PING), &Button, aTemp, FontSize, TEXTALIGN_MR); + TextRender()->TextColor(TextRender()->DefaultTextColor()); } } } @@ -434,10 +416,15 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) } } + WasListboxItemActivated = s_ListBox.WasItemActivated(); +} + +void CMenus::RenderServerbrowserStatusBox(CUIRect StatusBox, bool WasListboxItemActivated) +{ // Render bar that shows the loading progression. // The bar is only shown while loading and fades out when it's done. CUIRect RefreshBar; - Status.HSplitTop(5.0f, &RefreshBar, &Status); + StatusBox.HSplitTop(5.0f, &RefreshBar, &StatusBox); static float s_LoadingProgressionFadeEnd = 0.0f; if(ServerBrowser()->IsRefreshing() && ServerBrowser()->LoadingProgression() < 100) { @@ -452,48 +439,46 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) RefreshBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, RefreshBarAlpha), IGraphics::CORNER_NONE, 0.0f); } - CUIRect SearchInfoAndAddr, ServersAndConnect, Status3; - Status.VSplitRight(125.0f, &SearchInfoAndAddr, &ServersAndConnect); + TextRender()->SetFontPreset(EFontPreset::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); + const float SearchExcludeAddrStrMax = 130.0f; + const float SearchIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_MAGNIFYING_GLASS); + const float ExcludeIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_BAN); + const float ExcludeSearchIconMax = maximum(SearchIconWidth, ExcludeIconWidth); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + + CUIRect SearchInfoAndAddr, ServersAndConnect, ServersPlayersOnline, SearchAndInfo, ServerAddr, ConnectButtons; + StatusBox.VSplitRight(135.0f, &SearchInfoAndAddr, &ServersAndConnect); if(SearchInfoAndAddr.w > 350.0f) - SearchInfoAndAddr.VSplitLeft(350.0f, &SearchInfoAndAddr, NULL); - CUIRect SearchAndInfo, ServerAddr, ConnectButtons; + SearchInfoAndAddr.VSplitLeft(350.0f, &SearchInfoAndAddr, nullptr); SearchInfoAndAddr.HSplitTop(40.0f, &SearchAndInfo, &ServerAddr); - ServersAndConnect.HSplitTop(35.0f, &Status3, &ConnectButtons); + ServersAndConnect.HSplitTop(35.0f, &ServersPlayersOnline, &ConnectButtons); ConnectButtons.HSplitTop(5.0f, nullptr, &ConnectButtons); + CUIRect QuickSearch, QuickExclude; - - SearchAndInfo.HSplitTop(20.f, &QuickSearch, &QuickExclude); - QuickSearch.Margin(2.f, &QuickSearch); - QuickExclude.Margin(2.f, &QuickExclude); - - float SearchExcludeAddrStrMax = 130.0f; - - float SearchIconWidth = 0; - float ExcludeIconWidth = 0; - float ExcludeSearchIconMax = 0; + SearchAndInfo.HSplitTop(20.0f, &QuickSearch, &QuickExclude); + QuickSearch.Margin(2.0f, &QuickSearch); + QuickExclude.Margin(2.0f, &QuickExclude); // render quick search { TextRender()->SetFontPreset(EFontPreset::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); - UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 16.0f, TEXTALIGN_ML); - SearchIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f); - ExcludeIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_BAN, -1, -1.0f); - ExcludeSearchIconMax = maximum(SearchIconWidth, ExcludeIconWidth); TextRender()->SetRenderFlags(0); TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); - QuickSearch.VSplitLeft(ExcludeSearchIconMax, 0, &QuickSearch); - QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); + QuickSearch.VSplitLeft(ExcludeSearchIconMax, nullptr, &QuickSearch); + QuickSearch.VSplitLeft(5.0f, nullptr, &QuickSearch); char aBufSearch[64]; str_format(aBufSearch, sizeof(aBufSearch), "%s:", Localize("Search")); UI()->DoLabel(&QuickSearch, aBufSearch, 14.0f, TEXTALIGN_ML); - QuickSearch.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickSearch); - QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); + QuickSearch.VSplitLeft(SearchExcludeAddrStrMax, nullptr, &QuickSearch); + QuickSearch.VSplitLeft(5.0f, nullptr, &QuickSearch); static CLineInput s_FilterInput(g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString)); - if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) + if(!UI()->IsPopupOpen() && Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) { UI()->SetActiveItem(&s_FilterInput); s_FilterInput.SelectAll(); @@ -506,64 +491,72 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) { TextRender()->SetFontPreset(EFontPreset::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); - UI()->DoLabel(&QuickExclude, FONT_ICON_BAN, 16.0f, TEXTALIGN_ML); TextRender()->SetRenderFlags(0); TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); - QuickExclude.VSplitLeft(ExcludeSearchIconMax, 0, &QuickExclude); - QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); + QuickExclude.VSplitLeft(ExcludeSearchIconMax, nullptr, &QuickExclude); + QuickExclude.VSplitLeft(5.0f, nullptr, &QuickExclude); char aBufExclude[64]; str_format(aBufExclude, sizeof(aBufExclude), "%s:", Localize("Exclude")); UI()->DoLabel(&QuickExclude, aBufExclude, 14.0f, TEXTALIGN_ML); - QuickExclude.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickExclude); - QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); + QuickExclude.VSplitLeft(SearchExcludeAddrStrMax, nullptr, &QuickExclude); + QuickExclude.VSplitLeft(5.0f, nullptr, &QuickExclude); static CLineInput s_ExcludeInput(g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString)); - if(Input()->KeyPress(KEY_X) && Input()->ShiftIsPressed() && Input()->ModifierIsPressed()) + if(!UI()->IsPopupOpen() && Input()->KeyPress(KEY_X) && Input()->ShiftIsPressed() && Input()->ModifierIsPressed()) + { UI()->SetActiveItem(&s_ExcludeInput); + s_ExcludeInput.SelectAll(); + } if(UI()->DoClearableEditBox(&s_ExcludeInput, &QuickExclude, 12.0f)) Client()->ServerBrowserUpdate(); } // render status - char aBufSvr[128]; - char aBufPyr[128]; - if(ServerBrowser()->NumServers() != 1) - str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d servers"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers()); - else - str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d server"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers()); - if(NumPlayers != 1) - str_format(aBufPyr, sizeof(aBufPyr), Localize("%d players"), NumPlayers); - else - str_format(aBufPyr, sizeof(aBufPyr), Localize("%d player"), NumPlayers); - - CUIRect SvrsOnline, PlysOnline; - Status3.HSplitTop(20.f, &PlysOnline, &SvrsOnline); - PlysOnline.VSplitRight(TextRender()->TextWidth(12.0f, aBufPyr, -1, -1.0f), 0, &PlysOnline); - UI()->DoLabel(&PlysOnline, aBufPyr, 12.0f, TEXTALIGN_ML); - SvrsOnline.VSplitRight(TextRender()->TextWidth(12.0f, aBufSvr, -1, -1.0f), 0, &SvrsOnline); - UI()->DoLabel(&SvrsOnline, aBufSvr, 12.0f, TEXTALIGN_ML); - - // status box { + CUIRect ServersOnline, PlayersOnline; + ServersPlayersOnline.HSplitMid(&PlayersOnline, &ServersOnline); + + char aBuf[128]; + if(ServerBrowser()->NumServers() != 1) + str_format(aBuf, sizeof(aBuf), Localize("%d of %d servers"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers()); + else + str_format(aBuf, sizeof(aBuf), Localize("%d of %d server"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers()); + UI()->DoLabel(&ServersOnline, aBuf, 12.0f, TEXTALIGN_MR); + + int NumPlayers = 0; + for(int i = 0; i < ServerBrowser()->NumSortedServers(); i++) + NumPlayers += ServerBrowser()->SortedGet(i)->m_NumFilteredPlayers; + + if(NumPlayers != 1) + str_format(aBuf, sizeof(aBuf), Localize("%d players"), NumPlayers); + else + str_format(aBuf, sizeof(aBuf), Localize("%d player"), NumPlayers); + UI()->DoLabel(&PlayersOnline, aBuf, 12.0f, TEXTALIGN_MR); + } + + // address info + { + CUIRect ServerAddrLabel, ServerAddrEditBox; ServerAddr.Margin(2.0f, &ServerAddr); + ServerAddr.VSplitLeft(SearchExcludeAddrStrMax + 5.0f + ExcludeSearchIconMax + 5.0f, &ServerAddrLabel, &ServerAddrEditBox); - // address info - UI()->DoLabel(&ServerAddr, Localize("Server address:"), 14.0f, TEXTALIGN_ML); - ServerAddr.VSplitLeft(SearchExcludeAddrStrMax + 5.0f + ExcludeSearchIconMax + 5.0f, NULL, &ServerAddr); + UI()->DoLabel(&ServerAddrLabel, Localize("Server address:"), 14.0f, TEXTALIGN_ML); static CLineInput s_ServerAddressInput(g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress)); - if(UI()->DoClearableEditBox(&s_ServerAddressInput, &ServerAddr, 12.0f)) + if(UI()->DoClearableEditBox(&s_ServerAddressInput, &ServerAddrEditBox, 12.0f)) m_ServerBrowserShouldRevealSelection = true; + } - // button area + // buttons + { CUIRect ButtonRefresh, ButtonConnect; ConnectButtons.VSplitMid(&ButtonRefresh, &ButtonConnect, 5.0f); // refresh button { char aLabelBuf[32] = {0}; - const auto RefreshLabelFunc = [this, aLabelBuf]() mutable { + const auto &&RefreshLabelFunc = [this, aLabelBuf]() mutable { if(ServerBrowser()->IsRefreshing() || ServerBrowser()->IsGettingServerlist()) str_format(aLabelBuf, sizeof(aLabelBuf), "%s%s", FONT_ICON_ARROW_ROTATE_RIGHT, FONT_ICON_ELLIPSIS); else @@ -576,7 +569,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) Props.m_UseIconFont = true; static CButtonContainer s_RefreshButton; - if(UI()->DoButton_Menu(m_RefreshButton, &s_RefreshButton, RefreshLabelFunc, &ButtonRefresh, Props) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())) + if(UI()->DoButton_Menu(m_RefreshButton, &s_RefreshButton, RefreshLabelFunc, &ButtonRefresh, Props) || (!UI()->IsPopupOpen() && (Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())))) { RefreshBrowserTab(g_Config.m_UiPage); } @@ -584,14 +577,14 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) // connect button { - const auto ConnectLabelFunc = []() { return FONT_ICON_RIGHT_TO_BRACKET; }; + const auto &&ConnectLabelFunc = []() { return FONT_ICON_RIGHT_TO_BRACKET; }; SMenuButtonProperties Props; Props.m_UseIconFont = true; Props.m_Color = ColorRGBA(0.5f, 1.0f, 0.5f, 0.5f); static CButtonContainer s_ConnectButton; - if(UI()->DoButton_Menu(m_ConnectButton, &s_ConnectButton, ConnectLabelFunc, &ButtonConnect, Props) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) + if(UI()->DoButton_Menu(m_ConnectButton, &s_ConnectButton, ConnectLabelFunc, &ButtonConnect, Props) || WasListboxItemActivated || (!UI()->IsPopupOpen() && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))) { Connect(g_Config.m_UiServerAddress); } @@ -617,115 +610,100 @@ void CMenus::PopupConfirmSwitchServer() void CMenus::RenderServerbrowserFilters(CUIRect View) { - CUIRect ServerFilter = View, FilterHeader; - const float FontSize = 12.0f; + const float RowHeight = 18.0f; + const float FontSize = (RowHeight - 4.0f) * CUI::ms_FontmodHeight; // based on DoButton_CheckBox - // server filter - ServerFilter.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFilter); - FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f); + View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_B, 4.0f); + View.Margin(5.0f, &View); - ServerFilter.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f); - UI()->DoLabel(&FilterHeader, Localize("Server filter"), FontSize + 2.0f, TEXTALIGN_MC); - CUIRect Button, Button2; + CUIRect Button, ResetButton; + View.HSplitBottom(RowHeight, &View, &ResetButton); + View.HSplitBottom(3.0f, &View, nullptr); - ServerFilter.Margin(3.0f, &ServerFilter); - ServerFilter.VMargin(5.0f, &ServerFilter); - - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterEmpty, Localize("Has people playing"), g_Config.m_BrFilterEmpty, &Button)) g_Config.m_BrFilterEmpty ^= 1; - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterSpectators, Localize("Count players only"), g_Config.m_BrFilterSpectators, &Button)) g_Config.m_BrFilterSpectators ^= 1; - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterFull, Localize("Server not full"), g_Config.m_BrFilterFull, &Button)) g_Config.m_BrFilterFull ^= 1; - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button)) g_Config.m_BrFilterFriends ^= 1; - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterPw, Localize("No password"), g_Config.m_BrFilterPw, &Button)) g_Config.m_BrFilterPw ^= 1; - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button)) g_Config.m_BrFilterGametypeStrict ^= 1; - ServerFilter.HSplitTop(5.0f, 0, &ServerFilter); - - ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); + View.HSplitTop(3.0f, nullptr, &View); + View.HSplitTop(RowHeight, &Button, &View); UI()->DoLabel(&Button, Localize("Game types:"), FontSize, TEXTALIGN_ML); - Button.VSplitRight(60.0f, 0, &Button); - ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); + Button.VSplitRight(60.0f, nullptr, &Button); static CLineInput s_GametypeInput(g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype)); if(UI()->DoEditBox(&s_GametypeInput, &Button, FontSize)) Client()->ServerBrowserUpdate(); // server address - ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); - ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); + View.HSplitTop(6.0f, nullptr, &View); + View.HSplitTop(RowHeight, &Button, &View); + View.HSplitTop(6.0f, nullptr, &View); UI()->DoLabel(&Button, Localize("Server address:"), FontSize, TEXTALIGN_ML); - Button.VSplitRight(60.0f, 0, &Button); + Button.VSplitRight(60.0f, nullptr, &Button); static CLineInput s_FilterServerAddressInput(g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress)); if(UI()->DoEditBox(&s_FilterServerAddressInput, &Button, FontSize)) Client()->ServerBrowserUpdate(); // player country { - CUIRect Rect; - ServerFilter.HSplitTop(3.0f, nullptr, &ServerFilter); - ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter); - Button.HMargin(3.0f, &Button); - Button.VSplitRight(60.0f, &Button, &Rect); + CUIRect Flag; + View.HSplitTop(RowHeight, &Button, &View); + Button.VSplitRight(60.0f, &Button, &Flag); if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button)) g_Config.m_BrFilterCountry ^= 1; - float OldWidth = Rect.w; - Rect.w = Rect.h * 2; - Rect.x += (OldWidth - Rect.w) / 2.0f; - m_pClient->m_CountryFlags.Render(g_Config.m_BrFilterCountryIndex, ColorRGBA(1.0f, 1.0f, 1.0f, UI()->MouseHovered(&Rect) ? 1.0f : g_Config.m_BrFilterCountry ? 0.9f : 0.5f), Rect.x, Rect.y, Rect.w, Rect.h); + const float OldWidth = Flag.w; + Flag.w = Flag.h * 2.0f; + Flag.x += (OldWidth - Flag.w) / 2.0f; + m_pClient->m_CountryFlags.Render(g_Config.m_BrFilterCountryIndex, ColorRGBA(1.0f, 1.0f, 1.0f, UI()->HotItem() == &g_Config.m_BrFilterCountryIndex ? 1.0f : g_Config.m_BrFilterCountry ? 0.9f : 0.5f), Flag.x, Flag.y, Flag.w, Flag.h); - if(UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, 0, &Rect)) + if(UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, 0, &Flag)) { static SPopupMenuId s_PopupCountryId; static SPopupCountrySelectionContext s_PopupCountryContext; s_PopupCountryContext.m_pMenus = this; s_PopupCountryContext.m_Selection = g_Config.m_BrFilterCountryIndex; s_PopupCountryContext.m_New = true; - UI()->DoPopupMenu(&s_PopupCountryId, Rect.x, Rect.y + Rect.h, 490, 210, &s_PopupCountryContext, PopupCountrySelection); + UI()->DoPopupMenu(&s_PopupCountryId, Flag.x, Flag.y + Flag.h, 490, 210, &s_PopupCountryContext, PopupCountrySelection); } } - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterConnectingPlayers, Localize("Filter connecting players"), g_Config.m_BrFilterConnectingPlayers, &Button)) g_Config.m_BrFilterConnectingPlayers ^= 1; - CUIRect FilterTabs; - ServerFilter.HSplitBottom(23, &ServerFilter, &FilterTabs); - - CUIRect ResetButton; - - ServerFilter.HSplitBottom(ms_ButtonHeight - 5.0f, &ServerFilter, &ResetButton); - // ddnet country filters if(g_Config.m_UiPage == PAGE_DDNET) { - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, Localize("Indicate map finish"), g_Config.m_BrIndicateFinished, &Button)) { g_Config.m_BrIndicateFinished ^= 1; - if(g_Config.m_BrIndicateFinished) ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); } if(g_Config.m_BrIndicateFinished) { - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); + View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrFilterUnfinishedMap, Localize("Unfinished map"), g_Config.m_BrFilterUnfinishedMap, &Button)) g_Config.m_BrFilterUnfinishedMap ^= 1; } @@ -737,245 +715,52 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG) { - int Network = g_Config.m_UiPage == PAGE_DDNET ? IServerBrowser::NETWORK_DDNET : IServerBrowser::NETWORK_KOG; - // add more space - ServerFilter.HSplitTop(5.0f, 0, &ServerFilter); - ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); - ServerFilter.HSplitTop(120.0f, &ServerFilter, 0); + const ColorRGBA ColorActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f); + const ColorRGBA ColorInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f); - ServerFilter.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); + const int Network = g_Config.m_UiPage == PAGE_DDNET ? IServerBrowser::NETWORK_DDNET : IServerBrowser::NETWORK_KOG; - Button.VSplitMid(&Button, &Button2); + CUIRect TabContents, CountriesTab, TypesTab; + View.HSplitTop(6.0f, nullptr, &View); + View.HSplitTop(19.0f, &Button, &View); + View.HSplitTop(minimum(120.0f + CScrollRegion::HEIGHT_MAGIC_FIX, View.h), &TabContents, &View); + Button.VSplitMid(&CountriesTab, &TypesTab); + TabContents.Draw(ms_ColorTabbarInactive, IGraphics::CORNER_B, 4.0f); - static int s_ActivePage = 0; + enum EFilterTab + { + FILTERTAB_COUNTRIES = 0, + FILTERTAB_TYPES, + }; + static EFilterTab s_ActiveTab = FILTERTAB_COUNTRIES; static CButtonContainer s_CountriesButton; - if(DoButton_MenuTab(&s_CountriesButton, Localize("Countries"), s_ActivePage == 0, &Button, IGraphics::CORNER_TL)) + if(DoButton_MenuTab(&s_CountriesButton, Localize("Countries"), s_ActiveTab == FILTERTAB_COUNTRIES, &CountriesTab, IGraphics::CORNER_TL, nullptr, &ColorInactive, &ColorActive, nullptr, 4.0f)) { - s_ActivePage = 0; + s_ActiveTab = FILTERTAB_COUNTRIES; } static CButtonContainer s_TypesButton; - if(DoButton_MenuTab(&s_TypesButton, Localize("Types"), s_ActivePage == 1, &Button2, IGraphics::CORNER_TR)) + if(DoButton_MenuTab(&s_TypesButton, Localize("Types"), s_ActiveTab == FILTERTAB_TYPES, &TypesTab, IGraphics::CORNER_TR, nullptr, &ColorInactive, &ColorActive, nullptr, 4.0f)) { - s_ActivePage = 1; + s_ActiveTab = FILTERTAB_TYPES; } - if(s_ActivePage == 1) + if(s_ActiveTab == FILTERTAB_COUNTRIES) { - char *pFilterExcludeTypes = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeTypes : g_Config.m_BrFilterExcludeTypesKoG; - const int FilterExcludeTypesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeTypes) : sizeof(g_Config.m_BrFilterExcludeTypesKoG); - int MaxTypes = ServerBrowser()->NumTypes(Network); - int NumTypes = ServerBrowser()->NumTypes(Network); - int PerLine = 3; - - ServerFilter.HSplitTop(4.0f, 0, &ServerFilter); - ServerFilter.HSplitBottom(4.0f, &ServerFilter, 0); - - const float TypesWidth = 40.0f; - const float TypesHeight = ServerFilter.h / std::ceil(MaxTypes / (float)PerLine); - - CUIRect TypesRect, Left, Right; - - static std::vector s_vTypeButtons; - s_vTypeButtons.resize(MaxTypes); - - while(NumTypes > 0) - { - ServerFilter.HSplitTop(TypesHeight, &TypesRect, &ServerFilter); - TypesRect.VSplitMid(&Left, &Right); - - for(int i = 0; i < PerLine && NumTypes > 0; i++, NumTypes--) - { - int TypeIndex = MaxTypes - NumTypes; - const char *pName = ServerBrowser()->GetType(Network, TypeIndex); - bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, pName); - - vec2 Pos = vec2(TypesRect.x + TypesRect.w * ((i + 0.5f) / (float)PerLine), TypesRect.y); - - // correct pos - Pos.x -= TypesWidth / 2.0f; - - // create button logic - CUIRect Rect; - - Rect.x = Pos.x; - Rect.y = Pos.y; - Rect.w = TypesWidth; - Rect.h = TypesHeight; - - int Click = UI()->DoButtonLogic(&s_vTypeButtons[TypeIndex], 0, &Rect); - if(Click == 1 || Click == 2) - { - // left/right click to toggle filter - if(pFilterExcludeTypes[0] == '\0') - { - if(Click == 1) - { - // Left click: when all are active, only activate one - for(int j = 0; j < MaxTypes; ++j) - { - if(j != TypeIndex) - ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, FilterExcludeTypesSize, ServerBrowser()->GetType(Network, j)); - } - } - else if(Click == 2) - { - // Right click: when all are active, only deactivate one - ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, FilterExcludeTypesSize, ServerBrowser()->GetType(Network, TypeIndex)); - } - } - else - { - bool AllFilteredExceptUs = true; - for(int j = 0; j < MaxTypes; ++j) - { - if(j != TypeIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, ServerBrowser()->GetType(Network, j))) - { - AllFilteredExceptUs = false; - break; - } - } - // when last one is removed, reset (re-enable all) - if(AllFilteredExceptUs) - { - pFilterExcludeTypes[0] = '\0'; - } - else if(Active) - { - ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, FilterExcludeTypesSize, pName); - } - else - { - ServerBrowser()->DDNetFilterRem(pFilterExcludeTypes, FilterExcludeTypesSize, pName); - } - } - - ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); - } - else if(Click == 3) - { - // middle click to reset (re-enable all) - pFilterExcludeTypes[0] = '\0'; - ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); - } - - TextRender()->TextColor(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == &s_vTypeButtons[TypeIndex] ? 0.1f : 0.0f)); - UI()->DoLabel(&Rect, pName, FontSize, TEXTALIGN_MC); - TextRender()->TextColor(1.0, 1.0, 1.0, 1.0f); - } - } + RenderServerbrowserCountriesFilter(TabContents, Network); } - else + else if(s_ActiveTab == FILTERTAB_TYPES) { - char *pFilterExcludeCountries = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeCountries : g_Config.m_BrFilterExcludeCountriesKoG; - const int FilterExcludeCountriesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeCountries) : sizeof(g_Config.m_BrFilterExcludeCountriesKoG); - ServerFilter.HSplitTop(15.0f, &ServerFilter, &ServerFilter); - - const float FlagWidth = 40.0f; - const float FlagHeight = 20.0f; - - int MaxFlags = ServerBrowser()->NumCountries(Network); - int NumFlags = ServerBrowser()->NumCountries(Network); - int PerLine = MaxFlags > 8 ? 5 : 4; - - CUIRect FlagsRect; - - static std::vector s_vFlagButtons; - s_vFlagButtons.resize(MaxFlags); - - while(NumFlags > 0) - { - ServerFilter.HSplitTop(23.0f, &FlagsRect, &ServerFilter); - - for(int i = 0; i < PerLine && NumFlags > 0; i++, NumFlags--) - { - int CountryIndex = MaxFlags - NumFlags; - const char *pName = ServerBrowser()->GetCountryName(Network, CountryIndex); - bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, pName); - int FlagID = ServerBrowser()->GetCountryFlag(Network, CountryIndex); - - vec2 Pos = vec2(FlagsRect.x + FlagsRect.w * ((i + 0.5f) / (float)PerLine), FlagsRect.y); - - // correct pos - Pos.x -= FlagWidth / 2.0f; - Pos.y -= FlagHeight / 2.0f; - - // create button logic - CUIRect Rect; - - Rect.x = Pos.x; - Rect.y = Pos.y; - Rect.w = FlagWidth; - Rect.h = FlagHeight; - - int Click = UI()->DoButtonLogic(&s_vFlagButtons[CountryIndex], 0, &Rect); - if(Click == 1 || Click == 2) - { - // left/right click to toggle filter - if(pFilterExcludeCountries[0] == '\0') - { - if(Click == 1) - { - // Left click: when all are active, only activate one - for(int j = 0; j < MaxFlags; ++j) - { - if(j != CountryIndex) - ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, FilterExcludeCountriesSize, ServerBrowser()->GetCountryName(Network, j)); - } - } - else if(Click == 2) - { - // Right click: when all are active, only deactivate one - ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, FilterExcludeCountriesSize, ServerBrowser()->GetCountryName(Network, CountryIndex)); - } - } - else - { - bool AllFilteredExceptUs = true; - for(int j = 0; j < MaxFlags; ++j) - { - if(j != CountryIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, ServerBrowser()->GetCountryName(Network, j))) - { - AllFilteredExceptUs = false; - break; - } - } - // when last one is removed, reset (re-enable all) - if(AllFilteredExceptUs) - { - pFilterExcludeCountries[0] = '\0'; - } - else if(Active) - { - ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, FilterExcludeCountriesSize, pName); - } - else - { - ServerBrowser()->DDNetFilterRem(pFilterExcludeCountries, FilterExcludeCountriesSize, pName); - } - } - - ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); - } - else if(Click == 3) - { - // middle click to reset (re-enable all) - pFilterExcludeCountries[0] = '\0'; - ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); - } - - m_pClient->m_CountryFlags.Render(FlagID, ColorRGBA(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == &s_vFlagButtons[CountryIndex] ? 0.1f : 0.0f)), Pos.x, Pos.y, FlagWidth, FlagHeight); - } - } + RenderServerbrowserTypesFilter(TabContents, Network); } } - static CButtonContainer s_ClearButton; - if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &ResetButton)) + static CButtonContainer s_ResetButton; + if(DoButton_Menu(&s_ResetButton, Localize("Reset filter"), 0, &ResetButton)) { - g_Config.m_BrFilterString[0] = 0; - g_Config.m_BrExcludeString[0] = 0; + g_Config.m_BrFilterString[0] = '\0'; + g_Config.m_BrExcludeString[0] = '\0'; g_Config.m_BrFilterFull = 0; g_Config.m_BrFilterEmpty = 0; g_Config.m_BrFilterSpectators = 0; @@ -983,13 +768,13 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) g_Config.m_BrFilterCountry = 0; g_Config.m_BrFilterCountryIndex = -1; g_Config.m_BrFilterPw = 0; - g_Config.m_BrFilterGametype[0] = 0; + g_Config.m_BrFilterGametype[0] = '\0'; g_Config.m_BrFilterGametypeStrict = 0; g_Config.m_BrFilterConnectingPlayers = 1; g_Config.m_BrFilterUnfinishedMap = 0; - g_Config.m_BrFilterServerAddress[0] = 0; - g_Config.m_BrFilterExcludeCountries[0] = 0; - g_Config.m_BrFilterExcludeTypes[0] = 0; + g_Config.m_BrFilterServerAddress[0] = '\0'; + g_Config.m_BrFilterExcludeCountries[0] = '\0'; + g_Config.m_BrFilterExcludeTypes[0] = '\0'; if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG) ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); else @@ -997,6 +782,157 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) } } +void CMenus::RenderServerbrowserDDNetFilter(CUIRect View, + char *pFilterExclude, int FilterExcludeSize, + float ItemHeight, int MaxItems, int ItemsPerRow, + CScrollRegion &ScrollRegion, std::vector &vItemIds, + const std::function &GetItemName, + const std::function &RenderItem) +{ + vItemIds.resize(MaxItems); + + vec2 ScrollOffset(0.0f, 0.0f); + CScrollRegionParams ScrollParams; + ScrollParams.m_ScrollbarWidth = 10.0f; + ScrollParams.m_ScrollbarMargin = 3.0f; + ScrollParams.m_ScrollUnit = 2.0f * ItemHeight; + ScrollRegion.Begin(&View, &ScrollOffset, &ScrollParams); + View.y += ScrollOffset.y; + + CUIRect Row; + int ColumnIndex = 0; + for(int ItemIndex = 0; ItemIndex < MaxItems; ++ItemIndex) + { + CUIRect Item; + if(ColumnIndex == 0) + View.HSplitTop(ItemHeight, &Row, &View); + Row.VSplitLeft(View.w / ItemsPerRow, &Item, &Row); + ColumnIndex = (ColumnIndex + 1) % ItemsPerRow; + if(!ScrollRegion.AddRect(Item)) + continue; + + const void *pItemId = &vItemIds[ItemIndex]; + const char *pName = GetItemName(ItemIndex); + const bool Active = !ServerBrowser()->DDNetFiltered(pFilterExclude, pName); + + const int Click = UI()->DoButtonLogic(pItemId, 0, &Item); + if(Click == 1 || Click == 2) + { + // left/right click to toggle filter + if(pFilterExclude[0] == '\0') + { + if(Click == 1) + { + // Left click: when all are active, only activate one + for(int j = 0; j < MaxItems; ++j) + { + if(j != ItemIndex) + ServerBrowser()->DDNetFilterAdd(pFilterExclude, FilterExcludeSize, GetItemName(j)); + } + } + else if(Click == 2) + { + // Right click: when all are active, only deactivate one + ServerBrowser()->DDNetFilterAdd(pFilterExclude, FilterExcludeSize, GetItemName(ItemIndex)); + } + } + else + { + bool AllFilteredExceptUs = true; + for(int j = 0; j < MaxItems; ++j) + { + if(j != ItemIndex && !ServerBrowser()->DDNetFiltered(pFilterExclude, GetItemName(j))) + { + AllFilteredExceptUs = false; + break; + } + } + // when last one is removed, reset (re-enable all) + if(AllFilteredExceptUs) + { + pFilterExclude[0] = '\0'; + } + else if(Active) + { + ServerBrowser()->DDNetFilterAdd(pFilterExclude, FilterExcludeSize, pName); + } + else + { + ServerBrowser()->DDNetFilterRem(pFilterExclude, FilterExcludeSize, pName); + } + } + + ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); + } + else if(Click == 3) + { + // middle click to reset (re-enable all) + pFilterExclude[0] = '\0'; + ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType()); + } + + if(UI()->HotItem() == pItemId && !ScrollRegion.Animating()) + Item.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f), IGraphics::CORNER_ALL, 2.0f); + RenderItem(ItemIndex, Item, pItemId, Active); + } + + ScrollRegion.End(); +} + +void CMenus::RenderServerbrowserCountriesFilter(CUIRect View, int Network) +{ + char *pFilterExcludeCountries = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeCountries : g_Config.m_BrFilterExcludeCountriesKoG; + const int FilterExcludeCountriesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeCountries) : sizeof(g_Config.m_BrFilterExcludeCountriesKoG); + const int MaxEntries = ServerBrowser()->NumCountries(Network); + const int EntriesPerRow = MaxEntries > 8 ? 5 : 4; + + static CScrollRegion s_ScrollRegion; + static std::vector s_vItemIds; + + const float ItemHeight = 20.0f; + const float Spacing = 2.0f; + + const auto &&GetItemName = [&](int ItemIndex) { + return ServerBrowser()->GetCountryName(Network, ItemIndex); + }; + const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) { + Item.Margin(Spacing, &Item); + const float OldWidth = Item.w; + Item.w = Item.h * 2.0f; + Item.x += (OldWidth - Item.w) / 2.0f; + const int FlagID = ServerBrowser()->GetCountryFlag(Network, ItemIndex); + m_pClient->m_CountryFlags.Render(FlagID, ColorRGBA(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == pItemId ? 0.1f : 0.0f)), Item.x, Item.y, Item.w, Item.h); + }; + + RenderServerbrowserDDNetFilter(View, pFilterExcludeCountries, FilterExcludeCountriesSize, ItemHeight + 2.0f * Spacing, MaxEntries, EntriesPerRow, s_ScrollRegion, s_vItemIds, GetItemName, RenderItem); +} + +void CMenus::RenderServerbrowserTypesFilter(CUIRect View, int Network) +{ + char *pFilterExcludeTypes = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeTypes : g_Config.m_BrFilterExcludeTypesKoG; + const int FilterExcludeTypesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeTypes) : sizeof(g_Config.m_BrFilterExcludeTypesKoG); + const int MaxEntries = ServerBrowser()->NumTypes(Network); + const int EntriesPerRow = 3; + + static CScrollRegion s_ScrollRegion; + static std::vector s_vItemIds; + + const float ItemHeight = 13.0f; + const float Spacing = 2.0f; + + const auto &&GetItemName = [&](int ItemIndex) { + return ServerBrowser()->GetType(Network, ItemIndex); + }; + const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) { + Item.Margin(Spacing, &Item); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == pItemId ? 0.1f : 0.0f)); + UI()->DoLabel(&Item, GetItemName(ItemIndex), Item.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + }; + + RenderServerbrowserDDNetFilter(View, pFilterExcludeTypes, FilterExcludeTypesSize, ItemHeight + 2.0f * Spacing, MaxEntries, EntriesPerRow, s_ScrollRegion, s_vItemIds, GetItemName, RenderItem); +} + CUI::EPopupMenuFunctionResult CMenus::PopupCountrySelection(void *pContext, CUIRect View, bool Active) { SPopupCountrySelectionContext *pPopupContext = static_cast(pContext); @@ -1045,33 +981,21 @@ CUI::EPopupMenuFunctionResult CMenus::PopupCountrySelection(void *pContext, CUIR return CUI::POPUP_KEEP_OPEN; } -void CMenus::RenderServerbrowserServerDetail(CUIRect View) +void CMenus::RenderServerbrowserInfo(CUIRect View) { - CUIRect ServerDetails = View; - CUIRect ServerScoreBoard, ServerHeader; - const CServerInfo *pSelectedServer = ServerBrowser()->SortedGet(m_SelectedIndex); - // split off a piece to use for scoreboard - ServerDetails.HSplitTop(110.0f, &ServerDetails, &ServerScoreBoard); + const float RowHeight = 18.0f; + const float FontSize = (RowHeight - 4.0f) * CUI::ms_FontmodHeight; // based on DoButton_CheckBox - // server details - const float FontSize = 12.0f; - ServerDetails.HSplitTop(ms_ListheaderHeight, &ServerHeader, &ServerDetails); - ServerHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f); - ServerDetails.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f); - UI()->DoLabel(&ServerHeader, Localize("Server details"), FontSize + 2.0f, TEXTALIGN_MC); + CUIRect ServerDetails, Scoreboard; + View.HSplitTop(4.0f * 15.0f + RowHeight + 2.0f * 5.0f + 2.0f * 2.0f, &ServerDetails, &Scoreboard); + ServerDetails.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_B, 4.0f); if(pSelectedServer) { ServerDetails.Margin(5.0f, &ServerDetails); - CUIRect Row; - static CLocConstString s_aLabels[] = { - "Version", // Localize - these strings are localized within CLocConstString - "Game type", - "Ping"}; - // copy info button { CUIRect Button; @@ -1085,17 +1009,14 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) } } - ServerDetails.HSplitBottom(2.5f, &ServerDetails, nullptr); - // favorite checkbox { - CUIRect Button; - ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button); - CUIRect ButtonAddFav; - CUIRect ButtonLeakIp; - Button.VSplitMid(&ButtonAddFav, &ButtonLeakIp); + CUIRect ButtonAddFav, ButtonLeakIp; + ServerDetails.HSplitBottom(2.0f, &ServerDetails, nullptr); + ServerDetails.HSplitBottom(RowHeight, &ServerDetails, &ButtonAddFav); + ServerDetails.HSplitBottom(2.0f, &ServerDetails, nullptr); + ButtonAddFav.VSplitMid(&ButtonAddFav, &ButtonLeakIp); static int s_AddFavButton = 0; - static int s_LeakIpButton = 0; if(DoButton_CheckBox_Tristate(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &ButtonAddFav)) { if(pSelectedServer->m_Favorite != TRISTATE::NONE) @@ -1114,6 +1035,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) } if(pSelectedServer->m_Favorite != TRISTATE::NONE) { + static int s_LeakIpButton = 0; if(DoButton_CheckBox_Tristate(&s_LeakIpButton, Localize("Leak IP"), pSelectedServer->m_FavoriteAllowPing, &ButtonLeakIp)) { Favorites()->AllowPing(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses, pSelectedServer->m_FavoriteAllowPing == TRISTATE::NONE); @@ -1122,222 +1044,169 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View) } } - CUIRect LeftColumn, RightColumn; + CUIRect LeftColumn, RightColumn, Row; ServerDetails.VSplitLeft(80.0f, &LeftColumn, &RightColumn); - for(auto &Label : s_aLabels) - { - LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); - UI()->DoLabel(&Row, Label, FontSize, TEXTALIGN_ML); - } + LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); + UI()->DoLabel(&Row, Localize("Version"), FontSize, TEXTALIGN_ML); RightColumn.HSplitTop(15.0f, &Row, &RightColumn); UI()->DoLabel(&Row, pSelectedServer->m_aVersion, FontSize, TEXTALIGN_ML); + LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); + UI()->DoLabel(&Row, Localize("Game type"), FontSize, TEXTALIGN_ML); + RightColumn.HSplitTop(15.0f, &Row, &RightColumn); UI()->DoLabel(&Row, pSelectedServer->m_aGameType, FontSize, TEXTALIGN_ML); + LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); + UI()->DoLabel(&Row, Localize("Ping"), FontSize, TEXTALIGN_ML); + char aTemp[16]; - FormatServerbrowserPing(aTemp, sizeof(aTemp), pSelectedServer); + FormatServerbrowserPing(aTemp, pSelectedServer); RightColumn.HSplitTop(15.0f, &Row, &RightColumn); UI()->DoLabel(&Row, aTemp, FontSize, TEXTALIGN_ML); + + RenderServerbrowserInfoScoreboard(Scoreboard, pSelectedServer); } else { UI()->DoLabel(&ServerDetails, Localize("No server selected"), FontSize, TEXTALIGN_MC); } - - // server scoreboard - ServerScoreBoard.HSplitBottom(23.0f, &ServerScoreBoard, 0x0); - - CTextCursor Cursor; - if(pSelectedServer) - { - static CListBox s_ListBox; - s_ListBox.DoAutoSpacing(1.0f); - s_ListBox.DoStart(25.0f, pSelectedServer->m_NumReceivedClients, 1, 3, -1, &ServerScoreBoard); - - int ClientScoreKind = pSelectedServer->m_ClientScoreKind; - - for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++) - { - const CServerInfo::CClient &CurrentClient = pSelectedServer->m_aClients[i]; - const CListboxItem Item = s_ListBox.DoNextItem(&CurrentClient); - - if(!Item.m_Visible) - continue; - - const bool HasTeeToRender = pSelectedServer->m_aClients[i].m_aSkin[0] != '\0'; - - CUIRect Skin, Name, Clan, Score, Flag; - Name = Item.m_Rect; - - ColorRGBA Color = CurrentClient.m_FriendState == IFriends::FRIEND_NO ? - ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) : - ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f); - Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f); - Name.VSplitLeft(1.0f, nullptr, &Name); - Name.VSplitLeft(34.0f, &Score, &Name); - Name.VSplitLeft(18.0f, &Skin, &Name); - Name.VSplitRight(20.0, &Name, &Flag); - Flag.HMargin(4.0f, &Flag); - Name.HSplitTop(12.0f, &Name, &Clan); - - // score - char aTemp[16]; - - if(!CurrentClient.m_Player) - { - str_copy(aTemp, "SPEC"); - } - else if(ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_POINTS) - { - str_from_int(CurrentClient.m_Score, aTemp); - } - else - { - std::optional Time = {}; - - if(ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT) - { - int TempTime = absolute(CurrentClient.m_Score); - if(TempTime != 0 && TempTime != 9999) - Time = TempTime; - } - else - { - // CServerInfo::CLIENT_SCORE_KIND_POINTS - if(CurrentClient.m_Score >= 0) - Time = CurrentClient.m_Score; - } - - if(Time.has_value()) - { - str_time((int64_t)Time.value() * 100, TIME_HOURS, aTemp, sizeof(aTemp)); - } - else - { - aTemp[0] = 0; - } - } - - UI()->DoLabel(&Score, aTemp, FontSize, TEXTALIGN_ML); - - // render tee if available - if(HasTeeToRender) - { - CTeeRenderInfo TeeInfo; - const CSkin *pSkin = m_pClient->m_Skins.Find(CurrentClient.m_aSkin); - TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; - TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin; - TeeInfo.m_SkinMetrics = pSkin->m_Metrics; - TeeInfo.m_CustomColoredSkin = CurrentClient.m_CustomSkinColors; - if(CurrentClient.m_CustomSkinColors) - { - TeeInfo.m_ColorBody = color_cast(ColorHSLA(CurrentClient.m_CustomSkinColorBody).UnclampLighting()); - TeeInfo.m_ColorFeet = color_cast(ColorHSLA(CurrentClient.m_CustomSkinColorFeet).UnclampLighting()); - } - else - { - TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f); - TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f); - } - TeeInfo.m_Size = minimum(Skin.w, Skin.h); - - const CAnimState *pIdleState = CAnimState::GetIdle(); - vec2 OffsetToMid; - RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); - vec2 TeeRenderPos(Skin.x + TeeInfo.m_Size / 2, Skin.y + Skin.h / 2 + OffsetToMid.y); - - RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); - } - - // name - TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Name.w; - const char *pName = CurrentClient.m_aName; - bool Printed = false; - if(g_Config.m_BrFilterString[0]) - Printed = PrintHighlighted(pName, [this, &Cursor, pName](const char *pFilteredStr, const int FilterLen) { - TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1); - }); - if(!Printed) - TextRender()->TextEx(&Cursor, pName, -1); - - // clan - TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); - Cursor.m_LineWidth = Clan.w; - const char *pClan = CurrentClient.m_aClan; - Printed = false; - if(g_Config.m_BrFilterString[0]) - Printed = PrintHighlighted(pClan, [this, &Cursor, pClan](const char *pFilteredStr, const int FilterLen) { - TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredStr - pClan)); - TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen); - TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1); - }); - if(!Printed) - TextRender()->TextEx(&Cursor, pClan, -1); - - // flag - m_pClient->m_CountryFlags.Render(CurrentClient.m_Country, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), - Flag.x, Flag.y + ((Flag.h - Flag.w / 2) / 2), Flag.w, Flag.w / 2); - } - - const int NewSelected = s_ListBox.DoEnd(); - if(s_ListBox.WasItemSelected()) - { - const CServerInfo::CClient &SelectedClient = pSelectedServer->m_aClients[NewSelected]; - if(SelectedClient.m_FriendState == IFriends::FRIEND_PLAYER) - m_pClient->Friends()->RemoveFriend(SelectedClient.m_aName, SelectedClient.m_aClan); - else - m_pClient->Friends()->AddFriend(SelectedClient.m_aName, SelectedClient.m_aClan); - FriendlistOnUpdate(); - Client()->ServerBrowserUpdate(); - } - } } -template -bool CMenus::PrintHighlighted(const char *pName, F &&PrintFn) +void CMenus::RenderServerbrowserInfoScoreboard(CUIRect View, const CServerInfo *pSelectedServer) { - const char *pStr = g_Config.m_BrFilterString; - char aFilterStr[sizeof(g_Config.m_BrFilterString)]; - while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr)))) + const float FontSize = 10.0f; + + static CListBox s_ListBox; + View.VSplitLeft(5.0f, nullptr, &View); + if(!s_ListBox.ScrollbarShown()) + View.VSplitRight(5.0f, &View, nullptr); + s_ListBox.DoAutoSpacing(1.0f); + s_ListBox.SetScrollbarWidth(16.0f); + s_ListBox.SetScrollbarMargin(5.0f); + s_ListBox.DoStart(25.0f, pSelectedServer->m_NumReceivedClients, 1, 3, -1, &View); + + for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++) { - // highlight the parts that matches - const char *pFilteredStr; - int FilterLen = str_length(aFilterStr); - if(aFilterStr[0] == '"' && aFilterStr[FilterLen - 1] == '"') + const CServerInfo::CClient &CurrentClient = pSelectedServer->m_aClients[i]; + const CListboxItem Item = s_ListBox.DoNextItem(&CurrentClient); + if(!Item.m_Visible) + continue; + + CUIRect Skin, Name, Clan, Score, Flag; + Name = Item.m_Rect; + + ColorRGBA Color = CurrentClient.m_FriendState == IFriends::FRIEND_NO ? + ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) : + ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f); + Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f); + Name.VSplitLeft(1.0f, nullptr, &Name); + Name.VSplitLeft(34.0f, &Score, &Name); + Name.VSplitLeft(18.0f, &Skin, &Name); + Name.VSplitRight(26.0f, &Name, &Flag); + Flag.HMargin(6.0f, &Flag); + Name.HSplitTop(12.0f, &Name, &Clan); + + // score + char aTemp[16]; + if(!CurrentClient.m_Player) { - aFilterStr[FilterLen - 1] = '\0'; - pFilteredStr = str_comp(pName, &aFilterStr[1]) == 0 ? pName : nullptr; - FilterLen -= 2; + str_copy(aTemp, "SPEC"); + } + else if(pSelectedServer->m_ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_POINTS) + { + str_from_int(CurrentClient.m_Score, aTemp); } else { - const char *pFilteredStrEnd; - pFilteredStr = str_utf8_find_nocase(pName, aFilterStr, &pFilteredStrEnd); - if(pFilteredStr != nullptr && pFilteredStrEnd != nullptr) - FilterLen = pFilteredStrEnd - pFilteredStr; - } - if(pFilteredStr) - { - PrintFn(pFilteredStr, FilterLen); - return true; - } - } - return false; -} + std::optional Time = {}; -void CMenus::FriendlistOnUpdate() -{ - // TODO: friends are currently updated every frame; optimize and only update friends when necessary + if(pSelectedServer->m_ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT) + { + const int TempTime = absolute(CurrentClient.m_Score); + if(TempTime != 0 && TempTime != 9999) + Time = TempTime; + } + else + { + // CServerInfo::CLIENT_SCORE_KIND_POINTS + if(CurrentClient.m_Score >= 0) + Time = CurrentClient.m_Score; + } + + if(Time.has_value()) + { + str_time((int64_t)Time.value() * 100, TIME_HOURS, aTemp, sizeof(aTemp)); + } + else + { + aTemp[0] = '\0'; + } + } + + UI()->DoLabel(&Score, aTemp, FontSize, TEXTALIGN_ML); + + // render tee if available + if(CurrentClient.m_aSkin[0] != '\0') + { + const CTeeRenderInfo TeeInfo = GetTeeRenderInfo(vec2(Skin.w, Skin.h), CurrentClient.m_aSkin, CurrentClient.m_CustomSkinColors, CurrentClient.m_CustomSkinColorBody, CurrentClient.m_CustomSkinColorFeet); + const CAnimState *pIdleState = CAnimState::GetIdle(); + vec2 OffsetToMid; + RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); + const vec2 TeeRenderPos = vec2(Skin.x + TeeInfo.m_Size / 2.0f, Skin.y + Skin.h / 2.0f + OffsetToMid.y); + RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); + } + + // name + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 1.0f)) / 2.0f, FontSize - 1.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); + Cursor.m_LineWidth = Name.w; + const char *pName = CurrentClient.m_aName; + bool Printed = false; + if(g_Config.m_BrFilterString[0]) + Printed = PrintHighlighted(pName, [&](const char *pFilteredStr, const int FilterLen) { + TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName)); + TextRender()->TextColor(gs_HighlightedTextColor); + TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1); + }); + if(!Printed) + TextRender()->TextEx(&Cursor, pName, -1); + + // clan + TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2.0f)) / 2.0f, FontSize - 2.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); + Cursor.m_LineWidth = Clan.w; + const char *pClan = CurrentClient.m_aClan; + Printed = false; + if(g_Config.m_BrFilterString[0]) + Printed = PrintHighlighted(pClan, [&](const char *pFilteredStr, const int FilterLen) { + TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredStr - pClan)); + TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); + TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1); + }); + if(!Printed) + TextRender()->TextEx(&Cursor, pClan, -1); + + // flag + m_pClient->m_CountryFlags.Render(CurrentClient.m_Country, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), Flag.x, Flag.y, Flag.w, Flag.h); + } + + const int NewSelected = s_ListBox.DoEnd(); + if(s_ListBox.WasItemSelected()) + { + const CServerInfo::CClient &SelectedClient = pSelectedServer->m_aClients[NewSelected]; + if(SelectedClient.m_FriendState == IFriends::FRIEND_PLAYER) + m_pClient->Friends()->RemoveFriend(SelectedClient.m_aName, SelectedClient.m_aClan); + else + m_pClient->Friends()->AddFriend(SelectedClient.m_aName, SelectedClient.m_aClan); + FriendlistOnUpdate(); + Client()->ServerBrowserUpdate(); + } } void CMenus::RenderServerbrowserFriends(CUIRect View) @@ -1348,18 +1217,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) const ColorRGBA OfflineClanColor = ColorRGBA(0.7f, 0.45f, 0.75f, 1.0f); const float SpacingH = 2.0f; - char aBuf[256]; - CUIRect ServerFriends, FilterHeader, List; - - // header - View.HSplitTop(ms_ListheaderHeight, &FilterHeader, &View); - FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f); - View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f); - UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_MC); - - View.HSplitBottom(84.0f, &List, &ServerFriends); - List.HSplitTop(3.0f, nullptr, &List); - List.VSplitLeft(3.0f, nullptr, &List); + CUIRect List, ServerFriends; + View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_NONE, 0.0f); + View.HSplitBottom(70.0f, &List, &ServerFriends); + List.HSplitTop(5.0f, nullptr, &List); + List.VSplitLeft(5.0f, nullptr, &List); // calculate friends // TODO: optimize this @@ -1397,27 +1259,28 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) // friends list static CScrollRegion s_ScrollRegion; - if(!s_ScrollRegion.IsScrollbarShown()) - List.VSplitRight(3.0f, &List, nullptr); + if(!s_ScrollRegion.ScrollbarShown()) + List.VSplitRight(5.0f, &List, nullptr); vec2 ScrollOffset(0.0f, 0.0f); CScrollRegionParams ScrollParams; - ScrollParams.m_ScrollbarWidth = 14.0f; - ScrollParams.m_ScrollbarMargin = 4.0f; + ScrollParams.m_ScrollbarWidth = 16.0f; + ScrollParams.m_ScrollbarMargin = 5.0f; ScrollParams.m_ScrollUnit = 80.0f; s_ScrollRegion.Begin(&List, &ScrollOffset, &ScrollParams); List.y += ScrollOffset.y; + char aBuf[256]; for(size_t FriendType = 0; FriendType < NUM_FRIEND_TYPES; ++FriendType) { // header CUIRect Header, GroupIcon, GroupLabel; List.HSplitTop(ms_ListheaderHeight, &Header, &List); s_ScrollRegion.AddRect(Header); - Header.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, UI()->MouseHovered(&Header) ? 0.4f : 0.25f), IGraphics::CORNER_ALL, 5.0f); + Header.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, UI()->HotItem() == &s_aListExtended[FriendType] ? 0.4f : 0.25f), IGraphics::CORNER_ALL, 5.0f); Header.VSplitLeft(Header.h, &GroupIcon, &GroupLabel); GroupIcon.Margin(2.0f, &GroupIcon); TextRender()->SetFontPreset(EFontPreset::ICON_FONT); - TextRender()->TextColor(UI()->MouseHovered(&Header) ? TextRender()->DefaultTextColor() : ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f)); + TextRender()->TextColor(UI()->HotItem() == &s_aListExtended[FriendType] ? TextRender()->DefaultTextColor() : ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f)); UI()->DoLabel(&GroupIcon, s_aListExtended[FriendType] ? FONT_ICON_SQUARE_MINUS : FONT_ICON_SQUARE_PLUS, GroupIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); @@ -1458,14 +1321,13 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) const auto &Friend = m_avFriends[FriendType][FriendIndex]; List.HSplitTop(11.0f + 10.0f + 2 * 2.0f + 1.0f + (Friend.ServerInfo() == nullptr ? 0.0f : 10.0f), &Rect, &List); s_ScrollRegion.AddRect(Rect); - if(s_ScrollRegion.IsRectClipped(Rect)) + if(s_ScrollRegion.RectClipped(Rect)) continue; - const bool Inside = UI()->MouseHovered(&Rect); - bool ButtonResult = false; + const bool Inside = UI()->HotItem() == Friend.ListItemId() || UI()->HotItem() == Friend.RemoveButtonId(); + bool ButtonResult = UI()->DoButtonLogic(Friend.ListItemId(), 0, &Rect); if(Friend.ServerInfo()) { - ButtonResult = UI()->DoButtonLogic(Friend.ListItemId(), 0, &Rect); GameClient()->m_Tooltips.DoToolTip(Friend.ListItemId(), &Rect, Localize("Click to select server. Double click to join your friend.")); } const bool OfflineClan = Friend.FriendState() == IFriends::FRIEND_CLAN && FriendType == FRIEND_OFF; @@ -1489,29 +1351,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) Rect.VSplitLeft(Rect.h, &Skin, &Rect); Rect.VSplitLeft(2.0f, nullptr, &Rect); - CTeeRenderInfo TeeInfo; - const CSkin *pSkin = m_pClient->m_Skins.Find(Friend.Skin()); - TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; - TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin; - TeeInfo.m_SkinMetrics = pSkin->m_Metrics; - TeeInfo.m_CustomColoredSkin = Friend.CustomSkinColors(); - if(Friend.CustomSkinColors()) - { - TeeInfo.m_ColorBody = color_cast(ColorHSLA(Friend.CustomSkinColorBody()).UnclampLighting()); - TeeInfo.m_ColorFeet = color_cast(ColorHSLA(Friend.CustomSkinColorFeet()).UnclampLighting()); - } - else - { - TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f); - TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f); - } - TeeInfo.m_Size = minimum(Skin.w, Skin.h); - + const CTeeRenderInfo TeeInfo = GetTeeRenderInfo(vec2(Skin.w, Skin.h), Friend.Skin(), Friend.CustomSkinColors(), Friend.CustomSkinColorBody(), Friend.CustomSkinColorFeet()); const CAnimState *pIdleState = CAnimState::GetIdle(); vec2 OffsetToMid; RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid); - vec2 TeeRenderPos(Skin.x + Skin.w / 2.0f, Skin.y + Skin.h * 0.55f + OffsetToMid.y); - + const vec2 TeeRenderPos = vec2(Skin.x + Skin.w / 2.0f, Skin.y + Skin.h * 0.55f + OffsetToMid.y); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); } Rect.HSplitTop(11.0f, &NameLabel, &ClanLabel); @@ -1551,7 +1395,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) // server info text char aLatency[16]; - FormatServerbrowserPing(aLatency, sizeof(aLatency), Friend.ServerInfo()); + FormatServerbrowserPing(aLatency, Friend.ServerInfo()); if(aLatency[0] != '\0') str_format(aBuf, sizeof(aBuf), "%s | %s | %s", Friend.ServerInfo()->m_aMap, Friend.ServerInfo()->m_aGameType, aLatency); else @@ -1560,17 +1404,22 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) } // remove button - TextRender()->TextColor(UI()->MouseHovered(&RemoveButton) ? TextRender()->DefaultTextColor() : ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f)); - 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_OVERSIZE); - UI()->DoLabel(&RemoveButton, "×", RemoveButton.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); - TextRender()->SetRenderFlags(0); - TextRender()->TextColor(TextRender()->DefaultTextColor()); - if(UI()->DoButtonLogic(Friend.RemoveButtonId(), 0, &RemoveButton)) + if(Inside) { - m_pRemoveFriend = &Friend; - ButtonResult = false; + TextRender()->TextColor(UI()->HotItem() == Friend.RemoveButtonId() ? TextRender()->DefaultTextColor() : ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f)); + TextRender()->SetFontPreset(EFontPreset::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_OVERSIZE); + UI()->DoLabel(&RemoveButton, FONT_ICON_TRASH, RemoveButton.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + if(UI()->DoButtonLogic(Friend.RemoveButtonId(), 0, &RemoveButton)) + { + m_pRemoveFriend = &Friend; + ButtonResult = false; + } + GameClient()->m_Tooltips.DoToolTip(Friend.RemoveButtonId(), &RemoveButton, Friend.FriendState() == IFriends::FRIEND_PLAYER ? Localize("Click to remove this player from your friends list.") : Localize("Click to remove this clan from your friends list.")); } - GameClient()->m_Tooltips.DoToolTip(Friend.RemoveButtonId(), &RemoveButton, Friend.FriendState() == IFriends::FRIEND_PLAYER ? Localize("Click to remove this player from your friends list.") : Localize("Click to remove this clan from your friends list.")); // handle click and double click on item if(ButtonResult && Friend.ServerInfo()) @@ -1615,7 +1464,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS) { CUIRect Button; - ServerFriends.Margin(3.0f, &ServerFriends); + ServerFriends.Margin(5.0f, &ServerFriends); ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends); str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); @@ -1646,6 +1495,11 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) } } +void CMenus::FriendlistOnUpdate() +{ + // TODO: friends are currently updated every frame; optimize and only update friends when necessary +} + void CMenus::PopupConfirmRemoveFriend() { m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->FriendState() == IFriends::FRIEND_PLAYER ? m_pRemoveFriend->Name() : "", m_pRemoveFriend->Clan()); @@ -1654,84 +1508,157 @@ void CMenus::PopupConfirmRemoveFriend() m_pRemoveFriend = nullptr; } +enum +{ + UI_TOOLBOX_PAGE_FILTERS = 0, + UI_TOOLBOX_PAGE_INFO, + UI_TOOLBOX_PAGE_FRIENDS, + NUM_UI_TOOLBOX_PAGES, +}; + +void CMenus::RenderServerbrowserTabBar(CUIRect TabBar) +{ + CUIRect FilterTabButton, InfoTabButton, FriendsTabButton; + TabBar.VSplitLeft(TabBar.w / 3.0f, &FilterTabButton, &TabBar); + TabBar.VSplitMid(&InfoTabButton, &FriendsTabButton); + + const ColorRGBA ColorActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f); + const ColorRGBA ColorInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f); + + if(!UI()->IsPopupOpen() && UI()->ConsumeHotkey(CUI::HOTKEY_TAB)) + { + const int Direction = Input()->ShiftIsPressed() ? -1 : 1; + g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + NUM_UI_TOOLBOX_PAGES + Direction) % NUM_UI_TOOLBOX_PAGES; + } + + TextRender()->SetFontPreset(EFontPreset::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); + + static CButtonContainer s_FilterTabButton; + if(DoButton_MenuTab(&s_FilterTabButton, FONT_ICON_LIST_UL, g_Config.m_UiToolboxPage == UI_TOOLBOX_PAGE_FILTERS, &FilterTabButton, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_BROWSER_FILTER], &ColorInactive, &ColorActive)) + g_Config.m_UiToolboxPage = UI_TOOLBOX_PAGE_FILTERS; + + static CButtonContainer s_InfoTabButton; + if(DoButton_MenuTab(&s_InfoTabButton, FONT_ICON_INFO, g_Config.m_UiToolboxPage == UI_TOOLBOX_PAGE_INFO, &InfoTabButton, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_BROWSER_INFO], &ColorInactive, &ColorActive)) + g_Config.m_UiToolboxPage = UI_TOOLBOX_PAGE_INFO; + + static CButtonContainer s_FriendsTabButton; + if(DoButton_MenuTab(&s_FriendsTabButton, FONT_ICON_HEART, g_Config.m_UiToolboxPage == UI_TOOLBOX_PAGE_FRIENDS, &FriendsTabButton, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_BROWSER_FRIENDS], &ColorInactive, &ColorActive)) + g_Config.m_UiToolboxPage = UI_TOOLBOX_PAGE_FRIENDS; + + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); +} + +void CMenus::RenderServerbrowserToolBox(CUIRect ToolBox) +{ + ToolBox.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_B, 4.0f); + + switch(g_Config.m_UiToolboxPage) + { + case UI_TOOLBOX_PAGE_FILTERS: + RenderServerbrowserFilters(ToolBox); + return; + case UI_TOOLBOX_PAGE_INFO: + RenderServerbrowserInfo(ToolBox); + return; + case UI_TOOLBOX_PAGE_FRIENDS: + RenderServerbrowserFriends(ToolBox); + return; + default: + dbg_assert(false, "ui_toolbox_page invalid"); + return; + } +} + void CMenus::RenderServerbrowser(CUIRect MainView) { /* - +-----------------+ +-------+ - | | | | - | | | tool | - | server list | | box | - | | | | - | | | | - +-----------------+ | | - status box tab +-------+ + +-----------------+ +--tabs--+ + | | | | + | | | | + | server list | | tool | + | | | box | + | | | | + +-----------------+ | | + status box +--------+ */ - CUIRect ServerList, ToolBox; - - // background + CUIRect ServerList, StatusBox, ToolBox, TabBar; MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); MainView.Margin(10.0f, &MainView); - - // create server list, status box, tab bar and tool box area MainView.VSplitRight(205.0f, &ServerList, &ToolBox); - ServerList.VSplitRight(5.0f, &ServerList, 0); + ServerList.VSplitRight(5.0f, &ServerList, nullptr); + ToolBox.HSplitTop(24.0f, &TabBar, &ToolBox); + ServerList.HSplitBottom(65.0f, &ServerList, &StatusBox); - // server list + bool WasListboxItemActivated; + RenderServerbrowserServerList(ServerList, WasListboxItemActivated); + RenderServerbrowserStatusBox(StatusBox, WasListboxItemActivated); + + RenderServerbrowserTabBar(TabBar); + RenderServerbrowserToolBox(ToolBox); +} + +template +bool CMenus::PrintHighlighted(const char *pName, F &&PrintFn) +{ + const char *pStr = g_Config.m_BrFilterString; + char aFilterStr[sizeof(g_Config.m_BrFilterString)]; + while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr)))) { - RenderServerbrowserServerList(ServerList); + // highlight the parts that matches + const char *pFilteredStr; + int FilterLen = str_length(aFilterStr); + if(aFilterStr[0] == '"' && aFilterStr[FilterLen - 1] == '"') + { + aFilterStr[FilterLen - 1] = '\0'; + pFilteredStr = str_comp(pName, &aFilterStr[1]) == 0 ? pName : nullptr; + FilterLen -= 2; + } + else + { + const char *pFilteredStrEnd; + pFilteredStr = str_utf8_find_nocase(pName, aFilterStr, &pFilteredStrEnd); + if(pFilteredStr != nullptr && pFilteredStrEnd != nullptr) + FilterLen = pFilteredStrEnd - pFilteredStr; + } + if(pFilteredStr) + { + PrintFn(pFilteredStr, FilterLen); + return true; + } } + return false; +} - int ToolboxPage = g_Config.m_UiToolboxPage; +CTeeRenderInfo CMenus::GetTeeRenderInfo(vec2 Size, const char *pSkinName, bool CustomSkinColors, int CustomSkinColorBody, int CustomSkinColorFeet) const +{ + const CSkin *pSkin = m_pClient->m_Skins.Find(pSkinName); - // tool box + CTeeRenderInfo TeeInfo; + TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin; + TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin; + TeeInfo.m_SkinMetrics = pSkin->m_Metrics; + TeeInfo.m_CustomColoredSkin = CustomSkinColors; + if(CustomSkinColors) { - ToolBox.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_ALL, 4.0f); - - if(ToolboxPage == 0) - RenderServerbrowserFilters(ToolBox); - else if(ToolboxPage == 1) - RenderServerbrowserServerDetail(ToolBox); - else if(ToolboxPage == 2) - RenderServerbrowserFriends(ToolBox); + TeeInfo.m_ColorBody = color_cast(ColorHSLA(CustomSkinColorBody).UnclampLighting()); + TeeInfo.m_ColorFeet = color_cast(ColorHSLA(CustomSkinColorFeet).UnclampLighting()); } - - // tab bar + else { - CUIRect TabBar; - ToolBox.HSplitBottom(18, &ToolBox, &TabBar); - CUIRect TabButton0, TabButton1, TabButton2; - float CurTabBarWidth = ToolBox.w; - TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton0, &TabBar); - TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton1, &TabBar); - TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton2, &TabBar); - ColorRGBA Active = ms_ColorTabbarActive; - ColorRGBA InActive = ms_ColorTabbarInactive; - ms_ColorTabbarActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f); - ms_ColorTabbarInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f); - - static CButtonContainer s_FiltersTab; - if(DoButton_MenuTab(&s_FiltersTab, Localize("Filter"), ToolboxPage == 0, &TabButton0, IGraphics::CORNER_BL, NULL, NULL, NULL, NULL, 4.0f)) - ToolboxPage = 0; - - static CButtonContainer s_InfoTab; - if(DoButton_MenuTab(&s_InfoTab, Localize("Info"), ToolboxPage == 1, &TabButton1, IGraphics::CORNER_NONE, NULL, NULL, NULL, NULL, 4.0f)) - ToolboxPage = 1; - - static CButtonContainer s_FriendsTab; - if(DoButton_MenuTab(&s_FriendsTab, Localize("Friends"), ToolboxPage == 2, &TabButton2, IGraphics::CORNER_BR, NULL, NULL, NULL, NULL, 4.0f)) - ToolboxPage = 2; - - ms_ColorTabbarActive = Active; - ms_ColorTabbarInactive = InActive; - g_Config.m_UiToolboxPage = ToolboxPage; + TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f); + TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f); } + TeeInfo.m_Size = minimum(Size.x, Size.y); + return TeeInfo; } void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE) + if(pResult->NumArguments() >= 1 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE) { ((CMenus *)pUserData)->FriendlistOnUpdate(); ((CMenus *)pUserData)->Client()->ServerBrowserUpdate(); @@ -1741,6 +1668,6 @@ void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserDat void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); - if(pResult->NumArguments() && g_Config.m_UiPage == PAGE_FAVORITES) + if(pResult->NumArguments() >= 1 && g_Config.m_UiPage == PAGE_FAVORITES) ((CMenus *)pUserData)->ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); } diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 776cc958d..aa8078b9f 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -109,6 +109,8 @@ void CMenus::DemoSeekTick(IDemoPlayer::ETickOffset TickOffset) void CMenus::RenderDemoPlayer(CUIRect MainView) { const IDemoPlayer::CInfo *pInfo = DemoPlayer()->BaseInfo(); + const int CurrentTick = pInfo->m_CurrentTick - pInfo->m_FirstTick; + const int TotalTicks = pInfo->m_LastTick - pInfo->m_FirstTick; // When rendering a demo and starting paused, render the pause indicator permanently. #if defined(CONF_VIDEORECORDER) @@ -128,13 +130,49 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) m_LastSpeedChange = Client()->GlobalTime(); }; + // threshold value, accounts for slight inaccuracy when setting demo position + constexpr int Threshold = 10; + const auto &&FindPreviousMarkerPosition = [&]() { + for(int i = pInfo->m_NumTimelineMarkers - 1; i >= 0; i--) + { + if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) < CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) + { + return (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; + } + } + return 0.0f; + }; + const auto &&FindNextMarkerPosition = [&]() { + for(int i = 0; i < pInfo->m_NumTimelineMarkers; i++) + { + if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) > CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) + { + return (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; + } + } + return 1.0f; + }; + + static int s_SkipDurationIndex = 1; + static const int s_aSkipDurationsSeconds[] = {1, 5, 10, 30, 60, 5 * 60, 10 * 60}; + const int DemoLengthSeconds = TotalTicks / SERVER_TICK_SPEED; + int NumDurationLabels = 0; + for(size_t i = 0; i < std::size(s_aSkipDurationsSeconds); ++i) + { + if(s_aSkipDurationsSeconds[i] >= DemoLengthSeconds) + break; + NumDurationLabels = i + 1; + } + if(NumDurationLabels > 0 && s_SkipDurationIndex >= NumDurationLabels) + s_SkipDurationIndex = maximum(0, NumDurationLabels - 1); + // handle keyboard shortcuts independent of active menu float PositionToSeek = -1.0f; float TimeToSeek = 0.0f; - if(m_pClient->m_GameConsole.IsClosed() && m_DemoPlayerState == DEMOPLAYER_NONE && g_Config.m_ClDemoKeyboardShortcuts) + if(m_pClient->m_GameConsole.IsClosed() && m_DemoPlayerState == DEMOPLAYER_NONE && g_Config.m_ClDemoKeyboardShortcuts && !UI()->IsPopupOpen()) { // increase/decrease speed - if(!Input()->ShiftIsPressed()) + if(!Input()->ModifierIsPressed() && !Input()->ShiftIsPressed() && !Input()->AltIsPressed()) { if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) || Input()->KeyPress(KEY_UP)) { @@ -149,7 +187,7 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) } // pause/unpause - if(Input()->KeyPress(KEY_SPACE) || Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_K)) + if(Input()->KeyPress(KEY_SPACE) || Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_KP_ENTER) || Input()->KeyPress(KEY_K)) { if(pInfo->m_Paused) { @@ -162,22 +200,24 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) UpdateLastPauseChange(); } - // seek backward/forward 10/5 seconds - if(Input()->KeyPress(KEY_J)) + // seek backward/forward configured time + if(Input()->KeyPress(KEY_LEFT) || Input()->KeyPress(KEY_J)) { - TimeToSeek = -10.0f; + if(Input()->ModifierIsPressed()) + PositionToSeek = FindPreviousMarkerPosition(); + else if(Input()->ShiftIsPressed()) + s_SkipDurationIndex = maximum(s_SkipDurationIndex - 1, 0); + else + TimeToSeek = -s_aSkipDurationsSeconds[s_SkipDurationIndex]; } - else if(Input()->KeyPress(KEY_L)) + else if(Input()->KeyPress(KEY_RIGHT) || Input()->KeyPress(KEY_L)) { - TimeToSeek = 10.0f; - } - else if(Input()->KeyPress(KEY_LEFT)) - { - TimeToSeek = -5.0f; - } - else if(Input()->KeyPress(KEY_RIGHT)) - { - TimeToSeek = 5.0f; + if(Input()->ModifierIsPressed()) + PositionToSeek = FindNextMarkerPosition(); + else if(Input()->ShiftIsPressed()) + s_SkipDurationIndex = minimum(s_SkipDurationIndex + 1, NumDurationLabels - 1); + else + TimeToSeek = s_aSkipDurationsSeconds[s_SkipDurationIndex]; } // seek to 0-90% @@ -257,9 +297,6 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) m_LastSpeedChange = 0.0f; } - const int CurrentTick = pInfo->m_CurrentTick - pInfo->m_FirstTick; - const int TotalTicks = pInfo->m_LastTick - pInfo->m_FirstTick; - if(CurrentTick == TotalTicks) { DemoPlayer()->Pause(); @@ -496,37 +533,110 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_ResetButton, &Button, Localize("Stop the current demo")); + // skip time back + ButtonBar.VSplitLeft(Margins + 10.0f, nullptr, &ButtonBar); + ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); + static CButtonContainer s_TimeBackButton; + if(DoButton_FontIcon(&s_TimeBackButton, FONT_ICON_BACKWARD, 0, &Button, IGraphics::CORNER_ALL)) + { + TimeToSeek = -s_aSkipDurationsSeconds[s_SkipDurationIndex]; + } + GameClient()->m_Tooltips.DoToolTip(&s_TimeBackButton, &Button, Localize("Go back the specified duration")); + + // skip time dropdown + if(NumDurationLabels >= 2) + { + ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); + ButtonBar.VSplitLeft(4 * ButtonbarHeight, &Button, &ButtonBar); + + static std::vector s_vDurationNames; + static std::vector s_vpDurationNames; + s_vDurationNames.resize(NumDurationLabels); + s_vpDurationNames.resize(NumDurationLabels); + + for(int i = 0; i < NumDurationLabels; ++i) + { + char aBuf[256]; + if(s_aSkipDurationsSeconds[i] >= 60) + str_format(aBuf, sizeof(aBuf), Localize("%d min.", "Demo player duration"), s_aSkipDurationsSeconds[i] / 60); + else + str_format(aBuf, sizeof(aBuf), Localize("%d sec.", "Demo player duration"), s_aSkipDurationsSeconds[i]); + s_vDurationNames[i] = aBuf; + s_vpDurationNames[i] = s_vDurationNames[i].c_str(); + } + + static CUI::SDropDownState s_SkipDurationDropDownState; + static CScrollRegion s_SkipDurationDropDownScrollRegion; + s_SkipDurationDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_SkipDurationDropDownScrollRegion; + s_SkipDurationIndex = UI()->DoDropDown(&Button, s_SkipDurationIndex, s_vpDurationNames.data(), NumDurationLabels, s_SkipDurationDropDownState); + GameClient()->m_Tooltips.DoToolTip(&s_SkipDurationDropDownState.m_ButtonContainer, &Button, Localize("Change the skip duration")); + } + + // skip time forward + ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); + ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); + static CButtonContainer s_TimeForwardButton; + if(DoButton_FontIcon(&s_TimeForwardButton, FONT_ICON_FORWARD, 0, &Button, IGraphics::CORNER_ALL)) + { + TimeToSeek = s_aSkipDurationsSeconds[s_SkipDurationIndex]; + } + GameClient()->m_Tooltips.DoToolTip(&s_TimeForwardButton, &Button, Localize("Go forward the specified duration")); + // one tick back ButtonBar.VSplitLeft(Margins + 10.0f, nullptr, &ButtonBar); ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_OneTickBackButton; - if(DoButton_FontIcon(&s_OneTickBackButton, FONT_ICON_CHEVRON_LEFT, 0, &Button, IGraphics::CORNER_ALL)) + if(DoButton_FontIcon(&s_OneTickBackButton, FONT_ICON_BACKWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) + { DemoSeekTick(IDemoPlayer::TICK_PREVIOUS); + } GameClient()->m_Tooltips.DoToolTip(&s_OneTickBackButton, &Button, Localize("Go back one tick")); // one tick forward ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_OneTickForwardButton; - if(DoButton_FontIcon(&s_OneTickForwardButton, FONT_ICON_CHEVRON_RIGHT, 0, &Button, IGraphics::CORNER_ALL)) + if(DoButton_FontIcon(&s_OneTickForwardButton, FONT_ICON_FORWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) + { DemoSeekTick(IDemoPlayer::TICK_NEXT); + } GameClient()->m_Tooltips.DoToolTip(&s_OneTickForwardButton, &Button, Localize("Go forward one tick")); + // one marker back + ButtonBar.VSplitLeft(Margins + 10.0f, nullptr, &ButtonBar); + ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); + static CButtonContainer s_OneMarkerBackButton; + if(DoButton_FontIcon(&s_OneMarkerBackButton, FONT_ICON_BACKWARD_FAST, 0, &Button, IGraphics::CORNER_ALL)) + { + PositionToSeek = FindPreviousMarkerPosition(); + } + GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerBackButton, &Button, Localize("Go back one marker")); + + // one marker forward + ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); + ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); + static CButtonContainer s_OneMarkerForwardButton; + if(DoButton_FontIcon(&s_OneMarkerForwardButton, FONT_ICON_FORWARD_FAST, 0, &Button, IGraphics::CORNER_ALL)) + { + PositionToSeek = FindNextMarkerPosition(); + } + GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerForwardButton, &Button, Localize("Go forward one marker")); + // slowdown ButtonBar.VSplitLeft(Margins + 10.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); static CButtonContainer s_SlowDownButton; - if(DoButton_FontIcon(&s_SlowDownButton, FONT_ICON_BACKWARD, 0, &Button, IGraphics::CORNER_ALL)) + if(DoButton_FontIcon(&s_SlowDownButton, FONT_ICON_CHEVRON_DOWN, 0, &Button, IGraphics::CORNER_ALL)) DecreaseDemoSpeed = true; GameClient()->m_Tooltips.DoToolTip(&s_SlowDownButton, &Button, Localize("Slow down the demo")); // fastforward ButtonBar.VSplitLeft(Margins, 0, &ButtonBar); ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static CButtonContainer s_FastForwardButton; - if(DoButton_FontIcon(&s_FastForwardButton, FONT_ICON_FORWARD, 0, &Button, IGraphics::CORNER_ALL)) + static CButtonContainer s_SpeedUpButton; + if(DoButton_FontIcon(&s_SpeedUpButton, FONT_ICON_CHEVRON_UP, 0, &Button, IGraphics::CORNER_ALL)) IncreaseDemoSpeed = true; - GameClient()->m_Tooltips.DoToolTip(&s_FastForwardButton, &Button, Localize("Speed up the demo")); + GameClient()->m_Tooltips.DoToolTip(&s_SpeedUpButton, &Button, Localize("Speed up the demo")); // speed meter ButtonBar.VSplitLeft(Margins * 12, &SpeedBar, &ButtonBar); @@ -576,51 +686,11 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) char aDemoName[IO_MAX_PATH_LENGTH]; DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); m_DemoSliceInput.Set(aDemoName); - m_DemoSliceInput.Append(".demo"); UI()->SetActiveItem(&m_DemoSliceInput); m_DemoPlayerState = DEMOPLAYER_SLICE_SAVE; } GameClient()->m_Tooltips.DoToolTip(&s_SliceSaveButton, &Button, Localize("Export cut as a separate demo")); - // threshold value, accounts for slight inaccuracy when setting demo position - const int Threshold = 10; - - // one marker back - ButtonBar.VSplitLeft(Margins + 20.0f, nullptr, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static CButtonContainer s_OneMarkerBackButton; - if(DoButton_FontIcon(&s_OneMarkerBackButton, FONT_ICON_BACKWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) - { - PositionToSeek = 0.0f; - for(int i = pInfo->m_NumTimelineMarkers - 1; i >= 0; i--) - { - if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) < CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) - { - PositionToSeek = (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; - break; - } - } - } - GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerBackButton, &Button, Localize("Go back one marker")); - - // one marker forward - ButtonBar.VSplitLeft(Margins, nullptr, &ButtonBar); - ButtonBar.VSplitLeft(ButtonbarHeight, &Button, &ButtonBar); - static CButtonContainer s_OneMarkerForwardButton; - if(DoButton_FontIcon(&s_OneMarkerForwardButton, FONT_ICON_FORWARD_STEP, 0, &Button, IGraphics::CORNER_ALL)) - { - PositionToSeek = 1.0f; - for(int i = 0; i < pInfo->m_NumTimelineMarkers; i++) - { - if((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) > CurrentTick && absolute(((pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) - CurrentTick)) > Threshold) - { - PositionToSeek = (float)(pInfo->m_aTimelineMarkers[i] - pInfo->m_FirstTick) / TotalTicks; - break; - } - } - } - GameClient()->m_Tooltips.DoToolTip(&s_OneMarkerForwardButton, &Button, Localize("Go forward one marker")); - // close button ButtonBar.VSplitRight(ButtonbarHeight, &ButtonBar, &Button); static CButtonContainer s_ExitButton; @@ -726,13 +796,22 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) // remove chat checkbox static int s_RemoveChat = 0; - CUIRect RemoveChatCheckBox; - Box.HSplitTop(24.0f, &RemoveChatCheckBox, &Box); + + CUIRect CheckBoxBar, RemoveChatCheckBox, RenderCutCheckBox; + Box.HSplitTop(24.0f, &CheckBoxBar, &Box); Box.HSplitTop(20.0f, nullptr, &Box); + CheckBoxBar.VSplitMid(&RemoveChatCheckBox, &RenderCutCheckBox, 40.0f); if(DoButton_CheckBox(&s_RemoveChat, Localize("Remove chat"), s_RemoveChat, &RemoveChatCheckBox)) { s_RemoveChat ^= 1; } +#if defined(CONF_VIDEORECORDER) + static int s_RenderCut = 0; + if(DoButton_CheckBox(&s_RenderCut, Localize("Render cut to video"), s_RenderCut, &RenderCutCheckBox)) + { + s_RenderCut ^= 1; + } +#endif // buttons CUIRect ButtonBar, AbortButton, OkButton; @@ -748,11 +827,11 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &OkButton) || (!UI()->IsPopupOpen() && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))) { char aDemoName[IO_MAX_PATH_LENGTH]; + char aNameWithoutExt[IO_MAX_PATH_LENGTH]; DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); - str_append(aDemoName, ".demo"); - if(!str_endswith(m_DemoSliceInput.GetString(), ".demo")) - m_DemoSliceInput.Append(".demo"); + fs_split_file_extension(m_DemoSliceInput.GetString(), aNameWithoutExt, sizeof(aNameWithoutExt)); + m_DemoSliceInput.Set(aNameWithoutExt); if(str_comp(aDemoName, m_DemoSliceInput.GetString()) == 0) { @@ -780,14 +859,24 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) if(s_ConfirmPopupContext.m_Result == CUI::SConfirmPopupContext::CONFIRMED) { char aPath[IO_MAX_PATH_LENGTH]; - str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); + str_format(aPath, sizeof(aPath), "%s/%s.demo", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); str_copy(m_aCurrentDemoSelectionName, m_DemoSliceInput.GetString()); if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; - m_DemoPlayerState = DEMOPLAYER_NONE; + Client()->DemoSlice(aPath, CMenus::DemoFilterChat, &s_RemoveChat); DemolistPopulate(); DemolistOnUpdate(false); + m_DemoPlayerState = DEMOPLAYER_NONE; +#if defined(CONF_VIDEORECORDER) + if(s_RenderCut) + { + m_Popup = POPUP_RENDER_DEMO; + m_StartPaused = false; + m_DemoRenderInput.Set(m_aCurrentDemoSelectionName); + UI()->SetActiveItem(&m_DemoRenderInput); + } +#endif } if(s_ConfirmPopupContext.m_Result != CUI::SConfirmPopupContext::UNSET) { @@ -913,6 +1002,8 @@ void CMenus::DemolistOnUpdate(bool Reset) { bool Found = false; int SelectedIndex = -1; + RefreshFilteredDemos(); + // search for selected index for(auto &Item : m_vpFilteredDemos) { @@ -924,7 +1015,6 @@ void CMenus::DemolistOnUpdate(bool Reset) break; } } - RefreshFilteredDemos(); if(Found) m_DemolistSelectedIndex = SelectedIndex; @@ -1112,7 +1202,7 @@ void CMenus::RenderDemoList(CUIRect MainView) { int m_ID; int m_Sort; - CLocConstString m_Caption; + const char *m_pCaption; int m_Direction; float m_Width; CUIRect m_Rect; @@ -1170,7 +1260,7 @@ void CMenus::RenderDemoList(CUIRect MainView) // do headers for(auto &Col : s_aCols) { - if(DoButton_GridHeader(&Col.m_ID, Col.m_Caption, g_Config.m_BrDemoSort == Col.m_Sort, &Col.m_Rect)) + if(DoButton_GridHeader(&Col.m_ID, Localize(Col.m_pCaption), g_Config.m_BrDemoSort == Col.m_Sort, &Col.m_Rect)) { if(Col.m_Sort != -1) { @@ -1182,7 +1272,7 @@ void CMenus::RenderDemoList(CUIRect MainView) } // Don't rescan in order to keep fetched headers, just resort - std::stable_sort(m_vpFilteredDemos.begin(), m_vpFilteredDemos.end()); + std::stable_sort(m_vDemos.begin(), m_vDemos.end()); DemolistOnUpdate(false); } } diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 0d3c0b914..c4b7235ff 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -812,49 +812,45 @@ void CMenus::RenderServerControl(CUIRect MainView) void CMenus::RenderInGameNetwork(CUIRect MainView) { - CUIRect Box = MainView; - CUIRect Button; - - int Page = g_Config.m_UiPage; - int NewPage = -1; - MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f); - Box.HSplitTop(5.0f, &MainView, &MainView); - Box.HSplitTop(24.0f, &Box, &MainView); + CUIRect TabBar, Button; + MainView.HSplitTop(24.0f, &TabBar, &MainView); - Box.VSplitLeft(100.0f, &Button, &Box); + int NewPage = g_Config.m_UiPage; + + TabBar.VSplitLeft(100.0f, &Button, &TabBar); static CButtonContainer s_InternetButton; - if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), Page == PAGE_INTERNET, &Button, 0)) + if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), g_Config.m_UiPage == PAGE_INTERNET, &Button, IGraphics::CORNER_NONE)) { - if(Page != PAGE_INTERNET) + if(g_Config.m_UiPage != PAGE_INTERNET) ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); NewPage = PAGE_INTERNET; } - Box.VSplitLeft(80.0f, &Button, &Box); + TabBar.VSplitLeft(80.0f, &Button, &TabBar); static CButtonContainer s_LanButton; - if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), Page == PAGE_LAN, &Button, 0)) + if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), g_Config.m_UiPage == PAGE_LAN, &Button, IGraphics::CORNER_NONE)) { - if(Page != PAGE_LAN) + if(g_Config.m_UiPage != PAGE_LAN) ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); NewPage = PAGE_LAN; } - Box.VSplitLeft(110.0f, &Button, &Box); + TabBar.VSplitLeft(110.0f, &Button, &TabBar); static CButtonContainer s_FavoritesButton; - if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), Page == PAGE_FAVORITES, &Button, 0)) + if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), g_Config.m_UiPage == PAGE_FAVORITES, &Button, IGraphics::CORNER_NONE)) { - if(Page != PAGE_FAVORITES) + if(g_Config.m_UiPage != PAGE_FAVORITES) ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); NewPage = PAGE_FAVORITES; } - Box.VSplitLeft(110.0f, &Button, &Box); + TabBar.VSplitLeft(110.0f, &Button, &TabBar); static CButtonContainer s_DDNetButton; - if(DoButton_MenuTab(&s_DDNetButton, "DDNet", Page == PAGE_DDNET, &Button, 0) || Page < PAGE_INTERNET || Page > PAGE_KOG) + if(DoButton_MenuTab(&s_DDNetButton, "DDNet", g_Config.m_UiPage == PAGE_DDNET, &Button, IGraphics::CORNER_NONE) || g_Config.m_UiPage < PAGE_INTERNET || g_Config.m_UiPage > PAGE_KOG) { - if(Page != PAGE_DDNET) + if(g_Config.m_UiPage != PAGE_DDNET) { Client()->RequestDDNetInfo(); ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); @@ -862,11 +858,11 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) NewPage = PAGE_DDNET; } - Box.VSplitLeft(110.0f, &Button, &Box); + TabBar.VSplitLeft(110.0f, &Button, &TabBar); static CButtonContainer s_KoGButton; - if(DoButton_MenuTab(&s_KoGButton, "KoG", Page == PAGE_KOG, &Button, IGraphics::CORNER_BR)) + if(DoButton_MenuTab(&s_KoGButton, "KoG", g_Config.m_UiPage == PAGE_KOG, &Button, IGraphics::CORNER_NONE)) { - if(Page != PAGE_KOG) + if(g_Config.m_UiPage != PAGE_KOG) { Client()->RequestDDNetInfo(); ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); @@ -874,7 +870,7 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) NewPage = PAGE_KOG; } - if(NewPage != -1) + if(NewPage != g_Config.m_UiPage) { if(Client()->State() != IClient::STATE_OFFLINE) SetMenuPage(NewPage); @@ -988,7 +984,7 @@ void CMenus::RenderGhost(CUIRect MainView) struct CColumn { - CLocConstString m_Caption; + const char *m_pCaption; int m_Id; float m_Width; CUIRect m_Rect; @@ -1003,8 +999,8 @@ void CMenus::RenderGhost(CUIRect MainView) }; static CColumn s_aCols[] = { - {" ", -1, 2.0f, {0}, {0}}, - {" ", COL_ACTIVE, 30.0f, {0}, {0}}, + {"", -1, 2.0f, {0}, {0}}, + {"", COL_ACTIVE, 30.0f, {0}, {0}}, {Localizable("Name"), COL_NAME, 300.0f, {0}, {0}}, {Localizable("Time"), COL_TIME, 200.0f, {0}, {0}}, }; @@ -1022,7 +1018,7 @@ void CMenus::RenderGhost(CUIRect MainView) // do headers for(int i = 0; i < NumCols; i++) - DoButton_GridHeader(s_aCols[i].m_Caption, Localize(s_aCols[i].m_Caption), 0, &s_aCols[i].m_Rect); + DoButton_GridHeader(&s_aCols[i].m_Id, Localize(s_aCols[i].m_pCaption), 0, &s_aCols[i].m_Rect); View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 740fd39c7..8bef1e29b 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -323,13 +323,12 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView) { m_Dummy ^= 1; } + GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &Dummy, Localize("Toggle to edit your dummy settings")); // country flag selector MainView.HSplitTop(20.0f, 0, &MainView); int OldSelected = -1; static CListBox s_ListBox; - if(UI()->CheckActiveItem(&s_ClanInput) || UI()->CheckActiveItem(&s_NameInput)) - s_ListBox.SetActive(false); s_ListBox.DoStart(50.0f, m_pClient->m_CountryFlags.Num(), 10, 3, OldSelected, &MainView); for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i) @@ -615,7 +614,6 @@ void CMenus::RenderSettingsTee(CUIRect MainView) EyesLabel.HSplitTop(50.0f, &EyesLabel, &Eyes); static CButtonContainer s_aEyeButtons[6]; - static int s_aEyesToolTip[6]; for(int CurrentEyeEmote = 0; CurrentEyeEmote < 6; CurrentEyeEmote++) { EyesLabel.VSplitLeft(10.0f, 0, &EyesLabel); @@ -642,7 +640,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) GameClient()->m_Emoticon.EyeEmote(CurrentEyeEmote); } } - GameClient()->m_Tooltips.DoToolTip(&s_aEyesToolTip[CurrentEyeEmote], &EyesTee, Localize("Choose default eyes when joining a server")); + GameClient()->m_Tooltips.DoToolTip(&s_aEyeButtons[CurrentEyeEmote], &EyesTee, Localize("Choose default eyes when joining a server")); RenderTools()->RenderTee(pIdleState, &OwnSkinInfo, CurrentEyeEmote, vec2(1, 0), vec2(EyesTee.x + 25.0f, EyesTee.y + EyesTee.h / 2.0f + OffsetToMid.y)); } @@ -940,7 +938,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) typedef struct { - CLocConstString m_Name; + const char *m_pName; const char *m_pCommand; int m_KeyId; int m_ModifierCombination; @@ -948,7 +946,7 @@ typedef struct static CKeyInfo gs_aKeys[] = { - {Localizable("Move left"), "+left", 0, 0}, // Localize - these strings are localized within CLocConstString + {Localizable("Move left"), "+left", 0, 0}, {Localizable("Move right"), "+right", 0, 0}, {Localizable("Jump"), "+jump", 0, 0}, {Localizable("Fire"), "+fire", 0, 0}, @@ -1003,23 +1001,24 @@ void CMenus::DoSettingsControlsButtons(int Start, int Stop, CUIRect View) { for(int i = Start; i < Stop; i++) { - CKeyInfo &Key = gs_aKeys[i]; + const CKeyInfo &Key = gs_aKeys[i]; + CUIRect Button, Label; View.HSplitTop(20.0f, &Button, &View); Button.VSplitLeft(135.0f, &Label, &Button); char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%s:", Localize((const char *)Key.m_Name)); + str_format(aBuf, sizeof(aBuf), "%s:", Localize(Key.m_pName)); UI()->DoLabel(&Label, aBuf, 13.0f, TEXTALIGN_ML); int OldId = Key.m_KeyId, OldModifierCombination = Key.m_ModifierCombination, NewModifierCombination; - int NewId = DoKeyReader((void *)&Key.m_Name, &Button, OldId, OldModifierCombination, &NewModifierCombination); + int NewId = DoKeyReader(&Key.m_KeyId, &Button, OldId, OldModifierCombination, &NewModifierCombination); if(NewId != OldId || NewModifierCombination != OldModifierCombination) { if(OldId != 0 || NewId == 0) m_pClient->m_Binds.Bind(OldId, "", false, OldModifierCombination); if(NewId != 0) - m_pClient->m_Binds.Bind(NewId, gs_aKeys[i].m_pCommand, false, NewModifierCombination); + m_pClient->m_Binds.Bind(NewId, Key.m_pCommand, false, NewModifierCombination); } View.HSplitTop(2.0f, 0, &View); @@ -2840,6 +2839,7 @@ void CMenus::RenderSettingsAppearance(CUIRect MainView) Section.HSplitTop(LineSize, &Label, &Section); UI()->DoLabel(&Label, Localize("Colors of the hook collision line, in case of a possible collision with:"), 13.0f, TEXTALIGN_ML); + UI()->DoButtonLogic(&s_HookCollToolTip, 0, &Label); // Just for the tooltip, result ignored GameClient()->m_Tooltips.DoToolTip(&s_HookCollToolTip, &Label, Localize("Your movements are not taken into account when calculating the line colors")); DoLine_ColorPicker(&s_HookCollNoCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Nothing hookable"), &g_Config.m_ClHookCollColorNoColl, ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), false); DoLine_ColorPicker(&s_HookCollHookableCollResetID, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &Section, Localize("Something hookable"), &g_Config.m_ClHookCollColorHookableColl, ColorRGBA(130.0f / 255.0f, 232.0f / 255.0f, 160.0f / 255.0f, 1.0f), false); diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index e5f328917..b8a2a9960 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -309,7 +309,7 @@ void CMenus::ClearCustomItems(int CurTab) m_vEntitiesList.clear(); // reload current entities - m_pClient->m_MapImages.ChangeEntitiesPath(g_Config.m_ClAssetsEntites); + m_pClient->m_MapImages.ChangeEntitiesPath(g_Config.m_ClAssetsEntities); } else if(CurTab == ASSETS_TAB_GAME) { @@ -545,7 +545,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { - if(str_comp(pItem->m_aName, g_Config.m_ClAssetsEntites) == 0) + if(str_comp(pItem->m_aName, g_Config.m_ClAssetsEntities) == 0) OldSelected = i; } else if(s_CurCustomTab == ASSETS_TAB_GAME) @@ -604,7 +604,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) { if(s_CurCustomTab == ASSETS_TAB_ENTITIES) { - str_copy(g_Config.m_ClAssetsEntites, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName); + str_copy(g_Config.m_ClAssetsEntities, GetCustomItem(s_CurCustomTab, NewSelected)->m_aName); m_pClient->m_MapImages.ChangeEntitiesPath(GetCustomItem(s_CurCustomTab, NewSelected)->m_aName); } else if(s_CurCustomTab == ASSETS_TAB_GAME) @@ -708,7 +708,7 @@ void CMenus::ConchainAssetsEntities(IConsole::IResult *pResult, void *pUserData, if(pResult->NumArguments() == 1) { const char *pArg = pResult->GetString(0); - if(str_comp(pArg, g_Config.m_ClAssetsEntites) != 0) + if(str_comp(pArg, g_Config.m_ClAssetsEntities) != 0) { pThis->m_pClient->m_MapImages.ChangeEntitiesPath(pArg); } diff --git a/src/game/client/components/menus_start.cpp b/src/game/client/components/menus_start.cpp index 580e1f9f1..791afbf06 100644 --- a/src/game/client/components/menus_start.cpp +++ b/src/game/client/components/menus_start.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "menus.h" diff --git a/src/game/client/components/nameplates.cpp b/src/game/client/components/nameplates.cpp index ca08c6946..521cce7f2 100644 --- a/src/game/client/components/nameplates.cpp +++ b/src/game/client/components/nameplates.cpp @@ -144,7 +144,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP float tw = m_aNamePlates[ClientID].m_NameTextWidth; if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Teams.Team(ClientID)) - rgb = color_cast(ColorHSLA(m_pClient->m_Teams.Team(ClientID) / 64.0f, 1.0f, 0.75f)); + rgb = m_pClient->GetDDTeamColor(m_pClient->m_Teams.Team(ClientID), 0.75f); ColorRGBA TColor; ColorRGBA TOutlineColor; diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 0ab994cf4..95e137b78 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -763,7 +763,8 @@ void CPlayers::OnRender() m_aRenderInfo[i].m_TeeRenderFlags |= TEE_EFFECT_FROZEN; const CGameClient::CSnapState::CCharacterInfo &CharacterInfo = m_pClient->m_Snap.m_aCharacters[i]; - if((CharacterInfo.m_Cur.m_Weapon == WEAPON_NINJA || (CharacterInfo.m_HasExtendedData && CharacterInfo.m_ExtendedData.m_FreezeEnd != 0)) && g_Config.m_ClShowNinja) + const bool Frozen = CharacterInfo.m_HasExtendedData && CharacterInfo.m_ExtendedData.m_FreezeEnd != 0; + if((CharacterInfo.m_Cur.m_Weapon == WEAPON_NINJA || (Frozen && !m_pClient->m_GameInfo.m_NoSkinChangeForFrozen)) && g_Config.m_ClShowNinja) { // change the skin for the player to the ninja const auto *pSkin = m_pClient->m_Skins.FindOrNullptr("x_ninja"); diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index b84f07159..5643f57a4 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -355,7 +355,7 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch if(DDTeam != TEAM_FLOCK) { - ColorRGBA Color = color_cast(ColorHSLA(DDTeam / 64.0f, 1.0f, 0.5f, 0.5f)); + const ColorRGBA Color = m_pClient->GetDDTeamColor(DDTeam).WithAlpha(0.5f); int Corners = 0; if(OldDDTeam != DDTeam) Corners |= IGraphics::CORNER_TL | IGraphics::CORNER_TR; diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp index 592743b86..ce4812224 100644 --- a/src/game/client/components/skins.cpp +++ b/src/game/client/components/skins.cpp @@ -234,7 +234,7 @@ const CSkin *CSkins::LoadSkin(const char *pName, CImageInfo &Info) int OrgWeight = 0; int NewWeight = 192; - // find most common frequence + // find most common frequency for(int y = 0; y < BodyHeight; y++) for(int x = 0; x < BodyWidth; x++) { diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 739aaf2ec..0f5d28d40 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -173,7 +173,7 @@ void CSpectator::OnConsoleInit() Console()->Register("spectate_next", "", CFGFLAG_CLIENT, ConSpectateNext, this, "Spectate the next player"); Console()->Register("spectate_previous", "", CFGFLAG_CLIENT, ConSpectatePrevious, this, "Spectate the previous player"); Console()->Register("spectate_closest", "", CFGFLAG_CLIENT, ConSpectateClosest, this, "Spectate the closest player"); - Console()->Register("spectate_multiview", "i[id]", CFGFLAG_CLIENT, ConMultiView, this, "Add/remove Client-IDs to spectate them exclusivly (-1 to reset)"); + Console()->Register("spectate_multiview", "i[id]", CFGFLAG_CLIENT, ConMultiView, this, "Add/remove Client-IDs to spectate them exclusively (-1 to reset)"); } bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType) @@ -401,7 +401,7 @@ void CSpectator::OnRender() if(DDTeam != TEAM_FLOCK) { - ColorRGBA Color = color_cast(ColorHSLA(DDTeam / 64.0f, 1.0f, 0.5f, 0.5f)); + const ColorRGBA Color = m_pClient->GetDDTeamColor(DDTeam).WithAlpha(0.5f); int Corners = 0; if(OldDDTeam != DDTeam) Corners |= IGraphics::CORNER_TL | IGraphics::CORNER_TR; diff --git a/src/game/client/components/voting.cpp b/src/game/client/components/voting.cpp index dd9dca5c6..21242a9d5 100644 --- a/src/game/client/components/voting.cpp +++ b/src/game/client/components/voting.cpp @@ -130,7 +130,8 @@ void CVoting::AddvoteOption(const char *pDescription, const char *pCommand) void CVoting::Vote(int v) { - m_Voted = v; + if(m_Voted == 0) + m_Voted = v; CNetMsg_Cl_Vote Msg = {v}; Client()->SendPackMsgActive(&Msg, MSGFLAG_VITAL); } @@ -299,6 +300,11 @@ void CVoting::OnMessage(int MsgType, void *pRawMsg) } } } + else if(MsgType == NETMSGTYPE_SV_YOURVOTE) + { + CNetMsg_Sv_YourVote *pMsg = (CNetMsg_Sv_YourVote *)pRawMsg; + m_Voted = pMsg->m_Voted; + } } void CVoting::OnRender() diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 7abed9e1c..ae72bcdf9 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -76,7 +76,7 @@ using namespace std::chrono_literals; const char *CGameClient::Version() const { return GAME_VERSION; } const char *CGameClient::NetVersion() const { return GAME_NETVERSION; } -int CGameClient::DDNetVersion() const { return CLIENT_VERSIONNR; } +int CGameClient::DDNetVersion() const { return DDNET_VERSION_NUMBER; } const char *CGameClient::DDNetVersionStr() const { return m_aDDNetVersionStr; } const char *CGameClient::GetItemName(int Type) const { return m_NetObjHandler.GetObjName(Type); } @@ -305,7 +305,7 @@ void CGameClient::OnInit() else if(g_pData->m_aImages[i].m_pFilename[0] == '\0') // handle special null image without filename g_pData->m_aImages[i].m_Id = IGraphics::CTextureHandle(); else - g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); + g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL); m_Menus.RenderLoading(pLoadingDDNetCaption, Localize("Initializing assets"), 1); } @@ -637,8 +637,6 @@ void CGameClient::UpdatePositions() if(!m_MultiViewActivated && m_MultiView.m_IsInit) ResetMultiView(); - else if(!m_Snap.m_SpecInfo.m_Active) - m_MultiViewPersonalZoom = 0; UpdateRenderedCharacters(); } @@ -767,6 +765,14 @@ bool CGameClient::Predict() const return !m_Snap.m_SpecInfo.m_Active && m_Snap.m_pLocalCharacter; } +ColorRGBA CGameClient::GetDDTeamColor(int DDTeam, float Lightness) const +{ + // Use golden angle to generate unique colors with distinct adjacent colors. + // The first DDTeam (team 1) gets angle 0°, i.e. red hue. + const float Hue = std::fmod((DDTeam - 1) * (137.50776f / 360.0f), 1.0f); + return color_cast(ColorHSLA(Hue, 1.0f, Lightness)); +} + void CGameClient::OnRelease() { // release all systems @@ -904,7 +910,7 @@ void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker, int Conn, bool Dumm // if everyone of a team killed, we have no ids to spectate anymore, so we disable multi view if(!IsMultiViewIdSet()) - m_MultiViewActivated = false; + ResetMultiView(); else { // the "main" tee killed, search a new one @@ -1190,6 +1196,7 @@ static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize, Info.m_HudAmmo = true; Info.m_HudDDRace = false; Info.m_NoWeakHookAndBounce = false; + Info.m_NoSkinChangeForFrozen = false; if(Version >= 0) { @@ -1245,6 +1252,11 @@ static CGameInfo GetGameInfo(const CNetObj_GameInfoEx *pInfoEx, int InfoExSize, { Info.m_NoWeakHookAndBounce = Flags2 & GAMEINFOFLAG2_NO_WEAK_HOOK; } + if(Version >= 9) + { + Info.m_NoSkinChangeForFrozen = Flags2 & GAMEINFOFLAG2_NO_SKIN_CHANGE_FOR_FROZEN; + } + return Info; } @@ -1772,7 +1784,7 @@ void CGameClient::OnNewSnapshot() continue; } CMsgPacker Msg(NETMSGTYPE_CL_ISDDNETLEGACY, false); - Msg.AddInt(CLIENT_VERSIONNR); + Msg.AddInt(DDNetVersion()); Client()->SendMsg(i, &Msg, MSGFLAG_VITAL); m_aDDRaceMsgSent[i] = true; } @@ -3339,6 +3351,11 @@ void CGameClient::RefindSkins() Client.m_SkinInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin; Client.UpdateRenderInfo(IsTeamPlay()); } + else + { + Client.m_SkinInfo.m_OriginalRenderSkin.Reset(); + Client.m_SkinInfo.m_ColorableRenderSkin.Reset(); + } } m_Ghost.RefindSkin(); m_Chat.RefindSkins(); @@ -3579,7 +3596,7 @@ void CGameClient::HandleMultiView() m_MultiView.m_SecondChance = Client()->LocalTime() + 0.3f; else if(m_MultiView.m_SecondChance < Client()->LocalTime()) { - m_MultiViewActivated = false; + ResetMultiView(); return; } return; @@ -3587,7 +3604,7 @@ void CGameClient::HandleMultiView() else if(m_MultiView.m_SecondChance != 0.0f) m_MultiView.m_SecondChance = 0.0f; - // if we only have one tee thats in the list, we activate solo-mode + // if we only have one tee that's in the list, we activate solo-mode m_MultiView.m_Solo = std::count(std::begin(m_aMultiViewId), std::end(m_aMultiViewId), true) == 1; vec2 TargetPos = vec2((Minpos.x + Maxpos.x) / 2.0f, (Minpos.y + Maxpos.y) / 2.0f); @@ -3753,7 +3770,7 @@ float CGameClient::CalculateMultiViewZoom(vec2 MinPos, vec2 MaxPos, float Vel) float Zoom = std::max(ZoomX, ZoomY); // zoom out to maximum 10 percent of the current zoom for 70 velocity float Diff = clamp(MapValue(70.0f, 15.0f, Zoom * 0.10f, 0.0f, Vel), 0.0f, Zoom * 0.10f); - // zoom should stay inbetween 1.1 and 20.0 + // zoom should stay between 1.1 and 20.0 Zoom = clamp(Zoom + Diff, 1.1f, 20.0f); // add the user preference Zoom -= (Zoom * 0.1f) * m_MultiViewPersonalZoom; @@ -3769,6 +3786,7 @@ float CGameClient::MapValue(float MaxValue, float MinValue, float MaxRange, floa void CGameClient::ResetMultiView() { + m_Camera.SetZoom(std::pow(CCamera::ZOOM_STEP, g_Config.m_ClDefaultZoom - 10), g_Config.m_ClSmoothZoomTime); m_MultiViewPersonalZoom = 0; m_MultiViewActivated = false; m_MultiView.m_Solo = false; diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index f82fc9513..d758e07a5 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -96,6 +96,7 @@ public: bool m_HudDDRace; bool m_NoWeakHookAndBounce; + bool m_NoSkinChangeForFrozen; }; class CSnapEntities @@ -529,6 +530,7 @@ public: bool Predict() const; bool PredictDummy() { return g_Config.m_ClPredictDummy && Client()->DummyConnected() && m_Snap.m_LocalClientID >= 0 && m_PredictedDummyID >= 0 && !m_aClients[m_PredictedDummyID].m_Paused; } const CTuningParams *GetTuning(int i) { return &m_aTuningList[i]; } + ColorRGBA GetDDTeamColor(int DDTeam, float Lightness = 0.5f) const; CGameWorld m_GameWorld; CGameWorld m_PredictedWorld; diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index 9f7d3e050..e230024e4 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -159,6 +159,7 @@ void CLineInput::SetCursorOffset(size_t Offset) void CLineInput::SetSelection(size_t Start, size_t End) { + dbg_assert(m_CursorPos == Start || m_CursorPos == End, "Selection and cursor offset got desynchronized"); if(Start > End) std::swap(Start, End); m_SelectionStart = clamp(Start, 0, m_Len); @@ -385,6 +386,9 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event) STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth) { + // update derived attributes to handle external changes to the buffer + UpdateStrData(); + m_WasRendered = true; const char *pDisplayStr = GetDisplayedString(); @@ -472,12 +476,12 @@ STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Al TextRender()->TextEx(&Cursor, pDisplayStr); } - if(Cursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) + if(Cursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && Cursor.m_CursorCharacter >= 0) { const size_t NewCursorOffset = str_utf8_offset_chars_to_bytes(pDisplayStr, Cursor.m_CursorCharacter); SetCursorOffset(OffsetFromDisplayToActual(NewCursorOffset)); } - if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) + if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE && Cursor.m_SelectionStart >= 0 && Cursor.m_SelectionEnd >= 0) { const size_t NewSelectionStart = str_utf8_offset_chars_to_bytes(pDisplayStr, Cursor.m_SelectionStart); const size_t NewSelectionEnd = str_utf8_offset_chars_to_bytes(pDisplayStr, Cursor.m_SelectionEnd); diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index e3e23e440..93b298532 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -53,7 +53,7 @@ private: static char ms_aStars[128]; - char *m_pStr = nullptr; // explicitly set to nullptr outside of contructor, so SetBuffer works in this case + char *m_pStr = nullptr; // explicitly set to nullptr outside of constructor, so SetBuffer works in this case size_t m_MaxSize; size_t m_MaxChars; size_t m_Len; diff --git a/src/game/client/prediction/entities/character.cpp b/src/game/client/prediction/entities/character.cpp index 09edebeb8..701e5b387 100644 --- a/src/game/client/prediction/entities/character.cpp +++ b/src/game/client/prediction/entities/character.cpp @@ -939,7 +939,7 @@ void CCharacter::HandleTuneLayer() SetTuneZone(GameWorld()->m_WorldConfig.m_UseTuneZones ? Collision()->IsTune(CurrentIndex) : 0); if(m_IsLocal) - m_Core.m_pWorld->m_aTuning[g_Config.m_ClDummy] = *GetTuning(m_TuneZone); // throw tunings (from specific zone if in a tunezone) into gamecore if the character is local + GameWorld()->m_Core.m_aTuning[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); } @@ -1107,7 +1107,7 @@ void CCharacter::GiveAllWeapons() CTeamsCore *CCharacter::TeamsCore() { - return m_Core.m_pTeams; + return GameWorld()->Teams(); } CCharacter::CCharacter(CGameWorld *pGameWorld, int ID, CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtended) : @@ -1171,7 +1171,7 @@ void CCharacter::ResetPrediction() SetWeaponGot(w, false); SetWeaponAmmo(w, -1); } - if(m_Core.m_HookedPlayer >= 0) + if(m_Core.HookedPlayer() >= 0) { m_Core.SetHookedPlayer(-1); m_Core.m_HookState = HOOK_IDLE; @@ -1355,9 +1355,7 @@ void CCharacter::Read(CNetObj_Character *pChar, CNetObj_DDNetCharacter *pExtende void CCharacter::SetCoreWorld(CGameWorld *pGameWorld) { - m_Core.m_pWorld = &pGameWorld->m_Core; - m_Core.m_pCollision = pGameWorld->Collision(); - m_Core.m_pTeams = pGameWorld->Teams(); + m_Core.SetCoreWorld(&pGameWorld->m_Core, pGameWorld->Collision(), pGameWorld->Teams()); } bool CCharacter::Match(CCharacter *pChar) diff --git a/src/game/client/prediction/entity.h b/src/game/client/prediction/entity.h index 1083c6d33..364d0141b 100644 --- a/src/game/client/prediction/entity.h +++ b/src/game/client/prediction/entity.h @@ -3,33 +3,23 @@ #ifndef GAME_CLIENT_PREDICTION_ENTITY_H #define GAME_CLIENT_PREDICTION_ENTITY_H -#include "gameworld.h" #include -#define MACRO_ALLOC_HEAP() \ -public: \ - void *operator new(size_t Size) \ - { \ - void *p = malloc(Size); \ - mem_zero(p, Size); \ - return p; \ - } \ - void operator delete(void *pPtr) \ - { \ - free(pPtr); \ - } \ -\ -private: +#include + +#include "gameworld.h" class CEntity { MACRO_ALLOC_HEAP() - friend class CGameWorld; // entity list handling + +private: + friend CGameWorld; // entity list handling CEntity *m_pPrevTypeEntity; CEntity *m_pNextTypeEntity; protected: - class CGameWorld *m_pGameWorld; + CGameWorld *m_pGameWorld; bool m_MarkedForDestroy; int m_ID; int m_ObjType; @@ -41,7 +31,7 @@ public: virtual ~CEntity(); std::vector &Switchers() { return m_pGameWorld->Switchers(); } - class CGameWorld *GameWorld() { return m_pGameWorld; } + CGameWorld *GameWorld() { return m_pGameWorld; } CTuningParams *Tuning() { return GameWorld()->Tuning(); } CTuningParams *TuningList() { return GameWorld()->TuningList(); } CTuningParams *GetTuning(int i) { return GameWorld()->GetTuning(i); } diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index 63d250c22..05da81d55 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -182,11 +182,6 @@ void CGameWorld::RemoveEntities() } } -bool distCompare(std::pair a, std::pair b) -{ - return (a.first < b.first); -} - void CGameWorld::Tick() { // update all objects @@ -315,7 +310,7 @@ void CGameWorld::ReleaseHooked(int ClientID) for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) { CCharacterCore *pCore = pChr->Core(); - if(pCore->m_HookedPlayer == ClientID) + if(pCore->HookedPlayer() == ClientID) { pCore->SetHookedPlayer(-1); pCore->m_HookState = HOOK_RETRACTED; @@ -556,7 +551,7 @@ void CGameWorld::NetObjEnd() for(int i = 0; i < MAX_CLIENTS; i++) if(CCharacter *pChar = GetCharacterByID(i)) if(!pChar->m_MarkedForDestroy) - if(CCharacter *pHookedChar = GetCharacterByID(pChar->m_Core.m_HookedPlayer)) + if(CCharacter *pHookedChar = GetCharacterByID(pChar->m_Core.HookedPlayer())) if(pHookedChar->m_MarkedForDestroy) { pHookedChar->m_Pos = pHookedChar->m_Core.m_Pos = pChar->m_Core.m_HookPos; diff --git a/src/game/client/prediction/gameworld.h b/src/game/client/prediction/gameworld.h index 696956f8c..32d00dd78 100644 --- a/src/game/client/prediction/gameworld.h +++ b/src/game/client/prediction/gameworld.h @@ -15,8 +15,6 @@ class CEntity; class CGameWorld { - friend CCharacter; - public: enum { diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 14dc6a752..6022aa193 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -866,9 +866,14 @@ bool CUI::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float int CUI::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const std::function &GetTextLambda, const CUIRect *pRect, const SMenuButtonProperties &Props) { - CUIRect Text = *pRect; + CUIRect Text = *pRect, DropDownIcon; Text.HMargin(pRect->h >= 20.0f ? 2.0f : 1.0f, &Text); Text.HMargin((Text.h * Props.m_FontFactor) / 2.0f, &Text); + if(Props.m_ShowDropDownIcon) + { + Text.VSplitRight(pRect->h * 0.25f, &Text, nullptr); + Text.VSplitRight(pRect->h * 0.75f, &Text, &DropDownIcon); + } if(!UIElement.AreRectsInit() || Props.m_HintRequiresStringCheck || Props.m_HintCanChangePositionOrSize || !UIElement.Rect(0)->m_UITextContainer.Valid()) { @@ -946,6 +951,14 @@ int CUI::DoButton_Menu(CUIElement &UIElement, const CButtonContainer *pID, const Index = 1; Graphics()->TextureClear(); Graphics()->RenderQuadContainer(UIElement.Rect(Index)->m_UIRectQuadContainer, -1); + if(Props.m_ShowDropDownIcon) + { + TextRender()->SetFontPreset(EFontPreset::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); + DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + } ColorRGBA ColorText(TextRender()->DefaultTextColor()); ColorRGBA ColorTextOutline(TextRender()->DefaultTextOutlineColor()); if(UIElement.Rect(0)->m_UITextContainer.Valid()) @@ -1279,6 +1292,55 @@ void CUI::DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, *pOption = Value; } +void CUI::RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props) +{ + static float s_SpinnerOffset = 0.0f; + static float s_LastRender = Client()->LocalTime(); + s_SpinnerOffset += (Client()->LocalTime() - s_LastRender) * 1.5f; + s_SpinnerOffset = std::fmod(s_SpinnerOffset, 1.0f); + + Graphics()->TextureClear(); + Graphics()->QuadsBegin(); + + // The filled and unfilled segments need to begin at the same angle offset + // or the differences in pixel alignment will make the filled segments flicker. + const float SegmentsAngle = 2.0f * pi / Props.m_Segments; + const float InnerRadius = OuterRadius * 0.75f; + const float AngleOffset = -0.5f * pi; + Graphics()->SetColor(Props.m_Color.WithMultipliedAlpha(0.5f)); + for(int i = 0; i < Props.m_Segments; ++i) + { + const float Angle1 = AngleOffset + i * SegmentsAngle; + const float Angle2 = AngleOffset + (i + 1) * SegmentsAngle; + IGraphics::CFreeformItem Item = IGraphics::CFreeformItem( + Center.x + std::cos(Angle1) * InnerRadius, Center.y + std::sin(Angle1) * InnerRadius, + Center.x + std::cos(Angle2) * InnerRadius, Center.y + std::sin(Angle2) * InnerRadius, + Center.x + std::cos(Angle1) * OuterRadius, Center.y + std::sin(Angle1) * OuterRadius, + Center.x + std::cos(Angle2) * OuterRadius, Center.y + std::sin(Angle2) * OuterRadius); + Graphics()->QuadsDrawFreeform(&Item, 1); + } + + const float FilledRatio = Props.m_Progress < 0.0f ? 0.333f : Props.m_Progress; + const int FilledSegmentOffset = Props.m_Progress < 0.0f ? round_to_int(s_SpinnerOffset * Props.m_Segments) : 0; + const int FilledNumSegments = minimum(Props.m_Segments * FilledRatio + (Props.m_Progress < 0.0f ? 0 : 1), Props.m_Segments); + Graphics()->SetColor(Props.m_Color); + for(int i = 0; i < FilledNumSegments; ++i) + { + const float Angle1 = AngleOffset + (i + FilledSegmentOffset) * SegmentsAngle; + const float Angle2 = AngleOffset + ((i + 1 == FilledNumSegments && Props.m_Progress >= 0.0f) ? (2.0f * pi * Props.m_Progress) : ((i + FilledSegmentOffset + 1) * SegmentsAngle)); + IGraphics::CFreeformItem Item = IGraphics::CFreeformItem( + Center.x + std::cos(Angle1) * InnerRadius, Center.y + std::sin(Angle1) * InnerRadius, + Center.x + std::cos(Angle2) * InnerRadius, Center.y + std::sin(Angle2) * InnerRadius, + Center.x + std::cos(Angle1) * OuterRadius, Center.y + std::sin(Angle1) * OuterRadius, + Center.x + std::cos(Angle2) * OuterRadius, Center.y + std::sin(Angle2) * OuterRadius); + Graphics()->QuadsDrawFreeform(&Item, 1); + } + + Graphics()->QuadsEnd(); + + s_LastRender = Client()->LocalTime(); +} + void CUI::DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props) { constexpr float Margin = SPopupMenu::POPUP_BORDER + SPopupMenu::POPUP_MARGIN; @@ -1589,6 +1651,7 @@ int CUI::DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Nu SMenuButtonProperties Props; Props.m_HintRequiresStringCheck = true; Props.m_HintCanChangePositionOrSize = true; + Props.m_ShowDropDownIcon = true; if(IsPopupOpen(&State.m_SelectionPopupContext)) Props.m_Corners = IGraphics::CORNER_ALL & (~State.m_SelectionPopupContext.m_Props.m_Corners); if(DoButton_Menu(State.m_UiElement, &State.m_ButtonContainer, LabelFunc, pRect, Props)) @@ -1607,15 +1670,6 @@ int CUI::DoDropDown(CUIRect *pRect, int CurSelection, const char **pStrs, int Nu ShowPopupSelection(pRect->x, pRect->y, &State.m_SelectionPopupContext); } - CUIRect DropDownIcon; - pRect->HMargin(2.0f, &DropDownIcon); - DropDownIcon.VSplitRight(5.0f, &DropDownIcon, nullptr); - TextRender()->SetFontPreset(EFontPreset::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); - DoLabel(&DropDownIcon, FONT_ICON_CIRCLE_CHEVRON_DOWN, DropDownIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MR); - TextRender()->SetRenderFlags(0); - TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); - if(State.m_SelectionPopupContext.m_SelectionIndex >= 0) { const int NewSelection = State.m_SelectionPopupContext.m_SelectionIndex; diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 5a5b51bcb..ac26a640b 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -203,6 +203,7 @@ struct SMenuButtonProperties bool m_HintRequiresStringCheck = false; bool m_HintCanChangePositionOrSize = false; bool m_UseIconFont = false; + bool m_ShowDropDownIcon = false; int m_Corners = IGraphics::CORNER_ALL; float m_Rounding = 5.0f; float m_FontFactor = 0.0f; @@ -238,6 +239,13 @@ struct SValueSelectorProperties ColorRGBA m_Color = ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f); }; +struct SProgressSpinnerProperties +{ + float m_Progress = -1.0f; // between 0.0f and 1.0f, or negative for indeterminate progress + ColorRGBA m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + int m_Segments = 64; +}; + /** * Type safe UI ID for popup menus. */ @@ -511,6 +519,9 @@ public: float DoScrollbarH(const void *pID, const CUIRect *pRect, float Current, const ColorRGBA *pColorInner = nullptr); void DoScrollbarOption(const void *pID, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, const IScrollbarScale *pScale = &ms_LinearScrollbarScale, unsigned Flags = 0u, const char *pSuffix = ""); + // progress spinner + void RenderProgressSpinner(vec2 Center, float OuterRadius, const SProgressSpinnerProperties &Props = {}); + // popup menu void DoPopupMenu(const SPopupMenuId *pID, int X, int Y, int Width, int Height, void *pContext, FPopupMenuFunction pfnFunc, const SPopupMenuProperties &Props = {}); void RenderPopupMenus(); diff --git a/src/game/client/ui_listbox.cpp b/src/game/client/ui_listbox.cpp index 726e6c294..14cd6e65e 100644 --- a/src/game/client/ui_listbox.cpp +++ b/src/game/client/ui_listbox.cpp @@ -14,10 +14,11 @@ CListBox::CListBox() { m_ScrollOffset = vec2(0.0f, 0.0f); m_ListBoxUpdateScroll = false; - m_FilterOffset = 0.0f; + m_ScrollbarWidth = 20.0f; + m_ScrollbarMargin = 5.0f; m_HasHeader = false; m_AutoSpacing = 0.0f; - m_ScrollbarIsShown = false; + m_ScrollbarShown = false; m_Active = true; } @@ -118,6 +119,7 @@ void CListBox::DoStart(float RowHeight, int NumItems, int ItemsPerRow, int RowsP CScrollRegionParams ScrollParams; ScrollParams.m_Active = m_Active; ScrollParams.m_ScrollbarWidth = ScrollbarWidthMax(); + ScrollParams.m_ScrollbarMargin = ScrollbarMargin(); ScrollParams.m_ScrollUnit = (m_ListBoxRowHeight + m_AutoSpacing) * RowsPerScroll; ScrollParams.m_Flags = ForceShowScrollbar ? CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH : 0; m_ScrollRegion.Begin(&m_ListBoxView, &m_ScrollOffset, &ScrollParams); @@ -140,7 +142,7 @@ CListboxItem CListBox::DoNextRow() m_RowView.VSplitLeft(m_RowView.w / (m_ListBoxItemsPerRow - m_ListBoxItemIndex % m_ListBoxItemsPerRow), &Item.m_Rect, &m_RowView); Item.m_Selected = m_ListBoxSelectedIndex == m_ListBoxItemIndex; - Item.m_Visible = !m_ScrollRegion.IsRectClipped(Item.m_Rect); + Item.m_Visible = !m_ScrollRegion.RectClipped(Item.m_Rect); m_ListBoxItemIndex++; return Item; @@ -188,7 +190,7 @@ CListboxItem CListBox::DoNextItem(const void *pId, bool Selected) Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, m_Active ? 0.5f : 0.33f), IGraphics::CORNER_ALL, 5.0f); } - if(UI()->HotItem() == pId && !m_ScrollRegion.IsAnimating()) + if(UI()->HotItem() == pId && !m_ScrollRegion.Animating()) { Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f), IGraphics::CORNER_ALL, 5.0f); } @@ -208,7 +210,7 @@ int CListBox::DoEnd() m_ScrollRegion.End(); m_Active |= m_ScrollRegion.Params().m_Active; - m_ScrollbarIsShown = m_ScrollRegion.IsScrollbarShown(); + m_ScrollbarShown = m_ScrollRegion.ScrollbarShown(); if(m_ListBoxNewSelOffset != 0 && m_ListBoxNumItems > 0 && m_ListBoxSelectedIndex == m_ListBoxNewSelected) { m_ListBoxNewSelected = clamp((m_ListBoxNewSelected == -1 ? 0 : m_ListBoxNewSelected) + m_ListBoxNewSelOffset, 0, m_ListBoxNumItems - 1); diff --git a/src/game/client/ui_listbox.h b/src/game/client/ui_listbox.h index e50aa246a..1e2b9af37 100644 --- a/src/game/client/ui_listbox.h +++ b/src/game/client/ui_listbox.h @@ -29,14 +29,15 @@ private: int m_ListBoxItemsPerRow; bool m_ListBoxItemSelected; bool m_ListBoxItemActivated; - bool m_ScrollbarIsShown; + bool m_ScrollbarShown; const char *m_pBottomText; float m_FooterHeight; float m_AutoSpacing; CScrollRegion m_ScrollRegion; vec2 m_ScrollOffset; - float m_FilterOffset; int m_BackgroundCorners; + float m_ScrollbarWidth; + float m_ScrollbarMargin; bool m_HasHeader; bool m_Active; @@ -64,9 +65,12 @@ public: bool WasItemSelected() const { return m_ListBoxItemSelected; } bool WasItemActivated() const { return m_ListBoxItemActivated; } - bool ScrollbarIsShown() const { return m_ScrollbarIsShown; } - float ScrollbarWidth() const { return ScrollbarIsShown() ? ScrollbarWidthMax() : 0.0f; } - float ScrollbarWidthMax() const { return 20.0f; } + bool ScrollbarShown() const { return m_ScrollbarShown; } + float ScrollbarWidth() const { return ScrollbarShown() ? ScrollbarWidthMax() : 0.0f; } + float ScrollbarWidthMax() const { return m_ScrollbarWidth; } + void SetScrollbarWidth(float Width) { m_ScrollbarWidth = Width; } + float ScrollbarMargin() const { return m_ScrollbarMargin; } + void SetScrollbarMargin(float Margin) { m_ScrollbarMargin = Margin; } }; #endif diff --git a/src/game/client/ui_rect.h b/src/game/client/ui_rect.h index 5fb964e65..18143d1fd 100644 --- a/src/game/client/ui_rect.h +++ b/src/game/client/ui_rect.h @@ -113,6 +113,8 @@ public: void Draw(ColorRGBA Color, int Corners, float Rounding) const; void Draw4(ColorRGBA ColorTopLeft, ColorRGBA ColorTopRight, ColorRGBA ColorBottomLeft, ColorRGBA ColorBottomRight, int Corners, float Rounding) const; + + vec2 Center() const { return vec2(x + w / 2.0f, y + h / 2.0f); } }; #endif diff --git a/src/game/client/ui_scrollregion.cpp b/src/game/client/ui_scrollregion.cpp index 885f63fec..37ec86143 100644 --- a/src/game/client/ui_scrollregion.cpp +++ b/src/game/client/ui_scrollregion.cpp @@ -193,7 +193,7 @@ bool CScrollRegion::AddRect(const CUIRect &Rect, bool ShouldScrollHere) m_ContentH = maximum(std::ceil(Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + HEIGHT_MAGIC_FIX, m_ContentH); if(ShouldScrollHere) ScrollHere(); - return !IsRectClipped(Rect); + return !RectClipped(Rect); } void CScrollRegion::ScrollHere(EScrollOption Option) @@ -230,7 +230,7 @@ void CScrollRegion::ScrollRelative(EScrollRelative Direction, float SpeedMultipl void CScrollRegion::DoEdgeScrolling() { - if(!IsScrollbarShown()) + if(!ScrollbarShown()) return; const float ScrollBorderSize = 20.0f; @@ -244,17 +244,17 @@ void CScrollRegion::DoEdgeScrolling() ScrollRelative(SCROLLRELATIVE_DOWN, minimum(MaxScrollMultiplier, (UI()->MouseY() - BottomScrollPosition) * ScrollSpeedFactor)); } -bool CScrollRegion::IsRectClipped(const CUIRect &Rect) const +bool CScrollRegion::RectClipped(const CUIRect &Rect) const { return (m_ClipRect.x > (Rect.x + Rect.w) || (m_ClipRect.x + m_ClipRect.w) < Rect.x || m_ClipRect.y > (Rect.y + Rect.h) || (m_ClipRect.y + m_ClipRect.h) < Rect.y); } -bool CScrollRegion::IsScrollbarShown() const +bool CScrollRegion::ScrollbarShown() const { return m_ContentH > m_ClipRect.h; } -bool CScrollRegion::IsAnimating() const +bool CScrollRegion::Animating() const { return m_AnimTime > 0.0f; } diff --git a/src/game/client/ui_scrollregion.h b/src/game/client/ui_scrollregion.h index 3ade665e9..f196fb61c 100644 --- a/src/game/client/ui_scrollregion.h +++ b/src/game/client/ui_scrollregion.h @@ -68,7 +68,7 @@ Usage: s_ScrollRegion.AddRect(Rect); -- [Optional] Knowing if a rect is clipped -- - s_ScrollRegion.IsRectClipped(Rect); + s_ScrollRegion.RectClipped(Rect); -- [Optional] Scroll to a rect (to the last added rect)-- ... @@ -137,9 +137,9 @@ public: void ScrollRelative(EScrollRelative Direction, float SpeedMultiplier = 1.0f); const CUIRect *ClipRect() const { return &m_ClipRect; } void DoEdgeScrolling(); - bool IsRectClipped(const CUIRect &Rect) const; - bool IsScrollbarShown() const; - bool IsAnimating() const; + bool RectClipped(const CUIRect &Rect) const; + bool ScrollbarShown() const; + bool Animating() const; const CScrollRegionParams &Params() const { return m_Params; } }; diff --git a/src/game/ddracecommands.h b/src/game/ddracecommands.h index 29938c983..36b30be20 100644 --- a/src/game/ddracecommands.h +++ b/src/game/ddracecommands.h @@ -25,11 +25,14 @@ CONSOLE_COMMAND("unrifle", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnLaser, this, CONSOLE_COMMAND("unjetpack", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnJetpack, this, "Removes the jetpack from you") CONSOLE_COMMAND("unweapons", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnWeapons, this, "Removes all weapons from you") CONSOLE_COMMAND("ninja", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConNinja, this, "Makes you a ninja") +CONSOLE_COMMAND("unninja", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnNinja, this, "Removes ninja from you") CONSOLE_COMMAND("super", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConSuper, this, "Makes you super") CONSOLE_COMMAND("unsuper", "", CFGFLAG_SERVER, ConUnSuper, this, "Removes super from you") CONSOLE_COMMAND("endless_hook", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConEndlessHook, this, "Gives you endless hook") CONSOLE_COMMAND("unendless_hook", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnEndlessHook, this, "Removes endless hook from you") +CONSOLE_COMMAND("solo", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConSolo, this, "Puts you into solo part") CONSOLE_COMMAND("unsolo", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnSolo, this, "Puts you out of solo part") +CONSOLE_COMMAND("deep", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConDeep, this, "Puts you into deep freeze") CONSOLE_COMMAND("undeep", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnDeep, this, "Puts you out of deep freeze") CONSOLE_COMMAND("livefreeze", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConLiveFreeze, this, "Makes you live frozen") CONSOLE_COMMAND("unlivefreeze", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConUnLiveFreeze, this, "Puts you out of live freeze") diff --git a/src/game/editor/auto_map.cpp b/src/game/editor/auto_map.cpp index d339a2601..831be5b6d 100644 --- a/src/game/editor/auto_map.cpp +++ b/src/game/editor/auto_map.cpp @@ -5,8 +5,10 @@ #include #include +#include + #include "auto_map.h" -#include "editor.h" +#include "editor.h" // TODO: only needs CLayerTiles // Based on triple32inc from https://github.com/skeeto/hash-prospector/tree/79a6074062a84907df6e45b756134b74e2956760 static uint32_t HashUInt32(uint32_t Num) @@ -39,17 +41,21 @@ static int HashLocation(uint32_t Seed, uint32_t Run, uint32_t Rule, uint32_t X, CAutoMapper::CAutoMapper(CEditor *pEditor) { - m_pEditor = pEditor; - m_FileLoaded = false; + Init(pEditor); } void CAutoMapper::Load(const char *pTileName) { char aPath[IO_MAX_PATH_LENGTH]; - str_format(aPath, sizeof(aPath), "editor/%s.rules", pTileName); - IOHANDLE RulesFile = m_pEditor->Storage()->OpenFile(aPath, IOFLAG_READ | IOFLAG_SKIP_BOM, IStorage::TYPE_ALL); + str_format(aPath, sizeof(aPath), "editor/automap/%s.rules", pTileName); + IOHANDLE RulesFile = Storage()->OpenFile(aPath, IOFLAG_READ | IOFLAG_SKIP_BOM, IStorage::TYPE_ALL); if(!RulesFile) + { + char aBuf[IO_MAX_PATH_LENGTH + 32]; + str_format(aBuf, sizeof(aBuf), "failed to load %s", aPath); + Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor/automap", aBuf); return; + } CLineReader LineReader; LineReader.Init(RulesFile); @@ -362,7 +368,7 @@ void CAutoMapper::Load(const char *pTileName) char aBuf[IO_MAX_PATH_LENGTH + 16]; str_format(aBuf, sizeof(aBuf), "loaded %s", aPath); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor/automap", aBuf); + Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "editor/automap", aBuf); m_FileLoaded = true; } @@ -470,7 +476,7 @@ void CAutoMapper::Proceed(CLayerTiles *pLayer, int ConfigID, int Seed, int SeedO for(int x = 0; x < pLayer->m_Width; x++) { CTile *pTile = &(pLayer->m_pTiles[y * pLayer->m_Width + x]); - m_pEditor->m_Map.OnModify(); + Editor()->m_Map.OnModify(); for(size_t i = 0; i < pRun->m_vIndexRules.size(); ++i) { diff --git a/src/game/editor/auto_map.h b/src/game/editor/auto_map.h index b5fd6cc4d..4e998af72 100644 --- a/src/game/editor/auto_map.h +++ b/src/game/editor/auto_map.h @@ -3,7 +3,9 @@ #include -class CAutoMapper +#include "component.h" + +class CAutoMapper : public CEditorComponent { struct CIndexInfo { @@ -55,7 +57,7 @@ class CAutoMapper }; public: - CAutoMapper(class CEditor *pEditor); + explicit CAutoMapper(CEditor *pEditor); void Load(const char *pTileName); void ProceedLocalized(class CLayerTiles *pLayer, int ConfigID, int Seed = 0, int X = 0, int Y = 0, int Width = -1, int Height = -1); @@ -67,9 +69,8 @@ public: bool IsLoaded() const { return m_FileLoaded; } private: - std::vector m_vConfigs; - class CEditor *m_pEditor; - bool m_FileLoaded; + std::vector m_vConfigs = {}; + bool m_FileLoaded = false; }; #endif diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index bff0bfd14..6cefe7a49 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -31,6 +31,9 @@ #include #include +#include +#include + #include "auto_map.h" #include "editor.h" @@ -87,206 +90,6 @@ enum BUTTON_CONTEXT = 1, }; -CEditorImage::~CEditorImage() -{ - m_pEditor->Graphics()->UnloadTexture(&m_Texture); - free(m_pData); - m_pData = nullptr; -} - -CEditorSound::~CEditorSound() -{ - m_pEditor->Sound()->UnloadSample(m_SoundID); - free(m_pData); - m_pData = nullptr; -} - -CLayerGroup::CLayerGroup() -{ - m_vpLayers.clear(); - m_aName[0] = 0; - m_Visible = true; - m_Collapse = false; - m_GameGroup = false; - m_OffsetX = 0; - m_OffsetY = 0; - m_ParallaxX = 100; - m_ParallaxY = 100; - m_CustomParallaxZoom = 0; - m_ParallaxZoom = 100; - - m_UseClipping = 0; - m_ClipX = 0; - m_ClipY = 0; - m_ClipW = 0; - m_ClipH = 0; -} - -CLayerGroup::~CLayerGroup() -{ - m_vpLayers.clear(); -} - -void CLayerGroup::Convert(CUIRect *pRect) -{ - pRect->x += m_OffsetX; - pRect->y += m_OffsetY; -} - -void CLayerGroup::Mapping(float *pPoints) -{ - float ParallaxZoom = m_pMap->m_pEditor->m_PreviewZoom ? m_ParallaxZoom : 100.0f; - - m_pMap->m_pEditor->RenderTools()->MapScreenToWorld( - m_pMap->m_pEditor->MapView()->GetWorldOffset().x, m_pMap->m_pEditor->MapView()->GetWorldOffset().y, - m_ParallaxX, m_ParallaxY, ParallaxZoom, m_OffsetX, m_OffsetY, - m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->MapView()->WorldZoom(), pPoints); - - pPoints[0] += m_pMap->m_pEditor->MapView()->GetEditorOffset().x; - pPoints[1] += m_pMap->m_pEditor->MapView()->GetEditorOffset().y; - pPoints[2] += m_pMap->m_pEditor->MapView()->GetEditorOffset().x; - pPoints[3] += m_pMap->m_pEditor->MapView()->GetEditorOffset().y; -} - -void CLayerGroup::MapScreen() -{ - float aPoints[4]; - Mapping(aPoints); - m_pMap->m_pEditor->Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); -} - -void CLayerGroup::Render() -{ - MapScreen(); - IGraphics *pGraphics = m_pMap->m_pEditor->Graphics(); - - if(m_UseClipping) - { - float aPoints[4]; - m_pMap->m_pGameGroup->Mapping(aPoints); - float x0 = (m_ClipX - aPoints[0]) / (aPoints[2] - aPoints[0]); - float y0 = (m_ClipY - aPoints[1]) / (aPoints[3] - aPoints[1]); - float x1 = ((m_ClipX + m_ClipW) - aPoints[0]) / (aPoints[2] - aPoints[0]); - float y1 = ((m_ClipY + m_ClipH) - aPoints[1]) / (aPoints[3] - aPoints[1]); - - pGraphics->ClipEnable((int)(x0 * pGraphics->ScreenWidth()), (int)(y0 * pGraphics->ScreenHeight()), - (int)((x1 - x0) * pGraphics->ScreenWidth()), (int)((y1 - y0) * pGraphics->ScreenHeight())); - } - - for(auto &pLayer : m_vpLayers) - { - if(pLayer->m_Visible) - { - if(pLayer->m_Type == LAYERTYPE_TILES) - { - std::shared_ptr pTiles = std::static_pointer_cast(pLayer); - if(pTiles->m_Game || pTiles->m_Front || pTiles->m_Tele || pTiles->m_Speedup || pTiles->m_Tune || pTiles->m_Switch) - continue; - } - if(m_pMap->m_pEditor->m_ShowDetail || !(pLayer->m_Flags & LAYERFLAG_DETAIL)) - pLayer->Render(); - } - } - - for(auto &pLayer : m_vpLayers) - { - if(pLayer->m_Visible && pLayer->m_Type == LAYERTYPE_TILES && pLayer != m_pMap->m_pGameLayer && pLayer != m_pMap->m_pFrontLayer && pLayer != m_pMap->m_pTeleLayer && pLayer != m_pMap->m_pSpeedupLayer && pLayer != m_pMap->m_pSwitchLayer && pLayer != m_pMap->m_pTuneLayer) - { - std::shared_ptr pTiles = std::static_pointer_cast(pLayer); - if(pTiles->m_Game || pTiles->m_Front || pTiles->m_Tele || pTiles->m_Speedup || pTiles->m_Tune || pTiles->m_Switch) - { - pLayer->Render(); - } - } - } - - if(m_UseClipping) - pGraphics->ClipDisable(); -} - -void CLayerGroup::AddLayer(const std::shared_ptr &pLayer) -{ - m_pMap->OnModify(); - m_vpLayers.push_back(pLayer); -} - -void CLayerGroup::DeleteLayer(int Index) -{ - if(Index < 0 || Index >= (int)m_vpLayers.size()) - return; - m_vpLayers.erase(m_vpLayers.begin() + Index); - m_pMap->OnModify(); -} - -void CLayerGroup::DuplicateLayer(int Index) -{ - if(Index < 0 || Index >= (int)m_vpLayers.size()) - return; - - std::shared_ptr pDup = m_vpLayers[Index]->Duplicate(); - m_vpLayers.insert(m_vpLayers.begin() + Index + 1, pDup); - - m_pMap->OnModify(); -} - -void CLayerGroup::GetSize(float *pWidth, float *pHeight) const -{ - *pWidth = 0; - *pHeight = 0; - for(const auto &pLayer : m_vpLayers) - { - float lw, lh; - pLayer->GetSize(&lw, &lh); - *pWidth = maximum(*pWidth, lw); - *pHeight = maximum(*pHeight, lh); - } -} - -int CLayerGroup::SwapLayers(int Index0, int Index1) -{ - if(Index0 < 0 || Index0 >= (int)m_vpLayers.size()) - return Index0; - if(Index1 < 0 || Index1 >= (int)m_vpLayers.size()) - return Index0; - if(Index0 == Index1) - return Index0; - m_pMap->OnModify(); - std::swap(m_vpLayers[Index0], m_vpLayers[Index1]); - return Index1; -} - -void CEditorImage::AnalyseTileFlags() -{ - mem_zero(m_aTileFlags, sizeof(m_aTileFlags)); - - int tw = m_Width / 16; // tilesizes - int th = m_Height / 16; - if(tw == th && m_Format == CImageInfo::FORMAT_RGBA) - { - unsigned char *pPixelData = (unsigned char *)m_pData; - - int TileID = 0; - for(int ty = 0; ty < 16; ty++) - for(int tx = 0; tx < 16; tx++, TileID++) - { - bool Opaque = true; - for(int x = 0; x < tw; x++) - for(int y = 0; y < th; y++) - { - int p = (ty * tw + y) * m_Width + tx * tw + x; - if(pPixelData[p * 4 + 3] < 250) - { - Opaque = false; - break; - } - } - - if(Opaque) - m_aTileFlags[TileID] |= TILEFLAG_OPAQUE; - } - } -} - void CEditor::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser) { CEditor *pThis = (CEditor *)pUser; @@ -685,18 +488,6 @@ std::vector CEditor::GetSelectedQuads() return vpQuads; } -std::vector> CEditor::GetSelectedQuadPoints() -{ - std::shared_ptr pQuadLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); - std::vector> vpQuads; - if(!pQuadLayer) - return vpQuads; - vpQuads.reserve(m_vSelectedQuadPoints.size()); - for(auto [QuadIndex, SelectedPoints] : m_vSelectedQuadPoints) - vpQuads.emplace_back(&pQuadLayer->m_vQuads[QuadIndex], SelectedPoints); - return vpQuads; -} - CSoundSource *CEditor::GetSelectedSource() { std::shared_ptr pSounds = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_SOUNDS)); @@ -713,8 +504,8 @@ void CEditor::SelectLayer(int LayerIndex, int GroupIndex) m_SelectedGroup = GroupIndex; m_vSelectedLayers.clear(); - m_vSelectedQuads.clear(); - m_vSelectedQuadPoints.clear(); + DeselectQuads(); + DeselectQuadPoints(); AddSelectedLayer(LayerIndex); } @@ -747,27 +538,17 @@ void CEditor::DeselectQuads() void CEditor::DeselectQuadPoints() { - m_vSelectedQuadPoints.clear(); + m_SelectedQuadPoints = 0; } -void CEditor::SelectQuadPoint(int QuadIndex, int Index) +void CEditor::SelectQuadPoint(int Index) { - m_vSelectedQuadPoints.clear(); - m_vSelectedQuadPoints.emplace_back(QuadIndex, 1 << Index); + m_SelectedQuadPoints = 1 << Index; } -void CEditor::ToggleSelectQuadPoint(int QuadIndex, int Index) +void CEditor::ToggleSelectQuadPoint(int Index) { - int ListIndex = FindSelectedQuadPointIndex(QuadIndex); - - if(ListIndex >= 0) - { - m_vSelectedQuadPoints.at(ListIndex).second ^= 1 << Index; - if(m_vSelectedQuadPoints.at(ListIndex).second == 0) - m_vSelectedQuadPoints.erase(m_vSelectedQuadPoints.begin() + ListIndex); - } - else - m_vSelectedQuadPoints.emplace_back(QuadIndex, 1 << Index); + m_SelectedQuadPoints ^= 1 << Index; } void CEditor::DeleteSelectedQuads() @@ -783,13 +564,7 @@ void CEditor::DeleteSelectedQuads() if(m_vSelectedQuads[j] > m_vSelectedQuads[i]) m_vSelectedQuads[j]--; - int QuadIndex = m_vSelectedQuads.at(i); m_vSelectedQuads.erase(m_vSelectedQuads.begin() + i); - - int ListIndex = FindSelectedQuadPointIndex(QuadIndex); - if(ListIndex >= 0) - m_vSelectedQuadPoints.erase(m_vSelectedQuadPoints.begin() + ListIndex); - i--; } } @@ -799,10 +574,9 @@ bool CEditor::IsQuadSelected(int Index) const return FindSelectedQuadIndex(Index) >= 0; } -bool CEditor::IsQuadPointSelected(int QuadIndex, int Index) const +bool CEditor::IsQuadPointSelected(int Index) const { - int ListIndex = FindSelectedQuadPointIndex(QuadIndex); - return ListIndex >= 0 && (m_vSelectedQuadPoints.at(ListIndex).second & (1 << Index)); + return m_SelectedQuadPoints & (1 << Index); } int CEditor::FindSelectedQuadIndex(int Index) const @@ -813,19 +587,6 @@ int CEditor::FindSelectedQuadIndex(int Index) const return -1; } -int CEditor::FindSelectedQuadPointIndex(int QuadIndex) const -{ - auto Iter = std::find_if( - m_vSelectedQuadPoints.begin(), - m_vSelectedQuadPoints.end(), - [QuadIndex](auto Pair) { return Pair.first == QuadIndex; }); - - if(Iter != m_vSelectedQuadPoints.end()) - return Iter - m_vSelectedQuadPoints.begin(); - else - return -1; -} - int CEditor::FindEnvPointIndex(int Index, int Channel) const { auto Iter = std::find( @@ -1580,7 +1341,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) if(!IsQuadSelected(Index)) SelectQuad(Index); - if(Input()->ModifierIsPressed()) + if(Input()->ShiftIsPressed()) s_Operation = OP_MOVE_PIVOT; else s_Operation = OP_MOVE_ALL; @@ -1722,7 +1483,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) Graphics()->SetColor(1, 1, 1, 1); } - else if(Input()->KeyPress(KEY_R) && !m_vSelectedQuads.empty()) + else if(Input()->KeyPress(KEY_R) && !m_vSelectedQuads.empty() && m_Dialog == DIALOG_NONE) { UI()->EnableMouseLock(pID); UI()->SetActiveItem(pID); @@ -1748,7 +1509,7 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) ms_pUiGotContext = pID; Graphics()->SetColor(1, 1, 1, 1); - m_pTooltip = "Left mouse button to move. Hold ctrl to move pivot. Hold alt to ignore grid. Hold shift and right click to delete."; + m_pTooltip = "Left mouse button to move. Hold shift to move pivot. Hold alt to ignore grid. Hold shift and right click to delete."; if(UI()->MouseButton(0)) { @@ -1784,49 +1545,6 @@ void CEditor::DoQuad(CQuad *pQuad, int Index) else Graphics()->SetColor(0, 1, 0, 1); - // Handle copy/paste operations - static bool s_Pasted = false; - if(Input()->KeyPress(KEY_C) && Input()->ModifierIsPressed()) - { - m_vCopyBuffer.clear(); - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); - for(int Selected : m_vSelectedQuads) - m_vCopyBuffer.push_back(pLayer->m_vQuads[Selected]); - } - else if(Input()->KeyPress(KEY_V) && Input()->ModifierIsPressed() && !s_Pasted && !m_vCopyBuffer.empty()) - { - std::shared_ptr pLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); - - std::shared_ptr pLayerQuads = std::make_shared(); - pLayerQuads->m_pEditor = pLayer->m_pEditor; - pLayerQuads->m_Image = pLayer->m_Image; - - int MinX = m_vCopyBuffer.front().m_aPoints[4].x; - int MinY = m_vCopyBuffer.front().m_aPoints[4].y; - for(auto Quad : m_vCopyBuffer) - { - MinX = minimum(MinX, Quad.m_aPoints[4].x); - MinY = minimum(MinY, Quad.m_aPoints[4].y); - } - for(auto Quad : m_vCopyBuffer) - { - for(auto &Point : Quad.m_aPoints) - { - Point.x -= MinX; - Point.y -= MinY; - } - pLayerQuads->m_vQuads.push_back(Quad); - } - - m_pBrush->Clear(); - m_pBrush->AddLayer(pLayerQuads); - - DeselectQuads(); - DeselectQuadPoints(); - } - else if(!Input()->ModifierIsPressed()) - s_Pasted = false; - IGraphics::CQuadItem QuadItem(CenterX, CenterY, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale); Graphics()->QuadsDraw(&QuadItem, 1); } @@ -1842,7 +1560,7 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) float py = fx2f(pQuad->m_aPoints[V].y); // draw selection background - if(IsQuadPointSelected(QuadIndex, V)) + if(IsQuadPointSelected(V)) { Graphics()->SetColor(0, 0, 0, 1); IGraphics::CQuadItem QuadItem(px, py, 7.0f * m_MouseWScale, 7.0f * m_MouseWScale); @@ -1875,8 +1593,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) if(x * x + y * y > 20.0f) { - if(!IsQuadPointSelected(QuadIndex, V)) - SelectQuadPoint(QuadIndex, V); + if(!IsQuadPointSelected(V)) + SelectQuadPoint(V); if(Input()->ShiftIsPressed()) { @@ -1898,34 +1616,30 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) int OffsetX = f2fx(x) - pQuad->m_aPoints[V].x; int OffsetY = f2fx(y) - pQuad->m_aPoints[V].y; - for(auto [pSelectedQuad, SelectedPoints] : GetSelectedQuadPoints()) + for(int m = 0; m < 4; m++) { - for(int m = 0; m < 4; m++) + if(IsQuadPointSelected(m)) { - if(SelectedPoints & (1 << m)) - { - pSelectedQuad->m_aPoints[m].x += OffsetX; - pSelectedQuad->m_aPoints[m].y += OffsetY; - } + pQuad->m_aPoints[m].x += OffsetX; + pQuad->m_aPoints[m].y += OffsetY; } } } else if(s_Operation == OP_MOVEUV) { - for(auto [pSelectedQuad, SelectedPoints] : GetSelectedQuadPoints()) + for(int m = 0; m < 4; m++) { - for(int m = 0; m < 4; m++) - if(SelectedPoints & (1 << m)) - { - // 0,2;1,3 - line x - // 0,1;2,3 - line y + if(IsQuadPointSelected(m)) + { + // 0,2;1,3 - line x + // 0,1;2,3 - line y - pSelectedQuad->m_aTexcoords[m].x += f2fx(m_MouseDeltaWx * 0.001f); - pSelectedQuad->m_aTexcoords[(m + 2) % 4].x += f2fx(m_MouseDeltaWx * 0.001f); + pQuad->m_aTexcoords[m].x += f2fx(m_MouseDeltaWx * 0.001f); + pQuad->m_aTexcoords[(m + 2) % 4].x += f2fx(m_MouseDeltaWx * 0.001f); - pSelectedQuad->m_aTexcoords[m].y += f2fx(m_MouseDeltaWy * 0.001f); - pSelectedQuad->m_aTexcoords[m ^ 1].y += f2fx(m_MouseDeltaWy * 0.001f); - } + pQuad->m_aTexcoords[m].y += f2fx(m_MouseDeltaWy * 0.001f); + pQuad->m_aTexcoords[m ^ 1].y += f2fx(m_MouseDeltaWy * 0.001f); + } } } } @@ -1936,8 +1650,11 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) { if(m_vSelectedLayers.size() == 1) { + if(!IsQuadSelected(QuadIndex)) + SelectQuad(QuadIndex); + m_SelectedQuadPoint = V; - m_SelectedQuadIndex = FindSelectedQuadPointIndex(QuadIndex); + m_SelectedQuadIndex = FindSelectedQuadIndex(QuadIndex); static SPopupMenuId s_PopupPointId; UI()->DoPopupMenu(&s_PopupPointId, UI()->MouseX(), UI()->MouseY(), 120, 75, this, PopupPoint); @@ -1952,9 +1669,9 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) if(s_Operation == OP_SELECT) { if(Input()->ShiftIsPressed()) - ToggleSelectQuadPoint(QuadIndex, V); + ToggleSelectQuadPoint(V); else - SelectQuadPoint(QuadIndex, V); + SelectQuadPoint(V); } UI()->DisableMouseLock(); @@ -1986,8 +1703,8 @@ void CEditor::DoQuadPoint(CQuad *pQuad, int QuadIndex, int V) UI()->SetActiveItem(pID); - if(!IsQuadPointSelected(QuadIndex, V)) - SelectQuadPoint(QuadIndex, V); + if(!IsQuadPointSelected(V)) + SelectQuadPoint(V); } } else @@ -2672,7 +2389,7 @@ void CEditor::DoMapEditor(CUIRect View) m_pTooltip = Explain(Explanation, (int)wx / 32 + (int)wy / 32 * 16, Layer); } else if(m_pBrush->IsEmpty() && GetSelectedLayerType(0, LAYERTYPE_QUADS) != nullptr) - m_pTooltip = "Hold shift to select multiple quads. Use ctrl + c and ctrl + v to copy and paste quads. Press R to rotate selected quads."; + m_pTooltip = "Use left mouse button to drag and create a brush. Hold shift to select multiple quads. Press R to rotate selected quads. Use ctrl+right mouse to select layer."; else if(m_pBrush->IsEmpty()) m_pTooltip = "Use left mouse button to drag and create a brush. Use ctrl+right mouse to select layer."; else @@ -2728,31 +2445,16 @@ void CEditor::DoMapEditor(CUIRect View) if(!UI()->MouseButton(0)) { std::shared_ptr pQuadLayer = std::static_pointer_cast(GetSelectedLayerType(0, LAYERTYPE_QUADS)); - if(pQuadLayer) + if(Input()->ShiftIsPressed() && pQuadLayer) { - if(!Input()->ShiftIsPressed()) - { - DeselectQuads(); - DeselectQuadPoints(); - } - for(size_t i = 0; i < pQuadLayer->m_vQuads.size(); i++) { - CQuad *pQuad = &pQuadLayer->m_vQuads[i]; + const CQuad &Quad = pQuadLayer->m_vQuads[i]; + float px = fx2f(Quad.m_aPoints[4].x); + float py = fx2f(Quad.m_aPoints[4].y); - for(size_t v = 0; v < 5; v++) - { - float px = fx2f(pQuad->m_aPoints[v].x); - float py = fx2f(pQuad->m_aPoints[v].y); - - if(px > r.x && px < r.x + r.w && py > r.y && py < r.y + r.h) - { - if(v == 4) - ToggleSelectQuad(i); - else - ToggleSelectQuadPoint(i, v); - } - } + if(r.Inside(px, py) && !IsQuadSelected(i)) + SelectQuad(i); } } else @@ -3214,11 +2916,7 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * } else if(pProps[i].m_Type == PROPTYPE_COLOR) { - const ColorRGBA ColorPick = ColorRGBA( - ((pProps[i].m_Value >> 24) & 0xff) / 255.0f, - ((pProps[i].m_Value >> 16) & 0xff) / 255.0f, - ((pProps[i].m_Value >> 8) & 0xff) / 255.0f, - (pProps[i].m_Value & 0xff) / 255.0f); + const ColorRGBA ColorPick = ColorRGBA::UnpackAlphaLast(pProps[i].m_Value); CUIRect ColorRect; Shifter.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f * UI()->ButtonColorMul(&pIDs[i])), IGraphics::CORNER_ALL, 5.0f); @@ -3260,7 +2958,7 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int * else if(UI()->IsPopupOpen(&s_ColorPickerPopupContext)) { ColorRGBA c = color_cast(s_ColorPickerPopupContext.m_HsvaColor); - const int NewColor = ((int)(c.r * 255.0f) & 0xff) << 24 | ((int)(c.g * 255.0f) & 0xff) << 16 | ((int)(c.b * 255.0f) & 0xff) << 8 | ((int)(c.a * 255.0f) & 0xff); + const int NewColor = c.PackAlphaLast(s_ColorPickerPopupContext.m_Alpha); if(NewColor != pProps[i].m_Value) { *pNewVal = NewColor; @@ -4057,18 +3755,14 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup if(!pImg->m_External && g_Config.m_ClEditorDilate == 1 && pImg->m_Format == CImageInfo::FORMAT_RGBA) { - int ColorChannelCount = 0; - if(ImgInfo.m_Format == CImageInfo::FORMAT_RGBA) - ColorChannelCount = 4; - - DilateImage((unsigned char *)ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height, ColorChannelCount); + DilateImage((unsigned char *)ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.PixelSize()); } pImg->m_AutoMapper.Load(pImg->m_aName); int TextureLoadFlag = Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) TextureLoadFlag = 0; - pImg->m_Texture = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, TextureLoadFlag, pFileName); + pImg->m_Texture = Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, TextureLoadFlag, pFileName); ImgInfo.m_pData = nullptr; SortImages(); for(size_t i = 0; i < m_Map.m_vpImages.size(); ++i) @@ -4121,17 +3815,13 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser) if(!pImg->m_External && g_Config.m_ClEditorDilate == 1 && pImg->m_Format == CImageInfo::FORMAT_RGBA) { - int ColorChannelCount = 0; - if(ImgInfo.m_Format == CImageInfo::FORMAT_RGBA) - ColorChannelCount = 4; - - DilateImage((unsigned char *)ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height, ColorChannelCount); + DilateImage((unsigned char *)ImgInfo.m_pData, ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.PixelSize()); } int TextureLoadFlag = pEditor->Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) TextureLoadFlag = 0; - pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, TextureLoadFlag, pFileName); + pImg->m_Texture = pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, TextureLoadFlag, pFileName); ImgInfo.m_pData = nullptr; str_copy(pImg->m_aName, aBuf); pImg->m_AutoMapper.Load(pImg->m_aName); @@ -4765,8 +4455,11 @@ void CEditor::RenderFileDialog() { // render search bar UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, TEXTALIGN_ML); - if(m_FileDialogOpening) + if(m_FileDialogOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())) + { UI()->SetActiveItem(&m_FileDialogFilterInput); + m_FileDialogFilterInput.SelectAll(); + } if(UI()->DoClearableEditBox(&m_FileDialogFilterInput, &FileBox, 10.0f)) { RefreshFilteredFileList(); @@ -4815,7 +4508,7 @@ void CEditor::RenderFileDialog() if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType)) { Graphics()->UnloadTexture(&m_FilePreviewImage); - m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, 0); + m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, 0); Graphics()->FreePNG(&m_FilePreviewImageInfo); m_FilePreviewState = PREVIEW_LOADED; } @@ -4989,6 +4682,7 @@ void CEditor::RenderFileDialog() if(IsDir) // folder { m_FileDialogFilterInput.Clear(); + UI()->SetActiveItem(&m_FileDialogFilterInput); const bool ParentFolder = str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0; if(ParentFolder) // parent folder { @@ -6001,7 +5695,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(40.0f, &Button, &ToolBar); UI()->DoLabel(&Button, "Sync.", 10.0f, TEXTALIGN_ML); - if(UI()->MouseInside(&View)) + if(UI()->MouseInside(&View) && m_Dialog == DIALOG_NONE) { UI()->SetHotItem(&s_EnvelopeEditorID); @@ -7208,7 +6902,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar) if(DoButton_Menu(&s_ToolsButton, "Tools", 0, &ToolsButton, 0, nullptr)) { static SPopupMenuId s_PopupMenuToolsId; - UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 50.0f, this, PopupMenuTools, PopupProperties); + UI()->DoPopupMenu(&s_PopupMenuToolsId, ToolsButton.x, ToolsButton.y + ToolsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuTools, PopupProperties); } MenuBar.VSplitLeft(5.0f, nullptr, &MenuBar); @@ -7548,10 +7242,17 @@ void CEditor::RenderSavingIndicator(CUIRect View) if(m_WriterFinishJobs.empty()) return; + const char *pText = "Saving…"; + const float FontSize = 24.0f; + UI()->MapScreen(); - CUIRect Label; - View.Margin(20.0f, &Label); - UI()->DoLabel(&Label, "Saving…", 24.0f, TEXTALIGN_BR); + CUIRect Label, Spinner; + View.Margin(20.0f, &View); + View.HSplitBottom(FontSize, nullptr, &View); + View.VSplitRight(TextRender()->TextWidth(FontSize, pText) + 2.0f, &Spinner, &Label); + Spinner.VSplitRight(Spinner.h, nullptr, &Spinner); + UI()->DoLabel(&Label, pText, FontSize, TEXTALIGN_MR); + UI()->RenderProgressSpinner(Spinner.Center(), 8.0f); } void CEditor::FreeDynamicPopupMenus() @@ -7625,154 +7326,6 @@ void CEditor::Reset(bool CreateDefault) m_SettingsCommandInput.Clear(); } -void CEditorMap::OnModify() -{ - m_Modified = true; - m_ModifiedAuto = true; - m_LastModifiedTime = m_pEditor->Client()->GlobalTime(); -} - -void CEditorMap::DeleteEnvelope(int Index) -{ - if(Index < 0 || Index >= (int)m_vpEnvelopes.size()) - return; - - OnModify(); - - VisitEnvelopeReferences([Index](int &ElementIndex) { - if(ElementIndex == Index) - ElementIndex = -1; - else if(ElementIndex > Index) - ElementIndex--; - }); - - m_vpEnvelopes.erase(m_vpEnvelopes.begin() + Index); -} - -void CEditorMap::SwapEnvelopes(int Index0, int Index1) -{ - if(Index0 < 0 || Index0 >= (int)m_vpEnvelopes.size()) - return; - if(Index1 < 0 || Index1 >= (int)m_vpEnvelopes.size()) - return; - if(Index0 == Index1) - return; - - OnModify(); - - VisitEnvelopeReferences([Index0, Index1](int &ElementIndex) { - if(ElementIndex == Index0) - ElementIndex = Index1; - else if(ElementIndex == Index1) - ElementIndex = Index0; - }); - - std::swap(m_vpEnvelopes[Index0], m_vpEnvelopes[Index1]); -} - -template -void CEditorMap::VisitEnvelopeReferences(F &&Visitor) -{ - for(auto &pGroup : m_vpGroups) - { - for(auto &pLayer : pGroup->m_vpLayers) - { - if(pLayer->m_Type == LAYERTYPE_QUADS) - { - std::shared_ptr pLayerQuads = std::static_pointer_cast(pLayer); - for(auto &Quad : pLayerQuads->m_vQuads) - { - Visitor(Quad.m_PosEnv); - Visitor(Quad.m_ColorEnv); - } - } - else if(pLayer->m_Type == LAYERTYPE_TILES) - { - std::shared_ptr pLayerTiles = std::static_pointer_cast(pLayer); - Visitor(pLayerTiles->m_ColorEnv); - } - else if(pLayer->m_Type == LAYERTYPE_SOUNDS) - { - std::shared_ptr pLayerSounds = std::static_pointer_cast(pLayer); - for(auto &Source : pLayerSounds->m_vSources) - { - Visitor(Source.m_PosEnv); - Visitor(Source.m_SoundEnv); - } - } - } - } -} - -void CEditorMap::MakeGameLayer(const std::shared_ptr &pLayer) -{ - m_pGameLayer = std::static_pointer_cast(pLayer); - m_pGameLayer->m_pEditor = m_pEditor; -} - -void CEditorMap::MakeGameGroup(std::shared_ptr pGroup) -{ - m_pGameGroup = std::move(pGroup); - m_pGameGroup->m_GameGroup = true; - str_copy(m_pGameGroup->m_aName, "Game"); -} - -void CEditorMap::Clean() -{ - m_vpGroups.clear(); - m_vpEnvelopes.clear(); - m_vpImages.clear(); - m_vpSounds.clear(); - - m_MapInfo.Reset(); - m_MapInfoTmp.Reset(); - - m_vSettings.clear(); - - m_pGameLayer = nullptr; - m_pGameGroup = nullptr; - - m_Modified = false; - m_ModifiedAuto = false; - - m_pTeleLayer = nullptr; - m_pSpeedupLayer = nullptr; - m_pFrontLayer = nullptr; - m_pSwitchLayer = nullptr; - m_pTuneLayer = nullptr; -} - -void CEditorMap::CreateDefault(IGraphics::CTextureHandle EntitiesTexture) -{ - // add background - std::shared_ptr pGroup = NewGroup(); - pGroup->m_ParallaxX = 0; - pGroup->m_ParallaxY = 0; - pGroup->m_CustomParallaxZoom = 0; - pGroup->m_ParallaxZoom = 0; - std::shared_ptr pLayer = std::make_shared(); - pLayer->m_pEditor = m_pEditor; - CQuad *pQuad = pLayer->NewQuad(0, 0, 1600, 1200); - pQuad->m_aColors[0].r = pQuad->m_aColors[1].r = 94; - pQuad->m_aColors[0].g = pQuad->m_aColors[1].g = 132; - pQuad->m_aColors[0].b = pQuad->m_aColors[1].b = 174; - pQuad->m_aColors[2].r = pQuad->m_aColors[3].r = 204; - pQuad->m_aColors[2].g = pQuad->m_aColors[3].g = 232; - pQuad->m_aColors[2].b = pQuad->m_aColors[3].b = 255; - pGroup->AddLayer(pLayer); - - // add game layer and reset front, tele, speedup, tune and switch layer pointers - MakeGameGroup(NewGroup()); - MakeGameLayer(std::make_shared(50, 50)); - m_pGameGroup->AddLayer(m_pGameLayer); - - m_pFrontLayer = nullptr; - m_pTeleLayer = nullptr; - m_pSpeedupLayer = nullptr; - m_pSwitchLayer = nullptr; - m_pTuneLayer = nullptr; -} - int CEditor::GetTextureUsageFlag() { return Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; @@ -7781,42 +7334,42 @@ int CEditor::GetTextureUsageFlag() IGraphics::CTextureHandle CEditor::GetFrontTexture() { if(!m_FrontTexture.IsValid()) - m_FrontTexture = Graphics()->LoadTexture("editor/front.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_FrontTexture = Graphics()->LoadTexture("editor/front.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_FrontTexture; } IGraphics::CTextureHandle CEditor::GetTeleTexture() { if(!m_TeleTexture.IsValid()) - m_TeleTexture = Graphics()->LoadTexture("editor/tele.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_TeleTexture = Graphics()->LoadTexture("editor/tele.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_TeleTexture; } IGraphics::CTextureHandle CEditor::GetSpeedupTexture() { if(!m_SpeedupTexture.IsValid()) - m_SpeedupTexture = Graphics()->LoadTexture("editor/speedup.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_SpeedupTexture = Graphics()->LoadTexture("editor/speedup.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_SpeedupTexture; } IGraphics::CTextureHandle CEditor::GetSwitchTexture() { if(!m_SwitchTexture.IsValid()) - m_SwitchTexture = Graphics()->LoadTexture("editor/switch.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_SwitchTexture = Graphics()->LoadTexture("editor/switch.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_SwitchTexture; } IGraphics::CTextureHandle CEditor::GetTuneTexture() { if(!m_TuneTexture.IsValid()) - m_TuneTexture = Graphics()->LoadTexture("editor/tune.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_TuneTexture = Graphics()->LoadTexture("editor/tune.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_TuneTexture; } IGraphics::CTextureHandle CEditor::GetEntitiesTexture() { if(!m_EntitiesTexture.IsValid()) - m_EntitiesTexture = Graphics()->LoadTexture("editor/entities/DDNet.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, GetTextureUsageFlag()); + m_EntitiesTexture = Graphics()->LoadTexture("editor/entities/DDNet.png", IStorage::TYPE_ALL, GetTextureUsageFlag()); return m_EntitiesTexture; } @@ -7844,9 +7397,9 @@ void CEditor::Init() for(CEditorComponent &Component : m_vComponents) Component.Init(this); - 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); + m_CheckerTexture = Graphics()->LoadTexture("editor/checker.png", IStorage::TYPE_ALL); + m_BackgroundTexture = Graphics()->LoadTexture("editor/background.png", IStorage::TYPE_ALL); + m_CursorTexture = Graphics()->LoadTexture("editor/cursor.png", IStorage::TYPE_ALL); m_pTilesetPicker = std::make_shared(16, 16); m_pTilesetPicker->m_pEditor = this; @@ -8172,36 +7725,107 @@ void CEditor::LoadCurrentMap() MapView()->SetWorldOffset(Center); } +bool CEditor::Save(const char *pFilename) +{ + // Check if file with this name is already being saved at the moment + if(std::any_of(std::begin(m_WriterFinishJobs), std::end(m_WriterFinishJobs), [pFilename](const std::shared_ptr &Job) { return str_comp(pFilename, Job->GetRealFileName()) == 0; })) + return false; + + return m_Map.Save(pFilename); +} + +bool CEditor::HandleMapDrop(const char *pFileName, int StorageType) +{ + if(HasUnsavedData()) + { + str_copy(m_aFileNamePending, pFileName); + m_PopupEventType = CEditor::POPEVENT_LOADDROP; + m_PopupEventActivated = true; + return true; + } + else + { + return Load(pFileName, IStorage::TYPE_ALL_OR_ABSOLUTE); + } +} + +bool CEditor::Load(const char *pFileName, int StorageType) +{ + const auto &&ErrorHandler = [this](const char *pErrorMessage) { + ShowFileDialogError("%s", pErrorMessage); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor/load", pErrorMessage); + }; + + Reset(); + bool Result = m_Map.Load(pFileName, StorageType, std::move(ErrorHandler)); + if(Result) + { + str_copy(m_aFileName, pFileName); + SortImages(); + SelectGameLayer(); + MapView()->OnMapLoad(); + } + else + { + m_aFileName[0] = 0; + Reset(); + } + return Result; +} + +bool CEditor::Append(const char *pFileName, int StorageType) +{ + CEditorMap NewMap; + NewMap.m_pEditor = this; + + const auto &&ErrorHandler = [this](const char *pErrorMessage) { + ShowFileDialogError("%s", pErrorMessage); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor/append", pErrorMessage); + }; + if(!NewMap.Load(pFileName, StorageType, std::move(ErrorHandler))) + return false; + + // modify indices + static const auto &&s_ModifyAddIndex = [](int AddAmount) { + return [AddAmount](int *pIndex) { + if(*pIndex >= 0) + *pIndex += AddAmount; + }; + }; + NewMap.ModifyImageIndex(s_ModifyAddIndex(m_Map.m_vpImages.size())); + NewMap.ModifySoundIndex(s_ModifyAddIndex(m_Map.m_vpSounds.size())); + NewMap.ModifyEnvelopeIndex(s_ModifyAddIndex(m_Map.m_vpEnvelopes.size())); + + // transfer images + for(const auto &pImage : NewMap.m_vpImages) + m_Map.m_vpImages.push_back(pImage); + NewMap.m_vpImages.clear(); + + // transfer sounds + for(const auto &pSound : NewMap.m_vpSounds) + m_Map.m_vpSounds.push_back(pSound); + NewMap.m_vpSounds.clear(); + + // transfer envelopes + for(const auto &pEnvelope : NewMap.m_vpEnvelopes) + m_Map.m_vpEnvelopes.push_back(pEnvelope); + NewMap.m_vpEnvelopes.clear(); + + // transfer groups + for(const auto &pGroup : NewMap.m_vpGroups) + { + if(pGroup != NewMap.m_pGameGroup) + { + pGroup->m_pMap = &m_Map; + m_Map.m_vpGroups.push_back(pGroup); + } + } + NewMap.m_vpGroups.clear(); + + SortImages(); + + // all done \o/ + return true; +} + IEditor *CreateEditor() { return new CEditor; } - -// DDRace - -void CEditorMap::MakeTeleLayer(const std::shared_ptr &pLayer) -{ - m_pTeleLayer = std::static_pointer_cast(pLayer); - m_pTeleLayer->m_pEditor = m_pEditor; -} - -void CEditorMap::MakeSpeedupLayer(const std::shared_ptr &pLayer) -{ - m_pSpeedupLayer = std::static_pointer_cast(pLayer); - m_pSpeedupLayer->m_pEditor = m_pEditor; -} - -void CEditorMap::MakeFrontLayer(const std::shared_ptr &pLayer) -{ - m_pFrontLayer = std::static_pointer_cast(pLayer); - m_pFrontLayer->m_pEditor = m_pEditor; -} - -void CEditorMap::MakeSwitchLayer(const std::shared_ptr &pLayer) -{ - m_pSwitchLayer = std::static_pointer_cast(pLayer); - m_pSwitchLayer->m_pEditor = m_pEditor; -} - -void CEditorMap::MakeTuneLayer(const std::shared_ptr &pLayer) -{ - m_pTuneLayer = std::static_pointer_cast(pLayer); - m_pTuneLayer->m_pEditor = m_pEditor; -} diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 2773657b6..2ca9712cf 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -329,58 +329,8 @@ public: } }; -class CEditorImage : public CImageInfo -{ -public: - CEditor *m_pEditor; - - CEditorImage(CEditor *pEditor) : - m_AutoMapper(pEditor) - { - m_pEditor = pEditor; - m_aName[0] = 0; - m_Texture.Invalidate(); - m_External = 0; - m_Width = 0; - m_Height = 0; - m_pData = nullptr; - m_Format = 0; - } - - ~CEditorImage(); - - void AnalyseTileFlags(); - - IGraphics::CTextureHandle m_Texture; - int m_External; - char m_aName[IO_MAX_PATH_LENGTH]; - unsigned char m_aTileFlags[256]; - class CAutoMapper m_AutoMapper; -}; - -class CEditorSound -{ -public: - CEditor *m_pEditor; - - CEditorSound(CEditor *pEditor) - { - m_pEditor = pEditor; - m_aName[0] = 0; - m_SoundID = 0; - - m_pData = nullptr; - m_DataSize = 0; - } - - ~CEditorSound(); - - int m_SoundID; - char m_aName[IO_MAX_PATH_LENGTH]; - - void *m_pData; - unsigned m_DataSize; -}; +class CEditorImage; +class CEditorSound; class CEditorMap { @@ -865,6 +815,7 @@ public: m_BrushColorEnabled = true; m_aFileName[0] = '\0'; + m_aFileNamePending[0] = '\0'; m_aFileSaveName[0] = '\0'; m_ValidSaveFilename = false; @@ -978,6 +929,7 @@ public: void Reset(bool CreateDefault = true); bool Save(const char *pFilename) override; bool Load(const char *pFilename, int StorageType) override; + bool HandleMapDrop(const char *pFilename, int StorageType) override; bool Append(const char *pFilename, int StorageType); void LoadCurrentMap(); void Render(); @@ -988,7 +940,6 @@ public: void RenderMousePointer(); std::vector GetSelectedQuads(); - std::vector> GetSelectedQuadPoints(); std::shared_ptr GetSelectedLayerType(int Index, int Type) const; std::shared_ptr GetSelectedLayer(int Index) const; std::shared_ptr GetSelectedGroup() const; @@ -999,13 +950,12 @@ public: void ToggleSelectQuad(int Index); void DeselectQuads(); void DeselectQuadPoints(); - void SelectQuadPoint(int QuadIndex, int Index); - void ToggleSelectQuadPoint(int QuadIndex, int Index); + void SelectQuadPoint(int Index); + void ToggleSelectQuadPoint(int Index); void DeleteSelectedQuads(); bool IsQuadSelected(int Index) const; - bool IsQuadPointSelected(int QuadIndex, int Index) const; + bool IsQuadPointSelected(int Index) const; int FindSelectedQuadIndex(int Index) const; - int FindSelectedQuadPointIndex(int QuadIndex) const; int FindEnvPointIndex(int Index, int Channel) const; void SelectEnvPoint(int Index); @@ -1031,6 +981,7 @@ public: bool m_BrushColorEnabled; char m_aFileName[IO_MAX_PATH_LENGTH]; + char m_aFileNamePending[IO_MAX_PATH_LENGTH]; char m_aFileSaveName[IO_MAX_PATH_LENGTH]; bool m_ValidSaveFilename; @@ -1039,6 +990,7 @@ public: POPEVENT_EXIT = 0, POPEVENT_LOAD, POPEVENT_LOADCURRENT, + POPEVENT_LOADDROP, POPEVENT_NEW, POPEVENT_SAVE, POPEVENT_SAVE_COPY, @@ -1046,7 +998,10 @@ public: POPEVENT_PREVENTUNUSEDTILES, POPEVENT_IMAGEDIV16, POPEVENT_IMAGE_MAX, - POPEVENT_PLACE_BORDER_TILES + POPEVENT_PLACE_BORDER_TILES, + POPEVENT_PIXELART_BIG_IMAGE, + POPEVENT_PIXELART_MANY_COLORS, + POPEVENT_PIXELART_TOO_MANY_COLORS }; int m_PopupEventType; @@ -1226,7 +1181,7 @@ public: int m_SelectedQuadPoint; int m_SelectedQuadIndex; int m_SelectedGroup; - std::vector> m_vSelectedQuadPoints; + int m_SelectedQuadPoints; int m_SelectedEnvelope; std::vector> m_vSelectedEnvelopePoints; int m_SelectedQuadEnvelope; @@ -1238,8 +1193,6 @@ public: std::pair m_SelectedTangentOutPoint; bool m_UpdateEnvPointInfo; - std::vector m_vCopyBuffer; - bool m_QuadKnifeActive; int m_QuadKnifeCount; vec2 m_aQuadKnifePoints[4]; @@ -1265,6 +1218,11 @@ public: CLineInputBuffered<256> m_SettingsCommandInput; + CImageInfo m_TileartImageInfo; + char m_aTileartFilename[IO_MAX_PATH_LENGTH]; + void AddTileart(); + void TileartCheckColors(); + void PlaceBorderTiles(); void UpdateTooltip(const void *pID, const CUIRect *pRect, const char *pToolTip); @@ -1327,6 +1285,7 @@ public: static bool CallbackAppendMap(const char *pFileName, int StorageType, void *pUser); static bool CallbackSaveMap(const char *pFileName, int StorageType, void *pUser); static bool CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser); + static bool CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser); void PopupSelectImageInvoke(int Current, float x, float y); int PopupSelectImageResult(); diff --git a/src/game/editor/map_grid.cpp b/src/game/editor/map_grid.cpp index 7bcfb075e..f1babeb9c 100644 --- a/src/game/editor/map_grid.cpp +++ b/src/game/editor/map_grid.cpp @@ -23,8 +23,7 @@ void CMapGrid::OnRender(CUIRect View) float aGroupPoints[4]; pGroup->Mapping(aGroupPoints); - float w = UI()->Screen()->w; - float h = UI()->Screen()->h; + const CUIRect *pScreen = UI()->Screen(); int LineDistance = GridLineDistance(); @@ -36,14 +35,14 @@ void CMapGrid::OnRender(CUIRect View) Graphics()->TextureClear(); Graphics()->LinesBegin(); - for(int i = 0; i < (int)w; i++) + for(int i = 0; i < (int)pScreen->w; i++) { if((i + YGridOffset) % m_GridFactor == 0) Graphics()->SetColor(1.0f, 0.3f, 0.3f, 0.3f); else Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); - IGraphics::CLineItem Line = IGraphics::CLineItem(LineDistance * XOffset, LineDistance * i + LineDistance * YOffset, w + aGroupPoints[2], LineDistance * i + LineDistance * YOffset); + IGraphics::CLineItem Line = IGraphics::CLineItem(LineDistance * XOffset, LineDistance * i + LineDistance * YOffset, pScreen->w + aGroupPoints[2], LineDistance * i + LineDistance * YOffset); Graphics()->LinesDraw(&Line, 1); if((i + XGridOffset) % m_GridFactor == 0) @@ -51,7 +50,7 @@ void CMapGrid::OnRender(CUIRect View) else Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.15f); - Line = IGraphics::CLineItem(LineDistance * i + LineDistance * XOffset, LineDistance * YOffset, LineDistance * i + LineDistance * XOffset, h + aGroupPoints[3]); + Line = IGraphics::CLineItem(LineDistance * i + LineDistance * XOffset, LineDistance * YOffset, LineDistance * i + LineDistance * XOffset, pScreen->h + aGroupPoints[3]); Graphics()->LinesDraw(&Line, 1); } Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); diff --git a/src/game/editor/map_grid.h b/src/game/editor/map_grid.h index 6da8290da..6aabf1757 100644 --- a/src/game/editor/map_grid.h +++ b/src/game/editor/map_grid.h @@ -13,7 +13,7 @@ public: int GridLineDistance() const; /** - * Returns wether the grid is rendered. + * Returns whether the grid is rendered. */ bool IsEnabled() const; diff --git a/src/game/editor/map_view.cpp b/src/game/editor/map_view.cpp index 1c08c05e9..35bdd8c03 100644 --- a/src/game/editor/map_view.cpp +++ b/src/game/editor/map_view.cpp @@ -66,14 +66,14 @@ void CMapView::RenderGroupBorder() float w, h; pLayer->GetSize(&w, &h); - IGraphics::CLineItem Array[4] = { + IGraphics::CLineItem aArray[4] = { IGraphics::CLineItem(0, 0, w, 0), IGraphics::CLineItem(w, 0, w, h), IGraphics::CLineItem(w, h, 0, h), IGraphics::CLineItem(0, h, 0, 0)}; Graphics()->TextureClear(); Graphics()->LinesBegin(); - Graphics()->LinesDraw(Array, 4); + Graphics()->LinesDraw(aArray, std::size(aArray)); Graphics()->LinesEnd(); } } @@ -121,11 +121,11 @@ void CMapView::RenderMap() } } - std::shared_ptr pT = std::static_pointer_cast(Editor()->GetSelectedLayerType(0, LAYERTYPE_TILES)); - if(Editor()->m_ShowTileInfo != CEditor::SHOW_TILE_OFF && pT && pT->m_Visible && m_Zoom.GetValue() <= 300.0f) + std::shared_ptr pSelectedTilesLayer = std::static_pointer_cast(Editor()->GetSelectedLayerType(0, LAYERTYPE_TILES)); + if(Editor()->m_ShowTileInfo != CEditor::SHOW_TILE_OFF && pSelectedTilesLayer && pSelectedTilesLayer->m_Visible && m_Zoom.GetValue() <= 300.0f) { Editor()->GetSelectedGroup()->MapScreen(); - pT->ShowInfo(); + pSelectedTilesLayer->ShowInfo(); } } @@ -230,7 +230,7 @@ vec2 CMapView::GetEditorOffset() const return m_EditorOffset; } -float CMapView::WorldZoom() const +float CMapView::GetWorldZoom() const { return m_WorldZoom; } diff --git a/src/game/editor/map_view.h b/src/game/editor/map_view.h index 65a5d5118..5da4db236 100644 --- a/src/game/editor/map_view.h +++ b/src/game/editor/map_view.h @@ -38,7 +38,7 @@ public: bool m_ShowPicker; // TODO: make private - float WorldZoom() const; + float GetWorldZoom() const; void OffsetWorld(vec2 Offset); void OffsetEditor(vec2 Offset); diff --git a/src/game/editor/mapitems/image.cpp b/src/game/editor/mapitems/image.cpp new file mode 100644 index 000000000..514a1cadf --- /dev/null +++ b/src/game/editor/mapitems/image.cpp @@ -0,0 +1,56 @@ +#include "image.h" + +#include + +CEditorImage::CEditorImage(CEditor *pEditor) : + m_AutoMapper(pEditor) +{ + Init(pEditor); + m_Texture.Invalidate(); +} + +CEditorImage::~CEditorImage() +{ + Graphics()->UnloadTexture(&m_Texture); + free(m_pData); + m_pData = nullptr; +} + +void CEditorImage::Init(CEditor *pEditor) +{ + CEditorComponent::Init(pEditor); + RegisterSubComponent(m_AutoMapper); + InitSubComponents(); +} + +void CEditorImage::AnalyseTileFlags() +{ + mem_zero(m_aTileFlags, sizeof(m_aTileFlags)); + + int tw = m_Width / 16; // tilesizes + int th = m_Height / 16; + if(tw == th && m_Format == CImageInfo::FORMAT_RGBA) + { + unsigned char *pPixelData = (unsigned char *)m_pData; + + int TileID = 0; + for(int ty = 0; ty < 16; ty++) + for(int tx = 0; tx < 16; tx++, TileID++) + { + bool Opaque = true; + for(int x = 0; x < tw; x++) + for(int y = 0; y < th; y++) + { + int p = (ty * tw + y) * m_Width + tx * tw + x; + if(pPixelData[p * 4 + 3] < 250) + { + Opaque = false; + break; + } + } + + if(Opaque) + m_aTileFlags[TileID] |= TILEFLAG_OPAQUE; + } + } +} diff --git a/src/game/editor/mapitems/image.h b/src/game/editor/mapitems/image.h new file mode 100644 index 000000000..cffbd6c7e --- /dev/null +++ b/src/game/editor/mapitems/image.h @@ -0,0 +1,25 @@ +#ifndef GAME_EDITOR_MAPITEMS_IMAGE_H +#define GAME_EDITOR_MAPITEMS_IMAGE_H + +#include + +#include +#include + +class CEditorImage : public CImageInfo, public CEditorComponent +{ +public: + explicit CEditorImage(CEditor *pEditor); + ~CEditorImage(); + + void Init(CEditor *pEditor) override; + void AnalyseTileFlags(); + + IGraphics::CTextureHandle m_Texture; + int m_External = 0; + char m_aName[IO_MAX_PATH_LENGTH] = ""; + unsigned char m_aTileFlags[256]; + CAutoMapper m_AutoMapper; +}; + +#endif diff --git a/src/game/editor/mapitems/layer_front.cpp b/src/game/editor/mapitems/layer_front.cpp new file mode 100644 index 000000000..01ae277b8 --- /dev/null +++ b/src/game/editor/mapitems/layer_front.cpp @@ -0,0 +1,47 @@ +#include + +CLayerFront::CLayerFront(int w, int h) : + CLayerTiles(w, h) +{ + str_copy(m_aName, "Front"); + m_Front = 1; +} + +void CLayerFront::SetTile(int x, int y, CTile Tile) +{ + if(Tile.m_Index == TILE_THROUGH_CUT) + { + CTile nohook = {TILE_NOHOOK}; + m_pEditor->m_Map.m_pGameLayer->CLayerTiles::SetTile(x, y, nohook); // NOLINT(bugprone-parent-virtual-call) + } + else if(Tile.m_Index == TILE_AIR && CLayerTiles::GetTile(x, y).m_Index == TILE_THROUGH_CUT) + { + CTile air = {TILE_AIR}; + m_pEditor->m_Map.m_pGameLayer->CLayerTiles::SetTile(x, y, air); // NOLINT(bugprone-parent-virtual-call) + } + if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidFrontTile(Tile.m_Index)) + { + CLayerTiles::SetTile(x, y, Tile); + } + else + { + CTile air = {TILE_AIR}; + CLayerTiles::SetTile(x, y, air); + if(!m_pEditor->m_PreventUnusedTilesWasWarned) + { + m_pEditor->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES; + m_pEditor->m_PopupEventActivated = true; + m_pEditor->m_PreventUnusedTilesWasWarned = true; + } + } +} + +void CLayerFront::Resize(int NewW, int NewH) +{ + // resize tile data + CLayerTiles::Resize(NewW, NewH); + + // resize gamelayer too + if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) + m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); +} diff --git a/src/game/editor/layer_game.cpp b/src/game/editor/mapitems/layer_game.cpp similarity index 98% rename from src/game/editor/layer_game.cpp rename to src/game/editor/mapitems/layer_game.cpp index a77afc58e..fd9f82e47 100644 --- a/src/game/editor/layer_game.cpp +++ b/src/game/editor/mapitems/layer_game.cpp @@ -1,6 +1,6 @@ /* (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 "editor.h" +#include CLayerGame::CLayerGame(int w, int h) : CLayerTiles(w, h) diff --git a/src/game/editor/mapitems/layer_group.cpp b/src/game/editor/mapitems/layer_group.cpp new file mode 100644 index 000000000..2f01d77eb --- /dev/null +++ b/src/game/editor/mapitems/layer_group.cpp @@ -0,0 +1,155 @@ +#include + +CLayerGroup::CLayerGroup() +{ + m_vpLayers.clear(); + m_aName[0] = 0; + m_Visible = true; + m_Collapse = false; + m_GameGroup = false; + m_OffsetX = 0; + m_OffsetY = 0; + m_ParallaxX = 100; + m_ParallaxY = 100; + m_CustomParallaxZoom = 0; + m_ParallaxZoom = 100; + + m_UseClipping = 0; + m_ClipX = 0; + m_ClipY = 0; + m_ClipW = 0; + m_ClipH = 0; +} + +CLayerGroup::~CLayerGroup() +{ + m_vpLayers.clear(); +} + +void CLayerGroup::Convert(CUIRect *pRect) +{ + pRect->x += m_OffsetX; + pRect->y += m_OffsetY; +} + +void CLayerGroup::Mapping(float *pPoints) +{ + float ParallaxZoom = m_pMap->m_pEditor->m_PreviewZoom ? m_ParallaxZoom : 100.0f; + + m_pMap->m_pEditor->RenderTools()->MapScreenToWorld( + m_pMap->m_pEditor->MapView()->GetWorldOffset().x, m_pMap->m_pEditor->MapView()->GetWorldOffset().y, + m_ParallaxX, m_ParallaxY, ParallaxZoom, m_OffsetX, m_OffsetY, + m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->MapView()->GetWorldZoom(), pPoints); + + pPoints[0] += m_pMap->m_pEditor->MapView()->GetEditorOffset().x; + pPoints[1] += m_pMap->m_pEditor->MapView()->GetEditorOffset().y; + pPoints[2] += m_pMap->m_pEditor->MapView()->GetEditorOffset().x; + pPoints[3] += m_pMap->m_pEditor->MapView()->GetEditorOffset().y; +} + +void CLayerGroup::MapScreen() +{ + float aPoints[4]; + Mapping(aPoints); + m_pMap->m_pEditor->Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]); +} + +void CLayerGroup::Render() +{ + MapScreen(); + IGraphics *pGraphics = m_pMap->m_pEditor->Graphics(); + + if(m_UseClipping) + { + float aPoints[4]; + m_pMap->m_pGameGroup->Mapping(aPoints); + float x0 = (m_ClipX - aPoints[0]) / (aPoints[2] - aPoints[0]); + float y0 = (m_ClipY - aPoints[1]) / (aPoints[3] - aPoints[1]); + float x1 = ((m_ClipX + m_ClipW) - aPoints[0]) / (aPoints[2] - aPoints[0]); + float y1 = ((m_ClipY + m_ClipH) - aPoints[1]) / (aPoints[3] - aPoints[1]); + + pGraphics->ClipEnable((int)(x0 * pGraphics->ScreenWidth()), (int)(y0 * pGraphics->ScreenHeight()), + (int)((x1 - x0) * pGraphics->ScreenWidth()), (int)((y1 - y0) * pGraphics->ScreenHeight())); + } + + for(auto &pLayer : m_vpLayers) + { + if(pLayer->m_Visible) + { + if(pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pTiles = std::static_pointer_cast(pLayer); + if(pTiles->m_Game || pTiles->m_Front || pTiles->m_Tele || pTiles->m_Speedup || pTiles->m_Tune || pTiles->m_Switch) + continue; + } + if(m_pMap->m_pEditor->m_ShowDetail || !(pLayer->m_Flags & LAYERFLAG_DETAIL)) + pLayer->Render(); + } + } + + for(auto &pLayer : m_vpLayers) + { + if(pLayer->m_Visible && pLayer->m_Type == LAYERTYPE_TILES && pLayer != m_pMap->m_pGameLayer && pLayer != m_pMap->m_pFrontLayer && pLayer != m_pMap->m_pTeleLayer && pLayer != m_pMap->m_pSpeedupLayer && pLayer != m_pMap->m_pSwitchLayer && pLayer != m_pMap->m_pTuneLayer) + { + std::shared_ptr pTiles = std::static_pointer_cast(pLayer); + if(pTiles->m_Game || pTiles->m_Front || pTiles->m_Tele || pTiles->m_Speedup || pTiles->m_Tune || pTiles->m_Switch) + { + pLayer->Render(); + } + } + } + + if(m_UseClipping) + pGraphics->ClipDisable(); +} + +void CLayerGroup::AddLayer(const std::shared_ptr &pLayer) +{ + m_pMap->OnModify(); + m_vpLayers.push_back(pLayer); +} + +void CLayerGroup::DeleteLayer(int Index) +{ + if(Index < 0 || Index >= (int)m_vpLayers.size()) + return; + m_vpLayers.erase(m_vpLayers.begin() + Index); + m_pMap->OnModify(); +} + +void CLayerGroup::DuplicateLayer(int Index) +{ + if(Index < 0 || Index >= (int)m_vpLayers.size()) + return; + + std::shared_ptr pDup = m_vpLayers[Index]->Duplicate(); + m_vpLayers.insert(m_vpLayers.begin() + Index + 1, pDup); + + m_pMap->OnModify(); +} + +void CLayerGroup::GetSize(float *pWidth, float *pHeight) const +{ + *pWidth = 0; + *pHeight = 0; + for(const auto &pLayer : m_vpLayers) + { + float lw, lh; + pLayer->GetSize(&lw, &lh); + *pWidth = maximum(*pWidth, lw); + *pHeight = maximum(*pHeight, lh); + } +} + +int CLayerGroup::SwapLayers(int Index0, int Index1) +{ + if(Index0 < 0 || Index0 >= (int)m_vpLayers.size()) + return Index0; + if(Index1 < 0 || Index1 >= (int)m_vpLayers.size()) + return Index0; + if(Index0 == Index1) + return Index0; + m_pMap->OnModify(); + std::swap(m_vpLayers[Index0], m_vpLayers[Index1]); + return Index1; +} diff --git a/src/game/editor/layer_quads.cpp b/src/game/editor/mapitems/layer_quads.cpp similarity index 98% rename from src/game/editor/layer_quads.cpp rename to src/game/editor/mapitems/layer_quads.cpp index 0a362b0fa..b16eeba4a 100644 --- a/src/game/editor/layer_quads.cpp +++ b/src/game/editor/mapitems/layer_quads.cpp @@ -1,11 +1,8 @@ /* (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 +#include -#include - -#include "editor.h" -#include +#include "image.h" CLayerQuads::CLayerQuads() { diff --git a/src/game/editor/layer_sounds.cpp b/src/game/editor/mapitems/layer_sounds.cpp similarity index 99% rename from src/game/editor/layer_sounds.cpp rename to src/game/editor/mapitems/layer_sounds.cpp index 57e5dd40e..9a8d8547c 100644 --- a/src/game/editor/layer_sounds.cpp +++ b/src/game/editor/mapitems/layer_sounds.cpp @@ -1,8 +1,7 @@ +#include #include -#include "editor.h" - static const float s_SourceVisualSize = 32.0f; CLayerSounds::CLayerSounds() diff --git a/src/game/editor/mapitems/layer_speedup.cpp b/src/game/editor/mapitems/layer_speedup.cpp new file mode 100644 index 000000000..0c2e2e715 --- /dev/null +++ b/src/game/editor/mapitems/layer_speedup.cpp @@ -0,0 +1,244 @@ +#include + +CLayerSpeedup::CLayerSpeedup(int w, int h) : + CLayerTiles(w, h) +{ + str_copy(m_aName, "Speedup"); + m_Speedup = 1; + + m_pSpeedupTile = new CSpeedupTile[w * h]; + mem_zero(m_pSpeedupTile, (size_t)w * h * sizeof(CSpeedupTile)); +} + +CLayerSpeedup::~CLayerSpeedup() +{ + delete[] m_pSpeedupTile; +} + +void CLayerSpeedup::Resize(int NewW, int NewH) +{ + // resize speedup data + CSpeedupTile *pNewSpeedupData = new CSpeedupTile[NewW * NewH]; + mem_zero(pNewSpeedupData, (size_t)NewW * NewH * sizeof(CSpeedupTile)); + + // copy old data + for(int y = 0; y < minimum(NewH, m_Height); y++) + mem_copy(&pNewSpeedupData[y * NewW], &m_pSpeedupTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CSpeedupTile)); + + // replace old + delete[] m_pSpeedupTile; + m_pSpeedupTile = pNewSpeedupData; + + // resize tile data + CLayerTiles::Resize(NewW, NewH); + + // resize gamelayer too + if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) + m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); +} + +void CLayerSpeedup::Shift(int Direction) +{ + CLayerTiles::Shift(Direction); + ShiftImpl(m_pSpeedupTile, Direction, m_pEditor->m_ShiftBy); +} + +bool CLayerSpeedup::IsEmpty(const std::shared_ptr &pLayer) +{ + for(int y = 0; y < pLayer->m_Height; y++) + for(int x = 0; x < pLayer->m_Width; x++) + if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pLayer->GetTile(x, y).m_Index)) + return false; + + return true; +} + +void CLayerSpeedup::BrushDraw(std::shared_ptr pBrush, float wx, float wy) +{ + if(m_Readonly) + return; + + std::shared_ptr pSpeedupLayer = std::static_pointer_cast(pBrush); + int sx = ConvertX(wx); + int sy = ConvertY(wy); + if(str_comp(pSpeedupLayer->m_aFileName, m_pEditor->m_aFileName)) + { + m_pEditor->m_SpeedupAngle = pSpeedupLayer->m_SpeedupAngle; + m_pEditor->m_SpeedupForce = pSpeedupLayer->m_SpeedupForce; + m_pEditor->m_SpeedupMaxSpeed = pSpeedupLayer->m_SpeedupMaxSpeed; + } + + bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pSpeedupLayer); + + for(int y = 0; y < pSpeedupLayer->m_Height; y++) + for(int x = 0; x < pSpeedupLayer->m_Width; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index)) && pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR) + { + if(m_pEditor->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || m_pEditor->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed) + { + m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; + m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; + m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; + m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + } + else if(pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force) + { + m_pSpeedupTile[fy * m_Width + fx].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force; + m_pSpeedupTile[fy * m_Width + fx].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle; + m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed; + m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + } + else if(m_pEditor->m_SpeedupForce) + { + m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; + m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; + m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; + m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; + } + else + { + m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; + m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; + m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; + m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + } + } + else + { + m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; + m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; + m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; + m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + } + } + FlagModified(sx, sy, pSpeedupLayer->m_Width, pSpeedupLayer->m_Height); +} + +void CLayerSpeedup::BrushFlipX() +{ + CLayerTiles::BrushFlipX(); + BrushFlipXImpl(m_pSpeedupTile); +} + +void CLayerSpeedup::BrushFlipY() +{ + CLayerTiles::BrushFlipY(); + BrushFlipYImpl(m_pSpeedupTile); +} + +void CLayerSpeedup::BrushRotate(float Amount) +{ + int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° + if(Rotation < 0) + Rotation += 4; + + if(Rotation == 1 || Rotation == 3) + { + // 90° rotation + CSpeedupTile *pTempData1 = new CSpeedupTile[m_Width * m_Height]; + CTile *pTempData2 = new CTile[m_Width * m_Height]; + mem_copy(pTempData1, m_pSpeedupTile, (size_t)m_Width * m_Height * sizeof(CSpeedupTile)); + mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); + CSpeedupTile *pDst1 = m_pSpeedupTile; + CTile *pDst2 = m_pTiles; + for(int x = 0; x < m_Width; ++x) + for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) + { + *pDst1 = pTempData1[y * m_Width + x]; + *pDst2 = pTempData2[y * m_Width + x]; + } + + std::swap(m_Width, m_Height); + delete[] pTempData1; + delete[] pTempData2; + } + + if(Rotation == 2 || Rotation == 3) + { + BrushFlipX(); + BrushFlipY(); + } +} + +void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) +{ + if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) + return; + + Snap(&Rect); // corrects Rect; no need of <= + + Snap(&Rect); + + int sx = ConvertX(Rect.x); + int sy = ConvertY(Rect.y); + int w = ConvertX(Rect.w); + int h = ConvertY(Rect.h); + + std::shared_ptr pLt = std::static_pointer_cast(pBrush); + + bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); + + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int TgtIndex = fy * m_Width + fx; + + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset + { + m_pTiles[TgtIndex].m_Index = 0; + m_pSpeedupTile[TgtIndex].m_Force = 0; + m_pSpeedupTile[TgtIndex].m_Angle = 0; + } + else + { + m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; + if(pLt->m_Speedup && m_pTiles[TgtIndex].m_Index > 0) + { + m_pSpeedupTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; + + if((pLt->m_pSpeedupTile[SrcIndex].m_Force == 0 && m_pEditor->m_SpeedupForce) || m_pEditor->m_SpeedupForce != pLt->m_SpeedupForce) + m_pSpeedupTile[TgtIndex].m_Force = m_pEditor->m_SpeedupForce; + else + m_pSpeedupTile[TgtIndex].m_Force = pLt->m_pSpeedupTile[SrcIndex].m_Force; + + if((pLt->m_pSpeedupTile[SrcIndex].m_Angle == 0 && m_pEditor->m_SpeedupAngle) || m_pEditor->m_SpeedupAngle != pLt->m_SpeedupAngle) + m_pSpeedupTile[TgtIndex].m_Angle = m_pEditor->m_SpeedupAngle; + else + m_pSpeedupTile[TgtIndex].m_Angle = pLt->m_pSpeedupTile[SrcIndex].m_Angle; + + if((pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed == 0 && m_pEditor->m_SpeedupMaxSpeed) || m_pEditor->m_SpeedupMaxSpeed != pLt->m_SpeedupMaxSpeed) + m_pSpeedupTile[TgtIndex].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; + else + m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed; + } + } + } + } + FlagModified(sx, sy, w, h); +} diff --git a/src/game/editor/mapitems/layer_switch.cpp b/src/game/editor/mapitems/layer_switch.cpp new file mode 100644 index 000000000..1d0d978a4 --- /dev/null +++ b/src/game/editor/mapitems/layer_switch.cpp @@ -0,0 +1,270 @@ +#include + +CLayerSwitch::CLayerSwitch(int w, int h) : + CLayerTiles(w, h) +{ + str_copy(m_aName, "Switch"); + m_Switch = 1; + + m_pSwitchTile = new CSwitchTile[w * h]; + mem_zero(m_pSwitchTile, (size_t)w * h * sizeof(CSwitchTile)); +} + +CLayerSwitch::~CLayerSwitch() +{ + delete[] m_pSwitchTile; +} + +void CLayerSwitch::Resize(int NewW, int NewH) +{ + // resize switch data + CSwitchTile *pNewSwitchData = new CSwitchTile[NewW * NewH]; + mem_zero(pNewSwitchData, (size_t)NewW * NewH * sizeof(CSwitchTile)); + + // copy old data + for(int y = 0; y < minimum(NewH, m_Height); y++) + mem_copy(&pNewSwitchData[y * NewW], &m_pSwitchTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CSwitchTile)); + + // replace old + delete[] m_pSwitchTile; + m_pSwitchTile = pNewSwitchData; + + // resize tile data + CLayerTiles::Resize(NewW, NewH); + + // resize gamelayer too + if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) + m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); +} + +void CLayerSwitch::Shift(int Direction) +{ + CLayerTiles::Shift(Direction); + ShiftImpl(m_pSwitchTile, Direction, m_pEditor->m_ShiftBy); +} + +bool CLayerSwitch::IsEmpty(const std::shared_ptr &pLayer) +{ + for(int y = 0; y < pLayer->m_Height; y++) + for(int x = 0; x < pLayer->m_Width; x++) + if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pLayer->GetTile(x, y).m_Index)) + return false; + + return true; +} + +void CLayerSwitch::BrushDraw(std::shared_ptr pBrush, float wx, float wy) +{ + if(m_Readonly) + return; + + std::shared_ptr pSwitchLayer = std::static_pointer_cast(pBrush); + int sx = ConvertX(wx); + int sy = ConvertY(wy); + if(str_comp(pSwitchLayer->m_aFileName, m_pEditor->m_aFileName)) + { + m_pEditor->m_SwitchNum = pSwitchLayer->m_SwitchNumber; + m_pEditor->m_SwitchDelay = pSwitchLayer->m_SwitchDelay; + } + + bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pSwitchLayer); + + for(int y = 0; y < pSwitchLayer->m_Height; y++) + for(int x = 0; x < pSwitchLayer->m_Width; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) + { + if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay) + { + m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; + m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; + } + else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number) + { + m_pSwitchTile[fy * m_Width + fx].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; + m_pSwitchTile[fy * m_Width + fx].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; + } + else + { + m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; + m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; + } + + m_pSwitchTile[fy * m_Width + fx].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; + m_pSwitchTile[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; + m_pTiles[fy * m_Width + fx].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; + + if(!IsSwitchTileFlagsUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) + { + m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; + } + if(!IsSwitchTileNumberUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) + { + m_pSwitchTile[fy * m_Width + fx].m_Number = 0; + } + if(!IsSwitchTileDelayUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) + { + m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; + } + } + else + { + m_pSwitchTile[fy * m_Width + fx].m_Number = 0; + m_pSwitchTile[fy * m_Width + fx].m_Type = 0; + m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; + m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + } + } + FlagModified(sx, sy, pSwitchLayer->m_Width, pSwitchLayer->m_Height); +} + +void CLayerSwitch::BrushFlipX() +{ + CLayerTiles::BrushFlipX(); + BrushFlipXImpl(m_pSwitchTile); +} + +void CLayerSwitch::BrushFlipY() +{ + CLayerTiles::BrushFlipY(); + BrushFlipYImpl(m_pSwitchTile); +} + +void CLayerSwitch::BrushRotate(float Amount) +{ + int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° + if(Rotation < 0) + Rotation += 4; + + if(Rotation == 1 || Rotation == 3) + { + // 90° rotation + CSwitchTile *pTempData1 = new CSwitchTile[m_Width * m_Height]; + CTile *pTempData2 = new CTile[m_Width * m_Height]; + mem_copy(pTempData1, m_pSwitchTile, (size_t)m_Width * m_Height * sizeof(CSwitchTile)); + mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); + CSwitchTile *pDst1 = m_pSwitchTile; + CTile *pDst2 = m_pTiles; + for(int x = 0; x < m_Width; ++x) + for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) + { + *pDst1 = pTempData1[y * m_Width + x]; + *pDst2 = pTempData2[y * m_Width + x]; + if(IsRotatableTile(pDst2->m_Index)) + { + if(pDst2->m_Flags & TILEFLAG_ROTATE) + pDst2->m_Flags ^= (TILEFLAG_YFLIP | TILEFLAG_XFLIP); + pDst2->m_Flags ^= TILEFLAG_ROTATE; + } + } + + std::swap(m_Width, m_Height); + delete[] pTempData1; + delete[] pTempData2; + } + + if(Rotation == 2 || Rotation == 3) + { + BrushFlipX(); + BrushFlipY(); + } +} + +void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) +{ + if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) + return; + + Snap(&Rect); // corrects Rect; no need of <= + + Snap(&Rect); + + int sx = ConvertX(Rect.x); + int sy = ConvertY(Rect.y); + int w = ConvertX(Rect.w); + int h = ConvertY(Rect.h); + + std::shared_ptr pLt = std::static_pointer_cast(pBrush); + + bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); + + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int TgtIndex = fy * m_Width + fx; + + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index))) + { + m_pTiles[TgtIndex].m_Index = 0; + m_pSwitchTile[TgtIndex].m_Type = 0; + m_pSwitchTile[TgtIndex].m_Number = 0; + m_pSwitchTile[TgtIndex].m_Delay = 0; + } + else + { + m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; + m_pSwitchTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; + if(pLt->m_Switch && m_pTiles[TgtIndex].m_Index > 0) + { + if(!IsSwitchTileNumberUsed(m_pSwitchTile[TgtIndex].m_Type)) + m_pSwitchTile[TgtIndex].m_Number = 0; + else if(pLt->m_pSwitchTile[SrcIndex].m_Number == 0 || m_pEditor->m_SwitchNum != pLt->m_SwitchNumber) + m_pSwitchTile[TgtIndex].m_Number = m_pEditor->m_SwitchNum; + else + m_pSwitchTile[TgtIndex].m_Number = pLt->m_pSwitchTile[SrcIndex].m_Number; + + if(!IsSwitchTileDelayUsed(m_pSwitchTile[TgtIndex].m_Type)) + m_pSwitchTile[TgtIndex].m_Delay = 0; + else if(pLt->m_pSwitchTile[SrcIndex].m_Delay == 0 || m_pEditor->m_SwitchDelay != pLt->m_SwitchDelay) + m_pSwitchTile[TgtIndex].m_Delay = m_pEditor->m_SwitchDelay; + else + m_pSwitchTile[TgtIndex].m_Delay = pLt->m_pSwitchTile[SrcIndex].m_Delay; + + if(!IsSwitchTileFlagsUsed(m_pSwitchTile[TgtIndex].m_Type)) + m_pSwitchTile[TgtIndex].m_Flags = 0; + else + m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags; + } + } + } + } + FlagModified(sx, sy, w, h); +} + +bool CLayerSwitch::ContainsElementWithId(int Id) +{ + for(int y = 0; y < m_Height; ++y) + { + for(int x = 0; x < m_Width; ++x) + { + if(IsSwitchTileNumberUsed(m_pSwitchTile[y * m_Width + x].m_Type) && m_pSwitchTile[y * m_Width + x].m_Number == Id) + { + return true; + } + } + } + + return false; +} diff --git a/src/game/editor/mapitems/layer_tele.cpp b/src/game/editor/mapitems/layer_tele.cpp new file mode 100644 index 000000000..56afbce78 --- /dev/null +++ b/src/game/editor/mapitems/layer_tele.cpp @@ -0,0 +1,247 @@ +#include + +CLayerTele::CLayerTele(int w, int h) : + CLayerTiles(w, h) +{ + str_copy(m_aName, "Tele"); + m_Tele = 1; + + m_pTeleTile = new CTeleTile[w * h]; + mem_zero(m_pTeleTile, (size_t)w * h * sizeof(CTeleTile)); +} + +CLayerTele::~CLayerTele() +{ + delete[] m_pTeleTile; +} + +void CLayerTele::Resize(int NewW, int NewH) +{ + // resize tele data + CTeleTile *pNewTeleData = new CTeleTile[NewW * NewH]; + mem_zero(pNewTeleData, (size_t)NewW * NewH * sizeof(CTeleTile)); + + // copy old data + for(int y = 0; y < minimum(NewH, m_Height); y++) + mem_copy(&pNewTeleData[y * NewW], &m_pTeleTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CTeleTile)); + + // replace old + delete[] m_pTeleTile; + m_pTeleTile = pNewTeleData; + + // resize tile data + CLayerTiles::Resize(NewW, NewH); + + // resize gamelayer too + if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) + m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); +} + +void CLayerTele::Shift(int Direction) +{ + CLayerTiles::Shift(Direction); + ShiftImpl(m_pTeleTile, Direction, m_pEditor->m_ShiftBy); +} + +bool CLayerTele::IsEmpty(const std::shared_ptr &pLayer) +{ + for(int y = 0; y < pLayer->m_Height; y++) + for(int x = 0; x < pLayer->m_Width; x++) + if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pLayer->GetTile(x, y).m_Index)) + return false; + + return true; +} + +void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) +{ + if(m_Readonly) + return; + + std::shared_ptr pTeleLayer = std::static_pointer_cast(pBrush); + int sx = ConvertX(wx); + int sy = ConvertY(wy); + if(str_comp(pTeleLayer->m_aFileName, m_pEditor->m_aFileName)) + m_pEditor->m_TeleNumber = pTeleLayer->m_TeleNum; + + bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pTeleLayer); + + for(int y = 0; y < pTeleLayer->m_Height; y++) + for(int x = 0; x < pTeleLayer->m_Width; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) + { + if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) + { + // Tele tile number is unused. Set a known value which is not 0, + // as tiles with number 0 would be ignored by previous versions. + m_pTeleTile[fy * m_Width + fx].m_Number = 255; + } + else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) + { + m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; + } + else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number) + { + m_pTeleTile[fy * m_Width + fx].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; + } + else + { + if(!m_pEditor->m_TeleNumber) + { + m_pTeleTile[fy * m_Width + fx].m_Number = 0; + m_pTeleTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + continue; + } + else + m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; + } + + m_pTeleTile[fy * m_Width + fx].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; + } + else + { + m_pTeleTile[fy * m_Width + fx].m_Number = 0; + m_pTeleTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + } + } + FlagModified(sx, sy, pTeleLayer->m_Width, pTeleLayer->m_Height); +} + +void CLayerTele::BrushFlipX() +{ + CLayerTiles::BrushFlipX(); + BrushFlipXImpl(m_pTeleTile); +} + +void CLayerTele::BrushFlipY() +{ + CLayerTiles::BrushFlipY(); + BrushFlipYImpl(m_pTeleTile); +} + +void CLayerTele::BrushRotate(float Amount) +{ + int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° + if(Rotation < 0) + Rotation += 4; + + if(Rotation == 1 || Rotation == 3) + { + // 90° rotation + CTeleTile *pTempData1 = new CTeleTile[m_Width * m_Height]; + CTile *pTempData2 = new CTile[m_Width * m_Height]; + mem_copy(pTempData1, m_pTeleTile, (size_t)m_Width * m_Height * sizeof(CTeleTile)); + mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); + CTeleTile *pDst1 = m_pTeleTile; + CTile *pDst2 = m_pTiles; + for(int x = 0; x < m_Width; ++x) + for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) + { + *pDst1 = pTempData1[y * m_Width + x]; + *pDst2 = pTempData2[y * m_Width + x]; + } + + std::swap(m_Width, m_Height); + delete[] pTempData1; + delete[] pTempData2; + } + + if(Rotation == 2 || Rotation == 3) + { + BrushFlipX(); + BrushFlipY(); + } +} + +void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) +{ + if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) + return; + + Snap(&Rect); // corrects Rect; no need of <= + + Snap(&Rect); + + int sx = ConvertX(Rect.x); + int sy = ConvertY(Rect.y); + int w = ConvertX(Rect.w); + int h = ConvertY(Rect.h); + + std::shared_ptr pLt = std::static_pointer_cast(pBrush); + + bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); + + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int TgtIndex = fy * m_Width + fx; + + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index))) + { + m_pTiles[TgtIndex].m_Index = 0; + m_pTeleTile[TgtIndex].m_Type = 0; + m_pTeleTile[TgtIndex].m_Number = 0; + } + else + { + m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; + if(pLt->m_Tele && m_pTiles[TgtIndex].m_Index > 0) + { + m_pTeleTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; + + if(!IsTeleTileNumberUsed(m_pTeleTile[TgtIndex].m_Type)) + { + // Tele tile number is unused. Set a known value which is not 0, + // as tiles with number 0 would be ignored by previous versions. + m_pTeleTile[TgtIndex].m_Number = 255; + } + else if((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumber) || m_pEditor->m_TeleNumber != pLt->m_TeleNum) + m_pTeleTile[TgtIndex].m_Number = m_pEditor->m_TeleNumber; + else + m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number; + } + } + } + } + FlagModified(sx, sy, w, h); +} + +bool CLayerTele::ContainsElementWithId(int Id) +{ + for(int y = 0; y < m_Height; ++y) + { + for(int x = 0; x < m_Width; ++x) + { + if(IsTeleTileNumberUsed(m_pTeleTile[y * m_Width + x].m_Type) && m_pTeleTile[y * m_Width + x].m_Number == Id) + { + return true; + } + } + } + + return false; +} diff --git a/src/game/editor/layer_tiles.cpp b/src/game/editor/mapitems/layer_tiles.cpp similarity index 50% rename from src/game/editor/layer_tiles.cpp rename to src/game/editor/mapitems/layer_tiles.cpp index 9c56b43f6..cee314d73 100644 --- a/src/game/editor/layer_tiles.cpp +++ b/src/game/editor/mapitems/layer_tiles.cpp @@ -1,17 +1,11 @@ /* (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 +#include -#include -#include -#include -#include - -#include "editor.h" -#include - -#include #include +#include + +#include "image.h" CLayerTiles::CLayerTiles(int w, int h) { @@ -243,7 +237,7 @@ void CLayerTiles::BrushSelecting(CUIRect Rect) m_pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); m_pEditor->Graphics()->QuadsEnd(); char aBuf[16]; - str_format(aBuf, sizeof(aBuf), "%d,%d", ConvertX(Rect.w), ConvertY(Rect.h)); + str_format(aBuf, sizeof(aBuf), "%d⨯%d", ConvertX(Rect.w), ConvertY(Rect.h)); TextRender()->Text(Rect.x + 3.0f, Rect.y + 3.0f, m_pEditor->m_ShowPicker ? 15.0f : m_pEditor->MapView()->ScaleLength(15.0f), aBuf, -1.0f); } @@ -1089,1026 +1083,3 @@ void CLayerTiles::ModifyEnvelopeIndex(FIndexModifyFunction Func) { Func(&m_ColorEnv); } - -CLayerTele::CLayerTele(int w, int h) : - CLayerTiles(w, h) -{ - str_copy(m_aName, "Tele"); - m_Tele = 1; - - m_pTeleTile = new CTeleTile[w * h]; - mem_zero(m_pTeleTile, (size_t)w * h * sizeof(CTeleTile)); -} - -CLayerTele::~CLayerTele() -{ - delete[] m_pTeleTile; -} - -void CLayerTele::Resize(int NewW, int NewH) -{ - // resize tele data - CTeleTile *pNewTeleData = new CTeleTile[NewW * NewH]; - mem_zero(pNewTeleData, (size_t)NewW * NewH * sizeof(CTeleTile)); - - // copy old data - for(int y = 0; y < minimum(NewH, m_Height); y++) - mem_copy(&pNewTeleData[y * NewW], &m_pTeleTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CTeleTile)); - - // replace old - delete[] m_pTeleTile; - m_pTeleTile = pNewTeleData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerTele::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - ShiftImpl(m_pTeleTile, Direction, m_pEditor->m_ShiftBy); -} - -bool CLayerTele::IsEmpty(const std::shared_ptr &pLayer) -{ - for(int y = 0; y < pLayer->m_Height; y++) - for(int x = 0; x < pLayer->m_Width; x++) - if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pLayer->GetTile(x, y).m_Index)) - return false; - - return true; -} - -void CLayerTele::BrushDraw(std::shared_ptr pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - std::shared_ptr pTeleLayer = std::static_pointer_cast(pBrush); - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(pTeleLayer->m_aFileName, m_pEditor->m_aFileName)) - m_pEditor->m_TeleNumber = pTeleLayer->m_TeleNum; - - bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pTeleLayer); - - for(int y = 0; y < pTeleLayer->m_Height; y++) - for(int x = 0; x < pTeleLayer->m_Width; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTeleTile(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) && pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index != TILE_AIR) - { - if(!IsTeleTileNumberUsed(pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index)) - { - // Tele tile number is unused. Set a known value which is not 0, - // as tiles with number 0 would be ignored by previous versions. - m_pTeleTile[fy * m_Width + fx].m_Number = 255; - } - else if(m_pEditor->m_TeleNumber != pTeleLayer->m_TeleNum) - { - m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; - } - else if(pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number) - { - m_pTeleTile[fy * m_Width + fx].m_Number = pTeleLayer->m_pTeleTile[y * pTeleLayer->m_Width + x].m_Number; - } - else - { - if(!m_pEditor->m_TeleNumber) - { - m_pTeleTile[fy * m_Width + fx].m_Number = 0; - m_pTeleTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - continue; - } - else - m_pTeleTile[fy * m_Width + fx].m_Number = m_pEditor->m_TeleNumber; - } - - m_pTeleTile[fy * m_Width + fx].m_Type = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pTeleLayer->m_pTiles[y * pTeleLayer->m_Width + x].m_Index; - } - else - { - m_pTeleTile[fy * m_Width + fx].m_Number = 0; - m_pTeleTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - } - } - FlagModified(sx, sy, pTeleLayer->m_Width, pTeleLayer->m_Height); -} - -void CLayerTele::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - BrushFlipXImpl(m_pTeleTile); -} - -void CLayerTele::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - BrushFlipYImpl(m_pTeleTile); -} - -void CLayerTele::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation += 4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CTeleTile *pTempData1 = new CTeleTile[m_Width * m_Height]; - CTile *pTempData2 = new CTile[m_Width * m_Height]; - mem_copy(pTempData1, m_pTeleTile, (size_t)m_Width * m_Height * sizeof(CTeleTile)); - mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); - CTeleTile *pDst1 = m_pTeleTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y * m_Width + x]; - *pDst2 = pTempData2[y * m_Width + x]; - } - - std::swap(m_Width, m_Height); - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerTele::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) -{ - if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - std::shared_ptr pLt = std::static_pointer_cast(pBrush); - - bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); - const int TgtIndex = fy * m_Width + fx; - - if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTeleTile((pLt->m_pTiles[SrcIndex]).m_Index))) - { - m_pTiles[TgtIndex].m_Index = 0; - m_pTeleTile[TgtIndex].m_Type = 0; - m_pTeleTile[TgtIndex].m_Number = 0; - } - else - { - m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; - if(pLt->m_Tele && m_pTiles[TgtIndex].m_Index > 0) - { - m_pTeleTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; - - if(!IsTeleTileNumberUsed(m_pTeleTile[TgtIndex].m_Type)) - { - // Tele tile number is unused. Set a known value which is not 0, - // as tiles with number 0 would be ignored by previous versions. - m_pTeleTile[TgtIndex].m_Number = 255; - } - else if((pLt->m_pTeleTile[SrcIndex].m_Number == 0 && m_pEditor->m_TeleNumber) || m_pEditor->m_TeleNumber != pLt->m_TeleNum) - m_pTeleTile[TgtIndex].m_Number = m_pEditor->m_TeleNumber; - else - m_pTeleTile[TgtIndex].m_Number = pLt->m_pTeleTile[SrcIndex].m_Number; - } - } - } - } - FlagModified(sx, sy, w, h); -} - -bool CLayerTele::ContainsElementWithId(int Id) -{ - for(int y = 0; y < m_Height; ++y) - { - for(int x = 0; x < m_Width; ++x) - { - if(IsTeleTileNumberUsed(m_pTeleTile[y * m_Width + x].m_Type) && m_pTeleTile[y * m_Width + x].m_Number == Id) - { - return true; - } - } - } - - return false; -} - -CLayerSpeedup::CLayerSpeedup(int w, int h) : - CLayerTiles(w, h) -{ - str_copy(m_aName, "Speedup"); - m_Speedup = 1; - - m_pSpeedupTile = new CSpeedupTile[w * h]; - mem_zero(m_pSpeedupTile, (size_t)w * h * sizeof(CSpeedupTile)); -} - -CLayerSpeedup::~CLayerSpeedup() -{ - delete[] m_pSpeedupTile; -} - -void CLayerSpeedup::Resize(int NewW, int NewH) -{ - // resize speedup data - CSpeedupTile *pNewSpeedupData = new CSpeedupTile[NewW * NewH]; - mem_zero(pNewSpeedupData, (size_t)NewW * NewH * sizeof(CSpeedupTile)); - - // copy old data - for(int y = 0; y < minimum(NewH, m_Height); y++) - mem_copy(&pNewSpeedupData[y * NewW], &m_pSpeedupTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CSpeedupTile)); - - // replace old - delete[] m_pSpeedupTile; - m_pSpeedupTile = pNewSpeedupData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerSpeedup::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - ShiftImpl(m_pSpeedupTile, Direction, m_pEditor->m_ShiftBy); -} - -bool CLayerSpeedup::IsEmpty(const std::shared_ptr &pLayer) -{ - for(int y = 0; y < pLayer->m_Height; y++) - for(int x = 0; x < pLayer->m_Width; x++) - if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pLayer->GetTile(x, y).m_Index)) - return false; - - return true; -} - -void CLayerSpeedup::BrushDraw(std::shared_ptr pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - std::shared_ptr pSpeedupLayer = std::static_pointer_cast(pBrush); - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(pSpeedupLayer->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_SpeedupAngle = pSpeedupLayer->m_SpeedupAngle; - m_pEditor->m_SpeedupForce = pSpeedupLayer->m_SpeedupForce; - m_pEditor->m_SpeedupMaxSpeed = pSpeedupLayer->m_SpeedupMaxSpeed; - } - - bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pSpeedupLayer); - - for(int y = 0; y < pSpeedupLayer->m_Height; y++) - for(int x = 0; x < pSpeedupLayer->m_Width; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSpeedupTile(pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index)) && pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index != TILE_AIR) - { - if(m_pEditor->m_SpeedupAngle != pSpeedupLayer->m_SpeedupAngle || m_pEditor->m_SpeedupForce != pSpeedupLayer->m_SpeedupForce || m_pEditor->m_SpeedupMaxSpeed != pSpeedupLayer->m_SpeedupMaxSpeed) - { - m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - } - else if(pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force) - { - m_pSpeedupTile[fy * m_Width + fx].m_Force = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Force; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_Angle; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = pSpeedupLayer->m_pSpeedupTile[y * pSpeedupLayer->m_Width + x].m_MaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - } - else if(m_pEditor->m_SpeedupForce) - { - m_pSpeedupTile[fy * m_Width + fx].m_Force = m_pEditor->m_SpeedupForce; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = m_pEditor->m_SpeedupAngle; - m_pSpeedupTile[fy * m_Width + fx].m_Type = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pSpeedupLayer->m_pTiles[y * pSpeedupLayer->m_Width + x].m_Index; - } - else - { - m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - } - } - else - { - m_pSpeedupTile[fy * m_Width + fx].m_Force = 0; - m_pSpeedupTile[fy * m_Width + fx].m_MaxSpeed = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Angle = 0; - m_pSpeedupTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - } - } - FlagModified(sx, sy, pSpeedupLayer->m_Width, pSpeedupLayer->m_Height); -} - -void CLayerSpeedup::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - BrushFlipXImpl(m_pSpeedupTile); -} - -void CLayerSpeedup::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - BrushFlipYImpl(m_pSpeedupTile); -} - -void CLayerSpeedup::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation += 4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CSpeedupTile *pTempData1 = new CSpeedupTile[m_Width * m_Height]; - CTile *pTempData2 = new CTile[m_Width * m_Height]; - mem_copy(pTempData1, m_pSpeedupTile, (size_t)m_Width * m_Height * sizeof(CSpeedupTile)); - mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); - CSpeedupTile *pDst1 = m_pSpeedupTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y * m_Width + x]; - *pDst2 = pTempData2[y * m_Width + x]; - } - - std::swap(m_Width, m_Height); - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerSpeedup::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) -{ - if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - std::shared_ptr pLt = std::static_pointer_cast(pBrush); - - bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); - const int TgtIndex = fy * m_Width + fx; - - if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSpeedupTile((pLt->m_pTiles[SrcIndex]).m_Index))) // no speed up tile chosen: reset - { - m_pTiles[TgtIndex].m_Index = 0; - m_pSpeedupTile[TgtIndex].m_Force = 0; - m_pSpeedupTile[TgtIndex].m_Angle = 0; - } - else - { - m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; - if(pLt->m_Speedup && m_pTiles[TgtIndex].m_Index > 0) - { - m_pSpeedupTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; - - if((pLt->m_pSpeedupTile[SrcIndex].m_Force == 0 && m_pEditor->m_SpeedupForce) || m_pEditor->m_SpeedupForce != pLt->m_SpeedupForce) - m_pSpeedupTile[TgtIndex].m_Force = m_pEditor->m_SpeedupForce; - else - m_pSpeedupTile[TgtIndex].m_Force = pLt->m_pSpeedupTile[SrcIndex].m_Force; - - if((pLt->m_pSpeedupTile[SrcIndex].m_Angle == 0 && m_pEditor->m_SpeedupAngle) || m_pEditor->m_SpeedupAngle != pLt->m_SpeedupAngle) - m_pSpeedupTile[TgtIndex].m_Angle = m_pEditor->m_SpeedupAngle; - else - m_pSpeedupTile[TgtIndex].m_Angle = pLt->m_pSpeedupTile[SrcIndex].m_Angle; - - if((pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed == 0 && m_pEditor->m_SpeedupMaxSpeed) || m_pEditor->m_SpeedupMaxSpeed != pLt->m_SpeedupMaxSpeed) - m_pSpeedupTile[TgtIndex].m_MaxSpeed = m_pEditor->m_SpeedupMaxSpeed; - else - m_pSpeedupTile[TgtIndex].m_MaxSpeed = pLt->m_pSpeedupTile[SrcIndex].m_MaxSpeed; - } - } - } - } - FlagModified(sx, sy, w, h); -} - -CLayerFront::CLayerFront(int w, int h) : - CLayerTiles(w, h) -{ - str_copy(m_aName, "Front"); - m_Front = 1; -} - -void CLayerFront::SetTile(int x, int y, CTile Tile) -{ - if(Tile.m_Index == TILE_THROUGH_CUT) - { - CTile nohook = {TILE_NOHOOK}; - m_pEditor->m_Map.m_pGameLayer->CLayerTiles::SetTile(x, y, nohook); // NOLINT(bugprone-parent-virtual-call) - } - else if(Tile.m_Index == TILE_AIR && CLayerTiles::GetTile(x, y).m_Index == TILE_THROUGH_CUT) - { - CTile air = {TILE_AIR}; - m_pEditor->m_Map.m_pGameLayer->CLayerTiles::SetTile(x, y, air); // NOLINT(bugprone-parent-virtual-call) - } - if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidFrontTile(Tile.m_Index)) - { - CLayerTiles::SetTile(x, y, Tile); - } - else - { - CTile air = {TILE_AIR}; - CLayerTiles::SetTile(x, y, air); - if(!m_pEditor->m_PreventUnusedTilesWasWarned) - { - m_pEditor->m_PopupEventType = CEditor::POPEVENT_PREVENTUNUSEDTILES; - m_pEditor->m_PopupEventActivated = true; - m_pEditor->m_PreventUnusedTilesWasWarned = true; - } - } -} - -void CLayerFront::Resize(int NewW, int NewH) -{ - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -CLayerSwitch::CLayerSwitch(int w, int h) : - CLayerTiles(w, h) -{ - str_copy(m_aName, "Switch"); - m_Switch = 1; - - m_pSwitchTile = new CSwitchTile[w * h]; - mem_zero(m_pSwitchTile, (size_t)w * h * sizeof(CSwitchTile)); -} - -CLayerSwitch::~CLayerSwitch() -{ - delete[] m_pSwitchTile; -} - -void CLayerSwitch::Resize(int NewW, int NewH) -{ - // resize switch data - CSwitchTile *pNewSwitchData = new CSwitchTile[NewW * NewH]; - mem_zero(pNewSwitchData, (size_t)NewW * NewH * sizeof(CSwitchTile)); - - // copy old data - for(int y = 0; y < minimum(NewH, m_Height); y++) - mem_copy(&pNewSwitchData[y * NewW], &m_pSwitchTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CSwitchTile)); - - // replace old - delete[] m_pSwitchTile; - m_pSwitchTile = pNewSwitchData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerSwitch::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - ShiftImpl(m_pSwitchTile, Direction, m_pEditor->m_ShiftBy); -} - -bool CLayerSwitch::IsEmpty(const std::shared_ptr &pLayer) -{ - for(int y = 0; y < pLayer->m_Height; y++) - for(int x = 0; x < pLayer->m_Width; x++) - if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pLayer->GetTile(x, y).m_Index)) - return false; - - return true; -} - -void CLayerSwitch::BrushDraw(std::shared_ptr pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - std::shared_ptr pSwitchLayer = std::static_pointer_cast(pBrush); - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(pSwitchLayer->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_SwitchNum = pSwitchLayer->m_SwitchNumber; - m_pEditor->m_SwitchDelay = pSwitchLayer->m_SwitchDelay; - } - - bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pSwitchLayer); - - for(int y = 0; y < pSwitchLayer->m_Height; y++) - for(int x = 0; x < pSwitchLayer->m_Width; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidSwitchTile(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) && pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index != TILE_AIR) - { - if(m_pEditor->m_SwitchNum != pSwitchLayer->m_SwitchNumber || m_pEditor->m_SwitchDelay != pSwitchLayer->m_SwitchDelay) - { - m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; - } - else if(pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number) - { - m_pSwitchTile[fy * m_Width + fx].m_Number = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Number; - m_pSwitchTile[fy * m_Width + fx].m_Delay = pSwitchLayer->m_pSwitchTile[y * pSwitchLayer->m_Width + x].m_Delay; - } - else - { - m_pSwitchTile[fy * m_Width + fx].m_Number = m_pEditor->m_SwitchNum; - m_pSwitchTile[fy * m_Width + fx].m_Delay = m_pEditor->m_SwitchDelay; - } - - m_pSwitchTile[fy * m_Width + fx].m_Type = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; - m_pSwitchTile[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; - m_pTiles[fy * m_Width + fx].m_Index = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Flags = pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Flags; - - if(!IsSwitchTileFlagsUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) - { - m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; - } - if(!IsSwitchTileNumberUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) - { - m_pSwitchTile[fy * m_Width + fx].m_Number = 0; - } - if(!IsSwitchTileDelayUsed(pSwitchLayer->m_pTiles[y * pSwitchLayer->m_Width + x].m_Index)) - { - m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; - } - } - else - { - m_pSwitchTile[fy * m_Width + fx].m_Number = 0; - m_pSwitchTile[fy * m_Width + fx].m_Type = 0; - m_pSwitchTile[fy * m_Width + fx].m_Flags = 0; - m_pSwitchTile[fy * m_Width + fx].m_Delay = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - } - } - FlagModified(sx, sy, pSwitchLayer->m_Width, pSwitchLayer->m_Height); -} - -void CLayerSwitch::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - BrushFlipXImpl(m_pSwitchTile); -} - -void CLayerSwitch::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - BrushFlipYImpl(m_pSwitchTile); -} - -void CLayerSwitch::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation += 4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CSwitchTile *pTempData1 = new CSwitchTile[m_Width * m_Height]; - CTile *pTempData2 = new CTile[m_Width * m_Height]; - mem_copy(pTempData1, m_pSwitchTile, (size_t)m_Width * m_Height * sizeof(CSwitchTile)); - mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); - CSwitchTile *pDst1 = m_pSwitchTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y * m_Width + x]; - *pDst2 = pTempData2[y * m_Width + x]; - if(IsRotatableTile(pDst2->m_Index)) - { - if(pDst2->m_Flags & TILEFLAG_ROTATE) - pDst2->m_Flags ^= (TILEFLAG_YFLIP | TILEFLAG_XFLIP); - pDst2->m_Flags ^= TILEFLAG_ROTATE; - } - } - - std::swap(m_Width, m_Height); - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerSwitch::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) -{ - if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - Snap(&Rect); - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - std::shared_ptr pLt = std::static_pointer_cast(pBrush); - - bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); - const int TgtIndex = fy * m_Width + fx; - - if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidSwitchTile((pLt->m_pTiles[SrcIndex]).m_Index))) - { - m_pTiles[TgtIndex].m_Index = 0; - m_pSwitchTile[TgtIndex].m_Type = 0; - m_pSwitchTile[TgtIndex].m_Number = 0; - m_pSwitchTile[TgtIndex].m_Delay = 0; - } - else - { - m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; - m_pSwitchTile[TgtIndex].m_Type = m_pTiles[TgtIndex].m_Index; - if(pLt->m_Switch && m_pTiles[TgtIndex].m_Index > 0) - { - if(!IsSwitchTileNumberUsed(m_pSwitchTile[TgtIndex].m_Type)) - m_pSwitchTile[TgtIndex].m_Number = 0; - else if(pLt->m_pSwitchTile[SrcIndex].m_Number == 0 || m_pEditor->m_SwitchNum != pLt->m_SwitchNumber) - m_pSwitchTile[TgtIndex].m_Number = m_pEditor->m_SwitchNum; - else - m_pSwitchTile[TgtIndex].m_Number = pLt->m_pSwitchTile[SrcIndex].m_Number; - - if(!IsSwitchTileDelayUsed(m_pSwitchTile[TgtIndex].m_Type)) - m_pSwitchTile[TgtIndex].m_Delay = 0; - else if(pLt->m_pSwitchTile[SrcIndex].m_Delay == 0 || m_pEditor->m_SwitchDelay != pLt->m_SwitchDelay) - m_pSwitchTile[TgtIndex].m_Delay = m_pEditor->m_SwitchDelay; - else - m_pSwitchTile[TgtIndex].m_Delay = pLt->m_pSwitchTile[SrcIndex].m_Delay; - - if(!IsSwitchTileFlagsUsed(m_pSwitchTile[TgtIndex].m_Type)) - m_pSwitchTile[TgtIndex].m_Flags = 0; - else - m_pSwitchTile[TgtIndex].m_Flags = pLt->m_pSwitchTile[SrcIndex].m_Flags; - } - } - } - } - FlagModified(sx, sy, w, h); -} - -bool CLayerSwitch::ContainsElementWithId(int Id) -{ - for(int y = 0; y < m_Height; ++y) - { - for(int x = 0; x < m_Width; ++x) - { - if(IsSwitchTileNumberUsed(m_pSwitchTile[y * m_Width + x].m_Type) && m_pSwitchTile[y * m_Width + x].m_Number == Id) - { - return true; - } - } - } - - return false; -} - -//------------------------------------------------------ - -CLayerTune::CLayerTune(int w, int h) : - CLayerTiles(w, h) -{ - str_copy(m_aName, "Tune"); - m_Tune = 1; - - m_pTuneTile = new CTuneTile[w * h]; - mem_zero(m_pTuneTile, (size_t)w * h * sizeof(CTuneTile)); -} - -CLayerTune::~CLayerTune() -{ - delete[] m_pTuneTile; -} - -void CLayerTune::Resize(int NewW, int NewH) -{ - // resize Tune data - CTuneTile *pNewTuneData = new CTuneTile[NewW * NewH]; - mem_zero(pNewTuneData, (size_t)NewW * NewH * sizeof(CTuneTile)); - - // copy old data - for(int y = 0; y < minimum(NewH, m_Height); y++) - mem_copy(&pNewTuneData[y * NewW], &m_pTuneTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CTuneTile)); - - // replace old - delete[] m_pTuneTile; - m_pTuneTile = pNewTuneData; - - // resize tile data - CLayerTiles::Resize(NewW, NewH); - - // resize gamelayer too - if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) - m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); -} - -void CLayerTune::Shift(int Direction) -{ - CLayerTiles::Shift(Direction); - ShiftImpl(m_pTuneTile, Direction, m_pEditor->m_ShiftBy); -} - -bool CLayerTune::IsEmpty(const std::shared_ptr &pLayer) -{ - for(int y = 0; y < pLayer->m_Height; y++) - for(int x = 0; x < pLayer->m_Width; x++) - if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pLayer->GetTile(x, y).m_Index)) - return false; - - return true; -} - -void CLayerTune::BrushDraw(std::shared_ptr pBrush, float wx, float wy) -{ - if(m_Readonly) - return; - - std::shared_ptr pTuneLayer = std::static_pointer_cast(pBrush); - int sx = ConvertX(wx); - int sy = ConvertY(wy); - if(str_comp(pTuneLayer->m_aFileName, m_pEditor->m_aFileName)) - { - m_pEditor->m_TuningNum = pTuneLayer->m_TuningNumber; - } - - bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pTuneLayer); - - for(int y = 0; y < pTuneLayer->m_Height; y++) - for(int x = 0; x < pTuneLayer->m_Width; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index)) && pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR) - { - if(m_pEditor->m_TuningNum != pTuneLayer->m_TuningNumber) - { - m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; - } - else if(pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number) - m_pTuneTile[fy * m_Width + fx].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number; - else - { - if(!m_pEditor->m_TuningNum) - { - m_pTuneTile[fy * m_Width + fx].m_Number = 0; - m_pTuneTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - continue; - } - else - m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; - } - - m_pTuneTile[fy * m_Width + fx].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; - m_pTiles[fy * m_Width + fx].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; - } - else - { - m_pTuneTile[fy * m_Width + fx].m_Number = 0; - m_pTuneTile[fy * m_Width + fx].m_Type = 0; - m_pTiles[fy * m_Width + fx].m_Index = 0; - } - } - FlagModified(sx, sy, pTuneLayer->m_Width, pTuneLayer->m_Height); -} - -void CLayerTune::BrushFlipX() -{ - CLayerTiles::BrushFlipX(); - BrushFlipXImpl(m_pTuneTile); -} - -void CLayerTune::BrushFlipY() -{ - CLayerTiles::BrushFlipY(); - BrushFlipYImpl(m_pTuneTile); -} - -void CLayerTune::BrushRotate(float Amount) -{ - int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° - if(Rotation < 0) - Rotation += 4; - - if(Rotation == 1 || Rotation == 3) - { - // 90° rotation - CTuneTile *pTempData1 = new CTuneTile[m_Width * m_Height]; - CTile *pTempData2 = new CTile[m_Width * m_Height]; - mem_copy(pTempData1, m_pTuneTile, (size_t)m_Width * m_Height * sizeof(CTuneTile)); - mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); - CTuneTile *pDst1 = m_pTuneTile; - CTile *pDst2 = m_pTiles; - for(int x = 0; x < m_Width; ++x) - for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) - { - *pDst1 = pTempData1[y * m_Width + x]; - *pDst2 = pTempData2[y * m_Width + x]; - } - - std::swap(m_Width, m_Height); - delete[] pTempData1; - delete[] pTempData2; - } - - if(Rotation == 2 || Rotation == 3) - { - BrushFlipX(); - BrushFlipY(); - } -} - -void CLayerTune::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) -{ - if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) - return; - - Snap(&Rect); // corrects Rect; no need of <= - - int sx = ConvertX(Rect.x); - int sy = ConvertY(Rect.y); - int w = ConvertX(Rect.w); - int h = ConvertY(Rect.h); - - std::shared_ptr pLt = std::static_pointer_cast(pBrush); - - bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); - - for(int y = 0; y < h; y++) - { - for(int x = 0; x < w; x++) - { - int fx = x + sx; - int fy = y + sy; - - if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) - continue; - - if(!Destructive && GetTile(fx, fy).m_Index) - continue; - - const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); - const int TgtIndex = fy * m_Width + fx; - - if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index))) - { - m_pTiles[TgtIndex].m_Index = 0; - m_pTuneTile[TgtIndex].m_Type = 0; - m_pTuneTile[TgtIndex].m_Number = 0; - } - else - { - m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; - if(pLt->m_Tune && m_pTiles[TgtIndex].m_Index > 0) - { - m_pTuneTile[TgtIndex].m_Type = m_pTiles[fy * m_Width + fx].m_Index; - - if((pLt->m_pTuneTile[SrcIndex].m_Number == 0 && m_pEditor->m_TuningNum) || m_pEditor->m_TuningNum != pLt->m_TuningNumber) - m_pTuneTile[TgtIndex].m_Number = m_pEditor->m_TuningNum; - else - m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number; - } - } - } - } - - FlagModified(sx, sy, w, h); -} diff --git a/src/game/editor/mapitems/layer_tune.cpp b/src/game/editor/mapitems/layer_tune.cpp new file mode 100644 index 000000000..13c8ee17b --- /dev/null +++ b/src/game/editor/mapitems/layer_tune.cpp @@ -0,0 +1,218 @@ +#include + +CLayerTune::CLayerTune(int w, int h) : + CLayerTiles(w, h) +{ + str_copy(m_aName, "Tune"); + m_Tune = 1; + + m_pTuneTile = new CTuneTile[w * h]; + mem_zero(m_pTuneTile, (size_t)w * h * sizeof(CTuneTile)); +} + +CLayerTune::~CLayerTune() +{ + delete[] m_pTuneTile; +} + +void CLayerTune::Resize(int NewW, int NewH) +{ + // resize Tune data + CTuneTile *pNewTuneData = new CTuneTile[NewW * NewH]; + mem_zero(pNewTuneData, (size_t)NewW * NewH * sizeof(CTuneTile)); + + // copy old data + for(int y = 0; y < minimum(NewH, m_Height); y++) + mem_copy(&pNewTuneData[y * NewW], &m_pTuneTile[y * m_Width], minimum(m_Width, NewW) * sizeof(CTuneTile)); + + // replace old + delete[] m_pTuneTile; + m_pTuneTile = pNewTuneData; + + // resize tile data + CLayerTiles::Resize(NewW, NewH); + + // resize gamelayer too + if(m_pEditor->m_Map.m_pGameLayer->m_Width != NewW || m_pEditor->m_Map.m_pGameLayer->m_Height != NewH) + m_pEditor->m_Map.m_pGameLayer->Resize(NewW, NewH); +} + +void CLayerTune::Shift(int Direction) +{ + CLayerTiles::Shift(Direction); + ShiftImpl(m_pTuneTile, Direction, m_pEditor->m_ShiftBy); +} + +bool CLayerTune::IsEmpty(const std::shared_ptr &pLayer) +{ + for(int y = 0; y < pLayer->m_Height; y++) + for(int x = 0; x < pLayer->m_Width; x++) + if(m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pLayer->GetTile(x, y).m_Index)) + return false; + + return true; +} + +void CLayerTune::BrushDraw(std::shared_ptr pBrush, float wx, float wy) +{ + if(m_Readonly) + return; + + std::shared_ptr pTuneLayer = std::static_pointer_cast(pBrush); + int sx = ConvertX(wx); + int sy = ConvertY(wy); + if(str_comp(pTuneLayer->m_aFileName, m_pEditor->m_aFileName)) + { + m_pEditor->m_TuningNum = pTuneLayer->m_TuningNumber; + } + + bool Destructive = m_pEditor->m_BrushDrawDestructive || IsEmpty(pTuneLayer); + + for(int y = 0; y < pTuneLayer->m_Height; y++) + for(int x = 0; x < pTuneLayer->m_Width; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + if((m_pEditor->m_AllowPlaceUnusedTiles || IsValidTuneTile(pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index)) && pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index != TILE_AIR) + { + if(m_pEditor->m_TuningNum != pTuneLayer->m_TuningNumber) + { + m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; + } + else if(pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number) + m_pTuneTile[fy * m_Width + fx].m_Number = pTuneLayer->m_pTuneTile[y * pTuneLayer->m_Width + x].m_Number; + else + { + if(!m_pEditor->m_TuningNum) + { + m_pTuneTile[fy * m_Width + fx].m_Number = 0; + m_pTuneTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + continue; + } + else + m_pTuneTile[fy * m_Width + fx].m_Number = m_pEditor->m_TuningNum; + } + + m_pTuneTile[fy * m_Width + fx].m_Type = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; + m_pTiles[fy * m_Width + fx].m_Index = pTuneLayer->m_pTiles[y * pTuneLayer->m_Width + x].m_Index; + } + else + { + m_pTuneTile[fy * m_Width + fx].m_Number = 0; + m_pTuneTile[fy * m_Width + fx].m_Type = 0; + m_pTiles[fy * m_Width + fx].m_Index = 0; + } + } + FlagModified(sx, sy, pTuneLayer->m_Width, pTuneLayer->m_Height); +} + +void CLayerTune::BrushFlipX() +{ + CLayerTiles::BrushFlipX(); + BrushFlipXImpl(m_pTuneTile); +} + +void CLayerTune::BrushFlipY() +{ + CLayerTiles::BrushFlipY(); + BrushFlipYImpl(m_pTuneTile); +} + +void CLayerTune::BrushRotate(float Amount) +{ + int Rotation = (round_to_int(360.0f * Amount / (pi * 2)) / 90) % 4; // 0=0°, 1=90°, 2=180°, 3=270° + if(Rotation < 0) + Rotation += 4; + + if(Rotation == 1 || Rotation == 3) + { + // 90° rotation + CTuneTile *pTempData1 = new CTuneTile[m_Width * m_Height]; + CTile *pTempData2 = new CTile[m_Width * m_Height]; + mem_copy(pTempData1, m_pTuneTile, (size_t)m_Width * m_Height * sizeof(CTuneTile)); + mem_copy(pTempData2, m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); + CTuneTile *pDst1 = m_pTuneTile; + CTile *pDst2 = m_pTiles; + for(int x = 0; x < m_Width; ++x) + for(int y = m_Height - 1; y >= 0; --y, ++pDst1, ++pDst2) + { + *pDst1 = pTempData1[y * m_Width + x]; + *pDst2 = pTempData2[y * m_Width + x]; + } + + std::swap(m_Width, m_Height); + delete[] pTempData1; + delete[] pTempData2; + } + + if(Rotation == 2 || Rotation == 3) + { + BrushFlipX(); + BrushFlipY(); + } +} + +void CLayerTune::FillSelection(bool Empty, std::shared_ptr pBrush, CUIRect Rect) +{ + if(m_Readonly || (!Empty && pBrush->m_Type != LAYERTYPE_TILES)) + return; + + Snap(&Rect); // corrects Rect; no need of <= + + int sx = ConvertX(Rect.x); + int sy = ConvertY(Rect.y); + int w = ConvertX(Rect.w); + int h = ConvertY(Rect.h); + + std::shared_ptr pLt = std::static_pointer_cast(pBrush); + + bool Destructive = m_pEditor->m_BrushDrawDestructive || Empty || IsEmpty(pLt); + + for(int y = 0; y < h; y++) + { + for(int x = 0; x < w; x++) + { + int fx = x + sx; + int fy = y + sy; + + if(fx < 0 || fx >= m_Width || fy < 0 || fy >= m_Height) + continue; + + if(!Destructive && GetTile(fx, fy).m_Index) + continue; + + const int SrcIndex = Empty ? 0 : (y * pLt->m_Width + x % pLt->m_Width) % (pLt->m_Width * pLt->m_Height); + const int TgtIndex = fy * m_Width + fx; + + if(Empty || (!m_pEditor->m_AllowPlaceUnusedTiles && !IsValidTuneTile((pLt->m_pTiles[SrcIndex]).m_Index))) + { + m_pTiles[TgtIndex].m_Index = 0; + m_pTuneTile[TgtIndex].m_Type = 0; + m_pTuneTile[TgtIndex].m_Number = 0; + } + else + { + m_pTiles[TgtIndex] = pLt->m_pTiles[SrcIndex]; + if(pLt->m_Tune && m_pTiles[TgtIndex].m_Index > 0) + { + m_pTuneTile[TgtIndex].m_Type = m_pTiles[fy * m_Width + fx].m_Index; + + if((pLt->m_pTuneTile[SrcIndex].m_Number == 0 && m_pEditor->m_TuningNum) || m_pEditor->m_TuningNum != pLt->m_TuningNumber) + m_pTuneTile[TgtIndex].m_Number = m_pEditor->m_TuningNum; + else + m_pTuneTile[TgtIndex].m_Number = pLt->m_pTuneTile[SrcIndex].m_Number; + } + } + } + } + + FlagModified(sx, sy, w, h); +} diff --git a/src/game/editor/mapitems/map.cpp b/src/game/editor/mapitems/map.cpp new file mode 100644 index 000000000..48c58acd1 --- /dev/null +++ b/src/game/editor/mapitems/map.cpp @@ -0,0 +1,181 @@ +#include + +#include "image.h" + +void CEditorMap::OnModify() +{ + m_Modified = true; + m_ModifiedAuto = true; + m_LastModifiedTime = m_pEditor->Client()->GlobalTime(); +} + +void CEditorMap::DeleteEnvelope(int Index) +{ + if(Index < 0 || Index >= (int)m_vpEnvelopes.size()) + return; + + OnModify(); + + VisitEnvelopeReferences([Index](int &ElementIndex) { + if(ElementIndex == Index) + ElementIndex = -1; + else if(ElementIndex > Index) + ElementIndex--; + }); + + m_vpEnvelopes.erase(m_vpEnvelopes.begin() + Index); +} + +void CEditorMap::SwapEnvelopes(int Index0, int Index1) +{ + if(Index0 < 0 || Index0 >= (int)m_vpEnvelopes.size()) + return; + if(Index1 < 0 || Index1 >= (int)m_vpEnvelopes.size()) + return; + if(Index0 == Index1) + return; + + OnModify(); + + VisitEnvelopeReferences([Index0, Index1](int &ElementIndex) { + if(ElementIndex == Index0) + ElementIndex = Index1; + else if(ElementIndex == Index1) + ElementIndex = Index0; + }); + + std::swap(m_vpEnvelopes[Index0], m_vpEnvelopes[Index1]); +} + +template +void CEditorMap::VisitEnvelopeReferences(F &&Visitor) +{ + for(auto &pGroup : m_vpGroups) + { + for(auto &pLayer : pGroup->m_vpLayers) + { + if(pLayer->m_Type == LAYERTYPE_QUADS) + { + std::shared_ptr pLayerQuads = std::static_pointer_cast(pLayer); + for(auto &Quad : pLayerQuads->m_vQuads) + { + Visitor(Quad.m_PosEnv); + Visitor(Quad.m_ColorEnv); + } + } + else if(pLayer->m_Type == LAYERTYPE_TILES) + { + std::shared_ptr pLayerTiles = std::static_pointer_cast(pLayer); + Visitor(pLayerTiles->m_ColorEnv); + } + else if(pLayer->m_Type == LAYERTYPE_SOUNDS) + { + std::shared_ptr pLayerSounds = std::static_pointer_cast(pLayer); + for(auto &Source : pLayerSounds->m_vSources) + { + Visitor(Source.m_PosEnv); + Visitor(Source.m_SoundEnv); + } + } + } + } +} + +void CEditorMap::MakeGameLayer(const std::shared_ptr &pLayer) +{ + m_pGameLayer = std::static_pointer_cast(pLayer); + m_pGameLayer->m_pEditor = m_pEditor; +} + +void CEditorMap::MakeGameGroup(std::shared_ptr pGroup) +{ + m_pGameGroup = std::move(pGroup); + m_pGameGroup->m_GameGroup = true; + str_copy(m_pGameGroup->m_aName, "Game"); +} + +void CEditorMap::Clean() +{ + m_vpGroups.clear(); + m_vpEnvelopes.clear(); + m_vpImages.clear(); + m_vpSounds.clear(); + + m_MapInfo.Reset(); + m_MapInfoTmp.Reset(); + + m_vSettings.clear(); + + m_pGameLayer = nullptr; + m_pGameGroup = nullptr; + + m_Modified = false; + m_ModifiedAuto = false; + + m_pTeleLayer = nullptr; + m_pSpeedupLayer = nullptr; + m_pFrontLayer = nullptr; + m_pSwitchLayer = nullptr; + m_pTuneLayer = nullptr; +} + +void CEditorMap::CreateDefault(IGraphics::CTextureHandle EntitiesTexture) +{ + // add background + std::shared_ptr pGroup = NewGroup(); + pGroup->m_ParallaxX = 0; + pGroup->m_ParallaxY = 0; + pGroup->m_CustomParallaxZoom = 0; + pGroup->m_ParallaxZoom = 0; + std::shared_ptr pLayer = std::make_shared(); + pLayer->m_pEditor = m_pEditor; + CQuad *pQuad = pLayer->NewQuad(0, 0, 1600, 1200); + pQuad->m_aColors[0].r = pQuad->m_aColors[1].r = 94; + pQuad->m_aColors[0].g = pQuad->m_aColors[1].g = 132; + pQuad->m_aColors[0].b = pQuad->m_aColors[1].b = 174; + pQuad->m_aColors[2].r = pQuad->m_aColors[3].r = 204; + pQuad->m_aColors[2].g = pQuad->m_aColors[3].g = 232; + pQuad->m_aColors[2].b = pQuad->m_aColors[3].b = 255; + pGroup->AddLayer(pLayer); + + // add game layer and reset front, tele, speedup, tune and switch layer pointers + MakeGameGroup(NewGroup()); + MakeGameLayer(std::make_shared(50, 50)); + m_pGameGroup->AddLayer(m_pGameLayer); + + m_pFrontLayer = nullptr; + m_pTeleLayer = nullptr; + m_pSpeedupLayer = nullptr; + m_pSwitchLayer = nullptr; + m_pTuneLayer = nullptr; +} + +void CEditorMap::MakeTeleLayer(const std::shared_ptr &pLayer) +{ + m_pTeleLayer = std::static_pointer_cast(pLayer); + m_pTeleLayer->m_pEditor = m_pEditor; +} + +void CEditorMap::MakeSpeedupLayer(const std::shared_ptr &pLayer) +{ + m_pSpeedupLayer = std::static_pointer_cast(pLayer); + m_pSpeedupLayer->m_pEditor = m_pEditor; +} + +void CEditorMap::MakeFrontLayer(const std::shared_ptr &pLayer) +{ + m_pFrontLayer = std::static_pointer_cast(pLayer); + m_pFrontLayer->m_pEditor = m_pEditor; +} + +void CEditorMap::MakeSwitchLayer(const std::shared_ptr &pLayer) +{ + m_pSwitchLayer = std::static_pointer_cast(pLayer); + m_pSwitchLayer->m_pEditor = m_pEditor; +} + +void CEditorMap::MakeTuneLayer(const std::shared_ptr &pLayer) +{ + m_pTuneLayer = std::static_pointer_cast(pLayer); + m_pTuneLayer->m_pEditor = m_pEditor; +} diff --git a/src/game/editor/io.cpp b/src/game/editor/mapitems/map_io.cpp similarity index 91% rename from src/game/editor/io.cpp rename to src/game/editor/mapitems/map_io.cpp index 836776a4d..6224494f8 100644 --- a/src/game/editor/io.cpp +++ b/src/game/editor/mapitems/map_io.cpp @@ -1,6 +1,5 @@ -/* (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 "editor.h" +#include + #include #include #include @@ -12,6 +11,9 @@ #include #include +#include "image.h" +#include "sound.h" + template static int MakeVersion(int i, const T &v) { @@ -31,15 +33,6 @@ struct CSoundSource_DEPRECATED int m_SoundEnvOffset; }; -bool CEditor::Save(const char *pFilename) -{ - // Check if file with this name is already being saved at the moment - if(std::any_of(std::begin(m_WriterFinishJobs), std::end(m_WriterFinishJobs), [pFilename](const std::shared_ptr &Job) { return str_comp(pFilename, Job->GetRealFileName()) == 0; })) - return false; - - return m_Map.Save(pFilename); -} - bool CEditorMap::Save(const char *pFileName) { char aFileNameTmp[IO_MAX_PATH_LENGTH]; @@ -130,24 +123,26 @@ bool CEditorMap::Save(const char *pFileName) } else { + const size_t PixelSize = CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); + const size_t DataSize = (size_t)Item.m_Width * Item.m_Height * PixelSize; if(pImg->m_Format == CImageInfo::FORMAT_RGB) { // Convert to RGBA - unsigned char *pDataRGBA = (unsigned char *)malloc((size_t)Item.m_Width * Item.m_Height * 4); + unsigned char *pDataRGBA = (unsigned char *)malloc(DataSize); unsigned char *pDataRGB = (unsigned char *)pImg->m_pData; for(int j = 0; j < Item.m_Width * Item.m_Height; j++) { - pDataRGBA[j * 4] = pDataRGB[j * 3]; - pDataRGBA[j * 4 + 1] = pDataRGB[j * 3 + 1]; - pDataRGBA[j * 4 + 2] = pDataRGB[j * 3 + 2]; - pDataRGBA[j * 4 + 3] = 255; + pDataRGBA[j * PixelSize] = pDataRGB[j * 3]; + pDataRGBA[j * PixelSize + 1] = pDataRGB[j * 3 + 1]; + pDataRGBA[j * PixelSize + 2] = pDataRGB[j * 3 + 2]; + pDataRGBA[j * PixelSize + 3] = 255; } - Item.m_ImageData = Writer.AddData(Item.m_Width * Item.m_Height * 4, pDataRGBA); + Item.m_ImageData = Writer.AddData(DataSize, pDataRGBA); free(pDataRGBA); } else { - Item.m_ImageData = Writer.AddData(Item.m_Width * Item.m_Height * 4, pImg->m_pData); + Item.m_ImageData = Writer.AddData(DataSize, pImg->m_pData); } } Writer.AddItem(MAPITEMTYPE_IMAGE, i, sizeof(Item), &Item); @@ -364,7 +359,7 @@ bool CEditorMap::Save(const char *pFileName) // save points m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving envelope points"); - bool BezierUsed = true; + bool BezierUsed = false; for(const auto &pEnvelope : m_vpEnvelopes) { for(const auto &Point : pEnvelope->m_vPoints) @@ -426,30 +421,6 @@ bool CEditorMap::Save(const char *pFileName) return true; } -bool CEditor::Load(const char *pFileName, int StorageType) -{ - const auto &&ErrorHandler = [this](const char *pErrorMessage) { - ShowFileDialogError("%s", pErrorMessage); - Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor/load", pErrorMessage); - }; - - Reset(); - bool Result = m_Map.Load(pFileName, StorageType, std::move(ErrorHandler)); - if(Result) - { - str_copy(m_aFileName, pFileName); - SortImages(); - SelectGameLayer(); - MapView()->OnMapLoad(); - } - else - { - m_aFileName[0] = 0; - Reset(); - } - return Result; -} - bool CEditorMap::Load(const char *pFileName, int StorageType, const std::function &ErrorHandler) { CDataFileReader DataFile; @@ -518,7 +489,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio std::shared_ptr pImg = std::make_shared(m_pEditor); pImg->m_External = pItem->m_External; - const int Format = pItem->m_Version < CMapItemImage_v2::CURRENT_VERSION ? CImageInfo::FORMAT_RGBA : pItem->m_Format; + const CImageInfo::EImageFormat Format = pItem->m_Version < CMapItemImage_v2::CURRENT_VERSION ? CImageInfo::FORMAT_RGBA : CImageInfo::ImageFormatFromInt(pItem->m_Format); if(pImg->m_External || (Format != CImageInfo::FORMAT_RGB && Format != CImageInfo::FORMAT_RGBA)) { char aBuf[IO_MAX_PATH_LENGTH]; @@ -532,7 +503,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio int TextureLoadFlag = m_pEditor->Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) TextureLoadFlag = 0; - pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, CImageInfo::FORMAT_AUTO, TextureLoadFlag, aBuf); + pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(ImgInfo.m_Width, ImgInfo.m_Height, ImgInfo.m_Format, ImgInfo.m_pData, TextureLoadFlag, aBuf); ImgInfo.m_pData = nullptr; pImg->m_External = 1; } @@ -545,13 +516,13 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio // copy image data void *pData = DataFile.GetData(pItem->m_ImageData); - const size_t DataSize = (size_t)pImg->m_Width * pImg->m_Height * 4; + const size_t DataSize = (size_t)pImg->m_Width * pImg->m_Height * CImageInfo::PixelSize(Format); pImg->m_pData = malloc(DataSize); mem_copy(pImg->m_pData, pData, DataSize); int TextureLoadFlag = m_pEditor->Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0) TextureLoadFlag = 0; - pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, pImg->m_Format, pImg->m_pData, CImageInfo::FORMAT_AUTO, TextureLoadFlag); + pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(pImg->m_Width, pImg->m_Height, pImg->m_Format, pImg->m_pData, TextureLoadFlag); } // copy image name @@ -580,7 +551,6 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio // copy base info std::shared_ptr pSound = std::make_shared(m_pEditor); - if(pItem->m_External) { char aBuf[IO_MAX_PATH_LENGTH]; @@ -1044,58 +1014,3 @@ void CEditorMap::PerformSanityChecks(const std::functionPrint(IConsole::OUTPUT_LEVEL_STANDARD, "editor/append", pErrorMessage); - }; - if(!NewMap.Load(pFileName, StorageType, std::move(ErrorHandler))) - return false; - - // modify indices - static const auto &&s_ModifyAddIndex = [](int AddAmount) { - return [AddAmount](int *pIndex) { - if(*pIndex >= 0) - *pIndex += AddAmount; - }; - }; - NewMap.ModifyImageIndex(s_ModifyAddIndex(m_Map.m_vpImages.size())); - NewMap.ModifySoundIndex(s_ModifyAddIndex(m_Map.m_vpSounds.size())); - NewMap.ModifyEnvelopeIndex(s_ModifyAddIndex(m_Map.m_vpEnvelopes.size())); - - // transfer images - for(const auto &pImage : NewMap.m_vpImages) - m_Map.m_vpImages.push_back(pImage); - NewMap.m_vpImages.clear(); - - // transfer sounds - for(const auto &pSound : NewMap.m_vpSounds) - m_Map.m_vpSounds.push_back(pSound); - NewMap.m_vpSounds.clear(); - - // transfer envelopes - for(const auto &pEnvelope : NewMap.m_vpEnvelopes) - m_Map.m_vpEnvelopes.push_back(pEnvelope); - NewMap.m_vpEnvelopes.clear(); - - // transfer groups - for(const auto &pGroup : NewMap.m_vpGroups) - { - if(pGroup != NewMap.m_pGameGroup) - { - pGroup->m_pMap = &m_Map; - m_Map.m_vpGroups.push_back(pGroup); - } - } - NewMap.m_vpGroups.clear(); - - SortImages(); - - // all done \o/ - return true; -} diff --git a/src/game/editor/mapitems/sound.cpp b/src/game/editor/mapitems/sound.cpp new file mode 100644 index 000000000..6465f915b --- /dev/null +++ b/src/game/editor/mapitems/sound.cpp @@ -0,0 +1,15 @@ +#include "sound.h" + +#include + +CEditorSound::CEditorSound(CEditor *pEditor) +{ + Init(pEditor); +} + +CEditorSound::~CEditorSound() +{ + Sound()->UnloadSample(m_SoundID); + free(m_pData); + m_pData = nullptr; +} diff --git a/src/game/editor/mapitems/sound.h b/src/game/editor/mapitems/sound.h new file mode 100644 index 000000000..681f9ad30 --- /dev/null +++ b/src/game/editor/mapitems/sound.h @@ -0,0 +1,21 @@ +#ifndef GAME_EDITOR_MAPITEMS_SOUND_H +#define GAME_EDITOR_MAPITEMS_SOUND_H + +#include + +#include + +class CEditorSound : public CEditorComponent +{ +public: + explicit CEditorSound(CEditor *pEditor); + ~CEditorSound(); + + int m_SoundID = 0; + char m_aName[IO_MAX_PATH_LENGTH] = ""; + + void *m_pData = nullptr; + unsigned m_DataSize = 0; +}; + +#endif diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index 0b9ac96b1..e99d24c6d 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include "editor.h" @@ -198,6 +200,15 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuTools(void *pContext, CUIRect Vi pEditor->UI()->DoPopupMenu(&s_PopupGotoId, Slot.x, Slot.y + Slot.h, 120, 52, pEditor, PopupGoto); } + static int s_TileartButton = 0; + View.HSplitTop(2.0f, nullptr, &View); + View.HSplitTop(12.0f, &Slot, &View); + if(pEditor->DoButton_MenuItem(&s_TileartButton, "Add tileart", 0, &Slot, 0, "Generate tileart from image")) + { + pEditor->InvokeFileDialog(IStorage::TYPE_ALL, FILETYPE_IMG, "Add tileart", "Open", "mapres", false, CallbackAddTileart, pEditor); + return CUI::POPUP_CLOSE_CURRENT; + } + return CUI::POPUP_KEEP_OPEN; } @@ -1198,10 +1209,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast(pContext); - std::vector> vpQuads = pEditor->GetSelectedQuadPoints(); + std::vector vpQuads = pEditor->GetSelectedQuads(); if(!in_range(pEditor->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) return CUI::POPUP_CLOSE_CURRENT; - CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex].first; + CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex]; enum { @@ -1241,25 +1252,25 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, pEditor->m_Map.OnModify(); } - for(auto [pQuad, SelectedPoints] : vpQuads) + for(CQuad *pQuad : vpQuads) { if(Prop == PROP_POS_X) { for(int v = 0; v < 4; v++) - if(SelectedPoints & (1 << v)) + if(pEditor->IsQuadSelected(v)) pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X); } else if(Prop == PROP_POS_Y) { for(int v = 0; v < 4; v++) - if(SelectedPoints & (1 << v)) + if(pEditor->IsQuadSelected(v)) pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y); } else if(Prop == PROP_COLOR) { for(int v = 0; v < 4; v++) { - if(SelectedPoints & (1 << v)) + if(pEditor->IsQuadSelected(v)) { pQuad->m_aColors[v].r = (NewVal >> 24) & 0xff; pQuad->m_aColors[v].g = (NewVal >> 16) & 0xff; @@ -1271,13 +1282,13 @@ CUI::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, else if(Prop == PROP_TEX_U) { for(int v = 0; v < 4; v++) - if(SelectedPoints & (1 << v)) + if(pEditor->IsQuadSelected(v)) pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f); } else if(Prop == PROP_TEX_V) { for(int v = 0; v < 4; v++) - if(SelectedPoints & (1 << v)) + if(pEditor->IsQuadSelected(v)) pQuad->m_aTexcoords[v].y = f2fx(fx2f(pQuad->m_aTexcoords[v].y) + (NewVal - TextureV) / 1024.0f); } } @@ -1724,7 +1735,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, pTitle = "Exit the editor"; pMessage = "The map contains unsaved data, you might want to save it before you exit the editor.\n\nContinue anyway?"; } - else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT) + else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT || pEditor->m_PopupEventType == POPEVENT_LOADDROP) { pTitle = "Load map"; pMessage = "The map contains unsaved data, you might want to save it before you load a new map.\n\nContinue anyway?"; @@ -1764,6 +1775,21 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, pTitle = "Place border tiles"; pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?"; } + else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE) + { + pTitle = "Big image"; + pMessage = "The selected image is big. Converting it to tileart may take some time.\n\nContinue anyway?"; + } + else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS) + { + pTitle = "Many colors"; + pMessage = "The selected image contains many colors, which will lead to a big mapfile. You may want to consider reducing the number of colors.\n\nContinue anyway?"; + } + else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_TOO_MANY_COLORS) + { + pTitle = "Too many colors"; + pMessage = "The client only supports 64 images but more would be needed to add the selected image as tileart."; + } else { dbg_assert(false, "m_PopupEventType invalid"); @@ -1786,12 +1812,21 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, // button bar ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar); - if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX) + if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX && pEditor->m_PopupEventType != POPEVENT_PIXELART_TOO_MANY_COLORS) { static int s_CancelButton = 0; if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, 0, nullptr)) { + if(pEditor->m_PopupEventType == POPEVENT_LOADDROP) + pEditor->m_aFileNamePending[0] = 0; pEditor->m_PopupEventWasActivated = false; + + if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE || pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS) + { + free(pEditor->m_TileartImageInfo.m_pData); + pEditor->m_TileartImageInfo.m_pData = nullptr; + } + return CUI::POPUP_CLOSE_CURRENT; } } @@ -1812,6 +1847,13 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, { pEditor->LoadCurrentMap(); } + else if(pEditor->m_PopupEventType == POPEVENT_LOADDROP) + { + int Result = pEditor->Load(pEditor->m_aFileNamePending, IStorage::TYPE_ALL_OR_ABSOLUTE); + if(!Result) + dbg_msg("editor", "editing passed map file '%s' failed", pEditor->m_aFileNamePending); + pEditor->m_aFileNamePending[0] = 0; + } else if(pEditor->m_PopupEventType == POPEVENT_NEW) { pEditor->Reset(); @@ -1831,6 +1873,14 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, { pEditor->PlaceBorderTiles(); } + else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE) + { + pEditor->TileartCheckColors(); + } + else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS) + { + pEditor->AddTileart(); + } pEditor->m_PopupEventWasActivated = false; return CUI::POPUP_CLOSE_CURRENT; } @@ -2368,7 +2418,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupEntities(void *pContext, CUIRect Vie char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "editor/entities/%s.png", pName); - pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(aBuf, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, pEditor->GetTextureUsageFlag()); + pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(aBuf, IStorage::TYPE_ALL, pEditor->GetTextureUsageFlag()); return CUI::POPUP_CLOSE_CURRENT; } } diff --git a/src/game/editor/proof_mode.cpp b/src/game/editor/proof_mode.cpp index 5f957a499..779668835 100644 --- a/src/game/editor/proof_mode.cpp +++ b/src/game/editor/proof_mode.cpp @@ -119,28 +119,28 @@ void CProofMode::RenderScreenSizes() if(i == 0) { - IGraphics::CLineItem Array[2] = { + IGraphics::CLineItem aArray[2] = { IGraphics::CLineItem(aPoints[0], aPoints[1], aPoints[2], aPoints[1]), IGraphics::CLineItem(aPoints[0], aPoints[3], aPoints[2], aPoints[3])}; - Graphics()->LinesDraw(Array, 2); + Graphics()->LinesDraw(aArray, std::size(aArray)); } if(i != 0) { - IGraphics::CLineItem Array[4] = { + IGraphics::CLineItem aArray[4] = { IGraphics::CLineItem(aPoints[0], aPoints[1], aLastPoints[0], aLastPoints[1]), IGraphics::CLineItem(aPoints[2], aPoints[1], aLastPoints[2], aLastPoints[1]), IGraphics::CLineItem(aPoints[0], aPoints[3], aLastPoints[0], aLastPoints[3]), IGraphics::CLineItem(aPoints[2], aPoints[3], aLastPoints[2], aLastPoints[3])}; - Graphics()->LinesDraw(Array, 4); + Graphics()->LinesDraw(aArray, std::size(aArray)); } if(i == NumSteps) { - IGraphics::CLineItem Array[2] = { + IGraphics::CLineItem aArray[2] = { IGraphics::CLineItem(aPoints[0], aPoints[1], aPoints[0], aPoints[3]), IGraphics::CLineItem(aPoints[2], aPoints[1], aPoints[2], aPoints[3])}; - Graphics()->LinesDraw(Array, 2); + Graphics()->LinesDraw(aArray, std::size(aArray)); } mem_copy(aLastPoints, aPoints, sizeof(aPoints)); @@ -166,12 +166,12 @@ void CProofMode::RenderScreenSizes() r.w = aPoints[2] - aPoints[0]; r.h = aPoints[3] - aPoints[1]; - IGraphics::CLineItem Array[4] = { + IGraphics::CLineItem aArray[4] = { IGraphics::CLineItem(r.x, r.y, r.x + r.w, r.y), IGraphics::CLineItem(r.x + r.w, r.y, r.x + r.w, r.y + r.h), IGraphics::CLineItem(r.x + r.w, r.y + r.h, r.x, r.y + r.h), IGraphics::CLineItem(r.x, r.y + r.h, r.x, r.y)}; - Graphics()->LinesDraw(Array, 4); + Graphics()->LinesDraw(aArray, std::size(aArray)); Graphics()->SetColor(0, 1, 0, 1); } } @@ -188,16 +188,16 @@ void CProofMode::RenderScreenSizes() { Graphics()->SetColor(0, 1, 0, 0.3f); - std::set indices; + std::set Indices; for(int i = 0; i < (int)m_vMenuBackgroundPositions.size(); i++) - indices.insert(i); + Indices.insert(i); - while(!indices.empty()) + while(!Indices.empty()) { - int i = *indices.begin(); - indices.erase(i); + int i = *Indices.begin(); + Indices.erase(i); for(int k : m_vMenuBackgroundCollisions.at(i)) - indices.erase(k); + Indices.erase(k); vec2 Pos = m_vMenuBackgroundPositions[i]; Pos += WorldOffset - m_vMenuBackgroundPositions[m_CurrentMenuProofIndex]; diff --git a/src/game/editor/tileart.cpp b/src/game/editor/tileart.cpp new file mode 100644 index 000000000..c798bcbe2 --- /dev/null +++ b/src/game/editor/tileart.cpp @@ -0,0 +1,253 @@ +#include "editor.h" + +#include + +#include + +bool operator<(const ColorRGBA &Left, const ColorRGBA &Right) +{ + if(Left.r != Right.r) + return Left.r < Right.r; + else if(Left.g != Right.g) + return Left.g < Right.g; + else if(Left.b != Right.b) + return Left.b < Right.b; + else + return Left.a < Right.a; +} + +static ColorRGBA GetPixelColor(const CImageInfo &Image, size_t x, size_t y) +{ + uint8_t *pData = static_cast(Image.m_pData); + const size_t PixelSize = Image.PixelSize(); + const size_t PixelStartIndex = x * PixelSize + (Image.m_Width * PixelSize * y); + + ColorRGBA Color = {255, 255, 255, 255}; + if(PixelSize == 1) + { + Color.a = pData[PixelStartIndex]; + } + else + { + Color.r = pData[PixelStartIndex + 0]; + Color.g = pData[PixelStartIndex + 1]; + Color.b = pData[PixelStartIndex + 2]; + + if(PixelSize == 4) + Color.a = pData[PixelStartIndex + 3]; + } + + return Color; +} + +static void SetPixelColor(CImageInfo *pImage, size_t x, size_t y, ColorRGBA Color) +{ + uint8_t *pData = static_cast(pImage->m_pData); + const size_t PixelSize = pImage->PixelSize(); + const size_t PixelStartIndex = x * PixelSize + (pImage->m_Width * PixelSize * y); + + if(PixelSize == 1) + { + pData[PixelStartIndex] = Color.a; + } + else + { + pData[PixelStartIndex + 0] = Color.r; + pData[PixelStartIndex + 1] = Color.g; + pData[PixelStartIndex + 2] = Color.b; + + if(PixelSize == 4) + pData[PixelStartIndex + 3] = Color.a; + } +} + +static std::vector GetUniqueColors(const CImageInfo &Image) +{ + std::set ColorSet; + std::vector vUniqueColors; + for(int x = 0; x < Image.m_Width; x++) + { + for(int y = 0; y < Image.m_Height; y++) + { + ColorRGBA Color = GetPixelColor(Image, x, y); + if(Color.a > 0 && ColorSet.insert(Color).second) + vUniqueColors.push_back(Color); + } + } + std::sort(vUniqueColors.begin(), vUniqueColors.end()); + + return vUniqueColors; +} + +constexpr int NumTilesRow = 16; +constexpr int NumTilesColumn = 16; +constexpr int NumTiles = NumTilesRow * NumTilesColumn; +constexpr int TileSize = 64; + +static int GetColorIndex(const std::array &ColorGroup, ColorRGBA Color) +{ + std::array::const_iterator Iterator = std::find(ColorGroup.begin(), ColorGroup.end(), Color); + if(Iterator == ColorGroup.end()) + return 0; + return Iterator - ColorGroup.begin(); +} + +static std::vector> GroupColors(const std::vector &vColors) +{ + std::vector> vaColorGroups; + + for(size_t i = 0; i < vColors.size(); i += NumTiles - 1) + { + auto &Group = vaColorGroups.emplace_back(); + std::copy_n(vColors.begin() + i, std::min(NumTiles - 1, vColors.size() - i), Group.begin() + 1); + } + + return vaColorGroups; +} + +static void SetColorTile(CImageInfo *pImage, int x, int y, ColorRGBA Color) +{ + for(int i = 0; i < TileSize; i++) + { + for(int j = 0; j < TileSize; j++) + SetPixelColor(pImage, x * TileSize + i, y * TileSize + j, Color); + } +} + +static CImageInfo ColorGroupToImage(const std::array &aColorGroup) +{ + CImageInfo Image; + Image.m_Width = NumTilesRow * TileSize; + Image.m_Height = NumTilesColumn * TileSize; + Image.m_Format = CImageInfo::FORMAT_RGBA; + + uint8_t *pData = static_cast(malloc(static_cast(Image.m_Width) * Image.m_Height * 4 * sizeof(uint8_t))); + Image.m_pData = pData; + + for(int y = 0; y < NumTilesColumn; y++) + { + for(int x = 0; x < NumTilesRow; x++) + { + int ColorIndex = x + NumTilesRow * y; + SetColorTile(&Image, x, y, aColorGroup[ColorIndex]); + } + } + + return Image; +} + +static std::vector ColorGroupsToImages(const std::vector> &vaColorGroups) +{ + std::vector vImages; + vImages.reserve(vaColorGroups.size()); + for(const auto &ColorGroup : vaColorGroups) + vImages.push_back(ColorGroupToImage(ColorGroup)); + + return vImages; +} + +static std::shared_ptr ImageInfoToEditorImage(CEditor *pEditor, const CImageInfo &Image, const char *pName) +{ + std::shared_ptr pEditorImage = std::make_shared(pEditor); + pEditorImage->m_Width = Image.m_Width; + pEditorImage->m_Height = Image.m_Height; + pEditorImage->m_Format = Image.m_Format; + pEditorImage->m_pData = Image.m_pData; + + int TextureLoadFlag = pEditor->Graphics()->HasTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; + pEditorImage->m_Texture = pEditor->Graphics()->LoadTextureRaw(Image.m_Width, Image.m_Height, Image.m_Format, Image.m_pData, TextureLoadFlag, pName); + pEditorImage->m_External = 0; + str_copy(pEditorImage->m_aName, pName); + + return pEditorImage; +} + +static std::shared_ptr AddLayerWithImage(CEditor *pEditor, const std::shared_ptr &pGroup, int Width, int Height, const CImageInfo &Image, const char *pName) +{ + std::shared_ptr pEditorImage = ImageInfoToEditorImage(pEditor, Image, pName); + pEditor->m_Map.m_vpImages.push_back(pEditorImage); + + std::shared_ptr pLayer = std::make_shared(Width, Height); + str_copy(pLayer->m_aName, pName); + pLayer->m_pEditor = pEditor; + pLayer->m_Image = pEditor->m_Map.m_vpImages.size() - 1; + pGroup->AddLayer(pLayer); + + return pLayer; +} + +static void SetTilelayerIndices(const std::shared_ptr &pLayer, const std::array &aColorGroup, const CImageInfo &Image) +{ + for(int x = 0; x < pLayer->m_Width; x++) + { + for(int y = 0; y < pLayer->m_Height; y++) + pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(aColorGroup, GetPixelColor(Image, x, y)); + } +} + +void CEditor::AddTileart() +{ + std::shared_ptr pGroup = m_Map.NewGroup(); + str_copy(pGroup->m_aName, m_aTileartFilename); + + auto vUniqueColors = GetUniqueColors(m_TileartImageInfo); + auto vaColorGroups = GroupColors(vUniqueColors); + auto vColorImages = ColorGroupsToImages(vaColorGroups); + char aImageName[IO_MAX_PATH_LENGTH]; + for(size_t i = 0; i < vColorImages.size(); i++) + { + str_format(aImageName, sizeof(aImageName), "%s %" PRIzu, m_aTileartFilename, i + 1); + std::shared_ptr pLayer = AddLayerWithImage(this, pGroup, m_TileartImageInfo.m_Width, m_TileartImageInfo.m_Height, vColorImages[i], aImageName); + SetTilelayerIndices(pLayer, vaColorGroups[i], m_TileartImageInfo); + } + SortImages(); + + free(m_TileartImageInfo.m_pData); + m_TileartImageInfo.m_pData = nullptr; + m_Map.OnModify(); + m_Dialog = DIALOG_NONE; +} + +void CEditor::TileartCheckColors() +{ + auto vUniqueColors = GetUniqueColors(m_TileartImageInfo); + int NumColorGroups = std::ceil(vUniqueColors.size() / 255.0f); + if(m_Map.m_vpImages.size() + NumColorGroups >= 64) + { + m_PopupEventType = CEditor::POPEVENT_PIXELART_TOO_MANY_COLORS; + m_PopupEventActivated = true; + free(m_TileartImageInfo.m_pData); + m_TileartImageInfo.m_pData = nullptr; + } + else if(NumColorGroups > 1) + { + m_PopupEventType = CEditor::POPEVENT_PIXELART_MANY_COLORS; + m_PopupEventActivated = true; + } + else + AddTileart(); +} + +bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser) +{ + CEditor *pEditor = (CEditor *)pUser; + + if(!pEditor->Graphics()->LoadPNG(&pEditor->m_TileartImageInfo, pFilepath, StorageType)) + { + pEditor->ShowFileDialogError("Failed to load image from file '%s'.", pFilepath); + return false; + } + + IStorage::StripPathAndExtension(pFilepath, pEditor->m_aTileartFilename, sizeof(pEditor->m_aTileartFilename)); + if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000) + { + pEditor->m_PopupEventType = CEditor::POPEVENT_PIXELART_BIG_IMAGE; + pEditor->m_PopupEventActivated = true; + return false; + } + else + { + pEditor->TileartCheckColors(); + return false; + } +} diff --git a/src/game/gamecore.cpp b/src/game/gamecore.cpp index 0d40db737..bf7e80c69 100644 --- a/src/game/gamecore.cpp +++ b/src/game/gamecore.cpp @@ -97,6 +97,13 @@ void CCharacterCore::Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore Reset(); } +void CCharacterCore::SetCoreWorld(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams) +{ + m_pWorld = pWorld; + m_pCollision = pCollision; + m_pTeams = pTeams; +} + void CCharacterCore::Reset() { m_Pos = vec2(0, 0); diff --git a/src/game/gamecore.h b/src/game/gamecore.h index 42c9ddbc2..2b5ed4947 100644 --- a/src/game/gamecore.h +++ b/src/game/gamecore.h @@ -220,7 +220,6 @@ public: class CCharacterCore { - friend class CCharacter; CWorldCore *m_pWorld = nullptr; CCollision *m_pCollision; std::map> *m_pTeleOuts; @@ -236,8 +235,8 @@ public: vec2 m_HookTeleBase; int m_HookTick; int m_HookState; - int m_HookedPlayer; std::set m_AttachedPlayers; + int HookedPlayer() const { return m_HookedPlayer; } void SetHookedPlayer(int HookedPlayer); int m_ActiveWeapon; @@ -272,6 +271,7 @@ public: int m_TriggeredEvents; void Init(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams = nullptr, std::map> *pTeleOuts = nullptr); + void SetCoreWorld(CWorldCore *pWorld, CCollision *pCollision, CTeamsCore *pTeams); void Reset(); void TickDeferred(); void Tick(bool UseInput, bool DoDeferredTick = true); @@ -318,6 +318,7 @@ public: private: CTeamsCore *m_pTeams; int m_MoveRestrictions; + int m_HookedPlayer; static bool IsSwitchActiveCb(int Number, void *pUser); }; diff --git a/src/game/localization.cpp b/src/game/localization.cpp index e8b8850a6..1b3feb33b 100644 --- a/src/game/localization.cpp +++ b/src/game/localization.cpp @@ -13,28 +13,6 @@ const char *Localize(const char *pStr, const char *pContext) return pNewStr ? pNewStr : pStr; } -CLocConstString::CLocConstString(const char *pStr, const char *pContext) -{ - m_pDefaultStr = pStr; - m_Hash = str_quickhash(m_pDefaultStr); - m_Version = -1; -} - -void CLocConstString::Reload() -{ - m_Version = g_Localization.Version(); - const char *pNewStr = g_Localization.FindString(m_Hash, m_ContextHash); - m_pCurrentStr = pNewStr; - if(!m_pCurrentStr) - m_pCurrentStr = m_pDefaultStr; -} - -CLocalizationDatabase::CLocalizationDatabase() -{ - m_VersionCounter = 0; - m_CurrentVersion = 0; -} - void CLocalizationDatabase::LoadIndexfile(IStorage *pStorage, IConsole *pConsole) { m_vLanguages.clear(); @@ -214,7 +192,6 @@ bool CLocalizationDatabase::Load(const char *pFilename, IStorage *pStorage, ICon { m_vStrings.clear(); m_StringsHeap.Reset(); - m_CurrentVersion = 0; return true; } @@ -281,8 +258,6 @@ bool CLocalizationDatabase::Load(const char *pFilename, IStorage *pStorage, ICon } io_close(IoHandle); std::sort(m_vStrings.begin(), m_vStrings.end()); - - m_CurrentVersion = ++m_VersionCounter; return true; } diff --git a/src/game/localization.h b/src/game/localization.h index b6d3173c5..6bd02bf95 100644 --- a/src/game/localization.h +++ b/src/game/localization.h @@ -48,46 +48,20 @@ class CLocalizationDatabase std::vector m_vLanguages; std::vector m_vStrings; CHeap m_StringsHeap; - int m_VersionCounter; - int m_CurrentVersion; public: - CLocalizationDatabase(); - void LoadIndexfile(class IStorage *pStorage, class IConsole *pConsole); const std::vector &Languages() const { return m_vLanguages; } void SelectDefaultLanguage(class IConsole *pConsole, char *pFilename, size_t Length) const; bool Load(const char *pFilename, class IStorage *pStorage, class IConsole *pConsole); - int Version() const { return m_CurrentVersion; } - void AddString(const char *pOrgStr, const char *pNewStr, const char *pContext); const char *FindString(unsigned Hash, unsigned ContextHash) const; }; extern CLocalizationDatabase g_Localization; -class CLocConstString -{ - const char *m_pDefaultStr; - const char *m_pCurrentStr; - unsigned m_Hash; - unsigned m_ContextHash; - int m_Version; - -public: - CLocConstString(const char *pStr, const char *pContext = ""); - void Reload(); - - inline operator const char *() - { - if(m_Version != g_Localization.Version()) - Reload(); - return m_pCurrentStr; - } -}; - extern const char *Localize(const char *pStr, const char *pContext = "") GNUC_ATTRIBUTE((format_arg(1))); #endif diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index 15072e481..156b6ae23 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -98,6 +98,12 @@ void CGameContext::ConNinja(IConsole::IResult *pResult, void *pUserData) pSelf->ModifyWeapons(pResult, pUserData, WEAPON_NINJA, false); } +void CGameContext::ConUnNinja(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + pSelf->ModifyWeapons(pResult, pUserData, WEAPON_NINJA, true); +} + void CGameContext::ConEndlessHook(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; @@ -147,6 +153,16 @@ void CGameContext::ConUnSuper(IConsole::IResult *pResult, void *pUserData) } } +void CGameContext::ConSolo(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + if(!CheckClientID(pResult->m_ClientID)) + return; + CCharacter *pChr = pSelf->GetPlayerChar(pResult->m_ClientID); + if(pChr) + pChr->SetSolo(true); +} + void CGameContext::ConUnSolo(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; @@ -157,6 +173,16 @@ void CGameContext::ConUnSolo(IConsole::IResult *pResult, void *pUserData) pChr->SetSolo(false); } +void CGameContext::ConDeep(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + if(!CheckClientID(pResult->m_ClientID)) + return; + CCharacter *pChr = pSelf->GetPlayerChar(pResult->m_ClientID); + if(pChr) + pChr->SetDeepFrozen(true); +} + void CGameContext::ConUnDeep(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index dafc7dcdb..4960d3d16 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -206,9 +206,9 @@ void CCharacter::HandleJetpack() { float Strength; if(!m_TuneZone) - Strength = GameServer()->Tuning()->m_JetpackStrength; + Strength = Tuning()->m_JetpackStrength; else - Strength = GameServer()->TuningList()[m_TuneZone].m_JetpackStrength; + Strength = TuningList()[m_TuneZone].m_JetpackStrength; TakeDamage(Direction * -1.0f * (Strength / 100.0f / 6.11f), 0, m_pPlayer->GetCID(), m_Core.m_ActiveWeapon); } } @@ -255,9 +255,9 @@ void CCharacter::HandleNinja() vec2 GroundElasticity; if(!m_TuneZone) - GroundElasticity = vec2(GameServer()->Tuning()->m_GroundElasticityX, GameServer()->Tuning()->m_GroundElasticityY); + GroundElasticity = vec2(Tuning()->m_GroundElasticityX, Tuning()->m_GroundElasticityY); else - GroundElasticity = vec2(GameServer()->TuningList()[m_TuneZone].m_GroundElasticityX, GameServer()->TuningList()[m_TuneZone].m_GroundElasticityY); + GroundElasticity = vec2(TuningList()[m_TuneZone].m_GroundElasticityX, TuningList()[m_TuneZone].m_GroundElasticityY); Collision()->MoveBox(&m_Core.m_Pos, &m_Core.m_Vel, vec2(GetProximityRadius(), GetProximityRadius()), GroundElasticity); @@ -470,9 +470,9 @@ void CCharacter::FireWeapon() float Strength; if(!m_TuneZone) - Strength = GameServer()->Tuning()->m_HammerStrength; + Strength = Tuning()->m_HammerStrength; else - Strength = GameServer()->TuningList()[m_TuneZone].m_HammerStrength; + Strength = TuningList()[m_TuneZone].m_HammerStrength; vec2 Temp = pTarget->m_Core.m_Vel + normalize(Dir + vec2(0.f, -1.1f)) * 10.0f; Temp = ClampVel(pTarget->m_MoveRestrictions, Temp); @@ -494,9 +494,9 @@ void CCharacter::FireWeapon() { float FireDelay; if(!m_TuneZone) - FireDelay = GameServer()->Tuning()->m_HammerHitFireDelay; + FireDelay = Tuning()->m_HammerHitFireDelay; else - FireDelay = GameServer()->TuningList()[m_TuneZone].m_HammerHitFireDelay; + FireDelay = TuningList()[m_TuneZone].m_HammerHitFireDelay; m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000; } } @@ -508,9 +508,9 @@ void CCharacter::FireWeapon() { int Lifetime; if(!m_TuneZone) - Lifetime = (int)(Server()->TickSpeed() * GameServer()->Tuning()->m_GunLifetime); + Lifetime = (int)(Server()->TickSpeed() * Tuning()->m_GunLifetime); else - Lifetime = (int)(Server()->TickSpeed() * GameServer()->TuningList()[m_TuneZone].m_GunLifetime); + Lifetime = (int)(Server()->TickSpeed() * TuningList()[m_TuneZone].m_GunLifetime); new CProjectile( GameWorld(), @@ -534,9 +534,9 @@ void CCharacter::FireWeapon() { float LaserReach; if(!m_TuneZone) - LaserReach = GameServer()->Tuning()->m_LaserReach; + LaserReach = Tuning()->m_LaserReach; else - LaserReach = GameServer()->TuningList()[m_TuneZone].m_LaserReach; + LaserReach = TuningList()[m_TuneZone].m_LaserReach; new CLaser(&GameServer()->m_World, m_Pos, Direction, LaserReach, m_pPlayer->GetCID(), WEAPON_SHOTGUN); GameServer()->CreateSound(m_Pos, SOUND_SHOTGUN_FIRE, TeamMask()); @@ -547,9 +547,9 @@ void CCharacter::FireWeapon() { int Lifetime; if(!m_TuneZone) - Lifetime = (int)(Server()->TickSpeed() * GameServer()->Tuning()->m_GrenadeLifetime); + Lifetime = (int)(Server()->TickSpeed() * Tuning()->m_GrenadeLifetime); else - Lifetime = (int)(Server()->TickSpeed() * GameServer()->TuningList()[m_TuneZone].m_GrenadeLifetime); + Lifetime = (int)(Server()->TickSpeed() * TuningList()[m_TuneZone].m_GrenadeLifetime); new CProjectile( GameWorld(), @@ -572,9 +572,9 @@ void CCharacter::FireWeapon() { float LaserReach; if(!m_TuneZone) - LaserReach = GameServer()->Tuning()->m_LaserReach; + LaserReach = Tuning()->m_LaserReach; else - LaserReach = GameServer()->TuningList()[m_TuneZone].m_LaserReach; + LaserReach = TuningList()[m_TuneZone].m_LaserReach; new CLaser(GameWorld(), m_Pos, Direction, LaserReach, m_pPlayer->GetCID(), WEAPON_LASER); GameServer()->CreateSound(m_Pos, SOUND_LASER_FIRE, TeamMask()); @@ -601,9 +601,9 @@ void CCharacter::FireWeapon() { float FireDelay; if(!m_TuneZone) - GameServer()->Tuning()->Get(38 + m_Core.m_ActiveWeapon, &FireDelay); + Tuning()->Get(38 + m_Core.m_ActiveWeapon, &FireDelay); else - GameServer()->TuningList()[m_TuneZone].Get(38 + m_Core.m_ActiveWeapon, &FireDelay); + TuningList()[m_TuneZone].Get(38 + m_Core.m_ActiveWeapon, &FireDelay); m_ReloadTimer = FireDelay * Server()->TickSpeed() / 1000; } } @@ -768,7 +768,8 @@ void CCharacter::Tick() if(m_Core.m_TriggeredEvents & COREEVENT_HOOK_ATTACH_PLAYER) { - if(m_Core.m_HookedPlayer != -1 && GameServer()->m_apPlayers[m_Core.m_HookedPlayer]->GetTeam() != TEAM_SPECTATORS) + const int HookedPlayer = m_Core.HookedPlayer(); + if(HookedPlayer != -1 && GameServer()->m_apPlayers[HookedPlayer]->GetTeam() != TEAM_SPECTATORS) { Antibot()->OnHookAttach(m_pPlayer->GetCID(), true); } @@ -1138,7 +1139,7 @@ bool CCharacter::IsSnappingCharacterInView(int SnappingClientID) for(const auto &AttachedPlayerID : m_Core.m_AttachedPlayers) { CCharacter *pOtherPlayer = GameServer()->GetPlayerChar(AttachedPlayerID); - if(pOtherPlayer && pOtherPlayer->m_Core.m_HookedPlayer == ID) + if(pOtherPlayer && pOtherPlayer->m_Core.HookedPlayer() == ID) { if(!NetworkClippedLine(SnappingClientID, m_Pos, pOtherPlayer->m_Pos)) { @@ -1183,9 +1184,9 @@ void CCharacter::Snap(int SnappingClient) pDDNetCharacter->m_Flags |= CHARACTERFLAG_SUPER; if(m_Core.m_EndlessHook) pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_HOOK; - if(m_Core.m_CollisionDisabled || !GameServer()->Tuning()->m_PlayerCollision) + if(m_Core.m_CollisionDisabled || !Tuning()->m_PlayerCollision) pDDNetCharacter->m_Flags |= CHARACTERFLAG_COLLISION_DISABLED; - if(m_Core.m_HookHitDisabled || !GameServer()->Tuning()->m_PlayerHooking) + if(m_Core.m_HookHitDisabled || !Tuning()->m_PlayerHooking) pDDNetCharacter->m_Flags |= CHARACTERFLAG_HOOK_HIT_DISABLED; if(m_Core.m_EndlessJump) pDDNetCharacter->m_Flags |= CHARACTERFLAG_ENDLESS_JUMP; @@ -1261,7 +1262,7 @@ void CCharacter::SetTeleports(std::map> *pTeleOuts, std:: { m_pTeleOuts = pTeleOuts; m_pTeleCheckOuts = pTeleCheckOuts; - m_Core.m_pTeleOuts = pTeleOuts; + m_Core.SetTeleOuts(pTeleOuts); } void CCharacter::FillAntibot(CAntibotCharacterData *pData) @@ -1269,7 +1270,7 @@ void CCharacter::FillAntibot(CAntibotCharacterData *pData) pData->m_Pos = m_Pos; pData->m_Vel = m_Core.m_Vel; pData->m_Angle = m_Core.m_Angle; - pData->m_HookedPlayer = m_Core.m_HookedPlayer; + pData->m_HookedPlayer = m_Core.HookedPlayer(); pData->m_SpawnTick = m_SpawnTick; pData->m_WeaponChangeTick = m_WeaponChangeTick; pData->m_aLatestInputs[0].m_TargetX = m_LatestInput.m_TargetX; @@ -1802,7 +1803,7 @@ void CCharacter::HandleTiles(int Index) { if(m_Core.m_Super) return; - int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleOuts)[z - 1].size()); + int TeleOut = GameWorld()->m_Core.RandomOr0((*m_pTeleOuts)[z - 1].size()); m_Core.m_Pos = (*m_pTeleOuts)[z - 1][TeleOut]; if(!g_Config.m_SvTeleportHoldHook) { @@ -1817,7 +1818,7 @@ void CCharacter::HandleTiles(int Index) { if(m_Core.m_Super) return; - int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleOuts)[evilz - 1].size()); + int TeleOut = GameWorld()->m_Core.RandomOr0((*m_pTeleOuts)[evilz - 1].size()); m_Core.m_Pos = (*m_pTeleOuts)[evilz - 1][TeleOut]; if(!g_Config.m_SvOldTeleportHook && !g_Config.m_SvOldTeleportWeapons) { @@ -1844,7 +1845,7 @@ void CCharacter::HandleTiles(int Index) { if(!(*m_pTeleCheckOuts)[k].empty()) { - int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleCheckOuts)[k].size()); + int TeleOut = GameWorld()->m_Core.RandomOr0((*m_pTeleCheckOuts)[k].size()); m_Core.m_Pos = (*m_pTeleCheckOuts)[k][TeleOut]; m_Core.m_Vel = vec2(0, 0); @@ -1881,7 +1882,7 @@ void CCharacter::HandleTiles(int Index) { if(!(*m_pTeleCheckOuts)[k].empty()) { - int TeleOut = m_Core.m_pWorld->RandomOr0((*m_pTeleCheckOuts)[k].size()); + int TeleOut = GameWorld()->m_Core.RandomOr0((*m_pTeleCheckOuts)[k].size()); m_Core.m_Pos = (*m_pTeleCheckOuts)[k][TeleOut]; if(!g_Config.m_SvTeleportHoldHook) @@ -1914,9 +1915,9 @@ void CCharacter::HandleTuneLayer() m_TuneZone = Collision()->IsTune(CurrentIndex); if(m_TuneZone) - m_Core.m_Tuning = GameServer()->TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore + m_Core.m_Tuning = TuningList()[m_TuneZone]; // throw tunings from specific zone into gamecore else - m_Core.m_Tuning = *GameServer()->Tuning(); + m_Core.m_Tuning = *Tuning(); if(m_TuneZone != m_TuneZoneOld) // don't send tunigs all the time { @@ -2229,7 +2230,7 @@ void CCharacter::Pause(bool Pause) GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0; GameServer()->m_World.RemoveEntity(this); - if(m_Core.m_HookedPlayer != -1) // Keeping hook would allow cheats + if(m_Core.HookedPlayer() != -1) // Keeping hook would allow cheats { ResetHook(); GameWorld()->ReleaseHooked(GetPlayer()->GetCID()); @@ -2332,5 +2333,6 @@ CClientMask CCharacter::TeamMask() void CCharacter::SwapClients(int Client1, int Client2) { - m_Core.SetHookedPlayer(m_Core.m_HookedPlayer == Client1 ? Client2 : m_Core.m_HookedPlayer == Client2 ? Client1 : m_Core.m_HookedPlayer); + const int HookedPlayer = m_Core.HookedPlayer(); + m_Core.SetHookedPlayer(HookedPlayer == Client1 ? Client2 : HookedPlayer == Client2 ? Client1 : HookedPlayer); } diff --git a/src/game/server/entities/laser.cpp b/src/game/server/entities/laser.cpp index a819a2de3..913043d5c 100644 --- a/src/game/server/entities/laser.cpp +++ b/src/game/server/entities/laser.cpp @@ -58,9 +58,9 @@ bool CLaser::HitCharacter(vec2 From, vec2 To) float Strength; if(!m_TuneZone) - Strength = GameServer()->Tuning()->m_ShotgunStrength; + Strength = Tuning()->m_ShotgunStrength; else - Strength = GameServer()->TuningList()[m_TuneZone].m_ShotgunStrength; + Strength = TuningList()[m_TuneZone].m_ShotgunStrength; vec2 &HitPos = pHit->Core()->m_Pos; if(!g_Config.m_SvOldLaser) @@ -158,7 +158,7 @@ void CLaser::DoBounce() } else if(!m_TuneZone) { - m_Energy -= Distance + GameServer()->Tuning()->m_LaserBounceCost; + m_Energy -= Distance + Tuning()->m_LaserBounceCost; } else { @@ -179,9 +179,9 @@ void CLaser::DoBounce() m_WasTele = false; } - int BounceNum = GameServer()->Tuning()->m_LaserBounceNum; + int BounceNum = Tuning()->m_LaserBounceNum; if(m_TuneZone) - BounceNum = GameServer()->TuningList()[m_TuneZone].m_LaserBounceNum; + BounceNum = TuningList()[m_TuneZone].m_LaserBounceNum; if(m_Bounces > BounceNum) m_Energy = -1; @@ -279,9 +279,9 @@ void CLaser::Tick() float Delay; if(m_TuneZone) - Delay = GameServer()->TuningList()[m_TuneZone].m_LaserBounceDelay; + Delay = TuningList()[m_TuneZone].m_LaserBounceDelay; else - Delay = GameServer()->Tuning()->m_LaserBounceDelay; + Delay = Tuning()->m_LaserBounceDelay; if((Server()->Tick() - m_EvalTick) > (Server()->TickSpeed() * Delay / 1000.0f)) DoBounce(); diff --git a/src/game/server/entities/projectile.cpp b/src/game/server/entities/projectile.cpp index 8780b02c3..388859148 100644 --- a/src/game/server/entities/projectile.cpp +++ b/src/game/server/entities/projectile.cpp @@ -64,13 +64,13 @@ vec2 CProjectile::GetPos(float Time) case WEAPON_GRENADE: if(!m_TuneZone) { - Curvature = GameServer()->Tuning()->m_GrenadeCurvature; - Speed = GameServer()->Tuning()->m_GrenadeSpeed; + Curvature = Tuning()->m_GrenadeCurvature; + Speed = Tuning()->m_GrenadeSpeed; } else { - Curvature = GameServer()->TuningList()[m_TuneZone].m_GrenadeCurvature; - Speed = GameServer()->TuningList()[m_TuneZone].m_GrenadeSpeed; + Curvature = TuningList()[m_TuneZone].m_GrenadeCurvature; + Speed = TuningList()[m_TuneZone].m_GrenadeSpeed; } break; @@ -78,13 +78,13 @@ vec2 CProjectile::GetPos(float Time) case WEAPON_SHOTGUN: if(!m_TuneZone) { - Curvature = GameServer()->Tuning()->m_ShotgunCurvature; - Speed = GameServer()->Tuning()->m_ShotgunSpeed; + Curvature = Tuning()->m_ShotgunCurvature; + Speed = Tuning()->m_ShotgunSpeed; } else { - Curvature = GameServer()->TuningList()[m_TuneZone].m_ShotgunCurvature; - Speed = GameServer()->TuningList()[m_TuneZone].m_ShotgunSpeed; + Curvature = TuningList()[m_TuneZone].m_ShotgunCurvature; + Speed = TuningList()[m_TuneZone].m_ShotgunSpeed; } break; @@ -92,13 +92,13 @@ vec2 CProjectile::GetPos(float Time) case WEAPON_GUN: if(!m_TuneZone) { - Curvature = GameServer()->Tuning()->m_GunCurvature; - Speed = GameServer()->Tuning()->m_GunSpeed; + Curvature = Tuning()->m_GunCurvature; + Speed = Tuning()->m_GunSpeed; } else { - Curvature = GameServer()->TuningList()[m_TuneZone].m_GunCurvature; - Speed = GameServer()->TuningList()[m_TuneZone].m_GunSpeed; + Curvature = TuningList()[m_TuneZone].m_GunCurvature; + Speed = TuningList()[m_TuneZone].m_GunSpeed; } break; } diff --git a/src/game/server/entity.h b/src/game/server/entity.h index 799fde8f4..69e9a4385 100644 --- a/src/game/server/entity.h +++ b/src/game/server/entity.h @@ -5,7 +5,8 @@ #include -#include "alloc.h" +#include + #include "gameworld.h" class CCollision; @@ -60,6 +61,9 @@ public: // TODO: Maybe make protected /* Objects */ std::vector &Switchers() { return m_pGameWorld->m_Core.m_vSwitchers; } CGameWorld *GameWorld() { return m_pGameWorld; } + CTuningParams *Tuning() { return GameWorld()->Tuning(); } + CTuningParams *TuningList() { return GameWorld()->TuningList(); } + CTuningParams *GetTuning(int i) { return GameWorld()->GetTuning(i); } class CConfig *Config() { return m_pGameWorld->Config(); } class CGameContext *GameServer() { return m_pGameWorld->GameServer(); } class IServer *Server() { return m_pGameWorld->Server(); } @@ -134,7 +138,7 @@ public: // TODO: Maybe make protected ClientID of the initiator from this entity. -1 created by map. This is used by save/load to remove related entities to the tee. CCharacter should not return the PlayerId, because they get - handled separatly in save/load code. + handled separately in save/load code. */ virtual int GetOwnerID() const { return -1; } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index f13c32519..45c9cbcbe 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -641,17 +641,14 @@ void CGameContext::SendMotd(int ClientID) void CGameContext::SendSettings(int ClientID) { - if(Server()->IsSixup(ClientID)) - { - protocol7::CNetMsg_Sv_ServerSettings Msg; - Msg.m_KickVote = g_Config.m_SvVoteKick; - Msg.m_KickMin = g_Config.m_SvVoteKickMin; - Msg.m_SpecVote = g_Config.m_SvVoteSpectate; - Msg.m_TeamLock = 0; - Msg.m_TeamBalance = 0; - Msg.m_PlayerSlots = g_Config.m_SvMaxClients - g_Config.m_SvSpectatorSlots; - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); - } + protocol7::CNetMsg_Sv_ServerSettings Msg; + Msg.m_KickVote = g_Config.m_SvVoteKick; + Msg.m_KickMin = g_Config.m_SvVoteKickMin; + Msg.m_SpecVote = g_Config.m_SvVoteSpectate; + Msg.m_TeamLock = 0; + Msg.m_TeamBalance = 0; + Msg.m_PlayerSlots = g_Config.m_SvMaxClients - g_Config.m_SvSpectatorSlots; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); } void CGameContext::SendBroadcast(const char *pText, int ClientID, bool IsImportant) @@ -950,6 +947,8 @@ void CGameContext::OnTick() m_World.m_Core.m_aTuning[0] = m_Tuning; m_World.Tick(); + UpdatePlayerMaps(); + //if(world.paused) // make sure that the game object always updates m_pController->Tick(); @@ -1929,774 +1928,845 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) if(!pRawMsg) return; - CPlayer *pPlayer = m_apPlayers[ClientID]; - if(Server()->ClientIngame(ClientID)) { - if(MsgID == NETMSGTYPE_CL_SAY) + switch(MsgID) { - CNetMsg_Cl_Say *pMsg = (CNetMsg_Cl_Say *)pRawMsg; - if(!str_utf8_check(pMsg->m_pMessage)) - { - return; - } - bool Check = !pPlayer->m_NotEligibleForFinish && pPlayer->m_EligibleForFinishCheck + 10 * time_freq() >= time_get(); - if(Check && str_comp(pMsg->m_pMessage, "xd sure chillerbot.png is lyfe") == 0 && pMsg->m_Team == 0) - { - if(m_TeeHistorianActive) - { - m_TeeHistorian.RecordPlayerMessage(ClientID, pUnpacker->CompleteData(), pUnpacker->CompleteSize()); - } - - pPlayer->m_NotEligibleForFinish = true; - dbg_msg("hack", "bot detected, cid=%d", ClientID); - return; - } - int Team = pMsg->m_Team; - - // trim right and set maximum length to 256 utf8-characters - int Length = 0; - const char *p = pMsg->m_pMessage; - const char *pEnd = 0; - while(*p) - { - const char *pStrOld = p; - int Code = str_utf8_decode(&p); - - // check if unicode is not empty - if(!str_utf8_isspace(Code)) - { - pEnd = 0; - } - else if(pEnd == 0) - pEnd = pStrOld; - - if(++Length >= 256) - { - *(const_cast(p)) = 0; - break; - } - } - if(pEnd != 0) - *(const_cast(pEnd)) = 0; - - // drop empty and autocreated spam messages (more than 32 characters per second) - if(Length == 0 || (pMsg->m_pMessage[0] != '/' && (g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat + Server()->TickSpeed() * ((31 + Length) / 32) > Server()->Tick()))) - return; - - int GameTeam = ((CGameControllerDDRace *)m_pController)->m_Teams.m_Core.Team(pPlayer->GetCID()); - if(Team) - Team = ((pPlayer->GetTeam() == TEAM_SPECTATORS) ? CHAT_SPEC : GameTeam); - else - Team = CHAT_ALL; - - if(pMsg->m_pMessage[0] == '/') - { - if(str_startswith_nocase(pMsg->m_pMessage + 1, "w ")) - { - char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); - Whisper(pPlayer->GetCID(), aWhisperMsg); - } - else if(str_startswith_nocase(pMsg->m_pMessage + 1, "whisper ")) - { - char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 9, 256); - Whisper(pPlayer->GetCID(), aWhisperMsg); - } - else if(str_startswith_nocase(pMsg->m_pMessage + 1, "c ")) - { - char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); - Converse(pPlayer->GetCID(), aWhisperMsg); - } - else if(str_startswith_nocase(pMsg->m_pMessage + 1, "converse ")) - { - char aWhisperMsg[256]; - str_copy(aWhisperMsg, pMsg->m_pMessage + 10, 256); - Converse(pPlayer->GetCID(), aWhisperMsg); - } - else - { - if(g_Config.m_SvSpamprotection && !str_startswith(pMsg->m_pMessage + 1, "timeout ") && pPlayer->m_aLastCommands[0] && pPlayer->m_aLastCommands[0] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[1] && pPlayer->m_aLastCommands[1] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[2] && pPlayer->m_aLastCommands[2] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[3] && pPlayer->m_aLastCommands[3] + Server()->TickSpeed() > Server()->Tick()) - return; - - int64_t Now = Server()->Tick(); - pPlayer->m_aLastCommands[pPlayer->m_LastCommandPos] = Now; - pPlayer->m_LastCommandPos = (pPlayer->m_LastCommandPos + 1) % 4; - - Console()->SetFlagMask(CFGFLAG_CHAT); - int Authed = Server()->GetAuthedState(ClientID); - if(Authed) - Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); - else - Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); - - { - CClientChatLogger Logger(this, ClientID, log_get_scope_logger()); - CLogScope Scope(&Logger); - Console()->ExecuteLine(pMsg->m_pMessage + 1, ClientID, false); - } - // m_apPlayers[ClientID] can be NULL, if the player used a - // timeout code and replaced another client. - char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "%d used %s", ClientID, pMsg->m_pMessage); - Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "chat-command", aBuf); - - Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); - Console()->SetFlagMask(CFGFLAG_SERVER); - } - } - else - { - pPlayer->UpdatePlaytime(); - char aCensoredMessage[256]; - CensorMessage(aCensoredMessage, pMsg->m_pMessage, sizeof(aCensoredMessage)); - SendChat(ClientID, Team, aCensoredMessage, ClientID); - } - } - else if(MsgID == NETMSGTYPE_CL_CALLVOTE) - { - if(RateLimitPlayerVote(ClientID) || m_VoteCloseTime) - return; - - m_apPlayers[ClientID]->UpdatePlaytime(); - - m_VoteType = VOTE_TYPE_UNKNOWN; - char aChatmsg[512] = {0}; - char aDesc[VOTE_DESC_LENGTH] = {0}; - char aSixupDesc[VOTE_DESC_LENGTH] = {0}; - char aCmd[VOTE_CMD_LENGTH] = {0}; - char aReason[VOTE_REASON_LENGTH] = "No reason given"; - CNetMsg_Cl_CallVote *pMsg = (CNetMsg_Cl_CallVote *)pRawMsg; - if(!str_utf8_check(pMsg->m_pType) || !str_utf8_check(pMsg->m_pReason) || !str_utf8_check(pMsg->m_pValue)) - { - return; - } - if(pMsg->m_pReason[0]) - { - str_copy(aReason, pMsg->m_pReason, sizeof(aReason)); - } - - if(str_comp_nocase(pMsg->m_pType, "option") == 0) - { - int Authed = Server()->GetAuthedState(ClientID); - CVoteOptionServer *pOption = m_pVoteOptionFirst; - while(pOption) - { - if(str_comp_nocase(pMsg->m_pValue, pOption->m_aDescription) == 0) - { - if(!Console()->LineIsValid(pOption->m_aCommand)) - { - SendChatTarget(ClientID, "Invalid option"); - return; - } - if((str_find(pOption->m_aCommand, "sv_map ") != 0 || str_find(pOption->m_aCommand, "change_map ") != 0 || str_find(pOption->m_aCommand, "random_map") != 0 || str_find(pOption->m_aCommand, "random_unfinished_map") != 0) && RateLimitPlayerMapVote(ClientID)) - { - return; - } - - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", Server()->ClientName(ClientID), - pOption->m_aDescription, aReason); - str_copy(aDesc, pOption->m_aDescription); - - if((str_endswith(pOption->m_aCommand, "random_map") || str_endswith(pOption->m_aCommand, "random_unfinished_map")) && str_length(aReason) == 1 && aReason[0] >= '0' && aReason[0] <= '5') - { - int Stars = aReason[0] - '0'; - str_format(aCmd, sizeof(aCmd), "%s %d", pOption->m_aCommand, Stars); - } - else - { - str_copy(aCmd, pOption->m_aCommand); - } - - m_LastMapVote = time_get(); - break; - } - - pOption = pOption->m_pNext; - } - - if(!pOption) - { - if(Authed != AUTHED_ADMIN) // allow admins to call any vote they want - { - str_format(aChatmsg, sizeof(aChatmsg), "'%s' isn't an option on this server", pMsg->m_pValue); - SendChatTarget(ClientID, aChatmsg); - return; - } - else - { - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s'", Server()->ClientName(ClientID), pMsg->m_pValue); - str_copy(aDesc, pMsg->m_pValue); - str_copy(aCmd, pMsg->m_pValue); - } - } - - m_VoteType = VOTE_TYPE_OPTION; - } - else if(str_comp_nocase(pMsg->m_pType, "kick") == 0) - { - int Authed = Server()->GetAuthedState(ClientID); - if(!Authed && time_get() < m_apPlayers[ClientID]->m_Last_KickVote + (time_freq() * 5)) - return; - else if(!Authed && time_get() < m_apPlayers[ClientID]->m_Last_KickVote + (time_freq() * g_Config.m_SvVoteKickDelay)) - { - str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second wait time between kick votes for each player please wait %d second(s)", - g_Config.m_SvVoteKickDelay, - (int)(((m_apPlayers[ClientID]->m_Last_KickVote + (m_apPlayers[ClientID]->m_Last_KickVote * time_freq())) / time_freq()) - (time_get() / time_freq()))); - SendChatTarget(ClientID, aChatmsg); - m_apPlayers[ClientID]->m_Last_KickVote = time_get(); - return; - } - else if(!g_Config.m_SvVoteKick && !Authed) // allow admins to call kick votes even if they are forbidden - { - SendChatTarget(ClientID, "Server does not allow voting to kick players"); - m_apPlayers[ClientID]->m_Last_KickVote = time_get(); - return; - } - - if(g_Config.m_SvVoteKickMin && !GetDDRaceTeam(ClientID)) - { - char aaAddresses[MAX_CLIENTS][NETADDR_MAXSTRSIZE] = {{0}}; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(m_apPlayers[i]) - { - Server()->GetClientAddr(i, aaAddresses[i], NETADDR_MAXSTRSIZE); - } - } - int NumPlayers = 0; - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(i)) - { - NumPlayers++; - for(int j = 0; j < i; j++) - { - if(m_apPlayers[j] && m_apPlayers[j]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(j)) - { - if(str_comp(aaAddresses[i], aaAddresses[j]) == 0) - { - NumPlayers--; - break; - } - } - } - } - } - - if(NumPlayers < g_Config.m_SvVoteKickMin) - { - str_format(aChatmsg, sizeof(aChatmsg), "Kick voting requires %d players", g_Config.m_SvVoteKickMin); - SendChatTarget(ClientID, aChatmsg); - return; - } - } - - int KickID = str_toint(pMsg->m_pValue); - - if(KickID < 0 || KickID >= MAX_CLIENTS || !m_apPlayers[KickID]) - { - SendChatTarget(ClientID, "Invalid client id to kick"); - return; - } - if(KickID == ClientID) - { - SendChatTarget(ClientID, "You can't kick yourself"); - return; - } - if(!Server()->ReverseTranslate(KickID, ClientID)) - { - return; - } - int KickedAuthed = Server()->GetAuthedState(KickID); - if(KickedAuthed > Authed) - { - SendChatTarget(ClientID, "You can't kick authorized players"); - m_apPlayers[ClientID]->m_Last_KickVote = time_get(); - char aBufKick[128]; - str_format(aBufKick, sizeof(aBufKick), "'%s' called for vote to kick you", Server()->ClientName(ClientID)); - SendChatTarget(KickID, aBufKick); - return; - } - - // Don't allow kicking if a player has no character - if(!GetPlayerChar(ClientID) || !GetPlayerChar(KickID) || GetDDRaceTeam(ClientID) != GetDDRaceTeam(KickID)) - { - SendChatTarget(ClientID, "You can kick only your team member"); - m_apPlayers[ClientID]->m_Last_KickVote = time_get(); - return; - } - - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to kick '%s' (%s)", Server()->ClientName(ClientID), Server()->ClientName(KickID), aReason); - str_format(aSixupDesc, sizeof(aSixupDesc), "%2d: %s", KickID, Server()->ClientName(KickID)); - if(!GetDDRaceTeam(ClientID)) - { - if(!g_Config.m_SvVoteKickBantime) - { - str_format(aCmd, sizeof(aCmd), "kick %d Kicked by vote", KickID); - str_format(aDesc, sizeof(aDesc), "Kick '%s'", Server()->ClientName(KickID)); - } - else - { - char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; - Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr)); - str_format(aCmd, sizeof(aCmd), "ban %s %d Banned by vote", aAddrStr, g_Config.m_SvVoteKickBantime); - str_format(aDesc, sizeof(aDesc), "Ban '%s'", Server()->ClientName(KickID)); - } - } - else - { - str_format(aCmd, sizeof(aCmd), "uninvite %d %d; set_team_ddr %d 0", KickID, GetDDRaceTeam(KickID), KickID); - str_format(aDesc, sizeof(aDesc), "Move '%s' to team 0", Server()->ClientName(KickID)); - } - m_apPlayers[ClientID]->m_Last_KickVote = time_get(); - m_VoteType = VOTE_TYPE_KICK; - m_VoteVictim = KickID; - } - else if(str_comp_nocase(pMsg->m_pType, "spectate") == 0) - { - if(!g_Config.m_SvVoteSpectate) - { - SendChatTarget(ClientID, "Server does not allow voting to move players to spectators"); - return; - } - - int SpectateID = str_toint(pMsg->m_pValue); - - if(SpectateID < 0 || SpectateID >= MAX_CLIENTS || !m_apPlayers[SpectateID] || m_apPlayers[SpectateID]->GetTeam() == TEAM_SPECTATORS) - { - SendChatTarget(ClientID, "Invalid client id to move"); - return; - } - if(SpectateID == ClientID) - { - SendChatTarget(ClientID, "You can't move yourself"); - return; - } - if(!Server()->ReverseTranslate(SpectateID, ClientID)) - { - return; - } - - if(!GetPlayerChar(ClientID) || !GetPlayerChar(SpectateID) || GetDDRaceTeam(ClientID) != GetDDRaceTeam(SpectateID)) - { - SendChatTarget(ClientID, "You can only move your team member to spectators"); - return; - } - - str_format(aSixupDesc, sizeof(aSixupDesc), "%2d: %s", SpectateID, Server()->ClientName(SpectateID)); - if(g_Config.m_SvPauseable && g_Config.m_SvVotePause) - { - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to pause '%s' for %d seconds (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), g_Config.m_SvVotePauseTime, aReason); - str_format(aDesc, sizeof(aDesc), "Pause '%s' (%ds)", Server()->ClientName(SpectateID), g_Config.m_SvVotePauseTime); - str_format(aCmd, sizeof(aCmd), "uninvite %d %d; force_pause %d %d", SpectateID, GetDDRaceTeam(SpectateID), SpectateID, g_Config.m_SvVotePauseTime); - } - else - { - str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to move '%s' to spectators (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), aReason); - str_format(aDesc, sizeof(aDesc), "Move '%s' to spectators", Server()->ClientName(SpectateID)); - str_format(aCmd, sizeof(aCmd), "uninvite %d %d; set_team %d -1 %d", SpectateID, GetDDRaceTeam(SpectateID), SpectateID, g_Config.m_SvVoteSpectateRejoindelay); - } - m_VoteType = VOTE_TYPE_SPECTATE; - m_VoteVictim = SpectateID; - } - - if(aCmd[0] && str_comp_nocase(aCmd, "info") != 0) - CallVote(ClientID, aDesc, aCmd, aReason, aChatmsg, aSixupDesc[0] ? aSixupDesc : 0); - } - else if(MsgID == NETMSGTYPE_CL_VOTE) - { - if(!m_VoteCloseTime) - return; - - if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + Server()->TickSpeed() * 3 > Server()->Tick()) - return; - - int64_t Now = Server()->Tick(); - - pPlayer->m_LastVoteTry = Now; - pPlayer->UpdatePlaytime(); - - CNetMsg_Cl_Vote *pMsg = (CNetMsg_Cl_Vote *)pRawMsg; - if(!pMsg->m_Vote) - return; - - pPlayer->m_Vote = pMsg->m_Vote; - pPlayer->m_VotePos = ++m_VotePos; - m_VoteUpdate = true; - } - else if(MsgID == NETMSGTYPE_CL_SETTEAM && !m_World.m_Paused) - { - CNetMsg_Cl_SetTeam *pMsg = (CNetMsg_Cl_SetTeam *)pRawMsg; - - if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam + Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick())) - return; - - //Kill Protection - CCharacter *pChr = pPlayer->GetCharacter(); - if(pChr) - { - int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); - if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) - { - SendChatTarget(ClientID, "Kill Protection enabled. If you really want to join the spectators, first type /kill"); - return; - } - } - - if(pPlayer->m_TeamChangeTick > Server()->Tick()) - { - pPlayer->m_LastSetTeam = Server()->Tick(); - int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick()) / Server()->TickSpeed(); - char aTime[32]; - str_time((int64_t)TimeLeft * 100, TIME_HOURS, aTime, sizeof(aTime)); - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Time to wait before changing team: %s", aTime); - SendBroadcast(aBuf, ClientID); - return; - } - - // Switch team on given client and kill/respawn them - if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID)) - { - if(pPlayer->IsPaused()) - SendChatTarget(ClientID, "Use /pause first then you can kill"); - else - { - if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) - m_VoteUpdate = true; - m_pController->DoTeamChange(pPlayer, pMsg->m_Team); - pPlayer->m_TeamChangeTick = Server()->Tick(); - } - } - else - { - char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", Server()->MaxClients() - g_Config.m_SvSpectatorSlots); - SendBroadcast(aBuf, ClientID); - } - } - else if(MsgID == NETMSGTYPE_CL_ISDDNETLEGACY) - { - IServer::CClientInfo Info; - if(Server()->GetClientInfo(ClientID, &Info) && Info.m_GotDDNetVersion) - { - return; - } - int DDNetVersion = pUnpacker->GetInt(); - if(pUnpacker->Error() || DDNetVersion < 0) - { - DDNetVersion = VERSION_DDRACE; - } - Server()->SetClientDDNetVersion(ClientID, DDNetVersion); - OnClientDDNetVersionKnown(ClientID); - } - else if(MsgID == NETMSGTYPE_CL_SHOWOTHERSLEGACY) - { - if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) - { - CNetMsg_Cl_ShowOthersLegacy *pMsg = (CNetMsg_Cl_ShowOthersLegacy *)pRawMsg; - pPlayer->m_ShowOthers = pMsg->m_Show; - } - } - else if(MsgID == NETMSGTYPE_CL_SHOWOTHERS) - { - if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) - { - CNetMsg_Cl_ShowOthers *pMsg = (CNetMsg_Cl_ShowOthers *)pRawMsg; - pPlayer->m_ShowOthers = pMsg->m_Show; - } - } - else if(MsgID == NETMSGTYPE_CL_SHOWDISTANCE) - { - CNetMsg_Cl_ShowDistance *pMsg = (CNetMsg_Cl_ShowDistance *)pRawMsg; - pPlayer->m_ShowDistance = vec2(pMsg->m_X, pMsg->m_Y); - } - else if(MsgID == NETMSGTYPE_CL_SETSPECTATORMODE && !m_World.m_Paused) - { - CNetMsg_Cl_SetSpectatorMode *pMsg = (CNetMsg_Cl_SetSpectatorMode *)pRawMsg; - - pMsg->m_SpectatorID = clamp(pMsg->m_SpectatorID, (int)SPEC_FOLLOW, MAX_CLIENTS - 1); - - if(pMsg->m_SpectatorID >= 0) - if(!Server()->ReverseTranslate(pMsg->m_SpectatorID, ClientID)) - return; - - if((g_Config.m_SvSpamprotection && pPlayer->m_LastSetSpectatorMode && pPlayer->m_LastSetSpectatorMode + Server()->TickSpeed() / 4 > Server()->Tick())) - return; - - pPlayer->m_LastSetSpectatorMode = Server()->Tick(); - pPlayer->UpdatePlaytime(); - if(pMsg->m_SpectatorID >= 0 && (!m_apPlayers[pMsg->m_SpectatorID] || m_apPlayers[pMsg->m_SpectatorID]->GetTeam() == TEAM_SPECTATORS)) - SendChatTarget(ClientID, "Invalid spectator id used"); - else - pPlayer->m_SpectatorID = pMsg->m_SpectatorID; - } - else if(MsgID == NETMSGTYPE_CL_CHANGEINFO) - { - if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay > Server()->Tick()) - return; - - bool SixupNeedsUpdate = false; - - CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg; - if(!str_utf8_check(pMsg->m_pName) || !str_utf8_check(pMsg->m_pClan) || !str_utf8_check(pMsg->m_pSkin)) - { - return; - } - pPlayer->m_LastChangeInfo = Server()->Tick(); - pPlayer->UpdatePlaytime(); - - // set infos - if(Server()->WouldClientNameChange(ClientID, pMsg->m_pName) && !ProcessSpamProtection(ClientID)) - { - char aOldName[MAX_NAME_LENGTH]; - str_copy(aOldName, Server()->ClientName(ClientID), sizeof(aOldName)); - - Server()->SetClientName(ClientID, pMsg->m_pName); - - char aChatText[256]; - str_format(aChatText, sizeof(aChatText), "'%s' changed name to '%s'", aOldName, Server()->ClientName(ClientID)); - SendChat(-1, CGameContext::CHAT_ALL, aChatText); - - // reload scores - Score()->PlayerData(ClientID)->Reset(); - m_apPlayers[ClientID]->m_Score.reset(); - Score()->LoadPlayerData(ClientID); - - SixupNeedsUpdate = true; - - LogEvent("Name change", ClientID); - } - - if(str_comp(Server()->ClientClan(ClientID), pMsg->m_pClan)) - SixupNeedsUpdate = true; - Server()->SetClientClan(ClientID, pMsg->m_pClan); - - if(Server()->ClientCountry(ClientID) != pMsg->m_Country) - SixupNeedsUpdate = true; - Server()->SetClientCountry(ClientID, pMsg->m_Country); - - str_copy(pPlayer->m_TeeInfos.m_aSkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_aSkinName)); - pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; - pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; - pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; - if(!Server()->IsSixup(ClientID)) - pPlayer->m_TeeInfos.ToSixup(); - - if(SixupNeedsUpdate) - { - protocol7::CNetMsg_Sv_ClientDrop Drop; - Drop.m_ClientID = ClientID; - Drop.m_pReason = ""; - Drop.m_Silent = true; - - protocol7::CNetMsg_Sv_ClientInfo Info; - Info.m_ClientID = ClientID; - Info.m_pName = Server()->ClientName(ClientID); - Info.m_Country = pMsg->m_Country; - Info.m_pClan = pMsg->m_pClan; - Info.m_Local = 0; - Info.m_Silent = true; - Info.m_Team = pPlayer->GetTeam(); - - for(int p = 0; p < 6; p++) - { - Info.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; - Info.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; - Info.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; - } - - for(int i = 0; i < Server()->MaxClients(); i++) - { - if(i != ClientID) - { - Server()->SendPackMsg(&Drop, MSGFLAG_VITAL | MSGFLAG_NORECORD, i); - Server()->SendPackMsg(&Info, MSGFLAG_VITAL | MSGFLAG_NORECORD, i); - } - } - } - else - { - protocol7::CNetMsg_Sv_SkinChange Msg; - Msg.m_ClientID = ClientID; - for(int p = 0; p < 6; p++) - { - Msg.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; - Msg.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; - Msg.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; - } - - Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); - } - - Server()->ExpireServerInfo(); - } - else if(MsgID == NETMSGTYPE_CL_EMOTICON && !m_World.m_Paused) - { - CNetMsg_Cl_Emoticon *pMsg = (CNetMsg_Cl_Emoticon *)pRawMsg; - - auto &&CheckPreventEmote = [&](int64_t LastEmote, int64_t DelayInMs) { - return (LastEmote * (int64_t)1000) + (int64_t)Server()->TickSpeed() * DelayInMs > ((int64_t)Server()->Tick() * (int64_t)1000); - }; - - if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmote, (int64_t)g_Config.m_SvEmoticonMsDelay)) - return; - - CCharacter *pChr = pPlayer->GetCharacter(); - // player needs a character to send emotes - if(pChr != nullptr) - { - pPlayer->m_LastEmote = Server()->Tick(); - pPlayer->UpdatePlaytime(); - - // check if the global emoticon is prevented and emotes are only send to nearby players - if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmoteGlobal, (int64_t)g_Config.m_SvGlobalEmoticonMsDelay)) - { - for(int i = 0; i < MAX_CLIENTS; ++i) - { - if(m_apPlayers[i] && pChr->CanSnapCharacter(i) && pChr->IsSnappingCharacterInView(i)) - { - SendEmoticon(ClientID, pMsg->m_Emoticon, i); - } - } - } - else - { - // else send emoticons to all players - pPlayer->m_LastEmoteGlobal = Server()->Tick(); - SendEmoticon(ClientID, pMsg->m_Emoticon, -1); - } - - if(g_Config.m_SvEmotionalTees && pPlayer->m_EyeEmoteEnabled) - { - int EmoteType = EMOTE_NORMAL; - switch(pMsg->m_Emoticon) - { - case EMOTICON_EXCLAMATION: - case EMOTICON_GHOST: - case EMOTICON_QUESTION: - case EMOTICON_WTF: - EmoteType = EMOTE_SURPRISE; - break; - case EMOTICON_DOTDOT: - case EMOTICON_DROP: - case EMOTICON_ZZZ: - EmoteType = EMOTE_BLINK; - break; - case EMOTICON_EYES: - case EMOTICON_HEARTS: - case EMOTICON_MUSIC: - EmoteType = EMOTE_HAPPY; - break; - case EMOTICON_OOP: - case EMOTICON_SORRY: - case EMOTICON_SUSHI: - EmoteType = EMOTE_PAIN; - break; - case EMOTICON_DEVILTEE: - case EMOTICON_SPLATTEE: - case EMOTICON_ZOMG: - EmoteType = EMOTE_ANGRY; - break; - default: - break; - } - pChr->SetEmote(EmoteType, Server()->Tick() + 2 * Server()->TickSpeed()); - } - } - } - else if(MsgID == NETMSGTYPE_CL_KILL && !m_World.m_Paused) - { - if(m_VoteCloseTime && m_VoteCreator == ClientID && GetDDRaceTeam(ClientID) && (IsKickVote() || IsSpecVote())) - { - SendChatTarget(ClientID, "You are running a vote please try again after the vote is done!"); - return; - } - if(pPlayer->m_LastKill && pPlayer->m_LastKill + Server()->TickSpeed() * g_Config.m_SvKillDelay > Server()->Tick()) - return; - if(pPlayer->IsPaused()) - return; - - CCharacter *pChr = pPlayer->GetCharacter(); - if(!pChr) - return; - - //Kill Protection - int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); - if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) - { - SendChatTarget(ClientID, "Kill Protection enabled. If you really want to kill, type /kill"); - return; - } - - pPlayer->m_LastKill = Server()->Tick(); - pPlayer->KillCharacter(WEAPON_SELF); - pPlayer->Respawn(); + case NETMSGTYPE_CL_SAY: + OnSayNetMessage(static_cast(pRawMsg), ClientID, pUnpacker); + break; + case NETMSGTYPE_CL_CALLVOTE: + OnCallVoteNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_VOTE: + OnVoteNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_SETTEAM: + OnSetTeamNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_ISDDNETLEGACY: + OnIsDDNetLegacyNetMessage(static_cast(pRawMsg), ClientID, pUnpacker); + break; + case NETMSGTYPE_CL_SHOWOTHERSLEGACY: + OnShowOthersLegacyNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_SHOWOTHERS: + OnShowOthersNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_SHOWDISTANCE: + OnShowDistanceNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_SETSPECTATORMODE: + OnSetSpectatorModeNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_CHANGEINFO: + OnChangeInfoNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_EMOTICON: + OnEmoticonNetMessage(static_cast(pRawMsg), ClientID); + break; + case NETMSGTYPE_CL_KILL: + OnKillNetMessage(static_cast(pRawMsg), ClientID); + break; + default: + break; } } if(MsgID == NETMSGTYPE_CL_STARTINFO) { - if(pPlayer->m_IsReady) - return; - - CNetMsg_Cl_StartInfo *pMsg = (CNetMsg_Cl_StartInfo *)pRawMsg; - - if(!str_utf8_check(pMsg->m_pName)) - { - Server()->Kick(ClientID, "name is not valid utf8"); - return; - } - if(!str_utf8_check(pMsg->m_pClan)) - { - Server()->Kick(ClientID, "clan is not valid utf8"); - return; - } - if(!str_utf8_check(pMsg->m_pSkin)) - { - Server()->Kick(ClientID, "skin is not valid utf8"); - return; - } - - pPlayer->m_LastChangeInfo = Server()->Tick(); - - // set start infos - Server()->SetClientName(ClientID, pMsg->m_pName); - // trying to set client name can delete the player object, check if it still exists - if(!m_apPlayers[ClientID]) - { - return; - } - Server()->SetClientClan(ClientID, pMsg->m_pClan); - Server()->SetClientCountry(ClientID, pMsg->m_Country); - str_copy(pPlayer->m_TeeInfos.m_aSkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_aSkinName)); - pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; - pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; - pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; - if(!Server()->IsSixup(ClientID)) - pPlayer->m_TeeInfos.ToSixup(); - - // send clear vote options - CNetMsg_Sv_VoteClearOptions ClearMsg; - Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientID); - - // begin sending vote options - pPlayer->m_SendVoteIndex = 0; - - // send tuning parameters to client - SendTuningParams(ClientID, pPlayer->m_TuneZone); - - // client is ready to enter - pPlayer->m_IsReady = true; - CNetMsg_Sv_ReadyToEnter m; - Server()->SendPackMsg(&m, MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientID); - - Server()->ExpireServerInfo(); + OnStartInfoNetMessage(static_cast(pRawMsg), ClientID); } } +void CGameContext::OnSayNetMessage(const CNetMsg_Cl_Say *pMsg, int ClientID, const CUnpacker *pUnpacker) +{ + if(!str_utf8_check(pMsg->m_pMessage)) + { + return; + } + CPlayer *pPlayer = m_apPlayers[ClientID]; + bool Check = !pPlayer->m_NotEligibleForFinish && pPlayer->m_EligibleForFinishCheck + 10 * time_freq() >= time_get(); + if(Check && str_comp(pMsg->m_pMessage, "xd sure chillerbot.png is lyfe") == 0 && pMsg->m_Team == 0) + { + if(m_TeeHistorianActive) + { + m_TeeHistorian.RecordPlayerMessage(ClientID, pUnpacker->CompleteData(), pUnpacker->CompleteSize()); + } + + pPlayer->m_NotEligibleForFinish = true; + dbg_msg("hack", "bot detected, cid=%d", ClientID); + return; + } + int Team = pMsg->m_Team; + + // trim right and set maximum length to 256 utf8-characters + int Length = 0; + const char *p = pMsg->m_pMessage; + const char *pEnd = 0; + while(*p) + { + const char *pStrOld = p; + int Code = str_utf8_decode(&p); + + // check if unicode is not empty + if(!str_utf8_isspace(Code)) + { + pEnd = 0; + } + else if(pEnd == 0) + pEnd = pStrOld; + + if(++Length >= 256) + { + *(const_cast(p)) = 0; + break; + } + } + if(pEnd != 0) + *(const_cast(pEnd)) = 0; + + // drop empty and autocreated spam messages (more than 32 characters per second) + if(Length == 0 || (pMsg->m_pMessage[0] != '/' && (g_Config.m_SvSpamprotection && pPlayer->m_LastChat && pPlayer->m_LastChat + Server()->TickSpeed() * ((31 + Length) / 32) > Server()->Tick()))) + return; + + int GameTeam = ((CGameControllerDDRace *)m_pController)->m_Teams.m_Core.Team(pPlayer->GetCID()); + if(Team) + Team = ((pPlayer->GetTeam() == TEAM_SPECTATORS) ? CHAT_SPEC : GameTeam); + else + Team = CHAT_ALL; + + if(pMsg->m_pMessage[0] == '/') + { + if(str_startswith_nocase(pMsg->m_pMessage + 1, "w ")) + { + char aWhisperMsg[256]; + str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); + Whisper(pPlayer->GetCID(), aWhisperMsg); + } + else if(str_startswith_nocase(pMsg->m_pMessage + 1, "whisper ")) + { + char aWhisperMsg[256]; + str_copy(aWhisperMsg, pMsg->m_pMessage + 9, 256); + Whisper(pPlayer->GetCID(), aWhisperMsg); + } + else if(str_startswith_nocase(pMsg->m_pMessage + 1, "c ")) + { + char aWhisperMsg[256]; + str_copy(aWhisperMsg, pMsg->m_pMessage + 3, 256); + Converse(pPlayer->GetCID(), aWhisperMsg); + } + else if(str_startswith_nocase(pMsg->m_pMessage + 1, "converse ")) + { + char aWhisperMsg[256]; + str_copy(aWhisperMsg, pMsg->m_pMessage + 10, 256); + Converse(pPlayer->GetCID(), aWhisperMsg); + } + else + { + if(g_Config.m_SvSpamprotection && !str_startswith(pMsg->m_pMessage + 1, "timeout ") && pPlayer->m_aLastCommands[0] && pPlayer->m_aLastCommands[0] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[1] && pPlayer->m_aLastCommands[1] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[2] && pPlayer->m_aLastCommands[2] + Server()->TickSpeed() > Server()->Tick() && pPlayer->m_aLastCommands[3] && pPlayer->m_aLastCommands[3] + Server()->TickSpeed() > Server()->Tick()) + return; + + int64_t Now = Server()->Tick(); + pPlayer->m_aLastCommands[pPlayer->m_LastCommandPos] = Now; + pPlayer->m_LastCommandPos = (pPlayer->m_LastCommandPos + 1) % 4; + + Console()->SetFlagMask(CFGFLAG_CHAT); + int Authed = Server()->GetAuthedState(ClientID); + if(Authed) + Console()->SetAccessLevel(Authed == AUTHED_ADMIN ? IConsole::ACCESS_LEVEL_ADMIN : Authed == AUTHED_MOD ? IConsole::ACCESS_LEVEL_MOD : IConsole::ACCESS_LEVEL_HELPER); + else + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_USER); + + { + CClientChatLogger Logger(this, ClientID, log_get_scope_logger()); + CLogScope Scope(&Logger); + Console()->ExecuteLine(pMsg->m_pMessage + 1, ClientID, false); + } + // m_apPlayers[ClientID] can be NULL, if the player used a + // timeout code and replaced another client. + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "%d used %s", ClientID, pMsg->m_pMessage); + Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "chat-command", aBuf); + + Console()->SetAccessLevel(IConsole::ACCESS_LEVEL_ADMIN); + Console()->SetFlagMask(CFGFLAG_SERVER); + } + } + else + { + pPlayer->UpdatePlaytime(); + char aCensoredMessage[256]; + CensorMessage(aCensoredMessage, pMsg->m_pMessage, sizeof(aCensoredMessage)); + SendChat(ClientID, Team, aCensoredMessage, ClientID); + } +} + +void CGameContext::OnCallVoteNetMessage(const CNetMsg_Cl_CallVote *pMsg, int ClientID) +{ + if(RateLimitPlayerVote(ClientID) || m_VoteCloseTime) + return; + + m_apPlayers[ClientID]->UpdatePlaytime(); + + m_VoteType = VOTE_TYPE_UNKNOWN; + char aChatmsg[512] = {0}; + char aDesc[VOTE_DESC_LENGTH] = {0}; + char aSixupDesc[VOTE_DESC_LENGTH] = {0}; + char aCmd[VOTE_CMD_LENGTH] = {0}; + char aReason[VOTE_REASON_LENGTH] = "No reason given"; + if(!str_utf8_check(pMsg->m_pType) || !str_utf8_check(pMsg->m_pReason) || !str_utf8_check(pMsg->m_pValue)) + { + return; + } + if(pMsg->m_pReason[0]) + { + str_copy(aReason, pMsg->m_pReason, sizeof(aReason)); + } + + if(str_comp_nocase(pMsg->m_pType, "option") == 0) + { + int Authed = Server()->GetAuthedState(ClientID); + CVoteOptionServer *pOption = m_pVoteOptionFirst; + while(pOption) + { + if(str_comp_nocase(pMsg->m_pValue, pOption->m_aDescription) == 0) + { + if(!Console()->LineIsValid(pOption->m_aCommand)) + { + SendChatTarget(ClientID, "Invalid option"); + return; + } + if((str_find(pOption->m_aCommand, "sv_map ") != 0 || str_find(pOption->m_aCommand, "change_map ") != 0 || str_find(pOption->m_aCommand, "random_map") != 0 || str_find(pOption->m_aCommand, "random_unfinished_map") != 0) && RateLimitPlayerMapVote(ClientID)) + { + return; + } + + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s' (%s)", Server()->ClientName(ClientID), + pOption->m_aDescription, aReason); + str_copy(aDesc, pOption->m_aDescription); + + if((str_endswith(pOption->m_aCommand, "random_map") || str_endswith(pOption->m_aCommand, "random_unfinished_map")) && str_length(aReason) == 1 && aReason[0] >= '0' && aReason[0] <= '5') + { + int Stars = aReason[0] - '0'; + str_format(aCmd, sizeof(aCmd), "%s %d", pOption->m_aCommand, Stars); + } + else + { + str_copy(aCmd, pOption->m_aCommand); + } + + m_LastMapVote = time_get(); + break; + } + + pOption = pOption->m_pNext; + } + + if(!pOption) + { + if(Authed != AUTHED_ADMIN) // allow admins to call any vote they want + { + str_format(aChatmsg, sizeof(aChatmsg), "'%s' isn't an option on this server", pMsg->m_pValue); + SendChatTarget(ClientID, aChatmsg); + return; + } + else + { + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called vote to change server option '%s'", Server()->ClientName(ClientID), pMsg->m_pValue); + str_copy(aDesc, pMsg->m_pValue); + str_copy(aCmd, pMsg->m_pValue); + } + } + + m_VoteType = VOTE_TYPE_OPTION; + } + else if(str_comp_nocase(pMsg->m_pType, "kick") == 0) + { + int Authed = Server()->GetAuthedState(ClientID); + if(!Authed && time_get() < m_apPlayers[ClientID]->m_Last_KickVote + (time_freq() * 5)) + return; + else if(!Authed && time_get() < m_apPlayers[ClientID]->m_Last_KickVote + (time_freq() * g_Config.m_SvVoteKickDelay)) + { + str_format(aChatmsg, sizeof(aChatmsg), "There's a %d second wait time between kick votes for each player please wait %d second(s)", + g_Config.m_SvVoteKickDelay, + (int)(((m_apPlayers[ClientID]->m_Last_KickVote + (m_apPlayers[ClientID]->m_Last_KickVote * time_freq())) / time_freq()) - (time_get() / time_freq()))); + SendChatTarget(ClientID, aChatmsg); + m_apPlayers[ClientID]->m_Last_KickVote = time_get(); + return; + } + else if(!g_Config.m_SvVoteKick && !Authed) // allow admins to call kick votes even if they are forbidden + { + SendChatTarget(ClientID, "Server does not allow voting to kick players"); + m_apPlayers[ClientID]->m_Last_KickVote = time_get(); + return; + } + + if(g_Config.m_SvVoteKickMin && !GetDDRaceTeam(ClientID)) + { + char aaAddresses[MAX_CLIENTS][NETADDR_MAXSTRSIZE] = {{0}}; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_apPlayers[i]) + { + Server()->GetClientAddr(i, aaAddresses[i], NETADDR_MAXSTRSIZE); + } + } + int NumPlayers = 0; + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(i)) + { + NumPlayers++; + for(int j = 0; j < i; j++) + { + if(m_apPlayers[j] && m_apPlayers[j]->GetTeam() != TEAM_SPECTATORS && !GetDDRaceTeam(j)) + { + if(str_comp(aaAddresses[i], aaAddresses[j]) == 0) + { + NumPlayers--; + break; + } + } + } + } + } + + if(NumPlayers < g_Config.m_SvVoteKickMin) + { + str_format(aChatmsg, sizeof(aChatmsg), "Kick voting requires %d players", g_Config.m_SvVoteKickMin); + SendChatTarget(ClientID, aChatmsg); + return; + } + } + + int KickID = str_toint(pMsg->m_pValue); + + if(KickID < 0 || KickID >= MAX_CLIENTS || !m_apPlayers[KickID]) + { + SendChatTarget(ClientID, "Invalid client id to kick"); + return; + } + if(KickID == ClientID) + { + SendChatTarget(ClientID, "You can't kick yourself"); + return; + } + if(!Server()->ReverseTranslate(KickID, ClientID)) + { + return; + } + int KickedAuthed = Server()->GetAuthedState(KickID); + if(KickedAuthed > Authed) + { + SendChatTarget(ClientID, "You can't kick authorized players"); + m_apPlayers[ClientID]->m_Last_KickVote = time_get(); + char aBufKick[128]; + str_format(aBufKick, sizeof(aBufKick), "'%s' called for vote to kick you", Server()->ClientName(ClientID)); + SendChatTarget(KickID, aBufKick); + return; + } + + // Don't allow kicking if a player has no character + if(!GetPlayerChar(ClientID) || !GetPlayerChar(KickID) || GetDDRaceTeam(ClientID) != GetDDRaceTeam(KickID)) + { + SendChatTarget(ClientID, "You can kick only your team member"); + m_apPlayers[ClientID]->m_Last_KickVote = time_get(); + return; + } + + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to kick '%s' (%s)", Server()->ClientName(ClientID), Server()->ClientName(KickID), aReason); + str_format(aSixupDesc, sizeof(aSixupDesc), "%2d: %s", KickID, Server()->ClientName(KickID)); + if(!GetDDRaceTeam(ClientID)) + { + if(!g_Config.m_SvVoteKickBantime) + { + str_format(aCmd, sizeof(aCmd), "kick %d Kicked by vote", KickID); + str_format(aDesc, sizeof(aDesc), "Kick '%s'", Server()->ClientName(KickID)); + } + else + { + char aAddrStr[NETADDR_MAXSTRSIZE] = {0}; + Server()->GetClientAddr(KickID, aAddrStr, sizeof(aAddrStr)); + str_format(aCmd, sizeof(aCmd), "ban %s %d Banned by vote", aAddrStr, g_Config.m_SvVoteKickBantime); + str_format(aDesc, sizeof(aDesc), "Ban '%s'", Server()->ClientName(KickID)); + } + } + else + { + str_format(aCmd, sizeof(aCmd), "uninvite %d %d; set_team_ddr %d 0", KickID, GetDDRaceTeam(KickID), KickID); + str_format(aDesc, sizeof(aDesc), "Move '%s' to team 0", Server()->ClientName(KickID)); + } + m_apPlayers[ClientID]->m_Last_KickVote = time_get(); + m_VoteType = VOTE_TYPE_KICK; + m_VoteVictim = KickID; + } + else if(str_comp_nocase(pMsg->m_pType, "spectate") == 0) + { + if(!g_Config.m_SvVoteSpectate) + { + SendChatTarget(ClientID, "Server does not allow voting to move players to spectators"); + return; + } + + int SpectateID = str_toint(pMsg->m_pValue); + + if(SpectateID < 0 || SpectateID >= MAX_CLIENTS || !m_apPlayers[SpectateID] || m_apPlayers[SpectateID]->GetTeam() == TEAM_SPECTATORS) + { + SendChatTarget(ClientID, "Invalid client id to move"); + return; + } + if(SpectateID == ClientID) + { + SendChatTarget(ClientID, "You can't move yourself"); + return; + } + if(!Server()->ReverseTranslate(SpectateID, ClientID)) + { + return; + } + + if(!GetPlayerChar(ClientID) || !GetPlayerChar(SpectateID) || GetDDRaceTeam(ClientID) != GetDDRaceTeam(SpectateID)) + { + SendChatTarget(ClientID, "You can only move your team member to spectators"); + return; + } + + str_format(aSixupDesc, sizeof(aSixupDesc), "%2d: %s", SpectateID, Server()->ClientName(SpectateID)); + if(g_Config.m_SvPauseable && g_Config.m_SvVotePause) + { + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to pause '%s' for %d seconds (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), g_Config.m_SvVotePauseTime, aReason); + str_format(aDesc, sizeof(aDesc), "Pause '%s' (%ds)", Server()->ClientName(SpectateID), g_Config.m_SvVotePauseTime); + str_format(aCmd, sizeof(aCmd), "uninvite %d %d; force_pause %d %d", SpectateID, GetDDRaceTeam(SpectateID), SpectateID, g_Config.m_SvVotePauseTime); + } + else + { + str_format(aChatmsg, sizeof(aChatmsg), "'%s' called for vote to move '%s' to spectators (%s)", Server()->ClientName(ClientID), Server()->ClientName(SpectateID), aReason); + str_format(aDesc, sizeof(aDesc), "Move '%s' to spectators", Server()->ClientName(SpectateID)); + str_format(aCmd, sizeof(aCmd), "uninvite %d %d; set_team %d -1 %d", SpectateID, GetDDRaceTeam(SpectateID), SpectateID, g_Config.m_SvVoteSpectateRejoindelay); + } + m_VoteType = VOTE_TYPE_SPECTATE; + m_VoteVictim = SpectateID; + } + + if(aCmd[0] && str_comp_nocase(aCmd, "info") != 0) + CallVote(ClientID, aDesc, aCmd, aReason, aChatmsg, aSixupDesc[0] ? aSixupDesc : 0); +} + +void CGameContext::OnVoteNetMessage(const CNetMsg_Cl_Vote *pMsg, int ClientID) +{ + if(!m_VoteCloseTime) + return; + + CPlayer *pPlayer = m_apPlayers[ClientID]; + + if(g_Config.m_SvSpamprotection && pPlayer->m_LastVoteTry && pPlayer->m_LastVoteTry + Server()->TickSpeed() * 3 > Server()->Tick()) + return; + + int64_t Now = Server()->Tick(); + + pPlayer->m_LastVoteTry = Now; + pPlayer->UpdatePlaytime(); + + if(!pMsg->m_Vote) + return; + + pPlayer->m_Vote = pMsg->m_Vote; + pPlayer->m_VotePos = ++m_VotePos; + m_VoteUpdate = true; + + CNetMsg_Sv_YourVote Msg = {pMsg->m_Vote}; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientID); +} + +void CGameContext::OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int ClientID) +{ + if(m_World.m_Paused) + return; + + CPlayer *pPlayer = m_apPlayers[ClientID]; + + if(pPlayer->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && pPlayer->m_LastSetTeam && pPlayer->m_LastSetTeam + Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick())) + return; + + // Kill Protection + CCharacter *pChr = pPlayer->GetCharacter(); + if(pChr) + { + int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); + if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) + { + SendChatTarget(ClientID, "Kill Protection enabled. If you really want to join the spectators, first type /kill"); + return; + } + } + + if(pPlayer->m_TeamChangeTick > Server()->Tick()) + { + pPlayer->m_LastSetTeam = Server()->Tick(); + int TimeLeft = (pPlayer->m_TeamChangeTick - Server()->Tick()) / Server()->TickSpeed(); + char aTime[32]; + str_time((int64_t)TimeLeft * 100, TIME_HOURS, aTime, sizeof(aTime)); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Time to wait before changing team: %s", aTime); + SendBroadcast(aBuf, ClientID); + return; + } + + // Switch team on given client and kill/respawn them + if(m_pController->CanJoinTeam(pMsg->m_Team, ClientID)) + { + if(pPlayer->IsPaused()) + SendChatTarget(ClientID, "Use /pause first then you can kill"); + else + { + if(pPlayer->GetTeam() == TEAM_SPECTATORS || pMsg->m_Team == TEAM_SPECTATORS) + m_VoteUpdate = true; + m_pController->DoTeamChange(pPlayer, pMsg->m_Team); + pPlayer->m_TeamChangeTick = Server()->Tick(); + } + } + else + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", Server()->MaxClients() - g_Config.m_SvSpectatorSlots); + SendBroadcast(aBuf, ClientID); + } +} + +void CGameContext::OnIsDDNetLegacyNetMessage(const CNetMsg_Cl_IsDDNetLegacy *pMsg, int ClientID, CUnpacker *pUnpacker) +{ + IServer::CClientInfo Info; + if(Server()->GetClientInfo(ClientID, &Info) && Info.m_GotDDNetVersion) + { + return; + } + int DDNetVersion = pUnpacker->GetInt(); + if(pUnpacker->Error() || DDNetVersion < 0) + { + DDNetVersion = VERSION_DDRACE; + } + Server()->SetClientDDNetVersion(ClientID, DDNetVersion); + OnClientDDNetVersionKnown(ClientID); +} + +void CGameContext::OnShowOthersLegacyNetMessage(const CNetMsg_Cl_ShowOthersLegacy *pMsg, int ClientID) +{ + if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) + { + CPlayer *pPlayer = m_apPlayers[ClientID]; + pPlayer->m_ShowOthers = pMsg->m_Show; + } +} + +void CGameContext::OnShowOthersNetMessage(const CNetMsg_Cl_ShowOthers *pMsg, int ClientID) +{ + if(g_Config.m_SvShowOthers && !g_Config.m_SvShowOthersDefault) + { + CPlayer *pPlayer = m_apPlayers[ClientID]; + pPlayer->m_ShowOthers = pMsg->m_Show; + } +} + +void CGameContext::OnShowDistanceNetMessage(const CNetMsg_Cl_ShowDistance *pMsg, int ClientID) +{ + CPlayer *pPlayer = m_apPlayers[ClientID]; + pPlayer->m_ShowDistance = vec2(pMsg->m_X, pMsg->m_Y); +} + +void CGameContext::OnSetSpectatorModeNetMessage(const CNetMsg_Cl_SetSpectatorMode *pMsg, int ClientID) +{ + if(m_World.m_Paused) + return; + + int SpectatorID = clamp(pMsg->m_SpectatorID, (int)SPEC_FOLLOW, MAX_CLIENTS - 1); + if(SpectatorID >= 0) + if(!Server()->ReverseTranslate(SpectatorID, ClientID)) + return; + + CPlayer *pPlayer = m_apPlayers[ClientID]; + if((g_Config.m_SvSpamprotection && pPlayer->m_LastSetSpectatorMode && pPlayer->m_LastSetSpectatorMode + Server()->TickSpeed() / 4 > Server()->Tick())) + return; + + pPlayer->m_LastSetSpectatorMode = Server()->Tick(); + pPlayer->UpdatePlaytime(); + if(SpectatorID >= 0 && (!m_apPlayers[SpectatorID] || m_apPlayers[SpectatorID]->GetTeam() == TEAM_SPECTATORS)) + SendChatTarget(ClientID, "Invalid spectator id used"); + else + pPlayer->m_SpectatorID = SpectatorID; +} + +void CGameContext::OnChangeInfoNetMessage(const CNetMsg_Cl_ChangeInfo *pMsg, int ClientID) +{ + CPlayer *pPlayer = m_apPlayers[ClientID]; + if(g_Config.m_SvSpamprotection && pPlayer->m_LastChangeInfo && pPlayer->m_LastChangeInfo + Server()->TickSpeed() * g_Config.m_SvInfoChangeDelay > Server()->Tick()) + return; + + bool SixupNeedsUpdate = false; + + if(!str_utf8_check(pMsg->m_pName) || !str_utf8_check(pMsg->m_pClan) || !str_utf8_check(pMsg->m_pSkin)) + { + return; + } + pPlayer->m_LastChangeInfo = Server()->Tick(); + pPlayer->UpdatePlaytime(); + + // set infos + if(Server()->WouldClientNameChange(ClientID, pMsg->m_pName) && !ProcessSpamProtection(ClientID)) + { + char aOldName[MAX_NAME_LENGTH]; + str_copy(aOldName, Server()->ClientName(ClientID), sizeof(aOldName)); + + Server()->SetClientName(ClientID, pMsg->m_pName); + + char aChatText[256]; + str_format(aChatText, sizeof(aChatText), "'%s' changed name to '%s'", aOldName, Server()->ClientName(ClientID)); + SendChat(-1, CGameContext::CHAT_ALL, aChatText); + + // reload scores + Score()->PlayerData(ClientID)->Reset(); + m_apPlayers[ClientID]->m_Score.reset(); + Score()->LoadPlayerData(ClientID); + + SixupNeedsUpdate = true; + + LogEvent("Name change", ClientID); + } + + if(str_comp(Server()->ClientClan(ClientID), pMsg->m_pClan)) + SixupNeedsUpdate = true; + Server()->SetClientClan(ClientID, pMsg->m_pClan); + + if(Server()->ClientCountry(ClientID) != pMsg->m_Country) + SixupNeedsUpdate = true; + Server()->SetClientCountry(ClientID, pMsg->m_Country); + + str_copy(pPlayer->m_TeeInfos.m_aSkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_aSkinName)); + pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; + pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; + pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; + if(!Server()->IsSixup(ClientID)) + pPlayer->m_TeeInfos.ToSixup(); + + if(SixupNeedsUpdate) + { + protocol7::CNetMsg_Sv_ClientDrop Drop; + Drop.m_ClientID = ClientID; + Drop.m_pReason = ""; + Drop.m_Silent = true; + + protocol7::CNetMsg_Sv_ClientInfo Info; + Info.m_ClientID = ClientID; + Info.m_pName = Server()->ClientName(ClientID); + Info.m_Country = pMsg->m_Country; + Info.m_pClan = pMsg->m_pClan; + Info.m_Local = 0; + Info.m_Silent = true; + Info.m_Team = pPlayer->GetTeam(); + + for(int p = 0; p < 6; p++) + { + Info.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; + Info.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; + Info.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; + } + + for(int i = 0; i < Server()->MaxClients(); i++) + { + if(i != ClientID) + { + Server()->SendPackMsg(&Drop, MSGFLAG_VITAL | MSGFLAG_NORECORD, i); + Server()->SendPackMsg(&Info, MSGFLAG_VITAL | MSGFLAG_NORECORD, i); + } + } + } + else + { + protocol7::CNetMsg_Sv_SkinChange Msg; + Msg.m_ClientID = ClientID; + for(int p = 0; p < 6; p++) + { + Msg.m_apSkinPartNames[p] = pPlayer->m_TeeInfos.m_apSkinPartNames[p]; + Msg.m_aSkinPartColors[p] = pPlayer->m_TeeInfos.m_aSkinPartColors[p]; + Msg.m_aUseCustomColors[p] = pPlayer->m_TeeInfos.m_aUseCustomColors[p]; + } + + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, -1); + } + + Server()->ExpireServerInfo(); +} + +void CGameContext::OnEmoticonNetMessage(const CNetMsg_Cl_Emoticon *pMsg, int ClientID) +{ + if(m_World.m_Paused) + return; + + CPlayer *pPlayer = m_apPlayers[ClientID]; + + auto &&CheckPreventEmote = [&](int64_t LastEmote, int64_t DelayInMs) { + return (LastEmote * (int64_t)1000) + (int64_t)Server()->TickSpeed() * DelayInMs > ((int64_t)Server()->Tick() * (int64_t)1000); + }; + + if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmote, (int64_t)g_Config.m_SvEmoticonMsDelay)) + return; + + CCharacter *pChr = pPlayer->GetCharacter(); + + // player needs a character to send emotes + if(!pChr) + return; + + pPlayer->m_LastEmote = Server()->Tick(); + pPlayer->UpdatePlaytime(); + + // check if the global emoticon is prevented and emotes are only send to nearby players + if(g_Config.m_SvSpamprotection && CheckPreventEmote((int64_t)pPlayer->m_LastEmoteGlobal, (int64_t)g_Config.m_SvGlobalEmoticonMsDelay)) + { + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_apPlayers[i] && pChr->CanSnapCharacter(i) && pChr->IsSnappingCharacterInView(i)) + { + SendEmoticon(ClientID, pMsg->m_Emoticon, i); + } + } + } + else + { + // else send emoticons to all players + pPlayer->m_LastEmoteGlobal = Server()->Tick(); + SendEmoticon(ClientID, pMsg->m_Emoticon, -1); + } + + if(g_Config.m_SvEmotionalTees && pPlayer->m_EyeEmoteEnabled) + { + int EmoteType = EMOTE_NORMAL; + switch(pMsg->m_Emoticon) + { + case EMOTICON_EXCLAMATION: + case EMOTICON_GHOST: + case EMOTICON_QUESTION: + case EMOTICON_WTF: + EmoteType = EMOTE_SURPRISE; + break; + case EMOTICON_DOTDOT: + case EMOTICON_DROP: + case EMOTICON_ZZZ: + EmoteType = EMOTE_BLINK; + break; + case EMOTICON_EYES: + case EMOTICON_HEARTS: + case EMOTICON_MUSIC: + EmoteType = EMOTE_HAPPY; + break; + case EMOTICON_OOP: + case EMOTICON_SORRY: + case EMOTICON_SUSHI: + EmoteType = EMOTE_PAIN; + break; + case EMOTICON_DEVILTEE: + case EMOTICON_SPLATTEE: + case EMOTICON_ZOMG: + EmoteType = EMOTE_ANGRY; + break; + default: + break; + } + pChr->SetEmote(EmoteType, Server()->Tick() + 2 * Server()->TickSpeed()); + } +} + +void CGameContext::OnKillNetMessage(const CNetMsg_Cl_Kill *pMsg, int ClientID) +{ + if(m_World.m_Paused) + return; + + if(m_VoteCloseTime && m_VoteCreator == ClientID && GetDDRaceTeam(ClientID) && (IsKickVote() || IsSpecVote())) + { + SendChatTarget(ClientID, "You are running a vote please try again after the vote is done!"); + return; + } + CPlayer *pPlayer = m_apPlayers[ClientID]; + if(pPlayer->m_LastKill && pPlayer->m_LastKill + Server()->TickSpeed() * g_Config.m_SvKillDelay > Server()->Tick()) + return; + if(pPlayer->IsPaused()) + return; + + CCharacter *pChr = pPlayer->GetCharacter(); + if(!pChr) + return; + + // Kill Protection + int CurrTime = (Server()->Tick() - pChr->m_StartTime) / Server()->TickSpeed(); + if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == DDRACE_STARTED) + { + SendChatTarget(ClientID, "Kill Protection enabled. If you really want to kill, type /kill"); + return; + } + + pPlayer->m_LastKill = Server()->Tick(); + pPlayer->KillCharacter(WEAPON_SELF); + pPlayer->Respawn(); +} + +void CGameContext::OnStartInfoNetMessage(const CNetMsg_Cl_StartInfo *pMsg, int ClientID) +{ + CPlayer *pPlayer = m_apPlayers[ClientID]; + + if(pPlayer->m_IsReady) + return; + + if(!str_utf8_check(pMsg->m_pName)) + { + Server()->Kick(ClientID, "name is not valid utf8"); + return; + } + if(!str_utf8_check(pMsg->m_pClan)) + { + Server()->Kick(ClientID, "clan is not valid utf8"); + return; + } + if(!str_utf8_check(pMsg->m_pSkin)) + { + Server()->Kick(ClientID, "skin is not valid utf8"); + return; + } + + pPlayer->m_LastChangeInfo = Server()->Tick(); + + // set start infos + Server()->SetClientName(ClientID, pMsg->m_pName); + // trying to set client name can delete the player object, check if it still exists + if(!m_apPlayers[ClientID]) + { + return; + } + Server()->SetClientClan(ClientID, pMsg->m_pClan); + Server()->SetClientCountry(ClientID, pMsg->m_Country); + str_copy(pPlayer->m_TeeInfos.m_aSkinName, pMsg->m_pSkin, sizeof(pPlayer->m_TeeInfos.m_aSkinName)); + pPlayer->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor; + pPlayer->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody; + pPlayer->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet; + if(!Server()->IsSixup(ClientID)) + pPlayer->m_TeeInfos.ToSixup(); + + // send clear vote options + CNetMsg_Sv_VoteClearOptions ClearMsg; + Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientID); + + // begin sending vote options + pPlayer->m_SendVoteIndex = 0; + + // send tuning parameters to client + SendTuningParams(ClientID, pPlayer->m_TuneZone); + + // client is ready to enter + pPlayer->m_IsReady = true; + CNetMsg_Sv_ReadyToEnter m; + Server()->SendPackMsg(&m, MSGFLAG_VITAL | MSGFLAG_FLUSH, ClientID); + + Server()->ExpireServerInfo(); +} + void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; @@ -3324,6 +3394,16 @@ void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *p } } +void CGameContext::ConchainSettingUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + { + CGameContext *pSelf = (CGameContext *)pUserData; + pSelf->SendSettings(-1); + } +} + void CGameContext::OnConsoleInit() { m_pServer = Kernel()->RequestInterface(); @@ -3363,21 +3443,28 @@ void CGameContext::OnConsoleInit() Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this); + Console()->Chain("sv_vote_kick", ConchainSettingUpdate, this); + Console()->Chain("sv_vote_kick_min", ConchainSettingUpdate, this); + Console()->Chain("sv_vote_spectate", ConchainSettingUpdate, this); + Console()->Chain("sv_spectator_slots", ConchainSettingUpdate, this); + Console()->Chain("sv_max_clients", ConchainSettingUpdate, this); + #define CONSOLE_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, callback, userdata, help); #include #define CHAT_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, callback, userdata, help); #include } -void CGameContext::OnInit() +void CGameContext::OnInit(const void *pPersistentData) { + const CPersistentData *pPersistent = (const CPersistentData *)pPersistentData; + m_pServer = Kernel()->RequestInterface(); m_pConfig = Kernel()->RequestInterface()->Values(); m_pConsole = Kernel()->RequestInterface(); m_pEngine = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); m_pAntibot = Kernel()->RequestInterface(); - m_pAntibot->RoundStart(this); m_World.SetGameServer(this); m_Events.SetGameServer(this); @@ -3396,6 +3483,7 @@ void CGameContext::OnInit() m_Layers.Init(Kernel()); m_Collision.Init(&m_Layers); + m_World.m_pTuningList = m_aTuningList; m_World.m_Core.InitSwitchers(m_Collision.m_HighestSwitchNumber); char aMapName[IO_MAX_PATH_LENGTH]; @@ -3543,6 +3631,17 @@ void CGameContext::OnInit() GameInfo.m_MapSha256 = MapSha256; GameInfo.m_MapCrc = MapCrc; + if(pPersistent) + { + GameInfo.m_HavePrevGameUuid = true; + GameInfo.m_PrevGameUuid = pPersistent->m_PrevGameUuid; + } + else + { + GameInfo.m_HavePrevGameUuid = false; + mem_zero(&GameInfo.m_PrevGameUuid, sizeof(GameInfo.m_PrevGameUuid)); + } + m_TeeHistorian.Reset(&GameInfo, TeeHistorianWrite, this); for(int i = 0; i < MAX_CLIENTS; i++) @@ -3566,6 +3665,8 @@ void CGameContext::OnInit() if(GIT_SHORTREV_HASH) Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "git-revision", GIT_SHORTREV_HASH); + m_pAntibot->RoundStart(this); + #ifdef CONF_DEBUG if(g_Config.m_DbgDummies) { @@ -3813,8 +3914,15 @@ void CGameContext::OnMapChange(char *pNewMapName, int MapNameSize) str_copy(m_aDeleteTempfile, aTemp, sizeof(m_aDeleteTempfile)); } -void CGameContext::OnShutdown() +void CGameContext::OnShutdown(void *pPersistentData) { + CPersistentData *pPersistent = (CPersistentData *)pPersistentData; + + if(pPersistent) + { + pPersistent->m_PrevGameUuid = m_GameUuid; + } + Antibot()->RoundEnd(); if(m_TeeHistorianActive) @@ -3908,6 +4016,67 @@ void CGameContext::OnPostSnap() m_Events.Clear(); } +void CGameContext::UpdatePlayerMaps() +{ + const auto DistCompare = [](std::pair a, std::pair b) -> bool { + return (a.first < b.first); + }; + + if(Server()->Tick() % g_Config.m_SvMapUpdateRate != 0) + return; + + std::pair Dist[MAX_CLIENTS]; + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(!Server()->ClientIngame(i)) + continue; + if(Server()->GetClientVersion(i) >= VERSION_DDNET_OLD) + continue; + int *pMap = Server()->GetIdMap(i); + + // compute distances + for(int j = 0; j < MAX_CLIENTS; j++) + { + Dist[j].second = j; + if(j == i) + continue; + if(!Server()->ClientIngame(j) || !m_apPlayers[j]) + { + Dist[j].first = 1e10; + continue; + } + CCharacter *pChr = m_apPlayers[j]->GetCharacter(); + if(!pChr) + { + Dist[j].first = 1e9; + continue; + } + if(!pChr->CanSnapCharacter(i)) + Dist[j].first = 1e8; + else + Dist[j].first = length_squared(m_apPlayers[i]->m_ViewPos - pChr->GetPos()); + } + + // always send the player themselves, even if all in same position + Dist[i].first = -1; + + std::nth_element(&Dist[0], &Dist[VANILLA_MAX_CLIENTS - 1], &Dist[MAX_CLIENTS], DistCompare); + + int Index = 1; // exclude self client id + for(int j = 0; j < VANILLA_MAX_CLIENTS - 1; j++) + { + pMap[j + 1] = -1; // also fill player with empty name to say chat msgs + if(Dist[j].second == i || Dist[j].first > 5e9f) + continue; + pMap[Index++] = Dist[j].second; + } + + // sort by real client ids, guarantee order on distance changes, O(Nlog(N)) worst case + // sort just clients in game always except first (self client id) and last (fake client id) indexes + std::sort(&pMap[1], &pMap[minimum(Index, VANILLA_MAX_CLIENTS - 1)]); + } +} + bool CGameContext::IsClientReady(int ClientID) const { return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReady; diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index f8fd9d150..5ba1fddab 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -132,6 +132,7 @@ class CGameContext : public IGameServer static void ConDrySave(IConsole::IResult *pResult, void *pUserData); static void ConDumpAntibot(IConsole::IResult *pResult, void *pUserData); static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainSettingUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConDumpLog(IConsole::IResult *pResult, void *pUserData); void Construct(int Resetting); @@ -139,6 +140,11 @@ class CGameContext : public IGameServer void AddVote(const char *pDescription, const char *pCommand); static int MapScan(const char *pName, int IsDir, int DirType, void *pUserData); + struct CPersistentData + { + CUuid m_PrevGameUuid; + }; + struct CPersistentClientData { bool m_IsSpectator; @@ -269,19 +275,34 @@ public: void LoadMapSettings(); // engine events - void OnInit() override; + void OnInit(const void *pPersistentData) override; void OnConsoleInit() override; void OnMapChange(char *pNewMapName, int MapNameSize) override; - void OnShutdown() override; + void OnShutdown(void *pPersistentData) override; void OnTick() override; void OnPreSnap() override; void OnSnap(int ClientID) override; void OnPostSnap() override; + void UpdatePlayerMaps(); + void *PreProcessMsg(int *pMsgID, CUnpacker *pUnpacker, int ClientID); void CensorMessage(char *pCensoredMessage, const char *pMessage, int Size); void OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) override; + void OnSayNetMessage(const CNetMsg_Cl_Say *pMsg, int ClientID, const CUnpacker *pUnpacker); + void OnCallVoteNetMessage(const CNetMsg_Cl_CallVote *pMsg, int ClientID); + void OnVoteNetMessage(const CNetMsg_Cl_Vote *pMsg, int ClientID); + void OnSetTeamNetMessage(const CNetMsg_Cl_SetTeam *pMsg, int ClientID); + void OnIsDDNetLegacyNetMessage(const CNetMsg_Cl_IsDDNetLegacy *pMsg, int ClientID, CUnpacker *pUnpacker); + void OnShowOthersLegacyNetMessage(const CNetMsg_Cl_ShowOthersLegacy *pMsg, int ClientID); + void OnShowOthersNetMessage(const CNetMsg_Cl_ShowOthers *pMsg, int ClientID); + void OnShowDistanceNetMessage(const CNetMsg_Cl_ShowDistance *pMsg, int ClientID); + void OnSetSpectatorModeNetMessage(const CNetMsg_Cl_SetSpectatorMode *pMsg, int ClientID); + void OnChangeInfoNetMessage(const CNetMsg_Cl_ChangeInfo *pMsg, int ClientID); + void OnEmoticonNetMessage(const CNetMsg_Cl_Emoticon *pMsg, int ClientID); + void OnKillNetMessage(const CNetMsg_Cl_Kill *pMsg, int ClientID); + void OnStartInfoNetMessage(const CNetMsg_Cl_StartInfo *pMsg, int ClientID); bool OnClientDataPersist(int ClientID, void *pData) override; void OnClientConnected(int ClientID, void *pData) override; @@ -299,6 +320,7 @@ public: bool IsClientReady(int ClientID) const override; bool IsClientPlayer(int ClientID) const override; + int PersistentDataSize() const override { return sizeof(CPersistentData); } int PersistentClientDataSize() const override { return sizeof(CPersistentClientData); } CUuid GameUuid() const override; @@ -341,9 +363,12 @@ private: static void ConKillPlayer(IConsole::IResult *pResult, void *pUserData); static void ConNinja(IConsole::IResult *pResult, void *pUserData); + static void ConUnNinja(IConsole::IResult *pResult, void *pUserData); static void ConEndlessHook(IConsole::IResult *pResult, void *pUserData); static void ConUnEndlessHook(IConsole::IResult *pResult, void *pUserData); + static void ConSolo(IConsole::IResult *pResult, void *pUserData); static void ConUnSolo(IConsole::IResult *pResult, void *pUserData); + static void ConDeep(IConsole::IResult *pResult, void *pUserData); static void ConUnDeep(IConsole::IResult *pResult, void *pUserData); static void ConLiveFreeze(IConsole::IResult *pResult, void *pUserData); static void ConUnLiveFreeze(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index 7ea14b342..c35359a8f 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -6,7 +6,6 @@ #include "entity.h" #include "gamecontext.h" #include "gamecontroller.h" -#include "player.h" #include @@ -192,68 +191,6 @@ void CGameWorld::RemoveEntities() } } -bool distCompare(std::pair a, std::pair b) -{ - return (a.first < b.first); -} - -void CGameWorld::UpdatePlayerMaps() -{ - if(Server()->Tick() % g_Config.m_SvMapUpdateRate != 0) - return; - - std::pair Dist[MAX_CLIENTS]; - for(int i = 0; i < MAX_CLIENTS; i++) - { - if(!Server()->ClientIngame(i)) - continue; - if(Server()->GetClientVersion(i) >= VERSION_DDNET_OLD) - continue; - int *pMap = Server()->GetIdMap(i); - - // compute distances - for(int j = 0; j < MAX_CLIENTS; j++) - { - Dist[j].second = j; - if(j == i) - continue; - if(!Server()->ClientIngame(j) || !GameServer()->m_apPlayers[j]) - { - Dist[j].first = 1e10; - continue; - } - CCharacter *pChr = GameServer()->m_apPlayers[j]->GetCharacter(); - if(!pChr) - { - Dist[j].first = 1e9; - continue; - } - if(!pChr->CanSnapCharacter(i)) - Dist[j].first = 1e8; - else - Dist[j].first = length_squared(GameServer()->m_apPlayers[i]->m_ViewPos - pChr->m_Pos); - } - - // always send the player themselves, even if all in same position - Dist[i].first = -1; - - std::nth_element(&Dist[0], &Dist[VANILLA_MAX_CLIENTS - 1], &Dist[MAX_CLIENTS], distCompare); - - int Index = 1; // exclude self client id - for(int j = 0; j < VANILLA_MAX_CLIENTS - 1; j++) - { - pMap[j + 1] = -1; // also fill player with empty name to say chat msgs - if(Dist[j].second == i || Dist[j].first > 5e9f) - continue; - pMap[Index++] = Dist[j].second; - } - - // sort by real client ids, guarantee order on distance changes, O(Nlog(N)) worst case - // sort just clients in game always except first (self client id) and last (fake client id) indexes - std::sort(&pMap[1], &pMap[minimum(Index, VANILLA_MAX_CLIENTS - 1)]); - } -} - void CGameWorld::Tick() { if(m_ResetRequested) @@ -311,8 +248,6 @@ void CGameWorld::Tick() RemoveEntities(); - UpdatePlayerMaps(); - // find the characters' strong/weak id int StrongWeakID = 0; for(CCharacter *pChar = (CCharacter *)FindFirst(ENTTYPE_CHARACTER); pChar; pChar = (CCharacter *)pChar->TypeNext()) @@ -428,7 +363,7 @@ void CGameWorld::ReleaseHooked(int ClientID) for(; pChr; pChr = (CCharacter *)pChr->TypeNext()) { CCharacterCore *pCore = pChr->Core(); - if(pCore->m_HookedPlayer == ClientID && !pChr->IsSuper()) + if(pCore->HookedPlayer() == ClientID && !pChr->IsSuper()) { pCore->SetHookedPlayer(-1); pCore->m_TriggeredEvents |= COREEVENT_HOOK_RETRACT; @@ -436,3 +371,8 @@ void CGameWorld::ReleaseHooked(int ClientID) } } } + +CTuningParams *CGameWorld::Tuning() +{ + return &m_Core.m_aTuning[0]; +} diff --git a/src/game/server/gameworld.h b/src/game/server/gameworld.h index 120a550a1..c86af59fd 100644 --- a/src/game/server/gameworld.h +++ b/src/game/server/gameworld.h @@ -39,8 +39,6 @@ private: class CConfig *m_pConfig; class IServer *m_pServer; - void UpdatePlayerMaps(); - public: class CGameContext *GameServer() { return m_pGameServer; } class CConfig *Config() { return m_pConfig; } @@ -166,6 +164,12 @@ public: Returns list with all Characters on line. */ std::vector IntersectedCharacters(vec2 Pos0, vec2 Pos1, float Radius, const CEntity *pNotThis = nullptr); + + CTuningParams *Tuning(); + + CTuningParams *m_pTuningList; + CTuningParams *TuningList() { return m_pTuningList; } + CTuningParams *GetTuning(int i) { return &TuningList()[i]; } }; #endif diff --git a/src/game/server/player.h b/src/game/server/player.h index 7246079c5..799a04407 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -7,7 +7,8 @@ #include -#include "alloc.h" +#include + #include "teeinfo.h" #include diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index abdce8acf..fb764e77c 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -100,7 +100,7 @@ void CSaveTee::Save(CCharacter *pChr) m_HookTick = pChr->m_Core.m_HookTick; m_HookState = pChr->m_Core.m_HookState; - m_HookedPlayer = pChr->m_Core.m_HookedPlayer; + m_HookedPlayer = pChr->m_Core.HookedPlayer(); m_NewHook = pChr->m_Core.m_NewHook != 0; m_InputDirection = pChr->m_SavedInput.m_Direction; diff --git a/src/game/server/scoreworker.cpp b/src/game/server/scoreworker.cpp index 7e4e77c38..11b0e09af 100644 --- a/src/game/server/scoreworker.cpp +++ b/src/game/server/scoreworker.cpp @@ -425,7 +425,7 @@ bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameDat if(w == Write::NORMAL_FAILED) { int NumUpdated; - // move to non-tmp table succeded. delete from backup again + // move to non-tmp table succeeded. delete from backup again str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_race SELECT * FROM %s_race_backup WHERE GameId=? AND Name=? AND Timestamp=%s", pSqlServer->GetPrefix(), pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc()); @@ -442,7 +442,7 @@ bool CScoreWorker::SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameDat return true; } - // move to non-tmp table succeded. delete from backup again + // move to non-tmp table succeeded. delete from backup again str_format(aBuf, sizeof(aBuf), "DELETE FROM %s_race_backup WHERE GameId=? AND Name=? AND Timestamp=%s", pSqlServer->GetPrefix(), pSqlServer->InsertTimestampAsUtc()); @@ -1501,7 +1501,7 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData if(w == Write::NORMAL_SUCCEEDED) { - // write succeded on mysql server. delete from sqlite again + // write succeeded on mysql server. delete from sqlite again char aBuf[128] = {0}; str_format(aBuf, sizeof(aBuf), "DELETE FROM %s_saves_backup WHERE Code = ?", @@ -1518,7 +1518,7 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData { char aBuf[256] = {0}; bool End; - // move to non-tmp table succeded. delete from backup again + // move to non-tmp table succeeded. delete from backup again str_format(aBuf, sizeof(aBuf), "INSERT INTO %s_saves SELECT * FROM %s_saves_backup WHERE Code = ?", pSqlServer->GetPrefix(), pSqlServer->GetPrefix()); @@ -1532,7 +1532,7 @@ bool CScoreWorker::SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData return true; } - // move to non-tmp table succeded. delete from backup again + // move to non-tmp table succeeded. delete from backup again str_format(aBuf, sizeof(aBuf), "DELETE FROM %s_saves_backup WHERE Code = ?", pSqlServer->GetPrefix()); diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index b4f8850d1..30bf030c7 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -891,13 +891,8 @@ void CGameTeams::SwapTeamCharacters(CPlayer *pPrimaryPlayer, CPlayer *pTargetPla 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; - } - } + pPrimaryPlayer->m_SwapTargetsClientID = -1; + pTargetPlayer->m_SwapTargetsClientID = -1; int TimeoutAfterDelay = g_Config.m_SvSaveSwapGamesDelay + g_Config.m_SvSwapTimeout; if(Since >= TimeoutAfterDelay) diff --git a/src/game/server/teehistorian.cpp b/src/game/server/teehistorian.cpp index fd09aa046..d0bc838db 100644 --- a/src/game/server/teehistorian.cpp +++ b/src/game/server/teehistorian.cpp @@ -1,5 +1,6 @@ #include "teehistorian.h" +#include #include #include #include @@ -8,7 +9,7 @@ static const char TEEHISTORIAN_NAME[] = "teehistorian@ddnet.tw"; static const CUuid TEEHISTORIAN_UUID = CalculateUuid(TEEHISTORIAN_NAME); static const char TEEHISTORIAN_VERSION[] = "2"; -static const char TEEHISTORIAN_VERSION_MINOR[] = "5"; +static const char TEEHISTORIAN_VERSION_MINOR[] = "6"; #define UUID(id, name) static const CUuid UUID_##id = CalculateUuid(name); #include @@ -81,6 +82,18 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) str_timestamp_ex(pGameInfo->m_StartTime, aStartTime, sizeof(aStartTime), "%Y-%m-%dT%H:%M:%S%z"); sha256_str(pGameInfo->m_MapSha256, aMapSha256, sizeof(aMapSha256)); + char aPrevGameUuid[UUID_MAXSTRSIZE]; + char aPrevGameUuidJson[64]; + if(pGameInfo->m_HavePrevGameUuid) + { + FormatUuid(pGameInfo->m_PrevGameUuid, aPrevGameUuid, sizeof(aPrevGameUuid)); + str_format(aPrevGameUuidJson, sizeof(aPrevGameUuidJson), "\"prev_game_uuid\":\"%s\",", aPrevGameUuid); + } + else + { + aPrevGameUuidJson[0] = 0; + } + char aCommentBuffer[128]; char aServerVersionBuffer[128]; char aStartTimeBuffer[128]; @@ -100,6 +113,7 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) "\"version\":\"%s\"," "\"version_minor\":\"%s\"," "\"game_uuid\":\"%s\"," + "%s" "\"server_version\":\"%s\"," "\"start_time\":\"%s\"," "\"server_name\":\"%s\"," @@ -115,6 +129,7 @@ void CTeeHistorian::WriteHeader(const CGameInfo *pGameInfo) TEEHISTORIAN_VERSION, TEEHISTORIAN_VERSION_MINOR, aGameUuid, + aPrevGameUuidJson, E(aServerVersionBuffer, pGameInfo->m_pServerVersion), E(aStartTimeBuffer, aStartTime), E(aServerNameBuffer, pGameInfo->m_pServerName), diff --git a/src/game/server/teehistorian.h b/src/game/server/teehistorian.h index ffbfe3422..d4bc8d027 100644 --- a/src/game/server/teehistorian.h +++ b/src/game/server/teehistorian.h @@ -33,6 +33,9 @@ public: SHA256_DIGEST m_MapSha256; int m_MapCrc; + bool m_HavePrevGameUuid; + CUuid m_PrevGameUuid; + CConfig *m_pConfig; CTuningParams *m_pTuning; CUuidManager *m_pUuids; diff --git a/src/game/variables.h b/src/game/variables.h index 4cefb6bc6..f65cea0ab 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -70,7 +70,7 @@ MACRO_CONFIG_INT(ClShowpred, cl_showpred, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE MACRO_CONFIG_INT(ClEyeWheel, cl_eye_wheel, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show eye wheel along together with emotes") MACRO_CONFIG_INT(ClEyeDuration, cl_eye_duration, 999999, 1, 999999, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long the eyes emotes last") -MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") +MACRO_CONFIG_INT(ClAirjumpindicator, cl_airjumpindicator, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the air jump indicator") MACRO_CONFIG_INT(ClThreadsoundloading, cl_threadsoundloading, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Load sound files threaded") MACRO_CONFIG_INT(ClWarningTeambalance, cl_warning_teambalance, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Warn about team balance") @@ -98,9 +98,9 @@ MACRO_CONFIG_INT(EdAutosaveMax, ed_autosave_max, 10, 0, 1000, CFGFLAG_CLIENT | C MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Time of smooth zoom animation in the editor in ms (0 for off)") MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)") MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target") -MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") +MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys") -MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "") +MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client") MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day") // http map download diff --git a/src/game/version.h b/src/game/version.h index 65ef0f9e0..db1d9c0a4 100644 --- a/src/game/version.h +++ b/src/game/version.h @@ -7,7 +7,7 @@ #endif #define GAME_VERSION "0.6.4, " GAME_RELEASE_VERSION #define GAME_NETVERSION "0.6 626fce9a778df4d4" -#define CLIENT_VERSIONNR 17021 +#define DDNET_VERSION_NUMBER 17021 extern const char *GIT_SHORTREV_HASH; #define GAME_NAME "DDNet" #endif diff --git a/src/mastersrv/src/main.rs b/src/mastersrv/src/main.rs index 1c03f6a1b..d608fa415 100644 --- a/src/mastersrv/src/main.rs +++ b/src/mastersrv/src/main.rs @@ -718,8 +718,7 @@ fn handle_register( let info = i.as_object().ok_or("register info must be an object")?; // Normalize the JSON to strip any spaces etc. - let raw_info = json::to_string(&info).unwrap(); - Ok(json::value::RawValue::from_string(raw_info).unwrap()) + Ok(json::value::to_raw_value(&info).unwrap()) }) .transpose()?; diff --git a/src/test/teehistorian.cpp b/src/test/teehistorian.cpp index 48caa680a..9e88e7e0f 100644 --- a/src/test/teehistorian.cpp +++ b/src/test/teehistorian.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -68,6 +69,9 @@ protected: m_GameInfo.m_MapSha256 = Sha256; m_GameInfo.m_MapCrc = 0xeceaf25c; + m_GameInfo.m_HavePrevGameUuid = false; + mem_zero(&m_GameInfo.m_PrevGameUuid, sizeof(m_GameInfo.m_PrevGameUuid)); + m_GameInfo.m_pConfig = &m_Config; m_GameInfo.m_pTuning = &m_Tuning; m_GameInfo.m_pUuids = &m_UuidManager; @@ -101,7 +105,7 @@ protected: void Expect(const unsigned char *pOutput, size_t OutputSize) { static CUuid TEEHISTORIAN_UUID = CalculateUuid("teehistorian@ddnet.tw"); - static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"version_minor\":\"5\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\""; + static const char PREFIX1[] = "{\"comment\":\"teehistorian@ddnet.tw\",\"version\":\"2\",\"version_minor\":\"6\",\"game_uuid\":\"a1eb7182-796e-3b3e-941d-38ca71b2a4a8\",\"server_version\":\"DDNet test\",\"start_time\":\""; static const char PREFIX2[] = "\",\"server_name\":\"server name\",\"server_port\":\"8303\",\"game_type\":\"game type\",\"map_name\":\"Kobra 3 Solo\",\"map_size\":\"903514\",\"map_sha256\":\"0123456789012345678901234567890123456789012345678901234567890123\",\"map_crc\":\"eceaf25c\",\"prng_description\":\"test-prng:02468ace\",\"config\":{},\"tuning\":{},\"uuids\":["; static const char PREFIX3[] = "]}"; @@ -833,3 +837,23 @@ TEST_F(TeeHistorian, AntibotEmptyMessage) m_TH.RecordAntibot("🤖", 4); Expect(EXPECTED, sizeof(EXPECTED)); } + +TEST_F(TeeHistorian, PrevGameUuid) +{ + m_GameInfo.m_HavePrevGameUuid = true; + CUuid PrevGameUuid = {{ + // fe19c218-f555-4002-a273-126c59ccc17a + 0xfe, 0x19, 0xc2, 0x18, 0xf5, 0x55, 0x40, 0x02, + 0xa2, 0x73, 0x12, 0x6c, 0x59, 0xcc, 0xc1, 0x7a, + // + }}; + m_GameInfo.m_PrevGameUuid = PrevGameUuid; + Reset(&m_GameInfo); + Finish(); + json_value *pJson = json_parse((const char *)m_vBuffer.data() + 16, -1); + ASSERT_TRUE(pJson); + const json_value &JsonPrevGameUuid = (*pJson)["prev_game_uuid"]; + ASSERT_EQ(JsonPrevGameUuid.type, json_string); + EXPECT_STREQ(JsonPrevGameUuid, "fe19c218-f555-4002-a273-126c59ccc17a"); + json_value_free(pJson); +} diff --git a/src/tools/map_convert_07.cpp b/src/tools/map_convert_07.cpp index 679fde2f3..18ac1349c 100644 --- a/src/tools/map_convert_07.cpp +++ b/src/tools/map_convert_07.cpp @@ -124,7 +124,7 @@ void *ReplaceImageItem(CMapItemImage *pImgItem, CMapItemImage *pNewImgItem) pNewImgItem->m_ImageData = g_NextDataItemID++; g_apNewData[g_Index] = ImgInfo.m_pData; - g_aNewDataSize[g_Index] = ImgInfo.m_Width * ImgInfo.m_Height * 4; + g_aNewDataSize[g_Index] = (size_t)ImgInfo.m_Width * ImgInfo.m_Height * ImgInfo.PixelSize(); g_Index++; return (void *)pNewImgItem; diff --git a/src/tools/map_create_pixelart.cpp b/src/tools/map_create_pixelart.cpp index a40fefd08..52d54d471 100644 --- a/src/tools/map_create_pixelart.cpp +++ b/src/tools/map_create_pixelart.cpp @@ -220,9 +220,9 @@ bool GetPixelClamped(const CImageInfo &Img, int x, int y, uint8_t aPixel[4]) aPixel[2] = 255; aPixel[3] = 255; - int BPP = Img.m_Format == CImageInfo::FORMAT_RGB ? 3 : 4; - for(int i = 0; i < BPP; i++) - aPixel[i] = ((uint8_t *)Img.m_pData)[x * BPP + (Img.m_Width * BPP * y) + i]; + const size_t PixelSize = Img.PixelSize(); + for(size_t i = 0; i < PixelSize; i++) + aPixel[i] = ((uint8_t *)Img.m_pData)[x * PixelSize + (Img.m_Width * PixelSize * y) + i]; return aPixel[3] > 0; } diff --git a/src/tools/map_replace_image.cpp b/src/tools/map_replace_image.cpp index ca223526f..4576de186 100644 --- a/src/tools/map_replace_image.cpp +++ b/src/tools/map_replace_image.cpp @@ -95,7 +95,7 @@ void *ReplaceImageItem(CMapItemImage *pImgItem, const char *pImgName, const char IStorage::StripPathAndExtension(pImgFile, g_aNewName, sizeof(g_aNewName)); g_NewDataID = pImgItem->m_ImageData; g_pNewData = ImgInfo.m_pData; - g_NewDataSize = ImgInfo.m_Width * ImgInfo.m_Height * 4; + g_NewDataSize = (size_t)ImgInfo.m_Width * ImgInfo.m_Height * ImgInfo.PixelSize(); return (void *)pNewImgItem; }