cmake_minimum_required(VERSION 3.25)

# Target Apple Silicon even when CMake itself runs under Rosetta (an x86_64 cmake reports an x86_64 host).
if(APPLE AND NOT CMAKE_OSX_ARCHITECTURES)
  set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "Target architecture")
endif()
# The oldest macOS the binaries run on, as the README states; without it the compiler records the build machine's version.
if(APPLE AND NOT CMAKE_OSX_DEPLOYMENT_TARGET)
  set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0 CACHE STRING "Minimum macOS version")
endif()
project(openiw2 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# Every target of this project (libraries, tools, app, tests) builds warning-free; a new warning fails the build.
# Third-party code is only found through find_package, so these flags never reach it. Off by default so newer compilers do not break builds from source; maintainers and the release build use -DIW2_WERROR=ON.
option(IW2_WERROR "Treat compiler warnings as errors" OFF)
add_compile_options(-Wall -Wextra -Wpedantic)
if(IW2_WERROR)
  add_compile_options(-Werror)
endif()

find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED)

# The build's version (git describe of the checkout at configure time): the main menu corner (src/ui/appversion.h) and the
# first line of every log (src/util/log.cpp, docs/logging.md).
execute_process(COMMAND git describe --tags --always --dirty WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
                OUTPUT_VARIABLE IW2_GIT_DESCRIBE OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)

# Session logs, event lines and the trace recorder (docs/logging.md): POSIX only, no SDL; iw2view and iw2server.
add_library(iw2_log src/util/log.cpp)
target_include_directories(iw2_log PUBLIC src)
target_link_libraries(iw2_log PUBLIC Threads::Threads)
target_compile_definitions(iw2_log PRIVATE IW2_VERSION="${IW2_GIT_DESCRIBE}" IW2_BUILD_TYPE="${CMAKE_BUILD_TYPE}")

add_library(iw2_fs
  src/fs/zip_archive.cpp
  src/fs/vfs.cpp
  src/fs/gamedir.cpp)
target_include_directories(iw2_fs PUBLIC src)
target_link_libraries(iw2_fs PUBLIC ZLIB::ZLIB iw2_log)

add_library(iw2_assets
  src/assets/iwi.cpp
  src/assets/d3dbsp.cpp
  src/assets/material.cpp
  src/assets/xmodel.cpp
  src/assets/lightgrid.cpp
  src/assets/xanim.cpp
  src/assets/font.cpp
  src/assets/efx.cpp
  src/util/png.cpp
  src/util/screenshot.cpp)
target_include_directories(iw2_assets PUBLIC src)
target_link_libraries(iw2_assets PUBLIC ZLIB::ZLIB)

add_library(iw2_audio
  src/audio/sound.cpp
  src/audio/reverb.cpp
  src/audio/decode.cpp)
target_include_directories(iw2_audio PUBLIC src)
target_link_libraries(iw2_audio PUBLIC iw2_fs)
if(APPLE)
  # MP3 music is decoded with the system AudioToolbox framework (src/audio/decode_apple.cpp).
  target_sources(iw2_audio PRIVATE src/audio/decode_apple.cpp)
  target_link_libraries(iw2_audio PRIVATE "-framework AudioToolbox" "-framework CoreFoundation")
else()
  # No MP3 decoder elsewhere yet; enough for the headless tools (iw2server on Linux), which never decode music.
  target_sources(iw2_audio PRIVATE src/audio/decode_none.cpp)
endif()

add_library(iw2_video
  src/video/roq.cpp)
target_include_directories(iw2_video PUBLIC src)

add_library(iw2_script
  src/script/parser.cpp
  src/script/value.cpp
  src/script/vm.cpp
  src/script/vmstate.cpp
  src/script/stateio.cpp
  src/script/builtins_core.cpp)
target_include_directories(iw2_script PUBLIC src)
target_link_libraries(iw2_script PUBLIC iw2_log)

add_executable(iw2script tools/iw2script/main.cpp)
target_link_libraries(iw2script PRIVATE iw2_fs iw2_script iw2_assets)

add_library(iw2_game
  src/game/world.cpp
  src/game/corpses.cpp
  src/game/collision.cpp
  src/game/pmove.cpp
  src/game/animtree.cpp
  src/game/pathnodes.cpp
  src/game/actorfields.cpp
  src/game/combat.cpp src/game/bulletdamage.cpp src/game/playerweapon.cpp src/game/sounds.cpp src/game/menus.cpp src/game/hud.cpp src/game/inventory.cpp src/game/movers.cpp src/game/animscripted.cpp src/game/playerlink.cpp src/game/rockets.cpp src/game/viewweapon.cpp src/game/playerevents.cpp src/game/shellshock.cpp src/game/shellshockworld.cpp src/game/itempickup.cpp src/game/itempickupworld.cpp src/game/mpdrop.cpp src/game/mpturret.cpp src/game/triggerdamage.cpp
  src/game/vehicles.cpp src/game/vehicletriggers.cpp src/game/actortriggers.cpp src/game/vehicledrive.cpp src/game/campaign.cpp src/game/fx.cpp src/game/marks.cpp src/game/outdoor.cpp src/game/perception.cpp
  src/game/turrets.cpp src/game/turretthink.cpp src/game/entityclips.cpp src/game/actorturret.cpp src/game/hitboxes.cpp
  src/game/actorstate.cpp src/game/actorcover.cpp src/game/actorslide.cpp
  src/game/actorstate.cpp src/game/actorcover.cpp src/game/friendlychain.cpp src/game/friendlychainworld.cpp src/game/grenadehint.cpp
  src/game/actorpath.cpp src/game/actorfollow.cpp src/game/badplace.cpp src/game/spawnchecks.cpp src/game/softlock.cpp src/game/firemonitor.cpp src/game/inventorydiff.cpp src/game/grenades.cpp src/game/actorgrenade.cpp src/game/actorevents.cpp
  src/game/mpgame.cpp src/game/mpkillcam.cpp src/game/maprotation.cpp src/game/mphud.cpp src/game/mpchat.cpp src/game/mpscoreboard.cpp src/game/mpspectate.cpp src/game/mpgametypes.cpp src/game/mpbots.cpp src/game/mpbotcombat.cpp src/game/mpbotgoals.cpp src/game/mpplayers.cpp src/game/playeranimscript.cpp src/game/navgen.cpp src/game/watersim.cpp src/game/clientinput.cpp src/game/profilekeys.cpp src/game/playeractions.cpp src/game/playeruse.cpp
  src/game/mpremote.cpp src/game/prone.cpp src/game/proneworld.cpp src/game/savestate.cpp src/game/actorfacing.cpp src/game/actorpredict.cpp)
target_include_directories(iw2_game PUBLIC src)
target_link_libraries(iw2_game PUBLIC iw2_fs iw2_script iw2_assets iw2_ui ZLIB::ZLIB)

add_library(iw2_ui
  src/ui/menu.cpp
  src/ui/menuhide.cpp
  src/ui/menurun.cpp
  src/ui/mpserver.cpp
  src/ui/frontend.cpp
  src/ui/iw2options.cpp
  src/ui/profile.cpp
  src/ui/profilelist.cpp
  src/ui/savegame.cpp)
target_include_directories(iw2_ui PUBLIC src)
target_link_libraries(iw2_ui PUBLIC iw2_fs)

add_executable(iw2game tools/iw2game/main.cpp tools/iw2game/mp.cpp)
target_link_libraries(iw2game PRIVATE iw2_game iw2_audio)

add_executable(iw2server tools/iw2server/main.cpp)
target_link_libraries(iw2server PRIVATE iw2_dedicated)

add_executable(iw2fs tools/iw2fs/main.cpp)
target_link_libraries(iw2fs PRIVATE iw2_fs)

add_executable(iw2asset tools/iw2asset/main.cpp)
target_link_libraries(iw2asset PRIVATE iw2_assets iw2_audio) # iw2_fs comes with iw2_audio

add_executable(iw2roq tools/iw2roq/main.cpp)
target_link_libraries(iw2roq PRIVATE iw2_fs iw2_assets iw2_video)

enable_testing()
add_executable(test_vfs tests/test_vfs.cpp)
target_link_libraries(test_vfs PRIVATE iw2_fs)
add_test(NAME vfs COMMAND test_vfs)
add_executable(test_log tests/test_log.cpp)
target_link_libraries(test_log PRIVATE iw2_log)
add_test(NAME log COMMAND test_log)
add_executable(test_gamedir tests/test_gamedir.cpp)
target_link_libraries(test_gamedir PRIVATE iw2_fs)
add_test(NAME gamedir COMMAND test_gamedir)

add_executable(test_iwi tests/test_iwi.cpp)
target_link_libraries(test_iwi PRIVATE iw2_assets)
add_test(NAME iwi COMMAND test_iwi)

