mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-07 12:13:17 +01:00
Merge branch 'master' into feat/gl46-migration
This commit is contained in:
129
CMakeLists.txt
129
CMakeLists.txt
@@ -77,6 +77,7 @@ option(WITH_ZMQ "Compile with cppzmq" OFF)
|
|||||||
option(WITH_CRASHPAD "Compile with crashpad" OFF)
|
option(WITH_CRASHPAD "Compile with crashpad" OFF)
|
||||||
option(WITH_DISCORD_RPC "Compile with DISCORD RICH PRESENCE" ON)
|
option(WITH_DISCORD_RPC "Compile with DISCORD RICH PRESENCE" ON)
|
||||||
option(USE_LTO "Use link-time optimization" OFF)
|
option(USE_LTO "Use link-time optimization" OFF)
|
||||||
|
option(WITH_TESTS "Build and run the unit tests with the game, a failing test fails the build" ON)
|
||||||
option(WITH_BETTER_RENDERER "Experimental multi-backend renderer based on NVRHI" OFF)
|
option(WITH_BETTER_RENDERER "Experimental multi-backend renderer based on NVRHI" OFF)
|
||||||
option(GENERATE_PDB "Generate executable with program debugging symbols" ON)
|
option(GENERATE_PDB "Generate executable with program debugging symbols" ON)
|
||||||
option(ENABLE_MCC "Enable multicore compilation" ON)
|
option(ENABLE_MCC "Enable multicore compilation" ON)
|
||||||
@@ -119,15 +120,10 @@ set(SOURCES
|
|||||||
"vehicle/Gauge.cpp"
|
"vehicle/Gauge.cpp"
|
||||||
"utilities/Globals.cpp"
|
"utilities/Globals.cpp"
|
||||||
"utilities/Logs.cpp"
|
"utilities/Logs.cpp"
|
||||||
"McZapkie/friction.cpp"
|
|
||||||
"McZapkie/hamulce.cpp"
|
|
||||||
"McZapkie/Mover.cpp"
|
|
||||||
"McZapkie/Oerlikon_ESt.cpp"
|
|
||||||
"model/MdlMngr.cpp"
|
"model/MdlMngr.cpp"
|
||||||
"world/MemCell.cpp"
|
"world/MemCell.cpp"
|
||||||
"model/Model3d.cpp"
|
"model/Model3d.cpp"
|
||||||
"world/mtable.cpp"
|
"world/mtable.cpp"
|
||||||
"utilities/parser.cpp"
|
|
||||||
"rendering/nullrenderer.cpp"
|
"rendering/nullrenderer.cpp"
|
||||||
"rendering/renderer.cpp"
|
"rendering/renderer.cpp"
|
||||||
"model/ResourceManager.cpp"
|
"model/ResourceManager.cpp"
|
||||||
@@ -161,7 +157,6 @@ set(SOURCES
|
|||||||
"audio/audio.cpp"
|
"audio/audio.cpp"
|
||||||
"audio/audiorenderer.cpp"
|
"audio/audiorenderer.cpp"
|
||||||
"utilities/motiontelemetry.cpp"
|
"utilities/motiontelemetry.cpp"
|
||||||
"utilities/utilities.cpp"
|
|
||||||
"application/uitranscripts.cpp"
|
"application/uitranscripts.cpp"
|
||||||
"world/station.cpp"
|
"world/station.cpp"
|
||||||
"application/application.cpp"
|
"application/application.cpp"
|
||||||
@@ -225,7 +220,6 @@ set(SOURCES
|
|||||||
"utilities/dictionary.cpp"
|
"utilities/dictionary.cpp"
|
||||||
"rendering/particles.cpp"
|
"rendering/particles.cpp"
|
||||||
"utilities/headtrack.cpp"
|
"utilities/headtrack.cpp"
|
||||||
"scripting/ladderlogic.cpp"
|
|
||||||
"rendering/geometrybank.cpp"
|
"rendering/geometrybank.cpp"
|
||||||
"simulation/simulationsounds.cpp"
|
"simulation/simulationsounds.cpp"
|
||||||
"rendering/openglcamera.cpp"
|
"rendering/openglcamera.cpp"
|
||||||
@@ -454,11 +448,34 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
|
|||||||
add_compile_options("/utf-8")
|
add_compile_options("/utf-8")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# the build settings of the game: include dirs, definitions, compile options and libraries. the game,
|
||||||
|
# its core library and the unit tests all build with the same ones
|
||||||
|
add_library(eu07_settings INTERFACE)
|
||||||
|
|
||||||
|
# the parts of the simulation the game shares with the unit tests: the vehicle physics, the vehicle
|
||||||
|
# definition parser and the helpers they use. built once, and tested as the game uses them
|
||||||
|
set(CORE_SOURCES
|
||||||
|
"McZapkie/friction.cpp"
|
||||||
|
"McZapkie/hamulce.cpp"
|
||||||
|
"McZapkie/Mover.cpp"
|
||||||
|
"McZapkie/Oerlikon_ESt.cpp"
|
||||||
|
"utilities/parser.cpp"
|
||||||
|
"utilities/utilities.cpp"
|
||||||
|
"scripting/ladderlogic.cpp"
|
||||||
|
"vehicle/handover.cpp"
|
||||||
|
)
|
||||||
|
add_library(eu07_core STATIC ${CORE_SOURCES})
|
||||||
|
target_link_libraries(eu07_core PUBLIC eu07_settings)
|
||||||
|
set_target_properties(eu07_core PROPERTIES
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
|
FOLDER "libraries")
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
|
||||||
|
target_link_libraries(${PROJECT_NAME} eu07_core)
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFINITIONS})
|
target_compile_definitions(eu07_settings INTERFACE ${DEFINITIONS})
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
target_include_directories(eu07_settings INTERFACE
|
||||||
"."
|
"."
|
||||||
"Console"
|
"Console"
|
||||||
"McZapkie"
|
"McZapkie"
|
||||||
@@ -479,7 +496,7 @@ if (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
|
|||||||
# /wd4996: disable "deprecation" warnings
|
# /wd4996: disable "deprecation" warnings
|
||||||
# /wd4244: disable warnings for conversion with possible loss of data
|
# /wd4244: disable warnings for conversion with possible loss of data
|
||||||
# /wd5033: disable because it is all over Python headers
|
# /wd5033: disable because it is all over Python headers
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "/wd4996 /wd4244 /wd5033")
|
target_compile_options(eu07_settings INTERFACE /wd4996 /wd4244 /wd5033)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/LARGEADDRESSAWARE")
|
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/LARGEADDRESSAWARE")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -495,7 +512,7 @@ set_target_properties( ${PROJECT_NAME}
|
|||||||
|
|
||||||
check_cxx_symbol_exists(__GNUC__ "" COMPILER_HAVE_GNUC)
|
check_cxx_symbol_exists(__GNUC__ "" COMPILER_HAVE_GNUC)
|
||||||
if (COMPILER_HAVE_GNUC)
|
if (COMPILER_HAVE_GNUC)
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fvisibility=hidden")
|
target_compile_options(eu07_settings INTERFACE -fvisibility=hidden)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
||||||
@@ -503,6 +520,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
|||||||
|
|
||||||
if (USE_PCH)
|
if (USE_PCH)
|
||||||
target_precompile_headers(${PROJECT_NAME} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h>")
|
target_precompile_headers(${PROJECT_NAME} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h>")
|
||||||
|
target_precompile_headers(eu07_core PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_CURRENT_SOURCE_DIR}/stdafx.h>")
|
||||||
set_source_files_properties("/glad/src/glad.c" "ref/dds-ktx/src/dds-ktx.c" "ref/zstd/zstd.c" "stb/stb_image.c"
|
set_source_files_properties("/glad/src/glad.c" "ref/dds-ktx/src/dds-ktx.c" "ref/zstd/zstd.c" "stb/stb_image.c"
|
||||||
"imgui/imgui.cpp" "imgui/imgui_demo.cpp" "imgui/imgui_draw.cpp" "imgui/imgui_widgets.cpp" "imgui/imgui_tables.cpp" "imgui/imgui_impl_glfw.cpp" "imgui/ImGuizmo.cpp"
|
"imgui/imgui.cpp" "imgui/imgui_demo.cpp" "imgui/imgui_draw.cpp" "imgui/imgui_widgets.cpp" "imgui/imgui_tables.cpp" "imgui/imgui_impl_glfw.cpp" "imgui/ImGuizmo.cpp"
|
||||||
PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE)
|
PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE)
|
||||||
@@ -510,12 +528,12 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_LTO)
|
if (USE_LTO)
|
||||||
set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
set_property(TARGET ${PROJECT_NAME} eu07_core PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WITH_OPENVR)
|
if (WITH_OPENVR)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${OPENVR_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${OPENVR_INCLUDE_DIR})
|
||||||
target_link_libraries(${PROJECT_NAME} ${OPENVR_LIBRARY})
|
target_link_libraries(eu07_settings INTERFACE ${OPENVR_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WITH_CRASHPAD)
|
if (WITH_CRASHPAD)
|
||||||
@@ -530,12 +548,12 @@ if (WITH_CRASHPAD)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} glfw)
|
target_link_libraries(eu07_settings INTERFACE glfw)
|
||||||
|
|
||||||
if(WITH_PYTHON)
|
if(WITH_PYTHON)
|
||||||
find_package(Python3 3.14 REQUIRED COMPONENTS Development)
|
find_package(Python3 3.14 REQUIRED COMPONENTS Development)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS})
|
target_include_directories(eu07_settings INTERFACE ${Python3_INCLUDE_DIRS})
|
||||||
target_link_libraries(${PROJECT_NAME} ${Python3_LIBRARIES})
|
target_link_libraries(eu07_settings INTERFACE ${Python3_LIBRARIES})
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Python3_ROOT_DIR is only an input hint and is not populated by
|
# Python3_ROOT_DIR is only an input hint and is not populated by
|
||||||
@@ -553,78 +571,78 @@ endif()
|
|||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
find_package(PNG 1.6 REQUIRED)
|
find_package(PNG 1.6 REQUIRED)
|
||||||
target_link_libraries(${PROJECT_NAME} PNG::PNG)
|
target_link_libraries(eu07_settings INTERFACE PNG::PNG)
|
||||||
else()
|
else()
|
||||||
include_directories(${PNG_INCLUDE_DIRS})
|
include_directories(${PNG_INCLUDE_DIRS})
|
||||||
target_link_libraries(${PROJECT_NAME} ${PNG_LIBRARIES})
|
target_link_libraries(eu07_settings INTERFACE ${PNG_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(${PROJECT_NAME} Threads::Threads)
|
target_link_libraries(eu07_settings INTERFACE Threads::Threads)
|
||||||
|
|
||||||
find_package(GLM REQUIRED)
|
find_package(GLM REQUIRED)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${GLM_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${GLM_INCLUDE_DIR})
|
||||||
|
|
||||||
# JSON lib
|
# JSON lib
|
||||||
add_subdirectory(ref/json)
|
add_subdirectory(ref/json)
|
||||||
target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json)
|
target_link_libraries(eu07_settings INTERFACE nlohmann_json::nlohmann_json)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR})
|
target_include_directories(eu07_settings INTERFACE ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (WITH_DISCORD_RPC)
|
if (WITH_DISCORD_RPC)
|
||||||
add_subdirectory(ref/discord-rpc)
|
add_subdirectory(ref/discord-rpc)
|
||||||
set_target_properties(discord-rpc PROPERTIES FOLDER "libraries")
|
set_target_properties(discord-rpc PROPERTIES FOLDER "libraries")
|
||||||
target_link_libraries(${PROJECT_NAME} discord-rpc)
|
target_link_libraries(eu07_settings INTERFACE discord-rpc)
|
||||||
|
|
||||||
# Add the include directory so the compiler can find discord_rpc.h
|
# Add the include directory so the compiler can find discord_rpc.h
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
target_include_directories(eu07_settings INTERFACE
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/ref/discord-rpc/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/ref/discord-rpc/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PUBLIC WITH_DISCORD_RPC=1)
|
target_compile_definitions(eu07_settings INTERFACE WITH_DISCORD_RPC=1)
|
||||||
else()
|
else()
|
||||||
target_compile_definitions(${PROJECT_NAME} PUBLIC WITH_DISCORD_RPC=0)
|
target_compile_definitions(eu07_settings INTERFACE WITH_DISCORD_RPC=0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(OpenAL REQUIRED)
|
find_package(OpenAL REQUIRED)
|
||||||
if (TARGET OpenAL::OpenAL)
|
if (TARGET OpenAL::OpenAL)
|
||||||
target_link_libraries(${PROJECT_NAME} OpenAL::OpenAL)
|
target_link_libraries(eu07_settings INTERFACE OpenAL::OpenAL)
|
||||||
else()
|
else()
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${OPENAL_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${OPENAL_INCLUDE_DIR})
|
||||||
target_link_libraries(${PROJECT_NAME} ${OPENAL_LIBRARY})
|
target_link_libraries(eu07_settings INTERFACE ${OPENAL_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(SndFile REQUIRED)
|
find_package(SndFile REQUIRED)
|
||||||
target_link_libraries(${PROJECT_NAME} SndFile::sndfile)
|
target_link_libraries(eu07_settings INTERFACE SndFile::sndfile)
|
||||||
|
|
||||||
if (WITH_LUA)
|
if (WITH_LUA)
|
||||||
find_package(LuaJIT REQUIRED)
|
find_package(LuaJIT REQUIRED)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${LUAJIT_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${LUAJIT_INCLUDE_DIR})
|
||||||
target_link_libraries(${PROJECT_NAME} ${LUAJIT_LIBRARIES})
|
target_link_libraries(eu07_settings INTERFACE ${LUAJIT_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WITH_UART)
|
if (WITH_UART)
|
||||||
find_package(libserialport REQUIRED)
|
find_package(libserialport REQUIRED)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${libserialport_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${libserialport_INCLUDE_DIR})
|
||||||
target_link_libraries(${PROJECT_NAME} ${libserialport_LIBRARY})
|
target_link_libraries(eu07_settings INTERFACE ${libserialport_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(ASIO REQUIRED)
|
find_package(ASIO REQUIRED)
|
||||||
target_link_libraries(${PROJECT_NAME} asio::asio)
|
target_link_libraries(eu07_settings INTERFACE asio::asio)
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(${PROJECT_NAME} ws2_32)
|
target_link_libraries(eu07_settings INTERFACE ws2_32)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# http client of the editor's geoportal orthophoto layer: WinHTTP on Windows, libcurl elsewhere when available
|
# http client of the editor's geoportal orthophoto layer: WinHTTP on Windows, libcurl elsewhere when available
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_link_libraries(${PROJECT_NAME} winhttp)
|
target_link_libraries(eu07_settings INTERFACE winhttp)
|
||||||
else()
|
else()
|
||||||
find_package(CURL QUIET)
|
find_package(CURL QUIET)
|
||||||
if (CURL_FOUND)
|
if (CURL_FOUND)
|
||||||
target_link_libraries(${PROJECT_NAME} CURL::libcurl)
|
target_link_libraries(eu07_settings INTERFACE CURL::libcurl)
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE EU07_WITH_CURL)
|
target_compile_definitions(eu07_settings INTERFACE EU07_WITH_CURL)
|
||||||
else()
|
else()
|
||||||
message(STATUS "libcurl not found: the editor's orthophoto layer will only show cached tiles")
|
message(STATUS "libcurl not found: the editor's orthophoto layer will only show cached tiles")
|
||||||
endif()
|
endif()
|
||||||
@@ -632,8 +650,8 @@ endif()
|
|||||||
|
|
||||||
if (WITH_ZMQ)
|
if (WITH_ZMQ)
|
||||||
find_package(cppzmq REQUIRED)
|
find_package(cppzmq REQUIRED)
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE ${cppzmq_INCLUDE_DIR})
|
target_include_directories(eu07_settings INTERFACE ${cppzmq_INCLUDE_DIR})
|
||||||
target_link_libraries(${PROJECT_NAME} ${cppzmq_LIBRARY})
|
target_link_libraries(eu07_settings INTERFACE ${cppzmq_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
@@ -665,7 +683,7 @@ endif ()
|
|||||||
if (NOT TARGET yaml-cpp)
|
if (NOT TARGET yaml-cpp)
|
||||||
add_subdirectory("betterRenderer/thirdparty/yaml-cpp" "${CMAKE_CURRENT_BINARY_DIR}/yaml-cpp" EXCLUDE_FROM_ALL)
|
add_subdirectory("betterRenderer/thirdparty/yaml-cpp" "${CMAKE_CURRENT_BINARY_DIR}/yaml-cpp" EXCLUDE_FROM_ALL)
|
||||||
endif ()
|
endif ()
|
||||||
target_link_libraries(${PROJECT_NAME} yaml-cpp)
|
target_link_libraries(eu07_settings INTERFACE yaml-cpp)
|
||||||
|
|
||||||
|
|
||||||
# Enable compiling with debug symbols
|
# Enable compiling with debug symbols
|
||||||
@@ -680,7 +698,7 @@ if(GENERATE_PDB)
|
|||||||
add_link_options(/DEBUG /MAP)
|
add_link_options(/DEBUG /MAP)
|
||||||
|
|
||||||
# Ensure that the PCH is built with the correct runtime library for each configuration
|
# Ensure that the PCH is built with the correct runtime library for each configuration
|
||||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
set_target_properties(${PROJECT_NAME} eu07_core PROPERTIES
|
||||||
COMPILE_OPTIONS "$<$<CONFIG:Debug>:/MDd>$<$<CONFIG:Release>:/MD>")
|
COMPILE_OPTIONS "$<$<CONFIG:Debug>:/MDd>$<$<CONFIG:Release>:/MD>")
|
||||||
else()
|
else()
|
||||||
# GCC/Clang (ensure -g is set for all configurations)
|
# GCC/Clang (ensure -g is set for all configurations)
|
||||||
@@ -696,8 +714,27 @@ endif()
|
|||||||
if(ENABLE_AVX2)
|
if(ENABLE_AVX2)
|
||||||
message(STATUS "Enabling AVX2 instruction set.")
|
message(STATUS "Enabling AVX2 instruction set.")
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE /arch:AVX2)
|
target_compile_options(eu07_settings INTERFACE /arch:AVX2)
|
||||||
else()
|
else()
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE -mavx2)
|
target_compile_options(eu07_settings INTERFACE -mavx2)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WITH_TESTS)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(tests/unit)
|
||||||
|
# the unit tests run at the end of building the game, once it's linked: a failing one fails the build
|
||||||
|
# (and removes the game binary, so the next build links it again and reruns them)
|
||||||
|
add_dependencies(${PROJECT_NAME} eu07_unit_tests)
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DTESTS=$<TARGET_FILE:eu07_unit_tests>
|
||||||
|
-DGAME=$<TARGET_FILE:${PROJECT_NAME}>
|
||||||
|
-P "${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/run_tests.cmake"
|
||||||
|
COMMENT "Running the unit tests"
|
||||||
|
VERBATIM)
|
||||||
|
# a change of the tests alone links the game again, so they run (Ninja and Makefile generators;
|
||||||
|
# Visual Studio builds rerun them with the next change of the game)
|
||||||
|
set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY LINK_DEPENDS
|
||||||
|
$<TARGET_FILE:eu07_unit_tests> ${UNIT_TEST_DATA} "${CMAKE_CURRENT_SOURCE_DIR}/tests/unit/run_tests.cmake")
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include "hamulce.h"
|
#include "hamulce.h"
|
||||||
#include "scripting/ladderlogic.h"
|
#include "scripting/ladderlogic.h"
|
||||||
|
|
||||||
|
class cParser;
|
||||||
/*
|
/*
|
||||||
MaSzyna EU07 locomotive simulator
|
MaSzyna EU07 locomotive simulator
|
||||||
Copyright (C) 2001-2004 Maciej Czapkiewicz and others
|
Copyright (C) 2001-2004 Maciej Czapkiewicz and others
|
||||||
@@ -2534,10 +2536,12 @@ class TMoverParameters
|
|||||||
|
|
||||||
/*funkcje ladujace pliki opisujace pojazd*/
|
/*funkcje ladujace pliki opisujace pojazd*/
|
||||||
bool LoadFIZ(std::string const &chkpath); // Q 20160717 bool LoadChkFile(std::string chkpath);
|
bool LoadFIZ(std::string const &chkpath); // Q 20160717 bool LoadChkFile(std::string chkpath);
|
||||||
|
bool LoadFIZ_Text(std::string const &Text); // the same, from text in the .fiz format
|
||||||
bool CheckLocomotiveParameters(bool ReadyFlag, int Dir);
|
bool CheckLocomotiveParameters(bool ReadyFlag, int Dir);
|
||||||
std::string EngineDescription(int what) const;
|
std::string EngineDescription(int what) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool LoadFIZ_Parse(cParser &Parser); // reads the definition from either of the above
|
||||||
void LoadFIZ_Param(std::string const &line);
|
void LoadFIZ_Param(std::string const &line);
|
||||||
void LoadFIZ_Load(std::string const &line);
|
void LoadFIZ_Load(std::string const &line);
|
||||||
void LoadFIZ_Dimensions(std::string const &line);
|
void LoadFIZ_Dimensions(std::string const &line);
|
||||||
|
|||||||
@@ -9559,6 +9559,37 @@ bool TMoverParameters::LoadFIZ(std::string const &chkpath)
|
|||||||
chkPath = chkpath; // assign class path for reloading
|
chkPath = chkpath; // assign class path for reloading
|
||||||
|
|
||||||
ConversionError = 666;
|
ConversionError = 666;
|
||||||
|
std::string file = TypeName + ".fiz";
|
||||||
|
|
||||||
|
WriteLog("LOAD FIZ FROM " + file);
|
||||||
|
/*
|
||||||
|
std::ifstream in(file);
|
||||||
|
if (!in.is_open())
|
||||||
|
{
|
||||||
|
WriteLog("E8 - FIZ FILE NOT EXIST.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
cParser fizparser(file, cParser::buffer_FILE, chkpath);
|
||||||
|
if (false == fizparser.ok())
|
||||||
|
{
|
||||||
|
WriteLog("E8 - FIZ FILE NOT EXIST.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadFIZ_Parse(fizparser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reads the vehicle definition from text in the .fiz format, e.g. one made up by a unit test
|
||||||
|
bool TMoverParameters::LoadFIZ_Text(std::string const &Text)
|
||||||
|
{
|
||||||
|
ConversionError = 666;
|
||||||
|
cParser fizparser(Text, cParser::buffer_TEXT);
|
||||||
|
return LoadFIZ_Parse(fizparser);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TMoverParameters::LoadFIZ_Parse(cParser &fizparser) // NOSONAR the former body of LoadFIZ(), moved as it was; splitting it up is a change of its own
|
||||||
|
{
|
||||||
LISTLINE = 0;
|
LISTLINE = 0;
|
||||||
startBPT = false;
|
startBPT = false;
|
||||||
startMPT = false;
|
startMPT = false;
|
||||||
@@ -9577,23 +9608,6 @@ bool TMoverParameters::LoadFIZ(std::string const &chkpath)
|
|||||||
startDimmerList = false;
|
startDimmerList = false;
|
||||||
startLIGHTSLIST = false;
|
startLIGHTSLIST = false;
|
||||||
startCOMPRESSORLIST = false;
|
startCOMPRESSORLIST = false;
|
||||||
std::string file = TypeName + ".fiz";
|
|
||||||
|
|
||||||
WriteLog("LOAD FIZ FROM " + file);
|
|
||||||
/*
|
|
||||||
std::ifstream in(file);
|
|
||||||
if (!in.is_open())
|
|
||||||
{
|
|
||||||
WriteLog("E8 - FIZ FILE NOT EXIST.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
cParser fizparser(file, cParser::buffer_FILE, chkpath);
|
|
||||||
if (false == fizparser.ok())
|
|
||||||
{
|
|
||||||
WriteLog("E8 - FIZ FILE NOT EXIST.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConversionError = 0;
|
ConversionError = 0;
|
||||||
|
|
||||||
|
|||||||
21
ref/doctest/LICENSE.txt
Normal file
21
ref/doctest/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016-2023 Viktor Kirilov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
7106
ref/doctest/doctest.h
Normal file
7106
ref/doctest/doctest.h
Normal file
File diff suppressed because it is too large
Load Diff
31
tests/unit/CMakeLists.txt
Normal file
31
tests/unit/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Unit tests of the simulation, built and run without the game data: the test vehicles are JSON
|
||||||
|
# definitions in data/ (see vehicle.h), loaded from memory by the game's own vehicle loader. The code
|
||||||
|
# under test is the game's core library (eu07_core), the very objects the game links; the rest of the
|
||||||
|
# engine it reaches for is replaced with stubs (engine_stubs.cpp). They run as part of the build,
|
||||||
|
# from the IDE, or with ctest.
|
||||||
|
|
||||||
|
set(UNIT_TESTS_NAME eu07_unit_tests)
|
||||||
|
|
||||||
|
# a new test_*.cpp file or data/*.json definition is picked up on its own
|
||||||
|
file(GLOB UNIT_TEST_CASES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp")
|
||||||
|
file(GLOB UNIT_TEST_DATA CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/data/*.json")
|
||||||
|
|
||||||
|
add_executable(${UNIT_TESTS_NAME}
|
||||||
|
main.cpp
|
||||||
|
vehicle.cpp
|
||||||
|
engine_stubs.cpp
|
||||||
|
${UNIT_TEST_CASES})
|
||||||
|
|
||||||
|
# the core library brings the game's build settings along
|
||||||
|
target_link_libraries(${UNIT_TESTS_NAME} PRIVATE eu07_core)
|
||||||
|
target_include_directories(${UNIT_TESTS_NAME} PRIVATE "${DEPS_DIR}/doctest")
|
||||||
|
target_compile_definitions(${UNIT_TESTS_NAME} PRIVATE EU07_UNIT_TESTS_DATA="${CMAKE_CURRENT_SOURCE_DIR}/data")
|
||||||
|
if (MSVC)
|
||||||
|
target_compile_options(${UNIT_TESTS_NAME} PRIVATE /bigobj)
|
||||||
|
endif()
|
||||||
|
set_target_properties(${UNIT_TESTS_NAME} PROPERTIES FOLDER "tests")
|
||||||
|
|
||||||
|
add_test(NAME ${UNIT_TESTS_NAME} COMMAND ${UNIT_TESTS_NAME})
|
||||||
|
|
||||||
|
# for running the tests at the end of building the game, see the main CMakeLists.txt
|
||||||
|
set(UNIT_TEST_DATA ${UNIT_TEST_DATA} PARENT_SCOPE)
|
||||||
71
tests/unit/README.md
Normal file
71
tests/unit/README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Unit tests
|
||||||
|
|
||||||
|
C++ tests of the simulation logic: set up the conditions, run the code, check the outcome. They run
|
||||||
|
as part of every build of the game, in CI and locally alike, and from the IDE. They need no game
|
||||||
|
data and no window: the test vehicles are JSON definitions in `data/`, loaded from memory by the
|
||||||
|
game's own vehicle loader.
|
||||||
|
|
||||||
|
The code under test is `eu07_core`, the library of the simulation parts the game links as well
|
||||||
|
(physics, vehicle definition loader, ...): built once, with the game's settings, and tested as the
|
||||||
|
game uses it. The python library gets linked along with the game's settings, but the tests don't
|
||||||
|
use it and run without it.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
- **With the build**: building the game (`WITH_TESTS`, on by default) runs them at the end, once the
|
||||||
|
game is linked, and prints `Unit tests: 11/11 passed (80 checks)`. A failing test prints the full
|
||||||
|
report and fails the build; the game binary gets removed, so the next build links it again and
|
||||||
|
reruns them. They also run when only the tests, a definition in `data/` or `run_tests.cmake`
|
||||||
|
changed (Ninja and Makefile generators). `-DWITH_TESTS=OFF` builds the game without them.
|
||||||
|
- **From the IDE**: run or debug the `eu07_unit_tests` target. CLion recognises doctest and lists the
|
||||||
|
test cases, so a single one can be run or debugged from the gutter.
|
||||||
|
- **By hand**: `ctest --test-dir <build dir> --build-config Release`, or the binary itself, which takes
|
||||||
|
[doctest options](https://github.com/doctest/doctest/blob/master/doc/markdown/commandline.md), e.g.
|
||||||
|
`eu07_unit_tests -tc="brakes*"`. `EU07_UNIT_TESTS_LOG=1` shows the engine log, quiet otherwise.
|
||||||
|
|
||||||
|
## Writing a test
|
||||||
|
|
||||||
|
Add a `test_<topic>.cpp` here, it's picked up on its own.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
#include "vehicle.h"
|
||||||
|
|
||||||
|
TEST_CASE("brakes: service braking stops a heavier wagon too") {
|
||||||
|
// conditions: the wagon from data/wagon.json, heavier, running at 40 km/h
|
||||||
|
test::vehicle wagon{test::definition::load("wagon").set("Param", "M", 60000), 40.0 /* km/h */};
|
||||||
|
// action: full service application, as a coupled locomotive would make it
|
||||||
|
wagon.hold_brake_pipe(3.5); // bar
|
||||||
|
wagon.run(40.0 /* s */, test::track{0.15 /* friction */, 0.0 /* gradient */});
|
||||||
|
// outcome
|
||||||
|
CHECK(std::abs(wagon.speed()) < 0.1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `definition::load("<name>")` reads `data/<name>.json`, with the sections and keys of the game's
|
||||||
|
vehicle definitions; `set()` and `remove()` change it for the test. Table sections give their
|
||||||
|
rows as `"rows": [[...], ...]`, keys starting with `_` are comments. A section the loader doesn't
|
||||||
|
know is an error (the table of them is in `vehicle.cpp`); a misspelt key isn't caught, so check
|
||||||
|
in the test that the value the test is about made it to the vehicle. Keep the definitions
|
||||||
|
physically sensible: some parts need keys others don't (the ESt brake valves need `Brake.Size`),
|
||||||
|
and a test on a half-defined vehicle tests the definition rather than the simulation.
|
||||||
|
- The random numbers of the simulation are reseeded before every test case, so a test gives the
|
||||||
|
same result alone or among the others.
|
||||||
|
- Logic which lives in classes too big to set up in a test (the AI driver, the cab) goes into free
|
||||||
|
functions on the vehicle state the class calls, and the test calls them the same way: see
|
||||||
|
`vehicle/handover.h`, which `TController::TakeControl()` uses, and `test_handover.cpp`.
|
||||||
|
- `vehicle` creates the vehicle the way `TDynamicObject::Init()` does and steps it the way the
|
||||||
|
simulation loop does, on a straight track of the given friction and gradient. Coupled vehicles
|
||||||
|
aren't supported yet.
|
||||||
|
- Check behaviour (speeds, pressures, states the player sees) rather than internal flags, and make
|
||||||
|
the test check its own preconditions (`REQUIRE` that the braking skidded, before checking what a
|
||||||
|
skid leads to).
|
||||||
|
|
||||||
|
## Code to test, and when the tests fail to link
|
||||||
|
|
||||||
|
A source joins the tested code by moving from `SOURCES` to `CORE_SOURCES` in the main
|
||||||
|
`CMakeLists.txt`; the game links it from the library from then on. The library reaches for a little
|
||||||
|
of the rest of the engine, which `engine_stubs.cpp` stands in for in the tests (logs, settings,
|
||||||
|
scenery layers). If a change makes it use something new, either move the real source into the
|
||||||
|
library too, when it stands on its own, or add a stub to `engine_stubs.cpp`.
|
||||||
34
tests/unit/data/cab_vehicle.json
Normal file
34
tests/unit/data/cab_vehicle.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"_comment": "The wagon with the cab devices the driver uses: sandbox, light preset selector and modern headlight dimmer, both tables made after the Vectron's. No traction: enough for the logic of the cab controls.",
|
||||||
|
"Param": { "Category": "train", "M": 42200, "Vmax": 120, "SandCap": 500 },
|
||||||
|
"Dimensions": { "L": 26.8, "H": 4.7, "W": 2.88, "Cx": 0.15 },
|
||||||
|
"Wheels": { "D": 0.96, "Tw": 1.435, "Axle": "2'2'", "Ad": 2.5, "Bd": 19.5, "Rmin": 80, "BearingType": "Roll" },
|
||||||
|
"Brake": {
|
||||||
|
"BrakeValve": "KE", "NBpA": 6, "MBF": 15, "MaxBP": 3.8, "TareMaxBP": 2.3,
|
||||||
|
"BCN": 12, "BCR": 0.127, "BCD": 0.04, "BCM": 1.5, "BCS": 1.2, "BSA": 0, "BM": "Disk1", "BVV": 125
|
||||||
|
},
|
||||||
|
"BuffCoupl": { "CType": "Screw", "kB": 9200, "DmaxB": 0.11, "FmaxB": 320, "kC": 4500, "DmaxC": 0.1, "FmaxC": 850, "beta": 0.58, "AllowedFlag": 115 },
|
||||||
|
"Cntrl": { "BrakeSystem": "Pneumatic", "LocalBrake": "ManualBrake", "BrakeDelays": "P", "MaxBPMass": 63 },
|
||||||
|
"Switches": { "ModernDimmer": "Yes" },
|
||||||
|
"_lights": "light bits: 1 headlight left, 2 red marker left, 4 headlight upper, 16 headlight right, 32 red marker right",
|
||||||
|
"LightsList": {
|
||||||
|
"Size": 4, "Wrap": "No", "Default": 1,
|
||||||
|
"rows": [
|
||||||
|
[0, 0],
|
||||||
|
[21, 34],
|
||||||
|
[34, 0],
|
||||||
|
[17, 0]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_dimmer": "rows: high beam, dimmed, off",
|
||||||
|
"DimmerList": {
|
||||||
|
"Size": 5, "Cycle": "no", "DefaultPos": 3,
|
||||||
|
"rows": [
|
||||||
|
["yes", "no", "no"],
|
||||||
|
["yes", "yes", "no"],
|
||||||
|
["no", "no", "yes"],
|
||||||
|
["no", "no", "no"],
|
||||||
|
["no", "yes", "no"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
12
tests/unit/data/wagon.json
Normal file
12
tests/unit/data/wagon.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Passenger wagon with a KE distributor valve, made after a real one so the defaults make physical sense. Sections and keys are those of the game's vehicle definitions; tests change only the keys they're about.",
|
||||||
|
"Param": { "Category": "train", "M": 42200, "Vmax": 120 },
|
||||||
|
"Dimensions": { "L": 26.8, "H": 4.7, "W": 2.88, "Cx": 0.15 },
|
||||||
|
"Wheels": { "D": 0.96, "Tw": 1.435, "Axle": "2'2'", "Ad": 2.5, "Bd": 19.5, "Rmin": 80, "BearingType": "Roll" },
|
||||||
|
"Brake": {
|
||||||
|
"BrakeValve": "KE", "NBpA": 6, "MBF": 15, "MaxBP": 3.8, "TareMaxBP": 2.3,
|
||||||
|
"BCN": 12, "BCR": 0.127, "BCD": 0.04, "BCM": 1.5, "BCS": 1.2, "BSA": 0, "BM": "Disk1", "BVV": 125
|
||||||
|
},
|
||||||
|
"BuffCoupl": { "CType": "Screw", "kB": 9200, "DmaxB": 0.11, "FmaxB": 320, "kC": 4500, "DmaxC": 0.1, "FmaxC": 850, "beta": 0.58, "AllowedFlag": 115 },
|
||||||
|
"Cntrl": { "BrakeSystem": "Pneumatic", "LocalBrake": "ManualBrake", "BrakeDelays": "P", "MaxBPMass": 63 }
|
||||||
|
}
|
||||||
81
tests/unit/engine_stubs.cpp
Normal file
81
tests/unit/engine_stubs.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// stand-ins for the parts of the engine which the core library (eu07_core) links against, but which the
|
||||||
|
// unit tests don't exercise. when a change makes the library use something new from the engine and the
|
||||||
|
// test build fails to link, either move the real source into the library (CORE_SOURCES in the main
|
||||||
|
// CMakeLists.txt, if it stands on its own) or add a stub here.
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "utilities/Globals.h"
|
||||||
|
#include "utilities/Logs.h"
|
||||||
|
#include "scene/scenenodegroups.h"
|
||||||
|
#include "scene/scenelayers.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// settings ---------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
global_settings &GetGlobalSettings() {
|
||||||
|
// defaults, as before reading eu07.ini
|
||||||
|
static global_settings settings;
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// logs: quiet, unless EU07_UNIT_TESTS_LOG is set --------------------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool log_enabled() {
|
||||||
|
static bool const enabled{std::getenv("EU07_UNIT_TESTS_LOG") != nullptr};
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void WriteLog(const char *str, logtype const, bool const isError) {
|
||||||
|
if (log_enabled() && str != nullptr) {
|
||||||
|
(isError ? std::cerr : std::cout) << str << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteLog(const std::string &str, logtype const Type) {
|
||||||
|
WriteLog(str.c_str(), Type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorLog(const std::string &str, logtype const Type) {
|
||||||
|
WriteLog(str.c_str(), Type, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// scenery structure, which the parser reports include directives to --------------------------------
|
||||||
|
// the declarations come from the engine's headers, the stubs can't change them (hence the NOSONARs)
|
||||||
|
|
||||||
|
namespace scene {
|
||||||
|
|
||||||
|
node_groups Groups; // NOSONAR the engine's extern global, scene/scenenodegroups.h
|
||||||
|
node_layers Layers; // NOSONAR the engine's extern global, scene/scenelayers.h
|
||||||
|
|
||||||
|
group_handle node_groups::create(bool const) { // NOSONAR signature from scene/scenenodegroups.h
|
||||||
|
return null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
group_handle node_groups::close() { // NOSONAR signature from scene/scenenodegroups.h
|
||||||
|
return null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_handle node_layers::open(std::string const &, include_site) { // NOSONAR signature from scene/scenelayers.h
|
||||||
|
return null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_handle node_layers::close() { // NOSONAR signature from scene/scenelayers.h
|
||||||
|
return null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void node_layers::instance_begin(std::string const &, source_span const &) { // NOSONAR signature from scene/scenelayers.h
|
||||||
|
// no layers in the tests, so nothing to keep track of
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic_node has no key function for the Itanium C++ ABI (tooltip() is declared plain but defined inline), so
|
||||||
|
// GCC and Clang emit its vtable wherever scenenode.h is used, here too, and the vtable wants radius_().
|
||||||
|
// the same as the game's, in scene/scenenode.cpp
|
||||||
|
float basic_node::radius_() {
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace scene
|
||||||
40
tests/unit/main.cpp
Normal file
40
tests/unit/main.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include "utilities/Globals.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// the simulation draws random numbers (brake delays, pressures, wheel damage) from engines shared by
|
||||||
|
// everything. reseeding them before every run of a test case, subcases included, gives each test the
|
||||||
|
// same numbers whether it runs alone from the IDE or among all the others
|
||||||
|
class deterministic_random : public doctest::IReporter {
|
||||||
|
public:
|
||||||
|
explicit deterministic_random(doctest::ContextOptions const &) {}
|
||||||
|
|
||||||
|
void test_case_start(doctest::TestCaseData const &) override { reseed(); }
|
||||||
|
void test_case_reenter(doctest::TestCaseData const &) override { reseed(); }
|
||||||
|
|
||||||
|
// the rest of doctest's reporter interface: a listener reports nothing, the console reporter does that
|
||||||
|
void report_query(doctest::QueryData const &) override { /* nothing to report */ }
|
||||||
|
void test_run_start() override { /* nothing to report */ }
|
||||||
|
void test_run_end(doctest::TestRunStats const &) override { /* nothing to report */ }
|
||||||
|
void test_case_end(doctest::CurrentTestCaseStats const &) override { /* nothing to report */ }
|
||||||
|
void test_case_exception(doctest::TestCaseException const &) override { /* nothing to report */ }
|
||||||
|
void subcase_start(doctest::SubcaseSignature const &) override { /* nothing to report */ }
|
||||||
|
void subcase_end() override { /* nothing to report */ }
|
||||||
|
void log_assert(doctest::AssertData const &) override { /* nothing to report */ }
|
||||||
|
void log_message(doctest::MessageData const &) override { /* nothing to report */ }
|
||||||
|
void test_case_skipped(doctest::TestCaseData const &) override { /* nothing to report */ }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void reseed() {
|
||||||
|
Global.random_engine.seed(1);
|
||||||
|
Global.local_random_engine.seed(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DOCTEST_REGISTER_LISTENER("deterministic_random", 1, deterministic_random);
|
||||||
27
tests/unit/run_tests.cmake
Normal file
27
tests/unit/run_tests.cmake
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# runs the unit tests at the end of building the game: cmake -DTESTS=<test binary> -DGAME=<game binary> -P run_tests.cmake
|
||||||
|
# prints a one line summary when they pass, the full report when they don't. a failure fails the build and
|
||||||
|
# removes the game binary, so the next build links it again and reruns the tests
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
COMMAND "${TESTS}" --no-intro --no-version
|
||||||
|
RESULT_VARIABLE result
|
||||||
|
OUTPUT_VARIABLE report
|
||||||
|
ERROR_VARIABLE report)
|
||||||
|
|
||||||
|
string(REGEX MATCH "test cases: *([0-9]+) *\\| *([0-9]+) passed *\\| *([0-9]+) failed" _ "${report}")
|
||||||
|
set(cases "${CMAKE_MATCH_1}")
|
||||||
|
set(passed "${CMAKE_MATCH_2}")
|
||||||
|
set(failed "${CMAKE_MATCH_3}")
|
||||||
|
string(REGEX MATCH "assertions: *([0-9]+)" _ "${report}")
|
||||||
|
set(checks "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
if(result EQUAL 0)
|
||||||
|
message("Unit tests: ${passed}/${cases} passed (${checks} checks)")
|
||||||
|
else()
|
||||||
|
message("${report}")
|
||||||
|
file(REMOVE "${GAME}")
|
||||||
|
if(cases STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Unit tests: didn't run (${result}), see above. -DWITH_TESTS=OFF builds the game without them")
|
||||||
|
endif()
|
||||||
|
message(FATAL_ERROR "Unit tests: ${failed} of ${cases} FAILED, see above. -DWITH_TESTS=OFF builds the game without them")
|
||||||
|
endif()
|
||||||
93
tests/unit/test_brakes.cpp
Normal file
93
tests/unit/test_brakes.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
#include "vehicle.h"
|
||||||
|
|
||||||
|
using test::definition;
|
||||||
|
using test::vehicle;
|
||||||
|
|
||||||
|
// the brake pipe of a lone wagon is held at a pressure the way a coupled locomotive would hold it.
|
||||||
|
// pressures are in bar, the brake pipe runs at 5.0, a 1.5 bar reduction is a full service application.
|
||||||
|
|
||||||
|
TEST_CASE("brakes: KE wagon applies up to its tare limit on a service application and releases again") {
|
||||||
|
vehicle wagon{definition::load("wagon")}; // empty wagon, TareMaxBP=2.3
|
||||||
|
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(60.0);
|
||||||
|
CHECK(wagon->BrakePress < 0.1);
|
||||||
|
CHECK(wagon->Hamulec->GetBRP() > 4.5); // auxiliary reservoir charged
|
||||||
|
|
||||||
|
wagon.hold_brake_pipe(3.5);
|
||||||
|
wagon.run(10.0);
|
||||||
|
CHECK(wagon->BrakePress == doctest::Approx(2.3).epsilon(0.05));
|
||||||
|
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(60.0);
|
||||||
|
CHECK(wagon->BrakePress < 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brakes: a deeper brake pipe reduction brakes harder") {
|
||||||
|
auto applied = [](double Pipe) {
|
||||||
|
vehicle wagon{definition::load("wagon").set("Brake", "TareMaxBP", 3.8)}; // no tare limit in the way
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(60.0);
|
||||||
|
wagon.hold_brake_pipe(Pipe);
|
||||||
|
wagon.run(20.0);
|
||||||
|
return wagon->BrakePress;
|
||||||
|
};
|
||||||
|
auto const light{applied(4.6)};
|
||||||
|
auto const medium{applied(4.2)};
|
||||||
|
auto const full{applied(3.5)};
|
||||||
|
CHECK(light > 0.1);
|
||||||
|
CHECK(light < medium);
|
||||||
|
CHECK(medium < full);
|
||||||
|
CHECK(full <= 3.8 + 0.1); // MaxBP
|
||||||
|
}
|
||||||
|
|
||||||
|
// every distributor valve the engine implements goes through the same cycle; the ESt family needs the size
|
||||||
|
// of its reservoirs, as in the real .fiz files using it
|
||||||
|
TEST_CASE("brakes: every implemented distributor valve charges, applies and releases") {
|
||||||
|
for (auto const *valve : {"W", "K", "KE", "ESt", "ESt3", "NESt3", "ESt4", "ESt3AL2", "LSt", "EStED", "EP1", "EP2", "CV1"}) {
|
||||||
|
CAPTURE(valve);
|
||||||
|
vehicle wagon{definition::load("wagon").set("Brake", "BrakeValve", valve).set("Brake", "Size", 16)};
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(90.0);
|
||||||
|
CHECK(wagon->BrakePress < 0.1);
|
||||||
|
wagon.hold_brake_pipe(3.5);
|
||||||
|
wagon.run(20.0);
|
||||||
|
CHECK(wagon->BrakePress > 1.0);
|
||||||
|
CHECK(wagon->BrakePress <= 3.8 + 0.1); // MaxBP
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(90.0);
|
||||||
|
CHECK(wagon->BrakePress < 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brakes: service braking stops a wagon running at 60 km/h and keeps it stopped") {
|
||||||
|
vehicle wagon{definition::load("wagon"), 60.0};
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(10.0);
|
||||||
|
REQUIRE(wagon.speed() > 50.0); // still running before the braking
|
||||||
|
|
||||||
|
wagon.hold_brake_pipe(3.5);
|
||||||
|
wagon.run(30.0);
|
||||||
|
CHECK(std::abs(wagon.speed()) < 0.1);
|
||||||
|
wagon.run(30.0);
|
||||||
|
CHECK(std::abs(wagon.speed()) < 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brakes: a released wagon rolls on") {
|
||||||
|
vehicle wagon{definition::load("wagon"), 60.0};
|
||||||
|
wagon.hold_brake_pipe(5.0);
|
||||||
|
wagon.run(30.0);
|
||||||
|
CHECK(wagon.speed() > 50.0);
|
||||||
|
CHECK(wagon.speed() < 60.0); // running resistance slows it down a little
|
||||||
|
}
|
||||||
|
|
||||||
|
// the example in README.md
|
||||||
|
TEST_CASE("brakes: service braking stops a heavier wagon too") {
|
||||||
|
// the default wagon, with the keys the test is about changed
|
||||||
|
test::vehicle wagon{test::definition::load("wagon").set("Param", "M", 60000), 40.0 /* km/h */};
|
||||||
|
wagon.hold_brake_pipe(3.5); // bar, as a coupled locomotive would hold it
|
||||||
|
wagon.run(40.0 /* s */, test::track{0.15 /* friction */, 0.0 /* gradient */});
|
||||||
|
CHECK(std::abs(wagon.speed()) < 0.1);
|
||||||
|
}
|
||||||
45
tests/unit/test_definition.cpp
Normal file
45
tests/unit/test_definition.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
#include "vehicle.h"
|
||||||
|
|
||||||
|
using test::definition;
|
||||||
|
using test::vehicle;
|
||||||
|
|
||||||
|
TEST_CASE("definition: the wagon from data/wagon.json loads with its values") {
|
||||||
|
vehicle wagon{definition::load("wagon")};
|
||||||
|
|
||||||
|
CHECK(wagon->Mass == doctest::Approx(42200.0));
|
||||||
|
CHECK(wagon->Vmax == doctest::Approx(120.0));
|
||||||
|
CHECK(wagon->Dim.L == doctest::Approx(26.8));
|
||||||
|
CHECK(wagon->WheelDiameter == doctest::Approx(0.96));
|
||||||
|
CHECK(wagon->NAxles == 4);
|
||||||
|
CHECK(wagon->MaxBrakePress[3] == doctest::Approx(3.8));
|
||||||
|
CHECK(wagon->BrakeSystem == TBrakeSystem::Pneumatic);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("definition: keys set by the test reach the vehicle") {
|
||||||
|
vehicle heavy{definition::load("wagon").set("Param", "M", 60000).set("Wheels", "D", 1.0)};
|
||||||
|
|
||||||
|
CHECK(heavy->Mass == doctest::Approx(60000.0));
|
||||||
|
CHECK(heavy->WheelDiameter == doctest::Approx(1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("definition: an unknown test vehicle is an error, not a silently empty one") {
|
||||||
|
CHECK_THROWS(definition::load("no such vehicle"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("definition: a misspelt section is an error, not silently skipped by the loader") {
|
||||||
|
CHECK_THROWS(definition::load("wagon").set("Brakes", "MBF", 30));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("definition: rows of a table section reach the vehicle") {
|
||||||
|
auto const pc1{light::headlight_left | light::headlight_upper | light::headlight_right};
|
||||||
|
vehicle wagon{definition::load("wagon")
|
||||||
|
.set("LightsList", "Size", 2)
|
||||||
|
.set("LightsList", "Default", 1)
|
||||||
|
.set("LightsList", "rows", {{0, 0}, {pc1, 0}})};
|
||||||
|
|
||||||
|
CHECK(wagon->LightsPosNo == 2);
|
||||||
|
CHECK(wagon->LightsPos == 1);
|
||||||
|
CHECK(wagon->Lights[end::front][1] == pc1);
|
||||||
|
}
|
||||||
169
tests/unit/test_handover.cpp
Normal file
169
tests/unit/test_handover.cpp
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "doctest.h"
|
||||||
|
#include "vehicle.h"
|
||||||
|
|
||||||
|
#include "vehicle/handover.h"
|
||||||
|
|
||||||
|
using test::definition;
|
||||||
|
using test::vehicle;
|
||||||
|
|
||||||
|
// what the human driver gets when taking a vehicle over from the AI (Q key). the AI operates some controls
|
||||||
|
// directly and only the AI ever released them, so the player inherited e.g. a horn blowing forever.
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int const pc1{light::headlight_left | light::headlight_upper | light::headlight_right};
|
||||||
|
int const allheadlights{pc1 | light::redmarker_left | light::redmarker_right};
|
||||||
|
|
||||||
|
vehicle cab_vehicle() {
|
||||||
|
vehicle cab{definition::load("cab_vehicle")};
|
||||||
|
cab->DirActive = 1; // the AI drives: reverser set
|
||||||
|
return cab;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("handover: the horn the AI was sounding goes quiet (eu07/maszyna#511)") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
SUBCASE("low tone") { cab->WarningSignal = 1; }
|
||||||
|
SUBCASE("high tone") { cab->WarningSignal = 2; }
|
||||||
|
SUBCASE("both tones") { cab->WarningSignal = 1 | 2; }
|
||||||
|
REQUIRE(cab->WarningSignal != 0);
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK(cab->WarningSignal == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: sanding the AI started stops") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
cab->Sandbox(true); // the way the AI sands, bypassing the sanding button
|
||||||
|
REQUIRE(cab->SandDose);
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK_FALSE(cab->SandDose);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: sanding the player or the automatic system asked for goes on") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
SUBCASE("sanding button held") { cab->SandboxManual(true); }
|
||||||
|
SUBCASE("automatic sanding") {
|
||||||
|
cab->SandboxAutoAllow(true);
|
||||||
|
cab->SandboxAuto(true);
|
||||||
|
}
|
||||||
|
REQUIRE(cab->SandDose);
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK(cab->SandDose);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the departure signal the AI was giving stops") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
cab->signal_departure(true);
|
||||||
|
REQUIRE(cab->DepartureSignal);
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK_FALSE(cab->DepartureSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the brake releaser the AI was holding lets go") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
cab->BrakeReleaser(1);
|
||||||
|
REQUIRE(cab->Hamulec->Releaser());
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK_FALSE(cab->Hamulec->Releaser());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: nothing the AI didn't latch changes") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
|
||||||
|
handover::release_ai_controls(cab.mover(), cab.mover());
|
||||||
|
|
||||||
|
CHECK(cab->WarningSignal == 0);
|
||||||
|
CHECK_FALSE(cab->SandDose);
|
||||||
|
CHECK_FALSE(cab->DepartureSignal);
|
||||||
|
CHECK_FALSE(cab->Hamulec->Releaser());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the light selector moves to the position giving the head lights the AI has set") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
REQUIRE(cab->LightsPos == 1); // untouched selector: lights off
|
||||||
|
|
||||||
|
SUBCASE("Pc1") {
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||||
|
}
|
||||||
|
SUBCASE("lights off") {
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, 0, allheadlights) == 1);
|
||||||
|
}
|
||||||
|
SUBCASE("vehicle without the upper headlight, which the AI's Pc1 can't light") {
|
||||||
|
auto const inventory{allheadlights & ~light::headlight_upper};
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1 & inventory, inventory) == 2);
|
||||||
|
}
|
||||||
|
SUBCASE("no position matches: the selector stays") {
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the light selector stays within its table") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
|
||||||
|
SUBCASE("selector bigger than the table, as a vehicle definition can declare") {
|
||||||
|
// only the positions the table holds are looked at, the rest can't match
|
||||||
|
for (auto &table : cab->Lights) {
|
||||||
|
std::fill(std::begin(table) + cab->LightsPosNo, std::end(table), 0);
|
||||||
|
}
|
||||||
|
cab->LightsPosNo = 100;
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, light::headlight_left, allheadlights) == 1);
|
||||||
|
}
|
||||||
|
SUBCASE("table other than the front or rear one: the selector stays") {
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), -1, pc1, allheadlights) == 1);
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), 2, pc1, allheadlights) == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the light selector already matching the lights stays") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
cab->LightsPos = 2; // Pc1
|
||||||
|
|
||||||
|
CHECK(handover::light_selector_position(cab.mover(), end::front, pc1, allheadlights) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the headlight dimmer leaves the off position when the head lights are lit") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
REQUIRE(cab->enableModernDimmer);
|
||||||
|
REQUIRE(cab->dimPositions.size() == 5);
|
||||||
|
cab->modernDimmerPosition = 2; // off, left there by the player before the AI took over
|
||||||
|
|
||||||
|
SUBCASE("to the default position") {
|
||||||
|
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
|
||||||
|
}
|
||||||
|
SUBCASE("to the first regular position, when the default one is off as well") {
|
||||||
|
cab->modernDimmerDefaultPosition = 2;
|
||||||
|
CHECK(handover::dimmer_position(cab.mover(), true) == 3);
|
||||||
|
}
|
||||||
|
SUBCASE("unless the head lights are off anyway") {
|
||||||
|
CHECK(handover::dimmer_position(cab.mover(), false) == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: the headlight dimmer in any other position stays") {
|
||||||
|
auto cab{cab_vehicle()};
|
||||||
|
for (auto const position : {0, 1, 3, 4}) {
|
||||||
|
CAPTURE(position);
|
||||||
|
cab->modernDimmerPosition = position;
|
||||||
|
CHECK(handover::dimmer_position(cab.mover(), true) == position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("handover: a vehicle without the modern dimmer keeps its dimmer state") {
|
||||||
|
vehicle wagon{definition::load("wagon")};
|
||||||
|
REQUIRE_FALSE(wagon->enableModernDimmer);
|
||||||
|
|
||||||
|
CHECK(handover::dimmer_position(wagon.mover(), true) == wagon->modernDimmerPosition);
|
||||||
|
}
|
||||||
187
tests/unit/vehicle.cpp
Normal file
187
tests/unit/vehicle.cpp
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "vehicle.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct section_format {
|
||||||
|
char separator; // the game's definitions end section names with either '.' or ':'
|
||||||
|
char const *table_end{nullptr}; // the line closing the rows of a table section
|
||||||
|
};
|
||||||
|
|
||||||
|
// the sections TMoverParameters::LoadFIZ() reads, see its issection() calls. a section missing here
|
||||||
|
// would be silently skipped by the loader, and the test would run on the defaults instead
|
||||||
|
std::map<std::string, section_format, std::less<>> const sections{
|
||||||
|
{"Param", {'.'}},
|
||||||
|
{"Load", {':'}},
|
||||||
|
{"Dimensions", {':'}},
|
||||||
|
{"Wheels", {':'}},
|
||||||
|
{"Brake", {':'}},
|
||||||
|
{"Doors", {':'}},
|
||||||
|
{"BuffCoupl", {'.'}},
|
||||||
|
{"BuffCoupl1", {'.'}},
|
||||||
|
{"BuffCoupl2", {'.'}},
|
||||||
|
{"TurboPos", {':'}},
|
||||||
|
{"Cntrl", {'.'}},
|
||||||
|
{"Blending", {':'}},
|
||||||
|
{"DCEMUED", {':'}},
|
||||||
|
{"SpringBrake", {':'}},
|
||||||
|
{"Light", {':'}},
|
||||||
|
{"Headlights", {':'}},
|
||||||
|
{"Clima", {':'}},
|
||||||
|
{"Power", {':'}},
|
||||||
|
{"SpeedControl", {':'}},
|
||||||
|
{"Engine", {':'}},
|
||||||
|
{"Switches", {':'}},
|
||||||
|
{"Security", {':'}},
|
||||||
|
{"Circuit", {':'}},
|
||||||
|
{"AI", {':'}},
|
||||||
|
{"LightsList", {':', "endL"}},
|
||||||
|
{"DimmerList", {':', "endDimmerList"}},
|
||||||
|
{"WiperList", {':', "endwl"}},
|
||||||
|
{"CompressorList", {':', "endCL"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
section_format const &format_of(std::string const &Section) {
|
||||||
|
auto const lookup{sections.find(Section)};
|
||||||
|
if (lookup == sections.end()) {
|
||||||
|
throw definition_error("unknown vehicle definition section " + Section + ", see tests/unit/vehicle.cpp");
|
||||||
|
}
|
||||||
|
return lookup->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string value_text(nlohmann::ordered_json const &Value) {
|
||||||
|
if (Value.is_string()) {
|
||||||
|
return Value.get<std::string>();
|
||||||
|
}
|
||||||
|
if (Value.is_boolean()) {
|
||||||
|
return Value.get<bool>() ? "Yes" : "No";
|
||||||
|
}
|
||||||
|
return Value.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the rows of a table section, each a list of values, and the line closing them
|
||||||
|
std::string table_text(std::string const &Section, section_format const &Format, nlohmann::ordered_json const &Rows) {
|
||||||
|
if (Format.table_end == nullptr) {
|
||||||
|
throw definition_error("vehicle definition section " + Section + " has no rows");
|
||||||
|
}
|
||||||
|
std::string text;
|
||||||
|
for (auto const &row : Rows) {
|
||||||
|
for (auto const &value : row) {
|
||||||
|
text += value_text(value) + " ";
|
||||||
|
}
|
||||||
|
text += "\n";
|
||||||
|
}
|
||||||
|
return text + Format.table_end + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
definition definition::load(std::string const &Name) {
|
||||||
|
auto const path{std::string{EU07_UNIT_TESTS_DATA} + "/" + Name + ".json"};
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (false == file.is_open()) {
|
||||||
|
throw definition_error("no test vehicle definition " + path);
|
||||||
|
}
|
||||||
|
definition result;
|
||||||
|
// not brace-initialised: json{other} makes an array of other with GCC and Clang (the initializer list constructor wins)
|
||||||
|
auto const json = nlohmann::ordered_json::parse(file, nullptr, true, true);
|
||||||
|
if (false == json.is_object()) {
|
||||||
|
throw definition_error("test vehicle definition " + path + " isn't a JSON object");
|
||||||
|
}
|
||||||
|
for (auto const &[section, keys] : json.items()) {
|
||||||
|
// keys starting with _ are comments
|
||||||
|
if (section.starts_with('_')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
format_of(section);
|
||||||
|
result.m_sections[section] = keys;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition &definition::set(std::string const &Section, std::string const &Key, nlohmann::ordered_json const &Value) {
|
||||||
|
format_of(Section);
|
||||||
|
m_sections[Section][Key] = Value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition &definition::remove(std::string const &Section) {
|
||||||
|
m_sections.erase(Section);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition &definition::remove(std::string const &Section, std::string const &Key) {
|
||||||
|
if (m_sections.contains(Section)) {
|
||||||
|
m_sections[Section].erase(Key);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string definition::text() const {
|
||||||
|
std::string text;
|
||||||
|
for (auto const &[section, keys] : m_sections.items()) {
|
||||||
|
auto const &format{format_of(section)};
|
||||||
|
text += section + format.separator;
|
||||||
|
for (auto const &[key, value] : keys.items()) {
|
||||||
|
if (key != "rows") {
|
||||||
|
text += " " + key + "=" + value_text(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += "\n";
|
||||||
|
if (keys.contains("rows")) {
|
||||||
|
text += table_text(section, format, keys["rows"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
vehicle::vehicle(definition const &Definition, double Velocity, std::string const &Name) {
|
||||||
|
// the same steps as TDynamicObject::Init()
|
||||||
|
m_mover = std::make_unique<TMoverParameters>(Velocity, Name, Name, 0);
|
||||||
|
if (false == m_mover->LoadFIZ_Text(Definition.text())) {
|
||||||
|
throw definition_error("the test vehicle definition didn't load");
|
||||||
|
}
|
||||||
|
m_mover->AssignLoad("", 0.f);
|
||||||
|
m_mover->ComputeMass();
|
||||||
|
auto const driveractive{Velocity != 0.0};
|
||||||
|
if (false == m_mover->CheckLocomotiveParameters(driveractive, Velocity >= 0.0 ? 1 : -1)) {
|
||||||
|
throw definition_error("parameters mismatch in the test vehicle definition");
|
||||||
|
}
|
||||||
|
m_mover->MainCtrlPos = m_mover->MainCtrlNoPowerPos();
|
||||||
|
m_mover->LocalBrakePosA = 0.0;
|
||||||
|
m_mover->BrakeCtrlPos = static_cast<int>(std::floor(m_mover->Handle->GetPos(driveractive ? bh_RP : bh_NP)));
|
||||||
|
m_mover->BrakeLevelSet(m_mover->BrakeCtrlPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vehicle::step(track const &Track, double Dt) {
|
||||||
|
TTrackShape shape;
|
||||||
|
shape.Len = 1000.0;
|
||||||
|
shape.dHtrack = Track.gradient;
|
||||||
|
TTrackParam param;
|
||||||
|
param.Width = 1.435;
|
||||||
|
param.friction = Track.friction;
|
||||||
|
param.CategoryFlag = 1;
|
||||||
|
TTractionParam const traction;
|
||||||
|
TLocation const location{0.0, 0.0, 0.0};
|
||||||
|
TRotation const rotation{0.0, 0.0, 0.0};
|
||||||
|
if (m_pipepressure >= 0.0) {
|
||||||
|
m_mover->Pipe->CreatePress(m_pipepressure);
|
||||||
|
}
|
||||||
|
m_mover->ComputeTotalForce(Dt);
|
||||||
|
m_mover->ComputeMovement(Dt, Dt, shape, param, traction, location, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vehicle::run(double Seconds, track const &Track, double Dt) {
|
||||||
|
// whole steps: summing up Dt would make the step count depend on the rounding
|
||||||
|
auto const steps{std::lround(Seconds / Dt)};
|
||||||
|
for (long i = 0; i < steps; ++i) {
|
||||||
|
step(Track, Dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
71
tests/unit/vehicle.h
Normal file
71
tests/unit/vehicle.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include "McZapkie/MOVER.h"
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
// thrown for a test vehicle definition which can't be used: missing, malformed, or not taken by the loader
|
||||||
|
class definition_error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// definition of a test vehicle: one of the JSON files in tests/unit/data, with the sections and keys
|
||||||
|
// of the game's vehicle definitions, changed by the test where it needs to:
|
||||||
|
// auto heavy = definition::load("wagon").set("Param", "M", 60000).set("Brake", "BrakeValve", "ESt3");
|
||||||
|
class definition {
|
||||||
|
public:
|
||||||
|
// tests/unit/data/<Name>.json
|
||||||
|
static definition load(std::string const &Name);
|
||||||
|
|
||||||
|
// sets the key of the section, adding either one when it's missing
|
||||||
|
definition &set(std::string const &Section, std::string const &Key, nlohmann::ordered_json const &Value);
|
||||||
|
// drops the whole section, or one key of it
|
||||||
|
definition &remove(std::string const &Section);
|
||||||
|
definition &remove(std::string const &Section, std::string const &Key);
|
||||||
|
|
||||||
|
// the definition in the form the game's vehicle loader reads
|
||||||
|
std::string text() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
nlohmann::ordered_json m_sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
// conditions the vehicle runs in: straight track of the given friction and gradient (rise/run)
|
||||||
|
struct track {
|
||||||
|
double friction{0.15};
|
||||||
|
double gradient{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// one vehicle created the way TDynamicObject::Init() creates it, from the given definition
|
||||||
|
class vehicle {
|
||||||
|
public:
|
||||||
|
explicit vehicle(definition const &Definition, double Velocity = 0.0, std::string const &Name = "test");
|
||||||
|
|
||||||
|
TMoverParameters &mover() { return *m_mover; }
|
||||||
|
TMoverParameters *operator->() { return m_mover.get(); }
|
||||||
|
|
||||||
|
// keeps the brake pipe at the given pressure [bar], as a locomotive coupled to the vehicle would;
|
||||||
|
// a negative value leaves the pipe alone
|
||||||
|
void hold_brake_pipe(double Pressure) { m_pipepressure = Pressure; }
|
||||||
|
|
||||||
|
// advances the physics by one step of Dt, the way the simulation loop does
|
||||||
|
void step(track const &Track = {}, double Dt = 0.01);
|
||||||
|
// advances the physics by Seconds in steps of Dt
|
||||||
|
void run(double Seconds, track const &Track = {}, double Dt = 0.01);
|
||||||
|
|
||||||
|
// vehicle speed in kilometres per hour, negative when moving backwards
|
||||||
|
double speed() const { return m_mover->V * 3.6; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<TMoverParameters> m_mover;
|
||||||
|
double m_pipepressure{-1.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "utilities/translation.h"
|
#include "utilities/translation.h"
|
||||||
#include "utilities/Logs.h"
|
#include "utilities/Logs.h"
|
||||||
#include "vehicle/Train.h"
|
#include "vehicle/Train.h"
|
||||||
|
#include "vehicle/handover.h"
|
||||||
#include "world/mtable.h"
|
#include "world/mtable.h"
|
||||||
#include "vehicle/DynObj.h"
|
#include "vehicle/DynObj.h"
|
||||||
#include "world/Event.h"
|
#include "world/Event.h"
|
||||||
@@ -5561,6 +5562,12 @@ void TController::TakeControl( bool const Aidriver, bool const Forcevehiclecheck
|
|||||||
{ // teraz AI prowadzi
|
{ // teraz AI prowadzi
|
||||||
AIControllFlag = AIdriver;
|
AIControllFlag = AIdriver;
|
||||||
pVehicle->Controller = AIdriver;
|
pVehicle->Controller = AIdriver;
|
||||||
|
// the AI doesn't operate the headlight dimmer, drop the state left by the human driver
|
||||||
|
// so the lights set by the AI aren't hidden (dimmer in off position) or dimmed
|
||||||
|
pVehicle->HeadlightsAoff = false;
|
||||||
|
pVehicle->HeadlightsBoff = false;
|
||||||
|
pVehicle->DimHeadlights = false;
|
||||||
|
pVehicle->HighBeamLights = false;
|
||||||
control_lights(); // reinicjalizacja swiatel
|
control_lights(); // reinicjalizacja swiatel
|
||||||
mvOccupied->CabActivisation(true);
|
mvOccupied->CabActivisation(true);
|
||||||
iDirection = 0; // kierunek jazdy trzeba dopiero zgadnąć
|
iDirection = 0; // kierunek jazdy trzeba dopiero zgadnąć
|
||||||
@@ -5598,10 +5605,23 @@ void TController::TakeControl( bool const Aidriver, bool const Forcevehiclecheck
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // a teraz użytkownik
|
{ // a teraz użytkownik
|
||||||
if (!is_train() || !is_car()) // gasimy swiatla jesli przejmujemy odstawione
|
if (eAction == TAction::actSleep || OrderCurrentGet() == Wait_for_orders) // gasimy swiatla jesli przejmujemy odstawione
|
||||||
pVehicle->RaLightsSet(0, 0);
|
pVehicle->RaLightsSet(0, 0);
|
||||||
|
auto const aiwasdriving { AIControllFlag == AIdriver };
|
||||||
|
if( aiwasdriving ) {
|
||||||
|
// the horn, sandbox, departure signal and brake releaser the AI may have latched are momentary for the human driver
|
||||||
|
handover::release_ai_controls( *mvOccupied, *mvControlling );
|
||||||
|
fWarningDuration = 0.0;
|
||||||
|
remove_hint( driver_hint::hornon );
|
||||||
|
// the AI sets the lights directly, leave the selector in matching position so it doesn't revert them later
|
||||||
|
sync_lights_selector();
|
||||||
|
}
|
||||||
AIControllFlag = Humandriver;
|
AIControllFlag = Humandriver;
|
||||||
pVehicle->Controller = Humandriver;
|
pVehicle->Controller = Humandriver;
|
||||||
|
if( aiwasdriving ) {
|
||||||
|
// same with the headlight dimmer, which is only applied for the human driver
|
||||||
|
sync_lights_dimmer();
|
||||||
|
}
|
||||||
if( eAction == TAction::actSleep ) {
|
if( eAction == TAction::actSleep ) {
|
||||||
eAction = TAction::actUnknown;
|
eAction = TAction::actUnknown;
|
||||||
}
|
}
|
||||||
@@ -6329,6 +6349,57 @@ TController::control_handles() {
|
|||||||
} } // enginetype
|
} } // enginetype
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// moves the light preset selector to the position matching the lights set directly by the AI,
|
||||||
|
// otherwise the next SetLights() call (e.g. from CheckVehicles()) reverts them to the untouched selector position
|
||||||
|
void
|
||||||
|
TController::sync_lights_selector() {
|
||||||
|
|
||||||
|
if( mvOccupied->LightsPosNo <= 0 ) { return; }
|
||||||
|
|
||||||
|
auto const *headvehicle { pVehicles[ end::front ] };
|
||||||
|
if( headvehicle == nullptr ) { return; }
|
||||||
|
// same choice of the light table as in TDynamicObject::SetLights()
|
||||||
|
auto const lightstable { ( mvOccupied->CabOccupied * pVehicle->DirectionGet() >= 0 ? end::front : end::rear ) };
|
||||||
|
// same choice of the lit vehicle end as in TDynamicObject::RaLightsSet()
|
||||||
|
auto const headend { ( headvehicle->iDirection > 0 ? end::front : end::rear ) };
|
||||||
|
auto const position { handover::light_selector_position(
|
||||||
|
*mvOccupied, lightstable, headvehicle->MoverParameters->iLights[ headend ], headvehicle->LightList( headend ) ) };
|
||||||
|
if( position == mvOccupied->LightsPos ) { return; }
|
||||||
|
|
||||||
|
mvOccupied->LightsPos = position;
|
||||||
|
// visual feedback
|
||||||
|
auto *train { simulation::Trains.find( pVehicle->name() ) };
|
||||||
|
if( train != nullptr ) {
|
||||||
|
train->ggLightsButton.PutValue( static_cast<float>( position - 1 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-applies the headlight dimmer state after the AI handed the vehicle over to the human driver,
|
||||||
|
// moving the dimmer out of the off position if it'd hide the head lights set by the AI
|
||||||
|
void
|
||||||
|
TController::sync_lights_dimmer() {
|
||||||
|
|
||||||
|
if( mvOccupied->dimPositions.empty() ) { return; }
|
||||||
|
|
||||||
|
auto const *headvehicle { pVehicles[ end::front ] };
|
||||||
|
auto const headend { ( headvehicle != nullptr && headvehicle->iDirection > 0 ? end::front : end::rear ) };
|
||||||
|
auto const headlightson {
|
||||||
|
headvehicle != nullptr
|
||||||
|
&& ( headvehicle->MoverParameters->iLights[ headend ] & ( light::headlight_left | light::headlight_right | light::headlight_upper ) ) != 0 };
|
||||||
|
|
||||||
|
auto const position { handover::dimmer_position( *mvOccupied, headlightson ) };
|
||||||
|
if( position != mvOccupied->modernDimmerPosition ) {
|
||||||
|
mvOccupied->modernDimmerPosition = position;
|
||||||
|
// visual feedback
|
||||||
|
auto *train { simulation::Trains.find( pVehicle->name() ) };
|
||||||
|
if( train != nullptr ) {
|
||||||
|
train->ggModernLightDimSw.PutValue( static_cast<float>( position ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( position < 0 || position >= static_cast<int>( mvOccupied->dimPositions.size() ) ) { return; } // SetLightDimmings() doesn't check the range
|
||||||
|
pVehicle->SetLightDimmings();
|
||||||
|
}
|
||||||
|
|
||||||
// activate consist lamp codes
|
// activate consist lamp codes
|
||||||
void
|
void
|
||||||
TController::control_lights() {
|
TController::control_lights() {
|
||||||
|
|||||||
@@ -324,6 +324,8 @@ private:
|
|||||||
void control_security_system( double const Timedelta );
|
void control_security_system( double const Timedelta );
|
||||||
void control_handles();
|
void control_handles();
|
||||||
void control_lights();
|
void control_lights();
|
||||||
|
void sync_lights_selector();
|
||||||
|
void sync_lights_dimmer();
|
||||||
void control_compartment_lights();
|
void control_compartment_lights();
|
||||||
void control_doors();
|
void control_doors();
|
||||||
void UpdateCommand();
|
void UpdateCommand();
|
||||||
|
|||||||
76
vehicle/handover.cpp
Normal file
76
vehicle/handover.cpp
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stdafx.h"
|
||||||
|
#include "vehicle/handover.h"
|
||||||
|
|
||||||
|
#include "McZapkie/MOVER.h"
|
||||||
|
|
||||||
|
namespace handover {
|
||||||
|
|
||||||
|
void release_ai_controls(TMoverParameters &Occupied, TMoverParameters &Controlling) {
|
||||||
|
// the AI's hornoff action touches the horn only while the AI drives
|
||||||
|
Occupied.WarningSignal = 0;
|
||||||
|
// the AI drives the sandbox directly, bypassing the manual and automatic sanding states
|
||||||
|
if (Controlling.SandDose && false == (Controlling.SandDoseManual || Controlling.SandDoseAuto)) {
|
||||||
|
Controlling.Sandbox(false);
|
||||||
|
}
|
||||||
|
if (Occupied.DepartureSignal) {
|
||||||
|
Occupied.signal_departure(false);
|
||||||
|
}
|
||||||
|
if (Occupied.Hamulec->Releaser()) {
|
||||||
|
Occupied.BrakeReleaser(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int light_selector_position(TMoverParameters const &Occupied, int const Lightstable, int const Headlights, int const Inventory) {
|
||||||
|
// the selector size comes from the vehicle definition, the table holds a fixed number of positions
|
||||||
|
auto const positioncount{std::min(Occupied.LightsPosNo, static_cast<int>(std::size(Occupied.Lights[0])))};
|
||||||
|
if (positioncount <= 0 || Lightstable < 0 || Lightstable >= static_cast<int>(std::size(Occupied.Lights))) {
|
||||||
|
return Occupied.LightsPos;
|
||||||
|
}
|
||||||
|
auto const &lights{Occupied.Lights[Lightstable]};
|
||||||
|
auto const lightsmatch{[&lights, Inventory, Headlights](int const Position) { return (lights[Position - 1] & Inventory) == Headlights; }};
|
||||||
|
if (Occupied.LightsPos >= 1 && Occupied.LightsPos <= positioncount && lightsmatch(Occupied.LightsPos)) {
|
||||||
|
return Occupied.LightsPos;
|
||||||
|
}
|
||||||
|
for (auto position = 1; position <= positioncount; ++position) {
|
||||||
|
if (lightsmatch(position)) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Occupied.LightsPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dimmer_position(TMoverParameters const &Occupied, bool const Headlightson) {
|
||||||
|
auto const &positions{Occupied.dimPositions};
|
||||||
|
auto const current{Occupied.modernDimmerPosition};
|
||||||
|
auto const count{static_cast<int>(positions.size())};
|
||||||
|
auto const isoff{[&positions, count](int const Position) { return Position < 0 || Position >= count || positions[Position].isOff; }};
|
||||||
|
|
||||||
|
if (false == Occupied.enableModernDimmer || false == Headlightson || false == isoff(current)) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
if (false == isoff(Occupied.modernDimmerDefaultPosition)) {
|
||||||
|
return Occupied.modernDimmerDefaultPosition;
|
||||||
|
}
|
||||||
|
for (auto position = 0; position < count; ++position) {
|
||||||
|
if (false == (positions[position].isOff || positions[position].isDimmed || positions[position].isHighBeam)) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto position = 0; position < count; ++position) {
|
||||||
|
if (false == positions[position].isOff) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace handover
|
||||||
33
vehicle/handover.h
Normal file
33
vehicle/handover.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
This Source Code Form is subject to the
|
||||||
|
terms of the Mozilla Public License, v.
|
||||||
|
2.0. If a copy of the MPL was not
|
||||||
|
distributed with this file, You can
|
||||||
|
obtain one at
|
||||||
|
http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class TMoverParameters;
|
||||||
|
|
||||||
|
// what changes about a vehicle when the AI hands it over to the human driver. the AI operates some
|
||||||
|
// controls directly, bypassing the cab controls a human uses, and only the AI ever releases them;
|
||||||
|
// the human gets them in the state matching the cab controls instead.
|
||||||
|
namespace handover {
|
||||||
|
|
||||||
|
// releases what the AI latches, but which is held only while pressed for the human driver:
|
||||||
|
// the horn, the sandbox, the departure signal and the brake releaser
|
||||||
|
void release_ai_controls(TMoverParameters &Occupied, TMoverParameters &Controlling);
|
||||||
|
|
||||||
|
// position of the light preset selector [1..LightsPosNo] for the human driver: the current one when it
|
||||||
|
// gives the head lights the AI has set, else the first one which does, else the current one.
|
||||||
|
// Lightstable is the end of the selector's table in use, Inventory the lights the head end has
|
||||||
|
int light_selector_position(TMoverParameters const &Occupied, int Lightstable, int Headlights, int Inventory);
|
||||||
|
|
||||||
|
// position of the headlight dimmer for the human driver: the current one, unless it's the off position
|
||||||
|
// which would hide the head lights the AI has lit. then the default one, else the first regular one,
|
||||||
|
// else any other than off
|
||||||
|
int dimmer_position(TMoverParameters const &Occupied, bool Headlightson);
|
||||||
|
|
||||||
|
} // namespace handover
|
||||||
Reference in New Issue
Block a user