diff --git a/dumb/CMakeLists.txt b/dumb/CMakeLists.txt index 2b70ee412..9c1a69a79 100644 --- a/dumb/CMakeLists.txt +++ b/dumb/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required( VERSION 2.4 ) make_release_only() include( CheckFunctionExists ) +include( CheckCXXCompilerFlag ) # DUMB is much slower in a Debug build than a Release build, so we force a Release # build here, since we're not maintaining DUMB, only using it. @@ -104,5 +105,9 @@ add_library( dumb target_link_libraries( dumb ) if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - set_source_files_properties( src/it/filter.cpp PROPERTIES COMPILE_FLAGS -msse ) + CHECK_CXX_COMPILER_FLAG( -msse DUMB_CAN_USE_SSE ) + + if( DUMB_CAN_USE_SSE ) + set_source_files_properties( src/it/filter.cpp PROPERTIES COMPILE_FLAGS -msse ) + endif( DUMB_CAN_USE_SSE ) endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) diff --git a/lzma/C/CpuArch.c b/lzma/C/CpuArch.c index 425d18923..d6ab3f7f8 100644 --- a/lzma/C/CpuArch.c +++ b/lzma/C/CpuArch.c @@ -70,9 +70,9 @@ static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d) *c = c2; *d = d2; - #elif __PIC__ + #elif defined __PIC__ && defined __i386__ - /* GCC or Clang WITH position-independent code generation */ + /* GCC or Clang WITH position-independent code generation, i386 only */ __asm__ __volatile__ ( "xchgl %%ebx, %1\n" @@ -86,7 +86,7 @@ static void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d) #else - /* GCC or Clang WITHOUT position-independent code generation */ + /* GCC or Clang WITHOUT position-independent code generation, or x86_64 */ __asm__ __volatile__ ( "cpuid" diff --git a/output_sdl/CMakeLists.txt b/output_sdl/CMakeLists.txt index b959a6873..289f6f48d 100644 --- a/output_sdl/CMakeLists.txt +++ b/output_sdl/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required( VERSION 2.4 ) add_library( output_sdl MODULE output_sdl.c ) include_directories( ${FMOD_INCLUDE_DIR} ${SDL_INCLUDE_DIR} ) +target_link_libraries( output_sdl SDL ) FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so ]; then ln -sf output_sdl/liboutput_sdl.so ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so; fi" ) add_custom_command( TARGET output_sdl POST_BUILD diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 1128b2e55..cbb5b902c 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -92,30 +92,33 @@ Note: All fields default to false unless mentioned otherwise. linedef { - alpha = ; // Translucency of this line, default is 1.0 - renderstyle = ; // Render style, can be "translucent" or "add", - // default is "translucent". - playeruseback = ; // New SPAC flag, true = player can use from back side. - anycross = ; // New SPAC flag, true = any non-projectile - // crossing will trigger this line - monsteractivate = ; // Monsters can trigger this line. - // For compatibility only because this flag's - // semantics can not be fully reproduced with - // explicit trigger flags. - blockplayers = ; // Line blocks players' movement. - blockeverything = ; // Line blocks everything. - firstsideonly = ; // Line can only be triggered from the front side. - zoneboundary = ; // Line is a boundary for sound reverb zones. - clipmidtex = ; // Line's mid textures are clipped to floor and ceiling. - wrapmidtex = ; // Line's mid textures are wrapped. - midtex3d = ; // Actors can walk on mid texture. - checkswitchrange = ;// Switches can only be activated when vertically reachable. - blockprojectiles = ;// Line blocks all projectiles - blockuse = ; // Line blocks all use actions - blocksight = ; // Line blocks monster line of sight - blockhitscan = ; // Line blocks hitscan attacks - locknumber = ; // Line special is locked - arg0str = ; // Alternate string-based version of arg0 + alpha = ; // Translucency of this line, default is 1.0 + renderstyle = ; // Render style, can be "translucent" or "add", + // default is "translucent". + playeruseback = ; // New SPAC flag, true = player can use from back side. + anycross = ; // New SPAC flag, true = any non-projectile + // crossing will trigger this line + monsteractivate = ; // Monsters can trigger this line. + // For compatibility only because this flag's + // semantics can not be fully reproduced with + // explicit trigger flags. + blockplayers = ; // Line blocks players' movement. + blockeverything = ; // Line blocks everything. + firstsideonly = ; // Line can only be triggered from the front side. + zoneboundary = ; // Line is a boundary for sound reverb zones. + clipmidtex = ; // Line's mid textures are clipped to floor and ceiling. + wrapmidtex = ; // Line's mid textures are wrapped. + midtex3d = ; // Actors can walk on mid texture. + midtex3dimpassible = ;// Used in conjuction with midtex3d - causes the mid + // texture to behave like an impassible line (projectiles + // pass through it). + checkswitchrange = ; // Switches can only be activated when vertically reachable. + blockprojectiles = ; // Line blocks all projectiles + blockuse = ; // Line blocks all use actions + blocksight = ; // Line blocks monster line of sight + blockhitscan = ; // Line blocks hitscan attacks + locknumber = ; // Line special is locked + arg0str = ; // Alternate string-based version of arg0 transparent = ; // true = line is a Strife transparent line (alpha 0.25) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8c57d74be..a54488e20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,1215 +1,1276 @@ -cmake_minimum_required( VERSION 2.4 ) - -if( COMMAND cmake_policy ) - cmake_policy( SET CMP0003 NEW ) -endif( COMMAND cmake_policy ) - -include( CheckCXXSourceCompiles ) -include( CheckFunctionExists ) -include( CheckCXXCompilerFlag ) -include( FindPkgConfig ) - -if( NOT APPLE ) - option( NO_ASM "Disable assembly code" OFF ) -else( NOT APPLE ) - # At the moment asm code doesn't work with OS X, so disable by default - option( NO_ASM "Disable assembly code" ON ) -endif( NOT APPLE ) -if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - option( NO_STRIP "Do not strip Release or MinSizeRel builds" ) - # At least some versions of Xcode fail if you strip with the linker - # instead of the separate strip utility. - if( APPLE ) - set( NO_STRIP ON ) - endif( APPLE ) -endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - -option( DYN_FLUIDSYNTH "Dynamically load fluidsynth" ON ) - -if( CMAKE_SIZEOF_VOID_P MATCHES "8" ) - set( X64 64 ) -endif( CMAKE_SIZEOF_VOID_P MATCHES "8" ) - -# You can either use "make install" on the FMOD distribution to put it -# in standard system locations, or you can unpack the FMOD distribution -# in the root of the zdoom tree. e.g.: -# zdoom -# docs -# fmodapilinux[64] -or simply- fmod -# jpeg-6b -# ... -# The recommended method is to put it in the zdoom tree, since its -# headers are unversioned. Especially now that we can't work properly -# with anything newer than 4.26.xx, you probably don't want to use -# a system-wide version. - -# Construct version numbers for searching for the FMOD library on Linux. -set( MINOR_VERSIONS "50" "49" "48" "47" "46" "45" "44" "43" "42" "41" - "40" "39" "38" "37" "36" "35" "34" "33" "32" "31" "30" "29" "28" - "27" "26" "25" "24" "23" "22" "21" "20" "21" "19" "18" "17" "16" - "15" "14" "13" "12" "11" "10" "09" "08" "07" "06" "05" "04" "03" - "02" "01" "00" ) -set( MAJOR_VERSIONS "44" "34" "28" "26" "24" "22" "20" ) - -if( NOT FMOD_DIR_VERSIONS ) - set( FMOD_DIR_VERSIONS "" ) -endif( NOT FMOD_DIR_VERSIONS ) -if( NOT FMOD_VERSIONS ) - set( FMOD_VERSIONS "" ) -endif( NOT FMOD_VERSIONS ) -if( NOT FMOD_LOCAL_INC_DIRS ) - set( FMOD_LOCAL_INC_DIRS "" ) -endif( NOT FMOD_LOCAL_INC_DIRS ) -if( NOT FMOD_LOCAL_LIB_DIRS ) - set( FMOD_LOCAL_LIB_DIRS "" ) -endif( NOT FMOD_LOCAL_LIB_DIRS ) - -set( FMOD_DIR_VERSIONS ${FMOD_DIR_VERSIONS} "../fmod" ) -foreach( majver ${MAJOR_VERSIONS} ) - foreach( minver ${MINOR_VERSIONS} ) - set( FMOD_VERSIONS ${FMOD_VERSIONS} "fmodex${X64}-4.${majver}.${minver}" ) - set( FMOD_DIR_VERSIONS ${FMOD_DIR_VERSIONS} "${CMAKE_HOME_DIRECTORY}/fmodapi4${majver}${minver}linux${X64}" ) - endforeach( minver ${MINOR_VERSIONS} ) - foreach( dir ${FMOD_DIR_VERSIONS} ) - set( FMOD_LOCAL_INC_DIRS ${FMOD_LOCAL_INC_DIRS} "${dir}/api/inc" ) - set( FMOD_LOCAL_LIB_DIRS ${FMOD_LOCAL_LIB_DIRS} "${dir}/api/lib" ) - endforeach( dir ${FMOD_DIR_VERSIONS} ) -endforeach( majver ${MAJOR_VERSIONS} ) - -if( NOT ZDOOM_LIBS ) - set( ZDOOM_LIBS "" ) -endif( NOT ZDOOM_LIBS ) - -if( WIN32 ) - if( X64 ) - set( WIN_TYPE Win64 ) - set( XBITS x64 ) - else( X64 ) - set( WIN_TYPE Win32 ) - set( XBITS x86 ) - endif( X64 ) - - add_definitions( -D_WIN32 ) - - set( FMOD_SEARCH_PATHS - "C:/Program Files/FMOD SoundSystem/FMOD Programmers API ${WIN_TYPE}/api" - "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API ${WIN_TYPE}/api" - # This next one is for Randy. - "E:/Software/Dev/FMOD/${WIN_TYPE}/api" - # .. and this one for Graf Zahl - "D:/portable/FMOD SoundSystem 4.26/FMOD Programmers API WIN32/api" - ) - set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) - set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) - set( NASM_NAMES nasmw nasm ) - - find_path( D3D_INCLUDE_DIR d3d9.h - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Include ) - if( NOT D3D_INCLUDE_DIR ) - message( SEND_ERROR "Could not find DirectX 9 header files" ) - else( NOT D3D_INCLUDE_DIR ) - include_directories( ${D3D_INCLUDE_DIR} ) - endif( NOT D3D_INCLUDE_DIR ) - - find_path( XINPUT_INCLUDE_DIR xinput.h - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Include ) - if( NOT XINPUT_INCLUDE_DIR ) - message( SEND_ERROR "Could not find xinput.h. XInput will be disabled." ) - add_definitions( -DNO_XINPUT ) - else( NOT XINPUT_INCLUDE_DIR ) - include_directories( ${XINPUT_INCLUDE_DIR} ) - endif( NOT XINPUT_INCLUDE_DIR ) - - find_library( DX_dxguid_LIBRARY dxguid - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Lib Lib/${XBITS} ) - find_library( DX_dinput8_LIBRARY dinput8 - PATHS ENV DXSDK_DIR - PATH_SUFFIXES Lib Lib/${XBITS} ) - - set( DX_LIBS_FOUND YES ) - if( NOT DX_dxguid_LIBRARY ) - set( DX_LIBS_FOUND NO ) - endif( NOT DX_dxguid_LIBRARY ) - if( NOT DX_dinput8_LIBRARY ) - set( DX_LIBS_FOUND NO ) - endif( NOT DX_dinput8_LIBRARY ) - - if( NOT DX_LIBS_FOUND ) - message( FATAL_ERROR "Could not find DirectX 9 libraries" ) - endif( NOT DX_LIBS_FOUND ) - - set( ZDOOM_LIBS - wsock32 - winmm - "${DX_dxguid_LIBRARY}" - "${DX_dinput8_LIBRARY}" - ole32 - user32 - gdi32 - comctl32 - comdlg32 - ws2_32 - setupapi - oleaut32 - DelayImp ) -else( WIN32 ) - if( APPLE ) - set( FMOD_SEARCH_PATHS "/Developer/FMOD Programmers API Mac/api" ) - set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) - set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) - set( NO_GTK ON ) - else( APPLE ) - option( NO_GTK "Disable GTK+ dialogs (Not applicable to Windows)" ) - option( VALGRIND "Add special Valgrind sequences to self-modifying code" ) - - set( FMOD_SEARCH_PATHS - /usr/local/include - /usr/local/include/fmodex - /usr/include - /usr/include/fmodex - /opt/local/include - /opt/local/include/fmodex - /opt/include - /opt/include/fmodex ) - set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES fmodex ) - - # Use GTK+ for the IWAD picker, if available. - if( NOT NO_GTK ) - pkg_check_modules( GTK2 gtk+-2.0 ) - if( GTK2_FOUND ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} ${GTK2_LIBRARIES} ) - include_directories( ${GTK2_INCLUDE_DIRS} ) - else( GTK2_FOUND ) - set( NO_GTK ON ) - endif( GTK2_FOUND ) - endif( NOT NO_GTK ) - - # Check for Xcursor library and header files - find_library( XCURSOR_LIB Xcursor ) - if( XCURSOR_LIB ) - find_file( XCURSOR_HEADER "X11/Xcursor/Xcursor.h" ) - if( XCURSOR_HEADER ) - add_definitions( -DUSE_XCURSOR=1 ) - message( STATUS "Found Xcursor at ${XCURSOR_LIB}" ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} ${XCURSOR_LIB} ) - else( XCURSOR_HEADER ) - unset( XCURSOR_LIB ) - endif( XCURSOR_HEADER ) - endif( XCURSOR_LIB ) - endif( APPLE ) - set( NASM_NAMES nasm ) - - if( NO_GTK ) - add_definitions( -DNO_GTK=1 ) - endif( NO_GTK ) - - # Non-Windows version also needs SDL - find_package( SDL ) - if( NOT SDL_FOUND ) - message( SEND_ERROR "SDL is required for building." ) - endif( NOT SDL_FOUND ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} "${SDL_LIBRARY}" ) - include_directories( "${SDL_INCLUDE_DIR}" ) - - find_path( FPU_CONTROL_DIR fpu_control.h ) - if( FPU_CONTROL_DIR ) - include_directories( ${FPU_CONTROL_DIR} ) - add_definitions( -DHAVE_FPU_CONTROL ) - endif( FPU_CONTROL_DIR ) -endif( WIN32 ) - -# Decide on the name of the FMOD library we want to use. - -if( NOT FMOD_LIB_NAME AND MSVC ) - set( FMOD_LIB_NAME fmodex${X64}_vc ) -endif( NOT FMOD_LIB_NAME AND MSVC ) - -if( NOT FMOD_LIB_NAME AND BORLAND ) - set( FMOD_LIB_NAME fmodex${X64}_bc ) -endif( NOT FMOD_LIB_NAME AND BORLAND ) - -if( NOT FMOD_LIB_NAME ) - set( FMOD_LIB_NAME fmodex${X64} ) -endif( NOT FMOD_LIB_NAME ) - - -# Search for FMOD include files - -if( NOT WIN32 ) - find_path( FMOD_INCLUDE_DIR fmod.hpp - PATHS ${FMOD_LOCAL_INC_DIRS} ) -endif( NOT WIN32 ) - -if( NOT FMOD_INCLUDE_DIR ) - find_path( FMOD_INCLUDE_DIR fmod.hpp - PATHS ${FMOD_SEARCH_PATHS} - ${FMOD_INC_PATH_SUFFIXES} ) -endif( NOT FMOD_INCLUDE_DIR ) - -if( FMOD_INCLUDE_DIR ) - message( STATUS "FMOD include files found at ${FMOD_INCLUDE_DIR}" ) -else( FMOD_INCLUDE_DIR ) - message( SEND_ERROR "Could not find FMOD include files" ) -endif( FMOD_INCLUDE_DIR ) - - -# Search for FMOD library - -if( WIN32 OR APPLE ) - find_library( FMOD_LIBRARY ${FMOD_LIB_NAME} - PATHS ${FMOD_SEARCH_PATHS} - ${FMOD_LIB_PATH_SUFFIXES} ) -else( WIN32 OR APPLE ) - find_library( FMOD_LIBRARY - NAMES ${FMOD_VERSIONS} - PATHS ${FMOD_LOCAL_LIB_DIRS} ) -endif( WIN32 OR APPLE ) - -if( FMOD_LIBRARY ) - message( STATUS "FMOD library found at ${FMOD_LIBRARY}" ) -else( FMOD_LIBRARY ) - message( SEND_ERROR "Could not find FMOD library" ) -endif( FMOD_LIBRARY ) - - -# Search for FluidSynth - -find_package( FluidSynth ) - -# Search for NASM - -if( NOT NO_ASM ) - if( UNIX AND X64 ) - find_program( GAS_PATH as ) - - if( GAS_PATH ) - set( ASSEMBLER ${GAS_PATH} ) - else( GAS_PATH ) - message( STATUS "Could not find as. Disabling assembly code." ) - set( NO_ASM ON ) - endif( GAS_PATH ) - else( UNIX AND X64 ) - find_program( NASM_PATH NAMES ${NASM_NAMES} ) - find_program( YASM_PATH yasm ) - - if( X64 ) - if( YASM_PATH ) - set( ASSEMBLER ${YASM_PATH} ) - else( YASM_PATH ) - message( STATUS "Could not find YASM. Disabling assembly code." ) - set( NO_ASM ON ) - endif( YASM_PATH ) - else( X64 ) - if( NASM_PATH ) - set( ASSEMBLER ${NASM_PATH} ) - else( NASM_PATH ) - message( STATUS "Could not find NASM. Disabling assembly code." ) - set( NO_ASM ON ) - endif( NASM_PATH ) - endif( X64 ) - endif( UNIX AND X64 ) - - # I think the only reason there was a version requirement was because the - # executable name for Windows changed from 0.x to 2.0, right? This is - # how to do it in case I need to do something similar later. - - # execute_process( COMMAND ${NASM_PATH} -v - # OUTPUT_VARIABLE NASM_VER_STRING ) - # string( REGEX REPLACE ".*version ([0-9]+[.][0-9]+).*" "\\1" NASM_VER "${NASM_VER_STRING}" ) - # if( NOT NASM_VER LESS 2 ) - # message( SEND_ERROR "NASM version should be 2 or later. (Installed version is ${NASM_VER}.)" ) - # endif( NOT NASM_VER LESS 2 ) -endif( NOT NO_ASM ) - -if( NOT NO_ASM ) - # Valgrind support is meaningless without assembly code. - if( VALGRIND ) - add_definitions( -DVALGRIND_AWARE=1 ) - # If you're Valgrinding, you probably want to keep symbols around. - set( NO_STRIP ON ) - endif( VALGRIND ) - - # Tell CMake how to assemble our files - if( UNIX ) - set( ASM_OUTPUT_EXTENSION .o ) - if( X64 ) - set( ASM_FLAGS ) - set( ASM_SOURCE_EXTENSION .s ) - else( X64 ) - if( APPLE ) - set( ASM_FLAGS -fmacho -DM_TARGET_MACHO ) - else( APPLE ) - set( ASM_FLAGS -felf -DM_TARGET_LINUX ) - endif( APPLE ) - set( ASM_FLAGS "${ASM_FLAGS}" -i${CMAKE_CURRENT_SOURCE_DIR}/ ) - set( ASM_SOURCE_EXTENSION .asm ) - endif( X64 ) - else( UNIX ) - set( ASM_OUTPUT_EXTENSION .obj ) - set( ASM_SOURCE_EXTENSION .asm ) - if( X64 ) - set( ASM_FLAGS -f win64 -DWIN32 -DWIN64 ) - else( X64 ) - set( ASM_FLAGS -f win32 -DWIN32 -i${CMAKE_CURRENT_SOURCE_DIR}/ ) - endif( X64 ) - endif( UNIX ) - if( WIN32 ) - set( FIXRTEXT fixrtext ) - else( WIN32 ) - set( FIXRTEXT "" ) - endif( WIN32 ) - message( STATUS "Selected assembler: ${ASSEMBLER}" ) - MACRO( ADD_ASM_FILE indir infile ) - set( ASM_OUTPUT_${infile} "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir}/${infile}${ASM_OUTPUT_EXTENSION}" ) - if( WIN32 ) - set( FIXRTEXT_${infile} COMMAND ${FIXRTEXT} "${ASM_OUTPUT_${infile}}" ) - else( WIN32 ) - set( FIXRTEXT_${infile} COMMAND "" ) - endif( WIN32 ) - add_custom_command( OUTPUT ${ASM_OUTPUT_${infile}} - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir} - COMMAND ${ASSEMBLER} ${ASM_FLAGS} -o"${ASM_OUTPUT_${infile}}" "${CMAKE_CURRENT_SOURCE_DIR}/${indir}/${infile}${ASM_SOURCE_EXTENSION}" - ${FIXRTEXT_${infile}} - DEPENDS ${indir}/${infile}.asm ${FIXRTEXT} ) - set( ASM_SOURCES ${ASM_SOURCES} "${ASM_OUTPUT_${infile}}" ) - ENDMACRO( ADD_ASM_FILE ) -endif( NOT NO_ASM ) - -# Decide on SSE setup - -set( SSE_MATTERS NO ) - -# with global use of SSE 2 we do not need special handling for selected files -if (NOT ZDOOM_USE_SSE2) - # SSE only matters on 32-bit targets. We check compiler flags to know if we can do it. - if( CMAKE_SIZEOF_VOID_P MATCHES "4" AND NOT CMAKE_OSX_ARCHITECTURES MATCHES ppc ) - CHECK_CXX_COMPILER_FLAG( "-msse2 -mfpmath=sse" CAN_DO_MFPMATH ) - CHECK_CXX_COMPILER_FLAG( -arch:SSE2 CAN_DO_ARCHSSE2 ) - if( CAN_DO_MFPMATH ) - set( SSE1_ENABLE "-msse -mfpmath=sse" ) - set( SSE2_ENABLE "-msse2 -mfpmath=sse" ) - set( SSE_MATTERS YES ) - elseif( CAN_DO_ARCHSSE2 ) - set( SSE1_ENABLE -arch:SSE ) - set( SSE2_ENABLE -arch:SSE2 ) - set( SSE_MATTERS YES ) - endif( CAN_DO_MFPMATH ) - endif( CMAKE_SIZEOF_VOID_P MATCHES "4" AND NOT CMAKE_OSX_ARCHITECTURES MATCHES ppc ) -endif (NOT ZDOOM_USE_SSE2) - -if( SSE_MATTERS ) - if( WIN32 ) - set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) - else( WIN32 ) - CHECK_FUNCTION_EXISTS(mprotect HAVE_MPROTECT) - if( HAVE_MPROTECT ) - set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) - else( HAVE_MPROTECT ) - set( BACKPATCH 0 ) - endif( HAVE_MPROTECT ) - endif( WIN32 ) - set( SSE 1 CACHE BOOL "Build SSE and SSE2 versions of key code." ) -else( SSE_MATTERS ) - set( BACKPATCH 0 ) -endif( SSE_MATTERS ) - -# Set up flags for GCC - -if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - if( PROFILE ) - set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg" ) - set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg" ) - set( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -pg" ) - set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -pg" ) - endif( PROFILE ) - - set( REL_CXX_FLAGS "-fno-rtti" ) - if( NOT PROFILE ) - set( REL_CXX_FLAGS "${REL_CXX_FLAGS} -fomit-frame-pointer" ) - endif( NOT PROFILE ) - set( CMAKE_CXX_FLAGS_RELEASE "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE}" ) - set( CMAKE_CXX_FLAGS_MINSIZEREL "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_MINSIZEREL}" ) - set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" ) - - set( CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused -Wno-unused-parameter -Wno-missing-field-initializers ${CMAKE_CXX_FLAGS}" ) - - # Remove extra warnings when using the official DirectX headers. - # Also, TDM-GCC 4.4.0 no longer accepts glibc-style printf formats as valid, - # which is a royal pain. The previous version I had been using was fine with them. - if( WIN32 ) - set( CMAKE_CXX_FLAGS "-Wno-unknown-pragmas -Wno-comment -Wno-format ${CMAKE_CXX_FLAGS}" ) - endif( WIN32 ) - - if( NOT NO_STRIP ) - set (CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s" ) - set (CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} -s" ) - endif( NOT NO_STRIP ) -endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - -# Check for functions that may or may not exist. - -CHECK_FUNCTION_EXISTS( filelength FILELENGTH_EXISTS ) -if( FILELENGTH_EXISTS ) - add_definitions( -DHAVE_FILELENGTH=1 ) -endif( FILELENGTH_EXISTS ) - -CHECK_FUNCTION_EXISTS( strupr STRUPR_EXISTS ) -if( NOT STRUPR_EXISTS ) - add_definitions( -DNEED_STRUPR=1 ) -endif( NOT STRUPR_EXISTS ) - -CHECK_FUNCTION_EXISTS( stricmp STRICMP_EXISTS ) -if( NOT STRICMP_EXISTS ) - add_definitions( -Dstricmp=strcasecmp ) -endif( NOT STRICMP_EXISTS ) - -CHECK_FUNCTION_EXISTS( strnicmp STRNICMP_EXISTS ) -if( NOT STRNICMP_EXISTS ) - add_definitions( -Dstrnicmp=strncasecmp ) -endif( NOT STRNICMP_EXISTS ) - -if( NOT MSVC ) - add_definitions( -D__forceinline=inline ) -endif( NOT MSVC ) - -if( UNIX ) - CHECK_LIBRARY_EXISTS( rt clock_gettime "" CLOCK_GETTIME_IN_RT ) - if( NOT CLOCK_GETTIME_IN_RT ) - CHECK_FUNCTION_EXISTS( clock_gettime CLOCK_GETTIME_EXISTS ) - if( NOT CLOCK_GETTIME_EXISTS ) - message( STATUS "Could not find clock_gettime. Timing statistics will not be available." ) - add_definitions( -DNO_CLOCK_GETTIME ) - endif( NOT CLOCK_GETTIME_EXISTS ) - else( NOT CLOCK_GETTIME_IN_RT ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} rt ) - endif( NOT CLOCK_GETTIME_IN_RT ) -endif( UNIX ) - -CHECK_CXX_SOURCE_COMPILES( - "#include - int main() { va_list list1, list2; va_copy(list1, list2); return 0; }" - HAS_VA_COPY ) -if( NOT HAS_VA_COPY ) - CHECK_CXX_SOURCE_COMPILES( - "#include - int main() { va_list list1, list2; __va_copy(list1, list2); return 0; }" - HAS___VA_COPY ) - if( HAS___VA_COPY ) - add_definitions( -Dva_copy=__va_copy ) - else( HAS___VA_COPY ) - add_definitions( -DNO_VA_COPY ) - endif( HAS___VA_COPY ) -endif( NOT HAS_VA_COPY ) - -# Flags - -if( BACKPATCH ) - add_definitions( -DBACKPATCH ) -endif( BACKPATCH ) - -# Update gitinfo.h - -get_target_property( UPDATEREVISION_EXE updaterevision LOCATION ) - -add_custom_target( revision_check ALL - COMMAND ${UPDATEREVISION_EXE} src/gitinfo.h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - DEPENDS updaterevision ) - -# Libraries ZDoom needs - -message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) -set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${GME_LIBRARIES}" "${FMOD_LIBRARY}" ) -include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" ) - -if( NOT DYN_FLUIDSYNTH) - if( FLUIDSYNTH_FOUND ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" ) - include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) - endif( FLUIDSYNTH_FOUND ) -else( NOT DYN_FLUIDSYNTH ) - set( ZDOOM_LIBS ${ZDOOM_LIBS} ${CMAKE_DL_LIBS} ) -endif( NOT DYN_FLUIDSYNTH ) - -# Start defining source files for ZDoom -set( PLAT_WIN32_SOURCES - win32/eaxedit.cpp - win32/fb_d3d9.cpp - win32/fb_d3d9_wipe.cpp - win32/fb_ddraw.cpp - win32/hardware.cpp - win32/helperthread.cpp - win32/i_cd.cpp - win32/i_crash.cpp - win32/i_input.cpp - win32/i_keyboard.cpp - win32/i_mouse.cpp - win32/i_dijoy.cpp - win32/i_rawps2.cpp - win32/i_xinput.cpp - win32/i_main.cpp - win32/i_movie.cpp - win32/i_system.cpp - win32/st_start.cpp - win32/win32video.cpp ) -set( PLAT_SDL_SOURCES - sdl/crashcatcher.c - sdl/hardware.cpp - sdl/i_cd.cpp - sdl/i_input.cpp - sdl/i_joystick.cpp - sdl/i_main.cpp - sdl/i_movie.cpp - sdl/i_system.cpp - sdl/sdlvideo.cpp - sdl/st_start.cpp ) -set( PLAT_MAC_SOURCES - sdl/SDLMain.m - sdl/iwadpicker_cocoa.mm - sdl/i_system_cocoa.mm ) -if( WIN32 ) - set( SYSTEM_SOURCES_DIR win32 ) - set( SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) - set( OTHER_SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ${PLAT_MAC_SOURCES} ) - - if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - # CMake is not set up to compile and link rc files with GCC. :( - add_custom_command( OUTPUT zdoom-rc.o - COMMAND windres -o zdoom-rc.o -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zdoom.rc - DEPENDS win32/zdoom.rc ) - set( SYSTEM_SOURCES ${SYSTEM_SOURCES} zdoom-rc.o ) - else( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - set( SYSTEM_SOURCES ${SYSTEM_SOURCES} win32/zdoom.rc ) - endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) -else( WIN32 ) - set( SYSTEM_SOURCES_DIR sdl ) - set( SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ) - if( APPLE ) - set( SYSTEM_SOURCES ${SYSTEM_SOURCES} ${PLAT_MAC_SOURCES} ) - set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) - else( APPLE ) - set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_MAC_SOURCES} ) - endif( APPLE ) -endif( WIN32 ) - -if( NOT ASM_SOURCES ) - set( ASM_SOURCES "" ) -endif( NOT ASM_SOURCES ) - -if( NO_ASM ) - add_definitions( -DNOASM ) -else( NO_ASM ) - if( X64 ) - ADD_ASM_FILE( asm_x86_64 tmap3 ) - else( X64 ) - ADD_ASM_FILE( asm_ia32 a ) - ADD_ASM_FILE( asm_ia32 misc ) - ADD_ASM_FILE( asm_ia32 tmap ) - ADD_ASM_FILE( asm_ia32 tmap2 ) - ADD_ASM_FILE( asm_ia32 tmap3 ) - endif( X64 ) -endif( NO_ASM ) - -get_target_property( LEMON_EXE lemon LOCATION ) -get_target_property( RE2C_EXE re2c LOCATION ) - -add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.h - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y . - COMMAND ${LEMON_EXE} xlat_parser.y - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y ) - -add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.lemon ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.h - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/zscript/zcc-parse.lemon . - COMMAND ${LEMON_EXE} zcc-parse.lemon - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/zscript/zcc-parse.lemon ) - -add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h - COMMAND ${RE2C_EXE} --no-generation-date -s -o ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re - DEPENDS re2c ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re ) - -include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) - -if( SSE_MATTERS ) - if( SSE ) - set( X86_SOURCES nodebuild_classify_sse2.cpp ) - set_source_files_properties( nodebuild_classify_sse2.cpp PROPERTIES COMPILE_FLAGS "${SSE2_ENABLE}" ) - else( SSE ) - add_definitions( -DDISABLE_SSE ) - endif( SSE ) -else( SSE_MATTERS ) - add_definitions( -DDISABLE_SSE ) - set( X86_SOURCES ) -endif( SSE_MATTERS ) - -if( DYN_FLUIDSYNTH ) - add_definitions( -DHAVE_FLUIDSYNTH -DDYN_FLUIDSYNTH ) -elseif( FLUIDSYNTH_FOUND ) - add_definitions( -DHAVE_FLUIDSYNTH ) -endif( DYN_FLUIDSYNTH ) - -# Project files should be aware of the header files. We can GLOB these since -# there's generally a new cpp for every header so this file will get changed -if( WIN32 ) - set( EXTRA_HEADER_DIRS win32/*.h ) -else( WIN32 ) - set( EXTRA_HEADER_DIRS sdl/*.h ) -endif( WIN32 ) -file( GLOB HEADER_FILES - ${EXTRA_HEADER_DIRS} - fragglescript/*.h - g_doom/*.h - g_heretic/*.h - g_hexen/*.h - g_raven/*.h - g_shared/*.h - g_strife/*.h - intermission/*.h - menu/*.h - oplsynth/*.h - oplsynth/dosbox/*.h - r_data/*.h - resourcefiles/*.h - sdl/*.h - sfmt/*.h - sound/*.h - textures/*.h - thingdef/*.h - xlat/*.h - zcript/*.h - *.h -) - -# These files will be flagged as "headers" so that they appear in project files -# without being compiled. -set( NOT_COMPILED_SOURCE_FILES - ${OTHER_SYSTEM_SOURCES} - sc_man_scanner.h - sc_man_scanner.re - g_doom/a_arachnotron.cpp - g_doom/a_archvile.cpp - g_doom/a_bossbrain.cpp - g_doom/a_bruiser.cpp - g_doom/a_cacodemon.cpp - g_doom/a_cyberdemon.cpp - g_doom/a_demon.cpp - g_doom/a_doomimp.cpp - g_doom/a_doomweaps.cpp - g_doom/a_fatso.cpp - g_doom/a_keen.cpp - g_doom/a_lostsoul.cpp - g_doom/a_painelemental.cpp - g_doom/a_possessed.cpp - g_doom/a_revenant.cpp - g_doom/a_scriptedmarine.cpp - g_doom/a_spidermaster.cpp - g_heretic/a_chicken.cpp - g_heretic/a_dsparil.cpp - g_heretic/a_hereticartifacts.cpp - g_heretic/a_hereticimp.cpp - g_heretic/a_hereticweaps.cpp - g_heretic/a_ironlich.cpp - g_heretic/a_knight.cpp - g_heretic/a_wizard.cpp - g_hexen/a_bats.cpp - g_hexen/a_bishop.cpp - g_hexen/a_blastradius.cpp - g_hexen/a_boostarmor.cpp - g_hexen/a_centaur.cpp - g_hexen/a_clericflame.cpp - g_hexen/a_clericholy.cpp - g_hexen/a_clericmace.cpp - g_hexen/a_clericstaff.cpp - g_hexen/a_dragon.cpp - g_hexen/a_fighteraxe.cpp - g_hexen/a_fighterhammer.cpp - g_hexen/a_fighterplayer.cpp - g_hexen/a_fighterquietus.cpp - g_hexen/a_firedemon.cpp - g_hexen/a_flechette.cpp - g_hexen/a_fog.cpp - g_hexen/a_healingradius.cpp - g_hexen/a_heresiarch.cpp - g_hexen/a_hexenspecialdecs.cpp - g_hexen/a_iceguy.cpp - g_hexen/a_korax.cpp - g_hexen/a_magecone.cpp - g_hexen/a_magelightning.cpp - g_hexen/a_magestaff.cpp - g_hexen/a_pig.cpp - g_hexen/a_serpent.cpp - g_hexen/a_spike.cpp - g_hexen/a_summon.cpp - g_hexen/a_teleportother.cpp - g_hexen/a_wraith.cpp - g_strife/a_acolyte.cpp - g_strife/a_alienspectres.cpp - g_strife/a_coin.cpp - g_strife/a_crusader.cpp - g_strife/a_entityboss.cpp - g_strife/a_inquisitor.cpp - g_strife/a_oracle.cpp - g_strife/a_programmer.cpp - g_strife/a_reaver.cpp - g_strife/a_rebels.cpp - g_strife/a_sentinel.cpp - g_strife/a_stalker.cpp - g_strife/a_strifeitems.cpp - g_strife/a_strifeweapons.cpp - g_strife/a_templar.cpp - g_strife/a_thingstoblowup.cpp - g_shared/sbarinfo_commands.cpp - xlat/xlat_parser.y - xlat_parser.c - xlat_parser.h - zscript/zcc-parse.lemon - zcc-parse.c - zcc-parse.h - - # We could have the ASM macro add these files, but it wouldn't add all - # platforms. - asm_ia32/a.asm - asm_ia32/misc.asm - asm_ia32/tmap.asm - asm_ia32/tmap2.asm - asm_ia32/tmap3.asm - asm_x86_64/tmap3.asm - asm_x86_64/tmap3.s -) - -add_executable( zdoom WIN32 - ${HEADER_FILES} - ${NOT_COMPILED_SOURCE_FILES} - __autostart.cpp - ${ASM_SOURCES} - ${SYSTEM_SOURCES} - ${X86_SOURCES} - x86.cpp - actorptrselect.cpp - am_map.cpp - b_bot.cpp - b_func.cpp - b_game.cpp - b_move.cpp - b_think.cpp - bbannouncer.cpp - c_bind.cpp - c_cmds.cpp - c_console.cpp - c_consolebuffer.cpp - c_cvars.cpp - c_dispatch.cpp - c_expr.cpp - cmdlib.cpp - colormatcher.cpp - compatibility.cpp - configfile.cpp - ct_chat.cpp - d_dehacked.cpp - d_iwad.cpp - d_main.cpp - d_net.cpp - d_netinfo.cpp - d_protocol.cpp - decallib.cpp - dobject.cpp - dobjgc.cpp - dobjtype.cpp - doomdef.cpp - doomstat.cpp - dsectoreffect.cpp - dthinker.cpp - f_wipe.cpp - farchive.cpp - files.cpp - g_game.cpp - g_hub.cpp - g_level.cpp - g_mapinfo.cpp - g_skill.cpp - gameconfigfile.cpp - gi.cpp - gitinfo.cpp - hu_scores.cpp - i_net.cpp - info.cpp - keysections.cpp - lumpconfigfile.cpp - m_alloc.cpp - m_argv.cpp - m_bbox.cpp - m_cheat.cpp - m_joy.cpp - m_misc.cpp - m_png.cpp - m_random.cpp - m_specialpaths.cpp - memarena.cpp - md5.cpp - name.cpp - nodebuild.cpp - nodebuild_classify_nosse2.cpp - nodebuild_events.cpp - nodebuild_extract.cpp - nodebuild_gl.cpp - nodebuild_utility.cpp - p_3dfloors.cpp - p_3dmidtex.cpp - p_acs.cpp - p_buildmap.cpp - p_ceiling.cpp - p_conversation.cpp - p_doors.cpp - p_effect.cpp - p_enemy.cpp - p_floor.cpp - p_glnodes.cpp - p_interaction.cpp - p_lights.cpp - p_linkedsectors.cpp - p_lnspec.cpp - p_map.cpp - p_maputl.cpp - p_mobj.cpp - p_pillar.cpp - p_plats.cpp - p_pspr.cpp - p_saveg.cpp - p_sectors.cpp - p_setup.cpp - p_sight.cpp - p_slopes.cpp - p_spec.cpp - p_states.cpp - p_switch.cpp - p_teleport.cpp - p_terrain.cpp - p_things.cpp - p_tick.cpp - p_trace.cpp - p_udmf.cpp - p_usdf.cpp - p_user.cpp - p_writemap.cpp - p_xlat.cpp - parsecontext.cpp - po_man.cpp - r_swrenderer.cpp - r_utility.cpp - r_3dfloors.cpp - r_bsp.cpp - r_draw.cpp - r_drawt.cpp - r_main.cpp - r_plane.cpp - r_segs.cpp - r_sky.cpp - r_things.cpp - s_advsound.cpp - s_environment.cpp - s_playlist.cpp - s_sndseq.cpp - s_sound.cpp - sc_man.cpp - st_stuff.cpp - statistics.cpp - stats.cpp - stringtable.cpp - strnatcmp.c - tables.cpp - teaminfo.cpp - tempfiles.cpp - v_blend.cpp - v_collection.cpp - v_draw.cpp - v_font.cpp - v_palette.cpp - v_pfx.cpp - v_text.cpp - v_video.cpp - w_wad.cpp - wi_stuff.cpp - zstrformat.cpp - zstring.cpp - g_doom/a_doommisc.cpp - g_heretic/a_hereticmisc.cpp - g_hexen/a_hexenmisc.cpp - g_raven/a_artitele.cpp - g_raven/a_minotaur.cpp - g_strife/a_strifestuff.cpp - g_strife/strife_sbar.cpp - g_shared/a_action.cpp - g_shared/a_armor.cpp - g_shared/a_artifacts.cpp - g_shared/a_bridge.cpp - g_shared/a_camera.cpp - g_shared/a_debris.cpp - g_shared/a_decals.cpp - g_shared/a_fastprojectile.cpp - g_shared/a_flashfader.cpp - g_shared/a_fountain.cpp - g_shared/a_hatetarget.cpp - g_shared/a_keys.cpp - g_shared/a_lightning.cpp - g_shared/a_mapmarker.cpp - g_shared/a_morph.cpp - g_shared/a_movingcamera.cpp - g_shared/a_pickups.cpp - g_shared/a_puzzleitems.cpp - g_shared/a_quake.cpp - g_shared/a_randomspawner.cpp - g_shared/a_secrettrigger.cpp - g_shared/a_sectoraction.cpp - g_shared/a_setcolor.cpp - g_shared/a_skies.cpp - g_shared/a_soundenvironment.cpp - g_shared/a_soundsequence.cpp - g_shared/a_spark.cpp - g_shared/a_specialspot.cpp - g_shared/a_waterzone.cpp - g_shared/a_weaponpiece.cpp - g_shared/a_weapons.cpp - g_shared/hudmessages.cpp - g_shared/sbarinfo.cpp - g_shared/sbar_mugshot.cpp - g_shared/shared_hud.cpp - g_shared/shared_sbar.cpp - intermission/intermission.cpp - intermission/intermission_parse.cpp - menu/colorpickermenu.cpp - menu/joystickmenu.cpp - menu/listmenu.cpp - menu/loadsavemenu.cpp - menu/menu.cpp - menu/menudef.cpp - menu/menuinput.cpp - menu/messagebox.cpp - menu/optionmenu.cpp - menu/playerdisplay.cpp - menu/playermenu.cpp - menu/readthis.cpp - menu/videomenu.cpp - oplsynth/fmopl.cpp - oplsynth/mlopl.cpp - oplsynth/mlopl_io.cpp - oplsynth/music_opldumper_mididevice.cpp - oplsynth/music_opl_mididevice.cpp - oplsynth/opl_mus_player.cpp - oplsynth/dosbox/opl.cpp - oplsynth/OPL3.cpp - resourcefiles/ancientzip.cpp - resourcefiles/file_7z.cpp - resourcefiles/file_grp.cpp - resourcefiles/file_lump.cpp - resourcefiles/file_rff.cpp - resourcefiles/file_wad.cpp - resourcefiles/file_zip.cpp - resourcefiles/file_pak.cpp - resourcefiles/file_directory.cpp - resourcefiles/resourcefile.cpp - sfmt/SFMT.cpp - sound/fmodsound.cpp - sound/i_music.cpp - sound/i_sound.cpp - sound/music_cd.cpp - sound/music_dumb.cpp - sound/music_gme.cpp - sound/music_mus_midiout.cpp - sound/music_smf_midiout.cpp - sound/music_hmi_midiout.cpp - sound/music_xmi_midiout.cpp - sound/music_midistream.cpp - sound/music_midi_base.cpp - sound/music_midi_timidity.cpp - sound/music_mus_opl.cpp - sound/music_stream.cpp - sound/music_fluidsynth_mididevice.cpp - sound/music_softsynth_mididevice.cpp - sound/music_timidity_mididevice.cpp - sound/music_win_mididevice.cpp - sound/music_pseudo_mididevice.cpp - textures/animations.cpp - textures/anim_switches.cpp - textures/automaptexture.cpp - textures/bitmap.cpp - textures/buildtexture.cpp - textures/canvastexture.cpp - textures/ddstexture.cpp - textures/flattexture.cpp - textures/imgztexture.cpp - textures/jpegtexture.cpp - textures/multipatchtexture.cpp - textures/patchtexture.cpp - textures/pcxtexture.cpp - textures/pngtexture.cpp - textures/rawpagetexture.cpp - textures/emptytexture.cpp - textures/texture.cpp - textures/texturemanager.cpp - textures/tgatexture.cpp - textures/warptexture.cpp - thingdef/olddecorations.cpp - thingdef/thingdef.cpp - thingdef/thingdef_codeptr.cpp - thingdef/thingdef_data.cpp - thingdef/thingdef_exp.cpp - thingdef/thingdef_expression.cpp - thingdef/thingdef_function.cpp - thingdef/thingdef_parse.cpp - thingdef/thingdef_properties.cpp - thingdef/thingdef_states.cpp - timidity/common.cpp - timidity/instrum.cpp - timidity/instrum_dls.cpp - timidity/instrum_font.cpp - timidity/instrum_sf2.cpp - timidity/mix.cpp - timidity/playmidi.cpp - timidity/resample.cpp - timidity/timidity.cpp - xlat/parse_xlat.cpp - fragglescript/t_fspic.cpp - fragglescript/t_func.cpp - fragglescript/t_load.cpp - fragglescript/t_oper.cpp - fragglescript/t_parse.cpp - fragglescript/t_prepro.cpp - fragglescript/t_script.cpp - fragglescript/t_spec.cpp - fragglescript/t_variable.cpp - fragglescript/t_cmd.cpp - r_data/colormaps.cpp - r_data/sprites.cpp - r_data/voxels.cpp - r_data/renderstyle.cpp - r_data/r_interpolate.cpp - r_data/r_translate.cpp - zscript/ast.cpp - zscript/vmbuilder.cpp - zscript/vmdisasm.cpp - zscript/vmexec.cpp - zscript/vmframe.cpp - zscript/zcc_compile.cpp - zscript/zcc_expr.cpp - zscript/zcc_parser.cpp - zzautozend.cpp -) - -set_source_files_properties( xlat/parse_xlat.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c" ) -set_source_files_properties( sc_man.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h" ) -set_source_files_properties( ${NOT_COMPILED_SOURCE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE ) - -if(${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") - # [BL] Solaris requires these to be explicitly linked. - set( ZDOOM_LIBS ${ZDOOM_LIBS} nsl socket) -endif(${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") - -target_link_libraries( zdoom ${ZDOOM_LIBS} gdtoa dumb lzma ) -include_directories( . - g_doom - g_heretic - g_hexen - g_raven - g_strife - g_shared - oplsynth - sound - textures - thingdef - timidity - xlat - zcript - ../gdtoa - ../dumb/include - ${CMAKE_BINARY_DIR}/gdtoa - ${SYSTEM_SOURCES_DIR} ) - -add_dependencies( zdoom revision_check ) - -# Due to some quirks, we need to do this in this order -if( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) - # RUNTIME_OUTPUT_DIRECTORY does not exist in CMake 2.4. - # Linux distributions are slow to adopt 2.6. :( - set_target_properties( zdoom PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ZDOOM_OUTPUT_DIR} ) - set_target_properties( zdoom PROPERTIES OUTPUT_NAME ${ZDOOM_EXE_NAME} ) -else( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) - set_target_properties( zdoom PROPERTIES - RUNTIME_OUTPUT_NAME ${ZDOOM_EXE_NAME} - RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ZDOOM_OUTPUT_DIR} - RUNTIME_OUTPUT_NAME_DEBUG ${ZDOOM_EXE_NAME}d - RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ZDOOM_OUTPUT_DIR} - RUNTIME_OUTPUT_NAME_MINSIZEREL ${ZDOOM_EXE_NAME}msr - RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${ZDOOM_OUTPUT_DIR} - RUNTIME_OUTPUT_NAME_RELWITHDEBINFO ${ZDOOM_EXE_NAME}rd - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ZDOOM_OUTPUT_DIR} - ) -endif( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) - -if( MSVC ) - option( ZDOOM_GENERATE_MAPFILE "Generate .map file for debugging." OFF ) - if( ZDOOM_GENERATE_MAPFILE ) - set_target_properties(zdoom PROPERTIES LINK_FLAGS "/MANIFEST:NO /DELAYLOAD:\"fmodex${X64}.dll\" /MAP") - else( ZDOOM_GENERATE_MAPFILE ) - set_target_properties(zdoom PROPERTIES LINK_FLAGS "/MANIFEST:NO /DELAYLOAD:\"fmodex${X64}.dll\"") - endif( ZDOOM_GENERATE_MAPFILE ) - - add_custom_command(TARGET zdoom POST_BUILD - COMMAND "mt.exe" -manifest \"${CMAKE_CURRENT_SOURCE_DIR}\\win32\\zdoom.exe.manifest\" -outputresource:\"$(TargetDir)$(TargetFileName)\"\;\#1 - COMMENT "Adding manifest..." - ) - - create_default_target_launcher( zdoom WORKING_DIRECTORY ${ZDOOM_OUTPUT_DIR} ) -endif( MSVC ) - -if( NOT WIN32 ) - FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME} ]; then ln -sf ${CMAKE_CURRENT_BINARY_DIR}/${ZDOOM_EXE_NAME} ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME}; fi" ) - add_custom_command( TARGET zdoom POST_BUILD - COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make - COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make ) -endif( NOT WIN32 ) -if( CMAKE_COMPILER_IS_GNUCXX ) - # GCC misoptimizes this file - set_source_files_properties( oplsynth/fmopl.cpp PROPERTIES COMPILE_FLAGS "-fno-tree-dominator-opts -fno-tree-fre" ) -endif( CMAKE_COMPILER_IS_GNUCXX ) -if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - # Need to enable intrinsics for this file. - if( SSE_MATTERS ) - set_source_files_properties( x86.cpp PROPERTIES COMPILE_FLAGS "-msse2 -mmmx" ) - endif( SSE_MATTERS ) -endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) - -source_group("Assembly Files\\ia32" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_ia32/.+") -source_group("Assembly Files\\x86_64" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_x86_64/.+") -source_group("Audio Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/.+") -source_group("Audio Files\\OPL Synth" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/oplsynth/.+") -source_group("Audio Files\\OPL Synth\\DOSBox" FILES oplsynth/dosbox/opl.cpp oplsynth/dosbox/opl.h) -source_group("Audio Files\\Timidity\\Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.h$") -source_group("Audio Files\\Timidity\\Source" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.cpp$") -source_group("Decorate++" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/thingdef/.+") -source_group("FraggleScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/fragglescript/.+") -source_group("Games\\Doom Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_doom/.+") -source_group("Games\\Heretic Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_heretic/.+") -source_group("Games\\Hexen Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_hexen/.+") -source_group("Games\\Raven Shared" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_raven/.+") -source_group("Games\\Strife Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_strife/.+") -source_group("Intermission" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/intermission/.+") -source_group("Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/menu/.+") -source_group("Render Core\\Render Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_.+\\.h$") -source_group("Render Core\\Render Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_.+\\.cpp$") -source_group("Render Data\\Resource Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_data/.+\\.h$") -source_group("Render Data\\Resource Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_data/.+\\.cpp$") -source_group("Render Data\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/textures/.+") -source_group("Render Interface" FILES r_defs.h r_renderer.h r_sky.cpp r_sky.h r_state.h r_utility.cpp r_utility.h) -source_group("Resource Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/resourcefiles/.+") -source_group("SDL Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sdl/.+") -source_group("SFMT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sfmt/.+") -source_group("Shared Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_shared/.+") -source_group("Versioning" FILES version.h win32/zdoom.rc) -source_group("Win32 Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/win32/.+") -source_group("Xlat" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/xlat/.+" FILES ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.h) -source_group("ZScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/zscript/.+") -source_group("Source Files" FILES ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h sc_man_scanner.re) +cmake_minimum_required( VERSION 2.4 ) + +if( COMMAND cmake_policy ) + cmake_policy( SET CMP0003 NEW ) +endif( COMMAND cmake_policy ) + +include( CheckCXXSourceCompiles ) +include( CheckFunctionExists ) +include( CheckCXXCompilerFlag ) +include( CheckLibraryExists ) +include( FindPkgConfig ) + +if( NOT APPLE ) + option( NO_ASM "Disable assembly code" OFF ) +else( NOT APPLE ) + # At the moment asm code doesn't work with OS X, so disable by default + option( NO_ASM "Disable assembly code" ON ) +endif( NOT APPLE ) +if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + option( NO_STRIP "Do not strip Release or MinSizeRel builds" ) + # At least some versions of Xcode fail if you strip with the linker + # instead of the separate strip utility. + if( APPLE ) + set( NO_STRIP ON ) + endif( APPLE ) +endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + +option( DYN_FLUIDSYNTH "Dynamically load fluidsynth" ON ) + +if( APPLE ) + option( OSX_COCOA_BACKEND "Use native Cocoa backend instead of SDL" ON ) +endif( APPLE ) + +if( CMAKE_SIZEOF_VOID_P MATCHES "8" ) + set( X64 64 ) +endif( CMAKE_SIZEOF_VOID_P MATCHES "8" ) + +# You can either use "make install" on the FMOD distribution to put it +# in standard system locations, or you can unpack the FMOD distribution +# in the root of the zdoom tree. e.g.: +# zdoom +# docs +# fmodapilinux[64] -or simply- fmod +# jpeg-6b +# ... +# The recommended method is to put it in the zdoom tree, since its +# headers are unversioned. Especially now that we can't work properly +# with anything newer than 4.26.xx, you probably don't want to use +# a system-wide version. + +# Construct version numbers for searching for the FMOD library on Linux. +set( MINOR_VERSIONS "50" "49" "48" "47" "46" "45" "44" "43" "42" "41" + "40" "39" "38" "37" "36" "35" "34" "33" "32" "31" "30" "29" "28" + "27" "26" "25" "24" "23" "22" "21" "20" "21" "19" "18" "17" "16" + "15" "14" "13" "12" "11" "10" "09" "08" "07" "06" "05" "04" "03" + "02" "01" "00" ) +set( MAJOR_VERSIONS "44" "34" "28" "26" "24" "22" "20" ) + +if( NOT FMOD_DIR_VERSIONS ) + set( FMOD_DIR_VERSIONS "" ) +endif( NOT FMOD_DIR_VERSIONS ) +if( NOT FMOD_VERSIONS ) + set( FMOD_VERSIONS "" ) +endif( NOT FMOD_VERSIONS ) +if( NOT FMOD_LOCAL_INC_DIRS ) + set( FMOD_LOCAL_INC_DIRS "" ) +endif( NOT FMOD_LOCAL_INC_DIRS ) +if( NOT FMOD_LOCAL_LIB_DIRS ) + set( FMOD_LOCAL_LIB_DIRS "" ) +endif( NOT FMOD_LOCAL_LIB_DIRS ) + +set( FMOD_DIR_VERSIONS ${FMOD_DIR_VERSIONS} "../fmod" ) +foreach( majver ${MAJOR_VERSIONS} ) + foreach( minver ${MINOR_VERSIONS} ) + set( FMOD_VERSIONS ${FMOD_VERSIONS} "fmodex${X64}-4.${majver}.${minver}" ) + set( FMOD_DIR_VERSIONS ${FMOD_DIR_VERSIONS} "${CMAKE_HOME_DIRECTORY}/fmodapi4${majver}${minver}linux${X64}" ) + endforeach( minver ${MINOR_VERSIONS} ) + foreach( dir ${FMOD_DIR_VERSIONS} ) + set( FMOD_LOCAL_INC_DIRS ${FMOD_LOCAL_INC_DIRS} "${dir}/api/inc" ) + set( FMOD_LOCAL_LIB_DIRS ${FMOD_LOCAL_LIB_DIRS} "${dir}/api/lib" ) + endforeach( dir ${FMOD_DIR_VERSIONS} ) +endforeach( majver ${MAJOR_VERSIONS} ) + +if( NOT ZDOOM_LIBS ) + set( ZDOOM_LIBS "" ) +endif( NOT ZDOOM_LIBS ) + +if( WIN32 ) + if( X64 ) + set( WIN_TYPE Win64 ) + set( XBITS x64 ) + else( X64 ) + set( WIN_TYPE Win32 ) + set( XBITS x86 ) + endif( X64 ) + + add_definitions( -D_WIN32 ) + + set( FMOD_SEARCH_PATHS + "C:/Program Files/FMOD SoundSystem/FMOD Programmers API ${WIN_TYPE}/api" + "C:/Program Files (x86)/FMOD SoundSystem/FMOD Programmers API ${WIN_TYPE}/api" + # This next one is for Randy. + "E:/Software/Dev/FMOD/${WIN_TYPE}/api" + # .. and this one for Graf Zahl + "D:/portable/FMOD SoundSystem 4.26/FMOD Programmers API WIN32/api" + ) + set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) + set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) + set( NASM_NAMES nasmw nasm ) + + find_path( D3D_INCLUDE_DIR d3d9.h + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Include ) + if( NOT D3D_INCLUDE_DIR ) + message( SEND_ERROR "Could not find DirectX 9 header files" ) + else( NOT D3D_INCLUDE_DIR ) + include_directories( ${D3D_INCLUDE_DIR} ) + endif( NOT D3D_INCLUDE_DIR ) + + find_path( XINPUT_INCLUDE_DIR xinput.h + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Include ) + if( NOT XINPUT_INCLUDE_DIR ) + message( SEND_ERROR "Could not find xinput.h. XInput will be disabled." ) + add_definitions( -DNO_XINPUT ) + else( NOT XINPUT_INCLUDE_DIR ) + include_directories( ${XINPUT_INCLUDE_DIR} ) + endif( NOT XINPUT_INCLUDE_DIR ) + + find_library( DX_dxguid_LIBRARY dxguid + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Lib Lib/${XBITS} ) + find_library( DX_dinput8_LIBRARY dinput8 + PATHS ENV DXSDK_DIR + PATH_SUFFIXES Lib Lib/${XBITS} ) + + set( DX_LIBS_FOUND YES ) + if( NOT DX_dxguid_LIBRARY ) + set( DX_LIBS_FOUND NO ) + endif( NOT DX_dxguid_LIBRARY ) + if( NOT DX_dinput8_LIBRARY ) + set( DX_LIBS_FOUND NO ) + endif( NOT DX_dinput8_LIBRARY ) + + if( NOT DX_LIBS_FOUND ) + message( FATAL_ERROR "Could not find DirectX 9 libraries" ) + endif( NOT DX_LIBS_FOUND ) + + set( ZDOOM_LIBS + wsock32 + winmm + "${DX_dxguid_LIBRARY}" + "${DX_dinput8_LIBRARY}" + ole32 + user32 + gdi32 + comctl32 + comdlg32 + ws2_32 + setupapi + oleaut32 + DelayImp ) +else( WIN32 ) + if( APPLE ) + set( FMOD_SEARCH_PATHS "/Developer/FMOD Programmers API Mac/api" ) + set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) + set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) + set( NO_GTK ON ) + else( APPLE ) + option( NO_GTK "Disable GTK+ dialogs (Not applicable to Windows)" ) + option( VALGRIND "Add special Valgrind sequences to self-modifying code" ) + + set( FMOD_SEARCH_PATHS + /usr/local/include + /usr/local/include/fmodex + /usr/include + /usr/include/fmodex + /opt/local/include + /opt/local/include/fmodex + /opt/include + /opt/include/fmodex ) + set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES fmodex ) + + # Use GTK+ for the IWAD picker, if available. + if( NOT NO_GTK ) + pkg_check_modules( GTK2 gtk+-2.0 ) + if( GTK2_FOUND ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${GTK2_LIBRARIES} ) + include_directories( ${GTK2_INCLUDE_DIRS} ) + else( GTK2_FOUND ) + set( NO_GTK ON ) + endif( GTK2_FOUND ) + endif( NOT NO_GTK ) + + # Check for Xcursor library and header files + find_library( XCURSOR_LIB Xcursor ) + if( XCURSOR_LIB ) + find_file( XCURSOR_HEADER "X11/Xcursor/Xcursor.h" ) + if( XCURSOR_HEADER ) + add_definitions( -DUSE_XCURSOR=1 ) + message( STATUS "Found Xcursor at ${XCURSOR_LIB}" ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${XCURSOR_LIB} ) + else( XCURSOR_HEADER ) + unset( XCURSOR_LIB ) + endif( XCURSOR_HEADER ) + endif( XCURSOR_LIB ) + endif( APPLE ) + set( NASM_NAMES nasm ) + + if( NO_GTK ) + add_definitions( -DNO_GTK=1 ) + endif( NO_GTK ) + + # Non-Windows version also needs SDL + find_package( SDL ) + if( NOT SDL_FOUND ) + message( SEND_ERROR "SDL is required for building." ) + endif( NOT SDL_FOUND ) + if( NOT APPLE OR NOT OSX_COCOA_BACKEND ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${SDL_LIBRARY}" ) + endif( NOT APPLE OR NOT OSX_COCOA_BACKEND ) + include_directories( "${SDL_INCLUDE_DIR}" ) + + find_path( FPU_CONTROL_DIR fpu_control.h ) + if( FPU_CONTROL_DIR ) + include_directories( ${FPU_CONTROL_DIR} ) + add_definitions( -DHAVE_FPU_CONTROL ) + endif( FPU_CONTROL_DIR ) +endif( WIN32 ) + +# Decide on the name of the FMOD library we want to use. + +if( NOT FMOD_LIB_NAME AND MSVC ) + set( FMOD_LIB_NAME fmodex${X64}_vc ) +endif( NOT FMOD_LIB_NAME AND MSVC ) + +if( NOT FMOD_LIB_NAME AND BORLAND ) + set( FMOD_LIB_NAME fmodex${X64}_bc ) +endif( NOT FMOD_LIB_NAME AND BORLAND ) + +if( NOT FMOD_LIB_NAME ) + set( FMOD_LIB_NAME fmodex${X64} ) +endif( NOT FMOD_LIB_NAME ) + + +# Search for FMOD include files + +if( NOT WIN32 ) + find_path( FMOD_INCLUDE_DIR fmod.hpp + PATHS ${FMOD_LOCAL_INC_DIRS} ) +endif( NOT WIN32 ) + +if( NOT FMOD_INCLUDE_DIR ) + find_path( FMOD_INCLUDE_DIR fmod.hpp + PATHS ${FMOD_SEARCH_PATHS} + ${FMOD_INC_PATH_SUFFIXES} ) +endif( NOT FMOD_INCLUDE_DIR ) + +if( FMOD_INCLUDE_DIR ) + message( STATUS "FMOD include files found at ${FMOD_INCLUDE_DIR}" ) +else( FMOD_INCLUDE_DIR ) + message( SEND_ERROR "Could not find FMOD include files" ) +endif( FMOD_INCLUDE_DIR ) + + +# Search for FMOD library + +if( WIN32 OR APPLE ) + find_library( FMOD_LIBRARY ${FMOD_LIB_NAME} + PATHS ${FMOD_SEARCH_PATHS} + ${FMOD_LIB_PATH_SUFFIXES} ) +else( WIN32 OR APPLE ) + find_library( FMOD_LIBRARY + NAMES ${FMOD_VERSIONS} + PATHS ${FMOD_LOCAL_LIB_DIRS} ) +endif( WIN32 OR APPLE ) + +if( FMOD_LIBRARY ) + message( STATUS "FMOD library found at ${FMOD_LIBRARY}" ) +else( FMOD_LIBRARY ) + message( SEND_ERROR "Could not find FMOD library" ) +endif( FMOD_LIBRARY ) + + +# Search for FluidSynth + +find_package( FluidSynth ) + +# Search for NASM + +if( NOT NO_ASM ) + if( UNIX AND X64 ) + find_program( GAS_PATH as ) + + if( GAS_PATH ) + set( ASSEMBLER ${GAS_PATH} ) + else( GAS_PATH ) + message( STATUS "Could not find as. Disabling assembly code." ) + set( NO_ASM ON ) + endif( GAS_PATH ) + else( UNIX AND X64 ) + find_program( NASM_PATH NAMES ${NASM_NAMES} ) + find_program( YASM_PATH yasm ) + + if( X64 ) + if( YASM_PATH ) + set( ASSEMBLER ${YASM_PATH} ) + else( YASM_PATH ) + message( STATUS "Could not find YASM. Disabling assembly code." ) + set( NO_ASM ON ) + endif( YASM_PATH ) + else( X64 ) + if( NASM_PATH ) + set( ASSEMBLER ${NASM_PATH} ) + else( NASM_PATH ) + message( STATUS "Could not find NASM. Disabling assembly code." ) + set( NO_ASM ON ) + endif( NASM_PATH ) + endif( X64 ) + endif( UNIX AND X64 ) + + # I think the only reason there was a version requirement was because the + # executable name for Windows changed from 0.x to 2.0, right? This is + # how to do it in case I need to do something similar later. + + # execute_process( COMMAND ${NASM_PATH} -v + # OUTPUT_VARIABLE NASM_VER_STRING ) + # string( REGEX REPLACE ".*version ([0-9]+[.][0-9]+).*" "\\1" NASM_VER "${NASM_VER_STRING}" ) + # if( NOT NASM_VER LESS 2 ) + # message( SEND_ERROR "NASM version should be 2 or later. (Installed version is ${NASM_VER}.)" ) + # endif( NOT NASM_VER LESS 2 ) +endif( NOT NO_ASM ) + +if( NOT NO_ASM ) + # Valgrind support is meaningless without assembly code. + if( VALGRIND ) + add_definitions( -DVALGRIND_AWARE=1 ) + # If you're Valgrinding, you probably want to keep symbols around. + set( NO_STRIP ON ) + endif( VALGRIND ) + + # Tell CMake how to assemble our files + if( UNIX ) + set( ASM_OUTPUT_EXTENSION .o ) + if( X64 ) + set( ASM_FLAGS ) + set( ASM_SOURCE_EXTENSION .s ) + else( X64 ) + if( APPLE ) + set( ASM_FLAGS -fmacho -DM_TARGET_MACHO ) + else( APPLE ) + set( ASM_FLAGS -felf -DM_TARGET_LINUX ) + endif( APPLE ) + set( ASM_FLAGS "${ASM_FLAGS}" -i${CMAKE_CURRENT_SOURCE_DIR}/ ) + set( ASM_SOURCE_EXTENSION .asm ) + endif( X64 ) + else( UNIX ) + set( ASM_OUTPUT_EXTENSION .obj ) + set( ASM_SOURCE_EXTENSION .asm ) + if( X64 ) + set( ASM_FLAGS -f win64 -DWIN32 -DWIN64 ) + else( X64 ) + set( ASM_FLAGS -f win32 -DWIN32 -i${CMAKE_CURRENT_SOURCE_DIR}/ ) + endif( X64 ) + endif( UNIX ) + if( WIN32 AND NOT X64 ) + set( FIXRTEXT fixrtext ) + else( WIN32 AND NOT X64 ) + set( FIXRTEXT "" ) + endif( WIN32 AND NOT X64 ) + message( STATUS "Selected assembler: ${ASSEMBLER}" ) + MACRO( ADD_ASM_FILE indir infile ) + set( ASM_OUTPUT_${infile} "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir}/${infile}${ASM_OUTPUT_EXTENSION}" ) + if( WIN32 AND NOT X64 ) + set( FIXRTEXT_${infile} COMMAND ${FIXRTEXT} "${ASM_OUTPUT_${infile}}" ) + else( WIN32 AND NOT X64 ) + set( FIXRTEXT_${infile} COMMAND "" ) + endif( WIN32 AND NOT X64 ) + add_custom_command( OUTPUT ${ASM_OUTPUT_${infile}} + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/zdoom.dir/${indir} + COMMAND ${ASSEMBLER} ${ASM_FLAGS} -o"${ASM_OUTPUT_${infile}}" "${CMAKE_CURRENT_SOURCE_DIR}/${indir}/${infile}${ASM_SOURCE_EXTENSION}" + ${FIXRTEXT_${infile}} + DEPENDS ${indir}/${infile}.asm ${FIXRTEXT} ) + set( ASM_SOURCES ${ASM_SOURCES} "${ASM_OUTPUT_${infile}}" ) + ENDMACRO( ADD_ASM_FILE ) +endif( NOT NO_ASM ) + +# Decide on SSE setup + +set( SSE_MATTERS NO ) + +# with global use of SSE 2 we do not need special handling for selected files +if (NOT ZDOOM_USE_SSE2) + # SSE only matters on 32-bit targets. We check compiler flags to know if we can do it. + if( CMAKE_SIZEOF_VOID_P MATCHES "4" AND NOT CMAKE_OSX_ARCHITECTURES MATCHES ppc ) + CHECK_CXX_COMPILER_FLAG( "-msse2 -mfpmath=sse" CAN_DO_MFPMATH ) + CHECK_CXX_COMPILER_FLAG( -arch:SSE2 CAN_DO_ARCHSSE2 ) + if( CAN_DO_MFPMATH ) + set( SSE1_ENABLE "-msse -mfpmath=sse" ) + set( SSE2_ENABLE "-msse2 -mfpmath=sse" ) + set( SSE_MATTERS YES ) + elseif( CAN_DO_ARCHSSE2 ) + set( SSE1_ENABLE -arch:SSE ) + set( SSE2_ENABLE -arch:SSE2 ) + set( SSE_MATTERS YES ) + endif( CAN_DO_MFPMATH ) + endif( CMAKE_SIZEOF_VOID_P MATCHES "4" AND NOT CMAKE_OSX_ARCHITECTURES MATCHES ppc ) +endif (NOT ZDOOM_USE_SSE2) + +if( SSE_MATTERS ) + if( WIN32 ) + set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) + else( WIN32 ) + CHECK_FUNCTION_EXISTS(mprotect HAVE_MPROTECT) + if( HAVE_MPROTECT ) + set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) + else( HAVE_MPROTECT ) + set( BACKPATCH 0 ) + endif( HAVE_MPROTECT ) + endif( WIN32 ) + set( SSE 1 CACHE BOOL "Build SSE and SSE2 versions of key code." ) +else( SSE_MATTERS ) + set( BACKPATCH 0 ) +endif( SSE_MATTERS ) + +# Set up flags for GCC + +if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + if( PROFILE ) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg" ) + set( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -pg" ) + set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -pg" ) + endif( PROFILE ) + + set( REL_CXX_FLAGS "-fno-rtti" ) + if( NOT PROFILE AND NOT APPLE ) + # On OS X frame pointers are required for exception handling, at least with Clang + set( REL_CXX_FLAGS "${REL_CXX_FLAGS} -fomit-frame-pointer" ) + endif( NOT PROFILE AND NOT APPLE ) + set( CMAKE_CXX_FLAGS_RELEASE "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE}" ) + set( CMAKE_CXX_FLAGS_MINSIZEREL "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_MINSIZEREL}" ) + set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${REL_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" ) + + set( CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused -Wno-unused-parameter -Wno-missing-field-initializers ${CMAKE_CXX_FLAGS}" ) + + # Remove extra warnings when using the official DirectX headers. + # Also, TDM-GCC 4.4.0 no longer accepts glibc-style printf formats as valid, + # which is a royal pain. The previous version I had been using was fine with them. + if( WIN32 ) + set( CMAKE_CXX_FLAGS "-Wno-unknown-pragmas -Wno-comment -Wno-format ${CMAKE_CXX_FLAGS}" ) + endif( WIN32 ) + + if( NOT NO_STRIP ) + set (CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s" ) + set (CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} -s" ) + endif( NOT NO_STRIP ) +endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + +# Check for functions that may or may not exist. + +CHECK_FUNCTION_EXISTS( filelength FILELENGTH_EXISTS ) +if( FILELENGTH_EXISTS ) + add_definitions( -DHAVE_FILELENGTH=1 ) +endif( FILELENGTH_EXISTS ) + +CHECK_FUNCTION_EXISTS( strupr STRUPR_EXISTS ) +if( NOT STRUPR_EXISTS ) + add_definitions( -DNEED_STRUPR=1 ) +endif( NOT STRUPR_EXISTS ) + +CHECK_FUNCTION_EXISTS( stricmp STRICMP_EXISTS ) +if( NOT STRICMP_EXISTS ) + add_definitions( -Dstricmp=strcasecmp ) +endif( NOT STRICMP_EXISTS ) + +CHECK_FUNCTION_EXISTS( strnicmp STRNICMP_EXISTS ) +if( NOT STRNICMP_EXISTS ) + add_definitions( -Dstrnicmp=strncasecmp ) +endif( NOT STRNICMP_EXISTS ) + +if( NOT MSVC ) + add_definitions( -D__forceinline=inline ) +endif( NOT MSVC ) + +if( UNIX ) + CHECK_LIBRARY_EXISTS( rt clock_gettime "" CLOCK_GETTIME_IN_RT ) + if( NOT CLOCK_GETTIME_IN_RT ) + CHECK_FUNCTION_EXISTS( clock_gettime CLOCK_GETTIME_EXISTS ) + if( NOT CLOCK_GETTIME_EXISTS ) + message( STATUS "Could not find clock_gettime. Timing statistics will not be available." ) + add_definitions( -DNO_CLOCK_GETTIME ) + endif( NOT CLOCK_GETTIME_EXISTS ) + else( NOT CLOCK_GETTIME_IN_RT ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} rt ) + endif( NOT CLOCK_GETTIME_IN_RT ) +endif( UNIX ) + +CHECK_CXX_SOURCE_COMPILES( + "#include + int main() { va_list list1, list2; va_copy(list1, list2); return 0; }" + HAS_VA_COPY ) +if( NOT HAS_VA_COPY ) + CHECK_CXX_SOURCE_COMPILES( + "#include + int main() { va_list list1, list2; __va_copy(list1, list2); return 0; }" + HAS___VA_COPY ) + if( HAS___VA_COPY ) + add_definitions( -Dva_copy=__va_copy ) + else( HAS___VA_COPY ) + add_definitions( -DNO_VA_COPY ) + endif( HAS___VA_COPY ) +endif( NOT HAS_VA_COPY ) + +# Flags + +if( BACKPATCH ) + add_definitions( -DBACKPATCH ) +endif( BACKPATCH ) + +# Update gitinfo.h + +get_target_property( UPDATEREVISION_EXE updaterevision LOCATION ) + +add_custom_target( revision_check ALL + COMMAND ${UPDATEREVISION_EXE} src/gitinfo.h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS updaterevision ) + +# Libraries ZDoom needs + +message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) +set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${GME_LIBRARIES}" "${FMOD_LIBRARY}" ) +include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${GME_INCLUDE_DIR}" ) + +if( NOT DYN_FLUIDSYNTH) + if( FLUIDSYNTH_FOUND ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" ) + include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) + endif( FLUIDSYNTH_FOUND ) +else( NOT DYN_FLUIDSYNTH ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${CMAKE_DL_LIBS} ) +endif( NOT DYN_FLUIDSYNTH ) + +# Start defining source files for ZDoom +set( PLAT_WIN32_SOURCES + win32/eaxedit.cpp + win32/fb_d3d9.cpp + win32/fb_d3d9_wipe.cpp + win32/fb_ddraw.cpp + win32/hardware.cpp + win32/helperthread.cpp + win32/i_cd.cpp + win32/i_crash.cpp + win32/i_input.cpp + win32/i_keyboard.cpp + win32/i_mouse.cpp + win32/i_dijoy.cpp + win32/i_rawps2.cpp + win32/i_xinput.cpp + win32/i_main.cpp + win32/i_movie.cpp + win32/i_system.cpp + win32/st_start.cpp + win32/win32video.cpp ) +set( PLAT_SDL_SYSTEM_SOURCES + sdl/crashcatcher.c + sdl/hardware.cpp + sdl/i_cd.cpp + sdl/i_main.cpp + sdl/i_movie.cpp + sdl/i_steam.cpp + sdl/i_system.cpp + sdl/sdlvideo.cpp + sdl/st_start.cpp ) +set( PLAT_SDL_SPECIAL_SOURCES + sdl/i_gui.cpp + sdl/i_input.cpp + sdl/i_joystick.cpp + sdl/i_timer.cpp ) +set( PLAT_MAC_SOURCES + sdl/iwadpicker_cocoa.mm + sdl/i_system_cocoa.mm ) +set( PLAT_COCOA_SOURCES + cocoa/HID_Config_Utilities.c + cocoa/HID_Error_Handler.c + cocoa/HID_Name_Lookup.c + cocoa/HID_Queue_Utilities.c + cocoa/HID_Utilities.c + cocoa/IOHIDDevice_.c + cocoa/IOHIDElement_.c + cocoa/ImmrHIDUtilAddOn.c + cocoa/i_backend_cocoa.mm + cocoa/i_joystick.cpp + cocoa/i_timer.cpp + cocoa/zdoom.icns ) + +if( APPLE ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} "${FMOD_LIBRARY}" ) + + if( OSX_COCOA_BACKEND ) + set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} ${PLAT_COCOA_SOURCES} ) + else( OSX_COCOA_BACKEND ) + set( PLAT_MAC_SOURCES ${PLAT_MAC_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} sdl/SDLMain.m ) + endif( OSX_COCOA_BACKEND ) + + set_source_files_properties( cocoa/zdoom.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) + set_source_files_properties( "${FMOD_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks ) +else( APPLE ) + set( PLAT_SDL_SOURCES ${PLAT_SDL_SYSTEM_SOURCES} ${PLAT_SDL_SPECIAL_SOURCES} ) +endif( APPLE ) + +if( WIN32 ) + set( SYSTEM_SOURCES_DIR win32 ) + set( SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ${PLAT_MAC_SOURCES} ) + + if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + # CMake is not set up to compile and link rc files with GCC. :( + add_custom_command( OUTPUT zdoom-rc.o + COMMAND windres -o zdoom-rc.o -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zdoom.rc + DEPENDS win32/zdoom.rc ) + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} zdoom-rc.o ) + else( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} win32/zdoom.rc ) + endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) +else( WIN32 ) + set( SYSTEM_SOURCES_DIR sdl ) + set( SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ) + if( APPLE ) + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} ${PLAT_MAC_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) + else( APPLE ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_MAC_SOURCES} ) + endif( APPLE ) +endif( WIN32 ) + +if( NOT ASM_SOURCES ) + set( ASM_SOURCES "" ) +endif( NOT ASM_SOURCES ) + +if( NO_ASM ) + add_definitions( -DNOASM ) +else( NO_ASM ) + if( X64 ) + ADD_ASM_FILE( asm_x86_64 tmap3 ) + else( X64 ) + ADD_ASM_FILE( asm_ia32 a ) + ADD_ASM_FILE( asm_ia32 misc ) + ADD_ASM_FILE( asm_ia32 tmap ) + ADD_ASM_FILE( asm_ia32 tmap2 ) + ADD_ASM_FILE( asm_ia32 tmap3 ) + endif( X64 ) +endif( NO_ASM ) + +get_target_property( LEMON_EXE lemon LOCATION ) +get_target_property( RE2C_EXE re2c LOCATION ) + +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.h + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y . + COMMAND ${LEMON_EXE} xlat_parser.y + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/xlat/xlat_parser.y ) + +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.lemon ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.h + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/zscript/zcc-parse.lemon . + COMMAND ${LEMON_EXE} zcc-parse.lemon + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/zscript/zcc-parse.lemon ) + +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h + COMMAND ${RE2C_EXE} --no-generation-date -s -o ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re + DEPENDS re2c ${CMAKE_CURRENT_SOURCE_DIR}/sc_man_scanner.re ) + +include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + +if( SSE_MATTERS ) + if( SSE ) + set( X86_SOURCES nodebuild_classify_sse2.cpp ) + set_source_files_properties( nodebuild_classify_sse2.cpp PROPERTIES COMPILE_FLAGS "${SSE2_ENABLE}" ) + else( SSE ) + add_definitions( -DDISABLE_SSE ) + endif( SSE ) +else( SSE_MATTERS ) + add_definitions( -DDISABLE_SSE ) + set( X86_SOURCES ) +endif( SSE_MATTERS ) + +if( DYN_FLUIDSYNTH ) + add_definitions( -DHAVE_FLUIDSYNTH -DDYN_FLUIDSYNTH ) +elseif( FLUIDSYNTH_FOUND ) + add_definitions( -DHAVE_FLUIDSYNTH ) +endif( DYN_FLUIDSYNTH ) + +# Project files should be aware of the header files. We can GLOB these since +# there's generally a new cpp for every header so this file will get changed +if( WIN32 ) + set( EXTRA_HEADER_DIRS win32/*.h ) +else( WIN32 ) + set( EXTRA_HEADER_DIRS sdl/*.h ) +endif( WIN32 ) +file( GLOB HEADER_FILES + ${EXTRA_HEADER_DIRS} + fragglescript/*.h + g_doom/*.h + g_heretic/*.h + g_hexen/*.h + g_raven/*.h + g_shared/*.h + g_strife/*.h + intermission/*.h + menu/*.h + oplsynth/*.h + oplsynth/dosbox/*.h + r_data/*.h + resourcefiles/*.h + sdl/*.h + sfmt/*.h + sound/*.h + textures/*.h + thingdef/*.h + xlat/*.h + zscript/*.h + *.h +) + +# These files will be flagged as "headers" so that they appear in project files +# without being compiled. +set( NOT_COMPILED_SOURCE_FILES + ${OTHER_SYSTEM_SOURCES} + sc_man_scanner.h + sc_man_scanner.re + g_doom/a_arachnotron.cpp + g_doom/a_archvile.cpp + g_doom/a_bossbrain.cpp + g_doom/a_bruiser.cpp + g_doom/a_cacodemon.cpp + g_doom/a_cyberdemon.cpp + g_doom/a_demon.cpp + g_doom/a_doomimp.cpp + g_doom/a_doomweaps.cpp + g_doom/a_fatso.cpp + g_doom/a_keen.cpp + g_doom/a_lostsoul.cpp + g_doom/a_painelemental.cpp + g_doom/a_possessed.cpp + g_doom/a_revenant.cpp + g_doom/a_scriptedmarine.cpp + g_doom/a_spidermaster.cpp + g_heretic/a_chicken.cpp + g_heretic/a_dsparil.cpp + g_heretic/a_hereticartifacts.cpp + g_heretic/a_hereticimp.cpp + g_heretic/a_hereticweaps.cpp + g_heretic/a_ironlich.cpp + g_heretic/a_knight.cpp + g_heretic/a_wizard.cpp + g_hexen/a_bats.cpp + g_hexen/a_bishop.cpp + g_hexen/a_blastradius.cpp + g_hexen/a_boostarmor.cpp + g_hexen/a_centaur.cpp + g_hexen/a_clericflame.cpp + g_hexen/a_clericholy.cpp + g_hexen/a_clericmace.cpp + g_hexen/a_clericstaff.cpp + g_hexen/a_dragon.cpp + g_hexen/a_fighteraxe.cpp + g_hexen/a_fighterhammer.cpp + g_hexen/a_fighterplayer.cpp + g_hexen/a_fighterquietus.cpp + g_hexen/a_firedemon.cpp + g_hexen/a_flechette.cpp + g_hexen/a_fog.cpp + g_hexen/a_healingradius.cpp + g_hexen/a_heresiarch.cpp + g_hexen/a_hexenspecialdecs.cpp + g_hexen/a_iceguy.cpp + g_hexen/a_korax.cpp + g_hexen/a_magecone.cpp + g_hexen/a_magelightning.cpp + g_hexen/a_magestaff.cpp + g_hexen/a_pig.cpp + g_hexen/a_serpent.cpp + g_hexen/a_spike.cpp + g_hexen/a_summon.cpp + g_hexen/a_teleportother.cpp + g_hexen/a_wraith.cpp + g_strife/a_acolyte.cpp + g_strife/a_alienspectres.cpp + g_strife/a_coin.cpp + g_strife/a_crusader.cpp + g_strife/a_entityboss.cpp + g_strife/a_inquisitor.cpp + g_strife/a_oracle.cpp + g_strife/a_programmer.cpp + g_strife/a_reaver.cpp + g_strife/a_rebels.cpp + g_strife/a_sentinel.cpp + g_strife/a_stalker.cpp + g_strife/a_strifeitems.cpp + g_strife/a_strifeweapons.cpp + g_strife/a_templar.cpp + g_strife/a_thingstoblowup.cpp + g_shared/sbarinfo_commands.cpp + xlat/xlat_parser.y + xlat_parser.c + xlat_parser.h + zscript/zcc-parse.lemon + zcc-parse.c + zcc-parse.h + + # We could have the ASM macro add these files, but it wouldn't add all + # platforms. + asm_ia32/a.asm + asm_ia32/misc.asm + asm_ia32/tmap.asm + asm_ia32/tmap2.asm + asm_ia32/tmap3.asm + asm_x86_64/tmap3.asm + asm_x86_64/tmap3.s +) + +add_executable( zdoom WIN32 MACOSX_BUNDLE + ${HEADER_FILES} + ${NOT_COMPILED_SOURCE_FILES} + __autostart.cpp + ${ASM_SOURCES} + ${SYSTEM_SOURCES} + ${X86_SOURCES} + x86.cpp + actorptrselect.cpp + am_map.cpp + b_bot.cpp + b_func.cpp + b_game.cpp + b_move.cpp + b_think.cpp + bbannouncer.cpp + c_bind.cpp + c_cmds.cpp + c_console.cpp + c_consolebuffer.cpp + c_cvars.cpp + c_dispatch.cpp + c_expr.cpp + cmdlib.cpp + colormatcher.cpp + compatibility.cpp + configfile.cpp + ct_chat.cpp + d_dehacked.cpp + d_iwad.cpp + d_main.cpp + d_net.cpp + d_netinfo.cpp + d_protocol.cpp + decallib.cpp + dobject.cpp + dobjgc.cpp + dobjtype.cpp + doomdef.cpp + doomstat.cpp + dsectoreffect.cpp + dthinker.cpp + f_wipe.cpp + farchive.cpp + files.cpp + g_game.cpp + g_hub.cpp + g_level.cpp + g_mapinfo.cpp + g_skill.cpp + gameconfigfile.cpp + gi.cpp + gitinfo.cpp + hu_scores.cpp + i_net.cpp + info.cpp + keysections.cpp + lumpconfigfile.cpp + m_alloc.cpp + m_argv.cpp + m_bbox.cpp + m_cheat.cpp + m_joy.cpp + m_misc.cpp + m_png.cpp + m_random.cpp + m_specialpaths.cpp + memarena.cpp + md5.cpp + name.cpp + nodebuild.cpp + nodebuild_classify_nosse2.cpp + nodebuild_events.cpp + nodebuild_extract.cpp + nodebuild_gl.cpp + nodebuild_utility.cpp + p_3dfloors.cpp + p_3dmidtex.cpp + p_acs.cpp + p_buildmap.cpp + p_ceiling.cpp + p_conversation.cpp + p_doors.cpp + p_effect.cpp + p_enemy.cpp + p_floor.cpp + p_glnodes.cpp + p_interaction.cpp + p_lights.cpp + p_linkedsectors.cpp + p_lnspec.cpp + p_map.cpp + p_maputl.cpp + p_mobj.cpp + p_pillar.cpp + p_plats.cpp + p_pspr.cpp + p_saveg.cpp + p_sectors.cpp + p_setup.cpp + p_sight.cpp + p_slopes.cpp + p_spec.cpp + p_states.cpp + p_switch.cpp + p_teleport.cpp + p_terrain.cpp + p_things.cpp + p_tick.cpp + p_trace.cpp + p_udmf.cpp + p_usdf.cpp + p_user.cpp + p_writemap.cpp + p_xlat.cpp + parsecontext.cpp + po_man.cpp + r_swrenderer.cpp + r_utility.cpp + r_3dfloors.cpp + r_bsp.cpp + r_draw.cpp + r_drawt.cpp + r_main.cpp + r_plane.cpp + r_segs.cpp + r_sky.cpp + r_things.cpp + s_advsound.cpp + s_environment.cpp + s_playlist.cpp + s_sndseq.cpp + s_sound.cpp + sc_man.cpp + st_stuff.cpp + statistics.cpp + stats.cpp + stringtable.cpp + strnatcmp.c + tables.cpp + teaminfo.cpp + tempfiles.cpp + v_blend.cpp + v_collection.cpp + v_draw.cpp + v_font.cpp + v_palette.cpp + v_pfx.cpp + v_text.cpp + v_video.cpp + w_wad.cpp + wi_stuff.cpp + zstrformat.cpp + zstring.cpp + g_doom/a_doommisc.cpp + g_heretic/a_hereticmisc.cpp + g_hexen/a_hexenmisc.cpp + g_raven/a_artitele.cpp + g_raven/a_minotaur.cpp + g_strife/a_strifestuff.cpp + g_strife/strife_sbar.cpp + g_shared/a_action.cpp + g_shared/a_armor.cpp + g_shared/a_artifacts.cpp + g_shared/a_bridge.cpp + g_shared/a_camera.cpp + g_shared/a_debris.cpp + g_shared/a_decals.cpp + g_shared/a_fastprojectile.cpp + g_shared/a_flashfader.cpp + g_shared/a_fountain.cpp + g_shared/a_hatetarget.cpp + g_shared/a_keys.cpp + g_shared/a_lightning.cpp + g_shared/a_mapmarker.cpp + g_shared/a_morph.cpp + g_shared/a_movingcamera.cpp + g_shared/a_pickups.cpp + g_shared/a_puzzleitems.cpp + g_shared/a_quake.cpp + g_shared/a_randomspawner.cpp + g_shared/a_secrettrigger.cpp + g_shared/a_sectoraction.cpp + g_shared/a_setcolor.cpp + g_shared/a_skies.cpp + g_shared/a_soundenvironment.cpp + g_shared/a_soundsequence.cpp + g_shared/a_spark.cpp + g_shared/a_specialspot.cpp + g_shared/a_waterzone.cpp + g_shared/a_weaponpiece.cpp + g_shared/a_weapons.cpp + g_shared/hudmessages.cpp + g_shared/sbarinfo.cpp + g_shared/sbar_mugshot.cpp + g_shared/shared_hud.cpp + g_shared/shared_sbar.cpp + intermission/intermission.cpp + intermission/intermission_parse.cpp + menu/colorpickermenu.cpp + menu/joystickmenu.cpp + menu/listmenu.cpp + menu/loadsavemenu.cpp + menu/menu.cpp + menu/menudef.cpp + menu/menuinput.cpp + menu/messagebox.cpp + menu/optionmenu.cpp + menu/playerdisplay.cpp + menu/playermenu.cpp + menu/readthis.cpp + menu/videomenu.cpp + oplsynth/fmopl.cpp + oplsynth/mlopl.cpp + oplsynth/mlopl_io.cpp + oplsynth/music_opldumper_mididevice.cpp + oplsynth/music_opl_mididevice.cpp + oplsynth/opl_mus_player.cpp + oplsynth/dosbox/opl.cpp + oplsynth/OPL3.cpp + oplsynth/nukedopl3.cpp + resourcefiles/ancientzip.cpp + resourcefiles/file_7z.cpp + resourcefiles/file_grp.cpp + resourcefiles/file_lump.cpp + resourcefiles/file_rff.cpp + resourcefiles/file_wad.cpp + resourcefiles/file_zip.cpp + resourcefiles/file_pak.cpp + resourcefiles/file_directory.cpp + resourcefiles/resourcefile.cpp + sfmt/SFMT.cpp + sound/fmodsound.cpp + sound/i_music.cpp + sound/i_sound.cpp + sound/music_cd.cpp + sound/music_dumb.cpp + sound/music_gme.cpp + sound/music_mus_midiout.cpp + sound/music_smf_midiout.cpp + sound/music_hmi_midiout.cpp + sound/music_xmi_midiout.cpp + sound/music_midistream.cpp + sound/music_midi_base.cpp + sound/music_midi_timidity.cpp + sound/music_mus_opl.cpp + sound/music_stream.cpp + sound/music_fluidsynth_mididevice.cpp + sound/music_softsynth_mididevice.cpp + sound/music_timidity_mididevice.cpp + sound/music_win_mididevice.cpp + sound/music_pseudo_mididevice.cpp + textures/animations.cpp + textures/anim_switches.cpp + textures/automaptexture.cpp + textures/bitmap.cpp + textures/buildtexture.cpp + textures/canvastexture.cpp + textures/ddstexture.cpp + textures/flattexture.cpp + textures/imgztexture.cpp + textures/jpegtexture.cpp + textures/multipatchtexture.cpp + textures/patchtexture.cpp + textures/pcxtexture.cpp + textures/pngtexture.cpp + textures/rawpagetexture.cpp + textures/emptytexture.cpp + textures/texture.cpp + textures/texturemanager.cpp + textures/tgatexture.cpp + textures/warptexture.cpp + thingdef/olddecorations.cpp + thingdef/thingdef.cpp + thingdef/thingdef_codeptr.cpp + thingdef/thingdef_data.cpp + thingdef/thingdef_exp.cpp + thingdef/thingdef_expression.cpp + thingdef/thingdef_function.cpp + thingdef/thingdef_parse.cpp + thingdef/thingdef_properties.cpp + thingdef/thingdef_states.cpp + timidity/common.cpp + timidity/instrum.cpp + timidity/instrum_dls.cpp + timidity/instrum_font.cpp + timidity/instrum_sf2.cpp + timidity/mix.cpp + timidity/playmidi.cpp + timidity/resample.cpp + timidity/timidity.cpp + xlat/parse_xlat.cpp + fragglescript/t_fspic.cpp + fragglescript/t_func.cpp + fragglescript/t_load.cpp + fragglescript/t_oper.cpp + fragglescript/t_parse.cpp + fragglescript/t_prepro.cpp + fragglescript/t_script.cpp + fragglescript/t_spec.cpp + fragglescript/t_variable.cpp + fragglescript/t_cmd.cpp + r_data/colormaps.cpp + r_data/sprites.cpp + r_data/voxels.cpp + r_data/renderstyle.cpp + r_data/r_interpolate.cpp + r_data/r_translate.cpp + zscript/ast.cpp + zscript/vmbuilder.cpp + zscript/vmdisasm.cpp + zscript/vmexec.cpp + zscript/vmframe.cpp + zscript/zcc_compile.cpp + zscript/zcc_expr.cpp + zscript/zcc_parser.cpp + zzautozend.cpp +) + +set_source_files_properties( xlat/parse_xlat.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c" ) +set_source_files_properties( sc_man.cpp PROPERTIES OBJECT_DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h" ) +set_source_files_properties( ${NOT_COMPILED_SOURCE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE ) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") + # [BL] Solaris requires these to be explicitly linked. + set( ZDOOM_LIBS ${ZDOOM_LIBS} nsl socket) +endif(${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") + +target_link_libraries( zdoom ${ZDOOM_LIBS} gdtoa dumb lzma ) +include_directories( . + g_doom + g_heretic + g_hexen + g_raven + g_strife + g_shared + oplsynth + sound + textures + thingdef + timidity + xlat + zscript + ../gdtoa + ../dumb/include + ${CMAKE_BINARY_DIR}/gdtoa + ${SYSTEM_SOURCES_DIR} ) + +add_dependencies( zdoom revision_check ) + +# Due to some quirks, we need to do this in this order +if( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) + # RUNTIME_OUTPUT_DIRECTORY does not exist in CMake 2.4. + # Linux distributions are slow to adopt 2.6. :( + set_target_properties( zdoom PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ZDOOM_OUTPUT_DIR} ) + set_target_properties( zdoom PROPERTIES OUTPUT_NAME ${ZDOOM_EXE_NAME} ) +else( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) + set_target_properties( zdoom PROPERTIES + RUNTIME_OUTPUT_NAME ${ZDOOM_EXE_NAME} + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ZDOOM_OUTPUT_DIR} + RUNTIME_OUTPUT_NAME_DEBUG ${ZDOOM_EXE_NAME}d + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ZDOOM_OUTPUT_DIR} + RUNTIME_OUTPUT_NAME_MINSIZEREL ${ZDOOM_EXE_NAME}msr + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${ZDOOM_OUTPUT_DIR} + RUNTIME_OUTPUT_NAME_RELWITHDEBINFO ${ZDOOM_EXE_NAME}rd + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ZDOOM_OUTPUT_DIR} + ) +endif( NOT ZDOOM_OUTPUT_OLDSTYLE OR NO_GENERATOR_EXPRESSIONS ) + +if( MSVC ) + option( ZDOOM_GENERATE_MAPFILE "Generate .map file for debugging." OFF ) + if( ZDOOM_GENERATE_MAPFILE ) + set_target_properties(zdoom PROPERTIES LINK_FLAGS "/MANIFEST:NO /DELAYLOAD:\"fmodex${X64}.dll\" /MAP") + else( ZDOOM_GENERATE_MAPFILE ) + set_target_properties(zdoom PROPERTIES LINK_FLAGS "/MANIFEST:NO /DELAYLOAD:\"fmodex${X64}.dll\"") + endif( ZDOOM_GENERATE_MAPFILE ) + + add_custom_command(TARGET zdoom POST_BUILD + COMMAND "mt.exe" -manifest \"${CMAKE_CURRENT_SOURCE_DIR}\\win32\\zdoom.exe.manifest\" -outputresource:\"$(TargetDir)$(TargetFileName)\"\;\#1 + COMMENT "Adding manifest..." + ) + + create_default_target_launcher( zdoom WORKING_DIRECTORY ${ZDOOM_OUTPUT_DIR} ) +endif( MSVC ) + +if( NOT WIN32 ) + FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME} ]; then ln -sf ${CMAKE_CURRENT_BINARY_DIR}/${ZDOOM_EXE_NAME} ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME}; fi" ) + add_custom_command( TARGET zdoom POST_BUILD + COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make + COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make ) +endif( NOT WIN32 ) +if( CMAKE_COMPILER_IS_GNUCXX ) + # GCC misoptimizes this file + set_source_files_properties( oplsynth/fmopl.cpp PROPERTIES COMPILE_FLAGS "-fno-tree-dominator-opts -fno-tree-fre" ) +endif( CMAKE_COMPILER_IS_GNUCXX ) +if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + # Need to enable intrinsics for this file. + if( SSE_MATTERS ) + set_source_files_properties( x86.cpp PROPERTIES COMPILE_FLAGS "-msse2 -mmmx" ) + endif( SSE_MATTERS ) +endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + +if( APPLE ) + set_target_properties(zdoom PROPERTIES + LINK_FLAGS "-framework Carbon -framework Cocoa -framework IOKit -framework OpenGL" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cocoa/zdoom-info.plist" ) + + # Fix fmod link so that it can be found in the app bundle. + find_program( OTOOL otool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) + find_program( INSTALL_NAME_TOOL install_name_tool HINTS "/usr/bin" "${OSX_DEVELOPER_ROOT}/usr/bin" ) + execute_process( COMMAND "${OTOOL}" -L "${FMOD_LIBRARY}" + COMMAND grep "libfmodex.dylib (compat" + COMMAND head -n1 + COMMAND awk "{print $1}" + OUTPUT_VARIABLE FMOD_LINK + OUTPUT_STRIP_TRAILING_WHITESPACE ) + add_custom_command( TARGET zdoom POST_BUILD + COMMAND "${INSTALL_NAME_TOOL}" -change "${FMOD_LINK}" @executable_path/../Frameworks/libfmodex.dylib "$" + COMMENT "Relinking FMOD Ex" ) +endif( APPLE ) + +source_group("Assembly Files\\ia32" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_ia32/.+") +source_group("Assembly Files\\x86_64" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/asm_x86_64/.+") +source_group("Audio Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sound/.+") +source_group("Audio Files\\OPL Synth" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/oplsynth/.+") +source_group("Audio Files\\OPL Synth\\DOSBox" FILES oplsynth/dosbox/opl.cpp oplsynth/dosbox/opl.h) +source_group("Audio Files\\Timidity\\Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.h$") +source_group("Audio Files\\Timidity\\Source" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/timidity/.+\\.cpp$") +source_group("Cocoa Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/cocoa/.+") +source_group("Decorate++" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/thingdef/.+") +source_group("FraggleScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/fragglescript/.+") +source_group("Games\\Doom Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_doom/.+") +source_group("Games\\Heretic Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_heretic/.+") +source_group("Games\\Hexen Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_hexen/.+") +source_group("Games\\Raven Shared" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_raven/.+") +source_group("Games\\Strife Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_strife/.+") +source_group("Intermission" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/intermission/.+") +source_group("Menu" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/menu/.+") +source_group("Render Core\\Render Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_.+\\.h$") +source_group("Render Core\\Render Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_.+\\.cpp$") +source_group("Render Data\\Resource Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_data/.+\\.h$") +source_group("Render Data\\Resource Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/r_data/.+\\.cpp$") +source_group("Render Data\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/textures/.+") +source_group("Render Interface" FILES r_defs.h r_renderer.h r_sky.cpp r_sky.h r_state.h r_utility.cpp r_utility.h) +source_group("Resource Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/resourcefiles/.+") +source_group("SDL Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sdl/.+") +source_group("SFMT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/sfmt/.+") +source_group("Shared Game" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/g_shared/.+") +source_group("Versioning" FILES version.h win32/zdoom.rc) +source_group("Win32 Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/win32/.+") +source_group("Xlat" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/xlat/.+" FILES ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.c ${CMAKE_CURRENT_BINARY_DIR}/xlat_parser.h) +source_group("ZScript" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/zscript/.+") +source_group("Source Files" FILES ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h sc_man_scanner.re) diff --git a/src/actionspecials.h b/src/actionspecials.h index 1377def82..752ef77eb 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -102,6 +102,7 @@ DEFINE_SPECIAL(Scroll_Texture_Left, 100, -1, -1, 2) DEFINE_SPECIAL(Scroll_Texture_Right, 101, -1, -1, 2) DEFINE_SPECIAL(Scroll_Texture_Up, 102, -1, -1, 2) DEFINE_SPECIAL(Scroll_Texture_Down, 103, -1, -1, 2) +DEFINE_SPECIAL(Ceiling_CrushAndRaiseSilentDist, 104, 3, 5, 5) DEFINE_SPECIAL(Light_ForceLightning, 109, 1, 1, 1) DEFINE_SPECIAL(Light_RaiseByValue, 110, 2, 2, 2) diff --git a/src/actor.h b/src/actor.h index 555743f8d..5e8de2ea2 100644 --- a/src/actor.h +++ b/src/actor.h @@ -343,6 +343,20 @@ enum MF7_ALWAYSTELEFRAG = 0x00000004, // will unconditionally be telefragged when in the way. Overrides all other settings. MF7_HANDLENODELAY = 0x00000008, // respect NoDelay state flag MF7_WEAPONSPAWN = 0x00000010, // subject to DF_NO_COOP_WEAPON_SPAWN dmflag + MF7_HARMFRIENDS = 0x00000020, // is allowed to harm friendly monsters. + MF7_BUDDHA = 0x00000040, // Behaves just like the buddha cheat. + MF7_FOILBUDDHA = 0x00000080, // Similar to FOILINVUL, foils buddha mode. + MF7_DONTTHRUST = 0x00000100, // Thrusting functions do not take, and do not give thrust (damage) to actors with this flag. + MF7_ALLOWPAIN = 0x00000200, // Invulnerable or immune (via damagefactors) actors can still react to taking damage even if they don't. + MF7_CAUSEPAIN = 0x00000400, // Damage sources with this flag can cause similar effects like ALLOWPAIN. + MF7_THRUREFLECT = 0x00000800, // Actors who are reflective cause the missiles to not slow down or change angles. + MF7_MIRRORREFLECT = 0x00001000, // Actor is turned directly 180 degrees around when reflected. + MF7_AIMREFLECT = 0x00002000, // Actor is directly reflected straight back at the one who fired the projectile. + MF7_HITTARGET = 0x00004000, // The actor the projectile dies on is set to target, provided it's targetable anyway. + MF7_HITMASTER = 0x00008000, // Same as HITTARGET, except it's master instead of target. + MF7_HITTRACER = 0x00010000, // Same as HITTARGET, but for tracer. + + // --- mobj.renderflags --- @@ -679,6 +693,9 @@ public: // Transforms the actor into a finely-ground paste virtual bool Grind(bool items); + // Get this actor's team + int GetTeam(); + // Is the other actor on my team? bool IsTeammate (AActor *other); @@ -820,13 +837,14 @@ public: DWORD flags4; // [RH] Even more flags! DWORD flags5; // OMG! We need another one. DWORD flags6; // Shit! Where did all the flags go? - DWORD flags7; // + DWORD flags7; // WHO WANTS TO BET ON 8!? // [BB] If 0, everybody can see the actor, if > 0, only members of team (VisibleToTeam-1) can see it. DWORD VisibleToTeam; int special1; // Special info int special2; // Special info + int weaponspecial; // Special info for weapons. int health; BYTE movedir; // 0-7 SBYTE visdir; @@ -906,9 +924,6 @@ public: TObjPtr Inventory; // [RH] This actor's inventory DWORD InventoryID; // A unique ID to keep track of inventory items - //Added by MC: - SDWORD id; // Player ID (for items, # in list.) - BYTE smokecounter; BYTE FloatBobPhase; BYTE FriendPlayer; // [RH] Player # + 1 this friendly monster works for (so 0 is no player, 1 is player 0, etc) @@ -934,9 +949,12 @@ public: FNameNoInit DamageType; FNameNoInit DamageTypeReceived; fixed_t DamageFactor; + fixed_t DamageMultiply; FNameNoInit PainType; FNameNoInit DeathType; + PClassActor *TeleFogSourceType; + PClassActor *TeleFogDestType; FState *SpawnState; FState *SeeState; diff --git a/src/actorptrselect.cpp b/src/actorptrselect.cpp index f1f50eeb5..774067550 100644 --- a/src/actorptrselect.cpp +++ b/src/actorptrselect.cpp @@ -56,6 +56,13 @@ AActor *COPY_AAPTR(AActor *origin, int selector) case AAPTR_TRACER: return origin->tracer; case AAPTR_FRIENDPLAYER: return origin->FriendPlayer ? AAPTR_RESOLVE_PLAYERNUM(origin->FriendPlayer - 1) : NULL; + + case AAPTR_GET_LINETARGET: + { + AActor *gettarget = NULL; + P_BulletSlope(origin, &gettarget); + return gettarget; + } } } diff --git a/src/actorptrselect.h b/src/actorptrselect.h index ef19b05e7..d5dfd5ffc 100644 --- a/src/actorptrselect.h +++ b/src/actorptrselect.h @@ -36,12 +36,13 @@ enum AAPTR AAPTR_PLAYER8 = 0x2000, AAPTR_FRIENDPLAYER = 0x4000, + AAPTR_GET_LINETARGET = 0x8000, AAPTR_PLAYER_SELECTORS = AAPTR_PLAYER_GETTARGET|AAPTR_PLAYER_GETCONVERSATION, AAPTR_GENERAL_SELECTORS = - AAPTR_TARGET|AAPTR_MASTER|AAPTR_TRACER|AAPTR_FRIENDPLAYER, + AAPTR_TARGET|AAPTR_MASTER|AAPTR_TRACER|AAPTR_FRIENDPLAYER|AAPTR_GET_LINETARGET, AAPTR_STATIC_SELECTORS = AAPTR_PLAYER1|AAPTR_PLAYER2|AAPTR_PLAYER3|AAPTR_PLAYER4| diff --git a/src/am_map.cpp b/src/am_map.cpp index 904593dfb..29cd60793 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -908,8 +908,8 @@ void AM_StaticInit() if (gameinfo.mMapArrow.IsNotEmpty()) AM_ParseArrow(MapArrow, gameinfo.mMapArrow); if (gameinfo.mCheatMapArrow.IsNotEmpty()) AM_ParseArrow(CheatMapArrow, gameinfo.mCheatMapArrow); - AM_ParseArrow(CheatKey, "maparrows/key.txt"); - AM_ParseArrow(EasyKey, "maparrows/ravenkey.txt"); + AM_ParseArrow(CheatKey, gameinfo.mCheatKey); + AM_ParseArrow(EasyKey, gameinfo.mEasyKey); if (MapArrow.Size() == 0) I_FatalError("No automap arrow defined"); char namebuf[9]; diff --git a/src/b_bot.cpp b/src/b_bot.cpp index cf519fc1d..aa5262929 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -1,7 +1,7 @@ -// Cajun bot console commands. +// Cajun bot // -// [RH] Moved out of d_netcmd.c (in Cajun source), because they don't really -// belong there. +// [RH] Moved console commands out of d_netcmd.c (in Cajun source), because +// they don't really belong there. #include "c_cvars.h" #include "c_dispatch.h" @@ -14,6 +14,102 @@ #include "d_net.h" #include "farchive.h" +IMPLEMENT_POINTY_CLASS(DBot) + DECLARE_POINTER(dest) + DECLARE_POINTER(prev) + DECLARE_POINTER(enemy) + DECLARE_POINTER(missile) + DECLARE_POINTER(mate) + DECLARE_POINTER(last_mate) +END_POINTERS + +DBot::DBot () +: DThinker(STAT_BOT) +{ + Clear (); +} + +void DBot::Clear () +{ + player = NULL; + angle = 0; + dest = NULL; + prev = NULL; + enemy = NULL; + missile = NULL; + mate = NULL; + last_mate = NULL; + memset(&skill, 0, sizeof(skill)); + t_active = 0; + t_respawn = 0; + t_strafe = 0; + t_react = 0; + t_fight = 0; + t_roam = 0; + t_rocket = 0; + first_shot = true; + sleft = false; + allround = false; + increase = false; + oldx = 0; + oldy = 0; +} + +void DBot::Serialize (FArchive &arc) +{ + Super::Serialize (arc); + + if (SaveVersion < 4515) + { + angle_t savedyaw; + int savedpitch; + arc << savedyaw + << savedpitch; + } + else + { + arc << player; + } + + arc << angle + << dest + << prev + << enemy + << missile + << mate + << last_mate + << skill + << t_active + << t_respawn + << t_strafe + << t_react + << t_fight + << t_roam + << t_rocket + << first_shot + << sleft + << allround + << increase + << oldx + << oldy; +} + +void DBot::Tick () +{ + Super::Tick (); + + if (player->mo == NULL || bglobal.freeze) + { + return; + } + + BotThinkCycles.Clock(); + bglobal.m_Thinking = true; + Think (); + bglobal.m_Thinking = false; + BotThinkCycles.Unclock(); +} + CVAR (Int, bot_next_color, 11, 0) CVAR (Bool, bot_observer, false, 0) @@ -55,9 +151,14 @@ void FCajunMaster::ClearPlayer (int i, bool keepTeam) bot = bot->next; if (bot) { - bot->inuse = false; + bot->inuse = BOTINUSE_No; bot->lastteam = keepTeam ? players[i].userinfo.GetTeam() : TEAM_NONE; } + if (players[i].Bot != NULL) + { + players[i].Bot->Destroy (); + players[i].Bot = NULL; + } players[i].~player_t(); ::new(&players[i]) player_t; players[i].userinfo.Reset(); @@ -66,6 +167,12 @@ void FCajunMaster::ClearPlayer (int i, bool keepTeam) CCMD (removebots) { + if (!players[consoleplayer].settings_controller) + { + Printf ("Only setting controllers can remove bots\n"); + return; + } + Net_WriteByte (DEM_KILLBOTS); } @@ -91,7 +198,7 @@ CCMD (listbots) while (thebot) { - Printf ("%s%s\n", thebot->name, thebot->inuse ? " (active)" : ""); + Printf ("%s%s\n", thebot->name, thebot->inuse == BOTINUSE_Yes ? " (active)" : ""); thebot = thebot->next; count++; } diff --git a/src/b_bot.h b/src/b_bot.h index d178a85fa..69a2f7774 100644 --- a/src/b_bot.h +++ b/src/b_bot.h @@ -14,6 +14,7 @@ #include "d_ticcmd.h" #include "r_defs.h" #include "a_pickups.h" +#include "stats.h" #define FORWARDWALK 0x1900 #define FORWARDRUN 0x3200 @@ -60,6 +61,13 @@ struct botskill_t FArchive &operator<< (FArchive &arc, botskill_t &skill); +enum +{ + BOTINUSE_No, + BOTINUSE_Waiting, + BOTINUSE_Yes, +}; + //Info about all bots in the bots.cfg //Updated during each level start. //Info given to bots when they're spawned. @@ -69,7 +77,7 @@ struct botinfo_t char *name; char *info; botskill_t skill; - bool inuse; + int inuse; int lastteam; }; @@ -81,35 +89,29 @@ public: void ClearPlayer (int playernum, bool keepTeam); - //(B_Game.c) - void Main (int buf); + //(b_game.cpp) + void Main (); void Init (); void End(); - void CleanBotstuff (player_t *p); bool SpawnBot (const char *name, int color = NOCOLOR); + void TryAddBot (BYTE **stream, int player); + void RemoveAllBots (bool fromlist); bool LoadBots (); void ForgetBots (); - void DoAddBot (int bnum, char *info); - void RemoveAllBots (bool fromlist); - //(B_Func.c) - bool Check_LOS (AActor *mobj1, AActor *mobj2, angle_t vangle); + //(b_func.cpp) + void StartTravel (); + void FinishTravel (); + bool IsLeader (player_t *player); + void SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum); + fixed_t FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd); + bool SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm); - //(B_Think.c) - void WhatToGet (AActor *actor, AActor *item); - - //(B_move.c) - void Roam (AActor *actor, ticcmd_t *cmd); - bool Move (AActor *actor, ticcmd_t *cmd); - bool TryWalk (AActor *actor, ticcmd_t *cmd); - void NewChaseDir (AActor *actor, ticcmd_t *cmd); + //(b_move.cpp) bool CleanAhead (AActor *thing, fixed_t x, fixed_t y, ticcmd_t *cmd); - void TurnToAng (AActor *actor); - void Pitch (AActor *actor, AActor *target); bool IsDangerous (sector_t *sec); TArray getspawned; //Array of bots (their names) which should be spawned when starting a game. - bool botingame[MAXPLAYERS]; BYTE freeze:1; //Game in freeze mode. BYTE changefreeze:1; //Game wants to change freeze mode. int botnum; @@ -123,20 +125,8 @@ public: bool m_Thinking; private: - //(B_Func.c) - bool Reachable (AActor *actor, AActor *target); - void Dofire (AActor *actor, ticcmd_t *cmd); - AActor *Choose_Mate (AActor *bot); - AActor *Find_enemy (AActor *bot); - void SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum); - fixed_t FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd); - angle_t FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd); - bool SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm); - - //(B_Think.c) - void Think (AActor *actor, ticcmd_t *cmd); - void ThinkForMove (AActor *actor, ticcmd_t *cmd); - void Set_enemy (AActor *actor); + //(b_game.cpp) + bool DoAddBot (BYTE *info, botskill_t skill); protected: bool ctf; @@ -145,9 +135,84 @@ protected: bool observer; //Consoleplayer is observer. }; +class DBot : public DThinker +{ + DECLARE_CLASS(DBot,DThinker) + HAS_OBJECT_POINTERS +public: + DBot (); + + void Clear (); + void Serialize (FArchive &arc); + void Tick (); + + //(b_think.cpp) + void WhatToGet (AActor *item); + + //(b_func.cpp) + bool Check_LOS (AActor *to, angle_t vangle); + + player_t *player; + angle_t angle; // The wanted angle that the bot try to get every tic. + // (used to get a smooth view movement) + TObjPtr dest; // Move Destination. + TObjPtr prev; // Previous move destination. + TObjPtr enemy; // The dead meat. + TObjPtr missile; // A threatening missile that needs to be avoided. + TObjPtr mate; // Friend (used for grouping in teamplay or coop). + TObjPtr last_mate; // If bots mate disappeared (not if died) that mate is + // pointed to by this. Allows bot to roam to it if + // necessary. + + //Skills + struct botskill_t skill; + + //Tickers + int t_active; // Open door, lower lift stuff, door must open and + // lift must go down before bot does anything + // radical like try a stuckmove + int t_respawn; + int t_strafe; + int t_react; + int t_fight; + int t_roam; + int t_rocket; + + //Misc booleans + bool first_shot; // Used for reaction skill. + bool sleft; // If false, strafe is right. + bool allround; + bool increase; + + fixed_t oldx; + fixed_t oldy; + +private: + //(B_think.cpp) + void Think (); + void ThinkForMove (ticcmd_t *cmd); + void Set_enemy (); + + //(B_func.cpp) + bool Reachable (AActor *target); + void Dofire (ticcmd_t *cmd); + AActor *Choose_Mate (); + AActor *Find_enemy (); + angle_t FireRox (AActor *enemy, ticcmd_t *cmd); + + //(b_move.cpp) + void Roam (ticcmd_t *cmd); + bool Move (ticcmd_t *cmd); + bool TryWalk (ticcmd_t *cmd); + void NewChaseDir (ticcmd_t *cmd); + void TurnToAng (); + void Pitch (AActor *target); +}; + //Externs extern FCajunMaster bglobal; +extern cycle_t BotThinkCycles, BotSupportCycles; EXTERN_CVAR (Float, bot_flag_return_time) EXTERN_CVAR (Int, bot_next_color) @@ -158,7 +223,3 @@ EXTERN_CVAR (Bool, bot_watersplash) EXTERN_CVAR (Bool, bot_chat) #endif // __B_BOT_H__ - - - - diff --git a/src/b_func.cpp b/src/b_func.cpp index d79e5df11..7165d2cc1 100644 --- a/src/b_func.cpp +++ b/src/b_func.cpp @@ -24,24 +24,23 @@ static FRandom pr_botdofire ("BotDoFire"); -//Checks TRUE reachability from -//one looker to another. First mobj (looker) is looker. -bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) +//Checks TRUE reachability from bot to a looker. +bool DBot::Reachable (AActor *rtarget) { - if (looker == rtarget) + if (player->mo == rtarget) return false; if ((rtarget->Sector->ceilingplane.ZatPoint (rtarget->x, rtarget->y) - rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y)) - < looker->height) //Where rtarget is, looker can't be. + < player->mo->height) //Where rtarget is, player->mo can't be. return false; - sector_t *last_s = looker->Sector; - fixed_t last_z = last_s->floorplane.ZatPoint (looker->x, looker->y); - fixed_t estimated_dist = P_AproxDistance (looker->x - rtarget->x, looker->y - rtarget->y); + sector_t *last_s = player->mo->Sector; + fixed_t last_z = last_s->floorplane.ZatPoint (player->mo->x, player->mo->y); + fixed_t estimated_dist = P_AproxDistance (player->mo->x - rtarget->x, player->mo->y - rtarget->y); bool reachable = true; - FPathTraverse it(looker->x+looker->velx, looker->y+looker->vely, rtarget->x, rtarget->y, PT_ADDLINES|PT_ADDTHINGS); + FPathTraverse it(player->mo->x+player->mo->velx, player->mo->y+player->mo->vely, rtarget->x, rtarget->y, PT_ADDLINES|PT_ADDTHINGS); intercept_t *in; while ((in = it.Next())) { @@ -55,8 +54,8 @@ bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) frac = in->frac - FixedDiv (4*FRACUNIT, MAX_TRAVERSE_DIST); dist = FixedMul (frac, MAX_TRAVERSE_DIST); - hitx = it.Trace().x + FixedMul (looker->velx, frac); - hity = it.Trace().y + FixedMul (looker->vely, frac); + hitx = it.Trace().x + FixedMul (player->mo->velx, frac); + hity = it.Trace().y + FixedMul (player->mo->vely, frac); if (in->isaline) { @@ -76,7 +75,7 @@ bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) if (!bglobal.IsDangerous (s) && //Any nukage/lava? (floorheight <= (last_z+MAXMOVEHEIGHT) && ((ceilingheight == floorheight && line->special) - || (ceilingheight - floorheight) >= looker->height))) //Does it fit? + || (ceilingheight - floorheight) >= player->mo->height))) //Does it fit? { last_z = floorheight; last_s = s; @@ -95,7 +94,7 @@ bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) } thing = in->d.thing; - if (thing == looker) //Can't reach self in this case. + if (thing == player->mo) //Can't reach self in this case. continue; if (thing == rtarget && (rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y) <= (last_z+MAXMOVEHEIGHT))) { @@ -115,16 +114,16 @@ bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) //if these conditions are true, the function returns true. //GOOD TO KNOW is that the player's view angle //in doom is 90 degrees infront. -bool FCajunMaster::Check_LOS (AActor *from, AActor *to, angle_t vangle) +bool DBot::Check_LOS (AActor *to, angle_t vangle) { - if (!P_CheckSight (from, to, SF_SEEPASTBLOCKEVERYTHING)) + if (!P_CheckSight (player->mo, to, SF_SEEPASTBLOCKEVERYTHING)) return false; // out of sight if (vangle == ANGLE_MAX) return true; if (vangle == 0) return false; //Looker seems to be blind. - return (angle_t)abs (R_PointToAngle2 (from->x, from->y, to->x, to->y) - from->angle) <= vangle/2; + return (angle_t)abs (R_PointToAngle2 (player->mo->x, player->mo->y, to->x, to->y) - player->mo->angle) <= vangle/2; } //------------------------------------- @@ -132,7 +131,7 @@ bool FCajunMaster::Check_LOS (AActor *from, AActor *to, angle_t vangle) //------------------------------------- //The bot will check if it's time to fire //and do so if that is the case. -void FCajunMaster::Dofire (AActor *actor, ticcmd_t *cmd) +void DBot::Dofire (ticcmd_t *cmd) { bool no_fire; //used to prevent bot from pumping rockets into nearby walls. int aiming_penalty=0; //For shooting at shading target, if screen is red, MAKEME: When screen red. @@ -140,50 +139,48 @@ void FCajunMaster::Dofire (AActor *actor, ticcmd_t *cmd) fixed_t dist; angle_t an; int m; - static bool inc[MAXPLAYERS]; - AActor *enemy = actor->player->enemy; if (!enemy || !(enemy->flags & MF_SHOOTABLE) || enemy->health <= 0) return; - if (actor->player->ReadyWeapon == NULL) + if (player->ReadyWeapon == NULL) return; - if (actor->player->damagecount > actor->player->skill.isp) + if (player->damagecount > skill.isp) { - actor->player->first_shot = true; + first_shot = true; return; } //Reaction skill thing. - if (actor->player->first_shot && - !(actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING)) + if (first_shot && + !(player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING)) { - actor->player->t_react = (100-actor->player->skill.reaction+1)/((pr_botdofire()%3)+3); + t_react = (100-skill.reaction+1)/((pr_botdofire()%3)+3); } - actor->player->first_shot = false; - if (actor->player->t_react) + first_shot = false; + if (t_react) return; //MAKEME: Decrease the rocket suicides even more. no_fire = true; - //actor->player->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y); + //angle = R_PointToAngle2(player->mo->x, player->mo->y, player->enemy->x, player->enemy->y); //Distance to enemy. - dist = P_AproxDistance ((actor->x + actor->velx) - (enemy->x + enemy->velx), - (actor->y + actor->vely) - (enemy->y + enemy->vely)); + dist = P_AproxDistance ((player->mo->x + player->mo->velx) - (enemy->x + enemy->velx), + (player->mo->y + player->mo->vely) - (enemy->y + enemy->vely)); //FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go. - if (actor->player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON) + if (player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON) { - if ((actor->player->ReadyWeapon->ProjectileType != NULL)) + if ((player->ReadyWeapon->ProjectileType != NULL)) { - if (actor->player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true)) + if (player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true)) { // This weapon can fire a projectile and has enough ammo to do so goto shootmissile; } - else if (!(actor->player->ReadyWeapon->WeaponFlags & WIF_AMMO_OPTIONAL)) + else if (!(player->ReadyWeapon->WeaponFlags & WIF_AMMO_OPTIONAL)) { // Ammo is required, so don't shoot. This is for weapons that shoot // missiles that die at close range, such as the powered-up Phoneix Rod. @@ -196,51 +193,51 @@ void FCajunMaster::Dofire (AActor *actor, ticcmd_t *cmd) no_fire = (dist > (MELEERANGE*4)); } } - else if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG) + else if (player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG) { //MAKEME: This should be smarter. - if ((pr_botdofire()%200)<=actor->player->skill.reaction) - if(Check_LOS(actor, actor->player->enemy, SHOOTFOV)) + if ((pr_botdofire()%200)<=skill.reaction) + if(Check_LOS(enemy, SHOOTFOV)) no_fire = false; } - else if (actor->player->ReadyWeapon->ProjectileType != NULL) + else if (player->ReadyWeapon->ProjectileType != NULL) { - if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) + if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) { //Special rules for RL - an = FireRox (actor, enemy, cmd); + an = FireRox (enemy, cmd); if(an) { - actor->player->angle = an; + angle = an; //have to be somewhat precise. to avoid suicide. - if (abs (actor->player->angle - actor->angle) < 12*ANGLE_1) + if (abs (angle - player->mo->angle) < 12*ANGLE_1) { - actor->player->t_rocket = 9; + t_rocket = 9; no_fire = false; } } } // prediction aiming shootmissile: - dist = P_AproxDistance (actor->x - enemy->x, actor->y - enemy->y); - m = dist / GetDefaultByType (actor->player->ReadyWeapon->ProjectileType)->Speed; - SetBodyAt (enemy->x + enemy->velx*m*2, enemy->y + enemy->vely*m*2, enemy->z, 1); - actor->player->angle = R_PointToAngle2 (actor->x, actor->y, body1->x, body1->y); - if (Check_LOS (actor, enemy, SHOOTFOV)) + dist = P_AproxDistance (player->mo->x - enemy->x, player->mo->y - enemy->y); + m = dist / GetDefaultByType (player->ReadyWeapon->ProjectileType)->Speed; + bglobal.SetBodyAt (enemy->x + enemy->velx*m*2, enemy->y + enemy->vely*m*2, enemy->z, 1); + angle = R_PointToAngle2 (player->mo->x, player->mo->y, bglobal.body1->x, bglobal.body1->y); + if (Check_LOS (enemy, SHOOTFOV)) no_fire = false; } else { //Other weapons, mostly instant hit stuff. - actor->player->angle = R_PointToAngle2 (actor->x, actor->y, enemy->x, enemy->y); + angle = R_PointToAngle2 (player->mo->x, player->mo->y, enemy->x, enemy->y); aiming_penalty = 0; if (enemy->flags & MF_SHADOW) aiming_penalty += (pr_botdofire()%25)+10; - if (enemy->Sector->lightlevelplayer->powers & PW_INFRARED)*/) + if (enemy->Sector->lightlevelpowers & PW_INFRARED)*/) aiming_penalty += pr_botdofire()%40;//Dark - if (actor->player->damagecount) - aiming_penalty += actor->player->damagecount; //Blood in face makes it hard to aim - aiming_value = actor->player->skill.aiming - aiming_penalty; + if (player->damagecount) + aiming_penalty += player->damagecount; //Blood in face makes it hard to aim + aiming_value = skill.aiming - aiming_penalty; if (aiming_value <= 0) aiming_value = 1; m = ((SHOOTFOV/2)-(aiming_value*SHOOTFOV/200)); //Higher skill is more accurate @@ -249,18 +246,18 @@ shootmissile: if (m) { - if (inc[actor->id]) - actor->player->angle += m; + if (increase) + angle += m; else - actor->player->angle -= m; + angle -= m; } - if (abs (actor->player->angle - actor->angle) < 4*ANGLE_1) + if (abs (angle - player->mo->angle) < 4*ANGLE_1) { - inc[actor->id] = !inc[actor->id]; + increase = !increase; } - if (Check_LOS (actor, enemy, (SHOOTFOV/2))) + if (Check_LOS (enemy, (SHOOTFOV/2))) no_fire = false; } if (!no_fire) //If going to fire weapon @@ -268,53 +265,48 @@ shootmissile: cmd->ucmd.buttons |= BT_ATTACK; } //Prevents bot from jerking, when firing automatic things with low skill. - //actor->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y); + //player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, player->enemy->x, player->enemy->y); } +bool FCajunMaster::IsLeader (player_t *player) +{ + for (int count = 0; count < MAXPLAYERS; count++) + { + if (players[count].Bot != NULL + && players[count].Bot->mate == player->mo) + { + return true; + } + } + + return false; +} //This function is called every //tick (for each bot) to set //the mate (teammate coop mate). -AActor *FCajunMaster::Choose_Mate (AActor *bot) +AActor *DBot::Choose_Mate () { int count; - int count2; fixed_t closest_dist, test; AActor *target; AActor *observer; - bool p_leader[MAXPLAYERS]; //is mate alive? - if (bot->player->mate) + if (mate) { - if (bot->player->mate->health <= 0) - bot->player->mate = NULL; + if (mate->health <= 0) + mate = NULL; else - bot->player->last_mate = bot->player->mate; + last_mate = mate; } - if (bot->player->mate) //Still is.. - return bot->player->mate; + if (mate) //Still is.. + return mate; //Check old_mates status. - if (bot->player->last_mate) - if (bot->player->last_mate->health <= 0) - bot->player->last_mate = NULL; - - for (count = 0; count < MAXPLAYERS; count++) - { - if (!playeringame[count]) - continue; - p_leader[count] = false; - for (count2 = 0; count2 < MAXPLAYERS; count2++) - { - if (players[count].isbot - && players[count2].mate == players[count].mo) - { - p_leader[count] = true; - break; - } - } - } + if (last_mate) + if (last_mate->health <= 0) + last_mate = NULL; target = NULL; closest_dist = FIXED_MAX; @@ -330,18 +322,17 @@ AActor *FCajunMaster::Choose_Mate (AActor *bot) if (playeringame[count] && client->mo - && bot != client->mo - && (bot->IsTeammate (client->mo) || !deathmatch) + && player->mo != client->mo + && (player->mo->IsTeammate (client->mo) || !deathmatch) && client->mo->health > 0 && client->mo != observer - && ((bot->health/2) <= client->mo->health || !deathmatch) - && !p_leader[count]) //taken? + && ((player->mo->health/2) <= client->mo->health || !deathmatch) + && !bglobal.IsLeader(client)) //taken? { - - if (P_CheckSight (bot, client->mo, SF_IGNOREVISIBILITY)) + if (P_CheckSight (player->mo, client->mo, SF_IGNOREVISIBILITY)) { - test = P_AproxDistance (client->mo->x - bot->x, - client->mo->y - bot->y); + test = P_AproxDistance (client->mo->x - player->mo->x, + client->mo->y - player->mo->y); if (test < closest_dist) { @@ -354,15 +345,15 @@ AActor *FCajunMaster::Choose_Mate (AActor *bot) /* //Make a introducing to mate. - if(target && target!=bot->player->last_mate) + if(target && target!=last_mate) { if((P_Random()%(200*bglobal.botnum))<3) { - bot->player->chat = c_teamup; + chat = c_teamup; if(target->bot) - strcpy(bot->player->c_target, botsingame[target->bot_id]); + strcpy(c_target, botsingame[target->bot_id]); else if(target->player) - strcpy(bot->player->c_target, player_names[target->play_id]); + strcpy(c_target, player_names[target->play_id]); } } */ @@ -372,7 +363,7 @@ AActor *FCajunMaster::Choose_Mate (AActor *bot) } //MAKEME: Make this a smart decision -AActor *FCajunMaster::Find_enemy (AActor *bot) +AActor *DBot::Find_enemy () { int count; fixed_t closest_dist, temp; //To target. @@ -382,15 +373,15 @@ AActor *FCajunMaster::Find_enemy (AActor *bot) if (!deathmatch) { // [RH] Take advantage of the Heretic/Hexen code to be a little smarter - return P_RoughMonsterSearch (bot, 20); + return P_RoughMonsterSearch (player->mo, 20); } //Note: It's hard to ambush a bot who is not alone - if (bot->player->allround || bot->player->mate) + if (allround || mate) vangle = ANGLE_MAX; else vangle = ENEMY_SCAN_FOV; - bot->player->allround = false; + allround = false; target = NULL; closest_dist = FIXED_MAX; @@ -403,21 +394,21 @@ AActor *FCajunMaster::Find_enemy (AActor *bot) { player_t *client = &players[count]; if (playeringame[count] - && !bot->IsTeammate (client->mo) + && !player->mo->IsTeammate (client->mo) && client->mo != observer && client->mo->health > 0 - && bot != client->mo) + && player->mo != client->mo) { - if (Check_LOS (bot, client->mo, vangle)) //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. - //if(P_CheckSight( bot, players[count].mo)) + if (Check_LOS (client->mo, vangle)) //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. + //if(P_CheckSight(player->mo, players[count].mo)) { - temp = P_AproxDistance (client->mo->x - bot->x, - client->mo->y - bot->y); + temp = P_AproxDistance (client->mo->x - player->mo->x, + client->mo->y - player->mo->y); //Too dark? if (temp > DARK_DIST && client->mo->Sector->lightlevel < WHATS_DARK /*&& - bot->player->Powers & PW_INFRARED*/) + player->Powers & PW_INFRARED*/) continue; if (temp < closest_dist) @@ -501,16 +492,16 @@ fixed_t FCajunMaster::FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd) return dist; } -angle_t FCajunMaster::FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd) +angle_t DBot::FireRox (AActor *enemy, ticcmd_t *cmd) { fixed_t dist; angle_t ang; AActor *actor; int m; - SetBodyAt (bot->x + FixedMul(bot->velx, 5*FRACUNIT), - bot->y + FixedMul(bot->vely, 5*FRACUNIT), - bot->z + (bot->height / 2), 2); + bglobal.SetBodyAt (player->mo->x + FixedMul(player->mo->velx, 5*FRACUNIT), + player->mo->y + FixedMul(player->mo->vely, 5*FRACUNIT), + player->mo->z + (player->mo->height / 2), 2); actor = bglobal.body2; @@ -520,16 +511,16 @@ angle_t FCajunMaster::FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd) //Predict. m = (((dist+1)/FRACUNIT) / GetDefaultByName("Rocket")->Speed); - SetBodyAt (enemy->x + FixedMul(enemy->velx, (m+2*FRACUNIT)), - enemy->y + FixedMul(enemy->vely, (m+2*FRACUNIT)), ONFLOORZ, 1); + bglobal.SetBodyAt (enemy->x + FixedMul(enemy->velx, (m+2*FRACUNIT)), + enemy->y + FixedMul(enemy->vely, (m+2*FRACUNIT)), ONFLOORZ, 1); dist = P_AproxDistance(actor->x-bglobal.body1->x, actor->y-bglobal.body1->y); //try the predicted location if (P_CheckSight (actor, bglobal.body1, SF_IGNOREVISIBILITY)) //See the predicted location, so give a test missile { FCheckPosition tm; - if (SafeCheckPosition (bot, actor->x, actor->y, tm)) + if (bglobal.SafeCheckPosition (player->mo, actor->x, actor->y, tm)) { - if (FakeFire (actor, bglobal.body1, cmd) >= SAFE_SELF_MISDIST) + if (bglobal.FakeFire (actor, bglobal.body1, cmd) >= SAFE_SELF_MISDIST) { ang = R_PointToAngle2 (actor->x, actor->y, bglobal.body1->x, bglobal.body1->y); return ang; @@ -539,9 +530,9 @@ angle_t FCajunMaster::FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd) //Try fire straight. if (P_CheckSight (actor, enemy, 0)) { - if (FakeFire (bot, enemy, cmd) >= SAFE_SELF_MISDIST) + if (bglobal.FakeFire (player->mo, enemy, cmd) >= SAFE_SELF_MISDIST) { - ang = R_PointToAngle2(bot->x, bot->y, enemy->x, enemy->y); + ang = R_PointToAngle2(player->mo->x, player->mo->y, enemy->x, enemy->y); return ang; } } @@ -559,3 +550,25 @@ bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FChec actor->flags = savedFlags; return res; } + +void FCajunMaster::StartTravel () +{ + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (players[i].Bot != NULL) + { + players[i].Bot->ChangeStatNum (STAT_TRAVELLING); + } + } +} + +void FCajunMaster::FinishTravel () +{ + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (players[i].Bot != NULL) + { + players[i].Bot->ChangeStatNum (STAT_BOT); + } + } +} diff --git a/src/b_game.cpp b/src/b_game.cpp index f3878b8bb..44aff03fa 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -89,49 +89,27 @@ enum BOTCFG_TEAM }; -static bool waitingforspawn[MAXPLAYERS]; - FCajunMaster::~FCajunMaster() { ForgetBots(); } -//This function is called every tick (from g_game.c), -//send bots into thinking (+more). -void FCajunMaster::Main (int buf) +//This function is called every tick (from g_game.c). +void FCajunMaster::Main () { - int i; - BotThinkCycles.Reset(); - if (consoleplayer != Net_Arbitrator || demoplayback) + if (demoplayback || gamestate != GS_LEVEL || consoleplayer != Net_Arbitrator) return; - if (gamestate != GS_LEVEL) - return; - - m_Thinking = true; - - //Think for bots. - if (botnum) - { - BotThinkCycles.Clock(); - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].mo && !freeze && players[i].isbot) - Think (players[i].mo, &netcmds[i][buf]); - } - BotThinkCycles.Unclock(); - } - //Add new bots? if (wanted_botnum > botnum && !freeze) { if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY)) { - if (!SpawnBot (getspawned[spawn_tries])) + if (!SpawnBot (getspawned[spawn_tries])) wanted_botnum--; - spawn_tries++; + spawn_tries++; } t_join--; @@ -156,14 +134,10 @@ void FCajunMaster::Main (int buf) players[consoleplayer].mo->flags2 &= ~MF2_FLY; players[consoleplayer].mo->LinkToWorld (); } - - m_Thinking = false; } void FCajunMaster::Init () { - int i; - botnum = 0; firstthing = NULL; spawn_tries = 0; @@ -172,18 +146,6 @@ void FCajunMaster::Init () body1 = NULL; body2 = NULL; - //Remove all bots upon each level start, they'll get spawned instead. - for (i = 0; i < MAXPLAYERS; i++) - { - waitingforspawn[i] = false; - if (playeringame[i] && players[i].isbot) - { - CleanBotstuff (&players[i]); - players[i].isbot = false; - botingame[i] = false; - } - } - if (ctf && teamplay == false) teamplay = true; //Need teamplay for ctf. (which is not done yet) @@ -199,7 +161,7 @@ void FCajunMaster::Init () while (thebot != NULL) { - thebot->inuse = false; + thebot->inuse = BOTINUSE_No; thebot = thebot->next; } } @@ -212,19 +174,13 @@ void FCajunMaster::End () //Arrange wanted botnum and their names, so they can be spawned next level. getspawned.Clear(); - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].isbot) - { - if (deathmatch) - { - getspawned.Push(players[i].userinfo.GetName()); - } - CleanBotstuff (&players[i]); - } - } if (deathmatch) { + for (i = 0; i < MAXPLAYERS; i++) + { + getspawned.Push(players[i].userinfo.GetName()); + } + wanted_botnum = botnum; } } @@ -240,12 +196,10 @@ void FCajunMaster::End () //The color parameter can be either a //color (range from 0-10), or = NOCOLOR. //The color parameter overides bots -//induvidual colors if not = NOCOLOR. +//individual colors if not = NOCOLOR. bool FCajunMaster::SpawnBot (const char *name, int color) { - int playernumber; - //COLORS static const char colors[11][17] = { @@ -262,36 +216,31 @@ bool FCajunMaster::SpawnBot (const char *name, int color) "\\color\\cf df 90" //10 = Bleached Bone }; - for (playernumber = 0; playernumber < MAXPLAYERS; playernumber++) - { - if (!playeringame[playernumber] && !waitingforspawn[playernumber]) - { - break; - } - } - - if (playernumber == MAXPLAYERS) - { - Printf ("The maximum of %d players/bots has been reached\n", MAXPLAYERS); - return false; - } - botinfo_t *thebot; + int botshift; if (name) { thebot = botinfo; // Check if exist or already in the game. + botshift = 0; while (thebot && stricmp (name, thebot->name)) + { thebot = thebot->next; + botshift++; + } if (thebot == NULL) { Printf ("couldn't find %s in %s\n", name, BOTFILENAME); return false; } - else if (thebot->inuse) + else if (thebot->inuse == BOTINUSE_Waiting) + { + return false; + } + else if (thebot->inuse == BOTINUSE_Yes) { Printf ("%s is already in the thick\n", name); return false; @@ -304,9 +253,13 @@ bool FCajunMaster::SpawnBot (const char *name, int color) { int rnum = (pr_botspawn() % loaded_bots); thebot = botinfo; + botshift = 0; while (rnum) + { --rnum, thebot = thebot->next; - if (!thebot->inuse) + botshift++; + } + if (thebot->inuse == BOTINUSE_No) vacant = true; } } @@ -316,10 +269,10 @@ bool FCajunMaster::SpawnBot (const char *name, int color) return false; } - waitingforspawn[playernumber] = true; + thebot->inuse = BOTINUSE_Waiting; Net_WriteByte (DEM_ADDBOT); - Net_WriteByte (playernumber); + Net_WriteByte (botshift); { //Set color. char concat[512]; @@ -335,52 +288,106 @@ bool FCajunMaster::SpawnBot (const char *name, int color) } Net_WriteString (concat); } - - players[playernumber].skill = thebot->skill; - - thebot->inuse = true; - - //Increment this. - botnum++; + Net_WriteByte(thebot->skill.aiming); + Net_WriteByte(thebot->skill.perfection); + Net_WriteByte(thebot->skill.reaction); + Net_WriteByte(thebot->skill.isp); return true; } -void FCajunMaster::DoAddBot (int bnum, char *info) +void FCajunMaster::TryAddBot (BYTE **stream, int player) { - BYTE *infob = (BYTE *)info; - D_ReadUserInfoStrings (bnum, &infob, false); + int botshift = ReadByte (stream); + char *info = ReadString (stream); + botskill_t skill; + skill.aiming = ReadByte (stream); + skill.perfection = ReadByte (stream); + skill.reaction = ReadByte (stream); + skill.isp = ReadByte (stream); + + botinfo_t *thebot = NULL; + + if (consoleplayer == player) + { + thebot = botinfo; + + while (botshift > 0) + { + thebot = thebot->next; + botshift--; + } + } + + if (DoAddBot ((BYTE *)info, skill)) + { + //Increment this. + botnum++; + + if (thebot != NULL) + { + thebot->inuse = BOTINUSE_Yes; + } + } + else + { + if (thebot != NULL) + { + thebot->inuse = BOTINUSE_No; + } + } + + delete[] info; +} + +bool FCajunMaster::DoAddBot (BYTE *info, botskill_t skill) +{ + int bnum; + + for (bnum = 0; bnum < MAXPLAYERS; bnum++) + { + if (!playeringame[bnum]) + { + break; + } + } + + if (bnum == MAXPLAYERS) + { + Printf ("The maximum of %d players/bots has been reached\n", MAXPLAYERS); + return false; + } + + D_ReadUserInfoStrings (bnum, &info, false); + if (!deathmatch && playerstarts[bnum].type == 0) { Printf ("%s tried to join, but there was no player %d start\n", players[bnum].userinfo.GetName(), bnum+1); ClearPlayer (bnum, false); // Make the bot inactive again - if (botnum > 0) - { - botnum--; - } + return false; } + + multiplayer = true; //Prevents cheating and so on; emulates real netgame (almost). + players[bnum].Bot = new DBot; + players[bnum].Bot->player = &players[bnum]; + players[bnum].Bot->skill = skill; + playeringame[bnum] = true; + players[bnum].mo = NULL; + players[bnum].playerstate = PST_ENTER; + + if (teamplay) + Printf ("%s joined the %s team\n", players[bnum].userinfo.GetName(), Teams[players[bnum].userinfo.GetTeam()].GetName()); else + Printf ("%s joined the game\n", players[bnum].userinfo.GetName()); + + G_DoReborn (bnum, true); + if (StatusBar != NULL) { - multiplayer = true; //Prevents cheating and so on; emulates real netgame (almost). - players[bnum].isbot = true; - playeringame[bnum] = true; - players[bnum].mo = NULL; - players[bnum].playerstate = PST_ENTER; - botingame[bnum] = true; - - if (teamplay) - Printf ("%s joined the %s team\n", players[bnum].userinfo.GetName(), Teams[players[bnum].userinfo.GetTeam()].GetName()); - else - Printf ("%s joined the game\n", players[bnum].userinfo.GetName()); - - G_DoReborn (bnum, true); - if (StatusBar != NULL) - { - StatusBar->MultiplayerChanged (); - } + StatusBar->MultiplayerChanged (); } - waitingforspawn[bnum] = false; + + return true; } void FCajunMaster::RemoveAllBots (bool fromlist) @@ -389,13 +396,13 @@ void FCajunMaster::RemoveAllBots (bool fromlist) for (i = 0; i < MAXPLAYERS; ++i) { - if (playeringame[i] && botingame[i]) + if (players[i].Bot != NULL) { // If a player is looking through this bot's eyes, make him // look through his own eyes instead. for (j = 0; j < MAXPLAYERS; ++j) { - if (i != j && playeringame[j] && !botingame[j]) + if (i != j && playeringame[j] && players[j].Bot == NULL) { if (players[j].camera == players[i].mo) { @@ -415,34 +422,10 @@ void FCajunMaster::RemoveAllBots (bool fromlist) if (fromlist) { wanted_botnum = 0; - for (i = 0; i < MAXPLAYERS; i++) - waitingforspawn[i] = false; } botnum = 0; } -//Clean the bot part of the player_t -//Used when bots are respawned or at level starts. -void FCajunMaster::CleanBotstuff (player_t *p) -{ - p->angle = ANG45; - p->dest = NULL; - p->enemy = NULL; //The dead meat. - p->missile = NULL; //A threatening missile that needs to be avoided. - p->mate = NULL; //Friend (used for grouping in templay or coop. - p->last_mate = NULL; //If bot's mate dissapeared (not if died) that mate is pointed to by this. Allows bot to roam to it if necessary. - //Tickers - p->t_active = 0; //Open door, lower lift stuff, door must open and lift must go down before bot does anything radical like try a stuckmove - p->t_respawn = 0; - p->t_strafe = 0; - p->t_react = 0; - //Misc bools - p->isbot = true; //Important. - p->first_shot = true; //Used for reaction skill. - p->sleft = false; //If false, strafe is right. - p->allround = false; -} - //------------------ //Reads data for bot from diff --git a/src/b_move.cpp b/src/b_move.cpp index b456c0c84..fd0405457 100644 --- a/src/b_move.cpp +++ b/src/b_move.cpp @@ -17,20 +17,7 @@ #include "gi.h" #include "a_keys.h" #include "d_event.h" - -enum dirtype_t -{ - DI_EAST, - DI_NORTHEAST, - DI_NORTH, - DI_NORTHWEST, - DI_WEST, - DI_SOUTHWEST, - DI_SOUTH, - DI_SOUTHEAST, - DI_NODIR, - NUMDIRS -}; +#include "p_enemy.h" static FRandom pr_botopendoor ("BotOpenDoor"); static FRandom pr_bottrywalk ("BotTryWalk"); @@ -39,62 +26,58 @@ static FRandom pr_botnewchasedir ("BotNewChaseDir"); // borrow some tables from p_enemy.cpp extern dirtype_t opposite[9]; extern dirtype_t diags[4]; -extern fixed_t xspeed[8]; -extern fixed_t yspeed[8]; -extern TArray spechit; - -//Called while the bot moves after its player->dest mobj +//Called while the bot moves after its dest mobj //which can be a weapon/enemy/item whatever. -void FCajunMaster::Roam (AActor *actor, ticcmd_t *cmd) +void DBot::Roam (ticcmd_t *cmd) { int delta; - if (Reachable(actor, actor->player->dest)) + if (Reachable(dest)) { // Straight towards it. - actor->player->angle = R_PointToAngle2(actor->x, actor->y, actor->player->dest->x, actor->player->dest->y); + angle = R_PointToAngle2(player->mo->x, player->mo->y, dest->x, dest->y); } - else if (actor->movedir < 8) // turn towards movement direction if not there yet + else if (player->mo->movedir < 8) // turn towards movement direction if not there yet { - actor->player->angle &= (angle_t)(7<<29); - delta = actor->player->angle - (actor->movedir << 29); + angle &= (angle_t)(7<<29); + delta = angle - (player->mo->movedir << 29); if (delta > 0) - actor->player->angle -= ANG45; + angle -= ANG45; else if (delta < 0) - actor->player->angle += ANG45; + angle += ANG45; } // chase towards destination. - if (--actor->movecount < 0 || !Move (actor, cmd)) + if (--player->mo->movecount < 0 || !Move (cmd)) { - NewChaseDir (actor, cmd); + NewChaseDir (cmd); } } -bool FCajunMaster::Move (AActor *actor, ticcmd_t *cmd) +bool DBot::Move (ticcmd_t *cmd) { fixed_t tryx, tryy; bool try_ok; int good; - if (actor->movedir == DI_NODIR) + if (player->mo->movedir == DI_NODIR) return false; - if ((unsigned)actor->movedir >= 8) + if ((unsigned)player->mo->movedir >= 8) I_Error ("Weird bot movedir!"); - tryx = actor->x + 8*xspeed[actor->movedir]; - tryy = actor->y + 8*yspeed[actor->movedir]; + tryx = player->mo->x + 8*xspeed[player->mo->movedir]; + tryy = player->mo->y + 8*yspeed[player->mo->movedir]; - try_ok = CleanAhead (actor, tryx, tryy, cmd); + try_ok = bglobal.CleanAhead (player->mo, tryx, tryy, cmd); if (!try_ok) //Anything blocking that could be opened etc.. { if (!spechit.Size ()) return false; - actor->movedir = DI_NODIR; + player->mo->movedir = DI_NODIR; good = 0; line_t *ld; @@ -103,16 +86,16 @@ bool FCajunMaster::Move (AActor *actor, ticcmd_t *cmd) { bool tryit = true; - if (ld->special == Door_LockedRaise && !P_CheckKeys (actor, ld->args[3], false)) + if (ld->special == Door_LockedRaise && !P_CheckKeys (player->mo, ld->args[3], false)) tryit = false; - else if (ld->special == Generic_Door && !P_CheckKeys (actor, ld->args[4], false)) + else if (ld->special == Generic_Door && !P_CheckKeys (player->mo, ld->args[4], false)) tryit = false; if (tryit && - (P_TestActivateLine (ld, actor, 0, SPAC_Use) || - P_TestActivateLine (ld, actor, 0, SPAC_Push))) + (P_TestActivateLine (ld, player->mo, 0, SPAC_Use) || + P_TestActivateLine (ld, player->mo, 0, SPAC_Push))) { - good |= ld == actor->BlockingLine ? 1 : 2; + good |= ld == player->mo->BlockingLine ? 1 : 2; } } if (good && ((pr_botopendoor() >= 203) ^ (good & 1))) @@ -130,16 +113,16 @@ bool FCajunMaster::Move (AActor *actor, ticcmd_t *cmd) return true; } -bool FCajunMaster::TryWalk (AActor *actor, ticcmd_t *cmd) +bool DBot::TryWalk (ticcmd_t *cmd) { - if (!Move (actor, cmd)) + if (!Move (cmd)) return false; - actor->movecount = pr_bottrywalk() & 60; + player->mo->movecount = pr_bottrywalk() & 60; return true; } -void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) +void DBot::NewChaseDir (ticcmd_t *cmd) { fixed_t deltax; fixed_t deltay; @@ -151,7 +134,7 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) dirtype_t turnaround; - if (!actor->player->dest) + if (!dest) { #ifndef BOT_RELEASE_COMPILE Printf ("Bot tried move without destination\n"); @@ -159,11 +142,11 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) return; } - olddir = (dirtype_t)actor->movedir; + olddir = (dirtype_t)player->mo->movedir; turnaround = opposite[olddir]; - deltax = actor->player->dest->x - actor->x; - deltay = actor->player->dest->y - actor->y; + deltax = dest->x - player->mo->x; + deltay = dest->y - player->mo->y; if (deltax > 10*FRACUNIT) d[1] = DI_EAST; @@ -182,8 +165,8 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) // try direct route if (d[1] != DI_NODIR && d[2] != DI_NODIR) { - actor->movedir = diags[((deltay<0)<<1)+(deltax>0)]; - if (actor->movedir != turnaround && TryWalk(actor, cmd)) + player->mo->movedir = diags[((deltay<0)<<1)+(deltax>0)]; + if (player->mo->movedir != turnaround && TryWalk(cmd)) return; } @@ -203,16 +186,16 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) if (d[1]!=DI_NODIR) { - actor->movedir = d[1]; - if (TryWalk (actor, cmd)) + player->mo->movedir = d[1]; + if (TryWalk (cmd)) return; } if (d[2]!=DI_NODIR) { - actor->movedir = d[2]; + player->mo->movedir = d[2]; - if (TryWalk(actor, cmd)) + if (TryWalk(cmd)) return; } @@ -220,9 +203,9 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) // so pick another direction. if (olddir!=DI_NODIR) { - actor->movedir = olddir; + player->mo->movedir = olddir; - if (TryWalk(actor, cmd)) + if (TryWalk(cmd)) return; } @@ -235,9 +218,9 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) { if (tdir!=turnaround) { - actor->movedir = tdir; + player->mo->movedir = tdir; - if (TryWalk(actor, cmd)) + if (TryWalk(cmd)) return; } } @@ -250,9 +233,9 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) { if (tdir!=turnaround) { - actor->movedir = tdir; + player->mo->movedir = tdir; - if (TryWalk(actor, cmd)) + if (TryWalk(cmd)) return; } } @@ -260,12 +243,12 @@ void FCajunMaster::NewChaseDir (AActor *actor, ticcmd_t *cmd) if (turnaround != DI_NODIR) { - actor->movedir = turnaround; - if (TryWalk(actor, cmd)) + player->mo->movedir = turnaround; + if (TryWalk(cmd)) return; } - actor->movedir = DI_NODIR; // can not move + player->mo->movedir = DI_NODIR; // can not move } @@ -324,48 +307,48 @@ bool FCajunMaster::CleanAhead (AActor *thing, fixed_t x, fixed_t y, ticcmd_t *cm #define MAXTURN (15*ANGLE_1) //Max degrees turned in one tic. Lower is smother but may cause the bot not getting where it should = crash #define TURNSENS 3 //Higher is smoother but slower turn. -void FCajunMaster::TurnToAng (AActor *actor) +void DBot::TurnToAng () { int maxturn = MAXTURN; - if (actor->player->ReadyWeapon != NULL) + if (player->ReadyWeapon != NULL) { - if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) + if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) { - if (actor->player->t_roam && !actor->player->missile) + if (t_roam && !missile) { //Keep angle that where when shot where decided. return; } } - if(actor->player->enemy) - if(!actor->player->dest) //happens when running after item in combat situations, or normal, prevents weak turns - if(actor->player->ReadyWeapon->ProjectileType == NULL && !(actor->player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON)) - if(Check_LOS(actor, actor->player->enemy, SHOOTFOV+5*ANGLE_1)) + if(enemy) + if(!dest) //happens when running after item in combat situations, or normal, prevents weak turns + if(player->ReadyWeapon->ProjectileType == NULL && !(player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON)) + if(Check_LOS(enemy, SHOOTFOV+5*ANGLE_1)) maxturn = 3; } - int distance = actor->player->angle - actor->angle; + int distance = angle - player->mo->angle; - if (abs (distance) < OKAYRANGE && !actor->player->enemy) + if (abs (distance) < OKAYRANGE && !enemy) return; distance /= TURNSENS; if (abs (distance) > maxturn) distance = distance < 0 ? -maxturn : maxturn; - actor->angle += distance; + player->mo->angle += distance; } -void FCajunMaster::Pitch (AActor *actor, AActor *target) +void DBot::Pitch (AActor *target) { double aim; double diff; - diff = target->z - actor->z; - aim = atan (diff / (double)P_AproxDistance (actor->x - target->x, actor->y - target->y)); - actor->pitch = -(int)(aim * ANGLE_180/M_PI); + diff = target->z - player->mo->z; + aim = atan (diff / (double)P_AproxDistance (player->mo->x - target->x, player->mo->y - target->y)); + player->mo->pitch = -(int)(aim * ANGLE_180/M_PI); } //Checks if a sector is dangerous. @@ -388,4 +371,3 @@ bool FCajunMaster::IsDangerous (sector_t *sec) || special == Damage_InstantDeath || special == sDamage_SuperHellslime; } - diff --git a/src/b_think.cpp b/src/b_think.cpp index e00eea827..ea65395d6 100644 --- a/src/b_think.cpp +++ b/src/b_think.cpp @@ -24,47 +24,49 @@ static FRandom pr_botmove ("BotMove"); //This function is called each tic for each bot, //so this is what the bot does. -void FCajunMaster::Think (AActor *actor, ticcmd_t *cmd) +void DBot::Think () { + ticcmd_t *cmd = &netcmds[player - players][((gametic + 1)/ticdup)%BACKUPTICS]; + memset (cmd, 0, sizeof(*cmd)); - if (actor->player->enemy && actor->player->enemy->health <= 0) - actor->player->enemy = NULL; + if (enemy && enemy->health <= 0) + enemy = NULL; - if (actor->health > 0) //Still alive + if (player->mo->health > 0) //Still alive { if (teamplay || !deathmatch) - actor->player->mate = Choose_Mate (actor); + mate = Choose_Mate (); - angle_t oldyaw = actor->angle; - int oldpitch = actor->pitch; + angle_t oldyaw = player->mo->angle; + int oldpitch = player->mo->pitch; - Set_enemy (actor); - ThinkForMove (actor, cmd); - TurnToAng (actor); + Set_enemy (); + ThinkForMove (cmd); + TurnToAng (); - cmd->ucmd.yaw = (short)((actor->angle - oldyaw) >> 16) / ticdup; - cmd->ucmd.pitch = (short)((oldpitch - actor->pitch) >> 16); + cmd->ucmd.yaw = (short)((player->mo->angle - oldyaw) >> 16) / ticdup; + cmd->ucmd.pitch = (short)((oldpitch - player->mo->pitch) >> 16); if (cmd->ucmd.pitch == -32768) cmd->ucmd.pitch = -32767; cmd->ucmd.pitch /= ticdup; - actor->angle = oldyaw + (cmd->ucmd.yaw << 16) * ticdup; - actor->pitch = oldpitch - (cmd->ucmd.pitch << 16) * ticdup; + player->mo->angle = oldyaw + (cmd->ucmd.yaw << 16) * ticdup; + player->mo->pitch = oldpitch - (cmd->ucmd.pitch << 16) * ticdup; } - if (actor->player->t_active) actor->player->t_active--; - if (actor->player->t_strafe) actor->player->t_strafe--; - if (actor->player->t_react) actor->player->t_react--; - if (actor->player->t_fight) actor->player->t_fight--; - if (actor->player->t_rocket) actor->player->t_rocket--; - if (actor->player->t_roam) actor->player->t_roam--; + if (t_active) t_active--; + if (t_strafe) t_strafe--; + if (t_react) t_react--; + if (t_fight) t_fight--; + if (t_rocket) t_rocket--; + if (t_roam) t_roam--; //Respawn ticker - if (actor->player->t_respawn) + if (t_respawn) { - actor->player->t_respawn--; + t_respawn--; } - else if (actor->health <= 0) + else if (player->mo->health <= 0) { // Time to respawn cmd->ucmd.buttons |= BT_USE; } @@ -72,62 +74,57 @@ void FCajunMaster::Think (AActor *actor, ticcmd_t *cmd) //how the bot moves. //MAIN movement function. -void FCajunMaster::ThinkForMove (AActor *actor, ticcmd_t *cmd) +void DBot::ThinkForMove (ticcmd_t *cmd) { - player_t *b; fixed_t dist; bool stuck; int r; - b = actor->player; - if (!b->isbot) - return; - stuck = false; - dist = b->dest ? P_AproxDistance(actor->x-b->dest->x, actor->y-b->dest->y) : 0; + dist = dest ? P_AproxDistance(player->mo->x-dest->x, player->mo->y-dest->y) : 0; - if (b->missile && - ((!b->missile->velx || !b->missile->vely) || !Check_LOS(actor, b->missile, SHOOTFOV*3/2))) + if (missile && + ((!missile->velx || !missile->vely) || !Check_LOS(missile, SHOOTFOV*3/2))) { - b->sleft = !b->sleft; - b->missile = NULL; //Probably ended its travel. + sleft = !sleft; + missile = NULL; //Probably ended its travel. } - if (actor->pitch > 0) - actor->pitch -= 80; - else if (actor->pitch <= -60) - actor->pitch += 80; + if (player->mo->pitch > 0) + player->mo->pitch -= 80; + else if (player->mo->pitch <= -60) + player->mo->pitch += 80; //HOW TO MOVE: - if (b->missile && (P_AproxDistance(actor->x-b->missile->x, actor->y-b->missile->y)mo->x-missile->x, player->mo->y-missile->y)missile); - actor->player->angle = R_PointToAngle2(actor->x, actor->y, b->missile->x, b->missile->y); - cmd->ucmd.sidemove = b->sleft ? -SIDERUN : SIDERUN; + Pitch (missile); + angle = R_PointToAngle2(player->mo->x, player->mo->y, missile->x, missile->y); + cmd->ucmd.sidemove = sleft ? -SIDERUN : SIDERUN; cmd->ucmd.forwardmove = -FORWARDRUN; //Back IS best. - if ((P_AproxDistance(actor->x-b->oldx, actor->y-b->oldy)<50000) - && b->t_strafe<=0) + if ((P_AproxDistance(player->mo->x-oldx, player->mo->y-oldy)<50000) + && t_strafe<=0) { - b->t_strafe = 5; - b->sleft = !b->sleft; + t_strafe = 5; + sleft = !sleft; } //If able to see enemy while avoiding missile, still fire at enemy. - if (b->enemy && Check_LOS (actor, b->enemy, SHOOTFOV)) - Dofire (actor, cmd); //Order bot to fire current weapon + if (enemy && Check_LOS (enemy, SHOOTFOV)) + Dofire (cmd); //Order bot to fire current weapon } - else if (b->enemy && P_CheckSight (actor, b->enemy, 0)) //Fight! + else if (enemy && P_CheckSight (player->mo, enemy, 0)) //Fight! { - Pitch (actor, b->enemy); + Pitch (enemy); //Check if it's more important to get an item than fight. - if (b->dest && (b->dest->flags&MF_SPECIAL)) //Must be an item, that is close enough. + if (dest && (dest->flags&MF_SPECIAL)) //Must be an item, that is close enough. { -#define is(x) b->dest->IsKindOf (PClass::FindClass (#x)) +#define is(x) dest->IsKindOf (PClass::FindClass (#x)) if ( ( - (actor->health < b->skill.isp && + (player->mo->health < skill.isp && (is (Medikit) || is (Stimpack) || is (Soulsphere) || @@ -140,78 +137,78 @@ void FCajunMaster::ThinkForMove (AActor *actor, ticcmd_t *cmd) is (Megasphere) ) || dist < (GETINCOMBAT/4) || - (b->ReadyWeapon == NULL || b->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON) + (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON) ) - && (dist < GETINCOMBAT || (b->ReadyWeapon == NULL || b->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) - && Reachable (actor, b->dest)) + && (dist < GETINCOMBAT || (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + && Reachable (dest)) #undef is { goto roam; //Pick it up, no matter the situation. All bonuses are nice close up. } } - b->dest = NULL; //To let bot turn right + dest = NULL; //To let bot turn right - if (b->ReadyWeapon != NULL && !(b->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) - actor->flags &= ~MF_DROPOFF; //Don't jump off any ledges when fighting. + if (player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + player->mo->flags &= ~MF_DROPOFF; //Don't jump off any ledges when fighting. - if (!(b->enemy->flags3 & MF3_ISMONSTER)) - b->t_fight = AFTERTICS; + if (!(enemy->flags3 & MF3_ISMONSTER)) + t_fight = AFTERTICS; - if (b->t_strafe <= 0 && - (P_AproxDistance(actor->x-b->oldx, actor->y-b->oldy)<50000 + if (t_strafe <= 0 && + (P_AproxDistance(player->mo->x-oldx, player->mo->y-oldy)<50000 || ((pr_botmove()%30)==10)) ) { stuck = true; - b->t_strafe = 5; - b->sleft = !b->sleft; + t_strafe = 5; + sleft = !sleft; } - b->angle = R_PointToAngle2(actor->x, actor->y, b->enemy->x, b->enemy->y); + angle = R_PointToAngle2(player->mo->x, player->mo->y, enemy->x, enemy->y); - if (b->ReadyWeapon == NULL || - P_AproxDistance(actor->x-b->enemy->x, actor->y-b->enemy->y) > - b->ReadyWeapon->MoveCombatDist) + if (player->ReadyWeapon == NULL || + P_AproxDistance(player->mo->x-enemy->x, player->mo->y-enemy->y) > + player->ReadyWeapon->MoveCombatDist) { // If a monster, use lower speed (just for cooler apperance while strafing down doomed monster) - cmd->ucmd.forwardmove = (b->enemy->flags3 & MF3_ISMONSTER) ? FORWARDWALK : FORWARDRUN; + cmd->ucmd.forwardmove = (enemy->flags3 & MF3_ISMONSTER) ? FORWARDWALK : FORWARDRUN; } else if (!stuck) //Too close, so move away. { // If a monster, use lower speed (just for cooler apperance while strafing down doomed monster) - cmd->ucmd.forwardmove = (b->enemy->flags3 & MF3_ISMONSTER) ? -FORWARDWALK : -FORWARDRUN; + cmd->ucmd.forwardmove = (enemy->flags3 & MF3_ISMONSTER) ? -FORWARDWALK : -FORWARDRUN; } //Strafing. - if (b->enemy->flags3 & MF3_ISMONSTER) //It's just a monster so take it down cool. + if (enemy->flags3 & MF3_ISMONSTER) //It's just a monster so take it down cool. { - cmd->ucmd.sidemove = b->sleft ? -SIDEWALK : SIDEWALK; + cmd->ucmd.sidemove = sleft ? -SIDEWALK : SIDEWALK; } else { - cmd->ucmd.sidemove = b->sleft ? -SIDERUN : SIDERUN; + cmd->ucmd.sidemove = sleft ? -SIDERUN : SIDERUN; } - Dofire (actor, cmd); //Order bot to fire current weapon + Dofire (cmd); //Order bot to fire current weapon } - else if (b->mate && !b->enemy && (!b->dest || b->dest==b->mate)) //Follow mate move. + else if (mate && !enemy && (!dest || dest==mate)) //Follow mate move. { fixed_t matedist; - Pitch (actor, b->mate); + Pitch (mate); - if (!Reachable (actor, b->mate)) + if (!Reachable (mate)) { - if (b->mate == b->dest && pr_botmove.Random() < 32) + if (mate == dest && pr_botmove.Random() < 32) { // [RH] If the mate is the dest, pick a new dest sometimes - b->dest = NULL; + dest = NULL; } goto roam; } - actor->player->angle = R_PointToAngle2(actor->x, actor->y, b->mate->x, b->mate->y); + angle = R_PointToAngle2(player->mo->x, player->mo->y, mate->x, mate->y); - matedist = P_AproxDistance(actor->x - b->mate->x, actor->y - b->mate->y); + matedist = P_AproxDistance(player->mo->x - mate->x, player->mo->y - mate->y); if (matedist > (FRIEND_DIST*2)) cmd->ucmd.forwardmove = FORWARDRUN; else if (matedist > FRIEND_DIST) @@ -221,42 +218,42 @@ void FCajunMaster::ThinkForMove (AActor *actor, ticcmd_t *cmd) } else //Roam after something. { - b->first_shot = true; + first_shot = true; ///// roam: ///// - if (b->enemy && Check_LOS (actor, b->enemy, SHOOTFOV*3/2)) //If able to see enemy while avoiding missile , still fire at it. - Dofire (actor, cmd); //Order bot to fire current weapon + if (enemy && Check_LOS (enemy, SHOOTFOV*3/2)) //If able to see enemy while avoiding missile , still fire at it. + Dofire (cmd); //Order bot to fire current weapon - if (b->dest && !(b->dest->flags&MF_SPECIAL) && b->dest->health < 0) + if (dest && !(dest->flags&MF_SPECIAL) && dest->health < 0) { //Roaming after something dead. - b->dest = NULL; + dest = NULL; } - if (b->dest == NULL) + if (dest == NULL) { - if (b->t_fight && b->enemy) //Enemy/bot has jumped around corner. So what to do? + if (t_fight && enemy) //Enemy/bot has jumped around corner. So what to do? { - if (b->enemy->player) + if (enemy->player) { - if (((b->enemy->player->ReadyWeapon != NULL && b->enemy->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) || - (pr_botmove()%100)>b->skill.isp) && b->ReadyWeapon != NULL && !(b->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) - b->dest = b->enemy;//Dont let enemy kill the bot by supressive fire. So charge enemy. - else //hide while b->t_fight, but keep view at enemy. - b->angle = R_PointToAngle2(actor->x, actor->y, b->enemy->x, b->enemy->y); + if (((enemy->player->ReadyWeapon != NULL && enemy->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) || + (pr_botmove()%100)>skill.isp) && player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)) + dest = enemy;//Dont let enemy kill the bot by supressive fire. So charge enemy. + else //hide while t_fight, but keep view at enemy. + angle = R_PointToAngle2(player->mo->x, player->mo->y, enemy->x, enemy->y); } //Just a monster, so kill it. else - b->dest = b->enemy; + dest = enemy; - //VerifFavoritWeapon(actor->player); //Dont know why here.., but it must be here, i know the reason, but not why at this spot, uh. + //VerifFavoritWeapon(player); //Dont know why here.., but it must be here, i know the reason, but not why at this spot, uh. } else //Choose a distant target. to get things going. { r = pr_botmove(); if (r < 128) { - TThinkerIterator it (STAT_INVENTORY, firstthing); + TThinkerIterator it (STAT_INVENTORY, bglobal.firstthing); AInventory *item = it.Next(); if (item != NULL || (item = it.Next()) != NULL) @@ -271,60 +268,53 @@ void FCajunMaster::ThinkForMove (AActor *actor, ticcmd_t *cmd) { item = it.Next(); } - firstthing = item; - b->dest = item; + bglobal.firstthing = item; + dest = item; } } - else if (b->mate && (r < 179 || P_CheckSight(actor, b->mate))) + else if (mate && (r < 179 || P_CheckSight(player->mo, mate))) { - b->dest = b->mate; + dest = mate; } else if ((playeringame[(r&(MAXPLAYERS-1))]) && players[(r&(MAXPLAYERS-1))].mo->health > 0) { - b->dest = players[(r&(MAXPLAYERS-1))].mo; + dest = players[(r&(MAXPLAYERS-1))].mo; } } - if (b->dest) + if (dest) { - b->t_roam = MAXROAM; + t_roam = MAXROAM; } } - if (b->dest) + if (dest) { //Bot has a target so roam after it. - Roam (actor, cmd); + Roam (cmd); } } //End of movement main part. - if (!b->t_roam && b->dest) + if (!t_roam && dest) { - b->prev = b->dest; - b->dest = NULL; + prev = dest; + dest = NULL; } - if (b->t_fight<(AFTERTICS/2)) - actor->flags |= MF_DROPOFF; + if (t_fight<(AFTERTICS/2)) + player->mo->flags |= MF_DROPOFF; - b->oldx = actor->x; - b->oldy = actor->y; + oldx = player->mo->x; + oldy = player->mo->y; } //BOT_WhatToGet // //Determines if the bot will roam after an item or not. -void FCajunMaster::WhatToGet (AActor *actor, AActor *item) +void DBot::WhatToGet (AActor *item) { - player_t *b = actor->player; - - if (b == NULL) - { - return; - } - #define typeis(x) item->IsKindOf (PClass::FindClass (#x)) if ((item->renderflags & RF_INVISIBLE) //Under respawn and away. - || item == b->prev) + || item == prev) { return; } @@ -338,7 +328,7 @@ void FCajunMaster::WhatToGet (AActor *actor, AActor *item) // FIXME AWeapon *heldWeapon; - heldWeapon = static_cast (b->mo->FindInventory (item->GetClass())); + heldWeapon = dyn_cast(player->mo->FindInventory(item->GetClass())); if (heldWeapon != NULL) { if (!weapgiveammo) @@ -354,39 +344,38 @@ void FCajunMaster::WhatToGet (AActor *actor, AActor *item) { AAmmo *ammo = static_cast (item); PClassActor *parent = ammo->GetParentAmmo (); - AInventory *holdingammo = b->mo->FindInventory (parent); + AInventory *holdingammo = player->mo->FindInventory (parent); if (holdingammo != NULL && holdingammo->Amount >= holdingammo->MaxAmount) { return; } } - else if ((typeis (Megasphere) || typeis (Soulsphere) || typeis (HealthBonus)) && actor->health >= deh.MaxSoulsphere) + else if ((typeis (Megasphere) || typeis (Soulsphere) || typeis (HealthBonus)) && player->mo->health >= deh.MaxSoulsphere) return; - else if (item->IsKindOf (RUNTIME_CLASS(AHealth)) && actor->health >= deh.MaxHealth /*MAXHEALTH*/) + else if (item->IsKindOf (RUNTIME_CLASS(AHealth)) && player->mo->health >= deh.MaxHealth /*MAXHEALTH*/) return; - if ((b->dest == NULL || - !(b->dest->flags & MF_SPECIAL)/* || - !Reachable (actor, b->dest)*/)/* && - Reachable (actor, item)*/) // Calling Reachable slows this down tremendously + if ((dest == NULL || + !(dest->flags & MF_SPECIAL)/* || + !Reachable (dest)*/)/* && + Reachable (item)*/) // Calling Reachable slows this down tremendously { - b->prev = b->dest; - b->dest = item; - b->t_roam = MAXROAM; + prev = dest; + dest = item; + t_roam = MAXROAM; } } -void FCajunMaster::Set_enemy (AActor *actor) +void DBot::Set_enemy () { AActor *oldenemy; - AActor **enemy = &actor->player->enemy; - if (*enemy - && (*enemy)->health > 0 - && P_CheckSight (actor, *enemy)) + if (enemy + && enemy->health > 0 + && P_CheckSight (player->mo, enemy)) { - oldenemy = *enemy; + oldenemy = enemy; } else { @@ -395,15 +384,14 @@ void FCajunMaster::Set_enemy (AActor *actor) // [RH] Don't even bother looking for a different enemy if this is not deathmatch // and we already have an existing enemy. - if (deathmatch || !*enemy) + if (deathmatch || !enemy) { - actor->player->allround = !!*enemy; - *enemy = NULL; - *enemy = Find_enemy(actor); - if (!*enemy) - *enemy = oldenemy; //Try go for last (it will be NULL if there wasn't anyone) + allround = !!enemy; + enemy = Find_enemy(); + if (!enemy) + enemy = oldenemy; //Try go for last (it will be NULL if there wasn't anyone) } //Verify that that enemy is really something alive that bot can kill. - if (*enemy && (((*enemy)->health < 0 || !((*enemy)->flags&MF_SHOOTABLE)) || actor->IsFriend(*enemy))) - *enemy = NULL; + if (enemy && ((enemy->health < 0 || !(enemy->flags&MF_SHOOTABLE)) || player->mo->IsFriend(enemy))) + enemy = NULL; } diff --git a/src/c_bind.cpp b/src/c_bind.cpp index 0744efeee..66ed14ca5 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -230,7 +230,11 @@ const char *KeyNames[NUM_KEYS] = NULL, NULL, NULL, NULL, NULL, "pause", NULL, "home", //C0 "uparrow", "pgup", NULL, "leftarrow",NULL, "rightarrow",NULL, "end", //C8 "downarrow","pgdn", "ins", "del", NULL, NULL, NULL, NULL, //D0 +#ifdef __APPLE__ + NULL, NULL, NULL, "command", NULL, "apps", "power", "sleep", //D8 +#else // !__APPLE__ NULL, NULL, NULL, "lwin", "rwin", "apps", "power", "sleep", //D8 +#endif // __APPLE__ NULL, NULL, NULL, "wake", NULL, "search", "favorites","refresh", //E0 "webstop", "webforward","webback", "mycomputer","mail", "mediaselect",NULL, NULL, //E8 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, //F0 diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 2cff38145..b323259bc 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -122,6 +122,15 @@ CCMD (god) Net_WriteByte (CHT_GOD); } +CCMD(god2) +{ + if (CheckCheatmode()) + return; + + Net_WriteByte(DEM_GENERICCHEAT); + Net_WriteByte(CHT_GOD2); +} + CCMD (iddqd) { if (CheckCheatmode ()) @@ -140,6 +149,15 @@ CCMD (buddha) Net_WriteByte(CHT_BUDDHA); } +CCMD(buddha2) +{ + if (CheckCheatmode()) + return; + + Net_WriteByte(DEM_GENERICCHEAT); + Net_WriteByte(CHT_BUDDHA2); +} + CCMD (notarget) { if (CheckCheatmode ()) diff --git a/src/c_console.cpp b/src/c_console.cpp index e7c7dc30b..e3e3d98fe 100644 --- a/src/c_console.cpp +++ b/src/c_console.cpp @@ -561,6 +561,11 @@ int PrintString (int printlevel, const char *outline) maybedrawnow (false, false); } } + else if (Logfile != NULL) + { + fputs (outline, Logfile); + fflush (Logfile); + } return (int)strlen (outline); } @@ -1421,7 +1426,11 @@ static bool C_HandleKey (event_t *ev, BYTE *buffer, int len) case 'V': TabbedLast = false; TabbedList = false; +#ifdef __APPLE__ + if (ev->data3 & GKM_META) +#else // !__APPLE__ if (ev->data3 & GKM_CTRL) +#endif // __APPLE__ { if (data1 == 'C') { // copy to clipboard @@ -1545,13 +1554,6 @@ void C_MidPrint (FFont *font, const char *msg) AddToConsole (-1, bar1); AddToConsole (-1, msg); AddToConsole (-1, bar3); - if (Logfile) - { - fputs (logbar, Logfile); - fputs (msg, Logfile); - fputs (logbar, Logfile); - fflush (Logfile); - } StatusBar->AttachMessage (new DHUDMessage (font, msg, 1.5f, 0.375f, 0, 0, (EColorRange)PrintColors[PRINTLEVELS], con_midtime), MAKE_ID('C','N','T','R')); @@ -1569,13 +1571,6 @@ void C_MidPrintBold (FFont *font, const char *msg) AddToConsole (-1, bar2); AddToConsole (-1, msg); AddToConsole (-1, bar3); - if (Logfile) - { - fputs (logbar, Logfile); - fputs (msg, Logfile); - fputs (logbar, Logfile); - fflush (Logfile); - } StatusBar->AttachMessage (new DHUDMessage (font, msg, 1.5f, 0.375f, 0, 0, (EColorRange)PrintColors[PRINTLEVELS+1], con_midtime), MAKE_ID('C','N','T','R')); diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index 19187353f..c770bcbcf 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -500,9 +500,10 @@ UCVarValue FBaseCVar::FromString (const char *value, ECVarType type) goodv = false; break; default: - if (value[i] < '0' && value[i] > '9' && - value[i] < 'A' && value[i] > 'F' && - value[i] < 'a' && value[i] > 'f') + if (value[i] < '0' || + (value[i] > '9' && value[i] < 'A') || + (value[i] > 'F' && value[i] < 'a') || + value[i] > 'f') { goodv = false; } @@ -1514,6 +1515,22 @@ void UnlatchCVars (void) } } +void DestroyCVarsFlagged (DWORD flags) +{ + FBaseCVar *cvar = CVars; + FBaseCVar *next = cvar; + + while(cvar) + { + next = cvar->m_Next; + + if(cvar->Flags & flags) + delete cvar; + + cvar = next; + } +} + void C_SetCVarsToDefaults (void) { FBaseCVar *cvar = CVars; diff --git a/src/c_cvars.h b/src/c_cvars.h index 17d3f7b1d..ed2b16755 100644 --- a/src/c_cvars.h +++ b/src/c_cvars.h @@ -159,6 +159,7 @@ private: friend FBaseCVar *FindCVar (const char *var_name, FBaseCVar **prev); friend FBaseCVar *FindCVarSub (const char *var_name, int namelen); friend void UnlatchCVars (void); + friend void DestroyCVarsFlagged (DWORD flags); friend void C_ArchiveCVars (FConfigFile *f, uint32 filter); friend void C_SetCVarsToDefaults (void); friend void FilterCompactCVars (TArray &cvars, uint32 filter); @@ -190,6 +191,9 @@ FBaseCVar *C_CreateCVar(const char *var_name, ECVarType var_type, DWORD flags); // Called from G_InitNew() void UnlatchCVars (void); +// Destroy CVars with the matching flags; called from CCMD(restart) +void DestroyCVarsFlagged (DWORD flags); + // archive cvars to FILE f void C_ArchiveCVars (FConfigFile *f, uint32 filter); diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 105d8c0d3..65e017bdc 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -723,7 +723,7 @@ void AddCommandString (char *cmd, int keynum) // Note that deferred commands lose track of which key // (if any) they were pressed from. *brkpt = ';'; - new DWaitingCommand (brkpt, tics+1); + new DWaitingCommand (brkpt, tics); } return; } diff --git a/src/cmdlib.h b/src/cmdlib.h index e3a9d3d08..6df893580 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -5,6 +5,8 @@ #include "doomtype.h" +#include "doomdef.h" +#include "m_fixed.h" #include #include @@ -62,4 +64,25 @@ struct FFileList void ScanDirectory(TArray &list, const char *dirpath); + +//========================================================================== +// +// Functions to compensate for a tic being a bit short. +// Since ZDoom uses a milliseconds timer for game timing +// 35 tics are actually only 0.98 seconds. +// For real time display this needs to be adjusted +// +//========================================================================== + +inline int AdjustTics(int tics) +{ + return Scale(tics, 98, 100); +} + +inline int Tics2Seconds(int tics) +{ + return Scale(tics, 98, (100 * TICRATE)); +} + + #endif diff --git a/src/cocoa/HID_Config_Utilities.c b/src/cocoa/HID_Config_Utilities.c new file mode 100644 index 000000000..18d4672d3 --- /dev/null +++ b/src/cocoa/HID_Config_Utilities.c @@ -0,0 +1,926 @@ +// File: HID_Config_Utilities.c +// Abstract: Implementation of the HID configuration utilities +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#define LOG_SCORING 0 + +#include // malloc +#include // clock + +#include + +#include "HID_Utilities_External.h" + +// --------------------------------- + +// polls single device's elements for a change greater than kPercentMove. Times out after given time +// returns 1 and pointer to element if found +// returns 0 and NULL for both parameters if not found + +unsigned char HIDConfigureSingleDeviceAction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef, float timeout) { + if ( !inIOHIDDeviceRef ) { + return (0); + } + if ( 0 == HIDHaveDeviceList() ) { // if we do not have a device list + return (0); // return 0 + } + + Boolean found = FALSE; + + // build list of device and elements to save current values + int maxElements = HIDCountDeviceElements(inIOHIDDeviceRef, kHIDElementTypeInput); + int *saveValueArray = (int *) calloc( maxElements, sizeof(int) ); // 2D array to save values + + // store initial values on first pass / compare to initial value on subsequent passes + Boolean first = TRUE; + + // get all the elements from this device + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + // if that worked... + if ( elementCFArrayRef ) { + clock_t start = clock(), end; + + // poll all devices and elements + while ( !found ) { + int currElementIndex = 0; + CFIndex idx, cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + *outIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !*outIOHIDElementRef ) { + continue; + } + + // is this an input element? + IOHIDElementType type = IOHIDElementGetType(*outIOHIDElementRef); + + switch ( type ) { + // these types are inputs + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + default: + { + break; + } + case kIOHIDElementTypeOutput: + case kIOHIDElementTypeFeature: + case kIOHIDElementTypeCollection: + { + *outIOHIDElementRef = NULL; // these types are not ( Skip them ) + break; + } + } /* switch */ + if ( !*outIOHIDElementRef ) { + continue; // skip this element + } + + // get this elements current value + int value = 0; // default value is zero + IOHIDValueRef tIOHIDValueRef; + IOReturn ioReturn = IOHIDDeviceGetValue(inIOHIDDeviceRef, *outIOHIDElementRef, &tIOHIDValueRef); + if ( kIOReturnSuccess == ioReturn ) { + value = IOHIDValueGetScaledValue(tIOHIDValueRef, kIOHIDValueScaleTypePhysical); + } + if ( first ) { + saveValueArray[currElementIndex] = value; + } else { + CFIndex min = IOHIDElementGetLogicalMin(*outIOHIDElementRef); + CFIndex max = IOHIDElementGetLogicalMax(*outIOHIDElementRef); + + int initialValue = saveValueArray[currElementIndex]; + int delta = (float)(max - min) * kPercentMove * 0.01; + // is the new value within +/- delta of the initial value? + if ( ( (initialValue + delta) < value ) || ( (initialValue - delta) > value ) ) { + found = 1; // ( yes! ) mark as found + break; + } + } // if ( first ) + + currElementIndex++; // bump element index + } // next idx + + first = FALSE; // no longer the first pass + + // are we done? + end = clock(); + double secs = (double)(end - start) / CLOCKS_PER_SEC; + if ( secs > timeout ) { + break; // ( yes ) timeout + } + } // while ( !found ) + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + // return device and element moved + if ( found ) { + return (1); + } else { + *outIOHIDElementRef = NULL; + return (0); + } +} // HIDConfigureSingleDeviceAction + +//************************************************************************* +// +// HIDConfigureAction( outIOHIDDeviceRef, outIOHIDElementRef, inTimeout ) +// +// Purpose: polls all devices and elements for a change greater than kPercentMove. +// Times out after given time returns 1 and pointer to device and element +// if found; returns 0 and NULL for both parameters if not found +// +// Inputs: outIOHIDDeviceRef - address where to store the device +// outIOHIDElementRef - address where to store the element +// inTimeout - the timeout +// Returns: Boolean - if successful +// outIOHIDDeviceRef - the device +// outIOHIDElementRef - the element +// + +Boolean HIDConfigureAction(IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef, float inTimeout) { + // param error? + if ( !outIOHIDDeviceRef || !outIOHIDElementRef ) { + return (0); + } + if ( !gDeviceCFArrayRef ) { // if we do not have a device list + // and we can't build another list + if ( !HIDBuildDeviceList(0, 0) || !gDeviceCFArrayRef ) { + return (FALSE); // bail + } + } + + IOHIDDeviceRef tIOHIDDeviceRef; + IOHIDElementRef tIOHIDElementRef; + + // remember when we start; used to calculate timeout + clock_t start = clock(), end; + + // determine the maximum number of elements + CFIndex maxElements = 0; + CFIndex devIndex, devCount = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; // skip this one + } + + CFIndex count = HIDCountDeviceElements(tIOHIDDeviceRef, kHIDElementTypeInput); + if ( count > maxElements ) { + maxElements = count; + } + } + + // allocate an array of int's in which to store devCount * maxElements values + double *saveValueArray = (double *) calloc( devCount * maxElements, sizeof(double) ); // clear 2D array to save values + + // on first pass store initial values / compare current values to initial values on subsequent passes + Boolean found = FALSE, first = TRUE; + + while ( !found ) { + double maxDeltaPercent = 0; // we want to find the one that moves the most ( percentage wise ) + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; // skip this one + } + +#ifdef DEBUG + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); +#endif + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex eleIndex, eleCount = CFArrayGetCount(gElementCFArrayRef); + for ( eleIndex = 0; eleIndex < eleCount; eleIndex++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, eleIndex); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType tIOHIDElementType = IOHIDElementGetType(tIOHIDElementRef); + // only care about inputs (no outputs or features) + if ( tIOHIDElementType <= kIOHIDElementTypeInput_ScanCodes ) { + if ( IOHIDElementIsArray(tIOHIDElementRef) ) { + //printf( "ARRAY!\n" ); + continue; // skip array elements + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + uint32_t reportCount = IOHIDElementGetReportCount(tIOHIDElementRef); +#ifdef DEBUG + if ( first ) { + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + printf("%s, dev: {ref:%p, ven: 0x%08lX, pro: 0x%08lX}, ele: {ref:%p, cookie: %p, usage:%04lX:%08lX}\n", + __PRETTY_FUNCTION__, + tIOHIDDeviceRef, + vendorID, + productID, + tIOHIDElementRef, + cookie, + (long unsigned int) usagePage, + (long unsigned int) usage); + fflush(stdout); + if ( (0x054C == vendorID) && (0x0268 == productID) && (0x001E == (UInt32) cookie) ) { + //printf( "DING!\n" ); + } + } + +#endif +#if 1 // work-around for IOHIDValueGetScaledValue crash (when element report count > 1) + if ( reportCount > 1 ) { + //printf( "REPORT!\n" ); + continue; // skip reports + } + +#endif + // ignore PID elements and arrays + if ( (kHIDPage_PID != usagePage) && (((uint32_t)-1) != usage) ) { + // get this elements current value + double value = 0.0; // default value is zero + IOHIDValueRef tIOHIDValueRef; + IOReturn ioReturn = IOHIDDeviceGetValue(tIOHIDDeviceRef, tIOHIDElementRef, &tIOHIDValueRef); + if ( kIOReturnSuccess == ioReturn ) { + value = IOHIDValueGetScaledValue(tIOHIDValueRef, kIOHIDValueScaleTypePhysical); + } + if ( first ) { + saveValueArray[(devIndex * maxElements) + eleIndex] = value; + } else { + double initialValue = saveValueArray[(devIndex * maxElements) + eleIndex]; + + CFIndex valueMin = IOHIDElementGetPhysicalMin(tIOHIDElementRef); + CFIndex valueMax = IOHIDElementGetPhysicalMax(tIOHIDElementRef); + + double deltaPercent = fabs( (initialValue - value) * 100.0 / (valueMax - valueMin) ); +#if 0 // debug code useful to dump out value info for specific (vendorID, productID, usagePage and usage) device + if ( !first ) { + // Device: 0x13b6a0 = { Logitech Inc. - WingMan Force 3D, vendorID: 0x046D, productID: 0xC283, + // usage: 0x0001:0x0004, "Generic Desktop Joystick" + if ( (vendorID == 0x046D) && (productID == 0xC283) ) { + if ( (kHIDPage_GenericDesktop == usagePage) && (kHIDUsage_GD_Rz == usage) ) { + printf("initial: %6.2f, value: %6.2f, diff: %6.2f, delta percent: %6.2f!\n", + initialValue, + value, + fabs(initialValue - value), + deltaPercent); + } + } + } + + deltaPercent = 0.0; +#endif + if ( deltaPercent >= kPercentMove ) { + found = TRUE; + if ( deltaPercent > maxDeltaPercent ) { + maxDeltaPercent = deltaPercent; + + *outIOHIDDeviceRef = tIOHIDDeviceRef; + *outIOHIDElementRef = tIOHIDElementRef; + } + + break; + } + } // if first + + } // if usage + + } // if type + + } // for elements... + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + if ( found ) { + // HIDDumpElementInfo( tIOHIDElementRef ); + break; // DONE! + } + } // for devices + + first = FALSE; // no longer the first pass + + // are we done? + end = clock(); + double secs = (double)(end - start) / CLOCKS_PER_SEC; + if ( secs > inTimeout ) { + break; // ( yes ) timeout + } + } // while ( !found ) + // return device and element moved + if ( !found ) { + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + } + + return (found); +} // HIDConfigureAction + +//************************************************************************* +// +// HIDSaveElementPref( inKeyCFStringRef, inAppCFStringRef, inIOHIDDeviceRef, inIOHIDElementRef ) +// +// Purpose: Save the device & element values into the specified key in the specified applications preferences +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// inIOHIDDeviceRef - the device +// inIOHIDElementRef - the element +// Returns: Boolean - if successful +// + +Boolean HIDSaveElementPref(const CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHIDElementRef) { + Boolean success = FALSE; + if ( inKeyCFStringRef && inAppCFStringRef && inIOHIDDeviceRef && inIOHIDElementRef ) { + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + require(vendorID, Oops); + + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + require(productID, Oops); + + long locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + require(locID, Oops); + + uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef); + } + + require(usagePage && usage, Oops); + + uint32_t usagePageE = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usageE = IOHIDElementGetUsage(inIOHIDElementRef); + IOHIDElementCookie eleCookie = IOHIDElementGetCookie(inIOHIDElementRef); + + CFStringRef prefCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, + CFSTR("d:{v:%ld, p:%ld, l:%ld, p:%ld, u:%ld}, e:{p:%ld, u:%ld, c:%ld}"), + vendorID, productID, locID, usagePage, usage, + usagePageE, usageE, eleCookie); + if ( prefCFStringRef ) { + CFPreferencesSetAppValue(inKeyCFStringRef, prefCFStringRef, inAppCFStringRef); + CFRelease(prefCFStringRef); + success = TRUE; + } + } + +Oops: ; + return (success); +} // HIDSaveElementPref + +//************************************************************************* +// +// HIDRestoreElementPref( inKeyCFStringRef, inAppCFStringRef, outIOHIDDeviceRef, outIOHIDElementRef ) +// +// Purpose: Find the specified preference in the specified application +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// outIOHIDDeviceRef - address where to restore the device +// outIOHIDElementRef - address where to restore the element +// Returns: Boolean - if successful +// outIOHIDDeviceRef - the device +// outIOHIDElementRef - the element +// + +Boolean HIDRestoreElementPref(CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef * outIOHIDDeviceRef, + IOHIDElementRef *outIOHIDElementRef) { + Boolean found = FALSE; + if ( inKeyCFStringRef && inAppCFStringRef && outIOHIDDeviceRef && outIOHIDElementRef ) { + CFPropertyListRef prefCFPropertyListRef = CFPreferencesCopyAppValue(inKeyCFStringRef, inAppCFStringRef); + if ( prefCFPropertyListRef ) { + if ( CFStringGetTypeID() == CFGetTypeID(prefCFPropertyListRef) ) { + char buffer[256]; + if ( CFStringGetCString( (CFStringRef) prefCFPropertyListRef, buffer, sizeof(buffer), + kCFStringEncodingUTF8 ) ) + { + HID_info_rec searchHIDInfo; + + int count = sscanf(buffer, + "d:{v:%d, p:%d, l:%d, p:%d, u:%d}, e:{p:%d, u:%d, c:%ld}", + &searchHIDInfo.device.vendorID, + &searchHIDInfo.device.productID, + &searchHIDInfo.device.locID, + &searchHIDInfo.device.usagePage, + &searchHIDInfo.device.usage, + &searchHIDInfo.element.usagePage, + &searchHIDInfo.element.usage, + (long *) &searchHIDInfo.element.cookie); + if ( 8 == count ) { // if we found all eight parameters… + // and can find a device & element that matches these… + if ( HIDFindDeviceAndElement(&searchHIDInfo, outIOHIDDeviceRef, outIOHIDElementRef) ) { + found = TRUE; + } + } + } + } else { + // We found the entry with this key but it's the wrong type; delete it. + CFPreferencesSetAppValue(inKeyCFStringRef, NULL, inAppCFStringRef); + (void) CFPreferencesAppSynchronize(inAppCFStringRef); + } + + CFRelease(prefCFPropertyListRef); + } + } + + return (found); +} // HIDRestoreElementPref + +//************************************************************************* +// +// HIDFindDeviceAndElement( inSearchInfo, outFoundDevice, outFoundElement ) +// +// Purpose: find the closest matching device and element for this action +// +// Notes: matches device: serial, vendorID, productID, location, inUsagePage, usage +// matches element: cookie, inUsagePage, usage, +// +// Inputs: inSearchInfo - the device & element info we searching for +// outFoundDevice - the address of the best matching device +// outFoundElement - the address of the best matching element +// +// Returns: Boolean - TRUE if we find a match +// outFoundDevice - the best matching device +// outFoundElement - the best matching element +// + +Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo, IOHIDDeviceRef *outFoundDevice, IOHIDElementRef *outFoundElement) { + Boolean result = FALSE; + + IOHIDDeviceRef bestIOHIDDeviceRef = NULL; + IOHIDElementRef bestIOHIDElementRef = NULL; + long bestScore = 0; + + CFIndex devIndex, devCount = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIndex = 0; devIndex < devCount; devIndex++ ) { + long deviceScore = 1; + + IOHIDDeviceRef tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); + if ( !tIOHIDDeviceRef ) { + continue; + } + // match vendorID, productID (+10, +8) + if ( inSearchInfo->device.vendorID ) { + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + if ( vendorID ) { + if ( inSearchInfo->device.vendorID == vendorID ) { + deviceScore += 10; + if ( inSearchInfo->device.productID ) { + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( productID ) { + if ( inSearchInfo->device.productID == productID ) { + deviceScore += 8; + } // if ( inSearchInfo->device.productID == productID ) + + } // if ( productID ) + + } // if ( inSearchInfo->device.productID ) + + } // if (inSearchInfo->device.vendorID == vendorID) + + } // if vendorID + + } // if search->device.vendorID + // match usagePage & usage (+9) + if ( inSearchInfo->device.usagePage && inSearchInfo->device.usage ) { + uint32_t usagePage = IOHIDDevice_GetUsagePage(tIOHIDDeviceRef) ; + uint32_t usage = IOHIDDevice_GetUsage(tIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(tIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(tIOHIDDeviceRef); + } + if ( usagePage ) { + if ( inSearchInfo->device.usagePage == usagePage ) { + if ( usage ) { + if ( inSearchInfo->device.usage == usage ) { + deviceScore += 9; + } // if ( inSearchInfo->usage == usage ) + + } // if ( usage ) + + } // if ( inSearchInfo->usagePage == usagePage ) + + } // if ( usagePage ) + + } // if ( inSearchInfo->usagePage && inSearchInfo->usage ) + // match location ID (+5) + if ( inSearchInfo->device.locID ) { + long locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef); + if ( locID ) { + if ( inSearchInfo->device.locID == locID ) { + deviceScore += 5; + } + } + } + + // iterate over all elements of this device + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, 0); + if ( gElementCFArrayRef ) { + CFIndex eleIndex, eleCount = CFArrayGetCount(gElementCFArrayRef); + for ( eleIndex = 0; eleIndex < eleCount; eleIndex++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, eleIndex); + if ( !tIOHIDElementRef ) { + continue; + } + + long score = deviceScore; + // match usage page, usage & cookie + if ( inSearchInfo->element.usagePage && inSearchInfo->element.usage ) { + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + if ( inSearchInfo->element.usagePage == usagePage ) { + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( inSearchInfo->element.usage == usage ) { + score += 5; + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inSearchInfo->element.cookie == cookie ) { + score += 4; + } // cookies match + + } else { + score = 0; + } // usages match + + } else { + score = 0; + } // usage pages match + + } // if ( search usage page & usage ) + +#if LOG_SCORING + if ( kHIDPage_KeyboardOrKeypad != tElementRef->usagePage ) { // skip keyboards here + printf("%s: ( %ld:%ld )-I-Debug, score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + score); + HIDPrintElement(tIOHIDElementRef); + } + +#endif // LOG_SCORING + if ( score > bestScore ) { + bestIOHIDDeviceRef = tIOHIDDeviceRef; + bestIOHIDElementRef = tIOHIDElementRef; + bestScore = score; +#if LOG_SCORING + printf("%s: ( %ld:%ld )-I-Debug, better score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + score); + HIDPrintElement(bestIOHIDElementRef); +#endif // LOG_SCORING + } + } // for elements... + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // for ( devIndex = 0; devIndex < devCount; devIndex++ ) + if ( bestIOHIDDeviceRef || bestIOHIDElementRef ) { + *outFoundDevice = bestIOHIDDeviceRef; + *outFoundElement = bestIOHIDElementRef; +#if LOG_SCORING + printf("%s: ( %ld:%ld )-I-Debug, best score: %ld\t", + __PRETTY_FUNCTION__, + inSearchInfo->element.usagePage, + inSearchInfo->element.usage, + bestScore); + HIDPrintElement(bestIOHIDElementRef); +#endif // LOG_SCORING + result = TRUE; + } + + return (result); +} // HIDFindDeviceAndElement + +// --------------------------------- + +// takes input records, save required info +// assume file is open and at correct position. +// will always write to file (if file exists) size of HID_info_rec, even if device and or element is bad + +void HIDSaveElementConfig(FILE *fileRef, IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef, int actionCookie) { + // must save: + // actionCookie + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + HID_info_rec hidInfoRec; + HIDSetElementConfig(&hidInfoRec, inIOHIDDeviceRef, inIOHIDElementRef, actionCookie); + // write to file + if ( fileRef ) { + fwrite( (void *)&hidInfoRec, sizeof(HID_info_rec), 1, fileRef ); + } +} // HIDSaveElementConfig + +// --------------------------------- + +// take file, read one record (assume file position is correct and file is open) +// search for matching device +// return pDevice, pElement and cookie for action + +int HIDRestoreElementConfig(FILE *fileRef, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef) { + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + + HID_info_rec hidInfoRec; + fread( (void *) &hidInfoRec, 1, sizeof(HID_info_rec), fileRef ); + return ( HIDGetElementConfig(&hidInfoRec, outIOHIDDeviceRef, outIOHIDElementRef) ); +} // HIDRestoreElementConfig + +// --------------------------------- + +// Set up a config record for saving +// takes an input records, returns record user can save as they want +// Note: the save rec must be pre-allocated by the calling app and will be filled out +void HIDSetElementConfig(HID_info_ptr inHIDInfoPtr, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHIDElementRef, + int actionCookie) { + // must save: + // actionCookie + // Device: serial,vendorID, productID, location, usagePage, usage + // Element: cookie, usagePage, usage, + inHIDInfoPtr->actionCookie = actionCookie; + // device + // need to add serial number when I have a test case + if ( inIOHIDDeviceRef && inIOHIDElementRef ) { + inHIDInfoPtr->device.vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + inHIDInfoPtr->device.productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + inHIDInfoPtr->device.locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + inHIDInfoPtr->device.usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + inHIDInfoPtr->device.usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + + inHIDInfoPtr->element.usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + inHIDInfoPtr->element.usage = IOHIDElementGetUsage(inIOHIDElementRef); + inHIDInfoPtr->element.minReport = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef); + inHIDInfoPtr->element.maxReport = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef); + inHIDInfoPtr->element.cookie = IOHIDElementGetCookie(inIOHIDElementRef); + } else { + inHIDInfoPtr->device.vendorID = 0; + inHIDInfoPtr->device.productID = 0; + inHIDInfoPtr->device.locID = 0; + inHIDInfoPtr->device.usage = 0; + inHIDInfoPtr->device.usagePage = 0; + + inHIDInfoPtr->element.usagePage = 0; + inHIDInfoPtr->element.usage = 0; + inHIDInfoPtr->element.minReport = 0; + inHIDInfoPtr->element.maxReport = 0; + inHIDInfoPtr->element.cookie = 0; + } +} // HIDSetElementConfig + +// --------------------------------- +#if 0 // debug utility function to dump config record +void HIDDumpConfig(HID_info_ptr inHIDInfoPtr) { + printf( + "Config Record for action: %d\n\t vendor: %d product: %d location: %d\n\t usage: %d usagePage: %d\n\t element.usagePage: %d element.usage: %d\n\t minReport: %d maxReport: %d\n\t cookie: %d\n", + inHIDInfoPtr->actionCookie, + inHIDInfoPtr->device.vendorID, + inHIDInfoPtr->device.productID, + inHIDInfoPtr->locID, + inHIDInfoPtr->usage, + inHIDInfoPtr->usagePage, + inHIDInfoPtr->element.usagePage, + inHIDInfoPtr->element.usage, + inHIDInfoPtr->minReport, + inHIDInfoPtr->maxReport, + inHIDInfoPtr->cookie); +} // HIDDumpConfig +#endif // 0 +// --------------------------------- + +// Get matching element from config record +// takes a pre-allocated and filled out config record +// search for matching device +// return pDevice, pElement and cookie for action +int HIDGetElementConfig(HID_info_ptr inHIDInfoPtr, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef) { + if ( !inHIDInfoPtr->device.locID && !inHIDInfoPtr->device.vendorID && !inHIDInfoPtr->device.productID && !inHIDInfoPtr->device.usage + && !inHIDInfoPtr->device.usagePage ) // + { // + // early out + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + return (inHIDInfoPtr->actionCookie); + } + + IOHIDDeviceRef tIOHIDDeviceRef, foundIOHIDDeviceRef = NULL; + IOHIDElementRef tIOHIDElementRef, foundIOHIDElementRef = NULL; + + CFIndex devIdx, devCnt, idx, cnt; + // compare to current device list for matches + // look for device + if ( inHIDInfoPtr->device.locID && inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID ) { // look for specific device + // type plug in to same port + devCnt = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIdx = 0; devIdx < devCnt; devIdx++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIdx); + if ( !tIOHIDDeviceRef ) { + continue; // skip this device + } + + long locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef); + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( (inHIDInfoPtr->device.locID == locID) + && (inHIDInfoPtr->device.vendorID == vendorID) + && (inHIDInfoPtr->device.productID == productID) ) + { + foundIOHIDDeviceRef = tIOHIDDeviceRef; + } + if ( foundIOHIDDeviceRef ) { + break; + } + } // next devIdx + if ( foundIOHIDDeviceRef ) { + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( elementCFArrayRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inHIDInfoPtr->element.cookie == cookie ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } + if ( !foundIOHIDElementRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( (inHIDInfoPtr->element.usage == usage) && (inHIDInfoPtr->element.usagePage == usagePage) ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } // next idx + + } // if ( !foundIOHIDElementRef ) + if ( foundIOHIDElementRef ) { // if same device + // setup the calibration + IOHIDElement_SetupCalibration(tIOHIDElementRef); + + IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef, inHIDInfoPtr->element.minReport); + IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef, inHIDInfoPtr->element.maxReport); + } + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + + } // if ( foundIOHIDDeviceRef ) + // if we have not found a match, look at just vendor and product + if ( (!foundIOHIDDeviceRef) && (inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID) ) { + devCnt = CFArrayGetCount(gDeviceCFArrayRef); + for ( devIdx = 0; devIdx < devCnt; devIdx++ ) { + tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIdx); + if ( !tIOHIDDeviceRef ) { + continue; // skip this device + } + + long vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); + if ( (inHIDInfoPtr->device.vendorID == vendorID) + && (inHIDInfoPtr->device.productID == productID) ) + { + foundIOHIDDeviceRef = tIOHIDDeviceRef; + } + if ( foundIOHIDDeviceRef ) { + break; + } + } + // match elements by cookie since same device type + if ( foundIOHIDDeviceRef ) { + CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( elementCFArrayRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); + if ( inHIDInfoPtr->element.cookie == cookie ) { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } + // if no cookie match (should NOT occur) match on usage + if ( !foundIOHIDElementRef ) { + cnt = CFArrayGetCount(elementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; // skip this element + } + + uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); + if ( (inHIDInfoPtr->element.usage == usage) + && (inHIDInfoPtr->element.usagePage == usagePage) ) + { + foundIOHIDElementRef = tIOHIDElementRef; + } + if ( foundIOHIDElementRef ) { + break; + } + } // next idx + + } // if ( !foundIOHIDElementRef ) + if ( foundIOHIDElementRef ) { // if same device + // setup the calibration + IOHIDElement_SetupCalibration(tIOHIDElementRef); + IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef, inHIDInfoPtr->element.minReport); + IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef, inHIDInfoPtr->element.maxReport); + } + + CFRelease(elementCFArrayRef); + } // if ( elementCFArrayRef ) + + } // if ( foundIOHIDDeviceRef ) + + } // if ( device not found & vendorID & productID ) + + } // if ( inHIDInfoPtr->locID && inHIDInfoPtr->device.vendorID && inHIDInfoPtr->device.productID ) + // can't find matching device return NULL, do not return first device + if ( (!foundIOHIDDeviceRef) || (!foundIOHIDElementRef) ) { + // no HID device + *outIOHIDDeviceRef = NULL; + *outIOHIDElementRef = NULL; + return (inHIDInfoPtr->actionCookie); + } else { + // HID device + *outIOHIDDeviceRef = foundIOHIDDeviceRef; + *outIOHIDElementRef = foundIOHIDElementRef; + return (inHIDInfoPtr->actionCookie); + } +} // HIDGetElementConfig + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Error_Handler.c b/src/cocoa/HID_Error_Handler.c new file mode 100644 index 000000000..0802a2ca7 --- /dev/null +++ b/src/cocoa/HID_Error_Handler.c @@ -0,0 +1,108 @@ +// File: HID_Error_Handler.c +// Abstract: Implementation of the HID utilities error handlers +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#ifdef DEBUG // not used in release +#if !defined (kBuildingLibrary) +#define kVerboseErrors + +// system includes ---------------------------------------------------------- + +#ifdef kVerboseErrors +//#include +#endif +#endif // not kBuildingLibrary +#endif // DEBUG + +#include + +// project includes --------------------------------------------------------- + +#include "HID_Utilities_External.h" + +// globals (internal/private) ----------------------------------------------- + +// prototypes (internal/private) -------------------------------------------- + +// functions (internal/private) --------------------------------------------- + +#pragma mark - +// ------------------------------------- + +// central error reporting + +void HIDReportErrorNum(const char *strError, int numError) { + char errMsgCStr[256]; + + sprintf(errMsgCStr, "%s #%d (0x%x)", strError, numError, numError); + + // out as debug string +#ifdef kVerboseErrors + { + fputs(errMsgCStr, stderr); + } +#endif // kVerboseErrors +} // HIDReportErrorNum + +// ------------------------------------- + +void HIDReportError(const char *strError) { + char errMsgCStr[256]; + + sprintf(errMsgCStr, "%s", strError); + + // out as debug string +#ifdef kVerboseErrors + { + fputs(errMsgCStr, stderr); + } +#endif // kVerboseErrors +} // HIDReportError + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Name_Lookup.c b/src/cocoa/HID_Name_Lookup.c new file mode 100644 index 000000000..c9a1ca264 --- /dev/null +++ b/src/cocoa/HID_Name_Lookup.c @@ -0,0 +1,1210 @@ +// File: HID_Name_Lookup.c +// Abstract: HID Name Lookup Utilities. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#pragma mark - includes & imports +//***************************************************** +#include "HID_Utilities_External.h" +//***************************************************** +#pragma mark - typedefs, enums, defines, etc. +//***************************************************** +#define FAKE_MISSING_NAMES 0 // for debugging; returns the vendor, product & cookie ( or usage info ) as numbers. +#define VERBOSE_ELEMENT_NAMES 0 // set TRUE to include vender & product names in element names ( useful for debugging ) + +#define kNameKeyCFStringRef CFSTR("Name") +//***************************************************** +#pragma mark - local ( static ) function prototypes +//***************************************************** + +#if 0 // currently unused +static SInt32 hu_SaveToXMLFile(CFPropertyListRef inCFPRef, CFURLRef inCFURLRef); +static SInt32 hu_XMLSave(CFPropertyListRef inCFPropertyListRef, CFStringRef inResourceName, CFStringRef inResourceExtension); +#endif +static CFPropertyListRef hu_LoadFromXMLFile(CFURLRef inCFURLRef); +static CFPropertyListRef hu_XMLLoad(CFStringRef inResourceName, CFStringRef inResourceExtension); + +static Boolean hu_XMLSearchForElementNameByCookie(long inVendorID, long inProductID, IOHIDElementCookie inCookie, char *outCStr); +static Boolean hu_XMLSearchForElementNameByUsage(long inVendorID, long inProductID, long inUsagePage, long inUsage, char *outCStr); + +static Boolean hu_XMLSearchForVendorNameByVendorID(long inVendorID, char *outCStr); +static Boolean hu_XMLSearchForProductNameByVendorProductID(long inVendorID, long inProductID, char *outCStr); + +#if 0 // currently unused +static Boolean hu_AddVendorProductToCFDict(CFMutableDictionaryRef inCFMutableDictionaryRef, + long inVendorID, + CFStringRef inVendorCFStringRef, + long inProductID, + CFStringRef inProductCFStringRef); +static Boolean hu_AddDeviceElementToUsageXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef); +#endif +//***************************************************** +#pragma mark - exported globals +//***************************************************** +#pragma mark - local ( static ) globals +//***************************************************** +static CFPropertyListRef gCookieCFPropertyListRef = NULL; +static CFPropertyListRef gUsageCFPropertyListRef = NULL; + +//***************************************************** +#pragma mark - exported function implementations +//***************************************************** + +/************************************************************************* + * + * HIDGetVendorNameFromVendorID( inVendorID, inProductID, inCookie, outCStrName ) + * + * Purpose: Uses an devices vendor ID to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetVendorNameFromVendorID(long inVendorID, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForVendorNameByVendorID(inVendorID, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld}#", inVendorID); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetVendorNameFromVendorID + +/************************************************************************* + * + * HIDGetProductNameFromVendorProductID( inVendorID, inProductID, outCStrName ) + * + * Purpose: Uses an elements vendor, product & usage info to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetProductNameFromVendorProductID(long inVendorID, long inProductID, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForProductNameByVendorProductID(inVendorID, inProductID, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, U: %ld: %ld}#", inVendorID, inProductID, inUsagePage, inUsage); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetProductNameFromVendorProductID + +/************************************************************************* + * + * HIDGetElementNameFromVendorProductCookie( inVendorID, inProductID, inCookie, outCStrName ) + * + * Purpose: Uses an elements vendor, product & cookie to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inCookie - the elements cookie + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetElementNameFromVendorProductCookie(int inVendorID, int inProductID, IOHIDElementCookie inCookie, char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + // Look in the XML file first + if ( hu_XMLSearchForElementNameByCookie(inVendorID, inProductID, inCookie, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, C: %ld}#", inVendorID, inProductID, inCookie); +#else + result = FALSE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetElementNameFromVendorProductCookie + +/************************************************************************* + * + * HIDGetElementNameFromVendorProductUsage( inVendorID, inProductID, inUsagePage, inUsage, outCStrName ) + * + * Purpose: Uses an elements vendor, product & usage info to generate a name for it. + * + * Notes: Now uses XML files to store dictionary of names + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStrName - address where result will be returned + * Returns: Boolean - if successful + */ +Boolean HIDGetElementNameFromVendorProductUsage(long inVendorID, + long inProductID, + long inUsagePage, + long inUsage, + char *outCStrName) { + Boolean result = FALSE; + *outCStrName = 0; // clear name + if ( hu_XMLSearchForElementNameByUsage(inVendorID, inProductID, inUsagePage, inUsage, outCStrName) ) { + return (TRUE); + } + +#if FAKE_MISSING_NAMES + sprintf(outCStrName, "#{ V: %ld, P: %ld, U: %ld: %ld}#", inVendorID, inProductID, inUsagePage, inUsage); + result = TRUE; +#endif // FAKE_MISSING_NAMES + return (result); +} // HIDGetElementNameFromVendorProductUsage + +#if 0 // currently unused +/************************************************************************* + * + * HIDAddDeviceToXML( inDevice ) + * + * Purpose: Adds a devices info to the HID_device_usage_strings.plist( XML ) file + * + * Inputs: inDevice - the device + * Returns: Boolean - if successful + */ +static Boolean HIDAddDeviceToXML(IOHIDDeviceRef inIOHIDDeviceRef) { + Boolean result = FALSE; + if ( HIDIsValidDevice(inIOHIDDeviceRef) ) { + CFStringRef vendorCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + CFStringRef productCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + if ( vendorCFStringRef && productCFStringRef ) { +#if 0 // don't update the cookie xml file + gCookieCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + if ( gCookieCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gCookieCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + if ( hu_AddVendorProductToCFDict(tCFMutableDictionaryRef, vendorID, vendorCFStringRef, productID, + productCFStringRef) ) + { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + result = TRUE; + } + + CFRelease(tCFMutableDictionaryRef); + } + } + +#endif + if ( gUsageCFPropertyListRef ) { + CFRelease(gUsageCFPropertyListRef); + } + + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + if ( gUsageCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gUsageCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + if ( hu_AddVendorProductToCFDict(tCFMutableDictionaryRef, vendorID, vendorCFStringRef, productID, + productCFStringRef) ) + { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + result = TRUE; + } + + CFRelease(tCFMutableDictionaryRef); + } + } + } + } + + return (result); +} // HIDAddDeviceToXML + +/************************************************************************* + * + * HIDAddDeviceElementToXML( inDevice, inElement ) + * + * Purpose: Adds a devices info to the HID_device_usage_strings.plist( XML ) file + * + * Inputs: inDevice - the device + * inElement - the element + * + * Returns: Boolean - if successful + */ +Boolean HIDAddDeviceElementToXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + Boolean result = FALSE; + if ( HIDIsValidElement(inIOHIDElementRef) ) { + if ( HIDAddDeviceToXML(inIOHIDDeviceRef) ) { + result = TRUE; + } + if ( hu_AddDeviceElementToUsageXML(inIOHIDDeviceRef, inIOHIDElementRef) ) { + result = TRUE; + } + } + + return (result); +} // HIDAddDeviceElementToXML +#endif +/************************************************************************* + * + * HIDGetTypeName( inIOHIDElementType, outCStrName ) + * + * Purpose: return a C string for a given element type( see IOHIDKeys.h ) + * Notes: returns "Unknown Type" for invalid types + * + * Inputs: inIOHIDElementType - type element type + * outCStrName - address where to store element type string + * + * Returns: outCStrName - the element type string + */ + +void HIDGetTypeName(IOHIDElementType inIOHIDElementType, char *outCStrName) { + switch ( inIOHIDElementType ) { + case kIOHIDElementTypeInput_Misc: + { + sprintf(outCStrName, "Miscellaneous Input"); + break; + } + + case kIOHIDElementTypeInput_Button: + { + sprintf(outCStrName, "Button Input"); + break; + } + + case kIOHIDElementTypeInput_Axis: + { + sprintf(outCStrName, "Axis Input"); + break; + } + + case kIOHIDElementTypeInput_ScanCodes: + { + sprintf(outCStrName, "Scan Code Input"); + break; + } + + case kIOHIDElementTypeOutput: + { + sprintf(outCStrName, "Output"); + break; + } + + case kIOHIDElementTypeFeature: + { + sprintf(outCStrName, "Feature"); + break; + } + + case kIOHIDElementTypeCollection: + { + sprintf(outCStrName, "Collection"); + break; + } + + default: + { + sprintf(outCStrName, "Unknown Type"); + break; + } + } // switch + +} // HIDGetTypeName + +//************************************************************************* +// +// HIDCopyUsageName( inUsagePage, inUsage ) +// +// Purpose: return a CFStringRef string for a given usage page & usage( see IOUSBHIDParser.h ) +// +// Notes: returns usage page and usage values in CFString form for unknown values +// +// Inputs: inUsagePage - the usage page +// inUsage - the usage +// +// Returns: CFStringRef - the resultant string +// + +CFStringRef HIDCopyUsageName(long inUsagePage, long inUsage) { + static CFPropertyListRef tCFPropertyListRef = NULL; + CFStringRef result = NULL; + if ( !tCFPropertyListRef ) { + tCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_usage_strings"), CFSTR("plist") ); + } + if ( tCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(tCFPropertyListRef) ) + { + CFStringRef pageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("0x%4.4lX"), inUsagePage); + if ( pageKeyCFStringRef ) { + CFDictionaryRef pageCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(tCFPropertyListRef, pageKeyCFStringRef, + (const void **) &pageCFDictionaryRef) ) + { + CFStringRef pageCFStringRef; + if ( CFDictionaryGetValueIfPresent(pageCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &pageCFStringRef) ) + { + CFStringRef usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "0x%4.4lX"), inUsage); + if ( usageKeyCFStringRef ) { + CFStringRef usageCFStringRef; + if ( CFDictionaryGetValueIfPresent(pageCFDictionaryRef, usageKeyCFStringRef, + (const void **) &usageCFStringRef) ) + { + result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@"), pageCFStringRef, usageCFStringRef); + } + +#if FAKE_MISSING_NAMES + else { + result = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ #%@"), pageCFStringRef, usageKeyCFStringRef); + } +#endif + CFRelease(usageKeyCFStringRef); + } + } else { + // no name data for this page + } + } else { + // no data for this page + } + + CFRelease(pageKeyCFStringRef); + } + } + + // CFRelease( tCFPropertyListRef ); // Leak this! + // tCFPropertyListRef = NULL; + } + + return (result); +} // HIDCopyUsageName + +//***************************************************** +#pragma mark - local ( static ) function implementations +//***************************************************** +#if 0 // currently unused +/************************************************************************* + * + * hu_SaveToXMLFile( inCFPRef, inCFURLRef ) + * + * Purpose: save a property list into an XML file + * + * Inputs: inCFPRef - the data + * inCFURLRef - URL for the file + * + * Returns: SInt32 - error code ( if any ) + */ +static SInt32 hu_SaveToXMLFile(CFPropertyListRef inCFPRef, CFURLRef inCFURLRef) { + CFDataRef xmlCFDataRef; + SInt32 error = coreFoundationUnknownErr; + + // Convert the property list into XML data. + xmlCFDataRef = CFPropertyListCreateXMLData(kCFAllocatorDefault, inCFPRef); + if ( xmlCFDataRef ) { + // Write the XML data to the file. + (void) CFURLWriteDataAndPropertiesToResource(inCFURLRef, xmlCFDataRef, NULL, &error); + + // Release the XML data + CFRelease(xmlCFDataRef); + } + + return (error); +} // hu_SaveToXMLFile +#endif +/************************************************************************* + * + * hu_LoadFromXMLFile( inCFURLRef ) + * + * Purpose: load a property list from an XML file + * + * Inputs: inCFURLRef - URL for the file + * + * Returns: CFPropertyListRef - the data + */ +static CFPropertyListRef hu_LoadFromXMLFile(CFURLRef inCFURLRef) { + CFDataRef xmlCFDataRef; + CFPropertyListRef myCFPropertyListRef = NULL; + + // Read the XML file. + SInt32 error; + if ( CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, inCFURLRef, &xmlCFDataRef, NULL, NULL, &error) ) { + CFStringRef errorString; + // Reconstitute the dictionary using the XML data. + myCFPropertyListRef = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, + xmlCFDataRef, + kCFPropertyListImmutable, + &errorString); + // Release the XML data + CFRelease(xmlCFDataRef); + } + + return (myCFPropertyListRef); +} // hu_LoadFromXMLFile + +#if 0 // currently unused +/************************************************************************* + * + * hu_XMLSave( inCFPropertyListRef, inResourceName, inResourceExtension ) + * + * Purpose: save a CFPropertyListRef into a resource( XML ) file + * + * Inputs: inCFPropertyListRef - the data + * inResourceName - name of the resource file + * inResourceExtension - extension of the resource file + * + * Returns: SInt32 - error code ( if any ) + */ +static SInt32 hu_XMLSave(CFPropertyListRef inCFPropertyListRef, CFStringRef inResourceName, CFStringRef inResourceExtension) { + CFURLRef resFileCFURLRef; + SInt32 error = -1; + + // check the main (application) bundle + resFileCFURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), inResourceName, inResourceExtension, NULL); + + if ( !resFileCFURLRef ) { + // check this specific (HID_Utilities framework) bundle + CFBundleRef tCFBundleRef = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HID_Utilities")); + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + } + } + if ( !resFileCFURLRef ) { + // check bundles already loaded or otherwise known to the current process + CFArrayRef tCFArrayRef = CFBundleGetAllBundles(); + CFIndex idx, cnt = CFArrayGetCount(tCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + CFBundleRef tCFBundleRef = (CFBundleRef) CFArrayGetValueAtIndex(tCFArrayRef, idx) ; + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + if ( resFileCFURLRef ) { + break; + } + } + } + } + if ( resFileCFURLRef ) { + error = hu_SaveToXMLFile(inCFPropertyListRef, resFileCFURLRef); + CFRelease(resFileCFURLRef); + } + + return (error); +} // hu_XMLSave +#endif +/************************************************************************* + * + * hu_XMLLoad( inResourceName, inResourceExtension ) + * + * Purpose: Load a resource( XML ) file into a CFPropertyListRef + * + * Inputs: inResourceName - name of the resource file + * inResourceExtension - extension of the resource file + * + * Returns: CFPropertyListRef - the data + */ +static CFPropertyListRef hu_XMLLoad(CFStringRef inResourceName, CFStringRef inResourceExtension) { + CFURLRef resFileCFURLRef; + CFPropertyListRef tCFPropertyListRef = NULL; + + // check the main (application) bundle + resFileCFURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), inResourceName, inResourceExtension, NULL); + + if ( !resFileCFURLRef ) { + // check this specific (HID_Utilities framework) bundle + CFBundleRef tCFBundleRef = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HID_Utilities")); + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + } + } + if ( !resFileCFURLRef ) { + // check bundles already loaded or otherwise known to the current process + CFArrayRef tCFArrayRef = CFBundleGetAllBundles(); + CFIndex idx, cnt = CFArrayGetCount(tCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + CFBundleRef tCFBundleRef = (CFBundleRef) CFArrayGetValueAtIndex(tCFArrayRef, idx) ; + if ( tCFBundleRef ) { + resFileCFURLRef = CFBundleCopyResourceURL( tCFBundleRef, inResourceName, inResourceExtension, NULL ); + if ( resFileCFURLRef ) { + break; + } + } + } + } + if ( resFileCFURLRef ) { + tCFPropertyListRef = hu_LoadFromXMLFile(resFileCFURLRef); + CFRelease(resFileCFURLRef); + } + + return (tCFPropertyListRef); +} // hu_XMLLoad + +/************************************************************************* + * + * hu_XMLSearchForVendorNameByVendorID( inVendorID, outCStr ) + * + * Purpose: Find a vendor string in the resource ( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForVendorNameByVendorID(long inVendorID, char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + CFDictionaryRef vendorCFDictionaryRef; + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) && vendorCFStringRef ) + { + // CFShow( vendorCFStringRef ); + results = + CFStringGetCString(vendorCFStringRef, outCStr, CFStringGetLength( + vendorCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + } + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForVendorNameByVendorID + +/************************************************************************* + * + * hu_XMLSearchForProductNameByVendorProductID( inVendorID, inProductID, outCStr ) + * + * Purpose: Find an product string in the resource ( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForProductNameByVendorProductID(long inVendorID, long inProductID, char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + // first we make our vendor ID key + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + // and use it to look up our vendor dictionary + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + // pull our vendor name our of that dictionary + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { +#if FAKE_MISSING_NAMES + CFRetain(vendorCFStringRef); // so we can CFRelease it later + } else { + vendorCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "V: %@"), vendorKeyCFStringRef); +#endif + } + + // now we make our product ID key + CFStringRef productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld"), inProductID); + if ( productKeyCFStringRef ) { + // and use that key to look up our product dictionary in the vendor dictionary + CFDictionaryRef productCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + // pull our product name our of the product dictionary + CFStringRef productCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { +#if FAKE_MISSING_NAMES + CFRetain(productCFStringRef); // so we can CFRelease it later + } else { + productCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "P: %@"), kNameKeyCFStringRef); +#endif + } + + CFStringRef fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@"), vendorCFStringRef, + productCFStringRef); + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + +#if FAKE_MISSING_NAMES + if ( productCFStringRef ) { + CFRelease(productCFStringRef); + } + +#endif + } + + CFRelease(productKeyCFStringRef); + } + +#if FAKE_MISSING_NAMES + if ( vendorCFStringRef ) { + CFRelease(vendorCFStringRef); + } + +#endif + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForProductNameByVendorProductID + +/************************************************************************* + * + * hu_XMLSearchForElementNameByCookie( inVendorID, inProductID, inCookie, outCStr ) + * + * Purpose: Find an element string in the resource( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inCookie - the elements cookie + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForElementNameByCookie(long inVendorID, long inProductID, IOHIDElementCookie inCookie, char *outCStr) { + Boolean results = FALSE; + if ( !gCookieCFPropertyListRef ) { + gCookieCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_cookie_strings"), CFSTR("plist") ); + } + if ( gCookieCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gCookieCFPropertyListRef) ) + { + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gCookieCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFDictionaryRef productCFDictionaryRef; + CFStringRef productKeyCFStringRef; + CFStringRef vendorCFStringRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { + // CFShow( vendorCFStringRef ); + } + + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inProductID); + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + CFStringRef fullCFStringRef = NULL; + CFStringRef cookieKeyCFStringRef; + CFStringRef productCFStringRef; + CFStringRef cookieCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { + // CFShow( productCFStringRef ); + } + + cookieKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inCookie); + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, cookieKeyCFStringRef, + (const void **) &cookieCFStringRef) ) + { +#if VERBOSE_ELEMENT_NAMES + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ %@"), vendorCFStringRef, productCFStringRef, + cookieCFStringRef); +#else + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), cookieCFStringRef); +#endif // VERBOSE_ELEMENT_NAMES + // CFShow( cookieCFStringRef ); + } + +#if FAKE_MISSING_NAMES + else { + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ # %@"), vendorCFStringRef, productCFStringRef, + cookieKeyCFStringRef); + } +#endif // FAKE_MISSING_NAMES + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + + CFRelease(cookieKeyCFStringRef); + } + + CFRelease(productKeyCFStringRef); + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gCookieCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForElementNameByCookie + +/************************************************************************* + * + * hu_XMLSearchForElementNameByUsage( inVendorID, inProductID, inUsagePage, inUsage, outCStr ) + * + * Purpose: Find an element string in the resource( XML ) file + * + * Inputs: inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inUsagePage - the elements usage page + * inUsage - the elements usage + * outCStr - address where result will be returned + * + * Returns: Boolean - if successful + */ +static Boolean hu_XMLSearchForElementNameByUsage(long inVendorID, long inProductID, long inUsagePage, long inUsage, + char *outCStr) { + Boolean results = FALSE; + if ( !gUsageCFPropertyListRef ) { + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + if ( gUsageCFPropertyListRef ) { + if ( + CFDictionaryGetTypeID() == CFGetTypeID(gUsageCFPropertyListRef) ) + { + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( vendorKeyCFStringRef ) { + CFDictionaryRef vendorCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(gUsageCFPropertyListRef, vendorKeyCFStringRef, + (const void **) &vendorCFDictionaryRef) ) + { + CFStringRef vendorCFStringRef = NULL; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &vendorCFStringRef) ) + { + vendorCFStringRef = CFStringCreateCopy(kCFAllocatorDefault, vendorCFStringRef); + } else { + vendorCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("v: %ld"), inVendorID); + // CFShow( vendorCFStringRef ); + } + + CFStringRef productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld"), inProductID); + + CFDictionaryRef productCFDictionaryRef; + if ( CFDictionaryGetValueIfPresent(vendorCFDictionaryRef, productKeyCFStringRef, + (const void **) &productCFDictionaryRef) ) + { + CFStringRef fullCFStringRef = NULL; + + CFStringRef productCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, kNameKeyCFStringRef, + (const void **) &productCFStringRef) ) + { + // CFShow( productCFStringRef ); + } + + CFStringRef usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%ld:%ld"), inUsagePage, inUsage); + CFStringRef usageCFStringRef; + if ( CFDictionaryGetValueIfPresent(productCFDictionaryRef, usageKeyCFStringRef, + (const void **) &usageCFStringRef) ) + { +#if VERBOSE_ELEMENT_NAMES + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ %@"), vendorCFStringRef, productCFStringRef, + usageCFStringRef); +#else + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), usageCFStringRef); +#endif // VERBOSE_ELEMENT_NAMES + // CFShow( usageCFStringRef ); + } + +#if FAKE_MISSING_NAMES + else { + fullCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR( + "%@ %@ # %@"), vendorCFStringRef, productCFStringRef, + usageKeyCFStringRef); + } +#endif // FAKE_MISSING_NAMES + if ( fullCFStringRef ) { + // CFShow( fullCFStringRef ); + results = + CFStringGetCString(fullCFStringRef, outCStr, CFStringGetLength( + fullCFStringRef) * sizeof(UniChar) + 1, kCFStringEncodingUTF8); + CFRelease(fullCFStringRef); + } + + CFRelease(usageKeyCFStringRef); + } + if ( vendorCFStringRef ) { + CFRelease(vendorCFStringRef); + } + + CFRelease(productKeyCFStringRef); + } + + CFRelease(vendorKeyCFStringRef); + } + } + + // ++ CFRelease( gUsageCFPropertyListRef ); // Leak this ! + } + + return (results); +} // hu_XMLSearchForElementNameByUsage + +#if 0 // currently unused +/************************************************************************* + * + * hu_AddVendorProductToCFDict( inCFMutableDictionaryRef, inVendorID, inVendorCFStringRef, inProductID, inProductCFStringRef ) + * + * Purpose: add a vendor & product to a dictionary + * + * Inputs: inCFMutableDictionaryRef - the dictionary + * inVendorID - the elements vendor ID + * inProductID - the elements product ID + * inProductCFStringRef - the string to be added + * + * Returns: Boolean - if successful + */ +static Boolean hu_AddVendorProductToCFDict(CFMutableDictionaryRef inCFMutableDictionaryRef, + long inVendorID, + CFStringRef inVendorCFStringRef, + long inProductID, + CFStringRef inProductCFStringRef) { + Boolean results = FALSE; + if ( inCFMutableDictionaryRef && ( CFDictionaryGetTypeID() == CFGetTypeID(inCFMutableDictionaryRef) ) ) { + CFMutableDictionaryRef vendorCFMutableDictionaryRef; + CFStringRef vendorKeyCFStringRef; + + CFMutableDictionaryRef productCFMutableDictionaryRef; + CFStringRef productKeyCFStringRef; + + // if the vendor dictionary doesn't exist + vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inVendorID); + if ( CFDictionaryGetValueIfPresent(inCFMutableDictionaryRef, vendorKeyCFStringRef, + (const void **) &vendorCFMutableDictionaryRef) ) + { + // copy it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, vendorCFMutableDictionaryRef); + } else { // ...otherwise... + // create it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the vendor name key doesn't exist + if ( !CFDictionaryContainsKey(vendorCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + // create it. + CFDictionaryAddValue(vendorCFMutableDictionaryRef, kNameKeyCFStringRef, inVendorCFStringRef); + results = TRUE; + } + + // if the product key exists in the vendor dictionary + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), inProductID); + if ( CFDictionaryGetValueIfPresent(vendorCFMutableDictionaryRef, productKeyCFStringRef, + (const void **) &productCFMutableDictionaryRef) ) + { + // copy it. + productCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, productCFMutableDictionaryRef); + } else { // ...otherwise... + // create it. + productCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the product name key doesn't exist + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + // create it. + CFDictionaryAddValue(productCFMutableDictionaryRef, kNameKeyCFStringRef, inProductCFStringRef); + results = TRUE; + } + if ( vendorCFMutableDictionaryRef ) { + if ( productCFMutableDictionaryRef ) { + if ( results ) { + CFDictionarySetValue(vendorCFMutableDictionaryRef, productKeyCFStringRef, productCFMutableDictionaryRef); + } + + CFRelease(productCFMutableDictionaryRef); + } + if ( results ) { + CFDictionarySetValue(inCFMutableDictionaryRef, vendorKeyCFStringRef, vendorCFMutableDictionaryRef); + } + + CFRelease(vendorCFMutableDictionaryRef); + } + if ( productKeyCFStringRef ) { + CFRelease(productKeyCFStringRef); + } + if ( vendorKeyCFStringRef ) { + CFRelease(vendorKeyCFStringRef); + } + } + + return (results); +} // hu_AddVendorProductToCFDict + +/************************************************************************* + * + * hu_AddDeviceElementToUsageXML( inDevice, inElement ) + * + * Purpose: add a device and it's elements to our usage( XML ) file + * + * Inputs: inDevice - the device + * inElement - the element + * + * Returns: Boolean - if successful + */ +static Boolean hu_AddDeviceElementToUsageXML(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + Boolean results = FALSE; + if ( gUsageCFPropertyListRef ) { + CFRelease(gUsageCFPropertyListRef); + } + + gUsageCFPropertyListRef = + hu_XMLLoad( CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + if ( gUsageCFPropertyListRef ) { + CFMutableDictionaryRef tCFMutableDictionaryRef = + CFDictionaryCreateMutableCopy( + kCFAllocatorDefault, + 0, + gUsageCFPropertyListRef); + if ( tCFMutableDictionaryRef ) { + CFMutableDictionaryRef vendorCFMutableDictionaryRef; + + CFMutableDictionaryRef productCFMutableDictionaryRef; + CFStringRef productKeyCFStringRef; + + CFStringRef usageKeyCFStringRef; + + // if the vendor dictionary exists... + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + CFStringRef vendorKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), vendorID); + if ( vendorKeyCFStringRef ) { + if ( CFDictionaryGetValueIfPresent(tCFMutableDictionaryRef, vendorKeyCFStringRef, + (const void **) &vendorCFMutableDictionaryRef) ) + { + // ...copy it... + vendorCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, + 0, + vendorCFMutableDictionaryRef); + } else { // ...otherwise... + // ...create it. + vendorCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the vendor name key doesn't exist... + if ( !CFDictionaryContainsKey(vendorCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + CFStringRef manCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + // ...create it. + CFDictionaryAddValue(vendorCFMutableDictionaryRef, kNameKeyCFStringRef, manCFStringRef); + results = TRUE; + } + + // if the product key exists in the vendor dictionary... + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + productKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), productID); + if ( CFDictionaryGetValueIfPresent(vendorCFMutableDictionaryRef, productKeyCFStringRef, + (const void **) &productCFMutableDictionaryRef) ) + { + // ...copy it... + productCFMutableDictionaryRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, + 0, + productCFMutableDictionaryRef); + } else { // ...otherwise... + // ...create it. + productCFMutableDictionaryRef = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + results = TRUE; + } + // if the product name key doesn't exist... + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, kNameKeyCFStringRef) ) { + CFStringRef productCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + // ...create it. + CFDictionaryAddValue(productCFMutableDictionaryRef, kNameKeyCFStringRef, productCFStringRef); + results = TRUE; + } + + // if the usage key doesn't exist in the product dictionary... + uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsagePage(inIOHIDElementRef); + usageKeyCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld:%ld"), usagePage, usage); + if ( usageKeyCFStringRef ) { + if ( !CFDictionaryContainsKey(productCFMutableDictionaryRef, usageKeyCFStringRef) ) { + // find it's generic name + CFStringRef usageCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( usageCFStringRef ) { + // and add that. + CFDictionaryAddValue(productCFMutableDictionaryRef, usageKeyCFStringRef, usageCFStringRef); + results = TRUE; + CFRelease(usageCFStringRef); + } + } + + CFRelease(usageKeyCFStringRef); + } + if ( vendorCFMutableDictionaryRef ) { + if ( productCFMutableDictionaryRef ) { + if ( results ) { + CFDictionarySetValue(vendorCFMutableDictionaryRef, productKeyCFStringRef, productCFMutableDictionaryRef); + } + + CFRelease(productCFMutableDictionaryRef); + } + if ( results ) { + CFDictionarySetValue(tCFMutableDictionaryRef, vendorKeyCFStringRef, vendorCFMutableDictionaryRef); + } + + CFRelease(vendorCFMutableDictionaryRef); + } + + CFRelease(vendorKeyCFStringRef); + } + if ( productKeyCFStringRef ) { + CFRelease(productKeyCFStringRef); + } + if ( results ) { + hu_XMLSave( tCFMutableDictionaryRef, + CFSTR( + "HID_device_usage_strings"), CFSTR("plist") ); + } + + CFRelease( + tCFMutableDictionaryRef); + } + } + + return (results); +} // hu_AddDeviceElementToUsageXML +#endif + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Queue_Utilities.c b/src/cocoa/HID_Queue_Utilities.c new file mode 100644 index 000000000..ab1d83667 --- /dev/null +++ b/src/cocoa/HID_Queue_Utilities.c @@ -0,0 +1,361 @@ +// File: HID_Queue_Utilities.c +// Abstract: HID Queue Utilities. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#include "HID_Utilities_External.h" + +// ================================== +// private functions + +// creates a queue for a device, creates and opens device interface if required +static IOReturn HIDCreateQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + // do we already have a queue? + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { // (yes) + assert( IOHIDQueueGetTypeID() == CFGetTypeID(tIOHIDQueueRef) ); + } else { + tIOHIDQueueRef = IOHIDQueueCreate(kCFAllocatorDefault, inIOHIDDeviceRef, kDeviceQueueSize, kIOHIDOptionsTypeNone); + if ( tIOHIDQueueRef ) { // did that work + IOHIDDevice_SetQueue(inIOHIDDeviceRef, tIOHIDQueueRef); + result = kIOReturnSuccess; + } else { + HIDReportErrorNum("Failed to create queue via create", result); + } + } + } else { + HIDReportErrorNum("HID device ref does not exist for queue creation", result); + } + + return (result); +} /* HIDCreateQueue */ + +// --------------------------------- +// returns true if queue is empty false otherwise +// error if no device, empty if no queue +static unsigned char HIDIsDeviceQueueEmpty(IOHIDDeviceRef inIOHIDDeviceRef) { + if ( inIOHIDDeviceRef ) { // need device and queue + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + return (false); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + } else { + HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty."); + } + } else { + HIDReportError("NULL device passed to HIDIsDeviceQueueEmpty."); + } + + return (true); +} /* HIDIsDeviceQueueEmpty */ + +// --------------------------------- + +// disposes and releases queue, sets queue to NULL,. +// Note: will have no effect if device or queue do not exist +static IOReturn HIDDisposeReleaseQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + + // release the queue + CFRelease(tIOHIDQueueRef); + } + } else { + HIDReportError("NULL device passed to HIDDisposeReleaseQueue."); + } + + return (result); +} /* HIDDisposeReleaseQueue */ + +// ================================== +// public functions +// ---------------------------------- + +// queues specific element, performing any device queue set up required +// queue is started and ready to return events on exit from this function +int HIDQueueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( !tIOHIDQueueRef ) { // if no queue create queue + result = HIDCreateQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess == result ) { + tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + } + } + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + // queue element + if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) { + IOHIDQueueAddElement(tIOHIDQueueRef, inIOHIDElementRef); + } + + // restart queue + IOHIDQueueStart(tIOHIDQueueRef); + } else { + HIDReportError("No queue for device passed to HIDQueueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + } else { + HIDReportError("NULL element passed to HIDQueueElement."); + result = kIOReturnBadArgument; + } + } else { + HIDReportError("NULL device passed to HIDQueueElement."); + result = kIOReturnBadArgument; + } + + return (result); +} /* HIDQueueElement */ +// --------------------------------- + +// adds all elements to queue, performing any device queue set up required +// queue is started and ready to return events on exit from this function +int HIDQueueDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + // error checking + if ( !inIOHIDDeviceRef ) { + HIDReportError("Device does not exist, cannot queue device."); + return (kIOReturnBadArgument); + } + if ( !inIOHIDDeviceRef ) { // must have interface + HIDReportError("Device does not have hid device ref, cannot queue device."); + return (kIOReturnError); + } + + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( !tIOHIDQueueRef ) { // if no queue create queue + result = HIDCreateQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess == result ) { + tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + } + } + if ( (kIOReturnSuccess != result) || (!tIOHIDQueueRef) ) { + HIDReportErrorNum("Could not queue device due to problem creating queue.", result); + if ( kIOReturnSuccess != result ) { + return (result); + } else { + return (kIOReturnError); + } + } + + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + + // queue element + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + if ( !IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + IOHIDQueueAddElement(tIOHIDQueueRef, tIOHIDElementRef); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + + // restart queue + IOHIDQueueStart(tIOHIDQueueRef); + + return (result); +} /* HIDQueueDevice */ + +// --------------------------------- +// removes element for queue, if last element in queue will release queue and closes device interface +int HIDDequeueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHIDElementRef) { + IOReturn result = kIOReturnSuccess; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // stop queue + IOHIDQueueStop(tIOHIDQueueRef); + // de-queue element + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, inIOHIDElementRef) ) { + IOHIDQueueRemoveElement(tIOHIDQueueRef, inIOHIDElementRef); + } + // release device queue and close interface if queue empty + if ( HIDIsDeviceQueueEmpty(inIOHIDDeviceRef) ) { + result = HIDDisposeReleaseQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Failed to dispose and release queue.", result); + } + } else { // not empty so restart queue + IOHIDQueueStart(tIOHIDQueueRef); + } + } else { + HIDReportError("No queue for device passed to HIDDequeueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + } else { + HIDReportError("NULL element passed to HIDDequeueElement."); + result = kIOReturnBadArgument; + } + } else { + HIDReportError("NULL device passed to HIDDequeueElement."); + result = kIOReturnBadArgument; + } + + return (result); +} /* HIDDequeueElement */ + +// --------------------------------- +// completely removes all elements from queue and releases queue and closes device interface +// does not release device interfaces, application must call ReleaseHIDDeviceList on exit +int HIDDequeueDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOReturn result = kIOReturnSuccess; + // error checking + if ( !inIOHIDDeviceRef ) { + HIDReportError("Device does not exist, cannot queue device."); + return (kIOReturnBadArgument); + } + if ( !inIOHIDDeviceRef ) { // must have interface + HIDReportError("Device does not have hid device ref, cannot queue device."); + return (kIOReturnError); + } + + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + // iterate through elements and if queued, remove + IOHIDElementRef tIOHIDElementRef = HIDGetFirstDeviceElement(inIOHIDDeviceRef, kHIDElementTypeIO); + + while ( tIOHIDElementRef ) { + // de-queue element + if ( IOHIDQueueContainsElement(tIOHIDQueueRef, tIOHIDElementRef) ) { + IOHIDQueueRemoveElement(tIOHIDQueueRef, tIOHIDElementRef); + } + + tIOHIDElementRef = HIDGetNextDeviceElement(tIOHIDElementRef, kHIDElementTypeIO); + } + + // ensure queue is disposed and released + result = HIDDisposeReleaseQueue(inIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Failed to dispose and release queue.", result); + } + } else { + HIDReportError("No queue for device passed to HIDDequeueElement."); + if ( kIOReturnSuccess == result ) { + result = kIOReturnError; + } + } + + return (result); +} /* HIDDequeueDevice */ +// --------------------------------- + +// releases all device queues for quit or rebuild (must be called) +// does not release device interfaces, application must call ReleaseHIDDeviceList on exit +IOReturn HIDReleaseAllDeviceQueues(void) { + IOReturn result = kIOReturnSuccess; + IOHIDDeviceRef tIOHIDDeviceRef = HIDGetFirstDevice(); + + while ( tIOHIDDeviceRef ) { + result = HIDDequeueDevice(tIOHIDDeviceRef); + if ( kIOReturnSuccess != result ) { + HIDReportErrorNum("Could not dequeue device.", result); + } + + tIOHIDDeviceRef = HIDGetNextDevice(tIOHIDDeviceRef); + } + + return (result); +} /* HIDReleaseAllDeviceQueues */ + +// --------------------------------- +// Get the next event in the queue for a device +// elements or entire device should be queued prior to calling this with HIDQueueElement or HIDQueueDevice +// returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise +// Note: kIOReturnUnderrun returned from getNextEvent indicates an empty queue not an error condition +// Note: application should pass in a pointer to a IOHIDEventStruct cast to a void (for CFM compatibility) +unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHIDValueRef) { + if ( inIOHIDDeviceRef ) { + IOHIDQueueRef tIOHIDQueueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( tIOHIDQueueRef ) { + if ( pIOHIDValueRef ) { + *pIOHIDValueRef = IOHIDQueueCopyNextValueWithTimeout(tIOHIDQueueRef, 0.0); + if ( *pIOHIDValueRef ) { + return (true); + } + } + } else { + HIDReportError("Could not get HID event, hid queue reference does not exist."); + } + } else { + HIDReportError("Could not get HID event, device does not exist."); + } + + return (false); // did not get event +} /* HIDGetEvent */ + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Utilities.c b/src/cocoa/HID_Utilities.c new file mode 100644 index 000000000..3152cfdd9 --- /dev/null +++ b/src/cocoa/HID_Utilities.c @@ -0,0 +1,1068 @@ +// File: HID_Utilities.c +// Abstract: Implementation of the HID utilities +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//*************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#pragma mark - includes & imports +//----------------------------------------------------- + +#include + +#include "HID_Utilities_External.h" + +//*************************************************** +#pragma mark - typedefs, enums, defines, etc. +//----------------------------------------------------- +#define FAKE_MISSING_NAMES 1 // set this to true while debuging to get more explicit element names; false for +// the +// generic ones + +#define kPercentMove 10 // precent of overall range a element must move to register +#define kNameKeyCFStringRef CFSTR("Name") // dictionary key + +//*************************************************** +#pragma mark - local ( static ) function prototypes +//----------------------------------------------------- + +static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context); +static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context); +static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage); + +//*************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +IOHIDManagerRef gIOHIDManagerRef = NULL; +CFMutableArrayRef gDeviceCFArrayRef = NULL; +CFIndex gDeviceIndex; +CFArrayRef gElementCFArrayRef = NULL; + +//*************************************************** +#pragma mark - local ( static ) globals +//----------------------------------------------------- + +//*************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDBuildMultiDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) +// +// Purpose: builds list of devices with elements +// +// Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages +// inUsages - inNumDeviceTypes sized array of matching usages +// inNumDeviceTypes - number of usage pages & usages +// +// Returns: Boolean - if successful +// +Boolean HIDBuildMultiDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) { + Boolean result = FALSE; // assume failure ( pessimist! ) + Boolean first = (!gIOHIDManagerRef); // not yet created? + if ( first ) { + // create the manager + gIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + } + if ( gIOHIDManagerRef ) { + CFMutableArrayRef hidMatchingCFMutableArrayRef = NULL; + if ( inUsages && inUsagePages && inNumDeviceTypes ) { + hidMatchingCFMutableArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + if ( hidMatchingCFMutableArrayRef ) { + int idx; + for ( idx = 0; idx < inNumDeviceTypes; idx++ ) { // for all usage and usage page types + // Set up matching dictionary. returns NULL on error. + CFMutableDictionaryRef hidMatchingCFDictRef = hu_SetUpMatchingDictionary(inUsagePages[idx], inUsages[idx]); + if ( hidMatchingCFDictRef ) { + CFArrayAppendValue(hidMatchingCFMutableArrayRef, (void *) hidMatchingCFDictRef); + CFRelease(hidMatchingCFDictRef); + } else { + fprintf(stderr, "%s: Couldn’t create a matching dictionary.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: Couldn’t create a matching array.", __PRETTY_FUNCTION__); + } + } + + // set it for IOHIDManager to use to match against + IOHIDManagerSetDeviceMatchingMultiple(gIOHIDManagerRef, hidMatchingCFMutableArrayRef); + if ( hidMatchingCFMutableArrayRef ) { + CFRelease(hidMatchingCFMutableArrayRef); + } + if ( first ) { + // open it + IOReturn tIOReturn = IOHIDManagerOpen(gIOHIDManagerRef, kIOHIDOptionsTypeNone); + if ( kIOReturnSuccess != tIOReturn ) { + fprintf(stderr, "%s: Couldn’t open IOHIDManager.", __PRETTY_FUNCTION__); + goto Oops; + } + } + + HIDRebuildDevices(); + result = TRUE; + } else { + fprintf(stderr, "%s: Couldn’t create a IOHIDManager.", __PRETTY_FUNCTION__); + } + +Oops: ; + return (result); +} // HIDBuildMultiDeviceList + +/************************************************************************* + * + * HIDBuildDeviceList( inUsagePage, inUsage ) + * + * Purpose: builds list of devices with elements + * + * Notes: same as above but this uses a single inUsagePage and usage + * allocates memory and captures devices + * list is allocated internally within HID Utilites and can be accessed via accessor functions + * structures within list are considered flat and user accessable, but not user modifiable + * can be called again to rebuild list to account for new devices + * ( will do the right thing in case of disposing existing list ) + * + * Inputs: inUsagePage - usage page + * inUsage - usages + * + * Returns: Boolean - if successful + */ + +Boolean HIDBuildDeviceList(UInt32 inUsagePage, UInt32 inUsage) { + return ( HIDBuildMultiDeviceList(&inUsagePage, &inUsage, 1) ); // call HIDBuildMultiDeviceList with a single usage +} + +/************************************************************************* + * + * HIDUpdateDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) + * + * Purpose: updates the current device list for any new/removed devices + * + * Notes: if this is called before HIDBuildDeviceList then it functions like HIDBuildMultiDeviceList + * inUsagePage & inUsage are each a inNumDeviceTypes sized array of matching usage and usage pages + * + * Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages + * inUsages - inNumDeviceTypes sized array of matching usages + * inNumDeviceTypes - number of usage pages & usages + * + * Returns: Boolean - TRUE if the device config changed + */ + +Boolean HIDUpdateDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes) { + return ( HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes) ); +} + +/************************************************************************* + * + * HIDReleaseDeviceList( void ) + * + * Purpose: release list built by above functions + * + * Notes: MUST be called prior to application exit to properly release devices + * if not called( or app crashes ) devices can be recovered by pluging into different location in USB chain + * + * Inputs: none + * + * Returns: none + */ + +void HIDReleaseDeviceList(void) { + if ( gDeviceCFArrayRef ) { + CFRelease(gDeviceCFArrayRef); + gDeviceCFArrayRef = NULL; + } +} // HIDReleaseDeviceList + +/************************************************************************* + * + * HIDHaveDeviceList( void ) + * + * Purpose: does a device list exist? + * + * Inputs: none + * + * Returns: Boolean - TRUE if we have previously built a device list + */ + +Boolean HIDHaveDeviceList(void) { + return (NULL != gDeviceCFArrayRef); +} + +//************************************************************************* +// +// HIDRebuildDevices( ) +// +// Purpose: rebuilds the (internal) list of IOHIDDevices +// +// Inputs: none +// +// Returns: none +// + +void HIDRebuildDevices(void) { + // get the set of devices from the IOHID manager + CFSetRef devCFSetRef = IOHIDManagerCopyDevices(gIOHIDManagerRef); + if ( devCFSetRef ) { + // if the existing array isn't empty... + if ( gDeviceCFArrayRef ) { + // release it + CFRelease(gDeviceCFArrayRef); + } + + // create an empty array + gDeviceCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + // now copy the set to the array + CFSetApplyFunction(devCFSetRef, CFSetApplierFunctionCopyToCFArray, (void *) gDeviceCFArrayRef); + // now sort the array by location ID's + CFIndex cnt = CFArrayGetCount(gDeviceCFArrayRef); + CFArraySortValues(gDeviceCFArrayRef, CFRangeMake(0, cnt), CFDeviceArrayComparatorFunction, NULL); + + // and release the set we copied from the IOHID manager + CFRelease(devCFSetRef); + } +} // HIDRebuildDevices + +// --------------------------------- + +// how many HID devices have been found +// returns 0 if no device list exist + +UInt32 HIDCountDevices(void) { + return ( CFArrayGetCount(gDeviceCFArrayRef) ); +} + +// --------------------------------- + +// how many elements does a specific device have +// returns 0 if device is invlaid or NULL + +UInt32 HIDCountDeviceElements(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) { + int count = 0; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + count++; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + count++; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + count++; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + count++; + } + + break; + } + default: { + break; + } + } // switch ( type ) + + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + return (count); +} /* HIDCountDeviceElements */ + +// --------------------------------- + +// get the first device in the device list +// returns NULL if no list exists or it's empty + +IOHIDDeviceRef HIDGetFirstDevice(void) { + IOHIDDeviceRef result = NULL; + + gDeviceIndex = 0; + if ( gDeviceCFArrayRef ) { + CFIndex count = CFArrayGetCount(gDeviceCFArrayRef); + if ( (gDeviceIndex >= 0) && (gDeviceIndex < count) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + } + } + + return (result); +} /* HIDGetFirstDevice */ + +// --------------------------------- + +// get next device in list given current device as parameter +// returns NULL if end of list + +IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDDeviceRef result = NULL; + if ( gDeviceCFArrayRef && inIOHIDDeviceRef ) { + CFIndex idx, cnt = CFArrayGetCount(gDeviceCFArrayRef); + // quick case to verify the current device index is valid for current device + if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + if ( result && (result == inIOHIDDeviceRef) ) { + result = NULL; + gDeviceIndex++; // bump index + } else { + // previous index was invalid; + gDeviceIndex = -1; + // search for current device's index + for ( idx = 0; idx < cnt; idx++ ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, idx); + if ( (result) && (result == inIOHIDDeviceRef) ) { + gDeviceIndex = idx + 1; // found valid index; bump to next one + break; + } + } + + result = NULL; + } + if ( (gDeviceIndex >= 0) && (gDeviceIndex < cnt) ) { + result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, gDeviceIndex); + } + } // if valid index + + } // if ( gDeviceCFArrayRef && inIOHIDDeviceRef ) + + return (result); +} /* HIDGetNextDevice */ + +// --------------------------------- + +// get the first element of device passed in as parameter +// returns NULL if no list exists or device does not exists or is NULL +IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask) { + IOHIDElementRef result = NULL; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + result = tIOHIDElementRef; + } + + break; + } + default: { + break; + } + } // switch ( type ) + if ( result ) { + break; // DONE! + } + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + return (result); +} /* HIDGetFirstDeviceElement */ + +// --------------------------------- + +// get next element of given device in list given current element as parameter +// will walk down each collection then to next element or collection (depthwise traverse) +// returns NULL if end of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality +IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef inIOHIDElementRef, HIDElementTypeMask typeMask) { + IOHIDElementRef result = NULL; + if ( inIOHIDElementRef ) { + assert( IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef) ); + + IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef); + if ( tIOHIDDeviceRef ) { + Boolean found = FALSE; + + gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone); + if ( gElementCFArrayRef ) { + CFIndex idx, cnt = CFArrayGetCount(gElementCFArrayRef); + for ( idx = 0; idx < cnt; idx++ ) { + IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, idx); + if ( !tIOHIDElementRef ) { + continue; + } + if ( !found ) { + if ( inIOHIDElementRef == tIOHIDElementRef ) { + found = TRUE; + } + + continue; // next element + } else { + // we've found the current element; now find the next one of the right type + IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef); + + switch ( type ) { + case kIOHIDElementTypeInput_Misc: + case kIOHIDElementTypeInput_Button: + case kIOHIDElementTypeInput_Axis: + case kIOHIDElementTypeInput_ScanCodes: + { + if ( typeMask & kHIDElementTypeInput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeOutput: + { + if ( typeMask & kHIDElementTypeOutput ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeFeature: + { + if ( typeMask & kHIDElementTypeFeature ) { + result = tIOHIDElementRef; + } + + break; + } + + case kIOHIDElementTypeCollection: + { + if ( typeMask & kHIDElementTypeCollection ) { + result = tIOHIDElementRef; + } + + break; + } + default: { + break; + } + } // switch ( type ) + if ( result ) { + break; // DONE! + } + } // if ( !found ) + + } // next idx + + CFRelease(gElementCFArrayRef); + gElementCFArrayRef = NULL; + } // if ( gElementCFArrayRef ) + + } // if ( inIOHIDDeviceRef ) + + } // if ( inIOHIDElementRef ) + + return (result); +} /* HIDGetNextDeviceElement */ + +#if 0 +// --------------------------------- +// get previous element of given device in list given current element as parameter +// this wlaks directly up the tree to the top element and does not search at each level +// returns NULL if beginning of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +IOHIDElementRef HIDGetPreviousDeviceElement(IOHIDElementRef pElement, HIDElementTypeMask typeMask) { + IOHIDElementRef pPreviousElement = pElement->pPrevious; + + // walk back up tree to element prior + while ( pPreviousElement && !HIDMatchElementTypeMask(pPreviousElement->type, typeMask) ) { + pElement = pPreviousElement; // look at previous element + pPreviousElement = pElement->pPrevious; + } + + return (pPreviousElement); // return this element +} /* HIDGetPreviousDeviceElement */ + +#endif + +// utility routine to dump device info +void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef) { + char cstring[256]; + + printf("Device: %p = { ", inIOHIDDeviceRef); + + char manufacturer[256] = ""; // name of manufacturer + CFStringRef tCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, manufacturer, sizeof(manufacturer), kCFStringEncodingUTF8) ); + } + + char product[256] = ""; // name of product + tCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, product, sizeof(product), kCFStringEncodingUTF8) ); + } + + printf("%s - %s, ", manufacturer, product); + + long vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef); + if ( vendorID ) { +#if 1 + printf(" vendorID: 0x%04lX, ", vendorID); +#else + if ( HIDGetVendorNameFromVendorID(vendorID, cstring) ) { + printf(" vendorID: 0x%04lX (\"%s\"), ", vendorID, cstring); + } else { + printf(" vendorID: 0x%04lX, ", vendorID); + } + +#endif + } + + long productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef); + if ( productID ) { +#if 1 + printf(" productID: 0x%04lX, ", productID); +#else + if ( HIDGetProductNameFromVendorProductID(vendorID, productID, cstring) ) { + printf(" productID: 0x%04lX (\"%s\"), ", productID, cstring); + } else { + printf(" productID: 0x%04lX, ", productID); + } + +#endif + } + + uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef); + uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef); + if ( !usagePage || !usage ) { + usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef); + usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef); + } + + printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage); + +#if 1 + tCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("\"%s\", ", cstring); + CFRelease(tCFStringRef); + } + +#endif + +#if 1 + tCFStringRef = IOHIDDevice_GetTransport(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("Transport: \"%s\", ", cstring); + } + + long vendorIDSource = IOHIDDevice_GetVendorIDSource(inIOHIDDeviceRef); + if ( vendorIDSource ) { + printf("VendorIDSource: %ld, ", vendorIDSource); + } + + long version = IOHIDDevice_GetVersionNumber(inIOHIDDeviceRef); + if ( version ) { + printf("version: %ld, ", version); + } + + tCFStringRef = IOHIDDevice_GetSerialNumber(inIOHIDDeviceRef); + if ( tCFStringRef ) { + verify( CFStringGetCString(tCFStringRef, cstring, sizeof(cstring), kCFStringEncodingUTF8) ); + printf("SerialNumber: \"%s\", ", cstring); + } + + long country = IOHIDDevice_GetCountryCode(inIOHIDDeviceRef); + if ( country ) { + printf("CountryCode: %ld, ", country); + } + + long locationID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef); + if ( locationID ) { + printf("locationID: 0x%08lX, ", locationID); + } + +#if 0 + CFArrayRef pairs = IOHIDDevice_GetUsagePairs(inIOHIDDeviceRef); + if ( pairs ) { + CFIndex idx, cnt = CFArrayGetCount(pairs); + for ( idx = 0; idx < cnt; idx++ ) { + const void *pair = CFArrayGetValueAtIndex(pairs, idx); + CFShow(pair); + } + } + +#endif + long maxInputReportSize = IOHIDDevice_GetMaxInputReportSize(inIOHIDDeviceRef); + if ( maxInputReportSize ) { + printf("MaxInputReportSize: %ld, ", maxInputReportSize); + } + + long maxOutputReportSize = IOHIDDevice_GetMaxOutputReportSize(inIOHIDDeviceRef); + if ( maxOutputReportSize ) { + printf("MaxOutputReportSize: %ld, ", maxOutputReportSize); + } + + long maxFeatureReportSize = IOHIDDevice_GetMaxFeatureReportSize(inIOHIDDeviceRef); + if ( maxFeatureReportSize ) { + printf("MaxFeatureReportSize: %ld, ", maxOutputReportSize); + } + + long reportInterval = IOHIDDevice_GetReportInterval(inIOHIDDeviceRef); + if ( reportInterval ) { + printf("ReportInterval: %ld, ", reportInterval); + } + + IOHIDQueueRef queueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef); + if ( queueRef ) { + printf("queue: %p, ", queueRef); + } + + IOHIDTransactionRef transactionRef = IOHIDDevice_GetTransaction(inIOHIDDeviceRef); + if ( transactionRef ) { + printf("transaction: %p, ", transactionRef); + } + +#endif + printf("}\n"); + fflush(stdout); +} // HIDDumpDeviceInfo + +// utility routine to dump element info +void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef) { + if ( inIOHIDElementRef ) { + printf(" Element: %p = { ", inIOHIDElementRef); +#if 0 + IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef); + printf("Device: %p, ", tIOHIDDeviceRef); +#endif + IOHIDElementRef parentIOHIDElementRef = IOHIDElementGetParent(inIOHIDElementRef); + printf("parent: %p, ", parentIOHIDElementRef); +#if 0 + CFArrayRef childrenCFArrayRef = IOHIDElementGetChildren(inIOHIDElementRef); + printf("children: %p: { ", childrenCFArrayRef); + fflush(stdout); + CFShow(childrenCFArrayRef); + fflush(stdout); + printf(" }, "); +#endif + IOHIDElementCookie tIOHIDElementCookie = IOHIDElementGetCookie(inIOHIDElementRef); + printf("cookie: %p, ", tIOHIDElementCookie); + + IOHIDElementType tIOHIDElementType = IOHIDElementGetType(inIOHIDElementRef); + + switch ( tIOHIDElementType ) { + case kIOHIDElementTypeInput_Misc: + { + printf("type: Misc, "); + break; + } + + case kIOHIDElementTypeInput_Button: + { + printf("type: Button, "); + break; + } + + case kIOHIDElementTypeInput_Axis: + { + printf("type: Axis, "); + break; + } + + case kIOHIDElementTypeInput_ScanCodes: + { + printf("type: ScanCodes, "); + break; + } + + case kIOHIDElementTypeOutput: + { + printf("type: Output, "); + break; + } + + case kIOHIDElementTypeFeature: + { + printf("type: Feature, "); + break; + } + + case kIOHIDElementTypeCollection: + { + IOHIDElementCollectionType tIOHIDElementCollectionType = IOHIDElementGetCollectionType(inIOHIDElementRef); + + switch ( tIOHIDElementCollectionType ) { + case kIOHIDElementCollectionTypePhysical: + { + printf("type: Physical Collection, "); + break; + } + + case kIOHIDElementCollectionTypeApplication: + { + printf("type: Application Collection, "); + break; + } + + case kIOHIDElementCollectionTypeLogical: + { + printf("type: Logical Collection, "); + break; + } + + case kIOHIDElementCollectionTypeReport: + { + printf("type: Report Collection, "); + break; + } + + case kIOHIDElementCollectionTypeNamedArray: + { + printf("type: Named Array Collection, "); + break; + } + + case kIOHIDElementCollectionTypeUsageSwitch: + { + printf("type: Usage Switch Collection, "); + break; + } + + case kIOHIDElementCollectionTypeUsageModifier: + { + printf("type: Usage Modifier Collection, "); + break; + } + + default: + { + printf("type: %p Collection, ", (void *) tIOHIDElementCollectionType); + break; + } + } // switch + + break; + } + + default: + { + printf("type: %p, ", (void *) tIOHIDElementType); + break; + } + } /* switch */ + + uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); + uint32_t usage = IOHIDElementGetUsage(inIOHIDElementRef); + printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage); +#if 1 + CFStringRef tCFStringRef = HIDCopyUsageName(usagePage, usage); + if ( tCFStringRef ) { + char usageString[256] = ""; + verify( CFStringGetCString(tCFStringRef, usageString, sizeof(usageString), kCFStringEncodingUTF8) ); + printf("\"%s\", ", usageString); + CFRelease(tCFStringRef); + } + +#endif + CFStringRef nameCFStringRef = IOHIDElementGetName(inIOHIDElementRef); + char buffer[256]; + if ( nameCFStringRef && CFStringGetCString(nameCFStringRef, buffer, sizeof(buffer), kCFStringEncodingUTF8) ) { + printf("name: %s, ", buffer); + } + + uint32_t reportID = IOHIDElementGetReportID(inIOHIDElementRef); + uint32_t reportSize = IOHIDElementGetReportSize(inIOHIDElementRef); + uint32_t reportCount = IOHIDElementGetReportCount(inIOHIDElementRef); + printf("report: { ID: %lu, Size: %lu, Count: %lu }, ", + (long unsigned int) reportID, (long unsigned int) reportSize, (long unsigned int) reportCount); + + uint32_t unit = IOHIDElementGetUnit(inIOHIDElementRef); + uint32_t unitExp = IOHIDElementGetUnitExponent(inIOHIDElementRef); + if ( unit || unitExp ) { + printf("unit: %lu * 10^%lu, ", (long unsigned int) unit, (long unsigned int) unitExp); + } + + CFIndex logicalMin = IOHIDElementGetLogicalMin(inIOHIDElementRef); + CFIndex logicalMax = IOHIDElementGetLogicalMax(inIOHIDElementRef); + if ( logicalMin != logicalMax ) { + printf("logical: {min: %ld, max: %ld}, ", logicalMin, logicalMax); + } + + CFIndex physicalMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); + CFIndex physicalMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); + if ( physicalMin != physicalMax ) { + printf("physical: {min: %ld, max: %ld}, ", physicalMin, physicalMax); + } + + Boolean isVirtual = IOHIDElementIsVirtual(inIOHIDElementRef); + if ( isVirtual ) { + printf("isVirtual, "); + } + + Boolean isRelative = IOHIDElementIsRelative(inIOHIDElementRef); + if ( isRelative ) { + printf("isRelative, "); + } + + Boolean isWrapping = IOHIDElementIsWrapping(inIOHIDElementRef); + if ( isWrapping ) { + printf("isWrapping, "); + } + + Boolean isArray = IOHIDElementIsArray(inIOHIDElementRef); + if ( isArray ) { + printf("isArray, "); + } + + Boolean isNonLinear = IOHIDElementIsNonLinear(inIOHIDElementRef); + if ( isNonLinear ) { + printf("isNonLinear, "); + } + + Boolean hasPreferredState = IOHIDElementHasPreferredState(inIOHIDElementRef); + if ( hasPreferredState ) { + printf("hasPreferredState, "); + } + + Boolean hasNullState = IOHIDElementHasNullState(inIOHIDElementRef); + if ( hasNullState ) { + printf("hasNullState, "); + } + + printf(" }\n"); + } +} // HIDDumpElementInfo + +void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef) { + printf(" Element: %p = { ", inIOHIDElementRef); + + CFIndex calMin = IOHIDElement_GetCalibrationMin(inIOHIDElementRef); + CFIndex calMax = IOHIDElement_GetCalibrationMax(inIOHIDElementRef); + printf("cal: {min: %ld, max: %ld}, ", calMin, calMax); + + CFIndex satMin = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef); + CFIndex satMax = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef); + printf("sat: {min: %ld, max: %ld}, ", satMin, satMax); + + CFIndex deadMin = IOHIDElement_GetCalibrationDeadZoneMin(inIOHIDElementRef); + CFIndex deadMax = IOHIDElement_GetCalibrationDeadZoneMax(inIOHIDElementRef); + printf("dead: {min: %ld, max: %ld}, ", deadMin, deadMax); + + double_t granularity = IOHIDElement_GetCalibrationGranularity(inIOHIDElementRef); + printf("granularity: %6.2f }\n", granularity); +} // HIDDumpElementCalibrationInfo + +//*************************************************** +#pragma mark - local ( static ) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// CFSetApplierFunctionCopyToCFArray( value, context ) +// +// Purpose: CFSetApplierFunction to copy the CFSet to a CFArray +// +// Notes: called one time for each item in the CFSet +// +// Inputs: value - the current element of the CFSet +// context - the CFMutableArrayRef we're adding the CFSet elements to +// +// Returns: nothing +// +static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context) { + // printf( "%s: 0x%08lX\n", __PRETTY_FUNCTION__, (long unsigned int) value ); + CFArrayAppendValue( (CFMutableArrayRef) context, value ); +} // CFSetApplierFunctionCopyToCFArray + +// --------------------------------- +// used to sort the CFDevice array after copying it from the (unordered) (CF)set. +// we compare based on the location ID's since they're consistant (across boots & launches). +// +static CFComparisonResult CFDeviceArrayComparatorFunction(const void *val1, const void *val2, void *context) { +#pragma unused( context ) + CFComparisonResult result = kCFCompareEqualTo; + + long loc1 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val1 ); + long loc2 = IOHIDDevice_GetLocationID( (IOHIDDeviceRef) val2 ); + if ( loc1 < loc2 ) { + result = kCFCompareLessThan; + } else if ( loc1 > loc2 ) { + result = kCFCompareGreaterThan; + } + + return (result); +} // CFDeviceArrayComparatorFunction + +//************************************************************************* +// +// hu_SetUpMatchingDictionary( inUsagePage, inUsage ) +// +// Purpose: builds a matching dictionary based on usage page and usage +// +// Notes: Only called by HIDBuildMultiDeviceList +// +// Inputs: inUsagePage - usage page +// inUsage - usages +// +// Returns: CFMutableDictionaryRef - the matching dictionary +// + +static CFMutableDictionaryRef hu_SetUpMatchingDictionary(UInt32 inUsagePage, UInt32 inUsage) { + // create a dictionary to add usage page/usages to + CFMutableDictionaryRef refHIDMatchDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if ( refHIDMatchDictionary ) { + if ( inUsagePage ) { + // Add key for device type to refine the matching dictionary. + CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage); + if ( pageCFNumberRef ) { + CFDictionarySetValue(refHIDMatchDictionary, + CFSTR(kIOHIDPrimaryUsagePageKey), pageCFNumberRef); + CFRelease(pageCFNumberRef); + // note: the usage is only valid if the usage page is also defined + if ( inUsage ) { + CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage); + if ( usageCFNumberRef ) { + CFDictionarySetValue(refHIDMatchDictionary, + CFSTR(kIOHIDPrimaryUsageKey), usageCFNumberRef); + CFRelease(usageCFNumberRef); + } else { + fprintf(stderr, "%s: CFNumberCreate( usage ) failed.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: CFNumberCreate( usage page ) failed.", __PRETTY_FUNCTION__); + } + } + } else { + fprintf(stderr, "%s: CFDictionaryCreateMutable failed.", __PRETTY_FUNCTION__); + } + + return (refHIDMatchDictionary); +} // hu_SetUpMatchingDictionary + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/HID_Utilities_External.h b/src/cocoa/HID_Utilities_External.h new file mode 100644 index 000000000..bd498bc51 --- /dev/null +++ b/src/cocoa/HID_Utilities_External.h @@ -0,0 +1,417 @@ +// File: HID_Utilities_External.h +// Abstract: External interface for HID Utilities, can be used with either library or source. +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef _HID_Utilities_External_h_ +#define _HID_Utilities_External_h_ + +// ================================== + +#ifdef __cplusplus +extern "C" { +#endif + +// ================================== + +//includes + +#include +#include "IOHIDLib_.h" + +// ================================== + +#ifndef _IOKIT_HID_IOHIDKEYS_H_ +/*! + @typedef IOHIDElementCookie + @abstract Abstract data type used as a unique identifier for an element. + */ +#ifdef __LP64__ +typedef uint32_t IOHIDElementCookie; +#else +typedef void *IOHIDElementCookie; +#endif +#endif + +// Device and Element Interfaces + +enum HIDElementTypeMask { + kHIDElementTypeInput = 1 << 1, + kHIDElementTypeOutput = 1 << 2, + kHIDElementTypeFeature = 1 << 3, + kHIDElementTypeCollection = 1 << 4, + kHIDElementTypeIO = kHIDElementTypeInput | kHIDElementTypeOutput | kHIDElementTypeFeature, + kHIDElementTypeAll = kHIDElementTypeIO | kHIDElementTypeCollection +}; +typedef enum HIDElementTypeMask HIDElementTypeMask; + +// ================================== + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +extern IOHIDManagerRef gIOHIDManagerRef; +extern CFMutableArrayRef gDeviceCFArrayRef; +extern CFArrayRef gElementCFArrayRef; + +//************************************************************************* +// +// HIDBuildMultiDeviceList( inUsagePages, inUsages, inNumDeviceTypes ) +// +// Purpose: builds list of devices with elements (allocates memory and captures devices) in which +// the devices could be of different types/usages list is allocated internally within HID +// Utilites and can be accessed via accessor functions structures within list are considered +// flat and user accessable, but not user modifiable can be called again to rebuild list to +// account for new devices (will do the right thing in case of disposing existing list) +// usagePage, usage are each a numDeviceTypes sized array of matching usage and usage pages +// returns true if succesful +// +// Inputs: inUsagePages - inNumDeviceTypes sized array of matching usage pages +// inUsages - inNumDeviceTypes sized array of matching usages +// inNumDeviceTypes - number of usage pages & usages +// +// Returns: Boolean - if successful +// +extern Boolean HIDBuildMultiDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes); + +// same as above but this uses a single usagePage and usage +extern Boolean HIDBuildDeviceList(UInt32 usagePage, UInt32 usage); + +// updates the current device list for any new/removed devices +// if this is called before HIDBuildDeviceList the it functions like HIDBuildMultiDeviceList +// usagePage, usage are each a numDeviceTypes sized array of matching usage and usage pages +// returns true if successful which means if any device were added or removed (the device config changed) +extern Boolean HIDUpdateDeviceList(const UInt32 *inUsagePages, const UInt32 *inUsages, int inNumDeviceTypes); + +// release list built by above function +// MUST be called prior to application exit to properly release devices +// if not called (or app crashes) devices can be recovered by pluging into different location in USB chain +extern void HIDReleaseDeviceList(void); + +//************************************************************************* +// +// HIDRebuildDevices( ) +// +// Purpose: rebuilds the (internal) list of devices +// +// Inputs: none +// +// Returns: none +// + +extern void HIDRebuildDevices(void); + +// does a device list exist +extern unsigned char HIDHaveDeviceList(void); + +// how many HID devices have been found +// returns 0 if no device list exist +extern UInt32 HIDCountDevices(void); + +// how many elements does a specific device have +// returns 0 if device is invalid or NULL +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +extern UInt32 HIDCountDeviceElements(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask); + +// get the first device in the device list +// returns NULL if no list exists +extern IOHIDDeviceRef HIDGetFirstDevice(void); + +// get next device in list given current device as parameter +// returns NULL if end of list +extern IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// get the first element of device passed in as parameter +// returns NULL if no list exists or device does not exists or is NULL +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetFirstDeviceElement functionality +extern IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef inIOHIDDeviceRef, HIDElementTypeMask typeMask); + +// get next element of given device in list given current element as parameter +// will walk down each collection then to next element or collection (depthwise traverse) +// returns NULL if end of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality +extern IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef inIOHidElementRef, HIDElementTypeMask typeMask); + +// get previous element of given device in list given current element as parameter +// this walks directly up the tree to the top element and does not search at each level +// returns NULL if beginning of list +// uses mask of HIDElementTypeMask to restrict element found +// use kHIDElementTypeIO to get non-collection elements +extern IOHIDElementRef HIDGetPreviousDeviceElement(IOHIDElementRef inIOHidElementRef, HIDElementTypeMask typeMask); + +// returns C string type name given a type enumeration passed in as parameter( see IOHIDKeys.h ) +// returns empty string for invalid types +extern void HIDGetTypeName(IOHIDElementType inIOHIDElementType, char *outCStrName); + +//************************************************************************* +// +// HIDCopyUsageName( inUsagePage, inUsage ) +// +// Purpose: return a CFStringRef string for a given usage page & usage( see IOUSBHIDParser.h ) +// +// Notes: returns usage page and usage values in CFString form for unknown values +// +// Inputs: inUsagePage - the usage page +// inUsage - the usage +// +// Returns: CFStringRef - the resultant string +// + +extern CFStringRef HIDCopyUsageName(long inUsagePage, long inUsage); + +// ================================== + +// Element Event Queue and Value Interfaces + +enum { + kDefaultUserMin = 0, // default user min and max used for scaling + kDefaultUserMax = 255 +}; + +enum { + kDeviceQueueSize = 50 // this is wired kernel memory so should be set to as small as possible + // but should account for the maximum possible events in the queue + // USB updates will likely occur at 100 Hz so one must account for this rate of + // if states change quickly (updates are only posted on state changes) +}; + +// ================================== + +// queues specific element, performing any device queue set up required +extern int HIDQueueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef); + +// adds all elements to queue, performing any device queue set up required +extern int HIDQueueDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// removes element for queue, if last element in queue will release queue and device +extern int HIDDequeueElement(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef); + +// completely removes all elements from queue and releases queue and device +extern int HIDDequeueDevice(IOHIDDeviceRef inIOHIDDeviceRef); + +// releases all device queues for quit or rebuild (must be called) +extern int HIDReleaseAllDeviceQueues(void); + +// returns true if an event is avialable for the element and fills out *pHIDEvent structure, returns false otherwise +// pHIDEvent is a poiner to a IOHIDEventStruct, using void here for compatibility, users can cast a required +extern unsigned char HIDGetEvent(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDValueRef *pIOHIDValueRef); + +// ================================== + +// Conguration and Save Interfaces + +enum { + kPercentMove = 10 // precent of overall range a element must move to register +}; + +typedef struct HID_info_struct { + int actionCookie; + // device + // need to add serial number when I have a test case + struct { + int vendorID, productID; + int locID; + uint32_t usagePage, usage; + } device; + // elements + struct { + uint32_t usagePage, usage; + int minReport, maxReport; + IOHIDElementCookie cookie; // always 32 bits + } element; +}HID_info_rec, *HID_info_ptr; + +// get vendor name from vendor ID +extern Boolean HIDGetVendorNameFromVendorID(long inVendorID, char *outCStrName); + +// get product name from vendor/product ID +extern Boolean HIDGetProductNameFromVendorProductID(long inVendorID, long inProductID, char *outCStrName); + +// get element name from vendor id/product id look up ( using element cookie ) +extern Boolean HIDGetElementNameFromVendorProductCookie(int inVendorID, + int inProductID, + IOHIDElementCookie inCookie, + char * outCStrName); + +// get element name from vendor id/product id look up ( using element usage page & usage ) +extern Boolean HIDGetElementNameFromVendorProductUsage(long inVendorID, + long inProductID, + long inUsagePage, + long inUsage, + char *inCStrName); + +// utility routines to dump device or element info +extern void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef); +extern void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef); +extern void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef); + +// polls single device's elements for a change greater than kPercentMove. Times out after given time +// returns 1 and pointer to element if found +// returns 0 and NULL for both parameters if not found +extern unsigned char HIDConfigureSingleDeviceAction(IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef *outIOHIDElementRef, + float timeout); + +//************************************************************************* +// +// HIDConfigureAction( outDeviceRef, outElementRef, inTimeout ) +// +// Purpose: polls all devices and elements for a change greater than kPercentMove. +// Times out after given time returns 1 and pointer to device and element +// if found; returns 0 and NULL for both parameters if not found +// +// Inputs: outDeviceRef - address where to store the device +// outElementRef - address where to store the element +// inTimeout - the timeout +// Returns: Boolean - TRUE if successful +// outDeviceRef - the device +// outElementRef - the element +// + +extern Boolean HIDConfigureAction(IOHIDDeviceRef *outDeviceRef, IOHIDElementRef *outElementRef, float inTimeout); + +//************************************************************************* +// +// HIDSaveElementPref( inKeyCFStringRef, inAppCFStringRef, inDeviceRef, inElementRef ) +// +// Purpose: Save the device & element values into the specified key in the specified applications preferences +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// inDeviceRef - the device +// inElementRef - the element +// Returns: Boolean - if successful +// + +extern Boolean HIDSaveElementPref(const CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef inDeviceRef, + IOHIDElementRef inElementRef); + +//************************************************************************* +// +// HIDRestoreElementPref( inKeyCFStringRef, inAppCFStringRef, outDeviceRef, outElementRef ) +// +// Purpose: Find the specified preference in the specified application +// +// Inputs: inKeyCFStringRef - the preference key +// inAppCFStringRef - the application identifier +// outDeviceRef - address where to restore the device +// outElementRef - address where to restore the element +// Returns: Boolean - if successful +// outDeviceRef - the device +// outElementRef - the element +// + +extern Boolean HIDRestoreElementPref(CFStringRef inKeyCFStringRef, + CFStringRef inAppCFStringRef, + IOHIDDeviceRef * outDeviceRef, + IOHIDElementRef *outElementRef); + +//************************************************************************* +// +// HIDFindDeviceAndElement( inSearchInfo, outFoundDevice, outFoundElement ) +// +// Purpose: find the closest matching device and element for this action +// +// Notes: matches device: serial, vendorID, productID, location, inUsagePage, usage +// matches element: cookie, inUsagePage, usage, +// +// Inputs: inSearchInfo - the device & element info we searching for +// outFoundDevice - the address of the best matching device +// outFoundElement - the address of the best matching element +// +// Returns: Boolean - TRUE if we find a match +// outFoundDevice - the best matching device +// outFoundElement - the best matching element +// + +extern Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo, + IOHIDDeviceRef * outFoundDevice, + IOHIDElementRef * outFoundElement); + +// -- These are routines to use if the applcationwants HID Utilities to do the file handling -- +// Note: the FILE * is a MachO posix FILE and will not likely work directly with MW MSL FILE * type. + +// take input records, save required info +// assume file is open and at correct position. +void HIDSaveElementConfig(FILE *fileRef, IOHIDDeviceRef inIOHIDDeviceRef, IOHIDElementRef inIOHidElementRef, int actionCookie); + +// takes a file, reads one record (assume file position is correct and file is open) +// search for matching device +// return tIOHIDDeviceRef, tIOHIDElementRef and cookie for action +int HIDRestoreElementConfig(FILE *fileRef, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef); + +// -- These are routines to use if the client wants to use their own file handling -- + +// Set up a config record for saving +// takes an input records, returns record user can save as they want +// Note: the save rec must be pre-allocated by the calling app and will be filled out +void HIDSetElementConfig(HID_info_ptr inHIDInfoPtr, + IOHIDDeviceRef inIOHIDDeviceRef, + IOHIDElementRef inIOHidElementRef, + int actionCookie); + +// Get matching element from config record +// takes a pre-allocated and filled out config record +// search for matching device +// return tIOHIDDeviceRef, tIOHIDElementRef and cookie for action +int HIDGetElementConfig(HID_info_ptr inHIDInfoPtr, IOHIDDeviceRef *outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef); + +// ================================== + +// Error reporter, can be set to report however the application desires +extern void HIDReportError(const char *strError); + +// Error with numeric code reporter, can be set to report however the application desires +extern void HIDReportErrorNum(const char *strError, int numError); + +#ifdef __cplusplus +} +#endif + +#endif // _HID_Utilities_External_h_ diff --git a/src/cocoa/IOHIDDevice_.c b/src/cocoa/IOHIDDevice_.c new file mode 100644 index 000000000..30c01dc7c --- /dev/null +++ b/src/cocoa/IOHIDDevice_.c @@ -0,0 +1,619 @@ +// File: IOHIDDevice_.c +// Abstract: convieance functions for IOHIDDeviceGetProperty +// Version: 2.0 + 5.3 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#pragma mark - includes & imports +//----------------------------------------------------- + +#include "IOHIDDevice_.h" + +//***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. +//----------------------------------------------------- + +#define kIOHIDDevice_TransactionKey "DeviceTransactionRef" +#define kIOHIDDevice_QueueKey "DeviceQueueRef" + +//***************************************************** +#pragma mark - local (static) function prototypes +//----------------------------------------------------- + +static Boolean IOHIDDevice_GetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + uint32_t * outValue); +// static void IOHIDDevice_SetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t inValue); + +static Boolean IOHIDDevice_GetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + void ** outValue); +static void IOHIDDevice_SetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, + CFStringRef inKey, + void * inValue); + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDIsValidDevice( inIOHIDDeviceRef ) +// +// Purpose: validate this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: Boolean - TRUE if we find the device in our( internal ) device list +// + +Boolean HIDIsValidDevice(IOHIDDeviceRef inIOHIDDeviceRef) { + Boolean result = FALSE; // assume failure (pessimist!) + if ( inIOHIDDeviceRef ) { + if ( CFGetTypeID(inIOHIDDeviceRef) ==IOHIDDeviceGetTypeID() ) { + result = TRUE; + } + } + + return (result); +} // HIDIsValidDevice + +//************************************************************************* +// +// IOHIDDevice_GetTransport( inIOHIDDeviceRef ) +// +// Purpose: get the Transport CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Transport for this device +// + +CFStringRef IOHIDDevice_GetTransport(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDTransportKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetVendorID( inIOHIDDeviceRef ) +// +// Purpose: get the vendor ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the vendor ID for this device +// + +uint32_t IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey), &result); + return (result); +} // IOHIDDevice_GetVendorID + +//************************************************************************* +// +// IOHIDDevice_GetVendorIDSource( inIOHIDDeviceRef ) +// +// Purpose: get the VendorIDSource for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the VendorIDSource for this device +// + +uint32_t IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDSourceKey), &result); + return (result); +} // IOHIDDevice_GetVendorIDSource + +//************************************************************************* +// +// IOHIDDevice_GetProductID( inIOHIDDeviceRef ) +// +// Purpose: get the product ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the product ID for this device +// + +uint32_t IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey), &result); + return (result); +} // IOHIDDevice_GetProductID + +//************************************************************************* +// +// IOHIDDevice_GetVersionNumber( inIOHIDDeviceRef ) +// +// Purpose: get the VersionNumber CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the VersionNumber for this device +// + +uint32_t IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDVersionNumberKey), &result); + return (result); +} // IOHIDDevice_GetVersionNumber + +//************************************************************************* +// +// IOHIDDevice_GetManufacturer( inIOHIDDeviceRef ) +// +// Purpose: get the Manufacturer CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Manufacturer for this device +// + +CFStringRef IOHIDDevice_GetManufacturer(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDManufacturerKey) ) ); +} // IOHIDDevice_GetManufacturer + +//************************************************************************* +// +// IOHIDDevice_GetProduct( inIOHIDDeviceRef ) +// +// Purpose: get the Product CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the Product for this device +// + +CFStringRef IOHIDDevice_GetProduct(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDProductKey) ) ); +} // IOHIDDevice_GetProduct + +//************************************************************************* +// +// IOHIDDevice_GetSerialNumber( inIOHIDDeviceRef ) +// +// Purpose: get the SerialNumber CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFStringRef - the SerialNumber for this device +// + +CFStringRef IOHIDDevice_GetSerialNumber(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDSerialNumberKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetCountryCode( inIOHIDDeviceRef ) +// +// Purpose: get the CountryCode CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the CountryCode for this device +// + +uint32_t IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDCountryCodeKey), &result); + return (result); +} // IOHIDDevice_GetCountryCode + +//************************************************************************* +// +// IOHIDDevice_GetLocationID( inIOHIDDeviceRef ) +// +// Purpose: get the location ID for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the location ID for this device +// + +uint32_t IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey), &result); + return (result); +} // IOHIDDevice_GetLocationID + +//************************************************************************* +// +// IOHIDDevice_GetUsage( inIOHIDDeviceRef ) +// +// Purpose: get the usage for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the usage for this device +// + +uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsageKey), &result); + return (result); +} // IOHIDDevice_GetUsage + +//************************************************************************* +// +// IOHIDDevice_GetUsagePage( inIOHIDDeviceRef ) +// +// Purpose: get the usage page for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the usage page for this device +// + +uint32_t IOHIDDevice_GetUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePageKey), &result); + return (result); +} // IOHIDDevice_GetUsagePage + +//************************************************************************* +// +// IOHIDDevice_GetUsagePairs( inIOHIDDeviceRef ) +// +// Purpose: get the UsagePairs CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: CFArrayRef - the UsagePairs for this device +// + +CFArrayRef IOHIDDevice_GetUsagePairs(IOHIDDeviceRef inIOHIDDeviceRef) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + return ( IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDDeviceUsagePairsKey) ) ); +} + +//************************************************************************* +// +// IOHIDDevice_GetPrimaryUsage( inIOHIDDeviceRef ) +// +// Purpose: get the PrimaryUsage CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the PrimaryUsage for this device +// + +uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey), &result); + return (result); +} // IOHIDDevice_GetPrimaryUsage + +//************************************************************************* +// +// IOHIDDevice_GetPrimaryUsagePage( inIOHIDDeviceRef ) +// +// Purpose: get the PrimaryUsagePage CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the PrimaryUsagePage for this device +// + +uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey), &result); + return (result); +} // IOHIDDevice_GetPrimaryUsagePage + +//************************************************************************* +// +// IOHIDDevice_GetMaxInputReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxInputReportSize CFString for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the MaxInputReportSize for this device +// + +uint32_t IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxInputReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxInputReportSize + +//************************************************************************* +// +// IOHIDDevice_GetMaxOutputReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxOutputReportSize for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the MaxOutput for this device +// + +uint32_t IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxOutputReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxOutputReportSize + +//************************************************************************* +// +// IOHIDDevice_GetMaxFeatureReportSize( inIOHIDDeviceRef ) +// +// Purpose: get the MaxFeatureReportSize for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the MaxFeatureReportSize for this device +// + +uint32_t IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDMaxFeatureReportSizeKey), &result); + return (result); +} // IOHIDDevice_GetMaxFeatureReportSize + +//************************************************************************* +// +// IOHIDDevice_GetReportInterval( inIOHIDDeviceRef ) +// +// Purpose: get the ReportInterval for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: uint32_t - the ReportInterval for this device +// +#ifndef kIOHIDReportIntervalKey +#define kIOHIDReportIntervalKey "ReportInterval" +#endif +uint32_t IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef) { + uint32_t result = 0; + (void) IOHIDDevice_GetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDReportIntervalKey), &result); + return (result); +} // IOHIDDevice_GetReportInterval + +//************************************************************************* +// +// IOHIDDevice_GetQueue( inIOHIDDeviceRef ) +// +// Purpose: get the Queue for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: IOHIDQueueRef - the Queue for this device +// + +IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDQueueRef result = 0; + (void) IOHIDDevice_GetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), (void *) &result); + if ( result ) { + assert( IOHIDQueueGetTypeID() == CFGetTypeID(result) ); + } + + return (result); +} // IOHIDDevice_GetQueue + +//************************************************************************* +// +// IOHIDDevice_SetQueue( inIOHIDDeviceRef, inQueueRef ) +// +// Purpose: Set the Queue for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// inQueueRef - the Queue reference +// +// Returns: nothing +// + +void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueueRef) { + IOHIDDevice_SetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_QueueKey), inQueueRef); +} + +//************************************************************************* +// +// IOHIDDevice_GetTransaction( inIOHIDDeviceRef ) +// +// Purpose: get the Transaction for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// +// Returns: IOHIDTransactionRef - the Transaction for this device +// + +IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef) { + IOHIDTransactionRef result = 0; + (void) IOHIDDevice_GetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), (void *) &result); + return (result); +} // IOHIDDevice_GetTransaction + +//************************************************************************* +// +// IOHIDDevice_SetTransaction( inIOHIDDeviceRef, inTransactionRef ) +// +// Purpose: Set the Transaction for this device +// +// Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device +// inTransactionRef - the Transaction reference +// +// Returns: nothing +// + +void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactionRef inTransactionRef) { + IOHIDDevice_SetPtrProperty(inIOHIDDeviceRef, CFSTR(kIOHIDDevice_TransactionKey), inTransactionRef); +} + +//***************************************************** +#pragma mark - local (static) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// IOHIDDevice_GetUInt32Property( inIOHIDDeviceRef, inKey, outValue ) +// +// Purpose: convieance function to return a uint32_t property of a device +// +// Inputs: inIOHIDDeviceRef - the device +// inKey - CFString for the +// outValue - address where to restore the element +// Returns: the action cookie +// outValue - the device +// + +static Boolean IOHIDDevice_GetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t *outValue) { + Boolean result = FALSE; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); + } + } + } + + return (result); +} // IOHIDDevice_GetUInt32Property + +//************************************************************************* +// +// IOHIDDevice_SetUInt32Property( inIOHIDDeviceRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an Device +// +// Inputs: inIOHIDDeviceRef - the Device +// inKey - CFString for the key +// inValue - the value to set it to +// Returns: nothing +// +#if 0 // unused +static void IOHIDDevice_SetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, uint32_t inValue) { + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); + if ( tCFNumberRef ) { + IOHIDDeviceSetProperty(inIOHIDDeviceRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDDevice_SetUInt32Property +#endif +//************************************************************************* +// +// IOHIDDevice_GetPtrProperty( inIOHIDDeviceRef, inKey, outValue ) +// +// Purpose: convieance function to return a pointer property of a device +// +// Inputs: inIOHIDDeviceRef - the device +// inKey - CFString for the +// outValue - address where to restore the element +// Returns: the action cookie +// outValue - the device +// + +static Boolean IOHIDDevice_GetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, void **outValue) { + Boolean result = FALSE; + if ( inIOHIDDeviceRef ) { + assert( IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef) ); + + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value +#ifdef __LP64__ + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt64Type, outValue ); +#else + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); +#endif // ifdef __LP64__ + } + } + } + + return (result); +} // IOHIDDevice_GetPtrProperty + +//************************************************************************* +// +// IOHIDDevice_SetPtrProperty( inIOHIDDeviceRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an Device +// +// Inputs: inIOHIDDeviceRef - the Device +// inKey - CFString for the key +// inValue - the value to set it to +// Returns: nothing +// + +static void IOHIDDevice_SetPtrProperty(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey, void *inValue) { +#ifdef __LP64__ + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &inValue); +#else + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); +#endif // ifdef __LP64__ + if ( tCFNumberRef ) { + IOHIDDeviceSetProperty(inIOHIDDeviceRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDDevice_SetPtrProperty + +//***************************************************** + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/IOHIDDevice_.h b/src/cocoa/IOHIDDevice_.h new file mode 100644 index 000000000..eebccd667 --- /dev/null +++ b/src/cocoa/IOHIDDevice_.h @@ -0,0 +1,422 @@ +// File: IOHIDDevice_.h +// Abstract: convieance functions for IOHIDDeviceGetProperty +// Version: 2.0 + 5.3 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2014 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDDevice__ +#define __IOHIDDevice__ + +//***************************************************** +#pragma mark - includes & imports + +#include + +#include "IOHIDLib_.h" + +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //************************************************************************* + // + // HIDIsValidDevice( inIOHIDDeviceRef ) + // + // Purpose: validate this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: Boolean - TRUE if we find the device in our( internal ) device list + // + + extern Boolean HIDIsValidDevice(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetTransport( inIOHIDDeviceRef ) + // + // Purpose: get the Transport CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Transport CFString for this device + // + + extern CFStringRef IOHIDDevice_GetTransport(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVendorID( inIOHIDDeviceRef ) + // + // Purpose: get the vendor ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the vendor ID for this device + // + + extern uint32_t IOHIDDevice_GetVendorID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVendorIDSource( inIOHIDDeviceRef ) + // + // Purpose: get the VendorIDSource for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the VendorIDSource for this device + // + + extern uint32_t IOHIDDevice_GetVendorIDSource(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetProductID( inIOHIDDeviceRef ) + // + // Purpose: get the product ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the product ID for this device + // + + extern uint32_t IOHIDDevice_GetProductID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetVersionNumber( inIOHIDDeviceRef ) + // + // Purpose: get the VersionNumber CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the VersionNumber for this device + // + + extern uint32_t IOHIDDevice_GetVersionNumber(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetManufacturer( inIOHIDDeviceRef ) + // + // Purpose: get the Manufacturer CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Manufacturer CFString for this device + // + + extern CFStringRef IOHIDDevice_GetManufacturer(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetProduct( inIOHIDDeviceRef ) + // + // Purpose: get the Product CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the Product CFString for this device + // + + extern CFStringRef IOHIDDevice_GetProduct(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetSerialNumber( inIOHIDDeviceRef ) + // + // Purpose: get the SerialNumber CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the SerialNumber CFString for this device + // + + extern CFStringRef IOHIDDevice_GetSerialNumber(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetCountryCode( inIOHIDDeviceRef ) + // + // Purpose: get the CountryCode CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the CountryCode for this device + // + + extern uint32_t IOHIDDevice_GetCountryCode(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetLocationID( inIOHIDDeviceRef ) + // + // Purpose: get the location ID for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the location ID for this device + // + + extern uint32_t IOHIDDevice_GetLocationID(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsage( inIOHIDDeviceRef ) + // + // Purpose: get the usage for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the usage for this device + // + + extern uint32_t IOHIDDevice_GetUsage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsagePage( inIOHIDDeviceRef ) + // + // Purpose: get the usage page for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the usage page for this device + // + + extern uint32_t IOHIDDevice_GetUsagePage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetUsagePairs( inIOHIDDeviceRef ) + // + // Purpose: get the UsagePairs CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFArrayRef - the UsagePairs for this device + // + + extern CFArrayRef IOHIDDevice_GetUsagePairs(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetPrimaryUsage( inIOHIDDeviceRef ) + // + // Purpose: get the PrimaryUsage CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the PrimaryUsage CFString for this device + // + + extern uint32_t IOHIDDevice_GetPrimaryUsage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetPrimaryUsagePage( inIOHIDDeviceRef ) + // + // Purpose: get the PrimaryUsagePage CFString for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: CFStringRef - the PrimaryUsagePage CFString for this device + // + + extern uint32_t IOHIDDevice_GetPrimaryUsagePage(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxInputReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxInputReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the MaxInputReportSize for this device + // + + extern uint32_t IOHIDDevice_GetMaxInputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxOutputReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxOutputReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the MaxOutputReportSize for this device + // + + extern uint32_t IOHIDDevice_GetMaxOutputReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetMaxFeatureReportSize( inIOHIDDeviceRef ) + // + // Purpose: get the MaxFeatureReportSize for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the MaxFeatureReportSize for this device + // + + extern uint32_t IOHIDDevice_GetMaxFeatureReportSize(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetReportInterval( inIOHIDDeviceRef ) + // + // Purpose: get the ReportInterval for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: uint32_t - the ReportInterval for this device + // + + extern uint32_t IOHIDDevice_GetReportInterval(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_GetQueue( inIOHIDDeviceRef ) + // + // Purpose: get the Queue for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: IOHIDQueueRef - the Queue for this device + // + + extern IOHIDQueueRef IOHIDDevice_GetQueue(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_SetQueue( inIOHIDDeviceRef, inQueueRef ) + // + // Purpose: Set the Queue for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // inQueueRef - the Queue + // + // Returns: nothing + // + + extern void IOHIDDevice_SetQueue(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDQueueRef inQueueRef); + + //************************************************************************* + // + // IOHIDDevice_GetTransaction( inIOHIDDeviceRef ) + // + // Purpose: get the Transaction for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // + // Returns: IOHIDTransactionRef - the Transaction for this device + // + + extern IOHIDTransactionRef IOHIDDevice_GetTransaction(IOHIDDeviceRef inIOHIDDeviceRef); + + //************************************************************************* + // + // IOHIDDevice_SetTransaction( inIOHIDDeviceRef, inTransactionRef ) + // + // Purpose: Set the Transaction for this device + // + // Inputs: inIOHIDDeviceRef - the IDHIDDeviceRef for this device + // inTransactionRef - the Transaction + // + // Returns: nothing + // + + extern void IOHIDDevice_SetTransaction(IOHIDDeviceRef inIOHIDDeviceRef, IOHIDTransactionRef inTransactionRef); + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDDevice__ // diff --git a/src/cocoa/IOHIDElement_.c b/src/cocoa/IOHIDElement_.c new file mode 100644 index 000000000..906d2926a --- /dev/null +++ b/src/cocoa/IOHIDElement_.c @@ -0,0 +1,509 @@ +// File: IOHIDElement_.c +// Abstract: convieance functions for IOHIDElementGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#pragma mark - includes & imports +//----------------------------------------------------- + +#include "IOHIDElement_.h" + +//***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) function prototypes +//----------------------------------------------------- + +// static Boolean IOHIDElement_GetLongProperty( IOHIDElementRef inElementRef, CFStringRef inKey, long * outValue ); +// static void IOHIDElement_SetLongProperty( IOHIDElementRef inElementRef, CFStringRef inKey, long inValue ); + +//***************************************************** +#pragma mark - exported globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - local (static) globals +//----------------------------------------------------- + +//***************************************************** +#pragma mark - exported function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// HIDIsValidElement( inIOHIDElementRef ) +// +// Purpose: validate this element +// +// Inputs: inIOHIDElementRef - the element +// +// Returns: Boolean - TRUE if this is a valid element ref +// +Boolean HIDIsValidElement(IOHIDElementRef inIOHIDElementRef) { + Boolean result = FALSE; // assume failure (pessimist!) + if ( inIOHIDElementRef ) { + if ( CFGetTypeID(inIOHIDElementRef) ==IOHIDElementGetTypeID() ) { + result = TRUE; + } + } + + return (result); +} // HIDIsValidElement + +//************************************************************************* +// +// IOHIDElement_GetValue( inElementRef, inIOHIDValueScaleType ) +// +// Purpose: returns the current value for an element( polling ) +// +// Notes: will return 0 on error conditions which should be accounted for by application +// +// Inputs: inElementRef - the element +// inIOHIDValueScaleType - scale type ( calibrated or physical ) +// +// Returns: double - current value for element +// +double IOHIDElement_GetValue(IOHIDElementRef inElementRef, IOHIDValueScaleType inIOHIDValueScaleType) { + long result = 0; + IOHIDValueRef tIOHIDValueRef; + if ( kIOReturnSuccess == IOHIDDeviceGetValue(IOHIDElementGetDevice(inElementRef), inElementRef, &tIOHIDValueRef) ) { + result = IOHIDValueGetScaledValue(tIOHIDValueRef, inIOHIDValueScaleType); + } + + return (result); +} // IOHIDElement_GetValue + +//************************************************************************* +// +// IOHIDElement_GetCalibrationMin( inElementRef ) +// +// Purpose: get the minimum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the minimum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMaxKey), &result) ) { + result = 0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationMin( inElementRef, inValue ) +// +// Purpose: set the minimum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the minimum bounds for a calibrated value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMinKey), inValue); +} // IOHIDElement_SetCalibrationMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationMax( inElementRef ) +// +// Purpose: get the maximum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationMax( inElementRef, inValue ) +// +// Purpose: set the maximum bounds for a calibrated value for this element +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationMaxKey), inValue); +} // IOHIDElement_SetCalibrationMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationSaturationMin( inElementRef ) +// +// Purpose: get the mininum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationSaturationMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationSaturationMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationSaturationMin( inElementRef, inValue ) +// +// Purpose: set the mininum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationSaturationMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMinKey), inValue); +} // IOHIDElement_SetCalibrationSaturationMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationSaturationMax( inElementRef ) +// +// Purpose: get the maximum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationSaturationMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationSaturationMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationSaturationMax( inElementRef, inValue ) +// +// Purpose: set the maximum tolerance to be used when calibrating a logical element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationSaturationMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationSaturationMaxKey), inValue); +} // IOHIDElement_SetCalibrationSaturationMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationDeadZoneMin( inElementRef ) +// +// Purpose: get the minimum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationDeadZoneMin(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationDeadZoneMin + +//************************************************************************* +// +// IOHIDElement_SetCalibrationDeadZoneMin( inElementRef, inValue ) +// +// Purpose: set the minimum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationDeadZoneMin(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMinKey), inValue); +} // IOHIDElement_SetCalibrationDeadZoneMin + +//************************************************************************* +// +// IOHIDElement_GetCalibrationDeadZoneMax( inElementRef ) +// +// Purpose: get the maximum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: CFIndex - the maximum Calibration value for this element +// + +CFIndex IOHIDElement_GetCalibrationDeadZoneMax(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationDeadZoneMax + +//************************************************************************* +// +// IOHIDElement_SetCalibrationDeadZoneMax( inElementRef, inValue ) +// +// Purpose: set the maximum bounds near the midpoint of a logical value in which the value is ignored +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the maximum Calibration value for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationDeadZoneMax(IOHIDElementRef inElementRef, CFIndex inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationDeadZoneMaxKey), inValue); +} // IOHIDElement_SetCalibrationDeadZoneMax + +//************************************************************************* +// +// IOHIDElement_GetCalibrationGranularity( inElementRef ) +// +// Purpose: get the level of detail returned for a calibrated element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: double_t - the maximum Calibration value for this element +// + +double_t IOHIDElement_GetCalibrationGranularity(IOHIDElementRef inElementRef) { + CFIndex result; + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), &result) ) { + if ( !IOHIDElement_GetLongProperty(inElementRef, CFSTR(kIOHIDElementMinKey), &result) ) { + result = -0x7FFFFFFF; + } + + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), result); + } + + return (result); +} // IOHIDElement_GetCalibrationGranularity + +//************************************************************************* +// +// IOHIDElement_SetCalibrationGranularity( inElementRef, inValue ) +// +// Purpose: set the level of detail returned for a calibrated element value +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// inValue - the the level of detail for this element +// +// Returns: nothing +// + +void IOHIDElement_SetCalibrationGranularity(IOHIDElementRef inElementRef, double_t inValue) { + IOHIDElement_SetLongProperty(inElementRef, CFSTR(kIOHIDElementCalibrationGranularityKey), inValue); +} // IOHIDElement_SetCalibrationGranularity + +//************************************************************************* +// +// IOHIDElement_SetupCalibration( inElementRef ) +// +// Purpose: set default values for the element calibration parameters +// +// Inputs: inElementRef - the IOHIDElementRef for this element +// +// Returns: nothing +// +void IOHIDElement_SetupCalibration(IOHIDElementRef inIOHIDElementRef) { + // these are the min/max values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); + IOHIDElement_SetCalibrationMin( inIOHIDElementRef, IOHIDElementGetLogicalMin(inIOHIDElementRef) ); + IOHIDElement_SetCalibrationMax( inIOHIDElementRef, IOHIDElementGetLogicalMax(inIOHIDElementRef) ); + + // this is the granularity of the values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); + // for example if set to 0.1 the values returned will be multiples of 0.1 ( 0.1, 0.2, 0.3, etc. ) + IOHIDElement_SetCalibrationGranularity(inIOHIDElementRef, 0.); + + // these define the dead zone (like in the middel of joystick axis) + IOHIDElement_SetCalibrationDeadZoneMin(inIOHIDElementRef, 0); + IOHIDElement_SetCalibrationDeadZoneMax(inIOHIDElementRef, 0); +#if 1 + // get the current value of this element + double value = IOHIDElement_GetValue(inIOHIDElementRef, kIOHIDValueScaleTypePhysical); + // use it as our min/mas saturation + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, value); + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, value); +#else + // calculate the middle physical value we would expect from this element + CFIndex valueMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); + CFIndex valueMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); + CFIndex valueMid = (valueMin + valueMax) / 2; + + // use it as our min/mas saturation + // this value determines the min/max values that have been recieved from the device element + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, valueMid); + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, valueMid); + + // get the current value of this element + double value = IOHIDElement_GetValue(inIOHIDElementRef, kIOHIDValueScaleTypePhysical); + // and use it to adjust the current saturation values if it's outside their range + if ( value < IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef) ) { + IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, value); + } + if ( value > IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef) ) { + IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, value); + } + +#endif +} // IOHIDElement_SetupCalibration +//***************************************************** +#pragma mark - local (static) function implementations +//----------------------------------------------------- + +//************************************************************************* +// +// IOHIDElement_GetLongProperty( inElementRef, inKey, outValue ) +// +// Purpose: convieance function to return a long property of an element +// +// Inputs: inElementRef - the element +// inKey - CFString for the key +// outValue - address where to store the value +// Returns: Boolean - TRUE if successful +// outValue - the long property's value +// + +Boolean IOHIDElement_GetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long *outValue) { + Boolean result = FALSE; + + CFTypeRef tCFTypeRef = IOHIDElementGetProperty(inElementRef, inKey); + if ( tCFTypeRef ) { + // if this is a number + if ( CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef) ) { + // get it's value + result = CFNumberGetValue( (CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, outValue ); + } + } + + return (result); +} /* IOHIDElement_GetLongProperty */ + +//************************************************************************* +// +// IOHIDElement_SetLongProperty( inElementRef, inKey, inValue ) +// +// Purpose: convieance function to set a long property of an element +// +// Inputs: inElementRef - the element +// inKey - CFString for the key +// inValue - the value to set it to +// +// Returns: nothing +// + +void IOHIDElement_SetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long inValue) { + CFNumberRef tCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &inValue); + if ( tCFNumberRef ) { + IOHIDElementSetProperty(inElementRef, inKey, tCFNumberRef); + CFRelease(tCFNumberRef); + } +} // IOHIDElement_SetLongProperty + +//***************************************************** + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/IOHIDElement_.h b/src/cocoa/IOHIDElement_.h new file mode 100644 index 000000000..a8a631668 --- /dev/null +++ b/src/cocoa/IOHIDElement_.h @@ -0,0 +1,339 @@ +// File: IOHIDElement_.h +// Abstract: convieance functions for IOHIDElementGetProperty +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDElement___ +#define __IOHIDElement___ + +//***************************************************** +#pragma mark - includes & imports + +#include + +#include "IOHIDLib_.h" +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //************************************************************************* + // + // HIDIsValidElement( inIOHIDElementRef ) + // + // Purpose: validate this element + // + // Inputs: inIOHIDElementRef - the element + // + // Returns: Boolean - TRUE if this is a valid element ref + // + extern Boolean HIDIsValidElement(IOHIDElementRef inIOHIDElementRef); + + //************************************************************************* + // + // IOHIDElement_GetValue( inElementRef, inIOHIDValueScaleType ) + // + // Purpose: returns the current value for an element( polling ) + // + // Notes: will return 0 on error conditions which should be accounted for by application + // + // Inputs: inElementRef - the element + // inIOHIDValueScaleType - scale type ( calibrated or physical ) + // + // Returns: double - current value for element + // + extern double IOHIDElement_GetValue(IOHIDElementRef inElementRef, IOHIDValueScaleType inIOHIDValueScaleType); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationMin( inElementRef ) + // + // Purpose: get the minimum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the minimum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationMin( inElementRef, inValue ) + // + // Purpose: set the minimum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the minimum bounds for a calibrated value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationMax( inElementRef ) + // + // Purpose: get the maximum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationMax( inElementRef, inValue ) + // + // Purpose: set the maximum bounds for a calibrated value for this element + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationSaturationMin( inElementRef ) + // + // Purpose: get the mininum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationSaturationMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationSaturationMin( inElementRef, inValue ) + // + // Purpose: set the mininum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationSaturationMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationSaturationMax( inElementRef ) + // + // Purpose: get the maximum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationSaturationMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationSaturationMax( inElementRef, inValue ) + // + // Purpose: set the maximum tolerance to be used when calibrating a logical element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationSaturationMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationDeadZoneMin( inElementRef ) + // + // Purpose: get the minimum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationDeadZoneMin(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationDeadZoneMin( inElementRef, inValue ) + // + // Purpose: set the minimum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationDeadZoneMin(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationDeadZoneMax( inElementRef ) + // + // Purpose: get the maximum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: CFIndex - the maximum Calibration value for this element + // + + extern CFIndex IOHIDElement_GetCalibrationDeadZoneMax(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationDeadZoneMax( inElementRef, inValue ) + // + // Purpose: set the maximum bounds near the midpoint of a logical value in which the value is ignored + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the maximum Calibration value for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationDeadZoneMax(IOHIDElementRef inElementRef, CFIndex inValue); + + //************************************************************************* + // + // IOHIDElement_GetCalibrationGranularity( inElementRef ) + // + // Purpose: get the level of detail returned for a calibrated element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: double_t - the maximum Calibration value for this element + // + + extern double_t IOHIDElement_GetCalibrationGranularity(IOHIDElementRef inElementRef); + + //************************************************************************* + // + // IOHIDElement_SetCalibrationGranularity( inElementRef, inValue ) + // + // Purpose: set the level of detail returned for a calibrated element value + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // inValue - the the level of detail for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetCalibrationGranularity(IOHIDElementRef inElementRef, double_t inValue); + + //************************************************************************* + // + // IOHIDElement_SetupCalibration( inElementRef ) + // + // Purpose: set default values for the element calibration parameters + // + // Inputs: inElementRef - the IOHIDElementRef for this element + // + // Returns: nothing + // + + extern void IOHIDElement_SetupCalibration(IOHIDElementRef inIOHIDElementRef); + + extern Boolean IOHIDElement_GetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long *outValue); + extern void IOHIDElement_SetLongProperty(IOHIDElementRef inElementRef, CFStringRef inKey, long inValue); + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDElement___ // diff --git a/src/cocoa/IOHIDLib_.h b/src/cocoa/IOHIDLib_.h new file mode 100644 index 000000000..38c6248ac --- /dev/null +++ b/src/cocoa/IOHIDLib_.h @@ -0,0 +1,111 @@ +// File: IOHIDLib_.h +// Abstract: Single include file for all header files of IOHIDLib +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#ifndef __IOHIDLib___ +#define __IOHIDLib___ + +//***************************************************** +#pragma mark - includes & imports +//----------------------------------------------------- +#include + +#include "IOHIDDevice_.h" +#include "IOHIDElement_.h" + +#include "ImmrHIDUtilAddOn.h" + +//***************************************************** +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + + //***************************************************** +#pragma mark - typedef's, struct's, enums, defines, etc. + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported globals + //----------------------------------------------------- + + //***************************************************** +#pragma mark - exported function prototypes + //----------------------------------------------------- + + //***************************************************** +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __IOHIDLib___ diff --git a/src/cocoa/ImmrHIDUtilAddOn.c b/src/cocoa/ImmrHIDUtilAddOn.c new file mode 100644 index 000000000..4937d3687 --- /dev/null +++ b/src/cocoa/ImmrHIDUtilAddOn.c @@ -0,0 +1,108 @@ +// File: ImmrHIDUtilAddOn.c +// Abstract: Glue code to convert IOHIDDeviceRef's to (FFB) io_object_t's +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#include +#include + +#include "ImmrHIDUtilAddOn.h" + +//--------------------------------------------------------------------------------- +// +// AllocateHIDObjectFromIOHIDDeviceRef( ) +// +// returns: +// NULL, or acceptable io_object_t +// +//--------------------------------------------------------------------------------- +io_service_t AllocateHIDObjectFromIOHIDDeviceRef(IOHIDDeviceRef inIOHIDDeviceRef) { + io_service_t result = 0L; + if ( inIOHIDDeviceRef ) { + // Set up the matching criteria for the devices we're interested in. + // We are interested in instances of class IOHIDDevice. + // matchingDict is consumed below( in IOServiceGetMatchingService ) + // so we have no leak here. + CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey); + if ( matchingDict ) { + // Add a key for locationID to our matching dictionary. This works for matching to + // IOHIDDevices, so we will only look for a device attached to that particular port + // on the machine. + CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty( inIOHIDDeviceRef, CFSTR(kIOHIDLocationIDKey) ); + if ( tCFTypeRef ) { + CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDLocationIDKey), tCFTypeRef); + // CFRelease( tCFTypeRef ); // don't release objects that we "Get". + + // IOServiceGetMatchingService assumes that we already know that there is only one device + // that matches. This way we don't have to do the whole iteration dance to look at each + // device that matches. This is a new API in 10.2 + result = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict); + } + + // Note: We're not leaking the matchingDict. + // One reference is consumed by IOServiceGetMatchingServices + } + } + + return (result); +} // AllocateHIDObjectFromIOHIDDeviceRef + +//--------------------------------------------------------------------------------- +// +// FreeHIDObject( ) +// +//--------------------------------------------------------------------------------- +bool FreeHIDObject(io_service_t inHIDObject) { + kern_return_t kr; + + kr = IOObjectRelease(inHIDObject); + + return (kIOReturnSuccess == kr); +} // FreeHIDObject + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 diff --git a/src/cocoa/ImmrHIDUtilAddOn.h b/src/cocoa/ImmrHIDUtilAddOn.h new file mode 100644 index 000000000..72de752e3 --- /dev/null +++ b/src/cocoa/ImmrHIDUtilAddOn.h @@ -0,0 +1,50 @@ +// File: ImmrHIDUtilAddOn.h +// Abstract: Glue code to convert IOHIDDeviceRef's to (FFB) io_object_t's +// Version: 2.0 +// +// Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple +// Inc. ("Apple") in consideration of your agreement to the following +// terms, and your use, installation, modification or redistribution of +// this Apple software constitutes acceptance of these terms. If you do +// not agree with these terms, please do not use, install, modify or +// redistribute this Apple software. +// +// In consideration of your agreement to abide by the following terms, and +// subject to these terms, Apple grants you a personal, non-exclusive +// license, under Apple's copyrights in this original Apple software (the +// "Apple Software"), to use, reproduce, modify and redistribute the Apple +// Software, with or without modifications, in source and/or binary forms; +// provided that if you redistribute the Apple Software in its entirety and +// without modifications, you must retain this notice and the following +// text and disclaimers in all such redistributions of the Apple Software. +// Neither the name, trademarks, service marks or logos of Apple Inc. may +// be used to endorse or promote products derived from the Apple Software +// without specific prior written permission from Apple. Except as +// expressly stated in this notice, no other rights or licenses, express or +// implied, are granted by Apple herein, including but not limited to any +// patent rights that may be infringed by your derivative works or by other +// works in which the Apple Software may be incorporated. +// +// The Apple Software is provided by Apple on an "AS IS" basis. APPLE +// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION +// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND +// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. +// +// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, +// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED +// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), +// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// Copyright (C) 2009 Apple Inc. All Rights Reserved. +// +//***************************************************** +#include +#include + +extern io_service_t AllocateHIDObjectFromIOHIDDeviceRef(IOHIDDeviceRef inIOHIDDeviceRef); +extern bool FreeHIDObject(io_object_t inHIDObject); diff --git a/src/cocoa/i_backend_cocoa.mm b/src/cocoa/i_backend_cocoa.mm new file mode 100644 index 000000000..9085d92db --- /dev/null +++ b/src/cocoa/i_backend_cocoa.mm @@ -0,0 +1,2179 @@ +/* + ** i_backend_cocoa.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "bitmap.h" +#include "c_console.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_event.h" +#include "d_gui.h" +#include "dikeys.h" +#include "doomdef.h" +#include "doomstat.h" +#include "s_sound.h" +#include "textures.h" +#include "v_video.h" +#include "version.h" +#include "i_rbopts.h" +#include "i_osversion.h" + +#undef Class + + +#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) + + +// --------------------------------------------------------------------------- + + +// The following definitions are required to build with older OS X SDKs + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 + +typedef unsigned int NSUInteger; +typedef int NSInteger; + +typedef float CGFloat; + +// From HIToolbox/Events.h +enum +{ + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +@interface NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint; +@end + +@implementation NSView(SupportOutdatedOSX) +- (NSPoint)convertPointFromBase:(NSPoint)aPoint +{ + return [self convertPoint:aPoint fromView:nil]; +} +@end + +#endif // prior to 10.5 + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 + +enum +{ + NSApplicationActivationPolicyRegular +}; + +typedef NSInteger NSApplicationActivationPolicy; + +@interface NSApplication(ActivationPolicy) +- (BOOL)setActivationPolicy:(NSApplicationActivationPolicy)activationPolicy; +@end + +@interface NSWindow(SetStyleMask) +- (void)setStyleMask:(NSUInteger)styleMask; +@end + +#endif // prior to 10.6 + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 + +@interface NSView(HiDPIStubs) +- (NSPoint)convertPointToBacking:(NSPoint)aPoint; +- (NSSize)convertSizeToBacking:(NSSize)aSize; +- (NSSize)convertSizeFromBacking:(NSSize)aSize; + +- (void)setWantsBestResolutionOpenGLSurface:(BOOL)flag; +@end + +@interface NSScreen(HiDPIStubs) +- (NSRect)convertRectToBacking:(NSRect)aRect; +@end + +#endif // prior to 10.7 + + +// --------------------------------------------------------------------------- + + +RenderBufferOptions rbOpts; + +EXTERN_CVAR(Bool, fullscreen) +EXTERN_CVAR(Bool, vid_hidpi) + +CVAR(Bool, use_mouse, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, m_filter, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) +{ + if (self < 0) + { + self = 0; + } + else if (self > 2) + { + self = 2; + } +} + +bool GUICapture; + + +extern int paused, chatmodeon; +extern constate_e ConsoleState; + +EXTERN_CVAR(Int, m_use_mouse); + +void I_ShutdownJoysticks(); + + +namespace +{ + +const int ARGC_MAX = 64; + +int s_argc; +char* s_argv[ARGC_MAX]; + +TArray s_argvStorage; + +bool s_restartedFromWADPicker; + + +bool s_nativeMouse = true; + +// TODO: remove this magic! +size_t s_skipMouseMoves; + +NSCursor* s_cursor; + + +void CheckGUICapture() +{ + const bool wantCapture = (MENU_Off == menuactive) + ? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon) + : (menuactive == MENU_On || menuactive == MENU_OnNoPause); + + if (wantCapture != GUICapture) + { + GUICapture = wantCapture; + + ResetButtonStates(); + } +} + +void CenterCursor() +{ + NSWindow* window = [NSApp keyWindow]; + if (nil == window) + { + return; + } + + const NSRect displayRect = [[window screen] frame]; + const NSRect windowRect = [window frame]; + const CGPoint centerPoint = CGPointMake(NSMidX(windowRect), displayRect.size.height - NSMidY(windowRect)); + + CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); + + if (NULL != eventSource) + { + CGEventRef mouseMoveEvent = CGEventCreateMouseEvent(eventSource, + kCGEventMouseMoved, centerPoint, kCGMouseButtonLeft); + + if (NULL != mouseMoveEvent) + { + CGEventPost(kCGHIDEventTap, mouseMoveEvent); + CFRelease(mouseMoveEvent); + } + + CFRelease(eventSource); + } + + // TODO: remove this magic! + s_skipMouseMoves = 2; +} + + +bool IsInGame() +{ + switch (mouse_capturemode) + { + default: + case 0: + return gamestate == GS_LEVEL; + + case 1: + return gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_FINALE; + + case 2: + return true; + } +} + +void SetNativeMouse(bool wantNative) +{ + if (wantNative != s_nativeMouse) + { + s_nativeMouse = wantNative; + + if (!wantNative) + { + CenterCursor(); + } + + CGAssociateMouseAndMouseCursorPosition(wantNative); + + if (wantNative) + { + [NSCursor unhide]; + } + else + { + [NSCursor hide]; + } + } +} + +void CheckNativeMouse() +{ + bool windowed = (NULL == screen) || !screen->IsFullscreen(); + bool wantNative; + + if (windowed) + { + if (![NSApp isActive] || !use_mouse) + { + wantNative = true; + } + else if (MENU_WaitKey == menuactive) + { + wantNative = false; + } + else + { + wantNative = (!m_use_mouse || MENU_WaitKey != menuactive) + && (!IsInGame() || GUICapture || paused || demoplayback); + } + } + else + { + // ungrab mouse when in the menu with mouse control on. + wantNative = m_use_mouse + && (MENU_On == menuactive || MENU_OnNoPause == menuactive); + } + + SetNativeMouse(wantNative); +} + +} // unnamed namespace + + +// see cocoa/i_joystick.cpp +void I_ProcessJoysticks(); + + +void I_GetEvent() +{ + [[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode]; +} + +void I_StartTic() +{ + CheckGUICapture(); + CheckNativeMouse(); + + I_ProcessJoysticks(); + I_GetEvent(); +} + +void I_StartFrame() +{ + +} + + +void I_SetMouseCapture() +{ + +} + +void I_ReleaseMouseCapture() +{ + +} + + +// --------------------------------------------------------------------------- + + +namespace +{ + +const size_t KEY_COUNT = 128; + + +// See Carbon -> HIToolbox -> Events.h for kVK_ constants + +const uint8_t KEYCODE_TO_DIK[KEY_COUNT] = +{ + DIK_A, DIK_S, DIK_D, DIK_F, DIK_H, DIK_G, DIK_Z, DIK_X, // 0x00 - 0x07 + DIK_C, DIK_V, 0, DIK_B, DIK_Q, DIK_W, DIK_E, DIK_R, // 0x08 - 0x0F + DIK_Y, DIK_T, DIK_1, DIK_2, DIK_3, DIK_4, DIK_6, DIK_5, // 0x10 - 0x17 + DIK_EQUALS, DIK_9, DIK_7, DIK_MINUS, DIK_8, DIK_0, DIK_RBRACKET, DIK_O, // 0x18 - 0x1F + DIK_U, DIK_LBRACKET, DIK_I, DIK_P, DIK_RETURN, DIK_L, DIK_J, DIK_APOSTROPHE, // 0x20 - 0x27 + DIK_K, DIK_SEMICOLON, DIK_BACKSLASH, DIK_COMMA, DIK_SLASH, DIK_N, DIK_M, DIK_PERIOD, // 0x28 - 0x2F + DIK_TAB, DIK_SPACE, DIK_GRAVE, DIK_BACK, 0, DIK_ESCAPE, 0, DIK_LWIN, // 0x30 - 0x37 + DIK_LSHIFT, DIK_CAPITAL, DIK_LMENU, DIK_LCONTROL, DIK_RSHIFT, DIK_RMENU, DIK_RCONTROL, 0, // 0x38 - 0x3F + 0, DIK_DECIMAL, 0, DIK_MULTIPLY, 0, DIK_ADD, 0, 0, // 0x40 - 0x47 + DIK_VOLUMEUP, DIK_VOLUMEDOWN, DIK_MUTE, DIK_SLASH, DIK_NUMPADENTER, 0, DIK_SUBTRACT, 0, // 0x48 - 0x4F + 0, DIK_NUMPAD_EQUALS, DIK_NUMPAD0, DIK_NUMPAD1, DIK_NUMPAD2, DIK_NUMPAD3, DIK_NUMPAD4, DIK_NUMPAD5, // 0x50 - 0x57 + DIK_NUMPAD6, DIK_NUMPAD7, 0, DIK_NUMPAD8, DIK_NUMPAD9, 0, 0, 0, // 0x58 - 0x5F + DIK_F5, DIK_F6, DIK_F7, DIK_F3, DIK_F8, DIK_F9, 0, DIK_F11, // 0x60 - 0x67 + 0, DIK_F13, 0, DIK_F14, 0, DIK_F10, 0, DIK_F12, // 0x68 - 0x6F + 0, DIK_F15, 0, DIK_HOME, 0, DIK_DELETE, DIK_F4, DIK_END, // 0x70 - 0x77 + DIK_F2, 0, DIK_F1, DIK_LEFT, DIK_RIGHT, DIK_DOWN, DIK_UP, 0, // 0x78 - 0x7F +}; + +const uint8_t KEYCODE_TO_ASCII[KEY_COUNT] = +{ + 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', // 0x00 - 0x07 + 'c', 'v', 0, 'b', 'q', 'w', 'e', 'r', // 0x08 - 0x0F + 'y', 't', '1', '2', '3', '4', '6', '5', // 0x10 - 0x17 + '=', '9', '7', '-', '8', '0', ']', 'o', // 0x18 - 0x1F + 'u', '[', 'i', 'p', 13, 'l', 'j', '\'', // 0x20 - 0x27 + 'k', ';', '\\', ',', '/', 'n', 'm', '.', // 0x28 - 0x2F + 9, ' ', '`', 12, 0, 27, 0, 0, // 0x30 - 0x37 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x47 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x48 - 0x4F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x57 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F + 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77 + 0, 0, 0, 0, 0, 0, 0, 0, // 0x78 - 0x7F +}; + + +uint8_t ModifierToDIK(const uint32_t modifier) +{ + switch (modifier) + { + case NSAlphaShiftKeyMask: return DIK_CAPITAL; + case NSShiftKeyMask: return DIK_LSHIFT; + case NSControlKeyMask: return DIK_LCONTROL; + case NSAlternateKeyMask: return DIK_LMENU; + case NSCommandKeyMask: return DIK_LWIN; + } + + return 0; +} + +SWORD ModifierFlagsToGUIKeyModifiers(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return ((modifiers & NSShiftKeyMask ) ? GKM_SHIFT : 0) + | ((modifiers & NSControlKeyMask ) ? GKM_CTRL : 0) + | ((modifiers & NSAlternateKeyMask) ? GKM_ALT : 0) + | ((modifiers & NSCommandKeyMask ) ? GKM_META : 0); +} + +bool ShouldGenerateGUICharEvent(NSEvent* theEvent) +{ + const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); + return !(modifiers & NSControlKeyMask) + && !(modifiers & NSAlternateKeyMask) + && !(modifiers & NSCommandKeyMask) + && !(modifiers & NSFunctionKeyMask); +} + +void ProcessKeyboardFlagsEvent(NSEvent* theEvent) +{ + static const uint32_t FLAGS_MASK = + NSDeviceIndependentModifierFlagsMask & ~NSNumericPadKeyMask; + + const uint32_t modifiers = [theEvent modifierFlags] & FLAGS_MASK; + static uint32_t oldModifiers = 0; + const uint32_t deltaModifiers = modifiers ^ oldModifiers; + + if (0 == deltaModifiers) + { + return; + } + + event_t event = {}; + + event.type = modifiers > oldModifiers ? EV_KeyDown : EV_KeyUp; + event.data1 = ModifierToDIK(deltaModifiers); + + oldModifiers = modifiers; + + // Caps Lock is a modifier key which generates one event per state change + // but not per actual key press or release. So treat any event as key down + // Also its event should be not be posted in menu and console + + if (DIK_CAPITAL == event.data1) + { + if (GUICapture) + { + return; + } + + event.type = EV_KeyDown; + } + + D_PostEvent(&event); +} + +NSStringEncoding GetEncodingForUnicodeCharacter(const unichar character) +{ + if (character >= L'\u0100' && character <= L'\u024F') + { + return NSWindowsCP1250StringEncoding; // Central and Eastern Europe + } + else if (character >= L'\u0370' && character <= L'\u03FF') + { + return NSWindowsCP1253StringEncoding; // Greek + } + else if (character >= L'\u0400' && character <= L'\u04FF') + { + return NSWindowsCP1251StringEncoding; // Cyrillic + } + + // TODO: add handling for other characters + // TODO: Turkish should use NSWindowsCP1254StringEncoding + + return NSWindowsCP1252StringEncoding; +} + +unsigned char GetCharacterFromNSEvent(NSEvent* theEvent) +{ + const NSString* unicodeCharacters = [theEvent characters]; + + if (0 == [unicodeCharacters length]) + { + return '\0'; + } + + const unichar unicodeCharacter = [unicodeCharacters characterAtIndex:0]; + const NSStringEncoding encoding = GetEncodingForUnicodeCharacter(unicodeCharacter); + + unsigned char character = '\0'; + + if (NSWindowsCP1252StringEncoding == encoding) + { + // TODO: make sure that the following is always correct + character = unicodeCharacter & 0xFF; + } + else + { + const NSData* const characters = + [[theEvent characters] dataUsingEncoding:encoding]; + + character = [characters length] > 0 + ? *static_cast([characters bytes]) + : '\0'; + } + + return character; +} + +void ProcessKeyboardEventInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + event.type = EV_GUI_Event; + event.subtype = NSKeyDown == [theEvent type] ? EV_GUI_KeyDown : EV_GUI_KeyUp; + event.data2 = GetCharacterFromNSEvent(theEvent); + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + + if (EV_GUI_KeyDown == event.subtype && [theEvent isARepeat]) + { + event.subtype = EV_GUI_KeyRepeat; + } + + const unsigned short keyCode = [theEvent keyCode]; + + switch (keyCode) + { + case kVK_Return: event.data1 = GK_RETURN; break; + case kVK_PageUp: event.data1 = GK_PGUP; break; + case kVK_PageDown: event.data1 = GK_PGDN; break; + case kVK_End: event.data1 = GK_END; break; + case kVK_Home: event.data1 = GK_HOME; break; + case kVK_LeftArrow: event.data1 = GK_LEFT; break; + case kVK_RightArrow: event.data1 = GK_RIGHT; break; + case kVK_UpArrow: event.data1 = GK_UP; break; + case kVK_DownArrow: event.data1 = GK_DOWN; break; + case kVK_Delete: event.data1 = GK_BACKSPACE; break; + case kVK_ForwardDelete: event.data1 = GK_DEL; break; + case kVK_Escape: event.data1 = GK_ESCAPE; break; + case kVK_F1: event.data1 = GK_F1; break; + case kVK_F2: event.data1 = GK_F2; break; + case kVK_F3: event.data1 = GK_F3; break; + case kVK_F4: event.data1 = GK_F4; break; + case kVK_F5: event.data1 = GK_F5; break; + case kVK_F6: event.data1 = GK_F6; break; + case kVK_F7: event.data1 = GK_F7; break; + case kVK_F8: event.data1 = GK_F8; break; + case kVK_F9: event.data1 = GK_F9; break; + case kVK_F10: event.data1 = GK_F10; break; + case kVK_F11: event.data1 = GK_F11; break; + case kVK_F12: event.data1 = GK_F12; break; + default: + event.data1 = KEYCODE_TO_ASCII[keyCode]; + break; + } + + if (event.data1 < 128) + { + event.data1 = toupper(event.data1); + + D_PostEvent(&event); + } + + if (!iscntrl(event.data2) + && EV_GUI_KeyUp != event.subtype + && ShouldGenerateGUICharEvent(theEvent)) + { + event.subtype = EV_GUI_Char; + event.data1 = event.data2; + event.data2 = event.data3 & GKM_ALT; + + D_PostEvent(&event); + } +} + +void ProcessKeyboardEvent(NSEvent* theEvent) +{ + const unsigned short keyCode = [theEvent keyCode]; + if (keyCode >= KEY_COUNT) + { + assert(!"Unknown keycode"); + return; + } + + if (GUICapture) + { + ProcessKeyboardEventInMenu(theEvent); + } + else + { + event_t event = {}; + + event.type = NSKeyDown == [theEvent type] ? EV_KeyDown : EV_KeyUp; + event.data1 = KEYCODE_TO_DIK[ keyCode ]; + + if (0 != event.data1) + { + event.data2 = KEYCODE_TO_ASCII[ keyCode ]; + + D_PostEvent(&event); + } + } +} + + +bool IsHiDPISupported() +{ + // The following value shoud be equal to NSAppKitVersionNumber10_7 + // and it's hard-coded in order to build on earlier SDKs + return NSAppKitVersionNumber >= 1138; +} + +NSSize GetRealContentViewSize(const NSWindow* const window) +{ + const NSView* view = [window contentView]; + const NSSize frameSize = [view frame].size; + + // TODO: figure out why [NSView frame] returns different values in "fullscreen" and in window + // In "fullscreen" the result is multiplied by [NSScreen backingScaleFactor], but not in window + + return (vid_hidpi && !fullscreen) + ? [view convertSizeToBacking:frameSize] + : frameSize; +} + + +void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent) +{ + const NSWindow* window = [inEvent window]; + const NSView* view = [window contentView]; + + const NSPoint screenPos = [NSEvent mouseLocation]; + const NSPoint windowPos = [window convertScreenToBase:screenPos]; + + const NSPoint viewPos = IsHiDPISupported() + ? [view convertPointToBacking:windowPos] + : [view convertPoint:windowPos fromView:nil]; + + const CGFloat frameHeight = GetRealContentViewSize(window).height; + + const CGFloat posX = ( viewPos.x - rbOpts.shiftX) / rbOpts.pixelScale; + const CGFloat posY = (frameHeight - viewPos.y - rbOpts.shiftY) / rbOpts.pixelScale; + + outEvent->data1 = static_cast< int >(posX); + outEvent->data2 = static_cast< int >(posY); +} + +void ProcessMouseButtonEvent(NSEvent* theEvent) +{ + event_t event = {}; + + const NSEventType cocoaEventType = [theEvent type]; + + if (GUICapture) + { + event.type = EV_GUI_Event; + + switch (cocoaEventType) + { + case NSLeftMouseDown: event.subtype = EV_GUI_LButtonDown; break; + case NSRightMouseDown: event.subtype = EV_GUI_RButtonDown; break; + case NSOtherMouseDown: event.subtype = EV_GUI_MButtonDown; break; + case NSLeftMouseUp: event.subtype = EV_GUI_LButtonUp; break; + case NSRightMouseUp: event.subtype = EV_GUI_RButtonUp; break; + case NSOtherMouseUp: event.subtype = EV_GUI_MButtonUp; break; + default: break; + } + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); + } + else + { + switch (cocoaEventType) + { + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + event.type = EV_KeyDown; + break; + + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + event.type = EV_KeyUp; + break; + + default: + break; + } + + event.data1 = std::min(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8)); + + D_PostEvent(&event); + } +} + + +void ProcessMouseMoveInMenu(NSEvent* theEvent) +{ + event_t event = {}; + + event.type = EV_GUI_Event; + event.subtype = EV_GUI_MouseMove; + + NSEventToGameMousePosition(theEvent, &event); + + D_PostEvent(&event); +} + +void ProcessMouseMoveInGame(NSEvent* theEvent) +{ + if (!use_mouse) + { + return; + } + + // TODO: remove this magic! + + if (s_skipMouseMoves > 0) + { + --s_skipMouseMoves; + return; + } + + int x([theEvent deltaX]); + int y(-[theEvent deltaY]); + + if (0 == x && 0 == y) + { + return; + } + + if (!m_noprescale) + { + x *= 3; + y *= 2; + } + + event_t event = {}; + + static int lastX = 0, lastY = 0; + + if (m_filter) + { + event.x = (x + lastX) / 2; + event.y = (y + lastY) / 2; + } + else + { + event.x = x; + event.y = y; + } + + lastX = x; + lastY = y; + + if (0 != event.x | 0 != event.y) + { + event.type = EV_Mouse; + + D_PostEvent(&event); + } +} + +void ProcessMouseMoveEvent(NSEvent* theEvent) +{ + if (GUICapture) + { + ProcessMouseMoveInMenu(theEvent); + } + else + { + ProcessMouseMoveInGame(theEvent); + } +} + + +void ProcessMouseWheelEvent(NSEvent* theEvent) +{ + const CGFloat delta = [theEvent deltaY]; + const bool isZeroDelta = fabs(delta) < 1.0E-5; + + if (isZeroDelta && GUICapture) + { + return; + } + + event_t event = {}; + + if (GUICapture) + { + event.type = EV_GUI_Event; + event.subtype = delta > 0.0f ? EV_GUI_WheelUp : EV_GUI_WheelDown; + event.data3 = delta; + event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent); + } + else + { + event.type = isZeroDelta ? EV_KeyUp : EV_KeyDown; + event.data1 = delta > 0.0f ? KEY_MWHEELUP : KEY_MWHEELDOWN; + } + + D_PostEvent(&event); +} + + +const Uint16 BYTES_PER_PIXEL = 4; + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +namespace +{ + const NSInteger LEVEL_FULLSCREEN = NSMainMenuWindowLevel + 1; + const NSInteger LEVEL_WINDOWED = NSNormalWindowLevel; + + const NSUInteger STYLE_MASK_FULLSCREEN = NSBorderlessWindowMask; + const NSUInteger STYLE_MASK_WINDOWED = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask; +} + + +// --------------------------------------------------------------------------- + + +@interface FullscreenWindow : NSWindow +{ + +} + +- (bool)canBecomeKeyWindow; + +- (void)setLevel:(NSInteger)level; +- (void)setStyleMask:(NSUInteger)styleMask; + +@end + + +// --------------------------------------------------------------------------- + + +@interface FullscreenView : NSOpenGLView +{ + +} + +- (void)resetCursorRects; + +@end + + +@implementation FullscreenView + +- (void)resetCursorRects +{ + [super resetCursorRects]; + [self addCursorRect: [self bounds] + cursor: s_cursor]; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface ApplicationController : NSResponder +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + +#endif +{ +@private + FullscreenWindow* m_window; + + uint8_t* m_softwareRenderingBuffer; + GLuint m_softwareRenderingTexture; + + int m_multisample; + + int m_width; + int m_height; + bool m_fullscreen; + bool m_hiDPI; + + bool m_openGLInitialized; +} + +- (id)init; +- (void)dealloc; + +- (void)keyDown:(NSEvent*)theEvent; +- (void)keyUp:(NSEvent*)theEvent; + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification; +- (void)applicationWillResignActive:(NSNotification*)aNotification; + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; + +- (void)applicationWillTerminate:(NSNotification*)aNotification; + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename; + +- (int)multisample; +- (void)setMultisample:(int)multisample; + +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI; +- (void)useHiDPI:(bool)hiDPI; + +- (void)setupSoftwareRenderingWithWidth:(int)width height:(int)height; +- (void*)softwareRenderingBuffer; + +- (void)processEvents:(NSTimer*)timer; + +- (void)invalidateCursorRects; + +- (void)setMainWindowVisible:(bool)visible; + +- (void)setWindowStyleMask:(NSUInteger)styleMask; + +@end + + +static ApplicationController* appCtrl; + + +// --------------------------------------------------------------------------- + + +@implementation FullscreenWindow + +static bool s_fullscreenNewAPI; + ++ (void)initialize +{ + // The following value shoud be equal to NSAppKitVersionNumber10_6 + // and it's hard-coded in order to build on earlier SDKs + s_fullscreenNewAPI = NSAppKitVersionNumber >= 1038; +} + +- (bool)canBecomeKeyWindow +{ + return true; +} + +- (void)setLevel:(NSInteger)level +{ + if (s_fullscreenNewAPI) + { + [super setLevel:level]; + } + else + { + // Old Carbon-based way to make fullscreen window above dock and menu + // It's supported on 64-bit, but on 10.6 and later the following is preferred: + // [NSWindow setLevel:NSMainMenuWindowLevel + 1] + + const SystemUIMode mode = LEVEL_FULLSCREEN == level + ? kUIModeAllHidden + : kUIModeNormal; + SetSystemUIMode(mode, 0); + } +} + +- (void)setStyleMask:(NSUInteger)styleMask +{ + if (s_fullscreenNewAPI) + { + [super setStyleMask:styleMask]; + } + else + { + [appCtrl setWindowStyleMask:styleMask]; + } +} + +@end + + +// --------------------------------------------------------------------------- + + +@implementation ApplicationController + +- (id)init +{ + self = [super init]; + + m_window = nil; + + m_softwareRenderingBuffer = NULL; + m_softwareRenderingTexture = 0; + + m_multisample = 0; + + m_width = -1; + m_height = -1; + m_fullscreen = false; + m_hiDPI = false; + + m_openGLInitialized = false; + + return self; +} + +- (void)dealloc +{ + delete[] m_softwareRenderingBuffer; + + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &m_softwareRenderingTexture); + + [m_window release]; + + [super dealloc]; +} + + +- (void)keyDown:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + +- (void)keyUp:(NSEvent*)theEvent +{ + // Empty but present to avoid playing of 'beep' alert sound + + ZD_UNUSED(theEvent); +} + + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(1); +} + +- (void)applicationWillResignActive:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + S_SetSoundPaused(0); +} + + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification +{ + // When starting from command line with real executable path, e.g. ZDoom.app/Contents/MacOS/ZDoom + // application remains deactivated for an unknown reason. + // The following call resolves this issue + [NSApp activateIgnoringOtherApps:YES]; + + // Setup timer for custom event loop + + NSTimer* timer = [NSTimer timerWithTimeInterval:0 + target:self + selector:@selector(processEvents:) + userInfo:nil + repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:timer + forMode:NSDefaultRunLoopMode]; + + exit(SDL_main(s_argc, s_argv)); +} + + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename +{ + ZD_UNUSED(theApplication); + + if (s_restartedFromWADPicker + || 0 == [filename length] + || s_argc + 2 >= ARGC_MAX) + { + return FALSE; + } + + // Some parameters from command line are passed to this function + // These parameters need to be skipped to avoid duplication + // Note: SDL has different approach to fix this issue, see the same method in SDLMain.m + + const char* const charFileName = [filename UTF8String]; + + for (int i = 0; i < s_argc; ++i) + { + if (0 == strcmp(s_argv[i], charFileName)) + { + return FALSE; + } + } + + s_argvStorage.Push("-file"); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + + s_argvStorage.Push([filename UTF8String]); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + + return TRUE; +} + + +- (void)applicationWillTerminate:(NSNotification*)aNotification +{ + ZD_UNUSED(aNotification); + + // Hide window as nothing will be rendered at this point + [m_window orderOut:nil]; + + I_ShutdownJoysticks(); +} + + +- (int)multisample +{ + return m_multisample; +} + +- (void)setMultisample:(int)multisample +{ + m_multisample = multisample; +} + + +- (FullscreenWindow*)createWindow:(NSUInteger)styleMask +{ + FullscreenWindow* window = [[FullscreenWindow alloc] initWithContentRect:NSMakeRect(0, 0, 640, 480) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + [window setOpaque:YES]; + [window makeFirstResponder:self]; + [window setAcceptsMouseMovedEvents:YES]; + + return window; +} + +- (void)initializeOpenGL +{ + if (m_openGLInitialized) + { + return; + } + + m_window = [self createWindow:STYLE_MASK_WINDOWED]; + + // Create OpenGL context and view + + NSOpenGLPixelFormatAttribute attributes[16]; + size_t i = 0; + + attributes[i++] = NSOpenGLPFADoubleBuffer; + attributes[i++] = NSOpenGLPFAColorSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(32); + attributes[i++] = NSOpenGLPFADepthSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(24); + attributes[i++] = NSOpenGLPFAStencilSize; + attributes[i++] = NSOpenGLPixelFormatAttribute(8); + + if (m_multisample) + { + attributes[i++] = NSOpenGLPFAMultisample; + attributes[i++] = NSOpenGLPFASampleBuffers; + attributes[i++] = NSOpenGLPixelFormatAttribute(1); + attributes[i++] = NSOpenGLPFASamples; + attributes[i++] = NSOpenGLPixelFormatAttribute(m_multisample); + } + + attributes[i] = NSOpenGLPixelFormatAttribute(0); + + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + + const NSRect contentRect = [m_window contentRectForFrameRect:[m_window frame]]; + NSOpenGLView* glView = [[FullscreenView alloc] initWithFrame:contentRect + pixelFormat:pixelFormat]; + [[glView openGLContext] makeCurrentContext]; + + [m_window setContentView:glView]; + + m_openGLInitialized = true; +} + +- (void)setFullscreenModeWidth:(int)width height:(int)height +{ + NSScreen* screen = [m_window screen]; + + const NSRect screenFrame = [screen frame]; + const NSRect displayRect = vid_hidpi + ? [screen convertRectToBacking:screenFrame] + : screenFrame; + + const float displayWidth = displayRect.size.width; + const float displayHeight = displayRect.size.height; + + const float pixelScaleFactorX = displayWidth / static_cast(width ); + const float pixelScaleFactorY = displayHeight / static_cast(height); + + rbOpts.pixelScale = std::min(pixelScaleFactorX, pixelScaleFactorY); + + rbOpts.width = width * rbOpts.pixelScale; + rbOpts.height = height * rbOpts.pixelScale; + + rbOpts.shiftX = (displayWidth - rbOpts.width ) / 2.0f; + rbOpts.shiftY = (displayHeight - rbOpts.height) / 2.0f; + + if (!m_fullscreen) + { + [m_window setLevel:LEVEL_FULLSCREEN]; + [m_window setStyleMask:STYLE_MASK_FULLSCREEN]; + [m_window setHidesOnDeactivate:YES]; + } + + [m_window setFrame:displayRect display:YES]; + [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; +} + +- (void)setWindowedModeWidth:(int)width height:(int)height +{ + rbOpts.pixelScale = 1.0f; + + rbOpts.width = static_cast(width ); + rbOpts.height = static_cast(height); + + rbOpts.shiftX = 0.0f; + rbOpts.shiftY = 0.0f; + + const NSSize windowPixelSize = NSMakeSize(width, height); + const NSSize windowSize = vid_hidpi + ? [[m_window contentView] convertSizeFromBacking:windowPixelSize] + : windowPixelSize; + + if (m_fullscreen) + { + [m_window setLevel:LEVEL_WINDOWED]; + [m_window setStyleMask:STYLE_MASK_WINDOWED]; + [m_window setHidesOnDeactivate:NO]; + } + + [m_window setContentSize:windowSize]; + [m_window center]; + + NSButton* closeButton = [m_window standardWindowButton:NSWindowCloseButton]; + [closeButton setAction:@selector(terminate:)]; + [closeButton setTarget:NSApp]; +} + +- (void)changeVideoResolution:(bool)fullscreen width:(int)width height:(int)height useHiDPI:(bool)hiDPI +{ + if (fullscreen == m_fullscreen + && width == m_width + && height == m_height + && hiDPI == m_hiDPI) + { + return; + } + + [self initializeOpenGL]; + + if (IsHiDPISupported()) + { + NSOpenGLView* const glView = [m_window contentView]; + [glView setWantsBestResolutionOpenGLSurface:hiDPI]; + } + + if (fullscreen) + { + [self setFullscreenModeWidth:width height:height]; + } + else + { + [self setWindowedModeWidth:width height:height]; + } + + rbOpts.dirty = true; + + const NSSize viewSize = GetRealContentViewSize(m_window); + + glViewport(0, 0, static_cast(viewSize.width), static_cast(viewSize.height)); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + CGLFlushDrawable(CGLGetCurrentContext()); + + static NSString* const TITLE_STRING = + [NSString stringWithFormat:@"%s %s", GAMESIG, GetVersionString()]; + [m_window setTitle:TITLE_STRING]; + + if (![m_window isKeyWindow]) + { + [m_window makeKeyAndOrderFront:nil]; + } + + m_fullscreen = fullscreen; + m_width = width; + m_height = height; + m_hiDPI = hiDPI; +} + +- (void)useHiDPI:(bool)hiDPI +{ + if (!m_openGLInitialized) + { + return; + } + + [self changeVideoResolution:m_fullscreen + width:m_width + height:m_height + useHiDPI:hiDPI]; +} + + +- (void)setupSoftwareRenderingWithWidth:(int)width height:(int)height +{ + if (0 == m_softwareRenderingTexture) + { + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + glGenTextures(1, &m_softwareRenderingTexture); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_softwareRenderingTexture); + glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE); + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + delete[] m_softwareRenderingBuffer; + m_softwareRenderingBuffer = new uint8_t[width * height * BYTES_PER_PIXEL]; + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, width, height, 0.0, -1.0, 1.0); +} + +- (void*)softwareRenderingBuffer +{ + return m_softwareRenderingBuffer; +} + + +- (void)processEvents:(NSTimer*)timer +{ + ZD_UNUSED(timer); + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + while (true) + { + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate dateWithTimeIntervalSinceNow:0] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (nil == event) + { + break; + } + + const NSEventType eventType = [event type]; + + switch (eventType) + { + case NSMouseMoved: + ProcessMouseMoveEvent(event); + break; + + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSOtherMouseDown: + case NSOtherMouseUp: + ProcessMouseButtonEvent(event); + break; + + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSOtherMouseDragged: + ProcessMouseButtonEvent(event); + ProcessMouseMoveEvent(event); + break; + + case NSScrollWheel: + ProcessMouseWheelEvent(event); + break; + + case NSKeyDown: + case NSKeyUp: + ProcessKeyboardEvent(event); + break; + + case NSFlagsChanged: + ProcessKeyboardFlagsEvent(event); + break; + + default: + break; + } + + [NSApp sendEvent:event]; + } + + [NSApp updateWindows]; + + [pool release]; +} + + +- (void)invalidateCursorRects +{ + [m_window invalidateCursorRectsForView:[m_window contentView]]; +} + + +- (void)setMainWindowVisible:(bool)visible +{ + if (visible) + { + [m_window orderFront:nil]; + } + else + { + [m_window orderOut:nil]; + } +} + + +- (void)setWindowStyleMask:(NSUInteger)styleMask +{ + // Before 10.6 it's impossible to change window's style mask + // To workaround this new window should be created with required style mask + // This method should not be called when building for Snow Leopard or newer + + FullscreenWindow* tempWindow = [self createWindow:styleMask]; + [tempWindow setContentView:[m_window contentView]]; + + [m_window close]; + m_window = tempWindow; +} + +@end + + +// --------------------------------------------------------------------------- + + +CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (IsHiDPISupported()) + { + [appCtrl useHiDPI:self]; + } + else if (0 != self) + { + self = 0; + } +} + + +// --------------------------------------------------------------------------- + + +void I_SetMainWindowVisible(bool visible) +{ + [appCtrl setMainWindowVisible:visible]; + + SetNativeMouse(!visible); +} + + +// --------------------------------------------------------------------------- + + +bool I_SetCursor(FTexture* cursorpic) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + if (NULL == cursorpic || FTexture::TEX_Null == cursorpic->UseType) + { + s_cursor = [NSCursor arrowCursor]; + } + else + { + // Create bitmap image representation + + const NSInteger imageWidth = cursorpic->GetWidth(); + const NSInteger imageHeight = cursorpic->GetHeight(); + const NSInteger imagePitch = imageWidth * 4; + + NSBitmapImageRep* bitmapImageRep = [NSBitmapImageRep alloc]; + [bitmapImageRep initWithBitmapDataPlanes:NULL + pixelsWide:imageWidth + pixelsHigh:imageHeight + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:imagePitch + bitsPerPixel:0]; + + // Load bitmap data to representation + + BYTE* buffer = [bitmapImageRep bitmapData]; + memset(buffer, 0, imagePitch * imageHeight); + + FBitmap bitmap(buffer, imagePitch, imageWidth, imageHeight); + cursorpic->CopyTrueColorPixels(&bitmap, 0, 0); + + // Swap red and blue components in each pixel + + for (size_t i = 0; i < size_t(imageWidth * imageHeight); ++i) + { + const size_t offset = i * 4; + + const BYTE temp = buffer[offset ]; + buffer[offset ] = buffer[offset + 2]; + buffer[offset + 2] = temp; + } + + // Create image from representation and set it as cursor + + NSData* imageData = [bitmapImageRep representationUsingType:NSPNGFileType + properties:nil]; + NSImage* cursorImage = [[NSImage alloc] initWithData:imageData]; + + s_cursor = [[NSCursor alloc] initWithImage:cursorImage + hotSpot:NSMakePoint(0.0f, 0.0f)]; + } + + [appCtrl invalidateCursorRects]; + + [pool release]; + + return true; +} + + +// --------------------------------------------------------------------------- + + +const char* I_GetBackEndName() +{ + return "Native Cocoa"; +} + + +FString OSX_FindApplicationSupport() +{ + NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]; + if(url == nil) + return FString(); + return [[url path] UTF8String]; +} + +// --------------------------------------------------------------------------- + + +extern "C" +{ + +struct SDL_mutex +{ + pthread_mutex_t mutex; +}; + + +SDL_mutex* SDL_CreateMutex() +{ + pthread_mutexattr_t attributes; + pthread_mutexattr_init(&attributes); + pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE); + + SDL_mutex* result = new SDL_mutex; + + if (0 != pthread_mutex_init(&result->mutex, &attributes)) + { + delete result; + result = NULL; + } + + pthread_mutexattr_destroy(&attributes); + + return result; +} + +int SDL_mutexP(SDL_mutex* mutex) +{ + return pthread_mutex_lock(&mutex->mutex); +} + +int SDL_mutexV(SDL_mutex* mutex) +{ + return pthread_mutex_unlock(&mutex->mutex); +} + +void SDL_DestroyMutex(SDL_mutex* mutex) +{ + pthread_mutex_destroy(&mutex->mutex); + delete mutex; +} + + +static timeval s_startTicks; + +uint32_t SDL_GetTicks() +{ + timeval now; + gettimeofday(&now, NULL); + + const uint32_t ticks = + (now.tv_sec - s_startTicks.tv_sec ) * 1000 + + (now.tv_usec - s_startTicks.tv_usec) / 1000; + + return ticks; +} + + +int SDL_Init(Uint32 flags) +{ + ZD_UNUSED(flags); + + return 0; +} + +void SDL_Quit() +{ + if (NULL != appCtrl) + { + [NSApp setDelegate:nil]; + [NSApp deactivate]; + + [appCtrl release]; + appCtrl = NULL; + } +} + + +char* SDL_GetError() +{ + static char empty[] = {0}; + return empty; +} + + +char* SDL_VideoDriverName(char* namebuf, int maxlen) +{ + return strncpy(namebuf, "Native OpenGL", maxlen); +} + +const SDL_VideoInfo* SDL_GetVideoInfo() +{ + // NOTE: Only required fields are assigned + + static SDL_PixelFormat pixelFormat; + memset(&pixelFormat, 0, sizeof(pixelFormat)); + + pixelFormat.BitsPerPixel = 32; + + static SDL_VideoInfo videoInfo; + memset(&videoInfo, 0, sizeof(videoInfo)); + + const NSRect displayRect = [[NSScreen mainScreen] frame]; + + videoInfo.current_w = displayRect.size.width; + videoInfo.current_h = displayRect.size.height; + videoInfo.vfmt = &pixelFormat; + + return &videoInfo; +} + +SDL_Rect** SDL_ListModes(SDL_PixelFormat* format, Uint32 flags) +{ + ZD_UNUSED(format); + ZD_UNUSED(flags); + + static std::vector resolutions; + + if (resolutions.empty()) + { +#define DEFINE_RESOLUTION(WIDTH, HEIGHT) \ + static SDL_Rect resolution_##WIDTH##_##HEIGHT = { 0, 0, WIDTH, HEIGHT }; \ + resolutions.push_back(&resolution_##WIDTH##_##HEIGHT); + + DEFINE_RESOLUTION( 640, 480); + DEFINE_RESOLUTION( 720, 480); + DEFINE_RESOLUTION( 800, 480); + DEFINE_RESOLUTION( 800, 600); + DEFINE_RESOLUTION(1024, 600); + DEFINE_RESOLUTION(1024, 640); + DEFINE_RESOLUTION(1024, 768); + DEFINE_RESOLUTION(1152, 720); + DEFINE_RESOLUTION(1152, 864); + DEFINE_RESOLUTION(1280, 720); + DEFINE_RESOLUTION(1280, 768); + DEFINE_RESOLUTION(1280, 800); + DEFINE_RESOLUTION(1280, 854); + DEFINE_RESOLUTION(1280, 960); + DEFINE_RESOLUTION(1280, 1024); + DEFINE_RESOLUTION(1366, 768); + DEFINE_RESOLUTION(1400, 1050); + DEFINE_RESOLUTION(1440, 900); + DEFINE_RESOLUTION(1440, 960); + DEFINE_RESOLUTION(1440, 1080); + DEFINE_RESOLUTION(1600, 900); + DEFINE_RESOLUTION(1600, 1200); + DEFINE_RESOLUTION(1680, 1050); + DEFINE_RESOLUTION(1920, 1080); + DEFINE_RESOLUTION(1920, 1200); + DEFINE_RESOLUTION(2048, 1080); + DEFINE_RESOLUTION(2048, 1536); + DEFINE_RESOLUTION(2560, 1080); + DEFINE_RESOLUTION(2560, 1440); + DEFINE_RESOLUTION(2560, 1600); + DEFINE_RESOLUTION(2560, 2048); + DEFINE_RESOLUTION(2880, 1800); + DEFINE_RESOLUTION(3200, 1800); + DEFINE_RESOLUTION(3440, 1440); + DEFINE_RESOLUTION(3840, 2160); + DEFINE_RESOLUTION(3840, 2400); + DEFINE_RESOLUTION(4096, 2160); + DEFINE_RESOLUTION(5120, 2880); + +#undef DEFINE_RESOLUTION + + resolutions.push_back(NULL); + } + + return &resolutions[0]; +} + +int SDL_ShowCursor(int) +{ + // Does nothing + return 0; +} + + +static SDL_PixelFormat* GetPixelFormat() +{ + static SDL_PixelFormat result; + + result.palette = NULL; + result.BitsPerPixel = BYTES_PER_PIXEL * 8; + result.BytesPerPixel = BYTES_PER_PIXEL; + result.Rloss = 0; + result.Gloss = 0; + result.Bloss = 0; + result.Aloss = 8; + result.Rshift = 8; + result.Gshift = 16; + result.Bshift = 24; + result.Ashift = 0; + result.Rmask = 0x000000FF; + result.Gmask = 0x0000FF00; + result.Bmask = 0x00FF0000; + result.Amask = 0xFF000000; + result.colorkey = 0; + result.alpha = 0xFF; + + return &result; +} + + +SDL_Surface* SDL_SetVideoMode(int width, int height, int, Uint32 flags) +{ + [appCtrl changeVideoResolution:(SDL_FULLSCREEN & flags) + width:width + height:height + useHiDPI:vid_hidpi]; + + static SDL_Surface result; + + if (!(SDL_OPENGL & flags)) + { + [appCtrl setupSoftwareRenderingWithWidth:width + height:height]; + } + + result.flags = flags; + result.format = GetPixelFormat(); + result.w = width; + result.h = height; + result.pitch = width * BYTES_PER_PIXEL; + result.pixels = [appCtrl softwareRenderingBuffer]; + result.refcount = 1; + + result.clip_rect.x = 0; + result.clip_rect.y = 0; + result.clip_rect.w = width; + result.clip_rect.h = height; + + return &result; +} + + +void SDL_WM_SetCaption(const char* title, const char* icon) +{ + ZD_UNUSED(title); + ZD_UNUSED(icon); + + // Window title is set in SDL_SetVideoMode() +} + +int SDL_WM_ToggleFullScreen(SDL_Surface* surface) +{ + if (surface->flags & SDL_FULLSCREEN) + { + surface->flags &= ~SDL_FULLSCREEN; + } + else + { + surface->flags |= SDL_FULLSCREEN; + } + + [appCtrl changeVideoResolution:(SDL_FULLSCREEN & surface->flags) + width:surface->w + height:surface->h + useHiDPI:vid_hidpi]; + + return 1; +} + + +void SDL_GL_SwapBuffers() +{ + [[NSOpenGLContext currentContext] flushBuffer]; +} + +int SDL_GL_SetAttribute(SDL_GLattr attr, int value) +{ + if (SDL_GL_MULTISAMPLESAMPLES == attr) + { + [appCtrl setMultisample:value]; + } + + // Not interested in other attributes + + return 0; +} + + +int SDL_LockSurface(SDL_Surface* surface) +{ + ZD_UNUSED(surface); + + return 0; +} + +void SDL_UnlockSurface(SDL_Surface* surface) +{ + ZD_UNUSED(surface); +} + +int SDL_BlitSurface(SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, SDL_Rect* dstrect) +{ + ZD_UNUSED(src); + ZD_UNUSED(srcrect); + ZD_UNUSED(dst); + ZD_UNUSED(dstrect); + + return 0; +} + + +int SDL_Flip(SDL_Surface* screen) +{ + assert(NULL != screen); + + if (rbOpts.dirty) + { + glViewport(rbOpts.shiftX, rbOpts.shiftY, rbOpts.width, rbOpts.height); + + // TODO: Figure out why the following glClear() call is needed + // to avoid drawing of garbage in fullscreen mode when + // in-game's aspect ratio is different from display one + glClear(GL_COLOR_BUFFER_BIT); + + rbOpts.dirty = false; + } + + const int width = screen->w; + const int height = screen->h; + +#ifdef __LITTLE_ENDIAN__ + static const GLenum format = GL_RGBA; +#else // __BIG_ENDIAN__ + static const GLenum format = GL_ABGR_EXT; +#endif // __LITTLE_ENDIAN__ + + glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, + width, height, 0, format, GL_UNSIGNED_BYTE, screen->pixels); + + glBegin(GL_QUADS); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glTexCoord2f(0.0f, 0.0f); + glVertex2f(0.0f, 0.0f); + glTexCoord2f(width, 0.0f); + glVertex2f(width, 0.0f); + glTexCoord2f(width, height); + glVertex2f(width, height); + glTexCoord2f(0.0f, height); + glVertex2f(0.0f, height); + glEnd(); + + glFlush(); + + SDL_GL_SwapBuffers(); + + return 0; +} + +int SDL_SetPalette(SDL_Surface* surface, int flags, SDL_Color* colors, int firstcolor, int ncolors) +{ + ZD_UNUSED(surface); + ZD_UNUSED(flags); + ZD_UNUSED(colors); + ZD_UNUSED(firstcolor); + ZD_UNUSED(ncolors); + + return 0; +} + +} // extern "C" + + +namespace +{ + +NSMenuItem* CreateApplicationMenu() +{ + NSMenu* menu = [NSMenu new]; + + [menu addItemWithTitle:[@"About " stringByAppendingString:@GAMENAME] + action:@selector(orderFrontStandardAboutPanel:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Hide " stringByAppendingString:@GAMENAME] + action:@selector(hide:) + keyEquivalent:@"h"]; + [[menu addItemWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] + setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; + [menu addItemWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[@"Quit " stringByAppendingString:@GAMENAME] + action:@selector(terminate:) + keyEquivalent:@"q"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + if ([NSApp respondsToSelector:@selector(setAppleMenu:)]) + { + [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; + } + + return menuItem; +} + +NSMenuItem* CreateEditMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Edit"]; + + [menu addItemWithTitle:@"Undo" + action:@selector(undo:) + keyEquivalent:@"z"]; + [menu addItemWithTitle:@"Redo" + action:@selector(redo:) + keyEquivalent:@"Z"]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Cut" + action:@selector(cut:) + keyEquivalent:@"x"]; + [menu addItemWithTitle:@"Copy" + action:@selector(copy:) + keyEquivalent:@"c"]; + [menu addItemWithTitle:@"Paste" + action:@selector(paste:) + keyEquivalent:@"v"]; + [menu addItemWithTitle:@"Delete" + action:@selector(delete:) + keyEquivalent:@""]; + [menu addItemWithTitle:@"Select All" + action:@selector(selectAll:) + keyEquivalent:@"a"]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +NSMenuItem* CreateWindowMenu() +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:menu]; + + [menu addItemWithTitle:@"Minimize" + action:@selector(performMiniaturize:) + keyEquivalent:@"m"]; + [menu addItemWithTitle:@"Zoom" + action:@selector(performZoom:) + keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Bring All to Front" + action:@selector(arrangeInFront:) + keyEquivalent:@""]; + + NSMenuItem* menuItem = [NSMenuItem new]; + [menuItem setSubmenu:menu]; + + return menuItem; +} + +void CreateMenu() +{ + NSMenu* menuBar = [NSMenu new]; + [menuBar addItem:CreateApplicationMenu()]; + [menuBar addItem:CreateEditMenu()]; + [menuBar addItem:CreateWindowMenu()]; + + [NSApp setMainMenu:menuBar]; +} + +DarwinVersion GetDarwinVersion() +{ + DarwinVersion result = {}; + + int mib[2] = { CTL_KERN, KERN_OSRELEASE }; + size_t size = 0; + + if (0 == sysctl(mib, 2, NULL, &size, NULL, 0)) + { + char* version = static_cast(alloca(size)); + + if (0 == sysctl(mib, 2, version, &size, NULL, 0)) + { + sscanf(version, "%hu.%hu.%hu", + &result.major, &result.minor, &result.bugfix); + } + } + + return result; +} + +} // unnamed namespace + + +const DarwinVersion darwinVersion = GetDarwinVersion(); + + +#ifdef main +#undef main +#endif // main + +int main(int argc, char** argv) +{ + gettimeofday(&s_startTicks, NULL); + + for (int i = 0; i <= argc; ++i) + { + const char* const argument = argv[i]; + + if (NULL == argument || '\0' == argument[0]) + { + continue; + } + + if (0 == strcmp(argument, "-wad_picker_restart")) + { + s_restartedFromWADPicker = true; + } + else + { + s_argvStorage.Push(argument); + s_argv[s_argc++] = s_argvStorage.Last().LockBuffer(); + } + } + + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + + // The following code isn't mandatory, + // but it enables to run the application without a bundle + if ([NSApp respondsToSelector:@selector(setActivationPolicy:)]) + { + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + } + + CreateMenu(); + + appCtrl = [ApplicationController new]; + [NSApp setDelegate:appCtrl]; + + [NSApp run]; + + [pool release]; + + return EXIT_SUCCESS; +} diff --git a/src/cocoa/i_joystick.cpp b/src/cocoa/i_joystick.cpp new file mode 100644 index 000000000..9940c6ca8 --- /dev/null +++ b/src/cocoa/i_joystick.cpp @@ -0,0 +1,830 @@ +/* + ** i_joystick.cpp + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include "m_joy.h" + +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 + +#include "HID_Utilities_External.h" + +#include "d_event.h" +#include "doomdef.h" +#include "templates.h" +#include "i_osversion.h" + + +namespace +{ + +FString ToFString( const CFStringRef string ) +{ + if ( NULL == string ) + { + return FString(); + } + + const CFIndex stringLength = CFStringGetLength( string ); + + if ( 0 == stringLength ) + { + return FString(); + } + + const size_t bufferSize = CFStringGetMaximumSizeForEncoding( stringLength, kCFStringEncodingUTF8 ) + 1; + + char buffer[ bufferSize ]; + memset( buffer, 0, bufferSize ); + + CFStringGetCString( string, buffer, bufferSize, kCFStringEncodingUTF8 ); + + return FString( buffer ); +} + + +class IOKitJoystick : public IJoystickConfig +{ +public: + explicit IOKitJoystick( IOHIDDeviceRef device ); + virtual ~IOKitJoystick(); + + virtual FString GetName(); + virtual float GetSensitivity(); + virtual void SetSensitivity( float scale ); + + virtual int GetNumAxes(); + virtual float GetAxisDeadZone( int axis ); + virtual EJoyAxis GetAxisMap( int axis ); + virtual const char* GetAxisName( int axis ); + virtual float GetAxisScale( int axis ); + + virtual void SetAxisDeadZone( int axis, float deadZone ); + virtual void SetAxisMap( int axis, EJoyAxis gameAxis ); + virtual void SetAxisScale( int axis, float scale ); + + virtual bool IsSensitivityDefault(); + virtual bool IsAxisDeadZoneDefault( int axis ); + virtual bool IsAxisMapDefault( int axis ); + virtual bool IsAxisScaleDefault( int axis ); + + virtual void SetDefaultConfig(); + virtual FString GetIdentifier(); + + void AddAxes( float axes[ NUM_JOYAXIS ] ) const; + + void Update(); + +private: + IOHIDDeviceRef m_device; + + float m_sensitivity; + + struct AxisInfo + { + char name[ 64 ]; + + float value; + + float deadZone; + float defaultDeadZone; + float sensitivity; + float defaultSensitivity; + + EJoyAxis gameAxis; + EJoyAxis defaultGameAxis; + + IOHIDElementRef element; + }; + + TArray< AxisInfo > m_axes; + + TArray< IOHIDElementRef > m_buttons; + TArray< IOHIDElementRef > m_POVs; + + + static const float DEFAULT_DEADZONE; + static const float DEFAULT_SENSITIVITY; + + + bool ProcessAxis ( const IOHIDValueRef value ); + bool ProcessButton( const IOHIDValueRef value ); + bool ProcessPOV ( const IOHIDValueRef value ); + +}; + + +const float IOKitJoystick::DEFAULT_DEADZONE = 0.25f; +const float IOKitJoystick::DEFAULT_SENSITIVITY = 1.0f; + + +IOKitJoystick::IOKitJoystick( IOHIDDeviceRef device ) +: m_device( device ) +, m_sensitivity( DEFAULT_SENSITIVITY ) +{ + assert(NULL != device); + assert(IOHIDDeviceGetTypeID() == CFGetTypeID(device)); + + CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone); + assert(NULL != elements); + assert(CFArrayGetTypeID() == CFGetTypeID(elements)); + + for (CFIndex i = 0, count = CFArrayGetCount(elements); i < count; ++i) + { + const IOHIDElementRef element = + static_cast(const_cast(CFArrayGetValueAtIndex(elements, i))); + assert(NULL != element); + assert(IOHIDElementGetTypeID() == CFGetTypeID(element)); + + const uint32_t usagePage = IOHIDElementGetUsagePage( element ); + + if ( kHIDPage_GenericDesktop == usagePage ) + { + const uint32_t usage = IOHIDElementGetUsage( element ); + + if ( kHIDUsage_GD_Slider == usage + || kHIDUsage_GD_X == usage || kHIDUsage_GD_Y == usage || kHIDUsage_GD_Z == usage + || kHIDUsage_GD_Rx == usage || kHIDUsage_GD_Ry == usage || kHIDUsage_GD_Rz == usage ) + { + AxisInfo axis; + memset( &axis, 0, sizeof( axis ) ); + + if ( const CFStringRef name = IOHIDElementGetName( element ) ) + { + CFStringGetCString( name, axis.name, sizeof( axis.name ) - 1, kCFStringEncodingUTF8 ); + } + else + { + snprintf( axis.name, sizeof( axis.name ), "Axis %i", m_axes.Size() + 1 ); + } + + axis.element = element; + + m_axes.Push( axis ); + + IOHIDElement_SetCalibrationMin( element, -1 ); + IOHIDElement_SetCalibrationMax( element, 1 ); + + HIDQueueElement( m_device, element ); + } + else if ( kHIDUsage_GD_Hatswitch == usage && m_POVs.Size() < 4 ) + { + m_POVs.Push( element ); + + HIDQueueElement( m_device, element ); + } + } + else if ( kHIDPage_Button == usagePage ) + { + m_buttons.Push( element ); + + HIDQueueElement( m_device, element ); + } + } + + CFRelease(elements); + + SetDefaultConfig(); +} + +IOKitJoystick::~IOKitJoystick() +{ + M_SaveJoystickConfig( this ); +} + + +FString IOKitJoystick::GetName() +{ + FString result; + + result += ToFString( IOHIDDevice_GetManufacturer( m_device ) ); + result += " "; + result += ToFString( IOHIDDevice_GetProduct( m_device ) ); + + return result; +} + + +float IOKitJoystick::GetSensitivity() +{ + return m_sensitivity; +} + +void IOKitJoystick::SetSensitivity( float scale ) +{ + m_sensitivity = scale; +} + + +int IOKitJoystick::GetNumAxes() +{ + return static_cast< int >( m_axes.Size() ); +} + +#define IS_AXIS_VALID ( static_cast< unsigned int >( axis ) < m_axes.Size() ) + +float IOKitJoystick::GetAxisDeadZone( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].deadZone : 0.0f; +} + +EJoyAxis IOKitJoystick::GetAxisMap( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].gameAxis : JOYAXIS_None; +} + +const char* IOKitJoystick::GetAxisName( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].name : "Invalid"; +} + +float IOKitJoystick::GetAxisScale( int axis ) +{ + return IS_AXIS_VALID ? m_axes[ axis ].sensitivity : 0.0f; +} + +void IOKitJoystick::SetAxisDeadZone( int axis, float deadZone ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].deadZone = clamp( deadZone, 0.0f, 1.0f ); + } +} + +void IOKitJoystick::SetAxisMap( int axis, EJoyAxis gameAxis ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].gameAxis = ( gameAxis > JOYAXIS_None && gameAxis < NUM_JOYAXIS ) + ? gameAxis + : JOYAXIS_None; + } +} + +void IOKitJoystick::SetAxisScale( int axis, float scale ) +{ + if ( IS_AXIS_VALID ) + { + m_axes[ axis ].sensitivity = scale; + } +} + + +bool IOKitJoystick::IsSensitivityDefault() +{ + return DEFAULT_SENSITIVITY == m_sensitivity; +} + +bool IOKitJoystick::IsAxisDeadZoneDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].deadZone == m_axes[ axis ].defaultDeadZone ) + : true; +} + +bool IOKitJoystick::IsAxisMapDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].gameAxis == m_axes[ axis ].defaultGameAxis ) + : true; +} + +bool IOKitJoystick::IsAxisScaleDefault( int axis ) +{ + return IS_AXIS_VALID + ? ( m_axes[ axis ].sensitivity == m_axes[ axis ].defaultSensitivity ) + : true; +} + +#undef IS_AXIS_VALID + +void IOKitJoystick::SetDefaultConfig() +{ + m_sensitivity = DEFAULT_SENSITIVITY; + + const size_t axisCount = m_axes.Size(); + + for ( size_t i = 0; i < axisCount; ++i ) + { + m_axes[i].deadZone = DEFAULT_DEADZONE; + m_axes[i].sensitivity = DEFAULT_SENSITIVITY; + m_axes[i].gameAxis = JOYAXIS_None; + } + + // Two axes? Horizontal is yaw and vertical is forward. + + if ( 2 == axisCount) + { + m_axes[0].gameAxis = JOYAXIS_Yaw; + m_axes[1].gameAxis = JOYAXIS_Forward; + } + + // Three axes? First two are movement, third is yaw. + + else if ( axisCount >= 3 ) + { + m_axes[0].gameAxis = JOYAXIS_Side; + m_axes[1].gameAxis = JOYAXIS_Forward; + m_axes[2].gameAxis = JOYAXIS_Yaw; + + // Four axes? First two are movement, last two are looking around. + + if ( axisCount >= 4 ) + { + m_axes[3].gameAxis = JOYAXIS_Pitch; +// ??? m_axes[3].sensitivity = 0.75f; + + // Five axes? Use the fifth one for moving up and down. + + if ( axisCount >= 5 ) + { + m_axes[4].gameAxis = JOYAXIS_Up; + } + } + } + + // If there is only one axis, then we make no assumptions about how + // the user might want to use it. + + // Preserve defaults for config saving. + + for ( size_t i = 0; i < axisCount; ++i ) + { + m_axes[i].defaultDeadZone = m_axes[i].deadZone; + m_axes[i].defaultSensitivity = m_axes[i].sensitivity; + m_axes[i].defaultGameAxis = m_axes[i].gameAxis; + } +} + + +FString IOKitJoystick::GetIdentifier() +{ + char identifier[ 32 ] = {0}; + + snprintf( identifier, sizeof( identifier ), "VID_%04x_PID_%04x", + IOHIDDevice_GetVendorID( m_device ), IOHIDDevice_GetProductID( m_device ) ); + + return FString( identifier ); +} + + +void IOKitJoystick::AddAxes( float axes[ NUM_JOYAXIS ] ) const +{ + for ( size_t i = 0, count = m_axes.Size(); i < count; ++i ) + { + const EJoyAxis axis = m_axes[i].gameAxis; + + if ( JOYAXIS_None == axis ) + { + continue; + } + + axes[ axis ] -= m_axes[i].value; + } +} + + +void IOKitJoystick::Update() +{ + IOHIDValueRef value = NULL; + + while ( HIDGetEvent( m_device, &value ) && NULL != value ) + { + ProcessAxis( value ) || ProcessButton( value ) || ProcessPOV( value ); + + CFRelease( value ); + } +} + + +bool IOKitJoystick::ProcessAxis( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_axes.Size(); i < count; ++i ) + { + if ( element != m_axes[i].element ) + { + continue; + } + + AxisInfo& axis = m_axes[i]; + + const double scaledValue = IOHIDValueGetScaledValue( value, kIOHIDValueScaleTypeCalibrated ); + const double filteredValue = Joy_RemoveDeadZone( scaledValue, axis.deadZone, NULL ); + + axis.value = static_cast< float >( filteredValue * m_sensitivity * axis.sensitivity ); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessButton( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_buttons.Size(); i < count; ++i ) + { + if ( element != m_buttons[i] ) + { + continue; + } + + const int newButton = IOHIDValueGetIntegerValue( value ) & 1; + const int oldButton = ~newButton; + + Joy_GenerateButtonEvents( oldButton, newButton, 1, + static_cast< int >( KEY_FIRSTJOYBUTTON + i ) ); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessPOV( const IOHIDValueRef value ) +{ + const IOHIDElementRef element = IOHIDValueGetElement( value ); + + if ( NULL == element ) + { + return false; + } + + for ( size_t i = 0, count = m_POVs.Size(); i < count; ++i ) + { + if ( element != m_POVs[i] ) + { + continue; + } + + const CFIndex direction = IOHIDValueGetIntegerValue( value ); + + // Default values is for Up/North + int oldButtons = 0; + int newButtons = 1; + int numButtons = 1; + int baseButton = KEY_JOYPOV1_UP; + + switch ( direction ) + { + case 0: // N + break; + + case 1: // NE + newButtons = 3; + numButtons = 2; + break; + + case 2: // E + baseButton = KEY_JOYPOV1_RIGHT; + break; + + case 3: // SE + newButtons = 3; + numButtons = 2; + baseButton = KEY_JOYPOV1_RIGHT; + break; + + case 4: // S + baseButton = KEY_JOYPOV1_DOWN; + break; + + case 5: // SW + newButtons = 3; + numButtons = 2; + baseButton = KEY_JOYPOV1_DOWN; + break; + + case 6: // W + baseButton = KEY_JOYPOV1_LEFT; + break; + + case 7: // NW + newButtons = 9; // UP and LEFT + numButtons = 4; + break; + + default: + // release all four directions + oldButtons = 15; + newButtons = 0; + numButtons = 4; + break; + } + + Joy_GenerateButtonEvents( oldButtons, newButtons, numButtons, + static_cast< int >( baseButton + i * 4 ) ); + } + + return false; +} + + +// --------------------------------------------------------------------------- + + +class IOKitJoystickManager +{ +public: + IOKitJoystickManager(); + ~IOKitJoystickManager(); + + void GetJoysticks( TArray< IJoystickConfig* >& joysticks ) const; + + void AddAxes( float axes[ NUM_JOYAXIS ] ) const; + + // Updates axes/buttons states + void Update(); + + // Rebuilds device list + void Rescan(); + +private: + TArray< IOKitJoystick* > m_joysticks; + + static void OnDeviceChanged( void* context, IOReturn result, void* sender, IOHIDDeviceRef device ); + + void ReleaseJoysticks(); + + void EnableCallbacks(); + void DisableCallbacks(); + +}; + + +IOKitJoystickManager::IOKitJoystickManager() +{ + Rescan(); +} + +IOKitJoystickManager::~IOKitJoystickManager() +{ + ReleaseJoysticks(); + DisableCallbacks(); + + HIDReleaseDeviceList(); +} + + +void IOKitJoystickManager::GetJoysticks( TArray< IJoystickConfig* >& joysticks ) const +{ + const size_t joystickCount = m_joysticks.Size(); + + joysticks.Resize( joystickCount ); + + for ( size_t i = 0; i < joystickCount; ++i ) + { + M_LoadJoystickConfig( m_joysticks[i] ); + + joysticks[i] = m_joysticks[i]; + } +} + +void IOKitJoystickManager::AddAxes( float axes[ NUM_JOYAXIS ] ) const +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + m_joysticks[i]->AddAxes( axes ); + } +} + + +void IOKitJoystickManager::Update() +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + m_joysticks[i]->Update(); + } +} + + +void IOKitJoystickManager::Rescan() +{ + ReleaseJoysticks(); + DisableCallbacks(); + + const int usageCount = 2; + + const UInt32 usagePages[ usageCount ] = + { + kHIDPage_GenericDesktop, + kHIDPage_GenericDesktop + }; + + const UInt32 usages[ usageCount ] = + { + kHIDUsage_GD_Joystick, + kHIDUsage_GD_GamePad + }; + + if ( HIDUpdateDeviceList( usagePages, usages, usageCount ) ) + { + IOHIDDeviceRef device = HIDGetFirstDevice(); + + while ( NULL != device ) + { + IOKitJoystick* joystick = new IOKitJoystick( device ); + m_joysticks.Push( joystick ); + + device = HIDGetNextDevice( device ); + } + } + else + { + Printf( "IOKitJoystickManager: Failed to build gamepad/joystick device list.\n" ); + } + + EnableCallbacks(); +} + + +void IOKitJoystickManager::OnDeviceChanged( void* context, IOReturn result, void* sender, IOHIDDeviceRef device ) +{ + event_t event; + + memset( &event, 0, sizeof( event ) ); + event.type = EV_DeviceChange; + + D_PostEvent( &event ); +} + + +void IOKitJoystickManager::ReleaseJoysticks() +{ + for ( size_t i = 0, count = m_joysticks.Size(); i < count; ++i ) + { + delete m_joysticks[i]; + } + + m_joysticks.Clear(); +} + + +void IOKitJoystickManager::EnableCallbacks() +{ + if ( NULL == gIOHIDManagerRef ) + { + return; + } + + IOHIDManagerRegisterDeviceMatchingCallback( gIOHIDManagerRef, OnDeviceChanged, this ); + IOHIDManagerRegisterDeviceRemovalCallback ( gIOHIDManagerRef, OnDeviceChanged, this ); + IOHIDManagerScheduleWithRunLoop( gIOHIDManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); +} + +void IOKitJoystickManager::DisableCallbacks() +{ + if ( NULL == gIOHIDManagerRef ) + { + return; + } + + IOHIDManagerUnscheduleFromRunLoop( gIOHIDManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); + IOHIDManagerRegisterDeviceMatchingCallback( gIOHIDManagerRef, NULL, NULL ); + IOHIDManagerRegisterDeviceRemovalCallback ( gIOHIDManagerRef, NULL, NULL ); +} + + +IOKitJoystickManager* s_joystickManager; + + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +void I_StartupJoysticks() +{ + // HID Manager API is available on 10.5 (Darwin 9.x) or newer + + if (darwinVersion.major >= 9) + { + s_joystickManager = new IOKitJoystickManager; + } +} + +void I_ShutdownJoysticks() +{ + delete s_joystickManager; +} + +void I_GetJoysticks( TArray< IJoystickConfig* >& sticks ) +{ + if ( NULL != s_joystickManager ) + { + s_joystickManager->GetJoysticks( sticks ); + } +} + +void I_GetAxes( float axes[ NUM_JOYAXIS ] ) +{ + for ( size_t i = 0; i < NUM_JOYAXIS; ++i ) + { + axes[i] = 0.0f; + } + + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->AddAxes( axes ); + } +} + +IJoystickConfig* I_UpdateDeviceList() +{ + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->Rescan(); + } + + return NULL; +} + + +// --------------------------------------------------------------------------- + + +void I_ProcessJoysticks() +{ + if ( use_joystick && NULL != s_joystickManager ) + { + s_joystickManager->Update(); + } +} + +#else // prior to 10.5 + +void I_StartupJoysticks() +{ +} + +void I_ShutdownJoysticks() +{ +} + +void I_GetJoysticks(TArray& sticks) +{ + sticks.Clear(); +} + +void I_GetAxes(float axes[NUM_JOYAXIS]) +{ + for (size_t i = 0; i < NUM_JOYAXIS; ++i) + { + axes[i] = 0.0f; + } +} + +IJoystickConfig *I_UpdateDeviceList() +{ + return NULL; +} + +void I_ProcessJoysticks() +{ +} + +#endif // 10.5 or higher diff --git a/src/cocoa/i_osversion.h b/src/cocoa/i_osversion.h new file mode 100755 index 000000000..5e6ac6d20 --- /dev/null +++ b/src/cocoa/i_osversion.h @@ -0,0 +1,43 @@ +/* + ** i_osversion.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#include + +struct DarwinVersion +{ + uint16_t major; + uint16_t minor; + uint16_t bugfix; +}; + +extern const DarwinVersion darwinVersion; diff --git a/src/cocoa/i_rbopts.h b/src/cocoa/i_rbopts.h new file mode 100644 index 000000000..40a9ff17a --- /dev/null +++ b/src/cocoa/i_rbopts.h @@ -0,0 +1,52 @@ +/* + ** i_rbopts.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2014 Alexey Lysiuk + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions + ** are met: + ** + ** 1. Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** 2. Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in the + ** documentation and/or other materials provided with the distribution. + ** 3. The name of the author may not be used to endorse or promote products + ** derived from this software without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + **--------------------------------------------------------------------------- + ** + */ + +#ifndef SRC_COCOA_I_RBOPTS_H_INCLUDED +#define SRC_COCOA_I_RBOPTS_H_INCLUDED + +struct RenderBufferOptions +{ + float pixelScale; + + float shiftX; + float shiftY; + + float width; + float height; + + bool dirty; +}; + +extern RenderBufferOptions rbOpts; + +#endif // SRC_COCOA_I_RBOPTS_H_INCLUDED diff --git a/src/cocoa/i_timer.cpp b/src/cocoa/i_timer.cpp new file mode 100644 index 000000000..0699a9967 --- /dev/null +++ b/src/cocoa/i_timer.cpp @@ -0,0 +1,190 @@ + +#include +#include +#include +#include + +#include + +#include "basictypes.h" +#include "basicinlines.h" +#include "doomdef.h" +#include "i_system.h" +#include "templates.h" + + +unsigned int I_MSTime() +{ + return SDL_GetTicks(); +} + +unsigned int I_FPSTime() +{ + return SDL_GetTicks(); +} + + +bool g_isTicFrozen; + + +namespace +{ + +timespec GetNextTickTime() +{ + static const long MILLISECONDS_IN_SECOND = 1000; + static const long MICROSECONDS_IN_SECOND = 1000 * MILLISECONDS_IN_SECOND; + static const long NANOSECONDS_IN_SECOND = 1000 * MICROSECONDS_IN_SECOND; + + static timespec ts = {}; + + if (__builtin_expect((0 == ts.tv_sec), 0)) + { + timeval tv; + gettimeofday(&tv, NULL); + + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = (tv.tv_usec + MICROSECONDS_IN_SECOND / TICRATE) * MILLISECONDS_IN_SECOND; + } + else + { + ts.tv_nsec += (MICROSECONDS_IN_SECOND / TICRATE) * MILLISECONDS_IN_SECOND; + } + + if (ts.tv_nsec >= NANOSECONDS_IN_SECOND) + { + ts.tv_sec++; + ts.tv_nsec -= NANOSECONDS_IN_SECOND; + } + + return ts; +} + + +pthread_cond_t s_timerEvent; +pthread_mutex_t s_timerMutex; +pthread_t s_timerThread; + +bool s_timerInitialized; +bool s_timerExitRequested; + +uint32_t s_ticStart; +uint32_t s_timerStart; + +int s_tics; + + +void* TimerThreadFunc(void*) +{ + assert(s_timerInitialized); + assert(!s_timerExitRequested); + + while (true) + { + if (s_timerExitRequested) + { + break; + } + + const timespec timeToNextTick = GetNextTickTime(); + + pthread_mutex_lock(&s_timerMutex); + pthread_cond_timedwait(&s_timerEvent, &s_timerMutex, &timeToNextTick); + + if (!g_isTicFrozen) + { + // The following GCC/Clang intrinsic can be used instead of OS X specific function: + // __sync_add_and_fetch(&s_tics, 1); + // Although it's not supported on all platform/compiler combination, + // e.g. GCC 4.0.1 with PowerPC target architecture + + OSAtomicIncrement32(&s_tics); + } + + s_timerStart = SDL_GetTicks(); + + pthread_cond_broadcast(&s_timerEvent); + pthread_mutex_unlock(&s_timerMutex); + } + + return NULL; +} + +int GetTimeThreaded(bool saveMS) +{ + if (saveMS) + { + s_ticStart = s_timerStart; + } + + return s_tics; +} + +int WaitForTicThreaded(int prevTic) +{ + assert(!g_isTicFrozen); + + while (s_tics <= prevTic) + { + pthread_mutex_lock(&s_timerMutex); + pthread_cond_wait(&s_timerEvent, &s_timerMutex); + pthread_mutex_unlock(&s_timerMutex); + } + + return s_tics; +} + +void FreezeTimeThreaded(bool frozen) +{ + g_isTicFrozen = frozen; +} + +} // unnamed namespace + + +fixed_t I_GetTimeFrac(uint32* ms) +{ + const uint32_t now = SDL_GetTicks(); + + if (NULL != ms) + { + *ms = s_ticStart + 1000 / TICRATE; + } + + return 0 == s_ticStart + ? FRACUNIT + : clamp( (now - s_ticStart) * FRACUNIT * TICRATE / 1000, 0, FRACUNIT); +} + + +void I_InitTimer () +{ + assert(!s_timerInitialized); + s_timerInitialized = true; + + pthread_cond_init (&s_timerEvent, NULL); + pthread_mutex_init(&s_timerMutex, NULL); + + pthread_create(&s_timerThread, NULL, TimerThreadFunc, NULL); + + I_GetTime = GetTimeThreaded; + I_WaitForTic = WaitForTicThreaded; + I_FreezeTime = FreezeTimeThreaded; +} + +void I_ShutdownTimer () +{ + if (!s_timerInitialized) + { + // This might happen if Cancel button was pressed + // in the IWAD selector window + return; + } + + s_timerExitRequested = true; + + pthread_join(s_timerThread, NULL); + + pthread_mutex_destroy(&s_timerMutex); + pthread_cond_destroy (&s_timerEvent); +} diff --git a/src/cocoa/zdoom-info.plist b/src/cocoa/zdoom-info.plist new file mode 100644 index 000000000..2a1911cdf --- /dev/null +++ b/src/cocoa/zdoom-info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIconFile + zdoom.icns + CFBundleIdentifier + org.zdoom.zdoom + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ZDoom + CFBundlePackageType + APPL + CFBundleShortVersionString + Version 2.8.0 + CFBundleSignature + ???? + LSApplicationCategoryType + public.app-category.action-games + LSMinimumSystemVersion + 10.4 + CFBundleDocumentTypes + + + CFBundleTypeName + Doom Resource File + CFBundleTypeRole + Viewer + CFBundleTypeExtensions + + wad + pk3 + zip + pk7 + 7z + + + + NSPrincipalClass + NSApplication + + diff --git a/src/cocoa/zdoom.icns b/src/cocoa/zdoom.icns new file mode 100644 index 000000000..decb4c5ab Binary files /dev/null and b/src/cocoa/zdoom.icns differ diff --git a/src/compatibility.cpp b/src/compatibility.cpp index 7339a0f19..8a4341b76 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -81,6 +81,8 @@ enum CP_SECTORFLOOROFFSET, CP_SETWALLYSCALE, CP_SETTHINGZ, + CP_SETTAG, + CP_SETTHINGFLAGS, }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -140,6 +142,7 @@ static FCompatOption Options[] = { "maskedmidtex", COMPATF_MASKEDMIDTEX, SLOT_COMPAT }, { "badangles", COMPATF2_BADANGLES, SLOT_COMPAT2 }, { "floormove", COMPATF2_FLOORMOVE, SLOT_COMPAT2 }, + { "soundcutoff", COMPATF2_SOUNDCUTOFF, SLOT_COMPAT2 }, { NULL, 0, 0 } }; @@ -307,6 +310,24 @@ void ParseCompatibility() sc.MustGetFloat(); CompatParams.Push(FLOAT2FIXED(sc.Float)); } + else if (sc.Compare("setsectortag")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SETTAG); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + } + else if (sc.Compare("setthingflags")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SETTHINGFLAGS); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + } else { sc.UnGet(); @@ -520,6 +541,24 @@ void SetCompatibilityParams() i += 3; break; } + case CP_SETTAG: + { + if ((unsigned)CompatParams[i + 1] < (unsigned)numsectors) + { + sectors[CompatParams[i + 1]].tag = CompatParams[i + 2]; + } + i += 3; + break; + } + case CP_SETTHINGFLAGS: + { + if ((unsigned)CompatParams[i + 1] < MapThingsConverted.Size()) + { + MapThingsConverted[CompatParams[i + 1]].flags = CompatParams[i + 2]; + } + i += 3; + break; + } } } } diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp index c0c1f13f0..c0b73d3c0 100644 --- a/src/ct_chat.cpp +++ b/src/ct_chat.cpp @@ -145,12 +145,20 @@ bool CT_Responder (event_t *ev) CT_BackSpace (); return true; } +#ifdef __APPLE__ + else if (ev->data1 == 'C' && (ev->data3 & GKM_META)) +#else // !__APPLE__ else if (ev->data1 == 'C' && (ev->data3 & GKM_CTRL)) +#endif // __APPLE__ { I_PutInClipboard ((char *)ChatQueue); return true; } +#ifdef __APPLE__ + else if (ev->data1 == 'V' && (ev->data3 & GKM_META)) +#else // !__APPLE__ else if (ev->data1 == 'V' && (ev->data3 & GKM_CTRL)) +#endif // __APPLE__ { CT_PasteChat(I_GetFromClipboard(false)); } @@ -273,7 +281,8 @@ void CT_Drawer (void) if (players[consoleplayer].camera != NULL && (Button_ShowScores.bDown || - players[consoleplayer].camera->health <= 0) && + players[consoleplayer].camera->health <= 0 || + SB_ForceActive) && // Don't draw during intermission, since it has its own scoreboard in wi_stuff.cpp. gamestate != GS_INTERMISSION) { diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index 7311f6a7f..2b710b5a0 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -2208,6 +2208,13 @@ static int PatchText (int oldSize) { strncpy (deh.PlayerSprite, newStr, 4); } + for (unsigned ii = 0; ii < OrgSprNames.Size(); ii++) + { + if (!stricmp(OrgSprNames[ii].c, oldStr)) + { + strcpy(OrgSprNames[ii].c, newStr); + } + } // If this sprite is used by a pickup, then the DehackedPickup sprite map // needs to be updated too. for (i = 0; (size_t)i < countof(DehSpriteMappings); ++i) @@ -2294,7 +2301,10 @@ static int PatchStrings (int dummy) ReplaceSpecialChars (holdstring.LockBuffer()); holdstring.UnlockBuffer(); - GStrings.SetString (Line1, holdstring); + // Account for a discrepancy between Boom's and ZDoom's name for the red skull key pickup message + const char *ll = Line1; + if (!stricmp(ll, "GOTREDSKULL")) ll = "GOTREDSKUL"; + GStrings.SetString (ll, holdstring); DPrintf ("%s set to:\n%s\n", Line1, holdstring.GetChars()); } diff --git a/src/d_event.h b/src/d_event.h index 5bd4b02e0..7a9b48eb8 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -61,6 +61,7 @@ typedef enum ga_loadlevel, ga_newgame, ga_newgame2, + ga_recordgame, ga_loadgame, ga_loadgamehidecon, ga_loadgameplaydemo, diff --git a/src/d_gui.h b/src/d_gui.h index b88041771..86c3761ed 100644 --- a/src/d_gui.h +++ b/src/d_gui.h @@ -70,7 +70,8 @@ enum GUIKeyModifiers GKM_SHIFT = 1, GKM_CTRL = 2, GKM_ALT = 4, - GKM_LBUTTON = 8 + GKM_META = 8, + GKM_LBUTTON = 16 }; // Special codes for some GUI keys, including a few real ASCII codes. diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 6b430fa03..c03ed7a2f 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -430,27 +430,11 @@ int FIWadManager::IdentifyVersion (TArray &wadfiles, const char *iwad, } } } -#ifdef _WIN32 - FString steam_path = I_GetSteamPath(); - if (steam_path.IsNotEmpty()) + TArray steam_path = I_GetSteamPath(); + for (i = 0; i < steam_path.Size(); ++i) { - static const char *const steam_dirs[] = - { - "doom 2/base", - "final doom/base", - "heretic shadow of the serpent riders/base", - "hexen/base", - "hexen deathkings of the dark citadel/base", - "ultimate doom/base", - "DOOM 3 BFG Edition/base/wads" - }; - steam_path += "/SteamApps/common/"; - for (i = 0; i < countof(steam_dirs); ++i) - { - CheckIWAD (steam_path + steam_dirs[i], &wads[0]); - } + CheckIWAD (steam_path[i], &wads[0]); } -#endif } if (iwadparm != NULL && !wads[0].Path.IsEmpty()) diff --git a/src/d_main.cpp b/src/d_main.cpp index edebba6d9..82f2f3ab3 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -466,7 +466,7 @@ CUSTOM_CVAR (Int, dmflags2, 0, CVAR_SERVERINFO) } // Come out of chasecam mode if we're not allowed to use chasecam. - if (!(dmflags2 & DF2_CHASECAM) && !G_SkillProperty (SKILLP_DisableCheats) && !sv_cheats) + if (!(dmflags2 & DF2_CHASECAM) && CheckCheatmode(false)) { // Take us out of chasecam mode only. if (p->cheats & CF_CHASECAM) @@ -620,6 +620,7 @@ CVAR (Flag, compat_polyobj, compatflags, COMPATF_POLYOBJ); CVAR (Flag, compat_maskedmidtex, compatflags, COMPATF_MASKEDMIDTEX); CVAR (Flag, compat_badangles, compatflags2, COMPATF2_BADANGLES); CVAR (Flag, compat_floormove, compatflags2, COMPATF2_FLOORMOVE); +CVAR (Flag, compat_soundcutoff, compatflags2, COMPATF2_SOUNDCUTOFF); //========================================================================== // @@ -756,9 +757,9 @@ void D_Display () } screen->SetBlendingRect(viewwindowx, viewwindowy, viewwindowx + viewwidth, viewwindowy + viewheight); - P_PredictPlayer(&players[consoleplayer]); + Renderer->RenderView(&players[consoleplayer]); - P_UnPredictPlayer(); + if ((hw2d = screen->Begin2D(viewactive))) { // Redraw everything every frame when using 2D accel @@ -832,15 +833,23 @@ void D_Display () } } // draw pause pic - if (paused && menuactive == MENU_Off) + if ((paused || pauseext) && menuactive == MENU_Off) { FTexture *tex; int x; + FString pstring = "By "; tex = TexMan(gameinfo.PauseSign); x = (SCREENWIDTH - tex->GetScaledWidth() * CleanXfac)/2 + tex->GetScaledLeftOffset() * CleanXfac; screen->DrawTexture (tex, x, 4, DTA_CleanNoMove, true, TAG_DONE); + if (paused && multiplayer) + { + pstring += players[paused - 1].userinfo.GetName(); + screen->DrawText(SmallFont, CR_RED, + (screen->GetWidth() - SmallFont->StringWidth(pstring)*CleanXfac) / 2, + (tex->GetScaledHeight() * CleanYfac) + 4, pstring, DTA_CleanNoMove, true, TAG_DONE); + } } // [RH] Draw icon, if any @@ -974,25 +983,6 @@ void D_DoomLoop () I_StartTic (); D_ProcessEvents (); G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]); - //Added by MC: For some of that bot stuff. The main bot function. - int i; - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].isbot && players[i].mo) - { - players[i].savedyaw = players[i].mo->angle; - players[i].savedpitch = players[i].mo->pitch; - } - } - bglobal.Main (maketic%BACKUPTICS); - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].isbot && players[i].mo) - { - players[i].mo->angle = players[i].savedyaw; - players[i].mo->pitch = players[i].savedpitch; - } - } if (advancedemo) D_DoAdvanceDemo (); C_Ticker (); @@ -1337,6 +1327,7 @@ CCMD (endgame) { gameaction = ga_fullconsole; demosequence = -1; + G_CheckDemoStatus(); } } @@ -2588,6 +2579,7 @@ void D_DoomMain (void) new (&gameinfo) gameinfo_t; // Reset gameinfo S_Shutdown(); // free all channels and delete playlist C_ClearAliases(); // CCMDs won't be reinitialized so these need to be deleted here + DestroyCVarsFlagged(CVAR_MOD); // Delete any cvar left by mods GC::FullGC(); // perform one final garbage collection before deleting the class data restart++; diff --git a/src/d_net.cpp b/src/d_net.cpp index 586040d4b..a7ec870e0 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -39,7 +39,6 @@ #include "cmdlib.h" #include "s_sound.h" #include "m_cheat.h" -#include "p_effect.h" #include "p_local.h" #include "c_dispatch.h" #include "sbar.h" @@ -110,13 +109,15 @@ unsigned int lastrecvtime[MAXPLAYERS]; // [RH] Used for pings unsigned int currrecvtime[MAXPLAYERS]; unsigned int lastglobalrecvtime; // Identify the last time a packet was recieved. bool hadlate; +int netdelay[MAXNETNODES][BACKUPTICS]; // Used for storing network delay times. +int lastaverage; int nodeforplayer[MAXPLAYERS]; int playerfornode[MAXNETNODES]; int maketic; int skiptics; -int ticdup; +int ticdup; void D_ProcessEvents (void); void G_BuildTiccmd (ticcmd_t *cmd); @@ -151,6 +152,32 @@ CUSTOM_CVAR (Bool, cl_capfps, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) } } +CVAR(Bool, net_ticbalance, false, CVAR_SERVERINFO | CVAR_NOSAVE) +CUSTOM_CVAR(Int, net_extratic, 0, CVAR_SERVERINFO | CVAR_NOSAVE) +{ + if (self < 0) + { + self = 0; + } + else if (self > 2) + { + self = 2; + } +} + +#ifdef _DEBUG +CVAR(Int, net_fakelatency, 0, 0); + +struct PacketStore +{ + int timer; + doomcom_t message; +}; + +static TArray InBuffer; +static TArray OutBuffer; +#endif + // [RH] Special "ticcmds" get stored in here static struct TicSpecial { @@ -347,6 +374,9 @@ int NetbufferSize () k += netbuffer[k] + 1; } + // Network delay byte + k++; + if (netbuffer[0] & NCMD_MULTI) { count = netbuffer[k]; @@ -487,7 +517,30 @@ void HSendPacket (int node, int len) doomcom.remotenode = node; doomcom.datalength = len; - I_NetCmd (); +#ifdef _DEBUG + if (net_fakelatency / 2 > 0) + { + PacketStore store; + store.message = doomcom; + store.timer = I_GetTime(false) + ((net_fakelatency / 2) / (1000 / TICRATE)); + OutBuffer.Push(store); + } + else + I_NetCmd(); + + for (unsigned int i = 0; i < OutBuffer.Size(); i++) + { + if (OutBuffer[i].timer <= I_GetTime(false)) + { + doomcom = OutBuffer[i].message; + I_NetCmd(); + OutBuffer.Delete(i); + i = -1; + } + } +#else + I_NetCmd(); +#endif } // @@ -509,12 +562,42 @@ bool HGetPacket (void) if (demoplayback) return false; - + doomcom.command = CMD_GET; I_NetCmd (); + +#ifdef _DEBUG + if (net_fakelatency / 2 > 0 && doomcom.remotenode != -1) + { + PacketStore store; + store.message = doomcom; + store.timer = I_GetTime(false) + ((net_fakelatency / 2) / (1000 / TICRATE)); + InBuffer.Push(store); + doomcom.remotenode = -1; + } if (doomcom.remotenode == -1) + { + bool gotmessage = false; + for (unsigned int i = 0; i < InBuffer.Size(); i++) + { + if (InBuffer[i].timer <= I_GetTime(false)) + { + doomcom = InBuffer[i].message; + InBuffer.Delete(i); + gotmessage = true; + break; + } + } + if (!gotmessage) + return false; + } +#else + if (doomcom.remotenode == -1) + { return false; + } +#endif if (debugfile) { @@ -570,6 +653,9 @@ bool HGetPacket (void) if (doomcom.datalength != NetbufferSize ()) { + Printf("Bad packet length %i (calculated %i)\n", + doomcom.datalength, NetbufferSize()); + if (debugfile) fprintf (debugfile,"---bad packet length %i (calculated %i)\n", doomcom.datalength, NetbufferSize()); @@ -583,6 +669,9 @@ void PlayerIsGone (int netnode, int netconsole) { int i; + if (!nodeingame[netnode]) + return; + for (i = netnode + 1; i < doomcom.numnodes; ++i) { if (nodeingame[i]) @@ -593,77 +682,37 @@ void PlayerIsGone (int netnode, int netconsole) doomcom.numnodes = netnode; } + if (playeringame[netconsole]) + { + players[netconsole].playerstate = PST_GONE; + } nodeingame[netnode] = false; - playeringame[netconsole] = false; nodejustleft[netnode] = false; - if (deathmatch) - { - Printf ("%s left the game with %d frags\n", - players[netconsole].userinfo.GetName(), - players[netconsole].fragcount); - } - else - { - Printf ("%s left the game\n", players[netconsole].userinfo.GetName()); - } - - // [RH] Revert each player to their own view if spying through the player who left - for (int ii = 0; ii < MAXPLAYERS; ++ii) - { - if (playeringame[ii] && players[ii].camera == players[netconsole].mo) - { - players[ii].camera = players[ii].mo; - if (ii == consoleplayer && StatusBar != NULL) - { - StatusBar->AttachToPlayer (&players[ii]); - } - } - } - - // [RH] Make the player disappear - FBehavior::StaticStopMyScripts (players[netconsole].mo); - if (players[netconsole].mo != NULL) - { - P_DisconnectEffect (players[netconsole].mo); - players[netconsole].mo->player = NULL; - players[netconsole].mo->Destroy (); - if (!(players[netconsole].mo->ObjectFlags & OF_EuthanizeMe)) - { // We just destroyed a morphed player, so now the original player - // has taken their place. Destroy that one too. - players[netconsole].mo->Destroy(); - } - players[netconsole].mo = NULL; - players[netconsole].camera = NULL; - } - // [RH] Let the scripts know the player left - FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, netconsole); if (netconsole == Net_Arbitrator) { - bglobal.RemoveAllBots (true); - Printf ("Removed all bots\n"); - // Pick a new network arbitrator for (int i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && !players[i].isbot) + if (i != netconsole && playeringame[i] && players[i].Bot == NULL) { Net_Arbitrator = i; players[i].settings_controller = true; - Printf ("%s is the new arbitrator\n", players[i].userinfo.GetName()); + Printf("%s is the new arbitrator\n", players[i].userinfo.GetName()); break; } } - if (debugfile && NetMode == NET_PacketServer) + } + + if (debugfile && NetMode == NET_PacketServer) + { + if (Net_Arbitrator == consoleplayer) { - if (Net_Arbitrator == consoleplayer) - { - fprintf (debugfile, "I am the new master!\n"); - } - else - { - fprintf (debugfile, "Node %d is the new master!\n", nodeforplayer[Net_Arbitrator]); - } + fprintf(debugfile, "I am the new master!\n"); + } + else + { + fprintf(debugfile, "Node %d is the new master!\n", nodeforplayer[Net_Arbitrator]); } } @@ -772,7 +821,6 @@ void GetPackets (void) } if (netbuffer[0] & NCMD_QUITTERS) - { numplayers = netbuffer[k++]; for (int i = 0; i < numplayers; ++i) @@ -782,6 +830,9 @@ void GetPackets (void) } } + // Pull current network delay from node + netdelay[netnode][(nettics[netnode]+1) % BACKUPTICS] = netbuffer[k++]; + playerbytes[0] = netconsole; if (netbuffer[0] & NCMD_MULTI) { @@ -847,64 +898,18 @@ void GetPackets (void) for (i = 0; i < numplayers; ++i) { - int node = !players[playerbytes[i]].isbot ? - nodeforplayer[playerbytes[i]] : netnode; + int node = nodeforplayer[playerbytes[i]]; SkipTicCmd (&start, nettics[node] - realstart); for (tics = nettics[node]; tics < realend; tics++) ReadTicCmd (&start, playerbytes[i], tics); - } - // Update the number of tics received from each node. This must - // be separate from the above loop in case the master is also - // sending bot movements. If it's not separate, then the bots - // will only move on the master, because the other players will - // read the master's tics and then think they already got all - // the tics for the bots and skip the bot tics included in the - // packet. - for (i = 0; i < numplayers; ++i) - { - if (!players[playerbytes[i]].isbot) - { - nettics[nodeforplayer[playerbytes[i]]] = realend; - } - } - } - } -} -void AdjustBots (int gameticdiv) -{ - // [RH] This loop adjusts the bots' rotations for ticcmds that have - // been already created but not yet executed. This way, the bot is still - // able to create ticcmds that accurately reflect the state it wants to - // be in even when gametic lags behind maketic. - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].isbot && players[i].mo) - { - players[i].savedyaw = players[i].mo->angle; - players[i].savedpitch = players[i].mo->pitch; - for (int j = gameticdiv; j < maketic/ticdup; j++) - { - players[i].mo->angle += (netcmds[i][j%BACKUPTICS].ucmd.yaw << 16) * ticdup; - players[i].mo->pitch -= (netcmds[i][j%BACKUPTICS].ucmd.pitch << 16) * ticdup; + nettics[nodeforplayer[playerbytes[i]]] = realend; } } } } -void UnadjustBots () -{ - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].isbot && players[i].mo) - { - players[i].mo->angle = players[i].savedyaw; - players[i].mo->pitch = players[i].savedpitch; - } - } -} - // // NetUpdate // Builds ticcmds for console player, @@ -934,7 +939,7 @@ void NetUpdate (void) newtics = nowtime - gametime; gametime = nowtime; - if (newtics <= 0) // nothing new to update + if (newtics <= 0 || pauseext) // nothing new to update or window paused { GetPackets (); return; @@ -951,9 +956,7 @@ void NetUpdate (void) newtics = 0; } - // build new ticcmds for console player (and bots if I am the arbitrator) - AdjustBots (gametic / ticdup); - + // build new ticcmds for console player for (i = 0; i < newtics; i++) { I_StartTic (); @@ -963,11 +966,6 @@ void NetUpdate (void) //Printf ("mk:%i ",maketic); G_BuildTiccmd (&localcmds[maketic % LOCALCMDTICS]); - if (maketic % ticdup == 0) - { - //Added by MC: For some of that bot stuff. The main bot function. - bglobal.Main ((maketic / ticdup) % BACKUPTICS); - } maketic++; if (ticdup == 1 || maketic == 0) @@ -1047,8 +1045,6 @@ void NetUpdate (void) } } - UnadjustBots (); - if (singletics) return; // singletic update is synchronous @@ -1068,14 +1064,11 @@ void NetUpdate (void) if (consoleplayer == Net_Arbitrator) { - for (j = 0; j < MAXPLAYERS; j++) + for (j = 0; j < doomcom.numnodes; j++) { - if (playeringame[j]) + if (nodeingame[j] && NetMode == NET_PacketServer) { - if (players[j].isbot || NetMode == NET_PacketServer) - { - count++; - } + count++; } } @@ -1150,11 +1143,18 @@ void NetUpdate (void) netbuffer[k++] = lowtic; } - numtics = lowtic - realstart; + numtics = MAX(0, lowtic - realstart); if (numtics > BACKUPTICS) I_Error ("NetUpdate: Node %d missed too many tics", i); - resendto[i] = MAX (0, lowtic - doomcom.extratics); + switch (net_extratic) + { + case 0: + default: + resendto[i] = lowtic; break; + case 1: resendto[i] = MAX(0, lowtic - 1); break; + case 2: resendto[i] = nettics[i]; break; + } if (numtics == 0 && resendOnly && !remoteresend[i] && nettics[i]) { @@ -1190,6 +1190,10 @@ void NetUpdate (void) } } + // Send current network delay + // The number of tics we just made should be removed from the count. + netbuffer[k++] = ((maketic - newtics - gametic) / ticdup); + if (numtics > 0) { int l; @@ -1199,15 +1203,12 @@ void NetUpdate (void) netbuffer[0] |= NCMD_MULTI; netbuffer[k++] = count; - for (l = 1, j = 0; j < MAXPLAYERS; j++) + for (l = 1, j = 0; j < doomcom.numnodes; j++) { - if (playeringame[j] && j != playerfornode[i] && j != consoleplayer) + if (nodeingame[j] && j != i && j != nodeforplayer[consoleplayer] && NetMode == NET_PacketServer) { - if (players[j].isbot || NetMode == NET_PacketServer) - { - playerbytes[l++] = j; - netbuffer[k++] = j; - } + playerbytes[l++] = playerfornode[j]; + netbuffer[k++] = playerfornode[j]; } } } @@ -1227,7 +1228,7 @@ void NetUpdate (void) prev %= BACKUPTICS; // The local player has their tics sent first, followed by - // the other players/bots. + // the other players. if (l == 0) { WriteWord (localcmds[localstart].consistancy, &cmddata); @@ -1242,24 +1243,17 @@ void NetUpdate (void) } else if (i != 0) { - if (players[playerbytes[l]].isbot) - { + int len; + BYTE *spec; - WriteWord (0, &cmddata); // fake consistancy word - } - else + WriteWord (netcmds[playerbytes[l]][start].consistancy, &cmddata); + spec = NetSpecs[playerbytes[l]][start].GetData (&len); + if (spec != NULL) { - int len; - BYTE *spec; - - WriteWord (netcmds[playerbytes[l]][start].consistancy, &cmddata); - spec = NetSpecs[playerbytes[l]][start].GetData (&len); - if (spec != NULL) - { - memcpy (cmddata, spec, len); - cmddata += len; - } + memcpy (cmddata, spec, len); + cmddata += len; } + WriteUserCmdMessage (&netcmds[playerbytes[l]][start].ucmd, prev >= 0 ? &netcmds[playerbytes[l]][prev].ucmd : NULL, &cmddata); } @@ -1299,9 +1293,37 @@ void NetUpdate (void) // that it won't adapt. Fortunately, player prediction helps // alleviate the lag somewhat. - if (NetMode != NET_PacketServer) + if (NetMode == NET_PeerToPeer) { - mastertics = nettics[nodeforplayer[Net_Arbitrator]]; + int totalavg = 0; + if (net_ticbalance) + { + // Try to guess ahead the time it takes to send responses to the slowest node + int nodeavg = 0, arbavg = 0; + + for (j = 0; j < BACKUPTICS; j++) + { + arbavg += netdelay[nodeforplayer[Net_Arbitrator]][j]; + nodeavg += netdelay[0][j]; + } + arbavg /= BACKUPTICS; + nodeavg /= BACKUPTICS; + + // We shouldn't adapt if we are already the arbitrator isn't what we are waiting for, otherwise it just adds more latency + if (arbavg > nodeavg) + { + lastaverage = totalavg = ((arbavg + nodeavg) / 2); + } + else + { + // Allow room to guess two tics ahead + if (nodeavg > (arbavg + 2) && lastaverage > 0) + lastaverage--; + totalavg = lastaverage; + } + } + + mastertics = nettics[nodeforplayer[Net_Arbitrator]] + totalavg; } if (nettics[0] <= mastertics) { @@ -1346,9 +1368,8 @@ void NetUpdate (void) // // 0 One byte set to NCMD_SETUP+2 // 1 One byte for ticdup setting -// 2 One byte for extratics setting -// 3 One byte for NetMode setting -// 4 String with starting map's name +// 2 One byte for NetMode setting +// 3 String with starting map's name // . Four bytes for the RNG seed // . Stream containing remaining game info // @@ -1429,10 +1450,9 @@ bool DoArbitrate (void *userdata) data->gotsetup[0] = 0x80; ticdup = doomcom.ticdup = netbuffer[1]; - doomcom.extratics = netbuffer[2]; - NetMode = netbuffer[3]; + NetMode = netbuffer[2]; - stream = &netbuffer[4]; + stream = &netbuffer[3]; s = ReadString (&stream); startmap = s; delete[] s; @@ -1497,9 +1517,8 @@ bool DoArbitrate (void *userdata) { netbuffer[0] = NCMD_SETUP+2; netbuffer[1] = (BYTE)doomcom.ticdup; - netbuffer[2] = (BYTE)doomcom.extratics; - netbuffer[3] = NetMode; - stream = &netbuffer[4]; + netbuffer[2] = NetMode; + stream = &netbuffer[3]; WriteString (startmap, &stream); WriteLong (rngseed, &stream); C_WriteCVars (&stream, CVAR_SERVERINFO, true); @@ -1634,10 +1653,19 @@ void D_CheckNetGame (void) resendto[i] = 0; // which tic to start sending } + // Packet server has proven to be rather slow over the internet. Print a warning about it. + v = Args->CheckValue("-netmode"); + if (v != NULL && (atoi(v) != 0)) + { + Printf(TEXTCOLOR_YELLOW "Notice: Using PacketServer (netmode 1) over the internet is prone to running too slow on some internet configurations." + "\nIf the game is running well below expected speeds, use netmode 0 (P2P) instead.\n"); + } + // I_InitNetwork sets doomcom and netgame if (I_InitNetwork ()) { - NetMode = NET_PacketServer; + // For now, stop auto selecting PacketServer, as it's more likely to cause confusion. + //NetMode = NET_PacketServer; } if (doomcom.id != DOOMCOM_ID) { @@ -1647,15 +1675,23 @@ void D_CheckNetGame (void) consoleplayer = doomcom.consoleplayer; - v = Args->CheckValue ("-netmode"); - if (v != NULL) + if (consoleplayer == Net_Arbitrator) { - NetMode = atoi (v) != 0 ? NET_PacketServer : NET_PeerToPeer; - } - if (doomcom.numnodes > 1) - { - Printf ("Selected " TEXTCOLOR_BLUE "%s" TEXTCOLOR_NORMAL " networking mode. (%s)\n", NetMode == NET_PeerToPeer ? "peer to peer" : "packet server", - v != NULL ? "forced" : "auto"); + v = Args->CheckValue("-netmode"); + if (v != NULL) + { + NetMode = atoi(v) != 0 ? NET_PacketServer : NET_PeerToPeer; + } + if (doomcom.numnodes > 1) + { + Printf("Selected " TEXTCOLOR_BLUE "%s" TEXTCOLOR_NORMAL " networking mode. (%s)\n", NetMode == NET_PeerToPeer ? "peer to peer" : "packet server", + v != NULL ? "forced" : "auto"); + } + + if (Args->CheckParm("-extratic")) + { + net_extratic = 1; + } } // [RH] Setup user info @@ -1683,6 +1719,11 @@ void D_CheckNetGame (void) for (i = 0; i < doomcom.numnodes; i++) nodeingame[i] = true; + if (consoleplayer != Net_Arbitrator && doomcom.numnodes > 1) + { + Printf("Arbitrator selected " TEXTCOLOR_BLUE "%s" TEXTCOLOR_NORMAL " networking mode.\n", NetMode == NET_PeerToPeer ? "peer to peer" : "packet server"); + } + Printf ("player %i of %i (%i nodes)\n", consoleplayer+1, doomcom.numplayers, doomcom.numnodes); } @@ -1809,6 +1850,9 @@ void TryRunTics (void) { C_Ticker(); M_Ticker(); + // Repredict the player for new buffered movement + P_UnPredictPlayer(); + P_PredictPlayer(&players[consoleplayer]); } return; } @@ -1844,6 +1888,9 @@ void TryRunTics (void) { C_Ticker (); M_Ticker (); + // Repredict the player for new buffered movement + P_UnPredictPlayer(); + P_PredictPlayer(&players[consoleplayer]); return; } } @@ -1857,6 +1904,7 @@ void TryRunTics (void) // run the count tics if (counts > 0) { + P_UnPredictPlayer(); while (counts--) { if (gametic > lowtic) @@ -1876,6 +1924,7 @@ void TryRunTics (void) NetUpdate (); // check for new console commands } + P_PredictPlayer(&players[consoleplayer]); S_UpdateSounds (players[consoleplayer].camera); // move positional sounds } } @@ -2130,10 +2179,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) break; case DEM_ADDBOT: - { - BYTE num = ReadByte (stream); - bglobal.DoAddBot (num, s = ReadString (stream)); - } + bglobal.TryAddBot (stream, player); break; case DEM_KILLBOTS: @@ -2589,10 +2635,13 @@ void Net_SkipCommand (int type, BYTE **stream) switch (type) { case DEM_SAY: - case DEM_ADDBOT: skip = strlen ((char *)(*stream + 1)) + 2; break; + case DEM_ADDBOT: + skip = strlen ((char *)(*stream + 1)) + 6; + break; + case DEM_GIVECHEAT: case DEM_TAKECHEAT: skip = strlen ((char *)(*stream)) + 3; @@ -2713,7 +2762,6 @@ void Net_SkipCommand (int type, BYTE **stream) CCMD (pings) { int i; - for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) Printf ("% 4d %s\n", currrecvtime[i] - lastrecvtime[i], @@ -2755,7 +2803,7 @@ static void Network_Controller (int playernum, bool add) return; } - if (players[playernum].isbot) + if (players[playernum].Bot != NULL) { Printf ("Bots cannot be added to the controller list.\n"); return; diff --git a/src/d_net.h b/src/d_net.h index 4cd3e66f5..07921a301 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -70,7 +70,6 @@ struct doomcom_t // info common to all nodes SWORD numnodes; // console is always node 0. SWORD ticdup; // 1 = no duplication, 2-5 = dup for slow nets - SWORD extratics; // 1 = send a backup tic in every packet #ifdef DJGPP SWORD pad[5]; // keep things aligned for DOS drivers #endif @@ -143,6 +142,8 @@ extern struct ticcmd_t localcmds[LOCALCMDTICS]; extern int maketic; extern int nettics[MAXNETNODES]; +extern int netdelay[MAXNETNODES][BACKUPTICS]; +extern int nodeforplayer[MAXPLAYERS]; extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; extern int ticdup; diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index 730016c25..8bec1bcca 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -60,9 +60,6 @@ static FRandom pr_pickteam ("PickRandomTeam"); -extern bool st_firsttime; -EXTERN_CVAR (Bool, teamplay) - CVAR (Float, autoaim, 5000.f, CVAR_USERINFO | CVAR_ARCHIVE); CVAR (String, name, "Player", CVAR_USERINFO | CVAR_ARCHIVE); CVAR (Color, color, 0x40cf00, CVAR_USERINFO | CVAR_ARCHIVE); diff --git a/src/d_player.h b/src/d_player.h index fc62e5531..c66bb77de 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -96,8 +96,6 @@ public: }; FString GetPrintableDisplayName(PClassPlayerPawn *cls); -class player_t; - class APlayerPawn : public AActor { DECLARE_CLASS_WITH_META(APlayerPawn, AActor, PClassPlayerPawn) @@ -197,7 +195,8 @@ typedef enum PST_LIVE, // Playing or camping. PST_DEAD, // Dead on the ground, view follows killer. PST_REBORN, // Ready to restart/respawn??? - PST_ENTER // [BC] Entered the game + PST_ENTER, // [BC] Entered the game + PST_GONE // Player has left the game } playerstate_t; @@ -227,6 +226,8 @@ typedef enum CF_DOUBLEFIRINGSPEED= 1 << 21, // Player owns a double firing speed artifact CF_EXTREMELYDEAD = 1 << 22, // [RH] Reliably let the status bar know about extreme deaths. CF_INFINITEAMMO = 1 << 23, // Player owns an infinite ammo artifact + CF_BUDDHA2 = 1 << 24, // [MC] Absolute buddha. No voodoo can kill it either. + CF_GODMODE2 = 1 << 25, // [MC] Absolute godmode. No voodoo can kill it either. CF_BUDDHA = 1 << 27, // [SP] Buddha mode - take damage, but don't die CF_NOCLIP2 = 1 << 30, // [RH] More Quake-like noclip } cheat_t; @@ -419,8 +420,7 @@ public: int inventorytics; BYTE CurrentPlayerClass; // class # for this player instance - bool backpack; - + int frags[MAXPLAYERS]; // kills of other players int fragcount; // [RH] Cumulative frags for this player int lastkilltime; // [RH] For multikills @@ -465,47 +465,10 @@ public: FName LastDamageType; // [RH] For damage-specific pain and death sounds //Added by MC: - angle_t savedyaw; - int savedpitch; - - angle_t angle; // The wanted angle that the bot try to get every tic. - // (used to get a smoth view movement) - TObjPtr dest; // Move Destination. - TObjPtr prev; // Previous move destination. - - - TObjPtr enemy; // The dead meat. - TObjPtr missile; // A threatening missile that needs to be avoided. - TObjPtr mate; // Friend (used for grouping in teamplay or coop). - TObjPtr last_mate; // If bots mate disappeared (not if died) that mate is - // pointed to by this. Allows bot to roam to it if - // necessary. + TObjPtr Bot; bool settings_controller; // Player can control game settings. - //Skills - struct botskill_t skill; - - //Tickers - int t_active; // Open door, lower lift stuff, door must open and - // lift must go down before bot does anything - // radical like try a stuckmove - int t_respawn; - int t_strafe; - int t_react; - int t_fight; - int t_roam; - int t_rocket; - - //Misc booleans - bool isbot; - bool first_shot; // Used for reaction skill. - bool sleft; // If false, strafe is right. - bool allround; - - fixed_t oldx; - fixed_t oldy; - float BlendR; // [RH] Final blending values float BlendG; float BlendB; diff --git a/src/d_protocol.h b/src/d_protocol.h index 028ad1606..8b6e777d5 100644 --- a/src/d_protocol.h +++ b/src/d_protocol.h @@ -112,7 +112,7 @@ enum EDemoCommand DEM_DROPPLAYER, // 13 Not implemented, takes a byte DEM_CHANGEMAP, // 14 Name of map to change to DEM_SUICIDE, // 15 Player wants to die - DEM_ADDBOT, // 16 Byte: player#, String: userinfo for bot + DEM_ADDBOT, // 16 Byte: botshift, String: userinfo for bot, 4 Bytes: skill (aiming, perfection, reaction, isp) DEM_KILLBOTS, // 17 Remove all bots from the world DEM_INVUSEALL, // 18 Use every item (panic!) DEM_INVUSE, // 19 4 bytes: ID of item to use @@ -219,7 +219,9 @@ enum ECheatCommand CHT_GIMMIEJ, CHT_GIMMIEZ, CHT_BUDDHA, - CHT_NOCLIP2 + CHT_NOCLIP2, + CHT_BUDDHA2, + CHT_GOD2 }; void StartChunk (int id, BYTE **stream); diff --git a/src/doomdata.h b/src/doomdata.h index 215c2a263..f190be37d 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -162,6 +162,7 @@ enum ELineFlags ML_BLOCKUSE = 0x02000000, // blocks all use actions through this line ML_BLOCKSIGHT = 0x04000000, // blocks monster line of sight ML_BLOCKHITSCAN = 0x08000000, // blocks hitscan attacks + ML_3DMIDTEX_IMPASS = 0x10000000, // [TP] if 3D midtex, behaves like a height-restricted ML_BLOCKING }; diff --git a/src/doomdef.h b/src/doomdef.h index 3e00975cf..767f97661 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -339,6 +339,7 @@ enum COMPATF2_BADANGLES = 1 << 0, // It is impossible to face directly NSEW. COMPATF2_FLOORMOVE = 1 << 1, // Use the same floor motion behavior as Doom. + COMPATF2_SOUNDCUTOFF = 1 << 2, // Cut off sounds when an actor vanishes instead of making it owner-less }; // Emulate old bugs for select maps. These are not exposed by a cvar diff --git a/src/doomstat.h b/src/doomstat.h index 92ab5b8f8..565d15bd6 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -109,6 +109,7 @@ enum EMenuState extern bool automapactive; // In AutoMap mode? extern EMenuState menuactive; // Menu overlayed? extern int paused; // Game Pause? +extern bool pauseext; extern bool viewactive; @@ -136,6 +137,8 @@ extern int consoleplayer; // Disable save/end game? extern bool usergame; +extern FString newdemoname; +extern FString newdemomap; extern bool demoplayback; extern bool demorecording; extern int demover; diff --git a/src/g_doom/a_doomweaps.cpp b/src/g_doom/a_doomweaps.cpp index 108e40f96..b99ebf2a0 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -113,6 +113,9 @@ enum SAW_Flags SF_RANDOMLIGHTHIT = 4, SF_NOUSEAMMOMISS = 8, SF_NOUSEAMMO = 16, + SF_NOPULLIN = 32, + SF_NOTURN = 64, + SF_STEALARMOR = 128, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) @@ -127,6 +130,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) PARAM_ANGLE_OPT (spread_xy) { spread_xy = angle_t(2.8125 * (ANGLE_90 / 90.0)); } PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; } + PARAM_INT_OPT (lifestealmax) { lifestealmax = 0; } + PARAM_CLASS_OPT (armorbonustype, ABasicArmorBonus) { armorbonustype = NULL; } angle_t angle; angle_t slope; @@ -197,29 +202,58 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) if (lifesteal && !(linetarget->flags5 & MF5_DONTDRAIN)) { - P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS); + if (flags & SF_STEALARMOR) + { + if (armorbonustype == NULL) + { + armorbonustype = dyn_cast(PClass::FindClass("ArmorBonus")); + } + if (armorbonustype != NULL) + { + assert(armorbonustype->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))); + ABasicArmorBonus *armorbonus = static_cast(Spawn(armorbonustype, 0,0,0, NO_REPLACE)); + armorbonus->SaveAmount *= (actualdamage * lifesteal) >> FRACBITS; + armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; + armorbonus->flags |= MF_DROPPED; + armorbonus->ClearCounters(); + + if (!armorbonus->CallTryPickup (self)) + { + armorbonus->Destroy (); + } + } + } + + else + { + P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS, lifestealmax); + } } S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM); // turn to face target - angle = R_PointToAngle2 (self->x, self->y, - linetarget->x, linetarget->y); - if (angle - self->angle > ANG180) + if (!(flags & SF_NOTURN)) { - if (angle - self->angle < (angle_t)(-ANG90/20)) - self->angle = angle + ANG90/21; + angle = R_PointToAngle2(self->x, self->y, + linetarget->x, linetarget->y); + if (angle - self->angle > ANG180) + { + if (angle - self->angle < (angle_t)(-ANG90 / 20)) + self->angle = angle + ANG90 / 21; + else + self->angle -= ANG90 / 20; + } else - self->angle -= ANG90/20; + { + if (angle - self->angle > ANG90 / 20) + self->angle = angle - ANG90 / 21; + else + self->angle += ANG90 / 20; + } } - else - { - if (angle - self->angle > ANG90/20) - self->angle = angle - ANG90/21; - else - self->angle += ANG90/20; - } - self->flags |= MF_JUSTATTACKED; + if (!(flags & SF_NOPULLIN)) + self->flags |= MF_JUSTATTACKED; return 0; } @@ -593,6 +627,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FireBFG) return 0; } + // // A_BFGSpray // Spawn a BFG explosion on every monster in view @@ -603,6 +638,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) PARAM_CLASS_OPT (spraytype, AActor) { spraytype = NULL; } PARAM_INT_OPT (numrays) { numrays = 40; } PARAM_INT_OPT (damagecnt) { damagecnt = 15; } + PARAM_ANGLE_OPT (angle) { angle = ANGLE_90; } + PARAM_FIXED_OPT (distance) { distance = 16*64*FRACUNIT; } + PARAM_ANGLE_OPT (vrange) { vrange = 32*FRACUNIT; } + PARAM_INT_OPT (defdamage) { defdamage = 0; } int i; int j; @@ -611,10 +650,12 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) AActor *thingToHit; AActor *linetarget; - if (spraytype == NULL) spraytype = PClass::FindActor("BFGExtra"); if (numrays <= 0) numrays = 40; if (damagecnt <= 0) damagecnt = 15; + if (angle == 0) angle = ANG90; + if (distance <= 0) distance = 16 * 64 * FRACUNIT; + if (vrange == 0) vrange = ANGLE_1 * 32; // [RH] Don't crash if no target if (!self->target) @@ -623,10 +664,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) // offset angles from its attack angle for (i = 0; i < numrays; i++) { - an = self->angle - ANG90/2 + ANG90/numrays*i; + an = self->angle - angle/2 + angle/numrays*i; // self->target is the originator (player) of the missile - P_AimLineAttack (self->target, an, 16*64*FRACUNIT, &linetarget, ANGLE_1*32); + P_AimLineAttack (self->target, an, distance, &linetarget, vrange); if (!linetarget) continue; @@ -637,14 +678,24 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) if (spray && (spray->flags5 & MF5_PUFFGETSOWNER)) spray->target = self->target; - - damage = 0; - for (j = 0; j < damagecnt; ++j) - damage += (pr_bfgspray() & 7) + 1; + if (defdamage == 0) + { + damage = 0; + for (j = 0; j < damagecnt; ++j) + damage += (pr_bfgspray() & 7) + 1; + } + else + { + // if this is used, damagecnt will be ignored + damage = defdamage; + } + int dmgFlagPass = 0; + dmgFlagPass += (spray != NULL && (spray->flags3 & MF3_FOILINVUL)) ? DMG_FOILINVUL : 0; //[MC]Because the original foilinvul wasn't working. + dmgFlagPass += (spray != NULL && (spray->flags7 & MF7_FOILBUDDHA)) ? DMG_FOILBUDDHA : 0; thingToHit = linetarget; int newdam = P_DamageMobj (thingToHit, self->target, self->target, damage, spray != NULL? FName(spray->DamageType) : FName(NAME_BFGSplash), - spray != NULL && (spray->flags3 & MF3_FOILINVUL)? DMG_FOILINVUL : 0); + dmgFlagPass); P_TraceBleed (newdam > 0 ? newdam : damage, thingToHit, self->target); } return 0; diff --git a/src/g_game.cpp b/src/g_game.cpp index 405b42b99..d704d9873 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -76,6 +76,7 @@ #include "d_net.h" #include "d_event.h" #include "p_acs.h" +#include "p_effect.h" #include "m_joy.h" #include "farchive.h" #include "r_renderer.h" @@ -115,6 +116,7 @@ CVAR (Bool, chasedemo, false, 0); CVAR (Bool, storesavepic, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, longsavemessages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, save_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +CVAR (Bool, cl_waitforsave, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); EXTERN_CVAR (Float, con_midtime); //========================================================================== @@ -140,6 +142,7 @@ gameaction_t gameaction; gamestate_t gamestate = GS_STARTUP; int paused; +bool pauseext; bool sendpause; // send a pause event next tic bool sendsave; // send a save event next tic bool sendturn180; // [RH] send a 180 degree turn next tic @@ -161,6 +164,8 @@ int consoleplayer; // player taking events int gametic; CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); +FString newdemoname; +FString newdemomap; FString demoname; bool demorecording; bool demoplayback; @@ -877,7 +882,7 @@ static void ChangeSpy (int changespy) pnum &= MAXPLAYERS-1; if (playeringame[pnum] && (!checkTeam || players[pnum].mo->IsTeammate (players[consoleplayer].mo) || - (bot_allowspy && players[pnum].isbot))) + (bot_allowspy && players[pnum].Bot != NULL))) { break; } @@ -1016,10 +1021,16 @@ void G_Ticker () // do player reborns if needed for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && - (players[i].playerstate == PST_REBORN || players[i].playerstate == PST_ENTER)) + if (playeringame[i]) { - G_DoReborn (i, false); + if ((players[i].playerstate == PST_GONE)) + { + G_DoPlayerPop(i); + } + if ((players[i].playerstate == PST_REBORN || players[i].playerstate == PST_ENTER)) + { + G_DoReborn(i, false); + } } } @@ -1044,6 +1055,10 @@ void G_Ticker () case ga_loadlevel: G_DoLoadLevel (-1, false); break; + case ga_recordgame: + G_CheckDemoStatus(); + G_RecordDemo(newdemoname); + G_BeginRecording(newdemomap); case ga_newgame2: // Silence GCC (see above) case ga_newgame: G_DoNewGame (); @@ -1117,6 +1132,9 @@ void G_Ticker () // check, not just the player's x position like BOOM. DWORD rngsum = FRandom::StaticSumSeeds (); + //Added by MC: For some of that bot stuff. The main bot function. + bglobal.Main (); + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) @@ -1136,13 +1154,13 @@ void G_Ticker () // If the user alt-tabbed away, paused gets set to -1. In this case, // we do not want to read more demo commands until paused is no // longer negative. - if (demoplayback && paused >= 0) + if (demoplayback) { G_ReadDemoTiccmd (cmd, i); } else { - memcpy (cmd, newcmd, sizeof(ticcmd_t)); + memcpy(cmd, newcmd, sizeof(ticcmd_t)); } // check for turbo cheats @@ -1152,7 +1170,7 @@ void G_Ticker () Printf ("%s is turbo!\n", players[i].userinfo.GetName()); } - if (netgame && !players[i].isbot && !demoplayback && (gametic%ticdup) == 0) + if (netgame && players[i].Bot == NULL && !demoplayback && (gametic%ticdup) == 0) { //players[i].inconsistant = 0; if (gametic > BACKUPTICS*ticdup && consistancy[i][buf] != cmd->consistancy) @@ -1334,10 +1352,10 @@ void G_PlayerReborn (int player) int chasecam; BYTE currclass; userinfo_t userinfo; // [RH] Save userinfo - botskill_t b_skill; //Added by MC: APlayerPawn *actor; PClassPlayerPawn *cls; FString log; + DBot *Bot; //Added by MC: p = &players[player]; @@ -1347,18 +1365,19 @@ void G_PlayerReborn (int player) itemcount = p->itemcount; secretcount = p->secretcount; currclass = p->CurrentPlayerClass; - b_skill = p->skill; //Added by MC: userinfo.TransferFrom(p->userinfo); actor = p->mo; cls = p->cls; log = p->LogText; chasecam = p->cheats & CF_CHASECAM; + Bot = p->Bot; //Added by MC: // Reset player structure to its defaults p->~player_t(); ::new(p) player_t; memcpy (p->frags, frags, sizeof(p->frags)); + p->health = actor->health; p->fragcount = fragcount; p->killcount = killcount; p->itemcount = itemcount; @@ -1369,8 +1388,7 @@ void G_PlayerReborn (int player) p->cls = cls; p->LogText = log; p->cheats |= chasecam; - - p->skill = b_skill; //Added by MC: + p->Bot = Bot; //Added by MC: p->oldbuttons = ~0, p->attackdown = true; p->usedown = true; // don't do anything immediately p->original_oldbuttons = ~0; @@ -1378,15 +1396,19 @@ void G_PlayerReborn (int player) if (gamestate != GS_TITLELEVEL) { + // [GRB] Give inventory specified in DECORATE actor->GiveDefaultInventory (); p->ReadyWeapon = p->PendingWeapon; } - //Added by MC: Init bot structure. - if (bglobal.botingame[player]) - bglobal.CleanBotstuff (p); - else - p->isbot = false; + //Added by MC: Init bot structure. + if (p->Bot != NULL) + { + botskill_t skill = p->Bot->skill; + p->Bot->Clear (); + p->Bot->player = p; + p->Bot->skill = skill; + } } // @@ -1661,6 +1683,56 @@ void G_DoReborn (int playernum, bool freshbot) } } +// +// G_DoReborn +// +void G_DoPlayerPop(int playernum) +{ + playeringame[playernum] = false; + + if (deathmatch) + { + Printf("%s left the game with %d frags\n", + players[playernum].userinfo.GetName(), + players[playernum].fragcount); + } + else + { + Printf("%s left the game\n", players[playernum].userinfo.GetName()); + } + + // [RH] Revert each player to their own view if spying through the player who left + for (int ii = 0; ii < MAXPLAYERS; ++ii) + { + if (playeringame[ii] && players[ii].camera == players[playernum].mo) + { + players[ii].camera = players[ii].mo; + if (ii == consoleplayer && StatusBar != NULL) + { + StatusBar->AttachToPlayer(&players[ii]); + } + } + } + + // [RH] Make the player disappear + FBehavior::StaticStopMyScripts(players[playernum].mo); + if (players[playernum].mo != NULL) + { + P_DisconnectEffect(players[playernum].mo); + players[playernum].mo->player = NULL; + players[playernum].mo->Destroy(); + if (!(players[playernum].mo->ObjectFlags & OF_EuthanizeMe)) + { // We just destroyed a morphed player, so now the original player + // has taken their place. Destroy that one too. + players[playernum].mo->Destroy(); + } + players[playernum].mo = NULL; + players[playernum].camera = NULL; + } + // [RH] Let the scripts know the player left + FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, NULL, true, playernum); +} + void G_ScreenShot (char *filename) { shotfile = filename; @@ -2079,6 +2151,9 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio filename = G_BuildSaveName ("demosave.zds", -1); } + if (cl_waitforsave) + I_FreezeTime(true); + insave = true; G_SnapshotLevel (); @@ -2088,6 +2163,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio { Printf ("Could not create savegame '%s'\n", filename.GetChars()); insave = false; + I_FreezeTime(false); return; } @@ -2164,6 +2240,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio } insave = false; + I_FreezeTime(false); } @@ -2376,6 +2453,16 @@ void G_DeferedPlayDemo (const char *name) CCMD (playdemo) { + if (netgame) + { + Printf("End your current netgame first!"); + return; + } + if (demorecording) + { + Printf("End your current demo first!"); + return; + } if (argv.argc() > 1) { G_DeferedPlayDemo (argv[1]); @@ -2558,7 +2645,7 @@ void G_DoPlayDemo (void) { FixPathSeperator (defdemoname); DefaultExtension (defdemoname, ".lmp"); - M_ReadFile (defdemoname, &demobuffer); + M_ReadFileMalloc (defdemoname, &demobuffer); } demo_p = demobuffer; diff --git a/src/g_game.h b/src/g_game.h index 051be86b1..4714d8b55 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -81,6 +81,7 @@ enum EFinishLevelType void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags); void G_DoReborn (int playernum, bool freshbot); +void G_DoPlayerPop(int playernum); // Adds pitch to consoleplayer's viewpitch and clamps it void G_AddViewPitch (int look); diff --git a/src/g_heretic/a_ironlich.cpp b/src/g_heretic/a_ironlich.cpp index c5a74ac7b..c8ddb359f 100644 --- a/src/g_heretic/a_ironlich.cpp +++ b/src/g_heretic/a_ironlich.cpp @@ -28,10 +28,14 @@ int AWhirlwind::DoSpecialDamage (AActor *target, int damage, FName damagetype) { int randVal; - target->angle += pr_foo.Random2() << 20; - target->velx += pr_foo.Random2() << 10; - target->vely += pr_foo.Random2() << 10; - if ((level.time & 16) && !(target->flags2 & MF2_BOSS)) + if (!(target->flags7 & MF7_DONTTHRUST)) + { + target->angle += pr_foo.Random2() << 20; + target->velx += pr_foo.Random2() << 10; + target->vely += pr_foo.Random2() << 10; + } + + if ((level.time & 16) && !(target->flags2 & MF2_BOSS) && !(target->flags7 & MF7_DONTTHRUST)) { randVal = pr_foo(); if (randVal > 160) diff --git a/src/g_hexen/a_clericmace.cpp b/src/g_hexen/a_clericmace.cpp index 8b493f0ea..ecd28da4f 100644 --- a/src/g_hexen/a_clericmace.cpp +++ b/src/g_hexen/a_clericmace.cpp @@ -60,7 +60,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CMaceAttack) } } // didn't find any creatures, so try to strike any walls - player->mo->special1 = 0; + player->mo->weaponspecial = 0; angle = player->mo->angle; slope = P_AimLineAttack (player->mo, angle, MELEERANGE, &linetarget); diff --git a/src/g_hexen/a_clericstaff.cpp b/src/g_hexen/a_clericstaff.cpp index 96b6ababa..d65626d71 100644 --- a/src/g_hexen/a_clericstaff.cpp +++ b/src/g_hexen/a_clericstaff.cpp @@ -177,7 +177,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CStaffInitBlink) { PARAM_ACTION_PROLOGUE; - self->special1 = (pr_blink()>>1)+20; + self->weaponspecial = (pr_blink()>>1)+20; return 0; } @@ -193,10 +193,10 @@ DEFINE_ACTION_FUNCTION(AActor, A_CStaffCheckBlink) if (self->player && self->player->ReadyWeapon) { - if (!--self->special1) + if (!--self->weaponspecial) { P_SetPsprite (self->player, ps_weapon, self->player->ReadyWeapon->FindState ("Blink")); - self->special1 = (pr_blink()+50)>>2; + self->weaponspecial = (pr_blink()+50)>>2; } else { diff --git a/src/g_hexen/a_fighteraxe.cpp b/src/g_hexen/a_fighteraxe.cpp index 6a2c841a7..575b8d8c1 100644 --- a/src/g_hexen/a_fighteraxe.cpp +++ b/src/g_hexen/a_fighteraxe.cpp @@ -270,7 +270,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FAxeAttack) } } // didn't find any creatures, so try to strike any walls - pmo->special1 = 0; + pmo->weaponspecial = 0; angle = pmo->angle; slope = P_AimLineAttack (pmo, angle, MELEERANGE, &linetarget); diff --git a/src/g_hexen/a_fighterhammer.cpp b/src/g_hexen/a_fighterhammer.cpp index 4ced50dc1..2980dec7a 100644 --- a/src/g_hexen/a_fighterhammer.cpp +++ b/src/g_hexen/a_fighterhammer.cpp @@ -61,7 +61,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FHammerAttack) { P_ThrustMobj(linetarget, angle, power); } - pmo->special1 = false; // Don't throw a hammer + pmo->weaponspecial = false; // Don't throw a hammer goto hammerdone; } } @@ -77,7 +77,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FHammerAttack) { P_ThrustMobj(linetarget, angle, power); } - pmo->special1 = false; // Don't throw a hammer + pmo->weaponspecial = false; // Don't throw a hammer goto hammerdone; } } @@ -87,11 +87,11 @@ DEFINE_ACTION_FUNCTION(AActor, A_FHammerAttack) slope = P_AimLineAttack (pmo, angle, HAMMER_RANGE, &linetarget, 0, ALF_CHECK3D); if (P_LineAttack (pmo, angle, HAMMER_RANGE, slope, damage, NAME_Melee, hammertime, true) != NULL) { - pmo->special1 = false; + pmo->weaponspecial = false; } else { - pmo->special1 = true; + pmo->weaponspecial = true; } hammerdone: // Don't spawn a hammer if the player doesn't have enough mana @@ -99,7 +99,7 @@ hammerdone: !player->ReadyWeapon->CheckAmmo (player->ReadyWeapon->bAltFire ? AWeapon::AltFire : AWeapon::PrimaryFire, false, true)) { - pmo->special1 = false; + pmo->weaponspecial = false; } return 0; } @@ -122,7 +122,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FHammerThrow) return 0; } - if (!player->mo->special1) + if (!player->mo->weaponspecial) { return 0; } diff --git a/src/g_hexen/a_fighterplayer.cpp b/src/g_hexen/a_fighterplayer.cpp index 4fe9c0db6..f7f28acda 100644 --- a/src/g_hexen/a_fighterplayer.cpp +++ b/src/g_hexen/a_fighterplayer.cpp @@ -66,7 +66,7 @@ static bool TryPunch(APlayerPawn *pmo, angle_t angle, int damage, fixed_t power) slope = P_AimLineAttack (pmo, angle, 2*MELEERANGE, &linetarget); if (linetarget != NULL) { - if (++pmo->special1 >= 3) + if (++pmo->weaponspecial >= 3) { damage <<= 1; power *= 3; @@ -119,9 +119,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_FPunchAttack) if (TryPunch(pmo, pmo->angle + i*(ANG45/16), damage, power) || TryPunch(pmo, pmo->angle - i*(ANG45/16), damage, power)) { // hit something - if (pmo->special1 >= 3) + if (pmo->weaponspecial >= 3) { - pmo->special1 = 0; + pmo->weaponspecial = 0; P_SetPsprite (player, ps_weapon, player->ReadyWeapon->FindState("Fire2")); S_Sound (pmo, CHAN_VOICE, "*fistgrunt", 1, ATTN_NORM); } @@ -129,7 +129,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FPunchAttack) } } // didn't find any creatures, so try to strike any walls - pmo->special1 = 0; + pmo->weaponspecial = 0; AActor *linetarget; int slope = P_AimLineAttack (pmo, pmo->angle, MELEERANGE, &linetarget); diff --git a/src/g_level.cpp b/src/g_level.cpp index 69bee5de1..761ea4f48 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -199,6 +199,46 @@ CCMD (map) // //========================================================================== +CCMD(recordmap) +{ + if (netgame) + { + Printf("You cannot record a new game while in a netgame."); + return; + } + if (argv.argc() > 2) + { + try + { + if (!P_CheckMapData(argv[2])) + { + Printf("No map %s\n", argv[2]); + } + else + { + G_DeferedInitNew(argv[2]); + gameaction = ga_recordgame; + newdemoname = argv[1]; + newdemomap = argv[2]; + } + } + catch (CRecoverableError &error) + { + if (error.GetMessage()) + Printf("%s", error.GetMessage()); + } + } + else + { + Printf("Usage: recordmap \n"); + } +} + +//========================================================================== +// +// +//========================================================================== + CCMD (open) { if (netgame) @@ -236,6 +276,18 @@ void G_NewInit () { int i; + // Destory all old player refrences that may still exist + TThinkerIterator it(STAT_TRAVELLING); + APlayerPawn *pawn, *next; + + next = it.Next(); + while ((pawn = next) != NULL) + { + next = it.Next(); + pawn->flags |= MF_NOSECTOR | MF_NOBLOCKMAP; + pawn->Destroy(); + } + G_ClearSnapshots (); ST_SetNeedRefresh(); netgame = false; @@ -499,7 +551,9 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill } else if (strncmp(levelname, "enDSeQ", 6) != 0) { - nextinfo = FindLevelInfo (levelname, false); + FString reallevelname = levelname; + CheckWarpTransMap(reallevelname, true); + nextinfo = FindLevelInfo (reallevelname, false); if (nextinfo != NULL) { level_info_t *nextredir = nextinfo->CheckLevelRedirect(); @@ -507,8 +561,12 @@ void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill { nextinfo = nextredir; } + nextlevel = nextinfo->MapName; + } + else + { + nextlevel = levelname; } - nextlevel = nextinfo->MapName; } else { @@ -1106,6 +1164,8 @@ void G_StartTravel () } } } + + bglobal.StartTravel (); } //========================================================================== @@ -1169,6 +1229,7 @@ void G_FinishTravel () pawn->lastenemy = NULL; pawn->player->mo = pawn; pawn->player->camera = pawn; + pawn->flags2 &= ~MF2_BLASTED; DObject::StaticPointerSubstitution (oldpawn, pawn); oldpawn->Destroy(); pawndup->Destroy (); @@ -1176,6 +1237,15 @@ void G_FinishTravel () pawn->AddToHash (); pawn->SetState(pawn->SpawnState); pawn->player->SendPitchLimits(); + // Sync the FLY flags. + if (pawn->flags2 & MF2_FLY) + { + pawn->player->cheats |= CF_FLY; + } + else + { + pawn->player->cheats &= ~CF_FLY; + } for (inv = pawn->Inventory; inv != NULL; inv = inv->Inventory) { @@ -1193,6 +1263,8 @@ void G_FinishTravel () } } } + + bglobal.FinishTravel (); } //========================================================================== diff --git a/src/g_level.h b/src/g_level.h index 527f477c1..c51c5372b 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -50,7 +50,7 @@ class FScanner; #define GCC_YSEG #else #define MSVC_YSEG -#define GCC_YSEG __attribute__((section(SECTION_YREG))) +#define GCC_YSEG __attribute__((section(SECTION_YREG))) __attribute__((used)) #endif struct FIntermissionDescriptor; @@ -523,7 +523,7 @@ level_info_t *CheckLevelRedirect (level_info_t *info); FString CalcMapName (int episode, int level); -void G_ParseMapInfo (const char *basemapinfo); +void G_ParseMapInfo (FString basemapinfo); void G_ClearSnapshots (void); void P_RemoveDefereds (); diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index b4026b7cd..96accfb09 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -1306,6 +1306,7 @@ MapFlagHandlers[] = { "compat_maskedmidtex", MITYPE_COMPATFLAG, COMPATF_MASKEDMIDTEX, 0 }, { "compat_badangles", MITYPE_COMPATFLAG, 0, COMPATF2_BADANGLES }, { "compat_floormove", MITYPE_COMPATFLAG, 0, COMPATF2_FLOORMOVE }, + { "compat_soundcutoff", MITYPE_COMPATFLAG, 0, COMPATF2_SOUNDCUTOFF }, { "cd_start_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end1_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end2_track", MITYPE_EATNEXT, 0, 0 }, @@ -1886,7 +1887,7 @@ static void ClearMapinfo() // //========================================================================== -void G_ParseMapInfo (const char *basemapinfo) +void G_ParseMapInfo (FString basemapinfo) { int lump, lastlump = 0; level_info_t gamedefaults; @@ -1895,7 +1896,7 @@ void G_ParseMapInfo (const char *basemapinfo) atterm(ClearMapinfo); // Parse the default MAPINFO for the current game. This lump *MUST* come from zdoom.pk3. - if (basemapinfo != NULL) + if (basemapinfo.IsNotEmpty()) { FMapInfoParser parse; level_info_t defaultinfo; @@ -1903,7 +1904,7 @@ void G_ParseMapInfo (const char *basemapinfo) if (Wads.GetLumpFile(baselump) > 0) { I_FatalError("File %s is overriding core lump %s.", - Wads.GetWadFullName(Wads.GetLumpFile(baselump)), basemapinfo); + Wads.GetWadFullName(Wads.GetLumpFile(baselump)), basemapinfo.GetChars()); } parse.ParseMapInfo(baselump, gamedefaults, defaultinfo); } diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index d137f5c16..c18f4172a 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -25,6 +25,11 @@ void ABasicArmor::Serialize (FArchive &arc) { Super::Serialize (arc); arc << SavePercent << BonusCount << MaxAbsorb << MaxFullAbsorb << AbsorbCount << ArmorType; + + if (SaveVersion >= 4511) + { + arc << ActualSaveAmount; + } } //=========================================================================== @@ -69,6 +74,7 @@ AInventory *ABasicArmor::CreateCopy (AActor *other) copy->Icon = Icon; copy->BonusCount = BonusCount; copy->ArmorType = ArmorType; + copy->ActualSaveAmount = ActualSaveAmount; GoAwayAndDie (); return copy; } @@ -268,6 +274,7 @@ bool ABasicArmorPickup::Use (bool pickup) armor->MaxAbsorb = MaxAbsorb; armor->MaxFullAbsorb = MaxFullAbsorb; armor->ArmorType = this->GetClass()->TypeName; + armor->ActualSaveAmount = SaveAmount; return true; } @@ -360,6 +367,7 @@ bool ABasicArmorBonus::Use (bool pickup) armor->MaxAbsorb = MaxAbsorb; armor->ArmorType = this->GetClass()->TypeName; armor->MaxFullAbsorb = MaxFullAbsorb; + armor->ActualSaveAmount = MaxSaveAmount; } armor->Amount = MIN(armor->Amount + saveAmount, MaxSaveAmount + armor->BonusCount); @@ -508,7 +516,14 @@ void AHexenArmor::AbsorbDamage (int damage, FName damageType, int &newdamage) // with the dragon skin bracers. if (damage < 10000) { +#if __APPLE__ && __GNUC__ == 4 && __GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ == 1 + // -O1 optimizer bug work around. Only needed for + // GCC 4.2.1 on OS X for 10.4/10.5 tools compatibility. + volatile fixed_t tmp = 300; + Slots[i] -= Scale (damage, SlotsIncrement[i], tmp); +#else Slots[i] -= Scale (damage, SlotsIncrement[i], 300); +#endif if (Slots[i] < 2*FRACUNIT) { Slots[i] = 0; diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp index 88ab0c797..4fe0c7086 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -60,7 +60,8 @@ bool APowerupGiver::Use (bool pickup) } if (BlendColor != 0) { - power->BlendColor = BlendColor; + if (BlendColor != MakeSpecialColormap(65535)) power->BlendColor = BlendColor; + else power->BlendColor = 0; } if (Mode != NAME_None) { @@ -1296,6 +1297,18 @@ void APowerTargeter::InitEffect () PositionAccuracy (); } +bool APowerTargeter::HandlePickup(AInventory *item) +{ + if (Super::HandlePickup(item)) + { + InitEffect(); // reset the HUD sprites + return true; + } + return false; +} + + + void APowerTargeter::DoEffect () { Super::DoEffect (); @@ -1384,6 +1397,42 @@ void APowerFrightener::EndEffect () Owner->player->cheats &= ~CF_FRIGHTENING; } +// Buddha Powerup -------------------------------- + +IMPLEMENT_CLASS (APowerBuddha) + +//=========================================================================== +// +// APowerBuddha :: InitEffect +// +//=========================================================================== + +void APowerBuddha::InitEffect () +{ + Super::InitEffect(); + + if (Owner== NULL || Owner->player == NULL) + return; + + Owner->player->cheats |= CF_BUDDHA; +} + +//=========================================================================== +// +// APowerBuddha :: EndEffect +// +//=========================================================================== + +void APowerBuddha::EndEffect () +{ + Super::EndEffect(); + + if (Owner== NULL || Owner->player == NULL) + return; + + Owner->player->cheats &= ~CF_BUDDHA; +} + // Scanner powerup ---------------------------------------------------------- IMPLEMENT_CLASS (APowerScanner) diff --git a/src/g_shared/a_artifacts.h b/src/g_shared/a_artifacts.h index 668c1da8d..3f51f975d 100644 --- a/src/g_shared/a_artifacts.h +++ b/src/g_shared/a_artifacts.h @@ -173,6 +173,7 @@ protected: void EndEffect (); void PositionAccuracy (); void Travelled (); + bool HandlePickup(AInventory *item); }; class APowerFrightener : public APowerup @@ -183,6 +184,14 @@ protected: void EndEffect (); }; +class APowerBuddha : public APowerup +{ + DECLARE_CLASS (APowerBuddha, APowerup) +protected: + void InitEffect (); + void EndEffect (); +}; + class APowerTimeFreezer : public APowerup { DECLARE_CLASS( APowerTimeFreezer, APowerup ) diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index cf4d1b500..e001af470 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -12,6 +12,7 @@ #include "doomstat.h" #include "g_level.h" #include "farchive.h" +#include "p_enemy.h" static FRandom pr_morphmonst ("MorphMonster"); @@ -527,11 +528,11 @@ bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *mor if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) { AMorphedMonster *fakeme = static_cast(actor); + AActor *realme = fakeme->UnmorphedMe; if ((fakeme->UnmorphTime) && (fakeme->MorphStyle & MORPH_UNDOBYDEATH) && - (fakeme->UnmorphedMe)) + (realme)) { - AActor *realme = fakeme->UnmorphedMe; int realstyle = fakeme->MorphStyle; int realhealth = fakeme->health; if (P_UndoMonsterMorph(fakeme, !!(fakeme->MorphStyle & MORPH_UNDOBYDEATHFORCED))) @@ -542,6 +543,14 @@ bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *mor return true; } } + if (realme->flags4 & MF4_BOSSDEATH) + { + realme->health = 0; // make sure that A_BossDeath considers it dead. + // FIXME: Use the caller's stack once the whole chain is scriptable. + VMFrameStack stack; + VMValue params[3] = { realme, realme, VMValue(NULL, ATAG_STATE) }; + stack.Call(A_BossDeath_VMPtr, params, countof(params), NULL, 0, NULL); + } fakeme->flags3 |= MF3_STAYMORPHED; // moved here from AMorphedMonster::Die() return false; } diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index 0261d09d7..cd9f41628 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -545,7 +545,7 @@ bool AInventory::ShouldRespawn () { if ((ItemFlags & IF_BIGPOWERUP) && !(dmflags2 & DF2_RESPAWN_SUPER)) return false; if (ItemFlags & IF_NEVERRESPAWN) return false; - return !!(dmflags & DF_ITEMS_RESPAWN); + return !!((dmflags & DF_ITEMS_RESPAWN) || (ItemFlags & IF_ALWAYSRESPAWN)); } //=========================================================================== @@ -1070,8 +1070,8 @@ void AInventory::Touch (AActor *toucher) //Added by MC: Check if item taken was the roam destination of any bot for (int i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && this == players[i].dest) - players[i].dest = NULL; + if (players[i].Bot != NULL && this == players[i].Bot->dest) + players[i].Bot->dest = NULL; } } diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index b3a66097f..5c3f913c9 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -126,6 +126,7 @@ enum IF_NEVERRESPAWN = 1<<20, // Never, ever respawns IF_NOSCREENFLASH = 1<<21, // No pickup flash on the player's screen IF_TOSSED = 1<<22, // Was spawned by P_DropItem (i.e. as a monster drop) + IF_ALWAYSRESPAWN = 1<<23, // Always respawn, regardless of dmflag }; @@ -455,6 +456,7 @@ public: int MaxFullAbsorb; int BonusCount; FNameNoInit ArmorType; + int ActualSaveAmount; }; // BasicArmorPickup replaces the armor you have. diff --git a/src/g_shared/sbar_mugshot.cpp b/src/g_shared/sbar_mugshot.cpp index e234a2ead..214c452dd 100644 --- a/src/g_shared/sbar_mugshot.cpp +++ b/src/g_shared/sbar_mugshot.cpp @@ -444,7 +444,7 @@ int FMugShot::UpdateState(player_t *player, StateFlags stateflags) if (bNormal) { bool good; - if ((player->cheats & CF_GODMODE) || (player->mo != NULL && player->mo->flags2 & MF2_INVULNERABLE)) + if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo != NULL && player->mo->flags2 & MF2_INVULNERABLE)) { good = SetState((stateflags & ANIMATEDGODMODE) ? "godanimated" : "god"); } diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index e24811c85..413d8f745 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -886,8 +886,11 @@ class CommandDrawString : public SBarInfoCommand } break; case TIME: - str.Format("%02d:%02d:%02d", (level.time/TICRATE)/3600, ((level.time/TICRATE)%3600)/60, (level.time/TICRATE)%60); + { + int sec = Tics2Seconds(level.time); + str.Format("%02d:%02d:%02d", sec / 3600, (sec % 3600) / 60, sec % 60); break; + } case LOGTEXT: str = statusBar->CPlayer->LogText; break; @@ -3380,43 +3383,16 @@ class CommandInInventory : public SBarInfoCommandFlowControl AInventory *invItem[2] = { statusBar->CPlayer->mo->FindInventory(item[0]), statusBar->CPlayer->mo->FindInventory(item[1]) }; if (invItem[0] != NULL && amount[0] > 0 && invItem[0]->Amount < amount[0]) invItem[0] = NULL; if (invItem[1] != NULL && amount[1] > 0 && invItem[1]->Amount < amount[1]) invItem[1] = NULL; - if(invItem[1] != NULL && conditionAnd) + + if (item[1]) { - if((invItem[0] != NULL && invItem[1] != NULL) && !negate) - { - SetTruth(true, block, statusBar); - return; - } - else if((invItem[0] == NULL || invItem[1] == NULL) && negate) - { - SetTruth(true, block, statusBar); - return; - } + if (conditionAnd) + SetTruth((invItem[0] && invItem[1]) != negate, block, statusBar); + else + SetTruth((invItem[0] || invItem[1]) != negate, block, statusBar); } - else if(invItem[1] != NULL && !conditionAnd) - { - if((invItem[0] != NULL || invItem[1] != NULL) && !negate) - { - SetTruth(true, block, statusBar); - return; - } - else if((invItem[0] == NULL && invItem[1] == NULL) && negate) - { - SetTruth(true, block, statusBar); - return; - } - } - else if((invItem[0] != NULL) && !negate) - { - SetTruth(true, block, statusBar); - return; - } - else if((invItem[0] == NULL) && negate) - { - SetTruth(true, block, statusBar); - return; - } - SetTruth(false, block, statusBar); + else + SetTruth((invItem[0] != NULL) != negate, block, statusBar); } protected: bool conditionAnd; @@ -3455,6 +3431,46 @@ class CommandAlpha : public SBarInfoMainBlock //////////////////////////////////////////////////////////////////////////////// +class CommandIfHealth : public SBarInfoCommandFlowControl +{ + public: + CommandIfHealth(SBarInfo *script) : SBarInfoCommandFlowControl(script), + negate(false), percentage(false) + { + } + + void Parse(FScanner &sc, bool fullScreenOffsets) + { + if (sc.CheckToken(TK_Identifier)) + { + if (sc.Compare("not")) + negate = true; + else + sc.ScriptError("Expected 'not', but got '%s' instead.", sc.String); + } + + sc.MustGetToken(TK_IntConst); + percentage = sc.CheckToken('%'); + hpamount = sc.Number; + + SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); + } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + int phealth = percentage ? statusBar->CPlayer->mo->health * 100 / statusBar->CPlayer->mo->GetMaxHealth() : statusBar->CPlayer->mo->health; + + SetTruth((phealth >= hpamount) ^ negate, block, statusBar); + } + protected: + bool negate; + bool percentage; + int hpamount; +}; + +//////////////////////////////////////////////////////////////////////////////// + static const char *SBarInfoCommandNames[] = { "drawimage", "drawnumber", "drawswitchableimage", @@ -3464,7 +3480,7 @@ static const char *SBarInfoCommandNames[] = "gamemode", "playerclass", "playertype", "aspectratio", "isselected", "usesammo", "usessecondaryammo", "hasweaponpiece", "inventorybarnotvisible", - "weaponammo", "ininventory", "alpha", + "weaponammo", "ininventory", "alpha", "ifhealth", NULL }; @@ -3477,7 +3493,7 @@ enum SBarInfoCommands SBARINFO_GAMEMODE, SBARINFO_PLAYERCLASS, SBARINFO_PLAYERTYPE, SBARINFO_ASPECTRATIO, SBARINFO_ISSELECTED, SBARINFO_USESAMMO, SBARINFO_USESSECONDARYAMMO, SBARINFO_HASWEAPONPIECE, SBARINFO_INVENTORYBARNOTVISIBLE, - SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA, + SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA, SBARINFO_IFHEALTH, }; SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) @@ -3510,6 +3526,7 @@ SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) case SBARINFO_WEAPONAMMO: return new CommandWeaponAmmo(script); case SBARINFO_ININVENTORY: return new CommandInInventory(script); case SBARINFO_ALPHA: return new CommandAlpha(script); + case SBARINFO_IFHEALTH: return new CommandIfHealth(script); } sc.ScriptError("Unknown command '%s'.\n", sc.String); diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index 77dab48dc..5e96ab130 100644 --- a/src/g_shared/shared_hud.cpp +++ b/src/g_shared/shared_hud.cpp @@ -37,6 +37,7 @@ // copy would be. #include "doomtype.h" +#include "doomdef.h" #include "v_video.h" #include "gi.h" #include "c_cvars.h" @@ -48,6 +49,7 @@ #include "p_local.h" #include "doomstat.h" #include "g_level.h" +#include "d_net.h" #include @@ -73,6 +75,7 @@ CVAR (Bool, hud_showscore, false, CVAR_ARCHIVE); // for user maintained score CVAR (Bool, hud_showweapons, true, CVAR_ARCHIVE); // Show weapons collected CVAR (Int , hud_showtime, 0, CVAR_ARCHIVE); // Show time on HUD CVAR (Int , hud_timecolor, CR_GOLD,CVAR_ARCHIVE); // Color of in-game time on HUD +CVAR (Int , hud_showlag, 0, CVAR_ARCHIVE); // Show input latency (maketic - gametic difference) CVAR (Int, hud_ammo_red, 25, CVAR_ARCHIVE) // ammo percent less than which status is red CVAR (Int, hud_ammo_yellow, 50, CVAR_ARCHIVE) // ammo percent less is yellow more green @@ -866,7 +869,7 @@ static void DrawTime() : (hud_showtime < 6 ? level.time : level.totaltime); - const int timeSeconds = timeTicks / TICRATE; + const int timeSeconds = Tics2Seconds(timeTicks); hours = timeSeconds / 3600; minutes = (timeSeconds % 3600) / 60; @@ -916,6 +919,51 @@ static void DrawTime() DrawHudText(SmallFont, hud_timecolor, timeString, hudwidth - width, height, FRACUNIT); } +//--------------------------------------------------------------------------- +// +// Draw in-game latency +// +//--------------------------------------------------------------------------- + +static void DrawLatency() +{ + if (hud_showlag <= 0 || + (hud_showlag == 1 && !netgame) || + hud_showlag > 2) + { + return; + } + int i, localdelay = 0, arbitratordelay = 0; + for (i = 0; i < BACKUPTICS; i++) localdelay += netdelay[0][i]; + for (i = 0; i < BACKUPTICS; i++) arbitratordelay += netdelay[nodeforplayer[Net_Arbitrator]][i]; + localdelay = ((localdelay / BACKUPTICS) * ticdup) * (1000 / TICRATE); + arbitratordelay = ((arbitratordelay / BACKUPTICS) * ticdup) * (1000 / TICRATE); + int color = CR_GREEN; + if (MAX(localdelay, arbitratordelay) > 200) + { + color = CR_YELLOW; + } + if (MAX(localdelay, arbitratordelay) > 400) + { + color = CR_ORANGE; + } + if (MAX(localdelay, arbitratordelay) >= ((BACKUPTICS / 2 - 1) * ticdup) * (1000 / TICRATE)) + { + color = CR_RED; + } + + char tempstr[32]; + + const int millis = (level.time % TICRATE) * (1000 / TICRATE); + mysnprintf(tempstr, sizeof(tempstr), "a:%dms - l:%dms", arbitratordelay, localdelay); + + const int characterCount = (int)strlen(tempstr); + const int width = SmallFont->GetCharWidth('0') * characterCount + 2; // small offset from screen's border + const int height = SmallFont->GetHeight() * 2; + + DrawHudText(SmallFont, color, tempstr, hudwidth - width, height, FRACUNIT); +} + //--------------------------------------------------------------------------- // @@ -981,6 +1029,7 @@ void DrawHUD() if (idmypos) DrawCoordinates(CPlayer); DrawTime(); + DrawLatency(); } else { @@ -993,7 +1042,7 @@ void DrawHUD() if (am_showtotaltime) { - seconds = level.totaltime / TICRATE; + seconds = Tics2Seconds(level.totaltime); mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); DrawHudText(SmallFont, hudcolor_ttim, printstr, hudwidth-length, bottom, FRACUNIT); bottom -= fonth; @@ -1003,14 +1052,14 @@ void DrawHUD() { if (level.clusterflags&CLUSTER_HUB) { - seconds = level.time /TICRATE; + seconds = Tics2Seconds(level.time); mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); DrawHudText(SmallFont, hudcolor_time, printstr, hudwidth-length, bottom, FRACUNIT); bottom -= fonth; } // Single level time for hubs - seconds= level.maptime /TICRATE; + seconds= Tics2Seconds(level.maptime); mysnprintf(printstr, countof(printstr), "%02i:%02i:%02i", seconds/3600, (seconds%3600)/60, seconds%60); DrawHudText(SmallFont, hudcolor_ltim, printstr, hudwidth-length, bottom, FRACUNIT); } diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index aafc0ec19..df1bf6daf 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -146,7 +146,6 @@ void ST_LoadCrosshair(bool alwaysload) { int num = 0; char name[16], size; - int lump; if (!crosshairforce && players[consoleplayer].camera != NULL && @@ -179,18 +178,20 @@ void ST_LoadCrosshair(bool alwaysload) num = -num; } size = (SCREENWIDTH < 640) ? 'S' : 'B'; + mysnprintf (name, countof(name), "XHAIR%c%d", size, num); - if ((lump = Wads.CheckNumForName (name, ns_graphics)) == -1) + FTextureID texid = TexMan.CheckForTexture(name, FTexture::TEX_MiscPatch, FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ShortNameOnly); + if (!texid.isValid()) { mysnprintf (name, countof(name), "XHAIR%c1", size); - if ((lump = Wads.CheckNumForName (name, ns_graphics)) == -1) + texid = TexMan.CheckForTexture(name, FTexture::TEX_MiscPatch, FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ShortNameOnly); + if (!texid.isValid()) { - strcpy (name, "XHAIRS1"); + texid = TexMan.CheckForTexture("XHAIRS1", FTexture::TEX_MiscPatch, FTextureManager::TEXMAN_TryAny | FTextureManager::TEXMAN_ShortNameOnly); } - num = 1; } CrosshairNum = num; - CrosshairImage = TexMan[TexMan.CheckForTexture(name, FTexture::TEX_MiscPatch)]; + CrosshairImage = TexMan[texid]; } //--------------------------------------------------------------------------- @@ -1122,7 +1123,7 @@ void DBaseStatusBar::DrawCrosshair () ST_LoadCrosshair(); // Don't draw the crosshair if there is none - if (CrosshairImage == NULL || gamestate == GS_TITLELEVEL) + if (CrosshairImage == NULL || gamestate == GS_TITLELEVEL || camera->health <= 0) { return; } @@ -1306,8 +1307,8 @@ void DBaseStatusBar::Draw (EHudState state) } else if (automapactive) { - int y, time = level.time / TICRATE, height; - int totaltime = level.totaltime / TICRATE; + int y, time = Tics2Seconds(level.time), height; + int totaltime = Tics2Seconds(level.totaltime); EColorRange highlight = (gameinfo.gametype & GAME_DoomChex) ? CR_UNTRANSLATED : CR_YELLOW; diff --git a/src/g_strife/a_alienspectres.cpp b/src/g_strife/a_alienspectres.cpp index aea72e3a2..4dfd07983 100644 --- a/src/g_strife/a_alienspectres.cpp +++ b/src/g_strife/a_alienspectres.cpp @@ -114,7 +114,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_AlienSpectreDeath) switch (self->GetClass()->TypeName) { case NAME_AlienSpectre1: - EV_DoFloor (DFloor::floorLowerToLowest, NULL, 999, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToLowest, NULL, 999, FRACUNIT, 0, -1, 0, false); log = 95; break; @@ -188,7 +188,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_AlienSpectreDeath) { // Another Sigil piece. Woohoo! log = 83; } - EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, -1, 0, false); break; default: diff --git a/src/g_strife/a_crusader.cpp b/src/g_strife/a_crusader.cpp index 8307056c5..87da6b572 100644 --- a/src/g_strife/a_crusader.cpp +++ b/src/g_strife/a_crusader.cpp @@ -94,7 +94,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CrusaderDeath) if (CheckBossDeath (self)) { - EV_DoFloor (DFloor::floorLowerToLowest, NULL, 667, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToLowest, NULL, 667, FRACUNIT, 0, -1, 0, false); } return 0; } diff --git a/src/g_strife/a_strifestuff.cpp b/src/g_strife/a_strifestuff.cpp index 82054249d..82f4b55d7 100644 --- a/src/g_strife/a_strifestuff.cpp +++ b/src/g_strife/a_strifestuff.cpp @@ -563,7 +563,7 @@ void APowerCoupling::Die (AActor *source, AActor *inflictor, int dmgflags) P_NoiseAlert (source, this); } EV_DoDoor (DDoor::doorClose, NULL, players[i].mo, 225, 2*FRACUNIT, 0, 0, 0); - EV_DoFloor (DFloor::floorLowerToHighest, NULL, 44, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToHighest, NULL, 44, FRACUNIT, 0, -1, 0, false); players[i].mo->GiveInventoryType (QuestItemClasses[5]); S_Sound (CHAN_VOICE, "svox/voc13", 1, ATTN_NORM); players[i].SetLogNumber (13); diff --git a/src/g_strife/strife_sbar.cpp b/src/g_strife/strife_sbar.cpp index 950cba4ba..7e00906cf 100644 --- a/src/g_strife/strife_sbar.cpp +++ b/src/g_strife/strife_sbar.cpp @@ -586,29 +586,33 @@ private: screen->DrawTexture (Images[back], left, top, DTA_CleanNoMove, true, DTA_Alpha, FRACUNIT*3/4, TAG_DONE); screen->DrawTexture (Images[bars], left, top, DTA_CleanNoMove, true, TAG_DONE); + switch (CurrentPop) { case POP_Log: + { + int seconds = Tics2Seconds(level.time); // Draw the latest log message. - mysnprintf (buff, countof(buff), "%02d:%02d:%02d", - (level.time/TICRATE)/3600, - ((level.time/TICRATE)%3600)/60, - (level.time/TICRATE)%60); + mysnprintf(buff, countof(buff), "%02d:%02d:%02d", + seconds / 3600, + (seconds % 3600) / 60, + (seconds) % 60); - screen->DrawText (SmallFont2, CR_UNTRANSLATED, left+210*xscale, top+8*yscale, buff, + screen->DrawText(SmallFont2, CR_UNTRANSLATED, left + 210 * xscale, top + 8 * yscale, buff, DTA_CleanNoMove, true, TAG_DONE); if (CPlayer->LogText != NULL) { - FBrokenLines *lines = V_BreakLines (SmallFont2, 272, CPlayer->LogText); + FBrokenLines *lines = V_BreakLines(SmallFont2, 272, CPlayer->LogText); for (i = 0; lines[i].Width >= 0; ++i) { - screen->DrawText (SmallFont2, CR_UNTRANSLATED, left+24*xscale, top+(18+i*12)*yscale, + screen->DrawText(SmallFont2, CR_UNTRANSLATED, left + 24 * xscale, top + (18 + i * 12)*yscale, lines[i].Text, DTA_CleanNoMove, true, TAG_DONE); } - V_FreeBrokenLines (lines); + V_FreeBrokenLines(lines); } break; + } case POP_Keys: // List the keys the player has. diff --git a/src/gccinlines.h b/src/gccinlines.h index b905449de..ecdf45df6 100644 --- a/src/gccinlines.h +++ b/src/gccinlines.h @@ -41,7 +41,7 @@ static inline SDWORD Scale (SDWORD a, SDWORD b, SDWORD c) : "a,a,a,a,a,a" (a), "m,r,m,r,d,d" (b), "r,r,m,m,r,m" (c) - : "%cc" + : "cc" ); return result; @@ -59,7 +59,7 @@ static inline SDWORD MulScale (SDWORD a, SDWORD b, SDWORD c) : "a,a,a,a" (a), "m,r,m,r" (b), "c,c,I,I" (c) - : "%cc" + : "cc" ); return result; } @@ -210,7 +210,7 @@ static inline SDWORD DivScale (SDWORD a, SDWORD b, SDWORD c) : "a" (lo), "d" (hi), "r" (b) - : "%cc"); + : "cc"); return result; } @@ -226,7 +226,7 @@ static inline SDWORD DivScale1 (SDWORD a, SDWORD b) "=&d,d" (dummy) : "a,a" (a), "r,m" (b) - : "%cc"); + : "cc"); return result; } @@ -241,7 +241,7 @@ static inline SDWORD DivScale1 (SDWORD a, SDWORD b) : "a,a" (a<>(32-s)), \ "r,m" (b) \ - : "%cc"); \ + : "cc"); \ return result; \ } @@ -287,7 +287,7 @@ static inline SDWORD DivScale32 (SDWORD a, SDWORD b) "=d,d" (dummy) : "d,d" (a), "r,m" (b) - : "%cc"); + : "cc"); return result; } @@ -313,7 +313,7 @@ static inline void clearbufshort (void *buff, unsigned int count, WORD clear) "rep stosw" :"=D" (buff), "=c" (count) :"D" (buff), "c" (count), "a" (clear|(clear<<16)) - :"%cc"); + :"cc"); } static inline SDWORD ksgn (SDWORD a) @@ -327,6 +327,6 @@ static inline SDWORD ksgn (SDWORD a) "adc $0,%1" :"=r" (dummy), "=r" (result) :"0" (a) - :"%cc"); + :"cc"); return result; } diff --git a/src/gi.cpp b/src/gi.cpp index 4e64dd425..b0edf2a04 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -290,6 +290,8 @@ void FMapInfoParser::ParseGameInfo() else gameinfo.mCheatMapArrow = ""; } // Insert valid keys here. + GAMEINFOKEY_STRING(mCheatKey, "cheatKey") + GAMEINFOKEY_STRING(mEasyKey, "easyKey") GAMEINFOKEY_STRING(TitlePage, "titlePage") GAMEINFOKEY_STRINGARRAY(creditPages, "addcreditPage", 8, false) GAMEINFOKEY_STRINGARRAY(creditPages, "CreditPage", 8, true) diff --git a/src/gi.h b/src/gi.h index bbfbe73ff..d8d19a14b 100644 --- a/src/gi.h +++ b/src/gi.h @@ -169,6 +169,7 @@ struct gameinfo_t int TextScreenY; FName DefaultEndSequence; FString mMapArrow, mCheatMapArrow; + FString mEasyKey, mCheatKey; FGIFont mStatscreenMapNameFont; FGIFont mStatscreenFinishedFont; FGIFont mStatscreenEnteringFont; diff --git a/src/hu_scores.cpp b/src/hu_scores.cpp index af5d1bbaa..fe2735ede 100644 --- a/src/hu_scores.cpp +++ b/src/hu_scores.cpp @@ -48,6 +48,8 @@ #include "d_player.h" #include "hu_stuff.h" #include "gstrings.h" +#include "d_net.h" +#include "c_dispatch.h" // MACROS ------------------------------------------------------------------ @@ -61,7 +63,7 @@ static void HU_DoDrawScores (player_t *, player_t *[MAXPLAYERS]); static void HU_DrawTimeRemaining (int y); -static void HU_DrawPlayer (player_t *, bool, int, int, int, int, int, int, int, int); +static void HU_DrawPlayer(player_t *, bool, int, int, int, int, int, int, int, int, int); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -116,6 +118,8 @@ int STACK_ARGS compareteams (const void *arg1, const void *arg2) return diff; } +bool SB_ForceActive = false; + // PRIVATE DATA DEFINITIONS ------------------------------------------------ // CODE -------------------------------------------------------------------- @@ -228,7 +232,7 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER int maxnamewidth, maxscorewidth, maxiconheight; int numTeams = 0; int x, y, ypadding, bottom; - int col2, col3, col4; + int col2, col3, col4, col5; if (deathmatch) { @@ -309,12 +313,14 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER const char *text_color = GStrings("SCORE_COLOR"), *text_frags = GStrings(deathmatch ? "SCORE_FRAGS" : "SCORE_KILLS"), - *text_name = GStrings("SCORE_NAME"); + *text_name = GStrings("SCORE_NAME"), + *text_delay = GStrings("SCORE_DELAY"); col2 = (SmallFont->StringWidth(text_color) + 8) * CleanXfac; col3 = col2 + (SmallFont->StringWidth(text_frags) + 8) * CleanXfac; col4 = col3 + maxscorewidth * CleanXfac; - x = (SCREENWIDTH >> 1) - ((maxnamewidth * CleanXfac + col4) >> 1); + col5 = col4 + (maxnamewidth + 8) * CleanXfac; + x = (SCREENWIDTH >> 1) - (((SmallFont->StringWidth(text_delay) * CleanXfac) + col5) >> 1); screen->DrawText (SmallFont, color, x, y, text_color, DTA_CleanNoMove, true, TAG_DONE); @@ -325,6 +331,9 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER screen->DrawText (SmallFont, color, x + col4, y, text_name, DTA_CleanNoMove, true, TAG_DONE); + screen->DrawText(SmallFont, color, x + col5, y, text_delay, + DTA_CleanNoMove, true, TAG_DONE); + y += height + 6 * CleanYfac; bottom -= height; @@ -332,7 +341,7 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER { if (playeringame[sortedplayers[i] - players]) { - HU_DrawPlayer (sortedplayers[i], player==sortedplayers[i], x, col2, col3, col4, maxnamewidth, y, ypadding, lineheight); + HU_DrawPlayer(sortedplayers[i], player == sortedplayers[i], x, col2, col3, col4, col5, maxnamewidth, y, ypadding, lineheight); y += lineheight + CleanYfac; } } @@ -377,7 +386,7 @@ static void HU_DrawTimeRemaining (int y) // //========================================================================== -static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, int col3, int col4, int maxnamewidth, int y, int ypadding, int height) +static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, int col3, int col4, int col5, int maxnamewidth, int y, int ypadding, int height) { int color; char str[80]; @@ -387,12 +396,13 @@ static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, // The teamplay mode uses colors to show teams, so we need some // other way to do highlighting. And it may as well be used for // all modes for the sake of consistancy. - screen->Dim(MAKERGB(200,245,255), 0.125f, col1 - 12*CleanXfac, y - 1, col4 + (maxnamewidth + 24)*CleanXfac, height + 2); + screen->Dim(MAKERGB(200,245,255), 0.125f, col1 - 12*CleanXfac, y - 1, col5 + (maxnamewidth + 24)*CleanXfac, height + 2); } col2 += col1; col3 += col1; col4 += col1; + col5 += col1; color = HU_GetRowColor(player, highlight); HU_DrawColorBar(col1, y, height, (int)(player - players)); @@ -412,6 +422,18 @@ static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, screen->DrawText (SmallFont, color, col4, y + ypadding, player->userinfo.GetName(), DTA_CleanNoMove, true, TAG_DONE); + int avgdelay = 0; + for (int i = 0; i < BACKUPTICS; i++) + { + avgdelay += netdelay[nodeforplayer[(int)(player - players)]][i]; + } + avgdelay /= BACKUPTICS; + + mysnprintf(str, countof(str), "%d", (avgdelay * ticdup) * (1000 / TICRATE)); + + screen->DrawText(SmallFont, color, col5, y + ypadding, str, + DTA_CleanNoMove, true, TAG_DONE); + if (teamplay && Teams[player->userinfo.GetTeam()].GetLogo().IsNotEmpty ()) { FTexture *pic = TexMan[Teams[player->userinfo.GetTeam()].GetLogo().GetChars ()]; @@ -473,3 +495,8 @@ int HU_GetRowColor(player_t *player, bool highlight) } } } + +CCMD (togglescoreboard) +{ + SB_ForceActive = !SB_ForceActive; +} diff --git a/src/hu_stuff.h b/src/hu_stuff.h index dc22a2adc..eb2dc573b 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -52,6 +52,8 @@ void HU_GetPlayerWidths(int &maxnamewidth, int &maxscorewidth, int &maxiconheigh void HU_DrawColorBar(int x, int y, int height, int playernum); int HU_GetRowColor(player_t *player, bool hightlight); +extern bool SB_ForceActive; + // Sorting routines int STACK_ARGS comparepoints(const void *arg1, const void *arg2); diff --git a/src/i_net.cpp b/src/i_net.cpp index 889688b48..188d2871e 100644 --- a/src/i_net.cpp +++ b/src/i_net.cpp @@ -110,6 +110,7 @@ const char *neterror (void); enum { PRE_CONNECT, // Sent from guest to host for initial connection + PRE_KEEPALIVE, PRE_DISCONNECT, // Sent from guest that aborts the game PRE_ALLHERE, // Sent from host to guest when everybody has connected PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt @@ -134,8 +135,8 @@ struct PreGamePacket }; struct { - u_long address; - u_short port; + DWORD address; + WORD port; BYTE player; BYTE pad; } machines[MAXNETNODES]; @@ -208,11 +209,11 @@ void PacketSend (void) { I_FatalError("Netbuffer overflow!"); } + assert(!(doomcom.data[0] & NCMD_COMPRESSED)); uLong size = TRANSMIT_SIZE - 1; if (doomcom.datalength >= 10) { - assert(!(doomcom.data[0] & NCMD_COMPRESSED)); TransmitBuffer[0] = doomcom.data[0] | NCMD_COMPRESSED; c = compress2(TransmitBuffer + 1, &size, doomcom.data + 1, doomcom.datalength - 1, 9); size += 1; @@ -548,10 +549,15 @@ bool Host_CheckForConnects (void *userdata) SendConAck (doomcom.numnodes, numplayers); } break; + + case PRE_KEEPALIVE: + break; } } if (doomcom.numnodes < numplayers) { + // Send message to everyone as a keepalive + SendConAck(doomcom.numnodes, numplayers); return false; } @@ -654,6 +660,12 @@ void HostGame (int i) numplayers = 2; } + if (numplayers > MAXNETNODES) + { + I_FatalError("You cannot host a game with %d players. The limit is currently %d.", numplayers, MAXNETNODES); + return; + } + if (numplayers == 1) { // Special case: Only 1 player, so don't bother starting the network netgame = false; @@ -822,6 +834,10 @@ bool Guest_WaitForOthers (void *userdata) } } + packet.Fake = PRE_FAKE; + packet.Message = PRE_KEEPALIVE; + PreSend(&packet, 2, &sendaddress[1]); + return false; } @@ -938,11 +954,6 @@ bool I_InitNetwork (void) doomcom.ticdup = 1; } - if (Args->CheckParm ("-extratic")) - doomcom.extratics = 1; - else - doomcom.extratics = 0; - v = Args->CheckValue ("-port"); if (v) { diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 42897277f..762e5299e 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -99,6 +99,23 @@ void cht_DoCheat (player_t *player, int cheat) msg = GStrings("TXT_BUDDHAOFF"); break; + case CHT_GOD2: + player->cheats ^= CF_GODMODE2; + if (player->cheats & CF_GODMODE2) + msg = GStrings("STSTR_DQD2ON"); + else + msg = GStrings("STSTR_DQD2OFF"); + ST_SetNeedRefresh(); + break; + + case CHT_BUDDHA2: + player->cheats ^= CF_BUDDHA2; + if (player->cheats & CF_BUDDHA2) + msg = GStrings("TXT_BUDDHA2ON"); + else + msg = GStrings("TXT_BUDDHA2OFF"); + break; + case CHT_NOCLIP: player->cheats ^= CF_NOCLIP; if (player->cheats & CF_NOCLIP) @@ -323,7 +340,6 @@ void cht_DoCheat (player_t *player, int cheat) player->mo->Translation = TRANSLATION(TRANSLATION_Players, BYTE(player-players)); } player->mo->DamageType = NAME_None; -// player->mo->GiveDefaultInventory(); if (player->ReadyWeapon != NULL) { P_SetPsprite(player, ps_weapon, player->ReadyWeapon->GetUpState()); diff --git a/src/m_misc.cpp b/src/m_misc.cpp index c550c4593..7f4fa482d 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -137,6 +137,32 @@ int M_ReadFile (char const *name, BYTE **buffer) return length; } +// +// M_ReadFile (same as above but use malloc instead of new to allocate the buffer.) +// +int M_ReadFileMalloc (char const *name, BYTE **buffer) +{ + int handle, count, length; + struct stat fileinfo; + BYTE *buf; + + handle = open (name, O_RDONLY | O_BINARY, 0666); + if (handle == -1) + I_Error ("Couldn't read file %s", name); + if (fstat (handle,&fileinfo) == -1) + I_Error ("Couldn't read file %s", name); + length = fileinfo.st_size; + buf = (BYTE*)M_Malloc(length); + count = read (handle, buf, length); + close (handle); + + if (count < length) + I_Error ("Couldn't read file %s", name); + + *buffer = buf; + return length; +} + //--------------------------------------------------------------------------- // // PROC M_FindResponseFile diff --git a/src/m_misc.h b/src/m_misc.h index 90844221f..9599306de 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -33,6 +33,7 @@ extern FGameConfigFile *GameConfig; bool M_WriteFile (char const *name, void *source, int length); int M_ReadFile (char const *name, BYTE **buffer); +int M_ReadFileMalloc (char const *name, BYTE **buffer); void M_FindResponseFile (void); // [RH] M_ScreenShot now accepts a filename parameter. diff --git a/src/namedef.h b/src/namedef.h index db96b56c8..17626092a 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -300,6 +300,9 @@ xx(Abs) xx(ACS_NamedExecuteWithResult) xx(CallACS) xx(Sqrt) +xx(CheckClass) +xx(IsPointerEqual) +xx(Pick) // Various actor names which are used internally xx(MapSpot) @@ -420,6 +423,7 @@ xx(Passuse) xx(Repeatspecial) xx(Conversation) xx(Locknumber) +xx(Midtex3dimpassible) xx(Playercross) xx(Playeruse) diff --git a/src/nodebuild_utility.cpp b/src/nodebuild_utility.cpp index e7d5865d5..14ab7be58 100644 --- a/src/nodebuild_utility.cpp +++ b/src/nodebuild_utility.cpp @@ -69,7 +69,13 @@ static const int PO_LINE_EXPLICIT = 5; angle_t FNodeBuilder::PointToAngle (fixed_t x, fixed_t y) { const double rad2bam = double(1<<30) / M_PI; +#if defined __APPLE__ && !defined __llvm__ + // Work-around for vectorization issue in Apple's GCC 4.x + // See https://gcc.gnu.org/wiki/Math_Optimization_Flags for details + long double ang = atan2l (double(y), double(x)); +#else // !__APPLE__ || __llvm__ double ang = atan2 (double(y), double(x)); +#endif // __APPLE__ && !__llvm__ return angle_t(ang * rad2bam) << 1; } diff --git a/src/oplsynth/mlopl_io.cpp b/src/oplsynth/mlopl_io.cpp index b9d06629f..691463470 100644 --- a/src/oplsynth/mlopl_io.cpp +++ b/src/oplsynth/mlopl_io.cpp @@ -323,7 +323,7 @@ int OPLio::OPLinit(uint numchips, bool stereo, bool initopl3) { assert(numchips >= 1 && numchips <= countof(chips)); uint i; - IsOPL3 = (opl_core == 1 || opl_core == 2); + IsOPL3 = (opl_core == 1 || opl_core == 2 || opl_core == 3); memset(chips, 0, sizeof(chips)); if (IsOPL3) @@ -332,7 +332,7 @@ int OPLio::OPLinit(uint numchips, bool stereo, bool initopl3) } for (i = 0; i < numchips; ++i) { - OPLEmul *chip = IsOPL3 ? (opl_core == 1 ? DBOPLCreate(stereo) : JavaOPLCreate(stereo)) : YM3812Create(stereo); + OPLEmul *chip = IsOPL3 ? (opl_core == 1 ? DBOPLCreate(stereo) : (opl_core == 2 ? JavaOPLCreate(stereo) : NukedOPL3Create(stereo))) : YM3812Create(stereo); if (chip == NULL) { break; diff --git a/src/oplsynth/nukedopl3.cpp b/src/oplsynth/nukedopl3.cpp new file mode 100644 index 000000000..a520f68e1 --- /dev/null +++ b/src/oplsynth/nukedopl3.cpp @@ -0,0 +1,979 @@ +/* +* Copyright (C) 2013-2014 Nuke.YKT +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Nuked Yamaha YMF262(aka OPL3) emulator. + Thanks: + MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + Feedback and Rhythm part calculation information. + forums.submarine.org.uk(carbon14, opl3): + Tremolo and phase generator calculation information. + OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + OPL2 ROMs. +*/ + +//version 1.5 + +/* Changelog: + v1.1: + Vibrato's sign fix + v1.2: + Operator key fix + Corrected 4-operator mode + Corrected rhythm mode + Some small fixes + v1.2.1: + Small envelope generator fix + Removed EX_Get function(not used) + v1.3: + Complete rewrite + (Not released) + v1.4: + New envelope and waveform generator + Some small fixes. + (Not released) + v1.4.1: + Envelope generator rate calculation fix + (Not released) + v1.4.2: + Version for ZDoom. + v1.5: + Optimizations +*/ + + +/* Verified: + Noise generator. + Waveform generator. + Envelope generator increase table. + Tremolo. +*/ + +/* TODO: + Verify: + kslrom[15] value(is it 128?). + Sustain level = 15. + Vibrato, Phase generator. + Rhythm part. + Envelope generator state switching(decay->sustain when egt = 1 and decay->release). + Feedback. + Register write. + 4-operator. +*/ + +#include +#include +#include "nukedopl3.h" + +// +// Envelope generator +// + +typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope); +typedef void(*envelope_genfunc)(opl_slot *slott); + +Bit16s envelope_calcexp(Bit32u level) { + if (level > 0x1fff) { + level = 0x1fff; + } + return ((exprom[(level & 0xff) ^ 0xff] | 0x400) << 1) >> (level >> 8); +} + +Bit16s envelope_calcsin0(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + } + if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +Bit16s envelope_calcsin1(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin2(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x100) { + out = logsinrom[(phase & 0xff) ^ 0xff]; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin3(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x100) { + out = 0x1000; + } + else { + out = logsinrom[phase & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin4(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if ((phase & 0x300) == 0x100 && (phase & 0xff)) { + phase--; + neg = ~0; + } + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x80) { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else { + out = logsinrom[(phase << 1) & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +Bit16s envelope_calcsin5(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + if (phase & 0x200) { + out = 0x1000; + } + else if (phase & 0x80) { + out = logsinrom[((phase ^ 0xff) << 1) & 0xff]; + } + else { + out = logsinrom[(phase << 1) & 0xff]; + } + return envelope_calcexp(out + (envelope << 3)); +} + +Bit16s envelope_calcsin6(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + } + return envelope_calcexp(envelope << 3) ^ neg; +} + +Bit16s envelope_calcsin7(Bit16u phase, Bit16u envelope) { + phase &= 0x3ff; + Bit16u out = 0; + Bit16u neg = 0; + if (phase & 0x200 && (phase & 0x1ff)) { + phase--; + neg = ~0; + phase = (phase & 0x1ff) ^ 0x1ff; + } + out = phase << 3; + return envelope_calcexp(out + (envelope << 3)) ^ neg; +} + +envelope_sinfunc envelope_sin[8] = { + envelope_calcsin0, + envelope_calcsin1, + envelope_calcsin2, + envelope_calcsin3, + envelope_calcsin4, + envelope_calcsin5, + envelope_calcsin6, + envelope_calcsin7 +}; + +void envelope_gen_off(opl_slot *slott); +void envelope_gen_change(opl_slot *slott); +void envelope_gen_attack(opl_slot *slott); +void envelope_gen_decay(opl_slot *slott); +void envelope_gen_sustain(opl_slot *slott); +void envelope_gen_release(opl_slot *slott); + +envelope_genfunc envelope_gen[6] = { + envelope_gen_off, + envelope_gen_attack, + envelope_gen_decay, + envelope_gen_sustain, + envelope_gen_release, + envelope_gen_change +}; + +enum envelope_gen_num { + envelope_gen_num_off = 0, + envelope_gen_num_attack = 1, + envelope_gen_num_decay = 2, + envelope_gen_num_sustain = 3, + envelope_gen_num_release = 4, + envelope_gen_num_change = 5 +}; + +Bit8u envelope_calc_rate(opl_slot *slot, Bit8u reg_rate) { + if (reg_rate == 0x00) { + return 0x00; + } + Bit8u rate = (reg_rate << 2) + (slot->reg_ksr ? slot->channel->ksv : (slot->channel->ksv >> 2)); + if (rate > 0x3c) { + rate = 0x3c; + } + return rate; +} + +void envelope_update_ksl(opl_slot *slot) { + Bit16s ksl = (kslrom[slot->channel->f_num >> 6] << 1) - ((slot->channel->block ^ 0x07) << 5) - 0x20; + if (ksl < 0) { + ksl = 0; + } + slot->eg_ksl = (Bit8u)ksl; +} + +void envelope_update_rate(opl_slot *slot) { + switch (slot->eg_gen) { + case envelope_gen_num_off: + slot->eg_rate = 0; + break; + case envelope_gen_num_attack: + slot->eg_rate = envelope_calc_rate(slot, slot->reg_ar); + break; + case envelope_gen_num_decay: + slot->eg_rate = envelope_calc_rate(slot, slot->reg_dr); + break; + case envelope_gen_num_sustain: + case envelope_gen_num_release: + slot->eg_rate = envelope_calc_rate(slot, slot->reg_rr); + break; + } +} + +void envelope_gen_off(opl_slot *slot) { + slot->eg_rout = 0x1ff; +} + +void envelope_gen_change(opl_slot *slot) { + slot->eg_gen = slot->eg_gennext; + envelope_update_rate(slot); +} + +void envelope_gen_attack(opl_slot *slot) { + slot->eg_rout += ((~slot->eg_rout) *slot->eg_inc) >> 3; + if (slot->eg_rout < 0x00) { + slot->eg_rout = 0x00; + } + if (!slot->eg_rout) { + slot->eg_gen = envelope_gen_num_change; + slot->eg_gennext = envelope_gen_num_decay; + } +} + +void envelope_gen_decay(opl_slot *slot) { + slot->eg_rout += slot->eg_inc; + if (slot->eg_rout >= slot->reg_sl << 4) { + slot->eg_gen = envelope_gen_num_change; + slot->eg_gennext = envelope_gen_num_sustain; + } +} + +void envelope_gen_sustain(opl_slot *slot) { + if (!slot->reg_type) { + envelope_gen_release(slot); + } +} + +void envelope_gen_release(opl_slot *slot) { + slot->eg_rout += slot->eg_inc; + if (slot->eg_rout >= 0x1ff) { + slot->eg_gen = envelope_gen_num_change; + slot->eg_gennext = envelope_gen_num_off; + } +} + +void envelope_calc(opl_slot *slot) { + Bit8u rate_h, rate_l; + rate_h = slot->eg_rate >> 2; + rate_l = slot->eg_rate & 3; + Bit8u inc = 0; + if (slot->eg_gen == envelope_gen_num_attack && rate_h == 0x0f) { + inc = 8; + } + else if (eg_incsh[rate_h] > 0) { + if ((slot->chip->timer & ((1 << eg_incsh[rate_h]) - 1)) == 0) { + inc = eg_incstep[eg_incdesc[rate_h]][rate_l][((slot->chip->timer) >> eg_incsh[rate_h]) & 0x07]; + } + } + else { + inc = eg_incstep[eg_incdesc[rate_h]][rate_l][slot->chip->timer & 0x07] << (-eg_incsh[rate_h]); + } + slot->eg_inc = inc; + envelope_gen[slot->eg_gen](slot); + slot->eg_out = slot->eg_rout + (slot->reg_tl << 2) + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem; +} + +void eg_keyon(opl_slot *slot, Bit8u type) { + if (!slot->key) { + slot->eg_gen = envelope_gen_num_change; + slot->eg_gennext = envelope_gen_num_attack; + slot->pg_phase = 0x00; + } + slot->key |= type; +} + +void eg_keyoff(opl_slot *slot, Bit8u type) { + if (slot->key) { + slot->key &= (~type); + if (!slot->key) { + slot->eg_gen = envelope_gen_num_change; + slot->eg_gennext = envelope_gen_num_release; + } + } +} + +// +// Phase Generator +// + +void pg_generate(opl_slot *slot) { + Bit16u f_num = slot->channel->f_num; + if (slot->reg_vib) { + Bit8u f_num_high = f_num >> (7 + vib_table[(slot->chip->timer >> 10)&0x07] + (0x01 - slot->chip->dvb)); + f_num += f_num_high * vibsgn_table[(slot->chip->timer >> 10) & 0x07]; + } + slot->pg_phase += (((f_num << slot->channel->block) >> 1) * mt[slot->reg_mult]) >> 1; +} + +// +// Noise Generator +// + +void n_generate(opl_chip *chip) { + if (chip->noise & 0x01) { + chip->noise ^= 0x800302; + } + chip->noise >>= 1; +} + +// +// Slot +// + +void slot_write20(opl_slot *slot,Bit8u data) { + if ((data >> 7) & 0x01) { + slot->trem = &slot->chip->tremval; + } + else { + slot->trem = (Bit8u*)&slot->chip->zeromod; + } + slot->reg_vib = (data >> 6) & 0x01; + slot->reg_type = (data >> 5) & 0x01; + slot->reg_ksr = (data >> 4) & 0x01; + slot->reg_mult = data & 0x0f; + envelope_update_rate(slot); +} + +void slot_write40(opl_slot *slot, Bit8u data) { + slot->reg_ksl = (data >> 6) & 0x03; + slot->reg_tl = data & 0x3f; + envelope_update_ksl(slot); +} + +void slot_write60(opl_slot *slot, Bit8u data) { + slot->reg_ar = (data >> 4) & 0x0f; + slot->reg_dr = data & 0x0f; + envelope_update_rate(slot); +} + +void slot_write80(opl_slot *slot, Bit8u data) { + slot->reg_sl = (data >> 4) & 0x0f; + if (slot->reg_sl == 0x0f) { + slot->reg_sl = 0x1f; + } + slot->reg_rr = data & 0x0f; + envelope_update_rate(slot); +} + +void slot_writee0(opl_slot *slot, Bit8u data) { + slot->reg_wf = data & 0x07; + if (slot->chip->newm == 0x00) { + slot->reg_wf &= 0x03; + } +} + +void slot_generatephase(opl_slot *slot, Bit16u phase) { + slot->out = envelope_sin[slot->reg_wf](phase, slot->eg_out); +} + +void slot_generate(opl_slot *slot) { + slot->out = envelope_sin[slot->reg_wf]((slot->pg_phase >> 9) + (*slot->mod), slot->eg_out); +} + +void slot_generatezm(opl_slot *slot) { + slot->out = envelope_sin[slot->reg_wf]((slot->pg_phase >> 9), slot->eg_out); +} + +void slot_calgfb(opl_slot *slot) { + slot->prout[1] = slot->prout[0]; + slot->prout[0] = slot->out; + if (slot->channel->fb != 0x00) { + slot->fbmod = (slot->prout[0] + slot->prout[1]) >> (0x09 - slot->channel->fb); + } + else { + slot->fbmod = 0; + } +} + +// +// Channel +// + +void chan_setupalg(opl_channel *channel); + +void chan_updaterhythm(opl_chip *chip, Bit8u data) { + chip->rhy = data & 0x3f; + if (chip->rhy & 0x20) { + chip->channel[6].out[0] = &chip->slot[13].out; + chip->channel[6].out[1] = &chip->slot[13].out; + chip->channel[6].out[2] = &chip->zeromod; + chip->channel[6].out[3] = &chip->zeromod; + chip->channel[7].out[0] = &chip->slot[14].out; + chip->channel[7].out[1] = &chip->slot[14].out; + chip->channel[7].out[2] = &chip->slot[15].out; + chip->channel[7].out[3] = &chip->slot[15].out; + chip->channel[8].out[0] = &chip->slot[16].out; + chip->channel[8].out[1] = &chip->slot[16].out; + chip->channel[8].out[2] = &chip->slot[17].out; + chip->channel[8].out[3] = &chip->slot[17].out; + for (Bit8u chnum = 6; chnum < 9; chnum++) { + chip->channel[chnum].chtype = ch_drum; + } + //hh + if (chip->rhy & 0x01) { + eg_keyon(&chip->slot[14], egk_drum); + } + else { + eg_keyoff(&chip->slot[14], egk_drum); + } + //tc + if (chip->rhy & 0x02) { + eg_keyon(&chip->slot[17], egk_drum); + } + else { + eg_keyoff(&chip->slot[17], egk_drum); + } + //tom + if (chip->rhy & 0x04) { + eg_keyon(&chip->slot[16], egk_drum); + } + else { + eg_keyoff(&chip->slot[16], egk_drum); + } + //sd + if (chip->rhy & 0x08) { + eg_keyon(&chip->slot[15], egk_drum); + } + else { + eg_keyoff(&chip->slot[15], egk_drum); + } + //bd + if (chip->rhy & 0x10) { + eg_keyon(&chip->slot[12], egk_drum); + eg_keyon(&chip->slot[13], egk_drum); + } + else { + eg_keyoff(&chip->slot[12], egk_drum); + eg_keyoff(&chip->slot[13], egk_drum); + } + } + else { + for (Bit8u chnum = 6; chnum < 9; chnum++) { + chip->channel[chnum].chtype = ch_2op; + chan_setupalg(&chip->channel[chnum]); + } + } +} + +void chan_writea0(opl_channel *channel, Bit8u data) { + if (channel->chip->newm && channel->chtype == ch_4op2) { + return; + } + channel->f_num = (channel->f_num & 0x300) | data; + channel->ksv = (channel->block << 1) | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + envelope_update_ksl(channel->slots[0]); + envelope_update_ksl(channel->slots[1]); + envelope_update_rate(channel->slots[0]); + envelope_update_rate(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) { + channel->pair->f_num = channel->f_num; + channel->pair->ksv = channel->ksv; + envelope_update_ksl(channel->pair->slots[0]); + envelope_update_ksl(channel->pair->slots[1]); + envelope_update_rate(channel->pair->slots[0]); + envelope_update_rate(channel->pair->slots[1]); + } +} + +void chan_writeb0(opl_channel *channel, Bit8u data) { + if (channel->chip->newm && channel->chtype == ch_4op2) { + return; + } + channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8); + channel->block = (data >> 2) & 0x07; + channel->ksv = (channel->block << 1) | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01); + envelope_update_ksl(channel->slots[0]); + envelope_update_ksl(channel->slots[1]); + envelope_update_rate(channel->slots[0]); + envelope_update_rate(channel->slots[1]); + if (channel->chip->newm && channel->chtype == ch_4op) { + channel->pair->f_num = channel->f_num; + channel->pair->block = channel->block; + channel->pair->ksv = channel->ksv; + envelope_update_ksl(channel->pair->slots[0]); + envelope_update_ksl(channel->pair->slots[1]); + envelope_update_rate(channel->pair->slots[0]); + envelope_update_rate(channel->pair->slots[1]); + } +} + +void chan_setupalg(opl_channel *channel) { + if (channel->chtype == ch_drum) { + return; + } + if (channel->alg & 0x08) { + return; + } + if (channel->alg & 0x04) { + channel->pair->out[0] = &channel->chip->zeromod; + channel->pair->out[1] = &channel->chip->zeromod; + channel->pair->out[2] = &channel->chip->zeromod; + channel->pair->out[3] = &channel->chip->zeromod; + switch (channel->alg & 0x03) { + case 0x00: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->pair->slots[0]->out; + channel->slots[0]->mod = &channel->chip->zeromod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[1]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x02: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x03: + channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod; + channel->pair->slots[1]->mod = &channel->chip->zeromod; + channel->slots[0]->mod = &channel->pair->slots[1]->out; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->pair->slots[0]->out; + channel->out[1] = &channel->slots[0]->out; + channel->out[2] = &channel->slots[1]->out; + channel->out[3] = &channel->chip->zeromod; + break; + } + } + else { + switch (channel->alg & 0x01) { + case 0x00: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->slots[0]->out; + channel->out[0] = &channel->slots[1]->out; + channel->out[1] = &channel->chip->zeromod; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + case 0x01: + channel->slots[0]->mod = &channel->slots[0]->fbmod; + channel->slots[1]->mod = &channel->chip->zeromod; + channel->out[0] = &channel->slots[0]->out; + channel->out[1] = &channel->slots[1]->out; + channel->out[2] = &channel->chip->zeromod; + channel->out[3] = &channel->chip->zeromod; + break; + } + } +} + +void chan_writec0(opl_channel *channel, Bit8u data) { + channel->fb = (data & 0x0e) >> 1; + channel->con = data & 0x01; + channel->alg = channel->con; + if (channel->chip->newm) { + if (channel->chtype == ch_4op) { + channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con); + channel->alg = 0x08; + chan_setupalg(channel->pair); + } + else if (channel->chtype == ch_4op2) { + channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con); + channel->pair->alg = 0x08; + chan_setupalg(channel); + } + else { + chan_setupalg(channel); + } + } + else { + chan_setupalg(channel); + } + if (channel->chip->newm) { + channel->cha = ((data >> 4) & 0x01) ? ~0 : 0; + channel->chb = ((data >> 5) & 0x01) ? ~0 : 0; + } + else { + channel->cha = channel->chb = ~0; + } +} + +void chan_generaterhythm(opl_chip *chip) { + if (chip->rhy & 0x20) { + opl_channel *channel6 = &chip->channel[6]; + opl_channel *channel7 = &chip->channel[7]; + opl_channel *channel8 = &chip->channel[8]; + slot_generate(channel6->slots[0]); + slot_generate(channel6->slots[1]); + Bit16u phase14 = channel7->slots[0]->pg_phase & 0x3ff; + Bit16u phase17 = channel8->slots[1]->pg_phase & 0x3ff; + Bit16u phase = 0x00; + //hh tc phase bit + Bit16u phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; + //hh + phase = (phasebit << 9) | (0x34 << ((phasebit ^ (chip->noise & 0x01) << 1))); + slot_generatephase(channel7->slots[0], phase); + //sd + phase = (0x100 << ((phase14 >> 8) & 0x01)) ^ ((chip->noise & 0x01) << 8); + slot_generatephase(channel7->slots[1], phase); + //tt + slot_generatezm(channel8->slots[0]); + //tc + phase = 0x100 | (phasebit << 9); + slot_generatephase(channel8->slots[1], phase); + } +} + +void chan_generate(opl_channel *channel) { + if (channel->chtype == ch_drum) { + return; + } + if (channel->alg & 0x08) { + return; + } + if (channel->alg & 0x04) { + slot_generate(channel->pair->slots[0]); + slot_generate(channel->pair->slots[1]); + slot_generate(channel->slots[0]); + slot_generate(channel->slots[1]); + } + else { + slot_generate(channel->slots[0]); + slot_generate(channel->slots[1]); + } +} + +void chan_enable(opl_channel *channel) { + if (channel->chip->newm) { + if (channel->chtype == ch_4op) { + eg_keyon(channel->slots[0], egk_norm); + eg_keyon(channel->slots[1], egk_norm); + eg_keyon(channel->pair->slots[0], egk_norm); + eg_keyon(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) { + eg_keyon(channel->slots[0], egk_norm); + eg_keyon(channel->slots[1], egk_norm); + } + } + else { + eg_keyon(channel->slots[0], egk_norm); + eg_keyon(channel->slots[1], egk_norm); + } +} + +void chan_disable(opl_channel *channel) { + if (channel->chip->newm) { + if (channel->chtype == ch_4op) { + eg_keyoff(channel->slots[0], egk_norm); + eg_keyoff(channel->slots[1], egk_norm); + eg_keyoff(channel->pair->slots[0], egk_norm); + eg_keyoff(channel->pair->slots[1], egk_norm); + } + else if (channel->chtype == ch_2op || channel->chtype == ch_drum) { + eg_keyoff(channel->slots[0], egk_norm); + eg_keyoff(channel->slots[1], egk_norm); + } + } + else { + eg_keyoff(channel->slots[0], egk_norm); + eg_keyoff(channel->slots[1], egk_norm); + } +} + +void chan_set4op(opl_chip *chip, Bit8u data) { + for (Bit8u bit = 0; bit < 6; bit++) { + Bit8u chnum = bit; + if (bit >= 3) { + chnum += 9 - 3; + } + if ((data >> bit) & 0x01) { + chip->channel[chnum].chtype = ch_4op; + chip->channel[chnum + 3].chtype = ch_4op2; + } + else { + chip->channel[chnum].chtype = ch_2op; + chip->channel[chnum + 3].chtype = ch_2op; + } + } +} + +Bit16s limshort(Bit32s a) { + if (a > 32767) { + a = 32767; + } + else if (a < -32768) { + a = -32768; + } + return (Bit16s)a; +} + +void NukedOPL3::Reset() { + memset(&opl3, 0, sizeof(opl_chip)); + for (Bit8u slotnum = 0; slotnum < 36; slotnum++) { + opl3.slot[slotnum].channel = &opl3.channel[slotnum / 2]; + opl3.slot[slotnum].chip = &opl3; + opl3.slot[slotnum].mod = &opl3.zeromod; + opl3.slot[slotnum].eg_rout = 0x1ff; + opl3.slot[slotnum].eg_out = 0x1ff; + opl3.slot[slotnum].eg_gen = envelope_gen_num_off; + opl3.slot[slotnum].eg_gennext = envelope_gen_num_off; + opl3.slot[slotnum].trem = (Bit8u*)&opl3.zeromod; + } + for (Bit8u channum = 0; channum < 18; channum++) { + opl3.channel[channum].slots[0] = &opl3.slot[2 * channum]; + opl3.channel[channum].slots[1] = &opl3.slot[2 * channum + 1]; + if ((channum % 9) < 3) { + opl3.channel[channum].pair = &opl3.channel[channum + 3]; + } + else if ((channum % 9) < 6) { + opl3.channel[channum].pair = &opl3.channel[channum - 3]; + } + opl3.channel[channum].chip = &opl3; + opl3.channel[channum].out[0] = &opl3.zeromod; + opl3.channel[channum].out[1] = &opl3.zeromod; + opl3.channel[channum].out[2] = &opl3.zeromod; + opl3.channel[channum].out[3] = &opl3.zeromod; + opl3.channel[channum].chtype = ch_2op; + opl3.channel[channum].cha = ~0; + opl3.channel[channum].chb = ~0; + opl3.channel[channum].fcha = 1.0; + opl3.channel[channum].fchb = 1.0; + chan_setupalg(&opl3.channel[channum]); + } + opl3.noise = 0x306600; +} + +void NukedOPL3::WriteReg(int reg, int v) { + v &= 0xff; + reg &= 0x1ff; + Bit8u high = (reg >> 8) & 0x01; + Bit8u regm = reg & 0xff; + switch (regm & 0xf0) { + case 0x00: + if (high) { + switch (regm & 0x0f) { + case 0x04: + chan_set4op(&opl3, v); + break; + case 0x05: + opl3.newm = v & 0x01; + break; + } + } + else { + switch (regm & 0x0f) { + case 0x08: + opl3.nts = (v >> 6) & 0x01; + break; + } + } + break; + case 0x20: + case 0x30: + if (ad_slot[regm & 0x1f] >= 0) { + slot_write20(&opl3.slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x40: + case 0x50: + if (ad_slot[regm & 0x1f] >= 0) { + slot_write40(&opl3.slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x60: + case 0x70: + if (ad_slot[regm & 0x1f] >= 0) { + slot_write60(&opl3.slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0x80: + case 0x90: + if (ad_slot[regm & 0x1f] >= 0) { + slot_write80(&opl3.slot[18 * high + ad_slot[regm & 0x1f]], v);; + } + break; + case 0xe0: + case 0xf0: + if (ad_slot[regm & 0x1f] >= 0) { + slot_writee0(&opl3.slot[18 * high + ad_slot[regm & 0x1f]], v); + } + break; + case 0xa0: + if ((regm & 0x0f) < 9) { + chan_writea0(&opl3.channel[9 * high + (regm & 0x0f)], v); + } + break; + case 0xb0: + if (regm == 0xbd && !high) { + opl3.dam = v >> 7; + opl3.dvb = (v >> 6) & 0x01; + chan_updaterhythm(&opl3, v); + } + else if ((regm & 0x0f) < 9) { + chan_writeb0(&opl3.channel[9 * high + (regm & 0x0f)], v); + if (v & 0x20) { + chan_enable(&opl3.channel[9 * high + (regm & 0x0f)]); + } + else { + chan_disable(&opl3.channel[9 * high + (regm & 0x0f)]); + } + } + break; + case 0xc0: + if ((regm & 0x0f) < 9) { + chan_writec0(&opl3.channel[9 * high + (regm & 0x0f)], v); + } + break; + } +} + +void NukedOPL3::Update(float* sndptr, int numsamples) { + Bit32s outa, outb; + for (Bit32u i = 0; i < (Bit32u)numsamples; i++) { + outa = 0; + outb = 0; + for (Bit8u ii = 0; ii < 36; ii++) { + slot_calgfb(&opl3.slot[ii]); + } + chan_generaterhythm(&opl3); + for (Bit8u ii = 0; ii < 18; ii++) { + chan_generate(&opl3.channel[ii]); + Bit16s accm = 0; + for (Bit8u jj = 0; jj < 4; jj++) { + accm += *opl3.channel[ii].out[jj]; + } + if (FullPan) { + outa += (Bit16s)(accm * opl3.channel[ii].fcha); + outb += (Bit16s)(accm * opl3.channel[ii].fchb); + } + else { + outa += (Bit16s)(accm & opl3.channel[ii].cha); + outb += (Bit16s)(accm & opl3.channel[ii].chb); + } + } + for (Bit8u ii = 0; ii < 36; ii++) { + envelope_calc(&opl3.slot[ii]); + pg_generate(&opl3.slot[ii]); + } + n_generate(&opl3); + opl3.timer++; + if (!(opl3.timer & 0x3f)) { + if (!opl3.tremdir) { + if (opl3.tremtval == 105) { + opl3.tremtval--; + opl3.tremdir = 1; + } + else { + opl3.tremtval++; + } + } + else { + if (opl3.tremtval == 0) { + opl3.tremtval++; + opl3.tremdir = 0; + } + else { + opl3.tremtval--; + } + } + opl3.tremval = (opl3.tremtval >> 2) >> ((1 - opl3.dam) << 1); + } + *sndptr++ += (float)(outa / 10240.0); + *sndptr++ += (float)(outb / 10240.0); + } +} + +void NukedOPL3::SetPanning(int c, float left, float right) { + if (FullPan) { + opl3.channel[c].fcha = left; + opl3.channel[c].fchb = right; + } +} + +NukedOPL3::NukedOPL3(bool stereo) { + FullPan = stereo; + Reset(); +} + +OPLEmul *NukedOPL3Create(bool stereo) { + return new NukedOPL3(stereo); +} \ No newline at end of file diff --git a/src/oplsynth/nukedopl3.h b/src/oplsynth/nukedopl3.h new file mode 100644 index 000000000..ccf37fe14 --- /dev/null +++ b/src/oplsynth/nukedopl3.h @@ -0,0 +1,234 @@ +/* +* Copyright (C) 2013-2014 Nuke.YKT +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + Nuked Yamaha YMF262(aka OPL3) emulator. + Thanks: + MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + Feedback and Rhythm part calculation information. + forums.submarine.org.uk(carbon14, opl3): + Tremolo and phase generator calculation information. + OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + OPL2 ROMs. +*/ + +//version 1.5 + +#include "opl.h" +#include "muslib.h" + +typedef uintptr_t Bitu; +typedef intptr_t Bits; +typedef DWORD Bit32u; +typedef SDWORD Bit32s; +typedef WORD Bit16u; +typedef SWORD Bit16s; +typedef BYTE Bit8u; +typedef SBYTE Bit8s; + +// Channel types + +enum { + ch_2op = 0, + ch_4op = 1, + ch_4op2 = 2, + ch_drum = 3 +}; + +// Envelope key types + +enum { + egk_norm = 0x01, + egk_drum = 0x02 +}; + + +// +// logsin table +// + +static const Bit16u logsinrom[256] = { + 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, 0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365, + 0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd, 0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261, + 0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f, 0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd, + 0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195, 0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166, + 0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c, 0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118, + 0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8, 0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db, + 0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1, 0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9, + 0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094, 0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081, + 0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070, 0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060, + 0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052, 0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045, + 0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039, 0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f, + 0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026, 0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e, + 0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017, 0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011, + 0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c, 0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007, + 0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004, 0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002, + 0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 +}; + +// +// exp table +// + +static const Bit16u exprom[256] = { + 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, 0x016, 0x019, 0x01c, 0x01f, 0x022, 0x025, 0x028, 0x02a, + 0x02d, 0x030, 0x033, 0x036, 0x039, 0x03c, 0x03f, 0x042, 0x045, 0x048, 0x04b, 0x04e, 0x051, 0x054, 0x057, 0x05a, + 0x05d, 0x060, 0x063, 0x066, 0x069, 0x06c, 0x06f, 0x072, 0x075, 0x078, 0x07b, 0x07e, 0x082, 0x085, 0x088, 0x08b, + 0x08e, 0x091, 0x094, 0x098, 0x09b, 0x09e, 0x0a1, 0x0a4, 0x0a8, 0x0ab, 0x0ae, 0x0b1, 0x0b5, 0x0b8, 0x0bb, 0x0be, + 0x0c2, 0x0c5, 0x0c8, 0x0cc, 0x0cf, 0x0d2, 0x0d6, 0x0d9, 0x0dc, 0x0e0, 0x0e3, 0x0e7, 0x0ea, 0x0ed, 0x0f1, 0x0f4, + 0x0f8, 0x0fb, 0x0ff, 0x102, 0x106, 0x109, 0x10c, 0x110, 0x114, 0x117, 0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, + 0x130, 0x134, 0x137, 0x13b, 0x13e, 0x142, 0x146, 0x149, 0x14d, 0x151, 0x154, 0x158, 0x15c, 0x160, 0x163, 0x167, + 0x16b, 0x16f, 0x172, 0x176, 0x17a, 0x17e, 0x181, 0x185, 0x189, 0x18d, 0x191, 0x195, 0x199, 0x19c, 0x1a0, 0x1a4, + 0x1a8, 0x1ac, 0x1b0, 0x1b4, 0x1b8, 0x1bc, 0x1c0, 0x1c4, 0x1c8, 0x1cc, 0x1d0, 0x1d4, 0x1d8, 0x1dc, 0x1e0, 0x1e4, + 0x1e8, 0x1ec, 0x1f0, 0x1f5, 0x1f9, 0x1fd, 0x201, 0x205, 0x209, 0x20e, 0x212, 0x216, 0x21a, 0x21e, 0x223, 0x227, + 0x22b, 0x230, 0x234, 0x238, 0x23c, 0x241, 0x245, 0x249, 0x24e, 0x252, 0x257, 0x25b, 0x25f, 0x264, 0x268, 0x26d, + 0x271, 0x276, 0x27a, 0x27f, 0x283, 0x288, 0x28c, 0x291, 0x295, 0x29a, 0x29e, 0x2a3, 0x2a8, 0x2ac, 0x2b1, 0x2b5, + 0x2ba, 0x2bf, 0x2c4, 0x2c8, 0x2cd, 0x2d2, 0x2d6, 0x2db, 0x2e0, 0x2e5, 0x2e9, 0x2ee, 0x2f3, 0x2f8, 0x2fd, 0x302, + 0x306, 0x30b, 0x310, 0x315, 0x31a, 0x31f, 0x324, 0x329, 0x32e, 0x333, 0x338, 0x33d, 0x342, 0x347, 0x34c, 0x351, + 0x356, 0x35b, 0x360, 0x365, 0x36a, 0x370, 0x375, 0x37a, 0x37f, 0x384, 0x38a, 0x38f, 0x394, 0x399, 0x39f, 0x3a4, + 0x3a9, 0x3ae, 0x3b4, 0x3b9, 0x3bf, 0x3c4, 0x3c9, 0x3cf, 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa +}; + +// +// freq mult table multiplied by 2 +// +// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 +// + +static const Bit8u mt[16] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 }; + +// +// ksl table +// + +static const Bit8u kslrom[16] = { 0, 64, 80, 90, 96, 102, 106, 110, 112, 116, 118, 120, 122, 124, 126, 127 }; + +static const Bit8u kslshift[4] = { 8, 1, 2, 0 }; + +// +// LFO vibrato +// + +static const Bit8u vib_table[8] = { 3, 1, 0, 1, 3, 1, 0, 1 }; +static const Bit8s vibsgn_table[8] = { 1, 1, 1, 1, -1, -1, -1, -1 }; + +// +// envelope generator constants +// + +static const Bit8u eg_incstep[3][4][8] = { + { { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } }, + { { 0, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 0, 1, 0, 1 }, { 1, 1, 0, 1, 1, 1, 0, 1 }, { 1, 1, 1, 1, 1, 1, 0, 1 } }, + { { 1, 1, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 1, 1, 1, 1 }, { 2, 2, 1, 1, 2, 2, 1, 1 }, { 2, 2, 2, 2, 2, 2, 1, 1 } } +}; + +static const Bit8u eg_incdesc[16] = { + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2 +}; + +static const Bit8s eg_incsh[16] = { + 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2 +}; + +// +// address decoding +// + +static const Bit8s ad_slot[0x20] = { 0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + +struct opl_chip; +struct opl_slot; +struct opl_channel; + +struct opl_slot { + opl_channel *channel; + opl_chip *chip; + Bit16s out; + Bit16s fbmod; + Bit16s *mod; + Bit16s prout[2]; + Bit16s eg_rout; + Bit16s eg_out; + Bit8u eg_inc; + Bit8u eg_gen; + Bit8u eg_gennext; + Bit8u eg_rate; + Bit8u eg_ksl; + Bit8u *trem; + Bit8u reg_vib; + Bit8u reg_type; + Bit8u reg_ksr; + Bit8u reg_mult; + Bit8u reg_ksl; + Bit8u reg_tl; + Bit8u reg_ar; + Bit8u reg_dr; + Bit8u reg_sl; + Bit8u reg_rr; + Bit8u reg_wf; + Bit8u key; + Bit32u pg_phase; +}; + +struct opl_channel { + opl_slot *slots[2]; + opl_channel *pair; + opl_chip *chip; + Bit16s *out[4]; + Bit8u chtype; + Bit16u f_num; + Bit8u block; + Bit8u fb; + Bit8u con; + Bit8u alg; + Bit8u ksv; + Bit16u cha, chb; + float fcha, fchb; +}; + +struct opl_chip { + opl_channel channel[18]; + opl_slot slot[36]; + Bit16u timer; + Bit8u newm; + Bit8u nts; + Bit8u dvb; + Bit8u dam; + Bit8u rhy; + Bit8u vibpos; + Bit8u tremval; + Bit8u tremtval; + Bit8u tremdir; + Bit32u noise; + Bit16s zeromod; +}; + + +class NukedOPL3 : public OPLEmul { +private: + opl_chip opl3; + bool FullPan; +public: + void Reset(); + void Update(float* sndptr, int numsamples); + void WriteReg(int reg, int v); + void SetPanning(int c, float left, float right); + + NukedOPL3(bool stereo); +}; diff --git a/src/oplsynth/opl.h b/src/oplsynth/opl.h index 661258a26..2cbb19c32 100644 --- a/src/oplsynth/opl.h +++ b/src/oplsynth/opl.h @@ -20,6 +20,7 @@ public: OPLEmul *YM3812Create(bool stereo); OPLEmul *DBOPLCreate(bool stereo); OPLEmul *JavaOPLCreate(bool stereo); +OPLEmul *NukedOPL3Create(bool stereo); #define OPL_SAMPLE_RATE 49716.0 #define CENTER_PANNING_POWER 0.70710678118 /* [RH] volume at center for EQP */ diff --git a/src/p_3dfloors.cpp b/src/p_3dfloors.cpp index bdeb45d78..d13e46204 100644 --- a/src/p_3dfloors.cpp +++ b/src/p_3dfloors.cpp @@ -120,6 +120,7 @@ static void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flag //Add the floor ffloor = new F3DFloor; + ffloor->top.copied = ffloor->bottom.copied = false; ffloor->top.model = ffloor->bottom.model = ffloor->model = sec2; ffloor->target = sec; ffloor->ceilingclip = ffloor->floorclip = NULL; @@ -420,6 +421,8 @@ void P_Recalculate3DFloors(sector_t * sector) F3DFloor * pick; unsigned pickindex; F3DFloor * clipped=NULL; + F3DFloor * solid=NULL; + fixed_t solid_bottom=0; fixed_t clipped_top; fixed_t clipped_bottom=0; fixed_t maxheight, minheight; @@ -477,6 +480,7 @@ void P_Recalculate3DFloors(sector_t * sector) } oldlist.Delete(pickindex); + fixed_t pick_bottom=pick->bottom.plane->ZatPoint(CenterSpot(sector)); if (pick->flags & FF_THISINSIDE) { @@ -486,10 +490,38 @@ void P_Recalculate3DFloors(sector_t * sector) } else if (pick->flags&(FF_SWIMMABLE|FF_TRANSLUCENT) && pick->flags&FF_EXISTS) { - clipped=pick; - clipped_top=height; - clipped_bottom=pick->bottom.plane->ZatPoint(CenterSpot(sector)); - ffloors.Push(pick); + // We must check if this nonsolid segment gets clipped from the top by another 3D floor + if (solid != NULL && solid_bottom < height) + { + ffloors.Push(pick); + if (solid_bottom < pick_bottom) + { + // this one is fully covered + pick->flags|=FF_CLIPPED; + pick->flags&=~FF_EXISTS; + } + else + { + F3DFloor * dyn=new F3DFloor; + *dyn=*pick; + pick->flags|=FF_CLIPPED; + pick->flags&=~FF_EXISTS; + dyn->flags|=FF_DYNAMIC; + dyn->top.copyPlane(&solid->bottom); + ffloors.Push(dyn); + + clipped = dyn; + clipped_top = solid_bottom; + clipped_bottom = pick_bottom; + } + } + else + { + clipped = pick; + clipped_top = height; + clipped_bottom = pick_bottom; + ffloors.Push(pick); + } } else if (clipped && clipped_bottomflags|=FF_CLIPPED; clipped->flags&=~FF_EXISTS; dyn->flags|=FF_DYNAMIC; - dyn->bottom=pick->top; + dyn->bottom.copyPlane(&pick->top); ffloors.Push(dyn); ffloors.Push(pick); - fixed_t pick_bottom=pick->bottom.plane->ZatPoint(CenterSpot(sector)); - if (pick_bottom<=clipped_bottom) { clipped=NULL; @@ -515,14 +545,25 @@ void P_Recalculate3DFloors(sector_t * sector) dyn=new F3DFloor; *dyn=*clipped; dyn->flags|=FF_DYNAMIC|FF_EXISTS; - dyn->top=pick->bottom; + dyn->top.copyPlane(&pick->bottom); ffloors.Push(dyn); + clipped = dyn; + clipped_top = pick_bottom; } + solid = pick; + solid_bottom = pick_bottom; } else { - clipped=NULL; + clipped = NULL; + if (solid == NULL || solid_bottom > pick_bottom) + { + // only if this one is lower + solid = pick; + solid_bottom = pick_bottom; + } ffloors.Push(pick); + } } @@ -910,3 +951,27 @@ int P_Find3DFloor(sector_t * sec, fixed_t x, fixed_t y, fixed_t z, bool above, b } #endif + +#include "c_dispatch.h" + + +CCMD (dump3df) +{ + if (argv.argc() > 1) + { + int sec = strtol(argv[1], NULL, 10); + sector_t *sector = §ors[sec]; + TArray & ffloors=sector->e->XFloor.ffloors; + + for (unsigned int i = 0; i < ffloors.Size(); i++) + { + fixed_t height=ffloors[i]->top.plane->ZatPoint(CenterSpot(sector)); + fixed_t bheight=ffloors[i]->bottom.plane->ZatPoint(CenterSpot(sector)); + + Printf("FFloor %d @ top = %f (model = %d), bottom = %f (model = %d), flags = %B, alpha = %d %s %s\n", + i, height / 65536., ffloors[i]->top.model->sectornum, + bheight / 65536., ffloors[i]->bottom.model->sectornum, + ffloors[i]->flags, ffloors[i]->alpha, (ffloors[i]->flags&FF_EXISTS)? "Exists":"", (ffloors[i]->flags&FF_DYNAMIC)? "Dynamic":""); + } + } +} diff --git a/src/p_3dfloors.h b/src/p_3dfloors.h index d0bca5e8f..8d42560af 100644 --- a/src/p_3dfloors.h +++ b/src/p_3dfloors.h @@ -77,6 +77,13 @@ struct F3DFloor sector_t * model; int isceiling; int vindex; + bool copied; + + void copyPlane(planeref * other) + { + *this = *other; + copied = true; + } }; planeref bottom; diff --git a/src/p_3dmidtex.cpp b/src/p_3dmidtex.cpp index ccb6f0359..ac6d7aca7 100644 --- a/src/p_3dmidtex.cpp +++ b/src/p_3dmidtex.cpp @@ -258,6 +258,13 @@ bool P_GetMidTexturePosition(const line_t *line, int sideno, fixed_t *ptextop, f bool P_LineOpening_3dMidtex(AActor *thing, const line_t *linedef, FLineOpening &open, bool restrict) { + // [TP] Impassible-like 3dmidtextures do not block missiles + if ((linedef->flags & ML_3DMIDTEX_IMPASS) + && (thing->flags & MF_MISSILE || thing->BounceFlags & BOUNCE_MBF)) + { + return false; + } + fixed_t tt, tb; open.abovemidtex = false; diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 737721563..043f33cdc 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -120,6 +120,17 @@ FRandom pr_acs ("ACS"); #define SDF_ABSANGLE 1 #define SDF_PERMANENT 2 +// GetArmorInfo +enum +{ + ARMORINFO_CLASSNAME, + ARMORINFO_SAVEAMOUNT, + ARMORINFO_SAVEPERCENT, + ARMORINFO_MAXABSORB, + ARMORINFO_MAXFULLABSORB, + ARMORINFO_ACTUALSAVEAMOUNT, +}; + struct CallReturn { CallReturn(int pc, ScriptFunction *func, FBehavior *module, SDWORD *locals, ACSLocalArrays *arrays, bool discard, unsigned int runaway) @@ -1570,20 +1581,28 @@ void FBehavior::StaticSerializeModuleStates (FArchive &arc) for (modnum = 0; modnum < StaticModules.Size(); ++modnum) { FBehavior *module = StaticModules[modnum]; + int ModSize = module->GetDataSize(); if (arc.IsStoring()) { arc.WriteString (module->ModuleName); + if (SaveVersion >= 4516) arc << ModSize; } else { char *modname = NULL; arc << modname; + if (SaveVersion >= 4516) arc << ModSize; if (stricmp (modname, module->ModuleName) != 0) { delete[] modname; I_Error ("Level was saved with a different set of ACS modules."); } + else if (ModSize != module->GetDataSize()) + { + delete[] modname; + I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize); + } delete[] modname; } module->SerializeVars (arc); @@ -1862,7 +1881,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) funcm->HasReturnValue = funcf->HasReturnValue; funcm->ImportNum = funcf->ImportNum; funcm->LocalCount = funcf->LocalCount; - funcm->Address = funcf->Address; + funcm->Address = LittleLong(funcf->Address); } } @@ -2047,7 +2066,7 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) const char *const parse = (char *)&chunk[2]; DWORD i; - for (i = 0; i < chunk[1]; ) + for (i = 0; i < LittleLong(chunk[1]); ) { if (parse[i]) { @@ -2295,6 +2314,12 @@ void FBehavior::LoadScriptsDirectory () default: break; } + +// [EP] Clang 3.5.0 optimizer miscompiles this function and causes random +// crashes in the program. I hope that Clang 3.5.x will fix this. +#if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ >= 5 + asm("" : "+g" (NumScripts)); +#endif for (i = 0; i < NumScripts; ++i) { Scripts[i].Flags = 0; @@ -2334,7 +2359,7 @@ void FBehavior::LoadScriptsDirectory () scripts.b = FindChunk (MAKE_ID('S','F','L','G')); if (scripts.dw != NULL) { - max = scripts.dw[1] / 4; + max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { @@ -2350,7 +2375,7 @@ void FBehavior::LoadScriptsDirectory () scripts.b = FindChunk (MAKE_ID('S','V','C','T')); if (scripts.dw != NULL) { - max = scripts.dw[1] / 4; + max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { @@ -2664,7 +2689,7 @@ BYTE *FBehavior::FindChunk (DWORD id) const { return chunk; } - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; } return NULL; } @@ -2672,14 +2697,14 @@ BYTE *FBehavior::FindChunk (DWORD id) const BYTE *FBehavior::NextChunk (BYTE *chunk) const { DWORD id = *(DWORD *)chunk; - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; while (chunk != NULL && chunk < Data + DataSize) { if (((DWORD *)chunk)[0] == id) { return chunk; } - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; } return NULL; } @@ -2864,9 +2889,57 @@ DACSThinker::~DACSThinker () void DACSThinker::Serialize (FArchive &arc) { int scriptnum; + int scriptcount = 0; Super::Serialize (arc); - arc << Scripts << LastScript; + if (SaveVersion < 4515) + arc << Scripts << LastScript; + else + { + if (arc.IsStoring()) + { + DLevelScript *script; + script = Scripts; + while (script) + { + scriptcount++; + + // We want to store this list backwards, so we can't loose the last pointer + if (script->next == NULL) + break; + script = script->next; + } + arc << scriptcount; + + while (script) + { + arc << script; + script = script->prev; + } + } + else + { + // We are running through this list backwards, so the next entry is the last processed + DLevelScript *next = NULL; + arc << scriptcount; + Scripts = NULL; + LastScript = NULL; + for (int i = 0; i < scriptcount; i++) + { + arc << Scripts; + + Scripts->next = next; + Scripts->prev = NULL; + if (next != NULL) + next->prev = Scripts; + + next = Scripts; + + if (i == 0) + LastScript = Scripts; + } + } + } if (arc.IsStoring ()) { ScriptMap::Iterator it(RunningScripts); @@ -2952,7 +3025,8 @@ void DLevelScript::Serialize (FArchive &arc) DWORD i; Super::Serialize (arc); - arc << next << prev; + if (SaveVersion < 4515) + arc << next << prev; P_SerializeACSScriptNumber(arc, script, false); @@ -3606,6 +3680,7 @@ enum APROP_AttackZOffset = 40, APROP_StencilColor = 41, APROP_Friction = 42, + APROP_DamageMultiplier=43, }; // These are needed for ACS's APROP_RenderStyle @@ -3735,16 +3810,16 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) break; case APROP_Friendly: + if (actor->CountsAsKill()) level.total_monsters--; if (value) { - if (actor->CountsAsKill()) level.total_monsters--; actor->flags |= MF_FRIENDLY; } else { actor->flags &= ~MF_FRIENDLY; - if (actor->CountsAsKill()) level.total_monsters++; } + if (actor->CountsAsKill()) level.total_monsters++; break; @@ -3795,6 +3870,10 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) actor->DamageFactor = value; break; + case APROP_DamageMultiplier: + actor->DamageMultiply = value; + break; + case APROP_MasterTID: AActor *other; other = SingleActorFromTID (value, NULL); @@ -3825,6 +3904,10 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) actor->reactiontime = value; break; + case APROP_MeleeRange: + actor->meleerange = value; + break; + case APROP_ViewHeight: if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn))) static_cast(actor)->ViewHeight = value; @@ -3862,6 +3945,7 @@ int DLevelScript::GetActorProperty (int tid, int property, const SDWORD *stack, case APROP_Speed: return actor->Speed; case APROP_Damage: return actor->GetMissileDamage(0,1); case APROP_DamageFactor:return actor->DamageFactor; + case APROP_DamageMultiplier: return actor->DamageMultiply; case APROP_Alpha: return actor->alpha; case APROP_RenderStyle: for (int style = STYLE_None; style < STYLE_Count; ++style) { // Check for a legacy render style that matches. @@ -4206,7 +4290,7 @@ int DLevelScript::DoClassifyActor(int tid) { classify |= ACTOR_VOODOODOLL; } - if (actor->player->isbot) + if (actor->player->Bot != NULL) { classify |= ACTOR_BOT; } @@ -4348,6 +4432,13 @@ enum EACSFunctions ACSF_GetActorPowerupTics, ACSF_ChangeActorAngle, ACSF_ChangeActorPitch, // 80 + ACSF_GetArmorInfo, + ACSF_DropInventory, + ACSF_PickActor, + ACSF_IsPointerEqual, + ACSF_CanRaiseActor, + ACSF_SetActorTeleFog, // 86 + ACSF_SwapActorTeleFog, /* Zandronum's - these must be skipped when we reach 99! -100:ResetMap(0), @@ -4652,6 +4743,82 @@ static void SetActorPitch(AActor *activator, int tid, int angle, bool interpolat } } +static void SetActorTeleFog(AActor *activator, int tid, FName telefogsrc, FName telefogdest) +{ + //Simply put, if it doesn't exist, it won't change. One can use "" in this scenario. + PClassActor *check; + if (tid == 0) + { + if (activator != NULL) + { + check = PClass::FindActor(telefogsrc); + if (check == NULL || !stricmp(telefogsrc, "none") || !stricmp(telefogsrc, "null")) + activator->TeleFogSourceType = NULL; + else + activator->TeleFogSourceType = check; + + check = PClass::FindActor(telefogdest); + if (check == NULL || !stricmp(telefogdest, "none") || !stricmp(telefogdest, "null")) + activator->TeleFogDestType = NULL; + else + activator->TeleFogDestType = check; + } + } + else + { + FActorIterator iterator(tid); + AActor *actor; + + while ((actor = iterator.Next())) + { + check = PClass::FindActor(telefogsrc); + if (check == NULL || !stricmp(telefogsrc, "none") || !stricmp(telefogsrc, "null")) + actor->TeleFogSourceType = NULL; + else + actor->TeleFogSourceType = check; + + check = PClass::FindActor(telefogdest); + if (check == NULL || !stricmp(telefogdest, "none") || !stricmp(telefogdest, "null")) + actor->TeleFogDestType = NULL; + else + actor->TeleFogDestType = check; + } + } +} + +static int SwapActorTeleFog(AActor *activator, int tid) +{ + int count = 0; + if (tid == 0) + { + if ((activator == NULL) || (activator->TeleFogSourceType = activator->TeleFogDestType)) + return 0; //Does nothing if they're the same. + else + { + PClassActor *temp = activator->TeleFogSourceType; + activator->TeleFogSourceType = activator->TeleFogDestType; + activator->TeleFogDestType = temp; + return 1; + } + } + else + { + FActorIterator iterator(tid); + AActor *actor; + + while ((actor = iterator.Next())) + { + if (actor->TeleFogSourceType == actor->TeleFogDestType) + continue; //They're the same. Save the effort. + PClassActor *temp = actor->TeleFogSourceType; + actor->TeleFogSourceType = actor->TeleFogDestType; + actor->TeleFogDestType = temp; + count++; + } + } + return count; +} + int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args, const SDWORD *stack, int stackdepth) @@ -4814,6 +4981,41 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args, const return 0; } + case ACSF_GetArmorInfo: + { + if (activator == NULL || activator->player == NULL) return 0; + + ABasicArmor * equippedarmor = (ABasicArmor *) activator->FindInventory(NAME_BasicArmor); + + if (equippedarmor && equippedarmor->Amount != 0) + { + switch(args[0]) + { + case ARMORINFO_CLASSNAME: + return GlobalACSStrings.AddString(equippedarmor->ArmorType.GetChars(), stack, stackdepth); + + case ARMORINFO_SAVEAMOUNT: + return equippedarmor->MaxAmount; + + case ARMORINFO_SAVEPERCENT: + return equippedarmor->SavePercent; + + case ARMORINFO_MAXABSORB: + return equippedarmor->MaxAbsorb; + + case ARMORINFO_MAXFULLABSORB: + return equippedarmor->MaxFullAbsorb; + + case ARMORINFO_ACTUALSAVEAMOUNT: + return equippedarmor->ActualSaveAmount; + + default: + return 0; + } + } + return args[0] == ARMORINFO_CLASSNAME ? GlobalACSStrings.AddString("None", stack, stackdepth) : 0; + } + case ACSF_SpawnSpotForced: return DoSpawnSpot(args[0], args[1], args[2], args[3], true); @@ -5430,6 +5632,42 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) break; } + case ACSF_DropInventory: + { + const char *type = FBehavior::StaticLookupString(args[1]); + AInventory *inv; + + if (type != NULL) + { + if (args[0] == 0) + { + if (activator != NULL) + { + inv = activator->FindInventory(type); + if (inv) + { + activator->DropInventory(inv); + } + } + } + else + { + FActorIterator it(args[0]); + AActor *actor; + + while ((actor = it.Next()) != NULL) + { + inv = actor->FindInventory(type); + if (inv) + { + actor->DropInventory(inv); + } + } + } + } + break; + } + case ACSF_CheckFlag: { AActor *actor = SingleActorFromTID(args[0], activator); @@ -5494,6 +5732,97 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) SetActorPitch(activator, args[0], args[1], argCount > 2 ? !!args[2] : false); } break; + case ACSF_SetActorTeleFog: + if (argCount >= 3) + { + SetActorTeleFog(activator, args[0], FBehavior::StaticLookupString(args[1]), FBehavior::StaticLookupString(args[2])); + } + break; + case ACSF_SwapActorTeleFog: + if (argCount >= 1) + { + return SwapActorTeleFog(activator, args[0]); + } + break; + case ACSF_PickActor: + if (argCount >= 5) + { + actor = SingleActorFromTID(args[0], activator); + if (actor == NULL) + { + return 0; + } + + DWORD actorMask = MF_SHOOTABLE; + if (argCount >= 6) { + actorMask = args[5]; + } + + DWORD wallMask = ML_BLOCKEVERYTHING | ML_BLOCKHITSCAN; + if (argCount >= 7) { + wallMask = args[6]; + } + + bool forceTID = 0; + if (argCount >= 8) + { + if (args[7] != 0) + forceTID = 1; + } + + AActor* pickedActor = P_LinePickActor(actor, args[1] << 16, args[3], args[2] << 16, actorMask, wallMask); + if (pickedActor == NULL) { + return 0; + } + + if (!(forceTID) && (args[4] == 0) && (pickedActor->tid == 0)) + return 0; + + if ((pickedActor->tid == 0) || (forceTID)) + { + pickedActor->RemoveFromHash(); + pickedActor->tid = args[4]; + pickedActor->AddToHash(); + } + return 1; + } + break; + + case ACSF_IsPointerEqual: + { + int tid1 = 0, tid2 = 0; + switch (argCount) + { + case 4: tid2 = args[3]; + case 3: tid1 = args[2]; + } + + actor = SingleActorFromTID(tid1, activator); + AActor * actor2 = tid2 == tid1 ? actor : SingleActorFromTID(tid2, activator); + + return COPY_AAPTR(actor, args[0]) == COPY_AAPTR(actor2, args[1]); + } + break; + + case ACSF_CanRaiseActor: + if (argCount >= 1) { + if (args[0] == 0) { + actor = SingleActorFromTID(args[0], activator); + if (actor != NULL) { + return P_Thing_CanRaise(actor); + } + } + + FActorIterator iterator(args[0]); + bool canraiseall = false; + while ((actor = iterator.Next())) + { + canraiseall = !P_Thing_CanRaise(actor) | canraiseall; + } + + return !canraiseall; + } + break; default: break; @@ -7111,7 +7440,7 @@ scriptwait: while (min <= max) { int mid = (min + max) / 2; - SDWORD caseval = pc[mid*2]; + SDWORD caseval = LittleLong(pc[mid*2]); if (caseval == STACK(1)) { pc = activeBehavior->Ofs2PC (LittleLong(pc[mid*2+1])); @@ -7477,13 +7806,6 @@ scriptwait: AddToConsole (-1, consolecolor); AddToConsole (-1, work); AddToConsole (-1, bar); - if (Logfile) - { - fputs (logbar, Logfile); - fputs (work, Logfile); - fputs (logbar, Logfile); - fflush (Logfile); - } } } } @@ -8459,7 +8781,7 @@ scriptwait: } else { - STACK(1) = players[STACK(1)].isbot; + STACK(1) = (players[STACK(1)].Bot != NULL); } break; diff --git a/src/p_acs.h b/src/p_acs.h index 02544e367..88016f0db 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -308,6 +308,7 @@ public: int GetScriptIndex (const ScriptPtr *ptr) const { ptrdiff_t index = ptr - Scripts; return index >= NumScripts ? -1 : (int)index; } ScriptPtr *GetScriptPtr(int index) const { return index >= 0 && index < NumScripts ? &Scripts[index] : NULL; } int GetLumpNum() const { return LumpNum; } + int GetDataSize() const { return DataSize; } const char *GetModuleName() const { return ModuleName; } ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; } ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); } diff --git a/src/p_ceiling.cpp b/src/p_ceiling.cpp index 426bdf507..d0c54761a 100644 --- a/src/p_ceiling.cpp +++ b/src/p_ceiling.cpp @@ -133,7 +133,6 @@ void DCeiling::Tick () switch (m_Type) { case ceilCrushAndRaise: - case ceilCrushAndRaiseDist: m_Direction = -1; m_Speed = m_Speed1; if (!SN_IsMakingLoopingSound (m_Sector)) @@ -165,7 +164,6 @@ void DCeiling::Tick () switch (m_Type) { case ceilCrushAndRaise: - case ceilCrushAndRaiseDist: case ceilCrushRaiseAndStay: m_Speed = m_Speed2; m_Direction = 1; @@ -195,7 +193,6 @@ void DCeiling::Tick () switch (m_Type) { case ceilCrushAndRaise: - case ceilCrushAndRaiseDist: case ceilLowerAndCrush: case ceilLowerAndCrushDist: if (m_Speed1 == FRACUNIT && m_Speed2 == FRACUNIT) @@ -257,7 +254,6 @@ DCeiling *DCeiling::Create(sector_t *sec, DCeiling::ECeiling type, line_t *line, switch (type) { case ceilCrushAndRaise: - case ceilCrushAndRaiseDist: case ceilCrushRaiseAndStay: ceiling->m_TopHeight = sec->ceilingplane.d; case ceilLowerAndCrush: @@ -267,7 +263,7 @@ DCeiling *DCeiling::Create(sector_t *sec, DCeiling::ECeiling type, line_t *line, { targheight += 8*FRACUNIT; } - else if (type == ceilLowerAndCrushDist || type == ceilCrushAndRaiseDist) + else if (type == ceilCrushAndRaise) { targheight += height; } @@ -509,7 +505,7 @@ bool EV_DoCeiling (DCeiling::ECeiling type, line_t *line, // Reactivate in-stasis ceilings...for certain types. // This restarts a crusher after it has been stopped - if (type == DCeiling::ceilCrushAndRaise || type == DCeiling::ceilCrushAndRaiseDist) + if (type == DCeiling::ceilCrushAndRaise) { P_ActivateInStasisCeiling (tag); } diff --git a/src/p_doors.cpp b/src/p_doors.cpp index 7c6952d7f..844f23af9 100644 --- a/src/p_doors.cpp +++ b/src/p_doors.cpp @@ -460,7 +460,7 @@ bool EV_DoDoor (DDoor::EVlDoor type, line_t *line, AActor *thing, // run into them (otherwise opening them would be // a real pain). { - if (!thing->player || thing->player->isbot) + if (!thing->player || thing->player->Bot != NULL) return false; // JDC: bad guys never close doors //Added by MC: Neither do bots. diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index ee4e2e124..d0e1c2859 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -1592,7 +1592,7 @@ bool P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) } #endif // [SP] If you don't see any enemies in deathmatch, look for players (but only when friend to a specific player.) - if (actor->FriendPlayer == 0 && (!teamplay || actor->DesignatedTeam == TEAM_NONE)) return result; + if (actor->FriendPlayer == 0 && (!teamplay || actor->GetTeam() == TEAM_NONE)) return result; if (result || !deathmatch) return true; @@ -1600,7 +1600,10 @@ bool P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) if (!(gameinfo.gametype & (GAME_DoomStrifeChex)) && !multiplayer && - players[0].health <= 0) + players[0].health <= 0 && + actor->goal == NULL && + gamestate != GS_TITLELEVEL + ) { // Single player game and player is dead; look for monsters return P_LookForMonsters (actor); } @@ -3205,7 +3208,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Die) PARAM_ACTION_PROLOGUE; PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } - P_DamageMobj (self, NULL, NULL, self->health, damagetype, DMG_FORCED); + if (self->flags & MF_MISSILE) + P_ExplodeMissile(self, NULL, NULL); + else + P_DamageMobj(self, NULL, NULL, self->health, damagetype, DMG_FORCED); return 0; } @@ -3322,13 +3328,13 @@ void A_BossDeath(AActor *self) { if (type == NAME_Fatso) { - EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, -1, 0, false); return; } if (type == NAME_Arachnotron) { - EV_DoFloor (DFloor::floorRaiseByTexture, NULL, 667, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorRaiseByTexture, NULL, 667, FRACUNIT, 0, -1, 0, false); return; } } @@ -3337,11 +3343,11 @@ void A_BossDeath(AActor *self) switch (level.flags & LEVEL_SPECACTIONSMASK) { case LEVEL_SPECLOWERFLOOR: - EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, -1, 0, false); return; case LEVEL_SPECLOWERFLOORTOHIGHEST: - EV_DoFloor (DFloor::floorLowerToHighest, NULL, 666, FRACUNIT, 0, 0, 0, false); + EV_DoFloor (DFloor::floorLowerToHighest, NULL, 666, FRACUNIT, 0, -1, 0, false); return; case LEVEL_SPECOPENDOOR: diff --git a/src/p_floor.cpp b/src/p_floor.cpp index f63087619..9a5a6eaf8 100644 --- a/src/p_floor.cpp +++ b/src/p_floor.cpp @@ -320,7 +320,7 @@ manual_floor: rtn = true; floor = new DFloor (sec); floor->m_Type = floortype; - floor->m_Crush = -1; + floor->m_Crush = crush; floor->m_Hexencrush = hexencrush; floor->m_Speed = speed; floor->m_ResetCount = 0; // [RH] @@ -374,7 +374,6 @@ manual_floor: break; case DFloor::floorRaiseAndCrushDoom: - floor->m_Crush = crush; case DFloor::floorRaiseToLowestCeiling: floor->m_Direction = 1; newheight = sec->FindLowestCeilingSurrounding (&spot); @@ -406,7 +405,6 @@ manual_floor: break; case DFloor::floorRaiseAndCrush: - floor->m_Crush = crush; floor->m_Direction = 1; newheight = sec->FindLowestCeilingPoint (&spot) - 8*FRACUNIT; floor->m_FloorDestDist = sec->floorplane.PointToDist (spot, newheight); diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 437d598e1..24cdb0c0b 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -100,10 +100,10 @@ void P_TouchSpecialThing (AActor *special, AActor *toucher) return; //Added by MC: Finished with this destination. - if (toucher->player != NULL && toucher->player->isbot && special == toucher->player->dest) + if (toucher->player != NULL && toucher->player->Bot != NULL && special == toucher->player->Bot->dest) { - toucher->player->prev = toucher->player->dest; - toucher->player->dest = NULL; + toucher->player->Bot->prev = toucher->player->Bot->dest; + toucher->player->Bot->dest = NULL; } special->Touch (toucher); @@ -588,7 +588,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) // even those caused by other monsters players[0].killcount++; } - + if (player) { // [RH] Death messages @@ -601,19 +601,19 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) player->respawn_time = level.time + TICRATE; //Added by MC: Respawn bots - if (bglobal.botnum && consoleplayer == Net_Arbitrator && !demoplayback) + if (bglobal.botnum && !demoplayback) { - if (player->isbot) - player->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; + if (player->Bot != NULL) + player->Bot->t_respawn = (pr_botrespawn()%15)+((bglobal.botnum-1)*2)+TICRATE+1; //Added by MC: Discard enemies. for (int i = 0; i < MAXPLAYERS; i++) { - if (players[i].isbot && this == players[i].enemy) + if (players[i].Bot != NULL && this == players[i].Bot->enemy) { - if (players[i].dest == players[i].enemy) - players[i].dest = NULL; - players[i].enemy = NULL; + if (players[i].Bot->dest == players[i].Bot->enemy) + players[i].Bot->dest = NULL; + players[i].Bot->enemy = NULL; } } @@ -920,6 +920,11 @@ static inline bool MustForcePain(AActor *target, AActor *inflictor) (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)); } +static inline bool isFakePain(AActor *target, AActor *inflictor) +{ + return ((target->flags7 & MF7_ALLOWPAIN) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN))); +} + // Returns the amount of damage actually inflicted upon the target, or -1 if // the damage was cancelled. @@ -933,12 +938,22 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FState * woundstate = NULL; PainChanceList * pc = NULL; bool justhit = false; - + bool plrDontThrust = false; + bool invulpain = false; + bool fakedPain = false; + bool forcedPain = false; + int fakeDamage = 0; + int holdDamage = 0; + if (target == NULL || !((target->flags & MF_SHOOTABLE) || (target->flags6 & MF6_VULNERABLE))) { // Shouldn't happen return -1; } + //Rather than unnecessarily call the function over and over again, let's be a little more efficient. + fakedPain = (isFakePain(target, inflictor)); + forcedPain = (MustForcePain(target, inflictor)); + // Spectral targets only take damage from spectral projectiles. if (target->flags4 & MF4_SPECTRAL && damage < TELEFRAG_DAMAGE) { @@ -961,13 +976,20 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } return -1; } - if ((target->flags2 & MF2_INVULNERABLE) && damage < TELEFRAG_DAMAGE && !(flags & DMG_FORCED)) + if ((target->flags2 & MF2_INVULNERABLE) && (damage < TELEFRAG_DAMAGE) && (!(flags & DMG_FORCED))) { // actor is invulnerable if (target->player == NULL) { if (inflictor == NULL || (!(inflictor->flags3 & MF3_FOILINVUL) && !(flags & DMG_FOILINVUL))) { - return -1; + if (fakedPain) + { + invulpain = true; //This returns -1 later. + fakeDamage = damage; + goto fakepain; //The label is above the massive pile of checks. + } + else + return -1; } } else @@ -975,11 +997,21 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // Players are optionally excluded from getting thrust by damage. if (static_cast(target)->PlayerFlags & PPF_NOTHRUSTWHENINVUL) { - return -1; + if (fakedPain) + plrDontThrust = 1; + else + return -1; } } } + if ((fakedPain) && (damage < TELEFRAG_DAMAGE)) + { + //Intentionally do not jump to fakepain because the damage hasn't been dished out yet. + //Once it's dished out, THEN we can disregard damage factors affecting pain chances. + fakeDamage = damage; + } + if (inflictor != NULL) { if (inflictor->flags5 & MF5_PIERCEARMOR) @@ -1005,6 +1037,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // Invulnerable, and won't wake up return -1; } + player = target->player; if (player && damage > 1 && damage < TELEFRAG_DAMAGE) { @@ -1027,38 +1060,49 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, return -1; } } + if (damage > 0) + damage = inflictor->DoSpecialDamage (target, damage, mod); - damage = inflictor->DoSpecialDamage (target, damage, mod); - if (damage == -1) + if ((damage == -1) && (target->player == NULL)) //This isn't meant for the player. { + if (fakedPain) //Hold off ending the function before we can deal the pain chances. + goto fakepain; return -1; } } // Handle active damage modifiers (e.g. PowerDamage) - if (source != NULL && source->Inventory != NULL) + if (source != NULL) { int olddam = damage; - source->Inventory->ModifyDamage(olddam, mod, damage, false); - if (olddam != damage && damage <= 0) + + if (source->Inventory != NULL) + { + source->Inventory->ModifyDamage(olddam, mod, damage, false); + } + damage = FixedMul(damage, source->DamageMultiply); + + if (((source->flags7 & MF7_CAUSEPAIN) && (fakeDamage <= 0)) || (olddam != damage && damage <= 0)) { // Still allow FORCEPAIN - if (MustForcePain(target, inflictor)) - { + if (forcedPain) goto dopain; - } + else if (fakedPain) + goto fakepain; + return -1; } } - // Handle passive damage modifiers (e.g. PowerProtection) - if (target->Inventory != NULL) + // Handle passive damage modifiers (e.g. PowerProtection), provided they are not afflicted with protection penetrating powers. + if ((target->Inventory != NULL) && !(flags & DMG_NO_PROTECT)) { int olddam = damage; target->Inventory->ModifyDamage(olddam, mod, damage, true); - if (olddam != damage && damage <= 0) - { // Still allow FORCEPAIN - if (MustForcePain(target, inflictor)) - { + if ((olddam != damage && damage <= 0) && target->player == NULL) + { // Still allow FORCEPAIN and make sure we're still passing along fake damage to hit enemies for their pain states. + if (forcedPain) goto dopain; - } + else if (fakedPain) + goto fakepain; + return -1; } } @@ -1066,32 +1110,37 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (!(flags & DMG_NO_FACTOR)) { damage = FixedMul(damage, target->DamageFactor); - if (damage >= 0) + if (damage > 0) { damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, mod, target->GetClass()->DamageFactors); } - if (damage <= 0) + if (damage <= 0 && target->player == NULL) { // Still allow FORCEPAIN - if (MustForcePain(target, inflictor)) - { + if (forcedPain) goto dopain; - } + else if (fakedPain) + goto fakepain; + return -1; } } - - damage = target->TakeSpecialDamage (inflictor, source, damage, mod); + if (damage > 0) + damage = target->TakeSpecialDamage (inflictor, source, damage, mod); } - if (damage == -1) + if (damage == -1 && target->player == NULL) //Make sure it's not a player, the pain has yet to be processed with cheats. { + if (fakedPain) + goto fakepain; + return -1; } // Push the target unless the source's weapon's kickback is 0. // (i.e. Gauntlets/Chainsaw) - if (inflictor && inflictor != target // [RH] Not if hurting own self + if (!(plrDontThrust) && inflictor && inflictor != target // [RH] Not if hurting own self && !(target->flags & MF_NOCLIP) && !(inflictor->flags2 & MF2_NODMGTHRUST) && !(flags & DMG_THRUSTLESS) + && !(target->flags7 & MF7_DONTTHRUST) && (source == NULL || source->player == NULL || !(source->flags2 & MF2_NODMGTHRUST))) { int kickback; @@ -1128,11 +1177,10 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, { fltthrust = clamp((damage * 0.125 * kickback) / target->Mass, 0., fltthrust); } - thrust = FLOAT2FIXED(fltthrust); - - // Don't apply ultra-small damage thrust - if (thrust < FRACUNIT/100) thrust = 0; + // Don't apply ultra-small damage thrust. + if (thrust < FRACUNIT / 100) + thrust = 0; // make fall forwards sometimes if ((damage < 40) && (damage > target->health) @@ -1141,8 +1189,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // [RH] But only if not too fast and not flying && thrust < 10*FRACUNIT && !(target->flags & MF_NOGRAVITY) - && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL)) - ) + && (inflictor == NULL || !(inflictor->flags5 & MF5_NOFORWARDFALL))) { ang += ANG180; thrust *= 4; @@ -1188,11 +1235,10 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // if (player) { - - //Added by MC: Lets bots look allround for enemies if they survive an ambush. - if (player->isbot) + //Added by MC: Lets bots look allround for enemies if they survive an ambush. + if (player->Bot != NULL) { - player->allround = true; + player->Bot->allround = true; } // end of game hell hack @@ -1205,22 +1251,41 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (!(flags & DMG_FORCED)) { // check the real player, not a voodoo doll here for invulnerability effects - if (damage < TELEFRAG_DAMAGE && ((player->mo->flags2 & MF2_INVULNERABLE) || - (player->cheats & CF_GODMODE))) + if ((damage < TELEFRAG_DAMAGE && ((player->mo->flags2 & MF2_INVULNERABLE) || + (player->cheats & CF_GODMODE))) || + (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NODAMAGE)) + //Absolutely no hurting if NODAMAGE is involved. Same for GODMODE2. { // player is invulnerable, so don't hurt him - return -1; + //Make sure no godmodes and NOPAIN flags are found first. + //Then, check to see if the player has NODAMAGE or ALLOWPAIN, or inflictor has CAUSEPAIN. + if ((player->cheats & CF_GODMODE) || (player->cheats & CF_GODMODE2) || (player->mo->flags5 & MF5_NOPAIN)) + return -1; + else if ((((player->mo->flags7 & MF7_ALLOWPAIN) || (player->mo->flags5 & MF5_NODAMAGE)) || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) + { + invulpain = true; + fakeDamage = damage; + goto fakepain; + } + else + return -1; } if (!(flags & DMG_NO_ARMOR) && player->mo->Inventory != NULL) { int newdam = damage; - player->mo->Inventory->AbsorbDamage (damage, mod, newdam); - damage = newdam; + player->mo->Inventory->AbsorbDamage(damage, mod, newdam); + if (damage < TELEFRAG_DAMAGE) + { + // if we are telefragging don't let the damage value go below that magic value. Some further checks would fail otherwise. + damage = newdam; + } + if (damage <= 0) { // If MF6_FORCEPAIN is set, make the player enter the pain state. if (!(target->flags5 & MF5_NOPAIN) && inflictor != NULL && - (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS)) + (inflictor->flags6 & MF6_FORCEPAIN) && !(inflictor->flags5 & MF5_PAINLESS) + && (!(player->mo->flags2 & MF2_INVULNERABLE)) && (!(player->cheats & CF_GODMODE)) && (!(player->cheats & CF_GODMODE2))) { goto dopain; } @@ -1228,7 +1293,7 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } } - if (damage >= player->health + if (damage >= player->health && damage < TELEFRAG_DAMAGE && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) && !player->morphTics) { // Try to use some inventory health @@ -1250,9 +1315,9 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, // This does not save the player if damage >= TELEFRAG_DAMAGE, still need to // telefrag him right? ;) (Unfortunately the damage is "absorbed" by armor, // but telefragging should still do enough damage to kill the player) - if ((player->cheats & CF_BUDDHA) && damage < TELEFRAG_DAMAGE - // Ignore players that are already dead. - && player->playerstate != PST_DEAD) + // Ignore players that are already dead. + // [MC]Buddha2 absorbs telefrag damage, and anything else thrown their way. + if ((player->cheats & CF_BUDDHA2) || (((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && (damage < TELEFRAG_DAMAGE)) && (player->playerstate != PST_DEAD)) { // If this is a voodoo doll we need to handle the real player as well. player->mo->health = target->health = player->health = 1; @@ -1285,7 +1350,10 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, damage = newdam; if (damage <= 0) { - return damage; + if (fakedPain) + goto fakepain; + else + return damage; } } @@ -1312,43 +1380,53 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, if (target->health <= 0) - { // Death - target->special1 = damage; - - // use inflictor's death type if it got one. - if (inflictor && inflictor->DeathType != NAME_None) mod = inflictor->DeathType; - - // check for special fire damage or ice damage deaths - if (mod == NAME_Fire) + { + //[MC]Buddha flag for monsters. + if ((target->flags7 & MF7_BUDDHA) && (damage < TELEFRAG_DAMAGE) && ((inflictor == NULL || !(inflictor->flags3 & MF7_FOILBUDDHA)) && !(flags & DMG_FOILBUDDHA))) + { //FOILBUDDHA or Telefrag damage must kill it. + target->health = 1; + } + else { - if (player && !player->morphTics) - { // Check for flame death - if (!inflictor || - ((target->health > -50) && (damage > 25)) || - !(inflictor->flags5 & MF5_SPECIALFIREDAMAGE)) + + // Death + target->special1 = damage; + + // use inflictor's death type if it got one. + if (inflictor && inflictor->DeathType != NAME_None) mod = inflictor->DeathType; + + // check for special fire damage or ice damage deaths + if (mod == NAME_Fire) + { + if (player && !player->morphTics) + { // Check for flame death + if (!inflictor || + ((target->health > -50) && (damage > 25)) || + !(inflictor->flags5 & MF5_SPECIALFIREDAMAGE)) + { + target->DamageType = NAME_Fire; + } + } + else { target->DamageType = NAME_Fire; } } else { - target->DamageType = NAME_Fire; + target->DamageType = mod; } - } - else - { - target->DamageType = mod; - } - if (source && source->tracer && (source->flags5 & MF5_SUMMONEDMONSTER)) - { // Minotaur's kills go to his master - // Make sure still alive and not a pointer to fighter head - if (source->tracer->player && (source->tracer->player->mo == source->tracer)) - { - source = source->tracer; + if (source && source->tracer && (source->flags5 & MF5_SUMMONEDMONSTER)) + { // Minotaur's kills go to his master + // Make sure still alive and not a pointer to fighter head + if (source->tracer->player && (source->tracer->player->mo == source->tracer)) + { + source = source->tracer; + } } + target->Die (source, inflictor, flags); + return damage; } - target->Die (source, inflictor, flags); - return damage; } woundstate = target->FindState(NAME_Wound, mod); @@ -1363,6 +1441,16 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } } +fakepain: //Needed so we can skip the rest of the above, but still obey the original rules. + + //CAUSEPAIN can always attempt to trigger the chances of pain. + //ALLOWPAIN can do the same, only if the (unfiltered aka fake) damage is greater than 0. + if ((((target->flags7 & MF7_ALLOWPAIN) && (fakeDamage > 0)) + || ((inflictor != NULL) && (inflictor->flags7 & MF7_CAUSEPAIN)))) + { + holdDamage = damage; //Store the modified damage away after factors are taken into account. + damage = fakeDamage; //Retrieve the original damage. + } if (!(target->flags5 & MF5_NOPAIN) && (inflictor == NULL || !(inflictor->flags5 & MF5_PAINLESS)) && (target->player != NULL || !G_SkillProperty(SKILLP_NoPain)) && !(target->flags & MF_SKULLFLY)) @@ -1378,8 +1466,8 @@ int P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, } } - if ((damage >= target->PainThreshold && pr_damagemobj() < painchance) || - (inflictor != NULL && (inflictor->flags6 & MF6_FORCEPAIN))) + if ((((damage >= target->PainThreshold)) && (pr_damagemobj() < painchance)) + || (inflictor != NULL && (inflictor->flags6 & MF6_FORCEPAIN))) { dopain: if (mod == NAME_Electric) @@ -1416,6 +1504,7 @@ dopain: } } } + //ALLOWPAIN and CAUSEPAIN can still trigger infighting, even if no pain state is worked out. target->reactiontime = 0; // we're awake now... if (source) { @@ -1454,6 +1543,14 @@ dopain: if (justhit && (target->target == source || !target->target || !target->IsFriend(target->target))) target->flags |= MF_JUSTHIT; // fight back! + if (invulpain) //Note that this takes into account all the cheats a player has, in terms of invulnerability. + { + return -1; //NOW we return -1! + } + else if (fakedPain) + { + return holdDamage; //This is the calculated damage after all is said and done. + } return damage; } @@ -1572,7 +1669,7 @@ bool AActor::OkayToSwitchTarget (AActor *other) bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison) { - if((player->cheats&CF_GODMODE) || (player->mo->flags2 & MF2_INVULNERABLE)) + if((player->cheats&CF_GODMODE) || (player->mo->flags2 & MF2_INVULNERABLE) || (player->cheats & CF_GODMODE2)) { return false; } @@ -1623,8 +1720,8 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, { return; } - if (damage < TELEFRAG_DAMAGE && ((target->flags2 & MF2_INVULNERABLE) || - (player->cheats & CF_GODMODE))) + if ((damage < TELEFRAG_DAMAGE && ((target->flags2 & MF2_INVULNERABLE) || + (player->cheats & CF_GODMODE))) || (player->cheats & CF_GODMODE2)) { // target is invulnerable return; } @@ -1668,7 +1765,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, target->health -= damage; if (target->health <= 0) { // Death - if (player->cheats & CF_BUDDHA) + if ((((player->cheats & CF_BUDDHA) || (player->mo->flags7 & MF7_BUDDHA)) && damage < TELEFRAG_DAMAGE) || (player->cheats & CF_BUDDHA2)) { // [SP] Save the player... player->health = target->health = 1; } @@ -1706,7 +1803,6 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, P_SetMobjState(target, target->info->painstate); } */ - return; } diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index fc112963e..c8588a5b6 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -281,43 +281,43 @@ FUNC(LS_Generic_Door) FUNC(LS_Floor_LowerByValue) // Floor_LowerByValue (tag, speed, height) { - return EV_DoFloor (DFloor::floorLowerByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2, -1, 0, false); } FUNC(LS_Floor_LowerToLowest) // Floor_LowerToLowest (tag, speed) { - return EV_DoFloor (DFloor::floorLowerToLowest, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerToLowest, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_LowerToHighest) // Floor_LowerToHighest (tag, speed, adjust, hereticlower) { - return EV_DoFloor (DFloor::floorLowerToHighest, ln, arg0, SPEED(arg1), (arg2-128)*FRACUNIT, 0, 0, false, arg3==1); + return EV_DoFloor (DFloor::floorLowerToHighest, ln, arg0, SPEED(arg1), (arg2-128)*FRACUNIT, -1, 0, false, arg3==1); } FUNC(LS_Floor_LowerToNearest) // Floor_LowerToNearest (tag, speed) { - return EV_DoFloor (DFloor::floorLowerToNearest, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerToNearest, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_RaiseByValue) // Floor_RaiseByValue (tag, speed, height) { - return EV_DoFloor (DFloor::floorRaiseByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2, -1, 0, false); } FUNC(LS_Floor_RaiseToHighest) // Floor_RaiseToHighest (tag, speed) { - return EV_DoFloor (DFloor::floorRaiseToHighest, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseToHighest, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_RaiseToNearest) // Floor_RaiseToNearest (tag, speed) { - return EV_DoFloor (DFloor::floorRaiseToNearest, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseToNearest, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_RaiseAndCrush) @@ -335,13 +335,13 @@ FUNC(LS_Floor_RaiseAndCrushDoom) FUNC(LS_Floor_RaiseByValueTimes8) // FLoor_RaiseByValueTimes8 (tag, speed, height) { - return EV_DoFloor (DFloor::floorRaiseByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2*8, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2*8, -1, 0, false); } FUNC(LS_Floor_LowerByValueTimes8) // Floor_LowerByValueTimes8 (tag, speed, height) { - return EV_DoFloor (DFloor::floorLowerByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2*8, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerByValue, ln, arg0, SPEED(arg1), FRACUNIT*arg2*8, -1, 0, false); } FUNC(LS_Floor_CrushStop) @@ -353,51 +353,51 @@ FUNC(LS_Floor_CrushStop) FUNC(LS_Floor_LowerInstant) // Floor_LowerInstant (tag, unused, height) { - return EV_DoFloor (DFloor::floorLowerInstant, ln, arg0, 0, arg2*FRACUNIT*8, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerInstant, ln, arg0, 0, arg2*FRACUNIT*8, -1, 0, false); } FUNC(LS_Floor_RaiseInstant) // Floor_RaiseInstant (tag, unused, height) { - return EV_DoFloor (DFloor::floorRaiseInstant, ln, arg0, 0, arg2*FRACUNIT*8, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseInstant, ln, arg0, 0, arg2*FRACUNIT*8, -1, 0, false); } FUNC(LS_Floor_MoveToValueTimes8) // Floor_MoveToValueTimes8 (tag, speed, height, negative) { return EV_DoFloor (DFloor::floorMoveToValue, ln, arg0, SPEED(arg1), - arg2*FRACUNIT*8*(arg3?-1:1), 0, 0, false); + arg2*FRACUNIT*8*(arg3?-1:1), -1, 0, false); } FUNC(LS_Floor_MoveToValue) // Floor_MoveToValue (tag, speed, height, negative) { return EV_DoFloor (DFloor::floorMoveToValue, ln, arg0, SPEED(arg1), - arg2*FRACUNIT*(arg3?-1:1), 0, 0, false); + arg2*FRACUNIT*(arg3?-1:1), -1, 0, false); } FUNC(LS_Floor_RaiseToLowestCeiling) // Floor_RaiseToLowestCeiling (tag, speed) { - return EV_DoFloor (DFloor::floorRaiseToLowestCeiling, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseToLowestCeiling, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_RaiseByTexture) // Floor_RaiseByTexture (tag, speed) { - return EV_DoFloor (DFloor::floorRaiseByTexture, ln, arg0, SPEED(arg1), 0, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseByTexture, ln, arg0, SPEED(arg1), 0, -1, 0, false); } FUNC(LS_Floor_RaiseByValueTxTy) // Floor_RaiseByValueTxTy (tag, speed, height) { - return EV_DoFloor (DFloor::floorRaiseAndChange, ln, arg0, SPEED(arg1), arg2*FRACUNIT, 0, 0, false); + return EV_DoFloor (DFloor::floorRaiseAndChange, ln, arg0, SPEED(arg1), arg2*FRACUNIT, -1, 0, false); } FUNC(LS_Floor_LowerToLowestTxTy) // Floor_LowerToLowestTxTy (tag, speed) { - return EV_DoFloor (DFloor::floorLowerAndChange, ln, arg0, SPEED(arg1), arg2*FRACUNIT, 0, 0, false); + return EV_DoFloor (DFloor::floorLowerAndChange, ln, arg0, SPEED(arg1), arg2*FRACUNIT, -1, 0, false); } FUNC(LS_Floor_Waggle) @@ -641,7 +641,7 @@ FUNC(LS_Ceiling_CrushAndRaiseA) FUNC(LS_Ceiling_CrushAndRaiseDist) // Ceiling_CrushAndRaiseDist (tag, dist, speed, damage, crushtype) { - return EV_DoCeiling (DCeiling::ceilCrushAndRaiseDist, ln, arg0, SPEED(arg2), SPEED(arg2), arg1*FRACUNIT, arg3, 0, 0, CRUSHTYPE(arg4)); + return EV_DoCeiling (DCeiling::ceilCrushAndRaise, ln, arg0, SPEED(arg2), SPEED(arg2), arg1*FRACUNIT, arg3, 0, 0, CRUSHTYPE(arg4)); } FUNC(LS_Ceiling_CrushAndRaiseSilentA) @@ -650,6 +650,12 @@ FUNC(LS_Ceiling_CrushAndRaiseSilentA) return EV_DoCeiling (DCeiling::ceilCrushAndRaise, ln, arg0, SPEED(arg1), SPEED(arg2), 0, arg3, 1, 0, CRUSHTYPE(arg4)); } +FUNC(LS_Ceiling_CrushAndRaiseSilentDist) +// Ceiling_CrushAndRaiseSilentDist (tag, dist, upspeed, damage, crushtype) +{ + return EV_DoCeiling (DCeiling::ceilCrushAndRaise, ln, arg0, SPEED(arg2), SPEED(arg2), arg1*FRACUNIT, arg3, 1, 0, CRUSHTYPE(arg4)); +} + FUNC(LS_Ceiling_RaiseToNearest) // Ceiling_RaiseToNearest (tag, speed) { @@ -1504,7 +1510,7 @@ FUNC(LS_Thing_Raise) if (arg0==0) { - ok = P_Thing_Raise (it); + ok = P_Thing_Raise (it,NULL); } else { @@ -1512,7 +1518,7 @@ FUNC(LS_Thing_Raise) while ( (target = iterator.Next ()) ) { - ok |= P_Thing_Raise(target); + ok |= P_Thing_Raise(target,NULL); } } return ok; @@ -1758,7 +1764,7 @@ FUNC(LS_FloorAndCeiling_LowerRaise) // more or less unintuitive value for the fourth arg to trigger Boom's broken behavior if (arg3 != 1998 || !res) // (1998 for the year in which Boom was released... :P) { - res |= EV_DoFloor (DFloor::floorLowerToLowest, ln, arg0, SPEED(arg1), 0, 0, 0, false); + res |= EV_DoFloor (DFloor::floorLowerToLowest, ln, arg0, SPEED(arg1), 0, -1, 0, false); } return res; } @@ -3261,7 +3267,7 @@ lnSpecFunc LineSpecials[256] = /* 101 */ LS_NOP, // Scroll_Texture_Right /* 102 */ LS_NOP, // Scroll_Texture_Up /* 103 */ LS_NOP, // Scroll_Texture_Down - /* 104 */ LS_NOP, + /* 104 */ LS_Ceiling_CrushAndRaiseSilentDist, /* 105 */ LS_NOP, /* 106 */ LS_NOP, /* 107 */ LS_NOP, diff --git a/src/p_local.h b/src/p_local.h index 872ad29ed..8a3c7eddb 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -105,6 +105,7 @@ void P_FallingDamage (AActor *ent); void P_PlayerThink (player_t *player); void P_PredictPlayer (player_t *player); void P_UnPredictPlayer (); +void P_PredictionLerpReset(); // // P_MOBJ @@ -174,7 +175,8 @@ bool P_Thing_Move (int tid, AActor *source, int mapspot, bool fog); int P_Thing_Damage (int tid, AActor *whofor0, int amount, FName type); void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool add, bool setbob); void P_RemoveThing(AActor * actor); -bool P_Thing_Raise(AActor *thing); +bool P_Thing_Raise(AActor *thing, AActor *raiser); +bool P_Thing_CanRaise(AActor *thing); PClassActor *P_GetSpawnableType(int spawnnum); // @@ -466,6 +468,7 @@ enum // P_LineAttack flags AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, PClassActor *pufftype, int flags = 0, AActor **victim = NULL, int *actualdamage = NULL); AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, FName pufftype, int flags = 0, AActor **victim = NULL, int *actualdamage = NULL); +AActor *P_LinePickActor (AActor *t1, angle_t angle, fixed_t distance, int pitch, DWORD actorMask, DWORD wallMask); void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, AActor *missile); // missile version @@ -560,6 +563,8 @@ enum EDmgFlags DMG_NO_FACTOR = 16, DMG_PLAYERATTACK = 32, DMG_FOILINVUL = 64, + DMG_FOILBUDDHA = 128, + DMG_NO_PROTECT = 256, }; diff --git a/src/p_map.cpp b/src/p_map.cpp index 6236c696e..940c70752 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -74,6 +74,69 @@ TArray spechit; // Temporary holder for thing_sectorlist threads msecnode_t* sector_list = NULL; // phares 3/16/98 +//========================================================================== +// +// GetCoefficientClosestPointInLine24 +// +// Formula: (dotProduct(ldv1 - tm, ld) << 24) / dotProduct(ld, ld) +// with: ldv1 = (ld->v1->x, ld->v1->y), tm = (tm.x, tm.y) +// and ld = (ld->dx, ld->dy) +// Returns truncated to range [0, 1 << 24]. +// +//========================================================================== + +static fixed_t GetCoefficientClosestPointInLine24(line_t *ld, FCheckPosition &tm) +{ + // [EP] Use 64 bit integers in order to keep the exact result of the + // multiplication, because in the case the vertexes have both the + // distance coordinates equal to the map limit (32767 units, which is + // 2147418112 in fixed_t notation), the product result would occupy + // 62 bits and the sum of two products would occupy 63 bits + // in the worst case. If instead the vertexes are very close (1 in + // fixed_t notation, which is 1.52587890625e-05 in float notation), the + // product and the sum can be 1 in the worst case, which is very tiny. + + SQWORD r_num = ((SQWORD(tm.x - ld->v1->x)*ld->dx) + + (SQWORD(tm.y - ld->v1->y)*ld->dy)); + + // The denominator is always positive. Use this to avoid useless + // calculations. + SQWORD r_den = (SQWORD(ld->dx)*ld->dx + SQWORD(ld->dy)*ld->dy); + + if (r_num <= 0) { + // [EP] The numerator is less or equal to zero, hence the closest + // point on the line is the first vertex. Truncate the result to 0. + return 0; + } + + if (r_num >= r_den) { + // [EP] The division is greater or equal to 1, hence the closest + // point on the line is the second vertex. Truncate the result to + // 1 << 24. + return (1 << 24); + } + + // [EP] Deal with the limited bits. The original formula is: + // r = (r_num << 24) / r_den, + // but r_num might be big enough to make the shift overflow. + // Since the numerator can't be saved in a 128bit integer, + // the denominator must be right shifted. If the denominator is + // less than (1 << 24), there would be a division by zero. + // Thanks to the fact that in this code path the denominator is greater + // than the numerator, it's possible to avoid this bad situation by + // just checking the last 24 bits of the numerator. + if ((r_num >> (63-24)) != 0) { + // [EP] In fact, if the numerator is greater than + // (1 << (63-24)), the denominator must be greater than + // (1 << (63-24)), hence the denominator won't be zero after + // the right shift by 24 places. + return (fixed_t)(r_num/(r_den >> 24)); + } + // [EP] Having the last 24 bits all zero allows left shifting + // the numerator by 24 bits without overflow. + return (fixed_t)((r_num << 24)/r_den); +} + //========================================================================== // // PIT_FindFloorCeiling @@ -198,7 +261,7 @@ void P_GetFloorCeilingZ(FCheckPosition &tmf, int flags) if (ff_top > tmf.floorz) { - if (ff_top <= tmf.z || (!(flags && FFCF_3DRESTRICT) && (tmf.thing != NULL && ff_bottom < tmf.z && ff_top < tmf.z + tmf.thing->MaxStepHeight))) + if (ff_top <= tmf.z || (!(flags & FFCF_3DRESTRICT) && (tmf.thing != NULL && ff_bottom < tmf.z && ff_top < tmf.z + tmf.thing->MaxStepHeight))) { tmf.dropoffz = tmf.floorz = ff_top; tmf.floorpic = *rover->top.texture; @@ -380,7 +443,9 @@ bool P_TeleportMove(AActor *thing, fixed_t x, fixed_t y, fixed_t z, bool telefra // ... and some items can never be telefragged while others will be telefragged by everything that teleports upon them. if ((StompAlwaysFrags && !(th->flags6 & MF6_NOTELEFRAG)) || (th->flags7 & MF7_ALWAYSTELEFRAG)) { - P_DamageMobj(th, thing, thing, TELEFRAG_DAMAGE, NAME_Telefrag, DMG_THRUSTLESS); + // Don't actually damage if predicting a teleport + if (thing->player == NULL || !(thing->player->cheats & CF_PREDICTING)) + P_DamageMobj(th, thing, thing, TELEFRAG_DAMAGE, NAME_Telefrag, DMG_THRUSTLESS); continue; } return false; @@ -734,11 +799,8 @@ bool PIT_CheckLine(line_t *ld, const FBoundingBox &box, FCheckPosition &tm) else { // Find the point on the line closest to the actor's center, and use // that to calculate openings - float dx = (float)ld->dx; - float dy = (float)ld->dy; - fixed_t r = (fixed_t)(((float)(tm.x - ld->v1->x) * dx + - (float)(tm.y - ld->v1->y) * dy) / - (dx*dx + dy*dy) * 16777216.f); + fixed_t r = GetCoefficientClosestPointInLine24(ld, tm); + /* Printf ("%d:%d: %d (%d %d %d %d) (%d %d %d %d)\n", level.time, ld-lines, r, ld->frontsector->floorplane.a, ld->frontsector->floorplane.b, @@ -1109,24 +1171,27 @@ bool PIT_CheckThing(AActor *thing, FCheckPosition &tm) // cases where they are clearly supposed to do that if (thing->IsFriend(tm.thing->target)) { - // Friends never harm each other - return false; + // Friends never harm each other, unless the shooter has the HARMFRIENDS set. + if (!(thing->flags7 & MF7_HARMFRIENDS)) return false; } - if (thing->TIDtoHate != 0 && thing->TIDtoHate == tm.thing->target->TIDtoHate) + else { - // [RH] Don't hurt monsters that hate the same thing as you do - return false; - } - if (thing->GetSpecies() == tm.thing->target->GetSpecies() && !(thing->flags6 & MF6_DOHARMSPECIES)) - { - // Don't hurt same species or any relative - - // but only if the target isn't one's hostile. - if (!thing->IsHostile(tm.thing->target)) + if (thing->TIDtoHate != 0 && thing->TIDtoHate == tm.thing->target->TIDtoHate) { - // Allow hurting monsters the shooter hates. - if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid) + // [RH] Don't hurt monsters that hate the same thing as you do + return false; + } + if (thing->GetSpecies() == tm.thing->target->GetSpecies() && !(thing->flags6 & MF6_DOHARMSPECIES)) + { + // Don't hurt same species or any relative - + // but only if the target isn't one's hostile. + if (!thing->IsHostile(tm.thing->target)) { - return false; + // Allow hurting monsters the shooter hates. + if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid) + { + return false; + } } } } @@ -1218,6 +1283,16 @@ bool PIT_CheckThing(AActor *thing, FCheckPosition &tm) { P_GiveBody(thing, -damage); } + + if ((thing->flags7 & MF7_THRUREFLECT) && (thing->flags2 & MF2_REFLECTIVE) && (tm.thing->flags & MF_MISSILE)) + { + if (tm.thing->flags2 & MF2_SEEKERMISSILE) + { + tm.thing->tracer = tm.thing->target; + } + tm.thing->target = thing; + return true; + } return false; // don't traverse any more } if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH)) @@ -1578,7 +1653,7 @@ bool P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj) { // Don't clip against self continue; } - if ((actor->flags & MF_MISSILE) && thing == actor->target) + if ((actor->flags & MF_MISSILE) && (thing == actor->target)) { // Don't clip against whoever shot the missile. continue; } @@ -1917,13 +1992,13 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, } //Added by MC: To prevent bot from getting into dangerous sectors. - if (thing->player && thing->player->isbot && thing->flags & MF_SHOOTABLE) + if (thing->player && thing->player->Bot != NULL && thing->flags & MF_SHOOTABLE) { if (tm.sector != thing->Sector && bglobal.IsDangerous(tm.sector)) { - thing->player->prev = thing->player->dest; - thing->player->dest = NULL; + thing->player->Bot->prev = thing->player->Bot->dest; + thing->player->Bot->dest = NULL; thing->velx = 0; thing->vely = 0; thing->z = oldz; @@ -1981,13 +2056,6 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, thing->AdjustFloorClip(); } - // [RH] Don't activate anything if just predicting - if (thing->player && (thing->player->cheats & CF_PREDICTING)) - { - thing->flags6 &= ~MF6_INTRYMOVE; - return true; - } - // if any special lines were hit, do the effect if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP))) { @@ -1998,7 +2066,11 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, oldside = P_PointOnLineSide(oldx, oldy, ld); if (side != oldside && ld->special && !(thing->flags6 & MF6_NOTRIGGER)) { - if (thing->player) + if (thing->player && (thing->player->cheats & CF_PREDICTING)) + { + P_PredictLine(ld, thing, oldside, SPAC_Cross); + } + else if (thing->player) { P_ActivateLine(ld, thing, oldside, SPAC_Cross); } @@ -2024,6 +2096,13 @@ bool P_TryMove(AActor *thing, fixed_t x, fixed_t y, } } + // [RH] Don't activate anything if just predicting + if (thing->player && (thing->player->cheats & CF_PREDICTING)) + { + thing->flags6 &= ~MF6_INTRYMOVE; + return true; + } + // [RH] Check for crossing fake floor/ceiling newsec = thing->Sector; if (newsec->heightsec && oldsec->heightsec && newsec->SecActTarget) @@ -2151,7 +2230,7 @@ bool P_CheckMove(AActor *thing, fixed_t x, fixed_t y) { // too big a step up return false; } - else if ((thing->flags & MF_MISSILE) && !(thing->flags6 && MF6_STEPMISSILE) && tm.floorz > newz) + else if ((thing->flags & MF_MISSILE) && !(thing->flags6 & MF6_STEPMISSILE) && tm.floorz > newz) { // [RH] Don't let normal missiles climb steps return false; } @@ -2914,18 +2993,24 @@ bool P_BounceWall(AActor *mo) extern FRandom pr_bounce; bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) { + //Don't go through all of this if the actor is reflective and wants things to pass through them. + if (BlockingMobj && ((BlockingMobj->flags2 & MF2_REFLECTIVE) && (BlockingMobj->flags7 & MF7_THRUREFLECT))) return true; if (mo && BlockingMobj && ((mo->BounceFlags & BOUNCE_AllActors) - || ((mo->flags & MF_MISSILE) && (!(mo->flags2 & MF2_RIP) || (BlockingMobj->flags5 & MF5_DONTRIP) || ((mo->flags6 & MF6_NOBOSSRIP) && (BlockingMobj->flags2 & MF2_BOSS))) && (BlockingMobj->flags2 & MF2_REFLECTIVE)) - || ((BlockingMobj->player == NULL) && (!(BlockingMobj->flags3 & MF3_ISMONSTER))) - )) + || ((mo->flags & MF_MISSILE) && (!(mo->flags2 & MF2_RIP) + || (BlockingMobj->flags5 & MF5_DONTRIP) + || ((mo->flags6 & MF6_NOBOSSRIP) && (BlockingMobj->flags2 & MF2_BOSS))) && (BlockingMobj->flags2 & MF2_REFLECTIVE)) + || ((BlockingMobj->player == NULL) && (!(BlockingMobj->flags3 & MF3_ISMONSTER))))) { if (mo->bouncecount > 0 && --mo->bouncecount == 0) return false; + if (mo->flags7 & MF7_HITTARGET) mo->target = BlockingMobj; + if (mo->flags7 & MF7_HITMASTER) mo->master = BlockingMobj; + if (mo->flags7 & MF7_HITTRACER) mo->tracer = BlockingMobj; + if (!ontop) { fixed_t speed; - angle_t angle = R_PointToAngle2(BlockingMobj->x, - BlockingMobj->y, mo->x, mo->y) + ANGLE_1*((pr_bounce() % 16) - 8); + angle_t angle = R_PointToAngle2(BlockingMobj->x,BlockingMobj->y, mo->x, mo->y) + ANGLE_1*((pr_bounce() % 16) - 8); speed = P_AproxDistance(mo->velx, mo->vely); speed = FixedMul(speed, mo->wallbouncefactor); // [GZ] was 0.75, using wallbouncefactor seems more consistent mo->angle = angle; @@ -3665,6 +3750,8 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, hity = t1->y + FixedMul(vy, dist); hitz = shootz + FixedMul(vz, dist); + + // Spawn bullet puffs or blood spots, depending on target type. if ((puffDefaults != NULL && puffDefaults->flags3 & MF3_PUFFONACTORS) || (trace.Actor->flags & MF_NOBLOOD) || @@ -3677,6 +3764,13 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING); } + if (puffDefaults != NULL && trace.Actor != NULL && puff != NULL) + { + if (puffDefaults->flags7 && MF7_HITTARGET) puff->target = trace.Actor; + if (puffDefaults->flags7 && MF7_HITMASTER) puff->master = trace.Actor; + if (puffDefaults->flags7 && MF7_HITTRACER) puff->tracer = trace.Actor; + } + // Allow puffs to inflict poison damage, so that hitscans can poison, too. if (puffDefaults != NULL && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) { @@ -3686,7 +3780,7 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, // [GZ] If MF6_FORCEPAIN is set, we need to call P_DamageMobj even if damage is 0! // Note: The puff may not yet be spawned here so we must check the class defaults, not the actor. int newdam = damage; - if (damage || (puffDefaults != NULL && puffDefaults->flags6 & MF6_FORCEPAIN)) + if (damage || (puffDefaults != NULL && ((puffDefaults->flags6 & MF6_FORCEPAIN) || (puffDefaults->flags7 & MF7_CAUSEPAIN)))) { int dmgflags = DMG_INFLICTOR_IS_PUFF | pflag; // Allow MF5_PIERCEARMOR on a weapon as well. @@ -3695,7 +3789,7 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, { dmgflags |= DMG_NO_ARMOR; } - + if (puff == NULL) { // Since the puff is the damage inflictor we need it here @@ -3783,6 +3877,52 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, return NULL; } +//========================================================================== +// +// P_LinePickActor +// +//========================================================================== + +AActor *P_LinePickActor(AActor *t1, angle_t angle, fixed_t distance, int pitch, + DWORD actorMask, DWORD wallMask) +{ + fixed_t vx, vy, vz, shootz; + + angle >>= ANGLETOFINESHIFT; + pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT; + + vx = FixedMul(finecosine[pitch], finecosine[angle]); + vy = FixedMul(finecosine[pitch], finesine[angle]); + vz = -finesine[pitch]; + + shootz = t1->z - t1->floorclip + (t1->height >> 1); + if (t1->player != NULL) + { + shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor); + } + else + { + shootz += 8 * FRACUNIT; + } + + FTraceResults trace; + Origin TData; + + TData.Caller = t1; + TData.hitGhosts = true; + + if (Trace(t1->x, t1->y, shootz, t1->Sector, vx, vy, vz, distance, + actorMask, wallMask, t1, trace, TRACE_NoSky, CheckForActor, &TData)) + { + if (trace.HitType == TRACE_HitActor) + { + return trace.Actor; + } + } + + return NULL; +} + //========================================================================== // // @@ -4026,7 +4166,7 @@ void P_RailAttack(AActor *source, int damage, int offset_xy, fixed_t offset_z, i int flags; assert(puffclass != NULL); // Because we set it to a default above - AActor *puffDefaults = GetDefaultByType(puffclass->GetReplacement()); + AActor *puffDefaults = GetDefaultByType(puffclass->GetReplacement()); //Contains all the flags such as FOILINVUL, etc. flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? 0 : TRACE_PCross | TRACE_Impact; rail_data.StopAtInvul = (puffDefaults->flags3 & MF3_FOILINVUL) ? false : true; @@ -4059,7 +4199,7 @@ void P_RailAttack(AActor *source, int damage, int offset_xy, fixed_t offset_z, i z = shootz + FixedMul(hitdist, vz); if ((hitactor->flags & MF_NOBLOOD) || - (hitactor->flags2 & (MF2_DORMANT | MF2_INVULNERABLE))) + (hitactor->flags2 & MF2_DORMANT || ((hitactor->flags2 & MF2_INVULNERABLE) && !(puffDefaults->flags3 & MF3_FOILINVUL)))) { spawnpuff = (puffclass != NULL); } @@ -4076,11 +4216,21 @@ void P_RailAttack(AActor *source, int damage, int offset_xy, fixed_t offset_z, i { P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags); } + if (hitactor != NULL && puffDefaults != NULL && thepuff != NULL) + { + if (puffDefaults->flags7 & MF7_HITTARGET) thepuff->target = hitactor; + if (puffDefaults->flags7 & MF7_HITMASTER) thepuff->master = hitactor; + if (puffDefaults->flags7 & MF7_HITTRACER) thepuff->tracer = hitactor; + } if (puffDefaults && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) { P_PoisonMobj(hitactor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType); } - int newdam = P_DamageMobj(hitactor, thepuff ? thepuff : source, source, damage, damagetype, DMG_INFLICTOR_IS_PUFF); + int dmgFlagPass = DMG_INFLICTOR_IS_PUFF; + dmgFlagPass += (puffDefaults->flags3 & MF3_FOILINVUL) ? DMG_FOILINVUL : 0; //[MC]Because the original foilinvul check wasn't working. + dmgFlagPass += (puffDefaults->flags7 & MF7_FOILBUDDHA) ? DMG_FOILBUDDHA : 0; + int newdam = P_DamageMobj(hitactor, thepuff ? thepuff : source, source, damage, damagetype, dmgFlagPass); + if (bleed) { P_SpawnBlood(x, y, z, (source->angle + angleoffset) - ANG180, newdam > 0 ? newdam : damage, hitactor); @@ -4627,7 +4777,7 @@ void P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bo if (!(flags & RADF_NODAMAGE)) newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod); - else if (thing->player == NULL && !(flags & RADF_NOIMPACTDAMAGE)) + else if (thing->player == NULL && (!(flags & RADF_NOIMPACTDAMAGE) && !(thing->flags7 & MF7_DONTTHRUST))) thing->flags2 |= MF2_BLASTED; if (!(thing->flags & MF_ICECORPSE)) @@ -4639,25 +4789,29 @@ void P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bo { if (bombsource == NULL || !(bombsource->flags2 & MF2_NODMGTHRUST)) { - thrust = points * 0.5f / (double)thing->Mass; - if (bombsource == thing) + if (!(thing->flags7 & MF7_DONTTHRUST)) { - thrust *= selfthrustscale; + + thrust = points * 0.5f / (double)thing->Mass; + if (bombsource == thing) + { + thrust *= selfthrustscale; + } + velz = (double)(thing->z + (thing->height >> 1) - bombspot->z) * thrust; + if (bombsource != thing) + { + velz *= 0.5f; + } + else + { + velz *= 0.8f; + } + angle_t ang = R_PointToAngle2(bombspot->x, bombspot->y, thing->x, thing->y) >> ANGLETOFINESHIFT; + thing->velx += fixed_t(finecosine[ang] * thrust); + thing->vely += fixed_t(finesine[ang] * thrust); + if (!(flags & RADF_NODAMAGE)) + thing->velz += (fixed_t)velz; // this really doesn't work well } - velz = (double)(thing->z + (thing->height >> 1) - bombspot->z) * thrust; - if (bombsource != thing) - { - velz *= 0.5f; - } - else - { - velz *= 0.8f; - } - angle_t ang = R_PointToAngle2(bombspot->x, bombspot->y, thing->x, thing->y) >> ANGLETOFINESHIFT; - thing->velx += fixed_t(finecosine[ang] * thrust); - thing->vely += fixed_t(finesine[ang] * thrust); - if (!(flags & RADF_NODAMAGE)) - thing->velz += (fixed_t)velz; // this really doesn't work well } } } @@ -4970,6 +5124,8 @@ int P_PushUp(AActor *thing, FChangePosition *cpos) // is normally for projectiles which would have exploded by now anyway... if (thing->flags6 & MF6_THRUSPECIES && thing->GetSpecies() == intersect->GetSpecies()) continue; + if ((thing->flags & MF_MISSILE) && (intersect->flags2 & MF2_REFLECTIVE) && (intersect->flags7 & MF7_THRUREFLECT)) + continue; if (!(intersect->flags2 & MF2_PASSMOBJ) || (!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass) || (intersect->flags4 & MF4_ACTLIKEBRIDGE) @@ -4978,7 +5134,8 @@ int P_PushUp(AActor *thing, FChangePosition *cpos) // Can't push bridges or things more massive than ourself return 2; } - fixed_t oldz = intersect->z; + fixed_t oldz; + oldz = intersect->z; P_AdjustFloorCeil(intersect, cpos); intersect->z = thing->z + thing->height + 1; if (P_PushUp(intersect, cpos)) diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index 17fc97d2f..3a4a4c6a0 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -434,8 +434,8 @@ static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node) // add on a 386/486, but it certainly isn't on anything newer than that. fixed_t dx; fixed_t dy; - fixed_t left; - fixed_t right; + double left; + double right; if (!node->dx) { @@ -466,8 +466,9 @@ static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node) return 0; } - left = FixedMul ( node->dy>>FRACBITS , dx ); - right = FixedMul ( dy , node->dx>>FRACBITS ); + // we must use doubles here because the fixed point code will produce errors due to loss of precision for extremely short linedefs. + left = (double)node->dy * (double)dx; + right = (double)dy * (double)node->dx; if (right < left) { diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index d364cc0cd..8fa060456 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -84,7 +84,6 @@ static void PlayerLandedOnThing (AActor *mo, AActor *onmobj); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern cycle_t BotSupportCycles; extern int BotWTG; EXTERN_CVAR (Int, cl_rockettrails) @@ -291,6 +290,10 @@ void AActor::Serialize (FArchive &arc) { arc << flags7; } + if (SaveVersion >= 4512) + { + arc << weaponspecial; + } arc << special1 << special2 << health @@ -333,9 +336,13 @@ void AActor::Serialize (FArchive &arc) << MinMissileChance << SpawnFlags << Inventory - << InventoryID - << id - << FloatBobPhase + << InventoryID; + if (SaveVersion < 4513) + { + SDWORD id; + arc << id; + } + arc << FloatBobPhase << Translation << SeeSound << AttackSound @@ -390,8 +397,16 @@ void AActor::Serialize (FArchive &arc) } arc << lastpush << lastbump << PainThreshold - << DamageFactor - << WeaveIndexXY << WeaveIndexZ + << DamageFactor; + if (SaveVersion >= 4516) + { + arc << DamageMultiply; + } + else + { + DamageMultiply = FRACUNIT; + } + arc << WeaveIndexXY << WeaveIndexZ << PoisonDamageReceived << PoisonDurationReceived << PoisonPeriodReceived << Poisoner << PoisonDamage << PoisonDuration << PoisonPeriod; if (SaveVersion >= 3235) @@ -403,7 +418,11 @@ void AActor::Serialize (FArchive &arc) { arc << FriendPlayer; } - + if (SaveVersion >= 4517) + { + arc << TeleFogSourceType + << TeleFogDestType; + } { FString tagstr; if (arc.IsStoring() && Tag != NULL && Tag->Len() > 0) tagstr = *Tag; @@ -1254,7 +1273,7 @@ bool AActor::Massacre () P_DamageMobj (this, NULL, NULL, TELEFRAG_DAMAGE, NAME_Massacre); } while (health != prevhealth && health > 0); //abort if the actor wasn't hurt. - return true; + return health <= 0; } return false; } @@ -1282,17 +1301,14 @@ void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target) if (target != NULL && ((target->flags & (MF_SHOOTABLE|MF_CORPSE)) || (target->flags6 & MF6_KILLED)) ) { + if (mo->flags7 & MF7_HITTARGET) mo->target = target; + if (mo->flags7 & MF7_HITMASTER) mo->master = target; + if (mo->flags7 & MF7_HITTRACER) mo->tracer = target; if (target->flags & MF_NOBLOOD) nextstate = mo->FindState(NAME_Crash); if (nextstate == NULL) nextstate = mo->FindState(NAME_Death, NAME_Extreme); } if (nextstate == NULL) nextstate = mo->FindState(NAME_Death); - mo->SetState (nextstate); - if (mo->ObjectFlags & OF_EuthanizeMe) - { - return; - } - if (line != NULL && line->special == Line_Horizon && !(mo->flags3 & MF3_SKYEXPLODE)) { // [RH] Don't explode missiles on horizon lines. @@ -1367,8 +1383,17 @@ void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target) } } - if (nextstate != NULL) + // play the sound before changing the state, so that AActor::Destroy can call S_RelinkSounds on it and the death state can override it. + if (mo->DeathSound) { + S_Sound (mo, CHAN_VOICE, mo->DeathSound, 1, + (mo->flags3 & MF3_FULLVOLDEATH) ? ATTN_NONE : ATTN_NORM); + } + + mo->SetState (nextstate); + if (!(mo->ObjectFlags & OF_EuthanizeMe)) + { + // The rest only applies if the missile actor still exists. // [RH] Change render style of exploding rockets if (mo->flags5 & MF5_DEHEXPLOSION) { @@ -1401,11 +1426,6 @@ void P_ExplodeMissile (AActor *mo, line_t *line, AActor *target) mo->flags &= ~MF_MISSILE; - if (mo->DeathSound) - { - S_Sound (mo, CHAN_VOICE, mo->DeathSound, 1, - (mo->flags3 & MF3_FULLVOLDEATH) ? ATTN_NONE : ATTN_NORM); - } } } @@ -1742,6 +1762,7 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) int steps, step, totalsteps; fixed_t startx, starty; fixed_t oldfloorz = mo->floorz; + fixed_t oldz = mo->z; fixed_t maxmove = (mo->waterlevel < 1) || (mo->flags & MF_MISSILE) || (mo->player && mo->player->crouchoffset<-10*FRACUNIT) ? MAXMOVE : MAXMOVE/4; @@ -2031,20 +2052,53 @@ fixed_t P_XYMovement (AActor *mo, fixed_t scrollx, fixed_t scrolly) } if (BlockingMobj && (BlockingMobj->flags2 & MF2_REFLECTIVE)) { - angle = R_PointToAngle2(BlockingMobj->x, BlockingMobj->y, mo->x, mo->y); - - // Change angle for deflection/reflection - if (mo->AdjustReflectionAngle (BlockingMobj, angle)) + bool seeker = (mo->flags2 & MF2_SEEKERMISSILE) ? true : false; + // Don't change the angle if there's THRUREFLECT on the monster. + if (!(BlockingMobj->flags7 & MF7_THRUREFLECT)) { - goto explode; - } + int dir; + angle_t delta; + + if (BlockingMobj->flags7 & MF7_MIRRORREFLECT) + angle = mo->angle + ANG180; + else + angle = R_PointToAngle2(BlockingMobj->x, BlockingMobj->y, mo->x, mo->y); - // Reflect the missile along angle - mo->angle = angle; - angle >>= ANGLETOFINESHIFT; - mo->velx = FixedMul (mo->Speed>>1, finecosine[angle]); - mo->vely = FixedMul (mo->Speed>>1, finesine[angle]); - mo->velz = -mo->velz/2; + // Change angle for deflection/reflection + // AIMREFLECT calls precedence so make sure not to bother with adjusting here if declared. + if (!(BlockingMobj->flags7 & MF7_AIMREFLECT) && (mo->AdjustReflectionAngle(BlockingMobj, angle))) + { + goto explode; + } + + // Reflect the missile along angle + if (BlockingMobj->flags7 & MF7_AIMREFLECT) + { + dir = P_FaceMobj(mo, mo->target, &delta); + if (dir) + { // Turn clockwise + mo->angle += delta; + } + else + { // Turn counter clockwise + mo->angle -= delta; + } + angle = mo->angle >> ANGLETOFINESHIFT; + mo->velx = FixedMul(mo->Speed, finecosine[angle]); + mo->vely = FixedMul(mo->Speed, finesine[angle]); + mo->velz = -mo->velz; + } + else + { + mo->angle = angle; + angle >>= ANGLETOFINESHIFT; + mo->velx = FixedMul(mo->Speed >> 1, finecosine[angle]); + mo->vely = FixedMul(mo->Speed >> 1, finesine[angle]); + mo->velz = -mo->velz / 2; + } + + + } if (mo->flags2 & MF2_SEEKERMISSILE) { mo->tracer = mo->target; @@ -2725,18 +2779,10 @@ void P_NightmareRespawn (AActor *mobj) mo->PrevZ = z; // Do not interpolate Z position if we changed it since spawning. // spawn a teleport fog at old spot because of removal of the body? - mo = Spawn ("TeleportFog", mobj->x, mobj->y, mobj->z, ALLOW_REPLACE); - if (mo != NULL) - { - mo->z += TELEFOGHEIGHT; - } + P_SpawnTeleportFog(mobj, mobj->x, mobj->y, mobj->z + TELEFOGHEIGHT, true); // spawn a teleport fog at the new spot - mo = Spawn ("TeleportFog", x, y, z, ALLOW_REPLACE); - if (mo != NULL) - { - mo->z += TELEFOGHEIGHT; - } + P_SpawnTeleportFog(mobj, x, y, z + TELEFOGHEIGHT, false); // remove the old monster mobj->Destroy (); @@ -2988,6 +3034,7 @@ int AActor::SpecialMissileHit (AActor *victim) bool AActor::AdjustReflectionAngle (AActor *thing, angle_t &angle) { if (flags2 & MF2_DONTREFLECT) return true; + if (thing->flags7 & MF7_THRUREFLECT) return false; // Change angle for reflection if (thing->flags4&MF4_SHIELDREFLECT) @@ -3216,7 +3263,7 @@ void AActor::Tick () special2++; } //Added by MC: Freeze mode. - if (bglobal.freeze && !(player && !player->isbot)) + if (bglobal.freeze && !(player && player->Bot == NULL)) { return; } @@ -3330,40 +3377,40 @@ void AActor::Tick () } } - if (bglobal.botnum && consoleplayer == Net_Arbitrator && !demoplayback && + if (bglobal.botnum && !demoplayback && ((flags & (MF_SPECIAL|MF_MISSILE)) || (flags3 & MF3_ISMONSTER))) { BotSupportCycles.Clock(); bglobal.m_Thinking = true; for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || !players[i].isbot) + if (!playeringame[i] || players[i].Bot == NULL) continue; if (flags3 & MF3_ISMONSTER) { if (health > 0 - && !players[i].enemy + && !players[i].Bot->enemy && player ? !IsTeammate (players[i].mo) : true && P_AproxDistance (players[i].mo->x-x, players[i].mo->y-y) < MAX_MONSTER_TARGET_DIST && P_CheckSight (players[i].mo, this, SF_SEEPASTBLOCKEVERYTHING)) { //Probably a monster, so go kill it. - players[i].enemy = this; + players[i].Bot->enemy = this; } } else if (flags & MF_SPECIAL) { //Item pickup time //clock (BotWTG); - bglobal.WhatToGet (players[i].mo, this); + players[i].Bot->WhatToGet (this); //unclock (BotWTG); BotWTG++; } else if (flags & MF_MISSILE) { - if (!players[i].missile && (flags3 & MF3_WARNBOT)) + if (!players[i].Bot->missile && (flags3 & MF3_WARNBOT)) { //warn for incoming missiles. - if (target != players[i].mo && bglobal.Check_LOS (players[i].mo, this, ANGLE_90)) - players[i].missile = this; + if (target != players[i].mo && players[i].Bot->Check_LOS (this, ANGLE_90)) + players[i].Bot->missile = this; } } } @@ -3967,6 +4014,7 @@ AActor *AActor::StaticSpawn (PClassActor *type, fixed_t ix, fixed_t iy, fixed_t actor->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98 if (G_SkillProperty(SKILLP_FastMonsters) && actor->GetClass()->FastSpeed >= 0) actor->Speed = actor->GetClass()->FastSpeed; + actor->DamageMultiply = FRACUNIT; // set subsector and/or block links actor->LinkToWorld (SpawningMapThing); @@ -4350,6 +4398,12 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) if ((unsigned)playernum >= (unsigned)MAXPLAYERS || !playeringame[playernum]) return NULL; + // Old lerp data needs to go + if (playernum == consoleplayer) + { + P_PredictionLerpReset(); + } + p = &players[playernum]; if (p->cls == NULL) @@ -4390,12 +4444,15 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) { spawn_x = p->mo->x; spawn_y = p->mo->y; + spawn_z = p->mo->z; + spawn_angle = p->mo->angle; } else { spawn_x = mthing->x; spawn_y = mthing->y; + // Allow full angular precision but avoid roundoff errors for multiples of 45 degrees. if (mthing->angle % 45 != 0) { @@ -4409,14 +4466,14 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) { spawn_angle += 1 << ANGLETOFINESHIFT; } - } - if (GetDefaultByType(p->cls)->flags & MF_SPAWNCEILING) - spawn_z = ONCEILINGZ; - else if (GetDefaultByType(p->cls)->flags2 & MF2_SPAWNFLOAT) - spawn_z = FLOATRANDZ; - else - spawn_z = ONFLOORZ; + if (GetDefaultByType(p->cls)->flags & MF_SPAWNCEILING) + spawn_z = ONCEILINGZ; + else if (GetDefaultByType(p->cls)->flags2 & MF2_SPAWNFLOAT) + spawn_z = FLOATRANDZ; + else + spawn_z = ONFLOORZ; + } mobj = static_cast (Spawn (p->cls, spawn_x, spawn_y, spawn_z, NO_REPLACE)); @@ -4462,9 +4519,6 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) mobj->pitch = mobj->roll = 0; mobj->health = p->health; - //Added by MC: Identification (number in the players[MAXPLAYERS] array) - mobj->id = playernum; - // [RH] Set player sprite based on skin if (!(mobj->flags4 & MF4_NOSKIN)) { @@ -4536,7 +4590,8 @@ APlayerPawn *P_SpawnPlayer (FPlayerStart *mthing, int playernum, int flags) { APowerup *invul = static_cast(p->mo->GiveInventoryType (RUNTIME_CLASS(APowerInvulnerable))); invul->EffectTics = 3*TICRATE; - invul->BlendColor = 0; // don't mess with the view + invul->BlendColor = 0; // don't mess with the view + invul->ItemFlags |= IF_UNDROPPABLE; // Don't drop this p->mo->effects |= FX_RESPAWNINVUL; // [RH] special effect } @@ -5118,10 +5173,11 @@ void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage, AAc cls = static_cast(cls->ParentClass); } } + + statedone: + if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE; } -statedone: - if (!(bloodtype <= 1)) th->renderflags |= RF_INVISIBLE; if (bloodtype >= 1) P_DrawSplash2 (40, x, y, z, dir, 2, bloodcolor); } @@ -5958,22 +6014,41 @@ AActor *P_SpawnPlayerMissile (AActor *source, fixed_t x, fixed_t y, fixed_t z, return NULL; } +int AActor::GetTeam() +{ + if (player) + { + return player->userinfo.GetTeam(); + } + + int myTeam = DesignatedTeam; + + // Check for monsters that belong to a player on the team but aren't part of the team themselves. + if (myTeam == TEAM_NONE && FriendPlayer != 0) + { + myTeam = players[FriendPlayer - 1].userinfo.GetTeam(); + } + return myTeam; + +} + bool AActor::IsTeammate (AActor *other) { if (!other) + { return false; + } else if (!deathmatch && player && other->player) - return true; - int myTeam = DesignatedTeam; - int otherTeam = other->DesignatedTeam; - if (player) - myTeam = player->userinfo.GetTeam(); - if (other->player) - otherTeam = other->player->userinfo.GetTeam(); - if (teamplay && myTeam != TEAM_NONE && myTeam == otherTeam) { return true; } + else if (teamplay) + { + int myTeam = GetTeam(); + int otherTeam = other->GetTeam(); + + return (myTeam != TEAM_NONE && myTeam == otherTeam); + } return false; } @@ -6069,7 +6144,7 @@ bool AActor::IsHostile (AActor *other) int AActor::DoSpecialDamage (AActor *target, int damage, FName damagetype) { if (target->player && target->player->mo == target && damage < 1000 && - (target->player->cheats & CF_GODMODE)) + (target->player->cheats & CF_GODMODE || target->player->cheats & CF_GODMODE2)) { return -1; } diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index 664614e94..0551a2c8b 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -211,6 +211,7 @@ void P_BringUpWeapon (player_t *player) // make sure that the previous weapon's flash state is terminated. // When coming here from a weapon drop it may still be active. P_SetPsprite(player, ps_flash, NULL); + player->mo->weaponspecial = 0; } @@ -226,7 +227,7 @@ void P_FireWeapon (player_t *player, FState *state) // [SO] 9/2/02: People were able to do an awful lot of damage // when they were observers... - if (!player->isbot && bot_observer) + if (player->Bot == NULL && bot_observer) { return; } @@ -262,7 +263,7 @@ void P_FireWeaponAlt (player_t *player, FState *state) // [SO] 9/2/02: People were able to do an awful lot of damage // when they were observers... - if (!player->isbot && bot_observer) + if (player->Bot == NULL && bot_observer) { return; } @@ -297,7 +298,7 @@ void P_FireWeaponAlt (player_t *player, FState *state) void P_ReloadWeapon (player_t *player, FState *state) { AWeapon *weapon; - if (!player->isbot && bot_observer) + if (player->Bot == NULL && bot_observer) { return; } @@ -328,7 +329,7 @@ void P_ReloadWeapon (player_t *player, FState *state) void P_ZoomWeapon (player_t *player, FState *state) { AWeapon *weapon; - if (!player->isbot && bot_observer) + if (player->Bot == NULL && bot_observer) { return; } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index bf0da0cbe..a677420f5 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -271,7 +271,7 @@ static void CopyPlayer (player_t *dst, player_t *src, const char *name) dst->cheats |= chasecam; - if (dst->isbot) + if (dst->Bot != NULL) { botinfo_t *thebot = bglobal.botinfo; while (thebot && stricmp (name, thebot->name)) @@ -280,10 +280,9 @@ static void CopyPlayer (player_t *dst, player_t *src, const char *name) } if (thebot) { - thebot->inuse = true; + thebot->inuse = BOTINUSE_Yes; } bglobal.botnum++; - bglobal.botingame[dst - players] = true; dst->userinfo.TransferFrom(uibackup2); } else diff --git a/src/p_setup.cpp b/src/p_setup.cpp index fcd514f20..ec7bf7d98 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -537,34 +537,31 @@ void MapData::GetChecksum(BYTE cksum[16]) { MD5Context md5; - if (file != NULL) + if (isText) { - if (isText) + Seek(ML_TEXTMAP); + if (file != NULL) md5.Update(file, Size(ML_TEXTMAP)); + } + else + { + if (Size(ML_LABEL) != 0) { - Seek(ML_TEXTMAP); - md5.Update(file, Size(ML_TEXTMAP)); - } - else - { - if (Size(ML_LABEL) != 0) - { - Seek(ML_LABEL); - md5.Update(file, Size(ML_LABEL)); - } - Seek(ML_THINGS); - md5.Update(file, Size(ML_THINGS)); - Seek(ML_LINEDEFS); - md5.Update(file, Size(ML_LINEDEFS)); - Seek(ML_SIDEDEFS); - md5.Update(file, Size(ML_SIDEDEFS)); - Seek(ML_SECTORS); - md5.Update(file, Size(ML_SECTORS)); - } - if (HasBehavior) - { - Seek(ML_BEHAVIOR); - md5.Update(file, Size(ML_BEHAVIOR)); + Seek(ML_LABEL); + if (file != NULL) md5.Update(file, Size(ML_LABEL)); } + Seek(ML_THINGS); + if (file != NULL) md5.Update(file, Size(ML_THINGS)); + Seek(ML_LINEDEFS); + if (file != NULL) md5.Update(file, Size(ML_LINEDEFS)); + Seek(ML_SIDEDEFS); + if (file != NULL) md5.Update(file, Size(ML_SIDEDEFS)); + Seek(ML_SECTORS); + if (file != NULL) md5.Update(file, Size(ML_SECTORS)); + } + if (HasBehavior) + { + Seek(ML_BEHAVIOR); + if (file != NULL) md5.Update(file, Size(ML_BEHAVIOR)); } md5.Final(cksum); } diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 377e5959b..7ca756b2b 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -73,6 +73,7 @@ static FRandom pr_playerinspecialsector ("PlayerInSpecialSector"); void P_SetupPortals(); +EXTERN_CVAR(Bool, cl_predict_specials) IMPLEMENT_POINTY_CLASS (DScroller) DECLARE_POINTER (m_Interpolations[0]) @@ -408,6 +409,48 @@ bool P_TestActivateLine (line_t *line, AActor *mo, int side, int activationType) return true; } +//============================================================================ +// +// P_PredictLine +// +//============================================================================ + +bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType) +{ + int lineActivation; + INTBOOL buttonSuccess; + BYTE special; + + // Only predict a very specifc section of specials + if (line->special != Teleport_Line && + line->special != Teleport) + { + return false; + } + + if (!P_TestActivateLine(line, mo, side, activationType) || !cl_predict_specials) + { + return false; + } + + if (line->locknumber > 0) return false; + lineActivation = line->activation; + buttonSuccess = false; + buttonSuccess = P_ExecuteSpecial(line->special, + line, mo, side == 1, line->args[0], + line->args[1], line->args[2], + line->args[3], line->args[4]); + + special = line->special; + + // end of changed code + if (developer && buttonSuccess) + { + Printf("Line special %d predicted on line %i\n", special, int(line - lines)); + } + return true; +} + // // P_PlayerInSpecialSector // Called every tic frame @@ -671,6 +714,7 @@ void P_SectorDamage(int tag, int amount, FName type, PClassActor *protectClass, //============================================================================ CVAR(Bool, showsecretsector, false, 0) +CVAR(Bool, cl_showsecretmessage, true, CVAR_ARCHIVE) void P_GiveSecret(AActor *actor, bool printmessage, bool playsound, int sectornum) { @@ -680,7 +724,7 @@ void P_GiveSecret(AActor *actor, bool printmessage, bool playsound, int sectornu { actor->player->secretcount++; } - if (actor->CheckLocalView (consoleplayer)) + if (cl_showsecretmessage && actor->CheckLocalView(consoleplayer)) { if (printmessage) { diff --git a/src/p_spec.h b/src/p_spec.h index c94064ae7..e653fe3b1 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -166,6 +166,7 @@ void P_UpdateSpecials (void); // when needed bool P_ActivateLine (line_t *ld, AActor *mo, int side, int activationType); bool P_TestActivateLine (line_t *ld, AActor *mo, int side, int activationType); +bool P_PredictLine (line_t *ld, AActor *mo, int side, int activationType); void P_PlayerInSpecialSector (player_t *player, sector_t * sector=NULL); void P_PlayerOnSpecialFlat (player_t *player, int floorType); @@ -628,7 +629,6 @@ public: ceilLowerInstant, ceilRaiseInstant, ceilCrushAndRaise, - ceilCrushAndRaiseDist, ceilLowerAndCrush, ceilLowerAndCrushDist, ceilCrushRaiseAndStay, @@ -903,6 +903,7 @@ bool EV_DoChange (line_t *line, EChange changetype, int tag); // // P_TELEPT // +void P_SpawnTeleportFog(AActor *mobj, fixed_t x, fixed_t y, fixed_t z, bool beforeTele = true, bool setTarget = false); //Spawns teleport fog. Pass the actor to pluck TeleFogFromType and TeleFogToType. 'from' determines if this is the fog to spawn at the old position (true) or new (false). bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, bool useFog, bool sourceFog, bool keepOrientation, bool haltVelocity = true, bool keepHeight = false); bool EV_Teleport (int tid, int tag, line_t *line, int side, AActor *thing, bool fog, bool sourceFog, bool keepOrientation, bool haltVelocity = true, bool keepHeight = false); bool EV_SilentLineTeleport (line_t *line, int side, AActor *thing, int id, INTBOOL reverse); diff --git a/src/p_states.cpp b/src/p_states.cpp index 4c12dbd66..c554837c1 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -39,6 +39,7 @@ #include "cmdlib.h" #include "i_system.h" #include "c_dispatch.h" +#include "v_text.h" #include "thingdef/thingdef.h" // Each state is owned by an actor. Actors can own any number of @@ -718,6 +719,10 @@ FState *FStateDefinitions::ResolveGotoLabel (AActor *actor, PClassActor *mytype, { I_Error ("Attempt to get invalid state %s from actor %s.", label, type->TypeName.GetChars()); } + else + { + Printf (TEXTCOLOR_RED "Attempt to get invalid state %s from actor %s.\n", label, type->TypeName.GetChars()); + } delete[] namestart; // free the allocated string buffer return state; } diff --git a/src/p_teleport.cpp b/src/p_teleport.cpp index 6c0d07a43..6493459ac 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -74,19 +74,21 @@ void ATeleportFog::PostBeginPlay () // //========================================================================== -void P_SpawnTeleportFog(fixed_t x, fixed_t y, fixed_t z, int spawnid) +void P_SpawnTeleportFog(AActor *mobj, fixed_t x, fixed_t y, fixed_t z, bool beforeTele, bool setTarget) { - PClassActor *fog = P_GetSpawnableType(spawnid); - - if (fog == NULL) + AActor *mo; + if ((beforeTele ? mobj->TeleFogSourceType : mobj->TeleFogDestType) == NULL) { - AActor *mo = Spawn ("TeleportFog", x, y, z + TELEFOGHEIGHT, ALLOW_REPLACE); + //Do nothing. + mo = NULL; } else { - AActor *mo = Spawn (fog, x, y, z, ALLOW_REPLACE); - if (mo != NULL) S_Sound(mo, CHAN_BODY, mo->SeeSound, 1.f, ATTN_NORM); + mo = Spawn((beforeTele ? mobj->TeleFogSourceType : mobj->TeleFogDestType), x, y, z, ALLOW_REPLACE); } + + if (mo != NULL && setTarget) + mo->target = mobj; } // @@ -96,6 +98,8 @@ void P_SpawnTeleportFog(fixed_t x, fixed_t y, fixed_t z, int spawnid) bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, bool useFog, bool sourceFog, bool keepOrientation, bool bHaltVelocity, bool keepHeight) { + bool predicting = (thing->player && (thing->player->cheats & CF_PREDICTING)); + fixed_t oldx; fixed_t oldy; fixed_t oldz; @@ -181,19 +185,20 @@ bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, angle = thing->angle; } // Spawn teleport fog at source and destination - if (sourceFog) + if (sourceFog && !predicting) { fixed_t fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; - AActor *fog = Spawn (oldx, oldy, oldz + fogDelta, ALLOW_REPLACE); - fog->target = thing; + P_SpawnTeleportFog(thing, oldx, oldy, oldz, true, true); //Passes the actor through which then pulls the TeleFog metadate types based on properties. } if (useFog) { - fixed_t fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; - an = angle >> ANGLETOFINESHIFT; - AActor *fog = Spawn (x + 20*finecosine[an], - y + 20*finesine[an], thing->z + fogDelta, ALLOW_REPLACE); - fog->target = thing; + if (!predicting) + { + fixed_t fogDelta = thing->flags & MF_MISSILE ? 0 : TELEFOGHEIGHT; + an = angle >> ANGLETOFINESHIFT; + P_SpawnTeleportFog(thing, x + 20 * finecosine[an], y + 20 * finesine[an], thing->z + fogDelta, false, true); + + } if (thing->player) { // [RH] Zoom player's field of vision @@ -226,7 +231,7 @@ bool P_Teleport (AActor *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, return true; } -static AActor *SelectTeleDest (int tid, int tag) +static AActor *SelectTeleDest (int tid, int tag, bool norandom) { AActor *searcher; @@ -276,7 +281,7 @@ static AActor *SelectTeleDest (int tid, int tag) } else { - if (count != 1) + if (count != 1 && !norandom) { count = 1 + (pr_teleport() % count); } @@ -323,6 +328,7 @@ static AActor *SelectTeleDest (int tid, int tag) bool EV_Teleport (int tid, int tag, line_t *line, int side, AActor *thing, bool fog, bool sourceFog, bool keepOrientation, bool haltVelocity, bool keepHeight) { + AActor *searcher; fixed_t z; angle_t angle = 0; @@ -334,6 +340,7 @@ bool EV_Teleport (int tid, int tag, line_t *line, int side, AActor *thing, bool { // Teleport function called with an invalid actor return false; } + bool predicting = (thing->player && (thing->player->cheats & CF_PREDICTING)); if (thing->flags2 & MF2_NOTELEPORT) { return false; @@ -342,7 +349,7 @@ bool EV_Teleport (int tid, int tag, line_t *line, int side, AActor *thing, bool { // Don't teleport if hit back of line, so you can get out of teleporter. return 0; } - searcher = SelectTeleDest (tid, tag); + searcher = SelectTeleDest(tid, tag, predicting); if (searcher == NULL) { return false; @@ -390,7 +397,7 @@ bool EV_Teleport (int tid, int tag, line_t *line, int side, AActor *thing, bool thing->velx = FixedMul(velx, c) - FixedMul(vely, s); thing->vely = FixedMul(vely, c) + FixedMul(velx, s); } - if ((velx | vely) == 0 && thing->player != NULL && thing->player->mo == thing) + if ((velx | vely) == 0 && thing->player != NULL && thing->player->mo == thing && !predicting) { thing->player->mo->PlayIdle (); } diff --git a/src/p_things.cpp b/src/p_things.cpp index d8a49a828..21037a27b 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -92,7 +92,7 @@ bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, mobj->angle = (angle != ANGLE_MAX ? angle : spot->angle); if (fog) { - Spawn (spot->x, spot->y, spot->z + TELEFOGHEIGHT, ALLOW_REPLACE); + P_SpawnTeleportFog(mobj, spot->x, spot->y, spot->z + TELEFOGHEIGHT, false); } if (mobj->flags & MF_SPECIAL) mobj->flags |= MF_DROPPED; // Don't respawn @@ -130,8 +130,8 @@ bool P_MoveThing(AActor *source, fixed_t x, fixed_t y, fixed_t z, bool fog) { if (fog) { - Spawn (x, y, z + TELEFOGHEIGHT, ALLOW_REPLACE); - Spawn (oldx, oldy, oldz + TELEFOGHEIGHT, ALLOW_REPLACE); + P_SpawnTeleportFog(source, x, y, z); + P_SpawnTeleportFog(source, oldx, oldy, oldz, false); } source->PrevX = x; source->PrevY = y; @@ -402,13 +402,17 @@ void P_RemoveThing(AActor * actor) // Don't remove live players. if (actor->player == NULL || actor != actor->player->mo) { + // Don't also remove owned inventory items + if (actor->IsKindOf(RUNTIME_CLASS(AInventory)) && static_cast(actor)->Owner != NULL) return; + // be friendly to the level statistics. ;) actor->ClearCounters(); actor->Destroy (); } + } -bool P_Thing_Raise(AActor *thing) +bool P_Thing_Raise(AActor *thing, AActor *raiser) { FState * RaiseState = thing->GetRaiseState(); if (RaiseState == NULL) @@ -441,10 +445,50 @@ bool P_Thing_Raise(AActor *thing) thing->Revive(); + if (raiser != NULL) + { + // Let's copy the friendliness of the one who raised it. + thing->CopyFriendliness(raiser, false); + } + thing->SetState (RaiseState); return true; } +bool P_Thing_CanRaise(AActor *thing) +{ + FState * RaiseState = thing->GetRaiseState(); + if (RaiseState == NULL) + { + return false; + } + + AActor *info = thing->GetDefault(); + + // Check against real height and radius + int oldflags = thing->flags; + fixed_t oldheight = thing->height; + fixed_t oldradius = thing->radius; + + thing->flags |= MF_SOLID; + thing->height = info->height; + thing->radius = info->radius; + + bool check = P_CheckPosition (thing, thing->x, thing->y); + + // Restore checked properties + thing->flags = oldflags; + thing->radius = oldradius; + thing->height = oldheight; + + if (!check) + { + return false; + } + + return true; +} + void P_Thing_SetVelocity(AActor *actor, fixed_t vx, fixed_t vy, fixed_t vz, bool add, bool setbob) { if (actor != NULL) diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 07e153ffa..44fe88110 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -120,7 +120,7 @@ void P_Ticker (void) for (i = 0; iTick (); // [RH] moved this here diff --git a/src/p_trace.cpp b/src/p_trace.cpp index d1b9f63ac..26852269a 100644 --- a/src/p_trace.cpp +++ b/src/p_trace.cpp @@ -366,7 +366,7 @@ bool FTraceInfo::TraceTraverse (int ptflags) Results->HitTexture = CurSector->GetTexture(sector_t::ceiling); } else if (entersector == NULL || - hitz <= bf || hitz >= bc || + hitz < bf || hitz > bc || in->d.line->flags & WallMask) { // hit the wall Results->HitType = TRACE_HitWall; diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index 7588199b4..541d7d396 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -696,6 +696,7 @@ public: case NAME_FillColor: th->fillcolor = CheckInt(key); + break; case NAME_Health: th->health = CheckInt(key); @@ -1030,11 +1031,16 @@ public: Flag(ld->flags, ML_BLOCKHITSCAN, key); continue; - // [Dusk] lock number + // [TP] Locks the special with a key case NAME_Locknumber: ld->locknumber = CheckInt(key); continue; + // [TP] Causes a 3d midtex to behave like an impassible line + case NAME_Midtex3dimpassible: + Flag(ld->flags, ML_3DMIDTEX_IMPASS, key); + continue; + default: break; } @@ -1081,6 +1087,10 @@ public: { ld->args[1] = -FName(arg1str); } + if ((ld->flags & ML_3DMIDTEX_IMPASS) && !(ld->flags & ML_3DMIDTEX)) // [TP] + { + Printf ("Line %d has midtex3dimpassible without midtex3d.\n", index); + } } //=========================================================================== @@ -1531,11 +1541,11 @@ public: double ulen = TVector3(cp[0], cp[1], cp[2]).Length(); // normalize the vector, it must have a length of 1 - sec->floorplane.a = FLOAT2FIXED(cp[0] / ulen); - sec->floorplane.b = FLOAT2FIXED(cp[1] / ulen); - sec->floorplane.c = FLOAT2FIXED(cp[2] / ulen); - sec->floorplane.d = FLOAT2FIXED(cp[3] / ulen); - sec->floorplane.ic = FLOAT2FIXED(ulen / cp[2]); + sec->ceilingplane.a = FLOAT2FIXED(cp[0] / ulen); + sec->ceilingplane.b = FLOAT2FIXED(cp[1] / ulen); + sec->ceilingplane.c = FLOAT2FIXED(cp[2] / ulen); + sec->ceilingplane.d = FLOAT2FIXED(cp[3] / ulen); + sec->ceilingplane.ic = FLOAT2FIXED(ulen / cp[2]); } if (lightcolor == -1 && fadecolor == -1 && desaturation == -1) diff --git a/src/p_user.cpp b/src/p_user.cpp index d5896ca7f..0e3ae7d60 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -62,8 +62,32 @@ static FRandom pr_skullpop ("SkullPop"); // Variables for prediction CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + P_PredictionLerpReset(); +} +CUSTOM_CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0.1f) + self = 0.1f; + P_PredictionLerpReset(); +} + +struct PredictPos +{ + int gametic; + fixed_t x; + fixed_t y; + fixed_t z; + fixed_t pitch; + fixed_t yaw; +} static PredictionLerpFrom, PredictionLerpResult, PredictionLast; +static int PredictionLerptics; + static player_t PredictionPlayerBackup; -static BYTE PredictionActorBackup[sizeof(AActor)]; +static BYTE PredictionActorBackup[sizeof(APlayerPawn)]; static TArray PredictionTouchingSectorsBackup; static TArray PredictionSectorListBackup; static TArray PredictionSector_sprev_Backup; @@ -238,7 +262,6 @@ player_t::player_t() health(0), inventorytics(0), CurrentPlayerClass(0), - backpack(0), fragcount(0), lastkilltime(0), multicount(0), @@ -271,28 +294,7 @@ player_t::player_t() respawn_time(0), camera(0), air_finished(0), - savedyaw(0), - savedpitch(0), - angle(0), - dest(0), - prev(0), - enemy(0), - missile(0), - mate(0), - last_mate(0), - t_active(0), - t_respawn(0), - t_strafe(0), - t_react(0), - t_fight(0), - t_roam(0), - t_rocket(0), - isbot(0), - first_shot(0), - sleft(0), - allround(0), - oldx(0), - oldy(0), + Bot(0), BlendR(0), BlendG(0), BlendB(0), @@ -311,7 +313,6 @@ player_t::player_t() memset (&cmd, 0, sizeof(cmd)); memset (frags, 0, sizeof(frags)); memset (psprites, 0, sizeof(psprites)); - memset (&skill, 0, sizeof(skill)); } player_t &player_t::operator=(const player_t &p) @@ -339,7 +340,6 @@ player_t &player_t::operator=(const player_t &p) health = p.health; inventorytics = p.inventorytics; CurrentPlayerClass = p.CurrentPlayerClass; - backpack = p.backpack; memcpy(frags, &p.frags, sizeof(frags)); fragcount = p.fragcount; lastkilltime = p.lastkilltime; @@ -380,30 +380,8 @@ player_t &player_t::operator=(const player_t &p) camera = p.camera; air_finished = p.air_finished; LastDamageType = p.LastDamageType; - savedyaw = p.savedyaw; - savedpitch = p.savedpitch; - angle = p.angle; - dest = p.dest; - prev = p.prev; - enemy = p.enemy; - missile = p.missile; - mate = p.mate; - last_mate = p.last_mate; + Bot = p.Bot; settings_controller = p.settings_controller; - skill = p.skill; - t_active = p.t_active; - t_respawn = p.t_respawn; - t_strafe = p.t_strafe; - t_react = p.t_react; - t_fight = p.t_fight; - t_roam = p.t_roam; - t_rocket = p.t_rocket; - isbot = p.isbot; - first_shot = p.first_shot; - sleft = p.sleft; - allround = p.allround; - oldx = p.oldx; - oldy = p.oldy; BlendR = p.BlendR; BlendG = p.BlendG; BlendB = p.BlendB; @@ -445,12 +423,7 @@ size_t player_t::FixPointers (const DObject *old, DObject *rep) if (*&poisoner == old) poisoner = replacement, changed++; if (*&attacker == old) attacker = replacement, changed++; if (*&camera == old) camera = replacement, changed++; - if (*&dest == old) dest = replacement, changed++; - if (*&prev == old) prev = replacement, changed++; - if (*&enemy == old) enemy = replacement, changed++; - if (*&missile == old) missile = replacement, changed++; - if (*&mate == old) mate = replacement, changed++; - if (*&last_mate == old) last_mate = replacement, changed++; + if (*&Bot == old) Bot = static_cast(rep), changed++; if (ReadyWeapon == old) ReadyWeapon = static_cast(rep), changed++; if (PendingWeapon == old) PendingWeapon = static_cast(rep), changed++; if (*&PremorphWeapon == old) PremorphWeapon = static_cast(rep), changed++; @@ -465,12 +438,7 @@ size_t player_t::PropagateMark() GC::Mark(poisoner); GC::Mark(attacker); GC::Mark(camera); - GC::Mark(dest); - GC::Mark(prev); - GC::Mark(enemy); - GC::Mark(missile); - GC::Mark(mate); - GC::Mark(last_mate); + GC::Mark(Bot); GC::Mark(ReadyWeapon); GC::Mark(ConversationNPC); GC::Mark(ConversationPC); @@ -712,7 +680,16 @@ void APlayerPawn::BeginPlay () FString crouchspritename = sprites[crouchsprite].name; int spritenorm = Wads.CheckNumForName(normspritename + "A1", ns_sprites); + if (spritenorm==-1) + { + spritenorm = Wads.CheckNumForName(normspritename + "A0", ns_sprites); + } + int spritecrouch = Wads.CheckNumForName(crouchspritename + "A1", ns_sprites); + if (spritecrouch==-1) + { + spritecrouch = Wads.CheckNumForName(crouchspritename + "A0", ns_sprites); + } if (spritenorm==-1 || spritecrouch ==-1) { @@ -794,10 +771,10 @@ void APlayerPawn::SetupWeaponSlots() // If we're the local player, then there's a bit more work to do. // This also applies if we're a bot and this is the net arbitrator. if (player - players == consoleplayer || - (player->isbot && consoleplayer == Net_Arbitrator)) + (player->Bot != NULL && consoleplayer == Net_Arbitrator)) { FWeaponSlots local_slots(player->weapons); - if (player->isbot) + if (player->Bot != NULL) { // Bots only need weapons from KEYCONF, not INI modifications. P_PlaybackKeyConfWeapons(&local_slots); } @@ -1311,9 +1288,6 @@ void APlayerPawn::GiveDefaultInventory () { if (player == NULL) return; - // [GRB] Give inventory specified in DECORATE - player->health = GetDefault ()->health; - // HexenArmor must always be the first item in the inventory because // it provides player class based protection that should not affect // any other protection item. @@ -2233,7 +2207,7 @@ void P_DeathThink (player_t *player) if ((player->cmd.ucmd.buttons & BT_USE || ((multiplayer || alwaysapplydmflags) && (dmflags & DF_FORCE_RESPAWN))) && !(dmflags2 & DF2_NO_RESPAWN)) { - if (level.time >= player->respawn_time || ((player->cmd.ucmd.buttons & BT_USE) && !player->isbot)) + if (level.time >= player->respawn_time || ((player->cmd.ucmd.buttons & BT_USE) && player->Bot == NULL)) { player->cls = NULL; // Force a new class if the player is using a random class player->playerstate = (multiplayer || (level.flags2 & LEVEL2_ALLOWRESPAWN)) ? PST_REBORN : PST_ENTER; @@ -2703,7 +2677,8 @@ void P_PlayerThink (player_t *player) { if (player->mo->waterlevel < 3 || (player->mo->flags2 & MF2_INVULNERABLE) || - (player->cheats & (CF_GODMODE | CF_NOCLIP2))) + (player->cheats & (CF_GODMODE | CF_NOCLIP2)) || + (player->cheats & CF_GODMODE2)) { player->mo->ResetAirSupply (); } @@ -2715,6 +2690,29 @@ void P_PlayerThink (player_t *player) } } +void P_PredictionLerpReset() +{ + PredictionLerptics = PredictionLast.gametic = PredictionLerpFrom.gametic = PredictionLerpResult.gametic = 0; +} + +bool P_LerpCalculate(PredictPos from, PredictPos to, PredictPos &result, float scale) +{ + FVector3 vecFrom(FIXED2DBL(from.x), FIXED2DBL(from.y), FIXED2DBL(from.z)); + FVector3 vecTo(FIXED2DBL(to.x), FIXED2DBL(to.y), FIXED2DBL(to.z)); + FVector3 vecResult; + vecResult = vecTo - vecFrom; + vecResult *= scale; + vecResult = vecResult + vecFrom; + FVector3 delta = vecResult - vecTo; + + result.x = FLOAT2FIXED(vecResult.X); + result.y = FLOAT2FIXED(vecResult.Y); + result.z = FLOAT2FIXED(vecResult.Z); + + // As a fail safe, assume extrapolation is the threshold. + return (delta.LengthSquared() > cl_predict_lerpthreshold && scale <= 1.00f); +} + void P_PredictPlayer (player_t *player) { int maxtic; @@ -2742,8 +2740,8 @@ void P_PredictPlayer (player_t *player) // Save original values for restoration later PredictionPlayerBackup = *player; - AActor *act = player->mo; - memcpy (PredictionActorBackup, &act->x, sizeof(AActor)-((BYTE *)&act->x-(BYTE *)act)); + APlayerPawn *act = player->mo; + memcpy(PredictionActorBackup, &act->x, sizeof(APlayerPawn) - ((BYTE *)&act->x - (BYTE *)act)); act->flags &= ~MF_PICKUP; act->flags2 &= ~MF2_PUSHWALL; @@ -2800,11 +2798,67 @@ void P_PredictPlayer (player_t *player) } act->BlockNode = NULL; + // Values too small to be usable for lerping can be considered "off". + bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus(); for (int i = gametic; i < maxtic; ++i) { + if (!NoInterpolateOld) + R_RebuildViewInterpolation(player); + player->cmd = localcmds[i % LOCALCMDTICS]; P_PlayerThink (player); player->mo->Tick (); + + if (CanLerp && PredictionLast.gametic > 0 && i == PredictionLast.gametic && !NoInterpolateOld) + { + // Z is not compared as lifts will alter this with no apparent change + // Make lerping less picky by only testing whole units + DoLerp = ((PredictionLast.x >> 16) != (player->mo->x >> 16) || + (PredictionLast.y >> 16) != (player->mo->y >> 16)); + + // Aditional Debug information + if (developer && DoLerp) + { + DPrintf("Lerp! Ltic (%d) && Ptic (%d) | Lx (%d) && Px (%d) | Ly (%d) && Py (%d)\n", + PredictionLast.gametic, i, + (PredictionLast.x >> 16), (player->mo->x >> 16), + (PredictionLast.y >> 16), (player->mo->y >> 16)); + } + } + } + + if (CanLerp) + { + if (NoInterpolateOld) + P_PredictionLerpReset(); + + else if (DoLerp) + { + // If lerping is already in effect, use the previous camera postion so the view doesn't suddenly snap + PredictionLerpFrom = (PredictionLerptics == 0) ? PredictionLast : PredictionLerpResult; + PredictionLerptics = 1; + } + + PredictionLast.gametic = maxtic - 1; + PredictionLast.x = player->mo->x; + PredictionLast.y = player->mo->y; + PredictionLast.z = player->mo->z; + + if (PredictionLerptics > 0) + { + if (PredictionLerpFrom.gametic > 0 && + P_LerpCalculate(PredictionLerpFrom, PredictionLast, PredictionLerpResult, (float)PredictionLerptics * cl_predict_lerpscale)) + { + PredictionLerptics++; + player->mo->x = PredictionLerpResult.x; + player->mo->y = PredictionLerpResult.y; + player->mo->z = PredictionLerpResult.z; + } + else + { + PredictionLerptics = 0; + } + } } } @@ -2817,9 +2871,12 @@ void P_UnPredictPlayer () if (player->cheats & CF_PREDICTING) { unsigned int i; - AActor *act = player->mo; + APlayerPawn *act = player->mo; AActor *savedcamera = player->camera; + TObjPtr InvSel = act->InvSel; + int inventorytics = player->inventorytics; + *player = PredictionPlayerBackup; // Restore the camera instead of using the backup's copy, because spynext/prev @@ -2827,7 +2884,7 @@ void P_UnPredictPlayer () player->camera = savedcamera; act->UnlinkFromWorld(); - memcpy(&act->x, PredictionActorBackup, sizeof(AActor)-((BYTE *)&act->x - (BYTE *)act)); + memcpy(&act->x, PredictionActorBackup, sizeof(APlayerPawn) - ((BYTE *)&act->x - (BYTE *)act)); // The blockmap ordering needs to remain unchanged, too. // Restore sector links and refrences. @@ -2932,6 +2989,9 @@ void P_UnPredictPlayer () } block = block->NextBlock; } + + act->InvSel = InvSel; + player->inventorytics = inventorytics; } } @@ -2962,9 +3022,13 @@ void player_t::Serialize (FArchive &arc) << vely << centering << health - << inventorytics - << backpack - << fragcount + << inventorytics; + if (SaveVersion < 4513) + { + bool backpack; + arc << backpack; + } + arc << fragcount << spreecount << multicount << lastkilltime @@ -2993,9 +3057,17 @@ void player_t::Serialize (FArchive &arc) << respawn_time << air_finished << turnticks - << oldbuttons - << isbot - << BlendR + << oldbuttons; + bool IsBot; + if (SaveVersion >= 4514) + { + arc << Bot; + } + else + { + arc << IsBot; + } + arc << BlendR << BlendG << BlendB << BlendA; @@ -3078,33 +3150,37 @@ void player_t::Serialize (FArchive &arc) onground = (mo->z <= mo->floorz) || (mo->flags2 & MF2_ONMOBJ) || (mo->BounceFlags & BOUNCE_MBF) || (cheats & CF_NOCLIP2); } - if (isbot) + if (SaveVersion < 4514 && IsBot) { - arc << angle - << dest - << prev - << enemy - << missile - << mate - << last_mate - << skill - << t_active - << t_respawn - << t_strafe - << t_react - << t_fight - << t_roam - << t_rocket - << first_shot - << sleft - << allround - << oldx - << oldy; + Bot = new DBot; + + arc << Bot->angle + << Bot->dest + << Bot->prev + << Bot->enemy + << Bot->missile + << Bot->mate + << Bot->last_mate + << Bot->skill + << Bot->t_active + << Bot->t_respawn + << Bot->t_strafe + << Bot->t_react + << Bot->t_fight + << Bot->t_roam + << Bot->t_rocket + << Bot->first_shot + << Bot->sleft + << Bot->allround + << Bot->oldx + << Bot->oldy; } - else + + if (SaveVersion < 4516 && Bot != NULL) { - dest = prev = enemy = missile = mate = last_mate = NULL; + Bot->player = this; } + if (arc.IsLoading ()) { // If the player reloaded because they pressed +use after dying, we diff --git a/src/r_things.cpp b/src/r_things.cpp index ca4f23999..be2ea150f 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -95,6 +95,7 @@ extern fixed_t globaluclip, globaldclip; EXTERN_CVAR (Bool, st_scale) EXTERN_CVAR(Bool, r_shadercolormaps) EXTERN_CVAR(Int, r_drawfuzz) +EXTERN_CVAR(Bool, r_deathcamera); // // Sprite rotation 0 is facing the viewer, @@ -1417,7 +1418,8 @@ void R_DrawPlayerSprites () if (!r_drawplayersprites || !camera->player || - (players[consoleplayer].cheats & CF_CHASECAM)) + (players[consoleplayer].cheats & CF_CHASECAM) || + (r_deathcamera && camera->health <= 0)) return; if(fixedlightlev < 0 && viewsector->e && viewsector->e->XFloor.lightlist.Size()) { @@ -2083,9 +2085,22 @@ void R_DrawSprite (vissprite_t *spr) r1 = MAX (ds->x1, x1); r2 = MIN (ds->x2, x2); + fixed_t neardepth, fardepth; + if (!spr->bWallSprite) + { + if (ds->sz1 < ds->sz2) + { + neardepth = ds->sz1, fardepth = ds->sz2; + } + else + { + neardepth = ds->sz2, fardepth = ds->sz1; + } + } // Check if sprite is in front of draw seg: - if (DMulScale32(spr->gy - ds->curline->v1->y, ds->curline->v2->x - ds->curline->v1->x, - ds->curline->v1->x - spr->gx, ds->curline->v2->y - ds->curline->v1->y) <= 0) + if ((!spr->bWallSprite && neardepth > spr->depth) || ((spr->bWallSprite || fardepth > spr->depth) && + DMulScale32(spr->gy - ds->curline->v1->y, ds->curline->v2->x - ds->curline->v1->x, + ds->curline->v1->x - spr->gx, ds->curline->v2->y - ds->curline->v1->y) <= 0)) { // seg is behind sprite, so draw the mid texture if it has one if (ds->maskedtexturecol != -1 || ds->bFogBoundary) diff --git a/src/r_utility.cpp b/src/r_utility.cpp index 422598ae7..17e1dffe2 100644 --- a/src/r_utility.cpp +++ b/src/r_utility.cpp @@ -729,6 +729,42 @@ void R_ClearPastViewer (AActor *actor) } } +//========================================================================== +// +// R_RebuildViewInterpolation +// +//========================================================================== + +void R_RebuildViewInterpolation(player_t *player) +{ + if (player == NULL || player->camera == NULL) + return; + + if (!NoInterpolateView) + return; + NoInterpolateView = false; + + InterpolationViewer *iview = FindPastViewer(player->camera); + + iview->oviewx = iview->nviewx; + iview->oviewy = iview->nviewy; + iview->oviewz = iview->nviewz; + iview->oviewpitch = iview->nviewpitch; + iview->oviewangle = iview->nviewangle; +} + +//========================================================================== +// +// R_GetViewInterpolationStatus +// +//========================================================================== + +bool R_GetViewInterpolationStatus() +{ + return NoInterpolateView; +} + + //========================================================================== // // R_SetupFrame diff --git a/src/r_utility.h b/src/r_utility.h index 85ca7c410..2d9aac086 100644 --- a/src/r_utility.h +++ b/src/r_utility.h @@ -61,6 +61,8 @@ inline angle_t R_PointToAngle (fixed_t x, fixed_t y) { return R_PointToAngle2 (v subsector_t *R_PointInSubsector (fixed_t x, fixed_t y); fixed_t R_PointToDist2 (fixed_t dx, fixed_t dy); void R_ResetViewInterpolation (); +void R_RebuildViewInterpolation(player_t *player); +bool R_GetViewInterpolationStatus(); void R_SetViewSize (int blocks); void R_SetFOV (float fov); float R_GetFOV (); diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index 5b2e4e7c1..f7a40da9b 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -2347,14 +2347,14 @@ class AMusicChanger : public ASectorAction { DECLARE_CLASS (AMusicChanger, ASectorAction) public: - virtual bool TriggerAction (AActor *triggerer, int activationType); + virtual bool DoTriggerAction (AActor *triggerer, int activationType); virtual void Tick(); virtual void PostBeginPlay(); }; IMPLEMENT_CLASS(AMusicChanger) -bool AMusicChanger::TriggerAction (AActor *triggerer, int activationType) +bool AMusicChanger::DoTriggerAction (AActor *triggerer, int activationType) { if (activationType & SECSPAC_Enter) { @@ -2364,7 +2364,7 @@ bool AMusicChanger::TriggerAction (AActor *triggerer, int activationType) reactiontime = 30; } } - return Super::TriggerAction (triggerer, activationType); + return Super::DoTriggerAction (triggerer, activationType); } void AMusicChanger::Tick() diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 030d4da66..483502692 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -1321,7 +1321,7 @@ sfxinfo_t *S_LoadSound(sfxinfo_t *sfx) // If the sound is voc, use the custom loader. if (strncmp ((const char *)sfxstart, "Creative Voice File", 19) == 0) { - sfx->data = GSnd->LoadSoundVoc(sfxstart, len); + sfx->data = GSnd->LoadSoundVoc(sfxstart, size); } // If the sound is raw, just load it as such. // Otherwise, try the sound as DMX format. @@ -1575,7 +1575,7 @@ void S_RelinkSound (AActor *from, AActor *to) { chan->Actor = to; } - else if (!(chan->ChanFlags & CHAN_LOOP)) + else if (!(chan->ChanFlags & CHAN_LOOP) && !(compatflags2 & COMPATF2_SOUNDCUTOFF)) { chan->Actor = NULL; chan->SourceType = SOURCE_Unattached; @@ -1783,21 +1783,13 @@ void S_SetSoundPaused (int state) { if (state) { - if (paused <= 0) + if (paused == 0) { S_ResumeSound(true); if (GSnd != NULL) { GSnd->SetInactive(SoundRenderer::INACTIVE_Active); } - if (!netgame -#ifdef _DEBUG - && !demoplayback -#endif - ) - { - paused = 0; - } } } else @@ -1811,16 +1803,16 @@ void S_SetSoundPaused (int state) SoundRenderer::INACTIVE_Complete : SoundRenderer::INACTIVE_Mute); } - if (!netgame -#ifdef _DEBUG - && !demoplayback -#endif - ) - { - paused = -1; - } } } + if (!netgame +#ifdef _DEBUG + && !demoplayback +#endif + ) + { + pauseext = !state; + } } //========================================================================== diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index 4da98736e..1ebd1e65a 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -185,6 +185,7 @@ std2: 'random' { RET(TK_Random); } 'random2' { RET(TK_Random2); } 'frandom' { RET(TK_FRandom); } + 'pick' { RET(TK_Pick); } L (L|D)* { RET(TK_Identifier); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index 6bc01c184..ab6feba89 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -117,6 +117,7 @@ xx(TK_Abs, "'abs'") xx(TK_Random, "'random'") xx(TK_Random2, "'random2'") xx(TK_FRandom, "'frandom'") +xx(TK_Pick, "'pick'") xx(TK_Is, "'is'") xx(TK_Replaces, "'replaces'") xx(TK_Vector, "'vector'") diff --git a/src/sdl/i_gui.cpp b/src/sdl/i_gui.cpp new file mode 100644 index 000000000..b40fb7517 --- /dev/null +++ b/src/sdl/i_gui.cpp @@ -0,0 +1,131 @@ + +// Moved from sdl/i_system.cpp + +#include + +#include + +#include "bitmap.h" +#include "v_palette.h" +#include "textures.h" + +extern SDL_Surface *cursorSurface; +extern SDL_Rect cursorBlit; + +#ifdef USE_XCURSOR +// Xlib has its own GC, so don't let it interfere. +#define GC XGC +#include +#undef GC + +bool UseXCursor; +SDL_Cursor *X11Cursor; +SDL_Cursor *FirstCursor; + +// Hack! Hack! SDL does not provide a clean way to get the XDisplay. +// On the other hand, there are no more planned updates for SDL 1.2, +// so we should be fine making assumptions. +struct SDL_PrivateVideoData +{ + int local_X11; + Display *X11_Display; +}; + +struct SDL_VideoDevice +{ + const char *name; + int (*functions[9])(); + SDL_VideoInfo info; + SDL_PixelFormat *displayformatalphapixel; + int (*morefuncs[9])(); + Uint16 *gamma; + int (*somefuncs[9])(); + unsigned int texture; // Only here if SDL was compiled with OpenGL support. Ack! + int is_32bit; + int (*itsafuncs[13])(); + SDL_Surface *surfaces[3]; + SDL_Palette *physpal; + SDL_Color *gammacols; + char *wm_strings[2]; + int offsets[2]; + SDL_GrabMode input_grab; + int handles_any_size; + SDL_PrivateVideoData *hidden; // Why did they have to bury this so far in? +}; + +extern SDL_VideoDevice *current_video; +#define SDL_Display (current_video->hidden->X11_Display) + +SDL_Cursor *CreateColorCursor(FTexture *cursorpic) +{ + return NULL; +} +#endif + +bool I_SetCursor(FTexture *cursorpic) +{ + if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null) + { + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + +#ifdef USE_XCURSOR + if (UseXCursor) + { + if (FirstCursor == NULL) + { + FirstCursor = SDL_GetCursor(); + } + X11Cursor = CreateColorCursor(cursorpic); + if (X11Cursor != NULL) + { + SDL_SetCursor(X11Cursor); + return true; + } + } +#endif + if (cursorSurface == NULL) + cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0)); + + SDL_ShowCursor(0); + SDL_LockSurface(cursorSurface); + BYTE buffer[32*32*4]; + memset(buffer, 0, 32*32*4); + FBitmap bmp(buffer, 32*4, 32, 32); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4); + SDL_UnlockSurface(cursorSurface); + } + else + { + SDL_ShowCursor(1); + + if (cursorSurface != NULL) + { + SDL_FreeSurface(cursorSurface); + cursorSurface = NULL; + } +#ifdef USE_XCURSOR + if (X11Cursor != NULL) + { + SDL_SetCursor(FirstCursor); + SDL_FreeCursor(X11Cursor); + X11Cursor = NULL; + } +#endif + } + return true; +} + +void I_SetMainWindowVisible(bool visible) +{ + +} + +const char* I_GetBackEndName() +{ + return "SDL"; +} diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 27324edf6..7a18fb05f 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -75,6 +75,10 @@ extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*)); +#ifdef __APPLE__ +void Mac_I_FatalError(const char* errortext); +#endif + // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -235,6 +239,8 @@ static void unprotect_rtext() void I_StartupJoysticks(); void I_ShutdownJoysticks(); +const char* I_GetBackEndName(); + int main (int argc, char **argv) { #if !defined (__APPLE__) @@ -244,8 +250,8 @@ int main (int argc, char **argv) } #endif // !__APPLE__ - printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", - GetVersionString(), GetGitTime(), __DATE__); + printf(GAMENAME" %s - %s - %s version\nCompiled on %s\n", + GetVersionString(), GetGitTime(), I_GetBackEndName(), __DATE__); seteuid (getuid ()); std::set_new_handler (NewFailure); @@ -356,6 +362,11 @@ int main (int argc, char **argv) I_ShutdownJoysticks(); if (error.GetMessage ()) fprintf (stderr, "%s\n", error.GetMessage ()); + +#ifdef __APPLE__ + Mac_I_FatalError(error.GetMessage()); +#endif // __APPLE__ + exit (-1); } catch (...) diff --git a/src/sdl/i_steam.cpp b/src/sdl/i_steam.cpp new file mode 100644 index 000000000..23e74af5a --- /dev/null +++ b/src/sdl/i_steam.cpp @@ -0,0 +1,217 @@ +/* +** i_steam.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2013 Braden Obrzut +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include + +#include "doomerrors.h" +#include "d_main.h" +#include "zstring.h" +#include "sc_man.h" + +static void PSR_FindEndBlock(FScanner &sc) +{ + int depth = 1; + do + { + if(sc.CheckToken('}')) + --depth; + else if(sc.CheckToken('{')) + ++depth; + else + sc.MustGetAnyToken(); + } + while(depth); +} +static void PSR_SkipBlock(FScanner &sc) +{ + sc.MustGetToken('{'); + PSR_FindEndBlock(sc); +} +static bool PSR_FindAndEnterBlock(FScanner &sc, const char* keyword) +{ + // Finds a block with a given keyword and then enter it (opening brace) + // Should be closed with PSR_FindEndBlock + while(sc.GetToken()) + { + if(sc.TokenType == '}') + { + sc.UnGet(); + return false; + } + + sc.TokenMustBe(TK_StringConst); + if(!sc.Compare(keyword)) + { + if(!sc.CheckToken(TK_StringConst)) + PSR_SkipBlock(sc); + } + else + { + sc.MustGetToken('{'); + return true; + } + } + return false; +} +static TArray PSR_ReadBaseInstalls(FScanner &sc) +{ + TArray result; + + // Get a list of possible install directories. + while(sc.GetToken()) + { + if(sc.TokenType == '}') + break; + + sc.TokenMustBe(TK_StringConst); + FString key(sc.String); + if(key.Left(18).CompareNoCase("BaseInstallFolder_") == 0) + { + sc.MustGetToken(TK_StringConst); + result.Push(FString(sc.String) + "/steamapps/common"); + } + else + { + if(sc.CheckToken('{')) + PSR_FindEndBlock(sc); + else + sc.MustGetToken(TK_StringConst); + } + } + + return result; +} +static TArray ParseSteamRegistry(const char* path) +{ + TArray dirs; + + // Read registry data + FScanner sc; + sc.OpenFile(path); + sc.SetCMode(true); + + // Find the SteamApps listing + if(PSR_FindAndEnterBlock(sc, "InstallConfigStore")) + { + if(PSR_FindAndEnterBlock(sc, "Software")) + { + if(PSR_FindAndEnterBlock(sc, "Valve")) + { + if(PSR_FindAndEnterBlock(sc, "Steam")) + { + dirs = PSR_ReadBaseInstalls(sc); + } + PSR_FindEndBlock(sc); + } + PSR_FindEndBlock(sc); + } + PSR_FindEndBlock(sc); + } + + return dirs; +} + +static struct SteamAppInfo +{ + const char* const BasePath; + const int AppID; +} AppInfo[] = +{ + /*{"doom 2/base", 2300}, + {"final doom/base", 2290}, + {"heretic shadow of the serpent riders/base", 2390}, + {"hexen/base", 2360}, + {"hexen deathkings of the dark citadel/base", 2370}, + {"ultimate doom/base", 2280}, + {"DOOM 3 BFG Edition/base/wads", 208200},*/ + {"Strife", 317040} +}; + +TArray I_GetSteamPath() +{ + TArray result; + TArray SteamInstallFolders; + + // Linux and OS X actually allow the user to install to any location, so + // we need to figure out on an app-by-app basis where the game is installed. + // To do so, we read the virtual registry. +#ifdef __APPLE__ + FString OSX_FindApplicationSupport(); + + FString regPath = OSX_FindApplicationSupport() + "/Steam/config/config.vdf"; + try + { + SteamInstallFolders = ParseSteamRegistry(regPath); + } + catch(class CDoomError &error) + { + // If we can't parse for some reason just pretend we can't find anything. + return result; + } + + SteamInstallFolders.Push(OSX_FindApplicationSupport() + "/Steam/SteamApps/common"); +#else + char* home = getenv("HOME"); + if(home != NULL && *home != '\0') + { + FString regPath; + regPath.Format("%s/.local/share/Steam/config/config.vdf", home); + try + { + SteamInstallFolders = ParseSteamRegistry(regPath); + } + catch(class CDoomError &error) + { + // If we can't parse for some reason just pretend we can't find anything. + return result; + } + + regPath.Format("%s/.local/share/Steam/SteamApps/common", home); + SteamInstallFolders.Push(regPath); + } +#endif + + for(unsigned int i = 0;i < SteamInstallFolders.Size();++i) + { + for(unsigned int app = 0;app < countof(AppInfo);++app) + { + struct stat st; + FString candidate(SteamInstallFolders[i] + "/" + AppInfo[app].BasePath); + if(stat(candidate, &st) == 0 && S_ISDIR(st.st_mode)) + result.Push(candidate); + } + } + + return result; +} diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 9b3027a7d..41fdef323 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -71,12 +71,9 @@ #include "m_fixed.h" #include "g_level.h" -#ifdef USE_XCURSOR -// Xlib has its own GC, so don't let it interfere. -#define GC XGC -#include -#undef GC -#endif +#ifdef __APPLE__ +#include +#endif // __APPLE__ EXTERN_CVAR (String, language) @@ -91,11 +88,6 @@ extern bool GtkAvailable; #elif defined(__APPLE__) int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad); #endif -#ifdef USE_XCURSOR -bool UseXCursor; -SDL_Cursor *X11Cursor; -SDL_Cursor *FirstCursor; -#endif DWORD LanguageIDs[4]; @@ -122,190 +114,6 @@ void I_EndRead(void) } -static DWORD TicStart; -static DWORD TicNext; -static DWORD BaseTime; -static int TicFrozen; - -// Signal based timer. -static Semaphore timerWait; -static int tics; -static DWORD sig_start, sig_next; - -void I_SelectTimer(); - -// [RH] Returns time in milliseconds -unsigned int I_MSTime (void) -{ - unsigned int time = SDL_GetTicks (); - return time - BaseTime; -} - -// Exactly the same thing, but based does no modification to the time. -unsigned int I_FPSTime() -{ - return SDL_GetTicks(); -} - -// -// I_GetTime -// returns time in 1/35th second tics -// -int I_GetTimeSelect (bool saveMS) -{ - I_SelectTimer(); - return I_GetTime (saveMS); -} - -int I_GetTimePolled (bool saveMS) -{ - if (TicFrozen != 0) - { - return TicFrozen; - } - - DWORD tm = SDL_GetTicks(); - - if (saveMS) - { - TicStart = tm; - TicNext = Scale((Scale (tm, TICRATE, 1000) + 1), 1000, TICRATE); - } - return Scale(tm - BaseTime, TICRATE, 1000); -} - -int I_GetTimeSignaled (bool saveMS) -{ - if (saveMS) - { - TicStart = sig_start; - TicNext = sig_next; - } - return tics; -} - -int I_WaitForTicPolled (int prevtic) -{ - int time; - - assert (TicFrozen == 0); - while ((time = I_GetTimePolled(false)) <= prevtic) - ; - - return time; -} - -int I_WaitForTicSignaled (int prevtic) -{ - assert (TicFrozen == 0); - - while(tics <= prevtic) - { - SEMAPHORE_WAIT(timerWait) - } - - return tics; -} - -void I_FreezeTimeSelect (bool frozen) -{ - I_SelectTimer(); - return I_FreezeTime (frozen); -} - -void I_FreezeTimePolled (bool frozen) -{ - if (frozen) - { - assert(TicFrozen == 0); - TicFrozen = I_GetTimePolled(false); - } - else - { - assert(TicFrozen != 0); - int froze = TicFrozen; - TicFrozen = 0; - int now = I_GetTimePolled(false); - BaseTime += (now - froze) * 1000 / TICRATE; - } -} - -void I_FreezeTimeSignaled (bool frozen) -{ - TicFrozen = frozen; -} - -int I_WaitForTicSelect (int prevtic) -{ - I_SelectTimer(); - return I_WaitForTic (prevtic); -} - -// -// I_HandleAlarm -// Should be called every time there is an alarm. -// -void I_HandleAlarm (int sig) -{ - if(!TicFrozen) - tics++; - sig_start = SDL_GetTicks(); - sig_next = Scale((Scale (sig_start, TICRATE, 1000) + 1), 1000, TICRATE); - SEMAPHORE_SIGNAL(timerWait) -} - -// -// I_SelectTimer -// Sets up the timer function based on if we can use signals for efficent CPU -// usage. -// -void I_SelectTimer() -{ - SEMAPHORE_INIT(timerWait, 0, 0) -#ifndef __sun - signal(SIGALRM, I_HandleAlarm); -#else - struct sigaction alrmaction; - sigaction(SIGALRM, NULL, &alrmaction); - alrmaction.sa_handler = I_HandleAlarm; - sigaction(SIGALRM, &alrmaction, NULL); -#endif - - struct itimerval itv; - itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; - itv.it_interval.tv_usec = itv.it_value.tv_usec = 1000000/TICRATE; - - if (setitimer(ITIMER_REAL, &itv, NULL) != 0) - { - I_GetTime = I_GetTimePolled; - I_FreezeTime = I_FreezeTimePolled; - I_WaitForTic = I_WaitForTicPolled; - } - else - { - I_GetTime = I_GetTimeSignaled; - I_FreezeTime = I_FreezeTimeSignaled; - I_WaitForTic = I_WaitForTicSignaled; - } -} - -// Returns the fractional amount of a tic passed since the most recent tic -fixed_t I_GetTimeFrac (uint32 *ms) -{ - DWORD now = SDL_GetTicks (); - if (ms) *ms = TicNext; - DWORD step = TicNext - TicStart; - if (step == 0) - { - return FRACUNIT; - } - else - { - fixed_t frac = clamp ((now - TicStart)*FRACUNIT/step, 0, FRACUNIT); - return frac; - } -} - void I_WaitVBL (int count) { // I_WaitVBL is never used to actually synchronize to the @@ -327,6 +135,9 @@ void SetLanguageIDs () LanguageIDs[3] = LanguageIDs[2] = LanguageIDs[1] = LanguageIDs[0] = lang; } +void I_InitTimer (); +void I_ShutdownTimer (); + // // I_Init // @@ -335,11 +146,9 @@ void I_Init (void) CheckCPUID (&CPU); DumpCPUInfo (&CPU); - I_GetTime = I_GetTimeSelect; - I_WaitForTic = I_WaitForTicSelect; - I_FreezeTime = I_FreezeTimeSelect; atterm (I_ShutdownSound); I_InitSound (); + I_InitTimer (); } // @@ -355,6 +164,8 @@ void I_Quit (void) G_CheckDemoStatus(); C_DeinitConsole(); + + I_ShutdownTimer(); } @@ -790,6 +601,10 @@ int I_FindAttr (findstate_t *fileinfo) return 0; } +#ifdef __APPLE__ +static PasteboardRef s_clipboard; +#endif // __APPLE__ + // Clipboard support requires GTK+ // TODO: GTK+ uses UTF-8. We don't, so some conversions would be appropriate. void I_PutInClipboard (const char *str) @@ -810,6 +625,23 @@ void I_PutInClipboard (const char *str) } */ } +#elif defined __APPLE__ + if (NULL == s_clipboard) + { + PasteboardCreate(kPasteboardClipboard, &s_clipboard); + } + + PasteboardClear(s_clipboard); + PasteboardSynchronize(s_clipboard); + + const CFDataRef textData = CFDataCreate(kCFAllocatorDefault, + reinterpret_cast(str), strlen(str)); + + if (NULL != textData) + { + PasteboardPutItemFlavor(s_clipboard, PasteboardItemID(1), + CFSTR("public.utf8-plain-text"), textData, 0); + } #endif } @@ -831,6 +663,61 @@ FString I_GetFromClipboard (bool use_primary_selection) } } } +#elif defined __APPLE__ + FString result; + + if (NULL == s_clipboard) + { + PasteboardCreate(kPasteboardClipboard, &s_clipboard); + } + + PasteboardSynchronize(s_clipboard); + + ItemCount itemCount = 0; + PasteboardGetItemCount(s_clipboard, &itemCount); + + if (0 == itemCount) + { + return FString(); + } + + PasteboardItemID itemID; + + if (0 != PasteboardGetItemIdentifier(s_clipboard, 1, &itemID)) + { + return FString(); + } + + CFArrayRef flavorTypeArray; + + if (0 != PasteboardCopyItemFlavors(s_clipboard, itemID, &flavorTypeArray)) + { + return FString(); + } + + const CFIndex flavorCount = CFArrayGetCount(flavorTypeArray); + + for (CFIndex flavorIndex = 0; flavorIndex < flavorCount; ++flavorIndex) + { + const CFStringRef flavorType = static_cast( + CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex)); + + if (UTTypeConformsTo(flavorType, CFSTR("public.utf8-plain-text"))) + { + CFDataRef flavorData; + + if (0 == PasteboardCopyItemFlavorData(s_clipboard, itemID, flavorType, &flavorData)) + { + result += reinterpret_cast(CFDataGetBytePtr(flavorData)); + } + + CFRelease(flavorData); + } + } + + CFRelease(flavorTypeArray); + + return result; #endif return ""; } @@ -856,104 +743,3 @@ unsigned int I_MakeRNGSeed() } return seed; } - -#ifdef USE_XCURSOR -// Hack! Hack! SDL does not provide a clean way to get the XDisplay. -// On the other hand, there are no more planned updates for SDL 1.2, -// so we should be fine making assumptions. -struct SDL_PrivateVideoData -{ - int local_X11; - Display *X11_Display; -}; - -struct SDL_VideoDevice -{ - const char *name; - int (*functions[9])(); - SDL_VideoInfo info; - SDL_PixelFormat *displayformatalphapixel; - int (*morefuncs[9])(); - Uint16 *gamma; - int (*somefuncs[9])(); - unsigned int texture; // Only here if SDL was compiled with OpenGL support. Ack! - int is_32bit; - int (*itsafuncs[13])(); - SDL_Surface *surfaces[3]; - SDL_Palette *physpal; - SDL_Color *gammacols; - char *wm_strings[2]; - int offsets[2]; - SDL_GrabMode input_grab; - int handles_any_size; - SDL_PrivateVideoData *hidden; // Why did they have to bury this so far in? -}; - -extern SDL_VideoDevice *current_video; -#define SDL_Display (current_video->hidden->X11_Display) - -SDL_Cursor *CreateColorCursor(FTexture *cursorpic) -{ - return NULL; -} -#endif - -SDL_Surface *cursorSurface = NULL; -SDL_Rect cursorBlit = {0, 0, 32, 32}; -bool I_SetCursor(FTexture *cursorpic) -{ - if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null) - { - // Must be no larger than 32x32. - if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) - { - return false; - } - -#ifdef USE_XCURSOR - if (UseXCursor) - { - if (FirstCursor == NULL) - { - FirstCursor = SDL_GetCursor(); - } - X11Cursor = CreateColorCursor(cursorpic); - if (X11Cursor != NULL) - { - SDL_SetCursor(X11Cursor); - return true; - } - } -#endif - if (cursorSurface == NULL) - cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0)); - - SDL_ShowCursor(0); - SDL_LockSurface(cursorSurface); - BYTE buffer[32*32*4]; - memset(buffer, 0, 32*32*4); - FBitmap bmp(buffer, 32*4, 32, 32); - cursorpic->CopyTrueColorPixels(&bmp, 0, 0); - memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4); - SDL_UnlockSurface(cursorSurface); - } - else - { - SDL_ShowCursor(1); - - if (cursorSurface != NULL) - { - SDL_FreeSurface(cursorSurface); - cursorSurface = NULL; - } -#ifdef USE_XCURSOR - if (X11Cursor != NULL) - { - SDL_SetCursor(FirstCursor); - SDL_FreeCursor(X11Cursor); - X11Cursor = NULL; - } -#endif - } - return true; -} diff --git a/src/sdl/i_system.h b/src/sdl/i_system.h index a3341f4c5..fa03d3c37 100644 --- a/src/sdl/i_system.h +++ b/src/sdl/i_system.h @@ -120,6 +120,10 @@ void I_SetIWADInfo (); // Pick from multiple IWADs to use int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad); +// [RH] Checks the registry for Steam's install path, so we can scan its +// directories for IWADs if the user purchased any through Steam. +TArray I_GetSteamPath(); + // The ini could not be saved at exit bool I_WriteIniFailed (); diff --git a/src/sdl/i_timer.cpp b/src/sdl/i_timer.cpp new file mode 100644 index 000000000..e3f9906b6 --- /dev/null +++ b/src/sdl/i_timer.cpp @@ -0,0 +1,206 @@ + +// Moved from sdl/i_system.cpp + +#include +#include +#include + +#include + +#include "basictypes.h" +#include "basicinlines.h" +#include "hardware.h" +#include "i_system.h" +#include "templates.h" + + +static DWORD TicStart; +static DWORD BaseTime; +static int TicFrozen; + +// Signal based timer. +static Semaphore timerWait; +static int tics; +static DWORD sig_start; + +void I_SelectTimer(); + +// [RH] Returns time in milliseconds +unsigned int I_MSTime (void) +{ + unsigned int time = SDL_GetTicks (); + return time - BaseTime; +} + +// Exactly the same thing, but based does no modification to the time. +unsigned int I_FPSTime() +{ + return SDL_GetTicks(); +} + +// +// I_GetTime +// returns time in 1/35th second tics +// +int I_GetTimeSelect (bool saveMS) +{ + I_SelectTimer(); + return I_GetTime (saveMS); +} + +int I_GetTimePolled (bool saveMS) +{ + if (TicFrozen != 0) + { + return TicFrozen; + } + + DWORD tm = SDL_GetTicks(); + + if (saveMS) + { + TicStart = tm; + } + return Scale(tm - BaseTime, TICRATE, 1000); +} + +int I_GetTimeSignaled (bool saveMS) +{ + if (saveMS) + { + TicStart = sig_start; + } + return tics; +} + +int I_WaitForTicPolled (int prevtic) +{ + int time; + + assert (TicFrozen == 0); + while ((time = I_GetTimePolled(false)) <= prevtic) + ; + + return time; +} + +int I_WaitForTicSignaled (int prevtic) +{ + assert (TicFrozen == 0); + + while(tics <= prevtic) + { + SEMAPHORE_WAIT(timerWait) + } + + return tics; +} + +void I_FreezeTimeSelect (bool frozen) +{ + I_SelectTimer(); + return I_FreezeTime (frozen); +} + +void I_FreezeTimePolled (bool frozen) +{ + if (frozen) + { + assert(TicFrozen == 0); + TicFrozen = I_GetTimePolled(false); + } + else + { + assert(TicFrozen != 0); + int froze = TicFrozen; + TicFrozen = 0; + int now = I_GetTimePolled(false); + BaseTime += (now - froze) * 1000 / TICRATE; + } +} + +void I_FreezeTimeSignaled (bool frozen) +{ + TicFrozen = frozen; +} + +int I_WaitForTicSelect (int prevtic) +{ + I_SelectTimer(); + return I_WaitForTic (prevtic); +} + +// +// I_HandleAlarm +// Should be called every time there is an alarm. +// +void I_HandleAlarm (int sig) +{ + if(!TicFrozen) + tics++; + sig_start = SDL_GetTicks(); + SEMAPHORE_SIGNAL(timerWait) +} + +// +// I_SelectTimer +// Sets up the timer function based on if we can use signals for efficent CPU +// usage. +// +void I_SelectTimer() +{ + SEMAPHORE_INIT(timerWait, 0, 0) +#ifndef __sun + signal(SIGALRM, I_HandleAlarm); +#else + struct sigaction alrmaction; + sigaction(SIGALRM, NULL, &alrmaction); + alrmaction.sa_handler = I_HandleAlarm; + sigaction(SIGALRM, &alrmaction, NULL); +#endif + + struct itimerval itv; + itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; + itv.it_interval.tv_usec = itv.it_value.tv_usec = 1000000/TICRATE; + + if (setitimer(ITIMER_REAL, &itv, NULL) != 0) + { + I_GetTime = I_GetTimePolled; + I_FreezeTime = I_FreezeTimePolled; + I_WaitForTic = I_WaitForTicPolled; + } + else + { + I_GetTime = I_GetTimeSignaled; + I_FreezeTime = I_FreezeTimeSignaled; + I_WaitForTic = I_WaitForTicSignaled; + } +} + +// Returns the fractional amount of a tic passed since the most recent tic +fixed_t I_GetTimeFrac (uint32 *ms) +{ + DWORD now = SDL_GetTicks (); + if (ms) *ms = TicStart + (1000 / TICRATE); + if (TicStart == 0) + { + return FRACUNIT; + } + else + { + fixed_t frac = clamp ((now - TicStart)*FRACUNIT*TICRATE/1000, 0, FRACUNIT); + return frac; + } +} + +void I_InitTimer () +{ + I_GetTime = I_GetTimeSelect; + I_WaitForTic = I_WaitForTicSelect; + I_FreezeTime = I_FreezeTimeSelect; +} + +void I_ShutdownTimer () +{ + +} diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm index 3b414d5e8..d1364fa81 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/sdl/iwadpicker_cocoa.mm @@ -33,9 +33,29 @@ ** */ +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "cmdlib.h" #include "d_main.h" #include "version.h" +#include "c_cvars.h" +#include "m_argv.h" +#include "m_misc.h" +#include "gameconfigfile.h" + +#undef Class + #include +#include +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 +// Missing type definition for 10.4 and earlier +typedef unsigned int NSUInteger; +#endif // prior to 10.5 + +CVAR(String, osx_additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); enum { @@ -107,6 +127,45 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; @end +static NSDictionary* GetKnownFileTypes() +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + @"-file" , @"wad", + @"-file" , @"pk3", + @"-file" , @"zip", + @"-file" , @"pk7", + @"-file" , @"7z", + @"-deh" , @"deh", + @"-bex" , @"bex", + @"-exec" , @"cfg", + @"-playdemo", @"lmp", + nil]; +} + +static NSArray* GetKnownExtensions() +{ + return [GetKnownFileTypes() allKeys]; +} + +@interface NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath; +@end + +@implementation NSMutableString(AppendKnownFileType) +- (void)appendKnownFileType:(NSString *)filePath +{ + NSString* extension = [[filePath pathExtension] lowercaseString]; + NSString* parameter = [GetKnownFileTypes() objectForKey:extension]; + + if (nil == parameter) + { + return; + } + + [self appendFormat:@"%@ \"%@\" ", parameter, filePath]; +} +@end + // So we can listen for button actions and such we need to have an Obj-C class. @interface IWADPicker : NSObject { @@ -114,13 +173,18 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; NSWindow *window; NSButton *okButton; NSButton *cancelButton; + NSButton *browseButton; + NSTextField *parametersTextField; bool cancelled; } - (void)buttonPressed:(id) sender; +- (void)browseButtonPressed:(id) sender; - (void)doubleClicked:(id) sender; - (void)makeLabel:(NSTextField *)label withString:(const char*) str; - (int)pickIWad:(WadStuff *)wads num:(int) numwads showWindow:(bool) showwin defaultWad:(int) defaultiwad; +- (NSString*)commandLineParameters; +- (void)menuActionSent:(NSNotification*)notification; @end @implementation IWADPicker @@ -134,6 +198,52 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [app stopModal]; } +- (void)browseButtonPressed:(id) sender +{ + NSOpenPanel* openPanel = [NSOpenPanel openPanel]; + [openPanel setAllowsMultipleSelection:YES]; + [openPanel setCanChooseFiles:YES]; + [openPanel setCanChooseDirectories:YES]; + [openPanel setResolvesAliases:YES]; + [openPanel setAllowedFileTypes:GetKnownExtensions()]; + + if (NSOKButton == [openPanel runModal]) + { + NSArray* files = [openPanel URLs]; + NSMutableString* parameters = [NSMutableString string]; + + for (NSUInteger i = 0, ei = [files count]; i < ei; ++i) + { + NSString* filePath = [[files objectAtIndex:i] path]; + BOOL isDirectory = false; + + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory) + { + [parameters appendFormat:@"-file \"%@\" ", filePath]; + } + else + { + [parameters appendKnownFileType:filePath]; + } + } + + if ([parameters length] > 0) + { + NSString* newParameters = [parametersTextField stringValue]; + + if ([newParameters length] > 0 + && NO == [newParameters hasSuffix:@" "]) + { + newParameters = [newParameters stringByAppendingString:@" "]; + } + + newParameters = [newParameters stringByAppendingString:parameters]; + + [parametersTextField setStringValue: newParameters]; + } + } +} + - (void)doubleClicked:(id) sender { if ([sender clickedRow] >= 0) @@ -159,20 +269,18 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; cancelled = false; app = [NSApplication sharedApplication]; - id windowTitle = [NSString stringWithFormat:@GAMESIG " %s: Select an IWAD to use", GetVersionString()]; + id windowTitle = [NSString stringWithFormat:@"%s %s", GAMENAME, GetVersionString()]; NSRect frame = NSMakeRect(0, 0, 440, 450); window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]; [window setTitle:windowTitle]; - NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(22, 379, 412, 50)]; - [self makeLabel:description withString:"ZDoom found more than one IWAD\nSelect from the list below to determine which one to use:"]; + NSTextField *description = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 384, 402, 50)]; + [self makeLabel:description withString:GAMENAME " found more than one IWAD\nSelect from the list below to determine which one to use:"]; [[window contentView] addSubview:description]; [description release]; - // Commented out version would account for an additional parameters box. - //NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 103, 412, 288)]; - NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 50, 412, 341)]; + NSScrollView *iwadScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 135, 402, 256)]; NSTableView *iwadTable = [[NSTableView alloc] initWithFrame:[iwadScroller bounds]]; IWADTableData *tableData = [[IWADTableData alloc] init:wads num:numwads]; for(int i = 0;i < NUM_COLUMNS;i++) @@ -200,11 +308,12 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [iwadTable release]; [iwadScroller release]; - /*NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(17, 78, 144, 17)]; - [self makeLabel:additionalParametersLabel:"Additional Parameters"]; + NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(18, 108, 144, 17)]; + [self makeLabel:additionalParametersLabel withString:"Additional Parameters:"]; [[window contentView] addSubview:additionalParametersLabel]; - NSTextField *additionalParameters = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 360, 22)]; - [[window contentView] addSubview:additionalParameters];*/ + parametersTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 402, 54)]; + [parametersTextField setStringValue:[NSString stringWithUTF8String:osx_additional_parameters]]; + [[window contentView] addSubview:parametersTextField]; // Doesn't look like the SDL version implements this so lets not show it. /*NSButton *dontAsk = [[NSButton alloc] initWithFrame:NSMakeRect(18, 18, 178, 18)]; @@ -213,39 +322,164 @@ static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; [dontAsk setState:(showwin ? NSOffState : NSOnState)]; [[window contentView] addSubview:dontAsk];*/ - okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 12, 96, 32)]; - [okButton setTitle:[NSString stringWithUTF8String:"OK"]]; + okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 8, 96, 32)]; + [okButton setTitle:@"OK"]; [okButton setBezelStyle:NSRoundedBezelStyle]; [okButton setAction:@selector(buttonPressed:)]; [okButton setTarget:self]; [okButton setKeyEquivalent:@"\r"]; [[window contentView] addSubview:okButton]; - cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 12, 96, 32)]; - [cancelButton setTitle:[NSString stringWithUTF8String:"Cancel"]]; + cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(332, 8, 96, 32)]; + [cancelButton setTitle:@"Cancel"]; [cancelButton setBezelStyle:NSRoundedBezelStyle]; [cancelButton setAction:@selector(buttonPressed:)]; [cancelButton setTarget:self]; [cancelButton setKeyEquivalent:@"\033"]; [[window contentView] addSubview:cancelButton]; + + browseButton = [[NSButton alloc] initWithFrame:NSMakeRect(14, 8, 96, 32)]; + [browseButton setTitle:@"Browse..."]; + [browseButton setBezelStyle:NSRoundedBezelStyle]; + [browseButton setAction:@selector(browseButtonPressed:)]; + [browseButton setTarget:self]; + [[window contentView] addSubview:browseButton]; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(menuActionSent:) name:NSMenuDidSendActionNotification object:nil]; [window center]; [app runModalForWindow:window]; + [center removeObserver:self name:NSMenuDidSendActionNotification object:nil]; + [window release]; [okButton release]; [cancelButton release]; + [browseButton release]; return cancelled ? -1 : [iwadTable selectedRow]; } +- (NSString*)commandLineParameters +{ + return [parametersTextField stringValue]; +} + +- (void)menuActionSent:(NSNotification*)notification +{ + NSDictionary* userInfo = [notification userInfo]; + NSMenuItem* menuItem = [userInfo valueForKey:@"MenuItem"]; + + if ( @selector(terminate:) == [menuItem action] ) + { + exit(0); + } +} + @end + +EXTERN_CVAR(String, defaultiwad) + +static NSString* GetArchitectureString() +{ +#ifdef __i386__ + return @"i386"; +#elif defined __x86_64__ + return @"x86_64"; +#elif defined __ppc__ + return @"ppc"; +#elif defined __ppc64__ + return @"ppc64"; +#endif +} + +static void RestartWithParameters(const char* iwadPath, NSString* parameters) +{ + assert(nil != parameters); + + defaultiwad = ExtractFileBase(iwadPath); + + GameConfig->DoGameSetup("Doom"); + M_SaveDefaults(NULL); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + @try + { + const int commandLineParametersCount = Args->NumArgs(); + assert(commandLineParametersCount > 0); + + NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)]; + NSString* architecture = GetArchitectureString(); + + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:commandLineParametersCount + 6]; + [arguments addObject:@"-arch"]; + [arguments addObject:architecture]; + [arguments addObject:executablePath]; + [arguments addObject:@"-wad_picker_restart"]; + [arguments addObject:@"-iwad"]; + [arguments addObject:[NSString stringWithUTF8String:iwadPath]]; + + for (int i = 1; i < commandLineParametersCount; ++i) + { + NSString* currentParameter = [NSString stringWithUTF8String:Args->GetArg(i)]; + [arguments addObject:currentParameter]; + } + + wordexp_t expansion = {}; + + if (0 == wordexp([parameters UTF8String], &expansion, 0)) + { + for (size_t i = 0; i < expansion.we_wordc; ++i) + { + NSString* argumentString = [NSString stringWithCString:expansion.we_wordv[i] + encoding:NSUTF8StringEncoding]; + [arguments addObject:argumentString]; + } + + wordfree(&expansion); + } + + [NSTask launchedTaskWithLaunchPath:@"/usr/bin/arch" arguments:arguments]; + + _exit(0); // to avoid atexit()'s functions + } + @catch (NSException* e) + { + NSLog(@"Cannot restart: %@", [e reason]); + } + + [pool release]; +} + +void I_SetMainWindowVisible(bool visible); + // Simple wrapper so we can call this from outside. int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + I_SetMainWindowVisible(false); + IWADPicker *picker = [IWADPicker alloc]; int ret = [picker pickIWad:wads num:numwads showWindow:showwin defaultWad:defaultiwad]; - [picker release]; + + I_SetMainWindowVisible(true); + + NSString* parametersToAppend = [picker commandLineParameters]; + osx_additional_parameters = [parametersToAppend UTF8String]; + + if (ret >= 0) + { + if (0 != [parametersToAppend length]) + { + RestartWithParameters(wads[ret].Path, parametersToAppend); + } + } + + [pool release]; + return ret; } diff --git a/src/sdl/sdlvideo.cpp b/src/sdl/sdlvideo.cpp index 92741d325..e4222633d 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/sdl/sdlvideo.cpp @@ -15,6 +15,10 @@ #include +#ifdef __APPLE__ +#include +#endif // __APPLE__ + // MACROS ------------------------------------------------------------------ // TYPES ------------------------------------------------------------------- @@ -43,6 +47,8 @@ public: friend class SDLVideo; + virtual void SetVSync (bool vsync); + private: PalEntry SourcePalette[256]; BYTE GammaTable[3][256]; @@ -75,13 +81,15 @@ struct MiniModeInfo // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern IVideo *Video; -extern SDL_Surface *cursorSurface; -extern SDL_Rect cursorBlit; extern bool GUICapture; +SDL_Surface *cursorSurface = NULL; +SDL_Rect cursorBlit = {0, 0, 32, 32}; + EXTERN_CVAR (Float, Gamma) EXTERN_CVAR (Int, vid_maxfps) EXTERN_CVAR (Bool, cl_capfps) +EXTERN_CVAR (Bool, vid_vsync) // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -326,6 +334,7 @@ SDLFB::SDLFB (int width, int height, bool fullscreen) } memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); UpdateColors (); + SetVSync (vid_vsync); } SDLFB::~SDLFB () @@ -535,6 +544,26 @@ bool SDLFB::IsFullscreen () return (Screen->flags & SDL_FULLSCREEN) != 0; } +void SDLFB::SetVSync (bool vsync) +{ +#ifdef __APPLE__ + if (CGLContextObj context = CGLGetCurrentContext()) + { + // Apply vsync for native backend only (where OpenGL context is set) + +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 + // Inconsistency between 10.4 and 10.5 SDKs: + // third argument of CGLSetParameter() is const long* on 10.4 and const GLint* on 10.5 + // So, GLint typedef'ed to long instead of int to workaround this issue + typedef long GLint; +#endif // prior to 10.5 + + const GLint value = vsync ? 1 : 0; + CGLSetParameter(context, kCGLCPSwapInterval, &value); + } +#endif // __APPLE__ +} + ADD_STAT (blit) { FString out; diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index c95b0221f..07cd43a4b 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -494,6 +494,7 @@ SoundHandle SoundRenderer::LoadSoundVoc(BYTE *sfxdata, int length) break; default: break; } + i += blocksize; } } diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index 4acda49b9..38ba8557e 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -433,7 +433,7 @@ bool TimidityPPMIDIDevice::LaunchTimidity () } int forkres; - wordexp_t words; + wordexp_t words = {}; switch (wordexp (CommandLine.GetChars(), &words, 0)) { diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index ba0077903..5cb04ed0e 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -38,6 +38,7 @@ #include "templates.h" #include "doomdef.h" #include "m_swap.h" +#include "doomerrors.h" // MACROS ------------------------------------------------------------------ @@ -277,7 +278,16 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype) const return new TimidityMIDIDevice; case MDEV_OPL: - return new OPLMIDIDevice; + try + { + return new OPLMIDIDevice; + } + catch (CRecoverableError &err) + { + // The creation of an OPL MIDI device can abort with an error if no GENMIDI lump can be found. + Printf("Unable to create OPL MIDI device: %s\nFalling back to FModEx playback", err.GetMessage()); + return new FMODMIDIDevice; + } case MDEV_TIMIDITY: return new TimidityPPMIDIDevice; @@ -540,7 +550,7 @@ bool MIDIStreamer::IsPlaying() void MIDIStreamer::MusicVolumeChanged() { - if (MIDI->FakeVolume()) + if (MIDI != NULL && MIDI->FakeVolume()) { float realvolume = clamp(snd_musicvolume * relative_volume, 0.f, 1.f); Volume = clamp((DWORD)(realvolume * 65535.f), 0, 65535); @@ -622,7 +632,7 @@ void MIDIStreamer::FluidSettingStr(const char *setting, const char *value) void MIDIStreamer::OutputVolume (DWORD volume) { - if (MIDI->FakeVolume()) + if (MIDI != NULL && MIDI->FakeVolume()) { NewVolume = volume; VolumeChanged = true; diff --git a/src/statistics.cpp b/src/statistics.cpp index acd8440e8..7ed6e7f8e 100644 --- a/src/statistics.cpp +++ b/src/statistics.cpp @@ -420,7 +420,7 @@ static void StoreLevelStats() LevelData[i].killcount = level.killed_monsters; LevelData[i].totalsecrets = level.total_secrets; LevelData[i].secretcount = level.found_secrets; - LevelData[i].leveltime = level.maptime; + LevelData[i].leveltime = AdjustTics(level.maptime); // Check for living monsters. On some maps it can happen // that the counter misses some. @@ -490,7 +490,7 @@ void STAT_ChangeLevel(const char *newl) } infostring.Format("%4d/%4d, %3d/%3d, %2d", statvals[0], statvals[1], statvals[2], statvals[3], validlevels); - FSessionStatistics *es = StatisticsEntry(sl, infostring, level.totaltime); + FSessionStatistics *es = StatisticsEntry(sl, infostring, AdjustTics(level.totaltime)); for(unsigned i = 0; i < LevelData.Size(); i++) { diff --git a/src/statnums.h b/src/statnums.h index 344a328c8..25f644d1d 100644 --- a/src/statnums.h +++ b/src/statnums.h @@ -63,6 +63,7 @@ enum STAT_SECTOREFFECT, // All sector effects that cause floor and ceiling movement STAT_ACTORMOVER, // actor movers STAT_SCRIPTS, // The ACS thinker. This is to ensure that it can't tick before all actors called PostBeginPlay + STAT_BOT, // Bot thinker }; #endif \ No newline at end of file diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index 32cba99af..21074b9f2 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -244,11 +244,11 @@ enum EDefinitionType #define GCC_MSEG #else #define MSVC_ASEG -#define GCC_ASEG __attribute__((section(SECTION_AREG))) +#define GCC_ASEG __attribute__((section(SECTION_AREG))) __attribute__((used)) #define MSVC_PSEG -#define GCC_PSEG __attribute__((section(SECTION_GREG))) +#define GCC_PSEG __attribute__((section(SECTION_GREG))) __attribute__((used)) #define MSVC_MSEG -#define GCC_MSEG __attribute__((section(SECTION_MREG))) +#define GCC_MSEG __attribute__((section(SECTION_MREG))) __attribute__((used)) #endif @@ -367,6 +367,7 @@ int MatchString (const char *in, const char **strings); #define ACTION_SET_RESULT(v) do { if (numret > 0) { assert(ret != NULL); ret->SetInt(v); numret = 1; } } while(0) +#define ACTION_OR_RESULT(v) do { if (numret > 0) { assert(ret != NULL); ret->SetInt(*(int *)ret->Location | int(v)); numret = 1; } } while(0) // Checks to see what called the current action function #define ACTION_CALL_FROM_ACTOR() (callingstate == self->state) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index f74924386..49c30be5a 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1,5248 +1,5836 @@ -/* -** thingdef.cpp -** -** Code pointers for Actor definitions -** -**--------------------------------------------------------------------------- -** Copyright 2002-2006 Christoph Oelckers -** Copyright 2004-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be -** covered by the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or (at -** your option) any later version. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "gi.h" -#include "g_level.h" -#include "actor.h" -#include "info.h" -#include "sc_man.h" -#include "tarray.h" -#include "w_wad.h" -#include "templates.h" -#include "r_defs.h" -#include "a_pickups.h" -#include "s_sound.h" -#include "cmdlib.h" -#include "p_lnspec.h" -#include "p_enemy.h" -#include "a_action.h" -#include "decallib.h" -#include "m_random.h" -#include "i_system.h" -#include "p_local.h" -#include "c_console.h" -#include "doomerrors.h" -#include "a_sharedglobal.h" -#include "thingdef/thingdef.h" -#include "v_video.h" -#include "v_font.h" -#include "doomstat.h" -#include "v_palette.h" -#include "g_shared/a_specialspot.h" -#include "actorptrselect.h" -#include "m_bbox.h" -#include "r_data/r_translate.h" -#include "p_trace.h" -#include "gstrings.h" - - -static FRandom pr_camissile ("CustomActorfire"); -static FRandom pr_camelee ("CustomMelee"); -static FRandom pr_cabullet ("CustomBullet"); -static FRandom pr_cajump ("CustomJump"); -static FRandom pr_cwbullet ("CustomWpBullet"); -static FRandom pr_cwjump ("CustomWpJump"); -static FRandom pr_cwpunch ("CustomWpPunch"); -static FRandom pr_grenade ("ThrowGrenade"); -static FRandom pr_crailgun ("CustomRailgun"); -static FRandom pr_spawndebris ("SpawnDebris"); -static FRandom pr_spawnitemex ("SpawnItemEx"); -static FRandom pr_burst ("Burst"); -static FRandom pr_monsterrefire ("MonsterRefire"); -static FRandom pr_teleport("A_Teleport"); - -//========================================================================== -// -// ACustomInventory :: CallStateChain -// -// Executes the code pointers in a chain of states -// until there is no next state -// -//========================================================================== - -bool ACustomInventory::CallStateChain (AActor *actor, FState *state) -{ - INTBOOL result = false; - int counter = 0; - int retval, numret; - VMReturn ret; - ret.IntAt(&retval); - VMValue params[3] = { actor, this, 0 }; - - this->flags5 |= MF5_INSTATECALL; - FState *savedstate = this->state; - - while (state != NULL) - { - this->state = state; - - if (state->ActionFunc != NULL) - { - VMFrameStack stack; - - params[2] = VMValue(state, ATAG_STATE); - retval = true; // assume success - numret = stack.Call(state->ActionFunc, params, countof(params), &ret, 1); - // As long as even one state succeeds, the whole chain succeeds unless aborted below. - result |= retval; - } - - // Since there are no delays it is a good idea to check for infinite loops here! - counter++; - if (counter >= 10000) break; - - if (this->state == state) - { - FState *next = state->GetNextState(); - - if (state == next) - { // Abort immediately if the state jumps to itself! - result = false; - break; - } - - // If both variables are still the same there was no jump - // so we must advance to the next state. - state = next; - } - else - { - state = this->state; - } - } - this->flags5 &= ~MF5_INSTATECALL; - this->state = savedstate; - return !!result; -} - -//========================================================================== -// -// A_RearrangePointers -// -// Allow an actor to change its relationship to other actors by -// copying pointers freely between TARGET MASTER and TRACER. -// Can also assign null value, but does not duplicate A_ClearTarget. -// -//========================================================================== - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (ptr_target); - PARAM_INT_OPT (ptr_master) { ptr_master = AAPTR_DEFAULT; } - PARAM_INT_OPT (ptr_tracer) { ptr_tracer = AAPTR_TRACER; } - PARAM_INT_OPT (flags) { flags = 0; } - - // Rearrange pointers internally - - // Fetch all values before modification, so that all fields can get original values - AActor - *gettarget = self->target, - *getmaster = self->master, - *gettracer = self->tracer; - - switch (ptr_target) // pick the new target - { - case AAPTR_MASTER: - self->target = getmaster; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_TRACER: - self->target = gettracer; - if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); - break; - case AAPTR_NULL: - self->target = NULL; - // THIS IS NOT "A_ClearTarget", so no other targeting info is removed - break; - } - - // presently permitting non-monsters to set master - switch (ptr_master) // pick the new master - { - case AAPTR_TARGET: - self->master = gettarget; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_TRACER: - self->master = gettracer; - if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); - break; - case AAPTR_NULL: - self->master = NULL; - break; - } - - switch (ptr_tracer) // pick the new tracer - { - case AAPTR_TARGET: - self->tracer = gettarget; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_MASTER: - self->tracer = getmaster; - break; // no verification deemed necessary; the engine never follows a tracer chain(?) - case AAPTR_NULL: - self->tracer = NULL; - break; - } - return 0; -} - -//========================================================================== -// -// A_TransferPointer -// -// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), -// or from this actor's MASTER, TARGET or TRACER. -// -// You can copy any one of that actor's pointers -// -// Assign the copied pointer to any one pointer in SELF, -// MASTER, TARGET or TRACER. -// -// Any attempt to make an actor point to itself will replace the pointer -// with a null value. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (ptr_source); - PARAM_INT (ptr_recipient); - PARAM_INT (ptr_sourcefield); - PARAM_INT_OPT (ptr_recipientfield) { ptr_recipientfield = AAPTR_DEFAULT; } - PARAM_INT_OPT (flags) { flags = 0; } - - AActor *source, *recipient; - - // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) - source = COPY_AAPTR(self, ptr_source); - recipient = COPY_AAPTR(self, ptr_recipient); // pick an actor to store the provided pointer value - if (recipient == NULL) - { - return 0; - } - - // convert source from dataprovider to data - source = COPY_AAPTR(source, ptr_sourcefield); - if (source == recipient) - { // The recepient should not acquire a pointer to itself; will write NULL} - source = NULL; - } - if (ptr_recipientfield == AAPTR_DEFAULT) - { // If default: Write to same field as data was read from - ptr_recipientfield = ptr_sourcefield; - } - ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags); - return 0; -} - -//========================================================================== -// -// A_CopyFriendliness -// -// Join forces with one of the actors you are pointing to (MASTER by default) -// -// Normal CopyFriendliness reassigns health. This function will not. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT (ptr_source) { ptr_source = AAPTR_MASTER; } - - if (self->player != NULL) - { - return 0; - } - - AActor *source = COPY_AAPTR(self, ptr_source); - if (source != NULL) - { // No change in current target or health - self->CopyFriendliness(source, false, false); - } - return 0; -} - -//========================================================================== -// -// Simple flag changers -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) -{ - PARAM_ACTION_PROLOGUE; - self->flags |= MF_SOLID; - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) -{ - PARAM_ACTION_PROLOGUE; - self->flags &= ~MF_SOLID; - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) -{ - PARAM_ACTION_PROLOGUE; - self->flags |= MF_FLOAT; - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) -{ - PARAM_ACTION_PROLOGUE; - self->flags &= ~(MF_FLOAT|MF_INFLOAT); - return 0; -} - -//========================================================================== -// -// Customizable attack functions which use actor parameters. -// -//========================================================================== -static void DoAttack (AActor *self, bool domelee, bool domissile, - int MeleeDamage, FSoundID MeleeSound, PClassActor *MissileType,fixed_t MissileHeight) -{ - if (self->target == NULL) return; - - A_FaceTarget (self); - if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) - { - int damage = pr_camelee.HitDice(MeleeDamage); - if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); - P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (domissile && MissileType != NULL) - { - // This seemingly senseless code is needed for proper aiming. - self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); - self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2&MF2_SEEKERMISSILE) - { - missile->tracer=self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } -} - -DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) -{ - PARAM_ACTION_PROLOGUE; - int MeleeDamage = self->GetClass()->MeleeDamage; - FSoundID MeleeSound = self->GetClass()->MeleeSound; - DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) -{ - PARAM_ACTION_PROLOGUE; - PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); - fixed_t MissileHeight = self->GetClass()->MissileHeight; - DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); - return 0; -} - -DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) -{ - PARAM_ACTION_PROLOGUE; - int MeleeDamage = self->GetClass()->MeleeDamage; - FSoundID MeleeSound = self->GetClass()->MeleeSound; - PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); - fixed_t MissileHeight = self->GetClass()->MissileHeight; - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); - return 0; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (melee_damage); - PARAM_SOUND (melee_sound); - PARAM_CLASS (missile_type, AActor); - PARAM_FIXED (missile_height); - - if (missile_type != NULL) - { - DoAttack(self, true, true, melee_damage, melee_sound, missile_type, missile_height); - } - return 0; -} - -//========================================================================== -// -// Custom sound functions. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) -{ - PARAM_ACTION_PROLOGUE; - PARAM_SOUND_OPT (soundid) { soundid = "weapons/pistol"; } - PARAM_INT_OPT (channel) { channel = CHAN_BODY; } - PARAM_FLOAT_OPT (volume) { volume = 1; } - PARAM_BOOL_OPT (looping) { looping = false; } - PARAM_FLOAT_OPT (attenuation) { attenuation = ATTN_NORM; } - - if (!looping) - { - S_Sound (self, channel, soundid, (float)volume, (float)attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, channel&7, soundid)) - { - S_Sound (self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation); - } - } - return 0; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT(slot) { slot = CHAN_VOICE; } - - S_StopSound(self, slot); - return 0; -} - -//========================================================================== -// -// These come from a time when DECORATE constants did not exist yet and -// the sound interface was less flexible. As a result the parameters are -// not optimal and these functions have been deprecated in favor of extending -// A_PlaySound and A_StopSound. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) -{ - PARAM_ACTION_PROLOGUE; - PARAM_SOUND(soundid); - - S_Sound(self, CHAN_WEAPON, soundid, 1, ATTN_NORM); - return 0; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) -{ - PARAM_ACTION_PROLOGUE; - PARAM_SOUND (soundid); - PARAM_NAME (channel); - PARAM_BOOL_OPT (looping) { looping = false; } - PARAM_INT_OPT (attenuation_raw) { attenuation_raw = 0; } - - float attenuation; - switch (attenuation_raw) - { - case -1: attenuation = ATTN_STATIC; break; // drop off rapidly - default: - case 0: attenuation = ATTN_NORM; break; // normal - case 1: - case 2: attenuation = ATTN_NONE; break; // full volume - } - - if (channel < NAME_Auto || channel > NAME_SoundSlot7) - { - channel = NAME_Auto; - } - - if (!looping) - { - S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) - { - S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); - } - } - return 0; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME(channel); - - if (channel > NAME_Auto && channel <= NAME_SoundSlot7) - { - S_StopSound (self, int(channel) - NAME_Auto); - } - return 0; -} - -//========================================================================== -// -// Generic seeker missile function -// -//========================================================================== -static FRandom pr_seekermissile ("SeekerMissile"); -enum -{ - SMF_LOOK = 1, - SMF_PRECISE = 2, - SMF_CURSPEED = 4, -}; -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT(ang1); - PARAM_INT(ang2); - PARAM_INT_OPT(flags) { flags = 0; } - PARAM_INT_OPT(chance) { chance = 50; } - PARAM_INT_OPT(distance) { distance = 10; } - - if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); - } - if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) - { - if (flags & SMF_LOOK) - { // This monster is no longer seekable, so let us look for another one next time. - self->tracer = NULL; - } - } - return 0; -} - -//========================================================================== -// -// Hitscan attack with a customizable amount of bullets (specified in damage) -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) -{ - PARAM_ACTION_PROLOGUE; - - int i; - int bangle; - int slope; - - if (!self->target) return 0; - - A_FaceTarget (self); - bangle = self->angle; - - slope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i = self->GetMissileDamage (0, 1); i > 0; --i) - { - int angle = bangle + (pr_cabullet.Random2() << 20); - int damage = ((pr_cabullet()%5)+1)*3; - P_LineAttack(self, angle, MISSILERANGE, slope, damage, - NAME_Hitscan, NAME_BulletPuff); - } - return 0; -} - - -//========================================================================== -// -// Do the state jump -// -//========================================================================== -static void DoJump(AActor *self, AActor *stateowner, FState *callingstate, FState *jumpto) -{ - if (jumpto == NULL) return; - - if (stateowner->flags5 & MF5_INSTATECALL) - { - stateowner->state = jumpto; - } - else if (self->player != NULL && callingstate == self->player->psprites[ps_weapon].state) - { - P_SetPsprite(self->player, ps_weapon, jumpto); - } - else if (self->player != NULL && callingstate == self->player->psprites[ps_flash].state) - { - P_SetPsprite(self->player, ps_flash, jumpto); - } - else if (callingstate == self->state) - { - // Rather than using self->SetState(jumpto) to set the state, - // set the state directly. Since this function is only called by - // action functions, which are only called by SetState(), we - // know that somewhere above us in the stack, a SetState() - // call is waiting for us to return. We use the flag OF_StateChanged - // to cause it to bypass the normal next state mechanism and use - // the one we set here instead. - self->state = jumpto; - self->ObjectFlags |= OF_StateChanged; - } - else - { // something went very wrong. This should never happen. - assert(false); - } -} - -// This is just to avoid having to directly reference the internally defined -// CallingState and statecall parameters in the code below. -#define ACTION_JUMP(offset) DoJump(self, stateowner, callingstate, offset) - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT(maxchance) { maxchance = 256; } - - paramnum++; // Increment paramnum to point at the first jump target - int count = numparam - paramnum; - if (count > 0 && (maxchance >= 256 || pr_cajump() < maxchance)) - { - int jumpnum = (count == 1 ? 0 : (pr_cajump() % count)); - PARAM_STATE_AT(paramnum + jumpnum, jumpto); - ACTION_JUMP(jumpto); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - return numret; -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (health); - PARAM_STATE (jump); - - if (self->health < health) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - return numret; -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - if (!self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - return numret; -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - if (self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - return numret; -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -static int DoJumpIfCloser(AActor *target, VM_ARGS) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED (dist); - PARAM_STATE (jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - // No target - no jump - if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && - ( (self->z > target->z && self->z - (target->z + target->height) < dist) || - (self->z <=target->z && target->z - (self->z + self->height) < dist) - ) - ) - { - ACTION_JUMP(jump); - } - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) -{ - PARAM_ACTION_PROLOGUE; - - AActor *target; - - if (self->player == NULL) - { - target = self->target; - } - else - { - // Does the player aim at something that can be shot? - P_BulletSlope(self, &target); - } - return DoJumpIfCloser(target, VM_ARGS_NAMES); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) -{ - PARAM_ACTION_PROLOGUE; - return DoJumpIfCloser(self->tracer, VM_ARGS_NAMES); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) -{ - PARAM_ACTION_PROLOGUE; - return DoJumpIfCloser(self->master, VM_ARGS_NAMES); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -int DoJumpIfInventory(AActor *owner, AActor *self, AActor *stateowner, FState *callingstate, VMValue *param, int numparam, VMReturn *ret, int numret) -{ - int paramnum = NAP-1; - PARAM_CLASS (itemtype, AInventory); - PARAM_INT (itemamount); - PARAM_STATE (label); - PARAM_INT_OPT (setowner) { setowner = AAPTR_DEFAULT; } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (itemtype == NULL) - { - return numret; - } - owner = COPY_AAPTR(owner, setowner); - if (owner == NULL) - { - return numret; - } - - AInventory *item = owner->FindInventory(itemtype); - - if (item) - { - if (itemamount > 0) - { - if (item->Amount >= itemamount) - ACTION_JUMP(label); - } - else if (item->Amount >= item->MaxAmount) - { - ACTION_JUMP(label); - } - } - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) -{ - PARAM_ACTION_PROLOGUE; - return DoJumpIfInventory(self, self, stateowner, callingstate, param, numparam, ret, numret); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) -{ - PARAM_ACTION_PROLOGUE; - return DoJumpIfInventory(self->target, self, stateowner, callingstate, param, numparam, ret, numret); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (type); - PARAM_STATE (label); - PARAM_INT_OPT(amount) { amount = 1; } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - ABasicArmor *armor = (ABasicArmor *)self->FindInventory(NAME_BasicArmor); - - if (armor && armor->ArmorType == type && armor->Amount >= amount) - ACTION_JUMP(label); - return numret; -} - -//========================================================================== -// -// Parameterized version of A_Explode -// -//========================================================================== - -enum -{ - XF_HURTSOURCE = 1, - XF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT (damage) { damage = -1; } - PARAM_INT_OPT (distance) { distance = -1; } - PARAM_INT_OPT (flags) { flags = XF_HURTSOURCE; } - PARAM_BOOL_OPT (alert) { alert = false; } - PARAM_INT_OPT (fulldmgdistance) { fulldmgdistance = 0; } - PARAM_INT_OPT (nails) { nails = 0; } - PARAM_INT_OPT (naildamage) { naildamage = 10; } - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor("BulletPuff"); } - - if (damage < 0) // get parameters from metadata - { - damage = self->GetClass()->ExplosionDamage; - distance = self->GetClass()->ExplosionRadius; - flags = !self->GetClass()->DontHurtShooter; - alert = false; - } - if (distance <= 0) distance = damage; - - // NailBomb effect, from SMMU but not from its source code: instead it was implemented and - // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html - - if (nails) - { - angle_t ang; - for (int i = 0; i < nails; i++) - { - ang = i*(ANGLE_MAX/nails); - // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim - P_LineAttack (self, ang, MISSILERANGE, 0, - //P_AimLineAttack (self, ang, MISSILERANGE), - naildamage, NAME_Hitscan, pufftype); - } - } - - P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); - P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) - { - validcount++; - P_RecursiveSound (self->Sector, self->target, false, 0); - } - return 0; -} - -//========================================================================== -// -// A_RadiusThrust -// -//========================================================================== - -enum -{ - RTF_AFFECTSOURCE = 1, - RTF_NOIMPACTDAMAGE = 2, - RTF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT (force) { force = 128; } - PARAM_INT_OPT (distance) { distance = -1; } - PARAM_INT_OPT (flags) { flags = RTF_AFFECTSOURCE; } - PARAM_INT_OPT (fullthrustdistance) { fullthrustdistance = 0; } - - bool sourcenothrust = false; - - if (force == 0) force = 128; - if (distance <= 0) distance = abs(force); - - // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. - if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) - { - sourcenothrust = true; - self->target->flags2 &= ~MF2_NODMGTHRUST; - } - - P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); - P_CheckSplash(self, distance << FRACBITS); - - if (sourcenothrust) - { - self->target->flags2 |= MF2_NODMGTHRUST; - } - return 0; -} - -//========================================================================== -// -// Execute a line special / script -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (special); - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (arg3) { arg3 = 0; } - PARAM_INT_OPT (arg4) { arg4 = 0; } - PARAM_INT_OPT (arg5) { arg5 = 0; } - - bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); - - ACTION_SET_RESULT(res); - return numret; -} - -//========================================================================== -// -// The ultimate code pointer: Fully customizable missiles! -// -//========================================================================== -enum CM_Flags -{ - CMF_AIMMODE = 3, - CMF_TRACKOWNER = 4, - CMF_CHECKTARGETDEAD = 8, - - CMF_ABSOLUTEPITCH = 16, - CMF_OFFSETPITCH = 32, - CMF_SAVEPITCH = 64, - - CMF_ABSOLUTEANGLE = 128 -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (ti, AActor); - PARAM_FIXED_OPT (spawnheight) { spawnheight = 32*FRACUNIT; } - PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } - PARAM_ANGLE_OPT (angle) { angle = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_ANGLE_OPT (pitch) { pitch = 0; } - - int aimmode = flags & CMF_AIMMODE; - - AActor * targ; - AActor * missile; - - if (self->target != NULL || aimmode == 2) - { - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = spawnofs_xy * finecosine[ang]; - fixed_t y = spawnofs_xy * finesine[ang]; - fixed_t z = spawnheight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); - - switch (aimmode) - { - case 0: - default: - // same adjustment as above (in all 3 directions this time) - for better aiming! - self->x += x; - self->y += y; - self->z += z; - missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); - self->x -= x; - self->y -= y; - self->z -= z; - break; - - case 1: - missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + spawnheight, self, self->target, ti, false); - break; - - case 2: - self->x += x; - self->y += y; - missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + spawnheight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); - self->x -= x; - self->y -= y; - - flags |= CMF_ABSOLUTEPITCH; - - break; - } - - if (missile != NULL) - { - // Use the actual velocity instead of the missile's Speed property - // so that this can handle missiles with a high vertical velocity - // component properly. - - fixed_t missilespeed; - - if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) - { - if (CMF_OFFSETPITCH & flags) - { - FVector2 velocity (missile->velx, missile->vely); - pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); - } - ang = pitch >> ANGLETOFINESHIFT; - missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); - missile->velz = FixedMul(finesine[ang], missile->Speed); - } - else - { - FVector2 velocity (missile->velx, missile->vely); - missilespeed = (fixed_t)velocity.Length(); - } - - if (CMF_SAVEPITCH & flags) - { - missile->pitch = pitch; - // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter - // contains the unapplied parameter. In that case, it is set as pitch without - // otherwise affecting the spawned actor. - } - - missile->angle = (CMF_ABSOLUTEANGLE & flags) ? angle : missile->angle + angle ; - - ang = missile->angle >> ANGLETOFINESHIFT; - missile->velx = FixedMul(missilespeed, finecosine[ang]); - missile->vely = FixedMul(missilespeed, finesine[ang]); - - // handle projectile shooting projectiles - track the - // links back to a real owner - if (self->isMissile(!!(flags & CMF_TRACKOWNER))) - { - AActor *owner = self ;//->target; - while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) - owner = owner->target; - targ = owner; - missile->target = owner; - // automatic handling of seeker missiles - if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) - { - missile->tracer = self->tracer; - } - } - else if (missile->flags2 & MF2_SEEKERMISSILE) - { - // automatic handling of seeker missiles - missile->tracer = self->target; - } - // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong - if (missile->flags4 & MF4_SPECTRAL) - { - if (missile->target != NULL) - { - missile->SetFriendPlayer(missile->target->player); - } - else - { - missile->FriendPlayer = 0; - } - } - P_CheckMissileSpawn(missile, self->radius); - } - } - } - else if (flags & CMF_CHECKTARGETDEAD) - { - // Target is dead and the attack shall be aborted. - if (self->SeeState != NULL) - self->SetState(self->SeeState); - } - return 0; -} - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum CBA_Flags -{ - CBAF_AIMFACING = 1, - CBAF_NORANDOM = 2, - CBAF_EXPLICITANGLE = 4, - CBAF_NOPITCH = 8, - CBAF_NORANDOMPUFFZ = 16, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_ANGLE (spread_xy); - PARAM_ANGLE (spread_z); - PARAM_INT (numbullets); - PARAM_INT (damageperbullet); - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } - PARAM_FIXED_OPT (range) { range = MISSILERANGE; } - PARAM_INT_OPT (flags) { flags = 0; } - - if (range == 0) - range = MISSILERANGE; - - int i; - int bangle; - int bslope = 0; - int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if (self->target || (flags & CBAF_AIMFACING)) - { - if (!(flags & CBAF_AIMFACING)) A_FaceTarget (self); - bangle = self->angle; - - if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i = 0; i < numbullets; i++) - { - int angle = bangle; - int slope = bslope; - - if (flags & CBAF_EXPLICITANGLE) - { - angle += spread_xy; - slope += spread_z; - } - else - { - angle += pr_cwbullet.Random2() * (spread_xy / 255); - slope += pr_cwbullet.Random2() * (spread_z / 255); - } - - int damage = damageperbullet; - - if (!(flags & CBAF_NORANDOM)) - damage *= ((pr_cabullet()%3)+1); - - P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); - } - } - return 0; -} - -//========================================================================== -// -// A fully customizable melee attack -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT (damage) { damage = 0; } - PARAM_SOUND_OPT (meleesound) { meleesound = 0; } - PARAM_SOUND_OPT (misssound) { misssound = 0; } - PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } - PARAM_BOOL_OPT (bleed) { bleed = true; } - - if (damagetype == NAME_None) - damagetype = NAME_Melee; // Melee is the default type - - if (!self->target) - return 0; - - A_FaceTarget (self); - if (self->CheckMeleeRange ()) - { - if (meleesound) - S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); - if (bleed) - P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else - { - if (misssound) - S_Sound (self, CHAN_WEAPON, misssound, 1, ATTN_NORM); - } - return 0; -} - -//========================================================================== -// -// A fully customizable combo attack -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (ti, AActor); - PARAM_FIXED (spawnheight); - PARAM_INT (damage); - PARAM_SOUND_OPT (meleesound) { meleesound = 0; } - PARAM_NAME_OPT (damagetype) { damagetype = NAME_Melee; } - PARAM_BOOL_OPT (bleed) { bleed = true; } - - if (!self->target) - return 0; - - A_FaceTarget (self); - if (self->CheckMeleeRange()) - { - if (damagetype == NAME_None) - damagetype = NAME_Melee; // Melee is the default type - if (meleesound) - S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); - int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); - if (bleed) - P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); - } - else if (ti) - { - // This seemingly senseless code is needed for proper aiming. - self->z += spawnheight + self->GetBobOffset() - 32*FRACUNIT; - AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); - self->z -= spawnheight + self->GetBobOffset() - 32*FRACUNIT; - - if (missile) - { - // automatic handling of seeker missiles - if (missile->flags2 & MF2_SEEKERMISSILE) - { - missile->tracer = self->target; - } - P_CheckMissileSpawn(missile, self->radius); - } - } - return 0; -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (!ACTION_CALL_FROM_WEAPON()) - return numret; - - if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) - { - ACTION_JUMP(jump); - } - return numret; -} - - -//========================================================================== -// -// An even more customizable hitscan attack -// -//========================================================================== -enum FB_Flags -{ - FBF_USEAMMO = 1, - FBF_NORANDOM = 2, - FBF_EXPLICITANGLE = 4, - FBF_NOPITCH = 8, - FBF_NOFLASH = 16, - FBF_NORANDOMPUFFZ = 32, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) -{ - PARAM_ACTION_PROLOGUE; - PARAM_ANGLE (spread_xy); - PARAM_ANGLE (spread_z); - PARAM_INT (numbullets); - PARAM_INT (damageperbullet); - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } - PARAM_INT_OPT (flags) { flags = FBF_USEAMMO; } - PARAM_FIXED_OPT (range) { range = 0; } - - if (!self->player) return 0; - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - - int i; - int bangle; - int bslope = 0; - int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; - - if ((flags & FBF_USEAMMO) && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (range == 0) - range = PLAYERMISSILERANGE; - - if (!(flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); - - if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); - bangle = self->angle; - - if (pufftype == NULL) - pufftype = PClass::FindActor(NAME_BulletPuff); - - if (weapon != NULL) - { - S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if ((numbullets == 1 && !player->refire) || numbullets == 0) - { - int damage = damageperbullet; - - if (!(flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags); - } - else - { - if (numbullets < 0) - numbullets = 1; - for (i = 0; i < numbullets; i++) - { - int angle = bangle; - int slope = bslope; - - if (flags & FBF_EXPLICITANGLE) - { - angle += spread_xy; - slope += spread_z; - } - else - { - angle += pr_cwbullet.Random2() * (spread_xy / 255); - slope += pr_cwbullet.Random2() * (spread_z / 255); - } - - int damage = damageperbullet; - - if (!(flags & FBF_NORANDOM)) - damage *= ((pr_cwbullet()%3)+1); - - P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); - } - } - return 0; -} - - -//========================================================================== -// -// A_FireProjectile -// -//========================================================================== -enum FP_Flags -{ - FPF_AIMATANGLE = 1, - FPF_TRANSFERTRANSLATION = 2, -}; -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (ti, AActor); - PARAM_ANGLE_OPT (angle) { angle = 0; } - PARAM_BOOL_OPT (useammo) { useammo = true; } - PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } - PARAM_FIXED_OPT (spawnheight) { spawnheight = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_ANGLE_OPT (pitch) { pitch = 0; } - - if (!self->player) - return 0; - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - AActor *linetarget; - - if (useammo && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (ti) - { - angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; - fixed_t x = spawnofs_xy * finecosine[ang]; - fixed_t y = spawnofs_xy * finesine[ang]; - fixed_t z = spawnheight; - fixed_t shootangle = self->angle; - - if (flags & FPF_AIMATANGLE) shootangle += angle; - - // Temporarily adjusts the pitch - fixed_t saved_player_pitch = self->pitch; - self->pitch -= pitch; - AActor *misl = P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); - self->pitch = saved_player_pitch; - // automatic handling of seeker missiles - if (misl) - { - if (flags & FPF_TRANSFERTRANSLATION) - misl->Translation = self->Translation; - if (linetarget && (misl->flags2 & MF2_SEEKERMISSILE)) - misl->tracer = linetarget; - if (!(flags & FPF_AIMATANGLE)) - { - // This original implementation is to aim straight ahead and then offset - // the angle from the resulting direction. - FVector3 velocity(misl->velx, misl->vely, 0); - fixed_t missilespeed = (fixed_t)velocity.Length(); - misl->angle += angle; - angle_t an = misl->angle >> ANGLETOFINESHIFT; - misl->velx = FixedMul (missilespeed, finecosine[an]); - misl->vely = FixedMul (missilespeed, finesine[an]); - } - } - } - return 0; -} - - -//========================================================================== -// -// A_CustomPunch -// -// Berserk is not handled here. That can be done with A_CheckIfInventory -// -//========================================================================== - -enum -{ - CPF_USEAMMO = 1, - CPF_DAGGER = 2, - CPF_PULLIN = 4, - CPF_NORANDOMPUFFZ = 8, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (damage); - PARAM_BOOL_OPT (norandom) { norandom = false; } - PARAM_INT_OPT (flags) { flags = CPF_USEAMMO; } - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } - PARAM_FIXED_OPT (range) { range = 0; } - PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; } - - if (!self->player) - return 0; - - player_t *player = self->player; - AWeapon *weapon = player->ReadyWeapon; - - - angle_t angle; - int pitch; - AActor * linetarget; - int actualdamage; - - if (!norandom) - damage *= pr_cwpunch() % 8 + 1; - - angle = self->angle + (pr_cwpunch.Random2() << 18); - if (range == 0) - range = MELEERANGE; - pitch = P_AimLineAttack (self, angle, range, &linetarget); - - // only use ammo when actually hitting something! - if ((flags & CPF_USEAMMO) && linetarget && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - if (pufftype == NULL) - pufftype = PClass::FindActor(NAME_BulletPuff); - int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); - - P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &linetarget, &actualdamage); - - // turn to face target - if (linetarget) - { - if (lifesteal && !(linetarget->flags5 & MF5_DONTDRAIN)) - P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS); - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y); - - if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; - if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); - } - return 0; -} - - -//========================================================================== -// -// customizable railgun attack function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (damage); - PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } - PARAM_BOOL_OPT (useammo) { useammo = true; } - PARAM_COLOR_OPT (color1) { color1 = 0; } - PARAM_COLOR_OPT (color2) { color2 = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } - PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } - PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } - PARAM_FIXED_OPT (range) { range = 0; } - PARAM_INT_OPT (duration) { duration = 0; } - PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } - PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } - PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } - PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } - - if (range == 0) range = 8192*FRACUNIT; - if (sparsity == 0) sparsity=1.0; - - if (self->player == NULL) - return 0; - - AWeapon *weapon = self->player->ReadyWeapon; - - // only use ammo when actually hitting something! - if (useammo) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) - return 0; // out of ammo - } - - angle_t angle; - angle_t slope; - - if (flags & RAF_EXPLICITANGLE) - { - angle = spread_xy; - slope = spread_z; - } - else - { - angle = pr_crailgun.Random2() * (spread_xy / 255); - slope = pr_crailgun.Random2() * (spread_z / 255); - } - - P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass); - return 0; -} - -//========================================================================== -// -// also for monsters -// -//========================================================================== -enum -{ - CRF_DONTAIM = 0, - CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2, - CRF_EXPLICITANGLE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (damage); - PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } - PARAM_COLOR_OPT (color1) { color1 = 0; } - PARAM_COLOR_OPT (color2) { color2 = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_INT_OPT (aim) { aim = CRF_DONTAIM; } - PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } - PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } - PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } - PARAM_FIXED_OPT (range) { range = 0; } - PARAM_INT_OPT (duration) { duration = 0; } - PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } - PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } - PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } - PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } - - if (range == 0) range = 8192*FRACUNIT; - if (sparsity == 0) sparsity = 1; - - AActor *linetarget; - - fixed_t saved_x = self->x; - fixed_t saved_y = self->y; - angle_t saved_angle = self->angle; - fixed_t saved_pitch = self->pitch; - - if (aim && self->target == NULL) - { - return 0; - } - // [RH] Andy Baker's stealth monsters - if (self->flags & MF_STEALTH) - { - self->visdir = 1; - } - - self->flags &= ~MF_AMBUSH; - - - if (aim) - { - self->angle = R_PointToAngle2 (self->x, - self->y, - self->target->x, - self->target->y); - } - self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); - if (linetarget == NULL && aim) - { - // We probably won't hit the target, but aim at it anyway so we don't look stupid. - FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); - double zdiff = (self->target->z + (self->target->height>>1)) - - (self->z + (self->height>>1) - self->floorclip); - self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); - } - // Let the aim trail behind the player - if (aim) - { - saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - - if (aim == CRF_AIMDIRECT) - { - // Tricky: We must offset to the angle of the current position - // but then change the angle again to ensure proper aim. - self->x += spawnofs_xy * finecosine[self->angle]; - self->y += spawnofs_xy * finesine[self->angle]; - spawnofs_xy = 0; - self->angle = R_PointToAngle2 (self->x, self->y, - self->target->x - self->target->velx * 3, - self->target->y - self->target->vely * 3); - } - - if (self->target->flags & MF_SHADOW) - { - angle_t rnd = pr_crailgun.Random2() << 21; - self->angle += rnd; - saved_angle = rnd; - } - } - - angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; - - angle_t angleoffset; - angle_t slopeoffset; - - if (flags & CRF_EXPLICITANGLE) - { - angleoffset = spread_xy; - slopeoffset = spread_z; - } - else - { - angleoffset = pr_crailgun.Random2() * (spread_xy / 255); - slopeoffset = pr_crailgun.Random2() * (spread_z / 255); - } - - P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass); - - self->x = saved_x; - self->y = saved_y; - self->angle = saved_angle; - self->pitch = saved_pitch; - return 0; -} - -//=========================================================================== -// -// DoGiveInventory -// -//=========================================================================== - -static int DoGiveInventory(AActor *receiver, VM_ARGS) -{ - int paramnum = NAP-1; - PARAM_CLASS (mi, AInventory); - PARAM_INT_OPT (amount) { amount = 1; } - PARAM_INT_OPT (setreceiver) { setreceiver = AAPTR_DEFAULT; } - - receiver = COPY_AAPTR(receiver, setreceiver); - if (receiver == NULL) - { // If there's nothing to receive it, it's obviously a fail, right? - ACTION_SET_RESULT(false); - return numret; - } - - bool res = true; - - if (amount <= 0) - { - amount = 1; - } - if (mi) - { - AInventory *item = static_cast(Spawn(mi, 0, 0, 0, NO_REPLACE)); - if (item->IsKindOf(RUNTIME_CLASS(AHealth))) - { - item->Amount *= amount; - } - else - { - item->Amount = amount; - } - item->flags |= MF_DROPPED; - item->ClearCounters(); - if (!item->CallTryPickup(receiver)) - { - item->Destroy(); - res = false; - } - else - { - res = true; - } - } - else - { - res = false; - } - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) -{ - PARAM_ACTION_PROLOGUE; - return DoGiveInventory(self, VM_ARGS_NAMES); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) -{ - PARAM_ACTION_PROLOGUE; - return DoGiveInventory(self->target, VM_ARGS_NAMES); -} - -//=========================================================================== -// -// A_TakeInventory -// -//=========================================================================== - -enum -{ - TIF_NOTAKEINFINITE = 1, -}; - -int DoTakeInventory(AActor *receiver, VM_ARGS) -{ - int paramnum = NAP-1; - PARAM_CLASS (itemtype, AInventory); - PARAM_INT_OPT (amount) { amount = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_INT_OPT (setreceiver) { setreceiver = AAPTR_DEFAULT; } - - if (itemtype == NULL) - { - ACTION_SET_RESULT(true); - return numret; - } - receiver = COPY_AAPTR(receiver, setreceiver); - if (receiver == NULL) - { - ACTION_SET_RESULT(false); - return numret; - } - - bool res = false; - - AInventory *inv = receiver->FindInventory(itemtype); - - if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) - { - if (inv->Amount > 0) - { - res = true; - } - // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set - // and infinite ammo is on - if (flags & TIF_NOTAKEINFINITE && - ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && - inv->IsKindOf(RUNTIME_CLASS(AAmmo))) - { - // Nothing to do here, except maybe res = false;? Would it make sense? - } - else if (!amount || amount>=inv->Amount) - { - if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; - else inv->Destroy(); - } - else - { - inv->Amount -= amount; - } - } - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) -{ - PARAM_ACTION_PROLOGUE; - return DoTakeInventory(self, VM_ARGS_NAMES); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) -{ - PARAM_ACTION_PROLOGUE; - return DoTakeInventory(self->target, VM_ARGS_NAMES); -} - -//=========================================================================== -// -// Common code for A_SpawnItem and A_SpawnItemEx -// -//=========================================================================== - -enum SIX_Flags -{ - SIXF_TRANSFERTRANSLATION = 1 << 0, - SIXF_ABSOLUTEPOSITION = 1 << 1, - SIXF_ABSOLUTEANGLE = 1 << 2, - SIXF_ABSOLUTEVELOCITY = 1 << 3, - SIXF_SETMASTER = 1 << 4, - SIXF_NOCHECKPOSITION = 1 << 5, - SIXF_TELEFRAG = 1 << 6, - SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum - SIXF_TRANSFERAMBUSHFLAG = 1 << 8, - SIXF_TRANSFERPITCH = 1 << 9, - SIXF_TRANSFERPOINTERS = 1 << 10, - SIXF_USEBLOODCOLOR = 1 << 11, - SIXF_CLEARCALLERTID = 1 << 12, - SIXF_MULTIPLYSPEED = 1 << 13, - SIXF_TRANSFERSCALE = 1 << 14, - SIXF_TRANSFERSPECIAL = 1 << 15, - SIXF_CLEARCALLERSPECIAL = 1 << 16, - SIXF_TRANSFERSTENCILCOL = 1 << 17, -}; - -static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) -{ - if (mo == NULL) - { - return false; - } - AActor *originator = self; - - if (!(mo->flags2 & MF2_DONTTRANSLATE)) - { - if (flags & SIXF_TRANSFERTRANSLATION) - { - mo->Translation = self->Translation; - } - else if (flags & SIXF_USEBLOODCOLOR) - { - // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. - PalEntry bloodcolor = self->GetBloodColor(); - mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); - } - } - if (flags & SIXF_TRANSFERPOINTERS) - { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; - } - - mo->angle = self->angle; - if (flags & SIXF_TRANSFERPITCH) - { - mo->pitch = self->pitch; - } - while (originator && originator->isMissile()) - { - originator = originator->target; - } - - if (flags & SIXF_TELEFRAG) - { - P_TeleportMove(mo, mo->x, mo->y, mo->z, true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; - } - if (mo->flags3 & MF3_ISMONSTER) - { - if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) - { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; - } - else if (originator) - { - if (originator->flags3 & MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - if (flags & SIXF_SETMASTER) mo->master = originator; // don't let it attack you (optional)! - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); - - AActor * attacker=originator->player->attacker; - if (attacker) - { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } - } - } - } - } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target = originator ? originator : self; - } - if (flags & SIXF_TRANSFERSCALE) - { - mo->scaleX = self->scaleX; - mo->scaleY = self->scaleY; - } - if (flags & SIXF_TRANSFERAMBUSHFLAG) - { - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); - } - if (flags & SIXF_CLEARCALLERTID) - { - self->RemoveFromHash(); - self->tid = 0; - } - if (flags & SIXF_TRANSFERSPECIAL) - { - mo->special = self->special; - memcpy(mo->args, self->args, sizeof(self->args)); - } - if (flags & SIXF_CLEARCALLERSPECIAL) - { - self->special = 0; - memset(self->args, 0, sizeof(self->args)); - } - if (flags & SIXF_TRANSFERSTENCILCOL) - { - mo->fillcolor = self->fillcolor; - } - - return true; -} - -//=========================================================================== -// -// A_SpawnItem -// -// Spawns an item in front of the caller like Heretic's time bomb -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS_OPT (missile, AActor) { missile = PClass::FindActor("Unknown"); } - PARAM_FIXED_OPT (distance) { distance = 0; } - PARAM_FIXED_OPT (zheight) { zheight = 0; } - PARAM_BOOL_OPT (useammo) { useammo = true; } - PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; } - - if (missile == NULL) - { - ACTION_SET_RESULT(false); - return numret; - } - - ACTION_SET_RESULT(true); - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - { - return numret; - } - - if (distance == 0) - { - // use the minimum distance that does not result in an overlap - distance = (self->radius + GetDefaultByType(missile)->radius) >> FRACBITS; - } - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - return numret; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - return numret; - } - - AActor *mo = Spawn(missile, - self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), - self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), - self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); - - int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state - return numret; -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (missile, AActor); - PARAM_FIXED_OPT (xofs) { xofs = 0; } - PARAM_FIXED_OPT (yofs) { yofs = 0; } - PARAM_FIXED_OPT (zofs) { zofs = 0; } - PARAM_FIXED_OPT (xvel) { xvel = 0; } - PARAM_FIXED_OPT (yvel) { yvel = 0; } - PARAM_FIXED_OPT (zvel) { zvel = 0; } - PARAM_ANGLE_OPT (angle) { angle = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_INT_OPT (chance) { chance = 0; } - PARAM_INT_OPT (tid) { tid = 0; } - - if (missile == NULL) - { - ACTION_SET_RESULT(false); - return numret; - } - - ACTION_SET_RESULT(true); - if (chance > 0 && pr_spawnitemex() < chance) - return numret; - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - return numret; - - fixed_t x, y; - - if (!(flags & SIXF_ABSOLUTEANGLE)) - { - angle += self->angle; - } - - angle_t ang = angle >> ANGLETOFINESHIFT; - - if (flags & SIXF_ABSOLUTEPOSITION) - { - x = self->x + xofs; - y = self->y + yofs; - } - else - { - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); - y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); - } - - if (!(flags & SIXF_ABSOLUTEVELOCITY)) - { - // Same orientation issue here! - fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); - yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); - xvel = newxvel; - } - - AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); - bool res = InitSpawnedItem(self, mo, flags); - ACTION_SET_RESULT(res); // for an inventory item's use state - if (res) - { - if (tid != 0) - { - assert(mo->tid == 0); - mo->tid = tid; - mo->AddToHash(); - } - if (flags & SIXF_MULTIPLYSPEED) - { - mo->velx = FixedMul(xvel, mo->Speed); - mo->vely = FixedMul(yvel, mo->Speed); - mo->velz = FixedMul(zvel, mo->Speed); - } - else - { - mo->velx = xvel; - mo->vely = yvel; - mo->velz = zvel; - } - mo->angle = angle; - } - return numret; -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (missile, AActor); - PARAM_FIXED_OPT (zheight) { zheight = 0; } - PARAM_FIXED_OPT (xyvel) { xyvel = 0; } - PARAM_FIXED_OPT (zvel) { zvel = 0; } - PARAM_BOOL_OPT (useammo) { useammo = true; } - - ACTION_SET_RESULT(true); - if (missile == NULL) - return numret; - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - return numret; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - return numret; - } - - AActor *bo; - - bo = Spawn(missile, self->x, self->y, - self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), - ALLOW_REPLACE); - if (bo) - { - P_PlaySpawnSound(bo, self); - if (xyvel != 0) - bo->Speed = xyvel; - bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); - - angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; - angle_t angle = bo->angle >> ANGLETOFINESHIFT; - - // There are two vectors we are concerned about here: xy and z. We rotate - // them separately according to the shooter's pitch and then sum them to - // get the final velocity vector to shoot with. - - fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); - fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); - fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); - fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); - - pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; - fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); - fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); - fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); - fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); - - bo->velx = xy_velx + z_velx + (self->velx >> 1); - bo->vely = xy_vely + z_vely + (self->vely >> 1); - bo->velz = xy_velz + z_velz; - - bo->target = self; - P_CheckMissileSpawn (bo, self->radius); - } - else - { - ACTION_SET_RESULT(false); - } - return numret; -} - - -//=========================================================================== -// -// A_Recoil -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED(xyvel); - - angle_t angle = self->angle + ANG180; - angle >>= ANGLETOFINESHIFT; - self->velx += FixedMul(xyvel, finecosine[angle]); - self->vely += FixedMul(xyvel, finesine[angle]); - return 0; -} - - -//=========================================================================== -// -// A_SelectWeapon -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS(cls, AWeapon); - - if (cls == NULL || self->player == NULL) - { - ACTION_SET_RESULT(false); - return numret; - } - - AWeapon *weaponitem = static_cast(self->FindInventory(cls)); - - if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) - { - if (self->player->ReadyWeapon != weaponitem) - { - self->player->PendingWeapon = weaponitem; - } - ACTION_SET_RESULT(true); - } - else - { - ACTION_SET_RESULT(false); - } - return numret; -} - - -//=========================================================================== -// -// A_Print -// -//=========================================================================== -EXTERN_CVAR(Float, con_midtime) - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STRING (text); - PARAM_FLOAT_OPT (time) { time = 0; } - PARAM_NAME_OPT (fontname) { fontname = NAME_None; } - - if (text[0] == '$') text = GStrings(&text[1]); - if (self->CheckLocalView (consoleplayer) || - (self->target != NULL && self->target->CheckLocalView (consoleplayer))) - { - float saved = con_midtime; - FFont *font = NULL; - - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = float(time); - } - FString formatted = strbin1(text); - C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - } - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! - return numret; -} - -//=========================================================================== -// -// A_PrintBold -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STRING (text); - PARAM_FLOAT_OPT (time) { time = 0; } - PARAM_NAME_OPT (fontname) { fontname = NAME_None; } - - float saved = con_midtime; - FFont *font = NULL; - - if (text[0] == '$') text = GStrings(&text[1]); - if (fontname != NAME_None) - { - font = V_GetFont(fontname); - } - if (time > 0) - { - con_midtime = float(time); - } - FString formatted = strbin1(text); - C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); - con_midtime = saved; - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! - return numret; -} - -//=========================================================================== -// -// A_Log -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STRING(text); - - if (text[0] == '$') text = GStrings(&text[1]); - FString formatted = strbin1(text); - Printf("%s\n", formatted.GetChars()); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! - return numret; -} - -//========================================================================= -// -// A_LogInt -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT(num); - Printf("%d\n", num); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! - return numret; -} - -//=========================================================================== -// -// A_SetTranslucent -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED (alpha); - PARAM_INT_OPT (mode) { mode = 0; } - - mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha = clamp(alpha, 0, FRACUNIT); - self->RenderStyle = ERenderStyle(mode); - return 0; -} - -//=========================================================================== -// -// A_FadeIn -// -// Fades the actor in -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; } - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha += reduce; - // Should this clamp alpha to 1.0? - return 0; -} - -//=========================================================================== -// -// A_FadeOut -// -// fades the actor out and destroys it when done -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; } - PARAM_BOOL_OPT(remove) { remove = true; } - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha -= reduce; - if (self->alpha <= 0 && remove) - { - self->Destroy(); - } - return 0; -} - -//=========================================================================== -// -// A_FadeTo -// -// fades the actor to a specified transparency by a specified amount and -// destroys it if so desired -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED (target); - PARAM_FIXED_OPT (amount) { amount = fixed_t(0.1*FRACUNIT); } - PARAM_BOOL_OPT (remove) { remove = false; } - - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - - if (self->alpha > target) - { - self->alpha -= amount; - - if (self->alpha < target) - { - self->alpha = target; - } - } - else if (self->alpha < target) - { - self->alpha += amount; - - if (self->alpha > target) - { - self->alpha = target; - } - } - if (self->alpha == target && remove) - { - self->Destroy(); - } - return 0; -} - -//=========================================================================== -// -// A_Scale(float scalex, optional float scaley) -// -// Scales the actor's graphics. If scaley is 0, use scalex. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED (scalex); - PARAM_FIXED_OPT (scaley) { scaley = scalex; } - - self->scaleX = scalex; - self->scaleY = scaley; - return 0; -} - -//=========================================================================== -// -// A_SetMass(int mass) -// -// Sets the actor's mass. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (mass); - - self->Mass = mass; - return 0; -} - -//=========================================================================== -// -// A_SpawnDebris -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (debris, AActor); - PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; } - PARAM_FIXED_OPT (mult_h) { mult_h = FRACUNIT; } - PARAM_FIXED_OPT (mult_v) { mult_v = FRACUNIT; } - int i; - AActor *mo; - - if (debris == NULL) - return 0; - - // only positive values make sense here - if (mult_v <= 0) - mult_v = FRACUNIT; - if (mult_h <= 0) - mult_h = FRACUNIT; - - for (i = 0; i < GetDefaultByType(debris)->health; i++) - { - mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), - self->y + ((pr_spawndebris()-128)<<12), - self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); - if (mo) - { - if (transfer_translation) - { - mo->Translation = self->Translation; - } - if (i < mo->GetClass()->NumOwnedStates) - { - mo->SetState (mo->GetClass()->OwnedStates + i); - } - mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); - mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); - } - } - return 0; -} - - -//=========================================================================== -// -// A_CheckSight -// jumps if no player can see this actor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - // Always check sight from each player. - if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) - { - return numret; - } - // If a player is viewing from a non-player, then check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) - { - return numret; - } - } - } - - ACTION_JUMP(jump); - return numret; -} - -//=========================================================================== -// -// A_CheckSightOrRange -// Jumps if this actor is out of range of all players *and* out of sight. -// Useful for maps with many multi-actor special effects. -// -//=========================================================================== -static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height) - { - dz = self->z + self->height - eyez; - } - else if (eyez < self->z) - { - dz = self->z - eyez; - } - else - { - dz = 0; - } - if ((dx*dx) + (dy*dy) + (dz*dz) <= range) - { // Within range - return true; - } - - // Now check LOS. - if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) - { // Visible - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FLOAT(range); - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckSightOrRange(self, players[i].mo, range)) - { - return numret; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckSightOrRange(self, players[i].camera, range)) - { - return numret; - } - } - } - ACTION_JUMP(jump); - return numret; -} - -//=========================================================================== -// -// A_CheckRange -// Jumps if this actor is out of range of all players. -// -//=========================================================================== -static bool DoCheckRange(AActor *self, AActor *camera, double range) -{ - if (camera == NULL) - { - return false; - } - // Check distance first, since it's cheaper than checking sight. - double dx = self->x - camera->x; - double dy = self->y - camera->y; - double dz; - fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight - if (eyez > self->z + self->height){ - dz = self->z + self->height - eyez; - } - else if (eyez < self->z){ - dz = self->z - eyez; - } - else{ - dz = 0; - } - if ((dx*dx) + (dy*dy) + (dz*dz) <= range){ - // Within range - return true; - } - return false; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FLOAT(range); - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots - for (int i = 0; i < MAXPLAYERS; ++i) - { - if (playeringame[i]) - { - // Always check from each player. - if (DoCheckRange(self, players[i].mo, range)) - { - return numret; - } - // If a player is viewing from a non-player, check that too. - if (players[i].camera != NULL && players[i].camera->player == NULL && - DoCheckRange(self, players[i].camera, range)) - { - return numret; - } - } - } - ACTION_JUMP(jump); - return numret; -} - - -//=========================================================================== -// -// Inventory drop -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS(drop, AInventory); - - if (drop) - { - AInventory *inv = self->FindInventory(drop); - if (inv) - { - self->DropInventory(inv); - } - } - return 0; -} - - -//=========================================================================== -// -// A_SetBlend -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) -{ - PARAM_ACTION_PROLOGUE; - PARAM_COLOR (color); - PARAM_FLOAT (alpha); - PARAM_INT (tics); - PARAM_COLOR_OPT (color2) { color2 = 0; } - - if (color == MAKEARGB(255,255,255,255)) - color = 0; - if (color2 == MAKEARGB(255,255,255,255)) - color2 = 0; - if (color2.a == 0) - color2 = color; - - new DFlashFader(color.r/255.f, color.g/255.f, color.b/255.f, float(alpha), - color2.r/255.f, color2.g/255.f, color2.b/255.f, 0, - float(tics)/TICRATE, self); - return 0; -} - - -//=========================================================================== -// -// A_JumpIf -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) -{ - PARAM_ACTION_PROLOGUE; - PARAM_BOOL (condition); - PARAM_STATE (jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (condition) - ACTION_JUMP(jump); - return numret; -} - -//=========================================================================== -// -// A_KillMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME_OPT(damagetype) { damagetype = NAME_None; } - - if (self->master != NULL) - { - P_DamageMobj(self->master, self, self, self->master->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); - } - return 0; -} - -//=========================================================================== -// -// A_KillChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME_OPT(damagetype) { damagetype = NAME_None; } - - TThinkerIterator it; - AActor *mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - P_DamageMobj(mo, self, self, mo->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); - } - } - return 0; -} - -//=========================================================================== -// -// A_KillSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME_OPT(damagetype) { damagetype = NAME_None; } - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ( (mo = it.Next()) ) - { - if (mo->master == self->master && mo != self) - { - P_DamageMobj(mo, self, self, mo->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); - } - } - } - return 0; -} - -//=========================================================================== -// -// A_CountdownArg -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT(argnum); - PARAM_STATE_OPT(state) { state = self->FindState(NAME_Death); } - - if (argnum > 0 && argnum < (int)countof(self->args)) - { - if (!self->args[argnum]--) - { - if (self->flags & MF_MISSILE) - { - P_ExplodeMissile(self, NULL, NULL); - } - else if (self->flags & MF_SHOOTABLE) - { - P_DamageMobj(self, NULL, NULL, self->health, NAME_None, DMG_FORCED); - } - else - { - self->SetState(self->FindState(NAME_Death)); - } - } - else - { - self->SetState(state); - } - } - return 0; -} - -//============================================================================ -// -// A_Burst -// -//============================================================================ - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS(chunk, AActor); - - int i, numChunks; - AActor * mo; - - if (chunk == NULL) - { - return 0; - } - - self->velx = self->vely = self->velz = 0; - self->height = self->GetDefault()->height; - - // [RH] In Hexen, this creates a random number of shards (range [24,56]) - // with no relation to the size of the self shattering. I think it should - // base the number of shards on the size of the dead thing, so bigger - // things break up into more shards than smaller things. - // An self with radius 20 and height 64 creates ~40 chunks. - numChunks = MAX (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); - i = (pr_burst.Random2()) % (numChunks/4); - for (i = MAX (24, numChunks + i); i >= 0; i--) - { - mo = Spawn(chunk, - self->x + (((pr_burst()-128)*self->radius)>>7), - self->y + (((pr_burst()-128)*self->radius)>>7), - self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); - - if (mo) - { - mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; - mo->velx = pr_burst.Random2 () << (FRACBITS-7); - mo->vely = pr_burst.Random2 () << (FRACBITS-7); - mo->RenderStyle = self->RenderStyle; - mo->alpha = self->alpha; - mo->CopyFriendliness(self, true); - } - } - - // [RH] Do some stuff to make this more useful outside Hexen - if (self->flags4 & MF4_BOSSDEATH) - { - CALL_ACTION(A_BossDeath, self); - } - A_Unblock(self, true); - - self->Destroy (); - return 0; -} - -//=========================================================================== -// -// A_CheckFloor -// [GRB] Jumps if actor is standing on floor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->z <= self->floorz) - { - ACTION_JUMP(jump); - } - return numret; -} - -//=========================================================================== -// -// A_CheckCeiling -// [GZ] Totally copied from A_CheckFloor, jumps if actor touches ceiling -// - -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); - if (self->z + self->height >= self->ceilingz) // Height needs to be counted - { - ACTION_JUMP(jump); - } - return numret; -} - -//=========================================================================== -// -// A_Stop -// resets all velocity of the actor to 0 -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_Stop) -{ - PARAM_ACTION_PROLOGUE; - self->velx = self->vely = self->velz = 0; - if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } - return 0; -} - -static void CheckStopped(AActor *self) -{ - if (self->player != NULL && - self->player->mo == self && - !(self->player->cheats & CF_PREDICTING) && - !(self->velx | self->vely | self->velz)) - { - self->player->mo->PlayIdle(); - self->player->velx = self->player->vely = 0; - } -} - -//=========================================================================== -// -// A_Respawn -// -//=========================================================================== - -DECLARE_ACTION(A_RestoreSpecialPosition) - -enum RS_Flags -{ - RSF_FOG=1, - RSF_KEEPTARGET=2, - RSF_TELEFRAG=4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT(flags) { flags = RSF_FOG; } - - bool oktorespawn = false; - - self->flags |= MF_SOLID; - self->height = self->GetDefault()->height; - CALL_ACTION(A_RestoreSpecialPosition, self); - - if (flags & RSF_TELEFRAG) - { - // [KS] DIE DIE DIE DIE erm *ahem* =) - oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); - if (oktorespawn) - { // Need to do this over again, since P_TeleportMove() will redo - // it with the proper point-on-side calculation. - self->UnlinkFromWorld(); - self->LinkToWorld(true); - sector_t *sec = self->Sector; - self->dropoffz = - self->floorz = sec->floorplane.ZatPoint(self->x, self->y); - self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); - P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); - } - } - else - { - oktorespawn = P_CheckPosition(self, self->x, self->y, true); - } - - if (oktorespawn) - { - AActor *defs = self->GetDefault(); - self->health = defs->health; - - // [KS] Don't keep target, because it could be self if the monster committed suicide - // ...Actually it's better off an option, so you have better control over monster behavior. - if (!(flags & RSF_KEEPTARGET)) - { - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; - } - else - { - // Don't attack yourself (Re: "Marine targets itself after suicide") - if (self->target == self) - self->target = NULL; - if (self->lastenemy == self) - self->lastenemy = NULL; - } - - self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); - self->flags2 = defs->flags2; - self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); - self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); - self->flags5 = defs->flags5; - self->SetState (self->SpawnState); - self->renderflags &= ~RF_INVISIBLE; - - if (flags & RSF_FOG) - { - Spawn (self->x, self->y, self->z + TELEFOGHEIGHT, ALLOW_REPLACE); - } - if (self->CountsAsKill()) - { - level.total_monsters++; - } - } - else - { - self->flags &= ~MF_SOLID; - } - return 0; -} - - -//========================================================================== -// -// A_PlayerSkinCheck -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->player != NULL && - skins[self->player->userinfo.GetSkin()].othergame) - { - ACTION_JUMP(jump); - } - return numret; -} - -//=========================================================================== -// -// A_SetGravity -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED(gravity); - - self->gravity = clamp(gravity, 0, FRACUNIT*10); - return 0; -} - - -// [KS] *** Start of my modifications *** - -//=========================================================================== -// -// A_ClearTarget -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) -{ - PARAM_ACTION_PROLOGUE; - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; - return 0; -} - -//========================================================================== -// -// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, -// fixed range = 0, angle angle = 0, angle pitch = 0, -// fixed offsetheight = 32, fixed offsetwidth = 0, -// int ptr_target = AAPTR_DEFAULT (target) ) -// -//========================================================================== - -enum CLOF_flags -{ - CLOFF_NOAIM_VERT = 0x1, - CLOFF_NOAIM_HORZ = 0x2, - - CLOFF_JUMPENEMY = 0x4, - CLOFF_JUMPFRIEND = 0x8, - CLOFF_JUMPOBJECT = 0x10, - CLOFF_JUMPNONHOSTILE = 0x20, - - CLOFF_SKIPENEMY = 0x40, - CLOFF_SKIPFRIEND = 0x80, - CLOFF_SKIPOBJECT = 0x100, - CLOFF_SKIPNONHOSTILE = 0x200, - - CLOFF_MUSTBESHOOTABLE = 0x400, - - CLOFF_SKIPTARGET = 0x800, - CLOFF_ALLOWNULL = 0x1000, - CLOFF_CHECKPARTIAL = 0x2000, - - CLOFF_MUSTBEGHOST = 0x4000, - CLOFF_IGNOREGHOST = 0x8000, - - CLOFF_MUSTBESOLID = 0x10000, - CLOFF_BEYONDTARGET = 0x20000, - - CLOFF_FROMBASE = 0x40000, - CLOFF_MUL_HEIGHT = 0x80000, - CLOFF_MUL_WIDTH = 0x100000, - - CLOFF_JUMP_ON_MISS = 0x200000, - CLOFF_AIM_VERT_NOOFFSET = 0x400000, -}; - -struct LOFData -{ - AActor *Self; - AActor *Target; - int Flags; - bool BadActor; -}; - -ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) -{ - LOFData *data = (LOFData *)userdata; - int flags = data->Flags; - - if (trace.HitType != TRACE_HitActor) - { - return TRACE_Stop; - } - if (trace.Actor == data->Target) - { - if (flags & CLOFF_SKIPTARGET) - { - if (flags & CLOFF_BEYONDTARGET) - { - return TRACE_Skip; - } - return TRACE_Abort; - } - return TRACE_Stop; - } - if (flags & CLOFF_MUSTBESHOOTABLE) - { // all shootability checks go here - if (!(trace.Actor->flags & MF_SHOOTABLE)) - { - return TRACE_Skip; - } - if (trace.Actor->flags2 & MF2_NONSHOOTABLE) - { - return TRACE_Skip; - } - } - if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) - { - return TRACE_Skip; - } - if (flags & CLOFF_MUSTBEGHOST) - { - if (!(trace.Actor->flags3 & MF3_GHOST)) - { - return TRACE_Skip; - } - } - else if (flags & CLOFF_IGNOREGHOST) - { - if (trace.Actor->flags3 & MF3_GHOST) - { - return TRACE_Skip; - } - } - if ( - ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Stop; - } - if ( - ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || - ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || - ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || - ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) - ) - { - return TRACE_Skip; - } - data->BadActor = true; - return TRACE_Abort; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) -{ - // Check line of fire - - /* - Not accounted for / I don't know how it works: FLOORCLIP - */ - - AActor *target; - fixed_t - x1, y1, z1, - vx, vy, vz; - - PARAM_ACTION_PROLOGUE; - PARAM_STATE (jump); - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_FIXED_OPT (range) { range = 0; } - PARAM_FIXED_OPT (minrange) { minrange = 0; } - { - PARAM_ANGLE_OPT (angle) { angle = 0; } - PARAM_ANGLE_OPT (pitch) { pitch = 0; } - PARAM_FIXED_OPT (offsetheight) { offsetheight = 0; } - PARAM_FIXED_OPT (offsetwidth) { offsetwidth = 0; } - PARAM_INT_OPT (ptr_target) { ptr_target = AAPTR_DEFAULT; } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default - - if (flags & CLOFF_MUL_HEIGHT) - { - if (self->player != NULL) - { - // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player - offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); - } - else - { - offsetheight = FixedMul(offsetheight, self->height); - } - } - if (flags & CLOFF_MUL_WIDTH) - { - offsetwidth = FixedMul(self->radius, offsetwidth); - } - - x1 = self->x; - y1 = self->y; - z1 = self->z + offsetheight - self->floorclip; - - if (!(flags & CLOFF_FROMBASE)) - { // default to hitscan origin - - // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player - z1 += (self->height >> 1); - if (self->player != NULL) - { - z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); - } - else - { - z1 += 8*FRACUNIT; - } - } - - if (target) - { - FVector2 xyvec(target->x - x1, target->y - y1); - fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); - - if (range && !(flags & CLOFF_CHECKPARTIAL)) - { - if (distance > range) - return numret; - } - - { - angle_t ang; - - if (flags & CLOFF_NOAIM_HORZ) - { - ang = self->angle; - } - else ang = R_PointToAngle2 (x1, y1, target->x, target->y); - - angle += ang; - - ang >>= ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - - if (flags & CLOFF_NOAIM_VERT) - { - pitch += self->pitch; - } - else if (flags & CLOFF_AIM_VERT_NOOFFSET) - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); - } - else - { - pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); - } - } - else if (flags & CLOFF_ALLOWNULL) - { - angle += self->angle; - pitch += self->pitch; - - angle_t ang = self->angle >> ANGLETOFINESHIFT; - x1 += FixedMul(offsetwidth, finesine[ang]); - y1 -= FixedMul(offsetwidth, finecosine[ang]); - } - else - { - return numret; - } - - angle >>= ANGLETOFINESHIFT; - pitch = (0-pitch)>>ANGLETOFINESHIFT; - - vx = FixedMul (finecosine[pitch], finecosine[angle]); - vy = FixedMul (finecosine[pitch], finesine[angle]); - vz = -finesine[pitch]; - } - - /* Variable set: - - jump, flags, target - x1,y1,z1 (trace point of origin) - vx,vy,vz (trace unit vector) - range - */ - - sector_t *sec = P_PointInSector(x1, y1); - - if (range == 0) - { - range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; - } - - FTraceResults trace; - LOFData lof_data; - - lof_data.Self = self; - lof_data.Target = target; - lof_data.Flags = flags; - lof_data.BadActor = false; - - Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, - CheckLOFTraceFunc, &lof_data); - - if (trace.HitType == TRACE_HitActor || - ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) - { - if (minrange > 0 && trace.Distance < minrange) - { - return numret; - } - ACTION_JUMP(jump); - } - return numret; -} - -//========================================================================== -// -// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, -// optional fixed dist_max, optional fixed dist_close) -// -// Jumps if the actor can see its target, or if the player has a linetarget. -// ProjectileTarget affects how projectiles are treated. If set, it will use -// the target of the projectile for seekers, and ignore the target for -// normal projectiles. If not set, it will use the missile's owner instead -// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max -// sets the maximum distance that actor can see, 0 means forever. dist_close -// uses special behavior if certain flags are set, 0 means no checks. -// -//========================================================================== - -enum JLOS_flags -{ - JLOSF_PROJECTILE=1, - JLOSF_NOSIGHT=2, - JLOSF_CLOSENOFOV=4, - JLOSF_CLOSENOSIGHT=8, - JLOSF_CLOSENOJUMP=16, - JLOSF_DEADNOJUMP=32, - JLOSF_CHECKMASTER=64, - JLOSF_TARGETLOS=128, - JLOSF_FLIPFOV=256, - JLOSF_ALLYNOJUMP=512, - JLOSF_COMBATANTONLY=1024, - JLOSF_NOAUTOAIM=2048, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE (jump); - PARAM_ANGLE_OPT (fov) { fov = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_FIXED_OPT (dist_max) { dist_max = 0; } - PARAM_FIXED_OPT (dist_close) { dist_close = 0; } - - angle_t an; - AActor *target, *viewport; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - bool doCheckSight; - - if (!self->player) - { - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) - { - if (self->flags2 & MF2_SEEKERMISSILE) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (target == NULL) - return numret; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) - { - return numret; - } - - doCheckSight = !(flags & JLOSF_NOSIGHT); - } - else - { - // Does the player aim at something that can be shot? - P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); - - if (!target) return numret; - - switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) - { - case JLOSF_TARGETLOS|JLOSF_FLIPFOV: - // target makes sight check, player makes fov check; player has verified fov - fov = 0; - // fall-through - case JLOSF_TARGETLOS: - doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov - break; - default: - // player has verified sight and fov - fov = 0; - // fall-through - case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov - doCheckSight = false; - break; - } - } - - // [FDARI] If target is not a combatant, don't jump - if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) - return numret; - - // [FDARI] If actors share team, don't jump - if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) - return numret; - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) - return numret; - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return numret; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } - else { viewport = self; } - - if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) - return numret; - - if (flags & JLOSF_FLIPFOV) - { - if (viewport == self) { viewport = target; target = self; } - else { target = viewport; viewport = self; } - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (viewport->x, - viewport->y, - target->x, - target->y) - - viewport->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return numret; // [KS] Outside of FOV - return - } - - } - - ACTION_JUMP(jump); - return numret; -} - - -//========================================================================== -// -// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags -// optional fixed dist_max, optional fixed dist_close) -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE (jump); - PARAM_ANGLE_OPT (fov) { fov = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_FIXED_OPT (dist_max) { dist_max = 0; } - PARAM_FIXED_OPT (dist_close) { dist_close = 0; } - - angle_t an; - AActor *target; - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (flags & JLOSF_CHECKMASTER) - { - target = self->master; - } - else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) - { - if (self->flags2 & MF2_SEEKERMISSILE) - target = self->tracer; - else - target = NULL; - } - else - { - target = self->target; - } - - if (target == NULL) - { // [KS] Let's not call P_CheckSight unnecessarily in this case. - return numret; - } - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) - { - return numret; - } - - fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); - distance = P_AproxDistance(distance, target->z - self->z); - - if (dist_max && (distance > dist_max)) - { - return numret; - } - - bool doCheckSight = !(flags & JLOSF_NOSIGHT); - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return numret; - - if (flags & JLOSF_CLOSENOFOV) - fov = 0; - - if (flags & JLOSF_CLOSENOSIGHT) - doCheckSight = false; - } - - if (fov && (fov < ANGLE_MAX)) - { - an = R_PointToAngle2 (target->x, - target->y, - self->x, - self->y) - - target->angle; - - if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) - { - return numret; // [KS] Outside of FOV - return - } - } - if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) - return numret; - - ACTION_JUMP(jump); - return numret; -} - - -//=========================================================================== -// -// A_DamageMaster (int amount) -// Damages the master of this child by the specified amount. Negative values heal. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (amount); - PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } - - if (self->master != NULL) - { - if (amount > 0) - { - P_DamageMobj(self->master, self, self, amount, damagetype, DMG_NO_ARMOR); - } - else if (amount < 0) - { - amount = -amount; - P_GiveBody(self->master, amount); - } - } - return 0; -} - -//=========================================================================== -// -// A_DamageChildren (amount) -// Damages the children of this master by the specified amount. Negative values heal. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (amount); - PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } - - TThinkerIterator it; - AActor *mo; - - while ( (mo = it.Next()) ) - { - if (mo->master == self) - { - if (amount > 0) - { - P_DamageMobj(mo, self, self, amount, damagetype, DMG_NO_ARMOR); - } - else if (amount < 0) - { - amount = -amount; - P_GiveBody(mo, amount); - } - } - } - return 0; -} - -// [KS] *** End of my modifications *** - -//=========================================================================== -// -// A_DamageSiblings (amount) -// Damages the siblings of this master by the specified amount. Negative values heal. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (amount); - PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ( (mo = it.Next()) ) - { - if (mo->master == self->master && mo != self) - { - if (amount > 0) - { - P_DamageMobj(mo, self, self, amount, damagetype, DMG_NO_ARMOR); - } - else if (amount < 0) - { - amount = -amount; - P_GiveBody(mo, amount); - } - } - } - } - return 0; -} - - -//=========================================================================== -// -// Modified code pointer from Skulltag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) -{ - PARAM_ACTION_PROLOGUE; - - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return 0; - - PARAM_INT (count); - PARAM_STATE (jump); - PARAM_BOOL_OPT (dontincrement) { dontincrement = false; } - - if (count <= 0) - return 0; - - AWeapon *weapon = self->player->ReadyWeapon; - - int ReloadCounter = weapon->ReloadCounter; - if (!dontincrement || ReloadCounter != 0) - ReloadCounter = (weapon->ReloadCounter+1) % count; - else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. - ReloadCounter = 1; - - // If we have not made our last shot... - if (ReloadCounter != 0) - { - // Go back to the refire frames, instead of continuing on to the reload frames. - ACTION_JUMP(jump); - } - else - { - // We need to reload. However, don't reload if we're out of ammo. - weapon->CheckAmmo(false, false); - } - - if (!dontincrement) - weapon->ReloadCounter = ReloadCounter; - return 0; -} - -//=========================================================================== -// -// Resets the counter for the above function -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) -{ - PARAM_ACTION_PROLOGUE; - - if (self->player == NULL || self->player->ReadyWeapon == NULL) - return 0; - - AWeapon *weapon = self->player->ReadyWeapon; - weapon->ReloadCounter = 0; - return 0; -} - -//=========================================================================== -// -// A_ChangeFlag -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STRING (flagname); - PARAM_BOOL (value); - - const char *dot = strchr(flagname, '.'); - FFlagDef *fd; - PClassActor *cls = self->GetClass(); - - if (dot != NULL) - { - FString part1(flagname.GetChars(), dot - flagname); - fd = FindFlag(cls, part1, dot + 1); - } - else - { - fd = FindFlag(cls, flagname, NULL); - } - - if (fd != NULL) - { - bool kill_before, kill_after; - INTBOOL item_before, item_after; - INTBOOL secret_before, secret_after; - - kill_before = self->CountsAsKill(); - item_before = self->flags & MF_COUNTITEM; - secret_before = self->flags5 & MF5_COUNTSECRET; - - if (fd->structoffset == -1) - { - HandleDeprecatedFlags(self, cls, value, fd->flagbit); - } - else - { - DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); - - // If these 2 flags get changed we need to update the blockmap and sector links. - bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); - - if (linkchange) self->UnlinkFromWorld(); - ModActorFlag(self, fd, value); - if (linkchange) self->LinkToWorld(); - } - kill_after = self->CountsAsKill(); - item_after = self->flags & MF_COUNTITEM; - secret_after = self->flags5 & MF5_COUNTSECRET; - // Was this monster previously worth a kill but no longer is? - // Or vice versa? - if (kill_before != kill_after) - { - if (kill_after) - { // It counts as a kill now. - level.total_monsters++; - } - else - { // It no longer counts as a kill. - level.total_monsters--; - } - } - // same for items - if (item_before != item_after) - { - if (item_after) - { // It counts as an item now. - level.total_items++; - } - else - { // It no longer counts as an item - level.total_items--; - } - } - // and secretd - if (secret_before != secret_after) - { - if (secret_after) - { // It counts as an secret now. - level.total_secrets++; - } - else - { // It no longer counts as an secret - level.total_secrets--; - } - } - } - else - { - Printf("Unknown flag '%s' in '%s'\n", flagname.GetChars(), cls->TypeName.GetChars()); - } - return 0; -} - -//=========================================================================== -// -// A_CheckFlag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STRING (flagname); - PARAM_STATE (jumpto); - PARAM_INT_OPT (checkpointer) { checkpointer = AAPTR_DEFAULT; } - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - AActor *owner = COPY_AAPTR(self, checkpointer); - if (owner == NULL) - { - return numret; - } - - if (CheckActorFlag(owner, flagname)) - { - ACTION_JUMP(jumpto); - } - return numret; -} - - -//=========================================================================== -// -// A_RemoveMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RemoveMaster) -{ - PARAM_ACTION_PROLOGUE; - if (self->master != NULL) - { - P_RemoveThing(self->master); - } - return 0; -} - -//=========================================================================== -// -// A_RemoveChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) -{ - PARAM_ACTION_PROLOGUE; - PARAM_BOOL_OPT(removeall) { removeall = false; } - - TThinkerIterator it; - AActor *mo; - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self && (mo->health <= 0 || removeall)) - { - P_RemoveThing(mo); - } - } - return 0; -} - -//=========================================================================== -// -// A_RemoveSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) -{ - PARAM_ACTION_PROLOGUE; - PARAM_BOOL_OPT(removeall) { removeall = false; } - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) - { - P_RemoveThing(mo); - } - } - } - return 0; -} - -//=========================================================================== -// -// A_RaiseMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster) -{ - PARAM_ACTION_PROLOGUE; - if (self->master != NULL) - { - P_Thing_Raise(self->master); - } - return 0; -} - -//=========================================================================== -// -// A_RaiseChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren) -{ - PARAM_ACTION_PROLOGUE; - - TThinkerIterator it; - AActor *mo; - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self) - { - P_Thing_Raise(mo); - } - } - return 0; -} - -//=========================================================================== -// -// A_RaiseSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings) -{ - PARAM_ACTION_PROLOGUE; - - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self) - { - P_Thing_Raise(mo); - } - } - } - return 0; -} - -//=========================================================================== -// -// A_MonsterRefire -// -// Keep firing unless target got out of sight -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (prob); - PARAM_STATE (jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - A_FaceTarget(self); - - if (pr_monsterrefire() < prob) - return numret; - - if (self->target == NULL - || P_HitFriend (self) - || self->target->health <= 0 - || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) - { - ACTION_JUMP(jump); - } - return numret; -} - -//=========================================================================== -// -// A_SetAngle -// -// Set actor's angle (in degrees). -// -//=========================================================================== -enum -{ - SPF_FORCECLAMP = 1, // players always clamp - SPF_INTERPOLATE = 2, -}; - - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) -{ - PARAM_ACTION_PROLOGUE; - PARAM_ANGLE_OPT(angle) { angle = 0; } - PARAM_INT_OPT(flags) { flags = 0; } - - self->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); - return 0; -} - -//=========================================================================== -// -// A_SetPitch -// -// Set actor's pitch (in degrees). -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) -{ - PARAM_ACTION_PROLOGUE; - PARAM_ANGLE(pitch); - PARAM_INT_OPT(flags) { flags = 0; } - - if (self->player != NULL || (flags & SPF_FORCECLAMP)) - { // clamp the pitch we set - int min, max; - - if (self->player != NULL) - { - min = self->player->MinPitch; - max = self->player->MaxPitch; - } - else - { - min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); - max = ANGLE_90 - (1 << ANGLETOFINESHIFT); - } - pitch = clamp(pitch, min, max); - } - - self->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); - return 0; -} - -//=========================================================================== -// -// A_ScaleVelocity -// -// Scale actor's velocity. -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED(scale); - - INTBOOL was_moving = self->velx | self->vely | self->velz; - - self->velx = FixedMul(self->velx, scale); - self->vely = FixedMul(self->vely, scale); - self->velz = FixedMul(self->velz, scale); - - // If the actor was previously moving but now is not, and is a player, - // update its player variables. (See A_Stop.) - if (was_moving) - { - CheckStopped(self); - } - return 0; -} - -//=========================================================================== -// -// A_ChangeVelocity -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) -{ - PARAM_ACTION_PROLOGUE; - PARAM_FIXED_OPT (x) { x = 0; } - PARAM_FIXED_OPT (y) { y = 0; } - PARAM_FIXED_OPT (z) { z = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - - INTBOOL was_moving = self->velx | self->vely | self->velz; - - fixed_t vx = x, vy = y, vz = z; - fixed_t sina = finesine[self->angle >> ANGLETOFINESHIFT]; - fixed_t cosa = finecosine[self->angle >> ANGLETOFINESHIFT]; - - if (flags & 1) // relative axes - make x, y relative to actor's current angle - { - vx = DMulScale16(x, cosa, -y, sina); - vy = DMulScale16(x, sina, y, cosa); - } - if (flags & 2) // discard old velocity - replace old velocity with new velocity - { - self->velx = vx; - self->vely = vy; - self->velz = vz; - } - else // add new velocity to old velocity - { - self->velx += vx; - self->vely += vy; - self->velz += vz; - } - - if (was_moving) - { - CheckStopped(self); - } - return 0; -} - -//=========================================================================== -// -// A_SetArg -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT(pos); - PARAM_INT(value); - - // Set the value of the specified arg - if ((size_t)pos < countof(self->args)) - { - self->args[pos] = value; - } - return 0; -} - -//=========================================================================== -// -// A_SetSpecial -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (spec); - PARAM_INT_OPT (arg0) { arg0 = 0; } - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (arg3) { arg3 = 0; } - PARAM_INT_OPT (arg4) { arg4 = 0; } - - self->special = spec; - self->args[0] = arg0; - self->args[1] = arg1; - self->args[2] = arg2; - self->args[3] = arg3; - self->args[4] = arg4; - return 0; -} - -//=========================================================================== -// -// A_SetUserVar -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (varname); - PARAM_INT (value); - - PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); - - if (var == NULL || (var->Flags & VARF_Native) || !var->Type->IsKindOf(RUNTIME_CLASS(PBasicType))) - { - Printf("%s is not a user variable in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return 0; - } - // Set the value of the specified user variable. - var->Type->SetValue(reinterpret_cast(self) + var->Offset, value); - return 0; -} - -//=========================================================================== -// -// A_SetUserArray -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (varname); - PARAM_INT (pos); - PARAM_INT (value); - - PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); - - if (var == NULL || (var->Flags & VARF_Native) || - !var->Type->IsKindOf(RUNTIME_CLASS(PArray)) || - !static_cast(var->Type)->ElementType->IsKindOf(RUNTIME_CLASS(PBasicType))) - { - Printf("%s is not a user array in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return 0; - } - PArray *arraytype = static_cast(var->Type); - if ((unsigned)pos >= arraytype->ElementCount) - { - Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return 0; - } - // Set the value of the specified user array at index pos. - arraytype->ElementType->SetValue(reinterpret_cast(self) + var->Offset + arraytype->ElementSize * pos, value); - return 0; -} - -//=========================================================================== -// -// A_Teleport(optional state teleportstate, optional class targettype, -// optional class fogtype, optional int flags, optional fixed mindist, -// optional fixed maxdist) -// -// Attempts to teleport to a targettype at least mindist away and at most -// maxdist away (0 means unlimited). If successful, spawn a fogtype at old -// location and place calling actor in teleportstate. -// -//=========================================================================== -enum T_Flags -{ - TF_TELEFRAG = 1, // Allow telefrag in order to teleport. - TF_RANDOMDECIDE = 2, // Randomly fail based on health. (A_Srcr2Decide) -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) -{ - PARAM_ACTION_PROLOGUE; - PARAM_STATE_OPT (teleport_state) { teleport_state = NULL; } - PARAM_CLASS_OPT (target_type, ASpecialSpot) { target_type = PClass::FindActor("BossSpot"); } - PARAM_CLASS_OPT (fog_type, AActor) { fog_type = PClass::FindActor("TeleportFog"); } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_FIXED_OPT (mindist) { mindist = 128 << FRACBITS; } - PARAM_FIXED_OPT (maxdist) { maxdist = 128 << FRACBITS; } - - ACTION_SET_RESULT(true); - - // Randomly choose not to teleport like A_Srcr2Decide. - if (flags & TF_RANDOMDECIDE) - { - static const int chance[] = - { - 192, 120, 120, 120, 64, 64, 32, 16, 0 - }; - - unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); - - if (chanceindex >= countof(chance)) - { - chanceindex = countof(chance) - 1; - } - - if (pr_teleport() >= chance[chanceindex]) - { - return numret; - } - } - - if (teleport_state == NULL) - { - // Default to Teleport. - teleport_state = self->FindState("Teleport"); - // If still nothing, then return. - if (teleport_state == NULL) - { - return numret; - } - } - - DSpotState *state = DSpotState::GetSpotState(); - if (state == NULL) - { - return numret; - } - - if (target_type == NULL) - { - target_type = PClass::FindActor("BossSpot"); - } - - AActor *spot = state->GetSpotWithMinMaxDistance(target_type, self->x, self->y, mindist, maxdist); - if (spot == NULL) - { - return numret; - } - - fixed_t prevX = self->x; - fixed_t prevY = self->y; - fixed_t prevZ = self->z; - if (P_TeleportMove (self, spot->x, spot->y, spot->z, flags & TF_TELEFRAG)) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (fog_type != NULL) - { - Spawn(fog_type, prevX, prevY, prevZ, ALLOW_REPLACE); - } - - ACTION_JUMP(teleport_state); - - self->z = self->floorz; - self->angle = spot->angle; - self->velx = self->vely = self->velz = 0; - } - return numret; -} - -//=========================================================================== -// -// A_Turn -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) -{ - PARAM_ACTION_PROLOGUE; - PARAM_ANGLE_OPT(angle) { angle = 0; } - self->angle += angle; - return 0; -} - -//=========================================================================== -// -// A_Quake -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (intensity); - PARAM_INT (duration); - PARAM_INT (damrad); - PARAM_INT (tremrad); - PARAM_SOUND_OPT (sound) { sound = "world/quake"; } - - P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); - return 0; -} - -//=========================================================================== -// -// A_Weave -// -//=========================================================================== - -void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) -{ - fixed_t newX, newY; - int weaveXY, weaveZ; - int angle; - fixed_t dist; - - weaveXY = self->WeaveIndexXY & 63; - weaveZ = self->WeaveIndexZ & 63; - angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; - - if (xydist != 0 && xyspeed != 0) - { - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX = self->x - FixedMul (finecosine[angle], dist); - newY = self->y - FixedMul (finesine[angle], dist); - weaveXY = (weaveXY + xyspeed) & 63; - dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); - newX += FixedMul (finecosine[angle], dist); - newY += FixedMul (finesine[angle], dist); - if (!(self->flags5 & MF5_NOINTERACTION)) - { - P_TryMove (self, newX, newY, true); - } - else - { - self->UnlinkFromWorld (); - self->flags |= MF_NOBLOCKMAP; - self->x = newX; - self->y = newY; - self->LinkToWorld (); - } - self->WeaveIndexXY = weaveXY; - } - if (zdist != 0 && zspeed != 0) - { - self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - weaveZ = (weaveZ + zspeed) & 63; - self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); - self->WeaveIndexZ = weaveZ; - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (xspeed); - PARAM_INT (yspeed); - PARAM_FIXED (xdist); - PARAM_FIXED (ydist); - A_Weave(self, xspeed, yspeed, xdist, ydist); - return 0; -} - - - - -//=========================================================================== -// -// A_LineEffect -// -// This allows linedef effects to be activated inside deh frames. -// -//=========================================================================== - -void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT(special) { special = 0; } - PARAM_INT_OPT(tag) { tag = 0; } - - line_t junk; - maplinedef_t oldjunk; - bool res = false; - if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up - { - if ((oldjunk.special = special)) // Linedef type - { - oldjunk.tag = tag; // Sector tag for linedef - P_TranslateLineDef(&junk, &oldjunk); // Turn into native type - res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], - junk.args[1], junk.args[2], junk.args[3], junk.args[4]); - if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, - self->flags6 |= MF6_LINEDONE; // no more for this thing - } - } - ACTION_SET_RESULT(res); - return numret; -} - -//========================================================================== -// -// A Wolf3D-style attack codepointer -// -//========================================================================== -enum WolfAttackFlags -{ - WAF_NORANDOM = 1, - WAF_USEPUFF = 2, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_SOUND_OPT (sound) { sound = "weapons/pistol"; } - PARAM_FIXED_OPT (snipe) { snipe = FRACUNIT; } - PARAM_INT_OPT (maxdamage) { maxdamage = 64; } - PARAM_INT_OPT (blocksize) { blocksize = 128; } - PARAM_INT_OPT (pointblank) { pointblank = 2; } - PARAM_INT_OPT (longrange) { longrange = 4; } - PARAM_FIXED_OPT (runspeed) { runspeed = 160*FRACUNIT; } - PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor("BulletPuff"); } - - if (!self->target) - return 0; - - // Enemy can't see target - if (!P_CheckSight(self, self->target)) - return 0; - - A_FaceTarget (self); - - // Target can dodge if it can see enemy - angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; - angle >>= 24; - bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); - - // Distance check is simplistic - fixed_t dx = abs (self->x - self->target->x); - fixed_t dy = abs (self->y - self->target->y); - fixed_t dz; - fixed_t dist = dx > dy ? dx : dy; - - // Some enemies are more precise - dist = FixedMul(dist, snipe); - - // Convert distance into integer number of blocks - dist >>= FRACBITS; - dist /= blocksize; - - // Now for the speed accuracy thingie - fixed_t speed = FixedMul(self->target->velx, self->target->velx) - + FixedMul(self->target->vely, self->target->vely) - + FixedMul(self->target->velz, self->target->velz); - int hitchance = speed < runspeed ? 256 : 160; - - // Distance accuracy (factoring dodge) - hitchance -= dist * (dodge ? 16 : 8); - - // While we're here, we may as well do something for this: - if (self->target->flags & MF_SHADOW) - { - hitchance >>= 2; - } - - // The attack itself - if (pr_cabullet() < hitchance) - { - // Compute position for spawning blood/puff - dx = self->target->x; - dy = self->target->y; - dz = self->target->z + (self->target->height>>1); - angle = R_PointToAngle2(dx, dy, self->x, self->y); - - dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); - dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); - - int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); - if (dist >= pointblank) - damage >>= 1; - if (dist >= longrange) - damage >>= 1; - FName mod = NAME_None; - bool spawnblood = !((self->target->flags & MF_NOBLOOD) - || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); - if (flags & WAF_USEPUFF && pufftype) - { - AActor *dpuff = GetDefaultByType(pufftype->GetReplacement()); - mod = dpuff->DamageType; - - if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) - damage = 0; - - if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) - { - spawnblood = false; - P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); - } - } - else if (self->target->flags3 & MF3_GHOST) - damage >>= 2; - if (damage) - { - int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); - if (spawnblood) - { - P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); - P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); - } - } - } - - // And finally, let's play the sound - S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); - return 0; -} - - -//========================================================================== -// -// A_Warp -// -//========================================================================== - -enum WARPF -{ - WARPF_ABSOLUTEOFFSET = 0x1, - WARPF_ABSOLUTEANGLE = 0x2, - WARPF_USECALLERANGLE = 0x4, - - WARPF_NOCHECKPOSITION = 0x8, - - WARPF_INTERPOLATE = 0x10, - WARPF_WARPINTERPOLATION = 0x20, - WARPF_COPYINTERPOLATION = 0x40, - - WARPF_STOP = 0x80, - WARPF_TOFLOOR = 0x100, - WARPF_TESTONLY = 0x200 -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT (destination_selector); - PARAM_FIXED_OPT (xofs) { xofs = 0; } - PARAM_FIXED_OPT (yofs) { yofs = 0; } - PARAM_FIXED_OPT (zofs) { zofs = 0; } - PARAM_ANGLE_OPT (angle) { angle = 0; } - PARAM_INT_OPT (flags) { flags = 0; } - PARAM_STATE_OPT (success_state) { success_state = NULL; } - - fixed_t - - oldx, - oldy, - oldz; - - AActor *reference = COPY_AAPTR(self, destination_selector); - - if (!reference) - { - ACTION_SET_RESULT(false); - return numret; - } - - if (!(flags & WARPF_ABSOLUTEANGLE)) - { - angle += (flags & WARPF_USECALLERANGLE) ? self->angle : reference->angle; - } - - if (!(flags & WARPF_ABSOLUTEOFFSET)) - { - angle_t fineangle = angle>>ANGLETOFINESHIFT; - oldx = xofs; - - // (borrowed from A_SpawnItemEx, assumed workable) - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - - xofs = FixedMul(oldx, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); - yofs = FixedMul(oldx, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); - } - - oldx = self->x; - oldy = self->y; - oldz = self->z; - - if (flags & WARPF_TOFLOOR) - { - // set correct xy - - self->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z); - - // now the caller's floorz should be appropriate for the assigned xy-position - // assigning position again with - - if (zofs) - { - // extra unlink, link and environment calculation - self->SetOrigin( - self->x, - self->y, - self->floorz + zofs); - } - else - { - // if there is no offset, there should be no ill effect from moving down to the - // already identified floor - - // A_Teleport does the same thing anyway - self->z = self->floorz; - } - } - else - { - self->SetOrigin( - reference->x + xofs, - reference->y + yofs, - reference->z + zofs); - } - - if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(self)) - { - if (flags & WARPF_TESTONLY) - { - self->SetOrigin(oldx, oldy, oldz); - } - else - { - self->angle = angle; - - if (flags & WARPF_STOP) - { - self->velx = 0; - self->vely = 0; - self->velz = 0; - } - - if (flags & WARPF_WARPINTERPOLATION) - { - self->PrevX += self->x - oldx; - self->PrevY += self->y - oldy; - self->PrevZ += self->z - oldz; - } - else if (flags & WARPF_COPYINTERPOLATION) - { - self->PrevX = self->x + reference->PrevX - reference->x; - self->PrevY = self->y + reference->PrevY - reference->y; - self->PrevZ = self->z + reference->PrevZ - reference->z; - } - else if (! (flags & WARPF_INTERPOLATE)) - { - self->PrevX = self->x; - self->PrevY = self->y; - self->PrevZ = self->z; - } - } - - if (success_state) - { - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - // in this case, you have the statejump to help you handle all the success anyway. - ACTION_JUMP(success_state); - return numret; - } - - ACTION_SET_RESULT(true); - } - else - { - self->SetOrigin(oldx, oldy, oldz); - ACTION_SET_RESULT(false); - } - return numret; -} - -//========================================================================== -// -// ACS_Named* stuff - -// -// These are exactly like their un-named line special equivalents, except -// they take strings instead of integers to indicate which script to run. -// Some of these probably aren't very useful, but they are included for -// the sake of completeness. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (arg3) { arg3 = 0; } - PARAM_INT_OPT (arg4) { arg4 = 0; } - - int res = P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (arg3) { arg3 = 0; } - - int res = P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (arg3) { arg3 = 0; } - - int res = P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (lock) { lock = 0; } - - int res = P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - PARAM_INT_OPT (arg1) { arg1 = 0; } - PARAM_INT_OPT (arg2) { arg2 = 0; } - PARAM_INT_OPT (lock) { lock = 0; } - - int res = P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - - int res = P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - ACTION_SET_RESULT(res); - return numret; -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME (scriptname); - PARAM_INT_OPT (mapnum) { mapnum = 0; } - - int res = P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - ACTION_SET_RESULT(res); - return numret; -} - - -//========================================================================== -// -// A_RadiusGive -// -// Uses code roughly similar to A_Explode (but without all the compatibility -// baggage and damage computation code to give an item to all eligible mobjs -// in range. -// -//========================================================================== -enum RadiusGiveFlags -{ - RGF_GIVESELF = 1, - RGF_PLAYERS = 2, - RGF_MONSTERS = 4, - RGF_OBJECTS = 8, - RGF_VOODOO = 16, - RGF_CORPSES = 32, - RGF_MASK = 63, - RGF_NOTARGET = 64, - RGF_NOTRACER = 128, - RGF_NOMASTER = 256, - RGF_CUBE = 512, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (item, AInventory); - PARAM_FIXED (distance); - PARAM_INT (flags); - PARAM_INT_OPT (amount) { amount = 0; } - - // We need a valid item, valid targets, and a valid range - if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) - { - return 0; - } - if (amount == 0) - { - amount = 1; - } - FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); - double distsquared = double(distance) * double(distance); - - AActor *thing; - while ((thing = it.Next())) - { - // Don't give to inventory items - if (thing->flags & MF_SPECIAL) - { - continue; - } - // Avoid giving to self unless requested - if (thing == self && !(flags & RGF_GIVESELF)) - { - continue; - } - // Avoiding special pointers if requested - if (((thing == self->target) && (flags & RGF_NOTARGET)) || - ((thing == self->tracer) && (flags & RGF_NOTRACER)) || - ((thing == self->master) && (flags & RGF_NOMASTER))) - { - continue; - } - // Don't give to dead thing unless requested - if (thing->flags & MF_CORPSE) - { - if (!(flags & RGF_CORPSES)) - { - continue; - } - } - else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) - { - continue; - } - // Players, monsters, and other shootable objects - if (thing->player) - { - if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) - { - continue; - } - if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) - { - continue; - } - } - else if (thing->flags3 & MF3_ISMONSTER) - { - if (!(flags & RGF_MONSTERS)) - { - continue; - } - } - else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) - { - if (!(flags & RGF_OBJECTS)) - { - continue; - } - } - else - { - continue; - } - - if (flags & RGF_CUBE) - { // check if inside a cube - if (abs(thing->x - self->x) > distance || - abs(thing->y - self->y) > distance || - abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) - { - continue; - } - } - else - { // check if inside a sphere - TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); - TVector3 spos(self->x, self->y, self->z + self->height/2); - if ((tpos - spos).LengthSquared() > distsquared) - { - continue; - } - } - fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); - - if (P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) - { // OK to give; target is in direct path - AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); - if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) - { - gift->Amount *= amount; - } - else - { - gift->Amount = amount; - } - gift->flags |= MF_DROPPED; - gift->ClearCounters(); - if (!gift->CallTryPickup (thing)) - { - gift->Destroy (); - } - } - } - return 0; -} - - -//========================================================================== -// -// A_SetTics -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) -{ - PARAM_ACTION_PROLOGUE; - PARAM_INT(tics_to_set); - - if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) - { // Is this a weapon? Need to check psp states for a match, then. Blah. - for (int i = 0; i < NUMPSPRITES; ++i) - { - if (self->player->psprites[i].state == callingstate) - { - self->player->psprites[i].tics = tics_to_set; - return 0; - } - } - } - // Just set tics for self. - self->tics = tics_to_set; - return 0; -} - -//========================================================================== -// -// A_SetDamageType -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) -{ - PARAM_ACTION_PROLOGUE; - PARAM_NAME(damagetype); - - self->DamageType = damagetype; - return 0; -} - -//========================================================================== -// -// A_DropItem -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) -{ - PARAM_ACTION_PROLOGUE; - PARAM_CLASS (spawntype, AActor); - PARAM_INT_OPT (amount) { amount = -1; } - PARAM_INT_OPT (chance) { chance = 256; } - - P_DropItem(self, spawntype, amount, chance); - return 0; -} +/* +** thingdef.cpp +** +** Code pointers for Actor definitions +** +**--------------------------------------------------------------------------- +** Copyright 2002-2006 Christoph Oelckers +** Copyright 2004-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be +** covered by the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or (at +** your option) any later version. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gi.h" +#include "g_level.h" +#include "actor.h" +#include "info.h" +#include "sc_man.h" +#include "tarray.h" +#include "w_wad.h" +#include "templates.h" +#include "r_defs.h" +#include "a_pickups.h" +#include "s_sound.h" +#include "cmdlib.h" +#include "p_lnspec.h" +#include "p_enemy.h" +#include "a_action.h" +#include "decallib.h" +#include "m_random.h" +#include "i_system.h" +#include "p_local.h" +#include "c_console.h" +#include "doomerrors.h" +#include "a_sharedglobal.h" +#include "thingdef/thingdef.h" +#include "v_video.h" +#include "v_font.h" +#include "doomstat.h" +#include "v_palette.h" +#include "g_shared/a_specialspot.h" +#include "actorptrselect.h" +#include "m_bbox.h" +#include "r_data/r_translate.h" +#include "p_trace.h" +#include "gstrings.h" + + +static FRandom pr_camissile ("CustomActorfire"); +static FRandom pr_camelee ("CustomMelee"); +static FRandom pr_cabullet ("CustomBullet"); +static FRandom pr_cajump ("CustomJump"); +static FRandom pr_cwbullet ("CustomWpBullet"); +static FRandom pr_cwjump ("CustomWpJump"); +static FRandom pr_cwpunch ("CustomWpPunch"); +static FRandom pr_grenade ("ThrowGrenade"); +static FRandom pr_crailgun ("CustomRailgun"); +static FRandom pr_spawndebris ("SpawnDebris"); +static FRandom pr_spawnitemex ("SpawnItemEx"); +static FRandom pr_burst ("Burst"); +static FRandom pr_monsterrefire ("MonsterRefire"); +static FRandom pr_teleport("A_Teleport"); + +//========================================================================== +// +// ACustomInventory :: CallStateChain +// +// Executes the code pointers in a chain of states +// until there is no next state +// +//========================================================================== + +bool ACustomInventory::CallStateChain (AActor *actor, FState *state) +{ + INTBOOL result = false; + int counter = 0; + int retval, numret; + VMReturn ret; + ret.IntAt(&retval); + VMValue params[3] = { actor, this, 0 }; + + this->flags5 |= MF5_INSTATECALL; + FState *savedstate = this->state; + + while (state != NULL) + { + this->state = state; + + if (state->ActionFunc != NULL) + { + VMFrameStack stack; + + params[2] = VMValue(state, ATAG_STATE); + retval = true; // assume success + numret = stack.Call(state->ActionFunc, params, countof(params), &ret, 1); + // As long as even one state succeeds, the whole chain succeeds unless aborted below. + result |= retval; + } + + // Since there are no delays it is a good idea to check for infinite loops here! + counter++; + if (counter >= 10000) break; + + if (this->state == state) + { + FState *next = state->GetNextState(); + + if (state == next) + { // Abort immediately if the state jumps to itself! + result = false; + break; + } + + // If both variables are still the same there was no jump + // so we must advance to the next state. + state = next; + } + else + { + state = this->state; + } + } + this->flags5 &= ~MF5_INSTATECALL; + this->state = savedstate; + return !!result; +} + +//========================================================================== +// +// A_RearrangePointers +// +// Allow an actor to change its relationship to other actors by +// copying pointers freely between TARGET MASTER and TRACER. +// Can also assign null value, but does not duplicate A_ClearTarget. +// +//========================================================================== + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (ptr_target); + PARAM_INT_OPT (ptr_master) { ptr_master = AAPTR_DEFAULT; } + PARAM_INT_OPT (ptr_tracer) { ptr_tracer = AAPTR_TRACER; } + PARAM_INT_OPT (flags) { flags = 0; } + + // Rearrange pointers internally + + // Fetch all values before modification, so that all fields can get original values + AActor + *gettarget = self->target, + *getmaster = self->master, + *gettracer = self->tracer; + + switch (ptr_target) // pick the new target + { + case AAPTR_MASTER: + self->target = getmaster; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_TRACER: + self->target = gettracer; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_NULL: + self->target = NULL; + // THIS IS NOT "A_ClearTarget", so no other targeting info is removed + break; + } + + // presently permitting non-monsters to set master + switch (ptr_master) // pick the new master + { + case AAPTR_TARGET: + self->master = gettarget; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_TRACER: + self->master = gettracer; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_NULL: + self->master = NULL; + break; + } + + switch (ptr_tracer) // pick the new tracer + { + case AAPTR_TARGET: + self->tracer = gettarget; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_MASTER: + self->tracer = getmaster; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_NULL: + self->tracer = NULL; + break; + } + return 0; +} + +//========================================================================== +// +// A_TransferPointer +// +// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), +// or from this actor's MASTER, TARGET or TRACER. +// +// You can copy any one of that actor's pointers +// +// Assign the copied pointer to any one pointer in SELF, +// MASTER, TARGET or TRACER. +// +// Any attempt to make an actor point to itself will replace the pointer +// with a null value. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (ptr_source); + PARAM_INT (ptr_recipient); + PARAM_INT (ptr_sourcefield); + PARAM_INT_OPT (ptr_recipientfield) { ptr_recipientfield = AAPTR_DEFAULT; } + PARAM_INT_OPT (flags) { flags = 0; } + + AActor *source, *recipient; + + // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) + source = COPY_AAPTR(self, ptr_source); + recipient = COPY_AAPTR(self, ptr_recipient); // pick an actor to store the provided pointer value + if (recipient == NULL) + { + return 0; + } + + // convert source from dataprovider to data + source = COPY_AAPTR(source, ptr_sourcefield); + if (source == recipient) + { // The recepient should not acquire a pointer to itself; will write NULL} + source = NULL; + } + if (ptr_recipientfield == AAPTR_DEFAULT) + { // If default: Write to same field as data was read from + ptr_recipientfield = ptr_sourcefield; + } + ASSIGN_AAPTR(recipient, ptr_recipientfield, source, flags); + return 0; +} + +//========================================================================== +// +// A_CopyFriendliness +// +// Join forces with one of the actors you are pointing to (MASTER by default) +// +// Normal CopyFriendliness reassigns health. This function will not. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT (ptr_source) { ptr_source = AAPTR_MASTER; } + + if (self->player != NULL) + { + return 0; + } + + AActor *source = COPY_AAPTR(self, ptr_source); + if (source != NULL) + { // No change in current target or health + self->CopyFriendliness(source, false, false); + } + return 0; +} + +//========================================================================== +// +// Simple flag changers +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) +{ + PARAM_ACTION_PROLOGUE; + self->flags |= MF_SOLID; + return 0; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) +{ + PARAM_ACTION_PROLOGUE; + self->flags &= ~MF_SOLID; + return 0; +} + +DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) +{ + PARAM_ACTION_PROLOGUE; + self->flags |= MF_FLOAT; + return 0; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) +{ + PARAM_ACTION_PROLOGUE; + self->flags &= ~(MF_FLOAT|MF_INFLOAT); + return 0; +} + +//========================================================================== +// +// Customizable attack functions which use actor parameters. +// +//========================================================================== +static void DoAttack (AActor *self, bool domelee, bool domissile, + int MeleeDamage, FSoundID MeleeSound, PClassActor *MissileType,fixed_t MissileHeight) +{ + if (self->target == NULL) return; + + A_FaceTarget (self); + if (domelee && MeleeDamage>0 && self->CheckMeleeRange ()) + { + int damage = pr_camelee.HitDice(MeleeDamage); + if (MeleeSound) S_Sound (self, CHAN_WEAPON, MeleeSound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, NAME_Melee); + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (domissile && MissileType != NULL) + { + // This seemingly senseless code is needed for proper aiming. + self->z += MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, MissileType, false); + self->z -= MissileHeight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2&MF2_SEEKERMISSILE) + { + missile->tracer=self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, A_MeleeAttack) +{ + PARAM_ACTION_PROLOGUE; + int MeleeDamage = self->GetClass()->MeleeDamage; + FSoundID MeleeSound = self->GetClass()->MeleeSound; + DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); + return 0; +} + +DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) +{ + PARAM_ACTION_PROLOGUE; + PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); + fixed_t MissileHeight = self->GetClass()->MissileHeight; + DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); + return 0; +} + +DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) +{ + PARAM_ACTION_PROLOGUE; + int MeleeDamage = self->GetClass()->MeleeDamage; + FSoundID MeleeSound = self->GetClass()->MeleeSound; + PClassActor *MissileType = PClass::FindActor(self->GetClass()->MissileName); + fixed_t MissileHeight = self->GetClass()->MissileHeight; + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); + return 0; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (melee_damage); + PARAM_SOUND (melee_sound); + PARAM_CLASS (missile_type, AActor); + PARAM_FIXED (missile_height); + + if (missile_type != NULL) + { + DoAttack(self, true, true, melee_damage, melee_sound, missile_type, missile_height); + } + return 0; +} + +//========================================================================== +// +// Custom sound functions. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) +{ + PARAM_ACTION_PROLOGUE; + PARAM_SOUND_OPT (soundid) { soundid = "weapons/pistol"; } + PARAM_INT_OPT (channel) { channel = CHAN_BODY; } + PARAM_FLOAT_OPT (volume) { volume = 1; } + PARAM_BOOL_OPT (looping) { looping = false; } + PARAM_FLOAT_OPT (attenuation) { attenuation = ATTN_NORM; } + + if (!looping) + { + S_Sound (self, channel, soundid, (float)volume, (float)attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, channel&7, soundid)) + { + S_Sound (self, channel | CHAN_LOOP, soundid, (float)volume, (float)attenuation); + } + } + return 0; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(slot) { slot = CHAN_VOICE; } + + S_StopSound(self, slot); + return 0; +} + +//========================================================================== +// +// These come from a time when DECORATE constants did not exist yet and +// the sound interface was less flexible. As a result the parameters are +// not optimal and these functions have been deprecated in favor of extending +// A_PlaySound and A_StopSound. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayWeaponSound) +{ + PARAM_ACTION_PROLOGUE; + PARAM_SOUND(soundid); + + S_Sound(self, CHAN_WEAPON, soundid, 1, ATTN_NORM); + return 0; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) +{ + PARAM_ACTION_PROLOGUE; + PARAM_SOUND (soundid); + PARAM_NAME (channel); + PARAM_BOOL_OPT (looping) { looping = false; } + PARAM_INT_OPT (attenuation_raw) { attenuation_raw = 0; } + + float attenuation; + switch (attenuation_raw) + { + case -1: attenuation = ATTN_STATIC; break; // drop off rapidly + default: + case 0: attenuation = ATTN_NORM; break; // normal + case 1: + case 2: attenuation = ATTN_NONE; break; // full volume + } + + if (channel < NAME_Auto || channel > NAME_SoundSlot7) + { + channel = NAME_Auto; + } + + if (!looping) + { + S_Sound (self, int(channel) - NAME_Auto, soundid, 1, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, int(channel) - NAME_Auto, soundid)) + { + S_Sound (self, (int(channel) - NAME_Auto) | CHAN_LOOP, soundid, 1, attenuation); + } + } + return 0; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME(channel); + + if (channel > NAME_Auto && channel <= NAME_SoundSlot7) + { + S_StopSound (self, int(channel) - NAME_Auto); + } + return 0; +} + +//========================================================================== +// +// Generic seeker missile function +// +//========================================================================== +static FRandom pr_seekermissile ("SeekerMissile"); +enum +{ + SMF_LOOK = 1, + SMF_PRECISE = 2, + SMF_CURSPEED = 4, +}; +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(ang1); + PARAM_INT(ang2); + PARAM_INT_OPT(flags) { flags = 0; } + PARAM_INT_OPT(chance) { chance = 50; } + PARAM_INT_OPT(distance) { distance = 10; } + + if ((flags & SMF_LOOK) && (self->tracer == 0) && (pr_seekermissile()tracer = P_RoughMonsterSearch (self, distance, true); + } + if (!P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE), !!(flags & SMF_CURSPEED))) + { + if (flags & SMF_LOOK) + { // This monster is no longer seekable, so let us look for another one next time. + self->tracer = NULL; + } + } + return 0; +} + +//========================================================================== +// +// Hitscan attack with a customizable amount of bullets (specified in damage) +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) +{ + PARAM_ACTION_PROLOGUE; + + int i; + int bangle; + int slope; + + if (!self->target) return 0; + + A_FaceTarget (self); + bangle = self->angle; + + slope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i = self->GetMissileDamage (0, 1); i > 0; --i) + { + int angle = bangle + (pr_cabullet.Random2() << 20); + int damage = ((pr_cabullet()%5)+1)*3; + P_LineAttack(self, angle, MISSILERANGE, slope, damage, + NAME_Hitscan, NAME_BulletPuff); + } + return 0; +} + + +//========================================================================== +// +// Do the state jump +// +//========================================================================== +static void DoJump(AActor *self, AActor *stateowner, FState *callingstate, FState *jumpto) +{ + if (jumpto == NULL) return; + + if (stateowner->flags5 & MF5_INSTATECALL) + { + stateowner->state = jumpto; + } + else if (self->player != NULL && callingstate == self->player->psprites[ps_weapon].state) + { + P_SetPsprite(self->player, ps_weapon, jumpto); + } + else if (self->player != NULL && callingstate == self->player->psprites[ps_flash].state) + { + P_SetPsprite(self->player, ps_flash, jumpto); + } + else if (callingstate == self->state) + { + // Rather than using self->SetState(jumpto) to set the state, + // set the state directly. Since this function is only called by + // action functions, which are only called by SetState(), we + // know that somewhere above us in the stack, a SetState() + // call is waiting for us to return. We use the flag OF_StateChanged + // to cause it to bypass the normal next state mechanism and use + // the one we set here instead. + self->state = jumpto; + self->ObjectFlags |= OF_StateChanged; + } + else + { // something went very wrong. This should never happen. + assert(false); + } +} + +// This is just to avoid having to directly reference the internally defined +// CallingState and statecall parameters in the code below. +#define ACTION_JUMP(offset) DoJump(self, stateowner, callingstate, offset) + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(maxchance) { maxchance = 256; } + + paramnum++; // Increment paramnum to point at the first jump target + int count = numparam - paramnum; + if (count > 0 && (maxchance >= 256 || pr_cajump() < maxchance)) + { + int jumpnum = (count == 1 ? 0 : (pr_cajump() % count)); + PARAM_STATE_AT(paramnum + jumpnum, jumpto); + ACTION_JUMP(jumpto); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + return numret; +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (health); + PARAM_STATE (jump); + PARAM_INT_OPT (ptr_selector) { ptr_selector = AAPTR_DEFAULT; } + + AActor *measured; + + measured = COPY_AAPTR(self, ptr_selector); + + if (measured != NULL && measured->health < health) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + return numret; +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + if (!self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + return numret; +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + if (self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + return numret; +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +static int DoJumpIfCloser(AActor *target, VM_ARGS) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED (dist); + PARAM_STATE (jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + // No target - no jump + if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && + ( (self->z > target->z && self->z - (target->z + target->height) < dist) || + (self->z <=target->z && target->z - (self->z + self->height) < dist) + ) + ) + { + ACTION_JUMP(jump); + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +{ + PARAM_ACTION_PROLOGUE; + + AActor *target; + + if (self->player == NULL) + { + target = self->target; + } + else + { + // Does the player aim at something that can be shot? + P_BulletSlope(self, &target); + } + return DoJumpIfCloser(target, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) +{ + PARAM_ACTION_PROLOGUE; + return DoJumpIfCloser(self->tracer, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) +{ + PARAM_ACTION_PROLOGUE; + return DoJumpIfCloser(self->master, VM_ARGS_NAMES); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +int DoJumpIfInventory(AActor *owner, AActor *self, AActor *stateowner, FState *callingstate, VMValue *param, int numparam, VMReturn *ret, int numret) +{ + int paramnum = NAP-1; + PARAM_CLASS (itemtype, AInventory); + PARAM_INT (itemamount); + PARAM_STATE (label); + PARAM_INT_OPT (setowner) { setowner = AAPTR_DEFAULT; } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (itemtype == NULL) + { + return numret; + } + owner = COPY_AAPTR(owner, setowner); + if (owner == NULL) + { + return numret; + } + + AInventory *item = owner->FindInventory(itemtype); + + if (item) + { + if (itemamount > 0) + { + if (item->Amount >= itemamount) + ACTION_JUMP(label); + } + else if (item->Amount >= item->MaxAmount) + { + ACTION_JUMP(label); + } + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) +{ + PARAM_ACTION_PROLOGUE; + return DoJumpIfInventory(self, self, stateowner, callingstate, param, numparam, ret, numret); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) +{ + PARAM_ACTION_PROLOGUE; + return DoJumpIfInventory(self->target, self, stateowner, callingstate, param, numparam, ret, numret); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (type); + PARAM_STATE (label); + PARAM_INT_OPT(amount) { amount = 1; } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + ABasicArmor *armor = (ABasicArmor *)self->FindInventory(NAME_BasicArmor); + + if (armor && armor->ArmorType == type && armor->Amount >= amount) + ACTION_JUMP(label); + return numret; +} + +//========================================================================== +// +// Parameterized version of A_Explode +// +//========================================================================== + +enum +{ + XF_HURTSOURCE = 1, + XF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT (damage) { damage = -1; } + PARAM_INT_OPT (distance) { distance = -1; } + PARAM_INT_OPT (flags) { flags = XF_HURTSOURCE; } + PARAM_BOOL_OPT (alert) { alert = false; } + PARAM_INT_OPT (fulldmgdistance) { fulldmgdistance = 0; } + PARAM_INT_OPT (nails) { nails = 0; } + PARAM_INT_OPT (naildamage) { naildamage = 10; } + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor("BulletPuff"); } + + if (damage < 0) // get parameters from metadata + { + damage = self->GetClass()->ExplosionDamage; + distance = self->GetClass()->ExplosionRadius; + flags = !self->GetClass()->DontHurtShooter; + alert = false; + } + if (distance <= 0) distance = damage; + + // NailBomb effect, from SMMU but not from its source code: instead it was implemented and + // generalized from the documentation at http://www.doomworld.com/eternity/engine/codeptrs.html + + if (nails) + { + angle_t ang; + for (int i = 0; i < nails; i++) + { + ang = i*(ANGLE_MAX/nails); + // Comparing the results of a test wad with Eternity, it seems A_NailBomb does not aim + P_LineAttack (self, ang, MISSILERANGE, 0, + //P_AimLineAttack (self, ang, MISSILERANGE), + naildamage, NAME_Hitscan, pufftype); + } + } + + P_RadiusAttack (self, self->target, damage, distance, self->DamageType, flags, fulldmgdistance); + P_CheckSplash(self, distance<target != NULL && self->target->player != NULL) + { + validcount++; + P_RecursiveSound (self->Sector, self->target, false, 0); + } + return 0; +} + +//========================================================================== +// +// A_RadiusThrust +// +//========================================================================== + +enum +{ + RTF_AFFECTSOURCE = 1, + RTF_NOIMPACTDAMAGE = 2, + RTF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT (force) { force = 128; } + PARAM_INT_OPT (distance) { distance = -1; } + PARAM_INT_OPT (flags) { flags = RTF_AFFECTSOURCE; } + PARAM_INT_OPT (fullthrustdistance) { fullthrustdistance = 0; } + + bool sourcenothrust = false; + + if (force == 0) force = 128; + if (distance <= 0) distance = abs(force); + + // Temporarily negate MF2_NODMGTHRUST on the shooter, since it renders this function useless. + if (!(flags & RTF_NOTMISSILE) && self->target != NULL && self->target->flags2 & MF2_NODMGTHRUST) + { + sourcenothrust = true; + self->target->flags2 &= ~MF2_NODMGTHRUST; + } + + P_RadiusAttack (self, self->target, force, distance, self->DamageType, flags | RADF_NODAMAGE, fullthrustdistance); + P_CheckSplash(self, distance << FRACBITS); + + if (sourcenothrust) + { + self->target->flags2 |= MF2_NODMGTHRUST; + } + return 0; +} + +//========================================================================== +// +// Execute a line special / script +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (special); + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (arg3) { arg3 = 0; } + PARAM_INT_OPT (arg4) { arg4 = 0; } + PARAM_INT_OPT (arg5) { arg5 = 0; } + + bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); + + ACTION_SET_RESULT(res); + return numret; +} + +//========================================================================== +// +// The ultimate code pointer: Fully customizable missiles! +// +//========================================================================== +enum CM_Flags +{ + CMF_AIMMODE = 3, + CMF_TRACKOWNER = 4, + CMF_CHECKTARGETDEAD = 8, + + CMF_ABSOLUTEPITCH = 16, + CMF_OFFSETPITCH = 32, + CMF_SAVEPITCH = 64, + + CMF_ABSOLUTEANGLE = 128 +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (ti, AActor); + PARAM_FIXED_OPT (spawnheight) { spawnheight = 32*FRACUNIT; } + PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_ANGLE_OPT (pitch) { pitch = 0; } + + int aimmode = flags & CMF_AIMMODE; + + AActor * targ; + AActor * missile; + + if (self->target != NULL || aimmode == 2) + { + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = spawnofs_xy * finecosine[ang]; + fixed_t y = spawnofs_xy * finesine[ang]; + fixed_t z = spawnheight + self->GetBobOffset() - 32*FRACUNIT + (self->player? self->player->crouchoffset : 0); + + switch (aimmode) + { + case 0: + default: + // same adjustment as above (in all 3 directions this time) - for better aiming! + self->x += x; + self->y += y; + self->z += z; + missile = P_SpawnMissileXYZ(self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); + self->x -= x; + self->y -= y; + self->z -= z; + break; + + case 1: + missile = P_SpawnMissileXYZ(self->x+x, self->y+y, self->z + self->GetBobOffset() + spawnheight, self, self->target, ti, false); + break; + + case 2: + self->x += x; + self->y += y; + missile = P_SpawnMissileAngleZSpeed(self, self->z + self->GetBobOffset() + spawnheight, ti, self->angle, 0, GetDefaultByType(ti)->Speed, self, false); + self->x -= x; + self->y -= y; + + flags |= CMF_ABSOLUTEPITCH; + + break; + } + + if (missile != NULL) + { + // Use the actual velocity instead of the missile's Speed property + // so that this can handle missiles with a high vertical velocity + // component properly. + + fixed_t missilespeed; + + if ( (CMF_ABSOLUTEPITCH|CMF_OFFSETPITCH) & flags) + { + if (CMF_OFFSETPITCH & flags) + { + FVector2 velocity (missile->velx, missile->vely); + pitch += R_PointToAngle2(0,0, (fixed_t)velocity.Length(), missile->velz); + } + ang = pitch >> ANGLETOFINESHIFT; + missilespeed = abs(FixedMul(finecosine[ang], missile->Speed)); + missile->velz = FixedMul(finesine[ang], missile->Speed); + } + else + { + FVector2 velocity (missile->velx, missile->vely); + missilespeed = (fixed_t)velocity.Length(); + } + + if (CMF_SAVEPITCH & flags) + { + missile->pitch = pitch; + // In aimmode 0 and 1 without absolutepitch or offsetpitch, the pitch parameter + // contains the unapplied parameter. In that case, it is set as pitch without + // otherwise affecting the spawned actor. + } + + missile->angle = (CMF_ABSOLUTEANGLE & flags) ? angle : missile->angle + angle ; + + ang = missile->angle >> ANGLETOFINESHIFT; + missile->velx = FixedMul(missilespeed, finecosine[ang]); + missile->vely = FixedMul(missilespeed, finesine[ang]); + + // handle projectile shooting projectiles - track the + // links back to a real owner + if (self->isMissile(!!(flags & CMF_TRACKOWNER))) + { + AActor *owner = self ;//->target; + while (owner->isMissile(!!(flags & CMF_TRACKOWNER)) && owner->target) + owner = owner->target; + targ = owner; + missile->target = owner; + // automatic handling of seeker missiles + if (self->flags & missile->flags2 & MF2_SEEKERMISSILE) + { + missile->tracer = self->tracer; + } + } + else if (missile->flags2 & MF2_SEEKERMISSILE) + { + // automatic handling of seeker missiles + missile->tracer = self->target; + } + // we must redo the spectral check here because the owner is set after spawning so the FriendPlayer value may be wrong + if (missile->flags4 & MF4_SPECTRAL) + { + if (missile->target != NULL) + { + missile->SetFriendPlayer(missile->target->player); + } + else + { + missile->FriendPlayer = 0; + } + } + P_CheckMissileSpawn(missile, self->radius); + } + } + } + else if (flags & CMF_CHECKTARGETDEAD) + { + // Target is dead and the attack shall be aborted. + if (self->SeeState != NULL && (self->health > 0 || !(self->flags3 & MF3_ISMONSTER))) + self->SetState(self->SeeState); + } + return 0; +} + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum CBA_Flags +{ + CBAF_AIMFACING = 1, + CBAF_NORANDOM = 2, + CBAF_EXPLICITANGLE = 4, + CBAF_NOPITCH = 8, + CBAF_NORANDOMPUFFZ = 16, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE (spread_xy); + PARAM_ANGLE (spread_z); + PARAM_INT (numbullets); + PARAM_INT (damageperbullet); + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } + PARAM_FIXED_OPT (range) { range = MISSILERANGE; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (range == 0) + range = MISSILERANGE; + + int i; + int bangle; + int bslope = 0; + int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if (self->target || (flags & CBAF_AIMFACING)) + { + if (!(flags & CBAF_AIMFACING)) A_FaceTarget (self); + bangle = self->angle; + + if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i = 0; i < numbullets; i++) + { + int angle = bangle; + int slope = bslope; + + if (flags & CBAF_EXPLICITANGLE) + { + angle += spread_xy; + slope += spread_z; + } + else + { + angle += pr_cwbullet.Random2() * (spread_xy / 255); + slope += pr_cwbullet.Random2() * (spread_z / 255); + } + + int damage = damageperbullet; + + if (!(flags & CBAF_NORANDOM)) + damage *= ((pr_cabullet()%3)+1); + + P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); + } + } + return 0; +} + +//========================================================================== +// +// A fully customizable melee attack +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMeleeAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT (damage) { damage = 0; } + PARAM_SOUND_OPT (meleesound) { meleesound = 0; } + PARAM_SOUND_OPT (misssound) { misssound = 0; } + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_BOOL_OPT (bleed) { bleed = true; } + + if (damagetype == NAME_None) + damagetype = NAME_Melee; // Melee is the default type + + if (!self->target) + return 0; + + A_FaceTarget (self); + if (self->CheckMeleeRange ()) + { + if (meleesound) + S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); + if (bleed) + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else + { + if (misssound) + S_Sound (self, CHAN_WEAPON, misssound, 1, ATTN_NORM); + } + return 0; +} + +//========================================================================== +// +// A fully customizable combo attack +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (ti, AActor); + PARAM_FIXED (spawnheight); + PARAM_INT (damage); + PARAM_SOUND_OPT (meleesound) { meleesound = 0; } + PARAM_NAME_OPT (damagetype) { damagetype = NAME_Melee; } + PARAM_BOOL_OPT (bleed) { bleed = true; } + + if (!self->target) + return 0; + + A_FaceTarget (self); + if (self->CheckMeleeRange()) + { + if (damagetype == NAME_None) + damagetype = NAME_Melee; // Melee is the default type + if (meleesound) + S_Sound (self, CHAN_WEAPON, meleesound, 1, ATTN_NORM); + int newdam = P_DamageMobj (self->target, self, self, damage, damagetype); + if (bleed) + P_TraceBleed (newdam > 0 ? newdam : damage, self->target, self); + } + else if (ti) + { + // This seemingly senseless code is needed for proper aiming. + self->z += spawnheight + self->GetBobOffset() - 32*FRACUNIT; + AActor *missile = P_SpawnMissileXYZ (self->x, self->y, self->z + 32*FRACUNIT, self, self->target, ti, false); + self->z -= spawnheight + self->GetBobOffset() - 32*FRACUNIT; + + if (missile) + { + // automatic handling of seeker missiles + if (missile->flags2 & MF2_SEEKERMISSILE) + { + missile->tracer = self->target; + } + P_CheckMissileSpawn(missile, self->radius); + } + } + return 0; +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (!ACTION_CALL_FROM_WEAPON()) + return numret; + + if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) + { + ACTION_JUMP(jump); + } + return numret; +} + + +//========================================================================== +// +// An even more customizable hitscan attack +// +//========================================================================== +enum FB_Flags +{ + FBF_USEAMMO = 1, + FBF_NORANDOM = 2, + FBF_EXPLICITANGLE = 4, + FBF_NOPITCH = 8, + FBF_NOFLASH = 16, + FBF_NORANDOMPUFFZ = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE (spread_xy); + PARAM_ANGLE (spread_z); + PARAM_INT (numbullets); + PARAM_INT (damageperbullet); + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } + PARAM_INT_OPT (flags) { flags = FBF_USEAMMO; } + PARAM_FIXED_OPT (range) { range = 0; } + + if (!self->player) return 0; + + player_t *player = self->player; + AWeapon *weapon = player->ReadyWeapon; + + int i; + int bangle; + int bslope = 0; + int laflags = (flags & FBF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if ((flags & FBF_USEAMMO) && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) + return 0; // out of ammo + } + + if (range == 0) + range = PLAYERMISSILERANGE; + + if (!(flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); + + if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); + bangle = self->angle; + + if (pufftype == NULL) + pufftype = PClass::FindActor(NAME_BulletPuff); + + if (weapon != NULL) + { + S_Sound(self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if ((numbullets == 1 && !player->refire) || numbullets == 0) + { + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + + P_LineAttack(self, bangle, range, bslope, damage, NAME_Hitscan, pufftype, laflags); + } + else + { + if (numbullets < 0) + numbullets = 1; + for (i = 0; i < numbullets; i++) + { + int angle = bangle; + int slope = bslope; + + if (flags & FBF_EXPLICITANGLE) + { + angle += spread_xy; + slope += spread_z; + } + else + { + angle += pr_cwbullet.Random2() * (spread_xy / 255); + slope += pr_cwbullet.Random2() * (spread_z / 255); + } + + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + + P_LineAttack(self, angle, range, slope, damage, NAME_Hitscan, pufftype, laflags); + } + } + return 0; +} + + +//========================================================================== +// +// A_FireProjectile +// +//========================================================================== +enum FP_Flags +{ + FPF_AIMATANGLE = 1, + FPF_TRANSFERTRANSLATION = 2, +}; +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (ti, AActor); + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_BOOL_OPT (useammo) { useammo = true; } + PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } + PARAM_FIXED_OPT (spawnheight) { spawnheight = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_ANGLE_OPT (pitch) { pitch = 0; } + + if (!self->player) + return 0; + + player_t *player = self->player; + AWeapon *weapon = player->ReadyWeapon; + AActor *linetarget; + + if (useammo && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) + return 0; // out of ammo + } + + if (ti) + { + angle_t ang = (self->angle - ANGLE_90) >> ANGLETOFINESHIFT; + fixed_t x = spawnofs_xy * finecosine[ang]; + fixed_t y = spawnofs_xy * finesine[ang]; + fixed_t z = spawnheight; + fixed_t shootangle = self->angle; + + if (flags & FPF_AIMATANGLE) shootangle += angle; + + // Temporarily adjusts the pitch + fixed_t saved_player_pitch = self->pitch; + self->pitch -= pitch; + AActor *misl = P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); + self->pitch = saved_player_pitch; + // automatic handling of seeker missiles + if (misl) + { + if (flags & FPF_TRANSFERTRANSLATION) + misl->Translation = self->Translation; + if (linetarget && (misl->flags2 & MF2_SEEKERMISSILE)) + misl->tracer = linetarget; + if (!(flags & FPF_AIMATANGLE)) + { + // This original implementation is to aim straight ahead and then offset + // the angle from the resulting direction. + FVector3 velocity(misl->velx, misl->vely, 0); + fixed_t missilespeed = (fixed_t)velocity.Length(); + misl->angle += angle; + angle_t an = misl->angle >> ANGLETOFINESHIFT; + misl->velx = FixedMul (missilespeed, finecosine[an]); + misl->vely = FixedMul (missilespeed, finesine[an]); + } + } + } + return 0; +} + + +//========================================================================== +// +// A_CustomPunch +// +// Berserk is not handled here. That can be done with A_CheckIfInventory +// +//========================================================================== + +enum +{ + CPF_USEAMMO = 1, + CPF_DAGGER = 2, + CPF_PULLIN = 4, + CPF_NORANDOMPUFFZ = 8, + CPF_NOTURN = 16, + CPF_STEALARMOR = 32, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (damage); + PARAM_BOOL_OPT (norandom) { norandom = false; } + PARAM_INT_OPT (flags) { flags = CPF_USEAMMO; } + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; } + PARAM_INT_OPT (lifestealmax) { lifestealmax = 0; } + PARAM_CLASS_OPT (armorbonustype, ABasicArmorBonus) { armorbonustype = NULL; } + + if (!self->player) + return 0; + + player_t *player = self->player; + AWeapon *weapon = player->ReadyWeapon; + + + angle_t angle; + int pitch; + AActor * linetarget; + int actualdamage; + + if (!norandom) + damage *= pr_cwpunch() % 8 + 1; + + angle = self->angle + (pr_cwpunch.Random2() << 18); + if (range == 0) + range = MELEERANGE; + pitch = P_AimLineAttack (self, angle, range, &linetarget); + + // only use ammo when actually hitting something! + if ((flags & CPF_USEAMMO) && linetarget && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) + return 0; // out of ammo + } + + if (pufftype == NULL) + pufftype = PClass::FindActor(NAME_BulletPuff); + int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0); + + P_LineAttack (self, angle, range, pitch, damage, NAME_Melee, pufftype, puffFlags, &linetarget, &actualdamage); + + if (linetarget) + { + if (lifesteal && !(linetarget->flags5 & MF5_DONTDRAIN)) + { + if (flags & CPF_STEALARMOR) + { + if (armorbonustype == NULL) + { + armorbonustype = dyn_cast(PClass::FindClass("ArmorBonus")); + } + if (armorbonustype != NULL) + { + assert(armorbonustype->IsDescendantOf(RUNTIME_CLASS(ABasicArmorBonus))); + ABasicArmorBonus *armorbonus = static_cast(Spawn(armorbonustype, 0,0,0, NO_REPLACE)); + armorbonus->SaveAmount *= (actualdamage * lifesteal) >> FRACBITS; + armorbonus->MaxSaveAmount = lifestealmax <= 0 ? armorbonus->MaxSaveAmount : lifestealmax; + armorbonus->flags |= MF_DROPPED; + armorbonus->ClearCounters(); + + if (!armorbonus->CallTryPickup(self)) + { + armorbonus->Destroy (); + } + } + } + else + { + P_GiveBody (self, (actualdamage * lifesteal) >> FRACBITS, lifestealmax); + } + } + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if (!(flags & CPF_NOTURN)) + { + // turn to face target + self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y); + } + + if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; + if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); + } + return 0; +} + + +//========================================================================== +// +// customizable railgun attack function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (damage); + PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } + PARAM_BOOL_OPT (useammo) { useammo = true; } + PARAM_COLOR_OPT (color1) { color1 = 0; } + PARAM_COLOR_OPT (color2) { color2 = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } + PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } + PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_INT_OPT (duration) { duration = 0; } + PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } + PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } + PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } + PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } + + if (range == 0) range = 8192*FRACUNIT; + if (sparsity == 0) sparsity=1.0; + + if (self->player == NULL) + return 0; + + AWeapon *weapon = self->player->ReadyWeapon; + + // only use ammo when actually hitting something! + if (useammo) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) + return 0; // out of ammo + } + + angle_t angle; + angle_t slope; + + if (flags & RAF_EXPLICITANGLE) + { + angle = spread_xy; + slope = spread_z; + } + else + { + angle = pr_crailgun.Random2() * (spread_xy / 255); + slope = pr_crailgun.Random2() * (spread_z / 255); + } + + P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angle, slope, range, duration, sparsity, driftspeed, spawnclass); + return 0; +} + +//========================================================================== +// +// also for monsters +// +//========================================================================== +enum +{ + CRF_DONTAIM = 0, + CRF_AIMPARALLEL = 1, + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (damage); + PARAM_INT_OPT (spawnofs_xy) { spawnofs_xy = 0; } + PARAM_COLOR_OPT (color1) { color1 = 0; } + PARAM_COLOR_OPT (color2) { color2 = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_INT_OPT (aim) { aim = CRF_DONTAIM; } + PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } + PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } + PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_INT_OPT (duration) { duration = 0; } + PARAM_FLOAT_OPT (sparsity) { sparsity = 1; } + PARAM_FLOAT_OPT (driftspeed) { driftspeed = 1; } + PARAM_CLASS_OPT (spawnclass, AActor){ spawnclass = NULL; } + PARAM_FIXED_OPT (spawnofs_z) { spawnofs_z = 0; } + + if (range == 0) range = 8192*FRACUNIT; + if (sparsity == 0) sparsity = 1; + + AActor *linetarget; + + fixed_t saved_x = self->x; + fixed_t saved_y = self->y; + angle_t saved_angle = self->angle; + fixed_t saved_pitch = self->pitch; + + if (aim && self->target == NULL) + { + return 0; + } + // [RH] Andy Baker's stealth monsters + if (self->flags & MF_STEALTH) + { + self->visdir = 1; + } + + self->flags &= ~MF_AMBUSH; + + + if (aim) + { + self->angle = R_PointToAngle2 (self->x, + self->y, + self->target->x, + self->target->y); + } + self->pitch = P_AimLineAttack (self, self->angle, MISSILERANGE, &linetarget, ANGLE_1*60, 0, aim ? self->target : NULL); + if (linetarget == NULL && aim) + { + // We probably won't hit the target, but aim at it anyway so we don't look stupid. + FVector2 xydiff(self->target->x - self->x, self->target->y - self->y); + double zdiff = (self->target->z + (self->target->height>>1)) - + (self->z + (self->height>>1) - self->floorclip); + self->pitch = int(atan2(zdiff, xydiff.Length()) * ANGLE_180 / -M_PI); + } + // Let the aim trail behind the player + if (aim) + { + saved_angle = self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + + if (aim == CRF_AIMDIRECT) + { + // Tricky: We must offset to the angle of the current position + // but then change the angle again to ensure proper aim. + self->x += spawnofs_xy * finecosine[self->angle]; + self->y += spawnofs_xy * finesine[self->angle]; + spawnofs_xy = 0; + self->angle = R_PointToAngle2 (self->x, self->y, + self->target->x - self->target->velx * 3, + self->target->y - self->target->vely * 3); + } + + if (self->target->flags & MF_SHADOW) + { + angle_t rnd = pr_crailgun.Random2() << 21; + self->angle += rnd; + saved_angle = rnd; + } + } + + angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; + + angle_t angleoffset; + angle_t slopeoffset; + + if (flags & CRF_EXPLICITANGLE) + { + angleoffset = spread_xy; + slopeoffset = spread_z; + } + else + { + angleoffset = pr_crailgun.Random2() * (spread_xy / 255); + slopeoffset = pr_crailgun.Random2() * (spread_z / 255); + } + + P_RailAttack (self, damage, spawnofs_xy, spawnofs_z, color1, color2, maxdiff, flags, pufftype, angleoffset, slopeoffset, range, duration, sparsity, driftspeed, spawnclass); + + self->x = saved_x; + self->y = saved_y; + self->angle = saved_angle; + self->pitch = saved_pitch; + return 0; +} + +//=========================================================================== +// +// DoGiveInventory +// +//=========================================================================== + +static int DoGiveInventory(AActor *receiver, bool orresult, VM_ARGS) +{ + int paramnum = NAP-1; + PARAM_CLASS (mi, AInventory); + PARAM_INT_OPT (amount) { amount = 1; } + PARAM_INT_OPT (setreceiver) { setreceiver = AAPTR_DEFAULT; } + + receiver = COPY_AAPTR(receiver, setreceiver); + if (receiver == NULL) + { // If there's nothing to receive it, it's obviously a fail, right? + ACTION_SET_RESULT(false); + return numret; + } + + bool res = true; + + if (amount <= 0) + { + amount = 1; + } + if (mi) + { + AInventory *item = static_cast(Spawn(mi, 0, 0, 0, NO_REPLACE)); + if (item->IsKindOf(RUNTIME_CLASS(AHealth))) + { + item->Amount *= amount; + } + else + { + item->Amount = amount; + } + item->flags |= MF_DROPPED; + item->ClearCounters(); + if (!item->CallTryPickup(receiver)) + { + item->Destroy(); + res = false; + } + else + { + res = true; + } + } + else + { + res = false; + } + if (!orresult) + { + ACTION_SET_RESULT(res); + } + else + { + ACTION_OR_RESULT(res); + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) +{ + PARAM_ACTION_PROLOGUE; + return DoGiveInventory(self, false, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) +{ + PARAM_ACTION_PROLOGUE; + return DoGiveInventory(self->target, false, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) +{ + PARAM_ACTION_PROLOGUE; + + TThinkerIterator it; + AActor *mo; + + ACTION_SET_RESULT(false); + while ((mo = it.Next())) + { + if (mo->master == self) + { + numret = DoGiveInventory(mo, true, VM_ARGS_NAMES); + } + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) +{ + PARAM_ACTION_PROLOGUE; + + TThinkerIterator it; + AActor *mo; + + ACTION_SET_RESULT(false); + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) + { + numret = DoGiveInventory(mo, true, VM_ARGS_NAMES); + } + } + } + return numret; +} + +//=========================================================================== +// +// A_TakeInventory +// +//=========================================================================== + +enum +{ + TIF_NOTAKEINFINITE = 1, +}; + +int DoTakeInventory(AActor *receiver, bool orresult, VM_ARGS) +{ + int paramnum = NAP-1; + PARAM_CLASS (itemtype, AInventory); + PARAM_INT_OPT (amount) { amount = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_INT_OPT (setreceiver) { setreceiver = AAPTR_DEFAULT; } + + if (itemtype == NULL) + { + ACTION_SET_RESULT(true); + return numret; + } + receiver = COPY_AAPTR(receiver, setreceiver); + if (receiver == NULL) + { + ACTION_SET_RESULT(false); + return numret; + } + + bool res = false; + + AInventory *inv = receiver->FindInventory(itemtype); + + if (inv && !inv->IsKindOf(RUNTIME_CLASS(AHexenArmor))) + { + if (inv->Amount > 0) + { + res = true; + } + // Do not take ammo if the "no take infinite/take as ammo depletion" flag is set + // and infinite ammo is on + if (flags & TIF_NOTAKEINFINITE && + ((dmflags & DF_INFINITE_AMMO) || (receiver->player->cheats & CF_INFINITEAMMO)) && + inv->IsKindOf(RUNTIME_CLASS(AAmmo))) + { + // Nothing to do here, except maybe res = false;? Would it make sense? + } + else if (!amount || amount>=inv->Amount) + { + if (inv->ItemFlags&IF_KEEPDEPLETED) inv->Amount=0; + else inv->Destroy(); + } + else + { + inv->Amount -= amount; + } + } + if (!orresult) + { + ACTION_SET_RESULT(res); + } + else + { + ACTION_OR_RESULT(res); + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) +{ + PARAM_ACTION_PROLOGUE; + return DoTakeInventory(self, false, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) +{ + PARAM_ACTION_PROLOGUE; + return DoTakeInventory(self->target, false, VM_ARGS_NAMES); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) +{ + PARAM_ACTION_PROLOGUE; + ACTION_SET_RESULT(false); + + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) + { + DoTakeInventory(mo, true, VM_ARGS_NAMES); + } + } + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) +{ + PARAM_ACTION_PROLOGUE; + ACTION_SET_RESULT(false); + + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) + { + DoTakeInventory(mo, true, VM_ARGS_NAMES); + } + } + } + return numret; +} + +//=========================================================================== +// +// Common code for A_SpawnItem and A_SpawnItemEx +// +//=========================================================================== + +enum SIX_Flags +{ + SIXF_TRANSFERTRANSLATION = 1 << 0, + SIXF_ABSOLUTEPOSITION = 1 << 1, + SIXF_ABSOLUTEANGLE = 1 << 2, + SIXF_ABSOLUTEVELOCITY = 1 << 3, + SIXF_SETMASTER = 1 << 4, + SIXF_NOCHECKPOSITION = 1 << 5, + SIXF_TELEFRAG = 1 << 6, + SIXF_CLIENTSIDE = 1 << 7, // only used by Skulldronum + SIXF_TRANSFERAMBUSHFLAG = 1 << 8, + SIXF_TRANSFERPITCH = 1 << 9, + SIXF_TRANSFERPOINTERS = 1 << 10, + SIXF_USEBLOODCOLOR = 1 << 11, + SIXF_CLEARCALLERTID = 1 << 12, + SIXF_MULTIPLYSPEED = 1 << 13, + SIXF_TRANSFERSCALE = 1 << 14, + SIXF_TRANSFERSPECIAL = 1 << 15, + SIXF_CLEARCALLERSPECIAL = 1 << 16, + SIXF_TRANSFERSTENCILCOL = 1 << 17, + SIXF_TRANSFERALPHA = 1 << 18, + SIXF_TRANSFERRENDERSTYLE = 1 << 19, + SIXF_SETTARGET = 1 << 20, + SIXF_SETTRACER = 1 << 21, + SIXF_NOPOINTERS = 1 << 22, + SIXF_ORIGINATOR = 1 << 23, +}; + +static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) +{ + if (mo == NULL) + { + return false; + } + AActor *originator = self; + + if (!(mo->flags2 & MF2_DONTTRANSLATE)) + { + if (flags & SIXF_TRANSFERTRANSLATION) + { + mo->Translation = self->Translation; + } + else if (flags & SIXF_USEBLOODCOLOR) + { + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + PalEntry bloodcolor = self->GetBloodColor(); + mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); + } + } + if (flags & SIXF_TRANSFERPOINTERS) + { + mo->target = self->target; + mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set + mo->tracer = self->tracer; + } + + mo->angle = self->angle; + if (flags & SIXF_TRANSFERPITCH) + { + mo->pitch = self->pitch; + } + if (!(flags & SIXF_ORIGINATOR)) + { + while (originator && originator->isMissile()) + { + originator = originator->target; + } + } + if (flags & SIXF_TELEFRAG) + { + P_TeleportMove(mo, mo->x, mo->y, mo->z, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SIXF_NOCHECKPOSITION; + } + if (mo->flags3 & MF3_ISMONSTER) + { + if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) + { + // The monster is blocked so don't spawn it at all! + mo->ClearCounters(); + mo->Destroy(); + return false; + } + else if (originator && !(flags & SIXF_NOPOINTERS)) + { + if (originator->flags3 & MF3_ISMONSTER) + { + // If this is a monster transfer all friendliness information + mo->CopyFriendliness(originator, true); + } + else if (originator->player) + { + // A player always spawns a monster friendly to him + mo->flags |= MF_FRIENDLY; + mo->SetFriendPlayer(originator->player); + + AActor * attacker=originator->player->attacker; + if (attacker) + { + if (!(attacker->flags&MF_FRIENDLY) || + (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) + { + // Target the monster which last attacked the player + mo->LastHeard = mo->target = attacker; + } + } + } + } + } + else if (!(flags & SIXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo->target = originator ? originator : self; + } + if (flags & SIXF_NOPOINTERS) + { + //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. + mo->LastHeard = NULL; //Sanity check. + mo->target = NULL; + mo->master = NULL; + mo->tracer = NULL; + } + if (flags & SIXF_SETMASTER) + { // don't let it attack you (optional)! + mo->master = originator; + } + if (flags & SIXF_SETTARGET) + { + mo->target = originator; + } + if (flags & SIXF_SETTRACER) + { + mo->tracer = originator; + } + if (flags & SIXF_TRANSFERSCALE) + { + mo->scaleX = self->scaleX; + mo->scaleY = self->scaleY; + } + if (flags & SIXF_TRANSFERAMBUSHFLAG) + { + mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); + } + if (flags & SIXF_CLEARCALLERTID) + { + self->RemoveFromHash(); + self->tid = 0; + } + if (flags & SIXF_TRANSFERSPECIAL) + { + mo->special = self->special; + memcpy(mo->args, self->args, sizeof(self->args)); + } + if (flags & SIXF_CLEARCALLERSPECIAL) + { + self->special = 0; + memset(self->args, 0, sizeof(self->args)); + } + if (flags & SIXF_TRANSFERSTENCILCOL) + { + mo->fillcolor = self->fillcolor; + } + if (flags & SIXF_TRANSFERALPHA) + { + mo->alpha = self->alpha; + } + if (flags & SIXF_TRANSFERRENDERSTYLE) + { + mo->RenderStyle = self->RenderStyle; + } + + return true; +} + +//=========================================================================== +// +// A_SpawnItem +// +// Spawns an item in front of the caller like Heretic's time bomb +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItem) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS_OPT (missile, AActor) { missile = PClass::FindActor("Unknown"); } + PARAM_FIXED_OPT (distance) { distance = 0; } + PARAM_FIXED_OPT (zheight) { zheight = 0; } + PARAM_BOOL_OPT (useammo) { useammo = true; } + PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; } + + if (missile == NULL) + { + ACTION_SET_RESULT(false); + return numret; + } + + ACTION_SET_RESULT(true); + // Don't spawn monsters if this actor has been massacred + if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) + { + return numret; + } + + if (distance == 0) + { + // use the minimum distance that does not result in an overlap + distance = (self->radius + GetDefaultByType(missile)->radius) >> FRACBITS; + } + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon, so use some ammo + AWeapon *weapon = self->player->ReadyWeapon; + + if (weapon == NULL) + return numret; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) + return numret; + } + + AActor *mo = Spawn(missile, + self->x + FixedMul(distance, finecosine[self->angle>>ANGLETOFINESHIFT]), + self->y + FixedMul(distance, finesine[self->angle>>ANGLETOFINESHIFT]), + self->z - self->floorclip + self->GetBobOffset() + zheight, ALLOW_REPLACE); + + int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state + return numret; +} + +//=========================================================================== +// +// A_SpawnItemEx +// +// Enhanced spawning function +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (missile, AActor); + PARAM_FIXED_OPT (xofs) { xofs = 0; } + PARAM_FIXED_OPT (yofs) { yofs = 0; } + PARAM_FIXED_OPT (zofs) { zofs = 0; } + PARAM_FIXED_OPT (xvel) { xvel = 0; } + PARAM_FIXED_OPT (yvel) { yvel = 0; } + PARAM_FIXED_OPT (zvel) { zvel = 0; } + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_INT_OPT (chance) { chance = 0; } + PARAM_INT_OPT (tid) { tid = 0; } + + if (missile == NULL) + { + ACTION_SET_RESULT(false); + return numret; + } + + ACTION_SET_RESULT(true); + if (chance > 0 && pr_spawnitemex() < chance) + return numret; + + // Don't spawn monsters if this actor has been massacred + if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) + return numret; + + fixed_t x, y; + + if (!(flags & SIXF_ABSOLUTEANGLE)) + { + angle += self->angle; + } + + angle_t ang = angle >> ANGLETOFINESHIFT; + + if (flags & SIXF_ABSOLUTEPOSITION) + { + x = self->x + xofs; + y = self->y + yofs; + } + else + { + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + x = self->x + FixedMul(xofs, finecosine[ang]) + FixedMul(yofs, finesine[ang]); + y = self->y + FixedMul(xofs, finesine[ang]) - FixedMul(yofs, finecosine[ang]); + } + + if (!(flags & SIXF_ABSOLUTEVELOCITY)) + { + // Same orientation issue here! + fixed_t newxvel = FixedMul(xvel, finecosine[ang]) + FixedMul(yvel, finesine[ang]); + yvel = FixedMul(xvel, finesine[ang]) - FixedMul(yvel, finecosine[ang]); + xvel = newxvel; + } + + AActor *mo = Spawn(missile, x, y, self->z - self->floorclip + self->GetBobOffset() + zofs, ALLOW_REPLACE); + bool res = InitSpawnedItem(self, mo, flags); + ACTION_SET_RESULT(res); // for an inventory item's use state + if (res) + { + if (tid != 0) + { + assert(mo->tid == 0); + mo->tid = tid; + mo->AddToHash(); + } + if (flags & SIXF_MULTIPLYSPEED) + { + mo->velx = FixedMul(xvel, mo->Speed); + mo->vely = FixedMul(yvel, mo->Speed); + mo->velz = FixedMul(zvel, mo->Speed); + } + else + { + mo->velx = xvel; + mo->vely = yvel; + mo->velz = zvel; + } + mo->angle = angle; + } + return numret; +} + +//=========================================================================== +// +// A_ThrowGrenade +// +// Throws a grenade (like Hexen's fighter flechette) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (missile, AActor); + PARAM_FIXED_OPT (zheight) { zheight = 0; } + PARAM_FIXED_OPT (xyvel) { xyvel = 0; } + PARAM_FIXED_OPT (zvel) { zvel = 0; } + PARAM_BOOL_OPT (useammo) { useammo = true; } + + ACTION_SET_RESULT(true); + if (missile == NULL) + return numret; + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon, so use some ammo + AWeapon *weapon = self->player->ReadyWeapon; + + if (weapon == NULL) + return numret; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) + return numret; + } + + AActor *bo; + + bo = Spawn(missile, self->x, self->y, + self->z - self->floorclip + self->GetBobOffset() + zheight + 35*FRACUNIT + (self->player? self->player->crouchoffset : 0), + ALLOW_REPLACE); + if (bo) + { + P_PlaySpawnSound(bo, self); + if (xyvel != 0) + bo->Speed = xyvel; + bo->angle = self->angle + (((pr_grenade()&7) - 4) << 24); + + angle_t pitch = angle_t(-self->pitch) >> ANGLETOFINESHIFT; + angle_t angle = bo->angle >> ANGLETOFINESHIFT; + + // There are two vectors we are concerned about here: xy and z. We rotate + // them separately according to the shooter's pitch and then sum them to + // get the final velocity vector to shoot with. + + fixed_t xy_xyscale = FixedMul(bo->Speed, finecosine[pitch]); + fixed_t xy_velz = FixedMul(bo->Speed, finesine[pitch]); + fixed_t xy_velx = FixedMul(xy_xyscale, finecosine[angle]); + fixed_t xy_vely = FixedMul(xy_xyscale, finesine[angle]); + + pitch = angle_t(self->pitch) >> ANGLETOFINESHIFT; + fixed_t z_xyscale = FixedMul(zvel, finesine[pitch]); + fixed_t z_velz = FixedMul(zvel, finecosine[pitch]); + fixed_t z_velx = FixedMul(z_xyscale, finecosine[angle]); + fixed_t z_vely = FixedMul(z_xyscale, finesine[angle]); + + bo->velx = xy_velx + z_velx + (self->velx >> 1); + bo->vely = xy_vely + z_vely + (self->vely >> 1); + bo->velz = xy_velz + z_velz; + + bo->target = self; + P_CheckMissileSpawn (bo, self->radius); + } + else + { + ACTION_SET_RESULT(false); + } + return numret; +} + + +//=========================================================================== +// +// A_Recoil +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED(xyvel); + + angle_t angle = self->angle + ANG180; + angle >>= ANGLETOFINESHIFT; + self->velx += FixedMul(xyvel, finecosine[angle]); + self->vely += FixedMul(xyvel, finesine[angle]); + return 0; +} + + +//=========================================================================== +// +// A_SelectWeapon +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS(cls, AWeapon); + + if (cls == NULL || self->player == NULL) + { + ACTION_SET_RESULT(false); + return numret; + } + + AWeapon *weaponitem = static_cast(self->FindInventory(cls)); + + if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) + { + if (self->player->ReadyWeapon != weaponitem) + { + self->player->PendingWeapon = weaponitem; + } + ACTION_SET_RESULT(true); + } + else + { + ACTION_SET_RESULT(false); + } + return numret; +} + + +//=========================================================================== +// +// A_Print +// +//=========================================================================== +EXTERN_CVAR(Float, con_midtime) + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STRING (text); + PARAM_FLOAT_OPT (time) { time = 0; } + PARAM_NAME_OPT (fontname) { fontname = NAME_None; } + + if (text[0] == '$') text = GStrings(&text[1]); + if (self->CheckLocalView (consoleplayer) || + (self->target != NULL && self->target->CheckLocalView (consoleplayer))) + { + float saved = con_midtime; + FFont *font = NULL; + + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = float(time); + } + FString formatted = strbin1(text); + C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + } + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! + return numret; +} + +//=========================================================================== +// +// A_PrintBold +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STRING (text); + PARAM_FLOAT_OPT (time) { time = 0; } + PARAM_NAME_OPT (fontname) { fontname = NAME_None; } + + float saved = con_midtime; + FFont *font = NULL; + + if (text[0] == '$') text = GStrings(&text[1]); + if (fontname != NAME_None) + { + font = V_GetFont(fontname); + } + if (time > 0) + { + con_midtime = float(time); + } + FString formatted = strbin1(text); + C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); + con_midtime = saved; + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! + return numret; +} + +//=========================================================================== +// +// A_Log +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STRING(text); + + if (text[0] == '$') text = GStrings(&text[1]); + FString formatted = strbin1(text); + Printf("%s\n", formatted.GetChars()); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! + return numret; +} + +//========================================================================= +// +// A_LogInt +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(num); + Printf("%d\n", num); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! + return numret; +} + +//=========================================================================== +// +// A_SetTranslucent +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED (alpha); + PARAM_INT_OPT (mode) { mode = 0; } + + mode = mode == 0 ? STYLE_Translucent : mode == 2 ? STYLE_Fuzzy : STYLE_Add; + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha = clamp(alpha, 0, FRACUNIT); + self->RenderStyle = ERenderStyle(mode); + return 0; +} + +//=========================================================================== +// +// A_FadeIn +// +// Fades the actor in +// +//=========================================================================== + +enum FadeFlags +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; } + PARAM_INT_OPT(flags) { flags = 0; } + + if (reduce == 0) + { + reduce = FRACUNIT / 10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha += reduce; + + if (self->alpha >= FRACUNIT) + { + if (flags & FTF_CLAMP) + { + self->alpha = FRACUNIT; + } + if (flags & FTF_REMOVE) + { + self->Destroy(); + } + } + return 0; +} + +//=========================================================================== +// +// A_FadeOut +// +// fades the actor out and destroys it when done +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; } + PARAM_INT_OPT(flags) { flags = FTF_REMOVE; } + + if (reduce == 0) + { + reduce = FRACUNIT/10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha -= reduce; + if (self->alpha <= 0) + { + if (flags & FTF_CLAMP) + { + self->alpha = 0; + } + if (flags & FTF_REMOVE) + { + self->Destroy(); + } + } + return 0; +} + +//=========================================================================== +// +// A_FadeTo +// +// fades the actor to a specified transparency by a specified amount and +// destroys it if so desired +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED (target); + PARAM_FIXED_OPT (amount) { amount = fixed_t(0.1*FRACUNIT); } + PARAM_INT_OPT (flags) { flags = 0; } + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + + if (self->alpha > target) + { + self->alpha -= amount; + + if (self->alpha < target) + { + self->alpha = target; + } + } + else if (self->alpha < target) + { + self->alpha += amount; + + if (self->alpha > target) + { + self->alpha = target; + } + } + if (flags & FTF_CLAMP) + { + self->alpha = clamp(self->alpha, 0, FRACUNIT); + } + if (self->alpha == target && (flags & FTF_REMOVE)) + { + self->Destroy(); + } + return 0; +} + +//=========================================================================== +// +// A_Scale(float scalex, optional float scaley) +// +// Scales the actor's graphics. If scaley is 0, use scalex. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetScale) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED (scalex); + PARAM_FIXED_OPT (scaley) { scaley = scalex; } + + self->scaleX = scalex; + self->scaleY = scaley; + return 0; +} + +//=========================================================================== +// +// A_SetMass(int mass) +// +// Sets the actor's mass. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (mass); + + self->Mass = mass; + return 0; +} + +//=========================================================================== +// +// A_SpawnDebris +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (debris, AActor); + PARAM_BOOL_OPT (transfer_translation) { transfer_translation = false; } + PARAM_FIXED_OPT (mult_h) { mult_h = FRACUNIT; } + PARAM_FIXED_OPT (mult_v) { mult_v = FRACUNIT; } + int i; + AActor *mo; + + if (debris == NULL) + return 0; + + // only positive values make sense here + if (mult_v <= 0) + mult_v = FRACUNIT; + if (mult_h <= 0) + mult_h = FRACUNIT; + + for (i = 0; i < GetDefaultByType(debris)->health; i++) + { + mo = Spawn(debris, self->x+((pr_spawndebris()-128)<<12), + self->y + ((pr_spawndebris()-128)<<12), + self->z + (pr_spawndebris()*self->height/256+self->GetBobOffset()), ALLOW_REPLACE); + if (mo) + { + if (transfer_translation) + { + mo->Translation = self->Translation; + } + if (i < mo->GetClass()->NumOwnedStates) + { + mo->SetState (mo->GetClass()->OwnedStates + i); + } + mo->velz = FixedMul(mult_v, ((pr_spawndebris()&7)+5)*FRACUNIT); + mo->velx = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + mo->vely = FixedMul(mult_h, pr_spawndebris.Random2()<<(FRACBITS-6)); + } + } + return 0; +} + + +//=========================================================================== +// +// A_CheckSight +// jumps if no player can see this actor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + // Always check sight from each player. + if (P_CheckSight(players[i].mo, self, SF_IGNOREVISIBILITY)) + { + return numret; + } + // If a player is viewing from a non-player, then check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + P_CheckSight(players[i].camera, self, SF_IGNOREVISIBILITY)) + { + return numret; + } + } + } + + ACTION_JUMP(jump); + return numret; +} + +//=========================================================================== +// +// A_CheckSightOrRange +// Jumps if this actor is out of range of all players *and* out of sight. +// Useful for maps with many multi-actor special effects. +// +//=========================================================================== +static bool DoCheckSightOrRange(AActor *self, AActor *camera, double range) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height) + { + dz = self->z + self->height - eyez; + } + else if (eyez < self->z) + { + dz = self->z - eyez; + } + else + { + dz = 0; + } + if ((dx*dx) + (dy*dy) + (dz*dz) <= range) + { // Within range + return true; + } + + // Now check LOS. + if (P_CheckSight(camera, self, SF_IGNOREVISIBILITY)) + { // Visible + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSightOrRange) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FLOAT(range); + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckSightOrRange(self, players[i].mo, range)) + { + return numret; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckSightOrRange(self, players[i].camera, range)) + { + return numret; + } + } + } + ACTION_JUMP(jump); + return numret; +} + +//=========================================================================== +// +// A_CheckRange +// Jumps if this actor is out of range of all players. +// +//=========================================================================== +static bool DoCheckRange(AActor *self, AActor *camera, double range) +{ + if (camera == NULL) + { + return false; + } + // Check distance first, since it's cheaper than checking sight. + double dx = self->x - camera->x; + double dy = self->y - camera->y; + double dz; + fixed_t eyez = (camera->z + camera->height - (camera->height>>2)); // same eye height as P_CheckSight + if (eyez > self->z + self->height){ + dz = self->z + self->height - eyez; + } + else if (eyez < self->z){ + dz = self->z - eyez; + } + else{ + dz = 0; + } + if ((dx*dx) + (dy*dy) + (dz*dz) <= range){ + // Within range + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FLOAT(range); + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + range = range * range * (double(FRACUNIT) * FRACUNIT); // no need for square roots + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (playeringame[i]) + { + // Always check from each player. + if (DoCheckRange(self, players[i].mo, range)) + { + return numret; + } + // If a player is viewing from a non-player, check that too. + if (players[i].camera != NULL && players[i].camera->player == NULL && + DoCheckRange(self, players[i].camera, range)) + { + return numret; + } + } + } + ACTION_JUMP(jump); + return numret; +} + + +//=========================================================================== +// +// Inventory drop +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS(drop, AInventory); + + if (drop) + { + AInventory *inv = self->FindInventory(drop); + if (inv) + { + self->DropInventory(inv); + } + } + return 0; +} + + +//=========================================================================== +// +// A_SetBlend +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) +{ + PARAM_ACTION_PROLOGUE; + PARAM_COLOR (color); + PARAM_FLOAT (alpha); + PARAM_INT (tics); + PARAM_COLOR_OPT (color2) { color2 = 0; } + + if (color == MAKEARGB(255,255,255,255)) + color = 0; + if (color2 == MAKEARGB(255,255,255,255)) + color2 = 0; + if (color2.a == 0) + color2 = color; + + new DFlashFader(color.r/255.f, color.g/255.f, color.b/255.f, float(alpha), + color2.r/255.f, color2.g/255.f, color2.b/255.f, 0, + float(tics)/TICRATE, self); + return 0; +} + + +//=========================================================================== +// +// A_JumpIf +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL (condition); + PARAM_STATE (jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (condition) + ACTION_JUMP(jump); + return numret; +} + +//=========================================================================== +// +// A_CountdownArg +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(argnum); + PARAM_STATE_OPT(state) { state = self->FindState(NAME_Death); } + + if (argnum > 0 && argnum < (int)countof(self->args)) + { + if (!self->args[argnum]--) + { + if (self->flags & MF_MISSILE) + { + P_ExplodeMissile(self, NULL, NULL); + } + else if (self->flags & MF_SHOOTABLE) + { + P_DamageMobj(self, NULL, NULL, self->health, NAME_None, DMG_FORCED); + } + else + { + self->SetState(self->FindState(NAME_Death)); + } + } + else + { + self->SetState(state); + } + } + return 0; +} + +//============================================================================ +// +// A_Burst +// +//============================================================================ + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS(chunk, AActor); + + int i, numChunks; + AActor * mo; + + if (chunk == NULL) + { + return 0; + } + + self->velx = self->vely = self->velz = 0; + self->height = self->GetDefault()->height; + + // [RH] In Hexen, this creates a random number of shards (range [24,56]) + // with no relation to the size of the self shattering. I think it should + // base the number of shards on the size of the dead thing, so bigger + // things break up into more shards than smaller things. + // An self with radius 20 and height 64 creates ~40 chunks. + numChunks = MAX (4, (self->radius>>FRACBITS)*(self->height>>FRACBITS)/32); + i = (pr_burst.Random2()) % (numChunks/4); + for (i = MAX (24, numChunks + i); i >= 0; i--) + { + mo = Spawn(chunk, + self->x + (((pr_burst()-128)*self->radius)>>7), + self->y + (((pr_burst()-128)*self->radius)>>7), + self->z + (pr_burst()*self->height/255 + self->GetBobOffset()), ALLOW_REPLACE); + + if (mo) + { + mo->velz = FixedDiv(mo->z - self->z, self->height)<<2; + mo->velx = pr_burst.Random2 () << (FRACBITS-7); + mo->vely = pr_burst.Random2 () << (FRACBITS-7); + mo->RenderStyle = self->RenderStyle; + mo->alpha = self->alpha; + mo->CopyFriendliness(self, true); + } + } + + // [RH] Do some stuff to make this more useful outside Hexen + if (self->flags4 & MF4_BOSSDEATH) + { + CALL_ACTION(A_BossDeath, self); + } + A_Unblock(self, true); + + self->Destroy (); + return 0; +} + +//=========================================================================== +// +// A_CheckFloor +// [GRB] Jumps if actor is standing on floor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->z <= self->floorz) + { + ACTION_JUMP(jump); + } + return numret; +} + +//=========================================================================== +// +// A_CheckCeiling +// [GZ] Totally copied from A_CheckFloor, jumps if actor touches ceiling +// + +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); + if (self->z + self->height >= self->ceilingz) // Height needs to be counted + { + ACTION_JUMP(jump); + } + return numret; +} + +//=========================================================================== +// +// A_Stop +// resets all velocity of the actor to 0 +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_Stop) +{ + PARAM_ACTION_PROLOGUE; + self->velx = self->vely = self->velz = 0; + if (self->player && self->player->mo == self && !(self->player->cheats & CF_PREDICTING)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } + return 0; +} + +static void CheckStopped(AActor *self) +{ + if (self->player != NULL && + self->player->mo == self && + !(self->player->cheats & CF_PREDICTING) && + !(self->velx | self->vely | self->velz)) + { + self->player->mo->PlayIdle(); + self->player->velx = self->player->vely = 0; + } +} + +//=========================================================================== +// +// A_Respawn +// +//=========================================================================== + +DECLARE_ACTION(A_RestoreSpecialPosition) + +enum RS_Flags +{ + RSF_FOG=1, + RSF_KEEPTARGET=2, + RSF_TELEFRAG=4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(flags) { flags = RSF_FOG; } + + bool oktorespawn = false; + fixed_t oldx = self->x; + fixed_t oldy = self->y; + fixed_t oldz = self->z; + + self->flags |= MF_SOLID; + self->height = self->GetDefault()->height; + CALL_ACTION(A_RestoreSpecialPosition, self); + + if (flags & RSF_TELEFRAG) + { + // [KS] DIE DIE DIE DIE erm *ahem* =) + oktorespawn = P_TeleportMove(self, self->x, self->y, self->z, true); + if (oktorespawn) + { // Need to do this over again, since P_TeleportMove() will redo + // it with the proper point-on-side calculation. + self->UnlinkFromWorld(); + self->LinkToWorld(true); + sector_t *sec = self->Sector; + self->dropoffz = + self->floorz = sec->floorplane.ZatPoint(self->x, self->y); + self->ceilingz = sec->ceilingplane.ZatPoint(self->x, self->y); + P_FindFloorCeiling(self, FFCF_ONLYSPAWNPOS); + } + } + else + { + oktorespawn = P_CheckPosition(self, self->x, self->y, true); + } + + if (oktorespawn) + { + AActor *defs = self->GetDefault(); + self->health = defs->health; + + // [KS] Don't keep target, because it could be self if the monster committed suicide + // ...Actually it's better off an option, so you have better control over monster behavior. + if (!(flags & RSF_KEEPTARGET)) + { + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; + } + else + { + // Don't attack yourself (Re: "Marine targets itself after suicide") + if (self->target == self) + self->target = NULL; + if (self->lastenemy == self) + self->lastenemy = NULL; + } + + self->flags = (defs->flags & ~MF_FRIENDLY) | (self->flags & MF_FRIENDLY); + self->flags2 = defs->flags2; + self->flags3 = (defs->flags3 & ~(MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)) | (self->flags3 & (MF3_NOSIGHTCHECK | MF3_HUNTPLAYERS)); + self->flags4 = (defs->flags4 & ~MF4_NOHATEPLAYERS) | (self->flags4 & MF4_NOHATEPLAYERS); + self->flags5 = defs->flags5; + self->flags6 = defs->flags6; + self->flags7 = defs->flags7; + self->SetState (self->SpawnState); + self->renderflags &= ~RF_INVISIBLE; + + if (flags & RSF_FOG) + { + P_SpawnTeleportFog(self, oldx, oldy, oldz, true); + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + } + if (self->CountsAsKill()) + { + level.total_monsters++; + } + } + else + { + self->flags &= ~MF_SOLID; + } + return 0; +} + + +//========================================================================== +// +// A_PlayerSkinCheck +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE(jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->player != NULL && + skins[self->player->userinfo.GetSkin()].othergame) + { + ACTION_JUMP(jump); + } + return numret; +} + +//=========================================================================== +// +// A_SetGravity +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED(gravity); + + self->gravity = clamp(gravity, 0, FRACUNIT*10); + return 0; +} + + +// [KS] *** Start of my modifications *** + +//=========================================================================== +// +// A_ClearTarget +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) +{ + PARAM_ACTION_PROLOGUE; + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; + return 0; +} + +//========================================================================== +// +// A_CheckLOF (state jump, int flags = CRF_AIM_VERT|CRF_AIM_HOR, +// fixed range = 0, angle angle = 0, angle pitch = 0, +// fixed offsetheight = 32, fixed offsetwidth = 0, +// int ptr_target = AAPTR_DEFAULT (target) ) +// +//========================================================================== + +enum CLOF_flags +{ + CLOFF_NOAIM_VERT = 0x00000001, + CLOFF_NOAIM_HORZ = 0x00000002, + + CLOFF_JUMPENEMY = 0x00000004, + CLOFF_JUMPFRIEND = 0x00000008, + CLOFF_JUMPOBJECT = 0x00000010, + CLOFF_JUMPNONHOSTILE = 0x00000020, + + CLOFF_SKIPENEMY = 0x00000040, + CLOFF_SKIPFRIEND = 0x00000080, + CLOFF_SKIPOBJECT = 0x00000100, + CLOFF_SKIPNONHOSTILE = 0x00000200, + + CLOFF_MUSTBESHOOTABLE = 0x00000400, + + CLOFF_SKIPTARGET = 0x00000800, + CLOFF_ALLOWNULL = 0x00001000, + CLOFF_CHECKPARTIAL = 0x00002000, + + CLOFF_MUSTBEGHOST = 0x00004000, + CLOFF_IGNOREGHOST = 0x00008000, + + CLOFF_MUSTBESOLID = 0x00010000, + CLOFF_BEYONDTARGET = 0x00020000, + + CLOFF_FROMBASE = 0x00040000, + CLOFF_MUL_HEIGHT = 0x00080000, + CLOFF_MUL_WIDTH = 0x00100000, + + CLOFF_JUMP_ON_MISS = 0x00200000, + CLOFF_AIM_VERT_NOOFFSET = 0x00400000, + + CLOFF_SETTARGET = 0x00800000, + CLOFF_SETMASTER = 0x01000000, + CLOFF_SETTRACER = 0x02000000, +}; + +struct LOFData +{ + AActor *Self; + AActor *Target; + int Flags; + bool BadActor; +}; + +ETraceStatus CheckLOFTraceFunc(FTraceResults &trace, void *userdata) +{ + LOFData *data = (LOFData *)userdata; + int flags = data->Flags; + + if (trace.HitType != TRACE_HitActor) + { + return TRACE_Stop; + } + if (trace.Actor == data->Target) + { + if (flags & CLOFF_SKIPTARGET) + { + if (flags & CLOFF_BEYONDTARGET) + { + return TRACE_Skip; + } + return TRACE_Abort; + } + return TRACE_Stop; + } + if (flags & CLOFF_MUSTBESHOOTABLE) + { // all shootability checks go here + if (!(trace.Actor->flags & MF_SHOOTABLE)) + { + return TRACE_Skip; + } + if (trace.Actor->flags2 & MF2_NONSHOOTABLE) + { + return TRACE_Skip; + } + } + if ((flags & CLOFF_MUSTBESOLID) && !(trace.Actor->flags & MF_SOLID)) + { + return TRACE_Skip; + } + if (flags & CLOFF_MUSTBEGHOST) + { + if (!(trace.Actor->flags3 & MF3_GHOST)) + { + return TRACE_Skip; + } + } + else if (flags & CLOFF_IGNOREGHOST) + { + if (trace.Actor->flags3 & MF3_GHOST) + { + return TRACE_Skip; + } + } + if ( + ((flags & CLOFF_JUMPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_JUMPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_JUMPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_JUMPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Stop; + } + if ( + ((flags & CLOFF_SKIPENEMY) && data->Self->IsHostile(trace.Actor)) || + ((flags & CLOFF_SKIPFRIEND) && data->Self->IsFriend(trace.Actor)) || + ((flags & CLOFF_SKIPOBJECT) && !(trace.Actor->flags3 & MF3_ISMONSTER)) || + ((flags & CLOFF_SKIPNONHOSTILE) && (trace.Actor->flags3 & MF3_ISMONSTER) && !data->Self->IsHostile(trace.Actor)) + ) + { + return TRACE_Skip; + } + data->BadActor = true; + return TRACE_Abort; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckLOF) +{ + // Check line of fire + + /* + Not accounted for / I don't know how it works: FLOORCLIP + */ + + AActor *target; + fixed_t + x1, y1, z1, + vx, vy, vz; + + PARAM_ACTION_PROLOGUE; + PARAM_STATE (jump); + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_FIXED_OPT (minrange) { minrange = 0; } + { + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_ANGLE_OPT (pitch) { pitch = 0; } + PARAM_FIXED_OPT (offsetheight) { offsetheight = 0; } + PARAM_FIXED_OPT (offsetwidth) { offsetwidth = 0; } + PARAM_INT_OPT (ptr_target) { ptr_target = AAPTR_DEFAULT; } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + target = COPY_AAPTR(self, ptr_target == AAPTR_DEFAULT ? AAPTR_TARGET|AAPTR_PLAYER_GETTARGET|AAPTR_NULL : ptr_target); // no player-support by default + + if (flags & CLOFF_MUL_HEIGHT) + { + if (self->player != NULL) + { + // Synced with hitscan: self->player->mo->height is strangely conscientious about getting the right actor for player + offsetheight = FixedMul(offsetheight, FixedMul (self->player->mo->height, self->player->crouchfactor)); + } + else + { + offsetheight = FixedMul(offsetheight, self->height); + } + } + if (flags & CLOFF_MUL_WIDTH) + { + offsetwidth = FixedMul(self->radius, offsetwidth); + } + + x1 = self->x; + y1 = self->y; + z1 = self->z + offsetheight - self->floorclip; + + if (!(flags & CLOFF_FROMBASE)) + { // default to hitscan origin + + // Synced with hitscan: self->height is strangely NON-conscientious about getting the right actor for player + z1 += (self->height >> 1); + if (self->player != NULL) + { + z1 += FixedMul (self->player->mo->AttackZOffset, self->player->crouchfactor); + } + else + { + z1 += 8*FRACUNIT; + } + } + + if (target) + { + FVector2 xyvec(target->x - x1, target->y - y1); + fixed_t distance = P_AproxDistance((fixed_t)xyvec.Length(), target->z - z1); + + if (range && !(flags & CLOFF_CHECKPARTIAL)) + { + if (distance > range) + return numret; + } + + { + angle_t ang; + + if (flags & CLOFF_NOAIM_HORZ) + { + ang = self->angle; + } + else ang = R_PointToAngle2 (x1, y1, target->x, target->y); + + angle += ang; + + ang >>= ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + + if (flags & CLOFF_NOAIM_VERT) + { + pitch += self->pitch; + } + else if (flags & CLOFF_AIM_VERT_NOOFFSET) + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + offsetheight + target->height / 2); + } + else + { + pitch += R_PointToAngle2 (0,0, (fixed_t)xyvec.Length(), target->z - z1 + target->height / 2); + } + } + else if (flags & CLOFF_ALLOWNULL) + { + angle += self->angle; + pitch += self->pitch; + + angle_t ang = self->angle >> ANGLETOFINESHIFT; + x1 += FixedMul(offsetwidth, finesine[ang]); + y1 -= FixedMul(offsetwidth, finecosine[ang]); + } + else + { + return numret; + } + + angle >>= ANGLETOFINESHIFT; + pitch = (0-pitch)>>ANGLETOFINESHIFT; + + vx = FixedMul (finecosine[pitch], finecosine[angle]); + vy = FixedMul (finecosine[pitch], finesine[angle]); + vz = -finesine[pitch]; + } + + /* Variable set: + + jump, flags, target + x1,y1,z1 (trace point of origin) + vx,vy,vz (trace unit vector) + range + */ + + sector_t *sec = P_PointInSector(x1, y1); + + if (range == 0) + { + range = (self->player != NULL) ? PLAYERMISSILERANGE : MISSILERANGE; + } + + FTraceResults trace; + LOFData lof_data; + + lof_data.Self = self; + lof_data.Target = target; + lof_data.Flags = flags; + lof_data.BadActor = false; + + Trace(x1, y1, z1, sec, vx, vy, vz, range, 0xFFFFFFFF, ML_BLOCKEVERYTHING, self, trace, 0, + CheckLOFTraceFunc, &lof_data); + + if (trace.HitType == TRACE_HitActor || + ((flags & CLOFF_JUMP_ON_MISS) && !lof_data.BadActor && trace.HitType != TRACE_HitNone)) + { + if (minrange > 0 && trace.Distance < minrange) + { + return numret; + } + if ((trace.HitType == TRACE_HitActor) && (trace.Actor != NULL) && !(lof_data.BadActor)) + { + if (flags & (CLOFF_SETTARGET)) self->target = trace.Actor; + if (flags & (CLOFF_SETMASTER)) self->master = trace.Actor; + if (flags & (CLOFF_SETTRACER)) self->tracer = trace.Actor; + } + ACTION_JUMP(jump); + } + return numret; +} + +//========================================================================== +// +// A_JumpIfTargetInLOS (state label, optional fixed fov, optional int flags, +// optional fixed dist_max, optional fixed dist_close) +// +// Jumps if the actor can see its target, or if the player has a linetarget. +// ProjectileTarget affects how projectiles are treated. If set, it will use +// the target of the projectile for seekers, and ignore the target for +// normal projectiles. If not set, it will use the missile's owner instead +// (the default). ProjectileTarget is now flag JLOSF_PROJECTILE. dist_max +// sets the maximum distance that actor can see, 0 means forever. dist_close +// uses special behavior if certain flags are set, 0 means no checks. +// +//========================================================================== + +enum JLOS_flags +{ + JLOSF_PROJECTILE = 1 << 0, + JLOSF_NOSIGHT = 1 << 1, + JLOSF_CLOSENOFOV = 1 << 2, + JLOSF_CLOSENOSIGHT = 1 << 3, + JLOSF_CLOSENOJUMP = 1 << 4, + JLOSF_DEADNOJUMP = 1 << 5, + JLOSF_CHECKMASTER = 1 << 6, + JLOSF_TARGETLOS = 1 << 7, + JLOSF_FLIPFOV = 1 << 8, + JLOSF_ALLYNOJUMP = 1 << 9, + JLOSF_COMBATANTONLY = 1 << 10, + JLOSF_NOAUTOAIM = 1 << 11, + JLOSF_CHECKTRACER = 1 << 12, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE (jump); + PARAM_ANGLE_OPT (fov) { fov = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (dist_max) { dist_max = 0; } + PARAM_FIXED_OPT (dist_close) { dist_close = 0; } + + angle_t an; + AActor *target, *viewport; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + bool doCheckSight; + + if (!self->player) + { + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if ((self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) || (flags & JLOSF_CHECKTRACER)) + { + if ((self->flags2 & MF2_SEEKERMISSILE) || (flags & JLOSF_CHECKTRACER)) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (target == NULL) + return numret; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) + { + return numret; + } + + doCheckSight = !(flags & JLOSF_NOSIGHT); + } + else + { + // Does the player aim at something that can be shot? + P_AimLineAttack(self, self->angle, MISSILERANGE, &target, (flags & JLOSF_NOAUTOAIM) ? ANGLE_1/2 : 0); + + if (!target) return numret; + + switch (flags & (JLOSF_TARGETLOS|JLOSF_FLIPFOV)) + { + case JLOSF_TARGETLOS|JLOSF_FLIPFOV: + // target makes sight check, player makes fov check; player has verified fov + fov = 0; + // fall-through + case JLOSF_TARGETLOS: + doCheckSight = !(flags & JLOSF_NOSIGHT); // The target is responsible for sight check and fov + break; + default: + // player has verified sight and fov + fov = 0; + // fall-through + case JLOSF_FLIPFOV: // Player has verified sight, but target must verify fov + doCheckSight = false; + break; + } + } + + // [FDARI] If target is not a combatant, don't jump + if ( (flags & JLOSF_COMBATANTONLY) && (!target->player) && !(target->flags3 & MF3_ISMONSTER)) + return numret; + + // [FDARI] If actors share team, don't jump + if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) + return numret; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) + return numret; + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return numret; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (flags & JLOSF_TARGETLOS) { viewport = target; target = self; } + else { viewport = self; } + + if (doCheckSight && !P_CheckSight (viewport, target, SF_IGNOREVISIBILITY)) + return numret; + + if (flags & JLOSF_FLIPFOV) + { + if (viewport == self) { viewport = target; target = self; } + else { target = viewport; viewport = self; } + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (viewport->x, + viewport->y, + target->x, + target->y) + - viewport->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return numret; // [KS] Outside of FOV - return + } + + } + + ACTION_JUMP(jump); + return numret; +} + + +//========================================================================== +// +// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags +// optional fixed dist_max, optional fixed dist_close) +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE (jump); + PARAM_ANGLE_OPT (fov) { fov = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (dist_max) { dist_max = 0; } + PARAM_FIXED_OPT (dist_close) { dist_close = 0; } + + angle_t an; + AActor *target; + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) + { + if (self->flags2 & MF2_SEEKERMISSILE) + target = self->tracer; + else + target = NULL; + } + else + { + target = self->target; + } + + if (target == NULL) + { // [KS] Let's not call P_CheckSight unnecessarily in this case. + return numret; + } + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) + { + return numret; + } + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) + { + return numret; + } + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return numret; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (fov && (fov < ANGLE_MAX)) + { + an = R_PointToAngle2 (target->x, + target->y, + self->x, + self->y) + - target->angle; + + if (an > (fov / 2) && an < (ANGLE_MAX - (fov / 2))) + { + return numret; // [KS] Outside of FOV - return + } + } + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + return numret; + + ACTION_JUMP(jump); + return numret; +} + +//=========================================================================== +// +// Modified code pointer from Skulltag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) +{ + PARAM_ACTION_PROLOGUE; + + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return 0; + + PARAM_INT (count); + PARAM_STATE (jump); + PARAM_BOOL_OPT (dontincrement) { dontincrement = false; } + + if (count <= 0) + return 0; + + AWeapon *weapon = self->player->ReadyWeapon; + + int ReloadCounter = weapon->ReloadCounter; + if (!dontincrement || ReloadCounter != 0) + ReloadCounter = (weapon->ReloadCounter+1) % count; + else // 0 % 1 = 1? So how do we check if the weapon was never fired? We should only do this when we're not incrementing the counter though. + ReloadCounter = 1; + + // If we have not made our last shot... + if (ReloadCounter != 0) + { + // Go back to the refire frames, instead of continuing on to the reload frames. + ACTION_JUMP(jump); + } + else + { + // We need to reload. However, don't reload if we're out of ammo. + weapon->CheckAmmo(false, false); + } + + if (!dontincrement) + weapon->ReloadCounter = ReloadCounter; + return 0; +} + +//=========================================================================== +// +// Resets the counter for the above function +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) +{ + PARAM_ACTION_PROLOGUE; + + if (self->player == NULL || self->player->ReadyWeapon == NULL) + return 0; + + AWeapon *weapon = self->player->ReadyWeapon; + weapon->ReloadCounter = 0; + return 0; +} + +//=========================================================================== +// +// A_ChangeFlag +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STRING (flagname); + PARAM_BOOL (value); + + const char *dot = strchr(flagname, '.'); + FFlagDef *fd; + PClassActor *cls = self->GetClass(); + + if (dot != NULL) + { + FString part1(flagname.GetChars(), dot - flagname); + fd = FindFlag(cls, part1, dot + 1); + } + else + { + fd = FindFlag(cls, flagname, NULL); + } + + if (fd != NULL) + { + bool kill_before, kill_after; + INTBOOL item_before, item_after; + INTBOOL secret_before, secret_after; + + kill_before = self->CountsAsKill(); + item_before = self->flags & MF_COUNTITEM; + secret_before = self->flags5 & MF5_COUNTSECRET; + + if (fd->structoffset == -1) + { + HandleDeprecatedFlags(self, cls, value, fd->flagbit); + } + else + { + DWORD *flagp = (DWORD*) (((char*)self) + fd->structoffset); + + // If these 2 flags get changed we need to update the blockmap and sector links. + bool linkchange = flagp == &self->flags && (fd->flagbit == MF_NOBLOCKMAP || fd->flagbit == MF_NOSECTOR); + + if (linkchange) self->UnlinkFromWorld(); + ModActorFlag(self, fd, value); + if (linkchange) self->LinkToWorld(); + } + kill_after = self->CountsAsKill(); + item_after = self->flags & MF_COUNTITEM; + secret_after = self->flags5 & MF5_COUNTSECRET; + // Was this monster previously worth a kill but no longer is? + // Or vice versa? + if (kill_before != kill_after) + { + if (kill_after) + { // It counts as a kill now. + level.total_monsters++; + } + else + { // It no longer counts as a kill. + level.total_monsters--; + } + } + // same for items + if (item_before != item_after) + { + if (item_after) + { // It counts as an item now. + level.total_items++; + } + else + { // It no longer counts as an item + level.total_items--; + } + } + // and secretd + if (secret_before != secret_after) + { + if (secret_after) + { // It counts as an secret now. + level.total_secrets++; + } + else + { // It no longer counts as an secret + level.total_secrets--; + } + } + } + else + { + Printf("Unknown flag '%s' in '%s'\n", flagname.GetChars(), cls->TypeName.GetChars()); + } + return 0; +} + +//=========================================================================== +// +// A_CheckFlag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STRING (flagname); + PARAM_STATE (jumpto); + PARAM_INT_OPT (checkpointer) { checkpointer = AAPTR_DEFAULT; } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + AActor *owner = COPY_AAPTR(self, checkpointer); + if (owner == NULL) + { + return numret; + } + + if (CheckActorFlag(owner, flagname)) + { + ACTION_JUMP(jumpto); + } + return numret; +} + + +//=========================================================================== +// +// A_RaiseMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(copy) { copy = false; } + + if (self->master != NULL) + { + P_Thing_Raise(self->master, copy ? self : NULL); + } + return 0; +} + +//=========================================================================== +// +// A_RaiseChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(copy) { copy = false; } + + TThinkerIterator it; + AActor *mo; + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self) + { + P_Thing_Raise(mo, copy ? self : NULL); + } + } + return 0; +} + +//=========================================================================== +// +// A_RaiseSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(copy) { copy = false; } + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self) + { + P_Thing_Raise(mo, copy ? self : NULL); + } + } + } + return 0; +} + + //=========================================================================== + // +// [TP] A_FaceConsolePlayer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(max_turn_angle) { max_turn_angle = 0; } + // NOTE: It does nothing for zdoom. + return 0; +} + +//=========================================================================== +// +// A_MonsterRefire +// +// Keep firing unless target got out of sight +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (prob); + PARAM_STATE (jump); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + A_FaceTarget(self); + + if (pr_monsterrefire() < prob) + return numret; + + if (self->target == NULL + || P_HitFriend (self) + || self->target->health <= 0 + || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) + { + ACTION_JUMP(jump); + } + return numret; +} + +//=========================================================================== +// +// A_SetAngle +// +// Set actor's angle (in degrees). +// +//=========================================================================== +enum +{ + SPF_FORCECLAMP = 1, // players always clamp + SPF_INTERPOLATE = 2, +}; + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetAngle) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(angle) { angle = 0; } + PARAM_INT_OPT(flags) { flags = 0; } + + self->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); + return 0; +} + +//=========================================================================== +// +// A_SetPitch +// +// Set actor's pitch (in degrees). +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE(pitch); + PARAM_INT_OPT(flags) { flags = 0; } + + if (self->player != NULL || (flags & SPF_FORCECLAMP)) + { // clamp the pitch we set + int min, max; + + if (self->player != NULL) + { + min = self->player->MinPitch; + max = self->player->MaxPitch; + } + else + { + min = -ANGLE_90 + (1 << ANGLETOFINESHIFT); + max = ANGLE_90 - (1 << ANGLETOFINESHIFT); + } + pitch = clamp(pitch, min, max); + } + + self->SetPitch(pitch, !!(flags & SPF_INTERPOLATE)); + return 0; +} + +//=========================================================================== +// +// A_ScaleVelocity +// +// Scale actor's velocity. +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED(scale); + + INTBOOL was_moving = self->velx | self->vely | self->velz; + + self->velx = FixedMul(self->velx, scale); + self->vely = FixedMul(self->vely, scale); + self->velz = FixedMul(self->velz, scale); + + // If the actor was previously moving but now is not, and is a player, + // update its player variables. (See A_Stop.) + if (was_moving) + { + CheckStopped(self); + } + return 0; +} + +//=========================================================================== +// +// A_ChangeVelocity +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED_OPT (x) { x = 0; } + PARAM_FIXED_OPT (y) { y = 0; } + PARAM_FIXED_OPT (z) { z = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + + INTBOOL was_moving = self->velx | self->vely | self->velz; + + fixed_t vx = x, vy = y, vz = z; + fixed_t sina = finesine[self->angle >> ANGLETOFINESHIFT]; + fixed_t cosa = finecosine[self->angle >> ANGLETOFINESHIFT]; + + if (flags & 1) // relative axes - make x, y relative to actor's current angle + { + vx = DMulScale16(x, cosa, -y, sina); + vy = DMulScale16(x, sina, y, cosa); + } + if (flags & 2) // discard old velocity - replace old velocity with new velocity + { + self->velx = vx; + self->vely = vy; + self->velz = vz; + } + else // add new velocity to old velocity + { + self->velx += vx; + self->vely += vy; + self->velz += vz; + } + + if (was_moving) + { + CheckStopped(self); + } + return 0; +} + +//=========================================================================== +// +// A_SetArg +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(pos); + PARAM_INT(value); + + // Set the value of the specified arg + if ((size_t)pos < countof(self->args)) + { + self->args[pos] = value; + } + return 0; +} + +//=========================================================================== +// +// A_SetSpecial +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (spec); + PARAM_INT_OPT (arg0) { arg0 = 0; } + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (arg3) { arg3 = 0; } + PARAM_INT_OPT (arg4) { arg4 = 0; } + + self->special = spec; + self->args[0] = arg0; + self->args[1] = arg1; + self->args[2] = arg2; + self->args[3] = arg3; + self->args[4] = arg4; + return 0; +} + +//=========================================================================== +// +// A_SetUserVar +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (varname); + PARAM_INT (value); + + PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); + + if (var == NULL || (var->Flags & VARF_Native) || !var->Type->IsKindOf(RUNTIME_CLASS(PBasicType))) + { + Printf("%s is not a user variable in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return 0; + } + // Set the value of the specified user variable. + var->Type->SetValue(reinterpret_cast(self) + var->Offset, value); + return 0; +} + +//=========================================================================== +// +// A_SetUserArray +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (varname); + PARAM_INT (pos); + PARAM_INT (value); + + PField *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); + + if (var == NULL || (var->Flags & VARF_Native) || + !var->Type->IsKindOf(RUNTIME_CLASS(PArray)) || + !static_cast(var->Type)->ElementType->IsKindOf(RUNTIME_CLASS(PBasicType))) + { + Printf("%s is not a user array in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return 0; + } + PArray *arraytype = static_cast(var->Type); + if ((unsigned)pos >= arraytype->ElementCount) + { + Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return 0; + } + // Set the value of the specified user array at index pos. + arraytype->ElementType->SetValue(reinterpret_cast(self) + var->Offset + arraytype->ElementSize * pos, value); + return 0; +} + +//=========================================================================== +// +// A_Teleport(optional state teleportstate, optional class targettype, +// optional class fogtype, optional int flags, optional fixed mindist, +// optional fixed maxdist) +// +// Attempts to teleport to a targettype at least mindist away and at most +// maxdist away (0 means unlimited). If successful, spawn a fogtype at old +// location and place calling actor in teleportstate. +// +//=========================================================================== +enum T_Flags +{ + TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) + TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. + TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. + TF_KEEPANGLE = 0x00000010, // Keep angle. + TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. + TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. + TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. + TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. + TF_NOJUMP = 0x00000200, // Don't jump after teleporting. +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE_OPT (teleport_state) { teleport_state = NULL; } + PARAM_CLASS_OPT (target_type, ASpecialSpot) { target_type = PClass::FindActor("BossSpot"); } + PARAM_CLASS_OPT (fog_type, AActor) { fog_type = PClass::FindActor("TeleportFog"); } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (mindist) { mindist = 128 << FRACBITS; } + PARAM_FIXED_OPT (maxdist) { maxdist = 128 << FRACBITS; } + + ACTION_SET_RESULT(true); + + // Randomly choose not to teleport like A_Srcr2Decide. + if (flags & TF_RANDOMDECIDE) + { + static const int chance[] = + { + 192, 120, 120, 120, 64, 64, 32, 16, 0 + }; + + unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); + + if (chanceindex >= countof(chance)) + { + chanceindex = countof(chance) - 1; + } + + if (pr_teleport() >= chance[chanceindex]) + { + return numret; + } + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) + { + return numret; + } + + if (target_type == NULL) + { + target_type = PClass::FindActor("BossSpot"); + } + + AActor *spot = state->GetSpotWithMinMaxDistance(target_type, self->x, self->y, mindist, maxdist); + if (spot == NULL) + { + return numret; + } + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + //Take precedence and cooperate with telefragging first. + bool tele_result = P_TeleportMove(self, spot->x, spot->y, spot->z, flags & TF_TELEFRAG); + + if (!tele_result && (flags & TF_FORCED)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + //If for some reason the original move didn't work, regardless of telefrag, force it to move. + self->SetOrigin(spot->x, spot->y, spot->z); + tele_result = true; + } + + if (tele_result) + { + // If a fog type is defined in the parameter, or the user wants to use + // the actor's predefined fogs, and if there's no desire to be fogless, + // spawn a fog based upon settings. + if (fog_type || (flags & TF_USEACTORFOG)) + { + if (!(flags & TF_NOSRCFOG)) + { + if (flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); + else + Spawn(fog_type, prevX, prevY, prevZ, ALLOW_REPLACE); + } + if (!(flags & TF_NODESTFOG)) + { + if (flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, self->x, self->y, self->z, false); + else + Spawn(fog_type, self->x, self->y, self->z, ALLOW_REPLACE); + } + } + + self->z = (flags & TF_USESPOTZ) ? spot->z : self->floorz; + + if (!(flags & TF_KEEPANGLE)) + self->angle = spot->angle; + + if (!(flags & TF_KEEPVELOCITY)) + self->velx = self->vely = self->velz = 0; + + if (!(flags & TF_NOJUMP)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (teleport_state == NULL) + { + // Default to Teleport. + teleport_state = self->FindState("Teleport"); + // If still nothing, then return. + if (teleport_state == NULL) + { + return numret; + } + } + ACTION_JUMP(teleport_state); + return numret; + } + } + ACTION_SET_RESULT(tele_result); + return numret; +} + +//=========================================================================== +// +// A_Turn +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) +{ + PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(angle) { angle = 0; } + self->angle += angle; + return 0; +} + +//=========================================================================== +// +// A_Quake +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (intensity); + PARAM_INT (duration); + PARAM_INT (damrad); + PARAM_INT (tremrad); + PARAM_SOUND_OPT (sound) { sound = "world/quake"; } + + P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); + return 0; +} + +//=========================================================================== +// +// A_Weave +// +//=========================================================================== + +void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist) +{ + fixed_t newX, newY; + int weaveXY, weaveZ; + int angle; + fixed_t dist; + + weaveXY = self->WeaveIndexXY & 63; + weaveZ = self->WeaveIndexZ & 63; + angle = (self->angle + ANG90) >> ANGLETOFINESHIFT; + + if (xydist != 0 && xyspeed != 0) + { + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX = self->x - FixedMul (finecosine[angle], dist); + newY = self->y - FixedMul (finesine[angle], dist); + weaveXY = (weaveXY + xyspeed) & 63; + dist = MulScale13(finesine[weaveXY << BOBTOFINESHIFT], xydist); + newX += FixedMul (finecosine[angle], dist); + newY += FixedMul (finesine[angle], dist); + if (!(self->flags5 & MF5_NOINTERACTION)) + { + P_TryMove (self, newX, newY, true); + } + else + { + self->UnlinkFromWorld (); + self->flags |= MF_NOBLOCKMAP; + self->x = newX; + self->y = newY; + self->LinkToWorld (); + } + self->WeaveIndexXY = weaveXY; + } + if (zdist != 0 && zspeed != 0) + { + self->z -= MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + weaveZ = (weaveZ + zspeed) & 63; + self->z += MulScale13(finesine[weaveZ << BOBTOFINESHIFT], zdist); + self->WeaveIndexZ = weaveZ; + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Weave) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (xspeed); + PARAM_INT (yspeed); + PARAM_FIXED (xdist); + PARAM_FIXED (ydist); + A_Weave(self, xspeed, yspeed, xdist, ydist); + return 0; +} + + + + +//=========================================================================== +// +// A_LineEffect +// +// This allows linedef effects to be activated inside deh frames. +// +//=========================================================================== + +void P_TranslateLineDef (line_t *ld, maplinedef_t *mld); +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LineEffect) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(special) { special = 0; } + PARAM_INT_OPT(tag) { tag = 0; } + + line_t junk; + maplinedef_t oldjunk; + bool res = false; + if (!(self->flags6 & MF6_LINEDONE)) // Unless already used up + { + if ((oldjunk.special = special)) // Linedef type + { + oldjunk.tag = tag; // Sector tag for linedef + P_TranslateLineDef(&junk, &oldjunk); // Turn into native type + res = !!P_ExecuteSpecial(junk.special, NULL, self, false, junk.args[0], + junk.args[1], junk.args[2], junk.args[3], junk.args[4]); + if (res && !(junk.flags & ML_REPEAT_SPECIAL)) // If only once, + self->flags6 |= MF6_LINEDONE; // no more for this thing + } + } + ACTION_SET_RESULT(res); + return numret; +} + +//========================================================================== +// +// A Wolf3D-style attack codepointer +// +//========================================================================== +enum WolfAttackFlags +{ + WAF_NORANDOM = 1, + WAF_USEPUFF = 2, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_SOUND_OPT (sound) { sound = "weapons/pistol"; } + PARAM_FIXED_OPT (snipe) { snipe = FRACUNIT; } + PARAM_INT_OPT (maxdamage) { maxdamage = 64; } + PARAM_INT_OPT (blocksize) { blocksize = 128; } + PARAM_INT_OPT (pointblank) { pointblank = 2; } + PARAM_INT_OPT (longrange) { longrange = 4; } + PARAM_FIXED_OPT (runspeed) { runspeed = 160*FRACUNIT; } + PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor("BulletPuff"); } + + if (!self->target) + return 0; + + // Enemy can't see target + if (!P_CheckSight(self, self->target)) + return 0; + + A_FaceTarget (self); + + // Target can dodge if it can see enemy + angle_t angle = R_PointToAngle2(self->target->x, self->target->y, self->x, self->y) - self->target->angle; + angle >>= 24; + bool dodge = (P_CheckSight(self->target, self) && (angle>226 || angle<30)); + + // Distance check is simplistic + fixed_t dx = abs (self->x - self->target->x); + fixed_t dy = abs (self->y - self->target->y); + fixed_t dz; + fixed_t dist = dx > dy ? dx : dy; + + // Some enemies are more precise + dist = FixedMul(dist, snipe); + + // Convert distance into integer number of blocks + dist >>= FRACBITS; + dist /= blocksize; + + // Now for the speed accuracy thingie + fixed_t speed = FixedMul(self->target->velx, self->target->velx) + + FixedMul(self->target->vely, self->target->vely) + + FixedMul(self->target->velz, self->target->velz); + int hitchance = speed < runspeed ? 256 : 160; + + // Distance accuracy (factoring dodge) + hitchance -= dist * (dodge ? 16 : 8); + + // While we're here, we may as well do something for this: + if (self->target->flags & MF_SHADOW) + { + hitchance >>= 2; + } + + // The attack itself + if (pr_cabullet() < hitchance) + { + // Compute position for spawning blood/puff + dx = self->target->x; + dy = self->target->y; + dz = self->target->z + (self->target->height>>1); + angle = R_PointToAngle2(dx, dy, self->x, self->y); + + dx += FixedMul(self->target->radius, finecosine[angle>>ANGLETOFINESHIFT]); + dy += FixedMul(self->target->radius, finesine[angle>>ANGLETOFINESHIFT]); + + int damage = flags & WAF_NORANDOM ? maxdamage : (1 + (pr_cabullet() % maxdamage)); + if (dist >= pointblank) + damage >>= 1; + if (dist >= longrange) + damage >>= 1; + FName mod = NAME_None; + bool spawnblood = !((self->target->flags & MF_NOBLOOD) + || (self->target->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))); + if (flags & WAF_USEPUFF && pufftype) + { + AActor *dpuff = GetDefaultByType(pufftype->GetReplacement()); + mod = dpuff->DamageType; + + if (dpuff->flags2 & MF2_THRUGHOST && self->target->flags3 & MF3_GHOST) + damage = 0; + + if ((0 && dpuff->flags3 & MF3_PUFFONACTORS) || !spawnblood) + { + spawnblood = false; + P_SpawnPuff(self, pufftype, dx, dy, dz, angle, 0); + } + } + else if (self->target->flags3 & MF3_GHOST) + damage >>= 2; + if (damage) + { + int newdam = P_DamageMobj(self->target, self, self, damage, mod, DMG_THRUSTLESS); + if (spawnblood) + { + P_SpawnBlood(dx, dy, dz, angle, newdam > 0 ? newdam : damage, self->target); + P_TraceBleed(newdam > 0 ? newdam : damage, self->target, R_PointToAngle2(self->x, self->y, dx, dy), 0); + } + } + } + + // And finally, let's play the sound + S_Sound (self, CHAN_WEAPON, sound, 1, ATTN_NORM); + return 0; +} + + +//========================================================================== +// +// A_Warp +// +//========================================================================== + +enum WARPF +{ + WARPF_ABSOLUTEOFFSET = 0x1, + WARPF_ABSOLUTEANGLE = 0x2, + WARPF_USECALLERANGLE = 0x4, + + WARPF_NOCHECKPOSITION = 0x8, + + WARPF_INTERPOLATE = 0x10, + WARPF_WARPINTERPOLATION = 0x20, + WARPF_COPYINTERPOLATION = 0x40, + + WARPF_STOP = 0x80, + WARPF_TOFLOOR = 0x100, + WARPF_TESTONLY = 0x200, + WARPF_ABSOLUTEPOSITION = 0x400, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (destination_selector); + PARAM_FIXED_OPT (xofs) { xofs = 0; } + PARAM_FIXED_OPT (yofs) { yofs = 0; } + PARAM_FIXED_OPT (zofs) { zofs = 0; } + PARAM_ANGLE_OPT (angle) { angle = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_STATE_OPT (success_state) { success_state = NULL; } + + AActor *reference = COPY_AAPTR(self, destination_selector); + + if (!reference) + { + ACTION_SET_RESULT(false); + return numret; + } + + fixed_t oldx = self->x; + fixed_t oldy = self->y; + fixed_t oldz = self->z; + + if (!(flags & WARPF_ABSOLUTEANGLE)) + { + angle += (flags & WARPF_USECALLERANGLE) ? self->angle : reference->angle; + } + + if (!(flags & WARPF_ABSOLUTEPOSITION)) + { + if (!(flags & WARPF_ABSOLUTEOFFSET)) + { + angle_t fineangle = angle >> ANGLETOFINESHIFT; + fixed_t xofs1 = xofs; + + // (borrowed from A_SpawnItemEx, assumed workable) + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + + xofs = FixedMul(xofs1, finecosine[fineangle]) + FixedMul(yofs, finesine[fineangle]); + yofs = FixedMul(xofs1, finesine[fineangle]) - FixedMul(yofs, finecosine[fineangle]); + } + + if (flags & WARPF_TOFLOOR) + { + // set correct xy + + self->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z); + + // now the caller's floorz should be appropriate for the assigned xy-position + // assigning position again with + + if (zofs) + { + // extra unlink, link and environment calculation + self->SetOrigin( + self->x, + self->y, + self->floorz + zofs); + } + else + { + // if there is no offset, there should be no ill effect from moving down to the + // already identified floor + + // A_Teleport does the same thing anyway + self->z = self->floorz; + } + } + else + { + self->SetOrigin( + reference->x + xofs, + reference->y + yofs, + reference->z + zofs); + } + } + else //[MC] The idea behind "absolute" is meant to be "absolute". Override everything, just like A_SpawnItemEx's. + { + if (flags & WARPF_TOFLOOR) + { + self->SetOrigin(xofs, yofs, self->floorz + zofs); + } + else + { + self->SetOrigin(xofs, yofs, zofs); + } + } + if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(self)) + { + if (flags & WARPF_TESTONLY) + { + self->SetOrigin(oldx, oldy, oldz); + } + else + { + self->angle = angle; + + if (flags & WARPF_STOP) + { + self->velx = 0; + self->vely = 0; + self->velz = 0; + } + + if (flags & WARPF_WARPINTERPOLATION) + { + self->PrevX += self->x - oldx; + self->PrevY += self->y - oldy; + self->PrevZ += self->z - oldz; + } + else if (flags & WARPF_COPYINTERPOLATION) + { + self->PrevX = self->x + reference->PrevX - reference->x; + self->PrevY = self->y + reference->PrevY - reference->y; + self->PrevZ = self->z + reference->PrevZ - reference->z; + } + else if (!(flags & WARPF_INTERPOLATE)) + { + self->PrevX = self->x; + self->PrevY = self->y; + self->PrevZ = self->z; + } + } + + if (success_state) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + // in this case, you have the statejump to help you handle all the success anyway. + ACTION_JUMP(success_state); + return numret; + } + + ACTION_SET_RESULT(true); + } + else + { + self->SetOrigin(oldx, oldy, oldz); + ACTION_SET_RESULT(false); + } + return numret; +} + +//========================================================================== +// +// ACS_Named* stuff + +// +// These are exactly like their un-named line special equivalents, except +// they take strings instead of integers to indicate which script to run. +// Some of these probably aren't very useful, but they are included for +// the sake of completeness. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteWithResult) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (arg3) { arg3 = 0; } + PARAM_INT_OPT (arg4) { arg4 = 0; } + + int res = P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (arg3) { arg3 = 0; } + + int res = P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (arg3) { arg3 = 0; } + + int res = P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (lock) { lock = 0; } + + int res = P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + PARAM_INT_OPT (arg1) { arg1 = 0; } + PARAM_INT_OPT (arg2) { arg2 = 0; } + PARAM_INT_OPT (lock) { lock = 0; } + + int res = P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + + int res = P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + ACTION_SET_RESULT(res); + return numret; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME (scriptname); + PARAM_INT_OPT (mapnum) { mapnum = 0; } + + int res = P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + ACTION_SET_RESULT(res); + return numret; +} + + +//========================================================================== +// +// A_RadiusGive +// +// Uses code roughly similar to A_Explode (but without all the compatibility +// baggage and damage computation code to give an item to all eligible mobjs +// in range. +// +//========================================================================== +enum RadiusGiveFlags +{ + RGF_GIVESELF = 1 << 0, + RGF_PLAYERS = 1 << 1, + RGF_MONSTERS = 1 << 2, + RGF_OBJECTS = 1 << 3, + RGF_VOODOO = 1 << 4, + RGF_CORPSES = 1 << 5, + RGF_MASK = 2111, + RGF_NOTARGET = 1 << 6, + RGF_NOTRACER = 1 << 7, + RGF_NOMASTER = 1 << 8, + RGF_CUBE = 1 << 9, + RGF_NOSIGHT = 1 << 10, + RGF_MISSILES = 1 << 11, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusGive) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (item, AInventory); + PARAM_FIXED (distance); + PARAM_INT (flags); + PARAM_INT_OPT (amount) { amount = 0; } + + // We need a valid item, valid targets, and a valid range + if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) + { + return 0; + } + if (amount == 0) + { + amount = 1; + } + FBlockThingsIterator it(FBoundingBox(self->x, self->y, distance)); + double distsquared = double(distance) * double(distance); + + AActor *thing; + while ((thing = it.Next())) + { + // Don't give to inventory items + if (thing->flags & MF_SPECIAL) + { + continue; + } + // Avoid giving to self unless requested + if (thing == self && !(flags & RGF_GIVESELF)) + { + continue; + } + // Avoiding special pointers if requested + if (((thing == self->target) && (flags & RGF_NOTARGET)) || + ((thing == self->tracer) && (flags & RGF_NOTRACER)) || + ((thing == self->master) && (flags & RGF_NOMASTER))) + { + continue; + } + // Don't give to dead thing unless requested + if (thing->flags & MF_CORPSE) + { + if (!(flags & RGF_CORPSES)) + { + continue; + } + } + else if (thing->health <= 0 || thing->flags6 & MF6_KILLED) + { + continue; + } + // Players, monsters, and other shootable objects + if (thing->player) + { + if ((thing->player->mo == thing) && !(flags & RGF_PLAYERS)) + { + continue; + } + if ((thing->player->mo != thing) && !(flags & RGF_VOODOO)) + { + continue; + } + } + else if (thing->flags3 & MF3_ISMONSTER) + { + if (!(flags & RGF_MONSTERS)) + { + continue; + } + } + else if (thing->flags & MF_SHOOTABLE || thing->flags6 & MF6_VULNERABLE) + { + if (!(flags & RGF_OBJECTS)) + { + continue; + } + } + else if (thing->flags & MF_MISSILE) + { + if (!(flags & RGF_MISSILES)) + { + continue; + } + } + else + { + continue; + } + + if (flags & RGF_CUBE) + { // check if inside a cube + if (abs(thing->x - self->x) > distance || + abs(thing->y - self->y) > distance || + abs((thing->z + thing->height/2) - (self->z + self->height/2)) > distance) + { + continue; + } + } + else + { // check if inside a sphere + TVector3 tpos(thing->x, thing->y, thing->z + thing->height/2); + TVector3 spos(self->x, self->y, self->z + self->height/2); + if ((tpos - spos).LengthSquared() > distsquared) + { + continue; + } + } + fixed_t dz = abs ((thing->z + thing->height/2) - (self->z + self->height/2)); + + if ((flags & RGF_NOSIGHT) || P_CheckSight (thing, self, SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) + { // OK to give; target is in direct path, or the + // monster doesn't care about it being in line of sight. + AInventory *gift = static_cast(Spawn (item, 0, 0, 0, NO_REPLACE)); + if (gift->IsKindOf(RUNTIME_CLASS(AHealth))) + { + gift->Amount *= amount; + } + else + { + gift->Amount = amount; + } + gift->flags |= MF_DROPPED; + gift->ClearCounters(); + if (!gift->CallTryPickup (thing)) + { + gift->Destroy (); + } + } + } + return 0; +} + +//========================================================================== +// +// A_SetTics +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(tics_to_set); + + if (stateowner != self && self->player != NULL && stateowner->IsKindOf(RUNTIME_CLASS(AWeapon))) + { // Is this a weapon? Need to check psp states for a match, then. Blah. + for (int i = 0; i < NUMPSPRITES; ++i) + { + if (self->player->psprites[i].state == callingstate) + { + self->player->psprites[i].tics = tics_to_set; + return 0; + } + } + } + // Just set tics for self. + self->tics = tics_to_set; + return 0; +} + +//========================================================================== +// +// A_SetDamageType +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME(damagetype); + + self->DamageType = damagetype; + return 0; +} + +//========================================================================== +// +// A_DropItem +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) +{ + PARAM_ACTION_PROLOGUE; + PARAM_CLASS (spawntype, AActor); + PARAM_INT_OPT (amount) { amount = -1; } + PARAM_INT_OPT (chance) { chance = 256; } + + P_DropItem(self, spawntype, amount, chance); + return 0; +} + +//========================================================================== +// +// A_SetSpeed +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT(speed); + + self->Speed = speed; + return 0; +} + +//=========================================================================== +// +// Common A_Damage handler +// +// A_Damage* (int amount, str damagetype, int flags) +// Damages the specified actor by the specified amount. Negative values heal. +// +//=========================================================================== + +enum DMSS +{ + DMSS_FOILINVUL = 1, + DMSS_AFFECTARMOR = 2, + DMSS_KILL = 4, + DMSS_NOFACTOR = 8, + DMSS_FOILBUDDHA = 16, + DMSS_NOPROTECT = 32, +}; + +static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags) +{ + int dmgFlags = 0; + if (flags & DMSS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (flags & DMSS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + if ((flags & DMSS_KILL) || (flags & DMSS_NOFACTOR)) //Kill implies NoFactor + dmgFlags += DMG_NO_FACTOR; + if (!(flags & DMSS_AFFECTARMOR) || (flags & DMSS_KILL)) //Kill overrides AffectArmor + dmgFlags += DMG_NO_ARMOR; + if (flags & DMSS_KILL) //Kill adds the value of the damage done to it. Allows for more controlled extreme death types. + amount += dmgtarget->health; + if (flags & DMSS_NOPROTECT) //Ignore PowerProtection. + dmgFlags += DMG_NO_PROTECT; + + if (amount > 0) + { + //Should wind up passing them through just fine. + P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); + } + else if (amount < 0) + { + amount = -amount; + P_GiveBody(dmgtarget, amount); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + DoDamage(self, self, amount, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->target != NULL) + DoDamage(self->target, self, amount, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->tracer != NULL) + DoDamage(self->tracer, self, amount, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->master != NULL) + DoDamage(self->master, self, amount, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + DoDamage(mo, self, amount, damagetype, flags); + } + return 0; +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (amount); + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) + DoDamage(mo, self, amount, damagetype, flags); + } + } + return 0; +} + + +//=========================================================================== +// +// A_Kill*(damagetype, int flags) +// +//=========================================================================== +enum KILS +{ + KILS_FOILINVUL = 1 << 0, + KILS_KILLMISSILES = 1 << 1, + KILS_NOMONSTERS = 1 << 2, + KILS_FOILBUDDHA = 1 << 3, +}; + +static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags) +{ + int dmgFlags = DMG_NO_ARMOR + DMG_NO_FACTOR; + + if (KILS_FOILINVUL) + dmgFlags += DMG_FOILINVUL; + if (KILS_FOILBUDDHA) + dmgFlags += DMG_FOILBUDDHA; + + if ((killtarget->flags & MF_MISSILE) && (flags & KILS_KILLMISSILES)) + { + // [MC] Now that missiles can set masters, lets put in a check to properly + // destroy projectiles. BUT FIRST! New feature~! Check to see if it's + // invulnerable. Disregarded if foilinvul is on, but never works on a + // missile with NODAMAGE since that's the whole point of it. + if ((!(killtarget->flags2 & MF2_INVULNERABLE) || (flags & KILS_FOILINVUL)) && + (!(killtarget->flags2 & MF7_BUDDHA) || (flags & KILS_FOILBUDDHA)) && !(killtarget->flags5 & MF5_NODAMAGE)) + { + P_ExplodeMissile(killtarget, NULL, NULL); + } + } + if (!(flags & KILS_NOMONSTERS)) + { + P_DamageMobj(killtarget, self, self, killtarget->health, damagetype, dmgFlags); + } +} + + +//=========================================================================== +// +// A_KillTarget(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTarget) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->target != NULL) + DoKill(self->target, self, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// A_KillTracer(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->tracer != NULL) + DoKill(self->tracer, self, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// A_KillMaster(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + if (self->master != NULL) + DoKill(self->master, self, damagetype, flags); + return 0; +} + +//=========================================================================== +// +// A_KillChildren(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + DoKill(mo, self, damagetype, flags); + } + return 0; +} + +//=========================================================================== +// +// A_KillSiblings(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME_OPT (damagetype) { damagetype = NAME_None; } + PARAM_INT_OPT (flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ( (mo = it.Next()) ) + { + if (mo->master == self->master && mo != self) + DoKill(mo, self, damagetype, flags); + } + } + return 0; +} + +//=========================================================================== +// +// DoRemove +// +//=========================================================================== + +enum RMVF_flags +{ + RMVF_MISSILES = 1 << 0, + RMVF_NOMONSTERS = 1 << 1, + RMVF_MISC = 1 << 2, + RMVF_EVERYTHING = 1 << 3, +}; + +static void DoRemove(AActor *removetarget, int flags) +{ + if ((flags & RMVF_EVERYTHING)) + { + P_RemoveThing(removetarget); + } + if ((flags & RMVF_MISC) && !((removetarget->flags3 & MF3_ISMONSTER) && (removetarget->flags & MF_MISSILE))) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags3 & MF3_ISMONSTER) && !(flags & RMVF_NOMONSTERS)) + { + P_RemoveThing(removetarget); + } + if ((removetarget->flags & MF_MISSILE) && (flags & RMVF_MISSILES)) + { + P_RemoveThing(removetarget); + } +} + +//=========================================================================== +// +// A_RemoveTarget +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTarget) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(flags) { flags = 0; } + + if (self->target != NULL) + { + DoRemove(self->target, flags); + } + return 0; +} + +//=========================================================================== +// +// A_RemoveTracer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(flags) { flags = 0; } + + if (self->tracer != NULL) + { + DoRemove(self->tracer, flags); + } + return 0; +} + +//=========================================================================== +// +// A_RemoveMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT_OPT(flags) { flags = 0; } + + if (self->master != NULL) + { + DoRemove(self->master, flags); + } + return 0; +} + +//=========================================================================== +// +// A_RemoveChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(removeall) { removeall = false; } + PARAM_INT_OPT(flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags); + } + } + return 0; +} + +//=========================================================================== +// +// A_RemoveSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) +{ + PARAM_ACTION_PROLOGUE; + PARAM_BOOL_OPT(removeall) { removeall = false; } + PARAM_INT_OPT(flags) { flags = 0; } + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags); + } + } + } + return 0; +} + +//=========================================================================== +// +// A_Remove +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) +{ + PARAM_ACTION_PROLOGUE; + PARAM_INT (removee); + PARAM_INT_OPT (flags) { flags = 0; } + + AActor *reference = COPY_AAPTR(self, removee); + + if (reference != NULL) + { + DoRemove(reference, flags); + } + return 0; +} + +//=========================================================================== +// +// A_SetTeleFog +// +// Sets the teleport fog(s) for the calling actor. +// Takes a name of the classes for te source and destination. +// Can set both at the same time. Use "" to retain the previous fog without +// changing it. +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTeleFog) +{ + PARAM_ACTION_PROLOGUE; + PARAM_NAME(oldpos); + PARAM_NAME(newpos); + + PClassActor *check = PClass::FindActor(oldpos); + if (check == NULL || !stricmp(oldpos, "none") || !stricmp(oldpos, "null")) + self->TeleFogSourceType = NULL; + else if (!stricmp(oldpos, "")) + { //Don't change it if it's just "" + } + else + { + self->TeleFogSourceType = check; + } + + check = PClass::FindActor(newpos); + if (check == NULL || !stricmp(newpos, "none") || !stricmp(newpos, "null")) + self->TeleFogDestType = NULL; + else if (!stricmp(newpos, "")) + { //Don't change it if it's just "" + } + else + { + self->TeleFogDestType = check; + } + + return 0; +} + +//=========================================================================== +// +// A_SwapTeleFog +// +// Switches the source and dest telefogs around. +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) +{ + PARAM_ACTION_PROLOGUE; + if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. + { + PClassActor *temp = self->TeleFogSourceType; + self->TeleFogSourceType = self->TeleFogDestType; + self->TeleFogDestType = temp; + } + return 0; +} diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 686318680..2d9b54cab 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -241,6 +241,18 @@ static FFlagDef ActorFlags[]= DEFINE_FLAG(MF7, NOTELESTOMP, AActor, flags7), DEFINE_FLAG(MF7, ALWAYSTELEFRAG, AActor, flags7), DEFINE_FLAG(MF7, WEAPONSPAWN, AActor, flags7), + DEFINE_FLAG(MF7, HARMFRIENDS, AActor, flags7), + DEFINE_FLAG(MF7, BUDDHA, AActor, flags7), + DEFINE_FLAG(MF7, FOILBUDDHA, AActor, flags7), + DEFINE_FLAG(MF7, DONTTHRUST, AActor, flags7), + DEFINE_FLAG(MF7, ALLOWPAIN, AActor, flags7), + DEFINE_FLAG(MF7, CAUSEPAIN, AActor, flags7), + DEFINE_FLAG(MF7, THRUREFLECT, AActor, flags7), + DEFINE_FLAG(MF7, MIRRORREFLECT, AActor, flags7), + DEFINE_FLAG(MF7, AIMREFLECT, AActor, flags7), + DEFINE_FLAG(MF7, HITTARGET, AActor, flags7), + DEFINE_FLAG(MF7, HITMASTER, AActor, flags7), + DEFINE_FLAG(MF7, HITTRACER, AActor, flags7), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), @@ -311,6 +323,7 @@ static FFlagDef InventoryFlags[] = DEFINE_FLAG(IF, NEVERRESPAWN, AInventory, ItemFlags), DEFINE_FLAG(IF, NOSCREENFLASH, AInventory, ItemFlags), DEFINE_FLAG(IF, TOSSED, AInventory, ItemFlags), + DEFINE_FLAG(IF, ALWAYSRESPAWN, AInventory, ItemFlags), DEFINE_DEPRECATED_FLAG(PICKUPFLASH), DEFINE_DEPRECATED_FLAG(INTERHUBSTRIP),}; diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp index eba794ef6..112d2959c 100644 --- a/src/thingdef/thingdef_exp.cpp +++ b/src/thingdef/thingdef_exp.cpp @@ -369,6 +369,37 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls) return new FxRandom(rng, min, max, sc); } + else if (sc.CheckToken(TK_Pick)) + { + FRandom *rng; + TArray list; + list.Clear(); + int index = 0; + + if (sc.CheckToken('[')) + { + sc.MustGetToken(TK_Identifier); + rng = FRandom::StaticFindRNG(sc.String); + sc.MustGetToken(']'); + } + else + { + rng = &pr_exrandom; + } + sc.MustGetToken('('); + + while (!(sc.CheckToken(')'))) + { + FxExpression *min = ParseExpressionM(sc, cls); + list.Push(min); + if (sc.CheckToken(')')) + break; + else + sc.MustGetToken(','); + } + + return new FxPick(rng, list, sc); + } else if (sc.CheckToken(TK_FRandom)) { FRandom *rng; diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h index 9be15cb37..1c840c19b 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -176,6 +176,7 @@ struct ExpEmit ExpEmit(int reg, int type, bool konst) : RegNum(reg), RegType(type), Konst(konst), Fixed(false) {} ExpEmit(VMFunctionBuilder *build, int type); void Free(VMFunctionBuilder *build); + void Reuse(VMFunctionBuilder *build); BYTE RegNum, RegType, Konst:1, Fixed:1; }; @@ -617,7 +618,7 @@ public: class FxRandom : public FxExpression { protected: - FRandom * rng; + FRandom *rng; FxExpression *min, *max; public: @@ -635,6 +636,27 @@ public: // //========================================================================== +class FxPick : public FxExpression +{ +protected: + FRandom *rng; + TDeletingArray choices; + +public: + + FxPick(FRandom *, TArray &expr, const FScriptPosition &pos); + ~FxPick(); + FxExpression *Resolve(FCompileContext&); + + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// +// +//========================================================================== + class FxFRandom : public FxRandom { public: diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index f7e598815..2395b0d0e 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -87,6 +87,7 @@ DEFINE_MEMBER_VARIABLE(height, AActor) DEFINE_MEMBER_VARIABLE(radius, AActor) DEFINE_MEMBER_VARIABLE(reactiontime, AActor) DEFINE_MEMBER_VARIABLE(meleerange, AActor) +DEFINE_MEMBER_VARIABLE(Speed, AActor) ExpEmit::ExpEmit(VMFunctionBuilder *build, int type) : RegNum(build->Registers[type].Get(1)), RegType(type), Konst(false), Fixed(false) @@ -101,6 +102,15 @@ void ExpEmit::Free(VMFunctionBuilder *build) } } +void ExpEmit::Reuse(VMFunctionBuilder *build) +{ + if (!Fixed && !Konst) + { + bool success = build->Registers[RegType].Reuse(RegNum); + assert(success && "Attempt to reuse a register that is already in use"); + } +} + //========================================================================== // // FindDecorateBuiltinFunction @@ -2088,6 +2098,140 @@ ExpEmit FxRandom::Emit(VMFunctionBuilder *build) return out; } +//========================================================================== +// +// +// +//========================================================================== +FxPick::FxPick(FRandom *r, TArray &expr, const FScriptPosition &pos) +: FxExpression(pos) +{ + assert(expr.Size() > 0); + choices.Resize(expr.Size()); + for (unsigned int index = 0; index < expr.Size(); index++) + { + choices[index] = new FxIntCast(expr[index]); + } + rng = r; + ValueType = VAL_Int; +} + +//========================================================================== +// +// +// +//========================================================================== + +FxPick::~FxPick() +{ +} + +//========================================================================== +// +// +// +//========================================================================== + +FxExpression *FxPick::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + for (unsigned int index = 0; index < choices.Size(); index++) + { + RESOLVE(choices[index], ctx); + ABORT(choices[index]); + assert(choices[index]->ValueType == ValueType.Type); + } + return this; +}; + + +//========================================================================== +// +// FxPick :: Emit +// +// The expression: +// a = pick[rng](i_0, i_1, i_2, ..., i_n) +// [where i_x is a complete expression and not just a value] +// is syntactic sugar for: +// +// switch(random[rng](0, n)) { +// case 0: a = i_0; +// case 1: a = i_1; +// case 2: a = i_2; +// ... +// case n: a = i_n; +// } +// +//========================================================================== + +ExpEmit FxPick::Emit(VMFunctionBuilder *build) +{ + unsigned i; + + assert(choices.Size() > 0); + + // Call DecoRandom to generate a random number. + VMFunction *callfunc; + PSymbol *sym = FindDecorateBuiltinFunction(NAME_DecoRandom, DecoRandom); + + assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction))); + assert(((PSymbolVMFunction *)sym)->Function != NULL); + callfunc = ((PSymbolVMFunction *)sym)->Function; + + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, build->GetConstantAddress(rng, ATAG_RNG)); + build->EmitParamInt(0); + build->EmitParamInt(choices.Size() - 1); + build->Emit(OP_CALL_K, build->GetConstantAddress(callfunc, ATAG_OBJECT), 3, 1); + + ExpEmit resultreg(build, REGT_INT); + build->Emit(OP_RESULT, 0, REGT_INT, resultreg.RegNum); + build->Emit(OP_IJMP, resultreg.RegNum, 0); + + // Free the result register now. The simple code generation algorithm should + // automatically pick it as the destination register for each case. + resultreg.Free(build); + + // Allocate space for the jump table. + size_t jumptable = build->Emit(OP_JMP, 0); + for (i = 1; i < choices.Size(); ++i) + { + build->Emit(OP_JMP, 0); + } + + // Emit each case + TArray finishes(choices.Size() - 1); + for (unsigned i = 0; i < choices.Size(); ++i) + { + build->BackpatchToHere(jumptable + i); + ExpEmit casereg = choices[i]->Emit(build); + if (casereg.RegNum != resultreg.RegNum) + { // The result of the case is in a different register from what + // was expected. Copy it to the one we wanted. + + resultreg.Reuse(build); // This is really just for the assert in Reuse() + build->Emit(OP_MOVE, resultreg.RegNum, casereg.RegNum, 0); + resultreg.Free(build); + } + // Free this register so the remaining cases can use it. + casereg.Free(build); + // All but the final case needs a jump to the end of the expression's code. + if (i + 1 < choices.Size()) + { + size_t loc = build->Emit(OP_JMP, 0); + finishes.Push(loc); + } + } + // Backpatch each case (except the last, since it ends here) to jump to here. + for (i = 0; i < choices.Size() - 1; ++i) + { + build->BackpatchToHere(finishes[i]); + } + // The result register needs to be in-use when we return. + // It should have been freed earlier, so restore it's in-use flag. + resultreg.Reuse(build); + return resultreg; +} + //========================================================================== // // diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index 2632efcc1..564dc35e2 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -402,6 +402,16 @@ static void ParseUserVariable (FScanner &sc, PSymbolTable *symt, PClassActor *cl } FName symname = sc.String; + + // We must ensure that we do not define duplicates, even when they come from a parent table. + if (symt->FindSymbol(symname, true) != NULL) + { + sc.ScriptMessage ("'%s' is already defined in '%s' or one of its ancestors.", + symname.GetChars(), cls ? cls->TypeName.GetChars() : "Global"); + FScriptPosition::ErrorCounter++; + return; + } + if (sc.CheckToken('[')) { FxExpression *expr = ParseExpression(sc, cls); diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 8406918bd..ab551d804 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -1453,6 +1453,38 @@ DEFINE_PROPERTY(stamina, I, Actor) defaults->stamina = i; } +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(telefogsourcetype, S, Actor) +{ + PROP_STRING_PARM(str, 0); + if (!stricmp(str, "") || (!stricmp(str, "none")) || (!stricmp(str, "null")) || *str == 0) + { + defaults->TeleFogSourceType = NULL; + } + else + { + defaults->TeleFogSourceType = FindClassTentative(str, PClass::FindClass("TeleportFog")); + } +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(telefogdesttype, S, Actor) +{ + PROP_STRING_PARM(str, 0); + if (!stricmp(str, "") || (!stricmp(str, "none")) || (!stricmp(str, "null")) || *str == 0) + { + defaults->TeleFogDestType = NULL; + } + else + { + defaults->TeleFogDestType = FindClassTentative(str, PClass::FindClass("TeleportFog")); + } +} + //========================================================================== // // Special inventory properties @@ -2064,7 +2096,7 @@ DEFINE_CLASS_PROPERTY_PREFIX(powerup, color, C_f, Inventory) "INVERSEMAP", "GOLDMAP", "REDMAP", "GREENMAP", "BLUEMAP", NULL }; int alpha; - PalEntry * pBlendColor; + PalEntry *pBlendColor; if (info->IsDescendantOf(RUNTIME_CLASS(APowerup))) { @@ -2094,6 +2126,11 @@ DEFINE_CLASS_PROPERTY_PREFIX(powerup, color, C_f, Inventory) *pBlendColor = MakeSpecialColormap(v); return; } + else if (!stricmp(name, "none") && info->IsDescendantOf(RUNTIME_CLASS(APowerupGiver))) + { + *pBlendColor = MakeSpecialColormap(65535); + return; + } color = V_GetColor(NULL, name); } diff --git a/src/version.h b/src/version.h index 6d24c3fe8..6863f0573 100644 --- a/src/version.h +++ b/src/version.h @@ -51,7 +51,7 @@ const char *GetVersionString(); // Version identifier for network games. // Bump it every time you do a release unless you're certain you // didn't change anything that will affect sync. -#define NETGAMEVERSION 230 +#define NETGAMEVERSION 231 // Version stored in the ini's [LastRun] section. // Bump it if you made some configuration change that you want to @@ -76,7 +76,7 @@ const char *GetVersionString(); // Use 4500 as the base git save version, since it's higher than the // SVN revision ever got. -#define SAVEVER 4510 +#define SAVEVER 4517 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index e60fee303..02492f272 100644 --- a/src/wi_stuff.cpp +++ b/src/wi_stuff.cpp @@ -1330,7 +1330,7 @@ void WI_updateDeathmatchStats () for (i = 0; i < MAXPLAYERS; i++) { // If the player is in the game and not ready, stop checking - if (playeringame[i] && !players[i].isbot && !playerready[i]) + if (playeringame[i] && players[i].Bot == NULL && !playerready[i]) break; } @@ -1429,7 +1429,7 @@ void WI_drawDeathmatchStats () clamp(int(g*255.f), 0, 255), clamp(int(b*255.f), 0, 255)), 0.8f, x, y - ypadding, (deaths_x - x) + (8 * CleanXfac), lineheight); - if (playerready[pnum] || player->isbot) // Bots are automatically assumed ready, to prevent confusion + if (playerready[pnum] || player->Bot != NULL) // Bots are automatically assumed ready, to prevent confusion screen->DrawTexture(readyico, x - (readyico->GetWidth() * CleanXfac), y, DTA_CleanNoMove, true, TAG_DONE); color = (EColorRange)HU_GetRowColor(player, pnum == consoleplayer); @@ -1460,7 +1460,7 @@ void WI_drawDeathmatchStats () // Draw game time y += height + CleanYfac; - int seconds = plrs[me].stime / TICRATE; + int seconds = Tics2Seconds(plrs[me].stime); int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; seconds = seconds % 60; @@ -1638,7 +1638,7 @@ void WI_updateNetgameStats () for (i = 0; i < MAXPLAYERS; i++) { // If the player is in the game and not ready, stop checking - if (playeringame[i] && !players[i].isbot && !playerready[i]) + if (playeringame[i] && players[i].Bot == NULL && !playerready[i]) break; } @@ -1735,7 +1735,7 @@ void WI_drawNetgameStats () clamp(int(g*255.f), 0, 255), clamp(int(b*255.f), 0, 255)), 0.8f, x, y - ypadding, (secret_x - x) + (8 * CleanXfac), lineheight); - if (playerready[i] || player->isbot) // Bots are automatically assumed ready, to prevent confusion + if (playerready[i] || player->Bot != NULL) // Bots are automatically assumed ready, to prevent confusion screen->DrawTexture(readyico, x - (readyico->GetWidth() * CleanXfac), y, DTA_CleanNoMove, true, TAG_DONE); color = (EColorRange)HU_GetRowColor(player, i == consoleplayer); @@ -1817,9 +1817,9 @@ void WI_updateStats () cnt_kills[0] = plrs[me].skills; cnt_items[0] = plrs[me].sitems; cnt_secret[0] = plrs[me].ssecret; - cnt_time = plrs[me].stime / TICRATE; + cnt_time = Tics2Seconds(plrs[me].stime); cnt_par = wbs->partime / TICRATE; - cnt_total_time = wbs->totaltime / TICRATE; + cnt_total_time = Tics2Seconds(wbs->totaltime); } if (sp_state == 2) @@ -1882,19 +1882,21 @@ void WI_updateStats () cnt_total_time += 3; } - if (!gameinfo.intermissioncounter || cnt_time >= plrs[me].stime / TICRATE) - cnt_time = plrs[me].stime / TICRATE; + int sec = Tics2Seconds(plrs[me].stime); + if (!gameinfo.intermissioncounter || cnt_time >= sec) + cnt_time = sec; - if (!gameinfo.intermissioncounter || cnt_total_time >= wbs->totaltime / TICRATE) - cnt_total_time = wbs->totaltime / TICRATE; + int tsec = Tics2Seconds(wbs->totaltime); + if (!gameinfo.intermissioncounter || cnt_total_time >= tsec) + cnt_total_time = tsec; if (!gameinfo.intermissioncounter || cnt_par >= wbs->partime / TICRATE) { cnt_par = wbs->partime / TICRATE; - if (cnt_time >= plrs[me].stime / TICRATE) + if (cnt_time >= sec) { - cnt_total_time = wbs->totaltime / TICRATE; + cnt_total_time = tsec; S_Sound (CHAN_VOICE | CHAN_UI, "intermission/nextstage", 1, ATTN_NONE); sp_state++; } @@ -2008,7 +2010,7 @@ void WI_checkForAccelerate(void) { if ((player->cmd.ucmd.buttons ^ player->oldbuttons) && ((players[i].cmd.ucmd.buttons & players[i].oldbuttons) - == players[i].oldbuttons) && !player->isbot) + == players[i].oldbuttons) && player->Bot == NULL) { acceleratestage = 1; playerready[i] = true; diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 4248df274..71cc42af0 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -1524,20 +1524,36 @@ static bool QueryPathKey(HKEY key, const char *keypath, const char *valname, FSt // //========================================================================== -FString I_GetSteamPath() +TArray I_GetSteamPath() { + TArray result; + static const char *const steam_dirs[] = + { + "doom 2/base", + "final doom/base", + "heretic shadow of the serpent riders/base", + "hexen/base", + "hexen deathkings of the dark citadel/base", + "ultimate doom/base", + "DOOM 3 BFG Edition/base/wads", + "Strife" + }; + FString path; - if (QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path)) + if (!QueryPathKey(HKEY_CURRENT_USER, "Software\\Valve\\Steam", "SteamPath", path)) { - return path; + if (!QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path)) + return result; } - if (QueryPathKey(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", "InstallPath", path)) + path += "/SteamApps/common/"; + + for(unsigned int i = 0; i < countof(steam_dirs); ++i) { - return path; + result.Push(path + steam_dirs[i]); } - path = ""; - return path; + + return result; } //========================================================================== diff --git a/src/win32/i_system.h b/src/win32/i_system.h index 9fbf2db5c..647a08d13 100644 --- a/src/win32/i_system.h +++ b/src/win32/i_system.h @@ -142,7 +142,7 @@ void I_SetWndProc(); // [RH] Checks the registry for Steam's install path, so we can scan its // directories for IWADs if the user purchased any through Steam. -FString I_GetSteamPath(); +TArray I_GetSteamPath(); // Damn Microsoft for doing Get/SetWindowLongPtr half-assed. Instead of // giving them proper prototypes under Win32, they are just macros for diff --git a/src/win32/st_start.cpp b/src/win32/st_start.cpp index 04b52925e..af7f7ad98 100644 --- a/src/win32/st_start.cpp +++ b/src/win32/st_start.cpp @@ -767,7 +767,7 @@ void FHexenStartupScreen::NetProgress(int count) y = ST_NETPROGRESS_Y; ST_Util_DrawBlock (StartupBitmap, NetNotchBits, x, y, ST_NETNOTCH_WIDTH / 2, ST_NETNOTCH_HEIGHT); } - S_Sound (CHAN_BODY, "Drip", 1, ATTN_NONE); + S_Sound (CHAN_BODY, "misc/netnotch", 1, ATTN_NONE); I_GetEvent (); } } diff --git a/src/zscript/vmbuilder.cpp b/src/zscript/vmbuilder.cpp index 4a3462c4f..2dff77cfc 100644 --- a/src/zscript/vmbuilder.cpp +++ b/src/zscript/vmbuilder.cpp @@ -279,7 +279,7 @@ VMFunctionBuilder::RegAvailability::RegAvailability() //========================================================================== // -// VMFunctionBuilder :: RegAvailibity :: Get +// VMFunctionBuilder :: RegAvailability :: Get // // Gets one or more unused registers. If getting multiple registers, they // will all be consecutive. Returns -1 if there were not enough consecutive @@ -411,6 +411,30 @@ void VMFunctionBuilder::RegAvailability::Return(int reg, int count) } } +//========================================================================== +// +// VMFunctionBuilder :: RegAvailability :: Reuse +// +// Marks an unused register as in-use. Returns false if the register is +// already in use or true if it was successfully reused. +// +//========================================================================== + +bool VMFunctionBuilder::RegAvailability::Reuse(int reg) +{ + assert(reg >= 0 && reg <= 255); + + VM_UWORD mask = reg & 31; + int word = reg / 32; + + if (Used[word] & mask) + { // It's already in use! + return false; + } + Used[word] |= mask; + return true; +} + //========================================================================== // // VMFunctionBuilder :: Emit diff --git a/src/zscript/vmbuilder.h b/src/zscript/vmbuilder.h index c15f65739..74537c857 100644 --- a/src/zscript/vmbuilder.h +++ b/src/zscript/vmbuilder.h @@ -13,6 +13,7 @@ public: RegAvailability(); int Get(int count); // Returns the first register in the range void Return(int reg, int count); + bool Reuse(int regnum); private: VM_UWORD Used[256/32]; // Bitmap of used registers (bit set means reg is used) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 08a1c4f4b..4f9e9ae22 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required( VERSION 2.4 ) add_subdirectory( lemon ) add_subdirectory( re2c ) -if( WIN32 ) +if( WIN32 AND NOT CMAKE_SIZEOF_VOID_P MATCHES "8" ) add_subdirectory( fixrtext ) -endif( WIN32 ) +endif( WIN32 AND NOT CMAKE_SIZEOF_VOID_P MATCHES "8" ) add_subdirectory( updaterevision ) add_subdirectory( zipdir ) diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 185931277..d4910df91 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -26,6 +26,8 @@ ACTOR Actor native //: Thinker DesignatedTeam 255 PainType Normal DeathType Normal + TeleFogSourceType "TeleportFog" + TeleFogDestType "TeleportFog" // Variables for the expression evaluator // NOTE: fixed_t and angle_t are only used here to ensure proper conversion @@ -62,6 +64,7 @@ ACTOR Actor native //: Thinker native fixed_t radius; native int reactiontime; native fixed_t meleerange; + native fixed_t speed; // Meh, MBF redundant functions. Only for DeHackEd support. action native A_Turn(float angle = 0); @@ -69,7 +72,7 @@ ACTOR Actor native //: Thinker // End of MBF redundant functions. action native A_MonsterRail(); - action native A_BFGSpray(class spraytype = "BFGExtra", int numrays = 40, int damagecount = 15); + action native A_BFGSpray(class spraytype = "BFGExtra", int numrays = 40, int damagecount = 15, float/*angle*/ angle = 90, float distance = 16*64, float/*angle*/ vrange = 32, int damage = 0); action native A_Pain(); action native A_NoBlocking(); action native A_XScream(); @@ -185,6 +188,7 @@ ACTOR Actor native //: Thinker action native A_ClearSoundTarget(); action native A_FireAssaultGun(); action native A_CheckTerrain(); + action native A_FaceConsolePlayer(float MaxTurnAngle = 0); // [TP] action native A_MissileAttack(); action native A_MeleeAttack(); @@ -203,7 +207,7 @@ ACTOR Actor native //: Thinker action native A_CustomMissile(class missiletype, float spawnheight = 32, int spawnofs_xy = 0, float angle = 0, int flags = 0, float pitch = 0); action native A_CustomBulletAttack(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0); action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, int aim = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none", float spawnofs_z = 0); - action native A_JumpIfHealthLower(int health, state label); + action native A_JumpIfHealthLower(int health, state label, int ptr_selector = AAPTR_DEFAULT); action native A_JumpIfCloser(float distance, state label); action native A_JumpIfTracerCloser(float distance, state label); action native A_JumpIfMasterCloser(float distance, state label); @@ -220,9 +224,9 @@ ACTOR Actor native //: Thinker action native A_Log(string whattoprint); action native A_LogInt(int whattoprint); action native A_SetTranslucent(float alpha, int style = 0); - action native A_FadeIn(float reduce = 0.1); - action native A_FadeOut(float reduce = 0.1, bool remove = true); - action native A_FadeTo(float target, float amount = 0.1, bool remove = false); + action native A_FadeIn(float reduce = 0.1, int flags = 0); + action native A_FadeOut(float reduce = 0.1, int flags = 1); //bool remove == true + action native A_FadeTo(float target, float amount = 0.1, int flags = 0); action native A_SetScale(float scalex, float scaley = 0); action native A_SetMass(int mass); action native A_SpawnDebris(class spawntype, bool transfer_translation = false, float mult_h = 1, float mult_v = 1); @@ -233,15 +237,15 @@ ACTOR Actor native //: Thinker action native A_ChangeFlag(string flagname, bool value); action native A_CheckFlag(string flagname, state label, int check_pointer = AAPTR_DEFAULT); action native A_JumpIf(bool expression, state label); - action native A_RemoveMaster(); - action native A_RemoveChildren(bool removeall = false); - action native A_RemoveSiblings(bool removeall = false); - action native A_KillMaster(name damagetype = "none"); - action native A_KillChildren(name damagetype = "none"); - action native A_KillSiblings(name damagetype = "none"); - action native A_RaiseMaster(); - action native A_RaiseChildren(); - action native A_RaiseSiblings(); + action native A_RemoveMaster(int flags = 0); + action native A_RemoveChildren(bool removeall = false, int flags = 0); + action native A_RemoveSiblings(bool removeall = false, int flags = 0); + action native A_KillMaster(name damagetype = "none", int flags = 0); + action native A_KillChildren(name damagetype = "none", int flags = 0); + action native A_KillSiblings(name damagetype = "none", int flags = 0); + action native A_RaiseMaster(bool copy = 0); + action native A_RaiseChildren(bool copy = 0); + action native A_RaiseSiblings(bool copy = 0); action native A_CheckFloor(state label); action native A_CheckCeiling(state label); action native A_PlayerSkinCheck(state label); @@ -274,9 +278,9 @@ ACTOR Actor native //: Thinker action native A_CheckLOF(state jump, int flags = 0, float range = 0, float minrange = 0, float angle = 0, float pitch = 0, float offsetheight = 0, float offsetwidth = 0, int ptr_target = AAPTR_DEFAULT); action native A_JumpIfTargetInLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_JumpIfInTargetLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); - action native A_DamageMaster(int amount, name damagetype = "none"); - action native A_DamageChildren(int amount, name damagetype = "none"); - action native A_DamageSiblings(int amount, name damagetype = "none"); + action native A_DamageMaster(int amount, name damagetype = "none", int flags = 0); + action native A_DamageChildren(int amount, name damagetype = "none", int flags = 0); + action native A_DamageSiblings(int amount, name damagetype = "none", int flags = 0); action native A_SelectWeapon(class whichweapon); action native A_Punch(); action native A_Feathers(); @@ -302,6 +306,21 @@ ACTOR Actor native //: Thinker action native A_SetTics(int tics); action native A_SetDamageType(name damagetype); action native A_DropItem(class item, int dropamount = -1, int chance = 256); + action native A_SetSpeed(float speed); + action native A_DamageSelf(int amount, name damagetype = "none", int flags = 0); + action native A_DamageTarget(int amount, name damagetype = "none", int flags = 0); + action native A_DamageTracer(int amount, name damagetype = "none", int flags = 0); + action native A_KillTarget(name damagetype = "none", int flags = 0); + action native A_KillTracer(name damagetype = "none", int flags = 0); + action native A_RemoveTarget(int flags = 0); + action native A_RemoveTracer(int flags = 0); + action native A_Remove(int removee, int flags = 0); + action native A_GiveToChildren(class itemtype, int amount = 0); + action native A_GiveToSiblings(class itemtype, int amount = 0); + action native A_TakeFromChildren(class itemtype, int amount = 0); + action native A_TakeFromSiblings(class itemtype, int amount = 0); + action native A_SetTeleFog(name oldpos, name newpos); + action native A_SwapTeleFog(); action native A_CheckSightOrRange(float distance, state label); action native A_CheckRange(float distance, state label); diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index d14f865f8..75af8e747 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -1,365 +1,432 @@ - -// Flags for A_PainAttack -const int PAF_NOSKULLATTACK = 1; -const int PAF_AIMFACING = 2; -const int PAF_NOTARGET = 4; - -// Flags for A_VileAttack -const int VAF_DMGTYPEAPPLYTODIRECT = 1; - -// Flags for A_Saw -const int SF_NORANDOM = 1; -const int SF_RANDOMLIGHTMISS = 2; -const int SF_RANDOMLIGHTHIT = 4; -const int SF_RANDOMLIGHTBOTH = 6; -const int SF_NOUSEAMMOMISS = 8; -const int SF_NOUSEAMMO = 16; - -// Flags for A_CustomMissile -const int CMF_AIMOFFSET = 1; -const int CMF_AIMDIRECTION = 2; -const int CMF_TRACKOWNER = 4; -const int CMF_CHECKTARGETDEAD = 8; -const int CMF_ABSOLUTEPITCH = 16; -const int CMF_OFFSETPITCH = 32; -const int CMF_SAVEPITCH = 64; -const int CMF_ABSOLUTEANGLE = 128; - -// Flags for A_CustomBulletAttack -const int CBAF_AIMFACING = 1; -const int CBAF_NORANDOM = 2; -const int CBAF_EXPLICITANGLE = 4; -const int CBAF_NOPITCH = 8; -const int CBAF_NORANDOMPUFFZ = 16; - -// Flags for A_GunFlash -const int GFF_NOEXTCHANGE = 1; - -// Flags for A_FireBullets -const int FBF_USEAMMO = 1; -const int FBF_NORANDOM = 2; -const int FBF_EXPLICITANGLE = 4; -const int FBF_NOPITCH = 8; -const int FBF_NOFLASH = 16; -const int FBF_NORANDOMPUFFZ = 32; - -// Flags for A_SpawnItemEx -const int SXF_TRANSFERTRANSLATION = 1; -const int SXF_ABSOLUTEPOSITION = 2; -const int SXF_ABSOLUTEANGLE = 4; -const int SXF_ABSOLUTEMOMENTUM = 8; -const int SXF_ABSOLUTEVELOCITY = 8; -const int SXF_SETMASTER = 16; -const int SXF_NOCHECKPOSITION = 32; -const int SXF_TELEFRAG = 64; -const int SXF_CLIENTSIDE = 128; // only used by Skulltag -const int SXF_TRANSFERAMBUSHFLAG = 256; -const int SXF_TRANSFERPITCH = 512; -const int SXF_TRANSFERPOINTERS = 1024; -const int SXF_USEBLOODCOLOR = 2048; -const int SXF_CLEARCALLERTID = 4096; -const int SXF_MULTIPLYSPEED = 8192; -const int SXF_TRANSFERSCALE = 16384; -const int SXF_TRANSFERSPECIAL = 32768; -const int SXF_CLEARCALLERSPECIAL = 65536; -const int SXF_TRANSFERSTENCILCOL = 131072; - -// Flags for A_Chase -const int CHF_FASTCHASE = 1; -const int CHF_NOPLAYACTIVE = 2; -const int CHF_NIGHTMAREFAST = 4; -const int CHF_RESURRECT = 8; -const int CHF_DONTMOVE = 16; - -// Flags for A_LookEx -const int LOF_NOSIGHTCHECK = 1; -const int LOF_NOSOUNDCHECK = 2; -const int LOF_DONTCHASEGOAL = 4; -const int LOF_NOSEESOUND = 8; -const int LOF_FULLVOLSEESOUND = 16; -const int LOF_NOJUMP = 32; - -// Flags for A_Respawn -const int RSF_FOG = 1; -const int RSF_KEEPTARGET = 2; -const int RSF_TELEFRAG = 4; - -// Flags for A_JumpIfTargetInLOS and A_JumpIfInTargetLOS -const int JLOSF_PROJECTILE = 1; -const int JLOSF_NOSIGHT = 2; -const int JLOSF_CLOSENOFOV = 4; -const int JLOSF_CLOSENOSIGHT = 8; -const int JLOSF_CLOSENOJUMP = 16; -const int JLOSF_DEADNOJUMP = 32; -const int JLOSF_CHECKMASTER = 64; -const int JLOSF_TARGETLOS = 128; -const int JLOSF_FLIPFOV = 256; -const int JLOSF_ALLYNOJUMP = 512; -const int JLOSF_COMBATANTONLY = 1024; -const int JLOSF_NOAUTOAIM = 2048; - -// Flags for A_ChangeVelocity -const int CVF_RELATIVE = 1; -const int CVF_REPLACE = 2; - -// Flags for A_WeaponReady -const int WRF_NOBOB = 1; -const int WRF_NOSWITCH = 2; -const int WRF_NOPRIMARY = 4; -const int WRF_NOSECONDARY = 8; -const int WRF_NOFIRE = WRF_NOPRIMARY | WRF_NOSECONDARY; -const int WRF_ALLOWRELOAD = 16; -const int WRF_ALLOWZOOM = 32; -const int WRF_DISABLESWITCH = 64; - -// Morph constants -const int MRF_ADDSTAMINA = 1; -const int MRF_FULLHEALTH = 2; -const int MRF_UNDOBYTOMEOFPOWER = 4; -const int MRF_UNDOBYCHAOSDEVICE = 8; -const int MRF_FAILNOTELEFRAG = 16; -const int MRF_FAILNOLAUGH = 32; -const int MRF_WHENINVULNERABLE = 64; -const int MRF_LOSEACTUALWEAPON = 128; -const int MRF_NEWTIDBEHAVIOUR = 256; -const int MRF_UNDOBYDEATH = 512; -const int MRF_UNDOBYDEATHFORCED = 1024; -const int MRF_UNDOBYDEATHSAVES = 2048; - -// Flags for A_RailAttack and A_CustomRailgun -const int RGF_SILENT = 1; -const int RGF_NOPIERCING = 2; -const int RGF_EXPLICITANGLE = 4; -const int RGF_FULLBRIGHT = 8; -const int RGF_CENTERZ = 16; - -// Flags for A_Mushroom -const int MSF_Standard = 0; -const int MSF_Classic = 1; -const int MSF_DontHurt = 2; - -// Flags for A_Explode -const int XF_HURTSOURCE = 1; -const int XF_NOTMISSILE = 4; - -// Flags for A_RadiusThrust -const int RTF_AFFECTSOURCE = 1; -const int RTF_NOIMPACTDAMAGE = 2; -const int RTF_NOTMISSILE = 4; - -// Flags for A_Blast -const int BF_USEAMMO = 1; -const int BF_DONTWARN = 2; -const int BF_AFFECTBOSSES = 4; -const int BF_NOIMPACTDAMAGE = 8; - -// Flags for A_SeekerMissile -const int SMF_LOOK = 1; -const int SMF_PRECISE = 2; -const int SMF_CURSPEED = 4; - -// Flags for A_CustomPunch -const int CPF_USEAMMO = 1; -const int CPF_DAGGER = 2; -const int CPF_PULLIN = 4; -const int CPF_NORANDOMPUFFZ = 8; - -// Flags for A_CustomMissile -const int FPF_AIMATANGLE = 1; -const int FPF_TRANSFERTRANSLATION = 2; - -// Flags for A_Teleport -const int TF_TELEFRAG = 1;const int TF_RANDOMDECIDE = 2; - -// Flags for A_WolfAttack -const int WAF_NORANDOM = 1; -const int WAF_USEPUFF = 2; - -// Flags for A_RadiusGive -enum -{ - RGF_GIVESELF = 1, - RGF_PLAYERS = 2, - RGF_MONSTERS = 4, - RGF_OBJECTS = 8, - RGF_VOODOO = 16, - RGF_CORPSES = 32, - RGF_NOTARGET = 64, - RGF_NOTRACER = 128, - RGF_NOMASTER = 256, - RGF_CUBE = 512, -}; - -// Activation flags -enum -{ - THINGSPEC_Default = 0, - THINGSPEC_ThingActs = 1, - THINGSPEC_ThingTargets = 2, - THINGSPEC_TriggerTargets = 4, - THINGSPEC_MonsterTrigger = 8, - THINGSPEC_MissileTrigger = 16, - THINGSPEC_ClearSpecial = 32, - THINGSPEC_NoDeathSpecial = 64, - THINGSPEC_TriggerActs = 128, -}; -// Shorter aliases for same -const int AF_Default = 0; -const int AF_ThingActs = 1; -const int AF_ThingTargets = 2; -const int AF_TriggerTargets = 4; -const int AF_MonsterTrigger = 8; -const int AF_MissileTrigger = 16; -const int AF_ClearSpecial = 32; -const int AF_NoDeathSpecial = 64; -const int AF_TriggerActs = 128; - -// Flags for A_TakeInventory and A_TakeFromTarget -const int TIF_NOTAKEINFINITE = 1; - -// constants for A_PlaySound -enum -{ - CHAN_AUTO = 0, - CHAN_WEAPON = 1, - CHAN_VOICE = 2, - CHAN_ITEM = 3, - CHAN_BODY = 4, - CHAN_5 = 5, - CHAN_6 = 6, - CHAN_7 = 7, - - // modifier flags - CHAN_LISTENERZ = 8, - CHAN_MAYBE_LOCAL = 16, - CHAN_UI = 32, - CHAN_NOPAUSE = 64 -}; - -// sound attenuation values -const float ATTN_NONE = 0; -const float ATTN_NORM = 1; -const float ATTN_IDLE = 1.001; -const float ATTN_STATIC = 3; - -// For SetPlayerProprty action special -Const Int PROP_FROZEN = 0; -Const Int PROP_NOTARGET = 1; -Const Int PROP_INSTANTWEAPONSWITCH = 2; -Const Int PROP_FLY = 3; -Const Int PROP_TOTALLYFROZEN = 4; -Const Int PROP_INVULNERABILITY = 5; // (Deprecated) -Const Int PROP_STRENGTH = 6; // (Deprecated) -Const Int PROP_INVISIBILITY = 7; // (Deprecated) -Const Int PROP_RADIATIONSUIT = 8; // (Deprecated) -Const Int PROP_ALLMAP = 9; // (Deprecated) -Const Int PROP_INFRARED = 10; // (Deprecated) -Const Int PROP_WEAPONLEVEL2 = 11; // (Deprecated) -Const Int PROP_FLIGHT = 12; // (Deprecated) -Const Int PROP_SPEED = 15; // (Deprecated) -Const Int PROP_BUDDHA = 16; - -// Line_SetBlocking -Const Int BLOCKF_CREATURES = 1; -Const Int BLOCKF_MONSTERS = 2; -Const Int BLOCKF_PLAYERS = 4; -Const Int BLOCKF_FLOATERS = 8; -Const Int BLOCKF_PROJECTILES = 16; -Const Int BLOCKF_EVERYTHING = 32; -Const Int BLOCKF_RAILING = 64; -Const Int BLOCKF_USE = 128; - -// Pointer constants, bitfield-enabled - -Const Int AAPTR_DEFAULT = 0; -Const Int AAPTR_NULL = 1; -Const Int AAPTR_TARGET = 2; -Const Int AAPTR_MASTER = 4; -Const Int AAPTR_TRACER = 8; - -Const Int AAPTR_PLAYER_GETTARGET = 16; -Const Int AAPTR_PLAYER_GETCONVERSATION = 32; - -Const Int AAPTR_PLAYER1 = 64; -Const Int AAPTR_PLAYER2 = 128; -Const Int AAPTR_PLAYER3 = 256; -Const Int AAPTR_PLAYER4 = 512; -Const Int AAPTR_PLAYER5 = 1024; -Const Int AAPTR_PLAYER6 = 2048; -Const Int AAPTR_PLAYER7 = 4096; -Const Int AAPTR_PLAYER8 = 8192; - -Const Int AAPTR_FRIENDPLAYER = 16384; - -// Pointer operation flags - -Const Int PTROP_UNSAFETARGET = 1; -Const Int PTROP_UNSAFEMASTER = 2; -Const Int PTROP_NOSAFEGUARDS = PTROP_UNSAFETARGET|PTROP_UNSAFEMASTER; - - -// Flags for A_Warp - -Const Int WARPF_ABSOLUTEOFFSET = 0x1; -Const Int WARPF_ABSOLUTEANGLE = 0x2; -Const Int WARPF_USECALLERANGLE = 0x4; -Const Int WARPF_NOCHECKPOSITION = 0x8; -Const Int WARPF_INTERPOLATE = 0x10; -Const Int WARPF_WARPINTERPOLATION = 0x20; -Const Int WARPF_COPYINTERPOLATION = 0x40; -Const Int WARPF_STOP = 0x80; -Const Int WARPF_TOFLOOR = 0x100; -Const Int WARPF_TESTONLY = 0x200; - -// flags for A_SetPitch/SetAngle -const int SPF_FORCECLAMP = 1; -const int SPF_INTERPOLATE = 2; - - -// flags for A_CheckLOF - -enum -{ - CLOFF_NOAIM_VERT = 0x1, - CLOFF_NOAIM_HORZ = 0x2, - - CLOFF_JUMPENEMY = 0x4, - CLOFF_JUMPFRIEND = 0x8, - CLOFF_JUMPOBJECT = 0x10, - CLOFF_JUMPNONHOSTILE = 0x20, - - CLOFF_SKIPENEMY = 0x40, - CLOFF_SKIPFRIEND = 0x80, - CLOFF_SKIPOBJECT = 0x100, - CLOFF_SKIPNONHOSTILE = 0x200, - - CLOFF_MUSTBESHOOTABLE = 0x400, - - CLOFF_SKIPTARGET = 0x800, - CLOFF_ALLOWNULL = 0x1000, - CLOFF_CHECKPARTIAL = 0x2000, - - CLOFF_MUSTBEGHOST = 0x4000, - CLOFF_IGNOREGHOST = 0x8000, - - CLOFF_MUSTBESOLID = 0x10000, - CLOFF_BEYONDTARGET = 0x20000, - - CLOFF_FROMBASE = 0x40000, - CLOFF_MUL_HEIGHT = 0x80000, - CLOFF_MUL_WIDTH = 0x100000, - - CLOFF_JUMP_ON_MISS = 0x200000, - CLOFF_AIM_VERT_NOOFFSET = 0x400000, - - CLOFF_SKIPOBSTACLES = CLOFF_SKIPENEMY|CLOFF_SKIPFRIEND|CLOFF_SKIPOBJECT|CLOFF_SKIPNONHOSTILE, - CLOFF_NOAIM = CLOFF_NOAIM_VERT|CLOFF_NOAIM_HORZ -}; - - -// Flags for A_AlertMonsters -const int AMF_TARGETEMITTER = 1; -const int AMF_TARGETNONPLAYER = 2; -const int AMF_EMITFROMTARGET = 4; - - + +// Flags for A_PainAttack +const int PAF_NOSKULLATTACK = 1; +const int PAF_AIMFACING = 2; +const int PAF_NOTARGET = 4; + +// Flags for A_VileAttack +const int VAF_DMGTYPEAPPLYTODIRECT = 1; + +// Flags for A_Saw +const int SF_NORANDOM = 1; +const int SF_RANDOMLIGHTMISS = 2; +const int SF_RANDOMLIGHTHIT = 4; +const int SF_RANDOMLIGHTBOTH = 6; +const int SF_NOUSEAMMOMISS = 8; +const int SF_NOUSEAMMO = 16; +const int SF_NOPULLIN = 32; +const int SF_NOTURN = 64; +const int SF_STEALARMOR = 128; + +// Flags for A_CustomMissile +const int CMF_AIMOFFSET = 1; +const int CMF_AIMDIRECTION = 2; +const int CMF_TRACKOWNER = 4; +const int CMF_CHECKTARGETDEAD = 8; +const int CMF_ABSOLUTEPITCH = 16; +const int CMF_OFFSETPITCH = 32; +const int CMF_SAVEPITCH = 64; +const int CMF_ABSOLUTEANGLE = 128; + +// Flags for A_CustomBulletAttack +const int CBAF_AIMFACING = 1; +const int CBAF_NORANDOM = 2; +const int CBAF_EXPLICITANGLE = 4; +const int CBAF_NOPITCH = 8; +const int CBAF_NORANDOMPUFFZ = 16; + +// Flags for A_GunFlash +const int GFF_NOEXTCHANGE = 1; + +// Flags for A_FireBullets +const int FBF_USEAMMO = 1; +const int FBF_NORANDOM = 2; +const int FBF_EXPLICITANGLE = 4; +const int FBF_NOPITCH = 8; +const int FBF_NOFLASH = 16; +const int FBF_NORANDOMPUFFZ = 32; + +// Flags for A_SpawnItemEx +const int SXF_TRANSFERTRANSLATION = 1 << 0; +const int SXF_ABSOLUTEPOSITION = 1 << 1; +const int SXF_ABSOLUTEANGLE = 1 << 2; +const int SXF_ABSOLUTEMOMENTUM = 1 << 3; //Since "momentum" is declared to be deprecated in the expressions, for compatibility +const int SXF_ABSOLUTEVELOCITY = 1 << 3; //purposes, this was made. It does the same thing though. Do not change the value. +const int SXF_SETMASTER = 1 << 4; +const int SXF_NOCHECKPOSITION = 1 << 5; +const int SXF_TELEFRAG = 1 << 6; +const int SXF_CLIENTSIDE = 1 << 7; // only used by Skulltag +const int SXF_TRANSFERAMBUSHFLAG = 1 << 8; +const int SXF_TRANSFERPITCH = 1 << 9; +const int SXF_TRANSFERPOINTERS = 1 << 10; +const int SXF_USEBLOODCOLOR = 1 << 11; +const int SXF_CLEARCALLERTID = 1 << 12; +const int SXF_MULTIPLYSPEED = 1 << 13; +const int SXF_TRANSFERSCALE = 1 << 14; +const int SXF_TRANSFERSPECIAL = 1 << 15; +const int SXF_CLEARCALLERSPECIAL = 1 << 16; +const int SXF_TRANSFERSTENCILCOL = 1 << 17; +const int SXF_TRANSFERALPHA = 1 << 18; +const int SXF_TRANSFERRENDERSTYLE = 1 << 19; +const int SXF_SETTARGET = 1 << 20; +const int SXF_SETTRACER = 1 << 21; +const int SXF_NOPOINTERS = 1 << 22; +const int SXF_ORIGINATOR = 1 << 23; + +// Flags for A_Chase +const int CHF_FASTCHASE = 1; +const int CHF_NOPLAYACTIVE = 2; +const int CHF_NIGHTMAREFAST = 4; +const int CHF_RESURRECT = 8; +const int CHF_DONTMOVE = 16; + +// Flags for A_LookEx +const int LOF_NOSIGHTCHECK = 1; +const int LOF_NOSOUNDCHECK = 2; +const int LOF_DONTCHASEGOAL = 4; +const int LOF_NOSEESOUND = 8; +const int LOF_FULLVOLSEESOUND = 16; +const int LOF_NOJUMP = 32; + +// Flags for A_Respawn +const int RSF_FOG = 1; +const int RSF_KEEPTARGET = 2; +const int RSF_TELEFRAG = 4; + +// Flags for A_JumpIfTargetInLOS and A_JumpIfInTargetLOS +enum +{ + JLOSF_PROJECTILE = 1, + JLOSF_NOSIGHT = 1 << 1, + JLOSF_CLOSENOFOV = 1 << 2, + JLOSF_CLOSENOSIGHT = 1 << 3, + JLOSF_CLOSENOJUMP = 1 << 4, + JLOSF_DEADNOJUMP = 1 << 5, + JLOSF_CHECKMASTER = 1 << 6, + JLOSF_TARGETLOS = 1 << 7, + JLOSF_FLIPFOV = 1 << 8, + JLOSF_ALLYNOJUMP = 1 << 9, + JLOSF_COMBATANTONLY = 1 << 10, + JLOSF_NOAUTOAIM = 1 << 11, + JLOSF_CHECKTRACER = 1 << 12, +}; + +// Flags for A_ChangeVelocity +const int CVF_RELATIVE = 1; +const int CVF_REPLACE = 2; + +// Flags for A_WeaponReady +const int WRF_NOBOB = 1; +const int WRF_NOSWITCH = 2; +const int WRF_NOPRIMARY = 4; +const int WRF_NOSECONDARY = 8; +const int WRF_NOFIRE = WRF_NOPRIMARY | WRF_NOSECONDARY; +const int WRF_ALLOWRELOAD = 16; +const int WRF_ALLOWZOOM = 32; +const int WRF_DISABLESWITCH = 64; + +// Morph constants +const int MRF_ADDSTAMINA = 1; +const int MRF_FULLHEALTH = 2; +const int MRF_UNDOBYTOMEOFPOWER = 4; +const int MRF_UNDOBYCHAOSDEVICE = 8; +const int MRF_FAILNOTELEFRAG = 16; +const int MRF_FAILNOLAUGH = 32; +const int MRF_WHENINVULNERABLE = 64; +const int MRF_LOSEACTUALWEAPON = 128; +const int MRF_NEWTIDBEHAVIOUR = 256; +const int MRF_UNDOBYDEATH = 512; +const int MRF_UNDOBYDEATHFORCED = 1024; +const int MRF_UNDOBYDEATHSAVES = 2048; + +// Flags for A_RailAttack and A_CustomRailgun +const int RGF_SILENT = 1; +const int RGF_NOPIERCING = 2; +const int RGF_EXPLICITANGLE = 4; +const int RGF_FULLBRIGHT = 8; +const int RGF_CENTERZ = 16; + +// Flags for A_Mushroom +const int MSF_Standard = 0; +const int MSF_Classic = 1; +const int MSF_DontHurt = 2; + +// Flags for A_Explode +const int XF_HURTSOURCE = 1; +const int XF_NOTMISSILE = 4; + +// Flags for A_RadiusThrust +const int RTF_AFFECTSOURCE = 1; +const int RTF_NOIMPACTDAMAGE = 2; +const int RTF_NOTMISSILE = 4; + +// Flags for A_Blast +const int BF_USEAMMO = 1; +const int BF_DONTWARN = 2; +const int BF_AFFECTBOSSES = 4; +const int BF_NOIMPACTDAMAGE = 8; + +// Flags for A_SeekerMissile +const int SMF_LOOK = 1; +const int SMF_PRECISE = 2; +const int SMF_CURSPEED = 4; + +// Flags for A_CustomPunch +const int CPF_USEAMMO = 1; +const int CPF_DAGGER = 2; +const int CPF_PULLIN = 4; +const int CPF_NORANDOMPUFFZ = 8; +const int CPF_NOTURN = 16; +const int CPF_STEALARMOR = 32; + +// Flags for A_CustomMissile +const int FPF_AIMATANGLE = 1; +const int FPF_TRANSFERTRANSLATION = 2; + +// Flags for A_Teleport +enum +{ + TF_TELEFRAG = 0x00000001, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 0x00000002, // Randomly fail based on health. (A_Srcr2Decide) + TF_FORCED = 0x00000004, // Forget what's in the way. TF_Telefrag takes precedence though. + TF_KEEPVELOCITY = 0x00000008, // Preserve velocity. + TF_KEEPANGLE = 0x00000010, // Keep angle. + TF_USESPOTZ = 0x00000020, // Set the z to the spot's z, instead of the floor. + TF_NOSRCFOG = 0x00000040, // Don't leave any fog behind when teleporting. + TF_NODESTFOG = 0x00000080, // Don't spawn any fog at the arrival position. + TF_USEACTORFOG = 0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs. + TF_NOJUMP = 0x00000200, // Don't jump after teleporting. + + TF_KEEPORIENTATION = TF_KEEPVELOCITY|TF_KEEPANGLE, + TF_NOFOG = TF_NOSRCFOG|TF_NODESTFOG, +}; + +// Flags for A_WolfAttack +const int WAF_NORANDOM = 1; +const int WAF_USEPUFF = 2; + +// Flags for A_RadiusGive +enum +{ + RGF_GIVESELF = 1, + RGF_PLAYERS = 1 << 1, + RGF_MONSTERS = 1 << 2, + RGF_OBJECTS = 1 << 3, + RGF_VOODOO = 1 << 4, + RGF_CORPSES = 1 << 5, + RGF_NOTARGET = 1 << 6, + RGF_NOTRACER = 1 << 7, + RGF_NOMASTER = 1 << 8, + RGF_CUBE = 1 << 9, + RGF_NOSIGHT = 1 << 10, + RGF_MISSILES = 1 << 11, +}; + +// Activation flags +enum +{ + THINGSPEC_Default = 0, + THINGSPEC_ThingActs = 1, + THINGSPEC_ThingTargets = 2, + THINGSPEC_TriggerTargets = 4, + THINGSPEC_MonsterTrigger = 8, + THINGSPEC_MissileTrigger = 16, + THINGSPEC_ClearSpecial = 32, + THINGSPEC_NoDeathSpecial = 64, + THINGSPEC_TriggerActs = 128, +}; +// Shorter aliases for same +const int AF_Default = 0; +const int AF_ThingActs = 1; +const int AF_ThingTargets = 2; +const int AF_TriggerTargets = 4; +const int AF_MonsterTrigger = 8; +const int AF_MissileTrigger = 16; +const int AF_ClearSpecial = 32; +const int AF_NoDeathSpecial = 64; +const int AF_TriggerActs = 128; + +// Flags for A_TakeInventory and A_TakeFromTarget +const int TIF_NOTAKEINFINITE = 1; + +// constants for A_PlaySound +enum +{ + CHAN_AUTO = 0, + CHAN_WEAPON = 1, + CHAN_VOICE = 2, + CHAN_ITEM = 3, + CHAN_BODY = 4, + CHAN_5 = 5, + CHAN_6 = 6, + CHAN_7 = 7, + + // modifier flags + CHAN_LISTENERZ = 8, + CHAN_MAYBE_LOCAL = 16, + CHAN_UI = 32, + CHAN_NOPAUSE = 64 +}; + +// sound attenuation values +const float ATTN_NONE = 0; +const float ATTN_NORM = 1; +const float ATTN_IDLE = 1.001; +const float ATTN_STATIC = 3; + +// For SetPlayerProprty action special +Const Int PROP_FROZEN = 0; +Const Int PROP_NOTARGET = 1; +Const Int PROP_INSTANTWEAPONSWITCH = 2; +Const Int PROP_FLY = 3; +Const Int PROP_TOTALLYFROZEN = 4; +Const Int PROP_INVULNERABILITY = 5; // (Deprecated) +Const Int PROP_STRENGTH = 6; // (Deprecated) +Const Int PROP_INVISIBILITY = 7; // (Deprecated) +Const Int PROP_RADIATIONSUIT = 8; // (Deprecated) +Const Int PROP_ALLMAP = 9; // (Deprecated) +Const Int PROP_INFRARED = 10; // (Deprecated) +Const Int PROP_WEAPONLEVEL2 = 11; // (Deprecated) +Const Int PROP_FLIGHT = 12; // (Deprecated) +Const Int PROP_SPEED = 15; // (Deprecated) +Const Int PROP_BUDDHA = 16; + +// Line_SetBlocking +Const Int BLOCKF_CREATURES = 1; +Const Int BLOCKF_MONSTERS = 2; +Const Int BLOCKF_PLAYERS = 4; +Const Int BLOCKF_FLOATERS = 8; +Const Int BLOCKF_PROJECTILES = 16; +Const Int BLOCKF_EVERYTHING = 32; +Const Int BLOCKF_RAILING = 64; +Const Int BLOCKF_USE = 128; + +// Pointer constants, bitfield-enabled + +Const Int AAPTR_DEFAULT = 0; +Const Int AAPTR_NULL = 0x1; +Const Int AAPTR_TARGET = 0x2; +Const Int AAPTR_MASTER = 0x4; +Const Int AAPTR_TRACER = 0x8; + +Const Int AAPTR_PLAYER_GETTARGET = 0x10; +Const Int AAPTR_PLAYER_GETCONVERSATION = 0x20; + +Const Int AAPTR_PLAYER1 = 0x40; +Const Int AAPTR_PLAYER2 = 0x80; +Const Int AAPTR_PLAYER3 = 0x100; +Const Int AAPTR_PLAYER4 = 0x200; +Const Int AAPTR_PLAYER5 = 0x400; +Const Int AAPTR_PLAYER6 = 0x800; +Const Int AAPTR_PLAYER7 = 0x1000; +Const Int AAPTR_PLAYER8 = 0x2000; + +Const Int AAPTR_FRIENDPLAYER = 0x4000; +Const Int AAPTR_LINETARGET = 0x8000; + +// Pointer operation flags + +Const Int PTROP_UNSAFETARGET = 1; +Const Int PTROP_UNSAFEMASTER = 2; +Const Int PTROP_NOSAFEGUARDS = PTROP_UNSAFETARGET|PTROP_UNSAFEMASTER; + + +// Flags for A_Warp + +Const Int WARPF_ABSOLUTEOFFSET = 0x1; +Const Int WARPF_ABSOLUTEANGLE = 0x2; +Const Int WARPF_USECALLERANGLE = 0x4; +Const Int WARPF_NOCHECKPOSITION = 0x8; +Const Int WARPF_INTERPOLATE = 0x10; +Const Int WARPF_WARPINTERPOLATION = 0x20; +Const Int WARPF_COPYINTERPOLATION = 0x40; +Const Int WARPF_STOP = 0x80; +Const Int WARPF_TOFLOOR = 0x100; +Const Int WARPF_TESTONLY = 0x200; +Const Int WAPRF_ABSOLUTEPOSITION = 0x400; +Const Int WARPF_ABSOLUTEPOSITION = 0x400; + +// flags for A_SetPitch/SetAngle +const int SPF_FORCECLAMP = 1; +const int SPF_INTERPOLATE = 2; + + +// flags for A_CheckLOF + +enum +{ + CLOFF_NOAIM_VERT = 0x1, + CLOFF_NOAIM_HORZ = 0x2, + + CLOFF_JUMPENEMY = 0x4, + CLOFF_JUMPFRIEND = 0x8, + CLOFF_JUMPOBJECT = 0x10, + CLOFF_JUMPNONHOSTILE = 0x20, + + CLOFF_SKIPENEMY = 0x40, + CLOFF_SKIPFRIEND = 0x80, + CLOFF_SKIPOBJECT = 0x100, + CLOFF_SKIPNONHOSTILE = 0x200, + + CLOFF_MUSTBESHOOTABLE = 0x400, + + CLOFF_SKIPTARGET = 0x800, + CLOFF_ALLOWNULL = 0x1000, + CLOFF_CHECKPARTIAL = 0x2000, + + CLOFF_MUSTBEGHOST = 0x4000, + CLOFF_IGNOREGHOST = 0x8000, + + CLOFF_MUSTBESOLID = 0x10000, + CLOFF_BEYONDTARGET = 0x20000, + + CLOFF_FROMBASE = 0x40000, + CLOFF_MUL_HEIGHT = 0x80000, + CLOFF_MUL_WIDTH = 0x100000, + + CLOFF_JUMP_ON_MISS = 0x200000, + CLOFF_AIM_VERT_NOOFFSET = 0x400000, + + CLOFF_SETTARGET = 0x800000, + CLOFF_SETMASTER = 0x1000000, + CLOFF_SETTRACER = 0x2000000, + + CLOFF_SKIPOBSTACLES = CLOFF_SKIPENEMY|CLOFF_SKIPFRIEND|CLOFF_SKIPOBJECT|CLOFF_SKIPNONHOSTILE, + CLOFF_NOAIM = CLOFF_NOAIM_VERT|CLOFF_NOAIM_HORZ +}; + +// Flags for A_Kill (Master/Target/Tracer/Children/Siblings) series + +const int KILS_FOILINVUL = 1; +const int KILS_KILLMISSILES = 2; +const int KILS_NOMONSTERS = 4; +const int KILS_FOILBUDDHA = 8; + +// Flags for A_Damage (Master/Target/Tracer/Children/Siblings/Self) series +const int DMSS_FOILINVUL = 1; +const int DMSS_AFFECTARMOR = 2; +const int DMSS_KILL = 4; +const int DMSS_NOFACTOR = 8; +const int DMSS_FOILBUDDHA = 16; +const int DMSS_NOPROTECT = 32; + +// Flags for A_AlertMonsters +const int AMF_TARGETEMITTER = 1; +const int AMF_TARGETNONPLAYER = 2; +const int AMF_EMITFROMTARGET = 4; + +// Flags for A_Remove* +enum +{ + RMVF_MISSILES = 1 << 0, + RMVF_NOMONSTERS = 1 << 1, + RMVF_MISC = 1 << 2, + RMVF_EVERYTHING = 1 << 3, +}; + +// Flags for A_Fade* +enum +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; diff --git a/wadsrc/static/actors/doom/revenant.txt b/wadsrc/static/actors/doom/revenant.txt index 5f0a78691..5ef97789b 100644 --- a/wadsrc/static/actors/doom/revenant.txt +++ b/wadsrc/static/actors/doom/revenant.txt @@ -39,8 +39,8 @@ ACTOR Revenant 66 SKEL I 6 A_SkelFist Goto See Missile: - SKEL J 1 BRIGHT A_FaceTarget - SKEL J 9 BRIGHT A_FaceTarget + SKEL J 0 BRIGHT A_FaceTarget + SKEL J 10 BRIGHT A_FaceTarget SKEL K 10 A_SkelMissile SKEL K 10 A_FaceTarget Goto See diff --git a/wadsrc/static/actors/heretic/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 972fba5fb..ca85437e0 100644 --- a/wadsrc/static/actors/heretic/hereticweaps.txt +++ b/wadsrc/static/actors/heretic/hereticweaps.txt @@ -110,7 +110,7 @@ ACTOR StaffPuff2 // Gold wand ---------------------------------------------------------------- -ACTOR GoldWand : HereticWeapon +ACTOR GoldWand : HereticWeapon 9042 { Game Heretic +BLOODSPLATTER @@ -120,6 +120,7 @@ ACTOR GoldWand : HereticWeapon Weapon.AmmoType "GoldWandAmmo" Weapon.SisterWeapon "GoldWandPowered" Weapon.YAdjust 5 + Inventory.PickupMessage "$TXT_WPNGOLDWAND" Obituary "$OB_MPGOLDWAND" Tag "$TAG_GOLDWAND" @@ -127,6 +128,9 @@ ACTOR GoldWand : HereticWeapon States { + Spawn: + GWAN A -1 + Stop Ready: GWND A 1 A_WeaponReady Loop diff --git a/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index b6448ec71..4fa1d70bd 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -8,7 +8,7 @@ ACTOR Inventory native Inventory.PickupMessage "$TXT_DEFAULTPICKUPMSG" action native A_JumpIfNoAmmo(state label); - action native A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", float range = 0, float lifesteal = 0); + action native A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", float range = 0, float lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus"); action native A_FireBullets(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, float range = 0); action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, bool aimatangle = false, float pitch = 0); action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0, float range = 0, int duration = 0, float sparsity = 1.0, float driftspeed = 1.0, class spawnclass = "none", float spawnofs_z = 0); @@ -41,7 +41,7 @@ ACTOR Inventory native action native A_ClearReFire(); action native A_CheckReload(); action native A_GunFlash(state flash = "", int flags = 0); - action native A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float/*angle*/ spread_xy = 2.8125, float/*angle*/ spread_z = 0, float lifesteal = 0); + action native A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float/*angle*/ spread_xy = 2.8125, float/*angle*/ spread_z = 0, float lifesteal = 0, int lifestealmax = 0, class armorbonustype = "ArmorBonus"); action native A_CheckForReload(int counter, state label, bool dontincrement = false); action native A_ResetReloadCounter(); action native A_RestoreSpecialPosition(); @@ -271,6 +271,11 @@ ACTOR PowerFrightener : Powerup native Powerup.Duration -60 } +ACTOR PowerBuddha : Powerup native +{ + Powerup.Duration -60 +} + ACTOR PowerScanner : Powerup native { Powerup.Duration -80 diff --git a/wadsrc/static/actors/strife/crusader.txt b/wadsrc/static/actors/strife/crusader.txt index 7f7a0d0c5..eb1ceec6d 100644 --- a/wadsrc/static/actors/strife/crusader.txt +++ b/wadsrc/static/actors/strife/crusader.txt @@ -99,7 +99,7 @@ ACTOR CrusaderMissile Loop Death: SMIS A 0 Bright A_SetTranslucent(1,1) - SMIS A 5 Bright A_StopSoundEx("Voice") + SMIS A 5 Bright SMIS B 5 Bright SMIS C 4 Bright SMIS DEFG 2 Bright diff --git a/wadsrc/static/actors/strife/peasants.txt b/wadsrc/static/actors/strife/peasants.txt index e7bbdadcf..af12b75df 100644 --- a/wadsrc/static/actors/strife/peasants.txt +++ b/wadsrc/static/actors/strife/peasants.txt @@ -9,7 +9,7 @@ ACTOR Peasant : StrifeHumanoid Radius 20 Height 56 Monster - +FRIENDLY + +NEVERTARGET -COUNTKILL +NOSPLASHALERT +FLOORCLIP diff --git a/wadsrc/static/actors/strife/strifebishop.txt b/wadsrc/static/actors/strife/strifebishop.txt index dcedf7f58..6f38247fb 100644 --- a/wadsrc/static/actors/strife/strifebishop.txt +++ b/wadsrc/static/actors/strife/strifebishop.txt @@ -85,7 +85,7 @@ ACTOR BishopMissile Loop Death: SMIS A 0 Bright A_SetTranslucent(1,1) - SMIS A 0 Bright A_StopSoundEx("Voice") + SMIS A 0 Bright // State left for savegame compatibility SMIS A 5 Bright A_Explode(64,64,1,1) SMIS B 5 Bright SMIS C 4 Bright diff --git a/wadsrc/static/actors/strife/strifeweapons.txt b/wadsrc/static/actors/strife/strifeweapons.txt index deea01a52..3b256e226 100644 --- a/wadsrc/static/actors/strife/strifeweapons.txt +++ b/wadsrc/static/actors/strife/strifeweapons.txt @@ -390,7 +390,7 @@ ACTOR MiniMissile Loop Death: SMIS A 0 Bright A_SetTranslucent(1,1) - SMIS A 0 Bright A_StopSoundEx("Voice") + SMIS A 0 Bright // State left for savegame compatibility SMIS A 5 Bright A_Explode(64,64,1,1) SMIS B 5 Bright SMIS C 4 Bright diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index 1c2307ec0..d2bb85fd2 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -198,6 +198,14 @@ E2B5D1400279335811C1C1C0B437D9C8 // Deathknights of the Dark Citadel, map54 clearlineflags 1069 0x200 } +CBDE77E3ACB4B166D53C1812E5C72F54 // Hexen IWAD map04 +{ + setthingz 49 0 + setthingz 50 0 + setthingz 51 0 + setthingz 52 0 +} + 3F249EDD62A3A08F53A6C53CB4C7ABE5 // Artica 3 map01 { clearlinespecial 66 @@ -381,3 +389,8 @@ B9DFF13207EACAC675C71D82624D0007 // XtheaterIII map01 { DisablePushWindowCheck } + +A53AE580A4AF2B5D0B0893F86914781E // TNT: Evilution map31 +{ + setthingflags 470 2016 +} \ No newline at end of file diff --git a/wadsrc/static/iwadinfo.txt b/wadsrc/static/iwadinfo.txt index a2f8ef6c0..088e74466 100644 --- a/wadsrc/static/iwadinfo.txt +++ b/wadsrc/static/iwadinfo.txt @@ -1,5 +1,23 @@ // Must be sorted in identification order (easiest to recognize first!) +IWad +{ + Name = "The Adventures of Square" + Game = "Doom" + Config = "Square" + MustContain = "SQU-IWAD", "E1A1" + BannerColors = "ff ff ff", "80 00 80" +} + +IWad +{ + Name = "The Adventures of Square (Square-ware)" + Game = "Doom" + Config = "Square" + MustContain = "SQU-SWE1", "E1A1" + BannerColors = "ff ff ff", "80 00 80" +} + IWad { Name = "Harmony" @@ -361,4 +379,5 @@ Names "harm1.wad" "hacx.wad" "hacx2.wad" + "square1.pk3" } diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 4990cb417..a01a03559 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -292,6 +292,8 @@ STSTR_MUS = "Music Change"; STSTR_NOMUS = "IMPOSSIBLE SELECTION"; STSTR_DQDON = "Degreelessness Mode ON"; STSTR_DQDOFF = "Degreelessness Mode OFF"; +STSTR_DQD2ON = "Ultimate Degreelessness Mode ON"; +STSTR_DQD2OFF = "Ultimate Degreelessness Mode OFF"; STSTR_KFAADDED = "Very Happy Ammo Added"; STSTR_FAADDED = "Ammo (no keys) Added"; STSTR_NCON = "No Clipping Mode ON"; @@ -303,6 +305,8 @@ STSTR_CHOPPERS = "... doesn't suck - GM"; STSTR_CLEV = "Changing Level...\n"; TXT_BUDDHAON = "Buddha mode ON"; TXT_BUDDHAOFF = "Buddha mode OFF"; +TXT_BUDDHA2ON = "Ultimate Buddha Mode ON"; +TXT_BUDDHA2OFF = "Ultimate Buddha Mode OFF"; TXT_DEFAULTPICKUPMSG = "You got a pickup"; E1TEXT = @@ -836,6 +840,7 @@ SCORE_BONUS = "BONUS"; SCORE_COLOR = "COLOR"; SCORE_SECRET = "SECRET"; SCORE_NAME = "NAME"; +SCORE_DELAY = "DELAY(ms)"; SCORE_KILLS = "KILLS"; SCORE_FRAGS = "FRAGS"; SCORE_DEATHS = "DEATHS"; @@ -1241,6 +1246,7 @@ TXT_AMMOPHOENIXROD2 = "INFERNO ORB"; // Weapons +TXT_WPNGOLDWAND = "GOLD WAND"; TXT_WPNMACE = "FIREMACE"; TXT_WPNCROSSBOW = "ETHEREAL CROSSBOW"; TXT_WPNBLASTER = "DRAGON CLAW"; diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index 8e7886987..fc6379d01 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -1,3 +1,8 @@ +Gameinfo +{ + CheatKey = "maparrows/key.txt" + EasyKey = "maparrows/ravenkey.txt" +} Intermission Inter_Titlescreen { diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 324c94e6b..4e33029b4 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -341,6 +341,7 @@ OptionMenu "OptionsMenu" Submenu "Automap Options", "AutomapOptions" Submenu "HUD Options", "HUDOptions" Submenu "Miscellaneous Options", "MiscOptions" + Submenu "Network Options", "NetworkOptions" Submenu "Sound Options", "SoundOptions" Submenu "Display Options", "VideoOptions" Submenu "Set video mode", "VideoModeMenu" @@ -457,6 +458,7 @@ OptionMenu "CustomizeControls" Control "Run", "+speed" Control "Strafe", "+strafe" Control "Show Scoreboard", "+showscores" + Control "Toggle Scoreboard", "togglescoreboard" StaticText "" StaticText "Chat", 1 Control "Say", "messagemode" @@ -663,6 +665,7 @@ OptionMenu "VideoOptions" Slider "Screen size", "screenblocks", 3.0, 12.0, 1.0, 0 Slider "Brightness", "Gamma", 0.75, 3.0, 0.05, 2 Option "Vertical Sync", "vid_vsync", "OnOff" + Option "Rendering Interpolation", "cl_capfps", "OffOn" Option "Column render mode", "r_columnmethod", "ColumnMethods" StaticText " " @@ -683,6 +686,7 @@ OptionMenu "VideoOptions" Option "Blood Type", "cl_bloodtype", "BloodTypes" Option "Bullet Puff Type", "cl_pufftype", "PuffTypes" Slider "Number of particles", "r_maxparticles", 100, 10000, 100, 0 + Slider "Number of decals", "cl_maxdecals", 0, 10000, 100, 0 Option "Show player sprites", "r_drawplayersprites", "OnOff" Option "Death camera", "r_deathcamera", "OnOff" Option "Teleporter zoom", "telezoom", "OnOff" @@ -805,6 +809,13 @@ OptionValue "AltHUDTime" 9, "System" } +OptionValue "AltHUDLag" +{ + 0, "Off" + 1, "Netgames only" + 2, "Always" +} + OptionMenu "AltHUDOptions" { Title "Alternative HUD" @@ -819,6 +830,7 @@ OptionMenu "AltHUDOptions" Option "Show weapons", "hud_showweapons", "OnOff" Option "Show time", "hud_showtime", "AltHUDTime" Option "Time color", "hud_timecolor", "TextColors" + Option "Show network latency", "hud_showlag", "AltHUDLag" Slider "Red ammo display below %", "hud_ammo_red", 0, 100, 1, 0 Slider "Yellow ammo display below %", "hud_ammo_yellow", 0, 100, 1, 0 Slider "Red health display below", "hud_health_red", 0, 100, 1, 0 @@ -1088,6 +1100,7 @@ OptionMenu MessageOptions Title "MESSAGES" Option "Show messages", "show_messages", "OnOff" Option "Show obituaries", "show_obituaries", "OnOff" + Option "Show secret notifications", "cl_showsecretmessage", "OnOff" Option "Scale text in high res", "con_scaletext", "ScaleValues" Option "Minimum message level", "msg", "MessageLevels" Option "Center messages", "con_centernotify", "OnOff" @@ -1290,6 +1303,7 @@ OptionMenu "CompatibilityOptions" Option "Ignore Y offsets on masked midtextures", "compat_MASKEDMIDTEX", "YesNo" Option "Cannot travel straight NSEW", "compat_badangles", "YesNo" Option "Use Doom's floor motion behavior", "compat_floormove", "YesNo" + Option "Sounds stop when actor vanishes", "compat_soundcutoff", "YesNo" Class "CompatibilityMenu" } @@ -1466,6 +1480,7 @@ OptionValue OplCores 0, "MAME OPL2" 1, "DOSBox OPL3" 2, "Java OPL3" + 3, "Nuked OPL3" } OptionMenu AdvSoundOptions @@ -1591,3 +1606,30 @@ OptionMenu VideoModeMenu class VideoModeMenu } +/*======================================= + * + * Network options menu + * + *=======================================*/ + +OptionMenu NetworkOptions +{ + Title "NETWORK OPTIONS" + StaticText "Local options", 1 + Option "Movement prediction", "cl_noprediction", "OffOn" + Option "Predict line actions", "cl_predict_specials", "OnOff" + Slider "Prediction Lerp Scale", "cl_predict_lerpscale", 0.0, 0.5, 0.05 + Slider "Lerp Threshold", "cl_predict_lerpthreshold", 0.1, 16.0, 0.1 + StaticText " " + StaticText "Host options", 1 + Option "Extra Tics", "net_extratic", "ExtraTicMode" + Option "Latency balancing", "net_ticbalance", "OnOff" + +} + +OptionValue ExtraTicMode +{ + 0, "None" + 1, "1" + 2, "All unacknowledged" +} diff --git a/wadsrc/static/sndinfo.txt b/wadsrc/static/sndinfo.txt index 5508cd89b..be2ecd987 100644 --- a/wadsrc/static/sndinfo.txt +++ b/wadsrc/static/sndinfo.txt @@ -985,6 +985,7 @@ $alias menu/clear PlatformStop // Hexen does not have ripslop sound like Heretic misc/ripslop dsempty +misc/netnotch blddrp1 $alias intermission/cooptotal *death $alias intermission/nextstage DoorCloseLight diff --git a/wadsrc/static/sprites/GWANA0.png b/wadsrc/static/sprites/GWANA0.png new file mode 100644 index 000000000..f887f04c6 Binary files /dev/null and b/wadsrc/static/sprites/GWANA0.png differ diff --git a/wadsrc/static/sprites/pista0.png b/wadsrc/static/sprites/pista0.png index b3d9cd09a..9e3a4a052 100644 Binary files a/wadsrc/static/sprites/pista0.png and b/wadsrc/static/sprites/pista0.png differ diff --git a/wadsrc/static/xlat/base.txt b/wadsrc/static/xlat/base.txt index 11b9fd315..9607841d5 100644 --- a/wadsrc/static/xlat/base.txt +++ b/wadsrc/static/xlat/base.txt @@ -5,7 +5,7 @@ include "xlat/defines.i" 3 = WALK, Door_Close (tag, D_SLOW) 4 = WALK|MONST, Door_Raise (tag, D_SLOW, VDOORWAIT) 5 = WALK, Floor_RaiseToLowestCeiling (tag, F_SLOW) - 6 = WALK, Ceiling_CrushAndRaiseA (tag, C_NORMAL, C_NORMAL, 10) + 6 = WALK, Ceiling_CrushAndRaiseDist (tag, 8, C_NORMAL, 10) 7 = USE, Stairs_BuildUpDoom (tag, ST_SLOW, 8) 8 = WALK, Stairs_BuildUpDoom (tag, ST_SLOW, 8) 9 = USE, Floor_Donut (tag, DORATE, DORATE) @@ -24,7 +24,7 @@ include "xlat/defines.i" 22 = WALK, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) 23 = USE, Floor_LowerToLowest (tag, F_SLOW) 24 = SHOOT, Floor_RaiseToLowestCeiling (tag, F_SLOW) - 25 = WALK, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 10) + 25 = WALK, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 10) 26 = USE|REP, Door_LockedRaise (0, D_SLOW, VDOORWAIT, BCard | CardIsSkull, tag) 27 = USE|REP, Door_LockedRaise (0, D_SLOW, VDOORWAIT, YCard | CardIsSkull, tag) 28 = USE|REP, Door_LockedRaise (0, D_SLOW, VDOORWAIT, RCard | CardIsSkull, tag) @@ -72,11 +72,11 @@ include "xlat/defines.i" 70 = USE|REP, Floor_LowerToHighest (tag, F_FAST, 136) 71 = USE, Floor_LowerToHighest (tag, F_FAST, 136) 72 = WALK|REP, Ceiling_LowerAndCrush (tag, C_SLOW, 0, 2) - 73 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 10) + 73 = WALK|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 10) 74 = WALK|REP, Ceiling_CrushStop (tag) 75 = WALK|REP, Door_Close (tag, D_SLOW) 76 = WALK|REP, Door_CloseWaitOpen (tag, D_SLOW, 240) - 77 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_NORMAL, C_NORMAL, 10) + 77 = WALK|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_NORMAL, 10) 78 = USE|REP, Floor_TransferNumeric (tag) // <- BOOM special 79 = WALK|REP, Light_ChangeToValue (tag, 35) 80 = WALK|REP, Light_MaxNeighbor (tag) @@ -140,7 +140,7 @@ include "xlat/defines.i" 138 = USE|REP, Light_ChangeToValue (tag, 255) 139 = USE|REP, Light_ChangeToValue (tag, 35) 140 = USE, Floor_RaiseByValueTimes8 (tag, F_SLOW, 64) -141 = WALK, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 10) +141 = WALK, Ceiling_CrushAndRaiseSilentDist (tag, 8, C_SLOW, 10) /****** The following are all new to BOOM ******/ @@ -152,7 +152,7 @@ include "xlat/defines.i" 147 = WALK|REP, Floor_RaiseByValueTimes8 (tag, F_SLOW, 64) 148 = WALK|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 3) 149 = WALK|REP, Plat_UpByValueStayTx (tag, P_SLOW/2, 4) -150 = WALK|REP, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 10) +150 = WALK|REP, Ceiling_CrushAndRaiseSilentDist (tag, 8, C_SLOW, 10) 151 = WALK|REP, FloorAndCeiling_LowerRaise (tag, F_SLOW, C_SLOW) 152 = WALK|REP, Ceiling_LowerToFloor (tag, C_SLOW) 153 = WALK, Floor_TransferTrigger (tag) @@ -166,8 +166,8 @@ include "xlat/defines.i" 161 = USE, Floor_RaiseByValue (tag, F_SLOW, 24) 162 = USE, Plat_PerpetualRaiseLip (tag, P_SLOW, PLATWAIT, 0) 163 = USE, Plat_Stop (tag) -164 = USE, Ceiling_CrushAndRaiseA (tag, C_NORMAL, C_NORMAL, 10) -165 = USE, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 10) +164 = USE, Ceiling_CrushAndRaiseDist (tag, 8, C_NORMAL, 10) +165 = USE, Ceiling_CrushAndRaiseSilentDist (tag, 8, C_SLOW, 10) 166 = USE, FloorAndCeiling_LowerRaise (tag, F_SLOW, C_SLOW, 1998) 167 = USE, Ceiling_LowerAndCrush (tag, C_SLOW, 0, 2) 168 = USE, Ceiling_CrushStop (tag) @@ -185,9 +185,9 @@ include "xlat/defines.i" 180 = USE|REP, Floor_RaiseByValue (tag, F_SLOW, 24) 181 = USE|REP, Plat_PerpetualRaiseLip (tag, P_SLOW, PLATWAIT, 0) 182 = USE|REP, Plat_Stop (tag) -183 = USE|REP, Ceiling_CrushAndRaiseA (tag, C_NORMAL, C_NORMAL, 10) -184 = USE|REP, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 10) -185 = USE|REP, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 10) +183 = USE|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_NORMAL, 10) +184 = USE|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 10) +185 = USE|REP, Ceiling_CrushAndRaiseSilentDist (tag, 8, C_SLOW, 10) 186 = USE|REP, FloorAndCeiling_LowerRaise (tag, F_SLOW, C_SLOW, 1998) 187 = USE|REP, Ceiling_LowerAndCrush (tag, C_SLOW, 0, 2) 188 = USE|REP, Ceiling_CrushStop (tag) diff --git a/wadsrc/static/xlat/strife.txt b/wadsrc/static/xlat/strife.txt index 159933c80..755ace5ad 100644 --- a/wadsrc/static/xlat/strife.txt +++ b/wadsrc/static/xlat/strife.txt @@ -90,7 +90,7 @@ RetailOnly = 121 3 = WALK, Door_Close (tag, D_SLOW) 4 = WALK|MONST, Door_Raise (tag, D_SLOW, VDOORWAIT) 5 = WALK, Floor_RaiseToLowestCeiling (tag, F_SLOW) - 6 = WALK, Ceiling_CrushAndRaiseA (tag, C_FAST, C_FAST, 10) + 6 = WALK, Ceiling_CrushAndRaiseDist (tag, 8, C_FAST, 10) 8 = WALK, Stairs_BuildUpDoom (tag, ST_SLOW, 8) 10 = WALK|MONST, Plat_DownWaitUpStayLip (tag, P_FAST, PLATWAIT, 0) 12 = WALK, Light_MaxNeighbor (tag) @@ -99,7 +99,7 @@ RetailOnly = 121 17 = WALK, Light_StrobeDoom (tag, 5, 35) 19 = WALK, Floor_LowerToHighest (tag, F_SLOW, 128) 22 = WALK, Plat_RaiseAndStayTx0 (tag, P_SLOW/2) - 25 = WALK, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 0) + 25 = WALK, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 0) 30 = WALK, Floor_RaiseByTexture (tag, F_SLOW) 35 = WALK, Light_ChangeToValue (tag, 35) 36 = WALK, Floor_LowerToHighest (tag, F_FAST, 128) @@ -128,7 +128,7 @@ RetailOnly = 121 124 = WALK, Teleport_EndGame () 125 = MONWALK, Teleport (0, tag) 130 = WALK, Floor_RaiseToNearest (tag, F_FAST) -141 = WALK, Ceiling_CrushAndRaiseSilentA (tag, C_SLOW, C_SLOW, 0) +141 = WALK, Ceiling_CrushAndRaiseSilentDist (tag, 8, C_SLOW, 10) 174 = WALK, ACS_ExecuteAlways (0, 0, 174, tag) 183 = WALK, ACS_ExecuteAlways (0, 0, 183, tag) 178 = WALK, Generic_Stairs (tag, ST_FAST, 16, 0, 0) @@ -146,11 +146,11 @@ RetailOnly = 121 216 = WALK|REP, ACS_ExecuteAlways (0, 0, 216, tag) 90 = WALK|REP, Door_Raise (tag, D_SLOW, VDOORWAIT) 72 = WALK|REP, Ceiling_LowerAndCrushDist (tag, C_SLOW, 10) - 73 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_SLOW, C_SLOW, 0) + 73 = WALK|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_SLOW, 0) 74 = WALK|REP, Ceiling_CrushStop (tag) 75 = WALK|REP, Door_Close (tag, D_SLOW) 76 = WALK|REP, Door_CloseWaitOpen (tag, D_SLOW, 240) - 77 = WALK|REP, Ceiling_CrushAndRaiseA (tag, C_FAST, C_FAST, 10) + 77 = WALK|REP, Ceiling_CrushAndRaiseDist (tag, 8, C_FAST, 10) 79 = WALK|REP, Light_ChangeToValue (tag, 35) 80 = WALK|REP, Light_MaxNeighbor (tag) 81 = WALK|REP, Light_ChangeToValue (tag, 255) diff --git a/zdoom.vcproj b/zdoom.vcproj index cdc340984..07bea3116 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2632,6 +2632,14 @@ RelativePath=".\src\oplsynth\opl_mus_player.h" > + + + +