add_executable(test_screenshotname tests/test_screenshotname.cpp)
target_link_libraries(test_screenshotname PRIVATE iw2_assets)
add_test(NAME screenshotname COMMAND test_screenshotname)

add_executable(test_d3dbsp tests/test_d3dbsp.cpp)
target_link_libraries(test_d3dbsp PRIVATE iw2_assets)
add_test(NAME d3dbsp COMMAND test_d3dbsp)

find_package(SDL3 CONFIG)
if(SDL3_FOUND)
  add_executable(iw2view apps/iw2view/main.cpp apps/iw2view/jpeg.cpp)
  target_link_libraries(iw2view PRIVATE iw2_dedicated iw2_netgame iw2_video SDL3::SDL3)
  # The main menu shows the build's version (src/ui/appversion.h), IW2_GIT_DESCRIBE above.
  target_compile_definitions(iw2view PRIVATE IW2_VERSION="${IW2_GIT_DESCRIBE}")
  # The screenshotJpeg command encodes with ImageIO (apps/iw2view/jpeg.cpp).
  target_link_libraries(iw2view PRIVATE "-framework ImageIO" "-framework CoreGraphics" "-framework CoreFoundation")
  else()
  message(STATUS "SDL3 not found: iw2view will not be built (brew install sdl3)")
endif()

add_executable(test_screen_blur tests/test_screen_blur.cpp)
target_link_libraries(test_screen_blur PRIVATE iw2_game)
add_test(NAME screen_blur COMMAND test_screen_blur)
add_executable(test_glow_filter tests/test_glow_filter.cpp)
target_link_libraries(test_glow_filter PRIVATE iw2_game)
add_test(NAME glow_filter COMMAND test_glow_filter)

add_executable(test_xmodel tests/test_xmodel.cpp)
target_link_libraries(test_xmodel PRIVATE iw2_assets)
add_test(NAME xmodel COMMAND test_xmodel)

add_executable(test_xanim tests/test_xanim.cpp)
target_link_libraries(test_xanim PRIVATE iw2_assets)
add_test(NAME xanim COMMAND test_xanim)

add_executable(test_lightgrid tests/test_lightgrid.cpp)
target_link_libraries(test_lightgrid PRIVATE iw2_assets)
add_test(NAME lightgrid COMMAND test_lightgrid)

add_executable(test_vm tests/test_vm.cpp)
target_link_libraries(test_vm PRIVATE iw2_script)
add_test(NAME vm COMMAND test_vm)

add_executable(test_playerweapon tests/test_playerweapon.cpp)
target_link_libraries(test_playerweapon PRIVATE iw2_game)
add_test(NAME playerweapon COMMAND test_playerweapon)
add_test(NAME offhandturret COMMAND test_offhandturret)
add_executable(test_screenmessages tests/test_screenmessages.cpp)
target_link_libraries(test_screenmessages PRIVATE iw2_game)
add_test(NAME screenmessages COMMAND test_screenmessages)
add_executable(test_knockback tests/test_knockback.cpp)
target_link_libraries(test_knockback PRIVATE iw2_game)
add_test(NAME knockback COMMAND test_knockback)
add_executable(test_bulletdamage tests/test_bulletdamage.cpp)
target_link_libraries(test_bulletdamage PRIVATE iw2_game)
add_test(NAME bulletdamage COMMAND test_bulletdamage)
add_executable(test_damagerules tests/test_damagerules.cpp)
target_link_libraries(test_damagerules PRIVATE iw2_game)
add_test(NAME damagerules COMMAND test_damagerules)
add_executable(test_idledefer tests/test_idledefer.cpp)
target_link_libraries(test_idledefer PRIVATE iw2_game)
add_test(NAME idledefer COMMAND test_idledefer)

add_executable(test_clientinput tests/test_clientinput.cpp)
target_link_libraries(test_clientinput PRIVATE iw2_game)
add_test(NAME clientinput COMMAND test_clientinput)

add_executable(test_recoil tests/test_recoil.cpp)
target_link_libraries(test_recoil PRIVATE iw2_game)
add_test(NAME recoil COMMAND test_recoil)

add_executable(test_gunaim tests/test_gunaim.cpp)
target_link_libraries(test_gunaim PRIVATE iw2_game)
add_test(NAME gunaim COMMAND test_gunaim)
add_executable(test_viewimpulses tests/test_viewimpulses.cpp)
target_link_libraries(test_viewimpulses PRIVATE iw2_game)
add_test(NAME viewimpulses COMMAND test_viewimpulses)
add_executable(test_renderclock tests/test_renderclock.cpp)
add_test(NAME renderclock COMMAND test_renderclock ${CMAKE_SOURCE_DIR}/apps/iw2view/main.cpp)
add_executable(test_viewflash tests/test_viewflash.cpp)
target_link_libraries(test_viewflash PRIVATE iw2_game)
add_test(NAME viewflash COMMAND test_viewflash)
add_executable(test_playerevents tests/test_playerevents.cpp)
target_link_libraries(test_playerevents PRIVATE iw2_game)
add_test(NAME playerevents COMMAND test_playerevents)
add_executable(test_actorslide tests/test_actorslide.cpp)
target_link_libraries(test_actorslide PRIVATE iw2_game)
add_test(NAME actorslide COMMAND test_actorslide)

add_executable(test_shellshock tests/test_shellshock.cpp)
target_link_libraries(test_shellshock PRIVATE iw2_game)
add_test(NAME shellshock COMMAND test_shellshock)
add_executable(test_friendlychain tests/test_friendlychain.cpp)
target_link_libraries(test_friendlychain PRIVATE iw2_game)
add_test(NAME friendlychain COMMAND test_friendlychain)

add_executable(test_nodesearch tests/test_nodesearch.cpp)
target_link_libraries(test_nodesearch PRIVATE iw2_game)
add_test(NAME nodesearch COMMAND test_nodesearch)

add_executable(test_itempickup tests/test_itempickup.cpp)
target_link_libraries(test_itempickup PRIVATE iw2_game)
add_test(NAME itempickup COMMAND test_itempickup)

add_executable(test_sound tests/test_sound.cpp)
target_link_libraries(test_sound PRIVATE iw2_audio)
add_test(NAME sound COMMAND test_sound)

add_executable(test_menu tests/test_menu.cpp)
target_link_libraries(test_menu PRIVATE iw2_ui)
add_test(NAME menu COMMAND test_menu)
add_executable(test_menurun tests/test_menurun.cpp)
target_link_libraries(test_menurun PRIVATE iw2_ui)
add_test(NAME menurun COMMAND test_menurun)

add_executable(test_movers tests/test_movers.cpp)
target_link_libraries(test_movers PRIVATE iw2_game)
add_test(NAME movers COMMAND test_movers)

add_executable(test_builtinaudit tests/test_builtinaudit.cpp)
target_link_libraries(test_builtinaudit PRIVATE iw2_game)
add_executable(test_cursorhint tests/test_cursorhint.cpp)
target_link_libraries(test_cursorhint PRIVATE iw2_game)
add_test(NAME cursorhint COMMAND test_cursorhint)
add_executable(test_clientsound tests/test_clientsound.cpp)
target_link_libraries(test_clientsound PRIVATE iw2_audio)
add_test(NAME clientsound COMMAND test_clientsound)
add_executable(test_campaignflags tests/test_campaignflags.cpp)
target_link_libraries(test_campaignflags PRIVATE iw2_game)
add_executable(test_getorigin tests/test_getorigin.cpp)
target_link_libraries(test_getorigin PRIVATE iw2_game)
add_executable(test_scripterrors tests/test_scripterrors.cpp)
target_link_libraries(test_scripterrors PRIVATE iw2_game)
add_executable(test_hintstring tests/test_hintstring.cpp)
target_link_libraries(test_hintstring PRIVATE iw2_game)
add_test(NAME hintstring COMMAND test_hintstring)

add_executable(test_vehicletrigger tests/test_vehicletrigger.cpp)
target_link_libraries(test_vehicletrigger PRIVATE iw2_game)
add_executable(test_triggerfree tests/test_triggerfree.cpp)
target_link_libraries(test_triggerfree PRIVATE iw2_game)

add_executable(test_offhandturret tests/test_offhandturret.cpp)
target_link_libraries(test_offhandturret PRIVATE iw2_game)
add_executable(test_grenadecook tests/test_grenadecook.cpp)
target_link_libraries(test_grenadecook PRIVATE iw2_game)
add_executable(test_ammoshare tests/test_ammoshare.cpp)
target_link_libraries(test_ammoshare PRIVATE iw2_game)
add_executable(test_barrelclip tests/test_barrelclip.cpp)
target_link_libraries(test_barrelclip PRIVATE iw2_game)

