diff --git a/CMakeLists.txt b/CMakeLists.txt index 78264488d..b541a9c20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,42 +64,39 @@ set( CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${REL_C_FLAGS}" ) set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${REL_C_FLAGS}" ) set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${DEB_C_FLAGS} -D_DEBUG" ) -if( CMAKE_COMPILER_IS_GNUCXX AND PROFILE ) - set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg" ) - set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg" ) - set( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -pg" ) - set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -pg" ) -endif( CMAKE_COMPILER_IS_GNUCXX AND PROFILE ) - -if( ZLIB_FOUND ) +option(FORCE_INTERNAL_ZLIB "Use internal zlib") +option(FORCE_INTERNAL_JPEG "Use internal jpeg") +option(FORCE_INTERNAL_BZIP2 "Use internal bzip2") + +if( ZLIB_FOUND AND NOT FORCE_INTERNAL_ZLIB ) message( STATUS "Using system zlib" ) -else( ZLIB_FOUND ) +else( ZLIB_FOUND AND NOT FORCE_INTERNAL_ZLIB ) message( STATUS "Using internal zlib" ) add_subdirectory( zlib ) set( ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib ) set( ZLIB_LIBRARIES z ) set( ZLIB_LIBRARY z ) -endif( ZLIB_FOUND ) +endif( ZLIB_FOUND AND NOT FORCE_INTERNAL_ZLIB ) -if( JPEG_FOUND ) +if( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG ) message( STATUS "Using system jpeg library" ) -else( JPEG_FOUND ) +else( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG ) message( STATUS "Using internal jpeg library" ) add_subdirectory( jpeg-6b ) set( JPEG_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/jpeg-6b ) set( JPEG_LIBRARIES jpeg ) set( JPEG_LIBRARY jpeg ) -endif( JPEG_FOUND ) +endif( JPEG_FOUND AND NOT FORCE_INTERNAL_JPEG ) -if( BZIP2_FOUND ) +if( BZIP2_FOUND AND NOT FORCE_INTERNAL_BZIP2 ) message( STATUS "Using system bzip2 library" ) -else( BZIP2_FOUND ) +else( BZIP2_FOUND AND NOT FORCE_INTERNAL_BZIP2 ) message( STATUS "Using internal bzip2 library" ) add_subdirectory( bzip2 ) set( BZIP2_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/bzip2" ) set( BZIP2_LIBRARIES bz2 ) set( BZIP2_LIBRARY bz2 ) -endif( BZIP2_FOUND) +endif( BZIP2_FOUND AND NOT FORCE_INTERNAL_BZIP2 ) set( LZMA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/lzma/C" ) diff --git a/FindFluidSynth.cmake b/FindFluidSynth.cmake new file mode 100644 index 000000000..7d5cb6a8e --- /dev/null +++ b/FindFluidSynth.cmake @@ -0,0 +1,23 @@ +# - Find fluidsynth +# Find the native fluidsynth includes and library +# +# FLUIDSYNTH_INCLUDE_DIR - where to find fluidsynth.h +# FLUIDSYNTH_LIBRARIES - List of libraries when using fluidsynth. +# FLUIDSYNTH_FOUND - True if fluidsynth found. + + +IF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + # Already in cache, be silent + SET(FluidSynth_FIND_QUIETLY TRUE) +ENDIF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + +FIND_PATH(FLUIDSYNTH_INCLUDE_DIR fluidsynth.h) + +FIND_LIBRARY(FLUIDSYNTH_LIBRARIES NAMES fluidsynth ) +MARK_AS_ADVANCED( FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR ) + +# handle the QUIETLY and REQUIRED arguments and set FLUIDSYNTH_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FluidSynth DEFAULT_MSG FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR) + diff --git a/specs/fmod_version.txt b/specs/fmod_version.txt index a65aec918..c285c50b9 100644 --- a/specs/fmod_version.txt +++ b/specs/fmod_version.txt @@ -1 +1,3 @@ This version of ZDoom must be compiled with any version between 4.22 and 4.28 inclusive. +Use of the latest 4.26 is recommended though due to technical issues with 4.28. + diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 15512d9fb..fe6897d9f 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -1,5 +1,5 @@ =============================================================================== -Universal Doom Map Format ZDoom extensions v1.8 - 16.07.2009 +Universal Doom Map Format ZDoom extensions v1.10 - 25.04.2010 Copyright (c) 2008 Christoph Oelckers. @@ -38,7 +38,8 @@ between the TEXTMAP and ENDMAP lumps: BEHAVIOR = contains compiled ACS code DIALOGUE = contains compiled Strife conversation scripts. - ZNODES = Nodes (must be stored as compressed GL nodes) + ZNODES = Nodes (must be stored as extended GL nodes. Compression is allowed + but deprecated for portability reasons.) BLOCKMAP = blockmap. It is recommended not to include this lump in UDMF maps. REJECT = reject table. Recommended use is for special effects only. @@ -88,6 +89,7 @@ Note: All fields default to false unless mentioned otherwise. 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. @@ -159,6 +161,9 @@ Note: All fields default to false unless mentioned otherwise. nofallingdamage = ; // Falling damage is disabled in this sector dropactors = ; // Actors drop with instantly moving floors (*) norespawn = ; // Players can not respawn in this sector + soundsequence = ; // The sound sequence to play when this sector moves. Placing a + // sound sequence thing in the sector will override this property. + hidden = ; // if true this sector will not be drawn on the textured automap. * Note about dropactors @@ -176,6 +181,8 @@ Note: All fields default to false unless mentioned otherwise. class# = // Unlike the base spec, # can range from 1-8. // 8 is the maximum amount of classes the class // menu can display. + conversation = // Assigns a conversation dialogue to this thing. + // Parameter is the conversation ID, 0 meaning none. } @@ -255,6 +262,21 @@ Added sidedef scaling properties and side specific clipmidtex and wrapmidtex. Added NoDecals sidedef option Fixed conversion specifications for TranslucentLine special. +1.9 17.04.2010 +Changed node specifications to deprecate compression of node lump. + +1.10 25.04.2010 +Added 'playeruseback' line trigger flag. + +1.11 07.08.2010 +Added 'soundsequnce' sector property. + +1.12 22.08.2010 +Added 'conversation' thing property. + +1.13 29.08.2010 +Added 'hidden' sector property. + =============================================================================== EOF =============================================================================== diff --git a/specs/usdf.txt b/specs/usdf.txt new file mode 100644 index 000000000..fc955f02d --- /dev/null +++ b/specs/usdf.txt @@ -0,0 +1,158 @@ +=============================================================================== +Universal Strife Dialog Format Specification v2.0 - 08/20/10 + +Written by Braden "Blzut3" Obrzut - admin@maniacsvault.net + +Defined with input from: + +CodeImp +Gez +Graf Zahl +Quasar +et al. + + Copyright (c) 2010 Braden Obrzut. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + +The grammar and syntax is similar to that of UDMF. A compliant UDMF parser +should be applyable to the USDF. However, it will need to be capable of +handling sub-blocks. Unknown sub-blocks should be skipped. + +======================================= +II. Implementation Semantics +======================================= + +------------------------------------ +II.A : Storage and Retrieval of Data +------------------------------------ + +This is the same as in UDMF. + +----------------------------------- +II.B : Storage Within Archive Files +----------------------------------- + +There are two options for the USDF lump placement. This can either be a part +of the UDMF lump list or standalone. If used stand alone the lump name +DIALOGXY is used corresponding with MAPXY. For UDMF the lump shall be called +"DIALOGUE". + +-------------------------------- +II.C : Implementation Dependence +-------------------------------- + +USDF also implements the namespace statement. This has all the same +requirements as UDMF. + +======================================= +III. Standardized Fields +======================================= + +The following are required for all USDF complient implementations. Like UDMF, +any unknown field/function should be ignored and not treated as an error. + +NOTE: "mobj" refers to Strife's conversationIDs and not doom editor numbers or + Hexen's spawnids. A valid mobj value is any positive integer greater + than or equal to 1. + +--------------------- +III.A : Conversations +--------------------- + +Conversations are groups of pages that can be assigned to a particular object. +Implementors should preserve the IDs to allow for dynamic reassignment through +scripting although this is not a requirement. + +conversation // Starts a dialog. +{ + actor = ; // mobj for this conversation's actor. If previously + // used, this will override the previous conversation. + + page // Starts a new page. Pages are automatically numbered starting at 0. + { + name = ; // Name that goes in the upper left hand corner + panel = ; // Name of lump to render as the background. + voice = ; // Narration sound lump. + dialog = ; // Dialog of the page. + drop = ; // mobj for the object to drop if the actor is + // killed. + link = ; // Page to jump to if all ifitem conditions are + // satisified. + + // jumps to the specified page if the player has the specified amount + // or more of item in their inventory. This can be repeated as many + // times as the author wants, all conditions must be met for the + // jump to occur. + ifitem + { + item = ; // mobj of item to check. + amount = ; // amount required to be in inventory. + } + + // Choices shall be automatically numbered. + choice + { + text = ; // Name of the choice. + + // The amount of an item needed to successfully pick this option. + // This can be repeated, but only the first will be shown (provided + // diaplaycost is true). All costs must be satisfied for success. + cost + { + item = ; // Item that is required for this option. + amount = ; // Minimum amount of the item needed. + } + + displaycost = ; // Weather the cost should be + // displayed with the option. + // If no cost is specified this should + // be ignored. + yesmessage = ; // Text to add to console when choice + // is accepted. + nomessage = ; // Text to add to console when choice + // is denied. + + log = ; // LOG entry to use on success. + giveitem = ; // Gives the specified item upon + // success. + // The following are the same as the special for linedefs in UDMF. + // They are executed on success. + special = ; + arg0 = ; + arg1 = ; + arg2 = ; + arg3 = ; + arg4 = ; + + nextpage = ; // Sets the next page. + closedialog = ; // Should the dialog be closed upon + // selecting this choice? + // Default: false + } + } +} + +------------------------------- +III.B : Including Other Dialogs +------------------------------- + +Unlike the original Strife dialog format. The lump "SCRIPT00" should not be +included automatically. Instead the user must specify this behavior by using +the include function, which takes the name of a lump to include. Include only +needs to be available in the global scope and for compatibility reasons, must +include the result of the script and not act like a preprocessor statement. + +include = ; + +=============================================================================== +EOF +=============================================================================== diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt new file mode 100644 index 000000000..217354292 --- /dev/null +++ b/specs/usdf_zdoom.txt @@ -0,0 +1,89 @@ +=============================================================================== +ZDoom Strife Dialog Format ZDoom v1.1 - 23.08.2010 +based on Universal Strife Dialog Format v2.0 + + Copyright (c) 2010 Christoph Oelckers. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + + No changes. + +======================================= +II. Implementation Semantics +======================================= + +No changes. + +======================================= +III. Changes to USDF spec +======================================= + +ZDoom Strife Dialogue format implements the USDF base specification as described with one important change: +To take advantage of named actor classes any field specifying an actor type +by a conversationID takes a class name instead. +The following fields are affected by this change: + +conversation +{ + actor = ; + + page + { + drop = ; + ifitem + { + item = ; + } + + choice + { + cost + { + item = ; + } + + giveitem = ; + } + } +} + +It should be noted that this change creates an incompatibility with USDF +so technically speaking the ZDoom format is no longer 'real' USDF. +To accomodate what is needed here this is unavoidable, unfortunately. +Any proper USDF implementation not supporting named actor classes should +either refuse loading dialogues with the 'ZDoom' namespace or if it does not +outright abort on incompatible namespaces fail with a type mismatch error on +one of the specified propeties. + +ZDoom-format dialogues need to start with the line: + +namespace = "ZDoom"; + + +--------------------- +III.A : Conversations +--------------------- + +This block only lists the newly added fields. Currently ZDoom only adds one +field to the specification: + +conversation // Starts a dialog. +{ + id = ; // assigns an ID to a dialogue. IDs are used to dynamically assign + // dialogues to actors. For 'Strife' namespace or binary dialogues + // the standard conversation ID ('actor' property) is used instead + // for this purpose but since 'ZDoom' namespace requires the actor + // to be a class name it needs a separate field for this. +} + +=============================================================================== +EOF +=============================================================================== diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c360dfcb7..48f558fba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,13 +6,21 @@ endif( COMMAND cmake_policy ) include( CheckCXXSourceCompiles ) include( CheckFunctionExists ) +include( CheckCXXCompilerFlag ) include( FindPkgConfig ) option( NO_ASM "Disable assembly code" ) if( CMAKE_COMPILER_IS_GNUCXX ) option( NO_STRIP "Do not strip Release or MinSizeRel builds" ) + # At least some versions of Xcode fail if you strip with the linker + # instead of the separate strip utility. + if( APPLE ) + set( NO_STRIP ON ) + endif( APPLE ) endif( CMAKE_COMPILER_IS_GNUCXX ) +option( DYN_FLUIDSYNTH "Dynamically load fluidsynth" ) + if( CMAKE_SIZEOF_VOID_P MATCHES "8" ) set( X64 64 ) endif( CMAKE_SIZEOF_VOID_P MATCHES "8" ) @@ -25,6 +33,10 @@ endif( CMAKE_SIZEOF_VOID_P MATCHES "8" ) # fmodapilinux[64] -or simply- fmod # jpeg-6b # ... +# The recommended method is to put it in the zdoom tree, since its +# headers are unversioned. Especially now that we can't work properly +# with anything newer than 4.26.xx, you probably don't want to use +# a system-wide version. # Construct version numbers for searching for the FMOD library on Linux. set( MINOR_VERSIONS "50" "49" "48" "47" "46" "45" "44" "43" "42" "41" @@ -32,7 +44,7 @@ set( MINOR_VERSIONS "50" "49" "48" "47" "46" "45" "44" "43" "42" "41" "27" "26" "25" "24" "23" "22" "21" "20" "21" "19" "18" "17" "16" "15" "14" "13" "12" "11" "10" "09" "08" "07" "06" "05" "04" "03" "02" "01" "00" ) -set( MAJOR_VERSIONS "26" "24" "22" "20" ) +set( MAJOR_VERSIONS "30" "28" "26" "24" "22" "20" ) set( FMOD_DIR_VERSIONS ${FMOD_DIR_VERSIONS} "../fmod" ) foreach( majver ${MAJOR_VERSIONS} ) foreach( minver ${MINOR_VERSIONS} ) @@ -230,14 +242,11 @@ else( FMOD_LIBRARY ) endif( FMOD_LIBRARY ) -# Search for NASM +# Search for FluidSynth -if( CMAKE_SYSTEM_PROCESSOR MATCHES powerpc ) - if( NOT NO_ASM ) - message( STATUS "Disabling assembly code for PowerPC." ) - set( NO_ASM ON ) - endif( NOT NO_ASM ) -endif( CMAKE_SYSTEM_PROCESSOR MATCHES powerpc ) +include( ../FindFluidSynth.cmake ) + +# Search for NASM if( NOT NO_ASM ) if( UNIX AND X64 ) @@ -297,7 +306,12 @@ if( NOT NO_ASM ) set( ASM_FLAGS ) set( ASM_SOURCE_EXTENSION .s ) else( X64 ) - set( ASM_FLAGS -f elf -DM_TARGET_LINUX -i${CMAKE_CURRENT_SOURCE_DIR}/ ) + if( APPLE ) + set( ASM_FLAGS -fmacho -DM_TARGET_MACHO ) + else( APPLE ) + set( ASM_FLAGS -felf -DM_TARGET_LINUX ) + endif( APPLE ) + set( ASM_FLAGS "${ASM_FLAGS}" -i${CMAKE_CURRENT_SOURCE_DIR}/ ) set( ASM_SOURCE_EXTENSION .asm ) endif( X64 ) else( UNIX ) @@ -327,9 +341,52 @@ if( NOT NO_ASM ) ENDMACRO( ADD_ASM_FILE ) endif( NOT NO_ASM ) +# Decide on SSE setup + +set( SSE_MATTERS NO ) + +# SSE only matters on 32-bit targets. We check compiler flags to know if we can do it. +if( CMAKE_SIZEOF_VOID_P MATCHES "4" ) + CHECK_CXX_COMPILER_FLAG( "-msse2 -mfpmath=sse" CAN_DO_MFPMATH ) + CHECK_CXX_COMPILER_FLAG( -arch:SSE2 CAN_DO_ARCHSSE2 ) + if( CAN_DO_MFPMATH ) + set( SSE1_ENABLE "-msse -mfpmath=sse" ) + set( SSE2_ENABLE "-msse2 -mfpmath=sse" ) + set( SSE_MATTERS YES ) + elseif( CAN_DO_ARCHSSE2 ) + set( SSE1_ENABLE -arch:SSE ) + set( SSE2_ENABLE -arch:SSE2 ) + set( SSE_MATTERS YES ) + endif( CAN_DO_MFPMATH ) +endif( CMAKE_SIZEOF_VOID_P MATCHES "4" ) + +if( SSE_MATTERS ) + if( WIN32 ) + set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) + else( WIN32 ) + CHECK_FUNCTION_EXISTS(mprotect HAVE_MPROTECT) + if( HAVE_MPROTECT ) + set( BACKPATCH 1 CACHE BOOL "Enable backpatching." ) + else( HAVE_MPROTECT ) + set( BACKPATCH 0 ) + endif( HAVE_MPROTECT ) + endif( WIN32 ) + set( SSE 1 CACHE BOOL "Build SSE and SSE2 versions of key code." ) +else( SSE_MATTERS ) + set( BACKPATCH 0 ) +endif( SSE_MATTERS ) + # Set up flags for GCC if( CMAKE_COMPILER_IS_GNUCXX ) + if( PROFILE ) + set( CMAKE_C_FLinclude( FindFluidSynth.cmake ) +AGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg" ) + set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg" ) + set( CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -pg" ) + set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -pg" ) + endif( PROFILE ) + set( REL_CXX_FLAGS "-fno-rtti" ) if( NOT PROFILE ) set( REL_CXX_FLAGS "${REL_CXX_FLAGS} -fomit-frame-pointer" ) @@ -375,6 +432,13 @@ if( NOT STRNICMP_EXISTS ) add_definitions( -Dstrnicmp=strncasecmp ) endif( NOT STRNICMP_EXISTS ) +if( NOT WIN32 ) + CHECK_FUNCTION_EXISTS( sigtimedwait SIGTIMEDWAIT_EXISTS ) + if( SIGTIMEDWAIT_EXISTS ) + add_definitions( -DHAVE_SIGTIMEDWAIT ) + endif( SIGTIMEDWAIT_EXISTS ) +endif( NOT WIN32) + if( NOT MSVC ) add_definitions( -D__forceinline=inline ) endif( NOT MSVC ) @@ -408,6 +472,12 @@ if( NOT HAS_VA_COPY ) endif( HAS___VA_COPY ) endif( NOT HAS_VA_COPY ) +# Flags + +if( BACKPATCH ) + add_definitions( -DBACKPATCH ) +endif( BACKPATCH ) + # Update svnrevision.h add_custom_target( revision_check ALL @@ -417,8 +487,16 @@ add_custom_target( revision_check ALL # Libraries ZDoom needs +message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${FMOD_LIBRARY}" ) -include_directories( "${ZLIB_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" ) +include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" ) + +if( FLUIDSYNTH_FOUND ) + if( NOT DYN_FLUIDSYNTH) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" ) + include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) + endif( NOT DYN_FLUIDSYNTH ) +endif( FLUIDSYNTH_FOUND ) # Start defining source files for ZDoom @@ -466,7 +544,7 @@ else( WIN32 ) sdl/sdlvideo.cpp sdl/st_start.cpp ) if( APPLE ) - set( SYSTEM_SOURCES ${SYSTEM_SOURCES} sdl/SDLMain.m ) + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} sdl/SDLMain.m sdl/iwadpicker_cocoa.mm ) endif( APPLE ) endif( WIN32 ) @@ -507,12 +585,23 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) -if( CMAKE_SYSTEM_PROCESSOR MATCHES powerpc ) +if( SSE_MATTERS ) + if( SSE ) + set( X86_SOURCES nodebuild_classify_sse2.cpp ) + set_source_files_properties( nodebuild_classify_sse2.cpp PROPERTIES COMPILE_FLAGS "${SSE2_ENABLE}" ) + else( SSE ) + add_definitions( -DDISABLE_SSE ) + endif( SSE ) +else( SSE_MATTERS ) + add_definitions( -DDISABLE_SSE ) set( X86_SOURCES ) - set( NOT_X86 ON ) -else( CMAKE_SYSTEM_PROCESSOR MATCHES powerpc ) - set( X86_SOURCES nodebuild_classify_sse2.cpp ) -endif( CMAKE_SYSTEM_PROCESSOR MATCHES powerpc ) +endif( SSE_MATTERS ) + +if( DYN_FLUIDSYNTH ) + add_definitions( -DHAVE_FLUIDSYNTH -DDYN_FLUIDSYNTH ) +elseif( FLUIDSYNTH_FOUND ) + add_definitions( -DHAVE_FLUIDSYNTH ) +endif( DYN_FLUIDSYNTH ) add_executable( zdoom WIN32 autostart.cpp @@ -578,7 +667,6 @@ add_executable( zdoom WIN32 m_png.cpp m_random.cpp md5.cpp - mus2midi.cpp name.cpp nodebuild.cpp nodebuild_classify_nosse2.cpp @@ -596,6 +684,7 @@ add_executable( zdoom WIN32 p_effect.cpp p_enemy.cpp p_floor.cpp + p_glnodes.cpp p_interaction.cpp p_lights.cpp p_linkedsectors.cpp @@ -620,6 +709,7 @@ add_executable( zdoom WIN32 p_tick.cpp p_trace.cpp p_udmf.cpp + p_usdf.cpp p_user.cpp p_writemap.cpp p_xlat.cpp @@ -663,9 +753,7 @@ add_executable( zdoom WIN32 zstring.cpp g_doom/a_doommisc.cpp g_heretic/a_hereticmisc.cpp - g_heretic/heretic_sbar.cpp g_hexen/a_hexenmisc.cpp - g_hexen/hexen_sbar.cpp g_raven/a_artitele.cpp g_raven/a_minotaur.cpp g_strife/a_strifestuff.cpp @@ -729,13 +817,16 @@ add_executable( zdoom WIN32 sound/music_cd.cpp sound/music_dumb.cpp sound/music_gme.cpp + sound/music_mus_midiout.cpp + sound/music_smf_midiout.cpp + sound/music_hmi_midiout.cpp sound/music_midistream.cpp sound/music_midi_base.cpp - sound/music_midi_midiout.cpp sound/music_midi_timidity.cpp - sound/music_mus_midiout.cpp sound/music_mus_opl.cpp sound/music_stream.cpp + sound/music_fluidsynth_mididevice.cpp + sound/music_softsynth_mididevice.cpp sound/music_timidity_mididevice.cpp sound/music_win_mididevice.cpp textures/automaptexture.cpp @@ -824,21 +915,12 @@ if( NOT WIN32 ) COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make ) endif( NOT WIN32 ) - if( CMAKE_COMPILER_IS_GNUCXX ) # GCC misoptimizes this file set_source_files_properties( oplsynth/fmopl.cpp PROPERTIES COMPILE_FLAGS "-fno-tree-dominator-opts -fno-tree-fre" ) - - # Compile this one file with SSE2 support. - set_source_files_properties( nodebuild_classify_sse2.cpp PROPERTIES COMPILE_FLAGS "-msse2 -mfpmath=sse" ) # Need to enable intrinsics for this file. - if( NOT NOT_X86 ) + if( SSE_MATTERS ) set_source_files_properties( x86.cpp PROPERTIES COMPILE_FLAGS "-msse2 -mmmx" ) - endif( NOT NOT_X86 ) + endif( SSE_MATTERS ) endif( CMAKE_COMPILER_IS_GNUCXX ) - -if( MSVC ) - # Compile this one file with SSE2 support. - set_source_files_properties( nodebuild_classify_sse2.cpp PROPERTIES COMPILE_FLAGS "/arch:SSE2" ) -endif( MSVC ) diff --git a/src/actionspecials.h b/src/actionspecials.h index cd49504c3..c735c7a9d 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -56,7 +56,8 @@ DEFINE_SPECIAL(Sector_ChangeFlags, 54, 3, 3, 3) DEFINE_SPECIAL(Line_SetBlocking, 55, 3, 3, 3) DEFINE_SPECIAL(Line_SetTextureScale, 56, 5, 5, 5) DEFINE_SPECIAL(Sector_SetPortal, 57, -1, -1, 5) - +DEFINE_SPECIAL(Sector_CopyScroller, 58, -1, -1, 2) +DEFINE_SPECIAL(Polyobj_OR_MoveToSpot, 59, 3, 3, 3) DEFINE_SPECIAL(Plat_PerpetualRaise, 60, 3, 3, 3) DEFINE_SPECIAL(Plat_Stop, 61, 1, 1, 1) DEFINE_SPECIAL(Plat_DownWaitUpStay, 62, 3, 3, 3) @@ -76,14 +77,17 @@ DEFINE_SPECIAL(Teleport_EndGame, 75, 0, 0, 0) DEFINE_SPECIAL(TeleportOther, 76, 3, 3, 3) DEFINE_SPECIAL(TeleportGroup, 77, 5, 5, 5) DEFINE_SPECIAL(TeleportInSector, 78, 4, 5, 5) - +DEFINE_SPECIAL(Thing_SetConversation, 79, 2, 2, 2) DEFINE_SPECIAL(ACS_Execute, 80, 1, 5, 5) DEFINE_SPECIAL(ACS_Suspend, 81, 2, 2, 2) DEFINE_SPECIAL(ACS_Terminate, 82, 2, 2, 2) DEFINE_SPECIAL(ACS_LockedExecute, 83, 5, 5, 5) DEFINE_SPECIAL(ACS_ExecuteWithResult, 84, 1, 4, 4) DEFINE_SPECIAL(ACS_LockedExecuteDoor, 85, 5, 5, 5) - +DEFINE_SPECIAL(Polyobj_MoveToSpot, 86, 3, 3, 3) +DEFINE_SPECIAL(Polyobj_Stop, 87, 1, 1, 1) +DEFINE_SPECIAL(Polyobj_MoveTo, 88, 4, 4, 4) +DEFINE_SPECIAL(Polyobj_OR_MoveTo, 89, 4, 4, 4) DEFINE_SPECIAL(Polyobj_OR_RotateLeft, 90, 3, 3, 3) DEFINE_SPECIAL(Polyobj_OR_RotateRight, 91, 3, 3, 3) DEFINE_SPECIAL(Polyobj_OR_Move, 92, 4, 4, 4) @@ -110,7 +114,9 @@ DEFINE_SPECIAL(Plane_Copy, 118, -1, -1, 5) DEFINE_SPECIAL(Thing_Damage, 119, 2, 3, 3) DEFINE_SPECIAL(Radius_Quake, 120, 5, 5, 5) // Earthquake DEFINE_SPECIAL(Line_SetIdentification, 121, -1, -1, 5) + DEFINE_SPECIAL(Thing_Move, 125, 2, 3, 3) + DEFINE_SPECIAL(Thing_SetSpecial, 127, 5, 5, 5) DEFINE_SPECIAL(ThrustThingZ, 128, 4, 4, 4) DEFINE_SPECIAL(UsePuzzleItem, 129, 2, 5, 5) diff --git a/src/actor.h b/src/actor.h index 7ab8bc666..79b503468 100644 --- a/src/actor.h +++ b/src/actor.h @@ -40,6 +40,7 @@ #include "r_blend.h" #include "s_sound.h" +struct subsector_t; class PClassAmmo; // @@ -262,7 +263,7 @@ enum MF4_NOEXTREMEDEATH = 0x10000000, // this projectile or weapon never gibs its victim MF4_EXTREMEDEATH = 0x20000000, // this projectile or weapon always gibs its victim MF4_FRIGHTENED = 0x40000000, // Monster runs away from player - /* = 0x80000000, */ + MF4_BOSSSPAWNED = 0x80000000, // Spawned by a boss spawn cube // --- mobj.flags5 --- @@ -321,6 +322,9 @@ enum MF6_NOTRIGGER = 0x00010000, // actor cannot trigger any line actions MF6_SHATTERING = 0x00020000, // marks an ice corpse for forced shattering MF6_KILLED = 0x00040000, // Something that was killed (but not necessarily a corpse) + MF6_BLOCKEDBYSOLIDACTORS = 0x00080000, // Blocked by solid actors, even if not solid itself + MF6_ADDITIVEPOISONDAMAGE = 0x00100000, + MF6_ADDITIVEPOISONDURATION = 0x00200000, // --- mobj.renderflags --- @@ -616,7 +620,7 @@ public: bool CheckLocalView (int playernum) const; // Finds the first item of a particular type. - AInventory *FindInventory (PClassActor *type); + AInventory *FindInventory (PClassActor *type, bool subclass=false); AInventory *FindInventory (FName type); template T *FindInventory () { @@ -695,22 +699,29 @@ public: PClassActor *GetBloodType(int type = 0) const { + PClassActor *bloodcls; if (type == 0) { - return PClass::FindActor(GetClass()->BloodType); + bloodcls = PClass::FindActor(GetClass()->BloodType); } else if (type == 1) { - return PClass::FindActor(GetClass()->BloodType2); + bloodcls = PClass::FindActor(GetClass()->BloodType2); } else if (type == 2) { - return PClass::FindActor(GetClass()->BloodType3); + bloodcls = PClass::FindActor(GetClass()->BloodType3); } else { return NULL; } + + if (bloodcls != NULL) + { + bloodcls = bloodcls->GetReplacement(); + } + return bloodcls; } // Calculate amount of missile damage @@ -742,6 +753,7 @@ public: fixed_t pitch, roll; FBlockNode *BlockNode; // links in blocks (if needed) struct sector_t *Sector; + subsector_t * subsector; fixed_t floorz, ceilingz; // closest together of contacted secs fixed_t dropoffz; // killough 11/98: the lowest floor over all contacted Sectors. @@ -820,6 +832,15 @@ public: AActor *BlockingMobj; // Actor that blocked the last move line_t *BlockingLine; // Line that blocked the last move + int PoisonDamage; // Damage received per tic from poison. + int PoisonDuration; // Duration left for receiving poison damage. + int PoisonPeriod; // How often poison damage is applied. (Every X tics.) + + int PoisonDamageReceived; // Damage received per tic from poison. + int PoisonDurationReceived; // Duration left for receiving poison damage. + int PoisonPeriodReceived; // How often poison damage is applied. (Every X tics.) + TObjPtr Poisoner; // Last source of received poison damage. + // a linked list of sectors where this object appears struct msecnode_t *touching_sectorlist; // phares 3/14/98 @@ -859,8 +880,9 @@ public: FState *MeleeState; FState *MissileState; - // [RH] The dialogue to show when this actor is "used." - FStrifeDialogueNode *Conversation; + + int ConversationRoot; // THe root of the current dialogue + FStrifeDialogueNode *Conversation; // [RH] The dialogue to show when this actor is "used." // [RH] Decal(s) this weapon/projectile generates on impact. FDecalBase *DecalGenerator; @@ -890,8 +912,7 @@ public: void SetOrigin (fixed_t x, fixed_t y, fixed_t z); bool InStateSequence(FState * newstate, FState * basestate); int GetTics(FState * newstate); - bool SetState (FState *newstate); - bool SetStateNF (FState *newstate); + bool SetState (FState *newstate, bool nofunction=false); virtual bool UpdateWaterLevel (fixed_t oldz, bool splash=true); bool isFast(); void SetIdle(); diff --git a/src/am_map.cpp b/src/am_map.cpp index 28739c856..73a7f8c98 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -36,6 +36,9 @@ #include "r_translate.h" #include "d_event.h" #include "gi.h" +#include "r_bsp.h" +#include "p_setup.h" +#include "c_bind.h" #include "m_cheat.h" #include "i_system.h" @@ -58,6 +61,8 @@ #include "am_map.h" #include "a_artifacts.h" +#include "po_man.h" +#include "a_keys.h" struct AMColor { @@ -79,7 +84,7 @@ struct AMColor static AMColor Background, YourColor, WallColor, TSWallColor, FDWallColor, CDWallColor, ThingColor, - ThingColor_Item, ThingColor_Monster, ThingColor_Friend, + ThingColor_Item, ThingColor_CountItem, ThingColor_Monster, ThingColor_Friend, SecretWallColor, GridColor, XHairColor, NotSeenColor, LockedColor, @@ -170,27 +175,66 @@ CVAR (Color, am_secretsectorcolor, 0xff00ff, CVAR_ARCHIVE); CVAR (Color, am_ovsecretsectorcolor,0x00ffff, CVAR_ARCHIVE); CVAR (Int, am_map_secrets, 1, CVAR_ARCHIVE); CVAR (Bool, am_drawmapback, true, CVAR_ARCHIVE); +CVAR (Bool, am_showkeys, true, CVAR_ARCHIVE); CVAR (Color, am_thingcolor_friend, 0xfcfcfc, CVAR_ARCHIVE); CVAR (Color, am_thingcolor_monster, 0xfcfcfc, CVAR_ARCHIVE); CVAR (Color, am_thingcolor_item, 0xfcfcfc, CVAR_ARCHIVE); +CVAR (Color, am_thingcolor_citem, 0xfcfcfc, CVAR_ARCHIVE); CVAR (Color, am_ovthingcolor_friend, 0xe88800, CVAR_ARCHIVE); CVAR (Color, am_ovthingcolor_monster, 0xe88800, CVAR_ARCHIVE); CVAR (Color, am_ovthingcolor_item, 0xe88800, CVAR_ARCHIVE); +CVAR (Color, am_ovthingcolor_citem, 0xe88800, CVAR_ARCHIVE); + + +static int bigstate = 0; +static bool textured = 1; // internal toggle for texture mode + +CUSTOM_CVAR(Bool, am_textured, false, CVAR_ARCHIVE) +{ + textured |= self; +} + +CVAR(Int, am_showsubsector, -1, 0); + + +// Disable the ML_DONTDRAW line flag if x% of all lines in a map are flagged with it +// (To counter annoying mappers who think they are smart by making the automap unusable) +bool am_showallenabled; + +CUSTOM_CVAR (Int, am_showalllines, -1, 0) // This is a cheat so don't save it. +{ + int flagged = 0; + int total = 0; + if (self > 0 && numlines > 0) + { + for(int i=0;ifrontsector == line->backsector) continue; + + // disregard control sectors for deep water + if (line->frontsector->e->FakeFloor.Sectors.Size() > 0) continue; + + // disregard control sectors for 3D-floors + if (line->frontsector->e->XFloor.attached.Size() > 0) continue; + + total++; + if (line->flags & ML_DONTDRAW) flagged++; + } + am_showallenabled = (flagged * 100 / total >= self); + } + else if (self == 0) + { + am_showallenabled = true; + } + else + { + am_showallenabled = false; + } +} -// drawing stuff -#define AM_PANDOWNKEY KEY_DOWNARROW -#define AM_PANUPKEY KEY_UPARROW -#define AM_PANRIGHTKEY KEY_RIGHTARROW -#define AM_PANLEFTKEY KEY_LEFTARROW -#define AM_ZOOMINKEY KEY_EQUALS -#define AM_ZOOMINKEY2 0x4e // DIK_ADD -#define AM_ZOOMOUTKEY KEY_MINUS -#define AM_ZOOMOUTKEY2 0x4a // DIK_SUBTRACT -#define AM_GOBIGKEY 0x0b // DIK_0 -#define AM_FOLLOWKEY 'f' -#define AM_GRIDKEY 'g' -#define AM_MARKKEY 'm' -#define AM_CLEARMARKKEY 'c' #define AM_NUMMARKPOINTS 10 @@ -202,10 +246,10 @@ CVAR (Color, am_ovthingcolor_item, 0xe88800, CVAR_ARCHIVE); #define F_PANINC (140/TICRATE) // how much zoom-in per tic // goes to 2x in 1 second -#define M_ZOOMIN ((int) (1.02*MAPUNIT)) +#define M_ZOOMIN (1.02*MAPUNIT) // how much zoom-out per tic // pulls out to 0.5x in 1 second -#define M_ZOOMOUT ((int) (MAPUNIT/1.02)) +#define M_ZOOMOUT (MAPUNIT/1.02) // translates between frame-buffer and map coordinates #define CXMTOF(x) (MTOF((x)-m_x)/* - f_x*/) @@ -253,25 +297,22 @@ mline_t player_arrow[] = { { { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, // >>---> { { -R+3*R/8, 0 }, { -R+R/8, -R/4 } } }; +#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t)) mline_t player_arrow_raven[] = { - { { -R+R/4, 0 }, { 0, 0} }, // center line. - { { -R+R/4, R/8 }, { R, 0} }, // blade - { { -R+R/4, -R/8 }, { R, 0 } }, - { { -R+R/4, -R/4 }, { -R+R/4, R/4 } }, // crosspiece - { { -R+R/8, -R/4 }, { -R+R/8, R/4 } }, - { { -R+R/8, -R/4 }, { -R+R/4, -R/4} }, //crosspiece connectors - { { -R+R/8, R/4 }, { -R+R/4, R/4} }, - { { -R-R/4, R/8 }, { -R-R/4, -R/8 } }, //pommel - { { -R-R/4, R/8 }, { -R+R/8, R/8 } }, - { { -R-R/4, -R/8}, { -R+R/8, -R/8 } } - }; - -#undef R -#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t)) + { { -R+R/4, 0 }, { 0, 0} }, // center line. + { { -R+R/4, R/8 }, { R, 0} }, // blade + { { -R+R/4, -R/8 }, { R, 0 } }, + { { -R+R/4, -R/4 }, { -R+R/4, R/4 } }, // crosspiece + { { -R+R/8, -R/4 }, { -R+R/8, R/4 } }, + { { -R+R/8, -R/4 }, { -R+R/4, -R/4} }, //crosspiece connectors + { { -R+R/8, R/4 }, { -R+R/4, R/4} }, + { { -R-R/4, R/8 }, { -R-R/4, -R/8 } }, //pommel + { { -R-R/4, R/8 }, { -R+R/8, R/8 } }, + { { -R-R/4, -R/8}, { -R+R/8, -R/8 } } +}; #define NUMPLYRLINES_RAVEN (sizeof(player_arrow_raven)/sizeof(mline_t)) -#define R ((8*PLAYERRADIUS)/7) mline_t cheat_player_arrow[] = { { { -R+R/8, 0 }, { R, 0 } }, // ----- { { R, 0 }, { R-R/2, R/6 } }, // -----> @@ -290,9 +331,9 @@ mline_t cheat_player_arrow[] = { { { R/6, -R/7 }, { R/6+R/32, -R/7-R/32 } }, { { R/6+R/32, -R/7-R/32 }, { R/6+R/10, -R/7 } } }; +#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t)) #undef R -#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t)) #define R (MAPUNIT) // [RH] Avoid lots of warnings without compiler-specific #pragmas @@ -309,9 +350,37 @@ mline_t thintriangle_guy[] = { L (1,0, -.5,.7), L (-.5,.7, -.5,-.7) }; +#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t)) + +mline_t square_guy[] = { + L (0,1,1,0), + L (1,0,0,-1), + L (0,-1,-1,0), + L (-1,0,0,1) +}; +#define NUMSQUAREGUYLINES (sizeof(square_guy)/sizeof(mline_t)) + +#undef R +#define R (MAPUNIT) + +mline_t key_guy[] = { + L (-2, 0, -1.7, -0.5), + L (-1.7, -0.5, -1.5, -0.7), + L (-1.5, -0.7, -0.8, -0.5), + L (-0.8, -0.5, -0.6, 0), + L (-0.6, 0, -0.8, 0.5), + L (-1.5, 0.7, -0.8, 0.5), + L (-1.7, 0.5, -1.5, 0.7), + L (-2, 0, -1.7, 0.5), + L (-0.6, 0, 2, 0), + L (1.7, 0, 1.7, -1), + L (1.5, 0, 1.5, -1), + L (1.3, 0, 1.3, -1) +}; +#define NUMKEYGUYLINES (sizeof(key_guy)/sizeof(mline_t)) + #undef L #undef R -#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t)) @@ -344,7 +413,7 @@ static int amclock; static mpoint_t m_paninc; // how far the window pans each tic (map coords) static fixed_t mtof_zoommul; // how far the window zooms in each tic (map coords) -static fixed_t ftom_zoommul; // how far the window zooms in each tic (fb coords) +static float am_zoomdir; static fixed_t m_x, m_y; // LL x,y where the window is on the map (map coords) static fixed_t m_x2, m_y2; // UR x,y where the window is on the map (map coords) @@ -393,7 +462,69 @@ static void AM_calcMinMaxMtoF(); void AM_rotatePoint (fixed_t *x, fixed_t *y); void AM_rotate (fixed_t *x, fixed_t *y, angle_t an); void AM_doFollowPlayer (); -static void AM_ToggleFollowPlayer(); + + +//============================================================================= +// +// map functions +// +//============================================================================= +bool AM_addMark (); +bool AM_clearMarks (); +void AM_saveScaleAndLoc (); +void AM_restoreScaleAndLoc (); +void AM_minOutWindowScale (); + + +CCMD(am_togglefollow) +{ + followplayer = !followplayer; + f_oldloc.x = FIXED_MAX; + Printf ("%s\n", GStrings(followplayer ? "AMSTR_FOLLOWON" : "AMSTR_FOLLOWOFF")); +} + +CCMD(am_togglegrid) +{ + grid = !grid; + Printf ("%s\n", GStrings(grid ? "AMSTR_GRIDON" : "AMSTR_GRIDOFF")); +} + +CCMD(am_toggletexture) +{ + if (am_textured && hasglnodes) + { + textured = !textured; + Printf ("%s\n", GStrings(textured ? "AMSTR_TEXON" : "AMSTR_TEXOFF")); + } +} + +CCMD(am_setmark) +{ + if (AM_addMark()) + { + Printf ("%s %d\n", GStrings("AMSTR_MARKEDSPOT"), markpointnum); + } +} + +CCMD(am_clearmarks) +{ + if (AM_clearMarks()) + { + Printf ("%s\n", GStrings("AMSTR_MARKSCLEARED")); + } +} + +CCMD(am_gobig) +{ + bigstate = !bigstate; + if (bigstate) + { + AM_saveScaleAndLoc(); + AM_minOutWindowScale(); + } + else + AM_restoreScaleAndLoc(); +} // Calculates the slope and slope according to the x-axis of a line // segment in map coordinates (with the upright y-axis n' all) so @@ -702,11 +833,19 @@ void AM_initVariables () automapactive = true; + // Reset AM buttons + Button_AM_PanLeft.Reset(); + Button_AM_PanRight.Reset(); + Button_AM_PanUp.Reset(); + Button_AM_PanDown.Reset(); + Button_AM_ZoomIn.Reset(); + Button_AM_ZoomOut.Reset(); + + f_oldloc.x = FIXED_MAX; amclock = 0; m_paninc.x = m_paninc.y = 0; - ftom_zoommul = MAPUNIT; mtof_zoommul = MAPUNIT; m_w = FTOM(SCREENWIDTH); @@ -774,6 +913,7 @@ static void AM_initColors (bool overlayed) SecretWallColor = WallColor; SecretSectorColor.FromCVar (am_ovsecretsectorcolor); ThingColor_Item.FromCVar (am_ovthingcolor_item); + ThingColor_CountItem.FromCVar (am_ovthingcolor_citem); ThingColor_Friend.FromCVar (am_ovthingcolor_friend); ThingColor_Monster.FromCVar (am_ovthingcolor_monster); ThingColor.FromCVar (am_ovthingcolor); @@ -797,6 +937,7 @@ static void AM_initColors (bool overlayed) FDWallColor.FromCVar (am_fdwallcolor); CDWallColor.FromCVar (am_cdwallcolor); ThingColor_Item.FromCVar (am_thingcolor_item); + ThingColor_CountItem.FromCVar (am_thingcolor_citem); ThingColor_Friend.FromCVar (am_thingcolor_friend); ThingColor_Monster.FromCVar (am_thingcolor_monster); ThingColor.FromCVar (am_thingcolor); @@ -946,6 +1087,8 @@ void AM_LevelInit () if (scale_mtof > max_scale_mtof) scale_mtof = min_scale_mtof; scale_ftom = MapDiv(MAPUNIT, scale_mtof); + + am_showalllines.Callback(); } //============================================================================= @@ -1081,127 +1224,28 @@ void AM_ToggleMap () // //============================================================================= -bool AM_Responder (event_t *ev) +bool AM_Responder (event_t *ev, bool last) { - bool rc; - static int cheatstate = 0; - static int bigstate = 0; - - rc = false; - - if (automapactive && ev->type == EV_KeyDown) + if (automapactive && (ev->type == EV_KeyDown || ev->type == EV_KeyUp)) { - rc = true; - switch (ev->data1) + if (followplayer) { - case AM_PANRIGHTKEY: // pan right - if (!followplayer) - m_paninc.x = FTOM(F_PANINC); - else - rc = false; - break; - case AM_PANLEFTKEY: // pan left - if (!followplayer) - m_paninc.x = -FTOM(F_PANINC); - else - rc = false; - break; - case AM_PANUPKEY: // pan up - if (!followplayer) - m_paninc.y = FTOM(F_PANINC); - else - rc = false; - break; - case AM_PANDOWNKEY: // pan down - if (!followplayer) - m_paninc.y = -FTOM(F_PANINC); - else - rc = false; - break; - case AM_ZOOMOUTKEY: // zoom out - case AM_ZOOMOUTKEY2: - mtof_zoommul = M_ZOOMOUT; - ftom_zoommul = M_ZOOMIN; - break; - case AM_ZOOMINKEY: // zoom in - case AM_ZOOMINKEY2: - mtof_zoommul = M_ZOOMIN; - ftom_zoommul = M_ZOOMOUT; - break; - case AM_GOBIGKEY: - bigstate = !bigstate; - if (bigstate) - { - AM_saveScaleAndLoc(); - AM_minOutWindowScale(); - } - else - AM_restoreScaleAndLoc(); - break; - default: - switch (ev->data2) - { - case AM_FOLLOWKEY: - AM_ToggleFollowPlayer(); - break; - case AM_GRIDKEY: - grid = !grid; - Printf ("%s\n", GStrings(grid ? "AMSTR_GRIDON" : "AMSTR_GRIDOFF")); - break; - case AM_MARKKEY: - if (AM_addMark()) - { - Printf ("%s %d\n", GStrings("AMSTR_MARKEDSPOT"), markpointnum); - } - else - { - rc = false; - } - break; - case AM_CLEARMARKKEY: - if (AM_clearMarks()) - { - Printf ("%s\n", GStrings("AMSTR_MARKSCLEARED")); - } - else - { - rc = false; - } - break; - default: - cheatstate = 0; - rc = false; - } + // check for am_pan* and ignore in follow mode + const char *defbind = AutomapBindings.GetBind(ev->data1); + if (!strnicmp(defbind, "+am_pan", 7)) return false; } - } - else if (ev->type == EV_KeyUp) - { - rc = false; - switch (ev->data1) - { - case AM_PANRIGHTKEY: - if (!followplayer) m_paninc.x = 0; - break; - case AM_PANLEFTKEY: - if (!followplayer) m_paninc.x = 0; - break; - case AM_PANUPKEY: - if (!followplayer) m_paninc.y = 0; - break; - case AM_PANDOWNKEY: - if (!followplayer) m_paninc.y = 0; - break; - case AM_ZOOMOUTKEY: - case AM_ZOOMOUTKEY2: - case AM_ZOOMINKEY: - case AM_ZOOMINKEY2: - mtof_zoommul = MAPUNIT; - ftom_zoommul = MAPUNIT; - break; - } - } - return rc; + bool res = C_DoKey(ev, &AutomapBindings, NULL); + if (res && ev->type == EV_KeyUp && !last) + { + // If this is a release event we also need to check if it released a button in the main Bindings + // so that that button does not get stuck. + const char *defbind = Bindings.GetBind(ev->data1); + return (defbind[0] != '+'); // Let G_Responder handle button releases + } + return res; + } + return false; } @@ -1213,6 +1257,26 @@ bool AM_Responder (event_t *ev) void AM_changeWindowScale () { + int mtof_zoommul; + + if (am_zoomdir > 0) + { + mtof_zoommul = int(M_ZOOMIN * am_zoomdir); + } + else if (am_zoomdir < 0) + { + mtof_zoommul = int(M_ZOOMOUT / -am_zoomdir); + } + else if (Button_AM_ZoomIn.bDown) + { + mtof_zoommul = int(M_ZOOMIN); + } + else if (Button_AM_ZoomOut.bDown) + { + mtof_zoommul = int(M_ZOOMOUT); + } + am_zoomdir = 0; + // Change the scaling multipliers scale_mtof = MapMul(scale_mtof, mtof_zoommul); scale_ftom = MapDiv(MAPUNIT, scale_mtof); @@ -1223,6 +1287,13 @@ void AM_changeWindowScale () AM_maxOutWindowScale(); } +CCMD(am_zoom) +{ + if (argv.argc() >= 2) + { + am_zoomdir = (float)atof(argv[1]); + } +} //============================================================================= // @@ -1257,19 +1328,6 @@ void AM_doFollowPlayer () } } -//============================================================================= -// -// -// -//============================================================================= - -static void AM_ToggleFollowPlayer() -{ - followplayer = !followplayer; - f_oldloc.x = FIXED_MAX; - Printf ("%s\n", GStrings(followplayer ? "AMSTR_FOLLOWON" : "AMSTR_FOLLOWOFF")); -} - //============================================================================= // // Updates on Game Tick @@ -1284,10 +1342,20 @@ void AM_Ticker () amclock++; if (followplayer) + { AM_doFollowPlayer(); + } + else + { + m_paninc.x = m_paninc.y = 0; + if (Button_AM_PanLeft.bDown) m_paninc.x -= FTOM(F_PANINC); + if (Button_AM_PanRight.bDown) m_paninc.x += FTOM(F_PANINC); + if (Button_AM_PanUp.bDown) m_paninc.y += FTOM(F_PANINC); + if (Button_AM_PanDown.bDown) m_paninc.y -= FTOM(F_PANINC); + } // Change the zoom if necessary - if (ftom_zoommul != MAPUNIT) + if (Button_AM_ZoomIn.bDown || Button_AM_ZoomOut.bDown || am_zoomdir != 0) AM_changeWindowScale(); // Change x,y location @@ -1545,6 +1613,97 @@ void AM_drawGrid (const AMColor &color) } } +//============================================================================= +// +// AM_drawSubsectors +// +//============================================================================= + +void AM_drawSubsectors() +{ + static TArray points; + float scale = float(scale_mtof); + angle_t rotation; + sector_t tempsec; + int floorlight, ceilinglight; + double originx, originy; + FDynamicColormap *colormap; + + + for (int i = 0; i < numsubsectors; ++i) + { + if (subsectors[i].flags & SSECF_POLYORG) + { + continue; + } + + if ((!(subsectors[i].flags & SSECF_DRAWN) || (subsectors[i].render_sector->MoreFlags & SECF_HIDDEN)) && am_cheat == 0) + { + continue; + } + // Fill the points array from the subsector. + points.Resize(subsectors[i].numlines); + for (DWORD j = 0; j < subsectors[i].numlines; ++j) + { + mpoint_t pt = { subsectors[i].firstline[j].v1->x >> FRACTOMAPBITS, + subsectors[i].firstline[j].v1->y >> FRACTOMAPBITS }; + if (am_rotate == 1 || (am_rotate == 2 && viewactive)) + { + AM_rotatePoint(&pt.x, &pt.y); + } + points[j].X = f_x + ((pt.x - m_x) * scale / float(1 << 24)); + points[j].Y = f_y + (f_h - (pt.y - m_y) * scale / float(1 << 24)); + } + // For lighting and texture determination + sector_t *sec = R_FakeFlat (subsectors[i].render_sector, &tempsec, &floorlight, + &ceilinglight, false); + // Find texture origin. + mpoint_t originpt = { -sec->GetXOffset(sector_t::floor) >> FRACTOMAPBITS, + sec->GetYOffset(sector_t::floor) >> FRACTOMAPBITS }; + rotation = 0 - sec->GetAngle(sector_t::floor); + // Apply the floor's rotation to the texture origin. + if (rotation != 0) + { + AM_rotate(&originpt.x, &originpt.y, rotation); + } + // Apply the automap's rotation to the texture origin. + if (am_rotate == 1 || (am_rotate == 2 && viewactive)) + { + rotation += ANG90 - players[consoleplayer].camera->angle; + AM_rotatePoint(&originpt.x, &originpt.y); + } + originx = f_x + ((originpt.x - m_x) * scale / float(1 << 24)); + originy = f_y + (f_h - (originpt.y - m_y) * scale / float(1 << 24)); + // Coloring for the polygon + colormap = sec->ColorMap; + // If this subsector has not actually been seen yet (because you are cheating + // to see it on the map), tint and desaturate it. + if (!(subsectors[i].flags & SSECF_DRAWN)) + { + colormap = GetSpecialLights( + MAKERGB( + (colormap->Color.r + 255) / 2, + (colormap->Color.g + 200) / 2, + (colormap->Color.b + 160) / 2), + colormap->Fade, + 255 - (255 - colormap->Desaturate) / 4); + floorlight = (floorlight + 200*15) / 16; + } + + // Draw the polygon. + screen->FillSimplePoly( + TexMan(sec->GetTexture(sector_t::floor)), + &points[0], points.Size(), + originx, originy, + scale / (FIXED2FLOAT(sec->GetXScale(sector_t::floor)) * float(1 << MAPBITS)), + scale / (FIXED2FLOAT(sec->GetYScale(sector_t::floor)) * float(1 << MAPBITS)), + rotation, + colormap, + floorlight + ); + } +} + //============================================================================= // // @@ -1573,6 +1732,80 @@ static bool AM_CheckSecret(line_t *line) } +//============================================================================= +// +// Polyobject debug stuff +// +//============================================================================= + +void AM_drawSeg(seg_t *seg, const AMColor &color) +{ + mline_t l; + l.a.x = seg->v1->x >> FRACTOMAPBITS; + l.a.y = seg->v1->y >> FRACTOMAPBITS; + l.b.x = seg->v2->x >> FRACTOMAPBITS; + l.b.y = seg->v2->y >> FRACTOMAPBITS; + + if (am_rotate == 1 || (am_rotate == 2 && viewactive)) + { + AM_rotatePoint (&l.a.x, &l.a.y); + AM_rotatePoint (&l.b.x, &l.b.y); + } + AM_drawMline(&l, color); +} + +void AM_drawPolySeg(FPolySeg *seg, const AMColor &color) +{ + mline_t l; + l.a.x = seg->v1.x >> FRACTOMAPBITS; + l.a.y = seg->v1.y >> FRACTOMAPBITS; + l.b.x = seg->v2.x >> FRACTOMAPBITS; + l.b.y = seg->v2.y >> FRACTOMAPBITS; + + if (am_rotate == 1 || (am_rotate == 2 && viewactive)) + { + AM_rotatePoint (&l.a.x, &l.a.y); + AM_rotatePoint (&l.b.x, &l.b.y); + } + AM_drawMline(&l, color); +} + +void AM_showSS() +{ + if (am_showsubsector >= 0 && am_showsubsector < numsubsectors) + { + AMColor yellow; + yellow.FromRGB(255,255,0); + AMColor red; + red.FromRGB(255,0,0); + + subsector_t *sub = &subsectors[am_showsubsector]; + for (unsigned int i = 0; i < sub->numlines; i++) + { + AM_drawSeg(sub->firstline + i, yellow); + } + PO_LinkToSubsectors(); + + for (int i = 0; i subsectorlinks; + + while (pnode != NULL) + { + if (pnode->subsector == sub) + { + for (unsigned j = 0; j < pnode->segs.Size(); j++) + { + AM_drawPolySeg(&pnode->segs[j], red); + } + } + pnode = pnode->snext; + } + } + } +} + //============================================================================= // // Determines visible lines, draws them. @@ -1601,7 +1834,12 @@ void AM_drawWalls (bool allmap) if (am_cheat != 0 || (lines[i].flags & ML_MAPPED)) { if ((lines[i].flags & ML_DONTDRAW) && am_cheat == 0) - continue; + { + if (!am_showallenabled || CheckCheatmode(false)) + { + continue; + } + } if (AM_CheckSecret(&lines[i])) { @@ -1680,8 +1918,14 @@ void AM_drawWalls (bool allmap) } else if (allmap) { - if (!(lines[i].flags & ML_DONTDRAW)) - AM_drawMline(&l, NotSeenColor); + if ((lines[i].flags & ML_DONTDRAW) && am_cheat == 0) + { + if (!am_showallenabled || CheckCheatmode(false)) + { + continue; + } + } + AM_drawMline(&l, NotSeenColor); } } } @@ -1694,14 +1938,27 @@ void AM_drawWalls (bool allmap) // //============================================================================= -void AM_rotate (fixed_t *x, fixed_t *y, angle_t a) +void AM_rotate(fixed_t *xp, fixed_t *yp, angle_t a) { - fixed_t tmpx; + static angle_t angle_saved = 0; + static double sinrot = 0; + static double cosrot = 1; - a >>= ANGLETOFINESHIFT; - tmpx = DMulScale16 (*x,finecosine[a],*y,-finesine[a]); - *y = DMulScale16 (*x,finesine[a],*y,finecosine[a]); - *x = tmpx; + if (angle_saved != a) + { + angle_saved = a; + double rot = (double)a / (double)(1u << 31) * (double)M_PI; + sinrot = sin(rot); + cosrot = cos(rot); + } + + double x = FIXED2FLOAT(*xp); + double y = FIXED2FLOAT(*yp); + double tmpx = (x * cosrot) - (y * sinrot); + y = (x * sinrot) + (y * cosrot); + x = tmpx; + *xp = FLOAT2FIXED(x); + *yp = FLOAT2FIXED(y); } //============================================================================= @@ -1912,11 +2169,40 @@ void AM_drawThings () if (t->flags & MF_FRIENDLY || !(t->flags & MF_COUNTKILL)) color = ThingColor_Friend; else color = ThingColor_Monster; } - else if (t->flags&MF_SPECIAL) color = ThingColor_Item; + else if (t->flags&MF_SPECIAL) + { + // Find the key's own color. + // Only works correctly if single-key locks have lower numbers than any-key locks. + // That is the case for all default keys, however. + if (t->IsKindOf(RUNTIME_CLASS(AKey))) + { + if (am_showkeys) + { + int P_GetMapColorForKey (AInventory * key); + int c = P_GetMapColorForKey(static_cast(t)); - AM_drawLineCharacter - (thintriangle_guy, NUMTHINTRIANGLEGUYLINES, - 16<= 0) color.FromRGB(RPART(c), GPART(c), BPART(c)); + else color = ThingColor_CountItem; + AM_drawLineCharacter(key_guy, NUMKEYGUYLINES, 16<flags&MF_COUNTITEM) + color = ThingColor_CountItem; + else + color = ThingColor_Item; + } + + if (color.Index != -1) + { + AM_drawLineCharacter + (thintriangle_guy, NUMTHINTRIANGLEGUYLINES, + 16<= 3) { @@ -2041,7 +2327,11 @@ void AM_drawAuthorMarkers () while (marked != NULL) { - if (mark->args[1] == 0 || (mark->args[1] == 1 && marked->Sector->MoreFlags & SECF_DRAWN)) + // Use more correct info if we have GL nodes available + if (mark->args[1] == 0 || + (mark->args[1] == 1 && (hasglnodes ? + marked->subsector->flags & SSECF_DRAWN : + marked->Sector->MoreFlags & SECF_DRAWN))) { DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0, flip, mark->scaleX, mark->scaleY, mark->Translation, @@ -2075,7 +2365,7 @@ void AM_Drawer () return; bool allmap = (level.flags2 & LEVEL2_ALLMAP) != 0; - bool allthings = allmap && players[consoleplayer].mo->FindInventory() != NULL; + bool allthings = allmap && players[consoleplayer].mo->FindInventory(RUNTIME_CLASS(APowerScanner), true) != NULL; AM_initColors (viewactive); @@ -2100,6 +2390,9 @@ void AM_Drawer () } AM_activateNewScale(); + if (am_textured && hasglnodes && textured && !viewactive) + AM_drawSubsectors(); + if (grid) AM_drawGrid(GridColor); @@ -2114,6 +2407,8 @@ void AM_Drawer () AM_drawCrosshair(XHairColor); AM_drawMarks(); + + AM_showSS(); } //============================================================================= diff --git a/src/am_map.h b/src/am_map.h index 71740b9a7..20da6b96b 100644 --- a/src/am_map.h +++ b/src/am_map.h @@ -26,7 +26,7 @@ struct event_t; class FArchive; // Called by main loop. -bool AM_Responder (event_t* ev); +bool AM_Responder (event_t* ev, bool last); // Called by main loop. void AM_Ticker (void); diff --git a/src/asm_ia32/a.asm b/src/asm_ia32/a.asm index 196e8317a..d68fba116 100644 --- a/src/asm_ia32/a.asm +++ b/src/asm_ia32/a.asm @@ -93,7 +93,16 @@ setupvlineasm: selfmod premach3a, machvsh8+6 ret +%ifdef M_TARGET_MACHO + SECTION .text align=64 +%else SECTION .rtext progbits alloc exec write align=64 +%endif + +%ifdef M_TARGET_MACHO +GLOBAL _rtext_a_start +_rtext_a_start: +%endif ;eax = xscale ;ebx = palookupoffse @@ -325,6 +334,7 @@ setupmvlineasm: mov ecx, dword [esp+4] mov byte [maskmach3a+2], cl mov byte [machmv13+2], cl + mov byte [machmv14+2], cl mov byte [machmv15+2], cl mov byte [machmv16+2], cl @@ -538,3 +548,8 @@ ALIGN 16 mvcase0: jmp beginmvlineasm4 align 16 + +%ifdef M_TARGET_MACHO +GLOBAL _rtext_a_end +_rtext_a_end: +%endif diff --git a/src/asm_ia32/tmap.asm b/src/asm_ia32/tmap.asm index e4c477598..cbcd9f4f1 100644 --- a/src/asm_ia32/tmap.asm +++ b/src/asm_ia32/tmap.asm @@ -285,7 +285,16 @@ R_SetSpanSize_ASM: aret: ret +%ifdef M_TARGET_MACHO + SECTION .text align=64 +%else SECTION .rtext progbits alloc exec write align=64 +%endif + +%ifdef M_TARGET_MACHO +GLOBAL _rtext_tmap_start +_rtext_tmap_start: +%endif rtext_start: @@ -300,6 +309,8 @@ GLOBAL R_DrawSpanP_ASM ; edi: dest ; ebp: scratch ; esi: count +; [esp]: xstep +; [esp+4]: ystep align 16 @@ -315,6 +326,7 @@ R_DrawSpanP_ASM: push edi push ebp push esi + sub esp, 8 mov edi,ecx add edi,[dc_destorg] @@ -326,13 +338,13 @@ dsy1: shl edx,6 dsy3: shr ebp,26 xor ebx,ebx lea esi,[eax+1] - mov [ds_xstep],edx + mov [esp],edx mov edx,[ds_ystep] mov ecx,[ds_xfrac] dsy4: shr ecx,26 dsm8: and edx,0xffffffc0 or ebp,edx - mov [ds_ystep],ebp + mov [esp+4],ebp mov ebp,[ds_yfrac] mov edx,[ds_xfrac] dsy2: shl edx,6 @@ -346,8 +358,8 @@ dsm9: and ebp,0xffffffc0 mov ebp,ecx dsx1: rol ebp,6 dsm1: and ebp,0xfff - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] spreada mov bl,[ebp+SPACEFILLER4] spmapa mov bl,[ebx+SPACEFILLER4] mov [edi],bl @@ -358,13 +370,13 @@ dseven1 shr esi,1 ; do two more pixels mov ebp,ecx - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] dsm2: and ebp,0xfc00003f dsx2: rol ebp,6 mov eax,ecx - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] spreadb mov bl,[ebp+SPACEFILLER4] ;read texel1 dsx3: rol eax,6 dsm6: and eax,0xfff @@ -383,13 +395,13 @@ dsrest test esi,esi align 16 dsloop mov ebp,ecx -spstep1d add edx,[ds_xstep] -spstep2d adc ecx,[ds_ystep] +spstep1d add edx,[esp] +spstep2d adc ecx,[esp+4] dsm3: and ebp,0xfc00003f dsx4: rol ebp,6 mov eax,ecx -spstep1e add edx,[ds_xstep] -spstep2e adc ecx,[ds_ystep] +spstep1e add edx,[esp] +spstep2e adc ecx,[esp+4] spreadd mov bl,[ebp+SPACEFILLER4] ;read texel1 dsx5: rol eax,6 dsm5: and eax,0xfff @@ -397,8 +409,8 @@ spmapd mov bl,[ebx+SPACEFILLER4] ;map texel1 mov [edi],bl ;store texel1 mov ebp,ecx spreade mov bl,[eax+SPACEFILLER4] ;read texel2 -spstep1f add edx,[ds_xstep] -spstep2f adc ecx,[ds_ystep] +spstep1f add edx,[esp] +spstep2f adc ecx,[esp+4] dsm4: and ebp,0xfc00003f dsx6: rol ebp,6 spmape mov bl,[ebx+SPACEFILLER4] ;map texel2 @@ -411,14 +423,15 @@ dsx7: rol eax,6 dsm7: and eax,0xfff mov [edi-2],bl ;store texel3 spreadg mov bl,[eax+SPACEFILLER4] ;read texel4 -spstep1g add edx,[ds_xstep] -spstep2g adc ecx,[ds_ystep] +spstep1g add edx,[esp] +spstep2g adc ecx,[esp+4] spmapg mov bl,[ebx+SPACEFILLER4] ;map texel4 dec esi mov [edi-1],bl ;store texel4 jnz near dsloop -dsdone pop esi +dsdone add esp,8 + pop esi pop ebp pop edi pop ebx @@ -439,6 +452,8 @@ GLOBAL R_DrawSpanMaskedP_ASM ; edi: dest ; ebp: scratch ; esi: count +; [esp]: xstep +; [esp+4]: ystep align 16 @@ -454,6 +469,7 @@ R_DrawSpanMaskedP_ASM: push edi push ebp push esi + sub esp,8 mov edi,ecx add edi,[dc_destorg] @@ -465,13 +481,13 @@ dmsy1: shl edx,6 dmsy3: shr ebp,26 xor ebx,ebx lea esi,[eax+1] - mov [ds_xstep],edx + mov [esp],edx mov edx,[ds_ystep] mov ecx,[ds_xfrac] dmsy4: shr ecx,26 dmsm8: and edx,0xffffffc0 or ebp,edx - mov [ds_ystep],ebp + mov [esp+4],ebp mov ebp,[ds_yfrac] mov edx,[ds_xfrac] dmsy2: shl edx,6 @@ -485,8 +501,8 @@ dmsm9: and ebp,0xffffffc0 mov ebp,ecx dmsx1: rol ebp,6 dmsm1: and ebp,0xfff - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] mspreada mov bl,[ebp+SPACEFILLER4] cmp bl,0 je mspskipa @@ -499,13 +515,13 @@ dmseven1 shr esi,1 ; do two more pixels mov ebp,ecx - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] dmsm2: and ebp,0xfc00003f dmsx2: rol ebp,6 mov eax,ecx - add edx,[ds_xstep] - adc ecx,[ds_ystep] + add edx,[esp] + adc ecx,[esp+4] mspreadb mov bl,[ebp+SPACEFILLER4] ;read texel1 dmsx3: rol eax,6 dmsm6: and eax,0xfff @@ -528,13 +544,13 @@ dmsrest test esi,esi align 16 dmsloop mov ebp,ecx -mspstep1d add edx,[ds_xstep] -mspstep2d adc ecx,[ds_ystep] +mspstep1d add edx,[esp] +mspstep2d adc ecx,[esp+4] dmsm3: and ebp,0xfc00003f dmsx4: rol ebp,6 mov eax,ecx -mspstep1e add edx,[ds_xstep] -mspstep2e adc ecx,[ds_ystep] +mspstep1e add edx,[esp] +mspstep2e adc ecx,[esp+4] mspreadd mov bl,[ebp+SPACEFILLER4] ;read texel1 dmsx5: rol eax,6 dmsm5: and eax,0xfff @@ -544,8 +560,8 @@ dmsm5: and eax,0xfff mspmapd mov bl,[ebx+SPACEFILLER4] ;map texel1 mov [edi],bl ;store texel1 mspreade mov bl,[eax+SPACEFILLER4] ;read texel2 -mspstep1f add edx,[ds_xstep] -mspstep2f adc ecx,[ds_ystep] +mspstep1f add edx,[esp] +mspstep2f adc ecx,[esp+4] dmsm4: and ebp,0xfc00003f dmsx6: rol ebp,6 cmp bl,0 @@ -562,8 +578,8 @@ dmsm7: and eax,0xfff mspmapf mov bl,[ebx+SPACEFILLER4] ;map texel3 mov [edi-2],bl ;store texel3 mspreadg mov bl,[eax+SPACEFILLER4] ;read texel4 -mspstep1g add edx,[ds_xstep] -mspstep2g adc ecx,[ds_ystep] +mspstep1g add edx,[esp] +mspstep2g adc ecx,[esp+4] cmp bl,0 je mspskipg mspmapg mov bl,[ebx+SPACEFILLER4] ;map texel4 @@ -571,7 +587,8 @@ mspmapg mov bl,[ebx+SPACEFILLER4] ;map texel4 mspskipg dec esi jnz near dmsloop -dmsdone pop esi +dmsdone add esp,8 + pop esi pop ebp pop edi pop ebx @@ -1738,6 +1755,10 @@ ac4nil: pop edi ret rtext_end: +%ifdef M_TARGET_MACHO +GLOBAL _rtext_tmap_end +_rtext_tmap_end: +%endif align 16 ;************************ diff --git a/src/asm_ia32/tmap2.asm b/src/asm_ia32/tmap2.asm index 7f6ed82da..e1f166878 100644 --- a/src/asm_ia32/tmap2.asm +++ b/src/asm_ia32/tmap2.asm @@ -216,7 +216,13 @@ SetTiltedSpanSize: ret +%ifndef M_TARGET_MACHO SECTION .rtext progbits alloc exec write align=64 +%else + SECTION .text align=64 +GLOBAL _rtext_tmap2_start +_rtext_tmap2_start: +%endif rtext_start: @@ -628,3 +634,7 @@ fetch10 mov al,[ebp+esi+SPACEFILLER4] ret rtext_end: +%ifdef M_TARGET_MACHO +GLOBAL _rtext_tmap2_end +_rtext_tmap2_end: +%endif diff --git a/src/asm_ia32/tmap3.asm b/src/asm_ia32/tmap3.asm index 49444419c..3161ff368 100644 --- a/src/asm_ia32/tmap3.asm +++ b/src/asm_ia32/tmap3.asm @@ -80,7 +80,13 @@ setupvlinetallasm: selfmod shifter1, shift12+6 ret +%ifdef M_TARGET_MACHO + SECTION .text align=64 +GLOBAL _rtext_tmap3_start +_rtext_tmap3_start: +%else SECTION .rtext progbits alloc exec write align=64 +%endif ALIGN 16 @@ -331,3 +337,8 @@ shift12: shr ecx,16 pop ebx pop ebp ret + +%ifdef M_TARGET_MACHO +GLOBAL _rtext_tmap3_end +_rtext_tmap3_end: +%endif diff --git a/src/asm_x86_64/tmap3.s b/src/asm_x86_64/tmap3.s index 8a9b52e48..867d11c75 100644 --- a/src/asm_x86_64/tmap3.s +++ b/src/asm_x86_64/tmap3.s @@ -1,28 +1,28 @@ -#%include "valgrind.inc" +#%include "valgrind.inc" - .section .text + .section .text -.globl ASM_PatchPitch -ASM_PatchPitch: +.globl ASM_PatchPitch +ASM_PatchPitch: movl dc_pitch(%rip), %ecx movl %ecx, pm+3(%rip) - movl %ecx, vltpitch+3(%rip) -# selfmod pm, vltpitch+6 - ret - .align 16 + movl %ecx, vltpitch+3(%rip) +# selfmod pm, vltpitch+6 + ret + .align 16 -.globl setupvlinetallasm +.globl setupvlinetallasm setupvlinetallasm: movb %dil, shifter1+2(%rip) movb %dil, shifter2+2(%rip) movb %dil, shifter3+2(%rip) - movb %dil, shifter4+2(%rip) -# selfmod shifter1, shifter4+3 + movb %dil, shifter4+2(%rip) +# selfmod shifter1, shifter4+3 ret - .align 16 - - .section .rtext,"awx" - + .align 16 + + .section .rtext,"awx" + .globl vlinetallasm4 .type vlinetallasm4,@function vlinetallasm4: @@ -38,18 +38,18 @@ vlinetallasm4: subq $8, %rsp # Does the stack need to be 16-byte aligned for Linux? .cfi_adjust_cfa_offset 8 -# rax = bufplce base address -# rbx = -# rcx = offset from rdi/count (negative) -# edx/rdx = scratch -# rdi = bottom of columns to write to -# r8d-r11d = column offsets -# r12-r15 = palookupoffse[0] - palookupoffse[4] +# rax = bufplce base address +# rbx = +# rcx = offset from rdi/count (negative) +# edx/rdx = scratch +# rdi = bottom of columns to write to +# r8d-r11d = column offsets +# r12-r15 = palookupoffse[0] - palookupoffse[4] movl dc_count(%rip), %ecx movq dc_dest(%rip), %rdi testl %ecx, %ecx - jle vltepilog # count must be positive + jle vltepilog # count must be positive movq bufplce(%rip), %rax movq bufplce+8(%rip), %r8 @@ -60,14 +60,14 @@ vlinetallasm4: subq %rax, %r10 movl %r8d, source2+4(%rip) movl %r9d, source3+4(%rip) - movl %r10d, source4+4(%rip) + movl %r10d, source4+4(%rip) -pm: imulq $320, %rcx +pm: imulq $320, %rcx movq palookupoffse(%rip), %r12 movq palookupoffse+8(%rip), %r13 movq palookupoffse+16(%rip), %r14 - movq palookupoffse+24(%rip), %r15 + movq palookupoffse+24(%rip), %r15 movl vince(%rip), %r8d movl vince+4(%rip), %r9d @@ -76,53 +76,53 @@ pm: imulq $320, %rcx movl %r8d, step1+3(%rip) movl %r9d, step2+3(%rip) movl %r10d, step3+3(%rip) - movl %r11d, step4+3(%rip) + movl %r11d, step4+3(%rip) addq %rcx, %rdi - negq %rcx + negq %rcx movl vplce(%rip), %r8d movl vplce+4(%rip), %r9d movl vplce+8(%rip), %r10d movl vplce+12(%rip), %r11d # selfmod loopit, vltepilog - jmp loopit + jmp loopit - .align 16 + .align 16 loopit: movl %r8d, %edx shifter1: shrl $24, %edx -step1: addl $0x88888888, %r8d +step1: addl $0x44444444, %r8d movzbl (%rax,%rdx), %edx movl %r9d, %ebx movb (%r12,%rdx), %dl shifter2: shrl $24, %ebx -step2: addl $0x88888888, %r9d -source2: movzbl 0x88888888(%rax,%rbx), %ebx +step2: addl $0x44444444, %r9d +source2: movzbl 0x44444444(%rax,%rbx), %ebx movl %r10d, %ebp movb (%r13,%rbx), %bl shifter3: shr $24, %ebp -step3: addl $0x88888888, %r10d -source3: movzbl 0x88888888(%rax,%rbp), %ebp +step3: addl $0x44444444, %r10d +source3: movzbl 0x44444444(%rax,%rbp), %ebp movl %r11d, %esi movb (%r14,%rbp), %bpl shifter4: shr $24, %esi -step4: add $0x88888888, %r11d -source4: movzbl 0x88888888(%rax,%rsi), %esi +step4: add $0x44444444, %r11d +source4: movzbl 0x44444444(%rax,%rsi), %esi movb %dl, (%rdi,%rcx) movb %bl, 1(%rdi,%rcx) movb (%r15,%rsi), %sil movb %bpl, 2(%rdi,%rcx) movb %sil, 3(%rdi,%rcx) -vltpitch: addq $320, %rcx - jl loopit +vltpitch: addq $320, %rcx + jl loopit movl %r8d, vplce(%rip) movl %r9d, vplce+4(%rip) movl %r10d, vplce+8(%rip) - movl %r11d, vplce+12(%rip) - + movl %r11d, vplce+12(%rip) + vltepilog: addq $8, %rsp .cfi_adjust_cfa_offset -8 @@ -137,5 +137,5 @@ vltepilog: ret .cfi_endproc .align 16 - + diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 8a64e2366..8b3018b3f 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -67,8 +67,6 @@ CCMD (removebots) Net_WriteByte (DEM_KILLBOTS); } -extern bool CheckCheatmode (); - CCMD (freeze) { if (CheckCheatmode ()) diff --git a/src/basicinlines.h b/src/basicinlines.h index fa6ce822b..80f861c22 100644 --- a/src/basicinlines.h +++ b/src/basicinlines.h @@ -11,8 +11,8 @@ #pragma once #endif -#ifndef _MSC_VER -#define __forceinline inline +#if defined(__GNUC__) && !defined(__forceinline) +#define __forceinline __inline__ __attribute__((always_inline)) #endif static __forceinline SDWORD Scale (SDWORD a, SDWORD b, SDWORD c) diff --git a/src/c_bind.cpp b/src/c_bind.cpp index d96b5a742..b9489ca65 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -47,12 +47,6 @@ #include #include -struct FBinding -{ - const char *Key; - const char *Bind; -}; - /* Default keybindings for Doom (and all other games) */ static const FBinding DefBindings[] = @@ -178,6 +172,29 @@ static const FBinding DefStrifeBindings[] = // h - use health }; +static const FBinding DefAutomapBindings[] = +{ + { "f", "am_togglefollow" }, + { "g", "am_togglegrid" }, + { "p", "am_toggletexture" }, + { "m", "am_setmark" }, + { "c", "am_clearmarks" }, + { "0", "am_gobig" }, + { "rightarrow", "+am_panright" }, + { "leftarrow", "+am_panleft" }, + { "uparrow", "+am_panup" }, + { "downarrow", "+am_pandown" }, + { "-", "+am_zoomout" }, + { "=", "+am_zoomin" }, + { "kp-", "+am_zoomout" }, + { "kp+", "+am_zoomin" }, + { "mwheelup", "am_zoom 1.2" }, + { "mwheeldown", "am_zoom -1.2" }, + { NULL } +}; + + + const char *KeyNames[NUM_KEYS] = { // This array is dependant on the particular keyboard input @@ -278,11 +295,19 @@ const char *KeyNames[NUM_KEYS] = "pad_a", "pad_b", "pad_x", "pad_y" }; -static FString Bindings[NUM_KEYS]; -static FString DoubleBindings[NUM_KEYS]; +FKeyBindings Bindings; +FKeyBindings DoubleBindings; +FKeyBindings AutomapBindings; + static unsigned int DClickTime[NUM_KEYS]; static BYTE DClicked[(NUM_KEYS+7)/8]; +//============================================================================= +// +// +// +//============================================================================= + static int GetKeyFromName (const char *name) { int i; @@ -302,380 +327,15 @@ static int GetKeyFromName (const char *name) return 0; } -static const char *KeyName (int key) -{ - static char name[5]; - - if (KeyNames[key]) - return KeyNames[key]; - - mysnprintf (name, countof(name), "#%d", key); - return name; -} - -void C_UnbindAll () -{ - for (int i = 0; i < NUM_KEYS; ++i) - { - Bindings[i] = ""; - DoubleBindings[i] = ""; - } -} - -CCMD (unbindall) -{ - C_UnbindAll (); -} - -CCMD (unbind) -{ - int i; - - if (argv.argc() > 1) - { - if ( (i = GetKeyFromName (argv[1])) ) - { - Bindings[i] = ""; - } - else - { - Printf ("Unknown key \"%s\"\n", argv[1]); - return; - } - - } -} - -CCMD (bind) -{ - int i; - - if (argv.argc() > 1) - { - i = GetKeyFromName (argv[1]); - if (!i) - { - Printf ("Unknown key \"%s\"\n", argv[1]); - return; - } - if (argv.argc() == 2) - { - Printf ("\"%s\" = \"%s\"\n", argv[1], Bindings[i].GetChars()); - } - else - { - Bindings[i] = argv[2]; - } - } - else - { - Printf ("Current key bindings:\n"); - - for (i = 0; i < NUM_KEYS; i++) - { - if (!Bindings[i].IsEmpty()) - Printf ("%s \"%s\"\n", KeyName (i), Bindings[i].GetChars()); - } - } -} - -//========================================================================== +//============================================================================= // -// CCMD defaultbind // -// Binds a command to a key if that key is not already bound and if -// that command is not already bound to another key. // -//========================================================================== +//============================================================================= -CCMD (defaultbind) +static int GetConfigKeyFromName (const char *key) { - if (argv.argc() < 3) - { - Printf ("Usage: defaultbind \n"); - } - else - { - int key = GetKeyFromName (argv[1]); - if (key == 0) - { - Printf ("Unknown key \"%s\"\n", argv[1]); - return; - } - if (!Bindings[key].IsEmpty()) - { // This key is already bound. - return; - } - for (int i = 0; i < NUM_KEYS; ++i) - { - if (!Bindings[i].IsEmpty() && stricmp (Bindings[i], argv[2]) == 0) - { // This command is already bound to a key. - return; - } - } - // It is safe to do the bind, so do it. - Bindings[key] = argv[2]; - } -} - -CCMD (undoublebind) -{ - int i; - - if (argv.argc() > 1) - { - if ( (i = GetKeyFromName (argv[1])) ) - { - DoubleBindings[i] = ""; - } - else - { - Printf ("Unknown key \"%s\"\n", argv[1]); - return; - } - - } -} - -CCMD (doublebind) -{ - int i; - - if (argv.argc() > 1) - { - i = GetKeyFromName (argv[1]); - if (!i) - { - Printf ("Unknown key \"%s\"\n", argv[1]); - return; - } - if (argv.argc() == 2) - { - Printf ("\"%s\" = \"%s\"\n", argv[1], DoubleBindings[i].GetChars()); - } - else - { - DoubleBindings[i] = argv[2]; - } - } - else - { - Printf ("Current key doublebindings:\n"); - - for (i = 0; i < NUM_KEYS; i++) - { - if (!DoubleBindings[i].IsEmpty()) - Printf ("%s \"%s\"\n", KeyName (i), DoubleBindings[i].GetChars()); - } - } -} - -CCMD (rebind) -{ - FString *bindings; - - if (key == 0) - { - Printf ("Rebind cannot be used from the console\n"); - return; - } - - if (key & KEY_DBLCLICKED) - { - bindings = DoubleBindings; - key &= KEY_DBLCLICKED-1; - } - else - { - bindings = Bindings; - } - - if (argv.argc() > 1) - { - bindings[key] = argv[1]; - } -} - -static void SetBinds (const FBinding *array) -{ - while (array->Key) - { - C_DoBind (array->Key, array->Bind, false); - array++; - } -} - -void C_BindDefaults () -{ - SetBinds (DefBindings); - - if (gameinfo.gametype & (GAME_Raven|GAME_Strife)) - { - SetBinds (DefRavenBindings); - } - - if (gameinfo.gametype == GAME_Heretic) - { - SetBinds (DefHereticBindings); - } - - if (gameinfo.gametype == GAME_Hexen) - { - SetBinds (DefHexenBindings); - } - - if (gameinfo.gametype == GAME_Strife) - { - SetBinds (DefStrifeBindings); - } -} - -CCMD(binddefaults) -{ - C_BindDefaults (); -} - -void C_SetDefaultBindings () -{ - C_UnbindAll (); - C_BindDefaults (); -} - -bool C_DoKey (event_t *ev) -{ - FString binding; - bool dclick; - int dclickspot; - BYTE dclickmask; - - if (ev->type != EV_KeyDown && ev->type != EV_KeyUp) - return false; - - if ((unsigned int)ev->data1 >= NUM_KEYS) - return false; - - dclickspot = ev->data1 >> 3; - dclickmask = 1 << (ev->data1 & 7); - dclick = false; - - // This used level.time which didn't work outside a level. - if (DClickTime[ev->data1] > I_MSTime() && ev->type == EV_KeyDown) - { - // Key pressed for a double click - binding = DoubleBindings[ev->data1]; - DClicked[dclickspot] |= dclickmask; - dclick = true; - } - else - { - if (ev->type == EV_KeyDown) - { // Key pressed for a normal press - binding = Bindings[ev->data1]; - DClickTime[ev->data1] = I_MSTime() + 571; - } - else if (DClicked[dclickspot] & dclickmask) - { // Key released from a double click - binding = DoubleBindings[ev->data1]; - DClicked[dclickspot] &= ~dclickmask; - DClickTime[ev->data1] = 0; - dclick = true; - } - else - { // Key released from a normal press - binding = Bindings[ev->data1]; - } - } - - - if (binding.IsEmpty()) - { - binding = Bindings[ev->data1]; - dclick = false; - } - - if (!binding.IsEmpty() && (chatmodeon == 0 || ev->data1 < 256)) - { - if (ev->type == EV_KeyUp && binding[0] != '+') - { - return false; - } - - char *copy = binding.LockBuffer(); - - if (ev->type == EV_KeyUp) - { - copy[0] = '-'; - } - - AddCommandString (copy, dclick ? ev->data1 | KEY_DBLCLICKED : ev->data1); - return true; - } - return false; -} - -const char *C_ConfigKeyName(int keynum) -{ - const char *name = KeyName(keynum); - if (name[1] == 0) // Make sure given name is config-safe - { - if (name[0] == '[') - return "LeftBracket"; - else if (name[0] == ']') - return "RightBracket"; - else if (name[0] == '=') - return "Equals"; - else if (strcmp (name, "kp=") == 0) - return "KP-Equals"; - } - return name; -} - -// This function is first called for functions in custom key sections. -// In this case, matchcmd is non-NULL, and only keys bound to that command -// are stored. If a match is found, its binding is set to "\1". -// After all custom key sections are saved, it is called one more for the -// normal Bindings and DoubleBindings sections for this game. In this case -// matchcmd is NULL and all keys will be stored. The config section was not -// previously cleared, so all old bindings are still in place. If the binding -// for a key is empty, the corresponding key in the config is removed as well. -// If a binding is "\1", then the binding itself is cleared, but nothing -// happens to the entry in the config. -void C_ArchiveBindings (FConfigFile *f, bool dodouble, const char *matchcmd) -{ - FString *bindings; - int i; - - bindings = dodouble ? DoubleBindings : Bindings; - - for (i = 0; i < NUM_KEYS; i++) - { - if (bindings[i].IsEmpty()) - { - if (matchcmd == NULL) - { - f->ClearKey(C_ConfigKeyName(i)); - } - } - else if (matchcmd == NULL || stricmp(bindings[i], matchcmd) == 0) - { - if (bindings[i][0] == '\1') - { - bindings[i] = ""; - continue; - } - f->SetValueForKey(C_ConfigKeyName(i), bindings[i]); - if (matchcmd != NULL) - { // If saving a specific command, set a marker so that - // it does not get saved in the general binding list. - bindings[i] = "\1"; - } - } - } -} - -void C_DoBind (const char *key, const char *bind, bool dodouble) -{ - int keynum = GetKeyFromName (key); + int keynum = GetKeyFromName(key); if (keynum == 0) { if (stricmp (key, "LeftBracket") == 0) @@ -695,32 +355,55 @@ void C_DoBind (const char *key, const char *bind, bool dodouble) keynum = GetKeyFromName ("kp="); } } - if (keynum != 0) - { - (dodouble ? DoubleBindings : Bindings)[keynum] = bind; - } + return keynum; } -int C_GetKeysForCommand (char *cmd, int *first, int *second) +//============================================================================= +// +// +// +//============================================================================= + +static const char *KeyName (int key) { - int c, i; + static char name[5]; - *first = *second = c = i = 0; + if (KeyNames[key]) + return KeyNames[key]; - while (i < NUM_KEYS && c < 2) - { - if (stricmp (cmd, Bindings[i]) == 0) - { - if (c++ == 0) - *first = i; - else - *second = i; - } - i++; - } - return c; + mysnprintf (name, countof(name), "#%d", key); + return name; } +//============================================================================= +// +// +// +//============================================================================= + +static const char *ConfigKeyName(int keynum) +{ + const char *name = KeyName(keynum); + if (name[1] == 0) // Make sure given name is config-safe + { + if (name[0] == '[') + return "LeftBracket"; + else if (name[0] == ']') + return "RightBracket"; + else if (name[0] == '=') + return "Equals"; + else if (strcmp (name, "kp=") == 0) + return "KP-Equals"; + } + return name; +} + +//============================================================================= +// +// +// +//============================================================================= + void C_NameKeys (char *str, int first, int second) { int c = 0; @@ -744,28 +427,471 @@ void C_NameKeys (char *str, int first, int second) *str = '\0'; } -void C_UnbindACommand (char *str) +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::DoBind (const char *key, const char *bind) +{ + int keynum = GetConfigKeyFromName (key); + if (keynum != 0) + { + Binds[keynum] = bind; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::SetBinds(const FBinding *binds) +{ + while (binds->Key) + { + DoBind (binds->Key, binds->Bind); + binds++; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindAll () +{ + for (int i = 0; i < NUM_KEYS; ++i) + { + Binds[i] = ""; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindKey(const char *key) +{ + int i; + + if ( (i = GetKeyFromName (key)) ) + { + Binds[i] = ""; + } + else + { + Printf ("Unknown key \"%s\"\n", key); + return; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::PerformBind(FCommandLine &argv, const char *msg) +{ + int i; + + if (argv.argc() > 1) + { + i = GetKeyFromName (argv[1]); + if (!i) + { + Printf ("Unknown key \"%s\"\n", argv[1]); + return; + } + if (argv.argc() == 2) + { + Printf ("\"%s\" = \"%s\"\n", argv[1], Binds[i].GetChars()); + } + else + { + Binds[i] = argv[2]; + } + } + else + { + Printf ("%s:\n", msg); + + for (i = 0; i < NUM_KEYS; i++) + { + if (!Binds[i].IsEmpty()) + Printf ("%s \"%s\"\n", KeyName (i), Binds[i].GetChars()); + } + } +} + + +//============================================================================= +// +// This function is first called for functions in custom key sections. +// In this case, matchcmd is non-NULL, and only keys bound to that command +// are stored. If a match is found, its binding is set to "\1". +// After all custom key sections are saved, it is called one more for the +// normal Bindings and DoubleBindings sections for this game. In this case +// matchcmd is NULL and all keys will be stored. The config section was not +// previously cleared, so all old bindings are still in place. If the binding +// for a key is empty, the corresponding key in the config is removed as well. +// If a binding is "\1", then the binding itself is cleared, but nothing +// happens to the entry in the config. +// +//============================================================================= + +void FKeyBindings::ArchiveBindings(FConfigFile *f, const char *matchcmd) { int i; for (i = 0; i < NUM_KEYS; i++) { - if (!stricmp (str, Bindings[i])) + if (Binds[i].IsEmpty()) { - Bindings[i] = ""; + if (matchcmd == NULL) + { + f->ClearKey(ConfigKeyName(i)); + } + } + else if (matchcmd == NULL || stricmp(Binds[i], matchcmd) == 0) + { + if (Binds[i][0] == '\1') + { + Binds[i] = ""; + continue; + } + f->SetValueForKey(ConfigKeyName(i), Binds[i]); + if (matchcmd != NULL) + { // If saving a specific command, set a marker so that + // it does not get saved in the general binding list. + Binds[i] = "\1"; + } } } } -void C_ChangeBinding (const char *str, int newone) +//============================================================================= +// +// +// +//============================================================================= + +int FKeyBindings::GetKeysForCommand (char *cmd, int *first, int *second) { - if ((unsigned int)newone < NUM_KEYS) + int c, i; + + *first = *second = c = i = 0; + + while (i < NUM_KEYS && c < 2) { - Bindings[newone] = str; + if (stricmp (cmd, Binds[i]) == 0) + { + if (c++ == 0) + *first = i; + else + *second = i; + } + i++; + } + return c; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindACommand (char *str) +{ + int i; + + for (i = 0; i < NUM_KEYS; i++) + { + if (!stricmp (str, Binds[i])) + { + Binds[i] = ""; + } } } -const char *C_GetBinding (int key) +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::DefaultBind(const char *keyname, const char *cmd) { - return (unsigned int)key < NUM_KEYS ? Bindings[key].GetChars() : NULL; + int key = GetKeyFromName (keyname); + if (key == 0) + { + Printf ("Unknown key \"%s\"\n", keyname); + return; + } + if (!Binds[key].IsEmpty()) + { // This key is already bound. + return; + } + for (int i = 0; i < NUM_KEYS; ++i) + { + if (!Binds[i].IsEmpty() && stricmp (Binds[i], cmd) == 0) + { // This command is already bound to a key. + return; + } + } + // It is safe to do the bind, so do it. + Binds[key] = cmd; } + +//============================================================================= +// +// +// +//============================================================================= + +void C_UnbindAll () +{ + Bindings.UnbindAll(); + DoubleBindings.UnbindAll(); + AutomapBindings.UnbindAll(); +} + +CCMD (unbindall) +{ + C_UnbindAll (); +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (unbind) +{ + if (argv.argc() > 1) + { + Bindings.UnbindKey(argv[1]); + } +} + +CCMD (undoublebind) +{ + if (argv.argc() > 1) + { + DoubleBindings.UnbindKey(argv[1]); + } +} + +CCMD (unmapbind) +{ + if (argv.argc() > 1) + { + AutomapBindings.UnbindKey(argv[1]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (bind) +{ + Bindings.PerformBind(argv, "Current key bindings"); +} + +CCMD (doublebind) +{ + DoubleBindings.PerformBind(argv, "Current key doublebindings"); +} + +CCMD (mapbind) +{ + AutomapBindings.PerformBind(argv, "Current automap key bindings"); +} + +//========================================================================== +// +// CCMD defaultbind +// +// Binds a command to a key if that key is not already bound and if +// that command is not already bound to another key. +// +//========================================================================== + +CCMD (defaultbind) +{ + if (argv.argc() < 3) + { + Printf ("Usage: defaultbind \n"); + } + else + { + Bindings.DefaultBind(argv[1], argv[2]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (rebind) +{ + FKeyBindings *bindings; + + if (key == 0) + { + Printf ("Rebind cannot be used from the console\n"); + return; + } + + if (key & KEY_DBLCLICKED) + { + bindings = &DoubleBindings; + key &= KEY_DBLCLICKED-1; + } + else + { + bindings = &Bindings; + } + + if (argv.argc() > 1) + { + bindings->SetBind(key, argv[1]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void C_BindDefaults () +{ + Bindings.SetBinds (DefBindings); + + if (gameinfo.gametype & (GAME_Raven|GAME_Strife)) + { + Bindings.SetBinds (DefRavenBindings); + } + + if (gameinfo.gametype == GAME_Heretic) + { + Bindings.SetBinds (DefHereticBindings); + } + + if (gameinfo.gametype == GAME_Hexen) + { + Bindings.SetBinds (DefHexenBindings); + } + + if (gameinfo.gametype == GAME_Strife) + { + Bindings.SetBinds (DefStrifeBindings); + } + + AutomapBindings.SetBinds(DefAutomapBindings); +} + +CCMD(binddefaults) +{ + C_BindDefaults (); +} + +void C_SetDefaultBindings () +{ + C_UnbindAll (); + C_BindDefaults (); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds) +{ + FString binding; + bool dclick; + int dclickspot; + BYTE dclickmask; + + if (ev->type != EV_KeyDown && ev->type != EV_KeyUp) + return false; + + if ((unsigned int)ev->data1 >= NUM_KEYS) + return false; + + dclickspot = ev->data1 >> 3; + dclickmask = 1 << (ev->data1 & 7); + dclick = false; + + // This used level.time which didn't work outside a level. + if (DClickTime[ev->data1] > I_MSTime() && ev->type == EV_KeyDown) + { + // Key pressed for a double click + if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1); + DClicked[dclickspot] |= dclickmask; + dclick = true; + } + else + { + if (ev->type == EV_KeyDown) + { // Key pressed for a normal press + binding = binds->GetBinding(ev->data1); + DClickTime[ev->data1] = I_MSTime() + 571; + } + else if (DClicked[dclickspot] & dclickmask) + { // Key released from a double click + if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1); + DClicked[dclickspot] &= ~dclickmask; + DClickTime[ev->data1] = 0; + dclick = true; + } + else + { // Key released from a normal press + binding = binds->GetBinding(ev->data1); + } + } + + + if (binding.IsEmpty()) + { + binding = binds->GetBinding(ev->data1); + dclick = false; + } + + if (!binding.IsEmpty() && (chatmodeon == 0 || ev->data1 < 256)) + { + if (ev->type == EV_KeyUp && binding[0] != '+') + { + return false; + } + + char *copy = binding.LockBuffer(); + + if (ev->type == EV_KeyUp) + { + copy[0] = '-'; + } + + AddCommandString (copy, dclick ? ev->data1 | KEY_DBLCLICKED : ev->data1); + return true; + } + return false; +} + diff --git a/src/c_bind.h b/src/c_bind.h index 2b46118cf..7d71b462a 100644 --- a/src/c_bind.h +++ b/src/c_bind.h @@ -34,25 +34,65 @@ #ifndef __C_BINDINGS_H__ #define __C_BINDINGS_H__ +#include "doomdef.h" struct event_t; class FConfigFile; +class FCommandLine; -bool C_DoKey (event_t *ev); -void C_ArchiveBindings (FConfigFile *f, bool dodouble, const char *matchcmd=NULL); +void C_NameKeys (char *str, int first, int second); + +struct FBinding +{ + const char *Key; + const char *Bind; +}; + +class FKeyBindings +{ + FString Binds[NUM_KEYS]; + +public: + void PerformBind(FCommandLine &argv, const char *msg); + void SetBinds(const FBinding *binds); + bool DoKey(event_t *ev); + void ArchiveBindings(FConfigFile *F, const char *matchcmd = NULL); + int GetKeysForCommand (char *cmd, int *first, int *second); + void UnbindACommand (char *str); + void UnbindAll (); + void UnbindKey(const char *key); + void DoBind (const char *key, const char *bind); + void DefaultBind(const char *keyname, const char *cmd); + + void SetBind(unsigned int key, const char *bind) + { + if (key < NUM_KEYS) Binds[key] = bind; + } + + const FString &GetBinding(unsigned int index) const + { + return Binds[index]; + } + + const char *GetBind(unsigned int index) const + { + if (index < NUM_KEYS) return Binds[index]; + else return NULL; + } + +}; + +extern FKeyBindings Bindings; +extern FKeyBindings DoubleBindings; +extern FKeyBindings AutomapBindings; + + +bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds); // Stuff used by the customize controls menu -int C_GetKeysForCommand (char *cmd, int *first, int *second); -void C_NameKeys (char *str, int first, int second); -void C_UnbindACommand (char *str); -void C_ChangeBinding (const char *str, int newone); -void C_DoBind (const char *key, const char *bind, bool doublebind); void C_SetDefaultBindings (); void C_UnbindAll (); -// Returns string bound to given key (NULL if none) -const char *C_GetBinding (int key); - extern const char *KeyNames[]; #endif //__C_BINDINGS_H__ diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 68c0943b1..17a5125af 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -80,11 +80,11 @@ CCMD (toggleconsole) C_ToggleConsole(); } -bool CheckCheatmode () +bool CheckCheatmode (bool printmsg) { if ((G_SkillProperty(SKILLP_DisableCheats) || netgame || deathmatch) && (!sv_cheats)) { - Printf ("sv_cheats must be true to enable this command.\n"); + if (printmsg) Printf ("sv_cheats must be true to enable this command.\n"); return true; } else diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 2fbafd2a9..413b8d64c 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -119,7 +119,9 @@ FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, Button_Forward, Button_Right, Button_Left, Button_MoveDown, Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch, Button_Zoom, Button_Reload, - Button_User1, Button_User2, Button_User3, Button_User4; + Button_User1, Button_User2, Button_User3, Button_User4, + Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp, + Button_AM_ZoomIn, Button_AM_ZoomOut; bool ParsingKeyConf; @@ -131,13 +133,16 @@ bool ParsingKeyConf; FActionMap ActionMaps[] = { + { 0x0d52d67b, &Button_AM_PanLeft, "am_panleft"}, { 0x125f5226, &Button_User2, "user2" }, { 0x1eefa611, &Button_Jump, "jump" }, { 0x201f1c55, &Button_Right, "right" }, { 0x20ccc4d5, &Button_Zoom, "zoom" }, { 0x23a99cd7, &Button_Back, "back" }, + { 0x41df90c2, &Button_AM_ZoomIn, "am_zoomin"}, { 0x426b69e7, &Button_Reload, "reload" }, { 0x4463f43a, &Button_LookDown, "lookdown" }, + { 0x51f7a334, &Button_AM_ZoomOut, "am_zoomout"}, { 0x534c30ee, &Button_User4, "user4" }, { 0x5622bf42, &Button_Attack, "attack" }, { 0x577712d0, &Button_User1, "user1" }, @@ -147,12 +152,15 @@ FActionMap ActionMaps[] = { 0x676885b8, &Button_AltAttack, "altattack" }, { 0x6fa41b84, &Button_MoveLeft, "moveleft" }, { 0x818f08e6, &Button_MoveRight, "moveright" }, + { 0x8197097b, &Button_AM_PanRight, "am_panright"}, + { 0x8d89955e, &Button_AM_PanUp, "am_panup"} , { 0xa2b62d8b, &Button_Mlook, "mlook" }, { 0xab2c3e71, &Button_Crouch, "crouch" }, { 0xb000b483, &Button_Left, "left" }, { 0xb62b1e49, &Button_LookUp, "lookup" }, { 0xb6f8fe92, &Button_User3, "user3" }, { 0xb7e6a54b, &Button_Strafe, "strafe" }, + { 0xce301c81, &Button_AM_PanDown, "am_pandown"}, { 0xd5897c73, &Button_ShowScores, "showscores" }, { 0xe0ccb317, &Button_Speed, "speed" }, { 0xe0cfc260, &Button_Use, "use" }, @@ -160,6 +168,7 @@ FActionMap ActionMaps[] = }; #define NUM_ACTIONS countof(ActionMaps) + // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const char *KeyConfCommands[] = diff --git a/src/c_dispatch.h b/src/c_dispatch.h index df6fe1348..dbce97538 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -39,6 +39,8 @@ class FConfigFile; class APlayerPawn; +extern bool CheckCheatmode (bool printmsg = true); + void C_ExecCmdLineParams (); // Add commands to the console as if they were typed in. Can handle wait @@ -144,6 +146,7 @@ struct FButtonStatus bool PressKey (int keynum); // Returns true if this key caused the button to be pressed. bool ReleaseKey (int keynum); // Returns true if this key is no longer pressed. void ResetTriggers () { bWentDown = bWentUp = false; } + void Reset () { bDown = bWentDown = bWentUp = false; } }; extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, @@ -152,7 +155,9 @@ extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, Button_Forward, Button_Right, Button_Left, Button_MoveDown, Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch, Button_Zoom, Button_Reload, - Button_User1, Button_User2, Button_User3, Button_User4; + Button_User1, Button_User2, Button_User3, Button_User4, + Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp, + Button_AM_ZoomIn, Button_AM_ZoomOut; extern bool ParsingKeyConf; void ResetButtonTriggers (); // Call ResetTriggers for all buttons diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index dd32cdb55..36a900709 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -2,10 +2,14 @@ #ifdef _WIN32 #include +#include #else #include #include #include +#if !defined(__sun) +#include +#endif #endif #include "doomtype.h" #include "cmdlib.h" @@ -90,6 +94,23 @@ char *copystring (const char *s) return b; } +//============================================================================ +// +// ncopystring +// +// If the string has no content, returns NULL. Otherwise, returns a copy. +// +//============================================================================ + +char *ncopystring (const char *string) +{ + if (string == NULL || string[0] == 0) + { + return NULL; + } + return copystring (string); +} + //========================================================================== // // ReplaceString @@ -868,3 +889,153 @@ FString NicePath(const char *path) return where; #endif } + + +#ifdef _WIN32 + +//========================================================================== +// +// ScanDirectory +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + struct _finddata_t fileinfo; + intptr_t handle; + FString dirmatch; + + dirmatch << dirpath << "*"; + + if ((handle = _findfirst(dirmatch, &fileinfo)) == -1) + { + I_Error("Could not scan '%s': %s\n", dirpath, strerror(errno)); + } + else + { + do + { + if (fileinfo.attrib & _A_HIDDEN) + { + // Skip hidden files and directories. (Prevents SVN bookkeeping + // info from being included.) + continue; + } + + if (fileinfo.attrib & _A_SUBDIR) + { + if (fileinfo.name[0] == '.' && + (fileinfo.name[1] == '\0' || + (fileinfo.name[1] == '.' && fileinfo.name[2] == '\0'))) + { + // Do not record . and .. directories. + continue; + } + + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << fileinfo.name; + fl->isDirectory = true; + FString newdir = fl->Filename; + newdir << "/"; + ScanDirectory(list, newdir); + } + else + { + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << fileinfo.name; + fl->isDirectory = false; + } + } + while (_findnext(handle, &fileinfo) == 0); + _findclose(handle); + } +} + +#elif defined(__sun) || defined(linux) + +//========================================================================== +// +// ScanDirectory +// Solaris version +// +// Given NULL-terminated array of directory paths, create trees for them. +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + DIR *directory = opendir(dirpath); + if(directory == NULL) + return; + + struct dirent *file; + while((file = readdir(directory)) != NULL) + { + if(file->d_name[0] == '.') //File is hidden or ./.. directory so ignore it. + continue; + + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << file->d_name; + + struct stat fileStat; + stat(fl->Filename, &fileStat); + fl->isDirectory = S_ISDIR(fileStat.st_mode); + + if(fl->isDirectory) + { + FString newdir = fl->Filename; + newdir += "/"; + ScanDirectory(list, newdir); + continue; + } + } + + closedir(directory); +} + +#else + +//========================================================================== +// +// ScanDirectory +// 4.4BSD version +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + const char **argv[] = {dirpath, NULL }; + FTS *fts; + FTSENT *ent; + + fts = fts_open(argv, FTS_LOGICAL, NULL); + if (fts == NULL) + { + I_Error("Failed to start directory traversal: %s\n", strerror(errno)); + return; + } + while ((ent = fts_read(fts)) != NULL) + { + if (ent->fts_info == FTS_D && ent->fts_name[0] == '.') + { + // Skip hidden directories. (Prevents SVN bookkeeping + // info from being included.) + fts_set(fts, ent, FTS_SKIP); + } + if (ent->fts_info == FTS_D && ent->fts_level == 0) + { + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename = ent->fts_path; + fl->isDirectory = true; + } + if (ent->fts_info == FTS_F) + { + // We're only interested in remembering files. + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename = ent->fts_path; + fl->isDirectory = false; + } + } + fts_close(fts); +} +#endif diff --git a/src/cmdlib.h b/src/cmdlib.h index fe41204ec..7c29644d5 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -36,6 +36,7 @@ int ParseNum (const char *str); bool IsNum (const char *str); // [RH] added char *copystring(const char *s); +char *ncopystring(const char *s); void ReplaceString (char **ptr, const char *str); bool CheckWildcards (const char *pattern, const char *text); @@ -53,4 +54,12 @@ void CreatePath(const char * fn); FString ExpandEnvVars(const char *searchpathstring); FString NicePath(const char *path); +struct FFileList +{ + FString Filename; + bool isDirectory; +}; + +void ScanDirectory(TArray &list, const char *dirpath); + #endif diff --git a/src/compatibility.cpp b/src/compatibility.cpp index f4fbaa529..ab1c98ca7 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -43,10 +43,13 @@ #include "sc_man.h" #include "cmdlib.h" #include "doomdef.h" +#include "doomdata.h" #include "doomstat.h" #include "c_dispatch.h" #include "gi.h" #include "g_level.h" +#include "p_lnspec.h" +#include "r_state.h" // MACROS ------------------------------------------------------------------ @@ -59,6 +62,14 @@ struct FCompatOption int BCompatFlags; }; +enum +{ + CP_END, + CP_CLEARFLAGS, + CP_SETFLAGS, + CP_SETSPECIAL +}; + // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -77,7 +88,6 @@ static FCompatOption Options[] = { { "setslopeoverflow", 0, BCOMPATF_SETSLOPEOVERFLOW }, { "resetplayerspeed", 0, BCOMPATF_RESETPLAYERSPEED }, - { "spechitoverflow", 0, BCOMPATF_SPECHITOVERFLOW }, { "vileghosts", 0, BCOMPATF_VILEGHOSTS }, // list copied from g_mapinfo.cpp @@ -106,9 +116,15 @@ static FCompatOption Options[] = { "corpsegibs", COMPATF_CORPSEGIBS, 0 }, { "noblockfriends", COMPATF_NOBLOCKFRIENDS, 0 }, { "spritesort", COMPATF_SPRITESORT, 0 }, + { "hitscan", COMPATF_HITSCAN, 0 }, + { "lightlevel", COMPATF_LIGHT, 0 }, + { "polyobj", COMPATF_POLYOBJ, 0 }, { NULL, 0, 0 } }; +static TArray CompatParams; +static int ii_compatparams; + // CODE -------------------------------------------------------------------- //========================================================================== @@ -170,12 +186,57 @@ void ParseCompatibility() } while (!sc.Compare("{")); flags.CompatFlags = 0; flags.BCompatFlags = 0; - while (sc.MustGetString(), (i = sc.MatchString(&Options[0].Name, sizeof(*Options))) >= 0) + flags.ExtCommandIndex = ~0u; + while (sc.GetString()) { - flags.CompatFlags |= Options[i].CompatFlags; - flags.BCompatFlags |= Options[i].BCompatFlags; + if ((i = sc.MatchString(&Options[0].Name, sizeof(*Options))) >= 0) + { + flags.CompatFlags |= Options[i].CompatFlags; + flags.BCompatFlags |= Options[i].BCompatFlags; + } + else if (sc.Compare("clearlineflags")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_CLEARFLAGS); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + } + else if (sc.Compare("setlineflags")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SETFLAGS); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + } + else if (sc.Compare("setlinespecial")) + { + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); + CompatParams.Push(CP_SETSPECIAL); + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + + sc.MustGetString(); + CompatParams.Push(P_FindLineSpecial(sc.String, NULL, NULL)); + for(int i=0;i<5;i++) + { + sc.MustGetNumber(); + CompatParams.Push(sc.Number); + } + } + else + { + sc.UnGet(); + break; + } + } + if (flags.ExtCommandIndex != ~0u) + { + CompatParams.Push(CP_END); } - sc.UnGet(); sc.MustGetStringName("}"); for (j = 0; j < md5array.Size(); ++j) { @@ -196,12 +257,29 @@ void CheckCompatibility(MapData *map) FMD5Holder md5; FCompatValues *flags; - // When playing Doom IWAD levels force COMPAT_SHORTTEX. + // When playing Doom IWAD levels force COMPAT_SHORTTEX and COMPATF_LIGHT. + // I'm not sure if the IWAD maps actually need COMPATF_LIGHT but it certainly does not hurt. + // TNT's MAP31 also needs COMPATF_STAIRINDEX but that only gets activated for TNT.WAD. if (Wads.GetLumpFile(map->lumpnum) == 1 && (gameinfo.flags & GI_COMPATSHORTTEX) && !(level.flags & LEVEL_HEXENFORMAT)) { - ii_compatflags = COMPATF_SHORTTEX; + ii_compatflags = COMPATF_SHORTTEX|COMPATF_LIGHT; + if (gameinfo.flags & GI_COMPATSTAIRS) ii_compatflags |= COMPATF_STAIRINDEX; ib_compatflags = 0; + ii_compatparams = -1; } + else if (Wads.GetLumpFile(map->lumpnum) == 1 && (gameinfo.flags & GI_COMPATPOLY1) && Wads.CheckLumpName(map->lumpnum, "MAP36")) + { + ii_compatflags = COMPATF_POLYOBJ; + ib_compatflags = 0; + ii_compatparams = -1; + } + else if (Wads.GetLumpFile(map->lumpnum) == 2 && (gameinfo.flags & GI_COMPATPOLY2) && Wads.CheckLumpName(map->lumpnum, "MAP47")) + { + ii_compatflags = COMPATF_POLYOBJ; + ib_compatflags = 0; + ii_compatparams = -1; + } + else { map->GetChecksum(md5.Bytes); @@ -223,17 +301,74 @@ void CheckCompatibility(MapData *map) { ii_compatflags = flags->CompatFlags; ib_compatflags = flags->BCompatFlags; + ii_compatparams = flags->ExtCommandIndex; } else { ii_compatflags = 0; ib_compatflags = 0; + ii_compatparams = -1; } } // Reset i_compatflags compatflags.Callback(); } +//========================================================================== +// +// SetCompatibilityParams +// +//========================================================================== + +void SetCompatibilityParams() +{ + if (ii_compatparams != -1) + { + unsigned i = ii_compatparams; + + while (CompatParams[i] != CP_END && i < CompatParams.Size()) + { + switch (CompatParams[i]) + { + case CP_CLEARFLAGS: + { + if (CompatParams[i+1] < numlines) + { + line_t *line = &lines[CompatParams[i+1]]; + line->flags &= ~CompatParams[i+2]; + } + i+=3; + break; + } + case CP_SETFLAGS: + { + if (CompatParams[i+1] < numlines) + { + line_t *line = &lines[CompatParams[i+1]]; + line->flags |= CompatParams[i+2]; + } + i+=3; + break; + } + case CP_SETSPECIAL: + { + if (CompatParams[i+1] < numlines) + { + line_t *line = &lines[CompatParams[i+1]]; + line->special = CompatParams[i+2]; + for(int ii=0;ii<5;ii++) + { + line->args[ii] = CompatParams[i+ii+3]; + } + } + i+=8; + break; + } + } + } + } +} + //========================================================================== // // CCMD mapchecksum diff --git a/src/compatibility.h b/src/compatibility.h index f80314cbd..5726955b5 100644 --- a/src/compatibility.h +++ b/src/compatibility.h @@ -16,6 +16,7 @@ struct FCompatValues { int CompatFlags; int BCompatFlags; + unsigned int ExtCommandIndex; }; struct FMD5HashTraits @@ -37,5 +38,6 @@ extern TMap BCompatMap; void ParseCompatibility(); void CheckCompatibility(MapData *map); +void SetCompatibilityParams(); #endif diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index decd07b05..e268bcad8 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -953,7 +953,7 @@ static int PatchThing (int thingy) // compatibility, the upper bits are freed, but we have conflicts between the ZDoom bits // and the MBF bits. The only such flag exposed to DEHSUPP, though, is STEALTH -- the others // are not available through mnemonics, and aren't available either through their bit value. - // So if we find the STEALTH keyword, it's a ZDoom mod, otherwise assume assume FRIEND. + // So if we find the STEALTH keyword, it's a ZDoom mod, otherwise assume FRIEND. bool zdoomflags = false; char *strval; @@ -1355,8 +1355,8 @@ static int PatchFrame (int frameNum) } info->Tics = tics; info->Misc1 = misc1; - info->Frame = (frame & 0x3f) | - (frame & 0x8000 ? SF_FULLBRIGHT : 0); + info->Frame = frame & 0x3f; + info->Fullbright = frame & 0x8000 ? true : false; } return result; @@ -2106,13 +2106,13 @@ static int PatchText (int oldSize) // This must be done because the map is scanned using a binary search. while (i > 0 && strncmp (DehSpriteMappings[i-1].Sprite, newStr, 4) > 0) { - swap (DehSpriteMappings[i-1], DehSpriteMappings[i]); + swapvalues (DehSpriteMappings[i-1], DehSpriteMappings[i]); --i; } while ((size_t)i < countof(DehSpriteMappings)-1 && strncmp (DehSpriteMappings[i+1].Sprite, newStr, 4) < 0) { - swap (DehSpriteMappings[i+1], DehSpriteMappings[i]); + swapvalues (DehSpriteMappings[i+1], DehSpriteMappings[i]); ++i; } break; diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 3cf9773ce..9a49c533b 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -58,10 +58,10 @@ EIWADType gameiwad; const IWADInfo IWADInfos[NUM_IWAD_TYPES] = { // banner text, autoname, fg color, bg color - { "Final Doom: TNT - Evilution", "TNT", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/tnt.txt", GI_MAPxx | GI_COMPATSHORTTEX }, + { "Final Doom: TNT - Evilution", "TNT", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/tnt.txt", GI_MAPxx | GI_COMPATSHORTTEX | GI_COMPATSTAIRS }, { "Final Doom: Plutonia Experiment", "Plutonia", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/plutonia.txt", GI_MAPxx | GI_COMPATSHORTTEX }, - { "Hexen: Beyond Heretic", NULL, MAKERGB(240,240,240), MAKERGB(107,44,24), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx }, - { "Hexen: Deathkings of the Dark Citadel", "HexenDK", MAKERGB(240,240,240), MAKERGB(139,68,9), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx }, + { "Hexen: Beyond Heretic", NULL, MAKERGB(240,240,240), MAKERGB(107,44,24), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx | GI_COMPATPOLY1 }, + { "Hexen: Deathkings of the Dark Citadel", "HexenDK", MAKERGB(240,240,240), MAKERGB(139,68,9), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx | GI_COMPATPOLY1 | GI_COMPATPOLY2 }, { "Hexen: Demo Version", "HexenDemo",MAKERGB(240,240,240), MAKERGB(107,44,24), GAME_Hexen, "mapinfo/hexen.txt", GI_MAPxx | GI_SHAREWARE }, { "DOOM 2: Hell on Earth", "Doom2", MAKERGB(168,0,0), MAKERGB(168,168,168), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx | GI_COMPATSHORTTEX }, { "Heretic Shareware", NULL, MAKERGB(252,252,0), MAKERGB(168,0,0), GAME_Heretic, "mapinfo/hereticsw.txt",GI_SHAREWARE }, diff --git a/src/d_main.cpp b/src/d_main.cpp index 2f0c071fe..9d7f7a884 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -38,7 +38,7 @@ #endif #include -#ifdef unix +#if defined(unix) || defined(__APPLE__) #include #endif @@ -104,6 +104,7 @@ #include "compatibility.h" #include "m_joy.h" #include "sc_man.h" +#include "po_man.h" #include "resourcefiles/resourcefile.h" EXTERN_CVAR(Bool, hud_althud) @@ -120,7 +121,6 @@ extern void M_SetDefaultMode (); extern void R_ExecuteSetViewSize (); extern void G_NewInit (); extern void SetupPlayerClasses (); -extern bool CheckCheatmode (); const IWADInfo *D_FindIWAD(TArray &wadfiles, const char *iwad, const char *basewad); // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -201,6 +201,7 @@ gamestate_t wipegamestate = GS_DEMOSCREEN; // can be -1 to force a wipe bool PageBlank; FTexture *Page; FTexture *Advisory; +bool nospriterename; cycle_t FrameCycles; @@ -459,6 +460,8 @@ CVAR (Flag, sv_disallowspying, dmflags2, DF2_DISALLOW_SPYING); CVAR (Flag, sv_chasecam, dmflags2, DF2_CHASECAM); CVAR (Flag, sv_disallowsuicide, dmflags2, DF2_NOSUICIDE); CVAR (Flag, sv_noautoaim, dmflags2, DF2_NOAUTOAIM); +CVAR (Flag, sv_dontcheckammo, dmflags2, DF2_DONTCHECKAMMO); +CVAR (Flag, sv_killbossmonst, dmflags2, DF2_KILLBOSSMONST); //========================================================================== // @@ -479,7 +482,12 @@ static int GetCompatibility(int mask) CUSTOM_CVAR (Int, compatflags, 0, CVAR_ARCHIVE|CVAR_SERVERINFO) { + int old = i_compatflags; i_compatflags = GetCompatibility(self) | ii_compatflags; + if ((old ^i_compatflags) & COMPATF_POLYOBJ) + { + FPolyObj::ClearAllSubsectorLinks(); + } } CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL) @@ -495,28 +503,35 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL) case 1: // Doom2.exe compatible with a few relaxed settings v = COMPATF_SHORTTEX|COMPATF_STAIRINDEX|COMPATF_USEBLOCKING|COMPATF_NODOORLIGHT|COMPATF_SPRITESORT| - COMPATF_TRACE|COMPATF_MISSILECLIP|COMPATF_SOUNDTARGET|COMPATF_DEHHEALTH|COMPATF_CROSSDROPOFF; + COMPATF_TRACE|COMPATF_MISSILECLIP|COMPATF_SOUNDTARGET|COMPATF_DEHHEALTH|COMPATF_CROSSDROPOFF| + COMPATF_LIGHT; break; case 2: // same as 1 but stricter (NO_PASSMOBJ and INVISIBILITY are also set) v = COMPATF_SHORTTEX|COMPATF_STAIRINDEX|COMPATF_USEBLOCKING|COMPATF_NODOORLIGHT|COMPATF_SPRITESORT| COMPATF_TRACE|COMPATF_MISSILECLIP|COMPATF_SOUNDTARGET|COMPATF_NO_PASSMOBJ|COMPATF_LIMITPAIN| - COMPATF_DEHHEALTH|COMPATF_INVISIBILITY|COMPATF_CROSSDROPOFF|COMPATF_CORPSEGIBS; + COMPATF_DEHHEALTH|COMPATF_INVISIBILITY|COMPATF_CROSSDROPOFF|COMPATF_CORPSEGIBS|COMPATF_HITSCAN| + COMPATF_WALLRUN|COMPATF_NOTOSSDROPS|COMPATF_LIGHT; break; case 3: // Boom compat mode - v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL; + v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MISSILECLIP; break; case 4: // Old ZDoom compat mode - v = COMPATF_SOUNDTARGET; + v = COMPATF_SOUNDTARGET|COMPATF_LIGHT; break; case 5: // MBF compat mode - v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MUSHROOM| + v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MISSILECLIP|COMPATF_MUSHROOM| COMPATF_MBFMONSTERMOVE|COMPATF_NOBLOCKFRIENDS; break; + case 6: // Boom with some added settings to reenable spme 'broken' behavior + v = COMPATF_TRACE|COMPATF_SOUNDTARGET|COMPATF_BOOMSCROLL|COMPATF_MISSILECLIP|COMPATF_NO_PASSMOBJ| + COMPATF_INVISIBILITY|COMPATF_CORPSEGIBS|COMPATF_HITSCAN|COMPATF_WALLRUN|COMPATF_NOTOSSDROPS; + break; + } compatflags = v; } @@ -549,6 +564,9 @@ CVAR (Flag, compat_mbfmonstermove,compatflags, COMPATF_MBFMONSTERMOVE); CVAR (Flag, compat_corpsegibs, compatflags, COMPATF_CORPSEGIBS); CVAR (Flag, compat_noblockfriends,compatflags,COMPATF_NOBLOCKFRIENDS); CVAR (Flag, compat_spritesort, compatflags,COMPATF_SPRITESORT); +CVAR (Flag, compat_hitscan, compatflags,COMPATF_HITSCAN); +CVAR (Flag, compat_light, compatflags,COMPATF_LIGHT); +CVAR (Flag, compat_polyobj, compatflags,COMPATF_POLYOBJ); //========================================================================== // @@ -1199,8 +1217,11 @@ void D_DoAdvanceDemo (void) case 2: pagetic = (int)(gameinfo.pageTime * TICRATE); gamestate = GS_DEMOSCREEN; - pagename = gameinfo.creditPages[pagecount]; - pagecount = (pagecount+1) % gameinfo.creditPages.Size(); + if (gameinfo.creditPages.Size() > 0) + { + pagename = gameinfo.creditPages[pagecount]; + pagecount = (pagecount+1) % gameinfo.creditPages.Size(); + } demosequence = 1; break; } @@ -1658,6 +1679,10 @@ static FString ParseGameInfo(TArray &pwads, const char *fn, const char } while (sc.CheckToken(',')); } + else if (!nextKey.CompareNoCase("NOSPRITERENAME")) + { + nospriterename = true; + } } return iwad; } @@ -1754,6 +1779,9 @@ void D_DoomMain (void) #endif #endif + // Check response files before coalescing file parameters. + M_FindResponseFile (); + PClass::StaticInit(); PType::StaticInit(); @@ -1772,7 +1800,6 @@ void D_DoomMain (void) rngseed = I_MakeRNGSeed(); FRandom::StaticClearRandom (); - M_FindResponseFile (); Printf ("M_LoadDefaults: Load system defaults.\n"); M_LoadDefaults (); // load before initing other systems @@ -2274,6 +2301,14 @@ void FStartupScreen::AppendStatusLine(const char *status) // //========================================================================== +//========================================================================== +// +// STAT fps +// +// Displays statistics about rendering times +// +//========================================================================== + ADD_STAT (fps) { FString out; @@ -2282,6 +2317,24 @@ ADD_STAT (fps) return out; } + +static double f_acc, w_acc,p_acc,m_acc; +static int acc_c; + +ADD_STAT (fps_accumulated) +{ + f_acc += FrameCycles.TimeMS(); + w_acc += WallCycles.TimeMS(); + p_acc += PlaneCycles.TimeMS(); + m_acc += MaskedCycles.TimeMS(); + acc_c++; + FString out; + out.Format("frame=%04.1f ms walls=%04.1f ms planes=%04.1f ms masked=%04.1f ms %d counts", + f_acc/acc_c, w_acc/acc_c, p_acc/acc_c, m_acc/acc_c, acc_c); + Printf(PRINT_LOG, "%s\n", out.GetChars()); + return out; +} + //========================================================================== // // STAT wallcycles diff --git a/src/d_net.cpp b/src/d_net.cpp index 226baf9ba..a4a37b3fd 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -1917,6 +1917,22 @@ BYTE *FDynamicBuffer::GetData (int *len) } +static int KillAll(const PClass *cls) +{ + AActor *actor; + int killcount = 0; + TThinkerIterator iterator(cls); + while ( (actor = iterator.Next ()) ) + { + if (actor->IsA(cls)) + { + if (!(actor->flags2 & MF2_DORMANT) && (actor->flags3 & MF3_ISMONSTER)) + killcount += actor->Massacre (); + } + } + return killcount; + +} // [RH] Execute a special "ticcmd". The type byte should // have already been read, and the stream is positioned // at the beginning of the command's actual data. @@ -2021,7 +2037,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) // Using LEVEL_NOINTERMISSION tends to throw the game out of sync. // That was a long time ago. Maybe it works now? level.flags |= LEVEL_CHANGEMAPCHEAT; - G_ChangeLevel(s, pos, false); + G_ChangeLevel(s, pos, 0); break; case DEM_SUICIDE: @@ -2348,22 +2364,25 @@ void Net_DoCommand (int type, BYTE **stream, int player) case DEM_KILLCLASSCHEAT: { - AActor *actor; - TThinkerIterator iterator; - char *classname = ReadString (stream); int killcount = 0; + PClassActor *cls = PClass::FindActor(classname); - while ( (actor = iterator.Next ()) ) + if (cls != NULL) { - if (!stricmp (actor->GetClass ()->TypeName.GetChars (), classname)) + killcount = KillAll(cls); + const PClass *cls_rep = cls->GetReplacement(); + if (cls != cls_rep) { - if (!(actor->flags2 & MF2_DORMANT) && (actor->flags3 & MF3_ISMONSTER)) - killcount += actor->Massacre (); + killcount += KillAll(cls_rep); } + Printf ("Killed %d monsters of type %s.\n",killcount, classname); + } + else + { + Printf ("%s is not an actor class.\n", classname); } - Printf ("Killed %d monsters of type %s.\n",killcount, classname); } break; diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index 1bb709284..40710a6a3 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -772,10 +772,6 @@ void D_ReadUserInfoStrings (int i, BYTE **stream, bool update) // Rebuild translation in case the new skin uses a different range // than the old one. R_BuildPlayerTranslation (i); - if (StatusBar != NULL && i == StatusBar->GetPlayer()) - { - StatusBar->SetFace (&skins[info->skin]); - } break; case INFO_Gender: diff --git a/src/d_player.h b/src/d_player.h index b3dfe276d..66d946371 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -320,7 +320,7 @@ public: short fixedlightlevel; pspdef_t psprites[NUMPSPRITES]; // view sprites (gun, etc) int morphTics; // player is a chicken/pig if > 0 - BYTE MorphedPlayerClass; // [MH] (for SBARINFO) class # for this player instance when morphed + PClassPlayerPawn *MorphedPlayerClass; // [MH] (for SBARINFO) class # for this player instance when morphed int MorphStyle; // which effects to apply for this player instance when morphed PClassActor *MorphExitFlash; // flash to apply when demorphing (cache of value given to P_MorphPlayer) TObjPtr PremorphWeapon; // ready weapon before morphing diff --git a/src/decallib.cpp b/src/decallib.cpp index 8e06778b4..0e5b22cea 100644 --- a/src/decallib.cpp +++ b/src/decallib.cpp @@ -403,7 +403,7 @@ void FDecalLib::ReadDecals(FScanner &sc) } else { - sc.MustGetStringName(NULL); + sc.ScriptError("Unknown decaldef keyword '%s'", sc.String); } } } diff --git a/src/dobject.cpp b/src/dobject.cpp index d9d94aa34..3fc502b11 100644 --- a/src/dobject.cpp +++ b/src/dobject.cpp @@ -60,6 +60,40 @@ ClassReg DObject::RegistrationInfo = }; _DECLARE_TI(DObject) +CCMD (dumpactors) +{ + const char *const filters[32] = + { + "0:All", "1:Doom", "2:Heretic", "3:DoomHeretic", "4:Hexen", "5:DoomHexen", "6:Raven", "7:IdRaven", + "8:Strife", "9:DoomStrife", "10:HereticStrife", "11:DoomHereticStrife", "12:HexenStrife", + "13:DoomHexenStrife", "14:RavenStrife", "15:NotChex", "16:Chex", "17:DoomChex", "18:HereticChex", + "19:DoomHereticChex", "20:HexenChex", "21:DoomHexenChex", "22:RavenChex", "23:NotStrife", "24:StrifeChex", + "25:DoomStrifeChex", "26:HereticStrifeChex", "27:NotHexen", "28:HexenStrifeChex", "29:NotHeretic", + "30:NotDoom", "31:All", + }; + Printf("%i object class types total\nActor\tEd Num\tSpawnID\tFilter\tSource\n", PClass::AllClasses.Size()); + for (unsigned int i = 0; i < PClass::AllClasses.Size(); i++) + { + PClass *cls = PClass::AllClasses[i]; + PClassActor *acls = dyn_cast(cls); + if (acls != NULL) + { + Printf("%s\t%i\t%i\t%s\t%s\n", + acls->TypeName.GetChars(), acls->DoomEdNum, + acls->SpawnID, filters[acls->GameFilter & 31], + acls->SourceLumpName.GetChars()); + } + else if (cls != NULL) + { + Printf("%s\tn/a\tn/a\tn/a\tEngine (not an actor type)\n", cls->TypeName.GetChars()); + } + else + { + Printf("Type %i is not an object class\n", i); + } + } +} + CCMD (dumpclasses) { // This is by no means speed-optimized. But it's an informational console diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp index 1a5f3a557..bb02b52ee 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -71,6 +71,7 @@ #include "r_interpolate.h" #include "doomstat.h" #include "m_argv.h" +#include "po_man.h" #include "autosegs.h" // MACROS ------------------------------------------------------------------ diff --git a/src/doomdata.h b/src/doomdata.h index d066277a7..9c22b22a6 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -29,6 +29,7 @@ // Some global defines, that configure the game. #include "doomdef.h" +#include "m_swap.h" // // Map level types. @@ -138,7 +139,7 @@ enum ELineFlags ML_ADDTRANS = 0x00000400, // additive translucency (can only be set internally) // Extended flags - ML_MONSTERSCANACTIVATE = 0x00002000, // [RH] Monsters (as well as players) can active the line + ML_MONSTERSCANACTIVATE = 0x00002000, // [RH] Monsters (as well as players) can activate the line ML_BLOCK_PLAYERS = 0x00004000, ML_BLOCKEVERYTHING = 0x00008000, // [RH] Line blocks everything ML_ZONEBOUNDARY = 0x00010000, @@ -168,8 +169,9 @@ enum SPAC SPAC_AnyCross = 1<<7, // when anything without the MF2_TELEPORT flag crosses the line SPAC_MUse = 1<<8, // monsters can use SPAC_MPush = 1<<9, // monsters can push + SPAC_UseBack = 1<<10, // Can be used from the backside - SPAC_PlayerActivate = (SPAC_Cross|SPAC_Use|SPAC_Impact|SPAC_Push|SPAC_AnyCross|SPAC_UseThrough), + SPAC_PlayerActivate = (SPAC_Cross|SPAC_Use|SPAC_Impact|SPAC_Push|SPAC_AnyCross|SPAC_UseThrough|SPAC_UseBack), }; enum EMapLineFlags // These are flags that use different values internally @@ -220,6 +222,13 @@ struct mapsubsector_t WORD firstseg; // index of first one, segs are stored sequentially }; +#pragma pack(1) +struct mapsubsector4_t +{ + WORD numsegs; + DWORD firstseg; // index of first one, segs are stored sequentially +}; +#pragma pack() // LineSeg, generated by splitting LineDefs // using partition lines selected by BSP builder. @@ -231,6 +240,22 @@ struct mapseg_t WORD linedef; SWORD side; SWORD offset; + + int V1() { return LittleShort(v1); } + int V2() { return LittleShort(v2); } +}; + +struct mapseg4_t +{ + SDWORD v1; + SDWORD v2; + SWORD angle; + WORD linedef; + SWORD side; + SWORD offset; + + int V1() { return LittleLong(v1); } + int V2() { return LittleLong(v2); } }; @@ -238,18 +263,40 @@ struct mapseg_t // BSP node structure. // Indicate a leaf. -#define NF_SUBSECTOR 0x8000 struct mapnode_t { + enum + { + NF_SUBSECTOR = 0x8000, + NF_LUMPOFFSET = 0 + }; SWORD x,y,dx,dy; // partition line SWORD bbox[2][4]; // bounding box for each child // If NF_SUBSECTOR is or'ed in, it's a subsector, // else it's a node of another subtree. WORD children[2]; + + DWORD Child(int num) { return LittleShort(children[num]); } }; +struct mapnode4_t +{ + enum + { + NF_SUBSECTOR = 0x80000000, + NF_LUMPOFFSET = 8 + }; + SWORD x,y,dx,dy; // partition line + SWORD bbox[2][4]; // bounding box for each child + // If NF_SUBSECTOR is or'ed in, it's a subsector, + // else it's a node of another subtree. + DWORD children[2]; + + DWORD Child(int num) { return LittleLong(children[num]); } +}; + // Thing definition, position, orientation and type, @@ -293,6 +340,7 @@ struct FMapThing DWORD flags; int special; int args[5]; + int Conversation; void Serialize (FArchive &); }; diff --git a/src/doomdef.h b/src/doomdef.h index 485f15a5e..1108e770f 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -290,8 +290,10 @@ enum DF2_NO_AUTOMAP_ALLIES = 1 << 19, // Allies can been seen on the automap. DF2_DISALLOW_SPYING = 1 << 20, // You can spy on your allies. DF2_CHASECAM = 1 << 21, // Players can use the chasecam cheat. - DF2_NOSUICIDE = 1 << 22, // Players are allowed to suicide. + DF2_NOSUICIDE = 1 << 22, // Players are not allowed to suicide. DF2_NOAUTOAIM = 1 << 23, // Players cannot use autoaim. + DF2_DONTCHECKAMMO = 1 << 24, // Don't Check ammo when switching weapons. + DF2_KILLBOSSMONST = 1 << 25, // Kills all monsters spawned by a boss cube when the boss dies }; // [RH] Compatibility flags. @@ -325,6 +327,9 @@ enum COMPATF_CORPSEGIBS = 1 << 25, // Crushed monsters are turned into gibs, rather than replaced by gibs. COMPATF_NOBLOCKFRIENDS = 1 << 26, // Friendly monsters aren't blocked by monster-blocking lines. COMPATF_SPRITESORT = 1 << 27, // Invert sprite sorting order for sprites of equal distance + COMPATF_HITSCAN = 1 << 28, // Hitscans use original blockmap anf hit check code. + COMPATF_LIGHT = 1 << 29, // Find neighboring light level like Doom + COMPATF_POLYOBJ = 1 << 30, // Draw polyobjects the old fashioned way }; // Emulate old bugs for select maps. These are not exposed by a cvar @@ -333,8 +338,7 @@ enum { BCOMPATF_SETSLOPEOVERFLOW = 1 << 0, // SetSlope things can overflow BCOMPATF_RESETPLAYERSPEED = 1 << 1, // Set player speed to 1.0 when changing maps - BCOMPATF_SPECHITOVERFLOW = 1 << 2, // Emulate spechit overflow (e.g. Strain MAP07) - BCOMPATF_VILEGHOSTS = 1 << 3, // Monsters' radius and height aren't restored properly when resurrected. + BCOMPATF_VILEGHOSTS = 1 << 2, // Monsters' radius and height aren't restored properly when resurrected. }; // phares 3/20/98: diff --git a/src/doomtype.h b/src/doomtype.h index 7757a3cc6..31f96d2ab 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -44,12 +44,6 @@ // Since this file is included by everything, it seems an appropriate place // to check the NOASM/USEASM macros. -#if defined(__APPLE__) -// The assembly code needs to be tweaked for Mach-O before enabled on Macs. -#ifndef NOASM -#define NOASM -#endif -#endif // There are three assembly-related macros: // diff --git a/src/f_finale.cpp b/src/f_finale.cpp index 51498134f..f82011614 100644 --- a/src/f_finale.cpp +++ b/src/f_finale.cpp @@ -723,7 +723,7 @@ bool F_CastResponder (event_t* ev) if (ev->type != EV_KeyDown) return false; - const char *cmd = C_GetBinding (ev->data1); + const char *cmd = Bindings.GetBind (ev->data1); if (cmd != NULL && !stricmp (cmd, "toggleconsole")) return false; @@ -1226,7 +1226,7 @@ void F_Drawer (void) FTextureID picnum = TexMan.CheckForTexture (FinaleFlat, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable); if (picnum.isValid()) { - screen->FlatFill (0,0, SCREENWIDTH, SCREENHEIGHT, TexMan(picnum)); + screen->FlatFill (0,0, SCREENWIDTH, SCREENHEIGHT, TexMan[picnum]); } else { @@ -1322,15 +1322,15 @@ void F_Drawer (void) if (multiplayer) { screen->DrawTexture (TexMan["CHESSALL"], 20, 0, - DTA_VirtualWidth, w, - DTA_VirtualHeight, h, TAG_DONE); + DTA_VirtualWidthF, w, + DTA_VirtualHeightF, h, TAG_DONE); } else if (players[consoleplayer].CurrentPlayerClass > 0) { picname = players[consoleplayer].CurrentPlayerClass == 1 ? "CHESSC" : "CHESSM"; screen->DrawTexture (TexMan[picname], 60, 0, - DTA_VirtualWidth, w, - DTA_VirtualHeight, h, TAG_DONE); + DTA_VirtualWidthF, w, + DTA_VirtualHeightF, h, TAG_DONE); } } } diff --git a/src/farchive.h b/src/farchive.h index 32e3b4b6f..3ebfd30d3 100644 --- a/src/farchive.h +++ b/src/farchive.h @@ -282,6 +282,10 @@ template<> inline FArchive &operator<< (FArchive &arc, FFont* &font) return SerializeFFontPtr (arc, font); } +struct FStrifeDialogueNode; +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node); + + template inline FArchive &operator<< (FArchive &arc, TArray &self) diff --git a/src/files.cpp b/src/files.cpp index 5705b938a..332bddb1f 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -132,6 +132,8 @@ long FileReader::Seek (long offset, int origin) long FileReader::Read (void *buffer, long len) { + assert(len >= 0); + if (len <= 0) return 0; if (FilePos + len > StartPos + Length) { len = Length - FilePos + StartPos; diff --git a/src/files.h b/src/files.h index 5ee70e3fd..ebe9665a4 100644 --- a/src/files.h +++ b/src/files.h @@ -8,7 +8,56 @@ #include "doomtype.h" #include "m_swap.h" -class FileReader +class FileReaderBase +{ +public: + virtual ~FileReaderBase() {} + virtual long Read (void *buffer, long len) = 0; + + FileReaderBase &operator>> (BYTE &v) + { + Read (&v, 1); + return *this; + } + + FileReaderBase &operator>> (SBYTE &v) + { + Read (&v, 1); + return *this; + } + + FileReaderBase &operator>> (WORD &v) + { + Read (&v, 2); + v = LittleShort(v); + return *this; + } + + FileReaderBase &operator>> (SWORD &v) + { + Read (&v, 2); + v = LittleShort(v); + return *this; + } + + FileReaderBase &operator>> (DWORD &v) + { + Read (&v, 4); + v = LittleLong(v); + return *this; + } + + FileReaderBase &operator>> (fixed_t &v) + { + Read (&v, 4); + v = LittleLong(v); + return *this; + } + +}; + + +class FileReader : public FileReaderBase { public: FileReader (); @@ -82,13 +131,13 @@ protected: }; // Wraps around a FileReader to decompress a zlib stream -class FileReaderZ +class FileReaderZ : public FileReaderBase { public: FileReaderZ (FileReader &file, bool zip=false); ~FileReaderZ (); - long Read (void *buffer, long len); + virtual long Read (void *buffer, long len); FileReaderZ &operator>> (BYTE &v) { @@ -144,7 +193,7 @@ private: }; // Wraps around a FileReader to decompress a bzip2 stream -class FileReaderBZ2 +class FileReaderBZ2 : public FileReaderBase { public: FileReaderBZ2 (FileReader &file); @@ -206,7 +255,7 @@ private: }; // Wraps around a FileReader to decompress a lzma stream -class FileReaderLZMA +class FileReaderLZMA : public FileReaderBase { public: FileReaderLZMA (FileReader &file, size_t uncompressed_size, bool zip); diff --git a/src/g_doom/a_bossbrain.cpp b/src/g_doom/a_bossbrain.cpp index ef8d23b8f..0c656800c 100644 --- a/src/g_doom/a_bossbrain.cpp +++ b/src/g_doom/a_bossbrain.cpp @@ -85,6 +85,21 @@ DEFINE_ACTION_FUNCTION(AActor, A_BrainDie) if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT)) return 0; + // New dmflag: Kill all boss spawned monsters before ending the level. + if (dmflags2 & DF2_KILLBOSSMONST) + { + TThinkerIterator it; + AActor *mo; + while ((mo = it.Next())) + { + if (mo->flags4 & MF4_BOSSSPAWNED) + { + P_DamageMobj(mo, self, self, mo->health, NAME_None, + DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR); + } + } + } + G_ExitLevel (0, false); return 0; } @@ -261,6 +276,7 @@ static void SpawnFly(AActor *self, PClassActor *spawntype, FSoundID sound) // telefrag anything in this spot P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true); } + newmobj->flags4 |= MF4_BOSSSPAWNED; } } diff --git a/src/g_doom/a_doomweaps.cpp b/src/g_doom/a_doomweaps.cpp index a85af9dff..c32dcb775 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -54,7 +54,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_Punch) angle += pr_punch.Random2() << 18; pitch = P_AimLineAttack (self, angle, MELEERANGE, &linetarget); - P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true); + + P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &linetarget); // turn to face target if (linetarget) @@ -105,15 +106,30 @@ DEFINE_ACTION_FUNCTION(AActor, A_FirePistol) // // A_Saw // +enum SAW_Flags +{ + SF_NORANDOM = 1, + SF_RANDOMLIGHTMISS = 2, + SF_RANDOMLIGHTHIT = 4, + SF_NOUSEAMMOMISS = 8, + SF_NOUSEAMMO = 16, +}; + DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) { PARAM_ACTION_PROLOGUE; - PARAM_SOUND_OPT (fullsound) { fullsound = "weapons/sawfull"; } + PARAM_SOUND_OPT (fullsound) { fullsound = "weapons/sawfull"; } PARAM_SOUND_OPT (hitsound) { hitsound = "weapons/sawhit"; } PARAM_INT_OPT (damage) { damage = 2; } PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (range) { range = 0; } + PARAM_ANGLE_OPT (spread_xy) { spread_xy = angle_t(2.8125 * (ANGLE_90 / 90.0)); } + PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } + PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; } - angle_t angle; + angle_t angle; + angle_t slope; player_t *player; AActor *linetarget; @@ -122,13 +138,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) return 0; } - AWeapon *weapon = self->player->ReadyWeapon; - if (weapon != NULL) - { - if (!weapon->DepleteAmmo (weapon->bAltFire)) - return 0; - } - if (pufftype == NULL) { pufftype = PClass::FindActor(NAME_BulletPuff); @@ -137,21 +146,61 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Saw) { damage = 2; } - - damage *= (pr_saw()%10 + 1); - angle = self->angle; - angle += pr_saw.Random2() << 18; - - // use meleerange + 1 so the puff doesn't skip the flash (i.e. plays all states) - P_LineAttack (self, angle, MELEERANGE+1, - P_AimLineAttack (self, angle, MELEERANGE+1, &linetarget), damage, + if (!(flags & SF_NORANDOM)) + { + damage *= (pr_saw()%10+1); + } + if (range == 0) + { // use meleerange + 1 so the puff doesn't skip the flash (i.e. plays all states) + range = MELEERANGE+1; + } + + angle = self->angle + (pr_saw.Random2() * (spread_xy / 255)); + slope = P_AimLineAttack (self, angle, range, &linetarget) + (pr_saw.Random2() * (spread_z / 255)); + + P_LineAttack (self, angle, range, + slope, damage, NAME_None, pufftype); + AWeapon *weapon = self->player->ReadyWeapon; + if ((weapon != NULL) && !(flags & SF_NOUSEAMMO) && !(!linetarget && (flags & SF_NOUSEAMMOMISS))) + { + if (!weapon->DepleteAmmo (weapon->bAltFire)) + return 0; + } + if (!linetarget) { + if ((flags & SF_RANDOMLIGHTMISS) && (pr_saw() > 64)) + { + player->extralight = !player->extralight; + } S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM); return 0; } + + if (flags & SF_RANDOMLIGHTHIT) + { + int randVal = pr_saw(); + if (randVal < 64) + { + player->extralight = 0; + } + else if (randVal < 160) + { + player->extralight = 1; + } + else + { + player->extralight = 2; + } + } + + if (lifesteal) + { + P_GiveBody (self, (damage * lifesteal) >> FRACBITS); + } + S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM); // turn to face target diff --git a/src/g_doom/a_keen.cpp b/src/g_doom/a_keen.cpp index 2cb7196d1..22a344c44 100644 --- a/src/g_doom/a_keen.cpp +++ b/src/g_doom/a_keen.cpp @@ -17,9 +17,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_KeenDie) { PARAM_ACTION_PROLOGUE; - PARAM_INT(doortag); + PARAM_INT_OPT(doortag) { doortag = 666; } - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, false); // scan the remaining thinkers to see if all Keens are dead AActor *other; diff --git a/src/g_doom/a_painelemental.cpp b/src/g_doom/a_painelemental.cpp index 142c23a92..cf131af25 100644 --- a/src/g_doom/a_painelemental.cpp +++ b/src/g_doom/a_painelemental.cpp @@ -179,7 +179,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PainDie) self->flags &= ~MF_FRIENDLY; } PClassActor *spawntype = GetSpawnType(numparam > NAP ? ¶m[NAP] : NULL); - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, true); A_PainShootSkull (self, self->angle + ANG90, spawntype); A_PainShootSkull (self, self->angle + ANG180, spawntype); A_PainShootSkull (self, self->angle + ANG270, spawntype); diff --git a/src/g_doom/a_scriptedmarine.cpp b/src/g_doom/a_scriptedmarine.cpp index 4260bbcd5..151b665a6 100644 --- a/src/g_doom/a_scriptedmarine.cpp +++ b/src/g_doom/a_scriptedmarine.cpp @@ -283,7 +283,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Saw) P_LineAttack (self, angle, MELEERANGE+1, P_AimLineAttack (self, angle, MELEERANGE+1, &linetarget), damage, - NAME_Melee, pufftype); + NAME_Melee, pufftype, false, &linetarget); if (!linetarget) { @@ -338,7 +338,7 @@ static void MarinePunch(AActor *self, int damagemul) A_FaceTarget (self); angle = self->angle + (pr_m_punch.Random2() << 18); pitch = P_AimLineAttack (self, angle, MELEERANGE, &linetarget); - P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true); + P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &linetarget); // turn to face target if (linetarget) diff --git a/src/g_game.cpp b/src/g_game.cpp index 6a0602d3f..1722ef3f9 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -116,13 +116,13 @@ EXTERN_CVAR (Float, con_midtime); // // CVAR displaynametags // -// Selects whether to display name tags or not when changing weapons +// Selects whether to display name tags or not when changing weapons/items // //========================================================================== -CUSTOM_CVAR (Bool, displaynametags, 0, CVAR_ARCHIVE) +CUSTOM_CVAR (Int, displaynametags, 0, CVAR_ARCHIVE) { - if (self != 0 && self != 1) + if (self < 0 || self > 3) { self = 0; } @@ -288,7 +288,8 @@ CCMD (slot) if (slot < NUM_WEAPON_SLOTS) { - SendItemUse = players[consoleplayer].weapons.Slots[slot].PickWeapon (&players[consoleplayer]); + SendItemUse = players[consoleplayer].weapons.Slots[slot].PickWeapon (&players[consoleplayer], + !(dmflags2 & DF2_DONTCHECKAMMO)); } } } @@ -321,11 +322,23 @@ CCMD (turn180) CCMD (weapnext) { SendItemUse = players[consoleplayer].weapons.PickNextWeapon (&players[consoleplayer]); + // [BC] Option to display the name of the weapon being cycled to. + if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) + { + StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), + 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + } } CCMD (weapprev) { SendItemUse = players[consoleplayer].weapons.PickPrevWeapon (&players[consoleplayer]); + // [BC] Option to display the name of the weapon being cycled to. + if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) + { + StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), + 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + } } CCMD (invnext) @@ -353,10 +366,9 @@ CCMD (invnext) who->InvSel = who->Inventory; } } - if (displaynametags && StatusBar && SmallFont - && gamestate == GS_LEVEL && level.time > con_midtime && who->InvSel) - StatusBar->AttachMessage (new DHUDMessage (SmallFont, who->InvSel->GetTag(), - 2.5f, 0.375f, 0, 0, CR_YELLOW, con_midtime), MAKE_ID('S','I','N','V')); + if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), + 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -384,10 +396,9 @@ CCMD (invprev) } who->InvSel = item; } - if (displaynametags && StatusBar && SmallFont - && gamestate == GS_LEVEL && level.time > con_midtime && who->InvSel) - StatusBar->AttachMessage (new DHUDMessage (SmallFont, who->InvSel->GetTag(), - 2.5f, 0.375f, 0, 0, CR_YELLOW, con_midtime), MAKE_ID('S','I','N','V')); + if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), + 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -871,7 +882,7 @@ bool G_Responder (event_t *ev) if (gameaction == ga_nothing && (demoplayback || gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL)) { - const char *cmd = C_GetBinding (ev->data1); + const char *cmd = Bindings.GetBind (ev->data1); if (ev->type == EV_KeyDown) { @@ -894,11 +905,11 @@ bool G_Responder (event_t *ev) } else { - return C_DoKey (ev); + return C_DoKey (ev, &Bindings, &DoubleBindings); } } if (cmd && cmd[0] == '+') - return C_DoKey (ev); + return C_DoKey (ev, &Bindings, &DoubleBindings); return false; } @@ -910,7 +921,7 @@ bool G_Responder (event_t *ev) { if (ST_Responder (ev)) return true; // status window ate it - if (!viewactive && AM_Responder (ev)) + if (!viewactive && AM_Responder (ev, false)) return true; // automap ate it } else if (gamestate == GS_FINALE) @@ -922,12 +933,12 @@ bool G_Responder (event_t *ev) switch (ev->type) { case EV_KeyDown: - if (C_DoKey (ev)) + if (C_DoKey (ev, &Bindings, &DoubleBindings)) return true; break; case EV_KeyUp: - C_DoKey (ev); + C_DoKey (ev, &Bindings, &DoubleBindings); break; // [RH] mouse buttons are sent as key up/down events @@ -941,7 +952,7 @@ bool G_Responder (event_t *ev) // the events *last* so that any bound keys get precedence. if (gamestate == GS_LEVEL && viewactive) - return AM_Responder (ev); + return AM_Responder (ev, true); return (ev->type == EV_KeyDown || ev->type == EV_Mouse); @@ -1165,7 +1176,9 @@ void G_Ticker () // G_PlayerFinishLevel // Called when a player completes a level. // -void G_PlayerFinishLevel (int player, EFinishLevelType mode, bool resetinventory) +// flags is checked for RESETINVENTORY and RESETHEALTH only. + +void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags) { AInventory *item, *next; player_t *p; @@ -1237,8 +1250,14 @@ void G_PlayerFinishLevel (int player, EFinishLevelType mode, bool resetinventory P_UndoPlayerMorph (p, p, 0, true); } + // Resets player health to default + if (flags & CHANGELEVEL_RESETHEALTH) + { + p->health = p->mo->health = p->mo->SpawnHealth(); + } + // Clears the entire inventory and gives back the defaults for starting a game - if (resetinventory) + if (flags & CHANGELEVEL_RESETINVENTORY) { AInventory *inv = p->mo->Inventory; @@ -2044,11 +2063,11 @@ void G_DoSaveGame (bool okForQuicksave, FString filename, const char *descriptio M_AppendPNGChunk (stdfile, MAKE_ID('s','n','X','t'), &next, 1); } - M_NotifyNewSave (filename.GetChars(), description, okForQuicksave); - M_FinishPNG (stdfile); fclose (stdfile); + M_NotifyNewSave (filename.GetChars(), description, okForQuicksave); + // Check whether the file is ok. bool success = false; stdfile = fopen (filename.GetChars(), "rb"); diff --git a/src/g_game.h b/src/g_game.h index 56f16081b..20fdae892 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -71,7 +71,7 @@ enum EFinishLevelType FINISH_NoHub }; -void G_PlayerFinishLevel (int player, EFinishLevelType mode, bool resetinventory); +void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags); void G_DoReborn (int playernum, bool freshbot); diff --git a/src/g_heretic/a_chicken.cpp b/src/g_heretic/a_chicken.cpp index 86c4243cb..8fb626ab5 100644 --- a/src/g_heretic/a_chicken.cpp +++ b/src/g_heretic/a_chicken.cpp @@ -186,7 +186,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_BeakAttackPL1) damage = 1 + (pr_beakatkpl1()&3); angle = player->mo->angle; slope = P_AimLineAttack (player->mo, angle, MELEERANGE, &linetarget); - P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "BeakPuff", true); + P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "BeakPuff", true, &linetarget); if (linetarget) { player->mo->angle = R_PointToAngle2 (player->mo->x, @@ -222,7 +222,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_BeakAttackPL2) damage = pr_beakatkpl2.HitDice (4); angle = player->mo->angle; slope = P_AimLineAttack (player->mo, angle, MELEERANGE, &linetarget); - P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "BeakPuff", true); + P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "BeakPuff", true, &linetarget); if (linetarget) { player->mo->angle = R_PointToAngle2 (player->mo->x, diff --git a/src/g_heretic/a_dsparil.cpp b/src/g_heretic/a_dsparil.cpp index 4e1e0edfc..512a75d94 100644 --- a/src/g_heretic/a_dsparil.cpp +++ b/src/g_heretic/a_dsparil.cpp @@ -151,7 +151,7 @@ void P_DSparilTeleport (AActor *actor) DSpotState *state = DSpotState::GetSpotState(); if (state == NULL) return; - spot = state->GetSpotWithMinDistance(PClass::FindClass("BossSpot"), actor->x, actor->y, 128*FRACUNIT); + spot = state->GetSpotWithMinMaxDistance(PClass::FindClass("BossSpot"), actor->x, actor->y, 128*FRACUNIT, 0); if (spot == NULL) return; prevX = actor->x; diff --git a/src/g_heretic/a_hereticweaps.cpp b/src/g_heretic/a_hereticweaps.cpp index fface239b..3dd1bef3d 100644 --- a/src/g_heretic/a_hereticweaps.cpp +++ b/src/g_heretic/a_hereticweaps.cpp @@ -89,7 +89,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StaffAttack) angle = self->angle; angle += pr_sap.Random2() << 18; slope = P_AimLineAttack (self, angle, MELEERANGE, &linetarget); - P_LineAttack (self, angle, MELEERANGE, slope, damage, NAME_Melee, puff, true); + P_LineAttack (self, angle, MELEERANGE, slope, damage, NAME_Melee, puff, true, &linetarget); if (linetarget) { //S_StartSound(player->mo, sfx_stfhit); @@ -291,7 +291,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GauntletAttack) pufftype = PClass::FindActor("GauntletPuff1"); } slope = P_AimLineAttack (self, angle, dist, &linetarget); - P_LineAttack (self, angle, dist, slope, damage, NAME_Melee, pufftype); + P_LineAttack (self, angle, dist, slope, damage, NAME_Melee, pufftype, false, &linetarget); if (!linetarget) { if (pr_gatk() > 64) diff --git a/src/g_heretic/heretic_sbar.cpp b/src/g_heretic/heretic_sbar.cpp deleted file mode 100644 index d9cabf2d5..000000000 --- a/src/g_heretic/heretic_sbar.cpp +++ /dev/null @@ -1,795 +0,0 @@ -#include - -#include "doomtype.h" -#include "doomstat.h" -#include "v_font.h" -#include "sbar.h" -#include "r_defs.h" -#include "w_wad.h" -#include "m_random.h" -#include "d_player.h" -#include "st_stuff.h" -#include "v_video.h" -#include "r_draw.h" -#include "templates.h" -#include "a_keys.h" -#include "r_translate.h" -#include "g_level.h" -#include "v_palette.h" - - -static FRandom pr_chainwiggle; - -// This texture is used to shade each end of the health chain -class FHereticShader : public FTexture -{ -public: - FHereticShader (); - - const BYTE *GetColumn (unsigned int column, const Span **spans_out); - const BYTE *GetPixels (); - void Unload (); - -private: - static const BYTE Pixels[10*16]; - static const Span DummySpan[2]; -}; - -static FHereticShader ChainShade; - -const FTexture::Span FHereticShader::DummySpan[2] = { { 0, 10 }, { 0, 0 } }; -const BYTE FHereticShader::Pixels[10*16] = -{ - 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, - 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, - 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, - 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, - 160, 160, 160, 160, 160, 160, 160, 160, 160, 160, - 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, - 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, - 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -}; - -FHereticShader::FHereticShader () -{ - Width = 16; - Height = 10; - WidthBits = 4; - HeightBits = 4; - WidthMask = 15; -} - -void FHereticShader::Unload () -{ -} - -const BYTE *FHereticShader::GetColumn (unsigned int column, const Span **spans_out) -{ - if (spans_out != NULL) - { - *spans_out = DummySpan; - } - return Pixels + 10*(column & 15); -} - -const BYTE *FHereticShader::GetPixels () -{ - return Pixels; -} - - -class DHereticStatusBar : public DBaseStatusBar -{ - DECLARE_CLASS(DHereticStatusBar, DBaseStatusBar) - HAS_OBJECT_POINTERS -public: - DHereticStatusBar () : DBaseStatusBar (42) - { - static const char *hereticLumpNames[NUM_HERETICSB_IMAGES] = - { - "LTFACE", "RTFACE", "BARBACK", "INVBAR", "CHAIN", - NULL, "LIFEGEM2", "LTFCTOP", "RTFCTOP", "SELECTBO", - "INVGEML1", "INVGEML2", "INVGEMR1", "INVGEMR2", "BLACKSQ", - "ARMCLEAR", "CHAINBACK","GOD1", "GOD2", "USEARTIA", - "USEARTIB", "USEARTIC", "USEARTID", "YKEYICON", "GKEYICON", - "BKEYICON", "ARTIBOX", "PTN1A0", "PTN1B0", "PTN1C0" - }; - static const char *sharedLumpNames[] = - { - "LAME", "NEGNUM", "IN0", "IN1", "IN2", - "IN3", "IN4", "IN5", "IN6", "IN7", - "IN8", "IN9", "FONTB13", "FONTB16", "FONTB17", - "FONTB18", "FONTB19", "FONTB20", "FONTB21", "FONTB22", - "FONTB23", "FONTB24", "FONTB25", "SMALLIN0", "SMALLIN1", - "SMALLIN2", "SMALLIN3", "SMALLIN4", "SMALLIN5", "SMALLIN6", - "SMALLIN7", "SMALLIN8", "SMALLIN9" - }; - - if (deathmatch) - { - hereticLumpNames[5] = "STATBAR"; - } - else - { - hereticLumpNames[5] = "LIFEBAR"; - } - - DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES); - Images.Init (hereticLumpNames, NUM_HERETICSB_IMAGES); - - oldarti = NULL; - oldammo1 = oldammo2 = NULL; - oldammocount1 = oldammocount2 = -1; - oldartiCount = 0; - oldfrags = -9999; - oldarmor = -1; - oldhealth = -1; - oldlife = -1; - oldkeys = -1; - - HealthMarker = 0; - ChainWiggle = 0; - ArtifactFlash = 0; - } - - ~DHereticStatusBar () - { - } - - void Tick () - { - int curHealth; - - DBaseStatusBar::Tick (); - if (level.time & 1) - { - ChainWiggle = pr_chainwiggle() & 1; - } - curHealth = CPlayer->health; - if (curHealth < 0) - { - curHealth = 0; - } - if (curHealth < HealthMarker) - { - HealthMarker -= clamp ((HealthMarker - curHealth) >> 2, 1, 8); - } - else if (curHealth > HealthMarker) - { - HealthMarker += clamp ((curHealth - HealthMarker) >> 2, 1, 8); - } - - if (ArtifactFlash > 0) - { - if (--ArtifactFlash == 0) - { - ArtiRefresh = screen->GetPageCount (); - } - } - } - - void Draw (EHudState state) - { - DBaseStatusBar::Draw (state); - - if (state == HUD_Fullscreen) - { - DrawFullScreenStuff (); - SB_state = screen->GetPageCount (); - } - else if (state == HUD_StatusBar) - { - if (SB_state > 0) - { - DrawImage (Images[imgBARBACK], 0, 0); - if (CPlayer->cheats&CF_GODMODE) - { - DrawImage (Images[imgGOD1], 16, 9); - DrawImage (Images[imgGOD2], 287, 9); - } - oldhealth = -1; - } - DrawCommonBar (); - if (CPlayer->inventorytics == 0) - { - if (SB_state < 0) - { - SB_state = screen->GetPageCount (); - } - if (SB_state != 0) - { - // Main interface - SB_state--; - DrawImage (Images[imgSTATBAR], 34, 2); - oldarti = NULL; - oldammo1 = oldammo2 = NULL; - oldammocount1 = oldammocount2 = -1; - oldarmor = -1; - oldfrags = -9999; //can't use -1, 'cuz of negative frags - oldlife = -1; - oldkeys = -1; - oldhealth = -1; - ArtiRefresh = 0; - } - DrawMainBar (); - } - else - { - if (SB_state > -1) - { - SB_state = -screen->GetPageCount () - 1; - } - if (SB_state < -1) - { - SB_state++; - DrawImage (Images[imgINVBAR], 34, 2); - } - DrawInventoryBar (); - } - } - } - -private: -//--------------------------------------------------------------------------- -// -// PROC DrawCommonBar -// -//--------------------------------------------------------------------------- - - void DrawCommonBar () - { - int chainY; - int healthPos; - - DrawImage (Images[imgLTFCTOP], 0, -10); - //DrawImage (Images[imgRTFCTOP], 290, -10); - screen->DrawTexture (Images[imgRTFCTOP], ST_X+290, ST_Y, - DTA_Bottom320x200, Scaled, - DTA_TopOffset, Images[imgRTFCTOP]->GetHeight(), - TAG_DONE); - - if (oldhealth != HealthMarker) - { - oldhealth = HealthMarker; - HealthRefresh = screen->GetPageCount (); - } - if (HealthRefresh) - { - HealthRefresh--; - healthPos = HealthMarker; - if (healthPos < 0) - { - healthPos = 0; - } - if (healthPos > 100) - { - healthPos = 100; - } - healthPos = (healthPos * 256) / 100; - chainY = (HealthMarker == (CPlayer->health > 0 ? CPlayer->health : 0)) ? 33 : 33 + ChainWiggle; - DrawImage (Images[imgCHAINBACK], 0, 32); - DrawImage (Images[imgCHAIN], 2+(healthPos%17), chainY); - DrawImage (Images[imgLIFEGEM], 17+healthPos, chainY, multiplayer ? - translationtables[TRANSLATION_PlayersExtra][int(CPlayer - players)] : NULL); - DrawImage (Images[imgLTFACE], 0, 32); - DrawImage (Images[imgRTFACE], 276, 32); - screen->DrawTexture (&ChainShade, ST_X+19, ST_Y+32, - DTA_Bottom320x200, Scaled, - DTA_AlphaChannel, true, - DTA_FillColor, 0, - TAG_DONE); - screen->DrawTexture (&ChainShade, ST_X+277, ST_Y+32, - DTA_Bottom320x200, Scaled, - DTA_AlphaChannel, true, - DTA_FillColor, 0, - DTA_FlipX, true, - TAG_DONE); - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawMainBar -// -//--------------------------------------------------------------------------- - - void DrawMainBar () - { - AInventory *item; - AAmmo *ammo1, *ammo2; - int ammocount1, ammocount2; - int temp; - int playerkeys; - - // Ready artifact - if (ArtifactFlash) - { - DrawImage (Images[imgBLACKSQ], 180, 3); - DrawImage (Images[imgUSEARTIA + ArtifactFlash], 182, 3); - oldarti = NULL; // so that the correct artifact fills in after the flash - } - else if (oldarti != CPlayer->mo->InvSel - || (oldarti != NULL && oldartiCount != oldarti->Amount)) - { - oldarti = CPlayer->mo->InvSel; - GC::WriteBarrier(this, oldarti); - oldartiCount = oldarti != NULL ? oldarti->Amount : 0; - ArtiRefresh = screen->GetPageCount (); - } - if (ArtiRefresh) - { - ArtiRefresh--; - DrawImage (Images[imgBLACKSQ], 180, 3); - if (oldarti != NULL) - { - DrawDimImage (TexMan(oldarti->Icon), 179, 2, oldarti->Amount <= 0); - if (oldartiCount != 1) - { - DrSmallNumber (oldartiCount, 197, 24); - } - } - } - - // Frags - if (deathmatch) - { - temp = CPlayer->fragcount; - if (temp != oldfrags) - { - oldfrags = temp; - FragHealthRefresh = screen->GetPageCount (); - } - if (FragHealthRefresh) - { - FragHealthRefresh--; - DrawImage (Images[imgARMCLEAR], 57, 13); - DrINumber (temp, 61, 12); - } - } - else - { - temp = MAX(0, HealthMarker); - if (oldlife != temp) - { - oldlife = temp; - FragHealthRefresh = screen->GetPageCount (); - } - if (FragHealthRefresh) - { - FragHealthRefresh--; - DrawImage (Images[imgARMCLEAR], 57, 13); - DrINumber (temp, 61, 12); - } - } - - // Keys - playerkeys = 0; - - for (item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory) - { - if (item->IsKindOf (RUNTIME_CLASS(AKey))) - { - int keynum = static_cast(item)->KeyNumber; - if (keynum >= 1 && keynum <= 3) - { - playerkeys |= 1 << (keynum-1); - } - } - } - if (oldkeys != playerkeys) - { - oldkeys = playerkeys; - KeysRefresh = screen->GetPageCount (); - } - if (KeysRefresh) - { - KeysRefresh--; - // [RH] Erase the key images so the player can drop keys - // and see the status update. - screen->DrawTexture (Images[imgSTATBAR], ST_X+34, ST_Y+2, - DTA_WindowLeft, 119, - DTA_WindowRight, 129, - DTA_Bottom320x200, Scaled, - TAG_DONE); - if (playerkeys & 4) - { - DrawImage (Images[imgYKEYICON], 153, 6); - } - if (playerkeys & 1) - { - DrawImage (Images[imgGKEYICON], 153, 14); - } - if (playerkeys & 2) - { - DrawImage (Images[imgBKEYICON], 153, 22); - } - } - - // Ammo - GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2); - if (ammo1 == ammo2) - { - // Don't show the same ammo twice. - ammo2 = NULL; - } - if (oldammo1 != ammo1 || oldammo2 != ammo2 || - oldammocount1 != ammocount1 || oldammocount2 != ammocount2) - { - oldammo1 = ammo1; - oldammo2 = ammo2; - oldammocount1 = ammocount1; - oldammocount2 = ammocount2; - GC::WriteBarrier(this, ammo1); - GC::WriteBarrier(this, ammo2); - AmmoRefresh = screen->GetPageCount (); - } - if (AmmoRefresh) - { - AmmoRefresh--; - DrawImage (Images[imgBLACKSQ], 108, 3); - if (ammo2 != NULL) - { // Draw both ammos - screen->DrawTexture (TexMan[ammo1->Icon], 115+ST_X, 11+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, Scaled, - TAG_DONE); - DrSmallNumber (ammo1->Amount, 124, 7); - screen->DrawTexture (TexMan[ammo2->Icon], 115+ST_X, 22+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, Scaled, - TAG_DONE); - DrSmallNumber (ammo2->Amount, 124, 19); - } - else if (ammo1 != NULL) - { // Draw just one ammo - DrINumber (ammo1->Amount, 109, 4); - screen->DrawTexture (TexMan[ammo1->Icon], 123+ST_X, 22+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, Scaled, - TAG_DONE); - } - } - - // Armor - AInventory *armor = CPlayer->mo->FindInventory(); - int armorpoints = armor != NULL ? armor->Amount : 0; - if (oldarmor != armorpoints) - { - oldarmor = armorpoints; - ArmorRefresh = screen->GetPageCount (); - } - if (ArmorRefresh) - { - ArmorRefresh--; - DrawImage (Images[imgARMCLEAR], 224, 13); - DrINumber (armorpoints, 228, 12); - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawInventoryBar -// -//--------------------------------------------------------------------------- - - void DrawInventoryBar () - { - AInventory *item; - int i; - - DrawImage (Images[imgINVBAR], 34, 2); - CPlayer->mo->InvFirst = ValidateInvFirst (7); - if (CPlayer->mo->InvFirst != NULL) - { - for (item = CPlayer->mo->InvFirst, i = 0; item != NULL && i < 7; item = item->NextInv(), ++i) - { - DrawDimImage (TexMan(item->Icon), 50+i*31, 2, item->Amount <= 0); - if (item->Amount != 1) - { - DrSmallNumber (item->Amount, 65+i*31, 24); - } - if (item == CPlayer->mo->InvSel) - { - DrawImage (Images[imgSELECTBOX], 50+i*31, 31); - } - } - // Is there something to the left? - if (CPlayer->mo->FirstInv() != CPlayer->mo->InvFirst) - { - DrawImage (Images[!(gametic & 4) ? - imgINVLFGEM1 : imgINVLFGEM2], 38, 1); - } - // Is there something to the right? - if (item != NULL) - { - DrawImage (Images[!(gametic & 4) ? - imgINVRTGEM1 : imgINVRTGEM2], 269, 1); - } - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawFullScreenStuff -// -//--------------------------------------------------------------------------- - - void DrawFullScreenStuff () - { - AInventory *item; - FTexture *pic; - int i; - - // Draw health - if (CPlayer->mo->health > 0) - { - pic = Images[imgPTN1 + gametic/3%3]; - screen->DrawTexture (pic, 48, -3, - DTA_HUDRules, HUD_Normal, - DTA_LeftOffset, pic->GetWidth()/2, - DTA_TopOffset, pic->GetHeight(), - TAG_DONE); - DrBNumberOuter (CPlayer->mo->health, 5, -21); - } - else - { - DrBNumberOuter (0, 5, -20); - } - - // Draw armor - ABasicArmor *armor = CPlayer->mo->FindInventory(); - if (armor != NULL && armor->Amount != 0) - { - pic = TexMan(armor->Icon); - if (pic != NULL) - { - screen->DrawTexture (pic, 56, -24, - DTA_HUDRules, HUD_Normal, - DTA_LeftOffset, pic->GetWidth()/2, - DTA_TopOffset, pic->GetHeight(), - TAG_DONE); - } - DrBNumberOuter (armor->Amount, 5, -43); - } - - if (deathmatch) - { - // Draw frag count - DrINumberOuter (CPlayer->fragcount, 45, -16); - } - else - { - // Draw keys - int playerkeys = 0; - - for (item = CPlayer->mo->Inventory; item != NULL; item = item->Inventory) - { - if (item->IsKindOf (RUNTIME_CLASS(AKey))) - { - int keynum = static_cast(item)->KeyNumber; - if (keynum >= 1 && keynum <= 3) - { - playerkeys |= 1 << (keynum-1); - } - } - } - i = -7; - if (playerkeys & 2) - { - screen->DrawTexture (Images[imgBKEYICON], 54, i, - DTA_HUDRules, HUD_Normal, - TAG_DONE); - i -= 8; - } - if (playerkeys & 1) - { - screen->DrawTexture (Images[imgGKEYICON], 54, i, - DTA_HUDRules, HUD_Normal, - TAG_DONE); - i -= 8; - } - if (playerkeys & 4) - { - screen->DrawTexture (Images[imgYKEYICON], 54, i, - DTA_HUDRules, HUD_Normal, - TAG_DONE); - } - } - - // Draw ammo - AAmmo *ammo1, *ammo2; - int ammocount1, ammocount2; - - GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2); - if (ammo1 != NULL) - { - // Draw primary ammo in the bottom-right corner - DrINumberOuter (ammo1->Amount, -29, -15); - screen->DrawTexture (TexMan(ammo1->Icon), -14, -22, - DTA_HUDRules, HUD_Normal, - DTA_CenterBottomOffset, true, - TAG_DONE); - if (ammo2 != NULL && ammo2!=ammo1) - { - // Draw secondary ammo just above the primary ammo - DrINumberOuter (ammo2->Amount, -29, -56); - screen->DrawTexture (TexMan(ammo2->Icon), -14, -63, - DTA_HUDRules, HUD_Normal, - DTA_CenterBottomOffset, true, - TAG_DONE); - } - } - - // Draw inventory - if (CPlayer->inventorytics == 0) - { - if (ArtifactFlash) - { - screen->DrawTexture (Images[imgARTIBOX], -61, -31, - DTA_HUDRules, HUD_Normal, - DTA_Alpha, TRANSLUC50, - TAG_DONE); - screen->DrawTexture (Images[imgUSEARTIA + ArtifactFlash], -61, -31, - DTA_HUDRules, HUD_Normal, - TAG_DONE); - } - else if (CPlayer->mo->InvSel != NULL) - { - screen->DrawTexture (Images[imgARTIBOX], -61, -31, - DTA_HUDRules, HUD_Normal, - DTA_Alpha, TRANSLUC50, - TAG_DONE); - screen->DrawTexture (TexMan(CPlayer->mo->InvSel->Icon), -61, -31, - DTA_HUDRules, HUD_Normal, - DTA_ColorOverlay, CPlayer->mo->InvSel->Amount > 0 ? 0 : DIM_OVERLAY, - TAG_DONE); - if (CPlayer->mo->InvSel->Amount != 1) - { - DrSmallNumberOuter (CPlayer->mo->InvSel->Amount, -46, -9, false); - } - } - } - else - { - CPlayer->mo->InvFirst = ValidateInvFirst (7); - i = 0; - if (CPlayer->mo->InvFirst != NULL) - { - for (item = CPlayer->mo->InvFirst; item != NULL && i < 7; item = item->NextInv(), ++i) - { - screen->DrawTexture (Images[imgARTIBOX], -100+i*31, -32, - DTA_HUDRules, HUD_HorizCenter, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - screen->DrawTexture (TexMan(item->Icon), -100+i*31, -32, - DTA_HUDRules, HUD_HorizCenter, - DTA_ColorOverlay, item->Amount > 0 ? 0 : DIM_OVERLAY, - TAG_DONE); - if (item->Amount != 1) - { - DrSmallNumberOuter (item->Amount, -84+i*31, -10, true); - } - if (item == CPlayer->mo->InvSel) - { - screen->DrawTexture (Images[imgSELECTBOX], -100+i*31, -3, - DTA_HUDRules, HUD_HorizCenter, - TAG_DONE); - } - } - // Is there something to the left? - if (CPlayer->mo->FirstInv() != CPlayer->mo->InvFirst) - { - screen->DrawTexture (Images[!(gametic & 4) ? - imgINVLFGEM1 : imgINVLFGEM2], -112, -33, - DTA_HUDRules, HUD_HorizCenter, TAG_DONE); - } - // Is there something to the right? - if (item != NULL) - { - screen->DrawTexture (Images[!(gametic & 4) ? - imgINVRTGEM1 : imgINVRTGEM2], 119, -33, - DTA_HUDRules, HUD_HorizCenter, TAG_DONE); - } - } - for (; i < 7; i++) - { - screen->DrawTexture (Images[imgARTIBOX], -100+i*31, -32, - DTA_HUDRules, HUD_HorizCenter, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - } - } - } - -//--------------------------------------------------------------------------- -// -// PROC FlashItem -// -//--------------------------------------------------------------------------- - - void FlashItem (const PClass *itemtype) - { - ArtifactFlash = 4; - } - - static const char patcharti[][10]; - static const char ammopic[][10]; - - TObjPtr oldarti; - TObjPtr oldammo1, oldammo2; - int oldammocount1, oldammocount2; - int oldartiCount; - int oldfrags; - int oldarmor; - int oldhealth; - int oldlife; - int oldkeys; - - enum - { - imgLTFACE, - imgRTFACE, - imgBARBACK, - imgINVBAR, - imgCHAIN, - imgSTATBAR, - imgLIFEGEM, - imgLTFCTOP, - imgRTFCTOP, - imgSELECTBOX, - imgINVLFGEM1, - imgINVLFGEM2, - imgINVRTGEM1, - imgINVRTGEM2, - imgBLACKSQ, - imgARMCLEAR, - imgCHAINBACK, - imgGOD1, - imgGOD2, - imgUSEARTIA, - imgUSEARTIB, - imgUSEARTIC, - imgUSEARTID, - imgYKEYICON, - imgGKEYICON, - imgBKEYICON, - imgARTIBOX, - imgPTN1, - imgPTN2, - imgPTN3, - - NUM_HERETICSB_IMAGES - }; - - FImageCollection Images; - - int HealthMarker; - int ChainWiggle; - int ArtifactFlash; - - char HealthRefresh; - char ArtiRefresh; - char FragHealthRefresh; - char KeysRefresh; - char AmmoRefresh; - char ArmorRefresh; -}; - -IMPLEMENT_POINTY_CLASS(DHereticStatusBar) - DECLARE_POINTER(oldarti) - DECLARE_POINTER(oldammo1) - DECLARE_POINTER(oldammo2) -END_POINTERS - -DBaseStatusBar *CreateHereticStatusBar () -{ - return new DHereticStatusBar; -} diff --git a/src/g_hexen/a_clericmace.cpp b/src/g_hexen/a_clericmace.cpp index 39938183e..8b493f0ea 100644 --- a/src/g_hexen/a_clericmace.cpp +++ b/src/g_hexen/a_clericmace.cpp @@ -31,6 +31,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_CMaceAttack) return 0; } + PClassActor *hammertime = PClass::FindActor("HammerPuff"); + damage = 25+(pr_maceatk()&15); for (i = 0; i < 16; i++) { @@ -38,21 +40,23 @@ DEFINE_ACTION_FUNCTION(AActor, A_CMaceAttack) slope = P_AimLineAttack (player->mo, angle, 2*MELEERANGE, &linetarget); if (linetarget) { - P_LineAttack (player->mo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, PClass::FindActor("HammerPuff"), true); - AdjustPlayerAngle (player->mo, linetarget); -// player->mo->angle = R_PointToAngle2(player->mo->x, -// player->mo->y, linetarget->x, linetarget->y); - goto macedone; + P_LineAttack (player->mo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, hammertime, true, &linetarget); + if (linetarget != NULL) + { + AdjustPlayerAngle (player->mo, linetarget); + goto macedone; + } } angle = player->mo->angle-i*(ANG45/16); slope = P_AimLineAttack (player->mo, angle, 2*MELEERANGE, &linetarget); if (linetarget) { - P_LineAttack (player->mo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, PClass::FindActor("HammerPuff"), true); - AdjustPlayerAngle (player->mo, linetarget); -// player->mo->angle = R_PointToAngle2(player->mo->x, -// player->mo->y, linetarget->x, linetarget->y); - goto macedone; + P_LineAttack (player->mo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, hammertime, true, &linetarget); + if (linetarget != NULL) + { + AdjustPlayerAngle (player->mo, linetarget); + goto macedone; + } } } // didn't find any creatures, so try to strike any walls @@ -60,7 +64,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CMaceAttack) angle = player->mo->angle; slope = P_AimLineAttack (player->mo, angle, MELEERANGE, &linetarget); - P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, PClass::FindActor("HammerPuff")); + P_LineAttack (player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, hammertime); macedone: return 0; } diff --git a/src/g_hexen/a_clericstaff.cpp b/src/g_hexen/a_clericstaff.cpp index 211559b94..cec2fb708 100644 --- a/src/g_hexen/a_clericstaff.cpp +++ b/src/g_hexen/a_clericstaff.cpp @@ -56,6 +56,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CStaffCheck) int i; player_t *player; AActor *linetarget; + PClassActor *puff; if (NULL == (player = self->player)) { @@ -66,13 +67,14 @@ DEFINE_ACTION_FUNCTION(AActor, A_CStaffCheck) pmo = player->mo; damage = 20+(pr_staffcheck()&15); max = pmo->GetMaxHealth(); + puff = PClass::FindActor("CStaffPuff"); for (i = 0; i < 3; i++) { angle = pmo->angle+i*(ANG45/16); slope = P_AimLineAttack (pmo, angle, fixed_t(1.5*MELEERANGE), &linetarget, 0, ALF_CHECK3D); if (linetarget) { - P_LineAttack (pmo, angle, fixed_t(1.5*MELEERANGE), slope, damage, NAME_Melee, PClass::FindActor("CStaffPuff")); + P_LineAttack (pmo, angle, fixed_t(1.5*MELEERANGE), slope, damage, NAME_Melee, puff, false, &linetarget); pmo->angle = R_PointToAngle2 (pmo->x, pmo->y, linetarget->x, linetarget->y); if (((linetarget->player && (!linetarget->IsTeammate (pmo) || level.teamdamage != 0))|| linetarget->flags3&MF3_ISMONSTER) @@ -96,7 +98,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_CStaffCheck) slope = P_AimLineAttack (player->mo, angle, fixed_t(1.5*MELEERANGE), &linetarget, 0, ALF_CHECK3D); if (linetarget) { - P_LineAttack (pmo, angle, fixed_t(1.5*MELEERANGE), slope, damage, NAME_Melee, PClass::FindActor("CStaffPuff")); + P_LineAttack (pmo, angle, fixed_t(1.5*MELEERANGE), slope, damage, NAME_Melee, puff, false, &linetarget); pmo->angle = R_PointToAngle2 (pmo->x, pmo->y, linetarget->x, linetarget->y); if ((linetarget->player && (!linetarget->IsTeammate (pmo) || level.teamdamage != 0)) || linetarget->flags3&MF3_ISMONSTER) diff --git a/src/g_hexen/a_fighteraxe.cpp b/src/g_hexen/a_fighteraxe.cpp index 37635c4ee..194fcd45a 100644 --- a/src/g_hexen/a_fighteraxe.cpp +++ b/src/g_hexen/a_fighteraxe.cpp @@ -242,27 +242,33 @@ DEFINE_ACTION_FUNCTION(AActor, A_FAxeAttack) slope = P_AimLineAttack (pmo, angle, AXERANGE, &linetarget); if (linetarget) { - P_LineAttack (pmo, angle, AXERANGE, slope, damage, NAME_Melee, pufftype, true); - if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + P_LineAttack (pmo, angle, AXERANGE, slope, damage, NAME_Melee, pufftype, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj (linetarget, angle, power); + if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + { + P_ThrustMobj (linetarget, angle, power); + } + AdjustPlayerAngle (pmo, linetarget); + useMana++; + goto axedone; } - AdjustPlayerAngle (pmo, linetarget); - useMana++; - goto axedone; } angle = pmo->angle-i*(ANG45/16); slope = P_AimLineAttack (pmo, angle, AXERANGE, &linetarget); if (linetarget) { - P_LineAttack (pmo, angle, AXERANGE, slope, damage, NAME_Melee, pufftype, true); - if (linetarget->flags3&MF3_ISMONSTER) + P_LineAttack (pmo, angle, AXERANGE, slope, damage, NAME_Melee, pufftype, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj (linetarget, angle, power); + if (linetarget->flags3&MF3_ISMONSTER) + { + P_ThrustMobj (linetarget, angle, power); + } + AdjustPlayerAngle (pmo, linetarget); + useMana++; + goto axedone; } - AdjustPlayerAngle (pmo, linetarget); - useMana++; - goto axedone; } } // didn't find any creatures, so try to strike any walls diff --git a/src/g_hexen/a_fighterhammer.cpp b/src/g_hexen/a_fighterhammer.cpp index 4bacfa2e9..4ced50dc1 100644 --- a/src/g_hexen/a_fighterhammer.cpp +++ b/src/g_hexen/a_fighterhammer.cpp @@ -51,29 +51,35 @@ DEFINE_ACTION_FUNCTION(AActor, A_FHammerAttack) { angle = pmo->angle + i*(ANG45/32); slope = P_AimLineAttack (pmo, angle, HAMMER_RANGE, &linetarget, 0, ALF_CHECK3D); - if (linetarget) + if (linetarget != NULL) { - P_LineAttack (pmo, angle, HAMMER_RANGE, slope, damage, NAME_Melee, hammertime, true); - AdjustPlayerAngle(pmo, linetarget); - if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + P_LineAttack(pmo, angle, HAMMER_RANGE, slope, damage, NAME_Melee, hammertime, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj (linetarget, angle, power); + AdjustPlayerAngle(pmo, linetarget); + if (linetarget->flags3 & MF3_ISMONSTER || linetarget->player) + { + P_ThrustMobj(linetarget, angle, power); + } + pmo->special1 = false; // Don't throw a hammer + goto hammerdone; } - pmo->special1 = false; // Don't throw a hammer - goto hammerdone; } angle = pmo->angle-i*(ANG45/32); slope = P_AimLineAttack(pmo, angle, HAMMER_RANGE, &linetarget, 0, ALF_CHECK3D); - if(linetarget) + if (linetarget != NULL) { - P_LineAttack(pmo, angle, HAMMER_RANGE, slope, damage, NAME_Melee, hammertime, true); - AdjustPlayerAngle(pmo, linetarget); - if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + P_LineAttack(pmo, angle, HAMMER_RANGE, slope, damage, NAME_Melee, hammertime, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj(linetarget, angle, power); + AdjustPlayerAngle(pmo, linetarget); + if (linetarget->flags3 & MF3_ISMONSTER || linetarget->player) + { + P_ThrustMobj(linetarget, angle, power); + } + pmo->special1 = false; // Don't throw a hammer + goto hammerdone; } - pmo->special1 = false; // Don't throw a hammer - goto hammerdone; } } // didn't find any targets in meleerange, so set to throw out a hammer diff --git a/src/g_hexen/a_fighterplayer.cpp b/src/g_hexen/a_fighterplayer.cpp index db106b941..c98794e1c 100644 --- a/src/g_hexen/a_fighterplayer.cpp +++ b/src/g_hexen/a_fighterplayer.cpp @@ -167,13 +167,16 @@ DEFINE_ACTION_FUNCTION(AActor, A_FPunchAttack) power = 6*FRACUNIT; pufftype = PClass::FindActor("HammerPuff"); } - P_LineAttack (pmo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, pufftype, true); - if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + P_LineAttack (pmo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, pufftype, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj (linetarget, angle, power); + if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + { + P_ThrustMobj (linetarget, angle, power); + } + AdjustPlayerAngle (pmo, linetarget); + goto punchdone; } - AdjustPlayerAngle (pmo, linetarget); - goto punchdone; } angle = pmo->angle-i * (ANG45/16); slope = P_AimLineAttack (pmo, angle, 2*MELEERANGE, &linetarget); @@ -186,13 +189,16 @@ DEFINE_ACTION_FUNCTION(AActor, A_FPunchAttack) power = 6*FRACUNIT; pufftype = PClass::FindActor("HammerPuff"); } - P_LineAttack (pmo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, pufftype, true); - if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + P_LineAttack (pmo, angle, 2*MELEERANGE, slope, damage, NAME_Melee, pufftype, true, &linetarget); + if (linetarget != NULL) { - P_ThrustMobj (linetarget, angle, power); + if (linetarget->flags3&MF3_ISMONSTER || linetarget->player) + { + P_ThrustMobj (linetarget, angle, power); + } + AdjustPlayerAngle (pmo, linetarget); + goto punchdone; } - AdjustPlayerAngle (pmo, linetarget); - goto punchdone; } } // didn't find any creatures, so try to strike any walls diff --git a/src/g_hexen/a_fog.cpp b/src/g_hexen/a_fog.cpp index e755c6f10..2b01761a6 100644 --- a/src/g_hexen/a_fog.cpp +++ b/src/g_hexen/a_fog.cpp @@ -83,7 +83,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FogMove) if (self->args[3]-- <= 0) { - self->SetStateNF (self->FindState(NAME_Death)); + self->SetState (self->FindState(NAME_Death), true); return 0; } diff --git a/src/g_hexen/a_pig.cpp b/src/g_hexen/a_pig.cpp index e75ef9921..f7888f2e9 100644 --- a/src/g_hexen/a_pig.cpp +++ b/src/g_hexen/a_pig.cpp @@ -77,7 +77,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SnoutAttack) damage = 3+(pr_snoutattack()&3); angle = player->mo->angle; slope = P_AimLineAttack(player->mo, angle, MELEERANGE, &linetarget); - puff = P_LineAttack(player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "SnoutPuff", true); + puff = P_LineAttack(player->mo, angle, MELEERANGE, slope, damage, NAME_Melee, "SnoutPuff", true, &linetarget); S_Sound(player->mo, CHAN_VOICE, "PigActive", 1, ATTN_NORM); if(linetarget) { diff --git a/src/g_hexen/a_spike.cpp b/src/g_hexen/a_spike.cpp index 741781280..177b64b49 100644 --- a/src/g_hexen/a_spike.cpp +++ b/src/g_hexen/a_spike.cpp @@ -115,9 +115,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_ThrustRaise) { // Reached it's target height actor->args[0] = 1; if (actor->args[1]) - actor->SetStateNF (actor->FindState ("BloodThrustInit2")); + actor->SetState (actor->FindState ("BloodThrustInit2"), true); else - actor->SetStateNF (actor->FindState ("ThrustInit2")); + actor->SetState (actor->FindState ("ThrustInit2"), true); } // Lose the dirt clump @@ -142,9 +142,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_ThrustLower) { self->args[0] = 0; if (self->args[1]) - self->SetStateNF (self->FindState ("BloodThrustInit1")); + self->SetState (self->FindState ("BloodThrustInit1"), true); else - self->SetStateNF (self->FindState ("ThrustInit1")); + self->SetState (self->FindState ("ThrustInit1"), true); } return 0; } diff --git a/src/g_hexen/hexen_sbar.cpp b/src/g_hexen/hexen_sbar.cpp deleted file mode 100644 index caf62e59e..000000000 --- a/src/g_hexen/hexen_sbar.cpp +++ /dev/null @@ -1,1194 +0,0 @@ -#include - -#include "doomtype.h" -#include "doomstat.h" -#include "v_font.h" -#include "sbar.h" -#include "r_defs.h" -#include "w_wad.h" -#include "m_random.h" -#include "d_player.h" -#include "st_stuff.h" -#include "v_video.h" -#include "r_draw.h" -#include "templates.h" -#include "a_hexenglobal.h" -#include "a_keys.h" -#include "r_translate.h" -#include "a_weaponpiece.h" -#include "v_palette.h" - - -class FManaBar : public FTexture -{ -public: - FManaBar (); - - const BYTE *GetColumn (unsigned int column, const Span **spans_out); - const BYTE *GetPixels (); - void Unload (); - bool CheckModified (); - - void SetVial (FTexture *pic, AActor *actor, PClassActor *manaType); - -protected: - BYTE Pixels[5*24]; - static const Span DummySpan[2]; - - FTexture *VialPic; - int VialLevel; - bool NeedRefresh; - - void MakeTexture (); -}; - -const FTexture::Span FManaBar::DummySpan[2] = { { 0, 24 }, { 0, 0 } }; - -FManaBar::FManaBar () -: VialPic(0), VialLevel(0), NeedRefresh(false) -{ - Width = 5; - Height = 24; - WidthBits = 2; - HeightBits = 5; - WidthMask = 3; -} - -void FManaBar::Unload () -{ - if (VialPic != 0) - { - VialPic->Unload (); - } -} - -bool FManaBar::CheckModified () -{ - return NeedRefresh; -} - -const BYTE *FManaBar::GetColumn (unsigned int column, const Span **spans_out) -{ - if (NeedRefresh) - { - MakeTexture (); - } - if (column > 4) - { - column = 4; - } - if (spans_out != NULL) - { - *spans_out = DummySpan; - } - return Pixels + column*24; -} - -const BYTE *FManaBar::GetPixels () -{ - if (NeedRefresh) - { - MakeTexture (); - } - return Pixels; -} - -void FManaBar::SetVial (FTexture *pic, AActor *actor, PClassActor *manaType) -{ - int level, max; - AInventory *ammo; - - ammo = actor->FindInventory (manaType); - level = 0; max = 200; - if (ammo != NULL) - { - level = ammo->Amount; - max = ammo->MaxAmount; - if (!max) max = 1; - } - level = MIN (22*level/max, 22); - if (VialPic != pic || VialLevel != level) - { - VialPic = pic; - VialLevel = level; - NeedRefresh = true; - } -} - -void FManaBar::MakeTexture () -{ - int run = 22 - VialLevel; - BYTE color0 = GPalette.Remap[0]; - - NeedRefresh = false; - VialPic->CopyToBlock (Pixels, 5, 24, 0, 0); - memset (Pixels + 25, color0, run); - memset (Pixels + 25+24, color0, run); - memset (Pixels + 25+24+24, color0, run); -} - -class DHexenStatusBar : public DBaseStatusBar -{ - DECLARE_CLASS(DHexenStatusBar, DBaseStatusBar) - HAS_OBJECT_POINTERS -public: - DHexenStatusBar () : DBaseStatusBar (38) - { - static const char *hexenLumpNames[NUM_HEXENSB_IMAGES] = - { - "H2BAR", "H2TOP", "INVBAR", "LFEDGE", "RTEDGE", - "STATBAR", "KEYBAR", "SELECTBO", "ARTICLS", "ARMCLS", - "MANACLS", "MANAVL1", "MANAVL2", "MANAVL1D", "MANAVL2D", - "MANADIM1", "MANADIM2", "MANABRT1", "MANABRT2", "INVGEML1", - "INVGEML2", "INVGEMR1", "INVGEMR2", "KILLS", "USEARTIA", - "USEARTIB", "USEARTIC", "USEARTID", "USEARTIE", "KEYSLOT1", - "KEYSLOT2", "KEYSLOT3", "KEYSLOT4", "KEYSLOT5", "KEYSLOT6", - "KEYSLOT7", "KEYSLOT8", "KEYSLOT9", "KEYSLOTA", "KEYSLOTB", - "ARMSLOT1", "ARMSLOT2", "ARMSLOT3", "ARMSLOT4", "ARTIBOX", - "HAMOBACK" - }; - static const char *classLumpNames[3][NUM_HEXENCLASSSB_IMAGES] = - { - { - "WPSLOT0", "WPFULL0", "WPIECEF1", "WPIECEF2", - "WPIECEF3", "CHAIN", "LIFEGMF2" - }, - { - "WPSLOT1", "WPFULL1", "WPIECEC1", "WPIECEC2", - "WPIECEC3", "CHAIN2", "LIFEGMC2" - }, - { - "WPSLOT2", "WPFULL2", "WPIECEM1", "WPIECEM2", - "WPIECEM3", "CHAIN3", "LIFEGMM2" - } - }; - static const char *sharedLumpNames[] = - { - "LAME", "NEGNUM", "IN0", "IN1", "IN2", - "IN3", "IN4", "IN5", "IN6", "IN7", - "IN8", "IN9", "FONTB13", "FONTB16", "FONTB17", - "FONTB18", "FONTB19", "FONTB20", "FONTB21", "FONTB22", - "FONTB23", "FONTB24", "FONTB25", "SMALLIN0", "SMALLIN1", - "SMALLIN2", "SMALLIN3", "SMALLIN4", "SMALLIN5", "SMALLIN6", - "SMALLIN7", "SMALLIN8", "SMALLIN9", - - "INRED0", "INRED1", "INRED2", "INRED3", "INRED4", - "INRED5", "INRED6", "INRED7", "INRED8", "INRED9" - }; - - DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES + 10); - Images.Init (hexenLumpNames, NUM_HEXENSB_IMAGES); - ClassImages[0].Init (classLumpNames[0], NUM_HEXENCLASSSB_IMAGES); - ClassImages[1].Init (classLumpNames[1], NUM_HEXENCLASSSB_IMAGES); - ClassImages[2].Init (classLumpNames[2], NUM_HEXENCLASSSB_IMAGES); - - oldarti = NULL; - oldartiCount = 0; - oldammo1 = oldammo2 = NULL; - oldammocount1 = oldammocount2 = -1; - oldfrags = -9999; - oldmana1 = -1; - oldmana2 = -1; - oldusemana1 = -1; - oldusemana2 = -1; - olddrawbars = -1; - oldarmor = -1; - oldhealth = -1; - oldlife = -1; - oldpieces = -1; - oldkeys[0] = oldkeys[1] = oldkeys[2] = oldkeys[3] = oldkeys[4] = NULL; - - HealthMarker = 0; - ArtifactFlash = 0; - - ArtiRefresh = 0; - FragHealthRefresh = 0; - KeysRefresh = 0; - ArmorRefresh = 0; - HealthRefresh = 0; - Mana1Refresh = 0; - Mana2Refresh = 0; - AmmoRefresh = 0; - } - - ~DHexenStatusBar () - { - } - - void Tick () - { - int curHealth; - - DBaseStatusBar::Tick (); - if (CPlayer->mo == NULL) - { - curHealth = 0; - } - else - { - curHealth = CPlayer->mo->health; - } - if (curHealth < 0) - { - curHealth = 0; - } - if (curHealth < HealthMarker) - { - HealthMarker -= clamp ((HealthMarker - curHealth) >> 2, 1, 6); - } - else if (curHealth > HealthMarker) - { - HealthMarker += clamp ((curHealth - HealthMarker) >> 2, 1, 6); - } - - if (ArtifactFlash > 0) - { - if (--ArtifactFlash == 0) - { - ArtiRefresh = screen->GetPageCount (); - } - } - } - - void Draw (EHudState state) - { - DBaseStatusBar::Draw (state); - - if (state == HUD_Fullscreen) - { - DrawFullScreenStuff (); - SB_state = screen->GetPageCount (); - } - else if (state == HUD_StatusBar) - { - if (SB_state > 0) - { - DrawImage (Images[imgH2BAR], 0, -27); - oldhealth = -1; - } - DrawCommonBar (); - if (CPlayer->inventorytics == 0) - { - if (SB_state < 0) - { - SB_state = screen->GetPageCount (); - } - if (SB_state != 0) - { - // Main interface - SB_state--; - DrawImage (Images[!automapactive ? imgSTATBAR : imgKEYBAR], 38, 0); - oldarti = NULL; - oldammo1 = oldammo2 = NULL; - oldmana1 = -1; - oldmana2 = -1; - oldusemana1 = -1; - oldusemana2 = -1; - olddrawbars = -1; - oldarmor = -1; - oldpieces = -1; - oldfrags = -9999; //can't use -1, 'cuz of negative frags - oldlife = -1; - oldkeys[0] = oldkeys[1] = oldkeys[2] = oldkeys[3] = oldkeys[4] = NULL; - ArtiRefresh = 0; - //oldhealth = -1; - } - if (!automapactive) - { - DrawMainBar (); - } - else - { - DrawKeyBar (); - } - } - else - { - if (SB_state > -1) - { - SB_state = -screen->GetPageCount () - 1; - } - if (SB_state < -1) - { - SB_state++; - } - DrawInventoryBar (); - } - } - } - - void AttachToPlayer (player_t *player) - { - DBaseStatusBar::AttachToPlayer (player); - if (player->mo != NULL) - { - if (player->mo->IsKindOf (PClass::FindClass(NAME_MagePlayer))) - { - FourthWeaponClass = 2; - LifeBarClass = 2; - } - else if (player->mo->IsKindOf (PClass::FindClass(NAME_ClericPlayer))) - { - FourthWeaponClass = 1; - LifeBarClass = 1; - } - else - { - FourthWeaponClass = 0; - LifeBarClass = 0; - } - } - } - -private: -//--------------------------------------------------------------------------- -// -// PROC DrawCommonBar -// -//--------------------------------------------------------------------------- - - void DrawCommonBar () - { - int healthPos; - - DrawImage (Images[imgH2TOP], 0, -27); - - if (oldhealth != HealthMarker) - { - oldhealth = HealthMarker; - HealthRefresh = screen->GetPageCount (); - } - if (HealthRefresh) - { - int lifeClass = LifeBarClass; - - HealthRefresh--; - healthPos = clamp (HealthMarker, 0, 100); - DrawImage (ClassImages[lifeClass][imgCHAIN], 35+((healthPos*196/100)%9), 31); - DrawImage (ClassImages[lifeClass][imgLIFEGEM], 7+(healthPos*11/5), 31, multiplayer ? - translationtables[TRANSLATION_PlayersExtra][int(CPlayer - players)] : NULL); - DrawImage (Images[imgLFEDGE], 0, 31); - DrawImage (Images[imgRTEDGE], 277, 31); - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawMainBar -// -//--------------------------------------------------------------------------- - - void DrawMainBar () - { - int temp; - - // Ready artifact - if (ArtifactFlash) - { - DrawImage (Images[imgARTICLEAR], 144, -1); - DrawImage (Images[imgUSEARTIA + ArtifactFlash], 148, 2); - oldarti = NULL; // so that the correct artifact fills in after the flash - } - else if (oldarti != CPlayer->mo->InvSel - || (oldarti != NULL && oldartiCount != oldarti->Amount)) - { - oldarti = CPlayer->mo->InvSel; - GC::WriteBarrier(this, oldarti); - oldartiCount = oldarti != NULL ? oldarti->Amount : 0; - ArtiRefresh = screen->GetPageCount (); - } - if (ArtiRefresh) - { - ArtiRefresh--; - DrawImage (Images[imgARTICLEAR], 144, -1); - if (oldarti != NULL) - { - DrawDimImage (TexMan(oldarti->Icon), 143, 2, oldarti->Amount <= 0); - if (oldartiCount != 1) - { - DrSmallNumber (oldartiCount, 162, 23); - } - } - } - - // Frags - if (deathmatch) - { - temp = CPlayer->fragcount; - if (temp != oldfrags) - { - oldfrags = temp; - FragHealthRefresh = screen->GetPageCount (); - } - if (FragHealthRefresh) - { - FragHealthRefresh--; - DrawImage (Images[imgKILLS], 38, 1); - DrINumber (temp, 40, 15); - } - } - else - { - temp = MAX (0, HealthMarker); - if (oldlife != temp) - { - oldlife = temp; - FragHealthRefresh = screen->GetPageCount (); - } - if (FragHealthRefresh) - { - FragHealthRefresh--; - DrawImage (Images[imgARMCLEAR], 41, 16); - DrINumber (temp, 40, 14, temp >= 25 ? imgINumbers : NUM_BASESB_IMAGES); - } - } - - // Mana - AAmmo *ammo1, *ammo2; - int ammocount1, ammocount2; - int drawbar; - - GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2); - if (ammo1==ammo2) - { - // Don't show the same ammo twice. - ammo2=NULL; - } - - // If the weapon uses some ammo that is not mana, do not draw - // the mana bars; draw the specific used ammo instead. - PClassActor *mana1 = PClass::FindActor(NAME_Mana1); - PClassActor *mana2 = PClass::FindActor(NAME_Mana2); - - drawbar = !((ammo1 != NULL && ammo1->GetClass() != mana1 && ammo1->GetClass() != mana2) || - (ammo2 != NULL && ammo2->GetClass() != mana1 && ammo2->GetClass() != mana2)); - - if (drawbar != olddrawbars) - { - AmmoRefresh = screen->GetPageCount (); - olddrawbars = drawbar; - oldmana1 = -1; - oldmana2 = -1; - } - if (drawbar && oldammo2 != ammo2) - { - AmmoRefresh = screen->GetPageCount (); - } - if (drawbar) - { - DrawManaBars (ammo1, ammo2, mana1, mana2); - } - else - { - DrawMainAltAmmo (ammo1, ammo2, ammocount1, ammocount2); - } - - // Armor - temp = GetArmorPercent (NULL); - if (oldarmor != temp) - { - oldarmor = temp; - ArmorRefresh = screen->GetPageCount (); - } - if (ArmorRefresh) - { - ArmorRefresh--; - DrawImage (Images[imgARMCLEAR], 255, 16); - DrINumber (temp / (5*FRACUNIT), 250, 14); - } - - // Weapon Pieces - DrawWeaponPieces(); - } - -//--------------------------------------------------------------------------- -// -// PROC DrawMainAltAmmo -// -// Draw a generic ammo readout on the main bar, instead of the mana bars. -// -//--------------------------------------------------------------------------- - - void DrawMainAltAmmo (AAmmo *ammo1, AAmmo *ammo2, int ammocount1, int ammocount2) - { - if (ammo1 != oldammo1 || ammocount1 != oldammocount1) - { - AmmoRefresh = screen->GetPageCount (); - oldammo1 = ammo1; - oldammocount1 = ammocount1; - GC::WriteBarrier(this, ammo1); - } - if (ammo2 != oldammo2 || ammocount2 != oldammocount2) - { - AmmoRefresh = screen->GetPageCount (); - oldammo2 = ammo2; - oldammocount2 = ammocount2; - GC::WriteBarrier(this, ammo2); - } - - if (AmmoRefresh) - { - AmmoRefresh--; - DrawImage (Images[imgAMMOBACK], 77, 2); - if (ammo2 != NULL) - { // Draw both ammos - AmmoRefresh--; - screen->DrawTexture (TexMan[ammo1->Icon], 89+ST_X, 10+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, true, - TAG_DONE); - DrSmallNumber (ammo1->Amount, 86, 20); - - screen->DrawTexture (TexMan[ammo2->Icon], 113+ST_X, 10+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, true, - TAG_DONE); - DrSmallNumber (ammo2->Amount, 110, 20); - } - else - { // Draw one ammo - screen->DrawTexture (TexMan[ammo1->Icon], 100+ST_X, 10+ST_Y, - DTA_CenterOffset, true, - DTA_Bottom320x200, true, - TAG_DONE); - DrSmallNumber (ammo1->Amount, 97, 20); - } - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawManaBars -// -// Draws the mana bars on the main status bar -// -//--------------------------------------------------------------------------- - - void DrawManaBars (AAmmo *ammo1, AAmmo *ammo2, PClassActor *manatype1, PClassActor *manatype2) - { - AAmmo *mana1 = NULL, *mana2 = NULL; - int usemana1 = false, usemana2 = false; - int manacount1, manacount2; - int manaPatch1, manaPatch2; - int manaVialPatch1, manaVialPatch2; - - manaPatch1 = 0; - manaPatch2 = 0; - manaVialPatch1 = 0; - manaVialPatch2 = 0; - - if (AmmoRefresh) - { - Mana1Refresh = MAX(AmmoRefresh, Mana1Refresh); - Mana2Refresh = MAX(AmmoRefresh, Mana2Refresh); - AmmoRefresh--; - screen->DrawTexture (Images[imgSTATBAR], ST_X+38, ST_Y, - DTA_WindowLeft, 39, - DTA_WindowRight, 87, - DTA_Bottom320x200, Scaled, - TAG_DONE); - } - - // Locate Mana1 and Mana2 in the inventory, and decide which ones are used. - if (ammo1 == NULL) - { - } - else if (ammo1->GetClass() == manatype1) - { - mana1 = ammo1; - usemana1 = true; - } - else if (ammo1->GetClass() == manatype2) - { - mana2 = ammo1; - usemana2 = true; - } - if (ammo2 == NULL) - { - } - else if (ammo2->GetClass() == manatype1) - { - mana1 = ammo2; - usemana1 = true; - } - else if (ammo2->GetClass() == manatype2) - { - mana2 = ammo2; - usemana2 = true; - } - if (mana1 == NULL) - { - mana1 = static_cast(CPlayer->mo->FindInventory(manatype1)); - } - if (mana2 == NULL) - { - mana2 = static_cast(CPlayer->mo->FindInventory(manatype2)); - } - manacount1 = mana1 != NULL ? mana1->Amount : 0; - manacount2 = mana2 != NULL ? mana2->Amount : 0; - - // Has Mana1 changed since last time? - if (oldmana1 != manacount1 || oldusemana1 != usemana1) - { - oldmana1 = manacount1; - oldusemana1 = usemana1; - Mana1Refresh = screen->GetPageCount (); - } - - // Has Mana2 changed since last time? - if (oldmana2 != manacount2 || oldusemana2 != usemana2) - { - oldmana2 = manacount2; - oldusemana2 = usemana2; - Mana2Refresh = screen->GetPageCount (); - } - // Decide what to draw for vial 1 - if (Mana1Refresh) - { - Mana1Refresh--; - DrawImage (Images[imgMANACLEAR], 77, 16); - DrSmallNumber (manacount1, 79, 19); - if (!usemana1) - { // Draw Dim Mana icon - manaPatch1 = imgMANADIM1; - manaVialPatch1 = imgMANAVIALDIM1; - } - else - { - manaPatch1 = imgMANABRIGHT1; - manaVialPatch1 = manacount1 ? imgMANAVIAL1 : imgMANAVIALDIM1; - } - } - // Decide what to draw for vial 2 - if (Mana2Refresh) - { - Mana2Refresh--; - DrawImage (Images[imgMANACLEAR], 109, 16); - DrSmallNumber (manacount2, 111, 19); - if (!usemana2) - { // Draw Dim Mana icon - manaPatch2 = imgMANADIM2; - manaVialPatch2 = imgMANAVIALDIM2; - } - else - { - manaPatch2 = imgMANABRIGHT2; - manaVialPatch2 = manacount2 ? imgMANAVIAL2 : imgMANAVIALDIM2; - } - } - // Update mana graphics - if (manaPatch1 || manaPatch2 || manaVialPatch1) - { - if (manaVialPatch1) - { - DrawImage (Images[manaPatch1], 77, 2); - ManaVial1Pic.SetVial (Images[manaVialPatch1], CPlayer->mo, manatype1); - DrawImage (&ManaVial1Pic, 94, 2); - } - if (manaVialPatch2) - { - DrawImage (Images[manaPatch2], 110, 2); - ManaVial2Pic.SetVial (Images[manaVialPatch2], CPlayer->mo, manatype2); - DrawImage (&ManaVial2Pic, 102, 2); - } - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawInventoryBar -// -//--------------------------------------------------------------------------- - - void DrawInventoryBar () - { - AInventory *item; - int i; - - DrawImage (Images[imgINVBAR], 38, 0); - CPlayer->mo->InvFirst = ValidateInvFirst (7); - if (CPlayer->mo->InvFirst != NULL) - { - for (item = CPlayer->mo->InvFirst, i = 0; item != NULL && i < 7; item = item->NextInv(), ++i) - { - DrawDimImage (TexMan(item->Icon), 50+i*31, 1, item->Amount <= 0); - if (item->Amount != 1) - { - DrSmallNumber (item->Amount, 68+i*31, 23); - } - if (item == CPlayer->mo->InvSel) - { - DrawImage (Images[imgSELECTBOX], 51+i*31, 1); - } - } - // Is there something to the left? - if (CPlayer->mo->FirstInv() != CPlayer->mo->InvFirst) - { - DrawImage (Images[!(gametic & 4) ? - imgINVLFGEM1 : imgINVLFGEM2], 42, 1); - } - // Is there something to the right? - if (item != NULL) - { - DrawImage (Images[!(gametic & 4) ? - imgINVRTGEM1 : imgINVRTGEM2], 269, 1); - } - } - } - -//========================================================================== -// -// DrawKeyBar -// -//========================================================================== - - void DrawKeyBar () - { - AInventory *item; - AHexenArmor *armor; - AKey *keys[5]; - int i; - int temp; - bool different; - - keys[0] = keys[1] = keys[2] = keys[3] = keys[4] = NULL; - for (item = CPlayer->mo->Inventory, i = 0; - item != NULL && i < 5; - item = item->Inventory) - { - if (item->Icon.isValid() && - item->IsKindOf (RUNTIME_CLASS(AKey)) && - item->GetClass() != RUNTIME_CLASS(AKey)) - { - keys[i++] = static_cast(item); - } - } - different = false; - for (i = 0; i < 5; ++i) - { - if (keys[i] != oldkeys[i]) - { - oldkeys[i] = keys[i]; - GC::WriteBarrier(this, keys[i]); - different = true; - } - } - if (different) - { - KeysRefresh = screen->GetPageCount (); - } - if (KeysRefresh) - { - KeysRefresh--; - for (i = 0; i < 5 && keys[i] != NULL; i++) - { - DrawImage (TexMan[keys[i]->Icon], 46 + i*20, 2); - } - } - - temp = GetArmorPercent (&armor); - if (oldarmor != temp && armor != NULL) - { - for (i = 0; i < 4; i++) - { - if (armor->Slots[i] > 0 && armor->SlotsIncrement[i] > 0) - { - DrawFadedImage (Images[imgARMSLOT1+i], 150+31*i, 2, - MIN (OPAQUE, Scale (armor->Slots[i], OPAQUE, - armor->SlotsIncrement[i]))); - } - } - oldarmor = temp; - } - } - -//========================================================================== -// -// GetArmorPercent -// -//========================================================================== - - fixed_t GetArmorPercent (AHexenArmor **armorp) - { - AHexenArmor *harmor = CPlayer->mo->FindInventory(); - fixed_t amount = 0; - if (harmor != NULL) - { - amount = harmor->Slots[0] - + harmor->Slots[1] - + harmor->Slots[2] - + harmor->Slots[3] - + harmor->Slots[4]; - } - // [RH] Count basic armor too. - ABasicArmor *barmor = CPlayer->mo->FindInventory(); - if (barmor != NULL) - { - amount += barmor->SavePercent; - } - if (armorp != NULL) - { - *armorp = harmor; - } - return amount; - } - -//========================================================================== -// -// DrawWeaponPieces -// -//========================================================================== - - void DrawWeaponPieces () - { - static ENamedName FourthWeaponNames[] = { NAME_FWeapQuietus, NAME_CWeapWraithverge, NAME_MWeapBloodscourge }; - - for(AInventory *inv = CPlayer->mo->Inventory; inv != NULL; inv = inv->Inventory) - { - if (inv->IsA(RUNTIME_CLASS(AWeaponHolder))) - { - AWeaponHolder *hold = static_cast(inv); - - if (hold->PieceWeapon->TypeName == FourthWeaponNames[(int)FourthWeaponClass]) - { - // Weapon Pieces - if (oldpieces != hold->PieceMask) - { - oldpieces = hold->PieceMask; - - static int PieceX[3][3] = - { - { 190, 225, 234 }, - { 190, 212, 225 }, - { 190, 205, 224 }, - }; - int pieces = oldpieces; - int weapClass = FourthWeaponClass; - - if (pieces == 7) - { - DrawImage (ClassImages[weapClass][imgWEAPONFULL], 190, 0); - return; - } - DrawImage (ClassImages[weapClass][imgWEAPONSLOT], 190, 0); - if (pieces & WPIECE1) - { - DrawImage (ClassImages[weapClass][imgPIECE1], PieceX[weapClass][0], 0); - } - if (pieces & WPIECE2) - { - DrawImage (ClassImages[weapClass][imgPIECE2], PieceX[weapClass][1], 0); - } - if (pieces & WPIECE3) - { - DrawImage (ClassImages[weapClass][imgPIECE3], PieceX[weapClass][2], 0); - } - } - return; - } - } - } - if (oldpieces != 0) - { - DrawImage (ClassImages[(int)FourthWeaponClass][imgWEAPONSLOT], 190, 0); - oldpieces = 0; - } - } - -//--------------------------------------------------------------------------- -// -// PROC DrawFullScreenStuff -// -//--------------------------------------------------------------------------- - - void DrawFullScreenStuff () - { - AInventory *item; - int i; - - // Health - DrBNumberOuter (MAX (0, CPlayer->mo->health), 5, -20); - - // Frags - if (deathmatch) - { - DrINumberOuter (CPlayer->fragcount, 45, -15); - } - - // Inventory - if (CPlayer->inventorytics == 0) - { - if (ArtifactFlash) - { - screen->DrawTexture (Images[imgARTIBOX], -80, -30, - DTA_HUDRules, HUD_Normal, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - screen->DrawTexture (Images[imgUSEARTIA + ArtifactFlash], -76, -26, - DTA_HUDRules, HUD_Normal, - TAG_DONE); - } - else if (CPlayer->mo->InvSel != NULL) - { - screen->DrawTexture (Images[imgARTIBOX], -80, -30, - DTA_HUDRules, HUD_Normal, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - screen->DrawTexture (TexMan(CPlayer->mo->InvSel->Icon), -82, -31, - DTA_HUDRules, HUD_Normal, - DTA_ColorOverlay, CPlayer->mo->InvSel->Amount > 0 ? 0 : DIM_OVERLAY, - TAG_DONE); - if (CPlayer->mo->InvSel->Amount != 1) - { - DrSmallNumberOuter (CPlayer->mo->InvSel->Amount, -64, -8, false); - } - } - } - else - { - CPlayer->mo->InvFirst = ValidateInvFirst (7); - i = 0; - if (CPlayer->mo->InvFirst != NULL) - { - for (item = CPlayer->mo->InvFirst; item != NULL && i < 7; item = item->NextInv(), ++i) - { - screen->DrawTexture (Images[imgARTIBOX], -106+i*31, -32, - DTA_HUDRules, HUD_HorizCenter, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - screen->DrawTexture (TexMan(item->Icon), -108+i*31, -33, - DTA_HUDRules, HUD_HorizCenter, - DTA_ColorOverlay, item->Amount > 0 ? 0 : DIM_OVERLAY, - TAG_DONE); - if (item->Amount != 1) - { - DrSmallNumberOuter (item->Amount, -90+i*31, -11, true); - } - if (item == CPlayer->mo->InvSel) - { - screen->DrawTexture (Images[imgSELECTBOX], -107+i*31, -33, - DTA_HUDRules, HUD_HorizCenter, - TAG_DONE); - } - } - // Is there something to the left? - if (CPlayer->mo->FirstInv() != CPlayer->mo->InvFirst) - { - screen->DrawTexture (Images[!(gametic & 4) ? - imgINVLFGEM1 : imgINVLFGEM2], -118, -33, - DTA_HUDRules, HUD_HorizCenter, TAG_DONE); - } - // Is there something to the right? - if (item != NULL) - { - screen->DrawTexture (Images[!(gametic & 4) ? - imgINVRTGEM1 : imgINVRTGEM2], 113, -33, - DTA_HUDRules, HUD_HorizCenter, TAG_DONE); - } - } - for (; i < 7; i++) - { - screen->DrawTexture (Images[imgARTIBOX], -106+i*31, -32, - DTA_HUDRules, HUD_HorizCenter, - DTA_Alpha, HX_SHADOW, - TAG_DONE); - } - } - - // Mana - AAmmo *ammo1, *ammo2; - int ammocount1, ammocount2; - bool drawmana; - - GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2); - - // If the weapon uses some ammo that is not mana, do not draw - // the mana blocks; draw the specific used ammo instead. - PClassActor *mana1 = PClass::FindActor(NAME_Mana1); - PClassActor *mana2 = PClass::FindActor(NAME_Mana2); - - drawmana = !((ammo1 != NULL && ammo1->GetClass() != mana1 && ammo1->GetClass() != mana2) || - (ammo2 != NULL && ammo2->GetClass() != mana1 && ammo2->GetClass() != mana2)); - - if (drawmana) - { - int manaImage; - int ammo = 0; - - if (CPlayer->ReadyWeapon != NULL) - { - if (CPlayer->ReadyWeapon->Ammo1 != NULL) - { - if (CPlayer->ReadyWeapon->Ammo1->GetClass() == mana1) - { - ammo |= 1; - } - else if (CPlayer->ReadyWeapon->Ammo1->GetClass() == mana2) - { - ammo |= 2; - } - } - if (CPlayer->ReadyWeapon->Ammo2 != NULL) - { - if (CPlayer->ReadyWeapon->Ammo2->GetClass() == mana1) - { - ammo |= 1; - } - else if (CPlayer->ReadyWeapon->Ammo2->GetClass() == mana2) - { - ammo |= 2; - } - } - } - - item = CPlayer->mo->FindInventory (mana1); - i = item != NULL ? item->Amount : 0; - manaImage = ((ammo & 1) && i > 0) ? imgMANABRIGHT1 : imgMANADIM1; - screen->DrawTexture (Images[manaImage], -17, -30, - DTA_HUDRules, HUD_Normal, TAG_DONE); - DrINumberOuter (i, -47, -30); - - item = CPlayer->mo->FindInventory (mana2); - i = item != NULL ? item->Amount : 0; - manaImage = ((ammo & 2) && i > 0) ? imgMANABRIGHT2 : imgMANADIM2; - screen->DrawTexture (Images[manaImage], -17, -15, - DTA_HUDRules, HUD_Normal, TAG_DONE); - DrINumberOuter (i, -47, -15); - } - else - { - } - } - -//--------------------------------------------------------------------------- -// -// PROC FlashItem -// -//--------------------------------------------------------------------------- - - void FlashItem (const PClass *itemtype) - { - ArtifactFlash = 4; - } - - static const char patcharti[][10]; - static const char ammopic[][10]; - - TObjPtr oldarti; - TObjPtr oldammo1, oldammo2; - TObjPtr oldkeys[5]; - int oldammocount1, oldammocount2; - int oldartiCount; - int oldfrags; - int oldmana1; - int oldmana2; - int oldusemana1; - int oldusemana2; - int olddrawbars; - int oldarmor; - int oldhealth; - int oldlife; - int oldpieces; - - enum - { - imgH2BAR, - imgH2TOP, - imgINVBAR, - imgLFEDGE, - imgRTEDGE, - imgSTATBAR, - imgKEYBAR, - imgSELECTBOX, - imgARTICLEAR, - imgARMCLEAR, - imgMANACLEAR, - imgMANAVIAL1, - imgMANAVIAL2, - imgMANAVIALDIM1, - imgMANAVIALDIM2, - imgMANADIM1, - imgMANADIM2, - imgMANABRIGHT1, - imgMANABRIGHT2, - imgINVLFGEM1, - imgINVLFGEM2, - imgINVRTGEM1, - imgINVRTGEM2, - imgKILLS, - imgUSEARTIA, - imgUSEARTIB, - imgUSEARTIC, - imgUSEARTID, - imgUSEARTIE, - imgKEYSLOT1, - imgKEYSLOT2, - imgKEYSLOT3, - imgKEYSLOT4, - imgKEYSLOT5, - imgKEYSLOT6, - imgKEYSLOT7, - imgKEYSLOT8, - imgKEYSLOT9, - imgKEYSLOTA, - imgKEYSLOTB, - imgARMSLOT1, - imgARMSLOT2, - imgARMSLOT3, - imgARMSLOT4, - imgARTIBOX, - imgAMMOBACK, - - NUM_HEXENSB_IMAGES - }; - - enum - { - imgWEAPONSLOT, - imgWEAPONFULL, - imgPIECE1, - imgPIECE2, - imgPIECE3, - imgCHAIN, - imgLIFEGEM, - - NUM_HEXENCLASSSB_IMAGES - }; - - FImageCollection Images; - FImageCollection ClassImages[3]; - - int HealthMarker; - char ArtifactFlash; - - char FourthWeaponClass; - char LifeBarClass; - - char ArtiRefresh; - char FragHealthRefresh; - char KeysRefresh; - char ArmorRefresh; - char HealthRefresh; - char Mana1Refresh; - char Mana2Refresh; - char AmmoRefresh; - - FManaBar ManaVial1Pic; - FManaBar ManaVial2Pic; -}; - -IMPLEMENT_POINTY_CLASS(DHexenStatusBar) - DECLARE_POINTER(oldarti) - DECLARE_POINTER(oldammo1) - DECLARE_POINTER(oldammo2) - DECLARE_POINTER(oldkeys[0]) - DECLARE_POINTER(oldkeys[1]) - DECLARE_POINTER(oldkeys[2]) - DECLARE_POINTER(oldkeys[3]) - DECLARE_POINTER(oldkeys[4]) -END_POINTERS - -DBaseStatusBar *CreateHexenStatusBar () -{ - return new DHexenStatusBar; -} diff --git a/src/g_level.cpp b/src/g_level.cpp index 1fd0130dc..8f475d3f3 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -419,15 +419,7 @@ void G_InitNew (const char *mapname, bool bTitleLevel) int cstype = SBarInfoScript[SCRIPT_CUSTOM]->GetGameType(); //Did the user specify a "base" - if(cstype == GAME_Heretic) - { - StatusBar = CreateHereticStatusBar(); - } - else if(cstype == GAME_Hexen) - { - StatusBar = CreateHexenStatusBar(); - } - else if(cstype == GAME_Strife) + if(cstype == GAME_Strife) { StatusBar = CreateStrifeStatusBar(); } @@ -442,18 +434,10 @@ void G_InitNew (const char *mapname, bool bTitleLevel) } if (StatusBar == NULL) { - if (gameinfo.gametype & GAME_DoomChex) + if (gameinfo.gametype & (GAME_DoomChex|GAME_Heretic|GAME_Hexen)) { StatusBar = CreateCustomStatusBar (SCRIPT_DEFAULT); } - else if (gameinfo.gametype == GAME_Heretic) - { - StatusBar = CreateHereticStatusBar (); - } - else if (gameinfo.gametype == GAME_Hexen) - { - StatusBar = CreateHexenStatusBar (); - } else if (gameinfo.gametype == GAME_Strife) { StatusBar = CreateStrifeStatusBar (); @@ -545,10 +529,8 @@ void G_InitNew (const char *mapname, bool bTitleLevel) static FString nextlevel; static int startpos; // [RH] Support for multiple starts per level extern int NoWipe; // [RH] Don't wipe when travelling in hubs -static bool startkeepfacing; // [RH] Support for keeping your facing angle -static bool resetinventory; // Reset the inventory to the player's default for the next level +static int changeflags; static bool unloading; -static bool g_nomonsters; //========================================================================== // @@ -559,8 +541,7 @@ static bool g_nomonsters; //========================================================================== -void G_ChangeLevel(const char *levelname, int position, bool keepFacing, int nextSkill, - bool nointermission, bool resetinv, bool nomonsters) +void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill) { level_info_t *nextinfo = NULL; @@ -589,25 +570,32 @@ void G_ChangeLevel(const char *levelname, int position, bool keepFacing, int nex if (nextSkill != -1) NextSkill = nextSkill; - g_nomonsters = nomonsters; - - if (nointermission) level.flags |= LEVEL_NOINTERMISSION; + if (flags & CHANGELEVEL_NOINTERMISSION) + { + level.flags |= LEVEL_NOINTERMISSION; + } cluster_info_t *thiscluster = FindClusterInfo (level.cluster); cluster_info_t *nextcluster = nextinfo? FindClusterInfo (nextinfo->cluster) : NULL; startpos = position; - startkeepfacing = keepFacing; gameaction = ga_completed; - resetinventory = resetinv; if (nextinfo != NULL) { if (thiscluster != nextcluster || (thiscluster && !(thiscluster->flags & CLUSTER_HUB))) { - resetinventory |= !!(nextinfo->flags2 & LEVEL2_RESETINVENTORY); + if (nextinfo->flags2 & LEVEL2_RESETINVENTORY) + { + flags |= CHANGELEVEL_RESETINVENTORY; + } + if (nextinfo->flags2 & LEVEL2_RESETHEALTH) + { + flags |= CHANGELEVEL_RESETHEALTH; + } } } + changeflags = flags; bglobal.End(); //Added by MC: @@ -683,12 +671,12 @@ const char *G_GetSecretExitMap() void G_ExitLevel (int position, bool keepFacing) { - G_ChangeLevel(G_GetExitMap(), position, keepFacing); + G_ChangeLevel(G_GetExitMap(), position, keepFacing ? CHANGELEVEL_KEEPFACING : 0); } void G_SecretExitLevel (int position) { - G_ChangeLevel(G_GetSecretExitMap(), position, false); + G_ChangeLevel(G_GetSecretExitMap(), position, 0); } //========================================================================== @@ -802,7 +790,7 @@ void G_DoCompleted (void) { if (playeringame[i]) { // take away appropriate inventory - G_PlayerFinishLevel (i, mode, resetinventory); + G_PlayerFinishLevel (i, mode, changeflags); } } @@ -943,7 +931,7 @@ void G_DoLoadLevel (int position, bool autosave) players[i].fragcount = 0; } - if (g_nomonsters) + if (changeflags & CHANGELEVEL_NOMONSTERS) { level.flags2 |= LEVEL2_NOMONSTERS; } @@ -1180,7 +1168,7 @@ void G_FinishTravel () // The player being spawned here is a short lived dummy and // must not start any ENTER script or big problems will happen. pawndup = P_SpawnPlayer (&playerstarts[pawn->player - players], true); - if (!startkeepfacing) + if (!changeflags & CHANGELEVEL_KEEPFACING) { pawn->angle = pawndup->angle; pawn->pitch = pawndup->pitch; @@ -1476,8 +1464,8 @@ void G_SerializeLevel (FArchive &arc, bool hubLoad) P_SerializeThinkers (arc, hubLoad); P_SerializeWorld (arc); P_SerializePolyobjs (arc); + P_SerializeSubsectors(arc); StatusBar->Serialize (arc); - //SerializeInterpolations (arc); arc << level.total_monsters << level.total_items << level.total_secrets; diff --git a/src/g_level.h b/src/g_level.h index b1575e33f..41d17939d 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -127,6 +127,7 @@ enum ELevelFlags LEVEL_SPECLOWERFLOOR = 0x00000100, LEVEL_SPECOPENDOOR = 0x00000200, + LEVEL_SPECLOWERFLOORTOHIGHEST= 0x00000300, LEVEL_SPECACTIONSMASK = 0x00000300, LEVEL_MONSTERSTELEFRAG = 0x00000400, @@ -198,6 +199,7 @@ enum ELevelFlags LEVEL2_SMOOTHLIGHTING = 0x01000000, // Level uses the smooth lighting feature. LEVEL2_POLYGRIND = 0x02000000, // Polyobjects grind corpses to gibs. LEVEL2_RESETINVENTORY = 0x04000000, // Resets player inventory when starting this level (unless in a hub) + LEVEL2_RESETHEALTH = 0x08000000, // Resets player health when starting this level (unless in a hub) }; @@ -236,6 +238,7 @@ struct FOptionalMapinfoDataPtr }; typedef TMap FOptData; +typedef TMap FMusicMap; struct level_info_t { @@ -295,6 +298,7 @@ struct level_info_t float teamdamage; FOptData optdata; + FMusicMap MusicMap; TArray specialactions; @@ -486,8 +490,17 @@ void G_SecretExitLevel (int position); const char *G_GetExitMap(); const char *G_GetSecretExitMap(); -void G_ChangeLevel(const char * levelname, int position, bool keepFacing, int nextSkill=-1, - bool nointermission=false, bool resetinventory=false, bool nomonsters=false); +enum +{ + CHANGELEVEL_KEEPFACING = 1, + CHANGELEVEL_RESETINVENTORY = 2, + CHANGELEVEL_NOMONSTERS = 4, + CHANGELEVEL_CHANGESKILL = 8, + CHANGELEVEL_NOINTERMISSION = 16, + CHANGELEVEL_RESETHEALTH = 32, +}; + +void G_ChangeLevel(const char *levelname, int position, int flags, int nextSkill=-1); void G_SetForEndGame (char *nextmap); @@ -533,9 +546,11 @@ enum ESkillProperty SKILLP_ACSReturn, SKILLP_MonsterHealth, SKILLP_FriendlyHealth, - SKILLP_NoPain + SKILLP_NoPain, + SKILLP_ArmorFactor }; int G_SkillProperty(ESkillProperty prop); +const char * G_SkillName(); typedef TMap SkillMenuNames; @@ -556,8 +571,8 @@ struct FSkillInfo int SpawnFilter; int ACSReturn; FString MenuName; + FString PicName; SkillMenuNames MenuNamesForPlayerClass; - bool MenuNameIsLump; bool MustConfirm; FString MustConfirmText; char Shortcut; @@ -567,6 +582,7 @@ struct FSkillInfo fixed_t MonsterHealth; fixed_t FriendlyHealth; bool NoPain; + fixed_t ArmorFactor; FSkillInfo() {} FSkillInfo(const FSkillInfo &other) diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index a0da36af2..8f4f4bb27 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -51,6 +51,8 @@ #include "doomstat.h" #include "d_player.h" #include "autosegs.h" +#include "version.h" +#include "v_text.h" int FindEndSequence (int type, const char *picname); @@ -1324,6 +1326,7 @@ MapFlagHandlers[] = { "specialaction_exitlevel", MITYPE_SCFLAGS, 0, ~LEVEL_SPECACTIONSMASK }, { "specialaction_opendoor", MITYPE_SCFLAGS, LEVEL_SPECOPENDOOR, ~LEVEL_SPECACTIONSMASK }, { "specialaction_lowerfloor", MITYPE_SCFLAGS, LEVEL_SPECLOWERFLOOR, ~LEVEL_SPECACTIONSMASK }, + { "specialaction_lowerfloortohighest",MITYPE_SCFLAGS,LEVEL_SPECLOWERFLOORTOHIGHEST, ~LEVEL_SPECACTIONSMASK }, { "specialaction_killmonsters", MITYPE_SETFLAG, LEVEL_SPECKILLMONSTERS, 0 }, { "lightning", MITYPE_SETFLAG, LEVEL_STARTLIGHTNING, 0 }, { "smoothlighting", MITYPE_SETFLAG2, LEVEL2_SMOOTHLIGHTING, 0 }, @@ -1372,6 +1375,7 @@ MapFlagHandlers[] = { "grinding_polyobj", MITYPE_SETFLAG2, LEVEL2_POLYGRIND, 0 }, { "no_grinding_polyobj", MITYPE_CLRFLAG2, LEVEL2_POLYGRIND, 0 }, { "resetinventory", MITYPE_SETFLAG2, LEVEL2_RESETINVENTORY, 0 }, + { "resethealth", MITYPE_SETFLAG2, LEVEL2_RESETHEALTH, 0 }, { "unfreezesingleplayerconversations",MITYPE_SETFLAG2, LEVEL2_CONV_SINGLE_UNFREEZE, 0 }, { "nobotnodes", MITYPE_IGNORE, 0, 0 }, // Skulltag option: nobotnodes { "compat_shorttex", MITYPE_COMPATFLAG, COMPATF_SHORTTEX}, @@ -1399,6 +1403,8 @@ MapFlagHandlers[] = { "compat_corpsegibs", MITYPE_COMPATFLAG, COMPATF_CORPSEGIBS}, { "compat_noblockfriends", MITYPE_COMPATFLAG, COMPATF_NOBLOCKFRIENDS}, { "compat_spritesort", MITYPE_COMPATFLAG, COMPATF_SPRITESORT}, + { "compat_light", MITYPE_COMPATFLAG, COMPATF_LIGHT}, + { "compat_polyobj", MITYPE_COMPATFLAG, COMPATF_POLYOBJ}, { "cd_start_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end1_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end2_track", MITYPE_EATNEXT, 0, 0 }, @@ -1799,6 +1805,7 @@ void FMapInfoParser::ParseEpisodeInfo () EpisodeMenu[i].fulltext = !picisgfx; EpisodeNoSkill[i] = noskill; strncpy (EpisodeMaps[i], map, 8); + EpisodeMaps[i][8] = 0; } } @@ -1861,6 +1868,15 @@ void FMapInfoParser::ParseMapInfo (int lump, level_info_t &gamedefaults, level_i { sc.ScriptError("include file '%s' not found", sc.String); } + if (Wads.GetLumpFile(sc.LumpNum) != Wads.GetLumpFile(inclump)) + { + // Do not allow overriding includes from the default MAPINFO + if (Wads.GetLumpFile(sc.LumpNum) == 0) + { + I_FatalError("File %s is overriding core lump %s.", + Wads.GetWadFullName(Wads.GetLumpFile(inclump)), sc.String); + } + } FScanner saved_sc = sc; ParseMapInfo(inclump, gamedefaults, defaultinfo); sc = saved_sc; @@ -1952,12 +1968,18 @@ void G_ParseMapInfo (const char *basemapinfo) atterm(ClearEpisodes); - // Parse the default MAPINFO for the current game. + // Parse the default MAPINFO for the current game. This lump *MUST* come from zdoom.pk3. if (basemapinfo != NULL) { FMapInfoParser parse; level_info_t defaultinfo; - parse.ParseMapInfo(Wads.GetNumForFullName(basemapinfo), gamedefaults, defaultinfo); + int baselump = Wads.GetNumForFullName(basemapinfo); + if (Wads.GetLumpFile(baselump) > 0) + { + I_FatalError("File %s is overriding core lump %s.", + Wads.GetWadFullName(Wads.GetLumpFile(baselump)), basemapinfo); + } + parse.ParseMapInfo(baselump, gamedefaults, defaultinfo); } static const char *mapinfonames[] = { "MAPINFO", "ZMAPINFO", NULL }; diff --git a/src/g_raven/a_minotaur.cpp b/src/g_raven/a_minotaur.cpp index f2bd43ebc..5b9fdb49d 100644 --- a/src/g_raven/a_minotaur.cpp +++ b/src/g_raven/a_minotaur.cpp @@ -200,7 +200,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurDecide) && pr_minotaurdecide() < 150) { // Charge attack // Don't call the state function right away - self->SetStateNF (self->FindState ("Charge")); + self->SetState (self->FindState ("Charge"), true); self->flags |= MF_SKULLFLY; if (!friendly) { // Heretic's Minotaur is invulnerable during charge attack @@ -473,9 +473,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurRoam) { // Turn if (pr_minotaurroam() & 1) - self->movedir = (++self->movedir)%8; + self->movedir = (self->movedir + 1) % 8; else - self->movedir = (self->movedir+7)%8; + self->movedir = (self->movedir + 7) % 8; FaceMovementDirection (self); } return 0; @@ -552,11 +552,11 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurLook) if (self->target) { - self->SetStateNF (self->SeeState); + self->SetState (self->SeeState, true); } else { - self->SetStateNF (self->FindState ("Roam")); + self->SetState (self->FindState ("Roam"), true); } return 0; } diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 6261591d9..ce47803a8 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -57,10 +57,8 @@ IMPLEMENT_CLASS (ASwitchingDecoration) // //---------------------------------------------------------------------------- -DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking) +void A_Unblock(AActor *self, bool drop) { - PARAM_ACTION_PROLOGUE; - // [RH] Andy Baker's stealth monsters if (self->flags & MF_STEALTH) { @@ -70,18 +68,18 @@ DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking) self->flags &= ~MF_SOLID; - // If the self has a conversation that sets an item to drop, drop that. + // If the actor has a conversation that sets an item to drop, drop that. if (self->Conversation != NULL && self->Conversation->DropType != NULL) { P_DropItem (self, self->Conversation->DropType, -1, 256); self->Conversation = NULL; - return 0; + return; } self->Conversation = NULL; - // If the self has attached metadata for items to drop, drop those. - if (!self->IsKindOf (RUNTIME_CLASS (APlayerPawn))) // [GRB] + // If the actor has attached metadata for items to drop, drop those. + if (drop && !self->IsKindOf (RUNTIME_CLASS (APlayerPawn))) // [GRB] { DDropItem *di = self->GetDropItems(); @@ -101,14 +99,19 @@ DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking) } } } +} + +DEFINE_ACTION_FUNCTION(AActor, A_NoBlocking) +{ + PARAM_ACTION_PROLOGUE; + A_Unblock(self, true); return 0; } DEFINE_ACTION_FUNCTION(AActor, A_Fall) { PARAM_ACTION_PROLOGUE; - - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, true); return 0; } @@ -332,7 +335,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_FreezeDeathChunks) { CALL_ACTION(A_BossDeath, self); } - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, true); self->SetState(self->FindState(NAME_Null)); return 0; diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index 4230efe7d..67d230e22 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -5,6 +5,7 @@ #include "r_data.h" #include "a_pickups.h" #include "templates.h" +#include "g_level.h" IMPLEMENT_CLASS (AArmor) @@ -84,6 +85,18 @@ bool ABasicArmor::HandlePickup (AInventory *item) // You shouldn't be picking up BasicArmor anyway. return true; } + if (item->IsKindOf(RUNTIME_CLASS(ABasicArmorBonus)) && !(item->ItemFlags & IF_IGNORESKILL)) + { + ABasicArmorBonus *armor = static_cast(item); + + armor->SaveAmount = FixedMul(armor->SaveAmount, G_SkillProperty(SKILLP_ArmorFactor)); + } + else if (item->IsKindOf(RUNTIME_CLASS(ABasicArmorPickup)) && !(item->ItemFlags & IF_IGNORESKILL)) + { + ABasicArmorPickup *armor = static_cast(item); + + armor->SaveAmount = FixedMul(armor->SaveAmount, G_SkillProperty(SKILLP_ArmorFactor)); + } if (Inventory != NULL) { return Inventory->HandlePickup (item); @@ -198,10 +211,17 @@ void ABasicArmorPickup::Serialize (FArchive &arc) AInventory *ABasicArmorPickup::CreateCopy (AActor *other) { ABasicArmorPickup *copy = static_cast (Super::CreateCopy (other)); + + if (!(ItemFlags & IF_IGNORESKILL)) + { + SaveAmount = FixedMul(SaveAmount, G_SkillProperty(SKILLP_ArmorFactor)); + } + copy->SavePercent = SavePercent; copy->SaveAmount = SaveAmount; copy->MaxAbsorb = MaxAbsorb; copy->MaxFullAbsorb = MaxFullAbsorb; + return copy; } @@ -272,6 +292,12 @@ void ABasicArmorBonus::Serialize (FArchive &arc) AInventory *ABasicArmorBonus::CreateCopy (AActor *other) { ABasicArmorBonus *copy = static_cast (Super::CreateCopy (other)); + + if (!(ItemFlags & IF_IGNORESKILL)) + { + SaveAmount = FixedMul(SaveAmount, G_SkillProperty(SKILLP_ArmorFactor)); + } + copy->SavePercent = SavePercent; copy->SaveAmount = SaveAmount; copy->MaxSaveAmount = MaxSaveAmount; @@ -279,6 +305,7 @@ AInventory *ABasicArmorBonus::CreateCopy (AActor *other) copy->BonusMax = BonusMax; copy->MaxAbsorb = MaxAbsorb; copy->MaxFullAbsorb = MaxFullAbsorb; + return copy; } diff --git a/src/g_shared/a_artifacts.cpp b/src/g_shared/a_artifacts.cpp index fb246c0b6..11f89ccf0 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -750,10 +750,6 @@ void APowerIronFeet::AbsorbDamage (int damage, FName damageType, int &newdamage) if (damageType == NAME_Drowning) { newdamage = 0; - if (Owner->player != NULL) - { - Owner->player->mo->ResetAirSupply (); - } } else if (Inventory != NULL) { @@ -761,6 +757,21 @@ void APowerIronFeet::AbsorbDamage (int damage, FName damageType, int &newdamage) } } +//=========================================================================== +// +// APowerIronFeet :: DoEffect +// +//=========================================================================== + +void APowerIronFeet::DoEffect () +{ + if (Owner->player != NULL) + { + Owner->player->mo->ResetAirSupply (); + } +} + + // Strife Environment Suit Powerup ------------------------------------------- IMPLEMENT_CLASS (APowerMask) @@ -993,6 +1004,12 @@ void APowerFlight::EndEffect () bool APowerFlight::DrawPowerup (int x, int y) { + // If this item got a valid icon use that instead of the default spinning wings. + if (Icon.isValid()) + { + return Super::DrawPowerup(x, y); + } + if (EffectTics > BLINKTHRESHOLD || !(EffectTics & 16)) { FTextureID picnum = TexMan.CheckForTexture ("SPFLY0", FTexture::TEX_MiscPatch); diff --git a/src/g_shared/a_artifacts.h b/src/g_shared/a_artifacts.h index 6d1bdd31f..1b77278cb 100644 --- a/src/g_shared/a_artifacts.h +++ b/src/g_shared/a_artifacts.h @@ -86,6 +86,7 @@ class APowerIronFeet : public APowerup DECLARE_CLASS (APowerIronFeet, APowerup) public: void AbsorbDamage (int damage, FName damageType, int &newdamage); + void DoEffect (); }; class APowerMask : public APowerIronFeet diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index e319ca1b3..f21cfb275 100644 --- a/src/g_shared/a_fastprojectile.cpp +++ b/src/g_shared/a_fastprojectile.cpp @@ -72,7 +72,7 @@ void AFastProjectile::Tick () tm.LastRipped = NULL; // [RH] Do rip damage each step, like Hexen } - if (!P_TryMove (this, x + xfrac,y + yfrac, true, false, tm)) + if (!P_TryMove (this, x + xfrac,y + yfrac, true, NULL, tm)) { // Blocked move if (!(flags3 & MF3_SKYEXPLODE)) { @@ -158,10 +158,13 @@ void AFastProjectile::Effect() if (name != NAME_None) { fixed_t hitz = z-8*FRACUNIT; + if (hitz < floorz) { hitz = floorz; } + // Do not clip this offset to the floor. + hitz += GetClass()->MissileHeight; PClassActor *trail = PClass::FindActor(name); if (trail != NULL) diff --git a/src/g_shared/a_keys.cpp b/src/g_shared/a_keys.cpp index 2a2bdca06..a1098f9b1 100644 --- a/src/g_shared/a_keys.cpp +++ b/src/g_shared/a_keys.cpp @@ -19,7 +19,12 @@ struct OneKey bool check(AActor *owner) { - return !!owner->FindInventory(key); + // P_GetMapColorForKey() checks the key directly + if (owner->IsKindOf (RUNTIME_CLASS(AKey))) + return owner->IsA(key); + // Other calls check an actor that may have a key in its inventory. + else + return !!owner->FindInventory(key); } }; diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index 0cdd48768..4658bb757 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -99,15 +99,7 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp p->morphTics = (duration) ? duration : MORPHTICS; // [MH] Used by SBARINFO to speed up face drawing - p->MorphedPlayerClass = 0; - for (unsigned int i = 1; i < PlayerClasses.Size(); i++) - { - if (PlayerClasses[i].Type == spawntype) - { - p->MorphedPlayerClass = i; - break; - } - } + p->MorphedPlayerClass = spawntype; p->MorphStyle = style; p->MorphExitFlash = (exit_flash) ? exit_flash : RUNTIME_CLASS(ATeleportFog); @@ -149,19 +141,6 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp p->camera = morphed; } morphed->ScoreIcon = actor->ScoreIcon; // [GRB] - - // [MH] - // If the player that was morphed is the one - // taking events, set up the face, if any; - // this is only needed for old-skool skins - // and for the original DOOM status bar. - if (p == &players[consoleplayer]) - { - if (spawntype->Face.IsNotEmpty() && strcmp(spawntype->Face, "None") != 0) - { - StatusBar->SetFace(&skins[p->MorphedPlayerClass]); - } - } return true; } @@ -307,7 +286,6 @@ bool P_UndoPlayerMorph (player_t *activator, player_t *player, int unmorphflag, } } } - StatusBar->SetFace(&skins[skinindex]); } } diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index e4842b3ad..57d955648 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -401,6 +401,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialPosition) self->z += FloatBobOffsets[(self->FloatBobPhase + level.maptime) & 63]; } } + self->SetOrigin (self->x, self->y, self->z); return 0; } @@ -935,7 +936,7 @@ void AInventory::Touch (AActor *toucher) { const char * message = PickupMessage (); - if (toucher->CheckLocalView (consoleplayer) + if (message != NULL && *message != 0 && toucher->CheckLocalView (consoleplayer) && (StaticLastMessageTic != gametic || StaticLastMessage != message)) { StaticLastMessageTic = gametic; diff --git a/src/g_shared/a_pickups.h b/src/g_shared/a_pickups.h index 06f21c8e9..bd02ec35f 100644 --- a/src/g_shared/a_pickups.h +++ b/src/g_shared/a_pickups.h @@ -23,7 +23,7 @@ public: bool AddWeapon (const char *type); bool AddWeapon (PClassWeapon *type); void AddWeaponList (const char *list, bool clear); - AWeapon *PickWeapon (player_t *player); + AWeapon *PickWeapon (player_t *player, bool checkammo = false); int Size () const { return (int)Weapons.Size(); } int LocateWeapon (PClassWeapon *type); diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp index 60c8372f9..5e37f1c83 100644 --- a/src/g_shared/a_randomspawner.cpp +++ b/src/g_shared/a_randomspawner.cpp @@ -124,6 +124,7 @@ class ARandomSpawner : public AActor { // copy everything relevant newmobj->SpawnAngle = newmobj->angle = angle; + newmobj->SpawnPoint[2] = SpawnPoint[2]; newmobj->special = special; newmobj->args[0] = args[0]; newmobj->args[1] = args[1]; @@ -147,10 +148,10 @@ class ARandomSpawner : public AActor // a health set to -2 after spawning, for internal reasons. if (health != SpawnHealth()) newmobj->health = health; if (!(flags & MF_DROPPED)) newmobj->flags &= ~MF_DROPPED; - // Handle special altitude flags + // Handle special altitude flags if (newmobj->flags & MF_SPAWNCEILING) { - newmobj->z = newmobj->ceilingz - newmobj->height; + newmobj->z = newmobj->ceilingz - newmobj->height - SpawnPoint[2]; } else if (newmobj->flags2 & MF2_SPAWNFLOAT) { @@ -160,6 +161,7 @@ class ARandomSpawner : public AActor space -= 40*FRACUNIT; newmobj->z = MulScale8 (space, pr_randomspawn()) + newmobj->floorz + 40*FRACUNIT; } + newmobj->z += SpawnPoint[2]; } if (newmobj->flags & MF_MISSILE) P_CheckMissileSpawn(newmobj); diff --git a/src/g_shared/a_specialspot.cpp b/src/g_shared/a_specialspot.cpp index 2e9e5d849..a8623a20a 100644 --- a/src/g_shared/a_specialspot.cpp +++ b/src/g_shared/a_specialspot.cpp @@ -147,14 +147,20 @@ struct FSpotList // //---------------------------------------------------------------------------- - ASpecialSpot *GetSpotWithMinDistance(fixed_t x, fixed_t y, fixed_t distance) + ASpecialSpot *GetSpotWithMinMaxDistance(fixed_t x, fixed_t y, fixed_t mindist, fixed_t maxdist) { if (Spots.Size() == 0) return NULL; int i = pr_spot() % Spots.Size(); int initial = i; - while (P_AproxDistance(Spots[i]->x - x, Spots[i]->y - y) < distance) + fixed_t distance; + + while (true) { + distance = P_AproxDistance(Spots[i]->x - x, Spots[i]->y - y); + + if ((distance >= mindist) && ((maxdist == 0) || (distance <= maxdist))) break; + i = (i+1) % Spots.Size(); if (i == initial) return NULL; } @@ -329,10 +335,10 @@ ASpecialSpot *DSpotState::GetNextInList(const PClass *type, int skipcounter) // //---------------------------------------------------------------------------- -ASpecialSpot *DSpotState::GetSpotWithMinDistance(const PClass *type, fixed_t x, fixed_t y, fixed_t distance) +ASpecialSpot *DSpotState::GetSpotWithMinMaxDistance(const PClass *type, fixed_t x, fixed_t y, fixed_t mindist, fixed_t maxdist) { FSpotList *list = FindSpotList(type); - if (list != NULL) return list->GetSpotWithMinDistance(x, y, distance); + if (list != NULL) return list->GetSpotWithMinMaxDistance(x, y, mindist, maxdist); return NULL; } diff --git a/src/g_shared/a_specialspot.h b/src/g_shared/a_specialspot.h index ffc083978..21d90f73f 100644 --- a/src/g_shared/a_specialspot.h +++ b/src/g_shared/a_specialspot.h @@ -36,7 +36,7 @@ public: bool RemoveSpot(ASpecialSpot *spot); void Serialize(FArchive &arc); ASpecialSpot *GetNextInList(const PClass *type, int skipcounter); - ASpecialSpot *GetSpotWithMinDistance(const PClass *type, fixed_t x, fixed_t y, fixed_t distance); + ASpecialSpot *GetSpotWithMinMaxDistance(const PClass *type, fixed_t x, fixed_t y, fixed_t mindist, fixed_t maxdist); ASpecialSpot *GetRandomSpot(const PClass *type, bool onlyonce = false); }; diff --git a/src/g_shared/a_weapons.cpp b/src/g_shared/a_weapons.cpp index 326b82323..409691926 100644 --- a/src/g_shared/a_weapons.cpp +++ b/src/g_shared/a_weapons.cpp @@ -771,7 +771,7 @@ int FWeaponSlot::LocateWeapon(PClassWeapon *type) // //=========================================================================== -AWeapon *FWeaponSlot::PickWeapon(player_t *player) +AWeapon *FWeaponSlot::PickWeapon(player_t *player, bool checkammo) { int i, j; @@ -795,9 +795,12 @@ AWeapon *FWeaponSlot::PickWeapon(player_t *player) { AWeapon *weap = static_cast (player->mo->FindInventory(Weapons[j].Type)); - if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon)) && weap->CheckAmmo(AWeapon::EitherFire, false)) + if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon))) { - return weap; + if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false)) + { + return weap; + } } } } @@ -807,9 +810,12 @@ AWeapon *FWeaponSlot::PickWeapon(player_t *player) { AWeapon *weap = static_cast (player->mo->FindInventory(Weapons[i].Type)); - if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon)) && weap->CheckAmmo(AWeapon::EitherFire, false)) + if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon))) { - return weap; + if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false)) + { + return weap; + } } } return player->ReadyWeapon; diff --git a/src/g_shared/sbar.h b/src/g_shared/sbar.h index d3e72c79e..295e0e4e1 100644 --- a/src/g_shared/sbar.h +++ b/src/g_shared/sbar.h @@ -165,7 +165,7 @@ struct FMugShotFrame FMugShotFrame(); ~FMugShotFrame(); - FTexture *GetTexture(const char *default_face, FPlayerSkin *skin, int random, int level=0, + FTexture *GetTexture(const char *default_face, const char *skin_face, int random, int level=0, int direction=0, bool usesLevels=false, bool health2=false, bool healthspecial=false, bool directional=false); }; @@ -189,9 +189,9 @@ struct FMugShotState void Tick(); void Reset(); FMugShotFrame &GetCurrentFrame() { return Frames[Position]; } - FTexture *GetCurrentFrameTexture(const char *default_face, FPlayerSkin *skin, int level=0, int direction=0) + FTexture *GetCurrentFrameTexture(const char *default_face, const char *skin_face, int level=0, int direction=0) { - return GetCurrentFrame().GetTexture(default_face, skin, Random, level, direction, bUsesLevels, bHealth2, bHealthSpecial, bDirectional); + return GetCurrentFrame().GetTexture(default_face, skin_face, Random, level, direction, bUsesLevels, bHealth2, bHealthSpecial, bDirectional); } private: FMugShotState(); @@ -215,6 +215,7 @@ class FMugShot FMugShot(); void Grin(bool grin=true) { bEvilGrin = grin; } + void Reset(); void Tick(player_t *player); bool SetState(const char *state_name, bool wait_till_done=false, bool reset=false); int UpdateState(player_t *player, StateFlags stateflags=STANDARD); @@ -275,7 +276,7 @@ public: ST_DEADFACE = ST_GODFACE + 1 }; - DBaseStatusBar (int reltop); + DBaseStatusBar (int reltop, int hres=320, int vres=200); void Destroy (); void SetScaled (bool scale, bool force=false); @@ -300,8 +301,6 @@ public: virtual void AttachToPlayer (player_t *player); virtual void FlashCrosshair (); virtual void BlendView (float blend[4]); - virtual void SetFace (void *skn); // Takes a FPlayerSkin as input - virtual void AddFaceToImageCollection (void *skn, FImageCollection *images); // Takes a FPlayerSkin as input virtual void NewGame (); virtual void ScreenSizeChanged (); virtual void MultiplayerChanged (); @@ -334,14 +333,13 @@ protected: void GetCurrentAmmo (AAmmo *&ammo1, AAmmo *&ammo2, int &ammocount1, int &ammocount2) const; - void AddFaceToImageCollectionActual (void *skn, FImageCollection *images, bool isDoom); - public: AInventory *ValidateInvFirst (int numVisible) const; void DrawCrosshair (); int ST_X, ST_Y; int RelTop; + int HorizontalResolution, VirticalResolution; bool Scaled; bool Centering; bool FixedOrigin; @@ -378,8 +376,6 @@ extern DBaseStatusBar *StatusBar; // Status bar factories ----------------------------------------------------- -DBaseStatusBar *CreateHereticStatusBar(); -DBaseStatusBar *CreateHexenStatusBar(); DBaseStatusBar *CreateStrifeStatusBar(); DBaseStatusBar *CreateCustomStatusBar(int script=0); diff --git a/src/g_shared/sbar_mugshot.cpp b/src/g_shared/sbar_mugshot.cpp index a41476576..4b43bfe36 100644 --- a/src/g_shared/sbar_mugshot.cpp +++ b/src/g_shared/sbar_mugshot.cpp @@ -76,7 +76,7 @@ FMugShotFrame::~FMugShotFrame() // //=========================================================================== -FTexture *FMugShotFrame::GetTexture(const char *default_face, FPlayerSkin *skin, int random, int level, +FTexture *FMugShotFrame::GetTexture(const char *default_face, const char *skin_face, int random, int level, int direction, bool uses_levels, bool health2, bool healthspecial, bool directional) { int index = !directional ? random % Graphic.Size() : direction; @@ -84,7 +84,7 @@ FTexture *FMugShotFrame::GetTexture(const char *default_face, FPlayerSkin *skin, { index = Graphic.Size() - 1; } - FString sprite(skin->face[0] != 0 ? skin->face : default_face, 3); + FString sprite(skin_face != NULL && skin_face[0] != 0 ? skin_face : default_face, 3); sprite += Graphic[index]; if (uses_levels) //change the last character to the level { @@ -221,6 +221,17 @@ int FindMugShotStateIndex(FName state) //=========================================================================== FMugShot::FMugShot() +{ + Reset(); +} + +//=========================================================================== +// +// FMugShot :: Reset +// +//=========================================================================== + +void FMugShot::Reset() { FaceHealth = -1; bEvilGrin = false; @@ -492,8 +503,8 @@ FTexture *FMugShot::GetFace(player_t *player, const char *default_face, int accu } if (CurrentState != NULL) { - FPlayerSkin *skin = &skins[player->morphTics ? player->MorphedPlayerClass : player->userinfo.skin]; - return CurrentState->GetCurrentFrameTexture(default_face, skin, level, angle); + const char *skin_face = player->morphTics ? player->MorphedPlayerClass->Face : skins[player->userinfo.skin].face; + return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle); } return NULL; } diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index e020d4258..eed9d86c7 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -57,16 +57,8 @@ #include "g_level.h" #include "v_palette.h" #include "p_acs.h" - -#define ADJUST_RELCENTER(x, y, outX, outY) \ - if(x.RelCenter()) \ - outX = *x + SCREENWIDTH/(hud_scale ? CleanXfac*2 : 2); \ - else \ - outX = *x; \ - if(y.RelCenter()) \ - outY = *y + SCREENHEIGHT/(hud_scale ? CleanYfac*2 : 2); \ - else \ - outY = *y; +#include "gstrings.h" +#include "version.h" #define ARTIFLASH_OFFSET (statusBar->invBarOffset+6) enum @@ -189,8 +181,8 @@ class SBarInfoCommand } } - if(!fullScreenOffsets) - y.SetCoord((negative ? -sc.Number : sc.Number) - (200 - script->height)); + //if(!fullScreenOffsets) + // y.SetCoord((negative ? -sc.Number : sc.Number) - (200 - script->height)); } EColorRange GetTranslation(FScanner &sc) { @@ -213,60 +205,107 @@ class SBarInfoCommand class SBarInfoCommandFlowControl : public SBarInfoCommand { public: - SBarInfoCommandFlowControl(SBarInfo *script) : SBarInfoCommand(script) {} + SBarInfoCommandFlowControl(SBarInfo *script) : SBarInfoCommand(script), truth(false) {} ~SBarInfoCommandFlowControl() { - for(unsigned int i = 0;i < commands.Size();i++) - delete commands[i]; + for(unsigned int i = 0;i < 2;i++) + { + for(unsigned int j = 0;j < commands[i].Size();j++) + delete commands[i][j]; + } } void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) { - for(unsigned int i = 0;i < commands.Size();i++) - commands[i]->Draw(block, statusBar); + for(unsigned int i = 0;i < commands[truth].Size();i++) + commands[truth][i]->Draw(block, statusBar); } - int NumCommands() const { return commands.Size(); } + int NumCommands() const { return commands[truth].Size(); } void Parse(FScanner &sc, bool fullScreenOffsets) { - sc.MustGetToken('{'); + bool elseBlock = false; SBarInfoCommand *cmd = NULL; - while((cmd = NextCommand(sc)) != NULL) + // Should loop no more than twice. + while(true) { - cmd->Parse(sc, fullScreenOffsets); - commands.Push(cmd); + if(sc.CheckToken('{')) + { + while((cmd = NextCommand(sc)) != NULL) + { + cmd->Parse(sc, fullScreenOffsets); + commands[!elseBlock].Push(cmd); + } + } + else + { + if((cmd = NextCommand(sc)) != NULL) + { + cmd->Parse(sc, fullScreenOffsets); + commands[!elseBlock].Push(cmd); + } + else + sc.ScriptError("Missing command for flow control statement."); + } + + if(!elseBlock && sc.CheckToken(TK_Else)) + { + elseBlock = true; + continue; + } + break; } } void Reset() { - for(unsigned int i = 0;i < commands.Size();i++) - commands[i]->Reset(); + for(unsigned int i = 0;i < 2;i++) + { + for(unsigned int j = 0;j < commands[i].Size();j++) + commands[i][j]->Reset(); + } } void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { - for(unsigned int i = 0;i < commands.Size();i++) - commands[i]->Tick(block, statusBar, hudChanged); + for(unsigned int i = 0;i < commands[truth].Size();i++) + commands[truth][i]->Tick(block, statusBar, hudChanged); + } + + protected: + void SetTruth(bool truth, const SBarInfoMainBlock *block, const DSBarInfo *statusBar) + { + // If there is no change we don't need to do anything. Do note + // that this should not change more than once per tick. If it does + // there may be cosmetic problems. + if(this->truth == truth) + return; + + this->truth = truth; + if(block != NULL) + Tick(block, statusBar, true); } private: SBarInfoCommand *NextCommand(FScanner &sc); - TArray commands; + bool truth; + TArray commands[2]; }; class SBarInfoMainBlock : public SBarInfoCommandFlowControl { public: SBarInfoMainBlock(SBarInfo *script) : SBarInfoCommandFlowControl(script), - alpha(FRACUNIT), forceScaled(false), fullScreenOffsets(false) + alpha(FRACUNIT), currentAlpha(FRACUNIT), forceScaled(false), + fullScreenOffsets(false) { + SetTruth(true, NULL, NULL); } - int Alpha() const { return alpha; } + int Alpha() const { return currentAlpha; } void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, int xOffset, int yOffset, int alpha) { this->xOffset = xOffset; this->yOffset = yOffset; - this->alpha = alpha; + this->currentAlpha = fixed_t((((double) this->alpha / (double) FRACUNIT) * ((double) alpha / (double) FRACUNIT)) * FRACUNIT); SBarInfoCommandFlowControl::Draw(this, statusBar); } bool ForceScaled() const { return forceScaled; } @@ -299,8 +338,9 @@ class SBarInfoMainBlock : public SBarInfoCommandFlowControl int XOffset() const { return xOffset; } int YOffset() const { return yOffset; } - private: + protected: int alpha; + int currentAlpha; bool forceScaled; bool fullScreenOffsets; int xOffset; @@ -320,6 +360,7 @@ enum //Key words SBARINFO_COMPLETEBORDER, SBARINFO_MONOSPACEFONTS, SBARINFO_LOWERHEALTHCAP, + SBARINFO_RESOLUTION, SBARINFO_STATUSBAR, SBARINFO_MUGSHOT, SBARINFO_CREATEPOPUP, @@ -347,6 +388,7 @@ static const char *SBarInfoTopLevel[] = "completeborder", "monospacefonts", "lowerhealthcap", + "resolution", "statusbar", "mugshot", "createpopup", @@ -428,6 +470,7 @@ void SBarInfo::ParseSBarInfo(int lump) ParseSBarInfo(lump); continue; } + int baselump = -2; switch(sc.MustMatchString(SBarInfoTopLevel)) { case SBARINFO_BASE: @@ -436,21 +479,36 @@ void SBarInfo::ParseSBarInfo(int lump) sc.MustGetToken(TK_Identifier); if(sc.Compare("Doom")) { - int lump = Wads.CheckNumForFullName("sbarinfo/doom.txt", true); - if(lump == -1) - sc.ScriptError("Standard Doom Status Bar not found."); - ParseSBarInfo(lump); + baselump = Wads.CheckNumForFullName("sbarinfo/doom.txt", true); } else if(sc.Compare("Heretic")) - gameType = GAME_Heretic; + { + baselump = Wads.CheckNumForFullName("sbarinfo/heretic.txt", true); + } else if(sc.Compare("Hexen")) - gameType = GAME_Hexen; + { + baselump = Wads.CheckNumForFullName("sbarinfo/hexen.txt", true); + } else if(sc.Compare("Strife")) gameType = GAME_Strife; else if(sc.Compare("None")) gameType = GAME_Any; else sc.ScriptError("Bad game name: %s", sc.String); + // If one of the standard status bar should be loaded, baselump has been set to a different value. + if (baselump != -2) + { + if(baselump == -1) + { + sc.ScriptError("Standard %s status bar not found.", sc.String); + } + else if (Wads.GetLumpFile(baselump) > 0) + { + I_FatalError("File %s is overriding core lump sbarinfo/%s.txt.", + Wads.GetWadFullName(Wads.GetLumpFile(baselump)), sc.String); + } + ParseSBarInfo(baselump); + } sc.MustGetToken(';'); break; case SBARINFO_HEIGHT: @@ -518,6 +576,19 @@ void SBarInfo::ParseSBarInfo(int lump) sc.MustGetToken(','); sc.MustGetToken(TK_StringConst); //Don't tell anyone we're just ignoring this ;) } + if(sc.CheckToken(',')) + { + // Character alignment + sc.MustGetToken(TK_Identifier); + if(sc.Compare("left")) + spacingAlignment = ALIGN_LEFT; + else if(sc.Compare("center")) + spacingAlignment = ALIGN_CENTER; + else if(sc.Compare("right")) + spacingAlignment = ALIGN_RIGHT; + else + sc.ScriptError("Unknown alignment '%s'.", sc.String); + } sc.MustGetToken(';'); break; case SBARINFO_LOWERHEALTHCAP: @@ -532,6 +603,14 @@ void SBarInfo::ParseSBarInfo(int lump) } sc.MustGetToken(';'); break; + case SBARINFO_RESOLUTION: + sc.MustGetToken(TK_IntConst); + resW = sc.Number; + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + resH = sc.Number; + sc.MustGetToken(';'); + break; case SBARINFO_STATUSBAR: { if(!baseSet) //If the user didn't explicitly define a base, do so now. @@ -615,6 +694,13 @@ void SBarInfo::ParseSBarInfo(int lump) sc.MustGetToken(TK_IntConst); popup.speed = sc.Number; } + else if(sc.Compare("pushup")) + { + popup.transition = Popup::TRANSITION_PUSHUP; + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + popup.speed = sc.Number; + } else if(sc.Compare("fade")) { popup.transition = Popup::TRANSITION_FADE; @@ -707,6 +793,9 @@ void SBarInfo::Init() armorInterpolationSpeed = 8; height = 0; spacingCharacter = '\0'; + spacingAlignment = ALIGN_CENTER; + resW = 320; + resH = 200; for(unsigned int i = 0;i < NUMHUDS;i++) huds[i] = new SBarInfoMainBlock(this); @@ -722,78 +811,79 @@ SBarInfo::~SBarInfo() } //Popup -Popup::Popup() +Popup::Popup() : transition(TRANSITION_NONE), opened(false), moving(false), + height(320), width(200), speed(0), speed2(0), alpha(FRACUNIT), x(320), + y(200), displacementX(0), displacementY(0) { - transition = TRANSITION_NONE; - height = 320; - width = 200; - speed = 0; - x = 320; - y = 200; - alpha = FRACUNIT; - opened = false; - moving = false; } void Popup::init() { x = width; y = height; - if(transition == TRANSITION_SLIDEINBOTTOM) + switch(transition) { - x = 0; - } - else if(transition == TRANSITION_FADE) - { - alpha = 0; - x = 0; - y = 0; + case TRANSITION_SLIDEINBOTTOM: + case TRANSITION_PUSHUP: + x = 0; + break; + case TRANSITION_FADE: + alpha = 0; + x = 0; + y = 0; + break; + default: + break; } } void Popup::tick() { - if(transition == TRANSITION_SLIDEINBOTTOM) + switch(transition) { - if(moving) - { - if(opened) - y -= clamp(height + (y - height), 1, speed); + case TRANSITION_SLIDEINBOTTOM: + case TRANSITION_PUSHUP: + if(moving) + { + int oldY = y; + if(opened) + y -= clamp(height + (y - height), 1, speed); + else + y += clamp(height - y, 1, speed); + if(transition == TRANSITION_PUSHUP) + displacementY += y - oldY; + } + if(y != 0 && y != height) + moving = true; else - y += clamp(height - y, 1, speed); - } - if(y != 0 && y != height) - moving = true; - else - moving = false; - } - else if(transition == TRANSITION_FADE) - { - if(moving) - { - if(opened) - alpha = clamp(alpha + speed, 0, FRACUNIT); + moving = false; + break; + case TRANSITION_FADE: + if(moving) + { + if(opened) + alpha = clamp(alpha + speed, 0, FRACUNIT); + else + alpha = clamp(alpha - speed2, 0, FRACUNIT); + } + if(alpha == 0 || alpha == FRACUNIT) + moving = false; else - alpha = clamp(alpha - speed2, 0, FRACUNIT); - } - if(alpha == 0 || alpha == FRACUNIT) + moving = true; + break; + default: + if(opened) + { + y = 0; + x = 0; + } + else + { + y = height; + x = width; + } moving = false; - else - moving = true; - } - else - { - if(opened) - { - y = 0; - x = 0; - } - else - { - y = height; - x = width; - } - moving = false; + break; } } @@ -819,6 +909,16 @@ int Popup::getAlpha(int maxAlpha) return fixed_t((a * b) * FRACUNIT); } +int Popup::getXDisplacement() +{ + return displacementX; +} + +int Popup::getYDisplacement() +{ + return displacementY; +} + void Popup::open() { opened = true; @@ -833,11 +933,23 @@ void Popup::close() //////////////////////////////////////////////////////////////////////////////// +inline void adjustRelCenter(bool relX, bool relY, const double &x, const double &y, double &outX, double &outY, const double &xScale, const double &yScale) +{ + if(relX) + outX = x + (SCREENWIDTH/(hud_scale ? xScale*2 : 2)); + else + outX = x; + if(relY) + outY = y + (SCREENHEIGHT/(hud_scale ? yScale*2 : 2)); + else + outY = y; +} + class DSBarInfo : public DBaseStatusBar { DECLARE_CLASS(DSBarInfo, DBaseStatusBar) public: - DSBarInfo (SBarInfo *script=NULL) : DBaseStatusBar(script->height), + DSBarInfo (SBarInfo *script=NULL) : DBaseStatusBar(script->height, script->resW, script->resH), ammo1(NULL), ammo2(NULL), ammocount1(0), ammocount2(0), armor(NULL), pendingPopup(POP_None), currentPopup(POP_None), lastHud(-1), lastInventoryBar(NULL), lastPopup(NULL) @@ -861,10 +973,6 @@ public: { patchnames[i+script->Images.Size()] = InventoryBarLumps[i]; } - for (i = 0;i < numskins;i++) - { - AddFaceToImageCollection (&skins[i], &Images); - } invBarOffset = script->Images.Size(); Images.Init(&patchnames[0], patchnames.Size()); } @@ -918,8 +1026,13 @@ public: armor = CPlayer->mo->FindInventory(); if(hud != lastHud) script->huds[hud]->Tick(NULL, this, true); - script->huds[hud]->Draw(NULL, this, 0, 0, FRACUNIT); + + if(currentPopup != POP_None && !script->huds[hud]->FullScreenOffsets()) + script->huds[hud]->Draw(NULL, this, script->popups[currentPopup-1].getXDisplacement(), script->popups[currentPopup-1].getYDisplacement(), FRACUNIT); + else + script->huds[hud]->Draw(NULL, this, 0, 0, FRACUNIT); lastHud = hud; + if(CPlayer->inventorytics > 0 && !(level.flags & LEVEL_NOINVENTORYBAR) && (state == HUD_StatusBar || state == HUD_Fullscreen)) { SBarInfoMainBlock *inventoryBar = state == HUD_StatusBar ? script->huds[STBAR_INVENTORY] : script->huds[STBAR_INVENTORYFULLSCREEN]; @@ -1030,53 +1143,58 @@ public: if (texture == NULL) return; + double dx = *x; + double dy = *y; + if((offsetflags & SBarInfoCommand::CENTER) == SBarInfoCommand::CENTER) { - x -= (texture->GetScaledWidth()/2)-texture->LeftOffset; - y -= (texture->GetScaledHeight()/2)-texture->TopOffset; + dx -= (texture->GetScaledWidthDouble()/2.0)-texture->LeftOffset; + dy -= (texture->GetScaledHeightDouble()/2.0)-texture->TopOffset; } - x += xOffset; - y += yOffset; - int w, h; + dx += xOffset; + dy += yOffset; + double w, h; if(!fullScreenOffsets) { - fixed_t tmp = 0; - // I'll handle the conversion from fixed to int myself for more control - fixed_t fx = (x + ST_X).Coordinate() << FRACBITS; - fixed_t fy = (y + ST_Y).Coordinate() << FRACBITS; - fixed_t fw = (forceWidth <= -1 ? texture->GetScaledWidth() : forceWidth) << FRACBITS; - fixed_t fh = (forceHeight <= -1 ? texture->GetScaledHeight() : forceHeight) << FRACBITS; - fixed_t fcx = cx == 0 ? 0 : fx + cx - (texture->GetScaledLeftOffset() << FRACBITS); - fixed_t fcy = cy == 0 ? 0 : fy + cy - (texture->GetScaledTopOffset() << FRACBITS); - fixed_t fcr = fx + fw - cr; - fixed_t fcb = fy + fh - cb; + double tmp = 0; + dx += ST_X; + dy += ST_Y - (Scaled ? script->resH : 200) + script->height; + w = forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth; + h = forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight; + double dcx = cx == 0 ? 0 : dx + ((double) cx / FRACUNIT) - texture->GetScaledLeftOffsetDouble(); + double dcy = cy == 0 ? 0 : dy + ((double) cy / FRACUNIT) - texture->GetScaledTopOffsetDouble(); + double dcr = cr == 0 ? INT_MAX : dx + w - ((double) cr / FRACUNIT); + double dcb = cb == 0 ? INT_MAX : dy + h - ((double) cb / FRACUNIT); + if(Scaled) { if(cx != 0 || cy != 0) - screen->VirtualToRealCoordsFixed(fcx, fcy, tmp, tmp, 320, 200, true); + screen->VirtualToRealCoords(dcx, dcy, tmp, tmp, script->resW, script->resH, true); if(cr != 0 || cb != 0 || clearDontDraw) - screen->VirtualToRealCoordsFixed(fcr, fcb, tmp, tmp, 320, 200, true); - screen->VirtualToRealCoordsFixed(fx, fy, fw, fh, 320, 200, true); + screen->VirtualToRealCoords(dcr, dcb, tmp, tmp, script->resW, script->resH, true); + screen->VirtualToRealCoords(dx, dy, w, h, script->resW, script->resH, true); } - // Round to nearest - w = (fw + (FRACUNIT>>1)) >> FRACBITS; - h = (fh + (FRACUNIT>>1)) >> FRACBITS; - cr = cr != 0 ? fcr >> FRACBITS : INT_MAX; - cb = cb != 0 ? fcb >> FRACBITS : INT_MAX; + else + { + dy += 200 - script->resH; + dcy += 200 - script->resH; + dcb += 200 - script->resH; + } + if(clearDontDraw) - screen->Clear(MAX(fx, fcx)>>FRACBITS, MAX(fy, fcy)>>FRACBITS, fcr>>FRACBITS, fcb>>FRACBITS, GPalette.BlackIndex, 0); + screen->Clear(static_cast(MAX(dx, dcx)), static_cast(MAX(dy, dcy)), static_cast(dcr), static_cast(dcb), GPalette.BlackIndex, 0); else { if(alphaMap) { - screen->DrawTexture(texture, (fx >> FRACBITS), (fy >> FRACBITS), - DTA_DestWidth, w, - DTA_DestHeight, h, - DTA_ClipLeft, fcx>>FRACBITS, - DTA_ClipTop, fcy>>FRACBITS, - DTA_ClipRight, cr, - DTA_ClipBottom, cb, + screen->DrawTexture(texture, dx, dy, + DTA_DestWidthF, w, + DTA_DestHeightF, h, + DTA_ClipLeft, static_cast(dcx), + DTA_ClipTop, static_cast(dcy), + DTA_ClipRight, static_cast(MIN(INT_MAX, dcr)), + DTA_ClipBottom, static_cast(MIN(INT_MAX, dcb)), DTA_Translation, translate ? GetTranslation() : 0, DTA_ColorOverlay, dim ? DIM_OVERLAY : 0, DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM, @@ -1087,13 +1205,13 @@ public: } else { - screen->DrawTexture(texture, (fx >> FRACBITS), (fy >> FRACBITS), - DTA_DestWidth, w, - DTA_DestHeight, h, - DTA_ClipLeft, fcx>>FRACBITS, - DTA_ClipTop, fcy>>FRACBITS, - DTA_ClipRight, cr, - DTA_ClipBottom, cb, + screen->DrawTexture(texture, dx, dy, + DTA_DestWidthF, w, + DTA_DestHeightF, h, + DTA_ClipLeft, static_cast(dcx), + DTA_ClipTop, static_cast(dcy), + DTA_ClipRight, static_cast(MIN(INT_MAX, dcr)), + DTA_ClipBottom, static_cast(MIN(INT_MAX, dcb)), DTA_Translation, translate ? GetTranslation() : 0, DTA_ColorOverlay, dim ? DIM_OVERLAY : 0, DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM, @@ -1104,69 +1222,87 @@ public: } else { - int rx, ry, rcx=0, rcy=0, rcr=INT_MAX, rcb=INT_MAX; - ADJUST_RELCENTER(x,y,rx,ry) + double rx, ry, rcx=0, rcy=0, rcr=INT_MAX, rcb=INT_MAX; - w = (forceWidth <= -1 ? texture->GetScaledWidth() : forceWidth); - h = (forceHeight <= -1 ? texture->GetScaledHeight() : forceHeight); + double xScale = !hud_scale ? 1.0 : (double) CleanXfac*320.0/(double) script->resW;//(double) SCREENWIDTH/(double) script->resW; + double yScale = !hud_scale ? 1.0 : (double) CleanYfac*200.0/(double) script->resH;//(double) SCREENHEIGHT/(double) script->resH; + + adjustRelCenter(x.RelCenter(), y.RelCenter(), dx, dy, rx, ry, xScale, yScale); + + // We can't use DTA_HUDRules since it forces a width and height. + // Translation: No high res. + bool xright = rx < 0; + bool ybot = ry < 0; + + w = (forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth); + h = (forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight); if(vid_fps && rx < 0 && ry >= 0) ry += 10; + if(hud_scale) + { + rx *= xScale; + ry *= yScale; + w *= xScale; + h *= yScale; + } + if(xright) + rx = SCREENWIDTH + rx; + if(ybot) + ry = SCREENHEIGHT + ry; // Check for clipping if(cx != 0 || cy != 0 || cr != 0 || cb != 0) { - rcx = cx == 0 ? 0 : rx+(cx>>FRACBITS); - rcy = cy == 0 ? 0 : ry+(cy>>FRACBITS); - rcr = cr == 0 ? INT_MAX : rx+w-(cr>>FRACBITS); - rcb = cb == 0 ? INT_MAX : ry+h-(cb>>FRACBITS); + rcx = cx == 0 ? 0 : rx+(((double) cx/FRACUNIT)*xScale); + rcy = cy == 0 ? 0 : ry+(((double) cy/FRACUNIT)*yScale); + rcr = cr == 0 ? INT_MAX : rx+w-(((double) cr/FRACUNIT)*xScale); + rcb = cb == 0 ? INT_MAX : ry+h-(((double) cb/FRACUNIT)*yScale); + // Fix the clipping for fullscreenoffsets. - if(ry < 0) + /*if(ry < 0) { if(rcy != 0) - rcy = hud_scale ? SCREENHEIGHT + (rcy*CleanYfac) : SCREENHEIGHT + rcy; + rcy = hud_scale ? SCREENHEIGHT + (int) (rcy*CleanYfac*200.0/script->resH) : SCREENHEIGHT + rcy; if(rcb != INT_MAX) - rcb = hud_scale ? SCREENHEIGHT + (rcb*CleanYfac) : SCREENHEIGHT + rcb; + rcb = hud_scale ? SCREENHEIGHT + (int) (rcb*CleanYfac*200.0/script->resH) : SCREENHEIGHT + rcb; } else if(hud_scale) { - rcy *= CleanYfac; + rcy *= (int) (CleanYfac*200.0/script->resH); if(rcb != INT_MAX) - rcb *= CleanYfac; + rcb *= (int) (CleanYfac*200.0/script->resH); } if(rx < 0) { if(rcx != 0) - rcx = hud_scale ? SCREENWIDTH + (rcx*CleanXfac) : SCREENWIDTH + rcx; + rcx = hud_scale ? SCREENWIDTH + (int) (rcx*CleanXfac*320.0/script->resW) : SCREENWIDTH + rcx; if(rcr != INT_MAX) - rcr = hud_scale ? SCREENWIDTH + (rcr*CleanXfac) : SCREENWIDTH + rcr; + rcr = hud_scale ? SCREENWIDTH + (int) (rcr*CleanXfac*320.0/script->resW) : SCREENWIDTH + rcr; } else if(hud_scale) { - rcx *= CleanXfac; + rcx *= (int) (CleanXfac*320.0/script->resW); if(rcr != INT_MAX) - rcr *= CleanXfac; - } + rcr *= (int) (CleanXfac*320.0/script->resW); + }*/ } if(clearDontDraw) - { - screen->Clear(rcx, rcy, MIN(rcr, w*(hud_scale ? CleanXfac : 1)), MIN(rcb, h*(hud_scale ? CleanYfac : 1)), GPalette.BlackIndex, 0); - } + screen->Clear(static_cast(rcx), static_cast(rcy), static_cast(MIN(rcr, rcx+w)), static_cast(MIN(rcb, rcy+h)), GPalette.BlackIndex, 0); else { if(alphaMap) { screen->DrawTexture(texture, rx, ry, - DTA_DestWidth, w, - DTA_DestHeight, h, - DTA_ClipLeft, rcx, - DTA_ClipTop, rcy, - DTA_ClipRight, rcr, - DTA_ClipBottom, rcb, + DTA_DestWidthF, w, + DTA_DestHeightF, h, + DTA_ClipLeft, static_cast(rcx), + DTA_ClipTop, static_cast(rcy), + DTA_ClipRight, static_cast(rcr), + DTA_ClipBottom, static_cast(rcb), DTA_Translation, translate ? GetTranslation() : 0, DTA_ColorOverlay, dim ? DIM_OVERLAY : 0, DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM, - DTA_HUDRules, HUD_Normal, DTA_Alpha, alpha, DTA_AlphaChannel, alphaMap, DTA_FillColor, 0, @@ -1175,16 +1311,15 @@ public: else { screen->DrawTexture(texture, rx, ry, - DTA_DestWidth, w, - DTA_DestHeight, h, - DTA_ClipLeft, rcx, - DTA_ClipTop, rcy, - DTA_ClipRight, rcr, - DTA_ClipBottom, rcb, + DTA_DestWidthF, w, + DTA_DestHeightF, h, + DTA_ClipLeft, static_cast(rcx), + DTA_ClipTop, static_cast(rcy), + DTA_ClipRight, static_cast(rcr), + DTA_ClipBottom, static_cast(rcb), DTA_Translation, translate ? GetTranslation() : 0, DTA_ColorOverlay, dim ? DIM_OVERLAY : 0, DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM, - DTA_HUDRules, HUD_Normal, DTA_Alpha, alpha, TAG_DONE); } @@ -1192,20 +1327,32 @@ public: } } - void DrawString(FFont *font, const char* str, SBarInfoCoordinate x, SBarInfoCoordinate y, int xOffset, int yOffset, int alpha, bool fullScreenOffsets, EColorRange translation, int spacing=0, bool drawshadow=false) const + void DrawString(FFont *font, const char* str, SBarInfoCoordinate x, SBarInfoCoordinate y, int xOffset, int yOffset, int alpha, bool fullScreenOffsets, EColorRange translation, int spacing=0, bool drawshadow=false, int shadowX=2, int shadowY=2) const { x += spacing; - int ax = *x; - int ay = *y; + double ax = *x; + double ay = *y; + + double xScale = 1.0; + double yScale = 1.0; + if(fullScreenOffsets) { - ADJUST_RELCENTER(x,y,ax,ay) + if(hud_scale) + { + xScale = (double) CleanXfac*320.0/(double) script->resW;//(double) SCREENWIDTH/(double) script->resW; + yScale = (double) CleanYfac*200.0/(double) script->resH;//(double) SCREENWIDTH/(double) script->resW; + } + adjustRelCenter(x.RelCenter(), y.RelCenter(), *x, *y, ax, ay, xScale, yScale); } while(*str != '\0') { if(*str == ' ') { - ax += font->GetSpaceWidth(); + if(script->spacingCharacter == '\0') + ax += font->GetSpaceWidth(); + else + ax += font->GetCharWidth((int) script->spacingCharacter); str++; continue; } @@ -1223,65 +1370,77 @@ public: if(script->spacingCharacter == '\0') //If we are monospaced lets use the offset ax += (character->LeftOffset+1); //ignore x offsets since we adapt to character size - int rx, ry, rw, rh; + double rx, ry, rw, rh; rx = ax + xOffset; ry = ay + yOffset; - rw = character->GetScaledWidth(); - rh = character->GetScaledHeight(); + rw = character->GetScaledWidthDouble(); + rh = character->GetScaledHeightDouble(); + + if(script->spacingCharacter != '\0') + { + double spacingSize = font->GetCharWidth((int) script->spacingCharacter); + switch(script->spacingAlignment) + { + default: + break; + case SBarInfo::ALIGN_CENTER: + rx += (spacingSize/2)-(rw/2); + break; + case SBarInfo::ALIGN_RIGHT: + rx += spacingSize-rw; + break; + } + } + if(!fullScreenOffsets) { rx += ST_X; - ry += ST_Y; + ry += ST_Y - (Scaled ? script->resH : 200) + script->height; if(Scaled) - screen->VirtualToRealCoordsInt(rx, ry, rw, rh, 320, 200, true); + screen->VirtualToRealCoords(rx, ry, rw, rh, script->resW, script->resH, true); + else + { + ry += (200 - script->resH); + } } else { if(vid_fps && ax < 0 && ay >= 0) ry += 10; + + bool xright = rx < 0; + bool ybot = ry < 0; + + if(hud_scale) + { + rx *= xScale; + ry *= yScale; + rw *= xScale; + rh *= yScale; + } + if(xright) + rx = SCREENWIDTH + rx; + if(ybot) + ry = SCREENHEIGHT + ry; } if(drawshadow) { int salpha = fixed_t(((double) alpha / (double) FRACUNIT) * ((double) HR_SHADOW / (double) FRACUNIT) * FRACUNIT); - if(!fullScreenOffsets) - { - screen->DrawTexture(character, rx+2, ry+2, - DTA_DestWidth, rw, - DTA_DestHeight, rh, - DTA_Alpha, salpha, - DTA_FillColor, 0, - TAG_DONE); - } - else - { - screen->DrawTexture(character, rx+2, ry+2, - DTA_DestWidth, rw, - DTA_DestHeight, rh, - DTA_Alpha, salpha, - DTA_HUDRules, HUD_Normal, - DTA_FillColor, 0, - TAG_DONE); - } - } - if(!fullScreenOffsets) - { - screen->DrawTexture(character, rx, ry, - DTA_DestWidth, rw, - DTA_DestHeight, rh, - DTA_Translation, font->GetColorTranslation(translation), - DTA_Alpha, alpha, - TAG_DONE); - } - else - { - screen->DrawTexture(character, rx, ry, - DTA_DestWidth, rw, - DTA_DestHeight, rh, - DTA_Translation, font->GetColorTranslation(translation), - DTA_Alpha, alpha, - DTA_HUDRules, HUD_Normal, + double srx = rx + (shadowX*xScale); + double sry = ry + (shadowY*yScale); + screen->DrawTexture(character, srx, sry, + DTA_DestWidthF, rw, + DTA_DestHeightF, rh, + DTA_Alpha, salpha, + DTA_FillColor, 0, TAG_DONE); } + screen->DrawTexture(character, rx, ry, + DTA_DestWidthF, rw, + DTA_DestHeightF, rh, + DTA_Translation, font->GetColorTranslation(translation), + DTA_Alpha, alpha, + TAG_DONE); if(script->spacingCharacter == '\0') ax += width + spacing - (character->LeftOffset+1); else //width gets changed at the call to GetChar() diff --git a/src/g_shared/sbarinfo.h b/src/g_shared/sbarinfo.h index 6fd7b1f0c..d2d81f1f7 100644 --- a/src/g_shared/sbarinfo.h +++ b/src/g_shared/sbarinfo.h @@ -52,6 +52,7 @@ struct Popup { TRANSITION_NONE, TRANSITION_SLIDEINBOTTOM, + TRANSITION_PUSHUP, TRANSITION_FADE, }; @@ -65,6 +66,8 @@ struct Popup int alpha; int x; int y; + int displacementX; + int displacementY; Popup(); void init(); @@ -75,10 +78,19 @@ struct Popup int getXOffset(); int getYOffset(); int getAlpha(int maxAlpha=FRACUNIT); + int getXDisplacement(); + int getYDisplacement(); }; struct SBarInfo { + enum MonospaceAlignment + { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + TArray Images; SBarInfoMainBlock *huds[NUMHUDS]; Popup popups[NUMPOPUPS]; @@ -88,11 +100,14 @@ struct SBarInfo bool completeBorder; bool lowerHealthCap; char spacingCharacter; + MonospaceAlignment spacingAlignment; int interpolationSpeed; int armorInterpolationSpeed; int height; int gameType; FMugShot MugShot; + int resW; + int resH; int GetGameType() { return gameType; } void ParseSBarInfo(int lump); diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index 61c44cc27..0c9bec807 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -93,7 +93,10 @@ class CommandDrawImage : public SBarInfoCommand else if(sc.Compare("amulet")) type = HEXENARMOR_AMULET; else - sc.ScriptError("Unkown armor type: '%s'", sc.String); + { + sc.ScriptMessage("Unkown armor type: '%s'", sc.String); + type = HEXENARMOR_ARMOR; + } sc.MustGetToken(','); getImage = true; } @@ -108,9 +111,12 @@ class CommandDrawImage : public SBarInfoCommand const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item)) //must be a kind of Inventory { - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + } + else + { + sprite = ((AInventory *)GetDefaultByType(item))->Icon; } - sprite = ((AInventory *)GetDefaultByType(item))->Icon; image = -1; } } @@ -324,7 +330,7 @@ class CommandDrawSwitchableImage : public CommandDrawImage const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item)) //must be a kind of Inventory { - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); } GetOperation(sc, conditionalOperator[0], conditionalValue[0]); } @@ -349,7 +355,7 @@ class CommandDrawSwitchableImage : public CommandDrawImage const PClass* item = PClass::FindClass(sc.String); if(item == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item)) //must be a kind of Inventory { - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); } GetOperation(sc, conditionalOperator[1], conditionalValue[1]); } @@ -514,47 +520,292 @@ class CommandDrawString : public SBarInfoCommand { public: CommandDrawString(SBarInfo *script) : SBarInfoCommand(script), - shadow(false), spacing(0), font(NULL), translation(CR_UNTRANSLATED) + shadow(false), shadowX(2), shadowY(2), spacing(0), font(NULL), + translation(CR_UNTRANSLATED), cache(-1), strValue(CONSTANT), + valueArgument(0), alignment (ALIGN_RIGHT) { } void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) { - statusBar->DrawString(font, str.GetChars(), x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow); + statusBar->DrawString(font, str.GetChars(), x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow, shadowX, shadowY); } void Parse(FScanner &sc, bool fullScreenOffsets) { sc.MustGetToken(TK_Identifier); font = V_GetFont(sc.String); if(font == NULL) - sc.ScriptError("Unknown font '%s'.", sc.String); + { + sc.ScriptMessage("Unknown font '%s'.", sc.String); + font = SmallFont; + } sc.MustGetToken(','); translation = GetTranslation(sc); sc.MustGetToken(','); - sc.MustGetToken(TK_StringConst); - str = sc.String; + if(sc.CheckToken(TK_Identifier)) + { + if(sc.Compare("levelname")) + strValue = LEVELNAME; + else if(sc.Compare("levellump")) + strValue = LEVELLUMP; + else if(sc.Compare("skillname")) + strValue = SKILLNAME; + else if(sc.Compare("playerclass")) + strValue = PLAYERCLASS; + else if(sc.Compare("playername")) + strValue = PLAYERNAME; + else if(sc.Compare("ammo1tag")) + strValue = AMMO1TAG; + else if(sc.Compare("ammo2tag")) + strValue = AMMO2TAG; + else if(sc.Compare("weapontag")) + strValue = WEAPONTAG; + else if(sc.Compare("inventorytag")) + strValue = INVENTORYTAG; + else if(sc.Compare("globalvar")) + { + strValue = GLOBALVAR; + sc.MustGetToken(TK_IntConst); + if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS) + sc.ScriptError("Global variable number out of range: %d", sc.Number); + valueArgument = sc.Number; + } + else if(sc.Compare("globalarray")) + { + strValue = GLOBALARRAY; + sc.MustGetToken(TK_IntConst); + if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS) + sc.ScriptError("Global variable number out of range: %d", sc.Number); + valueArgument = sc.Number; + } + else + sc.ScriptError("Unknown string '%s'.", sc.String); + } + else + { + strValue = CONSTANT; + sc.MustGetToken(TK_StringConst); + if(sc.String[0] == '$') + str = GStrings[sc.String+1]; + else + str = sc.String; + } sc.MustGetToken(','); - GetCoordinates(sc, fullScreenOffsets, x, y); + GetCoordinates(sc, fullScreenOffsets, startX, y); if(sc.CheckToken(',')) //spacing { sc.MustGetToken(TK_IntConst); spacing = sc.Number; + if(sc.CheckToken(',')) //[KS] flags? flags! SIX FLAGS! + { + while(sc.CheckToken(TK_Identifier)) + { + if(sc.Compare("alignment")) + { + sc.MustGetToken('('); + sc.MustGetToken(TK_Identifier); + if(sc.Compare("right")) + alignment = ALIGN_RIGHT; + else if(sc.Compare("left")) + alignment = ALIGN_LEFT; + else if(sc.Compare("center")) + alignment = ALIGN_CENTER; + else + sc.ScriptError("Unknown alignment '%s'.", sc.String); + sc.MustGetToken(')'); + } + else if(sc.Compare("drawshadow")) + { + if(sc.CheckToken('(')) + { + sc.MustGetToken(TK_IntConst); + shadowX = sc.Number; + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + shadowY = sc.Number; + sc.MustGetToken(')'); + } + shadow = true; + } + else + sc.ScriptError("Unknown flag '%s'.", sc.String); + if(!sc.CheckToken('|') && !sc.CheckToken(',')) break; + } + } } sc.MustGetToken(';'); - if(script->spacingCharacter == '\0') - x -= static_cast (font->StringWidth(str)+(spacing * str.Len())); - else //monospaced, so just multiplay the character size - x -= static_cast ((font->GetCharWidth((int) script->spacingCharacter) + spacing) * str.Len()); + RealignString(); + } + void Reset() + { + switch(strValue) + { + case PLAYERCLASS: + // userinfo changes before the actual class change. + case SKILLNAME: + // Although it's not possible for the skill level to change + // midlevel, it is possible the level was restarted. + cache = -1; + break; + default: + break; + } + } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + switch(strValue) + { + case LEVELNAME: + if(level.lumpnum != cache) + { + cache = level.lumpnum; + str = level.LevelName; + RealignString(); + } + break; + case LEVELLUMP: + if(level.lumpnum != cache) + { + cache = level.lumpnum; + str = level.mapname; + RealignString(); + } + break; + case SKILLNAME: + if(level.lumpnum != cache) // Can only change skill between level. + { + cache = level.lumpnum; + str = G_SkillName(); + RealignString(); + } + break; + case PLAYERCLASS: + if(statusBar->CPlayer->userinfo.PlayerClass != cache) + { + cache = statusBar->CPlayer->userinfo.PlayerClass; + str = statusBar->CPlayer->cls->DisplayName; + RealignString(); + } + break; + case AMMO1TAG: + SetStringToTag(statusBar->ammo1); + break; + case AMMO2TAG: + SetStringToTag(statusBar->ammo2); + break; + case WEAPONTAG: + SetStringToTag(statusBar->CPlayer->ReadyWeapon); + break; + case INVENTORYTAG: + SetStringToTag(statusBar->CPlayer->mo->InvSel); + break; + case PLAYERNAME: + // Can't think of a good way to detect changes to this, so + // I guess copying it every tick will have to do. + str = statusBar->CPlayer->userinfo.netname; + RealignString(); + break; + case GLOBALVAR: + if(ACS_GlobalVars[valueArgument] != cache) + { + cache = ACS_GlobalVars[valueArgument]; + str = FBehavior::StaticLookupString(ACS_GlobalVars[valueArgument]); + RealignString(); + } + break; + case GLOBALARRAY: + if(ACS_GlobalArrays[valueArgument][consoleplayer] != cache) + { + cache = ACS_GlobalArrays[valueArgument][consoleplayer]; + str = FBehavior::StaticLookupString(ACS_GlobalArrays[valueArgument][consoleplayer]); + RealignString(); + } + break; + default: + break; + } } protected: + enum StringAlignment + { + ALIGN_RIGHT, + ALIGN_LEFT, + ALIGN_CENTER, + }; + + void RealignString() + { + x = startX; + switch (alignment) + { + case ALIGN_LEFT: + break; + case ALIGN_RIGHT: + if(script->spacingCharacter == '\0') + x -= static_cast (font->StringWidth(str)+(spacing * str.Len())); + else //monospaced, so just multiplay the character size + x -= static_cast ((font->GetCharWidth((int) script->spacingCharacter) + spacing) * str.Len()); + break; + case ALIGN_CENTER: + if(script->spacingCharacter == '\0') + x -= static_cast (font->StringWidth(str)+(spacing * str.Len()) / 2); + else + x -= static_cast ((font->GetCharWidth((int) script->spacingCharacter) + spacing) * str.Len() / 2); + break; + } + } + + enum StringValueType + { + LEVELNAME, + LEVELLUMP, + SKILLNAME, + PLAYERCLASS, + PLAYERNAME, + AMMO1TAG, + AMMO2TAG, + WEAPONTAG, + INVENTORYTAG, + GLOBALVAR, + GLOBALARRAY, + + CONSTANT + }; + bool shadow; + int shadowX; + int shadowY; int spacing; FFont *font; EColorRange translation; + SBarInfoCoordinate startX; SBarInfoCoordinate x; SBarInfoCoordinate y; + intptr_t cache; /// General purpose cache. + StringValueType strValue; + int valueArgument; FString str; + StringAlignment alignment; + + private: + void SetStringToTag(AActor *actor) + { + if (actor != NULL) + { + if ((intptr_t)actor->GetClass() != cache) + { + cache = (intptr_t)actor->GetClass(); + str = actor->GetTag(); + RealignString(); + } + } + else + { + cache = -1; + str = ""; + } + } }; //////////////////////////////////////////////////////////////////////////////// @@ -565,7 +816,7 @@ class CommandDrawNumber : public CommandDrawString CommandDrawNumber(SBarInfo *script) : CommandDrawString(script), fillZeros(false), whenNotZero(false), interpolationSpeed(0), drawValue(0), length(3), lowValue(-1), lowTranslation(CR_UNTRANSLATED), highValue(-1), - highTranslation(CR_UNTRANSLATED), value(CONSTANT), valueArgument(0), + highTranslation(CR_UNTRANSLATED), value(CONSTANT), inventoryItem(NULL) { } @@ -578,7 +829,10 @@ class CommandDrawNumber : public CommandDrawString sc.MustGetToken(TK_Identifier); font = V_GetFont(sc.String); if(font == NULL) - sc.ScriptError("Unknown font '%s'.", sc.String); + { + sc.ScriptMessage("Unknown font '%s'.", sc.String); + font = SmallFont; + } sc.MustGetToken(','); normalTranslation = GetTranslation(sc); sc.MustGetToken(','); @@ -608,7 +862,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo { - sc.ScriptError("'%s' is not a type of ammo.", sc.String); + sc.ScriptMessage("'%s' is not a type of ammo.", sc.String); + inventoryItem = RUNTIME_CLASS(AAmmo); } } else if(sc.Compare("ammocapacity")) @@ -618,7 +873,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo { - sc.ScriptError("'%s' is not a type of ammo.", sc.String); + sc.ScriptMessage("'%s' is not a type of ammo.", sc.String); + inventoryItem = RUNTIME_CLASS(AAmmo); } } else if(sc.Compare("frags")) @@ -662,7 +918,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(APowerupGiver)->IsAncestorOf(inventoryItem)) { - sc.ScriptError("'%s' is not a type of PowerupGiver.", sc.String); + sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String); + inventoryItem = RUNTIME_CLASS(APowerupGiver); } } else @@ -671,7 +928,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of ammo { - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); } } sc.MustGetToken(','); @@ -683,7 +941,18 @@ class CommandDrawNumber : public CommandDrawString else if(sc.Compare("whennotzero")) whenNotZero = true; else if(sc.Compare("drawshadow")) + { + if(sc.CheckToken('(')) + { + sc.MustGetToken(TK_IntConst); + shadowX = sc.Number; + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + shadowY = sc.Number; + sc.MustGetToken(')'); + } shadow = true; + } else if(sc.Compare("interpolate")) { sc.MustGetToken('('); @@ -691,6 +960,20 @@ class CommandDrawNumber : public CommandDrawString interpolationSpeed = sc.Number; sc.MustGetToken(')'); } + else if(sc.Compare("alignment")) + { + sc.MustGetToken('('); + sc.MustGetToken(TK_Identifier); + if(sc.Compare("right")) + alignment = ALIGN_RIGHT; + else if(sc.Compare("left")) + alignment = ALIGN_LEFT; + else if(sc.Compare("center")) + alignment = ALIGN_CENTER; + else + sc.ScriptError("Unknown alignment '%s'.", sc.String); + sc.MustGetToken(')'); + } else sc.ScriptError("Unknown flag '%s'.", sc.String); if(!sc.CheckToken('|')) @@ -880,9 +1163,7 @@ class CommandDrawNumber : public CommandDrawString translation = lowTranslation; else if(highValue != -1 && drawValue >= highValue) //high translation = highTranslation; - - x = startX; - + // 10^9 is a largest we can hold in a 32-bit int. So if we go any larger we have to toss out the positions limit. int maxval = length <= 9 ? (int) ceil(pow(10., length))-1 : INT_MAX; if(!fillZeros || length == 1) @@ -902,10 +1183,8 @@ class CommandDrawNumber : public CommandDrawString str.Insert(0, "0"); } } - if(script->spacingCharacter == '\0') - x -= static_cast (font->StringWidth(str)+(spacing * str.Len())); - else //monospaced, so just multiplay the character size - x -= static_cast ((font->GetCharWidth((int) script->spacingCharacter) + spacing) * str.Len()); + + RealignString(); } protected: enum ValueType @@ -948,11 +1227,8 @@ class CommandDrawNumber : public CommandDrawString EColorRange highTranslation; EColorRange normalTranslation; ValueType value; - int valueArgument; PClassActor *inventoryItem; - SBarInfoCoordinate startX; - friend class CommandDrawInventoryBar; }; @@ -1009,6 +1285,10 @@ class CommandDrawMugShot : public SBarInfoCommand GetCoordinates(sc, fullScreenOffsets, x, y); sc.MustGetToken(';'); } + void Reset() + { + script->MugShot.Reset(); + } protected: FString defaultFace; //Deprecated @@ -1032,6 +1312,9 @@ class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) { + if(alternateOnEmpty) + SBarInfoCommandFlowControl::Draw(block, statusBar); + if(statusBar->CPlayer->mo->InvSel != NULL && !(level.flags & LEVEL_NOINVENTORYBAR)) { if(artiflash && artiflashTick) @@ -1044,8 +1327,6 @@ class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private if(alwaysShowCounter || statusBar->CPlayer->mo->InvSel->Amount != 1) CommandDrawNumber::Draw(block, statusBar); } - else if(alternateOnEmpty) - SBarInfoCommandFlowControl::Draw(block, statusBar); } void Parse(FScanner &sc, bool fullScreenOffsets) { @@ -1065,12 +1346,26 @@ class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private else if(sc.Compare("centerbottom")) offset = static_cast (HMIDDLE|BOTTOM); else if(sc.Compare("drawshadow")) + { + if(sc.CheckToken('(')) + { + sc.MustGetToken(TK_IntConst); + shadowX = sc.Number; + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + shadowY = sc.Number; + sc.MustGetToken(')'); + } shadow = true; + } else { font = V_GetFont(sc.String); if(font == NULL) - sc.ScriptError("Unknown font '%s'.", sc.String); + { + sc.ScriptMessage("Unknown font '%s'.", sc.String); + font = SmallFont; + } sc.MustGetToken(','); break; } @@ -1101,9 +1396,13 @@ class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private } void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + if(artiflashTick > 0) artiflashTick--; - + + SetTruth(statusBar->CPlayer->mo->InvSel == NULL || (level.flags & LEVEL_NOINVENTORYBAR), block, statusBar); + CommandDrawImage::Tick(block, statusBar, hudChanged); CommandDrawNumber::Tick(block, statusBar, hudChanged); } @@ -1133,16 +1432,6 @@ class CommandGameMode : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if((!multiplayer && (modes & SINGLEPLAYER)) || - (deathmatch && (modes & DEATHMATCH)) || - (multiplayer && !deathmatch && (modes & COOPERATIVE)) || - (teamplay && (modes & TEAMGAME))) - { - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - } void Parse(FScanner &sc, bool fullScreenOffsets) { do @@ -1153,6 +1442,15 @@ class CommandGameMode : public SBarInfoCommandFlowControl while(sc.CheckToken(',')); SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + SetTruth((!multiplayer && (modes & SINGLEPLAYER)) || + (deathmatch && (modes & DEATHMATCH)) || + (multiplayer && !deathmatch && (modes & COOPERATIVE)) || + (teamplay && (modes & TEAMGAME)), block, statusBar); + } protected: static const char* const modeNames[]; enum GameModes @@ -1185,11 +1483,6 @@ class CommandUsesAmmo : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if((statusBar->CPlayer->ReadyWeapon != NULL && (statusBar->CPlayer->ReadyWeapon->AmmoType1 != NULL || statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL)) ^ negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } void Parse(FScanner &sc, bool fullScreenOffsets) { if(sc.CheckToken(TK_Identifier)) @@ -1201,6 +1494,12 @@ class CommandUsesAmmo : public SBarInfoCommandFlowControl } SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + SetTruth((statusBar->CPlayer->ReadyWeapon != NULL && (statusBar->CPlayer->ReadyWeapon->AmmoType1 != NULL || statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL)) ^ negate, block, statusBar); + } protected: bool negate; }; @@ -1214,10 +1513,11 @@ class CommandUsesSecondaryAmmo : public CommandUsesAmmo { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { - if((statusBar->CPlayer->ReadyWeapon != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType1 != statusBar->CPlayer->ReadyWeapon->AmmoType2) ^ negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + SetTruth((statusBar->CPlayer->ReadyWeapon != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType1 != statusBar->CPlayer->ReadyWeapon->AmmoType2) ^ negate, block, statusBar); } }; @@ -1230,10 +1530,11 @@ class CommandInventoryBarNotVisible : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { - if(statusBar->CPlayer->inventorytics <= 0 || (level.flags & LEVEL_NOINVENTORYBAR)) - SBarInfoCommandFlowControl::Draw(block, statusBar); + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + SetTruth(statusBar->CPlayer->inventorytics <= 0 || (level.flags & LEVEL_NOINVENTORYBAR), block, statusBar); } }; @@ -1247,11 +1548,6 @@ class CommandAspectRatio : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if(CheckRatio(screen->GetWidth(), screen->GetHeight()) == ratio) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } void Parse(FScanner &sc, bool fullScreenOffsets) { sc.MustGetToken(TK_StringConst); @@ -1267,6 +1563,10 @@ class CommandAspectRatio : public SBarInfoCommandFlowControl sc.ScriptError("Unkown aspect ratio: %s", sc.String); SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SetTruth(CheckRatio(screen->GetWidth(), screen->GetHeight()) == ratio, block, statusBar); + } protected: enum Ratio { @@ -1420,7 +1720,7 @@ class CommandDrawInventoryBar : public SBarInfoCommand { public: CommandDrawInventoryBar(SBarInfo *script) : SBarInfoCommand(script), - style(GAME_Doom), size(7), alwaysShow(false), noArtibox(false), + style(STYLE_Doom), size(7), alwaysShow(false), noArtibox(false), noArrows(false), alwaysShowCounter(false), translucent(false), vertical(false), counters(NULL), font(NULL), translation(CR_GOLD), fontSpacing(0) @@ -1440,9 +1740,9 @@ class CommandDrawInventoryBar : public SBarInfoCommand { int spacing = 0; if(!vertical) - spacing = (style != GAME_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; + spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; else - spacing = (style != GAME_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; + spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; int bgalpha = block->Alpha(); if(translucent) @@ -1461,20 +1761,22 @@ class CommandDrawInventoryBar : public SBarInfoCommand if(!noArtibox) statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgARTIBOX], rx, ry, block->XOffset(), block->YOffset(), bgalpha, block->FullScreenOffsets()); - if(style != GAME_Strife) //Strife draws the cursor before the icons - statusBar->DrawGraphic(TexMan(item->Icon), rx, ry, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, item->Amount <= 0); + if(style != STYLE_Strife) //Strife draws the cursor before the icons + statusBar->DrawGraphic(TexMan(item->Icon), rx - (style == STYLE_HexenStrict ? 2 : 0), ry - (style == STYLE_HexenStrict ? 1 : 0), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, item->Amount <= 0); if(item == statusBar->CPlayer->mo->InvSel) { - if(style == GAME_Heretic) + if(style == STYLE_Heretic) statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry+29, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); - else if(style == GAME_Hexen) + else if(style == STYLE_Hexen) statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry-1, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); - else if(style == GAME_Strife) + else if(style == STYLE_HexenStrict) + statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx-1, ry-1, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); + else if(style == STYLE_Strife) statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgCURSOR], rx-6, ry-2, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); else statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); } - if(style == GAME_Strife) + if(style == STYLE_Strife) statusBar->DrawGraphic(TexMan(item->Icon), rx, ry, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, item->Amount <= 0); if(counters != NULL && (alwaysShowCounter || item->Amount != 1)) { @@ -1488,16 +1790,18 @@ class CommandDrawInventoryBar : public SBarInfoCommand // Is there something to the left? if (!noArrows && statusBar->CPlayer->mo->FirstInv() != statusBar->CPlayer->mo->InvFirst) { - int offset = style != GAME_Strife ? -12 : 14; + int offset = (style != STYLE_Strife ? (style != STYLE_HexenStrict ? -12 : -10) : 14); + int yOffset = style != STYLE_HexenStrict ? 0 : -1; statusBar->DrawGraphic(statusBar->Images[!(gametic & 4) ? - statusBar->invBarOffset + imgINVLFGEM1 : statusBar->invBarOffset + imgINVLFGEM2], x + (!vertical ? offset : 0), y + (vertical ? offset : 0), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); + statusBar->invBarOffset + imgINVLFGEM1 : statusBar->invBarOffset + imgINVLFGEM2], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); } // Is there something to the right? if (!noArrows && item != NULL) { - int offset = style != GAME_Strife ? size*31+2 : size*35-4; + int offset = (style != STYLE_Strife ? (style != STYLE_HexenStrict ? size*31+2 : size*31) : size*35-4); + int yOffset = style != STYLE_HexenStrict ? 0 : -1; statusBar->DrawGraphic(statusBar->Images[!(gametic & 4) ? - statusBar->invBarOffset + imgINVRTGEM1 : statusBar->invBarOffset + imgINVRTGEM2], x + (!vertical ? offset : 0), y + (vertical ? offset : 0), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); + statusBar->invBarOffset + imgINVRTGEM1 : statusBar->invBarOffset + imgINVRTGEM2], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); } } } @@ -1505,13 +1809,15 @@ class CommandDrawInventoryBar : public SBarInfoCommand { sc.MustGetToken(TK_Identifier); if(sc.Compare("Doom")) - style = GAME_Doom; + style = STYLE_Doom; else if(sc.Compare("Heretic")) - style = GAME_Heretic; + style = STYLE_Heretic; else if(sc.Compare("Hexen")) - style = GAME_Hexen; + style = STYLE_Hexen; + else if(sc.Compare("HexenStrict")) + style = STYLE_HexenStrict; else if(sc.Compare("Strife")) - style = GAME_Strife; + style = STYLE_Strife; else sc.ScriptError("Unknown style '%s'.", sc.String); @@ -1541,7 +1847,10 @@ class CommandDrawInventoryBar : public SBarInfoCommand sc.MustGetToken(TK_Identifier); font = V_GetFont(sc.String); if(font == NULL) + { sc.ScriptError("Unknown font '%s'.", sc.String); + font = SmallFont; + } sc.MustGetToken(','); GetCoordinates(sc, fullScreenOffsets, x, y); @@ -1569,9 +1878,9 @@ class CommandDrawInventoryBar : public SBarInfoCommand { int spacing = 0; if(!vertical) - spacing = (style != GAME_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; + spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1; else - spacing = (style != GAME_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; + spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1; counters = new CommandDrawNumber*[size]; for(unsigned int i = 0;i < size;i++) @@ -1593,7 +1902,16 @@ class CommandDrawInventoryBar : public SBarInfoCommand counters[i]->Tick(block, statusBar, hudChanged); } protected: - int style; + enum Styles + { + STYLE_Doom, + STYLE_Heretic, + STYLE_Hexen, + STYLE_HexenStrict, + STYLE_Strife + }; + + Styles style; unsigned int size; bool alwaysShow; bool noArtibox; @@ -1619,8 +1937,8 @@ class CommandDrawKeyBar : public SBarInfoCommand { public: CommandDrawKeyBar(SBarInfo *script) : SBarInfoCommand(script), - number(3), vertical(false), reverseRows(false), iconSize(-1), - rowIconSize(-1), keyOffset(0), rowSize(0) + number(3), vertical(false), reverse(false), reverseRows(false), + iconSize(-1), rowIconSize(-1), keyOffset(0), rowSize(0) { } @@ -1657,12 +1975,12 @@ class CommandDrawKeyBar : public SBarInfoCommand if(iconSize == -1) { if(!vertical) - slotOffset += TexMan[item->Icon]->GetScaledWidth() + 2; + slotOffset += (reverse ? -1 : 1) * (TexMan[item->Icon]->GetScaledWidth() + 2); else - slotOffset += TexMan[item->Icon]->GetScaledHeight() + 2; + slotOffset += (reverse ? -1 : 1) * (TexMan[item->Icon]->GetScaledHeight() + 2); } else - slotOffset += iconSize; + slotOffset += (reverse ? -iconSize : iconSize); if(rowSize > 0 && (i % rowSize == rowSize-1)) { @@ -1695,6 +2013,8 @@ class CommandDrawKeyBar : public SBarInfoCommand { if(sc.Compare("reverserows")) reverseRows = true; + else if(sc.Compare("reverse")) + reverse = true; else sc.ScriptError("Unknown flag '%s'.", sc.String); if(!sc.CheckToken('|')) @@ -1735,6 +2055,7 @@ class CommandDrawKeyBar : public SBarInfoCommand protected: unsigned int number; bool vertical; + bool reverse; bool reverseRows; int iconSize; int rowIconSize; @@ -1763,9 +2084,12 @@ class CommandDrawBar : public SBarInfoCommand FTexture *fg = statusBar->Images[foreground]; FTexture *bg = (background != -1) ? statusBar->Images[background] : NULL; - + + fixed_t value = drawValue; if(border != 0) { + value = FRACUNIT - value; //invert since the new drawing method requires drawing the bg on the fg. + //Draw the whole foreground statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets()); } @@ -1782,7 +2106,7 @@ class CommandDrawBar : public SBarInfoCommand fixed_t clip[4] = {0, 0, 0, 0}; fixed_t sizeOfImage = (horizontal ? fg->GetScaledWidth()-border*2 : fg->GetScaledHeight()-border*2)<IsAncestorOf(inventoryItem)) //must be a kind of inventory - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + { + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); + } } } else if(sc.Compare("armor")) @@ -1823,7 +2150,10 @@ class CommandDrawBar : public SBarInfoCommand { inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of inventory - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + { + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); + } } } else if(sc.Compare("ammo1")) @@ -1837,7 +2167,8 @@ class CommandDrawBar : public SBarInfoCommand inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo { - sc.ScriptError("'%s' is not a type of ammo.", sc.String); + sc.ScriptMessage("'%s' is not a type of ammo.", sc.String); + inventoryItem = RUNTIME_CLASS(AAmmo); } } else if(sc.Compare("frags")) @@ -1857,7 +2188,8 @@ class CommandDrawBar : public SBarInfoCommand inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(APowerupGiver)->IsAncestorOf(inventoryItem)) { - sc.ScriptError("'%s' is not a type of PowerupGiver.", sc.String); + sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String); + inventoryItem = RUNTIME_CLASS(APowerupGiver); } } else @@ -1866,7 +2198,8 @@ class CommandDrawBar : public SBarInfoCommand inventoryItem = PClass::FindActor(sc.String); if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) { - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); } } sc.MustGetToken(','); @@ -2030,17 +2363,13 @@ class CommandDrawBar : public SBarInfoCommand } default: return; } - - if(border != 0) - value = max - value; //invert since the new drawing method requires drawing the bg on the fg. + if(max != 0 && value > 0) { value = (value << FRACBITS) / max; if(value > FRACUNIT) value = FRACUNIT; } - else if(border != 0 && max == 0 && value <= 0) - value = FRACUNIT; else value = 0; if(interpolationSpeed != 0 && (!hudChanged || level.time == 1)) @@ -2097,21 +2426,6 @@ class CommandIsSelected : public SBarInfoCommandFlowControl weapon[1] = NULL; } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if(statusBar->CPlayer->ReadyWeapon != NULL) - { - const PClass *readyWeapon = statusBar->CPlayer->ReadyWeapon->GetClass(); - if(((weapon[1] != NULL) && - ((negate && (weapon[0] != readyWeapon && weapon[1] != readyWeapon)) || - (!negate && (weapon[0] == readyWeapon || weapon[1] == readyWeapon)))) || - ((weapon[1] == NULL) && - ((!negate && weapon[0] == readyWeapon) || (negate && weapon[0] != readyWeapon)))) - { - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - } - } void Parse(FScanner &sc, bool fullScreenOffsets) { //Using StringConst instead of Identifieres is deperecated! @@ -2130,7 +2444,10 @@ class CommandIsSelected : public SBarInfoCommandFlowControl { weapon[i] = PClass::FindClass(sc.String); if(weapon[i] == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon[i])) - sc.ScriptError("'%s' is not a type of weapon.", sc.String); + { + sc.ScriptMessage("'%s' is not a type of weapon.", sc.String); + weapon[i] = RUNTIME_CLASS(AWeapon); + } if(sc.CheckToken(',')) { @@ -2142,6 +2459,20 @@ class CommandIsSelected : public SBarInfoCommandFlowControl } SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + if(statusBar->CPlayer->ReadyWeapon != NULL) + { + const PClass *readyWeapon = statusBar->CPlayer->ReadyWeapon->GetClass(); + SetTruth(((weapon[1] != NULL) && + ((negate && (weapon[0] != readyWeapon && weapon[1] != readyWeapon)) || + (!negate && (weapon[0] == readyWeapon || weapon[1] == readyWeapon)))) || + ((weapon[1] == NULL) && + ((!negate && weapon[0] == readyWeapon) || (negate && weapon[0] != readyWeapon))), block, statusBar); + } + } protected: bool negate; const PClass *weapon[2]; @@ -2156,18 +2487,6 @@ class CommandPlayerClass : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if(statusBar->CPlayer->cls == NULL) - return; //No class so we can not continue - - PClass *spawnClass = statusBar->CPlayer->cls; - for(unsigned int i = 0;i < classes.Size();i++) - { - if(classes[i] == spawnClass) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - } void Parse(FScanner &sc, bool fullScreenOffsets) { sc.MustGetToken(TK_Identifier); @@ -2183,14 +2502,34 @@ class CommandPlayerClass : public SBarInfoCommandFlowControl break; } } + /* if(!foundClass) sc.ScriptError("Unkown PlayerClass '%s'.", sc.String); + */ if(!sc.CheckToken(',')) break; } while(sc.CheckToken(TK_Identifier)); SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + if(statusBar->CPlayer->cls == NULL) + return; //No class so we can not continue + + PClass *spawnClass = statusBar->CPlayer->cls; + for(unsigned int i = 0;i < classes.Size();i++) + { + if(classes[i] == spawnClass) + { + SetTruth(true, block, statusBar); + return; + } + } + SetTruth(false, block, statusBar); + } protected: TArray classes; }; @@ -2205,8 +2544,26 @@ class CommandHasWeaponPiece : public SBarInfoCommandFlowControl { } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) + void Parse(FScanner &sc, bool fullScreenOffsets) { + sc.MustGetToken(TK_Identifier); + weapon = PClass::FindClass(sc.String); + if(weapon == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon)) //must be a weapon + { + sc.ScriptMessage("%s is not a kind of weapon.", sc.String); + weapon = RUNTIME_CLASS(AWeapon); + } + sc.MustGetToken(','); + sc.MustGetToken(TK_IntConst); + if(sc.Number < 1) + sc.ScriptError("Weapon piece number can not be less than 1."); + piece = sc.Number; + SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); + } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + for(AInventory *inv = statusBar->CPlayer->mo->Inventory;inv != NULL;inv=inv->Inventory) { if(inv->IsKindOf(RUNTIME_CLASS(AWeaponHolder))) @@ -2215,24 +2572,12 @@ class CommandHasWeaponPiece : public SBarInfoCommandFlowControl if(hold->PieceWeapon == weapon) { if(hold->PieceMask & (1 << (piece-1))) - SBarInfoCommandFlowControl::Draw(block, statusBar); - break; + SetTruth(true, block, statusBar); + return; } } } - } - void Parse(FScanner &sc, bool fullScreenOffsets) - { - sc.MustGetToken(TK_Identifier); - weapon = PClass::FindClass(sc.String); - if(weapon == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon)) //must be a weapon - sc.ScriptError("%s is not a kind of weapon.", sc.String); - sc.MustGetToken(','); - sc.MustGetToken(TK_IntConst); - if(sc.Number < 1) - sc.ScriptError("Weapon piece number can not be less than 1."); - piece = sc.Number; - SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); + SetTruth(false, block, statusBar); } protected: const PClass *weapon; @@ -2384,48 +2729,6 @@ class CommandWeaponAmmo : public SBarInfoCommandFlowControl ammo[1] = NULL; } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - if(statusBar->CPlayer->ReadyWeapon != NULL) - { - const PClass *AmmoType1 = statusBar->CPlayer->ReadyWeapon->AmmoType1; - const PClass *AmmoType2 = statusBar->CPlayer->ReadyWeapon->AmmoType2; - bool usesammo1 = (AmmoType1 != NULL); - bool usesammo2 = (AmmoType2 != NULL); - if(!negate && !usesammo1 && !usesammo2) //if the weapon doesn't use ammo don't go though the trouble. - { - SBarInfoCommandFlowControl::Draw(block, statusBar); - return; - } - //Or means only 1 ammo type needs to match and means both need to match. - if(ammo[1] != NULL) - { - bool match1 = ((usesammo1 && (AmmoType1 == ammo[0] || AmmoType1 == ammo[1])) || !usesammo1); - bool match2 = ((usesammo2 && (AmmoType2 == ammo[0] || AmmoType2 == ammo[1])) || !usesammo2); - if((!conditionAnd && (match1 || match2)) || (conditionAnd && (match1 && match2))) - { - if(!negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - else if(negate) - { - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - } - else //Every thing here could probably be one long if statement but then it would be more confusing. - { - if((usesammo1 && (AmmoType1 == ammo[0])) || (usesammo2 && (AmmoType2 == ammo[0]))) - { - if(!negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - else if(negate) - { - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - } - } - } void Parse(FScanner &sc, bool fullScreenOffsets) { sc.MustGetToken(TK_Identifier); @@ -2438,7 +2741,10 @@ class CommandWeaponAmmo : public SBarInfoCommandFlowControl { ammo[i] = PClass::FindClass(sc.String); if(ammo[i] == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo[i])) //must be a kind of ammo - sc.ScriptError("'%s' is not a type of ammo.", sc.String); + { + sc.ScriptMessage("'%s' is not a type of ammo.", sc.String); + ammo[i] = RUNTIME_CLASS(AAmmo); + } if(sc.CheckToken(TK_OrOr)) { @@ -2455,6 +2761,59 @@ class CommandWeaponAmmo : public SBarInfoCommandFlowControl } SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + if(statusBar->CPlayer->ReadyWeapon != NULL) + { + const PClass *AmmoType1 = statusBar->CPlayer->ReadyWeapon->AmmoType1; + const PClass *AmmoType2 = statusBar->CPlayer->ReadyWeapon->AmmoType2; + bool usesammo1 = (AmmoType1 != NULL); + bool usesammo2 = (AmmoType2 != NULL); + if(negate && !usesammo1 && !usesammo2) //if the weapon doesn't use ammo don't go though the trouble. + { + SetTruth(true, block, statusBar); + return; + } + //Or means only 1 ammo type needs to match and means both need to match. + if(ammo[1] != NULL) + { + bool match1 = ((usesammo1 && (AmmoType1 == ammo[0] || AmmoType1 == ammo[1])) || !usesammo1); + bool match2 = ((usesammo2 && (AmmoType2 == ammo[0] || AmmoType2 == ammo[1])) || !usesammo2); + if((!conditionAnd && (match1 || match2)) || (conditionAnd && (match1 && match2))) + { + if(!negate) + { + SetTruth(true, block, statusBar); + return; + } + } + else if(negate) + { + SetTruth(true, block, statusBar); + return; + } + } + else //Every thing here could probably be one long if statement but then it would be more confusing. + { + if((usesammo1 && (AmmoType1 == ammo[0])) || (usesammo2 && (AmmoType2 == ammo[0]))) + { + if(!negate) + { + SetTruth(true, block, statusBar); + return; + } + } + else if(negate) + { + SetTruth(true, block, statusBar); + return; + } + } + } + SetTruth(false, block, statusBar); + } protected: bool conditionAnd; bool negate; @@ -2473,30 +2832,6 @@ class CommandInInventory : public SBarInfoCommandFlowControl amount[0] = amount[1] = 0; } - void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) - { - AInventory *invItem[2] = { statusBar->CPlayer->mo->FindInventory(item[0]), statusBar->CPlayer->mo->FindInventory(item[1]) }; - if (invItem[0] != NULL && amount[0] > 0 && invItem[0]->Amount < amount[0]) invItem[0] = NULL; - if (invItem[1] != NULL && amount[1] > 0 && invItem[1]->Amount < amount[1]) invItem[1] = NULL; - if(invItem[1] != NULL && conditionAnd) - { - if((invItem[0] != NULL && invItem[1] != NULL) && !negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - else if((invItem[0] == NULL || invItem[1] == NULL) && negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - else if(invItem[1] != NULL && !conditionAnd) - { - if((invItem[0] != NULL || invItem[1] != NULL) && !negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - else if((invItem[0] == NULL && invItem[1] == NULL) && negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } - else if((invItem[0] != NULL) && !negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - else if((invItem[0] == NULL) && negate) - SBarInfoCommandFlowControl::Draw(block, statusBar); - } void Parse(FScanner &sc, bool fullScreenOffsets) { sc.MustGetToken(TK_Identifier); @@ -2509,7 +2844,10 @@ class CommandInInventory : public SBarInfoCommandFlowControl { item[i] = PClass::FindActor(sc.String); if(item[i] == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item[i])) - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + { + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + item[i] = RUNTIME_CLASS(AInventory); + } if (sc.CheckToken(',')) { @@ -2532,6 +2870,51 @@ class CommandInInventory : public SBarInfoCommandFlowControl } SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); } + void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) + { + SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged); + + AInventory *invItem[2] = { statusBar->CPlayer->mo->FindInventory(item[0]), statusBar->CPlayer->mo->FindInventory(item[1]) }; + if (invItem[0] != NULL && amount[0] > 0 && invItem[0]->Amount < amount[0]) invItem[0] = NULL; + if (invItem[1] != NULL && amount[1] > 0 && invItem[1]->Amount < amount[1]) invItem[1] = NULL; + if(invItem[1] != NULL && conditionAnd) + { + if((invItem[0] != NULL && invItem[1] != NULL) && !negate) + { + SetTruth(true, block, statusBar); + return; + } + else if((invItem[0] == NULL || invItem[1] == NULL) && negate) + { + SetTruth(true, block, statusBar); + return; + } + } + else if(invItem[1] != NULL && !conditionAnd) + { + if((invItem[0] != NULL || invItem[1] != NULL) && !negate) + { + SetTruth(true, block, statusBar); + return; + } + else if((invItem[0] == NULL && invItem[1] == NULL) && negate) + { + SetTruth(true, block, statusBar); + return; + } + } + else if((invItem[0] != NULL) && !negate) + { + SetTruth(true, block, statusBar); + return; + } + else if((invItem[0] == NULL) && negate) + { + SetTruth(true, block, statusBar); + return; + } + SetTruth(false, block, statusBar); + } protected: bool conditionAnd; bool negate; @@ -2541,6 +2924,34 @@ class CommandInInventory : public SBarInfoCommandFlowControl //////////////////////////////////////////////////////////////////////////////// +class CommandAlpha : public SBarInfoMainBlock +{ + public: + CommandAlpha(SBarInfo *script) : SBarInfoMainBlock(script) + { + } + + void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar) + { + forceScaled = block->ForceScaled(); + fullScreenOffsets = block->FullScreenOffsets(); + + SBarInfoMainBlock::Draw(block, statusBar, block->XOffset(), block->YOffset(), block->Alpha()); + } + + void Parse(FScanner &sc, bool fullScreenOffsets) + { + sc.MustGetToken(TK_FloatConst); + alpha = fixed_t(FRACUNIT * sc.Float); + + // We don't want to allow all the options of a regular main block + // so skip to the SBarInfoCommandFlowControl. + SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + static const char *SBarInfoCommandNames[] = { "drawimage", "drawnumber", "drawswitchableimage", @@ -2550,7 +2961,7 @@ static const char *SBarInfoCommandNames[] = "gamemode", "playerclass", "aspectratio", "isselected", "usesammo", "usessecondaryammo", "hasweaponpiece", "inventorybarnotvisible", - "weaponammo", "ininventory", + "weaponammo", "ininventory", "alpha", NULL }; @@ -2563,7 +2974,7 @@ enum SBarInfoCommands SBARINFO_GAMEMODE, SBARINFO_PLAYERCLASS, SBARINFO_ASPECTRATIO, SBARINFO_ISSELECTED, SBARINFO_USESAMMO, SBARINFO_USESSECONDARYAMMO, SBARINFO_HASWEAPONPIECE, SBARINFO_INVENTORYBARNOTVISIBLE, - SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, + SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA, }; SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) @@ -2594,6 +3005,7 @@ SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc) case SBARINFO_HASWEAPONPIECE: return new CommandHasWeaponPiece(script); case SBARINFO_WEAPONAMMO: return new CommandWeaponAmmo(script); case SBARINFO_ININVENTORY: return new CommandInInventory(script); + case SBARINFO_ALPHA: return new CommandAlpha(script); } sc.ScriptError("Unknown command '%s'.\n", sc.String); diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index 1bbe490ca..beb459cda 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -175,7 +175,7 @@ void ST_LoadCrosshair(bool alwaysload) // //--------------------------------------------------------------------------- -DBaseStatusBar::DBaseStatusBar (int reltop) +DBaseStatusBar::DBaseStatusBar (int reltop, int hres, int vres) { Centering = false; FixedOrigin = false; @@ -185,6 +185,8 @@ DBaseStatusBar::DBaseStatusBar (int reltop) Displacement = 0; CPlayer = NULL; ShowLog = false; + HorizontalResolution = hres; + VirticalResolution = vres; SetScaled (st_scale); } @@ -216,18 +218,20 @@ void DBaseStatusBar::Destroy () //--------------------------------------------------------------------------- //[BL] Added force argument to have forcescaled mean forcescaled. +// - Also, if the VirticalResolution is something other than the default (200) +// We should always obey the value of scale. void DBaseStatusBar::SetScaled (bool scale, bool force) { - Scaled = (RelTop != 0 || force) && (SCREENWIDTH != 320 && scale); + Scaled = (RelTop != 0 || force) && ((SCREENWIDTH != 320 || HorizontalResolution != 320) && scale); if (!Scaled) { - ST_X = (SCREENWIDTH - 320) / 2; + ST_X = (SCREENWIDTH - HorizontalResolution) / 2; ST_Y = SCREENHEIGHT - RelTop; ::ST_Y = ST_Y; if (RelTop > 0) { - Displacement = ((ST_Y * 200 / SCREENHEIGHT) - (200 - RelTop))*FRACUNIT/RelTop; + Displacement = ((ST_Y * VirticalResolution / SCREENHEIGHT) - (VirticalResolution - RelTop))*FRACUNIT/RelTop; } else { @@ -237,14 +241,14 @@ void DBaseStatusBar::SetScaled (bool scale, bool force) else { ST_X = 0; - ST_Y = 200 - RelTop; + ST_Y = VirticalResolution - RelTop; if (CheckRatio(SCREENWIDTH, SCREENHEIGHT) != 4) { // Normal resolution - ::ST_Y = Scale (ST_Y, SCREENHEIGHT, 200); + ::ST_Y = Scale (ST_Y, SCREENHEIGHT, VirticalResolution); } else { // 5:4 resolution - ::ST_Y = Scale(ST_Y - 100, SCREENHEIGHT*3, BaseRatioSizes[4][1]) + SCREENHEIGHT/2 + ::ST_Y = Scale(ST_Y - VirticalResolution/2, SCREENHEIGHT*3, Scale(VirticalResolution, BaseRatioSizes[4][1], 200)) + SCREENHEIGHT/2 + (SCREENHEIGHT - SCREENHEIGHT * BaseRatioSizes[4][3] / 48) / 2; } Displacement = 0; @@ -1000,7 +1004,7 @@ void DBaseStatusBar::RefreshBackground () const if (x > 0) { y = x == ST_X ? ST_Y : ::ST_Y; - x2 = !(ratio & 3) || !Scaled ? ST_X+320 : + x2 = !(ratio & 3) || !Scaled ? ST_X+HorizontalResolution : SCREENWIDTH - (SCREENWIDTH*(48-BaseRatioSizes[ratio][3])+48*2-1)/(48*2); R_DrawBorder (0, y, x, SCREENHEIGHT); R_DrawBorder (x2, y, SCREENWIDTH, SCREENHEIGHT); @@ -1500,18 +1504,23 @@ void DBaseStatusBar::BlendView (float blend[4]) if (CPlayer->bonuscount) { cnt = CPlayer->bonuscount << 3; - AddBlend (0.8431f, 0.7333f, 0.2706f, cnt > 128 ? 0.5f : cnt / 255.f, blend); + + AddBlend (RPART(gameinfo.pickupcolor)/255.f, GPART(gameinfo.pickupcolor)/255.f, + BPART(gameinfo.pickupcolor)/255.f, cnt > 128 ? 0.5f : cnt / 255.f, blend); } - cnt = DamageToAlpha[MIN (113, CPlayer->damagecount)]; - - if (cnt) + if (CPlayer->mo->DamageFade.a != 0) { - if (cnt > 228) - cnt = 228; + cnt = DamageToAlpha[MIN (113, CPlayer->damagecount * CPlayer->mo->DamageFade.a / 255)]; + + if (cnt) + { + if (cnt > 228) + cnt = 228; - APlayerPawn *mo = players[consoleplayer].mo; - AddBlend (mo->DamageFade.r / 255.f, mo->DamageFade.g / 255.f, mo->DamageFade.b / 255.f, cnt / 255.f, blend); + APlayerPawn *mo = CPlayer->mo; + AddBlend (mo->DamageFade.r / 255.f, mo->DamageFade.g / 255.f, mo->DamageFade.b / 255.f, cnt / 255.f, blend); + } } // Unlike Doom, I did not have any utility source to look at to find the @@ -1591,15 +1600,6 @@ void DBaseStatusBar::FlashItem (const PClass *itemtype) { } -void DBaseStatusBar::SetFace (void *skn) -{ -} - -void DBaseStatusBar::AddFaceToImageCollection (void *skn, FImageCollection *images) -{ - AddFaceToImageCollectionActual (skn, images, false); -} - void DBaseStatusBar::NewGame () { } @@ -1635,72 +1635,6 @@ void DBaseStatusBar::ScreenSizeChanged () } } -//--------------------------------------------------------------------------- -// -// AddFaceToImageCollectionActual -// -// Adds face graphics for specified skin to the specified image collection. -// If not in DOOM statusbar and no face in current skin, do NOT default STF* -// -//--------------------------------------------------------------------------- - -void DBaseStatusBar::AddFaceToImageCollectionActual (void *skn, FImageCollection *images, bool isDoom) -{ - const char *nameptrs[ST_NUMFACES]; - char names[ST_NUMFACES][9]; - char prefix[4]; - int i, j; - int namespc; - int facenum; - FPlayerSkin *skin = (FPlayerSkin *)skn; - - if ((skin->face[0] == 0) && !isDoom) - { - return; - } - - for (i = 0; i < ST_NUMFACES; i++) - { - nameptrs[i] = names[i]; - } - - if (skin->face[0] != 0) - { - prefix[0] = skin->face[0]; - prefix[1] = skin->face[1]; - prefix[2] = skin->face[2]; - prefix[3] = 0; - namespc = skin->namespc; - } - else - { - prefix[0] = 'S'; - prefix[1] = 'T'; - prefix[2] = 'F'; - prefix[3] = 0; - namespc = ns_global; - } - - facenum = 0; - - for (i = 0; i < ST_NUMPAINFACES; i++) - { - for (j = 0; j < ST_NUMSTRAIGHTFACES; j++) - { - mysnprintf (names[facenum++], countof(names[0]), "%sST%d%d", prefix, i, j); - } - mysnprintf (names[facenum++], countof(names[0]), "%sTR%d0", prefix, i); // turn right - mysnprintf (names[facenum++], countof(names[0]), "%sTL%d0", prefix, i); // turn left - mysnprintf (names[facenum++], countof(names[0]), "%sOUCH%d", prefix, i); // ouch! - mysnprintf (names[facenum++], countof(names[0]), "%sEVL%d", prefix, i); // evil grin ;) - mysnprintf (names[facenum++], countof(names[0]), "%sKILL%d", prefix, i); // pissed off - } - mysnprintf (names[facenum++], countof(names[0]), "%sGOD0", prefix); - mysnprintf (names[facenum++], countof(names[0]), "%sDEAD0", prefix); - - images->Add (nameptrs, ST_NUMFACES, namespc); -} - //--------------------------------------------------------------------------- // // ValidateInvFirst diff --git a/src/g_skill.cpp b/src/g_skill.cpp index a91e38264..9a07e99f8 100644 --- a/src/g_skill.cpp +++ b/src/g_skill.cpp @@ -35,12 +35,14 @@ #include #include "doomstat.h" +#include "d_player.h" #include "g_level.h" #include "g_game.h" #include "gi.h" #include "templates.h" #include "v_font.h" #include "m_fixed.h" +#include "gstrings.h" TArray AllSkills; int DefaultSkill = -1; @@ -70,7 +72,6 @@ void FMapInfoParser::ParseSkill () skill.Aggressiveness = FRACUNIT; skill.SpawnFilter = 0; skill.ACSReturn = 0; - skill.MenuNameIsLump = false; skill.MustConfirm = false; skill.Shortcut = 0; skill.TextColor = ""; @@ -79,6 +80,7 @@ void FMapInfoParser::ParseSkill () skill.MonsterHealth = FRACUNIT; skill.FriendlyHealth = FRACUNIT; skill.NoPain = false; + skill.ArmorFactor = FRACUNIT; sc.MustGetString(); skill.Name = sc.String; @@ -185,7 +187,6 @@ void FMapInfoParser::ParseSkill () ParseAssign(); sc.MustGetString (); skill.MenuName = sc.String; - skill.MenuNameIsLump = false; } else if (sc.Compare("PlayerClassName")) { @@ -200,8 +201,7 @@ void FMapInfoParser::ParseSkill () { ParseAssign(); sc.MustGetString (); - skill.MenuName = sc.String; - skill.MenuNameIsLump = true; + skill.PicName = sc.String; } else if (sc.Compare("MustConfirm")) { @@ -250,6 +250,12 @@ void FMapInfoParser::ParseSkill () { skill.NoPain = true; } + else if (sc.Compare("ArmorFactor")) + { + ParseAssign(); + sc.MustGetFloat(); + skill.ArmorFactor = FLOAT2FIXED(sc.Float); + } else if (sc.Compare("DefaultSkill")) { if (DefaultSkill >= 0) @@ -358,12 +364,38 @@ int G_SkillProperty(ESkillProperty prop) return AllSkills[gameskill].FriendlyHealth; case SKILLP_NoPain: - return AllSkills[gameskill].NoPain; + return AllSkills[gameskill].NoPain; + + case SKILLP_ArmorFactor: + return AllSkills[gameskill].ArmorFactor; } } return 0; } +//========================================================================== +// +// +// +//========================================================================== + +const char * G_SkillName() +{ + const char *name = AllSkills[gameskill].MenuName; + + player_t *player = &players[consoleplayer]; + const char *playerclass = player->mo->GetClass()->DisplayName; + + if (playerclass != NULL) + { + FString * pmnm = AllSkills[gameskill].MenuNamesForPlayerClass.CheckKey(playerclass); + if (pmnm != NULL) name = *pmnm; + } + + if (*name == '$') name = GStrings(name+1); + return name; +} + //========================================================================== // @@ -402,8 +434,8 @@ FSkillInfo &FSkillInfo::operator=(const FSkillInfo &other) SpawnFilter = other.SpawnFilter; ACSReturn = other.ACSReturn; MenuName = other.MenuName; + PicName = other.PicName; MenuNamesForPlayerClass = other.MenuNamesForPlayerClass; - MenuNameIsLump = other.MenuNameIsLump; MustConfirm = other.MustConfirm; MustConfirmText = other.MustConfirmText; Shortcut = other.Shortcut; @@ -413,6 +445,7 @@ FSkillInfo &FSkillInfo::operator=(const FSkillInfo &other) MonsterHealth = other.MonsterHealth; FriendlyHealth = other.FriendlyHealth; NoPain = other.NoPain; + ArmorFactor = other.ArmorFactor; return *this; } diff --git a/src/g_strife/a_alienspectres.cpp b/src/g_strife/a_alienspectres.cpp index 0e79642ad..348bab9c1 100644 --- a/src/g_strife/a_alienspectres.cpp +++ b/src/g_strife/a_alienspectres.cpp @@ -93,7 +93,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_AlienSpectreDeath) int log; int i; - CALL_ACTION(A_NoBlocking, self); // [RH] Need this for Sigil rewarding + A_Unblock(self, true); // [RH] Need this for Sigil rewarding if (!CheckBossDeath (self)) { return 0; diff --git a/src/g_strife/a_strifeweapons.cpp b/src/g_strife/a_strifeweapons.cpp index da345f692..0004fd0cb 100644 --- a/src/g_strife/a_strifeweapons.cpp +++ b/src/g_strife/a_strifeweapons.cpp @@ -112,7 +112,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_JabDagger) angle = self->angle + (pr_jabdagger.Random2() << 18); pitch = P_AimLineAttack (self, angle, 80*FRACUNIT, &linetarget); - P_LineAttack (self, angle, 80*FRACUNIT, pitch, damage, NAME_Melee, "StrifeSpark", true); + P_LineAttack (self, angle, 80*FRACUNIT, pitch, damage, NAME_Melee, "StrifeSpark", true, &linetarget); // turn to face target if (linetarget) diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 936d4e7f7..acf2bbad1 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -399,29 +399,38 @@ void FGameConfigFile::DoGameSetup (const char *gamename) ReadCVars (0); } - strncpy (subsection, "Bindings", sublen); - if (!SetSection (section)) - { // Config has no bindings for the given game - if (!bMigrating) - { - C_SetDefaultBindings (); - } - } - else + if (!bMigrating) { - C_UnbindAll (); + C_SetDefaultBindings (); + } + + strncpy (subsection, "Bindings", sublen); + if (SetSection (section)) + { + Bindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, false); + Bindings.DoBind (key, value); } } strncpy (subsection, "DoubleBindings", sublen); if (SetSection (section)) { + DoubleBindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, true); + DoubleBindings.DoBind (key, value); + } + } + + strncpy (subsection, "AutomapBindings", sublen); + if (SetSection (section)) + { + AutomapBindings.UnbindAll(); + while (NextInSection (key, value)) + { + AutomapBindings.DoBind (key, value); } } @@ -512,11 +521,15 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) strcpy (subsection, "Bindings"); SetSection (section, true); - C_ArchiveBindings (this, false); + Bindings.ArchiveBindings (this); strncpy (subsection, "DoubleBindings", sublen); SetSection (section, true); - C_ArchiveBindings (this, true); + DoubleBindings.ArchiveBindings (this); + + strncpy (subsection, "AutomapBindings", sublen); + SetSection (section, true); + AutomapBindings.ArchiveBindings (this); } void FGameConfigFile::ArchiveGlobalData () diff --git a/src/gi.cpp b/src/gi.cpp index e0813479a..03fea70a9 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -266,6 +266,7 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_INT(defKickback, "defKickback") GAMEINFOKEY_CSTRING(SkyFlatName, "SkyFlatName", 8) GAMEINFOKEY_STRING(translator, "translator") + GAMEINFOKEY_COLOR(pickupcolor, "pickupcolor") GAMEINFOKEY_COLOR(defaultbloodcolor, "defaultbloodcolor") GAMEINFOKEY_COLOR(defaultbloodparticlecolor, "defaultbloodparticlecolor") GAMEINFOKEY_STRING(backpacktype, "backpacktype") diff --git a/src/gi.h b/src/gi.h index 0ada094c2..cbb1a02bb 100644 --- a/src/gi.h +++ b/src/gi.h @@ -43,6 +43,9 @@ #define GI_MENUHACK_EXTENDED 0x00000004 // (Heretic) #define GI_TEASER2 0x00000008 // Alternate version of the Strife Teaser #define GI_COMPATSHORTTEX 0x00000010 // always force COMPAT_SHORTTEX for IWAD maps. +#define GI_COMPATSTAIRS 0x00000020 // same for stairbuilding +#define GI_COMPATPOLY1 0x00000040 // Hexen's MAP36 needs old polyobject drawing +#define GI_COMPATPOLY2 0x00000080 // so does HEXDD's MAP47 #include "gametype.h" @@ -105,6 +108,7 @@ struct gameinfo_t int defaultrespawntime; int defaultdropstyle; int player5start; + DWORD pickupcolor; const char *GetFinalePage(unsigned int num) const; }; diff --git a/src/i_net.cpp b/src/i_net.cpp index 633c75d1a..ae28bb779 100644 --- a/src/i_net.cpp +++ b/src/i_net.cpp @@ -269,8 +269,16 @@ void PacketGet (void) if (err == WSAECONNRESET) { // The remote node aborted unexpectedly, so pretend it sent an exit packet - StartScreen->NetMessage ("The connection from %s was dropped.\n", - players[sendplayer[node]].userinfo.netname); + if (StartScreen != NULL) + { + StartScreen->NetMessage ("The connection from %s was dropped.\n", + players[sendplayer[node]].userinfo.netname); + } + else + { + Printf("The connection from %s was dropped.\n", + players[sendplayer[node]].userinfo.netname); + } doomcom.data[0] = 0x80; // NCMD_EXIT c = 1; diff --git a/src/info.cpp b/src/info.cpp index 1b67189cf..838acd7bf 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -134,6 +134,10 @@ void PClassActor::StaticInit() // Sprite 1 is always ---- memcpy (temp.name, "----", 5); sprites.Push (temp); + + // Sprite 2 is always #### + memcpy (temp.name, "####", 5); + sprites.Push (temp); } Printf ("LoadActors: Load actor definitions.\n"); @@ -625,7 +629,6 @@ CCMD (dumpmapthings) FDoomEdMap::DumpMapThings (); } -bool CheckCheatmode (); static void SummonActor (int command, int command2, FCommandLine argv) { diff --git a/src/info.h b/src/info.h index 54ff8eb63..24e752427 100644 --- a/src/info.h +++ b/src/info.h @@ -46,20 +46,28 @@ #include "vm.h" #include "s_sound.h" -const BYTE SF_FULLBRIGHT = 0x40; - struct Baggage; class FScanner; struct FActorInfo; class FArchive; +// Sprites that are fixed in position because they can have special meanings. +enum +{ + SPR_TNT1, // The empty sprite + SPR_FIXED, // Do not change sprite or frame + SPR_NOCHANGE, // Do not change sprite (frame change is okay) +}; + struct FState { WORD sprite; SWORD Tics; - long Misc1; // Was changed to SBYTE, reverted to long for MBF compat - long Misc2; // Was changed to BYTE, reverted to long for MBF compat - BYTE Frame; + int Misc1; // Was changed to SBYTE, reverted to long for MBF compat + int Misc2; // Was changed to BYTE, reverted to long for MBF compat + BYTE Frame:6; + BYTE Fullbright:1; // State is fullbright + BYTE SameFrame:1; // Ignore Frame (except when spawning actor) BYTE DefineFlags; // Unused byte so let's use it during state creation. short Light; FState *NextState; @@ -67,11 +75,15 @@ struct FState inline int GetFrame() const { - return Frame & ~(SF_FULLBRIGHT); + return Frame; + } + inline bool GetSameFrame() const + { + return SameFrame; } inline int GetFullbright() const { - return Frame & SF_FULLBRIGHT ? 0x10 /*RF_FULLBRIGHT*/ : 0; + return Fullbright ? 0x10 /*RF_FULLBRIGHT*/ : 0; } inline int GetTics() const { @@ -91,7 +103,7 @@ struct FState } inline void SetFrame(BYTE frame) { - Frame = (Frame & SF_FULLBRIGHT) | (frame-'A'); + Frame = frame - 'A'; } void SetAction(VMFunction *func) { ActionFunc = func; } bool CallAction(AActor *self, AActor *stateowner, StateCallData *statecall = NULL); @@ -185,6 +197,7 @@ public: FName BloodType3; // AxeBlood replacement type DDropItem *DropItems; + FString SourceLumpName; // Old Decorate compatibility stuff bool DontHurtShooter; diff --git a/src/m_argv.cpp b/src/m_argv.cpp index 5dbdc4d3d..e414ca367 100644 --- a/src/m_argv.cpp +++ b/src/m_argv.cpp @@ -302,6 +302,19 @@ void DArgs::AppendArgs(int argc, const FString *argv) } } +//=========================================================================== +// +// DArgs :: RemoveArg +// +// Removes a single argument from argv. +// +//=========================================================================== + +void DArgs::RemoveArg(int argindex) +{ + Argv.Delete(argindex); +} + //=========================================================================== // // DArgs :: CollectFiles diff --git a/src/m_argv.h b/src/m_argv.h index b2a432edb..8b4fbf2dc 100644 --- a/src/m_argv.h +++ b/src/m_argv.h @@ -53,6 +53,7 @@ public: void AppendArg(FString arg); void AppendArgs(int argc, const FString *argv); + void RemoveArg(int argindex); void SetArgs(int argc, char **argv); void CollectFiles(const char *param, const char *extension); DArgs *GatherFiles(const char *param) const; diff --git a/src/m_bbox.cpp b/src/m_bbox.cpp index d13f790e6..6d3a5b744 100644 --- a/src/m_bbox.cpp +++ b/src/m_bbox.cpp @@ -91,8 +91,3 @@ int FBoundingBox::BoxOnLineSide (const line_t *ld) const return (p1 == p2) ? p1 : -1; } - - - - - diff --git a/src/m_bbox.h b/src/m_bbox.h index 7c7221688..febf9d2a2 100644 --- a/src/m_bbox.h +++ b/src/m_bbox.h @@ -25,6 +25,7 @@ #include "doomtype.h" struct line_t; +struct node_t; class FBoundingBox { diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 64f2c4535..f34ca3a4a 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -601,7 +601,7 @@ void GiveSpawner (player_t *player, PClassInventory *type, int amount) void cht_Give (player_t *player, const char *name, int amount) { - bool giveall; + enum { ALL_NO, ALL_YES, ALL_YESYES } giveall; int i; PClassActor *type; @@ -613,9 +613,17 @@ void cht_Give (player_t *player, const char *name, int amount) return; } - giveall = (stricmp (name, "all") == 0); + giveall = ALL_NO; + if (stricmp (name, "all") == 0) + { + giveall = ALL_YES; + } + else if (stricmp (name, "everything") == 0) + { + giveall = ALL_YESYES; + } - if (giveall || stricmp (name, "health") == 0) + if (stricmp (name, "health") == 0) { if (amount > 0) { @@ -640,9 +648,6 @@ void cht_Give (player_t *player, const char *name, int amount) player->health = deh.GodHealth; } } - - if (!giveall) - return; } if (giveall || stricmp (name, "backpack") == 0) @@ -757,7 +762,7 @@ void cht_Give (player_t *player, const char *name, int amount) player->weapons.LocateWeapon(static_cast(type), NULL, NULL)) { AWeapon *def = (AWeapon*)GetDefaultByType (type); - if (!(def->WeaponFlags & WIF_CHEATNOTWEAPON)) + if (giveall == ALL_YESYES || !(def->WeaponFlags & WIF_CHEATNOTWEAPON)) { GiveSpawner (player, static_cast(type), 1); } @@ -783,7 +788,7 @@ void cht_Give (player_t *player, const char *name, int amount) !type->IsDescendantOf (RUNTIME_CLASS(APowerup)) && !type->IsDescendantOf (RUNTIME_CLASS(AArmor))) { - GiveSpawner (player, static_cast(type), 1); + GiveSpawner (player, static_cast(type), amount <= 0 ? def->MaxAmount : amount); } } } @@ -801,7 +806,7 @@ void cht_Give (player_t *player, const char *name, int amount) AInventory *def = (AInventory*)GetDefaultByType (type); if (def->Icon.isValid()) { - GiveSpawner (player, static_cast(type), 1); + GiveSpawner (player, static_cast(type), amount <= 0 ? def->MaxAmount : amount); } } } @@ -1063,7 +1068,6 @@ void cht_Suicide (player_t *plyr) } } -bool CheckCheatmode (); CCMD (mdk) { diff --git a/src/m_menu.cpp b/src/m_menu.cpp index 5d0bdaf2d..1dac39080 100644 --- a/src/m_menu.cpp +++ b/src/m_menu.cpp @@ -196,6 +196,7 @@ static void M_ChangeClass (int choice); static void M_ChangeGender (int choice); static void M_ChangeSkin (int choice); static void M_ChangeAutoAim (int choice); +static void M_ChangeSwitchPickup (int choice); static void PickPlayerClass (); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -203,6 +204,8 @@ static void PickPlayerClass (); EXTERN_CVAR (String, playerclass) EXTERN_CVAR (String, name) EXTERN_CVAR (Int, team) +EXTERN_CVAR(Bool, neverswitchonpickup) +EXTERN_CVAR(Float, snd_menuvolume) extern bool sendpause; extern int flagsvar; @@ -391,7 +394,7 @@ oldmenuitem_t EpisodeMenu[MAX_EPISODES] = {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, }; -char EpisodeMaps[MAX_EPISODES][8]; +char EpisodeMaps[MAX_EPISODES][9]; bool EpisodeNoSkill[MAX_EPISODES]; oldmenu_t EpiDef = @@ -477,9 +480,16 @@ void M_StartupSkillMenu(const char *playerclass) { FSkillInfo &skill = AllSkills[i]; - SkillSelectMenu[i].name = skill.MenuName; - SkillSelectMenu[i].fulltext = !skill.MenuNameIsLump; - SkillSelectMenu[i].alphaKey = skill.MenuNameIsLump? skill.Shortcut : tolower(SkillSelectMenu[i].name[0]); + if (skill.PicName.Len() != 0) + { + SkillSelectMenu[i].name = skill.PicName; + SkillSelectMenu[i].fulltext = false; + } + else + { + SkillSelectMenu[i].name = skill.MenuName; + SkillSelectMenu[i].fulltext = true; + } SkillSelectMenu[i].textcolor = skill.GetTextColor(); SkillSelectMenu[i].alphaKey = skill.Shortcut; @@ -538,7 +548,8 @@ static oldmenuitem_t PlayerSetupMenu[] = { 2,0,'t',NULL,M_ChangeClass, CR_UNTRANSLATED}, { 2,0,'s',NULL,M_ChangeSkin, CR_UNTRANSLATED}, { 2,0,'e',NULL,M_ChangeGender, CR_UNTRANSLATED}, - { 2,0,'a',NULL,M_ChangeAutoAim, CR_UNTRANSLATED} + { 2,0,'a',NULL,M_ChangeAutoAim, CR_UNTRANSLATED}, + { 2,0,'p',NULL,M_ChangeSwitchPickup, CR_UNTRANSLATED} }; enum @@ -653,28 +664,28 @@ CCMD (menu_help) CCMD (quicksave) { // F6 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuickSave(); } CCMD (quickload) { // F9 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuickLoad(); } CCMD (menu_endgame) { // F7 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_EndGame(0); } CCMD (menu_quit) { // F10 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuitGame(0); } @@ -1286,6 +1297,9 @@ void M_DrawFrame (int left, int top, int width, int height) { FTexture *p; const gameborder_t *border = gameinfo.border; + // Sanity check for incomplete gameinfo + if (border == NULL) + return; int offset = border->offset; int right = left + width; int bottom = top + height; @@ -1426,7 +1440,7 @@ void M_QuickSaveResponse (int ch) if (ch == 'y') { M_DoSave (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } } @@ -1434,7 +1448,7 @@ void M_QuickSave () { if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -1465,7 +1479,7 @@ void M_QuickLoadResponse (int ch) if (ch == 'y') { M_LoadSelect (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } } @@ -1968,7 +1982,7 @@ void M_EndGame(int choice) choice = 0; if (!usergame) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -2029,7 +2043,7 @@ void M_QuitResponse(int ch) { if (gameinfo.quitSound.IsNotEmpty()) { - S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); I_WaitVBL (105); } } @@ -2350,6 +2364,13 @@ static void M_PlayerSetupDrawer () autoaim <= 2 ? "High" : autoaim <= 3 ? "Very High" : "Always", DTA_Clean, true, TAG_DONE); + + // Draw Switch on Pickup setting + x = SmallFont->StringWidth ("Switch on Pickup") + 8 + PSetupDef.x; + screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*9+yo, "Switch on Pickup", DTA_Clean, true, TAG_DONE); + screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*9+yo, + neverswitchonpickup == false ? "Yes" : "No", + DTA_Clean, true, TAG_DONE); } // A 32x32 cloud rendered with Photoshop, plus some other filters @@ -2657,6 +2678,14 @@ static void M_ChangeAutoAim (int choice) autoaim = aim; } +static void M_ChangeSwitchPickup (int choice) +{ + if (!choice) + neverswitchonpickup = (neverswitchonpickup == 1) ? 0 : 1; + else + neverswitchonpickup = (neverswitchonpickup == 0) ? 1 : 0; +} + static void M_EditPlayerName (int choice) { // we are going to be intercepting all chars @@ -2850,7 +2879,7 @@ void M_StartMessage (const char *string, void (*routine)(int)) if (messageRoutine != NULL) { S_StopSound (CHAN_VOICE); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", snd_menuvolume, ATTN_NONE); } return; } @@ -2869,7 +2898,7 @@ void M_EndMessage(int key) } SB_state = screen->GetPageCount(); // refresh the status bar BorderNeedRefresh = screen->GetPageCount(); - S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } @@ -3072,7 +3101,7 @@ bool M_Responder (event_t *ev) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; - S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); return true; } } @@ -3308,7 +3337,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (itemOn + 1 >= currentMenu->numitems) itemOn = 0; else itemOn++; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; @@ -3318,7 +3347,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (itemOn == 0) itemOn = currentMenu->numitems - 1; else itemOn--; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; @@ -3326,7 +3355,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); currentMenu->menuitems[itemOn].routine(0); } break; @@ -3335,7 +3364,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); currentMenu->menuitems[itemOn].routine(1); } break; @@ -3348,12 +3377,12 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].status == 2) { currentMenu->menuitems[itemOn].routine(1); // right arrow - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else { currentMenu->menuitems[itemOn].routine(itemOn); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); } } break; @@ -3573,7 +3602,7 @@ void M_StartControlPanel (bool makeSound, bool wantTop) if (makeSound) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); } } @@ -3917,12 +3946,12 @@ void M_PopMenuStack (void) } drawSkull = MenuStack[MenuStackDepth].drawSkull; ++MenuStackDepth; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", snd_menuvolume, ATTN_NONE); } else { M_ClearMenus (); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, ATTN_NONE); } } diff --git a/src/m_menu.h b/src/m_menu.h index f46b6a9e9..5653b694a 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -116,6 +116,7 @@ typedef enum { joy_slider, joy_map, joy_inverter, + mapcontrol, } itemtype; struct IJoystickConfig; @@ -273,7 +274,7 @@ extern int CurrentItem; extern oldmenuitem_t EpisodeMenu[MAX_EPISODES]; extern bool EpisodeNoSkill[MAX_EPISODES]; -extern char EpisodeMaps[MAX_EPISODES][8]; +extern char EpisodeMaps[MAX_EPISODES][9]; extern oldmenu_t EpiDef; #endif diff --git a/src/m_misc.cpp b/src/m_misc.cpp index c828a965a..324095d5c 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -150,40 +150,54 @@ int M_ReadFile (char const *name, BYTE **buffer) void M_FindResponseFile (void) { - int i; + const int limit = 100; // avoid infinite recursion + int added_stuff = 0; + int i = 1; - for (i = 1; i < Args->NumArgs(); i++) + while (i < Args->NumArgs()) { - if (Args->GetArg(i)[0] == '@') + if (Args->GetArg(i)[0] != '@') + { + i++; + } + else { char **argv; - char *file; - int argc; + char *file = NULL; + int argc = 0; FILE *handle; int size; - long argsize; - int k; + long argsize = 0; int index; - // READ THE RESPONSE FILE INTO MEMORY - handle = fopen (Args->GetArg(i) + 1,"rb"); - if (!handle) - { // [RH] Make this a warning, not an error. - Printf ("No such response file (%s)!", Args->GetArg(i) + 1); - continue; + // Any more response files after the limit will be removed from the + // command line. + if (added_stuff < limit) + { + // READ THE RESPONSE FILE INTO MEMORY + handle = fopen (Args->GetArg(i) + 1,"rb"); + if (!handle) + { // [RH] Make this a warning, not an error. + Printf ("No such response file (%s)!\n", Args->GetArg(i) + 1); + } + else + { + Printf ("Found response file %s!\n", Args->GetArg(i) + 1); + fseek (handle, 0, SEEK_END); + size = ftell (handle); + fseek (handle, 0, SEEK_SET); + file = new char[size+1]; + fread (file, size, 1, handle); + file[size] = 0; + fclose (handle); + + argsize = ParseCommandLine (file, &argc, NULL); + } + } + else + { + Printf ("Ignored response file %s.\n", Args->GetArg(i) + 1); } - - Printf ("Found response file %s!\n", Args->GetArg(i) + 1); - fseek (handle, 0, SEEK_END); - size = ftell (handle); - fseek (handle, 0, SEEK_SET); - file = new char[size+1]; - fread (file, size, 1, handle); - file[size] = 0; - fclose (handle); - - argsize = ParseCommandLine (file, &argc, NULL); - argc = Args->NumArgs() - 1; if (argc != 0) { @@ -199,27 +213,39 @@ void M_FindResponseFile (void) newargs->AppendArg(Args->GetArg(index)); // Copy parameters from response file. - for (index = 0; index < argc; ++i) + for (index = 0; index < argc; ++index) newargs->AppendArg(argv[index]); // Copy parameters after response file. - for (index = i + 1, i = newargs->NumArgs(); index < Args->NumArgs(); ++index) + for (index = i + 1; index < Args->NumArgs(); ++index) newargs->AppendArg(Args->GetArg(index)); // Use the new argument vector as the global Args object. Args = newargs; + if (++added_stuff == limit) + { + Printf("Response file limit of %d hit.\n", limit); + } + } + else + { + // Remove the response file from the Args object + Args->RemoveArg(i); + } + if (file != NULL) + { + delete[] file; } - - delete[] file; - - // DISPLAY ARGS - Printf ("%d command-line args:\n", Args->NumArgs ()); - for (k = 1; k < Args->NumArgs (); k++) - Printf ("%s\n", Args->GetArg (k)); - - break; } } + if (added_stuff > 0) + { + // DISPLAY ARGS + Printf ("Added %d response file%s, now have %d command-line args:\n", + added_stuff, added_stuff > 1 ? "s" : "", Args->NumArgs ()); + for (int k = 1; k < Args->NumArgs (); k++) + Printf ("%s\n", Args->GetArg (k)); + } } // ParseCommandLine diff --git a/src/m_options.cpp b/src/m_options.cpp index 9c8199757..fc5f1b7bb 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -93,13 +93,14 @@ EXTERN_CVAR(Int, showendoom) EXTERN_CVAR(Bool, hud_althud) EXTERN_CVAR(Int, compatmode) EXTERN_CVAR (Bool, vid_vsync) -EXTERN_CVAR(Bool, displaynametags) +EXTERN_CVAR(Int, displaynametags) EXTERN_CVAR (Int, snd_channels) // // defaulted values // CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) // Show messages has default, 0 = off, 1 = on CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -112,6 +113,7 @@ EXTERN_CVAR (Int, crosshair) EXTERN_CVAR (Bool, freelook) EXTERN_CVAR (Int, sv_smartaim) EXTERN_CVAR (Int, am_colorset) +EXTERN_CVAR (Bool, am_showkeys) EXTERN_CVAR (Int, vid_aspect) static void CalcIndent (menu_t *menu); @@ -149,13 +151,14 @@ value_t OffOn[2] = { { 1.0, "Off" } }; -value_t CompatModes[6] = { +value_t CompatModes[] = { { 0.0, "Default" }, { 1.0, "Doom" }, { 2.0, "Doom (strict)" }, { 3.0, "Boom" }, - { 4.0, "ZDoom 2.0.63" }, + { 6.0, "Boom (strict)" }, { 5.0, "MBF" }, + { 4.0, "ZDoom 2.0.63" }, }; menu_t *CurrentMenu; @@ -201,6 +204,7 @@ static menu_t ConfirmMenu = { * *=======================================*/ +static void StartAutomapMenu (void); static void CustomizeControls (void); static void GameplayOptions (void); static void CompatibilityOptions (void); @@ -225,6 +229,7 @@ static menuitem_t OptionItems[] = { more, "Player Setup", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_PlayerSetup} }, { more, "Gameplay Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)GameplayOptions} }, { more, "Compatibility Options",{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)CompatibilityOptions} }, + { more, "Automap Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartAutomapMenu} }, { more, "Sound Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SoundOptions} }, { more, "Display Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)VideoOptions} }, { more, "Set video mode", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SetVidMode} }, @@ -416,13 +421,13 @@ menu_t ControlsMenu = 2, }; + /*======================================= * * Display Options Menu * *=======================================*/ static void StartMessagesMenu (void); -static void StartAutomapMenu (void); static void StartScoreboardMenu (void); static void InitCrosshairsList(); @@ -437,6 +442,7 @@ EXTERN_CVAR (Int, wipetype) EXTERN_CVAR (Bool, vid_palettehack) EXTERN_CVAR (Bool, vid_attachedsurfaces) EXTERN_CVAR (Int, screenblocks) +EXTERN_CVAR (Int, r_fakecontrast) static TArray Crosshairs; @@ -476,9 +482,21 @@ static value_t Endoom[] = { { 2.0, "Only modified" } }; +static value_t Contrast[] = { + { 0.0, "Off" }, + { 1.0, "On" }, + { 2.0, "Smooth" } +}; + +static value_t DisplayTagsTypes[] = { + { 0.0, "None" }, + { 1.0, "Items" }, + { 2.0, "Weapons" }, + { 3.0, "Both" } +}; + static menuitem_t VideoItems[] = { { more, "Message Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartMessagesMenu} }, - { more, "Automap Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartAutomapMenu} }, { more, "Scoreboard Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartScoreboardMenu} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { slider, "Screen size", {&screenblocks}, {3.0}, {12.0}, {1.0}, {NULL} }, @@ -497,13 +515,14 @@ static menuitem_t VideoItems[] = { #endif { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Use fuzz effect", {&r_drawfuzz}, {2.0}, {0.0}, {0.0}, {YesNo} }, + { discrete, "Use fake contrast", {&r_fakecontrast}, {3.0}, {0.0}, {0.0}, {Contrast} }, { discrete, "Rocket Trails", {&cl_rockettrails}, {4.0}, {0.0}, {0.0}, {RocketTrailTypes} }, { discrete, "Blood Type", {&cl_bloodtype}, {3.0}, {0.0}, {0.0}, {BloodTypes} }, { discrete, "Bullet Puff Type", {&cl_pufftype}, {2.0}, {0.0}, {0.0}, {PuffTypes} }, - { discrete, "Display nametags", {&displaynametags}, {2.0}, {0.0}, {0.0}, {YesNo} }, + { discrete, "Display nametags", {&displaynametags}, {4.0}, {0.0}, {0.0}, {DisplayTagsTypes} }, }; -#define CROSSHAIR_INDEX 7 +#define CROSSHAIR_INDEX 6 menu_t VideoMenu = { @@ -520,6 +539,7 @@ menu_t VideoMenu = * *=======================================*/ static void StartMapColorsMenu (void); +static void StartMapControlsMenu (void); EXTERN_CVAR (Int, am_rotate) EXTERN_CVAR (Int, am_overlay) @@ -530,6 +550,7 @@ EXTERN_CVAR (Bool, am_showtime) EXTERN_CVAR (Int, am_map_secrets) EXTERN_CVAR (Bool, am_showtotaltime) EXTERN_CVAR (Bool, am_drawmapback) +EXTERN_CVAR (Bool, am_textured) static value_t MapColorTypes[] = { { 0, "Custom" }, @@ -559,9 +580,11 @@ static value_t OverlayTypes[] = { static menuitem_t AutomapItems[] = { { discrete, "Map color set", {&am_colorset}, {4.0}, {0.0}, {0.0}, {MapColorTypes} }, { more, "Set custom colors", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t*)StartMapColorsMenu} }, + { more, "Customize map controls", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t*)StartMapControlsMenu} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Rotate automap", {&am_rotate}, {3.0}, {0.0}, {0.0}, {RotateTypes} }, { discrete, "Overlay automap", {&am_overlay}, {3.0}, {0.0}, {0.0}, {OverlayTypes} }, + { discrete, "Enable textured display", {&am_textured}, {3.0}, {0.0}, {0.0}, {OnOff} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Show item counts", {&am_showitems}, {2.0}, {0.0}, {0.0}, {OnOff} }, { discrete, "Show monster counts", {&am_showmonsters}, {2.0}, {0.0}, {0.0}, {OnOff} }, @@ -570,6 +593,7 @@ static menuitem_t AutomapItems[] = { { discrete, "Show total time elapsed", {&am_showtotaltime}, {2.0}, {0.0}, {0.0}, {OnOff} }, { discrete, "Show secrets on map", {&am_map_secrets}, {3.0}, {0.0}, {0.0}, {SecretTypes} }, { discrete, "Draw map background", {&am_drawmapback}, {2.0}, {0.0}, {0.0}, {OnOff} }, + { discrete, "Show keys (cheat)", {&am_showkeys}, {2.0}, {0.0}, {0.0}, {OnOff} }, }; menu_t AutomapMenu = @@ -581,6 +605,37 @@ menu_t AutomapMenu = AutomapItems, }; +menuitem_t MapControlsItems[] = +{ + { redtext,"ENTER to change, BACKSPACE to clear", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { whitetext,"Map Controls", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { mapcontrol, "Pan left", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panleft"} }, + { mapcontrol, "Pan right", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panright"} }, + { mapcontrol, "Pan up", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panup"} }, + { mapcontrol, "Pan down", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_pandown"} }, + { mapcontrol, "Zoom in", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomin"} }, + { mapcontrol, "Zoom out", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomout"} }, + { mapcontrol, "Toggle zoom", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_gobig"} }, + { mapcontrol, "Toggle follow", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglefollow"} }, + { mapcontrol, "Toggle grid", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglegrid"} }, + { mapcontrol, "Toggle texture", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_toggletexture"} }, + { mapcontrol, "Set mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_setmark"} }, + { mapcontrol, "Clear mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_clearmarks"} }, +}; + +menu_t MapControlsMenu = +{ + "CUSTOMIZE MAP CONTROLS", + 3, + countof(MapControlsItems), + 0, + MapControlsItems, + 2, +}; + + + /*======================================= * * Map Colors Menu @@ -613,9 +668,11 @@ EXTERN_CVAR (Color, am_ovsecretsectorcolor) EXTERN_CVAR (Color, am_thingcolor_friend) EXTERN_CVAR (Color, am_thingcolor_monster) EXTERN_CVAR (Color, am_thingcolor_item) +EXTERN_CVAR (Color, am_thingcolor_citem) EXTERN_CVAR (Color, am_ovthingcolor_friend) EXTERN_CVAR (Color, am_ovthingcolor_monster) EXTERN_CVAR (Color, am_ovthingcolor_item) +EXTERN_CVAR (Color, am_ovthingcolor_citem) static menuitem_t MapColorsItems[] = { { rsafemore, "Restore default custom colors", {NULL}, {0}, {0}, {0}, {(value_t*)DefaultCustomColors} }, @@ -639,6 +696,7 @@ static menuitem_t MapColorsItems[] = { { colorpicker, "Monsters (for cheat)", {&am_thingcolor_monster}, {0}, {0}, {0}, {0} }, { colorpicker, "Friends (for cheat)", {&am_thingcolor_friend}, {0}, {0}, {0}, {0} }, { colorpicker, "Items (for cheat)", {&am_thingcolor_item}, {0}, {0}, {0}, {0} }, + { colorpicker, "Count Items (for cheat)", {&am_thingcolor_citem}, {0}, {0}, {0}, {0} }, { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, { colorpicker, "You (overlay)", {&am_ovyourcolor}, {0}, {0}, {0}, {0} }, { colorpicker, "1-sided walls (overlay)", {&am_ovwallcolor}, {0}, {0}, {0}, {0} }, @@ -650,6 +708,7 @@ static menuitem_t MapColorsItems[] = { { colorpicker, "Monsters (overlay) (for cheat)", {&am_ovthingcolor_monster}, {0}, {0}, {0}, {0} }, { colorpicker, "Friends (overlay) (for cheat)", {&am_ovthingcolor_friend}, {0}, {0}, {0}, {0} }, { colorpicker, "Items (overlay) (for cheat)", {&am_ovthingcolor_item}, {0}, {0}, {0}, {0} }, + { colorpicker, "Count Items (overlay) (for cheat)", {&am_ovthingcolor_citem}, {0}, {0}, {0}, {0} }, }; menu_t MapColorsMenu = @@ -1028,7 +1087,7 @@ static menuitem_t DMFlagsItems[] = { { bitflag, "Fast monsters", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_FAST_MONSTERS} }, { bitflag, "Degeneration", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_DEGENERATION} }, { bitflag, "Allow Autoaim", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NOAUTOAIM} }, - { bitflag, "Disallow Suicide", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NOSUICIDE} }, + { bitflag, "Allow Suicide", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NOSUICIDE} }, { bitmask, "Allow jump", {&dmflags}, {3.0}, {DF_NO_JUMP|DF_YES_JUMP}, {0}, {DF_Jump} }, { bitmask, "Allow crouch", {&dmflags}, {3.0}, {DF_NO_CROUCH|DF_YES_CROUCH}, {0}, {DF_Crouch} }, { bitflag, "Allow freelook", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_FREELOOK} }, @@ -1038,6 +1097,8 @@ static menuitem_t DMFlagsItems[] = { { bitflag, "Automap allies", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NO_AUTOMAP_ALLIES} }, { bitflag, "Allow spying", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_DISALLOW_SPYING} }, { bitflag, "Chasecam cheat", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_CHASECAM} }, + { bitflag, "Check ammo for weapon switch", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_DONTCHECKAMMO} }, + { bitflag, "Killing Romero kills all his spawns", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_KILLBOSSMONST} }, { redtext, " ", {NULL}, {0}, {0}, {0}, {NULL} }, { whitetext,"Deathmatch Settings", {NULL}, {0}, {0}, {0}, {NULL} }, @@ -1073,7 +1134,7 @@ static menu_t DMFlagsMenu = "GAMEPLAY OPTIONS", 0, countof(DMFlagsItems), - 0, + 222, DMFlagsItems, }; @@ -1084,14 +1145,14 @@ static menu_t DMFlagsMenu = *=======================================*/ static menuitem_t CompatibilityItems[] = { - { discrete, "Compatibility mode", {&compatmode}, {6.0}, {1.0}, {0.0}, {CompatModes} }, + { discrete, "Compatibility mode", {&compatmode}, {7.0}, {1.0}, {0.0}, {CompatModes} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { bitflag, "Find shortest textures like Doom", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SHORTTEX} }, { bitflag, "Use buggier stair building", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_STAIRINDEX} }, + { bitflag, "Find neighboring light like Doom", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_LIGHT} }, { bitflag, "Limit Pain Elementals' Lost Souls", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_LIMITPAIN} }, { bitflag, "Don't let others hear your pickups", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SILENTPICKUP} }, { bitflag, "Actors are infinitely tall", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_NO_PASSMOBJ} }, - { bitflag, "Cripple sound for silent BFG trick", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MAGICSILENCE} }, { bitflag, "Enable wall running", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_WALLRUN} }, { bitflag, "Spawn item drops on the floor", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_NOTOSSDROPS} }, { bitflag, "All special lines can block ", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_USEBLOCKING} }, @@ -1114,6 +1175,9 @@ static menuitem_t CompatibilityItems[] = { { bitflag, "Crushed monsters can be resurrected", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_CORPSEGIBS} }, { bitflag, "Friendly monsters aren't blocked", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_NOBLOCKFRIENDS} }, { bitflag, "Invert sprite sorting", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SPRITESORT} }, + { bitflag, "Use Doom code for hitscan checks", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_HITSCAN} }, + { bitflag, "Cripple sound for silent BFG trick", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MAGICSILENCE} }, + { bitflag, "Draw polyobjects like Hexen", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_POLYOBJ} }, { discrete, "Interpolate monster movement", {&nomonsterinterpolation}, {2.0}, {0.0}, {0.0}, {NoYes} }, }; @@ -1247,6 +1311,7 @@ static valueenum_t Resamplers[] = static menuitem_t SoundItems[] = { { slider, "Sounds volume", {&snd_sfxvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, + { slider, "Menu volume", {&snd_menuvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, { slider, "Music volume", {&snd_musicvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, { discrete, "MIDI device", {&snd_mididevice}, {0.0}, {0.0}, {0.0}, {NULL} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, @@ -1277,7 +1342,7 @@ static menu_t SoundMenu = SoundItems, }; -#define MIDI_DEVICE_ITEM 2 +#define MIDI_DEVICE_ITEM 3 /*======================================= * @@ -1479,13 +1544,13 @@ void M_SizeDisplay (int diff) CCMD (sizedown) { M_SizeDisplay (-1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } CCMD (sizeup) { M_SizeDisplay (1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } // Draws a string in the console font, scaled to the 8x8 cells @@ -1507,7 +1572,9 @@ void M_BuildKeyList (menuitem_t *item, int numitems) for (i = 0; i < numitems; i++, item++) { if (item->type == control) - C_GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); + Bindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); + else if (item->type == mapcontrol) + AutomapBindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); } } @@ -1796,7 +1863,7 @@ void M_OptDrawer () default: x = indent - width; - color = (item->type == control && menuactive == MENU_WaitKey && i == CurrentItem) + color = ((item->type == control || item->type == mapcontrol) && menuactive == MENU_WaitKey && i == CurrentItem) ? CR_YELLOW : LabelColor; break; } @@ -1954,6 +2021,7 @@ void M_OptDrawer () break; case control: + case mapcontrol: { char description[64]; @@ -2136,7 +2204,14 @@ void M_OptResponder(event_t *ev) { if (ev->data1 != KEY_ESCAPE) { - C_ChangeBinding(item->e.command, ev->data1); + if (item->type == control) + { + Bindings.SetBind(ev->data1, item->e.command); + } + else if (item->type == mapcontrol) + { + AutomapBindings.SetBind(ev->data1, item->e.command); + } M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems); } menuactive = MENU_On; @@ -2159,7 +2234,7 @@ void M_OptResponder(event_t *ev) NewBits = BitTranslate[DummyDepthCvar]; setmodeneeded = true; testingmode = I_GetTime(false) + 5 * TICRATE; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); SetModesMenu (NewWidth, NewHeight, NewBits); } else if (ev->data1 >= '0' && ev->data1 <= '9') @@ -2264,7 +2339,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) CurrentMenu->items[CurrentItem].a.selmode = modecol; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2319,7 +2394,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) } ytop *= CleanYfac_1; rowheight *= CleanYfac_1; - maxitems = (screen->GetHeight() - SmallFont->GetHeight() - ytop) / rowheight + 1; + maxitems = (screen->GetHeight() - rowheight - ytop) / rowheight + 1; CurrentMenu->scrollpos = MAX (0,CurrentMenu->numitems - maxitems + CurrentMenu->scrolltop); CurrentItem = CurrentMenu->numitems - 1; @@ -2334,7 +2409,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (CurrentMenu->items[CurrentItem].type == screenres) CurrentMenu->items[CurrentItem].a.selmode = modecol; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2356,7 +2431,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { ++CurrentItem; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2379,7 +2454,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { ++CurrentItem; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2417,7 +2492,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else item->a.cvar->SetGenericRep (newval, CVAR_Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_sens: @@ -2425,7 +2500,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (value.Float < item->b.min) value.Float = item->b.min; SELECTED_JOYSTICK->SetSensitivity(value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_slider: @@ -2458,12 +2533,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case palettegrid: SelColorIndex = (SelColorIndex - 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; case discretes: @@ -2508,14 +2583,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case ediscrete: value = item->a.cvar->GetGenericRep(CVAR_String); value.String = const_cast(M_FindPrevVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case bitmask: @@ -2534,21 +2609,21 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value.Int = (value.Int & ~bmask) | int(item->e.values[cur].value); item->a.cvar->SetGenericRep (value, CVAR_Int); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case inverter: value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_inverter: assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case screenres: @@ -2572,7 +2647,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) item->a.selmode = col; } } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; default: @@ -2614,7 +2689,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else item->a.cvar->SetGenericRep (newval, CVAR_Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_sens: @@ -2622,7 +2697,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (value.Float > item->c.max) value.Float = item->c.max; SELECTED_JOYSTICK->SetSensitivity(value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_slider: @@ -2655,12 +2730,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case palettegrid: SelColorIndex = (SelColorIndex + 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; case discretes: @@ -2705,14 +2780,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case ediscrete: value = item->a.cvar->GetGenericRep(CVAR_String); value.String = const_cast(M_FindNextVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case bitmask: @@ -2731,21 +2806,21 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value.Int = (value.Int & ~bmask) | int(item->e.values[cur].value); item->a.cvar->SetGenericRep (value, CVAR_Int); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case inverter: value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_inverter: assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case screenres: @@ -2772,7 +2847,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) item->a.selmode = col; } } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; default: @@ -2783,7 +2858,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) case MKEY_Clear: if (item->type == control) { - C_UnbindACommand (item->e.command); + Bindings.UnbindACommand (item->e.command); + item->b.key1 = item->c.key2 = 0; + } + else if (item->type == mapcontrol) + { + AutomapBindings.UnbindACommand (item->e.command); item->b.key1 = item->c.key2 = 0; } break; @@ -2802,7 +2882,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) setmodeneeded = true; NewBits = BitTranslate[DummyDepthCvar]; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); SetModesMenu (NewWidth, NewHeight, NewBits); } else if ((item->type == more || @@ -2814,7 +2894,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) && item->e.mfunc) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); if (item->type == safemore || item->type == rsafemore) { ActivateConfirm (item->label, item->e.mfunc); @@ -2849,9 +2929,9 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } - else if (item->type == control) + else if (item->type == control || item->type == mapcontrol) { menuactive = MENU_WaitKey; OldMessage = CurrentMenu->items[0].label; @@ -2862,7 +2942,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else if (item->type == listelement) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); item->e.lfunc (CurrentItem); } else if (item->type == inverter) @@ -2870,14 +2950,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else if (item->type == joy_inverter) { assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else if (item->type == screenres) { @@ -2885,7 +2965,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else if (item->type == colorpicker) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); StartColorPickerMenu (item->label, item->a.colorcvar); } else if (item->type == palettegrid) @@ -2971,6 +3051,12 @@ static void StartMapColorsMenu (void) M_SwitchMenu (&MapColorsMenu); } +static void StartMapControlsMenu (void) +{ + M_BuildKeyList (MapControlsMenu.items, MapControlsMenu.numitems); + M_SwitchMenu (&MapControlsMenu); +} + CCMD (menu_mapcolors) { M_StartControlPanel (true); @@ -3615,12 +3701,14 @@ void M_LoadKeys (const char *modname, bool dbl) mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname, dbl ? ".Double" : "."); + + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; if (GameConfig->SetSection (section)) { const char *key, *value; while (GameConfig->NextInSection (key, value)) { - C_DoBind (key, value, dbl); + bindings->DoBind (key, value); } } } @@ -3631,12 +3719,13 @@ int M_DoSaveKeys (FConfigFile *config, char *section, int i, bool dbl) config->SetSection (section, true); config->ClearCurrentSection (); + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; for (++i; i < most; ++i) { menuitem_t *item = &CustomControlsItems[i]; if (item->type == control) { - C_ArchiveBindings (config, dbl, item->e.command); + bindings->ArchiveBindings (config, item->e.command); continue; } break; @@ -3682,7 +3771,7 @@ void FreeKeySections() for (i = numStdControls; i < CustomControlsItems.Size(); ++i) { menuitem_t *item = &CustomControlsItems[i]; - if (item->type == whitetext || item->type == control) + if (item->type == whitetext || item->type == control || item->type == mapcontrol) { if (item->label != NULL) { diff --git a/src/mus2midi.cpp b/src/mus2midi.cpp index beef4ffa4..2157b4a89 100644 --- a/src/mus2midi.cpp +++ b/src/mus2midi.cpp @@ -53,7 +53,7 @@ static const BYTE StaticMIDIhead[] = 0, 255, 81, 3, 0x07, 0xa1, 0x20 }; -static const DWORD MUSMagic = MAKE_ID('M','U','S',0x1a); +extern int MUSHeaderSearch(const BYTE *head, int len); static const BYTE CtrlTranslate[15] = { @@ -110,23 +110,30 @@ static size_t WriteVarLen (TArray &file, int time) return ofs; } -bool ProduceMIDI (const BYTE *musBuf, TArray &outFile) +bool ProduceMIDI (const BYTE *musBuf, int len, TArray &outFile) { BYTE midStatus, midArgs, mid1, mid2; size_t mus_p, maxmus_p; BYTE event; int deltaTime; - const MUSHeader *musHead = (const MUSHeader *)musBuf; + const MUSHeader *musHead; BYTE status; BYTE chanUsed[16]; BYTE lastVel[16]; long trackLen; bool no_op; - // Do some validation of the MUS file - if (MUSMagic != musHead->Magic) + // Find the header + int offset = MUSHeaderSearch(musBuf, len); + + if (offset < 0 || offset + (int)sizeof(MUSHeader) >= len) return false; - + + musBuf += offset; + len -= offset; + musHead = (const MUSHeader *)musBuf; + + // Do some validation of the MUS file if (LittleShort(musHead->NumChans) > 15) return false; @@ -136,8 +143,12 @@ bool ProduceMIDI (const BYTE *musBuf, TArray &outFile) memcpy(&outFile[0], StaticMIDIhead, sizeof(StaticMIDIhead)); musBuf += LittleShort(musHead->SongStart); - maxmus_p = LittleShort(musHead->SongLen); mus_p = 0; + maxmus_p = LittleShort(musHead->SongLen); + if ((size_t)len - LittleShort(musHead->SongStart) < maxmus_p) + { + maxmus_p = len - LittleShort(musHead->SongStart); + } memset (lastVel, 100, 16); memset (chanUsed, 0, 16); @@ -184,9 +195,9 @@ bool ProduceMIDI (const BYTE *musBuf, TArray &outFile) switch (event & 0x70) { case MUS_NOTEOFF: - midStatus |= MIDI_NOTEOFF; + midStatus |= MIDI_NOTEON; mid1 = t & 127; - mid2 = 64; + mid2 = 0; break; case MUS_NOTEON: @@ -287,10 +298,10 @@ bool ProduceMIDI (const BYTE *musBuf, TArray &outFile) return true; } -bool ProduceMIDI(const BYTE *musBuf, FILE *outFile) +bool ProduceMIDI(const BYTE *musBuf, int len, FILE *outFile) { TArray work; - if (ProduceMIDI(musBuf, work)) + if (ProduceMIDI(musBuf, len, work)) { return fwrite(&work[0], 1, work.Size(), outFile) == work.Size(); } diff --git a/src/mus2midi.h b/src/mus2midi.h index 6780bdf47..f1d846927 100644 --- a/src/mus2midi.h +++ b/src/mus2midi.h @@ -75,7 +75,4 @@ typedef struct // WORD UsedInstruments[NumInstruments]; } MUSHeader; -bool ProduceMIDI (const BYTE *musBuf, TArray &outFile); -bool ProduceMIDI (const BYTE *musBuf, FILE *outFile); - #endif //__MUS2MIDI_H__ diff --git a/src/namedef.h b/src/namedef.h index 56c80ff08..4fb403027 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -385,9 +385,11 @@ xx(Firstsideonly) xx(Transparent) xx(Passuse) xx(Repeatspecial) +xx(Conversation) xx(Playercross) xx(Playeruse) +xx(Playeruseback) xx(Monstercross) xx(Impact) xx(Playerpush) @@ -418,6 +420,7 @@ xx(Gravity) xx(Lightcolor) xx(Fadecolor) xx(Desaturation) +xx(SoundSequence) xx(Silent) xx(Nofallingdamage) xx(Dropactors) @@ -441,5 +444,26 @@ xx(nofakecontrast) xx(smoothlighting) xx(blockprojectiles) xx(blockuse) +xx(hidden) xx(Renderstyle) + +// USDF keywords +xx(Amount) +xx(Text) +xx(Displaycost) +xx(Yesmessage) +xx(Nomessage) +xx(Log) +xx(Giveitem) +xx(Nextpage) +xx(Closedialog) +xx(Cost) +xx(Page) +xx(Count) +xx(Name) +xx(Panel) +xx(Dialog) +xx(Ifitem) +xx(Choice) +xx(Link) diff --git a/src/nodebuild.cpp b/src/nodebuild.cpp index 83393f7c3..cbeb23f77 100644 --- a/src/nodebuild.cpp +++ b/src/nodebuild.cpp @@ -62,10 +62,17 @@ const int AAPreference = 16; #define D(x) do{}while(0) #endif +FNodeBuilder::FNodeBuilder(FLevel &level) +: Level(level), GLNodes(false), SegsStuffed(0) +{ + + VertexMap = NULL; +} + FNodeBuilder::FNodeBuilder (FLevel &level, TArray &polyspots, TArray &anchors, - bool makeGLNodes, bool enableSSE2) - : Level(level), GLNodes(makeGLNodes), EnableSSE2(enableSSE2), SegsStuffed(0) + bool makeGLNodes) + : Level(level), GLNodes(makeGLNodes), SegsStuffed(0) { VertexMap = new FVertexMap (*this, Level.MinX, Level.MinY, Level.MaxX, Level.MaxY); FindUsedVertices (Level.Vertices, Level.NumVertices); @@ -83,6 +90,33 @@ FNodeBuilder::~FNodeBuilder() } } +void FNodeBuilder::BuildMini(bool makeGLNodes) +{ + GLNodes = makeGLNodes; + GroupSegPlanesSimple(); + BuildTree(); +} + +void FNodeBuilder::Clear() +{ + SegsStuffed = 0; + Nodes.Clear(); + Subsectors.Clear(); + SubsectorSets.Clear(); + Segs.Clear(); + Vertices.Clear(); + SegList.Clear(); + PlaneChecked.Clear(); + Planes.Clear(); + Touched.Clear(); + Colinear.Clear(); + SplitSharers.Clear(); + if (VertexMap == NULL) + { + VertexMap = new FVertexMapSimple(*this); + } +} + void FNodeBuilder::BuildTree () { fixed_t bbox[4]; @@ -90,35 +124,38 @@ void FNodeBuilder::BuildTree () C_InitTicker ("Building BSP", FRACUNIT); HackSeg = DWORD_MAX; HackMate = DWORD_MAX; - CreateNode (0, bbox); + CreateNode (0, Segs.Size(), bbox); CreateSubsectorsForReal (); C_InitTicker (NULL, 0); } -int FNodeBuilder::CreateNode (DWORD set, fixed_t bbox[4]) +int FNodeBuilder::CreateNode (DWORD set, unsigned int count, fixed_t bbox[4]) { node_t node; - int skip, count, selstat; + int skip, selstat; DWORD splitseg; - count = CountSegs (set); - skip = count / MaxSegs; + skip = int(count / MaxSegs); + // When building GL nodes, count may not be an exact count of the number of segs + // in the set. That's okay, because we just use it to get a skip count, so an + // estimate is fine. if ((selstat = SelectSplitter (set, node, splitseg, skip, true)) > 0 || (skip > 0 && (selstat = SelectSplitter (set, node, splitseg, 1, true)) > 0) || (selstat < 0 && (SelectSplitter (set, node, splitseg, skip, false) > 0 || (skip > 0 && SelectSplitter (set, node, splitseg, 1, false)))) || - CheckSubsector (set, node, splitseg, count)) + CheckSubsector (set, node, splitseg)) { // Create a normal node DWORD set1, set2; + unsigned int count1, count2; - SplitSegs (set, node, splitseg, set1, set2); + SplitSegs (set, node, splitseg, set1, set2, count1, count2); D(PrintSet (1, set1)); D(Printf (PRINT_LOG, "(%d,%d) delta (%d,%d) from seg %d\n", node.x>>16, node.y>>16, node.dx>>16, node.dy>>16, splitseg)); D(PrintSet (2, set2)); - node.intchildren[0] = CreateNode (set1, node.bbox[0]); - node.intchildren[1] = CreateNode (set2, node.bbox[1]); + node.intchildren[0] = CreateNode (set1, count1, node.bbox[0]); + node.intchildren[1] = CreateNode (set2, count2, node.bbox[1]); bbox[BOXTOP] = MAX (node.bbox[0][BOXTOP], node.bbox[1][BOXTOP]); bbox[BOXBOTTOM] = MIN (node.bbox[0][BOXBOTTOM], node.bbox[1][BOXBOTTOM]); bbox[BOXLEFT] = MIN (node.bbox[0][BOXLEFT], node.bbox[1][BOXLEFT]); @@ -173,17 +210,15 @@ void FNodeBuilder::CreateSubsectorsForReal () subsector_t sub; unsigned int i; - sub.poly = NULL; - sub.validcount = 0; - sub.CenterX = 0; // Code in p_setup.cpp will set these for us later. - sub.CenterY = 0; sub.sector = NULL; + sub.polys = NULL; + sub.BSP = NULL; for (i = 0; i < SubsectorSets.Size(); ++i) { DWORD set = SubsectorSets[i]; + DWORD firstline = (DWORD)SegList.Size(); - sub.firstline = (DWORD)SegList.Size(); while (set != DWORD_MAX) { USegPtr ptr; @@ -192,14 +227,22 @@ void FNodeBuilder::CreateSubsectorsForReal () SegList.Push (ptr); set = ptr.SegPtr->next; } - sub.numlines = (DWORD)(SegList.Size() - sub.firstline); + sub.numlines = (DWORD)(SegList.Size() - firstline); + sub.firstline = (seg_t *)(size_t)firstline; // Sort segs by linedef for special effects - qsort (&SegList[sub.firstline], sub.numlines, sizeof(USegPtr), SortSegs); + qsort (&SegList[firstline], sub.numlines, sizeof(USegPtr), SortSegs); // Convert seg pointers into indices - for (unsigned int i = sub.firstline; i < SegList.Size(); ++i) + D(Printf (PRINT_LOG, "Output subsector %d:\n", Subsectors.Size())); + for (unsigned int i = firstline; i < SegList.Size(); ++i) { + D(Printf (PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d)\n", SegList[i].SegPtr - &Segs[0], + SegList[i].SegPtr->linedef == -1 ? '+' : ' ', + Vertices[SegList[i].SegPtr->v1].x>>16, + Vertices[SegList[i].SegPtr->v1].y>>16, + Vertices[SegList[i].SegPtr->v2].x>>16, + Vertices[SegList[i].SegPtr->v2].y>>16)); SegList[i].SegNum = DWORD(SegList[i].SegPtr - &Segs[0]); } Subsectors.Push (sub); @@ -266,24 +309,12 @@ int STACK_ARGS FNodeBuilder::SortSegs (const void *a, const void *b) } } -int FNodeBuilder::CountSegs (DWORD set) const -{ - int count = 0; - - while (set != DWORD_MAX) - { - count++; - set = Segs[set].next; - } - return count; -} - // Given a set of segs, checks to make sure they all belong to a single // sector. If so, false is returned, and they become a subsector. If not, // a splitter is synthesized, and true is returned to continue processing // down this branch of the tree. -bool FNodeBuilder::CheckSubsector (DWORD set, node_t &node, DWORD &splitseg, int setsize) +bool FNodeBuilder::CheckSubsector (DWORD set, node_t &node, DWORD &splitseg) { sector_t *sec; DWORD seg; @@ -296,7 +327,9 @@ bool FNodeBuilder::CheckSubsector (DWORD set, node_t &node, DWORD &splitseg, int D(Printf (PRINT_LOG, " - seg %d(%d,%d)-(%d,%d) line %d front %d back %d\n", seg, Vertices[Segs[seg].v1].x>>16, Vertices[Segs[seg].v1].y>>16, Vertices[Segs[seg].v2].x>>16, Vertices[Segs[seg].v2].y>>16, - Segs[seg].linedef, Segs[seg].frontsector, Segs[seg].backsector)); + Segs[seg].linedef, + Segs[seg].frontsector == NULL ? -1 : Segs[seg].frontsector - sectors, + Segs[seg].backsector == NULL ? -1 : Segs[seg].backsector - sectors)); if (Segs[seg].linedef != -1 && Segs[seg].frontsector != sec // Segs with the same front and back sectors are allowed to reside @@ -362,7 +395,7 @@ bool FNodeBuilder::CheckSubsectorOverlappingSegs (DWORD set, node_t &node, DWORD { if (Segs[seg2].linedef == -1) { // Do not put minisegs into a new subsector. - swap (seg1, seg2); + swapvalues (seg1, seg2); } D(Printf(PRINT_LOG, "Need to synthesize a splitter for set %d on seg %d (ov)\n", set, seg2)); splitseg = DWORD_MAX; @@ -497,7 +530,7 @@ int FNodeBuilder::Heuristic (node_t &node, DWORD set, bool honorNoSplit) int realSegs[2] = { 0, 0 }; int specialSegs[2] = { 0, 0 }; DWORD i = set; - int sidev1, sidev2; + int sidev[2]; int side; bool splitter = false; unsigned int max, m2, p, q; @@ -516,7 +549,7 @@ int FNodeBuilder::Heuristic (node_t &node, DWORD set, bool honorNoSplit) } else { - side = ClassifyLine (node, test, sidev1, sidev2); + side = ClassifyLine (node, &Vertices[test->v1], &Vertices[test->v2], sidev); } switch (side) { @@ -526,9 +559,9 @@ int FNodeBuilder::Heuristic (node_t &node, DWORD set, bool honorNoSplit) // The "right" thing to do in this case is to only reject it if there is // another nosplit seg from the same sector at this vertex. Note that a line // that lies exactly on top of the splitter is okay. - if (test->loopnum && honorNoSplit && (sidev1 == 0 || sidev2 == 0)) + if (test->loopnum && honorNoSplit && (sidev[0] == 0 || sidev[1] == 0)) { - if ((sidev1 | sidev2) != 0) + if ((sidev[0] | sidev[1]) != 0) { max = Touched.Size(); for (p = 0; p < max; ++p) @@ -726,8 +759,10 @@ int FNodeBuilder::Heuristic (node_t &node, DWORD set, bool honorNoSplit) return score; } -void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &outset0, DWORD &outset1) +void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &outset0, DWORD &outset1, unsigned int &count0, unsigned int &count1) { + unsigned int _count0 = 0; + unsigned int _count1 = 0; outset0 = DWORD_MAX; outset1 = DWORD_MAX; @@ -740,18 +775,18 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou FPrivSeg *seg = &Segs[set]; int next = seg->next; - int sidev1, sidev2, side; + int sidev[2], side; if (HackSeg == set) { HackSeg = DWORD_MAX; side = 1; - sidev1 = sidev2 = 0; + sidev[0] = sidev[1] = 0; hack = true; } else { - side = ClassifyLine (node, seg, sidev1, sidev2); + side = ClassifyLine (node, &Vertices[seg->v1], &Vertices[seg->v2], sidev); hack = false; } @@ -760,11 +795,13 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou case 0: // seg is entirely in front seg->next = outset0; outset0 = set; + _count0++; break; case 1: // seg is entirely in back seg->next = outset1; outset1 = set; + _count1++; break; default: // seg needs to be split @@ -794,18 +831,20 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou Printf("SelectVertexClose selected endpoint of seg %u\n", set); } - seg2 = SplitSeg (set, vertnum, sidev1); + seg2 = SplitSeg (set, vertnum, sidev[0]); Segs[seg2].next = outset0; outset0 = seg2; Segs[set].next = outset1; outset1 = set; + _count0++; + _count1++; // Also split the seg on the back side if (Segs[set].partner != DWORD_MAX) { int partner1 = Segs[set].partner; - int partner2 = SplitSeg (partner1, vertnum, sidev2); + int partner2 = SplitSeg (partner1, vertnum, sidev[1]); // The newly created seg stays in the same set as the // back seg because it has not been considered for splitting // yet. If it had been, then the front seg would have already @@ -826,17 +865,17 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou } if (side >= 0 && GLNodes) { - if (sidev1 == 0) + if (sidev[0] == 0) { double dist1 = AddIntersection (node, seg->v1); - if (sidev2 == 0) + if (sidev[1] == 0) { double dist2 = AddIntersection (node, seg->v2); FSplitSharer share = { dist1, set, dist2 > dist1 }; SplitSharers.Push (share); } } - else if (sidev2 == 0) + else if (sidev[1] == 0) { AddIntersection (node, seg->v2); } @@ -872,6 +911,8 @@ void FNodeBuilder::SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &ou { AddMinisegs (node, splitseg, outset0, outset1); } + count0 = _count0; + count1 = _count1; } void FNodeBuilder::SetNodeFromSeg (node_t &node, const FPrivSeg *pseg) const @@ -1020,7 +1061,8 @@ void FNodeBuilder::PrintSet (int l, DWORD set) Printf (PRINT_LOG, "set %d:\n", l); for (; set != DWORD_MAX; set = Segs[set].next) { - Printf (PRINT_LOG, "\t%u(%td):%d(%d,%d)-%d(%d,%d) ", set, Segs[set].frontsector-sectors, + Printf (PRINT_LOG, "\t%u(%td)%c%d(%d,%d)-%d(%d,%d)\n", set, Segs[set].frontsector-sectors, + Segs[set].linedef == -1 ? '+' : ':', Segs[set].v1, Vertices[Segs[set].v1].x>>16, Vertices[Segs[set].v1].y>>16, Segs[set].v2, @@ -1028,3 +1070,93 @@ void FNodeBuilder::PrintSet (int l, DWORD set) } Printf (PRINT_LOG, "*\n"); } + + + +#ifdef BACKPATCH +#ifdef _WIN32 +extern "C" { +__declspec(dllimport) int __stdcall VirtualProtect(void *, unsigned long, unsigned long, unsigned long *); +} +#define PAGE_EXECUTE_READWRITE 64 +#else +#include +#include +#include +#endif + +#ifdef __GNUC__ +extern "C" int ClassifyLineBackpatch (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) +#else +static int *CallerOffset; +int ClassifyLineBackpatchC (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) +#endif +{ + // Select the routine based on SSE2 availability and patch the caller so that + // they call that routine directly next time instead of going through here. + int *calleroffset; + int diff; + int (*func)(node_t &, const FSimpleVert *, const FSimpleVert *, int[2]); + +#ifdef __GNUC__ + calleroffset = (int *)__builtin_return_address(0); +#else + calleroffset = CallerOffset; +#endif +// printf ("Patching for SSE %d @ %p %d\n", SSELevel, calleroffset, *calleroffset); + + if (CPU.bSSE2) + { + func = ClassifyLineSSE2; + diff = int((char *)ClassifyLineSSE2 - (char *)calleroffset); + } + else + { + func = ClassifyLine2; + diff = int((char *)ClassifyLine2 - (char *)calleroffset); + } + + calleroffset--; + // Patch the caller. +#ifdef _WIN32 + unsigned long oldprotect; + if (VirtualProtect (calleroffset, 4, PAGE_EXECUTE_READWRITE, &oldprotect)) +#else + // must make this page-aligned for mprotect + long pagesize = sysconf(_SC_PAGESIZE); + char *callerpage = (char *)((intptr_t)calleroffset & ~(pagesize - 1)); + size_t protectlen = (intptr_t)calleroffset + sizeof(void*) - (intptr_t)callerpage; + int ptect; + if (!(ptect = mprotect(callerpage, protectlen, PROT_READ|PROT_WRITE|PROT_EXEC))) +#endif + { + *calleroffset = diff; +#ifdef _WIN32 + VirtualProtect (calleroffset, sizeof(void*), oldprotect, &oldprotect); +#else + mprotect(callerpage, protectlen, PROT_READ|PROT_EXEC); +#endif + } + + // And return by calling the real function. + return func (node, v1, v2, sidev); +} + +#ifndef __GNUC__ +// The ClassifyLineBackpatch() function here is a stub that uses inline assembly and nakedness +// to retrieve the return address of the stack before sending control to the real +// ClassifyLineBackpatchC() function. Since BACKPATCH shouldn't be defined on 64-bit builds, +// we're okay that VC++ can't do inline assembly on that target. + +extern "C" __declspec(noinline) __declspec(naked) int ClassifyLineBackpatch (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) +{ + // We store the return address in a global, so as not to need to mess with the parameter list. + __asm + { + mov eax, [esp] + mov CallerOffset, eax + jmp ClassifyLineBackpatchC + } +} +#endif +#endif diff --git a/src/nodebuild.h b/src/nodebuild.h index d0d59e706..da82e26a2 100644 --- a/src/nodebuild.h +++ b/src/nodebuild.h @@ -1,6 +1,10 @@ #include "doomdata.h" #include "tarray.h" #include "r_defs.h" +#include "x86.h" + +struct FPolySeg; +struct FMiniBSP; struct FEventInfo { @@ -40,6 +44,27 @@ private: FEvent *Predecessor (FEvent *event) const; }; +struct FSimpleVert +{ + fixed_t x, y; +}; + +extern "C" +{ + int ClassifyLine2 (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]); +#ifndef DISABLE_SSE + int ClassifyLineSSE1 (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]); + int ClassifyLineSSE2 (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]); +#ifdef BACKPATCH +#ifdef __GNUC__ + int ClassifyLineBackpatch (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) __attribute__((noinline)); +#else + int __declspec(noinline) ClassifyLineBackpatch (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]); +#endif +#endif +#endif +} + class FNodeBuilder { struct FPrivSeg @@ -60,9 +85,8 @@ class FNodeBuilder bool planefront; FPrivSeg *hashnext; }; - struct FPrivVert + struct FPrivVert : FSimpleVert { - fixed_t x, y; DWORD segs; // segs that use this vertex as v1 DWORD segs2; // segs that use this vertex as v2 @@ -87,8 +111,24 @@ class FNodeBuilder bool Forward; }; + struct glseg_t : public seg_t + { + DWORD Partner; + }; + + // Like a blockmap, but for vertices instead of lines - class FVertexMap + class IVertexMap + { + public: + virtual ~IVertexMap(); + virtual int SelectVertexExact(FPrivVert &vert) = 0; + virtual int SelectVertexClose(FPrivVert &vert) = 0; + private: + IVertexMap &operator=(const IVertexMap &); + }; + + class FVertexMap : public IVertexMap { public: FVertexMap (FNodeBuilder &builder, fixed_t minx, fixed_t miny, fixed_t maxx, fixed_t maxy); @@ -116,12 +156,23 @@ class FNodeBuilder assert (y <= MaxY); return (unsigned(x - MinX) >> BLOCK_SHIFT) + (unsigned(y - MinY) >> BLOCK_SHIFT) * BlocksWide; } + }; - FVertexMap &operator= (const FVertexMap &) { return *this; } + class FVertexMapSimple : public IVertexMap + { + public: + FVertexMapSimple(FNodeBuilder &builder); + + int SelectVertexExact(FPrivVert &vert); + int SelectVertexClose(FPrivVert &vert); + private: + int InsertVertex(FPrivVert &vert); + + FNodeBuilder &MyBuilder; }; friend class FVertexMap; - + friend class FVertexMapSimple; public: struct FLevel @@ -132,7 +183,14 @@ public: fixed_t MinX, MinY, MaxX, MaxY; - void FindMapBounds (); + void FindMapBounds(); + void ResetMapBounds() + { + MinX = FIXED_MAX; + MinY = FIXED_MAX; + MaxX = FIXED_MIN; + MaxY = FIXED_MIN; + } }; struct FPolyStart @@ -141,16 +199,24 @@ public: fixed_t x, y; }; + FNodeBuilder (FLevel &level); FNodeBuilder (FLevel &level, TArray &polyspots, TArray &anchors, - bool makeGLNodes, bool enableSSE2); + bool makeGLNodes); ~FNodeBuilder (); void Extract (node_t *&nodes, int &nodeCount, - seg_t *&segs, int &segCount, + seg_t *&segs, glsegextra_t *&glsegextras, int &segCount, subsector_t *&ssecs, int &subCount, vertex_t *&verts, int &vertCount); + // These are used for building sub-BSP trees for polyobjects. + void Clear(); + void AddPolySegs(FPolySeg *segs, int numsegs); + void AddSegs(seg_t *segs, int numsegs); + void BuildMini(bool makeGLNodes); + void ExtractMini(FMiniBSP *bsp); + static angle_t PointToAngle (fixed_t dx, fixed_t dy); // < 0 : in front of line @@ -160,7 +226,7 @@ public: static inline int PointOnSide (int x, int y, int x1, int y1, int dx, int dy); private: - FVertexMap *VertexMap; + IVertexMap *VertexMap; TArray Nodes; TArray Subsectors; @@ -181,7 +247,6 @@ private: DWORD HackMate; // Seg to use in front of hack seg FLevel &Level; bool GLNodes; // Add minisegs to make GL nodes? - bool EnableSSE2; // Progress meter stuff int SegsStuffed; @@ -191,29 +256,27 @@ private: void MakeSegsFromSides (); int CreateSeg (int linenum, int sidenum); void GroupSegPlanes (); + void GroupSegPlanesSimple (); void FindPolyContainers (TArray &spots, TArray &anchors); bool GetPolyExtents (int polynum, fixed_t bbox[4]); int MarkLoop (DWORD firstseg, int loopnum); void AddSegToBBox (fixed_t bbox[4], const FPrivSeg *seg); - int CreateNode (DWORD set, fixed_t bbox[4]); + int CreateNode (DWORD set, unsigned int count, fixed_t bbox[4]); int CreateSubsector (DWORD set, fixed_t bbox[4]); void CreateSubsectorsForReal (); - bool CheckSubsector (DWORD set, node_t &node, DWORD &splitseg, int setsize); + bool CheckSubsector (DWORD set, node_t &node, DWORD &splitseg); bool CheckSubsectorOverlappingSegs (DWORD set, node_t &node, DWORD &splitseg); bool ShoveSegBehind (DWORD set, node_t &node, DWORD seg, DWORD mate); int SelectSplitter (DWORD set, node_t &node, DWORD &splitseg, int step, bool nosplit); - void SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &outset0, DWORD &outset1); + void SplitSegs (DWORD set, node_t &node, DWORD splitseg, DWORD &outset0, DWORD &outset1, unsigned int &count0, unsigned int &count1); DWORD SplitSeg (DWORD segnum, int splitvert, int v1InFront); int Heuristic (node_t &node, DWORD set, bool honorNoSplit); - int CountSegs (DWORD set) const; // Returns: // 0 = seg is in front // 1 = seg is in back // -1 = seg cuts the node - inline int ClassifyLine (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2); - int ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2); - int ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2); + inline int ClassifyLine (node_t &node, const FPrivVert *v1, const FPrivVert *v2, int sidev[2]); void FixSplitSharers (const node_t &node); double AddIntersection (const node_t &node, int vertex); @@ -225,10 +288,10 @@ private: DWORD AddMiniseg (int v1, int v2, DWORD partner, DWORD seg1, DWORD splitseg); void SetNodeFromSeg (node_t &node, const FPrivSeg *pseg) const; - int CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts); - DWORD PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_t *outVerts); - void PushConnectingGLSeg (int subsector, TArray &segs, vertex_t *v1, vertex_t *v2); - int OutputDegenerateSubsector (TArray &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts); + int CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts); + DWORD PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_t *outVerts); + void PushConnectingGLSeg (int subsector, TArray &segs, vertex_t *v1, vertex_t *v2); + int OutputDegenerateSubsector (TArray &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts); static int STACK_ARGS SortSegs (const void *a, const void *b); @@ -273,21 +336,27 @@ inline int FNodeBuilder::PointOnSide (int x, int y, int x1, int y1, int dx, int return s_num > 0.0 ? -1 : 1; } -inline int FNodeBuilder::ClassifyLine (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2) +inline int FNodeBuilder::ClassifyLine (node_t &node, const FPrivVert *v1, const FPrivVert *v2, int sidev[2]) { -#if !defined(_M_IX86) && !defined(_M_X64) && !defined(__i386__) && !defined(__amd64__) - return ClassifyLine2 (node, seg, sidev1, sidev2); -#elif defined(__SSE2__) +#ifdef DISABLE_SSE + return ClassifyLine2 (node, v1, v2, sidev); +#else +#if defined(__SSE2__) || defined(_M_IX64) // If compiling with SSE2 support everywhere, just use the SSE2 version. - return ClassifyLineSSE2 (node, seg, sidev1, sidev2); + return ClassifyLineSSE2 (node, v1, v2, sidev); #elif defined(_MSC_VER) && _MSC_VER < 1300 - // VC 6 does not support SSE2 optimizations. - return ClassifyLine2 (node, seg, sidev1, sidev2); + // VC 6 does not support SSE optimizations. + return ClassifyLine2 (node, v1, v2, sidev); #else // Select the routine based on our flag. - if (EnableSSE2) - return ClassifyLineSSE2 (node, seg, sidev1, sidev2); +#ifdef BACKPATCH + return ClassifyLineBackpatch (node, v1, v2, sidev); +#else + if (CPU.bSSE2) + return ClassifyLineSSE2 (node, v1, v2, sidev); else - return ClassifyLine2 (node, seg, sidev1, sidev2); + return ClassifyLine2 (node, v1, v2, sidev); +#endif +#endif #endif } diff --git a/src/nodebuild_classify_nosse2.cpp b/src/nodebuild_classify_nosse2.cpp index 2b7f6bec6..3fd1da272 100644 --- a/src/nodebuild_classify_nosse2.cpp +++ b/src/nodebuild_classify_nosse2.cpp @@ -3,11 +3,8 @@ #define FAR_ENOUGH 17179869184.f // 4<<32 -int FNodeBuilder::ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2) +extern "C" int ClassifyLine2 (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) { - const FPrivVert *v1 = &Vertices[seg->v1]; - const FPrivVert *v2 = &Vertices[seg->v2]; - double d_x1 = double(node.x); double d_y1 = double(node.y); double d_dx = double(node.dx); @@ -26,13 +23,13 @@ int FNodeBuilder::ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, { if (s_num2 <= -FAR_ENOUGH) { - sidev1 = sidev2 = 1; + sidev[0] = sidev[1] = 1; return 1; } if (s_num2 >= FAR_ENOUGH) { - sidev1 = 1; - sidev2 = -1; + sidev[0] = 1; + sidev[1] = -1; return -1; } nears = 1; @@ -41,13 +38,13 @@ int FNodeBuilder::ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, { if (s_num2 >= FAR_ENOUGH) { - sidev1 = sidev2 = -1; + sidev[0] = sidev[1] = -1; return 0; } if (s_num2 <= -FAR_ENOUGH) { - sidev1 = -1; - sidev2 = 1; + sidev[0] = -1; + sidev[1] = 1; return -1; } nears = 1; @@ -65,41 +62,41 @@ int FNodeBuilder::ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, double dist = s_num1 * s_num1 * l; if (dist < SIDE_EPSILON*SIDE_EPSILON) { - sidev1 = 0; + sidev[0] = 0; } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; } } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; } if (nears & 1) { double dist = s_num2 * s_num2 * l; if (dist < SIDE_EPSILON*SIDE_EPSILON) { - sidev2 = 0; + sidev[1] = 0; } else { - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } } else { - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } - if ((sidev1 | sidev2) == 0) + if ((sidev[0] | sidev[1]) == 0) { // seg is coplanar with the splitter, so use its orientation to determine // which child it ends up in. If it faces the same direction as the splitter, // it goes in front. Otherwise, it goes in back. @@ -127,11 +124,11 @@ int FNodeBuilder::ClassifyLine2 (node_t &node, const FPrivSeg *seg, int &sidev1, } } } - else if (sidev1 <= 0 && sidev2 <= 0) + else if (sidev[0] <= 0 && sidev[1] <= 0) { return 0; } - else if (sidev1 >= 0 && sidev2 >= 0) + else if (sidev[0] >= 0 && sidev[1] >= 0) { return 1; } diff --git a/src/nodebuild_classify_sse2.cpp b/src/nodebuild_classify_sse2.cpp index 05e4684a8..01c469093 100644 --- a/src/nodebuild_classify_sse2.cpp +++ b/src/nodebuild_classify_sse2.cpp @@ -1,18 +1,16 @@ +#ifndef DISABLE_SSE + #include "doomtype.h" #include "nodebuild.h" #define FAR_ENOUGH 17179869184.f // 4<<32 -// This function is identical to the ClassifyLine2 version. So how does it use SSE2? -// Easy! By explicitly enabling SSE2 in the configuration properties for this one -// file, we can build it with SSE2 enabled without forcing SSE2 on the rest of the -// project. +// You may notice that this function is identical to ClassifyLine2. +// The reason it is SSE2 is because this file is explicitly compiled +// with SSE2 math enabled, but the other files are not. -int FNodeBuilder::ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &sidev1, int &sidev2) +extern "C" int ClassifyLineSSE2 (node_t &node, const FSimpleVert *v1, const FSimpleVert *v2, int sidev[2]) { - const FPrivVert *v1 = &Vertices[seg->v1]; - const FPrivVert *v2 = &Vertices[seg->v2]; - double d_x1 = double(node.x); double d_y1 = double(node.y); double d_dx = double(node.dx); @@ -31,13 +29,13 @@ int FNodeBuilder::ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &side { if (s_num2 <= -FAR_ENOUGH) { - sidev1 = sidev2 = 1; + sidev[0] = sidev[1] = 1; return 1; } if (s_num2 >= FAR_ENOUGH) { - sidev1 = 1; - sidev2 = -1; + sidev[0] = 1; + sidev[1] = -1; return -1; } nears = 1; @@ -46,13 +44,13 @@ int FNodeBuilder::ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &side { if (s_num2 >= FAR_ENOUGH) { - sidev1 = sidev2 = -1; + sidev[0] = sidev[1] = -1; return 0; } if (s_num2 <= -FAR_ENOUGH) { - sidev1 = -1; - sidev2 = 1; + sidev[0] = -1; + sidev[1] = 1; return -1; } nears = 1; @@ -70,41 +68,41 @@ int FNodeBuilder::ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &side double dist = s_num1 * s_num1 * l; if (dist < SIDE_EPSILON*SIDE_EPSILON) { - sidev1 = 0; + sidev[0] = 0; } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; } } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; } if (nears & 1) { double dist = s_num2 * s_num2 * l; if (dist < SIDE_EPSILON*SIDE_EPSILON) { - sidev2 = 0; + sidev[1] = 0; } else { - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } } else { - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } } else { - sidev1 = s_num1 > 0.0 ? -1 : 1; - sidev2 = s_num2 > 0.0 ? -1 : 1; + sidev[0] = s_num1 > 0.0 ? -1 : 1; + sidev[1] = s_num2 > 0.0 ? -1 : 1; } - if ((sidev1 | sidev2) == 0) + if ((sidev[0] | sidev[1]) == 0) { // seg is coplanar with the splitter, so use its orientation to determine // which child it ends up in. If it faces the same direction as the splitter, // it goes in front. Otherwise, it goes in back. @@ -132,13 +130,15 @@ int FNodeBuilder::ClassifyLineSSE2 (node_t &node, const FPrivSeg *seg, int &side } } } - else if (sidev1 <= 0 && sidev2 <= 0) + else if (sidev[0] <= 0 && sidev[1] <= 0) { return 0; } - else if (sidev1 >= 0 && sidev2 >= 0) + else if (sidev[0] >= 0 && sidev[1] >= 0) { return 1; } return -1; } + +#endif diff --git a/src/nodebuild_extract.cpp b/src/nodebuild_extract.cpp index ffa69f705..c4546bb5b 100644 --- a/src/nodebuild_extract.cpp +++ b/src/nodebuild_extract.cpp @@ -45,8 +45,16 @@ #include "templates.h" #include "r_main.h" +#if 0 +#define D(x) x +#define DD 1 +#else +#define D(x) do{}while(0) +#undef DD +#endif + void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, - seg_t *&outSegs, int &segCount, + seg_t *&outSegs, glsegextra_t *&outSegExtras, int &segCount, subsector_t *&outSubs, int &subCount, vertex_t *&outVerts, int &vertCount) { @@ -63,7 +71,7 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, subCount = Subsectors.Size(); outSubs = new subsector_t[subCount]; - memset(outSubs,0,subCount * sizeof(subsector_t)); + memset(outSubs, 0, subCount * sizeof(subsector_t)); nodeCount = Nodes.Size (); outNodes = new node_t[nodeCount]; @@ -71,16 +79,19 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, memcpy (outNodes, &Nodes[0], nodeCount*sizeof(node_t)); for (i = 0; i < nodeCount; ++i) { + D(Printf(PRINT_LOG, "Node %d:\n", i)); // Go backwards because on 64-bit systems, both of the intchildren are // inside the first in-game child. for (int j = 1; j >= 0; --j) { if (outNodes[i].intchildren[j] & 0x80000000) { + D(Printf(PRINT_LOG, " subsector %d\n", outNodes[i].intchildren[j] & 0x7FFFFFFF)); outNodes[i].children[j] = (BYTE *)(outSubs + (outNodes[i].intchildren[j] & 0x7fffffff)) + 1; } else { + D(Printf(PRINT_LOG, " node %d\n", outNodes[i].intchildren[j])); outNodes[i].children[j] = outNodes + outNodes[i].intchildren[j]; } } @@ -88,25 +99,30 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, if (GLNodes) { - TArray segs (Segs.Size()*5/4); + TArray segs (Segs.Size()*5/4); for (i = 0; i < subCount; ++i) { DWORD numsegs = CloseSubsector (segs, i, outVerts); outSubs[i].numlines = numsegs; - outSubs[i].firstline = segs.Size() - numsegs; - outSubs[i].poly = NULL; + outSubs[i].firstline = (seg_t *)(size_t)(segs.Size() - numsegs); } segCount = segs.Size (); outSegs = new seg_t[segCount]; - memcpy (outSegs, &segs[0], segCount*sizeof(seg_t)); + outSegExtras = new glsegextra_t[segCount]; for (i = 0; i < segCount; ++i) { - if (outSegs[i].PartnerSeg != NULL) + outSegs[i] = *(seg_t *)&segs[i]; + + if (segs[i].Partner != DWORD_MAX) { - outSegs[i].PartnerSeg = &outSegs[Segs[(unsigned int)(size_t)outSegs[i].PartnerSeg-1].storedseg]; + outSegExtras[i].PartnerSeg = Segs[segs[i].Partner].storedseg; + } + else + { + outSegExtras[i].PartnerSeg = DWORD_MAX; } } } @@ -115,23 +131,28 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, memcpy (outSubs, &Subsectors[0], subCount*sizeof(subsector_t)); segCount = Segs.Size (); outSegs = new seg_t[segCount]; + outSegExtras = NULL; for (i = 0; i < segCount; ++i) { const FPrivSeg *org = &Segs[SegList[i].SegNum]; seg_t *out = &outSegs[i]; + D(Printf(PRINT_LOG, "Seg %d: v1(%d) -> v2(%d)\n", i, org->v1, org->v2)); + out->v1 = outVerts + org->v1; out->v2 = outVerts + org->v2; out->backsector = org->backsector; out->frontsector = org->frontsector; out->linedef = Level.Lines + org->linedef; out->sidedef = Level.Sides + org->sidedef; - out->PartnerSeg = NULL; - out->bPolySeg = false; } } + for (i = 0; i < subCount; ++i) + { + outSubs[i].firstline = &outSegs[(size_t)outSubs[i].firstline]; + } - //Printf ("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount); + D(Printf("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount)); for (i = 0; i < Level.NumLines; ++i) { @@ -140,7 +161,91 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, } } -int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts) +void FNodeBuilder::ExtractMini (FMiniBSP *bsp) +{ + unsigned int i; + + bsp->Verts.Resize(Vertices.Size()); + for (i = 0; i < Vertices.Size(); ++i) + { + bsp->Verts[i].x = Vertices[i].x; + bsp->Verts[i].y = Vertices[i].y; + } + + bsp->Subsectors.Resize(Subsectors.Size()); + memset(&bsp->Subsectors[0], 0, Subsectors.Size() * sizeof(subsector_t)); + + bsp->Nodes.Resize(Nodes.Size()); + memcpy(&bsp->Nodes[0], &Nodes[0], Nodes.Size()*sizeof(node_t)); + for (i = 0; i < Nodes.Size(); ++i) + { + D(Printf(PRINT_LOG, "Node %d:\n", i)); + // Go backwards because on 64-bit systems, both of the intchildren are + // inside the first in-game child. + for (int j = 1; j >= 0; --j) + { + if (bsp->Nodes[i].intchildren[j] & 0x80000000) + { + D(Printf(PRINT_LOG, " subsector %d\n", bsp->Nodes[i].intchildren[j] & 0x7FFFFFFF)); + bsp->Nodes[i].children[j] = (BYTE *)&bsp->Subsectors[bsp->Nodes[i].intchildren[j] & 0x7fffffff] + 1; + } + else + { + D(Printf(PRINT_LOG, " node %d\n", bsp->Nodes[i].intchildren[j])); + bsp->Nodes[i].children[j] = &bsp->Nodes[bsp->Nodes[i].intchildren[j]]; + } + } + } + + if (GLNodes) + { + TArray glsegs; + for (i = 0; i < Subsectors.Size(); ++i) + { + DWORD numsegs = CloseSubsector (glsegs, i, &bsp->Verts[0]); + bsp->Subsectors[i].numlines = numsegs; + bsp->Subsectors[i].firstline = &bsp->Segs[bsp->Segs.Size() - numsegs]; + } + bsp->Segs.Resize(glsegs.Size()); + for (i = 0; i < glsegs.Size(); ++i) + { + bsp->Segs[i] = *(seg_t *)&glsegs[i]; + } + } + else + { + memcpy(&bsp->Subsectors[0], &Subsectors[0], Subsectors.Size()*sizeof(subsector_t)); + bsp->Segs.Resize(Segs.Size()); + for (i = 0; i < Segs.Size(); ++i) + { + const FPrivSeg *org = &Segs[SegList[i].SegNum]; + seg_t *out = &bsp->Segs[i]; + + D(Printf(PRINT_LOG, "Seg %d: v1(%d) -> v2(%d)\n", i, org->v1, org->v2)); + + out->v1 = &bsp->Verts[org->v1]; + out->v2 = &bsp->Verts[org->v2]; + out->backsector = org->backsector; + out->frontsector = org->frontsector; + if (org->sidedef != int(NO_SIDE)) + { + out->linedef = Level.Lines + org->linedef; + out->sidedef = Level.Sides + org->sidedef; + } + else // part of a miniseg + { + out->linedef = NULL; + out->sidedef = NULL; + } + } + for (i = 0; i < bsp->Subsectors.Size(); ++i) + { + bsp->Subsectors[i].firstline = &bsp->Segs[(size_t)bsp->Subsectors[i].firstline]; + } + } +} + +int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts) { FPrivSeg *seg, *prev; angle_t prevAngle; @@ -151,7 +256,7 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * bool diffplanes; int firstplane; - first = Subsectors[subsector].firstline; + first = (DWORD)(size_t)Subsectors[subsector].firstline; max = first + Subsectors[subsector].numlines; count = 0; @@ -185,10 +290,26 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * prev = seg; firstVert = seg->v1; +#ifdef DD + Printf(PRINT_LOG, "--%d--\n", subsector); + for (j = first; j < max; ++j) + { + seg = &Segs[SegList[j].SegNum]; + angle_t ang = PointToAngle (Vertices[seg->v1].x - midx, Vertices[seg->v1].y - midy); + Printf(PRINT_LOG, "%d%c %5d(%5d,%5d)->%5d(%5d,%5d) - %3.3f %d,%d\n", j, + seg->linedef == -1 ? '+' : ':', + seg->v1, Vertices[seg->v1].x>>16, Vertices[seg->v1].y>>16, + seg->v2, Vertices[seg->v2].x>>16, Vertices[seg->v2].y>>16, + double(ang/2)*180/(1<<30), + seg->planenum, seg->planefront); + } +#endif + if (diffplanes) { // A well-behaved subsector. Output the segs sorted by the angle formed by connecting // the subsector's center to their first vertex. + D(Printf(PRINT_LOG, "Well behaved subsector\n")); for (i = first + 1; i < max; ++i) { angle_t bestdiff = ANGLE_MAX; @@ -226,7 +347,9 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[seg->v1]); count++; } - +#ifdef DD + Printf(PRINT_LOG, "+%d\n", bestj); +#endif prevAngle -= bestdiff; seg->storedseg = PushGLSeg (segs, seg, outVerts); count++; @@ -237,6 +360,9 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * break; } } +#ifdef DD + Printf(PRINT_LOG, "\n"); +#endif } else { // A degenerate subsector. These are handled in three stages: @@ -248,6 +374,8 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * // to the start seg. // A dot product serves to determine distance from the start seg. + D(Printf(PRINT_LOG, "degenerate subsector\n")); + // Stage 1. Go forward. count += OutputDegenerateSubsector (segs, subsector, true, 0, prev, outVerts); @@ -263,11 +391,23 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * PushConnectingGLSeg (subsector, segs, &outVerts[prev->v2], &outVerts[firstVert]); count++; } +#ifdef DD + Printf(PRINT_LOG, "Output GL subsector %d:\n", subsector); + for (i = segs.Size() - count; i < (int)segs.Size(); ++i) + { + Printf(PRINT_LOG, " Seg %5d%c(%5d,%5d)-(%5d,%5d)\n", i, + segs[i].linedef == NULL ? '+' : ' ', + segs[i].v1->x>>16, + segs[i].v1->y>>16, + segs[i].v2->x>>16, + segs[i].v2->y>>16); + } +#endif return count; } -int FNodeBuilder::OutputDegenerateSubsector (TArray &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts) +int FNodeBuilder::OutputDegenerateSubsector (TArray &segs, int subsector, bool bForward, double lastdot, FPrivSeg *&prev, vertex_t *outVerts) { static const double bestinit[2] = { -DBL_MAX, DBL_MAX }; FPrivSeg *seg; @@ -275,7 +415,7 @@ int FNodeBuilder::OutputDegenerateSubsector (TArray &segs, int subsector, double dot, x1, y1, dx, dy, dx2, dy2; bool wantside; - first = Subsectors[subsector].firstline; + first = (DWORD)(size_t)Subsectors[subsector].firstline; max = first + Subsectors[subsector].numlines; count = 0; @@ -334,9 +474,9 @@ int FNodeBuilder::OutputDegenerateSubsector (TArray &segs, int subsector, return count; } -DWORD FNodeBuilder::PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_t *outVerts) +DWORD FNodeBuilder::PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_t *outVerts) { - seg_t newseg; + glseg_t newseg; newseg.v1 = outVerts + seg->v1; newseg.v2 = outVerts + seg->v2; @@ -352,15 +492,13 @@ DWORD FNodeBuilder::PushGLSeg (TArray &segs, const FPrivSeg *seg, vertex_ newseg.linedef = NULL; newseg.sidedef = NULL; } - newseg.PartnerSeg = (seg_t *)(seg->partner == DWORD_MAX ? 0 : (size_t)seg->partner + 1); - newseg.bPolySeg = false; - newseg.Subsector = NULL; + newseg.Partner = seg->partner; return (DWORD)segs.Push (newseg); } -void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray &segs, vertex_t *v1, vertex_t *v2) +void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray &segs, vertex_t *v1, vertex_t *v2) { - seg_t newseg; + glseg_t newseg; newseg.v1 = v1; newseg.v2 = v2; @@ -368,8 +506,6 @@ void FNodeBuilder::PushConnectingGLSeg (int subsector, TArray &segs, vert newseg.frontsector = NULL; newseg.linedef = NULL; newseg.sidedef = NULL; - newseg.PartnerSeg = NULL; - newseg.bPolySeg = false; - newseg.Subsector = NULL; + newseg.Partner = DWORD_MAX; segs.Push (newseg); } diff --git a/src/nodebuild_utility.cpp b/src/nodebuild_utility.cpp index a8a618b9f..ef79ac4d9 100644 --- a/src/nodebuild_utility.cpp +++ b/src/nodebuild_utility.cpp @@ -49,6 +49,7 @@ #include "m_bbox.h" #include "r_main.h" #include "i_system.h" +#include "po_man.h" static const int PO_LINE_START = 1; static const int PO_LINE_EXPLICIT = 5; @@ -177,6 +178,86 @@ int FNodeBuilder::CreateSeg (int linenum, int sidenum) return segnum; } +// For every seg, create FPrivSegs and FPrivVerts. + +void FNodeBuilder::AddSegs(seg_t *segs, int numsegs) +{ + assert(numsegs > 0); + + for (int i = 0; i < numsegs; ++i) + { + FPrivSeg seg; + FPrivVert vert; + int segnum; + + seg.next = DWORD_MAX; + seg.loopnum = 0; + seg.partner = DWORD_MAX; + seg.hashnext = NULL; + seg.planefront = false; + seg.planenum = DWORD_MAX; + seg.storedseg = DWORD_MAX; + + seg.frontsector = segs[i].frontsector; + seg.backsector = segs[i].backsector; + vert.x = segs[i].v1->x; + vert.y = segs[i].v1->y; + seg.v1 = VertexMap->SelectVertexExact(vert); + vert.x = segs[i].v2->x; + vert.y = segs[i].v2->y; + seg.v2 = VertexMap->SelectVertexExact(vert); + seg.linedef = int(segs[i].linedef - Level.Lines); + seg.sidedef = segs[i].sidedef != NULL ? int(segs[i].sidedef - Level.Sides) : int(NO_SIDE); + seg.nextforvert = Vertices[seg.v1].segs; + seg.nextforvert2 = Vertices[seg.v2].segs2; + + segnum = (int)Segs.Push(seg); + Vertices[seg.v1].segs = segnum; + Vertices[seg.v2].segs2 = segnum; + } +} + +void FNodeBuilder::AddPolySegs(FPolySeg *segs, int numsegs) +{ + assert(numsegs > 0); + + for (int i = 0; i < numsegs; ++i) + { + FPrivSeg seg; + FPrivVert vert; + int segnum; + + seg.next = DWORD_MAX; + seg.loopnum = 0; + seg.partner = DWORD_MAX; + seg.hashnext = NULL; + seg.planefront = false; + seg.planenum = DWORD_MAX; + seg.storedseg = DWORD_MAX; + + side_t *side = segs[i].wall; + assert(side != NULL); + + seg.frontsector = side->sector; + seg.backsector = side->linedef->frontsector == side->sector ? side->linedef->backsector : side->linedef->frontsector; + vert.x = segs[i].v1.x; + vert.y = segs[i].v1.y; + seg.v1 = VertexMap->SelectVertexExact(vert); + vert.x = segs[i].v2.x; + vert.y = segs[i].v2.y; + seg.v2 = VertexMap->SelectVertexExact(vert); + seg.linedef = int(side->linedef - Level.Lines); + seg.sidedef = int(side - Level.Sides); + seg.nextforvert = Vertices[seg.v1].segs; + seg.nextforvert2 = Vertices[seg.v2].segs2; + + segnum = (int)Segs.Push(seg); + Vertices[seg.v1].segs = segnum; + Vertices[seg.v2].segs2 = segnum; + } +} + + // Group colinear segs together so that only one seg per line needs to be checked // by SelectSplitter(). @@ -269,6 +350,27 @@ void FNodeBuilder::GroupSegPlanes () PlaneChecked.Reserve ((planenum + 7) / 8); } +// Just create one plane per seg. Should be good enough for mini BSPs. +void FNodeBuilder::GroupSegPlanesSimple() +{ + Planes.Resize(Segs.Size()); + for (int i = 0; i < (int)Segs.Size(); ++i) + { + FPrivSeg *seg = &Segs[i]; + FSimpleLine *pline = &Planes[i]; + seg->next = i+1; + seg->hashnext = NULL; + seg->planenum = i; + seg->planefront = true; + pline->x = Vertices[seg->v1].x; + pline->y = Vertices[seg->v1].y; + pline->dx = Vertices[seg->v2].x - Vertices[seg->v1].x; + pline->dy = Vertices[seg->v2].y - Vertices[seg->v1].y; + } + Segs.Last().next = DWORD_MAX; + PlaneChecked.Reserve((Segs.Size() + 7) / 8); +} + // Find "loops" of segs surrounding polyobject's origin. Note that a polyobject's origin // is not solely defined by the polyobject's anchor, but also by the polyobject itself. // For the split avoidance to work properly, you must have a convex, complete loop of @@ -507,6 +609,10 @@ void FNodeBuilder::FLevel::FindMapBounds () MaxY = maxy; } +FNodeBuilder::IVertexMap::~IVertexMap() +{ +} + FNodeBuilder::FVertexMap::FVertexMap (FNodeBuilder &builder, fixed_t minx, fixed_t miny, fixed_t maxx, fixed_t maxy) : MyBuilder(builder) @@ -606,3 +712,52 @@ int FNodeBuilder::FVertexMap::InsertVertex (FNodeBuilder::FPrivVert &vert) return vertnum; } + +FNodeBuilder::FVertexMapSimple::FVertexMapSimple(FNodeBuilder &builder) + : MyBuilder(builder) +{ +} + +int FNodeBuilder::FVertexMapSimple::SelectVertexExact(FNodeBuilder::FPrivVert &vert) +{ + FPrivVert *verts = &MyBuilder.Vertices[0]; + unsigned int stop = MyBuilder.Vertices.Size(); + + for (unsigned int i = 0; i < stop; ++i) + { + if (verts[i].x == vert.x && verts[i].y == vert.y) + { + return i; + } + } + // Not present: add it! + return InsertVertex(vert); +} + +int FNodeBuilder::FVertexMapSimple::SelectVertexClose(FNodeBuilder::FPrivVert &vert) +{ + FPrivVert *verts = &MyBuilder.Vertices[0]; + unsigned int stop = MyBuilder.Vertices.Size(); + + for (unsigned int i = 0; i < stop; ++i) + { +#if VERTEX_EPSILON <= 1 + if (verts[i].x == vert.x && verts[i].y == y) +#else + if (abs(verts[i].x - vert.x) < VERTEX_EPSILON && + abs(verts[i].y - vert.y) < VERTEX_EPSILON) +#endif + { + return i; + } + } + // Not present: add it! + return InsertVertex (vert); +} + +int FNodeBuilder::FVertexMapSimple::InsertVertex (FNodeBuilder::FPrivVert &vert) +{ + vert.segs = DWORD_MAX; + vert.segs2 = DWORD_MAX; + return (int)MyBuilder.Vertices.Push (vert); +} diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index 9413ae4bb..ade5b5c12 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -74,25 +74,9 @@ OPLMIDIDevice::OPLMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - FWadLump data = Wads.OpenLumpName("GENMIDI"); OPLloadBank(data); -} - -//========================================================================== -// -// OPLMIDIDevice Destructor -// -//========================================================================== - -OPLMIDIDevice::~OPLMIDIDevice() -{ - Close(); + SampleRate = (int)OPL_SAMPLE_RATE; } //========================================================================== @@ -109,25 +93,14 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo { return 1; } - - Stream = GSnd->CreateStream(FillStream, int(OPL_SAMPLE_RATE / 14) * 4, - SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this); - if (Stream == NULL) + int ret = OpenStream(14, SoundStream::Mono, callback, userdata); + if (ret == 0) { - return 2; + OPLstopMusic(); + OPLplayMusic(100); + DEBUGOUT("========= New song started ==========\n", 0, 0, 0); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - - OPLstopMusic(); - OPLplayMusic(100); - DEBUGOUT("========= New song started ==========\n", 0, 0, 0); - - return 0; + return ret; } //========================================================================== @@ -138,24 +111,8 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo void OPLMIDIDevice::Close() { - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } + SoftSynthMIDIDevice::Close(); io->OPLdeinit(); - Started = false; -} - -//========================================================================== -// -// OPLMIDIDevice :: IsOpen -// -//========================================================================== - -bool OPLMIDIDevice::IsOpen() const -{ - return Stream != NULL; } //========================================================================== @@ -169,34 +126,6 @@ int OPLMIDIDevice::GetTechnology() const return MOD_FMSYNTH; } -//========================================================================== -// -// OPLMIDIDevice :: SetTempo -// -//========================================================================== - -int OPLMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - DEBUGOUT("Tempo changed to %.0f, %.2f samples/tick\n", Tempo, SamplesPerTick, 0); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int OPLMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - DEBUGOUT("Division changed to %.0f, %.2f samples/tick\n", Division, SamplesPerTick, 0); - return 0; -} - //========================================================================== // // OPLMIDIDevice :: CalcTickRate @@ -208,219 +137,22 @@ int OPLMIDIDevice::SetTimeDiv(int timediv) void OPLMIDIDevice::CalcTickRate() { - SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division; - io->SetClockRate(SamplesPerTick); -} - -//========================================================================== -// -// OPLMIDIDevice :: Resume -// -//========================================================================== - -int OPLMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: Stop -// -//========================================================================== - -void OPLMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int OPLMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - ChipAccess.Enter(); - StreamOut(header); - ChipAccess.Leave(); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int OPLMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: PrepareHeader -// -//========================================================================== - -int OPLMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int OPLMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: FakeVolume -// -// Since the OPL output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool OPLMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool OPLMIDIDevice::NeedThreadedCallback() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: Pause -// -//========================================================================== - -bool OPLMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + SoftSynthMIDIDevice::CalcTickRate(); + io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick); } //========================================================================== // // OPLMIDIDevice :: PlayTick // -// event[0] = delta time -// event[1] = unused -// event[2] = event +// We derive from two base classes that both define PlayTick(), so we need +// to be unambiguous about which one to use. // //========================================================================== int OPLMIDIDevice::PlayTick() { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { // Should I handle master volume changes? - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - HandleEvent(status, parm1, parm2); - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + return SoftSynthMIDIDevice::PlayTick(); } //========================================================================== @@ -508,14 +240,35 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2) //========================================================================== // -// OPLMIDIDevice :: FillStream static +// OPLMIDIDevice :: HandleLongEvent // //========================================================================== -bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void OPLMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - OPLMIDIDevice *device = (OPLMIDIDevice *)userdata; - return device->ServiceStream(buff, len); +} + +//========================================================================== +// +// OPLMIDIDevice :: ComputeOutput +// +// We override ServiceStream, so this function is never actually called. +// +//========================================================================== + +void OPLMIDIDevice::ComputeOutput(float *buffer, int len) +{ +} + +//========================================================================== +// +// OPLMIDIDevice :: ServiceStream +// +//========================================================================== + +bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes) +{ + return OPLmusicBlock::ServiceStream(buff, numbytes); } //========================================================================== diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp index befbaf535..31eef55fd 100644 --- a/src/oplsynth/opl_mus_player.cpp +++ b/src/oplsynth/opl_mus_player.cpp @@ -66,7 +66,7 @@ OPLmusicFile::OPLmusicFile (FILE *file, BYTE *musiccache, int len) { if (fread (scoredata, 1, len, file) != (size_t)len) { - delete[] scoredata; +fail: delete[] scoredata; scoredata = NULL; return; } @@ -78,9 +78,7 @@ OPLmusicFile::OPLmusicFile (FILE *file, BYTE *musiccache, int len) if (io->OPLinit (TwoChips + 1)) { - delete[] scoredata; - scoredata = NULL; - return; + goto fail; } // Check for RDosPlay raw OPL format @@ -96,12 +94,36 @@ OPLmusicFile::OPLmusicFile (FILE *file, BYTE *musiccache, int len) } // Check for DosBox OPL dump else if (((DWORD *)scoredata)[0] == MAKE_ID('D','B','R','A') && - ((DWORD *)scoredata)[1] == MAKE_ID('W','O','P','L') && - ((DWORD *)scoredata)[2] == MAKE_ID(0,0,1,0)) + ((DWORD *)scoredata)[1] == MAKE_ID('W','O','P','L')) { - RawPlayer = DosBox; - SamplesPerTick = OPL_SAMPLE_RATE / 1000; - ScoreLen = MIN(len - 24, LittleLong(((DWORD *)scoredata)[4])); + if (((DWORD *)scoredata)[2] == MAKE_ID(0,0,1,0)) + { + RawPlayer = DosBox1; + SamplesPerTick = OPL_SAMPLE_RATE / 1000; + ScoreLen = MIN(len - 24, LittleLong(((DWORD *)scoredata)[4])) + 24; + } + else if (((DWORD *)scoredata)[2] == MAKE_ID(2,0,0,0)) + { + if (scoredata[20] != 0) + { + Printf("Unsupported DOSBox Raw OPL format %d\n", scoredata[20]); + goto fail; + } + if (scoredata[21] != 0) + { + Printf("Unsupported DOSBox Raw OPL compression %d\n", scoredata[21]); + goto fail; + } + RawPlayer = DosBox2; + SamplesPerTick = OPL_SAMPLE_RATE / 1000; + int headersize = 0x1A + scoredata[0x19]; + ScoreLen = MIN(len - headersize, LittleLong(((DWORD *)scoredata)[3]) * 2) + headersize; + } + else + { + Printf("Unsupported DOSBox Raw OPL version %d.%d\n", LittleShort(((WORD *)scoredata)[4]), LittleShort(((WORD *)scoredata)[5])); + goto fail; + } } // Check for modified IMF format (includes a header) else if (((DWORD *)scoredata)[0] == MAKE_ID('A','D','L','I') && @@ -131,6 +153,10 @@ OPLmusicFile::OPLmusicFile (FILE *file, BYTE *musiccache, int len) ScoreLen = songlen + int(score - scoredata); } } + else + { + goto fail; + } Restart (); } @@ -159,18 +185,24 @@ void OPLmusicFile::Restart () { OPLmusicBlock::Restart(); WhichChip = 0; - if (RawPlayer == RDosPlay) + switch (RawPlayer) { + case RDosPlay: score = scoredata + 10; SamplesPerTick = LittleShort(*(WORD *)(scoredata + 8)) / ADLIB_CLOCK_MUL; - } - else if (RawPlayer == DosBox) - { + break; + + case DosBox1: score = scoredata + 24; SamplesPerTick = OPL_SAMPLE_RATE / 1000; - } - else if (RawPlayer == IMF) - { + break; + + case DosBox2: + score = scoredata + 0x1A + scoredata[0x19]; + SamplesPerTick = OPL_SAMPLE_RATE / 1000; + break; + + case IMF: score = scoredata + 6; // Skip track and game name @@ -183,6 +215,7 @@ void OPLmusicFile::Restart () { score += 4; // Skip song length } + break; } io->SetClockRate(SamplesPerTick); } @@ -331,9 +364,11 @@ void OPLmusicBlock::OffsetSamples(float *buff, int count) int OPLmusicFile::PlayTick () { BYTE reg, data; + WORD delay; - if (RawPlayer == RDosPlay) + switch (RawPlayer) { + case RDosPlay: while (score < scoredata + ScoreLen) { data = *score++; @@ -379,9 +414,9 @@ int OPLmusicFile::PlayTick () break; } } - } - else if (RawPlayer == DosBox) - { + break; + + case DosBox1: while (score < scoredata + ScoreLen) { reg = *score++; @@ -420,11 +455,42 @@ int OPLmusicFile::PlayTick () io->OPLwriteReg(WhichChip, reg, data); } } - } - else if (RawPlayer == IMF) - { - WORD delay = 0; + break; + case DosBox2: + { + BYTE *to_reg = scoredata + 0x1A; + BYTE to_reg_size = scoredata[0x19]; + BYTE short_delay_code = scoredata[0x17]; + BYTE long_delay_code = scoredata[0x18]; + + while (score < scoredata + ScoreLen) + { + BYTE code = *score++; + data = *score++; + + // Which OPL chip to write to is encoded in the high bit of the code value. + int which = !!(code & 0x80); + code &= 0x7F; + + if (code == short_delay_code) + { + return data + 1; + } + else if (code == long_delay_code) + { + return (data + 1) << 8; + } + else if (code < to_reg_size && (which = 0 || TwoChips)) + { + io->OPLwriteReg(which, to_reg[code], data); + } + } + } + break; + + case IMF: + delay = 0; while (delay == 0 && score + 4 - scoredata <= ScoreLen) { if (*(DWORD *)score == 0xFFFFFFFF) diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h index 58cadc8b6..d1357e0e0 100644 --- a/src/oplsynth/opl_mus_player.h +++ b/src/oplsynth/opl_mus_player.h @@ -41,7 +41,7 @@ protected: OPLmusicFile() {} int PlayTick(); - enum { RDosPlay, IMF, DosBox } RawPlayer; + enum { RDosPlay, IMF, DosBox1, DosBox2 } RawPlayer; int ScoreLen; int WhichChip; }; diff --git a/src/p_3dfloors.cpp b/src/p_3dfloors.cpp index b9b82a566..390fc23cf 100644 --- a/src/p_3dfloors.cpp +++ b/src/p_3dfloors.cpp @@ -680,5 +680,32 @@ void P_Spawn3DFloors (void) } +//========================================================================== +// +// Returns a 3D floorplane appropriate for the given coordinates +// +//========================================================================== + +secplane_t P_FindFloorPlane(sector_t * sector, fixed_t x, fixed_t y, fixed_t z) +{ + secplane_t retplane = sector->floorplane; + if (sector->e) // apparently this can be called when the data is already gone + { + for(unsigned int i=0;ie->XFloor.ffloors.Size();i++) + { + F3DFloor * rover= sector->e->XFloor.ffloors[i]; + if(!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue; + + if (rover->top.plane->ZatPoint(x, y) == z) + { + retplane = *rover->top.plane; + if (retplane.c<0) retplane.FlipVert(); + break; + } + } + } + return retplane; +} + #endif \ No newline at end of file diff --git a/src/p_3dfloors.h b/src/p_3dfloors.h index dfaf5a5eb..4cdb14506 100644 --- a/src/p_3dfloors.h +++ b/src/p_3dfloors.h @@ -125,6 +125,8 @@ struct FLineOpening; void P_LineOpening_XFloors (FLineOpening &open, AActor * thing, const line_t *linedef, fixed_t x, fixed_t y, fixed_t refx, fixed_t refy); + +secplane_t P_FindFloorPlane(sector_t * sector, fixed_t x, fixed_t y, fixed_t z); #else @@ -156,6 +158,8 @@ struct FLineOpening; inline void P_LineOpening_XFloors (FLineOpening &open, AActor * thing, const line_t *linedef, fixed_t x, fixed_t y, fixed_t refx, fixed_t refy) {} +//secplane_t P_FindFloorPlane(sector_t * sector, fixed_t x, fixed_t y, fixed_t z){return sector->floorplane;} + #endif diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 97680d6e2..eae0a2561 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -70,6 +70,7 @@ #include "cmdlib.h" #include "m_png.h" #include "p_setup.h" +#include "po_man.h" #include "g_shared/a_pickups.h" @@ -1084,8 +1085,8 @@ FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) if (Format == ACS_Old) { - StringTable = ((DWORD *)Data)[1]; - StringTable += ((DWORD *)(Data + StringTable))[0] * 12 + 4; + StringTable = LittleLong(((DWORD *)Data)[1]); + StringTable += LittleLong(((DWORD *)(Data + StringTable))[0]) * 12 + 4; } else { @@ -1396,8 +1397,8 @@ void FBehavior::LoadScriptsDirectory () switch (Format) { case ACS_Old: - scripts.dw = (DWORD *)(Data + ((DWORD *)Data)[1]); // FIXME: Has this been byte-swapped before-hand? - NumScripts = scripts.dw[0]; + scripts.dw = (DWORD *)(Data + LittleLong(((DWORD *)Data)[1])); + NumScripts = LittleLong(scripts.dw[0]); if (NumScripts != 0) { scripts.dw++; @@ -1426,7 +1427,7 @@ void FBehavior::LoadScriptsDirectory () } else if (*(DWORD *)Data != MAKE_ID('A','C','S',0)) { - NumScripts = scripts.dw[1] / 12; + NumScripts = LittleLong(scripts.dw[1]) / 12; Scripts = new ScriptPtr[NumScripts]; scripts.dw += 2; @@ -1443,7 +1444,7 @@ void FBehavior::LoadScriptsDirectory () } else { - NumScripts = scripts.dw[1] / 8; + NumScripts = LittleLong(scripts.dw[1]) / 8; Scripts = new ScriptPtr[NumScripts]; scripts.dw += 2; @@ -1487,7 +1488,7 @@ void FBehavior::LoadScriptsDirectory () // Make the closed version the first one. if (Scripts[i+1].Type == SCRIPT_Closed) { - swap(Scripts[i], Scripts[i+1]); + swapvalues(Scripts[i], Scripts[i+1]); } } } @@ -1759,7 +1760,7 @@ const char *FBehavior::LookupString (DWORD index) const if (index >= list[0]) return NULL; // Out of range for this list; - return (const char *)(Data + list[1+index]); + return (const char *)(Data + LittleLong(list[1+index])); } else { @@ -2051,7 +2052,7 @@ int DLevelScript::Random (int min, int max) { if (max < min) { - swap (max, min); + swapvalues (max, min); } return min + pr_acs(max - min + 1); @@ -2430,6 +2431,93 @@ void DLevelScript::DoSetFont (int fontnum) } } +int DoSetMaster (AActor *self, AActor *master) +{ + AActor *defs; + if (self->flags3&MF3_ISMONSTER) + { + if (master) + { + if (master->flags3&MF3_ISMONSTER) + { + self->FriendPlayer = 0; + self->master = master; + level.total_monsters -= self->CountsAsKill(); + self->flags = (self->flags & ~MF_FRIENDLY) | (master->flags & MF_FRIENDLY); + level.total_monsters += self->CountsAsKill(); + // Don't attack your new master + if (self->target == self->master) self->target = NULL; + if (self->lastenemy == self->master) self->lastenemy = NULL; + if (self->LastHeard == self->master) self->LastHeard = NULL; + return 1; + } + else if (master->player) + { + // [KS] Be friendly to this player + self->master = NULL; + level.total_monsters -= self->CountsAsKill(); + self->flags|=MF_FRIENDLY; + self->FriendPlayer = int(master->player-players+1); + + AActor * attacker=master->player->attacker; + if (attacker) + { + if (!(attacker->flags&MF_FRIENDLY) || + (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=self->FriendPlayer)) + { + self->LastHeard = self->target = attacker; + } + } + // And stop attacking him if necessary. + if (self->target == master) self->target = NULL; + if (self->lastenemy == master) self->lastenemy = NULL; + if (self->LastHeard == master) self->LastHeard = NULL; + return 1; + } + } + else + { + self->master = NULL; + self->FriendPlayer = 0; + // Go back to whatever friendliness we usually have... + defs = self->GetDefault(); + level.total_monsters -= self->CountsAsKill(); + self->flags = (self->flags & ~MF_FRIENDLY) | (defs->flags & MF_FRIENDLY); + level.total_monsters += self->CountsAsKill(); + // ...And re-side with our friends. + if (self->target && !self->IsHostile (self->target)) self->target = NULL; + if (self->lastenemy && !self->IsHostile (self->lastenemy)) self->lastenemy = NULL; + if (self->LastHeard && !self->IsHostile (self->LastHeard)) self->LastHeard = NULL; + return 1; + } + } + return 0; +} + +int DoGetMasterTID (AActor *self) +{ + if (self->master) return self->master->tid; + else if (self->FriendPlayer) + { + player_t *player = &players[(self->FriendPlayer)-1]; + return player->mo->tid; + } + else return 0; +} + +static AActor *SingleActorFromTID (int tid, AActor *defactor) +{ + if (tid == 0) + { + return defactor; + } + else + { + FActorIterator iterator (tid); + return iterator.Next(); + } +} + enum { APROP_Health = 0, @@ -2457,6 +2545,7 @@ enum APROP_Score = 22, APROP_Notrigger = 23, APROP_DamageFactor = 24, + APROP_MasterTID = 25, }; // These are needed for ACS's APROP_RenderStyle @@ -2573,9 +2662,15 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) case APROP_Friendly: if (value) + { + if (actor->CountsAsKill()) level.total_monsters--; actor->flags |= MF_FRIENDLY; + } else + { actor->flags &= ~MF_FRIENDLY; + if (actor->CountsAsKill()) level.total_monsters++; + } break; @@ -2625,25 +2720,18 @@ void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) actor->DamageFactor = value; break; + case APROP_MasterTID: + AActor *other; + other = SingleActorFromTID (value, NULL); + DoSetMaster (actor, other); + break; + default: // do nothing. break; } } -static AActor *SingleActorFromTID (int tid, AActor *defactor) -{ - if (tid == 0) - { - return defactor; - } - else - { - FActorIterator iterator (tid); - return iterator.Next(); - } -} - int DLevelScript::GetActorProperty (int tid, int property) { AActor *actor = SingleActorFromTID (tid, activator); @@ -2670,6 +2758,7 @@ int DLevelScript::GetActorProperty (int tid, int property) // so pretends it's normal. return STYLE_Normal; case APROP_Gravity: return actor->gravity; + case APROP_Invulnerable:return !!(actor->flags2 & MF2_INVULNERABLE); case APROP_Ambush: return !!(actor->flags & MF_AMBUSH); case APROP_Dropped: return !!(actor->flags & MF_DROPPED); case APROP_ChaseGoal: return !!(actor->flags5 & MF5_CHASEGOAL); @@ -2695,6 +2784,7 @@ int DLevelScript::GetActorProperty (int tid, int property) return 0; } case APROP_Score: return actor->Score; + case APROP_MasterTID: return DoGetMasterTID (actor); default: return 0; } } @@ -2725,10 +2815,12 @@ int DLevelScript::CheckActorProperty (int tid, int property, int value) case APROP_SpawnHealth: case APROP_JumpZ: case APROP_Score: + case APROP_MasterTID: return (GetActorProperty(tid, property) == value); // Boolean values need to compare to a binary version of value case APROP_Ambush: + case APROP_Invulnerable: case APROP_Dropped: case APROP_ChaseGoal: case APROP_Frightened: @@ -2940,6 +3032,9 @@ enum EACSFunctions ACSF_SoundSequenceOnActor, ACSF_SoundSequenceOnSector, ACSF_SoundSequenceOnPolyobj, + ACSF_GetPolyobjX, + ACSF_GetPolyobjY, + ACSF_CheckSight, }; int DLevelScript::SideFromID(int id, int side) @@ -3339,6 +3434,74 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) } break; + case ACSF_GetPolyobjX: + { + FPolyObj *poly = PO_GetPolyobj(args[0]); + if (poly != NULL) + { + return poly->StartSpot.x; + } + } + return FIXED_MAX; + + case ACSF_GetPolyobjY: + { + FPolyObj *poly = PO_GetPolyobj(args[0]); + if (poly != NULL) + { + return poly->StartSpot.y; + } + } + return FIXED_MAX; + + case ACSF_CheckSight: + { + AActor *source; + AActor *dest; + + int flags = SF_IGNOREVISIBILITY; + + if (args[2] & 1) flags |= SF_IGNOREWATERBOUNDARY; + if (args[2] & 2) flags |= SF_SEEPASTBLOCKEVERYTHING | SF_SEEPASTSHOOTABLELINES; + + if (args[0] == 0) + { + source = (AActor *) activator; + + if (args[1] == 0) return 1; // [KS] I'm sure the activator can see itself. + + TActorIterator dstiter (args[1]); + + while ( (dest = dstiter.Next ()) ) + { + if (P_CheckSight(source, dest, flags)) return 1; + } + } + else + { + TActorIterator srciter (args[0]); + + if (args[1] == 0) dest = (AActor *) activator; + + while ( (source = srciter.Next ()) ) + { + if (args[1] != 0) + { + TActorIterator dstiter (args[1]); + while ( (dest = dstiter.Next ()) ) + { + if (P_CheckSight(source, dest, flags)) return 1; + } + } + else + { + if (P_CheckSight(source, dest, flags)) return 1; + } + } + } + return 0; + } + default: break; } @@ -3346,6 +3509,13 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) return 0; } +enum +{ + PRINTNAME_LEVELNAME = -1, + PRINTNAME_LEVEL = -2, + PRINTNAME_SKILL = -3, +}; + #define NEXTWORD (LittleLong(*pc++)) #define NEXTBYTE (fmt==ACS_LittleEnhanced?getbyte(pc):NEXTWORD) @@ -3367,6 +3537,20 @@ inline int getshort (int *&pc) return res; } +// Read a possibly unaligned four-byte little endian integer. +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) +inline int uallong(int &foo) +{ + return foo; +} +#else +inline int uallong(int &foo) +{ + unsigned char *bar = (unsigned char *)&foo; + return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24); +} +#endif + int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; @@ -3481,7 +3665,8 @@ int DLevelScript::RunScript () break; case PCD_PUSHNUMBER: - PushToStack (NEXTWORD); + PushToStack (uallong(pc[0])); + pc++; break; case PCD_PUSHBYTE: @@ -3538,7 +3723,7 @@ int DLevelScript::RunScript () break; case PCD_SWAP: - swap(Stack[sp-2], Stack[sp-1]); + swapvalues(Stack[sp-2], Stack[sp-1]); break; case PCD_LSPEC1: @@ -3583,35 +3768,35 @@ int DLevelScript::RunScript () case PCD_LSPEC1DIRECT: temp = NEXTBYTE; LineSpecials[temp] (activationline, activator, backSide, - pc[0], 0, 0, 0, 0); + uallong(pc[0]), 0, 0, 0, 0); pc += 1; break; case PCD_LSPEC2DIRECT: temp = NEXTBYTE; LineSpecials[temp] (activationline, activator, backSide, - pc[0], pc[1], 0, 0, 0); + uallong(pc[0]), uallong(pc[1]), 0, 0, 0); pc += 2; break; case PCD_LSPEC3DIRECT: temp = NEXTBYTE; LineSpecials[temp] (activationline, activator, backSide, - pc[0], pc[1], pc[2], 0, 0); + uallong(pc[0]), uallong(pc[1]), uallong(pc[2]), 0, 0); pc += 3; break; case PCD_LSPEC4DIRECT: temp = NEXTBYTE; LineSpecials[temp] (activationline, activator, backSide, - pc[0], pc[1], pc[2], pc[3], 0); + uallong(pc[0]), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), 0); pc += 4; break; case PCD_LSPEC5DIRECT: temp = NEXTBYTE; LineSpecials[temp] (activationline, activator, backSide, - pc[0], pc[1], pc[2], pc[3], pc[4]); + uallong(pc[0]), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), uallong(pc[4])); pc += 5; break; @@ -4491,12 +4676,12 @@ int DLevelScript::RunScript () break; case PCD_GOTO: - pc = activeBehavior->Ofs2PC (*pc); + pc = activeBehavior->Ofs2PC (LittleLong(*pc)); break; case PCD_IFGOTO: if (STACK(1)) - pc = activeBehavior->Ofs2PC (*pc); + pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; @@ -4518,7 +4703,8 @@ int DLevelScript::RunScript () break; case PCD_DELAYDIRECT: - statedata = NEXTWORD + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); + statedata = uallong(pc[0]) + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); + pc++; if (statedata > 0) { state = SCRIPT_Delayed; @@ -4540,7 +4726,7 @@ int DLevelScript::RunScript () break; case PCD_RANDOMDIRECT: - PushToStack (Random (pc[0], pc[1])); + PushToStack (Random (uallong(pc[0]), uallong(pc[1]))); pc += 2; break; @@ -4555,7 +4741,7 @@ int DLevelScript::RunScript () break; case PCD_THINGCOUNTDIRECT: - PushToStack (ThingCount (pc[0], -1, pc[1], -1)); + PushToStack (ThingCount (uallong(pc[0]), -1, uallong(pc[1]), -1)); pc += 2; break; @@ -4582,7 +4768,8 @@ int DLevelScript::RunScript () case PCD_TAGWAITDIRECT: state = SCRIPT_TagWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; break; case PCD_POLYWAIT: @@ -4593,7 +4780,8 @@ int DLevelScript::RunScript () case PCD_POLYWAITDIRECT: state = SCRIPT_PolyWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; break; case PCD_CHANGEFLOOR: @@ -4602,7 +4790,7 @@ int DLevelScript::RunScript () break; case PCD_CHANGEFLOORDIRECT: - ChangeFlat (pc[0], pc[1], 0); + ChangeFlat (uallong(pc[0]), uallong(pc[1]), 0); pc += 2; break; @@ -4612,7 +4800,7 @@ int DLevelScript::RunScript () break; case PCD_CHANGECEILINGDIRECT: - ChangeFlat (pc[0], pc[1], 1); + ChangeFlat (uallong(pc[0]), uallong(pc[1]), 1); pc += 2; break; @@ -4674,7 +4862,7 @@ int DLevelScript::RunScript () case PCD_IFNOTGOTO: if (!STACK(1)) - pc = activeBehavior->Ofs2PC (*pc); + pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; @@ -4696,7 +4884,8 @@ int DLevelScript::RunScript () case PCD_SCRIPTWAITDIRECT: state = SCRIPT_ScriptWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; PutLast (); break; @@ -4706,14 +4895,14 @@ int DLevelScript::RunScript () break; case PCD_CASEGOTO: - if (STACK(1) == NEXTWORD) + if (STACK(1) == uallong(pc[0])) { - pc = activeBehavior->Ofs2PC (*pc); + pc = activeBehavior->Ofs2PC (uallong(pc[1])); sp--; } else { - pc++; + pc += 2; } break; @@ -4721,7 +4910,7 @@ int DLevelScript::RunScript () // The count and jump table are 4-byte aligned pc = (int *)(((size_t)pc + 3) & ~3); { - int numcases = NEXTWORD; + int numcases = uallong(pc[0]); pc++; int min = 0, max = numcases-1; while (min <= max) { @@ -4729,7 +4918,7 @@ int DLevelScript::RunScript () SDWORD caseval = pc[mid*2]; if (caseval == STACK(1)) { - pc = activeBehavior->Ofs2PC (pc[mid*2+1]); + pc = activeBehavior->Ofs2PC (LittleLong(pc[mid*2+1])); sp--; break; } @@ -4799,7 +4988,31 @@ int DLevelScript::RunScript () { player_t *player = NULL; - if (STACK(1) == 0 || (unsigned)STACK(1) > MAXPLAYERS) + if (STACK(1) < 0) + { + switch (STACK(1)) + { + case PRINTNAME_LEVELNAME: + work += level.LevelName; + break; + + case PRINTNAME_LEVEL: + work += level.mapname; + break; + + case PRINTNAME_SKILL: + work += G_SkillName(); + break; + + default: + work += ' '; + break; + } + sp--; + break; + + } + else if (STACK(1) == 0 || (unsigned)STACK(1) > MAXPLAYERS) { if (activator) { @@ -4881,7 +5094,7 @@ int DLevelScript::RunScript () { int key1 = 0, key2 = 0; - C_GetKeysForCommand ((char *)lookup, &key1, &key2); + Bindings.GetKeysForCommand ((char *)lookup, &key1, &key2); if (key2) work << KeyNames[key1] << " or " << KeyNames[key2]; @@ -5025,7 +5238,7 @@ int DLevelScript::RunScript () break; case PCD_SETFONTDIRECT: - DoSetFont (pc[0]); + DoSetFont (uallong(pc[0])); pc++; break; @@ -5314,7 +5527,7 @@ int DLevelScript::RunScript () break; case PCD_SETGRAVITYDIRECT: - level.gravity = (float)pc[0] / 65536.f; + level.gravity = (float)uallong(pc[0]) / 65536.f; pc++; break; @@ -5325,7 +5538,7 @@ int DLevelScript::RunScript () break; case PCD_SETAIRCONTROLDIRECT: - level.aircontrol = pc[0]; + level.aircontrol = uallong(pc[0]); pc++; G_AirControlChanged (); break; @@ -5336,7 +5549,7 @@ int DLevelScript::RunScript () break; case PCD_SPAWNDIRECT: - PushToStack (DoSpawn (pc[0], pc[1], pc[2], pc[3], pc[4], pc[5], false)); + PushToStack (DoSpawn (uallong(pc[0]), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), uallong(pc[4]), uallong(pc[5]), false)); pc += 6; break; @@ -5346,7 +5559,7 @@ int DLevelScript::RunScript () break; case PCD_SPAWNSPOTDIRECT: - PushToStack (DoSpawnSpot (pc[0], pc[1], pc[2], pc[3], false)); + PushToStack (DoSpawnSpot (uallong(pc[0]), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), false)); pc += 4; break; @@ -5402,7 +5615,7 @@ int DLevelScript::RunScript () break; case PCD_GIVEINVENTORYDIRECT: - GiveInventory (activator, FBehavior::StaticLookupString (pc[0]), pc[1]); + GiveInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); pc += 2; break; @@ -5432,7 +5645,7 @@ int DLevelScript::RunScript () break; case PCD_TAKEINVENTORYDIRECT: - TakeInventory (activator, FBehavior::StaticLookupString (pc[0]), pc[1]); + TakeInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); pc += 2; break; @@ -5447,7 +5660,7 @@ int DLevelScript::RunScript () break; case PCD_CHECKINVENTORYDIRECT: - PushToStack (CheckInventory (activator, FBehavior::StaticLookupString (pc[0]))); + PushToStack (CheckInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])))); pc += 1; break; @@ -5551,7 +5764,7 @@ int DLevelScript::RunScript () break; case PCD_SETMUSICDIRECT: - S_ChangeMusic (FBehavior::StaticLookupString (pc[0]), pc[1]); + S_ChangeMusic (FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); pc += 3; break; @@ -5566,7 +5779,7 @@ int DLevelScript::RunScript () case PCD_LOCALSETMUSICDIRECT: if (activator == players[consoleplayer].mo) { - S_ChangeMusic (FBehavior::StaticLookupString (pc[0]), pc[1]); + S_ChangeMusic (FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); } pc += 3; break; @@ -6214,12 +6427,7 @@ int DLevelScript::RunScript () case PCD_CHANGELEVEL: { - int flags = STACK(2); - G_ChangeLevel(FBehavior::StaticLookupString(STACK(4)), STACK(3), - !!(flags & CHANGELEVEL_KEEPFACING), STACK(1), - !!(flags & CHANGELEVEL_NOINTERMISSION), - !!(flags & CHANGELEVEL_RESETINVENTORY), - !!(flags & CHANGELEVEL_NOMONSTERS)); + G_ChangeLevel(FBehavior::StaticLookupString(STACK(4)), STACK(3), STACK(2), STACK(1)); sp -= 4; } break; diff --git a/src/p_acs.h b/src/p_acs.h index c57af5793..8e193d41e 100644 --- a/src/p_acs.h +++ b/src/p_acs.h @@ -667,16 +667,6 @@ public: SCRIPT_ModulusBy0, }; - enum - { - CHANGELEVEL_KEEPFACING = 1, - CHANGELEVEL_RESETINVENTORY = 2, - CHANGELEVEL_NOMONSTERS = 4, - CHANGELEVEL_CHANGESKILL = 8, - CHANGELEVEL_NOINTERMISSION = 16 - }; - - DLevelScript (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, bool backSide, int arg0, int arg1, int arg2, int always); ~DLevelScript (); diff --git a/src/p_buildmap.cpp b/src/p_buildmap.cpp index de7f1dfc1..73338c7a6 100644 --- a/src/p_buildmap.cpp +++ b/src/p_buildmap.cpp @@ -432,6 +432,7 @@ static void LoadSectors (sectortype *bsec) sec->lightlevel = (sec->GetPlaneLight(sector_t::floor) + sec->GetPlaneLight(sector_t::ceiling)) / 2; sec->seqType = -1; + sec->SeqName = NAME_None; sec->nextsec = -1; sec->prevsec = -1; sec->gravity = 1.f; diff --git a/src/p_ceiling.cpp b/src/p_ceiling.cpp index 4dd072a06..c9aec4f37 100644 --- a/src/p_ceiling.cpp +++ b/src/p_ceiling.cpp @@ -65,6 +65,10 @@ void DCeiling::PlayCeilingSound () { SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->seqType, SEQ_PLATFORM, 0, false); } + else if (m_Sector->SeqName != NAME_None) + { + SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->SeqName, 0); + } else { if (m_Silent == 2) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 614d6e4b1..5acac4a0e 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -59,6 +59,8 @@ #include "doomstat.h" #include "c_console.h" #include "sbar.h" +#include "farchive.h" +#include "p_lnspec.h" // The conversations as they exist inside a SCRIPTxx lump. struct Response @@ -103,19 +105,20 @@ void GiveSpawner (player_t *player, const PClass *type); TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -PClass *StrifeTypes[1001]; +typedef TMap FStrifeTypeMap; // maps conversation IDs to actor classes +typedef TMap FDialogueIDMap; // maps dialogue IDs to dialogue array index (for ACS) +typedef TMap FDialogueMap; // maps actor class names to dialogue array index + +static FStrifeTypeMap StrifeTypes; +static FDialogueIDMap DialogueRoots; +static FDialogueMap ClassRoots; static menu_t ConversationMenu; static TArray ConversationItems; static int ConversationPauseTic; static bool ShowGold; -static void LoadScriptFile (const char *name); -static void LoadScriptFile(FileReader *lump, int numnodes); +static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type); static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeakerType); static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeakerType); static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses); @@ -139,13 +142,42 @@ static FBrokenLines *DialogueLines; // //============================================================================ -static PClass *GetStrifeType (int typenum) +void SetStrifeType(int convid, PClassActor *Class) { - if (typenum > 0 && typenum < 1001) + StrifeTypes[convid] = Class; +} + +void SetConversation(int convid, PClassActor *Class, int dlgindex) +{ + if (convid != -1) { - return StrifeTypes[typenum]; + DialogueRoots[convid] = dlgindex; } - return NULL; + if (Class != NULL) + { + ClassRoots[Class->TypeName] = dlgindex; + } +} + +PClassActor *GetStrifeType (int typenum) +{ + PClassActor **ptype = StrifeTypes.CheckKey(typenum); + if (ptype == NULL) return NULL; + else return *ptype; +} + +int GetConversation(int conv_id) +{ + int *pindex = DialogueRoots.CheckKey(conv_id); + if (pindex == NULL) return -1; + else return *pindex; +} + +int GetConversation(FName classname) +{ + int *pindex = ClassRoots.CheckKey(classname); + if (pindex == NULL) return -1; + else return *pindex; } //============================================================================ @@ -158,11 +190,11 @@ static PClass *GetStrifeType (int typenum) void P_LoadStrifeConversations (MapData *map, const char *mapname) { + P_FreeStrifeConversations (); if (map->Size(ML_CONVERSATION) > 0) { - LoadScriptFile ("SCRIPT00"); map->Seek(ML_CONVERSATION); - LoadScriptFile (map->file, map->Size(ML_CONVERSATION)); + LoadScriptFile (map->lumpnum, map->file, map->Size(ML_CONVERSATION), false, 0); } else { @@ -170,10 +202,16 @@ void P_LoadStrifeConversations (MapData *map, const char *mapname) { return; } - char scriptname[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 }; + char scriptname_b[9] = { 'S','C','R','I','P','T',mapname[3],mapname[4],0 }; + char scriptname_t[9] = { 'D','I','A','L','O','G',mapname[3],mapname[4],0 }; - LoadScriptFile ("SCRIPT00"); - LoadScriptFile (scriptname); + if (!LoadScriptFile(scriptname_t, false, 2)) + { + if (!LoadScriptFile (scriptname_b, false, 1)) + { + LoadScriptFile ("SCRIPT00", false, 1); + } + } } } @@ -192,36 +230,13 @@ void P_FreeStrifeConversations () delete node; } - for (int i = 0; i < 344; ++i) - { - if (StrifeTypes[i] != NULL) - { - AActor * ac = GetDefaultByType (StrifeTypes[i]); - if (ac != NULL) ac->Conversation = NULL; - } - } + DialogueRoots.Clear(); + ClassRoots.Clear(); CurNode = NULL; PrevNode = NULL; } -//============================================================================ -// -// ncopystring -// -// If the string has no content, returns NULL. Otherwise, returns a copy. -// -//============================================================================ - -static char *ncopystring (const char *string) -{ - if (string == NULL || string[0] == 0) - { - return NULL; - } - return copystring (string); -} - //============================================================================ // // LoadScriptFile @@ -230,61 +245,89 @@ static char *ncopystring (const char *string) // //============================================================================ -static void LoadScriptFile (const char *name) +bool LoadScriptFile (const char *name, bool include, int type) { int lumpnum = Wads.CheckNumForName (name); FileReader *lump; if (lumpnum < 0) { - return; + return false; } lump = Wads.ReopenLumpNum (lumpnum); - LoadScriptFile(lump, Wads.LumpLength(lumpnum)); + bool res = LoadScriptFile(lumpnum, lump, Wads.LumpLength(lumpnum), include, type); delete lump; + return res; } -static void LoadScriptFile(FileReader *lump, int numnodes) +static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type) { int i; DWORD prevSpeakerType; FStrifeDialogueNode *node; + char buffer[4]; - if (!(gameinfo.flags & GI_SHAREWARE)) + lump->Read(buffer, 4); + lump->Seek(-4, SEEK_CUR); + + // The binary format is so primitive that this check is enough to detect it. + bool isbinary = (buffer[0] == 0 || buffer[1] == 0 || buffer[2] == 0 || buffer[3] == 0); + + if ((type == 1 && !isbinary) || (type == 2 && isbinary)) { - // Strife scripts are always a multiple of 1516 bytes because each entry - // is exactly 1516 bytes long. - if (numnodes % 1516 != 0) - { - return; - } - numnodes /= 1516; - } - else - { - // And the teaser version has 1488-byte entries. - if (numnodes % 1488 != 0) - { - return; - } - numnodes /= 1488; + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; } - prevSpeakerType = 0; - - for (i = 0; i < numnodes; ++i) + if (!isbinary) { + P_ParseUSDF(lumpnum, lump, numnodes); + } + else + { + if (!include) + { + LoadScriptFile("SCRIPT00", true, 1); + } if (!(gameinfo.flags & GI_SHAREWARE)) { - node = ReadRetailNode (lump, prevSpeakerType); + // Strife scripts are always a multiple of 1516 bytes because each entry + // is exactly 1516 bytes long. + if (numnodes % 1516 != 0) + { + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; + } + numnodes /= 1516; } else { - node = ReadTeaserNode (lump, prevSpeakerType); + // And the teaser version has 1488-byte entries. + if (numnodes % 1488 != 0) + { + DPrintf("Incorrect data format for %s.", Wads.GetLumpFullName(lumpnum)); + return false; + } + numnodes /= 1488; + } + + prevSpeakerType = 0; + + for (i = 0; i < numnodes; ++i) + { + if (!(gameinfo.flags & GI_SHAREWARE)) + { + node = ReadRetailNode (lump, prevSpeakerType); + } + else + { + node = ReadTeaserNode (lump, prevSpeakerType); + } + node->ThisNodeNum = StrifeDialogues.Push(node); } - node->ThisNodeNum = StrifeDialogues.Push(node); } + return true; } //============================================================================ @@ -300,7 +343,7 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker FStrifeDialogueNode *node; Speech speech; char fullsound[16]; - const PClass *type; + PClassActor *type; int j; node = new FStrifeDialogueNode; @@ -316,12 +359,14 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker // actor, so newly spawned actors will use this conversation by default. type = GetStrifeType (speech.SpeakerType); node->SpeakerType = type; - if (prevSpeakerType != speech.SpeakerType) + + if (speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType) { if (type != NULL) { - GetDefaultByType (type)->Conversation = node; + ClassRoots[type->TypeName] = StrifeDialogues.Size(); } + DialogueRoots[speech.SpeakerType] = StrifeDialogues.Size(); prevSpeakerType = speech.SpeakerType; } @@ -339,15 +384,17 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker // The speaker's name, if any. speech.Sound[0] = 0; //speech.Name[16] = 0; - node->SpeakerName = ncopystring (speech.Name); + node->SpeakerName = ncopystring(speech.Name); // The item the speaker should drop when killed. - node->DropType = dyn_cast(GetStrifeType (speech.DropType)); + node->DropType = dyn_cast(GetStrifeType(speech.DropType)); // Items you need to have to make the speaker use a different node. + node->ItemCheck.Resize(3); for (j = 0; j < 3; ++j) { - node->ItemCheck[j] = dyn_cast(GetStrifeType (speech.ItemCheck[j])); + node->ItemCheck[j].Item = dyn_cast(GetStrifeType(speech.ItemCheck[j])); + node->ItemCheck[j].Amount = -1; } node->ItemCheckNode = speech.Link; node->Children = NULL; @@ -370,7 +417,7 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker FStrifeDialogueNode *node; TeaserSpeech speech; char fullsound[16]; - const PClass *type; + PClassActor *type; int j; node = new FStrifeDialogueNode; @@ -383,14 +430,16 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker // Assign the first instance of a conversation as the default for its // actor, so newly spawned actors will use this conversation by default. - type = GetStrifeType (speech.SpeakerType); + type = GetStrifeType(speech.SpeakerType); node->SpeakerType = type; - if (prevSpeakerType != speech.SpeakerType) + + if (speech.SpeakerType >= 0 && prevSpeakerType != speech.SpeakerType) { if (type != NULL) { - GetDefaultByType (type)->Conversation = node; + ClassRoots[type->TypeName] = StrifeDialogues.Size(); } + DialogueRoots[speech.SpeakerType] = StrifeDialogues.Size(); prevSpeakerType = speech.SpeakerType; } @@ -419,9 +468,11 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker node->DropType = dyn_cast(GetStrifeType (speech.DropType)); // Items you need to have to make the speaker use a different node. + node->ItemCheck.Resize(3); for (j = 0; j < 3; ++j) { - node->ItemCheck[j] = NULL; + node->ItemCheck[j].Item = NULL; + node->ItemCheck[j].Amount = -1; } node->ItemCheckNode = 0; node->Children = NULL; @@ -476,15 +527,18 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) // The message to record in the log for this reply. reply->LogNumber = rsp->Log; + reply->LogString = NULL; // The item to receive when this reply is used. reply->GiveType = dyn_cast(GetStrifeType (rsp->GiveType)); + reply->ActionSpecial = 0; // Do you need anything special for this reply to succeed? + reply->ItemCheck.Resize(3); for (k = 0; k < 3; ++k) { - reply->ItemCheck[k] = dyn_cast(GetStrifeType (rsp->Item[k])); - reply->ItemCheckAmount[k] = rsp->Count[k]; + reply->ItemCheck[k].Item = dyn_cast(GetStrifeType(rsp->Item[k])); + reply->ItemCheck[k].Amount = rsp->Count[k]; } // ReplyLines is calculated when the menu is shown. It is just Reply @@ -517,7 +571,7 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) { reply->QuickYes = ncopystring (rsp->Yes); } - if (reply->ItemCheck[0] != 0) + if (reply->ItemCheck[0].Item != 0) { reply->QuickNo = ncopystring (rsp->No); } @@ -713,13 +767,20 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang } // Check if we should jump to another node - while (CurNode->ItemCheck[0] != NULL) + while (CurNode->ItemCheck.Size() > 0 && CurNode->ItemCheck[0].Item != NULL) { - if (CheckStrifeItem (pc->player, CurNode->ItemCheck[0]) && - CheckStrifeItem (pc->player, CurNode->ItemCheck[1]) && - CheckStrifeItem (pc->player, CurNode->ItemCheck[2])) + bool jump = true; + for (i = 0; i < (int)CurNode->ItemCheck.Size(); ++i) { - int root = FindNode (pc->player->ConversationNPC->GetDefault()->Conversation); + if(!CheckStrifeItem (pc->player, CurNode->ItemCheck[i].Item, CurNode->ItemCheck[i].Amount)) + { + jump = false; + break; + } + } + if (jump) + { + int root = pc->player->ConversationNPC->ConversationRoot; CurNode = StrifeDialogues[root + CurNode->ItemCheckNode - 1]; } else @@ -755,6 +816,18 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang toSay = "Go away!"; // Ok, it's lame - but it doesn't look like an error to the player. ;) } } + else + { + // handle string table replacement + if (toSay[0] == '$') + { + toSay = GStrings(toSay + 1); + } + } + if (toSay == NULL) + { + toSay = "."; + } DialogueLines = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay); // Fill out the possible choices @@ -772,6 +845,12 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang for (j = 0; reply->ReplyLines[j].Width >= 0; ++j) { item.label = reply->ReplyLines[j].Text.LockBuffer(); + // handle string table replacement + if (item.label[0] == '$') + { + item.label = GStrings(item.label + 1); + } + item.b.position = j == 0 ? i : 0; item.c.extra = reply; ConversationItems.Push (item); @@ -1063,9 +1142,9 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply npc = player->ConversationNPC; // Check if you have the requisite items for this choice - for (i = 0; i < 3; ++i) + for (i = 0; i < (int)reply->ItemCheck.Size(); ++i) { - if (!CheckStrifeItem(player, reply->ItemCheck[i], reply->ItemCheckAmount[i])) + if (!CheckStrifeItem(player, reply->ItemCheck[i].Item, reply->ItemCheck[i].Amount)) { // No, you don't. Say so and let the NPC animate negatively. if (reply->QuickNo && isconsole) @@ -1132,22 +1211,38 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply } } + if (reply->ActionSpecial != 0) + { + takestuff |= !!LineSpecials[reply->ActionSpecial](NULL, player->mo, false, + reply->Args[0], reply->Args[1], reply->Args[2], reply->Args[3], reply->Args[4]); + } + // Take away required items if the give was successful or none was needed. if (takestuff) { - for (i = 0; i < 3; ++i) + for (i = 0; i < (int)reply->ItemCheck.Size(); ++i) { - TakeStrifeItem (player, reply->ItemCheck[i], reply->ItemCheckAmount[i]); + TakeStrifeItem (player, reply->ItemCheck[i].Item, reply->ItemCheck[i].Amount); } replyText = reply->QuickYes; } else { - replyText = "You seem to have enough!"; + replyText = "$txt_haveenough"; } // Update the quest log, if needed. - if (reply->LogNumber != 0) + if (reply->LogString != NULL) + { + const char *log = reply->LogString; + if (log[0] == '$') + { + log = GStrings(log + 1); + } + + player->SetLogText(log); + } + else if (reply->LogNumber != 0) { player->SetLogNumber(reply->LogNumber); } @@ -1162,7 +1257,7 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply // will show the new node right away without terminating the dialogue. if (reply->NextNode != 0) { - int rootnode = FindNode (npc->GetDefault()->Conversation); + int rootnode = npc->ConversationRoot; if (reply->NextNode < 0) { npc->Conversation = StrifeDialogues[rootnode - reply->NextNode - 1]; @@ -1298,6 +1393,12 @@ static void TerminalResponse (const char *str) { if (str != NULL) { + // handle string table replacement + if (str[0] == '$') + { + str = GStrings(str + 1); + } + if (StatusBar != NULL) { AddToConsole(-1, str); @@ -1316,3 +1417,26 @@ static void TerminalResponse (const char *str) } } } + + +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node) +{ + DWORD convnum; + if (arc.IsStoring()) + { + arc.WriteCount (node == NULL? ~0u : node->ThisNodeNum); + } + else + { + convnum = arc.ReadCount(); + if (convnum >= StrifeDialogues.Size()) + { + node = NULL; + } + else + { + node = StrifeDialogues[convnum]; + } + } + return arc; +} diff --git a/src/p_conversation.h b/src/p_conversation.h index 804f6b913..061e66855 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -1,26 +1,28 @@ #ifndef P_CONVERSATION_H #define P_CONVERSATION_H 1 -// TODO: Generalize the conversation system to something NWN-like that -// users can edit as simple text files. Particularly useful would be -// the ability to call ACS functions to implement AppearsWhen properties -// and ACS scripts to implement ActionTaken properties. -// TODO: Make this work in demos. +#include struct FStrifeDialogueReply; class FTexture; struct FBrokenLines; +struct FStrifeDialogueItemCheck +{ + PClassInventory *Item; + int Amount; +}; + // FStrifeDialogueNode holds text an NPC says to the player struct FStrifeDialogueNode { ~FStrifeDialogueNode (); PClassActor *DropType; - PClassActor *ItemCheck[3]; + TArray ItemCheck; int ThisNodeNum; // location of this node in StrifeDialogues int ItemCheckNode; // index into StrifeDialogues - const PClass *SpeakerType; + PClassActor *SpeakerType; char *SpeakerName; FSoundID SpeakerVoice; FTextureID Backdrop; @@ -36,12 +38,14 @@ struct FStrifeDialogueReply FStrifeDialogueReply *Next; PClassActor *GiveType; - PClassActor *ItemCheck[3]; - int ItemCheckAmount[3]; + int ActionSpecial; + int Args[5]; + TArray ItemCheck; char *Reply; char *QuickYes; int NextNode; // index into StrifeDialogues int LogNumber; + char *LogString; char *QuickNo; bool NeedsGold; @@ -50,14 +54,16 @@ struct FStrifeDialogueReply extern TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -extern PClass *StrifeTypes[1001]; - struct MapData; +void SetStrifeType(int convid, PClassActor *Class); +void SetConversation(int convid, PClassActor *Class, int dlgindex); +PClassActor *GetStrifeType (int typenum); +int GetConversation(int conv_id); +int GetConversation(FName classname); + +bool LoadScriptFile (const char *name, bool include, int type = 0); + void P_LoadStrifeConversations (MapData *map, const char *mapname); void P_FreeStrifeConversations (); @@ -66,4 +72,8 @@ void P_ResumeConversation (); void P_ConversationCommand (int netcode, int player, BYTE **stream); +class FileReader; +bool P_ParseUSDF(int lumpnum, FileReader *lump, int lumplen); + + #endif diff --git a/src/p_doors.cpp b/src/p_doors.cpp index 29ec1fc6b..e82adbe4c 100644 --- a/src/p_doors.cpp +++ b/src/p_doors.cpp @@ -237,6 +237,10 @@ void DDoor::DoorSound (bool raise) const { SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->seqType, SEQ_DOOR, choice); } + else if (m_Sector->SeqName != NAME_None) + { + SN_StartSequence (m_Sector, CHAN_CEILING, m_Sector->SeqName, choice); + } else { const char *snd; @@ -424,7 +428,7 @@ bool EV_DoDoor (DDoor::EVlDoor type, line_t *line, AActor *thing, // Otherwise, just let the current one continue. // FIXME: This should be check if the sound sequence has separate up/down // paths, not if it was manually set. - if (sec->seqType == -1 || SN_CheckSequence(sec, CHAN_CEILING) == NULL) + if ((sec->seqType < 0 && sec->SeqName == NAME_None) || SN_CheckSequence(sec, CHAN_CEILING) == NULL) { door->DoorSound (false); } @@ -626,6 +630,17 @@ void DAnimatedDoor::Tick () MoveCeiling (2048*FRACUNIT, m_BotDist, -1); m_Sector->ceilingdata = NULL; Destroy (); + // Unset blocking flags on lines that didn't start with them. Since the + // ceiling is down now, we shouldn't need this flag anymore to keep things + // from getting through. + if (!m_SetBlocking1) + { + m_Line1->flags &= ~ML_BLOCKING; + } + if (!m_SetBlocking2) + { + m_Line2->flags &= ~ML_BLOCKING; + } break; } else @@ -668,6 +683,14 @@ void DAnimatedDoor::Serialize (FArchive &arc) << m_Speed << m_Delay << basetex; + if (SaveVersion < 2336) + { + m_SetBlocking1 = m_SetBlocking2 = true; + } + else + { + arc << m_SetBlocking1 << m_SetBlocking2; + } if (arc.IsLoading()) { @@ -727,6 +750,8 @@ DAnimatedDoor::DAnimatedDoor (sector_t *sec, line_t *line, int speed, int delay) m_Delay = delay; m_Timer = m_Speed; m_Frame = 0; + m_SetBlocking1 = !!(m_Line1->flags & ML_BLOCKING); + m_SetBlocking2 = !!(m_Line2->flags & ML_BLOCKING); m_Line1->flags |= ML_BLOCKING; m_Line2->flags |= ML_BLOCKING; m_BotDist = m_Sector->ceilingplane.d; diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 0e64486a5..d0cc3b6bd 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -512,12 +512,12 @@ bool P_Move (AActor *actor) try_ok = true; for(int i=1; i < steps; i++) { - try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), dropoff, false, tm); + try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), dropoff, NULL, tm); if (!try_ok) break; } // killough 3/15/98: don't jump over dropoffs: - if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, dropoff, false, tm); + if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, dropoff, NULL, tm); // [GrafZahl] Interpolating monster movement as it is done here just looks bad // so make it switchable @@ -679,7 +679,7 @@ void P_DoNewChaseDir (AActor *actor, fixed_t deltax, fixed_t deltay) { if ((pr_newchasedir() > 200 || abs(deltay) > abs(deltax))) { - swap (d[1], d[2]); + swapvalues (d[1], d[2]); } if (d[1] == turnaround) @@ -997,7 +997,7 @@ void P_RandomChaseDir (AActor *actor) // try other directions if (pr_newchasedir() > 200 || abs(deltay) > abs(deltax)) { - swap (d[1], d[2]); + swapvalues (d[1], d[2]); } if (d[1] == turnaround) @@ -1556,7 +1556,7 @@ bool P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) if (actor->MissileState != NULL) { - actor->SetStateNF(actor->SeeState); + actor->SetState(actor->SeeState, true); actor->flags &= ~MF_JUSTHIT; } @@ -1978,14 +1978,17 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LookEx) if (self->target && !(self->flags & MF_INCHASE)) { - if (seestate) - { - self->SetState (seestate); - } - else - { - self->SetState (self->SeeState); - } + if (!(flags & LOF_NOJUMP)) + { + if (seestate) + { + self->SetState (seestate); + } + else + { + self->SetState (self->SeeState); + } + } } return 0; } @@ -2688,7 +2691,7 @@ void A_Chase(VMFrameStack *stack, AActor *self) // A_FaceTarget // //============================================================================= -void A_FaceTarget (AActor *self) +void A_FaceTarget (AActor *self, angle_t max_turn) { if (!self->target) return; @@ -2700,17 +2703,53 @@ void A_FaceTarget (AActor *self) } self->flags &= ~MF_AMBUSH; - self->angle = R_PointToAngle2 (self->x, self->y, self->target->x, self->target->y); - - if (self->target->flags & MF_SHADOW) + + angle_t target_angle = R_PointToAngle2 (self->x, self->y, self->target->x, self->target->y); + + // 0 means no limit. Also, if we turn in a single step anyways, no need to go through the algorithms. + // It also means that there is no need to check for going past the target. + if (max_turn && (max_turn < (angle_t)abs(self->angle - target_angle))) + { + if (self->angle > target_angle) + { + if (self->angle - target_angle < ANGLE_180) + { + self->angle -= max_turn; + } + else + { + self->angle += max_turn; + } + } + else + { + if (target_angle - self->angle < ANGLE_180) + { + self->angle += max_turn; + } + else + { + self->angle -= max_turn; + } + } + } + else + { + self->angle = target_angle; + } + + // This will never work well if the turn angle is limited. + if (max_turn == 0 && (self->angle == target_angle) && self->target->flags & MF_SHADOW) { self->angle += pr_facetarget.Random2() << 21; } } -DEFINE_ACTION_FUNCTION(AActor, A_FaceTarget) +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FaceTarget) { PARAM_ACTION_PROLOGUE; + PARAM_ANGLE_OPT(max_turn) { max_turn = 0; } + A_FaceTarget(self); return 0; } @@ -2810,7 +2849,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_ScreamAndUnblock) { PARAM_ACTION_PROLOGUE; CALL_ACTION(A_Scream, self); - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, true); return 0; } @@ -2840,7 +2879,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_ActiveAndUnblock) { PARAM_ACTION_PROLOGUE; CALL_ACTION(A_ActiveSound, self); - CALL_ACTION(A_NoBlocking, self); + A_Unblock(self, true); return 0; } @@ -3168,6 +3207,10 @@ void A_BossDeath(AActor *self) EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0, false); return; + case LEVEL_SPECLOWERFLOORTOHIGHEST: + EV_DoFloor (DFloor::floorLowerToHighest, NULL, 666, FRACUNIT, 0, 0, 0, false); + return; + case LEVEL_SPECOPENDOOR: EV_DoDoor (DDoor::doorOpen, NULL, NULL, 666, 8*FRACUNIT, 0, 0, 0); return; diff --git a/src/p_enemy.h b/src/p_enemy.h index 9c1aee156..0ab0f18f9 100644 --- a/src/p_enemy.h +++ b/src/p_enemy.h @@ -32,6 +32,7 @@ enum LO_Flags LOF_DONTCHASEGOAL = 4, LOF_NOSEESOUND = 8, LOF_FULLVOLSEESOUND = 16, + LOF_NOJUMP = 32, }; struct FLookExParams @@ -44,6 +45,7 @@ struct FLookExParams FState *seestate; }; +void P_DaggerAlert (AActor *target, AActor *emitter); void P_RecursiveSound (sector_t *sec, AActor *soundtarget, bool splash, int soundblocks); bool P_HitFriend (AActor *self); void P_NoiseAlert (AActor *target, AActor *emmiter, bool splash=false); @@ -55,6 +57,7 @@ AInventory *P_DropItem (AActor *source, PClassActor *type, int special, int chan void P_TossItem (AActor *item); bool P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params); void A_Weave(AActor *self, int xyspeed, int zspeed, fixed_t xydist, fixed_t zdist); +void A_Unblock(AActor *self, bool drop); DECLARE_ACTION(A_Look) DECLARE_ACTION(A_Wander) @@ -68,7 +71,7 @@ DECLARE_ACTION(A_FreezeDeathChunks) void A_BossDeath(AActor *self); void A_Chase(VMFrameStack *stack, AActor *self); -void A_FaceTarget (AActor *actor); +void A_FaceTarget (AActor *actor, angle_t max_turn = 0); 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 00407a36c..3fff563f8 100644 --- a/src/p_floor.cpp +++ b/src/p_floor.cpp @@ -44,6 +44,10 @@ static void StartFloorSound (sector_t *sec) { SN_StartSequence (sec, CHAN_FLOOR, sec->seqType, SEQ_PLATFORM, 0); } + else if (sec->SeqName != NAME_None) + { + SN_StartSequence (sec, CHAN_FLOOR, sec->SeqName, 0); + } else { SN_StartSequence (sec, CHAN_FLOOR, "Floor", 0); @@ -358,7 +362,7 @@ manual_floor: floor->m_Direction = 1; newheight = sec->FindLowestCeilingSurrounding (&spot); if (floortype == DFloor::floorRaiseAndCrush) - floor->m_FloorDestDist -= 8 * FRACUNIT; + newheight -= 8 * FRACUNIT; ceilingheight = sec->FindLowestCeilingPoint (&spot2); floor->m_FloorDestDist = sec->floorplane.PointToDist (spot, newheight); if (sec->floorplane.ZatPointDist (spot2, floor->m_FloorDestDist) > ceilingheight) diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp new file mode 100644 index 000000000..382f026b2 --- /dev/null +++ b/src/p_glnodes.cpp @@ -0,0 +1,1553 @@ +/* +** gl_nodes.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2005-2010 Christoph Oelckers +** 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 _MSC_VER +#include // for alloca() +#endif + +#ifndef _WIN32 +#include + +#else +#include + +#define rmdir _rmdir + +// TODO, maybe: stop using DWORD so I don't need to worry about conflicting +// with Windows' typedef. Then I could just include the header file instead +// of declaring everything here. +#define MAX_PATH 260 +#define CSIDL_LOCAL_APPDATA 0x001c +extern "C" __declspec(dllimport) long __stdcall SHGetFolderPathA(void *hwnd, int csidl, void *hToken, unsigned long dwFlags, char *pszPath); + +#endif + +#ifdef __APPLE__ +#include +#endif + +#include "templates.h" +#include "m_alloc.h" +#include "m_argv.h" +#include "c_dispatch.h" +#include "m_swap.h" +#include "g_game.h" +#include "i_system.h" +#include "w_wad.h" +#include "doomdef.h" +#include "p_local.h" +#include "nodebuild.h" +#include "doomstat.h" +#include "vectors.h" +#include "stats.h" +#include "doomerrors.h" +#include "p_setup.h" +#include "x86.h" +#include "version.h" +#include "md5.h" + +void P_GetPolySpots (MapData * lump, TArray &spots, TArray &anchors); + +CVAR(Bool, gl_cachenodes, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Float, gl_cachetime, 0.6f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +void P_LoadZNodes (FileReader &dalump, DWORD id); +static bool CheckCachedNodes(MapData *map); +static void CreateCachedNodes(MapData *map); + + +// fixed 32 bit gl_vert format v2.0+ (glBsp 1.91) +struct mapglvertex_t +{ + fixed_t x,y; +}; + +struct gl3_mapsubsector_t +{ + SDWORD numsegs; + SDWORD firstseg; // Index of first one; segs are stored sequentially. +}; + +struct glseg_t +{ + WORD v1; // start vertex (16 bit) + WORD v2; // end vertex (16 bit) + WORD linedef; // linedef, or -1 for minisegs + WORD side; // side on linedef: 0 for right, 1 for left + WORD partner; // corresponding partner seg, or 0xffff on one-sided walls +}; + +struct glseg3_t +{ + SDWORD v1; + SDWORD v2; + WORD linedef; + WORD side; + SDWORD partner; +}; + +struct gl5_mapnode_t +{ + SWORD x,y,dx,dy; // partition line + SWORD bbox[2][4]; // bounding box for each child + // If NF_SUBSECTOR is or'ed in, it's a subsector, + // else it's a node of another subtree. + DWORD children[2]; +}; + + + +//========================================================================== +// +// Collect all sidedefs which are not entirely covered by segs +// Old ZDBSPs could create such maps. If such a BSP is discovered +// a node rebuild must be done to ensure proper rendering +// +//========================================================================== + +static int CheckForMissingSegs() +{ + float *added_seglen = new float[numsides]; + int missing = 0; + + memset(added_seglen, 0, sizeof(float)*numsides); + for(int i=0;isidedef!=NULL) + { + // check all the segs and calculate the length they occupy on their sidedef + TVector2 vec1(seg->v2->x - seg->v1->x, seg->v2->y - seg->v1->y); + added_seglen[seg->sidedef - sides] += float(vec1.Length()); + } + } + + for(int i=0;ilinedef; + + TVector2 lvec(line->dx, line->dy); + float linelen = float(lvec.Length()); + + missing += (added_seglen[i] < linelen - FRACUNIT); + } + + delete [] added_seglen; + return missing; +} + +//========================================================================== +// +// Checks whether the nodes are suitable for GL rendering +// +//========================================================================== + +bool P_CheckForGLNodes() +{ + int i; + + for(i=0;ifirstline; + seg_t * lastseg = sub->firstline + sub->numlines - 1; + + if (firstseg->v1 != lastseg->v2) + { + // This subsector is incomplete which means that these + // are normal nodes + return false; + } + else + { + for(DWORD j=0;jnumlines;j++) + { + if (segs[j].linedef==NULL) // miniseg + { + // We already have GL nodes. Great! + return true; + } + } + } + } + // all subsectors were closed but there are no minisegs + // Although unlikely this can happen. Such nodes are not a problem. + // all that is left is to check whether the BSP covers all sidedefs completely. + int missing = CheckForMissingSegs(); + if (missing > 0) + { + Printf("%d missing segs counted\nThe BSP needs to be rebuilt", missing); + } + return missing == 0; +} + + +//========================================================================== +// +// LoadGLVertexes +// +// loads GL vertices +// +//========================================================================== + +#define gNd2 MAKE_ID('g','N','d','2') +#define gNd4 MAKE_ID('g','N','d','4') +#define gNd5 MAKE_ID('g','N','d','5') + +#define GL_VERT_OFFSET 4 +static int firstglvertex; +static bool format5; + +static bool LoadGLVertexes(FileReader * f, wadlump_t * lump) +{ + BYTE *gldata; + int i; + + firstglvertex = numvertexes; + + int gllen=lump->Size; + + gldata = new BYTE[gllen]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(gldata, gllen); + + if (*(int *)gldata == gNd5) + { + format5=true; + } + else if (*(int *)gldata != gNd2) + { + // GLNodes V1 and V4 are unsupported. + // V1 because the precision is insufficient and + // V4 due to the missing partner segs + Printf("GL nodes v%d found. This format is not supported by "GAMENAME"\n", + (*(int *)gldata == gNd4)? 4:1); + + delete [] gldata; + return false; + } + else format5=false; + + mapglvertex_t* mgl; + + vertex_t * oldvertexes = vertexes; + numvertexes += (gllen - GL_VERT_OFFSET)/sizeof(mapglvertex_t); + vertexes = new vertex_t[numvertexes]; + mgl = (mapglvertex_t *) (gldata + GL_VERT_OFFSET); + + memcpy(vertexes, oldvertexes, firstglvertex * sizeof(vertex_t)); + for(i=0;ix); + vertexes[i].y = LittleLong(mgl->y); + mgl++; + } + delete[] gldata; + return true; +} + +//========================================================================== +// +// GL Nodes utilities +// +//========================================================================== + +static inline int checkGLVertex(int num) +{ + if (num & 0x8000) + num = (num&0x7FFF)+firstglvertex; + return num; +} + +static inline int checkGLVertex3(int num) +{ + if (num & 0xc0000000) + num = (num&0x3FFFFFFF)+firstglvertex; + return num; +} + +//========================================================================== +// +// LoadGLSegs +// +//========================================================================== + +static bool LoadGLSegs(FileReader * f, wadlump_t * lump) +{ + char *data; + int i; + line_t *ldef=NULL; + + numsegs = lump->Size; + data= new char[numsegs]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(data, lump->Size); + segs=NULL; + +#ifdef _MSC_VER + __try +#endif + { + if (!format5 && memcmp(data, "gNd3", 4)) + { + numsegs/=sizeof(glseg_t); + segs = new seg_t[numsegs]; + memset(segs,0,sizeof(seg_t)*numsegs); + glsegextras = new glsegextra_t[numsegs]; + + glseg_t * ml = (glseg_t*)data; + for(i = 0; i < numsegs; i++) + { // check for gl-vertices + segs[i].v1 = &vertexes[checkGLVertex(LittleShort(ml->v1))]; + segs[i].v2 = &vertexes[checkGLVertex(LittleShort(ml->v2))]; + + glsegextras[i].PartnerSeg = ml->partner == 0xFFFF ? DWORD_MAX : LittleShort(ml->partner); + if(ml->linedef != 0xffff) + { + ldef = &lines[LittleShort(ml->linedef)]; + segs[i].linedef = ldef; + + + ml->side=LittleShort(ml->side); + segs[i].sidedef = ldef->sidedef[ml->side]; + segs[i].frontsector = ldef->sidedef[ml->side]->sector; + if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL) + { + segs[i].backsector = ldef->sidedef[ml->side^1]->sector; + } + else + { + ldef->flags &= ~ML_TWOSIDED; + segs[i].backsector = 0; + } + + } + else + { + segs[i].linedef = NULL; + segs[i].sidedef = NULL; + + segs[i].frontsector = NULL; + segs[i].backsector = NULL; + } + ml++; + } + } + else + { + if (!format5) numsegs-=4; + numsegs/=sizeof(glseg3_t); + segs = new seg_t[numsegs]; + memset(segs,0,sizeof(seg_t)*numsegs); + glsegextras = new glsegextra_t[numsegs]; + + glseg3_t * ml = (glseg3_t*)(data+ (format5? 0:4)); + for(i = 0; i < numsegs; i++) + { // check for gl-vertices + segs[i].v1 = &vertexes[checkGLVertex3(LittleLong(ml->v1))]; + segs[i].v2 = &vertexes[checkGLVertex3(LittleLong(ml->v2))]; + + glsegextras[i].PartnerSeg = LittleLong(ml->partner); + + if(ml->linedef != 0xffff) // skip minisegs + { + ldef = &lines[LittleLong(ml->linedef)]; + segs[i].linedef = ldef; + + + ml->side=LittleShort(ml->side); + segs[i].sidedef = ldef->sidedef[ml->side]; + segs[i].frontsector = ldef->sidedef[ml->side]->sector; + if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL) + { + segs[i].backsector = ldef->sidedef[ml->side^1]->sector; + } + else + { + ldef->flags &= ~ML_TWOSIDED; + segs[i].backsector = 0; + } + + } + else + { + segs[i].linedef = NULL; + segs[i].sidedef = NULL; + segs[i].frontsector = NULL; + segs[i].backsector = NULL; + } + ml++; + } + } + delete [] data; + return true; + } +#ifdef _MSC_VER + __except(1) + { + // Invalid data has the bad habit of requiring extensive checks here + // so let's just catch anything invalid and output a message. + // (at least under MSVC. GCC can't do SEH even for Windows... :( ) + Printf("Invalid GL segs. The BSP will have to be rebuilt.\n"); + delete [] data; + delete [] segs; + segs = NULL; + return false; + } +#endif +} + + +//========================================================================== +// +// LoadGLSubsectors +// +//========================================================================== + +static bool LoadGLSubsectors(FileReader * f, wadlump_t * lump) +{ + char * datab; + int i; + + numsubsectors = lump->Size; + datab = new char[numsubsectors]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(datab, lump->Size); + + if (numsubsectors == 0) + { + delete [] datab; + return false; + } + + if (!format5 && memcmp(datab, "gNd3", 4)) + { + mapsubsector_t * data = (mapsubsector_t*) datab; + numsubsectors /= sizeof(mapsubsector_t); + subsectors = new subsector_t[numsubsectors]; + memset(subsectors,0,numsubsectors * sizeof(subsector_t)); + + for (i=0; ilinedef==NULL) seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector; + } + seg_t *firstseg = subsectors[i].firstline; + seg_t *lastseg = subsectors[i].firstline + subsectors[i].numlines - 1; + // The subsector must be closed. If it isn't we can't use these nodes and have to do a rebuild. + if (lastseg->v2 != firstseg->v1) + { + delete [] datab; + return false; + } + + } + delete [] datab; + return true; +} + +//========================================================================== +// +// P_LoadNodes +// +//========================================================================== + +static bool LoadNodes (FileReader * f, wadlump_t * lump) +{ + const int NF_SUBSECTOR = 0x8000; + const int GL5_NF_SUBSECTOR = (1 << 31); + + int i; + int j; + int k; + node_t* no; + WORD* used; + + if (!format5) + { + mapnode_t* mn, * basemn; + numnodes = lump->Size / sizeof(mapnode_t); + + if (numnodes == 0) return false; + + nodes = new node_t[numnodes]; + f->Seek(lump->FilePos, SEEK_SET); + + basemn = mn = new mapnode_t[numnodes]; + f->Read(mn, lump->Size); + + used = (WORD *)alloca (sizeof(WORD)*numnodes); + memset (used, 0, sizeof(WORD)*numnodes); + + no = nodes; + + for (i = 0; i < numnodes; i++, no++, mn++) + { + no->x = LittleShort(mn->x)<y = LittleShort(mn->y)<dx = LittleShort(mn->dx)<dy = LittleShort(mn->dy)<children[j]); + if (child & NF_SUBSECTOR) + { + child &= ~NF_SUBSECTOR; + if (child >= numsubsectors) + { + delete [] basemn; + return false; + } + no->children[j] = (BYTE *)&subsectors[child] + 1; + } + else if (child >= numnodes) + { + delete [] basemn; + return false; + } + else if (used[child]) + { + delete [] basemn; + return false; + } + else + { + no->children[j] = &nodes[child]; + used[child] = j + 1; + } + for (k = 0; k < 4; k++) + { + no->bbox[j][k] = LittleShort(mn->bbox[j][k])<Size / sizeof(gl5_mapnode_t); + + if (numnodes == 0) return false; + + nodes = new node_t[numnodes]; + f->Seek(lump->FilePos, SEEK_SET); + + basemn = mn = new gl5_mapnode_t[numnodes]; + f->Read(mn, lump->Size); + + used = (WORD *)alloca (sizeof(WORD)*numnodes); + memset (used, 0, sizeof(WORD)*numnodes); + + no = nodes; + + for (i = 0; i < numnodes; i++, no++, mn++) + { + no->x = LittleShort(mn->x)<y = LittleShort(mn->y)<dx = LittleShort(mn->dx)<dy = LittleShort(mn->dy)<children[j]); + if (child & GL5_NF_SUBSECTOR) + { + child &= ~GL5_NF_SUBSECTOR; + if (child >= numsubsectors) + { + delete [] basemn; + return false; + } + no->children[j] = (BYTE *)&subsectors[child] + 1; + } + else if (child >= numnodes) + { + delete [] basemn; + return false; + } + else if (used[child]) + { + delete [] basemn; + return false; + } + else + { + no->children[j] = &nodes[child]; + used[child] = j + 1; + } + for (k = 0; k < 4; k++) + { + no->bbox[j][k] = LittleShort(mn->bbox[j][k])<sidedef) + { + Printf("GL nodes contain invalid data. The BSP has to be rebuilt.\n"); + delete [] nodes; + nodes = NULL; + delete [] subsectors; + subsectors = NULL; + delete [] segs; + segs = NULL; + return false; + } + } + + // check whether the BSP covers all sidedefs completely. + int missing = CheckForMissingSegs(); + if (missing > 0) + { + Printf("%d missing segs counted in GL nodes.\nThe BSP has to be rebuilt", missing); + } + return missing == 0; +} + + +//=========================================================================== +// +// MatchHeader +// +// Checks whether a GL_LEVEL header belongs to this level +// +//=========================================================================== + +static bool MatchHeader(const char * label, const char * hdata) +{ + if (!memcmp(hdata, "LEVEL=", 6) == 0) + { + size_t labellen = strlen(label); + + if (strnicmp(hdata+6, label, labellen)==0 && + (hdata[6+labellen]==0xa || hdata[6+labellen]==0xd)) + { + return true; + } + } + return false; +} + +//=========================================================================== +// +// FindGLNodesInWAD +// +// Looks for GL nodes in the same WAD as the level itself +// +//=========================================================================== + +static int FindGLNodesInWAD(int labellump) +{ + int wadfile = Wads.GetLumpFile(labellump); + FString glheader; + + glheader.Format("GL_%s", Wads.GetLumpFullName(labellump)); + if (glheader.Len()<=8) + { + int gllabel = Wads.CheckNumForName(glheader, ns_global, wadfile); + if (gllabel >= 0) return gllabel; + } + else + { + // Before scanning the entire WAD directory let's check first whether + // it is necessary. + int gllabel = Wads.CheckNumForName("GL_LEVEL", ns_global, wadfile); + + if (gllabel >= 0) + { + int lastlump=0; + int lump; + while ((lump=Wads.FindLump("GL_LEVEL", &lastlump))>=0) + { + if (Wads.GetLumpFile(lump)==wadfile) + { + FMemLump mem = Wads.ReadLump(lump); + if (MatchHeader(Wads.GetLumpFullName(labellump), (const char *)mem.GetMem())) return true; + } + } + } + } + return -1; +} + +//=========================================================================== +// +// FindGLNodesInWAD +// +// Looks for GL nodes in the same WAD as the level itself +// When this function returns the file pointer points to +// the directory entry of the GL_VERTS lump +// +//=========================================================================== + +static int FindGLNodesInFile(FileReader * f, const char * label) +{ + FString glheader; + bool mustcheck=false; + DWORD id, dirofs, numentries; + DWORD offset, size; + char lumpname[9]; + + glheader.Format("GL_%.8s", label); + if (glheader.Len()>8) + { + glheader="GL_LEVEL"; + mustcheck=true; + } + + f->Seek(0, SEEK_SET); + (*f) >> id >> numentries >> dirofs; + + if ((id == IWAD_ID || id == PWAD_ID) && numentries > 4) + { + f->Seek(dirofs, SEEK_SET); + for(DWORD i=0;i> offset >> size; + f->Read(lumpname, 8); + if (!strnicmp(lumpname, glheader, 8)) + { + if (mustcheck) + { + char check[16]={0}; + int filepos = f->Tell(); + f->Seek(offset, SEEK_SET); + f->Read(check, 16); + f->Seek(filepos, SEEK_SET); + if (MatchHeader(label, check)) return i; + } + else return i; + } + } + } + return -1; +} + +//========================================================================== +// +// Checks for the presence of GL nodes in the loaded WADs or a .GWA file +// returns true if successful +// +//========================================================================== + +bool P_LoadGLNodes(MapData * map) +{ + if (!CheckCachedNodes(map)) + { + wadlump_t gwalumps[4]; + char path[256]; + int li; + int lumpfile = Wads.GetLumpFile(map->lumpnum); + bool mapinwad = map->file == Wads.GetFileReader(lumpfile); + FileReader * fr = map->file; + FILE * f_gwa = NULL; + + const char * name = Wads.GetWadFullName(lumpfile); + + if (mapinwad) + { + li = FindGLNodesInWAD(map->lumpnum); + + if (li>=0) + { + // GL nodes are loaded with a WAD + for(int i=0;i<4;i++) + { + gwalumps[i].FilePos=Wads.GetLumpOffset(li+i+1); + gwalumps[i].Size=Wads.LumpLength(li+i+1); + } + return DoLoadGLNodes(fr, gwalumps); + } + else + { + strcpy(path, name); + + char * ext = strrchr(path, '.'); + if (ext) + { + strcpy(ext, ".gwa"); + // Todo: Compare file dates + + f_gwa = fopen(path, "rb"); + if (f_gwa==NULL) return false; + + fr = new FileReader(f_gwa); + + strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(map->lumpnum), 8); + } + } + } + + bool result = false; + li = FindGLNodesInFile(fr, map->MapLumps[0].Name); + if (li!=-1) + { + static const char check[][9]={"GL_VERT","GL_SEGS","GL_SSECT","GL_NODES"}; + result=true; + for(unsigned i=0; i<4;i++) + { + (*fr) >> gwalumps[i].FilePos; + (*fr) >> gwalumps[i].Size; + fr->Read(gwalumps[i].Name, 8); + if (strnicmp(gwalumps[i].Name, check[i], 8)) + { + result=false; + break; + } + } + if (result) result = DoLoadGLNodes(fr, gwalumps); + } + + if (f_gwa) + { + delete fr; + fclose(f_gwa); + } + return result; + } + else return true; +} + +//========================================================================== +// +// Checks whether nodes are GL friendly or not +// +//========================================================================== + +bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime) +{ + bool ret = false; + + // If the map loading code has performed a node rebuild we don't need to check for it again. + if (!rebuilt && !P_CheckForGLNodes()) + { + ret = true; // we are not using the level's original nodes if we get here. + for (int i = 0; i < numsubsectors; i++) + { + gamesubsectors[i].sector = gamesubsectors[i].firstline->sidedef->sector; + } + + nodes = NULL; + numnodes = 0; + subsectors = NULL; + numsubsectors = 0; + if (segs) delete [] segs; + segs = NULL; + numsegs = 0; + + // Try to load GL nodes (cached or GWA) + if (!P_LoadGLNodes(map)) + { + // none found - we have to build new ones! + unsigned int startTime, endTime; + + startTime = I_FPSTime (); + TArray polyspots, anchors; + P_GetPolySpots (map, polyspots, anchors); + FNodeBuilder::FLevel leveldata = + { + vertexes, numvertexes, + sides, numsides, + lines, numlines + }; + leveldata.FindMapBounds (); + FNodeBuilder builder (leveldata, polyspots, anchors, true); + delete[] vertexes; + builder.Extract (nodes, numnodes, + segs, glsegextras, numsegs, + subsectors, numsubsectors, + vertexes, numvertexes); + endTime = I_FPSTime (); + DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs); + buildtime = endTime - startTime; + } + } + +#ifdef DEBUG + // Building nodes in debug is much slower so let's cache them only if cachetime is 0 + buildtime = 0; +#endif + if (gl_cachenodes && buildtime/1000.f >= gl_cachetime) + { + DPrintf("Caching nodes\n"); + CreateCachedNodes(map); + } + else + { + DPrintf("Not caching nodes (time = %f)\n", buildtime/1000.f); + } + + + if (!gamenodes) + { + gamenodes = nodes; + numgamenodes = numnodes; + gamesubsectors = subsectors; + numgamesubsectors = numsubsectors; + } + return ret; +} + +//========================================================================== +// +// Node caching +// +//========================================================================== + +typedef TArray MemFile; + +static FString GetCachePath() +{ + FString path; + +#ifdef _WIN32 + char pathstr[MAX_PATH]; + if (0 != SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathstr)) + { // Failed (e.g. On Win9x): use program directory + path = progdir; + } + else + { + path = pathstr; + } + path += "/zdoom/cache"; +#elif defined(__APPLE__) + char pathstr[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kLocalDomain, kApplicationSupportFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + path = pathstr; + } + else + { + path = progdir; + } + path += "/zdoom/cache"; +#else + // Don't use GAME_DIR and such so that ZDoom and its child ports can share the node cache. + path = NicePath("~/.zdoom/cache"); +#endif + return path; +} + +static FString CreateCacheName(MapData *map, bool create) +{ + FString path = GetCachePath(); + FString lumpname = Wads.GetLumpFullPath(map->lumpnum); + int separator = lumpname.IndexOf(':'); + path << '/' << lumpname.Left(separator); + if (create) CreatePath(path); + + lumpname.ReplaceChars('/', '%'); + path << '/' << lumpname.Right(lumpname.Len() - separator - 1) << ".gzc"; + return path; +} + +static void WriteByte(MemFile &f, BYTE b) +{ + f.Push(b); +} + +static void WriteWord(MemFile &f, WORD b) +{ + int v = f.Reserve(2); + f[v] = (BYTE)b; + f[v+1] = (BYTE)(b>>8); +} + +static void WriteLong(MemFile &f, DWORD b) +{ + int v = f.Reserve(4); + f[v] = (BYTE)b; + f[v+1] = (BYTE)(b>>8); + f[v+2] = (BYTE)(b>>16); + f[v+3] = (BYTE)(b>>24); +} + +static void CreateCachedNodes(MapData *map) +{ + MemFile ZNodes; + + WriteLong(ZNodes, 0); + WriteLong(ZNodes, numvertexes); + for(int i=0;isidedef[0]? 0:1); + } + else + { + WriteLong(ZNodes, 0xffffffffu); + WriteByte(ZNodes, 0); + } + } + + WriteLong(ZNodes, numnodes); + for(int i=0;i> FRACBITS); + WriteWord(ZNodes, nodes[i].y >> FRACBITS); + WriteWord(ZNodes, nodes[i].dx >> FRACBITS); + WriteWord(ZNodes, nodes[i].dy >> FRACBITS); + for (int j = 0; j < 2; ++j) + { + for (int k = 0; k < 4; ++k) + { + WriteWord(ZNodes, nodes[i].bbox[j][k] >> FRACBITS); + } + } + + for (int j = 0; j < 2; ++j) + { + DWORD child; + if ((size_t)nodes[i].children[j] & 1) + { + child = 0x80000000 | DWORD((subsector_t *)((BYTE *)nodes[i].children[j] - 1) - subsectors); + } + else + { + child = DWORD((node_t *)nodes[i].children[j] - nodes); + } + WriteLong(ZNodes, child); + } + } + + uLongf outlen = ZNodes.Size(); + BYTE *compressed; + int offset = numlines * 8 + 12 + 16; + int r; + do + { + compressed = new Bytef[outlen + offset]; + r = compress (compressed + offset, &outlen, &ZNodes[0], ZNodes.Size()); + if (r == Z_BUF_ERROR) + { + delete[] compressed; + outlen += 1024; + } + } + while (r == Z_BUF_ERROR); + + memcpy(compressed, "CACH", 4); + DWORD len = LittleLong(numlines); + memcpy(compressed+4, &len, 4); + map->GetChecksum(compressed+8); + for(int i=0;iGetChecksum(md5map); + if (memcmp(md5, md5map, 16)) goto errorout; + + verts = new DWORD[numlin * 8]; + if (fread(verts, 8, numlin, f) != numlin) goto errorout; + + if (fread(magic, 1, 4, f) != 4) goto errorout; + if (memcmp(magic, "ZGL2", 4)) goto errorout; + + + try + { + long pos = ftell(f); + FileReader fr(f); + fr.Seek(pos, SEEK_SET); + P_LoadZNodes (fr, MAKE_ID('Z','G','L','2')); + } + catch (CRecoverableError &error) + { + Printf ("Error loading nodes: %s\n", error.GetMessage()); + + if (subsectors != NULL) + { + delete[] subsectors; + subsectors = NULL; + } + if (segs != NULL) + { + delete[] segs; + segs = NULL; + } + if (nodes != NULL) + { + delete[] nodes; + nodes = NULL; + } + goto errorout; + } + + for(int i=0;i list; + FString path = GetCachePath(); + path += "/"; + + try + { + ScanDirectory(list, path); + } + catch (CRecoverableError &err) + { + Printf("%s", err.GetMessage()); + return; + } + + // Scan list backwards so that when we reach a directory + // all files within are already deleted. + for(int i = list.Size()-1; i >= 0; i--) + { + if (list[i].isDirectory) + { + rmdir(list[i].Filename); + } + else + { + remove(list[i].Filename); + } + } + + +} + +//========================================================================== +// +// Keep both the original nodes from the WAD and the GL nodes created here. +// The original set is only being used to get the sector for in-game +// positioning of actors but not for rendering. +// +// This is necessary because ZDBSP is much more sensitive +// to sloppy mapping practices that produce overlapping sectors. +// The crane in P:AR E1M3 is a good example that would be broken if +// this wasn't done. +// +//========================================================================== + + +//========================================================================== +// +// P_PointInSubsector +// +//========================================================================== + +subsector_t *P_PointInSubsector (fixed_t x, fixed_t y) +{ + node_t *node; + int side; + + // single subsector is a special case + if (numgamenodes == 0) + return gamesubsectors; + + node = gamenodes + numgamenodes - 1; + + do + { + side = R_PointOnSide (x, y, node); + node = (node_t *)node->children[side]; + } + while (!((size_t)node & 1)); + + return (subsector_t *)((BYTE *)node - 1); +} + + +//========================================================================== +// +// PointOnLine +// +// Same as the one im the node builder, but not part of a specific class +// +//========================================================================== + +static bool PointOnLine (int x, int y, int x1, int y1, int dx, int dy) +{ + const double SIDE_EPSILON = 6.5536; + + // For most cases, a simple dot product is enough. + double d_dx = double(dx); + double d_dy = double(dy); + double d_x = double(x); + double d_y = double(y); + double d_x1 = double(x1); + double d_y1 = double(y1); + + double s_num = (d_y1-d_y)*d_dx - (d_x1-d_x)*d_dy; + + if (fabs(s_num) < 17179869184.0) // 4<<32 + { + // Either the point is very near the line, or the segment defining + // the line is very short: Do a more expensive test to determine + // just how far from the line the point is. + double l = sqrt(d_dx*d_dx+d_dy*d_dy); + double dist = fabs(s_num)/l; + if (dist < SIDE_EPSILON) + { + return true; + } + } + return false; +} + + +//========================================================================== +// +// SetRenderSector +// +// Sets the render sector for each GL subsector so that the proper flat +// information can be retrieved +// +//========================================================================== + +void P_SetRenderSector() +{ + int i; + DWORD j; + TArray undetermined; + subsector_t * ss; + +#if 0 // doesn't work as expected :( + + // hide all sectors on textured automap that only have hidden lines. + bool *hidesec = new bool[numsectors]; + for(i = 0; i < numsectors; i++) + { + hidesec[i] = true; + } + for(i = 0; i < numlines; i++) + { + if (!(lines[i].flags & ML_DONTDRAW)) + { + hidesec[lines[i].frontsector - sectors] = false; + if (lines[i].backsector != NULL) + { + hidesec[lines[i].backsector - sectors] = false; + } + } + } + for(i = 0; i < numsectors; i++) + { + if (hidesec[i]) sectors[i].MoreFlags |= SECF_HIDDEN; + } + delete [] hidesec; +#endif + + // Check for incorrect partner seg info so that the following code does not crash. + if (glsegextras == NULL) + { + // This can be normal nodes, mistakenly identified as GL nodes so we must fill + // in the missing pieces differently. + + for (i = 0; i < numsubsectors; i++) + { + ss = &subsectors[i]; + ss->render_sector = ss->sector; + } + return; + } + + for(i=0;i=numsegs/*eh? || &segs[partner]!=glsegextras[i].PartnerSeg*/) + { + glsegextras[i].PartnerSeg=DWORD_MAX; + } + + // glbsp creates such incorrect references for Strife. + if (segs[i].linedef && glsegextras[i].PartnerSeg != DWORD_MAX && !segs[glsegextras[i].PartnerSeg].linedef) + { + glsegextras[i].PartnerSeg = glsegextras[glsegextras[i].PartnerSeg].PartnerSeg = DWORD_MAX; + } + } + + for(i=0;ifirstline; + + // Check for one-dimensional subsectors. These should be ignored when + // being processed for automap drawinng etc. + ss->flags |= SSECF_DEGENERATE; + for(j=2; jnumlines; j++) + { + if (!PointOnLine(seg[j].v1->x, seg[j].v1->y, seg->v1->x, seg->v1->y, seg->v2->x-seg->v1->x, seg->v2->y-seg->v1->y)) + { + // Not on the same line + ss->flags &= ~SSECF_DEGENERATE; + break; + } + } + + seg = ss->firstline; + for(j=0; jnumlines; j++) + { + if(seg->sidedef && (glsegextras[seg - segs].PartnerSeg == DWORD_MAX || seg->sidedef->sector!=segs[glsegextras[seg - segs].PartnerSeg].sidedef->sector)) + { + ss->render_sector = seg->sidedef->sector; + break; + } + seg++; + } + if(ss->render_sector == NULL) + { + undetermined.Push(ss); + } + } + + // assign a vaild render sector to all subsectors which haven't been processed yet. + while (undetermined.Size()) + { + bool deleted=false; + for(i=undetermined.Size()-1;i>=0;i--) + { + ss=undetermined[i]; + seg_t * seg = ss->firstline; + + for(j=0; jnumlines; j++) + { + DWORD partner = glsegextras[seg - segs].PartnerSeg; + if (partner != DWORD_MAX && glsegextras[partner].Subsector) + { + sector_t * backsec = glsegextras[partner].Subsector->render_sector; + if (backsec) + { + ss->render_sector=backsec; + undetermined.Delete(i); + deleted=1; + break; + } + } + seg++; + } + } + // We still got some left but the loop above was unable to assign them. + // This only happens when a subsector is off the map. + // Don't bother and just assign the real sector for rendering + if (!deleted && undetermined.Size()) + { + for(i=undetermined.Size()-1;i>=0;i--) + { + ss=undetermined[i]; + ss->render_sector=ss->sector; + } + break; + } + } + +#if 0 // may be useful later so let's keep it here for now + // now group the subsectors by sector + subsector_t ** subsectorbuffer = new subsector_t * [numsubsectors]; + + for(i=0, ss=subsectors; irender_sector->subsectorcount++; + } + + for (i=0; irender_sector->subsectors[ss->render_sector->subsectorcount++]=ss; + } +#endif + +} diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp index 45c706a8c..cabf82d9e 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1358,6 +1358,34 @@ dopain: target->flags |= MF_JUSTHIT; // fight back! } +void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period) +{ + int olddamage = target->PoisonDamageReceived; + int oldduration = target->PoisonDurationReceived; + + target->Poisoner = source; + + if (inflictor->flags6 & MF6_ADDITIVEPOISONDAMAGE) + { + target->PoisonDamageReceived += damage; + } + else + { + target->PoisonDamageReceived = damage; + } + + if (inflictor->flags6 & MF6_ADDITIVEPOISONDURATION) + { + target->PoisonDurationReceived += duration; + } + else + { + target->PoisonDurationReceived = duration; + } + + target->PoisonPeriodReceived = period; +} + bool AActor::OkayToSwitchTarget (AActor *other) { if (other == this) @@ -1528,7 +1556,6 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, return; } -bool CheckCheatmode (); CCMD (kill) { diff --git a/src/p_lights.cpp b/src/p_lights.cpp index c33e27466..f437f4c7b 100644 --- a/src/p_lights.cpp +++ b/src/p_lights.cpp @@ -39,6 +39,12 @@ static FRandom pr_lightflash ("LightFlash"); static FRandom pr_strobeflash ("StrobeFlash"); static FRandom pr_fireflicker ("FireFlicker"); +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + IMPLEMENT_CLASS (DLighting) DLighting::DLighting () @@ -51,9 +57,11 @@ DLighting::DLighting (sector_t *sector) ChangeStatNum (STAT_LIGHT); } +//----------------------------------------------------------------------------- // // FIRELIGHT FLICKER // +//----------------------------------------------------------------------------- IMPLEMENT_CLASS (DFireFlicker) @@ -68,9 +76,12 @@ void DFireFlicker::Serialize (FArchive &arc) } +//----------------------------------------------------------------------------- // // T_FireFlicker // +//----------------------------------------------------------------------------- + void DFireFlicker::Tick () { int amount; @@ -89,9 +100,12 @@ void DFireFlicker::Tick () } } +//----------------------------------------------------------------------------- // // P_SpawnFireFlicker // +//----------------------------------------------------------------------------- + DFireFlicker::DFireFlicker (sector_t *sector) : DLighting (sector) { @@ -108,9 +122,12 @@ DFireFlicker::DFireFlicker (sector_t *sector, int upper, int lower) m_Count = 4; } +//----------------------------------------------------------------------------- // // [RH] flickering light like Hexen's // +//----------------------------------------------------------------------------- + IMPLEMENT_CLASS (DFlicker) DFlicker::DFlicker () @@ -123,6 +140,11 @@ void DFlicker::Serialize (FArchive &arc) arc << m_Count << m_MaxLight << m_MinLight; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- void DFlicker::Tick () { @@ -142,6 +164,12 @@ void DFlicker::Tick () } } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + DFlicker::DFlicker (sector_t *sector, int upper, int lower) : DLighting (sector) { @@ -151,6 +179,12 @@ DFlicker::DFlicker (sector_t *sector, int upper, int lower) m_Count = (pr_flicker()&64)+1; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void EV_StartLightFlickering (int tag, int upper, int lower) { int secnum; @@ -163,9 +197,11 @@ void EV_StartLightFlickering (int tag, int upper, int lower) } +//----------------------------------------------------------------------------- // // BROKEN LIGHT FLASHING // +//----------------------------------------------------------------------------- IMPLEMENT_CLASS (DLightFlash) @@ -179,10 +215,13 @@ void DLightFlash::Serialize (FArchive &arc) arc << m_Count << m_MaxLight << m_MaxTime << m_MinLight << m_MinTime; } +//----------------------------------------------------------------------------- // // T_LightFlash // Do flashing lights. // +//----------------------------------------------------------------------------- + void DLightFlash::Tick () { if (--m_Count == 0) @@ -200,9 +239,12 @@ void DLightFlash::Tick () } } +//----------------------------------------------------------------------------- // // P_SpawnLightFlash // +//----------------------------------------------------------------------------- + DLightFlash::DLightFlash (sector_t *sector) : DLighting (sector) { @@ -226,9 +268,11 @@ DLightFlash::DLightFlash (sector_t *sector, int min, int max) } +//----------------------------------------------------------------------------- // // STROBE LIGHT FLASHING // +//----------------------------------------------------------------------------- IMPLEMENT_CLASS (DStrobe) @@ -242,9 +286,12 @@ void DStrobe::Serialize (FArchive &arc) arc << m_Count << m_MaxLight << m_MinLight << m_DarkTime << m_BrightTime; } +//----------------------------------------------------------------------------- // // T_StrobeFlash // +//----------------------------------------------------------------------------- + void DStrobe::Tick () { if (--m_Count == 0) @@ -262,9 +309,12 @@ void DStrobe::Tick () } } +//----------------------------------------------------------------------------- // // Hexen-style constructor // +//----------------------------------------------------------------------------- + DStrobe::DStrobe (sector_t *sector, int upper, int lower, int utics, int ltics) : DLighting (sector) { @@ -275,9 +325,12 @@ DStrobe::DStrobe (sector_t *sector, int upper, int lower, int utics, int ltics) m_Count = 1; // Hexen-style is always in sync } +//----------------------------------------------------------------------------- // // Doom-style constructor // +//----------------------------------------------------------------------------- + DStrobe::DStrobe (sector_t *sector, int utics, int ltics, bool inSync) : DLighting (sector) { @@ -295,10 +348,13 @@ DStrobe::DStrobe (sector_t *sector, int utics, int ltics, bool inSync) +//----------------------------------------------------------------------------- // // Start strobing lights (usually from a trigger) // [RH] Made it more configurable. // +//----------------------------------------------------------------------------- + void EV_StartLightStrobing (int tag, int upper, int lower, int utics, int ltics) { int secnum; @@ -330,10 +386,13 @@ void EV_StartLightStrobing (int tag, int utics, int ltics) } +//----------------------------------------------------------------------------- // // TURN LINE'S TAG LIGHTS OFF // [RH] Takes a tag instead of a line // +//----------------------------------------------------------------------------- + void EV_TurnTagLightsOff (int tag) { int i; @@ -358,10 +417,13 @@ void EV_TurnTagLightsOff (int tag) } +//----------------------------------------------------------------------------- // // TURN LINE'S TAG LIGHTS ON // [RH] Takes a tag instead of a line // +//----------------------------------------------------------------------------- + void EV_LightTurnOn (int tag, int bright) { int secnum = -1; @@ -370,6 +432,7 @@ void EV_LightTurnOn (int tag, int bright) while ((secnum = P_FindSectorFromTag (tag, secnum)) >= 0) { sector_t *sector = sectors + secnum; + int tbright = bright; //jff 5/17/98 search for maximum PER sector // bright = -1 means to search ([RH] Not 0) // for highest light level @@ -378,7 +441,6 @@ void EV_LightTurnOn (int tag, int bright) { int j; - bright = 0; for (j = 0; j < sector->linecount; j++) { sector_t *temp = getNextSector (sector->lines[j], sector); @@ -386,14 +448,23 @@ void EV_LightTurnOn (int tag, int bright) if (!temp) continue; - if (temp->lightlevel > bright) - bright = temp->lightlevel; + if (temp->lightlevel > tbright) + tbright = temp->lightlevel; } } - sector->SetLightLevel(bright); + sector->SetLightLevel(tbright); + + //jff 5/17/98 unless compatibility optioned + //then maximum near ANY tagged sector + if (i_compatflags & COMPATF_LIGHT) + { + bright = tbright; + } } } +//----------------------------------------------------------------------------- +// // killough 10/98 // // EV_LightTurnOnPartway @@ -404,7 +475,7 @@ void EV_LightTurnOn (int tag, int bright) // Sets the light to min on 0, max on 1, and interpolates in-between. // Used for doors with gradual lighting effects. // -// Returns true +//----------------------------------------------------------------------------- void EV_LightTurnOnPartway (int tag, fixed_t frac) { @@ -438,11 +509,14 @@ void EV_LightTurnOnPartway (int tag, fixed_t frac) } +//----------------------------------------------------------------------------- // // [RH] New function to adjust tagged sectors' light levels // by a relative amount. Light levels are clipped to // within the range 0-255 inclusive. // +//----------------------------------------------------------------------------- + void EV_LightChange (int tag, int value) { int secnum = -1; @@ -455,9 +529,12 @@ void EV_LightChange (int tag, int value) } +//----------------------------------------------------------------------------- // // Spawn glowing light // +//----------------------------------------------------------------------------- + IMPLEMENT_CLASS (DGlow) DGlow::DGlow () @@ -470,6 +547,12 @@ void DGlow::Serialize (FArchive &arc) arc << m_Direction << m_MaxLight << m_MinLight; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void DGlow::Tick () { int newlight = m_Sector->lightlevel; @@ -499,6 +582,11 @@ void DGlow::Tick () m_Sector->SetLightLevel(newlight); } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- DGlow::DGlow (sector_t *sector) : DLighting (sector) @@ -508,9 +596,11 @@ DGlow::DGlow (sector_t *sector) m_Direction = -1; } +//----------------------------------------------------------------------------- // // [RH] More glowing light, this time appropriate for Hexen-ish uses. // +//----------------------------------------------------------------------------- IMPLEMENT_CLASS (DGlow2) @@ -524,6 +614,12 @@ void DGlow2::Serialize (FArchive &arc) arc << m_End << m_MaxTics << m_OneShot << m_Start << m_Tics; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void DGlow2::Tick () { if (m_Tics++ >= m_MaxTics) @@ -546,6 +642,12 @@ void DGlow2::Tick () m_Sector->SetLightLevel(((m_End - m_Start) * m_Tics) / m_MaxTics + m_Start); } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + DGlow2::DGlow2 (sector_t *sector, int start, int end, int tics, bool oneshot) : DLighting (sector) { @@ -556,6 +658,12 @@ DGlow2::DGlow2 (sector_t *sector, int start, int end, int tics, bool oneshot) m_OneShot = oneshot; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void EV_StartLightGlowing (int tag, int upper, int lower, int tics) { int secnum; @@ -584,6 +692,12 @@ void EV_StartLightGlowing (int tag, int upper, int lower, int tics) } } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void EV_StartLightFading (int tag, int value, int tics) { int secnum; @@ -611,8 +725,12 @@ void EV_StartLightFading (int tag, int value, int tics) } +//----------------------------------------------------------------------------- +// // [RH] Phased lighting ala Hexen, but implemented without the help of the Hexen source // The effect is a little different, but close enough, I feel. +// +//----------------------------------------------------------------------------- IMPLEMENT_CLASS (DPhased) @@ -626,6 +744,12 @@ void DPhased::Serialize (FArchive &arc) arc << m_BaseLevel << m_Phase; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + void DPhased::Tick () { const int steps = 12; @@ -644,6 +768,12 @@ void DPhased::Tick () m_Phase--; } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + int DPhased::PhaseHelper (sector_t *sector, int index, int light, sector_t *prev) { if (!sector) @@ -675,6 +805,12 @@ int DPhased::PhaseHelper (sector_t *sector, int index, int light, sector_t *prev } } +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + DPhased::DPhased (sector_t *sector, int baselevel) : DLighting (sector) { diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 060cd7f4a..47658f93f 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -121,6 +121,21 @@ FUNC(LS_Polyobj_MoveTimes8) return EV_MovePoly (ln, arg0, SPEED(arg1), BYTEANGLE(arg2), arg3 * FRACUNIT * 8, false); } +FUNC(LS_Polyobj_MoveTo) +// Polyobj_MoveTo (po, speed, x, y) +{ + return EV_MovePolyTo (ln, arg0, SPEED(arg1), arg2 << FRACBITS, arg3 << FRACBITS, false); +} + +FUNC(LS_Polyobj_MoveToSpot) +// Polyobj_MoveToSpot (po, speed, tid) +{ + FActorIterator iterator (arg2); + AActor *spot = iterator.Next(); + if (spot == NULL) return false; + return EV_MovePolyTo (ln, arg0, SPEED(arg1), spot->x, spot->y, false); +} + FUNC(LS_Polyobj_DoorSwing) // Polyobj_DoorSwing (po, speed, angle, delay) { @@ -157,6 +172,27 @@ FUNC(LS_Polyobj_OR_MoveTimes8) return EV_MovePoly (ln, arg0, SPEED(arg1), BYTEANGLE(arg2), arg3 * FRACUNIT * 8, true); } +FUNC(LS_Polyobj_OR_MoveTo) +// Polyobj_OR_MoveTo (po, speed, x, y) +{ + return EV_MovePolyTo (ln, arg0, SPEED(arg1), arg2 << FRACBITS, arg3 << FRACBITS, true); +} + +FUNC(LS_Polyobj_OR_MoveToSpot) +// Polyobj_OR_MoveToSpot (po, speed, tid) +{ + FActorIterator iterator (arg2); + AActor *spot = iterator.Next(); + if (spot == NULL) return false; + return EV_MovePolyTo (ln, arg0, SPEED(arg1), spot->x, spot->y, true); +} + +FUNC(LS_Polyobj_Stop) +// Polyobj_Stop (po) +{ + return EV_StopPoly (arg0); +} + FUNC(LS_Door_Close) // Door_Close (tag, speed, lighttag) { @@ -792,7 +828,7 @@ FUNC(LS_Teleport_NewMap) if (info && CheckIfExitIsGood (it, info)) { - G_ChangeLevel(info->mapname, arg1, !!arg2); + G_ChangeLevel(info->mapname, arg1, arg2 ? CHANGELEVEL_KEEPFACING : 0); return true; } } @@ -2970,6 +3006,36 @@ FUNC(LS_StartConversation) return false; } +FUNC(LS_Thing_SetConversation) +// Thing_SetConversation (tid, dlg_id) +{ + int dlg_index = -1; + FStrifeDialogueNode *node = NULL; + + if (arg1 != 0) + { + dlg_index = GetConversation(arg1); + if (dlg_index == -1) return false; + node = StrifeDialogues[dlg_index]; + } + + if (arg0 != 0) + { + FActorIterator iterator (arg0); + while ((it = iterator.Next()) != NULL) + { + it->ConversationRoot = dlg_index; + it->Conversation = node; + } + } + else if (it) + { + it->ConversationRoot = dlg_index; + it->Conversation = node; + } + return true; +} + lnSpecFunc LineSpecials[256] = { @@ -3031,8 +3097,8 @@ lnSpecFunc LineSpecials[256] = /* 55 */ LS_Line_SetBlocking, /* 56 */ LS_Line_SetTextureScale, /* 57 */ LS_NOP, // Sector_SetPortal - /* 58 */ LS_NOP, - /* 59 */ LS_NOP, + /* 58 */ LS_NOP, // Sector_CopyScroller + /* 59 */ LS_Polyobj_OR_MoveToSpot, /* 60 */ LS_Plat_PerpetualRaise, /* 61 */ LS_Plat_Stop, /* 62 */ LS_Plat_DownWaitUpStay, @@ -3052,17 +3118,17 @@ lnSpecFunc LineSpecials[256] = /* 76 */ LS_TeleportOther, /* 77 */ LS_TeleportGroup, /* 78 */ LS_TeleportInSector, - /* 79 */ LS_NOP, + /* 79 */ LS_Thing_SetConversation, /* 80 */ LS_ACS_Execute, /* 81 */ LS_ACS_Suspend, /* 82 */ LS_ACS_Terminate, /* 83 */ LS_ACS_LockedExecute, /* 84 */ LS_ACS_ExecuteWithResult, /* 85 */ LS_ACS_LockedExecuteDoor, - /* 86 */ LS_NOP, - /* 87 */ LS_NOP, - /* 88 */ LS_NOP, - /* 89 */ LS_NOP, + /* 86 */ LS_Polyobj_MoveToSpot, + /* 87 */ LS_Polyobj_Stop, + /* 88 */ LS_Polyobj_MoveTo, + /* 89 */ LS_Polyobj_OR_MoveTo, /* 90 */ LS_Polyobj_OR_RotateLeft, /* 91 */ LS_Polyobj_OR_RotateRight, /* 92 */ LS_Polyobj_OR_Move, diff --git a/src/p_lnspec.h b/src/p_lnspec.h index 2f5a67810..050e0fe7b 100644 --- a/src/p_lnspec.h +++ b/src/p_lnspec.h @@ -104,6 +104,7 @@ typedef enum { dDamage_LavaWimpy = 82, dDamage_LavaHefty = 83, dScroll_EastLavaDamage = 84, + hDamage_Sludge = 85, Sector_Outside = 87, // And here are some for Strife @@ -113,8 +114,9 @@ typedef enum { sDamage_SuperHellslime = 116, Scroll_StrifeCurrent = 118, - // Caverns of Darkness healing sector - Sector_Heal = 196, + + Sector_Hidden = 195, + Sector_Heal = 196, // Caverns of Darkness healing sector Light_OutdoorLightning = 197, Light_IndoorLightning1 = 198, diff --git a/src/p_local.h b/src/p_local.h index 80378fe95..8c71f0a97 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -94,7 +94,7 @@ APlayerPawn *P_SpawnPlayer (FMapThing *mthing, bool tempplayer=false); void P_ThrustMobj (AActor *mo, angle_t angle, fixed_t move); int P_FaceMobj (AActor *source, AActor *target, angle_t *delta); -bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax); +bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax, bool precise = false); enum EPuffFlags { @@ -241,6 +241,7 @@ struct FLineOpening void P_LineOpening (FLineOpening &open, AActor *thing, const line_t *linedef, fixed_t x, fixed_t y, fixed_t refx=FIXED_MIN, fixed_t refy=0); class FBoundingBox; +struct polyblock_t; class FBlockLinesIterator { @@ -296,7 +297,7 @@ class FBlockThingsIterator public: FBlockThingsIterator(int minx, int miny, int maxx, int maxy); FBlockThingsIterator(const FBoundingBox &box); - AActor *Next(); + AActor *Next(bool centeronly = false); void Reset() { StartBlock(minx, miny); } }; @@ -311,7 +312,7 @@ class FPathTraverse unsigned int count; void AddLineIntercepts(int bx, int by); - void AddThingIntercepts(int bx, int by, FBlockThingsIterator &it); + void AddThingIntercepts(int bx, int by, FBlockThingsIterator &it, bool compatible); public: intercept_t *Next(); @@ -324,6 +325,7 @@ public: #define PT_ADDLINES 1 #define PT_ADDTHINGS 2 +#define PT_COMPATIBLE 4 AActor *P_BlockmapSearch (AActor *mo, int distance, AActor *(*check)(AActor*, int, void *), void *params = NULL); AActor *P_RoughMonsterSearch (AActor *mo, int distance); @@ -421,13 +423,13 @@ enum ALF_CHECKCONVERSATION = 8, }; -AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismelee = false); -AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, FName pufftype, bool ismelee = false); +AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismelee = false, AActor **victim = NULL); +AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, FName damageType, FName pufftype, bool ismelee = false, AActor **victim = NULL); void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, angle_t angle, int pitch); void P_TraceBleed (int damage, AActor *target, AActor *missile); // missile version void P_TraceBleed (int damage, AActor *target); // random direction version -void P_RailAttack (AActor *source, int damage, int offset, int color1 = 0, int color2 = 0, float maxdiff = 0, bool silent = false, PClassActor *puff = NULL, bool pierce = true); // [RH] Shoot a railgun +void P_RailAttack (AActor *source, int damage, int offset, int color1 = 0, int color2 = 0, float maxdiff = 0, bool silent = false, PClassActor *puff = NULL, bool pierce = true, angle_t angleoffset = 0, angle_t pitchoffset = 0); // [RH] Shoot a railgun bool P_HitFloor (AActor *thing); bool P_HitWater (AActor *thing, sector_t *sec, fixed_t splashx = FIXED_MIN, fixed_t splashy = FIXED_MIN, fixed_t splashz=FIXED_MIN, bool checkabove = false, bool alert = true); void P_CheckSplash(AActor *self, fixed_t distance); @@ -458,9 +460,10 @@ const secplane_t * P_CheckSlopeWalk (AActor *actor, fixed_t &xmove, fixed_t &ymo // (For ZDoom itself this doesn't make any difference here but for GZDoom it does.) // //---------------------------------------------------------------------------------- +subsector_t *P_PointInSubsector (fixed_t x, fixed_t y); inline sector_t *P_PointInSector(fixed_t x, fixed_t y) { - return R_PointInSubsector(x,y)->sector; + return P_PointInSubsector(x,y)->sector; } // @@ -482,6 +485,7 @@ extern FBlockNode** blocklinks; // for thing chains // void P_TouchSpecialThing (AActor *special, AActor *toucher); void P_DamageMobj (AActor *target, AActor *inflictor, AActor *source, int damage, FName mod, int flags=0); +void P_PoisonMobj (AActor *target, AActor *inflictor, AActor *source, int damage, int duration, int period); bool P_GiveBody (AActor *actor, int num); bool P_PoisonPlayer (player_t *player, AActor *poisoner, AActor *source, int poison); void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPainSound); @@ -507,8 +511,9 @@ typedef enum bool EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle, int direction, bool overRide); bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, fixed_t dist, bool overRide); +bool EV_MovePolyTo (line_t *line, int polyNum, int speed, fixed_t x, fixed_t y, bool overRide); bool EV_OpenPolyDoor (line_t *line, int polyNum, int speed, angle_t angle, int delay, int distance, podoortype_t type); - +bool EV_StopPoly (int polyNum); // [RH] Data structure for P_SpawnMapThing() to keep track @@ -539,12 +544,9 @@ extern int po_NumPolyobjs; extern polyspawns_t *polyspawns; // [RH] list of polyobject things to spawn -bool PO_MovePolyobj (int num, int x, int y, bool force=false); -bool PO_RotatePolyobj (int num, angle_t angle); void PO_Init (); bool PO_Busy (int polyobj); -void PO_ClosestPoint(const FPolyObj *poly, fixed_t ox, fixed_t oy, fixed_t &x, fixed_t &y, seg_t **seg); -struct FPolyObj *PO_GetPolyobj(int polyNum); +FPolyObj *PO_GetPolyobj(int polyNum); // // P_SPEC diff --git a/src/p_map.cpp b/src/p_map.cpp index 5df43255b..907328f96 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -620,16 +620,17 @@ bool PIT_CheckLine (line_t *ld, const FBoundingBox &box, FCheckPosition &tm) return false; } - // MBF bouncers are treated as missiles here. - bool Projectile = (tm.thing->flags & MF_MISSILE || tm.thing->BounceFlags & BOUNCE_MBF); - // MBF considers that friendly monsters are not blocked by monster-blocking lines. - // This is added here as a compatibility option. Note that monsters that are dehacked - // into being friendly with the MBF flag automatically gain MF3_NOBLOCKMONST, so this - // just optionally generalizes the behavior to other friendly monsters. - bool NotBlocked = ((tm.thing->flags3 & MF3_NOBLOCKMONST) - || ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (tm.thing->flags & MF_FRIENDLY))); + // MBF bouncers are treated as missiles here. + bool Projectile = (tm.thing->flags & MF_MISSILE || tm.thing->BounceFlags & BOUNCE_MBF); + // MBF considers that friendly monsters are not blocked by monster-blocking lines. + // This is added here as a compatibility option. Note that monsters that are dehacked + // into being friendly with the MBF flag automatically gain MF3_NOBLOCKMONST, so this + // just optionally generalizes the behavior to other friendly monsters. + bool NotBlocked = ((tm.thing->flags3 & MF3_NOBLOCKMONST) + || ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (tm.thing->flags & MF_FRIENDLY))); - if (!(Projectile) || (ld->flags & (ML_BLOCKEVERYTHING|ML_BLOCKPROJECTILE))) { + if (!(Projectile) || (ld->flags & (ML_BLOCKEVERYTHING|ML_BLOCKPROJECTILE))) + { if (ld->flags & ML_RAILING) { rail = true; @@ -654,8 +655,14 @@ bool PIT_CheckLine (line_t *ld, const FBoundingBox &box, FCheckPosition &tm) if (!(tm.thing->flags & MF_DROPOFF) && !(tm.thing->flags & (MF_NOGRAVITY|MF_NOCLIP))) { - if (ld->frontsector->floorplane.c < STEEPSLOPE || - ld->backsector->floorplane.c < STEEPSLOPE) + secplane_t frontplane = ld->frontsector->floorplane; + secplane_t backplane = ld->backsector->floorplane; +#ifdef _3DFLOORS + // Check 3D floors as well + frontplane = P_FindFloorPlane(ld->frontsector, tm.thing->x, tm.thing->y, tm.thing->floorz); + backplane = P_FindFloorPlane(ld->backsector, tm.thing->x, tm.thing->y, tm.thing->floorz); +#endif + if (frontplane.c < STEEPSLOPE || backplane.c < STEEPSLOPE) { const msecnode_t *node = tm.thing->touching_sectorlist; bool allow = false; @@ -1087,6 +1094,13 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) P_RipperBlood (tm.thing, thing); } S_Sound (tm.thing, CHAN_BODY, "misc/ripslop", 1, ATTN_IDLE); + + // Do poisoning (if using new style poison) + if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN) + { + P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod); + } + damage = tm.thing->GetMissileDamage (3, 2); P_DamageMobj (thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT)) @@ -1108,6 +1122,13 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) return true; } } + + // Do poisoning (if using new style poison) + if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN) + { + P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod); + } + // Do damage damage = tm.thing->GetMissileDamage ((tm.thing->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1); if ((damage > 0) || (tm.thing->flags6 & MF6_FORCEPAIN)) @@ -1148,7 +1169,8 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) } solid = (thing->flags & MF_SOLID) && !(thing->flags & MF_NOCLIP) && - (tm.thing->flags & MF_SOLID); + ((tm.thing->flags & MF_SOLID) || (tm.thing->flags6 & MF6_BLOCKEDBYSOLIDACTORS)); + // Check for special pickup if ((thing->flags & MF_SPECIAL) && (tm.thing->flags & MF_PICKUP) // [RH] The next condition is to compensate for the extra height @@ -1210,7 +1232,6 @@ bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm) { sector_t *newsec; AActor *thingblocker; - AActor *fakedblocker; fixed_t realheight = thing->height; tm.thing = thing; @@ -1275,13 +1296,8 @@ bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm) return true; // Check things first, possibly picking things up. - // The bounding box is extended by MAXRADIUS - // because DActors are grouped into mapblocks - // based on their origin point, and can overlap - // into adjacent blocks by up to MAXRADIUS units. thing->BlockingMobj = NULL; thingblocker = NULL; - fakedblocker = NULL; if (thing->player) { // [RH] Fake taller height to catch stepping up into things. thing->height = realheight + thing->MaxStepHeight; @@ -1328,7 +1344,6 @@ bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm) } // Nothing is blocking us, but this actor potentially could // if there is something else to step on. - fakedblocker = BlockingMobj; thing->BlockingMobj = NULL; } else @@ -2182,48 +2197,53 @@ void FSlide::HitSlideLine (line_t* ld) } // ^ else // | { // phares -#if 0 - fixed_t newlen; - - if (deltaangle > ANG180) - deltaangle += ANG180; - // I_Error ("SlideLine: ang>ANG180"); - - lineangle >>= ANGLETOFINESHIFT; - deltaangle >>= ANGLETOFINESHIFT; - - newlen = FixedMul (movelen, finecosine[deltaangle]); - - tmxmove = FixedMul (newlen, finecosine[lineangle]); - tmymove = FixedMul (newlen, finesine[lineangle]); -#else - divline_t dll, dlv; - fixed_t inter1, inter2, inter3; - - P_MakeDivline (ld, &dll); - - dlv.x = slidemo->x; - dlv.y = slidemo->y; - dlv.dx = dll.dy; - dlv.dy = -dll.dx; - - inter1 = P_InterceptVector(&dll, &dlv); - - dlv.dx = tmxmove; - dlv.dy = tmymove; - inter2 = P_InterceptVector (&dll, &dlv); - inter3 = P_InterceptVector (&dlv, &dll); - - if (inter3 != 0) + // Doom's original algorithm here does not work well due to imprecisions of the sine table. + // However, keep it active if the wallrunning compatibility flag is on + if (i_compatflags & COMPATF_WALLRUN) { - tmxmove = Scale (inter2-inter1, dll.dx, inter3); - tmymove = Scale (inter2-inter1, dll.dy, inter3); + fixed_t newlen; + + if (deltaangle > ANG180) + deltaangle += ANG180; + // I_Error ("SlideLine: ang>ANG180"); + + lineangle >>= ANGLETOFINESHIFT; + deltaangle >>= ANGLETOFINESHIFT; + + newlen = FixedMul (movelen, finecosine[deltaangle]); + + tmxmove = FixedMul (newlen, finecosine[lineangle]); + tmymove = FixedMul (newlen, finesine[lineangle]); } else { - tmxmove = tmymove = 0; + divline_t dll, dlv; + fixed_t inter1, inter2, inter3; + + P_MakeDivline (ld, &dll); + + dlv.x = slidemo->x; + dlv.y = slidemo->y; + dlv.dx = dll.dy; + dlv.dy = -dll.dx; + + inter1 = P_InterceptVector(&dll, &dlv); + + dlv.dx = tmxmove; + dlv.dy = tmymove; + inter2 = P_InterceptVector (&dll, &dlv); + inter3 = P_InterceptVector (&dlv, &dll); + + if (inter3 != 0) + { + tmxmove = Scale (inter2-inter1, dll.dx, inter3); + tmymove = Scale (inter2-inter1, dll.dy, inter3); + } + else + { + tmxmove = tmymove = 0; + } } -#endif } // phares } @@ -2937,7 +2957,7 @@ bool aim_t::AimTraverse3DFloors(const divline_t &trace, intercept_t * in) void aim_t::AimTraverse (fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy, AActor *target) { - FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES|PT_ADDTHINGS); + FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES|PT_ADDTHINGS|PT_COMPATIBLE); intercept_t *in; while ((in = it.Next())) @@ -3294,7 +3314,7 @@ static bool CheckForSpectral (FTraceResults &res) //========================================================================== AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, - int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismeleeattack) + int pitch, int damage, FName damageType, PClassActor *pufftype, bool ismeleeattack, AActor **victim) { fixed_t vx, vy, vz, shootz; FTraceResults trace; @@ -3305,6 +3325,11 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, AActor *puff = NULL; int flags = ismeleeattack? PF_MELEERANGE : 0; + if (victim != NULL) + { + *victim = NULL; + } + angle >>= ANGLETOFINESHIFT; pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT; @@ -3420,9 +3445,12 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD)); // Hit a thing, so it could be either a puff or blood - hitx = t1->x + FixedMul (vx, trace.Distance); - hity = t1->y + FixedMul (vy, trace.Distance); - hitz = shootz + FixedMul (vz, trace.Distance); + fixed_t dist = trace.Distance; + // position a bit closer for puffs/blood if using compatibility mode. + if (i_compatflags & COMPATF_HITSCAN) dist -= 10*FRACUNIT; + hitx = t1->x + FixedMul (vx, dist); + hity = t1->y + FixedMul (vy, dist); + hitz = shootz + FixedMul (vz, dist); // Spawn bullet puffs or blood spots, depending on target type. if ((puffDefaults->flags3 & MF3_PUFFONACTORS) || @@ -3463,16 +3491,23 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, trace.Actor, srcangle, srcpitch); } } + + // Allow puffs to inflict poison damage, so that hitscans can poison, too. + if (puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) + { + P_PoisonMobj(trace.Actor, puff ? puff : t1, t1, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod); + } + // [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. if (damage || (puffDefaults->flags6 & MF6_FORCEPAIN)) { - int flags = DMG_INFLICTOR_IS_PUFF; + int dmgflags = DMG_INFLICTOR_IS_PUFF; // Allow MF5_PIERCEARMOR on a weapon as well. if (t1->player != NULL && t1->player->ReadyWeapon != NULL && t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR) { - flags |= DMG_NO_ARMOR; + dmgflags |= DMG_NO_ARMOR; } if (puff == NULL) @@ -3482,7 +3517,11 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, puff = P_SpawnPuff (t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, flags|PF_HITTHING|PF_TEMPORARY); killPuff = true; } - P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType, flags); + P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType, dmgflags); + } + if (victim != NULL) + { + *victim = trace.Actor; } } if (trace.CrossedWater) @@ -3505,16 +3544,20 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, } AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, - int pitch, int damage, FName damageType, FName pufftype, bool ismeleeattack) + int pitch, int damage, FName damageType, FName pufftype, bool ismeleeattack, AActor **victim) { PClassActor *type = PClass::FindActor(pufftype); + if (victim != NULL) + { + *victim = NULL; + } if (type == NULL) { Printf("Attempt to spawn unknown actor type '%s'\n", pufftype.GetChars()); } else { - return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type, ismeleeattack); + return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type, ismeleeattack, victim); } return NULL; } @@ -3732,7 +3775,7 @@ static bool ProcessNoPierceRailHit (FTraceResults &res) // //========================================================================== -void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, float maxdiff, bool silent, PClassActor *puffclass, bool pierce) +void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, float maxdiff, bool silent, PClassActor *puffclass, bool pierce, angle_t angleoffset, angle_t pitchoffset) { fixed_t vx, vy, vz; angle_t angle, pitch; @@ -3746,8 +3789,8 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color puffclass = PClass::FindActor(NAME_BulletPuff); } - pitch = (angle_t)(-source->pitch) >> ANGLETOFINESHIFT; - angle = source->angle >> ANGLETOFINESHIFT; + pitch = ((angle_t)(-source->pitch) + pitchoffset) >> ANGLETOFINESHIFT; + angle = (source->angle + angleoffset) >> ANGLETOFINESHIFT; vx = FixedMul (finecosine[pitch], finecosine[angle]); vy = FixedMul (finecosine[pitch], finesine[angle]); @@ -3767,7 +3810,7 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color shootz += 8*FRACUNIT; } - angle = (source->angle - ANG90) >> ANGLETOFINESHIFT; + angle = ((source->angle + angleoffset) - ANG90) >> ANGLETOFINESHIFT; x1 += offset*finecosine[angle]; y1 += offset*finesine[angle]; @@ -3823,10 +3866,14 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color else { spawnpuff = (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF); - P_SpawnBlood (x, y, z, source->angle - ANG180, damage, RailHits[i].HitActor); + P_SpawnBlood (x, y, z, (source->angle + angleoffset) - ANG180, damage, RailHits[i].HitActor); P_TraceBleed (damage, x, y, z, RailHits[i].HitActor, source->angle, pitch); } - if (spawnpuff) P_SpawnPuff (source, puffclass, x, y, z, source->angle - ANG90, 1, PF_HITTHING); + if (spawnpuff) P_SpawnPuff (source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, PF_HITTHING); + + if (puffDefaults && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN) + P_PoisonMobj(RailHits[i].HitActor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod); + P_DamageMobj (RailHits[i].HitActor, thepuff? thepuff:source, source, damage, damagetype, DMG_INFLICTOR_IS_PUFF); } @@ -3836,7 +3883,7 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color SpawnShootDecal (source, trace); if (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF) { - P_SpawnPuff (source, puffclass, trace.X, trace.Y, trace.Z, source->angle - ANG90, 1, 0); + P_SpawnPuff (source, puffclass, trace.X, trace.Y, trace.Z, (source->angle + angleoffset) - ANG90, 1, 0); } } @@ -3986,7 +4033,7 @@ bool P_UseTraverse(AActor *usething, fixed_t endx, fixed_t endy, bool &foundline } FLineOpening open; - if (in->d.line->special == 0 || !(in->d.line->activation & (SPAC_Use|SPAC_UseThrough))) + if (in->d.line->special == 0 || !(in->d.line->activation & (SPAC_Use|SPAC_UseThrough|SPAC_UseBack))) { blocked: if (in->d.line->flags & (ML_BLOCKEVERYTHING|ML_BLOCKUSE)) @@ -4032,27 +4079,46 @@ bool P_UseTraverse(AActor *usething, fixed_t endx, fixed_t endy, bool &foundline } if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1) - // [RH] continue traversal for two-sided lines - //return in->d.line->backsector != NULL; // don't use back side - goto blocked; // do a proper check for back sides of triggers - - P_ActivateLine (in->d.line, usething, 0, SPAC_Use); + { + if (!(in->d.line->activation & SPAC_UseBack)) + { + // [RH] continue traversal for two-sided lines + //return in->d.line->backsector != NULL; // don't use back side + goto blocked; // do a proper check for back sides of triggers + } + else + { + P_ActivateLine (in->d.line, usething, 1, SPAC_UseBack); + return true; + } + } + else + { + if ((in->d.line->activation & (SPAC_Use|SPAC_UseThrough|SPAC_UseBack)) == SPAC_UseBack) + { + goto blocked; // Line cannot be used from front side so treat it as a non-trigger line + } + + P_ActivateLine (in->d.line, usething, 0, SPAC_Use); + + //WAS can't use more than one special line in a row + //jff 3/21/98 NOW multiple use allowed with enabling line flag + //[RH] And now I've changed it again. If the line is of type + // SPAC_USE, then it eats the use. Everything else passes + // it through, including SPAC_USETHROUGH. + if (i_compatflags & COMPATF_USEBLOCKING) + { + if (in->d.line->activation & SPAC_UseThrough) continue; + else return true; + } + else + { + if (!(in->d.line->activation & SPAC_Use)) continue; + else return true; + } - //WAS can't use more than one special line in a row - //jff 3/21/98 NOW multiple use allowed with enabling line flag - //[RH] And now I've changed it again. If the line is of type - // SPAC_USE, then it eats the use. Everything else passes - // it through, including SPAC_USETHROUGH. - if (i_compatflags & COMPATF_USEBLOCKING) - { - if (in->d.line->activation & SPAC_UseThrough) continue; - else return true; - } - else - { - if (!(in->d.line->activation & SPAC_Use)) continue; - else return true; } + } return false; } diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index d4ea1bdf9..9f4ddb39a 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -39,6 +39,7 @@ // State. #include "r_state.h" #include "templates.h" +#include "po_man.h" static AActor *RoughBlockCheck (AActor *mo, int index, void *); @@ -63,21 +64,20 @@ fixed_t P_AproxDistance (fixed_t dx, fixed_t dy) // P_InterceptVector // // Returns the fractional intercept point along the first divline. -// This is only called by the addthings and addlines traversers. // //========================================================================== fixed_t P_InterceptVector (const divline_t *v2, const divline_t *v1) { -#if 1 // [RH] Use 64 bit ints, so long divlines don't overflow +#if 0 // [RH] Use 64 bit ints, so long divlines don't overflow - SQWORD den = ((SQWORD)v1->dy*v2->dx - (SQWORD)v1->dx*v2->dy) >> FRACBITS; + SQWORD den = ( ((SQWORD)v1->dy*v2->dx - (SQWORD)v1->dx*v2->dy) >> FRACBITS ); if (den == 0) return 0; // parallel SQWORD num = ((SQWORD)(v1->x - v2->x)*v1->dy + (SQWORD)(v2->y - v1->y)*v1->dx); return (fixed_t)(num / den); -#elif 1 // This is the original Doom version +#elif 0 // This is the original Doom version fixed_t frac; fixed_t num; @@ -97,19 +97,24 @@ fixed_t P_InterceptVector (const divline_t *v2, const divline_t *v1) return frac; -#else // UNUSED, float debug. +#else // optimized version of the float debug version. A lot faster on modern systens. - float frac; - float num; - float den; - float v1x = (float)v1->x/FRACUNIT; - float v1y = (float)v1->y/FRACUNIT; - float v1dx = (float)v1->dx/FRACUNIT; - float v1dy = (float)v1->dy/FRACUNIT; - float v2x = (float)v2->x/FRACUNIT; - float v2y = (float)v2->y/FRACUNIT; - float v2dx = (float)v2->dx/FRACUNIT; - float v2dy = (float)v2->dy/FRACUNIT; + + double frac; + double num; + double den; + + // There's no need to divide by FRACUNIT here. + // At the end both num and den will contain a factor + // 1/(FRACUNIT*FRACUNIT) so they'll cancel each other out. + double v1x = (double)v1->x; + double v1y = (double)v1->y; + double v1dx = (double)v1->dx; + double v1dy = (double)v1->dy; + double v2x = (double)v2->x; + double v2y = (double)v2->y; + double v2dx = (double)v2->dx; + double v2dy = (double)v2->dy; den = v1dy*v2dx - v1dx*v2dy; @@ -119,10 +124,11 @@ fixed_t P_InterceptVector (const divline_t *v2, const divline_t *v1) num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; frac = num / den; - return frac*FRACUNIT; + return FLOAT2FIXED(frac); #endif } + //========================================================================== // // P_LineOpening @@ -296,7 +302,7 @@ void AActor::LinkToWorld (bool buggy) // link into subsector sector_t *sec; - if (!buggy || numnodes == 0) + if (!buggy || numgamenodes == 0) { sec = P_PointInSector (x, y); } @@ -316,6 +322,7 @@ void AActor::LinkToWorld (sector_t *sec) return; } Sector = sec; + subsector = R_PointInSubsector(x, y); // this is from the rendering nodes, not the gameplay nodes! if ( !(flags & MF_NOSECTOR) ) { @@ -454,7 +461,7 @@ static int R_PointOnSideSlow (fixed_t x, fixed_t y, node_t *node) sector_t *AActor::LinkToWorldForMapThing () { - node_t *node = nodes + numnodes - 1; + node_t *node = gamenodes + numgamenodes - 1; do { @@ -687,9 +694,9 @@ line_t *FBlockLinesIterator::Next() polyLink->polyobj->validcount = validcount; } - line_t *ld = polyLink->polyobj->lines[polyIndex]; + line_t *ld = polyLink->polyobj->Linedefs[polyIndex]; - if (++polyIndex >= polyLink->polyobj->numlines) + if (++polyIndex >= (int)polyLink->polyobj->Linedefs.Size()) { polyLink = polyLink->next; polyIndex = 0; @@ -825,7 +832,7 @@ void FBlockThingsIterator::SwitchBlock(int x, int y) // //=========================================================================== -AActor *FBlockThingsIterator::Next() +AActor *FBlockThingsIterator::Next(bool centeronly) { for (;;) { @@ -842,37 +849,55 @@ AActor *FBlockThingsIterator::Next() { // This actor doesn't span blocks, so we know it can only ever be checked once. return me; } - size_t hash = ((size_t)me >> 3) % countof(Buckets); - for (i = Buckets[hash]; i >= 0; ) + if (centeronly) { - entry = GetHashEntry(i); - if (entry->Actor == me) - { // I've already been checked. Skip to the next actor. - break; + // Block boundaries for compatibility mode + fixed_t blockleft = (curx << MAPBLOCKSHIFT) + bmaporgx; + fixed_t blockright = blockleft + MAPBLOCKSIZE; + fixed_t blockbottom = (cury << MAPBLOCKSHIFT) + bmaporgy; + fixed_t blocktop = blockbottom + MAPBLOCKSIZE; + + // only return actors with the center in this block + if (me->x >= blockleft && me->x < blockright && + me->y >= blockbottom && me->y < blocktop) + { + return me; } - i = entry->Next; } - if (i < 0) - { // Add me to the hash table and return me. - if (NumFixedHash < (int)countof(FixedHash)) + else + { + size_t hash = ((size_t)me >> 3) % countof(Buckets); + for (i = Buckets[hash]; i >= 0; ) { - entry = &FixedHash[NumFixedHash]; - entry->Next = Buckets[hash]; - Buckets[hash] = NumFixedHash++; - } - else - { - if (DynHash.Size() == 0) - { - DynHash.Grow(50); + entry = GetHashEntry(i); + if (entry->Actor == me) + { // I've already been checked. Skip to the next actor. + break; } - i = DynHash.Reserve(1); - entry = &DynHash[i]; - entry->Next = Buckets[hash]; - Buckets[hash] = i + countof(FixedHash); + i = entry->Next; + } + if (i < 0) + { // Add me to the hash table and return me. + if (NumFixedHash < (int)countof(FixedHash)) + { + entry = &FixedHash[NumFixedHash]; + entry->Next = Buckets[hash]; + Buckets[hash] = NumFixedHash++; + } + else + { + if (DynHash.Size() == 0) + { + DynHash.Grow(50); + } + i = DynHash.Reserve(1); + entry = &DynHash[i]; + entry->Next = Buckets[hash]; + Buckets[hash] = i + countof(FixedHash); + } + entry->Actor = me; + return me; } - entry->Actor = me; - return me; } } @@ -959,92 +984,147 @@ void FPathTraverse::AddLineIntercepts(int bx, int by) // //=========================================================================== -void FPathTraverse::AddThingIntercepts (int bx, int by, FBlockThingsIterator &it) +void FPathTraverse::AddThingIntercepts (int bx, int by, FBlockThingsIterator &it, bool compatible) { AActor *thing; it.SwitchBlock(bx, by); - while ((thing = it.Next())) + while ((thing = it.Next(compatible))) { int numfronts = 0; divline_t line; int i; - // [RH] Don't check a corner to corner crossection for hit. - // Instead, check against the actual bounding box. - // There's probably a smarter way to determine which two sides - // of the thing face the trace than by trying all four sides... - for (i = 0; i < 4; ++i) + if (!compatible) { - switch (i) + // [RH] Don't check a corner to corner crossection for hit. + // Instead, check against the actual bounding box (but not if compatibility optioned.) + + // There's probably a smarter way to determine which two sides + // of the thing face the trace than by trying all four sides... + for (i = 0; i < 4; ++i) { - case 0: // Top edge - line.x = thing->x + thing->radius; - line.y = thing->y + thing->radius; - line.dx = -thing->radius * 2; - line.dy = 0; - break; - - case 1: // Right edge - line.x = thing->x + thing->radius; - line.y = thing->y - thing->radius; - line.dx = 0; - line.dy = thing->radius * 2; - break; - - case 2: // Bottom edge - line.x = thing->x - thing->radius; - line.y = thing->y - thing->radius; - line.dx = thing->radius * 2; - line.dy = 0; - break; - - case 3: // Left edge - line.x = thing->x - thing->radius; - line.y = thing->y + thing->radius; - line.dx = 0; - line.dy = thing->radius * -2; - break; - } - // Check if this side is facing the trace origin - if (P_PointOnDivlineSide (trace.x, trace.y, &line) == 0) - { - numfronts++; - - // If it is, see if the trace crosses it - if (P_PointOnDivlineSide (line.x, line.y, &trace) != - P_PointOnDivlineSide (line.x + line.dx, line.y + line.dy, &trace)) + switch (i) { - // It's a hit - fixed_t frac = P_InterceptVector (&trace, &line); - if (frac < 0) - { // behind source + case 0: // Top edge + line.x = thing->x + thing->radius; + line.y = thing->y + thing->radius; + line.dx = -thing->radius * 2; + line.dy = 0; + break; + + case 1: // Right edge + line.x = thing->x + thing->radius; + line.y = thing->y - thing->radius; + line.dx = 0; + line.dy = thing->radius * 2; + break; + + case 2: // Bottom edge + line.x = thing->x - thing->radius; + line.y = thing->y - thing->radius; + line.dx = thing->radius * 2; + line.dy = 0; + break; + + case 3: // Left edge + line.x = thing->x - thing->radius; + line.y = thing->y + thing->radius; + line.dx = 0; + line.dy = thing->radius * -2; + break; + } + // Check if this side is facing the trace origin + if (P_PointOnDivlineSide (trace.x, trace.y, &line) == 0) + { + numfronts++; + + // If it is, see if the trace crosses it + if (P_PointOnDivlineSide (line.x, line.y, &trace) != + P_PointOnDivlineSide (line.x + line.dx, line.y + line.dy, &trace)) + { + // It's a hit + fixed_t frac = P_InterceptVector (&trace, &line); + if (frac < 0) + { // behind source + continue; + } + + intercept_t newintercept; + newintercept.frac = frac; + newintercept.isaline = false; + newintercept.done = false; + newintercept.d.thing = thing; + intercepts.Push (newintercept); continue; } + } + } + // If none of the sides was facing the trace, then the trace + // must have started inside the box, so add it as an intercept. + if (numfronts == 0) + { + intercept_t newintercept; + newintercept.frac = 0; + newintercept.isaline = false; + newintercept.done = false; + newintercept.d.thing = thing; + intercepts.Push (newintercept); + } + } + else + { + // Old code for compatibility purposes + fixed_t x1, y1, x2, y2; + int s1, s2; + divline_t dl; + fixed_t frac; + + bool tracepositive = (trace.dx ^ trace.dy)>0; + + // check a corner to corner crossection for hit + if (tracepositive) + { + x1 = thing->x - thing->radius; + y1 = thing->y + thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y - thing->radius; + } + else + { + x1 = thing->x - thing->radius; + y1 = thing->y - thing->radius; + + x2 = thing->x + thing->radius; + y2 = thing->y + thing->radius; + } + + s1 = P_PointOnDivlineSide (x1, y1, &trace); + s2 = P_PointOnDivlineSide (x2, y2, &trace); + + if (s1 != s2) + { + dl.x = x1; + dl.y = y1; + dl.dx = x2-x1; + dl.dy = y2-y1; + + frac = P_InterceptVector (&trace, &dl); + + if (frac >= 0) + { intercept_t newintercept; newintercept.frac = frac; newintercept.isaline = false; newintercept.done = false; newintercept.d.thing = thing; intercepts.Push (newintercept); - continue; } } } - - // If none of the sides was facing the trace, then the trace - // must have started inside the box, so add it as an intercept. - if (numfronts == 0) - { - intercept_t newintercept; - newintercept.frac = 0; - newintercept.isaline = false; - newintercept.done = false; - newintercept.d.thing = thing; - intercepts.Push (newintercept); - } } } @@ -1196,6 +1276,8 @@ FPathTraverse::FPathTraverse (fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, in // from skipping the break statement. mapx = xt1; mapy = yt1; + + bool compatible = (flags & PT_COMPATIBLE) && (i_compatflags & COMPATF_HITSCAN); // we want to use one list of checked actors for the entire operation FBlockThingsIterator btit; @@ -1208,7 +1290,7 @@ FPathTraverse::FPathTraverse (fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, in if (flags & PT_ADDTHINGS) { - AddThingIntercepts(mapx, mapy, btit); + AddThingIntercepts(mapx, mapy, btit, compatible); } if (mapx == xt2 && mapy == yt2) @@ -1238,21 +1320,28 @@ FPathTraverse::FPathTraverse (fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2, in // being entered need to be checked (which will happen when this loop // continues), but the other two blocks adjacent to the corner also need to // be checked. - if (flags & PT_ADDLINES) + if (!compatible) { - AddLineIntercepts(mapx + mapxstep, mapy); - AddLineIntercepts(mapx, mapy + mapystep); + if (flags & PT_ADDLINES) + { + AddLineIntercepts(mapx + mapxstep, mapy); + AddLineIntercepts(mapx, mapy + mapystep); + } + + if (flags & PT_ADDTHINGS) + { + AddThingIntercepts(mapx + mapxstep, mapy, btit, false); + AddThingIntercepts(mapx, mapy + mapystep, btit, false); + } + xintercept += xstep; + yintercept += ystep; + mapx += mapxstep; + mapy += mapystep; } - - if (flags & PT_ADDTHINGS) + else { - AddThingIntercepts(mapx + mapxstep, mapy, btit); - AddThingIntercepts(mapx, mapy + mapystep, btit); + count = 100; // Doom originally did not handle this case so do the same in compatibility mode. } - xintercept += xstep; - yintercept += ystep; - mapx += mapxstep; - mapy += mapystep; break; } } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index c339b3c9a..aba522a9c 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -60,6 +60,7 @@ #include "colormatcher.h" #include "v_palette.h" #include "p_enemy.h" +#include "gstrings.h" // MACROS ------------------------------------------------------------------ @@ -171,6 +172,7 @@ IMPLEMENT_POINTY_CLASS (AActor) DECLARE_POINTER (Inventory) DECLARE_POINTER (LastHeard) DECLARE_POINTER (master) + DECLARE_POINTER (Poisoner) END_POINTERS AActor::~AActor () @@ -345,6 +347,11 @@ void AActor::Serialize (FArchive &arc) WeaveIndexZ = 0; } } + if (SaveVersion >= 2450) + { + arc << PoisonDamageReceived << PoisonDurationReceived << PoisonPeriodReceived << Poisoner; + arc << PoisonDamage << PoisonDuration << PoisonPeriod; + } // Skip past uservar array in old savegames if (SaveVersion < 1933) @@ -354,65 +361,24 @@ void AActor::Serialize (FArchive &arc) arc << foo; } - if (arc.IsStoring ()) + if (SaveVersion > 2560) { - int convnum = 0; - unsigned int i; - - if (Conversation != NULL) - { - for (i = 0; i < StrifeDialogues.Size(); ++i) - { - if (StrifeDialogues[i] == GetDefault()->Conversation) - { - break; - } - } - for (; i + convnum < StrifeDialogues.Size(); ++convnum) - { - if (StrifeDialogues[i + convnum] == Conversation) - { - break; - } - } - if (i + convnum < StrifeDialogues.Size()) - { - convnum++; - } - else - { - convnum = 0; - } - } - arc.WriteCount (convnum); + arc << ConversationRoot << Conversation; } - else + else // old code which uses relative indexing. { int convnum; - unsigned int i; convnum = arc.ReadCount(); - if (convnum == 0 || GetDefault()->Conversation == NULL) + if (GetConversation(GetClass()->TypeName) == -1) { Conversation = NULL; + ConversationRoot = -1; } else { - for (i = 0; i < StrifeDialogues.Size(); ++i) - { - if (StrifeDialogues[i] == GetDefault()->Conversation) - { - break; - } - } - if (i + convnum <= StrifeDialogues.Size()) - { - Conversation = StrifeDialogues[i + convnum - 1]; - } - else - { - Conversation = GetDefault()->Conversation; - } + // This cannot be restored anymore. + I_Error("Cannot load old savegames with active dialogues"); } } @@ -528,7 +494,7 @@ int AActor::GetTics(FState * newstate) // //========================================================================== -bool AActor::SetState (FState *newstate) +bool AActor::SetState (FState *newstate, bool nofunction) { if (debugfile && player && (player->cheats & CF_PREDICTING)) fprintf (debugfile, "for pl %td: SetState while predicting!\n", player-players); @@ -554,37 +520,41 @@ bool AActor::SetState (FState *newstate) tics = GetTics(newstate); renderflags = (renderflags & ~RF_FULLBRIGHT) | newstate->GetFullbright(); newsprite = newstate->sprite; - if (newsprite != 1) - { - // Sprite 1 is ----, which means "do not change the sprite" - frame = newstate->GetFrame(); - - if (!(flags4 & MF4_NOSKIN) && newsprite == SpawnState->sprite) - { // [RH] If the new sprite is the same as the original sprite, and - // this actor is attached to a player, use the player's skin's - // sprite. If a player is not attached, do not change the sprite - // unless it is different from the previous state's sprite; a - // player may have been attached, died, and respawned elsewhere, - // and we do not want to lose the skin on the body. If it wasn't - // for Dehacked, I would move sprite changing out of the states - // altogether, since actors rarely change their sprites after - // spawning. - if (player != NULL) - { - sprite = skins[player->userinfo.skin].sprite; + if (newsprite != SPR_FIXED) + { // okay to change sprite and/or frame + if (!newstate->GetSameFrame()) + { // okay to change frame + frame = newstate->GetFrame(); + } + if (newsprite != SPR_NOCHANGE) + { // okay to change sprite + if (!(flags4 & MF4_NOSKIN) && newsprite == SpawnState->sprite) + { // [RH] If the new sprite is the same as the original sprite, and + // this actor is attached to a player, use the player's skin's + // sprite. If a player is not attached, do not change the sprite + // unless it is different from the previous state's sprite; a + // player may have been attached, died, and respawned elsewhere, + // and we do not want to lose the skin on the body. If it wasn't + // for Dehacked, I would move sprite changing out of the states + // altogether, since actors rarely change their sprites after + // spawning. + if (player != NULL) + { + sprite = skins[player->userinfo.skin].sprite; + } + else if (newsprite != prevsprite) + { + sprite = newsprite; + } } - else if (newsprite != prevsprite) + else { sprite = newsprite; } } - else - { - sprite = newsprite; - } } - if (newstate->CallAction(this, this)) + if (!nofunction && newstate->CallAction(this, this)) { // Check whether the called action function resulted in destroying the actor if (ObjectFlags & OF_EuthanizeMe) @@ -593,67 +563,10 @@ bool AActor::SetState (FState *newstate) newstate = newstate->GetNextState(); } while (tics == 0); - screen->StateChanged(this); - return true; -} - -//---------------------------------------------------------------------------- -// -// FUNC AActor::SetStateNF -// -// Same as SetState, but does not call the state function. -// -//---------------------------------------------------------------------------- - -bool AActor::SetStateNF (FState *newstate) -{ - do + if (screen != NULL) { - if (newstate == NULL) - { - state = NULL; - Destroy (); - return false; - } - int prevsprite, newsprite; - - if (state != NULL) - { - prevsprite = state->sprite; - } - else - { - prevsprite = -1; - } - state = newstate; - tics = GetTics(newstate); - renderflags = (renderflags & ~RF_FULLBRIGHT) | newstate->GetFullbright(); - newsprite = newstate->sprite; - if (newsprite != 1) - { - // Sprite 1 is ----, which means "do not change the sprite" - - frame = newstate->GetFrame(); - if (!(flags4 & MF4_NOSKIN) && newsprite == SpawnState->sprite) - { - if (player != NULL && gameinfo.gametype != GAME_Hexen) - { - sprite = skins[player->userinfo.skin].sprite; - } - else if (newsprite != prevsprite) - { - sprite = newsprite; - } - } - else - { - sprite = newsprite; - } - } - newstate = newstate->GetNextState(); - } while (tics == 0); - - screen->StateChanged(this); + screen->StateChanged(this); + } return true; } @@ -824,7 +737,7 @@ AInventory *AActor::DropInventory (AInventory *item) // //============================================================================ -AInventory *AActor::FindInventory (PClassActor *type) +AInventory *AActor::FindInventory (PClassActor *type, bool subclass) { AInventory *item; @@ -834,9 +747,19 @@ AInventory *AActor::FindInventory (PClassActor *type) } for (item = Inventory; item != NULL; item = item->Inventory) { - if (item->GetClass() == type) + if (!subclass) { - break; + if (item->GetClass() == type) + { + break; + } + } + else + { + if (item->IsKindOf(type)) + { + break; + } } } return item; @@ -1528,8 +1451,8 @@ bool AActor::CanSeek(AActor *target) const if (target->flags5 & MF5_CANTSEEK) return false; if ((flags2 & MF2_DONTSEEKINVISIBLE) && ((target->flags & MF_SHADOW) || - target->renderflags & RF_INVISIBLE || - target->RenderStyle.IsVisible(target->alpha) + (target->renderflags & RF_INVISIBLE) || + !target->RenderStyle.IsVisible(target->alpha) ) ) return false; return true; @@ -1544,7 +1467,7 @@ bool AActor::CanSeek(AActor *target) const // //---------------------------------------------------------------------------- -bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax) +bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax, bool precise) { int dir; int dist; @@ -1580,28 +1503,54 @@ bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax) actor->angle -= delta; } angle = actor->angle>>ANGLETOFINESHIFT; - actor->velx = FixedMul (actor->Speed, finecosine[angle]); - actor->vely = FixedMul (actor->Speed, finesine[angle]); - - if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))) + + if (!precise) { - if (actor->z + actor->height < target->z || - target->z + target->height < actor->z) - { // Need to seek vertically - dist = P_AproxDistance (target->x - actor->x, target->y - actor->y); - dist = dist / actor->Speed; - if (dist < 1) - { - dist = 1; + actor->velx = FixedMul (actor->Speed, finecosine[angle]); + actor->vely = FixedMul (actor->Speed, finesine[angle]); + + if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))) + { + if (actor->z + actor->height < target->z || + target->z + target->height < actor->z) + { // Need to seek vertically + dist = P_AproxDistance (target->x - actor->x, target->y - actor->y); + dist = dist / actor->Speed; + if (dist < 1) + { + dist = 1; + } + actor->velz = ((target->z+target->height/2) - (actor->z+actor->height/2)) / dist; } - actor->velz = ((target->z+target->height/2) - (actor->z+actor->height/2)) / dist; } } + else + { + angle_t pitch = 0; + if (!(actor->flags3 & (MF3_FLOORHUGGER|MF3_CEILINGHUGGER))) + { // Need to seek vertically + double dist = MAX(1.0, FVector2(target->x - actor->x, target->y - actor->y).Length()); + // Aim at a player's eyes and at the middle of the actor for everything else. + fixed_t aimheight = target->height/2; + if (target->IsKindOf(RUNTIME_CLASS(APlayerPawn))) + { + aimheight = static_cast(target)->ViewHeight; + } + pitch = R_PointToAngle2(0, actor->z + actor->height/2, xs_CRoundToInt(dist), target->z + aimheight); + pitch >>= ANGLETOFINESHIFT; + } + + fixed_t xyscale = FixedMul(actor->Speed, finecosine[pitch]); + actor->velz = FixedMul(actor->Speed, finesine[pitch]); + actor->velx = FixedMul(xyscale, finecosine[angle]); + actor->vely = FixedMul(xyscale, finesine[angle]); + } return true; } + // // P_XYMovement // @@ -3189,41 +3138,27 @@ void AActor::Tick () velz <= 0 && floorz == z) { - const secplane_t * floorplane = &floorsector->floorplane; - static secplane_t copyplane; + secplane_t floorplane = floorsector->floorplane; #ifdef _3DFLOORS // Check 3D floors as well - if (floorsector->e) // apparently this can be called when the data is already gone- - for(unsigned int i=0;ie->XFloor.ffloors.Size();i++) - { - F3DFloor * rover= floorsector->e->XFloor.ffloors[i]; - if(!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue; - - if (rover->top.plane->ZatPoint(x, y) == floorz) - { - copyplane = *rover->top.plane; - if (copyplane.c<0) copyplane.FlipVert(); - floorplane = ©plane; - break; - } - } + floorplane = P_FindFloorPlane(floorsector, x, y, floorz); #endif - if (floorplane->c < STEEPSLOPE && - floorplane->ZatPoint (x, y) <= floorz) + if (floorplane.c < STEEPSLOPE && + floorplane.ZatPoint (x, y) <= floorz) { const msecnode_t *node; bool dopush = true; - if (floorplane->c > STEEPSLOPE*2/3) + if (floorplane.c > STEEPSLOPE*2/3) { for (node = touching_sectorlist; node; node = node->m_tnext) { const sector_t *sec = node->m_sector; if (sec->floorplane.c >= STEEPSLOPE) { - if (floorplane->ZatPoint (x, y) >= z - MaxStepHeight) + if (floorplane.ZatPoint (x, y) >= z - MaxStepHeight) { dopush = false; break; @@ -3233,8 +3168,8 @@ void AActor::Tick () } if (dopush) { - velx += floorplane->a; - vely += floorplane->b; + velx += floorplane.a; + vely += floorplane.b; } } } @@ -3331,6 +3266,18 @@ void AActor::Tick () { return; } + + // Check for poison damage, but only once per PoisonPeriod tics (or once per second if none). + if (PoisonDurationReceived && (level.time % (PoisonPeriodReceived ? PoisonPeriodReceived : TICRATE) == 0)) + { + P_DamageMobj(this, NULL, Poisoner, PoisonDamageReceived, NAME_Poison, 0); + + --PoisonDurationReceived; + + // Must clear damage when duration is done, otherwise it + // could be added to with ADDITIVEPOISONDAMAGE. + if (!PoisonDurationReceived) PoisonDamageReceived = 0; + } } // cycle through states, calling action functions at transitions @@ -3495,6 +3442,7 @@ bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash) return false; // we did the splash ourselves } + //========================================================================== // // P_SpawnMobj @@ -3517,6 +3465,17 @@ AActor *AActor::StaticSpawn (PClassActor *type, fixed_t ix, fixed_t iy, fixed_t actor = static_cast(const_cast(type)->CreateNew ()); + // Set default dialogue + actor->ConversationRoot = GetConversation(actor->GetClass()->TypeName); + if (actor->ConversationRoot != -1) + { + actor->Conversation = StrifeDialogues[actor->ConversationRoot]; + } + else + { + actor->Conversation = NULL; + } + actor->x = actor->PrevX = ix; actor->y = actor->PrevY = iy; actor->z = actor->PrevZ = iz; @@ -3663,7 +3622,10 @@ AActor *AActor::StaticSpawn (PClassActor *type, fixed_t ix, fixed_t iy, fixed_t { level.total_items++; } - screen->StateChanged(actor); + if (screen != NULL) + { + screen->StateChanged(actor); + } return actor; } @@ -3985,7 +3947,6 @@ APlayerPawn *P_SpawnPlayer (FMapThing *mthing, bool tempplayer) // [GRB] Reset skin p->userinfo.skin = R_FindSkin (skins[p->userinfo.skin].name, p->CurrentPlayerClass); - StatusBar->SetFace (&skins[p->userinfo.skin]); if (!(mobj->flags2 & MF2_DONTTRANSLATE)) @@ -4314,6 +4275,11 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) mthing->args[0] = mthing->type - 14000; mthing->type = 14065; } + else if (mthing->type >= 14101 && mthing->type <= 14164) + { + mthing->args[0] = mthing->type - 14100; + mthing->type = 14165; + } // find which type to spawn i = DoomEdMap.FindType (mthing->type); @@ -4430,6 +4396,19 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) mobj->AddToHash (); mobj->PrevAngle = mobj->angle = (DWORD)((mthing->angle * UCONST64(0x100000000)) / 360); + + // Check if this actor's mapthing has a conversation defined + if (mthing->Conversation > 0) + { + // Make sure that this does not partially overwrite the default dialogue settings. + int root = GetConversation(mthing->Conversation); + if (root != -1) + { + mobj->ConversationRoot = root; + mobj->Conversation = StrifeDialogues[mobj->ConversationRoot]; + } + } + mobj->BeginPlay (); if (!(mobj->ObjectFlags & OF_EuthanizeMe)) { @@ -4522,7 +4501,7 @@ void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage, AAc if (bloodcls!=NULL && bloodtype <= 1) { z += pr_spawnblood.Random2 () << 10; - th = Spawn (bloodcls, x, y, z, ALLOW_REPLACE); + th = Spawn (bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement th->velz = FRACUNIT*2; th->angle = dir; if (gameinfo.gametype & GAME_DoomChex) @@ -4585,7 +4564,7 @@ void P_BloodSplatter (fixed_t x, fixed_t y, fixed_t z, AActor *originator) { AActor *mo; - mo = Spawn(bloodcls, x, y, z, ALLOW_REPLACE); + mo = Spawn(bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement mo->target = originator; mo->velx = pr_splatter.Random2 () << 10; mo->vely = pr_splatter.Random2 () << 10; @@ -4626,7 +4605,7 @@ void P_BloodSplatter2 (fixed_t x, fixed_t y, fixed_t z, AActor *originator) x += ((pr_splat()-128)<<11); y += ((pr_splat()-128)<<11); - mo = Spawn (bloodcls, x, y, z, ALLOW_REPLACE); + mo = Spawn (bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement mo->target = originator; // colorize the blood! @@ -4665,7 +4644,7 @@ void P_RipperBlood (AActor *mo, AActor *bleeder) if (bloodcls!=NULL && bloodtype <= 1) { AActor *th; - th = Spawn (bloodcls, x, y, z, ALLOW_REPLACE); + th = Spawn (bloodcls, x, y, z, NO_REPLACE); // GetBloodType already performed the replacement if (gameinfo.gametype == GAME_Heretic) th->flags |= MF_NOGRAVITY; th->velx = mo->velx >> 1; @@ -4728,6 +4707,20 @@ bool P_HitWater (AActor * thing, sector_t * sec, fixed_t x, fixed_t y, fixed_t z // don't splash above the object if (checkabove && z > thing->z + (thing->height >> 1)) return false; +#if 0 // needs some rethinking before activation + + // This avoids spawning splashes on invisible self referencing sectors. + // For network consistency do this only in single player though because + // it is not guaranteed that all players have GL nodes loaded. + if (!multiplayer && thing->subsector->sector != thing->subsector->render_sector) + { + fixed_t zs = thing->subsector->sector->floorplane.ZatPoint(x, y); + fixed_t zr = thing->subsector->render_sector->floorplane.ZatPoint(x, y); + + if (zs > zr && thing->z >= zs) return false; + } +#endif + #ifdef _3DFLOORS for(unsigned int i=0;ie->XFloor.ffloors.Size();i++) { @@ -4846,7 +4839,7 @@ bool P_HitFloor (AActor *thing) { thing->flags6 &= ~MF6_ARMED; // Disarm P_DamageMobj (thing, NULL, NULL, thing->health, NAME_Crush, DMG_FORCED); // kill object - return true; + return false; } if (thing->flags2 & MF2_FLOATBOB || thing->flags3 & MF3_DONTSPLASH) @@ -4972,7 +4965,7 @@ bool P_CheckMissileSpawn (AActor* th) bool MBFGrenade = (!(th->flags & MF_MISSILE) || (th->BounceFlags & BOUNCE_MBF)); // killough 3/15/98: no dropoff (really = don't care for missiles) - if (!(P_TryMove (th, th->x, th->y, false, false, tm))) + if (!(P_TryMove (th, th->x, th->y, false, NULL, tm))) { // [RH] Don't explode ripping missiles that spawn inside something if (th->BlockingMobj == NULL || !(th->flags2 & MF2_RIP) || (th->BlockingMobj->flags5 & MF5_DONTRIP)) @@ -5456,10 +5449,10 @@ int AActor::DoSpecialDamage (AActor *target, int damage) { if (target->player) { - int poisondamage = GetClass()->PoisonDamage; - if (poisondamage > 0) + // Only do this for old style poison damage. + if (PoisonDamage > 0 && PoisonDuration == INT_MIN) { - P_PoisonPlayer (target->player, this, this->target, poisondamage); + P_PoisonPlayer (target->player, this, this->target, PoisonDamage); damage >>= 1; } } @@ -5605,9 +5598,26 @@ bool AActor::IsSentient() const const char *AActor::GetTag(const char *def) const { - if (Tag != NAME_None) return Tag.GetChars(); - else if (def) return def; - else return GetClass()->TypeName.GetChars(); + if (Tag != NAME_None) + { + const char *tag = Tag.GetChars(); + if (tag[0] == '$') + { + return GStrings(tag + 1); + } + else + { + return tag; + } + } + else if (def) + { + return def; + } + else + { + return GetClass()->TypeName.GetChars(); + } } diff --git a/src/p_pillar.cpp b/src/p_pillar.cpp index 1d2d5ba81..4faed647f 100644 --- a/src/p_pillar.cpp +++ b/src/p_pillar.cpp @@ -190,9 +190,17 @@ DPillar::DPillar (sector_t *sector, EPillar type, fixed_t speed, } if (sector->seqType >= 0) + { SN_StartSequence (sector, CHAN_FLOOR, sector->seqType, SEQ_PLATFORM, 0); + } + else if (sector->SeqName != NAME_None) + { + SN_StartSequence (sector, CHAN_FLOOR, sector->SeqName, 0); + } else + { SN_StartSequence (sector, CHAN_FLOOR, "Floor", 0); + } } bool EV_DoPillar (DPillar::EPillar type, int tag, fixed_t speed, fixed_t height, diff --git a/src/p_plats.cpp b/src/p_plats.cpp index 87d9e6737..9e57f4baf 100644 --- a/src/p_plats.cpp +++ b/src/p_plats.cpp @@ -57,9 +57,17 @@ void DPlat::Serialize (FArchive &arc) void DPlat::PlayPlatSound (const char *sound) { if (m_Sector->seqType >= 0) + { SN_StartSequence (m_Sector, CHAN_FLOOR, m_Sector->seqType, SEQ_PLATFORM, 0); + } + else if (m_Sector->SeqName != NAME_None) + { + SN_StartSequence (m_Sector, CHAN_FLOOR, m_Sector->SeqName, 0); + } else + { SN_StartSequence (m_Sector, CHAN_FLOOR, sound, 0); + } } // diff --git a/src/p_pspr.cpp b/src/p_pspr.cpp index b389c51e4..b5f6314ad 100644 --- a/src/p_pspr.cpp +++ b/src/p_pspr.cpp @@ -61,13 +61,12 @@ static FRandom pr_gunshot ("GunShot"); // //--------------------------------------------------------------------------- -void P_SetPsprite (player_t *player, int position, FState *state) +void P_SetPsprite (player_t *player, int position, FState *state, bool nofunction) { pspdef_t *psp; - if (position == ps_weapon) - { - // A_WeaponReady will re-set these as needed + if (position == ps_weapon && !nofunction) + { // A_WeaponReady will re-set these as needed player->cheats &= ~(CF_WEAPONREADY | CF_WEAPONREADYALT | CF_WEAPONBOBBING | CF_WEAPONSWITCHOK); } @@ -81,6 +80,19 @@ void P_SetPsprite (player_t *player, int position, FState *state) } psp->state = state; + if (state->sprite != SPR_FIXED) + { // okay to change sprite and/or frame + if (!state->GetSameFrame()) + { // okay to change frame + psp->frame = state->GetFrame(); + } + if (state->sprite != SPR_NOCHANGE) + { // okay to change sprite + psp->sprite = state->sprite; + } + } + + if (sv_fastweapons >= 2 && position == ps_weapon) psp->tics = state->ActionFunc == NULL? 0 : 1; else if (sv_fastweapons) @@ -97,7 +109,7 @@ void P_SetPsprite (player_t *player, int position, FState *state) psp->sy = state->GetMisc2()<mo != NULL) + if (!nofunction && player->mo != NULL) { if (state->CallAction(player->mo, player->ReadyWeapon)) { @@ -112,40 +124,6 @@ void P_SetPsprite (player_t *player, int position, FState *state) } while (!psp->tics); // An initial state of 0 could cycle through. } -//--------------------------------------------------------------------------- -// -// PROC P_SetPspriteNF -// -// Identical to P_SetPsprite, without calling the action function -//--------------------------------------------------------------------------- - -void P_SetPspriteNF (player_t *player, int position, FState *state) -{ - pspdef_t *psp; - - psp = &player->psprites[position]; - do - { - if (state == NULL) - { // Object removed itself. - psp->state = NULL; - break; - } - psp->state = state; - psp->tics = state->GetTics(); // could be 0 - - if (state->GetMisc1()) - { // Set coordinates. - psp->sx = state->GetMisc1()<GetMisc2()) - { - psp->sy = state->GetMisc2()<state->GetNextState(); - } while (!psp->tics); // An initial state of 0 could cycle through. -} - //--------------------------------------------------------------------------- // // PROC P_BringUpWeapon @@ -914,5 +892,15 @@ void P_MovePsprites (player_t *player) FArchive &operator<< (FArchive &arc, pspdef_t &def) { - return arc << def.state << def.tics << def.sx << def.sy; + arc << def.state << def.tics << def.sx << def.sy; + if (SaveVersion >= 2295) + { + arc << def.sprite << def.frame; + } + else + { + def.sprite = def.state->sprite; + def.frame = def.state->Frame; + } + return arc; } diff --git a/src/p_pspr.h b/src/p_pspr.h index bd3ae6cad..16424adf5 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -68,7 +68,8 @@ struct pspdef_t int tics; fixed_t sx; fixed_t sy; - + int sprite; + int frame; }; class FArchive; @@ -79,8 +80,7 @@ class player_t; class AActor; struct FState; -void P_SetPsprite (player_t *player, int position, FState *state); -void P_SetPspriteNF (player_t *player, int position, FState *state); +void P_SetPsprite (player_t *player, int position, FState *state, bool nofunction=false); void P_CalcSwing (player_t *player); void P_BringUpWeapon (player_t *player); void P_FireWeapon (player_t *player); diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 5e9a18f9c..52ebf88d2 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -46,12 +46,15 @@ #include "a_sharedglobal.h" #include "r_interpolate.h" #include "g_level.h" +#include "po_man.h" +#include "p_setup.h" static void CopyPlayer (player_t *dst, player_t *src, const char *name); static void ReadOnePlayer (FArchive &arc, bool skipload); static void ReadMultiplePlayers (FArchive &arc, int numPlayers, int numPlayersNow, bool skipload); static void SpawnExtraPlayers (); + // // P_ArchivePlayers // @@ -346,6 +349,15 @@ void P_SerializeWorld (FArchive &arc) << sec->interpolations[2] << sec->interpolations[3]; + if (SaveVersion < 2492) + { + sec->SeqName = NAME_None; + } + else + { + arc << sec->SeqName; + } + sec->e->Serialize(arc); if (arc.IsStoring ()) { @@ -496,8 +508,8 @@ void P_SerializePolyobjs (FArchive &arc) arc << seg << po_NumPolyobjs; for(i = 0, po = polyobjs; i < po_NumPolyobjs; i++, po++) { - arc << po->tag << po->angle << po->startSpot[0] << - po->startSpot[1] << po->interpolation; + arc << po->tag << po->angle << po->StartSpot.x << + po->StartSpot.y << po->interpolation; } } else @@ -523,11 +535,116 @@ void P_SerializePolyobjs (FArchive &arc) I_Error ("UnarchivePolyobjs: Invalid polyobj tag"); } arc << angle; - PO_RotatePolyobj (po->tag, angle); + po->RotatePolyobj (angle); arc << deltaX << deltaY << po->interpolation; - deltaX -= po->startSpot[0]; - deltaY -= po->startSpot[1]; - PO_MovePolyobj (po->tag, deltaX, deltaY, true); + deltaX -= po->StartSpot.x; + deltaY -= po->StartSpot.y; + po->MovePolyobj (deltaX, deltaY, true); + } + } +} + +//========================================================================== +// +// RecalculateDrawnSubsectors +// +// In case the subsector data is unusable this function tries to reconstruct +// if from the linedefs' ML_MAPPED info. +// +//========================================================================== + +void RecalculateDrawnSubsectors() +{ + for(int i=0;inumlines;j++) + { + if (sub->firstline[j].linedef != NULL && + (sub->firstline[j].linedef->flags & ML_MAPPED)) + { + sub->flags |= SSECF_DRAWN; + } + } + } +} + +//========================================================================== +// +// ArchiveSubsectors +// +//========================================================================== + +void P_SerializeSubsectors(FArchive &arc) +{ + int num_verts, num_subs, num_nodes; + BYTE by; + + if (arc.IsStoring()) + { + if (hasglnodes) + { + arc << numvertexes << numsubsectors << numnodes; // These are only for verification + for(int i=0;i MapThingsConverted; int sidecount; -struct sidei_t // [RH] Only keep BOOM sidedef init stuff around for init -{ - union - { - // Used when unpacking sidedefs and assigning - // properties based on linedefs. - struct - { - short tag, special; - short alpha; - DWORD map; - } a; +sidei_t *sidetemp; - // Used when grouping sidedefs into loops. - struct - { - DWORD first, next; - char lineside; - } b; - }; -} *sidetemp; TArray linemap; -bool UsingGLNodes; - // BLOCKMAP // Created from axis aligned bounding box // of the map, a rectangular array of @@ -170,7 +161,6 @@ fixed_t bmaporgx; // origin of block map fixed_t bmaporgy; FBlockNode** blocklinks; // for thing chains - // REJECT @@ -793,7 +783,7 @@ void P_LoadVertexes (MapData * map) // //=========================================================================== -void P_LoadZSegs (FileReaderZ &data) +void P_LoadZSegs (FileReaderBase &data) { for (int i = 0; i < numsegs; ++i) { @@ -808,7 +798,6 @@ void P_LoadZSegs (FileReaderZ &data) segs[i].v2 = &vertexes[v2]; segs[i].linedef = ldef = &lines[line]; segs[i].sidedef = ldef->sidedef[side]; - segs[i].PartnerSeg = NULL; segs[i].frontsector = ldef->sidedef[side]->sector; if (ldef->flags & ML_TWOSIDED && ldef->sidedef[side^1] != NULL) { @@ -830,7 +819,7 @@ void P_LoadZSegs (FileReaderZ &data) // //=========================================================================== -void P_LoadGLZSegs (FileReaderZ &data, DWORD id) +void P_LoadGLZSegs (FileReaderBase &data, int type) { for (int i = 0; i < numsubsectors; ++i) { @@ -843,7 +832,7 @@ void P_LoadGLZSegs (FileReaderZ &data, DWORD id) BYTE side; data >> v1 >> partner; - if (id == MAKE_ID('Z','G','L','2')) + if (type == 2) { data >> line; } @@ -854,7 +843,7 @@ void P_LoadGLZSegs (FileReaderZ &data, DWORD id) } data >> side; - seg = &segs[subsectors[i].firstline + j]; + seg = subsectors[i].firstline + j; seg->v1 = &vertexes[v1]; if (j == 0) { @@ -864,14 +853,7 @@ void P_LoadGLZSegs (FileReaderZ &data, DWORD id) { seg[-1].v2 = seg->v1; } - if (partner == 0xFFFFFFFF) - { - seg->PartnerSeg = NULL; - } - else - { - seg->PartnerSeg = &segs[partner]; - } + glsegextras[seg - segs].PartnerSeg = partner; if (line != 0xFFFFFFFF) { line_t *ldef; @@ -893,7 +875,7 @@ void P_LoadGLZSegs (FileReaderZ &data, DWORD id) { seg->linedef = NULL; seg->sidedef = NULL; - seg->frontsector = seg->backsector = segs[subsectors[i].firstline].frontsector; + seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector; } } } @@ -905,14 +887,12 @@ void P_LoadGLZSegs (FileReaderZ &data, DWORD id) // //=========================================================================== -static void P_LoadZNodes (FileReader &dalump, DWORD id) +void LoadZNodes(FileReaderBase &data, int glnodes) { - FileReaderZ data (dalump); - DWORD i; - // Read extra vertices added during node building DWORD orgVerts, newVerts; vertex_t *newvertarray; + unsigned int i; data >> orgVerts >> newVerts; if (orgVerts + newVerts == (DWORD)numvertexes) @@ -953,7 +933,7 @@ static void P_LoadZNodes (FileReader &dalump, DWORD id) DWORD numsegs; data >> numsegs; - subsectors[i].firstline = currSeg; + subsectors[i].firstline = (seg_t *)(size_t)currSeg; // Oh damn. I should have stored the seg count sooner. subsectors[i].numlines = numsegs; currSeg += numsegs; } @@ -976,14 +956,21 @@ static void P_LoadZNodes (FileReader &dalump, DWORD id) numsegs = numSegs; segs = new seg_t[numsegs]; memset (segs, 0, numsegs*sizeof(seg_t)); + glsegextras = NULL; - if (id == MAKE_ID('Z','N','O','D')) + for (i = 0; i < numSubs; ++i) + { + subsectors[i].firstline = &segs[(size_t)subsectors[i].firstline]; + } + + if (glnodes == 0) { P_LoadZSegs (data); } else { - P_LoadGLZSegs (data, id); + glsegextras = new glsegextra_t[numsegs]; + P_LoadGLZSegs (data, glnodes); } // Read nodes @@ -1029,6 +1016,76 @@ static void P_LoadZNodes (FileReader &dalump, DWORD id) } +void P_LoadZNodes (FileReader &dalump, DWORD id) +{ + int type; + bool compressed; + + switch (id) + { + case MAKE_ID('Z','N','O','D'): + type = 0; + compressed = true; + break; + + case MAKE_ID('Z','G','L','N'): + type = 1; + compressed = true; + break; + + case MAKE_ID('Z','G','L','2'): + type = 2; + compressed = true; + break; + + case MAKE_ID('X','N','O','D'): + type = 0; + compressed = false; + break; + + case MAKE_ID('X','G','L','N'): + type = 1; + compressed = false; + break; + + case MAKE_ID('X','G','L','2'): + type = 2; + compressed = false; + break; + + default: + return; + } + + if (compressed) + { + FileReaderZ data (dalump); + LoadZNodes(data, type); + } + else + { + LoadZNodes(dalump, type); + } +} + + + +//=========================================================================== +// +// P_CheckV4Nodes +// http://www.sbsoftware.com/files/DeePBSPV4specs.txt +// +//=========================================================================== + +static bool P_CheckV4Nodes(MapData *map) +{ + char header[8]; + + map->Read(ML_NODES, header, 8); + return !memcmp(header, "xNd4\0\0\0\0", 8); +} + + //=========================================================================== // // P_LoadSegs @@ -1037,6 +1094,15 @@ static void P_LoadZNodes (FileReader &dalump, DWORD id) // //=========================================================================== +struct badseg +{ + badseg(int t, int s, int d) : badtype(t), badsegnum(s), baddata(d) {} + int badtype; + int badsegnum; + int baddata; +}; + +template void P_LoadSegs (MapData * map) { int i; @@ -1053,7 +1119,7 @@ void P_LoadSegs (MapData * map) memset (vertchanged,0,numvertexes); // phares 10/4/98 - numsegs = lumplen / sizeof(mapseg_t); + numsegs = lumplen / sizeof(segtype); if (numsegs == 0) { @@ -1071,6 +1137,11 @@ void P_LoadSegs (MapData * map) data = new BYTE[lumplen]; map->Read(ML_SEGS, data); + for (i = 0; i < numsubsectors; ++i) + { + subsectors[i].firstline = &segs[(size_t)subsectors[i].firstline]; + } + // phares: 10/4/98: Vertchanged is an array that represents the vertices. // Mark those used by linedefs. A marked vertex is one that is not a // candidate for movement further down. @@ -1085,23 +1156,22 @@ void P_LoadSegs (MapData * map) { for (i = 0; i < numsegs; i++) { - seg_t *li = segs+i; - mapseg_t *ml = (mapseg_t *) data + i; + seg_t *li = segs + i; + segtype *ml = ((segtype *) data) + i; int side, linedef; line_t *ldef; - vnum1 = LittleShort(ml->v1); - vnum2 = LittleShort(ml->v2); + vnum1 = ml->V1(); + vnum2 = ml->V2(); if (vnum1 >= numvertexes || vnum2 >= numvertexes) { - throw i * 4; + throw badseg(0, i, MAX(vnum1, vnum2)); } li->v1 = &vertexes[vnum1]; li->v2 = &vertexes[vnum2]; - li->PartnerSeg = NULL; segangle = (WORD)LittleShort(ml->angle); @@ -1164,14 +1234,14 @@ void P_LoadSegs (MapData * map) linedef = LittleShort(ml->linedef); if ((unsigned)linedef >= (unsigned)numlines) { - throw i * 4 + 1; + throw badseg(1, i, linedef); } ldef = &lines[linedef]; li->linedef = ldef; side = LittleShort(ml->side); if ((unsigned)(ldef->sidedef[side] - sides) >= (unsigned)numsides) { - throw i * 4 + 2; + throw badseg(2, i, int(ldef->sidedef[side] - sides)); } li->sidedef = ldef->sidedef[side]; li->frontsector = ldef->sidedef[side]->sector; @@ -1188,20 +1258,20 @@ void P_LoadSegs (MapData * map) } } } - catch (int foo) + catch (badseg bad) { - switch (foo & 3) + switch (bad.badtype) { case 0: - Printf ("Seg %d references a nonexistant vertex.\n", foo >> 2); + Printf ("Seg %d references a nonexistant vertex %d (max %d).\n", bad.badsegnum, bad.baddata, numvertexes); break; case 1: - Printf ("Seg %d references a nonexistant linedef.\n", foo >> 2); + Printf ("Seg %d references a nonexistant linedef %d (max %d).\n", bad.badsegnum, bad.baddata, numlines); break; case 2: - Printf ("The linedef for seg %d references a nonexistant sidedef.\n", foo >> 2); + Printf ("The linedef for seg %d references a nonexistant sidedef %d (max %d).\n", bad.badsegnum, bad.baddata, numsides); break; } Printf ("The BSP will be rebuilt.\n"); @@ -1222,12 +1292,13 @@ void P_LoadSegs (MapData * map) // //=========================================================================== +template void P_LoadSubsectors (MapData * map) { int i; - DWORD maxseg = map->Size(ML_SEGS) / sizeof(mapseg_t); + DWORD maxseg = map->Size(ML_SEGS) / sizeof(segtype); - numsubsectors = map->MapLumps[ML_SSECTORS].Size / sizeof(mapsubsector_t); + numsubsectors = map->MapLumps[ML_SSECTORS].Size / sizeof(subsectortype); if (numsubsectors == 0 || maxseg == 0 ) { @@ -1244,11 +1315,11 @@ void P_LoadSubsectors (MapData * map) for (i = 0; i < numsubsectors; i++) { - WORD numsegs, firstseg; + subsectortype subd; - (*map->file) >> numsegs >> firstseg; + (*map->file) >> subd.numsegs >> subd.firstseg; - if (numsegs == 0) + if (subd.numsegs == 0) { Printf ("Subsector %i is empty.\n", i); delete[] subsectors; @@ -1257,24 +1328,24 @@ void P_LoadSubsectors (MapData * map) return; } - subsectors[i].numlines = numsegs; - subsectors[i].firstline = firstseg; + subsectors[i].numlines = subd.numsegs; + subsectors[i].firstline = (seg_t *)(size_t)subd.firstseg; - if (subsectors[i].firstline >= maxseg) + if ((size_t)subsectors[i].firstline >= maxseg) { Printf ("Subsector %d contains invalid segs %u-%u\n" - "The BSP will be rebuilt.\n", i, subsectors[i].firstline, - subsectors[i].firstline + subsectors[i].numlines - 1); + "The BSP will be rebuilt.\n", i, (unsigned)((size_t)subsectors[i].firstline), + (unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1); ForceNodeBuild = true; delete[] nodes; delete[] subsectors; break; } - else if (subsectors[i].firstline + subsectors[i].numlines > maxseg) + else if ((size_t)subsectors[i].firstline + subsectors[i].numlines > maxseg) { Printf ("Subsector %d contains invalid segs %u-%u\n" "The BSP will be rebuilt.\n", i, maxseg, - subsectors[i].firstline + subsectors[i].numlines - 1); + (unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1); ForceNodeBuild = true; delete[] nodes; delete[] subsectors; @@ -1345,6 +1416,7 @@ void P_LoadSectors (MapData * map) ss->thinglist = NULL; ss->touching_thinglist = NULL; // phares 3/14/98 ss->seqType = defSeqType; + ss->SeqName = NAME_None; ss->nextsec = -1; //jff 2/26/98 add fields to support locking out ss->prevsec = -1; // stair retriggering until build completes @@ -1390,6 +1462,7 @@ void P_LoadSectors (MapData * map) // //=========================================================================== +template void P_LoadNodes (MapData * map) { FMemLump data; @@ -1397,13 +1470,13 @@ void P_LoadNodes (MapData * map) int j; int k; char *mnp; - mapnode_t *mn; + nodetype *mn; node_t* no; WORD* used; int lumplen = map->Size(ML_NODES); - int maxss = map->Size(ML_SSECTORS) / sizeof(mapsubsector_t); + int maxss = map->Size(ML_SSECTORS) / sizeof(subsectortype); - numnodes = lumplen / sizeof(mapnode_t); + numnodes = (lumplen - nodetype::NF_LUMPOFFSET) / sizeof(nodetype); if ((numnodes == 0 && maxss != 1) || maxss == 0) { @@ -1416,8 +1489,8 @@ void P_LoadNodes (MapData * map) memset (used, 0, sizeof(WORD)*numnodes); mnp = new char[lumplen]; - mn = (mapnode_t*)mnp; - map->Read(ML_NODES, mn); + mn = (nodetype*)(mnp + nodetype::NF_LUMPOFFSET); + map->Read(ML_NODES, mnp); no = nodes; for (i = 0; i < numnodes; i++, no++, mn++) @@ -1428,10 +1501,10 @@ void P_LoadNodes (MapData * map) no->dy = LittleShort(mn->dy)<children[j]); - if (child & NF_SUBSECTOR) + int child = mn->Child(j); + if (child & nodetype::NF_SUBSECTOR) { - child &= ~NF_SUBSECTOR; + child &= ~nodetype::NF_SUBSECTOR; if (child >= maxss) { Printf ("BSP node %d references invalid subsector %d.\n" @@ -1896,8 +1969,14 @@ void P_LoadLineDefs (MapData * map) } else { - if (LittleShort(mld->sidenum[0]) != NO_INDEX) - sidecount++; + // patch missing first sides instead of crashing out. + // Visual glitches are better than not being able to play. + if (LittleShort(mld->sidenum[0]) == NO_INDEX) + { + Printf("Line %d has no first side.\n", i); + mld->sidenum[0] = 0; + } + sidecount++; if (LittleShort(mld->sidenum[1]) != NO_INDEX) sidecount++; linemap[i] = i+skipped; @@ -1966,8 +2045,14 @@ void P_LoadLineDefs2 (MapData * map) } else { - if (LittleShort(mld->sidenum[0]) != NO_INDEX) - sidecount++; + // patch missing first sides instead of crashing out. + // Visual glitches are better than not being able to play. + if (LittleShort(mld->sidenum[0]) == NO_INDEX) + { + Printf("Line %d has no first side.\n", i); + mld->sidenum[0] = 0; + } + sidecount++; if (LittleShort(mld->sidenum[1]) != NO_INDEX) sidecount++; linemap[i] = i+skipped; @@ -2052,14 +2137,15 @@ static void P_AllocateSideDefs (int count) // [RH] Group sidedefs into loops so that we can easily determine // what walls any particular wall neighbors. -static void P_LoopSidedefs () +static void P_LoopSidedefs (bool firstloop) { int i; - if (sidetemp == NULL) + if (sidetemp != NULL) { - sidetemp = new sidei_t[MAX(numvertexes, numsides)]; + delete[] sidetemp; } + sidetemp = new sidei_t[MAX(numvertexes, numsides)]; for (i = 0; i < numvertexes; ++i) { @@ -2117,8 +2203,9 @@ static void P_LoopSidedefs () right = sidetemp[right].b.first; if (right == NO_SIDE) - { // There is no right side! - Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); + { + // There is no right side! + if (firstloop) Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); continue; } @@ -2167,9 +2254,8 @@ static void P_LoopSidedefs () sides[right].LeftSide = i; } - // Throw away sidedef init info now that we're done with it - delete[] sidetemp; - sidetemp = NULL; + // We keep the sidedef init info around until after polyobjects are initialized, + // so don't delete just yet. } int P_DetermineTranslucency (int lumpnum) @@ -2547,7 +2633,7 @@ static void P_CreateBlockMap () { if (bx > bx2) { - swap (block, endblock); + swapvalues (block, endblock); } do { @@ -2559,7 +2645,7 @@ static void P_CreateBlockMap () { if (by > by2) { - swap (block, endblock); + swapvalues (block, endblock); } do { @@ -2650,6 +2736,79 @@ static void P_CreateBlockMap () } } + + +// +// P_VerifyBlockMap +// +// haleyjd 03/04/10: do verification on validity of blockmap. +// +static bool P_VerifyBlockMap(int count) +{ + int x, y; + int *maxoffs = blockmaplump + count; + + int bmapwidth = blockmaplump[2]; + int bmapheight = blockmaplump[3]; + + for(y = 0; y < bmapheight; y++) + { + for(x = 0; x < bmapwidth; x++) + { + int offset; + int *list, *tmplist; + int *blockoffset; + + offset = y * bmapwidth + x; + blockoffset = blockmaplump + offset + 4; + + + // check that block offset is in bounds + if(blockoffset >= maxoffs) + { + Printf(PRINT_HIGH, "P_VerifyBlockMap: block offset overflow\n"); + return false; + } + + offset = *blockoffset; + + // check that list offset is in bounds + if(offset < 4 || offset >= count) + { + Printf(PRINT_HIGH, "P_VerifyBlockMap: list offset overflow\n"); + return false; + } + + list = blockmaplump + offset; + + // scan forward for a -1 terminator before maxoffs + for(tmplist = list; ; tmplist++) + { + // we have overflowed the lump? + if(tmplist >= maxoffs) + { + Printf(PRINT_HIGH, "P_VerifyBlockMap: open blocklist\n"); + return false; + } + if(*tmplist == -1) // found -1 + break; + } + + // scan the list for out-of-range linedef indicies in list + for(tmplist = list; *tmplist != -1; tmplist++) + { + if(*tmplist < 0 || *tmplist >= numlines) + { + Printf(PRINT_HIGH, "P_VerifyBlockMap: index >= numlines\n"); + return false; + } + } + } + } + + return true; +} + // // P_LoadBlockMap // @@ -2697,6 +2856,13 @@ void P_LoadBlockMap (MapData * map) blockmaplump[i] = t == -1 ? (DWORD)0xffffffff : (DWORD) t & 0xffff; } delete[] data; + + if (!P_VerifyBlockMap(count)) + { + DPrintf ("Generating BLOCKMAP\n"); + P_CreateBlockMap(); + } + } bmaporgx = blockmaplump[0]<sector; - subsectors[i].validcount = validcount; - - double accumx = 0.0, accumy = 0.0; - - for (jj = 0; jj < subsectors[i].numlines; ++jj) + subsectors[i].sector = subsectors[i].firstline->sidedef->sector; + } + if (glsegextras != NULL) + { + for (i = 0; i < numsubsectors; i++) { - seg_t *seg = &segs[subsectors[i].firstline + jj]; - seg->Subsector = &subsectors[i]; - accumx += seg->v1->x + seg->v2->x; - accumy += seg->v1->y + seg->v2->y; + for (jj = 0; jj < subsectors[i].numlines; ++jj) + { + glsegextras[subsectors[i].firstline - segs + jj].Subsector = &subsectors[i]; + } } - subsectors[i].CenterX = fixed_t(accumx * 0.5 / subsectors[i].numlines); - subsectors[i].CenterY = fixed_t(accumy * 0.5 / subsectors[i].numlines); } times[0].Unclock(); @@ -2926,26 +3089,6 @@ static void P_GroupLines (bool buildmap) } } } -#if 0 - int block; - - // adjust bounding box to map blocks - block = (bbox.Top()-bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT; - block = block >= bmapheight ? bmapheight-1 : block; - //sector->blockbox.Top()=block; - - block = (bbox.Bottom()-bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT; - block = block < 0 ? 0 : block; - //sector->blockbox.Bottom()=block; - - block = (bbox.Right()-bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT; - block = block >= bmapwidth ? bmapwidth-1 : block; - //sector->blockbox.Right()=block; - - block = (bbox.Left()-bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT; - block = block < 0 ? 0 : block; - //sector->blockbox.Left()=block; -#endif } delete[] linesDoneInEachSector; times[3].Unclock(); @@ -3198,6 +3341,7 @@ extern polyblock_t **PolyBlockMap; void P_FreeLevelData () { + FPolyObj::ClearAllSubsectorLinks(); // can't be done as part of the polyobj deletion process. SN_StopAllSequences (); DThinker::DestroyAllThinkers (); level.total_monsters = level.total_items = level.total_secrets = @@ -3215,6 +3359,11 @@ void P_FreeLevelData () delete[] segs; segs = NULL; } + if (glsegextras != NULL) + { + delete[] glsegextras; + glsegextras = NULL; + } if (sectors != NULL) { delete[] sectors[0].e; @@ -3222,16 +3371,33 @@ void P_FreeLevelData () sectors = NULL; numsectors = 0; // needed for the pointer cleanup code } + if (gamenodes != NULL && gamenodes != nodes) + { + delete[] gamenodes; + } + if (gamesubsectors != NULL && gamesubsectors != subsectors) + { + delete[] gamesubsectors; + } if (subsectors != NULL) { + for (int i = 0; i < numsubsectors; ++i) + { + if (subsectors[i].BSP != NULL) + { + delete subsectors[i].BSP; + } + } delete[] subsectors; - subsectors = NULL; } if (nodes != NULL) { delete[] nodes; - nodes = NULL; } + subsectors = gamesubsectors = NULL; + numsubsectors = numgamesubsectors = 0; + nodes = gamenodes = NULL; + numnodes = numgamenodes = 0; if (lines != NULL) { delete[] lines; @@ -3349,6 +3515,9 @@ void P_SetupLevel (char *lumpname, int position) int i; bool buildmap; + // This is motivated as follows: + bool RequireGLNodes = am_textured; + for (i = 0; i < (int)countof(times); ++i) { times[i].Reset(); @@ -3406,6 +3575,7 @@ void P_SetupLevel (char *lumpname, int position) // find map num level.lumpnum = map->lumpnum; + hasglnodes = false; // [RH] Support loading Build maps (because I felt like it. :-) buildmap = false; @@ -3516,15 +3686,7 @@ void P_SetupLevel (char *lumpname, int position) else P_LoadThings2 (map); // [RH] Load Hexen-style things - if (ib_compatflags & BCOMPATF_SPECHITOVERFLOW) - { - // restoring the original behavior doesn't work so we have to patch the levels in other ways. - // Fortunately the only known level depending on this bug is Strain's MAP07 and that's easy to fix. - if (numlines == 1022) - { - lines[1021].flags &= ~ML_BLOCKING; - } - } + SetCompatibilityParams(); } else { @@ -3532,7 +3694,7 @@ void P_SetupLevel (char *lumpname, int position) } times[6].Clock(); - P_LoopSidedefs (); + P_LoopSidedefs (true); times[6].Unclock(); linemap.Clear(); @@ -3542,29 +3704,32 @@ void P_SetupLevel (char *lumpname, int position) { ForceNodeBuild = true; } + bool reloop = false; - UsingGLNodes = false; if (!ForceNodeBuild) { // Check for compressed nodes first, then uncompressed nodes FWadLump test; - DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0; + DWORD id = MAKE_ID('X','x','X','x'), idcheck = 0, idcheck2 = 0, idcheck3 = 0, idcheck4 = 0; - if (map->MapLumps[ML_ZNODES].Size != 0 && !UsingGLNodes) + if (map->MapLumps[ML_ZNODES].Size != 0) { + // Test normal nodes first map->Seek(ML_ZNODES); idcheck = MAKE_ID('Z','N','O','D'); + idcheck2 = MAKE_ID('X','N','O','D'); } else if (map->MapLumps[ML_GLZNODES].Size != 0) { - // If normal nodes are not present but GL nodes are, use them. map->Seek(ML_GLZNODES); idcheck = MAKE_ID('Z','G','L','N'); idcheck2 = MAKE_ID('Z','G','L','2'); + idcheck3 = MAKE_ID('X','G','L','N'); + idcheck4 = MAKE_ID('X','G','L','2'); } map->file->Read (&id, 4); - if (id == idcheck || id == idcheck2) + if (id == idcheck || id == idcheck2 || id == idcheck3 || id == idcheck4) { try { @@ -3598,25 +3763,57 @@ void P_SetupLevel (char *lumpname, int position) // This just means that the map has no nodes and the engine is supposed to build them. if (map->Size(ML_SEGS) != 0 || map->Size(ML_SSECTORS) != 0 || map->Size(ML_NODES) != 0) { - times[7].Clock(); - P_LoadSubsectors (map); - times[7].Unclock(); + if (!P_CheckV4Nodes(map)) + { + times[7].Clock(); + P_LoadSubsectors (map); + times[7].Unclock(); - times[8].Clock(); - if (!ForceNodeBuild) P_LoadNodes (map); - times[8].Unclock(); + times[8].Clock(); + if (!ForceNodeBuild) P_LoadNodes (map); + times[8].Unclock(); - times[9].Clock(); - if (!ForceNodeBuild) P_LoadSegs (map); - times[9].Unclock(); + times[9].Clock(); + if (!ForceNodeBuild) P_LoadSegs (map); + times[9].Unclock(); + } + else + { + times[7].Clock(); + P_LoadSubsectors (map); + times[7].Unclock(); + + times[8].Clock(); + if (!ForceNodeBuild) P_LoadNodes (map); + times[8].Unclock(); + + times[9].Clock(); + if (!ForceNodeBuild) P_LoadSegs (map); + times[9].Unclock(); + } } else ForceNodeBuild = true; } else ForceNodeBuild = true; + + // If loading the regular nodes failed try GL nodes before considering a rebuild + if (ForceNodeBuild) + { + if (P_LoadGLNodes(map)) + { + ForceNodeBuild=false; + reloop = true; + } + } } + else reloop = true; + + unsigned int startTime=0, endTime=0; + + bool BuildGLNodes; if (ForceNodeBuild) { - unsigned int startTime, endTime; + BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes; startTime = I_FPSTime (); TArray polyspots, anchors; @@ -3628,15 +3825,68 @@ void P_SetupLevel (char *lumpname, int position) lines, numlines }; leveldata.FindMapBounds (); - UsingGLNodes |= genglnodes; - FNodeBuilder builder (leveldata, polyspots, anchors, UsingGLNodes, CPU.bSSE2); + // We need GL nodes if am_textured is on. + // In case a sync critical game mode is started, also build GL nodes to avoid problems + // if the different machines' am_textured setting differs. + FNodeBuilder builder (leveldata, polyspots, anchors, BuildGLNodes); delete[] vertexes; builder.Extract (nodes, numnodes, - segs, numsegs, + segs, glsegextras, numsegs, subsectors, numsubsectors, vertexes, numvertexes); endTime = I_FPSTime (); DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs); + reloop = true; + } + else + { + BuildGLNodes = false; + // Older ZDBSPs had problems with compressed sidedefs and assigned wrong sides to the segs if both sides were the same sidedef. + for(i=0;ibacksector == seg->frontsector && seg->linedef) + { + fixed_t d1=P_AproxDistance(seg->v1->x-seg->linedef->v1->x,seg->v1->y-seg->linedef->v1->y); + fixed_t d2=P_AproxDistance(seg->v2->x-seg->linedef->v1->x,seg->v2->y-seg->linedef->v1->y); + + if (d2sidedef = seg->linedef->sidedef[1]; + } + else // front side + { + seg->sidedef = seg->linedef->sidedef[0]; + } + } + } + } + + // Copy pointers to the old nodes so that R_PointInSubsector can use them + if (nodes && subsectors) + { + gamenodes = nodes; + numgamenodes = numnodes; + gamesubsectors = subsectors; + numgamesubsectors = numsubsectors; + } + else + { + gamenodes=NULL; + } + + if (RequireGLNodes) + { + // Build GL nodes if we want a textured automap or GL nodes are forced to be built. + // If the original nodes being loaded are not GL nodes they will be kept around for + // use in P_PointInSubsector to avoid problems with maps that depend on the specific + // nodes they were built with (P:AR E1M3 is a good example for a map where this is the case.) + reloop |= P_CheckNodes(map, BuildGLNodes, endTime - startTime); + hasglnodes = true; + } + else + { + hasglnodes = P_CheckForGLNodes(); } times[10].Clock(); @@ -3655,6 +3905,11 @@ void P_SetupLevel (char *lumpname, int position) P_FloodZones (); times[13].Unclock(); + if (hasglnodes) + { + P_SetRenderSector(); + } + bodyqueslot = 0; // phares 8/10/98: Clear body queue so the corpses from previous games are // not assumed to be from this one. @@ -3702,9 +3957,14 @@ void P_SetupLevel (char *lumpname, int position) P_SpawnSpecials (); times[16].Clock(); + if (reloop) P_LoopSidedefs (false); PO_Init (); // Initialize the polyobjs times[16].Unclock(); + assert(sidetemp != NULL); + delete[] sidetemp; + sidetemp = NULL; + // if deathmatch, randomly spawn the active players if (deathmatch) { @@ -3774,6 +4034,12 @@ void P_SetupLevel (char *lumpname, int position) } } MapThingsConverted.Clear(); + + if (glsegextras != NULL) + { + delete[] glsegextras; + glsegextras = NULL; + } } diff --git a/src/p_setup.h b/src/p_setup.h index c751c989c..aa860af0e 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -111,4 +111,35 @@ int P_TranslateSectorSpecial (int); int GetUDMFInt(int type, int index, const char *key); fixed_t GetUDMFFixed(int type, int index, const char *key); +bool P_LoadGLNodes(MapData * map); +bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime); +bool P_CheckForGLNodes(); +void P_SetRenderSector(); + + +struct sidei_t // [RH] Only keep BOOM sidedef init stuff around for init +{ + union + { + // Used when unpacking sidedefs and assigning + // properties based on linedefs. + struct + { + short tag, special; + short alpha; + DWORD map; + } a; + + // Used when grouping sidedefs into loops. + struct + { + DWORD first, next; + char lineside; + } b; + }; +}; +extern sidei_t *sidetemp; +extern bool hasglnodes; +extern struct glsegextra_t *glsegextras; + #endif diff --git a/src/p_sight.cpp b/src/p_sight.cpp index 55ab3f13a..4324ebc0a 100644 --- a/src/p_sight.cpp +++ b/src/p_sight.cpp @@ -18,6 +18,7 @@ #include "m_bbox.h" #include "p_lnspec.h" #include "g_level.h" +#include "po_man.h" // State. #include "r_state.h" @@ -305,7 +306,7 @@ bool SightCheck::P_SightBlockLinesIterator (int x, int y) int *list; polyblock_t *polyLink; - int i; + unsigned int i; extern polyblock_t **PolyBlockMap; offset = y*bmapwidth+x; @@ -318,9 +319,9 @@ bool SightCheck::P_SightBlockLinesIterator (int x, int y) if (polyLink->polyobj->validcount != validcount) { polyLink->polyobj->validcount = validcount; - for (i = 0; i < polyLink->polyobj->numlines; i++) + for (i = 0; i < polyLink->polyobj->Linedefs.Size(); i++) { - if (!P_SightCheckLine (polyLink->polyobj->lines[i])) + if (!P_SightCheckLine (polyLink->polyobj->Linedefs[i])) return false; } } diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 737ccc226..e37df68b1 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -294,7 +294,7 @@ bool P_TestActivateLine (line_t *line, AActor *mo, int side, int activationType) { lineActivation |= SPAC_Cross|SPAC_MCross; } - if (activationType == SPAC_Use) + if (activationType ==SPAC_Use || activationType == SPAC_UseBack) { if (!P_CheckSwitchRange(mo, line, side)) { @@ -452,6 +452,11 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector) P_DamageMobj (player->mo, NULL, NULL, 5, NAME_Slime); break; + case hDamage_Sludge: + if (ironfeet == NULL && !(level.time&0x1f)) + P_DamageMobj (player->mo, NULL, NULL, 4, NAME_Slime); + break; + case dDamage_SuperHellslime: // SUPER HELLSLIME DAMAGE case dLight_Strobe_Hurt: @@ -1099,6 +1104,11 @@ void P_SpawnSpecials (void) 0, -1, int(sector-sectors), 0); break; + case Sector_Hidden: + sector->MoreFlags |= SECF_HIDDEN; + sector->special &= 0xff00; + break; + default: if ((sector->special & 0xff) >= Scroll_North_Slow && (sector->special & 0xff) <= Scroll_SouthWest_Fast) @@ -1501,6 +1511,20 @@ static void P_SpawnScrollers(void) { int i; line_t *l = lines; + TArray copyscrollers; + + for (i = 0; i < numlines; i++) + { + if (lines[i].special == Sector_CopyScroller) + { + // don't allow copying the scroller if the sector has the same tag as it would just duplicate it. + if (lines[i].args[0] != lines[i].frontsector->tag) + { + copyscrollers.Push(i); + } + lines[i].special = 0; + } + } for (i = 0; i < numlines; i++, l++) { @@ -1571,20 +1595,53 @@ static void P_SpawnScrollers(void) case Scroll_Ceiling: for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;) + { new DScroller (DScroller::sc_ceiling, -dx, dy, control, s, accel); + } + for(unsigned j = 0;j < copyscrollers.Size(); j++) + { + line_t *line = &lines[copyscrollers[j]]; + + if (line->args[0] == l->args[0] && (line->args[1] & 1)) + { + new DScroller (DScroller::sc_ceiling, -dx, dy, control, int(line->frontsector-sectors), accel); + } + } break; case Scroll_Floor: if (l->args[2] != 1) { // scroll the floor texture for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;) + { new DScroller (DScroller::sc_floor, -dx, dy, control, s, accel); + } + for(unsigned j = 0;j < copyscrollers.Size(); j++) + { + line_t *line = &lines[copyscrollers[j]]; + + if (line->args[0] == l->args[0] && (line->args[1] & 2)) + { + new DScroller (DScroller::sc_floor, -dx, dy, control, int(line->frontsector-sectors), accel); + } + } } if (l->args[2] > 0) { // carry objects on the floor for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;) + { new DScroller (DScroller::sc_carry, dx, dy, control, s, accel); + } + for(unsigned j = 0;j < copyscrollers.Size(); j++) + { + line_t *line = &lines[copyscrollers[j]]; + + if (line->args[0] == l->args[0] && (line->args[1] & 4)) + { + new DScroller (DScroller::sc_carry, dx, dy, control, int(line->frontsector-sectors), accel); + } + } } break; diff --git a/src/p_spec.h b/src/p_spec.h index ca0d9492c..ca3694c09 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -640,6 +640,7 @@ protected: }; int m_Speed; int m_Delay; + bool m_SetBlocking1, m_SetBlocking2; friend bool EV_SlidingDoor (line_t *line, AActor *thing, int tag, int speed, int delay); private: diff --git a/src/p_states.cpp b/src/p_states.cpp index ecbb3d05f..617867cf7 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -848,15 +848,18 @@ bool FStateDefinitions::SetLoop() int FStateDefinitions::AddStates(FState *state, const char *framechars) { bool error = false; + int frame = 0; int count = 0; while (*framechars) { - int frame; - - if (*framechars == '^') - frame = '\\'-'A'; + bool noframe = false; + + if (*framechars == '#') + noframe = true; + else if (*framechars == '^') + frame = '\\' - 'A'; else - frame = ((*framechars)&223)-'A'; + frame = (*framechars & 223) - 'A'; framechars++; if (frame < 0 || frame > 28) @@ -865,7 +868,8 @@ int FStateDefinitions::AddStates(FState *state, const char *framechars) error = true; } - state->Frame = (state->Frame&(SF_FULLBRIGHT)) | frame; + state->Frame = frame; + state->SameFrame = noframe; StateArray.Push(*state); ++count; } diff --git a/src/p_switch.cpp b/src/p_switch.cpp index b7eeb784d..423633585 100644 --- a/src/p_switch.cpp +++ b/src/p_switch.cpp @@ -354,7 +354,7 @@ FSwitchDef *ParseSwitchDef (FScanner &sc, bool ignoreBad) max = sc.Number & 65535; if (min > max) { - swap (min, max); + swapvalues (min, max); } thisframe.Time = ((max - min + 1) << 16) | min; } diff --git a/src/p_trace.cpp b/src/p_trace.cpp index 1f38cc694..bd00da36f 100644 --- a/src/p_trace.cpp +++ b/src/p_trace.cpp @@ -74,7 +74,7 @@ bool Trace (fixed_t x, fixed_t y, fixed_t z, sector_t *sector, int ptflags; FTraceInfo inf; - ptflags = actorMask ? PT_ADDLINES|PT_ADDTHINGS : PT_ADDLINES; + ptflags = actorMask ? PT_ADDLINES|PT_ADDTHINGS|PT_COMPATIBLE : PT_ADDLINES; inf.StartX = x; inf.StartY = y; diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index 5dfa06dd7..5f2fca941 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -35,7 +35,6 @@ #include "r_data.h" #include "p_setup.h" -#include "sc_man.h" #include "p_lnspec.h" #include "templates.h" #include "i_system.h" @@ -43,6 +42,7 @@ #include "r_sky.h" #include "g_level.h" #include "v_palette.h" +#include "p_udmf.h" //=========================================================================== // @@ -124,6 +124,152 @@ extern TArray linemap; #define CHECK_N(f) if (!(namespace_bits&(f))) break; +//=========================================================================== +// +// Common parsing routines +// +//=========================================================================== + +//=========================================================================== +// +// Skip a key or block +// +//=========================================================================== + +void UDMFParserBase::Skip() +{ + if (developer) sc.ScriptMessage("Ignoring unknown key \"%s\".", sc.String); + if(sc.CheckToken('{')) + { + int level = 1; + while(sc.GetToken()) + { + if (sc.TokenType == '}') + { + level--; + if(level == 0) + { + sc.UnGet(); + break; + } + } + else if (sc.TokenType == '{') + { + level++; + } + } + } + else + { + sc.MustGetToken('='); + do + { + sc.MustGetAnyToken(); + } + while(sc.TokenType != ';'); + } +} + +//=========================================================================== +// +// Parses a 'key = value' line of the map +// +//=========================================================================== + +FName UDMFParserBase::ParseKey(bool checkblock, bool *isblock) +{ + sc.MustGetString(); + FName key = sc.String; + if (checkblock) + { + if (sc.CheckToken('{')) + { + if (isblock) *isblock = true; + return key; + } + else if (isblock) *isblock = false; + } + sc.MustGetToken('='); + + sc.Number = 0; + sc.Float = 0; + sc.MustGetAnyToken(); + + if (sc.TokenType == '+' || sc.TokenType == '-') + { + bool neg = (sc.TokenType == '-'); + sc.MustGetAnyToken(); + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Numeric constant expected"); + } + if (neg) + { + sc.Number = -sc.Number; + sc.Float = -sc.Float; + } + } + if (sc.TokenType == TK_StringConst) + { + parsedString = sc.String; + } + int savedtoken = sc.TokenType; + sc.MustGetToken(';'); + sc.TokenType = savedtoken; + return key; +} + +//=========================================================================== +// +// Syntax checks +// +//=========================================================================== + +int UDMFParserBase::CheckInt(const char *key) +{ + if (sc.TokenType != TK_IntConst) + { + sc.ScriptMessage("Integer value expected for key '%s'", key); + } + return sc.Number; +} + +double UDMFParserBase::CheckFloat(const char *key) +{ + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Floating point value expected for key '%s'", key); + } + return sc.Float; +} + +fixed_t UDMFParserBase::CheckFixed(const char *key) +{ + return FLOAT2FIXED(CheckFloat(key)); +} + +angle_t UDMFParserBase::CheckAngle(const char *key) +{ + return angle_t(CheckFloat(key) * ANGLE_90 / 90.); +} + +bool UDMFParserBase::CheckBool(const char *key) +{ + if (sc.TokenType == TK_True) return true; + if (sc.TokenType == TK_False) return false; + sc.ScriptMessage("Boolean value expected for key '%s'", key); + return false; +} + +const char *UDMFParserBase::CheckString(const char *key) +{ + if (sc.TokenType != TK_StringConst) + { + sc.ScriptMessage("String value expected for key '%s'", key); + } + return parsedString; +} + //=========================================================================== // // Storage of UDMF user properties @@ -233,15 +379,11 @@ fixed_t GetUDMFFixed(int type, int index, const char *key) // //=========================================================================== -struct UDMFParser +class UDMFParser : public UDMFParserBase { - FScanner sc; - FName namespc; - int namespace_bits; bool isTranslated; bool isExtended; bool floordrop; - FString parsedString; TArray ParsedLines; TArray ParsedSides; @@ -251,113 +393,13 @@ struct UDMFParser FDynamicColormap *fogMap, *normMap; +public: UDMFParser() { linemap.Clear(); fogMap = normMap = NULL; } - //=========================================================================== - // - // Parses a 'key = value' line of the map - // - //=========================================================================== - - FName ParseKey() - { - sc.MustGetString(); - FName key = sc.String; - sc.MustGetToken('='); - - sc.Number = 0; - sc.Float = 0; - sc.MustGetAnyToken(); - - if (sc.TokenType == '+' || sc.TokenType == '-') - { - bool neg = (sc.TokenType == '-'); - sc.MustGetAnyToken(); - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Numeric constant expected"); - } - if (neg) - { - sc.Number = -sc.Number; - sc.Float = -sc.Float; - } - } - if (sc.TokenType == TK_StringConst) - { - parsedString = sc.String; - } - int savedtoken = sc.TokenType; - sc.MustGetToken(';'); - sc.TokenType = savedtoken; - return key; - } - - //=========================================================================== - // - // Syntax checks - // - //=========================================================================== - - int CheckInt(const char *key) - { - if (sc.TokenType != TK_IntConst) - { - sc.ScriptMessage("Integer value expected for key '%s'", key); - } - return sc.Number; - } - - double CheckFloat(const char *key) - { - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Floating point value expected for key '%s'", key); - } - return sc.Float; - } - - fixed_t CheckFixed(const char *key) - { - return FLOAT2FIXED(CheckFloat(key)); - } - - angle_t CheckAngle(const char *key) - { - return angle_t(CheckFloat(key) * ANGLE_90 / 90.); - } - - bool CheckBool(const char *key) - { - if (sc.TokenType == TK_True) return true; - if (sc.TokenType == TK_False) return false; - sc.ScriptMessage("Boolean value expected for key '%s'", key); - return false; - } - - const char *CheckString(const char *key) - { - if (sc.TokenType != TK_StringConst) - { - sc.ScriptMessage("String value expected for key '%s'", key); - } - return parsedString; - } - - template - void Flag(T &value, int mask, const char *key) - { - if (CheckBool(key)) - value |= mask; - else - value &= ~mask; - } - - void AddUserKey(FName key, int kind, int index) { FUDMFKeys &keyarray = UDMFKeys[kind][index]; @@ -451,6 +493,11 @@ struct UDMFParser th->type = (short)CheckInt(key); break; + case NAME_Conversation: + CHECK_N(Zd | Zdt) + th->Conversation = CheckInt(key); + break; + case NAME_Special: CHECK_N(Hx | Zd | Zdt | Va) th->special = CheckInt(key); @@ -721,6 +768,10 @@ struct UDMFParser Flag(ld->activation, SPAC_Use, key); continue; + case NAME_Playeruseback: + Flag(ld->activation, SPAC_UseBack, key); + continue; + case NAME_Monstercross: Flag(ld->activation, SPAC_MCross, key); continue; @@ -846,6 +897,11 @@ struct UDMFParser { ld->Alpha = FRACUNIT * 3/4; } + if (ld->sidedef[0] == NULL) + { + ld->sidedef[0] = (side_t*)(intptr_t)(1); + Printf("Line %d has no first side.\n", index); + } } //=========================================================================== @@ -1015,7 +1071,7 @@ struct UDMFParser sec->SetYScale(sector_t::ceiling, FRACUNIT); sec->thinglist = NULL; sec->touching_thinglist = NULL; // phares 3/14/98 - sec->seqType = (level.flags & LEVEL_SNDSEQTOTALCTRL)? 0:-1; + sec->seqType = (level.flags & LEVEL_SNDSEQTOTALCTRL) ? 0 : -1; sec->nextsec = -1; //jff 2/26/98 add fields to support locking out sec->prevsec = -1; // stair retriggering until build completes sec->heightsec = NULL; // sector used to get floor and ceiling height @@ -1166,6 +1222,15 @@ struct UDMFParser Flag(sec->Flags, SECF_FLOORDROP, key); continue; + case NAME_SoundSequence: + sec->SeqName = CheckString(key); + sec->seqType = -1; + continue; + + case NAME_hidden: + sec->MoreFlags |= SECF_HIDDEN; + break; + default: break; } @@ -1443,6 +1508,10 @@ struct UDMFParser ParseVertex(&vt); ParsedVertices.Push(vt); } + else + { + Skip(); + } } // Create the real vertices diff --git a/src/p_udmf.h b/src/p_udmf.h new file mode 100644 index 000000000..0fc2f5564 --- /dev/null +++ b/src/p_udmf.h @@ -0,0 +1,38 @@ +#ifndef __P_UDMF_H +#define __P_UDMF_H + +#include "sc_man.h" +#include "m_fixed.h" +#include "tables.h" + +class UDMFParserBase +{ +protected: + FScanner sc; + FName namespc; + int namespace_bits; + FString parsedString; + + void Skip(); + FName ParseKey(bool checkblock = false, bool *isblock = NULL); + int CheckInt(const char *key); + double CheckFloat(const char *key); + fixed_t CheckFixed(const char *key); + angle_t CheckAngle(const char *key); + bool CheckBool(const char *key); + const char *CheckString(const char *key); + + template + void Flag(T &value, int mask, const char *key) + { + if (CheckBool(key)) + value |= mask; + else + value &= ~mask; + } + +}; + +#define BLOCK_ID (ENamedName)-1 + +#endif \ No newline at end of file diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp new file mode 100644 index 000000000..5a9814949 --- /dev/null +++ b/src/p_usdf.cpp @@ -0,0 +1,501 @@ +// +// p_usdf.cpp +// +// USDF dialogue parser +// +//--------------------------------------------------------------------------- +// Copyright (c) 2010 +// Braden "Blzut3" Obrzut +// Christoph Oelckers +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 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 "r_data.h" +#include "p_setup.h" +#include "p_lnspec.h" +#include "templates.h" +#include "i_system.h" +#include "p_conversation.h" +#include "p_udmf.h" +#include "doomerrors.h" + +#define Zd 1 +#define St 2 + +class USDFParser : public UDMFParserBase +{ + //=========================================================================== + // + // Checks an actor type (different representation depending on namespace) + // + //=========================================================================== + + PClassActor *CheckActorType(const char *key) + { + if (namespace_bits == St) + { + return GetStrifeType(CheckInt(key)); + } + else if (namespace_bits == Zd) + { + PClassActor *cls = PClass::FindActor(CheckString(key)); + if (cls == NULL) + { + sc.ScriptMessage("Unknown actor class '%s'", key); + return NULL; + } + return cls; + } + return NULL; + } + + //=========================================================================== + // + // Parse a cost block + // + //=========================================================================== + + bool ParseCost(FStrifeDialogueReply *response) + { + FStrifeDialogueItemCheck check; + check.Item = NULL; + check.Amount = -1; + + while (!sc.CheckToken('}')) + { + FName key = ParseKey(); + switch(key) + { + case NAME_Item: + check.Item = dyn_cast(CheckActorType(key)); + break; + + case NAME_Amount: + check.Amount = CheckInt(key); + break; + } + } + + response->ItemCheck.Push(check); + return true; + } + + //=========================================================================== + // + // Parse a choice block + // + //=========================================================================== + + bool ParseChoice(FStrifeDialogueReply **&replyptr) + { + FStrifeDialogueReply *reply = new FStrifeDialogueReply; + memset(reply, 0, sizeof(*reply)); + + reply->Next = *replyptr; + *replyptr = reply; + replyptr = &reply->Next; + + FString ReplyString; + FString QuickYes; + FString QuickNo; + FString LogString; + bool closeDialog = false; + + + reply->NeedsGold = false; + while (!sc.CheckToken('}')) + { + bool block = false; + int costs = 0; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Text: + ReplyString = CheckString(key); + break; + + case NAME_Displaycost: + reply->NeedsGold = CheckBool(key); + break; + + case NAME_Yesmessage: + QuickYes = CheckString(key); + //if (!QuickYes.Compare("_")) QuickYes = ""; + break; + + case NAME_Nomessage: + QuickNo = CheckString(key); + break; + + case NAME_Log: + if (namespace_bits == St) + { + const char *s = CheckString(key); + if(strlen(s) < 4 || strnicmp(s, "LOG", 3) != 0) + { + sc.ScriptMessage("Log must be in the format of LOG# to compile, ignoring."); + } + else + { + reply->LogNumber = atoi(s + 3); + } + } + else + { + LogString = CheckString(key); + } + break; + + case NAME_Giveitem: + reply->GiveType = CheckActorType(key); + break; + + case NAME_Nextpage: + reply->NextNode = CheckInt(key); + break; + + case NAME_Closedialog: + closeDialog = CheckBool(key); + break; + + case NAME_Special: + reply->ActionSpecial = CheckInt(key); + if (reply->ActionSpecial < 0 || reply->ActionSpecial > 255) + reply->ActionSpecial = 0; + break; + + case NAME_Arg0: + case NAME_Arg1: + case NAME_Arg2: + case NAME_Arg3: + case NAME_Arg4: + reply->Args[int(key)-int(NAME_Arg0)] = CheckInt(key); + break; + + + } + } + else + { + switch(key) + { + case NAME_Cost: + ParseCost(reply); + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + // Todo: Finalize + if (reply->ItemCheck.Size() > 0) + { + if (reply->ItemCheck[0].Amount <= 0) reply->NeedsGold = false; + if (reply->NeedsGold) ReplyString.AppendFormat(" for %u", reply->ItemCheck[0].Amount); + } + + reply->Reply = ncopystring(ReplyString); + reply->QuickYes = ncopystring(QuickYes); + if (reply->ItemCheck.Size() > 0 && reply->ItemCheck[0].Item != NULL) + { + reply->QuickNo = ncopystring(QuickNo); + } + else + { + reply->QuickNo = NULL; + } + reply->LogString = ncopystring(LogString); + if(!closeDialog) reply->NextNode *= -1; + return true; + } + + //=========================================================================== + // + // Parse an ifitem block + // + //=========================================================================== + + bool ParseIfItem(FStrifeDialogueNode *node) + { + FStrifeDialogueItemCheck check; + check.Item = NULL; + check.Amount = -1; + + while (!sc.CheckToken('}')) + { + FName key = ParseKey(); + switch(key) + { + case NAME_Item: + check.Item = dyn_cast(CheckActorType(key)); + break; + + case NAME_Count: + // Not yet implemented in the engine. Todo later + check.Amount = CheckInt(key); + break; + } + } + + node->ItemCheck.Push(check); + return true; + } + + //=========================================================================== + // + // Parse a page block + // + //=========================================================================== + + bool ParsePage() + { + FStrifeDialogueNode *node = new FStrifeDialogueNode; + FStrifeDialogueReply **replyptr = &node->Children; + memset(node, 0, sizeof(*node)); + //node->ItemCheckCount[0] = node->ItemCheckCount[1] = node->ItemCheckCount[2] = -1; + + node->ThisNodeNum = StrifeDialogues.Push(node); + + FString SpeakerName; + FString Dialogue; + + while (!sc.CheckToken('}')) + { + bool block = false; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Name: + SpeakerName = CheckString(key); + break; + + case NAME_Panel: + node->Backdrop = TexMan.CheckForTexture (CheckString(key), FTexture::TEX_MiscPatch); + break; + + case NAME_Voice: + { + FString soundname = (namespace_bits == St? "svox/" : ""); + const char * name = CheckString(key); + if (name[0] != 0) + { + soundname += name; + node->SpeakerVoice = FSoundID(S_FindSound(soundname)); + } + } + break; + + case NAME_Dialog: + Dialogue = CheckString(key); + break; + + case NAME_Drop: + node->DropType = CheckActorType(key); + break; + + case NAME_Link: + node->ItemCheckNode = CheckInt(key); + break; + + + } + } + else + { + switch(key) + { + case NAME_Ifitem: + if (!ParseIfItem(node)) return false; + break; + + case NAME_Choice: + if (!ParseChoice(replyptr)) return false; + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + node->SpeakerName = ncopystring(SpeakerName); + node->Dialogue = ncopystring(Dialogue); + return true; + } + + + //=========================================================================== + // + // Parse a conversation block + // + //=========================================================================== + + bool ParseConversation() + { + PClassActor *type = NULL; + int dlgid = -1; + unsigned int startpos = StrifeDialogues.Size(); + + while (!sc.CheckToken('}')) + { + bool block = false; + FName key = ParseKey(true, &block); + if (!block) + { + switch(key) + { + case NAME_Actor: + type = CheckActorType(key); + if (namespace_bits == St) + { + dlgid = CheckInt(key); + } + break; + + case NAME_Id: + if (namespace_bits == Zd) + { + dlgid = CheckInt(key); + } + break; + } + } + else + { + switch(key) + { + case NAME_Page: + if (!ParsePage()) return false; + break; + + default: + sc.UnGet(); + Skip(); + } + } + } + if (type == NULL && dlgid == 0) + { + sc.ScriptMessage("No valid actor type defined in conversation."); + return false; + } + SetConversation(dlgid, type, startpos); + for(;startpos < StrifeDialogues.Size(); startpos++) + { + StrifeDialogues[startpos]->SpeakerType = type; + } + return true; + } + + //=========================================================================== + // + // Parse an USDF lump + // + //=========================================================================== + +public: + bool Parse(int lumpnum, FileReader *lump, int lumplen) + { + char *buffer = new char[lumplen]; + lump->Read(buffer, lumplen); + sc.OpenMem(Wads.GetLumpFullName(lumpnum), buffer, lumplen); + delete [] buffer; + sc.SetCMode(true); + // Namespace must be the first field because everything else depends on it. + if (sc.CheckString("namespace")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_StringConst); + namespc = sc.String; + switch(namespc) + { + case NAME_ZDoom: + namespace_bits = Zd; + break; + case NAME_Strife: + namespace_bits = St; + break; + default: + sc.ScriptMessage("Unknown namespace %s. Ignoring dialogue lump.\n", sc.String); + return false; + } + sc.MustGetToken(';'); + } + else + { + sc.ScriptMessage("Map does not define a namespace.\n"); + return false; + } + + while (sc.GetString()) + { + if (sc.Compare("conversation")) + { + sc.MustGetToken('{'); + if (!ParseConversation()) return false; + } + else if (sc.Compare("include")) + { + sc.MustGetToken('='); + sc.MustGetToken(TK_StringConst); + LoadScriptFile(sc.String, true); + sc.MustGetToken(';'); + } + else + { + Skip(); + } + } + return true; + } +}; + + + +bool P_ParseUSDF(int lumpnum, FileReader *lump, int lumplen) +{ + USDFParser parse; + + try + { + if (!parse.Parse(lumpnum, lump, lumplen)) + { + // clean up the incomplete dialogue structures here + return false; + } + return true; + } + catch(CRecoverableError &err) + { + Printf("%s", err.GetMessage()); + return false; + } +} diff --git a/src/p_user.cpp b/src/p_user.cpp index acaaf28a9..8840704f8 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -370,17 +370,17 @@ void player_t::SetLogNumber (int num) data[length]=0; SetLogText (data); delete[] data; - - // Print log text to console - AddToConsole(-1, TEXTCOLOR_GOLD); - AddToConsole(-1, LogText); - AddToConsole(-1, "\n"); } } void player_t::SetLogText (const char *text) { LogText = text; + + // Print log text to console + AddToConsole(-1, TEXTCOLOR_GOLD); + AddToConsole(-1, LogText); + AddToConsole(-1, "\n"); } int player_t::GetSpawnClass() @@ -482,6 +482,10 @@ void APlayerPawn::Serialize (FArchive &arc) << MorphWeapon << DamageFade << PlayerFlags; + if (SaveVersion < 2435) + { + DamageFade.a = 255; + } } //=========================================================================== @@ -555,6 +559,15 @@ void APlayerPawn::Tick() void APlayerPawn::PostBeginPlay() { SetupWeaponSlots(); + + // Voodoo dolls: restore original floorz/ceilingz logic + if (player == NULL || player->mo != this) + { + dropoffz = floorz = Sector->floorplane.ZatPoint(x, y); + ceilingz = Sector->ceilingplane.ZatPoint(x, y); + P_FindFloorCeiling(this, true); + z = floorz; + } } //=========================================================================== @@ -2022,7 +2035,7 @@ void P_CrouchMove(player_t * player, int direction) // check whether the move is ok player->mo->height = FixedMul(defaultheight, player->crouchfactor); - if (!P_TryMove(player->mo, player->mo->x, player->mo->y, false, false)) + if (!P_TryMove(player->mo, player->mo->x, player->mo->y, false, NULL)) { player->mo->height = savedheight; if (direction > 0) @@ -2101,7 +2114,7 @@ void P_PlayerThink (player_t *player) player->inventorytics--; } // No-clip cheat - if (player->cheats & CF_NOCLIP) + if (player->cheats & CF_NOCLIP || (player->mo->GetDefault()->flags & MF_NOCLIP)) { player->mo->flags |= MF_NOCLIP; } diff --git a/src/p_writemap.cpp b/src/p_writemap.cpp index db758631f..7a052cb3b 100644 --- a/src/p_writemap.cpp +++ b/src/p_writemap.cpp @@ -172,8 +172,10 @@ static int WriteVERTEXES (FILE *file) return numvertexes * sizeof(mv); } + static int WriteSEGS (FILE *file) { +#if 0 mapseg_t ms; ms.offset = 0; // unused by ZDoom, so just leave it 0 @@ -190,10 +192,14 @@ static int WriteSEGS (FILE *file) } } return numsegs * sizeof(ms); +#else + return 0; +#endif } static int WriteSSECTORS (FILE *file) { +#if 0 mapsubsector_t mss; for (int i = 0; i < numsubsectors; ++i) @@ -203,10 +209,14 @@ static int WriteSSECTORS (FILE *file) fwrite (&mss, sizeof(mss), 1, file); } return numsubsectors * sizeof(mss); +#else + return 0; +#endif } static int WriteNODES (FILE *file) { +#if 0 mapnode_t mn; for (int i = 0; i < numnodes; ++i) @@ -224,7 +234,7 @@ static int WriteNODES (FILE *file) WORD child; if ((size_t)nodes[i].children[j] & 1) { - child = NF_SUBSECTOR | WORD((subsector_t *)((BYTE *)nodes[i].children[j] - 1) - subsectors); + child = mapnode_t::NF_SUBSECTOR | WORD((subsector_t *)((BYTE *)nodes[i].children[j] - 1) - subsectors); } else { @@ -235,6 +245,9 @@ static int WriteNODES (FILE *file) fwrite (&mn, sizeof(mn), 1, file); } return numnodes * sizeof(mn); +#else + return 0; +#endif } static int WriteSECTORS (FILE *file) diff --git a/src/po_man.cpp b/src/po_man.cpp index 13a5bda91..0f9b0b15a 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -26,6 +26,9 @@ #include "p_lnspec.h" #include "r_interpolate.h" #include "g_level.h" +#include "po_man.h" +#include "p_setup.h" +#include "vectors.h" // MACROS ------------------------------------------------------------------ @@ -33,6 +36,17 @@ // TYPES ------------------------------------------------------------------- +inline vertex_t *side_t::V1() const +{ + return this == linedef->sidedef[0]? linedef->v1 : linedef->v2; +} + +inline vertex_t *side_t::V2() const +{ + return this == linedef->sidedef[0]? linedef->v2 : linedef->v1; +} + + inline FArchive &operator<< (FArchive &arc, podoortype_t &type) { BYTE val = (BYTE)type; @@ -49,6 +63,8 @@ public: DPolyAction (int polyNum); void Serialize (FArchive &arc); void Destroy(); + void Stop(); + int GetSpeed() const { return m_Speed; } void StopInterpolation (); protected: @@ -59,8 +75,6 @@ protected: TObjPtr m_Interpolation; void SetInterpolation (); - - friend void ThrustMobj (AActor *actor, seg_t *seg, FPolyObj *po); }; class DRotatePoly : public DPolyAction @@ -92,6 +106,23 @@ protected: friend bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, fixed_t dist, bool overRide); }; +class DMovePolyTo : public DPolyAction +{ + DECLARE_CLASS(DMovePolyTo, DPolyAction) +public: + DMovePolyTo(int polyNum); + void Serialize(FArchive &arc); + void Tick(); +protected: + DMovePolyTo(); + fixed_t m_xSpeed; + fixed_t m_ySpeed; + fixed_t m_xTarget; + fixed_t m_yTarget; + + friend bool EV_MovePolyTo(line_t *line, int polyNum, int speed, fixed_t x, fixed_t y, bool overRide); +}; + class DPolyDoor : public DMovePoly { @@ -115,25 +146,25 @@ private: // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -bool PO_RotatePolyobj (int num, angle_t angle); void PO_Init (void); // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- -static int GetPolyobjMirror (int poly); -static void UpdateSegBBox (seg_t *seg); static void RotatePt (int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_t startSpotY); static void UnLinkPolyobj (FPolyObj *po); static void LinkPolyobj (FPolyObj *po); -static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po); +static bool CheckMobjBlocking (side_t *seg, FPolyObj *po); static void InitBlockMap (void); -static void IterFindPolySegs (vertex_t *v1, vertex_t *v2, seg_t **segList); +static void IterFindPolySides (FPolyObj *po, side_t *side); static void SpawnPolyobj (int index, int tag, int type); static void TranslateToStartSpot (int tag, int originX, int originY); static void DoMovePolyobj (FPolyObj *po, int x, int y); static void InitSegLists (); static void KillSegLists (); +static FPolyNode *NewPolyNode(); +static void FreePolyNode(); +static void ReleaseAllPolyNodes(); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -148,21 +179,23 @@ polyspawns_t *polyspawns; // [RH] Let P_SpawnMapThings() find our thingies for u // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static int PolySegCount; - -static SDWORD *SegListHead; // contains numvertexes elements -static TArray KnownPolySegs; +static TArray KnownPolySides; +static FPolyNode *FreePolyNodes; // CODE -------------------------------------------------------------------- + + +//========================================================================== +// +// +// +//========================================================================== + IMPLEMENT_POINTY_CLASS (DPolyAction) DECLARE_POINTER(m_Interpolation) END_POINTERS -IMPLEMENT_CLASS (DRotatePoly) -IMPLEMENT_CLASS (DMovePoly) -IMPLEMENT_CLASS (DPolyDoor) - DPolyAction::DPolyAction () { } @@ -185,7 +218,7 @@ void DPolyAction::Destroy() { FPolyObj *poly = PO_GetPolyobj (m_PolyObj); - if (poly->specialdata == NULL || poly->specialdata == this) + if (poly->specialdata == this) { poly->specialdata = NULL; } @@ -194,6 +227,13 @@ void DPolyAction::Destroy() Super::Destroy(); } +void DPolyAction::Stop() +{ + FPolyObj *poly = PO_GetPolyobj(m_PolyObj); + SN_StopSequence(poly); + Destroy(); +} + void DPolyAction::SetInterpolation () { FPolyObj *poly = PO_GetPolyobj (m_PolyObj); @@ -209,6 +249,14 @@ void DPolyAction::StopInterpolation () } } +//========================================================================== +// +// +// +//========================================================================== + +IMPLEMENT_CLASS (DRotatePoly) + DRotatePoly::DRotatePoly () { } @@ -218,6 +266,14 @@ DRotatePoly::DRotatePoly (int polyNum) { } +//========================================================================== +// +// +// +//========================================================================== + +IMPLEMENT_CLASS (DMovePoly) + DMovePoly::DMovePoly () { } @@ -236,6 +292,42 @@ DMovePoly::DMovePoly (int polyNum) m_ySpeed = 0; } +//========================================================================== +// +// +// +// +//========================================================================== + +IMPLEMENT_CLASS(DMovePolyTo) + +DMovePolyTo::DMovePolyTo() +{ +} + +void DMovePolyTo::Serialize(FArchive &arc) +{ + Super::Serialize(arc); + arc << m_xSpeed << m_ySpeed << m_xTarget << m_yTarget; +} + +DMovePolyTo::DMovePolyTo(int polyNum) + : Super(polyNum) +{ + m_xSpeed = 0; + m_ySpeed = 0; + m_xTarget = 0; + m_yTarget = 0; +} + +//========================================================================== +// +// +// +//========================================================================== + +IMPLEMENT_CLASS (DPolyDoor) + DPolyDoor::DPolyDoor () { } @@ -266,7 +358,10 @@ DPolyDoor::DPolyDoor (int polyNum, podoortype_t type) void DRotatePoly::Tick () { - if (PO_RotatePolyobj (m_PolyObj, m_Speed)) + FPolyObj *poly = PO_GetPolyobj (m_PolyObj); + if (poly == NULL) return; + + if (poly->RotatePolyobj (m_Speed)) { unsigned int absSpeed = abs (m_Speed); @@ -277,11 +372,6 @@ void DRotatePoly::Tick () m_Dist -= absSpeed; if (m_Dist == 0) { - FPolyObj *poly = PO_GetPolyobj (m_PolyObj); - if (poly->specialdata == this) - { - poly->specialdata = NULL; - } SN_StopSequence (poly); Destroy (); } @@ -315,7 +405,8 @@ bool EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle, } else { - I_Error("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum); + Printf("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum); + return false; } pe = new DRotatePoly (polyNum); if (byteAngle) @@ -337,12 +428,13 @@ bool EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle, poly->specialdata = pe; SN_StartSequence (poly, poly->seqType, SEQ_DOOR, 0); - while ( (mirror = GetPolyobjMirror(polyNum)) ) + while ( (mirror = poly->GetMirror()) ) { poly = PO_GetPolyobj(mirror); if (poly == NULL) { - I_Error ("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum); + Printf ("EV_RotatePoly: Invalid polyobj num: %d\n", polyNum); + break; } if (poly && poly->specialdata && !overRide) { // mirroring poly is already in motion @@ -381,27 +473,25 @@ bool EV_RotatePoly (line_t *line, int polyNum, int speed, int byteAngle, void DMovePoly::Tick () { - FPolyObj *poly; + FPolyObj *poly = PO_GetPolyobj (m_PolyObj); - if (PO_MovePolyobj (m_PolyObj, m_xSpeed, m_ySpeed)) + if (poly != NULL) { - int absSpeed = abs (m_Speed); - m_Dist -= absSpeed; - if (m_Dist <= 0) + if (poly->MovePolyobj (m_xSpeed, m_ySpeed)) { - poly = PO_GetPolyobj (m_PolyObj); - if (poly->specialdata == this) + int absSpeed = abs (m_Speed); + m_Dist -= absSpeed; + if (m_Dist <= 0) { - poly->specialdata = NULL; + SN_StopSequence (poly); + Destroy (); + } + else if (m_Dist < absSpeed) + { + m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1); + m_xSpeed = FixedMul (m_Speed, finecosine[m_Angle]); + m_ySpeed = FixedMul (m_Speed, finesine[m_Angle]); } - SN_StopSequence (poly); - Destroy (); - } - else if (m_Dist < absSpeed) - { - m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1); - m_xSpeed = FixedMul (m_Speed, finecosine[m_Angle]); - m_ySpeed = FixedMul (m_Speed, finesine[m_Angle]); } } } @@ -429,7 +519,8 @@ bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, } else { - I_Error("EV_MovePoly: Invalid polyobj num: %d\n", polyNum); + Printf("EV_MovePoly: Invalid polyobj num: %d\n", polyNum); + return false; } pe = new DMovePoly (polyNum); pe->m_Dist = dist; // Distance @@ -452,7 +543,7 @@ bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, pe->StopInterpolation (); } - while ( (mirror = GetPolyobjMirror(polyNum)) ) + while ( (mirror = poly->GetMirror()) ) { poly = PO_GetPolyobj(mirror); if (poly && poly->specialdata && !overRide) @@ -477,6 +568,109 @@ bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, return true; } +//========================================================================== +// +// DMovePolyTo :: Tick +// +//========================================================================== + +void DMovePolyTo::Tick () +{ + FPolyObj *poly = PO_GetPolyobj (m_PolyObj); + + if (poly != NULL) + { + if (poly->MovePolyobj (m_xSpeed, m_ySpeed)) + { + int absSpeed = abs (m_Speed); + m_Dist -= absSpeed; + if (m_Dist <= 0) + { + SN_StopSequence (poly); + Destroy (); + } + else if (m_Dist < absSpeed) + { + m_Speed = m_Dist * (m_Speed < 0 ? -1 : 1); + m_xSpeed = m_xTarget - poly->StartSpot.x; + m_ySpeed = m_yTarget - poly->StartSpot.y; + } + } + } +} + +//========================================================================== +// +// EV_MovePolyTo +// +//========================================================================== + +bool EV_MovePolyTo(line_t *line, int polyNum, int speed, fixed_t targx, fixed_t targy, bool overRide) +{ + int mirror; + DMovePolyTo *pe; + FPolyObj *poly; + TVector2 dist; + double distlen; + bool nointerp; + + if ( (poly = PO_GetPolyobj(polyNum)) ) + { + if (poly->specialdata && !overRide) + { // poly is already moving + return false; + } + } + else + { + Printf("EV_MovePolyTo: Invalid polyobj num: %d\n", polyNum); + return false; + } + dist.X = targx - poly->StartSpot.x; + dist.Y = targy - poly->StartSpot.y; + pe = new DMovePolyTo(polyNum); + poly->specialdata = pe; + pe->m_Dist = xs_RoundToInt(distlen = dist.MakeUnit()); + pe->m_Speed = speed; + pe->m_xSpeed = xs_RoundToInt(speed * dist.X); + pe->m_ySpeed = xs_RoundToInt(speed * dist.Y); + pe->m_xTarget = targx; + pe->m_yTarget = targy; + + nointerp = (pe->m_Dist / pe->m_Speed) <= 2; + if (nointerp) + { + pe->StopInterpolation(); + } + + while ( (mirror = poly->GetMirror()) ) + { + poly = PO_GetPolyobj(mirror); + if (poly && poly->specialdata && !overRide) + { // mirroring poly is already in motion + break; + } + // reverse the direction + dist.X = -dist.X; + dist.Y = -dist.Y; + pe = new DMovePolyTo(mirror); + poly->specialdata = pe; + pe->m_Dist = xs_RoundToInt(distlen); + pe->m_Speed = speed; + pe->m_xSpeed = xs_RoundToInt(speed * dist.X); + pe->m_ySpeed = xs_RoundToInt(speed * dist.Y); + pe->m_xTarget = xs_RoundToInt(poly->StartSpot.x + distlen * dist.X); + pe->m_yTarget = xs_RoundToInt(poly->StartSpot.y + distlen * dist.Y); + polyNum = mirror; + SN_StartSequence(poly, poly->seqType, SEQ_DOOR, 0); + if (nointerp) + { + pe->StopInterpolation(); + } + } + return true; +} + //========================================================================== // // T_PolyDoor @@ -486,13 +680,14 @@ bool EV_MovePoly (line_t *line, int polyNum, int speed, angle_t angle, void DPolyDoor::Tick () { int absSpeed; - FPolyObj *poly; + FPolyObj *poly = PO_GetPolyobj (m_PolyObj); + + if (poly == NULL) return; if (m_Tics) { if (!--m_Tics) { - poly = PO_GetPolyobj (m_PolyObj); SN_StartSequence (poly, poly->seqType, SEQ_DOOR, m_Close); } return; @@ -500,37 +695,30 @@ void DPolyDoor::Tick () switch (m_Type) { case PODOOR_SLIDE: - if (m_Dist <= 0 || PO_MovePolyobj (m_PolyObj, m_xSpeed, m_ySpeed)) + if (m_Dist <= 0 || poly->MovePolyobj (m_xSpeed, m_ySpeed)) { absSpeed = abs (m_Speed); m_Dist -= absSpeed; if (m_Dist <= 0) { - poly = PO_GetPolyobj (m_PolyObj); SN_StopSequence (poly); if (!m_Close) { m_Dist = m_TotalDist; m_Close = true; m_Tics = m_WaitTics; - m_Direction = (ANGLE_MAX>>ANGLETOFINESHIFT)- - m_Direction; + m_Direction = (ANGLE_MAX>>ANGLETOFINESHIFT) - m_Direction; m_xSpeed = -m_xSpeed; m_ySpeed = -m_ySpeed; } else { - if (poly->specialdata == this) - { - poly->specialdata = NULL; - } Destroy (); } } } else { - poly = PO_GetPolyobj (m_PolyObj); if (poly->crush || !m_Close) { // continue moving if the poly is a crusher, or is opening return; @@ -549,7 +737,7 @@ void DPolyDoor::Tick () break; case PODOOR_SWING: - if (PO_RotatePolyobj (m_PolyObj, m_Speed)) + if (poly->RotatePolyobj (m_Speed)) { absSpeed = abs (m_Speed); if (m_Dist == -1) @@ -559,7 +747,6 @@ void DPolyDoor::Tick () m_Dist -= absSpeed; if (m_Dist <= 0) { - poly = PO_GetPolyobj (m_PolyObj); SN_StopSequence (poly); if (!m_Close) { @@ -570,17 +757,12 @@ void DPolyDoor::Tick () } else { - if (poly->specialdata == this) - { - poly->specialdata = NULL; - } Destroy (); } } } else { - poly = PO_GetPolyobj (m_PolyObj); if(poly->crush || !m_Close) { // continue moving if the poly is a crusher, or is opening return; @@ -622,7 +804,8 @@ bool EV_OpenPolyDoor (line_t *line, int polyNum, int speed, angle_t angle, } else { - I_Error("EV_OpenPolyDoor: Invalid polyobj num: %d\n", polyNum); + Printf("EV_OpenPolyDoor: Invalid polyobj num: %d\n", polyNum); + return false; } pd = new DPolyDoor (polyNum, type); if (type == PODOOR_SLIDE) @@ -646,7 +829,7 @@ bool EV_OpenPolyDoor (line_t *line, int polyNum, int speed, angle_t angle, poly->specialdata = pd; - while ( (mirror = GetPolyobjMirror (polyNum)) ) + while ( (mirror = poly->GetMirror()) ) { poly = PO_GetPolyobj (mirror); if (poly && poly->specialdata) @@ -677,7 +860,28 @@ bool EV_OpenPolyDoor (line_t *line, int polyNum, int speed, angle_t angle, } return true; } - + +//========================================================================== +// +// EV_StopPoly +// +//========================================================================== + +bool EV_StopPoly(int polynum) +{ + FPolyObj *poly; + + if (NULL != (poly = PO_GetPolyobj(polynum))) + { + if (poly->specialdata != NULL) + { + poly->specialdata->Stop(); + } + return true; + } + return false; +} + // ===== Higher Level Poly Interface code ===== //========================================================================== @@ -700,24 +904,38 @@ FPolyObj *PO_GetPolyobj (int polyNum) return NULL; } + +//========================================================================== +// +// +// +//========================================================================== + +FPolyObj::FPolyObj() +{ + StartSpot.x = StartSpot.y = 0; + angle = 0; + tag = 0; + memset(bbox, 0, sizeof(bbox)); + validcount = 0; + crush = 0; + bHurtOnTouch = false; + seqType = 0; + size = 0; + subsectorlinks = NULL; + specialdata = NULL; + interpolation = NULL; +} + //========================================================================== // // GetPolyobjMirror // //========================================================================== -static int GetPolyobjMirror(int poly) +int FPolyObj::GetMirror() { - int i; - - for (i = 0; i < po_NumPolyobjs; i++) - { - if (polyobjs[i].tag == poly) - { - return polyobjs[i].lines[0]->args[1]; - } - } - return 0; + return Linedefs[0]->args[1]; } //========================================================================== @@ -726,7 +944,7 @@ static int GetPolyobjMirror(int poly) // //========================================================================== -void ThrustMobj (AActor *actor, seg_t *seg, FPolyObj *po) +void FPolyObj::ThrustMobj (AActor *actor, side_t *side) { int thrustAngle; int thrustX; @@ -739,20 +957,20 @@ void ThrustMobj (AActor *actor, seg_t *seg, FPolyObj *po) { return; } - thrustAngle = - (R_PointToAngle2 (seg->v1->x, seg->v1->y, seg->v2->x, seg->v2->y) - - ANGLE_90) >> ANGLETOFINESHIFT; + vertex_t *v1 = side->V1(); + vertex_t *v2 = side->V2(); + thrustAngle = (R_PointToAngle2 (v1->x, v1->y, v2->x, v2->y) - ANGLE_90) >> ANGLETOFINESHIFT; - pe = static_cast(po->specialdata); + pe = static_cast(specialdata); if (pe) { if (pe->IsKindOf (RUNTIME_CLASS (DRotatePoly))) { - force = pe->m_Speed >> 8; + force = pe->GetSpeed() >> 8; } else { - force = pe->m_Speed >> 3; + force = pe->GetSpeed() >> 3; } if (force < FRACUNIT) { @@ -772,12 +990,12 @@ void ThrustMobj (AActor *actor, seg_t *seg, FPolyObj *po) thrustY = FixedMul (force, finesine[thrustAngle]); actor->velx += thrustX; actor->vely += thrustY; - if (po->crush) + if (crush) { - if (po->bHurtOnTouch || !P_CheckMove (actor, actor->x + thrustX, actor->y + thrustY)) + if (bHurtOnTouch || !P_CheckMove (actor, actor->x + thrustX, actor->y + thrustY)) { - P_DamageMobj (actor, NULL, NULL, po->crush, NAME_Crush); - P_TraceBleed (po->crush, actor); + P_DamageMobj (actor, NULL, NULL, crush, NAME_Crush); + P_TraceBleed (crush, actor); } } if (level.flags2 & LEVEL2_POLYGRIND) actor->Grind(false); // crush corpses that get caught in a polyobject's way @@ -789,48 +1007,62 @@ void ThrustMobj (AActor *actor, seg_t *seg, FPolyObj *po) // //========================================================================== -static void UpdateSegBBox (seg_t *seg) +void FPolyObj::UpdateBBox () { - line_t *line; + for(unsigned i=0;ilinedef; + if (line->v1->x < line->v2->x) + { + line->bbox[BOXLEFT] = line->v1->x; + line->bbox[BOXRIGHT] = line->v2->x; + } + else + { + line->bbox[BOXLEFT] = line->v2->x; + line->bbox[BOXRIGHT] = line->v1->x; + } + if (line->v1->y < line->v2->y) + { + line->bbox[BOXBOTTOM] = line->v1->y; + line->bbox[BOXTOP] = line->v2->y; + } + else + { + line->bbox[BOXBOTTOM] = line->v2->y; + line->bbox[BOXTOP] = line->v1->y; + } - if (line->v1->x < line->v2->x) - { - line->bbox[BOXLEFT] = line->v1->x; - line->bbox[BOXRIGHT] = line->v2->x; - } - else - { - line->bbox[BOXLEFT] = line->v2->x; - line->bbox[BOXRIGHT] = line->v1->x; - } - if (line->v1->y < line->v2->y) - { - line->bbox[BOXBOTTOM] = line->v1->y; - line->bbox[BOXTOP] = line->v2->y; - } - else - { - line->bbox[BOXBOTTOM] = line->v2->y; - line->bbox[BOXTOP] = line->v1->y; + // Update the line's slopetype + line->dx = line->v2->x - line->v1->x; + line->dy = line->v2->y - line->v1->y; + if (!line->dx) + { + line->slopetype = ST_VERTICAL; + } + else if (!line->dy) + { + line->slopetype = ST_HORIZONTAL; + } + else + { + line->slopetype = ((line->dy ^ line->dx) >= 0) ? ST_POSITIVE : ST_NEGATIVE; + } } + CalcCenter(); +} - // Update the line's slopetype - line->dx = line->v2->x - line->v1->x; - line->dy = line->v2->y - line->v1->y; - if (!line->dx) +void FPolyObj::CalcCenter() +{ + SQWORD cx = 0, cy = 0; + for(unsigned i=0;islopetype = ST_VERTICAL; - } - else if (!line->dy) - { - line->slopetype = ST_HORIZONTAL; - } - else - { - line->slopetype = ((line->dy ^ line->dx) >= 0) ? ST_POSITIVE : ST_NEGATIVE; + cx += Vertices[i]->x; + cy += Vertices[i]->y; } + CenterSpot.x = (fixed_t)(cx / Vertices.Size()); + CenterSpot.y = (fixed_t)(cy / Vertices.Size()); } //========================================================================== @@ -839,41 +1071,35 @@ static void UpdateSegBBox (seg_t *seg) // //========================================================================== -bool PO_MovePolyobj (int num, int x, int y, bool force) +bool FPolyObj::MovePolyobj (int x, int y, bool force) { - FPolyObj *po; - - if (!(po = PO_GetPolyobj (num))) - { - I_Error ("PO_MovePolyobj: Invalid polyobj number: %d\n", num); - } - - UnLinkPolyobj (po); - DoMovePolyobj (po, x, y); + UnLinkPolyobj (); + DoMovePolyobj (x, y); if (!force) { - seg_t **segList = po->segs; bool blocked = false; - for (int count = po->numsegs; count; count--, segList++) + for(unsigned i=0;i < Sidedefs.Size(); i++) { - if (CheckMobjBlocking(*segList, po)) + if (CheckMobjBlocking(Sidedefs[i])) { blocked = true; - break; } } if (blocked) { - DoMovePolyobj (po, -x, -y); - LinkPolyobj(po); + DoMovePolyobj (-x, -y); + LinkPolyobj(); return false; } } - po->startSpot[0] += x; - po->startSpot[1] += y; - LinkPolyobj (po); + StartSpot.x += x; + StartSpot.y += y; + CenterSpot.x += x; + CenterSpot.y += y; + LinkPolyobj (); + ClearSubsectorLinks(); return true; } @@ -883,42 +1109,21 @@ bool PO_MovePolyobj (int num, int x, int y, bool force) // //========================================================================== -void DoMovePolyobj (FPolyObj *po, int x, int y) +void FPolyObj::DoMovePolyobj (int x, int y) { - int count; - seg_t **segList; - seg_t **veryTempSeg; - vertex_t *prevPts; - - segList = po->segs; - prevPts = po->prevPts; - - validcount++; - for (count = po->numsegs; count; count--, segList++, prevPts++) + for(unsigned i=0;i < Vertices.Size(); i++) { - line_t *linedef = (*segList)->linedef; - if (linedef->validcount != validcount) - { - linedef->bbox[BOXTOP] += y; - linedef->bbox[BOXBOTTOM] += y; - linedef->bbox[BOXLEFT] += x; - linedef->bbox[BOXRIGHT] += x; - linedef->validcount = validcount; - } - for (veryTempSeg = po->segs; veryTempSeg != segList; veryTempSeg++) - { - if ((*veryTempSeg)->v1 == (*segList)->v1) - { - break; - } - } - if (veryTempSeg == segList) - { - (*segList)->v1->x += x; - (*segList)->v1->y += y; - } - (*prevPts).x += x; // previous points are unique for each seg - (*prevPts).y += y; + Vertices[i]->x += x; + Vertices[i]->y += y; + PrevPts[i].x += x; + PrevPts[i].y += y; + } + for (unsigned i = 0; i < Linedefs.Size(); i++) + { + Linedefs[i]->bbox[BOXTOP] += y; + Linedefs[i]->bbox[BOXBOTTOM] += y; + Linedefs[i]->bbox[BOXLEFT] += x; + Linedefs[i]->bbox[BOXRIGHT] += x; } } @@ -943,77 +1148,48 @@ static void RotatePt (int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_ // //========================================================================== -bool PO_RotatePolyobj (int num, angle_t angle) +bool FPolyObj::RotatePolyobj (angle_t angle) { - int count; - seg_t **segList; - vertex_t *originalPts; - vertex_t *prevPts; int an; - FPolyObj *po; bool blocked; - if(!(po = PO_GetPolyobj(num))) + an = (this->angle+angle)>>ANGLETOFINESHIFT; + + UnLinkPolyobj(); + + for(unsigned i=0;i < Vertices.Size(); i++) { - I_Error("PO_RotatePolyobj: Invalid polyobj number: %d\n", num); + PrevPts[i].x = Vertices[i]->x; + PrevPts[i].y = Vertices[i]->y; + Vertices[i]->x = OriginalPts[i].x; + Vertices[i]->y = OriginalPts[i].y; + RotatePt(an, &Vertices[i]->x, &Vertices[i]->y, StartSpot.x, StartSpot.y); } - an = (po->angle+angle)>>ANGLETOFINESHIFT; - - UnLinkPolyobj(po); - - segList = po->segs; - originalPts = po->originalPts; - prevPts = po->prevPts; - - for(count = po->numsegs; count; count--, segList++, originalPts++, - prevPts++) - { - prevPts->x = (*segList)->v1->x; - prevPts->y = (*segList)->v1->y; - (*segList)->v1->x = originalPts->x; - (*segList)->v1->y = originalPts->y; - RotatePt (an, &(*segList)->v1->x, &(*segList)->v1->y, po->startSpot[0], - po->startSpot[1]); - } - segList = po->segs; blocked = false; validcount++; - for (count = po->numsegs; count; count--, segList++) + UpdateBBox(); + + for(unsigned i=0;i < Sidedefs.Size(); i++) { - if (CheckMobjBlocking(*segList, po)) + if (CheckMobjBlocking(Sidedefs[i])) { blocked = true; } - if ((*segList)->linedef->validcount != validcount) - { - UpdateSegBBox(*segList); - (*segList)->linedef->validcount = validcount; - } } if (blocked) { - segList = po->segs; - prevPts = po->prevPts; - for (count = po->numsegs; count; count--, segList++, prevPts++) + for(unsigned i=0;i < Vertices.Size(); i++) { - (*segList)->v1->x = prevPts->x; - (*segList)->v1->y = prevPts->y; + Vertices[i]->x = PrevPts[i].x; + Vertices[i]->y = PrevPts[i].y; } - segList = po->segs; - validcount++; - for (count = po->numsegs; count; count--, segList++, prevPts++) - { - if ((*segList)->linedef->validcount != validcount) - { - UpdateSegBBox(*segList); - (*segList)->linedef->validcount = validcount; - } - } - LinkPolyobj(po); + UpdateBBox(); + LinkPolyobj(); return false; } - po->angle += angle; - LinkPolyobj(po); + this->angle += angle; + LinkPolyobj(); + ClearSubsectorLinks(); return true; } @@ -1023,22 +1199,22 @@ bool PO_RotatePolyobj (int num, angle_t angle) // //========================================================================== -static void UnLinkPolyobj (FPolyObj *po) +void FPolyObj::UnLinkPolyobj () { polyblock_t *link; int i, j; int index; // remove the polyobj from each blockmap section - for(j = po->bbox[BOXBOTTOM]; j <= po->bbox[BOXTOP]; j++) + for(j = bbox[BOXBOTTOM]; j <= bbox[BOXTOP]; j++) { index = j*bmapwidth; - for(i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++) + for(i = bbox[BOXLEFT]; i <= bbox[BOXRIGHT]; i++) { if(i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight) { link = PolyBlockMap[index+i]; - while(link != NULL && link->polyobj != po) + while(link != NULL && link->polyobj != this) { link = link->next; } @@ -1052,99 +1228,13 @@ static void UnLinkPolyobj (FPolyObj *po) } } -//========================================================================== -// -// LinkPolyobj -// -//========================================================================== - -static void LinkPolyobj (FPolyObj *po) -{ - int leftX, rightX; - int topY, bottomY; - seg_t **tempSeg; - polyblock_t **link; - polyblock_t *tempLink; - int i, j; - - // calculate the polyobj bbox - tempSeg = po->segs; - rightX = leftX = (*tempSeg)->v1->x; - topY = bottomY = (*tempSeg)->v1->y; - - for(i = 0; i < po->numsegs; i++, tempSeg++) - { - if((*tempSeg)->v1->x > rightX) - { - rightX = (*tempSeg)->v1->x; - } - if((*tempSeg)->v1->x < leftX) - { - leftX = (*tempSeg)->v1->x; - } - if((*tempSeg)->v1->y > topY) - { - topY = (*tempSeg)->v1->y; - } - if((*tempSeg)->v1->y < bottomY) - { - bottomY = (*tempSeg)->v1->y; - } - } - po->bbox[BOXRIGHT] = (rightX-bmaporgx)>>MAPBLOCKSHIFT; - po->bbox[BOXLEFT] = (leftX-bmaporgx)>>MAPBLOCKSHIFT; - po->bbox[BOXTOP] = (topY-bmaporgy)>>MAPBLOCKSHIFT; - po->bbox[BOXBOTTOM] = (bottomY-bmaporgy)>>MAPBLOCKSHIFT; - // add the polyobj to each blockmap section - for(j = po->bbox[BOXBOTTOM]*bmapwidth; j <= po->bbox[BOXTOP]*bmapwidth; - j += bmapwidth) - { - for(i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++) - { - if(i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight*bmapwidth) - { - link = &PolyBlockMap[j+i]; - if(!(*link)) - { // Create a new link at the current block cell - *link = new polyblock_t; - (*link)->next = NULL; - (*link)->prev = NULL; - (*link)->polyobj = po; - continue; - } - else - { - tempLink = *link; - while(tempLink->next != NULL && tempLink->polyobj != NULL) - { - tempLink = tempLink->next; - } - } - if(tempLink->polyobj == NULL) - { - tempLink->polyobj = po; - continue; - } - else - { - tempLink->next = new polyblock_t; - tempLink->next->next = NULL; - tempLink->next->prev = tempLink; - tempLink->next->polyobj = po; - } - } - // else, don't link the polyobj, since it's off the map - } - } -} - //========================================================================== // // CheckMobjBlocking // //========================================================================== -static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po) +bool FPolyObj::CheckMobjBlocking (side_t *sd) { static TArray checker; FBlockNode *block; @@ -1154,12 +1244,12 @@ static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po) line_t *ld; bool blocked; - ld = seg->linedef; + ld = sd->linedef; - top = (ld->bbox[BOXTOP]-bmaporgy+MAXRADIUS)>>MAPBLOCKSHIFT; - bottom = (ld->bbox[BOXBOTTOM]-bmaporgy-MAXRADIUS)>>MAPBLOCKSHIFT; - left = (ld->bbox[BOXLEFT]-bmaporgx-MAXRADIUS)>>MAPBLOCKSHIFT; - right = (ld->bbox[BOXRIGHT]-bmaporgx+MAXRADIUS)>>MAPBLOCKSHIFT; + top = (ld->bbox[BOXTOP]-bmaporgy) >> MAPBLOCKSHIFT; + bottom = (ld->bbox[BOXBOTTOM]-bmaporgy) >> MAPBLOCKSHIFT; + left = (ld->bbox[BOXLEFT]-bmaporgx) >> MAPBLOCKSHIFT; + right = (ld->bbox[BOXRIGHT]-bmaporgx) >> MAPBLOCKSHIFT; blocked = false; checker.Clear(); @@ -1205,7 +1295,18 @@ static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po) { continue; } - ThrustMobj (mobj, seg, po); + // We have a two-sided linedef so we should only check one side + // so that the thrust from both sides doesn't cancel each other out. + // Best use the one facing the player and ignore the back side. + if (ld->sidedef[1] != NULL) + { + int side = P_PointOnLineSide(mobj->x, mobj->y, ld); + if (ld->sidedef[side] != sd) + { + continue; + } + } + ThrustMobj (mobj, sd); blocked = true; } } @@ -1217,461 +1318,70 @@ static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po) //========================================================================== // -// InitBlockMap +// LinkPolyobj // //========================================================================== -static void InitBlockMap (void) +void FPolyObj::LinkPolyobj () { - int i; + polyblock_t **link; + polyblock_t *tempLink; - PolyBlockMap = new polyblock_t *[bmapwidth*bmapheight]; - memset (PolyBlockMap, 0, bmapwidth*bmapheight*sizeof(polyblock_t *)); - - for (i = 0; i < po_NumPolyobjs; i++) + // calculate the polyobj bbox + Bounds.ClearBox(); + for(unsigned i = 0; i < Sidedefs.Size(); i++) { - LinkPolyobj(&polyobjs[i]); - } -} - -//========================================================================== -// -// InitSegLists [RH] -// -// Group segs by vertex and collect segs that are known to belong to a -// polyobject so that they can be initialized fast. -//========================================================================== - -static void InitSegLists () -{ - SDWORD i; - - SegListHead = new SDWORD[numvertexes]; - - clearbuf (SegListHead, numvertexes, -1); - - for (i = 0; i < numsegs; ++i) - { - if (segs[i].linedef != NULL) - { - SegListHead[segs[i].v1 - vertexes] = i; - if ((segs[i].linedef->special == Polyobj_StartLine || - segs[i].linedef->special == Polyobj_ExplicitLine)) - { - KnownPolySegs.Push (i); - } - } - } -} - -//========================================================================== -// -// KilSegLists [RH] -// -//========================================================================== - -static void KillSegLists () -{ - delete[] SegListHead; - SegListHead = NULL; - KnownPolySegs.Clear (); - KnownPolySegs.ShrinkToFit (); -} - -//========================================================================== -// -// IterFindPolySegs -// -// Passing NULL for segList will cause IterFindPolySegs to count the -// number of segs in the polyobj. v1 is the vertex to stop at, and v2 -// is the vertex to start at. -//========================================================================== - -static void IterFindPolySegs (vertex_t *v1, vertex_t *v2p, seg_t **segList) -{ - SDWORD j; - int v2 = int(v2p - vertexes); - int i; - - // This for loop exists solely to avoid infinitely looping on badly - // formed polyobjects. - for (i = 0; i < numsegs; i++) - { - j = SegListHead[v2]; - - if (j < 0) - { - break; - } - - if (segs[j].v1 == v1) - { - return; - } - - if (segs[j].linedef != NULL) - { - if (segList == NULL) - { - PolySegCount++; - } - else - { - *segList++ = &segs[j]; - segs[j].bPolySeg = true; - } - } - v2 = int(segs[j].v2 - vertexes); - } - I_Error ("IterFindPolySegs: Non-closed Polyobj around (%d,%d).\n", - v1->x >> FRACBITS, v1->y >> FRACBITS); -} - - -//========================================================================== -// -// SpawnPolyobj -// -//========================================================================== - -static void SpawnPolyobj (int index, int tag, int type) -{ - unsigned int ii; - int i; - int j; - - for (ii = 0; ii < KnownPolySegs.Size(); ++ii) - { - i = KnownPolySegs[ii]; - if (i < 0) - { - continue; - } + vertex_t *vt; - if (segs[i].linedef->special == Polyobj_StartLine && - segs[i].linedef->args[0] == tag) - { - if (polyobjs[index].segs) - { - I_Error ("SpawnPolyobj: Polyobj %d already spawned.\n", tag); - } - segs[i].linedef->special = 0; - segs[i].linedef->args[0] = 0; - segs[i].bPolySeg = true; - PolySegCount = 1; - IterFindPolySegs(segs[i].v1, segs[i].v2, NULL); - - polyobjs[index].numsegs = PolySegCount; - polyobjs[index].segs = new seg_t *[PolySegCount]; - polyobjs[index].segs[0] = &segs[i]; // insert the first seg - IterFindPolySegs (segs[i].v1, segs[i].v2, polyobjs[index].segs+1); - polyobjs[index].crush = (type != PO_SPAWN_TYPE) ? 3 : 0; - polyobjs[index].bHurtOnTouch = (type == PO_SPAWNHURT_TYPE); - polyobjs[index].tag = tag; - polyobjs[index].seqType = segs[i].linedef->args[2]; - if (polyobjs[index].seqType < 0 || polyobjs[index].seqType > 63) - { - polyobjs[index].seqType = 0; - } - break; - } + vt = Sidedefs[i]->linedef->v1; + Bounds.AddToBox(vt->x, vt->y); + vt = Sidedefs[i]->linedef->v2; + Bounds.AddToBox(vt->x, vt->y); } - if (!polyobjs[index].segs) - { // didn't find a polyobj through PO_LINE_START - TArray polySegList; - unsigned int psIndexOld; - polyobjs[index].numsegs = 0; - for (j = 1; j < PO_MAXPOLYSEGS; j++) + bbox[BOXRIGHT] = (Bounds.Right() - bmaporgx) >> MAPBLOCKSHIFT; + bbox[BOXLEFT] = (Bounds.Left() - bmaporgx) >> MAPBLOCKSHIFT; + bbox[BOXTOP] = (Bounds.Top() - bmaporgy) >> MAPBLOCKSHIFT; + bbox[BOXBOTTOM] = (Bounds.Bottom() - bmaporgy) >> MAPBLOCKSHIFT; + // add the polyobj to each blockmap section + for(int j = bbox[BOXBOTTOM]*bmapwidth; j <= bbox[BOXTOP]*bmapwidth; + j += bmapwidth) + { + for(int i = bbox[BOXLEFT]; i <= bbox[BOXRIGHT]; i++) { - psIndexOld = polySegList.Size(); - for (ii = 0; ii < KnownPolySegs.Size(); ++ii) + if(i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight*bmapwidth) { - i = KnownPolySegs[ii]; - - if (i >= 0 && - segs[i].linedef->special == Polyobj_ExplicitLine && - segs[i].linedef->args[0] == tag) + link = &PolyBlockMap[j+i]; + if(!(*link)) + { // Create a new link at the current block cell + *link = new polyblock_t; + (*link)->next = NULL; + (*link)->prev = NULL; + (*link)->polyobj = this; + continue; + } + else { - if (!segs[i].linedef->args[1]) + tempLink = *link; + while(tempLink->next != NULL && tempLink->polyobj != NULL) { - I_Error ("SpawnPolyobj: Explicit line missing order number (probably %d) in poly %d.\n", - j+1, tag); - } - if (segs[i].linedef->args[1] == j) - { - polySegList.Push (&segs[i]); - polyobjs[index].numsegs++; + tempLink = tempLink->next; } } - } - // Clear out any specials for these segs...we cannot clear them out - // in the above loop, since we aren't guaranteed one seg per linedef. - for (ii = 0; ii < KnownPolySegs.Size(); ++ii) - { - i = KnownPolySegs[ii]; - if (i >= 0 && - segs[i].linedef->special == Polyobj_ExplicitLine && - segs[i].linedef->args[0] == tag && segs[i].linedef->args[1] == j) + if(tempLink->polyobj == NULL) { - segs[i].linedef->special = 0; - segs[i].linedef->args[0] = 0; - segs[i].bPolySeg = true; - KnownPolySegs[ii] = -1; - } - } - if (polySegList.Size() == psIndexOld) - { // Check if an explicit line order has been skipped. - // A line has been skipped if there are any more explicit - // lines with the current tag value. [RH] Can this actually happen? - for (ii = 0; ii < KnownPolySegs.Size(); ++ii) - { - i = KnownPolySegs[ii]; - if (i >= 0 && - segs[i].linedef->special == Polyobj_ExplicitLine && - segs[i].linedef->args[0] == tag) - { - I_Error ("SpawnPolyobj: Missing explicit line %d for poly %d\n", - j, tag); - } + tempLink->polyobj = this; + continue; + } + else + { + tempLink->next = new polyblock_t; + tempLink->next->next = NULL; + tempLink->next->prev = tempLink; + tempLink->next->polyobj = this; } } + // else, don't link the polyobj, since it's off the map } - if (polyobjs[index].numsegs) - { - PolySegCount = polyobjs[index].numsegs; // PolySegCount used globally - polyobjs[index].crush = (type != PO_SPAWN_TYPE) ? 3 : 0; - polyobjs[index].bHurtOnTouch = (type == PO_SPAWNHURT_TYPE); - polyobjs[index].tag = tag; - polyobjs[index].segs = new seg_t *[polyobjs[index].numsegs]; - for (i = 0; i < polyobjs[index].numsegs; i++) - { - polyobjs[index].segs[i] = polySegList[i]; - } - polyobjs[index].seqType = (*polyobjs[index].segs)->linedef->args[3]; - // Next, change the polyobj's first line to point to a mirror - // if it exists - (*polyobjs[index].segs)->linedef->args[1] = - (*polyobjs[index].segs)->linedef->args[2]; - } - else - I_Error ("SpawnPolyobj: Poly %d does not exist\n", tag); - } - - TArray lines; - TArray vertices; - - for(int i=0; ilinedef; - int j; - - for(j = lines.Size() - 1; j >= 0; j--) - { - if (lines[j] == l) break; - } - if (j < 0) lines.Push(l); - - vertex_t *v = polyobjs[index].segs[i]->v1; - - for(j = vertices.Size() - 1; j >= 0; j--) - { - if (vertices[j] == v) break; - } - if (j < 0) vertices.Push(v); - - v = polyobjs[index].segs[i]->v2; - - for(j = vertices.Size() - 1; j >= 0; j--) - { - if (vertices[j] == v) break; - } - if (j < 0) vertices.Push(v); - } - polyobjs[index].numlines = lines.Size(); - polyobjs[index].lines = new line_t*[lines.Size()]; - memcpy(polyobjs[index].lines, &lines[0], sizeof(lines[0]) * lines.Size()); - - polyobjs[index].numvertices = vertices.Size(); - polyobjs[index].vertices = new vertex_t*[vertices.Size()]; - memcpy(polyobjs[index].vertices, &vertices[0], sizeof(vertices[0]) * vertices.Size()); -} - -//========================================================================== -// -// TranslateToStartSpot -// -//========================================================================== - -static void TranslateToStartSpot (int tag, int originX, int originY) -{ - seg_t **tempSeg; - seg_t **veryTempSeg; - vertex_t *tempPt; - subsector_t *sub; - FPolyObj *po; - int deltaX; - int deltaY; - vertex_t avg; // used to find a polyobj's center, and hence subsector - int i; - - po = NULL; - for (i = 0; i < po_NumPolyobjs; i++) - { - if (polyobjs[i].tag == tag) - { - po = &polyobjs[i]; - break; - } - } - if (po == NULL) - { // didn't match the tag with a polyobj tag - I_Error("TranslateToStartSpot: Unable to match polyobj tag: %d\n", - tag); - } - if (po->segs == NULL) - { - I_Error ("TranslateToStartSpot: Anchor point located without a StartSpot point: %d\n", tag); - } - po->originalPts = new vertex_t[po->numsegs]; - po->prevPts = new vertex_t[po->numsegs]; - deltaX = originX-po->startSpot[0]; - deltaY = originY-po->startSpot[1]; - - tempSeg = po->segs; - tempPt = po->originalPts; - avg.x = 0; - avg.y = 0; - - validcount++; - for (i = 0; i < po->numsegs; i++, tempSeg++, tempPt++) - { - (*tempSeg)->bPolySeg = true; // this is not set for all segs - (*tempSeg)->sidedef->Flags |= WALLF_POLYOBJ; - if ((*tempSeg)->linedef->validcount != validcount) - { - (*tempSeg)->linedef->bbox[BOXTOP] -= deltaY; - (*tempSeg)->linedef->bbox[BOXBOTTOM] -= deltaY; - (*tempSeg)->linedef->bbox[BOXLEFT] -= deltaX; - (*tempSeg)->linedef->bbox[BOXRIGHT] -= deltaX; - (*tempSeg)->linedef->validcount = validcount; - } - for (veryTempSeg = po->segs; veryTempSeg != tempSeg; veryTempSeg++) - { - if((*veryTempSeg)->v1 == (*tempSeg)->v1) - { - break; - } - } - if (veryTempSeg == tempSeg) - { // the point hasn't been translated, yet - (*tempSeg)->v1->x -= deltaX; - (*tempSeg)->v1->y -= deltaY; - } - avg.x += (*tempSeg)->v1->x >> FRACBITS; - avg.y += (*tempSeg)->v1->y >> FRACBITS; - // the original Pts are based off the startSpot Pt, and are - // unique to each seg, not each linedef - tempPt->x = (*tempSeg)->v1->x-po->startSpot[0]; - tempPt->y = (*tempSeg)->v1->y-po->startSpot[1]; - } - // Put polyobj in its subsector. - avg.x /= po->numsegs; - avg.y /= po->numsegs; - sub = R_PointInSubsector (avg.x << FRACBITS, avg.y << FRACBITS); - if (sub->poly != NULL) - { - I_Error ("PO_TranslateToStartSpot: Multiple polyobjs in a single subsector.\n"); - } - sub->poly = po; -} - -//========================================================================== -// -// PO_Init -// -//========================================================================== - -void PO_Init (void) -{ - // [RH] Hexen found the polyobject-related things by reloading the map's - // THINGS lump here and scanning through it. I have P_SpawnMapThing() - // record those things instead, so that in here we simply need to - // look at the polyspawns list. - polyspawns_t *polyspawn, **prev; - int polyIndex; - - // [RH] Make this faster - InitSegLists (); - - polyobjs = new FPolyObj[po_NumPolyobjs]; - memset (polyobjs, 0, po_NumPolyobjs*sizeof(FPolyObj)); - - polyIndex = 0; // index polyobj number - // Find the startSpot points, and spawn each polyobj - for (polyspawn = polyspawns, prev = &polyspawns; polyspawn;) - { - // 9301 (3001) = no crush, 9302 (3002) = crushing, 9303 = hurting touch - if (polyspawn->type == PO_SPAWN_TYPE || - polyspawn->type == PO_SPAWNCRUSH_TYPE || - polyspawn->type == PO_SPAWNHURT_TYPE) - { // Polyobj StartSpot Pt. - polyobjs[polyIndex].startSpot[0] = polyspawn->x; - polyobjs[polyIndex].startSpot[1] = polyspawn->y; - SpawnPolyobj(polyIndex, polyspawn->angle, polyspawn->type); - polyIndex++; - *prev = polyspawn->next; - delete polyspawn; - polyspawn = *prev; - } else { - prev = &polyspawn->next; - polyspawn = polyspawn->next; - } - } - for (polyspawn = polyspawns; polyspawn;) - { - polyspawns_t *next = polyspawn->next; - if (polyspawn->type == PO_ANCHOR_TYPE) - { // Polyobj Anchor Pt. - TranslateToStartSpot (polyspawn->angle, polyspawn->x, polyspawn->y); - } - delete polyspawn; - polyspawn = next; - } - polyspawns = NULL; - - // check for a startspot without an anchor point - for (polyIndex = 0; polyIndex < po_NumPolyobjs; polyIndex++) - { - if (!polyobjs[polyIndex].originalPts) - { - I_Error ("PO_Init: StartSpot located without an Anchor point: %d\n", - polyobjs[polyIndex].tag); - } - } - InitBlockMap(); - - // [RH] Don't need the seg lists anymore - KillSegLists (); -} - -//========================================================================== -// -// PO_Busy -// -//========================================================================== - -bool PO_Busy (int polyobj) -{ - FPolyObj *poly; - - poly = PO_GetPolyobj (polyobj); - if (poly == NULL || poly->specialdata == NULL) - { - return false; - } - else - { - return true; } } @@ -1684,18 +1394,18 @@ bool PO_Busy (int polyobj) // //=========================================================================== -void PO_ClosestPoint(const FPolyObj *poly, fixed_t fx, fixed_t fy, fixed_t &ox, fixed_t &oy, seg_t **seg) +void FPolyObj::ClosestPoint(fixed_t fx, fixed_t fy, fixed_t &ox, fixed_t &oy, side_t **side) const { - int i; + unsigned int i; double x = fx, y = fy; double bestdist = HUGE_VAL; double bestx = 0, besty = 0; - seg_t *bestseg = NULL; + side_t *bestline = NULL; - for (i = 0; i < poly->numsegs; ++i) + for (i = 0; i < Sidedefs.Size(); ++i) { - vertex_t *v1 = poly->segs[i]->v1; - vertex_t *v2 = poly->segs[i]->v2; + vertex_t *v1 = Sidedefs[i]->V1(); + vertex_t *v2 = Sidedefs[i]->V2(); double a = v2->x - v1->x; double b = v2->y - v1->y; double den = a*a + b*b; @@ -1734,42 +1444,874 @@ void PO_ClosestPoint(const FPolyObj *poly, fixed_t fx, fixed_t fy, fixed_t &ox, bestdist = dist; bestx = ix; besty = iy; - bestseg = poly->segs[i]; + bestline = Sidedefs[i]; } } ox = fixed_t(bestx); oy = fixed_t(besty); - if (seg != NULL) + if (side != NULL) { - *seg = bestseg; + *side = bestline; } } -FPolyObj::~FPolyObj() +//========================================================================== +// +// InitBlockMap +// +//========================================================================== + +static void InitBlockMap (void) { - if (segs != NULL) + int i; + + PolyBlockMap = new polyblock_t *[bmapwidth*bmapheight]; + memset (PolyBlockMap, 0, bmapwidth*bmapheight*sizeof(polyblock_t *)); + + for (i = 0; i < po_NumPolyobjs; i++) { - delete[] segs; - segs = NULL; - } - if (lines != NULL) - { - delete[] lines; - lines = NULL; - } - if (vertices != NULL) - { - delete[] vertices; - vertices = NULL; - } - if (originalPts != NULL) - { - delete[] originalPts; - originalPts = NULL; - } - if (prevPts != NULL) - { - delete[] prevPts; - prevPts = NULL; + polyobjs[i].LinkPolyobj(); + } +} + +//========================================================================== +// +// InitSideLists [RH] +// +// Group sides by vertex and collect side that are known to belong to a +// polyobject so that they can be initialized fast. +//========================================================================== + +static void InitSideLists () +{ + for (int i = 0; i < numsides; ++i) + { + if (sides[i].linedef != NULL && + (sides[i].linedef->special == Polyobj_StartLine || + sides[i].linedef->special == Polyobj_ExplicitLine)) + { + KnownPolySides.Push (i); + } + } +} + +//========================================================================== +// +// KillSideLists [RH] +// +//========================================================================== + +static void KillSideLists () +{ + KnownPolySides.Clear (); + KnownPolySides.ShrinkToFit (); +} + +//========================================================================== +// +// AddPolyVert +// +// Helper function for IterFindPolySides() +// +//========================================================================== + +static void AddPolyVert(TArray &vnum, DWORD vert) +{ + for (unsigned int i = vnum.Size() - 1; i-- != 0; ) + { + if (vnum[i] == vert) + { // Already in the set. No need to add it. + return; + } + } + vnum.Push(vert); +} + +//========================================================================== +// +// IterFindPolySides +// +// Beginning with the first vertex of the starting side, for each vertex +// in vnum, add all the sides that use it as a first vertex to the polyobj, +// and add all their second vertices to vnum. This continues until there +// are no new vertices in vnum. +// +//========================================================================== + +static void IterFindPolySides (FPolyObj *po, side_t *side) +{ + static TArray vnum; + unsigned int vnumat; + + assert(sidetemp != NULL); + + vnum.Clear(); + vnum.Push(DWORD(side->V1() - vertexes)); + vnumat = 0; + + while (vnum.Size() != vnumat) + { + DWORD sidenum = sidetemp[vnum[vnumat++]].b.first; + while (sidenum != NO_SIDE) + { + po->Sidedefs.Push(&sides[sidenum]); + AddPolyVert(vnum, DWORD(sides[sidenum].V2() - vertexes)); + sidenum = sidetemp[sidenum].b.next; + } + } +} + + +//========================================================================== +// +// SpawnPolyobj +// +//========================================================================== + +static void SpawnPolyobj (int index, int tag, int type) +{ + unsigned int ii; + int i; + int j; + FPolyObj *po = &polyobjs[index]; + + for (ii = 0; ii < KnownPolySides.Size(); ++ii) + { + i = KnownPolySides[ii]; + if (i < 0) + { + continue; + } + + side_t *sd = &sides[i]; + + if (sd->linedef->special == Polyobj_StartLine && + sd->linedef->args[0] == tag) + { + if (po->Sidedefs.Size() > 0) + { + I_Error ("SpawnPolyobj: Polyobj %d already spawned.\n", tag); + } + sd->linedef->special = 0; + sd->linedef->args[0] = 0; + IterFindPolySides(&polyobjs[index], sd); + po->crush = (type != PO_SPAWN_TYPE) ? 3 : 0; + po->bHurtOnTouch = (type == PO_SPAWNHURT_TYPE); + po->tag = tag; + po->seqType = sd->linedef->args[2]; + if (po->seqType < 0 || po->seqType > 63) + { + po->seqType = 0; + } + break; + } + } + if (po->Sidedefs.Size() == 0) + { + // didn't find a polyobj through PO_LINE_START + TArray polySideList; + unsigned int psIndexOld; + for (j = 1; j < PO_MAXPOLYSEGS; j++) + { + psIndexOld = po->Sidedefs.Size(); + for (ii = 0; ii < KnownPolySides.Size(); ++ii) + { + i = KnownPolySides[ii]; + + if (i >= 0 && + sides[i].linedef->special == Polyobj_ExplicitLine && + sides[i].linedef->args[0] == tag) + { + if (!sides[i].linedef->args[1]) + { + I_Error ("SpawnPolyobj: Explicit line missing order number (probably %d) in poly %d.\n", + j+1, tag); + } + if (sides[i].linedef->args[1] == j) + { + po->Sidedefs.Push (&sides[i]); + } + } + } + // Clear out any specials for these segs...we cannot clear them out + // in the above loop, since we aren't guaranteed one seg per linedef. + for (ii = 0; ii < KnownPolySides.Size(); ++ii) + { + i = KnownPolySides[ii]; + if (i >= 0 && + sides[i].linedef->special == Polyobj_ExplicitLine && + sides[i].linedef->args[0] == tag && sides[i].linedef->args[1] == j) + { + sides[i].linedef->special = 0; + sides[i].linedef->args[0] = 0; + KnownPolySides[ii] = -1; + } + } + if (po->Sidedefs.Size() == psIndexOld) + { // Check if an explicit line order has been skipped. + // A line has been skipped if there are any more explicit + // lines with the current tag value. [RH] Can this actually happen? + for (ii = 0; ii < KnownPolySides.Size(); ++ii) + { + i = KnownPolySides[ii]; + if (i >= 0 && + sides[i].linedef->special == Polyobj_ExplicitLine && + sides[i].linedef->args[0] == tag) + { + I_Error ("SpawnPolyobj: Missing explicit line %d for poly %d\n", + j, tag); + } + } + } + } + if (po->Sidedefs.Size() > 0) + { + po->crush = (type != PO_SPAWN_TYPE) ? 3 : 0; + po->bHurtOnTouch = (type == PO_SPAWNHURT_TYPE); + po->tag = tag; + po->seqType = po->Sidedefs[0]->linedef->args[3]; + // Next, change the polyobj's first line to point to a mirror + // if it exists + po->Sidedefs[0]->linedef->args[1] = + po->Sidedefs[0]->linedef->args[2]; + } + else + I_Error ("SpawnPolyobj: Poly %d does not exist\n", tag); + } + + validcount++; + for(unsigned int i=0; iSidedefs.Size(); i++) + { + line_t *l = po->Sidedefs[i]->linedef; + + if (l->validcount != validcount) + { + l->validcount = validcount; + po->Linedefs.Push(l); + + vertex_t *v = l->v1; + int j; + for(j = po->Vertices.Size() - 1; j >= 0; j--) + { + if (po->Vertices[j] == v) break; + } + if (j < 0) po->Vertices.Push(v); + + v = l->v2; + for(j = po->Vertices.Size() - 1; j >= 0; j--) + { + if (po->Vertices[j] == v) break; + } + if (j < 0) po->Vertices.Push(v); + + } + } + po->Sidedefs.ShrinkToFit(); + po->Linedefs.ShrinkToFit(); + po->Vertices.ShrinkToFit(); +} + +//========================================================================== +// +// TranslateToStartSpot +// +//========================================================================== + +static void TranslateToStartSpot (int tag, int originX, int originY) +{ + FPolyObj *po; + int deltaX; + int deltaY; + + po = NULL; + for (int i = 0; i < po_NumPolyobjs; i++) + { + if (polyobjs[i].tag == tag) + { + po = &polyobjs[i]; + break; + } + } + if (po == NULL) + { // didn't match the tag with a polyobj tag + I_Error("TranslateToStartSpot: Unable to match polyobj tag: %d\n", tag); + } + if (po->Sidedefs.Size() == 0) + { + I_Error ("TranslateToStartSpot: Anchor point located without a StartSpot point: %d\n", tag); + } + po->OriginalPts.Resize(po->Sidedefs.Size()); + po->PrevPts.Resize(po->Sidedefs.Size()); + deltaX = originX - po->StartSpot.x; + deltaY = originY - po->StartSpot.y; + + for (unsigned i = 0; i < po->Sidedefs.Size(); i++) + { + po->Sidedefs[i]->Flags |= WALLF_POLYOBJ; + } + for (unsigned i = 0; i < po->Linedefs.Size(); i++) + { + po->Linedefs[i]->bbox[BOXTOP] -= deltaY; + po->Linedefs[i]->bbox[BOXBOTTOM] -= deltaY; + po->Linedefs[i]->bbox[BOXLEFT] -= deltaX; + po->Linedefs[i]->bbox[BOXRIGHT] -= deltaX; + } + for (unsigned i = 0; i < po->Vertices.Size(); i++) + { + po->Vertices[i]->x -= deltaX; + po->Vertices[i]->y -= deltaY; + po->OriginalPts[i].x = po->Vertices[i]->x - po->StartSpot.x; + po->OriginalPts[i].y = po->Vertices[i]->y - po->StartSpot.y; + } + po->CalcCenter(); + // For compatibility purposes + po->CenterSubsector = R_PointInSubsector(po->CenterSpot.x, po->CenterSpot.y); +} + +//========================================================================== +// +// PO_Init +// +//========================================================================== + +void PO_Init (void) +{ + // [RH] Hexen found the polyobject-related things by reloading the map's + // THINGS lump here and scanning through it. I have P_SpawnMapThing() + // record those things instead, so that in here we simply need to + // look at the polyspawns list. + polyspawns_t *polyspawn, **prev; + int polyIndex; + + // [RH] Make this faster + InitSideLists (); + + polyobjs = new FPolyObj[po_NumPolyobjs]; + + polyIndex = 0; // index polyobj number + // Find the startSpot points, and spawn each polyobj + for (polyspawn = polyspawns, prev = &polyspawns; polyspawn;) + { + // 9301 (3001) = no crush, 9302 (3002) = crushing, 9303 = hurting touch + if (polyspawn->type == PO_SPAWN_TYPE || + polyspawn->type == PO_SPAWNCRUSH_TYPE || + polyspawn->type == PO_SPAWNHURT_TYPE) + { + // Polyobj StartSpot Pt. + polyobjs[polyIndex].StartSpot.x = polyspawn->x; + polyobjs[polyIndex].StartSpot.y = polyspawn->y; + SpawnPolyobj(polyIndex, polyspawn->angle, polyspawn->type); + polyIndex++; + *prev = polyspawn->next; + delete polyspawn; + polyspawn = *prev; + } + else + { + prev = &polyspawn->next; + polyspawn = polyspawn->next; + } + } + for (polyspawn = polyspawns; polyspawn;) + { + polyspawns_t *next = polyspawn->next; + if (polyspawn->type == PO_ANCHOR_TYPE) + { + // Polyobj Anchor Pt. + TranslateToStartSpot (polyspawn->angle, polyspawn->x, polyspawn->y); + } + delete polyspawn; + polyspawn = next; + } + polyspawns = NULL; + + // check for a startspot without an anchor point + for (polyIndex = 0; polyIndex < po_NumPolyobjs; polyIndex++) + { + if (polyobjs[polyIndex].OriginalPts.Size() == 0) + { + I_Error ("PO_Init: StartSpot located without an Anchor point: %d\n", + polyobjs[polyIndex].tag); + } + } + InitBlockMap(); + + // [RH] Don't need the seg lists anymore + KillSideLists (); + + for(int i=0;idx; + double fdy = (double)no->dy; + no->len = (float)sqrt(fdx * fdx + fdy * fdy); + } + + // mark all subsectors which have a seg belonging to a polyobj + // These ones should not be rendered on the textured automap. + for (int i = 0; i < numsubsectors; i++) + { + subsector_t *ss = &subsectors[i]; + for(DWORD j=0;jnumlines; j++) + { + if (ss->firstline[j].sidedef != NULL && + ss->firstline[j].sidedef->Flags & WALLF_POLYOBJ) + { + ss->flags |= SSECF_POLYORG; + break; + } + } + } + +} + +//========================================================================== +// +// PO_Busy +// +//========================================================================== + +bool PO_Busy (int polyobj) +{ + FPolyObj *poly; + + poly = PO_GetPolyobj (polyobj); + return (poly != NULL && poly->specialdata != NULL); +} + + + +//========================================================================== +// +// +// +//========================================================================== + +void FPolyObj::ClearSubsectorLinks() +{ + while (subsectorlinks != NULL) + { + assert(subsectorlinks->state == 1337); + + FPolyNode *next = subsectorlinks->snext; + + if (subsectorlinks->pnext != NULL) + { + assert(subsectorlinks->pnext->state == 1337); + subsectorlinks->pnext->pprev = subsectorlinks->pprev; + } + + if (subsectorlinks->pprev != NULL) + { + assert(subsectorlinks->pprev->state == 1337); + subsectorlinks->pprev->pnext = subsectorlinks->pnext; + } + else + { + subsectorlinks->subsector->polys = subsectorlinks->pnext; + } + + if (subsectorlinks->subsector->BSP != NULL) + { + subsectorlinks->subsector->BSP->bDirty = true; + } + + subsectorlinks->state = -1; + delete subsectorlinks; + subsectorlinks = next; + } + subsectorlinks = NULL; +} + +void FPolyObj::ClearAllSubsectorLinks() +{ + for (int i = 0; i < po_NumPolyobjs; i++) + { + polyobjs[i].ClearSubsectorLinks(); + } + ReleaseAllPolyNodes(); +} + +//========================================================================== +// +// GetIntersection +// +// adapted from P_InterceptVector +// +//========================================================================== + +static bool GetIntersection(FPolySeg *seg, node_t *bsp, FPolyVertex *v) +{ + double frac; + double num; + double den; + + double v2x = (double)seg->v1.x; + double v2y = (double)seg->v1.y; + double v2dx = (double)(seg->v2.x - seg->v1.x); + double v2dy = (double)(seg->v2.y - seg->v1.y); + double v1x = (double)bsp->x; + double v1y = (double)bsp->y; + double v1dx = (double)bsp->dx; + double v1dy = (double)bsp->dy; + + den = v1dy*v2dx - v1dx*v2dy; + + if (den == 0) + return false; // parallel + + num = (v1x - v2x)*v1dy + (v2y - v1y)*v1dx; + frac = num / den; + + if (frac < 0. || frac > 1.) return false; + + v->x = xs_RoundToInt(v2x + frac * v2dx); + v->y = xs_RoundToInt(v2y + frac * v2dy); + return true; +} + +//========================================================================== +// +// PartitionDistance +// +// Determine the distance of a vertex to a node's partition line. +// +//========================================================================== + +static double PartitionDistance(FPolyVertex *vt, node_t *node) +{ + return fabs(double(-node->dy) * (vt->x - node->x) + double(node->dx) * (vt->y - node->y)) / node->len; +} + +//========================================================================== +// +// AddToBBox +// +//========================================================================== + +static void AddToBBox(fixed_t child[4], fixed_t parent[4]) +{ + if (child[BOXTOP] > parent[BOXTOP]) + { + parent[BOXTOP] = child[BOXTOP]; + } + if (child[BOXBOTTOM] < parent[BOXBOTTOM]) + { + parent[BOXBOTTOM] = child[BOXBOTTOM]; + } + if (child[BOXLEFT] < parent[BOXLEFT]) + { + parent[BOXLEFT] = child[BOXLEFT]; + } + if (child[BOXRIGHT] > parent[BOXRIGHT]) + { + parent[BOXRIGHT] = child[BOXRIGHT]; + } +} + +//========================================================================== +// +// AddToBBox +// +//========================================================================== + +static void AddToBBox(FPolyVertex *v, fixed_t bbox[4]) +{ + if (v->x < bbox[BOXLEFT]) + { + bbox[BOXLEFT] = v->x; + } + if (v->x > bbox[BOXRIGHT]) + { + bbox[BOXRIGHT] = v->x; + } + if (v->y < bbox[BOXBOTTOM]) + { + bbox[BOXBOTTOM] = v->y; + } + if (v->y > bbox[BOXTOP]) + { + bbox[BOXTOP] = v->y; + } +} + +//========================================================================== +// +// SplitPoly +// +//========================================================================== + +static void SplitPoly(FPolyNode *pnode, void *node, fixed_t bbox[4]) +{ + static TArray lists[2]; + static const double POLY_EPSILON = 0.3125; + + if (!((size_t)node & 1)) // Keep going until found a subsector + { + node_t *bsp = (node_t *)node; + + int centerside = R_PointOnSide(pnode->poly->CenterSpot.x, pnode->poly->CenterSpot.y, bsp); + + lists[0].Clear(); + lists[1].Clear(); + for(unsigned i=0;isegs.Size(); i++) + { + FPolySeg *seg = &pnode->segs[i]; + + // Parts of the following code were taken from Eternity and are + // being used with permission. + + // get distance of vertices from partition line + // If the distance is too small, we may decide to + // change our idea of sidedness. + double dist_v1 = PartitionDistance(&seg->v1, bsp); + double dist_v2 = PartitionDistance(&seg->v2, bsp); + + // If the distances are less than epsilon, consider the points as being + // on the same side as the polyobj origin. Why? People like to build + // polyobject doors flush with their door tracks. This breaks using the + // usual assumptions. + + + // Addition to Eternity code: We must also check any seg with only one + // vertex inside the epsilon threshold. If not, these lines will get split but + // adjoining ones with both vertices inside the threshold won't thus messing up + // the order in which they get drawn. + + if(dist_v1 <= POLY_EPSILON) + { + if (dist_v2 <= POLY_EPSILON) + { + lists[centerside].Push(*seg); + } + else + { + int side = R_PointOnSide(seg->v2.x, seg->v2.y, bsp); + lists[side].Push(*seg); + } + } + else if (dist_v2 <= POLY_EPSILON) + { + int side = R_PointOnSide(seg->v1.x, seg->v1.y, bsp); + lists[side].Push(*seg); + } + else + { + int side1 = R_PointOnSide(seg->v1.x, seg->v1.y, bsp); + int side2 = R_PointOnSide(seg->v2.x, seg->v2.y, bsp); + + if(side1 != side2) + { + // if the partition line crosses this seg, we must split it. + + FPolyVertex vert; + + if (GetIntersection(seg, bsp, &vert)) + { + lists[0].Push(*seg); + lists[1].Push(*seg); + lists[side1].Last().v2 = vert; + lists[side2].Last().v1 = vert; + } + else + { + // should never happen + lists[side1].Push(*seg); + } + } + else + { + // both points on the same side. + lists[side1].Push(*seg); + } + } + } + if (lists[1].Size() == 0) + { + SplitPoly(pnode, bsp->children[0], bsp->bbox[0]); + AddToBBox(bsp->bbox[0], bbox); + } + else if (lists[0].Size() == 0) + { + SplitPoly(pnode, bsp->children[1], bsp->bbox[1]); + AddToBBox(bsp->bbox[1], bbox); + } + else + { + // create the new node + FPolyNode *newnode = NewPolyNode(); + newnode->poly = pnode->poly; + newnode->segs = lists[1]; + + // set segs for original node + pnode->segs = lists[0]; + + // recurse back side + SplitPoly(newnode, bsp->children[1], bsp->bbox[1]); + + // recurse front side + SplitPoly(pnode, bsp->children[0], bsp->bbox[0]); + + AddToBBox(bsp->bbox[0], bbox); + AddToBBox(bsp->bbox[1], bbox); + } + } + else + { + // we reached a subsector so we can link the node with this subsector + subsector_t *sub = (subsector_t *)((BYTE *)node - 1); + + // Link node to subsector + pnode->pnext = sub->polys; + if (pnode->pnext != NULL) + { + assert(pnode->pnext->state == 1337); + pnode->pnext->pprev = pnode; + } + pnode->pprev = NULL; + sub->polys = pnode; + + // link node to polyobject + pnode->snext = pnode->poly->subsectorlinks; + pnode->poly->subsectorlinks = pnode; + pnode->subsector = sub; + + // calculate bounding box for this polynode + assert(pnode->segs.Size() != 0); + fixed_t subbbox[4] = { FIXED_MIN, FIXED_MAX, FIXED_MAX, FIXED_MIN }; + + for (unsigned i = 0; i < pnode->segs.Size(); ++i) + { + AddToBBox(&pnode->segs[i].v1, subbbox); + AddToBBox(&pnode->segs[i].v2, subbbox); + } + // Potentially expand the parent node's bounding box to contain these bits of polyobject. + AddToBBox(subbbox, bbox); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FPolyObj::CreateSubsectorLinks() +{ + FPolyNode *node = NewPolyNode(); + // Even though we don't care about it, we need to initialize this + // bounding box to something so that Valgrind won't complain about it + // when SplitPoly modifies it. + fixed_t dummybbox[4] = { 0 }; + + node->poly = this; + node->segs.Resize(Sidedefs.Size()); + + for(unsigned i=0; isegs[i]; + side_t *side = Sidedefs[i]; + + seg->v1 = side->V1(); + seg->v2 = side->V2(); + seg->wall = side; + } + if (!(i_compatflags & COMPATF_POLYOBJ)) + { + SplitPoly(node, nodes + numnodes - 1, dummybbox); + } + else + { + subsector_t *sub = CenterSubsector; + + // Link node to subsector + node->pnext = sub->polys; + if (node->pnext != NULL) + { + assert(node->pnext->state == 1337); + node->pnext->pprev = node; + } + node->pprev = NULL; + sub->polys = node; + + // link node to polyobject + node->snext = node->poly->subsectorlinks; + node->poly->subsectorlinks = node; + node->subsector = sub; + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void PO_LinkToSubsectors() +{ + for (int i = 0; i < po_NumPolyobjs; i++) + { + if (polyobjs[i].subsectorlinks == NULL) + { + polyobjs[i].CreateSubsectorLinks(); + } + } +} + +//========================================================================== +// +// NewPolyNode +// +//========================================================================== + +static FPolyNode *NewPolyNode() +{ + FPolyNode *node; + + if (FreePolyNodes != NULL) + { + node = FreePolyNodes; + FreePolyNodes = node->pnext; + } + else + { + node = new FPolyNode; + } + node->state = 1337; + node->poly = NULL; + node->pnext = NULL; + node->pprev = NULL; + node->subsector = NULL; + node->snext = NULL; + return node; +} + +//========================================================================== +// +// FreePolyNode +// +//========================================================================== + +void FreePolyNode(FPolyNode *node) +{ + node->segs.Clear(); + node->pnext = FreePolyNodes; + FreePolyNodes = node; +} + +//========================================================================== +// +// ReleaseAllPolyNodes +// +//========================================================================== + +void ReleaseAllPolyNodes() +{ + FPolyNode *node, *next; + + for (node = FreePolyNodes; node != NULL; node = next) + { + next = node->pnext; + delete node; } } diff --git a/src/po_man.h b/src/po_man.h new file mode 100644 index 000000000..074426d88 --- /dev/null +++ b/src/po_man.h @@ -0,0 +1,116 @@ +#ifndef __PO_MAN_H +#define __PO_MAN_H + +#include "tarray.h" +#include "r_defs.h" +#include "m_bbox.h" + + +struct FPolyVertex +{ + fixed_t x, y; + + FPolyVertex &operator=(vertex_t *v) + { + x = v->x; + y = v->y; + return *this; + } +}; + +struct FPolySeg +{ + FPolyVertex v1; + FPolyVertex v2; + side_t *wall; +}; + +// +// Linked lists of polyobjects +// +struct FPolyObj; +struct FPolyNode +{ + FPolyObj *poly; // owning polyobject + FPolyNode *pnext; // next polyobj in list + FPolyNode *pprev; // previous polyobj + + subsector_t *subsector; // containimg subsector + FPolyNode *snext; // next subsector + + TArray segs; // segs for this node + int state; +}; + +// ===== Polyobj data ===== +struct FPolyObj +{ + TArray Sidedefs; + TArray Linedefs; + TArray Vertices; + TArray OriginalPts; + TArray PrevPts; + FPolyVertex StartSpot; + FPolyVertex CenterSpot; + FBoundingBox Bounds; // Bounds in map coordinates + subsector_t *CenterSubsector; + + angle_t angle; + int tag; // reference tag assigned in HereticEd + int bbox[4]; // bounds in blockmap coordinates + int validcount; + int crush; // should the polyobj attempt to crush mobjs? + bool bHurtOnTouch; // should the polyobj hurt anything it touches? + int seqType; + fixed_t size; // polyobj size (area of POLY_AREAUNIT == size of FRACUNIT) + FPolyNode *subsectorlinks; + DPolyAction *specialdata; // pointer to a thinker, if the poly is moving + TObjPtr interpolation; + + FPolyObj(); + DInterpolation *SetInterpolation(); + void StopInterpolation(); + + int GetMirror(); + bool MovePolyobj (int x, int y, bool force = false); + bool RotatePolyobj (angle_t angle); + void ClosestPoint(fixed_t fx, fixed_t fy, fixed_t &ox, fixed_t &oy, side_t **side) const; + void LinkPolyobj (); + void CreateSubsectorLinks(); + void ClearSubsectorLinks(); + void CalcCenter(); + static void ClearAllSubsectorLinks(); + +private: + + void ThrustMobj (AActor *actor, side_t *side); + void UpdateBBox (); + void DoMovePolyobj (int x, int y); + void UnLinkPolyobj (); + bool CheckMobjBlocking (side_t *sd); + +}; +extern FPolyObj *polyobjs; // list of all poly-objects on the level + +inline FArchive &operator<< (FArchive &arc, FPolyObj *&poly) +{ + return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); +} + +inline FArchive &operator<< (FArchive &arc, const FPolyObj *&poly) +{ + return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); +} + +struct polyblock_t +{ + FPolyObj *polyobj; + struct polyblock_t *prev; + struct polyblock_t *next; +}; + + +void PO_LinkToSubsectors(); + + +#endif \ No newline at end of file diff --git a/src/r_anim.cpp b/src/r_anim.cpp index 7daeafd83..cac361866 100644 --- a/src/r_anim.cpp +++ b/src/r_anim.cpp @@ -202,7 +202,7 @@ void R_InitPicAnims (void) // [RH] Allow for backward animations as well as forward. if (pic1 > pic2) { - swap (pic1, pic2); + swapvalues (pic1, pic2); animtype = FAnimDef::ANIM_Backward; } @@ -548,7 +548,7 @@ static void ParseRangeAnim (FScanner &sc, FTextureID picnum, int usetype, bool m { type = FAnimDef::ANIM_Backward; TexMan[framenum]->bNoDecals = TexMan[picnum]->bNoDecals; - swap (framenum, picnum); + swapvalues (framenum, picnum); } if (sc.GetString()) { diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index a1f6f6098..e7ba7c116 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -36,6 +36,7 @@ #include "i_system.h" #include "p_lnspec.h" +#include "p_setup.h" #include "r_main.h" #include "r_plane.h" @@ -43,6 +44,7 @@ #include "r_things.h" #include "a_sharedglobal.h" #include "g_level.h" +#include "nodebuild.h" // State. #include "doomstat.h" @@ -50,6 +52,7 @@ #include "r_bsp.h" #include "v_palette.h" #include "r_sky.h" +#include "po_man.h" int WallMost (short *mostbuf, const secplane_t &plane); @@ -107,6 +110,11 @@ WORD MirrorFlags; seg_t *ActiveWallMirror; TArray WallMirrors; +static FNodeBuilder::FLevel PolyNodeLevel; +static FNodeBuilder PolyNodeBuilder(PolyNodeLevel); + +static subsector_t *InSubsector; + CVAR (Bool, r_drawflat, false, 0) // [RH] Don't texture segs? @@ -162,10 +170,11 @@ static cliprange_t solidsegs[MAXWIDTH/2+2]; // //========================================================================== -void R_ClipWallSegment (int first, int last, bool solid) +bool R_ClipWallSegment (int first, int last, bool solid) { cliprange_t *next, *start; int i, j; + bool res = false; // Find the first range that touches the range // (adjacent pixels are touching). @@ -175,6 +184,7 @@ void R_ClipWallSegment (int first, int last, bool solid) if (first < start->first) { + res = true; if (last <= start->first) { // Post is entirely visible (above start). @@ -200,7 +210,7 @@ void R_ClipWallSegment (int first, int last, bool solid) next->last = last; } } - return; + return true; } // There is a fragment above *start. @@ -215,7 +225,7 @@ void R_ClipWallSegment (int first, int last, bool solid) // Bottom contained in start? if (last <= start->last) - return; + return res; next = start; while (last >= (next+1)->first) @@ -252,6 +262,31 @@ crunch: newend = start+i; } } + return true; +} + +bool R_CheckClipWallSegment (int first, int last) +{ + cliprange_t *start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first) + start++; + + if (first < start->first) + { + return true; + } + + // Bottom contained in start? + if (last > start->last) + { + return true; + } + + return false; } @@ -573,7 +608,7 @@ void R_AddLine (seg_t *line) int t = 256-WallTX1; WallTX1 = 256-WallTX2; WallTX2 = t; - swap (WallTY1, WallTY2); + swapvalues (WallTY1, WallTY2); } if (WallTX1 >= -WallTY1) @@ -621,6 +656,10 @@ void R_AddLine (seg_t *line) if (line->linedef == NULL) { + if (R_CheckClipWallSegment (WallSX1, WallSX2)) + { + InSubsector->flags |= SSECF_DRAWN; + } return; } @@ -650,7 +689,7 @@ void R_AddLine (seg_t *line) { // The seg is only part of the wall. if (line->linedef->sidedef[0] != line->sidedef) { - swap (v1, v2); + swapvalues (v1, v2); } tx1 = v1->x - viewx; tx2 = v2->x - viewx; @@ -787,6 +826,16 @@ void R_AddLine (seg_t *line) // Reject empty lines used for triggers and special events. // Identical floor and ceiling on both sides, identical light levels // on both sides, and no middle texture. + + // When using GL nodes, do a clipping test for these lines so we can + // mark their subsectors as visible for automap texturing. + if (hasglnodes && !(InSubsector->flags & SSECF_DRAWN)) + { + if (R_CheckClipWallSegment(WallSX1, WallSX2)) + { + InSubsector->flags |= SSECF_DRAWN; + } + } return; } } @@ -822,7 +871,10 @@ void R_AddLine (seg_t *line) #endif } - R_ClipWallSegment (WallSX1, WallSX2, solid); + if (R_ClipWallSegment (WallSX1, WallSX2, solid)) + { + InSubsector->flags |= SSECF_DRAWN; + } } @@ -900,7 +952,7 @@ static bool R_CheckBBox (fixed_t *bspcoord) // killough 1/28/98: static int t = 256-rx1; rx1 = 256-rx2; rx2 = t; - swap (ry1, ry2); + swapvalues (ry1, ry2); } if (rx1 >= -ry1) @@ -1006,6 +1058,78 @@ void R_GetExtraLight (int *light, const secplane_t &plane, FExtraLight *el) } } } + + + + +//========================================================================== +// +// FMiniBSP Constructor +// +//========================================================================== + +FMiniBSP::FMiniBSP() +{ + bDirty = false; +} + +//========================================================================== +// +// P_BuildPolyBSP +// +//========================================================================== + +void R_BuildPolyBSP(subsector_t *sub) +{ + assert((sub->BSP == NULL || sub->BSP->bDirty) && "BSP computed more than once"); + + // Set up level information for the node builder. + PolyNodeLevel.Sides = sides; + PolyNodeLevel.NumSides = numsides; + PolyNodeLevel.Lines = lines; + PolyNodeLevel.NumLines = numlines; + + // Feed segs to the nodebuilder and build the nodes. + PolyNodeBuilder.Clear(); + PolyNodeBuilder.AddSegs(sub->firstline, sub->numlines); + for (FPolyNode *pn = sub->polys; pn != NULL; pn = pn->pnext) + { + PolyNodeBuilder.AddPolySegs(&pn->segs[0], (int)pn->segs.Size()); + } + PolyNodeBuilder.BuildMini(false); + if (sub->BSP == NULL) + { + sub->BSP = new FMiniBSP; + } + else + { + sub->BSP->bDirty = false; + } + PolyNodeBuilder.ExtractMini(sub->BSP); + for (unsigned int i = 0; i < sub->BSP->Subsectors.Size(); ++i) + { + sub->BSP->Subsectors[i].sector = sub->sector; + } +} + +void R_Subsector (subsector_t *sub); +static void R_AddPolyobjs(subsector_t *sub) +{ + if (sub->BSP == NULL || sub->BSP->bDirty) + { + R_BuildPolyBSP(sub); + } + if (sub->BSP->Nodes.Size() == 0) + { + R_Subsector(&sub->BSP->Subsectors[0]); + } + else + { + R_RenderBSPNode(&sub->BSP->Nodes.Last()); + } +} + + // // R_Subsector // Determine floor/ceiling planes. @@ -1019,16 +1143,39 @@ void R_Subsector (subsector_t *sub) sector_t tempsec; // killough 3/7/98: deep water hack int floorlightlevel; // killough 3/16/98: set floor lightlevel int ceilinglightlevel; // killough 4/11/98 + bool outersubsector; + + if (InSubsector != NULL) + { // InSubsector is not NULL. This means we are rendering from a mini-BSP. + outersubsector = false; + } + else + { + outersubsector = true; + InSubsector = sub; + } #ifdef RANGECHECK - if (sub - subsectors >= (ptrdiff_t)numsubsectors) + if (outersubsector && sub - subsectors >= (ptrdiff_t)numsubsectors) I_Error ("R_Subsector: ss %ti with numss = %i", sub - subsectors, numsubsectors); #endif + assert(sub->sector != NULL); + + if (sub->polys) + { // Render the polyobjs in the subsector first + R_AddPolyobjs(sub); + if (outersubsector) + { + InSubsector = NULL; + } + return; + } + frontsector = sub->sector; frontsector->MoreFlags |= SECF_DRAWN; count = sub->numlines; - line = &segs[sub->firstline]; + line = sub->firstline; // killough 3/8/98, 4/4/98: Deep water / fake ceiling effect frontsector = R_FakeFlat(frontsector, &tempsec, &floorlightlevel, @@ -1092,30 +1239,27 @@ void R_Subsector (subsector_t *sub) ceilinglightlevel : floorlightlevel, FakeSide); // [RH] Add particles - int shade = LIGHT2SHADE((floorlightlevel + ceilinglightlevel)/2 + r_actualextralight); - for (WORD i = ParticlesInSubsec[(unsigned int)(sub-subsectors)]; i != NO_PARTICLE; i = Particles[i].snext) - { - R_ProjectParticle (Particles + i, subsectors[sub-subsectors].sector, shade, FakeSide); - } - - if (sub->poly) - { // Render the polyobj in the subsector first - int polyCount = sub->poly->numsegs; - seg_t **polySeg = sub->poly->segs; - while (polyCount--) + if ((unsigned int)(sub - subsectors) < (unsigned int)numsubsectors) + { // Only do it for the main BSP. + int shade = LIGHT2SHADE((floorlightlevel + ceilinglightlevel)/2 + r_actualextralight); + for (WORD i = ParticlesInSubsec[(unsigned int)(sub-subsectors)]; i != NO_PARTICLE; i = Particles[i].snext) { - R_AddLine (*polySeg++); + R_ProjectParticle (Particles + i, subsectors[sub-subsectors].sector, shade, FakeSide); } } while (count--) { - if (!line->bPolySeg) + if (!outersubsector || line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ)) { R_AddLine (line); } line++; } + if (outersubsector) + { + InSubsector = NULL; + } } // diff --git a/src/r_bsp.h b/src/r_bsp.h index 5701647a7..d8b00ec2a 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -75,6 +75,7 @@ EXTERN_CVAR (Bool, r_drawflat) // [RH] Don't texture segs? // BSP? void R_ClearClipSegs (short left, short right); void R_ClearDrawSegs (); +void R_BuildPolyBSP(subsector_t *sub); void R_RenderBSPNode (void *node); // killough 4/13/98: fake floors/ceilings for deep water / fake ceilings: diff --git a/src/r_data.cpp b/src/r_data.cpp index 0be54a8ad..ee40e29ca 100644 --- a/src/r_data.cpp +++ b/src/r_data.cpp @@ -421,80 +421,15 @@ void R_DeinitData () void R_PrecacheLevel (void) { BYTE *hitlist; - BYTE *spritelist; - int i; if (demoplayback) return; hitlist = new BYTE[TexMan.NumTextures()]; - spritelist = new BYTE[sprites.Size()]; - - // Precache textures (and sprites). memset (hitlist, 0, TexMan.NumTextures()); - memset (spritelist, 0, sprites.Size()); - { - AActor *actor; - TThinkerIterator iterator; - - while ( (actor = iterator.Next ()) ) - spritelist[actor->sprite] = 1; - } - - for (i = (int)(sprites.Size () - 1); i >= 0; i--) - { - if (spritelist[i]) - { - int j, k; - for (j = 0; j < sprites[i].numframes; j++) - { - const spriteframe_t *frame = &SpriteFrames[sprites[i].spriteframes + j]; - - for (k = 0; k < 16; k++) - { - FTextureID pic = frame->Texture[k]; - if (pic.isValid()) - { - hitlist[pic.GetIndex()] = 1; - } - } - } - } - } - - delete[] spritelist; - - for (i = numsectors - 1; i >= 0; i--) - { - hitlist[sectors[i].GetTexture(sector_t::floor).GetIndex()] = - hitlist[sectors[i].GetTexture(sector_t::ceiling).GetIndex()] |= 2; - } - - for (i = numsides - 1; i >= 0; i--) - { - hitlist[sides[i].GetTexture(side_t::top).GetIndex()] = - hitlist[sides[i].GetTexture(side_t::mid).GetIndex()] = - hitlist[sides[i].GetTexture(side_t::bottom).GetIndex()] |= 1; - } - - // Sky texture is always present. - // Note that F_SKY1 is the name used to - // indicate a sky floor/ceiling as a flat, - // while the sky texture is stored like - // a wall texture, with an episode dependant - // name. - - if (sky1texture.isValid()) - { - hitlist[sky1texture.GetIndex()] |= 1; - } - if (sky2texture.isValid()) - { - hitlist[sky2texture.GetIndex()] |= 1; - } - - for (i = TexMan.NumTextures() - 1; i >= 0; i--) + screen->GetHitlist(hitlist); + for (int i = TexMan.NumTextures() - 1; i >= 0; i--) { screen->PrecacheTexture(TexMan.ByIndex(i), hitlist[i]); } @@ -502,6 +437,8 @@ void R_PrecacheLevel (void) delete[] hitlist; } + + //========================================================================== // // R_GetColumn diff --git a/src/r_defs.h b/src/r_defs.h index f19656afa..9e0477ac2 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -75,6 +75,11 @@ struct vertex_t { return x == other.x && y == other.y; } + + void clear() + { + x = y = 0; + } }; // Forward of LineDefs, for Sectors. @@ -314,6 +319,7 @@ enum SECF_FORCEDUNDERWATER= 64, // sector is forced to be underwater SECF_UNDERWATERMASK = 32+64, SECF_DRAWN = 128, // sector has been drawn at least once + SECF_HIDDEN = 256, // Do not draw on textured automap }; enum @@ -635,6 +641,7 @@ struct sector_t int sky; short seqType; // this sector's sound sequence + FNameNoInit SeqName; // Sound sequence name. Setting seqType non-negative will override this. fixed_t soundorg[2]; // origin for any sounds played by the sector int validcount; // if == validcount, already checked @@ -763,7 +770,7 @@ struct side_t BYTE Flags; int Index; // needed to access custom UDMF fields which are stored in loading order. - int GetLightLevel (bool foggy, int baselight) const; + int GetLightLevel (bool foggy, int baselight, int *fake = NULL) const; void SetLight(SWORD l) { @@ -854,6 +861,9 @@ struct side_t DInterpolation *SetInterpolation(int position); void StopInterpolation(int position); + + vertex_t *V1() const; + vertex_t *V2() const; }; FArchive &operator<< (FArchive &arc, side_t::part &p); @@ -916,22 +926,8 @@ struct msecnode_t bool visited; // killough 4/4/98, 4/7/98: used in search algorithms }; -// -// A SubSector. -// References a Sector. -// Basically, this is a list of LineSegs indicating the visible walls that -// define (all or some) sides of a convex BSP leaf. -// -struct FPolyObj; -struct subsector_t -{ - sector_t *sector; - DWORD numlines; - DWORD firstline; - FPolyObj *poly; - int validcount; - fixed_t CenterX, CenterY; -}; +struct FPolyNode; +struct FMiniBSP; // // The LineSeg. @@ -947,53 +943,42 @@ struct seg_t // Sector references. Could be retrieved from linedef, too. sector_t* frontsector; sector_t* backsector; // NULL for one-sided lines - - subsector_t* Subsector; - seg_t* PartnerSeg; - - BITFIELD bPolySeg:1; }; -// ===== Polyobj data ===== -struct FPolyObj +struct glsegextra_t { - int numsegs; - seg_t **segs; - int numlines; - line_t **lines; - int numvertices; - vertex_t **vertices; - fixed_t startSpot[2]; - vertex_t *originalPts; // used as the base for the rotations - vertex_t *prevPts; // use to restore the old point values - angle_t angle; - int tag; // reference tag assigned in HereticEd - int bbox[4]; - int validcount; - int crush; // should the polyobj attempt to crush mobjs? - bool bHurtOnTouch; // should the polyobj hurt anything it touches? - int seqType; - fixed_t size; // polyobj size (area of POLY_AREAUNIT == size of FRACUNIT) - DThinker *specialdata; // pointer to a thinker, if the poly is moving - TObjPtr interpolation; - - ~FPolyObj(); - DInterpolation *SetInterpolation(); - void StopInterpolation(); + DWORD PartnerSeg; + subsector_t *Subsector; }; -extern FPolyObj *polyobjs; // list of all poly-objects on the level -inline FArchive &operator<< (FArchive &arc, FPolyObj *&poly) +// +// A SubSector. +// References a Sector. +// Basically, this is a list of LineSegs indicating the visible walls that +// define (all or some) sides of a convex BSP leaf. +// + +enum { - return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); -} + SSECF_DEGENERATE = 1, + SSECF_DRAWN = 2, + SSECF_POLYORG = 4, +}; -inline FArchive &operator<< (FArchive &arc, const FPolyObj *&poly) +struct subsector_t { - return arc.SerializePointer (polyobjs, (BYTE **)&poly, sizeof(FPolyObj)); -} + sector_t *sector; + FPolyNode *polys; + FMiniBSP *BSP; + seg_t *firstline; + sector_t *render_sector; + DWORD numlines; + int flags; +}; + + // // BSP node. // @@ -1005,6 +990,7 @@ struct node_t fixed_t dx; fixed_t dy; fixed_t bbox[2][4]; // Bounding box for each child. + float len; union { void *children[2]; // If bit 0 is set, it's a subsector. @@ -1013,11 +999,18 @@ struct node_t }; -struct polyblock_t +// An entire BSP tree. + +struct FMiniBSP { - FPolyObj *polyobj; - struct polyblock_t *prev; - struct polyblock_t *next; + FMiniBSP(); + + bool bDirty; + + TArray Nodes; + TArray Segs; + TArray Subsectors; + TArray Verts; }; diff --git a/src/r_draw.cpp b/src/r_draw.cpp index ce836f839..4334f6f79 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -1000,6 +1000,77 @@ const BYTE* ds_source; // just for profiling int dscount; + +#ifdef X86_ASM +extern "C" void R_SetSpanSource_ASM (const BYTE *flat); +extern "C" void STACK_ARGS R_SetSpanSize_ASM (int xbits, int ybits); +extern "C" void R_SetSpanColormap_ASM (BYTE *colormap); +extern "C" BYTE *ds_curcolormap, *ds_cursource, *ds_curtiltedsource; +#endif +} + +//========================================================================== +// +// R_SetSpanSource +// +// Sets the source bitmap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanSource(const BYTE *pixels) +{ + ds_source = pixels; +#ifdef X86_ASM + if (ds_cursource != ds_source) + { + R_SetSpanSource_ASM(pixels); + } +#endif +} + +//========================================================================== +// +// R_SetSpanColormap +// +// Sets the colormap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanColormap(BYTE *colormap) +{ + ds_colormap = colormap; +#ifdef X86_ASM + if (ds_colormap != ds_curcolormap) + { + R_SetSpanColormap_ASM (ds_colormap); + } +#endif +} + +//========================================================================== +// +// R_SetupSpanBits +// +// Sets the texture size for the span drawing routines. +// +//========================================================================== + +void R_SetupSpanBits(FTexture *tex) +{ + tex->GetWidth (); + ds_xbits = tex->WidthBits; + ds_ybits = tex->HeightBits; + if ((1 << ds_xbits) > tex->GetWidth()) + { + ds_xbits--; + } + if ((1 << ds_ybits) > tex->GetHeight()) + { + ds_ybits--; + } +#ifdef X86_ASM + R_SetSpanSize_ASM (ds_xbits, ds_ybits); +#endif } // diff --git a/src/r_draw.h b/src/r_draw.h index 4d452b49d..96e58bf8d 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -91,8 +91,11 @@ extern void (*R_DrawShadedColumn)(void); // Green/Red/Blue/Indigo shirts. extern void (*R_DrawTranslatedColumn)(void); -// Span drawing for rows, floor/ceiling. No Sepctre effect needed. +// Span drawing for rows, floor/ceiling. No Spectre effect needed. extern void (*R_DrawSpan)(void); +void R_SetupSpanBits(FTexture *tex); +void R_SetSpanColormap(BYTE *colormap); +void R_SetSpanSource(const BYTE *pixels); // Span drawing for masked textures. extern void (*R_DrawSpanMasked)(void); diff --git a/src/r_drawt.cpp b/src/r_drawt.cpp index 671a977ec..fbfaf6307 100644 --- a/src/r_drawt.cpp +++ b/src/r_drawt.cpp @@ -1131,7 +1131,7 @@ void R_DrawMaskedColumnHoriz (const BYTE *column, const FTexture::Span *span) if (sprflipvert) { - swap (dc_yl, dc_yh); + swapvalues (dc_yl, dc_yh); } if (dc_yh >= mfloorclip[dc_x]) @@ -1205,8 +1205,8 @@ nextpost: // instead of bottom-to-top. while (front < back) { - swap (front[0], back[0]); - swap (front[1], back[1]); + swapvalues (front[0], back[0]); + swapvalues (front[1], back[1]); front += 2; back -= 2; } diff --git a/src/r_interpolate.cpp b/src/r_interpolate.cpp index 238793981..f2fba2f8c 100644 --- a/src/r_interpolate.cpp +++ b/src/r_interpolate.cpp @@ -38,6 +38,8 @@ #include "stats.h" #include "r_interpolate.h" #include "p_local.h" +#include "i_system.h" +#include "po_man.h" //========================================================================== // @@ -134,6 +136,8 @@ class DPolyobjInterpolation : public DInterpolation FPolyObj *poly; TArray oldverts, bakverts; + fixed_t oldcx, oldcy; + fixed_t bakcx, bakcy; public: @@ -728,8 +732,8 @@ void DWallScrollInterpolation::Serialize(FArchive &arc) DPolyobjInterpolation::DPolyobjInterpolation(FPolyObj *po) { poly = po; - oldverts.Resize(po->numvertices<<1); - bakverts.Resize(po->numvertices<<1); + oldverts.Resize(po->Vertices.Size() << 1); + bakverts.Resize(po->Vertices.Size() << 1); UpdateInterpolation (); interpolator.AddInterpolation(this); } @@ -755,11 +759,13 @@ void DPolyobjInterpolation::Destroy() void DPolyobjInterpolation::UpdateInterpolation() { - for(int i = 0; i < poly->numvertices; i++) + for(unsigned int i = 0; i < poly->Vertices.Size(); i++) { - oldverts[i*2 ] = poly->vertices[i]->x; - oldverts[i*2+1] = poly->vertices[i]->y; + oldverts[i*2 ] = poly->Vertices[i]->x; + oldverts[i*2+1] = poly->Vertices[i]->y; } + oldcx = poly->CenterSpot.x; + oldcy = poly->CenterSpot.y; } //========================================================================== @@ -770,12 +776,14 @@ void DPolyobjInterpolation::UpdateInterpolation() void DPolyobjInterpolation::Restore() { - for(int i = 0; i < poly->numvertices; i++) + for(unsigned int i = 0; i < poly->Vertices.Size(); i++) { - poly->vertices[i]->x = bakverts[i*2 ]; - poly->vertices[i]->y = bakverts[i*2+1]; + poly->Vertices[i]->x = bakverts[i*2 ]; + poly->Vertices[i]->y = bakverts[i*2+1]; } - //poly->Moved(); + poly->CenterSpot.x = bakcx; + poly->CenterSpot.y = bakcy; + poly->ClearSubsectorLinks(); } //========================================================================== @@ -786,10 +794,10 @@ void DPolyobjInterpolation::Restore() void DPolyobjInterpolation::Interpolate(fixed_t smoothratio) { - for(int i = 0; i < poly->numvertices; i++) + for(unsigned int i = 0; i < poly->Vertices.Size(); i++) { - fixed_t *px = &poly->vertices[i]->x; - fixed_t *py = &poly->vertices[i]->y; + fixed_t *px = &poly->Vertices[i]->x; + fixed_t *py = &poly->Vertices[i]->y; bakverts[i*2 ] = *px; bakverts[i*2+1] = *py; @@ -797,7 +805,12 @@ void DPolyobjInterpolation::Interpolate(fixed_t smoothratio) *px = oldverts[i*2 ] + FixedMul(bakverts[i*2 ] - oldverts[i*2 ], smoothratio); *py = oldverts[i*2+1] + FixedMul(bakverts[i*2+1] - oldverts[i*2+1], smoothratio); } - //poly->Moved(); + bakcx = poly->CenterSpot.x; + bakcy = poly->CenterSpot.y; + poly->CenterSpot.x = bakcx + FixedMul(bakcx - oldcx, smoothratio); + poly->CenterSpot.y = bakcy + FixedMul(bakcy - oldcy, smoothratio); + + poly->ClearSubsectorLinks(); } //========================================================================== @@ -808,11 +821,21 @@ void DPolyobjInterpolation::Interpolate(fixed_t smoothratio) void DPolyobjInterpolation::Serialize(FArchive &arc) { - Super::Serialize(arc); int po = int(poly - polyobjs); arc << po << oldverts; poly = polyobjs + po; + + if (SaveVersion >= 2448) + { + arc << oldcx << oldcy; + } + else + { + // This will glitch if an old savegame is loaded but at least it'll allow loading it. + oldcx = poly->CenterSpot.x; + oldcy = poly->CenterSpot.y; + } if (arc.IsLoading()) bakverts.Resize(oldverts.Size()); } diff --git a/src/r_main.cpp b/src/r_main.cpp index 9817b4797..3ad7d3fa1 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -50,6 +50,7 @@ #include "r_bsp.h" #include "r_plane.h" #include "v_palette.h" +#include "po_man.h" // MACROS ------------------------------------------------------------------ @@ -99,6 +100,7 @@ static float CurrentVisibility = 8.f; static fixed_t MaxVisForWall; static fixed_t MaxVisForFloor; static FRandom pr_torchflicker ("TorchFlicker"); +static FRandom pr_hom; static TArray PastViewers; static int centerxwide; static bool polyclipped; @@ -110,6 +112,7 @@ bool r_dontmaplines; CVAR (String, r_viewsize, "", CVAR_NOSET) CVAR (Int, r_polymost, 0, 0) CVAR (Bool, r_deathcamera, false, CVAR_ARCHIVE) +CVAR (Int, r_clearbuffer, 0, 0) fixed_t r_BaseVisibility; fixed_t r_WallVisibility; @@ -376,7 +379,7 @@ fixed_t R_PointToDist2 (fixed_t dx, fixed_t dy) if (dy > dx) { - swap (dx, dy); + swapvalues (dx, dy); } return FixedDiv (dx, finecosine[tantoangle[FixedDiv (dy, dx) >> DBITS] >> ANGLETOFINESHIFT]); @@ -471,7 +474,7 @@ void R_InitTextureMapping () } for (i = 0; i < centerx; i++) { - xtoviewangle[i] = (angle_t)(-(signed)xtoviewangle[viewwidth-i-1]); + xtoviewangle[i] = (angle_t)(-(signed)xtoviewangle[viewwidth-i]); } } @@ -1054,7 +1057,7 @@ void R_SetupFrame (AActor *actor) ((player->cheats & CF_CHASECAM) || (r_deathcamera && camera->health <= 0)) && (camera->RenderStyle.BlendOp != STYLEOP_None) && !(camera->renderflags & RF_INVISIBLE) && - camera->sprite != 0) // Sprite 0 is always TNT1 + camera->sprite != SPR_TNT1) { // [RH] Use chasecam view P_AimCamera (camera, iview->nviewx, iview->nviewy, iview->nviewz, viewsector); @@ -1269,6 +1272,34 @@ void R_SetupFrame (AActor *actor) { polyclipped = RP_SetupFrame (false); } + + if (RenderTarget == screen && r_clearbuffer != 0) + { + int color; + int hom = r_clearbuffer; + + if (hom == 3) + { + hom = ((I_FPSTime() / 128) & 1) + 1; + } + if (hom == 1) + { + color = GPalette.BlackIndex; + } + else if (hom == 2) + { + color = GPalette.WhiteIndex; + } + else if (hom == 4) + { + color = (I_FPSTime() / 32) & 255; + } + else + { + color = pr_hom(); + } + memset(RenderTarget->GetBuffer(), color, RenderTarget->GetPitch() * RenderTarget->GetHeight()); + } } //========================================================================== @@ -1488,6 +1519,8 @@ void R_RenderActorView (AActor *actor, bool dontmaplines) { camera->renderflags |= RF_INVISIBLE; } + // Link the polyobjects right before drawing the scene to reduce the amounts of calls to this function + PO_LinkToSubsectors(); if (r_polymost < 2) { R_RenderBSPNode (nodes + numnodes - 1); // The head node is the last node output. diff --git a/src/r_plane.cpp b/src/r_plane.cpp index aae870fbe..cd0b9f4a5 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -740,6 +740,10 @@ inline void R_MakeSpans (int x, int t1, int b1, int t2, int b2, void (*mapfunc)( // in the normal convention for patches, but uses color 0 as a transparent // color instead. // +// Note that since ZDoom now uses color 0 as transparent for other purposes, +// you can use normal texture transparency, so the distinction isn't so +// important anymore, but you should still be aware of it. +// //========================================================================== static FTexture *frontskytex, *backskytex; @@ -987,22 +991,9 @@ void R_DrawSinglePlane (visplane_t *pl, fixed_t alpha, bool masked) { // Don't waste time on a masked texture if it isn't really masked. masked = false; } - tex->GetWidth (); - ds_xbits = tex->WidthBits; - ds_ybits = tex->HeightBits; - if ((1 << ds_xbits) > tex->GetWidth()) - { - ds_xbits--; - } - if ((1 << ds_ybits) > tex->GetHeight()) - { - ds_ybits--; - } + R_SetupSpanBits(tex); pl->xscale = MulScale16 (pl->xscale, tex->xScale); pl->yscale = MulScale16 (pl->yscale, tex->yScale); -#ifdef X86_ASM - R_SetSpanSize_ASM (ds_xbits, ds_ybits); -#endif ds_source = tex->GetPixels (); basecolormap = pl->colormap; diff --git a/src/r_plane.h b/src/r_plane.h index d52fc6db2..369083bad 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -110,5 +110,4 @@ bool R_PlaneInitData (void); extern visplane_t* floorplane; extern visplane_t* ceilingplane; - #endif // __R_PLANE_H__ diff --git a/src/r_polymost.cpp b/src/r_polymost.cpp index f5f82950a..12050440f 100644 --- a/src/r_polymost.cpp +++ b/src/r_polymost.cpp @@ -1204,7 +1204,7 @@ void RP_AddLine (seg_t *line) { // The seg is only part of the wall. if (line->linedef->sidedef[0] != line->sidedef) { - swap (v1, v2); + swapvalues (v1, v2); } tx1 = v1->x - viewx; tx2 = v2->x - viewx; @@ -1401,7 +1401,7 @@ void RP_Subsector (subsector_t *sub) frontsector = sub->sector; count = sub->numlines; - line = &segs[sub->firstline]; + line = sub->firstline; // killough 3/8/98, 4/4/98: Deep water / fake ceiling effect frontsector = R_FakeFlat(frontsector, &tempsec, &floorlightlevel, @@ -1471,6 +1471,7 @@ void RP_Subsector (subsector_t *sub) // R_ProjectParticle (Particles + i, subsectors[sub-subsectors].sector, shade, FakeSide); // } +#if 0 if (sub->poly) { // Render the polyobj in the subsector first int polyCount = sub->poly->numsegs; @@ -1480,10 +1481,11 @@ void RP_Subsector (subsector_t *sub) RP_AddLine (*polySeg++); } } +#endif while (count--) { - if (!line->bPolySeg) + if (line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ)) { RP_AddLine (line); } diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 06b793af4..eed5d78a8 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -53,6 +53,7 @@ #define WALLYREPEAT 8 + //CVAR (Int, ty, 8, 0) //CVAR (Int, tx, 8, 0) @@ -1453,34 +1454,51 @@ void R_NewWall (bool needlights) } } -CVAR(Bool, r_smoothlighting, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, r_fakecontrast, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) self = 1; + else if (self > 2) self = 2; +} -int side_t::GetLightLevel (bool foggy, int baselight) const +int side_t::GetLightLevel (bool foggy, int baselight, int *pfakecontrast) const { if (Flags & WALLF_ABSLIGHTING) { baselight = (BYTE)Light; } + if (pfakecontrast != NULL) + { + *pfakecontrast = 0; + } if (!foggy) // Don't do relative lighting in foggy sectors { - if (!(Flags & WALLF_NOFAKECONTRAST)) + if (!(Flags & WALLF_NOFAKECONTRAST) && r_fakecontrast != 0) { - if (((level.flags2 & LEVEL2_SMOOTHLIGHTING) || (Flags & WALLF_SMOOTHLIGHTING) || r_smoothlighting) && + int rel; + if (((level.flags2 & LEVEL2_SMOOTHLIGHTING) || (Flags & WALLF_SMOOTHLIGHTING) || r_fakecontrast == 2) && linedef->dx != 0) { - baselight += int // OMG LEE KILLOUGH LIVES! :/ + rel = xs_RoundToInt // OMG LEE KILLOUGH LIVES! :/ ( - (float(level.WallHorizLight) - +abs(atan(float(linedef->dy)/float(linedef->dx))/float(1.57079)) - *float(level.WallVertLight - level.WallHorizLight)) + level.WallHorizLight + + fabs(atan(double(linedef->dy) / linedef->dx) / 1.57079) + * (level.WallVertLight - level.WallHorizLight) ); } else { - baselight += linedef->dx==0? level.WallVertLight : - linedef->dy==0? level.WallHorizLight : 0; + rel = linedef->dx == 0 ? level.WallVertLight : + linedef->dy == 0 ? level.WallHorizLight : 0; + } + if (pfakecontrast != NULL) + { + *pfakecontrast = rel; + } + else + { + baselight += rel; } } if (!(Flags & WALLF_ABSLIGHTING)) @@ -1763,6 +1781,7 @@ int OWallMost (short *mostbuf, fixed_t z) s3 = MulScale16 (globaldclip, WallSZ1); s4 = MulScale16 (globaldclip, WallSZ2); bad = (zs3)<<2)+((z>s4)<<3); +#if 1 if ((bad&3) == 3) { memset (&mostbuf[WallSX1], 0, (WallSX2 - WallSX1)*sizeof(mostbuf[0])); @@ -1774,10 +1793,10 @@ int OWallMost (short *mostbuf, fixed_t z) clearbufshort (&mostbuf[WallSX1], WallSX2 - WallSX1, viewheight); return bad; } - +#endif ix1 = WallSX1; iy1 = WallSZ1; ix2 = WallSX2; iy2 = WallSZ2; - +#if 1 if (bad & 3) { int t = DivScale30 (z-s1, s2-s1); @@ -1824,7 +1843,38 @@ int OWallMost (short *mostbuf, fixed_t z) fixed_t yinc = (Scale (z, InvZtoScale, iy2) - y) / (ix2 - ix1); qinterpolatedown16short (&mostbuf[ix1], ix2-ix1, y + centeryfrac, yinc); } +#else + double max = viewheight; + double zz = z / 65536.0; +#if 0 + double z1 = zz * InvZtoScale / WallSZ1; + double z2 = zz * InvZtoScale / WallSZ2 - z1; + z2 /= (WallSX2 - WallSX1); + z1 += centeryfrac / 65536.0; + for (int x = WallSX1; x < WallSX2; ++x) + { + mostbuf[x] = xs_RoundToInt(clamp(z1, 0.0, max)); + z1 += z2; + } +#else + double top, bot, i; + + i = WallSX1 - centerx; + top = WallUoverZorg + WallUoverZstep * i; + bot = WallInvZorg + WallInvZstep * i; + double cy = centeryfrac / 65536.0; + + for (int x = WallSX1; x < WallSX2; x++) + { + double frac = top / bot; + double scale = frac * WallDepthScale + WallDepthOrg; + mostbuf[x] = xs_RoundToInt(clamp(zz / scale + cy, 0.0, max)); + top += WallUoverZstep; + bot += WallInvZstep; + } +#endif +#endif if (mostbuf[ix1] < 0) mostbuf[ix1] = 0; else if (mostbuf[ix1] > viewheight) mostbuf[ix1] = (short)viewheight; if (mostbuf[ix2] < 0) mostbuf[ix2] = 0; @@ -1990,80 +2040,12 @@ int WallMost (short *mostbuf, const secplane_t &plane) return bad; } -void PrepWall (fixed_t *swall, fixed_t *lwall, fixed_t walxrepeat) -{ // swall = scale, lwall = texturecolumn - int x; - float top, bot, i; - float xrepeat = (float)walxrepeat; - float ol, l, topinc, botinc; - - i = (float)(WallSX1 - centerx); - top = WallUoverZorg + WallUoverZstep * i; - bot = WallInvZorg + WallInvZstep * i; - topinc = WallUoverZstep * 4.f; - botinc = WallInvZstep * 4.f; - - x = WallSX1; - - l = top / bot; - swall[x] = xs_RoundToInt(l * WallDepthScale + WallDepthOrg); - lwall[x] = xs_RoundToInt(l * xrepeat); - // As long as l is invalid, step one column at a time so that - // we can get as many correct texture columns as possible. - while (l > 1.0 && x+1 < WallSX2) - { - l = (top += WallUoverZstep) / (bot += WallInvZstep); - x++; - swall[x] = xs_RoundToInt(l * WallDepthScale + WallDepthOrg); - lwall[x] = xs_RoundToInt(l * xrepeat); - } - l *= xrepeat; - while (x+4 < WallSX2) - { - top += topinc; bot += botinc; - ol = l; l = top / bot; - swall[x+4] = xs_RoundToInt(l * WallDepthScale + WallDepthOrg); - lwall[x+4] = xs_RoundToInt(l *= xrepeat); - - i = (ol+l) * 0.5f; - lwall[x+2] = xs_RoundToInt(i); - lwall[x+1] = xs_RoundToInt((ol+i) * 0.5f); - lwall[x+3] = xs_RoundToInt((l+i) * 0.5f); - swall[x+2] = ((swall[x]+swall[x+4])>>1); - swall[x+1] = ((swall[x]+swall[x+2])>>1); - swall[x+3] = ((swall[x+4]+swall[x+2])>>1); - x += 4; - } - if (x+2 < WallSX2) - { - top += topinc * 0.5f; bot += botinc * 0.5f; - ol = l; l = top / bot; - swall[x+2] = xs_RoundToInt(l * WallDepthScale + WallDepthOrg); - lwall[x+2] = xs_RoundToInt(l *= xrepeat); - - lwall[x+1] = xs_RoundToInt((l+ol)*0.5f); - swall[x+1] = (swall[x]+swall[x+2])>>1; - x += 2; - } - if (x+1 < WallSX2) - { - l = (top + WallUoverZstep) / (bot + WallInvZstep); - swall[x+1] = xs_RoundToInt(l * WallDepthScale + WallDepthOrg); - lwall[x+1] = xs_RoundToInt(l * xrepeat); - } - /* - for (x = WallSX1; x < WallSX2; x++) - { - frac = top / bot; - lwall[x] = xs_RoundToInt(frac * xrepeat); - swall[x] = xs_RoundToInt(frac * WallDepthScale + WallDepthOrg); - top += WallUoverZstep; - bot += WallInvZstep; - } - */ - +static void PrepWallRoundFix(fixed_t *lwall, fixed_t walxrepeat) +{ // fix for rounding errors fixed_t fix = (MirrorFlags & RF_XFLIP) ? walxrepeat-1 : 0; + int x; + if (WallSX1 > 0) { for (x = WallSX1; x < WallSX2; x++) @@ -2092,85 +2074,46 @@ void PrepWall (fixed_t *swall, fixed_t *lwall, fixed_t walxrepeat) } } -void PrepLWall (fixed_t *lwall, fixed_t walxrepeat) -{ // lwall = texturecolumn - int x; - float top, bot, i; - float xrepeat = (float)walxrepeat; - float ol, l, topinc, botinc; +void PrepWall (fixed_t *swall, fixed_t *lwall, fixed_t walxrepeat) +{ // swall = scale, lwall = texturecolumn + double top, bot, i; + double xrepeat = walxrepeat; - i = (float)(WallSX1 - centerx); + i = WallSX1 - centerx; top = WallUoverZorg + WallUoverZstep * i; bot = WallInvZorg + WallInvZstep * i; - topinc = WallUoverZstep * 4.f; - botinc = WallInvZstep * 4.f; - x = WallSX1; + for (int x = WallSX1; x < WallSX2; x++) + { + double frac = top / bot; + lwall[x] = xs_RoundToInt(frac * xrepeat); + swall[x] = xs_RoundToInt(frac * WallDepthScale + WallDepthOrg); + top += WallUoverZstep; + bot += WallInvZstep; + } + PrepWallRoundFix(lwall, walxrepeat); +} - l = top / bot; - lwall[x] = xs_RoundToInt(l * xrepeat); - // As long as l is invalid, step one column at a time so that - // we can get as many correct texture columns as possible. - while (l > 1.0 && x+1 < WallSX2) - { - l = (top += WallUoverZstep) / (bot += WallInvZstep); - lwall[++x] = xs_RoundToInt(l * xrepeat); - } - l *= xrepeat; - while (x+4 < WallSX2) - { - top += topinc; bot += botinc; - ol = l; l = top / bot; - lwall[x+4] = xs_RoundToInt(l *= xrepeat); +void PrepLWall (fixed_t *lwall, fixed_t walxrepeat) +{ // lwall = texturecolumn + double top, bot, i; + double xrepeat = walxrepeat; + double topstep; - i = (ol+l) * 0.5f; - lwall[x+2] = xs_RoundToInt(i); - lwall[x+1] = xs_RoundToInt((ol+i) * 0.5f); - lwall[x+3] = xs_RoundToInt((l+i) * 0.5f); - x += 4; - } - if (x+2 < WallSX2) - { - top += topinc * 0.5f; bot += botinc * 0.5f; - ol = l; l = top / bot; - lwall[x+2] = xs_RoundToInt(l *= xrepeat); - lwall[x+1] = xs_RoundToInt((l+ol)*0.5f); - x += 2; - } - if (x+1 < WallSX2) - { - l = (top + WallUoverZstep) / (bot + WallInvZstep); - lwall[x+1] = xs_RoundToInt(l * xrepeat); - } + i = WallSX1 - centerx; + top = WallUoverZorg + WallUoverZstep * i; + bot = WallInvZorg + WallInvZstep * i; - // fix for rounding errors - fixed_t fix = (MirrorFlags & RF_XFLIP) ? walxrepeat-1 : 0; - if (WallSX1 > 0) + top *= xrepeat; + topstep = WallUoverZstep * xrepeat; + + for (int x = WallSX1; x < WallSX2; x++) { - for (x = WallSX1; x < WallSX2; x++) - { - if ((unsigned)lwall[x] >= (unsigned)walxrepeat) - { - lwall[x] = fix; - } - else - { - break; - } - } - } - fix = walxrepeat - 1 - fix; - for (x = WallSX2-1; x >= WallSX1; x--) - { - if ((unsigned)lwall[x] >= (unsigned)walxrepeat) - { - lwall[x] = fix; - } - else - { - break; - } + lwall[x] = xs_RoundToInt(top / bot); + top += topstep; + bot += WallInvZstep; } + PrepWallRoundFix(lwall, walxrepeat); } // pass = 0: when seg is first drawn @@ -2284,7 +2227,7 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, int t = 256-WallTX1; WallTX1 = 256-WallTX2; WallTX2 = t; - swap (WallTY1, WallTY2); + swapvalues (WallTY1, WallTY2); } if (WallTX1 >= -WallTY1) @@ -2415,11 +2358,11 @@ static void R_RenderDecal (side_t *wall, DBaseDecal *decal, drawseg_t *clipper, goto done; } - swap (x1, WallSX1); - swap (x2, WallSX2); + swapvalues (x1, WallSX1); + swapvalues (x2, WallSX2); PrepWall (swall, lwall, WallSpriteTile->GetWidth() << FRACBITS); - swap (x1, WallSX1); - swap (x2, WallSX2); + swapvalues (x1, WallSX1); + swapvalues (x2, WallSX2); if (flipx) { diff --git a/src/r_state.h b/src/r_state.h index 38f8e4c3e..9044da72b 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -77,6 +77,13 @@ extern side_t* sides; extern int numzones; extern zone_t* zones; +extern node_t * gamenodes; +extern int numgamenodes; + +extern subsector_t * gamesubsectors; +extern int numgamesubsectors; + + extern FExtraLight* ExtraLights; extern FLightStack* LightStacks; diff --git a/src/r_things.cpp b/src/r_things.cpp index c557a2a1f..31560ae81 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1021,7 +1021,7 @@ void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *span) if (sprflipvert) { - swap (dc_yl, dc_yh); + swapvalues (dc_yl, dc_yh); } if (dc_yh >= mfloorclip[dc_x]) @@ -1531,18 +1531,18 @@ void R_DrawPSprite (pspdef_t* psp, int pspnum, AActor *owner, fixed_t sx, fixed_ assert(pspnum >= 0 && pspnum < NUMPSPRITES); // decide which patch to use - if ( (unsigned)psp->state->sprite >= (unsigned)sprites.Size ()) + if ( (unsigned)psp->sprite >= (unsigned)sprites.Size ()) { - DPrintf ("R_DrawPSprite: invalid sprite number %i\n", psp->state->sprite); + DPrintf ("R_DrawPSprite: invalid sprite number %i\n", psp->sprite); return; } - sprdef = &sprites[psp->state->sprite]; - if (psp->state->GetFrame() >= sprdef->numframes) + sprdef = &sprites[psp->sprite]; + if (psp->frame >= sprdef->numframes) { - DPrintf ("R_DrawPSprite: invalid sprite frame %i : %i\n", psp->state->sprite, psp->state->GetFrame()); + DPrintf ("R_DrawPSprite: invalid sprite frame %i : %i\n", psp->sprite, psp->frame); return; } - sprframe = &SpriteFrames[sprdef->spriteframes + psp->state->GetFrame()]; + sprframe = &SpriteFrames[sprdef->spriteframes + psp->frame]; picnum = sprframe->Texture[0]; flip = sprframe->Flip & 1; diff --git a/src/r_translate.cpp b/src/r_translate.cpp index d5ef79a1f..eef8c1c1b 100644 --- a/src/r_translate.cpp +++ b/src/r_translate.cpp @@ -312,8 +312,8 @@ void FRemapTable::AddIndexRange(int start, int end, int pal1, int pal2) if (start > end) { - swap (start, end); - swap (pal1, pal2); + swapvalues (start, end); + swapvalues (pal1, pal2); } else if (start == end) { @@ -354,7 +354,7 @@ void FRemapTable::AddColorRange(int start, int end, int _r1,int _g1, int _b1, in if (start > end) { - swap (start, end); + swapvalues (start, end); r = r2; g = g2; b = b2; diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index b5cfc1d82..46d4bc813 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -1294,6 +1294,7 @@ static void S_AddSNDINFO (int lump) else if (sc.Compare("standard")) MidiDevices[nm] = MDEV_MMAPI; else if (sc.Compare("opl")) MidiDevices[nm] = MDEV_OPL; else if (sc.Compare("default")) MidiDevices[nm] = MDEV_DEFAULT; + else if (sc.Compare("fluidsynth")) MidiDevices[nm] = MDEV_FLUIDSYNTH; else sc.ScriptError("Unknown MIDI device %s\n", sc.String); } break; @@ -1772,6 +1773,100 @@ int S_FindSkinnedSoundEx (AActor *actor, const char *name, const char *extendedn return S_FindSkinnedSound (actor, id); } +//========================================================================== +// +// S_ParseTimeTag +// +// Passed the value of a loop point tag, converts it to numbers. +// +// This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play +// time. Various parts may be left off. The only requirement is that it +// contain a colon. e.g. To start the loop at 20 seconds in, you can use +// ":20", "0:20", "00:00:20", ":20.0", etc. Values after the decimal are +// fractions of a second. +// +// If you don't include a colon but just have a raw number, then it's +// the number of PCM samples at which to loop. +// +// Returns true if the tag made sense, false if not. +// +//========================================================================== + +bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time) +{ + const char *bit = tag; + char ms[3] = { 0 }; + unsigned int times[3] = { 0 }; + int ms_pos = 0, time_pos = 0; + bool pcm = true, in_ms = false; + + for (bit = tag; *bit != '\0'; ++bit) + { + if (*bit >= '0' && *bit <= '9') + { + if (in_ms) + { + // Ignore anything past three fractional digits. + if (ms_pos < 3) + { + ms[ms_pos++] = *bit - '0'; + } + } + else + { + times[time_pos] = times[time_pos] * 10 + *bit - '0'; + } + } + else if (*bit == ':') + { + if (in_ms) + { // If we already specified milliseconds, we can't take any more parts. + return false; + } + pcm = false; + if (++time_pos == countof(times)) + { // Time too long. (Seriously, starting the loop days in?) + return false; + } + } + else if (*bit == '.') + { + if (pcm || in_ms) + { // It doesn't make sense to have fractional PCM values. + // It also doesn't make sense to have more than one dot. + return false; + } + in_ms = true; + } + else + { // Anything else: We don't understand this. + return false; + } + } + if (pcm) + { + *as_samples = true; + *time = times[0]; + } + else + { + unsigned int mytime = 0; + + // Add in hours, minutes, and seconds + for (int i = 0; i <= time_pos; ++i) + { + mytime = mytime * 60 + times[i]; + } + + // Add in milliseconds + mytime = mytime * 1000 + ms[0] * 100 + ms[1] * 10 + ms[2]; + + *as_samples = false; + *time = mytime; + } + return true; +} + //========================================================================== // // CCMD soundlist @@ -2111,3 +2206,82 @@ void AAmbientSound::Deactivate (AActor *activator) } } } + + +//========================================================================== +// +// S_ParseMusInfo +// Parses MUSINFO lump. +// +//========================================================================== + +void S_ParseMusInfo() +{ + int lastlump = 0, lump; + + while ((lump = Wads.FindLump ("MUSINFO", &lastlump)) != -1) + { + FScanner sc(lump); + + while (sc.GetString()) + { + level_info_t *map = FindLevelInfo(sc.String); + + if (map == NULL) + { + // Don't abort for invalid maps + sc.ScriptMessage("Unknown map '%s'", sc.String); + } + while (sc.CheckNumber()) + { + int index = sc.Number; + sc.MustGetString(); + if (index > 0) + { + FName music = sc.String; + if (map != NULL) + { + map->MusicMap[index] = music; + } + } + } + } + } +} + + +//========================================================================== +// +// Music changer. Uses the sector action class to do its job +// +//========================================================================== + +class AMusicChanger : public ASectorAction +{ + DECLARE_CLASS (AMusicChanger, ASectorAction) +public: + virtual bool TriggerAction (AActor *triggerer, int activationType); +}; + +IMPLEMENT_CLASS(AMusicChanger) + +bool AMusicChanger::TriggerAction (AActor *triggerer, int activationType) +{ + if (activationType & SECSPAC_Enter) + { + if (args[0] != 0) + { + FName *music = level.info->MusicMap.CheckKey(args[0]); + + if (music != NULL) + { + S_ChangeMusic(music->GetChars(), args[1]); + } + } + else + { + S_ChangeMusic("*"); + } + } + return Super::TriggerAction (triggerer, activationType); +} diff --git a/src/s_playlist.cpp b/src/s_playlist.cpp index e92a5ceea..ce82a8182 100644 --- a/src/s_playlist.cpp +++ b/src/s_playlist.cpp @@ -122,7 +122,12 @@ bool FPlayList::ChangeList (const char *path) // Path is relative; append it to the playlist directory. song = playlistdir + song; } - Songs.Push(song); + + // Just to make sure + if (song.IsNotEmpty()) + { + Songs.Push(song); + } } fclose (file); @@ -157,7 +162,7 @@ void FPlayList::Shuffle () for (i = 0; i < numsongs; ++i) { - swap (Songs[i], Songs[(rand() % (numsongs - i)) + i]); + swapvalues (Songs[i], Songs[(rand() % (numsongs - i)) + i]); } Position = 0; } diff --git a/src/s_sndseq.cpp b/src/s_sndseq.cpp index 381a27866..19076055e 100644 --- a/src/s_sndseq.cpp +++ b/src/s_sndseq.cpp @@ -22,6 +22,7 @@ #include "i_system.h" #include "cmdlib.h" #include "p_local.h" +#include "po_man.h" #include "gi.h" #include "templates.h" #include "c_dispatch.h" diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 290157265..ac0462993 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -49,6 +49,7 @@ #include "templates.h" #include "timidity/timidity.h" #include "g_level.h" +#include "po_man.h" // MACROS ------------------------------------------------------------------ @@ -323,6 +324,7 @@ void S_InitData () LastLocalSndInfo = LastLocalSndSeq = ""; S_ParseSndInfo (); S_ParseSndSeq (-1); + S_ParseMusInfo(); } //========================================================================== @@ -794,11 +796,11 @@ static void CalcSectorSoundOrg(const sector_t *sec, int channum, fixed_t *x, fix static void CalcPolyobjSoundOrg(const FPolyObj *poly, fixed_t *x, fixed_t *y, fixed_t *z) { - seg_t *seg; + side_t *side; sector_t *sec; - PO_ClosestPoint(poly, *x, *y, *x, *y, &seg); - sec = seg->frontsector; + poly->ClosestPoint(*x, *y, *x, *y, &side); + sec = side->sector; *z = clamp(*z, sec->floorplane.ZatPoint(*x, *y), sec->ceilingplane.ZatPoint(*x, *y)); } @@ -825,7 +827,7 @@ static FSoundChan *S_StartSound(AActor *actor, const sector_t *sec, const FPolyO FVector3 pos, vel; FRolloffInfo *rolloff; - if (sound_id <= 0 || volume <= 0) + if (sound_id <= 0 || volume <= 0 || nosfx) return NULL; int type; @@ -2327,7 +2329,8 @@ bool S_ChangeMusic (const char *musicname, int order, bool looping, bool force) // Don't choke if the map doesn't have a song attached S_StopMusic (true); mus_playing.name = ""; - return false; + LastSong = ""; + return true; } FString DEH_Music; diff --git a/src/s_sound.h b/src/s_sound.h index b616b9137..05237ba27 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -353,6 +353,8 @@ void S_ShrinkPlayerSoundLists (); void S_UnloadSound (sfxinfo_t *sfx); sfxinfo_t *S_LoadSound(sfxinfo_t *sfx); unsigned int S_GetMSLength(FSoundID sound); +void S_ParseMusInfo(); +bool S_ParseTimeTag(const char *tag, bool *as_samples, unsigned int *time); // [RH] Prints sound debug info to the screen. // Modelled after Hexen's noise cheat. @@ -373,9 +375,10 @@ enum EMidiDevice { MDEV_DEFAULT = -1, MDEV_MMAPI = 0, - MDEV_TIMIDITY = 1, - MDEV_OPL = 2, - MDEV_FMOD = 3, + MDEV_OPL = 1, + MDEV_FMOD = 2, + MDEV_TIMIDITY = 3, + MDEV_FLUIDSYNTH = 4, }; typedef TMap MidiDeviceMap; diff --git a/src/sc_man.cpp b/src/sc_man.cpp index 505c9c0d4..40c10ee53 100644 --- a/src/sc_man.cpp +++ b/src/sc_man.cpp @@ -333,6 +333,24 @@ void FScanner::RestorePos (const FScanner::SavedPos &pos) Crossed = false; } +//========================================================================== +// +// FScanner :: isText +// +// Checks if this is a text file. +// +//========================================================================== + +bool FScanner::isText() +{ + for(unsigned int i=0;i #endif #include +#if defined(__MACH__) && !defined(NOASM) +#include +#include +#endif #include "doomerrors.h" #include "m_argv.h" @@ -202,6 +206,46 @@ static int DoomSpecificInfo (char *buffer, char *end) 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 + int main (int argc, char **argv) { printf(GAMENAME" v%s - SVN revision %s - SDL version\nCompiled on %s\n\n", @@ -215,6 +259,10 @@ int main (int argc, char **argv) seteuid (getuid ()); std::set_new_handler (NewFailure); +#if defined(__MACH__) && !defined(NOASM) + unprotect_rtext(); +#endif + #ifndef NO_GTK GtkAvailable = gtk_init_check (&argc, &argv); #endif diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 931b8a37d..c05ed2027 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #ifndef NO_GTK #include #include @@ -77,6 +78,8 @@ extern "C" #ifndef NO_GTK extern bool GtkAvailable; +#elif defined(__APPLE__) +int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad); #endif DWORD LanguageIDs[4] = @@ -117,6 +120,16 @@ static DWORD TicNext; static DWORD BaseTime; static int TicFrozen; +// Signal based timer. +static struct timespec SignalTimeOut = { 0, 1000000/TICRATE }; +#ifdef HAVE_SIGTIMEDWAIT +static sigset_t SignalWaitSet; +#endif +static int tics; +static DWORD sig_start, sig_next; + +void I_SelectTimer(); + // [RH] Returns time in milliseconds unsigned int I_MSTime (void) { @@ -134,6 +147,12 @@ unsigned int I_FPSTime() // 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) @@ -151,6 +170,21 @@ int I_GetTimePolled (bool saveMS) return Scale(tm - BaseTime, TICRATE, 1000); } +int I_GetTimeSignaled (bool saveMS) +{ + if (TicFrozen != 0) + { + return TicFrozen; + } + + if (saveMS) + { + TicStart = sig_start; + TicNext = sig_next; + } + return tics; +} + int I_WaitForTicPolled (int prevtic) { int time; @@ -162,6 +196,27 @@ int I_WaitForTicPolled (int prevtic) return time; } +int I_WaitForTicSignaled (int prevtic) +{ + assert (TicFrozen == 0); + +#ifdef HAVE_SIGTIMEDWAIT + while(sigtimedwait(&SignalWaitSet, NULL, &SignalTimeOut) == -1 && errno == EINTR) + ; +#else + while(nanosleep(&SignalTimeOut, NULL) == -1 && errno == EINTR) + ; +#endif + + return I_GetTimePolled(false); +} + +void I_FreezeTimeSelect (bool frozen) +{ + I_SelectTimer(); + return I_FreezeTime (frozen); +} + void I_FreezeTimePolled (bool frozen) { if (frozen) @@ -179,6 +234,75 @@ void I_FreezeTimePolled (bool frozen) } } +void I_FreezeTimeSignaled (bool frozen) +{ + TicFrozen = frozen; +} + +int I_WaitForTicSelect (int prevtic) +{ + I_SelectTimer(); + return I_WaitForTic (prevtic); +} + +// +// I_HandleAlarm +// Should be called every time there is an alarm. +// +void I_HandleAlarm (int sig) +{ + if(!TicFrozen) + tics++; + sig_start = SDL_GetTicks(); + sig_next = Scale((Scale (sig_start, TICRATE, 1000) + 1), 1000, TICRATE); +} + +// +// I_SelectTimer +// Sets up the timer function based on if we can use signals for efficent CPU +// usage. +// +void I_SelectTimer() +{ + struct sigaction act; + struct itimerval itv; + + sigfillset (&act.sa_mask); + act.sa_flags = 0; + // [BL] This doesn't seem to be executed consistantly, I'm guessing the + // sleep functions are taking over the signal. So for now, lets just + // attach WaitForTic to signals in order to reduce CPU usage. + //act.sa_handler = I_HandleAlarm; + act.sa_handler = SIG_IGN; + + sigaction (SIGALRM, &act, NULL); + + itv.it_interval.tv_sec = itv.it_value.tv_sec = 0; + itv.it_interval.tv_usec = itv.it_value.tv_usec = 1000000/TICRATE; + + // [BL] See above. + I_GetTime = I_GetTimePolled; + I_FreezeTime = I_FreezeTimePolled; + + if (setitimer(ITIMER_REAL, &itv, NULL) != 0) + { + //I_GetTime = I_GetTimePolled; + //I_FreezeTime = I_FreezeTimePolled; + I_WaitForTic = I_WaitForTicPolled; + } + else + { +#ifdef HAVE_SIGTIMEDWAIT + sigemptyset(&SignalWaitSet); + sigaddset(&SignalWaitSet, SIGALRM); +#endif + + //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) { @@ -218,9 +342,9 @@ void I_Init (void) CheckCPUID (&CPU); DumpCPUInfo (&CPU); - I_GetTime = I_GetTimePolled; - I_WaitForTic = I_WaitForTicPolled; - I_FreezeTime = I_FreezeTimePolled; + I_GetTime = I_GetTimeSelect; + I_WaitForTic = I_WaitForTicSelect; + I_FreezeTime = I_FreezeTimeSelect; atterm (I_ShutdownSound); I_InitSound (); } @@ -489,6 +613,8 @@ int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad) { return I_PickIWad_Gtk (wads, numwads, showwin, defaultiwad); } +#elif defined(__APPLE__) + return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); #endif printf ("Please select a game wad (or 0 to exit):\n"); diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm new file mode 100644 index 000000000..a835c6dff --- /dev/null +++ b/src/sdl/iwadpicker_cocoa.mm @@ -0,0 +1,248 @@ +/* + ** iwadpicker_cocoa.mm + ** + ** Implements Mac OS X native IWAD Picker. + ** + **--------------------------------------------------------------------------- + ** Copyright 2010 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 "d_main.h" +#include "version.h" +#include + +enum +{ + COLUMN_IWAD, + COLUMN_GAME, + + NUM_COLUMNS +}; + +static const char* const tableHeaders[NUM_COLUMNS] = { "IWAD", "Game" }; + +// Class to convert the IWAD data into a form that Cocoa can use. +@interface IWADTableData : NSObject +{ + NSMutableArray *data; +} + +- (void)dealloc; +- (IWADTableData *)init:(WadStuff *) wads:(int) numwads; + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView; +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex; +@end + +@implementation IWADTableData + +- (void)dealloc +{ + [data release]; + + [super dealloc]; +} + +- (IWADTableData *)init:(WadStuff *) wads:(int) numwads +{ + data = [[NSMutableArray alloc] initWithCapacity:numwads]; + + for(int i = 0;i < numwads;i++) + { + NSMutableDictionary *record = [[NSMutableDictionary alloc] initWithCapacity:NUM_COLUMNS]; + const char* filename = strrchr(wads[i].Path, '/'); + if(filename == NULL) + filename = wads[i].Path; + else + filename++; + [record setObject:[NSString stringWithCString:filename] forKey:[NSString stringWithCString:tableHeaders[COLUMN_IWAD]]]; + [record setObject:[NSString stringWithCString:IWADInfos[wads[i].Type].Name] forKey:[NSString stringWithCString:tableHeaders[COLUMN_GAME]]]; + [data addObject:record]; + [record release]; + } + + return self; +} + +- (int)numberOfRowsInTableView:(NSTableView *)aTableView +{ + return [data count]; +} + +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex +{ + NSParameterAssert(rowIndex >= 0 && (unsigned int) rowIndex < [data count]); + NSMutableDictionary *record = [data objectAtIndex:rowIndex]; + return [record objectForKey:[aTableColumn identifier]]; +} + +@end + +// So we can listen for button actions and such we need to have an Obj-C class. +@interface IWADPicker : NSObject +{ + NSApplication *app; + NSWindow *window; + NSButton *okButton; + NSButton *cancelButton; + bool cancelled; +} + +- (void)buttonPressed:(id) sender; +- (void)doubleClicked:(id) sender; +- (void)makeLabel:(NSTextField *)label:(const char*) str; +- (int)pickIWad:(WadStuff *)wads:(int) numwads:(bool) showwin:(int) defaultiwad; +@end + +@implementation IWADPicker + +- (void)buttonPressed:(id) sender; +{ + if(sender == cancelButton) + cancelled = true; + + [window orderOut:self]; + [app stopModal]; +} + +- (void)doubleClicked:(id) sender; +{ + if ([sender clickedRow] >= 0) + { + [window orderOut:self]; + [app stopModal]; + } +} + +// Apparently labels in Cocoa are uneditable text fields, so lets make this a +// little more automated. +- (void)makeLabel:(NSTextField *)label:(const char*) str +{ + [label setStringValue:[NSString stringWithCString:str]]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + [label setEditable:NO]; + [label setSelectable:NO]; +} + +- (int)pickIWad:(WadStuff *)wads:(int) numwads:(bool) showwin:(int) defaultiwad +{ + cancelled = false; + + app = [NSApplication sharedApplication]; + id windowTitle = [NSString stringWithCString:GAMESIG " " DOTVERSIONSTR ": Select an IWAD to use"]; + + 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:"ZDoom 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)]; + NSTableView *iwadTable = [[NSTableView alloc] initWithFrame:[iwadScroller bounds]]; + IWADTableData *tableData = [[IWADTableData alloc] init:wads:numwads]; + for(int i = 0;i < NUM_COLUMNS;i++) + { + NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:[NSString stringWithCString:tableHeaders[i]]]; + [[column headerCell] setStringValue:[column identifier]]; + if(i == 0) + [column setMaxWidth:110]; + [column setEditable:NO]; + [column setResizingMask:NSTableColumnAutoresizingMask]; + [iwadTable addTableColumn:column]; + [column release]; + } + [iwadScroller setDocumentView:iwadTable]; + [iwadScroller setHasVerticalScroller:YES]; + [iwadTable setDataSource:tableData]; + [iwadTable sizeToFit]; + [iwadTable setDoubleAction:@selector(doubleClicked:)]; + [iwadTable setTarget:self]; + NSIndexSet *selection = [[NSIndexSet alloc] initWithIndex:defaultiwad]; + [iwadTable selectRowIndexes:selection byExtendingSelection:NO]; + [selection release]; + [iwadTable scrollRowToVisible:defaultiwad]; + [[window contentView] addSubview:iwadScroller]; + [iwadTable release]; + [iwadScroller release]; + + /*NSTextField *additionalParametersLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(17, 78, 144, 17)]; + [self makeLabel:additionalParametersLabel:"Additional Parameters"]; + [[window contentView] addSubview:additionalParametersLabel]; + NSTextField *additionalParameters = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 48, 360, 22)]; + [[window contentView] addSubview:additionalParameters];*/ + + // Doesn't look like the SDL version implements this so lets not show it. + /*NSButton *dontAsk = [[NSButton alloc] initWithFrame:NSMakeRect(18, 18, 178, 18)]; + [dontAsk setTitle:[NSString stringWithCString:"Don't ask me this again"]]; + [dontAsk setButtonType:NSSwitchButton]; + [dontAsk setState:(showwin ? NSOffState : NSOnState)]; + [[window contentView] addSubview:dontAsk];*/ + + okButton = [[NSButton alloc] initWithFrame:NSMakeRect(236, 12, 96, 32)]; + [okButton setTitle:[NSString stringWithCString:"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 stringWithCString:"Cancel"]]; + [cancelButton setBezelStyle:NSRoundedBezelStyle]; + [cancelButton setAction:@selector(buttonPressed:)]; + [cancelButton setTarget:self]; + [cancelButton setKeyEquivalent:@"\033"]; + [[window contentView] addSubview:cancelButton]; + + [window center]; + [app runModalForWindow:window]; + + [window release]; + [okButton release]; + [cancelButton release]; + + return cancelled ? -1 : [iwadTable selectedRow]; +} + +@end + +// Simple wrapper so we can call this from outside. +int I_PickIWad_Cocoa (WadStuff *wads, int numwads, bool showwin, int defaultiwad) +{ + IWADPicker *picker = [IWADPicker alloc]; + int ret = [picker pickIWad:wads:numwads:showwin:defaultiwad]; + [picker release]; + return ret; +} diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 671b73d21..23e374a7b 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -61,6 +61,7 @@ extern HWND Window; #include "v_video.h" #include "v_palette.h" #include "cmdlib.h" +#include "s_sound.h" // MACROS ------------------------------------------------------------------ @@ -114,8 +115,8 @@ CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_profile, false, 0) +CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); // Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter. CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -167,7 +168,10 @@ static const FEnumList OutputNames[] = { "SDL", 666 }, // Mac +#if FMOD_VERSION < 0x43000 + // Sound Manager support was removed sometime in the 4.29 line. { "Sound Manager", FMOD_OUTPUTTYPE_SOUNDMANAGER }, +#endif { "Core Audio", FMOD_OUTPUTTYPE_COREAUDIO }, { NULL, 0 } @@ -702,7 +706,8 @@ bool FMODSoundRenderer::Init() if (!ShowedBanner) { - Printf("FMOD Sound System, copyright © Firelight Technologies Pty, Ltd., 1994-2009.\n"); + // '\xa9' is the copyright symbol in the Windows-1252 code page. + Printf("FMOD Sound System, copyright \xa9 Firelight Technologies Pty, Ltd., 1994-2009.\n"); Printf("Loaded FMOD version %x.%02x.%02x\n", version >> 16, (version >> 8) & 255, version & 255); ShowedBanner = true; } @@ -1326,10 +1331,10 @@ FString FMODSoundRenderer::GatherStats() #endif out.Format ("%d channels,"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% CPU " - "(DSP:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% " - "Stream:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% " - "Geometry:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% " - "Update:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%%)", + "(DSP:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% " + "Stream:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% " + "Geometry:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% " + "Update:"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%%)", channels, total, dsp, stream, geometry, update); return out; } @@ -1432,6 +1437,108 @@ SoundStream *FMODSoundRenderer::CreateStream (SoundStreamCallback callback, int return capsule; } +//========================================================================== +// +// GetTagData +// +// Checks for a string-type tag, and returns its data. +// +//========================================================================== + +const char *GetTagData(FMOD::Sound *sound, const char *tag_name) +{ + FMOD_TAG tag; + + if (FMOD_OK == sound->getTag(tag_name, 0, &tag) && + (tag.datatype == FMOD_TAGDATATYPE_STRING || tag.datatype == FMOD_TAGDATATYPE_STRING_UTF8)) + { + return (const char *)tag.data; + } + return NULL; +} + +//========================================================================== +// +// SetCustomLoopPts +// +// Sets up custom sound loops by checking for these tags: +// LOOP_START +// LOOP_END +// LOOP_BIDI +// +//========================================================================== + +static void SetCustomLoopPts(FMOD::Sound *sound) +{ +#if 0 + FMOD_TAG tag; + int numtags; + if (FMOD_OK == stream->getNumTags(&numtags, NULL)) + { + for (int i = 0; i < numtags; ++i) + { + if (FMOD_OK == sound->getTag(NULL, i, &tag)) + { + Printf("Tag %2d. %d %s = %s\n", i, tag.datatype, tag.name, tag.data); + } + } + } +#endif + const char *tag_data; + unsigned int looppt[2]; + bool looppt_as_samples[2], have_looppt[2] = { false }; + static const char *const loop_tags[2] = { "LOOP_START", "LOOP_END" }; + + for (int i = 0; i < 2; ++i) + { + if (NULL != (tag_data = GetTagData(sound, loop_tags[i]))) + { + if (S_ParseTimeTag(tag_data, &looppt_as_samples[i], &looppt[i])) + { + have_looppt[i] = true; + } + else + { + Printf("Invalid %s tag: '%s'\n", loop_tags[i], tag_data); + } + } + } + if (have_looppt[0] && !have_looppt[1]) + { // Have a start tag, but not an end tag: End at the end of the song. + have_looppt[1] = (FMOD_OK == sound->getLength(&looppt[1], FMOD_TIMEUNIT_PCM)); + looppt_as_samples[1] = true; + } + else if (!have_looppt[0] && have_looppt[1]) + { // Have an end tag, but no start tag: Start at beginning of the song. + looppt[0] = 0; + looppt_as_samples[0] = true; + have_looppt[0] = true; + } + if (have_looppt[0] && have_looppt[1]) + { // Have both loop points: Try to set the loop. + FMOD_RESULT res = sound->setLoopPoints( + looppt[0], looppt_as_samples[0] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS, + looppt[1] - 1, looppt_as_samples[1] ? FMOD_TIMEUNIT_PCM : FMOD_TIMEUNIT_MS); + if (res != FMOD_OK) + { + Printf("Setting custom loop points failed. Error %d\n", res); + } + } + // Check for a bi-directional loop. + if (NULL != (tag_data = GetTagData(sound, "LOOP_BIDI")) && + (stricmp(tag_data, "on") == 0 || + stricmp(tag_data, "true") == 0 || + stricmp(tag_data, "yes") == 0 || + stricmp(tag_data, "1") == 0)) + { + FMOD_MODE mode; + if (FMOD_OK == (sound->getMode(&mode))) + { + sound->setMode((mode & ~(FMOD_LOOP_OFF | FMOD_LOOP_NORMAL)) | FMOD_LOOP_BIDI); + } + } +} + //========================================================================== // // FMODSoundRenderer :: OpenStream @@ -1493,6 +1600,7 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla } if (result == FMOD_OK) { + SetCustomLoopPts(stream); return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL); } return NULL; @@ -1534,7 +1642,15 @@ FISoundChannel *FMODSoundRenderer::StartSound(SoundHandle sfx, float vol, int pi mode = (mode & ~FMOD_3D) | FMOD_2D; if (flags & SNDF_LOOP) { - mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL; + mode &= ~FMOD_LOOP_OFF; + if (!(mode & (FMOD_LOOP_NORMAL | FMOD_LOOP_BIDI))) + { + mode |= FMOD_LOOP_NORMAL; + } + } + else + { + mode |= FMOD_LOOP_OFF; } chan->setMode(mode); chan->setChannelGroup((flags & SNDF_NOPAUSE) ? SfxGroup : PausableSfx); @@ -1634,7 +1750,16 @@ FISoundChannel *FMODSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener * } if (flags & SNDF_LOOP) { - mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL; + mode &= ~FMOD_LOOP_OFF; + if (!(mode & (FMOD_LOOP_NORMAL | FMOD_LOOP_BIDI))) + { + mode |= FMOD_LOOP_NORMAL; + } + } + else + { + // FMOD_LOOP_OFF overrides FMOD_LOOP_NORMAL and FMOD_LOOP_BIDI + mode |= FMOD_LOOP_OFF; } mode = SetChanHeadSettings(listener, chan, pos, !!(flags & SNDF_AREA), mode); chan->setMode(mode); @@ -1731,13 +1856,14 @@ bool FMODSoundRenderer::HandleChannelDelay(FMOD::Channel *chan, FISoundChannel * // Clamp the position of looping sounds to be within the sound. // If we try to start it several minutes past its normal end, // FMOD doesn't like that. + // FIXME: Clamp this right for loops that don't cover the whole sound. if (flags & SNDF_LOOP) { FMOD::Sound *sound; if (FMOD_OK == chan->getCurrentSound(&sound)) { unsigned int len; - if (FMOD_OK == sound->getLength(&len, FMOD_TIMEUNIT_MS) && len) + if (FMOD_OK == sound->getLength(&len, FMOD_TIMEUNIT_MS) && len != 0) { difftime %= len; } @@ -2279,6 +2405,7 @@ SoundHandle FMODSoundRenderer::LoadSound(BYTE *sfxdata, int length) DPrintf("Failed to allocate sample: Error %d\n", result); return retval; } + SetCustomLoopPts(sample); retval.data = sample; return retval; } diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 38e820c8b..172553df5 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -3,7 +3,7 @@ ** Plays music ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2010 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -84,6 +84,16 @@ extern void ChildSigHandler (int signum); #define GZIP_FNAME 8 #define GZIP_FCOMMENT 16 +enum EMIDIType +{ + MIDI_NOTMIDI, + MIDI_MIDI, + MIDI_HMI, + MIDI_MUS +}; + +extern int MUSHeaderSearch(const BYTE *head, int len); + EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Int, snd_mididevice) @@ -160,6 +170,18 @@ void MusInfo::TimidityVolumeChanged() { } +void MusInfo::FluidSettingInt(const char *, int) +{ +} + +void MusInfo::FluidSettingNum(const char *, double) +{ +} + +void MusInfo::FluidSettingStr(const char *, const char *) +{ +} + FString MusInfo::GetStats() { return "No stats available for this song"; @@ -291,13 +313,52 @@ MusInfo *I_RegisterURLSong (const char *url) return NULL; } +static MusInfo *CreateMIDISong(FILE *file, const char *filename, BYTE *musiccache, int offset, int len, EMIDIDevice devtype, EMIDIType miditype) +{ + if (devtype == MIDI_Timidity) + { + assert(miditype == MIDI_MIDI); + return new TimiditySong(file, musiccache, len); + } + else if (devtype >= MIDI_Null) + { + assert(miditype == MIDI_MIDI); + if (musiccache != NULL) + { + return new StreamSong((char *)musiccache, -1, len); + } + else + { + return new StreamSong(filename, offset, len); + } + } + else if (miditype == MIDI_MUS) + { + return new MUSSong2(file, musiccache, len, devtype); + } + else if (miditype == MIDI_MIDI) + { + return new MIDISong2(file, musiccache, len, devtype); + } + else if (miditype == MIDI_HMI) + { + return new HMISong(file, musiccache, len, devtype); + } + return NULL; +} + MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int len, int device) { FILE *file; MusInfo *info = NULL; - DWORD id; + union + { + DWORD id[32/4]; + BYTE idstr[32]; + }; const char *fmt; BYTE *ungzipped; + int i; if (nomusic) { @@ -322,22 +383,32 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int { fseek (file, offset, SEEK_SET); } - - if (fread (&id, 4, 1, file) != 1) + if (len < 32) + { + return 0; + } + if (fread (id, 4, 32/4, file) != 32/4) { fclose (file); return 0; } - fseek (file, -4, SEEK_CUR); + fseek (file, -32, SEEK_CUR); } else { file = NULL; - id = *(DWORD *)musiccache; + if (len < 32) + { + return 0; + } + for (i = 0; i < 32/4; ++i) + { + id[i] = ((DWORD *)musiccache)[i]; + } } #ifndef _WIN32 - // non-windows platforms don't support MDEV_MIDI so map to MDEV_FMOD + // non-Windows platforms don't support MDEV_MMAPI so map to MDEV_FMOD if (device == MDEV_MMAPI) device = MDEV_FMOD; #endif @@ -346,7 +417,7 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int // that can handle it, so it simplifies things if we make all songs // gzippable. ungzipped = NULL; - if ((id & MAKE_ID(255,255,255,0)) == GZIP_ID) + if ((id[0] & MAKE_ID(255,255,255,0)) == GZIP_ID) { if (offset != -1) { @@ -370,182 +441,159 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int return NULL; } musiccache = ungzipped; - id = *(DWORD *)ungzipped; + for (i = 0; i < 32/4; ++i) + { + id[i] = ((DWORD *)musiccache)[i]; + } } + EMIDIType miditype = MIDI_NOTMIDI; + // Check for MUS format - if (id == MAKE_ID('M','U','S',0x1a)) + // Tolerate sloppy wads by searching up to 32 bytes for the header + if (MUSHeaderSearch(idstr, sizeof(idstr)) >= 0) { - /* MUS are played as: - - OPL: - - if explicitly selected by $mididevice - - when snd_mididevice is -3 and no midi device is set for the song + miditype = MIDI_MUS; + } + // Check for HMI format + else + if (id[0] == MAKE_ID('H','M','I','-') && + id[1] == MAKE_ID('M','I','D','I') && + id[2] == MAKE_ID('S','O','N','G')) + { + miditype = MIDI_HMI; + } + // Check for MIDI format + else if (id[0] == MAKE_ID('M','T','h','d')) + { + miditype = MIDI_MIDI; + } - Timidity: - - if explicitly selected by $mididevice - - when snd_mididevice is -2 and no midi device is set for the song + if (miditype != MIDI_NOTMIDI) + { + TArray midi; + /* MIDI are played as: + - OPL: + - if explicitly selected by $mididevice + - when snd_mididevice is -3 and no midi device is set for the song - FMod: - - if explicitly selected by $mididevice - - when snd_mididevice is -1 and no midi device is set for the song - - as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0 + - Timidity: + - if explicitly selected by $mididevice + - when snd_mididevice is -2 and no midi device is set for the song - MMAPI (Win32 only): - - if explicitly selected by $mididevice (non-Win32 redirects this to FMOD) - - when snd_mididevice is >= 0 and no midi device is set for the song - - as fallback when both OPL and Timidity failed and snd_mididevice is >= 0 + - FMod: + - if explicitly selected by $mididevice + - when snd_mididevice is -1 and no midi device is set for the song + - as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0 + + - MMAPI (Win32 only): + - if explicitly selected by $mididevice (non-Win32 redirects this to FMOD) + - when snd_mididevice is >= 0 and no midi device is set for the song + - as fallback when both OPL and Timidity failed and snd_mididevice is >= 0 */ - if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL) + EMIDIDevice devtype = MIDI_Null; + + // Choose the type of MIDI device we want. + if (device == MDEV_FMOD || (snd_mididevice == -1 && device == MDEV_DEFAULT)) { - info = new MUSSong2 (file, musiccache, len, MIDI_OPL); + devtype = MIDI_FMOD; } - else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2)) + else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) { - info = new TimiditySong (file, musiccache, len); + devtype = MIDI_Timidity; + } + else if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) + { + devtype = MIDI_OPL; } else if (snd_mididevice == -4 && device == MDEV_DEFAULT) { - info = new MUSSong2(file, musiccache, len, MIDI_Timidity); + devtype = MIDI_GUS; } +#ifdef HAVE_FLUIDSYNTH + else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) + { + devtype = MIDI_Fluid; + } +#endif +#ifdef _WIN32 + else + { + devtype = MIDI_Win; + } +#endif + +retry_as_fmod: + if (miditype != MIDI_MIDI && devtype >= MIDI_Null) + { + // Convert to standard MIDI for external sequencers. + MIDIStreamer *streamer; + + if (miditype == MIDI_MUS) + { + streamer = new MUSSong2(file, musiccache, len, MIDI_Null); + } + else + { + assert(miditype == MIDI_HMI); + streamer = new HMISong(file, musiccache, len, MIDI_Null); + } + if (streamer->IsValid()) + { + streamer->CreateSMF(midi); + miditype = MIDI_MIDI; + musiccache = &midi[0]; + len = midi.Size(); + if (file != NULL) + { + fclose(file); + file = NULL; + } + } + delete streamer; + } + info = CreateMIDISong(file, filename, musiccache, offset, len, devtype, miditype); if (info != NULL && !info->IsValid()) { delete info; info = NULL; - device = MDEV_DEFAULT; } - if (info == NULL && (snd_mididevice == -1 || device == MDEV_FMOD) && device != MDEV_MMAPI) + if (info == NULL && devtype != MIDI_FMOD && snd_mididevice < 0) { - TArray midi; - bool midi_made = false; - - if (file == NULL) - { - midi_made = ProduceMIDI((BYTE *)musiccache, midi); - } - else - { - BYTE *mus = new BYTE[len]; - size_t did_read = fread(mus, 1, len, file); - if (did_read == (size_t)len) - { - midi_made = ProduceMIDI(mus, midi); - } - fseek(file, -(long)did_read, SEEK_CUR); - delete[] mus; - } - if (midi_made) - { - info = new StreamSong((char *)&midi[0], -1, midi.Size()); - if (!info->IsValid()) - { - delete info; - info = NULL; - } - } + devtype = MIDI_FMOD; + goto retry_as_fmod; } #ifdef _WIN32 - if (info == NULL) + if (info == NULL && devtype != MIDI_Win && snd_mididevice >= 0) { - info = new MUSSong2 (file, musiccache, len, MIDI_Win); + info = CreateMIDISong(file, filename, musiccache, offset, len, MIDI_Win, miditype); } -#endif // _WIN32 +#endif } - // Check for MIDI format - else + + // Check for various raw OPL formats + else if ( + (id[0] == MAKE_ID('R','A','W','A') && id[1] == MAKE_ID('D','A','T','A')) || // Rdos Raw OPL + (id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) || // DosBox Raw OPL + (id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B')) // Martin Fernandez's modified IMF { - if (id == MAKE_ID('M','T','h','d')) - { - // This is a midi file - - /* MIDI are played as: - OPL: - - if explicitly selected by $mididevice - - when snd_mididevice is -3 and no midi device is set for the song - - Timidity: - - if explicitly selected by $mididevice - - when snd_mididevice is -2 and no midi device is set for the song - - FMOD: - - if explicitly selected by $mididevice - - when snd_mididevice is -1 and no midi device is set for the song - - as fallback when Timidity failed unless snd_mididevice is >= 0 - - MMAPI (Win32 only): - - if explicitly selected by $mididevice (non-Win32 redirects this to FMOD) - - when snd_mididevice is >= 0 and no midi device is set for the song - - as fallback when Timidity failed and snd_mididevice is >= 0 - */ - if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) - { - info = new MIDISong2 (file, musiccache, len, MIDI_OPL); - } - else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) - { - info = new TimiditySong (file, musiccache, len); - } - else if (snd_mididevice == -4 && device == MDEV_DEFAULT) - { - info = new MIDISong2(file, musiccache, len, MIDI_Timidity); - } - if (info != NULL && !info->IsValid()) - { - delete info; - info = NULL; - device = MDEV_DEFAULT; - } -#ifdef _WIN32 - if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI)) - { - info = new MIDISong2 (file, musiccache, len, MIDI_Win); - } -#endif // _WIN32 - } - // Check for various raw OPL formats - else if (len >= 12 && - (id == MAKE_ID('R','A','W','A') || // Rdos Raw OPL - id == MAKE_ID('D','B','R','A') || // DosBox Raw OPL - id == MAKE_ID('A','D','L','I'))) // Martin Fernandez's modified IMF - { - DWORD fullsig[2]; - - if (file != NULL) - { - if (fread (fullsig, 4, 2, file) != 2) - { - fclose (file); - return 0; - } - fseek (file, -8, SEEK_CUR); - } - else - { - memcpy(fullsig, musiccache, 8); - } - - if ((fullsig[0] == MAKE_ID('R','A','W','A') && fullsig[1] == MAKE_ID('D','A','T','A')) || - (fullsig[0] == MAKE_ID('D','B','R','A') && fullsig[1] == MAKE_ID('W','O','P','L')) || - (fullsig[0] == MAKE_ID('A','D','L','I') && (fullsig[1] & MAKE_ID(255,255,0,0)) == MAKE_ID('B',1,0,0))) - { - info = new OPLMUSSong (file, musiccache, len); - } - } - // Check for game music - else if ((fmt = GME_CheckFormat(id)) != NULL && fmt[0] != '\0') - { - info = GME_OpenSong(file, musiccache, len, fmt); - } - // Check for module formats - else - { - info = MOD_OpenSong(file, musiccache, len); - } + info = new OPLMUSSong (file, musiccache, len); + } + // Check for game music + else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0') + { + info = GME_OpenSong(file, musiccache, len, fmt); + } + // Check for module formats + else + { + info = MOD_OpenSong(file, musiccache, len); } if (info == NULL) { // Check for CDDA "format" - if (id == (('R')|(('I')<<8)|(('F')<<16)|(('F')<<24))) + if (id[0] == (('R')|(('I')<<8)|(('F')<<16)|(('F')<<24))) { if (file != NULL) { @@ -572,7 +620,7 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int // smaller than this can't possibly be a valid music file if it hasn't // been identified already, so don't even bother trying to load it. // Of course MIDIs shorter than 1024 bytes should pass. - if (info == NULL && (len >= 1024 || id == MAKE_ID('M','T','h','d'))) + if (info == NULL && (len >= 1024 || id[0] == MAKE_ID('M','T','h','d'))) { // Let FMOD figure out what it is. if (file != NULL) @@ -786,7 +834,7 @@ CCMD (writeopl) } else { - Printf ("Usage: writeopl "); + Printf ("Usage: writeopl \n"); } } diff --git a/src/sound/i_music.h b/src/sound/i_music.h index 4bde02df1..88e6f61c0 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -98,6 +98,9 @@ public: virtual FString GetStats(); virtual MusInfo *GetOPLDumper(const char *filename); virtual MusInfo *GetWaveDumper(const char *filename, int rate); + virtual void FluidSettingInt(const char *setting, int value); // FluidSynth settings + virtual void FluidSettingNum(const char *setting, double value); // " + virtual void FluidSettingStr(const char *setting, const char *value); // " enum EState { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index a715e844c..8c6507167 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -95,6 +95,9 @@ public: virtual bool NeedThreadedCallback() = 0; virtual void PrecacheInstruments(const WORD *instruments, int count); virtual void TimidityVolumeChanged(); + virtual void FluidSettingInt(const char *setting, int value); + virtual void FluidSettingNum(const char *setting, double value); + virtual void FluidSettingStr(const char *setting, const char *value); virtual FString GetStats(); }; @@ -136,14 +139,14 @@ protected: }; #endif -// OPL implementation of a MIDI output device ------------------------------- +// Base class for software synthesizer MIDI output devices ------------------ -class OPLMIDIDevice : public MIDIDevice, protected OPLmusicBlock +class SoftSynthMIDIDevice : public MIDIDevice { public: - OPLMIDIDevice(); - ~OPLMIDIDevice(); - int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + SoftSynthMIDIDevice(); + ~SoftSynthMIDIDevice(); + void Close(); bool IsOpen() const; int GetTechnology() const; @@ -158,24 +161,51 @@ public: bool FakeVolume(); bool NeedThreadedCallback(); bool Pause(bool paused); - FString GetStats(); protected: - static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + FCriticalSection CritSec; + SoundStream *Stream; + double Tempo; + double Division; + double SamplesPerTick; + double NextTickIn; + MIDIHDR *Events; + bool Started; + DWORD Position; + int SampleRate; void (*Callback)(unsigned int, void *, DWORD, DWORD); void *CallbackData; - void CalcTickRate(); - void HandleEvent(int status, int parm1, int parm2); + virtual void CalcTickRate(); int PlayTick(); + int OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + virtual bool ServiceStream (void *buff, int numbytes); - SoundStream *Stream; - double Tempo; - double Division; - MIDIHDR *Events; - bool Started; - DWORD Position; + virtual void HandleEvent(int status, int parm1, int parm2) = 0; + virtual void HandleLongEvent(const BYTE *data, int len) = 0; + virtual void ComputeOutput(float *buffer, int len) = 0; +}; + +// OPL implementation of a MIDI output device ------------------------------- + +class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock +{ +public: + OPLMIDIDevice(); + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + void Close(); + int GetTechnology() const; + FString GetStats(); + +protected: + void CalcTickRate(); + int PlayTick(); + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); + bool ServiceStream(void *buff, int numbytes); }; // OPL dumper implementation of a MIDI output device ------------------------ @@ -193,52 +223,22 @@ public: namespace Timidity { struct Renderer; } -class TimidityMIDIDevice : public MIDIDevice +class TimidityMIDIDevice : public SoftSynthMIDIDevice { public: TimidityMIDIDevice(); - TimidityMIDIDevice(int rate); ~TimidityMIDIDevice(); int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); - void Close(); - bool IsOpen() const; - int GetTechnology() const; - int SetTempo(int tempo); - int SetTimeDiv(int timediv); - int StreamOut(MIDIHDR *data); - int StreamOutSync(MIDIHDR *data); - int Resume(); - void Stop(); - int PrepareHeader(MIDIHDR *data); - int UnprepareHeader(MIDIHDR *data); - bool FakeVolume(); - bool Pause(bool paused); - bool NeedThreadedCallback(); void PrecacheInstruments(const WORD *instruments, int count); - void TimidityVolumeChanged(); FString GetStats(); protected: - static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); - bool ServiceStream (void *buff, int numbytes); - - void (*Callback)(unsigned int, void *, DWORD, DWORD); - void *CallbackData; - - void CalcTickRate(); - int PlayTick(); - - FCriticalSection CritSec; - SoundStream *Stream; Timidity::Renderer *Renderer; - double Tempo; - double Division; - double SamplesPerTick; - double NextTickIn; - MIDIHDR *Events; - bool Started; - DWORD Position; + + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); }; // Internal TiMidity disk writing version of a MIDI device ------------------ @@ -255,6 +255,77 @@ protected: FILE *File; }; +// FluidSynth implementation of a MIDI device ------------------------------- + +#ifdef HAVE_FLUIDSYNTH +#ifndef DYN_FLUIDSYNTH +#include +#else +struct fluid_settings_t; +struct fluid_synth_t; +#endif + +class FluidSynthMIDIDevice : public SoftSynthMIDIDevice +{ +public: + FluidSynthMIDIDevice(); + ~FluidSynthMIDIDevice(); + + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + FString GetStats(); + void FluidSettingInt(const char *setting, int value); + void FluidSettingNum(const char *setting, double value); + void FluidSettingStr(const char *setting, const char *value); + +protected: + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); + int LoadPatchSets(const char *patches); + + fluid_settings_t *FluidSettings; + fluid_synth_t *FluidSynth; + +#ifdef DYN_FLUIDSYNTH + enum { FLUID_FAILED = 1, FLUID_OK = 0 }; + fluid_settings_t *(STACK_ARGS *new_fluid_settings)(); + fluid_synth_t *(STACK_ARGS *new_fluid_synth)(fluid_settings_t *); + int (STACK_ARGS *delete_fluid_synth)(fluid_synth_t *); + void (STACK_ARGS *delete_fluid_settings)(fluid_settings_t *); + int (STACK_ARGS *fluid_settings_setnum)(fluid_settings_t *, const char *, double); + int (STACK_ARGS *fluid_settings_setstr)(fluid_settings_t *, const char *, const char *); + int (STACK_ARGS *fluid_settings_setint)(fluid_settings_t *, const char *, int); + int (STACK_ARGS *fluid_settings_getstr)(fluid_settings_t *, const char *, char **); + int (STACK_ARGS *fluid_settings_getint)(fluid_settings_t *, const char *, int *); + int (STACK_ARGS *fluid_synth_set_interp_method)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_set_polyphony)(fluid_synth_t *, int); + int (STACK_ARGS *fluid_synth_get_polyphony)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_get_active_voice_count)(fluid_synth_t *); + double (STACK_ARGS *fluid_synth_get_cpu_load)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_system_reset)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_noteon)(fluid_synth_t *, int, int, int); + int (STACK_ARGS *fluid_synth_noteoff)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_cc)(fluid_synth_t *, int, int, int); + int (STACK_ARGS *fluid_synth_program_change)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_channel_pressure)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_pitch_bend)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_write_float)(fluid_synth_t *, int, void *, int, int, void *, int, int); + int (STACK_ARGS *fluid_synth_sfload)(fluid_synth_t *, const char *, int); + void (STACK_ARGS *fluid_synth_set_reverb)(fluid_synth_t *, double, double, double, double); + void (STACK_ARGS *fluid_synth_set_chorus)(fluid_synth_t *, int, double, double, double, int); + int (STACK_ARGS *fluid_synth_sysex)(fluid_synth_t *, const char *, int, char *, int *, int *, int); + +#ifdef _WIN32 + HMODULE FluidSynthDLL; +#else + void *FluidSynthSO; +#endif + bool LoadFluidSynth(); + void UnloadFluidSynth(); +#endif +}; +#endif + // Base class for streaming MUS and MIDI files ------------------------------ // MIDI device selection. @@ -262,6 +333,12 @@ enum EMIDIDevice { MIDI_Win, MIDI_OPL, + MIDI_GUS, + MIDI_Fluid, + + // only used by I_RegisterSong + MIDI_Null, + MIDI_FMOD, MIDI_Timidity }; @@ -282,23 +359,27 @@ public: bool IsValid() const; void Update(); FString GetStats(); + void FluidSettingInt(const char *setting, int value); + void FluidSettingNum(const char *setting, double value); + void FluidSettingStr(const char *setting, const char *value); + void CreateSMF(TArray &file); protected: MIDIStreamer(const char *dumpname, EMIDIDevice type); void OutputVolume (DWORD volume); int FillBuffer(int buffer_num, int max_events, DWORD max_time); - bool ServiceEvent(); + int ServiceEvent(); int VolumeControllerChange(int channel, int volume); static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2); // Virtuals for subclasses to override - virtual void CheckCaps(); + virtual void CheckCaps(int tech); virtual void DoInitialSetup() = 0; virtual void DoRestart() = 0; virtual bool CheckDone() = 0; - virtual void Precache() = 0; + virtual void Precache(); virtual DWORD *MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) = 0; enum @@ -338,6 +419,7 @@ protected: DWORD Volume; EMIDIDevice DeviceType; bool CallbackIsThreaded; + bool IgnoreLoops; FString DumpFilename; }; @@ -381,11 +463,10 @@ public: protected: MIDISong2(const MIDISong2 *original, const char *filename, EMIDIDevice type); // file dump constructor - void CheckCaps(); + void CheckCaps(int tech); void DoInitialSetup(); void DoRestart(); bool CheckDone(); - void Precache(); DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); void AdvanceTracks(DWORD time); @@ -405,6 +486,64 @@ protected: WORD DesignationMask; }; +// HMI file played with a MIDI stream --------------------------------------- + +class HMISong : public MIDIStreamer +{ +public: + HMISong(FILE *file, BYTE *musiccache, int length, EMIDIDevice type); + ~HMISong(); + + MusInfo *GetOPLDumper(const char *filename); + MusInfo *GetWaveDumper(const char *filename, int rate); + +protected: + HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor + + void CheckCaps(int tech); + void DoInitialSetup(); + void DoRestart(); + bool CheckDone(); + DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); + void AdvanceTracks(DWORD time); + + struct TrackInfo; + + void ProcessInitialMetaEvents (); + DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay); + TrackInfo *FindNextDue (); + void SetTempo(int new_tempo); + + struct AutoNoteOff + { + DWORD Delay; + BYTE Channel, Key; + }; + // Sorry, std::priority_queue, but I want to be able to modify the contents of the heap. + class NoteOffQueue : public TArray + { + public: + void AddNoteOff(DWORD delay, BYTE channel, BYTE key); + void AdvanceTime(DWORD time); + bool Pop(AutoNoteOff &item); + + protected: + void Heapify(); + + unsigned int Parent(unsigned int i) { return (i + 1u) / 2u - 1u; } + unsigned int Left(unsigned int i) { return (i + 1u) * 2u - 1u; } + unsigned int Right(unsigned int i) { return (i + 1u) * 2u; } + }; + + BYTE *MusHeader; + int SongLen; + int NumTracks; + TrackInfo *Tracks; + TrackInfo *TrackDue; + TrackInfo *FakeTrack; + NoteOffQueue NoteOffs; +}; + // Anything supported by FMOD out of the box -------------------------------- class StreamSong : public MusInfo diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index b25bbd6fb..0a946b3c1 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -81,6 +81,7 @@ CVAR (String, snd_output, "default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_pitched, false, CVAR_ARCHIVE) SoundRenderer *GSnd; +bool nosfx; void I_CloseSound (); @@ -234,7 +235,8 @@ public: void I_InitSound () { /* Get command line options: */ - bool nosound = !!Args->CheckParm ("-nosfx") || !!Args->CheckParm ("-nosound"); + bool nosound = !!Args->CheckParm ("-nosound"); + nosfx = !!Args->CheckParm ("-nosfx"); if (nosound) { diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index d8cf166fc..e5edcbb40 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -143,6 +143,7 @@ public: }; extern SoundRenderer *GSnd; +extern bool nosfx; void I_InitSound (); void I_ShutdownSound (); diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp new file mode 100644 index 000000000..d50e236b7 --- /dev/null +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -0,0 +1,728 @@ +/* +** music_fluidsynth_mididevice.cpp +** Provides access to FluidSynth as a generic MIDI device. +** +**--------------------------------------------------------------------------- +** Copyright 2010 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. +**--------------------------------------------------------------------------- +** +*/ + +#ifdef HAVE_FLUIDSYNTH + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +#ifdef DYN_FLUIDSYNTH + +#ifdef _WIN32 +#ifndef _M_X64 +#define FLUIDSYNTHLIB "fluidsynth.dll" +#else +#define FLUIDSYNTHLIB "fluidsynth64.dll" +#endif +#else +#include + +#define FLUIDSYNTHLIB "libfluidsynth.so.1" +#endif + +#define FLUID_REVERB_DEFAULT_ROOMSIZE 0.2f +#define FLUID_REVERB_DEFAULT_DAMP 0.0f +#define FLUID_REVERB_DEFAULT_WIDTH 0.5f +#define FLUID_REVERB_DEFAULT_LEVEL 0.9f + +#define FLUID_CHORUS_MOD_SINE 0 +#define FLUID_CHORUS_MOD_TRIANGLE 1 + +#define FLUID_CHORUS_DEFAULT_N 3 +#define FLUID_CHORUS_DEFAULT_LEVEL 2.0f +#define FLUID_CHORUS_DEFAULT_SPEED 0.3f +#define FLUID_CHORUS_DEFAULT_DEPTH 8.0f +#define FLUID_CHORUS_DEFAULT_TYPE FLUID_CHORUS_MOD_SINE + +#endif + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(String, fluid_patchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Float, fluid_gain, 0.5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 10) + self = 10; + else if (currSong != NULL) + currSong->FluidSettingNum("synth.gain", self); +} + +CUSTOM_CVAR(Bool, fluid_reverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (currSong != NULL) + currSong->FluidSettingStr("synth.reverb.active", self ? "yes" : "no"); +} + +CUSTOM_CVAR(Bool, fluid_chorus, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (currSong != NULL) + currSong->FluidSettingStr("synth.chorus.active", self ? "yes" : "no"); +} + +CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 16) + self = 16; + else if (self > 4096) + self = 4096; + else if (currSong != NULL) + currSong->FluidSettingInt("synth.polyphony", self); +} + +CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + // Values are: 0 = FLUID_INTERP_NONE + // 1 = FLUID_INTERP_LINEAR + // 4 = FLUID_INTERP_4THORDER (the FluidSynth default) + // 7 = FLUID_INTERP_7THORDER + // (And here I thought it was just a linear list.) + // Round undefined values to the nearest valid one. + if (self < 0) + self = 0; + else if (self == 2) + self = 1; + else if (self == 3 || self == 5) + self = 4; + else if (self == 6 || self > 7) + self = 7; + else if (currSong != NULL) + currSong->FluidSettingInt("synth.interpolation", self); +} + +CVAR(Int, fluid_samplerate, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +// I don't know if this setting even matters for us, since we aren't letting +// FluidSynth drives its own output. +CUSTOM_CVAR(Int, fluid_threads, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 1) + self = 1; + else if (self > 256) + self = 256; +} + +CUSTOM_CVAR(Float, fluid_reverb_roomsize, FLUID_REVERB_DEFAULT_ROOMSIZE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1.2f) + self = 1.2f; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_damping, FLUID_REVERB_DEFAULT_DAMP, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_width, FLUID_REVERB_DEFAULT_WIDTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 100) + self = 100; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_level, FLUID_REVERB_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Int, fluid_chorus_voices, FLUID_CHORUS_DEFAULT_N, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 99) + self = 99; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_chorus_level, FLUID_CHORUS_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_chorus_speed, FLUID_CHORUS_DEFAULT_SPEED, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0.29f) + self = 0.29f; + else if (self > 5) + self = 5; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +// depth is in ms and actual maximum depends on the sample rate +CUSTOM_CVAR(Float, fluid_chorus_depth, FLUID_CHORUS_DEFAULT_DEPTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 21) + self = 21; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Int, fluid_chorus_type, FLUID_CHORUS_DEFAULT_TYPE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self != FLUID_CHORUS_MOD_SINE && self != FLUID_CHORUS_MOD_TRIANGLE) + self = FLUID_CHORUS_DEFAULT_TYPE; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FluidSynthMIDIDevice Constructor +// +//========================================================================== + +FluidSynthMIDIDevice::FluidSynthMIDIDevice() +{ + FluidSynth = NULL; + FluidSettings = NULL; +#ifdef DYN_FLUIDSYNTH + if (!LoadFluidSynth()) + { + return; + } +#endif + FluidSettings = new_fluid_settings(); + if (FluidSettings == NULL) + { + printf("Failed to create FluidSettings.\n"); + return; + } + SampleRate = fluid_samplerate; + if (SampleRate < 22050 || SampleRate > 96000) + { // Match sample rate to SFX rate + SampleRate = clamp((int)GSnd->GetOutputRate(), 22050, 96000); + } + fluid_settings_setnum(FluidSettings, "synth.sample-rate", SampleRate); + fluid_settings_setnum(FluidSettings, "synth.gain", fluid_gain); + fluid_settings_setstr(FluidSettings, "synth.reverb.active", fluid_reverb ? "yes" : "no"); + fluid_settings_setstr(FluidSettings, "synth.chorus.active", fluid_chorus ? "yes" : "no"); + fluid_settings_setint(FluidSettings, "synth.polyphony", fluid_voices); + fluid_settings_setint(FluidSettings, "synth.cpu-cores", fluid_threads); + FluidSynth = new_fluid_synth(FluidSettings); + if (FluidSynth == NULL) + { + Printf("Failed to create FluidSynth.\n"); + return; + } + fluid_synth_set_interp_method(FluidSynth, -1, fluid_interp); + fluid_synth_set_reverb(FluidSynth, fluid_reverb_roomsize, fluid_reverb_damping, + fluid_reverb_width, fluid_reverb_level); + fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level, + fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type); + if (0 == LoadPatchSets(fluid_patchset)) + { +#ifdef unix + // This is the standard location on Ubuntu. + if (0 == LoadPatchSets("/usr/share/sounds/sf2/FluidR3_GS.sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2")) + { +#endif +#ifdef _WIN32 + // On Windows, look for the 4 megabyte patch set installed by Creative's drivers as a default. + char sysdir[MAX_PATH+sizeof("\\CT4MGM.SF2")]; + UINT filepart; + if (0 != (filepart = GetSystemDirectoryA(sysdir, MAX_PATH))) + { + strcat(sysdir, "\\CT4MGM.SF2"); + if (0 == LoadPatchSets(sysdir)) + { + // Try again with CT2MGM.SF2 + sysdir[filepart + 3] = '2'; + if (0 == LoadPatchSets(sysdir)) + { +#endif + Printf("Failed to load any MIDI patches.\n"); + delete_fluid_synth(FluidSynth); + FluidSynth = NULL; +#ifdef _WIN32 + } + } + } +#endif +#ifdef unix + } +#endif + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice Destructor +// +//========================================================================== + +FluidSynthMIDIDevice::~FluidSynthMIDIDevice() +{ + Close(); + if (FluidSynth != NULL) + { + delete_fluid_synth(FluidSynth); + } + if (FluidSettings != NULL) + { + delete_fluid_settings(FluidSettings); + } +#ifdef DYN_FLUIDSYNTH + UnloadFluidSynth(); +#endif +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Open +// +// Returns 0 on success. +// +//========================================================================== + +int FluidSynthMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + if (FluidSynth == NULL) + { + return 2; + } + int ret = OpenStream(4, 0, callback, userdata); + if (ret == 0) + { + fluid_synth_system_reset(FluidSynth); + } + return ret; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: HandleEvent +// +// Translates a MIDI event into FluidSynth calls. +// +//========================================================================== + +void FluidSynthMIDIDevice::HandleEvent(int status, int parm1, int parm2) +{ + int command = status & 0xF0; + int channel = status & 0x0F; + + switch (command) + { + case MIDI_NOTEOFF: + fluid_synth_noteoff(FluidSynth, channel, parm1); + break; + + case MIDI_NOTEON: + fluid_synth_noteon(FluidSynth, channel, parm1, parm2); + break; + + case MIDI_POLYPRESS: + break; + + case MIDI_CTRLCHANGE: + fluid_synth_cc(FluidSynth, channel, parm1, parm2); + break; + + case MIDI_PRGMCHANGE: + fluid_synth_program_change(FluidSynth, channel, parm1); + break; + + case MIDI_CHANPRESS: + fluid_synth_channel_pressure(FluidSynth, channel, parm1); + break; + + case MIDI_PITCHBEND: + fluid_synth_pitch_bend(FluidSynth, channel, (parm1 & 0x7f) | ((parm2 & 0x7f) << 7)); + break; + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: HandleLongEvent +// +// Handle SysEx messages. +// +//========================================================================== + +void FluidSynthMIDIDevice::HandleLongEvent(const BYTE *data, int len) +{ + if (len > 1 && (data[0] == 0xF0 || data[0] == 0xF7)) + { + fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - 1, NULL, NULL, NULL, 0); + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: ComputeOutput +// +//========================================================================== + +void FluidSynthMIDIDevice::ComputeOutput(float *buffer, int len) +{ + fluid_synth_write_float(FluidSynth, len, + buffer, 0, 2, + buffer, 1, 2); +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: LoadPatchSets +// +// Loads a delimiter-separated list of patch sets. This delimiter matches +// that of the PATH environment variable. On Windows, it is ';'. On other +// systems, it is ':'. Returns the number of patch sets loaded. +// +//========================================================================== + +int FluidSynthMIDIDevice::LoadPatchSets(const char *patches) +{ + int count; + char *wpatches = strdup(patches); + char *tok; +#ifdef _WIN32 + const char *const delim = ";"; +#else + const char *const delim = ":"; +#endif + + if (wpatches == NULL) + { + return 0; + } + tok = strtok(wpatches, delim); + count = 0; + while (tok != NULL) + { + if (FLUID_FAILED != fluid_synth_sfload(FluidSynth, tok, count == 0)) + { + DPrintf("Loaded patch set %s.\n", tok); + count++; + } + else + { + DPrintf("Failed to load patch set %s.\n", tok); + } + tok = strtok(NULL, delim); + } + free(wpatches); + return count; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingInt +// +// Changes an integer setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingInt(const char *setting, int value) +{ + if (FluidSynth == NULL || FluidSettings == NULL) + { + return; + } + + if (strcmp(setting, "synth.interpolation") == 0) + { + if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value)) + { + Printf("Setting interpolation method %d failed.\n", value); + } + } + else if (strcmp(setting, "synth.polyphony") == 0) + { + if (FLUID_OK != fluid_synth_set_polyphony(FluidSynth, value)) + { + Printf("Setting polyphony to %d failed.\n", value); + } + } + else if (strcmp(setting, "z.reverb-changed") == 0) + { + fluid_synth_set_reverb(FluidSynth, fluid_reverb_roomsize, fluid_reverb_damping, + fluid_reverb_width, fluid_reverb_level); + } + else if (strcmp(setting, "z.chorus-changed") == 0) + { + fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level, + fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type); + } + else if (FLUID_OK != fluid_settings_setint(FluidSettings, setting, value)) + { + Printf("Faild to set %s to %d.\n", setting, value); + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingNum +// +// Changes a numeric setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingNum(const char *setting, double value) +{ + if (FluidSettings != NULL) + { + if (!fluid_settings_setnum(FluidSettings, setting, value)) + { + Printf("Failed to set %s to %g.\n", setting, value); + } + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingStr +// +// Changes a string setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingStr(const char *setting, const char *value) +{ + if (FluidSettings != NULL) + { + if (!fluid_settings_setstr(FluidSettings, setting, value)) + { + Printf("Failed to set %s to %s.\n", setting, value); + } + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: GetStats +// +//========================================================================== + +FString FluidSynthMIDIDevice::GetStats() +{ + if (FluidSynth == NULL || FluidSettings == NULL) + { + return "FluidSynth is invalid"; + } + FString out; + + CritSec.Enter(); + int polyphony = fluid_synth_get_polyphony(FluidSynth); + int voices = fluid_synth_get_active_voice_count(FluidSynth); + double load = fluid_synth_get_cpu_load(FluidSynth); + char *chorus, *reverb; + int maxpoly; + fluid_settings_getstr(FluidSettings, "synth.chorus.active", &chorus); + fluid_settings_getstr(FluidSettings, "synth.reverb.active", &reverb); + fluid_settings_getint(FluidSettings, "synth.polyphony", &maxpoly); + CritSec.Leave(); + + out.Format("Voices: "TEXTCOLOR_YELLOW"%3d"TEXTCOLOR_NORMAL"/"TEXTCOLOR_ORANGE"%3d"TEXTCOLOR_NORMAL"("TEXTCOLOR_RED"%3d"TEXTCOLOR_NORMAL")" + TEXTCOLOR_YELLOW"%6.2f"TEXTCOLOR_NORMAL"%% CPU " + "Reverb: "TEXTCOLOR_YELLOW"%3s"TEXTCOLOR_NORMAL + " Chorus: "TEXTCOLOR_YELLOW"%3s", + voices, polyphony, maxpoly, load, reverb, chorus); + return out; +} + +#ifdef DYN_FLUIDSYNTH + +struct LibFunc +{ + void **FuncPointer; + const char *FuncName; +}; + +//========================================================================== +// +// FluidSynthMIDIDevice :: LoadFluidSynth +// +// Returns true if the FluidSynth library was successfully loaded. +// +//========================================================================== + +bool FluidSynthMIDIDevice::LoadFluidSynth() +{ + LibFunc imports[] = + { + { (void **)&new_fluid_settings, "new_fluid_settings" }, + { (void **)&new_fluid_synth, "new_fluid_synth" }, + { (void **)&delete_fluid_synth, "delete_fluid_synth" }, + { (void **)&delete_fluid_settings, "delete_fluid_settings" }, + { (void **)&fluid_settings_setnum, "fluid_settings_setnum" }, + { (void **)&fluid_settings_setstr, "fluid_settings_setstr" }, + { (void **)&fluid_settings_setint, "fluid_settings_setint" }, + { (void **)&fluid_settings_getstr, "fluid_settings_getstr" }, + { (void **)&fluid_settings_getint, "fluid_settings_getint" }, + { (void **)&fluid_synth_set_interp_method, "fluid_synth_set_interp_method" }, + { (void **)&fluid_synth_set_polyphony, "fluid_synth_set_polyphony" }, + { (void **)&fluid_synth_get_polyphony, "fluid_synth_get_polyphony" }, + { (void **)&fluid_synth_get_active_voice_count, "fluid_synth_get_active_voice_count" }, + { (void **)&fluid_synth_get_cpu_load, "fluid_synth_get_cpu_load" }, + { (void **)&fluid_synth_system_reset, "fluid_synth_system_reset" }, + { (void **)&fluid_synth_noteon, "fluid_synth_noteon" }, + { (void **)&fluid_synth_noteoff, "fluid_synth_noteoff" }, + { (void **)&fluid_synth_cc, "fluid_synth_cc" }, + { (void **)&fluid_synth_program_change, "fluid_synth_program_change" }, + { (void **)&fluid_synth_channel_pressure, "fluid_synth_channel_pressure" }, + { (void **)&fluid_synth_pitch_bend, "fluid_synth_pitch_bend" }, + { (void **)&fluid_synth_write_float, "fluid_synth_write_float" }, + { (void **)&fluid_synth_sfload, "fluid_synth_sfload" }, + { (void **)&fluid_synth_set_reverb, "fluid_synth_set_reverb" }, + { (void **)&fluid_synth_set_chorus, "fluid_synth_set_chorus" }, + { (void **)&fluid_synth_sysex, "fluid_synth_sysex" }, + }; + int fail = 0; + +#ifdef _WIN32 + FluidSynthDLL = LoadLibrary(FLUIDSYNTHLIB); + if (FluidSynthDLL == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB "\n"); + return false; + } +#else + FluidSynthSO = dlopen(FLUIDSYNTHLIB, RTLD_LAZY); + if (FluidSynthSO == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB ": %s\n", dlerror()); + return false; + } +#endif + + for (size_t i = 0; i < countof(imports); ++i) + { +#ifdef _WIN32 + FARPROC proc = GetProcAddress(FluidSynthDLL, imports[i].FuncName); +#else + void *proc = dlsym(FluidSynthSO, imports[i].FuncName); +#endif + if (proc == NULL) + { + Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, FLUIDSYNTHLIB); + fail++; + } + *imports[i].FuncPointer = (void *)proc; + } + if (fail == 0) + { + return true; + } + else + { +#ifdef _WIN32 + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; +#else + dlclose(FluidSynthSO); + FluidSynthSO = NULL; +#endif + return false; + } + +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: UnloadFluidSynth +// +//========================================================================== + +void FluidSynthMIDIDevice::UnloadFluidSynth() +{ +#ifdef _WIN32 + if (FluidSynthDLL != NULL) + { + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; + } +#else + if (FluidSynthSO != NULL) + { + dlclose(FluidSynthSO); + FluidSynthSO = NULL; + } +#endif +} + +#endif + +#endif + diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp new file mode 100644 index 000000000..c2075bf1a --- /dev/null +++ b/src/sound/music_hmi_midiout.cpp @@ -0,0 +1,880 @@ +/* +** music_midi_midiout.cpp +** Code to let ZDoom play HMI MIDI music through the MIDI streaming API. +** +**--------------------------------------------------------------------------- +** Copyright 2010 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 "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" + +// MACROS ------------------------------------------------------------------ + +#define SONG_MAGIC "HMI-MIDISONG" +#define TRACK_MAGIC "HMI-MIDITRACK" + +// Used by SendCommand to check for unexpected end-of-track conditions. +#define CHECK_FINISHED \ + if (track->TrackP >= track->MaxTrackP) \ + { \ + track->Finished = true; \ + return events; \ + } + +// In song header +#define TRACK_COUNT_OFFSET 0xE4 +#define TRACK_DIR_PTR_OFFSET 0xE8 + +// In track header +#define TRACK_DATA_PTR_OFFSET 0x57 +#define TRACK_DESIGNATION_OFFSET 0x99 + +#define NUM_DESIGNATIONS 8 + +// MIDI device types for designation +#define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device) +#define HMI_DEV_MPU401 0xA001 // MPU-401, Roland Sound Canvas, Ensoniq SoundScape, Rolad RAP-10 +#define HMI_DEV_OPL2 0xA002 // SoundBlaster (Pro), ESS AudioDrive +#define HMI_DEV_MT32 0xA004 // MT-32 +#define HMI_DEV_SBAWE32 0xA008 // SoundBlaster AWE32 +#define HMI_DEV_OPL3 0xA009 // SoundBlaster 16, Microsoft Sound System, Pro Audio Spectrum 16 +#define HMI_DEV_GUS 0xA00A // Gravis UltraSound, Gravis UltraSound Max/Ace + + +// Data accessors, since this data is highly likely to be unaligned. +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) +inline int GetShort(const BYTE *foo) +{ + return *(const short *)foo; +} +inline int GetInt(const BYTE *foo) +{ + return *(const int *)foo; +} +#else +inline int GetShort(const BYTE *foo) +{ + return short(foo[0] | (foo[1] << 8)); +} +inline int GetInt(const BYTE *foo) +{ + return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24)); +} +#endif + +// TYPES ------------------------------------------------------------------- + +struct HMISong::TrackInfo +{ + const BYTE *TrackBegin; + size_t TrackP; + size_t MaxTrackP; + DWORD Delay; + DWORD PlayedTime; + WORD Designation[NUM_DESIGNATIONS]; + bool Enabled; + bool Finished; + BYTE RunningStatus; + + DWORD ReadVarLen (); +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern char MIDI_EventLengths[7]; +extern char MIDI_CommonLengths[15]; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// HMISong Constructor +// +// Buffers the file and does some validation of the HMI header. +// +//========================================================================== + +HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) +: MIDIStreamer(type), MusHeader(0), Tracks(0) +{ + int p; + int i; + +#ifdef _WIN32 + if (ExitEvent == NULL) + { + return; + } +#endif + if (len < 0x100) + { // Way too small to be HMI. + return; + } + MusHeader = new BYTE[len]; + SongLen = len; + if (file != NULL) + { + if (fread(MusHeader, 1, len, file) != (size_t)len) + return; + } + else + { + memcpy(MusHeader, musiccache, len); + } + + // Do some validation of the MIDI file + if (memcmp(MusHeader, SONG_MAGIC, 12) != 0) + return; + + NumTracks = GetShort(MusHeader + TRACK_COUNT_OFFSET); + if (NumTracks <= 0) + { + return; + } + + // The division is the number of pulses per quarter note (PPQN). + Division = 60; + + Tracks = new TrackInfo[NumTracks + 1]; + int track_dir = GetInt(MusHeader + TRACK_DIR_PTR_OFFSET); + + // Gather information about each track + for (i = 0, p = 0; i < NumTracks; ++i) + { + int start = GetInt(MusHeader + track_dir + i*4); + int tracklen, datastart; + + if (start > len - TRACK_DESIGNATION_OFFSET - 4) + { // Track is incomplete. + continue; + } + + // BTW, HMI does not actually check the track header. + if (memcmp(MusHeader + start, TRACK_MAGIC, 13) != 0) + { + continue; + } + + // The track ends where the next one begins. If this is the + // last track, then it ends at the end of the file. + if (i == NumTracks - 1) + { + tracklen = len - start; + } + else + { + tracklen = GetInt(MusHeader + track_dir + i*4 + 4) - start; + } + // Clamp incomplete tracks to the end of the file. + tracklen = MIN(tracklen, len - start); + if (tracklen <= 0) + { + continue; + } + + // Offset to actual MIDI events. + datastart = GetInt(MusHeader + start + TRACK_DATA_PTR_OFFSET); + tracklen -= datastart; + if (tracklen <= 0) + { + continue; + } + + // Store track information + Tracks[p].TrackBegin = MusHeader + start + datastart; + Tracks[p].TrackP = 0; + Tracks[p].MaxTrackP = tracklen; + + // Retrieve track designations. We can't check them yet, since we have not yet + // connected to the MIDI device. + for (int ii = 0; ii < NUM_DESIGNATIONS; ++ii) + { + Tracks[p].Designation[ii] = GetShort(MusHeader + start + TRACK_DESIGNATION_OFFSET + ii*2); + } + + p++; + } + + // In case there were fewer actual chunks in the file than the + // header specified, update NumTracks with the current value of p. + NumTracks = p; + + if (NumTracks == 0) + { // No tracks, so nothing to play + return; + } +} + +//========================================================================== +// +// HMISong Destructor +// +//========================================================================== + +HMISong::~HMISong () +{ + if (Tracks != NULL) + { + delete[] Tracks; + } + if (MusHeader != NULL) + { + delete[] MusHeader; + } +} + +//========================================================================== +// +// HMISong :: CheckCaps +// +// Check track designations and disable tracks that have not been +// designated for the device we will be playing on. +// +//========================================================================== + +void HMISong::CheckCaps(int tech) +{ + // What's the equivalent HMI device for our technology? + if (tech == MOD_FMSYNTH) + { + tech = HMI_DEV_OPL3; + } + else if (tech == MOD_MIDIPORT) + { + tech = HMI_DEV_MPU401; + } + else + { // Good enough? Or should we just say we're GM. + tech = HMI_DEV_SBAWE32; + } + + for (int i = 0; i < NumTracks; ++i) + { + Tracks[i].Enabled = false; + // Track designations are stored in a 0-terminated array. + for (int j = 0; j < NUM_DESIGNATIONS && Tracks[i].Designation[j] != 0; ++j) + { + if (Tracks[i].Designation[j] == tech) + { + Tracks[i].Enabled = true; + } + // If a track is designated for device 0xA000, it will be played by a MIDI + // driver for device types 0xA000, 0xA001, and 0xA008. Why this does not + // include the GUS, I do not know. + else if (Tracks[i].Designation[j] == HMI_DEV_GM) + { + Tracks[i].Enabled = (tech == HMI_DEV_MPU401 || tech == HMI_DEV_SBAWE32); + } + // If a track is designated for device 0xA002, it will be played by a MIDI + // driver for device types 0xA002 or 0xA009. + else if (Tracks[i].Designation[j] == HMI_DEV_OPL2) + { + Tracks[i].Enabled = (tech == HMI_DEV_OPL3); + } + // Any other designation must match the specific MIDI driver device number. + // (Which we handled first above.) + + if (Tracks[i].Enabled) + { // This track's been enabled, so we can stop checking other designations. + break; + } + } + } +} + + +//========================================================================== +// +// HMISong :: DoInitialSetup +// +// Sets the starting channel volumes. +// +//========================================================================== + +void HMISong :: DoInitialSetup() +{ + for (int i = 0; i < 16; ++i) + { + ChannelVolumes[i] = 100; + } +} + +//========================================================================== +// +// HMISong :: DoRestart +// +// Rewinds every track. +// +//========================================================================== + +void HMISong :: DoRestart() +{ + int i; + + // Set initial state. + FakeTrack = &Tracks[NumTracks]; + NoteOffs.Clear(); + for (i = 0; i <= NumTracks; ++i) + { + Tracks[i].TrackP = 0; + Tracks[i].Finished = false; + Tracks[i].RunningStatus = 0; + Tracks[i].PlayedTime = 0; + } + ProcessInitialMetaEvents (); + for (i = 0; i < NumTracks; ++i) + { + Tracks[i].Delay = Tracks[i].ReadVarLen(); + } + Tracks[i].Delay = 0; // for the FakeTrack + Tracks[i].Enabled = true; + TrackDue = Tracks; + TrackDue = FindNextDue(); +} + +//========================================================================== +// +// HMISong :: CheckDone +// +//========================================================================== + +bool HMISong::CheckDone() +{ + return TrackDue == NULL; +} + +//========================================================================== +// +// HMISong :: MakeEvents +// +// Copies MIDI events from the file and puts them into a MIDI stream +// buffer. Returns the new position in the buffer. +// +//========================================================================== + +DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) +{ + DWORD *start_events; + DWORD tot_time = 0; + DWORD time = 0; + DWORD delay; + + start_events = events; + while (TrackDue && events < max_event_p && tot_time <= max_time) + { + // It's possible that this tick may be nothing but meta-events and + // not generate any real events. Repeat this until we actually + // get some output so we don't send an empty buffer to the MIDI + // device. + do + { + delay = TrackDue->Delay; + time += delay; + // Advance time for all tracks by the amount needed for the one up next. + tot_time += delay * Tempo / Division; + AdvanceTracks(delay); + // Play all events for this tick. + do + { + DWORD *new_events = SendCommand(events, TrackDue, time); + TrackDue = FindNextDue(); + if (new_events != events) + { + time = 0; + } + events = new_events; + } + while (TrackDue && TrackDue->Delay == 0 && events < max_event_p); + } + while (start_events == events && TrackDue); + time = 0; + } + return events; +} + +//========================================================================== +// +// HMISong :: AdvanceTracks +// +// Advaces time for all tracks by the specified amount. +// +//========================================================================== + +void HMISong::AdvanceTracks(DWORD time) +{ + for (int i = 0; i <= NumTracks; ++i) + { + if (Tracks[i].Enabled && !Tracks[i].Finished) + { + Tracks[i].Delay -= time; + Tracks[i].PlayedTime += time; + } + } + NoteOffs.AdvanceTime(time); +} + +//========================================================================== +// +// HMISong :: SendCommand +// +// Places a single MIDIEVENT in the event buffer. +// +//========================================================================== + +DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) +{ + DWORD len; + BYTE event, data1 = 0, data2 = 0; + + // If the next event comes from the fake track, pop an entry off the note-off queue. + if (track == FakeTrack) + { + AutoNoteOff off; + NoteOffs.Pop(off); + events[0] = delay; + events[1] = 0; + events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8); + return events + 3; + } + + CHECK_FINISHED + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe) + { + // Normal short message + if ((event & 0xF0) == 0xF0) + { + if (MIDI_CommonLengths[event & 15] > 0) + { + data1 = track->TrackBegin[track->TrackP++]; + if (MIDI_CommonLengths[event & 15] > 1) + { + data2 = track->TrackBegin[track->TrackP++]; + } + } + } + else if ((event & 0x80) == 0) + { + data1 = event; + event = track->RunningStatus; + } + else + { + track->RunningStatus = event; + data1 = track->TrackBegin[track->TrackP++]; + } + + CHECK_FINISHED + + if (MIDI_EventLengths[(event&0x70)>>4] == 2) + { + data2 = track->TrackBegin[track->TrackP++]; + } + + // Monitor channel volume controller changes. + if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70) && data1 == 7) + { + data2 = VolumeControllerChange(event & 15, data2); + } + + events[0] = delay; + events[1] = 0; + if (event != MIDI_META) + { + events[2] = event | (data1<<8) | (data2<<16); + } + else + { + events[2] = MEVT_NOP; + } + events += 3; + + if ((event & 0x70) == (MIDI_NOTEON & 0x70)) + { // HMI note on events include the time until an implied note off event. + NoteOffs.AddNoteOff(track->ReadVarLen(), event & 0x0F, data1); + } + } + else + { + // Skip SysEx events just because I don't want to bother with them. + // The old MIDI player ignored them too, so this won't break + // anything that played before. + if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) + { + len = track->ReadVarLen (); + track->TrackP += len; + } + else if (event == MIDI_META) + { + // It's a meta-event + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + len = track->ReadVarLen (); + CHECK_FINISHED + + if (track->TrackP + len <= track->MaxTrackP) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + Tempo = + (track->TrackBegin[track->TrackP+0]<<16) | + (track->TrackBegin[track->TrackP+1]<<8) | + (track->TrackBegin[track->TrackP+2]); + events[0] = delay; + events[1] = 0; + events[2] = (MEVT_TEMPO << 24) | Tempo; + events += 3; + break; + } + track->TrackP += len; + if (track->TrackP == track->MaxTrackP) + { + track->Finished = true; + } + } + else + { + track->Finished = true; + } + } + else if (event == 0xFE) + { // Skip unknown HMI events. + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + if (event == 0x13 || event == 0x15) + { + track->TrackP += 6; + } + else if (event == 0x12 || event == 0x14) + { + track->TrackP += 2; + } + else if (event == 0x10) + { + track->TrackP += 2; + CHECK_FINISHED + track->TrackP += track->TrackBegin[track->TrackP] + 5; + CHECK_FINISHED + } + else + { // No idea. + track->Finished = true; + } + } + } + if (!track->Finished) + { + track->Delay = track->ReadVarLen(); + } + return events; +} + +//========================================================================== +// +// HMISong :: ProcessInitialMetaEvents +// +// Handle all the meta events at the start of each track. +// +//========================================================================== + +void HMISong::ProcessInitialMetaEvents () +{ + TrackInfo *track; + int i; + BYTE event; + DWORD len; + + for (i = 0; i < NumTracks; ++i) + { + track = &Tracks[i]; + while (!track->Finished && + track->TrackP < track->MaxTrackP - 4 && + track->TrackBegin[track->TrackP] == 0 && + track->TrackBegin[track->TrackP+1] == 0xFF) + { + event = track->TrackBegin[track->TrackP+2]; + track->TrackP += 3; + len = track->ReadVarLen (); + if (track->TrackP + len <= track->MaxTrackP) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + SetTempo( + (track->TrackBegin[track->TrackP+0]<<16) | + (track->TrackBegin[track->TrackP+1]<<8) | + (track->TrackBegin[track->TrackP+2]) + ); + break; + } + } + track->TrackP += len; + } + if (track->TrackP >= track->MaxTrackP - 4) + { + track->Finished = true; + } + } +} + +//========================================================================== +// +// HMISong :: TrackInfo :: ReadVarLen +// +// Reads a variable-length SMF number. +// +//========================================================================== + +DWORD HMISong::TrackInfo::ReadVarLen () +{ + DWORD time = 0, t = 0x80; + + while ((t & 0x80) && TrackP < MaxTrackP) + { + t = TrackBegin[TrackP++]; + time = (time << 7) | (t & 127); + } + return time; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: AddNoteOff +// +//========================================================================== + +void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) +{ + unsigned int i = Reserve(1); + while (i > 0 && (*this)[Parent(i)].Delay > delay) + { + (*this)[i] = (*this)[Parent(i)]; + i = Parent(i); + } + (*this)[i].Delay = delay; + (*this)[i].Channel = channel; + (*this)[i].Key = key; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: Pop +// +//========================================================================== + +bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item) +{ + item = (*this)[0]; + if (TArray::Pop((*this)[0])) + { + Heapify(); + return true; + } + return false; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: AdvanceTime +// +//========================================================================== + +void HMISong::NoteOffQueue::AdvanceTime(DWORD time) +{ + // Because the time is decreasing by the same amount for every entry, + // the heap property is maintained. + for (unsigned int i = 0; i < Size(); ++i) + { + assert((*this)[i].Delay >= time); + (*this)[i].Delay -= time; + } +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: Heapify +// +//========================================================================== + +void HMISong::NoteOffQueue::Heapify() +{ + unsigned int i = 0; + for (;;) + { + unsigned int l = Left(i); + unsigned int r = Right(i); + unsigned int smallest = i; + if (l < Size() && (*this)[l].Delay < (*this)[i].Delay) + { + smallest = l; + } + if (r < Size() && (*this)[r].Delay < (*this)[smallest].Delay) + { + smallest = r; + } + if (smallest == i) + { + break; + } + swapvalues((*this)[i], (*this)[smallest]); + i = smallest; + } +} + +//========================================================================== +// +// HMISong :: FindNextDue +// +// Scans every track for the next event to play. Returns NULL if all events +// have been consumed. +// +//========================================================================== + +HMISong::TrackInfo *HMISong::FindNextDue () +{ + TrackInfo *track; + DWORD best; + int i; + + if (TrackDue != FakeTrack && !TrackDue->Finished && TrackDue->Delay == 0) + { + return TrackDue; + } + + // Check regular tracks. + track = NULL; + best = 0xFFFFFFFF; + for (i = 0; i < NumTracks; ++i) + { + if (Tracks[i].Enabled && !Tracks[i].Finished && Tracks[i].Delay < best) + { + best = Tracks[i].Delay; + track = &Tracks[i]; + } + } + // Check automatic note-offs. + if (NoteOffs.Size() != 0 && NoteOffs[0].Delay <= best) + { + FakeTrack->Delay = NoteOffs[0].Delay; + return FakeTrack; + } + return track; +} + + +//========================================================================== +// +// HMISong :: SetTempo +// +// Sets the tempo from a track's initial meta events. +// +//========================================================================== + +void HMISong::SetTempo(int new_tempo) +{ + if (0 == MIDI->SetTempo(new_tempo)) + { + Tempo = new_tempo; + } +} + +//========================================================================== +// +// HMISong :: GetOPLDumper +// +//========================================================================== + +MusInfo *HMISong::GetOPLDumper(const char *filename) +{ + return new HMISong(this, filename, MIDI_OPL); +} + +//========================================================================== +// +// HMISong :: GetWaveDumper +// +//========================================================================== + +MusInfo *HMISong::GetWaveDumper(const char *filename, int rate) +{ + return new HMISong(this, filename, MIDI_GUS); +} + +//========================================================================== +// +// HMISong File Dumping Constructor +// +//========================================================================== + +HMISong::HMISong(const HMISong *original, const char *filename, EMIDIDevice type) +: MIDIStreamer(filename, type) +{ + SongLen = original->SongLen; + MusHeader = new BYTE[original->SongLen]; + memcpy(MusHeader, original->MusHeader, original->SongLen); + NumTracks = original->NumTracks; + Division = original->Division; + Tempo = InitialTempo = original->InitialTempo; + Tracks = new TrackInfo[NumTracks]; + for (int i = 0; i < NumTracks; ++i) + { + TrackInfo *newtrack = &Tracks[i]; + const TrackInfo *oldtrack = &original->Tracks[i]; + + newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader); + newtrack->TrackP = 0; + newtrack->MaxTrackP = oldtrack->MaxTrackP; + } +} diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index d7983a70f..39f42cc35 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -9,6 +9,7 @@ static DWORD nummididevices; static bool nummididevicesset; + #ifdef _WIN32 UINT mididevice; @@ -19,7 +20,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) if (!nummididevicesset) return; - if ((self >= (signed)nummididevices) || (self < -4)) + if ((self >= (signed)nummididevices) || (self < -5)) { Printf ("ID out of range. Using default device.\n"); self = 0; @@ -74,25 +75,34 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) { if (*outValues == NULL) { - int count = 3 + nummididevices; + int count = 4 + nummididevices; value_t *values; UINT id; - int p; + int p = 0; *outValues = values = new value_t[count]; - values[0].name = "OPL Synth Emulation"; - values[0].value = -3.0; - values[1].name = "TiMidity++"; - values[1].value = -2.0; - values[2].name = "FMOD"; - values[2].value = -1.0; - for (id = 0, p = 3; id < nummididevices; ++id) +#ifdef HAVE_FLUIDSYNTH + values[p].name = "FluidSynth"; + values[p].value = -5.0; + ++p; +#endif + values[p].name = "OPL Synth Emulation"; + values[p].value = -3.0; + ++p; + values[p].name = "TiMidity++"; + values[p].value = -2.0; + ++p; + values[p].name = "FMOD"; + values[p].value = -1.0; + ++p; + for (id = 0; id < nummididevices; ++id) { MIDIOUTCAPS caps; MMRESULT res; res = midiOutGetDevCaps (id, &caps, sizeof(caps)); + assert(res == MMSYSERR_NOERROR); if (res == MMSYSERR_NOERROR) { size_t len = strlen (caps.szPname) + 1; @@ -104,8 +114,7 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) ++p; } } - assert(p == count); - *numValues = float(count); + *numValues = float(p); } } @@ -151,6 +160,9 @@ CCMD (snd_listmididevices) MIDIOUTCAPS caps; MMRESULT res; +#ifdef HAVE_FLUIDSYNTH + PrintMidiDevice (-5, "FluidSynth", MOD_SWSYNTH, 0); +#endif PrintMidiDevice (-3, "Emulated OPL FM Synth", MOD_FMSYNTH, 0); PrintMidiDevice (-2, "TiMidity++", 0, MOD_SWSYNTH); PrintMidiDevice (-1, "FMOD", 0, MOD_SWSYNTH); @@ -177,8 +189,8 @@ CCMD (snd_listmididevices) CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { - if (self < -3) - self = -3; + if (self < -5) + self = -5; else if (self > -1) self = -1; } @@ -188,21 +200,33 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) if (*outValues == NULL) { value_t *values; + int p = 0; - *outValues = values = new value_t[3]; + *outValues = values = new value_t[4]; - values[0].name = "OPL Synth Emulation"; - values[0].value = -3.0; - values[1].name = "TiMidity++"; - values[1].value = -2.0; - values[2].name = "FMOD"; - values[2].value = -1.0; - *numValues = 3.f; +#ifdef HAVE_FLUIDSYNTH + values[p].name = "FluidSynth"; + values[p].value = -5.0; + ++p; +#endif + values[p].name = "OPL Synth Emulation"; + values[p].value = -3.0; + ++p; + values[p].name = "TiMidity++"; + values[p].value = -2.0; + ++p; + values[p].name = "FMOD"; + values[p].value = -1.0; + ++p; + *numValues = float(p); } } CCMD (snd_listmididevices) { +#ifdef HAVE_FLUIDSYNTH + Printf("%s-5. FluidSynth\n", -5 == snd_mididevice ? TEXTCOLOR_BOLD : ""); +#endif Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-1. FMOD\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index 15e69c8a6..6e2acb32d 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -207,7 +207,7 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) BYTE *buf; - if (file!=NULL) + if (file != NULL) { buf = new BYTE[len]; fread (buf, 1, len, file); @@ -217,18 +217,8 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) buf = musiccache; } - - // The file type has already been checked before this class instance was - // created, so we only need to check one character to determine if this - // is a MUS or MIDI file and write it to disk as appropriate. - if (buf[1] == 'T') - { - success = (fwrite (buf, 1, len, f) == (size_t)len); - } - else - { - success = ProduceMIDI (buf, f); - } + // Write to temporary file + success = (fwrite (buf, 1, len, f) == (size_t)len); fclose (f); if (file != NULL) { diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 6771906a0..454ff0d3f 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -49,6 +49,8 @@ // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- +static void WriteVarLen (TArray &file, DWORD value); + // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR(Float, snd_musicvolume) @@ -57,8 +59,21 @@ EXTERN_CVAR(Float, snd_musicvolume) extern UINT mididevice; #endif +extern char MIDI_EventLengths[7]; + // PRIVATE DATA DEFINITIONS ------------------------------------------------ +static const BYTE StaticMIDIhead[] = +{ + 'M','T','h','d', 0, 0, 0, 6, + 0, 0, // format 0: only one track + 0, 1, // yes, there is really only one track + 0, 0, // divisions (filled in) + 'M','T','r','k', 0, 0, 0, 0, + // The first event sets the tempo (filled in) + 0, 255, 81, 3, 0, 0, 0 +}; + // PUBLIC DATA DEFINITIONS ------------------------------------------------- // CODE -------------------------------------------------------------------- @@ -172,7 +187,7 @@ bool MIDIStreamer::IsValid() const // //========================================================================== -void MIDIStreamer::CheckCaps() +void MIDIStreamer::CheckCaps(int tech) { } @@ -200,7 +215,7 @@ void MIDIStreamer::Play(bool looping, int subsong) { MIDI = new OPLDumperMIDIDevice(DumpFilename); } - else if (DeviceType == MIDI_Timidity) + else if (DeviceType == MIDI_GUS) { MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0); } @@ -215,27 +230,38 @@ void MIDIStreamer::Play(bool looping, int subsong) assert(0); // Intentional fall-through for non-Windows systems. - case MIDI_Timidity: +#ifdef HAVE_FLUIDSYNTH + case MIDI_Fluid: + MIDI = new FluidSynthMIDIDevice; + break; +#endif + + case MIDI_GUS: MIDI = new TimidityMIDIDevice; break; case MIDI_OPL: MIDI = new OPLMIDIDevice; break; + + default: + MIDI = NULL; + break; } #ifndef _WIN32 - assert(MIDI->NeedThreadedCallback() == false); + assert(MIDI == NULL || MIDI->NeedThreadedCallback() == false); #endif - if (0 != MIDI->Open(Callback, this)) + if (MIDI == NULL || 0 != MIDI->Open(Callback, this)) { Printf(PRINT_BOLD, "Could not open MIDI out device\n"); return; } - CheckCaps(); + CheckCaps(MIDI->GetTechnology()); Precache(); + IgnoreLoops = false; // Set time division and tempo. if (0 != MIDI->SetTimeDiv(Division) || @@ -435,6 +461,48 @@ void MIDIStreamer::TimidityVolumeChanged() } } +//========================================================================== +// +// MIDIStreamer :: FluidSettingInt +// +//========================================================================== + +void MIDIStreamer::FluidSettingInt(const char *setting, int value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingInt(setting, value); + } +} + +//========================================================================== +// +// MIDIStreamer :: FluidSettingNum +// +//========================================================================== + +void MIDIStreamer::FluidSettingNum(const char *setting, double value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingNum(setting, value); + } +} + +//========================================================================== +// +// MIDIDeviceStreamer :: FluidSettingStr +// +//========================================================================== + +void MIDIStreamer::FluidSettingStr(const char *setting, const char *value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingStr(setting, value); + } +} + //========================================================================== // @@ -466,7 +534,7 @@ void MIDIStreamer::OutputVolume (DWORD volume) int MIDIStreamer::VolumeControllerChange(int channel, int volume) { ChannelVolumes[channel] = volume; - return ((volume + 1) * Volume) >> 16; + return IgnoreLoops ? volume : ((volume + 1) * Volume) >> 16; } //========================================================================== @@ -518,9 +586,59 @@ void MIDIStreamer::Update() if (PlayerThread != NULL && WaitForSingleObject(PlayerThread, 0) == WAIT_OBJECT_0) { + static const char *const MMErrorCodes[] = + { + "No error", + "Unspecified error", + "Device ID out of range", + "Driver failed enable", + "Device already allocated", + "Device handle is invalid", + "No device driver present", + "Memory allocation error", + "Function isn't supported", + "Error value out of range", + "Invalid flag passed", + "Invalid parameter passed", + "Handle being used simultaneously on another thread", + "Specified alias not found", + "Bad registry database", + "Registry key not found", + "Registry read error", + "Registry write error", + "Registry delete error", + "Registry value not found", + "Driver does not call DriverCallback", + "More data to be returned", + }; + static const char *const MidiErrorCodes[] = + { + "MIDI header not prepared", + "MIDI still playing something", + "MIDI no configured instruments", + "MIDI hardware is still busy", + "MIDI port no longer connected", + "MIDI invalid MIF", + "MIDI operation unsupported with open mode", + "MIDI through device 'eating' a message", + }; + DWORD code = 0xABADCAFE; + GetExitCodeThread(PlayerThread, &code); CloseHandle(PlayerThread); PlayerThread = NULL; - Printf ("MIDI playback failure\n"); + Printf ("MIDI playback failure: "); + if (code >= 0 && code < countof(MMErrorCodes)) + { + Printf("%s\n", MMErrorCodes[code]); + } + else if (code >= MIDIERR_BASE && code < MIDIERR_BASE + countof(MidiErrorCodes)) + { + Printf("%s\n", MidiErrorCodes[code - MIDIERR_BASE]); + } + else + { + Printf("%08x\n", code); + } Stop(); } #endif @@ -553,6 +671,7 @@ DWORD WINAPI MIDIStreamer::PlayerProc (LPVOID lpParameter) DWORD MIDIStreamer::PlayerLoop() { HANDLE events[2] = { BufferDoneEvent, ExitEvent }; + int res; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); @@ -561,9 +680,9 @@ DWORD MIDIStreamer::PlayerLoop() switch (WaitForMultipleObjects(2, events, FALSE, INFINITE)) { case WAIT_OBJECT_0: - if (ServiceEvent()) + if (0 != (res = ServiceEvent())) { - return 1; + return res; } break; @@ -572,7 +691,7 @@ DWORD MIDIStreamer::PlayerLoop() default: // Should not happen. - return 1; + return MMSYSERR_ERROR; } } } @@ -584,28 +703,31 @@ DWORD MIDIStreamer::PlayerLoop() // // Fills the buffer that just finished playing with new events and appends // it to the MIDI stream queue. Stops the song if playback is over. Returns -// true if a problem occured and playback should stop. +// non-zero if a problem occured and playback should stop. // //========================================================================== -bool MIDIStreamer::ServiceEvent() +int MIDIStreamer::ServiceEvent() { + int res; + if (EndQueued == 1) { - return false; + return 0; } - if (0 != MIDI->UnprepareHeader(&Buffer[BufferNum])) + if (0 != (res = MIDI->UnprepareHeader(&Buffer[BufferNum]))) { - return true; + return res; } fill: - switch (FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME)) + res = FillBuffer(BufferNum, MAX_EVENTS, MAX_TIME); + switch (res & 3) { case SONG_MORE: - if ((MIDI->NeedThreadedCallback() && 0 != MIDI->StreamOutSync(&Buffer[BufferNum])) || - (!MIDI->NeedThreadedCallback() && 0 != MIDI->StreamOut(&Buffer[BufferNum]))) + if ((MIDI->NeedThreadedCallback() && 0 != (res = MIDI->StreamOutSync(&Buffer[BufferNum]))) || + (!MIDI->NeedThreadedCallback() && 0 != (res = MIDI->StreamOut(&Buffer[BufferNum])))) { - return true; + return res; } else { @@ -623,16 +745,16 @@ fill: break; default: - return true; + return res >> 2; } - return false; + return 0; } //========================================================================== // // MIDIStreamer :: FillBuffer // -// Copies MIDI events from the SMF and puts them into a MIDI stream +// Copies MIDI events from the MIDI file and puts them into a MIDI stream // buffer. Filling the buffer stops when the song end is encountered, the // buffer space is used up, or the maximum time for a buffer is hit. // @@ -720,13 +842,249 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) Buffer[buffer_num].lpData = (LPSTR)Events[buffer_num]; Buffer[buffer_num].dwBufferLength = DWORD((LPSTR)events - Buffer[buffer_num].lpData); Buffer[buffer_num].dwBytesRecorded = Buffer[buffer_num].dwBufferLength; - if (0 != MIDI->PrepareHeader(&Buffer[buffer_num])) + if (0 != (i = MIDI->PrepareHeader(&Buffer[buffer_num]))) { - return SONG_ERROR; + return SONG_ERROR | (i << 2); } return SONG_MORE; } +//========================================================================== +// +// MIDIStreamer :: Precache +// +// Generates a list of instruments this song uses and passes them to the +// MIDI device for precaching. The default implementation here pretends to +// play the song and watches for program change events on normal channels +// and note on events on channel 10. +// +//========================================================================== + +void MIDIStreamer::Precache() +{ + BYTE found_instruments[256] = { 0, }; + BYTE found_banks[256] = { 0, }; + bool multiple_banks = false; + + IgnoreLoops = true; + DoRestart(); + found_banks[0] = true; // Bank 0 is always used. + found_banks[128] = true; + + // Simulate playback to pick out used instruments. + while (!CheckDone()) + { + DWORD *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600); + for (DWORD *event = Events[0]; event < event_end; ) + { + if (MEVT_EVENTTYPE(event[2]) == 0) + { + int command = (event[2] & 0x70); + int channel = (event[2] & 0x0f); + int data1 = (event[2] >> 8) & 0x7f; + int data2 = (event[2] >> 16) & 0x7f; + + if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70)) + { + found_instruments[data1] = true; + } + else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0) + { // On a percussion channel, program change also serves as bank select. + multiple_banks = true; + found_banks[data1 | 128] = true; + } + else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) + { + found_instruments[data1 | 128] = true; + } + else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0) + { + multiple_banks = true; + if (channel == 9) + { + found_banks[data2 | 128] = true; + } + else + { + found_banks[data2] = true; + } + } + } + // Advance to next event + if (event[2] < 0x80000000) + { // short message + event += 3; + } + else + { // long message + event += 3 + ((MEVT_EVENTPARM(event[2]) + 3) >> 2); + } + } + } + DoRestart(); + + // Now pack everything into a contiguous region for the PrecacheInstruments call(). + TArray packed; + + for (int i = 0; i < 256; ++i) + { + if (found_instruments[i]) + { + WORD packnum = (i & 127) | ((i & 128) << 7); + if (!multiple_banks) + { + packed.Push(packnum); + } + else + { // In order to avoid having to multiplex tracks in a type 1 file, + // precache every used instrument in every used bank, even if not + // all combinations are actually used. + for (int j = 0; j < 128; ++j) + { + if (found_banks[j + (i & 128)]) + { + packed.Push(packnum | (j << 7)); + } + } + } + } + } + MIDI->PrecacheInstruments(&packed[0], packed.Size()); +} + +//========================================================================== +// +// MIDIStreamer :: CreateSMF +// +// Simulates playback to create a Standard MIDI File. +// +//========================================================================== + +void MIDIStreamer::CreateSMF(TArray &file) +{ + DWORD delay = 0; + BYTE running_status = 0; + + // Always create songs aimed at GM devices. + CheckCaps(MOD_MIDIPORT); + IgnoreLoops = true; + DoRestart(); + Tempo = InitialTempo; + + file.Reserve(sizeof(StaticMIDIhead)); + memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); + file[12] = Division >> 8; + file[13] = Division & 0xFF; + file[26] = InitialTempo >> 16; + file[27] = InitialTempo >> 8; + file[28] = InitialTempo; + + while (!CheckDone()) + { + DWORD *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600); + for (DWORD *event = Events[0]; event < event_end; ) + { + delay += event[0]; + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + WriteVarLen(file, delay); + delay = 0; + DWORD tempo = MEVT_EVENTPARM(event[2]); + file.Push(MIDI_META); + file.Push(MIDI_META_TEMPO); + file.Push(3); + file.Push(BYTE(tempo >> 16)); + file.Push(BYTE(tempo >> 8)); + file.Push(BYTE(tempo)); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { + WriteVarLen(file, delay); + delay = 0; + DWORD len = MEVT_EVENTPARM(event[2]); + BYTE *bytes = (BYTE *)&event[3]; + if (bytes[0] == MIDI_SYSEX) + { + len--; + file.Push(MIDI_SYSEX); + WriteVarLen(file, len); + memcpy(&file[file.Reserve(len - 1)], bytes, len); + } + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { + WriteVarLen(file, delay); + delay = 0; + BYTE status = BYTE(event[2]); + if (status != running_status) + { + running_status = status; + file.Push(status); + } + file.Push(BYTE((event[2] >> 8) & 0x7F)); + if (MIDI_EventLengths[(status >> 4) & 7] == 2) + { + file.Push(BYTE((event[2] >> 16) & 0x7F)); + } + } + // Advance to next event + if (event[2] < 0x80000000) + { // short message + event += 3; + } + else + { // long message + event += 3 + ((MEVT_EVENTPARM(event[2]) + 3) >> 2); + } + } + } + + // End track + WriteVarLen(file, delay); + file.Push(MIDI_META); + file.Push(MIDI_META_EOT); + file.Push(0); + + // Fill in track length + DWORD len = file.Size() - 22; + file[18] = BYTE(len >> 24); + file[19] = BYTE(len >> 16); + file[20] = BYTE(len >> 8); + file[21] = BYTE(len & 255); + + IgnoreLoops = false; +} + +//========================================================================== +// +// WriteVarLen +// +//========================================================================== + +static void WriteVarLen (TArray &file, DWORD value) +{ + DWORD buffer = value & 0x7F; + + while ( (value >>= 7) ) + { + buffer <<= 8; + buffer |= (value & 0x7F) | 0x80; + } + + for (;;) + { + file.Push(BYTE(buffer)); + if (buffer & 0x80) + { + buffer >>= 8; + } + else + { + break; + } + } +} + //========================================================================== // // MIDIStreamer :: GetStats @@ -786,6 +1144,36 @@ void MIDIDevice::TimidityVolumeChanged() { } +//========================================================================== +// +// MIDIDevice :: FluidSettingInt +// +//========================================================================== + +void MIDIDevice::FluidSettingInt(const char *setting, int value) +{ +} + +//========================================================================== +// +// MIDIDevice :: FluidSettingNum +// +//========================================================================== + +void MIDIDevice::FluidSettingNum(const char *setting, double value) +{ +} + +//========================================================================== +// +// MIDIDevice :: FluidSettingStr +// +//========================================================================== + +void MIDIDevice::FluidSettingStr(const char *setting, const char *value) +{ +} + //========================================================================== // // MIDIDevice :: GetStats diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index fcdf704bd..44a0d45e6 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -51,6 +51,8 @@ // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- +int MUSHeaderSearch(const BYTE *head, int len); + // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -99,32 +101,56 @@ MUSSong2::MUSSong2 (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) } #endif - MusHeader = (MUSHeader *)new BYTE[len]; - if (file != NULL) + BYTE front[32]; + int start; + + if (file == NULL) { - if (fread(MusHeader, 1, len, file) != (size_t)len) + memcpy(front, musiccache, sizeof(front)); + } + else if (fread(front, 1, sizeof(front), file) != sizeof(front)) + { + return; + } + + // To tolerate sloppy wads (diescum.wad, I'm looking at you), we search + // the first 32 bytes of the file for a signature. My guess is that DMX + // does no validation whatsoever and just assumes it was passed a valid + // MUS file, since where the header is offset affects how it plays. + start = MUSHeaderSearch(front, sizeof(front)); + if (start < 0) + { + return; + } + + // Read the remainder of the song. + len = int(len - start); + if (len < (int)sizeof(MusHeader)) + { // It's too short. + return; + } + MusHeader = (MUSHeader *)new BYTE[len]; + if (file == NULL) + { + memcpy(MusHeader, musiccache + start, len); + } + else + { + memcpy(MusHeader, front + start, sizeof(front) - start); + if (fread((BYTE *)MusHeader + sizeof(front) - start, 1, len - (sizeof(front) - start), file) != (size_t)(len - (32 - start))) { return; } } - else - { - memcpy(MusHeader, musiccache, len); - } - // Do some validation of the MUS file - if (MusHeader->Magic != MAKE_ID('M','U','S','\x1a')) - { - return; - } - + // Do some validation of the MUS file. if (LittleShort(MusHeader->NumChans) > 15) { return; } MusBuffer = (BYTE *)MusHeader + LittleShort(MusHeader->SongStart); - MaxMusP = MIN (LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart)); + MaxMusP = MIN(LittleShort(MusHeader->SongLen), len - LittleShort(MusHeader->SongStart)); Division = 140; InitialTempo = 1000000; } @@ -257,9 +283,9 @@ DWORD *MUSSong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) switch (event & 0x70) { case MUS_NOTEOFF: - status |= MIDI_NOTEOFF; + status |= MIDI_NOTEON; mid1 = t; - mid2 = 64; + mid2 = 0; break; case MUS_NOTEON: @@ -356,7 +382,7 @@ MusInfo *MUSSong2::GetOPLDumper(const char *filename) MusInfo *MUSSong2::GetWaveDumper(const char *filename, int rate) { - return new MUSSong2(this, filename, MIDI_Timidity); + return new MUSSong2(this, filename, MIDI_GUS); } //========================================================================== @@ -376,3 +402,25 @@ MUSSong2::MUSSong2(const MUSSong2 *original, const char *filename, EMIDIDevice t Division = 140; InitialTempo = 1000000; } + +//========================================================================== +// +// MUSHeaderSearch +// +// Searches for the MUS header within the given memory block, returning +// the offset it was found at, or -1 if not present. +// +//========================================================================== + +int MUSHeaderSearch(const BYTE *head, int len) +{ + len -= 4; + for (int i = 0; i <= len; ++i) + { + if (head[i+0] == 'M' && head[i+1] == 'U' && head[i+2] == 'S' && head[i+3] == 0x1A) + { + return i; + } + } + return -1; +} diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_smf_midiout.cpp similarity index 79% rename from src/sound/music_midi_midiout.cpp rename to src/sound/music_smf_midiout.cpp index 00d816c66..0f01be75b 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -86,11 +86,11 @@ struct MIDISong2::TrackInfo // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; -static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // PUBLIC DATA DEFINITIONS ------------------------------------------------- +char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; +char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + // CODE -------------------------------------------------------------------- //========================================================================== @@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 () // //========================================================================== -void MIDISong2::CheckCaps() +void MIDISong2::CheckCaps(int tech) { - int tech = MIDI->GetTechnology(); - DesignationMask = 0xFF0F; if (tech == MOD_FMSYNTH) { @@ -389,10 +387,10 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) // Normal short message if ((event & 0xF0) == 0xF0) { - if (CommonLengths[event & 15] > 0) + if (MIDI_CommonLengths[event & 15] > 0) { data1 = track->TrackBegin[track->TrackP++]; - if (CommonLengths[event & 15] > 1) + if (MIDI_CommonLengths[event & 15] > 1) { data2 = track->TrackBegin[track->TrackP++]; } @@ -411,7 +409,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) CHECK_FINISHED - if (EventLengths[(event&0x70)>>4] == 2) + if (MIDI_EventLengths[(event&0x70)>>4] == 2) { data2 = track->TrackBegin[track->TrackP++]; } @@ -500,10 +498,13 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) break; case 116: // EMIDI Loop Begin - track->LoopBegin = track->TrackP; - track->LoopDelay = 0; - track->LoopCount = data2; - track->LoopFinished = track->Finished; + if (!IgnoreLoops) + { + track->LoopBegin = track->TrackP; + track->LoopDelay = 0; + track->LoopCount = data2; + track->LoopFinished = track->Finished; + } event = MIDI_META; break; @@ -529,12 +530,15 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) break; case 118: // EMIDI Global Loop Begin - for (i = 0; i < NumTracks; ++i) + if (!IgnoreLoops) { - Tracks[i].LoopBegin = Tracks[i].TrackP; - Tracks[i].LoopDelay = Tracks[i].Delay; - Tracks[i].LoopCount = data2; - Tracks[i].LoopFinished = Tracks[i].Finished; + for (i = 0; i < NumTracks; ++i) + { + Tracks[i].LoopBegin = Tracks[i].TrackP; + Tracks[i].LoopDelay = Tracks[i].Delay; + Tracks[i].LoopCount = data2; + Tracks[i].LoopFinished = Tracks[i].Finished; + } } event = MIDI_META; break; @@ -709,7 +713,7 @@ DWORD MIDISong2::TrackInfo::ReadVarLen () //========================================================================== // -// MIDISong2 :: TrackInfo :: FindNextDue +// MIDISong2 :: FindNextDue // // Scans every track for the next event to play. Returns NULL if all events // have been consumed. @@ -776,135 +780,6 @@ void MIDISong2::SetTempo(int new_tempo) } } -//========================================================================== -// -// MIDISong2 :: Precache -// -// Scans each track for program change events on normal channels and note on -// events on channel 10. Does not care about bank selects, since they're -// unlikely to appear in a song aimed at Doom. -// -//========================================================================== - -void MIDISong2::Precache() -{ - // This array keeps track of instruments that are used. The first 128 - // entries are for melodic instruments. The second 128 are for - // percussion. - BYTE found_instruments[256] = { 0, }; - BYTE found_banks[256] = { 0, }; - bool multiple_banks = false; - int i, j; - - DoRestart(); - found_banks[0] = true; // Bank 0 is always used. - found_banks[128] = true; - for (i = 0; i < NumTracks; ++i) - { - TrackInfo *track = &Tracks[i]; - BYTE running_status = 0; - BYTE ev, data1, data2, command, channel; - int len; - - data2 = 0; // Silence, GCC - while (track->TrackP < track->MaxTrackP) - { - ev = track->TrackBegin[track->TrackP++]; - command = ev & 0xF0; - - if (ev == MIDI_META) - { - track->TrackP++; - len = track->ReadVarLen(); - track->TrackP += len; - } - else if (ev == MIDI_SYSEX || ev == MIDI_SYSEXEND) - { - len = track->ReadVarLen(); - track->TrackP += len; - } - else if (command == 0xF0) - { - track->TrackP += CommonLengths[ev & 0x0F]; - } - else - { - if ((ev & 0x80) == 0) - { // Use running status. - data1 = ev; - ev = running_status; - } - else - { // Store new running status. - running_status = ev; - data1 = track->TrackBegin[track->TrackP++]; - } - command = ev & 0x70; - channel = ev & 0x0F; - if (EventLengths[command >> 4] == 2) - { - data2 = track->TrackBegin[track->TrackP++]; - } - if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70)) - { - found_instruments[data1 & 127] = true; - } - else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0) - { // On a percussion channel, program change also serves as bank select. - multiple_banks = true; - found_banks[data1 | 128] = true; - } - else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) - { - found_instruments[data1 | 128] = true; - } - else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0) - { - multiple_banks = true; - if (channel == 9) - { - found_banks[data2 | 128] = true; - } - else - { - found_banks[data2 & 127] = true; - } - } - } - track->ReadVarLen(); // Skip delay. - } - } - DoRestart(); - - // Now pack everything into a contiguous region for the PrecacheInstruments call(). - TArray packed; - - for (i = 0; i < 256; ++i) - { - if (found_instruments[i]) - { - WORD packnum = (i & 127) | ((i & 128) << 7); - if (!multiple_banks) - { - packed.Push(packnum); - } - else - { // In order to avoid having to multiplex tracks in a type 1 file, - // precache every used instrument in every used bank, even if not - // all combinations are actually used. - for (j = 0; j < 128; ++j) - { - if (found_banks[j + (i & 128)]) - { - packed.Push(packnum | (j << 7)); - } - } - } - } - } - MIDI->PrecacheInstruments(&packed[0], packed.Size()); -} - //========================================================================== // // MIDISong2 :: GetOPLDumper @@ -924,7 +799,7 @@ MusInfo *MIDISong2::GetOPLDumper(const char *filename) MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate) { - return new MIDISong2(this, filename, MIDI_Timidity); + return new MIDISong2(this, filename, MIDI_GUS); } //========================================================================== diff --git a/src/sound/music_softsynth_mididevice.cpp b/src/sound/music_softsynth_mididevice.cpp new file mode 100644 index 000000000..9d2e69298 --- /dev/null +++ b/src/sound/music_softsynth_mididevice.cpp @@ -0,0 +1,498 @@ +/* +** music_softsynth_mididevice.cpp +** Common base clase for software synthesis MIDI devices. +** +**--------------------------------------------------------------------------- +** Copyright 2008-2010 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 "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(Bool, synth_watch, false, 0) + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// SoftSynthMIDIDevice Constructor +// +//========================================================================== + +SoftSynthMIDIDevice::SoftSynthMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + SampleRate = GSnd != NULL ? (int)GSnd->GetOutputRate() : 44100; +} + +//========================================================================== +// +// SoftSynthMIDIDevice Destructor +// +//========================================================================== + +SoftSynthMIDIDevice::~SoftSynthMIDIDevice() +{ + Close(); +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: OpenStream +// +//========================================================================== + +int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + Stream = GSnd->CreateStream(FillStream, (SampleRate / chunks) * 4, SoundStream::Float | flags, SampleRate, this); + if (Stream == NULL) + { + return 2; + } + + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Close +// +//========================================================================== + +void SoftSynthMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + Started = false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: IsOpen +// +//========================================================================== + +bool SoftSynthMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: GetTechnology +// +//========================================================================== + +int SoftSynthMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTempo +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void SoftSynthMIDIDevice::CalcTickRate() +{ + SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Resume +// +//========================================================================== + +int SoftSynthMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1)) + { + Started = true; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Stop +// +//========================================================================== + +void SoftSynthMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + } +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOutSync +// +// This version is called from the main game thread and needs to +// synchronize with the player thread. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOutSync(MIDIHDR *header) +{ + CritSec.Enter(); + StreamOut(header); + CritSec.Leave(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOut +// +// This version is called from the player thread so does not need to +// arbitrate for access to the Events pointer. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOut(MIDIHDR *header) +{ + header->lpNext = NULL; + if (Events == NULL) + { + Events = header; + NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; + Position = 0; + } + else + { + MIDIHDR **p; + + for (p = &Events; *p != NULL; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PrepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FakeVolume +// +// Since the softsynth output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool SoftSynthMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: NeedThreadedCallabck +// +// We can service the callback directly rather than using a separate +// thread. +// +//========================================================================== + +bool SoftSynthMIDIDevice::NeedThreadedCallback() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Pause +// +//========================================================================== + +bool SoftSynthMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int SoftSynthMIDIDevice::PlayTick() +{ + DWORD delay = 0; + + while (delay == 0 && Events != NULL) + { + DWORD *event = (DWORD *)(Events->lpData + Position); + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + SetTempo(MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { + HandleLongEvent((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { // Short MIDI event + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + HandleEvent(status, parm1, parm2); + + if (synth_watch) + { + static const char *const commands[8] = + { + "Note off", + "Note on", + "Poly press", + "Ctrl change", + "Prgm change", + "Chan press", + "Pitch bend", + "SysEx" + }; + char buffer[128]; + mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); +#ifdef _WIN32 + OutputDebugString(buffer); +#else + fputs(buffer, stderr); +#endif + } + } + + // Advance to next event. + if (event[2] < 0x80000000) + { // Short message + Position += 12; + } + else + { // Long message + Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); + } + + // Did we use up this buffer? + if (Position >= Events->dwBytesRecorded) + { + Events = Events->lpNext; + Position = 0; + + if (Callback != NULL) + { + Callback(MOM_DONE, CallbackData, 0, 0); + } + } + + if (Events == NULL) + { // No more events. Just return something to keep the song playing + // while we wait for more to be submitted. + return int(Division); + } + + delay = *(DWORD *)(Events->lpData + Position); + } + return delay; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: ServiceStream +// +//========================================================================== + +bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes) +{ + float *samples = (float *)buff; + float *samples1; + int numsamples = numbytes / sizeof(float) / 2; + bool prev_ended = false; + bool res = true; + + samples1 = samples; + memset(buff, 0, numbytes); + + CritSec.Enter(); + while (Events != NULL && numsamples > 0) + { + double ticky = NextTickIn; + int tick_in = int(NextTickIn); + int samplesleft = MIN(numsamples, tick_in); + + if (samplesleft > 0) + { + ComputeOutput(samples1, samplesleft); + assert(NextTickIn == ticky); + NextTickIn -= samplesleft; + assert(NextTickIn >= 0); + numsamples -= samplesleft; + samples1 += samplesleft * 2; + } + + if (NextTickIn < 1) + { + int next = PlayTick(); + assert(next >= 0); + if (next == 0) + { // end of song + if (numsamples > 0) + { + ComputeOutput(samples1, numsamples); + } + res = false; + break; + } + else + { + NextTickIn += SamplesPerTick * next; + assert(NextTickIn >= 0); + } + } + } + + if (Events == NULL) + { + res = false; + } + CritSec.Leave(); + return res; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FillStream static +// +//========================================================================== + +bool SoftSynthMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + SoftSynthMIDIDevice *device = (SoftSynthMIDIDevice *)userdata; + return device->ServiceStream(buff, len); +} diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp index a13f46e61..9e5be625b 100644 --- a/src/sound/music_timidity_mididevice.cpp +++ b/src/sound/music_timidity_mididevice.cpp @@ -78,8 +78,6 @@ struct FmtChunk // PUBLIC DATA DEFINITIONS ------------------------------------------------- -CVAR(Bool, timidity_watch, false, 0) - // CODE -------------------------------------------------------------------- //========================================================================== @@ -90,33 +88,8 @@ CVAR(Bool, timidity_watch, false, 0) TimidityMIDIDevice::TimidityMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; Renderer = NULL; - Renderer = new Timidity::Renderer(GSnd->GetOutputRate()); -} - -//========================================================================== -// -// TimidityMIDIDevice Constructor with rate parameter -// -//========================================================================== - -TimidityMIDIDevice::TimidityMIDIDevice(int rate) -{ - // Need to support multiple instances with different playback rates - // before we can use this parameter. - rate = (int)GSnd->GetOutputRate(); - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - Renderer = NULL; - Renderer = new Timidity::Renderer((float)rate); + Renderer = new Timidity::Renderer((float)SampleRate); } //========================================================================== @@ -144,261 +117,12 @@ TimidityMIDIDevice::~TimidityMIDIDevice() int TimidityMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) { - Stream = GSnd->CreateStream(FillStream, int(Renderer->rate / 2) * 4, - SoundStream::Float, int(Renderer->rate), this); - if (Stream == NULL) + int ret = OpenStream(2, 0, callback, userdata); + if (ret == 0) { - return 2; + Renderer->Reset(); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - Renderer->Reset(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Close -// -//========================================================================== - -void TimidityMIDIDevice::Close() -{ - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } - Started = false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: IsOpen -// -//========================================================================== - -bool TimidityMIDIDevice::IsOpen() const -{ - return Stream != NULL; -} - -//========================================================================== -// -// TimidityMIDIDevice :: GetTechnology -// -//========================================================================== - -int TimidityMIDIDevice::GetTechnology() const -{ - return MOD_SWSYNTH; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTempo -// -//========================================================================== - -int TimidityMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int TimidityMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: CalcTickRate -// -// Tempo is the number of microseconds per quarter note. -// Division is the number of ticks per quarter note. -// -//========================================================================== - -void TimidityMIDIDevice::CalcTickRate() -{ - SamplesPerTick = Renderer->rate / (1000000.0 / Tempo) / Division; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Resume -// -//========================================================================== - -int TimidityMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1/*timidity_mastervolume*/)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Stop -// -//========================================================================== - -void TimidityMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - CritSec.Enter(); - StreamOut(header); - CritSec.Leave(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: PrepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: FakeVolume -// -// Since the TiMidity output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool TimidityMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool TimidityMIDIDevice::NeedThreadedCallback() -{ - return false; -} - - -//========================================================================== -// -// TimidityMIDIDevice :: TimidityVolumeChanged -// -//========================================================================== - -void TimidityMIDIDevice::TimidityVolumeChanged() -{ - /* - if (Stream != NULL) - { - Stream->SetVolume(timidity_mastervolume); - } - */ -} - -//========================================================================== -// -// TimidityMIDIDevice :: Pause -// -//========================================================================== - -bool TimidityMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + return ret; } //========================================================================== @@ -423,164 +147,35 @@ void TimidityMIDIDevice::PrecacheInstruments(const WORD *instruments, int count) //========================================================================== // -// TimidityMIDIDevice :: PlayTick -// -// event[0] = delta time -// event[1] = unused -// event[2] = event +// TimidityMIDIDevice :: HandleEvent // //========================================================================== -int TimidityMIDIDevice::PlayTick() +void TimidityMIDIDevice::HandleEvent(int status, int parm1, int parm2) { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { - Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - Renderer->HandleEvent(status, parm1, parm2); - - if (timidity_watch) - { - static const char *const commands[8] = - { - "Note off", - "Note on", - "Poly press", - "Ctrl change", - "Prgm change", - "Chan press", - "Pitch bend", - "SysEx" - }; -#ifdef _WIN32 - char buffer[128]; - mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); - OutputDebugString(buffer); -#else - //fprintf(stderr, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); -#endif - } - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + Renderer->HandleEvent(status, parm1, parm2); } //========================================================================== // -// TimidityMIDIDevice :: ServiceStream +// TimidityMIDIDevice :: HandleLongEvent // //========================================================================== -bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes) +void TimidityMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - float *samples = (float *)buff; - float *samples1; - int numsamples = numbytes / sizeof(float) / 2; - bool prev_ended = false; - bool res = true; - - samples1 = samples; - memset(buff, 0, numbytes); - - CritSec.Enter(); - while (Events != NULL && numsamples > 0) - { - double ticky = NextTickIn; - int tick_in = int(NextTickIn); - int samplesleft = MIN(numsamples, tick_in); - - if (samplesleft > 0) - { - Renderer->ComputeOutput(samples1, samplesleft); - assert(NextTickIn == ticky); - NextTickIn -= samplesleft; - assert(NextTickIn >= 0); - numsamples -= samplesleft; - samples1 += samplesleft * 2; - } - - if (NextTickIn < 1) - { - int next = PlayTick(); - assert(next >= 0); - if (next == 0) - { // end of song - if (numsamples > 0) - { - Renderer->ComputeOutput(samples1, numsamples); - } - res = false; - break; - } - else - { - NextTickIn += SamplesPerTick * next; - assert(NextTickIn >= 0); - } - } - } - if (Events == NULL) - { - res = false; - } - CritSec.Leave(); - return res; + Renderer->HandleLongMessage(data, len); } //========================================================================== // -// TimidityMIDIDevice :: FillStream static +// TimidityMIDIDevice :: ComputeOutput // //========================================================================== -bool TimidityMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void TimidityMIDIDevice::ComputeOutput(float *buffer, int len) { - TimidityMIDIDevice *device = (TimidityMIDIDevice *)userdata; - return device->ServiceStream(buff, len); + Renderer->ComputeOutput(buffer, len); } //========================================================================== diff --git a/src/st_stuff.cpp b/src/st_stuff.cpp index 59b65ef85..08e6f0be4 100644 --- a/src/st_stuff.cpp +++ b/src/st_stuff.cpp @@ -293,7 +293,6 @@ static cheatseq_t SpecialCheats[] = }; -extern bool CheckCheatmode (); CVAR(Bool, allcheats, false, CVAR_ARCHIVE) diff --git a/src/statnums.h b/src/statnums.h index f699b9c39..344a328c8 100644 --- a/src/statnums.h +++ b/src/statnums.h @@ -35,7 +35,7 @@ ** lists for different types of thinkers is taken from Build. Every thinker ** is ticked by statnum, so a thinker with a low statnum will always tick ** before a thinker with a high statnum. If a thinker is not explicitly -** created with a statnum, it will be given MAX_STATNUM. +** created with a statnum, it will be given STAT_DEFAULT */ enum @@ -59,10 +59,10 @@ enum STAT_EARTHQUAKE, // Earthquake actors STAT_MAPMARKER, // Map marker actors - STAT_DEFAULT = 100, + STAT_DEFAULT = 100, // Thinkers go here unless specified otherwise. 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 calles PostBeginPlay + STAT_SCRIPTS, // The ACS thinker. This is to ensure that it can't tick before all actors called PostBeginPlay }; #endif \ No newline at end of file diff --git a/src/stringtable.cpp b/src/stringtable.cpp index 6b0dd91f5..f0e5b326f 100644 --- a/src/stringtable.cpp +++ b/src/stringtable.cpp @@ -139,6 +139,7 @@ void FStringTable::LoadStrings (bool enuOnly) void FStringTable::LoadLanguage (int lumpnum, DWORD code, bool exactMatch, int passnum) { + static bool errordone = false; const DWORD orMask = exactMatch ? 0 : MAKE_ID(0,0,0xff,0); DWORD inCode = 0; StringEntry *entry, **pentry; @@ -211,6 +212,15 @@ void FStringTable::LoadLanguage (int lumpnum, DWORD code, bool exactMatch, int p { // Process string definitions. if (inCode == 0) { + // LANGUAGE lump is bad. We need to check if this is an old binary + // lump and if so just skip it to allow old WADs to run which contain + // such a lump. + if (!sc.isText()) + { + if (!errordone) Printf("Skipping binary 'LANGUAGE' lump.\n"); + errordone = true; + return; + } sc.ScriptError ("Found a string without a language specified."); } diff --git a/src/tarray.h b/src/tarray.h index cf8aa1dd0..8ea166712 100644 --- a/src/tarray.h +++ b/src/tarray.h @@ -153,12 +153,27 @@ public: { return Array[index]; } + // Returns a reference to the last element + T &Last() const + { + return Array[Count-1]; + } + unsigned int Push (const T &item) { Grow (1); ::new((void*)&Array[Count]) T(item); return Count++; } + bool Pop () + { + if (Count > 0) + { + Array[--Count].~T(); + return true; + } + return false; + } bool Pop (T &item) { if (Count > 0) @@ -183,10 +198,13 @@ public: void Delete (unsigned int index, int deletecount) { - if (index + deletecount > Count) deletecount = Count - index; + if (index + deletecount > Count) + { + deletecount = Count - index; + } if (deletecount > 0) { - for(int i = 0; i < deletecount; i++) + for (int i = 0; i < deletecount; i++) { Array[index + i].~T(); } diff --git a/src/templates.h b/src/templates.h index c3037456a..002bdeb3b 100644 --- a/src/templates.h +++ b/src/templates.h @@ -195,14 +195,14 @@ T clamp (const T in, const T min, const T max) //========================================================================== // -// swap +// swapvalues // // Swaps the values of a and b. //========================================================================== template inline -void swap (T &a, T &b) +void swapvalues (T &a, T &b) { T temp = a; a = b; b = temp; } diff --git a/src/textures/bitmap.cpp b/src/textures/bitmap.cpp index 20dfd38bd..b552f9797 100644 --- a/src/textures/bitmap.cpp +++ b/src/textures/bitmap.cpp @@ -101,7 +101,7 @@ void iCopyColors(BYTE *pout, const BYTE *pin, int count, int step, FCopyInfo *in a = TSrc::A(pin); if (TBlend::ProcessAlpha0() || a) { - gray = clamp(255 - TSrc::Gray(pin),0,255); + gray = clamp(TSrc::Gray(pin),0,255); PalEntry pe = cm->GrayscaleToColor[gray]; TBlend::OpC(pout[TDest::RED], pe.r , a, inf); diff --git a/src/textures/multipatchtexture.cpp b/src/textures/multipatchtexture.cpp index faaa5db6c..90c52dd63 100644 --- a/src/textures/multipatchtexture.cpp +++ b/src/textures/multipatchtexture.cpp @@ -499,6 +499,8 @@ void FMultiPatchTexture::MakeTexture () { for (int i = 0; i < NumParts; ++i) { + if (Parts[i].Texture->bHasCanvas) continue; // cannot use camera textures as patch. + BYTE *trans = Parts[i].Translation ? Parts[i].Translation->Remap : NULL; { if (Parts[i].Blend != 0) @@ -561,6 +563,8 @@ int FMultiPatchTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rota for(int i = 0; i < NumParts; i++) { int ret = -1; + + if (Parts[i].Texture->bHasCanvas) continue; // cannot use camera textures as patch. // rotated multipatch parts cannot be composited directly bool rotatedmulti = Parts[i].Rotate != 0 && Parts[i].Texture->bMultiPatch; @@ -614,6 +618,7 @@ int FMultiPatchTexture::CopyTrueColorPixels(FBitmap *bmp, int x, int y, int rota FBitmap bmp1; if (bmp1.Create(Parts[i].Texture->GetWidth(), Parts[i].Texture->GetHeight())) { + bmp1.Zero(); Parts[i].Texture->CopyTrueColorPixels(&bmp1, 0, 0); bmp->CopyPixelDataRGB(x+Parts[i].OriginX, y+Parts[i].OriginY, bmp1.GetPixels(), bmp1.GetWidth(), bmp1.GetHeight(), 4, bmp1.GetPitch(), Parts[i].Rotate, CF_BGRA, inf); diff --git a/src/textures/pngtexture.cpp b/src/textures/pngtexture.cpp index d1fe3e8f5..4e30802c4 100644 --- a/src/textures/pngtexture.cpp +++ b/src/textures/pngtexture.cpp @@ -202,7 +202,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename { DWORD palette[256]; BYTE pngpal[256][3]; - }; + } p; BYTE trans[256]; bool havetRNS = false; DWORD len, id; @@ -260,14 +260,14 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case MAKE_ID('P','L','T','E'): PaletteSize = MIN (len / 3, 256); - lump.Read (pngpal, PaletteSize * 3); + lump.Read (p.pngpal, PaletteSize * 3); if (PaletteSize * 3 != (int)len) { lump.Seek (len - PaletteSize * 3, SEEK_CUR); } for (i = PaletteSize - 1; i >= 0; --i) { - palette[i] = MAKERGB(pngpal[i][0], pngpal[i][1], pngpal[i][2]); + p.palette[i] = MAKERGB(p.pngpal[i][0], p.pngpal[i][1], p.pngpal[i][2]); } break; @@ -314,7 +314,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case 3: // Paletted PaletteMap = new BYTE[PaletteSize]; - GPalette.MakeRemap (palette, PaletteMap, trans, PaletteSize); + GPalette.MakeRemap (p.palette, PaletteMap, trans, PaletteSize); for (i = 0; i < PaletteSize; ++i) { if (trans[i] == 0) diff --git a/src/textures/texture.cpp b/src/textures/texture.cpp index 97f393cb2..576c30de9 100644 --- a/src/textures/texture.cpp +++ b/src/textures/texture.cpp @@ -366,12 +366,12 @@ void FTexture::FlipSquareBlock (BYTE *block, int x, int y) if (count & 1) { count--; - swap (corner[count], corner[count*x]); + swapvalues (corner[count], corner[count*x]); } for (j = 0; j < count; j += 2) { - swap (corner[j], corner[j*x]); - swap (corner[j+1], corner[(j+1)*x]); + swapvalues (corner[j], corner[j*x]); + swapvalues (corner[j+1], corner[(j+1)*x]); } } } @@ -549,8 +549,8 @@ FTexture *FTexture::GetRedirect(bool wantwarped) void FTexture::SetScaledSize(int fitwidth, int fitheight) { - xScale = DivScale16(Width, fitwidth); - yScale = DivScale16(Height,fitheight); + xScale = FLOAT2FIXED(float(Width) / fitwidth); + yScale = FLOAT2FIXED(float(Height) / fitheight); // compensate for roundoff errors if (MulScale16(xScale, fitwidth) != Width) xScale++; if (MulScale16(yScale, fitheight) != Height) yScale++; diff --git a/src/textures/texturemanager.cpp b/src/textures/texturemanager.cpp index 34e2f4ae6..e562923db 100644 --- a/src/textures/texturemanager.cpp +++ b/src/textures/texturemanager.cpp @@ -822,10 +822,13 @@ void FTextureManager::AddTexturesForWad(int wadnum) } } - // Seventh step: Check for hires replacements. - AddHiresTextures(wadnum); + // Check for text based texture definitions LoadTextureDefs(wadnum, "TEXTURES"); LoadTextureDefs(wadnum, "HIRESTEX"); + + // Seventh step: Check for hires replacements. + AddHiresTextures(wadnum); + SortTexturesByType(firsttexture, Textures.Size()); } diff --git a/src/thingdef/olddecorations.cpp b/src/thingdef/olddecorations.cpp index 3fe2f6e39..57ff991a9 100644 --- a/src/thingdef/olddecorations.cpp +++ b/src/thingdef/olddecorations.cpp @@ -685,7 +685,7 @@ static void ParseSpriteFrames (PClassActor *info, TArray &states, FScann { sc.ScriptError ("* must come after a frame"); } - state.Frame |= SF_FULLBRIGHT; + state.Fullbright = true; } else if (*token < 'A' || *token > ']') { diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index a430ae878..613e896c6 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -231,13 +231,6 @@ enum DEPF_INTERHUBSTRIP, }; -enum -{ - ACMETA_BASE = 0x83000, - ACMETA_DropItems, // Int (index into DropItemList) -}; - - // Types of old style decorations enum EDefinitionType { diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 56aa425d1..8e175312e 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -66,6 +66,7 @@ #include "v_font.h" #include "doomstat.h" #include "v_palette.h" +#include "g_shared/a_specialspot.h" static FRandom pr_camissile ("CustomActorfire"); @@ -81,6 +82,7 @@ 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"); //========================================================================== @@ -368,6 +370,7 @@ static FRandom pr_seekermissile ("SeekerMissile"); enum { SMF_LOOK = 1, + SMF_PRECISE = 2, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) { @@ -382,7 +385,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SeekerMissile) { self->tracer = P_RoughMonsterSearch (self, distance); } - P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1); + P_SeekerMissile(self, clamp(ang1, 0, 90) * ANGLE_1, clamp(ang2, 0, 90) * ANGLE_1, !!(flags & SMF_PRECISE)); return 0; } @@ -500,12 +503,29 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfHealthLower) // State jump function // //========================================================================== -DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +void DoJumpIfCloser(AActor *target, VM_ARGS) { PARAM_ACTION_PROLOGUE; PARAM_FIXED (dist); PARAM_STATE (jump); + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + // No target - no jump + if (target != NULL && P_AproxDistance(self->x-target->x, self->y-target->y) < dist && + ( (self->z > target->z && self->z - (target->z + target->height) < dist) || + (self->z <=target->z && target->z - (self->z + self->height) < dist) + ) + ) + { + ACTION_JUMP(jump); + } +} + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) +{ + PARAM_ACTION_PROLOGUE; + AActor *target; if (self->player == NULL) @@ -517,24 +537,29 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser) // Does the player aim at something that can be shot? P_BulletSlope(self, &target); } + DoJumpIfCloser(target, VM_ARGS_NAMES); + return 0; +} - ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTracerCloser) +{ + PARAM_ACTION_PROLOGUE; - // No target - no jump - if (target == NULL) - return 0; - - if (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) - ) - ) + // Is there really any reason to limit this to seeker missiles? + if (self->flags2 & MF2_SEEKERMISSILE) { - ACTION_JUMP(jump); + DoJumpIfCloser(self->tracer, VM_ARGS_NAMES); } return 0; } +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfMasterCloser) +{ + PARAM_ACTION_PROLOGUE; + DoJumpIfCloser(self->master, VM_ARGS_NAMES); + return 0; +} + //========================================================================== // // State jump function @@ -851,6 +876,14 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomMissile) // An even more customizable hitscan attack // //========================================================================== +enum CBA_Flags +{ + CBAF_AIMFACING = 1, + CBAF_NORANDOM = 2, + CBAF_EXPLICITANGLE = 4, + CBAF_NOPITCH = 8, +}; + DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) { PARAM_ACTION_PROLOGUE; @@ -860,7 +893,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) PARAM_INT (damageperbullet); PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } PARAM_FIXED_OPT (range) { range = MISSILERANGE; } - PARAM_BOOL_OPT (aimfacing) { aimfacing = false; } + PARAM_INT_OPT (flags) { flags = 0; } if (range == 0) range = MISSILERANGE; @@ -869,19 +902,35 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) int bangle; int bslope; - if (self->target || aimfacing) + if (self->target || (flags & CBAF_AIMFACING)) { - if (!aimfacing) A_FaceTarget (self); + if (!(flags & CBAF_AIMFACING)) A_FaceTarget (self); bangle = self->angle; - bslope = P_AimLineAttack (self, bangle, MISSILERANGE); + if (!(flags & CBAF_NOPITCH)) bslope = P_AimLineAttack (self, bangle, MISSILERANGE); S_Sound (self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NORM); for (i = 0; i < numbullets; i++) { - int angle = bangle + pr_cabullet.Random2() * (spread_xy / 255); - int slope = bslope + pr_cabullet.Random2() * (spread_z / 255); - int damage = ((pr_cabullet()%3)+1) * damageperbullet; + int angle = bangle; + int slope = bslope; + + if (flags & CBAF_EXPLICITANGLE) + { + angle += spread_xy; + slope += spread_z; + } + else + { + angle += pr_cwbullet.Random2() * (spread_xy / 255); + slope += pr_cwbullet.Random2() * (spread_z / 255); + } + + int damage = damageperbullet; + + if (!(flags & CBAF_NORANDOM)) + damage *= ((pr_cabullet()%3)+1); + P_LineAttack(self, angle, range, slope, damage, NAME_None, pufftype); } } @@ -1006,6 +1055,15 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfNoAmmo) // An even more customizable hitscan attack // //========================================================================== +enum FB_Flags +{ + FBF_USEAMMO = 1, + FBF_NORANDOM = 2, + FBF_EXPLICITANGLE = 4, + FBF_NOPITCH = 8, + FBF_NOFLASH = 16, +}; + DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) { PARAM_ACTION_PROLOGUE; @@ -1014,7 +1072,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) PARAM_INT (numbullets); PARAM_INT (damageperbullet); PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } - PARAM_BOOL_OPT (useammo) { useammo = true; } + PARAM_INT_OPT (flags) { flags = FBF_USEAMMO; } PARAM_FIXED_OPT (range) { range = 0; } if (!self->player) return 0; @@ -1026,7 +1084,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) int bangle; int bslope; - if (useammo && weapon) + if ((flags & FBF_USEAMMO) && weapon) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return 0; // out of ammo @@ -1035,9 +1093,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if (range == 0) range = PLAYERMISSILERANGE; - static_cast(self)->PlayAttacking2(); + if (!(flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); - bslope = P_BulletSlope(self); + if (!(flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); bangle = self->angle; if (pufftype == NULL) @@ -1047,7 +1105,11 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if ((numbullets == 1 && !player->refire) || numbullets == 0) { - int damage = ((pr_cwbullet()%3)+1) * damageperbullet; + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + P_LineAttack(self, bangle, range, bslope, damage, NAME_None, pufftype); } else @@ -1056,9 +1118,25 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) numbullets = 1; for (i = 0; i < numbullets; i++) { - int angle = bangle + pr_cwbullet.Random2() * (spread_xy / 255); - int slope = bslope + pr_cwbullet.Random2() * (spread_z / 255); - int damage = ((pr_cwbullet()%3)+1) * damageperbullet; + int angle = bangle; + int slope = bslope; + + if (flags & FBF_EXPLICITANGLE) + { + angle += spread_xy; + slope += spread_z; + } + else + { + angle += pr_cwbullet.Random2() * (spread_xy / 255); + slope += pr_cwbullet.Random2() * (spread_z / 255); + } + + int damage = damageperbullet; + + if (!(flags & FBF_NORANDOM)) + damage *= ((pr_cwbullet()%3)+1); + P_LineAttack(self, angle, range, slope, damage, NAME_None, pufftype); } } @@ -1139,14 +1217,23 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireCustomMissile) // Berserk is not handled here. That can be done with A_CheckIfInventory // //========================================================================== + +enum +{ + CPF_USEAMMO = 1, + CPF_DAGGER = 2, + CPF_PULLIN = 4, +}; + DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) { PARAM_ACTION_PROLOGUE; PARAM_INT (damage); PARAM_BOOL_OPT (norandom) { norandom = false; } - PARAM_BOOL_OPT (useammo) { useammo = true; } + PARAM_INT_OPT (flags) { flags = CPF_USEAMMO; } PARAM_CLASS_OPT (pufftype, AActor) { pufftype = NULL; } PARAM_FIXED_OPT (range) { range = 0; } + PARAM_FIXED_OPT (lifesteal) { lifesteal = 0; } if (!self->player) return 0; @@ -1168,7 +1255,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) pitch = P_AimLineAttack (self, angle, range, &linetarget); // only use ammo when actually hitting something! - if (useammo && linetarget && weapon) + if ((flags & CPF_USEAMMO) && linetarget && weapon) { if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return 0; // out of ammo @@ -1177,14 +1264,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) if (pufftype == NULL) pufftype = PClass::FindActor(NAME_BulletPuff); - P_LineAttack (self, angle, range, pitch, damage, NAME_None, pufftype, true); + P_LineAttack (self, angle, range, pitch, damage, NAME_None, pufftype, true, &linetarget); // turn to face target if (linetarget) { + if (lifesteal) + P_GiveBody (self, (damage * lifesteal) >> FRACBITS); + S_Sound (self, CHAN_WEAPON, weapon->AttackSound, 1, ATTN_NORM); self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y); + + if (flags & CPF_PULLIN) self->flags |= MF_JUSTATTACKED; + if (flags & CPF_DAGGER) P_DaggerAlert (self, linetarget); + } return 0; } @@ -1193,7 +1287,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) enum { RAF_SILENT = 1, - RAF_NOPIERCE = 2 + RAF_NOPIERCE = 2, + RAF_EXPLICITANGLE = 4, }; //========================================================================== @@ -1212,6 +1307,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) PARAM_INT_OPT (flags) { flags = 0; } PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } + PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } + PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } if (!self->player) return 0; @@ -1225,7 +1322,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) return 0; // out of ammo } - P_RailAttack (self, damage, spawnofs_xy, color1, color2, float(maxdiff), (flags & RAF_SILENT), pufftype, (!(flags & RAF_NOPIERCE))); + 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, color1, color2, maxdiff, (flags & RAF_SILENT), pufftype, (!(flags & RAF_NOPIERCE)), angle, slope); return 0; } @@ -1238,7 +1349,8 @@ enum { CRF_DONTAIM = 0, CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2 + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) @@ -1252,6 +1364,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) PARAM_INT_OPT (aim) { aim = CRF_DONTAIM; } PARAM_FLOAT_OPT (maxdiff) { maxdiff = 0; } PARAM_CLASS_OPT (pufftype, AActor) { pufftype = PClass::FindActor(NAME_BulletPuff); } + PARAM_ANGLE_OPT (spread_xy) { spread_xy = 0; } + PARAM_ANGLE_OPT (spread_z) { spread_z = 0; } AActor *linetarget; @@ -1318,7 +1432,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; - P_RailAttack (self, damage, spawnofs_xy, color1, color2, float(maxdiff), flags & RAF_SILENT, pufftype, !(flags & RAF_NOPIERCE)); + 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, color1, color2, maxdiff, (flags & RAF_SILENT), pufftype, (!(flags & RAF_NOPIERCE)), angleoffset, slopeoffset); self->x = saved_x; self->y = saved_y; @@ -1400,11 +1528,17 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget) // //=========================================================================== +enum +{ + TIF_NOTAKEINFINITE = 1, +}; + void DoTakeInventory(AActor *receiver, StateCallData *statecall, VM_ARGS) { int paramnum = NAP-1; PARAM_CLASS (itemtype, AInventory); PARAM_INT_OPT (amount) { amount = 0; } + PARAM_INT_OPT (flags) { flags = 0; } if (itemtype == NULL || receiver == NULL) return; @@ -1418,12 +1552,18 @@ void DoTakeInventory(AActor *receiver, StateCallData *statecall, VM_ARGS) { res = true; } - if (amount == 0 || amount >= inv->Amount) + // 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))) { - if (inv->ItemFlags & IF_KEEPDEPLETED) - inv->Amount = 0; - else - inv->Destroy(); + // 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 { @@ -1833,6 +1973,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Print) C_MidPrint(font != NULL ? font : SmallFont, formatted.GetChars()); con_midtime = saved; } + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! return 0; } @@ -1863,6 +2004,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_PrintBold) FString formatted = strbin1(text); C_MidPrintBold(font != NULL ? font : SmallFont, formatted.GetChars()); con_midtime = saved; + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! return 0; } @@ -1877,6 +2019,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Log) PARAM_ACTION_PROLOGUE; PARAM_STRING(text); Printf("%s\n", text); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! return 0; } @@ -1891,6 +2034,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LogInt) PARAM_ACTION_PROLOGUE; PARAM_INT(num); Printf("%d\n", num); + ACTION_SET_RESULT(false); // Prints should never set the result for inventory state chains! return 0; } @@ -1926,10 +2070,12 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeIn) PARAM_FIXED_OPT(reduce) { reduce = FRACUNIT/10; } if (reduce == 0) + { reduce = FRACUNIT/10; - + } self->RenderStyle.Flags &= ~STYLEF_Alpha1; self->alpha += reduce; + // Should this clamp alpha to 1.0? return 0; } @@ -1947,12 +2093,57 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeOut) PARAM_BOOL_OPT(remove) { remove = true; } if (reduce == 0) + { reduce = FRACUNIT/10; - + } self->RenderStyle.Flags &= ~STYLEF_Alpha1; self->alpha -= reduce; - if (self->alpha<=0 && remove) + if (self->alpha <= 0 && remove) + { self->Destroy(); + } + return 0; +} + +//=========================================================================== +// +// A_FadeTo +// +// fades the actor to a specified transparency by a specified amount and +// destroys it if so desired +// +//=========================================================================== +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FadeTo) +{ + PARAM_ACTION_PROLOGUE; + PARAM_FIXED (target); + PARAM_FIXED_OPT (amount) { amount = fixed_t(0.1*FRACUNIT); } + PARAM_BOOL_OPT (remove) { remove = false; } + + self->RenderStyle.Flags &= ~STYLEF_Alpha1; + + if (self->alpha > target) + { + self->alpha -= amount; + + if (self->alpha < target) + { + self->alpha = target; + } + } + else if (self->alpha < target) + { + self->alpha += amount; + + if (self->alpha > target) + { + self->alpha = target; + } + } + if (self->alpha == target && remove) + { + self->Destroy(); + } return 0; } @@ -2256,49 +2447,51 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Burst) PARAM_ACTION_PROLOGUE; PARAM_CLASS(chunk, AActor); - int i, numChunks; - AActor *mo; + int i, numChunks; + AActor * mo; - if (chunk == NULL) - return 0; + if (chunk == NULL) + { + return 0; + } - self->velx = self->vely = self->velz = 0; - self->height = self->GetDefault()->height; + 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), ALLOW_REPLACE); + // [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), 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); - } - } + 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) - { + // [RH] Do some stuff to make this more useful outside Hexen + if (self->flags4 & MF4_BOSSDEATH) + { CALL_ACTION(A_BossDeath, self); - } - CALL_ACTION(A_NoBlocking, self); + } + A_Unblock(self, true); - self->Destroy(); - return 0; + self->Destroy (); + return 0; } //=========================================================================== @@ -2508,23 +2701,38 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearTarget) //========================================================================== // -// A_JumpIfTargetInLOS (state label, optional fixed fov, optional bool -// projectiletarget) +// 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). +// (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, +}; + DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) { PARAM_ACTION_PROLOGUE; PARAM_STATE (jump); - PARAM_ANGLE_OPT (fov) { fov = 0; } - PARAM_BOOL_OPT (projtarg) { projtarg = false; } + PARAM_ANGLE_OPT (fov) { fov = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (dist_max) { dist_max = 0; } + PARAM_FIXED_OPT (dist_close) { dist_close = 0; } angle_t an; AActor *target; @@ -2533,7 +2741,11 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) if (!self->player) { - if (self->flags & MF_MISSILE && projtarg) + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) { if (self->flags2 & MF2_SEEKERMISSILE) target = self->tracer; @@ -2548,7 +2760,34 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) if (target == NULL) return 0; // [KS] Let's not call P_CheckSight unnecessarily in this case. - if (!P_CheckSight (self, target, SF_IGNOREVISIBILITY)) + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) + { + return 0; + } + + 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 0; + } + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return 0; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (doCheckSight && !P_CheckSight (self, target, SF_IGNOREVISIBILITY)) return 0; if (fov && (fov < ANGLE_MAX)) @@ -2566,10 +2805,25 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) { // Does the player aim at something that can be shot? P_BulletSlope(self, &target); - } - if (target == NULL) - return 0; + if (target == NULL) + { + return 0; + } + + 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 0; + } + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return 0; + } + } ACTION_JUMP(jump); return 0; @@ -2578,8 +2832,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) //========================================================================== // -// A_JumpIfInTargetLOS (state label, optional fixed fov, optional bool -// projectiletarget) +// A_JumpIfInTargetLOS (state label, optional fixed fov, optional int flags +// optional fixed dist_max, optional fixed dist_close) // //========================================================================== @@ -2587,15 +2841,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) { PARAM_ACTION_PROLOGUE; PARAM_STATE (jump); - PARAM_ANGLE_OPT (fov) { fov = 0; } - PARAM_BOOL_OPT (projtarg) { projtarg = false; } + PARAM_ANGLE_OPT (fov) { fov = 0; } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (dist_max) { dist_max = 0; } + PARAM_FIXED_OPT (dist_close) { dist_close = 0; } angle_t an; AActor *target; ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (self->flags & MF_MISSILE && projtarg) + if (flags & JLOSF_CHECKMASTER) + { + target = self->master; + } + else if (self->flags & MF_MISSILE && (flags & JLOSF_PROJECTILE)) { if (self->flags2 & MF2_SEEKERMISSILE) target = self->tracer; @@ -2608,9 +2868,38 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) } if (target == NULL) - return 0; // [KS] Let's not call P_CheckSight unnecessarily in this case. + { // [KS] Let's not call P_CheckSight unnecessarily in this case. + return 0; + } - if (!P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) + { + return 0; + } + + 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 0; + } + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return 0; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) return 0; if (fov && (fov < ANGLE_MAX)) @@ -3187,16 +3476,16 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserVar) PARAM_NAME (varname); PARAM_INT (value); - PSymbolVariable *var = dyn_cast(stateowner->GetClass()->Symbols.FindSymbol(varname, true)); + PSymbolVariable *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); if (var == NULL || !var->bUserVar || var->ValueType.Type != VAL_Int) { Printf("%s is not a user variable in class %s\n", varname.GetChars(), - stateowner->GetClass()->TypeName.GetChars()); + self->GetClass()->TypeName.GetChars()); return 0; } // Set the value of the specified user variable. - *(int *)(reinterpret_cast(stateowner) + var->offset) = value; + *(int *)(reinterpret_cast(self) + var->offset) = value; return 0; } @@ -3213,22 +3502,119 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) PARAM_INT (pos); PARAM_INT (value); - PSymbolVariable *var = dyn_cast(stateowner->GetClass()->Symbols.FindSymbol(varname, true)); + PSymbolVariable *var = dyn_cast(self->GetClass()->Symbols.FindSymbol(varname, true)); if (var == NULL || !var->bUserVar || var->ValueType.Type != VAL_Array || var->ValueType.BaseType != VAL_Int) { Printf("%s is not a user array in class %s\n", varname.GetChars(), - stateowner->GetClass()->TypeName.GetChars()); + self->GetClass()->TypeName.GetChars()); return 0; } if (pos < 0 || pos >= var->ValueType.size) { Printf("%d is out of bounds in array %s in class %s\n", pos, varname.GetChars(), - stateowner->GetClass()->TypeName.GetChars()); + self->GetClass()->TypeName.GetChars()); return 0; } // Set the value of the specified user array at index pos. - ((int *)(reinterpret_cast(stateowner) + var->offset))[pos] = value; + ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; + return 0; +} + +//=========================================================================== +// +// A_Teleport(optional state teleportstate, optional class targettype, +// optional class fogtype, optional int flags, optional fixed mindist, +// optional fixed maxdist) +// +// Attempts to teleport to a targettype at least mindist away and at most +// maxdist away (0 means unlimited). If successful, spawn a fogtype at old +// location and place calling actor in teleportstate. +// +//=========================================================================== +enum T_Flags +{ + TF_TELEFRAG = 1, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 2, // Randomly fail based on health. (A_Srcr2Decide) +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) +{ + PARAM_ACTION_PROLOGUE; + PARAM_STATE_OPT (teleport_state) { teleport_state = NULL; } + PARAM_CLASS_OPT (target_type, ASpecialSpot) { target_type = PClass::FindActor("BossSpot"); } + PARAM_CLASS_OPT (fog_type, AActor) { fog_type = PClass::FindActor("TeleportFog"); } + PARAM_INT_OPT (flags) { flags = 0; } + PARAM_FIXED_OPT (mindist) { mindist = 128 << FRACBITS; } + PARAM_FIXED_OPT (maxdist) { maxdist = 128 << FRACBITS; } + + // 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 0; + } + } + + if (teleport_state == NULL) + { + // Default to Teleport. + teleport_state = self->FindState("Teleport"); + // If still nothing, then return. + if (teleport_state == NULL) + { + return 0; + } + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) + { + return 0; + } + + if (target_type == NULL) + { + target_type = PClass::FindActor("BossSpot"); + } + + AActor *spot = state->GetSpotWithMinMaxDistance(target_type, self->x, self->y, mindist, maxdist); + if (spot == NULL) + { + return 0; + } + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + if (P_TeleportMove (self, spot->x, spot->y, spot->z, flags & TF_TELEFRAG)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (fog_type != NULL) + { + Spawn(fog_type, prevX, prevY, prevZ, ALLOW_REPLACE); + } + + ACTION_JUMP(teleport_state); + + self->z = self->floorz; + self->angle = spot->angle; + self->velx = self->vely = self->velz = 0; + } return 0; } diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 01919e85e..b6b8fe7d9 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -224,6 +224,9 @@ static FFlagDef ActorFlags[]= DEFINE_FLAG(MF6, JUMPDOWN, AActor, flags6), DEFINE_FLAG(MF6, VULNERABLE, AActor, flags6), DEFINE_FLAG(MF6, NOTRIGGER, AActor, flags6), + DEFINE_FLAG(MF6, ADDITIVEPOISONDAMAGE, AActor, flags6), + DEFINE_FLAG(MF6, ADDITIVEPOISONDURATION, AActor, flags6), + DEFINE_FLAG(MF6, BLOCKEDBYSOLIDACTORS, AActor, flags6), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index 734e3e12b..7492f12a0 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -1223,7 +1223,7 @@ ExpEmit FxAddSub::Emit(VMFunctionBuilder *build) // Since addition is commutative, only the second operand may be a constant. if (op1.Konst) { - swap(op1, op2); + swapvalues(op1, op2); } assert(!op1.Konst); op1.Free(build); @@ -1404,7 +1404,7 @@ ExpEmit FxMulDiv::Emit(VMFunctionBuilder *build) // Multiplication is commutative, so only the second operand may be constant. if (op1.Konst) { - swap(op1, op2); + swapvalues(op1, op2); } assert(!op1.Konst); op1.Free(build); @@ -1709,7 +1709,7 @@ ExpEmit FxCompareEq::Emit(VMFunctionBuilder *build) // Only the second operand may be constant. if (op1.Konst) { - swap(op1, op2); + swapvalues(op1, op2); } assert(!op1.Konst); @@ -1873,7 +1873,7 @@ ExpEmit FxBinaryInt::Emit(VMFunctionBuilder *build) op2 = right->Emit(build); if (op1.Konst) { - swap(op1, op2); + swapvalues(op1, op2); } assert(!op1.Konst); rop = op2.RegNum; @@ -2439,7 +2439,7 @@ ExpVal FxRandom::EvalExpression (AActor *self) if (maxval < minval) { - swap (maxval, minval); + swapvalues (maxval, minval); } val.Int = (*rng)(maxval - minval + 1) + minval; @@ -2469,7 +2469,7 @@ int DecoRandom(VMFrameStack *stack, VMValue *param, int numparam, VMReturn *ret, int min = param[1].i, max = param[2].i; if (max < min) { - swap(max, min); + swapvalues(max, min); } ret->SetInt((*rng)(max - min + 1) + min); } @@ -2545,7 +2545,7 @@ ExpVal FxFRandom::EvalExpression (AActor *self) if (maxval < minval) { - swap (maxval, minval); + swapvalues (maxval, minval); } val.Float = frandom * (maxval - minval) + minval; @@ -2570,7 +2570,7 @@ int DecoFRandom(VMFrameStack *stack, VMValue *param, int numparam, VMReturn *ret double min = param[1].f, max = param[2].f; if (max < min) { - swap(max, min); + swapvalues(max, min); } ret->SetFloat(frandom * (max - min) + min); } diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index 0bf7832cc..3e807885b 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -52,6 +52,9 @@ #include "thingdef_exp.h" #include "w_wad.h" #include "v_video.h" +#include "version.h" +#include "v_text.h" +#include "m_argv.h" void ParseOldDecoration(FScanner &sc, EDefinitionType def); @@ -1119,6 +1122,8 @@ static PClassActor *ParseActorHeader(FScanner &sc, Baggage *bag) { PClassActor *info = CreateNewActor(sc, typeName, parentName, native); info->DoomEdNum = DoomEdNum > 0 ? DoomEdNum : -1; + info->SourceLumpName = Wads.GetLumpFullPath(sc.LumpNum); + SetReplacement(info, replaceName); ResetBaggage (bag, info == RUNTIME_CLASS(AActor) ? NULL : static_cast(info->ParentClass)); @@ -1225,6 +1230,16 @@ void ParseDecorate (FScanner &sc) case TK_Include: { sc.MustGetString(); + // This check needs to remain overridable for testing purposes. + if (Wads.GetLumpFile(sc.LumpNum) == 0 && !Args->CheckParm("-allowdecoratecrossincludes")) + { + int includefile = Wads.GetLumpFile(Wads.CheckNumForFullName(sc.String, true)); + if (includefile != 0) + { + I_FatalError("File %s is overriding core lump %s.", + Wads.GetWadFullName(includefile), sc.String); + } + } FScanner newscanner; newscanner.Open(sc.String); ParseDecorate(newscanner); diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index ef147b183..0d6408dd0 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -265,17 +265,10 @@ DEFINE_INFO_PROPERTY(conversationid, IiI, Actor) if ((gameinfo.flags & (GI_SHAREWARE|GI_TEASER2)) == (GI_SHAREWARE|GI_TEASER2)) convid = id2; - if (convid == -1) - return; - } - if (convid < 0 || convid > 1000) - { - I_Error ("ConversationID must be in the range [0,1000]"); - } - else - { - StrifeTypes[convid] = info; } + + if (convid <= 0) return; // 0 is not usable because the dialogue scripts use it as 'no object'. + SetStrifeType(convid, info); } //========================================================================== @@ -1024,11 +1017,26 @@ DEFINE_PROPERTY(maxdropoffheight, F, Actor) //========================================================================== // //========================================================================== -DEFINE_PROPERTY(poisondamage, I, Actor) +DEFINE_PROPERTY(poisondamage, Iii, Actor) { - PROP_INT_PARM(i, 0); - assert(info->IsKindOf(RUNTIME_CLASS(PClassActor))); - static_cast(info)->PoisonDamage = i; + PROP_INT_PARM(poisondamage, 0); + PROP_INT_PARM(poisonduration, 1); + PROP_INT_PARM(poisonperiod, 2); + + defaults->PoisonDamage = poisondamage; + if (PROP_PARM_COUNT == 1) + { + defaults->PoisonDuration = INT_MIN; + } + else + { + defaults->PoisonDuration = poisonduration; + + if (PROP_PARM_COUNT > 2) + defaults->PoisonPeriod = poisonperiod; + else + defaults->PoisonPeriod = 0; + } } //========================================================================== @@ -1900,7 +1908,7 @@ DEFINE_CLASS_PROPERTY_PREFIX(player, colorrange, I_I, PlayerPawn) PROP_INT_PARM(end, 1); if (start > end) - swap (start, end); + swapvalues (start, end); assert(info->IsKindOf(RUNTIME_CLASS(PClassPlayerPawn))); static_cast(info)->ColorRangeStart = start; @@ -2138,10 +2146,19 @@ DEFINE_CLASS_PROPERTY_PREFIX(player, crouchsprite, S, PlayerPawn) //========================================================================== // //========================================================================== -DEFINE_CLASS_PROPERTY_PREFIX(player, damagescreencolor, C, PlayerPawn) +DEFINE_CLASS_PROPERTY_PREFIX(player, damagescreencolor, Cf, PlayerPawn) { PROP_COLOR_PARM(c, 0); defaults->DamageFade = c; + if (PROP_PARM_COUNT < 3) // Because colors count as 2 parms + { + defaults->DamageFade.a = 255; + } + else + { + PROP_FLOAT_PARM(a, 2); + defaults->DamageFade.a = BYTE(255 * clamp(a, 0.f, 1.f)); + } } //========================================================================== diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index 095e19661..7efade8ad 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -234,7 +234,7 @@ do_stop: { if (sc.Compare("BRIGHT")) { - state.Frame |= SF_FULLBRIGHT; + state.Fullbright = true; continue; } if (sc.Compare("OFFSET")) diff --git a/src/timidity/instrum_sf2.cpp b/src/timidity/instrum_sf2.cpp index 5064c9fe1..a455bbdee 100644 --- a/src/timidity/instrum_sf2.cpp +++ b/src/timidity/instrum_sf2.cpp @@ -1035,11 +1035,11 @@ void SFFile::CheckZones(int start, int stop, bool instr) // Check for swapped ranges. (Should we fix them or ignore them?) if (bag[i].KeyRange.Lo > bag[i].KeyRange.Hi) { - swap(bag[i].KeyRange.Lo, bag[i].KeyRange.Hi); + swapvalues(bag[i].KeyRange.Lo, bag[i].KeyRange.Hi); } if (bag[i].VelRange.Lo > bag[i].VelRange.Hi) { - swap(bag[i].VelRange.Lo, bag[i].VelRange.Hi); + swapvalues(bag[i].VelRange.Lo, bag[i].VelRange.Hi); } } } diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 42b0b5145..af6b804d1 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -62,6 +62,9 @@ int CleanWidth, CleanHeight; // Above minus 1 (or 1, if they are already 1) int CleanXfac_1, CleanYfac_1, CleanWidth_1, CleanHeight_1; +// FillSimplePoly uses this +extern "C" short spanend[MAXHEIGHT]; + CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); // For routines that take RGB colors, cache the previous lookup in case there @@ -69,6 +72,7 @@ CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); static int LastPal = -1; static uint32 LastRGB; + static int PalFromRGB(uint32 rgb) { if (LastPal >= 0 && LastRGB == rgb) @@ -186,7 +190,7 @@ void STACK_ARGS DCanvas::DrawTextureV(FTexture *img, double x, double y, uint32 double iyscale = 1 / yscale; spryscale = FLOAT2FIXED(yscale); - + assert(spryscale > 2); #if 0 // Fix precision errors that are noticeable at some resolutions if ((y0 + parms.destheight) > (y0 + yscale * img->GetHeight())) @@ -919,7 +923,7 @@ void DCanvas::DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 real { // horizontal line if (x0 > x1) { - swap (x0, x1); + swapvalues (x0, x1); } memset (GetBuffer() + y0*GetPitch() + x0, palColor, deltaX+1); } @@ -1091,6 +1095,172 @@ void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uin } } +//========================================================================== +// +// DCanvas :: FillSimplePoly +// +// Fills a simple polygon with a texture. Here, "simple" means that a +// horizontal scanline at any vertical position within the polygon will +// not cross it more than twice. +// +// The originx, originy, scale, and rotation parameters specify +// transformation of the filling texture, not of the points. +// +// The points must be specified in clockwise order. +// +//========================================================================== + +void DCanvas::FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, angle_t rotation, + FDynamicColormap *colormap, int lightlevel) +{ + // Use an equation similar to player sprites to determine shade + fixed_t shade = LIGHT2SHADE(lightlevel) - 12*FRACUNIT; + float topy, boty, leftx, rightx; + int toppt, botpt, pt1, pt2; + int i; + int y1, y2, y; + fixed_t x; + double rot = rotation * M_PI / double(1u << 31); + bool dorotate = rot != 0; + double cosrot, sinrot; + + if (--npoints < 2 || Buffer == NULL) + { // not a polygon or we're not locked + return; + } + + // Find the extents of the polygon, in particular the highest and lowest points. + for (botpt = toppt = 0, boty = topy = points[0].Y, leftx = rightx = points[0].X, i = 1; i <= npoints; ++i) + { + if (points[i].Y < topy) + { + topy = points[i].Y; + toppt = i; + } + if (points[i].Y > boty) + { + boty = points[i].Y; + botpt = i; + } + if (points[i].X < leftx) + { + leftx = points[i].X; + } + if (points[i].X > rightx) + { + rightx = points[i].X; + } + } + if (topy >= Height || // off the bottom of the screen + boty <= 0 || // off the top of the screen + leftx >= Width || // off the right of the screen + rightx <= 0) // off the left of the screen + { + return; + } + + cosrot = cos(rot); + sinrot = sin(rot); + + // Setup constant texture mapping parameters. + R_SetupSpanBits(tex); + R_SetSpanColormap(colormap != NULL ? &colormap->Maps[clamp(shade >> FRACBITS, 0, NUMCOLORMAPS-1) * 256] : identitymap); + R_SetSpanSource(tex->GetPixels()); + scalex = double(1u << (32 - ds_xbits)) / scalex; + scaley = double(1u << (32 - ds_ybits)) / scaley; + ds_xstep = xs_RoundToInt(cosrot * scalex); + ds_ystep = xs_RoundToInt(sinrot * scaley); + + // Travel down the right edge and create an outline of that edge. + pt1 = toppt; + pt2 = toppt + 1; if (pt2 > npoints) pt2 = 0; + y1 = xs_RoundToInt(points[pt1].Y + 0.5f); + do + { + x = FLOAT2FIXED(points[pt1].X + 0.5f); + y2 = xs_RoundToInt(points[pt2].Y + 0.5f); + if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height)) + { + } + else + { + fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y)); + int y3 = MIN(y2, Height); + if (y1 < 0) + { + x += xinc * -y1; + y1 = 0; + } + for (y = y1; y < y3; ++y) + { + spanend[y] = clamp(x >> FRACBITS, -1, Width); + x += xinc; + } + } + y1 = y2; + pt1 = pt2; + pt2++; if (pt2 > npoints) pt2 = 0; + } while (pt1 != botpt); + + // Travel down the left edge and fill it in. + pt1 = toppt; + pt2 = toppt - 1; if (pt2 < 0) pt2 = npoints; + y1 = xs_RoundToInt(points[pt1].Y + 0.5f); + do + { + x = FLOAT2FIXED(points[pt1].X + 0.5f); + y2 = xs_RoundToInt(points[pt2].Y + 0.5f); + if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height)) + { + } + else + { + fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y)); + int y3 = MIN(y2, Height); + if (y1 < 0) + { + x += xinc * -y1; + y1 = 0; + } + for (y = y1; y < y3; ++y) + { + int x1 = x >> FRACBITS; + int x2 = spanend[y]; + if (x2 > x1 && x2 > 0 && x1 < Width) + { + x1 = MAX(x1, 0); + x2 = MIN(x2, Width); +#if 0 + memset(this->Buffer + y * this->Pitch + x1, (int)tex, x2 - x1); +#else + ds_y = y; + ds_x1 = x1; + ds_x2 = x2 - 1; + + TVector2 tex(x1 - originx, y - originy); + if (dorotate) + { + double t = tex.X; + tex.X = t * cosrot - tex.Y * sinrot; + tex.Y = tex.Y * cosrot + t * sinrot; + } + ds_xfrac = xs_RoundToInt(tex.X * scalex); + ds_yfrac = xs_RoundToInt(tex.Y * scaley); + + R_DrawSpan(); +#endif + } + x += xinc; + } + } + y1 = y2; + pt1 = pt2; + pt2--; if (pt2 < 0) pt2 = npoints; + } while (pt1 != botpt); +} + + /********************************/ /* */ /* Other miscellaneous routines */ diff --git a/src/v_palette.cpp b/src/v_palette.cpp index c47f75370..3149f5df5 100644 --- a/src/v_palette.cpp +++ b/src/v_palette.cpp @@ -224,7 +224,7 @@ void FPalette::MakeGoodRemap () if (new0 > dup) { // Make the lower-numbered entry a copy of color 0. (Just because.) - swap (new0, dup); + swapvalues (new0, dup); } Remap[0] = new0; Remap[new0] = dup; @@ -483,12 +483,12 @@ void InitPalette () { int intensity = (GPalette.BaseColors[c].r * 77 + GPalette.BaseColors[c].g * 143 + - GPalette.BaseColors[c].b * 37) / 255; + GPalette.BaseColors[c].b * 37) / 256; int r = (GPalette.BaseColors[c].r * (31-m) + intensity *m) / 31; int g = (GPalette.BaseColors[c].g * (31-m) + intensity *m) / 31; int b = (GPalette.BaseColors[c].b * (31-m) + intensity *m) / 31; - shade[c] = ColorMatcher.Pick (r, g, b); + shade[c] = ColorMatcher.Pick(r, g, b); } } diff --git a/src/v_video.cpp b/src/v_video.cpp index 24ed1a562..1e8993e74 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -62,6 +62,7 @@ #include "m_png.h" #include "colormatcher.h" #include "v_palette.h" +#include "r_sky.h" IMPLEMENT_ABSTRACT_CLASS (DCanvas) @@ -1196,6 +1197,83 @@ void DFrameBuffer::WipeCleanup() wipe_Cleanup(); } +//=========================================================================== +// +// Create texture hitlist +// +//=========================================================================== + +void DFrameBuffer::GetHitlist(BYTE *hitlist) +{ + BYTE *spritelist; + int i; + + spritelist = new BYTE[sprites.Size()]; + + // Precache textures (and sprites). + memset (spritelist, 0, sprites.Size()); + + { + AActor *actor; + TThinkerIterator iterator; + + while ( (actor = iterator.Next ()) ) + spritelist[actor->sprite] = 1; + } + + for (i = (int)(sprites.Size () - 1); i >= 0; i--) + { + if (spritelist[i]) + { + int j, k; + for (j = 0; j < sprites[i].numframes; j++) + { + const spriteframe_t *frame = &SpriteFrames[sprites[i].spriteframes + j]; + + for (k = 0; k < 16; k++) + { + FTextureID pic = frame->Texture[k]; + if (pic.isValid()) + { + hitlist[pic.GetIndex()] = 1; + } + } + } + } + } + + delete[] spritelist; + + for (i = numsectors - 1; i >= 0; i--) + { + hitlist[sectors[i].GetTexture(sector_t::floor).GetIndex()] = + hitlist[sectors[i].GetTexture(sector_t::ceiling).GetIndex()] |= 2; + } + + for (i = numsides - 1; i >= 0; i--) + { + hitlist[sides[i].GetTexture(side_t::top).GetIndex()] = + hitlist[sides[i].GetTexture(side_t::mid).GetIndex()] = + hitlist[sides[i].GetTexture(side_t::bottom).GetIndex()] |= 1; + } + + // Sky texture is always present. + // Note that F_SKY1 is the name used to + // indicate a sky floor/ceiling as a flat, + // while the sky texture is stored like + // a wall texture, with an episode dependant + // name. + + if (sky1texture.isValid()) + { + hitlist[sky1texture.GetIndex()] |= 1; + } + if (sky2texture.isValid()) + { + hitlist[sky2texture.GetIndex()] |= 1; + } +} + //=========================================================================== // // Texture precaching @@ -1690,3 +1768,14 @@ const int BaseRatioSizes[5][4] = { 960, 600, 0, 48 }, { 960, 640, (int)(6.5*FRACUNIT), 48*15/16 } // 5:4 320, 213.3333, multiplied by three }; + +void IVideo::DumpAdapters () +{ + Printf("Multi-monitor support unavailable.\n"); +} + +CCMD(vid_listadapters) +{ + if (Video != NULL) + Video->DumpAdapters(); +} diff --git a/src/v_video.h b/src/v_video.h index 6beaeea7b..b1ba7a863 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -133,6 +133,7 @@ enum class FFont; struct FRemapTable; class player_t; +typedef uint32 angle_t; // // VIDEO @@ -175,6 +176,11 @@ public: // Fill an area with a texture virtual void FlatFill (int left, int top, int right, int bottom, FTexture *src, bool local_origin=false); + // Fill a simple polygon with a texture + virtual void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, angle_t rotation, + struct FDynamicColormap *colormap, int lightlevel); + // Set an area to a specified color virtual void Clear (int left, int top, int right, int bottom, int palcolor, uint32 color); @@ -398,6 +404,7 @@ public: virtual FNativePalette *CreatePalette(FRemapTable *remap); // Precaches or unloads a texture + virtual void GetHitlist(BYTE *hitlist); virtual void PrecacheTexture(FTexture *tex, int cache); // Screen wiping diff --git a/src/vectors.h b/src/vectors.h index 013077042..c0783e250 100644 --- a/src/vectors.h +++ b/src/vectors.h @@ -242,12 +242,14 @@ struct TVector2 return *this * len; } - // Scales this vector into a unit vector - void MakeUnit() + // Scales this vector into a unit vector. Returns the old length + double MakeUnit() { - double len = Length(); - if (len != 0) len = 1 / len; - *this *= len; + double len, ilen; + len = ilen = Length(); + if (ilen != 0) ilen = 1 / ilen; + *this *= ilen; + return len; } // Dot product diff --git a/src/version.h b/src/version.h index de017995c..5854c5f01 100644 --- a/src/version.h +++ b/src/version.h @@ -40,16 +40,16 @@ /** Lots of different version numbers **/ -#define DOTVERSIONSTR_NOREV "2.4.0" +#define DOTVERSIONSTR_NOREV "2.5.0" // The version string the user actually sees. #define DOTVERSIONSTR DOTVERSIONSTR_NOREV " (r" SVN_REVISION_STRING ")" // The version as seen in the Windows resource -#define RC_FILEVERSION 2,4,0,SVN_REVISION_NUMBER -#define RC_PRODUCTVERSION 2,4,0,0 +#define RC_FILEVERSION 2,5,0,SVN_REVISION_NUMBER +#define RC_PRODUCTVERSION 2,5,0,0 #define RC_FILEVERSION2 DOTVERSIONSTR -#define RC_PRODUCTVERSION2 "2.4" +#define RC_PRODUCTVERSION2 "2.5" // Version identifier for network games. // Bump it every time you do a release unless you're certain you diff --git a/src/w_wad.cpp b/src/w_wad.cpp index 5727c8bcc..13e7ff14a 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -70,6 +70,7 @@ struct FWadCollection::LumpRecord }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- +extern bool nospriterename; // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -759,7 +760,7 @@ void FWadCollection::RenameSprites () } } - renameAll = !!Args->CheckParm ("-oldsprites"); + renameAll = !!Args->CheckParm ("-oldsprites") || nospriterename; for (DWORD i = 0; i < LumpInfo.Size(); i++) { diff --git a/src/win32/cursor1.cur b/src/win32/cursor1.cur deleted file mode 100644 index 048f06b4a..000000000 Binary files a/src/win32/cursor1.cur and /dev/null differ diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index 561891e29..7453571e5 100644 --- a/src/win32/fb_d3d9.cpp +++ b/src/win32/fb_d3d9.cpp @@ -237,11 +237,14 @@ CVAR(Bool, vid_hwaalines, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) // //========================================================================== -D3DFB::D3DFB (int width, int height, bool fullscreen) +D3DFB::D3DFB (UINT adapter, int width, int height, bool fullscreen) : BaseWinFB (width, height) { D3DPRESENT_PARAMETERS d3dpp; + LastHR = 0; + + Adapter = adapter; D3DDevice = NULL; VertexBuffer = NULL; IndexBuffer = NULL; @@ -275,7 +278,7 @@ D3DFB::D3DFB (int width, int height, bool fullscreen) GatheringWipeScreen = false; ScreenWipe = NULL; InScene = false; - QuadExtra = new BufferedQuad[MAX_QUAD_BATCH]; + QuadExtra = new BufferedTris[MAX_QUAD_BATCH]; Packs = NULL; PixelDoubling = 0; SkipAt = -1; @@ -321,20 +324,28 @@ D3DFB::D3DFB (int width, int height, bool fullscreen) HRESULT hr; - if (FAILED(hr = D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Window, - D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice))) + LOG("CreateDevice attempt 1 hwvp\n"); + if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) { - if (FAILED(D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Window, - D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice))) + LOG2("CreateDevice returned hr %08x dev %p; attempt 2 swvp\n", hr, D3DDevice); + if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) { if (d3dpp.FullScreen_RefreshRateInHz != 0) { d3dpp.FullScreen_RefreshRateInHz = 0; - if (FAILED(hr = D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Window, - D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice))) + LOG2("CreateDevice returned hr %08x dev %p; attempt 3 (hwvp, default Hz)\n", hr, D3DDevice); + if (FAILED(hr = D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + (hr != D3DERR_DEVICELOST || D3DDevice == NULL)) { - if (FAILED(D3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, Window, - D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice))) + LOG2("CreateDevice returned hr %08x dev %p; attempt 4 (swvp, default Hz)\n", hr, D3DDevice); + if (FAILED(D3D->CreateDevice(Adapter, D3DDEVTYPE_HAL, Window, + D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &d3dpp, &D3DDevice)) && + hr != D3DERR_DEVICELOST) { D3DDevice = NULL; } @@ -342,6 +353,8 @@ D3DFB::D3DFB (int width, int height, bool fullscreen) } } } + LOG2("Final CreateDevice returned HR %08x and device %p\n", hr, D3DDevice); + LastHR = hr; if (D3DDevice != NULL) { D3DADAPTER_IDENTIFIER9 adapter_id; @@ -360,9 +373,7 @@ D3DFB::D3DFB (int width, int height, bool fullscreen) { DeviceCaps.LineCaps |= D3DLINECAPS_ANTIALIAS; } - // I don't know about ATI's drivers. The only ATI device - // I have readily available to test with (a Mobility X300) - // really doesn't support them. + // ATI's drivers apparently also lie, so screw this caps bit. } CreateResources(); SetInitialState(); @@ -998,7 +1009,7 @@ bool D3DFB::IsValid () HRESULT D3DFB::GetHR () { - return 0; + return LastHR; } //========================================================================== @@ -1716,7 +1727,7 @@ void D3DFB::GetScreenshotBuffer(const BYTE *&buffer, int &pitch, ESSType &color_ } else { - buffer = (const BYTE *)lrect.pBits + lrect.Pitch * LBOffsetI; + buffer = (const BYTE *)lrect.pBits; pitch = lrect.Pitch; color_type = SS_BGRA; } @@ -1761,7 +1772,7 @@ IDirect3DTexture9 *D3DFB::GetCurrentScreen(D3DPOOL pool) assert(pool == D3DPOOL_SYSTEMMEM || pool == D3DPOOL_DEFAULT); - if (FAILED(FrontCopySurface->GetDesc(&desc))) + if (FrontCopySurface == NULL || FAILED(FrontCopySurface->GetDesc(&desc))) { return NULL; } @@ -1854,7 +1865,7 @@ void D3DFB::DrawPackedTextures(int packnum) CheckQuadBatch(); - BufferedQuad *quad = &QuadExtra[QuadBatchPos]; + BufferedTris *quad = &QuadExtra[QuadBatchPos]; FBVERTEX *vert = &VertexData[VertexPos]; quad->Group1 = 0; @@ -1870,6 +1881,8 @@ void D3DFB::DrawPackedTextures(int packnum) } quad->Palette = NULL; quad->Texture = pack->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; float x0 = float(x) - 0.5f; float y0 = float(y) - 0.5f; @@ -2956,7 +2969,7 @@ void STACK_ARGS D3DFB::DrawTextureV (FTexture *img, double x, double y, uint32 t if (parms.flipX) { - swap(u0, u1); + swapvalues(u0, u1); } if (parms.windowleft > 0 || parms.windowright < parms.texwidth) { @@ -3010,16 +3023,20 @@ void STACK_ARGS D3DFB::DrawTextureV (FTexture *img, double x, double y, uint32 t parms.bilinear = false; D3DCOLOR color0, color1; - if (!SetStyle(tex, parms, color0, color1, QuadExtra[QuadBatchPos])) + BufferedTris *quad = &QuadExtra[QuadBatchPos]; + + if (!SetStyle(tex, parms, color0, color1, *quad)) { return; } - QuadExtra[QuadBatchPos].Texture = tex->Box->Owner->Tex; + quad->Texture = tex->Box->Owner->Tex; if (parms.bilinear) { - QuadExtra[QuadBatchPos].Flags |= BQF_Bilinear; + quad->Flags |= BQF_Bilinear; } + quad->NumTris = 2; + quad->NumVerts = 4; float yoffs = GatheringWipeScreen ? 0.5f : 0.5f - LBOffset; @@ -3141,7 +3158,7 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo CheckQuadBatch(); - BufferedQuad *quad = &QuadExtra[QuadBatchPos]; + BufferedTris *quad = &QuadExtra[QuadBatchPos]; FBVERTEX *vert = &VertexData[VertexPos]; quad->Group1 = 0; @@ -3157,6 +3174,8 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo } quad->Palette = NULL; quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = 4; + quad->NumTris = 2; vert[0].x = x0; vert[0].y = y0; @@ -3206,6 +3225,128 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo IndexPos += 6; } +//========================================================================== +// +// D3DFB :: FillSimplePoly +// +// Here, "simple" means that a simple triangle fan can draw it. +// +//========================================================================== + +void D3DFB::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, + angle_t rotation, FDynamicColormap *colormap, int lightlevel) +{ + // Use an equation similar to player sprites to determine shade + fixed_t shade = LIGHT2SHADE(lightlevel) - 12*FRACUNIT; + BufferedTris *quad; + FBVERTEX *verts; + D3DTex *tex; + float yoffs, uscale, vscale; + int i, ipos; + D3DCOLOR color0, color1; + float ox, oy; + float cosrot, sinrot; + float rot = float(rotation * M_PI / float(1u << 31)); + bool dorotate = rot != 0; + + if (npoints < 3) + { // This is no polygon. + return; + } + if (In2D < 2) + { + Super::FillSimplePoly(texture, points, npoints, originx, originy, scalex, scaley, rotation, colormap, lightlevel); + return; + } + if (!InScene) + { + return; + } + tex = static_cast(texture->GetNative(true)); + if (tex == NULL) + { + return; + } + + cosrot = cos(rot); + sinrot = sin(rot); + + CheckQuadBatch(npoints - 2, npoints); + quad = &QuadExtra[QuadBatchPos]; + verts = &VertexData[VertexPos]; + + color0 = 0; + color1 = 0xFFFFFFFF; + + quad->Group1 = 0; + if (tex->GetTexFormat() == D3DFMT_L8 && !tex->IsGray) + { + quad->Flags = BQF_WrapUV | BQF_GamePalette | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_PalTex; + if (colormap != NULL) + { + if (colormap->Desaturate != 0) + { + quad->Flags |= BQF_Desaturated; + } + quad->ShaderNum = BQS_InGameColormap; + color0 = D3DCOLOR_ARGB(colormap->Desaturate, + colormap->Color.r, colormap->Color.g, colormap->Color.b); + double fadelevel = clamp(shade / (NUMCOLORMAPS * 65536.0), 0.0, 1.0); + color1 = D3DCOLOR_ARGB(DWORD((1 - fadelevel) * 255), + DWORD(colormap->Fade.r * fadelevel), + DWORD(colormap->Fade.g * fadelevel), + DWORD(colormap->Fade.b * fadelevel)); + } + } + else + { + quad->Flags = BQF_WrapUV | BQF_DisableAlphaTest; + quad->ShaderNum = BQS_Plain; + } + quad->Palette = NULL; + quad->Texture = tex->Box->Owner->Tex; + quad->NumVerts = npoints; + quad->NumTris = npoints - 2; + + yoffs = GatheringWipeScreen ? 0 : LBOffset; + uscale = float(1.f / (texture->GetScaledWidth() * scalex)); + vscale = float(1.f / (texture->GetScaledHeight() * scaley)); + ox = float(originx); + oy = float(originy); + + for (i = 0; i < npoints; ++i) + { + verts[i].x = points[i].X; + verts[i].y = points[i].Y + yoffs; + verts[i].z = 0; + verts[i].rhw = 1; + verts[i].color0 = color0; + verts[i].color1 = color1; + float u = points[i].X - 0.5f - ox; + float v = points[i].Y - 0.5f - oy; + if (dorotate) + { + float t = u; + u = t * cosrot - v * sinrot; + v = v * cosrot + t * sinrot; + } + verts[i].tu = u * uscale; + verts[i].tv = v * vscale; + } + for (ipos = IndexPos, i = 2; i < npoints; ++i, ipos += 3) + { + IndexData[ipos ] = VertexPos; + IndexData[ipos + 1] = VertexPos + i - 1; + IndexData[ipos + 2] = VertexPos + i; + } + + QuadBatchPos++; + VertexPos += npoints; + IndexPos = ipos; +} + //========================================================================== // // D3DFB :: AddColorOnlyQuad @@ -3216,7 +3357,7 @@ void D3DFB::FlatFill(int left, int top, int right, int bottom, FTexture *src, bo void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color) { - BufferedQuad *quad; + BufferedTris *quad; FBVERTEX *verts; CheckQuadBatch(); @@ -3236,6 +3377,8 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR } quad->Palette = NULL; quad->Texture = NULL; + quad->NumVerts = 4; + quad->NumTris = 2; verts[0].x = x; verts[0].y = y; @@ -3289,17 +3432,19 @@ void D3DFB::AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR // // D3DFB :: CheckQuadBatch // -// Make sure there's enough room in the batch for one more quad. +// Make sure there's enough room in the batch for one more set of triangles. // //========================================================================== -void D3DFB::CheckQuadBatch() +void D3DFB::CheckQuadBatch(int numtris, int numverts) { if (BatchType == BATCH_Lines) { EndLineBatch(); } - else if (QuadBatchPos == MAX_QUAD_BATCH) + else if (QuadBatchPos == MAX_QUAD_BATCH || + VertexPos + numverts > NUM_VERTS || + IndexPos + numtris * 3 > NUM_INDEXES) { EndQuadBatch(); } @@ -3360,23 +3505,33 @@ void D3DFB::EndQuadBatch() D3DDevice->SetIndices(IndexBuffer); bool uv_wrapped = false; bool uv_should_wrap; + int indexpos, vertpos; + indexpos = vertpos = 0; for (int i = 0; i < QuadBatchPos; ) { - const BufferedQuad *quad = &QuadExtra[i]; + const BufferedTris *quad = &QuadExtra[i]; int j; + int startindex = indexpos; + int startvertex = vertpos; + + indexpos += quad->NumTris * 3; + vertpos += quad->NumVerts; + // Quads with matching parameters should be done with a single // DrawPrimitive call. for (j = i + 1; j < QuadBatchPos; ++j) { - const BufferedQuad *q2 = &QuadExtra[j]; + const BufferedTris *q2 = &QuadExtra[j]; if (quad->Texture != q2->Texture || quad->Group1 != q2->Group1 || quad->Palette != q2->Palette) { break; } + indexpos += q2->NumTris * 3; + vertpos += q2->NumVerts; } // Set the palette (if one) @@ -3456,7 +3611,12 @@ void D3DFB::EndQuadBatch() } // Draw the quad - D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4 * i, 4 * (j - i), 6 * i, 2 * (j - i)); + D3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, + startvertex, // MinIndex + vertpos - startvertex, // NumVertices + startindex, // StartIndex + (indexpos - startindex) / 3 // PrimitiveCount + /*4 * i, 4 * (j - i), 6 * i, 2 * (j - i)*/); i = j; } if (uv_wrapped) @@ -3497,7 +3657,7 @@ void D3DFB::EndBatch() // //========================================================================== -bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedQuad &quad) +bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad) { D3DFORMAT fmt = tex->GetTexFormat(); FRenderStyle style = parms.style; @@ -3629,7 +3789,7 @@ bool D3DFB::SetStyle(D3DTex *tex, DrawParms &parms, D3DCOLOR &color0, D3DCOLOR & if (quad.Flags & BQF_InvertSource) { quad.Flags &= ~BQF_InvertSource; - swap(start, end); + swapvalues(start, end); } quad.ShaderNum = BQS_SpecialColormap; color0 = D3DCOLOR_RGBA(DWORD(start[0]/2*255), DWORD(start[1]/2*255), DWORD(start[2]/2*255), color0 >> 24); diff --git a/src/win32/fb_d3d9_wipe.cpp b/src/win32/fb_d3d9_wipe.cpp index 9a3b34412..3407136aa 100644 --- a/src/win32/fb_d3d9_wipe.cpp +++ b/src/win32/fb_d3d9_wipe.cpp @@ -231,7 +231,7 @@ void D3DFB::WipeEndScreen() // waste time copying from TempRenderTexture to FinalWipeScreen. if (FinalWipeScreen != TempRenderTexture) { - swap(RenderTexture[CurrRenderTexture], FinalWipeScreen); + swapvalues(RenderTexture[CurrRenderTexture], FinalWipeScreen); TempRenderTexture = RenderTexture[CurrRenderTexture]; } @@ -466,7 +466,7 @@ bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb) { fb->CheckQuadBatch(); - BufferedQuad *quad = &fb->QuadExtra[fb->QuadBatchPos]; + BufferedTris *quad = &fb->QuadExtra[fb->QuadBatchPos]; FBVERTEX *vert = &fb->VertexData[fb->VertexPos]; WORD *index = &fb->IndexData[fb->IndexPos]; @@ -475,6 +475,8 @@ bool D3DFB::Wiper_Melt::Run(int ticks, D3DFB *fb) quad->ShaderNum = BQS_Plain; quad->Palette = NULL; quad->Texture = fb->InitialWipeScreen; + quad->NumVerts = 4; + quad->NumTris = 2; // Fill the vertex buffer. float u0 = rect.left / float(fb->FBWidth); diff --git a/src/win32/fb_ddraw.cpp b/src/win32/fb_ddraw.cpp index e440bd7f1..3a9b3d35e 100644 --- a/src/win32/fb_ddraw.cpp +++ b/src/win32/fb_ddraw.cpp @@ -774,7 +774,7 @@ void DDrawFB::RebuildColorTable () for (i = 0; i < 256; i++) { - swap (syspal[i].peRed, syspal[i].peBlue); + swapvalues (syspal[i].peRed, syspal[i].peBlue); } for (i = 0; i < 256; i++) { diff --git a/src/win32/hardware.h b/src/win32/hardware.h index bdff41cf8..223ba621c 100644 --- a/src/win32/hardware.h +++ b/src/win32/hardware.h @@ -52,6 +52,7 @@ class IVideo virtual bool SetResolution (int width, int height, int bits); + virtual void DumpAdapters(); }; void I_InitGraphics (); diff --git a/src/win32/i_crash.cpp b/src/win32/i_crash.cpp index 727e4b7ed..5e4dfb1c9 100644 --- a/src/win32/i_crash.cpp +++ b/src/win32/i_crash.cpp @@ -69,6 +69,54 @@ #include #include +#if defined(_WIN64) && defined(__GNUC__) +struct KNONVOLATILE_CONTEXT_POINTERS { + union { + PDWORD64 IntegerContext[16]; + struct { + PDWORD64 Rax; + PDWORD64 Rcx; + PDWORD64 Rdx; + PDWORD64 Rbx; + PDWORD64 Rsp; + PDWORD64 Rbp; + PDWORD64 Rsi; + PDWORD64 Rdi; + PDWORD64 R8; + PDWORD64 R9; + PDWORD64 R10; + PDWORD64 R11; + PDWORD64 R12; + PDWORD64 R13; + PDWORD64 R14; + PDWORD64 R15; + }; + }; +}; +typedef +EXCEPTION_DISPOSITION +NTAPI +EXCEPTION_ROUTINE ( + struct _EXCEPTION_RECORD *ExceptionRecord, + PVOID EstablisherFrame, + struct _CONTEXT *ContextRecord, + PVOID DispatcherContext + ); +NTSYSAPI +EXCEPTION_ROUTINE * +NTAPI +RtlVirtualUnwind ( + DWORD HandlerType, + DWORD64 ImageBase, + DWORD64 ControlPc, + PRUNTIME_FUNCTION FunctionEntry, + PCONTEXT ContextRecord, + PVOID *HandlerData, + PDWORD64 EstablisherFrame, + KNONVOLATILE_CONTEXT_POINTERS *ContextPointers + ); +#endif + // MACROS ------------------------------------------------------------------ #define REMOTE_HOST "localhost" @@ -1312,7 +1360,7 @@ static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD } // If we reach a RIP of zero, this means we've walked off the end of // the call stack and are done. - if (context.Rip == NULL) + if (context.Rip == 0) { break; } diff --git a/src/win32/i_dijoy.cpp b/src/win32/i_dijoy.cpp index e21f77e25..89ddd1600 100644 --- a/src/win32/i_dijoy.cpp +++ b/src/win32/i_dijoy.cpp @@ -629,7 +629,7 @@ bool FDInputJoystick::ReorderAxisPair(const GUID &xid, const GUID &yid, int pos) } if (x == pos + 1 && y == pos) { // Xbox 360 Controllers return them in this order. - swap(Axes[pos], Axes[pos + 1]); + swapvalues(Axes[pos], Axes[pos + 1]); } else if (x != pos || y != pos + 1) { diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index e9d2a26bb..d01547dae 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -147,6 +147,7 @@ EXTERN_CVAR (Bool, lookstrafe) EXTERN_CVAR (Bool, use_joystick) static int WheelDelta; +extern bool CursorState; extern BOOL paused; static bool noidle = false; @@ -418,6 +419,14 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) I_CheckNativeMouse (false); break; + case WM_SETCURSOR: + if (!CursorState) + { + SetCursor(NULL); // turn off window cursor + return TRUE; // Prevent Windows from setting cursor to window class cursor + } + break; + case WM_SIZE: InvalidateRect (Window, NULL, FALSE); break; diff --git a/src/win32/i_main.cpp b/src/win32/i_main.cpp index 18b3990f1..40ee6138a 100644 --- a/src/win32/i_main.cpp +++ b/src/win32/i_main.cpp @@ -118,7 +118,6 @@ extern BYTE *ST_Util_BitsForBitmap (BITMAPINFO *bitmap_info); extern EXCEPTION_POINTERS CrashPointers; extern BITMAPINFO *StartupBitmap; extern UINT TimerPeriod; -extern HCURSOR TheArrowCursor, TheInvisibleCursor; // PUBLIC DATA DEFINITIONS ------------------------------------------------- @@ -932,9 +931,6 @@ void DoMain (HINSTANCE hInstance) x = y = 0; } - TheInvisibleCursor = LoadCursor (hInstance, MAKEINTRESOURCE(IDC_INVISIBLECURSOR)); - TheArrowCursor = LoadCursor (NULL, IDC_ARROW); - WNDCLASS WndClass; WndClass.style = 0; WndClass.lpfnWndProc = LConProc; @@ -942,7 +938,7 @@ void DoMain (HINSTANCE hInstance) WndClass.cbWndExtra = 0; WndClass.hInstance = hInstance; WndClass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(IDI_ICON1)); - WndClass.hCursor = TheArrowCursor; + WndClass.hCursor = LoadCursor (NULL, IDC_ARROW); WndClass.hbrBackground = NULL; WndClass.lpszMenuName = NULL; WndClass.lpszClassName = (LPCTSTR)WinClassName; diff --git a/src/win32/i_mouse.cpp b/src/win32/i_mouse.cpp index 80c00bdbe..d347a4d72 100644 --- a/src/win32/i_mouse.cpp +++ b/src/win32/i_mouse.cpp @@ -87,8 +87,6 @@ public: void Ungrab(); protected: - void CenterMouse(int x, int y); - POINT UngrabbedPointerPos; LONG PrevX, PrevY; bool Grabbed; @@ -112,6 +110,7 @@ static void SetCursorState(bool visible); static FMouse *CreateWin32Mouse(); static FMouse *CreateDInputMouse(); static FMouse *CreateRawMouse(); +static void CenterMouse(int x, int y, LONG *centx, LONG *centy); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- @@ -135,12 +134,12 @@ static FMouse *(*MouseFactory[])() = FMouse *Mouse; -HCURSOR TheArrowCursor; -HCURSOR TheInvisibleCursor; +bool CursorState; -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, 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, m_hidepointer, true, 0) CUSTOM_CVAR (Int, in_mouse, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL) { @@ -183,11 +182,48 @@ CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) static void SetCursorState(bool visible) { - HCURSOR usingCursor = visible ? TheArrowCursor : TheInvisibleCursor; - SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)usingCursor); + CursorState = visible || !m_hidepointer; if (GetForegroundWindow() == Window) { - SetCursor(usingCursor); + if (CursorState) + { + SetCursor((HCURSOR)(intptr_t)GetClassLongPtr(Window, GCLP_HCURSOR)); + } + else + { + SetCursor(NULL); + } + } +} + +//========================================================================== +// +// CenterMouse +// +// Moves the mouse to the center of the window, but only if the current +// position isn't already in the center. +// +//========================================================================== + +static void CenterMouse(int curx, int cury, LONG *centxp, LONG *centyp) +{ + RECT rect; + + GetWindowRect(Window, &rect); + + int centx = (rect.left + rect.right) >> 1; + int centy = (rect.top + rect.bottom) >> 1; + + // Reduce the number of WM_MOUSEMOVE messages that get sent + // by only calling SetCursorPos when we really need to. + if (centx != curx || centy != cury) + { + if (centxp != NULL) + { + *centxp = centx; + *centyp = centy; + } + SetCursorPos(centx, centy); } } @@ -449,6 +485,7 @@ static FMouse *CreateRawMouse() FRawMouse::FRawMouse() { Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -512,12 +549,11 @@ void FRawMouse::Grab() { GetCursorPos(&UngrabbedPointerPos); Grabbed = true; - while (ShowCursor(FALSE) >= 0) - { } + SetCursorState(false); // By setting the cursor position, we force the pointer image // to change right away instead of having it delayed until // some time in the future. - SetCursorPos(0, 0); + CenterMouse(-1, -1, NULL, NULL); } } } @@ -543,7 +579,7 @@ void FRawMouse::Ungrab() Grabbed = false; ClearButtonState(); } - ShowCursor(TRUE); + SetCursorState(true); SetCursorPos(UngrabbedPointerPos.x, UngrabbedPointerPos.y); } } @@ -586,8 +622,13 @@ bool FRawMouse::ProcessRawInput(RAWINPUT *raw, int code) { WheelMoved(1, (SHORT)raw->data.mouse.usButtonData); } - PostMouseMove(m_noprescale ? raw->data.mouse.lLastX : raw->data.mouse.lLastX<<2, - -raw->data.mouse.lLastY); + int x = m_noprescale ? raw->data.mouse.lLastX : raw->data.mouse.lLastX << 2; + int y = -raw->data.mouse.lLastY; + PostMouseMove(x, y); + if (x | y) + { + CenterMouse(-1, -1, NULL, NULL); + } return true; } @@ -638,6 +679,7 @@ FDInputMouse::FDInputMouse() { Device = NULL; Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -863,6 +905,7 @@ FWin32Mouse::FWin32Mouse() { GetCursorPos(&UngrabbedPointerPos); Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -920,7 +963,7 @@ void FWin32Mouse::ProcessInput() } if (x | y) { - CenterMouse(pt.x, pt.y); + CenterMouse(pt.x, pt.y, &PrevX, &PrevY); } PostMouseMove(x, y); } @@ -944,13 +987,13 @@ bool FWin32Mouse::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa { if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) { - CenterMouse(-1, -1); + CenterMouse(-1, -1, &PrevX, &PrevY); return true; } } else if (message == WM_MOVE) { - CenterMouse(-1, -1); + CenterMouse(-1, -1, &PrevX, &PrevY); return true; } else if (message == WM_SYSCOMMAND) @@ -1059,7 +1102,7 @@ void FWin32Mouse::Grab() ClipCursor(&rect); SetCursorState(false); - CenterMouse(-1, -1); + CenterMouse(-1, -1, &PrevX, &PrevY); Grabbed = true; } @@ -1085,34 +1128,6 @@ void FWin32Mouse::Ungrab() ClearButtonState(); } -//========================================================================== -// -// FWin32Mouse :: CenterMouse -// -// Moves the mouse to the center of the window, but only if the current -// position isn't already in the center. -// -//========================================================================== - -void FWin32Mouse::CenterMouse(int curx, int cury) -{ - RECT rect; - - GetWindowRect (Window, &rect); - - int centx = (rect.left + rect.right) >> 1; - int centy = (rect.top + rect.bottom) >> 1; - - // Reduce the number of WM_MOUSEMOVE messages that get sent - // by only calling SetCursorPos when we really need to. - if (centx != curx || centy != cury) - { - PrevX = centx; - PrevY = centy; - SetCursorPos (centx, centy); - } -} - /**************************************************************************/ /**************************************************************************/ diff --git a/src/win32/i_rawps2.cpp b/src/win32/i_rawps2.cpp index 68803fefb..d060b869d 100644 --- a/src/win32/i_rawps2.cpp +++ b/src/win32/i_rawps2.cpp @@ -388,8 +388,9 @@ FRawPS2Controller::~FRawPS2Controller() bool FRawPS2Controller::ProcessInput(RAWHID *raw, int code) { - // w32api has an incompatible definition of bRawData -#if __GNUC__ + // w32api has an incompatible definition of bRawData. + // (But the version that comes with MinGW64 is fine.) +#if defined(__GNUC__) && !defined(_WIN64) BYTE *rawdata = &raw->bRawData; #else BYTE *rawdata = raw->bRawData; diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index 04e2694d6..c8b496656 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -997,7 +997,7 @@ void I_PrintStr(const char *cp) if (edit != NULL) { // GDI uses BGR colors, but color is RGB, so swap the R and the B. - swap(color.r, color.b); + swapvalues(color.r, color.b); // Change the color. format.cbSize = sizeof(format); format.dwMask = CFM_COLOR; diff --git a/src/win32/st_start.cpp b/src/win32/st_start.cpp index 8fe844f70..1f0838ef2 100644 --- a/src/win32/st_start.cpp +++ b/src/win32/st_start.cpp @@ -656,23 +656,23 @@ FHexenStartupScreen::FHexenStartupScreen(int max_progress, HRESULT &hr) { RGBQUAD color; DWORD quad; - }; + } c; Wads.ReadLump (startup_lump, startup_screen); - color.rgbReserved = 0; + c.color.rgbReserved = 0; StartupBitmap = ST_Util_CreateBitmap (640, 480, 4); // Initialize the bitmap palette. for (int i = 0; i < 16; ++i) { - color.rgbRed = startup_screen[i*3+0]; - color.rgbGreen = startup_screen[i*3+1]; - color.rgbBlue = startup_screen[i*3+2]; + c.color.rgbRed = startup_screen[i*3+0]; + c.color.rgbGreen = startup_screen[i*3+1]; + c.color.rgbBlue = startup_screen[i*3+2]; // Convert from 6-bit per component to 8-bit per component. - quad = (quad << 2) | ((quad >> 4) & 0x03030303); - StartupBitmap->bmiColors[i] = color; + c.quad = (c.quad << 2) | ((c.quad >> 4) & 0x03030303); + StartupBitmap->bmiColors[i] = c.color; } // Fill in the bitmap data. Convert to chunky, because I can't figure out diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h index 9bdfd1072..6193c641c 100644 --- a/src/win32/win32iface.h +++ b/src/win32/win32iface.h @@ -75,6 +75,8 @@ class Win32Video : public IVideo bool GoFullscreen (bool yes); void BlankForGDI (); + void DumpAdapters (); + private: struct ModeInfo { @@ -97,12 +99,13 @@ class Win32Video : public IVideo int m_IteratorBits; bool m_IteratorFS; bool m_IsFullscreen; + UINT m_Adapter; void AddMode (int x, int y, int bits, int baseHeight, int doubling); void FreeModes (); static HRESULT WINAPI EnumDDModesCB (LPDDSURFACEDESC desc, void *modes); - void AddD3DModes (D3DFORMAT format); + void AddD3DModes (UINT adapter, D3DFORMAT format); void AddLowResModes (); void AddLetterboxModes (); void ScaleModes (int doubling); @@ -218,7 +221,7 @@ class D3DFB : public BaseWinFB { DECLARE_CLASS(D3DFB, BaseWinFB) public: - D3DFB (int width, int height, bool fullscreen); + D3DFB (UINT adapter, int width, int height, bool fullscreen); ~D3DFB (); bool IsValid (); @@ -254,6 +257,9 @@ public: void FlatFill (int left, int top, int right, int bottom, FTexture *src, bool local_origin); void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 realcolor); void DrawPixel(int x, int y, int palcolor, uint32 rgbcolor); + void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, + angle_t rotation, FDynamicColormap *colormap, int lightlevel); bool WipeStartScreen(int type); void WipeEndScreen(); bool WipeDo(int ticks); @@ -275,7 +281,7 @@ private: }; #define D3DFVF_FBVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1) - struct BufferedQuad + struct BufferedTris { union { @@ -290,6 +296,8 @@ private: }; D3DPal *Palette; IDirect3DTexture9 *Texture; + WORD NumVerts; // Number of _unique_ vertices used by this set. + WORD NumTris; // Number of triangles used by this set. }; enum @@ -352,12 +360,12 @@ private: void DrawPackedTextures(int packnum); void DrawLetterbox(); void Draw3DPart(bool copy3d); - bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedQuad &quad); + bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad); static D3DBLEND GetStyleAlpha(int type); static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1); void DoWindowedGamma(); void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color); - void CheckQuadBatch(); + void CheckQuadBatch(int numtris=2, int numverts=4); void BeginQuadBatch(); void EndQuadBatch(); void BeginLineBatch(); @@ -414,7 +422,9 @@ private: D3DPal *Palettes; D3DTex *Textures; PackingTexture *Packs; + HRESULT LastHR; + UINT Adapter; IDirect3DDevice9 *D3DDevice; IDirect3DTexture9 *FBTexture; IDirect3DTexture9 *TempRenderTexture, *RenderTexture[2]; @@ -428,7 +438,7 @@ private: FBVERTEX *VertexData; IDirect3DIndexBuffer9 *IndexBuffer; WORD *IndexData; - BufferedQuad *QuadExtra; + BufferedTris *QuadExtra; int VertexPos; int IndexPos; int QuadBatchPos; @@ -494,7 +504,7 @@ enum #define LOG4(x,y,z,a,b) do { if (dbg) { fprintf (dbg, x, y, z, a, b); fflush (dbg); } } while(0) #define LOG5(x,y,z,a,b,c) do { if (dbg) { fprintf (dbg, x, y, z, a, b, c); fflush (dbg); } } while(0) FILE *dbg; -#elif _DEBUG +#elif _DEBUG && 0 #define STARTLOG #define STOPLOG #define LOG(x) { OutputDebugString(x); } diff --git a/src/win32/win32video.cpp b/src/win32/win32video.cpp index 2753c0aeb..e9df043d0 100644 --- a/src/win32/win32video.cpp +++ b/src/win32/win32video.cpp @@ -38,6 +38,7 @@ #define DIRECTDRAW_VERSION 0x0300 #define DIRECT3D_VERSION 0x0900 +#define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN #include #include @@ -51,6 +52,7 @@ #include #include #include +#include #define USE_WINDOWS_DWORD #include "doomtype.h" @@ -65,6 +67,7 @@ #include "doomerrors.h" #include "m_argv.h" #include "r_defs.h" +#include "v_text.h" #include "win32iface.h" @@ -107,12 +110,14 @@ IDirect3D9 *D3D; IDirect3DDevice9 *D3Device; CVAR (Bool, vid_forceddraw, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR (Int, vid_adapter, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // CODE -------------------------------------------------------------------- Win32Video::Win32Video (int parm) : m_Modes (NULL), - m_IsFullscreen (false) + m_IsFullscreen (false), + m_Adapter (D3DADAPTER_DEFAULT) { I_SetWndProc(); if (!InitD3D9()) @@ -168,9 +173,13 @@ bool Win32Video::InitD3D9 () goto closelib; } + // Select adapter. + m_Adapter = (vid_adapter < 1 || (UINT)vid_adapter > D3D->GetAdapterCount()) + ? D3DADAPTER_DEFAULT : (UINT)vid_adapter - 1u; + // Check that we have at least PS 1.4 available. D3DCAPS9 devcaps; - if (FAILED(D3D->GetDeviceCaps (D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &devcaps))) + if (FAILED(D3D->GetDeviceCaps (m_Adapter, D3DDEVTYPE_HAL, &devcaps))) { goto d3drelease; } @@ -185,8 +194,8 @@ bool Win32Video::InitD3D9 () // Enumerate available display modes. FreeModes (); - AddD3DModes (D3DFMT_X8R8G8B8); - AddD3DModes (D3DFMT_R5G6B5); + AddD3DModes (m_Adapter, D3DFMT_X8R8G8B8); + AddD3DModes (m_Adapter, D3DFMT_R5G6B5); if (Args->CheckParm ("-2")) { // Force all modes to be pixel-doubled. ScaleModes (1); @@ -340,6 +349,62 @@ void Win32Video::BlankForGDI () static_cast (screen)->Blank (); } +//========================================================================== +// +// Win32Video :: DumpAdapters +// +// Dumps the list of display adapters to the console. Only meaningful for +// Direct3D. +// +//========================================================================== + +void Win32Video::DumpAdapters() +{ + if (D3D == NULL) + { + Printf("Multi-monitor support requires Direct3D.\n"); + return; + } + + UINT num_adapters = D3D->GetAdapterCount(); + + for (UINT i = 0; i < num_adapters; ++i) + { + D3DADAPTER_IDENTIFIER9 ai; + char moreinfo[64] = ""; + + if (FAILED(D3D->GetAdapterIdentifier(i, 0, &ai))) + { + continue; + } + // Strip trailing whitespace from adapter description. + for (char *p = ai.Description + strlen(ai.Description) - 1; + p >= ai.Description && isspace(*p); + --p) + { + *p = '\0'; + } + // Get monitor info from GDI for more details. Windows 95 apparently does not have + // the GetMonitorInfo function. I will leave this like this for now instead of using + // GetProcAddress to see if it's still worth worrying about Windows 95 support. + // (e.g. Will anybody complain that they can't run ZDoom anymore?) + HMONITOR hm = D3D->GetAdapterMonitor(i); + MONITORINFOEX mi; + mi.cbSize = sizeof(mi); + if (GetMonitorInfo(hm, &mi)) + { + mysnprintf(moreinfo, countof(moreinfo), " [%ldx%ld @ (%ld,%ld)]%s", + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.dwFlags & MONITORINFOF_PRIMARY ? " (Primary)" : ""); + } + Printf("%s%u. %s%s\n", + i == m_Adapter ? TEXTCOLOR_BOLD : "", + i + 1, ai.Description, moreinfo); + } +} + // Mode enumeration -------------------------------------------------------- HRESULT WINAPI Win32Video::EnumDDModesCB (LPDDSURFACEDESC desc, void *data) @@ -348,15 +413,15 @@ HRESULT WINAPI Win32Video::EnumDDModesCB (LPDDSURFACEDESC desc, void *data) return DDENUMRET_OK; } -void Win32Video::AddD3DModes (D3DFORMAT format) +void Win32Video::AddD3DModes (UINT adapter, D3DFORMAT format) { UINT modecount, i; D3DDISPLAYMODE mode; - modecount = D3D->GetAdapterModeCount (D3DADAPTER_DEFAULT, format); + modecount = D3D->GetAdapterModeCount (adapter, format); for (i = 0; i < modecount; ++i) { - if (D3D_OK == D3D->EnumAdapterModes (D3DADAPTER_DEFAULT, format, i, &mode)) + if (D3D_OK == D3D->EnumAdapterModes (adapter, format, i, &mode)) { AddMode (mode.Width, mode.Height, 8, mode.Height, 0); } @@ -571,7 +636,7 @@ DFrameBuffer *Win32Video::CreateFrameBuffer (int width, int height, bool fullscr if (D3D != NULL) { - fb = new D3DFB (width, height, fullscreen); + fb = new D3DFB (m_Adapter, width, height, fullscreen); } else { @@ -596,6 +661,7 @@ DFrameBuffer *Win32Video::CreateFrameBuffer (int width, int height, bool fullscr { hr = fb->GetHR (); } + fb->ObjectFlags |= OF_YesReallyDelete; delete fb; LOG1 ("fb is bad: %08lx\n", hr); diff --git a/src/win32/zdoom.rc b/src/win32/zdoom.rc index 7c953edac..b23fe14eb 100644 --- a/src/win32/zdoom.rc +++ b/src/win32/zdoom.rc @@ -72,7 +72,7 @@ BEGIN " VALUE ""FileDescription"", ""ZDoom""\r\n" " VALUE ""FileVersion"", RC_FILEVERSION2\r\n" " VALUE ""InternalName"", ""ZDoom""\r\n" - " VALUE ""LegalCopyright"", ""Copyright © 1993-1996 id Software, 1998-2007 Randy Heit""\r\n" + " VALUE ""LegalCopyright"", ""Copyright \u00A9 1993-1996 id Software, 1998-2010 Randy Heit, 2002-2010 Christoph Oelckers, et al.""\r\n" " VALUE ""LegalTrademarks"", ""Doom® is a Registered Trademark of id Software, Inc.""\r\n" " VALUE ""OriginalFilename"", ""zdoom.exe""\r\n" " VALUE ""ProductName"", ""ZDoom""\r\n" @@ -444,13 +444,6 @@ BEGIN END -///////////////////////////////////////////////////////////////////////////// -// -// Cursor -// - -IDC_INVISIBLECURSOR CURSOR "cursor1.cur" - ///////////////////////////////////////////////////////////////////////////// // // Bitmap diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 7427112c3..ab39d3b59 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -61,7 +61,7 @@ ACTOR Actor native //: Thinker action native A_XScream(); action native A_Look(); action native A_Chase(state melee = "*", state missile = "none", int flags = 0); - action native A_FaceTarget(); + action native A_FaceTarget(float max_turn = 0); action native A_PosAttack(); action native A_Scream(); action native A_SPosAttack(); @@ -184,14 +184,16 @@ ACTOR Actor native //: Thinker 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, bool aimfacing = false); - action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, int aim = 0, float maxdiff = 0, class pufftype = "BulletPuff"); + action native A_CustomBulletAttack(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0); + action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, int aim = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0); action native A_JumpIfHealthLower(int health, state label); action native A_JumpIfCloser(float distance, state label); + action native A_JumpIfTracerCloser(float distance, state label); + action native A_JumpIfMasterCloser(float distance, state label); action native A_JumpIfInventory(class itemtype, int itemamount, state label); action native A_JumpIfArmorType(name Type, state label, int amount = 1); action native A_GiveInventory(class itemtype, int amount = 0); - action native A_TakeInventory(class itemtype, int amount = 0); + action native A_TakeInventory(class itemtype, int amount = 0, int flags = 0); action native A_SpawnItem(class itemtype = "Unknown", float distance = 0, float zheight = 0, bool useammo = true, bool transfer_translation = false); action native A_SpawnItemEx(class itemtype, float xofs = 0, float yofs = 0, float zofs = 0, float xvel = 0, float yvel = 0, float zvel = 0, float angle = 0, int flags = 0, int failchance = 0); action native A_Print(string whattoprint, float time = 0, name fontname = ""); @@ -201,6 +203,7 @@ ACTOR Actor native //: Thinker 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_SpawnDebris(class spawntype, bool transfer_translation = false, float mult_h = 1, float mult_v = 1); action native A_CheckSight(state label); action native A_ExtChase(bool usemelee, bool usemissile, bool playactive = true, bool nightmarefast = false); @@ -221,13 +224,14 @@ ACTOR Actor native //: Thinker 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_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); action native A_Recoil(float xyvel); action native A_JumpIfInTargetInventory(class itemtype, int amount, state label); action native A_GiveToTarget(class itemtype, int amount = 0); - action native A_TakeFromTarget(class itemtype, int amount = 0); + action native A_TakeFromTarget(class itemtype, int amount = 0, int flags = 0); action native A_CountdownArg(int argnum, state targstate = ""); action native A_CustomMeleeAttack(int damage = 0, sound meleesound = "", sound misssound = "", name damagetype = "none", bool bleed = true); action native A_CustomComboAttack(class missiletype, float spawnheight, int damage, sound meleesound = "", name damagetype = "none", bool bleed = true); @@ -242,8 +246,8 @@ ACTOR Actor native //: Thinker action native A_DeQueueCorpse(); action native A_LookEx(int flags = 0, float minseedist = 0, float maxseedist = 0, float maxheardist = 0, float fov = 0, state label = ""); action native A_ClearTarget(); - action native A_JumpIfTargetInLOS (state label, float fov = 0, bool projectiletarget = false); - action native A_JumpIfInTargetLOS (state label, float fov = 0, bool projectiletarget = false); + action native A_JumpIfTargetInLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); + action native A_JumpIfInTargetLOS (state label, float/*angle*/ fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_DamageMaster(int amount, name damagetype = "none"); action native A_DamageChildren(int amount, name damagetype = "none"); action native A_DamageSiblings(int amount, name damagetype = "none"); @@ -282,7 +286,7 @@ ACTOR Actor native //: Thinker Stop GenericFreezeDeath: // Generic freeze death frames. Woo! - "----" A 5 A_GenericFreezeDeath + "####" "#" 5 A_GenericFreezeDeath "----" A 1 A_FreezeDeathChunks Wait GenericCrush: diff --git a/wadsrc/static/actors/chex/chexweapons.txt b/wadsrc/static/actors/chex/chexweapons.txt index 4fa7eec56..97043f2ca 100644 --- a/wadsrc/static/actors/chex/chexweapons.txt +++ b/wadsrc/static/actors/chex/chexweapons.txt @@ -4,6 +4,7 @@ actor Bootspoon : Fist { game Chex obituary "$OB_MPSPOON" + Tag "$TAG_SPOON" } actor SuperBootspork : Chainsaw 2005 @@ -11,6 +12,7 @@ actor SuperBootspork : Chainsaw 2005 game Chex obituary "$OB_MPBOOTSPORK" Inventory.PickupMessage "$GOTSUPERBOOTSPORK" + Tag "$TAG_SPORK" } actor MiniZorcher : Pistol @@ -18,6 +20,7 @@ actor MiniZorcher : Pistol game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTMINIZORCHER" + Tag "$TAG_MINIZORCHER" states { Spawn: @@ -30,6 +33,7 @@ actor LargeZorcher : Shotgun 2001 game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTLARGEZORCHER" + Tag "$TAG_LARGEZORCHER" } actor SuperLargeZorcher : SuperShotgun 82 @@ -37,6 +41,7 @@ actor SuperLargeZorcher : SuperShotgun 82 game Chex obituary "$OB_MPMEGAZORCH" inventory.pickupmessage "$GOTSUPERLARGEZORCHER" + Tag "$TAG_SUPERLARGEZORCHER" } actor RapidZorcher : Chaingun 2002 @@ -44,6 +49,7 @@ actor RapidZorcher : Chaingun 2002 game Chex obituary "$OB_MPRAPIDZORCH" inventory.pickupmessage "$GOTRAPIDZORCHER" + Tag "$TAG_RAPIDZORCHER" } actor ZorchPropulsor : RocketLauncher 2003 @@ -51,6 +57,7 @@ actor ZorchPropulsor : RocketLauncher 2003 game Chex obituary "" inventory.pickupmessage "$GOTZORCHPROPULSOR" + Tag "$TAG_ZORCHPROPULSOR" States { Fire: @@ -75,6 +82,7 @@ actor PhasingZorcher : PlasmaRifle 2004 game Chex obituary "" inventory.pickupmessage "$GOTPHASINGZORCHER" + Tag "$TAG_PHASINGZORCHER" states { Fire: @@ -104,6 +112,7 @@ actor LAZDevice : BFG9000 2006 game Chex obituary "" inventory.pickupmessage "$GOTLAZDEVICE" + Tag "$TAG_LAZDEVICE" states { Fire: diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 8c60430ed..baa4b1808 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -1,10 +1,31 @@ +// Flags for A_Saw +const int SF_NORANDOM = 1; +const int SF_RANDOMLIGHTMISS = 2; +const int SF_RANDOMLIGHTHIT = 4; +const int SF_RANDOMLIGHTBOTH = 6; +const int SF_NOUSEAMMOMISS = 8; +const int SF_NOUSEAMMO = 16; + // Flags for A_CustomMissile const int CMF_AIMOFFSET = 1; const int CMF_AIMDIRECTION = 2; const int CMF_TRACKOWNER = 4; const int CMF_CHECKTARGETDEAD = 8; +// Flags for A_CustomBulletAttack +const int CBAF_AIMFACING = 1; +const int CBAF_NORANDOM = 2; +const int CBAF_EXPLICITANGLE = 4; +const int CBAF_NOPITCH = 8; + +// 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; + // Flags for A_SpawnItemEx const int SXF_TRANSFERTRANSLATION=1; const int SXF_ABSOLUTEPOSITION=2; @@ -38,6 +59,15 @@ 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; + // Flags for A_ChangeVelocity const int CVF_RELATIVE = 1; const int CVF_REPLACE = 2; @@ -66,6 +96,7 @@ 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; // Flags for A_Mushroom const int MSF_Standard = 0; @@ -79,6 +110,16 @@ const int BF_AFFECTBOSSES = 4; // Flags for A_SeekerMissile const int SMF_LOOK = 1; +const int SMF_PRECISE = 2; + +// Flags for A_CustomPunch +const int CPF_USEAMMO = 1; +const int CPF_DAGGER = 2; +const int CPF_PULLIN = 4; + +// Flags for A_Teleport +const int TF_TELEFRAG = 1; +const int TF_RANDOMDECIDE = 2; // Activation flags enum @@ -104,6 +145,9 @@ 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 { diff --git a/wadsrc/static/actors/doom/doomweapons.txt b/wadsrc/static/actors/doom/doomweapons.txt index 076559c0e..b748f8611 100644 --- a/wadsrc/static/actors/doom/doomweapons.txt +++ b/wadsrc/static/actors/doom/doomweapons.txt @@ -21,6 +21,7 @@ ACTOR Fist : Weapon Weapon.SelectionOrder 3700 Weapon.Kickback 100 Obituary "$OB_MPFIST" + Tag "$FIST" +WEAPON.WIMPY_WEAPON +WEAPON.MELEEWEAPON States @@ -61,6 +62,7 @@ ACTOR Pistol : DoomWeapon 5010 Obituary "$OB_MPPISTOL" +WEAPON.WIMPY_WEAPON Inventory.Pickupmessage "$PICKUP_PISTOL_DROPPED" + Tag "$TAG_PISTOL" States { Ready: @@ -105,6 +107,7 @@ ACTOR Chainsaw : Weapon 2005 Weapon.ReadySound "weapons/sawidle" Inventory.PickupMessage "$GOTCHAINSAW" Obituary "$OB_MPCHAINSAW" + Tag "$TAG_CHAINSAW" +WEAPON.MELEEWEAPON States { @@ -144,6 +147,7 @@ ACTOR Shotgun : DoomWeapon 2001 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN" Obituary "$OB_MPSHOTGUN" + Tag "$TAG_SHOTGUN" States { Ready: @@ -190,6 +194,7 @@ ACTOR SuperShotgun : DoomWeapon 82 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN2" Obituary "$OB_MPSSHOTGUN" + Tag "$TAG_SUPERSHOTGUN" States { Ready: @@ -243,6 +248,7 @@ ACTOR Chaingun : DoomWeapon 2002 Weapon.AmmoType "Clip" Inventory.PickupMessage "$GOTCHAINGUN" Obituary "$OB_MPCHAINGUN" + Tag "$TAG_CHAINGUN" States { Ready: @@ -285,6 +291,7 @@ ACTOR RocketLauncher : DoomWeapon 2003 Weapon.AmmoType "RocketAmmo" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTLAUNCHER" + Tag "$TAG_ROCKETLAUNCHER" States { Ready: @@ -406,6 +413,7 @@ ACTOR PlasmaRifle : DoomWeapon 2004 Weapon.AmmoGive 40 Weapon.AmmoType "Cell" Inventory.PickupMessage "$GOTPLASMA" + Tag "$TAG_PLASMARIFLE" States { Ready: @@ -511,6 +519,7 @@ ACTOR BFG9000 : DoomWeapon 2006 Weapon.AmmoType "Cell" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTBFG9000" + Tag "$TAG_BFG9000" States { Ready: diff --git a/wadsrc/static/actors/heretic/hereticartifacts.txt b/wadsrc/static/actors/heretic/hereticartifacts.txt index 0d5ede1a6..67d3946b9 100644 --- a/wadsrc/static/actors/heretic/hereticartifacts.txt +++ b/wadsrc/static/actors/heretic/hereticartifacts.txt @@ -33,6 +33,7 @@ ACTOR ArtiInvisibility : PowerupGiver 75 Inventory.Icon ARTIINVS Powerup.Type Ghost Inventory.PickupMessage "$TXT_ARTIINVISIBILITY" + Tag "$TAG_ARTIINVISIBILITY" States { Spawn: @@ -54,6 +55,7 @@ ACTOR ArtiTomeOfPower : PowerupGiver 86 native Inventory.Icon "ARTIPWBK" Powerup.Type Weaponlevel2 Inventory.PickupMessage "$TXT_ARTITOMEOFPOWER" + Tag "$TAG_ARTITOMEOFPOWER" States { Spawn: @@ -98,6 +100,7 @@ ACTOR ArtiTimeBomb : Inventory 34 native Inventory.Icon "ARTIFBMB" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIFIREBOMB" + Tag "$TAG_ARTIFIREBOMB" Inventory.DefMaxAmount States { diff --git a/wadsrc/static/actors/heretic/heretickeys.txt b/wadsrc/static/actors/heretic/heretickeys.txt index 774629866..71beab253 100644 --- a/wadsrc/static/actors/heretic/heretickeys.txt +++ b/wadsrc/static/actors/heretic/heretickeys.txt @@ -13,6 +13,7 @@ ACTOR KeyGreen : HereticKey 73 Game Heretic SpawnID 86 Inventory.PickupMessage "$TXT_GOTGREENKEY" + Inventory.Icon "GKEYICON" States { Spawn: @@ -28,6 +29,7 @@ ACTOR KeyBlue : HereticKey 79 Game Heretic SpawnID 85 Inventory.PickupMessage "$TXT_GOTBLUEKEY" + Inventory.Icon "BKEYICON" States { Spawn: @@ -43,6 +45,7 @@ ACTOR KeyYellow : HereticKey 80 Game Heretic SpawnID 87 Inventory.PickupMessage "$TXT_GOTYELLOWKEY" + Inventory.Icon "YKEYICON" States { Spawn: diff --git a/wadsrc/static/actors/heretic/hereticmisc.txt b/wadsrc/static/actors/heretic/hereticmisc.txt index b41a2d4f7..b6644e444 100644 --- a/wadsrc/static/actors/heretic/hereticmisc.txt +++ b/wadsrc/static/actors/heretic/hereticmisc.txt @@ -204,7 +204,7 @@ ACTOR VolcanoBlast States { Spawn: - VFBL A 4 BRIGHT A_SpawnItemEx("Puffy", random2[BeastPuff]()*0.015625, random2[BeastPuff]()*0.015625, random2[BeastPuff]()*0.015625, + VFBL AB 4 BRIGHT A_SpawnItemEx("Puffy", random2[BeastPuff]()*0.015625, random2[BeastPuff]()*0.015625, random2[BeastPuff]()*0.015625, 0,0,0,0,SXF_ABSOLUTEPOSITION, 64) Loop diff --git a/wadsrc/static/actors/heretic/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 993da1485..7e2d0a2ee 100644 --- a/wadsrc/static/actors/heretic/hereticweaps.txt +++ b/wadsrc/static/actors/heretic/hereticweaps.txt @@ -15,6 +15,8 @@ ACTOR Staff : HereticWeapon +WIMPY_WEAPON +MELEEWEAPON Weapon.sisterweapon "StaffPowered" + Obituary "$OB_MPSTAFF" + Tag "$TAG_STAFF" action native A_StaffAttack (int damage, class puff); @@ -45,6 +47,8 @@ ACTOR StaffPowered : Staff +WEAPON.POWERED_UP +WEAPON.READYSNDHALF +WEAPON.STAFF2_KICKBACK + Obituary "$OB_MPPSTAFF" + Tag "$TAG_STAFFP" States { Ready: @@ -116,6 +120,8 @@ ACTOR GoldWand : HereticWeapon Weapon.AmmoType "GoldWandAmmo" Weapon.SisterWeapon "GoldWandPowered" Weapon.YAdjust 5 + Obituary "$OB_MPGOLDWAND" + Tag "$TAG_GOLDWAND" action native A_FireGoldWandPL1 (); @@ -145,6 +151,8 @@ ACTOR GoldWandPowered : GoldWand +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "GoldWand" + Obituary "$OB_MPPGOLDWAND" + Tag "$TAG_GOLDWANDP" action native A_FireGoldWandPL2 (); @@ -173,6 +181,7 @@ ACTOR GoldWandFX1 Projectile RenderStyle Add DeathSound "weapons/wandhit" + Obituary "$OB_MPPGOLDWAND" States { Spawn: @@ -209,7 +218,8 @@ ACTOR GoldWandPuff1 +NOGRAVITY +PUFFONACTORS RenderStyle Add - States { + States + { Spawn: PUF2 ABCDE 3 BRIGHT Stop @@ -243,7 +253,8 @@ ACTOR Crossbow : HereticWeapon 2001 Weapon.AmmoType "CrossbowAmmo" Weapon.SisterWeapon "CrossbowPowered" Weapon.YAdjust 15 - Inventory.PickupMessage "$TxT_WPNCROSSBOW" + Inventory.PickupMessage "$TXT_WPNCROSSBOW" + Tag "$TAG_CROSSBOW" action native A_FireCrossbowPL1 (); @@ -277,6 +288,7 @@ ACTOR CrossbowPowered : Crossbow +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "Crossbow" + Tag "$TAG_CROSSBOWP" action native A_FireCrossbowPL2(); @@ -310,6 +322,7 @@ ACTOR CrossbowFX1 RenderStyle Add SeeSound "weapons/bowshoot" DeathSound "weapons/bowhit" + Obituary "$OB_MPCROSSBOW" States { Spawn: @@ -330,6 +343,7 @@ ACTOR CrossbowFX2 : CrossbowFX1 SpawnID 148 Speed 32 Damage 6 + Obituary "$OB_MPPCROSSBOW" States { Spawn: @@ -394,6 +408,8 @@ ACTOR Gauntlets : Weapon 2005 Weapon.UpSound "weapons/gauntletsactivate" Weapon.SisterWeapon "GauntletsPowered" Inventory.PickupMessage "$TXT_WPNGAUNTLETS" + Tag "$TAG_GAUNTLETS" + Obituary "$OB_MPGAUNTLETS" action native A_GauntletAttack (int power); @@ -427,6 +443,8 @@ ACTOR GauntletsPowered : Gauntlets { Game Heretic +POWERED_UP + Tag "$TAG_GAUNTLETSP" + Obituary "$OB_MPPGAUNTLETS" Weapon.SisterWeapon "Gauntlets" States { @@ -469,7 +487,7 @@ ACTOR GauntletPuff1 } } -// Gauntlett puff 2 --------------------------------------------------------- +// Gauntlet puff 2 --------------------------------------------------------- ACTOR GauntletPuff2 : GauntletPuff1 { @@ -494,7 +512,8 @@ ACTOR Mace : HereticWeapon Weapon.YAdjust 15 Weapon.AmmoType "MaceAmmo" Weapon.SisterWeapon "MacePowered" - Inventory.PickupMessage "$TxT_WPNMACE" + Inventory.PickupMessage "$TXT_WPNMACE" + Tag "$TAG_MACE" action native A_FireMacePL1(); @@ -529,6 +548,7 @@ ACTOR MacePowered : Mace Weapon.AmmoUse 5 Weapon.AmmoGive 0 Weapon.SisterWeapon "Mace" + Tag "$TAG_MACEP" action native A_FireMacePL2(); @@ -558,6 +578,7 @@ ACTOR MaceFX1 +THRUGHOST BounceType "HereticCompat" SeeSound "weapons/maceshoot" + Obituary "$OB_MPMACE" action native A_MacePL1Check(); action native A_MaceBallImpact(); @@ -636,6 +657,7 @@ ACTOR MaceFX4 native -NOTELEPORT BounceType "HereticCompat" SeeSound "" + Obituary "$OB_MPPMACE" action native A_DeathBallImpact(); @@ -682,7 +704,9 @@ ACTOR Blaster : HereticWeapon 53 Weapon.YAdjust 15 Weapon.AmmoType "BlasterAmmo" Weapon.SisterWeapon "BlasterPowered" - Inventory.PickupMessage "$TxT_WPNBLASTER" + Inventory.PickupMessage "$TXT_WPNBLASTER" + Tag "$TAG_BLASTER" + Obituary "$OB_MPBLASTER" action native A_FireBlasterPL1(); @@ -740,6 +764,8 @@ ACTOR BlasterFX1 : FastProjectile native SeeSound "weapons/blastershoot" DeathSound "weapons/blasterhit" +SPAWNSOUNDSOURCE + Obituary "$OB_MPPBLASTER" + Tag "$TAG_BLASTERP" action native A_SpawnRippers(); @@ -787,6 +813,7 @@ ACTOR Ripper native Projectile +RIPPER DeathSound "weapons/blasterpowhit" + Obituary "$OB_MPPBLASTER" States { Spawn: @@ -832,7 +859,8 @@ ACTOR SkullRod : HereticWeapon 2004 Weapon.YAdjust 15 Weapon.AmmoType1 "SkullRodAmmo" Weapon.SisterWeapon "SkullRodPowered" - Inventory.PickupMessage "$TxT_WPNSKULLROD" + Inventory.PickupMessage "$TXT_WPNSKULLROD" + Tag "$TAG_SKULLROD" action native A_FireSkullRodPL1(); @@ -864,6 +892,7 @@ ACTOR SkullRodPowered : SkullRod Weapon.AmmoUse1 5 Weapon.AmmoGive1 0 Weapon.SisterWeapon "SkullRod" + Tag "$TAG_SKULLRODP" action native A_FireSkullRodPL2(); @@ -899,6 +928,7 @@ ACTOR HornRodFX1 RenderStyle Add SeeSound "weapons/hornrodshoot" DeathSound "weapons/hornrodhit" + Obituary "$OB_MPSKULLROD" States { Spawn: @@ -926,6 +956,7 @@ ACTOR HornRodFX2 native RenderStyle Add SeeSound "weapons/hornrodpowshoot" DeathSound "weapons/hornrodpowhit" + Obituary "$OB_MPPSKULLROD" action native A_AddPlayerRain(); action native A_HideInCeiling(); @@ -963,6 +994,7 @@ ACTOR RainPillar native -ACTIVATEPCROSS -ACTIVATEIMPACT RenderStyle Add + Obituary "$OB_MPPSKULLROD" action native A_RainImpact(); @@ -1003,7 +1035,8 @@ ACTOR PhoenixRod : Weapon 2003 native Weapon.AmmoGive 2 Weapon.AmmoType "PhoenixRodAmmo" Weapon.Sisterweapon "PhoenixRodPowered" - Inventory.PickupMessage "$TxT_WPNPHOENIxROD" + Inventory.PickupMessage "$TXT_WPNPHOENIxROD" + Tag "$TAG_PHOENIxROD" action native A_FirePhoenixPL1(); @@ -1037,6 +1070,7 @@ ACTOR PhoenixRodPowered : PhoenixRod native +WEAPON.MELEEWEAPON Weapon.SisterWeapon "PhoenixRod" Weapon.AmmoGive 0 + Tag "$TAG_PHOENIxRODP" action native A_InitPhoenixPL2(); action native A_FirePhoenixPL2(); @@ -1071,6 +1105,7 @@ ACTOR PhoenixFX1 native +SPECIALFIREDAMAGE SeeSound "weapons/phoenixshoot" DeathSound "weapons/phoenixhit" + Obituary "$OB_MPPHOENIXROD" action native A_PhoenixPuff(); @@ -1116,6 +1151,7 @@ ACTOR PhoenixFX2 native DamageType Fire Projectile RenderStyle Add + Obituary "$OB_MPPPHOENIXROD" action native A_FlameEnd(); action native A_FloatPuff(); diff --git a/wadsrc/static/actors/hexen/blastradius.txt b/wadsrc/static/actors/hexen/blastradius.txt index 7e4259cf8..ad98d7ecc 100644 --- a/wadsrc/static/actors/hexen/blastradius.txt +++ b/wadsrc/static/actors/hexen/blastradius.txt @@ -10,6 +10,7 @@ ACTOR ArtiBlastRadius : CustomInventory 10110 Inventory.Icon "ARTIBLST" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBLASTRADIUS" + Tag "$TAG_ARTIBLASTRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/boostarmor.txt b/wadsrc/static/actors/hexen/boostarmor.txt index ca3b8384c..607c8d66a 100644 --- a/wadsrc/static/actors/hexen/boostarmor.txt +++ b/wadsrc/static/actors/hexen/boostarmor.txt @@ -13,6 +13,7 @@ ACTOR ArtiBoostArmor : Inventory 8041 native Inventory.Icon "ARTIBRAC" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTARMOR" + Tag "$TAG_ARTIBOOSTARMOR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/clericflame.txt b/wadsrc/static/actors/hexen/clericflame.txt index 29312ca79..5fb4175fd 100644 --- a/wadsrc/static/actors/hexen/clericflame.txt +++ b/wadsrc/static/actors/hexen/clericflame.txt @@ -12,6 +12,7 @@ ACTOR CWeapFlame : ClericWeapon 8009 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C3" + Tag "$TAG_CWEAPFLAME" action native A_CFlameAttack(); @@ -126,6 +127,7 @@ ACTOR CircleFlame -ACTIVATEPCROSS RenderStyle Add DeathSound "ClericFlameCircle" + Obituary "$OB_MPCWEAPFLAME" action native A_CFlameRotate(); @@ -166,6 +168,7 @@ ACTOR CFlameMissile : FastProjectile native DamageType "Fire" +INVISIBLE RenderStyle Add + Obituary "$OB_MPCWEAPFLAME" action native A_CFlamePuff(); action native A_CFlameMissile(); diff --git a/wadsrc/static/actors/hexen/clericholy.txt b/wadsrc/static/actors/hexen/clericholy.txt index 0c441726d..15673ab1b 100644 --- a/wadsrc/static/actors/hexen/clericholy.txt +++ b/wadsrc/static/actors/hexen/clericholy.txt @@ -84,6 +84,7 @@ ACTOR CWeapWraithverge : ClericWeapon native Weapon.AmmoType1 "Mana1" Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C4" + Tag "$TAG_CWEAPWRAITHVERGE" Inventory.PickupSound "WeaponBuild" action native A_CHolyAttack(); @@ -187,6 +188,7 @@ ACTOR HolySpirit native RenderStyle Translucent Alpha 0.4 DeathSound "SpiritDie" + Obituary "$OB_MPCWEAPWRAITHVERGE" action native A_CHolySeek(); action native A_CHolyCheckScream(); diff --git a/wadsrc/static/actors/hexen/clericmace.txt b/wadsrc/static/actors/hexen/clericmace.txt index ae9d3b84a..14ef4b00f 100644 --- a/wadsrc/static/actors/hexen/clericmace.txt +++ b/wadsrc/static/actors/hexen/clericmace.txt @@ -8,6 +8,8 @@ ACTOR CWeapMace : ClericWeapon Weapon.KickBack 150 Weapon.YAdjust -8 +BLOODSPLATTER + Obituary "$OB_MPCWEAPMACE" + Tag "$TAG_CWEAPMACE" action native A_CMaceAttack(); diff --git a/wadsrc/static/actors/hexen/clericstaff.txt b/wadsrc/static/actors/hexen/clericstaff.txt index 3b9cd1699..e55674fa5 100644 --- a/wadsrc/static/actors/hexen/clericstaff.txt +++ b/wadsrc/static/actors/hexen/clericstaff.txt @@ -12,6 +12,8 @@ ACTOR CWeapStaff : ClericWeapon 10 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_C2" + Obituary "$OB_MPCWEAPSTAFFM" + Tag "$TAG_CWEAPSTAFF" action native A_CStaffInitBlink(); action native A_CStaffCheckBlink(); @@ -65,6 +67,7 @@ ACTOR CStaffMissile native RenderStyle Add Projectile DeathSound "ClericCStaffExplode" + Obituary "$OB_MPCWEAPSTAFFR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/fighteraxe.txt b/wadsrc/static/actors/hexen/fighteraxe.txt index 78cbbf0a3..502de5030 100644 --- a/wadsrc/static/actors/hexen/fighteraxe.txt +++ b/wadsrc/static/actors/hexen/fighteraxe.txt @@ -13,6 +13,8 @@ ACTOR FWeapAxe : FighterWeapon 8010 native Weapon.YAdjust -12 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_F2" + Obituary "$OB_MPFWEAPAXE" + Tag "$TAG_FWEAPAXE" action native A_FAxeCheckUp(); action native A_FAxeCheckReady(); diff --git a/wadsrc/static/actors/hexen/fighterfist.txt b/wadsrc/static/actors/hexen/fighterfist.txt index 6693b79bf..0122d7f74 100644 --- a/wadsrc/static/actors/hexen/fighterfist.txt +++ b/wadsrc/static/actors/hexen/fighterfist.txt @@ -8,6 +8,8 @@ ACTOR FWeapFist : FighterWeapon Weapon.SelectionOrder 3400 +WEAPON.MELEEWEAPON Weapon.KickBack 150 + Obituary "$OB_MPFWEAPFIST" + Tag "$TAG_FWEAPFIST" action native A_FPunchAttack(); diff --git a/wadsrc/static/actors/hexen/fighterhammer.txt b/wadsrc/static/actors/hexen/fighterhammer.txt index 63585b1cc..5c7d6dc25 100644 --- a/wadsrc/static/actors/hexen/fighterhammer.txt +++ b/wadsrc/static/actors/hexen/fighterhammer.txt @@ -14,6 +14,8 @@ ACTOR FWeapHammer : FighterWeapon 123 Weapon.YAdjust -10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F3" + Obituary "$OB_MPFWEAPHAMMERM" + Tag "$TAG_FWEAPHAMMER" action native A_FHammerAttack(); action native A_FHammerThrow(); @@ -60,6 +62,7 @@ ACTOR HammerMissile DamageType "Fire" Projectile DeathSound "FighterHammerExplode" + Obituary "$OB_MPFWEAPHAMMERR" States { diff --git a/wadsrc/static/actors/hexen/fighterquietus.txt b/wadsrc/static/actors/hexen/fighterquietus.txt index 3bd5904f7..034914d23 100644 --- a/wadsrc/static/actors/hexen/fighterquietus.txt +++ b/wadsrc/static/actors/hexen/fighterquietus.txt @@ -86,6 +86,7 @@ ACTOR FWeapQuietus : FighterWeapon Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_FWEAPQUIETUS" action native A_FSwordAttack(); @@ -131,6 +132,7 @@ ACTOR FSwordMissile native +EXTREMEDEATH RenderStyle Add DeathSound "FighterSwordExplode" + Obituary "$OB_MPFWEAPQUIETUS" action native A_FSwordFlames(); diff --git a/wadsrc/static/actors/hexen/flame.txt b/wadsrc/static/actors/hexen/flame.txt index c3a222ea6..984c2d90c 100644 --- a/wadsrc/static/actors/hexen/flame.txt +++ b/wadsrc/static/actors/hexen/flame.txt @@ -10,10 +10,10 @@ ACTOR FlameSmallTemp 10500 { Spawn: FFSM AB 3 Bright - FFSM C 2 Bright A_CountdownArg(1) + FFSM C 2 Bright A_CountdownArg(0) FFSM C 2 Bright FFSM D 3 Bright - FFSM E 3 Bright A_CountdownArg(1) + FFSM E 3 Bright A_CountdownArg(0) Loop } } @@ -31,21 +31,21 @@ ACTOR FlameLargeTemp 10502 { Spawn: FFLG A 4 Bright - FFLG B 4 Bright A_CountdownArg(1) + FFLG B 4 Bright A_CountdownArg(0) FFLG C 4 Bright - FFLG D 4 Bright A_CountdownArg(1) + FFLG D 4 Bright A_CountdownArg(0) FFLG E 4 Bright - FFLG F 4 Bright A_CountdownArg(1) + FFLG F 4 Bright A_CountdownArg(0) FFLG G 4 Bright - FFLG H 4 Bright A_CountdownArg(1) + FFLG H 4 Bright A_CountdownArg(0) FFLG I 4 Bright - FFLG J 4 Bright A_CountdownArg(1) + FFLG J 4 Bright A_CountdownArg(0) FFLG K 4 Bright - FFLG L 4 Bright A_CountdownArg(1) + FFLG L 4 Bright A_CountdownArg(0) FFLG M 4 Bright - FFLG N 4 Bright A_CountdownArg(1) + FFLG N 4 Bright A_CountdownArg(0) FFLG O 4 Bright - FFLG P 4 Bright A_CountdownArg(1) + FFLG P 4 Bright A_CountdownArg(0) Goto Spawn+4 } } diff --git a/wadsrc/static/actors/hexen/flechette.txt b/wadsrc/static/actors/hexen/flechette.txt index a0ad52268..b2063a6ec 100644 --- a/wadsrc/static/actors/hexen/flechette.txt +++ b/wadsrc/static/actors/hexen/flechette.txt @@ -105,6 +105,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native Inventory.Icon "ARTIPSBG" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIPOISONBAG" + Tag "$TAG_ARTIPOISONBAG" States { Spawn: @@ -118,6 +119,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native ACTOR ArtiPoisonBag1 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB1" + Tag "$TAG_ARTIPOISONBAG1" } // Poison Bag 2 (The Mage's) ------------------------------------------------ @@ -125,6 +127,7 @@ ACTOR ArtiPoisonBag1 : ArtiPoisonBag native ACTOR ArtiPoisonBag2 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB2" + Tag "$TAG_ARTIPOISONBAG2" } // Poison Bag 3 (The Fighter's) --------------------------------------------- @@ -132,6 +135,7 @@ ACTOR ArtiPoisonBag2 : ArtiPoisonBag native ACTOR ArtiPoisonBag3 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB3" + Tag "$TAG_ARTIPOISONBAG3" } // Poison Cloud ------------------------------------------------------------- @@ -142,7 +146,7 @@ ACTOR PoisonCloud native Mass 0x7fffffff +NOBLOCKMAP +NOGRAVITY +DROPOFF +NODAMAGETHRUST - +DONTSPLASH +FOILINVUL +CANBLAST +BLOODLESSIMPACT + +DONTSPLASH +FOILINVUL +CANBLAST +BLOODLESSIMPACT +BLOCKEDBYSOLIDACTORS RenderStyle Translucent Alpha 0.6 DeathSound "PoisonShroomDeath" diff --git a/wadsrc/static/actors/hexen/healingradius.txt b/wadsrc/static/actors/hexen/healingradius.txt index 8c5b0e53a..e0556915b 100644 --- a/wadsrc/static/actors/hexen/healingradius.txt +++ b/wadsrc/static/actors/hexen/healingradius.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealingRadius : Inventory 10120 native Inventory.Icon "ARTIHRAD" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALINGRADIUS" + Tag "$TAG_ARTIHEALINGRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/hexenspecialdecs.txt b/wadsrc/static/actors/hexen/hexenspecialdecs.txt index 7d4a13a94..e36451e7f 100644 --- a/wadsrc/static/actors/hexen/hexenspecialdecs.txt +++ b/wadsrc/static/actors/hexen/hexenspecialdecs.txt @@ -216,6 +216,7 @@ ACTOR CorpseBloodDrip Gravity 0.125 +MISSILE +NOICEDEATH + DeathSound "Drip" States { Spawn: diff --git a/wadsrc/static/actors/hexen/magecone.txt b/wadsrc/static/actors/hexen/magecone.txt index 17c7ccf4f..b5dfdb546 100644 --- a/wadsrc/static/actors/hexen/magecone.txt +++ b/wadsrc/static/actors/hexen/magecone.txt @@ -13,6 +13,8 @@ ACTOR MWeapFrost : MageWeapon 53 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_M2" + Obituary "$OB_MPMWEAPFROST" + Tag "$TAG_MWEAPFROST" action native A_FireConePL1(); @@ -55,6 +57,7 @@ ACTOR FrostMissile native DamageType "Ice" Projectile DeathSound "MageShardsExplode" + Obituary "$OB_MPMWEAPFROST" action native A_ShedShard(); diff --git a/wadsrc/static/actors/hexen/magelightning.txt b/wadsrc/static/actors/hexen/magelightning.txt index 02658bcba..85402f223 100644 --- a/wadsrc/static/actors/hexen/magelightning.txt +++ b/wadsrc/static/actors/hexen/magelightning.txt @@ -12,6 +12,7 @@ ACTOR MWeapLightning : MageWeapon 8040 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_M3" + Tag "$TAG_MWEAPLIGHTNING" action native A_LightningReady(); action native A_MLightningAttack(class floor = "LightningFloor", class ceiling = "LightningCeiling"); @@ -56,6 +57,7 @@ ACTOR Lightning native MissileType "LightningZap" AttackSound "MageLightningZap" ActiveSound "MageLightningContinuous" + Obituary "$OB_MPMWEAPLIGHTNING" } ACTOR LightningCeiling : Lightning @@ -137,6 +139,7 @@ ACTOR LightningZap native -ACTIVATEIMPACT -ACTIVATEPCROSS RenderStyle Add + Obituary "$OB_MPMWEAPLIGHTNING" action native A_ZapMimic(); diff --git a/wadsrc/static/actors/hexen/magestaff.txt b/wadsrc/static/actors/hexen/magestaff.txt index e402e57c0..65c0ac893 100644 --- a/wadsrc/static/actors/hexen/magestaff.txt +++ b/wadsrc/static/actors/hexen/magestaff.txt @@ -86,6 +86,7 @@ ACTOR MWeapBloodscourge : MageWeapon native +Inventory.NoAttenPickupSound Inventory.PickupMessage "$TXT_WEAPON_M4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_MWEAPBLOODSCOURGE" action native A_MStaffAttack(); action native A_MStaffPalette(); @@ -128,6 +129,7 @@ ACTOR MageStaffFX2 native +SCREENSEEKER +EXTREMEDEATH DeathSound "MageStaffExplode" + Obituary "$OB_MPMWEAPBLOODSCOURGE" action native A_MStaffTrack(); diff --git a/wadsrc/static/actors/hexen/magewand.txt b/wadsrc/static/actors/hexen/magewand.txt index ebb8e2ca1..aaac9e5da 100644 --- a/wadsrc/static/actors/hexen/magewand.txt +++ b/wadsrc/static/actors/hexen/magewand.txt @@ -7,6 +7,7 @@ ACTOR MWeapWand : MageWeapon Weapon.SelectionOrder 3600 Weapon.KickBack 0 Weapon.YAdjust 9 + Tag "$TAG_MWEAPWAND" States { @@ -56,6 +57,7 @@ ACTOR MageWandMissile : FastProjectile +SPAWNSOUNDSOURCE MissileType "MageWandSmoke" SeeSound "MageWandFire" + Obituary "$OB_MPMWEAPWAND" States { Spawn: diff --git a/wadsrc/static/actors/hexen/mana.txt b/wadsrc/static/actors/hexen/mana.txt index 6130fe962..ee7ddd37c 100644 --- a/wadsrc/static/actors/hexen/mana.txt +++ b/wadsrc/static/actors/hexen/mana.txt @@ -81,6 +81,7 @@ ACTOR ArtiBoostMana : CustomInventory 8003 Inventory.Icon "ARTIBMAN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTMANA" + Tag "$TAG_ARTIBOOSTMANA" States { Spawn: diff --git a/wadsrc/static/actors/hexen/pig.txt b/wadsrc/static/actors/hexen/pig.txt index eaecd692e..45b649597 100644 --- a/wadsrc/static/actors/hexen/pig.txt +++ b/wadsrc/static/actors/hexen/pig.txt @@ -142,7 +142,7 @@ ACTOR Pig : MorphedMonster Death: PIGY E 4 A_Scream PIGY F 3 A_NoBlocking - PIGY G 4 + PIGY G 4 A_QueueCorpse PIGY H 3 PIGY IJK 4 PIGY L -1 diff --git a/wadsrc/static/actors/hexen/puzzleitems.txt b/wadsrc/static/actors/hexen/puzzleitems.txt index c3bdf29ba..dd15fc8fd 100644 --- a/wadsrc/static/actors/hexen/puzzleitems.txt +++ b/wadsrc/static/actors/hexen/puzzleitems.txt @@ -8,6 +8,7 @@ ACTOR PuzzSkull : PuzzleItem 9002 PuzzleItem.Number 0 Inventory.Icon ARTISKLL Inventory.PickupMessage "$TXT_ARTIPUZZSKULL" + Tag "$TAG_ARTIPUZZSKULL" States { Spawn: @@ -26,6 +27,7 @@ ACTOR PuzzGemBig : PuzzleItem 9003 PuzzleItem.Number 1 Inventory.Icon ARTIBGEM Inventory.PickupMessage "$TXT_ARTIPUZZGEMBIG" + Tag "$TAG_ARTIPUZZGEMBIG" States { Spawn: @@ -43,6 +45,7 @@ ACTOR PuzzGemRed : PuzzleItem 9004 PuzzleItem.Number 2 Inventory.Icon ARTIGEMR Inventory.PickupMessage "$TXT_ARTIPUZZGEMRED" + Tag "$TAG_ARTIPUZZGEMRED" States { Spawn: @@ -61,6 +64,7 @@ ACTOR PuzzGemGreen1 : PuzzleItem 9005 PuzzleItem.Number 3 Inventory.Icon ARTIGEMG Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN1" + Tag "$TAG_ARTIPUZZGEMGREEN1" States { Spawn: @@ -79,6 +83,7 @@ ACTOR PuzzGemGreen2 : PuzzleItem 9009 PuzzleItem.Number 4 Inventory.Icon ARTIGMG2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN2" + Tag "$TAG_ARTIPUZZGEMGREEN2" States { Spawn: @@ -97,6 +102,7 @@ ACTOR PuzzGemBlue1 : PuzzleItem 9006 PuzzleItem.Number 5 Inventory.Icon ARTIGEMB Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE1" + Tag "$TAG_ARTIPUZZGEMBLUE1" States { Spawn: @@ -115,6 +121,7 @@ ACTOR PuzzGemBlue2 : PuzzleItem 9010 PuzzleItem.Number 6 Inventory.Icon ARTIGMB2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE2" + Tag "$TAG_ARTIPUZZGEMBLUE2" States { Spawn: @@ -133,6 +140,7 @@ ACTOR PuzzBook1 : PuzzleItem 9007 PuzzleItem.Number 7 Inventory.Icon ARTIBOK1 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK1" + Tag "$TAG_ARTIPUZZBOOK1" States { Spawn: @@ -151,6 +159,7 @@ ACTOR PuzzBook2 : PuzzleItem 9008 PuzzleItem.Number 8 Inventory.Icon ARTIBOK2 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK2" + Tag "$TAG_ARTIPUZZBOOK2" States { Spawn: @@ -169,6 +178,7 @@ ACTOR PuzzFlameMask : PuzzleItem 9014 PuzzleItem.Number 9 Inventory.Icon ARTISKL2 Inventory.PickupMessage "$TXT_ARTIPUZZSKULL2" + Tag "$TAG_ARTIPUZZSKULL2" States { Spawn: @@ -185,6 +195,7 @@ ACTOR PuzzFWeapon : PuzzleItem 9015 PuzzleItem.Number 10 Inventory.Icon ARTIFWEP Inventory.PickupMessage "$TXT_ARTIPUZZFWEAPON" + Tag "$TAG_ARTIPUZZFWEAPON" States { Spawn: @@ -202,6 +213,7 @@ ACTOR PuzzCWeapon : PuzzleItem 9016 PuzzleItem.Number 11 Inventory.Icon ARTICWEP Inventory.PickupMessage "$TXT_ARTIPUZZCWEAPON" + Tag "$TAG_ARTIPUZZCWEAPON" States { Spawn: @@ -219,6 +231,7 @@ ACTOR PuzzMWeapon : PuzzleItem 9017 PuzzleItem.Number 12 Inventory.Icon ARTIMWEP Inventory.PickupMessage "$TXT_ARTIPUZZMWEAPON" + Tag "$TAG_ARTIPUZZMWEAPON" States { Spawn: @@ -235,6 +248,7 @@ ACTOR PuzzGear1 : PuzzleItem 9018 PuzzleItem.Number 13 Inventory.Icon ARTIGEAR Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR1" States { Spawn: @@ -252,6 +266,7 @@ ACTOR PuzzGear2 : PuzzleItem 9019 PuzzleItem.Number 14 Inventory.Icon ARTIGER2 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR2" States { Spawn: @@ -269,6 +284,7 @@ ACTOR PuzzGear3 : PuzzleItem 9020 PuzzleItem.Number 15 Inventory.Icon ARTIGER3 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR3" States { Spawn: @@ -286,6 +302,7 @@ ACTOR PuzzGear4 : PuzzleItem 9021 PuzzleItem.Number 16 Inventory.Icon ARTIGER4 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR4" States { Spawn: diff --git a/wadsrc/static/actors/hexen/speedboots.txt b/wadsrc/static/actors/hexen/speedboots.txt index 17d193d2a..5fd6703b8 100644 --- a/wadsrc/static/actors/hexen/speedboots.txt +++ b/wadsrc/static/actors/hexen/speedboots.txt @@ -9,6 +9,7 @@ ACTOR ArtiSpeedBoots : PowerupGiver 8002 +INVENTORY.PICKUPFLASH Inventory.Icon ARTISPED Inventory.PickupMessage "$TXT_ARTISPEED" + Tag "$TAG_ARTISPEED" Powerup.Type Speed States { diff --git a/wadsrc/static/actors/hexen/summon.txt b/wadsrc/static/actors/hexen/summon.txt index 399c2dd89..a4b3b8584 100644 --- a/wadsrc/static/actors/hexen/summon.txt +++ b/wadsrc/static/actors/hexen/summon.txt @@ -14,6 +14,7 @@ ACTOR ArtiDarkServant : Inventory 86 native Inventory.Icon "ARTISUMN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUMMON" + Tag "$TAG_ARTISUMMON" States { Spawn: diff --git a/wadsrc/static/actors/hexen/teleportother.txt b/wadsrc/static/actors/hexen/teleportother.txt index bcbdfc33c..77a05f6af 100644 --- a/wadsrc/static/actors/hexen/teleportother.txt +++ b/wadsrc/static/actors/hexen/teleportother.txt @@ -14,6 +14,7 @@ ACTOR ArtiTeleportOther : Inventory 10040 native Inventory.Icon "ARTITELO" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORTOTHER" + Tag "$TAG_ARTITELEPORTOTHER" States { Spawn: diff --git a/wadsrc/static/actors/raven/artiegg.txt b/wadsrc/static/actors/raven/artiegg.txt index 6b8de50c9..d94f00151 100644 --- a/wadsrc/static/actors/raven/artiegg.txt +++ b/wadsrc/static/actors/raven/artiegg.txt @@ -38,6 +38,7 @@ ACTOR ArtiEgg : CustomInventory 30 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIEGG" Inventory.DefMaxAmount + Tag "$TAG_ARTIEGG" States { Spawn: @@ -91,10 +92,11 @@ ACTOR ArtiPork : CustomInventory 30 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIEGG2" Inventory.DefMaxAmount + Tag "$TAG_ARTIPORK" States { Spawn: - PORK ABCDEFGH 6 + PORK ABCDEFGH 5 Loop Use: TNT1 A 0 A_FireCustomMissile("PorkFX", -15, 0, 0, 0, 1) diff --git a/wadsrc/static/actors/raven/artitele.txt b/wadsrc/static/actors/raven/artitele.txt index 5c1fa9d8f..1f0fe73d2 100644 --- a/wadsrc/static/actors/raven/artitele.txt +++ b/wadsrc/static/actors/raven/artitele.txt @@ -13,6 +13,7 @@ ACTOR ArtiTeleport : Inventory 36 native Inventory.Icon "ARTIATLP" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORT" + Tag "$TAG_ARTITELEPORT" States { Spawn: diff --git a/wadsrc/static/actors/raven/ravenartifacts.txt b/wadsrc/static/actors/raven/ravenartifacts.txt index 84264764b..c02c19c3b 100644 --- a/wadsrc/static/actors/raven/ravenartifacts.txt +++ b/wadsrc/static/actors/raven/ravenartifacts.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealth : HealthPickup 82 Inventory.Icon ARTIPTN2 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALTH" + Tag "$TAG_ARTIHEALTH" HealthPickup.Autouse 1 States { @@ -36,6 +37,7 @@ ACTOR ArtiSuperHealth : HealthPickup 32 Inventory.Icon ARTISPHL Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUPERHEALTH" + Tag "$TAG_ARTISUPERHEALTH" HealthPickup.Autouse 2 States { @@ -58,6 +60,7 @@ ACTOR ArtiFly : PowerupGiver 83 Inventory.RespawnTics 4230 Inventory.Icon ARTISOAR Inventory.PickupMessage "$TXT_ARTIFLY" + Tag "$TAG_ARTIFLY" Powerup.Type Flight States { @@ -79,6 +82,7 @@ ACTOR ArtiInvulnerability : PowerupGiver 84 Inventory.RespawnTics 4230 Inventory.Icon ARTIINVU Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY" + Tag "$TAG_ARTIINVULNERABILITY" Powerup.Type Invulnerable Powerup.Color GoldMap States @@ -102,6 +106,7 @@ ACTOR ArtiInvulnerability2 : PowerupGiver 84 Inventory.Icon ARTIDEFN Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY2" Powerup.Type Invulnerable + Tag "$TAG_ARTIDEFENDER" States { Spawn: @@ -121,6 +126,7 @@ ACTOR ArtiTorch : PowerupGiver 33 +INVENTORY.PICKUPFLASH Inventory.Icon ARTITRCH Inventory.PickupMessage "$TXT_ARTITORCH" + Tag "$TAG_ARTITORCH" Powerup.Type Torch States { diff --git a/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index a3c93b738..28c867ba9 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -5,13 +5,13 @@ ACTOR Inventory native Inventory.InterHubAmount 1 Inventory.UseSound "misc/invuse" Inventory.PickupSound "misc/i_pkup" - Inventory.PickupMessage "You got a pickup." + Inventory.PickupMessage "$TXT_DEFAULTPICKUPMSG" action native A_JumpIfNoAmmo(state label); - action native A_CustomPunch(int damage, bool norandom = false, bool useammo = true, class pufftype = "BulletPuff", float range = 0); - action native A_FireBullets(float spread_xy, float spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", bool useammo = true, float range = 0); + 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_FireBullets(float/*angle*/ spread_xy, float/*angle*/ spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, float range = 0); action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, bool aimatangle = false, float pitch = 0); - action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff"); + action native A_RailAttack(int damage, int spawnofs_xy = 0, bool useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float/*angle*/ spread_xy = 0, float/*angle*/ spread_z = 0); action native A_Light(int extralight); action native A_Light0(); action native A_Light1(); @@ -41,7 +41,7 @@ ACTOR Inventory native action native A_ClearReFire(); action native A_CheckReload(); action native A_GunFlash(state flash = ""); - action native A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff"); + action native A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float/*angle*/ spread_xy = 2.8125, float/*angle*/ spread_z = 0, float lifesteal = 0); action native A_CheckForReload(int counter, state label, bool dontincrement = false); action native A_ResetReloadCounter(); action native A_RestoreSpecialPosition(); diff --git a/wadsrc/static/actors/shared/sectoraction.txt b/wadsrc/static/actors/shared/sectoraction.txt index 24b81bc39..2d7fe2a80 100644 --- a/wadsrc/static/actors/shared/sectoraction.txt +++ b/wadsrc/static/actors/shared/sectoraction.txt @@ -73,3 +73,9 @@ ACTOR SecActHitFakeFloor : SectorAction 9989 native { } +// Music changer ---------------------------------- + +ACTOR MusicChanger : SectorAction 14165 native +{ +} + diff --git a/wadsrc/static/actors/strife/acolyte.txt b/wadsrc/static/actors/strife/acolyte.txt index 139b13056..1d04147f0 100644 --- a/wadsrc/static/actors/strife/acolyte.txt +++ b/wadsrc/static/actors/strife/acolyte.txt @@ -13,8 +13,9 @@ ACTOR Acolyte : StrifeHumanoid +SEESDAGGERS +NOSPLASHALERT +FLOORCLIP + +NEVERRESPAWN MinMissileChance 150 - Tag "ACOLYTE" + Tag "$TAG_ACOLYTE" SeeSound "acolyte/sight" PainSound "acolyte/pain" AttackSound "acolyte/rifle" diff --git a/wadsrc/static/actors/strife/beggars.txt b/wadsrc/static/actors/strife/beggars.txt index 8820f32d0..b24080b1a 100644 --- a/wadsrc/static/actors/strife/beggars.txt +++ b/wadsrc/static/actors/strife/beggars.txt @@ -13,7 +13,7 @@ ACTOR Beggar : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Beggar" + Tag "$TAG_BEGGAR" MaxStepHeight 16 MaxDropoffHeight 32 HitObituary "$OB_BEGGAR" diff --git a/wadsrc/static/actors/strife/coin.txt b/wadsrc/static/actors/strife/coin.txt index f569fb1ed..e4e50af77 100644 --- a/wadsrc/static/actors/strife/coin.txt +++ b/wadsrc/static/actors/strife/coin.txt @@ -10,7 +10,7 @@ ACTOR Coin : Inventory 93 native +FLOORCLIP Inventory.MaxAmount 0x7fffffff +INVENTORY.INVBAR - Tag "coin" + Tag "$TAG_COIN" Inventory.Icon "I_COIN" Inventory.PickupMessage "$TXT_COIN" States @@ -29,7 +29,7 @@ ACTOR Gold10 : Coin 138 Game Strife ConversationID 169, 162, 166 Inventory.Amount 10 - Tag "10 gold" + Tag "$TAG_10GOLD" Inventory.PickupMessage "$TXT_10GOLD" States { @@ -46,7 +46,7 @@ ACTOR Gold25 : Coin 139 Game Strife ConversationID 170, 163, 167 Inventory.Amount 25 - Tag "25 gold" + Tag "$TAG_25GOLD" Inventory.PickupMessage "$TXT_25GOLD" States { @@ -63,7 +63,7 @@ ACTOR Gold50 : Coin 140 Game Strife ConversationID 171, 164, 168 Inventory.Amount 50 - Tag "50 gold" + Tag "$TAG_50GOLD" Inventory.PickupMessage "$TXT_50GOLD" States { @@ -79,7 +79,7 @@ ACTOR Gold300 : Coin { ConversationID 172, -1, -1 Inventory.Amount 300 - Tag "300 gold" + Tag "$TAG_300GOLD" Inventory.PickupMessage "$TXT_300GOLD" Inventory.GiveQuest 3 +INVENTORY.ALWAYSPICKUP diff --git a/wadsrc/static/actors/strife/loremaster.txt b/wadsrc/static/actors/strife/loremaster.txt index 39ffcea4c..fee3eb0f0 100644 --- a/wadsrc/static/actors/strife/loremaster.txt +++ b/wadsrc/static/actors/strife/loremaster.txt @@ -20,9 +20,10 @@ ACTOR Loremaster 12 +INCOMBAT +LOOKALLAROUND +NOICEDEATH + +NEVERRESPAWN DamageFactor "Fire", 0.5 MinMissileChance 150 - Tag "PRIEST" + Tag "$TAG_PRIEST" SeeSound "loremaster/sight" AttackSound "loremaster/attack" PainSound "loremaster/pain" diff --git a/wadsrc/static/actors/strife/macil.txt b/wadsrc/static/actors/strife/macil.txt index ba99be435..ac23e4e66 100644 --- a/wadsrc/static/actors/strife/macil.txt +++ b/wadsrc/static/actors/strife/macil.txt @@ -16,13 +16,14 @@ ACTOR Macil1 64 +NOICEDEATH +NOSPLASHALERT +NODAMAGE + +NEVERRESPAWN DamageFactor "Fire", 0.5 MinMissileChance 150 SeeSound "macil/sight" PainSound "macil/pain" ActiveSound "macil/active" CrushPainSound "misc/pcrush" - Tag "MACIL" + Tag "$TAG_MACIL1" Obituary "$OB_MACIL" DropItem "BoxOfBullets" MaxStepHeight 16 @@ -65,6 +66,7 @@ ACTOR Macil2 : Macil1 200 +COUNTKILL +SPECTRAL -NODAMAGE + Tag "$TAG_MACIL2" DeathSound "macil/slop" DropItem "None" DamageFactor "SpectralLow", 0 diff --git a/wadsrc/static/actors/strife/merchants.txt b/wadsrc/static/actors/strife/merchants.txt index 65f5ac653..b6a2e09a5 100644 --- a/wadsrc/static/actors/strife/merchants.txt +++ b/wadsrc/static/actors/strife/merchants.txt @@ -59,7 +59,7 @@ ACTOR WeaponSmith : Merchant 116 Game Strife ConversationID 2 PainSound "smith/pain" - Tag "Weapon Smith" + Tag "$TAG_WEAPONSMITH" } @@ -72,7 +72,7 @@ ACTOR BarKeep : Merchant 72 ConversationID 3 PainSound "barkeep/pain" ActiveSound "barkeep/active" - Tag "Bar Keep" + Tag "$TAG_BARKEEP" } @@ -84,7 +84,7 @@ ACTOR Armorer : Merchant 73 Translation 5 ConversationID 4 PainSound "armorer/pain" - Tag "Armorer" + Tag "$TAG_ARMORER" } @@ -96,6 +96,6 @@ ACTOR Medic : Merchant 74 Translation 6 ConversationID 5 PainSound "medic/pain" - Tag "Medic" + Tag "$TAG_MEDIC" } diff --git a/wadsrc/static/actors/strife/oracle.txt b/wadsrc/static/actors/strife/oracle.txt index ca25f2526..2e0aff948 100644 --- a/wadsrc/static/actors/strife/oracle.txt +++ b/wadsrc/static/actors/strife/oracle.txt @@ -11,10 +11,11 @@ ACTOR Oracle 199 Monster +NOTDMATCH +NOBLOOD + +NEVERRESPAWN DamageFactor "Fire", 0.5 DamageFactor "SpectralLow", 0 MaxDropoffHeight 32 - Tag "Oracle" + Tag "$TAG_ORACLE" DropItem "Meat" action native A_WakeOracleSpectre (); diff --git a/wadsrc/static/actors/strife/questitems.txt b/wadsrc/static/actors/strife/questitems.txt index b06cdfaa8..cf6b98b0b 100644 --- a/wadsrc/static/actors/strife/questitems.txt +++ b/wadsrc/static/actors/strife/questitems.txt @@ -64,19 +64,19 @@ ACTOR QuestItem3 : QuestItem ACTOR QuestItem4 : QuestItem { ConversationID 315, 296, 313 - Tag "quest4" + Tag "$TAG_QUEST4" } ACTOR QuestItem5 : QuestItem { ConversationID 316, 297, 314 - Tag "quest5" + Tag "$TAG_QUEST5" } ACTOR QuestItem6 : QuestItem { ConversationID 317, 298, 315 - Tag "quest4" + Tag "TAG_QUEST6" } ACTOR QuestItem7 : QuestItem diff --git a/wadsrc/static/actors/strife/ratbuddy.txt b/wadsrc/static/actors/strife/ratbuddy.txt index 762aff659..eaefda35d 100644 --- a/wadsrc/static/actors/strife/ratbuddy.txt +++ b/wadsrc/static/actors/strife/ratbuddy.txt @@ -12,7 +12,7 @@ ACTOR RatBuddy 85 MinMissileChance 150 MaxStepHeight 16 MaxDropoffHeight 32 - Tag "rat buddy" + Tag "$TAG_RATBUDDY" SeeSound "rat/sight" DeathSound "rat/death" ActiveSound "rat/active" diff --git a/wadsrc/static/actors/strife/rebels.txt b/wadsrc/static/actors/strife/rebels.txt index ff6faeec2..3c539bca4 100644 --- a/wadsrc/static/actors/strife/rebels.txt +++ b/wadsrc/static/actors/strife/rebels.txt @@ -14,7 +14,7 @@ ACTOR Rebel : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Rebel" + Tag "$TAG_REBEL" SeeSound "rebel/sight" PainSound "rebel/pain" DeathSound "rebel/death" @@ -124,7 +124,7 @@ ACTOR TeleporterBeacon : Inventory 10 native +DROPPED +INVENTORY.INVBAR Inventory.Icon "I_BEAC" - Tag "Teleporter Beacon" + Tag "$TAG_TELEPORTERBEACON" Inventory.PickupMessage "$TXT_BEACON" action native A_Beacon (); diff --git a/wadsrc/static/actors/strife/sentinel.txt b/wadsrc/static/actors/strife/sentinel.txt index 8128063c4..14bbaf8e1 100644 --- a/wadsrc/static/actors/strife/sentinel.txt +++ b/wadsrc/static/actors/strife/sentinel.txt @@ -20,6 +20,7 @@ ACTOR Sentinel 3006 +INCOMBAT +MISSILEMORE +LOOKALLAROUND + +NEVERRESPAWN MinMissileChance 150 SeeSound "sentinel/sight" DeathSound "sentinel/death" diff --git a/wadsrc/static/actors/strife/sigil.txt b/wadsrc/static/actors/strife/sigil.txt index ab9c6d21b..a2cc66c7b 100644 --- a/wadsrc/static/actors/strife/sigil.txt +++ b/wadsrc/static/actors/strife/sigil.txt @@ -11,7 +11,7 @@ ACTOR Sigil : Weapon native +FLOORCLIP +WEAPON.CHEATNOTWEAPON Inventory.PickupSound "weapons/sigilcharge" - Tag "SIGIL" + Tag "$TAG_SIGIL" Inventory.Icon "I_SGL1" Inventory.PickupMessage "$TXT_SIGIL" diff --git a/wadsrc/static/actors/strife/strifeammo.txt b/wadsrc/static/actors/strife/strifeammo.txt index 1de16474f..801714434 100644 --- a/wadsrc/static/actors/strife/strifeammo.txt +++ b/wadsrc/static/actors/strife/strifeammo.txt @@ -10,7 +10,7 @@ ACTOR HEGrenadeRounds : Ammo 152 Ammo.BackpackAmount 6 Ammo.BackpackMaxAmount 60 Inventory.Icon "I_GRN1" - Tag "HE-Grenade Rounds" + Tag "$TAG_HEGRENADES" Inventory.PickupMessage "$TXT_HEGRENADES" States { @@ -32,7 +32,7 @@ ACTOR PhosphorusGrenadeRounds : Ammo 153 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 32 Inventory.Icon "I_GRN2" - Tag "Phoshorus-Grenade Rounds" // "Fire-Grenade_Rounds" in the Teaser + Tag "$TAG_PHGRENADES" Inventory.PickupMessage "$TXT_PHGRENADES" States { @@ -55,7 +55,7 @@ ACTOR ClipOfBullets : Ammo 2007 Ammo.BackpackAmount 10 Ammo.BackpackMaxAmount 500 Inventory.Icon "I_BLIT" - Tag "Clip of Bullets" // "bullets" in the Teaser + Tag "$TAG_CLIPOFBULLETS" Inventory.PickupMessage "$TXT_CLIPOFBULLETS" States { @@ -73,7 +73,7 @@ ACTOR BoxOfBullets : ClipOfBullets 2048 SpawnID 139 ConversationID 180, 174, 178 Inventory.Amount 50 - Tag "Ammo" + Tag "$TAG_BOXOFBULLETS" Inventory.PickupMessage "$TXT_BOXOFBULLETS" States { @@ -96,7 +96,7 @@ ACTOR MiniMissiles : Ammo 2010 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 200 Inventory.Icon "I_ROKT" - Tag "Mini Missiles" //"rocket" in the Teaser + Tag "$TAG_MINIMISSILES" Inventory.PickupMessage "$TXT_MINIMISSILES" States { @@ -114,7 +114,7 @@ ACTOR CrateOfMissiles : MiniMissiles 2046 SpawnID 141 ConversationID 182, 176, 180 Inventory.Amount 20 - Tag "Crate of Missiles" //"box_of_rockets" in the Teaser + Tag "$TAG_CRATEOFMISSILES" Inventory.PickupMessage "$TXT_CRATEOFMISSILES" States { @@ -138,7 +138,7 @@ ACTOR EnergyPod : Ammo 2047 Ammo.BackpackMaxAmount 800 Ammo.DropAmount 20 Inventory.Icon "I_BRY1" - Tag "Energy Pod" + Tag "$TAG_ENERGYPOD" Inventory.PickupMessage "$TXT_ENERGYPOD" States { @@ -156,7 +156,7 @@ ACTOR EnergyPack : EnergyPod 17 SpawnID 142 ConversationID 184, 178, 182 Inventory.Amount 100 - Tag "Energy Pack" + Tag "$TAG_ENERGYPACK" Inventory.PickupMessage "$TXT_ENERGYPACK" States { @@ -178,7 +178,7 @@ ACTOR PoisonBolts : Ammo 115 Ammo.BackpackAmount 2 Ammo.BackpackMaxAmount 50 Inventory.Icon "I_PQRL" - Tag "Poison Bolts" // "poison_arrows" in the Teaser + Tag "$TAG_POISONBOLTS" Inventory.PickupMessage "$TXT_POISONBOLTS" States { @@ -200,7 +200,7 @@ ACTOR ElectricBolts : Ammo 114 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 100 Inventory.Icon "I_XQRL" - Tag "Electric Bolts" // "electric_arrows" in the Teaser + Tag "$TAG_ELECTRICBOLTS" Inventory.PickupMessage "$TXT_ELECTRICBOLTS" States { @@ -219,7 +219,7 @@ ACTOR AmmoSatchel : BackpackItem 183 ConversationID 187, 181, 184 +FLOORCLIP Inventory.Icon "I_BKPK" - Tag "Ammo Satchel" // "Back_pack" in the Teaser + Tag "$TAG_AMMOSATCHEL" Inventory.PickupMessage "$TXT_AMMOSATCHEL" States { diff --git a/wadsrc/static/actors/strife/strifearmor.txt b/wadsrc/static/actors/strife/strifearmor.txt index 2be8d8ad6..2431e9438 100644 --- a/wadsrc/static/actors/strife/strifearmor.txt +++ b/wadsrc/static/actors/strife/strifearmor.txt @@ -14,7 +14,7 @@ ACTOR MetalArmor : BasicArmorPickup 2019 Inventory.PickupMessage "$TXT_METALARMOR" Armor.SaveAmount 200 Armor.SavePercent 50 - Tag "Metal Armor" + Tag "$TAG_METALARMOR" States { Spawn: @@ -38,7 +38,7 @@ ACTOR LeatherArmor : BasicArmorPickup 2018 Inventory.PickupMessage "$TXT_LEATHERARMOR" Armor.SaveAmount 100 Armor.SavePercent 33.335 - Tag "Leather Armor" + Tag "$TAG_LEATHER" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifebishop.txt b/wadsrc/static/actors/strife/strifebishop.txt index 303129271..dcedf7f58 100644 --- a/wadsrc/static/actors/strife/strifebishop.txt +++ b/wadsrc/static/actors/strife/strifebishop.txt @@ -17,6 +17,7 @@ ACTOR StrifeBishop 187 +FLOORCLIP +INCOMBAT +NOICEDEATH + +NEVERRESPAWN DamageFactor "Fire", 0.5 MinMissileChance 150 MaxDropoffHeight 32 diff --git a/wadsrc/static/actors/strife/strifeitems.txt b/wadsrc/static/actors/strife/strifeitems.txt index 407a91843..74b957b83 100644 --- a/wadsrc/static/actors/strife/strifeitems.txt +++ b/wadsrc/static/actors/strife/strifeitems.txt @@ -8,7 +8,7 @@ ACTOR MedPatch : HealthPickup 2011 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 20 - Tag "Med patch" + Tag "$TAG_MEDPATCH" Inventory.Icon "I_STMP" Inventory.PickupMessage "$TXT_MEDPATCH" HealthPickup.Autouse 3 @@ -31,7 +31,7 @@ ACTOR MedicalKit : HealthPickup 2012 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 15 - Tag "Medical kit" + Tag "$TAG_MEDICALKIT" Inventory.Icon "I_MDKT" Inventory.PickupMessage "$TXT_MEDICALKIT" HealthPickup.Autouse 3 @@ -54,7 +54,7 @@ ACTOR SurgeryKit : HealthPickup 83 +INVENTORY.INVBAR Health -100 Inventory.MaxAmount 5 - Tag "Surgery Kit" // "full_health" in the Teaser + Tag "$TAG_SURGERYKIT" Inventory.Icon "I_FULL" Inventory.PickupMessage "$TXT_SURGERYKIT" States @@ -75,7 +75,7 @@ ACTOR StrifeMap : MapRevealer 2026 ConversationID 164, 160, 163 +FLOORCLIP Inventory.PickupSound "misc/p_pkup" - Inventory.PickupMessage "$TXT_STRIFEMAP" + Inventory.PickupMessage "$TXT_STRIFEMAP" States { Spawn: @@ -94,7 +94,7 @@ ACTOR BeldinsRing : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 173, 165, 169 - Tag "Ring" + Tag "$TAG_BELDINSRING" Inventory.Icon "I_RING" Inventory.GiveQuest 1 Inventory.PickupMessage "$TXT_BELDINSRING" @@ -118,7 +118,7 @@ ACTOR OfferingChalice : Inventory 205 ConversationID 174, 166, 170 Radius 10 Height 16 - Tag "Offering Chalice" + Tag "$TAG_OFFERINGCHALICE" Inventory.Icon "I_RELC" Inventory.PickupMessage "$TXT_OFFERINGCHALICE" Inventory.GiveQuest 2 @@ -139,7 +139,7 @@ ACTOR Ear : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 175, 167, 171 - Tag "Ear" + Tag "$TAG_EAR" Inventory.Icon "I_EARS" Inventory.PickupMessage "$TXT_EAR" Inventory.GiveQuest 9 @@ -164,7 +164,7 @@ ACTOR BrokenPowerCoupling : Inventory 226 +INVENTORY.INVBAR Radius 16 Height 16 - Tag "Broken Power Coupling" + Tag "$TAG_BROKENCOUPLING" Inventory.MaxAmount 1 Inventory.Icon "I_COUP" Inventory.PickupMessage "$TXT_BROKENCOUPLING" @@ -190,7 +190,7 @@ ACTOR ShadowArmor : PowerupGiver 2024 +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND RenderStyle Translucent - Tag "Shadow Armor" + Tag "$TAG_SHADOWARMOR" Inventory.MaxAmount 2 Powerup.Type "Shadow" Inventory.Icon "I_SHD1" @@ -217,7 +217,7 @@ ACTOR EnvironmentalSuit : PowerupGiver 2025 -INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 5 Powerup.Type "Mask" - Tag "Environmental Suit" + Tag "$TAG_ENVSUIT" Inventory.Icon "I_MASK" Inventory.PickupSound "misc/i_pkup" Inventory.PickupMessage "$TXT_ENVSUIT" @@ -238,7 +238,7 @@ ACTOR GuardUniform : Inventory 90 ConversationID 162, 158, 161 +FLOORCLIP +INVENTORY.INVBAR - Tag "Guard Uniform" + Tag "$TAG_GUARDUNIFORM" Inventory.Icon "I_UNIF" Inventory.PickupMessage "$TXT_GUARDUNIFORM" Inventory.GiveQuest 15 @@ -259,7 +259,7 @@ ACTOR OfficersUniform : Inventory 52 ConversationID 163, 159, 162 +FLOORCLIP +INVENTORY.INVBAR - Tag "Officer's Uniform" + Tag "$TAG_OFFICERSUNIFORM" Inventory.Icon "I_OFIC" Inventory.PickupMessage "$TXT_OFFICERSUNIFORM" States @@ -280,7 +280,7 @@ ACTOR FlameThrowerParts : Inventory +FLOORCLIP +INVENTORY.INVBAR Inventory.Icon "I_BFLM" - Tag "Flame Thrower Parts" + Tag "$TAG_FTHROWERPARTS" Inventory.PickupMessage "$TXT_FTHROWERPARTS" States { @@ -300,7 +300,7 @@ ACTOR InterrogatorReport : Inventory Game Strife ConversationID 308, 289, 306 +FLOORCLIP - Tag "Report" + Tag "$TAG_REPORT" Inventory.PickupMessage "$TXT_REPORT" States { @@ -319,7 +319,7 @@ ACTOR Info : Inventory ConversationID 300, 282, 299 +FLOORCLIP +INVENTORY.INVBAR - Tag "Info" + Tag "$TAG_INFO" Inventory.Icon "I_TOKN" Inventory.PickupMessage "$TXT_INFO" States @@ -340,7 +340,7 @@ ACTOR Targeter : PowerupGiver 207 +FLOORCLIP +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND - Tag "Targeter" + Tag "$TAG_TARGETER" Powerup.Type "Targeter" Inventory.MaxAmount 5 Inventory.Icon "I_TARG" @@ -361,7 +361,7 @@ ACTOR Communicator : Inventory 206 Game Strife ConversationID 176, 168, 172 +NOTDMATCH - Tag "Communicator" + Tag "$TAG_COMMUNICATOR" Inventory.Icon "I_COMM" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_COMMUNICATOR" @@ -389,7 +389,7 @@ ACTOR DegninOre : Inventory 59 native +FLOORCLIP +INCOMBAT +INVENTORY.INVBAR - Tag "Degnin Ore" + Tag "$TAG_DEGNINORE" DeathSound "ore/explode" Inventory.Icon "I_XPRK" Inventory.PickupMessage "$TXT_DEGNINORE" @@ -418,7 +418,7 @@ ACTOR GunTraining : Inventory +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Accuracy" + Tag "$TAG_GUNTRAINING" Inventory.Icon "I_GUNT" States { @@ -438,7 +438,7 @@ ACTOR HealthTraining : Inventory native +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Toughness" + Tag "$TAG_HEALTHTRAINING" Inventory.Icon "I_HELT" States { @@ -459,7 +459,7 @@ ACTOR Scanner : PowerupGiver 2027 native +FLOORCLIP +INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 1 - Tag "Scanner" + Tag "$TAG_SCANNER" Inventory.Icon "I_PMUP" Powerup.Type "Scanner" Inventory.PickupSound "misc/i_pkup" @@ -479,8 +479,8 @@ ACTOR PrisonPass : Key native Game Strife ConversationID 304, 286, 303 Inventory.Icon "I_TOKN" - Tag "Prison Pass" - Inventory.PickupMessage "TXT_PRISONPASS" + Tag "$TAG_PRISONPASS" + Inventory.PickupMessage "$TXT_PRISONPASS" States { Spawn: @@ -509,7 +509,7 @@ ACTOR DummyStrifeItem : Inventory native ACTOR RaiseAlarm : DummyStrifeItem native { ConversationID 301, 283, 300 - Tag "Alarm" + Tag "$TAG_ALARM" } // Open door tag 222 -------------------------------------------------------- @@ -538,7 +538,7 @@ ACTOR OpenDoor224 : DummyStrifeItem native ACTOR AmmoFillup : DummyStrifeItem native { ConversationID 298,280,297 - Tag "Ammo" + Tag "$TAG_AMMOFILLUP" } // Health ------------------------------------------------------------------- @@ -546,7 +546,7 @@ ACTOR AmmoFillup : DummyStrifeItem native ACTOR HealthFillup : DummyStrifeItem native { ConversationID 299,281,298 - Tag "Health" + Tag "$TAG_HEALTHFILLUP" } // Upgrade Stamina ---------------------------------------------------------- diff --git a/wadsrc/static/actors/strife/strifekeys.txt b/wadsrc/static/actors/strife/strifekeys.txt index d128cbbd6..b304f83bc 100644 --- a/wadsrc/static/actors/strife/strifekeys.txt +++ b/wadsrc/static/actors/strife/strifekeys.txt @@ -13,7 +13,7 @@ ACTOR BaseKey : StrifeKey 230 Game Strife ConversationID 133, 129, 132 Inventory.Icon "I_FUSL" - Tag "Base Key" + Tag "$TAG_BASEKEY" Inventory.PickupMessage "$TXT_BASEKEY" States { @@ -31,7 +31,7 @@ ACTOR GovsKey : StrifeKey Game Strife ConversationID 134, 130, 133 Inventory.Icon "I_REBL" - Tag "Govs Key" // "Rebel_Key" in the Teaser + Tag "$TAG_GOVSKEY" Inventory.PickupMessage "$TXT_GOVSKEY" States { @@ -49,7 +49,7 @@ ACTOR Passcard : StrifeKey 185 Game Strife ConversationID 135, 131, 134 Inventory.Icon "I_TPAS" - Tag "Passcard" + Tag "$TAG_PASSCARD" Inventory.PickupMessage "$TXT_PASSCARD" States { @@ -67,7 +67,7 @@ ACTOR IDBadge : StrifeKey 184 Game Strife ConversationID 136, 132, 135 Inventory.Icon "I_CRD1" - Tag "ID Badge" + Tag "$TAG_IDBADGE" Inventory.PickupMessage "$TXT_IDBADGE" States { @@ -85,7 +85,7 @@ ACTOR PrisonKey : StrifeKey Game Strife ConversationID 137, 133, 136 Inventory.Icon "I_PRIS" - Tag "Prison Key" + Tag "$TAG_PRISONKEY" Inventory.GiveQuest 11 Inventory.PickupMessage "$TXT_PRISONKEY" States @@ -104,7 +104,7 @@ ACTOR SeveredHand : StrifeKey 91 Game Strife ConversationID 138, 134, 137 Inventory.Icon "I_HAND" - Tag "Severed Hand" + Tag "$TAG_SEVEREDHAND" Inventory.GiveQuest 12 Inventory.PickupMessage "$TXT_SEVEREDHAND" States @@ -123,7 +123,7 @@ ACTOR Power1Key : StrifeKey Game Strife ConversationID 139, 135, 138 Inventory.Icon "I_PWR1" - Tag "Power1 Key" + Tag "$TAG_POWER1KEY" Inventory.PickupMessage "$TXT_POWER1KEY" States { @@ -141,7 +141,7 @@ ACTOR Power2Key : StrifeKey Game Strife ConversationID 140, 136, 139 Inventory.Icon "I_PWR2" - Tag "Power2 Key" + Tag "$TAG_POWER2KEY" Inventory.PickupMessage "$TXT_POWER2KEY" States { @@ -159,7 +159,7 @@ ACTOR Power3Key : StrifeKey Game Strife ConversationID 141, 137, 140 Inventory.Icon "I_PWR3" - Tag "Power3 Key" + Tag "$TAG_POWER3KEY" Inventory.PickupMessage "$TXT_POWER3KEY" States { @@ -177,7 +177,7 @@ ACTOR GoldKey : StrifeKey 40 Game Strife ConversationID 142, 138, 141 Inventory.Icon "I_KY1G" - Tag "Gold Key" + Tag "$TAG_GOLDKEY" Inventory.PickupMessage "$TXT_GOLDKEY" States { @@ -195,7 +195,7 @@ ACTOR IDCard : StrifeKey 13 Game Strife ConversationID 143, 139, 142 Inventory.Icon "I_CRD2" - Tag "ID Card" + Tag "$TAG_IDCARD" Inventory.PickupMessage "$TXT_IDCARD" States { @@ -213,7 +213,7 @@ ACTOR SilverKey : StrifeKey 38 Game Strife ConversationID 144, 140, 143 Inventory.Icon "I_KY2S" - Tag "Silver Key" + Tag "$TAG_SILVERKEY" Inventory.PickupMessage "$TXT_SILVERKEY" States { @@ -231,7 +231,7 @@ ACTOR OracleKey : StrifeKey 61 Game Strife ConversationID 145, 141, 144 Inventory.Icon "I_ORAC" - Tag "Oracle Key" + Tag "$TAG_ORACLEKEY" Inventory.PickupMessage "$TXT_ORACLEKEY" States { @@ -249,7 +249,7 @@ ACTOR MilitaryID : StrifeKey Game Strife ConversationID 146, 142, 145 Inventory.Icon "I_GYID" - Tag "Military ID" + Tag "$TAG_MILITARYID" Inventory.PickupMessage "$TXT_MILITARYID" States { @@ -267,7 +267,7 @@ ACTOR OrderKey : StrifeKey 86 Game Strife ConversationID 147, 143, 146 Inventory.Icon "I_FUBR" - Tag "Order Key" + Tag "$TAG_ORDERKEY" Inventory.PickupMessage "$TXT_ORDERKEY" States { @@ -285,7 +285,7 @@ ACTOR WarehouseKey : StrifeKey 166 Game Strife ConversationID 148, 144, 147 Inventory.Icon "I_WARE" - Tag "Warehouse Key" + Tag "$TAG_WAREHOUSEKEY" Inventory.PickupMessage "$TXT_WAREHOUSEKEY" States { @@ -303,7 +303,7 @@ ACTOR BrassKey : StrifeKey 39 Game Strife ConversationID 149, 145, 148 Inventory.Icon "I_KY3B" - Tag "Brass Key" + Tag "$TAG_BRASSKEY" Inventory.PickupMessage "$TXT_BRASSKEY" States { @@ -321,7 +321,7 @@ ACTOR RedCrystalKey : StrifeKey 192 Game Strife ConversationID 150, 146, 149 Inventory.Icon "I_RCRY" - Tag "Red Crystal Key" + Tag "$TAG_REDCRYSTALKEY" Inventory.PickupMessage "$TXT_REDCRYSTAL" States { @@ -339,7 +339,7 @@ ACTOR BlueCrystalKey : StrifeKey 193 Game Strife ConversationID 151, 147, 150 Inventory.Icon "I_BCRY" - Tag "Blue Crystal Key" + Tag "$TAG_BLUECRYSTALKEY" Inventory.PickupMessage "$TXT_BLUECRYSTAL" States { @@ -357,7 +357,7 @@ ACTOR ChapelKey : StrifeKey 195 Game Strife ConversationID 152, 148, 151 Inventory.Icon "I_CHAP" - Tag "Chapel Key" + Tag "$TAG_CHAPELKEY" Inventory.PickupMessage "$TXT_CHAPELKEY" States { @@ -375,7 +375,7 @@ ACTOR CatacombKey : StrifeKey Game Strife ConversationID 153, 149, 152 Inventory.Icon "I_TUNL" - Tag "Catacomb Key" // "Tunnel_Key" in the Teaser + Tag "$TAG_CATACOMBKEY" Inventory.GiveQuest 28 Inventory.PickupMessage "$TXT_CATACOMBKEY" States @@ -394,7 +394,7 @@ ACTOR SecurityKey : StrifeKey Game Strife ConversationID 154, 150, 153 Inventory.Icon "I_SECK" - Tag "Security Key" + Tag "$TAG_SECURITYKEY" Inventory.PickupMessage "$TXT_SECURITYKEY" States { @@ -412,7 +412,7 @@ ACTOR CoreKey : StrifeKey 236 Game Strife ConversationID 155, 151, 154 Inventory.Icon "I_GOID" - Tag "Core Key" // "New_Key1" in the Teaser + Tag "$TAG_COREKEY" Inventory.PickupMessage "$TXT_COREKEY" States { @@ -430,7 +430,7 @@ ACTOR MaulerKey : StrifeKey 233 Game Strife ConversationID 156, 152, 155 Inventory.Icon "I_BLTK" - Tag "Mauler Key" // "New_Key2" in the Teaser + Tag "$TAG_MAULERKEY" Inventory.PickupMessage "$TXT_MAULERKEY" States { @@ -448,7 +448,7 @@ ACTOR FactoryKey : StrifeKey 234 Game Strife ConversationID 157, 153, 156 Inventory.Icon "I_PROC" - Tag "Factory Key" // "New_Key3" in the Teaser + Tag "$TAG_FACTORYKEY" Inventory.PickupMessage "$TXT_FACTORYKEY" States { @@ -465,8 +465,8 @@ ACTOR MineKey : StrifeKey 235 { Game Strife ConversationID 158, 154, 157 - Inventory.Icon "I_MINE" // "New_Key4" in the Teaser - Tag "Mine_Key" + Inventory.Icon "I_MINE" + Tag "$TAG_MINEKEY" Inventory.PickupMessage "$TXT_MINEKEY" States { @@ -484,7 +484,7 @@ ACTOR NewKey5 : StrifeKey Game Strife ConversationID 159, 155, 158 Inventory.Icon "I_BLTK" - Tag "New Key5" + Tag "$TAG_NEWKEY5" Inventory.PickupMessage "$TXT_NEWKEY5" States { @@ -505,7 +505,7 @@ ACTOR OraclePass : Inventory Inventory.Icon "I_OTOK" Inventory.GiveQuest 18 Inventory.PickupMessage "$TXT_ORACLEPASS" - Tag "Oracle Pass" + Tag "$TAG_ORACLEPASS" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifestuff.txt b/wadsrc/static/actors/strife/strifestuff.txt index d8ecd9bbb..a80685c35 100644 --- a/wadsrc/static/actors/strife/strifestuff.txt +++ b/wadsrc/static/actors/strife/strifestuff.txt @@ -1662,7 +1662,6 @@ ACTOR TargetPractice 208 ACTOR ForceFieldGuard 25 native { Game Strife - ConversationID 0 Health 10 Radius 2 Height 1 diff --git a/wadsrc/static/actors/strife/strifeweapons.txt b/wadsrc/static/actors/strife/strifeweapons.txt index d287ace08..5e3a419d5 100644 --- a/wadsrc/static/actors/strife/strifeweapons.txt +++ b/wadsrc/static/actors/strife/strifeweapons.txt @@ -48,6 +48,8 @@ ACTOR PunchDagger : StrifeWeapon Game Strife Weapon.SelectionOrder 3900 +WEAPON.NOALERT + Obituary "$OB_MPPUNCHDAGGER" + Tag "$TAG_PUNCHDAGGER" action native A_JabDagger (); @@ -109,6 +111,7 @@ ACTOR ElectricBolt : StrifeZap1 SeeSound "misc/swish" ActiveSound "misc/swish" DeathSound "weapons/xbowhit" + Obituary "$OB_MPELECTRICBOLT" States { Spawn: @@ -132,6 +135,7 @@ ACTOR PoisonBolt native MaxStepHeight 4 SeeSound "misc/swish" ActiveSound "misc/swish" + Obituary "$OB_MPPOISONBOLT" States { Spawn: @@ -158,8 +162,8 @@ ACTOR StrifeCrossbow : StrifeWeapon 2001 Weapon.AmmoType1 "ElectricBolts" Weapon.SisterWeapon "StrifeCrossbow2" Inventory.PickupMessage "$TXT_STRIFECROSSBOW" + Tag "$TAG_STRIFECROSSBOW1" Inventory.Icon "CBOWA0" - Tag "Crossbow" action native A_ClearFlash (); action native A_ShowElectricFlash (); @@ -205,6 +209,7 @@ ACTOR StrifeCrossbow2 : StrifeCrossbow Weapon.AmmoGive1 0 Weapon.AmmoType1 "PoisonBolts" Weapon.SisterWeapon "StrifeCrossbow" + Tag "$TAG_STRIFECROSSBOW2" States { @@ -243,8 +248,9 @@ actor AssaultGun : StrifeWeapon 2002 Weapon.AmmoGive1 20 Weapon.AmmoType1 "ClipOfBullets" Inventory.Icon "RIFLA0" - Tag "Assault Gun" + Tag "$TAG_ASSAULTGUN" Inventory.PickupMessage "$TXT_ASSAULTGUN" + Obituary "$OB_MPASSAULTGUN" States { Spawn: @@ -299,7 +305,7 @@ ACTOR MiniMissileLauncher : StrifeWeapon 2003 Weapon.AmmoGive1 8 Weapon.AmmoType1 "MiniMissiles" Inventory.Icon "MMSLA0" - Tag "Mini Missile Launcher" + Tag "$TAG_MMLAUNCHER" Inventory.PickupMessage "$TXT_MMLAUNCHER" action native A_FireMiniMissile (); @@ -376,6 +382,7 @@ ACTOR MiniMissile MaxStepHeight 4 SeeSound "weapons/minimissile" DeathSound "weapons/minimissilehit" + Obituary "$OB_MPMINIMISSILELAUNCHER" States { Spawn: @@ -407,7 +414,7 @@ ACTOR FlameThrower : StrifeWeapon 2005 Weapon.ReadySound "weapons/flameidle" Weapon.AmmoType1 "EnergyPod" Inventory.Icon "FLAMA0" - Tag "Flame Thrower" + Tag "$TAG_FLAMER" Inventory.PickupMessage "$TXT_FLAMER" action native A_FireFlamer (); @@ -451,6 +458,7 @@ ACTOR FlameMissile MaxStepHeight 4 RenderStyle Add SeeSound "weapons/flamethrower" + Obituary "$OB_MPFLAMETHROWER" action native A_FlameDie (); @@ -483,8 +491,9 @@ ACTOR Mauler : StrifeWeapon 2004 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler2" Inventory.Icon "TRPDA0" - Tag "Mauler" + Tag "$TAG_MAULER1" Inventory.PickupMessage "$TXT_MAULER" + Obituary "$OB_MPMAULER1" action native A_FireMauler1 (); @@ -525,6 +534,8 @@ ACTOR Mauler2 : Mauler Weapon.AmmoGive1 0 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler" + Obituary "$OB_MPMAULER2" + Tag "$TAG_MAULER2" action native A_FireMauler2Pre (); action native A_FireMauler2 (); @@ -586,6 +597,7 @@ ACTOR MaulerTorpedo RenderStyle Add SeeSound "weapons/mauler2fire" DeathSound "weapons/mauler2hit" + Obituary "$OB_MPMAULER" action native A_MaulerTorpedoWave (); @@ -616,6 +628,7 @@ ACTOR MaulerTorpedoWave +STRIFEDAMAGE MaxStepHeight 4 RenderStyle Add + Obituary "$OB_MPMAULER" States { Spawn: @@ -650,6 +663,7 @@ ACTOR HEGrenade BounceCount 2 SeeSound "weapons/hegrenadeshoot" DeathSound "weapons/hegrenadebang" + Obituary "$OB_MPSTRIFEGRENADE" States { Spawn: @@ -687,6 +701,7 @@ ACTOR PhosphorousGrenade BounceCount 2 SeeSound "weapons/phgrenadeshoot" DeathSound "weapons/phgrenadebang" + Obituary "$OB_MPPHOSPHOROUSGRENADE" States { Spawn: @@ -710,6 +725,7 @@ ACTOR PhosphorousFire native +NODAMAGETHRUST +DONTSPLASH RenderStyle Add + Obituary "$OB_MPPHOSPHOROUSGRENADE" action native A_Burnarea (); action native A_Burnination (); @@ -748,7 +764,7 @@ ACTOR StrifeGrenadeLauncher : StrifeWeapon 154 Weapon.AmmoType1 "HEGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher2" Inventory.Icon "GRNDA0" - Tag "Grenade Launcher" + Tag "$TAG_GLAUNCHER1" Inventory.PickupMessage "$TXT_GLAUNCHER" action native A_FireGrenade (class grenadetype, int angleofs, state flash); @@ -794,6 +810,7 @@ ACTOR StrifeGrenadeLauncher2 : StrifeGrenadeLauncher Weapon.AmmoGive1 0 Weapon.AmmoType1 "PhosphorusGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher" + Tag "$TAG_GLAUNCHER2" States { diff --git a/wadsrc/static/actors/strife/templar.txt b/wadsrc/static/actors/strife/templar.txt index 214804b34..c20aed49b 100644 --- a/wadsrc/static/actors/strife/templar.txt +++ b/wadsrc/static/actors/strife/templar.txt @@ -20,7 +20,7 @@ ACTOR Templar 3003 DeathSound "templar/death" ActiveSound "templar/active" CrushPainSound "misc/pcrush" - Tag "TEMPLAR" + Tag "$TAG_TEMPLAR" HitObituary "$OB_TEMPLARHIT" Obituary "$OB_TEMPLAR" DropItem "EnergyPod" diff --git a/wadsrc/static/althudcf.txt b/wadsrc/static/althudcf.txt index 524561180..957b381bf 100644 --- a/wadsrc/static/althudcf.txt +++ b/wadsrc/static/althudcf.txt @@ -1,4 +1,5 @@ Fist PUNGC0 PunchDagger PNCHD0 +FWeapFist FPCHC0 Beak "" Snout "" diff --git a/wadsrc/static/animdefs.txt b/wadsrc/static/animdefs.txt index f4b33eaab..a2a4498e2 100644 --- a/wadsrc/static/animdefs.txt +++ b/wadsrc/static/animdefs.txt @@ -95,6 +95,12 @@ pic SPMINO13 tics 3 pic SPMINO14 tics 3 pic SPMINO15 tics 3 +// Animate health vial for Heretic fullscreen HUD +texture optional PTN1A0 +pic PTN1A0 tics 3 +pic PTN1B0 tics 3 +pic PTN1C0 tics 3 + // The Wings of Wrath are not included, because they stop spinning when // you stop flying, so they can't be a simple animation. diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index 685fda086..98528363d 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -21,6 +21,7 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15 2F1F8E27FBB5EF21AFBE1F3B13C03037 // map16 1CE294781A2455DE72C197E0B3DF6212 // map31 { + setslopeoverflow resetplayerspeed } @@ -58,7 +59,7 @@ F84AB4557464A383E93F37CD3A82AC48 // MM2 map03 9D50EBE17CEC78938C7A668DB0768611 // Strain map07: Make the exit accessible { - spechitoverflow + clearlineflags 1021 1 } 71C2E6D9CFA3D8750C6A9599FB2453BD // Hacx map03: There are some switches behind @@ -128,3 +129,9 @@ CA267398C9B3A8F79349D3394F8B2106 // map20 { spritesort } + +DCE862393CAAA6FF1294FB7056B53057 // UAC Ultra map07: Contains a scroller depending on Boom side effects +{ + setlinespecial 391 Sector_CopyScroller 99 6 0 0 0 +} + diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 53d74a483..58cc87049 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -268,6 +268,8 @@ AMSTR_FOLLOWON = "Follow Mode ON"; AMSTR_FOLLOWOFF = "Follow Mode OFF"; AMSTR_GRIDON = "Grid ON"; AMSTR_GRIDOFF = "Grid OFF"; +AMSTR_TEXON = "Texture Mode ON"; +AMSTR_TEXOFF = "Texture Mode OFF"; AMSTR_MARKEDSPOT = "Marked Spot"; AMSTR_MARKSCLEARED = "All Marks Cleared"; STSTR_MUS = "Music Change"; @@ -284,6 +286,7 @@ STSTR_CHOPPERS = "... doesn't suck - GM"; STSTR_CLEV = "Changing Level...\n"; TXT_BUDDHAON = "Buddha mode ON"; TXT_BUDDHAOFF = "Buddha mode OFF"; +TXT_DEFAULTPICKUPMSG = "You got a pickup"; E1TEXT = "Once you beat the big badasses and\n" @@ -727,6 +730,50 @@ OB_MPTELEFRAG = "%o was telefragged by %k."; OB_RAILGUN = "%o was railed by %k."; OB_MPBFG_MBF = "%o was burned by %k's BFG."; +OB_MPSTAFF = "%o got staffed by %k."; +OB_MPGAUNTLETS = "%o got a shock from %k's gauntlets."; +OB_MPGOLDWAND = "%o waved goodbye to %k's elven wand."; +OB_MPCROSSBOW = "%o was pegged by %k's ethereal crossbow."; +OB_MPBLASTER = "%o was blasted a new one by %k's dragon claw."; +OB_MPSKULLROD = "%o got sent down under by %k's hellstaff."; +OB_MPPHOENIXROD = "%o was scorched to cinders by %k's phoenix rod."; +OB_MPMACE = "%o was bounced by $k's firemace."; + +OB_MPPSTAFF = "%o got clapped by %k's charged staff."; +OB_MPPGAUNTLETS = "%o was bled dry by %k's gauntlets."; +OB_MPPGOLDWAND = "%o was assaulted by %k's elven wand."; +OB_MPPCROSSBOW = "%o was shafted by %k's ethereal crossbow."; +OB_MPPBLASTER = "%o was ripped apart by %k's dragon claw."; +OB_MPPSKULLROD = "%k poured his hellstaff on %o."; +OB_MPPPHOENIXROD = "%o was burned down by %k's phoenix staff."; +OB_MPPMACE = "%o was squished by %k's giant mace sphere."; + +OB_MPFWEAPFIST = "%o was beaten to a pulp by %k's bare fists."; +OB_MPFWEAPAXE = "%o got the axe from %k."; +OB_MPFWEAPHAMMERM = "%o had %p head caved in by %k's hammer."; +OB_MPFWEAPHAMMERR = "%o's soul was forged anew by %k's hammer."; +OB_MPFWEAPQUIETUS = "%o was silenced by %k's mighty Quietus."; +OB_MPCWEAPMACE = "%o got a mace to the face from %k."; +OB_MPCWEAPSTAFFM = "%o was bitten by %k's serpent staff."; +OB_MPCWEAPSTAFFR = "%o choked on %k's serpent staff."; +OB_MPCWEAPFLAME = "%o was lit up by %k's flames."; +OB_MPCWEAPWRAITHVERGE = "%o was cleansed by %k's Wraithverge."; +OB_MPMWEAPWAND = "%o took one too many sapphire beams from %k."; +OB_MPMWEAPFROST = "%o was turned into a frosty fellow by %k."; +OB_MPMWEAPLIGHTNING = "%o recieved a shocking revelation from %k."; +OB_MPMWEAPBLOODSCOURGE = "%o was wiped off the face of the universe by %k's Bloodscourge."; + +OB_MPPUNCHDAGGER = "%o was unwittingly backstabbed by %k."; +OB_MPELECTRICBOLT = "%o got bolted to the wall by %k."; +OB_MPPOISONBOLT = "%o recieved a lethal dose of %k's wrath."; +OB_MPASSAULTGUN = "%o was drilled full of holes by %k's assault gun."; +OB_MPMINIMISSILELAUNCHER = "%o gulped down %k's missile."; +OB_MPSTRIFEGRENADE = "%o was inverted by %k's H-E grenade."; +OB_MPPHOSPHOROUSGRENADE = "%o took a flame bath in %k's phosphorous pyre."; +OB_MPFLAMETHROWER = "%o was barbecued by %k."; +OB_MPMAULER = "%o was viciously vaporized by %k."; +OB_MPSIGIL = "%o bowed down to the sheer power of %k's Sigil."; + // Same as OB_MPTELEFRAG, but shown when a monster telefrags you OB_MONTELEFRAG = "%o was telefragged."; @@ -743,6 +790,207 @@ STARTUP3 = ""; STARTUP4 = ""; STARTUP5 = ""; + +// Item tags: Doom weapons +TAG_FIST = "Brass Knuckles"; +TAG_CHAINSAW = "Chainsaw"; +TAG_PISTOL = "Pistol"; +TAG_SHOTGUN = "Shotgun"; +TAG_SUPERSHOTGUN = "Super Shotgun"; +TAG_CHAINGUN = "Chaingun"; +TAG_ROCKETLAUNCHER = "Rocket Launcher"; +TAG_PLASMARIFLE = "Plama Rifle"; +TAG_BFG9000 = "BFG 9000"; + +// Item tags: Heretic weapons +TAG_STAFF = "Staff"; +TAG_GAUNTLETS = "Gauntlets of the Necromancer"; +TAG_GOLDWAND = "Elven Wand"; +TAG_CROSSBOW = "Ethereal Crossbow"; +TAG_BLASTER = "Dragon Claw"; +TAG_SKULLROD = "Hellstaff"; +TAG_PHOENIXROD = "Phoenix Rod"; +TAG_MACE = "Firemace"; + +// Item tags: Heretic artifacts +TAG_ARTIEGG = "Morph Ovum"; +TAG_ARTIFIREBOMB = "Timebomb of the Ancients"; +TAG_ARTIFLY = "Wings of Wrath"; +TAG_ARTIHEALTH = "Quartz Flask"; +TAG_ARTIINVISIBILITY = "Shadowsphere"; +TAG_ARTIINVULNERABILITY = "Ring of Invincibility"; +TAG_ARTISUPERHEALTH = "Mystic Urn"; +TAG_ARTITELEPORT = "Chaos Device"; +TAG_ARTITOMEOFPOWER = "Tome of Power"; +TAG_ARTITORCH = "Torch"; + +// Item tags: Hexen weapons +TAG_CWEAPMACE = "Mace of Contrition"; +TAG_CWEAPSTAFF = "Serpent Staff"; +TAG_CWEAPFLAME = "Firestorm"; +TAG_CWEAPWRAITHVERGE = "Wraithverge"; +TAG_FWEAPFIST = "Spiked Gauntlets"; +TAG_FWEAPAXE = "Timon's Axe"; +TAG_FWEAPHAMMER = "Hammer of Retribution"; +TAG_FWEAPQUIETUS = "Quietus"; +TAG_MWEAPWAND = "Sapphire Wand"; +TAG_MWEAPFROST = "Frost Shards"; +TAG_MWEAPLIGHTNING = "Arcs of Death"; +TAG_MWEAPBLOODSCOURGE = "Bloodscourge"; + +// Item tags: Hexen artifacts +TAG_ARTIBLASTRADIUS = "Disc of Repulsion"; +TAG_ARTIBOOSTARMOR = "Dragonskin Bracers"; +TAG_ARTIBOOSTMANA = "Krater of Might"; +TAG_ARTIPOISONBAG = "Flechette"; +TAG_ARTIPOISONBAG1 = "Poison Cloud Flechette"; +TAG_ARTIPOISONBAG2 = "Timebomb Flechette"; +TAG_ARTIPOISONBAG3 = "Grenade Flechette"; +TAG_ARTIHEALINGRADIUS = "Mystic Ambit Incant"; +TAG_ARTIDEFENDER = "Icon of the Defender"; +TAG_ARTIPORK = "Porkelator"; +TAG_ARTISPEED = "Boots of Speed"; +TAG_ARTISUMMON = "Dark Servant"; +TAG_ARTITELEPORTOTHER = "Banishment Device"; + +// Item tags: Hexen puzzle items +TAG_ARTIPUZZSKULL = "Yorick's Skull"; +TAG_ARTIPUZZGEMBIG = "Heart of D'Sparil"; +TAG_ARTIPUZZGEMRED = "Ruby Planet"; +TAG_ARTIPUZZGEMGREEN1 = "Emerald Planet (1)"; +TAG_ARTIPUZZGEMGREEN2 = "Emerald Planet (2)"; +TAG_ARTIPUZZGEMBLUE1 = "Sapphire Planet (1)"; +TAG_ARTIPUZZGEMBLUE2 = "Sapphire Planet (2)"; +TAG_ARTIPUZZBOOK1 = "Daemon Codex"; +TAG_ARTIPUZZBOOK2 = "Liber Obscura"; +TAG_ARTIPUZZSKULL2 = "Flame Mask"; +TAG_ARTIPUZZFWEAPON = "Glaive Seal"; +TAG_ARTIPUZZCWEAPON = "Holy Relic"; +TAG_ARTIPUZZMWEAPON = "Sigil of the Magus"; +TAG_ARTIPUZZGEAR1 = "Iron gear"; +TAG_ARTIPUZZGEAR2 = "Brass gear"; +TAG_ARTIPUZZGEAR3 = "Brass and iron gear"; +TAG_ARTIPUZZGEAR4 = "Silver and brass gear"; + +// Item tags: Strife weapons +TAG_PUNCHDAGGER = "Dagger"; +TAG_STRIFECROSSBOW1 = "Crossbow"; +TAG_STRIFECROSSBOW2 = "Crossbow"; +TAG_ASSAULTGUN = "Assault Gun"; +TAG_MMLAUNCHER = "Mini Missile Launcher"; +TAG_FLAMER = "Flame Thrower"; +TAG_MAULER1 = "Mauler"; +TAG_MAULER2 = "Mauler"; +TAG_GLAUNCHER1 = "Grenade Launcher"; +TAG_GLAUNCHER2 = "Grenade Launcher"; +TAG_SIGIL = "SIGIL"; + +// Item tags: Strife artifacts +TAG_COIN = "coin"; +TAG_MEDPATCH = "Med patch"; +TAG_MEDICALKIT = "Medical kit"; +TAG_SURGERYKIT = "Surgery Kit"; // "full_health" in the Teaser +TAG_BELDINSRING = "Ring"; +TAG_OFFERINGCHALICE = "Offering Chalice"; +TAG_EAR = "Ear"; +TAG_BROKENCOUPLING = "Broken Power Coupling"; +TAG_SHADOWARMOR = "Shadow Armor"; +TAG_ENVSUIT = "Environmental Suit"; +TAG_GUARDUNIFORM = "Guard Uniform"; +TAG_OFFICERSUNIFORM = "Officer's Uniform"; +TAG_FTHROWERPARTS = "Flame Thrower Parts"; +TAG_REPORT = "Report"; +TAG_INFO = "Info"; +TAG_TARGETER = "Targeter"; +TAG_COMMUNICATOR = "Communicator"; +TAG_DEGNINORE = "Degnin Ore"; +TAG_GUNTRAINING = "Accuracy"; +TAG_HEALTHTRAINING = "Toughness"; +TAG_SCANNER = "Scanner"; +TAG_PRISONPASS = "Prison Pass"; +TAG_ALARM = "Alarm"; +TAG_AMMOFILLUP = "Ammo"; +TAG_HEALTHFILLUP = "Health"; +TAG_TELEPORTERBEACON = "Teleporter Beacon"; +TAG_METALARMOR = "Metal Armor"; +TAG_LEATHER = "Leather Armor"; +TAG_HEGRENADES = "HE-Grenade Rounds"; +TAG_PHGRENADES = "Phoshorus-Grenade Rounds"; // "Fire-Grenade_Rounds" in the Teaser +TAG_CLIPOFBULLETS = "Clip of Bullets"; // "bullets" in the Teaser +TAG_BOXOFBULLETS = "Ammo"; +TAG_MINIMISSILES = "Mini Missiles"; //"rocket" in the Teaser +TAG_CRATEOFMISSILES = "Crate of Missiles"; //"box_of_rockets" in the Teaser +TAG_ENERGYPOD = "Energy Pod"; +TAG_ENERGYPACK = "Energy Pack"; +TAG_POISONBOLTS = "Poison Bolts"; // "poison_arrows" in the Teaser +TAG_ELECTRICBOLTS = "Electric Bolts"; // "electric_arrows" in the Teaser +TAG_AMMOSATCHEL = "Ammo Satchel"; // "Back_pack" in the Teaser + +// Item tags: Strife keys +TAG_BASEKEY = "Base Key"; +TAG_GOVSKEY = "Govs Key"; // "Rebel_Key" in the Teaser +TAG_PASSCARD = "Passcard"; +TAG_IDBADGE = "ID Badge"; +TAG_PRISONKEY = "Prison Key"; +TAG_SEVEREDHAND = "Severed Hand"; +TAG_POWER1KEY = "Power1 Key"; +TAG_POWER2KEY = "Power2 Key"; +TAG_POWER3KEY = "Power3 Key"; +TAG_GOLDKEY = "Gold Key"; +TAG_IDCARD = "ID Card"; +TAG_SILVERKEY = "Silver Key"; +TAG_ORACLEKEY = "Oracle Key"; +TAG_MILITARYID = "Military ID"; +TAG_ORDERKEY = "Order Key"; +TAG_WAREHOUSEKEY = "Warehouse Key"; +TAG_BRASSKEY = "Brass Key"; +TAG_REDCRYSTALKEY = "Red Crystal Key"; +TAG_BLUECRYSTALKEY = "Blue Crystal Key"; +TAG_CHAPELKEY = "Chapel Key"; +TAG_CATACOMBKEY = "Catacomb Key"; // "Tunnel_Key" in the Teaser +TAG_SECURITYKEY = "Security Key"; +TAG_COREKEY = "Core Key"; // "New_Key1" in the Teaser +TAG_MAULERKEY = "Mauler Key"; // "New_Key2" in the Teaser +TAG_FACTORYKEY = "Factory Key"; // "New_Key3" in the Teaser +TAG_MINEKEY = "Mine_Key"; // "New_Key4" in the Teaser +TAG_NEWKEY5 = "New Key5"; +TAG_ORACLEPASS = "Oracle Pass"; + +// Item tags: misc Strife stuff +TAG_10GOLD = "10 gold"; +TAG_25GOLD = "25 gold"; +TAG_50GOLD = "50 gold"; +TAG_300GOLD = "300 gold"; +TAG_QUEST4 = "quest4"; +TAG_QUEST5 = "quest5"; +TAG_QUEST6 = "quest4"; + +// Item tags: Strife NPCs +TAG_ACOLYTE = "ACOLYTE"; +TAG_ARMORER = "Armorer"; +TAG_BARKEEP = "Bar Keep"; +TAG_BEGGAR = "Beggar"; +TAG_MACIL1 = "MACIL"; +TAG_MACIL2 = "MACIL"; +TAG_MEDIC = "Medic"; +TAG_ORACLE = "Oracle"; +TAG_PRIEST = "PRIEST"; +TAG_RATBUDDY = "rat buddy"; +TAG_REBEL = "Rebel"; +TAG_TEMPLAR = "TEMPLAR"; +TAG_WEAPONSMITH = "Weapon Smith"; + +// Item tags: Chex weapons +TAG_SPOON = "Spoon"; +TAG_SPORK = "Super Bootspork"; +TAG_MINIZORCHER = "Mini Zorcher"; +TAG_LARGEZORCHER = "Large Zorcher"; +TAG_SUPERLARGEZORCHER = "Super-Large Zorcher"; +TAG_RAPIDZORCHER = "Rapid Zorcher"; +TAG_ZORCHPROPULSOR = "Zorch Propulsor"; +TAG_PHASINGZORCHER = "Phasing Zorcher"; +TAG_LAZDEVICE = "LAZ Device"; + // Heretic strings HE1TEXT = "with the destruction of the iron\n" @@ -1231,7 +1479,27 @@ TXT_RANDOM_PGUARD_10 = "If there is any honor inside that pathetic shell of a bo TXT_RANDOMGOODBYE_1 = "Bye!"; TXT_RANDOMGOODBYE_2 = "Thanks, bye!"; TXT_RANDOMGOODBYE_3 = "See you later!"; +TXT_HAVEENOUGH = "You seem to have enough!"; +// Skills: + +SKILL_BABY = "I'm too young to die"; +SKILL_EASY = "Hey, not too rough"; +SKILL_NORMAL = "Hurt me plenty"; +SKILL_HARD = "Ultra-Violence"; +SKILL_NIGHTMARE = "NIGHTMARE!"; + +CSKILL_BABY = "Easy does it"; +CSKILL_EASY = "Not so sticky"; +CSKILL_NORMAL = "Gobs of goo"; +CSKILL_HARD = "Extreme Ooze"; +CSKILL_NIGHTMARE = "Super Slimey!"; + +SSKILL_BABY = "Training"; +SSKILL_EASY = "Rookie"; +SSKILL_NORMAL = "Veteran"; +SSKILL_HARD = "Elite"; +SSKILL_NIGHTMARE = "Bloodbath"; // Menu MNU_NEWGAME = "NEW GAME"; diff --git a/wadsrc/static/mapinfo/chex.txt b/wadsrc/static/mapinfo/chex.txt index edfbc8a46..deefea4d3 100644 --- a/wadsrc/static/mapinfo/chex.txt +++ b/wadsrc/static/mapinfo/chex.txt @@ -41,6 +41,8 @@ gameinfo defaultdropstyle = 1 endoom = "ENDOOM" player5start = 4001 + drawreadthis = true + pickupcolor = "d6 ba 45" } skill baby @@ -51,6 +53,7 @@ skill baby EasyBossBrain SpawnFilter = Baby PicName = "M_JKILL" + Name = "$CSKILL_BABY" Key = "i" } @@ -59,6 +62,7 @@ skill easy EasyBossBrain SpawnFilter = Easy PicName = "M_ROUGH" + Name = "$CSKILL_EASY" Key = "h" } @@ -66,6 +70,7 @@ skill normal { SpawnFilter = Normal PicName = "M_HURT" + Name = "$CSKILL_NORMAL" Key = "h" } @@ -73,6 +78,7 @@ skill hard { SpawnFilter = Hard PicName = "M_ULTRA" + Name = "$CSKILL_HARD" Key = "u" } @@ -85,6 +91,7 @@ skill nightmare SpawnFilter = Nightmare PicName = "M_NMARE" MustConfirm = "$CNIGHTMARE" + Name = "$CSKILL_NIGHTMARE" Key = "n" } diff --git a/wadsrc/static/mapinfo/doomcommon.txt b/wadsrc/static/mapinfo/doomcommon.txt index 4aeee99ad..f67783509 100644 --- a/wadsrc/static/mapinfo/doomcommon.txt +++ b/wadsrc/static/mapinfo/doomcommon.txt @@ -40,6 +40,7 @@ gameinfo defaultdropstyle = 1 endoom = "ENDOOM" player5start = 4001 + pickupcolor = "d6 ba 45" } skill baby @@ -50,6 +51,7 @@ skill baby EasyBossBrain SpawnFilter = Baby PicName = "M_JKILL" + Name = "$SKILL_BABY" Key = "i" } @@ -58,6 +60,7 @@ skill easy EasyBossBrain SpawnFilter = Easy PicName = "M_ROUGH" + Name = "$SKILL_EASY" Key = "h" } @@ -66,6 +69,7 @@ skill normal SpawnFilter = Normal PicName = "M_HURT" Key = "h" + Name = "$SKILL_NORMAL" DefaultSkill } @@ -73,6 +77,7 @@ skill hard { SpawnFilter = Hard PicName = "M_ULTRA" + Name = "$SKILL_HARD" Key = "u" } @@ -84,6 +89,7 @@ skill nightmare RespawnTime = 12 SpawnFilter = Nightmare PicName = "M_NMARE" + Name = "$SKILL_NIGHTMARE" MustConfirm Key = "n" } diff --git a/wadsrc/static/mapinfo/heretic.txt b/wadsrc/static/mapinfo/heretic.txt index 955574891..4ca727272 100644 --- a/wadsrc/static/mapinfo/heretic.txt +++ b/wadsrc/static/mapinfo/heretic.txt @@ -24,7 +24,7 @@ gameinfo defaultbloodparticlecolor = "ff 00 00" backpacktype = "BagOfHolding" armoricons = "SHLDA0", 0.75, "SHD2A0" - statusbar = "" + statusbar = "sbarinfo/heretic.txt" intermissionmusic = "mus_intr" intermissioncounter = false weaponslot = 1, "Staff", "Gauntlets" @@ -41,6 +41,7 @@ gameinfo defaultdropstyle = 1 endoom = "ENDTEXT" player5start = 4001 + pickupcolor = "d6 ba 45" } skill baby @@ -193,7 +194,7 @@ map E1M8 lookup "HHUSTR_E1M8" cluster = 1 nointermission ironlichspecial - specialaction_lowerfloor + specialaction_lowerfloortohighest music = "MUS_E1M8" } @@ -279,7 +280,7 @@ map E2M8 lookup "HHUSTR_E2M8" cluster = 2 nointermission minotaurspecial - specialaction_lowerfloor + specialaction_lowerfloortohighest specialaction_killmonsters music = "MUS_E2M8" } @@ -366,7 +367,7 @@ map E3M8 lookup "HHUSTR_E3M8" cluster = 3 nointermission dsparilspecial - specialaction_lowerfloor + specialaction_lowerfloortohighest specialaction_killmonsters music = "MUS_E1M9" } @@ -453,7 +454,7 @@ map E4M8 lookup "HHUSTR_E4M8" cluster = 4 nointermission ironlichspecial - specialaction_lowerfloor + specialaction_lowerfloortohighest specialaction_killmonsters music = "MUS_E1M8" } @@ -541,7 +542,7 @@ map E5M8 lookup "HHUSTR_E5M8" nointermission minotaurspecial specialaction_killmonsters - specialaction_lowerfloor + specialaction_lowerfloortohighest music = "MUS_E2M8" } diff --git a/wadsrc/static/mapinfo/hexen.txt b/wadsrc/static/mapinfo/hexen.txt index cdababe39..16ebcf5b4 100644 --- a/wadsrc/static/mapinfo/hexen.txt +++ b/wadsrc/static/mapinfo/hexen.txt @@ -26,7 +26,7 @@ gameinfo defaultbloodcolor = "68 00 00" defaultbloodparticlecolor = "ff 00 00" backpacktype = "BagOfHolding" // Hexen doesn't have a backpack so use Heretic's. - statusbar = "" + statusbar = "sbarinfo/hexen.txt" intermissionmusic = "hub" intermissioncounter = false weaponslot = 1, "FWeapFist", "CWeapMace", "MWeapWand" @@ -39,6 +39,7 @@ gameinfo defaultrespawntime = 12 defaultdropstyle = 1 player5start = 9100 + pickupcolor = "d6 ba 45" } skill baby diff --git a/wadsrc/static/mapinfo/strife.txt b/wadsrc/static/mapinfo/strife.txt index 7495c860b..e5c9043ee 100644 --- a/wadsrc/static/mapinfo/strife.txt +++ b/wadsrc/static/mapinfo/strife.txt @@ -42,6 +42,7 @@ gameinfo defaultdropstyle = 2 endoom = "ENDSTRF" player5start = 5 + pickupcolor = "d6 ba 45" } skill baby @@ -52,6 +53,7 @@ skill baby EasyBossBrain SpawnFilter = Baby PicName = "M_JKILL" + Name = "$SSKILL_BABY" Key = "t" } @@ -59,6 +61,7 @@ skill easy { SpawnFilter = Easy PicName = "M_ROUGH" + Name = "$SSKILL_EASY" Key = "r" } @@ -66,6 +69,7 @@ skill normal { SpawnFilter = Normal PicName = "M_HURT" + Name = "$SSKILL_NORMAL" Key = "v" DefaultSkill } @@ -74,6 +78,7 @@ skill hard { SpawnFilter = Hard PicName = "M_ULTRA" + Name = "$SSKILL_HARD" Key = "e" } @@ -85,6 +90,7 @@ skill nightmare RespawnTime = 16 SpawnFilter = Nightmare PicName = "M_NMARE" + Name = "$SSKILL_NIGHTMARE" Key = "b" } diff --git a/wadsrc/static/sbarinfo/doom.txt b/wadsrc/static/sbarinfo/doom.txt index cb7f75cb6..d80d0390b 100644 --- a/wadsrc/static/sbarinfo/doom.txt +++ b/wadsrc/static/sbarinfo/doom.txt @@ -38,8 +38,7 @@ statusbar fullscreen, fullscreenoffsets // ZDoom HUD drawselectedinventory centerbottom, drawshadow, alwaysshowcounter, HUDFONT_DOOM, -14, -39, -26, -56, untranslated; } } - //no secondary ammo - usessecondaryammo not + else //no secondary ammo { inventorybarnotvisible { @@ -47,9 +46,7 @@ statusbar fullscreen, fullscreenoffsets // ZDoom HUD } } } - - // no ammo but inventory - usesammo not + else // no ammo but inventory { inventorybarnotvisible { @@ -61,7 +58,7 @@ statusbar fullscreen, fullscreenoffsets // ZDoom HUD { drawnumber 2147483647, HUDFONT_DOOM, untranslated, frags, drawshadow, -3, 1; } - gamemode singleplayer, cooperative, teamgame + else { // let's hope no mod ever uses 100 keys... drawkeybar 100, vertical, reverserows, auto, -10, 2, 0, 3, auto; diff --git a/wadsrc/static/sbarinfo/heretic.txt b/wadsrc/static/sbarinfo/heretic.txt new file mode 100755 index 000000000..c5b22f6b3 --- /dev/null +++ b/wadsrc/static/sbarinfo/heretic.txt @@ -0,0 +1,123 @@ +/******************************************************************************* + * DEFAULT HERETIC STATUS BAR + ******************************************************************************* + * If you wish to include this file into a custom status bar please use the + * following command: + * + * base Heretic; + * + * Using #include "sbarinfo/heretic.txt" will not be supported. + ******************************************************************************/ + +height 42; +monospacefonts true, "0", center; + +statusbar fullscreen, fullscreenoffsets +{ + //health + drawimage "PTN1A0", 48, -3, centerbottom; + drawnumber 2147483647, BIGFONT, untranslated, health, drawshadow, interpolate(8), 41, -21, 1; + + //armor + drawimage armoricon, 56, -24, centerbottom; + drawnumber 2147483647, BIGFONT, untranslated, armor, drawshadow, whennotzero, 41, -43, 1; + + //frags/keys + gamemode deathmatch + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, frags, drawshadow, 70, -16, 1; + else + drawkeybar 100, vertical, reverse, 8, 54, -7, 0, 3, auto; + + //ammo + drawimage ammoicon1, -14, -22, centerbottom; + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, ammo1, drawshadow(1, 1), -3, -15, 1; + //secondary ammo + usessecondaryammo + { + drawimage ammoicon2, -14, -63, centerbottom; + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, ammo2, drawshadow(1, 1), -3, -56, 1; + } + + inventorybarnotvisible + { + drawselectedinventory alternateonempty, artiflash, INDEXFONT_RAVEN, -61, -31, -34, -9, untranslated + { + } + else + { + alpha 0.6 + drawimage "ARTIBOX", -61, -31; + } + } +} + +statusbar normal +{ + drawimage "BARBACK", 0, 158; + drawimage "LTFCTOP", 0, 148; + drawimage "RTFCTOP", 290, 148; + + //god mode + drawswitchableimage invulnerable, "GOD1", "nullimage", 16, 167; + drawswitchableimage invulnerable, "GOD2", "nullimage", 287, 167; + + //health + drawimage "CHAINBAC", 0, 190; + gamemode singleplayer + drawgem wiggle, interpolate(8), "CHAIN", "LIFEGEM2", 15, 25, 16, 2, 191; + else + drawgem wiggle, interpolate(8), translatable, "CHAIN", "LIFEGEM2", 15, 25, 16, 2, 191; + drawimage "LTFACE", 0, 190; + drawimage "RTFACE", 276, 190; + drawshader 16, 10, horizontal, 19, 190; + drawshader 16, 10, horizontal, reverse, 278, 190; + + //statbar + gamemode singleplayer, cooperative + { + drawimage "LIFEBAR", 34, 160; + drawimage "ARMCLEAR", 57, 171; + drawnumber 3, HUDFONT_RAVEN, untranslated, health, interpolate(8), 87, 170, 1; + } + else + { + drawimage "STATBAR", 34, 160; + drawimage "ARMCLEAR", 57, 171; + drawnumber 3, HUDFONT_RAVEN, untranslated, frags, 87, 170, 1; + } + drawimage "ARMCLEAR", 224, 171; + drawnumber 3, HUDFONT_RAVEN, untranslated, armor, 254, 170, 1; + + //ammo + usessecondaryammo not + { + drawnumber 3, HUDFONT_RAVEN, untranslated, ammo1, 135, 162, 1; + drawimage ammoicon1, 123, 180, center; + } + else + { + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo1, 137, 165; + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo2, 137, 177; + drawimage ammoicon1, 115, 169, center; + drawimage ammoicon2, 115, 180, center; + } + + //keys + drawswitchableimage keyslot 3, "nullimage", "YKEYICON", 153, 164; + drawswitchableimage keyslot 1, "nullimage", "GKEYICON", 153, 172; + drawswitchableimage keyslot 2, "nullimage", "BKEYICON", 153, 180; + + //inventory box + drawselectedinventory artiflash, INDEXFONT_RAVEN, 179, 160, 209, 182, untranslated; +} + +statusbar inventory +{ + drawimage "INVBAR", 34, 160; + drawinventorybar Heretic, noartibox, 7, INDEXFONT_RAVEN, 50, 160, 77, 182, untranslated; +} + +statusbar inventoryfullscreen, fullscreenoffsets // ZDoom HUD overlay. +{ + drawinventorybar Heretic, translucent, 7, INDEXFONT_RAVEN, -106+center, -31, -78+center, -9, untranslated; +} diff --git a/wadsrc/static/sbarinfo/hexen.txt b/wadsrc/static/sbarinfo/hexen.txt new file mode 100755 index 000000000..f1c294525 --- /dev/null +++ b/wadsrc/static/sbarinfo/hexen.txt @@ -0,0 +1,259 @@ +/******************************************************************************* + * DEFAULT HEXEN STATUS BAR + ******************************************************************************* + * If you wish to include this file into a custom status bar please use the + * following command: + * + * base Hexen; + * + * Using #include "sbarinfo/hexen.txt" will not be supported. + ******************************************************************************/ + +height 38; +monospacefonts true, "0", center; + +statusbar fullscreen, fullscreenoffsets +{ + //health + drawnumber 2147483647, BIGFONT, untranslated, health, drawshadow, interpolate(8), 40, -20, 1; + + //frags + gamemode deathmatch + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, frags, drawshadow(1, 1), 70, -15, 1; + + inventorybarnotvisible + { + drawselectedinventory alternateonempty, artiflash, INDEXFONT_RAVEN, -82, -31, -52, -8, untranslated + { + } + else + { + alpha 0.6 + drawimage "ARTIBOX", -80, -30; + } + } + + // Mana + weaponammo Mana1 && Mana2 + { + weaponammo Mana1 + drawimage "MANABRT1", -17, -30; + else + drawimage "MANADIM1", -17, -30; + weaponammo Mana2 + drawimage "MANABRT2", -17, -15; + else + drawimage "MANADIM2", -17, -15; + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, ammo Mana1, drawshadow(1, 1), -21, -30, 1; + drawnumber 2147483647, HUDFONT_RAVEN, untranslated, ammo Mana2, drawshadow(1, 1), -21, -15, 1; + } +} + +statusbar Normal +{ + drawimage "H2BAR", 0, 135; + drawimage "STATBAR", 38, 162; + + drawselectedinventory artiflash, INDEXFONT_RAVEN, 143, 163, 174, 184, untranslated; + + gamemode deathmatch, teamgame + { + drawimage "KILLS", 38, 163; + drawnumber 3, HUDFONT_RAVEN, untranslated, frags, 58, 163, 1; + } + else + { + drawimage "ARMCLS", 41, 178; + drawnumber 3, HUDFONT_RAVEN, untranslated, health, interpolate(6), 65, 176, 1, red, 25; + } + + //mana bars + weaponammo Mana1 && Mana2 + { + weaponammo Mana1 + { + drawimage "MANABRT1", 77, 164; + drawbar "MANAVL1", "nullimage", ammo Mana1, vertical, 94, 164, 1; + } + else + { + drawimage "MANADIM1", 77, 164; + drawbar "MANAVL1D", "nullimage", ammo Mana1, vertical, 94, 164, 1; + } + weaponammo Mana2 + { + drawimage "MANABRT2", 110, 164; + drawbar "MANAVL2", "nullimage", ammo Mana2, vertical, 102, 164, 1; + } + else + { + drawimage "MANADIM2", 110, 164; + drawbar "MANAVL2D", "nullimage", ammo Mana2, vertical, 102, 164, 1; + } + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo Mana1, 91, 181; + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo Mana2, 123, 181; + } + else //Weapon doesn't use ammo draw an alternative + { + drawimage "HAMOBACK", 77, 164; + usessecondaryammo + { + drawimage ammoicon1, 89, 172, center; + drawimage ammoicon2, 113, 172, center; + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo1, 98, 182; + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo2, 122, 182; + } + else + { + drawimage ammoicon1, 100, 172, center; + drawnumber 3, INDEXFONT_RAVEN, untranslated, ammo1, 109, 182; + } + } + + //armor + drawimage "ARMCLS", 255, 178; + drawnumber 2, HUDFONT_RAVEN, untranslated, armorclass, 275, 176, 1; + + playerclass Cleric + { + drawimage "WPSLOT1", 190, 162; + hasweaponpiece CWeapWraithverge, 1 + { + drawimage "WPIECEC1", 190, 162; + } + hasweaponpiece CWeapWraithverge, 2 + { + drawimage "WPIECEC2", 212, 162; + } + hasweaponpiece CWeapWraithverge, 3 + { + drawimage "WPIECEC3", 225, 162; + } + hasweaponpiece CWeapWraithverge, 1 + { + hasweaponpiece CWeapWraithverge, 2 + { + hasweaponpiece CWeapWraithverge, 3 + { + drawimage "WPFULL1", 190, 162; + } + } + } + + gamemode singleplayer + drawgem interpolate(6), "CHAIN2", "LIFEGMC2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN2", "LIFEGMC2", -23, 49, 15, 30, 193; + } + else playerclass Mage + { + drawimage "WPSLOT2", 190, 162; + hasweaponpiece MWeapBloodscourge, 1 + { + drawimage "WPIECEM1", 190, 162; + } + hasweaponpiece MWeapBloodscourge, 2 + { + drawimage "WPIECEM2", 205, 162; + } + hasweaponpiece MWeapBloodscourge, 3 + { + drawimage "WPIECEM3", 224, 162; + } + hasweaponpiece MWeapBloodscourge, 1 + { + hasweaponpiece MWeapBloodscourge, 2 + { + hasweaponpiece MWeapBloodscourge, 3 + { + drawimage "WPFULL2", 190, 162; + } + } + } + + gamemode singleplayer + drawgem interpolate(6), "CHAIN3", "LIFEGMM2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN3", "LIFEGMM2", -23, 49, 15, 30, 193; + } + else + { + drawimage "WPSLOT0", 190, 162; + hasweaponpiece FWeapQuietus, 1 + { + drawimage "WPIECEF1", 190, 162; + } + hasweaponpiece FWeapQuietus, 2 + { + drawimage "WPIECEF2", 225, 162; + } + hasweaponpiece FWeapQuietus, 3 + { + drawimage "WPIECEF3", 234, 162; + } + hasweaponpiece FWeapQuietus, 1 + { + hasweaponpiece FWeapQuietus, 2 + { + hasweaponpiece FWeapQuietus, 3 + { + drawimage "WPFULL0", 190, 162; + } + } + } + + gamemode singleplayer + drawgem interpolate(6), "CHAIN", "LIFEGMF2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN", "LIFEGMF2", -23, 49, 15, 30, 193; + } + drawimage "LFEDGE", 0, 193; + drawimage "RTEDGE", 277, 193; +} + +statusbar Automap +{ + drawimage "H2BAR", 0, 135; + drawimage "KEYBAR", 38, 162; + drawkeybar 5, horizontal, 20, 46, 164; + drawimage hexenarmor armor, "ARMSLOT1", 150, 164; + drawimage hexenarmor shield, "ARMSLOT2", 181, 164; + drawimage hexenarmor helm, "ARMSLOT3", 212, 164; + drawimage hexenarmor amulet, "ARMSLOT4", 243, 164; + + // Also draw the life gem here + playerclass Fighter + { + gamemode singleplayer + drawgem interpolate(6), "CHAIN", "LIFEGMF2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN", "LIFEGMF2", -23, 49, 15, 30, 193; + } + else playerclass Cleric + { + gamemode singleplayer + drawgem interpolate(6), "CHAIN2", "LIFEGMC2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN2", "LIFEGMC2", -23, 49, 15, 30, 193; + } + else playerclass Mage + { + gamemode singleplayer + drawgem interpolate(6), "CHAIN3", "LIFEGMM2", -23, 49, 15, 30, 193; + else + drawgem translatable, interpolate(6), "CHAIN3", "LIFEGMM2", -23, 49, 15, 30, 193; + } + drawimage "LFEDGE", 0, 193; + drawimage "RTEDGE", 277, 193; +} + +statusbar inventory +{ + drawimage "INVBAR", 38, 162; + drawinventorybar HexenStrict, noartibox, 7, INDEXFONT_RAVEN, 52, 164, 80, 185, untranslated; +} + +statusbar inventoryfullscreen, fullscreenoffsets // ZDoom HUD overlay. +{ + drawinventorybar HexenStrict, translucent, 7, INDEXFONT_RAVEN, -106+center, -31, -78+center, -10, untranslated; +} diff --git a/wadsrc/static/terrain.txt b/wadsrc/static/terrain.txt index 9a38a3d97..f1bfa6f4a 100644 --- a/wadsrc/static/terrain.txt +++ b/wadsrc/static/terrain.txt @@ -54,6 +54,58 @@ splash WaterSound noalert } +ifheretic + +splash Water +{ + smallclass WaterSplashBase + smallclip 12 + smallsound world/drip + + baseclass WaterSplashBase + chunkclass WaterSplash + chunkxvelshift 8 + chunkyvelshift 8 + chunkzvelshift 8 + chunkbasezvel 2 + sound world/watersplash + noalert +} + +splash Sludge +{ + smallclass SludgeSplash + smallclip 12 + smallsound world/sludgegloop + + baseclass SludgeSplash + chunkclass SludgeChunk + chunkxvelshift 8 + chunkyvelshift 8 + chunkzvelshift 8 + chunkbasezvel 1 + sound world/sludgegloop + noalert +} + +splash Lava +{ + smallclass LavaSplash + smallclip 12 + smallsound world/lavasizzle + + baseclass LavaSplash + chunkclass LavaSmoke + chunkxvelshift -1 + chunkyvelshift -1 + chunkzvelshift 7 + chunkbasezvel 1 + sound world/lavasizzle + noalert +} + +endif + // Terrain types ----------------------------------------------------------- terrain Water diff --git a/wadsrc/static/xlat/base.txt b/wadsrc/static/xlat/base.txt index a7aa69f1c..45b23e52f 100644 --- a/wadsrc/static/xlat/base.txt +++ b/wadsrc/static/xlat/base.txt @@ -321,6 +321,10 @@ include "xlat/defines.i" 350 = 0, Transfer_Heights (tag, 2) // Just fake the floor 351 = 0, Transfer_Heights (tag, 6) // Just fake the floor and clip it too +352 = 0, Sector_CopyScroller(tag, 1) // copy ceiling scroller +353 = 0, Sector_CopyScroller(tag, 2) // copy floor scroller +354 = 0, Sector_CopyScroller(tag, 6) // copy carrying floor scroller + /****** EDGE linetypes ******/ 400 = 0, ExtraFloor_LightOnly (tag, 0) // thick diff --git a/wadsrc/static/xlat/defines.i b/wadsrc/static/xlat/defines.i index 648679894..60d582f70 100644 --- a/wadsrc/static/xlat/defines.i +++ b/wadsrc/static/xlat/defines.i @@ -135,6 +135,7 @@ enum dDamage_LavaWimpy = 82, dDamage_LavaHefty = 83, dScroll_EastLavaDamage = 84, + hDamage_Sludge = 85, Sector_Outside = 87, // And here are some for Strife @@ -144,8 +145,8 @@ enum sDamage_SuperHellslime = 116, Scroll_StrifeCurrent = 118, - // Caverns of Darkness healing sector - Sector_Heal = 196, + Sector_Hidden = 195, + Sector_Heal = 196, // Caverns of Darkness healing sector Light_OutdoorLightning = 197, Light_IndoorLightning1 = 198, diff --git a/wadsrc/static/xlat/heretic.txt b/wadsrc/static/xlat/heretic.txt index da4d103a5..8a2b696bc 100644 --- a/wadsrc/static/xlat/heretic.txt +++ b/wadsrc/static/xlat/heretic.txt @@ -18,7 +18,7 @@ sector 2 = dLight_StrobeFast; sector 3 = dLight_StrobeSlow; sector 4 = dScroll_EastLavaDamage; sector 5 = dDamage_LavaWimpy; -sector 7 = dDamage_Nukage; +sector 7 = hDamage_Sludge; sector 8 = dLight_Glow; sector 9 = SECRET_MASK nobitmask; sector 10 = dSector_DoorCloseIn30; diff --git a/zdoom.vcproj b/zdoom.vcproj index 91e381c58..4dbee7678 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -57,7 +57,7 @@ OmitFramePointers="true" WholeProgramOptimization="false" AdditionalIncludeDirectories="src\zscript;src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;"jpeg-6b";game-music-emu\gme;gdtoa;bzip2;lzma\C" - PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY" + PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY,BACKPATCH,HAVE_FLUIDSYNTH,DYN_FLUIDSYNTH" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -287,7 +287,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="src\zscript;src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;"jpeg-6b";game-music-emu\gme;gdtoa;bzip2;lzma\C" - PreprocessorDefinitions="WIN32,_DEBUG,_WIN32,_WINDOWS,_CRTDBG_MAP_ALLOC,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY" + PreprocessorDefinitions="WIN32,_DEBUG,_WIN32,_WINDOWS,_CRTDBG_MAP_ALLOC,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY,BACKPATCH,HAVE_FLUIDSYNTH,DYN_FLUIDSYNTH" MinimalRebuild="true" RuntimeLibrary="1" EnableFunctionLevelLinking="true" @@ -318,7 +318,7 @@ - - @@ -808,6 +804,10 @@ RelativePath=".\src\p_floor.cpp" > + + @@ -904,6 +904,10 @@ RelativePath=".\src\p_udmf.cpp" > + + @@ -1493,6 +1497,10 @@ RelativePath=".\src\parsecontext.h" > + + @@ -2291,6 +2299,14 @@ Name="VCCLCompilerTool" /> + + + + + + - - - - + + + + + +