diff --git a/FindSDL2.cmake b/FindSDL2.cmake new file mode 100644 index 000000000..614426ccc --- /dev/null +++ b/FindSDL2.cmake @@ -0,0 +1,180 @@ +# Locate SDL2 library +# This module defines +# SDL2_LIBRARY, the name of the library to link against +# SDL2_FOUND, if false, do not try to link to SDL2 +# SDL2_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL2_BUILDING_LIBRARY +# If this is defined, then no SDL2_main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL2_LIBRARY variable. +# +# Don't forget to include SDL2main.h and SDL2main.m your project for the +# OS X framework based version. (Other versions link to -lSDL2main which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration +# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library +# (SDL2.dll, libsdl2.so, SDL2.framework, etc). +# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. +# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL2_LIBRARY +# variable, but when these values are unset, SDL2_LIBRARY does not get created. +# +# +# $SDL2DIR is an environment variable that would +# correspond to the ./configure --prefix=$SDL2DIR +# used in building SDL2. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL2 guidelines. +# Added a search for SDL2main which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL2_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL2/SDL.h to just SDL.h +# This needed to change because "proper" SDL2 convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL2/ (see FreeBSD). +# +# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake +# module with the minor edit of changing "SDL" to "SDL2" where necessary. This +# was not created for redistribution, and exists temporarily pending official +# SDL2 CMake modules. + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +FIND_PATH(SDL2_INCLUDE_DIR SDL.h + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES include/SDL2 include + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include/SDL2 + /usr/include/SDL2 + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) +#MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}") + +FIND_LIBRARY(SDL2_LIBRARY_TEMP + NAMES SDL2 + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt +) + +#MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}") + +IF(NOT SDL2_BUILDING_LIBRARY) + IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL2main for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + $ENV{SDL2DIR} + PATH_SUFFIXES lib64 lib + PATHS + /sw + /opt/local + /opt/csw + /opt + ) + ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL2_BUILDING_LIBRARY) + +# SDL2 may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +SET(SDL2_FOUND "NO") +IF(SDL2_LIBRARY_TEMP) + # For SDL2main + IF(NOT SDL2_BUILDING_LIBRARY) + IF(SDL2MAIN_LIBRARY) + SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(SDL2MAIN_LIBRARY) + ENDIF(NOT SDL2_BUILDING_LIBRARY) + + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") + + SET(SDL2_FOUND "YES") +ENDIF(SDL2_LIBRARY_TEMP) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 + REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) 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/output_sdl/CMakeLists.txt b/output_sdl/CMakeLists.txt index 1e7a9eb00..a601fb990 100644 --- a/output_sdl/CMakeLists.txt +++ b/output_sdl/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required( VERSION 2.4 ) if( NOT NO_FMOD AND FMOD_INCLUDE_DIR ) add_library( output_sdl MODULE output_sdl.c ) - include_directories( ${FMOD_INCLUDE_DIR} ${SDL_INCLUDE_DIR} ) + include_directories( ${FMOD_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} ) + target_link_libraries( output_sdl ${SDL2_LIBRARY} ) 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 ed14f5719..ae0feccf0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ endif( COMMAND cmake_policy ) include( CheckCXXSourceCompiles ) include( CheckFunctionExists ) include( CheckCXXCompilerFlag ) +include( CheckLibraryExists ) include( FindPkgConfig ) if( NOT APPLE ) @@ -26,6 +27,10 @@ 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" ) @@ -186,19 +191,6 @@ else( WIN32 ) 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 ) @@ -206,13 +198,12 @@ else( WIN32 ) 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}" ) + # Non-Windows version also needs SDL except native OS X backend + if( NOT APPLE OR NOT OSX_COCOA_BACKEND ) + find_package( SDL2 REQUIRED ) + include_directories( "${SDL2_INCLUDE_DIR}" ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${SDL2_LIBRARY}" ) + endif( NOT APPLE OR NOT OSX_COCOA_BACKEND ) find_path( FPU_CONTROL_DIR fpu_control.h ) if( FPU_CONTROL_DIR ) @@ -384,19 +375,19 @@ if( NOT NO_ASM ) set( ASM_FLAGS -f win32 -DWIN32 -i${CMAKE_CURRENT_SOURCE_DIR}/ ) endif( X64 ) endif( UNIX ) - if( WIN32 ) + if( WIN32 AND NOT X64 ) set( FIXRTEXT fixrtext ) - else( WIN32 ) + else( WIN32 AND NOT X64 ) set( FIXRTEXT "" ) - endif( WIN32 ) + 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 ) + if( WIN32 AND NOT X64 ) set( FIXRTEXT_${infile} COMMAND ${FIXRTEXT} "${ASM_OUTPUT_${infile}}" ) - else( WIN32 ) + else( WIN32 AND NOT X64 ) set( FIXRTEXT_${infile} COMMAND "" ) - endif( WIN32 ) + 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}" @@ -455,9 +446,10 @@ if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) endif( PROFILE ) set( REL_CXX_FLAGS "-fno-rtti" ) - if( NOT PROFILE ) + 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 ) + 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}" ) @@ -591,25 +583,36 @@ set( PLAT_WIN32_SOURCES win32/i_system.cpp win32/st_start.cpp win32/win32video.cpp ) +set( PLAT_POSIX_SOURCES + posix/i_cd.cpp + posix/i_movie.cpp + posix/i_steam.cpp + posix/i_system.cpp + posix/st_start.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 ) + posix/sdl/crashcatcher.c + posix/sdl/hardware.cpp + posix/sdl/i_gui.cpp + posix/sdl/i_input.cpp + posix/sdl/i_joystick.cpp + posix/sdl/i_main.cpp + posix/sdl/i_timer.cpp + posix/sdl/sdlvideo.cpp ) +set( PLAT_OSX_SOURCES + posix/osx/iwadpicker_cocoa.mm + posix/osx/zdoom.icns ) +set( PLAT_COCOA_SOURCES + posix/cocoa/critsec.cpp + posix/cocoa/i_input.mm + posix/cocoa/i_joystick.cpp + posix/cocoa/i_main.mm + posix/cocoa/i_timer.cpp + posix/cocoa/i_video.mm ) + if( WIN32 ) set( SYSTEM_SOURCES_DIR win32 ) set( SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ) - set( OTHER_SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ${PLAT_MAC_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ) if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) # CMake is not set up to compile and link rc files with GCC. :( @@ -620,15 +623,26 @@ if( WIN32 ) else( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) set( SYSTEM_SOURCES ${SYSTEM_SOURCES} win32/zdoom.rc ) endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) +elseif( APPLE ) + if( OSX_COCOA_BACKEND ) + set( SYSTEM_SOURCES_DIR posix posix/cocoa ) + set( SYSTEM_SOURCES ${PLAT_COCOA_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_SDL_SOURCES} ) + else( OSX_COCOA_BACKEND ) + set( SYSTEM_SOURCES_DIR posix posix/sdl ) + set( SYSTEM_SOURCES ${PLAT_SDL_SOURCES} ) + set( PLAT_OSX_SOURCES ${PLAT_OSX_SOURCES} posix/sdl/i_system.mm ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_COCOA_SOURCES} ) + endif( OSX_COCOA_BACKEND ) + + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} ${PLAT_POSIX_SOURCES} ${PLAT_OSX_SOURCES} "${FMOD_LIBRARY}" ) + + set_source_files_properties( posix/osx/zdoom.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) + set_source_files_properties( "${FMOD_LIBRARY}" PROPERTIES MACOSX_PACKAGE_LOCATION Frameworks ) 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 ) + set( SYSTEM_SOURCES_DIR posix posix/sdl ) + set( SYSTEM_SOURCES ${PLAT_POSIX_SOURCES} ${PLAT_SDL_SOURCES} ) + set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ) endif( WIN32 ) if( NOT ASM_SOURCES ) @@ -692,8 +706,14 @@ endif( DYN_FLUIDSYNTH ) # there's generally a new cpp for every header so this file will get changed if( WIN32 ) set( EXTRA_HEADER_DIRS win32/*.h ) +elseif( APPLE ) + if( OSX_COCOA_BACKEND ) + set( EXTRA_HEADER_DIRS posix/*.h posix/cocoa/*.h ) + else( OSX_COCOA_BACKEND ) + set( EXTRA_HEADER_DIRS posix/*.h posix/sdl/*.h ) + endif( OSX_COCOA_BACKEND ) else( WIN32 ) - set( EXTRA_HEADER_DIRS sdl/*.h ) + set( EXTRA_HEADER_DIRS posix/*.h posix/sdl/*.h ) endif( WIN32 ) file( GLOB HEADER_FILES ${EXTRA_HEADER_DIRS} @@ -708,9 +728,11 @@ file( GLOB HEADER_FILES menu/*.h oplsynth/*.h oplsynth/dosbox/*.h + posix/*.h + posix/cocoa/*.h + posix/sdl/*.h r_data/*.h resourcefiles/*.h - sdl/*.h sfmt/*.h sound/*.h textures/*.h @@ -813,7 +835,7 @@ set( NOT_COMPILED_SOURCE_FILES asm_x86_64/tmap3.s ) -add_executable( zdoom WIN32 +add_executable( zdoom WIN32 MACOSX_BUNDLE ${HEADER_FILES} ${NOT_COMPILED_SOURCE_FILES} __autostart.cpp @@ -1033,6 +1055,7 @@ add_executable( zdoom WIN32 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 @@ -1208,6 +1231,25 @@ if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) 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}/posix/osx/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/.+") @@ -1231,7 +1273,10 @@ source_group("Render Data\\Resource Sources" REGULAR_EXPRESSION "^${CMAKE_CURREN 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("POSIX Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/.+") +source_group("Cocoa Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/cocoa/.+") +source_group("OS X Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/osx/.+") +source_group("SDL Files" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/posix/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) diff --git a/src/actor.h b/src/actor.h index bf4b92457..0ab589107 100644 --- a/src/actor.h +++ b/src/actor.h @@ -341,6 +341,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 --- @@ -713,6 +727,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); @@ -768,6 +785,7 @@ public: // These also set CF_INTERPVIEW for players. void SetPitch(int p, bool interpolate); void SetAngle(angle_t ang, bool interpolate); + void SetRoll(angle_t roll, bool interpolate); const PClass *GetBloodType(int type = 0) const { @@ -850,7 +868,7 @@ 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; @@ -937,9 +955,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) @@ -965,9 +980,15 @@ public: FNameNoInit DamageType; FNameNoInit DamageTypeReceived; fixed_t DamageFactor; + fixed_t DamageMultiply; FNameNoInit PainType; FNameNoInit DeathType; + const PClass *TeleFogSourceType; + const PClass *TeleFogDestType; + int RipperLevel; + int RipLevelMin; + int RipLevelMax; 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 46e1aa54d..bfc88fb3c 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 26c8f99bd..05cac045b 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..25689a9be 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,31 +125,93 @@ 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; - int loaded_bots; int t_join; 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 +222,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..e136f3f29 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,16 @@ 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 (deathmatch) { - if (playeringame[i] && players[i].isbot) + for (i = 0; i < MAXPLAYERS; i++) { - if (deathmatch) + if (players[i].Bot != NULL) { getspawned.Push(players[i].userinfo.GetName()); } - CleanBotstuff (&players[i]); } - } - if (deathmatch) - { + wanted_botnum = botnum; } } @@ -240,12 +199,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,64 +219,69 @@ 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; + botinfo_t *thebot = botinfo; + int botshift = 0; if (name) { - thebot = botinfo; - // Check if exist or already in the game. while (thebot && stricmp (name, thebot->name)) + { + botshift++; thebot = thebot->next; + } 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; } } - else if (botnum < loaded_bots) - { - bool vacant = false; //Spawn a random bot from bots.cfg if no name given. - while (!vacant) - { - int rnum = (pr_botspawn() % loaded_bots); - thebot = botinfo; - while (rnum) - --rnum, thebot = thebot->next; - if (!thebot->inuse) - vacant = true; - } - } else { - Printf ("Couldn't spawn bot; no bot left in %s\n", BOTFILENAME); - return false; + //Spawn a random bot from bots.cfg if no name given. + TArray BotInfoAvailable; + + while (thebot) + { + if (thebot->inuse == BOTINUSE_No) + BotInfoAvailable.Push (thebot); + + thebot = thebot->next; + } + + if (BotInfoAvailable.Size () == 0) + { + Printf ("Couldn't spawn bot; no bot left in %s\n", BOTFILENAME); + return false; + } + + thebot = BotInfoAvailable[pr_botspawn() % BotInfoAvailable.Size ()]; + + botinfo_t *thebot2 = botinfo; + while (thebot2) + { + if (thebot == thebot2) + break; + + botshift++; + thebot2 = thebot2->next; + } } - waitingforspawn[playernumber] = true; + thebot->inuse = BOTINUSE_Waiting; Net_WriteByte (DEM_ADDBOT); - Net_WriteByte (playernumber); + Net_WriteByte (botshift); { //Set color. char concat[512]; @@ -335,52 +297,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 +405,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 +431,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 @@ -495,7 +487,6 @@ void FCajunMaster::ForgetBots () } botinfo = NULL; - loaded_bots = 0; } bool FCajunMaster::LoadBots () @@ -503,6 +494,7 @@ bool FCajunMaster::LoadBots () FScanner sc; FString tmp; bool gotteam = false; + int loaded_bots = 0; bglobal.ForgetBots (); tmp = M_GetCajunPath(BOTFILENAME); @@ -619,9 +611,9 @@ bool FCajunMaster::LoadBots () newinfo->next = bglobal.botinfo; newinfo->lastteam = TEAM_NONE; bglobal.botinfo = newinfo; - bglobal.loaded_bots++; + loaded_bots++; } - Printf ("%d bots read from %s\n", bglobal.loaded_bots, BOTFILENAME); + Printf ("%d bots read from %s\n", loaded_bots, BOTFILENAME); return true; } 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 cc7f087e6..34baeee9c 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 = static_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); const PClass *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 89005dbc7..9290c36c3 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -124,6 +124,15 @@ CCMD (god) Net_WriteByte (CHT_GOD); } +CCMD(god2) +{ + if (CheckCheatmode()) + return; + + Net_WriteByte(DEM_GENERICCHEAT); + Net_WriteByte(CHT_GOD2); +} + CCMD (iddqd) { if (CheckCheatmode ()) @@ -142,6 +151,15 @@ CCMD (buddha) Net_WriteByte(CHT_BUDDHA); } +CCMD(buddha2) +{ + if (CheckCheatmode()) + return; + + Net_WriteByte(DEM_GENERICCHEAT); + Net_WriteByte(CHT_BUDDHA2); +} + CCMD (notarget) { if (CheckCheatmode ()) @@ -882,8 +900,8 @@ CCMD(info) linetarget->SpawnHealth()); PrintMiscActorInfo(linetarget); } - else Printf("No target found. Info cannot find actors that have\ - the NOBLOCKMAP flag or have height/radius of 0.\n"); + else Printf("No target found. Info cannot find actors that have " + "the NOBLOCKMAP flag or have height/radius of 0.\n"); } //----------------------------------------------------------------------------- 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 65e017bdc..7a6748fdc 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -185,6 +185,9 @@ static const char *KeyConfCommands[] = "clearplayerclasses" }; +static TArray StoredStartupSets; +static bool RunningStoredStartups; + // CODE -------------------------------------------------------------------- IMPLEMENT_CLASS (DWaitingCommand) @@ -537,6 +540,18 @@ void ResetButtonStates () } } +void C_ExecStoredSets() +{ + assert(!RunningStoredStartups); + RunningStoredStartups = true; + for (unsigned i = 0; i < StoredStartupSets.Size(); ++i) + { + C_DoCommand(StoredStartupSets[i]); + } + StoredStartupSets.Clear(); + RunningStoredStartups = false; +} + void C_DoCommand (const char *cmd, int keynum) { FConsoleCommand *com; @@ -612,7 +627,22 @@ void C_DoCommand (const char *cmd, int keynum) if ( (com = FindNameInHashTable (Commands, beg, len)) ) { - if (gamestate != GS_STARTUP || ParsingKeyConf || + if (gamestate == GS_STARTUP && !RunningStoredStartups && + len == 3 && strnicmp(beg, "set", 3) == 0) + { + // Save setting of unknown cvars for later, in case a loaded wad has a + // CVARINFO that defines it. + FCommandLine args(beg); + if (args.argc() > 1 && FindCVar(args[1], NULL) == NULL) + { + StoredStartupSets.Push(beg); + } + else + { + com->Run(args, players[consoleplayer].mo, keynum); + } + } + else if (gamestate != GS_STARTUP || ParsingKeyConf || (len == 3 && strnicmp (beg, "set", 3) == 0) || (len == 7 && strnicmp (beg, "logfile", 7) == 0) || (len == 9 && strnicmp (beg, "unbindall", 9) == 0) || @@ -657,12 +687,15 @@ void C_DoCommand (const char *cmd, int keynum) } else { // We don't know how to handle this command - char cmdname[64]; - size_t minlen = MIN (len, 63); - - memcpy (cmdname, beg, minlen); - cmdname[len] = 0; - Printf ("Unknown command \"%s\"\n", cmdname); + if (gamestate == GS_STARTUP && !RunningStoredStartups) + { + // Save it for later, in case a CVARINFO defines it. + StoredStartupSets.Push(beg); + } + else + { + Printf ("Unknown command \"%.*s\"\n", len, beg); + } } } } diff --git a/src/c_dispatch.h b/src/c_dispatch.h index f4518608d..b494005c7 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -42,6 +42,7 @@ class APlayerPawn; extern bool CheckCheatmode (bool printmsg = true); void C_ExecCmdLineParams (); +void C_ExecStoredSets(); // Add commands to the console as if they were typed in. Can handle wait // and semicolon-separated commands. This function may modify the source diff --git a/src/compatibility.cpp b/src/compatibility.cpp index 351c2672a..8a4341b76 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -82,6 +82,7 @@ enum CP_SETWALLYSCALE, CP_SETTHINGZ, CP_SETTAG, + CP_SETTHINGFLAGS, }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -141,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 } }; @@ -317,6 +319,15 @@ void ParseCompatibility() 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(); @@ -539,6 +550,15 @@ void SetCompatibilityParams() 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..78efab9d0 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) { @@ -334,6 +343,10 @@ static void CT_ClearChatMessage () static void ShoveChatStr (const char *str, BYTE who) { + // Don't send empty messages + if (str == NULL || str[0] == '\0') + return; + FString substBuff; if (str[0] == '/' && diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index d14015ca9..41ad71472 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -2247,7 +2247,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 ab33bfaec..817d3b9ec 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(); } } @@ -2282,6 +2273,8 @@ void D_DoomMain (void) execFiles = Args->GatherFiles ("-exec"); D_MultiExec (execFiles, true); + C_ExecCmdLineParams (); // [RH] do all +set commands on the command line + CopyFiles(allwads, pwads); // Since this function will never leave we must delete this array here manually. @@ -2297,7 +2290,8 @@ void D_DoomMain (void) // Now that wads are loaded, define mod-specific cvars. ParseCVarInfo(); - C_ExecCmdLineParams (); // [RH] do all +set commands on the command line + // Try setting previously unknown cvars again, as a CVARINFO may have made them known. + C_ExecStoredSets(); // [RH] Initialize localizable strings. GStrings.LoadStrings (false); @@ -2587,6 +2581,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 PClass::ClearRuntimeData(); // clear all runtime generated class data diff --git a/src/d_net.cpp b/src/d_net.cpp index 9ba6eefa0..b3e70d410 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 { @@ -158,7 +185,7 @@ static struct TicSpecial size_t used[BACKUPTICS]; BYTE *streamptr; size_t streamoffs; - int specialsize; + size_t specialsize; int lastmaketic; bool okay; @@ -197,11 +224,11 @@ static struct TicSpecial } // Make more room for special commands. - void GetMoreSpace () + void GetMoreSpace (size_t needed) { int i; - specialsize <<= 1; + specialsize = MAX(specialsize * 2, needed + 30); DPrintf ("Expanding special size to %d\n", specialsize); @@ -213,8 +240,8 @@ static struct TicSpecial void CheckSpace (size_t needed) { - if (streamoffs >= specialsize - needed) - GetMoreSpace (); + if (streamoffs + needed >= specialsize) + GetMoreSpace (streamoffs + needed); streamoffs += needed; } @@ -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,87 +669,63 @@ void PlayerIsGone (int netnode, int netconsole) { int i; - for (i = netnode + 1; i < doomcom.numnodes; ++i) + if (nodeingame[netnode]) { - if (nodeingame[i]) - break; - } - if (i == doomcom.numnodes) - { - doomcom.numnodes = netnode; - } - - 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) + for (i = netnode + 1; i < doomcom.numnodes; ++i) { - players[ii].camera = players[ii].mo; - if (ii == consoleplayer && StatusBar != NULL) - { - StatusBar->AttachToPlayer (&players[ii]); - } + if (nodeingame[i]) + break; + } + if (i == doomcom.numnodes) + { + doomcom.numnodes = netnode; } - } - // [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(); + if (playeringame[netconsole]) + { + players[netconsole].playerstate = PST_GONE; } - players[netconsole].mo = NULL; - players[netconsole].camera = NULL; + nodeingame[netnode] = false; + nodejustleft[netnode] = false; } - // [RH] Let the scripts know the player left - FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, NULL, true, netconsole); + else if (nodejustleft[netnode]) // Packet Server + { + if (netnode + 1 == doomcom.numnodes) + { + doomcom.numnodes = netnode; + } + if (playeringame[netconsole]) + { + players[netconsole].playerstate = PST_GONE; + } + nodejustleft[netnode] = false; + } + else return; + 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]); } } @@ -741,7 +803,6 @@ void GetPackets (void) else { nodeingame[netnode] = false; - playeringame[netconsole] = false; nodejustleft[netnode] = true; } continue; @@ -772,7 +833,6 @@ void GetPackets (void) } if (netbuffer[0] & NCMD_QUITTERS) - { numplayers = netbuffer[k++]; for (int i = 0; i < numplayers; ++i) @@ -782,6 +842,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 +910,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 +951,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 +968,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 +978,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 +1057,6 @@ void NetUpdate (void) } } - UnadjustBots (); - if (singletics) return; // singletic update is synchronous @@ -1068,19 +1076,16 @@ void NetUpdate (void) if (consoleplayer == Net_Arbitrator) { - for (j = 0; j < MAXPLAYERS; j++) + if (NetMode == NET_PacketServer) { - if (playeringame[j]) + for (j = 0; j < MAXPLAYERS; j++) { - if (players[j].isbot || NetMode == NET_PacketServer) + if (playeringame[j] && players[j].Bot == NULL) { count++; } } - } - if (NetMode == NET_PacketServer) - { // The loop above added the local player to the count a second time, // and it also added the player being sent the packet to the count. count -= 2; @@ -1150,11 +1155,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 +1202,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,11 +1215,11 @@ void NetUpdate (void) netbuffer[0] |= NCMD_MULTI; netbuffer[k++] = count; - for (l = 1, j = 0; j < MAXPLAYERS; j++) + if (NetMode == NET_PacketServer) { - if (playeringame[j] && j != playerfornode[i] && j != consoleplayer) + for (l = 1, j = 0; j < MAXPLAYERS; j++) { - if (players[j].isbot || NetMode == NET_PacketServer) + if (playeringame[j] && players[j].Bot == NULL && j != playerfornode[i] && j != consoleplayer) { playerbytes[l++] = j; netbuffer[k++] = j; @@ -1227,7 +1243,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 +1258,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 +1308,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 +1383,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 +1465,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 +1532,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 +1668,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 +1690,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 +1734,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 +1865,9 @@ void TryRunTics (void) { C_Ticker(); M_Ticker(); + // Repredict the player for new buffered movement + P_UnPredictPlayer(); + P_PredictPlayer(&players[consoleplayer]); } return; } @@ -1844,6 +1903,9 @@ void TryRunTics (void) { C_Ticker (); M_Ticker (); + // Repredict the player for new buffered movement + P_UnPredictPlayer(); + P_PredictPlayer(&players[consoleplayer]); return; } } @@ -1857,6 +1919,7 @@ void TryRunTics (void) // run the count tics if (counts > 0) { + P_UnPredictPlayer(); while (counts--) { if (gametic > lowtic) @@ -1876,6 +1939,7 @@ void TryRunTics (void) NetUpdate (); // check for new console commands } + P_PredictPlayer(&players[consoleplayer]); S_UpdateSounds (players[consoleplayer].camera); // move positional sounds } } @@ -2130,10 +2194,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 +2650,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 +2777,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 +2818,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 f3f83e25e..6c5fbb6c5 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 f50db97b1..e5644e8cb 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -76,8 +76,6 @@ FPlayerColorSet *P_GetPlayerColorSet(FName classname, int setnum); void P_EnumPlayerColorSets(FName classname, TArray *out); const char *GetPrintableDisplayName(const PClass *cls); -class player_t; - class APlayerPawn : public AActor { DECLARE_CLASS (APlayerPawn, AActor) @@ -176,7 +174,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; @@ -206,6 +205,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; @@ -398,8 +399,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 @@ -444,47 +444,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 424e691e7..898bf7c5b 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -109,6 +109,7 @@ enum SAW_Flags SF_NOUSEAMMO = 16, SF_NOPULLIN = 32, SF_NOTURN = 64, + SF_STEALARMOR = 128, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) @@ -119,7 +120,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) AActor *linetarget; int actualdamage; - ACTION_PARAM_START(9); + ACTION_PARAM_START(11); ACTION_PARAM_SOUND(fullsound, 0); ACTION_PARAM_SOUND(hitsound, 1); ACTION_PARAM_INT(damage, 2); @@ -129,6 +130,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) ACTION_PARAM_ANGLE(Spread_XY, 6); ACTION_PARAM_ANGLE(Spread_Z, 7); ACTION_PARAM_FIXED(LifeSteal, 8); + ACTION_PARAM_INT(lifestealmax, 9); + ACTION_PARAM_CLASS(armorbonustype, 10); if (NULL == (player = self->player)) { @@ -184,7 +187,31 @@ 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) armorbonustype = PClass::FindClass("ArmorBonus"); + + if (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); @@ -546,6 +573,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FireBFG) P_SpawnPlayerMissile (self, 0, 0, 0, PClass::FindClass("BFGBall"), self->angle, NULL, NULL, !!(dmflags2 & DF2_NO_FREEAIMBFG)); } + // // A_BFGSpray // Spawn a BFG explosion on every monster in view @@ -559,14 +587,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BFGSpray) AActor *thingToHit; AActor *linetarget; - ACTION_PARAM_START(3); + ACTION_PARAM_START(7); ACTION_PARAM_CLASS(spraytype, 0); ACTION_PARAM_INT(numrays, 1); ACTION_PARAM_INT(damagecnt, 2); + ACTION_PARAM_ANGLE(angle, 3); + ACTION_PARAM_FIXED(distance, 4); + ACTION_PARAM_ANGLE(vrange, 5); + ACTION_PARAM_INT(defdamage, 6); if (spraytype == NULL) spraytype = PClass::FindClass("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) @@ -575,10 +610,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; @@ -589,14 +624,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); } } diff --git a/src/g_game.cpp b/src/g_game.cpp index 2058c2df0..ba53254c2 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; @@ -874,7 +879,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; } @@ -1013,10 +1018,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); + } } } @@ -1041,6 +1052,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 (); @@ -1114,6 +1129,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]) @@ -1133,13 +1151,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 @@ -1149,7 +1167,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) @@ -1331,10 +1349,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; const PClass *cls; FString log; + DBot *Bot; //Added by MC: p = &players[player]; @@ -1344,18 +1362,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; @@ -1366,8 +1385,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; @@ -1375,15 +1393,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; + } } // @@ -1658,6 +1680,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; @@ -2076,6 +2148,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 (); @@ -2085,6 +2160,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; } @@ -2161,6 +2237,7 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio } insave = false; + I_FreezeTime(false); } @@ -2373,6 +2450,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]); @@ -2555,7 +2642,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 d8c1fd285..dba16b622 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_level.cpp b/src/g_level.cpp index d84a48776..713847b05 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; @@ -1112,6 +1164,8 @@ void G_StartTravel () } } } + + bglobal.StartTravel (); } //========================================================================== @@ -1183,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) { @@ -1200,6 +1263,8 @@ void G_FinishTravel () } } } + + bglobal.FinishTravel (); } //========================================================================== @@ -1222,6 +1287,7 @@ void G_InitLevelLocals () level.teamdamage = teamdamage; level.flags = 0; level.flags2 = 0; + level.flags3 = 0; info = FindLevelInfo (level.MapName); @@ -1275,6 +1341,7 @@ void G_InitLevelLocals () level.clusterflags = clus ? clus->flags : 0; level.flags |= info->flags; level.flags2 |= info->flags2; + level.flags3 |= info->flags3; level.levelnum = info->levelnum; level.Music = info->Music; level.musicorder = info->musicorder; diff --git a/src/g_level.h b/src/g_level.h index 05290f48b..6e07b0c74 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -52,7 +52,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; @@ -216,6 +216,9 @@ enum ELevelFlags LEVEL2_ENDGAME = 0x20000000, // This is an epilogue level that cannot be quit. LEVEL2_NOAUTOSAVEHINT = 0x40000000, // tell the game that an autosave for this level does not need to be kept LEVEL2_FORGETSTATE = 0x80000000, // forget this map's state in a hub + + // More flags! + LEVEL3_FORCEFAKECONTRAST = 0x00000001, // forces fake contrast even with fog enabled }; @@ -285,6 +288,8 @@ struct level_info_t int sucktime; DWORD flags; DWORD flags2; + DWORD flags3; + FString Music; FString LevelName; SBYTE WallVertLight, WallHorizLight; @@ -398,6 +403,7 @@ struct FLevelLocals DWORD flags; DWORD flags2; + DWORD flags3; DWORD fadeto; // The color the palette fades to (usually black) DWORD outsidefog; // The fog for sectors with sky ceilings diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index 5a451dfaa..c44f81ab2 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -1190,6 +1190,9 @@ enum EMIType MITYPE_SETFLAG2, MITYPE_CLRFLAG2, MITYPE_SCFLAGS2, + MITYPE_SETFLAG3, + MITYPE_CLRFLAG3, + MITYPE_SCFLAGS3, MITYPE_COMPATFLAG, }; @@ -1275,6 +1278,7 @@ MapFlagHandlers[] = { "rememberstate", MITYPE_CLRFLAG2, LEVEL2_FORGETSTATE, 0 }, { "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 }, { "spawnwithweaponraised", MITYPE_SETFLAG2, LEVEL2_PRERAISEWEAPON, 0 }, + { "forcefakecontrast", MITYPE_SETFLAG3, LEVEL3_FORCEFAKECONTRAST, 0 }, { "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes { "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX, 0 }, { "compat_stairs", MITYPE_COMPATFLAG, COMPATF_STAIRINDEX, 0 }, @@ -1306,6 +1310,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 }, @@ -1371,6 +1376,20 @@ void FMapInfoParser::ParseMapDefinition(level_info_t &info) info.flags2 = (info.flags2 & handler->data2) | handler->data1; break; + case MITYPE_SETFLAG3: + info.flags3 |= handler->data1; + info.flags3 |= handler->data2; + break; + + case MITYPE_CLRFLAG3: + info.flags3 &= ~handler->data1; + info.flags3 |= handler->data2; + break; + + case MITYPE_SCFLAGS3: + info.flags3 = (info.flags3 & handler->data2) | handler->data1; + break; + case MITYPE_COMPATFLAG: { int set = 1; diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index a745197c2..0f343e523 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -516,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 8891c8674..21006776e 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 0efd64ab8..4e1823f5a 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 2b3a6040d..e3433f12e 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,19 +528,26 @@ bool P_MorphedDeath(AActor *actor, AActor **morphed, int *morphedstyle, int *mor if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) { AMorphedMonster *fakeme = static_cast(actor); - if ((fakeme->UnmorphTime) && - (fakeme->MorphStyle & MORPH_UNDOBYDEATH) && - (fakeme->UnmorphedMe)) + AActor *realme = fakeme->UnmorphedMe; + if (realme != NULL) { - AActor *realme = fakeme->UnmorphedMe; - int realstyle = fakeme->MorphStyle; - int realhealth = fakeme->health; - if (P_UndoMonsterMorph(fakeme, !!(fakeme->MorphStyle & MORPH_UNDOBYDEATHFORCED))) + if ((fakeme->UnmorphTime) && + (fakeme->MorphStyle & MORPH_UNDOBYDEATH)) { - *morphed = realme; - *morphedstyle = realstyle; - *morphedhealth = realhealth; - return true; + int realstyle = fakeme->MorphStyle; + int realhealth = fakeme->health; + if (P_UndoMonsterMorph(fakeme, !!(fakeme->MorphStyle & MORPH_UNDOBYDEATHFORCED))) + { + *morphed = realme; + *morphedstyle = realstyle; + *morphedhealth = realhealth; + return true; + } + } + if (realme->flags4 & MF4_BOSSDEATH) + { + realme->health = 0; // make sure that A_BossDeath considers it dead. + CALL_ACTION(A_BossDeath, realme); } } fakeme->flags3 |= MF3_STAYMORPHED; // moved here from AMorphedMonster::Die() diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index acb6224a3..1aab998a9 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -499,7 +499,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)); } //=========================================================================== @@ -1024,8 +1024,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 72548776a..8616393e7 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -135,6 +135,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 }; diff --git a/src/g_shared/sbar_mugshot.cpp b/src/g_shared/sbar_mugshot.cpp index 96dd07265..b8f419ef0 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.cpp b/src/g_shared/sbarinfo.cpp index a738a5aad..7a11a2865 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1215,7 +1215,11 @@ public: if(Scaled) { if(cx != 0 || cy != 0) + { screen->VirtualToRealCoords(dcx, dcy, tmp, tmp, script->resW, script->resH, true); + if (cx == 0) dcx = 0; + if (cy == 0) dcy = 0; + } if(cr != 0 || cb != 0 || clearDontDraw) screen->VirtualToRealCoords(dcr, dcb, tmp, tmp, script->resW, script->resH, true); screen->VirtualToRealCoords(dx, dy, w, h, script->resW, script->resH, true); diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp index 2daeff7a8..110675791 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 @@ -917,6 +920,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 = 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); +} + //--------------------------------------------------------------------------- // @@ -982,6 +1030,7 @@ void DrawHUD() if (idmypos) DrawCoordinates(CPlayer); DrawTime(); + DrawLatency(); } else { diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index 2a18801a1..d8e113824 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -1123,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; } diff --git a/src/g_strife/a_alienspectres.cpp b/src/g_strife/a_alienspectres.cpp index 4626891bb..2b9f499e9 100644 --- a/src/g_strife/a_alienspectres.cpp +++ b/src/g_strife/a_alienspectres.cpp @@ -106,7 +106,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; @@ -180,7 +180,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 6d34182f4..7ef0b24a1 100644 --- a/src/g_strife/a_crusader.cpp +++ b/src/g_strife/a_crusader.cpp @@ -80,6 +80,6 @@ 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); } } diff --git a/src/g_strife/a_loremaster.cpp b/src/g_strife/a_loremaster.cpp index 3c42ea181..d720b7b9b 100644 --- a/src/g_strife/a_loremaster.cpp +++ b/src/g_strife/a_loremaster.cpp @@ -23,7 +23,7 @@ int ALoreShot::DoSpecialDamage (AActor *target, int damage, FName damagetype) { FVector3 thrust; - if (this->target != NULL) + if (this->target != NULL && !(this->target->flags7 & MF7_DONTTHRUST)) { thrust.X = float(this->target->x - target->x); thrust.Y = float(this->target->y - target->y); diff --git a/src/g_strife/a_strifestuff.cpp b/src/g_strife/a_strifestuff.cpp index d51b618b7..95b0030a0 100644 --- a/src/g_strife/a_strifestuff.cpp +++ b/src/g_strife/a_strifestuff.cpp @@ -548,7 +548,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 89659e61d..b69222d36 100644 --- a/src/g_strife/strife_sbar.cpp +++ b/src/g_strife/strife_sbar.cpp @@ -601,7 +601,7 @@ private: screen->DrawText(SmallFont2, CR_UNTRANSLATED, left + 210 * xscale, top + 8 * yscale, buff, DTA_CleanNoMove, true, TAG_DONE); - if (CPlayer->LogText != NULL) + if (CPlayer->LogText.IsNotEmpty()) { FBrokenLines *lines = V_BreakLines(SmallFont2, 272, CPlayer->LogText); for (i = 0; lines[i].Width >= 0; ++i) 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 5d9d48bea..e48b79980 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/menu/listmenu.cpp b/src/menu/listmenu.cpp index 53b4fee22..402c1d5e9 100644 --- a/src/menu/listmenu.cpp +++ b/src/menu/listmenu.cpp @@ -300,7 +300,12 @@ void FListMenuItem::DrawSelector(int xofs, int yofs, FTextureID tex) if ((DMenu::MenuTime%8) < 6) { screen->DrawText(ConFont, OptionSettings.mFontColorSelection, - mXpos + xofs, mYpos + yofs, "\xd", DTA_Clean, true, TAG_DONE); + (mXpos + xofs - 160) * CleanXfac + screen->GetWidth() / 2, + (mYpos + yofs - 100) * CleanYfac + screen->GetHeight() / 2, + "\xd", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); } } else diff --git a/src/menu/videomenu.cpp b/src/menu/videomenu.cpp index 1d1d02383..5e44bbfd4 100644 --- a/src/menu/videomenu.cpp +++ b/src/menu/videomenu.cpp @@ -247,8 +247,12 @@ static void BuildModesList (int hiwidth, int hiheight, int hi_bits) if (Video != NULL) { while ((haveMode = Video->NextMode (&width, &height, &letterbox)) && - (ratiomatch >= 0 && CheckRatio (width, height) != ratiomatch)) + ratiomatch >= 0) { + int ratio; + CheckRatio (width, height, &ratio); + if (ratio == ratiomatch) + break; } } diff --git a/src/namedef.h b/src/namedef.h index 32251560b..89d819f42 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -298,6 +298,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) @@ -418,6 +421,7 @@ xx(Passuse) xx(Repeatspecial) xx(Conversation) xx(Locknumber) +xx(Midtex3dimpassible) xx(Playercross) xx(Playeruse) @@ -597,4 +601,4 @@ xx(NeverSwitchOnPickup) xx(MoveBob) xx(StillBob) xx(PlayerClass) -xx(Wi_NoAutostartMap) +xx(Wi_NoAutostartMap) 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..fc35409dc 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,29 @@ 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)); + + IGNORE_FORMAT_PRE + 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":""); + IGNORE_FORMAT_POST + } + } +} 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 fd41d6e51..4b9a8bbab 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -1581,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); @@ -1873,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); } } @@ -2058,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]) { @@ -2351,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) { @@ -2367,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) { @@ -2385,7 +2393,7 @@ void FBehavior::LoadScriptsDirectory () int size = LittleLong(scripts.dw[1]); if (size >= 6) { - int script_num = LittleShort(scripts.w[4]); + int script_num = LittleShort(scripts.sw[4]); ScriptPtr *ptr = const_cast(FindScript(script_num)); if (ptr != NULL) { @@ -2681,7 +2689,7 @@ BYTE *FBehavior::FindChunk (DWORD id) const { return chunk; } - chunk += ((DWORD *)chunk)[1] + 8; + chunk += LittleLong(((DWORD *)chunk)[1]) + 8; } return NULL; } @@ -2689,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; } @@ -2881,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); @@ -2969,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); @@ -3624,6 +3681,7 @@ enum APROP_AttackZOffset = 40, APROP_StencilColor = 41, APROP_Friction = 42, + APROP_DamageMultiplier=43, }; // These are needed for ACS's APROP_RenderStyle @@ -3813,6 +3871,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); @@ -3843,6 +3905,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; @@ -3880,6 +3946,7 @@ int DLevelScript::GetActorProperty (int tid, int property, const SDWORD *stack, case APROP_Speed: return actor->Speed; case APROP_Damage: return actor->Damage; // Should this call GetMissileDamage() instead? 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. @@ -4224,7 +4291,7 @@ int DLevelScript::DoClassifyActor(int tid) { classify |= ACTOR_VOODOODOLL; } - if (actor->player->isbot) + if (actor->player->Bot != NULL) { classify |= ACTOR_BOT; } @@ -4369,6 +4436,13 @@ enum EACSFunctions ACSF_GetArmorInfo, ACSF_DropInventory, ACSF_PickActor, + ACSF_IsPointerEqual, + ACSF_CanRaiseActor, + ACSF_SetActorTeleFog, // 86 + ACSF_SwapActorTeleFog, + ACSF_SetActorRoll, + ACSF_ChangeActorRoll, + ACSF_GetActorRoll, /* Zandronum's - these must be skipped when we reach 99! -100:ResetMap(0), @@ -4680,6 +4754,103 @@ static void SetActorPitch(AActor *activator, int tid, int angle, bool interpolat } } +static void SetActorRoll(AActor *activator, int tid, int angle, bool interpolate) +{ + if (tid == 0) + { + if (activator != NULL) + { + activator->SetRoll(angle << 16, interpolate); + } + } + else + { + FActorIterator iterator(tid); + AActor *actor; + + while ((actor = iterator.Next())) + { + actor->SetRoll(angle << 16, interpolate); + } + } +} + +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. + const PClass *check; + if (tid == 0) + { + if (activator != NULL) + { + check = PClass::FindClass(telefogsrc); + if (check == NULL || !stricmp(telefogsrc, "none") || !stricmp(telefogsrc, "null")) + activator->TeleFogSourceType = NULL; + else + activator->TeleFogSourceType = check; + + check = PClass::FindClass(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::FindClass(telefogsrc); + if (check == NULL || !stricmp(telefogsrc, "none") || !stricmp(telefogsrc, "null")) + actor->TeleFogSourceType = NULL; + else + actor->TeleFogSourceType = check; + + check = PClass::FindClass(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 + { + const PClass *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. + const PClass *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) @@ -5593,7 +5764,18 @@ 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) { @@ -5613,19 +5795,87 @@ doplaysound: if (funcIndex == ACSF_PlayActorSound) 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; } - pickedActor->RemoveFromHash(); - pickedActor->tid = args[4]; - pickedActor->AddToHash(); - + 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; + + // [Nash] Actor roll functions. Let's roll! + case ACSF_SetActorRoll: + actor = SingleActorFromTID(args[0], activator); + if (actor != NULL) + { + actor->SetRoll(args[1] << 16, false); + } + return 0; + + case ACSF_ChangeActorRoll: + if (argCount >= 2) + { + SetActorRoll(activator, args[0], args[1], argCount > 2 ? !!args[2] : false); + } + break; + + case ACSF_GetActorRoll: + actor = SingleActorFromTID(args[0], activator); + return actor != NULL? actor->roll >> 16 : 0; + default: break; } @@ -7242,7 +7492,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])); @@ -7290,22 +7540,9 @@ scriptwait: break; case PCD_PRINTBINARY: -#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6)))) || defined(__clang__) -#define HAS_DIAGNOSTIC_PRAGMA -#endif -#ifdef HAS_DIAGNOSTIC_PRAGMA -#pragma GCC diagnostic push -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wformat-invalid-specifier" -#else -#pragma GCC diagnostic ignored "-Wformat=" -#endif -#pragma GCC diagnostic ignored "-Wformat-extra-args" -#endif + IGNORE_FORMAT_PRE work.AppendFormat ("%B", STACK(1)); -#ifdef HAS_DIAGNOSTIC_PRAGMA -#pragma GCC diagnostic pop -#endif + IGNORE_FORMAT_POST --sp; break; @@ -7608,13 +7845,6 @@ scriptwait: AddToConsole (-1, consolecolor); AddToConsole (-1, work); AddToConsole (-1, bar); - if (Logfile) - { - fputs (logbar, Logfile); - fputs (work, Logfile); - fputs (logbar, Logfile); - fflush (Logfile); - } } } } @@ -8590,7 +8820,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_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 00e2a3dc2..ef6136fdc 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; @@ -3161,7 +3161,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Die) ACTION_PARAM_START(1); ACTION_PARAM_NAME(damagetype, 0); - 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); } // @@ -3275,13 +3278,13 @@ DEFINE_ACTION_FUNCTION(AActor, A_BossDeath) { 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; } } @@ -3290,11 +3293,11 @@ DEFINE_ACTION_FUNCTION(AActor, A_BossDeath) 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_enemy.h b/src/p_enemy.h index ff40c7003..f5cc387dd 100644 --- a/src/p_enemy.h +++ b/src/p_enemy.h @@ -72,7 +72,8 @@ DECLARE_ACTION(A_FreezeDeathChunks) DECLARE_ACTION(A_BossDeath) void A_Chase(AActor *self); -void A_FaceTarget (AActor *actor, angle_t max_turn = 0, angle_t max_pitch = ANGLE_270); +void A_FaceTarget(AActor *actor, angle_t max_turn = 0, angle_t max_pitch = ANGLE_270); +void A_Face(AActor *self, AActor *other, angle_t max_turn = 0, angle_t max_pitch = ANGLE_270); bool A_RaiseMobj (AActor *, fixed_t speed); bool A_SinkMobj (AActor *, fixed_t speed); 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 c3be908ea..36a68281e 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); @@ -593,7 +593,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags) // even those caused by other monsters players[0].killcount++; } - + if (player) { // [RH] Death messages @@ -606,19 +606,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; } } @@ -925,6 +925,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. @@ -938,12 +943,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) { @@ -966,13 +981,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 @@ -980,11 +1002,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) @@ -1010,6 +1042,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) { @@ -1032,38 +1065,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; } } @@ -1071,32 +1115,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()->ActorInfo->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; @@ -1133,11 +1182,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) @@ -1146,8 +1194,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; @@ -1193,11 +1240,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 @@ -1210,22 +1256,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; } @@ -1233,7 +1298,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 @@ -1255,9 +1320,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; @@ -1290,7 +1355,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; } } @@ -1317,43 +1385,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); @@ -1368,6 +1446,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)) @@ -1383,8 +1471,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) @@ -1421,6 +1509,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) { @@ -1459,6 +1548,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; } @@ -1577,7 +1674,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; } @@ -1628,8 +1725,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; } @@ -1673,7 +1770,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; } @@ -1711,7 +1808,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 14bcd430c..c170cacd3 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) @@ -519,19 +519,19 @@ FUNC(LS_Generic_Stairs) FUNC(LS_Pillar_Build) // Pillar_Build (tag, speed, height) { - return EV_DoPillar (DPillar::pillarBuild, arg0, SPEED(arg1), arg2*FRACUNIT, 0, -1, false); + return EV_DoPillar (DPillar::pillarBuild, ln, arg0, SPEED(arg1), arg2*FRACUNIT, 0, -1, false); } FUNC(LS_Pillar_BuildAndCrush) // Pillar_BuildAndCrush (tag, speed, height, crush, crushtype) { - return EV_DoPillar (DPillar::pillarBuild, arg0, SPEED(arg1), arg2*FRACUNIT, 0, arg3, CRUSHTYPE(arg4)); + return EV_DoPillar (DPillar::pillarBuild, ln, arg0, SPEED(arg1), arg2*FRACUNIT, 0, arg3, CRUSHTYPE(arg4)); } FUNC(LS_Pillar_Open) // Pillar_Open (tag, speed, f_height, c_height) { - return EV_DoPillar (DPillar::pillarOpen, arg0, SPEED(arg1), arg2*FRACUNIT, arg3*FRACUNIT, -1, false); + return EV_DoPillar (DPillar::pillarOpen, ln, arg0, SPEED(arg1), arg2*FRACUNIT, arg3*FRACUNIT, -1, false); } FUNC(LS_Ceiling_LowerByValue) @@ -1510,7 +1510,7 @@ FUNC(LS_Thing_Raise) if (arg0==0) { - ok = P_Thing_Raise (it); + ok = P_Thing_Raise (it,NULL); } else { @@ -1518,7 +1518,7 @@ FUNC(LS_Thing_Raise) while ( (target = iterator.Next ()) ) { - ok |= P_Thing_Raise(target); + ok |= P_Thing_Raise(target,NULL); } } return ok; @@ -1764,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; } diff --git a/src/p_local.h b/src/p_local.h index 939018734..fe7958807 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 @@ -132,7 +133,7 @@ enum EPuffFlags PF_NORANDOMZ = 16 }; -AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags = 0); +AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags = 0, AActor *vict = NULL); void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage, AActor *originator); void P_BloodSplatter (fixed_t x, fixed_t y, fixed_t z, AActor *originator); void P_BloodSplatter2 (fixed_t x, fixed_t y, fixed_t z, AActor *originator); @@ -170,7 +171,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); const PClass *P_GetSpawnableType(int spawnnum); // @@ -557,6 +559,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 2274e7571..68d0b21f0 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, @@ -825,6 +887,20 @@ bool PIT_CheckLine(line_t *ld, const FBoundingBox &box, FCheckPosition &tm) return true; } + +//========================================================================== +// +// Isolated to keep the code readable and fix the logic +// +//========================================================================== + +static bool CheckRipLevel(AActor *victim, AActor *projectile) +{ + if (victim->RipLevelMin > 0 && projectile->RipperLevel < victim->RipLevelMin) return false; + if (victim->RipLevelMax > 0 && projectile->RipperLevel > victim->RipLevelMax) return false; + return true; +} + //========================================================================== // // PIT_CheckThing @@ -1109,24 +1185,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; + } } } } @@ -1142,7 +1221,8 @@ bool PIT_CheckThing(AActor *thing, FCheckPosition &tm) { return true; } - if (tm.DoRipping && !(thing->flags5 & MF5_DONTRIP)) + + if ((tm.DoRipping && !(thing->flags5 & MF5_DONTRIP)) && CheckRipLevel(thing, tm.thing)) { if (!(tm.thing->flags6 & MF6_NOBOSSRIP) || !(thing->flags2 & MF2_BOSS)) { @@ -1218,6 +1298,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 +1668,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 +2007,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 +2071,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 +2081,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 +2111,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 +2245,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 +3008,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 +3765,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) || @@ -3674,7 +3776,7 @@ AActor *P_LineAttack(AActor *t1, angle_t angle, fixed_t distance, puffFlags |= PF_HITTHINGBLEED; // We must pass the unreplaced puff type here - puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING); + puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING, trace.Actor); } // Allow puffs to inflict poison damage, so that hitscans can poison, too. @@ -3686,7 +3788,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 +3797,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 @@ -4069,7 +4171,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; @@ -4102,7 +4204,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); } @@ -4117,13 +4219,18 @@ void P_RailAttack(AActor *source, int damage, int offset_xy, fixed_t offset_z, i } if (spawnpuff) { - P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags); + P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags, 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); @@ -4175,7 +4282,7 @@ CVAR(Float, chase_dist, 90.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) void P_AimCamera(AActor *t1, fixed_t &CameraX, fixed_t &CameraY, fixed_t &CameraZ, sector_t *&CameraSector) { - fixed_t distance = (fixed_t)(chase_dist * FRACUNIT); + fixed_t distance = (fixed_t)(clamp(chase_dist, 0, 30000) * FRACUNIT); angle_t angle = (t1->angle - ANG180) >> ANGLETOFINESHIFT; angle_t pitch = (angle_t)(t1->pitch) >> ANGLETOFINESHIFT; FTraceResults trace; @@ -4185,7 +4292,7 @@ void P_AimCamera(AActor *t1, fixed_t &CameraX, fixed_t &CameraY, fixed_t &Camera vy = FixedMul(finecosine[pitch], finesine[angle]); vz = finesine[pitch]; - sz = t1->z - t1->floorclip + t1->height + (fixed_t)(chase_height * FRACUNIT); + sz = t1->z - t1->floorclip + t1->height + (fixed_t)(clamp(chase_height, -1000, 1000) * FRACUNIT); if (Trace(t1->x, t1->y, sz, t1->Sector, vx, vy, vz, distance, 0, 0, NULL, trace) && @@ -4661,7 +4768,7 @@ void P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, int bo points *= thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT) / (double)FRACUNIT; // points and bombdamage should be the same sign - if ((points * bombdamage) > 0 && P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY)) + if (((bombspot->flags7 & MF7_CAUSEPAIN) || (points * bombdamage) > 0) && P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY)) { // OK to damage; target is in direct path double velz; double thrust; @@ -4670,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)) @@ -4682,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 } } } @@ -5014,6 +5125,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) @@ -5022,7 +5135,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_mobj.cpp b/src/p_mobj.cpp index d035e0117..fea80f868 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) @@ -252,9 +251,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 @@ -309,8 +312,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) @@ -322,6 +333,17 @@ void AActor::Serialize (FArchive &arc) { arc << FriendPlayer; } + if (SaveVersion >= 4517) + { + arc << TeleFogSourceType + << TeleFogDestType; + } + if (SaveVersion >= 4518) + { + arc << RipperLevel + << RipLevelMin + << RipLevelMax; + } { FString tagstr; @@ -1163,7 +1185,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; } @@ -1191,17 +1213,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. @@ -1276,8 +1295,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) { @@ -1310,11 +1338,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); - } } } @@ -1651,6 +1674,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; @@ -1940,20 +1964,55 @@ 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; + bool dontReflect = (mo->AdjustReflectionAngle(BlockingMobj, angle)); + // Change angle for deflection/reflection - // 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; + if (!dontReflect) + { + bool tg = (mo->target != NULL); + bool blockingtg = (BlockingMobj->target != NULL); + if (BlockingMobj->flags7 & MF7_AIMREFLECT && (tg || blockingtg)) + { + AActor *origin; + if (tg) + origin = mo->target; + else if (blockingtg) + origin = BlockingMobj->target; + + float speed = (float)(mo->Speed); + //dest->x - source->x + FVector3 velocity(origin->x - mo->x, origin->y - mo->y, (origin->z + (origin->height/2)) - mo->z); + velocity.Resize(speed); + angle = mo->angle >> ANGLETOFINESHIFT; + mo->velx = (fixed_t)(velocity.X); + mo->vely = (fixed_t)(velocity.Y); + mo->velz = (fixed_t)(velocity.Z); + /* + 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; + } + } + else + { + goto explode; + } + } if (mo->flags2 & MF2_SEEKERMISSILE) { mo->tracer = mo->target; @@ -2634,18 +2693,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 (); @@ -2884,9 +2935,12 @@ 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; + if (thing->flags7 & MF7_MIRRORREFLECT) + angle += ANGLE_180; // Change angle for reflection - if (thing->flags4&MF4_SHIELDREFLECT) + else if (thing->flags4&MF4_SHIELDREFLECT) { // Shield reflection (from the Centaur if (abs (angle - thing->angle)>>24 > 45) @@ -2909,6 +2963,13 @@ bool AActor::AdjustReflectionAngle (AActor *thing, angle_t &angle) else angle -= ANG45; } + else if (thing->flags7 & MF7_AIMREFLECT) + { + if (this->target != NULL) + A_Face(this, this->target); + else if (thing->target != NULL) + A_Face(this, thing->target); + } else angle += ANGLE_1 * ((pr_reflect()%16)-8); return false; @@ -3016,6 +3077,18 @@ void AActor::SetAngle(angle_t ang, bool interpolate) } } +void AActor::SetRoll(angle_t r, bool interpolate) +{ + if (r != roll) + { + roll = r; + if (player != NULL && interpolate) + { + player->cheats |= CF_INTERPVIEW; + } + } +} + // // P_MobjThinker // @@ -3112,7 +3185,7 @@ void AActor::Tick () special2++; } //Added by MC: Freeze mode. - if (bglobal.freeze && !(player && !player->isbot)) + if (bglobal.freeze && !(player && player->Bot == NULL)) { return; } @@ -3226,40 +3299,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; } } } @@ -3866,6 +3939,7 @@ AActor *AActor::StaticSpawn (const PClass *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->Speed = actor->GetClass()->Meta.GetMetaFixed(AMETA_FastSpeed, actor->Speed); + actor->DamageMultiply = FRACUNIT; // set subsector and/or block links @@ -4246,6 +4320,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) @@ -4286,12 +4366,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) { @@ -4305,14 +4388,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)); @@ -4358,9 +4441,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)) { @@ -4432,7 +4512,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 } @@ -4868,7 +4949,7 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) // P_SpawnPuff // -AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags) +AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown, int flags, AActor *vict) { AActor *puff; @@ -4878,9 +4959,24 @@ AActor *P_SpawnPuff (AActor *source, const PClass *pufftype, fixed_t x, fixed_t puff = Spawn (pufftype, x, y, z, ALLOW_REPLACE); if (puff == NULL) return NULL; + if ((puff->flags4 & MF4_RANDOMIZE) && puff->tics > 0) + { + puff->tics -= pr_spawnpuff() & 3; + if (puff->tics < 1) + puff->tics = 1; + } + + //Moved puff creation and target/master/tracer setting to here. + if (puff && vict) + { + if (puff->flags7 & MF7_HITTARGET) puff->target = vict; + if (puff->flags7 & MF7_HITMASTER) puff->master = vict; + if (puff->flags7 & MF7_HITTRACER) puff->tracer = vict; + } // [BB] If the puff came from a player, set the target of the puff to this player. if ( puff && (puff->flags5 & MF5_PUFFGETSOWNER)) puff->target = source; + if (source != NULL) puff->angle = R_PointToAngle2(x, y, source->x, source->y); @@ -5011,10 +5107,11 @@ void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage, AAc cls = 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); } @@ -5850,22 +5947,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; } @@ -5961,7 +6077,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_pillar.cpp b/src/p_pillar.cpp index 1f77c7c7b..809542949 100644 --- a/src/p_pillar.cpp +++ b/src/p_pillar.cpp @@ -212,16 +212,28 @@ DPillar::DPillar (sector_t *sector, EPillar type, fixed_t speed, } } -bool EV_DoPillar (DPillar::EPillar type, int tag, fixed_t speed, fixed_t height, - fixed_t height2, int crush, bool hexencrush) +bool EV_DoPillar (DPillar::EPillar type, line_t *line, int tag, + fixed_t speed, fixed_t height, fixed_t height2, int crush, bool hexencrush) { + int secnum; + sector_t *sec; bool rtn = false; - int secnum = -1; - while ((secnum = P_FindSectorFromTag (tag, secnum)) >= 0) + // check if a manual trigger; if so do just the sector on the backside + if (tag == 0) { - sector_t *sec = §ors[secnum]; + if (!line || !(sec = line->backsector)) + return rtn; + secnum = (int)(sec-sectors); + goto manual_pillar; + } + secnum = -1; + while (tag && (secnum = P_FindSectorFromTag (tag, secnum)) >= 0) + { + sec = §ors[secnum]; + +manual_pillar: if (sec->PlaneMoving(sector_t::floor) || sec->PlaneMoving(sector_t::ceiling)) continue; diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index e477d9751..6e699a41b 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -227,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; } @@ -263,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; } @@ -298,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; } @@ -329,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..119d92a9c 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 @@ -567,7 +566,7 @@ void P_SerializePolyobjs (FArchive &arc) I_Error ("UnarchivePolyobjs: Invalid polyobj tag"); } arc << angle; - po->RotatePolyobj (angle); + po->RotatePolyobj (angle, true); arc << deltaX << deltaY << po->interpolation; deltaX -= po->StartSpot.x; deltaY -= po->StartSpot.y; diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index 699350839..904c76c1b 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -965,7 +965,7 @@ int side_t::GetLightLevel (bool foggy, int baselight, bool noabsolute, int *pfak *pfakecontrast = 0; } - if (!foggy) // Don't do relative lighting in foggy sectors + if (!foggy || level.flags3 & LEVEL3_FORCEFAKECONTRAST) // Don't do relative lighting in foggy sectors { if (!(Flags & WALLF_NOFAKECONTRAST) && r_fakecontrast != 0) { diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 00ed322c0..676191555 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, const PClass *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 c9bb6eded..0d7ef4cff 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); @@ -514,8 +515,8 @@ private: DPillar (); }; -bool EV_DoPillar (DPillar::EPillar type, int tag, fixed_t speed, fixed_t height, - fixed_t height2, int crush, bool hexencrush); +bool EV_DoPillar (DPillar::EPillar type, line_t *line, int tag, + fixed_t speed, fixed_t height, fixed_t height2, int crush, bool hexencrush); // // P_DOORS @@ -902,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 cfe455716..3ba0ae14f 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 @@ -699,6 +700,10 @@ FState *FStateDefinitions::ResolveGotoLabel (AActor *actor, const PClass *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 50470d9eb..40e432af0 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -74,19 +74,22 @@ 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) { - const PClass *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 +99,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 +186,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 +232,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 +282,7 @@ static AActor *SelectTeleDest (int tid, int tag) } else { - if (count != 1) + if (count != 1 && !norandom) { count = 1 + (pr_teleport() % count); } @@ -323,6 +329,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 +341,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 +350,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 +398,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_terrain.h b/src/p_terrain.h index d2933b687..9f00d07bc 100644 --- a/src/p_terrain.h +++ b/src/p_terrain.h @@ -49,11 +49,13 @@ public: WORD operator [](FTextureID tex) const { + if ((unsigned)tex.GetIndex() >= Types.Size()) return DefaultTerrainType; WORD type = Types[tex.GetIndex()]; return type == 0xffff? DefaultTerrainType : type; } WORD operator [](int texnum) const { + if ((unsigned)texnum >= Types.Size()) return DefaultTerrainType; WORD type = Types[texnum]; return type == 0xffff? DefaultTerrainType : type; } diff --git a/src/p_things.cpp b/src/p_things.cpp index e62c65ae7..a8a34c384 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 fbe24cc83..e91766cf0 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; @@ -239,7 +263,6 @@ player_t::player_t() health(0), inventorytics(0), CurrentPlayerClass(0), - backpack(0), fragcount(0), lastkilltime(0), multicount(0), @@ -272,28 +295,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), @@ -312,7 +314,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) @@ -340,7 +341,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; @@ -381,30 +381,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; @@ -446,12 +424,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++; @@ -466,12 +439,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); @@ -720,10 +688,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); } @@ -1237,9 +1205,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. @@ -1400,7 +1365,7 @@ void APlayerPawn::Die (AActor *source, AActor *inflictor, int dmgflags) weap->SpawnState != ::GetDefault()->SpawnState) { item = P_DropItem (this, weap->GetClass(), -1, 256); - if (item != NULL) + if (item != NULL && item->IsKindOf(RUNTIME_CLASS(AWeapon))) { if (weap->AmmoGive1 && weap->Ammo1) { @@ -2155,7 +2120,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; @@ -2625,7 +2590,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 (); } @@ -2637,6 +2603,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; @@ -2664,8 +2653,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; @@ -2722,11 +2711,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; + } + } } } @@ -2739,9 +2784,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 @@ -2749,7 +2797,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. @@ -2854,6 +2902,9 @@ void P_UnPredictPlayer () } block = block->NextBlock; } + + act->InvSel = InvSel; + player->inventorytics = inventorytics; } } @@ -2884,9 +2935,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 @@ -2915,9 +2970,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; @@ -3000,33 +3063,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/po_man.cpp b/src/po_man.cpp index e19acc53a..c6e273a0f 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -1051,7 +1051,7 @@ static void RotatePt (int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_ // //========================================================================== -bool FPolyObj::RotatePolyobj (angle_t angle) +bool FPolyObj::RotatePolyobj (angle_t angle, bool fromsave) { int an; bool blocked; @@ -1073,23 +1073,27 @@ bool FPolyObj::RotatePolyobj (angle_t angle) validcount++; UpdateBBox(); - for(unsigned i=0;i < Sidedefs.Size(); i++) + // If we are loading a savegame we do not really want to damage actors and be blocked by them. This can also cause crashes when trying to damage incompletely deserialized player pawns. + if (!fromsave) { - if (CheckMobjBlocking(Sidedefs[i])) + for (unsigned i = 0; i < Sidedefs.Size(); i++) { - blocked = true; + if (CheckMobjBlocking(Sidedefs[i])) + { + blocked = true; + } } - } - if (blocked) - { - for(unsigned i=0;i < Vertices.Size(); i++) + if (blocked) { - Vertices[i]->x = PrevPts[i].x; - Vertices[i]->y = PrevPts[i].y; + for(unsigned i=0;i < Vertices.Size(); i++) + { + Vertices[i]->x = PrevPts[i].x; + Vertices[i]->y = PrevPts[i].y; + } + UpdateBBox(); + LinkPolyobj(); + return false; } - UpdateBBox(); - LinkPolyobj(); - return false; } this->angle += angle; LinkPolyobj(); diff --git a/src/po_man.h b/src/po_man.h index 70ab9d360..9e81cc266 100644 --- a/src/po_man.h +++ b/src/po_man.h @@ -74,7 +74,7 @@ struct FPolyObj int GetMirror(); bool MovePolyobj (int x, int y, bool force = false); - bool RotatePolyobj (angle_t angle); + bool RotatePolyobj (angle_t angle, bool fromsave = false); void ClosestPoint(fixed_t fx, fixed_t fy, fixed_t &ox, fixed_t &oy, side_t **side) const; void LinkPolyobj (); void RecalcActorFloorCeil(FBoundingBox bounds) const; diff --git a/src/posix/cocoa/critsec.cpp b/src/posix/cocoa/critsec.cpp new file mode 100644 index 000000000..cbf112491 --- /dev/null +++ b/src/posix/cocoa/critsec.cpp @@ -0,0 +1,62 @@ +/* + ** critsec.cpp + ** + **--------------------------------------------------------------------------- + ** 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. + **--------------------------------------------------------------------------- + ** + */ + +#include "critsec.h" + +// TODO: add error handling + +FCriticalSection::FCriticalSection() +{ + pthread_mutexattr_t attributes; + pthread_mutexattr_init(&attributes); + pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&m_mutex, &attributes); + + pthread_mutexattr_destroy(&attributes); +} + +FCriticalSection::~FCriticalSection() +{ + pthread_mutex_destroy(&m_mutex); +} + +void FCriticalSection::Enter() +{ + pthread_mutex_lock(&m_mutex); +} + +void FCriticalSection::Leave() +{ + pthread_mutex_unlock(&m_mutex); +} diff --git a/src/posix/cocoa/critsec.h b/src/posix/cocoa/critsec.h new file mode 100644 index 000000000..7940dfe32 --- /dev/null +++ b/src/posix/cocoa/critsec.h @@ -0,0 +1,53 @@ +/* + ** critsec.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 CRITSEC_H +#define CRITSEC_H + +#include + +class FCriticalSection +{ +public: + FCriticalSection(); + ~FCriticalSection(); + + void Enter(); + void Leave(); + +private: + pthread_mutex_t m_mutex; + +}; + +#endif diff --git a/src/posix/cocoa/i_common.h b/src/posix/cocoa/i_common.h new file mode 100644 index 000000000..081466e87 --- /dev/null +++ b/src/posix/cocoa/i_common.h @@ -0,0 +1,168 @@ +/* + ** i_common.h + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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. + **--------------------------------------------------------------------------- + ** + */ + +#import + + +struct RenderBufferOptions +{ + float pixelScale; + + float shiftX; + float shiftY; + + float width; + float height; + + bool dirty; +}; + +extern RenderBufferOptions rbOpts; + + +inline bool I_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; +} + +void I_ProcessEvent(NSEvent* event); + +void I_ProcessJoysticks(); + +NSSize I_GetContentViewSize(const NSWindow* window); +void I_SetMainWindowVisible(bool visible); +void I_SetNativeMouse(bool wantNative); + + +// 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 +}; + +#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 diff --git a/src/posix/cocoa/i_input.mm b/src/posix/cocoa/i_input.mm new file mode 100644 index 000000000..5e0c5f1c8 --- /dev/null +++ b/src/posix/cocoa/i_input.mm @@ -0,0 +1,740 @@ +/* + ** i_input.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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 "i_common.h" + +#import + +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "d_event.h" +#include "d_gui.h" +#include "dikeys.h" +#include "doomdef.h" +#include "doomstat.h" +#include "v_video.h" + +#undef Class + + +EXTERN_CVAR(Int, m_use_mouse) + +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; + } +} + + +extern int paused, chatmodeon; +extern constate_e ConsoleState; + +bool GUICapture; + + +namespace +{ + +// TODO: remove this magic! +size_t s_skipMouseMoves; + + +// --------------------------------------------------------------------------- + + +void CheckGUICapture() +{ + const bool wantCapture = (MENU_Off == menuactive) + ? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon) + : (MENU_On == menuactive || MENU_OnNoPause == menuactive); + + 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 CheckNativeMouse() +{ + const 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); + } + + I_SetNativeMouse(wantNative); +} + +} // unnamed namespace + + +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() +{ +} + +void I_SetNativeMouse(bool wantNative) +{ + static bool nativeMouse = true; + + if (wantNative != nativeMouse) + { + nativeMouse = wantNative; + + if (!wantNative) + { + CenterCursor(); + } + + CGAssociateMouseAndMouseCursorPosition(wantNative); + + if (wantNative) + { + [NSCursor unhide]; + } + else + { + [NSCursor hide]; + } + } +} + + +// --------------------------------------------------------------------------- + + +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); +} + + +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 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 = I_IsHiDPISupported() + ? [view convertPointToBacking:windowPos] + : [view convertPoint:windowPos fromView:nil]; + + const CGFloat frameHeight = I_GetContentViewSize(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(posX); + outEvent->data2 = static_cast(posY); +} + +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 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); + } + } +} + +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); +} + + +void ProcessMouseMoveEvent(NSEvent* theEvent) +{ + if (GUICapture) + { + ProcessMouseMoveInMenu(theEvent); + } + else + { + ProcessMouseMoveInGame(theEvent); + } +} + +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 = MIN(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8)); + + D_PostEvent(&event); + } +} + +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); +} + +} // unnamed namespace + + +void I_ProcessEvent(NSEvent* event) +{ + 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; + } +} diff --git a/src/posix/cocoa/i_joystick.cpp b/src/posix/cocoa/i_joystick.cpp new file mode 100644 index 000000000..9b9487cf6 --- /dev/null +++ b/src/posix/cocoa/i_joystick.cpp @@ -0,0 +1,1252 @@ +/* + ** i_joystick.cpp + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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 "d_event.h" +#include "doomdef.h" +#include "i_system.h" +#include "m_argv.h" +#include "m_joy.h" +#include "templates.h" +#include "v_text.h" + + +EXTERN_CVAR(Bool, joy_axespolling) + + +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(io_object_t 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(); + + void UseAxesPolling(bool axesPolling); + + io_object_t* GetNotificationPtr(); + +private: + IOHIDDeviceInterface** m_interface; + IOHIDQueueInterface** m_queue; + + FString m_name; + FString m_identifier; + + float m_sensitivity; + + struct AnalogAxis + { + IOHIDElementCookie cookie; + + char name[64]; + + float value; + + int32_t minValue; + int32_t maxValue; + + float deadZone; + float defaultDeadZone; + float sensitivity; + float defaultSensitivity; + + EJoyAxis gameAxis; + EJoyAxis defaultGameAxis; + + AnalogAxis() + { + memset(this, 0, sizeof *this); + } + }; + + TArray m_axes; + + struct DigitalButton + { + IOHIDElementCookie cookie; + int32_t value; + + DigitalButton(const IOHIDElementCookie cookie) + : cookie(cookie) + { } + }; + + TArray m_buttons; + TArray m_POVs; + + bool m_useAxesPolling; + + io_object_t m_notification; + + + static const float DEFAULT_DEADZONE; + static const float DEFAULT_SENSITIVITY; + + void ProcessAxes(); + bool ProcessAxis (const IOHIDEventStruct& event); + bool ProcessButton(const IOHIDEventStruct& event); + bool ProcessPOV (const IOHIDEventStruct& event); + + void GatherDeviceInfo(io_object_t device, CFDictionaryRef properties); + + static void GatherElementsHandler(const void* value, void* parameter); + void GatherCollectionElements(CFDictionaryRef properties); + + void AddAxis(CFDictionaryRef element); + void AddButton(CFDictionaryRef element); + void AddPOV(CFDictionaryRef element); + + void AddToQueue(IOHIDElementCookie cookie); + void RemoveFromQueue(IOHIDElementCookie cookie); +}; + + +const float IOKitJoystick::DEFAULT_DEADZONE = 0.25f; +const float IOKitJoystick::DEFAULT_SENSITIVITY = 1.0f; + + +IOHIDDeviceInterface** CreateDeviceInterface(const io_object_t device) +{ + IOCFPlugInInterface** plugInInterface = NULL; + SInt32 score = 0; + + const kern_return_t pluginResult = IOCreatePlugInInterfaceForService(device, + kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); + + IOHIDDeviceInterface** interface = NULL; + + if (KERN_SUCCESS == pluginResult) + { + // Call a method of the intermediate plug-in to create the device interface + + const HRESULT queryResult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), reinterpret_cast(&interface)); + + IODestroyPlugInInterface(plugInInterface); // [?] or maybe (*plugInInterface)->Release(plugInInterface); + + if (S_OK == queryResult) + { + const IOReturn openResult = (*interface)->open(interface, 0); + + if (kIOReturnSuccess != openResult) + { + (*interface)->Release(interface); + + Printf(TEXTCOLOR_RED "IOHIDDeviceInterface::open() failed with code 0x%08X\n", openResult); + return NULL; + } + } + else + { + Printf(TEXTCOLOR_RED "IOCFPlugInInterface::QueryInterface() failed with code 0x%08X\n", + static_cast(queryResult)); + return NULL; + } + } + else + { + Printf(TEXTCOLOR_RED "IOCreatePlugInInterfaceForService() failed with code %i\n", pluginResult); + return NULL; + } + + return interface; +} + +IOHIDQueueInterface** CreateDeviceQueue(IOHIDDeviceInterface** const interface) +{ + if (NULL == interface) + { + return NULL; + } + + IOHIDQueueInterface** queue = (*interface)->allocQueue(interface); + + if (NULL == queue) + { + Printf(TEXTCOLOR_RED "IOHIDDeviceInterface::allocQueue() failed\n"); + return NULL; + } + + static const uint32_t QUEUE_FLAGS = 0; + static const uint32_t QUEUE_DEPTH = 0; + + const IOReturn queueResult = (*queue)->create(queue, QUEUE_FLAGS, QUEUE_DEPTH); + + if (kIOReturnSuccess != queueResult) + { + (*queue)->Release(queue); + + Printf(TEXTCOLOR_RED "IOHIDQueueInterface::create() failed with code 0x%08X\n", queueResult); + return NULL; + } + + return queue; +} + + +IOKitJoystick::IOKitJoystick(const io_object_t device) +: m_interface(CreateDeviceInterface(device)) +, m_queue(CreateDeviceQueue(m_interface)) +, m_sensitivity(DEFAULT_SENSITIVITY) +, m_useAxesPolling(true) +, m_notification(0) +{ + if (NULL == m_interface || NULL == m_queue) + { + return; + } + + CFMutableDictionaryRef properties = NULL; + const kern_return_t propertiesResult = + IORegistryEntryCreateCFProperties(device, &properties, kCFAllocatorDefault, kNilOptions); + + if (KERN_SUCCESS != propertiesResult || NULL == properties) + { + Printf(TEXTCOLOR_RED "IORegistryEntryCreateCFProperties() failed with code %i\n", propertiesResult); + return; + } + + GatherDeviceInfo(device, properties); + GatherCollectionElements(properties); + + CFRelease(properties); + + UseAxesPolling(joy_axespolling); + + (*m_queue)->start(m_queue); + + SetDefaultConfig(); +} + +IOKitJoystick::~IOKitJoystick() +{ + M_SaveJoystickConfig(this); + + if (0 != m_notification) + { + IOObjectRelease(m_notification); + } + + if (NULL != m_queue) + { + (*m_queue)->stop(m_queue); + (*m_queue)->dispose(m_queue); + (*m_queue)->Release(m_queue); + } + + if (NULL != m_interface) + { + (*m_interface)->close(m_interface); + (*m_interface)->Release(m_interface); + } +} + + +FString IOKitJoystick::GetName() +{ + return m_name; +} + + +float IOKitJoystick::GetSensitivity() +{ + return m_sensitivity; +} + +void IOKitJoystick::SetSensitivity(float scale) +{ + m_sensitivity = scale; +} + + +int IOKitJoystick::GetNumAxes() +{ + return static_cast(m_axes.Size()); +} + +#define IS_AXIS_VALID (static_cast(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 = 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() +{ + return m_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::UseAxesPolling(const bool axesPolling) +{ + m_useAxesPolling = axesPolling; + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + AnalogAxis& axis = m_axes[i]; + + if (m_useAxesPolling) + { + RemoveFromQueue(axis.cookie); + } + else + { + AddToQueue(axis.cookie); + } + } +} + + +void IOKitJoystick::Update() +{ + if (NULL == m_queue) + { + return; + } + + IOHIDEventStruct event = { }; + AbsoluteTime zeroTime = { }; + + const IOReturn eventResult = (*m_queue)->getNextEvent(m_queue, &event, zeroTime, 0); + + if (kIOReturnSuccess == eventResult) + { + if (use_joystick) + { + ProcessAxis(event) || ProcessButton(event) || ProcessPOV(event); + } + } + else if (kIOReturnUnderrun != eventResult) + { + Printf(TEXTCOLOR_RED "IOHIDQueueInterface::getNextEvent() failed with code 0x%08X\n", eventResult); + } + + ProcessAxes(); +} + + +void IOKitJoystick::ProcessAxes() +{ + if (NULL == m_interface || !m_useAxesPolling) + { + return; + } + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + AnalogAxis& axis = m_axes[i]; + + static const double scaledMin = -1; + static const double scaledMax = 1; + + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) + { + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + } + else + { + axis.value = 0.0f; + } + } +} + + +bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) +{ + if (m_useAxesPolling) + { + return false; + } + + for (size_t i = 0, count = m_axes.Size(); i < count; ++i) + { + if (event.elementCookie != m_axes[i].cookie) + { + continue; + } + + AnalogAxis& axis = m_axes[i]; + + static const double scaledMin = -1; + static const double scaledMax = 1; + + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessButton(const IOHIDEventStruct& event) +{ + for (size_t i = 0, count = m_buttons.Size(); i < count; ++i) + { + if (event.elementCookie != m_buttons[i].cookie) + { + continue; + } + + int32_t& current = m_buttons[i].value; + const int32_t previous = current; + current = event.value; + + Joy_GenerateButtonEvents(previous, current, 1, static_cast(KEY_FIRSTJOYBUTTON + i)); + + return true; + } + + return false; +} + +bool IOKitJoystick::ProcessPOV(const IOHIDEventStruct& event) +{ + for (size_t i = 0, count = m_POVs.Size(); i ( + CFDictionaryGetValue(properties, CFSTR(kIOHIDManufacturerKey))); + CFStringRef productRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDProductKey))); + CFNumberRef vendorIDRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDVendorIDKey))); + CFNumberRef productIDRef = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDProductIDKey))); + + CFMutableDictionaryRef usbProperties = NULL; + + if ( NULL == vendorRef || NULL == productRef + || NULL == vendorIDRef || NULL == productIDRef) + { + // OS X is not mirroring all USB properties to HID page, so need to look at USB device page also + // Step up two levels and get dictionary of USB properties + + io_registry_entry_t parent1; + kern_return_t ioResult = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent1); + + if (KERN_SUCCESS == ioResult) + { + io_registry_entry_t parent2; + ioResult = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent2); + + if (KERN_SUCCESS == ioResult) + { + ioResult = IORegistryEntryCreateCFProperties(parent2, &usbProperties, kCFAllocatorDefault, kNilOptions); + + if (KERN_SUCCESS != ioResult) + { + Printf(TEXTCOLOR_RED "IORegistryEntryCreateCFProperties() failed with code %i\n", ioResult); + } + + IOObjectRelease(parent2); + } + else + { + Printf(TEXTCOLOR_RED "IORegistryEntryGetParentEntry(2) failed with code %i\n", ioResult); + } + + IOObjectRelease(parent1); + } + else + { + Printf(TEXTCOLOR_RED "IORegistryEntryGetParentEntry(1) failed with code %i\n", ioResult); + } + } + + if (NULL != usbProperties) + { + if (NULL == vendorRef) + { + vendorRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("USB Vendor Name"))); + } + + if (NULL == productRef) + { + productRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("USB Product Name"))); + } + + if (NULL == vendorIDRef) + { + vendorIDRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("idVendor"))); + } + + if (NULL == productIDRef) + { + productIDRef = static_cast( + CFDictionaryGetValue(usbProperties, CFSTR("idProduct"))); + } + } + + m_name += ToFString(vendorRef); + m_name += " "; + m_name += ToFString(productRef); + + int vendorID = 0, productID = 0; + + if (NULL != vendorIDRef) + { + CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID); + } + + if (NULL != productIDRef) + { + CFNumberGetValue(productIDRef, kCFNumberIntType, &productID); + } + + m_identifier.AppendFormat("VID_%04x_PID_%04x", vendorID, productID); + + if (NULL != usbProperties) + { + CFRelease(usbProperties); + } +} + + +long GetElementValue(const CFDictionaryRef element, const CFStringRef key) +{ + const CFNumberRef number = + static_cast(CFDictionaryGetValue(element, key)); + long result = 0; + + if (NULL != number && CFGetTypeID(number) == CFNumberGetTypeID()) + { + CFNumberGetValue(number, kCFNumberLongType, &result); + } + + return result; +} + +void IOKitJoystick::GatherElementsHandler(const void* value, void* parameter) +{ + assert(NULL != value); + assert(NULL != parameter); + + const CFDictionaryRef element = static_cast(value); + IOKitJoystick* thisPtr = static_cast(parameter); + + if (CFGetTypeID(element) != CFDictionaryGetTypeID()) + { + Printf(TEXTCOLOR_RED "IOKitJoystick: Encountered wrong element type\n"); + return; + } + + const long type = GetElementValue(element, CFSTR(kIOHIDElementTypeKey)); + + if (kIOHIDElementTypeCollection == type) + { + thisPtr->GatherCollectionElements(element); + } + else if (0 != type) + { + const long usagePage = GetElementValue(element, CFSTR(kIOHIDElementUsagePageKey)); + + if (kHIDPage_GenericDesktop == usagePage) + { + const long usage = GetElementValue(element, CFSTR(kIOHIDElementUsageKey)); + + 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) + { + thisPtr->AddAxis(element); + } + else if (kHIDUsage_GD_Hatswitch == usage && thisPtr->m_POVs.Size() < 4) + { + thisPtr->AddPOV(element); + } + } + else if (kHIDPage_Button == usagePage) + { + thisPtr->AddButton(element); + } + } +} + +void IOKitJoystick::GatherCollectionElements(const CFDictionaryRef properties) +{ + const CFArrayRef topElement = static_cast( + CFDictionaryGetValue(properties, CFSTR(kIOHIDElementKey))); + + if (NULL == topElement || CFGetTypeID(topElement) != CFArrayGetTypeID()) + { + Printf(TEXTCOLOR_RED "GatherCollectionElements: invalid properties dictionary\n"); + return; + } + + const CFRange range = { 0, CFArrayGetCount(topElement) }; + + CFArrayApplyFunction(topElement, range, GatherElementsHandler, this); +} + + +IOHIDElementCookie GetElementCookie(const CFDictionaryRef element) +{ + // Use C-style cast to avoid 32/64-bit IOHIDElementCookie type issue + return (IOHIDElementCookie)GetElementValue(element, CFSTR(kIOHIDElementCookieKey)); +} + +void IOKitJoystick::AddAxis(const CFDictionaryRef element) +{ + AnalogAxis axis; + + axis.cookie = GetElementCookie(element); + axis.minValue = GetElementValue(element, CFSTR(kIOHIDElementMinKey)); + axis.maxValue = GetElementValue(element, CFSTR(kIOHIDElementMaxKey)); + + const CFStringRef nameRef = static_cast( + CFDictionaryGetValue(element, CFSTR(kIOHIDElementNameKey))); + + if (NULL != nameRef && CFStringGetTypeID() == CFGetTypeID(nameRef)) + { + CFStringGetCString(nameRef, axis.name, sizeof(axis.name) - 1, kCFStringEncodingUTF8); + } + else + { + snprintf(axis.name, sizeof(axis.name), "Axis %i", m_axes.Size() + 1); + } + + m_axes.Push(axis); +} + +void IOKitJoystick::AddButton(CFDictionaryRef element) +{ + const DigitalButton button(GetElementCookie(element)); + + m_buttons.Push(button); + + AddToQueue(button.cookie); +} + +void IOKitJoystick::AddPOV(CFDictionaryRef element) +{ + const DigitalButton pov(GetElementCookie(element)); + + m_POVs.Push(pov); + + AddToQueue(pov.cookie); +} + + +void IOKitJoystick::AddToQueue(const IOHIDElementCookie cookie) +{ + if (NULL == m_queue) + { + return; + } + + if (!(*m_queue)->hasElement(m_queue, cookie)) + { + (*m_queue)->addElement(m_queue, cookie, 0); + } +} + +void IOKitJoystick::RemoveFromQueue(const IOHIDElementCookie cookie) +{ + if (NULL == m_queue) + { + return; + } + + if ((*m_queue)->hasElement(m_queue, cookie)) + { + (*m_queue)->removeElement(m_queue, cookie); + } +} + + +io_object_t* IOKitJoystick::GetNotificationPtr() +{ + return &m_notification; +} + + +// --------------------------------------------------------------------------- + + +class IOKitJoystickManager +{ +public: + IOKitJoystickManager(); + ~IOKitJoystickManager(); + + void GetJoysticks(TArray& joysticks) const; + + void AddAxes(float axes[NUM_JOYAXIS]) const; + + // Updates axes/buttons states + void Update(); + + void UseAxesPolling(bool axesPolling); + +private: + typedef TDeletingArray JoystickList; + JoystickList m_joysticks; + + static const size_t NOTIFICATION_PORT_COUNT = 2; + + IONotificationPortRef m_notificationPorts[NOTIFICATION_PORT_COUNT]; + io_iterator_t m_notifications [NOTIFICATION_PORT_COUNT]; + + // Rebuilds device list + void Rescan(int usagePage, int usage, size_t notificationPortIndex); + void AddDevices(IONotificationPortRef notificationPort, const io_iterator_t iterator); + + static void OnDeviceAttached(void* refcon, io_iterator_t iterator); + static void OnDeviceRemoved(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); +}; + + +IOKitJoystickManager* s_joystickManager; + + +IOKitJoystickManager::IOKitJoystickManager() +{ + memset(m_notifications, 0, sizeof m_notifications); + + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + m_notificationPorts[i] = IONotificationPortCreate(kIOMasterPortDefault); + + if (NULL == m_notificationPorts[i]) + { + Printf(TEXTCOLOR_RED "IONotificationPortCreate(%zu) failed\n", i); + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(m_notificationPorts[i]), kCFRunLoopDefaultMode); + } + + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, 0); + Rescan(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, 1); +} + +IOKitJoystickManager::~IOKitJoystickManager() +{ + for (size_t i = 0; i < NOTIFICATION_PORT_COUNT; ++i) + { + IONotificationPortRef& port = m_notificationPorts[i]; + + if (NULL != port) + { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource(port), kCFRunLoopDefaultMode); + + IONotificationPortDestroy(port); + port = NULL; + } + + io_iterator_t& notification = m_notifications[i]; + + if (0 != notification) + { + IOObjectRelease(notification); + notification = NULL; + } + } +} + + +void IOKitJoystickManager::GetJoysticks(TArray& 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::UseAxesPolling(const bool axesPolling) +{ + for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) + { + m_joysticks[i]->UseAxesPolling(axesPolling); + } +} + + +void PostDeviceChangeEvent() +{ + const event_t event = { EV_DeviceChange }; + D_PostEvent(&event); +} + + +void IOKitJoystickManager::Rescan(const int usagePage, const int usage, const size_t notificationPortIndex) +{ + CFMutableDictionaryRef deviceMatching = IOServiceMatching(kIOHIDDeviceKey); + + if (NULL == deviceMatching) + { + Printf(TEXTCOLOR_RED "IOServiceMatching() returned NULL\n"); + return; + } + + const CFNumberRef usagePageRef = + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage); + CFDictionarySetValue(deviceMatching, CFSTR(kIOHIDPrimaryUsagePageKey), usagePageRef); + + const CFNumberRef usageRef = + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + CFDictionarySetValue(deviceMatching, CFSTR(kIOHIDPrimaryUsageKey), usageRef); + + assert(notificationPortIndex < NOTIFICATION_PORT_COUNT); + io_iterator_t* iteratorPtr = &m_notifications[notificationPortIndex]; + + const IONotificationPortRef notificationPort = m_notificationPorts[notificationPortIndex]; + assert(NULL != notificationPort); + + const kern_return_t notificationResult = IOServiceAddMatchingNotification(notificationPort, + kIOFirstMatchNotification, deviceMatching, OnDeviceAttached, notificationPort, iteratorPtr); + + // IOServiceAddMatchingNotification() consumes one reference of matching dictionary + // Thus CFRelease(deviceMatching) is not needed + + CFRelease(usageRef); + CFRelease(usagePageRef); + + if (KERN_SUCCESS != notificationResult) + { + Printf(TEXTCOLOR_RED "IOServiceAddMatchingNotification() failed with code %i\n", notificationResult); + } + + AddDevices(notificationPort, *iteratorPtr); +} + +void IOKitJoystickManager::AddDevices(const IONotificationPortRef notificationPort, const io_iterator_t iterator) +{ + while (io_object_t device = IOIteratorNext(iterator)) + { + IOKitJoystick* joystick = new IOKitJoystick(device); + m_joysticks.Push(joystick); + + const kern_return_t notificationResult = IOServiceAddInterestNotification(notificationPort, + device, kIOGeneralInterest, OnDeviceRemoved, joystick, joystick->GetNotificationPtr()); + if (KERN_SUCCESS != notificationResult) + { + Printf(TEXTCOLOR_RED "IOServiceAddInterestNotification() failed with code %i\n", notificationResult); + } + + IOObjectRelease(device); + + PostDeviceChangeEvent(); + } +} + + +void IOKitJoystickManager::OnDeviceAttached(void* const refcon, const io_iterator_t iterator) +{ + assert(NULL != refcon); + const IONotificationPortRef notificationPort = static_cast(refcon); + + assert(NULL != s_joystickManager); + s_joystickManager->AddDevices(notificationPort, iterator); +} + +void IOKitJoystickManager::OnDeviceRemoved(void* const refcon, io_service_t, const natural_t messageType, void*) +{ + if (messageType != kIOMessageServiceIsTerminated) + { + return; + } + + assert(NULL != refcon); + IOKitJoystick* const joystick = static_cast(refcon); + + assert(NULL != s_joystickManager); + JoystickList& joysticks = s_joystickManager->m_joysticks; + + for (unsigned int i = 0, count = joysticks.Size(); i < count; ++i) + { + if (joystick == joysticks[i]) + { + joysticks.Delete(i); + break; + } + } + + delete joystick; + + PostDeviceChangeEvent(); +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +void I_ShutdownJoysticks() +{ + // Needed in order to support existing interface + // Left empty intentionally +} + +static void ShutdownJoysticks() +{ + delete s_joystickManager; + s_joystickManager = NULL; +} + +void I_GetJoysticks(TArray& sticks) +{ + // Instances of IOKitJoystick depend on GameConfig object. + // M_SaveDefaultsFinal() must be called after destruction of IOKitJoystickManager. + // To ensure this, its initialization is moved here. + // As M_LoadDefaults() was already called at this moment, + // the order of atterm's functions will be correct + + if (NULL == s_joystickManager && !Args->CheckParm("-nojoy")) + { + s_joystickManager = new IOKitJoystickManager; + atterm(ShutdownJoysticks); + } + + 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() +{ + // Does nothing, device list is always kept up-to-date + + return NULL; +} + + +// --------------------------------------------------------------------------- + + +void I_ProcessJoysticks() +{ + if (NULL != s_joystickManager) + { + s_joystickManager->Update(); + } +} + + +// --------------------------------------------------------------------------- + + +CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (NULL != s_joystickManager) + { + s_joystickManager->UseAxesPolling(self); + } +} diff --git a/src/posix/cocoa/i_main.mm b/src/posix/cocoa/i_main.mm new file mode 100644 index 000000000..05a8081f6 --- /dev/null +++ b/src/posix/cocoa/i_main.mm @@ -0,0 +1,551 @@ +/* + ** i_main.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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 "i_common.h" + +#include +#include + +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "c_console.h" +#include "c_cvars.h" +#include "cmdlib.h" +#include "d_main.h" +#include "doomerrors.h" +#include "i_system.h" +#include "m_argv.h" +#include "s_sound.h" +#include "version.h" + +#undef Class + + +#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) + + +// --------------------------------------------------------------------------- + + +EXTERN_CVAR(Int, vid_defwidth ) +EXTERN_CVAR(Int, vid_defheight) +EXTERN_CVAR(Bool, vid_vsync ) +EXTERN_CVAR(Bool, fullscreen ) + + +// --------------------------------------------------------------------------- + + +namespace +{ + +// The maximum number of functions that can be registered with atterm. +const size_t MAX_TERMS = 64; + +void (*TermFuncs[MAX_TERMS])(); +const char *TermNames[MAX_TERMS]; +size_t NumTerms; + +void call_terms() +{ + while (NumTerms > 0) + { + TermFuncs[--NumTerms](); + } +} + +} // unnamed namespace + + +void addterm(void (*func)(), const char *name) +{ + // Make sure this function wasn't already registered. + + for (size_t i = 0; i < NumTerms; ++i) + { + if (TermFuncs[i] == func) + { + return; + } + } + + if (NumTerms == MAX_TERMS) + { + func(); + I_FatalError("Too many exit functions registered."); + } + + TermNames[NumTerms] = name; + TermFuncs[NumTerms] = func; + + ++NumTerms; +} + +void popterm() +{ + if (NumTerms) + { + --NumTerms; + } +} + + +void Mac_I_FatalError(const char* const message) +{ + I_SetMainWindowVisible(false); + + const CFStringRef errorString = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, + message, kCFStringEncodingASCII, kCFAllocatorNull); + + if (NULL != errorString) + { + CFOptionFlags dummy; + + CFUserNotificationDisplayAlert( 0, kCFUserNotificationStopAlertLevel, NULL, NULL, NULL, + CFSTR("Fatal Error"), errorString, CFSTR("Exit"), NULL, NULL, &dummy); + + CFRelease(errorString); + } +} + + +DArgs* Args; // command line arguments + + +namespace +{ + +const int ARGC_MAX = 64; + +int s_argc; +char* s_argv[ARGC_MAX]; + +TArray s_argvStorage; + +bool s_restartedFromWADPicker; + + +void NewFailure() +{ + I_FatalError("Failed to allocate memory from system heap"); +} + + +int OriginalMain(int argc, char** argv) +{ + printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n", + GetVersionString(), GetGitTime(), __DATE__); + + seteuid(getuid()); + std::set_new_handler(NewFailure); + + // Set LC_NUMERIC environment variable in case some library decides to + // clear the setlocale call at least this will be correct. + // Note that the LANG environment variable is overridden by LC_* + setenv("LC_NUMERIC", "C", 1); + setlocale(LC_ALL, "C"); + + // Set reasonable default values for video settings + + const NSSize screenSize = [[NSScreen mainScreen] frame].size; + vid_defwidth = static_cast(screenSize.width); + vid_defheight = static_cast(screenSize.height); + vid_vsync = true; + fullscreen = true; + + try + { + Args = new DArgs(argc, argv); + + /* + killough 1/98: + + This fixes some problems with exit handling + during abnormal situations. + + The old code called I_Quit() to end program, + while now I_Quit() is installed as an exit + handler and exit() is called to exit, either + normally or abnormally. Seg faults are caught + and the error handler is used, to prevent + being left in graphics mode or having very + loud SFX noise because the sound card is + left in an unstable state. + */ + + atexit(call_terms); + atterm(I_Quit); + + NSString* exePath = [[NSBundle mainBundle] executablePath]; + progdir = [[exePath stringByDeletingLastPathComponent] UTF8String]; + progdir += "/"; + + C_InitConsole(80 * 8, 25 * 8, false); + D_DoomMain(); + } + catch(const CDoomError& error) + { + const char* const message = error.GetMessage(); + + if (NULL != message) + { + fprintf(stderr, "%s\n", message); + Mac_I_FatalError(message); + } + + exit(-1); + } + catch(...) + { + call_terms(); + throw; + } + + return 0; +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +@interface ApplicationController : NSResponder +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 + +#endif +{ +} + +- (void)keyDown:(NSEvent*)theEvent; +- (void)keyUp:(NSEvent*)theEvent; + +- (void)applicationDidBecomeActive:(NSNotification*)aNotification; +- (void)applicationWillResignActive:(NSNotification*)aNotification; + +- (void)applicationDidFinishLaunching:(NSNotification*)aNotification; + +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename; + +- (void)processEvents:(NSTimer*)timer; + +@end + + +ApplicationController* appCtrl; + + +@implementation ApplicationController + +- (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(OriginalMain(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)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; + } + + I_ProcessEvent(event); + + [NSApp sendEvent:event]; + } + + [NSApp updateWindows]; + + [pool release]; +} + +@end + + +// --------------------------------------------------------------------------- + + +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]; +} + +void ReleaseApplicationController() +{ + if (NULL != appCtrl) + { + [NSApp setDelegate:nil]; + [NSApp deactivate]; + + [appCtrl release]; + appCtrl = NULL; + } +} + +} // unnamed namespace + + +int main(int argc, char** argv) +{ + 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(); + + atterm(ReleaseApplicationController); + + appCtrl = [ApplicationController new]; + [NSApp setDelegate:appCtrl]; + [NSApp run]; + + [pool release]; + + return EXIT_SUCCESS; +} diff --git a/src/posix/cocoa/i_timer.cpp b/src/posix/cocoa/i_timer.cpp new file mode 100644 index 000000000..b08a43139 --- /dev/null +++ b/src/posix/cocoa/i_timer.cpp @@ -0,0 +1,241 @@ +/* + ** i_timer.cpp + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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 "basictypes.h" +#include "basicinlines.h" +#include "doomdef.h" +#include "i_system.h" +#include "templates.h" + + +namespace +{ + +timeval s_gameStartTicks; +timeval s_systemBootTicks; + +unsigned int GetMillisecondsSince(const timeval& time) +{ + timeval now; + gettimeofday(&now, NULL); + + return static_cast( + (now.tv_sec - time.tv_sec ) * 1000 + + (now.tv_usec - time.tv_usec) / 1000); +} + + +bool s_isTicFrozen; + +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 (!s_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 = I_MSTime(); + + 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(!s_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) +{ + s_isTicFrozen = frozen; +} + +} // unnamed namespace + + +unsigned int I_MSTime() +{ + return GetMillisecondsSince(s_gameStartTicks); +} + +unsigned int I_FPSTime() +{ + return GetMillisecondsSince(s_systemBootTicks); +} + + +fixed_t I_GetTimeFrac(uint32* ms) +{ + const uint32_t now = I_MSTime(); + + 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; + + gettimeofday(&s_gameStartTicks, NULL); + + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + size_t len = sizeof s_systemBootTicks; + + sysctl(mib, 2, &s_systemBootTicks, &len, NULL, 0); + + 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/posix/cocoa/i_video.mm b/src/posix/cocoa/i_video.mm new file mode 100644 index 000000000..57f7c16fd --- /dev/null +++ b/src/posix/cocoa/i_video.mm @@ -0,0 +1,1232 @@ +/* + ** i_video.mm + ** + **--------------------------------------------------------------------------- + ** Copyright 2012-2015 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 "i_common.h" + +#import +#import + +// Avoid collision between DObject class and Objective-C +#define Class ObjectClass + +#include "bitmap.h" +#include "c_dispatch.h" +#include "doomstat.h" +#include "hardware.h" +#include "i_system.h" +#include "m_argv.h" +#include "r_renderer.h" +#include "r_swrenderer.h" +#include "stats.h" +#include "textures.h" +#include "v_palette.h" +#include "v_pfx.h" +#include "v_text.h" +#include "v_video.h" +#include "version.h" + +#undef Class + + +EXTERN_CVAR(Bool, ticker ) +EXTERN_CVAR(Bool, vid_vsync) +EXTERN_CVAR(Bool, vid_hidpi) + +CUSTOM_CVAR(Bool, fullscreen, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + extern int NewWidth, NewHeight, NewBits, DisplayBits; + + NewWidth = screen->GetWidth(); + NewHeight = screen->GetHeight(); + NewBits = DisplayBits; + setmodeneeded = true; +} + + +RenderBufferOptions rbOpts; + + +// --------------------------------------------------------------------------- + + +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 CocoaWindow : NSWindow +{ +} + +- (BOOL)canBecomeKeyWindow; + +@end + + +@implementation CocoaWindow + +- (BOOL)canBecomeKeyWindow +{ + return true; +} + +@end + + +// --------------------------------------------------------------------------- + + +@interface CocoaView : NSOpenGLView +{ + NSCursor* m_cursor; +} + +- (void)resetCursorRects; + +- (void)setCursor:(NSCursor*)cursor; + +@end + + +@implementation CocoaView + +- (void)resetCursorRects +{ + [super resetCursorRects]; + + NSCursor* const cursor = nil == m_cursor + ? [NSCursor arrowCursor] + : m_cursor; + + [self addCursorRect:[self bounds] + cursor:cursor]; +} + +- (void)setCursor:(NSCursor*)cursor +{ + m_cursor = cursor; +} + +@end + + + +// --------------------------------------------------------------------------- + + +class CocoaVideo : public IVideo +{ +public: + explicit CocoaVideo(int multisample); + + virtual EDisplayType GetDisplayType() { return DISPLAY_Both; } + virtual void SetWindowedScale(float scale); + + virtual DFrameBuffer* CreateFrameBuffer(int width, int height, bool fs, DFrameBuffer* old); + + virtual void StartModeIterator(int bits, bool fullscreen); + virtual bool NextMode(int* width, int* height, bool* letterbox); + + static bool IsFullscreen(); + static void UseHiDPI(bool hiDPI); + static void SetCursor(NSCursor* cursor); + static void SetWindowVisible(bool visible); + +private: + struct ModeIterator + { + size_t index; + int bits; + bool fullscreen; + }; + + ModeIterator m_modeIterator; + + CocoaWindow* m_window; + + int m_width; + int m_height; + bool m_fullscreen; + bool m_hiDPI; + + void SetStyleMask(NSUInteger styleMask); + void SetFullscreenMode(int width, int height); + void SetWindowedMode(int width, int height); + void SetMode(int width, int height, bool fullscreen, bool hiDPI); + + static CocoaVideo* GetInstance(); +}; + + +class CocoaFrameBuffer : public DFrameBuffer +{ +public: + CocoaFrameBuffer(int width, int height, bool fullscreen); + ~CocoaFrameBuffer(); + + virtual bool Lock(bool buffer); + virtual void Unlock(); + virtual void Update(); + + virtual PalEntry* GetPalette(); + virtual void GetFlashedPalette(PalEntry pal[256]); + virtual void UpdatePalette(); + + virtual bool SetGamma(float gamma); + virtual bool SetFlash(PalEntry rgb, int amount); + virtual void GetFlash(PalEntry &rgb, int &amount); + + virtual int GetPageCount(); + + virtual bool IsFullscreen(); + + virtual void SetVSync(bool vsync); + +private: + static const size_t BYTES_PER_PIXEL = 4; + + PalEntry m_palette[256]; + bool m_needPaletteUpdate; + + BYTE m_gammaTable[3][256]; + float m_gamma; + bool m_needGammaUpdate; + + PalEntry m_flashColor; + int m_flashAmount; + + bool m_isUpdatePending; + + uint8_t* m_pixelBuffer; + GLuint m_texture; + + void Flip(); + + void UpdateColors(); +}; + + +// --------------------------------------------------------------------------- + + +EXTERN_CVAR(Float, Gamma) + +CUSTOM_CVAR(Float, rgamma, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (NULL != screen) + { + screen->SetGamma(Gamma); + } +} + +CUSTOM_CVAR(Float, ggamma, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (NULL != screen) + { + screen->SetGamma(Gamma); + } +} + +CUSTOM_CVAR(Float, bgamma, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (NULL != screen) + { + screen->SetGamma(Gamma); + } +} + + +// --------------------------------------------------------------------------- + + +extern id appCtrl; + + +namespace +{ + +const struct +{ + uint16_t width; + uint16_t height; +} +VideoModes[] = +{ + { 320, 200 }, + { 320, 240 }, + { 400, 225 }, // 16:9 + { 400, 300 }, + { 480, 270 }, // 16:9 + { 480, 360 }, + { 512, 288 }, // 16:9 + { 512, 384 }, + { 640, 360 }, // 16:9 + { 640, 400 }, + { 640, 480 }, + { 720, 480 }, // 16:10 + { 720, 540 }, + { 800, 450 }, // 16:9 + { 800, 480 }, + { 800, 500 }, // 16:10 + { 800, 600 }, + { 848, 480 }, // 16:9 + { 960, 600 }, // 16:10 + { 960, 720 }, + { 1024, 576 }, // 16:9 + { 1024, 600 }, // 17:10 + { 1024, 640 }, // 16:10 + { 1024, 768 }, + { 1088, 612 }, // 16:9 + { 1152, 648 }, // 16:9 + { 1152, 720 }, // 16:10 + { 1152, 864 }, + { 1280, 720 }, // 16:9 + { 1280, 854 }, + { 1280, 800 }, // 16:10 + { 1280, 960 }, + { 1280, 1024 }, // 5:4 + { 1360, 768 }, // 16:9 + { 1366, 768 }, + { 1400, 787 }, // 16:9 + { 1400, 875 }, // 16:10 + { 1400, 1050 }, + { 1440, 900 }, + { 1440, 960 }, + { 1440, 1080 }, + { 1600, 900 }, // 16:9 + { 1600, 1000 }, // 16:10 + { 1600, 1200 }, + { 1920, 1080 }, + { 1920, 1200 }, + { 2048, 1536 }, + { 2560, 1440 }, + { 2560, 1600 }, + { 2560, 2048 }, + { 2880, 1800 }, + { 3200, 1800 }, + { 3840, 2160 }, + { 3840, 2400 }, + { 4096, 2160 }, + { 5120, 2880 } +}; + + +cycle_t BlitCycles; +cycle_t FlipCycles; + + +CocoaWindow* CreateCocoaWindow(const NSUInteger styleMask) +{ + static const CGFloat TEMP_WIDTH = VideoModes[0].width - 1; + static const CGFloat TEMP_HEIGHT = VideoModes[0].height - 1; + + CocoaWindow* const window = [CocoaWindow alloc]; + [window initWithContentRect:NSMakeRect(0, 0, TEMP_WIDTH, TEMP_HEIGHT) + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:NO]; + [window setOpaque:YES]; + [window makeFirstResponder:appCtrl]; + [window setAcceptsMouseMovedEvents:YES]; + + return window; +} + +} // unnamed namespace + + +// --------------------------------------------------------------------------- + + +CocoaVideo::CocoaVideo(const int multisample) +: m_window(CreateCocoaWindow(STYLE_MASK_WINDOWED)) +, m_width(-1) +, m_height(-1) +, m_fullscreen(false) +, m_hiDPI(false) +{ + memset(&m_modeIterator, 0, sizeof m_modeIterator); + + // Set attributes for OpenGL context + + 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 (multisample) + { + attributes[i++] = NSOpenGLPFAMultisample; + attributes[i++] = NSOpenGLPFASampleBuffers; + attributes[i++] = NSOpenGLPixelFormatAttribute(1); + attributes[i++] = NSOpenGLPFASamples; + attributes[i++] = NSOpenGLPixelFormatAttribute(multisample); + } + + attributes[i] = NSOpenGLPixelFormatAttribute(0); + + // Create OpenGL context and view + + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; + + const NSRect contentRect = [m_window contentRectForFrameRect:[m_window frame]]; + NSOpenGLView* glView = [[CocoaView alloc] initWithFrame:contentRect + pixelFormat:pixelFormat]; + [[glView openGLContext] makeCurrentContext]; + + [m_window setContentView:glView]; +} + +void CocoaVideo::StartModeIterator(const int bits, const bool fullscreen) +{ + m_modeIterator.index = 0; + m_modeIterator.bits = bits; + m_modeIterator.fullscreen = fullscreen; +} + +bool CocoaVideo::NextMode(int* const width, int* const height, bool* const letterbox) +{ + assert(NULL != width); + assert(NULL != height); + + const int bits = m_modeIterator.bits; + + if (8 != bits && 16 != bits && 24 != bits && 32 != bits) + { + return false; + } + + size_t& index = m_modeIterator.index; + + if (index < sizeof(VideoModes) / sizeof(VideoModes[0])) + { + *width = VideoModes[index].width; + *height = VideoModes[index].height; + + if (m_modeIterator.fullscreen && NULL != letterbox) + { + const NSSize screenSize = [[m_window screen] frame].size; + const float screenRatio = screenSize.width / screenSize.height; + const float modeRatio = float(*width) / *height; + + *letterbox = fabs(screenRatio - modeRatio) > 0.001f; + } + + ++index; + + return true; + } + + return false; +} + +DFrameBuffer* CocoaVideo::CreateFrameBuffer(const int width, const int height, const bool fullscreen, DFrameBuffer* const old) +{ + PalEntry flashColor = 0; + int flashAmount = 0; + + if (NULL != old) + { + if (width == m_width && height == m_height) + { + SetMode(width, height, fullscreen, vid_hidpi); + return old; + } + + old->GetFlash(flashColor, flashAmount); + old->ObjectFlags |= OF_YesReallyDelete; + + if (old == screen) + { + screen = NULL; + } + + delete old; + } + + CocoaFrameBuffer* fb = new CocoaFrameBuffer(width, height, fullscreen); + fb->SetFlash(flashColor, flashAmount); + + SetMode(width, height, fullscreen, vid_hidpi); + + return fb; +} + +void CocoaVideo::SetWindowedScale(float scale) +{ +} + + +bool CocoaVideo::IsFullscreen() +{ + CocoaVideo* const video = GetInstance(); + return NULL == video + ? false + : video->m_fullscreen; +} + +void CocoaVideo::UseHiDPI(const bool hiDPI) +{ + if (CocoaVideo* const video = GetInstance()) + { + video->SetMode(video->m_width, video->m_height, video->m_fullscreen, hiDPI); + } +} + +void CocoaVideo::SetCursor(NSCursor* cursor) +{ + if (CocoaVideo* const video = GetInstance()) + { + NSWindow* const window = video->m_window; + CocoaView* const view = [window contentView]; + + [view setCursor:cursor]; + [window invalidateCursorRectsForView:view]; + } +} + +void CocoaVideo::SetWindowVisible(bool visible) +{ + if (CocoaVideo* const video = GetInstance()) + { + if (visible) + { + [video->m_window orderFront:nil]; + } + else + { + [video->m_window orderOut:nil]; + } + } +} + + +static bool HasModernFullscreenAPI() +{ + // The following value shoud be equal to NSAppKitVersionNumber10_6 + // and it's hard-coded in order to build on earlier SDKs + + return NSAppKitVersionNumber >= 1038; +} + +void CocoaVideo::SetStyleMask(const 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 running on Snow Leopard or newer + + assert(!HasModernFullscreenAPI()); + + CocoaWindow* tempWindow = CreateCocoaWindow(styleMask); + [tempWindow setContentView:[m_window contentView]]; + + [m_window close]; + m_window = tempWindow; +} + +void CocoaVideo::SetFullscreenMode(const int width, const 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 = 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) + { + if (HasModernFullscreenAPI()) + { + [m_window setLevel:LEVEL_FULLSCREEN]; + [m_window setStyleMask:STYLE_MASK_FULLSCREEN]; + } + 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] + + SetSystemUIMode(kUIModeAllHidden, 0); + SetStyleMask(STYLE_MASK_FULLSCREEN); + } + + [m_window setHidesOnDeactivate:YES]; + } + + [m_window setFrame:displayRect display:YES]; + [m_window setFrameOrigin:NSMakePoint(0.0f, 0.0f)]; +} + +void CocoaVideo::SetWindowedMode(const int width, const 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) + { + if (HasModernFullscreenAPI()) + { + [m_window setLevel:LEVEL_WINDOWED]; + [m_window setStyleMask:STYLE_MASK_WINDOWED]; + } + else + { + SetSystemUIMode(kUIModeNormal, 0); + 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 CocoaVideo::SetMode(const int width, const int height, const bool fullscreen, const bool hiDPI) +{ + if (fullscreen == m_fullscreen + && width == m_width + && height == m_height + && hiDPI == m_hiDPI) + { + return; + } + + if (I_IsHiDPISupported()) + { + NSOpenGLView* const glView = [m_window contentView]; + [glView setWantsBestResolutionOpenGLSurface:hiDPI]; + } + + if (fullscreen) + { + SetFullscreenMode(width, height); + } + else + { + SetWindowedMode(width, height); + } + + rbOpts.dirty = true; + + const NSSize viewSize = I_GetContentViewSize(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); + + [[NSOpenGLContext currentContext] flushBuffer]; + + 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; +} + + +CocoaVideo* CocoaVideo::GetInstance() +{ + return static_cast(Video); +} + + +CocoaFrameBuffer::CocoaFrameBuffer(int width, int height, bool fullscreen) +: DFrameBuffer(width, height) +, m_needPaletteUpdate(false) +, m_gamma(0.0f) +, m_needGammaUpdate(false) +, m_flashAmount(0) +, m_isUpdatePending(false) +, m_pixelBuffer(new uint8_t[width * height * BYTES_PER_PIXEL]) +, m_texture(0) +{ + glEnable(GL_TEXTURE_RECTANGLE_ARB); + + glGenTextures(1, &m_texture); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, m_texture); + 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); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, width, height, 0.0, -1.0, 1.0); + + GPfx.SetFormat(32, 0x000000FF, 0x0000FF00, 0x00FF0000); + + for (size_t i = 0; i < 256; ++i) + { + m_gammaTable[0][i] = m_gammaTable[1][i] = m_gammaTable[2][i] = i; + } + + memcpy(m_palette, GPalette.BaseColors, sizeof(PalEntry) * 256); + UpdateColors(); + + SetVSync(vid_vsync); +} + + +CocoaFrameBuffer::~CocoaFrameBuffer() +{ + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &m_texture); + + delete[] m_pixelBuffer; +} + +int CocoaFrameBuffer::GetPageCount() +{ + return 1; +} + +bool CocoaFrameBuffer::Lock(bool buffered) +{ + return DSimpleCanvas::Lock(buffered); +} + +void CocoaFrameBuffer::Unlock() +{ + if (m_isUpdatePending && LockCount == 1) + { + Update(); + } + else if (--LockCount <= 0) + { + Buffer = NULL; + LockCount = 0; + } +} + +void CocoaFrameBuffer::Update() +{ + if (LockCount != 1) + { + if (LockCount > 0) + { + m_isUpdatePending = true; + --LockCount; + } + return; + } + + DrawRateStuff(); + + Buffer = NULL; + LockCount = 0; + m_isUpdatePending = false; + + BlitCycles.Reset(); + FlipCycles.Reset(); + BlitCycles.Clock(); + + GPfx.Convert(MemBuffer, Pitch, m_pixelBuffer, Width * BYTES_PER_PIXEL, + Width, Height, FRACUNIT, FRACUNIT, 0, 0); + + FlipCycles.Clock(); + Flip(); + FlipCycles.Unclock(); + + BlitCycles.Unclock(); + + if (m_needGammaUpdate) + { + CalcGamma(rgamma == 0.0f ? m_gamma : m_gamma * rgamma, m_gammaTable[0]); + CalcGamma(ggamma == 0.0f ? m_gamma : m_gamma * ggamma, m_gammaTable[1]); + CalcGamma(bgamma == 0.0f ? m_gamma : m_gamma * bgamma, m_gammaTable[2]); + + m_needGammaUpdate = false; + m_needPaletteUpdate = true; + } + + if (m_needPaletteUpdate) + { + m_needPaletteUpdate = false; + UpdateColors(); + } +} + +void CocoaFrameBuffer::UpdateColors() +{ + PalEntry palette[256]; + + for (size_t i = 0; i < 256; ++i) + { + palette[i].r = m_gammaTable[0][m_palette[i].r]; + palette[i].g = m_gammaTable[1][m_palette[i].g]; + palette[i].b = m_gammaTable[2][m_palette[i].b]; + } + + if (0 != m_flashAmount) + { + DoBlending(palette, palette, 256, + m_gammaTable[0][m_flashColor.r], + m_gammaTable[1][m_flashColor.g], + m_gammaTable[2][m_flashColor.b], + m_flashAmount); + } + + GPfx.SetPalette(palette); +} + +PalEntry* CocoaFrameBuffer::GetPalette() +{ + return m_palette; +} + +void CocoaFrameBuffer::UpdatePalette() +{ + m_needPaletteUpdate = true; +} + +bool CocoaFrameBuffer::SetGamma(float gamma) +{ + m_gamma = gamma; + m_needGammaUpdate = true; + + return true; +} + +bool CocoaFrameBuffer::SetFlash(PalEntry rgb, int amount) +{ + m_flashColor = rgb; + m_flashAmount = amount; + m_needPaletteUpdate = true; + + return true; +} + +void CocoaFrameBuffer::GetFlash(PalEntry &rgb, int &amount) +{ + rgb = m_flashColor; + amount = m_flashAmount; +} + +void CocoaFrameBuffer::GetFlashedPalette(PalEntry pal[256]) +{ + memcpy(pal, m_palette, sizeof m_palette); + + if (0 != m_flashAmount) + { + DoBlending(pal, pal, 256, + m_flashColor.r, m_flashColor.g, m_flashColor.b, + m_flashAmount); + } +} + +bool CocoaFrameBuffer::IsFullscreen() +{ + return CocoaVideo::IsFullscreen(); +} + +void CocoaFrameBuffer::SetVSync(bool vsync) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 + const long value = vsync ? 1 : 0; +#else // 10.5 or newer + const GLint value = vsync ? 1 : 0; +#endif // prior to 10.5 + + [[NSOpenGLContext currentContext] setValues:&value + forParameter:NSOpenGLCPSwapInterval]; +} + +void CocoaFrameBuffer::Flip() +{ + 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; + } + +#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, m_pixelBuffer); + + 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(); + + [[NSOpenGLContext currentContext] flushBuffer]; +} + + +ADD_STAT(blit) +{ + FString result; + result.Format("blit=%04.1f ms flip=%04.1f ms", BlitCycles.TimeMS(), FlipCycles.TimeMS()); + return result; +} + + +IVideo* Video; + + +void I_ShutdownGraphics() +{ + if (NULL != screen) + { + screen->ObjectFlags |= OF_YesReallyDelete; + delete screen; + screen = NULL; + } + + delete Video; + Video = NULL; +} + +void I_InitGraphics() +{ + UCVarValue val; + + val.Bool = !!Args->CheckParm("-devparm"); + ticker.SetGenericRepDefault(val, CVAR_Bool); + + Video = new CocoaVideo(0); + atterm(I_ShutdownGraphics); +} + + +static void I_DeleteRenderer() +{ + delete Renderer; + Renderer = NULL; +} + +void I_CreateRenderer() +{ + if (NULL == Renderer) + { + Renderer = new FSoftwareRenderer; + atterm(I_DeleteRenderer); + } +} + + +DFrameBuffer* I_SetMode(int &width, int &height, DFrameBuffer* old) +{ + return Video->CreateFrameBuffer(width, height, fullscreen, old); +} + +bool I_CheckResolution(const int width, const int height, const int bits) +{ + int twidth, theight; + + Video->StartModeIterator(bits, fullscreen); + + while (Video->NextMode(&twidth, &theight, NULL)) + { + if (width == twidth && height == theight) + { + return true; + } + } + + return false; +} + +void I_ClosestResolution(int *width, int *height, int bits) +{ + int twidth, theight; + int cwidth = 0, cheight = 0; + int iteration; + DWORD closest = DWORD(-1); + + for (iteration = 0; iteration < 2; ++iteration) + { + Video->StartModeIterator(bits, fullscreen); + + while (Video->NextMode(&twidth, &theight, NULL)) + { + if (twidth == *width && theight == *height) + { + return; + } + + if (iteration == 0 && (twidth < *width || theight < *height)) + { + continue; + } + + const DWORD dist = (twidth - *width) * (twidth - *width) + + (theight - *height) * (theight - *height); + + if (dist < closest) + { + closest = dist; + cwidth = twidth; + cheight = theight; + } + } + + if (closest != DWORD(-1)) + { + *width = cwidth; + *height = cheight; + return; + } + } +} + + +EXTERN_CVAR(Int, vid_maxfps); +EXTERN_CVAR(Bool, cl_capfps); + +// So Apple doesn't support POSIX timers and I can't find a good substitute short of +// having Objective-C Cocoa events or something like that. +void I_SetFPSLimit(int limit) +{ +} + +CUSTOM_CVAR(Int, vid_maxfps, 200, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (vid_maxfps < TICRATE && vid_maxfps != 0) + { + vid_maxfps = TICRATE; + } + else if (vid_maxfps > 1000) + { + vid_maxfps = 1000; + } + else if (cl_capfps == 0) + { + I_SetFPSLimit(vid_maxfps); + } +} + +CUSTOM_CVAR(Bool, vid_hidpi, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (I_IsHiDPISupported()) + { + CocoaVideo::UseHiDPI(self); + } + else if (0 != self) + { + self = 0; + } +} + + +CCMD(vid_listmodes) +{ + if (Video == NULL) + { + return; + } + + static const char* const ratios[5] = { "", " - 16:9", " - 16:10", " - 17:10", " - 5:4" }; + int width, height; + bool letterbox; + + Video->StartModeIterator(32, screen->IsFullscreen()); + + while (Video->NextMode(&width, &height, &letterbox)) + { + const bool current = width == DisplayWidth && height == DisplayHeight; + const int ratio = CheckRatio(width, height); + + Printf(current ? PRINT_BOLD : PRINT_HIGH, "%s%4d x%5d x%3d%s%s\n", + current || !(ratio & 3) ? "" : TEXTCOLOR_GOLD, + width, height, 32, ratios[ratio], + current || !letterbox ? "" : TEXTCOLOR_BROWN " LB"); + } +} + +CCMD(vid_currentmode) +{ + Printf("%dx%dx%d\n", DisplayWidth, DisplayHeight, DisplayBits); +} + + +// --------------------------------------------------------------------------- + + +bool I_SetCursor(FTexture* cursorpic) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSCursor* cursor = nil; + + if (NULL != cursorpic && FTexture::TEX_Null != cursorpic->UseType) + { + // 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]; + + cursor = [[NSCursor alloc] initWithImage:cursorImage + hotSpot:NSMakePoint(0.0f, 0.0f)]; + } + + CocoaVideo::SetCursor(cursor); + + [pool release]; + + return true; +} + + +NSSize I_GetContentViewSize(const NSWindow* const window) +{ + const NSView* const 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 I_SetMainWindowVisible(bool visible) +{ + CocoaVideo::SetWindowVisible(visible); + I_SetNativeMouse(!visible); +} diff --git a/src/sdl/dikeys.h b/src/posix/dikeys.h similarity index 96% rename from src/sdl/dikeys.h rename to src/posix/dikeys.h index 8ce9e95bb..4541b0ffd 100644 --- a/src/sdl/dikeys.h +++ b/src/posix/dikeys.h @@ -1,155 +1,155 @@ -// ZDoom bases its keycodes on DirectInput's scan codes -// Why? Because it was Win32-only before porting to anything else, -// so this made sense. AFAIK, it's primarily used under Win32 now, -// so it still makes sense. -// -// Actually, these key codes may only be used for key bindings now, -// in which case they're not really necessary--if we tweaked c_bind.cpp. - -enum -{ - DIK_ESCAPE = 1, - DIK_1, - DIK_2, - DIK_3, - DIK_4, - DIK_5, - DIK_6, - DIK_7, - DIK_8, - DIK_9, - DIK_0, - DIK_MINUS, /* - on main keyboard */ - DIK_EQUALS, - DIK_BACK, /* backspace */ - DIK_TAB, - DIK_Q, - DIK_W, - DIK_E, - DIK_R, - DIK_T, - DIK_Y, - DIK_U, - DIK_I, - DIK_O, - DIK_P, - DIK_LBRACKET, - DIK_RBRACKET, - DIK_RETURN, /* Enter on main keyboard */ - DIK_LCONTROL, - DIK_A, - DIK_S, - DIK_D, - DIK_F, - DIK_G, - DIK_H, - DIK_J, - DIK_K, - DIK_L, - DIK_SEMICOLON, - DIK_APOSTROPHE, - DIK_GRAVE, /* accent grave */ - DIK_LSHIFT, - DIK_BACKSLASH, - DIK_Z, - DIK_X, - DIK_C, - DIK_V, - DIK_B, - DIK_N, - DIK_M, - DIK_COMMA, - DIK_PERIOD, /* . on main keyboard */ - DIK_SLASH, /* / on main keyboard */ - DIK_RSHIFT, - DIK_MULTIPLY, /* * on numeric keypad */ - DIK_LMENU, /* left Alt */ - DIK_SPACE, - DIK_CAPITAL, - DIK_F1, - DIK_F2, - DIK_F3, - DIK_F4, - DIK_F5, - DIK_F6, - DIK_F7, - DIK_F8, - DIK_F9, - DIK_F10, - DIK_NUMLOCK, - DIK_SCROLL, /* Scroll Lock */ - DIK_NUMPAD7, - DIK_NUMPAD8, - DIK_NUMPAD9, - DIK_SUBTRACT, /* - on numeric keypad */ - DIK_NUMPAD4, - DIK_NUMPAD5, - DIK_NUMPAD6, - DIK_ADD, /* + on numeric keypad */ - DIK_NUMPAD1, - DIK_NUMPAD2, - DIK_NUMPAD3, - DIK_NUMPAD0, - DIK_DECIMAL, /* . on numeric keypad */ - DIK_OEM_102 = 0x56, /* < > | on UK/Germany keyboards */ - DIK_F11, - DIK_F12, - DIK_F13 = 0x64, /* (NEC PC98) */ - DIK_F14, /* (NEC PC98) */ - DIK_F15, /* (NEC PC98) */ - DIK_KANA = 0x70, /* (Japanese keyboard) */ - DIK_ABNT_C1 = 0x73, /* / ? on Portugese (Brazilian) keyboards */ - DIK_CONVERT = 0x79, /* (Japanese keyboard) */ - DIK_NOCONVERT = 0x7B, /* (Japanese keyboard) */ - DIK_YEN = 0x7D, /* (Japanese keyboard) */ - DIK_ABNT_C2 = 0x7E, /* Numpad . on Portugese (Brazilian) keyboards */ - DIK_NUMPAD_EQUALS = 0x8D, /* = on numeric keypad (NEC PC98) */ - DIK_PREVTRACK = 0x90, /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */ - DIK_AT, /* (NEC PC98) */ - DIK_COLON, /* (NEC PC98) */ - DIK_UNDERLINE, /* (NEC PC98) */ - DIK_KANJI, /* (Japanese keyboard) */ - DIK_STOP, /* (NEC PC98) */ - DIK_AX, /* (Japan AX) */ - DIK_UNLABELED, /* (J3100) */ - DIK_NEXTTRACK = 0x99, /* Next Track */ - DIK_NUMPADENTER = 0x9C, /* Enter on numeric keypad */ - DIK_RCONTROL = 0x9D, - DIK_MUTE = 0xA0, /* Mute */ - DIK_CALCULATOR = 0xA1, /* Calculator */ - DIK_PLAYPAUSE = 0xA2, /* Play / Pause */ - DIK_MEDIASTOP = 0xA4, /* Media Stop */ - DIK_VOLUMEDOWN = 0xAE, /* Volume - */ - DIK_VOLUMEUP = 0xB0, /* Volume + */ - DIK_WEBHOME = 0xB2, /* Web home */ - DIK_NUMPADCOMMA = 0xB3, /* , on numeric keypad (NEC PC98) */ - DIK_DIVIDE = 0xB5, /* / on numeric keypad */ - DIK_SYSRQ = 0xB7, - DIK_RMENU = 0xB8, /* right Alt */ - DIK_PAUSE = 0xC5, /* Pause */ - DIK_HOME = 0xC7, /* Home on arrow keypad */ - DIK_UP = 0xC8, /* UpArrow on arrow keypad */ - DIK_PRIOR = 0xC9, /* PgUp on arrow keypad */ - DIK_LEFT = 0xCB, /* LeftArrow on arrow keypad */ - DIK_RIGHT = 0xCD, /* RightArrow on arrow keypad */ - DIK_END = 0xCF, /* End on arrow keypad */ - DIK_DOWN = 0xD0, /* DownArrow on arrow keypad */ - DIK_NEXT = 0xD1, /* PgDn on arrow keypad */ - DIK_INSERT = 0xD2, /* Insert on arrow keypad */ - DIK_DELETE = 0xD3, /* Delete on arrow keypad */ - DIK_LWIN = 0xDB, /* Left Windows key */ - DIK_RWIN = 0xDC, /* Right Windows key */ - DIK_APPS = 0xDD, /* AppMenu key */ - DIK_POWER = 0xDE, /* System Power */ - DIK_SLEEP = 0xDF, /* System Sleep */ - DIK_WAKE = 0xE3, /* System Wake */ - DIK_WEBSEARCH = 0xE5, /* Web Search */ - DIK_WEBFAVORITES = 0xE6, /* Web Favorites */ - DIK_WEBREFRESH = 0xE7, /* Web Refresh */ - DIK_WEBSTOP = 0xE8, /* Web Stop */ - DIK_WEBFORWARD = 0xE9, /* Web Forward */ - DIK_WEBBACK = 0xEA, /* Web Back */ - DIK_MYCOMPUTER = 0xEB, /* My Computer */ - DIK_MAIL = 0xEC, /* Mail */ - DIK_MEDIASELECT = 0xED /* Media Select */ -}; +// ZDoom bases its keycodes on DirectInput's scan codes +// Why? Because it was Win32-only before porting to anything else, +// so this made sense. AFAIK, it's primarily used under Win32 now, +// so it still makes sense. +// +// Actually, these key codes may only be used for key bindings now, +// in which case they're not really necessary--if we tweaked c_bind.cpp. + +enum +{ + DIK_ESCAPE = 1, + DIK_1, + DIK_2, + DIK_3, + DIK_4, + DIK_5, + DIK_6, + DIK_7, + DIK_8, + DIK_9, + DIK_0, + DIK_MINUS, /* - on main keyboard */ + DIK_EQUALS, + DIK_BACK, /* backspace */ + DIK_TAB, + DIK_Q, + DIK_W, + DIK_E, + DIK_R, + DIK_T, + DIK_Y, + DIK_U, + DIK_I, + DIK_O, + DIK_P, + DIK_LBRACKET, + DIK_RBRACKET, + DIK_RETURN, /* Enter on main keyboard */ + DIK_LCONTROL, + DIK_A, + DIK_S, + DIK_D, + DIK_F, + DIK_G, + DIK_H, + DIK_J, + DIK_K, + DIK_L, + DIK_SEMICOLON, + DIK_APOSTROPHE, + DIK_GRAVE, /* accent grave */ + DIK_LSHIFT, + DIK_BACKSLASH, + DIK_Z, + DIK_X, + DIK_C, + DIK_V, + DIK_B, + DIK_N, + DIK_M, + DIK_COMMA, + DIK_PERIOD, /* . on main keyboard */ + DIK_SLASH, /* / on main keyboard */ + DIK_RSHIFT, + DIK_MULTIPLY, /* * on numeric keypad */ + DIK_LMENU, /* left Alt */ + DIK_SPACE, + DIK_CAPITAL, + DIK_F1, + DIK_F2, + DIK_F3, + DIK_F4, + DIK_F5, + DIK_F6, + DIK_F7, + DIK_F8, + DIK_F9, + DIK_F10, + DIK_NUMLOCK, + DIK_SCROLL, /* Scroll Lock */ + DIK_NUMPAD7, + DIK_NUMPAD8, + DIK_NUMPAD9, + DIK_SUBTRACT, /* - on numeric keypad */ + DIK_NUMPAD4, + DIK_NUMPAD5, + DIK_NUMPAD6, + DIK_ADD, /* + on numeric keypad */ + DIK_NUMPAD1, + DIK_NUMPAD2, + DIK_NUMPAD3, + DIK_NUMPAD0, + DIK_DECIMAL, /* . on numeric keypad */ + DIK_OEM_102 = 0x56, /* < > | on UK/Germany keyboards */ + DIK_F11, + DIK_F12, + DIK_F13 = 0x64, /* (NEC PC98) */ + DIK_F14, /* (NEC PC98) */ + DIK_F15, /* (NEC PC98) */ + DIK_KANA = 0x70, /* (Japanese keyboard) */ + DIK_ABNT_C1 = 0x73, /* / ? on Portugese (Brazilian) keyboards */ + DIK_CONVERT = 0x79, /* (Japanese keyboard) */ + DIK_NOCONVERT = 0x7B, /* (Japanese keyboard) */ + DIK_YEN = 0x7D, /* (Japanese keyboard) */ + DIK_ABNT_C2 = 0x7E, /* Numpad . on Portugese (Brazilian) keyboards */ + DIK_NUMPAD_EQUALS = 0x8D, /* = on numeric keypad (NEC PC98) */ + DIK_PREVTRACK = 0x90, /* Previous Track (DIK_CIRCUMFLEX on Japanese keyboard) */ + DIK_AT, /* (NEC PC98) */ + DIK_COLON, /* (NEC PC98) */ + DIK_UNDERLINE, /* (NEC PC98) */ + DIK_KANJI, /* (Japanese keyboard) */ + DIK_STOP, /* (NEC PC98) */ + DIK_AX, /* (Japan AX) */ + DIK_UNLABELED, /* (J3100) */ + DIK_NEXTTRACK = 0x99, /* Next Track */ + DIK_NUMPADENTER = 0x9C, /* Enter on numeric keypad */ + DIK_RCONTROL = 0x9D, + DIK_MUTE = 0xA0, /* Mute */ + DIK_CALCULATOR = 0xA1, /* Calculator */ + DIK_PLAYPAUSE = 0xA2, /* Play / Pause */ + DIK_MEDIASTOP = 0xA4, /* Media Stop */ + DIK_VOLUMEDOWN = 0xAE, /* Volume - */ + DIK_VOLUMEUP = 0xB0, /* Volume + */ + DIK_WEBHOME = 0xB2, /* Web home */ + DIK_NUMPADCOMMA = 0xB3, /* , on numeric keypad (NEC PC98) */ + DIK_DIVIDE = 0xB5, /* / on numeric keypad */ + DIK_SYSRQ = 0xB7, + DIK_RMENU = 0xB8, /* right Alt */ + DIK_PAUSE = 0xC5, /* Pause */ + DIK_HOME = 0xC7, /* Home on arrow keypad */ + DIK_UP = 0xC8, /* UpArrow on arrow keypad */ + DIK_PRIOR = 0xC9, /* PgUp on arrow keypad */ + DIK_LEFT = 0xCB, /* LeftArrow on arrow keypad */ + DIK_RIGHT = 0xCD, /* RightArrow on arrow keypad */ + DIK_END = 0xCF, /* End on arrow keypad */ + DIK_DOWN = 0xD0, /* DownArrow on arrow keypad */ + DIK_NEXT = 0xD1, /* PgDn on arrow keypad */ + DIK_INSERT = 0xD2, /* Insert on arrow keypad */ + DIK_DELETE = 0xD3, /* Delete on arrow keypad */ + DIK_LWIN = 0xDB, /* Left Windows key */ + DIK_RWIN = 0xDC, /* Right Windows key */ + DIK_APPS = 0xDD, /* AppMenu key */ + DIK_POWER = 0xDE, /* System Power */ + DIK_SLEEP = 0xDF, /* System Sleep */ + DIK_WAKE = 0xE3, /* System Wake */ + DIK_WEBSEARCH = 0xE5, /* Web Search */ + DIK_WEBFAVORITES = 0xE6, /* Web Favorites */ + DIK_WEBREFRESH = 0xE7, /* Web Refresh */ + DIK_WEBSTOP = 0xE8, /* Web Stop */ + DIK_WEBFORWARD = 0xE9, /* Web Forward */ + DIK_WEBBACK = 0xEA, /* Web Back */ + DIK_MYCOMPUTER = 0xEB, /* My Computer */ + DIK_MAIL = 0xEC, /* Mail */ + DIK_MEDIASELECT = 0xED /* Media Select */ +}; diff --git a/src/sdl/hardware.h b/src/posix/hardware.h similarity index 97% rename from src/sdl/hardware.h rename to src/posix/hardware.h index 6544f1498..618941fe5 100644 --- a/src/sdl/hardware.h +++ b/src/posix/hardware.h @@ -1,96 +1,96 @@ -/* -** hardware.h -** -**--------------------------------------------------------------------------- -** Copyright 1998-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. -** -** 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 __HARDWARE_H__ -#define __HARDWARE_H__ - -#include "i_video.h" -#include "v_video.h" - -// Semaphores -#ifdef __APPLE__ -#include -#include -#include -typedef semaphore_t Semaphore; -#define SEMAPHORE_WAIT(sem) \ - while(semaphore_wait(sem) != KERN_SUCCESS){} -#define SEMAPHORE_SIGNAL(sem) \ - semaphore_signal(sem); -#define SEMAPHORE_INIT(sem, shared, value) \ - semaphore_create(mach_task_self(), &sem, shared, value); -#else -#include -typedef sem_t Semaphore; -#define SEMAPHORE_WAIT(sem) \ - do { \ - while(sem_wait(&sem) != 0); \ - int semValue; \ - sem_getvalue(&sem, &semValue); \ - if(semValue < 1) \ - break; \ - } while(true); -#define SEMAPHORE_SIGNAL(sem) \ - sem_post(&sem); -#define SEMAPHORE_INIT(sem, shared, value) \ - sem_init(&sem, shared, value); -#endif - -class IVideo -{ - public: - virtual ~IVideo () {} - - virtual EDisplayType GetDisplayType () = 0; - virtual void SetWindowedScale (float scale) = 0; - - virtual DFrameBuffer *CreateFrameBuffer (int width, int height, bool fs, DFrameBuffer *old) = 0; - - virtual void StartModeIterator (int bits, bool fs) = 0; - virtual bool NextMode (int *width, int *height, bool *letterbox) = 0; - - virtual bool SetResolution (int width, int height, int bits); - - virtual void DumpAdapters(); -}; - -void I_InitGraphics (); -void I_ShutdownGraphics (); -void I_CreateRenderer(); - -extern Semaphore FPSLimitSemaphore; -void I_SetFPSLimit(int limit); - -extern IVideo *Video; - -#endif // __HARDWARE_H__ +/* +** hardware.h +** +**--------------------------------------------------------------------------- +** Copyright 1998-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. +** +** 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 __HARDWARE_H__ +#define __HARDWARE_H__ + +#include "i_video.h" +#include "v_video.h" + +// Semaphores +#ifdef __APPLE__ +#include +#include +#include +typedef semaphore_t Semaphore; +#define SEMAPHORE_WAIT(sem) \ + while(semaphore_wait(sem) != KERN_SUCCESS){} +#define SEMAPHORE_SIGNAL(sem) \ + semaphore_signal(sem); +#define SEMAPHORE_INIT(sem, shared, value) \ + semaphore_create(mach_task_self(), &sem, shared, value); +#else +#include +typedef sem_t Semaphore; +#define SEMAPHORE_WAIT(sem) \ + do { \ + while(sem_wait(&sem) != 0); \ + int semValue; \ + sem_getvalue(&sem, &semValue); \ + if(semValue < 1) \ + break; \ + } while(true); +#define SEMAPHORE_SIGNAL(sem) \ + sem_post(&sem); +#define SEMAPHORE_INIT(sem, shared, value) \ + sem_init(&sem, shared, value); +#endif + +class IVideo +{ + public: + virtual ~IVideo () {} + + virtual EDisplayType GetDisplayType () = 0; + virtual void SetWindowedScale (float scale) = 0; + + virtual DFrameBuffer *CreateFrameBuffer (int width, int height, bool fs, DFrameBuffer *old) = 0; + + virtual void StartModeIterator (int bits, bool fs) = 0; + virtual bool NextMode (int *width, int *height, bool *letterbox) = 0; + + virtual bool SetResolution (int width, int height, int bits); + + virtual void DumpAdapters(); +}; + +void I_InitGraphics (); +void I_ShutdownGraphics (); +void I_CreateRenderer(); + +extern Semaphore FPSLimitSemaphore; +void I_SetFPSLimit(int limit); + +extern IVideo *Video; + +#endif // __HARDWARE_H__ diff --git a/src/sdl/i_cd.cpp b/src/posix/i_cd.cpp similarity index 95% rename from src/sdl/i_cd.cpp rename to src/posix/i_cd.cpp index 83f1e8c89..96b59934c 100644 --- a/src/sdl/i_cd.cpp +++ b/src/posix/i_cd.cpp @@ -1,154 +1,154 @@ -#include "i_cd.h" - -//========================================================================== -// -// CD_Init -// -//========================================================================== - -bool CD_Init () -{ - return false; -} - -bool CD_Init (int device) -{ - return false; -} - -//========================================================================== -// -// CD_InitID -// -//========================================================================== - -bool CD_InitID (unsigned int id, int guess) -{ - return false; -} - -//========================================================================== -// -// CD_Close -// -//========================================================================== - -void CD_Close () -{ -} - -//========================================================================== -// -// CD_Eject -// -//========================================================================== - -void CD_Eject () -{ -} - -//========================================================================== -// -// CD_UnEject -// -//========================================================================== - -bool CD_UnEject () -{ - return false; -} - -//========================================================================== -// -// CD_Stop -// -//========================================================================== - -void CD_Stop () -{ -} - -//========================================================================== -// -// CD_Play -// -//========================================================================== - -bool CD_Play (int track, bool looping) -{ - return false; -} - -//========================================================================== -// -// CD_PlayNoWait -// -//========================================================================== - -void CD_PlayNoWait (int track, bool looping) -{ -} - -//========================================================================== -// -// CD_PlayCD -// -//========================================================================== - -bool CD_PlayCD (bool looping) -{ - return false; -} - -//========================================================================== -// -// CD_PlayCDNoWait -// -//========================================================================== - -void CD_PlayCDNoWait (bool looping) -{ -} - -//========================================================================== -// -// CD_Pause -// -//========================================================================== - -void CD_Pause () -{ -} - -//========================================================================== -// -// CD_Resume -// -//========================================================================== - -bool CD_Resume () -{ - return false; -} - -//========================================================================== -// -// CD_GetMode -// -//========================================================================== - -ECDModes CD_GetMode () -{ - return CDMode_Unknown; -} - -//========================================================================== -// -// CD_CheckTrack -// -//========================================================================== - -bool CD_CheckTrack (int track) -{ - return false; -} +#include "i_cd.h" + +//========================================================================== +// +// CD_Init +// +//========================================================================== + +bool CD_Init () +{ + return false; +} + +bool CD_Init (int device) +{ + return false; +} + +//========================================================================== +// +// CD_InitID +// +//========================================================================== + +bool CD_InitID (unsigned int id, int guess) +{ + return false; +} + +//========================================================================== +// +// CD_Close +// +//========================================================================== + +void CD_Close () +{ +} + +//========================================================================== +// +// CD_Eject +// +//========================================================================== + +void CD_Eject () +{ +} + +//========================================================================== +// +// CD_UnEject +// +//========================================================================== + +bool CD_UnEject () +{ + return false; +} + +//========================================================================== +// +// CD_Stop +// +//========================================================================== + +void CD_Stop () +{ +} + +//========================================================================== +// +// CD_Play +// +//========================================================================== + +bool CD_Play (int track, bool looping) +{ + return false; +} + +//========================================================================== +// +// CD_PlayNoWait +// +//========================================================================== + +void CD_PlayNoWait (int track, bool looping) +{ +} + +//========================================================================== +// +// CD_PlayCD +// +//========================================================================== + +bool CD_PlayCD (bool looping) +{ + return false; +} + +//========================================================================== +// +// CD_PlayCDNoWait +// +//========================================================================== + +void CD_PlayCDNoWait (bool looping) +{ +} + +//========================================================================== +// +// CD_Pause +// +//========================================================================== + +void CD_Pause () +{ +} + +//========================================================================== +// +// CD_Resume +// +//========================================================================== + +bool CD_Resume () +{ + return false; +} + +//========================================================================== +// +// CD_GetMode +// +//========================================================================== + +ECDModes CD_GetMode () +{ + return CDMode_Unknown; +} + +//========================================================================== +// +// CD_CheckTrack +// +//========================================================================== + +bool CD_CheckTrack (int track) +{ + return false; +} diff --git a/src/sdl/i_input.h b/src/posix/i_input.h similarity index 95% rename from src/sdl/i_input.h rename to src/posix/i_input.h index 124c2ca85..07ee1115d 100644 --- a/src/sdl/i_input.h +++ b/src/posix/i_input.h @@ -1,10 +1,10 @@ -#ifndef __I_INPUT_H__ -#define __I_INPUT_H__ - -void I_PutInClipboard (const char *str); -FString I_GetFromClipboard (bool use_primary_selection); -void I_SetMouseCapture(); -void I_ReleaseMouseCapture(); - -#endif - +#ifndef __I_INPUT_H__ +#define __I_INPUT_H__ + +void I_PutInClipboard (const char *str); +FString I_GetFromClipboard (bool use_primary_selection); +void I_SetMouseCapture(); +void I_ReleaseMouseCapture(); + +#endif + diff --git a/src/sdl/i_movie.cpp b/src/posix/i_movie.cpp similarity index 92% rename from src/sdl/i_movie.cpp rename to src/posix/i_movie.cpp index 020541516..8aea4b500 100644 --- a/src/sdl/i_movie.cpp +++ b/src/posix/i_movie.cpp @@ -1,7 +1,7 @@ -#include "i_movie.h" - -int I_PlayMovie (const char *movie) -{ - return MOVIE_Failed; -} - +#include "i_movie.h" + +int I_PlayMovie (const char *movie) +{ + return MOVIE_Failed; +} + diff --git a/src/posix/i_steam.cpp b/src/posix/i_steam.cpp new file mode 100644 index 000000000..9819bc09e --- /dev/null +++ b/src/posix/i_steam.cpp @@ -0,0 +1,232 @@ +/* +** 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 + +#ifdef __APPLE__ +#include +#endif // __APPLE__ + +#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 appSupportPath; + + { + char cpath[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + appSupportPath = cpath; + } + } + + FString regPath = appSupportPath + "/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(appSupportPath + "/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/posix/i_system.cpp similarity index 73% rename from src/sdl/i_system.cpp rename to src/posix/i_system.cpp index 8fea36c37..c2ce4aa9f 100644 --- a/src/sdl/i_system.cpp +++ b/src/posix/i_system.cpp @@ -41,7 +41,6 @@ #include "doomerrors.h" #include -#include "SDL.h" #include "doomtype.h" #include "doomstat.h" #include "version.h" @@ -71,12 +70,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 +87,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,185 +113,6 @@ void I_EndRead(void) } -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_WaitVBL (int count) { // I_WaitVBL is never used to actually synchronize to the @@ -322,6 +134,9 @@ void SetLanguageIDs () LanguageIDs[3] = LanguageIDs[2] = LanguageIDs[1] = LanguageIDs[0] = lang; } +void I_InitTimer (); +void I_ShutdownTimer (); + // // I_Init // @@ -330,11 +145,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 (); } // @@ -350,6 +163,8 @@ void I_Quit (void) G_CheckDemoStatus(); C_DeinitConsole(); + + I_ShutdownTimer(); } @@ -785,6 +600,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) @@ -805,6 +624,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 } @@ -826,6 +662,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 ""; } @@ -851,104 +742,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/posix/i_system.h similarity index 95% rename from src/sdl/i_system.h rename to src/posix/i_system.h index a3341f4c5..abda490c4 100644 --- a/src/sdl/i_system.h +++ b/src/posix/i_system.h @@ -1,166 +1,170 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// $Id:$ -// -// Copyright (C) 1993-1996 by id Software, Inc. -// -// This source is available for distribution and/or modification -// only under the terms of the DOOM Source Code License as -// published by id Software. All rights reserved. -// -// The source is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License -// for more details. -// -// DESCRIPTION: -// System specific interface stuff. -// -//----------------------------------------------------------------------------- - - -#ifndef __I_SYSTEM__ -#define __I_SYSTEM__ - -#include -#include - -#include "doomtype.h" - -struct ticcmd_t; -struct WadStuff; - -#ifndef SHARE_DIR -#define SHARE_DIR "/usr/local/share/" -#endif - -// Index values into the LanguageIDs array -enum -{ - LANGIDX_UserPreferred, - LANGIDX_UserDefault, - LANGIDX_SysPreferred, - LANGIDX_SysDefault -}; -extern DWORD LanguageIDs[4]; -extern void SetLanguageIDs (); - -// Called by DoomMain. -void I_Init (void); - -// Called by D_DoomLoop, -// returns current time in tics. -extern int (*I_GetTime) (bool saveMS); - -// like I_GetTime, except it waits for a new tic before returning -extern int (*I_WaitForTic) (int); - -// Freezes tic counting temporarily. While frozen, calls to I_GetTime() -// will always return the same value. This does not affect I_MSTime(). -// You must also not call I_WaitForTic() while freezing time, since the -// tic will never arrive (unless it's the current one). -extern void (*I_FreezeTime) (bool frozen); - -fixed_t I_GetTimeFrac (uint32 *ms); - -// Return a seed value for the RNG. -unsigned int I_MakeRNGSeed(); - - -// -// Called by D_DoomLoop, -// called before processing any tics in a frame -// (just after displaying a frame). -// Time consuming syncronous operations -// are performed here (joystick reading). -// Can call D_PostEvent. -// -void I_StartFrame (void); - - -// -// Called by D_DoomLoop, -// called before processing each tic in a frame. -// Quick syncronous operations are performed here. -// Can call D_PostEvent. -void I_StartTic (void); - -// Asynchronous interrupt functions should maintain private queues -// that are read by the synchronous functions -// to be converted into events. - -// Either returns a null ticcmd, -// or calls a loadable driver to build it. -// This ticcmd will then be modified by the gameloop -// for normal input. -ticcmd_t *I_BaseTiccmd (void); - - -// Called by M_Responder when quit is selected. -// Clean exit, displays sell blurb. -void I_Quit (void); - - -void I_Tactile (int on, int off, int total); - -void STACK_ARGS I_Error (const char *error, ...) GCCPRINTF(1,2); -void STACK_ARGS I_FatalError (const char *error, ...) GCCPRINTF(1,2); - -void addterm (void (*func)(void), const char *name); -#define atterm(t) addterm (t, #t) -void popterm (); - -// Print a console string -void I_PrintStr (const char *str); - -// Set the title string of the startup window -void I_SetIWADInfo (); - -// Pick from multiple IWADs to use -int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad); - -// The ini could not be saved at exit -bool I_WriteIniFailed (); - -// [RH] Returns millisecond-accurate time -unsigned int I_MSTime (void); -unsigned int I_FPSTime(); - -class FTexture; -bool I_SetCursor(FTexture *); - -// Directory searching routines - -struct findstate_t -{ - int count; - struct dirent **namelist; - int current; -}; - -void *I_FindFirst (const char *filespec, findstate_t *fileinfo); -int I_FindNext (void *handle, findstate_t *fileinfo); -int I_FindClose (void *handle); -int I_FindAttr (findstate_t *fileinfo); - -#define I_FindName(a) ((a)->namelist[(a)->current]->d_name) - -#define FA_RDONLY 1 -#define FA_HIDDEN 2 -#define FA_SYSTEM 4 -#define FA_DIREC 8 -#define FA_ARCH 16 - -static inline char *strlwr(char *str) -{ - char *ptr = str; - while(*ptr) - { - *ptr = tolower(*ptr); - ++ptr; - } - return str; -} - -#endif +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id:$ +// +// Copyright (C) 1993-1996 by id Software, Inc. +// +// This source is available for distribution and/or modification +// only under the terms of the DOOM Source Code License as +// published by id Software. All rights reserved. +// +// The source is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License +// for more details. +// +// DESCRIPTION: +// System specific interface stuff. +// +//----------------------------------------------------------------------------- + + +#ifndef __I_SYSTEM__ +#define __I_SYSTEM__ + +#include +#include + +#include "doomtype.h" + +struct ticcmd_t; +struct WadStuff; + +#ifndef SHARE_DIR +#define SHARE_DIR "/usr/local/share/" +#endif + +// Index values into the LanguageIDs array +enum +{ + LANGIDX_UserPreferred, + LANGIDX_UserDefault, + LANGIDX_SysPreferred, + LANGIDX_SysDefault +}; +extern DWORD LanguageIDs[4]; +extern void SetLanguageIDs (); + +// Called by DoomMain. +void I_Init (void); + +// Called by D_DoomLoop, +// returns current time in tics. +extern int (*I_GetTime) (bool saveMS); + +// like I_GetTime, except it waits for a new tic before returning +extern int (*I_WaitForTic) (int); + +// Freezes tic counting temporarily. While frozen, calls to I_GetTime() +// will always return the same value. This does not affect I_MSTime(). +// You must also not call I_WaitForTic() while freezing time, since the +// tic will never arrive (unless it's the current one). +extern void (*I_FreezeTime) (bool frozen); + +fixed_t I_GetTimeFrac (uint32 *ms); + +// Return a seed value for the RNG. +unsigned int I_MakeRNGSeed(); + + +// +// Called by D_DoomLoop, +// called before processing any tics in a frame +// (just after displaying a frame). +// Time consuming syncronous operations +// are performed here (joystick reading). +// Can call D_PostEvent. +// +void I_StartFrame (void); + + +// +// Called by D_DoomLoop, +// called before processing each tic in a frame. +// Quick syncronous operations are performed here. +// Can call D_PostEvent. +void I_StartTic (void); + +// Asynchronous interrupt functions should maintain private queues +// that are read by the synchronous functions +// to be converted into events. + +// Either returns a null ticcmd, +// or calls a loadable driver to build it. +// This ticcmd will then be modified by the gameloop +// for normal input. +ticcmd_t *I_BaseTiccmd (void); + + +// Called by M_Responder when quit is selected. +// Clean exit, displays sell blurb. +void I_Quit (void); + + +void I_Tactile (int on, int off, int total); + +void STACK_ARGS I_Error (const char *error, ...) GCCPRINTF(1,2); +void STACK_ARGS I_FatalError (const char *error, ...) GCCPRINTF(1,2); + +void addterm (void (*func)(void), const char *name); +#define atterm(t) addterm (t, #t) +void popterm (); + +// Print a console string +void I_PrintStr (const char *str); + +// Set the title string of the startup window +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 (); + +// [RH] Returns millisecond-accurate time +unsigned int I_MSTime (void); +unsigned int I_FPSTime(); + +class FTexture; +bool I_SetCursor(FTexture *); + +// Directory searching routines + +struct findstate_t +{ + int count; + struct dirent **namelist; + int current; +}; + +void *I_FindFirst (const char *filespec, findstate_t *fileinfo); +int I_FindNext (void *handle, findstate_t *fileinfo); +int I_FindClose (void *handle); +int I_FindAttr (findstate_t *fileinfo); + +#define I_FindName(a) ((a)->namelist[(a)->current]->d_name) + +#define FA_RDONLY 1 +#define FA_HIDDEN 2 +#define FA_SYSTEM 4 +#define FA_DIREC 8 +#define FA_ARCH 16 + +static inline char *strlwr(char *str) +{ + char *ptr = str; + while(*ptr) + { + *ptr = tolower(*ptr); + ++ptr; + } + return str; +} + +#endif diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/posix/osx/iwadpicker_cocoa.mm similarity index 52% rename from src/sdl/iwadpicker_cocoa.mm rename to src/posix/osx/iwadpicker_cocoa.mm index 3b414d5e8..d2bc1ff0c 100644 --- a/src/sdl/iwadpicker_cocoa.mm +++ b/src/posix/osx/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,172 @@ 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 + { + NSString* executablePath = [NSString stringWithUTF8String:Args->GetArg(0)]; + + NSMutableArray* const arguments = [[NSMutableArray alloc] init]; + + // The following value shoud be equal to NSAppKitVersionNumber10_5 + // It's hard-coded in order to build with earlier SDKs + const bool canSelectArchitecture = NSAppKitVersionNumber >= 949; + + if (canSelectArchitecture) + { + [arguments addObject:@"-arch"]; + [arguments addObject:GetArchitectureString()]; + [arguments addObject:executablePath]; + + executablePath = @"/usr/bin/arch"; + } + + [arguments addObject:@"-wad_picker_restart"]; + [arguments addObject:@"-iwad"]; + [arguments addObject:[NSString stringWithUTF8String:iwadPath]]; + + for (int i = 1, count = Args->NumArgs(); i < count; ++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:executablePath + 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/posix/osx/zdoom-info.plist b/src/posix/osx/zdoom-info.plist new file mode 100644 index 000000000..2a1911cdf --- /dev/null +++ b/src/posix/osx/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/posix/osx/zdoom.icns b/src/posix/osx/zdoom.icns new file mode 100644 index 000000000..decb4c5ab Binary files /dev/null and b/src/posix/osx/zdoom.icns differ diff --git a/src/posix/readme.md b/src/posix/readme.md new file mode 100644 index 000000000..ce66dab2c --- /dev/null +++ b/src/posix/readme.md @@ -0,0 +1,6 @@ +This directory contains files required to support POSIX-compatible OSes, like GNU/Linux, OS X or BSD. + +Common files are placed in this directory directly. +SDL backend files are in `sdl` subdirectory. +Native OS X backend files are in `cocoa` subdirectory. +Shared files for both OS X backends are in `osx` subdirectory. diff --git a/src/sdl/crashcatcher.c b/src/posix/sdl/crashcatcher.c similarity index 95% rename from src/sdl/crashcatcher.c rename to src/posix/sdl/crashcatcher.c index a4f68d9a3..4754a369a 100644 --- a/src/sdl/crashcatcher.c +++ b/src/posix/sdl/crashcatcher.c @@ -1,429 +1,429 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#ifndef PR_SET_PTRACER -#define PR_SET_PTRACER 0x59616d61 -#endif -#elif defined (__APPLE__) -#include -#endif - - -static const char crash_switch[] = "--cc-handle-crash"; - -static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; -static const char pipe_err[] = "!!! Failed to create pipe\n"; -static const char fork_err[] = "!!! Failed to fork debug process\n"; -static const char exec_err[] = "!!! Failed to exec debug process\n"; - -static char argv0[PATH_MAX]; - -static char altstack[SIGSTKSZ]; - - -static struct { - int signum; - pid_t pid; - int has_siginfo; - siginfo_t siginfo; - char buf[1024]; -} crash_info; - - -static const struct { - const char *name; - int signum; -} signals[] = { - { "Segmentation fault", SIGSEGV }, - { "Illegal instruction", SIGILL }, - { "FPU exception", SIGFPE }, - { "System BUS error", SIGBUS }, - { NULL, 0 } -}; - -static const struct { - int code; - const char *name; -} sigill_codes[] = { -#ifndef __FreeBSD__ - { ILL_ILLOPC, "Illegal opcode" }, - { ILL_ILLOPN, "Illegal operand" }, - { ILL_ILLADR, "Illegal addressing mode" }, - { ILL_ILLTRP, "Illegal trap" }, - { ILL_PRVOPC, "Privileged opcode" }, - { ILL_PRVREG, "Privileged register" }, - { ILL_COPROC, "Coprocessor error" }, - { ILL_BADSTK, "Internal stack error" }, -#endif - { 0, NULL } -}; - -static const struct { - int code; - const char *name; -} sigfpe_codes[] = { - { FPE_INTDIV, "Integer divide by zero" }, - { FPE_INTOVF, "Integer overflow" }, - { FPE_FLTDIV, "Floating point divide by zero" }, - { FPE_FLTOVF, "Floating point overflow" }, - { FPE_FLTUND, "Floating point underflow" }, - { FPE_FLTRES, "Floating point inexact result" }, - { FPE_FLTINV, "Floating point invalid operation" }, - { FPE_FLTSUB, "Subscript out of range" }, - { 0, NULL } -}; - -static const struct { - int code; - const char *name; -} sigsegv_codes[] = { -#ifndef __FreeBSD__ - { SEGV_MAPERR, "Address not mapped to object" }, - { SEGV_ACCERR, "Invalid permissions for mapped object" }, -#endif - { 0, NULL } -}; - -static const struct { - int code; - const char *name; -} sigbus_codes[] = { -#ifndef __FreeBSD__ - { BUS_ADRALN, "Invalid address alignment" }, - { BUS_ADRERR, "Non-existent physical address" }, - { BUS_OBJERR, "Object specific hardware error" }, -#endif - { 0, NULL } -}; - -static int (*cc_user_info)(char*, char*); - - -static void gdb_info(pid_t pid) -{ - char respfile[64]; - char cmd_buf[128]; - FILE *f; - int fd; - - /* Create a temp file to put gdb commands into */ - strcpy(respfile, "gdb-respfile-XXXXXX"); - if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) - { - fprintf(f, "attach %d\n" - "shell echo \"\"\n" - "shell echo \"* Loaded Libraries\"\n" - "info sharedlibrary\n" - "shell echo \"\"\n" - "shell echo \"* Threads\"\n" - "info threads\n" - "shell echo \"\"\n" - "shell echo \"* FPU Status\"\n" - "info float\n" - "shell echo \"\"\n" - "shell echo \"* Registers\"\n" - "info registers\n" - "shell echo \"\"\n" - "shell echo \"* Backtrace\"\n" - "thread apply all backtrace full\n" - "detach\n" - "quit\n", pid); - fclose(f); - - /* Run gdb and print process info. */ - snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); - printf("Executing: %s\n", cmd_buf); - fflush(stdout); - - system(cmd_buf); - /* Clean up */ - remove(respfile); - } - else - { - /* Error creating temp file */ - if(fd >= 0) - { - close(fd); - remove(respfile); - } - printf("!!! Could not create gdb command file\n"); - } - fflush(stdout); -} - -static void sys_info(void) -{ -#ifdef __unix__ - system("echo \"System: `uname -a`\""); - putchar('\n'); - fflush(stdout); -#endif -} - - -static size_t safe_write(int fd, const void *buf, size_t len) -{ - size_t ret = 0; - while(ret < len) - { - ssize_t rem; - if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) - { - if(errno == EINTR) - continue; - break; - } - ret += rem; - } - return ret; -} - -static void crash_catcher(int signum, siginfo_t *siginfo, void *context) -{ - //ucontext_t *ucontext = (ucontext_t*)context; - pid_t dbg_pid; - int fd[2]; - - /* Make sure the effective uid is the real uid */ - if(getuid() != geteuid()) - { - raise(signum); - return; - } - - safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); - if(pipe(fd) == -1) - { - safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); - raise(signum); - return; - } - - crash_info.signum = signum; - crash_info.pid = getpid(); - crash_info.has_siginfo = !!siginfo; - if(siginfo) - crash_info.siginfo = *siginfo; - if(cc_user_info) - cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); - - /* Fork off to start a crash handler */ - switch((dbg_pid=fork())) - { - /* Error */ - case -1: - safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); - raise(signum); - return; - - case 0: - dup2(fd[0], STDIN_FILENO); - close(fd[0]); - close(fd[1]); - - execl(argv0, argv0, crash_switch, NULL); - - safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); - _exit(1); - - default: -#ifdef __linux__ - prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); -#endif - safe_write(fd[1], &crash_info, sizeof(crash_info)); - close(fd[0]); - close(fd[1]); - - /* Wait; we'll be killed when gdb is done */ - do { - int status; - if(waitpid(dbg_pid, &status, 0) == dbg_pid && - (WIFEXITED(status) || WIFSIGNALED(status))) - { - /* The debug process died before it could kill us */ - raise(signum); - break; - } - } while(1); - } -} - -static void crash_handler(const char *logfile) -{ - const char *sigdesc = ""; - int i; - - if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) - { - fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); - exit(1); - } - - /* Get the signal description */ - for(i = 0;signals[i].name;++i) - { - if(signals[i].signum == crash_info.signum) - { - sigdesc = signals[i].name; - break; - } - } - - if(crash_info.has_siginfo) - { - switch(crash_info.signum) - { - case SIGSEGV: - for(i = 0;sigsegv_codes[i].name;++i) - { - if(sigsegv_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigsegv_codes[i].name; - break; - } - } - break; - - case SIGFPE: - for(i = 0;sigfpe_codes[i].name;++i) - { - if(sigfpe_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigfpe_codes[i].name; - break; - } - } - break; - - case SIGILL: - for(i = 0;sigill_codes[i].name;++i) - { - if(sigill_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigill_codes[i].name; - break; - } - } - break; - - case SIGBUS: - for(i = 0;sigbus_codes[i].name;++i) - { - if(sigbus_codes[i].code == crash_info.siginfo.si_code) - { - sigdesc = sigbus_codes[i].name; - break; - } - } - break; - } - } - fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); - if(crash_info.has_siginfo) - fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); - fputc('\n', stderr); - - if(logfile) - { - /* Create crash log file and redirect shell output to it */ - if(freopen(logfile, "wa", stdout) != stdout) - { - fprintf(stderr, "!!! Could not create %s following signal\n", logfile); - exit(1); - } - fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); - - printf("*** Fatal Error ***\n" - "%s (signal %i)\n", sigdesc, crash_info.signum); - if(crash_info.has_siginfo) - printf("Address: %p\n", crash_info.siginfo.si_addr); - fputc('\n', stdout); - fflush(stdout); - } - - sys_info(); - - crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; - printf("%s\n", crash_info.buf); - fflush(stdout); - - if(crash_info.pid > 0) - { - gdb_info(crash_info.pid); - kill(crash_info.pid, SIGKILL); - } - - if(logfile) - { - const char *str; - char buf[512]; - - if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) - snprintf(buf, sizeof(buf), "kdialog --title \"Very Fatal Error\" --textbox \"%s\" 800 600", logfile); - else if((str=getenv("GNOME_DESKTOP_SESSION_ID")) && str[0] != '\0') - snprintf(buf, sizeof(buf), "gxmessage -buttons \"Okay:0\" -geometry 800x600 -title \"Very Fatal Error\" -center -file \"%s\"", logfile); - else - snprintf(buf, sizeof(buf), "xmessage -buttons \"Okay:0\" -center -file \"%s\"", logfile); - - system(buf); - } - exit(0); -} - -int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) -{ - struct sigaction sa; - stack_t altss; - int retval; - - if(argc == 2 && strcmp(argv[1], crash_switch) == 0) - crash_handler(logfile); - - cc_user_info = user_info; - - if(argv[0][0] == '/') - snprintf(argv0, sizeof(argv0), "%s", argv[0]); - else - { - getcwd(argv0, sizeof(argv0)); - retval = strlen(argv0); - snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); - } - - /* Set an alternate signal stack so SIGSEGVs caused by stack overflows - * still run */ - altss.ss_sp = altstack; - altss.ss_flags = 0; - altss.ss_size = sizeof(altstack); - sigaltstack(&altss, NULL); - - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = crash_catcher; - sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; - sigemptyset(&sa.sa_mask); - - retval = 0; - while(num_signals--) - { - if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && - *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) - { - *signals = 0; - retval = -1; - } - ++signals; - } - return retval; -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#elif defined (__APPLE__) +#include +#endif + + +static const char crash_switch[] = "--cc-handle-crash"; + +static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; +static const char pipe_err[] = "!!! Failed to create pipe\n"; +static const char fork_err[] = "!!! Failed to fork debug process\n"; +static const char exec_err[] = "!!! Failed to exec debug process\n"; + +static char argv0[PATH_MAX]; + +static char altstack[SIGSTKSZ]; + + +static struct { + int signum; + pid_t pid; + int has_siginfo; + siginfo_t siginfo; + char buf[1024]; +} crash_info; + + +static const struct { + const char *name; + int signum; +} signals[] = { + { "Segmentation fault", SIGSEGV }, + { "Illegal instruction", SIGILL }, + { "FPU exception", SIGFPE }, + { "System BUS error", SIGBUS }, + { NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { +#ifndef __FreeBSD__ + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { +#ifndef __FreeBSD__ + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { +#ifndef __FreeBSD__ + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, +#endif + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + + +static void gdb_info(pid_t pid) +{ + char respfile[64]; + char cmd_buf[128]; + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + { + fprintf(f, "attach %d\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "thread apply all backtrace full\n" + "detach\n" + "quit\n", pid); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); + printf("Executing: %s\n", cmd_buf); + fflush(stdout); + + system(cmd_buf); + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("!!! Could not create gdb command file\n"); + } + fflush(stdout); +} + +static void sys_info(void) +{ +#ifdef __unix__ + system("echo \"System: `uname -a`\""); + putchar('\n'); + fflush(stdout); +#endif +} + + +static size_t safe_write(int fd, const void *buf, size_t len) +{ + size_t ret = 0; + while(ret < len) + { + ssize_t rem; + if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) + { + if(errno == EINTR) + continue; + break; + } + ret += rem; + } + return ret; +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ + //ucontext_t *ucontext = (ucontext_t*)context; + pid_t dbg_pid; + int fd[2]; + + /* Make sure the effective uid is the real uid */ + if(getuid() != geteuid()) + { + raise(signum); + return; + } + + safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); + if(pipe(fd) == -1) + { + safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); + raise(signum); + return; + } + + crash_info.signum = signum; + crash_info.pid = getpid(); + crash_info.has_siginfo = !!siginfo; + if(siginfo) + crash_info.siginfo = *siginfo; + if(cc_user_info) + cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); + + /* Fork off to start a crash handler */ + switch((dbg_pid=fork())) + { + /* Error */ + case -1: + safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); + raise(signum); + return; + + case 0: + dup2(fd[0], STDIN_FILENO); + close(fd[0]); + close(fd[1]); + + execl(argv0, argv0, crash_switch, NULL); + + safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); + _exit(1); + + default: +#ifdef __linux__ + prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); +#endif + safe_write(fd[1], &crash_info, sizeof(crash_info)); + close(fd[0]); + close(fd[1]); + + /* Wait; we'll be killed when gdb is done */ + do { + int status; + if(waitpid(dbg_pid, &status, 0) == dbg_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) + { + /* The debug process died before it could kill us */ + raise(signum); + break; + } + } while(1); + } +} + +static void crash_handler(const char *logfile) +{ + const char *sigdesc = ""; + int i; + + if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) + { + fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + exit(1); + } + + /* Get the signal description */ + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == crash_info.signum) + { + sigdesc = signals[i].name; + break; + } + } + + if(crash_info.has_siginfo) + { + switch(crash_info.signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stderr); + + if(logfile) + { + /* Create crash log file and redirect shell output to it */ + if(freopen(logfile, "wa", stdout) != stdout) + { + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); + } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf("*** Fatal Error ***\n" + "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + printf("Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stdout); + fflush(stdout); + } + + sys_info(); + + crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; + printf("%s\n", crash_info.buf); + fflush(stdout); + + if(crash_info.pid > 0) + { + gdb_info(crash_info.pid); + kill(crash_info.pid, SIGKILL); + } + + if(logfile) + { + const char *str; + char buf[512]; + + if((str=getenv("KDE_FULL_SESSION")) && strcmp(str, "true") == 0) + snprintf(buf, sizeof(buf), "kdialog --title \"Very Fatal Error\" --textbox \"%s\" 800 600", logfile); + else if((str=getenv("GNOME_DESKTOP_SESSION_ID")) && str[0] != '\0') + snprintf(buf, sizeof(buf), "gxmessage -buttons \"Okay:0\" -geometry 800x600 -title \"Very Fatal Error\" -center -file \"%s\"", logfile); + else + snprintf(buf, sizeof(buf), "xmessage -buttons \"Okay:0\" -center -file \"%s\"", logfile); + + system(buf); + } + exit(0); +} + +int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + struct sigaction sa; + stack_t altss; + int retval; + + if(argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(logfile); + + cc_user_info = user_info; + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else + { + getcwd(argv0, sizeof(argv0)); + retval = strlen(argv0); + snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); + } + + /* Set an alternate signal stack so SIGSEGVs caused by stack overflows + * still run */ + altss.ss_sp = altstack; + altss.ss_flags = 0; + altss.ss_size = sizeof(altstack); + sigaltstack(&altss, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + + retval = 0; + while(num_signals--) + { + if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && + *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + return retval; +} diff --git a/src/sdl/critsec.h b/src/posix/sdl/critsec.h similarity index 94% rename from src/sdl/critsec.h rename to src/posix/sdl/critsec.h index daaf30f7d..a3d6210af 100644 --- a/src/sdl/critsec.h +++ b/src/posix/sdl/critsec.h @@ -1,48 +1,48 @@ -// Wraps an SDL mutex object. (A critical section is a Windows synchronization -// object similar to a mutex but optimized for access by threads belonging to -// only one process, hence the class name.) - -#ifndef CRITSEC_H -#define CRITSEC_H - -#include "SDL.h" -#include "SDL_thread.h" -#include "i_system.h" - -class FCriticalSection -{ -public: - FCriticalSection() - { - CritSec = SDL_CreateMutex(); - if (CritSec == NULL) - { - I_FatalError("Failed to create a critical section mutex."); - } - } - ~FCriticalSection() - { - if (CritSec != NULL) - { - SDL_DestroyMutex(CritSec); - } - } - void Enter() - { - if (SDL_mutexP(CritSec) != 0) - { - I_FatalError("Failed entering a critical section."); - } - } - void Leave() - { - if (SDL_mutexV(CritSec) != 0) - { - I_FatalError("Failed to leave a critical section."); - } - } -private: - SDL_mutex *CritSec; -}; - -#endif +// Wraps an SDL mutex object. (A critical section is a Windows synchronization +// object similar to a mutex but optimized for access by threads belonging to +// only one process, hence the class name.) + +#ifndef CRITSEC_H +#define CRITSEC_H + +#include "SDL.h" +#include "SDL_thread.h" +#include "i_system.h" + +class FCriticalSection +{ +public: + FCriticalSection() + { + CritSec = SDL_CreateMutex(); + if (CritSec == NULL) + { + I_FatalError("Failed to create a critical section mutex."); + } + } + ~FCriticalSection() + { + if (CritSec != NULL) + { + SDL_DestroyMutex(CritSec); + } + } + void Enter() + { + if (SDL_mutexP(CritSec) != 0) + { + I_FatalError("Failed entering a critical section."); + } + } + void Leave() + { + if (SDL_mutexV(CritSec) != 0) + { + I_FatalError("Failed to leave a critical section."); + } + } +private: + SDL_mutex *CritSec; +}; + +#endif diff --git a/src/sdl/hardware.cpp b/src/posix/sdl/hardware.cpp similarity index 96% rename from src/sdl/hardware.cpp rename to src/posix/sdl/hardware.cpp index 352fe8558..25e19ff21 100644 --- a/src/sdl/hardware.cpp +++ b/src/posix/sdl/hardware.cpp @@ -1,330 +1,330 @@ -/* -** hardware.cpp -** Somewhat OS-independant interface to the screen, mouse, keyboard, and stick -** -**--------------------------------------------------------------------------- -** Copyright 1998-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. -** -** 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 "hardware.h" -#include "i_video.h" -#include "i_system.h" -#include "c_console.h" -#include "c_cvars.h" -#include "c_dispatch.h" -#include "sdlvideo.h" -#include "v_text.h" -#include "doomstat.h" -#include "m_argv.h" -#include "r_renderer.h" -#include "r_swrenderer.h" - -EXTERN_CVAR (Bool, ticker) -EXTERN_CVAR (Bool, fullscreen) -EXTERN_CVAR (Float, vid_winscale) - -IVideo *Video; - -void I_ShutdownGraphics () -{ - if (screen) - { - DFrameBuffer *s = screen; - screen = NULL; - s->ObjectFlags |= OF_YesReallyDelete; - delete s; - } - if (Video) - delete Video, Video = NULL; -} - -void I_InitGraphics () -{ - UCVarValue val; - - val.Bool = !!Args->CheckParm ("-devparm"); - ticker.SetGenericRepDefault (val, CVAR_Bool); - - Video = new SDLVideo (0); - if (Video == NULL) - I_FatalError ("Failed to initialize display"); - - atterm (I_ShutdownGraphics); - - Video->SetWindowedScale (vid_winscale); -} - -static void I_DeleteRenderer() -{ - if (Renderer != NULL) delete Renderer; -} - -void I_CreateRenderer() -{ - if (Renderer == NULL) - { - Renderer = new FSoftwareRenderer; - atterm(I_DeleteRenderer); - } -} - - -/** Remaining code is common to Win32 and Linux **/ - -// VIDEO WRAPPERS --------------------------------------------------------- - -DFrameBuffer *I_SetMode (int &width, int &height, DFrameBuffer *old) -{ - bool fs = false; - switch (Video->GetDisplayType ()) - { - case DISPLAY_WindowOnly: - fs = false; - break; - case DISPLAY_FullscreenOnly: - fs = true; - break; - case DISPLAY_Both: - fs = fullscreen; - break; - } - DFrameBuffer *res = Video->CreateFrameBuffer (width, height, fs, old); - - /* Right now, CreateFrameBuffer cannot return NULL - if (res == NULL) - { - I_FatalError ("Mode %dx%d is unavailable\n", width, height); - } - */ - return res; -} - -bool I_CheckResolution (int width, int height, int bits) -{ - int twidth, theight; - - Video->StartModeIterator (bits, screen ? screen->IsFullscreen() : fullscreen); - while (Video->NextMode (&twidth, &theight, NULL)) - { - if (width == twidth && height == theight) - return true; - } - return false; -} - -void I_ClosestResolution (int *width, int *height, int bits) -{ - int twidth, theight; - int cwidth = 0, cheight = 0; - int iteration; - DWORD closest = 4294967295u; - - for (iteration = 0; iteration < 2; iteration++) - { - Video->StartModeIterator (bits, screen ? screen->IsFullscreen() : fullscreen); - while (Video->NextMode (&twidth, &theight, NULL)) - { - if (twidth == *width && theight == *height) - return; - - if (iteration == 0 && (twidth < *width || theight < *height)) - continue; - - DWORD dist = (twidth - *width) * (twidth - *width) - + (theight - *height) * (theight - *height); - - if (dist < closest) - { - closest = dist; - cwidth = twidth; - cheight = theight; - } - } - if (closest != 4294967295u) - { - *width = cwidth; - *height = cheight; - return; - } - } -} - -//========================================================================== -// -// SetFPSLimit -// -// Initializes an event timer to fire at a rate of /sec. The video -// update will wait for this timer to trigger before updating. -// -// Pass 0 as the limit for unlimited. -// Pass a negative value for the limit to use the value of vid_maxfps. -// -//========================================================================== - -EXTERN_CVAR(Int, vid_maxfps); -EXTERN_CVAR(Bool, cl_capfps); - -#ifndef __APPLE__ -Semaphore FPSLimitSemaphore; - -static void FPSLimitNotify(sigval val) -{ - SEMAPHORE_SIGNAL(FPSLimitSemaphore) -} - -void I_SetFPSLimit(int limit) -{ - static sigevent FPSLimitEvent; - static timer_t FPSLimitTimer; - static bool FPSLimitTimerEnabled = false; - static bool EventSetup = false; - if(!EventSetup) - { - EventSetup = true; - FPSLimitEvent.sigev_notify = SIGEV_THREAD; - FPSLimitEvent.sigev_signo = 0; - FPSLimitEvent.sigev_value.sival_int = 0; - FPSLimitEvent.sigev_notify_function = FPSLimitNotify; - FPSLimitEvent.sigev_notify_attributes = NULL; - - SEMAPHORE_INIT(FPSLimitSemaphore, 0, 0) - } - - if (limit < 0) - { - limit = vid_maxfps; - } - // Kill any leftover timer. - if (FPSLimitTimerEnabled) - { - timer_delete(FPSLimitTimer); - FPSLimitTimerEnabled = false; - } - if (limit == 0) - { // no limit - DPrintf("FPS timer disabled\n"); - } - else - { - FPSLimitTimerEnabled = true; - if(timer_create(CLOCK_REALTIME, &FPSLimitEvent, &FPSLimitTimer) == -1) - Printf("Failed to create FPS limitter event\n"); - itimerspec period = { {0, 0}, {0, 0} }; - period.it_value.tv_nsec = period.it_interval.tv_nsec = 1000000000 / limit; - if(timer_settime(FPSLimitTimer, 0, &period, NULL) == -1) - Printf("Failed to set FPS limitter timer\n"); - DPrintf("FPS timer set to %u ms\n", (unsigned int) period.it_interval.tv_nsec / 1000000); - } -} -#else -// So Apple doesn't support POSIX timers and I can't find a good substitute short of -// having Objective-C Cocoa events or something like that. -void I_SetFPSLimit(int limit) -{ -} -#endif - -CUSTOM_CVAR (Int, vid_maxfps, 200, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -{ - if (vid_maxfps < TICRATE && vid_maxfps != 0) - { - vid_maxfps = TICRATE; - } - else if (vid_maxfps > 1000) - { - vid_maxfps = 1000; - } - else if (cl_capfps == 0) - { - I_SetFPSLimit(vid_maxfps); - } -} - -extern int NewWidth, NewHeight, NewBits, DisplayBits; - -CUSTOM_CVAR (Bool, fullscreen, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - NewWidth = screen->GetWidth(); - NewHeight = screen->GetHeight(); - NewBits = DisplayBits; - setmodeneeded = true; -} - -CUSTOM_CVAR (Float, vid_winscale, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (self < 1.f) - { - self = 1.f; - } - else if (Video) - { - Video->SetWindowedScale (self); - NewWidth = screen->GetWidth(); - NewHeight = screen->GetHeight(); - NewBits = DisplayBits; - setmodeneeded = true; - } -} - -CCMD (vid_listmodes) -{ - static const char *ratios[5] = { "", " - 16:9", " - 16:10", "", " - 5:4" }; - int width, height, bits; - bool letterbox; - - if (Video == NULL) - { - return; - } - for (bits = 1; bits <= 32; bits++) - { - Video->StartModeIterator (bits, screen->IsFullscreen()); - while (Video->NextMode (&width, &height, &letterbox)) - { - bool thisMode = (width == DisplayWidth && height == DisplayHeight && bits == DisplayBits); - int ratio = CheckRatio (width, height); - Printf (thisMode ? PRINT_BOLD : PRINT_HIGH, - "%s%4d x%5d x%3d%s%s\n", - thisMode || !(ratio & 3) ? "" : TEXTCOLOR_GOLD, - width, height, bits, - ratios[ratio], - thisMode || !letterbox ? "" : TEXTCOLOR_BROWN " LB" - ); - } - } -} - -CCMD (vid_currentmode) -{ - Printf ("%dx%dx%d\n", DisplayWidth, DisplayHeight, DisplayBits); -} +/* +** hardware.cpp +** Somewhat OS-independant interface to the screen, mouse, keyboard, and stick +** +**--------------------------------------------------------------------------- +** Copyright 1998-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. +** +** 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 "hardware.h" +#include "i_video.h" +#include "i_system.h" +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "sdlvideo.h" +#include "v_text.h" +#include "doomstat.h" +#include "m_argv.h" +#include "r_renderer.h" +#include "r_swrenderer.h" + +EXTERN_CVAR (Bool, ticker) +EXTERN_CVAR (Bool, fullscreen) +EXTERN_CVAR (Float, vid_winscale) + +IVideo *Video; + +void I_ShutdownGraphics () +{ + if (screen) + { + DFrameBuffer *s = screen; + screen = NULL; + s->ObjectFlags |= OF_YesReallyDelete; + delete s; + } + if (Video) + delete Video, Video = NULL; +} + +void I_InitGraphics () +{ + UCVarValue val; + + val.Bool = !!Args->CheckParm ("-devparm"); + ticker.SetGenericRepDefault (val, CVAR_Bool); + + Video = new SDLVideo (0); + if (Video == NULL) + I_FatalError ("Failed to initialize display"); + + atterm (I_ShutdownGraphics); + + Video->SetWindowedScale (vid_winscale); +} + +static void I_DeleteRenderer() +{ + if (Renderer != NULL) delete Renderer; +} + +void I_CreateRenderer() +{ + if (Renderer == NULL) + { + Renderer = new FSoftwareRenderer; + atterm(I_DeleteRenderer); + } +} + + +/** Remaining code is common to Win32 and Linux **/ + +// VIDEO WRAPPERS --------------------------------------------------------- + +DFrameBuffer *I_SetMode (int &width, int &height, DFrameBuffer *old) +{ + bool fs = false; + switch (Video->GetDisplayType ()) + { + case DISPLAY_WindowOnly: + fs = false; + break; + case DISPLAY_FullscreenOnly: + fs = true; + break; + case DISPLAY_Both: + fs = fullscreen; + break; + } + DFrameBuffer *res = Video->CreateFrameBuffer (width, height, fs, old); + + /* Right now, CreateFrameBuffer cannot return NULL + if (res == NULL) + { + I_FatalError ("Mode %dx%d is unavailable\n", width, height); + } + */ + return res; +} + +bool I_CheckResolution (int width, int height, int bits) +{ + int twidth, theight; + + Video->StartModeIterator (bits, screen ? screen->IsFullscreen() : fullscreen); + while (Video->NextMode (&twidth, &theight, NULL)) + { + if (width == twidth && height == theight) + return true; + } + return false; +} + +void I_ClosestResolution (int *width, int *height, int bits) +{ + int twidth, theight; + int cwidth = 0, cheight = 0; + int iteration; + DWORD closest = 4294967295u; + + for (iteration = 0; iteration < 2; iteration++) + { + Video->StartModeIterator (bits, screen ? screen->IsFullscreen() : fullscreen); + while (Video->NextMode (&twidth, &theight, NULL)) + { + if (twidth == *width && theight == *height) + return; + + if (iteration == 0 && (twidth < *width || theight < *height)) + continue; + + DWORD dist = (twidth - *width) * (twidth - *width) + + (theight - *height) * (theight - *height); + + if (dist < closest) + { + closest = dist; + cwidth = twidth; + cheight = theight; + } + } + if (closest != 4294967295u) + { + *width = cwidth; + *height = cheight; + return; + } + } +} + +//========================================================================== +// +// SetFPSLimit +// +// Initializes an event timer to fire at a rate of /sec. The video +// update will wait for this timer to trigger before updating. +// +// Pass 0 as the limit for unlimited. +// Pass a negative value for the limit to use the value of vid_maxfps. +// +//========================================================================== + +EXTERN_CVAR(Int, vid_maxfps); +EXTERN_CVAR(Bool, cl_capfps); + +#ifndef __APPLE__ +Semaphore FPSLimitSemaphore; + +static void FPSLimitNotify(sigval val) +{ + SEMAPHORE_SIGNAL(FPSLimitSemaphore) +} + +void I_SetFPSLimit(int limit) +{ + static sigevent FPSLimitEvent; + static timer_t FPSLimitTimer; + static bool FPSLimitTimerEnabled = false; + static bool EventSetup = false; + if(!EventSetup) + { + EventSetup = true; + FPSLimitEvent.sigev_notify = SIGEV_THREAD; + FPSLimitEvent.sigev_signo = 0; + FPSLimitEvent.sigev_value.sival_int = 0; + FPSLimitEvent.sigev_notify_function = FPSLimitNotify; + FPSLimitEvent.sigev_notify_attributes = NULL; + + SEMAPHORE_INIT(FPSLimitSemaphore, 0, 0) + } + + if (limit < 0) + { + limit = vid_maxfps; + } + // Kill any leftover timer. + if (FPSLimitTimerEnabled) + { + timer_delete(FPSLimitTimer); + FPSLimitTimerEnabled = false; + } + if (limit == 0) + { // no limit + DPrintf("FPS timer disabled\n"); + } + else + { + FPSLimitTimerEnabled = true; + if(timer_create(CLOCK_REALTIME, &FPSLimitEvent, &FPSLimitTimer) == -1) + Printf("Failed to create FPS limitter event\n"); + itimerspec period = { {0, 0}, {0, 0} }; + period.it_value.tv_nsec = period.it_interval.tv_nsec = 1000000000 / limit; + if(timer_settime(FPSLimitTimer, 0, &period, NULL) == -1) + Printf("Failed to set FPS limitter timer\n"); + DPrintf("FPS timer set to %u ms\n", (unsigned int) period.it_interval.tv_nsec / 1000000); + } +} +#else +// So Apple doesn't support POSIX timers and I can't find a good substitute short of +// having Objective-C Cocoa events or something like that. +void I_SetFPSLimit(int limit) +{ +} +#endif + +CUSTOM_CVAR (Int, vid_maxfps, 200, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (vid_maxfps < TICRATE && vid_maxfps != 0) + { + vid_maxfps = TICRATE; + } + else if (vid_maxfps > 1000) + { + vid_maxfps = 1000; + } + else if (cl_capfps == 0) + { + I_SetFPSLimit(vid_maxfps); + } +} + +extern int NewWidth, NewHeight, NewBits, DisplayBits; + +CUSTOM_CVAR (Bool, fullscreen, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + NewWidth = screen->GetWidth(); + NewHeight = screen->GetHeight(); + NewBits = DisplayBits; + setmodeneeded = true; +} + +CUSTOM_CVAR (Float, vid_winscale, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 1.f) + { + self = 1.f; + } + else if (Video) + { + Video->SetWindowedScale (self); + NewWidth = screen->GetWidth(); + NewHeight = screen->GetHeight(); + NewBits = DisplayBits; + setmodeneeded = true; + } +} + +CCMD (vid_listmodes) +{ + static const char *ratios[5] = { "", " - 16:9", " - 16:10", "", " - 5:4" }; + int width, height, bits; + bool letterbox; + + if (Video == NULL) + { + return; + } + for (bits = 1; bits <= 32; bits++) + { + Video->StartModeIterator (bits, screen->IsFullscreen()); + while (Video->NextMode (&width, &height, &letterbox)) + { + bool thisMode = (width == DisplayWidth && height == DisplayHeight && bits == DisplayBits); + int ratio = CheckRatio (width, height); + Printf (thisMode ? PRINT_BOLD : PRINT_HIGH, + "%s%4d x%5d x%3d%s%s\n", + thisMode || !(ratio & 3) ? "" : TEXTCOLOR_GOLD, + width, height, bits, + ratios[ratio], + thisMode || !letterbox ? "" : TEXTCOLOR_BROWN " LB" + ); + } + } +} + +CCMD (vid_currentmode) +{ + Printf ("%dx%dx%d\n", DisplayWidth, DisplayHeight, DisplayBits); +} diff --git a/src/posix/sdl/i_gui.cpp b/src/posix/sdl/i_gui.cpp new file mode 100644 index 000000000..f9e6714b4 --- /dev/null +++ b/src/posix/sdl/i_gui.cpp @@ -0,0 +1,61 @@ + +// Moved from sdl/i_system.cpp + +#include + +#include + +#include "bitmap.h" +#include "v_palette.h" +#include "textures.h" + +bool I_SetCursor(FTexture *cursorpic) +{ + static SDL_Cursor *cursor; + static SDL_Surface *cursorSurface; + + if (cursorpic != NULL && cursorpic->UseType != FTexture::TEX_Null) + { + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + + 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_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); + + if (cursor) + SDL_FreeCursor (cursor); + cursor = SDL_CreateColorCursor (cursorSurface, 0, 0); + SDL_SetCursor (cursor); + } + else + { + if (cursor) + { + SDL_SetCursor (NULL); + SDL_FreeCursor (cursor); + cursor = NULL; + } + if (cursorSurface != NULL) + { + SDL_FreeSurface(cursorSurface); + cursorSurface = NULL; + } + } + return true; +} + +void I_SetMainWindowVisible(bool visible) +{ + +} diff --git a/src/posix/sdl/i_input.cpp b/src/posix/sdl/i_input.cpp new file mode 100644 index 000000000..6fff2d2a9 --- /dev/null +++ b/src/posix/sdl/i_input.cpp @@ -0,0 +1,514 @@ +#include +#include +#include "doomtype.h" +#include "c_dispatch.h" +#include "doomdef.h" +#include "doomstat.h" +#include "m_argv.h" +#include "i_input.h" +#include "v_video.h" + +#include "d_main.h" +#include "d_event.h" +#include "d_gui.h" +#include "c_console.h" +#include "c_cvars.h" +#include "i_system.h" +#include "dikeys.h" +#include "templates.h" +#include "s_sound.h" + +void ScaleWithAspect (int &w, int &h, int Width, int Height); + +static void I_CheckGUICapture (); +static void I_CheckNativeMouse (); + +bool GUICapture; +static bool NativeMouse = true; + +extern int paused; + +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) + +EXTERN_CVAR (Bool, fullscreen) + +extern int WaitingForKey, chatmodeon; +extern constate_e ConsoleState; + +static bool DownState[SDL_NUM_SCANCODES]; + +static const SDL_Keycode DIKToKeySym[256] = +{ + 0, SDLK_ESCAPE, SDLK_1, SDLK_2, SDLK_3, SDLK_4, SDLK_5, SDLK_6, + SDLK_7, SDLK_8, SDLK_9, SDLK_0,SDLK_MINUS, SDLK_EQUALS, SDLK_BACKSPACE, SDLK_TAB, + SDLK_q, SDLK_w, SDLK_e, SDLK_r, SDLK_t, SDLK_y, SDLK_u, SDLK_i, + SDLK_o, SDLK_p, SDLK_LEFTBRACKET, SDLK_RIGHTBRACKET, SDLK_RETURN, SDLK_LCTRL, SDLK_a, SDLK_s, + SDLK_d, SDLK_f, SDLK_g, SDLK_h, SDLK_j, SDLK_k, SDLK_l, SDLK_SEMICOLON, + SDLK_QUOTE, SDLK_BACKQUOTE, SDLK_LSHIFT, SDLK_BACKSLASH, SDLK_z, SDLK_x, SDLK_c, SDLK_v, + SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA, SDLK_PERIOD, SDLK_SLASH, SDLK_RSHIFT, SDLK_KP_MULTIPLY, + SDLK_LALT, SDLK_SPACE, SDLK_CAPSLOCK, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, + SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_NUMLOCKCLEAR, SDLK_SCROLLLOCK, SDLK_KP_7, + SDLK_KP_8, SDLK_KP_9, SDLK_KP_MINUS, SDLK_KP_4, SDLK_KP_5, SDLK_KP_6, SDLK_KP_PLUS, SDLK_KP_1, + SDLK_KP_2, SDLK_KP_3, SDLK_KP_0, SDLK_KP_PERIOD, 0, 0, 0, SDLK_F11, + SDLK_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, SDLK_F13, SDLK_F14, SDLK_F15, 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, + 0, 0, 0, 0, 0, SDLK_KP_EQUALS, 0, 0, + 0, SDLK_AT, SDLK_COLON, 0, 0, 0, 0, 0, + 0, 0, 0, 0, SDLK_KP_ENTER, SDLK_RCTRL, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, SDLK_KP_COMMA, 0, SDLK_KP_DIVIDE, 0, SDLK_SYSREQ, + SDLK_RALT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, SDLK_PAUSE, 0, SDLK_HOME, + SDLK_UP, SDLK_PAGEUP, 0, SDLK_LEFT, 0, SDLK_RIGHT, 0, SDLK_END, + SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, 0, 0, 0, 0, + 0, 0, 0, SDLK_LGUI, SDLK_RGUI, SDLK_MENU, SDLK_POWER, SDLK_SLEEP, + 0, 0, 0, 0, 0, SDLK_AC_SEARCH, SDLK_AC_BOOKMARKS, SDLK_AC_REFRESH, + SDLK_AC_STOP, SDLK_AC_FORWARD, SDLK_AC_BACK, SDLK_COMPUTER, SDLK_MAIL, SDLK_MEDIASELECT, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const SDL_Scancode DIKToKeyScan[256] = +{ + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, + SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0 ,SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, + SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, + SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, + SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, + SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, + SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_KP_MULTIPLY, + SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, + SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_KP_7, + SDL_SCANCODE_KP_8, SDL_SCANCODE_KP_9, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_KP_4, SDL_SCANCODE_KP_5, SDL_SCANCODE_KP_6, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_KP_1, + SDL_SCANCODE_KP_2, SDL_SCANCODE_KP_3, SDL_SCANCODE_KP_0, SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F11, + SDL_SCANCODE_F12, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_EQUALS, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RCTRL, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_COMMA, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_SYSREQ, + SDL_SCANCODE_RALT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_HOME, + SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_END, + SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_MENU, SDL_SCANCODE_POWER, SDL_SCANCODE_SLEEP, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_AC_SEARCH, SDL_SCANCODE_AC_BOOKMARKS, SDL_SCANCODE_AC_REFRESH, + SDL_SCANCODE_AC_STOP, SDL_SCANCODE_AC_FORWARD, SDL_SCANCODE_AC_BACK, SDL_SCANCODE_COMPUTER, SDL_SCANCODE_MAIL, SDL_SCANCODE_MEDIASELECT, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, + SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN +}; + +static TMap InitKeySymMap () +{ + TMap KeySymToDIK; + + for (int i = 0; i < 256; ++i) + { + KeySymToDIK[DIKToKeySym[i]] = i; + } + KeySymToDIK[0] = 0; + KeySymToDIK[SDLK_RSHIFT] = DIK_LSHIFT; + KeySymToDIK[SDLK_RCTRL] = DIK_LCONTROL; + KeySymToDIK[SDLK_RALT] = DIK_LMENU; + // Depending on your Linux flavor, you may get SDLK_PRINT or SDLK_SYSREQ + KeySymToDIK[SDLK_PRINTSCREEN] = DIK_SYSRQ; + + return KeySymToDIK; +} +static const TMap KeySymToDIK(InitKeySymMap()); + +static TMap InitKeyScanMap () +{ + TMap KeyScanToDIK; + + for (int i = 0; i < 256; ++i) + { + KeyScanToDIK[DIKToKeyScan[i]] = i; + } + + return KeyScanToDIK; +} +static const TMap KeyScanToDIK(InitKeyScanMap()); + +static void I_CheckGUICapture () +{ + bool wantCapt; + + if (menuactive == MENU_Off) + { + wantCapt = ConsoleState == c_down || ConsoleState == c_falling || chatmodeon; + } + else + { + wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause); + } + + if (wantCapt != GUICapture) + { + GUICapture = wantCapt; + if (wantCapt) + { + memset (DownState, 0, sizeof(DownState)); + } + } +} + +void I_SetMouseCapture() +{ + // Clear out any mouse movement. + SDL_GetRelativeMouseState (NULL, NULL); + SDL_SetRelativeMouseMode (SDL_TRUE); +} + +void I_ReleaseMouseCapture() +{ + SDL_SetRelativeMouseMode (SDL_FALSE); +} + +static void PostMouseMove (int x, int y) +{ + static int lastx = 0, lasty = 0; + event_t ev = { 0,0,0,0,0,0,0 }; + + if (m_filter) + { + ev.x = (x + lastx) / 2; + ev.y = (y + lasty) / 2; + } + else + { + ev.x = x; + ev.y = y; + } + lastx = x; + lasty = y; + if (ev.x | ev.y) + { + ev.type = EV_Mouse; + D_PostEvent (&ev); + } +} + +static void MouseRead () +{ + int x, y; + + if (NativeMouse) + { + return; + } + + SDL_GetRelativeMouseState (&x, &y); + if (!m_noprescale) + { + x *= 3; + y *= 2; + } + if (x | y) + { + PostMouseMove (x, -y); + } +} + +CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) +{ + if (self < 0) self = 0; + else if (self > 2) self = 2; +} + +static bool inGame() +{ + 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; + } +} + +static void I_CheckNativeMouse () +{ + bool focus = SDL_GetKeyboardFocus() != NULL; + bool fs = screen->IsFullscreen(); + + bool wantNative = !focus || (!use_mouse || GUICapture || paused || demoplayback || !inGame()); + + if (wantNative != NativeMouse) + { + NativeMouse = wantNative; + SDL_ShowCursor (wantNative); + if (wantNative) + I_ReleaseMouseCapture (); + else + I_SetMouseCapture (); + } +} + +void MessagePump (const SDL_Event &sev) +{ + static int lastx = 0, lasty = 0; + int x, y; + event_t event = { 0,0,0,0,0,0,0 }; + + switch (sev.type) + { + case SDL_QUIT: + exit (0); + + case SDL_WINDOWEVENT: + switch (sev.window.event) + { + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + S_SetSoundPaused(sev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED); + break; + } + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEMOTION: + if (!GUICapture || sev.button.button == 4 || sev.button.button == 5) + { + if(sev.type != SDL_MOUSEMOTION) + { + event.type = sev.type == SDL_MOUSEBUTTONDOWN ? EV_KeyDown : EV_KeyUp; + /* These button mappings work with my Gentoo system using the + * evdev driver and a Logitech MX510 mouse. Whether or not they + * carry over to other Linux systems, I have no idea, but I sure + * hope so. (Though buttons 11 and 12 are kind of useless, since + * they also trigger buttons 4 and 5.) + */ + switch (sev.button.button) + { + case SDL_BUTTON_LEFT: event.data1 = KEY_MOUSE1; break; + case SDL_BUTTON_MIDDLE: event.data1 = KEY_MOUSE3; break; + case SDL_BUTTON_RIGHT: event.data1 = KEY_MOUSE2; break; + case 8: event.data1 = KEY_MOUSE4; break; // For whatever reason my side mouse buttons are here. + case 9: event.data1 = KEY_MOUSE5; break; + case SDL_BUTTON_X1: event.data1 = KEY_MOUSE6; break; // And these don't exist + case SDL_BUTTON_X2: event.data1 = KEY_MOUSE7; break; + case 6: event.data1 = KEY_MOUSE8; break; + default: printf("SDL mouse button %s %d\n", + sev.type == SDL_MOUSEBUTTONDOWN ? "down" : "up", sev.button.button); break; + } + if (event.data1 != 0) + { + D_PostEvent(&event); + } + } + } + else if (sev.type == SDL_MOUSEMOTION || (sev.button.button >= 1 && sev.button.button <= 3)) + { + int x, y; + SDL_GetMouseState (&x, &y); + + // Detect if we're doing scaling in the Window and adjust the mouse + // coordinates accordingly. This could be more efficent, but I + // don't think performance is an issue in the menus. + SDL_Window *focus; + if (screen->IsFullscreen() && (focus = SDL_GetMouseFocus ())) + { + int w, h; + SDL_GetWindowSize (focus, &w, &h); + int realw = w, realh = h; + ScaleWithAspect (realw, realh, SCREENWIDTH, SCREENHEIGHT); + if (realw != SCREENWIDTH || realh != SCREENHEIGHT) + { + double xratio = (double)SCREENWIDTH/realw; + double yratio = (double)SCREENHEIGHT/realh; + if (realw < w) + { + x = (x - (w - realw)/2)*xratio; + y *= yratio; + } + else + { + y = (y - (h - realh)/2)*yratio; + x *= xratio; + } + } + } + + event.data1 = x; + event.data2 = y; + event.type = EV_GUI_Event; + if(sev.type == SDL_MOUSEMOTION) + event.subtype = EV_GUI_MouseMove; + else + { + event.subtype = sev.type == SDL_MOUSEBUTTONDOWN ? EV_GUI_LButtonDown : EV_GUI_LButtonUp; + event.subtype += (sev.button.button - 1) * 3; + } + D_PostEvent(&event); + } + break; + + case SDL_MOUSEWHEEL: + if (GUICapture) + { + event.type = EV_GUI_Event; + event.subtype = sev.wheel.y > 0 ? EV_GUI_WheelUp : EV_GUI_WheelDown; + D_PostEvent (&event); + } + else + { + event.type = EV_KeyDown; + event.data1 = sev.wheel.y > 0 ? KEY_MWHEELUP : KEY_MWHEELDOWN; + D_PostEvent (&event); + event.type = EV_KeyUp; + D_PostEvent (&event); + } + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + if (!GUICapture) + { + event.type = sev.type == SDL_KEYDOWN ? EV_KeyDown : EV_KeyUp; + + // Try to look up our key mapped key for conversion to DirectInput. + // If that fails, then we'll do a lookup against the scan code, + // which may not return the right key, but at least the key should + // work in the game. + if (const BYTE *dik = KeySymToDIK.CheckKey (sev.key.keysym.sym)) + event.data1 = *dik; + else if (const BYTE *dik = KeyScanToDIK.CheckKey (sev.key.keysym.scancode)) + event.data1 = *dik; + + if (event.data1) + { + if (sev.key.keysym.sym < 256) + { + event.data2 = sev.key.keysym.sym; + } + D_PostEvent (&event); + } + } + else + { + event.type = EV_GUI_Event; + event.subtype = sev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp; + event.data3 = ((sev.key.keysym.mod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((sev.key.keysym.mod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((sev.key.keysym.mod & KMOD_ALT) ? GKM_ALT : 0); + + if (event.subtype == EV_GUI_KeyDown) + { + if (DownState[sev.key.keysym.scancode]) + { + event.subtype = EV_GUI_KeyRepeat; + } + DownState[sev.key.keysym.scancode] = 1; + } + else + { + DownState[sev.key.keysym.scancode] = 0; + } + + switch (sev.key.keysym.sym) + { + case SDLK_KP_ENTER: event.data1 = GK_RETURN; break; + case SDLK_PAGEUP: event.data1 = GK_PGUP; break; + case SDLK_PAGEDOWN: event.data1 = GK_PGDN; break; + case SDLK_END: event.data1 = GK_END; break; + case SDLK_HOME: event.data1 = GK_HOME; break; + case SDLK_LEFT: event.data1 = GK_LEFT; break; + case SDLK_RIGHT: event.data1 = GK_RIGHT; break; + case SDLK_UP: event.data1 = GK_UP; break; + case SDLK_DOWN: event.data1 = GK_DOWN; break; + case SDLK_DELETE: event.data1 = GK_DEL; break; + case SDLK_ESCAPE: event.data1 = GK_ESCAPE; break; + case SDLK_F1: event.data1 = GK_F1; break; + case SDLK_F2: event.data1 = GK_F2; break; + case SDLK_F3: event.data1 = GK_F3; break; + case SDLK_F4: event.data1 = GK_F4; break; + case SDLK_F5: event.data1 = GK_F5; break; + case SDLK_F6: event.data1 = GK_F6; break; + case SDLK_F7: event.data1 = GK_F7; break; + case SDLK_F8: event.data1 = GK_F8; break; + case SDLK_F9: event.data1 = GK_F9; break; + case SDLK_F10: event.data1 = GK_F10; break; + case SDLK_F11: event.data1 = GK_F11; break; + case SDLK_F12: event.data1 = GK_F12; break; + default: + if (sev.key.keysym.sym < 256) + { + event.data1 = sev.key.keysym.sym; + } + break; + } + if (event.data1 < 128) + { + event.data1 = toupper(event.data1); + D_PostEvent (&event); + } + } + break; + + case SDL_TEXTINPUT: + if (GUICapture) + { + event.type = EV_GUI_Event; + event.subtype = EV_GUI_Char; + event.data1 = sev.text.text[0]; + D_PostEvent (&event); + } + break; + + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + if (!GUICapture) + { + event.type = sev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp; + event.data1 = KEY_FIRSTJOYBUTTON + sev.jbutton.button; + if(event.data1 != 0) + D_PostEvent(&event); + } + break; + } +} + +void I_GetEvent () +{ + SDL_Event sev; + + while (SDL_PollEvent (&sev)) + { + MessagePump (sev); + } + if (use_mouse) + { + MouseRead (); + } +} + +void I_StartTic () +{ + I_CheckGUICapture (); + I_CheckNativeMouse (); + I_GetEvent (); +} + +void I_ProcessJoysticks (); +void I_StartFrame () +{ + I_ProcessJoysticks(); +} diff --git a/src/sdl/i_joystick.cpp b/src/posix/sdl/i_joystick.cpp similarity index 99% rename from src/sdl/i_joystick.cpp rename to src/posix/sdl/i_joystick.cpp index 7a529861f..9b1d326ae 100644 --- a/src/sdl/i_joystick.cpp +++ b/src/posix/sdl/i_joystick.cpp @@ -35,7 +35,7 @@ public: FString GetName() { - return SDL_JoystickName(DeviceIndex); + return SDL_JoystickName(Device); } float GetSensitivity() { diff --git a/src/sdl/i_main.cpp b/src/posix/sdl/i_main.cpp similarity index 87% rename from src/sdl/i_main.cpp rename to src/posix/sdl/i_main.cpp index 27324edf6..ff700eff4 100644 --- a/src/sdl/i_main.cpp +++ b/src/posix/sdl/i_main.cpp @@ -1,367 +1,338 @@ -/* -** i_main.cpp -** System-specific startup code. Eventually calls D_DoomMain. -** -**--------------------------------------------------------------------------- -** Copyright 1998-2007 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. -** -** 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. -**--------------------------------------------------------------------------- -** -*/ - -// HEADER FILES ------------------------------------------------------------ - -#include -#include -#include -#include -#include -#include -#ifndef NO_GTK -#include -#endif -#include -#if defined(__MACH__) && !defined(NOASM) -#include -#include -#endif - -#include "doomerrors.h" -#include "m_argv.h" -#include "d_main.h" -#include "i_system.h" -#include "i_video.h" -#include "c_console.h" -#include "errors.h" -#include "version.h" -#include "w_wad.h" -#include "g_level.h" -#include "r_state.h" -#include "cmdlib.h" -#include "r_utility.h" -#include "doomstat.h" - -// MACROS ------------------------------------------------------------------ - -// The maximum number of functions that can be registered with atterm. -#define MAX_TERMS 64 - -// TYPES ------------------------------------------------------------------- - -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*)); - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -#ifdef USE_XCURSOR -extern bool UseXCursor; -#endif - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -#ifndef NO_GTK -bool GtkAvailable; -#endif - -// The command line arguments. -DArgs *Args; - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static void (*TermFuncs[MAX_TERMS]) (); -static const char *TermNames[MAX_TERMS]; -static int NumTerms; - -// CODE -------------------------------------------------------------------- - -void addterm (void (*func) (), const char *name) -{ - // Make sure this function wasn't already registered. - for (int i = 0; i < NumTerms; ++i) - { - if (TermFuncs[i] == func) - { - return; - } - } - if (NumTerms == MAX_TERMS) - { - func (); - I_FatalError ( - "Too many exit functions registered.\n" - "Increase MAX_TERMS in i_main.cpp"); - } - TermNames[NumTerms] = name; - TermFuncs[NumTerms++] = func; -} - -void popterm () -{ - if (NumTerms) - NumTerms--; -} - -void STACK_ARGS call_terms () -{ - while (NumTerms > 0) - { -// printf ("term %d - %s\n", NumTerms, TermNames[NumTerms-1]); - TermFuncs[--NumTerms] (); - } -} - -static void STACK_ARGS NewFailure () -{ - I_FatalError ("Failed to allocate memory from system heap"); -} - -static int DoomSpecificInfo (char *buffer, char *end) -{ - const char *arg; - int size = end-buffer-2; - int i, p; - - p = 0; - p += snprintf (buffer+p, size-p, GAMENAME" version %s (%s)\n", GetVersionString(), GetGitHash()); -#ifdef __VERSION__ - p += snprintf (buffer+p, size-p, "Compiler version: %s\n", __VERSION__); -#endif - p += snprintf (buffer+p, size-p, "\nCommand line:"); - for (i = 0; i < Args->NumArgs(); ++i) - { - p += snprintf (buffer+p, size-p, " %s", Args->GetArg(i)); - } - p += snprintf (buffer+p, size-p, "\n"); - - for (i = 0; (arg = Wads.GetWadName (i)) != NULL; ++i) - { - p += snprintf (buffer+p, size-p, "\nWad %d: %s", i, arg); - } - - if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL) - { - p += snprintf (buffer+p, size-p, "\n\nNot in a level."); - } - else - { - p += snprintf (buffer+p, size-p, "\n\nCurrent map: %s", level.MapName.GetChars()); - - if (!viewactive) - { - p += snprintf (buffer+p, size-p, "\n\nView not active."); - } - else - { - p += snprintf (buffer+p, size-p, "\n\nviewx = %d", (int)viewx); - p += snprintf (buffer+p, size-p, "\nviewy = %d", (int)viewy); - p += snprintf (buffer+p, size-p, "\nviewz = %d", (int)viewz); - p += snprintf (buffer+p, size-p, "\nviewangle = %x", (unsigned int)viewangle); - } - } - buffer[p++] = '\n'; - buffer[p++] = '\0'; - - return p; -} - -#if defined(__MACH__) && !defined(NOASM) -// NASM won't let us create custom sections for Mach-O. Whether that's a limitation of NASM -// or of Mach-O, I don't know, but since we're using NASM for the assembly, it doesn't much -// matter. -extern "C" -{ - extern void *rtext_a_start, *rtext_a_end; - extern void *rtext_tmap_start, *rtext_tmap_end; - extern void *rtext_tmap2_start, *rtext_tmap2_end; - extern void *rtext_tmap3_start, *rtext_tmap3_end; -}; - -static void unprotect_pages(long pagesize, void *start, void *end) -{ - char *page = (char *)((intptr_t)start & ~(pagesize - 1)); - size_t len = (char *)end - (char *)start; - if (mprotect(page, len, PROT_READ|PROT_WRITE|PROT_EXEC) != 0) - { - fprintf(stderr, "mprotect failed\n"); - exit(1); - } -} - -static void unprotect_rtext() -{ - static void *const pages[] = - { - rtext_a_start, rtext_a_end, - rtext_tmap_start, rtext_tmap_end, - rtext_tmap2_start, rtext_tmap2_end, - rtext_tmap3_start, rtext_tmap3_end - }; - long pagesize = sysconf(_SC_PAGESIZE); - for (void *const *p = pages; p < &pages[countof(pages)]; p += 2) - { - unprotect_pages(pagesize, p[0], p[1]); - } -} -#endif - -void I_StartupJoysticks(); -void I_ShutdownJoysticks(); - -int main (int argc, char **argv) -{ -#if !defined (__APPLE__) - { - int s[4] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS }; - cc_install_handlers(argc, argv, 4, s, "zdoom-crash.log", DoomSpecificInfo); - } -#endif // !__APPLE__ - - printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", - GetVersionString(), GetGitTime(), __DATE__); - - seteuid (getuid ()); - std::set_new_handler (NewFailure); - -#if defined(__MACH__) && !defined(NOASM) - unprotect_rtext(); -#endif - - // Set LC_NUMERIC environment variable in case some library decides to - // clear the setlocale call at least this will be correct. - // Note that the LANG environment variable is overridden by LC_* - setenv ("LC_NUMERIC", "C", 1); - -#ifndef NO_GTK - GtkAvailable = gtk_init_check (&argc, &argv); -#endif - - setlocale (LC_ALL, "C"); - - if (SDL_Init (SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE|SDL_INIT_JOYSTICK) == -1) - { - fprintf (stderr, "Could not initialize SDL:\n%s\n", SDL_GetError()); - return -1; - } - atterm (SDL_Quit); - - { - char viddriver[80]; - - if (SDL_VideoDriverName(viddriver, sizeof(viddriver)) != NULL) - { - printf("Using video driver %s\n", viddriver); -#ifdef USE_XCURSOR - UseXCursor = (strcmp(viddriver, "x11") == 0); -#endif - } - printf("\n"); - } - - char caption[100]; - mysnprintf(caption, countof(caption), GAMESIG " %s (%s)", GetVersionString(), GetGitTime()); - SDL_WM_SetCaption(caption, caption); - -#ifdef __APPLE__ - - const SDL_VideoInfo* videoInfo = SDL_GetVideoInfo(); - if ( NULL != videoInfo ) - { - EXTERN_CVAR( Int, vid_defwidth ) - EXTERN_CVAR( Int, vid_defheight ) - EXTERN_CVAR( Int, vid_defbits ) - EXTERN_CVAR( Bool, vid_vsync ) - EXTERN_CVAR( Bool, fullscreen ) - - vid_defwidth = videoInfo->current_w; - vid_defheight = videoInfo->current_h; - vid_defbits = videoInfo->vfmt->BitsPerPixel; - vid_vsync = true; - fullscreen = true; - } - -#endif // __APPLE__ - - try - { - Args = new DArgs(argc, argv); - - /* - killough 1/98: - - This fixes some problems with exit handling - during abnormal situations. - - The old code called I_Quit() to end program, - while now I_Quit() is installed as an exit - handler and exit() is called to exit, either - normally or abnormally. Seg faults are caught - and the error handler is used, to prevent - being left in graphics mode or having very - loud SFX noise because the sound card is - left in an unstable state. - */ - - atexit (call_terms); - atterm (I_Quit); - - // Should we even be doing anything with progdir on Unix systems? - char program[PATH_MAX]; - if (realpath (argv[0], program) == NULL) - strcpy (program, argv[0]); - char *slash = strrchr (program, '/'); - if (slash != NULL) - { - *(slash + 1) = '\0'; - progdir = program; - } - else - { - progdir = "./"; - } - - I_StartupJoysticks(); - C_InitConsole (80*8, 25*8, false); - D_DoomMain (); - } - catch (class CDoomError &error) - { - I_ShutdownJoysticks(); - if (error.GetMessage ()) - fprintf (stderr, "%s\n", error.GetMessage ()); - exit (-1); - } - catch (...) - { - call_terms (); - throw; - } - return 0; -} +/* +** i_main.cpp +** System-specific startup code. Eventually calls D_DoomMain. +** +**--------------------------------------------------------------------------- +** Copyright 1998-2007 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. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#ifndef NO_GTK +#include +#endif +#include +#if defined(__MACH__) && !defined(NOASM) +#include +#include +#endif + +#include "doomerrors.h" +#include "m_argv.h" +#include "d_main.h" +#include "i_system.h" +#include "i_video.h" +#include "c_console.h" +#include "errors.h" +#include "version.h" +#include "w_wad.h" +#include "g_level.h" +#include "r_state.h" +#include "cmdlib.h" +#include "r_utility.h" +#include "doomstat.h" + +// MACROS ------------------------------------------------------------------ + +// The maximum number of functions that can be registered with atterm. +#define MAX_TERMS 64 + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +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 --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +#ifndef NO_GTK +bool GtkAvailable; +#endif + +// The command line arguments. +DArgs *Args; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static void (*TermFuncs[MAX_TERMS]) (); +static const char *TermNames[MAX_TERMS]; +static int NumTerms; + +// CODE -------------------------------------------------------------------- + +void addterm (void (*func) (), const char *name) +{ + // Make sure this function wasn't already registered. + for (int i = 0; i < NumTerms; ++i) + { + if (TermFuncs[i] == func) + { + return; + } + } + if (NumTerms == MAX_TERMS) + { + func (); + I_FatalError ( + "Too many exit functions registered.\n" + "Increase MAX_TERMS in i_main.cpp"); + } + TermNames[NumTerms] = name; + TermFuncs[NumTerms++] = func; +} + +void popterm () +{ + if (NumTerms) + NumTerms--; +} + +void STACK_ARGS call_terms () +{ + while (NumTerms > 0) + { +// printf ("term %d - %s\n", NumTerms, TermNames[NumTerms-1]); + TermFuncs[--NumTerms] (); + } +} + +static void STACK_ARGS NewFailure () +{ + I_FatalError ("Failed to allocate memory from system heap"); +} + +static int DoomSpecificInfo (char *buffer, char *end) +{ + const char *arg; + int size = end-buffer-2; + int i, p; + + p = 0; + p += snprintf (buffer+p, size-p, GAMENAME" version %s (%s)\n", GetVersionString(), GetGitHash()); +#ifdef __VERSION__ + p += snprintf (buffer+p, size-p, "Compiler version: %s\n", __VERSION__); +#endif + p += snprintf (buffer+p, size-p, "\nCommand line:"); + for (i = 0; i < Args->NumArgs(); ++i) + { + p += snprintf (buffer+p, size-p, " %s", Args->GetArg(i)); + } + p += snprintf (buffer+p, size-p, "\n"); + + for (i = 0; (arg = Wads.GetWadName (i)) != NULL; ++i) + { + p += snprintf (buffer+p, size-p, "\nWad %d: %s", i, arg); + } + + if (gamestate != GS_LEVEL && gamestate != GS_TITLELEVEL) + { + p += snprintf (buffer+p, size-p, "\n\nNot in a level."); + } + else + { + p += snprintf (buffer+p, size-p, "\n\nCurrent map: %s", level.MapName.GetChars()); + + if (!viewactive) + { + p += snprintf (buffer+p, size-p, "\n\nView not active."); + } + else + { + p += snprintf (buffer+p, size-p, "\n\nviewx = %d", (int)viewx); + p += snprintf (buffer+p, size-p, "\nviewy = %d", (int)viewy); + p += snprintf (buffer+p, size-p, "\nviewz = %d", (int)viewz); + p += snprintf (buffer+p, size-p, "\nviewangle = %x", (unsigned int)viewangle); + } + } + buffer[p++] = '\n'; + buffer[p++] = '\0'; + + return p; +} + +#if defined(__MACH__) && !defined(NOASM) +// NASM won't let us create custom sections for Mach-O. Whether that's a limitation of NASM +// or of Mach-O, I don't know, but since we're using NASM for the assembly, it doesn't much +// matter. +extern "C" +{ + extern void *rtext_a_start, *rtext_a_end; + extern void *rtext_tmap_start, *rtext_tmap_end; + extern void *rtext_tmap2_start, *rtext_tmap2_end; + extern void *rtext_tmap3_start, *rtext_tmap3_end; +}; + +static void unprotect_pages(long pagesize, void *start, void *end) +{ + char *page = (char *)((intptr_t)start & ~(pagesize - 1)); + size_t len = (char *)end - (char *)start; + if (mprotect(page, len, PROT_READ|PROT_WRITE|PROT_EXEC) != 0) + { + fprintf(stderr, "mprotect failed\n"); + exit(1); + } +} + +static void unprotect_rtext() +{ + static void *const pages[] = + { + rtext_a_start, rtext_a_end, + rtext_tmap_start, rtext_tmap_end, + rtext_tmap2_start, rtext_tmap2_end, + rtext_tmap3_start, rtext_tmap3_end + }; + long pagesize = sysconf(_SC_PAGESIZE); + for (void *const *p = pages; p < &pages[countof(pages)]; p += 2) + { + unprotect_pages(pagesize, p[0], p[1]); + } +} +#endif + +void I_StartupJoysticks(); +void I_ShutdownJoysticks(); + +int main (int argc, char **argv) +{ +#if !defined (__APPLE__) + { + int s[4] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS }; + cc_install_handlers(argc, argv, 4, s, "zdoom-crash.log", DoomSpecificInfo); + } +#endif // !__APPLE__ + + printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", + GetVersionString(), GetGitTime(), __DATE__); + + seteuid (getuid ()); + std::set_new_handler (NewFailure); + +#if defined(__MACH__) && !defined(NOASM) + unprotect_rtext(); +#endif + + // Set LC_NUMERIC environment variable in case some library decides to + // clear the setlocale call at least this will be correct. + // Note that the LANG environment variable is overridden by LC_* + setenv ("LC_NUMERIC", "C", 1); + +#ifndef NO_GTK + GtkAvailable = gtk_init_check (&argc, &argv); +#endif + + setlocale (LC_ALL, "C"); + + if (SDL_Init (SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE|SDL_INIT_JOYSTICK) == -1) + { + fprintf (stderr, "Could not initialize SDL:\n%s\n", SDL_GetError()); + return -1; + } + atterm (SDL_Quit); + + printf("Using video driver %s\n", SDL_GetCurrentVideoDriver()); + printf("\n"); + + try + { + Args = new DArgs(argc, argv); + + /* + killough 1/98: + + This fixes some problems with exit handling + during abnormal situations. + + The old code called I_Quit() to end program, + while now I_Quit() is installed as an exit + handler and exit() is called to exit, either + normally or abnormally. Seg faults are caught + and the error handler is used, to prevent + being left in graphics mode or having very + loud SFX noise because the sound card is + left in an unstable state. + */ + + atexit (call_terms); + atterm (I_Quit); + + // Should we even be doing anything with progdir on Unix systems? + char program[PATH_MAX]; + if (realpath (argv[0], program) == NULL) + strcpy (program, argv[0]); + char *slash = strrchr (program, '/'); + if (slash != NULL) + { + *(slash + 1) = '\0'; + progdir = program; + } + else + { + progdir = "./"; + } + + I_StartupJoysticks(); + C_InitConsole (80*8, 25*8, false); + D_DoomMain (); + } + catch (class CDoomError &error) + { + I_ShutdownJoysticks(); + if (error.GetMessage ()) + fprintf (stderr, "%s\n", error.GetMessage ()); + +#ifdef __APPLE__ + Mac_I_FatalError(error.GetMessage()); +#endif // __APPLE__ + + exit (-1); + } + catch (...) + { + call_terms (); + throw; + } + return 0; +} diff --git a/src/sdl/i_system_cocoa.mm b/src/posix/sdl/i_system.mm similarity index 100% rename from src/sdl/i_system_cocoa.mm rename to src/posix/sdl/i_system.mm diff --git a/src/posix/sdl/i_timer.cpp b/src/posix/sdl/i_timer.cpp new file mode 100644 index 000000000..e3f9906b6 --- /dev/null +++ b/src/posix/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/sdlvideo.cpp b/src/posix/sdl/sdlvideo.cpp similarity index 60% rename from src/sdl/sdlvideo.cpp rename to src/posix/sdl/sdlvideo.cpp index 92741d325..7a04ce901 100644 --- a/src/sdl/sdlvideo.cpp +++ b/src/posix/sdl/sdlvideo.cpp @@ -1,544 +1,732 @@ - -// HEADER FILES ------------------------------------------------------------ - -#include "doomtype.h" - -#include "templates.h" -#include "i_system.h" -#include "i_video.h" -#include "v_video.h" -#include "v_pfx.h" -#include "stats.h" -#include "v_palette.h" -#include "sdlvideo.h" -#include "r_swrenderer.h" - -#include - -// MACROS ------------------------------------------------------------------ - -// TYPES ------------------------------------------------------------------- - -class SDLFB : public DFrameBuffer -{ - DECLARE_CLASS(SDLFB, DFrameBuffer) -public: - SDLFB (int width, int height, bool fullscreen); - ~SDLFB (); - - bool Lock (bool buffer); - void Unlock (); - bool Relock (); - void ForceBuffering (bool force); - bool IsValid (); - void Update (); - PalEntry *GetPalette (); - void GetFlashedPalette (PalEntry pal[256]); - void UpdatePalette (); - bool SetGamma (float gamma); - bool SetFlash (PalEntry rgb, int amount); - void GetFlash (PalEntry &rgb, int &amount); - int GetPageCount (); - bool IsFullscreen (); - - friend class SDLVideo; - -private: - PalEntry SourcePalette[256]; - BYTE GammaTable[3][256]; - PalEntry Flash; - int FlashAmount; - float Gamma; - bool UpdatePending; - - SDL_Surface *Screen; - - bool NeedPalUpdate; - bool NeedGammaUpdate; - bool NotPaletted; - - void UpdateColors (); - - SDLFB () {} -}; -IMPLEMENT_CLASS(SDLFB) - -struct MiniModeInfo -{ - WORD Width, Height; -}; - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -extern IVideo *Video; -extern SDL_Surface *cursorSurface; -extern SDL_Rect cursorBlit; -extern bool GUICapture; - -EXTERN_CVAR (Float, Gamma) -EXTERN_CVAR (Int, vid_maxfps) -EXTERN_CVAR (Bool, cl_capfps) - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -CVAR (Int, vid_displaybits, 8, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -// vid_asyncblit needs a restart to work. SDL doesn't seem to change if the -// frame buffer is changed at run time. -CVAR (Bool, vid_asyncblit, 1, CVAR_NOINITCALL|CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -CUSTOM_CVAR (Float, rgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (screen != NULL) - { - screen->SetGamma (Gamma); - } -} -CUSTOM_CVAR (Float, ggamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (screen != NULL) - { - screen->SetGamma (Gamma); - } -} -CUSTOM_CVAR (Float, bgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (screen != NULL) - { - screen->SetGamma (Gamma); - } -} - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -// Dummy screen sizes to pass when windowed -static MiniModeInfo WinModes[] = -{ - { 320, 200 }, - { 320, 240 }, - { 400, 225 }, // 16:9 - { 400, 300 }, - { 480, 270 }, // 16:9 - { 480, 360 }, - { 512, 288 }, // 16:9 - { 512, 384 }, - { 640, 360 }, // 16:9 - { 640, 400 }, - { 640, 480 }, - { 720, 480 }, // 16:10 - { 720, 540 }, - { 800, 450 }, // 16:9 - { 800, 500 }, // 16:10 - { 800, 600 }, - { 848, 480 }, // 16:9 - { 960, 600 }, // 16:10 - { 960, 720 }, - { 1024, 576 }, // 16:9 - { 1024, 640 }, // 16:10 - { 1024, 768 }, - { 1088, 612 }, // 16:9 - { 1152, 648 }, // 16:9 - { 1152, 720 }, // 16:10 - { 1152, 864 }, - { 1280, 720 }, // 16:9 - { 1280, 800 }, // 16:10 - { 1280, 960 }, - { 1360, 768 }, // 16:9 - { 1400, 787 }, // 16:9 - { 1400, 875 }, // 16:10 - { 1400, 1050 }, - { 1600, 900 }, // 16:9 - { 1600, 1000 }, // 16:10 - { 1600, 1200 }, - { 1920, 1080 }, -}; - -static cycle_t BlitCycles; -static cycle_t SDLFlipCycles; - -// CODE -------------------------------------------------------------------- - -SDLVideo::SDLVideo (int parm) -{ - IteratorBits = 0; - IteratorFS = false; -} - -SDLVideo::~SDLVideo () -{ -} - -void SDLVideo::StartModeIterator (int bits, bool fs) -{ - IteratorMode = 0; - IteratorBits = bits; - IteratorFS = fs; -} - -bool SDLVideo::NextMode (int *width, int *height, bool *letterbox) -{ - if (IteratorBits != 8) - return false; - - if (!IteratorFS) - { - if ((unsigned)IteratorMode < sizeof(WinModes)/sizeof(WinModes[0])) - { - *width = WinModes[IteratorMode].Width; - *height = WinModes[IteratorMode].Height; - ++IteratorMode; - return true; - } - } - else - { - SDL_Rect **modes = SDL_ListModes (NULL, SDL_FULLSCREEN|SDL_HWSURFACE); - if (modes != NULL && modes[IteratorMode] != NULL) - { - *width = modes[IteratorMode]->w; - *height = modes[IteratorMode]->h; - ++IteratorMode; - return true; - } - } - return false; -} - -DFrameBuffer *SDLVideo::CreateFrameBuffer (int width, int height, bool fullscreen, DFrameBuffer *old) -{ - static int retry = 0; - static int owidth, oheight; - - PalEntry flashColor; - int flashAmount; - - if (old != NULL) - { // Reuse the old framebuffer if its attributes are the same - SDLFB *fb = static_cast (old); - if (fb->Width == width && - fb->Height == height) - { - bool fsnow = (fb->Screen->flags & SDL_FULLSCREEN) != 0; - - if (fsnow != fullscreen) - { - SDL_WM_ToggleFullScreen (fb->Screen); - } - return old; - } - old->GetFlash (flashColor, flashAmount); - old->ObjectFlags |= OF_YesReallyDelete; - if (screen == old) screen = NULL; - delete old; - } - else - { - flashColor = 0; - flashAmount = 0; - } - - SDLFB *fb = new SDLFB (width, height, fullscreen); - retry = 0; - - // If we could not create the framebuffer, try again with slightly - // different parameters in this order: - // 1. Try with the closest size - // 2. Try in the opposite screen mode with the original size - // 3. Try in the opposite screen mode with the closest size - // This is a somewhat confusing mass of recursion here. - - while (fb == NULL || !fb->IsValid ()) - { - if (fb != NULL) - { - delete fb; - } - - switch (retry) - { - case 0: - owidth = width; - oheight = height; - case 2: - // Try a different resolution. Hopefully that will work. - I_ClosestResolution (&width, &height, 8); - break; - - case 1: - // Try changing fullscreen mode. Maybe that will work. - width = owidth; - height = oheight; - fullscreen = !fullscreen; - break; - - default: - // I give up! - I_FatalError ("Could not create new screen (%d x %d)", owidth, oheight); - } - - ++retry; - fb = static_cast(CreateFrameBuffer (width, height, fullscreen, NULL)); - } - - fb->SetFlash (flashColor, flashAmount); - - return fb; -} - -void SDLVideo::SetWindowedScale (float scale) -{ -} - -// FrameBuffer implementation ----------------------------------------------- - -SDLFB::SDLFB (int width, int height, bool fullscreen) - : DFrameBuffer (width, height) -{ - int i; - - NeedPalUpdate = false; - NeedGammaUpdate = false; - UpdatePending = false; - NotPaletted = false; - FlashAmount = 0; - Screen = SDL_SetVideoMode (width, height, vid_displaybits, - (vid_asyncblit ? SDL_ASYNCBLIT : 0)|SDL_HWSURFACE|SDL_HWPALETTE|SDL_DOUBLEBUF|SDL_ANYFORMAT| - (fullscreen ? SDL_FULLSCREEN : 0)); - - if (Screen == NULL) - return; - - for (i = 0; i < 256; i++) - { - GammaTable[0][i] = GammaTable[1][i] = GammaTable[2][i] = i; - } - if (Screen->format->palette == NULL) - { - NotPaletted = true; - GPfx.SetFormat (Screen->format->BitsPerPixel, - Screen->format->Rmask, - Screen->format->Gmask, - Screen->format->Bmask); - } - memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); - UpdateColors (); -} - -SDLFB::~SDLFB () -{ -} - -bool SDLFB::IsValid () -{ - return DFrameBuffer::IsValid() && Screen != NULL; -} - -int SDLFB::GetPageCount () -{ - return 1; -} - -bool SDLFB::Lock (bool buffered) -{ - return DSimpleCanvas::Lock (); -} - -bool SDLFB::Relock () -{ - return DSimpleCanvas::Lock (); -} - -void SDLFB::Unlock () -{ - if (UpdatePending && LockCount == 1) - { - Update (); - } - else if (--LockCount <= 0) - { - Buffer = NULL; - LockCount = 0; - } -} - -void SDLFB::Update () -{ - if (LockCount != 1) - { - if (LockCount > 0) - { - UpdatePending = true; - --LockCount; - } - return; - } - - DrawRateStuff (); - -#ifndef __APPLE__ - if(vid_maxfps && !cl_capfps) - { - SEMAPHORE_WAIT(FPSLimitSemaphore) - } -#endif - - Buffer = NULL; - LockCount = 0; - UpdatePending = false; - - BlitCycles.Reset(); - SDLFlipCycles.Reset(); - BlitCycles.Clock(); - - if (SDL_LockSurface (Screen) == -1) - return; - - if (NotPaletted) - { - GPfx.Convert (MemBuffer, Pitch, - Screen->pixels, Screen->pitch, Width, Height, - FRACUNIT, FRACUNIT, 0, 0); - } - else - { - if (Screen->pitch == Pitch) - { - memcpy (Screen->pixels, MemBuffer, Width*Height); - } - else - { - for (int y = 0; y < Height; ++y) - { - memcpy ((BYTE *)Screen->pixels+y*Screen->pitch, MemBuffer+y*Pitch, Width); - } - } - } - - SDL_UnlockSurface (Screen); - - if (cursorSurface != NULL && GUICapture) - { - // SDL requires us to draw a surface to get true color cursors. - SDL_BlitSurface(cursorSurface, NULL, Screen, &cursorBlit); - } - - SDLFlipCycles.Clock(); - SDL_Flip (Screen); - SDLFlipCycles.Unclock(); - - BlitCycles.Unclock(); - - if (NeedGammaUpdate) - { - bool Windowed = false; - NeedGammaUpdate = false; - CalcGamma ((Windowed || rgamma == 0.f) ? Gamma : (Gamma * rgamma), GammaTable[0]); - CalcGamma ((Windowed || ggamma == 0.f) ? Gamma : (Gamma * ggamma), GammaTable[1]); - CalcGamma ((Windowed || bgamma == 0.f) ? Gamma : (Gamma * bgamma), GammaTable[2]); - NeedPalUpdate = true; - } - - if (NeedPalUpdate) - { - NeedPalUpdate = false; - UpdateColors (); - } -} - -void SDLFB::UpdateColors () -{ - if (NotPaletted) - { - PalEntry palette[256]; - - for (int i = 0; i < 256; ++i) - { - palette[i].r = GammaTable[0][SourcePalette[i].r]; - palette[i].g = GammaTable[1][SourcePalette[i].g]; - palette[i].b = GammaTable[2][SourcePalette[i].b]; - } - if (FlashAmount) - { - DoBlending (palette, palette, - 256, GammaTable[0][Flash.r], GammaTable[1][Flash.g], GammaTable[2][Flash.b], - FlashAmount); - } - GPfx.SetPalette (palette); - } - else - { - SDL_Color colors[256]; - - for (int i = 0; i < 256; ++i) - { - colors[i].r = GammaTable[0][SourcePalette[i].r]; - colors[i].g = GammaTable[1][SourcePalette[i].g]; - colors[i].b = GammaTable[2][SourcePalette[i].b]; - } - if (FlashAmount) - { - DoBlending ((PalEntry *)colors, (PalEntry *)colors, - 256, GammaTable[2][Flash.b], GammaTable[1][Flash.g], GammaTable[0][Flash.r], - FlashAmount); - } - SDL_SetPalette (Screen, SDL_LOGPAL|SDL_PHYSPAL, colors, 0, 256); - } -} - -PalEntry *SDLFB::GetPalette () -{ - return SourcePalette; -} - -void SDLFB::UpdatePalette () -{ - NeedPalUpdate = true; -} - -bool SDLFB::SetGamma (float gamma) -{ - Gamma = gamma; - NeedGammaUpdate = true; - return true; -} - -bool SDLFB::SetFlash (PalEntry rgb, int amount) -{ - Flash = rgb; - FlashAmount = amount; - NeedPalUpdate = true; - return true; -} - -void SDLFB::GetFlash (PalEntry &rgb, int &amount) -{ - rgb = Flash; - amount = FlashAmount; -} - -// Q: Should I gamma adjust the returned palette? -void SDLFB::GetFlashedPalette (PalEntry pal[256]) -{ - memcpy (pal, SourcePalette, 256*sizeof(PalEntry)); - if (FlashAmount) - { - DoBlending (pal, pal, 256, Flash.r, Flash.g, Flash.b, FlashAmount); - } -} - -bool SDLFB::IsFullscreen () -{ - return (Screen->flags & SDL_FULLSCREEN) != 0; -} - -ADD_STAT (blit) -{ - FString out; - out.Format ("blit=%04.1f ms flip=%04.1f ms", - BlitCycles.Time() * 1e-3, SDLFlipCycles.TimeMS()); - return out; -} + +// HEADER FILES ------------------------------------------------------------ + +#include "doomtype.h" + +#include "templates.h" +#include "i_system.h" +#include "i_video.h" +#include "v_video.h" +#include "v_pfx.h" +#include "stats.h" +#include "v_palette.h" +#include "sdlvideo.h" +#include "r_swrenderer.h" +#include "version.h" + +#include + +#ifdef __APPLE__ +#include +#endif // __APPLE__ + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class SDLFB : public DFrameBuffer +{ + DECLARE_CLASS(SDLFB, DFrameBuffer) +public: + SDLFB (int width, int height, bool fullscreen); + ~SDLFB (); + + bool Lock (bool buffer); + void Unlock (); + bool Relock (); + void ForceBuffering (bool force); + bool IsValid (); + void Update (); + PalEntry *GetPalette (); + void GetFlashedPalette (PalEntry pal[256]); + void UpdatePalette (); + bool SetGamma (float gamma); + bool SetFlash (PalEntry rgb, int amount); + void GetFlash (PalEntry &rgb, int &amount); + void SetFullscreen (bool fullscreen); + int GetPageCount (); + bool IsFullscreen (); + + friend class SDLVideo; + + virtual void SetVSync (bool vsync); + +private: + PalEntry SourcePalette[256]; + BYTE GammaTable[3][256]; + PalEntry Flash; + int FlashAmount; + float Gamma; + bool UpdatePending; + + SDL_Window *Screen; + SDL_Renderer *Renderer; + union + { + SDL_Texture *Texture; + SDL_Surface *Surface; + }; + SDL_Rect UpdateRect; + + bool UsingRenderer; + bool NeedPalUpdate; + bool NeedGammaUpdate; + bool NotPaletted; + + void UpdateColors (); + void ResetSDLRenderer (); + + SDLFB () {} +}; +IMPLEMENT_CLASS(SDLFB) + +struct MiniModeInfo +{ + WORD Width, Height; +}; + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern IVideo *Video; +extern bool GUICapture; + +EXTERN_CVAR (Float, Gamma) +EXTERN_CVAR (Int, vid_maxfps) +EXTERN_CVAR (Bool, cl_capfps) +EXTERN_CVAR (Bool, vid_vsync) + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR (Int, vid_adapter, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +CVAR (Int, vid_displaybits, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +CVAR (Bool, vid_forcesurface, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +CUSTOM_CVAR (Float, rgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} +CUSTOM_CVAR (Float, ggamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} +CUSTOM_CVAR (Float, bgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (screen != NULL) + { + screen->SetGamma (Gamma); + } +} + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// Dummy screen sizes to pass when windowed +static MiniModeInfo WinModes[] = +{ + { 320, 200 }, + { 320, 240 }, + { 400, 225 }, // 16:9 + { 400, 300 }, + { 480, 270 }, // 16:9 + { 480, 360 }, + { 512, 288 }, // 16:9 + { 512, 384 }, + { 640, 360 }, // 16:9 + { 640, 400 }, + { 640, 480 }, + { 720, 480 }, // 16:10 + { 720, 540 }, + { 800, 450 }, // 16:9 + { 800, 480 }, + { 800, 500 }, // 16:10 + { 800, 600 }, + { 848, 480 }, // 16:9 + { 960, 600 }, // 16:10 + { 960, 720 }, + { 1024, 576 }, // 16:9 + { 1024, 600 }, // 17:10 + { 1024, 640 }, // 16:10 + { 1024, 768 }, + { 1088, 612 }, // 16:9 + { 1152, 648 }, // 16:9 + { 1152, 720 }, // 16:10 + { 1152, 864 }, + { 1280, 720 }, // 16:9 + { 1280, 854 }, + { 1280, 800 }, // 16:10 + { 1280, 960 }, + { 1280, 1024 }, // 5:4 + { 1360, 768 }, // 16:9 + { 1366, 768 }, + { 1400, 787 }, // 16:9 + { 1400, 875 }, // 16:10 + { 1400, 1050 }, + { 1440, 900 }, + { 1440, 960 }, + { 1440, 1080 }, + { 1600, 900 }, // 16:9 + { 1600, 1000 }, // 16:10 + { 1600, 1200 }, + { 1920, 1080 }, + { 1920, 1200 }, + { 2048, 1536 }, + { 2560, 1440 }, + { 2560, 1600 }, + { 2560, 2048 }, + { 2880, 1800 }, + { 3200, 1800 }, + { 3840, 2160 }, + { 3840, 2400 }, + { 4096, 2160 }, + { 5120, 2880 } +}; + +static cycle_t BlitCycles; +static cycle_t SDLFlipCycles; + +// CODE -------------------------------------------------------------------- + +void ScaleWithAspect (int &w, int &h, int Width, int Height) +{ + int resRatio = CheckRatio (Width, Height); + int screenRatio; + CheckRatio (w, h, &screenRatio); + if (resRatio == screenRatio) + return; + + double yratio; + switch(resRatio) + { + case 0: yratio = 4./3.; break; + case 1: yratio = 16./9.; break; + case 2: yratio = 16./10.; break; + case 3: yratio = 17./10.; break; + case 4: yratio = 5./4.; break; + default: return; + } + double y = w/yratio; + if (y > h) + w = h*yratio; + else + h = y; +} + +SDLVideo::SDLVideo (int parm) +{ + IteratorBits = 0; +} + +SDLVideo::~SDLVideo () +{ +} + +void SDLVideo::StartModeIterator (int bits, bool fs) +{ + IteratorMode = 0; + IteratorBits = bits; +} + +bool SDLVideo::NextMode (int *width, int *height, bool *letterbox) +{ + if (IteratorBits != 8) + return false; + + if ((unsigned)IteratorMode < sizeof(WinModes)/sizeof(WinModes[0])) + { + *width = WinModes[IteratorMode].Width; + *height = WinModes[IteratorMode].Height; + ++IteratorMode; + return true; + } + return false; +} + +DFrameBuffer *SDLVideo::CreateFrameBuffer (int width, int height, bool fullscreen, DFrameBuffer *old) +{ + static int retry = 0; + static int owidth, oheight; + + PalEntry flashColor; + int flashAmount; + + if (old != NULL) + { // Reuse the old framebuffer if its attributes are the same + SDLFB *fb = static_cast (old); + if (fb->Width == width && + fb->Height == height) + { + bool fsnow = (SDL_GetWindowFlags (fb->Screen) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; + + if (fsnow != fullscreen) + { + fb->SetFullscreen (fullscreen); + } + return old; + } + old->GetFlash (flashColor, flashAmount); + old->ObjectFlags |= OF_YesReallyDelete; + if (screen == old) screen = NULL; + delete old; + } + else + { + flashColor = 0; + flashAmount = 0; + } + + SDLFB *fb = new SDLFB (width, height, fullscreen); + retry = 0; + + // If we could not create the framebuffer, try again with slightly + // different parameters in this order: + // 1. Try with the closest size + // 2. Try in the opposite screen mode with the original size + // 3. Try in the opposite screen mode with the closest size + // This is a somewhat confusing mass of recursion here. + + while (fb == NULL || !fb->IsValid ()) + { + if (fb != NULL) + { + delete fb; + } + + switch (retry) + { + case 0: + owidth = width; + oheight = height; + case 2: + // Try a different resolution. Hopefully that will work. + I_ClosestResolution (&width, &height, 8); + break; + + case 1: + // Try changing fullscreen mode. Maybe that will work. + width = owidth; + height = oheight; + fullscreen = !fullscreen; + break; + + default: + // I give up! + I_FatalError ("Could not create new screen (%d x %d)", owidth, oheight); + } + + ++retry; + fb = static_cast(CreateFrameBuffer (width, height, fullscreen, NULL)); + } + + fb->SetFlash (flashColor, flashAmount); + + return fb; +} + +void SDLVideo::SetWindowedScale (float scale) +{ +} + +// FrameBuffer implementation ----------------------------------------------- + +SDLFB::SDLFB (int width, int height, bool fullscreen) + : DFrameBuffer (width, height) +{ + int i; + + NeedPalUpdate = false; + NeedGammaUpdate = false; + UpdatePending = false; + NotPaletted = false; + FlashAmount = 0; + + FString caption; + caption.Format(GAMESIG " %s (%s)", GetVersionString(), GetGitTime()); + + Screen = SDL_CreateWindow (caption, + SDL_WINDOWPOS_UNDEFINED_DISPLAY(vid_adapter), SDL_WINDOWPOS_UNDEFINED_DISPLAY(vid_adapter), + width, height, (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)); + + if (Screen == NULL) + return; + + Renderer = NULL; + Texture = NULL; + ResetSDLRenderer (); + + for (i = 0; i < 256; i++) + { + GammaTable[0][i] = GammaTable[1][i] = GammaTable[2][i] = i; + } + + memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256); + UpdateColors (); + +#ifdef __APPLE__ + SetVSync (vid_vsync); +#endif +} + + +SDLFB::~SDLFB () +{ + if(Screen) + { + if (Renderer) + { + if (Texture) + SDL_DestroyTexture (Texture); + SDL_DestroyRenderer (Renderer); + } + + SDL_DestroyWindow (Screen); + } +} + +bool SDLFB::IsValid () +{ + return DFrameBuffer::IsValid() && Screen != NULL; +} + +int SDLFB::GetPageCount () +{ + return 1; +} + +bool SDLFB::Lock (bool buffered) +{ + return DSimpleCanvas::Lock (); +} + +bool SDLFB::Relock () +{ + return DSimpleCanvas::Lock (); +} + +void SDLFB::Unlock () +{ + if (UpdatePending && LockCount == 1) + { + Update (); + } + else if (--LockCount <= 0) + { + Buffer = NULL; + LockCount = 0; + } +} + +void SDLFB::Update () +{ + if (LockCount != 1) + { + if (LockCount > 0) + { + UpdatePending = true; + --LockCount; + } + return; + } + + DrawRateStuff (); + +#ifndef __APPLE__ + if(vid_maxfps && !cl_capfps) + { + SEMAPHORE_WAIT(FPSLimitSemaphore) + } +#endif + + Buffer = NULL; + LockCount = 0; + UpdatePending = false; + + BlitCycles.Reset(); + SDLFlipCycles.Reset(); + BlitCycles.Clock(); + + void *pixels; + int pitch; + if (UsingRenderer) + { + if (SDL_LockTexture (Texture, NULL, &pixels, &pitch)) + return; + } + else + { + if (SDL_LockSurface (Surface)) + return; + + pixels = Surface->pixels; + pitch = Surface->pitch; + } + + if (NotPaletted) + { + GPfx.Convert (MemBuffer, Pitch, + pixels, pitch, Width, Height, + FRACUNIT, FRACUNIT, 0, 0); + } + else + { + if (pitch == Pitch) + { + memcpy (pixels, MemBuffer, Width*Height); + } + else + { + for (int y = 0; y < Height; ++y) + { + memcpy ((BYTE *)pixels+y*pitch, MemBuffer+y*Pitch, Width); + } + } + } + + if (UsingRenderer) + { + SDL_UnlockTexture (Texture); + + SDLFlipCycles.Clock(); + SDL_RenderCopy(Renderer, Texture, NULL, &UpdateRect); + SDL_RenderPresent(Renderer); + SDLFlipCycles.Unclock(); + } + else + { + SDL_UnlockSurface (Surface); + + SDLFlipCycles.Clock(); + SDL_UpdateWindowSurface (Screen); + SDLFlipCycles.Unclock(); + } + + BlitCycles.Unclock(); + + if (NeedGammaUpdate) + { + bool Windowed = false; + NeedGammaUpdate = false; + CalcGamma ((Windowed || rgamma == 0.f) ? Gamma : (Gamma * rgamma), GammaTable[0]); + CalcGamma ((Windowed || ggamma == 0.f) ? Gamma : (Gamma * ggamma), GammaTable[1]); + CalcGamma ((Windowed || bgamma == 0.f) ? Gamma : (Gamma * bgamma), GammaTable[2]); + NeedPalUpdate = true; + } + + if (NeedPalUpdate) + { + NeedPalUpdate = false; + UpdateColors (); + } +} + +void SDLFB::UpdateColors () +{ + if (NotPaletted) + { + PalEntry palette[256]; + + for (int i = 0; i < 256; ++i) + { + palette[i].r = GammaTable[0][SourcePalette[i].r]; + palette[i].g = GammaTable[1][SourcePalette[i].g]; + palette[i].b = GammaTable[2][SourcePalette[i].b]; + } + if (FlashAmount) + { + DoBlending (palette, palette, + 256, GammaTable[0][Flash.r], GammaTable[1][Flash.g], GammaTable[2][Flash.b], + FlashAmount); + } + GPfx.SetPalette (palette); + } + else + { + SDL_Color colors[256]; + + for (int i = 0; i < 256; ++i) + { + colors[i].r = GammaTable[0][SourcePalette[i].r]; + colors[i].g = GammaTable[1][SourcePalette[i].g]; + colors[i].b = GammaTable[2][SourcePalette[i].b]; + } + if (FlashAmount) + { + DoBlending ((PalEntry *)colors, (PalEntry *)colors, + 256, GammaTable[2][Flash.b], GammaTable[1][Flash.g], GammaTable[0][Flash.r], + FlashAmount); + } + SDL_SetPaletteColors (Surface->format->palette, colors, 0, 256); + } +} + +PalEntry *SDLFB::GetPalette () +{ + return SourcePalette; +} + +void SDLFB::UpdatePalette () +{ + NeedPalUpdate = true; +} + +bool SDLFB::SetGamma (float gamma) +{ + Gamma = gamma; + NeedGammaUpdate = true; + return true; +} + +bool SDLFB::SetFlash (PalEntry rgb, int amount) +{ + Flash = rgb; + FlashAmount = amount; + NeedPalUpdate = true; + return true; +} + +void SDLFB::GetFlash (PalEntry &rgb, int &amount) +{ + rgb = Flash; + amount = FlashAmount; +} + +// Q: Should I gamma adjust the returned palette? +void SDLFB::GetFlashedPalette (PalEntry pal[256]) +{ + memcpy (pal, SourcePalette, 256*sizeof(PalEntry)); + if (FlashAmount) + { + DoBlending (pal, pal, 256, Flash.r, Flash.g, Flash.b, FlashAmount); + } +} + +void SDLFB::SetFullscreen (bool fullscreen) +{ + SDL_SetWindowFullscreen (Screen, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + if (!fullscreen) + { + // Restore proper window size + SDL_SetWindowSize (Screen, Width, Height); + } + + ResetSDLRenderer (); +} + +bool SDLFB::IsFullscreen () +{ + return (SDL_GetWindowFlags (Screen) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; +} + +void SDLFB::ResetSDLRenderer () +{ + if (Renderer) + { + if (Texture) + SDL_DestroyTexture (Texture); + SDL_DestroyRenderer (Renderer); + } + + UsingRenderer = !vid_forcesurface; + if (UsingRenderer) + { + Renderer = SDL_CreateRenderer (Screen, -1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_TARGETTEXTURE| + (vid_vsync ? SDL_RENDERER_PRESENTVSYNC : 0)); + if (!Renderer) + return; + + Uint32 fmt; + switch(vid_displaybits) + { + default: fmt = SDL_PIXELFORMAT_ARGB8888; break; + case 30: fmt = SDL_PIXELFORMAT_ARGB2101010; break; + case 24: fmt = SDL_PIXELFORMAT_RGB888; break; + case 16: fmt = SDL_PIXELFORMAT_RGB565; break; + case 15: fmt = SDL_PIXELFORMAT_ARGB1555; break; + } + Texture = SDL_CreateTexture (Renderer, fmt, SDL_TEXTUREACCESS_STREAMING, Width, Height); + + { + NotPaletted = true; + + Uint32 format; + SDL_QueryTexture(Texture, &format, NULL, NULL, NULL); + + Uint32 Rmask, Gmask, Bmask, Amask; + int bpp; + SDL_PixelFormatEnumToMasks(format, &bpp, &Rmask, &Gmask, &Bmask, &Amask); + GPfx.SetFormat (bpp, Rmask, Gmask, Bmask); + } + } + else + { + Surface = SDL_GetWindowSurface (Screen); + + if (Surface->format->palette == NULL) + { + NotPaletted = true; + GPfx.SetFormat (Surface->format->BitsPerPixel, Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask); + } + else + NotPaletted = false; + } + + // Calculate update rectangle + if (IsFullscreen ()) + { + int w, h; + SDL_GetWindowSize (Screen, &w, &h); + UpdateRect.w = w; + UpdateRect.h = h; + ScaleWithAspect (UpdateRect.w, UpdateRect.h, Width, Height); + UpdateRect.x = (w - UpdateRect.w)/2; + UpdateRect.y = (h - UpdateRect.h)/2; + } + else + { + // In windowed mode we just update the whole window. + UpdateRect.x = 0; + UpdateRect.y = 0; + UpdateRect.w = Width; + UpdateRect.h = Height; + } +} + +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); + } +#else + ResetSDLRenderer (); +#endif // __APPLE__ +} + +ADD_STAT (blit) +{ + FString out; + out.Format ("blit=%04.1f ms flip=%04.1f ms", + BlitCycles.TimeMS(), SDLFlipCycles.TimeMS()); + return out; +} diff --git a/src/sdl/sdlvideo.h b/src/posix/sdl/sdlvideo.h similarity index 92% rename from src/sdl/sdlvideo.h rename to src/posix/sdl/sdlvideo.h index 3cd38a140..072167b5a 100644 --- a/src/sdl/sdlvideo.h +++ b/src/posix/sdl/sdlvideo.h @@ -1,22 +1,21 @@ -#include "hardware.h" -#include "v_video.h" - -class SDLVideo : public IVideo -{ - public: - SDLVideo (int parm); - ~SDLVideo (); - - EDisplayType GetDisplayType () { return DISPLAY_Both; } - void SetWindowedScale (float scale); - - DFrameBuffer *CreateFrameBuffer (int width, int height, bool fs, DFrameBuffer *old); - - void StartModeIterator (int bits, bool fs); - bool NextMode (int *width, int *height, bool *letterbox); - -private: - int IteratorMode; - int IteratorBits; - bool IteratorFS; -}; +#include "hardware.h" +#include "v_video.h" + +class SDLVideo : public IVideo +{ + public: + SDLVideo (int parm); + ~SDLVideo (); + + EDisplayType GetDisplayType () { return DISPLAY_Both; } + void SetWindowedScale (float scale); + + DFrameBuffer *CreateFrameBuffer (int width, int height, bool fs, DFrameBuffer *old); + + void StartModeIterator (int bits, bool fs); + bool NextMode (int *width, int *height, bool *letterbox); + +private: + int IteratorMode; + int IteratorBits; +}; diff --git a/src/sdl/st_start.cpp b/src/posix/st_start.cpp similarity index 96% rename from src/sdl/st_start.cpp rename to src/posix/st_start.cpp index 33a5abe0b..060548b53 100644 --- a/src/sdl/st_start.cpp +++ b/src/posix/st_start.cpp @@ -1,354 +1,354 @@ -/* -** st_start.cpp -** Handles the startup screen. -** -**--------------------------------------------------------------------------- -** Copyright 2006-2007 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. -** -** 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. -**--------------------------------------------------------------------------- -** -*/ - -// HEADER FILES ------------------------------------------------------------ - -#include -#include -#include -#include - -#include "st_start.h" -#include "doomdef.h" -#include "i_system.h" -#include "c_cvars.h" - -// MACROS ------------------------------------------------------------------ - -// TYPES ------------------------------------------------------------------- - -class FTTYStartupScreen : public FStartupScreen -{ - public: - FTTYStartupScreen(int max_progress); - ~FTTYStartupScreen(); - - void Progress(); - void NetInit(const char *message, int num_players); - void NetProgress(int count); - void NetMessage(const char *format, ...); // cover for printf - void NetDone(); - bool NetLoop(bool (*timer_callback)(void *), void *userdata); - protected: - bool DidNetInit; - int NetMaxPos, NetCurPos; - const char *TheNetMessage; - termios OldTermIOS; -}; - -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -void I_ShutdownJoysticks(); - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -static void DeleteStartupScreen(); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -FStartupScreen *StartScreen; - -CUSTOM_CVAR(Int, showendoom, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (self < 0) self = 0; - else if (self > 2) self=2; -} - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static const char SpinnyProgressChars[4] = { '|', '/', '-', '\\' }; - -// CODE -------------------------------------------------------------------- - -//========================================================================== -// -// FStartupScreen :: CreateInstance -// -// Initializes the startup screen for the detected game. -// Sets the size of the progress bar and displays the startup screen. -// -//========================================================================== - -FStartupScreen *FStartupScreen::CreateInstance(int max_progress) -{ - atterm(DeleteStartupScreen); - return new FTTYStartupScreen(max_progress); -} - -//=========================================================================== -// -// DeleteStartupScreen -// -// Makes sure the startup screen has been deleted before quitting. -// -//=========================================================================== - -void DeleteStartupScreen() -{ - if (StartScreen != NULL) - { - delete StartScreen; - StartScreen = NULL; - } -} - -//=========================================================================== -// -// FTTYStartupScreen Constructor -// -// Sets the size of the progress bar and displays the startup screen. -// -//=========================================================================== - -FTTYStartupScreen::FTTYStartupScreen(int max_progress) - : FStartupScreen(max_progress) -{ - DidNetInit = false; - NetMaxPos = 0; - NetCurPos = 0; - TheNetMessage = NULL; -} - -//=========================================================================== -// -// FTTYStartupScreen Destructor -// -// Called just before entering graphics mode to deconstruct the startup -// screen. -// -//=========================================================================== - -FTTYStartupScreen::~FTTYStartupScreen() -{ - NetDone(); // Just in case it wasn't called yet and needs to be. -} - -//=========================================================================== -// -// FTTYStartupScreen :: Progress -// -// If there was a progress bar, this would move it. But the basic TTY -// startup screen doesn't have one, so this function does nothing. -// -//=========================================================================== - -void FTTYStartupScreen::Progress() -{ -} - -//=========================================================================== -// -// FTTYStartupScreen :: NetInit -// -// Sets stdin for unbuffered I/O, displays the given message, and shows -// a progress meter. -// -//=========================================================================== - -void FTTYStartupScreen::NetInit(const char *message, int numplayers) -{ - if (!DidNetInit) - { - termios rawtermios; - - fprintf (stderr, "Press 'Q' to abort network game synchronization."); - // Set stdin to raw mode so we can get keypresses in ST_CheckNetAbort() - // immediately without waiting for an EOL. - tcgetattr (STDIN_FILENO, &OldTermIOS); - rawtermios = OldTermIOS; - rawtermios.c_lflag &= ~(ICANON | ECHO); - tcsetattr (STDIN_FILENO, TCSANOW, &rawtermios); - DidNetInit = true; - } - if (numplayers == 1) - { - // Status message without any real progress info. - fprintf (stderr, "\n%s.", message); - } - else - { - fprintf (stderr, "\n%s: ", message); - } - fflush (stderr); - TheNetMessage = message; - NetMaxPos = numplayers; - NetCurPos = 0; - NetProgress(1); // You always know about yourself -} - -//=========================================================================== -// -// FTTYStartupScreen :: NetDone -// -// Restores the old stdin tty settings. -// -//=========================================================================== - -void FTTYStartupScreen::NetDone() -{ - // Restore stdin settings - if (DidNetInit) - { - tcsetattr (STDIN_FILENO, TCSANOW, &OldTermIOS); - printf ("\n"); - DidNetInit = false; - } -} - -//=========================================================================== -// -// FTTYStartupScreen :: NetMessage -// -// Call this between NetInit() and NetDone() instead of Printf() to -// display messages, because the progress meter is mixed in the same output -// stream as normal messages. -// -//=========================================================================== - -void FTTYStartupScreen::NetMessage(const char *format, ...) -{ - FString str; - va_list argptr; - - va_start (argptr, format); - str.VFormat (format, argptr); - va_end (argptr); - fprintf (stderr, "\r%-40s\n", str.GetChars()); -} - -//=========================================================================== -// -// FTTYStartupScreen :: NetProgress -// -// Sets the network progress meter. If count is 0, it gets bumped by 1. -// Otherwise, it is set to count. -// -//=========================================================================== - -void FTTYStartupScreen::NetProgress(int count) -{ - int i; - - if (count == 0) - { - NetCurPos++; - } - else if (count > 0) - { - NetCurPos = count; - } - if (NetMaxPos == 0) - { - // Spinny-type progress meter, because we're a guest waiting for the host. - fprintf (stderr, "\r%s: %c", TheNetMessage, SpinnyProgressChars[NetCurPos & 3]); - fflush (stderr); - } - else if (NetMaxPos > 1) - { - // Dotty-type progress meter. - fprintf (stderr, "\r%s: ", TheNetMessage); - for (i = 0; i < NetCurPos; ++i) - { - fputc ('.', stderr); - } - fprintf (stderr, "%*c[%2d/%2d]", NetMaxPos + 1 - NetCurPos, ' ', NetCurPos, NetMaxPos); - fflush (stderr); - } -} - -//=========================================================================== -// -// FTTYStartupScreen :: NetLoop -// -// The timer_callback function is called at least two times per second -// and passed the userdata value. It should return true to stop the loop and -// return control to the caller or false to continue the loop. -// -// ST_NetLoop will return true if the loop was halted by the callback and -// false if the loop was halted because the user wants to abort the -// network synchronization. -// -//=========================================================================== - -bool FTTYStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata) -{ - fd_set rfds; - struct timeval tv; - int retval; - char k; - - for (;;) - { - // Don't flood the network with packets on startup. - tv.tv_sec = 0; - tv.tv_usec = 500000; - - FD_ZERO (&rfds); - FD_SET (STDIN_FILENO, &rfds); - - retval = select (1, &rfds, NULL, NULL, &tv); - - if (retval == -1) - { - // Error - } - else if (retval == 0) - { - if (timer_callback (userdata)) - { - fputc ('\n', stderr); - return true; - } - } - else if (read (STDIN_FILENO, &k, 1) == 1) - { - // Check input on stdin - if (k == 'q' || k == 'Q') - { - fprintf (stderr, "\nNetwork game synchronization aborted."); - return false; - } - } - } -} - -void ST_Endoom() -{ - I_ShutdownJoysticks(); - exit(0); -} +/* +** st_start.cpp +** Handles the startup screen. +** +**--------------------------------------------------------------------------- +** Copyright 2006-2007 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. +** +** 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. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include +#include + +#include "st_start.h" +#include "doomdef.h" +#include "i_system.h" +#include "c_cvars.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class FTTYStartupScreen : public FStartupScreen +{ + public: + FTTYStartupScreen(int max_progress); + ~FTTYStartupScreen(); + + void Progress(); + void NetInit(const char *message, int num_players); + void NetProgress(int count); + void NetMessage(const char *format, ...); // cover for printf + void NetDone(); + bool NetLoop(bool (*timer_callback)(void *), void *userdata); + protected: + bool DidNetInit; + int NetMaxPos, NetCurPos; + const char *TheNetMessage; + termios OldTermIOS; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +void I_ShutdownJoysticks(); + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +static void DeleteStartupScreen(); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +FStartupScreen *StartScreen; + +CUSTOM_CVAR(Int, showendoom, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) self = 0; + else if (self > 2) self=2; +} + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static const char SpinnyProgressChars[4] = { '|', '/', '-', '\\' }; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FStartupScreen :: CreateInstance +// +// Initializes the startup screen for the detected game. +// Sets the size of the progress bar and displays the startup screen. +// +//========================================================================== + +FStartupScreen *FStartupScreen::CreateInstance(int max_progress) +{ + atterm(DeleteStartupScreen); + return new FTTYStartupScreen(max_progress); +} + +//=========================================================================== +// +// DeleteStartupScreen +// +// Makes sure the startup screen has been deleted before quitting. +// +//=========================================================================== + +void DeleteStartupScreen() +{ + if (StartScreen != NULL) + { + delete StartScreen; + StartScreen = NULL; + } +} + +//=========================================================================== +// +// FTTYStartupScreen Constructor +// +// Sets the size of the progress bar and displays the startup screen. +// +//=========================================================================== + +FTTYStartupScreen::FTTYStartupScreen(int max_progress) + : FStartupScreen(max_progress) +{ + DidNetInit = false; + NetMaxPos = 0; + NetCurPos = 0; + TheNetMessage = NULL; +} + +//=========================================================================== +// +// FTTYStartupScreen Destructor +// +// Called just before entering graphics mode to deconstruct the startup +// screen. +// +//=========================================================================== + +FTTYStartupScreen::~FTTYStartupScreen() +{ + NetDone(); // Just in case it wasn't called yet and needs to be. +} + +//=========================================================================== +// +// FTTYStartupScreen :: Progress +// +// If there was a progress bar, this would move it. But the basic TTY +// startup screen doesn't have one, so this function does nothing. +// +//=========================================================================== + +void FTTYStartupScreen::Progress() +{ +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetInit +// +// Sets stdin for unbuffered I/O, displays the given message, and shows +// a progress meter. +// +//=========================================================================== + +void FTTYStartupScreen::NetInit(const char *message, int numplayers) +{ + if (!DidNetInit) + { + termios rawtermios; + + fprintf (stderr, "Press 'Q' to abort network game synchronization."); + // Set stdin to raw mode so we can get keypresses in ST_CheckNetAbort() + // immediately without waiting for an EOL. + tcgetattr (STDIN_FILENO, &OldTermIOS); + rawtermios = OldTermIOS; + rawtermios.c_lflag &= ~(ICANON | ECHO); + tcsetattr (STDIN_FILENO, TCSANOW, &rawtermios); + DidNetInit = true; + } + if (numplayers == 1) + { + // Status message without any real progress info. + fprintf (stderr, "\n%s.", message); + } + else + { + fprintf (stderr, "\n%s: ", message); + } + fflush (stderr); + TheNetMessage = message; + NetMaxPos = numplayers; + NetCurPos = 0; + NetProgress(1); // You always know about yourself +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetDone +// +// Restores the old stdin tty settings. +// +//=========================================================================== + +void FTTYStartupScreen::NetDone() +{ + // Restore stdin settings + if (DidNetInit) + { + tcsetattr (STDIN_FILENO, TCSANOW, &OldTermIOS); + printf ("\n"); + DidNetInit = false; + } +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetMessage +// +// Call this between NetInit() and NetDone() instead of Printf() to +// display messages, because the progress meter is mixed in the same output +// stream as normal messages. +// +//=========================================================================== + +void FTTYStartupScreen::NetMessage(const char *format, ...) +{ + FString str; + va_list argptr; + + va_start (argptr, format); + str.VFormat (format, argptr); + va_end (argptr); + fprintf (stderr, "\r%-40s\n", str.GetChars()); +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetProgress +// +// Sets the network progress meter. If count is 0, it gets bumped by 1. +// Otherwise, it is set to count. +// +//=========================================================================== + +void FTTYStartupScreen::NetProgress(int count) +{ + int i; + + if (count == 0) + { + NetCurPos++; + } + else if (count > 0) + { + NetCurPos = count; + } + if (NetMaxPos == 0) + { + // Spinny-type progress meter, because we're a guest waiting for the host. + fprintf (stderr, "\r%s: %c", TheNetMessage, SpinnyProgressChars[NetCurPos & 3]); + fflush (stderr); + } + else if (NetMaxPos > 1) + { + // Dotty-type progress meter. + fprintf (stderr, "\r%s: ", TheNetMessage); + for (i = 0; i < NetCurPos; ++i) + { + fputc ('.', stderr); + } + fprintf (stderr, "%*c[%2d/%2d]", NetMaxPos + 1 - NetCurPos, ' ', NetCurPos, NetMaxPos); + fflush (stderr); + } +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetLoop +// +// The timer_callback function is called at least two times per second +// and passed the userdata value. It should return true to stop the loop and +// return control to the caller or false to continue the loop. +// +// ST_NetLoop will return true if the loop was halted by the callback and +// false if the loop was halted because the user wants to abort the +// network synchronization. +// +//=========================================================================== + +bool FTTYStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata) +{ + fd_set rfds; + struct timeval tv; + int retval; + char k; + + for (;;) + { + // Don't flood the network with packets on startup. + tv.tv_sec = 0; + tv.tv_usec = 500000; + + FD_ZERO (&rfds); + FD_SET (STDIN_FILENO, &rfds); + + retval = select (1, &rfds, NULL, NULL, &tv); + + if (retval == -1) + { + // Error + } + else if (retval == 0) + { + if (timer_callback (userdata)) + { + fputc ('\n', stderr); + return true; + } + } + else if (read (STDIN_FILENO, &k, 1) == 1) + { + // Check input on stdin + if (k == 'q' || k == 'Q') + { + fprintf (stderr, "\nNetwork game synchronization aborted."); + return false; + } + } + } +} + +void ST_Endoom() +{ + I_ShutdownJoysticks(); + exit(0); +} diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index c56255888..bf0ac910f 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -809,19 +809,18 @@ void FWallTmapVals::InitFromWallCoords(const FWallCoords *wallc) { if (MirrorFlags & RF_XFLIP) { - UoverZorg = (float)wallc->tx2 * WallTMapScale; - UoverZstep = (float)(-wallc->ty2) * 32.f; - InvZorg = (float)(wallc->tx2 - wallc->tx1) * WallTMapScale; - InvZstep = (float)(wallc->ty1 - wallc->ty2) * 32.f; + UoverZorg = (float)wallc->tx2 * centerx; + UoverZstep = (float)(-wallc->ty2); + InvZorg = (float)(wallc->tx2 - wallc->tx1) * centerx; + InvZstep = (float)(wallc->ty1 - wallc->ty2); } else { - UoverZorg = (float)wallc->tx1 * WallTMapScale; - UoverZstep = (float)(-wallc->ty1) * 32.f; - InvZorg = (float)(wallc->tx1 - wallc->tx2) * WallTMapScale; - InvZstep = (float)(wallc->ty2 - wallc->ty1) * 32.f; + UoverZorg = (float)wallc->tx1 * centerx; + UoverZstep = (float)(-wallc->ty1); + InvZorg = (float)(wallc->tx1 - wallc->tx2) * centerx; + InvZstep = (float)(wallc->ty2 - wallc->ty1); } - InitDepth(); } void FWallTmapVals::InitFromLine(int tx1, int ty1, int tx2, int ty2) @@ -837,17 +836,10 @@ void FWallTmapVals::InitFromLine(int tx1, int ty1, int tx2, int ty2) fullx2 = -fullx2; } - UoverZorg = (float)fullx1 * WallTMapScale; - UoverZstep = (float)(-fully1) * 32.f; - InvZorg = (float)(fullx1 - fullx2) * WallTMapScale; - InvZstep = (float)(fully2 - fully1) * 32.f; - InitDepth(); -} - -void FWallTmapVals::InitDepth() -{ - DepthScale = InvZstep * WallTMapScale2; - DepthOrg = -UoverZstep * WallTMapScale2; + UoverZorg = (float)fullx1 * centerx; + UoverZstep = (float)(-fully1); + InvZorg = (float)(fullx1 - fullx2) * centerx; + InvZstep = (float)(fully2 - fully1); } // diff --git a/src/r_bsp.h b/src/r_bsp.h index 1b5af9805..acd519c62 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -44,13 +44,11 @@ struct FWallCoords struct FWallTmapVals { - float DepthOrg, DepthScale; float UoverZorg, UoverZstep; float InvZorg, InvZstep; void InitFromWallCoords(const FWallCoords *wallc); void InitFromLine(int x1, int y1, int x2, int y2); - void InitDepth(); }; extern FWallCoords WallC; diff --git a/src/r_main.cpp b/src/r_main.cpp index bc3c4c7c0..dbf4cbeb5 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -117,7 +117,6 @@ FDynamicColormap*basecolormap; // [RH] colormap currently drawing with int fixedlightlev; lighttable_t *fixedcolormap; FSpecialColormap *realfixedcolormap; -float WallTMapScale; float WallTMapScale2; @@ -386,8 +385,7 @@ void R_SWRSetWindow(int windowSize, int fullWidth, int fullHeight, int stHeight, iyaspectmulfloat = (float)virtwidth * r_Yaspect / 320.f / (float)virtheight; InvZtoScale = yaspectmul * centerx; - WallTMapScale = (float)centerx * 32.f; - WallTMapScale2 = iyaspectmulfloat * 2.f / (float)centerx; + WallTMapScale2 = iyaspectmulfloat * 64.f / (float)centerx; // psprite scales pspritexscale = (centerxwide << FRACBITS) / 160; diff --git a/src/r_main.h b/src/r_main.h index 0126d3906..715a60610 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -42,7 +42,6 @@ extern fixed_t FocalLengthX, FocalLengthY; extern float FocalLengthXfloat; extern fixed_t InvZtoScale; -extern float WallTMapScale; extern float WallTMapScale2; extern int viewwindowx; diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 15f736c93..cf00abc45 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -2893,6 +2893,8 @@ void PrepWall (fixed_t *swall, fixed_t *lwall, fixed_t walxrepeat, int x1, int x { // swall = scale, lwall = texturecolumn double top, bot, i; double xrepeat = fabs((double)walxrepeat); + double depth_scale = WallT.InvZstep * WallTMapScale2; + double depth_org = -WallT.UoverZstep * WallTMapScale2; i = x1 - centerx; top = WallT.UoverZorg + WallT.UoverZstep * i; @@ -2909,7 +2911,7 @@ void PrepWall (fixed_t *swall, fixed_t *lwall, fixed_t walxrepeat, int x1, int x { lwall[x] = xs_RoundToInt(frac * xrepeat); } - swall[x] = xs_RoundToInt(frac * WallT.DepthScale + WallT.DepthOrg); + swall[x] = xs_RoundToInt(frac * depth_scale + depth_org); top += WallT.UoverZstep; bot += WallT.InvZstep; } diff --git a/src/r_things.cpp b/src/r_things.cpp index c0475ee75..e27a5c87e 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, @@ -1410,7 +1411,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()) { diff --git a/src/r_utility.cpp b/src/r_utility.cpp index 5bf38ad26..585e3dcf3 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/resourcefiles/resourcefile.cpp b/src/resourcefiles/resourcefile.cpp index 8a4f07fd7..15a4337b1 100644 --- a/src/resourcefiles/resourcefile.cpp +++ b/src/resourcefiles/resourcefile.cpp @@ -150,11 +150,28 @@ void FResourceLump::LumpNameSetup(const char *iname) // //========================================================================== +static bool IsWadInFolder(const FResourceFile* const archive, const char* const resPath) +{ + // Checks a special case when was put in + // directory inside + + if (NULL == archive) + { + return false; + } + + const FString dirName = ExtractFileBase(archive->Filename); + const FString fileName = ExtractFileBase(resPath, true); + const FString filePath = dirName + '/' + fileName; + + return 0 == filePath.CompareNoCase(resPath); +} + void FResourceLump::CheckEmbedded() { // Checks for embedded archives const char *c = strstr(FullName, ".wad"); - if (c && strlen(c) == 4 && !strchr(FullName, '/')) + if (c && strlen(c) == 4 && (!strchr(FullName, '/') || IsWadInFolder(Owner, FullName))) { // Mark all embedded WADs Flags |= LUMPF_EMBEDDED; diff --git a/src/s_sound.cpp b/src/s_sound.cpp index b417d8b17..e791e4cbf 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -1559,7 +1559,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; @@ -1767,21 +1767,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 @@ -1795,16 +1787,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 dd90ce1d1..52a5d9f61 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -158,6 +158,7 @@ std2: 'random' { RET(TK_Random); } 'random2' { RET(TK_Random2); } 'frandom' { RET(TK_FRandom); } + 'randompick' { RET(TK_RandomPick); } L (L|D)* { RET(TK_Identifier); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index 9dde74972..22f6e9cd4 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -122,4 +122,5 @@ xx(TK_Array, "'array'") xx(TK_In, "'in'") xx(TK_SizeOf, "'sizeof'") xx(TK_AlignOf, "'alignof'") +xx(TK_RandomPick, "'randompick'") #undef xx diff --git a/src/sdl/SDLMain.m b/src/sdl/SDLMain.m deleted file mode 100644 index 78c4e3d2b..000000000 --- a/src/sdl/SDLMain.m +++ /dev/null @@ -1,387 +0,0 @@ -/* SDLMain.m - main entry point for our Cocoa-ized SDL app - Initial Version: Darrell Walisser - Non-NIB-Code & other changes: Max Horn - - Feel free to customize this file to suit your needs -*/ - -#import "SDL.h" -#import -#import /* for MAXPATHLEN */ -#import - -@interface SDLMain : NSObject -@end - -/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, - but the method still is there and works. To avoid warnings, we declare - it ourselves here. */ -@interface NSApplication(SDL_Missing_Methods) -- (void)setAppleMenu:(NSMenu *)menu; -@end - -/* Use this flag to determine whether we use SDLMain.nib or not */ -#define SDL_USE_NIB_FILE 0 - -/* Use this flag to determine whether we use CPS (docking) or not */ -#define SDL_USE_CPS 1 -#ifdef SDL_USE_CPS -/* Portions of CPS.h */ -typedef struct CPSProcessSerNum -{ - UInt32 lo; - UInt32 hi; -} CPSProcessSerNum; - -extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); -extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); -extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); - -#endif /* SDL_USE_CPS */ - -static int gArgc; -static char **gArgv; -static BOOL gFinderLaunch; -static BOOL gCalledAppMainline = FALSE; - -static NSString *getApplicationName(void) -{ - NSDictionary *dict; - NSString *appName = 0; - - /* Determine the application name */ - dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); - if (dict) - appName = [dict objectForKey: @"CFBundleName"]; - - if (![appName length]) - appName = [[NSProcessInfo processInfo] processName]; - - return appName; -} - -#if SDL_USE_NIB_FILE -/* A helper category for NSString */ -@interface NSString (ReplaceSubString) -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; -@end -#endif - -@interface SDLApplication : NSApplication -@end - -@implementation SDLApplication -/* Invoked from the Quit menu item */ -- (void)terminate:(id)sender -{ - /* Post a SDL_QUIT event */ - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); -} -@end - -/* The main class of the application, the application's delegate */ -@implementation SDLMain - -/* Set the working directory to the .app's parent directory */ -- (void) setupWorkingDirectory:(BOOL)shouldChdir -{ - if (shouldChdir) - { - char parentdir[MAXPATHLEN]; - CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); - if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) { - assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ - } - CFRelease(url); - CFRelease(url2); - } - -} - -#if SDL_USE_NIB_FILE - -/* Fix menu to contain the real app name instead of "SDL App" */ -- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName -{ - NSRange aRange; - NSEnumerator *enumerator; - NSMenuItem *menuItem; - - aRange = [[aMenu title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; - - enumerator = [[aMenu itemArray] objectEnumerator]; - while ((menuItem = [enumerator nextObject])) - { - aRange = [[menuItem title] rangeOfString:@"SDL App"]; - if (aRange.length != 0) - [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; - if ([menuItem hasSubmenu]) - [self fixMenu:[menuItem submenu] withAppName:appName]; - } - [ aMenu sizeToFit ]; -} - -#else - -static void setApplicationMenu(void) -{ - /* warning: this code is very odd */ - NSMenu *appleMenu; - NSMenuItem *menuItem; - NSString *title; - NSString *appName; - - appName = getApplicationName(); - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - - /* Add menu items */ - title = [@"About " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Hide " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; - - [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Quit " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - - - /* Put menu into the menubar */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:appleMenu]; - [[NSApp mainMenu] addItem:menuItem]; - - /* Tell the application object that this is now the application menu */ - [NSApp setAppleMenu:appleMenu]; - - /* Finally give up our references to the objects */ - [appleMenu release]; - [menuItem release]; -} - -/* Create a window menu */ -static void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - /* "Minimize" item */ - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - [menuItem release]; - - /* Put menu into the menubar */ - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[NSApp mainMenu] addItem:windowMenuItem]; - - /* Tell the application object that this is now the window menu */ - [NSApp setWindowsMenu:windowMenu]; - - /* Finally give up our references to the objects */ - [windowMenu release]; - [windowMenuItem release]; -} - -/* Replacement for NSApplicationMain */ -static void CustomApplicationMain (int argc, char **argv) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - SDLMain *sdlMain; - - /* Ensure the application object is initialised */ - [SDLApplication sharedApplication]; - -#ifdef SDL_USE_CPS - { - CPSProcessSerNum PSN; - /* Tell the dock about us */ - if (!CPSGetCurrentProcess(&PSN)) - if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) - if (!CPSSetFrontProcess(&PSN)) - [SDLApplication sharedApplication]; - } -#endif /* SDL_USE_CPS */ - - /* Set up the menubar */ - [NSApp setMainMenu:[[NSMenu alloc] init]]; - setApplicationMenu(); - setupWindowMenu(); - - /* Create SDLMain and make it the app delegate */ - sdlMain = [[SDLMain alloc] init]; - [NSApp setDelegate:sdlMain]; - - /* Start the main event loop */ - [NSApp run]; - - [sdlMain release]; - [pool release]; -} - -#endif - - -/* - * Catch document open requests...this lets us notice files when the app - * was launched by double-clicking a document, or when a document was - * dragged/dropped on the app's icon. You need to have a - * CFBundleDocumentsType section in your Info.plist to get this message, - * apparently. - * - * Files are added to gArgv, so to the app, they'll look like command line - * arguments. Previously, apps launched from the finder had nothing but - * an argv[0]. - * - * This message may be received multiple times to open several docs on launch. - * - * This message is ignored once the app's mainline has been called. - */ -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename -{ - const char *temparg; - size_t arglen; - char *arg; - char **newargv; - - if (!gFinderLaunch) /* MacOS is passing command line args. */ - return FALSE; - - if (gCalledAppMainline) /* app has started, ignore this document. */ - return FALSE; - - temparg = [filename UTF8String]; - arglen = SDL_strlen(temparg) + 1; - arg = (char *) SDL_malloc(arglen); - if (arg == NULL) - return FALSE; - - newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); - if (newargv == NULL) - { - SDL_free(arg); - return FALSE; - } - gArgv = newargv; - - SDL_strlcpy(arg, temparg, arglen); - gArgv[gArgc++] = arg; - gArgv[gArgc] = NULL; - return TRUE; -} - - -/* Called when the internal event loop has just started running */ -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - int status; - - /* Set the working directory to the .app's parent directory */ - [self setupWorkingDirectory:gFinderLaunch]; - -#if SDL_USE_NIB_FILE - /* Set the main menu to contain the real app name instead of "SDL App" */ - [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; -#endif - - /* Hand off to main application code */ - gCalledAppMainline = TRUE; - status = SDL_main (gArgc, gArgv); - - /* We're done, thank you for playing */ - exit(status); -} -@end - - -@implementation NSString (ReplaceSubString) - -- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString -{ - unsigned int bufferSize; - unsigned int selfLen = [self length]; - unsigned int aStringLen = [aString length]; - unichar *buffer; - NSRange localRange; - NSString *result; - - bufferSize = selfLen + aStringLen - aRange.length; - buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); - - /* Get first part into buffer */ - localRange.location = 0; - localRange.length = aRange.location; - [self getCharacters:buffer range:localRange]; - - /* Get middle part into buffer */ - localRange.location = 0; - localRange.length = aStringLen; - [aString getCharacters:(buffer+aRange.location) range:localRange]; - - /* Get last part into buffer */ - localRange.location = aRange.location + aRange.length; - localRange.length = selfLen - localRange.location; - [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; - - /* Build output string */ - result = [NSString stringWithCharacters:buffer length:bufferSize]; - - NSDeallocateMemoryPages(buffer, bufferSize); - - return result; -} - -@end - - - -#ifdef main -# undef main -#endif - - -/* Main entry point to executable - should *not* be SDL_main! */ -int main (int argc, char **argv) -{ - /* Copy the arguments into a global variable */ - /* This is passed if we are launched by double-clicking */ - if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { - gArgv = (char **) SDL_malloc(sizeof (char *) * 2); - gArgv[0] = argv[0]; - gArgv[1] = NULL; - gArgc = 1; - gFinderLaunch = YES; - } else { - int i; - gArgc = argc; - gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); - for (i = 0; i <= argc; i++) - gArgv[i] = argv[i]; - gFinderLaunch = NO; - } - -#if SDL_USE_NIB_FILE - [SDLApplication poseAsClass:[NSApplication class]]; - NSApplicationMain (argc, argv); -#else - CustomApplicationMain (argc, argv); -#endif - return 0; -} - diff --git a/src/sdl/i_input.cpp b/src/sdl/i_input.cpp deleted file mode 100644 index bf676db70..000000000 --- a/src/sdl/i_input.cpp +++ /dev/null @@ -1,505 +0,0 @@ -#include -#include -#include "doomtype.h" -#include "c_dispatch.h" -#include "doomdef.h" -#include "doomstat.h" -#include "m_argv.h" -#include "i_input.h" -#include "v_video.h" - -#include "d_main.h" -#include "d_event.h" -#include "d_gui.h" -#include "c_console.h" -#include "c_cvars.h" -#include "i_system.h" -#include "dikeys.h" -#include "templates.h" -#include "s_sound.h" - -static void I_CheckGUICapture (); -static void I_CheckNativeMouse (); - -bool GUICapture; -static bool NativeMouse = true; - -extern int paused; - -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) -CVAR (Bool, sdl_nokeyrepeat, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -EXTERN_CVAR (Bool, fullscreen) - -extern int WaitingForKey, chatmodeon; -extern constate_e ConsoleState; - -extern SDL_Surface *cursorSurface; -extern SDL_Rect cursorBlit; - -static BYTE KeySymToDIK[SDLK_LAST], DownState[SDLK_LAST]; - -static WORD DIKToKeySym[256] = -{ - 0, SDLK_ESCAPE, '1', '2', '3', '4', '5', '6', - '7', '8', '9', '0', '-', '=', SDLK_BACKSPACE, SDLK_TAB, - 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', - 'o', 'p', '[', ']', SDLK_RETURN, SDLK_LCTRL, 'a', 's', - 'd', 'f', 'g', 'h', 'j', 'k', 'l', SDLK_SEMICOLON, - '\'', '`', SDLK_LSHIFT, '\\', 'z', 'x', 'c', 'v', - 'b', 'n', 'm', ',', '.', '/', SDLK_RSHIFT, SDLK_KP_MULTIPLY, - SDLK_LALT, ' ', SDLK_CAPSLOCK, SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, - SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_NUMLOCK, SDLK_SCROLLOCK, SDLK_KP7, - SDLK_KP8, SDLK_KP9, SDLK_KP_MINUS, SDLK_KP4, SDLK_KP5, SDLK_KP6, SDLK_KP_PLUS, SDLK_KP1, - SDLK_KP2, SDLK_KP3, SDLK_KP0, SDLK_KP_PERIOD, 0, 0, 0, SDLK_F11, - SDLK_F12, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, SDLK_F13, SDLK_F14, SDLK_F15, 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, - 0, 0, 0, 0, 0, SDLK_KP_EQUALS, 0, 0, - 0, SDLK_AT, SDLK_COLON, 0, 0, 0, 0, 0, - 0, 0, 0, 0, SDLK_KP_ENTER, SDLK_RCTRL, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, SDLK_KP_DIVIDE, 0, SDLK_SYSREQ, - SDLK_RALT, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, SDLK_PAUSE, 0, SDLK_HOME, - SDLK_UP, SDLK_PAGEUP, 0, SDLK_LEFT, 0, SDLK_RIGHT, 0, SDLK_END, - SDLK_DOWN, SDLK_PAGEDOWN, SDLK_INSERT, SDLK_DELETE, 0, 0, 0, 0, - 0, 0, 0, SDLK_LSUPER, SDLK_RSUPER, SDLK_MENU, SDLK_POWER, 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 -}; - -static void FlushDIKState (int low=0, int high=NUM_KEYS-1) -{ -} - -static void InitKeySymMap () -{ - for (int i = 0; i < 256; ++i) - { - KeySymToDIK[DIKToKeySym[i]] = i; - } - KeySymToDIK[0] = 0; - KeySymToDIK[SDLK_RSHIFT] = DIK_LSHIFT; - KeySymToDIK[SDLK_RCTRL] = DIK_LCONTROL; - KeySymToDIK[SDLK_RALT] = DIK_LMENU; - // Depending on your Linux flavor, you may get SDLK_PRINT or SDLK_SYSREQ - KeySymToDIK[SDLK_PRINT] = DIK_SYSRQ; -} - -static void I_CheckGUICapture () -{ - bool wantCapt; - bool repeat; - int oldrepeat, interval; - - SDL_GetKeyRepeat(&oldrepeat, &interval); - - if (menuactive == MENU_Off) - { - wantCapt = ConsoleState == c_down || ConsoleState == c_falling || chatmodeon; - } - else - { - wantCapt = (menuactive == MENU_On || menuactive == MENU_OnNoPause); - } - - if (wantCapt != GUICapture) - { - GUICapture = wantCapt; - if (wantCapt) - { - int x, y; - SDL_GetMouseState (&x, &y); - cursorBlit.x = x; - cursorBlit.y = y; - - FlushDIKState (); - memset (DownState, 0, sizeof(DownState)); - repeat = !sdl_nokeyrepeat; - SDL_EnableUNICODE (1); - } - else - { - repeat = false; - SDL_EnableUNICODE (0); - } - } - if (wantCapt) - { - repeat = !sdl_nokeyrepeat; - } - else - { - repeat = false; - } - if (repeat != (oldrepeat != 0)) - { - if (repeat) - { - SDL_EnableKeyRepeat (SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); - } - else - { - SDL_EnableKeyRepeat (0, 0); - } - } -} - -void I_SetMouseCapture() -{ -} - -void I_ReleaseMouseCapture() -{ -} - -static void CenterMouse () -{ - SDL_WarpMouse (screen->GetWidth()/2, screen->GetHeight()/2); - SDL_PumpEvents (); - SDL_GetRelativeMouseState (NULL, NULL); -} - -static void PostMouseMove (int x, int y) -{ - static int lastx = 0, lasty = 0; - event_t ev = { 0,0,0,0,0,0,0 }; - - if (m_filter) - { - ev.x = (x + lastx) / 2; - ev.y = (y + lasty) / 2; - } - else - { - ev.x = x; - ev.y = y; - } - lastx = x; - lasty = y; - if (ev.x | ev.y) - { - ev.type = EV_Mouse; - D_PostEvent (&ev); - } -} - -static void MouseRead () -{ - int x, y; - - if (NativeMouse) - { - return; - } - - SDL_GetRelativeMouseState (&x, &y); - if (!m_noprescale) - { - x *= 3; - y *= 2; - } - if (x | y) - { - CenterMouse (); - PostMouseMove (x, -y); - } -} - -static void WheelMoved(event_t *event) -{ - if (GUICapture) - { - if (event->type != EV_KeyUp) - { - SDLMod mod = SDL_GetModState(); - event->type = EV_GUI_Event; - event->subtype = event->data1 == KEY_MWHEELUP ? EV_GUI_WheelUp : EV_GUI_WheelDown; - event->data1 = 0; - event->data3 = ((mod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((mod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((mod & KMOD_ALT) ? GKM_ALT : 0); - D_PostEvent(event); - } - } - else - { - D_PostEvent(event); - } -} - -CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) -{ - if (self < 0) self = 0; - else if (self > 2) self = 2; -} - -static bool inGame() -{ - 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; - } -} - -static void I_CheckNativeMouse () -{ - bool focus = (SDL_GetAppState() & (SDL_APPINPUTFOCUS|SDL_APPACTIVE)) - == (SDL_APPINPUTFOCUS|SDL_APPACTIVE); - bool fs = (SDL_GetVideoSurface ()->flags & SDL_FULLSCREEN) != 0; - - bool wantNative = !focus || (!use_mouse || GUICapture || paused || demoplayback || !inGame()); - - if (wantNative != NativeMouse) - { - NativeMouse = wantNative; - SDL_ShowCursor (wantNative ? cursorSurface == NULL : 0); - if (wantNative) - { - SDL_WM_GrabInput (SDL_GRAB_OFF); - FlushDIKState (KEY_MOUSE1, KEY_MOUSE8); - } - else - { - SDL_WM_GrabInput (SDL_GRAB_ON); - CenterMouse (); - } - } -} - -void MessagePump (const SDL_Event &sev) -{ - static int lastx = 0, lasty = 0; - int x, y; - event_t event = { 0,0,0,0,0,0,0 }; - - switch (sev.type) - { - case SDL_QUIT: - exit (0); - - case SDL_ACTIVEEVENT: - if (sev.active.state == SDL_APPINPUTFOCUS) - { - if (sev.active.gain == 0) - { // kill focus - FlushDIKState (); - } - S_SetSoundPaused(sev.active.gain); - } - break; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEMOTION: - if (!GUICapture || sev.button.button == 4 || sev.button.button == 5) - { - if(sev.type != SDL_MOUSEMOTION) - { - event.type = sev.type == SDL_MOUSEBUTTONDOWN ? EV_KeyDown : EV_KeyUp; - /* These button mappings work with my Gentoo system using the - * evdev driver and a Logitech MX510 mouse. Whether or not they - * carry over to other Linux systems, I have no idea, but I sure - * hope so. (Though buttons 11 and 12 are kind of useless, since - * they also trigger buttons 4 and 5.) - */ - switch (sev.button.button) - { - case 1: event.data1 = KEY_MOUSE1; break; - case 2: event.data1 = KEY_MOUSE3; break; - case 3: event.data1 = KEY_MOUSE2; break; - case 4: event.data1 = KEY_MWHEELUP; break; - case 5: event.data1 = KEY_MWHEELDOWN; break; - case 6: event.data1 = KEY_MOUSE4; break; /* dunno; not generated by my mouse */ - case 7: event.data1 = KEY_MOUSE5; break; /* ditto */ - case 8: event.data1 = KEY_MOUSE4; break; - case 9: event.data1 = KEY_MOUSE5; break; - case 10: event.data1 = KEY_MOUSE6; break; - case 11: event.data1 = KEY_MOUSE7; break; - case 12: event.data1 = KEY_MOUSE8; break; - default: printf("SDL mouse button %s %d\n", - sev.type == SDL_MOUSEBUTTONDOWN ? "down" : "up", sev.button.button); break; - } - if (event.data1 != 0) - { - //DIKState[ActiveDIKState][event.data1] = (event.type == EV_KeyDown); - if (event.data1 == KEY_MWHEELUP || event.data1 == KEY_MWHEELDOWN) - { - WheelMoved(&event); - } - else - { - D_PostEvent(&event); - } - } - } - } - else if (sev.type == SDL_MOUSEMOTION || (sev.button.button >= 1 && sev.button.button <= 3)) - { - int x, y; - SDL_GetMouseState (&x, &y); - - cursorBlit.x = event.data1 = x; - cursorBlit.y = event.data2 = y; - event.type = EV_GUI_Event; - if(sev.type == SDL_MOUSEMOTION) - event.subtype = EV_GUI_MouseMove; - else - { - event.subtype = sev.type == SDL_MOUSEBUTTONDOWN ? EV_GUI_LButtonDown : EV_GUI_LButtonUp; - event.subtype += (sev.button.button - 1) * 3; - } - D_PostEvent(&event); - } - break; - - case SDL_KEYDOWN: - case SDL_KEYUP: - if (sev.key.keysym.sym >= SDLK_LAST) - break; - - if (!GUICapture) - { - event.type = sev.type == SDL_KEYDOWN ? EV_KeyDown : EV_KeyUp; - event.data1 = KeySymToDIK[sev.key.keysym.sym]; - if (event.data1) - { - if (sev.key.keysym.sym < 256) - { - event.data2 = sev.key.keysym.sym; - } - D_PostEvent (&event); - } - } - else - { - event.type = EV_GUI_Event; - event.subtype = sev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp; - event.data3 = ((sev.key.keysym.mod & KMOD_SHIFT) ? GKM_SHIFT : 0) | - ((sev.key.keysym.mod & KMOD_CTRL) ? GKM_CTRL : 0) | - ((sev.key.keysym.mod & KMOD_ALT) ? GKM_ALT : 0); - - if (sev.key.keysym.sym < SDLK_LAST) - { - if (event.subtype == EV_GUI_KeyDown) - { - if (DownState[sev.key.keysym.sym]) - { - event.subtype = EV_GUI_KeyRepeat; - } - DownState[sev.key.keysym.sym] = 1; - } - else - { - DownState[sev.key.keysym.sym] = 0; - } - } - - switch (sev.key.keysym.sym) - { - case SDLK_KP_ENTER: event.data1 = GK_RETURN; break; - case SDLK_PAGEUP: event.data1 = GK_PGUP; break; - case SDLK_PAGEDOWN: event.data1 = GK_PGDN; break; - case SDLK_END: event.data1 = GK_END; break; - case SDLK_HOME: event.data1 = GK_HOME; break; - case SDLK_LEFT: event.data1 = GK_LEFT; break; - case SDLK_RIGHT: event.data1 = GK_RIGHT; break; - case SDLK_UP: event.data1 = GK_UP; break; - case SDLK_DOWN: event.data1 = GK_DOWN; break; - case SDLK_DELETE: event.data1 = GK_DEL; break; - case SDLK_ESCAPE: event.data1 = GK_ESCAPE; break; - case SDLK_F1: event.data1 = GK_F1; break; - case SDLK_F2: event.data1 = GK_F2; break; - case SDLK_F3: event.data1 = GK_F3; break; - case SDLK_F4: event.data1 = GK_F4; break; - case SDLK_F5: event.data1 = GK_F5; break; - case SDLK_F6: event.data1 = GK_F6; break; - case SDLK_F7: event.data1 = GK_F7; break; - case SDLK_F8: event.data1 = GK_F8; break; - case SDLK_F9: event.data1 = GK_F9; break; - case SDLK_F10: event.data1 = GK_F10; break; - case SDLK_F11: event.data1 = GK_F11; break; - case SDLK_F12: event.data1 = GK_F12; break; - default: - if (sev.key.keysym.sym < 256) - { - event.data1 = sev.key.keysym.sym; - } - break; - } - event.data2 = sev.key.keysym.unicode & 0xff; - if (event.data1 < 128) - { - event.data1 = toupper(event.data1); - D_PostEvent (&event); - } - if (!iscntrl(event.data2) && event.subtype != EV_GUI_KeyUp) - { - event.subtype = EV_GUI_Char; - event.data1 = event.data2; - event.data2 = sev.key.keysym.mod & KMOD_ALT; - event.data3 = 0; - D_PostEvent (&event); - } - } - break; - - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: - if (!GUICapture) - { - event.type = sev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp; - event.data1 = KEY_FIRSTJOYBUTTON + sev.jbutton.button; - if(event.data1 != 0) - D_PostEvent(&event); - } - break; - } -} - -void I_GetEvent () -{ - SDL_Event sev; - - while (SDL_PollEvent (&sev)) - { - MessagePump (sev); - } - if (use_mouse) - { - MouseRead (); - } -} - -void I_StartTic () -{ - I_CheckGUICapture (); - I_CheckNativeMouse (); - I_GetEvent (); -} - -void I_ProcessJoysticks (); -void I_StartFrame () -{ - if (KeySymToDIK[SDLK_BACKSPACE] == 0) - { - InitKeySymMap (); - } - - I_ProcessJoysticks(); -} diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 36ec7598a..770869f7a 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -37,7 +37,6 @@ #include #include #else -#include #include #include #include diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 698a8b5a8..2f30b95ab 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -13,7 +13,6 @@ #include #include #else -#include #define FALSE 0 #define TRUE 1 #endif diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index 3c780d459..21e72958e 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 1c8b23936..b973cca40 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; 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/tempfiles.h b/src/tempfiles.h index beeb2bc1d..406c54153 100644 --- a/src/tempfiles.h +++ b/src/tempfiles.h @@ -38,6 +38,8 @@ #pragma once #endif +#include + // Returns a file name suitable for use as a temp file. // If you create a file with this name (and presumably you // will), it will be deleted automatically by this class's diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index c388a8544..5225b787a 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -266,11 +266,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 diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index f80e89aad..300ee7556 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -1,4996 +1,5712 @@ -/* -** 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) -{ - StateCallData StateCall; - bool result = false; - int counter = 0; - - while (State != NULL) - { - // Assume success. The code pointer will set this to false if necessary - StateCall.State = State; - StateCall.Result = true; - if (State->CallAction(actor, this, &StateCall)) - { - // collect all the results. Even one successful call signifies overall success. - result |= StateCall.Result; - } - - - // Since there are no delays it is a good idea to check for infinite loops here! - counter++; - if (counter >= 10000) break; - - if (StateCall.State == State) - { - // Abort immediately if the state jumps to itself! - if (State == State->GetNextState()) - { - return false; - } - - // If both variables are still the same there was no jump - // so we must advance to the next state. - State = State->GetNextState(); - } - else - { - State = StateCall.State; - } - } - 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) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(ptr_target, 0); - ACTION_PARAM_INT(ptr_master, 1); - ACTION_PARAM_INT(ptr_tracer, 2); - ACTION_PARAM_INT(flags, 3); - - // 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; - } -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ptr_source, 0); - ACTION_PARAM_INT(ptr_recepient, 1); - ACTION_PARAM_INT(ptr_sourcefield, 2); - ACTION_PARAM_INT(ptr_recepientfield, 3); - ACTION_PARAM_INT(flags, 4); - - AActor *source, *recepient; - - // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) - - source = COPY_AAPTR(self, ptr_source); - COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value - - // convert source from dataprovider to data - - source = COPY_AAPTR(source, ptr_sourcefield); - - if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL - - if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from - - ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(ptr_source, 0); - - if (self->player) return; - - AActor *source; - COPY_AAPTR_NOT_NULL(self, source, ptr_source); - self->CopyFriendliness(source, false, false); // No change in current target or health -} - -//========================================================================== -// -// Simple flag changers -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) -{ - self->flags |= MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) -{ - self->flags &= ~MF_SOLID; -} - -DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) -{ - self->flags |= MF_FLOAT; -} - -DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) -{ - self->flags &= ~(MF_FLOAT|MF_INFLOAT); -} - -//========================================================================== -// -// Customizable attack functions which use actor parameters. -// -//========================================================================== -static void DoAttack (AActor *self, bool domelee, bool domissile, - int MeleeDamage, FSoundID MeleeSound, const PClass *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) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); -} - -DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) -{ - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) -{ - int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); - FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); - const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); - fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(MeleeDamage, 0); - ACTION_PARAM_SOUND(MeleeSound, 1); - ACTION_PARAM_CLASS(MissileType, 2); - ACTION_PARAM_FIXED(MissileHeight, 3); - - if (MissileType == NULL) return; - DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); -} - -//========================================================================== -// -// Custom sound functions. -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_INT(channel, 1); - ACTION_PARAM_FLOAT(volume, 2); - ACTION_PARAM_BOOL(looping, 3); - ACTION_PARAM_FLOAT(attenuation, 4); - - if (!looping) - { - S_Sound (self, channel, soundid, volume, attenuation); - } - else - { - if (!S_IsActorPlayingSomething (self, channel&7, soundid)) - { - S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(slot, 0); - - S_StopSound(self, slot); -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_SOUND(soundid, 0); - - S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_SOUND(soundid, 0); - ACTION_PARAM_NAME(channel, 1); - ACTION_PARAM_BOOL(looping, 2); - ACTION_PARAM_INT(attenuation_raw, 3); - - 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); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(channel, 0); - - if (channel > NAME_Auto && channel <= NAME_SoundSlot7) - { - S_StopSound (self, int(channel) - NAME_Auto); - } -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(ang1, 0); - ACTION_PARAM_INT(ang2, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(chance, 3); - ACTION_PARAM_INT(distance, 4); - - 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; - } - } -} - -//========================================================================== -// -// Hitscan attack with a customizable amount of bullets (specified in damage) -// -//========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) -{ - int i; - int bangle; - int slope; - - if (!self->target) return; - - 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); - } -} - - -//========================================================================== -// -// Do the state jump -// -//========================================================================== -static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) -{ - if (jumpto == NULL) return; - - if (statecall != NULL) - { - statecall->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) - { - self->SetState (jumpto); - } - 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, CallingState, offset, statecall) - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_INT(maxchance, 1); - - if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) - { - int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); - ACTION_PARAM_STATE(jumpto, jumps); - ACTION_JUMP(jumpto); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(health, 0); - ACTION_PARAM_STATE(jump, 1); - - if (self->health < health) ACTION_JUMP(jump); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (!self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - if (self->CheckMeleeRange()) - { - ACTION_JUMP(jump); - } - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! -} -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(dist, 0); - ACTION_PARAM_STATE(jump, 1); - - 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); - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) -{ - AActor *target; - - if (!self->player) - { - target = self->target; - } - else - { - // Does the player aim at something that can be shot? - P_BulletSlope(self, &target); - } - DoJumpIfCloser(target, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) -{ - DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) -{ - DoJumpIfCloser(self->master, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(Type, 0); - ACTION_PARAM_INT(ItemAmount, 1); - ACTION_PARAM_STATE(JumpOffset, 2); - ACTION_PARAM_INT(setowner, 3); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - if (!Type) return; - COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL - - AInventory *Item = owner->FindInventory(Type); - - if (Item) - { - if (ItemAmount > 0) - { - if (Item->Amount >= ItemAmount) - ACTION_JUMP(JumpOffset); - } - else if (Item->Amount >= Item->MaxAmount) - { - ACTION_JUMP(JumpOffset); - } - } -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) -{ - DoJumpIfInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) -{ - DoJumpIfInventory(self->target, PUSH_PARAMINFO); -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(Type, 0); - ACTION_PARAM_STATE(JumpOffset, 1); - ACTION_PARAM_INT(amount, 2); - - 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(JumpOffset); -} - -//========================================================================== -// -// Parameterized version of A_Explode -// -//========================================================================== - -enum -{ - XF_HURTSOURCE = 1, - XF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) -{ - ACTION_PARAM_START(8); - ACTION_PARAM_INT(damage, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_BOOL(alert, 3); - ACTION_PARAM_INT(fulldmgdistance, 4); - ACTION_PARAM_INT(nails, 5); - ACTION_PARAM_INT(naildamage, 6); - ACTION_PARAM_CLASS(pufftype, 7); - - if (damage < 0) // get parameters from metadata - { - damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); - distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); - flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); - alert = false; - } - else - { - 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); - } -} - -//========================================================================== -// -// A_RadiusThrust -// -//========================================================================== - -enum -{ - RTF_AFFECTSOURCE = 1, - RTF_NOIMPACTDAMAGE = 2, - RTF_NOTMISSILE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_INT(force, 0); - ACTION_PARAM_INT(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(fullthrustdistance, 3); - - 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; - } -} - -//========================================================================== -// -// Execute a line special / script -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - ACTION_PARAM_INT(arg5, 5); - - bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); - - ACTION_SET_RESULT(res); -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(Spawnofs_XY, 2); - ACTION_PARAM_ANGLE(Angle, 3); - ACTION_PARAM_INT(flags, 4); - ACTION_PARAM_ANGLE(pitch, 5); - - 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) - { - // 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); - } -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(pufftype, 4); - ACTION_PARAM_FIXED(Range, 5); - ACTION_PARAM_INT(Flags, 6); - - 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 (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); - - if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); - - S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); - for (i=0 ; itarget) - return; - - 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); - } -} - -//========================================================================== -// -// A fully customizable combo attack -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_CLASS(ti, 0); - ACTION_PARAM_FIXED(SpawnHeight, 1); - ACTION_PARAM_INT(damage, 2); - ACTION_PARAM_SOUND(MeleeSound, 3); - ACTION_PARAM_NAME(DamageType, 4); - ACTION_PARAM_BOOL(bleed, 5); - - if (!self->target) - return; - - 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); - } - } -} - -//========================================================================== -// -// State jump function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (!ACTION_CALL_FROM_WEAPON()) return; - - if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) - { - ACTION_JUMP(jump); - } - -} - - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_ANGLE(Spread_XY, 0); - ACTION_PARAM_ANGLE(Spread_Z, 1); - ACTION_PARAM_INT(NumberOfBullets, 2); - ACTION_PARAM_INT(DamagePerBullet, 3); - ACTION_PARAM_CLASS(PuffType, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FIXED(Range, 6); - - if (!self->player) return; - - 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; // 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) PuffType = PClass::FindClass(NAME_BulletPuff); - - if (weapon != NULL) - { - S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); - } - - if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==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 (NumberOfBullets == -1) NumberOfBullets = 1; - for (i=0 ; iplayer) return; - - - player_t *player=self->player; - AWeapon * weapon=player->ReadyWeapon; - AActor *linetarget; - - if (UseAmmo && weapon) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // 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 SavedPlayerPitch = self->pitch; - self->pitch -= pitch; - AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); - self->pitch = SavedPlayerPitch; - - // 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]); - } - } - } -} - - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_BOOL(norandom, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_CLASS(PuffType, 3); - ACTION_PARAM_FIXED(Range, 4); - ACTION_PARAM_FIXED(LifeSteal, 5); - - if (!self->player) return; - - 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; // out of ammo - } - - if (!PuffType) PuffType = PClass::FindClass(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); - } -} - - -//========================================================================== -// -// customizable railgun attack function -// -//========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_BOOL(UseAmmo, 2); - ACTION_PARAM_COLOR(Color1, 3); - ACTION_PARAM_COLOR(Color2, 4); - ACTION_PARAM_INT(Flags, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - if (!self->player) return; - - AWeapon * weapon=self->player->ReadyWeapon; - - // only use ammo when actually hitting something! - if (UseAmmo) - { - if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // 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); -} - -//========================================================================== -// -// also for monsters -// -//========================================================================== -enum -{ - CRF_DONTAIM = 0, - CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2, - CRF_EXPLICITANGLE = 4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) -{ - ACTION_PARAM_START(16); - ACTION_PARAM_INT(Damage, 0); - ACTION_PARAM_INT(Spawnofs_XY, 1); - ACTION_PARAM_COLOR(Color1, 2); - ACTION_PARAM_COLOR(Color2, 3); - ACTION_PARAM_INT(Flags, 4); - ACTION_PARAM_INT(aim, 5); - ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); - ACTION_PARAM_ANGLE(Spread_XY, 8); - ACTION_PARAM_ANGLE(Spread_Z, 9); - ACTION_PARAM_FIXED(Range, 10); - ACTION_PARAM_INT(Duration, 11); - ACTION_PARAM_FLOAT(Sparsity, 12); - ACTION_PARAM_FLOAT(DriftSpeed, 13); - ACTION_PARAM_CLASS(SpawnClass, 14); - ACTION_PARAM_FIXED(Spawnofs_Z, 15); - - if(Range==0) Range=8192*FRACUNIT; - if(Sparsity==0) Sparsity=1.0; - - 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; - } - // [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; -} - -//=========================================================================== -// -// DoGiveInventory -// -//=========================================================================== - -static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_CLASS(mi, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(setreceiver, 2); - - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - - 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); - -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) -{ - DoGiveInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) -{ - DoGiveInventory(self->target, PUSH_PARAMINFO); -} - -//=========================================================================== -// -// A_TakeInventory -// -//=========================================================================== - -enum -{ - TIF_NOTAKEINFINITE = 1, -}; - -void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(setreceiver, 3); - - if (!item) return; - COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); - - bool res = false; - - AInventory * inv = receiver->FindInventory(item); - - 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); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) -{ - DoTakeInventory(self, PUSH_PARAMINFO); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) -{ - DoTakeInventory(self->target, PUSH_PARAMINFO); -} - -//=========================================================================== -// -// 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, -}; - -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_SETMASTER) - { - mo->master = 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_FIXED(zheight, 2); - ACTION_PARAM_BOOL(useammo, 3); - ACTION_PARAM_BOOL(transfer_translation, 4); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - 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) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - 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 -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) -{ - ACTION_PARAM_START(11); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_FIXED(xvel, 4); - ACTION_PARAM_FIXED(yvel, 5); - ACTION_PARAM_FIXED(zvel, 6); - ACTION_PARAM_ANGLE(Angle, 7); - ACTION_PARAM_INT(flags, 8); - ACTION_PARAM_INT(chance, 9); - ACTION_PARAM_INT(tid, 10); - - if (!missile) - { - ACTION_SET_RESULT(false); - return; - } - - if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; - - 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; - } -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_CLASS(missile, 0); - ACTION_PARAM_FIXED(zheight, 1); - ACTION_PARAM_FIXED(xyvel, 2); - ACTION_PARAM_FIXED(zvel, 3); - ACTION_PARAM_BOOL(useammo, 4); - - if (missile == NULL) return; - - if (ACTION_CALL_FROM_WEAPON()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (!weapon) return; - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; - } - - - 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); -} - - -//=========================================================================== -// -// A_Recoil -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(xyvel, 0); - - angle_t angle = self->angle + ANG180; - angle >>= ANGLETOFINESHIFT; - self->velx += FixedMul (xyvel, finecosine[angle]); - self->vely += FixedMul (xyvel, finesine[angle]); -} - - -//=========================================================================== -// -// A_SelectWeapon -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(cls, 0); - - if (cls == NULL || self->player == NULL) - { - ACTION_SET_RESULT(false); - return; - } - - AWeapon * weaponitem = static_cast(self->FindInventory(cls)); - - if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) - { - if (self->player->ReadyWeapon != weaponitem) - { - self->player->PendingWeapon = weaponitem; - } - } - else ACTION_SET_RESULT(false); - -} - - -//=========================================================================== -// -// A_Print -// -//=========================================================================== -EXTERN_CVAR(Float, con_midtime) - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - 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 = 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! -} - -//=========================================================================== -// -// A_PrintBold -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(text, 0); - ACTION_PARAM_FLOAT(time, 1); - ACTION_PARAM_NAME(fontname, 2); - - 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 = 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! -} - -//=========================================================================== -// -// A_Log -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STRING(text, 0); - - 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! -} - -//========================================================================= -// -// A_LogInt -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(num, 0); - Printf("%d\n", num); - ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! -} - -//=========================================================================== -// -// A_SetTranslucent -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(alpha, 0); - ACTION_PARAM_INT(mode, 1); - - 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); -} - -//=========================================================================== -// -// A_FadeIn -// -// Fades the actor in -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(reduce, 0); - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha += reduce; - // Should this clamp alpha to 1.0? -} - -//=========================================================================== -// -// A_FadeOut -// -// fades the actor out and destroys it when done -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(reduce, 0); - ACTION_PARAM_BOOL(remove, 1); - - if (reduce == 0) - { - reduce = FRACUNIT/10; - } - self->RenderStyle.Flags &= ~STYLEF_Alpha1; - self->alpha -= reduce; - if (self->alpha <= 0 && remove) - { - self->Destroy(); - } -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_FIXED(target, 0); - ACTION_PARAM_FIXED(amount, 1); - ACTION_PARAM_BOOL(remove, 2); - - 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(); - } -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_FIXED(scalex, 0); - ACTION_PARAM_FIXED(scaley, 1); - - self->scaleX = scalex; - self->scaleY = scaley ? scaley : scalex; -} - -//=========================================================================== -// -// A_SetMass(int mass) -// -// Sets the actor's mass. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(mass, 0); - - self->Mass = mass; -} - -//=========================================================================== -// -// A_SpawnDebris -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) -{ - int i; - AActor * mo; - - ACTION_PARAM_START(4); - ACTION_PARAM_CLASS(debris, 0); - ACTION_PARAM_BOOL(transfer_translation, 1); - ACTION_PARAM_FIXED(mult_h, 2); - ACTION_PARAM_FIXED(mult_v, 3); - - if (debris == NULL) return; - - // 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()->ActorInfo->NumOwnedStates) - { - mo->SetState(mo->GetClass()->ActorInfo->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)); - } - } -} - - -//=========================================================================== -// -// A_CheckSight -// jumps if no player can see this actor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - 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; - } - // 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; - } - } - } - - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(2); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - - 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; - } - // 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; - } - } - } - ACTION_JUMP(jump); -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(2); - double range = EvalExpressionF(ParameterIndex+0, self); - ACTION_PARAM_STATE(jump, 1); - - 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; - } - // 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; - } - } - } - ACTION_JUMP(jump); -} - - -//=========================================================================== -// -// Inventory drop -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(drop, 0); - - if (drop) - { - AInventory * inv = self->FindInventory(drop); - if (inv) - { - self->DropInventory(inv); - } - } -} - - -//=========================================================================== -// -// A_SetBlend -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_COLOR(color, 0); - ACTION_PARAM_FLOAT(alpha, 1); - ACTION_PARAM_INT(tics, 2); - ACTION_PARAM_COLOR(color2, 3); - - if (color == MAKEARGB(255,255,255,255)) color=0; - if (color2 == MAKEARGB(255,255,255,255)) color2=0; - if (!color2.a) - color2 = color; - - new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, - color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, - (float)tics/TICRATE, self); -} - - -//=========================================================================== -// -// A_JumpIf -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_BOOL(expression, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (expression) ACTION_JUMP(jump); - -} - -//=========================================================================== -// -// A_KillMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - if (self->master != NULL) - { - P_DamageMobj(self->master, self, self, self->master->health, damagetype, DMG_NO_ARMOR | DMG_NO_FACTOR); - } -} - -//=========================================================================== -// -// A_KillChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - 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); - } - } -} - -//=========================================================================== -// -// A_KillSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - 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); - } - } - } -} - -//=========================================================================== -// -// A_CountdownArg -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(cnt, 0); - ACTION_PARAM_STATE(state, 1); - - if (cnt<0 || cnt>=5) return; - if (!self->args[cnt]--) - { - 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 - { - // can't use "Death" as default parameter with current DECORATE parser. - if (state == NULL) state = self->FindState(NAME_Death); - self->SetState(state); - } - } - -} - -//============================================================================ -// -// A_Burst -// -//============================================================================ - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_CLASS(chunk, 0); - - int i, numChunks; - AActor * mo; - - if (chunk == NULL) return; - - 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 (); -} - -//=========================================================================== -// -// A_CheckFloor -// [GRB] Jumps if actor is standing on floor -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->z <= self->floorz) - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_CheckCeiling -// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling -// - -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - ACTION_SET_RESULT(false); - if (self->z+self->height >= self->ceilingz) // Height needs to be counted - { - ACTION_JUMP(jump); - } - -} - -//=========================================================================== -// -// A_Stop -// resets all velocity of the actor to 0 -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_Stop) -{ - 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; - } -} - -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 -// -//=========================================================================== - -extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); - -enum RS_Flags -{ - RSF_FOG=1, - RSF_KEEPTARGET=2, - RSF_TELEFRAG=4, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(flags, 0); - - 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; - } -} - - -//========================================================================== -// -// A_PlayerSkinCheck -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_STATE(jump, 0); - - 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); - } -} - -//=========================================================================== -// -// A_SetGravity -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(val, 0); - - self->gravity = clamp (val, 0, FRACUNIT*10); -} - - -// [KS] *** Start of my modifications *** - -//=========================================================================== -// -// A_ClearTarget -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) -{ - self->target = NULL; - self->LastHeard = NULL; - self->lastenemy = NULL; -} - -//========================================================================== -// -// 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; - - ACTION_PARAM_START(9); - - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_INT(flags, 1); - ACTION_PARAM_FIXED(range, 2); - ACTION_PARAM_FIXED(minrange, 3); - { - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_ANGLE(pitch, 5); - ACTION_PARAM_FIXED(offsetheight, 6); - ACTION_PARAM_FIXED(offsetwidth, 7); - ACTION_PARAM_INT(ptr_target, 8); - - 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; - } - - { - 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; - - 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; - } - ACTION_JUMP(jump); - } -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - 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) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - 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; - - 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; - - // [FDARI] If actors share team, don't jump - if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; - - 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; - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - 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; - - 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; // [KS] Outside of FOV - return - } - - } - - ACTION_JUMP(jump); -} - - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_STATE(jump, 0); - ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_FIXED(dist_max, 3); - ACTION_PARAM_FIXED(dist_close, 4); - - 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) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - - if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; - - 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; - - bool doCheckSight = !(flags & JLOSF_NOSIGHT); - - if (dist_close && (distance < dist_close)) - { - if (flags & JLOSF_CLOSENOJUMP) - return; - - 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; // [KS] Outside of FOV - return - } - } - - if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) - return; - - ACTION_JUMP(jump); -} - - -//=========================================================================== -// -// A_DamageMaster (int amount) -// Damages the master of this child by the specified amount. Negative values heal. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - - 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); - } - } -} - -//=========================================================================== -// -// A_DamageChildren (amount) -// Damages the children of this master by the specified amount. Negative values heal. -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) -{ - TThinkerIterator it; - AActor * mo; - - ACTION_PARAM_START(2); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - - 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); - } - } - } -} - -// [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) -{ - TThinkerIterator it; - AActor * mo; - - ACTION_PARAM_START(2); - ACTION_PARAM_INT(amount, 0); - ACTION_PARAM_NAME(DamageType, 1); - - 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); - } - } - } - } -} - - -//=========================================================================== -// -// Modified code pointer from Skulltag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - ACTION_PARAM_START(2); - ACTION_PARAM_INT(count, 0); - ACTION_PARAM_STATE(jump, 1); - ACTION_PARAM_BOOL(dontincrement, 2) - - if (count <= 0) return; - - 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; -} - -//=========================================================================== -// -// Resets the counter for the above function -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) -{ - if ( self->player == NULL || self->player->ReadyWeapon == NULL ) - return; - - AWeapon *weapon = self->player->ReadyWeapon; - weapon->ReloadCounter = 0; -} - -//=========================================================================== -// -// A_ChangeFlag -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_BOOL(expression, 1); - - const char *dot = strchr (flagname, '.'); - FFlagDef *fd; - const PClass *cls = self->GetClass(); - - if (dot != NULL) - { - FString part1(flagname, 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->ActorInfo, expression, 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, expression); - 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, cls->TypeName.GetChars()); - } -} - -//=========================================================================== -// -// A_CheckFlag -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_STRING(flagname, 0); - ACTION_PARAM_STATE(jumpto, 1); - ACTION_PARAM_INT(checkpointer, 2); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - - AActor *owner; - - COPY_AAPTR_NOT_NULL(self, owner, checkpointer); - - if (CheckActorFlag(owner, flagname)) - { - ACTION_JUMP(jumpto); - } -} - - -//=========================================================================== -// -// A_RemoveMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RemoveMaster) -{ - if (self->master != NULL) - { - P_RemoveThing(self->master); - } -} - -//=========================================================================== -// -// A_RemoveChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(removeall,0); - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self && (mo->health <= 0 || removeall)) - { - P_RemoveThing(mo); - } - } -} - -//=========================================================================== -// -// A_RemoveSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) -{ - TThinkerIterator it; - AActor *mo; - ACTION_PARAM_START(1); - ACTION_PARAM_BOOL(removeall,0); - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) - { - P_RemoveThing(mo); - } - } - } -} - -//=========================================================================== -// -// A_RaiseMaster -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseMaster) -{ - if (self->master != NULL) - { - P_Thing_Raise(self->master); - } -} - -//=========================================================================== -// -// A_RaiseChildren -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseChildren) -{ - TThinkerIterator it; - AActor *mo; - - while ((mo = it.Next()) != NULL) - { - if (mo->master == self) - { - P_Thing_Raise(mo); - } - } -} - -//=========================================================================== -// -// A_RaiseSiblings -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_RaiseSiblings) -{ - TThinkerIterator it; - AActor *mo; - - if (self->master != NULL) - { - while ((mo = it.Next()) != NULL) - { - if (mo->master == self->master && mo != self) - { - P_Thing_Raise(mo); - } - } - } -} - -//=========================================================================== -// -// A_MonsterRefire -// -// Keep firing unless target got out of sight -// -//=========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(prob, 0); - ACTION_PARAM_STATE(jump, 1); - - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - A_FaceTarget (self); - - if (pr_monsterrefire() < prob) - return; - - if (!self->target - || P_HitFriend (self) - || self->target->health <= 0 - || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) - { - ACTION_JUMP(jump); - } -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_ANGLE(angle, 0); - ACTION_PARAM_INT(flags, 1) - self->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); -} - -//=========================================================================== -// -// A_SetPitch -// -// Set actor's pitch (in degrees). -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_ANGLE(pitch, 0); - ACTION_PARAM_INT(flags, 1); - - 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)); -} - -//=========================================================================== -// -// A_ScaleVelocity -// -// Scale actor's velocity. -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(scale, 0); - - 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); - } -} - -//=========================================================================== -// -// A_ChangeVelocity -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_FIXED(x, 0); - ACTION_PARAM_FIXED(y, 1); - ACTION_PARAM_FIXED(z, 2); - ACTION_PARAM_INT(flags, 3); - - 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); - } -} - -//=========================================================================== -// -// A_SetArg -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(pos, 0); - ACTION_PARAM_INT(value, 1); - - // Set the value of the specified arg - if ((size_t)pos < countof(self->args)) - { - self->args[pos] = value; - } -} - -//=========================================================================== -// -// A_SetSpecial -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_INT(spec, 0); - ACTION_PARAM_INT(arg0, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - ACTION_PARAM_INT(arg4, 5); - - self->special = spec; - self->args[0] = arg0; - self->args[1] = arg1; - self->args[2] = arg2; - self->args[3] = arg3; - self->args[4] = arg4; -} - -//=========================================================================== -// -// A_SetUserVar -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(value, 1); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Int) - { - Printf("%s is not a user variable in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user variable. - *(int *)(reinterpret_cast(self) + var->offset) = value; -} - -//=========================================================================== -// -// A_SetUserArray -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_NAME(varname, 0); - ACTION_PARAM_INT(pos, 1); - ACTION_PARAM_INT(value, 2); - - PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); - PSymbolVariable *var; - - if (sym == NULL || sym->SymbolType != SYM_Variable || - !(var = static_cast(sym))->bUserVar || - var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) - { - Printf("%s is not a user array in class %s\n", varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - if (pos < 0 || pos >= var->ValueType.size) - { - Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), - self->GetClass()->TypeName.GetChars()); - return; - } - // Set the value of the specified user array at index pos. - ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(6); - ACTION_PARAM_STATE(TeleportState, 0); - ACTION_PARAM_CLASS(TargetType, 1); - ACTION_PARAM_CLASS(FogType, 2); - ACTION_PARAM_INT(Flags, 3); - ACTION_PARAM_FIXED(MinDist, 4); - ACTION_PARAM_FIXED(MaxDist, 5); - - // 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; - } - - if (TeleportState == NULL) - { - // Default to Teleport. - TeleportState = self->FindState("Teleport"); - // If still nothing, then return. - if (!TeleportState) return; - } - - DSpotState *state = DSpotState::GetSpotState(); - if (state == NULL) return; - - if (!TargetType) TargetType = PClass::FindClass("BossSpot"); - - AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); - if (spot == NULL) return; - - 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 (FogType) - { - Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); - } - - ACTION_JUMP(TeleportState); - - self->z = self->floorz; - self->angle = spot->angle; - self->velx = self->vely = self->velz = 0; - } -} - -//=========================================================================== -// -// A_Turn -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_ANGLE(angle, 0); - self->angle += angle; -} - -//=========================================================================== -// -// A_Quake -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) -{ - ACTION_PARAM_START(5); - ACTION_PARAM_INT(intensity, 0); - ACTION_PARAM_INT(duration, 1); - ACTION_PARAM_INT(damrad, 2); - ACTION_PARAM_INT(tremrad, 3); - ACTION_PARAM_SOUND(sound, 4); - P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); -} - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(4); - ACTION_PARAM_INT(xspeed, 0); - ACTION_PARAM_INT(yspeed, 1); - ACTION_PARAM_FIXED(xdist, 2); - ACTION_PARAM_FIXED(ydist, 3); - A_Weave(self, xspeed, yspeed, xdist, ydist); -} - - - - -//=========================================================================== -// -// 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) -{ - ACTION_PARAM_START(2); - ACTION_PARAM_INT(special, 0); - ACTION_PARAM_INT(tag, 1); - - 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); -} - -//========================================================================== -// -// A Wolf3D-style attack codepointer -// -//========================================================================== -enum WolfAttackFlags -{ - WAF_NORANDOM = 1, - WAF_USEPUFF = 2, -}; - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) -{ - ACTION_PARAM_START(9); - ACTION_PARAM_INT(flags, 0); - ACTION_PARAM_SOUND(sound, 1); - ACTION_PARAM_FIXED(snipe, 2); - ACTION_PARAM_INT(maxdamage, 3); - ACTION_PARAM_INT(blocksize, 4); - ACTION_PARAM_INT(pointblank, 5); - ACTION_PARAM_INT(longrange, 6); - ACTION_PARAM_FIXED(runspeed, 7); - ACTION_PARAM_CLASS(pufftype, 8); - - if (!self->target) - return; - - // Enemy can't see target - if (!P_CheckSight(self, self->target)) - return; - - 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); -} - - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(7); - - ACTION_PARAM_INT(destination_selector, 0); - ACTION_PARAM_FIXED(xofs, 1); - ACTION_PARAM_FIXED(yofs, 2); - ACTION_PARAM_FIXED(zofs, 3); - ACTION_PARAM_ANGLE(angle, 4); - ACTION_PARAM_INT(flags, 5); - ACTION_PARAM_STATE(success_state, 6); - - fixed_t - - oldx, - oldy, - oldz; - - AActor *reference = COPY_AAPTR(self, destination_selector); - - if (!reference) - { - ACTION_SET_RESULT(false); - return; - } - - 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; - } - - ACTION_SET_RESULT(true); - } - else - { - self->SetOrigin(oldx, oldy, oldz); - ACTION_SET_RESULT(false); - } - -} - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(arg1, 1); - ACTION_PARAM_INT(arg2, 2); - ACTION_PARAM_INT(arg3, 3); - ACTION_PARAM_INT(arg4, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(arg3, 4); - - bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) -{ - ACTION_PARAM_START(5); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - ACTION_PARAM_INT(arg1, 2); - ACTION_PARAM_INT(arg2, 3); - ACTION_PARAM_INT(lock, 4); - - bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) -{ - ACTION_PARAM_START(2); - - ACTION_PARAM_NAME(scriptname, 0); - ACTION_PARAM_INT(mapnum, 1); - - bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); - - ACTION_SET_RESULT(res); -} - - -//========================================================================== -// -// 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) -{ - ACTION_PARAM_START(7); - ACTION_PARAM_CLASS(item, 0); - ACTION_PARAM_FIXED(distance, 1); - ACTION_PARAM_INT(flags, 2); - ACTION_PARAM_INT(amount, 3); - - // We need a valid item, valid targets, and a valid range - if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) - { - return; - } - 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 (); - } - } - } -} - - -//========================================================================== -// -// A_SetTics -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_INT(tics_to_set, 0); - - 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; - } - } - } - // Just set tics for self. - self->tics = tics_to_set; -} - -//========================================================================== -// -// A_SetDamageType -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_NAME(damagetype, 0); - - self->DamageType = damagetype; -} - -//========================================================================== -// -// A_DropItem -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) -{ - ACTION_PARAM_START(3); - ACTION_PARAM_CLASS(spawntype, 0); - ACTION_PARAM_INT(amount, 1); - ACTION_PARAM_INT(chance, 2); - - P_DropItem(self, spawntype, amount, chance); -} - -//========================================================================== -// -// A_SetSpeed -// -//========================================================================== - -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) -{ - ACTION_PARAM_START(1); - ACTION_PARAM_FIXED(speed, 0); - - self->Speed = speed; +/* +** 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) +{ + StateCallData StateCall; + bool result = false; + int counter = 0; + + while (State != NULL) + { + // Assume success. The code pointer will set this to false if necessary + StateCall.State = State; + StateCall.Result = true; + if (State->CallAction(actor, this, &StateCall)) + { + // collect all the results. Even one successful call signifies overall success. + result |= StateCall.Result; + } + + + // Since there are no delays it is a good idea to check for infinite loops here! + counter++; + if (counter >= 10000) break; + + if (StateCall.State == State) + { + // Abort immediately if the state jumps to itself! + if (State == State->GetNextState()) + { + return false; + } + + // If both variables are still the same there was no jump + // so we must advance to the next state. + State = State->GetNextState(); + } + else + { + State = StateCall.State; + } + } + 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) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(ptr_target, 0); + ACTION_PARAM_INT(ptr_master, 1); + ACTION_PARAM_INT(ptr_tracer, 2); + ACTION_PARAM_INT(flags, 3); + + // 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; + } +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ptr_source, 0); + ACTION_PARAM_INT(ptr_recepient, 1); + ACTION_PARAM_INT(ptr_sourcefield, 2); + ACTION_PARAM_INT(ptr_recepientfield, 3); + ACTION_PARAM_INT(flags, 4); + + AActor *source, *recepient; + + // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) + + source = COPY_AAPTR(self, ptr_source); + COPY_AAPTR_NOT_NULL(self, recepient, ptr_recepient); // pick an actor to store the provided pointer value + + // convert source from dataprovider to data + + source = COPY_AAPTR(source, ptr_sourcefield); + + if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL + + if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from + + ASSIGN_AAPTR(recepient, ptr_recepientfield, source, flags); +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr_source, 0); + + if (self->player) return; + + AActor *source; + COPY_AAPTR_NOT_NULL(self, source, ptr_source); + self->CopyFriendliness(source, false, false); // No change in current target or health +} + +//========================================================================== +// +// Simple flag changers +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_SetSolid) +{ + self->flags |= MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetSolid) +{ + self->flags &= ~MF_SOLID; +} + +DEFINE_ACTION_FUNCTION(AActor, A_SetFloat) +{ + self->flags |= MF_FLOAT; +} + +DEFINE_ACTION_FUNCTION(AActor, A_UnsetFloat) +{ + self->flags &= ~(MF_FLOAT|MF_INFLOAT); +} + +//========================================================================== +// +// Customizable attack functions which use actor parameters. +// +//========================================================================== +static void DoAttack (AActor *self, bool domelee, bool domissile, + int MeleeDamage, FSoundID MeleeSound, const PClass *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) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + DoAttack(self, true, false, MeleeDamage, MeleeSound, NULL, 0); +} + +DEFINE_ACTION_FUNCTION(AActor, A_MissileAttack) +{ + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, false, true, 0, 0, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION(AActor, A_ComboAttack) +{ + int MeleeDamage = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeDamage, 0); + FSoundID MeleeSound = self->GetClass()->Meta.GetMetaInt (ACMETA_MeleeSound, 0); + const PClass *MissileType=PClass::FindClass((ENamedName) self->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None)); + fixed_t MissileHeight= self->GetClass()->Meta.GetMetaFixed (ACMETA_MissileHeight, 32*FRACUNIT); + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(MeleeDamage, 0); + ACTION_PARAM_SOUND(MeleeSound, 1); + ACTION_PARAM_CLASS(MissileType, 2); + ACTION_PARAM_FIXED(MissileHeight, 3); + + if (MissileType == NULL) return; + DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight); +} + +//========================================================================== +// +// Custom sound functions. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySound) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_INT(channel, 1); + ACTION_PARAM_FLOAT(volume, 2); + ACTION_PARAM_BOOL(looping, 3); + ACTION_PARAM_FLOAT(attenuation, 4); + + if (!looping) + { + S_Sound (self, channel, soundid, volume, attenuation); + } + else + { + if (!S_IsActorPlayingSomething (self, channel&7, soundid)) + { + S_Sound (self, channel | CHAN_LOOP, soundid, volume, attenuation); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSound) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(slot, 0); + + S_StopSound(self, slot); +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_SOUND(soundid, 0); + + S_Sound (self, CHAN_WEAPON, soundid, 1, ATTN_NORM); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlaySoundEx) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_SOUND(soundid, 0); + ACTION_PARAM_NAME(channel, 1); + ACTION_PARAM_BOOL(looping, 2); + ACTION_PARAM_INT(attenuation_raw, 3); + + 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); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StopSoundEx) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(channel, 0); + + if (channel > NAME_Auto && channel <= NAME_SoundSlot7) + { + S_StopSound (self, int(channel) - NAME_Auto); + } +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ang1, 0); + ACTION_PARAM_INT(ang2, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(chance, 3); + ACTION_PARAM_INT(distance, 4); + + 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; + } + } +} + +//========================================================================== +// +// Hitscan attack with a customizable amount of bullets (specified in damage) +// +//========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack) +{ + int i; + int bangle; + int slope; + + if (!self->target) return; + + 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); + } +} + + +//========================================================================== +// +// Do the state jump +// +//========================================================================== +static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall) +{ + if (jumpto == NULL) return; + + if (statecall != NULL) + { + statecall->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) + { + self->SetState (jumpto); + } + 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, CallingState, offset, statecall) + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_INT(maxchance, 1); + + if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance)) + { + int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1))); + ACTION_PARAM_STATE(jumpto, jumps); + ACTION_JUMP(jumpto); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(health, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_INT(ptr_selector, 2); + + AActor *measured; + + measured = COPY_AAPTR(self, ptr_selector); + + if (measured && measured->health < health) + { + ACTION_JUMP(jump); + } + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetOutsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (!self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInsideMeleeRange) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + if (self->CheckMeleeRange()) + { + ACTION_JUMP(jump); + } + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +} +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfCloser(AActor *target, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(dist, 0); + ACTION_PARAM_STATE(jump, 1); + + 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); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +{ + AActor *target; + + if (!self->player) + { + target = self->target; + } + else + { + // Does the player aim at something that can be shot? + P_BulletSlope(self, &target); + } + DoJumpIfCloser(target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) +{ + DoJumpIfCloser(self->tracer, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) +{ + DoJumpIfCloser(self->master, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(Type, 0); + ACTION_PARAM_INT(ItemAmount, 1); + ACTION_PARAM_STATE(JumpOffset, 2); + ACTION_PARAM_INT(setowner, 3); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (!Type) return; + COPY_AAPTR_NOT_NULL(owner, owner, setowner); // returns if owner ends up being NULL + + AInventory *Item = owner->FindInventory(Type); + + if (Item) + { + if (ItemAmount > 0) + { + if (Item->Amount >= ItemAmount) + ACTION_JUMP(JumpOffset); + } + else if (Item->Amount >= Item->MaxAmount) + { + ACTION_JUMP(JumpOffset); + } + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory) +{ + DoJumpIfInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory) +{ + DoJumpIfInventory(self->target, PUSH_PARAMINFO); +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfArmorType) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(Type, 0); + ACTION_PARAM_STATE(JumpOffset, 1); + ACTION_PARAM_INT(amount, 2); + + 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(JumpOffset); +} + +//========================================================================== +// +// Parameterized version of A_Explode +// +//========================================================================== + +enum +{ + XF_HURTSOURCE = 1, + XF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Explode) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(damage, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_BOOL(alert, 3); + ACTION_PARAM_INT(fulldmgdistance, 4); + ACTION_PARAM_INT(nails, 5); + ACTION_PARAM_INT(naildamage, 6); + ACTION_PARAM_CLASS(pufftype, 7); + + if (damage < 0) // get parameters from metadata + { + damage = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionDamage, 128); + distance = self->GetClass()->Meta.GetMetaInt (ACMETA_ExplosionRadius, damage); + flags = !self->GetClass()->Meta.GetMetaInt (ACMETA_DontHurtShooter); + alert = false; + } + else + { + 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); + } +} + +//========================================================================== +// +// A_RadiusThrust +// +//========================================================================== + +enum +{ + RTF_AFFECTSOURCE = 1, + RTF_NOIMPACTDAMAGE = 2, + RTF_NOTMISSILE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RadiusThrust) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_INT(force, 0); + ACTION_PARAM_INT(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(fullthrustdistance, 3); + + 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; + } +} + +//========================================================================== +// +// Execute a line special / script +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + ACTION_PARAM_INT(arg5, 5); + + bool res = !!P_ExecuteSpecial(special, NULL, self, false, arg1, arg2, arg3, arg4, arg5); + + ACTION_SET_RESULT(res); +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(Spawnofs_XY, 2); + ACTION_PARAM_ANGLE(Angle, 3); + ACTION_PARAM_INT(flags, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_INT(ptr, 6); + + AActor *ref = COPY_AAPTR(self, ptr); + + int aimmode = flags & CMF_AIMMODE; + + AActor * targ; + AActor * missile; + + if (ref != 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, ref, 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, ref, 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) + { + // 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); + } +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(pufftype, 4); + ACTION_PARAM_FIXED(Range, 5); + ACTION_PARAM_INT(Flags, 6); + ACTION_PARAM_INT(ptr, 7); + + AActor *ref = COPY_AAPTR(self, ptr); + + if(Range==0) Range=MISSILERANGE; + + int i; + int bangle; + int bslope = 0; + int laflags = (Flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0; + + if (ref || (Flags & CBAF_AIMFACING)) + { + if (!(Flags & CBAF_AIMFACING)) + { + A_Face(self, ref); + } + bangle = self->angle; + + if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); + + if (!(Flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); + + S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); + for (i=0 ; itarget) + return; + + 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); + } +} + +//========================================================================== +// +// A fully customizable combo attack +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomComboAttack) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_CLASS(ti, 0); + ACTION_PARAM_FIXED(SpawnHeight, 1); + ACTION_PARAM_INT(damage, 2); + ACTION_PARAM_SOUND(MeleeSound, 3); + ACTION_PARAM_NAME(DamageType, 4); + ACTION_PARAM_BOOL(bleed, 5); + + if (!self->target) + return; + + 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); + } + } +} + +//========================================================================== +// +// State jump function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (!ACTION_CALL_FROM_WEAPON()) return; + + if (!self->player->ReadyWeapon->CheckAmmo(self->player->ReadyWeapon->bAltFire, false, true)) + { + ACTION_JUMP(jump); + } + +} + + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_ANGLE(Spread_XY, 0); + ACTION_PARAM_ANGLE(Spread_Z, 1); + ACTION_PARAM_INT(NumberOfBullets, 2); + ACTION_PARAM_INT(DamagePerBullet, 3); + ACTION_PARAM_CLASS(PuffType, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FIXED(Range, 6); + + if (!self->player) return; + + 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; // 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) PuffType = PClass::FindClass(NAME_BulletPuff); + + if (weapon != NULL) + { + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); + } + + if ((NumberOfBullets==1 && !player->refire) || NumberOfBullets==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 (NumberOfBullets == -1) NumberOfBullets = 1; + for (i=0 ; iplayer) return; + + + player_t *player=self->player; + AWeapon * weapon=player->ReadyWeapon; + AActor *linetarget; + + if (UseAmmo && weapon) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // 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 SavedPlayerPitch = self->pitch; + self->pitch -= pitch; + AActor * misl=P_SpawnPlayerMissile (self, x, y, z, ti, shootangle, &linetarget); + self->pitch = SavedPlayerPitch; + + // 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]); + } + } + } +} + + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(8); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_BOOL(norandom, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(PuffType, 3); + ACTION_PARAM_FIXED(Range, 4); + ACTION_PARAM_FIXED(LifeSteal, 5); + ACTION_PARAM_INT(lifestealmax, 6); + ACTION_PARAM_CLASS(armorbonustype, 7); + + if (!self->player) return; + + 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; // out of ammo + } + + if (!PuffType) PuffType = PClass::FindClass(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) armorbonustype = PClass::FindClass("ArmorBonus"); + + if (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); + } +} + + +//========================================================================== +// +// customizable railgun attack function +// +//========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_BOOL(UseAmmo, 2); + ACTION_PARAM_COLOR(Color1, 3); + ACTION_PARAM_COLOR(Color2, 4); + ACTION_PARAM_INT(Flags, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + if (!self->player) return; + + AWeapon * weapon=self->player->ReadyWeapon; + + // only use ammo when actually hitting something! + if (UseAmmo) + { + if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // 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); +} + +//========================================================================== +// +// also for monsters +// +//========================================================================== +enum +{ + CRF_DONTAIM = 0, + CRF_AIMPARALLEL = 1, + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) +{ + ACTION_PARAM_START(16); + ACTION_PARAM_INT(Damage, 0); + ACTION_PARAM_INT(Spawnofs_XY, 1); + ACTION_PARAM_COLOR(Color1, 2); + ACTION_PARAM_COLOR(Color2, 3); + ACTION_PARAM_INT(Flags, 4); + ACTION_PARAM_INT(aim, 5); + ACTION_PARAM_FLOAT(MaxDiff, 6); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); + ACTION_PARAM_FIXED(Range, 10); + ACTION_PARAM_INT(Duration, 11); + ACTION_PARAM_FLOAT(Sparsity, 12); + ACTION_PARAM_FLOAT(DriftSpeed, 13); + ACTION_PARAM_CLASS(SpawnClass, 14); + ACTION_PARAM_FIXED(Spawnofs_Z, 15); + + if(Range==0) Range=8192*FRACUNIT; + if(Sparsity==0) Sparsity=1.0; + + 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; + } + // [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; +} + +//=========================================================================== +// +// DoGiveInventory +// +//=========================================================================== + +static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_CLASS(mi, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(setreceiver, 2); + + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + + 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); + +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) +{ + DoGiveInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) +{ + DoGiveInventory(self->target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoGiveInventory(mo, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoGiveInventory(mo, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// A_TakeInventory +// +//=========================================================================== + +enum +{ + TIF_NOTAKEINFINITE = 1, +}; + +void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(setreceiver, 3); + + if (!item) return; + COPY_AAPTR_NOT_NULL(receiver, receiver, setreceiver); + + bool res = false; + + AInventory * inv = receiver->FindInventory(item); + + 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); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) +{ + DoTakeInventory(self, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget) +{ + DoTakeInventory(self->target, PUSH_PARAMINFO); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromChildren) +{ + TThinkerIterator it; + AActor * mo; + + while ((mo = it.Next())) + { + if (mo->master == self) DoTakeInventory(mo, PUSH_PARAMINFO); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromSiblings) +{ + TThinkerIterator it; + AActor * mo; + + if (self->master != NULL) + { + while ((mo = it.Next())) + { + if (mo->master == self->master && mo != self) DoTakeInventory(mo, PUSH_PARAMINFO); + } + } +} + +//=========================================================================== +// +// 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) + { + 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) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_FIXED(zheight, 2); + ACTION_PARAM_BOOL(useammo, 3); + ACTION_PARAM_BOOL(transfer_translation, 4); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + // Don't spawn monsters if this actor has been massacred + if (self->DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + 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) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + 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 +} + +//=========================================================================== +// +// A_SpawnItemEx +// +// Enhanced spawning function +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnItemEx) +{ + ACTION_PARAM_START(11); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_FIXED(xvel, 4); + ACTION_PARAM_FIXED(yvel, 5); + ACTION_PARAM_FIXED(zvel, 6); + ACTION_PARAM_ANGLE(Angle, 7); + ACTION_PARAM_INT(flags, 8); + ACTION_PARAM_INT(chance, 9); + ACTION_PARAM_INT(tid, 10); + + if (!missile) + { + ACTION_SET_RESULT(false); + return; + } + + if (chance > 0 && pr_spawnitemex()DamageType == NAME_Massacre && GetDefaultByType(missile)->flags3&MF3_ISMONSTER) return; + + 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; + } +} + +//=========================================================================== +// +// A_ThrowGrenade +// +// Throws a grenade (like Hexen's fighter flechette) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_CLASS(missile, 0); + ACTION_PARAM_FIXED(zheight, 1); + ACTION_PARAM_FIXED(xyvel, 2); + ACTION_PARAM_FIXED(zvel, 3); + ACTION_PARAM_BOOL(useammo, 4); + + if (missile == NULL) return; + + if (ACTION_CALL_FROM_WEAPON()) + { + // Used from a weapon, so use some ammo + AWeapon *weapon = self->player->ReadyWeapon; + + if (!weapon) return; + if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) return; + } + + + 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); +} + + +//=========================================================================== +// +// A_Recoil +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Recoil) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(xyvel, 0); + + angle_t angle = self->angle + ANG180; + angle >>= ANGLETOFINESHIFT; + self->velx += FixedMul (xyvel, finecosine[angle]); + self->vely += FixedMul (xyvel, finesine[angle]); +} + + +//=========================================================================== +// +// A_SelectWeapon +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(cls, 0); + + if (cls == NULL || self->player == NULL) + { + ACTION_SET_RESULT(false); + return; + } + + AWeapon * weaponitem = static_cast(self->FindInventory(cls)); + + if (weaponitem != NULL && weaponitem->IsKindOf(RUNTIME_CLASS(AWeapon))) + { + if (self->player->ReadyWeapon != weaponitem) + { + self->player->PendingWeapon = weaponitem; + } + } + else ACTION_SET_RESULT(false); + +} + + +//=========================================================================== +// +// A_Print +// +//=========================================================================== +EXTERN_CVAR(Float, con_midtime) + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + 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 = 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! +} + +//=========================================================================== +// +// A_PrintBold +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(text, 0); + ACTION_PARAM_FLOAT(time, 1); + ACTION_PARAM_NAME(fontname, 2); + + 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 = 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! +} + +//=========================================================================== +// +// A_Log +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STRING(text, 0); + + 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! +} + +//========================================================================= +// +// A_LogInt +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(num, 0); + Printf("%d\n", num); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! +} + +//=========================================================================== +// +// A_SetTranslucent +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTranslucent) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(alpha, 0); + ACTION_PARAM_INT(mode, 1); + + 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); +} + +//=========================================================================== +// +// A_FadeIn +// +// Fades the actor in +// +//=========================================================================== + +enum FadeFlags +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + if (reduce == 0) + { + reduce = FRACUNIT / 10; + } + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + self->alpha += reduce; + + if (self->alpha >= (FRACUNIT * 1)) + { + if (flags & FTF_CLAMP) + self->alpha = (FRACUNIT * 1); + if (flags & FTF_REMOVE) + self->Destroy(); + } +} + +//=========================================================================== +// +// A_FadeOut +// +// fades the actor out and destroys it when done +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(reduce, 0); + ACTION_PARAM_INT(flags, 1); + + 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(); + } +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_FIXED(target, 0); + ACTION_PARAM_FIXED(amount, 1); + ACTION_PARAM_INT(flags, 2); + + 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) + { + if (self->alpha > (FRACUNIT * 1)) + self->alpha = (FRACUNIT * 1); + else if (self->alpha < 0) + self->alpha = 0; + } + if (self->alpha == target && (flags & FTF_REMOVE)) + { + self->Destroy(); + } +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_FIXED(scalex, 0); + ACTION_PARAM_FIXED(scaley, 1); + + self->scaleX = scalex; + self->scaleY = scaley ? scaley : scalex; +} + +//=========================================================================== +// +// A_SetMass(int mass) +// +// Sets the actor's mass. +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetMass) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(mass, 0); + + self->Mass = mass; +} + +//=========================================================================== +// +// A_SpawnDebris +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnDebris) +{ + int i; + AActor * mo; + + ACTION_PARAM_START(4); + ACTION_PARAM_CLASS(debris, 0); + ACTION_PARAM_BOOL(transfer_translation, 1); + ACTION_PARAM_FIXED(mult_h, 2); + ACTION_PARAM_FIXED(mult_v, 3); + + if (debris == NULL) return; + + // 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()->ActorInfo->NumOwnedStates) + { + mo->SetState(mo->GetClass()->ActorInfo->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)); + } + } +} + + +//=========================================================================== +// +// A_CheckSight +// jumps if no player can see this actor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckSight) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + 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; + } + // 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; + } + } + } + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// 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, bool twodi) +{ + 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; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= 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) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + 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, twodi)) + { + return; + } + // 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, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// A_CheckRange +// Jumps if this actor is out of range of all players. +// +//=========================================================================== +static bool DoCheckRange(AActor *self, AActor *camera, double range, bool twodi) +{ + 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; + } + double distance = (dx * dx) + (dy * dy) + (twodi == 0? (dz * dz) : 0); + if (distance <= range){ + // Within range + return true; + } + return false; +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckRange) +{ + ACTION_PARAM_START(3); + double range = EvalExpressionF(ParameterIndex+0, self); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(twodi, 2); + + 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, twodi)) + { + return; + } + // 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, twodi)) + { + return; + } + } + } + ACTION_JUMP(jump); +} + + +//=========================================================================== +// +// Inventory drop +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(drop, 0); + + if (drop) + { + AInventory * inv = self->FindInventory(drop); + if (inv) + { + self->DropInventory(inv); + } + } +} + + +//=========================================================================== +// +// A_SetBlend +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetBlend) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_COLOR(color, 0); + ACTION_PARAM_FLOAT(alpha, 1); + ACTION_PARAM_INT(tics, 2); + ACTION_PARAM_COLOR(color2, 3); + + if (color == MAKEARGB(255,255,255,255)) color=0; + if (color2 == MAKEARGB(255,255,255,255)) color2=0; + if (!color2.a) + color2 = color; + + new DFlashFader(color.r/255.0f, color.g/255.0f, color.b/255.0f, alpha, + color2.r/255.0f, color2.g/255.0f, color2.b/255.0f, 0, + (float)tics/TICRATE, self); +} + + +//=========================================================================== +// +// A_JumpIf +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIf) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_BOOL(expression, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (expression) ACTION_JUMP(jump); + +} + +//=========================================================================== +// +// A_CountdownArg +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CountdownArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(cnt, 0); + ACTION_PARAM_STATE(state, 1); + + if (cnt<0 || cnt>=5) return; + if (!self->args[cnt]--) + { + 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 + { + // can't use "Death" as default parameter with current DECORATE parser. + if (state == NULL) state = self->FindState(NAME_Death); + self->SetState(state); + } + } + +} + +//============================================================================ +// +// A_Burst +// +//============================================================================ + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_CLASS(chunk, 0); + + int i, numChunks; + AActor * mo; + + if (chunk == NULL) return; + + 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 (); +} + +//=========================================================================== +// +// A_CheckFloor +// [GRB] Jumps if actor is standing on floor +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFloor) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + if (self->z <= self->floorz) + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_CheckCeiling +// [GZ] Totally copied on A_CheckFloor, jumps if actor touches ceiling +// + +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckCeiling) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + ACTION_SET_RESULT(false); + if (self->z+self->height >= self->ceilingz) // Height needs to be counted + { + ACTION_JUMP(jump); + } + +} + +//=========================================================================== +// +// A_Stop +// resets all velocity of the actor to 0 +// +//=========================================================================== +DEFINE_ACTION_FUNCTION(AActor, A_Stop) +{ + 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; + } +} + +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 +// +//=========================================================================== + +extern void AF_A_RestoreSpecialPosition(DECLARE_PARAMINFO); + +enum RS_Flags +{ + RSF_FOG=1, + RSF_KEEPTARGET=2, + RSF_TELEFRAG=4, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Respawn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(flags, 0); + 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; + } +} + + +//========================================================================== +// +// A_PlayerSkinCheck +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PlayerSkinCheck) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_STATE(jump, 0); + + 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); + } +} + +//=========================================================================== +// +// A_SetGravity +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetGravity) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(val, 0); + + self->gravity = clamp (val, 0, FRACUNIT*10); +} + + +// [KS] *** Start of my modifications *** + +//=========================================================================== +// +// A_ClearTarget +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) +{ + self->target = NULL; + self->LastHeard = NULL; + self->lastenemy = NULL; +} + +//========================================================================== +// +// 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; + + ACTION_PARAM_START(9); + + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_FIXED(range, 2); + ACTION_PARAM_FIXED(minrange, 3); + { + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_ANGLE(pitch, 5); + ACTION_PARAM_FIXED(offsetheight, 6); + ACTION_PARAM_FIXED(offsetwidth, 7); + ACTION_PARAM_INT(ptr_target, 8); + + 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; + } + + { + 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; + + 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; + } + 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); + } +} + +//========================================================================== +// +// 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 = 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) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + 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) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + 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; + + 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; + + // [FDARI] If actors share team, don't jump + if ((flags & JLOSF_ALLYNOJUMP) && self->IsFriend(target)) return; + + 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; + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + 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; + + 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; // [KS] Outside of FOV - return + } + + } + + ACTION_JUMP(jump); +} + + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_STATE(jump, 0); + ACTION_PARAM_ANGLE(fov, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); + + 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) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. + + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + 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; + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + 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; // [KS] Outside of FOV - return + } + } + + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + return; + + ACTION_JUMP(jump); +} + +//=========================================================================== +// +// Modified code pointer from Skulltag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckForReload) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + ACTION_PARAM_START(2); + ACTION_PARAM_INT(count, 0); + ACTION_PARAM_STATE(jump, 1); + ACTION_PARAM_BOOL(dontincrement, 2) + + if (count <= 0) return; + + 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; +} + +//=========================================================================== +// +// Resets the counter for the above function +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_ResetReloadCounter) +{ + if ( self->player == NULL || self->player->ReadyWeapon == NULL ) + return; + + AWeapon *weapon = self->player->ReadyWeapon; + weapon->ReloadCounter = 0; +} + +//=========================================================================== +// +// A_ChangeFlag +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeFlag) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_BOOL(expression, 1); + + const char *dot = strchr (flagname, '.'); + FFlagDef *fd; + const PClass *cls = self->GetClass(); + + if (dot != NULL) + { + FString part1(flagname, 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->ActorInfo, expression, 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, expression); + 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, cls->TypeName.GetChars()); + } +} + +//=========================================================================== +// +// A_CheckFlag +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CheckFlag) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_STRING(flagname, 0); + ACTION_PARAM_STATE(jumpto, 1); + ACTION_PARAM_INT(checkpointer, 2); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + AActor *owner; + + COPY_AAPTR_NOT_NULL(self, owner, checkpointer); + + if (CheckActorFlag(owner, flagname)) + { + ACTION_JUMP(jumpto); + } +} + +//=========================================================================== +// +// A_RaiseMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseMaster) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + + if (self->master != NULL) + { + if (copy) + P_Thing_Raise(self->master, self); + else + P_Thing_Raise(self->master, NULL); + } +} + +//=========================================================================== +// +// A_RaiseChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseChildren) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } +} + +//=========================================================================== +// +// A_RaiseSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RaiseSiblings) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_BOOL(copy, 0); + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self) + { + if (copy) + P_Thing_Raise(mo, self); + else + P_Thing_Raise(mo, NULL); + } + } + } +} + +//=========================================================================== +// +// [TP] A_FaceConsolePlayer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS (AActor, A_FaceConsolePlayer) { + ACTION_PARAM_START (1); + ACTION_PARAM_ANGLE (MaxTurnAngle, 0); + // NOTE: It does nothing for zdoom. +} + +//=========================================================================== +// +// A_MonsterRefire +// +// Keep firing unless target got out of sight +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_MonsterRefire) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(prob, 0); + ACTION_PARAM_STATE(jump, 1); + + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + A_FaceTarget (self); + + if (pr_monsterrefire() < prob) + return; + + if (!self->target + || P_HitFriend (self) + || self->target->health <= 0 + || !P_CheckSight (self, self->target, SF_SEEPASTBLOCKEVERYTHING|SF_SEEPASTSHOOTABLELINES) ) + { + ACTION_JUMP(jump); + } +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_ANGLE(angle, 0); + ACTION_PARAM_INT(flags, 1) + self->SetAngle(angle, !!(flags & SPF_INTERPOLATE)); +} + +//=========================================================================== +// +// A_SetPitch +// +// Set actor's pitch (in degrees). +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetPitch) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_ANGLE(pitch, 0); + ACTION_PARAM_INT(flags, 1); + + 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)); +} + +//=========================================================================== +// +// [Nash] A_SetRoll +// +// Set actor's roll (in degrees). +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRoll) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_ANGLE(roll, 0); + ACTION_PARAM_INT(flags, 1); + self->SetRoll(roll, !!(flags & SPF_INTERPOLATE)); +} + +//=========================================================================== +// +// A_ScaleVelocity +// +// Scale actor's velocity. +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ScaleVelocity) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(scale, 0); + + 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); + } +} + +//=========================================================================== +// +// A_ChangeVelocity +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ChangeVelocity) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_FIXED(x, 0); + ACTION_PARAM_FIXED(y, 1); + ACTION_PARAM_FIXED(z, 2); + ACTION_PARAM_INT(flags, 3); + + 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); + } +} + +//=========================================================================== +// +// A_SetArg +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetArg) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(pos, 0); + ACTION_PARAM_INT(value, 1); + + // Set the value of the specified arg + if ((size_t)pos < countof(self->args)) + { + self->args[pos] = value; + } +} + +//=========================================================================== +// +// A_SetSpecial +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpecial) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_INT(spec, 0); + ACTION_PARAM_INT(arg0, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + ACTION_PARAM_INT(arg4, 5); + + self->special = spec; + self->args[0] = arg0; + self->args[1] = arg1; + self->args[2] = arg2; + self->args[3] = arg3; + self->args[4] = arg4; +} + +//=========================================================================== +// +// A_SetUserVar +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(value, 1); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Int) + { + Printf("%s is not a user variable in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user variable. + *(int *)(reinterpret_cast(self) + var->offset) = value; +} + +//=========================================================================== +// +// A_SetUserArray +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_NAME(varname, 0); + ACTION_PARAM_INT(pos, 1); + ACTION_PARAM_INT(value, 2); + + PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); + PSymbolVariable *var; + + if (sym == NULL || sym->SymbolType != SYM_Variable || + !(var = static_cast(sym))->bUserVar || + var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) + { + Printf("%s is not a user array in class %s\n", varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + if (pos < 0 || pos >= var->ValueType.size) + { + Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), + self->GetClass()->TypeName.GetChars()); + return; + } + // Set the value of the specified user array at index pos. + ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_STATE(TeleportState, 0); + ACTION_PARAM_CLASS(TargetType, 1); + ACTION_PARAM_CLASS(FogType, 2); + ACTION_PARAM_INT(Flags, 3); + ACTION_PARAM_FIXED(MinDist, 4); + ACTION_PARAM_FIXED(MaxDist, 5); + + // 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; + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) return; + + if (!TargetType) TargetType = PClass::FindClass("BossSpot"); + + AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); + if (spot == NULL) return; + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + fixed_t aboveFloor = spot->z - spot->floorz; + fixed_t finalz = spot->floorz + aboveFloor; + + if (spot->z + self->height > spot->ceilingz) + finalz = spot->ceilingz - self->height; + else if (spot->z < spot->floorz) + finalz = spot->floorz; + + + //Take precedence and cooperate with telefragging first. + bool teleResult = P_TeleportMove(self, spot->x, spot->y, finalz, Flags & TF_TELEFRAG); + + if (Flags & TF_FORCED) + { + //If for some reason the original move didn't work, regardless of telefrag, force it to move. + self->SetOrigin(spot->x, spot->y, finalz); + teleResult = true; + } + + if (teleResult) + { + //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 (FogType || (Flags & TF_USEACTORFOG)) + { + if (!(Flags & TF_NOSRCFOG)) + { + if (Flags & TF_USEACTORFOG) + P_SpawnTeleportFog(self, prevX, prevY, prevZ, true); + else + Spawn(FogType, 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(FogType, self->x, self->y, self->z, ALLOW_REPLACE); + } + } + + if (Flags & TF_USESPOTZ) + self->z = spot->z; + else + self->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 (TeleportState == NULL) + { + // Default to Teleport. + TeleportState = self->FindState("Teleport"); + // If still nothing, then return. + if (!TeleportState) return; + } + ACTION_JUMP(TeleportState); + return; + } + } + ACTION_SET_RESULT(teleResult); +} + +//=========================================================================== +// +// A_Turn +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Turn) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_ANGLE(angle, 0); + self->angle += angle; +} + +//=========================================================================== +// +// A_Quake +// +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(intensity, 0); + ACTION_PARAM_INT(duration, 1); + ACTION_PARAM_INT(damrad, 2); + ACTION_PARAM_INT(tremrad, 3); + ACTION_PARAM_SOUND(sound, 4); + P_StartQuake(self, 0, intensity, duration, damrad, tremrad, sound); +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(xspeed, 0); + ACTION_PARAM_INT(yspeed, 1); + ACTION_PARAM_FIXED(xdist, 2); + ACTION_PARAM_FIXED(ydist, 3); + A_Weave(self, xspeed, yspeed, xdist, ydist); +} + + + + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(special, 0); + ACTION_PARAM_INT(tag, 1); + + 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); +} + +//========================================================================== +// +// A Wolf3D-style attack codepointer +// +//========================================================================== +enum WolfAttackFlags +{ + WAF_NORANDOM = 1, + WAF_USEPUFF = 2, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_WolfAttack) +{ + ACTION_PARAM_START(9); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_SOUND(sound, 1); + ACTION_PARAM_FIXED(snipe, 2); + ACTION_PARAM_INT(maxdamage, 3); + ACTION_PARAM_INT(blocksize, 4); + ACTION_PARAM_INT(pointblank, 5); + ACTION_PARAM_INT(longrange, 6); + ACTION_PARAM_FIXED(runspeed, 7); + ACTION_PARAM_CLASS(pufftype, 8); + + if (!self->target) + return; + + // Enemy can't see target + if (!P_CheckSight(self, self->target)) + return; + + 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); +} + + +//========================================================================== +// +// 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, + WARPF_BOB = 0x800, + WARPF_MOVEPTR = 0x1000, +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Warp) +{ + ACTION_PARAM_START(7); + + ACTION_PARAM_INT(destination_selector, 0); + ACTION_PARAM_FIXED(xofs, 1); + ACTION_PARAM_FIXED(yofs, 2); + ACTION_PARAM_FIXED(zofs, 3); + ACTION_PARAM_ANGLE(angle, 4); + ACTION_PARAM_INT(flags, 5); + ACTION_PARAM_STATE(success_state, 6); + + AActor *reference = COPY_AAPTR(self, destination_selector); + + //If there is no actor to warp to, fail. + if (!reference) + { + ACTION_SET_RESULT(false); + return; + } + + AActor *caller = self; + + if (flags & WARPF_MOVEPTR) + { + AActor *temp = reference; + reference = caller; + caller = temp; + } + + fixed_t oldx = caller->x; + fixed_t oldy = caller->y; + fixed_t oldz = caller->z; + + if (!(flags & WARPF_ABSOLUTEANGLE)) + { + angle += (flags & WARPF_USECALLERANGLE) ? caller->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 + + caller->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 + caller->SetOrigin( + caller->x, + caller->y, + caller->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 + caller->z = caller->floorz; + } + } + else + { + caller->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) + { + caller->SetOrigin(xofs, yofs, caller->floorz + zofs); + } + else + { + caller->SetOrigin(xofs, yofs, zofs); + } + } + + if ((flags & WARPF_NOCHECKPOSITION) || P_TestMobjLocation(caller)) + { + if (flags & WARPF_TESTONLY) + { + caller->SetOrigin(oldx, oldy, oldz); + } + else + { + caller->angle = angle; + + if (flags & WARPF_STOP) + { + caller->velx = 0; + caller->vely = 0; + caller->velz = 0; + } + + if (flags & WARPF_WARPINTERPOLATION) + { + caller->PrevX += caller->x - oldx; + caller->PrevY += caller->y - oldy; + caller->PrevZ += caller->z - oldz; + } + else if (flags & WARPF_COPYINTERPOLATION) + { + caller->PrevX = caller->x + reference->PrevX - reference->x; + caller->PrevY = caller->y + reference->PrevY - reference->y; + caller->PrevZ = caller->z + reference->PrevZ - reference->z; + } + else if (!(flags & WARPF_INTERPOLATE)) + { + caller->PrevX = caller->x; + caller->PrevY = caller->y; + caller->PrevZ = caller->z; + } + + if ((flags & WARPF_BOB) && (reference->flags2 & MF2_FLOATBOB)) + { + caller->z += reference->GetBobOffset(); + } + } + + + 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; + } + + ACTION_SET_RESULT(true); + } + else + { + caller->SetOrigin(oldx, oldy, oldz); + ACTION_SET_RESULT(false); + } + +} + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(arg1, 1); + ACTION_PARAM_INT(arg2, 2); + ACTION_PARAM_INT(arg3, 3); + ACTION_PARAM_INT(arg4, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteWithResult, NULL, self, false, -scriptname, arg1, arg2, arg3, arg4); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_Execute, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedExecuteAlways) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(arg3, 4); + + bool res = !!P_ExecuteSpecial(ACS_ExecuteAlways, NULL, self, false, -scriptname, mapnum, arg1, arg2, arg3); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecute) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecute, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedLockedExecuteDoor) +{ + ACTION_PARAM_START(5); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + ACTION_PARAM_INT(arg1, 2); + ACTION_PARAM_INT(arg2, 3); + ACTION_PARAM_INT(lock, 4); + + bool res = !!P_ExecuteSpecial(ACS_LockedExecuteDoor, NULL, self, false, -scriptname, mapnum, arg1, arg2, lock); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedSuspend) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Suspend, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, ACS_NamedTerminate) +{ + ACTION_PARAM_START(2); + + ACTION_PARAM_NAME(scriptname, 0); + ACTION_PARAM_INT(mapnum, 1); + + bool res = !!P_ExecuteSpecial(ACS_Terminate, NULL, self, false, -scriptname, mapnum, 0, 0, 0); + + ACTION_SET_RESULT(res); +} + + +//========================================================================== +// +// 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) +{ + ACTION_PARAM_START(7); + ACTION_PARAM_CLASS(item, 0); + ACTION_PARAM_FIXED(distance, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_INT(amount, 3); + + // We need a valid item, valid targets, and a valid range + if (item == NULL || (flags & RGF_MASK) == 0 || distance <= 0) + { + return; + } + 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 (); + } + } + } +} + + +//========================================================================== +// +// A_SetTics +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetTics) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(tics_to_set, 0); + + 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; + } + } + } + // Just set tics for self. + self->tics = tics_to_set; +} + +//========================================================================== +// +// A_SetDamageType +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetDamageType) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_NAME(damagetype, 0); + + self->DamageType = damagetype; +} + +//========================================================================== +// +// A_DropItem +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropItem) +{ + ACTION_PARAM_START(3); + ACTION_PARAM_CLASS(spawntype, 0); + ACTION_PARAM_INT(amount, 1); + ACTION_PARAM_INT(chance, 2); + + P_DropItem(self, spawntype, amount, chance); +} + +//========================================================================== +// +// A_SetSpeed +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetSpeed) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_FIXED(speed, 0); + + self->Speed = speed; +} + +static bool DoCheckSpecies(AActor *mo, FName species, bool exclude) +{ + return (!(species) || mo->Species == NAME_None || (species && ((exclude) ? (mo->Species != species) : (mo->Species == species)))); +} + +static bool DoCheckFilter(AActor *mo, const PClass *filter, bool exclude) +{ + const PClass *c1 = mo->GetClass(); + return (!(filter) || (filter == NULL) || (filter && ((exclude) ? (c1 != filter) : (c1 == filter)))); +} + +//=========================================================================== +// +// Common A_Damage handler +// +// A_Damage* (int amount, str damagetype, int flags, str filter, str species) +// Damages the specified actor by the specified amount. Negative values heal. +// Flags: See below. +// Filter: Specified actor is the only type allowed to be affected. +// Species: Specified species is the only type allowed to be affected. +// +// Examples: +// A_Damage(20,"Normal",DMSS_FOILINVUL,0,"DemonicSpecies") <--Only actors +// with a species "DemonicSpecies" will be affected. Use 0 to not filter by actor. +// +//=========================================================================== + +enum DMSS +{ + DMSS_FOILINVUL = 1, //Foil invulnerability + DMSS_AFFECTARMOR = 2, //Make it affect armor + DMSS_KILL = 4, //Damages them for their current health + DMSS_NOFACTOR = 8, //Ignore DamageFactors + DMSS_FOILBUDDHA = 16, //Can kill actors with Buddha flag, except the player. + DMSS_NOPROTECT = 32, //Ignores PowerProtection entirely + DMSS_EXFILTER = 64, //Changes filter into a blacklisted class instead of whitelisted. + DMSS_EXSPECIES = 128, // ^ but with species instead. + DMSS_EITHER = 256, //Allow either type or species to be affected. +}; + +static void DoDamage(AActor *dmgtarget, AActor *self, int amount, FName DamageType, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(dmgtarget, filter, (flags & DMSS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(dmgtarget, species, (flags & DMSS_EXSPECIES) ? true : false); + if ((flags & DMSS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + 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) + P_DamageMobj(dmgtarget, self, self, amount, DamageType, dmgFlags); //Should wind up passing them through just fine. + + else if (amount < 0) + { + amount = -amount; + P_GiveBody(dmgtarget, amount); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSelf) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + DoDamage(self, self, amount, DamageType, flags, filter, species); +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTarget) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->target != NULL) + { + DoDamage(self->target, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageTracer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->tracer != NULL) + { + DoDamage(self->tracer, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageMaster) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + if (self->master != NULL) + { + DoDamage(self->master, self, amount, DamageType, flags, filter, species); + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageChildren) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + TThinkerIterator it; + AActor * mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoDamage(mo, self, amount, DamageType, flags, filter, species); + } + } +} + +//=========================================================================== +// +// +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DamageSiblings) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(amount, 0); + ACTION_PARAM_NAME(DamageType, 1); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_CLASS(filter, 3); + ACTION_PARAM_NAME(species, 4); + + 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, filter, species); + } + } + } +} + + +//=========================================================================== +// +// A_Kill*(damagetype, int flags) +// +//=========================================================================== +enum KILS +{ + KILS_FOILINVUL = 1 << 0, + KILS_KILLMISSILES = 1 << 1, + KILS_NOMONSTERS = 1 << 2, + KILS_FOILBUDDHA = 1 << 3, + KILS_EXFILTER = 1 << 4, + KILS_EXSPECIES = 1 << 5, + KILS_EITHER = 1 << 6, +}; + +static void DoKill(AActor *killtarget, AActor *self, FName damagetype, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(killtarget, filter, (flags & KILS_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(killtarget, species, (flags & KILS_EXSPECIES) ? true : false); + if ((flags & KILS_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) //Check this first. I think it'll save the engine a lot more time this way. + { + 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) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->target != NULL) + { + DoKill(self->target, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillTracer(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillTracer) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->tracer != NULL) + { + DoKill(self->tracer, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillMaster(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillMaster) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + DoKill(self->master, self, damagetype, flags, filter, species); + } +} + +//=========================================================================== +// +// A_KillChildren(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillChildren) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + while ( (mo = it.Next()) ) + { + if (mo->master == self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_KillSiblings(damagetype, int flags) +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KillSiblings) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_NAME(damagetype, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + TThinkerIterator it; + AActor *mo; + + if (self->master != NULL) + { + while ( (mo = it.Next()) ) + { + if (mo->master == self->master && mo != self) + { + DoKill(mo, self, damagetype, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// DoRemove +// +//=========================================================================== + +enum RMVF_flags +{ + RMVF_MISSILES = 1 << 0, + RMVF_NOMONSTERS = 1 << 1, + RMVF_MISC = 1 << 2, + RMVF_EVERYTHING = 1 << 3, + RMVF_EXFILTER = 1 << 4, + RMVF_EXSPECIES = 1 << 5, + RMVF_EITHER = 1 << 6, +}; + +static void DoRemove(AActor *removetarget, int flags, const PClass *filter, FName species) +{ + bool filterpass = DoCheckFilter(removetarget, filter, (flags & RMVF_EXFILTER) ? true : false), + speciespass = DoCheckSpecies(removetarget, species, (flags & RMVF_EXSPECIES) ? true : false); + if ((flags & RMVF_EITHER) ? (filterpass || speciespass) : (filterpass && speciespass)) + { + 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) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->target != NULL) + { + DoRemove(self->target, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveTracer +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveTracer) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->tracer != NULL) + { + DoRemove(self->tracer, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveMaster +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveMaster) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_INT(flags, 0); + ACTION_PARAM_CLASS(filter, 1); + ACTION_PARAM_NAME(species, 2); + + if (self->master != NULL) + { + DoRemove(self->master, flags, filter, species); + } +} + +//=========================================================================== +// +// A_RemoveChildren +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveChildren) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + + while ((mo = it.Next()) != NULL) + { + if (mo->master == self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } +} + +//=========================================================================== +// +// A_RemoveSiblings +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RemoveSiblings) +{ + TThinkerIterator it; + AActor *mo; + ACTION_PARAM_START(4); + ACTION_PARAM_BOOL(removeall, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + if (self->master != NULL) + { + while ((mo = it.Next()) != NULL) + { + if (mo->master == self->master && mo != self && (mo->health <= 0 || removeall)) + { + DoRemove(mo, flags, filter, species); + } + } + } +} + +//=========================================================================== +// +// A_Remove +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Remove) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(removee, 0); + ACTION_PARAM_INT(flags, 1); + ACTION_PARAM_CLASS(filter, 2); + ACTION_PARAM_NAME(species, 3); + + AActor *reference = COPY_AAPTR(self, removee); + if (reference != NULL) + { + DoRemove(reference, flags, filter, species); + } +} + +//=========================================================================== +// +// 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) +{ + ACTION_PARAM_START(2); + ACTION_PARAM_NAME(oldpos, 0); + ACTION_PARAM_NAME(newpos, 1); + const PClass *check = PClass::FindClass(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::FindClass(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; +} + +//=========================================================================== +// +// A_SwapTeleFog +// +// Switches the source and dest telefogs around. +//=========================================================================== + +DEFINE_ACTION_FUNCTION(AActor, A_SwapTeleFog) +{ + if ((self->TeleFogSourceType != self->TeleFogDestType)) //Does nothing if they're the same. + { + const PClass *temp = self->TeleFogSourceType; + self->TeleFogSourceType = self->TeleFogDestType; + self->TeleFogDestType = temp; + } +} + +//=========================================================================== +// +// A_SetFloatBobPhase +// +// Changes the FloatBobPhase of the +//=========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetFloatBobPhase) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(bob, 0); + + //Respect float bob phase limits. + if (self && (bob >= 0 && bob <= 63)) + self->FloatBobPhase = bob; +} + +//=========================================================================== +// +// A_SetRipperLevel(int level) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipperLevel) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(level, 0); + self->RipperLevel = level; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMin) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(min, 1); + self->RipLevelMin = min; +} + +//=========================================================================== +// +// A_SetRipMin(int min) +// +// Sets the ripper level/requirement of the calling actor. +// Also sets the minimum and maximum levels to rip through. +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetRipMax) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(max, 1); + self->RipLevelMax = max; +} \ No newline at end of file diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 013415cc7..fbe28fb5b 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 773f7ffac..7bd10b38d 100644 --- a/src/thingdef/thingdef_exp.cpp +++ b/src/thingdef/thingdef_exp.cpp @@ -371,6 +371,35 @@ static FxExpression *ParseExpression0 (FScanner &sc, const PClass *cls) return new FxRandom(rng, min, max, sc); } + else if (sc.CheckToken(TK_RandomPick)) + { + 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('('); + + for (;;) + { + FxExpression *expr = ParseExpressionM(sc, cls); + list.Push(expr); + if (sc.CheckToken(')')) + break; + sc.MustGetToken(','); + } + return new FxRandomPick(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 77af37f53..807ffcd87 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -559,6 +559,27 @@ public: // //========================================================================== +class FxRandomPick : public FxExpression +{ +protected: + FRandom * rng; + TDeletingArray min; + +public: + + FxRandomPick(FRandom *, TArray mi, const FScriptPosition &pos); + ~FxRandomPick(); + FxExpression *Resolve(FCompileContext&); + + ExpVal EvalExpression(AActor *self); +}; + +//========================================================================== +// +// +// +//========================================================================== + class FxFRandom : public FxRandom { public: diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index 7dbf5ec36..08b261bbf 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -89,6 +89,7 @@ DEFINE_MEMBER_VARIABLE(radius, AActor) DEFINE_MEMBER_VARIABLE(reactiontime, AActor) DEFINE_MEMBER_VARIABLE(meleerange, AActor) DEFINE_MEMBER_VARIABLE(Speed, AActor) +DEFINE_MEMBER_VARIABLE(roll, AActor) //========================================================================== @@ -1691,6 +1692,73 @@ ExpVal FxRandom::EvalExpression (AActor *self) return val; } +//========================================================================== +// +// +// +//========================================================================== +FxRandomPick::FxRandomPick(FRandom * r, TArray mi, const FScriptPosition &pos) +: FxExpression(pos) +{ + for (unsigned int index = 0; index < mi.Size(); index++) + { + min.Push(new FxIntCast(mi[index])); + } + rng = r; + ValueType = VAL_Int; +} + +//========================================================================== +// +// +// +//========================================================================== + +FxRandomPick::~FxRandomPick() +{ +} + +//========================================================================== +// +// +// +//========================================================================== + +FxExpression *FxRandomPick::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + for (unsigned int index = 0; index < min.Size(); index++) + { + RESOLVE(min[index], ctx); + ABORT(min[index]); + } + return this; +}; + + +//========================================================================== +// +// +// +//========================================================================== + +ExpVal FxRandomPick::EvalExpression(AActor *self) +{ + ExpVal val; + val.Type = VAL_Int; + int max = min.Size(); + if (max > 0) + { + int select = (*rng)(max); + val.Int = min[select]->EvalExpression(self).GetInt(); + } + else + { + val.Int = (*rng)(); + } + return val; +} + //========================================================================== // // diff --git a/src/thingdef/thingdef_function.cpp b/src/thingdef/thingdef_function.cpp index 2858e9a86..fcc936017 100644 --- a/src/thingdef/thingdef_function.cpp +++ b/src/thingdef/thingdef_function.cpp @@ -43,6 +43,8 @@ #include "tarray.h" #include "thingdef.h" #include "thingdef_exp.h" +#include "actor.h" +#include "actorptrselect.h" static TMap CreatorMap; @@ -185,3 +187,110 @@ public: GLOBALFUNCTION_ADDER(Sqrt); +//========================================================================== +// +// Function: checkclass +// +//========================================================================== + +class FxGlobalFunctionCall_CheckClass : public FxGlobalFunctionCall +{ + public: + GLOBALFUNCTION_DEFINE(CheckClass); + + FxExpression *Resolve(FCompileContext& ctx) + { + CHECKRESOLVED(); + + if (!ResolveArgs(ctx, 1, 3, false)) + return NULL; + + for (int i = ArgList->Size(); i > 1;) + { + if (!(*ArgList)[--i]->ValueType.isNumeric()) + { + ScriptPosition.Message(MSG_ERROR, "numeric value expected for parameter"); + delete this; + return NULL; + } + } + + switch ((*ArgList)[0]->ValueType.Type) + { + case VAL_Class: case VAL_Name:break; + default: + ScriptPosition.Message(MSG_ERROR, "actor class expected for parameter"); + delete this; + return NULL; + } + + ValueType = VAL_Float; + return this; + } + + ExpVal EvalExpression(AActor *self) + { + ExpVal ret; + ret.Type = VAL_Int; + + const PClass * checkclass; + { + ExpVal v = (*ArgList)[0]->EvalExpression(self); + checkclass = v.GetClass(); + if (!checkclass) + { + checkclass = PClass::FindClass(v.GetName()); + if (!checkclass) { ret.Int = 0; return ret; } + } + } + + bool match_superclass = false; + int pick_pointer = AAPTR_DEFAULT; + + switch (ArgList->Size()) + { + case 3: match_superclass = (*ArgList)[2]->EvalExpression(self).GetBool(); + case 2: pick_pointer = (*ArgList)[1]->EvalExpression(self).GetInt(); + } + + self = COPY_AAPTR(self, pick_pointer); + if (!self){ ret.Int = 0; return ret; } + ret.Int = match_superclass ? checkclass->IsAncestorOf(self->GetClass()) : checkclass == self->GetClass(); + return ret; + } + }; + +GLOBALFUNCTION_ADDER(CheckClass); + +//========================================================================== +// +// Function: ispointerequal +// +//========================================================================== + +class FxGlobalFunctionCall_IsPointerEqual : public FxGlobalFunctionCall +{ + public: + GLOBALFUNCTION_DEFINE(IsPointerEqual); + + FxExpression *Resolve(FCompileContext& ctx) + { + CHECKRESOLVED(); + + if (!ResolveArgs(ctx, 2, 2, true)) + return NULL; + + ValueType = VAL_Int; + return this; + } + + ExpVal EvalExpression(AActor *self) + { + ExpVal ret; + ret.Type = VAL_Int; + ret.Int = COPY_AAPTR(self, (*ArgList)[0]->EvalExpression(self).GetInt()) == COPY_AAPTR(self, (*ArgList)[1]->EvalExpression(self).GetInt()); + return ret; + } +}; + +GLOBALFUNCTION_ADDER(IsPointerEqual); \ No newline at end of file diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index b5fdc8e42..677979276 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -1416,6 +1416,65 @@ 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,"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, "TeleportFog"); +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(ripperlevel, I, Actor) +{ + PROP_INT_PARM(id, 0); + if (id < 0) + { + I_Error ("RipperLevel must not be negative"); + } + defaults->RipperLevel = id; +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(riplevelmin, I, Actor) +{ + PROP_INT_PARM(id, 0); + if (id < 0) + { + I_Error ("RipLevelMin must not be negative"); + } + defaults->RipLevelMin = id; +} + +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(riplevelmax, I, Actor) +{ + PROP_INT_PARM(id, 0); + if (id < 0) + { + I_Error ("RipLevelMax must not be negative"); + } + defaults->RipLevelMax = id; +} + //========================================================================== // // Special inventory properties @@ -2050,6 +2109,11 @@ DEFINE_CLASS_PROPERTY_PREFIX(powerup, color, C_f, Inventory) *pBlendColor = MakeSpecialColormap(v); return; } + else if (!stricmp(name, "none") && info->Class->IsDescendantOf(RUNTIME_CLASS(APowerupGiver))) + { + *pBlendColor = MakeSpecialColormap(65535); + return; + } color = V_GetColor(NULL, name); } diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index 6a28e54cd..eb740aad2 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -332,7 +332,8 @@ do_stop: goto endofstate; } - PSymbol *sym = bag.Info->Class->Symbols.FindSymbol (FName(sc.String, true), true); + FName funcname = FName(sc.String, true); + PSymbol *sym = bag.Info->Class->Symbols.FindSymbol (funcname, true); if (sym != NULL && sym->SymbolType == SYM_ActionFunction) { PSymbolActionFunction *afd = static_cast(sym); @@ -434,7 +435,7 @@ do_stop: sc.MustGetString(); if (sc.Compare("(")) { - sc.ScriptError("You cannot pass parameters to '%s'\n",sc.String); + sc.ScriptError("You cannot pass parameters to '%s'\n", funcname.GetChars()); } sc.UnGet(); } diff --git a/src/timidity/instrum.cpp b/src/timidity/instrum.cpp index 5ab55201e..7454ca7a4 100644 --- a/src/timidity/instrum.cpp +++ b/src/timidity/instrum.cpp @@ -485,7 +485,7 @@ fail: sample_t *tmp; for (i = sp->data_length, tmp = sp->data; i; --i) { - a = abs(*tmp++); + a = fabsf(*tmp++); if (a > maxamp) maxamp = a; } diff --git a/src/v_text.cpp b/src/v_text.cpp index 64b95c041..cad9bb5d2 100644 --- a/src/v_text.cpp +++ b/src/v_text.cpp @@ -128,7 +128,6 @@ void DCanvas::DrawTextV(FFont *font, int normalcolor, int x, int y, const char * { va_list *more_p; DWORD data; - void *ptrval; switch (tag) { @@ -150,15 +149,9 @@ void DCanvas::DrawTextV(FFont *font, int normalcolor, int x, int y, const char * // We don't handle these. :( case DTA_DestWidth: case DTA_DestHeight: - *(DWORD *)tags = TAG_IGNORE; - data = va_arg (tags, DWORD); - break; - - // Translation is specified explicitly by the text. case DTA_Translation: - *(DWORD *)tags = TAG_IGNORE; - ptrval = va_arg (tags, void*); - break; + assert("Bad parameter for DrawText" && false); + return; case DTA_CleanNoMove_1: boolval = va_arg (tags, INTBOOL); diff --git a/src/version.h b/src/version.h index c8917212d..09b830438 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 4512 +#define SAVEVER 4518 #define SAVEVERSTRINGIFY2(x) #x #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x) diff --git a/src/wi_stuff.cpp b/src/wi_stuff.cpp index 2ab93f090..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); @@ -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); @@ -2010,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/zstring.h b/src/zstring.h index 1736f3421..0104020fa 100644 --- a/src/zstring.h +++ b/src/zstring.h @@ -47,6 +47,24 @@ #define PRINTFISH(x) #endif +#ifdef __clang__ +#define IGNORE_FORMAT_PRE \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wformat-invalid-specifier\"") \ + _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"") +#define IGNORE_FORMAT_POST _Pragma("GCC diagnostic pop") +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 6))) +#define IGNORE_FORMAT_PRE \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wformat=\"") \ + _Pragma("GCC diagnostic ignored \"-Wformat-extra-args\"") +#define IGNORE_FORMAT_POST _Pragma("GCC diagnostic pop") +#else +#define IGNORE_FORMAT_PRE +#define IGNORE_FORMAT_POST +#endif + + struct FStringData { unsigned int Len; // Length of string, excluding terminating null 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 e66226c1e..b2fa80c30 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -26,6 +26,11 @@ ACTOR Actor native //: Thinker DesignatedTeam 255 PainType Normal DeathType Normal + TeleFogSourceType "TeleportFog" + TeleFogDestType "TeleportFog" + RipperLevel 0 + RipLevelMin 0 + RipLevelMax 0 // Variables for the expression evaluator // NOTE: fixed_t and angle_t are only used here to ensure proper conversion @@ -63,6 +68,7 @@ ACTOR Actor native //: Thinker native int reactiontime; native fixed_t meleerange; native fixed_t speed; + native angle_t roll; // Meh, MBF redundant functions. Only for DeHackEd support. action native A_Turn(float angle = 0); @@ -70,7 +76,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 = 90, float distance = 16*64, float vrange = 32, int damage = 0); action native A_Pain(); action native A_NoBlocking(); action native A_XScream(); @@ -186,6 +192,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(); @@ -201,10 +208,10 @@ ACTOR Actor native //: Thinker action native A_StopSoundEx(coerce name slot); action native A_SeekerMissile(int threshold, int turnmax, int flags = 0, int chance = 50, int distance = 10); action native A_Jump(int chance = 256, state label, ...); - 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 spread_xy, float spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0); + action native A_CustomMissile(class missiletype, float spawnheight = 32, int spawnofs_xy = 0, float angle = 0, int flags = 0, float pitch = 0, int ptr = AAPTR_TARGET); + action native A_CustomBulletAttack(float spread_xy, float spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0, int ptr = AAPTR_TARGET); action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, bool aim = false, float maxdiff = 0, class pufftype = "BulletPuff", float spread_xy = 0, float 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); @@ -221,9 +228,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); @@ -234,20 +241,14 @@ 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_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); action native A_BasicAttack(int meleedamage, sound meleesound, class missiletype, float missileheight); - action native A_Teleport(state teleportstate = "", class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, float mindist = 128, float maxdist = 0); + action native A_Teleport(state teleportstate = "", class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, float mindist = 0, float maxdist = 0); action native A_Warp(int ptr_destination, float xofs = 0, float yofs = 0, float zofs = 0, float angle = 0, int flags = 0, state success_state = ""); action native A_ThrowGrenade(class itemtype, float zheight = 0, float xyvel = 0, float zvel = 0, bool useammo = true); action native A_Weave(int xspeed, int yspeed, float xdist, float ydist); @@ -275,9 +276,6 @@ 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 fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_JumpIfInTargetLOS (state label, float 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_SelectWeapon(class whichweapon); action native A_Punch(); action native A_Feathers(); @@ -293,6 +291,7 @@ ACTOR Actor native //: Thinker action native A_MonsterRefire(int chance, state label); action native A_SetAngle(float angle = 0, int flags = 0); action native A_SetPitch(float pitch, int flags = 0); + action native A_SetRoll(float roll, int flags = 0); action native A_ScaleVelocity(float scale); action native A_ChangeVelocity(float x = 0, float y = 0, float z = 0, int flags = 0); action native A_SetArg(int pos, int value); @@ -304,9 +303,36 @@ ACTOR Actor native //: Thinker 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, class filter = "None", name species = "None"); + action native A_DamageTarget(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_DamageMaster(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_DamageTracer(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_DamageChildren(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_DamageSiblings(int amount, name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_KillTarget(name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_KillMaster(name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_KillTracer(name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_KillChildren(name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_KillSiblings(name damagetype = "none", int flags = 0, class filter = "None", name species = "None"); + action native A_RemoveTarget(int flags = 0, class filter = "None", name species = "None"); + action native A_RemoveMaster(int flags = 0, class filter = "None", name species = "None"); + action native A_RemoveTracer(int flags = 0, class filter = "None", name species = "None"); + action native A_RemoveChildren(bool removeall = false, int flags = 0, class filter = "None", name species = "None"); + action native A_RemoveSiblings(bool removeall = false, int flags = 0, class filter = "None", name species = "None"); + action native A_Remove(int removee, int flags = 0, class filter = "None", name species = "None"); + 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_SetFloatBobPhase(int bob); + action native A_SetRipperLevel(int level); + action native A_SetRipMin(int min); + action native A_SetRipMax(int max); - action native A_CheckSightOrRange(float distance, state label); - action native A_CheckRange(float distance, state label); + action native A_CheckSightOrRange(float distance, state label, bool two_dimension = false); + action native A_CheckRange(float distance, state label, bool two_dimension = false); action native A_RearrangePointers(int newtarget, int newmaster = AAPTR_DEFAULT, int newtracer = AAPTR_DEFAULT, int flags=0); action native A_TransferPointer(int ptr_source, int ptr_recepient, int sourcefield, int recepientfield=AAPTR_DEFAULT, int flags=0); diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index bf998e6b0..074953afe 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -1,371 +1,452 @@ - -// 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; - -// 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; -const int SXF_TRANSFERALPHA = 262144; -const int SXF_TRANSFERRENDERSTYLE = 524288; - -// 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; - - -// This is only here to provide one global variable for testing. -native int testglobalvar; + +// 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; +Const Int WARPF_BOB = 0x800; +Const Int WARPF_MOVEPTR = 0x1000; + +// flags for A_SetPitch/SetAngle/SetRoll +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 +enum +{ + KILS_FOILINVUL = 0x00000001, + KILS_KILLMISSILES = 0x00000002, + KILS_NOMONSTERS = 0x00000004, + KILS_FOILBUDDHA = 0x00000008, + KILS_EXFILTER = 0x00000010, + KILS_EXSPECIES = 0x00000020, + KILS_EITHER = 0x00000040, +}; + +// Flags for A_Damage (Master/Target/Tracer/Children/Siblings/Self) series +enum +{ + DMSS_FOILINVUL = 0x00000001, + DMSS_AFFECTARMOR = 0x00000002, + DMSS_KILL = 0x00000004, + DMSS_NOFACTOR = 0x00000008, + DMSS_FOILBUDDHA = 0x00000010, + DMSS_NOPROTECT = 0x00000020, + DMSS_EXFILTER = 0x00000040, + DMSS_EXSPECIES = 0x00000080, + DMSS_EITHER = 0x00000100, +}; + +// 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 = 0x00000001, + RMVF_NOMONSTERS = 0x00000002, + RMVF_MISC = 0x00000004, + RMVF_EVERYTHING = 0x00000008, + RMVF_EXFILTER = 0x00000010, + RMVF_EXSPECIES = 0x00000020, + RMVF_EITHER = 0x00000040, +}; + +// Flags for A_Fade* +enum +{ + FTF_REMOVE = 1 << 0, + FTF_CLAMP = 1 << 1, +}; + + +// This is only here to provide one global variable for testing. +native int testglobalvar; 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 e293282b8..7860b9387 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 spread_xy, float 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, int useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float spread_xy = 0, float 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 spread_xy = 2.8125, float 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 spread_xy = 2.8125, float 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 30c54c556..d2bb85fd2 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -389,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/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 0881bca31..a38cfd545 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" } @@ -1500,6 +1514,7 @@ OptionValue OplCores 0, "MAME OPL2" 1, "DOSBox OPL3" 2, "Java OPL3" + 3, "Nuked OPL3" } OptionMenu AdvSoundOptions @@ -1603,6 +1618,10 @@ OptionMenu VideoModeMenu Title "VIDEO MODE" Option "Fullscreen", "fullscreen", "YesNo" + IfOption(Mac) + { + Option "Retina/HiDPI support", "vid_hidpi", "YesNo" + } Option "Aspect ratio", "menu_screenratios", "Ratios" Option "Force aspect ratio", "vid_aspect", "ForceRatios" Option "Enable 5:4 aspect ratio","vid_tft", "YesNo" @@ -1623,3 +1642,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/zdoom.vcproj b/zdoom.vcproj index d29165742..7a10b4fbf 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -2644,6 +2644,14 @@ RelativePath=".\src\oplsynth\opl_mus_player.h" > + + + +