add_executable(test_rhinecover tests/test_rhinecover.cpp)
target_link_libraries(test_rhinecover PRIVATE iw2_game)
add_executable(test_staticmodelhit tests/test_staticmodelhit.cpp)
target_link_libraries(test_staticmodelhit PRIVATE iw2_game)
add_executable(test_pipecollision tests/test_pipecollision.cpp)
target_link_libraries(test_pipecollision PRIVATE iw2_game)

add_executable(test_animtree tests/test_animtree.cpp)
target_link_libraries(test_animtree PRIVATE iw2_game)
add_test(NAME animtree COMMAND test_animtree)

add_executable(test_efx tests/test_efx.cpp)
target_link_libraries(test_efx PRIVATE iw2_game)
add_test(NAME efx COMMAND test_efx)

add_executable(test_hitboxes tests/test_hitboxes.cpp)
target_link_libraries(test_hitboxes PRIVATE iw2_game)
add_test(NAME hitboxes COMMAND test_hitboxes)

add_executable(test_pmove tests/test_pmove.cpp)
target_link_libraries(test_pmove PRIVATE iw2_game)
add_test(NAME pmove COMMAND test_pmove)
add_executable(test_mppmove tests/test_mppmove.cpp)
target_link_libraries(test_mppmove PRIVATE iw2_game)
add_test(NAME mppmove COMMAND test_mppmove)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mppmove PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_vehicledrive tests/test_vehicledrive.cpp)
target_link_libraries(test_vehicledrive PRIVATE iw2_game)
add_test(NAME vehicledrive COMMAND test_vehicledrive)

add_executable(test_texscroll tests/test_texscroll.cpp)
target_link_libraries(test_texscroll PRIVATE iw2_game)
add_test(NAME texscroll COMMAND test_texscroll)

add_executable(test_watersim tests/test_watersim.cpp)
target_link_libraries(test_watersim PRIVATE iw2_game)
add_test(NAME watersim COMMAND test_watersim)

add_executable(test_fog tests/test_fog.cpp)
target_link_libraries(test_fog PRIVATE iw2_game)
add_test(NAME fog COMMAND test_fog)
add_executable(test_fogargs tests/test_fogargs.cpp)
target_link_libraries(test_fogargs PRIVATE iw2_game)
add_test(NAME fogargs COMMAND test_fogargs)
add_executable(test_farplane tests/test_farplane.cpp)
target_link_libraries(test_farplane PRIVATE iw2_game)
add_test(NAME farplane COMMAND test_farplane)
add_executable(test_viewcull tests/test_viewcull.cpp)
target_link_libraries(test_viewcull PRIVATE iw2_game)
add_test(NAME viewcull COMMAND test_viewcull)
add_executable(test_viewspace tests/test_viewspace.cpp)
target_link_libraries(test_viewspace PRIVATE iw2_game)
add_test(NAME viewspace COMMAND test_viewspace)
add_executable(test_materialstate tests/test_materialstate.cpp)
target_link_libraries(test_materialstate PRIVATE iw2_game)
add_test(NAME materialstate COMMAND test_materialstate)

add_executable(test_frontend tests/test_frontend.cpp)
target_link_libraries(test_frontend PRIVATE iw2_ui)
add_test(NAME frontend COMMAND test_frontend)
add_executable(test_menu_focus tests/test_menu_focus.cpp)
target_link_libraries(test_menu_focus PRIVATE iw2_ui)
add_test(NAME menu_focus COMMAND test_menu_focus)
add_executable(test_profilelist tests/test_profilelist.cpp)
target_link_libraries(test_profilelist PRIVATE iw2_ui)
add_test(NAME profilelist COMMAND test_profilelist)
add_executable(test_menuexeckey tests/test_menuexeckey.cpp)
target_link_libraries(test_menuexeckey PRIVATE iw2_ui)
add_test(NAME menuexeckey COMMAND test_menuexeckey)

add_executable(test_menuhide tests/test_menuhide.cpp)
target_link_libraries(test_menuhide PRIVATE iw2_ui)
add_test(NAME menuhide COMMAND test_menuhide)
add_executable(test_mpui tests/test_mpui.cpp)
target_link_libraries(test_mpui PRIVATE iw2_ui)
add_test(NAME mpui COMMAND test_mpui)
add_executable(test_mpfocuspreview tests/test_mpfocuspreview.cpp)
target_link_libraries(test_mpfocuspreview PRIVATE iw2_ui)
add_executable(test_mpbrowser tests/test_mpbrowser.cpp)
target_link_libraries(test_mpbrowser PRIVATE iw2_net iw2_game)
add_test(NAME mpbrowser COMMAND test_mpbrowser)
add_executable(test_listenbind tests/test_listenbind.cpp)
target_link_libraries(test_listenbind PRIVATE iw2_net)
add_test(NAME listenbind COMMAND test_listenbind)
set_tests_properties(listenbind PROPERTIES TIMEOUT 30)
add_executable(test_mpmenunav tests/test_mpmenunav.cpp)
target_link_libraries(test_mpmenunav PRIVATE iw2_game)

add_executable(test_listbox tests/test_listbox.cpp)
target_link_libraries(test_listbox PRIVATE iw2_ui)
add_test(NAME listbox COMMAND test_listbox)

add_executable(test_profile tests/test_profile.cpp)
target_link_libraries(test_profile PRIVATE iw2_ui)
add_test(NAME profile COMMAND test_profile)
# The one key table of a profile for SP and MP (docs/spec/controls.md "Profile key tables"); with the game files also the shipped defaults.
add_executable(test_profilekeys tests/test_profilekeys.cpp)
target_link_libraries(test_profilekeys PRIVATE iw2_game)
add_test(NAME profilekeys COMMAND test_profilekeys ${CMAKE_SOURCE_DIR}/apps/iw2view/main.cpp)
if(IW2_TEST_MAIN)
  add_test(NAME profilekeys_world COMMAND test_profilekeys ${CMAKE_SOURCE_DIR}/apps/iw2view/main.cpp)
  set_tests_properties(profilekeys_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()
add_executable(test_hitmarker tests/test_hitmarker.cpp)
target_link_libraries(test_hitmarker PRIVATE iw2_game)
add_test(NAME hitmarker COMMAND test_hitmarker)
add_executable(test_iw2options tests/test_iw2options.cpp)
target_link_libraries(test_iw2options PRIVATE iw2_ui iw2_assets)
add_test(NAME iw2options COMMAND test_iw2options)
add_executable(test_cheatkeys tests/test_cheatkeys.cpp)
target_link_libraries(test_cheatkeys PRIVATE iw2_ui iw2_game iw2_assets)
add_test(NAME cheatkeys COMMAND test_cheatkeys)

add_executable(test_savegame tests/test_savegame.cpp)
target_link_libraries(test_savegame PRIVATE iw2_ui)
add_test(NAME savegame COMMAND test_savegame)

add_executable(test_prone tests/test_prone.cpp)
target_link_libraries(test_prone PRIVATE iw2_game)
add_test(NAME prone COMMAND test_prone)
add_executable(test_orientrelease tests/test_orientrelease.cpp)
target_link_libraries(test_orientrelease PRIVATE iw2_game)
add_test(NAME orientrelease COMMAND test_orientrelease)
add_executable(test_yield tests/test_yield.cpp)
target_link_libraries(test_yield PRIVATE iw2_game)
add_test(NAME yield COMMAND test_yield)
add_executable(test_actortrail tests/test_actortrail.cpp)
target_link_libraries(test_actortrail PRIVATE iw2_game)
add_test(NAME actortrail COMMAND test_actortrail)
add_executable(test_deathseen tests/test_deathseen.cpp)
target_include_directories(test_deathseen PRIVATE tools/iw2game)
target_link_libraries(test_deathseen PRIVATE iw2_game)
add_test(NAME deathseen COMMAND test_deathseen)
add_executable(test_compassspring tests/test_compassspring.cpp)
target_link_libraries(test_compassspring PRIVATE iw2_game)
add_test(NAME compassspring COMMAND test_compassspring)

add_executable(test_hudlayers tests/test_hudlayers.cpp)
target_link_libraries(test_hudlayers PRIVATE iw2_game)
add_test(NAME hudlayers COMMAND test_hudlayers)
add_executable(test_compassping tests/test_compassping.cpp)
target_link_libraries(test_compassping PRIVATE iw2_game)
add_test(NAME compassping COMMAND test_compassping)
add_executable(test_screenfade tests/test_screenfade.cpp)
target_link_libraries(test_screenfade PRIVATE iw2_game)
add_test(NAME screenfade COMMAND test_screenfade)

add_executable(test_roq tests/test_roq.cpp)
target_link_libraries(test_roq PRIVATE iw2_video)
add_test(NAME roq COMMAND test_roq)

add_executable(test_triggerdamage tests/test_triggerdamage.cpp)
target_link_libraries(test_triggerdamage PRIVATE iw2_game)
add_test(NAME triggerdamage COMMAND test_triggerdamage)
add_executable(test_grenadehint tests/test_grenadehint.cpp)
target_link_libraries(test_grenadehint PRIVATE iw2_game)
add_test(NAME grenadehint COMMAND test_grenadehint)
add_executable(test_grenadeangles tests/test_grenadeangles.cpp)
target_link_libraries(test_grenadeangles PRIVATE iw2_game)
add_test(NAME grenadeangles COMMAND test_grenadeangles)

add_executable(test_turretpose tests/test_turretpose.cpp)
target_link_libraries(test_turretpose PRIVATE iw2_game)
add_executable(test_gunnertarget tests/test_gunnertarget.cpp)
target_link_libraries(test_gunnertarget PRIVATE iw2_game)

add_executable(test_mgshake tests/test_mgshake.cpp)
target_link_libraries(test_mgshake PRIVATE iw2_game)
add_executable(test_turretads tests/test_turretads.cpp)
target_link_libraries(test_turretads PRIVATE iw2_game)
add_executable(test_vehicleads tests/test_vehicleads.cpp)
target_link_libraries(test_vehicleads PRIVATE iw2_game)
add_executable(test_dronehit tests/test_dronehit.cpp)
target_link_libraries(test_dronehit PRIVATE iw2_game)

add_executable(test_damageorder tests/test_damageorder.cpp)
target_link_libraries(test_damageorder PRIVATE iw2_game)
add_executable(test_corpses tests/test_corpses.cpp)
target_link_libraries(test_corpses PRIVATE iw2_game)
add_executable(test_itemdrop tests/test_itemdrop.cpp)
target_link_libraries(test_itemdrop PRIVATE iw2_game)
add_executable(test_bulletpass tests/test_bulletpass.cpp)
target_link_libraries(test_bulletpass PRIVATE iw2_game)
add_executable(test_spturretbullet tests/test_spturretbullet.cpp)
target_link_libraries(test_spturretbullet PRIVATE iw2_game)
add_executable(test_soundpool tests/test_soundpool.cpp)
target_link_libraries(test_soundpool PRIVATE iw2_game iw2_audio)
add_executable(test_savestate tests/test_savestate.cpp)
target_link_libraries(test_savestate PRIVATE iw2_game)
add_test(NAME savestate COMMAND test_savestate)
add_executable(test_downtownreload tests/test_downtownreload.cpp)
target_link_libraries(test_downtownreload PRIVATE iw2_game)
add_executable(test_missionfail tests/test_missionfail.cpp)
target_link_libraries(test_missionfail PRIVATE iw2_game)
add_test(NAME missionfail COMMAND test_missionfail)
# The World round trip needs the game files; registered only when they are found.
# Override with -DIW2_TEST_MAIN=<dir> or the IW2_MAIN environment variable.
set(IW2_TEST_MAIN "" CACHE PATH "Directory holding the game's iw_*.iwd files, for the *_world tests")
if(NOT IW2_TEST_MAIN AND DEFINED ENV{IW2_MAIN})
  set(IW2_TEST_MAIN "$ENV{IW2_MAIN}")
endif()
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME savestate_world COMMAND test_savestate)
  set_tests_properties(savestate_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME missionfail_world COMMAND test_missionfail)
  set_tests_properties(missionfail_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME downtownreload_world COMMAND test_downtownreload)
  set_tests_properties(downtownreload_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME scripterrors_world COMMAND test_scripterrors)
  set_tests_properties(scripterrors_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME campaignflags_world COMMAND test_campaignflags)
  set_tests_properties(campaignflags_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME getorigin_world COMMAND test_getorigin)
  set_tests_properties(getorigin_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME builtinaudit_world COMMAND test_builtinaudit)
  set_tests_properties(builtinaudit_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME vehicletrigger_world COMMAND test_vehicletrigger)
  set_tests_properties(vehicletrigger_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME triggerfree_world COMMAND test_triggerfree)
  set_tests_properties(triggerfree_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME staticmodelhit_world COMMAND test_staticmodelhit)
  set_tests_properties(staticmodelhit_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME pipecollision_world COMMAND test_pipecollision)
  set_tests_properties(pipecollision_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME rhinecover_world COMMAND test_rhinecover)
  set_tests_properties(rhinecover_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME hitmarker_world COMMAND test_hitmarker)
  set_tests_properties(hitmarker_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME cheatkeys_world COMMAND test_cheatkeys)
  set_tests_properties(cheatkeys_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME iw2options_world COMMAND test_iw2options)
  set_tests_properties(iw2options_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME grenadecook_data COMMAND test_grenadecook)
  set_tests_properties(grenadecook_data PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME ammoshare_data COMMAND test_ammoshare)
  set_tests_properties(ammoshare_data PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME barrelclip_world COMMAND test_barrelclip)
  set_tests_properties(barrelclip_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME itempickup_world COMMAND test_itempickup)
  set_tests_properties(itempickup_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME turretpose_world COMMAND test_turretpose)
  add_test(NAME gunnertarget_world COMMAND test_gunnertarget)
  add_test(NAME firemonitor_world COMMAND test_firemonitor_world)
  set_tests_properties(firemonitor_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  set_tests_properties(turretpose_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME mgshake_world COMMAND test_mgshake)
  set_tests_properties(mgshake_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME turretads_world COMMAND test_turretads)
  set_tests_properties(turretads_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME vehicleads_world COMMAND test_vehicleads)
  add_test(NAME dronehit_world COMMAND test_dronehit)
  set_tests_properties(dronehit_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  set_tests_properties(vehicleads_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME bulletpass_world COMMAND test_bulletpass)
  add_test(NAME spturretbullet_world COMMAND test_spturretbullet)
  set_tests_properties(spturretbullet_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  set_tests_properties(bulletpass_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME soundpool_world COMMAND test_soundpool)
  set_tests_properties(soundpool_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME damageorder_world COMMAND test_damageorder)
  set_tests_properties(damageorder_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME corpses_world COMMAND test_corpses)
  set_tests_properties(corpses_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME itemdrop_world COMMAND test_itemdrop)
  set_tests_properties(itemdrop_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  # Mission-script smoke: a few maps headless (plain + one --autoplay run + a determinism check).
  add_test(NAME script_smoke COMMAND ${CMAKE_SOURCE_DIR}/tools/script_sweep.sh --smoke --bin $<TARGET_FILE:iw2game>)
  set_tests_properties(script_smoke PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_usehints tests/test_usehints.cpp)
target_link_libraries(test_usehints PRIVATE iw2_game)
add_test(NAME usehints COMMAND test_usehints)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(usehints PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_nodedrop tests/test_nodedrop.cpp)
target_link_libraries(test_nodedrop PRIVATE iw2_game)
add_test(NAME nodedrop COMMAND test_nodedrop)
add_executable(test_aisight tests/test_aisight.cpp)
target_link_libraries(test_aisight PRIVATE iw2_game)
add_test(NAME aisight COMMAND test_aisight)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(nodedrop PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_spawndrop tests/test_spawndrop.cpp)
target_link_libraries(test_spawndrop PRIVATE iw2_game)
add_test(NAME spawndrop COMMAND test_spawndrop)
add_executable(test_softlock tests/test_softlock.cpp)
target_link_libraries(test_softlock PRIVATE iw2_game)
add_test(NAME softlock COMMAND test_softlock)
add_executable(test_firemonitor tests/test_firemonitor.cpp)
add_executable(test_firemonitor_world tests/test_firemonitor_world.cpp)
target_link_libraries(test_firemonitor_world PRIVATE iw2_game)
target_link_libraries(test_firemonitor PRIVATE iw2_game)
add_test(NAME firemonitor COMMAND test_firemonitor)
add_executable(test_inventorydiff tests/test_inventorydiff.cpp)
target_link_libraries(test_inventorydiff PRIVATE iw2_game)
add_test(NAME inventorydiff COMMAND test_inventorydiff)
add_executable(test_spawnchecks tests/test_spawnchecks.cpp)
target_link_libraries(test_spawnchecks PRIVATE iw2_game)
add_test(NAME spawnchecks COMMAND test_spawnchecks)
add_executable(test_frameorder tests/test_frameorder.cpp)
target_link_libraries(test_frameorder PRIVATE iw2_game)
add_test(NAME frameorder COMMAND test_frameorder)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(spawndrop PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_persistcarry tests/test_persistcarry.cpp)
target_link_libraries(test_persistcarry PRIVATE iw2_game)
add_test(NAME persistcarry COMMAND test_persistcarry)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(persistcarry PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_playerrocket tests/test_playerrocket.cpp)
target_link_libraries(test_playerrocket PRIVATE iw2_game)
add_test(NAME playerrocket COMMAND test_playerrocket)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(playerrocket PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_framescale tests/test_framescale.cpp)
target_link_libraries(test_framescale PRIVATE iw2_game)
add_test(NAME framescale COMMAND test_framescale)

add_executable(test_hudfade tests/test_hudfade.cpp)
target_link_libraries(test_hudfade PRIVATE iw2_game)
add_test(NAME hudfade COMMAND test_hudfade)
add_executable(test_thirdpersonview tests/test_thirdpersonview.cpp)
target_link_libraries(test_thirdpersonview PRIVATE iw2_game)
add_test(NAME thirdpersonview COMMAND test_thirdpersonview)
add_executable(test_vehiclehit tests/test_vehiclehit.cpp)
target_link_libraries(test_vehiclehit PRIVATE iw2_game)
add_test(NAME vehiclehit COMMAND test_vehiclehit)
add_executable(test_grenadeindicator tests/test_grenadeindicator.cpp)
target_link_libraries(test_grenadeindicator PRIVATE iw2_game)
add_test(NAME grenadeindicator COMMAND test_grenadeindicator)
add_executable(test_spritesortkey tests/test_spritesortkey.cpp)
target_link_libraries(test_spritesortkey PRIVATE iw2_game)
add_test(NAME spritesortkey COMMAND test_spritesortkey)
add_executable(test_viewprojection tests/test_viewprojection.cpp)
target_link_libraries(test_viewprojection PRIVATE iw2_game)
add_test(NAME viewprojection COMMAND test_viewprojection)

add_executable(test_aieventmask tests/test_aieventmask.cpp)
target_link_libraries(test_aieventmask PRIVATE iw2_game)
add_test(NAME aieventmask COMMAND test_aieventmask)
add_executable(test_exposedai tests/test_exposedai.cpp)
target_link_libraries(test_exposedai PRIVATE iw2_game)
add_test(NAME exposedai COMMAND test_exposedai)
add_executable(test_actorpredict tests/test_actorpredict.cpp)
target_link_libraries(test_actorpredict PRIVATE iw2_game)
add_test(NAME actorpredict COMMAND test_actorpredict)
add_executable(test_chaintouch tests/test_chaintouch.cpp)
target_link_libraries(test_chaintouch PRIVATE iw2_game)
add_test(NAME chaintouch COMMAND test_chaintouch)
add_executable(test_deadtouch tests/test_deadtouch.cpp)
target_link_libraries(test_deadtouch PRIVATE iw2_game)
add_test(NAME deadtouch COMMAND test_deadtouch)
add_executable(test_playerstart tests/test_playerstart.cpp)
target_link_libraries(test_playerstart PRIVATE iw2_game)
add_test(NAME playerstart COMMAND test_playerstart)
add_executable(test_balcony tests/test_balcony.cpp)
target_link_libraries(test_balcony PRIVATE iw2_game)
add_test(NAME balcony COMMAND test_balcony)

add_executable(test_melee_start tests/test_melee_start.cpp)
target_link_libraries(test_melee_start PRIVATE iw2_assets iw2_fs)
add_test(NAME melee_start COMMAND test_melee_start)

add_executable(test_flakanim tests/test_flakanim.cpp)
target_link_libraries(test_flakanim PRIVATE iw2_game)
add_test(NAME flakanim COMMAND test_flakanim)
add_executable(test_vehicleturretsound tests/test_vehicleturretsound.cpp)
target_link_libraries(test_vehicleturretsound PRIVATE iw2_game)
add_test(NAME vehicleturretsound COMMAND test_vehicleturretsound)
add_executable(test_vehicleseat tests/test_vehicleseat.cpp)
target_link_libraries(test_vehicleseat PRIVATE iw2_game)
add_test(NAME vehicleseat COMMAND test_vehicleseat)
add_executable(test_decoyride tests/test_decoyride.cpp)
target_link_libraries(test_decoyride PRIVATE iw2_game)
add_test(NAME decoyride COMMAND test_decoyride)
add_executable(test_artillerytarget tests/test_artillerytarget.cpp)
target_link_libraries(test_artillerytarget PRIVATE iw2_game)
add_test(NAME artillerytarget COMMAND test_artillerytarget)

add_executable(test_swapdrop tests/test_swapdrop.cpp)
target_link_libraries(test_swapdrop PRIVATE iw2_game)
add_test(NAME swapdrop COMMAND test_swapdrop)

add_executable(test_deathplant tests/test_deathplant.cpp)
target_link_libraries(test_deathplant PRIVATE iw2_game)
add_test(NAME deathplant COMMAND test_deathplant)
add_executable(test_deadtank tests/test_deadtank.cpp)
target_link_libraries(test_deadtank PRIVATE iw2_game)
add_test(NAME deadtank COMMAND test_deadtank)
add_executable(test_deadtargetname tests/test_deadtargetname.cpp)
target_link_libraries(test_deadtargetname PRIVATE iw2_game)
add_test(NAME deadtargetname COMMAND test_deadtargetname)
add_executable(test_matmataflak tests/test_matmataflak.cpp)
target_link_libraries(test_matmataflak PRIVATE iw2_game)
add_test(NAME matmataflak COMMAND test_matmataflak)
add_executable(test_armoredcar tests/test_armoredcar.cpp)
target_link_libraries(test_armoredcar PRIVATE iw2_game)
add_test(NAME armoredcar COMMAND test_armoredcar)
add_executable(test_playertank tests/test_playertank.cpp)
target_link_libraries(test_playertank PRIVATE iw2_game)
add_test(NAME playertank COMMAND test_playertank)

if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # The shipped ui_mp menus: createserver -> gametype -> map -> extension panel -> StartServer (docs/spec/mp-ui.md).
  add_test(NAME menuhide_world COMMAND test_menuhide)
  set_tests_properties(menuhide_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME mpui_world COMMAND test_mpui)
  set_tests_properties(mpui_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  # ui_mp main menu: ESC and every sub-menu's back/ESC path leave only the original's menus open.
  add_test(NAME mpfocuspreview COMMAND test_mpfocuspreview)
  set_tests_properties(mpfocuspreview PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
  add_test(NAME mpmenunav COMMAND test_mpmenunav)
  set_tests_properties(mpmenunav PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mplisten tests/test_mplisten.cpp)
target_link_libraries(test_mplisten PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mplisten_e2e COMMAND test_mplisten)
  set_tests_properties(mplisten_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
add_executable(test_mpjoinmenus tests/test_mpjoinmenus.cpp)
target_link_libraries(test_mpjoinmenus PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpjoinmenus_e2e COMMAND test_mpjoinmenus)
  set_tests_properties(mpjoinmenus_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 600)
endif()

# The dedicated start of the create-server screen: settings, console window model, the shared server loop.
add_executable(test_dedicatedstart tests/test_dedicatedstart.cpp)
target_link_libraries(test_dedicatedstart PRIVATE iw2_dedicated)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME dedicatedstart COMMAND test_dedicatedstart)
  set_tests_properties(dedicatedstart PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache"
    PASS_REGULAR_EXPRESSION "dedicatedstart: ok" TIMEOUT 180)
else()
  add_test(NAME dedicatedstart COMMAND test_dedicatedstart)
endif()
# The options of iw2server: weapon lists, the command line, limits (tests/test_serveroptions.cpp).
add_executable(test_serveroptions tests/test_serveroptions.cpp)
target_link_libraries(test_serveroptions PRIVATE iw2_dedicated)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME serveroptions COMMAND test_serveroptions)
  set_tests_properties(serveroptions PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache"
    PASS_REGULAR_EXPRESSION "serveroptions: ok" TIMEOUT 600)
else()
  add_test(NAME serveroptions COMMAND test_serveroptions)
endif()
add_executable(test_mphands tests/test_mphands.cpp)
target_link_libraries(test_mphands PRIVATE iw2_netgame)
add_test(NAME mphands COMMAND test_mphands)
if(IW2_TEST_MAIN)
  set_tests_properties(mphands PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
# Another player on a ladder or mantling as the others see him (tests/test_mpladderanim.cpp, docs/spec/mp-players.md).
add_executable(test_mpladderanim tests/test_mpladderanim.cpp)
target_link_libraries(test_mpladderanim PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpladderanim COMMAND test_mpladderanim)
  set_tests_properties(mpladderanim PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
else()
  add_test(NAME mpladderanim COMMAND test_mpladderanim)
endif()
add_executable(test_mpperclient tests/test_mpperclient.cpp)
target_link_libraries(test_mpperclient PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpperclient_world COMMAND test_mpperclient)
  set_tests_properties(mpperclient_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
else()
  add_test(NAME mpperclient_world COMMAND test_mpperclient)
endif()
add_executable(test_mpnetdmview tests/test_mpnetdmview.cpp)
target_link_libraries(test_mpnetdmview PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpnetdmview_e2e COMMAND test_mpnetdmview)
  set_tests_properties(mpnetdmview_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
add_executable(test_predictedammo tests/test_predictedammo.cpp)
target_link_libraries(test_predictedammo PRIVATE iw2_netgame)
add_test(NAME predictedammo COMMAND test_predictedammo)
add_executable(test_weaponpredict tests/test_weaponpredict.cpp)
target_link_libraries(test_weaponpredict PRIVATE iw2_netgame)
add_test(NAME weaponpredict COMMAND test_weaponpredict)
add_executable(test_healthoverlay tests/test_healthoverlay.cpp)
target_link_libraries(test_healthoverlay PRIVATE iw2_game)
add_test(NAME healthoverlay COMMAND test_healthoverlay)
if(IW2_TEST_MAIN)
  set_tests_properties(healthoverlay PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()
add_executable(test_weaponselect tests/test_weaponselect.cpp)
target_link_libraries(test_weaponselect PRIVATE iw2_netgame)
add_test(NAME weaponselect COMMAND test_weaponselect)
add_executable(test_mpturretview tests/test_mpturretview.cpp)
target_link_libraries(test_mpturretview PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpturretview_e2e COMMAND test_mpturretview)
  set_tests_properties(mpturretview_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()

add_executable(test_mpkillcam_grenade tests/test_mpkillcam_grenade.cpp)
target_link_libraries(test_mpkillcam_grenade PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpkillcam_grenade_e2e COMMAND test_mpkillcam_grenade)
  set_tests_properties(mpkillcam_grenade_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
# The recorded view state of the killcam (tests/test_mpkillcam_record.cpp): a bot's throw through the offhand states, and the
# network killcam's view equal to the local one from the same archived frame.
add_executable(test_mpkillcam_record tests/test_mpkillcam_record.cpp)
target_link_libraries(test_mpkillcam_record PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpkillcam_record_e2e COMMAND test_mpkillcam_record)
  set_tests_properties(mpkillcam_record_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
add_executable(test_mpnetgrenade tests/test_mpnetgrenade.cpp)
target_link_libraries(test_mpnetgrenade PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpnetgrenade_e2e COMMAND test_mpnetgrenade)
  set_tests_properties(mpnetgrenade_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 300)
endif()
add_executable(test_mpdedicatedclient tests/test_mpdedicatedclient.cpp)
target_link_libraries(test_mpdedicatedclient PRIVATE iw2_netgame)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mpdedicatedclient_e2e COMMAND test_mpdedicatedclient)
  set_tests_properties(mpdedicatedclient_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}" TIMEOUT 300)
endif()
add_executable(test_mpnetcombat tests/test_mpnetcombat.cpp)
target_link_libraries(test_mpnetcombat PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpnetcombat_e2e COMMAND test_mpnetcombat)
  set_tests_properties(mpnetcombat_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 600)
endif()

# Network soak with invariants (tests/test_mpnetsoak.cpp): iw2server as a child process with bots and a short map
# rotation, two scripted network clients; violations listed in tests/mpnetsoak_known.txt are reported but do not fail.
add_executable(test_mpnetsoak tests/test_mpnetsoak.cpp)
target_link_libraries(test_mpnetsoak PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpnetsoak COMMAND test_mpnetsoak)
  set_tests_properties(mpnetsoak PROPERTIES ENVIRONMENT
    "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache;IW2_SOAK_SERVER=$<TARGET_FILE:iw2server>;IW2_SOAK_KNOWN=${CMAKE_SOURCE_DIR}/tests/mpnetsoak_known.txt;IW2_SOAK_REPORT=${CMAKE_BINARY_DIR}/mpnetsoak_report.txt"
    TIMEOUT 1500)
endif()

add_executable(test_mpantilag tests/test_mpantilag.cpp)
target_link_libraries(test_mpantilag PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpantilag_e2e COMMAND test_mpantilag)
  set_tests_properties(mpantilag_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 600)
endif()

add_executable(test_mpremote tests/test_mpremote.cpp)
target_link_libraries(test_mpremote PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mpremote_world COMMAND test_mpremote)
  set_tests_properties(mpremote_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()
add_executable(test_mpdm tests/test_mpdm.cpp)
target_link_libraries(test_mpdm PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mpdm_world COMMAND test_mpdm)
  set_tests_properties(mpdm_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # Artillery through the binoculars on decoytown (docs/spec/grenades.md "Radius damage"): the player holds +binoculars at the
  # radio man, aims at a halted panzer and presses use; the maps/_artillery.gsc barrage must destroy it with its first shell
  # (the shells' radius damage reaches the tank's origin from below the terrain, traced from the target to the explosion).
  add_test(NAME artillery_binoculars COMMAND iw2game ${IW2_TEST_MAIN} decoytown 50 --god --spawn-all --answer-menus ok
           --level-call 20 maps/_artillery.gsc SetRadioMan=@macgregor --level-call 20.1 maps/_artillery.gsc EnableUse --binoc-strike 41 axis)
  set_tests_properties(artillery_binoculars PROPERTIES ENVIRONMENT "IW2_SCRIPT_SEED=1"
    PASS_REGULAR_EXPRESSION "binoc-strike: target #[0-9]+ destroyed by shell 1,.*runtime errors 0" TIMEOUT 240)
endif()
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # iw2game multiplayer mode: two local clients, a kill and a respawn on mp_toujane DM without script errors.
  add_test(NAME mpdm_iw2game COMMAND iw2game ${IW2_TEST_MAIN} mp/mp_toujane dm 6 --mp-clients 2 --mp-kill 2:1:0 --mp-use 5:1)
  set_tests_properties(mpdm_iw2game PROPERTIES PASS_REGULAR_EXPRESSION "client 0 player: playing team [a-z]+ score 1 deaths 0.*client 1 client1: playing team [a-z]+ score 0 deaths 1.*runtime errors 0")
endif()

add_library(iw2_net
  src/net/msg.cpp
  src/net/huffman.cpp
  src/net/huffman_table.cpp
  src/net/netchan.cpp
  src/net/oob.cpp
  src/net/delta.cpp
  src/net/scramble.cpp
  src/net/client.cpp
  src/net/wiredump.cpp
  src/net/clientclock.cpp
  src/net/gameclient.cpp
  src/net/netfields.cpp
  src/net/playerstate.cpp
  src/net/server.cpp
  src/net/svconsole.cpp
  src/net/udp.cpp
  src/net/lanbrowser.cpp
  src/net/resolve.cpp)
target_include_directories(iw2_net PUBLIC src)
target_link_libraries(iw2_net PUBLIC iw2_log)
# Network game layer: shared movement for server and client prediction, snapshot interpolation (docs/spec/net.md 10).
add_library(iw2_netgame src/netgame/netmove.cpp src/netgame/netcombat.cpp src/netgame/worldgame.cpp src/netgame/remoteview.cpp)
target_link_libraries(iw2_netgame PUBLIC iw2_game iw2_net)
# The dedicated server loop (tools/iw2server and the dedicated start of iw2view) and its console window's model.
add_library(iw2_dedicated src/netgame/dedicated.cpp src/netgame/consoleview.cpp src/netgame/serverargs.cpp)
target_link_libraries(iw2_dedicated PUBLIC iw2_netgame iw2_audio Threads::Threads)

# The session log's capture with the dedicated console window's StdoutTee inside it (docs/logging.md "Capture").
add_executable(test_logtee tests/test_logtee.cpp)
target_link_libraries(test_logtee PRIVATE iw2_dedicated)
add_test(NAME logtee COMMAND test_logtee)
add_executable(iw2netinfo tools/iw2netinfo/main.cpp)
target_link_libraries(iw2netinfo PRIVATE iw2_net)
add_executable(iw2netclient tools/iw2netclient/main.cpp)
target_link_libraries(iw2netclient PRIVATE iw2_net)

add_executable(test_net tests/test_net.cpp)
target_link_libraries(test_net PRIVATE iw2_net)
add_test(NAME net COMMAND test_net)
add_executable(test_svconsole tests/test_svconsole.cpp)
target_link_libraries(test_svconsole PRIVATE iw2_net)
add_test(NAME svconsole COMMAND test_svconsole)
add_executable(test_net_udp tests/test_net_udp.cpp)
target_link_libraries(test_net_udp PRIVATE iw2_net)
add_test(NAME net_udp COMMAND test_net_udp)

add_executable(test_mpchat tests/test_mpchat.cpp)
target_link_libraries(test_mpchat PRIVATE iw2_netgame)
add_test(NAME mpchat COMMAND test_mpchat)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mpchat PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpmenus tests/test_mpmenus.cpp)
target_link_libraries(test_mpmenus PRIVATE iw2_game)
add_test(NAME mpmenus COMMAND test_mpmenus)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mpmenus PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpgametypes tests/test_mpgametypes.cpp)
target_link_libraries(test_mpgametypes PRIVATE iw2_game)
if(IW2_TEST_MAIN)
  add_test(NAME mpgametypes_world COMMAND test_mpgametypes)
  set_tests_properties(mpgametypes_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
  add_test(NAME mpgametypes_matrix COMMAND test_mpgametypes matrix)
  set_tests_properties(mpgametypes_matrix PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 1500)
endif()

add_executable(test_mphud tests/test_mphud.cpp)
target_link_libraries(test_mphud PRIVATE iw2_game)
if(IW2_TEST_MAIN)
  add_test(NAME mphud_world COMMAND test_mphud)
  set_tests_properties(mphud_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
else()
  add_test(NAME mphud_world COMMAND test_mphud)
endif()

add_executable(test_mpbots tests/test_mpbots.cpp)
target_link_libraries(test_mpbots PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # Bots on mp_toujane: generated navigation graph (cached in the build tree), walking to a goal, DM fights and score.
  add_test(NAME mpbots_world COMMAND test_mpbots)
  set_tests_properties(mpbots_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
  # iw2game --mp-bots: two bots and the local player on mp_toujane DM for 60 s; somebody scores, no script errors.
  add_test(NAME mpbots_iw2game COMMAND iw2game ${IW2_TEST_MAIN} mp/mp_toujane dm 60 --mp-bots 2 --mp-bot-skill 3)
  set_tests_properties(mpbots_iw2game PROPERTIES ENVIRONMENT "IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache"
    PASS_REGULAR_EXPRESSION "client 1 bot0: [a-z]+ team [a-z]+ score [0-9]+.*bot 1 skill veteran.*client 2 bot1:.*log K;.*runtime errors 0")
  # iw2server end to end (docs/spec/dedicated.md): bot, client, rcon, getstatus, rotation by rcon, stdin console.
  add_test(NAME iw2server_dedicated COMMAND sh ${CMAKE_SOURCE_DIR}/tests/iw2server_dedicated.sh $<TARGET_FILE:iw2server>
    $<TARGET_FILE:iw2netclient> $<TARGET_FILE:iw2netinfo> ${IW2_TEST_MAIN})
  set_tests_properties(iw2server_dedicated PROPERTIES ENVIRONMENT "IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache"
    PASS_REGULAR_EXPRESSION "iw2server dedicated: ok" TIMEOUT 240)
  # The server's session log (docs/logging.md); the log folder is a temp folder, never the real one.
  add_test(NAME iw2server_log COMMAND sh ${CMAKE_SOURCE_DIR}/tests/iw2server_log.sh $<TARGET_FILE:iw2server>
    $<TARGET_FILE:iw2netclient> $<TARGET_FILE:iw2netinfo> ${IW2_TEST_MAIN})
  set_tests_properties(iw2server_log PROPERTIES ENVIRONMENT "IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache"
    PASS_REGULAR_EXPRESSION "iw2server log: ok" TIMEOUT 240)
else()
  add_test(NAME mpbots_world COMMAND test_mpbots)
endif()

add_executable(test_netmantle tests/test_netmantle.cpp)
target_link_libraries(test_netmantle PRIVATE iw2_netgame iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # A network player climbs mantle links of mp_toujane through the shared server / prediction move.
  add_test(NAME netmantle_world COMMAND test_netmantle)
  set_tests_properties(netmantle_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
else()
  add_test(NAME netmantle_world COMMAND test_netmantle)
endif()

add_executable(test_mpbotcombat tests/test_mpbotcombat.cpp)
target_link_libraries(test_mpbotcombat PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # Bot combat (docs/spec/mp-bots.md "Combat behaviour"): fights per skill, pistol, hearing; 15 bots' cost on the
  # largest map; the stuck watch on every map (IW2_BOT_SOAK_SECONDS, default 60).
  set(_botenv "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
  add_test(NAME mpbotcombat COMMAND test_mpbotcombat)
  set_tests_properties(mpbotcombat PROPERTIES ENVIRONMENT "${_botenv}" TIMEOUT 900)
  add_test(NAME mpbotcombat_weapons COMMAND test_mpbotcombat weapons)
  set_tests_properties(mpbotcombat_weapons PROPERTIES ENVIRONMENT "${_botenv}" TIMEOUT 900)
  add_test(NAME mpbotcombat_perf COMMAND test_mpbotcombat perf mp_decoy)
  set_tests_properties(mpbotcombat_perf PROPERTIES ENVIRONMENT "${_botenv}" TIMEOUT 900 RUN_SERIAL TRUE)
  # Bots that want to walk do not stay on the spot (docs/spec/mp-bots.md "Stuck watch"): 8 bots for 600 game seconds.
  add_test(NAME mpbotstall_carentan COMMAND test_mpbotcombat stall-check mp_carentan)
  set_tests_properties(mpbotstall_carentan PROPERTIES ENVIRONMENT "${_botenv};IW2_BOT_SOAK_SECONDS=600" TIMEOUT 900)
  foreach(_map breakout brecourt burgundy carentan dawnville decoy downtown farmhouse harbor leningrad matmata railyard rhine toujane trainstation)
    add_test(NAME mpbotsoak_${_map} COMMAND test_mpbotcombat soak mp_${_map})
    set_tests_properties(mpbotsoak_${_map} PROPERTIES ENVIRONMENT "${_botenv}" TIMEOUT 1800)
  endforeach()
else()
  add_test(NAME mpbotcombat COMMAND test_mpbotcombat)
endif()

add_executable(test_mpdrop tests/test_mpdrop.cpp)
target_link_libraries(test_mpdrop PRIVATE iw2_game iw2_ui)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # MP dropped weapons (dropItem, g_maxDroppedWeapons, pickup by other clients) and the MP load screen text.
  add_test(NAME mpdrop_world COMMAND test_mpdrop)
  set_tests_properties(mpdrop_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mphitscale tests/test_mphitscale.cpp)
target_link_libraries(test_mphitscale PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mphitscale_world COMMAND test_mphitscale)
  set_tests_properties(mphitscale_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpradiusbrush tests/test_mpradiusbrush.cpp)
target_link_libraries(test_mpradiusbrush PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mpradiusbrush_world COMMAND test_mpradiusbrush)
  set_tests_properties(mpradiusbrush_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpturretdamage tests/test_mpturretdamage.cpp)
target_link_libraries(test_mpturretdamage PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # Multiplayer mounted MG kills with every weapon class configuration.
  add_test(NAME mpturretdamage_world COMMAND test_mpturretdamage)
  set_tests_properties(mpturretdamage_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpweaponcredit tests/test_mpweaponcredit.cpp)
target_link_libraries(test_mpweaponcredit PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # The weapon of MP shots and damage: bot shotgun pellets through the shared fire path, grenade kills credited to the grenade.
  add_test(NAME mpweaponcredit_world COMMAND test_mpweaponcredit)
  set_tests_properties(mpweaponcredit_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_navmaps tests/test_navmaps.cpp)
target_link_libraries(test_navmaps PRIVATE iw2_game)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  # Navigation graphs of the 15 MP maps: every spawn point on the graph (one report line per map).
  add_test(NAME navmaps COMMAND test_navmaps)
  set_tests_properties(navmaps PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
endif()

add_executable(test_net_specdecoder tests/test_net_specdecoder.cpp tests/specdecoder.cpp)
target_link_libraries(test_net_specdecoder PRIVATE iw2_net)
add_test(NAME net_specdecoder COMMAND test_net_specdecoder $<TARGET_FILE:iw2server> "${IW2_TEST_MAIN}")
set_tests_properties(net_specdecoder PROPERTIES TIMEOUT 120 ENVIRONMENT "IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
add_executable(test_net_wirediff tests/test_net_wirediff.cpp)
target_link_libraries(test_net_wirediff PRIVATE iw2_net)
add_test(NAME net_wirediff COMMAND test_net_wirediff $<TARGET_FILE:iw2server> ${IW2_TEST_MAIN})
set_tests_properties(net_wirediff PROPERTIES TIMEOUT 120)
add_executable(test_net_interop tests/test_net_interop.cpp)
target_link_libraries(test_net_interop PRIVATE iw2_netgame)
add_test(NAME net_interop COMMAND test_net_interop)
add_executable(test_net_server tests/test_net_server.cpp)
target_link_libraries(test_net_server PRIVATE iw2_net)
add_test(NAME net_server COMMAND test_net_server)
add_executable(test_mppredict tests/test_mppredict.cpp)
target_link_libraries(test_mppredict PRIVATE iw2_dedicated iw2_netgame)
add_test(NAME mppredict COMMAND test_mppredict)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mppredict PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_net_gameclient tests/test_net_gameclient.cpp)
target_link_libraries(test_net_gameclient PRIVATE iw2_netgame)
add_test(NAME net_gameclient COMMAND test_net_gameclient)
add_executable(test_net_disconnect tests/test_net_disconnect.cpp)
target_link_libraries(test_net_disconnect PRIVATE iw2_netgame)
add_test(NAME net_disconnect COMMAND test_net_disconnect)

add_executable(test_mpplayers tests/test_mpplayers.cpp)
target_link_libraries(test_mpplayers PRIVATE iw2_game)
add_test(NAME mpplayers_grammar COMMAND test_mpplayers)
if(IW2_TEST_MAIN)
  add_test(NAME mpplayers_world COMMAND test_mpplayers)
  set_tests_properties(mpplayers_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
endif()

add_executable(test_mpflow tests/test_mpflow.cpp)
target_link_libraries(test_mpflow PRIVATE iw2_game)
add_test(NAME mpflow COMMAND test_mpflow)
if(IW2_TEST_MAIN)
  add_test(NAME mpflow_world COMMAND test_mpflow)
  set_tests_properties(mpflow_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache")
endif()

add_executable(test_mpkillcam tests/test_mpkillcam.cpp)
target_link_libraries(test_mpkillcam PRIVATE iw2_game)
add_test(NAME mpkillcam COMMAND test_mpkillcam)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mpkillcam PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpkillcam_lerp tests/test_mpkillcam_lerp.cpp)
target_link_libraries(test_mpkillcam_lerp PRIVATE iw2_game)
add_test(NAME mpkillcam_lerp COMMAND test_mpkillcam_lerp)

add_executable(test_mpkillcam_bots tests/test_mpkillcam_bots.cpp)
target_link_libraries(test_mpkillcam_bots PRIVATE iw2_game)
add_test(NAME mpkillcam_bots COMMAND test_mpkillcam_bots)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mpkillcam_bots PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpspectate tests/test_mpspectate.cpp)
target_link_libraries(test_mpspectate PRIVATE iw2_game)
add_test(NAME mpspectate COMMAND test_mpspectate)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(mpspectate PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpteams tests/test_mpteams.cpp)
target_link_libraries(test_mpteams PRIVATE iw2_game iw2_ui)
# sd with five bots: autoassign splits them 3 / 2; a hand-picked bigger team is evened only with scr_teambalance 1.
add_test(NAME mpteams_world COMMAND test_mpteams)
set_tests_properties(mpteams_world PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 600)
add_executable(test_mpkillcam_fidelity tests/test_mpkillcam_fidelity.cpp)
target_link_libraries(test_mpkillcam_fidelity PRIVATE iw2_game)
add_test(NAME mpkillcam_fidelity COMMAND test_mpkillcam_fidelity)
add_executable(test_gammaramp tests/test_gammaramp.cpp)
target_link_libraries(test_gammaramp PRIVATE iw2_game)
add_test(NAME gammaramp COMMAND test_gammaramp)
# The client's per-frame clock and the effects read on it.
add_executable(test_clienttime tests/test_clienttime.cpp)
target_link_libraries(test_clienttime PRIVATE iw2_netgame)
add_test(NAME clienttime COMMAND test_clienttime)

add_executable(test_eventrack tests/test_eventrack.cpp)
target_include_directories(test_eventrack PRIVATE src)
add_test(NAME eventrack COMMAND test_eventrack)
add_executable(test_predictedturret tests/test_predictedturret.cpp)
target_include_directories(test_predictedturret PRIVATE src)
add_test(NAME predictedturret COMMAND test_predictedturret)
add_executable(test_placehistory tests/test_placehistory.cpp)
target_include_directories(test_placehistory PRIVATE src)
add_test(NAME placehistory COMMAND test_placehistory)
# Camera height of a network client around a landing, against the dedicated server.
# Movement through an impaired network path (tests/test_mplag.cpp): iw2server as a child process, two network clients
# through an in-process UDP relay with latency, jitter, loss and reordering from a fixed seed; no position snap.
add_executable(test_mplag tests/test_mplag.cpp)
target_link_libraries(test_mplag PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mplag COMMAND test_mplag)
  set_tests_properties(mplag PROPERTIES TIMEOUT 240 ENVIRONMENT
    "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache;IW2_LAG_SERVER=$<TARGET_FILE:iw2server>")
  # The weapon selection through the same path, with the rate limits of an internet client: A changes its weapon
  # every 150 / 400 ms; the selection must change only by the presses (docs/spec/net.md section 10 "Weapon selection").
  add_test(NAME mplag_weapon COMMAND test_mplag)
  set_tests_properties(mplag_weapon PROPERTIES TIMEOUT 240 ENVIRONMENT
    "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache;IW2_LAG_SERVER=$<TARGET_FILE:iw2server>;IW2_LAG_PROFILE=wan;IW2_LAG_ASWITCH=150;IW2_LAG_SECONDS=12;IW2_NET_WAN=1")
endif()

# The entity events of a player's weapon state changes: reload, rechamber, raise, put away reach other players and the
# killcam (tests/test_mpweaponevents.cpp).
add_executable(test_mpweaponevents tests/test_mpweaponevents.cpp)
target_link_libraries(test_mpweaponevents PRIVATE iw2_netgame)
add_test(NAME mpweaponevents COMMAND test_mpweaponevents)
if(IW2_TEST_MAIN)
  set_tests_properties(mpweaponevents PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mplanding tests/test_mplanding.cpp)
target_link_libraries(test_mplanding PRIVATE iw2_dedicated iw2_netgame)
add_test(NAME mplanding COMMAND test_mplanding)
if(IW2_TEST_MAIN)
  set_tests_properties(mplanding PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_flakcrew tests/test_flakcrew.cpp)
target_link_libraries(test_flakcrew PRIVATE iw2_game)
add_test(NAME flakcrew COMMAND test_flakcrew)

add_executable(test_levelcarry tests/test_levelcarry.cpp)
target_link_libraries(test_levelcarry PRIVATE iw2_game)
add_test(NAME levelcarry COMMAND test_levelcarry)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  set_tests_properties(levelcarry PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}")
endif()

add_executable(test_mpplayerclip tests/test_mpplayerclip.cpp)
target_link_libraries(test_mpplayerclip PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpplayerclip_e2e COMMAND test_mpplayerclip)
  set_tests_properties(mpplayerclip_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache" TIMEOUT 600)
endif()

add_executable(test_mphostparity tests/test_mphostparity.cpp)
target_link_libraries(test_mphostparity PRIVATE iw2_netgame)
if(EXISTS "${IW2_TEST_MAIN}/iw_00.iwd")
  add_test(NAME mphostparity_e2e COMMAND test_mphostparity)
  set_tests_properties(mphostparity_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}" TIMEOUT 300)
endif()

add_executable(test_mpdedicatedghost tests/test_mpdedicatedghost.cpp)
target_link_libraries(test_mpdedicatedghost PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpdedicatedghost_e2e COMMAND test_mpdedicatedghost)
  set_tests_properties(mpdedicatedghost_e2e PROPERTIES ENVIRONMENT "IW2_MAIN=${IW2_TEST_MAIN}" TIMEOUT 300)
endif()

# Bodies and dropped weapons of network clients that kill each other, go through their killcams and join again
# (tests/test_mpbodies.cpp): iw2server as a child process, three scripted network clients; every client keeps every body
# and item it saw, sees the same ones as the others and draws them (the baselines are the level's, docs/spec/net.md).
add_executable(test_mpbodies tests/test_mpbodies.cpp)
target_link_libraries(test_mpbodies PRIVATE iw2_netgame)
if(IW2_TEST_MAIN)
  add_test(NAME mpbodies_e2e COMMAND test_mpbodies)
  set_tests_properties(mpbodies_e2e PROPERTIES TIMEOUT 240 ENVIRONMENT
    "IW2_MAIN=${IW2_TEST_MAIN};IW2_NAV_CACHE=${CMAKE_BINARY_DIR}/navcache;IW2_BODIES_SERVER=$<TARGET_FILE:iw2server>")
endif()

# No test writes a session log into the real log folder (docs/logging.md): every test runs with IW2_LOG=0 and a log
# folder in the build tree (tests/iw2server_log.sh turns the log on for its own temp folder).
get_property(IW2_ALL_TESTS DIRECTORY PROPERTY TESTS)
foreach(t IN LISTS IW2_ALL_TESTS)
  set_property(TEST ${t} APPEND PROPERTY ENVIRONMENT "IW2_LOG=0" "IW2_LOG_DIR=${CMAKE_BINARY_DIR}/test-logs")
endforeach()
