diff --git a/CMakeLists.txt b/CMakeLists.txt index 78264488de..b541a9c20d 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 0000000000..7d5cb6a8ed --- /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 a65aec9184..c285c50b98 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 e0ec439254..fe6897d9ff 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -161,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 @@ -178,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. } @@ -263,6 +268,15 @@ 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 0000000000..fc955f02d5 --- /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 0000000000..2173542922 --- /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 31b3e14147..e34d8ea3dc 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" ) @@ -415,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 @@ -424,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 @@ -473,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 ) @@ -508,12 +579,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 @@ -567,19 +649,17 @@ add_executable( zdoom WIN32 hu_scores.cpp i_net.cpp info.cpp + keysections.cpp lumpconfigfile.cpp m_alloc.cpp m_argv.cpp m_bbox.cpp m_cheat.cpp m_joy.cpp - m_menu.cpp m_misc.cpp - m_options.cpp m_png.cpp m_random.cpp md5.cpp - mus2midi.cpp name.cpp nodebuild.cpp nodebuild_classify_nosse2.cpp @@ -597,6 +677,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 @@ -621,6 +702,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 @@ -705,6 +787,19 @@ add_executable( zdoom WIN32 g_shared/sbar_mugshot.cpp g_shared/shared_hud.cpp g_shared/shared_sbar.cpp + menu/colorpickermenu.cpp + menu/joystickmenu.cpp + menu/listmenu.cpp + menu/loadsavemenu.cpp + menu/menu.cpp + menu/menudef.cpp + menu/menuinput.cpp + menu/messagebox.cpp + menu/optionmenu.cpp + menu/playerdisplay.cpp + menu/playermenu.cpp + menu/readthis.cpp + menu/videomenu.cpp oplsynth/fmopl.cpp oplsynth/mlopl.cpp oplsynth/mlopl_io.cpp @@ -728,13 +823,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 @@ -817,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 de2d86bfbd..c735c7a9d6 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -57,7 +57,7 @@ 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) @@ -77,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) @@ -111,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 6e0c625f14..2a8921b56e 100644 --- a/src/actor.h +++ b/src/actor.h @@ -40,6 +40,7 @@ #include "r_blend.h" #include "s_sound.h" +struct subsector_t; // // NOTES: AActor // @@ -320,6 +321,8 @@ enum 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 --- @@ -505,7 +508,6 @@ enum AMETA_BloodColor, // colorized blood AMETA_GibHealth, // negative health below which this monster dies an extreme death AMETA_WoundHealth, // health needed to enter wound state - AMETA_PoisonDamage, // Amount of poison damage AMETA_FastSpeed, // Speed in fast mode AMETA_RDFactor, // Radius damage factor AMETA_CameraHeight, // Height of camera when used as such @@ -721,19 +723,26 @@ public: const PClass *GetBloodType(int type = 0) const { + const PClass *bloodcls; if (type == 0) { - return PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType, NAME_Blood)); + bloodcls = PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType, NAME_Blood)); } else if (type == 1) { - return PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType2, NAME_BloodSplatter)); + bloodcls = PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType2, NAME_BloodSplatter)); } else if (type == 2) { - return PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType3, NAME_AxeBlood)); + bloodcls = PClass::FindClass((ENamedName)GetClass()->Meta.GetMetaInt(AMETA_BloodType3, NAME_AxeBlood)); } else return NULL; + + if (bloodcls != NULL) + { + bloodcls = bloodcls->GetReplacement(); + } + return bloodcls; } // Calculate amount of missile damage @@ -765,6 +774,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. @@ -843,6 +853,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 @@ -882,8 +901,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; diff --git a/src/am_map.cpp b/src/am_map.cpp index 36d31a00e3..73a7f8c989 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,12 +175,27 @@ 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) @@ -216,21 +236,6 @@ CUSTOM_CVAR (Int, am_showalllines, -1, 0) // This is a cheat so don't save it. } -// 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 // player radius for automap checking @@ -241,10 +246,10 @@ CUSTOM_CVAR (Int, am_showalllines, -1, 0) // This is a cheat so don't save it. #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*/) @@ -292,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 } }, // -----> @@ -329,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 @@ -348,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)) @@ -383,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) @@ -432,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 @@ -741,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); @@ -813,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); @@ -836,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); @@ -1122,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; } @@ -1254,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); @@ -1264,6 +1287,13 @@ void AM_changeWindowScale () AM_maxOutWindowScale(); } +CCMD(am_zoom) +{ + if (argv.argc() >= 2) + { + am_zoomdir = (float)atof(argv[1]); + } +} //============================================================================= // @@ -1298,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 @@ -1325,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 @@ -1586,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 + ); + } +} + //============================================================================= // // @@ -1614,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. @@ -1746,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); } //============================================================================= @@ -1964,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) { @@ -2093,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, @@ -2152,6 +2390,9 @@ void AM_Drawer () } AM_activateNewScale(); + if (am_textured && hasglnodes && textured && !viewactive) + AM_drawSubsectors(); + if (grid) AM_drawGrid(GridColor); @@ -2166,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 71740b9a7d..20da6b96be 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 196e8317aa..d68fba116f 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 e4c4775986..cbcd9f4f15 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 7f6ed82da4..e1f166878a 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 49444419c6..3161ff3689 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 8a9b52e48f..867d11c759 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/basicinlines.h b/src/basicinlines.h index fa6ce822b3..80f861c22d 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 d96b5a7426..cceb8bd352 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 (const 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 (const 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 2b46118cf3..4c9edb2bb4 100644 --- a/src/c_bind.h +++ b/src/c_bind.h @@ -34,25 +34,80 @@ #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 (const char *cmd, int *first, int *second); + void UnbindACommand (const 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; +extern FKeyBindings MenuBindings; + + +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[]; +struct FKeyAction +{ + FString mTitle; + FString mAction; +}; + +struct FKeySection +{ + FString mTitle; + FString mSection; + TArray mActions; +}; +extern TArray KeySections; + #endif //__C_BINDINGS_H__ diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 6d21a79225..f3cb409587 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -936,3 +936,4 @@ CCMD(currentpos) FIXED2FLOAT(mo->x), FIXED2FLOAT(mo->y), FIXED2FLOAT(mo->z), mo->angle/float(ANGLE_1), FIXED2FLOAT(mo->floorz), mo->Sector->sectornum, mo->Sector->lightlevel); } + diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index d4e01128d9..7b53876354 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -1119,7 +1119,7 @@ void FFlagCVar::DoSet (UCVarValue value, ECVarType type) // exec scripts because all flags will base their changes off of the value of // the "master" cvar at the time the script was run, overriding any changes // another flag might have made to the same cvar earlier in the script. - if ((ValueVar.Flags & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) + if ((ValueVar.GetFlags() & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) { if (netgame && !players[consoleplayer].settings_controller) { @@ -1139,6 +1139,114 @@ void FFlagCVar::DoSet (UCVarValue value, ECVarType type) } } +// +// Mask cvar implementation +// +// Similar to FFlagCVar but can have multiple bits +// + +FMaskCVar::FMaskCVar (const char *name, FIntCVar &realvar, DWORD bitval) +: FBaseCVar (name, 0, NULL), +ValueVar (realvar), +BitVal (bitval) +{ + int bit; + + Flags &= ~CVAR_ISDEFAULT; + + assert (bitval != 0); + + bit = 0; + while ((bitval & 1) == 0) + { + ++bit; + bitval >>= 1; + } + BitNum = bit; +} + +ECVarType FMaskCVar::GetRealType () const +{ + return CVAR_Dummy; +} + +UCVarValue FMaskCVar::GetGenericRep (ECVarType type) const +{ + return FromInt ((ValueVar & BitVal) >> BitNum, type); +} + +UCVarValue FMaskCVar::GetFavoriteRep (ECVarType *type) const +{ + UCVarValue ret; + *type = CVAR_Int; + ret.Int = (ValueVar & BitVal) >> BitNum; + return ret; +} + +UCVarValue FMaskCVar::GetGenericRepDefault (ECVarType type) const +{ + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + return FromInt ((def.Int & BitVal) >> BitNum, type); +} + +UCVarValue FMaskCVar::GetFavoriteRepDefault (ECVarType *type) const +{ + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + def.Int = (def.Int & BitVal) >> BitNum; + *type = CVAR_Int; + return def; +} + +void FMaskCVar::SetGenericRepDefault (UCVarValue value, ECVarType type) +{ + int val = ToInt(value, type) << BitNum; + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + def.Int &= ~BitVal; + def.Int |= val; + ValueVar.SetGenericRepDefault (def, CVAR_Int); +} + +void FMaskCVar::DoSet (UCVarValue value, ECVarType type) +{ + int val = ToInt(value, type) << BitNum; + + // Server cvars that get changed by this need to use a special message, because + // changes are not processed until the next net update. This is a problem with + // exec scripts because all flags will base their changes off of the value of + // the "master" cvar at the time the script was run, overriding any changes + // another flag might have made to the same cvar earlier in the script. + if ((ValueVar.GetFlags() & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) + { + if (netgame && !players[consoleplayer].settings_controller) + { + Printf ("Only setting controllers can change %s\n", Name); + return; + } + // Ugh... + for(int i = 0; i < 32; i++) + { + if (BitVal & (1<> BitNum; } + inline int operator *() const { return (ValueVar & BitVal) >> BitNum; } + +protected: + virtual void DoSet (UCVarValue value, ECVarType type); + + FIntCVar &ValueVar; + uint32 BitVal; + int BitNum; +}; + class FGUIDCVar : public FBaseCVar { public: diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 2fbafd2a95..413b8d64c4 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 a19352d32b..d93709c90a 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -146,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, @@ -154,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 dd32cdb55d..36a9007094 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 fe41204ec8..7c29644d55 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 b93cb68f23..ab1c98ca7d 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -118,6 +118,7 @@ static FCompatOption Options[] = { "spritesort", COMPATF_SPRITESORT, 0 }, { "hitscan", COMPATF_HITSCAN, 0 }, { "lightlevel", COMPATF_LIGHT, 0 }, + { "polyobj", COMPATF_POLYOBJ, 0 }, { NULL, 0, 0 } }; @@ -185,7 +186,7 @@ void ParseCompatibility() } while (!sc.Compare("{")); flags.CompatFlags = 0; flags.BCompatFlags = 0; - flags.ExtCommandIndex = -1; + flags.ExtCommandIndex = ~0u; while (sc.GetString()) { if ((i = sc.MatchString(&Options[0].Name, sizeof(*Options))) >= 0) @@ -195,7 +196,7 @@ void ParseCompatibility() } else if (sc.Compare("clearlineflags")) { - if (flags.ExtCommandIndex == -1) flags.ExtCommandIndex = CompatParams.Size(); + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); CompatParams.Push(CP_CLEARFLAGS); sc.MustGetNumber(); CompatParams.Push(sc.Number); @@ -204,7 +205,7 @@ void ParseCompatibility() } else if (sc.Compare("setlineflags")) { - if (flags.ExtCommandIndex == -1) flags.ExtCommandIndex = CompatParams.Size(); + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); CompatParams.Push(CP_SETFLAGS); sc.MustGetNumber(); CompatParams.Push(sc.Number); @@ -213,7 +214,7 @@ void ParseCompatibility() } else if (sc.Compare("setlinespecial")) { - if (flags.ExtCommandIndex == -1) flags.ExtCommandIndex = CompatParams.Size(); + if (flags.ExtCommandIndex == ~0u) flags.ExtCommandIndex = CompatParams.Size(); CompatParams.Push(CP_SETSPECIAL); sc.MustGetNumber(); CompatParams.Push(sc.Number); @@ -232,7 +233,7 @@ void ParseCompatibility() break; } } - if (flags.ExtCommandIndex != -1) + if (flags.ExtCommandIndex != ~0u) { CompatParams.Push(CP_END); } @@ -266,6 +267,19 @@ void CheckCompatibility(MapData *map) 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); diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index 2578f1a609..97c8296a4d 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -952,7 +952,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; @@ -2109,13 +2109,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_gui.h b/src/d_gui.h index 5e9854f7cd..b88041771b 100644 --- a/src/d_gui.h +++ b/src/d_gui.h @@ -43,27 +43,34 @@ enum EGUIEvent EV_GUI_KeyRepeat, // same EV_GUI_KeyUp, // same EV_GUI_Char, // data1: translated character (for user text input), data2: alt down? - EV_GUI_MouseMove, - EV_GUI_LButtonDown, - EV_GUI_LButtonUp, - EV_GUI_LButtonDblClick, - EV_GUI_MButtonDown, - EV_GUI_MButtonUp, - EV_GUI_MButtonDblClick, - EV_GUI_RButtonDown, - EV_GUI_RButtonUp, - EV_GUI_RButtonDblClick, - EV_GUI_WheelUp, // data3: shift/ctrl/alt - EV_GUI_WheelDown, // " - EV_GUI_WheelRight, // " - EV_GUI_WheelLeft, // " + EV_GUI_FirstMouseEvent, + EV_GUI_MouseMove, + EV_GUI_LButtonDown, + EV_GUI_LButtonUp, + EV_GUI_LButtonDblClick, + EV_GUI_MButtonDown, + EV_GUI_MButtonUp, + EV_GUI_MButtonDblClick, + EV_GUI_RButtonDown, + EV_GUI_RButtonUp, + EV_GUI_RButtonDblClick, + EV_GUI_WheelUp, // data3: shift/ctrl/alt + EV_GUI_WheelDown, // " + EV_GUI_WheelRight, // " + EV_GUI_WheelLeft, // " + EV_GUI_BackButtonDown, + EV_GUI_BackButtonUp, + EV_GUI_FwdButtonDown, + EV_GUI_FwdButtonUp, + EV_GUI_LastMouseEvent, }; enum GUIKeyModifiers { GKM_SHIFT = 1, GKM_CTRL = 2, - GKM_ALT = 4 + GKM_ALT = 4, + GKM_LBUTTON = 8 }; // Special codes for some GUI keys, including a few real ASCII codes. @@ -100,7 +107,7 @@ enum ESpecialGUIKeys GK_ESCAPE = 27, // ASCII GK_FREE1 = 28, GK_FREE2 = 29, - GK_FREE3 = 30, + GK_BACK = 30, // browser back key GK_CESCAPE = 31 // color escape }; diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index e5939e1a7e..499d7cbd7a 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -60,8 +60,8 @@ 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 | 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 }, @@ -79,7 +79,7 @@ const IWADInfo IWADInfos[NUM_IWAD_TYPES] = { "FreeDM", "FreeDM", MAKERGB(50,84,67), MAKERGB(198,220,209), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, { "Blasphemer", "Blasphemer",MAKERGB(115,0,0), MAKERGB(0,0,0), GAME_Heretic, "mapinfo/heretic.txt" }, { "Chex(R) Quest", "Chex1", MAKERGB(255,255,0), MAKERGB(0,192,0), GAME_Chex, "mapinfo/chex.txt" }, - { "Chex(R) Quest 3", "Chex3", MAKERGB(255,255,0), MAKERGB(0,192,0), GAME_Chex, "mapinfo/chex3.txt" }, + { "Chex(R) Quest 3", "Chex3", MAKERGB(255,255,0), MAKERGB(0,192,0), GAME_Chex, "mapinfo/chex3.txt", GI_NOTEXTCOLOR }, { "Action Doom 2: Urban Brawl", "UrbanBrawl",MAKERGB(168,168,0), MAKERGB(168,0,0), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, { "Harmony", "Harmony", MAKERGB(110,180,230), MAKERGB(69,79,126), GAME_Doom, "mapinfo/doom2.txt", GI_MAPxx }, //{ "ZDoom Engine", NULL, MAKERGB(168,0,0), MAKERGB(168,168,168) }, diff --git a/src/d_main.cpp b/src/d_main.cpp index 3b1e841a51..9dc18f473d 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 @@ -61,7 +61,7 @@ #include "f_wipe.h" #include "m_argv.h" #include "m_misc.h" -#include "m_menu.h" +#include "menu/menu.h" #include "c_console.h" #include "c_dispatch.h" #include "i_system.h" @@ -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) @@ -200,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; @@ -390,6 +392,18 @@ CVAR (Flag, sv_nofov, dmflags, DF_NO_FOV); CVAR (Flag, sv_noweaponspawn, dmflags, DF_NO_COOP_WEAPON_SPAWN); CVAR (Flag, sv_nocrouch, dmflags, DF_NO_CROUCH); CVAR (Flag, sv_allowcrouch, dmflags, DF_YES_CROUCH); +CVAR (Flag, sv_cooploseinventory, dmflags, DF_COOP_LOSE_INVENTORY); +CVAR (Flag, sv_cooplosekeys, dmflags, DF_COOP_LOSE_KEYS); +CVAR (Flag, sv_cooploseweapons, dmflags, DF_COOP_LOSE_WEAPONS); +CVAR (Flag, sv_cooplosearmor, dmflags, DF_COOP_LOSE_ARMOR); +CVAR (Flag, sv_cooplosepowerups, dmflags, DF_COOP_LOSE_POWERUPS); +CVAR (Flag, sv_cooploseammo, dmflags, DF_COOP_LOSE_AMMO); +CVAR (Flag, sv_coophalveammo, dmflags, DF_COOP_HALVE_AMMO); + +// Some (hopefully cleaner) interface to these settings. +CVAR (Mask, sv_crouch, dmflags, DF_NO_CROUCH|DF_YES_CROUCH); +CVAR (Mask, sv_jump, dmflags, DF_NO_JUMP|DF_YES_JUMP); +CVAR (Mask, sv_fallingdamage, dmflags, DF_FORCE_FALLINGHX|DF_FORCE_FALLINGZD); //========================================================================== // @@ -460,7 +474,6 @@ 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); - //========================================================================== // // CVAR compatflags @@ -480,7 +493,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) @@ -559,6 +577,7 @@ 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); //========================================================================== // @@ -880,6 +899,8 @@ void D_DoomLoop () // Clamp the timer to TICRATE until the playloop has been entered. r_NoInterpolate = true; + I_SetCursor(TexMan["cursor"]); + for (;;) { try @@ -1671,6 +1692,10 @@ static FString ParseGameInfo(TArray &pwads, const char *fn, const char } while (sc.CheckToken(',')); } + else if (!nextKey.CompareNoCase("NOSPRITERENAME")) + { + nospriterename = true; + } } return iwad; } @@ -2065,6 +2090,7 @@ void D_DoomMain (void) // [GRB] Initialize player class list SetupPlayerClasses (); + // [RH] Load custom key and weapon settings from WADs D_LoadWadSettings (); @@ -2134,7 +2160,7 @@ void D_DoomMain (void) bglobal.spawn_tries = 0; bglobal.wanted_botnum = bglobal.getspawned.Size(); - Printf ("M_Init: Init miscellaneous info.\n"); + Printf ("M_Init: Init menus.\n"); M_Init (); Printf ("P_Init: Init Playloop state.\n"); @@ -2287,6 +2313,14 @@ void FStartupScreen::AppendStatusLine(const char *status) // //========================================================================== +//========================================================================== +// +// STAT fps +// +// Displays statistics about rendering times +// +//========================================================================== + ADD_STAT (fps) { FString out; @@ -2295,6 +2329,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 9979199634..e088997198 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -25,7 +25,7 @@ #include #include "version.h" -#include "m_menu.h" +#include "menu/menu.h" #include "m_random.h" #include "i_system.h" #include "i_video.h" @@ -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. @@ -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; + const PClass *cls = PClass::FindClass(classname); - while ( (actor = iterator.Next ()) ) + if (cls != NULL && cls->ActorInfo != 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_player.h b/src/d_player.h index 28b240c105..2d58e00ee4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -54,6 +54,7 @@ enum APMETA_ColorRange, // skin color range APMETA_InvulMode, APMETA_HealingRadius, + APMETA_Portrait, APMETA_Hexenarmor0, APMETA_Hexenarmor1, APMETA_Hexenarmor2, diff --git a/src/dobject.cpp b/src/dobject.cpp index 0ccdd130b6..3bd815bd60 100644 --- a/src/dobject.cpp +++ b/src/dobject.cpp @@ -188,6 +188,33 @@ const char *FMetaTable::GetMetaString (DWORD id) const return meta != NULL ? meta->Value.String : NULL; } +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::m_Types.Size()); + for (unsigned int i = 0; i < PClass::m_Types.Size(); i++) + { + PClass *cls = PClass::m_Types[i]; + if (cls != NULL && cls->ActorInfo != NULL) + Printf("%s\t%i\t%i\t%s\t%s\n", + cls->TypeName.GetChars(), cls->ActorInfo->DoomEdNum, + cls->ActorInfo->SpawnID, filters[cls->ActorInfo->GameFilter & 31], + cls->Meta.GetMetaString (ACMETA_Lump)); + 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 1c8e618bdd..4314d0488a 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -71,6 +71,8 @@ #include "r_interpolate.h" #include "doomstat.h" #include "m_argv.h" +#include "po_man.h" +#include "menu/menu.h" // MACROS ------------------------------------------------------------------ @@ -297,6 +299,7 @@ static void MarkRoot() Mark(Args); Mark(screen); Mark(StatusBar); + Mark(DMenu::CurrentMenu); DThinker::MarkRoots(); FCanvasTextureInfo::Mark(); Mark(DACSThinker::ActiveThinker); diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index a51e8ad0c5..f232d81f2b 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -68,7 +68,7 @@ void PClass::StaticInit () // MinGW's linker is linking the object files backwards for me now... if (head > tail) { - swap (head, tail); + swapvalues (head, tail); } qsort (head + 1, tail - head - 1, sizeof(REGINFO), cregcmp); @@ -486,6 +486,11 @@ const PClass *PClass::NativeClass() const return cls; } +PClass *PClass::GetReplacement() const +{ + return ActorInfo->GetReplacement()->Class; +} + // Symbol tables ------------------------------------------------------------ PSymbol::~PSymbol() diff --git a/src/dobjtype.h b/src/dobjtype.h index dd35e7e47d..e019299012 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -174,6 +174,7 @@ struct PClass static const PClass *FindClass (ENamedName name) { return FindClass (FName (name)); } static const PClass *FindClass (FName name); const PClass *FindClassTentative (FName name); // not static! + PClass *GetReplacement() const; static TArray m_Types; static TArray m_RuntimeActors; diff --git a/src/doomdata.h b/src/doomdata.h index 8592462611..9c22b22a60 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -340,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 3aa527523b..1108e770f3 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -329,6 +329,7 @@ enum 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 diff --git a/src/doomtype.h b/src/doomtype.h index 7757a3cc6a..31f96d2aba 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 c37d1f4593..a851fdc319 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; diff --git a/src/farchive.h b/src/farchive.h index 9af7d25baa..533b3920eb 100644 --- a/src/farchive.h +++ b/src/farchive.h @@ -290,6 +290,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 5705b938a3..332bddb1fa 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/g_doom/a_doomweaps.cpp b/src/g_doom/a_doomweaps.cpp index 7cd53b49c2..515a3015ee 100644 --- a/src/g_doom/a_doomweaps.cpp +++ b/src/g_doom/a_doomweaps.cpp @@ -100,52 +100,88 @@ 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) { - angle_t angle; + angle_t angle; + angle_t slope; player_t *player; AActor *linetarget; - ACTION_PARAM_START(4); + ACTION_PARAM_START(9); ACTION_PARAM_SOUND(fullsound, 0); ACTION_PARAM_SOUND(hitsound, 1); ACTION_PARAM_INT(damage, 2); ACTION_PARAM_CLASS(pufftype, 3); - ACTION_PARAM_FIXED(Range, 4) - ACTION_PARAM_FIXED(LifeSteal, 5); + ACTION_PARAM_INT(Flags, 4); + ACTION_PARAM_FIXED(Range, 5); + ACTION_PARAM_ANGLE(Spread_XY, 6); + ACTION_PARAM_ANGLE(Spread_Z, 7); + ACTION_PARAM_FIXED(LifeSteal, 8); if (NULL == (player = self->player)) { return; } + if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff); + if (damage == 0) damage = 2; + + if (!(Flags & SF_NORANDOM)) + damage *= (pr_saw()%10+1); + + // use meleerange + 1 so the puff doesn't skip the flash (i.e. plays all states) + if (Range == 0) 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) + if ((weapon != NULL) && !(Flags & SF_NOUSEAMMO) && !(!linetarget && (Flags & SF_NOUSEAMMOMISS))) { if (!weapon->DepleteAmmo (weapon->bAltFire)) return; } - if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff); - if (damage == 0) 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) - if (Range == 0) Range = MELEERANGE+1; - - P_LineAttack (self, angle, Range, - P_AimLineAttack (self, angle, Range, &linetarget), damage, - NAME_None, pufftype); - if (!linetarget) { + if ((Flags & SF_RANDOMLIGHTMISS) && (pr_saw() > 64)) + { + player->extralight = !player->extralight; + } S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM); return; } + 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); diff --git a/src/g_game.cpp b/src/g_game.cpp index d68ead9ef4..caf43cd6f0 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -40,7 +40,7 @@ #include "f_finale.h" #include "m_argv.h" #include "m_misc.h" -#include "m_menu.h" +#include "menu/menu.h" #include "m_random.h" #include "m_crc32.h" #include "i_system.h" @@ -116,18 +116,20 @@ 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; } } +CVAR(Int, nametagcolor, CR_GOLD, CVAR_ARCHIVE) + gameaction_t gameaction; gamestate_t gamestate = GS_STARTUP; @@ -322,11 +324,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, (EColorRange)*nametagcolor, 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, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + } } CCMD (invnext) @@ -354,10 +368,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, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -385,10 +398,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, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -869,7 +881,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) { @@ -887,16 +899,17 @@ bool G_Responder (event_t *ev) stricmp (cmd, "bumpgamma") && stricmp (cmd, "screenshot"))) { - M_StartControlPanel (true, true); + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); return true; } 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; } @@ -908,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) @@ -920,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 @@ -939,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); @@ -2050,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_heretic/a_dsparil.cpp b/src/g_heretic/a_dsparil.cpp index b6a8c6a1ea..aa0bd46ed7 100644 --- a/src/g_heretic/a_dsparil.cpp +++ b/src/g_heretic/a_dsparil.cpp @@ -139,7 +139,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_level.cpp b/src/g_level.cpp index b14959efe7..10ed1f83e9 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -68,7 +68,6 @@ #include "m_png.h" #include "m_random.h" #include "version.h" -#include "m_menu.h" #include "statnums.h" #include "sbarinfo.h" #include "r_translate.h" @@ -78,6 +77,7 @@ #include "d_net.h" #include "d_netinf.h" #include "v_palette.h" +#include "menu/menu.h" #include "gi.h" @@ -99,6 +99,7 @@ void STAT_WRITE(FILE *f); EXTERN_CVAR (Float, sv_gravity) EXTERN_CVAR (Float, sv_aircontrol) EXTERN_CVAR (Int, disableautosave) +EXTERN_CVAR (String, playerclass) #define SNAP_ID MAKE_ID('s','n','A','p') #define DSNP_ID MAKE_ID('d','s','N','p') @@ -230,6 +231,15 @@ void G_DeferedInitNew (const char *mapname, int newskill) gameaction = ga_newgame2; } +void G_DeferedInitNew (FGameStartup *gs) +{ + playerclass = gs->PlayerClass; + d_mapname = AllEpisodes[gs->Episode].mEpisodeMap; + d_skill = gs->Skill; + CheckWarpTransMap (d_mapname, true); + gameaction = ga_newgame2; +} + //========================================================================== // // @@ -1464,8 +1474,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 701437452b..cf7fd2d482 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -486,6 +486,8 @@ void G_InitNew (const char *mapname, bool bTitleLevel); // A normal game starts at map 1, // but a warp test can start elsewhere void G_DeferedInitNew (const char *mapname, int skill = -1); +struct FGameStartup; +void G_DeferedInitNew (FGameStartup *gs); void G_ExitLevel (int position, bool keepFacing); void G_SecretExitLevel (int position); @@ -548,7 +550,8 @@ enum ESkillProperty SKILLP_ACSReturn, SKILLP_MonsterHealth, SKILLP_FriendlyHealth, - SKILLP_NoPain + SKILLP_NoPain, + SKILLP_ArmorFactor }; int G_SkillProperty(ESkillProperty prop); const char * G_SkillName(); @@ -583,6 +586,7 @@ struct FSkillInfo fixed_t MonsterHealth; fixed_t FriendlyHealth; bool NoPain; + fixed_t ArmorFactor; FSkillInfo() {} FSkillInfo(const FSkillInfo &other) @@ -601,6 +605,16 @@ struct FSkillInfo extern TArray AllSkills; extern int DefaultSkill; +struct FEpisode +{ + FString mEpisodeName; + FString mEpisodeMap; + FString mPicName; + char mShortcut; + bool mNoSkill; +}; + +extern TArray AllEpisodes; #endif //__G_LEVEL_H__ diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index 1eb6363fdd..6b3faa78a5 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -38,7 +38,6 @@ #include "g_level.h" #include "sc_man.h" #include "w_wad.h" -#include "m_menu.h" #include "cmdlib.h" #include "v_video.h" #include "p_lnspec.h" @@ -63,6 +62,8 @@ TArray wadlevelinfos; level_info_t TheDefaultLevelInfo; static cluster_info_t TheDefaultClusterInfo; +TArray AllEpisodes; + //========================================================================== // // @@ -1404,6 +1405,7 @@ MapFlagHandlers[] = { "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 }, @@ -1654,10 +1656,10 @@ level_info_t *FMapInfoParser::ParseMapHeader(level_info_t &defaultinfo) void FMapInfoParser::ParseEpisodeInfo () { - int i; + unsigned int i; char map[9]; - char *pic = NULL; - bool picisgfx = false; // Shut up, GCC!!!! + FString pic; + FString name; bool remove = false; char key = 0; bool noskill = false; @@ -1696,15 +1698,13 @@ void FMapInfoParser::ParseEpisodeInfo () { ParseAssign(); sc.MustGetString (); - ReplaceString (&pic, sc.String); - picisgfx = false; + name = sc.String; } else if (sc.Compare ("picname")) { ParseAssign(); sc.MustGetString (); - ReplaceString (&pic, sc.String); - picisgfx = true; + pic = sc.String; } else if (sc.Compare ("remove")) { @@ -1750,9 +1750,9 @@ void FMapInfoParser::ParseEpisodeInfo () } - for (i = 0; i < EpiDef.numitems; ++i) + for (i = 0; i < AllEpisodes.Size(); i++) { - if (strncmp (EpisodeMaps[i], map, 8) == 0) + if (AllEpisodes[i].mEpisodeMap.CompareNoCase(map) == 0) { break; } @@ -1761,50 +1761,17 @@ void FMapInfoParser::ParseEpisodeInfo () if (remove) { // If the remove property is given for an episode, remove it. - if (i < EpiDef.numitems) - { - if (i+1 < EpiDef.numitems) - { - memmove (&EpisodeMaps[i], &EpisodeMaps[i+1], - sizeof(EpisodeMaps[0])*(EpiDef.numitems - i - 1)); - memmove (&EpisodeMenu[i], &EpisodeMenu[i+1], - sizeof(EpisodeMenu[0])*(EpiDef.numitems - i - 1)); - memmove (&EpisodeNoSkill[i], &EpisodeNoSkill[i+1], - sizeof(EpisodeNoSkill[0])*(EpiDef.numitems - i - 1)); - } - EpiDef.numitems--; - } + AllEpisodes.Delete(i); } else { - if (pic == NULL) - { - pic = copystring (map); - picisgfx = false; - } + FEpisode *epi = &AllEpisodes[AllEpisodes.Reserve(1)]; - if (i == EpiDef.numitems) - { - if (EpiDef.numitems == MAX_EPISODES) - { - i = EpiDef.numitems - 1; - } - else - { - i = EpiDef.numitems++; - } - } - else - { - delete[] const_cast(EpisodeMenu[i].name); - } - - EpisodeMenu[i].name = pic; - EpisodeMenu[i].alphaKey = tolower(key); - EpisodeMenu[i].fulltext = !picisgfx; - EpisodeNoSkill[i] = noskill; - strncpy (EpisodeMaps[i], map, 8); - EpisodeMaps[i][8] = 0; + epi->mEpisodeMap = map; + epi->mEpisodeName = name; + epi->mPicName = pic; + epi->mShortcut = tolower(key); + epi->mNoSkill = noskill; } } @@ -1817,12 +1784,7 @@ void FMapInfoParser::ParseEpisodeInfo () void ClearEpisodes() { - for (int i = 0; i < EpiDef.numitems; ++i) - { - delete[] const_cast(EpisodeMenu[i].name); - EpisodeMenu[i].name = NULL; - } - EpiDef.numitems = 0; + AllEpisodes.Clear(); } //========================================================================== @@ -2003,7 +1965,7 @@ void G_ParseMapInfo (const char *basemapinfo) } EndSequences.ShrinkToFit (); - if (EpiDef.numitems == 0) + if (AllEpisodes.Size() == 0) { I_FatalError ("You cannot use clearepisodes in a MAPINFO if you do not define any new episodes after it."); } diff --git a/src/g_raven/a_minotaur.cpp b/src/g_raven/a_minotaur.cpp index a614d3b41f..eb54441470 100644 --- a/src/g_raven/a_minotaur.cpp +++ b/src/g_raven/a_minotaur.cpp @@ -448,9 +448,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); } } diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 3697bcba86..2243d48a4d 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -68,7 +68,7 @@ void A_Unblock(AActor *self, bool drop) 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); diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index 9e43ccfb6a..1475f86d24 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 088e480c52..c4f0f9d6a0 100644 --- a/src/g_shared/a_artifacts.cpp +++ b/src/g_shared/a_artifacts.cpp @@ -1004,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_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index 3bd2903eeb..d45a9f2a82 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()->Meta.GetMetaFixed (ACMETA_MissileHeight); const PClass *trail = PClass::FindClass(name); if (trail != NULL) diff --git a/src/g_shared/a_keys.cpp b/src/g_shared/a_keys.cpp index 575d135920..c9139f76b6 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_pickups.cpp b/src/g_shared/a_pickups.cpp index 1946189337..30dcd482cf 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -356,6 +356,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_RestoreSpecialPosition) self->z += FloatBobOffsets[(self->FloatBobPhase + level.maptime) & 63]; } } + self->SetOrigin (self->x, self->y, self->z); } int AInventory::StaticLastMessageTic; @@ -889,7 +890,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; @@ -959,9 +960,7 @@ void AInventory::DoPickupSpecial (AActor *toucher) const char *AInventory::PickupMessage () { - const char *message = GetClass()->Meta.GetMetaString (AIMETA_PickupMessage); - - return message != NULL? message : "You got a pickup"; + return GetClass()->Meta.GetMetaString (AIMETA_PickupMessage); } //=========================================================================== diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp index 67bd4b84a2..4c1b5b964c 100644 --- a/src/g_shared/a_randomspawner.cpp +++ b/src/g_shared/a_randomspawner.cpp @@ -71,7 +71,7 @@ class ARandomSpawner : public AActor cls = PClass::FindClass(di->Name); if (cls != NULL) { - const PClass *rep = cls->ActorInfo->GetReplacement()->Class; + const PClass *rep = cls->GetReplacement(); if (rep != NULL) { cls = rep; diff --git a/src/g_shared/a_specialspot.cpp b/src/g_shared/a_specialspot.cpp index dbf2cffca8..44ee6b848b 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 ffc083978a..21d90f73f0 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/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 6895ce321c..eed9d86c79 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -933,16 +933,16 @@ void Popup::close() //////////////////////////////////////////////////////////////////////////////// -inline void adjustRelCenter(const SBarInfoCoordinate &x, const SBarInfoCoordinate &y, double &outX, double &outY, const double &xScale, const double &yScale) +inline void adjustRelCenter(bool relX, bool relY, const double &x, const double &y, double &outX, double &outY, const double &xScale, const double &yScale) { - if(x.RelCenter()) - outX = *x + (SCREENWIDTH/(hud_scale ? xScale*2 : 2)); + if(relX) + outX = x + (SCREENWIDTH/(hud_scale ? xScale*2 : 2)); else - outX = *x; - if(y.RelCenter()) - outY = *y + (SCREENHEIGHT/(hud_scale ? yScale*2 : 2)); + outX = x; + if(relY) + outY = y + (SCREENHEIGHT/(hud_scale ? yScale*2 : 2)); else - outY = *y; + outY = y; } class DSBarInfo : public DBaseStatusBar @@ -1227,7 +1227,7 @@ public: 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, y, rx, ry, xScale, yScale); + 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. @@ -1288,7 +1288,7 @@ public: } if(clearDontDraw) - screen->Clear(static_cast(rcx), static_cast(rcy), static_cast(MIN(rcr, w)), static_cast(MIN(rcb, h)), 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) @@ -1343,13 +1343,16 @@ public: 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, y, ax, ay, xScale, yScale); + 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; } diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index 9016c24279..e66d23f79e 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 || !PClass::FindClass("Inventory")->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 || !PClass::FindClass("Inventory")->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 || !PClass::FindClass("Inventory")->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]); } @@ -516,7 +522,7 @@ class CommandDrawString : public SBarInfoCommand CommandDrawString(SBarInfo *script) : SBarInfoCommand(script), shadow(false), shadowX(2), shadowY(2), spacing(0), font(NULL), translation(CR_UNTRANSLATED), cache(-1), strValue(CONSTANT), - valueArgument(0) + valueArgument(0), alignment (ALIGN_RIGHT) { } @@ -529,7 +535,10 @@ class CommandDrawString : public SBarInfoCommand 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(','); @@ -587,6 +596,42 @@ class CommandDrawString : public SBarInfoCommand { 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(';'); @@ -682,13 +727,33 @@ class CommandDrawString : public SBarInfoCommand } } protected: + enum StringAlignment + { + ALIGN_RIGHT, + ALIGN_LEFT, + ALIGN_CENTER, + }; + void RealignString() { x = startX; - 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()); + 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 @@ -721,6 +786,7 @@ class CommandDrawString : public SBarInfoCommand StringValueType strValue; int valueArgument; FString str; + StringAlignment alignment; private: void SetStringToTag(AActor *actor) @@ -763,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(','); @@ -793,7 +862,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindClass(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")) @@ -803,7 +873,8 @@ class CommandDrawNumber : public CommandDrawString inventoryItem = PClass::FindClass(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")) @@ -845,18 +916,20 @@ class CommandDrawNumber : public CommandDrawString value = POWERUPTIME; sc.MustGetToken(TK_Identifier); inventoryItem = PClass::FindClass(sc.String); - if(inventoryItem == NULL || !PClass::FindClass("PowerupGiver")->IsAncestorOf(inventoryItem)) + 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 { value = INVENTORY; inventoryItem = PClass::FindClass(sc.String); - if(inventoryItem == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(inventoryItem)) //must be a kind of ammo + 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(','); @@ -887,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('|')) @@ -1275,7 +1362,10 @@ class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private { 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; } @@ -1757,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); @@ -1991,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()); } @@ -2010,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); + if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of inventory + { + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); + } } } else if(sc.Compare("armor")) @@ -2050,8 +2149,11 @@ class CommandDrawBar : public SBarInfoCommand if(sc.CheckToken(TK_Identifier)) { inventoryItem = PClass::FindClass(sc.String); - if(inventoryItem == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(inventoryItem)) //must be a kind of inventory - sc.ScriptError("'%s' is not a type of inventory item.", sc.String); + if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of inventory + { + sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String); + inventoryItem = RUNTIME_CLASS(AInventory); + } } } else if(sc.Compare("ammo1")) @@ -2065,7 +2167,8 @@ class CommandDrawBar : public SBarInfoCommand inventoryItem = PClass::FindClass(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")) @@ -2083,9 +2186,10 @@ class CommandDrawBar : public SBarInfoCommand type = POWERUPTIME; sc.MustGetToken(TK_Identifier); inventoryItem = PClass::FindClass(sc.String); - if(inventoryItem == NULL || !PClass::FindClass("PowerupGiver")->IsAncestorOf(inventoryItem)) + 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 @@ -2094,7 +2198,8 @@ class CommandDrawBar : public SBarInfoCommand inventoryItem = PClass::FindClass(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(','); @@ -2258,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)) @@ -2343,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(',')) { @@ -2445,7 +2549,10 @@ class CommandHasWeaponPiece : public SBarInfoCommandFlowControl 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.ScriptMessage("%s is not a kind of weapon.", sc.String); + weapon = RUNTIME_CLASS(AWeapon); + } sc.MustGetToken(','); sc.MustGetToken(TK_IntConst); if(sc.Number < 1) @@ -2634,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)) { @@ -2734,7 +2844,10 @@ class CommandInInventory : public SBarInfoCommandFlowControl { item[i] = PClass::FindClass(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(',')) { diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index 8c04738323..beb459cdab 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -1504,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 diff --git a/src/g_skill.cpp b/src/g_skill.cpp index 424b69a475..cac38563a9 100644 --- a/src/g_skill.cpp +++ b/src/g_skill.cpp @@ -80,6 +80,7 @@ void FMapInfoParser::ParseSkill () skill.MonsterHealth = FRACUNIT; skill.FriendlyHealth = FRACUNIT; skill.NoPain = false; + skill.ArmorFactor = FRACUNIT; sc.MustGetString(); skill.Name = sc.String; @@ -249,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) @@ -357,7 +364,10 @@ 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; @@ -435,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/gameconfigfile.cpp b/src/gameconfigfile.cpp index 936d4e7f75..acf2bbad18 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 e0813479ab..190d2b0311 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -103,12 +103,11 @@ const char* GameInfoBoarders[] = do \ { \ sc.MustGetToken(TK_StringConst); \ - if(strlen(sc.String) > length) \ + if(length > 0 && strlen(sc.String) > length) \ { \ sc.ScriptError("Value for '%s' can not be longer than %d characters.", #key, length); \ } \ - FName val = sc.String; \ - gameinfo.key.Push(val); \ + gameinfo.key[gameinfo.key.Reserve(1)] = sc.String; \ } \ while (sc.CheckToken(',')); \ } @@ -266,6 +265,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") @@ -282,6 +282,16 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_INT(defaultdropstyle, "defaultdropstyle") GAMEINFOKEY_CSTRING(Endoom, "endoom", 8) GAMEINFOKEY_INT(player5start, "player5start") + GAMEINFOKEY_STRINGARRAY(quitmessages, "quitmessages", 0) + GAMEINFOKEY_STRING(mTitleColor, "menufontcolor_title") + GAMEINFOKEY_STRING(mFontColor, "menufontcolor_label") + GAMEINFOKEY_STRING(mFontColorValue, "menufontcolor_value") + GAMEINFOKEY_STRING(mFontColorMore, "menufontcolor_action") + GAMEINFOKEY_STRING(mFontColorHeader, "menufontcolor_header") + GAMEINFOKEY_STRING(mFontColorHighlight, "menufontcolor_highlight") + GAMEINFOKEY_STRING(mFontColorSelection, "menufontcolor_selection") + GAMEINFOKEY_CSTRING(mBackButton, "menubackbutton", 8) + else { // ignore unkown keys. diff --git a/src/gi.h b/src/gi.h index a87098555b..ef9777553c 100644 --- a/src/gi.h +++ b/src/gi.h @@ -43,7 +43,10 @@ #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 0x00000010 // same for stairbuilding +#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 +#define GI_NOTEXTCOLOR 0x00000100 #include "gametype.h" @@ -106,6 +109,16 @@ struct gameinfo_t int defaultrespawntime; int defaultdropstyle; int player5start; + DWORD pickupcolor; + TArray quitmessages; + FName mTitleColor; + FName mFontColor; + FName mFontColorValue; + FName mFontColorMore; + FName mFontColorHeader; + FName mFontColorHighlight; + FName mFontColorSelection; + char mBackButton[9]; const char *GetFinalePage(unsigned int num) const; }; diff --git a/src/keysections.cpp b/src/keysections.cpp new file mode 100644 index 0000000000..3da17fb380 --- /dev/null +++ b/src/keysections.cpp @@ -0,0 +1,146 @@ +/* +** keysections.cpp +** Custom key bindings +** +**--------------------------------------------------------------------------- +** Copyright 1998-2009 Randy Heit +** Copyright 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 "menu/menu.h" +#include "g_level.h" +#include "d_player.h" +#include "gi.h" +#include "c_bind.h" +#include "c_dispatch.h" +#include "gameconfigfile.h" + +TArray KeySections; + +static void LoadKeys (const char *modname, bool dbl) +{ + char section[64]; + + if (GameNames[gameinfo.gametype] == NULL) + return; + + 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)) + { + bindings->DoBind (key, value); + } + } +} + +static void DoSaveKeys (FConfigFile *config, const char *section, FKeySection *keysection, bool dbl) +{ + config->SetSection (section, true); + config->ClearCurrentSection (); + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; + for (unsigned i = 0; i < keysection->mActions.Size(); ++i) + { + bindings->ArchiveBindings (config, keysection->mActions[i].mAction); + } +} + +void M_SaveCustomKeys (FConfigFile *config, char *section, char *subsection, size_t sublen) +{ + for (unsigned i=0; i \n"); + return; + } + + // Limit the ini name to 32 chars + if (strlen (argv[2]) > 32) + argv[2][32] = 0; + + for (unsigned i = 0; i < KeySections.Size(); i++) + { + if (KeySections[i].mTitle.CompareNoCase(argv[2] == 0)) + { + CurrentKeySection = i; + return; + } + } + + CurrentKeySection = KeySections.Reserve(1); + KeySections[CurrentKeySection].mTitle = argv[1]; + KeySections[CurrentKeySection].mSection = argv[2]; + // Load bindings for this section from the ini + LoadKeys (argv[2], 0); + LoadKeys (argv[2], 1); + } +} + +CCMD (addmenukey) +{ + if (ParsingKeyConf) + { + if (argv.argc() != 3) + { + Printf ("Usage: addmenukey \n"); + return; + } + if (CurrentKeySection == -1 || CurrentKeySection >= (int)KeySections.Size()) + { + Printf ("You must use addkeysection first.\n"); + return; + } + + FKeySection *sect = &KeySections[CurrentKeySection]; + + FKeyAction *act = §->mActions[sect->mActions.Reserve(1)]; + act->mTitle = argv[1]; + act->mAction = argv[2]; + } +} + diff --git a/src/m_bbox.cpp b/src/m_bbox.cpp index d13f790e6c..6d3a5b7443 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 7c72216883..febf9d2a25 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 0a7928a912..9da1b1ff5f 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -754,8 +754,8 @@ void cht_Give (player_t *player, const char *name, int amount) // Don't give replaced weapons unless the replacement was done by Dehacked. if (type != RUNTIME_CLASS(AWeapon) && type->IsDescendantOf (RUNTIME_CLASS(AWeapon)) && - (type->ActorInfo->GetReplacement() == type->ActorInfo || - type->ActorInfo->GetReplacement()->Class->IsDescendantOf(RUNTIME_CLASS(ADehackedPickup)))) + (type->GetReplacement() == type || + type->GetReplacement()->IsDescendantOf(RUNTIME_CLASS(ADehackedPickup)))) { // Give the weapon only if it belongs to the current game or diff --git a/src/m_joy.h b/src/m_joy.h index 2c939f679f..00537dee8a 100644 --- a/src/m_joy.h +++ b/src/m_joy.h @@ -60,5 +60,6 @@ double Joy_RemoveDeadZone(double axisval, double deadzone, BYTE *buttons); void I_GetAxes(float axes[NUM_JOYAXIS]); void I_GetJoysticks(TArray &sticks); IJoystickConfig *I_UpdateDeviceList(); +extern void UpdateJoystickMenu(IJoystickConfig *); #endif diff --git a/src/m_menu.cpp b/src/m_menu.cpp deleted file mode 100644 index 49d5638ef3..0000000000 --- a/src/m_menu.cpp +++ /dev/null @@ -1,4133 +0,0 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// $Id:$ -// -// Copyright (C) 1993-1996 by id Software, Inc. -// -// This source is available for distribution and/or modification -// only under the terms of the DOOM Source Code License as -// published by id Software. All rights reserved. -// -// The source is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License -// for more details. -// -// $Log:$ -// -// DESCRIPTION: -// DOOM selection menu, options, episode etc. -// Sliders and icons. Kinda widget stuff. -// -//----------------------------------------------------------------------------- - -// HEADER FILES ------------------------------------------------------------ - -#include -#include -#include -#include - -#if defined(_WIN32) -#include -#else -#include -#include -#include -#endif - -#include "doomdef.h" -#include "gstrings.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "d_main.h" -#include "i_system.h" -#include "i_video.h" -#include "v_video.h" -#include "v_palette.h" -#include "w_wad.h" -#include "r_local.h" -#include "hu_stuff.h" -#include "g_game.h" -#include "m_argv.h" -#include "m_swap.h" -#include "m_random.h" -#include "s_sound.h" -#include "doomstat.h" -#include "m_menu.h" -#include "v_text.h" -#include "st_stuff.h" -#include "d_gui.h" -#include "version.h" -#include "m_png.h" -#include "templates.h" -#include "lists.h" -#include "gi.h" -#include "p_tick.h" -#include "st_start.h" -#include "teaminfo.h" -#include "r_translate.h" -#include "g_level.h" -#include "d_event.h" -#include "colormatcher.h" -#include "d_netinf.h" - -// MACROS ------------------------------------------------------------------ - -#define SKULLXOFF -32 -#define SELECTOR_XOFFSET (-28) -#define SELECTOR_YOFFSET (-1) - -#define KEY_REPEAT_DELAY (TICRATE*5/12) -#define KEY_REPEAT_RATE (3) - -#define INPUTGRID_WIDTH 13 -#define INPUTGRID_HEIGHT 5 - -// TYPES ------------------------------------------------------------------- - -struct FSaveGameNode : public Node -{ - char Title[SAVESTRINGSIZE]; - FString Filename; - bool bOldVersion; - bool bMissingWads; -}; - -struct FBackdropTexture : public FTexture -{ -public: - FBackdropTexture(); - - const BYTE *GetColumn(unsigned int column, const Span **spans_out); - const BYTE *GetPixels(); - void Unload(); - bool CheckModified(); - -protected: - BYTE Pixels[144*160]; - static const Span DummySpan[2]; - int LastRenderTic; - - angle_t time1, time2, time3, time4; - angle_t t1ang, t2ang, z1ang, z2ang; - - void Render(); -}; - -// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- - -void R_GetPlayerTranslation (int color, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table); - -// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- - -int M_StringHeight (const char *string); -void M_ClearMenus (); - -// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- - -static void M_NewGame (int choice); -static void M_Episode (int choice); -static void M_ChooseSkill (int choice); -static void M_LoadGame (int choice); -static void M_SaveGame (int choice); -static void M_Options (int choice); -static void M_EndGame (int choice); -static void M_ReadThis (int choice); -static void M_ReadThisMore (int choice); -static void M_QuitGame (int choice); -static void M_GameFiles (int choice); -static void M_ClearSaveStuff (); - -static void SCClass (int choice); -static void M_ChooseClass (int choice); - -static void M_FinishReadThis (int choice); -static void M_QuickSave (); -static void M_QuickLoad (); -static void M_LoadSelect (const FSaveGameNode *file); -static void M_SaveSelect (const FSaveGameNode *file); -static void M_ReadSaveStrings (); -static void M_UnloadSaveStrings (); -static FSaveGameNode *M_RemoveSaveSlot (FSaveGameNode *file); -static void M_ExtractSaveData (const FSaveGameNode *file); -static void M_UnloadSaveData (); -static void M_InsertSaveNode (FSaveGameNode *node); -static bool M_SaveLoadResponder (event_t *ev); -static void M_SaveLoadButtonHandler(EMenuKey key); -static void M_DeleteSaveResponse (int choice); - -static void M_DrawMainMenu (); -static void M_DrawReadThis (); -static void M_DrawNewGame (); -static void M_DrawEpisode (); -static void M_DrawLoad (); -static void M_DrawSave (); -static void DrawClassMenu (); -static void DrawHexenSkillMenu (); -static void M_DrawClassMenu (); - -static void M_DrawHereticMainMenu (); -static void M_DrawFiles (); - -void M_DrawFrame (int x, int y, int width, int height); -static void M_DrawSaveLoadBorder (int x,int y, int len); -static void M_DrawSaveLoadCommon (); -static void M_DrawInputGrid(); - -static void M_SetupNextMenu (oldmenu_t *menudef); -static void M_StartMessage (const char *string, void(*routine)(int)); -static void M_EndMessage (int key); - -// [RH] For player setup menu. - void M_PlayerSetup (); -static void M_PlayerSetupTicker (); -static void M_PlayerSetupDrawer (); -static void M_EditPlayerName (int choice); -static void M_ChangePlayerTeam (int choice); -static void M_PlayerNameChanged (FSaveGameNode *dummy); -static void M_PlayerNameNotChanged (); -static void M_ChangeColorSet (int choice); -static void M_SlidePlayerRed (int choice); -static void M_SlidePlayerGreen (int choice); -static void M_SlidePlayerBlue (int choice); -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 PickPlayerClass (); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -EXTERN_CVAR (String, playerclass) -EXTERN_CVAR (String, name) -EXTERN_CVAR (Int, team) - -extern bool sendpause; -extern int flagsvar; - -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -EMenuState menuactive; -menustack_t MenuStack[16]; -int MenuStackDepth; -int skullAnimCounter; // skull animation counter -bool drawSkull; // [RH] don't always draw skull -bool M_DemoNoPlay; -bool OptionsActive; -FButtonStatus MenuButtons[NUM_MKEYS]; -int MenuButtonTickers[NUM_MKEYS]; - -// PRIVATE DATA DEFINITIONS ------------------------------------------------ - -static char tempstring[80]; -static char underscore[2]; - -static FSaveGameNode *quickSaveSlot; // NULL = no quicksave slot picked! -static FSaveGameNode *lastSaveSlot; // Used for highlighting the most recently used slot in the menu -static int messageToPrint; // 1 = message to be printed -static const char *messageString; // ...and here is the message string! -static EMenuState messageLastMenuActive; -static void (*messageRoutine)(int response); // Non-NULL if only Y/N should close message -static int showSharewareMessage; -static int messageSelection; // 0 {Yes) or 1 (No) [if messageRoutine is non-NULL] - -static int genStringEnter; // we are going to be entering a savegame string -static size_t genStringLen; // [RH] Max # of chars that can be entered -static void (*genStringEnd)(FSaveGameNode *); -static void (*genStringCancel)(); -static int saveSlot; // which slot to save in -static size_t saveCharIndex; // which char we're editing - -static int LINEHEIGHT; -static const int PLAYERSETUP_LINEHEIGHT = 16; - -static char savegamestring[SAVESTRINGSIZE]; -static FString EndString; - -static short itemOn; // menu item skull is on -static int MenuTime; -static int InfoType; -static int InfoTic; - -static oldmenu_t *currentMenu; // current menudef -static oldmenu_t *TopLevelMenu; // The main menu everything hangs off of - -static FBackdropTexture *FireTexture; -static FRemapTable FireRemap(256); - -static const char *genders[3] = { "male", "female", "other" }; -static FPlayerClass *PlayerClass; -static int PlayerSkin; -static FState *PlayerState; -static int PlayerTics; -static int PlayerRotation; -static TArray PlayerColorSets; - -static FTexture *SavePic; -static FBrokenLines *SaveComment; -static List SaveGames; -static FSaveGameNode *TopSaveGame; -static FSaveGameNode *SelSaveGame; -static FSaveGameNode NewSaveNode; - -static int epi; // Selected episode - -static const char *saved_playerclass = NULL; - -// Heretic and Hexen do not, by default, come with glyphs for all of these -// characters. Oh well. Doom and Strife do. -static const char InputGridChars[INPUTGRID_WIDTH * INPUTGRID_HEIGHT] = - "ABCDEFGHIJKLM" - "NOPQRSTUVWXYZ" - "0123456789+-=" - ".,!?@'\":;[]()" - "<>^#$%&*/_ \b"; -static int InputGridX = INPUTGRID_WIDTH - 1; -static int InputGridY = INPUTGRID_HEIGHT - 1; -static bool InputGridOkay; // Last input was with a controller. - -// PRIVATE MENU DEFINITIONS ------------------------------------------------ - -// -// DOOM MENU -// -static oldmenuitem_t MainMenu[]= -{ - {1,0,'n',"M_NGAME",M_NewGame, CR_UNTRANSLATED}, - {1,0,'l',"M_LOADG",M_LoadGame, CR_UNTRANSLATED}, - {1,0,'s',"M_SAVEG",M_SaveGame, CR_UNTRANSLATED}, - {1,0,'o',"M_OPTION",M_Options, CR_UNTRANSLATED}, // [RH] Moved - {1,0,'r',"M_RDTHIS",M_ReadThis, CR_UNTRANSLATED}, // Another hickup with Special edition. - {1,0,'q',"M_QUITG",M_QuitGame, CR_UNTRANSLATED} -}; - -static oldmenu_t MainDef = -{ - countof(MainMenu), - MainMenu, - M_DrawMainMenu, - 97,64, - 0 -}; - -// -// HERETIC MENU -// -static oldmenuitem_t HereticMainMenu[] = -{ - {1,1,'n',"$MNU_NEWGAME",M_NewGame, CR_UNTRANSLATED}, - {1,1,'o',"$MNU_OPTIONS",M_Options, CR_UNTRANSLATED}, - {1,1,'f',"$MNU_GAMEFILES",M_GameFiles, CR_UNTRANSLATED}, - {1,1,'i',"$MNU_INFO",M_ReadThis, CR_UNTRANSLATED}, - {1,1,'q',"$MNU_QUITGAME",M_QuitGame, CR_UNTRANSLATED} -}; - -static oldmenu_t HereticMainDef = -{ - countof(HereticMainMenu), - HereticMainMenu, - M_DrawHereticMainMenu, - 110, 56, - 0 -}; - -// -// HEXEN "NEW GAME" MENU -// -static oldmenuitem_t ClassItems[] = -{ - { 1,1, 'f', "$MNU_FIGHTER", SCClass, CR_UNTRANSLATED }, - { 1,1, 'c', "$MNU_CLERIC", SCClass, CR_UNTRANSLATED }, - { 1,1, 'm', "$MNU_MAGE", SCClass, CR_UNTRANSLATED }, - { 1,1, 'r', "$MNU_RANDOM", SCClass, CR_UNTRANSLATED } // [RH] -}; - -static oldmenu_t ClassMenu = -{ - 4, ClassItems, - DrawClassMenu, - 66, 58, - 0 -}; - -// -// [GRB] CLASS SELECT -// -oldmenuitem_t ClassMenuItems[8] = -{ - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, - {1,1,0, NULL, M_ChooseClass, CR_UNTRANSLATED }, -}; - -oldmenu_t ClassMenuDef = -{ - 0, - ClassMenuItems, - M_DrawClassMenu, - 48,63, - 0 -}; - -// -// EPISODE SELECT -// -oldmenuitem_t EpisodeMenu[MAX_EPISODES] = -{ - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, - {1,0,0, NULL, M_Episode, CR_UNTRANSLATED}, -}; - -char EpisodeMaps[MAX_EPISODES][9]; -bool EpisodeNoSkill[MAX_EPISODES]; - -oldmenu_t EpiDef = -{ - 0, - EpisodeMenu, // oldmenuitem_t -> - M_DrawEpisode, // drawing routine -> - 48,63, // x,y - 0 // lastOn -}; - -// -// GAME FILES -// -static oldmenuitem_t FilesItems[] = -{ - {1,1,'l',"$MNU_LOADGAME",M_LoadGame, CR_UNTRANSLATED}, - {1,1,'s',"$MNU_SAVEGAME",M_SaveGame, CR_UNTRANSLATED} -}; - -static oldmenu_t FilesMenu = -{ - countof(FilesItems), - FilesItems, - M_DrawFiles, - 110,60, - 0 -}; - -// -// DOOM SKILL SELECT -// -static oldmenuitem_t SkillSelectMenu[]={ - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, - { 1, 0, 0, "", M_ChooseSkill, CR_UNTRANSLATED}, -}; - -static oldmenu_t SkillDef = -{ - 0, - SkillSelectMenu, // oldmenuitem_t -> - M_DrawNewGame, // drawing routine -> - 48,63, // x,y - -1 // lastOn -}; - -static oldmenu_t HexenSkillMenu = -{ - 0, - SkillSelectMenu, - DrawHexenSkillMenu, - 120, 44, - -1 -}; - - -void M_StartupSkillMenu(const char *playerclass) -{ - if (gameinfo.gametype & GAME_Raven) - { - SkillDef.x = 38; - SkillDef.y = 30; - - if (gameinfo.gametype == GAME_Hexen) - { - HexenSkillMenu.x = 38; - if (playerclass != NULL) - { - if (!stricmp(playerclass, "fighter")) HexenSkillMenu.x = 120; - else if (!stricmp(playerclass, "cleric")) HexenSkillMenu.x = 116; - else if (!stricmp(playerclass, "mage")) HexenSkillMenu.x = 112; - } - } - } - SkillDef.numitems = HexenSkillMenu.numitems = 0; - for(unsigned int i = 0; i < AllSkills.Size() && i < 8; i++) - { - FSkillInfo &skill = AllSkills[i]; - - 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; - - if (playerclass != NULL) - { - FString * pmnm = skill.MenuNamesForPlayerClass.CheckKey(playerclass); - if (pmnm != NULL) - { - SkillSelectMenu[i].name = GStrings(*pmnm); - SkillSelectMenu[i].fulltext = true; - if (skill.Shortcut == 0) - SkillSelectMenu[i].alphaKey = tolower(SkillSelectMenu[i].name[0]); - } - } - SkillDef.numitems++; - HexenSkillMenu.numitems++; - } - int defskill = DefaultSkill; - if ((unsigned int)defskill >= AllSkills.Size()) - { - defskill = (AllSkills.Size() - 1) / 2; - } - // The default skill is only set the first time the menu is opened. - // After that, it opens on whichever skill you last selected. - if (SkillDef.lastOn < 0) - { - SkillDef.lastOn = defskill; - } - if (HexenSkillMenu.lastOn < 0) - { - HexenSkillMenu.lastOn = defskill; - } - // Hexen needs some manual coordinate adjustments based on player class - if (gameinfo.gametype == GAME_Hexen) - { - M_SetupNextMenu(&HexenSkillMenu); - } - else - { - M_SetupNextMenu(&SkillDef); - } - -} - -// -// [RH] Player Setup Menu -// -static oldmenuitem_t PlayerSetupMenu[] = -{ - { 1,0,'n',NULL,M_EditPlayerName, CR_UNTRANSLATED}, - { 2,0,'t',NULL,M_ChangePlayerTeam, CR_UNTRANSLATED}, - { 2,0,'c',NULL,M_ChangeColorSet, CR_UNTRANSLATED}, - { 2,0,'r',NULL,M_SlidePlayerRed, CR_UNTRANSLATED}, - { 2,0,'g',NULL,M_SlidePlayerGreen, CR_UNTRANSLATED}, - { 2,0,'b',NULL,M_SlidePlayerBlue, CR_UNTRANSLATED}, - { 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} -}; - -enum -{ - // These must be changed if the menu definition is altered - PSM_RED = 3, - PSM_GREEN = 4, - PSM_BLUE = 5, -}; - -static oldmenu_t PSetupDef = -{ - countof(PlayerSetupMenu), - PlayerSetupMenu, - M_PlayerSetupDrawer, - 48, 47, - 0 -}; - -// -// Read This! MENU 1 & 2 -// -static oldmenuitem_t ReadMenu[] = -{ - {1,0,0,NULL,M_ReadThisMore} -}; - -static oldmenu_t ReadDef = -{ - 1, - ReadMenu, - M_DrawReadThis, - 280,185, - 0 -}; - -// -// LOAD GAME MENU -// -static oldmenuitem_t LoadMenu[]= -{ - {1,0,'1',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'2',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'3',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'4',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'5',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'6',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'7',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'8',NULL, NULL, CR_UNTRANSLATED}, -}; - -static oldmenu_t LoadDef = -{ - countof(LoadMenu), - LoadMenu, - M_DrawLoad, - 80,54, - 0 -}; - -// -// SAVE GAME MENU -// -static oldmenuitem_t SaveMenu[] = -{ - {1,0,'1',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'2',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'3',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'4',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'5',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'6',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'7',NULL, NULL, CR_UNTRANSLATED}, - {1,0,'8',NULL, NULL, CR_UNTRANSLATED}, -}; - -static oldmenu_t SaveDef = -{ - countof(LoadMenu), - SaveMenu, - M_DrawSave, - 80,54, - 0 -}; - -// CODE -------------------------------------------------------------------- - -// [RH] Most menus can now be accessed directly -// through console commands. -CCMD (menu_main) -{ - M_StartControlPanel (true, true); -} - -CCMD (menu_load) -{ // F3 - M_StartControlPanel (true); - M_LoadGame (0); -} - -CCMD (menu_save) -{ // F2 - M_StartControlPanel (true); - M_SaveGame (0); -} - -CCMD (menu_help) -{ // F1 - M_StartControlPanel (true); - M_ReadThis (0); -} - -CCMD (quicksave) -{ // F6 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); - M_QuickSave(); -} - -CCMD (quickload) -{ // F9 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); - M_QuickLoad(); -} - -CCMD (menu_endgame) -{ // F7 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); - M_EndGame(0); -} - -CCMD (menu_quit) -{ // F10 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); - M_QuitGame(0); -} - -CCMD (menu_game) -{ - M_StartControlPanel (true); - M_NewGame(0); -} - -CCMD (menu_options) -{ - M_StartControlPanel (true); - M_Options(0); -} - -CCMD (menu_player) -{ - M_StartControlPanel (true); - M_PlayerSetup (); -} - -CCMD (bumpgamma) -{ - // [RH] Gamma correction tables are now generated - // on the fly for *any* gamma level. - // Q: What are reasonable limits to use here? - - float newgamma = Gamma + 0.1f; - - if (newgamma > 3.0) - newgamma = 1.0; - - Gamma = newgamma; - Printf ("Gamma correction level %g\n", *Gamma); -} - -void M_ActivateMenuInput () -{ - ResetButtonStates (); - menuactive = MENU_On; - // Pause sound effects before we play the menu switch sound. - // That way, it won't be paused. - P_CheckTickerPaused (); -} - -void M_DeactivateMenuInput () -{ - menuactive = MENU_Off; -} - -void M_DrawFiles () -{ -} - -void M_GameFiles (int choice) -{ - M_SetupNextMenu (&FilesMenu); -} - -// -// M_ReadSaveStrings -// -// Find savegames and read their titles -// -static void M_ReadSaveStrings () -{ - if (SaveGames.IsEmpty ()) - { - void *filefirst; - findstate_t c_file; - FString filter; - - atterm (M_UnloadSaveStrings); - - filter = G_BuildSaveName ("*.zds", -1); - filefirst = I_FindFirst (filter.GetChars(), &c_file); - if (filefirst != ((void *)(-1))) - { - do - { - // I_FindName only returns the file's name and not its full path - FString filepath = G_BuildSaveName (I_FindName(&c_file), -1); - FILE *file = fopen (filepath, "rb"); - - if (file != NULL) - { - PNGHandle *png; - char sig[16]; - char title[SAVESTRINGSIZE+1]; - bool oldVer = true; - bool addIt = false; - bool missing = false; - - // ZDoom 1.23 betas 21-33 have the savesig first. - // Earlier versions have the savesig second. - // Later versions have the savegame encapsulated inside a PNG. - // - // Old savegame versions are always added to the menu so - // the user can easily delete them if desired. - - title[SAVESTRINGSIZE] = 0; - - if (NULL != (png = M_VerifyPNG (file))) - { - char *ver = M_GetPNGText (png, "ZDoom Save Version"); - char *engine = M_GetPNGText (png, "Engine"); - if (ver != NULL) - { - if (!M_GetPNGText (png, "Title", title, SAVESTRINGSIZE)) - { - strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE); - } - if (strncmp (ver, SAVESIG, 9) == 0 && - atoi (ver+9) >= MINSAVEVER && - engine != NULL) - { - // Was saved with a compatible ZDoom version, - // so check if it's for the current game. - // If it is, add it. Otherwise, ignore it. - char *iwad = M_GetPNGText (png, "Game WAD"); - if (iwad != NULL) - { - if (stricmp (iwad, Wads.GetWadName (FWadCollection::IWAD_FILENUM)) == 0) - { - addIt = true; - oldVer = false; - missing = !G_CheckSaveGameWads (png, false); - } - delete[] iwad; - } - } - else - { // An old version - addIt = true; - } - delete[] ver; - } - if (engine != NULL) - { - delete[] engine; - } - delete png; - } - else - { - fseek (file, 0, SEEK_SET); - if (fread (sig, 1, 16, file) == 16) - { - - if (strncmp (sig, "ZDOOMSAVE", 9) == 0) - { - if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) - { - addIt = true; - } - } - else - { - memcpy (title, sig, 16); - if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && - fread (sig, 1, 16, file) == 16 && - strncmp (sig, "ZDOOMSAVE", 9) == 0) - { - addIt = true; - } - } - } - } - - if (addIt) - { - FSaveGameNode *node = new FSaveGameNode; - node->Filename = filepath; - node->bOldVersion = oldVer; - node->bMissingWads = missing; - memcpy (node->Title, title, SAVESTRINGSIZE); - M_InsertSaveNode (node); - } - fclose (file); - } - } while (I_FindNext (filefirst, &c_file) == 0); - I_FindClose (filefirst); - } - } - if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) - { - SelSaveGame = static_cast(SaveGames.Head); - } -} - -static void M_UnloadSaveStrings() -{ - M_UnloadSaveData(); - while (!SaveGames.IsEmpty()) - { - M_RemoveSaveSlot (static_cast(SaveGames.Head)); - } -} - -static FSaveGameNode *M_RemoveSaveSlot (FSaveGameNode *file) -{ - FSaveGameNode *next = static_cast(file->Succ); - - if (file == TopSaveGame) - { - TopSaveGame = next; - } - if (quickSaveSlot == file) - { - quickSaveSlot = NULL; - } - if (lastSaveSlot == file) - { - lastSaveSlot = NULL; - } - file->Remove (); - delete file; - return next; -} - -void M_InsertSaveNode (FSaveGameNode *node) -{ - FSaveGameNode *probe; - - if (SaveGames.IsEmpty ()) - { - SaveGames.AddHead (node); - return; - } - - if (node->bOldVersion) - { // Add node at bottom of list - probe = static_cast(SaveGames.TailPred); - while (probe->Pred != NULL && probe->bOldVersion && - stricmp (node->Title, probe->Title) < 0) - { - probe = static_cast(probe->Pred); - } - node->Insert (probe); - } - else - { // Add node at top of list - probe = static_cast(SaveGames.Head); - while (probe->Succ != NULL && !probe->bOldVersion && - stricmp (node->Title, probe->Title) > 0) - { - probe = static_cast(probe->Succ); - } - node->InsertBefore (probe); - } -} - -void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave) -{ - FSaveGameNode *node; - - if (file == NULL) - return; - - M_ReadSaveStrings (); - - // See if the file is already in our list - for (node = static_cast(SaveGames.Head); - node->Succ != NULL; - node = static_cast(node->Succ)) - { -#ifdef unix - if (node->Filename.Compare (file) == 0) -#else - if (node->Filename.CompareNoCase (file) == 0) -#endif - { - strcpy (node->Title, title); - node->bOldVersion = false; - node->bMissingWads = false; - break; - } - } - - if (node->Succ == NULL) - { - node = new FSaveGameNode; - strcpy (node->Title, title); - node->Filename = file; - node->bOldVersion = false; - node->bMissingWads = false; - M_InsertSaveNode (node); - SelSaveGame = node; - } - - if (okForQuicksave) - { - if (quickSaveSlot == NULL) quickSaveSlot = node; - lastSaveSlot = node; - } -} - -// -// M_LoadGame & Cie. -// -void M_DrawLoad (void) -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - FTexture *title = TexMan["M_LOADG"]; - screen->DrawTexture (title, - (SCREENWIDTH - title->GetScaledWidth()*CleanXfac)/2, 20*CleanYfac, - DTA_CleanNoMove, true, TAG_DONE); - } - else - { - const char *loadgame = GStrings("MNU_LOADGAME"); - screen->DrawText (BigFont, CR_UNTRANSLATED, - (SCREENWIDTH - BigFont->StringWidth (loadgame)*CleanXfac)/2, 10*CleanYfac, - loadgame, DTA_CleanNoMove, true, TAG_DONE); - } - M_DrawSaveLoadCommon (); -} - - - -// -// Draw border for the savegame description -// [RH] Width of the border is variable -// -void M_DrawSaveLoadBorder (int x, int y, int len) -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - int i; - - screen->DrawTexture (TexMan["M_LSLEFT"], x-8, y+7, DTA_Clean, true, TAG_DONE); - - for (i = 0; i < len; i++) - { - screen->DrawTexture (TexMan["M_LSCNTR"], x, y+7, DTA_Clean, true, TAG_DONE); - x += 8; - } - - screen->DrawTexture (TexMan["M_LSRGHT"], x, y+7, DTA_Clean, true, TAG_DONE); - } - else - { - screen->DrawTexture (TexMan["M_FSLOT"], x, y+1, DTA_Clean, true, TAG_DONE); - } -} - -static void M_ExtractSaveData (const FSaveGameNode *node) -{ - FILE *file; - PNGHandle *png; - - M_UnloadSaveData (); - - if (node != NULL && - node->Succ != NULL && - !node->Filename.IsEmpty() && - !node->bOldVersion && - (file = fopen (node->Filename.GetChars(), "rb")) != NULL) - { - if (NULL != (png = M_VerifyPNG (file))) - { - char *time, *pcomment, *comment; - size_t commentlen, totallen, timelen; - - // Extract comment - time = M_GetPNGText (png, "Creation Time"); - pcomment = M_GetPNGText (png, "Comment"); - if (pcomment != NULL) - { - commentlen = strlen (pcomment); - } - else - { - commentlen = 0; - } - if (time != NULL) - { - timelen = strlen (time); - totallen = timelen + commentlen + 3; - } - else - { - timelen = 0; - totallen = commentlen + 1; - } - if (totallen != 0) - { - comment = new char[totallen]; - - if (timelen) - { - memcpy (comment, time, timelen); - comment[timelen] = '\n'; - comment[timelen+1] = '\n'; - timelen += 2; - } - if (commentlen) - { - memcpy (comment + timelen, pcomment, commentlen); - } - comment[timelen+commentlen] = 0; - SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment); - delete[] comment; - delete[] time; - delete[] pcomment; - } - - // Extract pic - SavePic = PNGTexture_CreateFromFile(png, node->Filename); - - delete png; - } - fclose (file); - } -} - -static void M_UnloadSaveData () -{ - if (SavePic != NULL) - { - delete SavePic; - } - if (SaveComment != NULL) - { - V_FreeBrokenLines (SaveComment); - } - - SavePic = NULL; - SaveComment = NULL; -} - -static void M_DrawSaveLoadCommon () -{ - const int savepicLeft = 10; - const int savepicTop = 54*CleanYfac; - const int savepicWidth = 216*screen->GetWidth()/640; - const int savepicHeight = 135*screen->GetHeight()/400; - - const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; - const int listboxLeft = savepicLeft + savepicWidth + 14; - const int listboxTop = savepicTop; - const int listboxWidth = screen->GetWidth() - listboxLeft - 10; - const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; - const int listboxRows = (listboxHeight1 - 1) / rowHeight; - const int listboxHeight = listboxRows * rowHeight + 1; - const int listboxRight = listboxLeft + listboxWidth; - const int listboxBottom = listboxTop + listboxHeight; - - const int commentLeft = savepicLeft; - const int commentTop = savepicTop + savepicHeight + 16; - const int commentWidth = savepicWidth; - const int commentHeight = (51+(screen->GetHeight()>200?10:0))*CleanYfac; - const int commentRight = commentLeft + commentWidth; - const int commentBottom = commentTop + commentHeight; - - FSaveGameNode *node; - int i; - bool didSeeSelected = false; - - // Draw picture area - if (gameaction == ga_loadgame || gameaction == ga_savegame) - { - return; - } - - M_DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); - if (SavePic != NULL) - { - screen->DrawTexture(SavePic, savepicLeft, savepicTop, - DTA_DestWidth, savepicWidth, - DTA_DestHeight, savepicHeight, - DTA_Masked, false, - TAG_DONE); - } - else - { - screen->Clear (savepicLeft, savepicTop, - savepicLeft+savepicWidth, savepicTop+savepicHeight, 0, 0); - - if (!SaveGames.IsEmpty ()) - { - const char *text = - (SelSaveGame == NULL || !SelSaveGame->bOldVersion) - ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); - const int textlen = SmallFont->StringWidth (text)*CleanXfac; - - screen->DrawText (SmallFont, CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, - savepicTop+(savepicHeight-rowHeight)/2, text, - DTA_CleanNoMove, true, TAG_DONE); - } - } - - // Draw comment area - M_DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); - screen->Clear (commentLeft, commentTop, commentRight, commentBottom, 0, 0); - if (SaveComment != NULL) - { - // I'm not sure why SaveComment would go NULL in this loop, but I got - // a crash report where it was NULL when i reached 1, so now I check - // for that. - for (i = 0; SaveComment != NULL && SaveComment[i].Width >= 0 && i < 6; ++i) - { - screen->DrawText (SmallFont, CR_GOLD, commentLeft, commentTop - + SmallFont->GetHeight()*i*CleanYfac, SaveComment[i].Text, - DTA_CleanNoMove, true, TAG_DONE); - } - } - - // Draw file area - do - { - M_DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); - screen->Clear (listboxLeft, listboxTop, listboxRight, listboxBottom, 0, 0); - - if (SaveGames.IsEmpty ()) - { - const char * text = GStrings("MNU_NOFILES"); - const int textlen = SmallFont->StringWidth (text)*CleanXfac; - - screen->DrawText (SmallFont, CR_GOLD, listboxLeft+(listboxWidth-textlen)/2, - listboxTop+(listboxHeight-rowHeight)/2, text, - DTA_CleanNoMove, true, TAG_DONE); - return; - } - - for (i = 0, node = TopSaveGame; - i < listboxRows && node->Succ != NULL; - ++i, node = static_cast(node->Succ)) - { - int color; - if (node->bOldVersion) - { - color = CR_BLUE; - } - else if (node->bMissingWads) - { - color = CR_ORANGE; - } - else if (node == SelSaveGame) - { - color = CR_WHITE; - } - else - { - color = CR_TAN; - } - if (node == SelSaveGame) - { - screen->Clear (listboxLeft, listboxTop+rowHeight*i, - listboxRight, listboxTop+rowHeight*(i+1), -1, - genStringEnter ? MAKEARGB(255,255,0,0) : MAKEARGB(255,0,0,255)); - didSeeSelected = true; - if (!genStringEnter) - { - screen->DrawText (SmallFont, color, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, - DTA_CleanNoMove, true, TAG_DONE); - } - else - { - screen->DrawText (SmallFont, CR_WHITE, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, savegamestring, - DTA_CleanNoMove, true, TAG_DONE); - screen->DrawText (SmallFont, CR_WHITE, - listboxLeft+1+SmallFont->StringWidth (savegamestring)*CleanXfac, - listboxTop+rowHeight*i+CleanYfac, underscore, - DTA_CleanNoMove, true, TAG_DONE); - } - } - else - { - screen->DrawText (SmallFont, color, - listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, - DTA_CleanNoMove, true, TAG_DONE); - } - } - - // This is dumb: If the selected node was not visible, - // scroll down and redraw. M_SaveLoadResponder() - // guarantees that if the node is not visible, it will - // always be below the visible list instead of above it. - // This should not really be done here, but I don't care. - - if (!didSeeSelected) - { - for (i = 1; node->Succ != NULL && node != SelSaveGame; ++i) - { - node = static_cast(node->Succ); - } - if (node->Succ == NULL) - { // SelSaveGame is invalid - didSeeSelected = true; - } - else - { - do - { - TopSaveGame = static_cast(TopSaveGame->Succ); - } while (--i); - } - } - } while (!didSeeSelected); -} - -// Draw a frame around the specified area using the view border -// frame graphics. The border is drawn outside the area, not in it. -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; - - // Draw top and bottom sides. - p = TexMan[border->t]; - screen->FlatFill(left, top - p->GetHeight(), right, top, p, true); - p = TexMan[border->b]; - screen->FlatFill(left, bottom, right, bottom + p->GetHeight(), p, true); - - // Draw left and right sides. - p = TexMan[border->l]; - screen->FlatFill(left - p->GetWidth(), top, left, bottom, p, true); - p = TexMan[border->r]; - screen->FlatFill(right, top, right + p->GetWidth(), bottom, p, true); - - // Draw beveled corners. - screen->DrawTexture (TexMan[border->tl], left-offset, top-offset, TAG_DONE); - screen->DrawTexture (TexMan[border->tr], left+width, top-offset, TAG_DONE); - screen->DrawTexture (TexMan[border->bl], left-offset, top+height, TAG_DONE); - screen->DrawTexture (TexMan[border->br], left+width, top+height, TAG_DONE); -} - -// -// Selected from DOOM menu -// -void M_LoadGame (int choice) -{ - if (netgame) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage (GStrings("CLOADNET"), NULL); - else - M_StartMessage (GStrings("LOADNET"), NULL); - return; - } - - M_SetupNextMenu (&LoadDef); - drawSkull = false; - M_ReadSaveStrings (); - TopSaveGame = static_cast(SaveGames.Head); - M_ExtractSaveData (SelSaveGame); -} - - -// -// M_SaveGame & Cie. -// -void M_DrawSave() -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - FTexture *title = TexMan["M_SAVEG"]; - screen->DrawTexture (title, - (SCREENWIDTH-title->GetScaledWidth()*CleanXfac)/2, 20*CleanYfac, - DTA_CleanNoMove, true, TAG_DONE); - } - else - { - const char *text = GStrings("MNU_SAVEGAME"); - screen->DrawText (BigFont, CR_UNTRANSLATED, - (SCREENWIDTH - BigFont->StringWidth (text)*CleanXfac)/2, 10*CleanYfac, - text, DTA_CleanNoMove, true, TAG_DONE); - } - M_DrawSaveLoadCommon (); -} - -// -// M_Responder calls this when the user is finished -// -void M_DoSave (FSaveGameNode *node) -{ - if (node != &NewSaveNode) - { - G_SaveGame (node->Filename.GetChars(), savegamestring); - } - else - { - // Find an unused filename and save as that - FString filename; - int i; - FILE *test; - - for (i = 0;; ++i) - { - filename = G_BuildSaveName ("save", i); - test = fopen (filename, "rb"); - if (test == NULL) - { - break; - } - fclose (test); - } - G_SaveGame (filename, savegamestring); - } - M_ClearMenus (); - BorderNeedRefresh = screen->GetPageCount (); -} - -// -// Selected from DOOM menu -// -void M_SaveGame (int choice) -{ - if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) - { - M_StartMessage (GStrings("SAVEDEAD"), NULL); - return; - } - - if (gamestate != GS_LEVEL) - return; - - M_SetupNextMenu(&SaveDef); - drawSkull = false; - - M_ReadSaveStrings(); - SaveGames.AddHead (&NewSaveNode); - TopSaveGame = static_cast(SaveGames.Head); - if (lastSaveSlot == NULL) - { - SelSaveGame = &NewSaveNode; - } - else - { - SelSaveGame = lastSaveSlot; - } - M_ExtractSaveData (SelSaveGame); -} - - - -// -// M_QuickSave -// -void M_QuickSaveResponse (int ch) -{ - if (ch == 'y') - { - M_DoSave (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); - } -} - -void M_QuickSave () -{ - if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) - { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); - return; - } - - if (gamestate != GS_LEVEL) - return; - - if (quickSaveSlot == NULL) - { - M_StartControlPanel(false); - M_SaveGame (0); - return; - } - if(gameinfo.gametype == GAME_Chex) - mysnprintf (tempstring, countof(tempstring), GStrings("CQSPROMPT"), quickSaveSlot->Title); - else - mysnprintf (tempstring, countof(tempstring), GStrings("QSPROMPT"), quickSaveSlot->Title); - strcpy (savegamestring, quickSaveSlot->Title); - M_StartMessage (tempstring, M_QuickSaveResponse); -} - - - -// -// M_QuickLoad -// -void M_QuickLoadResponse (int ch) -{ - if (ch == 'y') - { - M_LoadSelect (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); - } -} - - -void M_QuickLoad () -{ - if (netgame) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage (GStrings("CQLOADNET"), NULL); - else - M_StartMessage (GStrings("QLOADNET"), NULL); - return; - } - - if (quickSaveSlot == NULL) - { - M_StartControlPanel(false); - // signal that whatever gets loaded should be the new quicksave - quickSaveSlot = (FSaveGameNode *)1; - M_LoadGame (0); - return; - } - if(gameinfo.gametype == GAME_Chex) - mysnprintf (tempstring, countof(tempstring), GStrings("CQLPROMPT"), quickSaveSlot->Title); - else - mysnprintf (tempstring, countof(tempstring), GStrings("QLPROMPT"), quickSaveSlot->Title); - M_StartMessage (tempstring, M_QuickLoadResponse); -} - -// -// Read This Menus -// -void M_DrawReadThis () -{ - FTexture *tex = NULL, *prevpic = NULL; - fixed_t alpha; - - // Did the mapper choose a custom help page via MAPINFO? - if ((level.info != NULL) && level.info->f1[0] != 0) - { - tex = TexMan.FindTexture(level.info->f1); - InfoType = 1; - } - - if (tex == NULL) - { - tex = TexMan[gameinfo.infoPages[InfoType-1].GetChars()]; - } - - if (InfoType > 1) - { - prevpic = TexMan[gameinfo.infoPages[InfoType-2].GetChars()]; - } - - alpha = MIN (Scale (gametic - InfoTic, OPAQUE, TICRATE/3), OPAQUE); - if (alpha < OPAQUE && prevpic != NULL) - { - screen->DrawTexture (prevpic, 0, 0, - DTA_DestWidth, screen->GetWidth(), - DTA_DestHeight, screen->GetHeight(), - TAG_DONE); - } - screen->DrawTexture (tex, 0, 0, - DTA_DestWidth, screen->GetWidth(), - DTA_DestHeight, screen->GetHeight(), - DTA_Alpha, alpha, - TAG_DONE); -} - -// -// M_DrawMainMenu -// -void M_DrawMainMenu (void) -{ - if (gameinfo.gametype & GAME_DoomChex) - { - screen->DrawTexture (TexMan["M_DOOM"], 94, 2, DTA_Clean, true, TAG_DONE); - } - else - { - screen->DrawTexture (TexMan["M_STRIFE"], 84, 2, DTA_Clean, true, TAG_DONE); - } -} - -void M_DrawHereticMainMenu () -{ - char name[9]; - - screen->DrawTexture (TexMan["M_HTIC"], 88, 0, DTA_Clean, true, TAG_DONE); - - if (gameinfo.gametype == GAME_Hexen) - { - int frame = (MenuTime / 5) % 7; - - mysnprintf (name, countof(name), "FBUL%c0", (frame+2)%7 + 'A'); - screen->DrawTexture (TexMan[name], 37, 80, DTA_Clean, true, TAG_DONE); - - mysnprintf (name, countof(name), "FBUL%c0", frame + 'A'); - screen->DrawTexture (TexMan[name], 278, 80, DTA_Clean, true, TAG_DONE); - } - else - { - int frame = (MenuTime / 3) % 18; - - mysnprintf (name, countof(name), "M_SKL%.2d", 17 - frame); - screen->DrawTexture (TexMan[name], 40, 10, DTA_Clean, true, TAG_DONE); - - mysnprintf (name, countof(name), "M_SKL%.2d", frame); - screen->DrawTexture (TexMan[name], 232, 10, DTA_Clean, true, TAG_DONE); - } -} - -// -// M_NewGame -// -void M_DrawNewGame(void) -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - screen->DrawTexture (TexMan[gameinfo.gametype & GAME_DoomChex ? "M_NEWG" : "M_NGAME"], 96, 14, DTA_Clean, true, TAG_DONE); - screen->DrawTexture (TexMan["M_SKILL"], 54, 38, DTA_Clean, true, TAG_DONE); - } -} - -void M_NewGame(int choice) -{ - if (netgame && !demoplayback) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage (GStrings("CNEWGAME"), NULL); - else - M_StartMessage (GStrings("NEWGAME"), NULL); - return; - } - - // Set up episode menu positioning - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - EpiDef.x = 48; - EpiDef.y = 63; - } - else - { - EpiDef.x = 80; - EpiDef.y = 50; - } - if (EpiDef.numitems > 4) - { - EpiDef.y -= LINEHEIGHT; - } - epi = 0; - - if (gameinfo.gametype == GAME_Hexen && ClassMenuDef.numitems == 0) - { // [RH] Make the default entry the last class the player used. - ClassMenu.lastOn = players[consoleplayer].userinfo.PlayerClass; - if (ClassMenu.lastOn < 0) - { - ClassMenu.lastOn = 3; - } - M_SetupNextMenu (&ClassMenu); - } - // [GRB] Class select - else if (ClassMenuDef.numitems > 1) - { - ClassMenuDef.lastOn = ClassMenuDef.numitems - 1; - if (players[consoleplayer].userinfo.PlayerClass >= 0) - { - int n = 0; - for (int i = 0; i < (int)PlayerClasses.Size () && n < 7; i++) - { - if (!(PlayerClasses[i].Flags & PCF_NOMENU)) - { - if (i == players[consoleplayer].userinfo.PlayerClass) - { - ClassMenuDef.lastOn = n; - break; - } - n++; - } - } - } - - PickPlayerClass (); - - PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; - PlayerTics = PlayerState->GetTics(); - - if (FireTexture == NULL) - { - FireTexture = new FBackdropTexture; - } - M_SetupNextMenu (&ClassMenuDef); - } - else if (EpiDef.numitems <= 1) - { - if (AllSkills.Size() == 1) - { - M_ChooseSkill(0); - } - else if (EpisodeNoSkill[0]) - { - M_ChooseSkill(AllSkills.Size() == 2? 1:2); - } - else - { - M_StartupSkillMenu(NULL); - } - } - else - { - M_SetupNextMenu (&EpiDef); - } -} - -//========================================================================== -// -// DrawClassMenu -// -//========================================================================== - -static void DrawClassMenu(void) -{ - char name[9]; - int classnum; - - static const char boxLumpName[3][7] = - { - "M_FBOX", - "M_CBOX", - "M_MBOX" - }; - static const char walkLumpName[3][10] = - { - "M_FWALK%d", - "M_CWALK%d", - "M_MWALK%d" - }; - - const char *text = GStrings("MNU_CHOOSECLASS"); - screen->DrawText (BigFont, CR_UNTRANSLATED, 34, 24, text, DTA_Clean, true, TAG_DONE); - classnum = itemOn; - if (classnum > 2) - { - classnum = (MenuTime>>2) % 3; - } - screen->DrawTexture (TexMan[boxLumpName[classnum]], 174, 8, DTA_Clean, true, TAG_DONE); - - mysnprintf (name, countof(name), walkLumpName[classnum], ((MenuTime >> 3) & 3) + 1); - screen->DrawTexture (TexMan[name], 174+24, 8+12, DTA_Clean, true, TAG_DONE); -} - -// [GRB] Class select drawer -static void M_DrawClassMenu () -{ - int tit_y = 15; - const char * text = GStrings("MNU_CHOOSECLASS"); - - if (ClassMenuDef.numitems > 4 && gameinfo.gametype & GAME_Raven) - tit_y = 2; - - screen->DrawText (BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - 160 - BigFont->StringWidth (text)/2, - tit_y, - text, DTA_Clean, true, TAG_DONE); - - int x = (200-160)*CleanXfac+(SCREENWIDTH>>1); - int y = (ClassMenuDef.y-100)*CleanYfac+(SCREENHEIGHT>>1); - - if (!FireTexture) - { - screen->Clear (x, y, x + 72 * CleanXfac, y + 80 * CleanYfac-1, 0, 0); - } - else - { - screen->DrawTexture (FireTexture, x, y - 1, - DTA_DestWidth, 72 * CleanXfac, - DTA_DestHeight, 80 * CleanYfac, - DTA_Translation, &FireRemap, - DTA_Masked, true, - TAG_DONE); - } - - M_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); - - spriteframe_t *sprframe = &SpriteFrames[sprites[PlayerState->sprite].spriteframes + PlayerState->GetFrame()]; - fixed_t scaleX = GetDefaultByType (PlayerClass->Type)->scaleX; - fixed_t scaleY = GetDefaultByType (PlayerClass->Type)->scaleY; - - if (sprframe != NULL) - { - FTexture *tex = TexMan(sprframe->Texture[0]); - if (tex != NULL && tex->UseType != FTexture::TEX_Null) - { - screen->DrawTexture (tex, - x + 36*CleanXfac, y + 71*CleanYfac, - DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, scaleX), - DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, scaleY), - TAG_DONE); - } - } -} - -//--------------------------------------------------------------------------- -// -// PROC DrawSkillMenu -// -//--------------------------------------------------------------------------- - -static void DrawHexenSkillMenu() -{ - screen->DrawText (BigFont, CR_UNTRANSLATED, 74, 16, GStrings("MNU_CHOOSESKILL"), DTA_Clean, true, TAG_DONE); -} - - -// -// M_Episode -// -void M_DrawEpisode () -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - screen->DrawTexture (TexMan["M_EPISOD"], 54, 38, DTA_Clean, true, TAG_DONE); - } -} - -static int confirmskill; - -void M_VerifyNightmare (int ch) -{ - if (ch != 'y') - return; - - G_DeferedInitNew (EpisodeMaps[epi], confirmskill); - if (gamestate == GS_FULLCONSOLE) - { - gamestate = GS_HIDECONSOLE; - gameaction = ga_newgame; - } - M_ClearMenus (); -} - -void M_ChooseSkill (int choice) -{ - if (AllSkills[choice].MustConfirm) - { - const char *msg = AllSkills[choice].MustConfirmText; - if (*msg==0) msg = GStrings("NIGHTMARE"); - if (*msg=='$') msg = GStrings(msg+1); - confirmskill = choice; - M_StartMessage (msg, M_VerifyNightmare); - return; - } - - G_DeferedInitNew (EpisodeMaps[epi], choice); - if (gamestate == GS_FULLCONSOLE) - { - gamestate = GS_HIDECONSOLE; - gameaction = ga_newgame; - } - M_ClearMenus (); -} - -void M_Episode (int choice) -{ - if ((gameinfo.flags & GI_SHAREWARE) && choice) - { - if (gameinfo.gametype == GAME_Doom) - { - M_StartMessage(GStrings("SWSTRING"), NULL); - //M_SetupNextMenu(&ReadDef); - } - else if (gameinfo.gametype == GAME_Chex) - { - M_StartMessage(GStrings("CSWSTRING"), NULL); - } - else - { - showSharewareMessage = 3*TICRATE; - } - return; - } - - epi = choice; - - if (AllSkills.Size() == 1) - { - saved_playerclass = NULL; - M_ChooseSkill(0); - return; - } - else if (EpisodeNoSkill[choice]) - { - saved_playerclass = NULL; - M_ChooseSkill(AllSkills.Size() == 2? 1:2); - return; - } - M_StartupSkillMenu(saved_playerclass); - saved_playerclass = NULL; -} - -//========================================================================== -// -// SCClass -// -//========================================================================== - -static void SCClass (int option) -{ - if (netgame) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage (GStrings("CNEWGAME"), NULL); - else - M_StartMessage (GStrings("NEWGAME"), NULL); - return; - } - - if (option == 3) - playerclass = "Random"; - else - playerclass = PlayerClasses[option].Type->Meta.GetMetaString (APMETA_DisplayName); - - if (EpiDef.numitems > 1) - { - saved_playerclass = playerclass; - M_SetupNextMenu (&EpiDef); - } - else if (AllSkills.Size() == 1) - { - M_ChooseSkill(0); - } - else if (!EpisodeNoSkill[0]) - { - M_StartupSkillMenu(playerclass); - } - else - { - M_ChooseSkill(AllSkills.Size() == 2? 1:2); - } -} - -// [GRB] -static void M_ChooseClass (int choice) -{ - if (netgame) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage (GStrings("CNEWGAME"), NULL); - else - M_StartMessage (GStrings("NEWGAME"), NULL); - return; - } - - playerclass = (choice < ClassMenuDef.numitems-1) ? ClassMenuItems[choice].name : "Random"; - - if (EpiDef.numitems > 1) - { - saved_playerclass = playerclass; - M_SetupNextMenu (&EpiDef); - } - else if (AllSkills.Size() == 1) - { - M_ChooseSkill(0); - } - else if (EpisodeNoSkill[0]) - { - M_ChooseSkill(AllSkills.Size() == 2? 1:2); - } - else - { - M_StartupSkillMenu(playerclass); - } -} - - -void M_Options (int choice) -{ - OptionsActive = M_StartOptionsMenu (); -} - - - - -// -// M_EndGame -// -void M_EndGameResponse(int ch) -{ - if (ch != 'y') - return; - - currentMenu->lastOn = itemOn; - M_ClearMenus (); - D_StartTitle (); -} - -void M_EndGame(int choice) -{ - choice = 0; - if (!usergame) - { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); - return; - } - - if (netgame) - { - if(gameinfo.gametype == GAME_Chex) - M_StartMessage(GStrings("CNETEND"), NULL); - else - M_StartMessage(GStrings("NETEND"), NULL); - return; - } - - if(gameinfo.gametype == GAME_Chex) - M_StartMessage(GStrings("CENDGAME"), M_EndGameResponse); - else - M_StartMessage(GStrings("ENDGAME"), M_EndGameResponse); -} - - - - -// -// M_ReadThis -// -void M_ReadThis (int choice) -{ - drawSkull = false; - InfoType = 1; - InfoTic = gametic; - M_SetupNextMenu (&ReadDef); -} - -void M_ReadThisMore (int choice) -{ - InfoType++; - InfoTic = gametic; - if ((level.info != NULL && level.info->f1[0] != 0) || InfoType > int(gameinfo.infoPages.Size())) - { - M_FinishReadThis (0); - } -} - -void M_FinishReadThis (int choice) -{ - drawSkull = true; - M_PopMenuStack (); -} - -// -// M_QuitGame -// - -void M_QuitResponse(int ch) -{ - if (ch != 'y') - return; - if (!netgame) - { - if (gameinfo.quitSound.IsNotEmpty()) - { - S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, 1, ATTN_NONE); - I_WaitVBL (105); - } - } - ST_Endoom(); -} - -void M_QuitGame (int choice) -{ - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - int quitmsg = 0; - if (gameinfo.gametype == GAME_Doom) - { - quitmsg = gametic % (NUM_QUITDOOMMESSAGES + 1); - } - else if (gameinfo.gametype == GAME_Strife) - { - quitmsg = gametic % (NUM_QUITSTRIFEMESSAGES + 1); - if (quitmsg != 0) quitmsg += NUM_QUITDOOMMESSAGES; - } - else - { - quitmsg = gametic % (NUM_QUITCHEXMESSAGES + 1); - if (quitmsg != 0) quitmsg += NUM_QUITDOOMMESSAGES + NUM_QUITSTRIFEMESSAGES; - } - - if (quitmsg != 0) - { - EndString.Format("QUITMSG%d", quitmsg); - EndString.Format("%s\n\n%s", GStrings(EndString), GStrings("DOSY")); - } - else - { - EndString.Format("%s\n\n%s", GStrings("QUITMSG"), GStrings("DOSY")); - } - } - else - { - EndString = GStrings("RAVENQUITMSG"); - } - - M_StartMessage (EndString, M_QuitResponse); -} - - -// -// [RH] Player Setup Menu code -// -void M_PlayerSetup (void) -{ - OptionsActive = false; - drawSkull = true; - strcpy (savegamestring, name); - M_DemoNoPlay = true; - if (demoplayback) - G_CheckDemoStatus (); - M_SetupNextMenu (&PSetupDef); - if (players[consoleplayer].mo != NULL) - { - PlayerClass = &PlayerClasses[players[consoleplayer].CurrentPlayerClass]; - } - PlayerSkin = players[consoleplayer].userinfo.skin; - R_GetPlayerTranslation (players[consoleplayer].userinfo.color, - P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), - &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); - PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; - PlayerTics = PlayerState->GetTics(); - if (FireTexture == NULL) - { - FireTexture = new FBackdropTexture; - } - P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); -} - -static void M_PlayerSetupTicker (void) -{ - // Based on code in f_finale.c - FPlayerClass *oldclass = PlayerClass; - - if (currentMenu == &ClassMenuDef) - { - int item; - - if (itemOn < ClassMenuDef.numitems-1) - item = itemOn; - else - item = (MenuTime>>2) % (ClassMenuDef.numitems-1); - - PlayerClass = &PlayerClasses[D_PlayerClassToInt (ClassMenuItems[item].name)]; - P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); - } - else - { - PickPlayerClass (); - } - - if (PlayerClass != oldclass) - { - PlayerState = GetDefaultByType (PlayerClass->Type)->SeeState; - PlayerTics = PlayerState->GetTics(); - - PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(PlayerClass - &PlayerClasses[0])); - R_GetPlayerTranslation (players[consoleplayer].userinfo.color, - P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), - &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); - } - - if (PlayerState->GetTics () != -1 && PlayerState->GetNextState () != NULL) - { - if (--PlayerTics > 0) - return; - - PlayerState = PlayerState->GetNextState(); - PlayerTics = PlayerState->GetTics(); - } -} - - -static void M_DrawPlayerSlider (int x, int y, int cur) -{ - const int range = 255; - - x = (x - 160) * CleanXfac + screen->GetWidth() / 2; - y = (y - 100) * CleanYfac + screen->GetHeight() / 2; - - screen->DrawText (ConFont, CR_WHITE, x, y, - "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); - screen->DrawText (ConFont, CR_ORANGE, x + (5 + (int)((cur * 78) / range)) * CleanXfac, y, - "\x13", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); -} - -static void M_PlayerSetupDrawer () -{ - const int LINEHEIGHT = PLAYERSETUP_LINEHEIGHT; - int x, xo, yo; - EColorRange label, value; - DWORD color; - - if (!(gameinfo.gametype & (GAME_DoomStrifeChex))) - { - xo = 5; - yo = 5; - label = CR_GREEN; - value = CR_UNTRANSLATED; - } - else - { - xo = yo = 0; - label = CR_UNTRANSLATED; - value = CR_GREY; - } - - // Draw title - const char *text = GStrings("MNU_PLAYERSETUP"); - screen->DrawText (BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - 160 - BigFont->StringWidth (text)/2, - 15, - text, DTA_Clean, true, TAG_DONE); - - - // Draw player name box - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y+yo, "Name", DTA_Clean, true, TAG_DONE); - M_DrawSaveLoadBorder (PSetupDef.x + 56, PSetupDef.y, MAXPLAYERNAME+1); - screen->DrawText (SmallFont, CR_UNTRANSLATED, PSetupDef.x + 56 + xo, PSetupDef.y+yo, savegamestring, - DTA_Clean, true, TAG_DONE); - - // Draw cursor for player name box - if (genStringEnter) - screen->DrawText (SmallFont, CR_UNTRANSLATED, - PSetupDef.x + SmallFont->StringWidth(savegamestring) + 56+xo, - PSetupDef.y + yo, underscore, DTA_Clean, true, TAG_DONE); - - // Draw player team setting - x = SmallFont->StringWidth ("Team") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT+yo, "Team", - DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT+yo, - !TeamLibrary.IsValidTeam (players[consoleplayer].userinfo.team) ? "None" : - Teams[players[consoleplayer].userinfo.team].GetName (), - DTA_Clean, true, TAG_DONE); - - // Draw player character - { - int x = 320 - 88 - 32 + xo, y = PSetupDef.y + LINEHEIGHT*3 - 18 + yo; - - x = (x-160)*CleanXfac+(SCREENWIDTH>>1); - y = (y-100)*CleanYfac+(SCREENHEIGHT>>1); - if (!FireTexture) - { - screen->Clear (x, y, x + 72 * CleanXfac, y + 80 * CleanYfac-1, 0, 0); - } - else - { - screen->DrawTexture (FireTexture, x, y - 1, - DTA_DestWidth, 72 * CleanXfac, - DTA_DestHeight, 80 * CleanYfac, - DTA_Translation, &FireRemap, - DTA_Masked, false, - TAG_DONE); - } - - M_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); - } - { - spriteframe_t *sprframe; - fixed_t ScaleX, ScaleY; - - if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.PlayerClass == -1 || - PlayerState->sprite != GetDefaultByType (PlayerClass->Type)->SpawnState->sprite) - { - sprframe = &SpriteFrames[sprites[PlayerState->sprite].spriteframes + PlayerState->GetFrame()]; - ScaleX = GetDefaultByType(PlayerClass->Type)->scaleX; - ScaleY = GetDefaultByType(PlayerClass->Type)->scaleY; - } - else - { - sprframe = &SpriteFrames[sprites[skins[PlayerSkin].sprite].spriteframes + PlayerState->GetFrame()]; - ScaleX = skins[PlayerSkin].ScaleX; - ScaleY = skins[PlayerSkin].ScaleY; - } - - if (sprframe != NULL) - { - FTexture *tex = TexMan(sprframe->Texture[0]); - if (tex != NULL && tex->UseType != FTexture::TEX_Null) - { - if (tex->Rotations != 0xFFFF) - { - tex = TexMan(SpriteFrames[tex->Rotations].Texture[PlayerRotation]); - } - screen->DrawTexture (tex, - (320 - 52 - 32 + xo - 160)*CleanXfac + (SCREENWIDTH)/2, - (PSetupDef.y + LINEHEIGHT*3 + 57 - 104)*CleanYfac + (SCREENHEIGHT/2), - DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, ScaleX), - DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, ScaleY), - DTA_Translation, translationtables[TRANSLATION_Players](MAXPLAYERS), - TAG_DONE); - } - } - - const char *str = "PRESS " TEXTCOLOR_WHITE "SPACE"; - screen->DrawText (SmallFont, CR_GOLD, 320 - 52 - 32 - - SmallFont->StringWidth (str)/2, - PSetupDef.y + LINEHEIGHT*3 + 76, str, - DTA_Clean, true, TAG_DONE); - str = PlayerRotation ? "TO SEE FRONT" : "TO SEE BACK"; - screen->DrawText (SmallFont, CR_GOLD, 320 - 52 - 32 - - SmallFont->StringWidth (str)/2, - PSetupDef.y + LINEHEIGHT*3 + 76 + SmallFont->GetHeight (), str, - DTA_Clean, true, TAG_DONE); - } - - // Draw player color selection and sliders - FPlayerColorSet *colorset = P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset); - x = SmallFont->StringWidth("Color") + 8 + PSetupDef.x; - screen->DrawText(SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*2+yo, "Color", DTA_Clean, true, TAG_DONE); - screen->DrawText(SmallFont, value, x, PSetupDef.y + LINEHEIGHT*2+yo, - colorset != NULL ? colorset->Name.GetChars() : "Custom", DTA_Clean, true, TAG_DONE); - - // Only show the sliders for a custom color set. - if (colorset == NULL) - { - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*2.875)+yo, "Red", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*3.5)+yo, "Green", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + int(LINEHEIGHT*4.125)+yo, "Blue", DTA_Clean, true, TAG_DONE); - - x = SmallFont->StringWidth ("Green") + 8 + PSetupDef.x; - color = players[consoleplayer].userinfo.color; - - M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*2.875)+yo, RPART(color)); - M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*3.5)+yo, GPART(color)); - M_DrawPlayerSlider (x, PSetupDef.y + int(LINEHEIGHT*4.125)+yo, BPART(color)); - } - - // [GRB] Draw class setting - int pclass = players[consoleplayer].userinfo.PlayerClass; - x = SmallFont->StringWidth ("Class") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*5+yo, "Class", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*5+yo, - pclass == -1 ? "Random" : PlayerClasses[pclass].Type->Meta.GetMetaString (APMETA_DisplayName), - DTA_Clean, true, TAG_DONE); - - // Draw skin setting - x = SmallFont->StringWidth ("Skin") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*6+yo, "Skin", DTA_Clean, true, TAG_DONE); - if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.PlayerClass == -1) - { - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*6+yo, "Base", DTA_Clean, true, TAG_DONE); - } - else - { - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*6+yo, - skins[PlayerSkin].name, DTA_Clean, true, TAG_DONE); - } - - // Draw gender setting - x = SmallFont->StringWidth ("Gender") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*7+yo, "Gender", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*7+yo, - genders[players[consoleplayer].userinfo.gender], DTA_Clean, true, TAG_DONE); - - // Draw autoaim setting - x = SmallFont->StringWidth ("Autoaim") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*8+yo, "Autoaim", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*8+yo, - autoaim == 0 ? "Never" : - autoaim <= 0.25 ? "Very Low" : - autoaim <= 0.5 ? "Low" : - autoaim <= 1 ? "Medium" : - autoaim <= 2 ? "High" : - autoaim <= 3 ? "Very High" : "Always", - DTA_Clean, true, TAG_DONE); -} - -// A 32x32 cloud rendered with Photoshop, plus some other filters -static BYTE pattern1[1024] = -{ - 5, 9, 7,10, 9,15, 9, 7, 8,10, 5, 3, 5, 7, 9, 8,14, 8, 4, 7, 8, 9, 5, 7,14, 7, 0, 7,13,13, 9, 6, - 2, 7, 9, 7, 7,10, 8, 8,11,10, 6, 7,10, 7, 5, 6, 6, 4, 7,13,15,16,11,15,11, 8, 0, 4,13,22,17,11, - 5, 9, 9, 7, 9,10, 4, 3, 6, 7, 8, 6, 5, 4, 2, 2, 1, 4, 6,11,15,15,14,13,17, 9, 5, 9,11,12,17,20, - 9,16, 9, 8,12,13, 7, 3, 7, 9, 5, 4, 2, 5, 5, 5, 7,11, 6, 7, 6,13,17,10,10, 9,12,17,14,12,16,15, - 15,13, 5, 3, 9,10, 4,10,12,12, 7, 9, 8, 8, 8,10, 7, 6, 5, 5, 5, 6,11, 9, 3,13,16,18,21,16,23,18, - 23,13, 0, 0, 0, 0, 0,12,18,14,15,16,13, 7, 7, 5, 9, 6, 6, 8, 4, 0, 0, 0, 0,14,19,17,14,20,21,25, - 19,20,14,13, 7, 5,13,19,14,13,17,15,14, 7, 3, 5, 6,11, 7, 7, 8, 8,10, 9, 9,18,17,15,14,15,18,16, - 16,29,24,23,18, 9,17,20,11, 5,12,15,15,12, 6, 3, 4, 6, 7,10,13,18,18,19,16,12,17,19,23,16,14,14, - 9,18,20,26,19, 5,18,18,10, 5,12,15,14,17,11, 6,11, 9,10,13,10,20,24,20,21,20,14,18,15,22,20,19, - 0, 6,16,18, 8, 7,15,18,10,13,17,17,13,11,15,11,19,12,13,10, 4,15,19,21,21,24,14, 9,17,20,24,17, - 18,17, 7, 7,16,21,22,15, 5,14,20,14,13,21,13, 8,12,14, 7, 8,11,15,13,11,16,17, 7, 5,12,17,19,14, - 25,23,17,16,23,18,15, 7, 0, 6,11, 6,11,15,11, 7,12, 7, 4,10,16,13, 7, 7,15,13, 9,15,21,14, 5, 0, - 18,22,21,21,21,22,12, 6,14,20,15, 6,10,19,13, 8, 7, 3, 7,12,14,16, 9,12,22,15,12,18,24,19,17, 9, - 0,15,18,21,17,25,14,13,19,21,21,11, 6,13,16,16,12,10,12,11,13,20,14,13,18,13, 9,15,16,25,31,20, - 5,20,24,16, 7,14,14,11,18,19,19, 6, 0, 5,11,14,17,16,19,14,15,21,19,15,14,14, 8, 0, 7,24,18,16, - 9,17,15, 7, 6,14,12, 7,14,16,11, 4, 7, 6,13,16,15,13,12,20,21,20,21,17,18,26,14, 0,13,23,21,11, - 9,12,18,11,15,21,13, 8,13,13,10, 7,13, 8, 8,19,13, 7, 4,15,19,18,14,12,14,15, 8, 6,16,22,22,15, - 9,17,14,19,15,14,15, 9,11, 9, 6, 8,14,13,13,12, 5, 0, 0, 6,12,13, 7, 7, 9, 7, 0,12,21,16,15,18, - 15,16,18,11, 6, 8,15, 9, 2, 0, 5,10,10,16, 9, 0, 4,12,15, 9,12, 9, 7, 7,12, 7, 0, 6,12, 6, 9,13, - 12,19,15,14,11, 7, 8, 9,12,10, 5, 5, 7,12,12,10,14,16,16,11, 8,12,10,12,10, 8,10,10,14,12,16,16, - 16,17,20,22,12,15,12,14,19,11, 6, 5,10,13,17,17,21,19,15, 9, 6, 9,15,18,10,10,18,14,20,15,16,17, - 11,19,19,18,19,14,17,13,12,12, 7,11,18,17,16,15,19,19,10, 2, 0, 8,15,12, 8,11,12,10,19,20,19,19, - 6,14,18,13,13,16,16,12, 5, 8,10,12,10,13,18,12, 9,10, 7, 6, 5,11, 8, 6, 7,13,16,13,10,15,20,14, - 0, 5,12,12, 4, 0, 9,16, 9,10,12, 8, 0, 9,13, 9, 0, 2, 4, 7,10, 6, 7, 3, 4,11,16,18,10,11,21,21, - 16,13,11,15, 8, 0, 5, 9, 8, 7, 6, 3, 0, 9,17, 9, 0, 0, 0, 3, 5, 4, 3, 5, 7,15,16,16,17,14,22,22, - 24,14,15,12, 9, 0, 5,10, 8, 4, 7,12,10,11,12, 7, 6, 8, 6, 5, 7, 8, 8,11,13,10,15,14,12,18,20,16, - 16,17,17,18,12, 9,12,16,10, 5, 6,20,13,15, 8, 4, 8, 9, 8, 7, 9,11,12,17,16,16,11,10, 9,10, 5, 0, - 0,14,18,18,15,16,14, 9,10, 9, 9,15,14,10, 4, 6,10, 8, 8, 7,10, 9,10,16,18,10, 0, 0, 7,12,10, 8, - 0,14,19,14, 9,11,11, 8, 8,10,15, 9,10, 7, 4,10,13, 9, 7, 5, 5, 7, 7, 7,13,13, 5, 5,14,22,18,16, - 0,10,14,10, 3, 6, 5, 6, 8, 9, 8, 9, 5, 9, 8, 9, 6, 8, 8, 8, 1, 0, 0, 0, 9,17,12,12,17,19,20,13, - 6,11,17,11, 5, 5, 8,10, 6, 5, 6, 6, 3, 7, 9, 7, 6, 8,12,10, 4, 8, 6, 6,11,16,16,15,16,17,17,16, - 11, 9,10,10, 5, 6,12,10, 5, 1, 6,10, 5, 3, 3, 5, 4, 7,15,10, 7,13, 7, 8,15,11,15,15,15, 8,11,15, -}; - -// Just a 32x32 cloud rendered with the standard Photoshop filter -static BYTE pattern2[1024] = -{ - 9, 9, 8, 8, 8, 8, 6, 6,13,13,11,21,19,21,23,18,23,24,19,19,24,17,18,12, 9,14, 8,12,12, 5, 8, 6, - 11,10, 6, 7, 8, 8, 9,13,10,11,17,15,23,22,23,22,20,26,27,26,17,21,20,14,12, 8,11, 8,11, 7, 8, 7, - 6, 9,13,13,10, 9,13, 7,12,13,16,19,16,20,22,25,22,25,27,22,21,23,15,10,14,14,15,13,12, 8,12, 6, - 6, 7,12,12,12,16, 9,12,12,15,16,11,21,24,19,24,23,26,28,27,26,21,14,15, 7, 7,10,15,12,11,10, 9, - 7,14,11,16,12,18,16,14,16,14,11,14,15,21,23,17,20,18,26,24,27,18,20,11,11,14,10,17,17,10, 6,10, - 13, 9,14,10,13,11,14,15,18,15,15,12,19,19,20,18,22,20,19,22,19,19,19,20,17,15,15,11,16,14,10, 8, - 13,16,12,16,17,19,17,18,15,19,14,18,15,14,15,17,21,19,23,18,23,22,18,18,17,15,15,16,12,12,15,10, - 10,12,14,10,16,11,18,15,21,20,20,17,18,19,16,19,14,20,19,14,19,25,22,21,22,24,18,12, 9, 9, 8, 6, - 10,10,13, 9,15,13,20,19,22,18,18,17,17,21,21,13,13,12,19,18,16,17,27,26,22,23,20,17,12,11, 8, 9, - 7,13,14,15,11,13,18,22,19,23,23,20,22,24,21,14,12,16,17,19,18,18,22,18,24,23,19,17,16,14, 8, 7, - 12,12, 8, 8,16,20,26,25,28,28,22,29,23,22,21,18,13,16,15,15,20,17,25,24,19,17,17,17,15,10, 8, 9, - 7,12,15,11,17,20,25,25,25,29,30,31,28,26,18,16,17,18,20,21,22,20,23,19,18,19,10,16,16,11,11, 8, - 5, 6, 8,14,14,17,17,21,27,23,27,31,27,22,23,21,19,19,21,19,20,19,17,22,13,17,12,15,10,10,12, 6, - 8, 9, 8,14,15,16,15,18,27,26,23,25,23,22,18,21,20,17,19,20,20,16,20,14,15,13,12, 8, 8, 7,11,13, - 7, 6,11,11,11,13,15,22,25,24,26,22,24,26,23,18,24,24,20,18,20,16,17,12,12,12,10, 8,11, 9, 6, 8, - 9,10, 9, 6, 5,14,16,19,17,21,26,20,23,19,19,17,20,21,26,25,23,21,17,13,12, 5,13,11, 7,12,10,12, - 6, 5, 4,10,11, 9,10,13,17,20,20,18,23,26,27,20,21,24,20,19,24,20,18,10,11, 3, 6,13, 9, 6, 8, 8, - 1, 2, 2,11,13,13,11,16,16,16,19,21,20,23,22,28,21,20,19,18,23,16,18, 7, 5, 9, 7, 6, 5,10, 8, 8, - 0, 0, 6, 9,11,15,12,12,19,18,19,26,22,24,26,30,23,22,22,16,20,19,12,12, 3, 4, 6, 5, 4, 7, 2, 4, - 2, 0, 0, 7,11, 8,14,13,15,21,26,28,25,24,27,26,23,24,22,22,15,17,12, 8,10, 7, 7, 4, 0, 5, 0, 1, - 1, 2, 0, 1, 9,14,13,10,19,24,22,29,30,28,30,30,31,23,24,19,17,14,13, 8, 8, 8, 1, 4, 0, 0, 0, 3, - 5, 2, 4, 2, 9, 8, 8, 8,18,23,20,27,30,27,31,25,28,30,28,24,24,15,11,14,10, 3, 4, 3, 0, 0, 1, 3, - 9, 3, 4, 3, 5, 6, 8,13,14,23,21,27,28,27,28,27,27,29,30,24,22,23,13,15, 8, 6, 2, 0, 4, 3, 4, 1, - 6, 5, 5, 3, 9, 3, 6,14,13,16,23,26,28,23,30,31,28,29,26,27,21,20,15,15,13, 9, 1, 0, 2, 0, 5, 8, - 8, 4, 3, 7, 2, 0,10, 7,10,14,21,21,29,28,25,27,30,28,25,24,27,22,19,13,10, 5, 0, 0, 0, 0, 0, 7, - 7, 6, 7, 0, 2, 2, 5, 6,15,11,19,24,22,29,27,31,30,30,31,28,23,18,14,14, 7, 5, 0, 0, 1, 0, 1, 0, - 5, 5, 5, 0, 0, 4, 5,11, 7,10,13,20,21,21,28,31,28,30,26,28,25,21, 9,12, 3, 3, 0, 2, 2, 2, 0, 1, - 3, 3, 0, 2, 0, 3, 5, 3,11,11,16,19,19,27,26,26,30,27,28,26,23,22,16, 6, 2, 2, 3, 2, 0, 2, 4, 0, - 0, 0, 0, 3, 3, 1, 0, 4, 5, 9,11,16,24,20,28,26,28,24,28,25,22,21,16, 5, 7, 5, 7, 3, 2, 3, 3, 6, - 0, 0, 2, 0, 2, 0, 4, 3, 8,12, 9,17,16,23,23,27,27,22,26,22,21,21,13,14, 5, 3, 7, 3, 2, 4, 6, 1, - 2, 5, 6, 4, 0, 1, 5, 8, 7, 6,15,17,22,20,24,28,23,25,20,21,18,16,13,15,13,10, 8, 5, 5, 9, 3, 7, - 7, 7, 0, 5, 1, 6, 7, 9,12, 9,12,21,22,25,24,22,23,25,24,18,24,22,17,13,10, 9,10, 9, 6,11, 6, 5, -}; - -const FTexture::Span FBackdropTexture::DummySpan[2] = { { 0, 160 }, { 0, 0 } }; - -FBackdropTexture::FBackdropTexture() -{ - Width = 144; - Height = 160; - WidthBits = 8; - HeightBits = 8; - WidthMask = 255; - LastRenderTic = 0; - - time1 = ANGLE_1*180; - time2 = ANGLE_1*56; - time3 = ANGLE_1*99; - time4 = ANGLE_1*1; - t1ang = ANGLE_90; - t2ang = 0; - z1ang = 0; - z2ang = ANGLE_90/2; -} - -bool FBackdropTexture::CheckModified() -{ - return LastRenderTic != gametic; -} - -void FBackdropTexture::Unload() -{ -} - -const BYTE *FBackdropTexture::GetColumn(unsigned int column, const Span **spans_out) -{ - if (LastRenderTic != gametic) - { - Render(); - } - column = clamp(column, 0u, 143u); - if (spans_out != NULL) - { - *spans_out = DummySpan; - } - return Pixels + column*160; -} - -const BYTE *FBackdropTexture::GetPixels() -{ - if (LastRenderTic != gametic) - { - Render(); - } - return Pixels; -} - -// This is one plasma and two rotozoomers. I think it turned out quite awesome. -void FBackdropTexture::Render() -{ - BYTE *from; - int width, height, pitch; - - width = 160; - height = 144; - pitch = width; - - int x, y; - - const angle_t a1add = ANGLE_1/2; - const angle_t a2add = ANGLE_MAX-ANGLE_1; - const angle_t a3add = ANGLE_1*5/7; - const angle_t a4add = ANGLE_MAX-ANGLE_1*4/3; - - const angle_t t1add = ANGLE_MAX-ANGLE_1*2; - const angle_t t2add = ANGLE_MAX-ANGLE_1*3+ANGLE_1/6; - const angle_t t3add = ANGLE_1*16/7; - const angle_t t4add = ANGLE_MAX-ANGLE_1*2/3; - const angle_t x1add = 5<>ANGLETOFINESHIFT]>>2)+FRACUNIT/2; - fixed_t z2 = (finecosine[z1ang>>ANGLETOFINESHIFT]>>2)+FRACUNIT*3/4; - - tc = MulScale5 (finecosine[t1ang>>ANGLETOFINESHIFT], z1); - ts = MulScale5 (finesine[t1ang>>ANGLETOFINESHIFT], z1); - uc = MulScale5 (finecosine[t2ang>>ANGLETOFINESHIFT], z2); - us = MulScale5 (finesine[t2ang>>ANGLETOFINESHIFT], z2); - - ltx = -width/2*tc; - lty = -width/2*ts; - lux = -width/2*uc; - luy = -width/2*us; - - for (y = 0; y < height; ++y) - { - a1 = time1; - a2 = time2; - c3 = finecosine[a3>>ANGLETOFINESHIFT]; - c4 = finecosine[a4>>ANGLETOFINESHIFT]; - tx = ltx - (y-height/2)*ts; - ty = lty + (y-height/2)*tc; - ux = lux - (y-height/2)*us; - uy = luy + (y-height/2)*uc; - for (x = 0; x < width; ++x) - { - c1 = finecosine[a1>>ANGLETOFINESHIFT]; - c2 = finecosine[a2>>ANGLETOFINESHIFT]; - from[x] = ((c1 + c2 + c3 + c4) >> (FRACBITS+3-7)) + 128 // plasma - + pattern1[(tx>>27)+((ty>>22)&992)] // rotozoomer 1 - + pattern2[(ux>>27)+((uy>>22)&992)]; // rotozoomer 2 - tx += tc; - ty += ts; - ux += uc; - uy += us; - a1 += a1add; - a2 += a2add; - } - a3 += a3add; - a4 += a4add; - from += pitch; - } - - time1 += t1add; - time2 += t2add; - time3 += t3add; - time4 += t4add; - t1ang += x1add; - t2ang += x2add; - z1ang += z1add; - z2ang += z2add; - - LastRenderTic = gametic; -} - -static void M_ChangeClass (int choice) -{ - if (PlayerClasses.Size () == 1) - { - return; - } - - int type = players[consoleplayer].userinfo.PlayerClass; - - if (!choice) - type = (type < 0) ? (int)PlayerClasses.Size () - 1 : type - 1; - else - type = (type < (int)PlayerClasses.Size () - 1) ? type + 1 : -1; - - cvar_set ("playerclass", type < 0 ? "Random" : - PlayerClasses[type].Type->Meta.GetMetaString (APMETA_DisplayName)); -} - -static void M_ChangeSkin (int choice) -{ - if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.PlayerClass == -1) - { - return; - } - - do - { - if (!choice) - PlayerSkin = (PlayerSkin == 0) ? (int)numskins - 1 : PlayerSkin - 1; - else - PlayerSkin = (PlayerSkin < (int)numskins - 1) ? PlayerSkin + 1 : 0; - } while (!PlayerClass->CheckSkin (PlayerSkin)); - - R_GetPlayerTranslation (players[consoleplayer].userinfo.color, - P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), - &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); - - cvar_set ("skin", skins[PlayerSkin].name); -} - -static void M_ChangeGender (int choice) -{ - int gender = players[consoleplayer].userinfo.gender; - - if (!choice) - gender = (gender == 0) ? 2 : gender - 1; - else - gender = (gender == 2) ? 0 : gender + 1; - - cvar_set ("gender", genders[gender]); -} - -static void M_ChangeAutoAim (int choice) -{ - static const float ranges[] = { 0, 0.25, 0.5, 1, 2, 3, 5000 }; - float aim = autoaim; - int i; - - if (!choice) { - // Select a lower autoaim - - for (i = 6; i >= 1; i--) - { - if (aim >= ranges[i]) - { - aim = ranges[i - 1]; - break; - } - } - } - else - { - // Select a higher autoaim - - for (i = 5; i >= 0; i--) - { - if (aim >= ranges[i]) - { - aim = ranges[i + 1]; - break; - } - } - } - - autoaim = aim; -} - -static void M_EditPlayerName (int choice) -{ - // we are going to be intercepting all chars - genStringEnter = 2; - genStringEnd = M_PlayerNameChanged; - genStringCancel = M_PlayerNameNotChanged; - genStringLen = MAXPLAYERNAME; - - saveSlot = 0; - saveCharIndex = strlen (savegamestring); -} - -static void M_PlayerNameNotChanged () -{ - strcpy (savegamestring, name); -} - -static void M_PlayerNameChanged (FSaveGameNode *dummy) -{ - const char *p; - FString command("name \""); - - // Escape any backslashes or quotation marks before sending the name to the console. - for (p = savegamestring; *p != '\0'; ++p) - { - if (*p == '"' || *p == '\\') - { - command << '\\'; - } - command << *p; - } - command << '"'; - C_DoCommand (command); -} - -static void M_ChangePlayerTeam (int choice) -{ - if (!choice) - { - if (team == 0) - { - team = TEAM_NONE; - } - else if (team == TEAM_NONE) - { - team = Teams.Size () - 1; - } - else - { - team = team - 1; - } - } - else - { - if (team == int(Teams.Size () - 1)) - { - team = TEAM_NONE; - } - else if (team == TEAM_NONE) - { - team = 0; - } - else - { - team = team + 1; - } - } -} - -static void M_ChangeColorSet (int choice) -{ - int curpos = (int)PlayerColorSets.Size(); - int mycolorset = players[consoleplayer].userinfo.colorset; - while (--curpos >= 0) - { - if (PlayerColorSets[curpos] == mycolorset) - break; - } - if (choice == 0) - { - curpos--; - } - else - { - curpos++; - } - if (curpos < -1) - { - curpos = (int)PlayerColorSets.Size() - 1; - } - else if (curpos >= (int)PlayerColorSets.Size()) - { - curpos = -1; - } - mycolorset = (curpos >= 0) ? PlayerColorSets[curpos] : -1; - - // disable the sliders if a valid colorset is selected - PlayerSetupMenu[PSM_RED].status = - PlayerSetupMenu[PSM_GREEN].status = - PlayerSetupMenu[PSM_BLUE].status = (mycolorset == -1? 2:-1); - - char command[24]; - mysnprintf(command, countof(command), "colorset %d", mycolorset); - C_DoCommand(command); - R_GetPlayerTranslation(players[consoleplayer].userinfo.color, - P_GetPlayerColorSet(PlayerClass->Type->TypeName, mycolorset), - &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); -} - -static void SendNewColor (int red, int green, int blue) -{ - char command[24]; - - mysnprintf (command, countof(command), "color \"%02x %02x %02x\"", red, green, blue); - C_DoCommand (command); - R_GetPlayerTranslation(MAKERGB (red, green, blue), - P_GetPlayerColorSet(PlayerClass->Type->TypeName, players[consoleplayer].userinfo.colorset), - &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); -} - -static void M_SlidePlayerRed (int choice) -{ - int color = players[consoleplayer].userinfo.color; - int red = RPART(color); - - if (choice == 0) { - red -= 16; - if (red < 0) - red = 0; - } else { - red += 16; - if (red > 255) - red = 255; - } - - SendNewColor (red, GPART(color), BPART(color)); -} - -static void M_SlidePlayerGreen (int choice) -{ - int color = players[consoleplayer].userinfo.color; - int green = GPART(color); - - if (choice == 0) { - green -= 16; - if (green < 0) - green = 0; - } else { - green += 16; - if (green > 255) - green = 255; - } - - SendNewColor (RPART(color), green, BPART(color)); -} - -static void M_SlidePlayerBlue (int choice) -{ - int color = players[consoleplayer].userinfo.color; - int blue = BPART(color); - - if (choice == 0) { - blue -= 16; - if (blue < 0) - blue = 0; - } else { - blue += 16; - if (blue > 255) - blue = 255; - } - - SendNewColor (RPART(color), GPART(color), blue); -} - - -// -// Menu Functions -// -void M_StartMessage (const char *string, void (*routine)(int)) -{ - C_HideConsole (); - messageLastMenuActive = menuactive; - messageToPrint = 1; - messageString = string; - messageRoutine = routine; - messageSelection = 0; - if (menuactive == MENU_Off) - { - M_ActivateMenuInput (); - } - if (messageRoutine != NULL) - { - S_StopSound (CHAN_VOICE); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", 1, ATTN_NONE); - } - return; -} - -void M_EndMessage(int key) -{ - menuactive = messageLastMenuActive; - messageToPrint = 0; - if (messageRoutine != NULL) - { - messageRoutine(key); - } - if (menuactive != MENU_Off) - { - M_DeactivateMenuInput(); - } - SB_state = screen->GetPageCount(); // refresh the status bar - BorderNeedRefresh = screen->GetPageCount(); - S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); -} - - -// -// Find string height from hu_font chars -// -int M_StringHeight (FFont *font, const char *string) -{ - int h; - int height = font->GetHeight (); - - h = height; - while (*string) - { - if ((*string++) == '\n') - h += height; - } - - return h; -} - - - -// -// CONTROL PANEL -// - -// -// M_Responder -// -bool M_Responder (event_t *ev) -{ - int ch; - int i; - EMenuKey mkey = NUM_MKEYS; - bool keyup = true; - - ch = -1; - - if (chatmodeon) - { - return false; - } - if (menuactive == MENU_Off && ev->type == EV_KeyDown) - { - // Pop-up menu? - if (ev->data1 == KEY_ESCAPE) - { - M_StartControlPanel(true, true); - return true; - } - // If devparm is set, pressing F1 always takes a screenshot no matter - // what it's bound to. (for those who don't bother to read the docs) - if (devparm && ev->data1 == KEY_F1) - { - G_ScreenShot(NULL); - return true; - } - return false; - } - if (menuactive == MENU_WaitKey && OptionsActive) - { - M_OptResponder(ev); - return true; - } - if (menuactive != MENU_On && menuactive != MENU_OnNoPause && - !genStringEnter && !messageToPrint) - { - return false; - } - - // There are a few input sources we are interested in: - // - // EV_KeyDown / EV_KeyUp : joysticks/gamepads/controllers - // EV_GUI_KeyDown / EV_GUI_KeyUp : the keyboard - // EV_GUI_Char : printable characters, which we want in string input mode - // - // This code previously listened for EV_GUI_KeyRepeat to handle repeating - // in the menus, but that doesn't work with gamepads, so now we combine - // the multiple inputs into buttons and handle the repetition manually. - if (ev->type == EV_GUI_Event) - { - // Save game and player name string input - if (genStringEnter) - { - if (ev->subtype == EV_GUI_Char) - { - InputGridOkay = false; - if (saveCharIndex < genStringLen && - (genStringEnter == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(savegamestring) < (genStringLen-1)*8)) - { - savegamestring[saveCharIndex] = (char)ev->data1; - savegamestring[++saveCharIndex] = 0; - } - return true; - } - ch = ev->data1; - if ((ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) && ch == '\b') - { - if (saveCharIndex > 0) - { - saveCharIndex--; - savegamestring[saveCharIndex] = 0; - } - } - else if (ev->subtype == EV_GUI_KeyDown) - { - if (ch == GK_ESCAPE) - { - genStringEnter = 0; - genStringCancel(); // [RH] Function to call when escape is pressed - } - else if (ch == '\r') - { - if (savegamestring[0]) - { - genStringEnter = 0; - if (messageToPrint) - M_ClearMenus (); - genStringEnd (SelSaveGame); // [RH] Function to call when enter is pressed - } - } - } - if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) - { - return true; - } - } - if (ev->subtype != EV_GUI_KeyDown && ev->subtype != EV_GUI_KeyUp) - { - return false; - } - if (ev->subtype == EV_GUI_KeyRepeat) - { - // We do our own key repeat handling but still want to eat the - // OS's repeated keys. - return true; - } - ch = ev->data1; - keyup = ev->subtype == EV_GUI_KeyUp; - if (messageToPrint && messageRoutine == NULL) - { - if (!keyup && !OptionsActive) - { - D_RemoveNextCharEvent(); - M_EndMessage(ch); - return true; - } - } - switch (ch) - { - case GK_ESCAPE: mkey = MKEY_Back; break; - case GK_RETURN: mkey = MKEY_Enter; break; - case GK_UP: mkey = MKEY_Up; break; - case GK_DOWN: mkey = MKEY_Down; break; - case GK_LEFT: mkey = MKEY_Left; break; - case GK_RIGHT: mkey = MKEY_Right; break; - case GK_BACKSPACE: mkey = MKEY_Clear; break; - case GK_PGUP: mkey = MKEY_PageUp; break; - case GK_PGDN: mkey = MKEY_PageDown; break; - default: - if (ch == ' ' && currentMenu == &PSetupDef) - { - mkey = MKEY_Clear; - } - else if (!keyup) - { - if (OptionsActive) - { - M_OptResponder(ev); - } - else - { - ch = tolower (ch); - if (messageToPrint) - { - // Take care of any messages that need input - ch = tolower (ch); - assert(messageRoutine != NULL); - if (ch != ' ' && ch != 'n' && ch != 'y') - { - return false; - } - D_RemoveNextCharEvent(); - M_EndMessage(ch); - return true; - } - else - { - // Search for a menu item associated with the pressed key. - for (i = (itemOn + 1) % currentMenu->numitems; - i != itemOn; - i = (i + 1) % currentMenu->numitems) - { - if (currentMenu->menuitems[i].alphaKey == ch) - { - break; - } - } - if (currentMenu->menuitems[i].alphaKey == ch) - { - itemOn = i; - S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - return true; - } - } - } - } - break; - } - if (!keyup) - { - InputGridOkay = false; - } - } - else if (ev->type == EV_KeyDown || ev->type == EV_KeyUp) - { - keyup = ev->type == EV_KeyUp; - // If this is a button down, it's okay to show the input grid if the - // next action causes us to enter genStringEnter mode. If we are - // already in that mode, then we let M_ButtonHandler() turn it on so - // that it will know if a button press happened while the input grid - // was turned off. - if (!keyup && !genStringEnter) - { - InputGridOkay = true; - } - ch = ev->data1; - switch (ch) - { - case KEY_JOY1: - case KEY_PAD_A: - mkey = MKEY_Enter; - break; - - case KEY_JOY2: - case KEY_PAD_B: - mkey = MKEY_Back; - break; - - case KEY_JOY3: - case KEY_PAD_X: - mkey = MKEY_Clear; - break; - - case KEY_JOY5: - case KEY_PAD_LSHOULDER: - mkey = MKEY_PageUp; - break; - - case KEY_JOY6: - case KEY_PAD_RSHOULDER: - mkey = MKEY_PageDown; - break; - - case KEY_PAD_DPAD_UP: - case KEY_PAD_LTHUMB_UP: - case KEY_JOYAXIS1MINUS: - case KEY_JOYPOV1_UP: - mkey = MKEY_Up; - break; - - case KEY_PAD_DPAD_DOWN: - case KEY_PAD_LTHUMB_DOWN: - case KEY_JOYAXIS1PLUS: - case KEY_JOYPOV1_DOWN: - mkey = MKEY_Down; - break; - - case KEY_PAD_DPAD_LEFT: - case KEY_PAD_LTHUMB_LEFT: - case KEY_JOYAXIS2MINUS: - case KEY_JOYPOV1_LEFT: - mkey = MKEY_Left; - break; - - case KEY_PAD_DPAD_RIGHT: - case KEY_PAD_LTHUMB_RIGHT: - case KEY_JOYAXIS2PLUS: - case KEY_JOYPOV1_RIGHT: - mkey = MKEY_Right; - break; - } - // Any button press will work for messages without callbacks - if (!keyup && messageToPrint && messageRoutine == NULL) - { - M_EndMessage(ch); - return true; - } - } - - if (mkey != NUM_MKEYS) - { - if (keyup) - { - MenuButtons[mkey].ReleaseKey(ch); - } - else - { - MenuButtons[mkey].PressKey(ch); - if (mkey <= MKEY_PageDown) - { - MenuButtonTickers[mkey] = KEY_REPEAT_DELAY; - } - M_ButtonHandler(mkey, false); - } - } - - if (ev->type == EV_GUI_Event && (currentMenu == &SaveDef || currentMenu == &LoadDef)) - { - return M_SaveLoadResponder (ev); - } - - // Eat key downs, but let the rest through. - return !keyup; -} - -void M_ButtonHandler(EMenuKey key, bool repeat) -{ - if (OptionsActive) - { - M_OptButtonHandler(key, repeat); - return; - } - if (key == MKEY_Back) - { - if (genStringEnter) - { - // Cancel string entry. - genStringEnter = 0; - genStringCancel(); - } - else if (messageToPrint) - { - M_EndMessage(GK_ESCAPE); - } - else - { - // Save the cursor position on the current menu, and pop it off the stack - // to go back to the previous menu. - currentMenu->lastOn = itemOn; - M_PopMenuStack(); - } - return; - } - if (messageToPrint) - { - if (key == MKEY_Down || key == MKEY_Up) - { - messageSelection ^= 1; - } - else if (key == MKEY_Enter) - { - M_EndMessage(messageSelection == 0 ? 'y' : 'n'); - } - return; - } - if (genStringEnter) - { - int ch; - - switch (key) - { - case MKEY_Down: - InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; - break; - - case MKEY_Up: - InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; - break; - - case MKEY_Right: - InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; - break; - - case MKEY_Left: - InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; - break; - - case MKEY_Clear: - if (saveCharIndex > 0) - { - savegamestring[--saveCharIndex] = 0; - } - break; - - case MKEY_Enter: - assert(unsigned(InputGridX) < INPUTGRID_WIDTH && unsigned(InputGridY) < INPUTGRID_HEIGHT); - if (InputGridOkay) - { - ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; - if (ch == 0) // end - { - if (savegamestring[0] != '\0') - { - genStringEnter = 0; - if (messageToPrint) - { - M_ClearMenus(); - } - genStringEnd(SelSaveGame); - } - } - else if (ch == '\b') // bs - { - if (saveCharIndex > 0) - { - savegamestring[--saveCharIndex] = 0; - } - } - else if (saveCharIndex < genStringLen && - (genStringEnter == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(savegamestring) < (genStringLen-1)*8)) - { - savegamestring[saveCharIndex] = ch; - savegamestring[++saveCharIndex] = 0; - } - } - break; - - default: - break; // Keep GCC quiet - } - InputGridOkay = true; - return; - } - if (currentMenu == &SaveDef || currentMenu == &LoadDef) - { - M_SaveLoadButtonHandler(key); - return; - } - switch (key) - { - case MKEY_Down: - do - { - if (itemOn + 1 >= currentMenu->numitems) - itemOn = 0; - else itemOn++; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } while (currentMenu->menuitems[itemOn].status == -1); - break; - - case MKEY_Up: - do - { - if (itemOn == 0) - itemOn = currentMenu->numitems - 1; - else itemOn--; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } while (currentMenu->menuitems[itemOn].status == -1); - break; - - case MKEY_Left: - if (currentMenu->menuitems[itemOn].routine && - currentMenu->menuitems[itemOn].status == 2) - { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - currentMenu->menuitems[itemOn].routine(0); - } - break; - - case MKEY_Right: - if (currentMenu->menuitems[itemOn].routine && - currentMenu->menuitems[itemOn].status == 2) - { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - currentMenu->menuitems[itemOn].routine(1); - } - break; - - case MKEY_Enter: - if (currentMenu->menuitems[itemOn].routine && - currentMenu->menuitems[itemOn].status) - { - currentMenu->lastOn = itemOn; - if (currentMenu->menuitems[itemOn].status == 2) - { - currentMenu->menuitems[itemOn].routine(1); // right arrow - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - } - else - { - currentMenu->menuitems[itemOn].routine(itemOn); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - } - } - break; - - case MKEY_Clear: - if (currentMenu == &PSetupDef) - { - PlayerRotation ^= 8; - } - break; - - default: - break; // Keep GCC quiet - } -} - -static void M_SaveLoadButtonHandler(EMenuKey key) -{ - if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) - { - return; - } - switch (key) - { - case MKEY_Up: - if (SelSaveGame != SaveGames.Head) - { - if (SelSaveGame == TopSaveGame) - { - TopSaveGame = static_cast(TopSaveGame->Pred); - } - SelSaveGame = static_cast(SelSaveGame->Pred); - } - else - { - SelSaveGame = static_cast(SaveGames.TailPred); - } - M_UnloadSaveData (); - M_ExtractSaveData (SelSaveGame); - break; - - case MKEY_Down: - if (SelSaveGame != SaveGames.TailPred) - { - SelSaveGame = static_cast(SelSaveGame->Succ); - } - else - { - SelSaveGame = TopSaveGame = - static_cast(SaveGames.Head); - } - M_UnloadSaveData (); - M_ExtractSaveData (SelSaveGame); - break; - - case MKEY_Enter: - if (currentMenu == &LoadDef) - { - M_LoadSelect (SelSaveGame); - } - else - { - M_SaveSelect (SelSaveGame); - } - break; - - default: - break; // Keep GCC quiet - } -} - -static bool M_SaveLoadResponder (event_t *ev) -{ - if (ev->subtype != EV_GUI_KeyDown) - { - return false; - } - if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) - { - switch (ev->data1) - { - case GK_F1: - if (!SelSaveGame->Filename.IsEmpty()) - { - char workbuf[512]; - - mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars()); - if (SaveComment != NULL) - { - V_FreeBrokenLines (SaveComment); - } - SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf); - } - break; - - case GK_DEL: - case '\b': - if (SelSaveGame != &NewSaveNode) - { - EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", - GStrings("MNU_DELETESG"), SelSaveGame->Title, GStrings("PRESSYN")); - - M_StartMessage (EndString, M_DeleteSaveResponse); - } - break; - - case 'N': - if (currentMenu == &SaveDef) - { - SelSaveGame = TopSaveGame = &NewSaveNode; - M_UnloadSaveData (); - } - break; - } - } - return true; -} - -static void M_LoadSelect (const FSaveGameNode *file) -{ - G_LoadGame (file->Filename.GetChars()); - if (gamestate == GS_FULLCONSOLE) - { - gamestate = GS_HIDECONSOLE; - } - if (quickSaveSlot == (FSaveGameNode *)1) - { - quickSaveSlot = SelSaveGame; - } - M_ClearMenus (); - BorderNeedRefresh = screen->GetPageCount (); -} - - -// -// User wants to save. Start string input for M_Responder -// -static void M_CancelSaveName () -{ -} - -static void M_SaveSelect (const FSaveGameNode *file) -{ - // we are going to be intercepting all chars - genStringEnter = 1; - genStringEnd = M_DoSave; - genStringCancel = M_CancelSaveName; - genStringLen = SAVESTRINGSIZE-1; - - if (file != &NewSaveNode) - { - strcpy (savegamestring, file->Title); - } - else - { - // If we are naming a new save, don't start the cursor on "end". - if (InputGridX == INPUTGRID_WIDTH - 1 && InputGridY == INPUTGRID_HEIGHT - 1) - { - InputGridX = 0; - InputGridY = 0; - } - savegamestring[0] = 0; - } - saveCharIndex = strlen (savegamestring); -} - -static void M_DeleteSaveResponse (int choice) -{ - M_ClearSaveStuff (); - if (choice == 'y') - { - FSaveGameNode *next = static_cast(SelSaveGame->Succ); - if (next->Succ == NULL) - { - next = static_cast(SelSaveGame->Pred); - if (next->Pred == NULL) - { - next = NULL; - } - } - - remove (SelSaveGame->Filename.GetChars()); - M_UnloadSaveData (); - SelSaveGame = M_RemoveSaveSlot (SelSaveGame); - M_ExtractSaveData (SelSaveGame); - } -} - -// -// M_StartControlPanel -// -void M_StartControlPanel (bool makeSound, bool wantTop) -{ - // intro might call this repeatedly - if (menuactive == MENU_On) - return; - - for (int i = 0; i < NUM_MKEYS; ++i) - { - MenuButtons[i].ReleaseKey(0); - } - drawSkull = true; - MenuStackDepth = 0; - if (wantTop) - { - M_SetupNextMenu(TopLevelMenu); - } - else - { - // Just a default. The caller ought to call M_SetupNextMenu() next. - currentMenu = TopLevelMenu; - itemOn = currentMenu->lastOn; - } - C_HideConsole (); // [RH] Make sure console goes bye bye. - OptionsActive = false; // [RH] Make sure none of the options menus appear. - M_ActivateMenuInput (); - - if (makeSound) - { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); - } -} - - -// -// M_Drawer -// Called after the view has been rendered, -// but before it has been blitted. -// -void M_Drawer () -{ - int i, x, y, max; - PalEntry fade = 0; - - player_t *player = &players[consoleplayer]; - AActor *camera = player->camera; - - if (!screen->Accel2D && camera != NULL && (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)) - { - if (camera->player != NULL) - { - player = camera->player; - } - fade = PalEntry (BYTE(player->BlendA*255), BYTE(player->BlendR*255), BYTE(player->BlendG*255), BYTE(player->BlendB*255)); - } - - // Horiz. & Vertically center string and print it. - if (messageToPrint) - { - int fontheight = SmallFont->GetHeight(); - screen->Dim (fade); - BorderNeedRefresh = screen->GetPageCount (); - SB_state = screen->GetPageCount (); - - FBrokenLines *lines = V_BreakLines (SmallFont, 320, messageString); - y = 100; - - for (i = 0; lines[i].Width >= 0; i++) - y -= SmallFont->GetHeight () / 2; - - for (i = 0; lines[i].Width >= 0; i++) - { - screen->DrawText (SmallFont, CR_UNTRANSLATED, 160 - lines[i].Width/2, y, lines[i].Text, - DTA_Clean, true, TAG_DONE); - y += fontheight; - } - V_FreeBrokenLines (lines); - if (messageRoutine != NULL) - { - y += fontheight; - screen->DrawText(SmallFont, CR_UNTRANSLATED, 160, y, GStrings["TXT_YES"], DTA_Clean, true, TAG_DONE); - screen->DrawText(SmallFont, CR_UNTRANSLATED, 160, y + fontheight + 1, GStrings["TXT_NO"], DTA_Clean, true, TAG_DONE); - if (skullAnimCounter < 6) - { - screen->DrawText(ConFont, CR_RED, - (150 - 160) * CleanXfac + screen->GetWidth() / 2, - (y + (fontheight + 1) * messageSelection - 100) * CleanYfac + screen->GetHeight() / 2, - "\xd", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); - } - } - } - else if (menuactive != MENU_Off) - { - if (InfoType == 0 && !OptionsActive) - { - screen->Dim (fade); - } - // For Heretic shareware message: - if (showSharewareMessage) - { - const char *text = GStrings("MNU_ONLYREGISTERED"); - screen->DrawText (SmallFont, CR_WHITE, 160 - SmallFont->StringWidth(text)/2, - 8, text, DTA_Clean, true, TAG_DONE); - } - - BorderNeedRefresh = screen->GetPageCount (); - SB_state = screen->GetPageCount (); - - if (OptionsActive) - { - M_OptDrawer (); - } - else - { - if (currentMenu->routine) - currentMenu->routine(); // call Draw routine - - // DRAW MENU - x = currentMenu->x; - y = currentMenu->y; - max = currentMenu->numitems; - - for (i = 0; i < max; i++) - { - if (currentMenu->menuitems[i].name) - { - if (currentMenu->menuitems[i].fulltext) - { - int color = currentMenu->menuitems[i].textcolor; - if (color == CR_UNTRANSLATED) - { - // The default DBIGFONT is white but Doom's default should be red. - if (gameinfo.gametype & GAME_DoomChex) - { - color = CR_RED; - } - } - const char *text = currentMenu->menuitems[i].name; - if (*text == '$') text = GStrings(text+1); - screen->DrawText (BigFont, color, x, y, text, - DTA_Clean, true, TAG_DONE); - } - else - { - screen->DrawTexture (TexMan[currentMenu->menuitems[i].name], x, y, - DTA_Clean, true, TAG_DONE); - } - } - y += LINEHEIGHT; - } - - // DRAW CURSOR - if (drawSkull) - { - if (currentMenu == &PSetupDef) - { - // [RH] Use options menu cursor for the player setup menu. - if (skullAnimCounter < 6) - { - double item; - // The green slider is halfway between lines, and the red and - // blue ones are offset slightly to make room for it. - if (itemOn < 3) - { - item = itemOn; - } - else if (itemOn > 5) - { - item = itemOn - 1; - } - else if (itemOn == 3) - { - item = 2.875; - } - else if (itemOn == 4) - { - item = 3.5; - } - else - { - item = 4.125; - } - screen->DrawText (ConFont, CR_RED, x - 16, - currentMenu->y + int(item*PLAYERSETUP_LINEHEIGHT) + - (!(gameinfo.gametype & (GAME_DoomStrifeChex)) ? 6 : -1), "\xd", - DTA_Clean, true, TAG_DONE); - } - } - else if (gameinfo.gametype & GAME_DoomChex) - { - screen->DrawTexture (TexMan("M_SKULL1"), - x + SKULLXOFF, currentMenu->y - 5 + itemOn*LINEHEIGHT, - DTA_Clean, true, TAG_DONE); - } - else if (gameinfo.gametype == GAME_Strife) - { - screen->DrawTexture (TexMan("M_CURS1"), - x - 28, currentMenu->y - 5 + itemOn*LINEHEIGHT, - DTA_Clean, true, TAG_DONE); - } - else - { - screen->DrawTexture (TexMan("M_SLCTR1"), - x + SELECTOR_XOFFSET, - currentMenu->y + itemOn*LINEHEIGHT + SELECTOR_YOFFSET, - DTA_Clean, true, TAG_DONE); - } - } - } - if (genStringEnter && InputGridOkay) - { - M_DrawInputGrid(); - } - } -} - - -static void M_ClearSaveStuff () -{ - M_UnloadSaveData (); - if (SaveGames.Head == &NewSaveNode) - { - SaveGames.RemHead (); - if (SelSaveGame == &NewSaveNode) - { - SelSaveGame = static_cast(SaveGames.Head); - } - if (TopSaveGame == &NewSaveNode) - { - TopSaveGame = static_cast(SaveGames.Head); - } - } - if (quickSaveSlot == (FSaveGameNode *)1) - { - quickSaveSlot = NULL; - } -} - -static void M_DrawInputGrid() -{ - const int cell_width = 18 * CleanXfac; - const int cell_height = 12 * CleanYfac; - const int top_padding = cell_height / 2 - SmallFont->GetHeight() * CleanYfac / 2; - - // Darken the background behind the character grid. - // Unless we frame it with a border, I think it looks better to extend the - // background across the full width of the screen. - screen->Dim(0, 0.8f, - 0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, - screen->GetHeight() - 5 * cell_height, - screen->GetWidth() /*13 * cell_width*/, - 5 * cell_height); - - // Highlight the background behind the selected character. - screen->Dim(MAKERGB(255,248,220), 0.6f, - InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2, - InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(), - cell_width, cell_height); - - for (int y = 0; y < INPUTGRID_HEIGHT; ++y) - { - const int yy = y * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(); - for (int x = 0; x < INPUTGRID_WIDTH; ++x) - { - int width; - const int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2; - const int ch = InputGridChars[y * INPUTGRID_WIDTH + x]; - FTexture *pic = SmallFont->GetChar(ch, &width); - EColorRange color; - FRemapTable *remap; - - // The highlighted character is yellow; the rest are dark gray. - color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; - remap = SmallFont->GetColorTranslation(color); - - if (pic != NULL) - { - // Draw a normal character. - screen->DrawTexture(pic, xx + cell_width/2 - width*CleanXfac/2, yy + top_padding, - DTA_Translation, remap, - DTA_CleanNoMove, true, - TAG_DONE); - } - else if (ch == ' ') - { - // Draw the space as a box outline. We also draw it 50% wider than it really is. - const int x1 = xx + cell_width/2 - width * CleanXfac * 3 / 4; - const int x2 = x1 + width * 3 * CleanXfac / 2; - const int y1 = yy + top_padding; - const int y2 = y1 + SmallFont->GetHeight() * CleanYfac; - const int palentry = remap->Remap[remap->NumEntries*2/3]; - const uint32 palcolor = remap->Palette[remap->NumEntries*2/3]; - screen->Clear(x1, y1, x2, y1+CleanYfac, palentry, palcolor); // top - screen->Clear(x1, y2, x2, y2+CleanYfac, palentry, palcolor); // bottom - screen->Clear(x1, y1+CleanYfac, x1+CleanXfac, y2, palentry, palcolor); // left - screen->Clear(x2-CleanXfac, y1+CleanYfac, x2, y2, palentry, palcolor); // right - } - else if (ch == '\b' || ch == 0) - { - // Draw the backspace and end "characters". - const char *const str = ch == '\b' ? "BS" : "ED"; - screen->DrawText(SmallFont, color, - xx + cell_width/2 - SmallFont->StringWidth(str)*CleanXfac/2, - yy + top_padding, str, DTA_CleanNoMove, true, TAG_DONE); - } - } - } -} - -// -// M_ClearMenus -// -void M_ClearMenus () -{ - if (FireTexture) - { - delete FireTexture; - FireTexture = NULL; - } - M_ClearSaveStuff (); - M_DeactivateMenuInput (); - MenuStackDepth = 0; - OptionsActive = false; - InfoType = 0; - drawSkull = true; - M_DemoNoPlay = false; - BorderNeedRefresh = screen->GetPageCount (); -} - - - - -// -// M_SetupNextMenu -// -void M_SetupNextMenu (oldmenu_t *menudef) -{ - MenuStack[MenuStackDepth].menu.old = menudef; - MenuStack[MenuStackDepth].isNewStyle = false; - MenuStack[MenuStackDepth].drawSkull = drawSkull; - MenuStackDepth++; - - currentMenu = menudef; - itemOn = currentMenu->lastOn; -} - - -void M_PopMenuStack (void) -{ - M_DemoNoPlay = false; - InfoType = 0; - M_ClearSaveStuff (); - flagsvar = 0; - if (MenuStackDepth > 1) - { - MenuStackDepth -= 2; - if (MenuStack[MenuStackDepth].isNewStyle) - { - OptionsActive = true; - CurrentMenu = MenuStack[MenuStackDepth].menu.newmenu; - CurrentItem = CurrentMenu->lastOn; - } - else - { - OptionsActive = false; - currentMenu = MenuStack[MenuStackDepth].menu.old; - itemOn = currentMenu->lastOn; - } - drawSkull = MenuStack[MenuStackDepth].drawSkull; - ++MenuStackDepth; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", 1, ATTN_NONE); - } - else - { - M_ClearMenus (); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", 1, ATTN_NONE); - } -} - - -// -// M_Ticker -// -void M_Ticker (void) -{ - if (showSharewareMessage) - { - --showSharewareMessage; - } - if (menuactive == MENU_Off) - { - return; - } - MenuTime++; - if (--skullAnimCounter <= 0) - { - skullAnimCounter = 8; - } - if (currentMenu == &PSetupDef || currentMenu == &ClassMenuDef) - { - M_PlayerSetupTicker(); - } - - for (int i = 0; i < NUM_MKEYS; ++i) - { - if (MenuButtons[i].bDown) - { - if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0) - { - MenuButtonTickers[i] = KEY_REPEAT_RATE; - M_ButtonHandler(EMenuKey(i), true); - } - } - } -} - - -// -// M_Init -// -EXTERN_CVAR (Int, screenblocks) - -void M_Init (void) -{ - unsigned int i; - - atterm (M_Deinit); - - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - TopLevelMenu = currentMenu = &MainDef; - if (gameinfo.gametype == GAME_Strife) - { - MainDef.y = 45; - //NewDef.lastOn = 1; - } - } - else - { - TopLevelMenu = currentMenu = &HereticMainDef; - PSetupDef.y -= 7; - LoadDef.y -= 20; - SaveDef.y -= 20; - } - PickPlayerClass (); - OptionsActive = false; - menuactive = MENU_Off; - InfoType = 0; - itemOn = currentMenu->lastOn; - skullAnimCounter = 10; - drawSkull = true; - messageToPrint = 0; - messageString = NULL; - messageLastMenuActive = menuactive; - quickSaveSlot = NULL; - lastSaveSlot = NULL; - strcpy (NewSaveNode.Title, ""); - - underscore[0] = (gameinfo.gametype & (GAME_DoomStrifeChex)) ? '_' : '['; - underscore[1] = '\0'; - - if (gameinfo.gametype & GAME_DoomChex) - { - LINEHEIGHT = 16; - } - else if (gameinfo.gametype == GAME_Strife) - { - LINEHEIGHT = 19; - } - else - { - LINEHEIGHT = 20; - } - - if (!gameinfo.drawreadthis) - { - MainMenu[MainDef.numitems-2] = MainMenu[MainDef.numitems-1]; - MainDef.numitems--; - MainDef.y += 8; - ReadDef.routine = M_DrawReadThis; - ReadDef.x = 330; - ReadDef.y = 165; - //ReadMenu[0].routine = M_FinishReadThis; - } - M_OptInit (); - - // [GRB] Set up player class menu - if (!(gameinfo.gametype == GAME_Hexen && PlayerClasses.Size () == 3 && - PlayerClasses[0].Type->IsDescendantOf (PClass::FindClass (NAME_FighterPlayer)) && - PlayerClasses[1].Type->IsDescendantOf (PClass::FindClass (NAME_ClericPlayer)) && - PlayerClasses[2].Type->IsDescendantOf (PClass::FindClass (NAME_MagePlayer)))) - { - int n = 0; - - for (i = 0; i < PlayerClasses.Size () && n < 7; i++) - { - if (!(PlayerClasses[i].Flags & PCF_NOMENU)) - { - ClassMenuItems[n].name = - PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); - n++; - } - } - - if (n > 1) - { - ClassMenuItems[n].name = "Random"; - ClassMenuDef.numitems = n+1; - } - else - { - if (n == 0) - { - ClassMenuItems[0].name = - PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName); - } - ClassMenuDef.numitems = 1; - } - - if (gameinfo.gametype & (GAME_DoomStrifeChex)) - { - ClassMenuDef.x = 48; - ClassMenuDef.y = 63; - } - else - { - ClassMenuDef.x = 80; - ClassMenuDef.y = 50; - } - if (ClassMenuDef.numitems > 4) - { - ClassMenuDef.y -= LINEHEIGHT; - } - } - - // [RH] Build a palette translation table for the player setup effect - if (gameinfo.gametype != GAME_Hexen) - { - for (i = 0; i < 256; i++) - { - FireRemap.Remap[i] = ColorMatcher.Pick (i/2+32, 0, i/4); - FireRemap.Palette[i] = PalEntry(255, i/2+32, 0, i/4); - } - } - else - { // The reddish color ramp above doesn't look too good with the - // Hexen palette, so Hexen gets a greenish one instead. - for (i = 0; i < 256; ++i) - { - FireRemap.Remap[i] = ColorMatcher.Pick (i/4, i*13/40+7, i/4); - FireRemap.Palette[i] = PalEntry(255, i/4, i*13/40+7, i/4); - } - } -} - -static void PickPlayerClass () -{ - int pclass = 0; - - // [GRB] Pick a class from player class list - if (PlayerClasses.Size () > 1) - { - pclass = players[consoleplayer].userinfo.PlayerClass; - - if (pclass < 0) - { - pclass = (MenuTime>>7) % PlayerClasses.Size (); - } - } - - PlayerClass = &PlayerClasses[pclass]; - P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); -} diff --git a/src/m_menu.h b/src/m_menu.h deleted file mode 100644 index 4f45cbed23..0000000000 --- a/src/m_menu.h +++ /dev/null @@ -1,279 +0,0 @@ -// Emacs style mode select -*- C++ -*- -//----------------------------------------------------------------------------- -// -// $Id:$ -// -// Copyright (C) 1993-1996 by id Software, Inc. -// -// This source is available for distribution and/or modification -// only under the terms of the DOOM Source Code License as -// published by id Software. All rights reserved. -// -// The source is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License -// for more details. -// -// DESCRIPTION: -// Menu widget stuff, episode selection and such. -// -//----------------------------------------------------------------------------- - - -#ifndef __M_MENU_H__ -#define __M_MENU_H__ - -#include "c_cvars.h" - -struct event_t; -struct menu_t; -// -// MENUS -// -// Called by main loop, -// saves config file and calls I_Quit when user exits. -// Even when the menu is not displayed, -// this can resize the view and change game parameters. -// Does all the real work of the menu interaction. -bool M_Responder (event_t *ev); - -// Called by main loop, -// only used for menu (skull cursor) animation. -void M_Ticker (void); - -// Called by main loop, -// draws the menus directly into the screen buffer. -void M_Drawer (void); - -// Called by D_DoomMain, loads the config file. -void M_Init (void); - -void M_Deinit (); - -// Called by intro code to force menu up upon a keypress, -// does nothing if menu is already up. -void M_StartControlPanel (bool makeSound, bool wantTop=false); - -// Turns off the menu -void M_ClearMenus (); - -// [RH] Setup options menu -bool M_StartOptionsMenu (void); - -// [RH] Handle keys for options menu -void M_OptResponder (event_t *ev); - -// [RH] Draw options menu -void M_OptDrawer (void); - -// [RH] Initialize options menu -void M_OptInit (void); - -// [RH] Initialize the video modes menu -void M_InitVideoModesMenu (void); - -void M_SwitchMenu (struct menu_t *menu); - -void M_PopMenuStack (void); - -// [RH] Called whenever the display mode changes -void M_RefreshModesList (); - -void M_ActivateMenuInput (); -void M_DeactivateMenuInput (); - -void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); - -// -// MENU TYPEDEFS -// -typedef enum { - whitetext, - redtext, - more, - rightmore, - safemore, - rsafemore, - joymore, - slider, - absslider, - inverter, - discrete, - discretes, - cdiscrete, - ediscrete, - control, - screenres, - bitflag, - bitmask, - listelement, - nochoice, - numberedmore, - colorpicker, - intslider, - palettegrid, - joy_sens, - joy_slider, - joy_map, - joy_inverter, -} itemtype; - -struct IJoystickConfig; -void UpdateJoystickMenu(IJoystickConfig *selected); - -// Yeargh! It's a monster! -struct menuitem_t -{ - itemtype type; - const char *label; - union { - FBaseCVar *cvar; - FIntCVar *intcvar; - FGUIDCVar *guidcvar; - FColorCVar *colorcvar; - int selmode; - float fval; - int joyselection; - } a; - union { - float min; /* aka numvalues aka invflag */ - float numvalues; - float invflag; - int key1; - char *res1; - int position; - } b; - union { - float max; - int key2; - char *res2; - void *extra; - float discretecenter; // 1 to center or 2 to disable repeat (do I even use centered discretes?) - } c; - union { - float step; - char *res3; - FBoolCVar *graycheck; // for drawing discrete items - } d; - union { - struct value_t *values; - struct valuestring_t *valuestrings; - struct valueenum_t *enumvalues; - char *command; - void (*cfunc)(FBaseCVar *cvar, float newval); - void (*mfunc)(void); - void (*lfunc)(int); - int highlight; - int flagmask; - int joyslidernum; - } e; -}; - -struct menu_t { - const char *texttitle; - int lastOn; - int numitems; - int indent; - menuitem_t *items; - int scrolltop; - int scrollpos; - int y; - bool (*PreDraw)(void); - bool DontDim; - void (*EscapeHandler)(void); -}; - -struct value_t { - float value; - const char *name; -}; - -struct valuestring_t { - float value; - FString name; -}; - -struct valueenum_t { - const char *value; // Value of cvar - const char *name; // Name on menu -}; - -struct oldmenuitem_t -{ - // -1 = no cursor here, 1 = ok, 2 = arrows ok - SBYTE status; - BYTE fulltext; // [RH] Menu name is text, not a graphic - - // hotkey in menu - char alphaKey; - - const char *name; - - // choice = menu item #. - // if status = 2, - // choice=0:leftarrow,1:rightarrow - void (*routine)(int choice); - int textcolor; -}; - -struct oldmenu_t -{ - short numitems; // # of menu items - oldmenuitem_t *menuitems; // menu items - void (*routine)(void); // draw routine - short x; - short y; // x,y of menu - short lastOn; // last item user was on in menu -}; - -struct menustack_t -{ - union { - menu_t *newmenu; - oldmenu_t *old; - } menu; - bool isNewStyle; - bool drawSkull; -}; - -enum EMenuKey -{ - MKEY_Up, - MKEY_Down, - MKEY_Left, - MKEY_Right, - MKEY_PageUp, - MKEY_PageDown, - //----------------- Keys past here do not repeat. - MKEY_Enter, - MKEY_Back, // Back to previous menu - MKEY_Clear, // Clear keybinding/flip player sprite preview - - NUM_MKEYS -}; - -void M_ButtonHandler(EMenuKey key, bool repeat); -void M_OptButtonHandler(EMenuKey key, bool repeat); -void M_DrawConText (int color, int x, int y, const char *str); - -extern value_t YesNo[2]; -extern value_t NoYes[2]; -extern value_t OnOff[2]; - -extern menustack_t MenuStack[16]; -extern int MenuStackDepth; - -extern bool OptionsActive; -extern int skullAnimCounter; - -extern menu_t *CurrentMenu; -extern int CurrentItem; - -#define MAX_EPISODES 8 - -extern oldmenuitem_t EpisodeMenu[MAX_EPISODES]; -extern bool EpisodeNoSkill[MAX_EPISODES]; -extern char EpisodeMaps[MAX_EPISODES][9]; -extern oldmenu_t EpiDef; - -#endif diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 1f8b9f3e6c..d070fdd524 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -85,8 +85,6 @@ CVAR(String, screenshot_type, "png", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR(String, screenshot_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); EXTERN_CVAR(Bool, longsavemessages); -extern void FreeKeySections(); - static long ParseCommandLine (const char *args, int *argc, char **argv); // @@ -167,7 +165,7 @@ void M_FindResponseFile (void) int argc = 0; FILE *handle; int size; - long argsize; + long argsize = 0; int index; // Any more response files after the limit will be removed from the @@ -420,7 +418,6 @@ void M_LoadDefaults () { GameConfig = new FGameConfigFile; GameConfig->DoGlobalSetup (); - atterm (FreeKeySections); atterm (M_SaveDefaultsFinal); } diff --git a/src/m_misc.h b/src/m_misc.h index 1a7b17a965..23905d7653 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -43,6 +43,7 @@ void M_LoadDefaults (); bool M_SaveDefaults (const char *filename); void M_SaveCustomKeys (FConfigFile *config, char *section, char *subsection, size_t sublen); + // Prepends ~/.zdoom to path FString GetUserFile (const char *path); diff --git a/src/m_options.cpp b/src/m_options.cpp deleted file mode 100644 index 324b45cc6b..0000000000 --- a/src/m_options.cpp +++ /dev/null @@ -1,3882 +0,0 @@ -/* -** m_options.cpp -** New options menu code -** -**--------------------------------------------------------------------------- -** Copyright 1998-2009 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. -**--------------------------------------------------------------------------- -** -** Sorry this got so convoluted. It was originally much cleaner until -** I started adding all sorts of gadgets to the menus. I might someday -** make a project of rewriting the entire menu system using Amiga-style -** taglists to describe each menu item. We'll see... (Probably not.) -*/ - -#include "templates.h" - -#include "doomdef.h" -#include "gstrings.h" - -#include "c_console.h" -#include "c_dispatch.h" -#include "c_bind.h" - -#include "d_main.h" -#include "d_gui.h" - -#include "i_system.h" -#include "i_video.h" - -#include "i_music.h" -#include "i_input.h" -#include "m_joy.h" - -#include "v_video.h" -#include "v_text.h" -#include "w_wad.h" -#include "gi.h" - -#include "r_local.h" -#include "v_palette.h" -#include "gameconfigfile.h" - -#include "hu_stuff.h" - -#include "g_game.h" - -#include "m_argv.h" -#include "m_swap.h" - -#include "s_sound.h" - -#include "doomstat.h" - -#include "m_misc.h" -#include "hardware.h" -#include "sc_man.h" -#include "cmdlib.h" -#include "d_event.h" - -#include "sbar.h" - -// Data. -#include "m_menu.h" - -extern FButtonStatus MenuButtons[NUM_MKEYS]; - -EXTERN_CVAR(Bool, nomonsterinterpolation) -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, snd_channels) - -// -// defaulted values -// -CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) - -// Show messages has default, 0 = off, 1 = on -CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Bool, show_obituaries, true, CVAR_ARCHIVE) -EXTERN_CVAR (Bool, longsavemessages) -EXTERN_CVAR (Bool, screenshot_quiet) - -EXTERN_CVAR (Bool, cl_run) -EXTERN_CVAR (Int, crosshair) -EXTERN_CVAR (Bool, freelook) -EXTERN_CVAR (Int, sv_smartaim) -EXTERN_CVAR (Int, am_colorset) -EXTERN_CVAR (Int, vid_aspect) - -static void CalcIndent (menu_t *menu); - -void M_ChangeMessages (); -void M_SizeDisplay (int diff); - -int M_StringHeight (char *string); - -EColorRange LabelColor; -EColorRange ValueColor; -EColorRange MoreColor; - -static bool CanScrollUp; -static bool CanScrollDown; -static int VisBottom; - -value_t YesNo[2] = { - { 0.0, "No" }, - { 1.0, "Yes" } -}; - -value_t NoYes[2] = { - { 0.0, "Yes" }, - { 1.0, "No" } -}; - -value_t OnOff[2] = { - { 0.0, "Off" }, - { 1.0, "On" } -}; - -value_t OffOn[2] = { - { 0.0, "On" }, - { 1.0, "Off" } -}; - -value_t CompatModes[] = { - { 0.0, "Default" }, - { 1.0, "Doom" }, - { 2.0, "Doom (strict)" }, - { 3.0, "Boom" }, - { 6.0, "Boom (strict)" }, - { 5.0, "MBF" }, - { 4.0, "ZDoom 2.0.63" }, -}; - -menu_t *CurrentMenu; -int CurrentItem; -static const char *OldMessage; -static itemtype OldType; - -int flagsvar; -enum -{ - SHOW_DMFlags = 1, - SHOW_DMFlags2 = 2, - SHOW_CompatFlags = 4 -}; - -/*======================================= - * - * Confirm Menu - Used by safemore - * - *=======================================*/ -static void ActivateConfirm (const char *text, void (*func)()); -static void ConfirmIsAGo (); - -static menuitem_t ConfirmItems[] = { - { whitetext,NULL, {NULL}, {0}, {0}, {0}, {NULL} }, - { redtext, "Do you really want to do this?", {NULL}, {0}, {0}, {0}, {NULL} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {NULL} }, - { rightmore,"Yes", {NULL}, {0}, {0}, {0}, {(value_t*)ConfirmIsAGo} }, - { rightmore,"No", {NULL}, {0}, {0}, {0}, {(value_t*)M_PopMenuStack} }, -}; - -static menu_t ConfirmMenu = { - "PLEASE CONFIRM", - 3, - countof(ConfirmItems), - 140, - ConfirmItems, -}; - -/*======================================= - * - * Options Menu - * - *=======================================*/ - -static void CustomizeControls (void); -static void GameplayOptions (void); -static void CompatibilityOptions (void); -static void VideoOptions (void); -static void SoundOptions (void); -static void MouseOptions (void); -static void JoystickOptions (void); -static void GoToConsole (void); -void M_PlayerSetup (void); -void Reset2Defaults (void); -void Reset2Saved (void); - -static void SetVidMode (void); - -static menuitem_t OptionItems[] = -{ - { more, "Customize Controls", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)CustomizeControls} }, - { more, "Mouse options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)MouseOptions} }, - { more, "Joystick options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)JoystickOptions} }, - { discrete, "Always Run", {&cl_run}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { 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, "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} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { safemore, "Reset to defaults", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)Reset2Defaults} }, - { safemore, "Reset to last saved", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)Reset2Saved} }, - { more, "Go to console", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)GoToConsole} }, -}; - -menu_t OptionMenu = -{ - "OPTIONS", - 0, - countof(OptionItems), - 0, - OptionItems, -}; - -/*======================================= - * - * Mouse Menu - * - *=======================================*/ - -EXTERN_CVAR (Bool, use_mouse) -EXTERN_CVAR (Bool, smooth_mouse) -EXTERN_CVAR (Float, m_forward) -EXTERN_CVAR (Float, m_pitch) -EXTERN_CVAR (Float, m_side) -EXTERN_CVAR (Float, m_yaw) -EXTERN_CVAR (Bool, invertmouse) -EXTERN_CVAR (Bool, lookspring) -EXTERN_CVAR (Bool, lookstrafe) -EXTERN_CVAR (Bool, m_noprescale) - -static menuitem_t MouseItems[] = -{ - { discrete, "Enable mouse", {&use_mouse}, {2.0}, {0.0}, {0.0}, {YesNo} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { slider, "Overall sensitivity", {&mouse_sensitivity}, {0.5}, {2.5}, {0.1f}, {NULL} }, - { discrete, "Prescale mouse movement",{&m_noprescale}, {2.0}, {0.0}, {0.0}, {NoYes} }, - { discrete, "Smooth mouse movement",{&smooth_mouse}, {2.0}, {0.0}, {0.0}, {YesNo} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { slider, "Turning speed", {&m_yaw}, {0.0}, {2.5}, {0.1f}, {NULL} }, - { slider, "Mouselook speed", {&m_pitch}, {0.0}, {2.5}, {0.1f}, {NULL} }, - { slider, "Forward/Backward speed",{&m_forward}, {0.0}, {2.5}, {0.1f}, {NULL} }, - { slider, "Strafing speed", {&m_side}, {0.0}, {2.5}, {0.1f}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Always Mouselook", {&freelook}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Invert Mouse", {&invertmouse}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Lookspring", {&lookspring}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Lookstrafe", {&lookstrafe}, {2.0}, {0.0}, {0.0}, {OnOff} }, -}; - -menu_t MouseMenu = -{ - "MOUSE OPTIONS", - 0, - countof(MouseItems), - 0, - MouseItems, -}; - -/*======================================= - * - * Joystick Menu - * - *=======================================*/ - -EXTERN_CVAR(Bool, use_joystick) -EXTERN_CVAR(Bool, joy_ps2raw) -EXTERN_CVAR(Bool, joy_dinput) -EXTERN_CVAR(Bool, joy_xinput) - -static TArray Joysticks; -static TArray JoystickItems; - -menu_t JoystickMenu = -{ - "CONTROLLER OPTIONS", -}; - -/*======================================= - * - * Joystick Config Menu - * - *=======================================*/ - -IJoystickConfig *SELECTED_JOYSTICK; - -static value_t JoyAxisMapNames[6] = -{ - { (float)JOYAXIS_None, "None" }, - { (float)JOYAXIS_Yaw, "Turning" }, - { (float)JOYAXIS_Pitch, "Looking Up/Down" }, - { (float)JOYAXIS_Forward, "Moving Forward" }, - { (float)JOYAXIS_Side, "Strafing" }, - { (float)JOYAXIS_Up, "Moving Up/Down" } -}; - -static value_t Inversion[2] = -{ - { 0.0, "Not Inverted" }, - { 1.0, "Inverted" } -}; - -static TArray JoystickConfigItems; - -menu_t JoystickConfigMenu = -{ - "CONFIGURE CONTROLLER", -}; - - -/*======================================= - * - * Controls Menu - * - *=======================================*/ - -menuitem_t ControlsItems[] = -{ - { 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,"Controls", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Fire", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+attack"} }, - { control, "Secondary Fire", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+altattack"} }, - { control, "Use / Open", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+use"} }, - { control, "Move forward", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+forward"} }, - { control, "Move backward", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+back"} }, - { control, "Strafe left", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+moveleft"} }, - { control, "Strafe right", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+moveright"} }, - { control, "Turn left", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+left"} }, - { control, "Turn right", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+right"} }, - { control, "Jump", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+jump"} }, - { control, "Crouch", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+crouch"} }, - { control, "Crouch Toggle", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"crouch"} }, - { control, "Fly / Swim up", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+moveup"} }, - { control, "Fly / Swim down", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+movedown"} }, - { control, "Stop flying", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"land"} }, - { control, "Mouse look", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+mlook"} }, - { control, "Keyboard look", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+klook"} }, - { control, "Look up", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+lookup"} }, - { control, "Look down", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+lookdown"} }, - { control, "Center view", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"centerview"} }, - { control, "Run", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+speed"} }, - { control, "Strafe", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+strafe"} }, - { control, "Show Scoreboard", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+showscores"} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Chat", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Say", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"messagemode"} }, - { control, "Team say", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"messagemode2"} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Weapons", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Next weapon", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"weapnext"} }, - { control, "Previous weapon", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"weapprev"} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Inventory", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Activate item", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invuse"} }, - { control, "Activate all items", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invuseall"} }, - { control, "Next item", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invnext"} }, - { control, "Previous item", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invprev"} }, - { control, "Drop item", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invdrop"} }, - { control, "Query item", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"invquery"} }, - { control, "Drop weapon", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"weapdrop"} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Other", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Toggle automap", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"togglemap"} }, - { control, "Chasecam", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"chase"} }, - { control, "Coop spy", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"spynext"} }, - { control, "Screenshot", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"screenshot"} }, - { control, "Open console", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"toggleconsole"} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Strife Popup Screens", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { control, "Mission objectives", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"showpop 1"} }, - { control, "Keys list", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"showpop 2"} }, - { control, "Weapons/ammo/stats", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"showpop 3"} }, -}; - -static TArray CustomControlsItems (0); - -menu_t ControlsMenu = -{ - "CUSTOMIZE CONTROLS", - 3, - countof(ControlsItems), - 0, - ControlsItems, - 2, -}; - -/*======================================= - * - * Display Options Menu - * - *=======================================*/ -static void StartMessagesMenu (void); -static void StartAutomapMenu (void); -static void StartScoreboardMenu (void); -static void InitCrosshairsList(); - -EXTERN_CVAR (Bool, st_scale) -EXTERN_CVAR (Bool, r_stretchsky) -EXTERN_CVAR (Int, r_columnmethod) -EXTERN_CVAR (Bool, r_drawfuzz) -EXTERN_CVAR (Int, cl_rockettrails) -EXTERN_CVAR (Int, cl_pufftype) -EXTERN_CVAR (Int, cl_bloodtype) -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; - -static value_t ColumnMethods[] = { - { 0.0, "Original" }, - { 1.0, "Optimized" } -}; - -static value_t RocketTrailTypes[] = { - { 0.0, "Off" }, - { 1.0, "Particles" }, - { 2.0, "Sprites" }, - { 3.0, "Sprites & Particles" } -}; - -static value_t BloodTypes[] = { - { 0.0, "Sprites" }, - { 1.0, "Sprites & Particles" }, - { 2.0, "Particles" } -}; - -static value_t PuffTypes[] = { - { 0.0, "Sprites" }, - { 1.0, "Particles" } -}; - -static value_t Wipes[] = { - { 0.0, "None" }, - { 1.0, "Melt" }, - { 2.0, "Burn" }, - { 3.0, "Crossfade" } -}; - -static value_t Endoom[] = { - { 0.0, "Off" }, - { 1.0, "On" }, - { 2.0, "Only modified" } -}; - -static value_t Contrast[] = { - { 0.0, "Off" }, - { 1.0, "On" }, - { 2.0, "Smooth" } -}; - -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} }, - { slider, "Brightness", {&Gamma}, {1.0}, {3.0}, {0.1f}, {NULL} }, - { discrete, "Vertical Sync", {&vid_vsync}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discretes,"Crosshair", {&crosshair}, {8.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Column render mode", {&r_columnmethod}, {2.0}, {0.0}, {0.0}, {ColumnMethods} }, - { discrete, "Stretch short skies", {&r_stretchsky}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Stretch status bar", {&st_scale}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Alternative HUD", {&hud_althud}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Screen wipe style", {&wipetype}, {4.0}, {0.0}, {0.0}, {Wipes} }, -#ifdef _WIN32 - { discrete, "Show ENDOOM screen", {&showendoom}, {3.0}, {0.0}, {0.0}, {Endoom} }, - { discrete, "DirectDraw palette hack", {&vid_palettehack}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Use attached surfaces", {&vid_attachedsurfaces},{2.0}, {0.0}, {0.0}, {OnOff} }, -#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} }, -}; - -#define CROSSHAIR_INDEX 7 - -menu_t VideoMenu = -{ - "DISPLAY OPTIONS", - 0, - countof(VideoItems), - 0, - VideoItems, -}; - -/*======================================= - * - * Automap Menu - * - *=======================================*/ -static void StartMapColorsMenu (void); - -EXTERN_CVAR (Int, am_rotate) -EXTERN_CVAR (Int, am_overlay) -EXTERN_CVAR (Bool, am_showitems) -EXTERN_CVAR (Bool, am_showmonsters) -EXTERN_CVAR (Bool, am_showsecrets) -EXTERN_CVAR (Bool, am_showtime) -EXTERN_CVAR (Int, am_map_secrets) -EXTERN_CVAR (Bool, am_showtotaltime) -EXTERN_CVAR (Bool, am_drawmapback) - -static value_t MapColorTypes[] = { - { 0, "Custom" }, - { 1, "Traditional Doom" }, - { 2, "Traditional Strife" }, - { 3, "Traditional Raven" } -}; - -static value_t SecretTypes[] = { - { 0, "Never" }, - { 1, "Only when found" }, - { 2, "Always" }, -}; - -static value_t RotateTypes[] = { - { 0, "Off" }, - { 1, "On" }, - { 2, "On for overlay only" } -}; - -static value_t OverlayTypes[] = { - { 0, "Off" }, - { 1, "Overlay+Normal" }, - { 2, "Overlay Only" } -}; - -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} }, - { 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} }, - { 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} }, - { discrete, "Show secret counts", {&am_showsecrets}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Show time elapsed", {&am_showtime}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { 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} }, -}; - -menu_t AutomapMenu = -{ - "AUTOMAP OPTIONS", - 0, - countof(AutomapItems), - 0, - AutomapItems, -}; - -/*======================================= - * - * Map Colors Menu - * - *=======================================*/ -static void DefaultCustomColors(); - -EXTERN_CVAR (Color, am_backcolor) -EXTERN_CVAR (Color, am_yourcolor) -EXTERN_CVAR (Color, am_wallcolor) -EXTERN_CVAR (Color, am_secretwallcolor) -EXTERN_CVAR (Color, am_tswallcolor) -EXTERN_CVAR (Color, am_fdwallcolor) -EXTERN_CVAR (Color, am_cdwallcolor) -EXTERN_CVAR (Color, am_thingcolor) -EXTERN_CVAR (Color, am_gridcolor) -EXTERN_CVAR (Color, am_xhaircolor) -EXTERN_CVAR (Color, am_notseencolor) -EXTERN_CVAR (Color, am_lockedcolor) -EXTERN_CVAR (Color, am_ovyourcolor) -EXTERN_CVAR (Color, am_ovwallcolor) -EXTERN_CVAR (Color, am_ovthingcolor) -EXTERN_CVAR (Color, am_ovotherwallscolor) -EXTERN_CVAR (Color, am_ovunseencolor) -EXTERN_CVAR (Color, am_ovtelecolor) -EXTERN_CVAR (Color, am_intralevelcolor) -EXTERN_CVAR (Color, am_interlevelcolor) -EXTERN_CVAR (Color, am_secretsectorcolor) -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_ovthingcolor_friend) -EXTERN_CVAR (Color, am_ovthingcolor_monster) -EXTERN_CVAR (Color, am_ovthingcolor_item) - -static menuitem_t MapColorsItems[] = { - { rsafemore, "Restore default custom colors", {NULL}, {0}, {0}, {0}, {(value_t*)DefaultCustomColors} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { colorpicker, "Background", {&am_backcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "You", {&am_yourcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "1-sided walls", {&am_wallcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "2-sided walls with different floors", {&am_fdwallcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "2-sided walls with different ceilings", {&am_cdwallcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Map grid", {&am_gridcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Center point", {&am_xhaircolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Not-yet-seen walls", {&am_notseencolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Locked doors", {&am_lockedcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Teleporter to the same map", {&am_intralevelcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Teleporter to a different map", {&am_interlevelcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Secret sector", {&am_secretsectorcolor}, {0}, {0}, {0}, {0} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { colorpicker, "Invisible 2-sided walls (for cheat)", {&am_tswallcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Secret walls (for cheat)", {&am_secretwallcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Actors (for cheat)", {&am_thingcolor}, {0}, {0}, {0}, {0} }, - { 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} }, - { 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} }, - { colorpicker, "2-sided walls (overlay)", {&am_ovotherwallscolor},{0}, {0}, {0}, {0} }, - { colorpicker, "Not-yet-seen walls (overlay)", {&am_ovunseencolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Teleporter (overlay)", {&am_ovtelecolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Secret sector (overlay)", {&am_ovsecretsectorcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "Actors (overlay) (for cheat)", {&am_ovthingcolor}, {0}, {0}, {0}, {0} }, - { 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} }, -}; - -menu_t MapColorsMenu = -{ - "CUSTOMIZE MAP COLORS", - 0, - countof(MapColorsItems), - 48, - MapColorsItems, -}; - -/*======================================= - * - * Color Picker Sub-menu - * - *=======================================*/ -static void StartColorPickerMenu (const char *colorname, FColorCVar *cvar); -static void ColorPickerReset (); -static int CurrColorIndex; -static int SelColorIndex; -static void UpdateSelColor (int index); - - -static menuitem_t ColorPickerItems[] = { - { redtext, NULL, {NULL}, {0}, {0}, {0}, {0} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { intslider, "Red", {NULL}, {0}, {255}, {15}, {0} }, - { intslider, "Green", {NULL}, {0}, {255}, {15}, {0} }, - { intslider, "Blue", {NULL}, {0}, {255}, {15}, {0} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { more, "Undo changes", {NULL}, {0}, {0}, {0}, {(value_t*)ColorPickerReset} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {0}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {1}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {2}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {3}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {4}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {5}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {6}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {7}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {8}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {9}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {10}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {11}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {12}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {13}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {14}, {0}, {0}, {0} }, - { palettegrid, " ", {NULL}, {15}, {0}, {0}, {0} }, -}; - -menu_t ColorPickerMenu = -{ - "SELECT COLOR", - 2, - countof(ColorPickerItems), - 0, - ColorPickerItems, -}; - -/*======================================= - * - * Messages Menu - * - *=======================================*/ -EXTERN_CVAR (Int, con_scaletext) -EXTERN_CVAR (Bool, con_centernotify) -EXTERN_CVAR (Int, msg0color) -EXTERN_CVAR (Int, msg1color) -EXTERN_CVAR (Int, msg2color) -EXTERN_CVAR (Int, msg3color) -EXTERN_CVAR (Int, msg4color) -EXTERN_CVAR (Int, msgmidcolor) -EXTERN_CVAR (Int, msglevel) - -static value_t ScaleValues[] = -{ - { 0.0, "Off" }, - { 1.0, "On" }, - { 2.0, "Double" } -}; - -static value_t TextColors[] = -{ - { 0.0, "brick" }, - { 1.0, "tan" }, - { 2.0, "gray" }, - { 3.0, "green" }, - { 4.0, "brown" }, - { 5.0, "gold" }, - { 6.0, "red" }, - { 7.0, "blue" }, - { 8.0, "orange" }, - { 9.0, "white" }, - { 10.0, "yellow" }, - { 11.0, "default" }, - { 12.0, "black" }, - { 13.0, "light blue" }, - { 14.0, "cream" }, - { 15.0, "olive" }, - { 16.0, "dark green" }, - { 17.0, "dark red" }, - { 18.0, "dark brown" }, - { 19.0, "purple" }, - { 20.0, "dark gray" }, -}; - -static value_t MessageLevels[] = { - { 0.0, "Item Pickup" }, - { 1.0, "Obituaries" }, - { 2.0, "Critical Messages" } -}; - -static menuitem_t MessagesItems[] = { - { discrete, "Show messages", {&show_messages}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Show obituaries", {&show_obituaries}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Scale text in high res", {&con_scaletext}, {3.0}, {0.0}, {0.0}, {ScaleValues} }, - { discrete, "Minimum message level", {&msglevel}, {3.0}, {0.0}, {0.0}, {MessageLevels} }, - { discrete, "Center messages", {&con_centernotify}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext, "Message Colors", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { cdiscrete, "Item Pickup", {&msg0color}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Obituaries", {&msg1color}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Critical Messages", {&msg2color}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Chat Messages", {&msg3color}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Team Messages", {&msg4color}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Centered Messages", {&msgmidcolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Screenshot messages", {&screenshot_quiet}, {2.0}, {0.0}, {0.0}, {OffOn} }, - { discrete, "Detailed save messages",{&longsavemessages}, {2.0}, {0.0}, {0.0}, {OnOff} }, -}; - -menu_t MessagesMenu = -{ - "MESSAGES", - 0, - countof(MessagesItems), - 0, - MessagesItems, -}; - - -/*======================================= - * - * Scoreboard Menu - * - *=======================================*/ - -EXTERN_CVAR (Bool, sb_cooperative_enable) -EXTERN_CVAR (Int, sb_cooperative_headingcolor) -EXTERN_CVAR (Int, sb_cooperative_yourplayercolor) -EXTERN_CVAR (Int, sb_cooperative_otherplayercolor) - -EXTERN_CVAR (Bool, sb_deathmatch_enable) -EXTERN_CVAR (Int, sb_deathmatch_headingcolor) -EXTERN_CVAR (Int, sb_deathmatch_yourplayercolor) -EXTERN_CVAR (Int, sb_deathmatch_otherplayercolor) - -EXTERN_CVAR (Bool, sb_teamdeathmatch_enable) -EXTERN_CVAR (Int, sb_teamdeathmatch_headingcolor) - -static menuitem_t ScoreboardItems[] = { - { whitetext, "Cooperative Options", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Enable Scoreboard", {&sb_cooperative_enable}, {21.0}, {0.0}, {0.0}, {YesNo} }, - { cdiscrete, "Header Color", {&sb_cooperative_headingcolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Your Player Color", {&sb_cooperative_yourplayercolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Other Players' Color", {&sb_cooperative_otherplayercolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext, "Deathmatch Options", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Enable Scoreboard", {&sb_deathmatch_enable}, {21.0}, {0.0}, {0.0}, {YesNo} }, - { cdiscrete, "Header Color", {&sb_deathmatch_headingcolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Your Player Color", {&sb_deathmatch_yourplayercolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { cdiscrete, "Other Players' Color", {&sb_deathmatch_otherplayercolor}, {21.0}, {0.0}, {0.0}, {TextColors} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext, "Team Deathmatch Options", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Enable Scoreboard", {&sb_teamdeathmatch_enable}, {21.0}, {0.0}, {0.0}, {YesNo} }, - { cdiscrete, "Header Color", {&sb_teamdeathmatch_headingcolor}, {21.0}, {0.0}, {0.0}, {TextColors} } -}; - -menu_t ScoreboardMenu = -{ - "SCOREBOARD OPTIONS", - 2, - countof(ScoreboardItems), - 0, - ScoreboardItems, -}; - - -/*======================================= - * - * Video Modes Menu - * - *=======================================*/ - -extern bool setmodeneeded; -extern int NewWidth, NewHeight, NewBits; -extern int DisplayBits; - -int testingmode; // Holds time to revert to old mode -int OldWidth, OldHeight, OldBits; - -void M_FreeModesList (); -static void BuildModesList (int hiwidth, int hiheight, int hi_id); -static bool GetSelectedSize (int line, int *width, int *height); -static void SetModesMenu (int w, int h, int bits); - -EXTERN_CVAR (Int, vid_defwidth) -EXTERN_CVAR (Int, vid_defheight) -EXTERN_CVAR (Int, vid_defbits) - -static FIntCVar DummyDepthCvar (NULL, 0, 0); - -EXTERN_CVAR (Bool, fullscreen) - -static value_t Depths[22]; - -EXTERN_CVAR (Bool, vid_tft) // Defined below -CUSTOM_CVAR (Int, menu_screenratios, 0, CVAR_ARCHIVE) -{ - if (self < 0 || self > 4) - { - self = 3; - } - else if (self == 4 && !vid_tft) - { - self = 3; - } - else - { - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - } -} - -static value_t ForceRatios[] = -{ - { 0.0, "Off" }, - { 3.0, "4:3" }, - { 1.0, "16:9" }, - { 2.0, "16:10" }, - { 4.0, "5:4" } -}; -static value_t Ratios[] = -{ - { 0.0, "4:3" }, - { 1.0, "16:9" }, - { 2.0, "16:10" }, - { 3.0, "All" } -}; -static value_t RatiosTFT[] = -{ - { 0.0, "4:3" }, - { 4.0, "5:4" }, - { 1.0, "16:9" }, - { 2.0, "16:10" }, - { 3.0, "All" } -}; - -static char VMEnterText[] = "Press ENTER to set mode"; -static char VMTestText[] = "T to test mode for 5 seconds"; - -static menuitem_t ModesItems[] = { -// { discrete, "Screen mode", {&DummyDepthCvar}, {0.0}, {0.0}, {0.0}, {Depths} }, - { discrete, "Force aspect ratio", {&vid_aspect}, {5.0}, {0.0}, {0.0}, {ForceRatios} }, - { discrete, "Aspect ratio", {&menu_screenratios}, {4.0}, {0.0}, {0.0}, {Ratios} }, - { discrete, "Fullscreen", {&fullscreen}, {2.0}, {0.0}, {0.0}, {YesNo} }, - { discrete, "Enable 5:4 aspect ratio",{&vid_tft}, {2.0}, {0.0}, {0.0}, {YesNo} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { screenres,NULL, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, -// { whitetext,"Note: Only 8 bpp modes are supported",{NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, VMEnterText, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, VMTestText, {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, -}; - -#define VM_ASPECTITEM 1 -#define VM_RESSTART 5 -#define VM_ENTERLINE 15 -#define VM_TESTLINE 17 - -menu_t ModesMenu = -{ - "VIDEO MODE", - 2, - countof(ModesItems), - 0, - ModesItems, -}; - -CUSTOM_CVAR (Bool, vid_tft, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - if (self) - { - ModesItems[VM_ASPECTITEM].b.numvalues = 5.f; - ModesItems[VM_ASPECTITEM].e.values = RatiosTFT; - } - else - { - ModesItems[VM_ASPECTITEM].b.numvalues = 4.f; - ModesItems[VM_ASPECTITEM].e.values = Ratios; - if (menu_screenratios == 4) - { - menu_screenratios = 0; - } - } - setsizeneeded = true; - if (StatusBar != NULL) - { - StatusBar->ScreenSizeChanged(); - } -} - -/*======================================= - * - * Gameplay Options (dmflags) Menu - * - *=======================================*/ -value_t SmartAim[4] = { - { 0.0, "Off" }, - { 1.0, "On" }, - { 2.0, "Never friends" }, - { 3.0, "Only monsters" } -}; - -value_t FallingDM[4] = { - { 0, "Off" }, - { DF_FORCE_FALLINGZD, "Old" }, - { DF_FORCE_FALLINGHX, "Hexen" }, - { DF_FORCE_FALLINGZD|DF_FORCE_FALLINGHX, "Strife" } -}; - -value_t DF_Jump[3] = { - { 0, "Default" }, - { DF_NO_JUMP, "Off" }, - { DF_YES_JUMP, "On" } -}; - -value_t DF_Crouch[3] = { - { 0, "Default" }, - { DF_NO_CROUCH, "Off" }, - { DF_YES_CROUCH, "On" } -}; - - -static menuitem_t DMFlagsItems[] = { - { discrete, "Teamplay", {&teamplay}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Team damage scalar", {&teamdamage}, {0.0}, {1.0}, {0.05f},{NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Smart Autoaim", {&sv_smartaim}, {4.0}, {0.0}, {0.0}, {SmartAim} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { bitmask, "Falling damage", {&dmflags}, {4.0}, {DF_FORCE_FALLINGZD|DF_FORCE_FALLINGHX}, {0}, {FallingDM} }, - { bitflag, "Drop weapon", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_WEAPONDROP} }, - { bitflag, "Double ammo", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_DOUBLEAMMO} }, - { bitflag, "Infinite ammo", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_INFINITE_AMMO} }, - { bitflag, "Infinite inventory", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_INFINITE_INVENTORY} }, - { bitflag, "No monsters", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_NO_MONSTERS} }, - { bitflag, "No monsters to exit", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_KILL_MONSTERS} }, - { bitflag, "Monsters respawn", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_MONSTERS_RESPAWN} }, - { bitflag, "No respawn", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_NO_RESPAWN} }, - { bitflag, "Items respawn", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_ITEMS_RESPAWN} }, - { bitflag, "Big powerups respawn", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_RESPAWN_SUPER} }, - { 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, "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} }, - { bitflag, "Allow FOV", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_FOV} }, - { bitflag, "Allow BFG aiming", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NO_FREEAIMBFG} }, - { bitflag, "Allow automap", {&dmflags2}, {1}, {0}, {0}, {(value_t *)DF2_NO_AUTOMAP} }, - { 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 boss brain kills all its monsters", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_KILLBOSSMONST} }, - - { redtext, " ", {NULL}, {0}, {0}, {0}, {NULL} }, - { whitetext,"Deathmatch Settings", {NULL}, {0}, {0}, {0}, {NULL} }, - { bitflag, "Weapons stay", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_WEAPONS_STAY} }, - { bitflag, "Allow powerups", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_ITEMS} }, - { bitflag, "Allow health", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_HEALTH} }, - { bitflag, "Allow armor", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_ARMOR} }, - { bitflag, "Spawn farthest", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_SPAWN_FARTHEST} }, - { bitflag, "Same map", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_SAME_LEVEL} }, - { bitflag, "Force respawn", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_FORCE_RESPAWN} }, - { bitflag, "Allow exit", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_EXIT} }, - { bitflag, "Barrels respawn", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_BARRELS_RESPAWN} }, - { bitflag, "Respawn protection", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_RESPAWN_INVUL} }, - { bitflag, "Lose frag if fragged", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_LOSEFRAG} }, - { bitflag, "Keep frags gained", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_YES_KEEPFRAGS} }, - { bitflag, "No team switching", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_NO_TEAM_SWITCH} }, - - { redtext, " ", {NULL}, {0}, {0}, {0}, {NULL} }, - { whitetext,"Cooperative Settings", {NULL}, {0}, {0}, {0}, {NULL} }, - { bitflag, "Spawn multi. weapons", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_NO_COOP_WEAPON_SPAWN} }, - { bitflag, "Lose entire inventory",{&dmflags}, {0}, {0}, {0}, {(value_t *)DF_COOP_LOSE_INVENTORY} }, - { bitflag, "Keep keys", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_COOP_LOSE_KEYS} }, - { bitflag, "Keep weapons", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_COOP_LOSE_WEAPONS} }, - { bitflag, "Keep armor", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_COOP_LOSE_ARMOR} }, - { bitflag, "Keep powerups", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_COOP_LOSE_POWERUPS} }, - { bitflag, "Keep ammo", {&dmflags}, {1}, {0}, {0}, {(value_t *)DF_COOP_LOSE_AMMO} }, - { bitflag, "Lose half ammo", {&dmflags}, {0}, {0}, {0}, {(value_t *)DF_COOP_HALVE_AMMO} }, - { bitflag, "Spawn where died", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_SAME_SPAWN_SPOT} }, -}; - -static menu_t DMFlagsMenu = -{ - "GAMEPLAY OPTIONS", - 0, - countof(DMFlagsItems), - 0, - DMFlagsItems, -}; - -/*======================================= - * - * Compatibility Options Menu - * - *=======================================*/ - -static menuitem_t CompatibilityItems[] = { - { 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, "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} }, - { bitflag, "Disable BOOM door light effect", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_NODOORLIGHT} }, - { bitflag, "Raven scrollers use original speed", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_RAVENSCROLL} }, - { bitflag, "Use original sound target handling", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SOUNDTARGET} }, - { bitflag, "DEH health settings like Doom2.exe", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_DEHHEALTH} }, - { bitflag, "Self ref. sectors don't block shots", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_TRACE} }, - { bitflag, "Monsters get stuck over dropoffs", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_DROPOFF} }, - { bitflag, "Monsters cannot cross dropoffs", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_CROSSDROPOFF} }, - { bitflag, "Monsters see invisible players", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_INVISIBILITY} }, - { bitflag, "Boom scrollers are additive", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_BOOMSCROLL} }, - { bitflag, "Inst. moving floors are not silent", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SILENT_INSTANT_FLOORS} }, - { bitflag, "Sector sounds use center as source", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SECTORSOUNDS} }, - { bitflag, "Use Doom heights for missile clipping", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MISSILECLIP} }, - { bitflag, "Allow any bossdeath for level special", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_ANYBOSSDEATH} }, - { bitflag, "No Minotaur floor flames in water", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MINOTAUR} }, - { bitflag, "Original A_Mushroom speed in DEH mods", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MUSHROOM} }, - { bitflag, "Monster movement is affected by effects", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MBFMONSTERMOVE} }, - { 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} }, - - { discrete, "Interpolate monster movement", {&nomonsterinterpolation}, {2.0}, {0.0}, {0.0}, {NoYes} }, -}; - -static menu_t CompatibilityMenu = -{ - "COMPATIBILITY OPTIONS", - 0, - countof(CompatibilityItems), - 0, - CompatibilityItems, -}; - -/*======================================= - * - * Sound Options Menu - * - *=======================================*/ - -#ifdef _WIN32 -EXTERN_CVAR (Float, snd_movievolume) -#endif -EXTERN_CVAR (Bool, snd_flipstereo) -EXTERN_CVAR (Bool, snd_pitched) -EXTERN_CVAR (String, snd_output_format) -EXTERN_CVAR (String, snd_speakermode) -EXTERN_CVAR (String, snd_resampler) -EXTERN_CVAR (String, snd_output) -EXTERN_CVAR (Int, snd_buffersize) -EXTERN_CVAR (Int, snd_buffercount) -EXTERN_CVAR (Int, snd_samplerate) -EXTERN_CVAR (Bool, snd_hrtf) -EXTERN_CVAR (Bool, snd_waterreverb) -EXTERN_CVAR (Float, snd_waterlp) -EXTERN_CVAR (Int, snd_mididevice) - -static void MakeSoundChanges (); -static void AdvSoundOptions (); -static void ModReplayerOptions (); - -static value_t SampleRates[] = -{ - { 0.f, "Default" }, - { 4000.f, "4000 Hz" }, - { 8000.f, "8000 Hz" }, - { 11025.f, "11025 Hz" }, - { 22050.f, "22050 Hz" }, - { 32000.f, "32000 Hz" }, - { 44100.f, "44100 Hz" }, - { 48000.f, "48000 Hz" } -}; - -static value_t BufferSizes[] = -{ - { 0.f, "Default" }, - { 64.f, "64 samples" }, - { 128.f, "128 samples" }, - { 256.f, "256 samples" }, - { 512.f, "512 samples" }, - { 1024.f, "1024 samples" }, - { 2048.f, "2048 samples" }, - { 4096.f, "4096 samples" } -}; - -static value_t BufferCounts[] = -{ - { 0.f, "Default" }, - { 2.f, "2" }, - { 3.f, "3" }, - { 4.f, "4" }, - { 5.f, "5" }, - { 6.f, "6" }, - { 7.f, "7" }, - { 8.f, "8" }, - { 9.f, "9" }, - { 10.f, "10" }, - { 11.f, "11" }, - { 12.f, "12" } -}; - -static valueenum_t Outputs[] = -{ - { "Default", "Default" }, -#if defined(_WIN32) - { "DirectSound", "DirectSound" }, - { "WASAPI", "Vista WASAPI" }, - { "ASIO", "ASIO" }, - { "WaveOut", "WaveOut" }, - { "OpenAL", "OpenAL (very beta)" }, -#elif defined(unix) - { "OSS", "OSS" }, - { "ALSA", "ALSA" }, - { "SDL", "SDL" }, - { "ESD", "ESD" }, -#elif defined(__APPLE__) - { "Sound Manager", "Sound Manager" }, - { "Core Audio", "Core Audio" }, -#endif - { "No sound", "No sound" } -}; - -static valueenum_t OutputFormats[] = -{ - { "PCM-8", "8-bit" }, - { "PCM-16", "16-bit" }, - { "PCM-24", "24-bit" }, - { "PCM-32", "32-bit" }, - { "PCM-Float", "32-bit float" } -}; - -static valueenum_t SpeakerModes[] = -{ - { "Auto", "Auto" }, - { "Mono", "Mono" }, - { "Stereo", "Stereo" }, - { "Prologic", "Dolby Prologic Decoder" }, - { "Quad", "Quad" }, - { "Surround", "5 speakers" }, - { "5.1", "5.1 speakers" }, - { "7.1", "7.1 speakers" } -}; - -static valueenum_t Resamplers[] = -{ - { "NoInterp", "No interpolation" }, - { "Linear", "Linear" }, - { "Cubic", "Cubic" }, - { "Spline", "Spline" } -}; - -static menuitem_t SoundItems[] = -{ - { slider, "Sounds volume", {&snd_sfxvolume}, {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} }, - { discrete, "Underwater reverb", {&snd_waterreverb}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Underwater cutoff", {&snd_waterlp}, {0.0}, {2000.0},{50.0}, {NULL} }, - { discrete, "Randomize pitches", {&snd_pitched}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Sound channels", {&snd_channels}, {8.0}, {256.0}, {8.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { more, "Restart sound", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)MakeSoundChanges} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { ediscrete,"Output system", {&snd_output}, {countof(Outputs)}, {0.0}, {0.0}, {(value_t *)Outputs} }, - { ediscrete,"Output format", {&snd_output_format}, {5.0}, {0.0}, {0.0}, {(value_t *)OutputFormats} }, - { ediscrete,"Speaker mode", {&snd_speakermode}, {8.0}, {0.0}, {0.0}, {(value_t *)SpeakerModes} }, - { ediscrete,"Resampler", {&snd_resampler}, {4.0}, {0.0}, {0.0}, {(value_t *)Resamplers} }, - { discrete, "HRTF filter", {&snd_hrtf}, {2.0}, {0.0}, {0.0}, {(value_t *)OnOff} }, - - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { more, "Advanced options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)AdvSoundOptions} }, - { more, "Module replayer options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)ModReplayerOptions} }, -}; - -static menu_t SoundMenu = -{ - "SOUND OPTIONS", - 0, - countof(SoundItems), - 0, - SoundItems, -}; - -#define MIDI_DEVICE_ITEM 2 - -/*======================================= - * - * Advanced Sound Options Menu - * - *=======================================*/ - -EXTERN_CVAR (Bool, opl_onechip) - -static menuitem_t AdvSoundItems[] = -{ - { discrete, "Sample rate", {&snd_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, - { discrete, "Buffer count", {&snd_buffercount}, {12.0}, {0.0}, {0.0}, {BufferCounts} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} }, -}; - -static menu_t AdvSoundMenu = -{ - "ADVANCED SOUND OPTIONS", - 0, - countof(AdvSoundItems), - 0, - AdvSoundItems, -}; - -/*======================================= - * - * Module Replayer Options Menu - * - *=======================================*/ - -EXTERN_CVAR(Bool, mod_dumb) -EXTERN_CVAR(Int, mod_samplerate) -EXTERN_CVAR(Int, mod_volramp) -EXTERN_CVAR(Int, mod_interp) -EXTERN_CVAR(Bool, mod_autochip) -EXTERN_CVAR(Int, mod_autochip_size_force) -EXTERN_CVAR(Int, mod_autochip_size_scan) -EXTERN_CVAR(Int, mod_autochip_scan_threshold) - -static value_t ModReplayers[] = -{ - { 0.0, "FMOD" }, - { 1.0, "foo_dumb" } -}; - -static value_t ModInterpolations[] = -{ - { 0.0, "None" }, - { 1.0, "Linear" }, - { 2.0, "Cubic" } -}; - -static value_t ModVolumeRamps[] = -{ - { 0.0, "None" }, - { 1.0, "Logarithmic" }, - { 2.0, "Linear" }, - { 3.0, "XM=lin, else none" }, - { 4.0, "XM=lin, else log" } -}; - -static menuitem_t ModReplayerItems[] = -{ - { discrete, "Replayer engine", {&mod_dumb}, {2.0}, {0.0}, {0.0}, {ModReplayers} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Sample rate", {&mod_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Interpolation", {&mod_interp}, {3.0}, {0.0}, {0.0}, {ModInterpolations} }, - { discrete, "Volume ramping", {&mod_volramp}, {5.0}, {0.0}, {0.0}, {ModVolumeRamps} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Chip-o-matic", {&mod_autochip}, {2.0}, {0.0}, {0.0}, {OnOff} }, - // TODO if the menu system is ever rewritten: Provide a decent - // mechanism to edit the chip-o-matic settings like you can with - // the foo_dumb preferences in foobar2000. -}; - -static menu_t ModReplayerMenu = -{ - "MODULE REPLAYER OPTIONS", - 0, - countof(ModReplayerItems), - 0, - ModReplayerItems, -}; - - -//=========================================================================== -static void ActivateConfirm (const char *text, void (*func)()) -{ - ConfirmItems[0].label = text; - ConfirmItems[0].e.mfunc = func; - ConfirmMenu.lastOn = 3; - M_SwitchMenu (&ConfirmMenu); -} - -static void ConfirmIsAGo () -{ - M_PopMenuStack (); - ConfirmItems[0].e.mfunc (); -} - -// -// Set some stuff up for the video modes menu -// -static BYTE BitTranslate[32]; - -void M_OptInit (void) -{ - if (gameinfo.gametype & GAME_DoomChex) - { - LabelColor = CR_UNTRANSLATED; - ValueColor = CR_GRAY; - MoreColor = CR_GRAY; - } - else if (gameinfo.gametype == GAME_Heretic) - { - LabelColor = CR_GREEN; - ValueColor = CR_UNTRANSLATED; - MoreColor = CR_UNTRANSLATED; - } - else // Hexen - { - LabelColor = CR_RED; - ValueColor = CR_UNTRANSLATED; - MoreColor = CR_UNTRANSLATED; - } -} - -void M_InitVideoModesMenu () -{ - int dummy1, dummy2; - size_t currval = 0; - - M_RefreshModesList(); - - for (unsigned int i = 1; i <= 32 && currval < countof(Depths); i++) - { - Video->StartModeIterator (i, screen->IsFullscreen()); - if (Video->NextMode (&dummy1, &dummy2, NULL)) - { - /* - Depths[currval].value = currval; - mysnprintf (name, countof(name), "%d bit", i); - Depths[currval].name = copystring (name); - */ - BitTranslate[currval++] = i; - } - } - - //ModesItems[VM_DEPTHITEM].b.min = (float)currval; - - switch (Video->GetDisplayType ()) - { - case DISPLAY_FullscreenOnly: - ModesItems[2].type = nochoice; - ModesItems[2].b.min = 1.f; - break; - case DISPLAY_WindowOnly: - ModesItems[2].type = nochoice; - ModesItems[2].b.min = 0.f; - break; - default: - break; - } -} - - -// -// Toggle messages on/off -// -void M_ChangeMessages () -{ - if (show_messages) - { - Printf (128, "%s\n", GStrings("MSGOFF")); - show_messages = false; - } - else - { - Printf (128, "%s\n", GStrings("MSGON")); - show_messages = true; - } -} - -CCMD (togglemessages) -{ - M_ChangeMessages (); -} - -void M_SizeDisplay (int diff) -{ - // changing screenblocks automatically resizes the display - screenblocks = screenblocks + diff; -} - -CCMD (sizedown) -{ - M_SizeDisplay (-1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); -} - -CCMD (sizeup) -{ - M_SizeDisplay (1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); -} - -// Draws a string in the console font, scaled to the 8x8 cells -// used by the default console font. -void M_DrawConText (int color, int x, int y, const char *str) -{ - int len = (int)strlen(str); - - screen->DrawText (ConFont, color, x, y, str, - DTA_CellX, 8 * CleanXfac_1, - DTA_CellY, 8 * CleanYfac_1, - TAG_DONE); -} - -void M_BuildKeyList (menuitem_t *item, int numitems) -{ - int i; - - for (i = 0; i < numitems; i++, item++) - { - if (item->type == control) - C_GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); - } -} - -static void CalcIndent (menu_t *menu) -{ - int i, widest = 0, thiswidth; - menuitem_t *item; - - for (i = 0; i < menu->numitems; i++) - { - item = menu->items + i; - if (item->type != whitetext && item->type != redtext && item->type != screenres && - item->type != joymore && (item->type != discrete || item->c.discretecenter != 1)) - { - thiswidth = SmallFont->StringWidth (item->label); - if (thiswidth > widest) - widest = thiswidth; - } - } - menu->indent = widest + 4; -} - -void M_SwitchMenu (menu_t *menu) -{ - MenuStack[MenuStackDepth].menu.newmenu = menu; - MenuStack[MenuStackDepth].isNewStyle = true; - MenuStack[MenuStackDepth].drawSkull = false; - MenuStackDepth++; - - CanScrollUp = false; - CanScrollDown = false; - CurrentMenu = menu; - CurrentItem = menu->lastOn; - - if (!menu->indent) - { - CalcIndent (menu); - } - - flagsvar = 0; -} - -bool M_StartOptionsMenu (void) -{ - M_SwitchMenu (&OptionMenu); - return true; -} - -// Draw a slider. Set fracdigits negative to not display the current value numerically. -static void M_DrawSlider (int x, int y, double min, double max, double cur,int fracdigits) -{ - double range; - - range = max - min; - double ccur = clamp(cur, min, max) - min; - - M_DrawConText(CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12"); - M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * CleanXfac_1), y, "\x13"); - - if (fracdigits >= 0) - { - char textbuf[16]; - mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); - screen->DrawText(SmallFont, CR_DARKGRAY, x + (12*8 + 4) * CleanXfac_1, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE); - } -} - -int M_FindCurVal (float cur, value_t *values, int numvals) -{ - int v; - - for (v = 0; v < numvals; v++) - if (values[v].value == cur) - break; - - return v; -} - -int M_FindCurVal (float cur, valuestring_t *values, int numvals) -{ - int v; - - for (v = 0; v < numvals; v++) - if (values[v].value == cur) - break; - - return v; -} - -const char *M_FindCurVal(const char *cur, valueenum_t *values, int numvals) -{ - for (int v = 0; v < numvals; ++v) - { - if (stricmp(values[v].value, cur) == 0) - { - return values[v].name; - } - } - return cur; -} - -const char *M_FindPrevVal(const char *cur, valueenum_t *values, int numvals) -{ - for (int v = 0; v < numvals; ++v) - { - if (stricmp(values[v].value, cur) == 0) - { - return values[v == 0 ? numvals - 1 : v - 1].value; - } - } - return values[0].value; -} - -const char *M_FindNextVal(const char *cur, valueenum_t *values, int numvals) -{ - for (int v = 0; v < numvals; ++v) - { - if (stricmp(values[v].value, cur) == 0) - { - return values[v == numvals - 1 ? 0 : v + 1].value; - } - } - return values[0].value; -} - -void M_OptDrawer () -{ - EColorRange color; - int y, width, i, x, ytop, fontheight; - menuitem_t *item; - UCVarValue value; - DWORD overlay; - int labelofs; - int indent; - int cursorspace; - - if (!CurrentMenu->DontDim) - { - screen->Dim (); - } - - if (CurrentMenu->PreDraw != NULL) - { - if (CurrentMenu->PreDraw ()) return; - } - - if (CurrentMenu->y != 0) - { - y = CurrentMenu->y; - } - else - { - if (BigFont && CurrentMenu->texttitle) - { - screen->DrawText (BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - (screen->GetWidth() - BigFont->StringWidth(CurrentMenu->texttitle) * CleanXfac_1) / 2, 10*CleanYfac_1, - CurrentMenu->texttitle, DTA_CleanNoMove_1, true, TAG_DONE); - y = 15 + BigFont->GetHeight(); - } - else - { - y = 15; - } - } - if (gameinfo.gametype & GAME_Raven) - { - labelofs = 2 * CleanXfac_1; - y -= 2; - fontheight = 9; - } - else - { - labelofs = 0; - fontheight = 8; - } - cursorspace = 14 * CleanXfac_1; - y *= CleanYfac_1; - fontheight *= CleanYfac_1; - ytop = y + CurrentMenu->scrolltop * 8 * CleanYfac_1; - int lastrow = screen->GetHeight() - SmallFont->GetHeight() * CleanYfac_1; - - for (i = 0; i < CurrentMenu->numitems && y <= lastrow; i++, y += fontheight) - { - if (i == CurrentMenu->scrolltop) - { - i += CurrentMenu->scrollpos; - } - - item = CurrentMenu->items + i; - overlay = 0; - if (item->type == discrete && item->c.discretecenter == 1) - { - indent = screen->GetWidth() / 2; - } - else if (item->type == joymore) - { - indent = 4 * CleanXfac_1; - } - else - { - indent = CurrentMenu->indent; - if (indent > 280) - { // kludge for the compatibility options with their extremely long labels - if (indent + 40 <= CleanWidth_1) - { - indent = (screen->GetWidth() - ((indent + 40) * CleanXfac_1)) / 2 + indent * CleanXfac_1; - } - else - { - indent = screen->GetWidth() - 40 * CleanXfac_1; - } - } - else - { - indent = (indent - 160) * CleanXfac_1 + screen->GetWidth() / 2; - } - } - - if (item->type != screenres) - { - FString somestring; - const char *label; - if (item->type != joymore) - { - label = item->label; - } - else - { - if (Joysticks.Size() == 0) - { - label = "No devices connected"; - } - else - { - somestring = Joysticks[item->a.joyselection]->GetName(); - label = somestring; - } - } - width = SmallFont->StringWidth(label) * CleanXfac_1; - switch (item->type) - { - case more: - case safemore: - x = indent - width; - color = MoreColor; - break; - - case joymore: - x = 20 * CleanXfac_1; - color = MoreColor; - break; - - case numberedmore: - case rsafemore: - case rightmore: - x = indent + cursorspace; - color = item->type != rightmore ? CR_GREEN : MoreColor; - break; - - case redtext: - x = screen->GetWidth() / 2 - width / 2; - color = LabelColor; - break; - - case whitetext: - x = screen->GetWidth() / 2 - width / 2; - color = CR_GOLD;//ValueColor; - break; - - case listelement: - x = indent + cursorspace; - color = LabelColor; - break; - - case colorpicker: - x = indent + cursorspace; - color = MoreColor; - break; - - case discrete: - if (item->d.graycheck != NULL && !(**item->d.graycheck)) - { - overlay = MAKEARGB(128,0,0,0); - } - // Intentional fall-through - - default: - x = indent - width; - color = (item->type == control && menuactive == MENU_WaitKey && i == CurrentItem) - ? CR_YELLOW : LabelColor; - break; - } - screen->DrawText (SmallFont, color, x, y, label, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); - - switch (item->type) - { - case numberedmore: - if (item->b.position != 0) - { - char tbuf[16]; - - mysnprintf (tbuf, countof(tbuf), "%d.", item->b.position); - x = indent - SmallFont->StringWidth (tbuf) * CleanXfac_1; - screen->DrawText (SmallFont, CR_GREY, x, y, tbuf, DTA_CleanNoMove_1, true, TAG_DONE); - } - break; - - case bitmask: - { - int v, vals; - - value = item->a.cvar->GetGenericRep (CVAR_Int); - value.Float = float(value.Int & int(item->c.max)); - vals = (int)item->b.numvalues; - - v = M_FindCurVal (value.Float, item->e.values, vals); - - if (v == vals) - { - screen->DrawText (SmallFont, ValueColor, indent + cursorspace, y, "Unknown", - DTA_CleanNoMove_1, true, TAG_DONE); - } - else - { - screen->DrawText (SmallFont, item->type == cdiscrete ? v : ValueColor, - indent + cursorspace, y, item->e.values[v].name, - DTA_CleanNoMove_1, true, TAG_DONE); - } - - } - break; - - case discretes: - case discrete: - case cdiscrete: - case inverter: - case joy_map: - { - int v, vals; - - overlay = 0; - if (item->type == joy_map) - { - value.Float = (float)SELECTED_JOYSTICK->GetAxisMap(item->a.joyselection); - } - else - { - value = item->a.cvar->GetGenericRep (CVAR_Float); - } - if (item->type == inverter) - { - value.Float = (value.Float < 0.f); - vals = 2; - } - else - { - vals = (int)item->b.numvalues; - } - if (item->type != discretes) - { - v = M_FindCurVal (value.Float, item->e.values, vals); - } - else - { - v = M_FindCurVal (value.Float, item->e.valuestrings, vals); - } - if (item->type == discrete) - { - if (item->d.graycheck != NULL && !(**item->d.graycheck)) - { - overlay = MAKEARGB(96,48,0,0); - } - } - - if (v == vals) - { - screen->DrawText (SmallFont, ValueColor, indent + cursorspace, y, "Unknown", - DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); - } - else - { - screen->DrawText (SmallFont, item->type == cdiscrete ? v : ValueColor, - indent + cursorspace, y, - item->type != discretes ? item->e.values[v].name : item->e.valuestrings[v].name.GetChars(), - DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); - } - - } - break; - - case ediscrete: - { - const char *v; - - value = item->a.cvar->GetGenericRep (CVAR_String); - v = M_FindCurVal(value.String, item->e.enumvalues, (int)item->b.numvalues); - screen->DrawText(SmallFont, ValueColor, indent + cursorspace, y, v, DTA_CleanNoMove_1, true, TAG_DONE); - } - break; - - case nochoice: - screen->DrawText (SmallFont, CR_GOLD, indent + cursorspace, y, - (item->e.values[(int)item->b.min]).name, DTA_CleanNoMove_1, true, TAG_DONE); - break; - - case joy_sens: - value.Float = SELECTED_JOYSTICK->GetSensitivity(); - M_DrawSlider (indent + cursorspace, y + labelofs, item->b.min, item->c.max, value.Float, 1); - break; - - case joy_slider: - if (item->e.joyslidernum == 0) - { - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - } - else - { - assert(item->e.joyslidernum == 1); - value.Float = SELECTED_JOYSTICK->GetAxisDeadZone(item->a.joyselection); - } - M_DrawSlider (indent + cursorspace, y + labelofs, item->b.min, item->c.max, fabs(value.Float), 3); - break; - - case joy_inverter: - assert(item->e.joyslidernum == 0); - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - screen->DrawText(SmallFont, ValueColor, indent + cursorspace, y, - (value.Float < 0) ? "Yes" : "No", - DTA_CleanNoMove_1, true, TAG_DONE); - break; - - case slider: - value = item->a.cvar->GetGenericRep (CVAR_Float); - M_DrawSlider (indent + cursorspace, y + labelofs, item->b.min, item->c.max, value.Float, 1); - break; - - case absslider: - value = item->a.cvar->GetGenericRep (CVAR_Float); - M_DrawSlider (indent + cursorspace, y + labelofs, item->b.min, item->c.max, fabs(value.Float), 1); - break; - - case intslider: - M_DrawSlider (indent + cursorspace, y + labelofs, item->b.min, item->c.max, item->a.fval, 0); - break; - - case control: - { - char description[64]; - - C_NameKeys (description, item->b.key1, item->c.key2); - if (description[0]) - { - M_DrawConText(CR_WHITE, indent + cursorspace, y-1+labelofs, description); - } - else - { - screen->DrawText(SmallFont, CR_BLACK, indent + cursorspace, y + labelofs, "---", - DTA_CleanNoMove_1, true, TAG_DONE); - } - } - break; - - case colorpicker: - { - int box_x, box_y; - box_x = indent - 35 * CleanXfac_1; - box_y = (gameinfo.gametype & GAME_Raven) ? y - CleanYfac_1 : y; - screen->Clear (box_x, box_y, box_x + 32*CleanXfac_1, box_y + fontheight-CleanYfac_1, - item->a.colorcvar->GetIndex(), 0); - } - break; - - case palettegrid: - { - int box_x, box_y; - int x1, p; - const int w = fontheight; - const int h = fontheight; - - box_y = y - 2 * CleanYfac_1; - p = 0; - box_x = indent - 32 * CleanXfac_1; - for (x1 = 0, p = int(item->b.min * 16); x1 < 16; ++p, ++x1) - { - screen->Clear (box_x, box_y, box_x + w, box_y + h, p, 0); - if (p == CurrColorIndex || (i == CurrentItem && x1 == SelColorIndex)) - { - int r, g, b; - DWORD col; - double blinky; - if (i == CurrentItem && x1 == SelColorIndex) - { - r = 255, g = 128, b = 0; - } - else - { - r = 200, g = 200, b = 255; - } - // Make sure the cursors stand out against similar colors - // by pulsing them. - blinky = fabs(sin(I_MSTime()/1000.0)) * 0.5 + 0.5; - col = MAKEARGB(255,int(r*blinky),int(g*blinky),int(b*blinky)); - - screen->Clear (box_x, box_y, box_x + w, box_y + 1, -1, col); - screen->Clear (box_x, box_y + h-1, box_x + w, box_y + h, -1, col); - screen->Clear (box_x, box_y, box_x + 1, box_y + h, -1, col); - screen->Clear (box_x + w - 1, box_y, box_x + w, box_y + h, -1, col); - } - box_x += w; - } - } - break; - - case bitflag: - { - value_t *value; - const char *str; - - if (item->b.min) - value = NoYes; - else - value = YesNo; - - if (item->a.cvar) - { - if ((*(item->a.intcvar)) & item->e.flagmask) - str = value[1].name; - else - str = value[0].name; - } - else - { - str = "???"; - } - - screen->DrawText (SmallFont, ValueColor, - indent + cursorspace, y, str, DTA_CleanNoMove_1, true, TAG_DONE); - } - break; - - default: - break; - } - - if (item->type != palettegrid && // Palette grids draw their own cursor - i == CurrentItem && - (skullAnimCounter < 6 || menuactive == MENU_WaitKey)) - { - M_DrawConText(CR_RED, indent + 3 * CleanXfac_1, y-CleanYfac_1+labelofs, "\xd"); - } - } - else - { - char *str = NULL; - int colwidth = screen->GetWidth() / 3; - - for (x = 0; x < 3; x++) - { - switch (x) - { - case 0: str = item->b.res1; break; - case 1: str = item->c.res2; break; - case 2: str = item->d.res3; break; - } - if (str) - { - if (x == item->e.highlight) - color = CR_GOLD; //ValueColor; - else - color = CR_BRICK; //LabelColor; - - screen->DrawText (SmallFont, color, colwidth * x + 20 * CleanXfac_1, y, str, DTA_CleanNoMove_1, true, TAG_DONE); - } - } - - if (i == CurrentItem && ((item->a.selmode != -1 && (skullAnimCounter < 6 || menuactive == MENU_WaitKey)) || testingmode)) - { - M_DrawConText(CR_RED, item->a.selmode * colwidth + 8 * CleanXfac_1, y - CleanYfac_1 + labelofs, "\xd"); - } - } - } - - CanScrollUp = (CurrentMenu->scrollpos > 0); - CanScrollDown = (i < CurrentMenu->numitems); - VisBottom = i - 1; - - if (CanScrollUp) - { - M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, ytop + labelofs, "\x1a"); - } - if (CanScrollDown) - { - M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, y - 8*CleanYfac_1 + labelofs, "\x1b"); - } - - if (flagsvar) - { - static const FIntCVar *const vars[3] = { &dmflags, &dmflags2, &compatflags }; - char flagsblah[256]; - char *fillptr = flagsblah; - bool printed = false; - - for (int i = 0; i < 3; ++i) - { - if (flagsvar & (1 << i)) - { - if (printed) - { - fillptr += mysnprintf (fillptr, countof(flagsblah) - (fillptr - flagsblah), " "); - } - printed = true; - fillptr += mysnprintf (fillptr, countof(flagsblah) - (fillptr - flagsblah), "%s = %d", vars[i]->GetName (), **vars[i]); - } - } - screen->DrawText (SmallFont, ValueColor, - (screen->GetWidth() - SmallFont->StringWidth (flagsblah) * CleanXfac_1) / 2, 0, flagsblah, - DTA_CleanNoMove_1, true, TAG_DONE); - } -} - -void M_OptResponder(event_t *ev) -{ - menuitem_t *item = CurrentMenu->items + CurrentItem; - - if (menuactive == MENU_WaitKey && ev->type == EV_KeyDown) - { - if (ev->data1 != KEY_ESCAPE) - { - C_ChangeBinding(item->e.command, ev->data1); - M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems); - } - menuactive = MENU_On; - CurrentMenu->items[0].label = OldMessage; - CurrentMenu->items[0].type = OldType; - } - else if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown) - { - if (CurrentMenu == &ModesMenu && (ev->data1 == 't' || ev->data1 == 'T')) - { // Test selected resolution - if (!(item->type == screenres && - GetSelectedSize (CurrentItem, &NewWidth, &NewHeight))) - { - NewWidth = SCREENWIDTH; - NewHeight = SCREENHEIGHT; - } - OldWidth = SCREENWIDTH; - OldHeight = SCREENHEIGHT; - OldBits = DisplayBits; - NewBits = BitTranslate[DummyDepthCvar]; - setmodeneeded = true; - testingmode = I_GetTime(false) + 5 * TICRATE; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - SetModesMenu (NewWidth, NewHeight, NewBits); - } - else if (ev->data1 >= '0' && ev->data1 <= '9') - { // Activate an item of type numberedmore - int i; - int num = ev->data1 == '0' ? 10 : ev->data1 - '0'; - - for (i = 0; i < CurrentMenu->numitems; ++i) - { - menuitem_t *item = CurrentMenu->items + i; - - if (item->type == numberedmore && item->b.position == num) - { - CurrentItem = i; - M_OptButtonHandler(MKEY_Enter, false); - break; - } - } - } - } -} - -void M_OptButtonHandler(EMenuKey key, bool repeat) -{ - menuitem_t *item; - UCVarValue value; - - item = CurrentMenu->items + CurrentItem; - - if (item->type == bitflag && - (key == MKEY_Left || key == MKEY_Right || key == MKEY_Enter) - && !demoplayback) - { - *(item->a.intcvar) = (*(item->a.intcvar)) ^ item->e.flagmask; - return; - } - - // The controls that manipulate joystick interfaces can only be changed from the - // keyboard, because I can't think of a good way to avoid problems otherwise. - if (item->type == discrete && item->c.discretecenter == 2 && (key == MKEY_Left || key == MKEY_Right)) - { - if (repeat) - { - return; - } - for (int i = 0; i < FButtonStatus::MAX_KEYS; ++i) - { - if (MenuButtons[key].Keys[i] >= KEY_FIRSTJOYBUTTON) - { - return; - } - } - } - - switch (key) - { - default: - break; // Keep GCC quiet - - case MKEY_Down: - if (CurrentMenu->numitems > 1) - { - int modecol; - - if (item->type == screenres) - { - modecol = item->a.selmode; - item->a.selmode = -1; - } - else - { - modecol = 0; - } - - do - { - CurrentItem++; - if (CanScrollDown && CurrentItem == VisBottom) - { - CurrentMenu->scrollpos++; - VisBottom++; - } - if (CurrentItem == CurrentMenu->numitems) - { - CurrentMenu->scrollpos = 0; - CurrentItem = 0; - } - } while (CurrentMenu->items[CurrentItem].type == redtext || - CurrentMenu->items[CurrentItem].type == whitetext || - (CurrentMenu->items[CurrentItem].type == screenres && - !CurrentMenu->items[CurrentItem].b.res1) || - (CurrentMenu->items[CurrentItem].type == numberedmore && - !CurrentMenu->items[CurrentItem].b.position)); - - if (CurrentMenu->items[CurrentItem].type == screenres) - { - item = &CurrentMenu->items[CurrentItem]; - while ((modecol == 2 && !item->d.res3) || (modecol == 1 && !item->c.res2)) - { - modecol--; - } - CurrentMenu->items[CurrentItem].a.selmode = modecol; - } - - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } - break; - - case MKEY_Up: - if (CurrentMenu->numitems > 1) - { - int modecol; - - if (item->type == screenres) - { - modecol = item->a.selmode; - item->a.selmode = -1; - } - else - { - modecol = 0; - } - - do - { - CurrentItem--; - if (CurrentMenu->scrollpos > 0 && - CurrentItem == CurrentMenu->scrolltop + CurrentMenu->scrollpos) - { - CurrentMenu->scrollpos--; - } - if (CurrentItem < 0) - { - int ytop, maxitems, rowheight; - - // Figure out how many lines of text fit on the menu - if (CurrentMenu->y != 0) - { - ytop = CurrentMenu->y; - } - else if (BigFont && CurrentMenu->texttitle) - { - ytop = 15 + BigFont->GetHeight (); - } - else - { - ytop = 15; - } - if (!(gameinfo.gametype & GAME_DoomChex)) - { - ytop -= 2; - rowheight = 9; - } - else - { - rowheight = 8; - } - ytop *= CleanYfac_1; - rowheight *= CleanYfac_1; - maxitems = (screen->GetHeight() - rowheight - ytop) / rowheight + 1; - - CurrentMenu->scrollpos = MAX (0,CurrentMenu->numitems - maxitems + CurrentMenu->scrolltop); - CurrentItem = CurrentMenu->numitems - 1; - } - } while (CurrentMenu->items[CurrentItem].type == redtext || - CurrentMenu->items[CurrentItem].type == whitetext || - (CurrentMenu->items[CurrentItem].type == screenres && - !CurrentMenu->items[CurrentItem].b.res1) || - (CurrentMenu->items[CurrentItem].type == numberedmore && - !CurrentMenu->items[CurrentItem].b.position)); - - if (CurrentMenu->items[CurrentItem].type == screenres) - CurrentMenu->items[CurrentItem].a.selmode = modecol; - - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } - break; - - case MKEY_PageUp: - if (CurrentMenu->scrollpos > 0) - { - CurrentMenu->scrollpos -= VisBottom - CurrentMenu->scrollpos - CurrentMenu->scrolltop; - if (CurrentMenu->scrollpos < 0) - { - CurrentMenu->scrollpos = 0; - } - CurrentItem = CurrentMenu->scrolltop + CurrentMenu->scrollpos + 1; - while (CurrentMenu->items[CurrentItem].type == redtext || - CurrentMenu->items[CurrentItem].type == whitetext || - (CurrentMenu->items[CurrentItem].type == screenres && - !CurrentMenu->items[CurrentItem].b.res1) || - (CurrentMenu->items[CurrentItem].type == numberedmore && - !CurrentMenu->items[CurrentItem].b.position)) - { - ++CurrentItem; - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } - break; - - case MKEY_PageDown: - if (CanScrollDown) - { - int pagesize = VisBottom - CurrentMenu->scrollpos - CurrentMenu->scrolltop; - CurrentMenu->scrollpos += pagesize; - if (CurrentMenu->scrollpos + CurrentMenu->scrolltop + pagesize > CurrentMenu->numitems) - { - CurrentMenu->scrollpos = CurrentMenu->numitems - CurrentMenu->scrolltop - pagesize; - } - CurrentItem = CurrentMenu->scrolltop + CurrentMenu->scrollpos + 1; - while (CurrentMenu->items[CurrentItem].type == redtext || - CurrentMenu->items[CurrentItem].type == whitetext || - (CurrentMenu->items[CurrentItem].type == screenres && - !CurrentMenu->items[CurrentItem].b.res1) || - (CurrentMenu->items[CurrentItem].type == numberedmore && - !CurrentMenu->items[CurrentItem].b.position)) - { - ++CurrentItem; - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - } - break; - - case MKEY_Left: - switch (item->type) - { - case slider: - case absslider: - case intslider: - { - UCVarValue newval; - bool reversed; - - if (item->type == intslider) - value.Float = item->a.fval; - else - value = item->a.cvar->GetGenericRep (CVAR_Float); - reversed = item->type == absslider && value.Float < 0.f; - newval.Float = (reversed ? -value.Float : value.Float) - item->d.step; - - if (newval.Float < item->b.min) - newval.Float = item->b.min; - else if (newval.Float > item->c.max) - newval.Float = item->c.max; - - if (reversed) - { - newval.Float = -newval.Float; - } - - if (item->type == intslider) - item->a.fval = newval.Float; - else if (item->e.cfunc) - item->e.cfunc (item->a.cvar, newval.Float); - else - item->a.cvar->SetGenericRep (newval, CVAR_Float); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - break; - - case joy_sens: - value.Float = SELECTED_JOYSTICK->GetSensitivity() - item->d.step; - 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); - break; - - case joy_slider: - if (item->e.joyslidernum == 0) - { - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - } - else - { - assert(item->e.joyslidernum == 1); - value.Float = SELECTED_JOYSTICK->GetAxisDeadZone(item->a.joyselection); - } - if (value.Float >= 0) - { - value.Float -= item->d.step; - if (value.Float < item->b.min) - value.Float = item->b.min; - } - else - { - value.Float += item->d.step; - if (value.Float < -item->c.max) - value.Float = -item->c.max; - } - if (item->e.joyslidernum == 0) - { - SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, value.Float); - } - else - { - SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - break; - - case palettegrid: - SelColorIndex = (SelColorIndex - 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - break; - - case discretes: - case discrete: - case cdiscrete: - case joy_map: - { - int cur; - int numvals; - - numvals = (int)item->b.min; - if (item->type == joy_map) - { - value.Float = (float)SELECTED_JOYSTICK->GetAxisMap(item->a.joyselection); - } - else - { - value = item->a.cvar->GetGenericRep (CVAR_Float); - } - if (item->type != discretes) - { - cur = M_FindCurVal (value.Float, item->e.values, numvals); - } - else - { - cur = M_FindCurVal (value.Float, item->e.valuestrings, numvals); - } - if (--cur < 0) - cur = numvals - 1; - - value.Float = item->type != discretes ? item->e.values[cur].value : item->e.valuestrings[cur].value; - if (item->type == joy_map) - { - SELECTED_JOYSTICK->SetAxisMap(item->a.joyselection, (EJoyAxis)(int)value.Float); - } - else - { - item->a.cvar->SetGenericRep (value, CVAR_Float); - } - - // Hack hack. Rebuild list of resolutions - if (item->e.values == Depths) - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, 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); - break; - - case bitmask: - { - int cur; - int numvals; - int bmask = int(item->c.max); - - numvals = (int)item->b.min; - value = item->a.cvar->GetGenericRep (CVAR_Int); - - cur = M_FindCurVal (float(value.Int & bmask), item->e.values, numvals); - if (--cur < 0) - cur = numvals - 1; - - 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); - 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); - 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); - break; - - case screenres: - { - int col; - - col = item->a.selmode - 1; - if (col < 0) - { - if (CurrentItem > 0) - { - if (CurrentMenu->items[CurrentItem - 1].type == screenres) - { - item->a.selmode = -1; - CurrentMenu->items[--CurrentItem].a.selmode = 2; - } - } - } - else - { - item->a.selmode = col; - } - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - break; - - default: - break; - } - break; - - case MKEY_Right: - switch (item->type) - { - case slider: - case absslider: - case intslider: - { - UCVarValue newval; - bool reversed; - - if (item->type == intslider) - value.Float = item->a.fval; - else - value = item->a.cvar->GetGenericRep (CVAR_Float); - reversed = item->type == absslider && value.Float < 0.f; - newval.Float = (reversed ? -value.Float : value.Float) + item->d.step; - - if (newval.Float > item->c.max) - newval.Float = item->c.max; - else if (newval.Float < item->b.min) - newval.Float = item->b.min; - - if (reversed) - { - newval.Float = -newval.Float; - } - - if (item->type == intslider) - item->a.fval = newval.Float; - else if (item->e.cfunc) - item->e.cfunc (item->a.cvar, newval.Float); - else - item->a.cvar->SetGenericRep (newval, CVAR_Float); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - break; - - case joy_sens: - value.Float = SELECTED_JOYSTICK->GetSensitivity() + item->d.step; - 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); - break; - - case joy_slider: - if (item->e.joyslidernum == 0) - { - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - } - else - { - assert(item->e.joyslidernum == 1); - value.Float = SELECTED_JOYSTICK->GetAxisDeadZone(item->a.joyselection); - } - if (value.Float >= 0) - { - value.Float += item->d.step; - if (value.Float > item->c.max) - value.Float = item->c.max; - } - else - { - value.Float -= item->d.step; - if (value.Float > item->b.min) - value.Float = -item->b.min; - } - if (item->e.joyslidernum == 0) - { - SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, value.Float); - } - else - { - SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - break; - - case palettegrid: - SelColorIndex = (SelColorIndex + 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - break; - - case discretes: - case discrete: - case cdiscrete: - case joy_map: - { - int cur; - int numvals; - - numvals = (int)item->b.min; - if (item->type == joy_map) - { - value.Float = (float)SELECTED_JOYSTICK->GetAxisMap(item->a.joyselection); - } - else - { - value = item->a.cvar->GetGenericRep (CVAR_Float); - } - if (item->type != discretes) - { - cur = M_FindCurVal (value.Float, item->e.values, numvals); - } - else - { - cur = M_FindCurVal (value.Float, item->e.valuestrings, numvals); - } - if (++cur >= numvals) - cur = 0; - - value.Float = item->type != discretes ? item->e.values[cur].value : item->e.valuestrings[cur].value; - if (item->type == joy_map) - { - SELECTED_JOYSTICK->SetAxisMap(item->a.joyselection, (EJoyAxis)(int)value.Float); - } - else - { - item->a.cvar->SetGenericRep (value, CVAR_Float); - } - - // Hack hack. Rebuild list of resolutions - if (item->e.values == Depths) - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, 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); - break; - - case bitmask: - { - int cur; - int numvals; - int bmask = int(item->c.max); - - numvals = (int)item->b.min; - value = item->a.cvar->GetGenericRep (CVAR_Int); - - cur = M_FindCurVal (float(value.Int & bmask), item->e.values, numvals); - if (++cur >= numvals) - cur = 0; - - 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); - 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); - 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); - break; - - case screenres: - { - int col; - - col = item->a.selmode + 1; - if ((col > 2) || (col == 2 && !item->d.res3) || (col == 1 && !item->c.res2)) - { - if (CurrentMenu->numitems - 1 > CurrentItem) - { - if (CurrentMenu->items[CurrentItem + 1].type == screenres) - { - if (CurrentMenu->items[CurrentItem + 1].b.res1) - { - item->a.selmode = -1; - CurrentMenu->items[++CurrentItem].a.selmode = 0; - } - } - } - } - else - { - item->a.selmode = col; - } - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); - break; - - default: - break; - } - break; - - case MKEY_Clear: - if (item->type == control) - { - C_UnbindACommand (item->e.command); - item->b.key1 = item->c.key2 = 0; - } - break; - - case MKEY_Enter: - if (CurrentMenu == &ModesMenu && item->type == screenres) - { - if (!GetSelectedSize (CurrentItem, &NewWidth, &NewHeight)) - { - NewWidth = SCREENWIDTH; - NewHeight = SCREENHEIGHT; - } - else - { - testingmode = 1; - setmodeneeded = true; - NewBits = BitTranslate[DummyDepthCvar]; - } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - SetModesMenu (NewWidth, NewHeight, NewBits); - } - else if ((item->type == more || - item->type == numberedmore || - item->type == rightmore || - item->type == rsafemore || - item->type == joymore || - item->type == safemore) - && item->e.mfunc) - { - CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - if (item->type == safemore || item->type == rsafemore) - { - ActivateConfirm (item->label, item->e.mfunc); - } - else - { - item->e.mfunc(); - } - } - else if (item->type == discrete || item->type == cdiscrete || item->type == discretes) - { - int cur; - int numvals; - - numvals = (int)item->b.min; - value = item->a.cvar->GetGenericRep (CVAR_Float); - if (item->type != discretes) - { - cur = M_FindCurVal (value.Float, item->e.values, numvals); - } - else - { - cur = M_FindCurVal (value.Float, item->e.valuestrings, numvals); - } - if (++cur >= numvals) - cur = 0; - - value.Float = item->type != discretes ? item->e.values[cur].value : item->e.valuestrings[cur].value; - item->a.cvar->SetGenericRep (value, CVAR_Float); - - // Hack hack. Rebuild list of resolutions - if (item->e.values == Depths) - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); - } - else if (item->type == control) - { - menuactive = MENU_WaitKey; - OldMessage = CurrentMenu->items[0].label; - OldType = CurrentMenu->items[0].type; - CurrentMenu->items[0].label = "Press new key for control, ESC to cancel"; - CurrentMenu->items[0].type = redtext; - } - else if (item->type == listelement) - { - CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - item->e.lfunc (CurrentItem); - } - else if (item->type == 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); - } - 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); - } - else if (item->type == screenres) - { - } - else if (item->type == colorpicker) - { - CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); - StartColorPickerMenu (item->label, item->a.colorcvar); - } - else if (item->type == palettegrid) - { - UpdateSelColor (SelColorIndex + int(item->b.min * 16)); - } - break; - - case MKEY_Back: - CurrentMenu->lastOn = CurrentItem; - if (CurrentMenu->EscapeHandler != NULL) - { - CurrentMenu->EscapeHandler (); - } - M_PopMenuStack (); - break; - } -} - -static void GoToConsole (void) -{ - M_ClearMenus (); - C_ToggleConsole (); -} - -static void UpdateStuff (void) -{ - M_SizeDisplay (0); -} - -void Reset2Defaults (void) -{ - C_SetDefaultBindings (); - C_SetCVarsToDefaults (); - UpdateStuff(); -} - -void Reset2Saved (void) -{ - GameConfig->DoGlobalSetup (); - GameConfig->DoGameSetup (GameNames[gameinfo.gametype]); - UpdateStuff(); -} - -static void StartMessagesMenu (void) -{ - M_SwitchMenu (&MessagesMenu); -} - -static void StartAutomapMenu (void) -{ - M_SwitchMenu (&AutomapMenu); -} - -static void StartScoreboardMenu (void) -{ - M_SwitchMenu (&ScoreboardMenu); -} - -CCMD (menu_messages) -{ - M_StartControlPanel (true); - OptionsActive = true; - StartMessagesMenu (); -} - -CCMD (menu_automap) -{ - M_StartControlPanel (true); - OptionsActive = true; - StartAutomapMenu (); -} - -CCMD (menu_scoreboard) -{ - M_StartControlPanel (true); - OptionsActive = true; - StartScoreboardMenu (); -} - -static void StartMapColorsMenu (void) -{ - M_SwitchMenu (&MapColorsMenu); -} - -CCMD (menu_mapcolors) -{ - M_StartControlPanel (true); - OptionsActive = true; - StartMapColorsMenu (); -} - -static void DefaultCustomColors () -{ - // Find the color cvars by scanning the MapColors menu. - for (int i = 0; i < MapColorsMenu.numitems; ++i) - { - if (MapColorsItems[i].type == colorpicker) - { - MapColorsItems[i].a.colorcvar->ResetToDefault (); - } - } -} - -static bool ColorPickerDrawer () -{ - DWORD newColor = MAKEARGB(255, - int(ColorPickerItems[2].a.fval), - int(ColorPickerItems[3].a.fval), - int(ColorPickerItems[4].a.fval)); - DWORD oldColor = DWORD(*ColorPickerItems[0].a.colorcvar) | 0xFF000000; - - int x = screen->GetWidth()*2/3; - int y = (15 + BigFont->GetHeight() + SmallFont->GetHeight()*5 - 10) * CleanYfac_1; - - screen->Clear (x, y, x + 48*CleanXfac_1, y + 48*CleanYfac_1, -1, oldColor); - screen->Clear (x + 48*CleanXfac_1, y, x + 48*2*CleanXfac_1, y + 48*CleanYfac_1, -1, newColor); - - y += 49*CleanYfac_1; - screen->DrawText (SmallFont, CR_GRAY, x+(24-SmallFont->StringWidth("Old")/2)*CleanXfac_1, y, - "Old", DTA_CleanNoMove_1, true, TAG_DONE); - screen->DrawText (SmallFont, CR_WHITE, x+(48+24-SmallFont->StringWidth("New")/2)*CleanXfac_1, y, - "New", DTA_CleanNoMove_1, true, TAG_DONE); - return false; -} - -static void SetColorPickerSliders () -{ - FColorCVar *cvar = ColorPickerItems[0].a.colorcvar; - ColorPickerItems[2].a.fval = float(RPART(DWORD(*cvar))); - ColorPickerItems[3].a.fval = float(GPART(DWORD(*cvar))); - ColorPickerItems[4].a.fval = float(BPART(DWORD(*cvar))); - CurrColorIndex = cvar->GetIndex(); -} - -static void UpdateSelColor (int index) -{ - ColorPickerItems[2].a.fval = GPalette.BaseColors[index].r; - ColorPickerItems[3].a.fval = GPalette.BaseColors[index].g; - ColorPickerItems[4].a.fval = GPalette.BaseColors[index].b; -} - -static void ColorPickerReset () -{ - SetColorPickerSliders (); -} - -static void ActivateColorChoice () -{ - UCVarValue val; - val.Int = MAKERGB - (int(ColorPickerItems[2].a.fval), - int(ColorPickerItems[3].a.fval), - int(ColorPickerItems[4].a.fval)); - ColorPickerItems[0].a.colorcvar->SetGenericRep (val, CVAR_Int); -} - -static void StartColorPickerMenu (const char *colorname, FColorCVar *cvar) -{ - ColorPickerMenu.PreDraw = ColorPickerDrawer; - ColorPickerMenu.EscapeHandler = ActivateColorChoice; - ColorPickerItems[0].label = colorname; - ColorPickerItems[0].a.colorcvar = cvar; - SetColorPickerSliders (); - M_SwitchMenu (&ColorPickerMenu); -} - -static void CustomizeControls (void) -{ - M_BuildKeyList (ControlsMenu.items, ControlsMenu.numitems); - M_SwitchMenu (&ControlsMenu); -} - -CCMD (menu_keys) -{ - M_StartControlPanel (true); - OptionsActive = true; - CustomizeControls (); -} - -EXTERN_CVAR (Int, dmflags) - -static void GameplayOptions (void) -{ - M_SwitchMenu (&DMFlagsMenu); - flagsvar = SHOW_DMFlags | SHOW_DMFlags2; -} - -CCMD (menu_gameplay) -{ - M_StartControlPanel (true); - OptionsActive = true; - GameplayOptions (); -} - -static void CompatibilityOptions (void) -{ - M_SwitchMenu (&CompatibilityMenu); - flagsvar = SHOW_CompatFlags; -} - -CCMD (menu_compatibility) -{ - M_StartControlPanel (true); - OptionsActive = true; - CompatibilityOptions (); -} - -static void MouseOptions () -{ - M_SwitchMenu (&MouseMenu); -} - -CCMD (menu_mouse) -{ - M_StartControlPanel (true); - OptionsActive = true; - MouseOptions (); -} - -static bool DrawJoystickConfigMenuHeader() -{ - FString joyname = SELECTED_JOYSTICK->GetName(); - screen->DrawText(BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - (screen->GetWidth() - BigFont->StringWidth(CurrentMenu->texttitle) * CleanXfac_1) / 2, - 5 * CleanYfac_1, - CurrentMenu->texttitle, DTA_CleanNoMove_1, true, TAG_DONE); - screen->DrawText(SmallFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - (screen->GetWidth() - SmallFont->StringWidth(joyname) * CleanXfac_1) / 2, (8 + BigFont->GetHeight()) * CleanYfac_1, - joyname, DTA_CleanNoMove_1, true, TAG_DONE); - return false; -} - -static void UpdateJoystickConfigMenu(IJoystickConfig *joy) -{ - int i; - menuitem_t item = { whitetext }; - - JoystickConfigItems.Clear(); - if (joy == NULL) - { - item.type = redtext; - item.label = "Invalid controller specified for menu"; - JoystickConfigItems.Push(item); - } - else - { - SELECTED_JOYSTICK = joy; - - item.type = joy_sens; - item.label = "Overall sensitivity"; - item.b.min = 0; - item.c.max = 2; - item.d.step = 0.1f; - JoystickConfigItems.Push(item); - - item.type = redtext; - item.label = " "; - JoystickConfigItems.Push(item); - - item.type = whitetext; - if (joy->GetNumAxes() > 0) - { - item.label = "Axis Configuration"; - JoystickConfigItems.Push(item); - - for (i = 0; i < joy->GetNumAxes(); ++i) - { - item.type = redtext; - item.label = " "; - JoystickConfigItems.Push(item); - - item.type = joy_map; - item.label = joy->GetAxisName(i); - item.a.joyselection = i; - item.b.numvalues = countof(JoyAxisMapNames); - item.e.values = JoyAxisMapNames; - JoystickConfigItems.Push(item); - - item.type = joy_slider; - item.label = "Sensitivity"; - item.b.min = 0; - item.c.max = 4; - item.d.step = 0.1f; - item.e.joyslidernum = 0; - JoystickConfigItems.Push(item); - - item.type = joy_inverter; - item.label = "Invert"; - JoystickConfigItems.Push(item); - - item.type = joy_slider; - item.label = "Dead Zone"; - item.b.position = 1; - item.c.max = 0.9f; - item.d.step = 0.05f; - item.e.joyslidernum = 1; - JoystickConfigItems.Push(item); - } - } - else - { - item.label = "No configurable axes"; - JoystickConfigItems.Push(item); - } - } - JoystickConfigMenu.items = &JoystickConfigItems[0]; - JoystickConfigMenu.numitems = JoystickConfigItems.Size(); - JoystickConfigMenu.lastOn = 0; - JoystickConfigMenu.scrollpos = 0; - JoystickConfigMenu.y = 25 + BigFont->GetHeight(); - JoystickConfigMenu.PreDraw = DrawJoystickConfigMenuHeader; - if (screen != NULL) - { - CalcIndent(&JoystickConfigMenu); - } -} - -static void StartJoystickConfigMenu() -{ - UpdateJoystickConfigMenu(Joysticks[JoystickItems[JoystickMenu.lastOn].a.joyselection]); - M_SwitchMenu(&JoystickConfigMenu); -} - -void UpdateJoystickMenu(IJoystickConfig *selected) -{ - int i; - menuitem_t item = { whitetext }; - int itemnum = -1; - - JoystickItems.Clear(); - I_GetJoysticks(Joysticks); - if ((unsigned)itemnum >= Joysticks.Size()) - { - itemnum = Joysticks.Size() - 1; - } - if (selected != NULL) - { - for (i = 0; (unsigned)i < Joysticks.Size(); ++i) - { - if (Joysticks[i] == selected) - { - itemnum = i; - break; - } - } - } - item.type = discrete; - item.label = "Enable controller support"; - item.a.cvar = &use_joystick; - item.b.numvalues = 2; - item.c.discretecenter = 2; - item.e.values = YesNo; - JoystickItems.Push(item); - -#ifdef _WIN32 - item.label = "Enable DirectInput controllers"; - item.a.cvar = &joy_dinput; - JoystickItems.Push(item); - - item.label = "Enable XInput controllers"; - item.a.cvar = &joy_xinput; - JoystickItems.Push(item); - - item.label = "Enable raw PlayStation 2 adapters"; - item.a.cvar = &joy_ps2raw; - JoystickItems.Push(item); -#endif - - item.type = redtext; - item.label = " "; - item.c.discretecenter = 0; - JoystickItems.Push(item); - - if (Joysticks.Size() == 0) - { - item.type = redtext; - item.label = "No controllers detected"; - JoystickItems.Push(item); - if (!use_joystick) - { - item.type = whitetext; - item.label = "Controller support must be"; - JoystickItems.Push(item); - - item.label = "enabled to detect any"; - JoystickItems.Push(item); - } - } - else - { - item.label = "Configure controllers:"; - JoystickItems.Push(item); - - item.type = joymore; - item.e.mfunc = StartJoystickConfigMenu; - for (int i = 0; i < (int)Joysticks.Size(); ++i) - { - item.a.joyselection = i; - if (i == itemnum) - { - JoystickMenu.lastOn = JoystickItems.Size(); - } - JoystickItems.Push(item); - } - } - JoystickMenu.items = &JoystickItems[0]; - JoystickMenu.numitems = JoystickItems.Size(); - if (JoystickMenu.lastOn >= JoystickMenu.numitems) - { - JoystickMenu.lastOn = JoystickMenu.numitems - 1; - } - if (CurrentMenu == &JoystickMenu && CurrentItem >= JoystickMenu.numitems) - { - CurrentItem = JoystickMenu.lastOn; - } - if (screen != NULL) - { - CalcIndent(&JoystickMenu); - } - - // If the joystick config menu is open, close it if the device it's - // open for is gone. - for (i = 0; (unsigned)i < Joysticks.Size(); ++i) - { - if (Joysticks[i] == SELECTED_JOYSTICK) - { - break; - } - } - if (i == (int)Joysticks.Size()) - { - SELECTED_JOYSTICK = NULL; - if (CurrentMenu == &JoystickConfigMenu) - { - M_PopMenuStack(); - } - } -} - -static void JoystickOptions () -{ - UpdateJoystickMenu (NULL); - M_SwitchMenu (&JoystickMenu); -} - -CCMD (menu_joystick) -{ - M_StartControlPanel (true); - OptionsActive = true; - JoystickOptions (); -} - -static void FreeMIDIMenuList() -{ - if (SoundItems[MIDI_DEVICE_ITEM].e.values != NULL) - { - delete[] SoundItems[MIDI_DEVICE_ITEM].e.values; - } -} - -static void SoundOptions () -{ - I_BuildMIDIMenuList(&SoundItems[MIDI_DEVICE_ITEM].e.values, &SoundItems[MIDI_DEVICE_ITEM].b.min); - atterm(FreeMIDIMenuList); - M_SwitchMenu(&SoundMenu); -} - -CCMD (menu_sound) -{ - M_StartControlPanel (true); - OptionsActive = true; - SoundOptions (); -} - -static void AdvSoundOptions () -{ - M_SwitchMenu (&AdvSoundMenu); -} - -CCMD (menu_advsound) -{ - M_StartControlPanel (true); - OptionsActive = true; - AdvSoundOptions (); -} - -static void MakeSoundChanges (void) -{ - static char snd_reset[] = "snd_reset"; - AddCommandString (snd_reset); -} - -static void ModReplayerOptions() -{ - for (size_t i = 2; i < countof(ModReplayerItems); ++i) - { - if (ModReplayerItems[i].type == discrete) - { - ModReplayerItems[i].d.graycheck = &mod_dumb; - } - } - M_SwitchMenu(&ModReplayerMenu); -} - -CCMD (menu_modreplayer) -{ - M_StartControlPanel(true); - OptionsActive = true; - ModReplayerOptions(); -} - -static void VideoOptions (void) -{ - InitCrosshairsList(); - M_SwitchMenu (&VideoMenu); -} - -CCMD (menu_display) -{ - M_StartControlPanel (true); - OptionsActive = true; - InitCrosshairsList(); - M_SwitchMenu (&VideoMenu); -} - -static void BuildModesList (int hiwidth, int hiheight, int hi_bits) -{ - char strtemp[32], **str; - int i, c; - int width, height, showbits; - bool letterbox=false; - int ratiomatch; - - if (menu_screenratios >= 0 && menu_screenratios <= 4 && menu_screenratios != 3) - { - ratiomatch = menu_screenratios; - } - else - { - ratiomatch = -1; - } - showbits = BitTranslate[DummyDepthCvar]; - - if (Video != NULL) - { - Video->StartModeIterator (showbits, screen->IsFullscreen()); - } - - for (i = VM_RESSTART; ModesItems[i].type == screenres; i++) - { - ModesItems[i].e.highlight = -1; - for (c = 0; c < 3; c++) - { - bool haveMode = false; - - switch (c) - { - default: str = &ModesItems[i].b.res1; break; - case 1: str = &ModesItems[i].c.res2; break; - case 2: str = &ModesItems[i].d.res3; break; - } - if (Video != NULL) - { - while ((haveMode = Video->NextMode (&width, &height, &letterbox)) && - (ratiomatch >= 0 && CheckRatio (width, height) != ratiomatch)) - { - } - } - - if (haveMode) - { - if (/* hi_bits == showbits && */ width == hiwidth && height == hiheight) - ModesItems[i].e.highlight = ModesItems[i].a.selmode = c; - - mysnprintf (strtemp, countof(strtemp), "%dx%d%s", width, height, letterbox?TEXTCOLOR_BROWN" LB":""); - ReplaceString (str, strtemp); - } - else - { - if (*str) - { - delete[] *str; - *str = NULL; - } - } - } - } -} - -void M_RefreshModesList () -{ - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); -} - -void M_FreeModesList () -{ - for (int i = VM_RESSTART; ModesItems[i].type == screenres; ++i) - { - for (int c = 0; c < 3; ++c) - { - char **str; - - switch (c) - { - default: str = &ModesItems[i].b.res1; break; - case 1: str = &ModesItems[i].c.res2; break; - case 2: str = &ModesItems[i].d.res3; break; - } - if (str != NULL) - { - delete[] *str; - *str = NULL; - } - } - } -} - -static bool GetSelectedSize (int line, int *width, int *height) -{ - if (ModesItems[line].type != screenres) - { - return false; - } - else - { - char *res, *breakpt; - long x, y; - - switch (ModesItems[line].a.selmode) - { - default: res = ModesItems[line].b.res1; break; - case 1: res = ModesItems[line].c.res2; break; - case 2: res = ModesItems[line].d.res3; break; - } - x = strtol (res, &breakpt, 10); - y = strtol (breakpt+1, NULL, 10); - - *width = x; - *height = y; - return true; - } -} - -static int FindBits (int bits) -{ - int i; - - for (i = 0; i < 22; i++) - { - if (BitTranslate[i] == bits) - return i; - } - - return 0; -} - -static void SetModesMenu (int w, int h, int bits) -{ - DummyDepthCvar = FindBits (bits); - - if (testingmode <= 1) - { - if (ModesItems[VM_ENTERLINE].label != VMEnterText) - free (const_cast(ModesItems[VM_ENTERLINE].label)); - ModesItems[VM_ENTERLINE].label = VMEnterText; - ModesItems[VM_TESTLINE].label = VMTestText; - } - else - { - char strtemp[64]; - - mysnprintf (strtemp, countof(strtemp), "TESTING %dx%dx%d", w, h, bits); - ModesItems[VM_ENTERLINE].label = copystring (strtemp); - ModesItems[VM_TESTLINE].label = "Please wait 5 seconds..."; - } - - BuildModesList (w, h, bits); -} - -void M_RestoreMode () -{ - NewWidth = OldWidth; - NewHeight = OldHeight; - NewBits = OldBits; - setmodeneeded = true; - testingmode = 0; - SetModesMenu (OldWidth, OldHeight, OldBits); -} - -void M_SetDefaultMode () -{ - // Make current resolution the default - vid_defwidth = SCREENWIDTH; - vid_defheight = SCREENHEIGHT; - vid_defbits = DisplayBits; - testingmode = 0; - SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); -} - -static void SetVidMode () -{ - SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - if (ModesMenu.items[ModesMenu.lastOn].type == screenres) - { - if (ModesMenu.items[ModesMenu.lastOn].a.selmode == -1) - { - ModesMenu.items[ModesMenu.lastOn].a.selmode++; - } - } - M_SwitchMenu (&ModesMenu); -} - -CCMD (menu_video) -{ - M_StartControlPanel (true); - OptionsActive = true; - SetVidMode (); -} - -void M_LoadKeys (const char *modname, bool dbl) -{ - char section[64]; - - if (GameNames[gameinfo.gametype] == NULL) - return; - - mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname, - dbl ? ".Double" : "."); - if (GameConfig->SetSection (section)) - { - const char *key, *value; - while (GameConfig->NextInSection (key, value)) - { - C_DoBind (key, value, dbl); - } - } -} - -int M_DoSaveKeys (FConfigFile *config, char *section, int i, bool dbl) -{ - int most = (int)CustomControlsItems.Size(); - - config->SetSection (section, true); - config->ClearCurrentSection (); - for (++i; i < most; ++i) - { - menuitem_t *item = &CustomControlsItems[i]; - if (item->type == control) - { - C_ArchiveBindings (config, dbl, item->e.command); - continue; - } - break; - } - return i; -} - -void M_SaveCustomKeys (FConfigFile *config, char *section, char *subsection, size_t sublen) -{ - if (ControlsMenu.items == ControlsItems) - return; - - // Start after the normal controls - unsigned int i = countof(ControlsItems); - unsigned int most = CustomControlsItems.Size(); - - while (i < most) - { - menuitem_t *item = &CustomControlsItems[i]; - - if (item->type == whitetext) - { - assert (item->e.command != NULL); - mysnprintf (subsection, sublen, "%s.Bindings", item->e.command); - M_DoSaveKeys (config, section, (int)i, false); - mysnprintf (subsection, sublen, "%s.DoubleBindings", item->e.command); - i = M_DoSaveKeys (config, section, (int)i, true); - } - else - { - i++; - } - } -} - -static int AddKeySpot; - -void FreeKeySections() -{ - const unsigned int numStdControls = countof(ControlsItems); - unsigned int i; - - for (i = numStdControls; i < CustomControlsItems.Size(); ++i) - { - menuitem_t *item = &CustomControlsItems[i]; - if (item->type == whitetext || item->type == control) - { - if (item->label != NULL) - { - delete[] item->label; - item->label = NULL; - } - if (item->e.command != NULL) - { - delete[] item->e.command; - item->e.command = NULL; - } - } - } -} - -CCMD (addkeysection) -{ - if (argv.argc() != 3) - { - Printf ("Usage: addkeysection \n"); - return; - } - - const int numStdControls = countof(ControlsItems); - int i; - - if (ControlsMenu.items == ControlsItems) - { // No custom controls have been defined yet. - for (i = 0; i < numStdControls; ++i) - { - CustomControlsItems.Push (ControlsItems[i]); - } - } - - // See if this section already exists - int last = (int)CustomControlsItems.Size(); - for (i = numStdControls; i < last; ++i) - { - menuitem_t *item = &CustomControlsItems[i]; - - if (item->type == whitetext && - stricmp (item->label, argv[1]) == 0) - { // found it - break; - } - } - - if (i == last) - { // Add the new section - // Limit the ini name to 32 chars - if (strlen (argv[2]) > 32) - argv[2][32] = 0; - - menuitem_t tempItem = { redtext, " " }; - - // Add a blank line to the menu - CustomControlsItems.Push (tempItem); - - // Add the section name to the menu - tempItem.type = whitetext; - tempItem.label = copystring (argv[1]); - tempItem.e.command = copystring (argv[2]); // Record ini section name in command field - CustomControlsItems.Push (tempItem); - ControlsMenu.items = &CustomControlsItems[0]; - - // Load bindings for this section from the ini - M_LoadKeys (argv[2], 0); - M_LoadKeys (argv[2], 1); - - AddKeySpot = 0; - } - else - { // Add new keys to the end of this section - do - { - i++; - } while (i < last && CustomControlsItems[i].type == control); - if (i < last) - { - AddKeySpot = i; - } - else - { - AddKeySpot = 0; - } - } -} - -CCMD (addmenukey) -{ - if (argv.argc() != 3) - { - Printf ("Usage: addmenukey \n"); - return; - } - if (ControlsMenu.items == ControlsItems) - { - Printf ("You must use addkeysection first.\n"); - return; - } - - menuitem_t newItem = { control, }; - newItem.label = copystring (argv[1]); - newItem.e.command = copystring (argv[2]); - if (AddKeySpot == 0) - { // Just add to the end of the menu - CustomControlsItems.Push (newItem); - } - else - { // Add somewhere in the middle of the menu - size_t movecount = CustomControlsItems.Size() - AddKeySpot; - CustomControlsItems.Reserve (1); - memmove (&CustomControlsItems[AddKeySpot+1], - &CustomControlsItems[AddKeySpot], - sizeof(menuitem_t)*movecount); - CustomControlsItems[AddKeySpot++] = newItem; - } - ControlsMenu.items = &CustomControlsItems[0]; - ControlsMenu.numitems = (int)CustomControlsItems.Size(); -} - -void M_Deinit () -{ - // Free bitdepth names for the modes menu. - for (size_t i = 0; i < countof(Depths); ++i) - { - if (Depths[i].name != NULL) - { - delete[] Depths[i].name; - Depths[i].name = NULL; - } - } - - // Free resolutions from the modes menu. - M_FreeModesList(); -} - -// Reads any XHAIRS lumps for the names of crosshairs and -// adds them to the display options menu. -void InitCrosshairsList() -{ - int lastlump, lump; - valuestring_t value; - - lastlump = 0; - - Crosshairs.Clear(); - value.value = 0; - value.name = "None"; - Crosshairs.Push(value); - - while ((lump = Wads.FindLump("XHAIRS", &lastlump)) != -1) - { - FScanner sc(lump); - while (sc.GetNumber()) - { - value.value = float(sc.Number); - sc.MustGetString(); - value.name = sc.String; - if (value.value != 0) - { // Check if it already exists. If not, add it. - unsigned int i; - - for (i = 1; i < Crosshairs.Size(); ++i) - { - if (Crosshairs[i].value == value.value) - { - break; - } - } - if (i < Crosshairs.Size()) - { - Crosshairs[i].name = value.name; - } - else - { - Crosshairs.Push(value); - } - } - } - } - VideoItems[CROSSHAIR_INDEX].b.numvalues = float(Crosshairs.Size()); - VideoItems[CROSSHAIR_INDEX].e.valuestrings = &Crosshairs[0]; -} diff --git a/src/menu/colorpickermenu.cpp b/src/menu/colorpickermenu.cpp new file mode 100644 index 0000000000..d803022bf9 --- /dev/null +++ b/src/menu/colorpickermenu.cpp @@ -0,0 +1,357 @@ +/* +** colorpickermenu.cpp +** The color picker menu +** +**--------------------------------------------------------------------------- +** Copyright 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 + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" + +#define NO_IMP +#include "menu/optionmenuitems.h" + +class DColorPickerMenu : public DOptionMenu +{ + DECLARE_CLASS(DColorPickerMenu, DOptionMenu) + + float mRed; + float mGreen; + float mBlue; + + int mGridPosX; + int mGridPosY; + + int mStartItem; + + FColorCVar *mCVar; + +public: + + DColorPickerMenu(DMenu *parent, const char *name, FOptionMenuDescriptor *desc, FColorCVar *cvar) + { + mStartItem = desc->mItems.Size(); + mRed = (float)RPART(DWORD(*cvar)); + mGreen = (float)GPART(DWORD(*cvar)); + mBlue = (float)BPART(DWORD(*cvar)); + mGridPosX = 0; + mGridPosY = 0; + mCVar = cvar; + + // This menu uses some featurs that are hard to implement in an external control lump + // so it creates its own list of menu items. + desc->mItems.Resize(mStartItem+8); + desc->mItems[mStartItem+0] = new FOptionMenuItemStaticText(name, false); + desc->mItems[mStartItem+1] = new FOptionMenuItemStaticText(" ", false); + desc->mItems[mStartItem+2] = new FOptionMenuSliderVar("Red", &mRed, 0, 255, 15, 0); + desc->mItems[mStartItem+3] = new FOptionMenuSliderVar("Green", &mGreen, 0, 255, 15, 0); + desc->mItems[mStartItem+4] = new FOptionMenuSliderVar("Blue", &mBlue, 0, 255, 15, 0); + desc->mItems[mStartItem+5] = new FOptionMenuItemStaticText(" ", false); + desc->mItems[mStartItem+6] = new FOptionMenuItemCommand("Undo changes", "undocolorpic"); + desc->mItems[mStartItem+7] = new FOptionMenuItemStaticText(" ", false); + desc->mSelectedItem = mStartItem + 2; + Init(parent, desc); + desc->mIndent = 0; + desc->CalcIndent(); + } + + void Destroy() + { + if (mStartItem >= 0) + { + for(unsigned i=0;i<8;i++) + { + delete mDesc->mItems[mStartItem+i]; + mDesc->mItems.Resize(mStartItem); + } + UCVarValue val; + val.Int = MAKERGB(int(mRed), int(mGreen), int(mBlue)); + if (mCVar != NULL) mCVar->SetGenericRep (val, CVAR_Int); + mStartItem = -1; + } + } + + void Reset() + { + mRed = (float)RPART(DWORD(*mCVar)); + mGreen = (float)GPART(DWORD(*mCVar)); + mBlue = (float)BPART(DWORD(*mCVar)); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MenuEvent (int mkey, bool fromcontroller) + { + int &mSelectedItem = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Down: + if (mSelectedItem == mStartItem+6) // last valid item + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY = 0; + // let it point to the last static item so that the super class code still has a valid item + mSelectedItem = mStartItem+7; + return true; + } + else if (mSelectedItem == mStartItem+7) + { + if (mGridPosY < 15) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY++; + } + return true; + } + break; + + case MKEY_Up: + if (mSelectedItem == mStartItem+7) + { + if (mGridPosY > 0) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY--; + } + else + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mSelectedItem = mStartItem+6; + } + return true; + } + break; + + case MKEY_Left: + if (mSelectedItem == mStartItem+7) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + if (--mGridPosX < 0) mGridPosX = 15; + return true; + } + break; + + case MKEY_Right: + if (mSelectedItem == mStartItem+7) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + if (++mGridPosX > 15) mGridPosX = 0; + return true; + } + break; + + case MKEY_Enter: + if (mSelectedItem == mStartItem+7) + { + // Choose selected palette entry + int index = mGridPosX + mGridPosY * 16; + mRed = GPalette.BaseColors[index].r; + mGreen = GPalette.BaseColors[index].g; + mBlue = GPalette.BaseColors[index].b; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + return true; + } + break; + } + if (mSelectedItem >= 0 && mSelectedItem < mStartItem+7) + { + if (mDesc->mItems[mDesc->mSelectedItem]->MenuEvent(mkey, fromcontroller)) return true; + } + return Super::MenuEvent(mkey, fromcontroller); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MouseEvent(int type, int mx, int my) + { + int olditem = mDesc->mSelectedItem; + bool res = Super::MouseEvent(type, mx, my); + + if (mDesc->mSelectedItem == -1 || mDesc->mSelectedItem == mStartItem+7) + { + int y = (-mDesc->mPosition + BigFont->GetHeight() + mDesc->mItems.Size() * OptionSettings.mLinespacing) * CleanYfac_1; + int h = (screen->GetHeight() - y) / 16; + int fh = OptionSettings.mLinespacing * CleanYfac_1; + int w = fh; + int yy = y + 2 * CleanYfac_1; + int indent = (screen->GetWidth() / 2); + + if (h > fh) h = fh; + else if (h < 4) return res; // no space to draw it. + + int box_y = y - 2 * CleanYfac_1; + int box_x = indent - 16*w; + + if (mx >= box_x && mx < box_x + 16*w && my >= box_y && my < box_y + 16*h) + { + int cell_x = (mx - box_x) / w; + int cell_y = (my - box_y) / h; + + if (olditem != mStartItem+7 || cell_x != mGridPosX || cell_y != mGridPosY) + { + mGridPosX = cell_x; + mGridPosY = cell_y; + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mSelectedItem = mStartItem+7; + if (type == MOUSE_Release) + { + MenuEvent(MKEY_Enter, true); + if (m_use_mouse == 2) mDesc->mSelectedItem = -1; + } + res = true; + } + } + return res; + } + + //============================================================================= + // + // + // + //============================================================================= + + void Drawer() + { + Super::Drawer(); + + if (mCVar == NULL) return; + int y = (-mDesc->mPosition + BigFont->GetHeight() + mDesc->mItems.Size() * OptionSettings.mLinespacing) * CleanYfac_1; + int h = (screen->GetHeight() - y) / 16; + int fh = OptionSettings.mLinespacing * CleanYfac_1; + int w = fh; + int yy = y; + + if (h > fh) h = fh; + else if (h < 4) return; // no space to draw it. + + int indent = (screen->GetWidth() / 2); + int p = 0; + + for(int i = 0; i < 16; i++, y += h) + { + int box_x, box_y; + int x1; + + box_y = y - 2 * CleanYfac_1; + box_x = indent - 16*w; + for (x1 = 0; x1 < 16; ++x1, p++) + { + screen->Clear (box_x, box_y, box_x + w, box_y + h, p, 0); + if ((mDesc->mSelectedItem == mStartItem+7) && + (/*p == CurrColorIndex ||*/ (i == mGridPosY && x1 == mGridPosX))) + { + int r, g, b; + DWORD col; + double blinky; + if (i == mGridPosY && x1 == mGridPosX) + { + r = 255, g = 128, b = 0; + } + else + { + r = 200, g = 200, b = 255; + } + // Make sure the cursors stand out against similar colors + // by pulsing them. + blinky = fabs(sin(I_MSTime()/1000.0)) * 0.5 + 0.5; + col = MAKEARGB(255,int(r*blinky),int(g*blinky),int(b*blinky)); + + screen->Clear (box_x, box_y, box_x + w, box_y + 1, -1, col); + screen->Clear (box_x, box_y + h-1, box_x + w, box_y + h, -1, col); + screen->Clear (box_x, box_y, box_x + 1, box_y + h, -1, col); + screen->Clear (box_x + w - 1, box_y, box_x + w, box_y + h, -1, col); + } + box_x += w; + } + } + y = yy; + DWORD newColor = MAKEARGB(255, int(mRed), int(mGreen), int(mBlue)); + DWORD oldColor = DWORD(*mCVar) | 0xFF000000; + + int x = screen->GetWidth()*2/3; + + screen->Clear (x, y, x + 48*CleanXfac_1, y + 48*CleanYfac_1, -1, oldColor); + screen->Clear (x + 48*CleanXfac_1, y, x + 48*2*CleanXfac_1, y + 48*CleanYfac_1, -1, newColor); + + y += 49*CleanYfac_1; + screen->DrawText (SmallFont, CR_GRAY, x+(24-SmallFont->StringWidth("Old")/2)*CleanXfac_1, y, + "Old", DTA_CleanNoMove_1, true, TAG_DONE); + screen->DrawText (SmallFont, CR_WHITE, x+(48+24-SmallFont->StringWidth("New")/2)*CleanXfac_1, y, + "New", DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_ABSTRACT_CLASS(DColorPickerMenu) + +CCMD(undocolorpic) +{ + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DColorPickerMenu))) + { + static_cast(DMenu::CurrentMenu)->Reset(); + } +} + + +DMenu *StartPickerMenu(DMenu *parent, const char *name, FColorCVar *cvar) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Colorpickermenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + return new DColorPickerMenu(parent, name, (FOptionMenuDescriptor*)(*desc), cvar); + } + else + { + return NULL; + } +} + diff --git a/src/menu/joystickmenu.cpp b/src/menu/joystickmenu.cpp new file mode 100644 index 0000000000..a29df9596f --- /dev/null +++ b/src/menu/joystickmenu.cpp @@ -0,0 +1,420 @@ +/* +** joystickmenu.cpp +** The joystick configuration menus +** +**--------------------------------------------------------------------------- +** Copyright 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 + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" + +#define NO_IMP +#include "optionmenuitems.h" + + +static TArray Joysticks; +IJoystickConfig *SELECTED_JOYSTICK; + +FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy); + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoySensitivity : public FOptionMenuSliderBase +{ +public: + FOptionMenuSliderJoySensitivity(const char *label, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + } + + double GetValue() + { + return SELECTED_JOYSTICK->GetSensitivity(); + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetSensitivity(float(val)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoyScale : public FOptionMenuSliderBase +{ + int mAxis; + int mNeg; + +public: + FOptionMenuSliderJoyScale(const char *label, int axis, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mAxis = axis; + mNeg = 1; + } + + double GetValue() + { + double d = SELECTED_JOYSTICK->GetAxisScale(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetAxisScale(mAxis, float(val * mNeg)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoyDeadZone : public FOptionMenuSliderBase +{ + int mAxis; + int mNeg; + +public: + FOptionMenuSliderJoyDeadZone(const char *label, int axis, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mAxis = axis; + mNeg = 1; + } + + double GetValue() + { + double d = SELECTED_JOYSTICK->GetAxisDeadZone(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetAxisDeadZone(mAxis, float(val * mNeg)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemJoyMap : public FOptionMenuItemOptionBase +{ + int mAxis; +public: + + FOptionMenuItemJoyMap(const char *label, int axis, const char *values, int center) + : FOptionMenuItemOptionBase(label, "none", values, NULL, center) + { + mAxis = axis; + } + + int GetSelection() + { + float f = (float)(int)SELECTED_JOYSTICK->GetAxisMap(mAxis); + for(unsigned i=0;imValues.Size(); i++) + { + if (fabs(f - mValues->mValues[i].Value) < FLT_EPSILON) + { + return i; + } + } + return -1; + } + + void SetSelection(int Selection) + { + SELECTED_JOYSTICK->SetAxisMap(mAxis, (EJoyAxis)Selection); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemInverter : public FOptionMenuItemOptionBase +{ + int mAxis; +public: + + FOptionMenuItemInverter(const char *label, int axis, int center) + : FOptionMenuItemOptionBase(label, "none", "YesNo", NULL, center) + { + mAxis = axis; + } + + int GetSelection() + { + float f = SELECTED_JOYSTICK->GetAxisScale(mAxis); + return f > 0? 0:1; + } + + void SetSelection(int Selection) + { + float f = fabs(SELECTED_JOYSTICK->GetAxisScale(mAxis)); + if (Selection) f*=-1; + SELECTED_JOYSTICK->SetAxisScale(mAxis, f); + } +}; + +class DJoystickConfigMenu : public DOptionMenu +{ + DECLARE_CLASS(DJoystickConfigMenu, DOptionMenu) +}; + +IMPLEMENT_CLASS(DJoystickConfigMenu) + +//============================================================================= +// +// Executes a CCMD, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemJoyConfigMenu : public FOptionMenuItemSubmenu +{ + IJoystickConfig *mJoy; +public: + FOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy) + : FOptionMenuItemSubmenu(label, "JoystickConfigMenu") + { + mJoy = joy; + } + + bool Activate() + { + UpdateJoystickConfigMenu(mJoy); + return FOptionMenuItemSubmenu::Activate(); + } +}; + + +/*======================================= + * + * Joystick Menu + * + *=======================================*/ + +FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickConfigMenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; + FOptionMenuItem *it; + for(unsigned i=0;imItems.Size();i++) + { + delete opt->mItems[i]; + opt->mItems.Clear(); + } + opt->mTitle.Format("Configure %s", joy->GetName().GetChars()); + + if (joy == NULL) + { + it = new FOptionMenuItemStaticText("Invalid controller specified for menu", false); + opt->mItems.Push(it); + } + else + { + SELECTED_JOYSTICK = joy; + + it = new FOptionMenuSliderJoySensitivity("Overall sensitivity", 0, 2, 0.1, 3); + opt->mItems.Push(it); + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + if (joy->GetNumAxes() > 0) + { + it = new FOptionMenuItemStaticText("Axis Configuration", true); + opt->mItems.Push(it); + + for (int i = 0; i < joy->GetNumAxes(); ++i) + { + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + it = new FOptionMenuItemJoyMap(joy->GetAxisName(i), i, "JoyAxisMapNames", false); + opt->mItems.Push(it); + it = new FOptionMenuSliderJoyScale("Overall sensitivity", i, 0, 4, 0.1, 3); + opt->mItems.Push(it); + it = new FOptionMenuItemInverter("Invert", i, false); + opt->mItems.Push(it); + it = new FOptionMenuSliderJoyDeadZone("Dead Zone", i, 0, 0.9, 0.05, 3); + opt->mItems.Push(it); + } + } + else + { + it = new FOptionMenuItemStaticText("No configurable axes", false); + opt->mItems.Push(it); + } + } + opt->mScrollPos = 0; + opt->mSelectedItem = -1; + opt->mIndent = 0; + opt->mPosition = -25; + opt->CalcIndent(); + return opt; + } + return NULL; +} + + + +void UpdateJoystickMenu(IJoystickConfig *selected) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickOptions); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; + FOptionMenuItem *it; + for(unsigned i=0;imItems.Size();i++) + { + delete opt->mItems[i]; + opt->mItems.Clear(); + } + + int i; + int itemnum = -1; + + I_GetJoysticks(Joysticks); + if ((unsigned)itemnum >= Joysticks.Size()) + { + itemnum = Joysticks.Size() - 1; + } + if (selected != NULL) + { + for (i = 0; (unsigned)i < Joysticks.Size(); ++i) + { + if (Joysticks[i] == selected) + { + itemnum = i; + break; + } + } + } + + // Todo: Block joystick for changing this one. + it = new FOptionMenuItemOption("Enable controller support", "use_joystick", "YesNo", NULL, false); + opt->mItems.Push(it); + #ifdef _WIN32 + it = new FOptionMenuItemOption("Enable DirectInput controllers", "joy_dinput", "YesNo", NULL, false); + opt->mItems.Push(it); + it = new FOptionMenuItemOption("Enable XInput controllers", "joy_xinput", "YesNo", NULL, false); + opt->mItems.Push(it); + it = new FOptionMenuItemOption("Enable raw PlayStation 2 adapters", "joy_ps2raw", "YesNo", NULL, false); + opt->mItems.Push(it); + #endif + + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + if (Joysticks.Size() == 0) + { + it = new FOptionMenuItemStaticText("No controllers detected", false); + opt->mItems.Push(it); + if (!use_joystick) + { + it = new FOptionMenuItemStaticText("Controller support must be", false); + opt->mItems.Push(it); + it = new FOptionMenuItemStaticText("enabled to detect any", false); + opt->mItems.Push(it); + } + } + else + { + it = new FOptionMenuItemStaticText("Configure controllers:", false); + opt->mItems.Push(it); + + for (int i = 0; i < (int)Joysticks.Size(); ++i) + { + it = new FOptionMenuItemJoyConfigMenu(Joysticks[i]->GetName(), Joysticks[i]); + opt->mItems.Push(it); + if (i == itemnum) opt->mSelectedItem = opt->mItems.Size(); + } + } + if (opt->mSelectedItem >= (int)opt->mItems.Size()) + { + opt->mSelectedItem = opt->mItems.Size() - 1; + } + + opt->CalcIndent(); + + // If the joystick config menu is open, close it if the device it's + // open for is gone. + for (i = 0; (unsigned)i < Joysticks.Size(); ++i) + { + if (Joysticks[i] == SELECTED_JOYSTICK) + { + break; + } + } + if (i == (int)Joysticks.Size()) + { + SELECTED_JOYSTICK = NULL; + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DJoystickConfigMenu))) + { + DMenu::CurrentMenu->Close(); + } + } + } +} + diff --git a/src/menu/listmenu.cpp b/src/menu/listmenu.cpp new file mode 100644 index 0000000000..42164c16b3 --- /dev/null +++ b/src/menu/listmenu.cpp @@ -0,0 +1,511 @@ +/* +** listmenu.cpp +** A simple menu consisting of a list of items +** +**--------------------------------------------------------------------------- +** Copyright 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 "v_video.h" +#include "v_font.h" +#include "cmdlib.h" +#include "gstrings.h" +#include "g_level.h" +#include "gi.h" +#include "d_gui.h" +#include "d_event.h" +#include "menu/menu.h" + +IMPLEMENT_CLASS(DListMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DListMenu::DListMenu(DMenu *parent, FListMenuDescriptor *desc) +: DMenu(parent) +{ + mDesc = desc; + mFocusControl = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Init(DMenu *parent, FListMenuDescriptor *desc) +{ + mParentMenu = parent; + GC::WriteBarrier(this, parent); + mDesc = desc; +} + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItem *DListMenu::GetItem(FName name) +{ + for(unsigned i=0;imItems.Size(); i++) + { + FName nm = mDesc->mItems[i]->GetAction(NULL); + if (nm == name) return mDesc->mItems[i]; + } + return NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_KeyDown) + { + int ch = tolower (ev->data1); + + for(unsigned i = mDesc->mSelectedItem + 1; i < mDesc->mItems.Size(); i++) + { + if (mDesc->mItems[i]->CheckHotkey(ch)) + { + mDesc->mSelectedItem = i; + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + } + } + for(int i = 0; i < mDesc->mSelectedItem; i++) + { + if (mDesc->mItems[i]->CheckHotkey(ch)) + { + mDesc->mSelectedItem = i; + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + } + } + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int startedAt = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + do + { + if (--mDesc->mSelectedItem < 0) mDesc->mSelectedItem = mDesc->mItems.Size()-1; + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + + case MKEY_Down: + do + { + if (++mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) mDesc->mSelectedItem = 0; + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + + case MKEY_Enter: + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + } + return true; + + default: + return Super::MenuEvent(mkey, fromcontroller); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::MouseEvent(int type, int x, int y) +{ + int sel = -1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; + + if (mFocusControl != NULL) + { + mFocusControl->MouseEvent(type, x, y); + return true; + } + else + { + if ((mDesc->mWLeft <= 0 || x > mDesc->mWLeft) && + (mDesc->mWRight <= 0 || x < mDesc->mWRight)) + { + for(unsigned i=0;imItems.Size(); i++) + { + if (mDesc->mItems[i]->CheckCoordinate(x, y)) + { + if (i != mDesc->mSelectedItem) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mSelectedItem = i; + mDesc->mItems[i]->MouseEvent(type, x, y); + return true; + } + } + } + } + mDesc->mSelectedItem = -1; + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Ticker () +{ + Super::Ticker(); + for(unsigned i=0;imItems.Size(); i++) + { + mDesc->mItems[i]->Ticker(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Drawer () +{ + for(unsigned i=0;imItems.Size(); i++) + { + if (mDesc->mItems[i]->mEnabled) mDesc->mItems[i]->Drawer(mDesc->mSelectedItem == i); + } + if (mDesc->mSelectedItem >= 0 && mDesc->mSelectedItem < (int)mDesc->mItems.Size()) + mDesc->mItems[mDesc->mSelectedItem]->DrawSelector(mDesc->mSelectOfsX, mDesc->mSelectOfsY, mDesc->mSelector); + Super::Drawer(); +} + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +FListMenuItem::~FListMenuItem() +{ +} + +bool FListMenuItem::CheckCoordinate(int x, int y) +{ + return false; +} + +void FListMenuItem::Ticker() +{ +} + +void FListMenuItem::Drawer(bool selected) +{ +} + +bool FListMenuItem::Selectable() +{ + return false; +} + +void FListMenuItem::DrawSelector(int xofs, int yofs, FTextureID tex) +{ + if (tex.isNull()) + { + if ((DMenu::MenuTime%8) < 6) + { + screen->DrawText(ConFont, OptionSettings.mFontColorSelection, + mXpos + xofs, mYpos + yofs, "\xd", DTA_Clean, true, TAG_DONE); + } + } + else + { + screen->DrawTexture (TexMan(tex), mXpos + xofs, mYpos + yofs, DTA_Clean, true, TAG_DONE); + } +} + +bool FListMenuItem::Activate() +{ + return false; // cannot be activated +} + +FName FListMenuItem::GetAction(int *pparam) +{ + return mAction; +} + +bool FListMenuItem::SetString(int i, const char *s) +{ + return false; +} + +bool FListMenuItem::GetString(int i, char *s, int len) +{ + return false; +} + +bool FListMenuItem::SetValue(int i, int value) +{ + return false; +} + +bool FListMenuItem::GetValue(int i, int *pvalue) +{ + return false; +} + +void FListMenuItem::Enable(bool on) +{ + mEnabled = on; +} + +bool FListMenuItem::MenuEvent(int mkey, bool fromcontroller) +{ + return false; +} + +bool FListMenuItem::MouseEvent(int type, int x, int y) +{ + return false; +} + +bool FListMenuItem::CheckHotkey(int c) +{ + return false; +} + + +//============================================================================= +// +// static patch +// +//============================================================================= + +FListMenuItemStaticPatch::FListMenuItemStaticPatch(int x, int y, FTextureID patch, bool centered) +: FListMenuItem(x, y) +{ + mTexture = patch; + mCentered = centered; +} + +void FListMenuItemStaticPatch::Drawer(bool selected) +{ + int x = mXpos; + FTexture *tex = TexMan(mTexture); + if (mYpos >= 0) + { + if (mCentered) x -= tex->GetScaledWidth()/2; + screen->DrawTexture (tex, x, mYpos, DTA_Clean, true, TAG_DONE); + } + else + { + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + if (mCentered) x -= (tex->GetScaledWidth()*CleanXfac)/2; + screen->DrawTexture (tex, x, -mYpos*CleanYfac, DTA_CleanNoMove, true, TAG_DONE); + } +} + +//============================================================================= +// +// static text +// +//============================================================================= + +FListMenuItemStaticText::FListMenuItemStaticText(int x, int y, const char *text, FFont *font, EColorRange color, bool centered) +: FListMenuItem(x, y) +{ + mText = ncopystring(text); + mFont = font; + mColor = color; + mCentered = centered; +} + +void FListMenuItemStaticText::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + if (mYpos >= 0) + { + int x = mXpos; + if (mCentered) x -= mFont->StringWidth(text)/2; + screen->DrawText(mFont, mColor, x, mYpos, text, DTA_Clean, true, TAG_DONE); + } + else + { + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + if (mCentered) x -= (mFont->StringWidth(text)*CleanXfac)/2; + screen->DrawText (mFont, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true, TAG_DONE); + } + } +} + +FListMenuItemStaticText::~FListMenuItemStaticText() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// base class for selectable items +// +//============================================================================= + +FListMenuItemSelectable::FListMenuItemSelectable(int x, int y, int height, FName action, int param) +: FListMenuItem(x, y, action) +{ + mHeight = height; + mParam = param; + mHotkey = 0; +} + +bool FListMenuItemSelectable::CheckCoordinate(int x, int y) +{ + return mEnabled && y >= mYpos && y < mYpos + mHeight; // no x check here +} + +bool FListMenuItemSelectable::Selectable() +{ + return mEnabled; +} + +bool FListMenuItemSelectable::Activate() +{ + M_SetMenu(mAction, mParam); + return true; +} + +FName FListMenuItemSelectable::GetAction(int *pparam) +{ + if (pparam != NULL) *pparam = mParam; + return mAction; +} + +bool FListMenuItemSelectable::CheckHotkey(int c) +{ + return c == tolower(mHotkey); +} + +bool FListMenuItemSelectable::MouseEvent(int type, int x, int y) +{ + if (type == DMenu::MOUSE_Release) + { + if (DMenu::CurrentMenu->MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + return false; +} + +//============================================================================= +// +// text item +// +//============================================================================= + +FListMenuItemText::FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param) +: FListMenuItemSelectable(x, y, height, child, param) +{ + mText = ncopystring(text); + mFont = font; + mColor = color; + mHotkey = hotkey; +} + +FListMenuItemText::~FListMenuItemText() +{ + if (mText != NULL) + { + delete [] mText; + } +} + +void FListMenuItemText::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, mColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + } +} + +//============================================================================= +// +// patch item +// +//============================================================================= + +FListMenuItemPatch::FListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID patch, FName child, int param) +: FListMenuItemSelectable(x, y, height, child, param) +{ + mHotkey = hotkey; + mTexture = patch; +} + +void FListMenuItemPatch::Drawer(bool selected) +{ + screen->DrawTexture (TexMan(mTexture), mXpos, mYpos, DTA_Clean, true, TAG_DONE); +} diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp new file mode 100644 index 0000000000..130dd0bd52 --- /dev/null +++ b/src/menu/loadsavemenu.cpp @@ -0,0 +1,1165 @@ +/* +** loadsavemenu.cpp +** The load game and save game menus +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 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 "menu/menu.h" +#include "i_system.h" +#include "lists.h" +#include "version.h" +#include "g_game.h" +#include "m_png.h" +#include "w_wad.h" +#include "v_text.h" +#include "d_event.h" +#include "gstrings.h" +#include "v_palette.h" +#include "doomstat.h" +#include "gi.h" +#include "d_gui.h" + + + + +class DLoadSaveMenu : public DListMenu +{ + DECLARE_CLASS(DLoadSaveMenu, DListMenu) + +protected: + static List SaveGames; + static FSaveGameNode *TopSaveGame; + static FSaveGameNode *lastSaveSlot; + static FSaveGameNode *SelSaveGame; + + + friend void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); + + static FSaveGameNode *RemoveSaveSlot (FSaveGameNode *file); + static void UnloadSaveStrings(); + static void InsertSaveNode (FSaveGameNode *node); + static void ReadSaveStrings (); + static void NotifyNewSave (const char *file, const char *title, bool okForQuicksave); + + + FTexture *SavePic; + FBrokenLines *SaveComment; + bool mEntering; + char savegamestring[SAVESTRINGSIZE]; + bool mWheelScrolled; + + DLoadSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + void Destroy(); + + void UnloadSaveData (); + void ClearSaveStuff (); + void ExtractSaveData (const FSaveGameNode *node); + void Drawer (); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + bool Responder(event_t *ev); + +}; + +IMPLEMENT_CLASS(DLoadSaveMenu) + +List DLoadSaveMenu::SaveGames; +FSaveGameNode *DLoadSaveMenu::TopSaveGame; +FSaveGameNode *DLoadSaveMenu::lastSaveSlot; +FSaveGameNode *DLoadSaveMenu::SelSaveGame; + +FSaveGameNode *quickSaveSlot; + +//============================================================================= +// +// Save data maintenance (stored statically) +// +//============================================================================= + +FSaveGameNode *DLoadSaveMenu::RemoveSaveSlot (FSaveGameNode *file) +{ + FSaveGameNode *next = static_cast(file->Succ); + + if (file == TopSaveGame) + { + TopSaveGame = next; + } + if (quickSaveSlot == file) + { + quickSaveSlot = NULL; + } + if (lastSaveSlot == file) + { + lastSaveSlot = NULL; + } + file->Remove (); + if (!file->bNoDelete) delete file; + return next; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::UnloadSaveStrings() +{ + while (!SaveGames.IsEmpty()) + { + RemoveSaveSlot (static_cast(SaveGames.Head)); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::InsertSaveNode (FSaveGameNode *node) +{ + FSaveGameNode *probe; + + if (SaveGames.IsEmpty ()) + { + SaveGames.AddHead (node); + return; + } + + if (node->bOldVersion) + { // Add node at bottom of list + probe = static_cast(SaveGames.TailPred); + while (probe->Pred != NULL && probe->bOldVersion && + stricmp (node->Title, probe->Title) < 0) + { + probe = static_cast(probe->Pred); + } + node->Insert (probe); + } + else + { // Add node at top of list + probe = static_cast(SaveGames.Head); + while (probe->Succ != NULL && !probe->bOldVersion && + stricmp (node->Title, probe->Title) > 0) + { + probe = static_cast(probe->Succ); + } + node->InsertBefore (probe); + } +} + + +//============================================================================= +// +// M_ReadSaveStrings +// +// Find savegames and read their titles +// +//============================================================================= + +void DLoadSaveMenu::ReadSaveStrings () +{ + if (SaveGames.IsEmpty ()) + { + void *filefirst; + findstate_t c_file; + FString filter; + + atterm (UnloadSaveStrings); + + filter = G_BuildSaveName ("*.zds", -1); + filefirst = I_FindFirst (filter.GetChars(), &c_file); + if (filefirst != ((void *)(-1))) + { + do + { + // I_FindName only returns the file's name and not its full path + FString filepath = G_BuildSaveName (I_FindName(&c_file), -1); + FILE *file = fopen (filepath, "rb"); + + if (file != NULL) + { + PNGHandle *png; + char sig[16]; + char title[SAVESTRINGSIZE+1]; + bool oldVer = true; + bool addIt = false; + bool missing = false; + + // ZDoom 1.23 betas 21-33 have the savesig first. + // Earlier versions have the savesig second. + // Later versions have the savegame encapsulated inside a PNG. + // + // Old savegame versions are always added to the menu so + // the user can easily delete them if desired. + + title[SAVESTRINGSIZE] = 0; + + if (NULL != (png = M_VerifyPNG (file))) + { + char *ver = M_GetPNGText (png, "ZDoom Save Version"); + char *engine = M_GetPNGText (png, "Engine"); + if (ver != NULL) + { + if (!M_GetPNGText (png, "Title", title, SAVESTRINGSIZE)) + { + strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE); + } + if (strncmp (ver, SAVESIG, 9) == 0 && + atoi (ver+9) >= MINSAVEVER && + engine != NULL) + { + // Was saved with a compatible ZDoom version, + // so check if it's for the current game. + // If it is, add it. Otherwise, ignore it. + char *iwad = M_GetPNGText (png, "Game WAD"); + if (iwad != NULL) + { + if (stricmp (iwad, Wads.GetWadName (FWadCollection::IWAD_FILENUM)) == 0) + { + addIt = true; + oldVer = false; + missing = !G_CheckSaveGameWads (png, false); + } + delete[] iwad; + } + } + else + { // An old version + addIt = true; + } + delete[] ver; + } + if (engine != NULL) + { + delete[] engine; + } + delete png; + } + else + { + fseek (file, 0, SEEK_SET); + if (fread (sig, 1, 16, file) == 16) + { + + if (strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) + { + addIt = true; + } + } + else + { + memcpy (title, sig, 16); + if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && + fread (sig, 1, 16, file) == 16 && + strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + addIt = true; + } + } + } + } + + if (addIt) + { + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = oldVer; + node->bMissingWads = missing; + memcpy (node->Title, title, SAVESTRINGSIZE); + InsertSaveNode (node); + } + fclose (file); + } + } while (I_FindNext (filefirst, &c_file) == 0); + I_FindClose (filefirst); + } + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + SelSaveGame = static_cast(SaveGames.Head); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::NotifyNewSave (const char *file, const char *title, bool okForQuicksave) +{ + FSaveGameNode *node; + + if (file == NULL) + return; + + ReadSaveStrings (); + + // See if the file is already in our list + for (node = static_cast(SaveGames.Head); + node->Succ != NULL; + node = static_cast(node->Succ)) + { +#ifdef unix + if (node->Filename.Compare (file) == 0) +#else + if (node->Filename.CompareNoCase (file) == 0) +#endif + { + strcpy (node->Title, title); + node->bOldVersion = false; + node->bMissingWads = false; + break; + } + } + + if (node->Succ == NULL) + { + node = new FSaveGameNode; + strcpy (node->Title, title); + node->Filename = file; + node->bOldVersion = false; + node->bMissingWads = false; + InsertSaveNode (node); + SelSaveGame = node; + } + + if (okForQuicksave) + { + if (quickSaveSlot == NULL) quickSaveSlot = node; + lastSaveSlot = node; + } +} + +void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave) +{ + DLoadSaveMenu::NotifyNewSave(file, title, okForQuicksave); +} + +//============================================================================= +// +// End of static savegame maintenance code +// +//============================================================================= + +DLoadSaveMenu::DLoadSaveMenu(DMenu *parent, FListMenuDescriptor *desc) +: DListMenu(parent, desc) +{ + ReadSaveStrings(); + mWheelScrolled = false; +} + +void DLoadSaveMenu::Destroy() +{ + ClearSaveStuff (); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::UnloadSaveData () +{ + if (SavePic != NULL) + { + delete SavePic; + } + if (SaveComment != NULL) + { + V_FreeBrokenLines (SaveComment); + } + + SavePic = NULL; + SaveComment = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::ClearSaveStuff () +{ + UnloadSaveData(); + if (quickSaveSlot == (FSaveGameNode *)1) + { + quickSaveSlot = NULL; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::ExtractSaveData (const FSaveGameNode *node) +{ + FILE *file; + PNGHandle *png; + + UnloadSaveData (); + + if (node != NULL && + node->Succ != NULL && + !node->Filename.IsEmpty() && + !node->bOldVersion && + (file = fopen (node->Filename.GetChars(), "rb")) != NULL) + { + if (NULL != (png = M_VerifyPNG (file))) + { + char *time, *pcomment, *comment; + size_t commentlen, totallen, timelen; + + // Extract comment + time = M_GetPNGText (png, "Creation Time"); + pcomment = M_GetPNGText (png, "Comment"); + if (pcomment != NULL) + { + commentlen = strlen (pcomment); + } + else + { + commentlen = 0; + } + if (time != NULL) + { + timelen = strlen (time); + totallen = timelen + commentlen + 3; + } + else + { + timelen = 0; + totallen = commentlen + 1; + } + if (totallen != 0) + { + comment = new char[totallen]; + + if (timelen) + { + memcpy (comment, time, timelen); + comment[timelen] = '\n'; + comment[timelen+1] = '\n'; + timelen += 2; + } + if (commentlen) + { + memcpy (comment + timelen, pcomment, commentlen); + } + comment[timelen+commentlen] = 0; + SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment); + delete[] comment; + delete[] time; + delete[] pcomment; + } + + // Extract pic + SavePic = PNGTexture_CreateFromFile(png, node->Filename); + + delete png; + } + fclose (file); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::Drawer () +{ + Super::Drawer(); + + const int savepicLeft = 10; + const int savepicTop = 54*CleanYfac; + const int savepicWidth = 216*screen->GetWidth()/640; + const int savepicHeight = 135*screen->GetHeight()/400; + + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxLeft = savepicLeft + savepicWidth + 14; + const int listboxTop = savepicTop; + const int listboxWidth = screen->GetWidth() - listboxLeft - 10; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + const int listboxHeight = listboxRows * rowHeight + 1; + const int listboxRight = listboxLeft + listboxWidth; + const int listboxBottom = listboxTop + listboxHeight; + + const int commentLeft = savepicLeft; + const int commentTop = savepicTop + savepicHeight + 16; + const int commentWidth = savepicWidth; + const int commentHeight = (51+(screen->GetHeight()>200?10:0))*CleanYfac; + const int commentRight = commentLeft + commentWidth; + const int commentBottom = commentTop + commentHeight; + + FSaveGameNode *node; + int i; + bool didSeeSelected = false; + + // Draw picture area + if (gameaction == ga_loadgame || gameaction == ga_savegame) + { + return; + } + + V_DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); + if (SavePic != NULL) + { + screen->DrawTexture(SavePic, savepicLeft, savepicTop, + DTA_DestWidth, savepicWidth, + DTA_DestHeight, savepicHeight, + DTA_Masked, false, + TAG_DONE); + } + else + { + screen->Clear (savepicLeft, savepicTop, + savepicLeft+savepicWidth, savepicTop+savepicHeight, 0, 0); + + if (!SaveGames.IsEmpty ()) + { + const char *text = + (SelSaveGame == NULL || !SelSaveGame->bOldVersion) + ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); + const int textlen = SmallFont->StringWidth (text)*CleanXfac; + + screen->DrawText (SmallFont, CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, + savepicTop+(savepicHeight-rowHeight)/2, text, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // Draw comment area + V_DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); + screen->Clear (commentLeft, commentTop, commentRight, commentBottom, 0, 0); + if (SaveComment != NULL) + { + // I'm not sure why SaveComment would go NULL in this loop, but I got + // a crash report where it was NULL when i reached 1, so now I check + // for that. + for (i = 0; SaveComment != NULL && SaveComment[i].Width >= 0 && i < 6; ++i) + { + screen->DrawText (SmallFont, CR_GOLD, commentLeft, commentTop + + SmallFont->GetHeight()*i*CleanYfac, SaveComment[i].Text, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // Draw file area + do + { + V_DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); + screen->Clear (listboxLeft, listboxTop, listboxRight, listboxBottom, 0, 0); + + if (SaveGames.IsEmpty ()) + { + const char * text = GStrings("MNU_NOFILES"); + const int textlen = SmallFont->StringWidth (text)*CleanXfac; + + screen->DrawText (SmallFont, CR_GOLD, listboxLeft+(listboxWidth-textlen)/2, + listboxTop+(listboxHeight-rowHeight)/2, text, + DTA_CleanNoMove, true, TAG_DONE); + return; + } + + for (i = 0, node = TopSaveGame; + i < listboxRows && node->Succ != NULL; + ++i, node = static_cast(node->Succ)) + { + int color; + if (node->bOldVersion) + { + color = CR_BLUE; + } + else if (node->bMissingWads) + { + color = CR_ORANGE; + } + else if (node == SelSaveGame) + { + color = CR_WHITE; + } + else + { + color = CR_TAN; + } + if (node == SelSaveGame) + { + screen->Clear (listboxLeft, listboxTop+rowHeight*i, + listboxRight, listboxTop+rowHeight*(i+1), -1, + mEntering ? MAKEARGB(255,255,0,0) : MAKEARGB(255,0,0,255)); + didSeeSelected = true; + if (!mEntering) + { + screen->DrawText (SmallFont, color, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, + DTA_CleanNoMove, true, TAG_DONE); + } + else + { + screen->DrawText (SmallFont, CR_WHITE, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, savegamestring, + DTA_CleanNoMove, true, TAG_DONE); + + screen->DrawText (SmallFont, CR_WHITE, + listboxLeft+1+SmallFont->StringWidth (savegamestring)*CleanXfac, + listboxTop+rowHeight*i+CleanYfac, + (gameinfo.gametype & (GAME_DoomStrifeChex)) ? "_" : "[", + DTA_CleanNoMove, true, TAG_DONE); + } + } + else + { + screen->DrawText (SmallFont, color, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // This is dumb: If the selected node was not visible, + // scroll down and redraw. M_SaveLoadResponder() + // guarantees that if the node is not visible, it will + // always be below the visible list instead of above it. + // This should not really be done here, but I don't care. + + if (!didSeeSelected) + { + // no, this shouldn't be here - and that's why there's now another hack in here + // so that the mouse scrolling does not get screwed by this code... + if (mWheelScrolled) + { + didSeeSelected = true; + SelSaveGame = NULL; + mWheelScrolled = false; + } + for (i = 1; node->Succ != NULL && node != SelSaveGame; ++i) + { + node = static_cast(node->Succ); + } + if (node->Succ == NULL) + { // SelSaveGame is invalid + didSeeSelected = true; + } + else + { + do + { + TopSaveGame = static_cast(TopSaveGame->Succ); + } while (--i); + } + } + } while (!didSeeSelected); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::MenuEvent (int mkey, bool fromcontroller) +{ + switch (mkey) + { + case MKEY_Up: + if (SelSaveGame == NULL) + { + SelSaveGame = TopSaveGame; + } + else if (SelSaveGame->Succ != NULL) + { + if (SelSaveGame != SaveGames.Head) + { + if (SelSaveGame == TopSaveGame) + { + TopSaveGame = static_cast(TopSaveGame->Pred); + } + SelSaveGame = static_cast(SelSaveGame->Pred); + } + else + { + SelSaveGame = static_cast(SaveGames.TailPred); + } + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + } + return true; + + case MKEY_Down: + if (SelSaveGame == NULL) + { + SelSaveGame = TopSaveGame; + } + else if (SelSaveGame->Succ != NULL) + { + if (SelSaveGame != SaveGames.TailPred) + { + SelSaveGame = static_cast(SelSaveGame->Succ); + } + else + { + SelSaveGame = TopSaveGame = + static_cast(SaveGames.Head); + } + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + } + return true; + + case MKEY_Enter: + return false; // This event will be handled by the subclasses + + case MKEY_MBYes: + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + FSaveGameNode *next = static_cast(SelSaveGame->Succ); + if (next->Succ == NULL) + { + next = static_cast(SelSaveGame->Pred); + if (next->Pred == NULL) + { + next = NULL; + } + } + + remove (SelSaveGame->Filename.GetChars()); + UnloadSaveData (); + SelSaveGame = RemoveSaveSlot (SelSaveGame); + ExtractSaveData (SelSaveGame); + } + return true; + } + + default: + return Super::MenuEvent(mkey, fromcontroller); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::MouseEvent(int type, int x, int y) +{ + const int savepicLeft = 10; + const int savepicTop = 54*CleanYfac; + const int savepicWidth = 216*screen->GetWidth()/640; + + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxLeft = savepicLeft + savepicWidth + 14; + const int listboxTop = savepicTop; + const int listboxWidth = screen->GetWidth() - listboxLeft - 10; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + const int listboxHeight = listboxRows * rowHeight + 1; + const int listboxRight = listboxLeft + listboxWidth; + const int listboxBottom = listboxTop + listboxHeight; + + if (x >= listboxLeft && x < listboxLeft + listboxWidth && + y >= listboxTop && y < listboxTop + listboxHeight) + { + int lineno = (y - listboxTop) / rowHeight; + FSaveGameNode *top = TopSaveGame; + while (lineno > 0 && top->Succ != NULL) + { + lineno--; + top = (FSaveGameNode *)top->Succ; + } + if (lineno == 0) + { + if (SelSaveGame != top) + { + SelSaveGame = top; + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + // Sound? + } + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + } + else SelSaveGame = NULL; + } + else + { + SelSaveGame = NULL; + } + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_KeyDown) + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + switch (ev->data1) + { + case GK_F1: + if (!SelSaveGame->Filename.IsEmpty()) + { + char workbuf[512]; + + mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars()); + if (SaveComment != NULL) + { + V_FreeBrokenLines (SaveComment); + } + SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf); + } + return true; + + case GK_DEL: + case '\b': + { + FString EndString; + EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", + GStrings("MNU_DELETESG"), SelSaveGame->Title, GStrings("PRESSYN")); + M_StartMessage (EndString, 0); + } + return true; + } + } + } + else if (ev->subtype == EV_GUI_WheelUp) + { + if (TopSaveGame != SaveGames.Head && TopSaveGame != NULL) + { + TopSaveGame = static_cast(TopSaveGame->Pred); + mWheelScrolled = true; + } + return true; + } + else if (ev->subtype == EV_GUI_WheelDown) + { + const int savepicTop = 54*CleanYfac; + const int listboxTop = savepicTop; + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + + FSaveGameNode *node = TopSaveGame; + if (node != NULL) + { + int count = 1; + while (node != NULL && node != SaveGames.TailPred) + { + node = (FSaveGameNode*)node->Succ; + count++; + } + if (count > listboxRows) + { + TopSaveGame = (FSaveGameNode*)TopSaveGame->Succ; + mWheelScrolled = true; + } + } + } + } + return Super::Responder(ev); +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DSaveMenu : public DLoadSaveMenu +{ + DECLARE_CLASS(DSaveMenu, DLoadSaveMenu) + + FSaveGameNode NewSaveNode; + +public: + + DSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + void Destroy(); + void DoSave (FSaveGameNode *node); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + +}; + +IMPLEMENT_CLASS(DSaveMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +DSaveMenu::DSaveMenu(DMenu *parent, FListMenuDescriptor *desc) +: DLoadSaveMenu(parent, desc) +{ + strcpy (NewSaveNode.Title, ""); + NewSaveNode.bNoDelete = true; + SaveGames.AddHead (&NewSaveNode); + TopSaveGame = static_cast(SaveGames.Head); + if (lastSaveSlot == NULL) + { + SelSaveGame = &NewSaveNode; + } + else + { + SelSaveGame = lastSaveSlot; + } + ExtractSaveData (SelSaveGame); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DSaveMenu::Destroy() +{ + if (SaveGames.Head == &NewSaveNode) + { + SaveGames.RemHead (); + if (SelSaveGame == &NewSaveNode) + { + SelSaveGame = static_cast(SaveGames.Head); + } + if (TopSaveGame == &NewSaveNode) + { + TopSaveGame = static_cast(SaveGames.Head); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DSaveMenu::DoSave (FSaveGameNode *node) +{ + if (node != &NewSaveNode) + { + G_SaveGame (node->Filename.GetChars(), savegamestring); + } + else + { + // Find an unused filename and save as that + FString filename; + int i; + FILE *test; + + for (i = 0;; ++i) + { + filename = G_BuildSaveName ("save", i); + test = fopen (filename, "rb"); + if (test == NULL) + { + break; + } + fclose (test); + } + G_SaveGame (filename, savegamestring); + } + M_ClearMenus(); + BorderNeedRefresh = screen->GetPageCount (); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DSaveMenu::MenuEvent (int mkey, bool fromcontroller) +{ + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + return false; + } + + if (mkey == MKEY_Enter) + { + if (SelSaveGame != &NewSaveNode) + { + strcpy (savegamestring, SelSaveGame->Title); + } + else + { + savegamestring[0] = 0; + } + DMenu *input = new DTextEnterMenu(this, savegamestring, SAVESTRINGSIZE, 1, fromcontroller); + M_ActivateMenu(input); + mEntering = true; + } + else if (mkey == MKEY_Input) + { + mEntering = false; + DoSave(SelSaveGame); + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DSaveMenu::Responder (event_t *ev) +{ + if (ev->subtype == EV_GUI_KeyDown) + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + switch (ev->data1) + { + case GK_DEL: + case '\b': + // cannot delete 'new save game' item + if (SelSaveGame == &NewSaveNode) return true; + break; + + case 'N': + SelSaveGame = TopSaveGame = &NewSaveNode; + UnloadSaveData (); + return true; + } + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +class DLoadMenu : public DLoadSaveMenu +{ + DECLARE_CLASS(DLoadMenu, DLoadSaveMenu) + +public: + + DLoadMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + + bool MenuEvent (int mkey, bool fromcontroller); +}; + +IMPLEMENT_CLASS(DLoadMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +DLoadMenu::DLoadMenu(DMenu *parent, FListMenuDescriptor *desc) +: DLoadSaveMenu(parent, desc) +{ + TopSaveGame = static_cast(SaveGames.Head); + ExtractSaveData (SelSaveGame); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadMenu::MenuEvent (int mkey, bool fromcontroller) +{ + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + return false; + } + + if (mkey == MKEY_Enter) + { + G_LoadGame (SelSaveGame->Filename.GetChars()); + if (gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + } + if (quickSaveSlot == (FSaveGameNode *)1) + { + quickSaveSlot = SelSaveGame; + } + M_ClearMenus(); + BorderNeedRefresh = screen->GetPageCount (); + return true; + } + return false; +} + diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp new file mode 100644 index 0000000000..cb557512fd --- /dev/null +++ b/src/menu/menu.cpp @@ -0,0 +1,949 @@ +/* +** menu.cpp +** Menu base class and global interface +** +**--------------------------------------------------------------------------- +** Copyright 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 "doomdef.h" +#include "doomstat.h" +#include "c_dispatch.h" +#include "d_gui.h" +#include "d_player.h" +#include "g_level.h" +#include "c_console.h" +#include "c_bind.h" +#include "s_sound.h" +#include "p_tick.h" +#include "g_game.h" +#include "c_cvars.h" +#include "d_event.h" +#include "v_video.h" +#include "hu_stuff.h" +#include "gi.h" +#include "i_input.h" +#include "gameconfigfile.h" +#include "gstrings.h" +#include "r_main.h" +#include "menu/menu.h" +#include "textures/textures.h" + +// +// Todo: Move these elsewhere +// +CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, show_obituaries, true, CVAR_ARCHIVE) + + +CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) +CVAR(Int, m_use_mouse, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Int, m_show_backbutton, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +DMenu *DMenu::CurrentMenu; +int DMenu::MenuTime; + +FListMenuDescriptor *MainMenu; +FGameStartup GameStartupInfo; +EMenuState menuactive; +bool M_DemoNoPlay; +FButtonStatus MenuButtons[NUM_MKEYS]; +int MenuButtonTickers[NUM_MKEYS]; +bool MenuButtonOrigin[NUM_MKEYS]; +int BackbuttonTime; +fixed_t BackbuttonAlpha; + + +#define KEY_REPEAT_DELAY (TICRATE*5/12) +#define KEY_REPEAT_RATE (3) + +//============================================================================ +// +// DMenu base class +// +//============================================================================ + +IMPLEMENT_POINTY_CLASS (DMenu) + DECLARE_POINTER(mParentMenu) +END_POINTERS + +DMenu::DMenu(DMenu *parent) +{ + mParentMenu = parent; + mMouseCapture = false; + mBackbuttonSelected = false; + GC::WriteBarrier(this, parent); +} + +bool DMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_LButtonDown) + { + MouseEventBack(MOUSE_Click, ev->data1, ev->data2); + if (MouseEvent(MOUSE_Click, ev->data1, ev->data2)) + { + SetCapture(); + } + + } + else if (ev->subtype == EV_GUI_MouseMove) + { + BackbuttonTime = BACKBUTTON_TIME; + if (mMouseCapture || m_use_mouse == 1) + { + MouseEventBack(MOUSE_Move, ev->data1, ev->data2); + return MouseEvent(MOUSE_Move, ev->data1, ev->data2); + } + } + else if (ev->subtype == EV_GUI_LButtonUp) + { + if (mMouseCapture) + { + ReleaseCapture(); + MouseEventBack(MOUSE_Release, ev->data1, ev->data2); + return MouseEvent(MOUSE_Release, ev->data1, ev->data2); + } + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MenuEvent (int mkey, bool fromcontroller) +{ + switch (mkey) + { + case MKEY_Back: + { + Close(); + S_Sound (CHAN_VOICE | CHAN_UI, + DMenu::CurrentMenu != NULL? "menu/backup" : "menu/clear", snd_menuvolume, ATTN_NONE); + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::Close () +{ + assert(DMenu::CurrentMenu == this); + DMenu::CurrentMenu = mParentMenu; + Destroy(); + if (DMenu::CurrentMenu != NULL) + { + GC::WriteBarrier(DMenu::CurrentMenu); + } + else + { + M_ClearMenus (); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MouseEvent(int type, int x, int y) +{ + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MouseEventBack(int type, int x, int y) +{ + if (m_show_backbutton >= 0) + { + FTexture *tex = TexMan[gameinfo.mBackButton]; + if (tex != NULL) + { + if (m_show_backbutton&1) x -= screen->GetWidth() - tex->GetScaledWidth() * CleanXfac; + if (m_show_backbutton&2) y -= screen->GetHeight() - tex->GetScaledHeight() * CleanYfac; + mBackbuttonSelected = (x >= 0 && x < tex->GetScaledWidth() * CleanXfac && y < tex->GetScaledHeight() * CleanYfac); + if (mBackbuttonSelected && type == MOUSE_Release) + { + if (m_use_mouse == 2) mBackbuttonSelected = false; + MenuEvent(MKEY_Back, true); + } + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::SetCapture() +{ + if (!mMouseCapture) + { + mMouseCapture = true; + I_SetMouseCapture(); + } +} + +void DMenu::ReleaseCapture() +{ + if (mMouseCapture) + { + mMouseCapture = false; + I_ReleaseMouseCapture(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::Ticker () +{ +} + +void DMenu::Drawer () +{ + if (this == DMenu::CurrentMenu && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) + { + FTexture *tex = TexMan[gameinfo.mBackButton]; + int w = tex->GetScaledWidth() * CleanXfac; + int h = tex->GetScaledHeight() * CleanYfac; + int x = (!(m_show_backbutton&1))? 0:screen->GetWidth() - w; + int y = (!(m_show_backbutton&2))? 0:screen->GetHeight() - h; + if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) + { + screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, MAKEARGB(40, 255,255,255), TAG_DONE); + } + else + { + screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha, TAG_DONE); + } + } +} + +bool DMenu::DimAllowed() +{ + return true; +} + +bool DMenu::TranslateKeyboardEvents() +{ + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_StartControlPanel (bool makeSound) +{ + // intro might call this repeatedly + if (DMenu::CurrentMenu != NULL) + return; + + ResetButtonStates (); + for (int i = 0; i < NUM_MKEYS; ++i) + { + MenuButtons[i].ReleaseKey(0); + } + + C_HideConsole (); // [RH] Make sure console goes bye bye. + menuactive = MENU_On; + // Pause sound effects before we play the menu switch sound. + // That way, it won't be paused. + P_CheckTickerPaused (); + + if (makeSound) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + } + BackbuttonTime = 0; + BackbuttonAlpha = 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_ActivateMenu(DMenu *menu) +{ + if (menuactive == MENU_Off) menuactive = MENU_On; + if (DMenu::CurrentMenu != NULL) DMenu::CurrentMenu->ReleaseCapture(); + DMenu::CurrentMenu = menu; + GC::WriteBarrier(DMenu::CurrentMenu); +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_SetMenu(FName menu, int param) +{ + // some menus need some special treatment + switch (menu) + { + case NAME_Episodemenu: + // sent from the player class menu + GameStartupInfo.Skill = -1; + GameStartupInfo.Episode = -1; + GameStartupInfo.PlayerClass = + param == -1? "Random" : PlayerClasses[param].Type->Meta.GetMetaString (APMETA_DisplayName); + break; + + case NAME_Skillmenu: + // sent from the episode menu + + if ((gameinfo.flags & GI_SHAREWARE) && param > 0) + { + // Only Doom and Heretic have multi-episode shareware versions. + if (gameinfo.gametype == GAME_Doom) + { + M_StartMessage(GStrings("SWSTRING"), 1); + } + else + { + M_StartMessage(GStrings("MNU_ONLYREGISTERED"), 1); + } + return; + } + + GameStartupInfo.Episode = param; + M_StartupSkillMenu(&GameStartupInfo); // needs player class name from class menu (later) + break; + + case NAME_StartgameConfirm: + { + // sent from the skill menu for a skill that needs to be confirmed + GameStartupInfo.Skill = param; + + const char *msg = AllSkills[param].MustConfirmText; + if (*msg==0) msg = GStrings("NIGHTMARE"); + M_StartMessage (msg, 0, NAME_Startgame); + return; + } + + case NAME_Startgame: + // sent either from skill menu or confirmation screen. Skill gets only set if sent from skill menu + // Now we can finally start the game. Ugh... + if (GameStartupInfo.Skill == -1) GameStartupInfo.Skill = param; + + G_DeferedInitNew (&GameStartupInfo); + if (gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + gameaction = ga_newgame; + } + M_ClearMenus (); + return; + + case NAME_Savegamemenu: + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) + { + // cannot save outside the game. + M_StartMessage (GStrings("SAVEDEAD"), 1); + return; + } + } + + // End of special checks + + FMenuDescriptor **desc = MenuDescriptors.CheckKey(menu); + if (desc != NULL) + { + if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame) + { + M_StartMessage((*desc)->mNetgameMessage, 1); + return; + } + + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + if (ld->mAutoselect >= 0 && ld->mAutoselect < (int)ld->mItems.Size()) + { + // recursively activate the autoselected item without ever creating this menu. + ld->mItems[ld->mAutoselect]->Activate(); + } + else + { + const PClass *cls = ld->mClass == NULL? RUNTIME_CLASS(DListMenu) : ld->mClass; + + DListMenu *newmenu = (DListMenu *)cls->CreateNew(); + newmenu->Init(DMenu::CurrentMenu, ld); + M_ActivateMenu(newmenu); + } + } + else if ((*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *ld = static_cast(*desc); + const PClass *cls = ld->mClass == NULL? RUNTIME_CLASS(DOptionMenu) : ld->mClass; + + DOptionMenu *newmenu = (DOptionMenu *)cls->CreateNew(); + newmenu->Init(DMenu::CurrentMenu, ld); + M_ActivateMenu(newmenu); + } + return; + } + else + { + const PClass *menuclass = PClass::FindClass(menu); + if (menuclass != NULL) + { + if (menuclass->IsDescendantOf(RUNTIME_CLASS(DMenu))) + { + DMenu *newmenu = (DMenu*)menuclass->CreateNew(); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); + return; + } + } + } + Printf("Attempting to open menu of unknown type '%s'\n", menu.GetChars()); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool M_Responder (event_t *ev) +{ + int ch = 0; + bool keyup = false; + int mkey = NUM_MKEYS; + bool fromcontroller = true; + + if (chatmodeon) + { + return false; + } + + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + { + // There are a few input sources we are interested in: + // + // EV_KeyDown / EV_KeyUp : joysticks/gamepads/controllers + // EV_GUI_KeyDown / EV_GUI_KeyUp : the keyboard + // EV_GUI_Char : printable characters, which we want in string input mode + // + // This code previously listened for EV_GUI_KeyRepeat to handle repeating + // in the menus, but that doesn't work with gamepads, so now we combine + // the multiple inputs into buttons and handle the repetition manually. + if (ev->type == EV_GUI_Event) + { + fromcontroller = false; + if (ev->subtype == EV_GUI_KeyRepeat) + { + // We do our own key repeat handling but still want to eat the + // OS's repeated keys. + return true; + } + else if (ev->subtype == EV_GUI_BackButtonDown || ev->subtype == EV_GUI_BackButtonUp) + { + mkey = MKEY_Back; + keyup = ev->subtype == EV_GUI_BackButtonUp; + } + else if (ev->subtype != EV_GUI_KeyDown && ev->subtype != EV_GUI_KeyUp) + { + // do we want mouse input? + if (ev->subtype >= EV_GUI_FirstMouseEvent && ev->subtype <= EV_GUI_LastMouseEvent) + { + // FIXME: Mouse events in SDL code are mostly useless so mouse is + // disabled until that code is fixed + #ifdef _WIN32 + if (!m_use_mouse) + #endif + return true; + } + + // pass everything else on to the current menu + return DMenu::CurrentMenu->Responder(ev); + } + else if (DMenu::CurrentMenu->TranslateKeyboardEvents()) + { + ch = ev->data1; + keyup = ev->subtype == EV_GUI_KeyUp; + switch (ch) + { + case GK_BACK: mkey = MKEY_Back; break; + case GK_ESCAPE: mkey = MKEY_Back; break; + case GK_RETURN: mkey = MKEY_Enter; break; + case GK_UP: mkey = MKEY_Up; break; + case GK_DOWN: mkey = MKEY_Down; break; + case GK_LEFT: mkey = MKEY_Left; break; + case GK_RIGHT: mkey = MKEY_Right; break; + case GK_BACKSPACE: mkey = MKEY_Clear; break; + case GK_PGUP: mkey = MKEY_PageUp; break; + case GK_PGDN: mkey = MKEY_PageDown; break; + default: + if (!keyup) + { + return DMenu::CurrentMenu->Responder(ev); + } + break; + } + } + } + else if (ev->type == EV_KeyDown || ev->type == EV_KeyUp) + { + keyup = ev->type == EV_KeyUp; + + ch = ev->data1; + switch (ch) + { + case KEY_JOY1: + case KEY_PAD_A: + mkey = MKEY_Enter; + break; + + case KEY_JOY2: + case KEY_PAD_B: + mkey = MKEY_Back; + break; + + case KEY_JOY3: + case KEY_PAD_X: + mkey = MKEY_Clear; + break; + + case KEY_JOY5: + case KEY_PAD_LSHOULDER: + mkey = MKEY_PageUp; + break; + + case KEY_JOY6: + case KEY_PAD_RSHOULDER: + mkey = MKEY_PageDown; + break; + + case KEY_PAD_DPAD_UP: + case KEY_PAD_LTHUMB_UP: + case KEY_JOYAXIS1MINUS: + case KEY_JOYPOV1_UP: + mkey = MKEY_Up; + break; + + case KEY_PAD_DPAD_DOWN: + case KEY_PAD_LTHUMB_DOWN: + case KEY_JOYAXIS1PLUS: + case KEY_JOYPOV1_DOWN: + mkey = MKEY_Down; + break; + + case KEY_PAD_DPAD_LEFT: + case KEY_PAD_LTHUMB_LEFT: + case KEY_JOYAXIS2MINUS: + case KEY_JOYPOV1_LEFT: + mkey = MKEY_Left; + break; + + case KEY_PAD_DPAD_RIGHT: + case KEY_PAD_LTHUMB_RIGHT: + case KEY_JOYAXIS2PLUS: + case KEY_JOYPOV1_RIGHT: + mkey = MKEY_Right; + break; + } + } + + if (mkey != NUM_MKEYS) + { + if (keyup) + { + MenuButtons[mkey].ReleaseKey(ch); + return false; + } + else + { + MenuButtons[mkey].PressKey(ch); + MenuButtonOrigin[mkey] = fromcontroller; + if (mkey <= MKEY_PageDown) + { + MenuButtonTickers[mkey] = KEY_REPEAT_DELAY; + } + DMenu::CurrentMenu->MenuEvent(mkey, fromcontroller); + return true; + } + } + return DMenu::CurrentMenu->Responder(ev) || !keyup; + } + else + { + if (ev->type == EV_KeyDown) + { + // Pop-up menu? + if (ev->data1 == KEY_ESCAPE) + { + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); + return true; + } + // If devparm is set, pressing F1 always takes a screenshot no matter + // what it's bound to. (for those who don't bother to read the docs) + if (devparm && ev->data1 == KEY_F1) + { + G_ScreenShot(NULL); + return true; + } + return false; + } + else if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_LButtonDown && + ConsoleState != c_down && m_use_mouse) + { + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Ticker (void) +{ + DMenu::MenuTime++; + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + DMenu::CurrentMenu->Ticker(); + + for (int i = 0; i < NUM_MKEYS; ++i) + { + if (MenuButtons[i].bDown) + { + if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0) + { + MenuButtonTickers[i] = KEY_REPEAT_RATE; + DMenu::CurrentMenu->MenuEvent(i, MenuButtonOrigin[i]); + } + } + } + + if (BackbuttonTime > 0) + { + if (BackbuttonAlpha < FRACUNIT) BackbuttonAlpha += FRACUNIT/10; + BackbuttonTime--; + } + else + { + if (BackbuttonAlpha > 0) BackbuttonAlpha -= FRACUNIT/10; + if (BackbuttonAlpha < 0) BackbuttonAlpha = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Drawer (void) +{ + player_t *player = &players[consoleplayer]; + AActor *camera = player->camera; + PalEntry fade = 0; + + if (!screen->Accel2D && camera != NULL && (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)) + { + if (camera->player != NULL) + { + player = camera->player; + } + fade = PalEntry (BYTE(player->BlendA*255), BYTE(player->BlendR*255), BYTE(player->BlendG*255), BYTE(player->BlendB*255)); + } + + + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + { + if (DMenu::CurrentMenu->DimAllowed()) screen->Dim(fade); + DMenu::CurrentMenu->Drawer(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_ClearMenus () +{ + M_DemoNoPlay = false; + if (DMenu::CurrentMenu != NULL) + { + DMenu::CurrentMenu->Destroy(); + DMenu::CurrentMenu = NULL; + } + BorderNeedRefresh = screen->GetPageCount (); + menuactive = MENU_Off; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Init (void) +{ + M_ParseMenuDefs(); + M_CreateMenus(); +} + + +//============================================================================= +// +// [RH] Most menus can now be accessed directly +// through console commands. +// +//============================================================================= + +CCMD (menu_main) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); +} + +CCMD (menu_load) +{ // F3 + M_StartControlPanel (true); + M_SetMenu(NAME_Loadgamemenu, -1); +} + +CCMD (menu_save) +{ // F2 + M_StartControlPanel (true); + M_SetMenu(NAME_Savegamemenu, -1); +} + +CCMD (menu_help) +{ // F1 + M_StartControlPanel (true); + M_SetMenu(NAME_Readthismenu, -1); +} + +CCMD (menu_game) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playerclassmenu, -1); // The playerclass menu is the first in the 'start game' chain +} + +CCMD (menu_options) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Optionsmenu, -1); +} + +CCMD (menu_player) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playermenu, -1); +} + +CCMD (menu_messages) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MessageOptions, -1); +} + +CCMD (menu_automap) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AutomapOptions, -1); +} + +CCMD (menu_scoreboard) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_ScoreboardOptions, -1); +} + +CCMD (menu_mapcolors) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MapColorMenu, -1); +} + +CCMD (menu_keys) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CustomizeControls, -1); +} + +CCMD (menu_gameplay) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_GameplayOptions, -1); +} + +CCMD (menu_compatibility) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CompatibilityOptions, -1); +} + +CCMD (menu_mouse) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MouseOptions, -1); +} + +CCMD (menu_joystick) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_JoystickOptions, -1); +} + +CCMD (menu_sound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_SoundOptions, -1); +} + +CCMD (menu_advsound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AdvSoundOptions, -1); +} + +CCMD (menu_modreplayer) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_ModReplayerOptions, -1); +} + +CCMD (menu_display) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoOptions, -1); +} + +CCMD (menu_video) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoModeMenu, -1); +} + + + +CCMD (openmenu) +{ + if (argv.argc() < 2) + { + Printf("Usage: openmenu \"menu_name\""); + return; + } + M_StartControlPanel (true); + M_SetMenu(argv[1], -1); +} + +// +// Toggle messages on/off +// +CCMD (togglemessages) +{ + if (show_messages) + { + Printf (128, "%s\n", GStrings("MSGOFF")); + show_messages = false; + } + else + { + Printf (128, "%s\n", GStrings("MSGON")); + show_messages = true; + } +} + +EXTERN_CVAR (Int, screenblocks) + +CCMD (sizedown) +{ + screenblocks = screenblocks - 1; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD (sizeup) +{ + screenblocks = screenblocks + 1; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD(menuconsole) +{ + M_ClearMenus(); + C_ToggleConsole(); +} + +CCMD(reset2defaults) +{ + C_SetDefaultBindings (); + C_SetCVarsToDefaults (); + R_SetViewSize (screenblocks); +} + +CCMD(reset2saved) +{ + GameConfig->DoGlobalSetup (); + GameConfig->DoGameSetup (GameNames[gameinfo.gametype]); + R_SetViewSize (screenblocks); +} diff --git a/src/menu/menu.h b/src/menu/menu.h new file mode 100644 index 0000000000..b1073a0410 --- /dev/null +++ b/src/menu/menu.h @@ -0,0 +1,652 @@ +#ifndef __M_MENU_MENU_H__ +#define __M_MENU_MENU_H__ + + + + +#include "dobject.h" +#include "lists.h" +#include "d_player.h" +#include "r_translate.h" +#include "c_cvars.h" +#include "v_font.h" +#include "version.h" +#include "textures/textures.h" + +EXTERN_CVAR(Float, snd_menuvolume) +EXTERN_CVAR(Int, m_use_mouse); + + +struct event_t; +class FTexture; +class FFont; +enum EColorRange; +class FPlayerClass; +class FKeyBindings; + +enum EMenuKey +{ + MKEY_Up, + MKEY_Down, + MKEY_Left, + MKEY_Right, + MKEY_PageUp, + MKEY_PageDown, + //----------------- Keys past here do not repeat. + MKEY_Enter, + MKEY_Back, // Back to previous menu + MKEY_Clear, // Clear keybinding/flip player sprite preview + NUM_MKEYS, + + // These are not buttons but events sent from other menus + + MKEY_Input, // Sent when input is confirmed + MKEY_Abort, // Input aborted + MKEY_MBYes, + MKEY_MBNo, +}; + + +struct FGameStartup +{ + const char *PlayerClass; + int Episode; + int Skill; +}; + +extern FGameStartup GameStartupInfo; + +struct FSaveGameNode : public Node +{ + char Title[SAVESTRINGSIZE]; + FString Filename; + bool bOldVersion; + bool bMissingWads; + bool bNoDelete; + + FSaveGameNode() { bNoDelete = false; } +}; + + + +//============================================================================= +// +// menu descriptor. This is created from the menu definition lump +// Items must be inserted in the order they are cycled through with the cursor +// +//============================================================================= + +enum EMenuDescriptorType +{ + MDESC_ListMenu, + MDESC_OptionsMenu, +}; + +struct FMenuDescriptor +{ + FName mMenuName; + FString mNetgameMessage; + int mType; + + virtual ~FMenuDescriptor() {} +}; + +class FListMenuItem; +class FOptionMenuItem; + +struct FListMenuDescriptor : public FMenuDescriptor +{ + TDeletingArray mItems; + int mSelectedItem; + int mSelectOfsX; + int mSelectOfsY; + FTextureID mSelector; + int mDisplayTop; + int mXpos, mYpos; + int mWLeft, mWRight; + int mLinespacing; // needs to be stored for dynamically created menus + int mAutoselect; // this can only be set by internal menu creation functions + FFont *mFont; + EColorRange mFontColor; + EColorRange mFontColor2; + const PClass *mClass; + FMenuDescriptor *mRedirect; // used to redirect overlong skill and episode menus to option menu based alternatives +}; + +struct FOptionMenuSettings +{ + EColorRange mTitleColor; + EColorRange mFontColor; + EColorRange mFontColorValue; + EColorRange mFontColorMore; + EColorRange mFontColorHeader; + EColorRange mFontColorHighlight; + EColorRange mFontColorSelection; + int mLinespacing; + int mLabelOffset; +}; + +struct FOptionMenuDescriptor : public FMenuDescriptor +{ + TDeletingArray mItems; + FString mTitle; + int mSelectedItem; + int mDrawTop; + int mScrollTop; + int mScrollPos; + int mIndent; + int mPosition; + bool mDontDim; + const PClass *mClass; + + void CalcIndent(); + FOptionMenuItem *GetItem(FName name); + +}; + + +typedef TMap MenuDescriptorList; + +extern FOptionMenuSettings OptionSettings; +extern MenuDescriptorList MenuDescriptors; + +#define CURSORSPACE (14 * CleanXfac_1) + +//============================================================================= +// +// +// +//============================================================================= + +struct FMenuRect +{ + int x, y; + int width, height; + + void set(int _x, int _y, int _w, int _h) + { + x = _x; + y = _y; + width = _w; + height = _h; + } + + bool inside(int _x, int _y) + { + return _x >= x && _x < x+width && _y >= y && _y < y+height; + } + +}; + + +class DMenu : public DObject +{ + DECLARE_CLASS (DMenu, DObject) + HAS_OBJECT_POINTERS + +protected: + bool mMouseCapture; + bool mBackbuttonSelected; + +public: + enum + { + MOUSE_Click, + MOUSE_Move, + MOUSE_Release + }; + + enum + { + BACKBUTTON_TIME = 4*TICRATE + }; + + static DMenu *CurrentMenu; + static int MenuTime; + + TObjPtr mParentMenu; + + DMenu(DMenu *parent = NULL); + virtual bool Responder (event_t *ev); + virtual bool MenuEvent (int mkey, bool fromcontroller); + virtual void Ticker (); + virtual void Drawer (); + virtual bool DimAllowed (); + virtual bool TranslateKeyboardEvents(); + virtual void Close(); + virtual bool MouseEvent(int type, int x, int y); + bool MouseEventBack(int type, int x, int y); + void SetCapture(); + void ReleaseCapture(); + bool HasCapture() + { + return mMouseCapture; + } +}; + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +class FListMenuItem +{ +protected: + int mXpos, mYpos; + FName mAction; + +public: + bool mEnabled; + + FListMenuItem(int xpos = 0, int ypos = 0, FName action = NAME_None) + { + mXpos = xpos; + mYpos = ypos; + mAction = action; + mEnabled = true; + } + + virtual ~FListMenuItem(); + + virtual bool CheckCoordinate(int x, int y); + virtual void Ticker(); + virtual void Drawer(bool selected); + virtual bool Selectable(); + virtual bool Activate(); + virtual FName GetAction(int *pparam); + virtual bool SetString(int i, const char *s); + virtual bool GetString(int i, char *s, int len); + virtual bool SetValue(int i, int value); + virtual bool GetValue(int i, int *pvalue); + virtual void Enable(bool on); + virtual bool MenuEvent (int mkey, bool fromcontroller); + virtual bool MouseEvent(int type, int x, int y); + virtual bool CheckHotkey(int c); + void DrawSelector(int xofs, int yofs, FTextureID tex); + void OffsetPositionY(int ydelta) { mYpos += ydelta; } + int GetY() { return mYpos; } +}; + +class FListMenuItemStaticPatch : public FListMenuItem +{ +protected: + FTextureID mTexture; + bool mCentered; + +public: + FListMenuItemStaticPatch(int x, int y, FTextureID patch, bool centered); + void Drawer(bool selected); +}; + +class FListMenuItemStaticText : public FListMenuItem +{ +protected: + const char *mText; + FFont *mFont; + EColorRange mColor; + bool mCentered; + +public: + FListMenuItemStaticText(int x, int y, const char *text, FFont *font, EColorRange color, bool centered); + ~FListMenuItemStaticText(); + void Drawer(bool selected); +}; + +//============================================================================= +// +// the player sprite window +// +//============================================================================= + +class FListMenuItemPlayerDisplay : public FListMenuItem +{ + FListMenuDescriptor *mOwner; + FTexture *mBackdrop; + FRemapTable mRemap; + FPlayerClass *mPlayerClass; + FState *mPlayerState; + int mPlayerTics; + bool mNoportrait; + BYTE mRotation; + BYTE mMode; // 0: automatic (used by class selection), 1: manual (used by player setup) + BYTE mTranslate; + int mSkin; + int mRandomClass; + int mRandomTimer; + int mClassNum; + + void SetPlayerClass(int classnum, bool force = false); + bool UpdatePlayerClass(); + void UpdateRandomClass(); + +public: + + enum + { + PDF_ROTATION = 0x10001, + PDF_SKIN = 0x10002, + PDF_CLASS = 0x10003, + PDF_MODE = 0x10004, + PDF_TRANSLATE = 0x10005, + }; + + FListMenuItemPlayerDisplay(FListMenuDescriptor *menu, int x, int y, PalEntry c1, PalEntry c2, bool np, FName action); + ~FListMenuItemPlayerDisplay(); + virtual void Ticker(); + virtual void Drawer(bool selected); + bool SetValue(int i, int value); +}; + + +//============================================================================= +// +// selectable items +// +//============================================================================= + +class FListMenuItemSelectable : public FListMenuItem +{ +protected: + int mHotkey; + int mHeight; + int mParam; + +public: + FListMenuItemSelectable(int x, int y, int height, FName childmenu, int mParam = -1); + bool CheckCoordinate(int x, int y); + bool Selectable(); + bool CheckHotkey(int c); + bool Activate(); + bool MouseEvent(int type, int x, int y); + FName GetAction(int *pparam); +}; + +class FListMenuItemText : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mColor; +public: + FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param = 0); + ~FListMenuItemText(); + void Drawer(bool selected); +}; + +class FListMenuItemPatch : public FListMenuItemSelectable +{ + FTextureID mTexture; +public: + FListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID patch, FName child, int param = 0); + void Drawer(bool selected); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FPlayerNameBox : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mFontColor; + int mFrameSize; + char mPlayerName[MAXPLAYERNAME+1]; + char mEditName[MAXPLAYERNAME+2]; + bool mEntering; + + void DrawBorder (int x, int y, int len); + +public: + + FPlayerNameBox(int x, int y, int height, int frameofs, const char *text, FFont *font, EColorRange color, FName action); + ~FPlayerNameBox(); + bool SetString(int i, const char *s); + bool GetString(int i, char *s, int len); + void Drawer(bool selected); + bool MenuEvent (int mkey, bool fromcontroller); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FValueTextItem : public FListMenuItemSelectable +{ + TArray mSelections; + const char *mText; + int mSelection; + FFont *mFont; + EColorRange mFontColor; + EColorRange mFontColor2; + +public: + + FValueTextItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, EColorRange valuecolor, FName action, FName values); + ~FValueTextItem(); + bool SetString(int i, const char *s); + bool SetValue(int i, int value); + bool GetValue(int i, int *pvalue); + bool MenuEvent (int mkey, bool fromcontroller); + void Drawer(bool selected); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FSliderItem : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mFontColor; + int mMinrange, mMaxrange; + int mStep; + int mSelection; + + void DrawSlider (int x, int y); + +public: + + FSliderItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, FName action, int min, int max, int step); + ~FSliderItem(); + bool SetValue(int i, int value); + bool GetValue(int i, int *pvalue); + bool MenuEvent (int mkey, bool fromcontroller); + void Drawer(bool selected); + bool MouseEvent(int type, int x, int y); +}; + +//============================================================================= +// +// list menu class runs a menu described by a FListMenuDescriptor +// +//============================================================================= + +class DListMenu : public DMenu +{ + DECLARE_CLASS(DListMenu, DMenu) + +protected: + FListMenuDescriptor *mDesc; + FListMenuItem *mFocusControl; + +public: + DListMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + virtual void Init(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + FListMenuItem *GetItem(FName name); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); + void SetFocus(FListMenuItem *fc) + { + mFocusControl = fc; + } + bool CheckFocus(FListMenuItem *fc) + { + return mFocusControl == fc; + } + void ReleaseFocus() + { + mFocusControl = NULL; + } +}; + + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +class FOptionMenuItem : public FListMenuItem +{ +protected: + char *mLabel; + bool mCentered; + + void drawLabel(int indent, int y, EColorRange color, bool grayed = false); +public: + + FOptionMenuItem(const char *text, FName action = NAME_None, bool center = false) + : FListMenuItem(0, 0, action) + { + mLabel = copystring(text); + mCentered = center; + } + + ~FOptionMenuItem(); + virtual bool CheckCoordinate(FOptionMenuDescriptor *desc, int x, int y); + virtual int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected); + virtual bool Selectable(); + virtual int GetIndent(); + virtual bool MouseEvent(int type, int x, int y); +}; + +//============================================================================= +// +// +// +//============================================================================= +struct FOptionValues +{ + struct Pair + { + double Value; + FString TextValue; + FString Text; + }; + + TArray mValues; +}; + +typedef TMap< FName, FOptionValues* > FOptionMap; + +extern FOptionMap OptionValues; + + +//============================================================================= +// +// Option menu class runs a menu described by a FOptionMenuDescriptor +// +//============================================================================= + +class DOptionMenu : public DMenu +{ + DECLARE_CLASS(DOptionMenu, DMenu) + + bool CanScrollUp; + bool CanScrollDown; + int VisBottom; + FOptionMenuItem *mFocusControl; + +protected: + FOptionMenuDescriptor *mDesc; + +public: + FOptionMenuItem *GetItem(FName name); + DOptionMenu(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); + virtual void Init(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); + const FOptionMenuDescriptor *GetDescriptor() const { return mDesc; } + void SetFocus(FOptionMenuItem *fc) + { + mFocusControl = fc; + } + bool CheckFocus(FOptionMenuItem *fc) + { + return mFocusControl == fc; + } + void ReleaseFocus() + { + mFocusControl = NULL; + } +}; + + +//============================================================================= +// +// Input some text +// +//============================================================================= + +class DTextEnterMenu : public DMenu +{ + DECLARE_ABSTRACT_CLASS(DTextEnterMenu, DMenu) + + char *mEnterString; + unsigned int mEnterSize; + unsigned int mEnterPos; + int mSizeMode; // 1: size is length in chars. 2: also check string width + bool mInputGridOkay; + + int InputGridX; + int InputGridY; + +public: + + DTextEnterMenu(DMenu *parent, char *textbuffer, int maxlen, int sizemode, bool showgrid); + + void Drawer (); + bool MenuEvent (int mkey, bool fromcontroller); + bool Responder(event_t *ev); + bool TranslateKeyboardEvents(); + bool MouseEvent(int type, int x, int y); + +}; + + + + +struct event_t; +bool M_Responder (event_t *ev); +void M_Ticker (void); +void M_Drawer (void); +void M_Init (void); +void M_CreateMenus(); +void M_ActivateMenu(DMenu *menu); +void M_ClearMenus (); +void M_ParseMenuDefs(); +void M_StartupSkillMenu(FGameStartup *gs); +void M_StartControlPanel (bool makeSound); +void M_SetMenu(FName menu, int param = -1); +void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); +void M_StartMessage(const char *message, int messagemode, FName action = NAME_None); +DMenu *StartPickerMenu(DMenu *parent, const char *name, FColorCVar *cvar); +void M_RefreshModesList (); +void M_InitVideoModesMenu (); + + +#endif \ No newline at end of file diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp new file mode 100644 index 0000000000..31fe0fd3b5 --- /dev/null +++ b/src/menu/menudef.cpp @@ -0,0 +1,1389 @@ +/* +** menudef.cpp +** MENUDEF parser amd menu generation code +** +**--------------------------------------------------------------------------- +** Copyright 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 + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" +#include "gi.h" + +#include "optionmenuitems.h" + +MenuDescriptorList MenuDescriptors; +static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus +static FOptionMenuDescriptor DefaultOptionMenuSettings; // contains common settings for all Option menus +FOptionMenuSettings OptionSettings; +FOptionMap OptionValues; + +static void DeinitMenus() +{ + { + MenuDescriptorList::Iterator it(MenuDescriptors); + + MenuDescriptorList::Pair *pair; + + while (it.NextPair(pair)) + { + delete pair->Value; + pair->Value = NULL; + } + } + + { + FOptionMap::Iterator it(OptionValues); + + FOptionMap::Pair *pair; + + while (it.NextPair(pair)) + { + delete pair->Value; + pair->Value = NULL; + } + } + DMenu::CurrentMenu = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +static void SkipSubBlock(FScanner &sc) +{ + sc.MustGetStringName("{"); + int depth = 1; + while (depth > 0) + { + sc.MustGetString(); + if (sc.Compare("{")) depth++; + if (sc.Compare("}")) depth--; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool CheckSkipGameBlock(FScanner &sc) +{ + int filter = 0; + sc.MustGetStringName("("); + do + { + sc.MustGetString(); + if (sc.Compare("Doom")) filter |= GAME_Doom; + if (sc.Compare("Heretic")) filter |= GAME_Heretic; + if (sc.Compare("Hexen")) filter |= GAME_Hexen; + if (sc.Compare("Strife")) filter |= GAME_Strife; + if (sc.Compare("Chex")) filter |= GAME_Chex; + } + while (sc.CheckString(",")); + sc.MustGetStringName(")"); + if (!(gameinfo.gametype & filter)) + { + SkipSubBlock(sc); + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool CheckSkipOptionBlock(FScanner &sc) +{ + bool filter = false; + sc.MustGetStringName("("); + do + { + sc.MustGetString(); + if (sc.Compare("ReadThis")) filter |= gameinfo.drawreadthis; + else if (sc.Compare("Windows")) + { + #ifdef _WIN32 + filter = true; + #endif + } + else if (sc.Compare("unix")) + { + #ifdef unix + filter = true; + #endif + } + else if (sc.Compare("Mac")) + { + #ifdef __APPLE__ + filter = true; + #endif + } + } + while (sc.CheckString(",")); + sc.MustGetStringName(")"); + if (!filter) + { + SkipSubBlock(sc); + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseListMenuBody(sc, desc); + } + } + else if (sc.Compare("ifoption")) + { + if (!CheckSkipOptionBlock(sc)) + { + // recursively parse sub-block + ParseListMenuBody(sc, desc); + } + } + else if (sc.Compare("Class")) + { + sc.MustGetString(); + const PClass *cls = PClass::FindClass(sc.String); + if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DListMenu))) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; + } + else if (sc.Compare("Selector")) + { + sc.MustGetString(); + desc->mSelector = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mSelectOfsX = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mSelectOfsY = sc.Number; + } + else if (sc.Compare("Linespacing")) + { + sc.MustGetNumber(); + desc->mLinespacing = sc.Number; + } + else if (sc.Compare("Position")) + { + sc.MustGetNumber(); + desc->mXpos = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mYpos = sc.Number; + } + else if (sc.Compare("MouseWindow")) + { + sc.MustGetNumber(); + desc->mWLeft = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mWRight = sc.Number; + } + else if (sc.Compare("StaticPatch") || sc.Compare("StaticPatchCentered")) + { + bool centered = sc.Compare("StaticPatchCentered"); + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + + FListMenuItem *it = new FListMenuItemStaticPatch(x, y, tex, centered); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticText") || sc.Compare("StaticTextCentered")) + { + bool centered = sc.Compare("StaticTextCentered"); + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FListMenuItem *it = new FListMenuItemStaticText(x, y, sc.String, desc->mFont, desc->mFontColor, centered); + desc->mItems.Push(it); + } + else if (sc.Compare("PatchItem")) + { + sc.MustGetString(); + FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + sc.MustGetStringName(","); + sc.MustGetString(); + int hotkey = sc.String[0]; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + int param = 0; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + param = sc.Number; + } + + FListMenuItem *it = new FListMenuItemPatch(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, tex, action, param); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("TextItem")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + int hotkey = sc.String[0]; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + int param = 0; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + param = sc.Number; + } + + FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, action, param); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + + } + else if (sc.Compare("Font")) + { + sc.MustGetString(); + FFont *newfont = V_GetFont(sc.String); + if (newfont != NULL) desc->mFont = newfont; + if (sc.CheckString(",")) + { + sc.MustGetString(); + desc->mFontColor2 = desc->mFontColor = V_FindFontColor((FName)sc.String); + if (sc.CheckString(",")) + { + sc.MustGetString(); + desc->mFontColor2 = V_FindFontColor((FName)sc.String); + } + } + else + { + desc->mFontColor = OptionSettings.mFontColor; + desc->mFontColor2 = OptionSettings.mFontColorValue; + } + } + else if (sc.Compare("NetgameMessage")) + { + sc.MustGetString(); + desc->mNetgameMessage = sc.String; + } + else if (sc.Compare("PlayerDisplay")) + { + bool noportrait = false; + FName action = NAME_None; + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + PalEntry c1 = V_GetColor(NULL, sc.String); + sc.MustGetStringName(","); + sc.MustGetString(); + PalEntry c2 = V_GetColor(NULL, sc.String); + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + noportrait = !!sc.Number; + if (sc.CheckString(",")) + { + sc.MustGetString(); + action = sc.String; + } + } + FListMenuItemPlayerDisplay *it = new FListMenuItemPlayerDisplay(desc, x, y, c1, c2, noportrait, action); + desc->mItems.Push(it); + } + else if (sc.Compare("PlayerNameBox")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int ofs = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FListMenuItem *it = new FPlayerNameBox(desc->mXpos, desc->mYpos, desc->mLinespacing, ofs, text, desc->mFont, desc->mFontColor, sc.String); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("ValueText")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + FName values; + if (sc.CheckString(",")) + { + sc.MustGetString(); + values = sc.String; + } + FListMenuItem *it = new FValueTextItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, values); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("Slider")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString action = sc.String; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int min = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int max = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int step = sc.Number; + FListMenuItem *it = new FSliderItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, action, min, max, step); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseListMenu(FScanner &sc) +{ + sc.MustGetString(); + + FListMenuDescriptor *desc = new FListMenuDescriptor; + desc->mType = MDESC_ListMenu; + desc->mMenuName = sc.String; + desc->mSelectedItem = -1; + desc->mAutoselect = -1; + desc->mSelectOfsX = DefaultListMenuSettings.mSelectOfsX; + desc->mSelectOfsY = DefaultListMenuSettings.mSelectOfsY; + desc->mSelector = DefaultListMenuSettings.mSelector; + desc->mDisplayTop = DefaultListMenuSettings.mDisplayTop; + desc->mXpos = DefaultListMenuSettings.mXpos; + desc->mYpos = DefaultListMenuSettings.mYpos; + desc->mLinespacing = DefaultListMenuSettings.mLinespacing; + desc->mNetgameMessage = DefaultListMenuSettings.mNetgameMessage; + desc->mFont = DefaultListMenuSettings.mFont; + desc->mFontColor = DefaultListMenuSettings.mFontColor; + desc->mFontColor2 = DefaultListMenuSettings.mFontColor2; + desc->mClass = NULL; + desc->mRedirect = NULL; + desc->mWLeft = 0; + desc->mWRight = 0; + + FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); + if (pOld != NULL && *pOld != NULL) delete *pOld; + MenuDescriptors[desc->mMenuName] = desc; + + ParseListMenuBody(sc, desc); +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionValue(FScanner &sc) +{ + FName optname; + + FOptionValues *val = new FOptionValues; + sc.MustGetString(); + optname = sc.String; + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)]; + sc.MustGetFloat(); + pair.Value = sc.Float; + sc.MustGetStringName(","); + sc.MustGetString(); + pair.Text = strbin1(sc.String); + } + FOptionValues **pOld = OptionValues.CheckKey(optname); + if (pOld != NULL && *pOld != NULL) delete *pOld; + OptionValues[optname] = val; +} + + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionString(FScanner &sc) +{ + FName optname; + + FOptionValues *val = new FOptionValues; + sc.MustGetString(); + optname = sc.String; + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)]; + sc.MustGetString(); + pair.Value = DBL_MAX; + pair.TextValue = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + pair.Text = strbin1(sc.String); + } + FOptionValues **pOld = OptionValues.CheckKey(optname); + if (pOld != NULL && *pOld != NULL) delete *pOld; + OptionValues[optname] = val; +} + + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionSettings(FScanner &sc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseOptionSettings(sc); + } + } + else if (sc.Compare("Linespacing")) + { + sc.MustGetNumber(); + OptionSettings.mLinespacing = sc.Number; + } + else if (sc.Compare("LabelOffset")) + { + sc.MustGetNumber(); + OptionSettings.mLabelOffset = sc.Number; + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseOptionMenuBody(sc, desc); + } + } + else if (sc.Compare("ifoption")) + { + if (!CheckSkipOptionBlock(sc)) + { + // recursively parse sub-block + ParseOptionMenuBody(sc, desc); + } + } + else if (sc.Compare("Class")) + { + sc.MustGetString(); + const PClass *cls = PClass::FindClass(sc.String); + if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DOptionMenu))) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; + } + else if (sc.Compare("Title")) + { + sc.MustGetString(); + desc->mTitle = sc.String; + } + else if (sc.Compare("Position")) + { + sc.MustGetNumber(); + desc->mPosition = sc.Number; + } + else if (sc.Compare("DefaultSelection")) + { + sc.MustGetNumber(); + desc->mSelectedItem = sc.Number; + } + else if (sc.Compare("ScrollTop")) + { + sc.MustGetNumber(); + desc->mScrollTop = sc.Number; + } + else if (sc.Compare("Indent")) + { + sc.MustGetNumber(); + desc->mIndent = sc.Number; + } + else if (sc.Compare("Submenu")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemSubmenu(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("Option")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString cvar = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString values = sc.String; + FString check; + int center = 0; + if (sc.CheckString(",")) + { + sc.MustGetString(); + if (*sc.String != 0) check = sc.String; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + center = sc.Number; + } + } + FOptionMenuItem *it = new FOptionMenuItemOption(label, cvar, values, check, center); + desc->mItems.Push(it); + } + else if (sc.Compare("Command")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemCommand(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("SafeCommand")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("Control") || sc.Compare("MapControl")) + { + bool map = sc.Compare("MapControl"); + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemControl(label, sc.String, map? &AutomapBindings : &Bindings); + desc->mItems.Push(it); + } + else if (sc.Compare("ColorPicker")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemColorPicker(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticText")) + { + sc.MustGetString(); + FString label = sc.String; + bool cr = false; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + cr = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuItemStaticText(label, cr); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticTextSwitchable")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString label2 = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + bool cr = false; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + cr = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuItemStaticTextSwitchable(label, label2, action, cr); + desc->mItems.Push(it); + } + else if (sc.Compare("Slider")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString action = sc.String; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double min = sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double max = sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double step = sc.Float; + bool showvalue = true; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + showvalue = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuSliderCVar(text, action, min, max, step, showvalue? 1:-1); + desc->mItems.Push(it); + } + else if (sc.Compare("screenresolution")) + { + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuScreenResolutionLine(sc.String); + desc->mItems.Push(it); + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionMenu(FScanner &sc) +{ + sc.MustGetString(); + + FOptionMenuDescriptor *desc = new FOptionMenuDescriptor; + desc->mType = MDESC_OptionsMenu; + desc->mMenuName = sc.String; + desc->mSelectedItem = -1; + desc->mScrollPos = 0; + desc->mClass = NULL; + desc->mPosition = DefaultOptionMenuSettings.mPosition; + desc->mScrollTop = DefaultOptionMenuSettings.mScrollTop; + desc->mIndent = DefaultOptionMenuSettings.mIndent; + desc->mDontDim = DefaultOptionMenuSettings.mDontDim; + + FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); + if (pOld != NULL && *pOld != NULL) delete *pOld; + MenuDescriptors[desc->mMenuName] = desc; + + ParseOptionMenuBody(sc, desc); + + if (desc->mIndent == 0) desc->CalcIndent(); +} + + +//============================================================================= +// +// +// +//============================================================================= + +void M_ParseMenuDefs() +{ + int lump, lastlump = 0; + + OptionSettings.mTitleColor = V_FindFontColor(gameinfo.mTitleColor); + OptionSettings.mFontColor = V_FindFontColor(gameinfo.mFontColor); + OptionSettings.mFontColorValue = V_FindFontColor(gameinfo.mFontColorValue); + OptionSettings.mFontColorMore = V_FindFontColor(gameinfo.mFontColorMore); + OptionSettings.mFontColorHeader = V_FindFontColor(gameinfo.mFontColorHeader); + OptionSettings.mFontColorHighlight = V_FindFontColor(gameinfo.mFontColorHighlight); + OptionSettings.mFontColorSelection = V_FindFontColor(gameinfo.mFontColorSelection); + + atterm( DeinitMenus); + while ((lump = Wads.FindLump ("MENUDEF", &lastlump)) != -1) + { + FScanner sc(lump); + + sc.SetCMode(true); + while (sc.GetString()) + { + if (sc.Compare("LISTMENU")) + { + ParseListMenu(sc); + } + else if (sc.Compare("DEFAULTLISTMENU")) + { + ParseListMenuBody(sc, &DefaultListMenuSettings); + } + else if (sc.Compare("OPTIONVALUE")) + { + ParseOptionValue(sc); + } + else if (sc.Compare("OPTIONSTRING")) + { + ParseOptionString(sc); + } + else if (sc.Compare("OPTIONMENUSETTINGS")) + { + ParseOptionSettings(sc); + } + else if (sc.Compare("OPTIONMENU")) + { + ParseOptionMenu(sc); + } + else if (sc.Compare("DEFAULTOPTIONMENU")) + { + ParseOptionMenuBody(sc, &DefaultOptionMenuSettings); + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } + } +} + + +//============================================================================= +// +// Creates the episode menu +// Falls back on an option menu if there's not enough screen space to show all episodes +// +//============================================================================= + +static void BuildEpisodeMenu() +{ + // Build episode menu + bool success = false; + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + int posy = ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + AllEpisodes.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || AllEpisodes.Size() == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy -= topdelta; + } + + ld->mSelectedItem = ld->mItems.Size(); + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + FListMenuItem *it; + if (AllEpisodes[i].mPicName.IsNotEmpty()) + { + FTextureID tex = TexMan.CheckForTexture(AllEpisodes[i].mPicName, FTexture::TEX_MiscPatch); + it = new FListMenuItemPatch(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, + tex, NAME_Skillmenu, i); + } + else + { + it = new FListMenuItemText(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, + AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, NAME_Skillmenu, i); + } + ld->mItems.Push(it); + posy += ld->mLinespacing; + } + if (AllEpisodes.Size() == 1) + { + ld->mAutoselect = ld->mSelectedItem; + } + success = true; + } + } + } + if (!success) + { + // Couldn't create the episode menu, either because there's too many episodes or some error occured + // Create an option menu for episode selection instead. + FOptionMenuDescriptor *od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Episodemenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Episodemenu; + od->mTitle = "$MNU_EPISODE"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i); + od->mItems.Push(it); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void BuildPlayerclassMenu() +{ + bool success = false; + + // Build player class menu + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + // add player display + ld->mSelectedItem = ld->mItems.Size(); + + int posy = ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // Count the number of items this menu will show + int numclassitems = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + numclassitems++; + } + } + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + (numclassitems+1) * ld->mLinespacing - topy; + + if (totalheight <= 190 || numclassitems == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy -= topdelta; + } + + int n = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, i); + ld->mItems.Push(it); + ld->mYpos += ld->mLinespacing; + n++; + } + } + } + if (n > 1) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r', + "$MNU_RANDOM", ld->mFont,ld->mFontColor, NAME_Episodemenu, -1); + ld->mItems.Push(it); + } + if (n == 0) + { + const char *pname = PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, 0); + ld->mItems.Push(it); + } + } + if (n < 2) + { + ld->mAutoselect = ld->mSelectedItem; + } + success = true; + } + } + } + if (!success) + { + // Couldn't create the playerclass menu, either because there's too many episodes or some error occured + // Create an option menu for class selection instead. + FOptionMenuDescriptor *od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Playerclassmenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Playerclassmenu; + od->mTitle = "$MNU_CHOOSECLASS"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + od->mNetgameMessage = "$NETGAME"; + + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(pname, "Episodemenu", i); + od->mItems.Push(it); + } + } + } + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu("Random", "Episodemenu", -1); + od->mItems.Push(it); + } +} + +//============================================================================= +// +// Reads any XHAIRS lumps for the names of crosshairs and +// adds them to the display options menu. +// +//============================================================================= + +static void InitCrosshairsList() +{ + int lastlump, lump; + + lastlump = 0; + + FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs); + if (opt == NULL) + { + return; // no crosshair value list present. No need to go on. + } + + FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)]; + pair->Value = 0; + pair->Text = "None"; + + while ((lump = Wads.FindLump("XHAIRS", &lastlump)) != -1) + { + FScanner sc(lump); + while (sc.GetNumber()) + { + FOptionValues::Pair value; + value.Value = sc.Number; + sc.MustGetString(); + value.Text = sc.String; + if (value.Value != 0) + { // Check if it already exists. If not, add it. + unsigned int i; + + for (i = 1; i < (*opt)->mValues.Size(); ++i) + { + if ((*opt)->mValues[i].Value == value.Value) + { + break; + } + } + if (i < (*opt)->mValues.Size()) + { + (*opt)->mValues[i].Text = value.Text; + } + else + { + (*opt)->mValues.Push(value); + } + } + } + } +} + +//============================================================================= +// +// With the current workings of the menu system this cannot be done any longer +// from within the respective CCMDs. +// +//============================================================================= + +static void InitKeySections() +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_CustomizeControls); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *menu = static_cast(*desc); + + for (unsigned i = 0; i < KeySections.Size(); i++) + { + FKeySection *sect = &KeySections[i]; + FOptionMenuItem *item = new FOptionMenuItemStaticText(" ", false); + menu->mItems.Push(item); + item = new FOptionMenuItemStaticText(sect->mTitle, true); + menu->mItems.Push(item); + for (unsigned j = 0; j < sect->mActions.Size(); j++) + { + FKeyAction *act = §->mActions[j]; + item = new FOptionMenuItemControl(act->mTitle, act->mAction, &Bindings); + menu->mItems.Push(item); + } + } + } + } +} + +//============================================================================= +// +// Special menus will be created once all engine data is loaded +// +//============================================================================= + +void M_CreateMenus() +{ + BuildEpisodeMenu(); + BuildPlayerclassMenu(); + InitCrosshairsList(); + InitKeySections(); + UpdateJoystickMenu(NULL); + + FOptionValues **opt = OptionValues.CheckKey(NAME_Mididevices); + if (opt != NULL) + { + I_BuildMIDIMenuList(*opt); + } +} + +//============================================================================= +// +// THe skill menu must be refeshed each time it starts up +// +//============================================================================= + +void M_StartupSkillMenu(FGameStartup *gs) +{ + static bool done = false; + bool success = false; + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + int x = ld->mXpos; + int y = ld->mYpos; + if (gameinfo.gametype == GAME_Hexen) + { + // THere really needs to be a better way to do this... :( + if (gs->PlayerClass != NULL) + { + if (!stricmp(gs->PlayerClass, "fighter")) x = 120; + else if (!stricmp(gs->PlayerClass, "cleric")) x = 116; + else if (!stricmp(gs->PlayerClass, "mage")) x = 112; + } + } + + // Delete previous contents + for(unsigned i=0; imItems.Size(); i++) + { + FName n = ld->mItems[i]->GetAction(NULL); + if (n == NAME_Startgame || n == NAME_StartgameConfirm) + { + for(unsigned j=i; jmItems.Size(); j++) + { + delete ld->mItems[j]; + } + ld->mItems.Resize(i); + break; + } + } + + if (!done) + { + done = true; + int defskill = DefaultSkill; + if ((unsigned int)defskill >= AllSkills.Size()) + { + defskill = (AllSkills.Size() - 1) / 2; + } + ld->mSelectedItem = ld->mItems.Size() + defskill; + + int posy = y; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + AllSkills.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || AllSkills.Size() == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + y = ld->mYpos = posy - topdelta; + } + } + else + { + // too large + delete ld; + desc = NULL; + done = false; + goto fail; + } + } + + unsigned firstitem = ld->mItems.Size(); + for(unsigned int i = 0; i < AllSkills.Size(); i++) + { + FSkillInfo &skill = AllSkills[i]; + FListMenuItem *li; + // Using a different name for skills that must be confirmed makes handling this easier. + FName action = skill.MustConfirm? NAME_StartgameConfirm : NAME_Startgame; + + FString *pItemText = NULL; + if (gs->PlayerClass != NULL) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + + if (skill.PicName.Len() != 0 && pItemText == NULL) + { + FTextureID tex = TexMan.CheckForTexture(skill.PicName, FTexture::TEX_MiscPatch); + li = new FListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i); + } + else + { + EColorRange color = (EColorRange)skill.GetTextColor(); + if (color == CR_UNTRANSLATED) color = ld->mFontColor; + li = new FListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut, + pItemText? *pItemText : skill.MenuName, ld->mFont, color, action, i); + } + ld->mItems.Push(li); + y += ld->mLinespacing; + } + if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1) + { + ld->mAutoselect = MIN(2u, AllEpisodes.Size()-1); + } + success = true; + } + } + if (success) return; +fail: + // Option menu fallback for overlong skill lists + FOptionMenuDescriptor *od; + if (desc == NULL) + { + od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Skillmenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Skillmenu; + od->mTitle = "$MNU_CHOOSESKILL"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + } + else + { + od = static_cast(*desc); + for(unsigned i=0;imItems.Size(); i++) + { + delete od->mItems[i]; + } + od->mItems.Clear(); + } + for(unsigned int i = 0; i < AllSkills.Size(); i++) + { + FSkillInfo &skill = AllSkills[i]; + FOptionMenuItem *li; + // Using a different name for skills that must be confirmed makes handling this easier. + const char *action = skill.MustConfirm? "StartgameConfirm" : "Startgame"; + + FString *pItemText = NULL; + if (gs->PlayerClass != NULL) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + li = new FOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i); + od->mItems.Push(li); + if (!done) + { + done = true; + int defskill = DefaultSkill; + if ((unsigned int)defskill >= AllSkills.Size()) + { + defskill = (AllSkills.Size() - 1) / 2; + } + od->mSelectedItem = defskill; + } + } +} diff --git a/src/menu/menuinput.cpp b/src/menu/menuinput.cpp new file mode 100644 index 0000000000..0f35b7e1f5 --- /dev/null +++ b/src/menu/menuinput.cpp @@ -0,0 +1,366 @@ +/* +** menuinput.cpp +** The string input code +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 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 "menu/menu.h" +#include "v_video.h" +#include "c_cvars.h" +#include "d_event.h" +#include "d_gui.h" +#include "v_font.h" +#include "v_palette.h" + +IMPLEMENT_ABSTRACT_CLASS(DTextEnterMenu) + +#define INPUTGRID_WIDTH 13 +#define INPUTGRID_HEIGHT 5 + +// Heretic and Hexen do not, by default, come with glyphs for all of these +// characters. Oh well. Doom and Strife do. +static const char InputGridChars[INPUTGRID_WIDTH * INPUTGRID_HEIGHT] = + "ABCDEFGHIJKLM" + "NOPQRSTUVWXYZ" + "0123456789+-=" + ".,!?@'\":;[]()" + "<>^#$%&*/_ \b"; + + +CVAR(Bool, m_showinputgrid, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +//============================================================================= +// +// +// +//============================================================================= + +DTextEnterMenu::DTextEnterMenu(DMenu *parent, char *textbuffer, int maxlen, int sizemode, bool showgrid) +: DMenu(parent) +{ + mEnterString = textbuffer; + mEnterSize = maxlen; + mEnterPos = (unsigned)strlen(textbuffer); + mSizeMode = sizemode; + mInputGridOkay = showgrid || m_showinputgrid; + if (mEnterPos > 0) + { + InputGridX = INPUTGRID_WIDTH - 1; + InputGridY = INPUTGRID_HEIGHT - 1; + } + else + { + // If we are naming a new save, don't start the cursor on "end". + InputGridX = 0; + InputGridY = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::TranslateKeyboardEvents() +{ + return mInputGridOkay; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::Responder(event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + // Save game and player name string input + if (ev->subtype == EV_GUI_Char) + { + mInputGridOkay = false; + if (mEnterPos < mEnterSize && + (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) + { + mEnterString[mEnterPos] = (char)ev->data1; + mEnterString[++mEnterPos] = 0; + } + return true; + } + char ch = (char)ev->data1; + if ((ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) && ch == '\b') + { + if (mEnterPos > 0) + { + mEnterPos--; + mEnterString[mEnterPos] = 0; + } + } + else if (ev->subtype == EV_GUI_KeyDown) + { + if (ch == GK_ESCAPE) + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Abort, false); + return true; + } + else if (ch == '\r') + { + if (mEnterString[0]) + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Input, false); + return true; + } + } + } + if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) + { + return true; + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::MouseEvent(int type, int x, int y) +{ + const int cell_width = 18 * CleanXfac; + const int cell_height = 12 * CleanYfac; + const int screen_y = screen->GetHeight() - INPUTGRID_HEIGHT * cell_height; + const int screen_x = (screen->GetWidth() - INPUTGRID_WIDTH * cell_width) / 2; + + if (x >= screen_x && x < screen_x + INPUTGRID_WIDTH * cell_width && y >= screen_y) + { + InputGridX = (x - screen_x) / cell_width; + InputGridY = (y - screen_y) / cell_height; + if (type == DMenu::MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + if (m_use_mouse == 2) InputGridX = InputGridY = -1; + return true; + } + } + } + else + { + InputGridX = InputGridY = -1; + } + return Super::MouseEvent(type, x, y); +} + + + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::MenuEvent (int key, bool fromcontroller) +{ + if (key == MKEY_Back) + { + mParentMenu->MenuEvent(MKEY_Abort, false); + return Super::MenuEvent(key, fromcontroller); + } + if (fromcontroller) + { + mInputGridOkay = true; + } + + if (mInputGridOkay) + { + int ch; + + if (InputGridX == -1 || InputGridY == -1) + { + InputGridX = InputGridY = 0; + } + switch (key) + { + case MKEY_Down: + InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Up: + InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Right: + InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Left: + InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Clear: + if (mEnterPos > 0) + { + mEnterString[--mEnterPos] = 0; + } + return true; + + case MKEY_Enter: + assert(unsigned(InputGridX) < INPUTGRID_WIDTH && unsigned(InputGridY) < INPUTGRID_HEIGHT); + if (mInputGridOkay) + { + ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; + if (ch == 0) // end + { + if (mEnterString[0] != '\0') + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Input, false); + return true; + } + } + else if (ch == '\b') // bs + { + if (mEnterPos > 0) + { + mEnterString[--mEnterPos] = 0; + } + } + else if (mEnterPos < mEnterSize && + (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) + { + mEnterString[mEnterPos] = ch; + mEnterString[++mEnterPos] = 0; + } + } + return true; + + default: + break; // Keep GCC quiet + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DTextEnterMenu::Drawer () +{ + mParentMenu->Drawer(); + if (mInputGridOkay) + { + const int cell_width = 18 * CleanXfac; + const int cell_height = 12 * CleanYfac; + const int top_padding = cell_height / 2 - SmallFont->GetHeight() * CleanYfac / 2; + + // Darken the background behind the character grid. + // Unless we frame it with a border, I think it looks better to extend the + // background across the full width of the screen. + screen->Dim(0, 0.8f, + 0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, + screen->GetHeight() - INPUTGRID_HEIGHT * cell_height, + screen->GetWidth() /*13 * cell_width*/, + INPUTGRID_HEIGHT * cell_height); + + if (InputGridX >= 0 && InputGridY >= 0) + { + // Highlight the background behind the selected character. + screen->Dim(MAKERGB(255,248,220), 0.6f, + InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2, + InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(), + cell_width, cell_height); + } + + for (int y = 0; y < INPUTGRID_HEIGHT; ++y) + { + const int yy = y * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(); + for (int x = 0; x < INPUTGRID_WIDTH; ++x) + { + int width; + const int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2; + const int ch = InputGridChars[y * INPUTGRID_WIDTH + x]; + FTexture *pic = SmallFont->GetChar(ch, &width); + EColorRange color; + FRemapTable *remap; + + // The highlighted character is yellow; the rest are dark gray. + color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; + remap = SmallFont->GetColorTranslation(color); + + if (pic != NULL) + { + // Draw a normal character. + screen->DrawTexture(pic, xx + cell_width/2 - width*CleanXfac/2, yy + top_padding, + DTA_Translation, remap, + DTA_CleanNoMove, true, + TAG_DONE); + } + else if (ch == ' ') + { + // Draw the space as a box outline. We also draw it 50% wider than it really is. + const int x1 = xx + cell_width/2 - width * CleanXfac * 3 / 4; + const int x2 = x1 + width * 3 * CleanXfac / 2; + const int y1 = yy + top_padding; + const int y2 = y1 + SmallFont->GetHeight() * CleanYfac; + const int palentry = remap->Remap[remap->NumEntries*2/3]; + const uint32 palcolor = remap->Palette[remap->NumEntries*2/3]; + screen->Clear(x1, y1, x2, y1+CleanYfac, palentry, palcolor); // top + screen->Clear(x1, y2, x2, y2+CleanYfac, palentry, palcolor); // bottom + screen->Clear(x1, y1+CleanYfac, x1+CleanXfac, y2, palentry, palcolor); // left + screen->Clear(x2-CleanXfac, y1+CleanYfac, x2, y2, palentry, palcolor); // right + } + else if (ch == '\b' || ch == 0) + { + // Draw the backspace and end "characters". + const char *const str = ch == '\b' ? "BS" : "ED"; + screen->DrawText(SmallFont, color, + xx + cell_width/2 - SmallFont->StringWidth(str)*CleanXfac/2, + yy + top_padding, str, DTA_CleanNoMove, true, TAG_DONE); + } + } + } + } + Super::Drawer(); +} \ No newline at end of file diff --git a/src/menu/messagebox.cpp b/src/menu/messagebox.cpp new file mode 100644 index 0000000000..bde298894d --- /dev/null +++ b/src/menu/messagebox.cpp @@ -0,0 +1,733 @@ +/* +** messagebox.cpp +** Confirmation, notification screns +** +**--------------------------------------------------------------------------- +** Copyright 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 "menu/menu.h" +#include "d_event.h" +#include "d_gui.h" +#include "v_video.h" +#include "v_text.h" +#include "d_main.h" +#include "gstrings.h" +#include "gi.h" +#include "i_video.h" +#include "st_start.h" +#include "c_dispatch.h" +#include "g_game.h" + + +extern FSaveGameNode *quickSaveSlot; + +class DMessageBoxMenu : public DMenu +{ + DECLARE_CLASS(DMessageBoxMenu, DMenu) + + FBrokenLines *mMessage; + int mMessageMode; + int messageSelection; + int mMouseLeft, mMouseRight, mMouseY; + FName mAction; + +public: + + DMessageBoxMenu(DMenu *parent = NULL, const char *message = NULL, int messagemode = 0, bool playsound = false, FName action = NAME_None); + void Destroy(); + void Init(DMenu *parent, const char *message, int messagemode, bool playsound = false); + void Drawer(); + bool Responder(event_t *ev); + bool MenuEvent(int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void CloseSound(); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DMessageBoxMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DMessageBoxMenu::DMessageBoxMenu(DMenu *parent, const char *message, int messagemode, bool playsound, FName action) +: DMenu(parent) +{ + mAction = action; + messageSelection = 0; + mMouseLeft = 140; + mMouseY = INT_MIN; + int mr1 = 170 + SmallFont->StringWidth(GStrings["TXT_YES"]); + int mr2 = 170 + SmallFont->StringWidth(GStrings["TXT_NO"]); + mMouseRight = MAX(mr1, mr2); + + Init(parent, message, messagemode, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Init(DMenu *parent, const char *message, int messagemode, bool playsound) +{ + mParentMenu = parent; + if (message != NULL) + { + if (*message == '$') message = GStrings(message+1); + mMessage = V_BreakLines(SmallFont, 300, message); + } + else mMessage = NULL; + mMessageMode = messagemode; + if (playsound) + { + S_StopSound (CHAN_VOICE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", snd_menuvolume, ATTN_NONE); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Destroy() +{ + if (mMessage != NULL) V_FreeBrokenLines(mMessage); + mMessage = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::CloseSound() +{ + S_Sound (CHAN_VOICE | CHAN_UI, + DMenu::CurrentMenu != NULL? "menu/backup" : "menu/dismiss", snd_menuvolume, ATTN_NONE); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::HandleResult(bool res) +{ + if (mParentMenu != NULL) + { + if (mMessageMode == 0) + { + if (mAction == NAME_None) + { + mParentMenu->MenuEvent(res? MKEY_MBYes : MKEY_MBNo, false); + Close(); + } + else + { + Close(); + if (res) M_SetMenu(mAction, -1); + } + CloseSound(); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Drawer () +{ + int i, y; + PalEntry fade = 0; + + int fontheight = SmallFont->GetHeight(); + //BorderNeedRefresh = screen->GetPageCount (); + //SB_state = screen->GetPageCount (); + + y = 100; + + if (mMessage != NULL) + { + for (i = 0; mMessage[i].Width >= 0; i++) + y -= SmallFont->GetHeight () / 2; + + for (i = 0; mMessage[i].Width >= 0; i++) + { + screen->DrawText (SmallFont, CR_UNTRANSLATED, 160 - mMessage[i].Width/2, y, mMessage[i].Text, + DTA_Clean, true, TAG_DONE); + y += fontheight; + } + } + + if (mMessageMode == 0) + { + y += fontheight; + mMouseY = y; + screen->DrawText(SmallFont, + messageSelection == 0? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, + 160, y, GStrings["TXT_YES"], DTA_Clean, true, TAG_DONE); + screen->DrawText(SmallFont, + messageSelection == 1? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, + 160, y + fontheight + 1, GStrings["TXT_NO"], DTA_Clean, true, TAG_DONE); + + if (messageSelection >= 0) + { + if ((DMenu::MenuTime%8) < 6) + { + screen->DrawText(ConFont, OptionSettings.mFontColorSelection, + (150 - 160) * CleanXfac + screen->GetWidth() / 2, + (y + (fontheight + 1) * messageSelection - 100) * CleanYfac + screen->GetHeight() / 2, + "\xd", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); + } + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::Responder(event_t *ev) +{ + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown) + { + if (mMessageMode == 0) + { + int ch = tolower(ev->data1); + if (ch == 'n' || ch == ' ') + { + HandleResult(false); + return true; + } + else if (ch == 'y') + { + HandleResult(true); + return true; + } + } + else + { + Close(); + return true; + } + return false; + } + else if (ev->type == EV_KeyDown) + { + Close(); + return true; + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::MenuEvent(int mkey, bool fromcontroller) +{ + if (mMessageMode == 0) + { + if (mkey == MKEY_Up || mkey == MKEY_Down) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + messageSelection = !messageSelection; + return true; + } + else if (mkey == MKEY_Enter) + { + // 0 is yes, 1 is no + HandleResult(!messageSelection); + return true; + } + else if (mkey == MKEY_Back) + { + HandleResult(false); + return true; + } + return false; + } + else + { + Close(); + CloseSound(); + return true; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::MouseEvent(int type, int x, int y) +{ + if (mMessageMode == 1) + { + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; + } + else + { + int sel = -1; + int fh = SmallFont->GetHeight() + 1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; + + if (x >= mMouseLeft && x <= mMouseRight && y >= mMouseY && y < mMouseY + 2 * fh) + { + sel = y >= mMouseY + fh; + } + if (sel != -1 && sel != messageSelection) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + messageSelection = sel; + if (type == MOUSE_Release) + { + return MenuEvent(MKEY_Enter, true); + } + return true; + } +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuitMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuitMenu, DMessageBoxMenu) + +public: + + DQuitMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuitMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuitMenu::DQuitMenu(bool playsound) +{ + int messageindex = gametic % gameinfo.quitmessages.Size(); + FString EndString; + const char *msg = gameinfo.quitmessages[messageindex]; + if (msg[0] == '$') + { + if (msg[1] == '*') + { + EndString = GStrings(msg+2); + } + else + { + EndString.Format("%s\n\n%s", GStrings(msg+1), GStrings("DOSY")); + } + } + else EndString = gameinfo.quitmessages[messageindex]; + + Init(NULL, EndString, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuitMenu::HandleResult(bool res) +{ + if (res) + { + if (!netgame) + { + if (gameinfo.quitSound.IsNotEmpty()) + { + S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); + I_WaitVBL (105); + } + } + ST_Endoom(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (menu_quit) +{ // F10 + M_StartControlPanel (true); + DMenu *newmenu = new DQuitMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + + + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DEndGameMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DEndGameMenu, DMessageBoxMenu) + +public: + + DEndGameMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DEndGameMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DEndGameMenu::DEndGameMenu(bool playsound) +{ + int messageindex = gametic % gameinfo.quitmessages.Size(); + FString EndString = gameinfo.quitmessages[messageindex]; + + if (netgame) + { + if(gameinfo.gametype == GAME_Chex) + EndString = GStrings("CNETEND"); + else + EndString = GStrings("NETEND"); + return; + } + + if(gameinfo.gametype == GAME_Chex) + EndString = GStrings("CENDGAME"); + else + EndString = GStrings("ENDGAME"); + + + Init(NULL, EndString, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DEndGameMenu::HandleResult(bool res) +{ + if (res) + { + M_ClearMenus (); + D_StartTitle (); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (menu_endgame) +{ // F7 + if (!usergame) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + //M_StartControlPanel (true); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + DMenu *newmenu = new DEndGameMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuickSaveMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuickSaveMenu, DMessageBoxMenu) + +public: + + DQuickSaveMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuickSaveMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuickSaveMenu::DQuickSaveMenu(bool playsound) +{ + FString tempstring; + + if(gameinfo.gametype == GAME_Chex) + tempstring.Format(GStrings("CQSPROMPT"), quickSaveSlot->Title); + else + tempstring.Format(GStrings("QSPROMPT"), quickSaveSlot->Title); + + Init(NULL, tempstring, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuickSaveMenu::HandleResult(bool res) +{ + if (res) + { + G_SaveGame (quickSaveSlot->Filename.GetChars(), quickSaveSlot->Title); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quicksave) +{ // F6 + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + if (gamestate != GS_LEVEL) + return; + + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + if (quickSaveSlot == NULL) + { + M_StartControlPanel(false); + M_SetMenu(NAME_Savegamemenu); + return; + } + DMenu *newmenu = new DQuickSaveMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuickLoadMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuickLoadMenu, DMessageBoxMenu) + +public: + + DQuickLoadMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuickLoadMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuickLoadMenu::DQuickLoadMenu(bool playsound) +{ + FString tempstring; + + if(gameinfo.gametype == GAME_Chex) + tempstring.Format(GStrings("CQLPROMPT"), quickSaveSlot->Title); + else + tempstring.Format(GStrings("QLPROMPT"), quickSaveSlot->Title); + + Init(NULL, tempstring, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuickLoadMenu::HandleResult(bool res) +{ + if (res) + { + G_LoadGame (quickSaveSlot->Filename.GetChars()); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quickload) +{ // F9 + M_StartControlPanel (true); + + if (netgame) + { + if(gameinfo.gametype == GAME_Chex) + M_StartMessage (GStrings("CQLOADNET"), NULL); + else + M_StartMessage (GStrings("QLOADNET"), NULL); + return; + } + + if (quickSaveSlot == NULL) + { + M_StartControlPanel(false); + // signal that whatever gets loaded should be the new quicksave + quickSaveSlot = (FSaveGameNode *)1; + M_SetMenu(NAME_Loadgamemenu); + return; + } + DMenu *newmenu = new DQuickLoadMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_StartMessage(const char *message, int messagemode, FName action) +{ + if (DMenu::CurrentMenu == NULL) + { + // only play a sound if no menu was active before + M_StartControlPanel(menuactive == MENU_Off); + } + DMenu *newmenu = new DMessageBoxMenu(DMenu::CurrentMenu, message, messagemode, false, action); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + diff --git a/src/menu/optionmenu.cpp b/src/menu/optionmenu.cpp new file mode 100644 index 0000000000..a64f77431c --- /dev/null +++ b/src/menu/optionmenu.cpp @@ -0,0 +1,591 @@ +/* +** optionmenu.cpp +** Handler class for the option menus and associated items +** +**--------------------------------------------------------------------------- +** Copyright 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 "v_video.h" +#include "v_font.h" +#include "cmdlib.h" +#include "gstrings.h" +#include "g_level.h" +#include "gi.h" +#include "v_palette.h" +#include "d_gui.h" +#include "d_event.h" +#include "c_dispatch.h" +#include "c_console.h" +#include "c_cvars.h" +#include "c_bind.h" +#include "gameconfigfile.h" +#include "menu/menu.h" + + +//============================================================================= +// +// Draws a string in the console font, scaled to the 8x8 cells +// used by the default console font. +// +//============================================================================= + +void M_DrawConText (int color, int x, int y, const char *str) +{ + int len = (int)strlen(str); + + screen->DrawText (ConFont, color, x, y, str, + DTA_CellX, 8 * CleanXfac_1, + DTA_CellY, 8 * CleanYfac_1, + TAG_DONE); +} + +//============================================================================= +// +// Draw a slider. Set fracdigits negative to not display the current value numerically. +// +//============================================================================= + +void M_DrawSlider (int x, int y, double min, double max, double cur,int fracdigits) +{ + double range; + + range = max - min; + double ccur = clamp(cur, min, max) - min; + + if (CleanXfac > CleanXfac_1 || CleanXfac_1 * 320 < screen->GetWidth()) + { + M_DrawConText(CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12"); + M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * CleanXfac_1), y, "\x13"); + + if (fracdigits >= 0) + { + char textbuf[16]; + mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); + screen->DrawText(SmallFont, CR_DARKGRAY, x + (12*8 + 4) * CleanXfac_1, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE); + } + } + else + { + // On 320x200 we need a shorter slider + M_DrawConText(CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x12"); + M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 38) / range)) * CleanXfac_1), y, "\x13"); + + if (fracdigits >= 0) + { + char textbuf[16]; + mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); + screen->DrawText(SmallFont, CR_DARKGRAY, x + (7*8 + 4) * CleanXfac_1, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE); + } + } +} + + + +IMPLEMENT_CLASS(DOptionMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DOptionMenu::DOptionMenu(DMenu *parent, FOptionMenuDescriptor *desc) +: DMenu(parent) +{ + CanScrollUp = false; + CanScrollDown = false; + VisBottom = 0; + mFocusControl = NULL; + Init(parent, desc); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Init(DMenu *parent, FOptionMenuDescriptor *desc) +{ + mParentMenu = parent; + GC::WriteBarrier(this, parent); + mDesc = desc; + if (mDesc != NULL && mDesc->mSelectedItem < 0) + { + // Go down to the first selectable item + int i = -1; + mDesc->mSelectedItem = -1; + do + { + i++; + } + while (!mDesc->mItems[i]->Selectable() && i < (int)mDesc->mItems.Size()); + if (i>=0) mDesc->mSelectedItem = i; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuItem *DOptionMenu::GetItem(FName name) +{ + for(unsigned i=0;imItems.Size(); i++) + { + FName nm = mDesc->mItems[i]->GetAction(NULL); + if (nm == name) return mDesc->mItems[i]; + } + return NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_WheelUp) + { + if (mDesc->mScrollPos > 0) + { + mDesc->mScrollPos--; + } + return true; + } + else if (ev->subtype == EV_GUI_WheelDown) + { + if (CanScrollDown) + { + mDesc->mScrollPos++; + VisBottom++; + } + return true; + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int startedAt = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + do + { + --mDesc->mSelectedItem; + + if (mDesc->mScrollPos > 0 && + mDesc->mSelectedItem == mDesc->mScrollTop + mDesc->mScrollPos) + { + mDesc->mScrollPos--; + } + + if (mDesc->mSelectedItem < 0) + { + // Figure out how many lines of text fit on the menu + int y = mDesc->mPosition; + + if (y <= 0) + { + if (BigFont && mDesc->mTitle.IsNotEmpty()) + { + y = -y + BigFont->GetHeight(); + } + else + { + y = -y; + } + } + y *= CleanYfac_1; + int rowheight = OptionSettings.mLinespacing * CleanYfac_1; + int maxitems = (screen->GetHeight() - rowheight - y) / rowheight + 1; + + mDesc->mScrollPos = MAX (0, (int)mDesc->mItems.Size() - maxitems + mDesc->mScrollTop); + mDesc->mSelectedItem = mDesc->mItems.Size()-1; + } + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + break; + + case MKEY_Down: + do + { + ++mDesc->mSelectedItem; + + if (CanScrollDown && mDesc->mSelectedItem == VisBottom) + { + mDesc->mScrollPos++; + VisBottom++; + } + if (mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) + { + mDesc->mSelectedItem = 0; + mDesc->mScrollPos = 0; + } + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + break; + + case MKEY_PageUp: + if (mDesc->mScrollPos > 0) + { + mDesc->mScrollPos -= VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; + if (mDesc->mScrollPos < 0) + { + mDesc->mScrollPos = 0; + } + mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos + 1; + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) + { + ++mDesc->mSelectedItem; + } + } + break; + + case MKEY_PageDown: + if (CanScrollDown) + { + int pagesize = VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; + mDesc->mScrollPos += pagesize; + if (mDesc->mScrollPos + mDesc->mScrollTop + pagesize > (int)mDesc->mItems.Size()) + { + mDesc->mScrollPos = mDesc->mItems.Size() - mDesc->mScrollTop - pagesize; + } + mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos; + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) + { + ++mDesc->mSelectedItem; + } + } + break; + + case MKEY_Enter: + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + { + return true; + } + // fall through to default + default: + if (mDesc->mSelectedItem >= 0 && + mDesc->mItems[mDesc->mSelectedItem]->MenuEvent(mkey, fromcontroller)) return true; + return Super::MenuEvent(mkey, fromcontroller); + } + + if (mDesc->mSelectedItem != startedAt) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::MouseEvent(int type, int x, int y) +{ + y = (y / CleanYfac_1) - mDesc->mDrawTop; + + if (mFocusControl) + { + mFocusControl->MouseEvent(type, x, y); + return true; + } + else + { + int yline = (y / OptionSettings.mLinespacing); + if (yline >= mDesc->mScrollTop) + { + yline += mDesc->mScrollPos; + } + if ((unsigned)yline < mDesc->mItems.Size() && mDesc->mItems[yline]->Selectable()) + { + if (yline != mDesc->mSelectedItem) + { + mDesc->mSelectedItem = yline; + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mItems[yline]->MouseEvent(type, x, y); + return true; + } + } + mDesc->mSelectedItem = -1; + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Ticker () +{ + Super::Ticker(); + for(unsigned i=0;imItems.Size(); i++) + { + mDesc->mItems[i]->Ticker(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Drawer () +{ + int y = mDesc->mPosition; + + if (y <= 0) + { + if (BigFont && mDesc->mTitle.IsNotEmpty()) + { + const char *tt = mDesc->mTitle; + if (*tt == '$') tt = GStrings(tt+1); + screen->DrawText (BigFont, OptionSettings.mTitleColor, + (screen->GetWidth() - BigFont->StringWidth(tt) * CleanXfac_1) / 2, 10*CleanYfac_1, + tt, DTA_CleanNoMove_1, true, TAG_DONE); + y = -y + BigFont->GetHeight(); + } + else + { + y = -y; + } + } + mDesc->mDrawTop = y; + //int labelofs = OptionSettings.mLabelOffset * CleanXfac_1; + //int cursorspace = 14 * CleanXfac_1; + int fontheight = OptionSettings.mLinespacing * CleanYfac_1; + y *= CleanYfac_1; + + int indent = mDesc->mIndent; + if (indent > 280) + { // kludge for the compatibility options with their extremely long labels + if (indent + 40 <= CleanWidth_1) + { + indent = (screen->GetWidth() - ((indent + 40) * CleanXfac_1)) / 2 + indent * CleanXfac_1; + } + else + { + indent = screen->GetWidth() - 40 * CleanXfac_1; + } + } + else + { + indent = (indent - 160) * CleanXfac_1 + screen->GetWidth() / 2; + } + + int ytop = y + mDesc->mScrollTop * 8 * CleanYfac_1; + int lastrow = screen->GetHeight() - SmallFont->GetHeight() * CleanYfac_1; + + unsigned i; + for (i = 0; i < mDesc->mItems.Size() && y <= lastrow; i++, y += fontheight) + { + // Don't scroll the uppermost items + if (i == mDesc->mScrollTop) + { + i += mDesc->mScrollPos; + if (i >= mDesc->mItems.Size()) break; // skipped beyond end of menu + } + int cur_indent = mDesc->mItems[i]->Draw(mDesc, y, indent, mDesc->mSelectedItem == i); + if (cur_indent >= 0 && mDesc->mSelectedItem == i && mDesc->mItems[i]->Selectable()) + { + if (((DMenu::MenuTime%8) < 6) || DMenu::CurrentMenu != this) + { + M_DrawConText(OptionSettings.mFontColorSelection, cur_indent + 3 * CleanXfac_1, y-CleanYfac_1+OptionSettings.mLabelOffset, "\xd"); + } + } + } + + CanScrollUp = (mDesc->mScrollPos > 0); + CanScrollDown = (i < mDesc->mItems.Size()); + VisBottom = i - 1; + + if (CanScrollUp) + { + M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, ytop + OptionSettings.mLabelOffset, "\x1a"); + } + if (CanScrollDown) + { + M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, y - 8*CleanYfac_1 + OptionSettings.mLabelOffset, "\x1b"); + } + Super::Drawer(); +} + + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +FOptionMenuItem::~FOptionMenuItem() +{ + if (mLabel != NULL) delete [] mLabel; +} + +bool FOptionMenuItem::CheckCoordinate(FOptionMenuDescriptor *desc, int x, int y) +{ + return false; +} + +int FOptionMenuItem::Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) +{ + return indent; +} + +bool FOptionMenuItem::Selectable() +{ + return true; +} + +bool FOptionMenuItem::MouseEvent(int type, int x, int y) +{ + if (Selectable() && type == DMenu::MOUSE_Release) + { + return DMenu::CurrentMenu->MenuEvent(MKEY_Enter, true); + } + return false; +} + +int FOptionMenuItem::GetIndent() +{ + return mCentered? 0 : SmallFont->StringWidth(mLabel); +} + +void FOptionMenuItem::drawLabel(int indent, int y, EColorRange color, bool grayed) +{ + const char *label = mLabel; + if (*label == '$') label = GStrings(label+1); + + int overlay = grayed? MAKEARGB(96,48,0,0) : 0; + + int x; + int w = SmallFont->StringWidth(label) * CleanXfac_1; + if (!mCentered) x = indent - w; + else x = (screen->GetWidth() - w) / 2; + screen->DrawText (SmallFont, color, x, y, label, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); +} + + + +void FOptionMenuDescriptor::CalcIndent() +{ + // calculate the menu indent + int widest = 0, thiswidth; + + for (unsigned i = 0; i < mItems.Size(); i++) + { + thiswidth = mItems[i]->GetIndent(); + if (thiswidth > widest) widest = thiswidth; + } + mIndent = widest + 4; +} + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuItem *FOptionMenuDescriptor::GetItem(FName name) +{ + for(unsigned i=0;iGetAction(NULL); + if (nm == name) return mItems[i]; + } + return NULL; +} + + + + +class DGameplayMenu : public DOptionMenu +{ + DECLARE_CLASS(DGameplayMenu, DOptionMenu) + +public: + DGameplayMenu() + {} + + void Drawer () + { + Super::Drawer(); + + char text[64]; + mysnprintf(text, 64, "dmflags = %d dmflags2 = %d", *dmflags, *dmflags2); + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, + (screen->GetWidth() - SmallFont->StringWidth (text) * CleanXfac_1) / 2, 0, text, + DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_CLASS(DGameplayMenu) + +class DCompatibilityMenu : public DOptionMenu +{ + DECLARE_CLASS(DCompatibilityMenu, DOptionMenu) + +public: + DCompatibilityMenu() + {} + + void Drawer () + { + Super::Drawer(); + + char text[64]; + mysnprintf(text, 64, "compatflags = %d", *compatflags); + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, + (screen->GetWidth() - SmallFont->StringWidth (text) * CleanXfac_1) / 2, 0, text, + DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_CLASS(DCompatibilityMenu) diff --git a/src/menu/optionmenuitems.h b/src/menu/optionmenuitems.h new file mode 100644 index 0000000000..07a72fb539 --- /dev/null +++ b/src/menu/optionmenuitems.h @@ -0,0 +1,916 @@ +/* +** optionmenuitems.h +** Control items for option menus +** +**--------------------------------------------------------------------------- +** Copyright 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. +**--------------------------------------------------------------------------- +** +*/ + + +void M_DrawConText (int color, int x, int y, const char *str); +void M_DrawSlider (int x, int y, double min, double max, double cur,int fracdigits); +void M_SetVideoMode(); + + + +//============================================================================= +// +// opens a submenu, action is a submenu name +// +//============================================================================= + +class FOptionMenuItemSubmenu : public FOptionMenuItem +{ + int mParam; +public: + FOptionMenuItemSubmenu(const char *label, const char *menu, int param = 0) + : FOptionMenuItem(label, menu) + { + mParam = param; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColorMore); + return indent; + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + M_SetMenu(mAction, mParam); + return true; + } +}; + + +//============================================================================= +// +// Executes a CCMD, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemCommand : public FOptionMenuItemSubmenu +{ +public: + FOptionMenuItemCommand(const char *label, const char *menu) + : FOptionMenuItemSubmenu(label, menu) + { + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + C_DoCommand(mAction); + return true; + } + +}; + +//============================================================================= +// +// Executes a CCMD after confirmation, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand +{ + // action is a CCMD +public: + FOptionMenuItemSafeCommand(const char *label, const char *menu) + : FOptionMenuItemCommand(label, menu) + { + } + + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == MKEY_MBYes) + { + C_DoCommand(mAction); + return true; + } + return FOptionMenuItemCommand::MenuEvent(mkey, fromcontroller); + } + + bool Activate() + { + M_StartMessage("Do you really want to do this?", 0); + return true; + } +}; + +//============================================================================= +// +// Base class for option lists +// +//============================================================================= + +class FOptionMenuItemOptionBase : public FOptionMenuItem +{ +protected: + // action is a CVAR + FOptionValues *mValues; + FBaseCVar *mGrayCheck; + int mCenter; +public: + + enum + { + OP_VALUES = 0x11001 + }; + + FOptionMenuItemOptionBase(const char *label, const char *menu, const char *values, const char *graycheck, int center) + : FOptionMenuItem(label, menu) + { + FOptionValues **opt = OptionValues.CheckKey(values); + if (opt != NULL) + { + mValues = *opt; + } + else + { + mValues = NULL; + } + mGrayCheck = (FBoolCVar*)FindCVar(graycheck, NULL); + mCenter = center; + } + + bool SetString(int i, const char *newtext) + { + if (i == OP_VALUES) + { + FOptionValues **opt = OptionValues.CheckKey(newtext); + if (opt != NULL) + { + mValues = *opt; + int s = GetSelection(); + if (s >= (int)mValues->mValues.Size()) s = 0; + SetSelection(s); // readjust the CVAR if its value is outside the range now + return true; + } + } + return false; + } + + + + //============================================================================= + virtual int GetSelection() = 0; + virtual void SetSelection(int Selection) = 0; + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + bool grayed = mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool); + + if (mCenter) + { + indent = (screen->GetWidth() / 2); + } + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed); + + int overlay = grayed? MAKEARGB(96,48,0,0) : 0; + const char *text; + int Selection = GetSelection(); + if (Selection < 0) + { + text = "Unknown"; + } + else + { + text = mValues->mValues[Selection].Text; + } + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, indent + CURSORSPACE, y, + text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); + return indent; + } + + //============================================================================= + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mValues->mValues.Size() > 0) + { + int Selection = GetSelection(); + if (mkey == MKEY_Left) + { + if (Selection == -1) Selection = 0; + else if (--Selection < 0) Selection = mValues->mValues.Size()-1; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + if (++Selection >= (int)mValues->mValues.Size()) Selection = 0; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + SetSelection(Selection); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + return true; + } + + bool Selectable() + { + return !(mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool)); + } +}; + +//============================================================================= +// +// Change a CVAR, action is the CVAR name +// +//============================================================================= + +class FOptionMenuItemOption : public FOptionMenuItemOptionBase +{ + // action is a CVAR + FBaseCVar *mCVar; +public: + + FOptionMenuItemOption(const char *label, const char *menu, const char *values, const char *graycheck, int center) + : FOptionMenuItemOptionBase(label, menu, values, graycheck, center) + { + mCVar = FindCVar(mAction, NULL); + } + + //============================================================================= + int GetSelection() + { + int Selection = -1; + if (mValues != NULL && mCVar != NULL && mValues->mValues.Size() > 0) + { + if (mValues->mValues[0].TextValue.IsEmpty()) + { + UCVarValue cv = mCVar->GetGenericRep(CVAR_Float); + for(unsigned i=0;imValues.Size(); i++) + { + if (fabs(cv.Float - mValues->mValues[i].Value) < FLT_EPSILON) + { + Selection = i; + break; + } + } + } + else + { + UCVarValue cv = mCVar->GetGenericRep(CVAR_String); + for(unsigned i=0;imValues.Size(); i++) + { + if (mValues->mValues[i].TextValue.CompareNoCase(cv.String) == 0) + { + Selection = i; + break; + } + } + } + } + return Selection; + } + + void SetSelection(int Selection) + { + UCVarValue value; + if (mValues != NULL && mCVar != NULL && mValues->mValues.Size() > 0) + { + if (mValues->mValues[0].TextValue.IsEmpty()) + { + value.Float = (float)mValues->mValues[Selection].Value; + mCVar->SetGenericRep (value, CVAR_Float); + } + else + { + value.String = mValues->mValues[Selection].TextValue.LockBuffer(); + mCVar->SetGenericRep (value, CVAR_String); + mValues->mValues[Selection].TextValue.UnlockBuffer(); + } + } + } +}; + +//============================================================================= +// +// This class is used to capture the key to be used as the new key binding +// for a control item +// +//============================================================================= + +class DEnterKey : public DMenu +{ + DECLARE_CLASS(DEnterKey, DMenu) + + int *pKey; + +public: + DEnterKey(DMenu *parent, int *keyptr) + : DMenu(parent) + { + pKey = keyptr; + SetMenuMessage(1); + menuactive = MENU_WaitKey; // There should be a better way to disable GUI capture... + } + + bool TranslateKeyboardEvents() + { + return false; + } + + void SetMenuMessage(int which) + { + if (mParentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu))) + { + DOptionMenu *m = barrier_cast(mParentMenu); + FListMenuItem *it = m->GetItem(NAME_Controlmessage); + if (it != NULL) + { + it->SetValue(0, which); + } + } + } + + bool Responder(event_t *ev) + { + if (ev->type == EV_KeyDown) + { + *pKey = ev->data1; + menuactive = MENU_On; + SetMenuMessage(0); + Close(); + mParentMenu->MenuEvent((ev->data1 == KEY_ESCAPE)? MKEY_Abort : MKEY_Input, 0); + return true; + } + return false; + } + + void Drawer() + { + mParentMenu->Drawer(); + } +}; + +#ifndef NO_IMP +IMPLEMENT_ABSTRACT_CLASS(DEnterKey) +#endif + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class FOptionMenuItemControl : public FOptionMenuItem +{ + FKeyBindings *mBindings; + int mInput; + bool mWaiting; +public: + + FOptionMenuItemControl(const char *label, const char *menu, FKeyBindings *bindings) + : FOptionMenuItem(label, menu) + { + mBindings = bindings; + mWaiting = false; + } + + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mWaiting? OptionSettings.mFontColorHighlight: + (selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor)); + + char description[64]; + int Key1, Key2; + + mBindings->GetKeysForCommand(mAction, &Key1, &Key2); + C_NameKeys (description, Key1, Key2); + if (description[0]) + { + M_DrawConText(CR_WHITE, indent + CURSORSPACE, y-1+OptionSettings.mLabelOffset, description); + } + else + { + screen->DrawText(SmallFont, CR_BLACK, indent + CURSORSPACE, y + OptionSettings.mLabelOffset, "---", + DTA_CleanNoMove_1, true, TAG_DONE); + } + return indent; + } + + //============================================================================= + bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == MKEY_Input) + { + mWaiting = false; + mBindings->SetBind(mInput, mAction); + return true; + } + else if (mkey == MKEY_Clear) + { + mBindings->UnbindACommand(mAction); + return true; + } + else if (mkey == MKEY_Abort) + { + mWaiting = false; + return true; + } + return false; + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + mWaiting = true; + DMenu *input = new DEnterKey(DMenu::CurrentMenu, &mInput); + M_ActivateMenu(input); + return true; + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemStaticText : public FOptionMenuItem +{ + EColorRange mColor; +public: + FOptionMenuItemStaticText(const char *label, bool header) + : FOptionMenuItem(label, NAME_None, true) + { + mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mColor); + return -1; + } + + bool Selectable() + { + return false; + } + +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemStaticTextSwitchable : public FOptionMenuItem +{ + EColorRange mColor; + FString mAltText; + int mCurrent; + +public: + FOptionMenuItemStaticTextSwitchable(const char *label, const char *label2, FName action, bool header) + : FOptionMenuItem(label, action, true) + { + mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; + mAltText = label2; + mCurrent = 0; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + const char *txt = mCurrent? (const char*)mAltText : mLabel; + int w = SmallFont->StringWidth(txt) * CleanXfac_1; + int x = (screen->GetWidth() - w) / 2; + screen->DrawText (SmallFont, mColor, x, y, txt, DTA_CleanNoMove_1, true, TAG_DONE); + return -1; + } + + bool SetValue(int i, int val) + { + if (i == 0) + { + mCurrent = val; + return true; + } + return false; + } + + bool SetString(int i, const char *newtext) + { + if (i == 0) + { + mAltText = newtext; + return true; + } + return false; + } + + bool Selectable() + { + return false; + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderBase : public FOptionMenuItem +{ + // action is a CVAR + double mMin, mMax, mStep; + int mShowValue; + int mDrawX; +public: + FOptionMenuSliderBase(const char *label, double min, double max, double step, int showval) + : FOptionMenuItem(label, NAME_None) + { + mMin = min; + mMax = max; + mStep = step; + mShowValue = showval; + mDrawX = 0; + } + + virtual double GetValue() = 0; + virtual void SetValue(double val) = 0; + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); + mDrawX = indent + CURSORSPACE; + M_DrawSlider (mDrawX, y + OptionSettings.mLabelOffset, mMin, mMax, GetValue(), mShowValue); + return indent; + } + + //============================================================================= + bool MenuEvent (int mkey, bool fromcontroller) + { + double value = GetValue(); + + if (mkey == MKEY_Left) + { + value -= mStep; + } + else if (mkey == MKEY_Right) + { + value += mStep; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + SetValue(clamp(value, mMin, mMax)); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + + bool MouseEvent(int type, int x, int y) + { + DOptionMenu *lm = static_cast(DMenu::CurrentMenu); + if (type != DMenu::MOUSE_Click) + { + if (!lm->CheckFocus(this)) return false; + } + if (type == DMenu::MOUSE_Release) + { + lm->ReleaseFocus(); + } + + int slide_left = mDrawX+8*CleanXfac_1; + int slide_right = slide_left + 10*8*CleanXfac_1; // 12 char cells with 8 pixels each. + + if (type == DMenu::MOUSE_Click) + { + if (x < slide_left || x >= slide_right) return true; + } + + x = clamp(x, slide_left, slide_right); + double v = mMin + ((x - slide_left) * (mMax - mMin)) / (slide_right - slide_left); + if (v != GetValue()) + { + SetValue(v); + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + if (type == DMenu::MOUSE_Click) + { + lm->SetFocus(this); + } + return true; + } + +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderCVar : public FOptionMenuSliderBase +{ + FBaseCVar *mCVar; +public: + FOptionMenuSliderCVar(const char *label, const char *menu, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mCVar = FindCVar(menu, NULL); + } + + double GetValue() + { + if (mCVar != NULL) + { + return mCVar->GetGenericRep(CVAR_Float).Float; + } + else + { + return 0; + } + } + + void SetValue(double val) + { + if (mCVar != NULL) + { + UCVarValue value; + value.Float = (float)val; + mCVar->SetGenericRep(value, CVAR_Float); + } + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderVar : public FOptionMenuSliderBase +{ + float *mPVal; +public: + + FOptionMenuSliderVar(const char *label, float *pVal, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mPVal = pVal; + } + + double GetValue() + { + return *mPVal; + } + + void SetValue(double val) + { + *mPVal = (float)val; + } +}; + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class FOptionMenuItemColorPicker : public FOptionMenuItem +{ + FColorCVar *mCVar; +public: + + enum + { + CPF_RESET = 0x20001, + }; + + FOptionMenuItemColorPicker(const char *label, const char *menu) + : FOptionMenuItem(label, menu) + { + FBaseCVar *cv = FindCVar(menu, NULL); + if (cv->GetRealType() == CVAR_Color) + { + mCVar = (FColorCVar*)cv; + } + else mCVar = NULL; + } + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); + + if (mCVar != NULL) + { + int box_x = indent + CURSORSPACE; + int box_y = y + OptionSettings.mLabelOffset * CleanYfac_1 / 2; + screen->Clear (box_x, box_y, box_x + 32*CleanXfac_1, box_y + (SmallFont->GetHeight() - 1) * CleanYfac_1, + -1, (uint32)*mCVar | 0xff000000); + } + return indent; + } + + bool SetValue(int i, int v) + { + if (i == CPF_RESET && mCVar != NULL) + { + mCVar->ResetToDefault(); + return true; + } + return false; + } + + bool Activate() + { + if (mCVar != NULL) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + DMenu *picker = StartPickerMenu(DMenu::CurrentMenu, mLabel, mCVar); + if (picker != NULL) + { + M_ActivateMenu(picker); + return true; + } + } + return false; + } +}; + +class FOptionMenuScreenResolutionLine : public FOptionMenuItem +{ + FString mResTexts[3]; + int mSelection; + int mHighlight; + int mMaxValid; +public: + + enum + { + SRL_INDEX = 0x30000, + SRL_SELECTION = 0x30003, + SRL_HIGHLIGHT = 0x30004, + }; + + FOptionMenuScreenResolutionLine(const char *action) + : FOptionMenuItem("", action) + { + mSelection = 0; + mHighlight = -1; + } + + bool SetValue(int i, int v) + { + if (i == SRL_SELECTION) + { + mSelection = v; + return true; + } + else if (i == SRL_HIGHLIGHT) + { + mHighlight = v; + return true; + } + return false; + } + + bool GetValue(int i, int *v) + { + if (i == SRL_SELECTION) + { + *v = mSelection; + return true; + } + return false; + } + + bool SetString(int i, const char *newtext) + { + if (i >= SRL_INDEX && i <= SRL_INDEX+2) + { + mResTexts[i-SRL_INDEX] = newtext; + if (mResTexts[0].IsEmpty()) mMaxValid = -1; + else if (mResTexts[1].IsEmpty()) mMaxValid = 0; + else if (mResTexts[2].IsEmpty()) mMaxValid = 1; + else mMaxValid = 2; + return true; + } + return false; + } + + bool GetString(int i, char *s, int len) + { + if (i >= SRL_INDEX && i <= SRL_INDEX+2) + { + strncpy(s, mResTexts[i-SRL_INDEX], len-1); + s[len-1] = 0; + return true; + } + return false; + } + + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == MKEY_Left) + { + if (--mSelection < 0) mSelection = mMaxValid; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + else if (mkey == MKEY_Right) + { + if (++mSelection > mMaxValid) mSelection = 0; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + return false; + } + + bool MouseEvent(int type, int x, int y) + { + int colwidth = screen->GetWidth() / 3; + mSelection = x / colwidth; + return FOptionMenuItem::MouseEvent(type, x, y); + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + M_SetVideoMode(); + return true; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + int colwidth = screen->GetWidth() / 3; + EColorRange color; + + for (int x = 0; x < 3; x++) + { + if (selected && mSelection == x) + color = OptionSettings.mFontColorSelection; + else if (x == mHighlight) + color = OptionSettings.mFontColorHighlight; + else + color = OptionSettings.mFontColorValue; + + screen->DrawText (SmallFont, color, colwidth * x + 20 * CleanXfac_1, y, mResTexts[x], DTA_CleanNoMove_1, true, TAG_DONE); + } + return colwidth * mSelection + 20 * CleanXfac_1 - CURSORSPACE; + } + + bool Selectable() + { + return mMaxValid >= 0; + } +}; + +#ifndef NO_IMP +CCMD(am_restorecolors) +{ + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu))) + { + DOptionMenu *m = (DOptionMenu*)DMenu::CurrentMenu; + const FOptionMenuDescriptor *desc = m->GetDescriptor(); + // Find the color cvars by scanning the MapColors menu. + for (unsigned i = 0; i < desc->mItems.Size(); ++i) + { + desc->mItems[i]->SetValue(FOptionMenuItemColorPicker::CPF_RESET, 0); + } + } +} +#endif diff --git a/src/menu/playerdisplay.cpp b/src/menu/playerdisplay.cpp new file mode 100644 index 0000000000..f9871e1ca6 --- /dev/null +++ b/src/menu/playerdisplay.cpp @@ -0,0 +1,561 @@ +/* +** playerdisplay.cpp +** The player display for the player setup and class selection screen +** +**--------------------------------------------------------------------------- +** Copyright 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 "doomtype.h" +#include "doomstat.h" +#include "d_player.h" +#include "tables.h" +#include "m_fixed.h" +#include "templates.h" +#include "menu/menu.h" +#include "colormatcher.h" +#include "textures/textures.h" +#include "w_wad.h" +#include "v_font.h" +#include "v_video.h" +#include "g_level.h" +#include "gi.h" +#include "r_defs.h" +#include "r_state.h" + + +//============================================================================= +// +// Used by the player display +// +//============================================================================= + +struct FBackdropTexture : public FTexture +{ +public: + FBackdropTexture(); + + const BYTE *GetColumn(unsigned int column, const Span **spans_out); + const BYTE *GetPixels(); + void Unload(); + bool CheckModified(); + +protected: + BYTE Pixels[144*160]; + static const Span DummySpan[2]; + int LastRenderTic; + + angle_t time1, time2, time3, time4; + angle_t t1ang, t2ang, z1ang, z2ang; + + void Render(); +}; + + + +// A 32x32 cloud rendered with Photoshop, plus some other filters +static BYTE pattern1[1024] = +{ + 5, 9, 7,10, 9,15, 9, 7, 8,10, 5, 3, 5, 7, 9, 8,14, 8, 4, 7, 8, 9, 5, 7,14, 7, 0, 7,13,13, 9, 6, + 2, 7, 9, 7, 7,10, 8, 8,11,10, 6, 7,10, 7, 5, 6, 6, 4, 7,13,15,16,11,15,11, 8, 0, 4,13,22,17,11, + 5, 9, 9, 7, 9,10, 4, 3, 6, 7, 8, 6, 5, 4, 2, 2, 1, 4, 6,11,15,15,14,13,17, 9, 5, 9,11,12,17,20, + 9,16, 9, 8,12,13, 7, 3, 7, 9, 5, 4, 2, 5, 5, 5, 7,11, 6, 7, 6,13,17,10,10, 9,12,17,14,12,16,15, + 15,13, 5, 3, 9,10, 4,10,12,12, 7, 9, 8, 8, 8,10, 7, 6, 5, 5, 5, 6,11, 9, 3,13,16,18,21,16,23,18, + 23,13, 0, 0, 0, 0, 0,12,18,14,15,16,13, 7, 7, 5, 9, 6, 6, 8, 4, 0, 0, 0, 0,14,19,17,14,20,21,25, + 19,20,14,13, 7, 5,13,19,14,13,17,15,14, 7, 3, 5, 6,11, 7, 7, 8, 8,10, 9, 9,18,17,15,14,15,18,16, + 16,29,24,23,18, 9,17,20,11, 5,12,15,15,12, 6, 3, 4, 6, 7,10,13,18,18,19,16,12,17,19,23,16,14,14, + 9,18,20,26,19, 5,18,18,10, 5,12,15,14,17,11, 6,11, 9,10,13,10,20,24,20,21,20,14,18,15,22,20,19, + 0, 6,16,18, 8, 7,15,18,10,13,17,17,13,11,15,11,19,12,13,10, 4,15,19,21,21,24,14, 9,17,20,24,17, + 18,17, 7, 7,16,21,22,15, 5,14,20,14,13,21,13, 8,12,14, 7, 8,11,15,13,11,16,17, 7, 5,12,17,19,14, + 25,23,17,16,23,18,15, 7, 0, 6,11, 6,11,15,11, 7,12, 7, 4,10,16,13, 7, 7,15,13, 9,15,21,14, 5, 0, + 18,22,21,21,21,22,12, 6,14,20,15, 6,10,19,13, 8, 7, 3, 7,12,14,16, 9,12,22,15,12,18,24,19,17, 9, + 0,15,18,21,17,25,14,13,19,21,21,11, 6,13,16,16,12,10,12,11,13,20,14,13,18,13, 9,15,16,25,31,20, + 5,20,24,16, 7,14,14,11,18,19,19, 6, 0, 5,11,14,17,16,19,14,15,21,19,15,14,14, 8, 0, 7,24,18,16, + 9,17,15, 7, 6,14,12, 7,14,16,11, 4, 7, 6,13,16,15,13,12,20,21,20,21,17,18,26,14, 0,13,23,21,11, + 9,12,18,11,15,21,13, 8,13,13,10, 7,13, 8, 8,19,13, 7, 4,15,19,18,14,12,14,15, 8, 6,16,22,22,15, + 9,17,14,19,15,14,15, 9,11, 9, 6, 8,14,13,13,12, 5, 0, 0, 6,12,13, 7, 7, 9, 7, 0,12,21,16,15,18, + 15,16,18,11, 6, 8,15, 9, 2, 0, 5,10,10,16, 9, 0, 4,12,15, 9,12, 9, 7, 7,12, 7, 0, 6,12, 6, 9,13, + 12,19,15,14,11, 7, 8, 9,12,10, 5, 5, 7,12,12,10,14,16,16,11, 8,12,10,12,10, 8,10,10,14,12,16,16, + 16,17,20,22,12,15,12,14,19,11, 6, 5,10,13,17,17,21,19,15, 9, 6, 9,15,18,10,10,18,14,20,15,16,17, + 11,19,19,18,19,14,17,13,12,12, 7,11,18,17,16,15,19,19,10, 2, 0, 8,15,12, 8,11,12,10,19,20,19,19, + 6,14,18,13,13,16,16,12, 5, 8,10,12,10,13,18,12, 9,10, 7, 6, 5,11, 8, 6, 7,13,16,13,10,15,20,14, + 0, 5,12,12, 4, 0, 9,16, 9,10,12, 8, 0, 9,13, 9, 0, 2, 4, 7,10, 6, 7, 3, 4,11,16,18,10,11,21,21, + 16,13,11,15, 8, 0, 5, 9, 8, 7, 6, 3, 0, 9,17, 9, 0, 0, 0, 3, 5, 4, 3, 5, 7,15,16,16,17,14,22,22, + 24,14,15,12, 9, 0, 5,10, 8, 4, 7,12,10,11,12, 7, 6, 8, 6, 5, 7, 8, 8,11,13,10,15,14,12,18,20,16, + 16,17,17,18,12, 9,12,16,10, 5, 6,20,13,15, 8, 4, 8, 9, 8, 7, 9,11,12,17,16,16,11,10, 9,10, 5, 0, + 0,14,18,18,15,16,14, 9,10, 9, 9,15,14,10, 4, 6,10, 8, 8, 7,10, 9,10,16,18,10, 0, 0, 7,12,10, 8, + 0,14,19,14, 9,11,11, 8, 8,10,15, 9,10, 7, 4,10,13, 9, 7, 5, 5, 7, 7, 7,13,13, 5, 5,14,22,18,16, + 0,10,14,10, 3, 6, 5, 6, 8, 9, 8, 9, 5, 9, 8, 9, 6, 8, 8, 8, 1, 0, 0, 0, 9,17,12,12,17,19,20,13, + 6,11,17,11, 5, 5, 8,10, 6, 5, 6, 6, 3, 7, 9, 7, 6, 8,12,10, 4, 8, 6, 6,11,16,16,15,16,17,17,16, + 11, 9,10,10, 5, 6,12,10, 5, 1, 6,10, 5, 3, 3, 5, 4, 7,15,10, 7,13, 7, 8,15,11,15,15,15, 8,11,15, +}; + +// Just a 32x32 cloud rendered with the standard Photoshop filter +static BYTE pattern2[1024] = +{ + 9, 9, 8, 8, 8, 8, 6, 6,13,13,11,21,19,21,23,18,23,24,19,19,24,17,18,12, 9,14, 8,12,12, 5, 8, 6, + 11,10, 6, 7, 8, 8, 9,13,10,11,17,15,23,22,23,22,20,26,27,26,17,21,20,14,12, 8,11, 8,11, 7, 8, 7, + 6, 9,13,13,10, 9,13, 7,12,13,16,19,16,20,22,25,22,25,27,22,21,23,15,10,14,14,15,13,12, 8,12, 6, + 6, 7,12,12,12,16, 9,12,12,15,16,11,21,24,19,24,23,26,28,27,26,21,14,15, 7, 7,10,15,12,11,10, 9, + 7,14,11,16,12,18,16,14,16,14,11,14,15,21,23,17,20,18,26,24,27,18,20,11,11,14,10,17,17,10, 6,10, + 13, 9,14,10,13,11,14,15,18,15,15,12,19,19,20,18,22,20,19,22,19,19,19,20,17,15,15,11,16,14,10, 8, + 13,16,12,16,17,19,17,18,15,19,14,18,15,14,15,17,21,19,23,18,23,22,18,18,17,15,15,16,12,12,15,10, + 10,12,14,10,16,11,18,15,21,20,20,17,18,19,16,19,14,20,19,14,19,25,22,21,22,24,18,12, 9, 9, 8, 6, + 10,10,13, 9,15,13,20,19,22,18,18,17,17,21,21,13,13,12,19,18,16,17,27,26,22,23,20,17,12,11, 8, 9, + 7,13,14,15,11,13,18,22,19,23,23,20,22,24,21,14,12,16,17,19,18,18,22,18,24,23,19,17,16,14, 8, 7, + 12,12, 8, 8,16,20,26,25,28,28,22,29,23,22,21,18,13,16,15,15,20,17,25,24,19,17,17,17,15,10, 8, 9, + 7,12,15,11,17,20,25,25,25,29,30,31,28,26,18,16,17,18,20,21,22,20,23,19,18,19,10,16,16,11,11, 8, + 5, 6, 8,14,14,17,17,21,27,23,27,31,27,22,23,21,19,19,21,19,20,19,17,22,13,17,12,15,10,10,12, 6, + 8, 9, 8,14,15,16,15,18,27,26,23,25,23,22,18,21,20,17,19,20,20,16,20,14,15,13,12, 8, 8, 7,11,13, + 7, 6,11,11,11,13,15,22,25,24,26,22,24,26,23,18,24,24,20,18,20,16,17,12,12,12,10, 8,11, 9, 6, 8, + 9,10, 9, 6, 5,14,16,19,17,21,26,20,23,19,19,17,20,21,26,25,23,21,17,13,12, 5,13,11, 7,12,10,12, + 6, 5, 4,10,11, 9,10,13,17,20,20,18,23,26,27,20,21,24,20,19,24,20,18,10,11, 3, 6,13, 9, 6, 8, 8, + 1, 2, 2,11,13,13,11,16,16,16,19,21,20,23,22,28,21,20,19,18,23,16,18, 7, 5, 9, 7, 6, 5,10, 8, 8, + 0, 0, 6, 9,11,15,12,12,19,18,19,26,22,24,26,30,23,22,22,16,20,19,12,12, 3, 4, 6, 5, 4, 7, 2, 4, + 2, 0, 0, 7,11, 8,14,13,15,21,26,28,25,24,27,26,23,24,22,22,15,17,12, 8,10, 7, 7, 4, 0, 5, 0, 1, + 1, 2, 0, 1, 9,14,13,10,19,24,22,29,30,28,30,30,31,23,24,19,17,14,13, 8, 8, 8, 1, 4, 0, 0, 0, 3, + 5, 2, 4, 2, 9, 8, 8, 8,18,23,20,27,30,27,31,25,28,30,28,24,24,15,11,14,10, 3, 4, 3, 0, 0, 1, 3, + 9, 3, 4, 3, 5, 6, 8,13,14,23,21,27,28,27,28,27,27,29,30,24,22,23,13,15, 8, 6, 2, 0, 4, 3, 4, 1, + 6, 5, 5, 3, 9, 3, 6,14,13,16,23,26,28,23,30,31,28,29,26,27,21,20,15,15,13, 9, 1, 0, 2, 0, 5, 8, + 8, 4, 3, 7, 2, 0,10, 7,10,14,21,21,29,28,25,27,30,28,25,24,27,22,19,13,10, 5, 0, 0, 0, 0, 0, 7, + 7, 6, 7, 0, 2, 2, 5, 6,15,11,19,24,22,29,27,31,30,30,31,28,23,18,14,14, 7, 5, 0, 0, 1, 0, 1, 0, + 5, 5, 5, 0, 0, 4, 5,11, 7,10,13,20,21,21,28,31,28,30,26,28,25,21, 9,12, 3, 3, 0, 2, 2, 2, 0, 1, + 3, 3, 0, 2, 0, 3, 5, 3,11,11,16,19,19,27,26,26,30,27,28,26,23,22,16, 6, 2, 2, 3, 2, 0, 2, 4, 0, + 0, 0, 0, 3, 3, 1, 0, 4, 5, 9,11,16,24,20,28,26,28,24,28,25,22,21,16, 5, 7, 5, 7, 3, 2, 3, 3, 6, + 0, 0, 2, 0, 2, 0, 4, 3, 8,12, 9,17,16,23,23,27,27,22,26,22,21,21,13,14, 5, 3, 7, 3, 2, 4, 6, 1, + 2, 5, 6, 4, 0, 1, 5, 8, 7, 6,15,17,22,20,24,28,23,25,20,21,18,16,13,15,13,10, 8, 5, 5, 9, 3, 7, + 7, 7, 0, 5, 1, 6, 7, 9,12, 9,12,21,22,25,24,22,23,25,24,18,24,22,17,13,10, 9,10, 9, 6,11, 6, 5, +}; + +const FTexture::Span FBackdropTexture::DummySpan[2] = { { 0, 160 }, { 0, 0 } }; + +//============================================================================= +// +// +// +//============================================================================= + +FBackdropTexture::FBackdropTexture() +{ + Width = 144; + Height = 160; + WidthBits = 8; + HeightBits = 8; + WidthMask = 255; + LastRenderTic = 0; + + time1 = ANGLE_1*180; + time2 = ANGLE_1*56; + time3 = ANGLE_1*99; + time4 = ANGLE_1*1; + t1ang = ANGLE_90; + t2ang = 0; + z1ang = 0; + z2ang = ANGLE_90/2; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FBackdropTexture::CheckModified() +{ + return LastRenderTic != gametic; +} + +void FBackdropTexture::Unload() +{ +} + +//============================================================================= +// +// +// +//============================================================================= + +const BYTE *FBackdropTexture::GetColumn(unsigned int column, const Span **spans_out) +{ + if (LastRenderTic != gametic) + { + Render(); + } + column = clamp(column, 0u, 143u); + if (spans_out != NULL) + { + *spans_out = DummySpan; + } + return Pixels + column*160; +} + +//============================================================================= +// +// +// +//============================================================================= + +const BYTE *FBackdropTexture::GetPixels() +{ + if (LastRenderTic != gametic) + { + Render(); + } + return Pixels; +} + +//============================================================================= +// +// This is one plasma and two rotozoomers. I think it turned out quite awesome. +// +//============================================================================= + +void FBackdropTexture::Render() +{ + BYTE *from; + int width, height, pitch; + + width = 160; + height = 144; + pitch = width; + + int x, y; + + const angle_t a1add = ANGLE_1/2; + const angle_t a2add = ANGLE_MAX-ANGLE_1; + const angle_t a3add = ANGLE_1*5/7; + const angle_t a4add = ANGLE_MAX-ANGLE_1*4/3; + + const angle_t t1add = ANGLE_MAX-ANGLE_1*2; + const angle_t t2add = ANGLE_MAX-ANGLE_1*3+ANGLE_1/6; + const angle_t t3add = ANGLE_1*16/7; + const angle_t t4add = ANGLE_MAX-ANGLE_1*2/3; + const angle_t x1add = 5<>ANGLETOFINESHIFT]>>2)+FRACUNIT/2; + fixed_t z2 = (finecosine[z1ang>>ANGLETOFINESHIFT]>>2)+FRACUNIT*3/4; + + tc = MulScale5 (finecosine[t1ang>>ANGLETOFINESHIFT], z1); + ts = MulScale5 (finesine[t1ang>>ANGLETOFINESHIFT], z1); + uc = MulScale5 (finecosine[t2ang>>ANGLETOFINESHIFT], z2); + us = MulScale5 (finesine[t2ang>>ANGLETOFINESHIFT], z2); + + ltx = -width/2*tc; + lty = -width/2*ts; + lux = -width/2*uc; + luy = -width/2*us; + + for (y = 0; y < height; ++y) + { + a1 = time1; + a2 = time2; + c3 = finecosine[a3>>ANGLETOFINESHIFT]; + c4 = finecosine[a4>>ANGLETOFINESHIFT]; + tx = ltx - (y-height/2)*ts; + ty = lty + (y-height/2)*tc; + ux = lux - (y-height/2)*us; + uy = luy + (y-height/2)*uc; + for (x = 0; x < width; ++x) + { + c1 = finecosine[a1>>ANGLETOFINESHIFT]; + c2 = finecosine[a2>>ANGLETOFINESHIFT]; + from[x] = ((c1 + c2 + c3 + c4) >> (FRACBITS+3-7)) + 128 // plasma + + pattern1[(tx>>27)+((ty>>22)&992)] // rotozoomer 1 + + pattern2[(ux>>27)+((uy>>22)&992)]; // rotozoomer 2 + tx += tc; + ty += ts; + ux += uc; + uy += us; + a1 += a1add; + a2 += a2add; + } + a3 += a3add; + a4 += a4add; + from += pitch; + } + + time1 += t1add; + time2 += t2add; + time3 += t3add; + time4 += t4add; + t1ang += x1add; + t2ang += x2add; + z1ang += z1add; + z2ang += z2add; + + LastRenderTic = gametic; +} + + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItemPlayerDisplay::FListMenuItemPlayerDisplay(FListMenuDescriptor *menu, int x, int y, PalEntry c1, PalEntry c2, bool np, FName action) +: FListMenuItem(x, y, action) +{ + mOwner = menu; + + for (int i = 0; i < 256; i++) + { + int r = c1.r + c2.r * i / 255; + int g = c1.g + c2.g * i / 255; + int b = c1.b + c2.b * i / 255; + mRemap.Remap[i] = ColorMatcher.Pick (r, g, b); + mRemap.Palette[i] = PalEntry(255, r, g, b); + } + mBackdrop = new FBackdropTexture; + mPlayerClass = NULL; + mPlayerState = NULL; + mNoportrait = np; + mMode = 0; + mRotation = 0; + mTranslate = false; + mSkin = 0; + mRandomClass = 0; + mRandomTimer = 0; + mClassNum = -1; +} + + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItemPlayerDisplay::~FListMenuItemPlayerDisplay() +{ + delete mBackdrop; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::UpdateRandomClass() +{ + if (--mRandomTimer < 0) + { + if (++mRandomClass >= (int)PlayerClasses.Size ()) mRandomClass = 0; + mPlayerClass = &PlayerClasses[mRandomClass]; + mPlayerState = GetDefaultByType (mPlayerClass->Type)->SeeState; + mPlayerTics = mPlayerState->GetTics(); + mRandomTimer = 6; + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::SetPlayerClass(int classnum, bool force) +{ + if (classnum < 0 || classnum >= (int)PlayerClasses.Size ()) + { + if (mClassNum != -1) + { + mClassNum = -1; + mRandomTimer = 0; + UpdateRandomClass(); + } + } + else if (mPlayerClass != &PlayerClasses[classnum] || force) + { + mPlayerClass = &PlayerClasses[classnum]; + mPlayerState = GetDefaultByType (mPlayerClass->Type)->SeeState; + mPlayerTics = mPlayerState->GetTics(); + mClassNum = classnum; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FListMenuItemPlayerDisplay::UpdatePlayerClass() +{ + int classnum; + FName seltype = mOwner->mItems[mOwner->mSelectedItem]->GetAction(&classnum); + + if (seltype != NAME_Episodemenu) return false; + if (PlayerClasses.Size() == 0) return false; + + SetPlayerClass(classnum); + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FListMenuItemPlayerDisplay::SetValue(int i, int value) +{ + switch (i) + { + case PDF_MODE: + mMode = value; + return true; + + case PDF_ROTATION: + mRotation = value; + return true; + + case PDF_TRANSLATE: + mTranslate = value; + + case PDF_CLASS: + SetPlayerClass(value, true); + break; + + case PDF_SKIN: + mSkin = value; + break; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::Ticker() +{ + if (mClassNum < 0) UpdateRandomClass(); + + if (mPlayerState != NULL && mPlayerState->GetTics () != -1 && mPlayerState->GetNextState () != NULL) + { + if (--mPlayerTics <= 0) + { + mPlayerState = mPlayerState->GetNextState(); + mPlayerTics = mPlayerState->GetTics(); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::Drawer(bool selected) +{ + if (mMode == 0 && !UpdatePlayerClass()) + { + return; + } + + const char *portrait = mPlayerClass->Type->Meta.GetMetaString(APMETA_Portrait); + + if (portrait != NULL && !mNoportrait) + { + FTextureID texid = TexMan.CheckForTexture(portrait, FTexture::TEX_MiscPatch); + if (texid.isValid()) + { + FTexture *tex = TexMan(texid); + if (tex != NULL) + { + screen->DrawTexture (tex, mXpos, mYpos, DTA_Clean, true, TAG_DONE); + return; + } + } + } + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + int y = (mYpos - 100) * CleanYfac + (SCREENHEIGHT>>1); + + screen->DrawTexture (mBackdrop, x, y - 1, + DTA_DestWidth, 72 * CleanXfac, + DTA_DestHeight, 80 * CleanYfac, + DTA_Translation, &mRemap, + DTA_Masked, true, + TAG_DONE); + + V_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); + + spriteframe_t *sprframe; + fixed_t scaleX, scaleY; + + if (mSkin == 0) + { + sprframe = &SpriteFrames[sprites[mPlayerState->sprite].spriteframes + mPlayerState->GetFrame()]; + scaleX = GetDefaultByType(mPlayerClass->Type)->scaleX; + scaleY = GetDefaultByType(mPlayerClass->Type)->scaleY; + } + else + { + sprframe = &SpriteFrames[sprites[skins[mSkin].sprite].spriteframes + mPlayerState->GetFrame()]; + scaleX = skins[mSkin].ScaleX; + scaleY = skins[mSkin].ScaleY; + } + + if (sprframe != NULL) + { + FTexture *tex = TexMan(sprframe->Texture[mRotation]); + if (tex != NULL && tex->UseType != FTexture::TEX_Null) + { + FRemapTable *trans = NULL; + if (mTranslate) trans = translationtables[TRANSLATION_Players](MAXPLAYERS); + screen->DrawTexture (tex, + x + 36*CleanXfac, y + 71*CleanYfac, + DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, scaleX), + DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, scaleY), + DTA_Translation, trans, + TAG_DONE); + } + } +} + diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp new file mode 100644 index 0000000000..b25b86f1a6 --- /dev/null +++ b/src/menu/playermenu.cpp @@ -0,0 +1,1142 @@ +/* +** playermenu.cpp +** The player setup menu +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 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 "menu/menu.h" +#include "v_video.h" +#include "v_font.h" +#include "gi.h" +#include "gstrings.h" +#include "d_player.h" +#include "d_event.h" +#include "d_gui.h" +#include "c_dispatch.h" +#include "teaminfo.h" +#include "v_palette.h" +#include "r_state.h" +#include "r_translate.h" +#include "v_text.h" + +EXTERN_CVAR (String, playerclass) +EXTERN_CVAR (String, name) +EXTERN_CVAR (Int, team) +EXTERN_CVAR (Float, autoaim) +EXTERN_CVAR(Bool, neverswitchonpickup) +EXTERN_CVAR (Bool, cl_run) + +void R_GetPlayerTranslation (int color, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table); + +//============================================================================= +// +// Player's name +// +//============================================================================= + +FPlayerNameBox::FPlayerNameBox(int x, int y, int height, int frameofs, const char *text, FFont *font, EColorRange color, FName action) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mFrameSize = frameofs; + mPlayerName[0] = 0; + mEntering = false; +} + +FPlayerNameBox::~FPlayerNameBox() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FPlayerNameBox::SetString(int i, const char *s) +{ + if (i == 0) + { + strncpy(mPlayerName, s, MAXPLAYERNAME); + mPlayerName[MAXPLAYERNAME] = 0; + return true; + } + return false; +} + +bool FPlayerNameBox::GetString(int i, char *s, int len) +{ + if (i == 0) + { + strncpy(s, mPlayerName, len); + s[len] = 0; + return true; + } + return false; +} + +//============================================================================= +// +// [RH] Width of the border is variable +// +//============================================================================= + +void FPlayerNameBox::DrawBorder (int x, int y, int len) +{ + if (gameinfo.gametype & (GAME_DoomStrifeChex)) + { + int i; + + screen->DrawTexture (TexMan["M_LSLEFT"], x-8, y+7, DTA_Clean, true, TAG_DONE); + + for (i = 0; i < len; i++) + { + screen->DrawTexture (TexMan["M_LSCNTR"], x, y+7, DTA_Clean, true, TAG_DONE); + x += 8; + } + + screen->DrawTexture (TexMan["M_LSRGHT"], x, y+7, DTA_Clean, true, TAG_DONE); + } + else + { + screen->DrawTexture (TexMan["M_FSLOT"], x, y+1, DTA_Clean, true, TAG_DONE); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FPlayerNameBox::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + } + + // Draw player name box + int x = mXpos + mFont->StringWidth(text) + 16 + mFrameSize; + DrawBorder (x, mYpos - mFrameSize, MAXPLAYERNAME+1); + if (!mEntering) + { + screen->DrawText (SmallFont, CR_UNTRANSLATED, x + mFrameSize, mYpos, mPlayerName, + DTA_Clean, true, TAG_DONE); + } + else + { + size_t l = strlen(mEditName); + mEditName[l] = (gameinfo.gametype & (GAME_DoomStrifeChex)) ? '_' : '['; + + screen->DrawText (SmallFont, CR_UNTRANSLATED, x + mFrameSize, mYpos, mEditName, + DTA_Clean, true, TAG_DONE); + + mEditName[l] = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FPlayerNameBox::MenuEvent(int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + strcpy(mEditName, mPlayerName); + mEntering = true; + DMenu *input = new DTextEnterMenu(DMenu::CurrentMenu, mEditName, MAXPLAYERNAME, 2, fromcontroller); + M_ActivateMenu(input); + return true; + } + else if (mkey == MKEY_Input) + { + strcpy(mPlayerName, mEditName); + mEntering = false; + return true; + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + return true; + } + return false; +} + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +FValueTextItem::FValueTextItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, EColorRange valuecolor, FName action, FName values) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mFontColor2 = valuecolor; + mSelection = 0; + if (values != NAME_None) + { + FOptionValues **opt = OptionValues.CheckKey(values); + if (opt != NULL) + { + for(unsigned i=0;i<(*opt)->mValues.Size(); i++) + { + SetString(i, (*opt)->mValues[i].Text); + } + } + } +} + +FValueTextItem::~FValueTextItem() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::SetString(int i, const char *s) +{ + // should actually use the index... + FString str = s; + if (i==0) mSelections.Clear(); + mSelections.Push(str); + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::SetValue(int i, int value) +{ + if (i == 0) + { + mSelection = value; + return true; + } + return false; +} + +bool FValueTextItem::GetValue(int i, int *pvalue) +{ + if (i == 0) + { + *pvalue = mSelection; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::MenuEvent (int mkey, bool fromcontroller) +{ + if (mSelections.Size() > 1) + { + if (mkey == MKEY_Left) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if (--mSelection < 0) mSelection = mSelections.Size() - 1; + return true; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if (++mSelection >= (int)mSelections.Size()) mSelection = 0; + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FValueTextItem::Drawer(bool selected) +{ + const char *text = mText; + + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + + int x = mXpos + mFont->StringWidth(text) + 8; + if (mSelections.Size() > 0) screen->DrawText(mFont, mFontColor2, x, mYpos, mSelections[mSelection], DTA_Clean, true, TAG_DONE); +} + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +FSliderItem::FSliderItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, FName action, int min, int max, int step) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mSelection = 0; + mMinrange = min; + mMaxrange = max; + mStep = step; +} + +FSliderItem::~FSliderItem() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::SetValue(int i, int value) +{ + if (i == 0) + { + mSelection = value; + return true; + } + return false; +} + +bool FSliderItem::GetValue(int i, int *pvalue) +{ + if (i == 0) + { + *pvalue = mSelection; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::MenuEvent (int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Left) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if ((mSelection -= mStep) < mMinrange) mSelection = mMinrange; + return true; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if ((mSelection += mStep) > mMaxrange) mSelection = mMaxrange; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::MouseEvent(int type, int x, int y) +{ + DListMenu *lm = static_cast(DMenu::CurrentMenu); + if (type != DMenu::MOUSE_Click) + { + if (!lm->CheckFocus(this)) return false; + } + if (type == DMenu::MOUSE_Release) + { + lm->ReleaseFocus(); + } + + int slide_left = SmallFont->StringWidth ("Green") + 8 + mXpos; + int slide_right = slide_left + 12*8; // 12 char cells with 8 pixels each. + + if (type == DMenu::MOUSE_Click) + { + if (x < slide_left || x >= slide_right) return true; + } + + x = clamp(x, slide_left, slide_right); + int v = mMinrange + Scale(x - slide_left, mMaxrange - mMinrange, slide_right - slide_left); + if (v != mSelection) + { + mSelection = v; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + if (type == DMenu::MOUSE_Click) + { + lm->SetFocus(this); + } + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSliderItem::DrawSlider (int x, int y) +{ + int range = mMaxrange - mMinrange; + int cur = mSelection - mMinrange; + + x = (x - 160) * CleanXfac + screen->GetWidth() / 2; + y = (y - 100) * CleanYfac + screen->GetHeight() / 2; + + screen->DrawText (ConFont, CR_WHITE, x, y, + "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); + screen->DrawText (ConFont, CR_ORANGE, x + (5 + (int)((cur * 78) / range)) * CleanXfac, y, + "\x13", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSliderItem::Drawer(bool selected) +{ + const char *text = mText; + + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + + int x = SmallFont->StringWidth ("Green") + 8 + mXpos; + DrawSlider (x, mYpos); +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DPlayerMenu : public DListMenu +{ + DECLARE_CLASS(DPlayerMenu, DListMenu) + + int PlayerClassIndex; + FPlayerClass *PlayerClass; + TArray PlayerColorSets; + TArray PlayerSkins; + int mRotation; + + void PickPlayerClass (); + void UpdateColorsets(); + void UpdateSkins(); + void UpdateTranslation(); + void SendNewColor (int red, int green, int blue); + + void PlayerNameChanged(FListMenuItem *li); + void ColorSetChanged (FListMenuItem *li); + void ClassChanged (FListMenuItem *li); + void AutoaimChanged (FListMenuItem *li); + void SkinChanged (FListMenuItem *li); + + +public: + + DPlayerMenu() {} + void Init(DMenu *parent, FListMenuDescriptor *desc); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); +}; + +IMPLEMENT_CLASS(DPlayerMenu) + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) +{ + FListMenuItem *li; + + Super::Init(parent, desc); + PickPlayerClass(); + mRotation = 0; + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_ROTATION, 0); + li->SetValue(FListMenuItemPlayerDisplay::PDF_MODE, 1); + li->SetValue(FListMenuItemPlayerDisplay::PDF_TRANSLATE, 1); + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + if (PlayerClass != NULL && !(GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN) && + players[consoleplayer].userinfo.PlayerClass != -1) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, players[consoleplayer].userinfo.skin); + } + } + + li = GetItem(NAME_Playerbox); + if (li != NULL) + { + li->SetString(0, name); + } + + li = GetItem(NAME_Team); + if (li != NULL) + { + li->SetString(0, "None"); + for(unsigned i=0;iSetString(i+1, Teams[i].GetName()); + } + li->SetValue(0, team == TEAM_NONE? 0 : team + 1); + } + + int mycolorset = players[consoleplayer].userinfo.colorset; + int color = players[consoleplayer].userinfo.color; + + UpdateColorsets(); + + li = GetItem(NAME_Red); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, RPART(color)); + } + + li = GetItem(NAME_Green); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, GPART(color)); + } + + li = GetItem(NAME_Blue); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, BPART(color)); + } + + li = GetItem(NAME_Class); + if (li != NULL) + { + if (PlayerClasses.Size() == 1) + { + li->SetString(0, PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName)); + li->SetValue(0, 0); + } + else + { + li->SetString(0, "Random"); + for(unsigned i=0; i< PlayerClasses.Size(); i++) + { + const char *cls = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + li->SetString(i+1, cls); + } + li->SetValue(0, players[consoleplayer].userinfo.PlayerClass + 1); + } + } + + UpdateSkins(); + + li = GetItem(NAME_Gender); + if (li != NULL) + { + li->SetValue(0, players[consoleplayer].userinfo.gender); + } + + li = GetItem(NAME_Autoaim); + if (li != NULL) + { + int sel = + autoaim == 0 ? 0 : + autoaim <= 0.25 ? 1 : + autoaim <= 0.5 ? 2 : + autoaim <= 1 ? 3 : + autoaim <= 2 ? 4 : + autoaim <= 3 ? 5:6; + li->SetValue(0, sel); + } + + li = GetItem(NAME_Switch); + if (li != NULL) + { + li->SetValue(0, neverswitchonpickup); + } + + li = GetItem(NAME_AlwaysRun); + if (li != NULL) + { + li->SetValue(0, cl_run); + } + + if (mDesc->mSelectedItem < 0) mDesc->mSelectedItem = 1; + +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DPlayerMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_Char && ev->data1 == ' ') + { + // turn the player sprite around + mRotation = 8 - mRotation; + FListMenuItem *li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_ROTATION, mRotation); + } + return true; + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateTranslation() +{ + int PlayerColor = players[consoleplayer].userinfo.color; + int PlayerSkin = players[consoleplayer].userinfo.skin; + int PlayerColorset = players[consoleplayer].userinfo.colorset; + + if (PlayerClass != NULL) + { + PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(PlayerClass - &PlayerClasses[0])); + R_GetPlayerTranslation(PlayerColor, + P_GetPlayerColorSet(PlayerClass->Type->TypeName, PlayerColorset), + &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::PickPlayerClass() +{ + + /* + // What's the point of this? Aren't we supposed to edit the + // userinfo? + if (players[consoleplayer].mo != NULL) + { + PlayerClassIndex = players[consoleplayer].CurrentPlayerClass; + } + else + */ + { + int pclass = 0; + // [GRB] Pick a class from player class list + if (PlayerClasses.Size () > 1) + { + pclass = players[consoleplayer].userinfo.PlayerClass; + + if (pclass < 0) + { + pclass = (MenuTime>>7) % PlayerClasses.Size (); + } + } + PlayerClassIndex = pclass; + } + PlayerClass = &PlayerClasses[PlayerClassIndex]; + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::SendNewColor (int red, int green, int blue) +{ + char command[24]; + + players[consoleplayer].userinfo.color = MAKERGB(red, green, blue); + mysnprintf (command, countof(command), "color \"%02x %02x %02x\"", red, green, blue); + C_DoCommand (command); + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateColorsets() +{ + FListMenuItem *li = GetItem(NAME_Color); + if (li != NULL) + { + int sel = 0; + P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); + li->SetString(0, "Custom"); + for(unsigned i=0;iType->TypeName, PlayerColorSets[i]); + li->SetString(i+1, colorset->Name); + } + int mycolorset = players[consoleplayer].userinfo.colorset; + if (mycolorset != -1) + { + for(unsigned i=0;iSetValue(0, sel); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateSkins() +{ + int sel = 0; + int skin; + FListMenuItem *li = GetItem(NAME_Skin); + if (li != NULL) + { + if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || + players[consoleplayer].userinfo.PlayerClass == -1) + { + li->SetString(0, "Base"); + li->SetValue(0, 0); + skin = 0; + } + else + { + PlayerSkins.Clear(); + for(unsigned i=0;i<(unsigned)numskins; i++) + { + if (PlayerClass->CheckSkin(i)) + { + int j = PlayerSkins.Push(i); + li->SetString(j, skins[i].name); + if (players[consoleplayer].userinfo.skin == i) + { + sel = j; + } + } + } + li->SetValue(0, sel); + skin = PlayerSkins[sel]; + } + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, skin); + } + } + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::PlayerNameChanged(FListMenuItem *li) +{ + char pp[MAXPLAYERNAME+1]; + const char *p; + if (li->GetString(0, pp, MAXPLAYERNAME)) + { + FString command("name \""); + + // Escape any backslashes or quotation marks before sending the name to the console. + for (p = pp; *p != '\0'; ++p) + { + if (*p == '"' || *p == '\\') + { + command << '\\'; + } + command << *p; + } + command << '"'; + C_DoCommand (command); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::ColorSetChanged (FListMenuItem *li) +{ + int sel; + + if (li->GetValue(0, &sel)) + { + int mycolorset = -1; + + if (sel > 0) mycolorset = PlayerColorSets[sel-1]; + + FListMenuItem *red = GetItem(NAME_Red); + FListMenuItem *green = GetItem(NAME_Green); + FListMenuItem *blue = GetItem(NAME_Blue); + + // disable the sliders if a valid colorset is selected + if (red != NULL) red->Enable(mycolorset == -1); + if (green != NULL) green->Enable(mycolorset == -1); + if (blue != NULL) blue->Enable(mycolorset == -1); + + char command[24]; + players[consoleplayer].userinfo.colorset = mycolorset; + mysnprintf(command, countof(command), "colorset %d", mycolorset); + C_DoCommand(command); + UpdateTranslation(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::ClassChanged (FListMenuItem *li) +{ + if (PlayerClasses.Size () == 1) + { + return; + } + + int sel; + + if (li->GetValue(0, &sel)) + { + players[consoleplayer].userinfo.PlayerClass = sel-1; + + cvar_set ("playerclass", + sel == 0 ? "Random" : PlayerClass->Type->Meta.GetMetaString (APMETA_DisplayName)); + + PickPlayerClass(); + UpdateSkins(); + UpdateColorsets(); + UpdateTranslation(); + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::SkinChanged (FListMenuItem *li) +{ + if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || + players[consoleplayer].userinfo.PlayerClass == -1) + { + return; + } + + int sel; + + if (li->GetValue(0, &sel)) + { + sel = PlayerSkins[sel]; + players[consoleplayer].userinfo.skin = sel; + UpdateTranslation(); + cvar_set ("skin", skins[sel].name); + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, sel); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::AutoaimChanged (FListMenuItem *li) +{ + static const float ranges[] = { 0, 0.25, 0.5, 1, 2, 3, 5000 }; + + int sel; + + if (li->GetValue(0, &sel)) + { + autoaim = ranges[sel]; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DPlayerMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int v; + if (mDesc->mSelectedItem >= 0) + { + FListMenuItem *li = mDesc->mItems[mDesc->mSelectedItem]; + if (li->MenuEvent(mkey, fromcontroller)) + { + FName current = li->GetAction(NULL); + switch(current) + { + // item specific handling comes here + + case NAME_Playerbox: + PlayerNameChanged(li); + break; + + case NAME_Team: + if (li->GetValue(0, &v)) + { + team = v==0? TEAM_NONE : v-1; + } + break; + + case NAME_Color: + ColorSetChanged(li); + break; + + case NAME_Red: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (v, GPART(color), BPART(color)); + } + break; + + case NAME_Green: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), v, BPART(color)); + } + break; + + case NAME_Blue: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), GPART(color), v); + } + break; + + case NAME_Class: + ClassChanged(li); + break; + + case NAME_Skin: + SkinChanged(li); + break; + + case NAME_Gender: + if (li->GetValue(0, &v)) + { + cvar_set ("gender", v==0? "male" : v==1? "female" : "other"); + } + break; + + case NAME_Autoaim: + AutoaimChanged(li); + break; + + case NAME_Switch: + if (li->GetValue(0, &v)) + { + neverswitchonpickup = !!v; + } + break; + + case NAME_AlwaysRun: + if (li->GetValue(0, &v)) + { + cl_run = !!v; + } + break; + + default: + break; + } + return true; + } + } + return Super::MenuEvent(mkey, fromcontroller); +} + + +bool DPlayerMenu::MouseEvent(int type, int x, int y) +{ + int v; + FListMenuItem *li = mFocusControl; + bool res = Super::MouseEvent(type, x, y); + if (li == NULL) li = mFocusControl; + if (li != NULL) + { + // Check if the colors have changed + FName current = li->GetAction(NULL); + switch(current) + { + case NAME_Red: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (v, GPART(color), BPART(color)); + } + break; + + case NAME_Green: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), v, BPART(color)); + } + break; + + case NAME_Blue: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), GPART(color), v); + } + break; + } + } + return res; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Ticker () +{ + + Super::Ticker(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Drawer () +{ + + Super::Drawer(); + + const char *str = "PRESS " TEXTCOLOR_WHITE "SPACE"; + screen->DrawText (SmallFont, CR_GOLD, 320 - 32 - 32 - + SmallFont->StringWidth (str)/2, + 50 + 48 + 70, str, + DTA_Clean, true, TAG_DONE); + str = mRotation ? "TO SEE FRONT" : "TO SEE BACK"; + screen->DrawText (SmallFont, CR_GOLD, 320 - 32 - 32 - + SmallFont->StringWidth (str)/2, + 50 + 48 + 70 + SmallFont->GetHeight (), str, + DTA_Clean, true, TAG_DONE); + +} diff --git a/src/menu/readthis.cpp b/src/menu/readthis.cpp new file mode 100644 index 0000000000..388dca716a --- /dev/null +++ b/src/menu/readthis.cpp @@ -0,0 +1,154 @@ +/* +** readthis.cpp +** Help screens +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 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 "menu/menu.h" +#include "v_video.h" +#include "g_level.h" +#include "gi.h" +#include "textures/textures.h" + +class DReadThisMenu : public DMenu +{ + DECLARE_CLASS(DReadThisMenu, DMenu) + int mScreen; + int mInfoTic; + +public: + + DReadThisMenu(DMenu *parent = NULL); + void Drawer(); + bool MenuEvent(int mkey, bool fromcontroller); + bool DimAllowed () { return false; } + bool MouseEvent(int type, int x, int y); +}; + +IMPLEMENT_CLASS(DReadThisMenu) + +//============================================================================= +// +// Read This Menus +// +//============================================================================= + +DReadThisMenu::DReadThisMenu(DMenu *parent) +: DMenu(parent) +{ + mScreen = 1; + mInfoTic = gametic; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DReadThisMenu::Drawer() +{ + FTexture *tex = NULL, *prevpic = NULL; + fixed_t alpha; + + // Did the mapper choose a custom help page via MAPINFO? + if ((level.info != NULL) && level.info->f1[0] != 0) + { + tex = TexMan.FindTexture(level.info->f1); + mScreen = 1; + } + + if (tex == NULL) + { + tex = TexMan[gameinfo.infoPages[mScreen-1].GetChars()]; + } + + if (mScreen > 1) + { + prevpic = TexMan[gameinfo.infoPages[mScreen-2].GetChars()]; + } + + alpha = MIN (Scale (gametic - mInfoTic, OPAQUE, TICRATE/3), OPAQUE); + if (alpha < OPAQUE && prevpic != NULL) + { + screen->DrawTexture (prevpic, 0, 0, + DTA_DestWidth, screen->GetWidth(), + DTA_DestHeight, screen->GetHeight(), + TAG_DONE); + } + screen->DrawTexture (tex, 0, 0, + DTA_DestWidth, screen->GetWidth(), + DTA_DestHeight, screen->GetHeight(), + DTA_Alpha, alpha, + TAG_DONE); + +} + + +//============================================================================= +// +// +// +//============================================================================= + +bool DReadThisMenu::MenuEvent(int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + mScreen++; + mInfoTic = gametic; + if ((level.info != NULL && level.info->f1[0] != 0) || mScreen > int(gameinfo.infoPages.Size())) + { + Close(); + } + return true; + } + else return Super::MenuEvent(mkey, fromcontroller); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DReadThisMenu::MouseEvent(int type, int x, int y) +{ + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; +} + diff --git a/src/menu/videomenu.cpp b/src/menu/videomenu.cpp new file mode 100644 index 0000000000..0d15904635 --- /dev/null +++ b/src/menu/videomenu.cpp @@ -0,0 +1,442 @@ +/* +** videomenu.cpp +** The video modes menu +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 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 + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" +#include "sbar.h" +#include "hardware.h" + +#define NO_IMP +#include "optionmenuitems.h" + + +/*======================================= + * + * Video Modes Menu + * + *=======================================*/ +static void BuildModesList (int hiwidth, int hiheight, int hi_id); +static bool GetSelectedSize (int *width, int *height); +static void SetModesMenu (int w, int h, int bits); +FOptionMenuDescriptor *GetVideoModeMenu(); + +extern bool setmodeneeded; +extern int NewWidth, NewHeight, NewBits; +extern int DisplayBits; + +EXTERN_CVAR (Int, vid_defwidth) +EXTERN_CVAR (Int, vid_defheight) +EXTERN_CVAR (Int, vid_defbits) +EXTERN_CVAR (Bool, fullscreen) +EXTERN_CVAR (Bool, vid_tft) // Defined below + +int testingmode; // Holds time to revert to old mode +int OldWidth, OldHeight, OldBits; +static FIntCVar DummyDepthCvar (NULL, 0, 0); +static BYTE BitTranslate[32]; + +CUSTOM_CVAR (Int, menu_screenratios, 0, CVAR_ARCHIVE) +{ + if (self < 0 || self > 4) + { + self = 3; + } + else if (self == 4 && !vid_tft) + { + self = 3; + } + else + { + BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); + } +} + +CUSTOM_CVAR (Bool, vid_tft, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + FOptionMenuItem *it = opt->GetItem("menu_screenratios"); + if (it != NULL) + { + if (self) + { + it->SetString(FOptionMenuItemOptionBase::OP_VALUES, "RatiosTFT"); + } + else + { + it->SetString(FOptionMenuItemOptionBase::OP_VALUES, "Ratios"); + } + } + } + setsizeneeded = true; + if (StatusBar != NULL) + { + StatusBar->ScreenSizeChanged(); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DVideoModeMenu : public DOptionMenu +{ + DECLARE_CLASS(DVideoModeMenu, DOptionMenu) + +public: + + DVideoModeMenu() + { + SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); + } + + bool MenuEvent(int mkey, bool fromcontroller) + { + if ((mkey == MKEY_Up || mkey == MKEY_Down) && mDesc->mSelectedItem >= 0 && + mDesc->mSelectedItem < (int)mDesc->mItems.Size()) + { + int sel; + bool selected = mDesc->mItems[mDesc->mSelectedItem]->GetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, &sel); + bool res = Super::MenuEvent(mkey, fromcontroller); + if (selected) mDesc->mItems[mDesc->mSelectedItem]->SetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, sel); + return res; + } + return Super::MenuEvent(mkey, fromcontroller); + } + + bool Responder(event_t *ev) + { + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown && + (ev->data1 == 't' || ev->data1 == 'T')) + { + if (!GetSelectedSize (&NewWidth, &NewHeight)) + { + NewWidth = SCREENWIDTH; + NewHeight = SCREENHEIGHT; + } + OldWidth = SCREENWIDTH; + OldHeight = SCREENHEIGHT; + OldBits = DisplayBits; + NewBits = BitTranslate[DummyDepthCvar]; + setmodeneeded = true; + testingmode = I_GetTime(false) + 5 * TICRATE; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + SetModesMenu (NewWidth, NewHeight, NewBits); + return true; + } + return Super::Responder(ev); + } +}; + +IMPLEMENT_CLASS(DVideoModeMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuDescriptor *GetVideoModeMenu() +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_VideoModeMenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + return (FOptionMenuDescriptor *)*desc; + } + return NULL; +} + +//============================================================================= +// +// Set some stuff up for the video modes menu +// +//============================================================================= + +static void BuildModesList (int hiwidth, int hiheight, int hi_bits) +{ + char strtemp[32]; + int i, c; + int width, height, showbits; + bool letterbox=false; + int ratiomatch; + + if (menu_screenratios >= 0 && menu_screenratios <= 4 && menu_screenratios != 3) + { + ratiomatch = menu_screenratios; + } + else + { + ratiomatch = -1; + } + showbits = BitTranslate[DummyDepthCvar]; + + if (Video != NULL) + { + Video->StartModeIterator (showbits, screen->IsFullscreen()); + } + + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + for (i = NAME_res_0; i<= NAME_res_9; i++) + { + FOptionMenuItem *it = opt->GetItem((ENamedName)i); + if (it != NULL) + { + it->SetValue(FOptionMenuScreenResolutionLine::SRL_HIGHLIGHT, -1); + for (c = 0; c < 3; c++) + { + bool haveMode = false; + + if (Video != NULL) + { + while ((haveMode = Video->NextMode (&width, &height, &letterbox)) && + (ratiomatch >= 0 && CheckRatio (width, height) != ratiomatch)) + { + } + } + + if (haveMode) + { + if (width == hiwidth && height == hiheight) + { + it->SetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, c); + it->SetValue(FOptionMenuScreenResolutionLine::SRL_HIGHLIGHT, c); + } + + mysnprintf (strtemp, countof(strtemp), "%dx%d%s", width, height, letterbox?TEXTCOLOR_BROWN" LB":""); + it->SetString(FOptionMenuScreenResolutionLine::SRL_INDEX+c, strtemp); + } + else + { + it->SetString(FOptionMenuScreenResolutionLine::SRL_INDEX+c, ""); + } + } + } + } + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void M_RestoreMode () +{ + NewWidth = OldWidth; + NewHeight = OldHeight; + NewBits = OldBits; + setmodeneeded = true; + testingmode = 0; + SetModesMenu (OldWidth, OldHeight, OldBits); +} + +void M_SetDefaultMode () +{ + // Make current resolution the default + vid_defwidth = SCREENWIDTH; + vid_defheight = SCREENHEIGHT; + vid_defbits = DisplayBits; + testingmode = 0; + SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); +} + + + +//============================================================================= +// +// +// +//============================================================================= + +void M_RefreshModesList () +{ + BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); +} + +void M_InitVideoModesMenu () +{ + int dummy1, dummy2; + size_t currval = 0; + + M_RefreshModesList(); + + for (unsigned int i = 1; i <= 32 && currval < countof(BitTranslate); i++) + { + Video->StartModeIterator (i, screen->IsFullscreen()); + if (Video->NextMode (&dummy1, &dummy2, NULL)) + { + BitTranslate[currval++] = i; + } + } + + /* It doesn't look like this can be anything but DISPLAY_Both, regardless of any other settings. + switch (Video->GetDisplayType ()) + { + case DISPLAY_FullscreenOnly: + case DISPLAY_WindowOnly: + // todo: gray out fullscreen option + default: + break; + } + */ +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool GetSelectedSize (int *width, int *height) +{ + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + int line = opt->mSelectedItem; + int hsel; + FOptionMenuItem *it = opt->mItems[line]; + if (it->GetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, &hsel)) + { + char buffer[32]; + char *breakpt; + if (it->GetString(FOptionMenuScreenResolutionLine::SRL_INDEX+hsel, buffer, sizeof(buffer))) + { + *width = strtol (buffer, &breakpt, 10); + *height = strtol (breakpt+1, NULL, 10); + return true; + } + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_SetVideoMode() +{ + if (!GetSelectedSize (&NewWidth, &NewHeight)) + { + NewWidth = SCREENWIDTH; + NewHeight = SCREENHEIGHT; + } + else + { + testingmode = 1; + setmodeneeded = true; + NewBits = BitTranslate[DummyDepthCvar]; + } + SetModesMenu (NewWidth, NewHeight, NewBits); +} + +//============================================================================= +// +// +// +//============================================================================= + +static int FindBits (int bits) +{ + int i; + + for (i = 0; i < 22; i++) + { + if (BitTranslate[i] == bits) + return i; + } + + return 0; +} + +static void SetModesMenu (int w, int h, int bits) +{ + DummyDepthCvar = FindBits (bits); + + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + FOptionMenuItem *it; + if (testingmode <= 1) + { + it = opt->GetItem(NAME_VMEnterText); + if (it != NULL) it->SetValue(0, 0); + it = opt->GetItem(NAME_VMTestText); + if (it != NULL) it->SetValue(0, 0); + } + else + { + + it = opt->GetItem(NAME_VMTestText); + if (it != NULL) it->SetValue(0, 1); + it = opt->GetItem(NAME_VMEnterText); + if (it != NULL) + { + char strtemp[64]; + mysnprintf (strtemp, countof(strtemp), "TESTING %dx%dx%d", w, h, bits); + it->SetValue(0, 1); + it->SetString(0, strtemp); + } + } + } + BuildModesList (w, h, bits); +} diff --git a/src/mus2midi.cpp b/src/mus2midi.cpp index 63e59e755a..2157b4a89a 100644 --- a/src/mus2midi.cpp +++ b/src/mus2midi.cpp @@ -195,9 +195,9 @@ bool ProduceMIDI (const BYTE *musBuf, int len, 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: diff --git a/src/mus2midi.h b/src/mus2midi.h index 6dc75dc039..f1d8469274 100644 --- a/src/mus2midi.h +++ b/src/mus2midi.h @@ -75,7 +75,4 @@ typedef struct // WORD UsedInstruments[NumInstruments]; } MUSHeader; -bool ProduceMIDI (const BYTE *musBuf, int len, TArray &outFile); -bool ProduceMIDI (const BYTE *musBuf, int len, FILE *outFile); - #endif //__MUS2MIDI_H__ diff --git a/src/namedef.h b/src/namedef.h index 4ee1e7c0f1..9f7e5a87d1 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -383,6 +383,7 @@ xx(Firstsideonly) xx(Transparent) xx(Passuse) xx(Repeatspecial) +xx(Conversation) xx(Playercross) xx(Playeruse) @@ -417,6 +418,7 @@ xx(Gravity) xx(Lightcolor) xx(Fadecolor) xx(Desaturation) +xx(SoundSequence) xx(Silent) xx(Nofallingdamage) xx(Dropactors) @@ -440,5 +442,87 @@ 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) + +// Special menus +xx(Mainmenu) +xx(Episodemenu) +xx(Playerclassmenu) +xx(HexenDefaultPlayerclassmenu) +xx(Skillmenu) +xx(Startgame) +xx(StartgameConfirm) +xx(Loadgamemenu) +xx(Savegamemenu) +xx(Readthismenu) +xx(Optionsmenu) +xx(Quitmenu) +xx(Savemenu) +xx(Playermenu) + +xx(Playerbox) +xx(Team) +xx(Color) +xx(Red) +xx(Green) +xx(Blue) +xx(Class) +xx(Skin) +xx(Gender) +xx(Autoaim) +xx(Switch) +xx(Playerdisplay) +xx(Controlmessage) +xx(Crosshairs) +xx(Colorpickermenu) +xx(Mididevices) +xx(CustomizeControls) +xx(MessageOptions) +xx(AutomapOptions) +xx(ScoreboardOptions) +xx(MapColorMenu) +xx(GameplayOptions) +xx(CompatibilityOptions) +xx(MouseOptions) +xx(JoystickOptions) +xx(SoundOptions) +xx(AdvSoundOptions) +xx(ModReplayerOptions) +xx(VideoOptions) +xx(JoystickConfigMenu) +xx(VMEnterText) +xx(VMTestText) +xx(VideoModeMenu) +xx(res_0) +xx(res_1) +xx(res_2) +xx(res_3) +xx(res_4) +xx(res_5) +xx(res_6) +xx(res_7) +xx(res_8) +xx(res_9) +xx(AlwaysRun) diff --git a/src/nodebuild.cpp b/src/nodebuild.cpp index 63a736e6a7..cbeb23f77b 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,15 @@ 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 D(Printf (PRINT_LOG, "Output subsector %d:\n", Subsectors.Size())); - for (unsigned int i = sub.firstline; i < SegList.Size(); ++i) + 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 ? '+' : ' ', @@ -273,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; @@ -371,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; @@ -506,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; @@ -525,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) { @@ -535,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) @@ -735,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; @@ -749,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; } @@ -769,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 @@ -803,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 @@ -835,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); } @@ -881,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 @@ -1038,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 d0d59e7062..da82e26a23 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 2b7f6bec6b..3fd1da2726 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 05e4684a82..01c469093c 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 f363cc4ec2..c4546bb5be 100644 --- a/src/nodebuild_extract.cpp +++ b/src/nodebuild_extract.cpp @@ -54,7 +54,7 @@ #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) { @@ -99,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; } } } @@ -126,6 +131,7 @@ 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]; @@ -139,10 +145,12 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, 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]; + } D(Printf("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount)); @@ -153,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; @@ -164,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; @@ -315,7 +407,7 @@ int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t * 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; @@ -323,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; @@ -382,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; @@ -400,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; @@ -416,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 a8a618b9ff..ef79ac4d95 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 9413ae4bbf..ade5b5c122 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/p_3dfloors.cpp b/src/p_3dfloors.cpp index 39d6be1021..25dfd33185 100644 --- a/src/p_3dfloors.cpp +++ b/src/p_3dfloors.cpp @@ -689,5 +689,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 5219a58ae8..197b90f851 100644 --- a/src/p_3dfloors.h +++ b/src/p_3dfloors.h @@ -130,6 +130,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 @@ -161,6 +163,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 eb69c60be8..5565929914 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); @@ -2135,7 +2136,7 @@ do_count: { // Again, with decorate replacements replacemented = true; - PClass *newkind = kind->ActorInfo->GetReplacement()->Class; + PClass *newkind = kind->GetReplacement(); if (newkind != kind) { kind = newkind; @@ -2431,6 +2432,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, @@ -2458,6 +2546,7 @@ enum APROP_Score = 22, APROP_Notrigger = 23, APROP_DamageFactor = 24, + APROP_MasterTID = 25, }; // These are needed for ACS's APROP_RenderStyle @@ -2574,9 +2663,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; @@ -2626,25 +2721,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); @@ -2671,6 +2759,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); @@ -2696,6 +2785,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; } } @@ -2726,10 +2816,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: @@ -2941,6 +3033,9 @@ enum EACSFunctions ACSF_SoundSequenceOnActor, ACSF_SoundSequenceOnSector, ACSF_SoundSequenceOnPolyobj, + ACSF_GetPolyobjX, + ACSF_GetPolyobjY, + ACSF_CheckSight, }; int DLevelScript::SideFromID(int id, int side) @@ -3344,6 +3439,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; } @@ -3379,6 +3542,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; @@ -3493,7 +3670,8 @@ int DLevelScript::RunScript () break; case PCD_PUSHNUMBER: - PushToStack (NEXTWORD); + PushToStack (uallong(pc[0])); + pc++; break; case PCD_PUSHBYTE: @@ -3550,7 +3728,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: @@ -3595,35 +3773,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; @@ -4503,12 +4681,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--; @@ -4530,7 +4708,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; @@ -4552,7 +4731,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; @@ -4567,7 +4746,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; @@ -4594,7 +4773,8 @@ int DLevelScript::RunScript () case PCD_TAGWAITDIRECT: state = SCRIPT_TagWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; break; case PCD_POLYWAIT: @@ -4605,7 +4785,8 @@ int DLevelScript::RunScript () case PCD_POLYWAITDIRECT: state = SCRIPT_PolyWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; break; case PCD_CHANGEFLOOR: @@ -4614,7 +4795,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; @@ -4624,7 +4805,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; @@ -4686,7 +4867,7 @@ int DLevelScript::RunScript () case PCD_IFNOTGOTO: if (!STACK(1)) - pc = activeBehavior->Ofs2PC (*pc); + pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; @@ -4708,7 +4889,8 @@ int DLevelScript::RunScript () case PCD_SCRIPTWAITDIRECT: state = SCRIPT_ScriptWait; - statedata = NEXTWORD; + statedata = uallong(pc[0]); + pc++; PutLast (); break; @@ -4718,14 +4900,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; @@ -4733,7 +4915,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) { @@ -4741,7 +4923,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; } @@ -4917,7 +5099,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]; @@ -5061,7 +5243,7 @@ int DLevelScript::RunScript () break; case PCD_SETFONTDIRECT: - DoSetFont (pc[0]); + DoSetFont (uallong(pc[0])); pc++; break; @@ -5350,7 +5532,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; @@ -5361,7 +5543,7 @@ int DLevelScript::RunScript () break; case PCD_SETAIRCONTROLDIRECT: - level.aircontrol = pc[0]; + level.aircontrol = uallong(pc[0]); pc++; G_AirControlChanged (); break; @@ -5372,7 +5554,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; @@ -5382,7 +5564,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; @@ -5438,7 +5620,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; @@ -5468,7 +5650,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; @@ -5483,7 +5665,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; @@ -5587,7 +5769,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; @@ -5602,7 +5784,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; diff --git a/src/p_buildmap.cpp b/src/p_buildmap.cpp index 86d7575ad4..afc6d52775 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 4dd072a062..c9aec4f37d 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 8e32ee8abd..a2c287a422 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -41,7 +41,6 @@ #include "w_wad.h" #include "cmdlib.h" #include "s_sound.h" -#include "m_menu.h" #include "v_text.h" #include "v_video.h" #include "m_random.h" @@ -56,9 +55,13 @@ #include "d_net.h" #include "g_level.h" #include "d_event.h" +#include "d_gui.h" #include "doomstat.h" #include "c_console.h" #include "sbar.h" +#include "farchive.h" +#include "p_lnspec.h" +#include "menu/menu.h" // The conversations as they exist inside a SCRIPTxx lump. struct Response @@ -103,30 +106,28 @@ 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. -const 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 int ConversationMenuY; -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); static bool DrawConversationMenu (); -static void PickConversationReply (); +static void PickConversationReply (int replyindex); static void CleanupConversationMenu (); -static void ConversationMenuEscaped (); static void TerminalResponse (const char *str); -static FStrifeDialogueNode *CurNode, *PrevNode; -static FBrokenLines *DialogueLines; +static FStrifeDialogueNode *PrevNode; #define NUM_RANDOM_LINES 10 #define NUM_RANDOM_GOODBYES 3 @@ -139,13 +140,42 @@ static FBrokenLines *DialogueLines; // //============================================================================ -static const PClass *GetStrifeType (int typenum) +void SetStrifeType(int convid, const PClass *Class) { - if (typenum > 0 && typenum < 1001) + StrifeTypes[convid] = Class; +} + +void SetConversation(int convid, const PClass *Class, int dlgindex) +{ + if (convid != -1) { - return StrifeTypes[typenum]; + DialogueRoots[convid] = dlgindex; } - return NULL; + if (Class != NULL) + { + ClassRoots[Class->TypeName] = dlgindex; + } +} + +const PClass *GetStrifeType (int typenum) +{ + const PClass **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 +188,11 @@ static const 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 +200,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 +228,12 @@ 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 +242,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; } //============================================================================ @@ -316,12 +356,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; } @@ -345,9 +387,11 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker node->DropType = 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] = GetStrifeType (speech.ItemCheck[j]); + node->ItemCheck[j].Item = GetStrifeType (speech.ItemCheck[j]); + node->ItemCheck[j].Amount = -1; } node->ItemCheckNode = speech.Link; node->Children = NULL; @@ -385,12 +429,14 @@ static FStrifeDialogueNode *ReadTeaserNode (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; } @@ -419,9 +465,11 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker node->DropType = 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,21 +524,20 @@ 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 = 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] = GetStrifeType (rsp->Item[k]); - reply->ItemCheckAmount[k] = rsp->Count[k]; + reply->ItemCheck[k].Item = GetStrifeType (rsp->Item[k]); + reply->ItemCheck[k].Amount = rsp->Count[k]; } - // ReplyLines is calculated when the menu is shown. It is just Reply - // with word wrap turned on. - reply->ReplyLines = NULL; - // If the first item check has a positive amount required, then // add that to the reply string. Otherwise, use the reply as-is. if (rsp->Count[0] > 0) @@ -517,7 +564,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); } @@ -561,7 +608,6 @@ FStrifeDialogueReply::~FStrifeDialogueReply () if (Reply != NULL) delete[] Reply; if (QuickYes != NULL) delete[] QuickYes; if (QuickNo != NULL) delete[] QuickNo; - if (ReplyLines != NULL) V_FreeBrokenLines (ReplyLines); } //============================================================================ @@ -649,6 +695,372 @@ CUSTOM_CVAR(Float, dlg_musicvolume, 1.0f, CVAR_ARCHIVE) else if (self > 1.f) self = 1.f; } +//============================================================================ +// +// The conversation menu +// +//============================================================================ + +class DConversationMenu : public DMenu +{ + DECLARE_CLASS(DConversationMenu, DMenu) + + FString mSpeaker; + FBrokenLines *mDialogueLines; + TArray mResponseLines; + TArray mResponses; + bool mShowGold; + FStrifeDialogueNode *mCurNode; + int mYpos; + +public: + static int mSelection; + + //============================================================================= + // + // + // + //============================================================================= + + DConversationMenu(FStrifeDialogueNode *CurNode) + { + menuactive = MENU_OnNoPause; + mCurNode = CurNode; + mDialogueLines = NULL; + mShowGold = false; + + // Format the speaker's message. + const char * toSay = CurNode->Dialogue; + if (strncmp (toSay, "RANDOM_", 7) == 0) + { + FString dlgtext; + + dlgtext.Format("TXT_%s_%02d", toSay, 1+(pr_randomspeech() % NUM_RANDOM_LINES)); + toSay = GStrings[dlgtext]; + if (toSay == NULL) + { + 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 = "."; + } + mDialogueLines = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay); + + FStrifeDialogueReply *reply; + int i,j; + for (reply = CurNode->Children, i = 1; reply != NULL; reply = reply->Next) + { + if (reply->Reply == NULL) + { + continue; + } + mShowGold |= reply->NeedsGold; + + const char *ReplyText = reply->Reply; + if (ReplyText[0] == '$') + { + ReplyText = GStrings(ReplyText + 1); + } + FBrokenLines *ReplyLines = V_BreakLines (SmallFont, 320-50-10, ReplyText); + + mResponses.Push(mResponseLines.Size()); + for (j = 0; ReplyLines[j].Width >= 0; ++j) + { + mResponseLines.Push(ReplyLines[j].Text); + } + ++i; + V_FreeBrokenLines (ReplyLines); + } + char goodbye[25]; + mysnprintf(goodbye, countof(goodbye), "TXT_RANDOMGOODBYE_%d", 1+(pr_randomspeech() % NUM_RANDOM_GOODBYES)); + const char *goodbyestr = GStrings[goodbye]; + if (goodbyestr == NULL) goodbyestr = "Bye."; + mResponses.Push(mResponseLines.Size()); + mResponseLines.Push(FString(goodbyestr)); + + // Determine where the top of the reply list should be positioned. + i = OptionSettings.mLinespacing; + mYpos = MIN (140, 192 - mResponseLines.Size() * i); + for (i = 0; mDialogueLines[i].Width >= 0; ++i) + { } + i = 44 + i * 10; + if (mYpos - 100 < i - screen->GetHeight() / CleanYfac / 2) + { + mYpos = i - screen->GetHeight() / CleanYfac / 2 + 100; + } + ConversationMenuY = mYpos; + //ConversationMenu.indent = 50; + } + + //============================================================================= + // + // + // + //============================================================================= + + void Destroy() + { + V_FreeBrokenLines(mDialogueLines); + mDialogueLines = NULL; + I_SetMusicVolume (1.f); + } + + bool DimAllowed() + { + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == MKEY_Up) + { + if (--mSelection < 0) mSelection = mResponses.Size() - 1; + return true; + } + else if (mkey == MKEY_Down) + { + if (++mSelection >= (int)mResponses.Size()) mSelection = 0; + return true; + } + else if (mkey == MKEY_Back) + { + Close(); + return true; + } + else if (mkey == MKEY_Enter) + { + if ((unsigned)mSelection >= mResponses.Size()) + { + Net_WriteByte(DEM_CONVCLOSE); + } + else + { + // Send dialogue and reply numbers across the wire. + assert((unsigned)mCurNode->ThisNodeNum < StrifeDialogues.Size()); + assert(StrifeDialogues[mCurNode->ThisNodeNum] == mCurNode); + Net_WriteByte(DEM_CONVREPLY); + Net_WriteWord(mCurNode->ThisNodeNum); + Net_WriteByte(mSelection); + } + Close(); + return true; + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MouseEvent(int type, int x, int y) + { + int sel = -1; + int fh = SmallFont->GetHeight(); + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; + + if (x >= 24 && x <= 320-24 && y >= mYpos && y < mYpos + fh * (int)mResponseLines.Size()) + { + sel = (y - mYpos) / fh; + for(unsigned i=0;i sel) + { + sel = i-1; + break; + } + } + } + if (sel != -1 && sel != mSelection) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mSelection = sel; + if (type == MOUSE_Release) + { + return MenuEvent(MKEY_Enter, true); + } + return true; + } + + + //============================================================================= + // + // + // + //============================================================================= + + bool Responder(event_t *ev) + { + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_Char && ev->data1 >= '0' && ev->data1 <= '9') + { // Activate an item of type numberedmore (dialogue only) + mSelection = ev->data1 == '0' ? 10 : ev->data1 - '0'; + return MenuEvent(MKEY_Enter, false); + } + return Super::Responder(ev); + } + + //============================================================================ + // + // DrawConversationMenu + // + //============================================================================ + + void Drawer() + { + const char *speakerName; + int i, x, y, linesize; + int width, fontheight; + int labelofs; + + player_t *cp = &players[consoleplayer]; + + assert (mDialogueLines != NULL); + assert (mCurNode != NULL); + + FStrifeDialogueNode *CurNode = mCurNode; + + if (CurNode == NULL) + { + Close (); + return; + } + + // [CW] Freeze the game depending on MAPINFO options. + if (ConversationPauseTic < gametic && !multiplayer && !(level.flags2 & LEVEL2_CONV_SINGLE_UNFREEZE)) + { + menuactive = MENU_On; + } + + if (CurNode->Backdrop.isValid()) + { + screen->DrawTexture (TexMan(CurNode->Backdrop), 0, 0, DTA_320x200, true, TAG_DONE); + } + x = 16 * screen->GetWidth() / 320; + y = 16 * screen->GetHeight() / 200; + linesize = 10 * CleanYfac; + + // Who is talking to you? + if (CurNode->SpeakerName != NULL) + { + speakerName = CurNode->SpeakerName; + } + else + { + speakerName = cp->ConversationNPC->GetTag("Person"); + } + + // Dim the screen behind the dialogue (but only if there is no backdrop). + if (!CurNode->Backdrop.isValid()) + { + for (i = 0; mDialogueLines[i].Width >= 0; ++i) + { } + screen->Dim (0, 0.45f, 14 * screen->GetWidth() / 320, 13 * screen->GetHeight() / 200, + 308 * screen->GetWidth() / 320 - 14 * screen->GetWidth () / 320, + speakerName == NULL ? linesize * i + 6 * CleanYfac + : linesize * i + 6 * CleanYfac + linesize * 3/2); + } + + // Dim the screen behind the PC's choices. + + screen->Dim (0, 0.45f, (24-160) * CleanXfac + screen->GetWidth()/2, + (mYpos - 2 - 100) * CleanYfac + screen->GetHeight()/2, + 272 * CleanXfac, + MIN(mResponseLines.Size() * OptionSettings.mLinespacing + 4, 200 - mYpos) * CleanYfac); + + if (speakerName != NULL) + { + screen->DrawText (SmallFont, CR_WHITE, x, y, speakerName, + DTA_CleanNoMove, true, TAG_DONE); + y += linesize * 3 / 2; + } + x = 24 * screen->GetWidth() / 320; + for (i = 0; mDialogueLines[i].Width >= 0; ++i) + { + screen->DrawText (SmallFont, CR_UNTRANSLATED, x, y, mDialogueLines[i].Text, + DTA_CleanNoMove, true, TAG_DONE); + y += linesize; + } + + if (ShowGold) + { + AInventory *coin = cp->ConversationPC->FindInventory (RUNTIME_CLASS(ACoin)); + char goldstr[32]; + + mysnprintf (goldstr, countof(goldstr), "%d", coin != NULL ? coin->Amount : 0); + screen->DrawText (SmallFont, CR_GRAY, 21, 191, goldstr, DTA_320x200, true, + DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); + screen->DrawTexture (TexMan(((AInventory *)GetDefaultByType (RUNTIME_CLASS(ACoin)))->Icon), + 3, 190, DTA_320x200, true, + DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); + screen->DrawText (SmallFont, CR_GRAY, 20, 190, goldstr, DTA_320x200, true, TAG_DONE); + screen->DrawTexture (TexMan(((AInventory *)GetDefaultByType (RUNTIME_CLASS(ACoin)))->Icon), + 2, 189, DTA_320x200, true, TAG_DONE); + } + + y = mYpos; + labelofs = OptionSettings.mLabelOffset; + y -= labelofs; + fontheight = OptionSettings.mLinespacing; + + int response = 0; + for (i = 0; i < (int)mResponseLines.Size(); i++, y += fontheight) + { + width = SmallFont->StringWidth(mResponseLines[i]); + x = 64; + + screen->DrawText (SmallFont, CR_GREEN, x, y, mResponseLines[i], DTA_Clean, true, TAG_DONE); + + if (i == mResponses[response]) + { + char tbuf[16]; + + response++; + mysnprintf (tbuf, countof(tbuf), "%d.", response); + x = 50 - SmallFont->StringWidth (tbuf); + screen->DrawText (SmallFont, CR_GREY, x, y, tbuf, DTA_Clean, true, TAG_DONE); + + if (response == mSelection+1) + { + int color = ((DMenu::MenuTime%8) < 4) || DMenu::CurrentMenu != this ? CR_RED:CR_GREY; + + x = (50 + 3 - 160) * CleanXfac + screen->GetWidth() / 2; + int yy = (y-1+labelofs - 100) * CleanYfac + screen->GetHeight() / 2; + screen->DrawText (ConFont, color, x, yy, "\xd", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); + } + } + } + } + +}; + +IMPLEMENT_ABSTRACT_CLASS(DConversationMenu) +int DConversationMenu::mSelection; // needs to be preserved if the same dialogue is restarted + + //============================================================================ // // P_StartConversation @@ -660,10 +1072,7 @@ CUSTOM_CVAR(Float, dlg_musicvolume, 1.0f, CVAR_ARCHIVE) void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveangle) { AActor *oldtarget; - FStrifeDialogueReply *reply; - menuitem_t item; - const char *toSay; - int i, j; + int i; // Make sure this is actually a player. if (pc->player == NULL) return; @@ -713,13 +1122,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 @@ -737,84 +1153,19 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang S_Sound (npc, CHAN_VOICE|CHAN_NOPAUSE, CurNode->SpeakerVoice, 1, ATTN_NORM); } - // Set up the menu - ::CurNode = CurNode; // only set the global variable for the consoleplayer - ConversationMenu.PreDraw = DrawConversationMenu; - ConversationMenu.EscapeHandler = ConversationMenuEscaped; + DConversationMenu *cmenu = new DConversationMenu(CurNode); - // Format the speaker's message. - toSay = CurNode->Dialogue; - if (strncmp (toSay, "RANDOM_", 7) == 0) - { - FString dlgtext; - dlgtext.Format("TXT_%s_%02d", toSay, 1+(pr_randomspeech() % NUM_RANDOM_LINES)); - toSay = GStrings[dlgtext]; - if (toSay == NULL) - { - toSay = "Go away!"; // Ok, it's lame - but it doesn't look like an error to the player. ;) - } - } - DialogueLines = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay); - - // Fill out the possible choices - ShowGold = false; - item.type = numberedmore; - item.e.mfunc = PickConversationReply; - for (reply = CurNode->Children, i = 1; reply != NULL; reply = reply->Next) - { - if (reply->Reply == NULL) - { - continue; - } - ShowGold |= reply->NeedsGold; - reply->ReplyLines = V_BreakLines (SmallFont, 320-50-10, reply->Reply); - for (j = 0; reply->ReplyLines[j].Width >= 0; ++j) - { - item.label = reply->ReplyLines[j].Text.LockBuffer(); - item.b.position = j == 0 ? i : 0; - item.c.extra = reply; - ConversationItems.Push (item); - } - ++i; - } - char goodbye[25]; - mysnprintf(goodbye, countof(goodbye), "TXT_RANDOMGOODBYE_%d", 1+(pr_randomspeech() % NUM_RANDOM_GOODBYES)); - item.label = (char*)GStrings[goodbye]; - if (item.label == NULL) item.label = "Bye."; - item.b.position = i; - item.c.extra = NULL; - ConversationItems.Push (item); - - // Determine where the top of the reply list should be positioned. - i = (gameinfo.gametype & GAME_Raven) ? 9 : 8; - ConversationMenu.y = MIN (140, 192 - ConversationItems.Size() * i); - for (i = 0; DialogueLines[i].Width >= 0; ++i) - { } - i = 44 + i * 10; - if (ConversationMenu.y - 100 < i - screen->GetHeight() / CleanYfac / 2) - { - ConversationMenu.y = i - screen->GetHeight() / CleanYfac / 2 + 100; - } - ConversationMenu.indent = 50; - - // Finish setting up the menu - ConversationMenu.items = &ConversationItems[0]; - ConversationMenu.numitems = ConversationItems.Size(); if (CurNode != PrevNode) { // Only reset the selection if showing a different menu. - ConversationMenu.lastOn = 0; + DConversationMenu::mSelection = 0; PrevNode = CurNode; } - ConversationMenu.DontDim = true; // And open the menu M_StartControlPanel (false); - OptionsActive = true; - menuactive = MENU_OnNoPause; + M_ActivateMenu(cmenu); ConversationPauseTic = gametic + 20; - - M_SwitchMenu (&ConversationMenu); } } @@ -842,193 +1193,6 @@ void P_ResumeConversation () } } -//============================================================================ -// -// DrawConversationMenu -// -//============================================================================ - -static bool DrawConversationMenu () -{ - const char *speakerName; - int i, x, y, linesize; - int width, fontheight; - menuitem_t *item; - int labelofs; - - player_t *cp = &players[consoleplayer]; - - assert (DialogueLines != NULL); - assert (CurNode != NULL); - - if (CurNode == NULL) - { - M_ClearMenus (); - return true; - } - - // [CW] Freeze the game depending on MAPINFO options. - if (ConversationPauseTic < gametic && !multiplayer && !(level.flags2 & LEVEL2_CONV_SINGLE_UNFREEZE)) - { - menuactive = MENU_On; - } - - if (CurNode->Backdrop.isValid()) - { - screen->DrawTexture (TexMan(CurNode->Backdrop), 0, 0, DTA_320x200, true, TAG_DONE); - } - x = 16 * screen->GetWidth() / 320; - y = 16 * screen->GetHeight() / 200; - linesize = 10 * CleanYfac; - - // Who is talking to you? - if (CurNode->SpeakerName != NULL) - { - speakerName = CurNode->SpeakerName; - } - else - { - speakerName = cp->ConversationNPC->GetTag("Person"); - } - - // Dim the screen behind the dialogue (but only if there is no backdrop). - if (!CurNode->Backdrop.isValid()) - { - for (i = 0; DialogueLines[i].Width >= 0; ++i) - { } - screen->Dim (0, 0.45f, 14 * screen->GetWidth() / 320, 13 * screen->GetHeight() / 200, - 308 * screen->GetWidth() / 320 - 14 * screen->GetWidth () / 320, - speakerName == NULL ? linesize * i + 6 * CleanYfac - : linesize * i + 6 * CleanYfac + linesize * 3/2); - } - - // Dim the screen behind the PC's choices. - screen->Dim (0, 0.45f, (24-160) * CleanXfac + screen->GetWidth()/2, - (ConversationMenu.y - 2 - 100) * CleanYfac + screen->GetHeight()/2, - 272 * CleanXfac, - MIN(ConversationMenu.numitems * (gameinfo.gametype & GAME_Raven ? 9 : 8) + 4, - 200 - ConversationMenu.y) * CleanYfac); - - if (speakerName != NULL) - { - screen->DrawText (SmallFont, CR_WHITE, x, y, speakerName, - DTA_CleanNoMove, true, TAG_DONE); - y += linesize * 3 / 2; - } - x = 24 * screen->GetWidth() / 320; - for (i = 0; DialogueLines[i].Width >= 0; ++i) - { - screen->DrawText (SmallFont, CR_UNTRANSLATED, x, y, DialogueLines[i].Text, - DTA_CleanNoMove, true, TAG_DONE); - y += linesize; - } - - if (ShowGold) - { - AInventory *coin = cp->ConversationPC->FindInventory (RUNTIME_CLASS(ACoin)); - char goldstr[32]; - - mysnprintf (goldstr, countof(goldstr), "%d", coin != NULL ? coin->Amount : 0); - screen->DrawText (SmallFont, CR_GRAY, 21, 191, goldstr, DTA_320x200, true, - DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); - screen->DrawTexture (TexMan(((AInventory *)GetDefaultByType (RUNTIME_CLASS(ACoin)))->Icon), - 3, 190, DTA_320x200, true, - DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); - screen->DrawText (SmallFont, CR_GRAY, 20, 190, goldstr, DTA_320x200, true, TAG_DONE); - screen->DrawTexture (TexMan(((AInventory *)GetDefaultByType (RUNTIME_CLASS(ACoin)))->Icon), - 2, 189, DTA_320x200, true, TAG_DONE); - } - - y = CurrentMenu->y; - - if (gameinfo.gametype & GAME_Raven) - { - labelofs = 2; - y -= 2; - fontheight = 9; - } - else - { - labelofs = 0; - fontheight = 8; - } - for (i = 0; i < CurrentMenu->numitems; i++, y += fontheight) - { - item = CurrentMenu->items + i; - - width = SmallFont->StringWidth(item->label); - x = CurrentMenu->indent + 14; - - screen->DrawText (SmallFont, CR_GREEN, x, y, item->label, DTA_Clean, true, TAG_DONE); - - if (item->b.position != 0) - { - char tbuf[16]; - - mysnprintf (tbuf, countof(tbuf), "%d.", item->b.position); - x = CurrentMenu->indent - SmallFont->StringWidth (tbuf); - screen->DrawText (SmallFont, CR_GREY, x, y, tbuf, DTA_Clean, true, TAG_DONE); - } - - if (i == CurrentItem && - (skullAnimCounter < 6 || menuactive == MENU_WaitKey)) - { - int x = (CurrentMenu->indent + 3 - 160) * CleanXfac + screen->GetWidth() / 2; - int yy = (y-1+labelofs - 100) * CleanYfac + screen->GetHeight() / 2; - screen->DrawText (ConFont, CR_RED, x, yy, "\xd", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); - } - } - return true; -} - - -//============================================================================ -// -// PickConversationReply -// -// Run only on the local machine with the conversation menu up. -// -//============================================================================ - -static void PickConversationReply () -{ - FStrifeDialogueReply *reply = (FStrifeDialogueReply *)ConversationItems[ConversationMenu.lastOn].c.extra; - FStrifeDialogueReply *replyscan; - int replynum = 0; - - assert(CurNode->ThisNodeNum >= 0 && CurNode->ThisNodeNum < 65536); - assert(StrifeDialogues[CurNode->ThisNodeNum] == CurNode); - - // Determine reply number for netcode. - if (reply == NULL) - { - replyscan = NULL; - } - else - { - for (replyscan = CurNode->Children; replyscan != NULL && replyscan != reply; ++replynum, replyscan = replyscan->Next) - { } - } - - M_ClearMenus (); - if (replyscan == NULL) - { - Net_WriteByte(DEM_CONVCLOSE); - } - else - { - // Send dialogue and reply numbers across the wire. - assert(replynum < 256); - Net_WriteByte(DEM_CONVREPLY); - Net_WriteWord(CurNode->ThisNodeNum); - Net_WriteByte(replynum); - } - CleanupConversationMenu (); -} - //============================================================================ // // HandleReply @@ -1063,9 +1227,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 +1296,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 +1342,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]; @@ -1212,27 +1392,6 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply void CleanupConversationMenu () { - FStrifeDialogueReply *reply; - - if (CurNode != NULL) - { - for (reply = CurNode->Children; reply != NULL; reply = reply->Next) - { - if (reply->ReplyLines != NULL) - { - V_FreeBrokenLines (reply->ReplyLines); - reply->ReplyLines = NULL; - } - } - CurNode = NULL; - } - if (DialogueLines != NULL) - { - V_FreeBrokenLines (DialogueLines); - DialogueLines = NULL; - } - ConversationItems.Clear (); - I_SetMusicVolume (1.f); } //============================================================================ @@ -1298,6 +1457,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); @@ -1307,7 +1472,7 @@ static void TerminalResponse (const char *str) // their dialogue screen. I think most other conversations use this // only as a response for terminating the dialogue. StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, str, - float(CleanWidth/2) + 0.4f, float(ConversationMenu.y - 110 + CleanHeight/2), CleanWidth, -CleanHeight, + float(CleanWidth/2) + 0.4f, float(ConversationMenuY - 110 + CleanHeight/2), CleanWidth, -CleanHeight, CR_UNTRANSLATED, 3, 1), MAKE_ID('T','A','L','K')); } else @@ -1316,3 +1481,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 13658e0f08..d4fa600098 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -1,22 +1,24 @@ #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 +{ + const PClass *Item; + int Amount; +}; + // FStrifeDialogueNode holds text an NPC says to the player struct FStrifeDialogueNode { ~FStrifeDialogueNode (); const PClass *DropType; - const PClass *ItemCheck[3]; + TArray ItemCheck; int ThisNodeNum; // location of this node in StrifeDialogues int ItemCheckNode; // index into StrifeDialogues @@ -36,28 +38,30 @@ struct FStrifeDialogueReply FStrifeDialogueReply *Next; const PClass *GiveType; - const PClass *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; - - FBrokenLines *ReplyLines; }; 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 const PClass *StrifeTypes[1001]; - struct MapData; +void SetStrifeType(int convid, const PClass *Class); +void SetConversation(int convid, const PClass *Class, int dlgindex); +const PClass *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 +70,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 ccfd61c5d5..e82adbe4c0 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); } diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index d3ddd8ddd7..1cf28aa5d9 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; } @@ -1975,14 +1975,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); + } + } } } @@ -2688,7 +2691,7 @@ void A_FaceTarget (AActor *self, angle_t max_turn) // 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 < abs(self->angle - target_angle))) + if (max_turn && (max_turn < (angle_t)abs(self->angle - target_angle))) { if (self->angle > target_angle) { diff --git a/src/p_enemy.h b/src/p_enemy.h index 1d80e08e37..f996a0c52e 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 diff --git a/src/p_floor.cpp b/src/p_floor.cpp index 00407a36c6..3fff563f89 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 0000000000..382f026b28 --- /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 7dfd7398f3..d4db864361 100644 --- a/src/p_interaction.cpp +++ b/src/p_interaction.cpp @@ -1369,6 +1369,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) diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index fb0bd31ab7..a53356a8ef 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) { @@ -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 f0942c4fde..050e0fe7bf 100644 --- a/src/p_lnspec.h +++ b/src/p_lnspec.h @@ -114,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 2092f8f4f8..2997f31216 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -237,6 +237,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 { @@ -424,7 +425,7 @@ void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *target, 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, const PClass *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, const PClass *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); @@ -455,9 +456,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; } // @@ -479,6 +481,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); @@ -504,8 +507,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 @@ -536,12 +540,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 34f396317e..f578ec7feb 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -655,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; @@ -1088,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)) @@ -1109,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)) @@ -3332,7 +3352,7 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, (t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST)); // We need to check the defaults of the replacement here - AActor *puffDefaults = GetDefaultByType(pufftype->ActorInfo->GetReplacement()->Class); + AActor *puffDefaults = GetDefaultByType(pufftype->GetReplacement()); // if the puff uses a non-standard damage type this will override default and melee damage type. // All other explicitly passed damage types (currenty only MDK) will be preserved. @@ -3471,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) @@ -3490,7 +3517,7 @@ 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) { @@ -3748,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, const PClass *puffclass, bool pierce) +void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, float maxdiff, bool silent, const PClass *puffclass, bool pierce, angle_t angleoffset, angle_t pitchoffset) { fixed_t vx, vy, vz; angle_t angle, pitch; @@ -3759,8 +3786,8 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color if (puffclass == NULL) puffclass = PClass::FindClass(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]); @@ -3780,7 +3807,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]; @@ -3792,7 +3819,7 @@ void P_RailAttack (AActor *source, int damage, int offset, int color1, int color int flags; AActor *puffDefaults = puffclass == NULL? - NULL : GetDefaultByType (puffclass->ActorInfo->GetReplacement()->Class); + NULL : GetDefaultByType (puffclass->GetReplacement()); if (puffDefaults != NULL && puffDefaults->flags6 & MF6_NOTRIGGER) flags = 0; else flags = TRACE_PCross|TRACE_Impact; @@ -3836,10 +3863,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); } @@ -3849,7 +3880,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); } } diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index f7874c345c..9f4ddb39a0 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 *); @@ -301,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); } @@ -321,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) ) { @@ -459,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 { @@ -692,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; diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 6fd05c040d..4d6cdc765a 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -172,6 +172,7 @@ IMPLEMENT_POINTY_CLASS (AActor) DECLARE_POINTER (Inventory) DECLARE_POINTER (LastHeard) DECLARE_POINTER (master) + DECLARE_POINTER (Poisoner) END_POINTERS AActor::~AActor () @@ -346,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) @@ -355,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"); } } @@ -1046,7 +1011,7 @@ bool AActor::Grind(bool items) if (i != NULL) { - i = i->ActorInfo->GetReplacement()->Class; + i = i->GetReplacement(); const AActor *defaults = GetDefaultByType (i); if (defaults->SpawnState == NULL || @@ -1485,8 +1450,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; @@ -1560,7 +1525,7 @@ bool P_SeekerMissile (AActor *actor, angle_t thresh, angle_t turnMax, bool preci } else { - angle_t pitch; + 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()); @@ -3172,41 +3137,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; @@ -3216,8 +3167,8 @@ void AActor::Tick () } if (dopush) { - velx += floorplane->a; - vely += floorplane->b; + velx += floorplane.a; + vely += floorplane.b; } } } @@ -3314,6 +3265,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 @@ -3478,6 +3441,7 @@ bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash) return false; // we did the splash ourselves } + //========================================================================== // // P_SpawnMobj @@ -3497,13 +3461,24 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t } if (allowreplacement) - type = type->ActorInfo->GetReplacement()->Class; + type = type->GetReplacement(); AActor *actor; 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; @@ -4322,7 +4297,7 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) { // Handle decorate replacements explicitly here // to check for missing frames in the replacement object. - i = i->ActorInfo->GetReplacement()->Class; + i = i->GetReplacement(); const AActor *defaults = GetDefaultByType (i); if (defaults->SpawnState == NULL || @@ -4419,6 +4394,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)) { @@ -4511,7 +4499,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) @@ -4574,7 +4562,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; @@ -4615,7 +4603,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! @@ -4654,7 +4642,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; @@ -4717,6 +4705,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++) { @@ -4835,7 +4837,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) @@ -4961,7 +4963,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)) @@ -5444,10 +5446,10 @@ int AActor::DoSpecialDamage (AActor *target, int damage) { if (target->player) { - int poisondamage = GetClass()->Meta.GetMetaInt(AMETA_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; } } diff --git a/src/p_pillar.cpp b/src/p_pillar.cpp index 1d2d5ba819..4faed647f0 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 87d9e6737d..9e57f4bafc 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_saveg.cpp b/src/p_saveg.cpp index 5e9a18f9c6..52ebf88d28 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 @@ -808,7 +798,6 @@ void P_LoadZSegs (FileReaderBase &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) { @@ -854,7 +843,7 @@ void P_LoadGLZSegs (FileReaderBase &data, int type) } 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 (FileReaderBase &data, int type) { 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 (FileReaderBase &data, int type) { seg->linedef = NULL; seg->sidedef = NULL; - seg->frontsector = seg->backsector = segs[subsectors[i].firstline].frontsector; + seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector; } } } @@ -905,7 +887,7 @@ void P_LoadGLZSegs (FileReaderBase &data, int type) // //=========================================================================== -static void LoadZNodes(FileReaderBase &data, int glnodes) +void LoadZNodes(FileReaderBase &data, int glnodes) { // Read extra vertices added during node building DWORD orgVerts, newVerts; @@ -951,7 +933,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes) 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; } @@ -974,6 +956,12 @@ static void LoadZNodes(FileReaderBase &data, int glnodes) numsegs = numSegs; segs = new seg_t[numsegs]; memset (segs, 0, numsegs*sizeof(seg_t)); + glsegextras = NULL; + + for (i = 0; i < numSubs; ++i) + { + subsectors[i].firstline = &segs[(size_t)subsectors[i].firstline]; + } if (glnodes == 0) { @@ -981,6 +969,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes) } else { + glsegextras = new glsegextra_t[numsegs]; P_LoadGLZSegs (data, glnodes); } @@ -1027,7 +1016,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes) } -static void P_LoadZNodes (FileReader &dalump, DWORD id) +void P_LoadZNodes (FileReader &dalump, DWORD id) { int type; bool compressed; @@ -1105,6 +1094,14 @@ static bool P_CheckV4Nodes(MapData *map) // //=========================================================================== +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) { @@ -1140,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. @@ -1165,12 +1167,11 @@ void P_LoadSegs (MapData * map) 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); @@ -1233,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; @@ -1257,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"); @@ -1328,23 +1329,23 @@ void P_LoadSubsectors (MapData * map) } subsectors[i].numlines = subd.numsegs; - subsectors[i].firstline = subd.firstseg; + 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; @@ -1415,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 @@ -2135,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) { @@ -2200,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; } @@ -2250,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) @@ -2630,7 +2633,7 @@ static void P_CreateBlockMap () { if (bx > bx2) { - swap (block, endblock); + swapvalues (block, endblock); } do { @@ -2642,7 +2645,7 @@ static void P_CreateBlockMap () { if (by > by2) { - swap (block, endblock); + swapvalues (block, endblock); } do { @@ -2909,20 +2912,17 @@ static void P_GroupLines (bool buildmap) times[0].Clock(); for (i = 0; i < numsubsectors; i++) { - subsectors[i].sector = segs[subsectors[i].firstline].sidedef->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(); @@ -3341,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 = @@ -3358,6 +3359,11 @@ void P_FreeLevelData () delete[] segs; segs = NULL; } + if (glsegextras != NULL) + { + delete[] glsegextras; + glsegextras = NULL; + } if (sectors != NULL) { delete[] sectors[0].e; @@ -3365,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; @@ -3492,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(); @@ -3549,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; @@ -3667,7 +3694,7 @@ void P_SetupLevel (char *lumpname, int position) } times[6].Clock(); - P_LoopSidedefs (); + P_LoopSidedefs (true); times[6].Unclock(); linemap.Clear(); @@ -3677,23 +3704,23 @@ 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, 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'); @@ -3768,10 +3795,25 @@ void P_SetupLevel (char *lumpname, int position) 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; @@ -3783,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(); @@ -3810,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. @@ -3857,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) { @@ -3929,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 c751c989ca..aa860af0e3 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 55ab3f13ae..4324ebc0ad 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 915d4a508c..e37df68b1a 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -1104,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) diff --git a/src/p_switch.cpp b/src/p_switch.cpp index b7eeb784d3..423633585e 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_things.cpp b/src/p_things.cpp index cd1540f4fc..6622ac0a78 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -65,7 +65,7 @@ bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, return false; // Handle decorate replacements. - kind = kind->ActorInfo->GetReplacement()->Class; + kind = kind->GetReplacement(); if ((GetDefaultByType (kind)->flags3 & MF3_ISMONSTER) && ((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS))) @@ -200,7 +200,7 @@ bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_nam // Handle decorate replacements. - kind = kind->ActorInfo->GetReplacement()->Class; + kind = kind->GetReplacement(); defflags3 = GetDefaultByType (kind)->flags3; if ((defflags3 & MF3_ISMONSTER) && diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index 6cbfccbef2..5f2fca9411 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); @@ -1024,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 @@ -1175,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; } @@ -1452,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 0000000000..0fc2f5564d --- /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 0000000000..b831b4140f --- /dev/null +++ b/src/p_usdf.cpp @@ -0,0 +1,506 @@ +// +// 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 manespace) + // + //=========================================================================== + + const PClass *CheckActorType(const char *key) + { + if (namespace_bits == St) + { + return GetStrifeType(CheckInt(key)); + } + else if (namespace_bits == Zd) + { + const PClass *cls = PClass::FindClass(CheckString(key)); + if (cls == NULL) + { + sc.ScriptMessage("Unknown actor class '%s'", key); + return NULL; + } + if (!cls->IsDescendantOf(RUNTIME_CLASS(AActor))) + { + sc.ScriptMessage("'%s' is not an actor type", 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 = 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 = 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() + { + const PClass *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 a350babab1..eef4f5025f 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -376,17 +376,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() @@ -426,6 +426,10 @@ void APlayerPawn::Serialize (FArchive &arc) << MorphWeapon << DamageFade << PlayerFlags; + if (SaveVersion < 2435) + { + DamageFade.a = 255; + } } //=========================================================================== @@ -499,6 +503,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; + } } //=========================================================================== @@ -1962,7 +1975,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) diff --git a/src/po_man.cpp b/src/po_man.cpp index 12e3f57ca9..0f9b0b15a1 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,7 +1244,7 @@ static bool CheckMobjBlocking (seg_t *seg, FPolyObj *po) line_t *ld; bool blocked; - ld = seg->linedef; + ld = sd->linedef; top = (ld->bbox[BOXTOP]-bmaporgy) >> MAPBLOCKSHIFT; bottom = (ld->bbox[BOXBOTTOM]-bmaporgy) >> MAPBLOCKSHIFT; @@ -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 0000000000..074426d889 --- /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 7daeafd83f..cac361866a 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 e232717a9f..74f9134ad5 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); @@ -121,6 +124,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? @@ -176,10 +184,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). @@ -189,12 +198,13 @@ 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). R_StoreWallRange (first, last); // kg3D - if(fake3D & 7) return; + if(fake3D & 7) return true; // Insert a new clippost for solid walls. if (solid) @@ -216,7 +226,7 @@ void R_ClipWallSegment (int first, int last, bool solid) next->last = last; } } - return; + return true; } // There is a fragment above *start. @@ -233,7 +243,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) @@ -255,7 +265,7 @@ void R_ClipWallSegment (int first, int last, bool solid) crunch: // kg3D - if((fake3D & 7)) return; + if((fake3D & 7)) return true; if (solid) { @@ -273,6 +283,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; } @@ -594,7 +629,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) @@ -642,6 +677,10 @@ void R_AddLine (seg_t *line) if (line->linedef == NULL) { + if (R_CheckClipWallSegment (WallSX1, WallSX2)) + { + InSubsector->flags |= SSECF_DRAWN; + } return; } @@ -671,7 +710,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; @@ -816,6 +855,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; } } @@ -851,7 +900,10 @@ void R_AddLine (seg_t *line) #endif } - R_ClipWallSegment (WallSX1, WallSX2, solid); + if (R_ClipWallSegment (WallSX1, WallSX2, solid)) + { + InSubsector->flags |= SSECF_DRAWN; + } } @@ -929,7 +981,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) @@ -1043,9 +1095,9 @@ void R_FakeDrawLoop(subsector_t *sub) seg_t* line; count = sub->numlines; - line = &segs[sub->firstline]; + line = sub->firstline; - if (sub->poly) + /*if (sub->poly) { // Render the polyobj in the subsector first int polyCount = sub->poly->numsegs; seg_t **polySeg = sub->poly->segs; @@ -1053,10 +1105,10 @@ void R_FakeDrawLoop(subsector_t *sub) { R_AddLine (*polySeg++); } - } + }*/ while (count--) { - if (!line->bPolySeg) + if (!(line->sidedef->Flags & WALLF_POLYOBJ)) { R_AddLine (line); } @@ -1064,6 +1116,74 @@ void R_FakeDrawLoop(subsector_t *sub) } } +//========================================================================== +// +// 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. @@ -1077,6 +1197,7 @@ 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; // kg3D visplane_t *backupfp; @@ -1084,15 +1205,37 @@ void R_Subsector (subsector_t *sub) secplane_t templane; lightlist_t *light; + 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, @@ -1338,23 +1481,22 @@ void R_Subsector (subsector_t *sub) } count = sub->numlines; - line = &segs[sub->firstline]; + line = sub->firstline; basecolormap = frontsector->ColorMap; - 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)) { // kg3D if(line->backsector && frontsector->e && line->backsector->e->XFloor.ffloors.Size()) { @@ -1396,6 +1538,10 @@ void R_Subsector (subsector_t *sub) } line++; } + if (outersubsector) + { + InSubsector = NULL; + } } // diff --git a/src/r_bsp.h b/src/r_bsp.h index 299441ad82..a2b8e06cec 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -83,6 +83,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 0be54a8adb..ee40e29cae 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 64d9b7614d..9e0477ac27 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 @@ -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 ce836f839b..ba77ad2d8c 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 } // @@ -1890,7 +1961,7 @@ void R_DrawBorder (int x1, int y1, int x2, int y2) int BorderNeedRefresh; void V_MarkRect (int x, int y, int width, int height); -void M_DrawFrame (int x, int y, int width, int height); +void V_DrawFrame (int x, int y, int width, int height); void R_DrawViewBorder (void) { @@ -1911,7 +1982,7 @@ void R_DrawViewBorder (void) R_DrawBorder (viewwindowx + viewwidth, viewwindowy, SCREENWIDTH, viewheight + viewwindowy); R_DrawBorder (0, viewwindowy + viewheight, SCREENWIDTH, ST_Y); - M_DrawFrame (viewwindowx, viewwindowy, viewwidth, viewheight); + V_DrawFrame (viewwindowx, viewwindowy, viewwidth, viewheight); V_MarkRect (0, 0, SCREENWIDTH, ST_Y); } diff --git a/src/r_draw.h b/src/r_draw.h index 246b145432..11ad89f42f 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 adeea89f29..e4ff05420b 100644 --- a/src/r_drawt.cpp +++ b/src/r_drawt.cpp @@ -1132,7 +1132,7 @@ void R_DrawMaskedColumnHoriz (const BYTE *column, const FTexture::Span *span, WO if (sprflipvert) { - swap (dc_yl, dc_yh); + swapvalues (dc_yl, dc_yh); } if (dc_yh >= mfloorclip[dc_x]) @@ -1213,8 +1213,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 2387939819..f2fba2f8c0 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 5424c6c79f..e48bfb2386 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]); } } @@ -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 fd9e1ffcb6..282e603023 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -782,6 +782,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; @@ -1049,22 +1053,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 00064b7064..8449d6904d 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -112,5 +112,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 f5f82950aa..12050440f6 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 341a0ef34b..79284c1bad 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -530,7 +530,7 @@ void R_RenderFakeWalls (drawseg_t *ds, int x1, int x2) int t = 256-WallTX1; WallTX1 = 256-WallTX2; WallTX2 = t; - swap (WallTY1, WallTY2); + swapvalues (WallTY1, WallTY2); } v1 = curline->linedef->v1; @@ -557,7 +557,7 @@ void R_RenderFakeWalls (drawseg_t *ds, int x1, int x2) { // The seg is only part of the wall. if (curline->linedef->sidedef[0] != curline->sidedef) { - swap (v1, v2); + swapvalues (v1, v2); } tx1 = v1->x - viewx; tx2 = v2->x - viewx; @@ -1701,17 +1701,17 @@ int side_t::GetLightLevel (bool foggy, int baselight, int *pfakecontrast) const if (((level.flags2 & LEVEL2_SMOOTHLIGHTING) || (Flags & WALLF_SMOOTHLIGHTING) || r_fakecontrast == 2) && linedef->dx != 0) { - rel = 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 { - rel = 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) { @@ -2014,6 +2014,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])); @@ -2025,10 +2026,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); @@ -2075,7 +2076,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; @@ -2241,80 +2273,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++) @@ -2343,85 +2307,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 @@ -2535,7 +2460,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) @@ -2666,11 +2591,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 38f8e4c3e8..9044da72be 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 7c45f51a6e..3544f11e88 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1022,7 +1022,7 @@ void R_DrawMaskedColumn (const BYTE *column, const FTexture::Span *span, WORD de if (sprflipvert) { - swap (dc_yl, dc_yh); + swapvalues (dc_yl, dc_yh); } if (dc_yh >= mfloorclip[dc_x]) diff --git a/src/r_translate.cpp b/src/r_translate.cpp index d5ef79a1f5..eef8c1c1bf 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 e121bc2ee1..46d4bc813e 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; diff --git a/src/s_playlist.cpp b/src/s_playlist.cpp index e7170eaa80..ce82a8182d 100644 --- a/src/s_playlist.cpp +++ b/src/s_playlist.cpp @@ -162,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 381a278664..19076055e6 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 c31e2f7aca..8d64241690 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 ------------------------------------------------------------------ @@ -795,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)); } @@ -826,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; diff --git a/src/s_sound.h b/src/s_sound.h index 8d89d1a0de..05237ba271 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -378,6 +378,7 @@ enum EMidiDevice MDEV_OPL = 1, MDEV_FMOD = 2, MDEV_TIMIDITY = 3, + MDEV_FLUIDSYNTH = 4, }; typedef TMap MidiDeviceMap; diff --git a/src/sdl/hardware.h b/src/sdl/hardware.h index 073a555b19..5b14b7cbb7 100644 --- a/src/sdl/hardware.h +++ b/src/sdl/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/sdl/i_input.cpp b/src/sdl/i_input.cpp index 3b3823e376..3e94ff045b 100644 --- a/src/sdl/i_input.cpp +++ b/src/sdl/i_input.cpp @@ -147,6 +147,14 @@ static void I_CheckGUICapture () } } +void I_SetMouseCapture() +{ +} + +void I_ReleaseMouseCapture() +{ +} + static void CenterMouse () { SDL_WarpMouse (screen->GetWidth()/2, screen->GetHeight()/2); diff --git a/src/sdl/i_input.h b/src/sdl/i_input.h index 01d3673c6c..124c2ca853 100644 --- a/src/sdl/i_input.h +++ b/src/sdl/i_input.h @@ -3,6 +3,8 @@ void I_PutInClipboard (const char *str); FString I_GetFromClipboard (bool use_primary_selection); +void I_SetMouseCapture(); +void I_ReleaseMouseCapture(); #endif diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 5650c07819..4305a07cab 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -44,6 +44,10 @@ #include #endif #include +#if defined(__MACH__) && !defined(NOASM) +#include +#include +#endif #include "doomerrors.h" #include "m_argv.h" @@ -201,6 +205,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", @@ -214,6 +258,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 4ed1131cb4..ff15b1e888 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -78,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] = @@ -611,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"); @@ -774,3 +778,8 @@ unsigned int I_MakeRNGSeed() } return seed; } + +bool I_SetCursor(FTexture *cursorpic) +{ + return false; +} diff --git a/src/sdl/i_system.h b/src/sdl/i_system.h index 778af53f54..acdae0167d 100644 --- a/src/sdl/i_system.h +++ b/src/sdl/i_system.h @@ -128,6 +128,8 @@ bool I_WriteIniFailed (); unsigned int I_MSTime (void); unsigned int I_FPSTime(); +class FTexture; +bool I_SetCursor(FTexture *); // Directory searching routines diff --git a/src/sdl/iwadpicker_cocoa.mm b/src/sdl/iwadpicker_cocoa.mm new file mode 100644 index 0000000000..a835c6dff5 --- /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 e94ec04b7d..23e374a7bd 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -115,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) @@ -706,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; } @@ -1330,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; } @@ -1456,6 +1457,88 @@ const char *GetTagData(FMOD::Sound *sound, const char *tag_name) 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 @@ -1517,61 +1600,7 @@ SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int fla } if (result == FMOD_OK) { - // Handle custom loop starts by checking for a "loop_start" tag. -#if 0 - FMOD_TAG tag; - int numtags; - if (FMOD_OK == stream->getNumTags(&numtags, NULL)) - { - for (int i = 0; i < numtags; ++i) - { - if (FMOD_OK == stream->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(stream, 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 == stream->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 = stream->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 song loop points for song failed. Error %d\n", res); - } - } + SetCustomLoopPts(stream); return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL); } return NULL; @@ -1613,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); @@ -1713,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); @@ -1810,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; } @@ -2358,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 e25d4b5d62..172553df5d 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,14 @@ 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) @@ -162,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"; @@ -293,6 +313,40 @@ 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; @@ -393,153 +447,147 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int } } + EMIDIType miditype = MIDI_NOTMIDI; + // Check for MUS format // 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, len, 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, len, 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[0] == 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 ( - (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 - { - 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); - } + 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) @@ -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 4bde02df1d..5b238cd4c9 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -37,13 +37,14 @@ #include "doomdef.h" class FileReader; +struct FOptionValues; // // MUSIC I/O // void I_InitMusic (); void I_ShutdownMusic (); -void I_BuildMIDIMenuList (struct value_t **values, float *numValues); +void I_BuildMIDIMenuList (FOptionValues *); void I_UpdateMusic (); // Volume. @@ -98,6 +99,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 e5c4771eb8..8c65071674 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,6 +359,10 @@ 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); @@ -294,11 +375,11 @@ protected: 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 b25bbd6fbf..0a946b3c18 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 d8cf166fc5..e5edcbb400 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 0000000000..d50e236b7c --- /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 0000000000..c2075bf1a8 --- /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 d7983a70f3..97eb887083 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -5,10 +5,11 @@ #include "templates.h" #include "v_text.h" -#include "m_menu.h" +#include "menu/menu.h" 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; @@ -70,42 +71,43 @@ void I_ShutdownMusicWin32 () } } -void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) +#ifdef HAVE_FLUIDSYNTH +#define NUM_DEF_DEVICES 4 +#else +#define NUM_DEF_DEVICES 3 +#endif +void I_BuildMIDIMenuList (FOptionValues *opt) { - if (*outValues == NULL) + int p; + FOptionValues::Pair *pair = &opt->mValues[opt->mValues.Reserve(NUM_DEF_DEVICES)]; +#ifdef HAVE_FLUIDSYNTH + pair[0].Text = "FluidSynth"; + pair[0].Value = -5.0; + p = 1; +#else + p = 0; +#endif + pair[p].Text = "OPL Synth Emulation"; + pair[p].Value = -3.0; + pair[p+1].Text = "TiMidity++"; + pair[p+1].Value = -2.0; + pair[p+2].Text = "FMOD"; + pair[p+2].Value = -1.0; + + + for (DWORD id = 0; id < nummididevices; ++id) { - int count = 3 + nummididevices; - value_t *values; - UINT id; - int p; + MIDIOUTCAPS caps; + MMRESULT res; - *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) + res = midiOutGetDevCaps (id, &caps, sizeof(caps)); + assert(res == MMSYSERR_NOERROR); + if (res == MMSYSERR_NOERROR) { - MIDIOUTCAPS caps; - MMRESULT res; - - res = midiOutGetDevCaps (id, &caps, sizeof(caps)); - if (res == MMSYSERR_NOERROR) - { - size_t len = strlen (caps.szPname) + 1; - char *name = new char[len]; - - memcpy (name, caps.szPname, len); - values[p].name = name; - values[p].value = (float)id; - ++p; - } + pair = &opt->mValues[opt->mValues.Reserve(1)]; + pair->Text = caps.szPname; + pair->Value = (float)id; } - assert(p == count); - *numValues = float(count); } } @@ -151,6 +153,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,32 +182,41 @@ 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; } -void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) +#ifdef HAVE_FLUIDSYNTH +#define NUM_DEF_DEVICES 4 +#else +#define NUM_DEF_DEVICES 3 +#endif +void I_BuildMIDIMenuList (FOptionValues *opt) { - if (*outValues == NULL) - { - value_t *values; - - *outValues = values = new value_t[3]; - - 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; - } + int p; + FOptionValues::Pair *pair = &opt->mValues[opt->mValues.Reserve(NUM_DEF_DEVICES)]; +#ifdef HAVE_FLUIDSYNTH + pair[0].Text = "FluidSynth"; + pair[0].Value = -5.0; + p = 1; +#else + p = 0; +#endif + pair[p].Text = "OPL Synth Emulation"; + pair[p].Value = -3.0; + pair[p+1].Text = "TiMidity++"; + pair[p+1].Value = -2.0; + pair[p+2].Text = "FMOD"; + pair[p+2].Value = -1.0; } 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 ea725d0da0..6e2acb32d3 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, len, 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 5ed94c34c3..454ff0d3f1 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; } //========================================================================== @@ -686,7 +754,7 @@ fill: // // 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. // @@ -781,6 +849,242 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) 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 @@ -840,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 68558bdadb..44a0d45e6e 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -125,7 +125,7 @@ MUSSong2::MUSSong2 (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) // Read the remainder of the song. len = int(len - start); - if (len < sizeof(MusHeader)) + if (len < (int)sizeof(MusHeader)) { // It's too short. return; } @@ -283,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: @@ -382,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); } //========================================================================== 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 00d816c663..0f01be75b4 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 0000000000..9d2e692980 --- /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 a13f46e61f..9e5be625b6 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/tarray.h b/src/tarray.h index 09d092e00e..3eab7be33f 100644 --- a/src/tarray.h +++ b/src/tarray.h @@ -129,12 +129,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) diff --git a/src/templates.h b/src/templates.h index c3037456a2..002bdeb3b9 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.h b/src/textures/bitmap.h index 4bd7f32bb5..da039fd0f8 100644 --- a/src/textures/bitmap.h +++ b/src/textures/bitmap.h @@ -100,6 +100,7 @@ public: Width = w; Height = h; data = new BYTE[4*w*h]; + memset(data, 0, 4*w*h); FreeBuffer = true; ClipRect.x = ClipRect.y = 0; ClipRect.width = w; diff --git a/src/textures/multipatchtexture.cpp b/src/textures/multipatchtexture.cpp index 90c52dd63c..4debfa24a0 100644 --- a/src/textures/multipatchtexture.cpp +++ b/src/textures/multipatchtexture.cpp @@ -189,7 +189,7 @@ protected: private: void CheckForHacks (); - void ParsePatch(FScanner &sc, TexPart & part); + void ParsePatch(FScanner &sc, TexPart & part, bool silent); }; //========================================================================== @@ -970,7 +970,7 @@ void FTextureManager::AddTexturesLumps (int lump1, int lump2, int patcheslump) // //========================================================================== -void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) +void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part, bool silent) { FString patchname; sc.MustGetString(); @@ -1011,7 +1011,7 @@ void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) } if (part.Texture == NULL) { - Printf("Unknown patch '%s' in texture '%s'\n", sc.String, Name); + if (!silent) Printf("Unknown patch '%s' in texture '%s'\n", sc.String, Name); } sc.MustGetStringName(","); sc.MustGetNumber(); @@ -1164,6 +1164,14 @@ void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) bComplex |= (part.op != OP_COPY); bTranslucentPatches = bComplex; } + else if (sc.Compare("useoffsets")) + { + if (part.Texture != NULL) + { + part.OriginX -= part.Texture->LeftOffset; + part.OriginY -= part.Texture->TopOffset; + } + } } } if (Mirror & 2) @@ -1187,10 +1195,23 @@ FMultiPatchTexture::FMultiPatchTexture (FScanner &sc, int usetype) : Pixels (0), Spans(0), Parts(0), bRedirect(false), bTranslucentPatches(false) { TArray parts; + bool bSilent = false; bMultiPatch = true; sc.SetCMode(true); sc.MustGetString(); + if (sc.Compare("optional")) + { + bSilent = true; + sc.MustGetString(); + if (sc.Compare(",")) + { + // this is not right. Apparently a texture named 'optional' is being defined right now... + sc.UnGet(); + sc.String = "optional"; + bSilent = false; + } + } uppercopy(Name, sc.String); Name[8] = 0; sc.MustGetStringName(","); @@ -1231,7 +1252,7 @@ FMultiPatchTexture::FMultiPatchTexture (FScanner &sc, int usetype) else if (sc.Compare("Patch")) { TexPart part; - ParsePatch(sc, part); + ParsePatch(sc, part, bSilent); if (part.Texture != NULL) parts.Push(part); part.Texture = NULL; part.Translation = NULL; diff --git a/src/textures/pngtexture.cpp b/src/textures/pngtexture.cpp index d1fe3e8f54..4e30802c42 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 234d447c40..576c30de9a 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]); } } } diff --git a/src/thingdef/thingdef.h b/src/thingdef/thingdef.h index df4bc78ab7..6b2124149d 100644 --- a/src/thingdef/thingdef.h +++ b/src/thingdef/thingdef.h @@ -230,6 +230,7 @@ enum ACMETA_MeleeDamage, ACMETA_MissileName, ACMETA_MissileHeight, + ACMETA_Lump, }; diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index a4977eaa6c..b304e638fe 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"); //========================================================================== @@ -823,6 +825,8 @@ enum CBA_Flags { CBAF_AIMFACING = 1, CBAF_NORANDOM = 2, + CBAF_EXPLICITANGLE = 4, + CBAF_NOPITCH = 8, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) @@ -849,13 +853,25 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomBulletAttack) if (!pufftype) pufftype = PClass::FindClass(NAME_BulletPuff); - 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(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) PuffType = PClass::FindClass(NAME_BulletPuff); @@ -1192,7 +1210,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomPunch) enum { RAF_SILENT = 1, - RAF_NOPIERCE = 2 + RAF_NOPIERCE = 2, + RAF_EXPLICITANGLE = 4, }; //========================================================================== @@ -1202,7 +1221,7 @@ enum //========================================================================== DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) { - ACTION_PARAM_START(8); + ACTION_PARAM_START(10); ACTION_PARAM_INT(Damage, 0); ACTION_PARAM_INT(Spawnofs_XY, 1); ACTION_PARAM_BOOL(UseAmmo, 2); @@ -1210,7 +1229,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) ACTION_PARAM_COLOR(Color2, 4); ACTION_PARAM_INT(Flags, 5); ACTION_PARAM_FLOAT(MaxDiff, 6); - ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); if (!self->player) return; @@ -1222,7 +1243,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RailAttack) if (!weapon->DepleteAmmo(weapon->bAltFire, true)) return; // out of ammo } - P_RailAttack (self, Damage, Spawnofs_XY, Color1, Color2, 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); } //========================================================================== @@ -1234,12 +1269,13 @@ enum { CRF_DONTAIM = 0, CRF_AIMPARALLEL = 1, - CRF_AIMDIRECT = 2 + CRF_AIMDIRECT = 2, + CRF_EXPLICITANGLE = 4, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) { - ACTION_PARAM_START(8); + ACTION_PARAM_START(10); ACTION_PARAM_INT(Damage, 0); ACTION_PARAM_INT(Spawnofs_XY, 1); ACTION_PARAM_COLOR(Color1, 2); @@ -1248,6 +1284,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) ACTION_PARAM_INT(aim, 5); ACTION_PARAM_FLOAT(MaxDiff, 6); ACTION_PARAM_CLASS(PuffType, 7); + ACTION_PARAM_ANGLE(Spread_XY, 8); + ACTION_PARAM_ANGLE(Spread_Z, 9); AActor *linetarget; @@ -1314,7 +1352,21 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) angle_t angle = (self->angle - ANG90) >> ANGLETOFINESHIFT; - P_RailAttack (self, Damage, Spawnofs_XY, Color1, Color2, 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; @@ -2492,23 +2544,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) { - ACTION_PARAM_START(3); + ACTION_PARAM_START(5); ACTION_PARAM_STATE(jump, 0); ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_BOOL(projtarg, 2); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); angle_t an; AActor *target; @@ -2517,7 +2584,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; @@ -2531,7 +2602,28 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) if (!target) return; // [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; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (doCheckSight && !P_CheckSight (self, target, SF_IGNOREVISIBILITY)) return; if (fov && (fov < ANGLE_MAX)) @@ -2553,9 +2645,20 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfTargetInLOS) { // Does the player aim at something that can be shot? P_BulletSlope(self, &target); - } - if (!target) return; + if (!target) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + } + } ACTION_JUMP(jump); } @@ -2563,24 +2666,30 @@ 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) // //========================================================================== DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) { - ACTION_PARAM_START(3); + ACTION_PARAM_START(5); ACTION_PARAM_STATE(jump, 0); ACTION_PARAM_ANGLE(fov, 1); - ACTION_PARAM_BOOL(projtarg, 2); + ACTION_PARAM_INT(flags, 2); + ACTION_PARAM_FIXED(dist_max, 3); + ACTION_PARAM_FIXED(dist_close, 4); angle_t an; AActor *target; ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! - if (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; @@ -2594,7 +2703,28 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetLOS) if (!target) return; // [KS] Let's not call P_CheckSight unnecessarily in this case. - if (!P_CheckSight (target, self, SF_IGNOREVISIBILITY)) + if ((flags & JLOSF_DEADNOJUMP) && (target->health <= 0)) return; + + fixed_t distance = P_AproxDistance(target->x - self->x, target->y - self->y); + distance = P_AproxDistance(distance, target->z - self->z); + + if (dist_max && (distance > dist_max)) return; + + bool doCheckSight = !(flags & JLOSF_NOSIGHT); + + if (dist_close && (distance < dist_close)) + { + if (flags & JLOSF_CLOSENOJUMP) + return; + + if (flags & JLOSF_CLOSENOFOV) + fov = 0; + + if (flags & JLOSF_CLOSENOSIGHT) + doCheckSight = false; + } + + if (doCheckSight && !P_CheckSight (target, self, SF_IGNOREVISIBILITY)) return; if (fov && (fov < ANGLE_MAX)) @@ -3194,6 +3324,87 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SetUserArray) ((int *)(reinterpret_cast(self) + var->offset))[pos] = value; } +//=========================================================================== +// +// A_Teleport(optional state teleportstate, optional class targettype, +// optional class fogtype, optional int flags, optional fixed mindist, +// optional fixed maxdist) +// +// Attempts to teleport to a targettype at least mindist away and at most +// maxdist away (0 means unlimited). If successful, spawn a fogtype at old +// location and place calling actor in teleportstate. +// +//=========================================================================== +enum T_Flags +{ + TF_TELEFRAG = 1, // Allow telefrag in order to teleport. + TF_RANDOMDECIDE = 2, // Randomly fail based on health. (A_Srcr2Decide) +}; + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport) +{ + ACTION_PARAM_START(6); + ACTION_PARAM_STATE(TeleportState, 0); + ACTION_PARAM_CLASS(TargetType, 1); + ACTION_PARAM_CLASS(FogType, 2); + ACTION_PARAM_INT(Flags, 3); + ACTION_PARAM_FIXED(MinDist, 4); + ACTION_PARAM_FIXED(MaxDist, 5); + + // Randomly choose not to teleport like A_Srcr2Decide. + if (Flags & TF_RANDOMDECIDE) + { + static const int chance[] = + { + 192, 120, 120, 120, 64, 64, 32, 16, 0 + }; + + unsigned int chanceindex = self->health / ((self->SpawnHealth()/8 == 0) ? 1 : self->SpawnHealth()/8); + + if (chanceindex >= countof(chance)) + { + chanceindex = countof(chance) - 1; + } + + if (pr_teleport() >= chance[chanceindex]) return; + } + + if (TeleportState == NULL) + { + // Default to Teleport. + TeleportState = self->FindState("Teleport"); + // If still nothing, then return. + if (!TeleportState) return; + } + + DSpotState *state = DSpotState::GetSpotState(); + if (state == NULL) return; + + if (!TargetType) TargetType = PClass::FindClass("BossSpot"); + + AActor * spot = state->GetSpotWithMinMaxDistance(TargetType, self->x, self->y, MinDist, MaxDist); + if (spot == NULL) return; + + fixed_t prevX = self->x; + fixed_t prevY = self->y; + fixed_t prevZ = self->z; + if (P_TeleportMove (self, spot->x, spot->y, spot->z, Flags & TF_TELEFRAG)) + { + ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains! + + if (FogType) + { + Spawn(FogType, prevX, prevY, prevZ, ALLOW_REPLACE); + } + + ACTION_JUMP(TeleportState); + + self->z = self->floorz; + self->angle = spot->angle; + self->velx = self->vely = self->velz = 0; + } +} + //=========================================================================== // // A_Turn diff --git a/src/thingdef/thingdef_data.cpp b/src/thingdef/thingdef_data.cpp index 7ebb680d82..b08e487199 100644 --- a/src/thingdef/thingdef_data.cpp +++ b/src/thingdef/thingdef_data.cpp @@ -224,6 +224,8 @@ 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 diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index 2511b93a8b..9440a05b00 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -1667,7 +1667,7 @@ ExpVal FxRandom::EvalExpression (AActor *self) if (maxval < minval) { - swap (maxval, minval); + swapvalues (maxval, minval); } val.Int = (*rng)(maxval - minval + 1) + minval; @@ -1714,7 +1714,7 @@ ExpVal FxFRandom::EvalExpression (AActor *self) if (maxval < minval) { - swap (maxval, minval); + swapvalues (maxval, minval); } val.Float = frandom * (maxval - minval) + minval; diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index 07f4ca4e23..ae236a58c3 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -1106,6 +1106,8 @@ static FActorInfo *ParseActorHeader(FScanner &sc, Baggage *bag) { FActorInfo *info = CreateNewActor(sc, typeName, parentName, native); info->DoomEdNum = DoomEdNum > 0? DoomEdNum : -1; + info->Class->Meta.SetMetaString (ACMETA_Lump, Wads.GetLumpFullPath(sc.LumpNum)); + SetReplacement(info, replaceName); ResetBaggage (bag, info->Class->ParentClass); diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index f58eb65002..d41d79891a 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -255,13 +255,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->Class; + + if (convid <= 0) return; // 0 is not usable because the dialogue scripts use it as 'no object'. + SetStrifeType(convid, info->Class); } //========================================================================== @@ -999,10 +996,26 @@ DEFINE_PROPERTY(maxdropoffheight, F, Actor) //========================================================================== // //========================================================================== -DEFINE_PROPERTY(poisondamage, I, Actor) +DEFINE_PROPERTY(poisondamage, Iii, Actor) { - PROP_INT_PARM(i, 0); - info->Class->Meta.SetMetaInt (AMETA_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; + } } //========================================================================== @@ -1861,7 +1874,7 @@ DEFINE_CLASS_PROPERTY_PREFIX(player, colorrange, I_I, PlayerPawn) PROP_INT_PARM(end, 1); if (start > end) - swap (start, end); + swapvalues (start, end); info->Class->Meta.SetMetaInt (APMETA_ColorRange, (start & 255) | ((end & 255) << 8)); } @@ -2094,10 +2107,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)); + } } //========================================================================== @@ -2160,6 +2182,15 @@ DEFINE_CLASS_PROPERTY_PREFIX(player, hexenarmor, FFFFF, PlayerPawn) } } +//========================================================================== +// +//========================================================================== +DEFINE_CLASS_PROPERTY_PREFIX(player, portrait, S, PlayerPawn) +{ + PROP_STRING_PARM(val, 0); + info->Class->Meta.SetMetaString (APMETA_Portrait, val); +} + //========================================================================== // //========================================================================== diff --git a/src/timidity/instrum_sf2.cpp b/src/timidity/instrum_sf2.cpp index 5064c9fe11..a455bbdee8 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 42b0b51455..f22c9c9284 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -45,6 +45,7 @@ #include "r_translate.h" #include "doomstat.h" #include "v_palette.h" +#include "gi.h" #include "i_system.h" #include "i_video.h" @@ -62,6 +63,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 +73,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 +191,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 +924,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 +1096,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 */ @@ -1181,3 +1352,35 @@ bool DCanvas::ClipBox (int &x, int &y, int &w, int &h, const BYTE *&src, const i } return false; } + +// Draw a frame around the specified area using the view border +// frame graphics. The border is drawn outside the area, not in it. +void V_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; + + // Draw top and bottom sides. + p = TexMan[border->t]; + screen->FlatFill(left, top - p->GetHeight(), right, top, p, true); + p = TexMan[border->b]; + screen->FlatFill(left, bottom, right, bottom + p->GetHeight(), p, true); + + // Draw left and right sides. + p = TexMan[border->l]; + screen->FlatFill(left - p->GetWidth(), top, left, bottom, p, true); + p = TexMan[border->r]; + screen->FlatFill(right, top, right + p->GetWidth(), bottom, p, true); + + // Draw beveled corners. + screen->DrawTexture (TexMan[border->tl], left-offset, top-offset, TAG_DONE); + screen->DrawTexture (TexMan[border->tr], left+width, top-offset, TAG_DONE); + screen->DrawTexture (TexMan[border->bl], left-offset, top+height, TAG_DONE); + screen->DrawTexture (TexMan[border->br], left+width, top+height, TAG_DONE); +} \ No newline at end of file diff --git a/src/v_font.cpp b/src/v_font.cpp index ea6feb1493..3815c8ae49 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -2043,6 +2043,15 @@ void V_InitFontColors () while ((lump = Wads.FindLump ("TEXTCOLO", &lastlump)) != -1) { + if (gameinfo.flags & GI_NOTEXTCOLOR) + { + // Chex3 contains a bad TEXTCOLO lump, probably to force all text to be green. + // This renders the Gray, Gold, Red and Yellow color range inoperable, some of + // which are used by the menu. So we have no choice but to skip this lump so that + // all colors work properly. + // The text colors should be the end user's choice anyway. + if (Wads.GetLumpFile(lump) == 1) continue; + } FScanner sc(lump); while (sc.GetString()) { diff --git a/src/v_palette.cpp b/src/v_palette.cpp index 231e160c02..7761a3dece 100644 --- a/src/v_palette.cpp +++ b/src/v_palette.cpp @@ -118,6 +118,21 @@ CUSTOM_CVAR (Float, Gamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) } } +CCMD (bumpgamma) +{ + // [RH] Gamma correction tables are now generated + // on the fly for *any* gamma level. + // Q: What are reasonable limits to use here? + + float newgamma = Gamma + 0.1f; + + if (newgamma > 3.0) + newgamma = 1.0; + + Gamma = newgamma; + Printf ("Gamma correction level %g\n", *Gamma); +} + /****************************/ /* Palette management stuff */ @@ -224,7 +239,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; diff --git a/src/v_video.cpp b/src/v_video.cpp index 24ed1a5620..c51f13cfca 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -42,7 +42,6 @@ #include "m_argv.h" #include "m_bbox.h" #include "m_swap.h" -#include "m_menu.h" #include "i_video.h" #include "v_video.h" @@ -62,6 +61,8 @@ #include "m_png.h" #include "colormatcher.h" #include "v_palette.h" +#include "r_sky.h" +#include "menu/menu.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 6beaeea7b9..14782519cc 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 @@ -405,6 +412,7 @@ public: virtual void WipeEndScreen(); virtual bool WipeDo(int ticks); virtual void WipeCleanup(); + virtual int GetPixelDoubling() const { return 1; } uint32 GetLastFPS() const { return LastCount; } @@ -482,6 +490,7 @@ FString V_GetColorStringByName (const char *name); // Tries to get color by name, then by string int V_GetColor (const DWORD *palette, const char *str); +void V_DrawFrame (int left, int top, int width, int height); #if defined(X86_ASM) || defined(X64_ASM) extern "C" void ASM_PatchPitch (void); diff --git a/src/vectors.h b/src/vectors.h index 013077042f..c0783e250e 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 de017995c9..5854c5f019 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 5727c8bcc7..13e7ff14a2 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 048f06b4ae..0000000000 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 02da9910ff..7453571e5d 100644 --- a/src/win32/fb_d3d9.cpp +++ b/src/win32/fb_d3d9.cpp @@ -278,7 +278,7 @@ D3DFB::D3DFB (UINT adapter, 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; @@ -1772,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; } @@ -1865,7 +1865,7 @@ void D3DFB::DrawPackedTextures(int packnum) CheckQuadBatch(); - BufferedQuad *quad = &QuadExtra[QuadBatchPos]; + BufferedTris *quad = &QuadExtra[QuadBatchPos]; FBVERTEX *vert = &VertexData[VertexPos]; quad->Group1 = 0; @@ -1881,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; @@ -2967,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) { @@ -3021,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; @@ -3152,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; @@ -3168,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; @@ -3217,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 @@ -3227,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(); @@ -3247,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; @@ -3300,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(); } @@ -3371,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) @@ -3467,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) @@ -3508,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; @@ -3640,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 9a3b34412d..3407136aa7 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 e440bd7f18..3a9b3d35ed 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 bdff41cf85..223ba621c0 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 727e4b7edf..5e4dfb1c9f 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 e21f77e25c..b78fea64e4 100644 --- a/src/win32/i_dijoy.cpp +++ b/src/win32/i_dijoy.cpp @@ -21,7 +21,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" @@ -236,8 +235,6 @@ protected: // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -extern void UpdateJoystickMenu(); - // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -246,7 +243,6 @@ static void MapAxis(FIntCVar &var, int num); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern menu_t JoystickMenu; extern LPDIRECTINPUT8 g_pdi; extern HWND Window; @@ -629,7 +625,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 e9d2a26bbf..f302d807d0 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -86,7 +86,6 @@ #include "i_input.h" #include "v_video.h" #include "i_sound.h" -#include "m_menu.h" #include "g_game.h" #include "d_main.h" #include "d_gui.h" @@ -145,8 +144,10 @@ extern HWND EAXEditWindow; EXTERN_CVAR (String, language) EXTERN_CVAR (Bool, lookstrafe) EXTERN_CVAR (Bool, use_joystick) +EXTERN_CVAR (Bool, use_mouse) static int WheelDelta; +extern bool CursorState; extern BOOL paused; static bool noidle = false; @@ -190,6 +191,16 @@ static void I_CheckGUICapture () } } +void I_SetMouseCapture() +{ + SetCapture(Window); +} + +void I_ReleaseMouseCapture() +{ + ReleaseCapture(); +} + bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) { event_t ev = { EV_GUI_Event }; @@ -227,28 +238,29 @@ bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESU { switch (wParam) { - case VK_PRIOR: ev.data1 = GK_PGUP; break; - case VK_NEXT: ev.data1 = GK_PGDN; break; - case VK_END: ev.data1 = GK_END; break; - case VK_HOME: ev.data1 = GK_HOME; break; - case VK_LEFT: ev.data1 = GK_LEFT; break; - case VK_RIGHT: ev.data1 = GK_RIGHT; break; - case VK_UP: ev.data1 = GK_UP; break; - case VK_DOWN: ev.data1 = GK_DOWN; break; - case VK_DELETE: ev.data1 = GK_DEL; break; - case VK_ESCAPE: ev.data1 = GK_ESCAPE; break; - case VK_F1: ev.data1 = GK_F1; break; - case VK_F2: ev.data1 = GK_F2; break; - case VK_F3: ev.data1 = GK_F3; break; - case VK_F4: ev.data1 = GK_F4; break; - case VK_F5: ev.data1 = GK_F5; break; - case VK_F6: ev.data1 = GK_F6; break; - case VK_F7: ev.data1 = GK_F7; break; - case VK_F8: ev.data1 = GK_F8; break; - case VK_F9: ev.data1 = GK_F9; break; - case VK_F10: ev.data1 = GK_F10; break; - case VK_F11: ev.data1 = GK_F11; break; - case VK_F12: ev.data1 = GK_F12; break; + case VK_PRIOR: ev.data1 = GK_PGUP; break; + case VK_NEXT: ev.data1 = GK_PGDN; break; + case VK_END: ev.data1 = GK_END; break; + case VK_HOME: ev.data1 = GK_HOME; break; + case VK_LEFT: ev.data1 = GK_LEFT; break; + case VK_RIGHT: ev.data1 = GK_RIGHT; break; + case VK_UP: ev.data1 = GK_UP; break; + case VK_DOWN: ev.data1 = GK_DOWN; break; + case VK_DELETE: ev.data1 = GK_DEL; break; + case VK_ESCAPE: ev.data1 = GK_ESCAPE; break; + case VK_F1: ev.data1 = GK_F1; break; + case VK_F2: ev.data1 = GK_F2; break; + case VK_F3: ev.data1 = GK_F3; break; + case VK_F4: ev.data1 = GK_F4; break; + case VK_F5: ev.data1 = GK_F5; break; + case VK_F6: ev.data1 = GK_F6; break; + case VK_F7: ev.data1 = GK_F7; break; + case VK_F8: ev.data1 = GK_F8; break; + case VK_F9: ev.data1 = GK_F9; break; + case VK_F10: ev.data1 = GK_F10; break; + case VK_F11: ev.data1 = GK_F11; break; + case VK_F12: ev.data1 = GK_F12; break; + case VK_BROWSER_BACK: ev.data1 = GK_BACK; break; } if (ev.data1 != 0) { @@ -277,6 +289,9 @@ bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESU case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_MOUSEMOVE: if (message >= WM_LBUTTONDOWN && message <= WM_LBUTTONDBLCLK) { ev.subtype = message - WM_LBUTTONDOWN + EV_GUI_LButtonDown; @@ -289,11 +304,35 @@ bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESU { ev.subtype = message - WM_MBUTTONDOWN + EV_GUI_MButtonDown; } - D_PostEvent(&ev); + else if (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONUP) + { + ev.subtype = message - WM_XBUTTONDOWN + EV_GUI_BackButtonDown; + if (GET_XBUTTON_WPARAM(wParam) == 2) + { + ev.subtype += EV_GUI_FwdButtonDown - EV_GUI_BackButtonDown; + } + else if (GET_XBUTTON_WPARAM(wParam) != 1) + { + break; + } + } + else if (message == WM_MOUSEMOVE) + { + ev.subtype = EV_GUI_MouseMove; + } + ev.data1 = LOWORD(lParam) >> screen->GetPixelDoubling(); + ev.data2 = HIWORD(lParam) >> screen->GetPixelDoubling(); + + if (wParam & MK_SHIFT) ev.data3 |= GKM_SHIFT; + if (wParam & MK_CONTROL) ev.data3 |= GKM_CTRL; + if (GetKeyState(VK_MENU) & 0x8000) ev.data3 |= GKM_ALT; + + if (use_mouse) D_PostEvent(&ev); return true; // Note: If the mouse is grabbed, it sends the mouse wheel events itself. case WM_MOUSEWHEEL: + if (!use_mouse) return false; if (wParam & MK_SHIFT) ev.data3 |= GKM_SHIFT; if (wParam & MK_CONTROL) ev.data3 |= GKM_CTRL; if (GetKeyState(VK_MENU) & 0x8000) ev.data3 |= GKM_ALT; @@ -384,6 +423,15 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return result; } + if ((gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL) && message == WM_LBUTTONDOWN) + { + if (GUIWndProcHook(hWnd, message, wParam, lParam, &result)) + { + return result; + } + } + + switch (message) { case WM_DESTROY: @@ -418,6 +466,18 @@ 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 + } + else + { + return DefWindowProc(hWnd, message, wParam, lParam); + } + break; + case WM_SIZE: InvalidateRect (Window, NULL, FALSE); break; diff --git a/src/win32/i_input.h b/src/win32/i_input.h index 1c21d78c35..b0d409844d 100644 --- a/src/win32/i_input.h +++ b/src/win32/i_input.h @@ -37,6 +37,9 @@ #include "doomtype.h" #include "doomdef.h" +void I_SetMouseCapture(); +void I_ReleaseMouseCapture(); + bool I_InitInput (void *hwnd); void I_ShutdownInput (); void I_PutInClipboard (const char *str); diff --git a/src/win32/i_main.cpp b/src/win32/i_main.cpp index a76380bda0..808ae4fbe2 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 ------------------------------------------------- @@ -931,9 +930,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; @@ -941,7 +937,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 80c00bdbe6..0fa46c1db1 100644 --- a/src/win32/i_mouse.cpp +++ b/src/win32/i_mouse.cpp @@ -1,8 +1,3 @@ -#ifdef _MSC_VER -// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" -// generated by SetClassLongPtr(). -#pragma warning(disable:4244) -#endif // HEADER FILES ------------------------------------------------------------ @@ -22,6 +17,7 @@ #include "doomstat.h" #include "win32iface.h" #include "rawinput.h" +#include "menu/menu.h" // MACROS ------------------------------------------------------------------ @@ -87,8 +83,6 @@ public: void Ungrab(); protected: - void CenterMouse(int x, int y); - POINT UngrabbedPointerPos; LONG PrevX, PrevY; bool Grabbed; @@ -112,6 +106,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 +130,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 +178,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); } } @@ -229,18 +261,17 @@ void I_CheckNativeMouse(bool preferNative) if (!windowed) { - want_native = false; + // ungrab mouse when in the menu with mouse control on. + want_native = m_use_mouse && (menuactive == MENU_On || menuactive == MENU_OnNoPause); } else { want_native = (GetForegroundWindow() != Window) || - !CaptureMode_InGame() || - GUICapture || - paused || preferNative || !use_mouse || - demoplayback; + ((!m_use_mouse || menuactive != MENU_WaitKey) && + (!CaptureMode_InGame() || GUICapture || paused || demoplayback)); } //Printf ("%d %d %d\n", wantNative, preferNative, NativeMouse); @@ -449,6 +480,7 @@ static FMouse *CreateRawMouse() FRawMouse::FRawMouse() { Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -512,12 +544,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 +574,7 @@ void FRawMouse::Ungrab() Grabbed = false; ClearButtonState(); } - ShowCursor(TRUE); + SetCursorState(true); SetCursorPos(UngrabbedPointerPos.x, UngrabbedPointerPos.y); } } @@ -556,7 +587,7 @@ void FRawMouse::Ungrab() bool FRawMouse::ProcessRawInput(RAWINPUT *raw, int code) { - if (!Grabbed || raw->header.dwType != RIM_TYPEMOUSE) + if (!Grabbed || raw->header.dwType != RIM_TYPEMOUSE || !use_mouse) { return false; } @@ -586,8 +617,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 +674,7 @@ FDInputMouse::FDInputMouse() { Device = NULL; Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -764,7 +801,7 @@ void FDInputMouse::ProcessInput() dx = 0; dy = 0; - if (!Grabbed) + if (!Grabbed || !use_mouse) return; event_t ev = { 0 }; @@ -863,6 +900,7 @@ FWin32Mouse::FWin32Mouse() { GetCursorPos(&UngrabbedPointerPos); Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -905,7 +943,7 @@ void FWin32Mouse::ProcessInput() POINT pt; int x, y; - if (!Grabbed || !GetCursorPos(&pt)) + if (!Grabbed || !use_mouse || !GetCursorPos(&pt)) { return; } @@ -920,7 +958,7 @@ void FWin32Mouse::ProcessInput() } if (x | y) { - CenterMouse(pt.x, pt.y); + CenterMouse(pt.x, pt.y, &PrevX, &PrevY); } PostMouseMove(x, y); } @@ -944,13 +982,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) @@ -963,6 +1001,11 @@ bool FWin32Mouse::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa return true; } } + else if (!use_mouse) + { + // all following messages should only be processed if the mouse is in use + return false; + } else if (message == WM_MOUSEWHEEL) { WheelMoved(0, (SHORT)HIWORD(wParam)); @@ -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 68803fefb0..cab2844a61 100644 --- a/src/win32/i_rawps2.cpp +++ b/src/win32/i_rawps2.cpp @@ -16,7 +16,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" @@ -388,8 +387,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 04e2694d6c..f2178ffbfa 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -83,9 +83,17 @@ #include "doomstat.h" #include "v_palette.h" #include "stats.h" +#include "r_data.h" +#include "textures/bitmap.h" // MACROS ------------------------------------------------------------------ +#ifdef _MSC_VER +// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" +// generated by SetClassLongPtr(). +#pragma warning(disable:4244) +#endif + // TYPES ------------------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -108,6 +116,11 @@ static int I_WaitForTicEvent(int prevtic); static void I_FreezeTimeEventDriven(bool frozen); static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2); +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic); +static HCURSOR CreateAlphaCursor(FTexture *cursorpic); +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask); +static void DestroyCustomCursor(); + // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR(String, language); @@ -158,6 +171,8 @@ static WadStuff *WadList; static int NumWads; static int DefaultWad; +static HCURSOR CustomCursor; + // CODE -------------------------------------------------------------------- //========================================================================== @@ -997,7 +1012,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; @@ -1175,6 +1190,208 @@ int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad) return defaultiwad; } +//========================================================================== +// +// I_SetCursor +// +// Returns true if the cursor was successfully changed. +// +//========================================================================== + +bool I_SetCursor(FTexture *cursorpic) +{ + HCURSOR cursor; + + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + + cursor = CreateAlphaCursor(cursorpic); + if (cursor == NULL) + { + cursor = CreateCompatibleCursor(cursorpic); + } + if (cursor == NULL) + { + return false; + } + // Replace the existing cursor with the new one. + if (CustomCursor != NULL) + { + DestroyCursor(CustomCursor); + } + CustomCursor = cursor; + atterm(DestroyCustomCursor); + SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)cursor); + return true; +} + +//========================================================================== +// +// CreateCompatibleCursor +// +// Creates a cursor with a 1-bit alpha channel. +// +//========================================================================== + +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic) +{ + int picwidth = cursorpic->GetWidth(); + int picheight = cursorpic->GetHeight(); + + // Create bitmap masks for the cursor from the texture. + HDC dc = GetDC(NULL); + if (dc == NULL) + { + return false; + } + HDC and_mask_dc = CreateCompatibleDC(dc); + HDC xor_mask_dc = CreateCompatibleDC(dc); + HBITMAP and_mask = CreateCompatibleBitmap(dc, 32, 32); + HBITMAP xor_mask = CreateCompatibleBitmap(dc, 32, 32); + ReleaseDC(NULL, dc); + + SelectObject(and_mask_dc, and_mask); + SelectObject(xor_mask_dc, xor_mask); + + // Initialize with an invisible cursor. + SelectObject(and_mask_dc, GetStockObject(WHITE_PEN)); + SelectObject(and_mask_dc, GetStockObject(WHITE_BRUSH)); + Rectangle(and_mask_dc, 0, 0, 32, 32); + SelectObject(xor_mask_dc, GetStockObject(BLACK_PEN)); + SelectObject(xor_mask_dc, GetStockObject(BLACK_BRUSH)); + Rectangle(xor_mask_dc, 0, 0, 32, 32); + + FBitmap bmp; + const BYTE *pixels; + + bmp.Create(picwidth, picheight); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + pixels = bmp.GetPixels(); + + // Copy color data from the source texture to the cursor bitmaps. + for (int y = 0; y < picheight; ++y) + { + for (int x = 0; x < picwidth; ++x) + { + const BYTE *bgra = &pixels[x*4 + y*bmp.GetPitch()]; + if (bgra[3] != 0) + { + SetPixelV(and_mask_dc, x, y, RGB(0,0,0)); + SetPixelV(xor_mask_dc, x, y, RGB(bgra[2], bgra[1], bgra[0])); + } + } + } + DeleteDC(and_mask_dc); + DeleteDC(xor_mask_dc); + + // Create the cursor from the bitmaps. + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, and_mask, xor_mask); +} + +//========================================================================== +// +// CreateAlphaCursor +// +// Creates a cursor with a full alpha channel. +// +//========================================================================== + +static HCURSOR CreateAlphaCursor(FTexture *cursorpic) +{ + HDC dc; + BITMAPV5HEADER bi; + HBITMAP color, mono; + void *bits; + + memset(&bi, 0, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = 32; + bi.bV5Height = 32; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + dc = GetDC(NULL); + if (dc == NULL) + { + return NULL; + } + + // Create the DIB section with an alpha channel. + color = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0); + ReleaseDC(NULL, dc); + + if (color == NULL) + { + return NULL; + } + + // Create an empty mask bitmap, since CreateIconIndirect requires this. + mono = CreateBitmap(32, 32, 1, 1, NULL); + if (mono == NULL) + { + DeleteObject(color); + return NULL; + } + + // Copy cursor to the color bitmap. Note that GDI bitmaps are upside down compared + // to normal conventions, so we create the FBitmap pointing at the last row and use + // a negative pitch so that CopyTrueColorPixels will use GDI's orientation. + FBitmap bmp((BYTE *)bits + 31*32*4, -32*4, 32, 32); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, mono, color); +} + +//========================================================================== +// +// CreateBitmapCursor +// +// Create the cursor from the bitmaps. Deletes the bitmaps before returning. +// +//========================================================================== + +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask) +{ + ICONINFO iconinfo = + { + FALSE, // fIcon + xhot, // xHotspot + yhot, // yHotspot + and_mask, // hbmMask + color_mask // hbmColor + }; + HCURSOR cursor = CreateIconIndirect(&iconinfo); + + // Delete the bitmaps. + DeleteObject(and_mask); + DeleteObject(color_mask); + + return cursor; +} + +//========================================================================== +// +// DestroyCustomCursor +// +//========================================================================== + +static void DestroyCustomCursor() +{ + if (CustomCursor != NULL) + { + DestroyCursor(CustomCursor); + CustomCursor = NULL; + } +} + //========================================================================== // // I_WriteIniFailed diff --git a/src/win32/i_system.h b/src/win32/i_system.h index 66b2bac7d8..1a5e755e30 100644 --- a/src/win32/i_system.h +++ b/src/win32/i_system.h @@ -114,6 +114,10 @@ void STACK_ARGS I_FatalError (const char *error, ...) GCCPRINTF(1,2); void atterm (void (*func)(void)); void popterm (); +// Set the mouse cursor. The texture must be 32x32. +class FTexture; +bool I_SetCursor(FTexture *cursor); + // Repaint the pre-game console void I_PaintConsole (void); diff --git a/src/win32/i_xinput.cpp b/src/win32/i_xinput.cpp index e3ad1eed0d..5a02d7af4b 100644 --- a/src/win32/i_xinput.cpp +++ b/src/win32/i_xinput.cpp @@ -19,7 +19,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" diff --git a/src/win32/st_start.cpp b/src/win32/st_start.cpp index 8fe844f700..1f0838ef22 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 e46c392c3b..e3710b2343 100644 --- a/src/win32/win32iface.h +++ b/src/win32/win32iface.h @@ -257,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); @@ -278,7 +281,7 @@ private: }; #define D3DFVF_FBVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX1) - struct BufferedQuad + struct BufferedTris { union { @@ -293,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 @@ -355,18 +360,19 @@ 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(); void EndLineBatch(); void EndBatch(); void CopyNextFrontBuffer(); + int GetPixelDoubling() const { return PixelDoubling; } D3DCAPS9 DeviceCaps; @@ -433,7 +439,7 @@ private: FBVERTEX *VertexData; IDirect3DIndexBuffer9 *IndexBuffer; WORD *IndexData; - BufferedQuad *QuadExtra; + BufferedTris *QuadExtra; int VertexPos; int IndexPos; int QuadBatchPos; @@ -499,7 +505,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 a610fd4090..e9df043d09 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" @@ -403,14 +405,6 @@ void Win32Video::DumpAdapters() } } -CCMD(vid_listadapters) -{ - if (Video != NULL) - { - static_cast(Video)->DumpAdapters(); - } -} - // Mode enumeration -------------------------------------------------------- HRESULT WINAPI Win32Video::EnumDDModesCB (LPDDSURFACEDESC desc, void *data) diff --git a/src/win32/zdoom.rc b/src/win32/zdoom.rc index 7c953edaca..b23fe14eb0 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 ec6e0cea03..fb06555244 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -185,7 +185,7 @@ ACTOR Actor native //: Thinker action native A_Jump(int chance = 256, state label, ...); action native A_CustomMissile(class missiletype, float spawnheight = 32, int spawnofs_xy = 0, float angle = 0, int flags = 0, float pitch = 0); action native A_CustomBulletAttack(float spread_xy, float spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", float range = 0, int flags = 0); - action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, bool aim = false, float maxdiff = 0, class pufftype = "BulletPuff"); + action native A_CustomRailgun(int damage, int spawnofs_xy = 0, color color1 = "", color color2 = "", int flags = 0, bool aim = false, float maxdiff = 0, class pufftype = "BulletPuff", float spread_xy = 0, float spread_z = 0); action native A_JumpIfHealthLower(int health, state label); action native A_JumpIfCloser(float distance, state label); action native A_JumpIfTracerCloser(float distance, state label); @@ -224,6 +224,7 @@ 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); @@ -245,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 fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); + action native A_JumpIfInTargetLOS (state label, float fov = 0, int flags = 0, float dist_max = 0, float dist_close = 0); action native A_DamageMaster(int amount, name damagetype = "none"); action native A_DamageChildren(int amount, name damagetype = "none"); action native A_DamageSiblings(int amount, name damagetype = "none"); diff --git a/wadsrc/static/actors/chex/chexweapons.txt b/wadsrc/static/actors/chex/chexweapons.txt index 4fa7eec560..97043f2ca2 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 6c027d82a1..d6fdd42de0 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -1,4 +1,12 @@ +// 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; @@ -8,11 +16,15 @@ 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; @@ -47,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; @@ -75,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; @@ -95,6 +117,10 @@ 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 { diff --git a/wadsrc/static/actors/doom/doomweapons.txt b/wadsrc/static/actors/doom/doomweapons.txt index 076559c0ee..b748f86115 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 0d5ede1a63..67d3946b94 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/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 993da14859..7e2d0a2eeb 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 7e4259cf8a..ad98d7ecc7 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 ca3b8384c7..607c8d66a3 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 29312ca79d..5fb4175fd4 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 0c441726da..15673ab1b5 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 ae9d3b84ae..14ef4b00fe 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/clericplayer.txt b/wadsrc/static/actors/hexen/clericplayer.txt index 4fe6629e28..73276e9815 100644 --- a/wadsrc/static/actors/hexen/clericplayer.txt +++ b/wadsrc/static/actors/hexen/clericplayer.txt @@ -23,6 +23,7 @@ ACTOR ClericPlayer : PlayerPawn Player.HealRadiusType "Health" Player.Hexenarmor 10, 10, 25, 5, 20 Player.StartItem "CWeapMace" + Player.Portrait "P_CWALK1" Player.WeaponSlot 1, CWeapMace Player.WeaponSlot 2, CWeapStaff Player.WeaponSlot 3, CWeapFlame diff --git a/wadsrc/static/actors/hexen/clericstaff.txt b/wadsrc/static/actors/hexen/clericstaff.txt index 3b9cd16995..e55674fa5d 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 78cbbf0a3b..502de50307 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 6693b79bf1..0122d7f740 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 63585b1cc5..5c7d6dc256 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/fighterplayer.txt b/wadsrc/static/actors/hexen/fighterplayer.txt index 2253ba5b64..809b1f9e98 100644 --- a/wadsrc/static/actors/hexen/fighterplayer.txt +++ b/wadsrc/static/actors/hexen/fighterplayer.txt @@ -23,6 +23,7 @@ ACTOR FighterPlayer : PlayerPawn Player.StartItem "FWeapFist" Player.ForwardMove 1.08, 1.2 Player.SideMove 1.125, 1.475 + Player.Portrait "P_FWALK1" Player.WeaponSlot 1, FWeapFist Player.WeaponSlot 2, FWeapAxe Player.WeaponSlot 3, FWeapHammer diff --git a/wadsrc/static/actors/hexen/fighterquietus.txt b/wadsrc/static/actors/hexen/fighterquietus.txt index 3bd5904f78..034914d23d 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 c3a222ea63..984c2d90ce 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 4820a706db..b2063a6ec0 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 ------------------------------------------------------------- diff --git a/wadsrc/static/actors/hexen/healingradius.txt b/wadsrc/static/actors/hexen/healingradius.txt index 8c5b0e53ab..e0556915bc 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 7d4a13a949..e36451e7f5 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 17c7ccf4f9..b5dfdb5462 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 02658bcba5..85402f223c 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/mageplayer.txt b/wadsrc/static/actors/hexen/mageplayer.txt index a34698366d..d843b00a9c 100644 --- a/wadsrc/static/actors/hexen/mageplayer.txt +++ b/wadsrc/static/actors/hexen/mageplayer.txt @@ -25,6 +25,7 @@ ACTOR MagePlayer : PlayerPawn Player.StartItem "MWeapWand" Player.ForwardMove 0.88, 0.92 Player.SideMove 0.875, 0.925 + Player.Portrait "P_MWALK1" Player.WeaponSlot 1, MWeapWand Player.WeaponSlot 2, MWeapFrost Player.WeaponSlot 3, MWeapLightning diff --git a/wadsrc/static/actors/hexen/magestaff.txt b/wadsrc/static/actors/hexen/magestaff.txt index e402e57c0b..65c0ac8931 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 ebb8e2ca12..aaac9e5da0 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 6130fe9624..ee7ddd37c6 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 eaecd692e2..45b649597c 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 c3bdf29ba5..dd15fc8fd9 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 17d193d2ac..5fd6703b87 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 399c2dd89b..a4b3b85844 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 bcbdfc33cb..77a05f6afe 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 6b8de50c9b..d94f001515 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 5c1fa9d8fc..1f0fe73d22 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 84264764b5..c02c19c3be 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 3a4dd20d97..49b6b39a24 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -5,12 +5,13 @@ ACTOR Inventory native Inventory.InterHubAmount 1 Inventory.UseSound "misc/invuse" Inventory.PickupSound "misc/i_pkup" + Inventory.PickupMessage "$TXT_DEFAULTPICKUPMSG" action native A_JumpIfNoAmmo(state label); action native A_CustomPunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class pufftype = "BulletPuff", float range = 0, float lifesteal = 0); action native A_FireBullets(float spread_xy, float spread_z, int numbullets, int damageperbullet, class pufftype = "BulletPuff", int flags = 1, float range = 0); action native A_FireCustomMissile(class missiletype, float angle = 0, bool useammo = true, int spawnofs_xy = 0, float spawnheight = 0, bool aimatangle = false, float pitch = 0); - action native A_RailAttack(int damage, int spawnofs_xy = 0, int useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff"); + action native A_RailAttack(int damage, int spawnofs_xy = 0, int useammo = true, color color1 = "", color color2 = "", int flags = 0, float maxdiff = 0, class pufftype = "BulletPuff", float spread_xy = 0, float spread_z = 0); action native A_Light(int extralight); action native A_Light0(); action native A_Light1(); @@ -40,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", float range = 0, float lifesteal = 0); + action native A_Saw(sound fullsound = "weapons/sawfull", sound hitsound = "weapons/sawhit", int damage = 2, class pufftype = "BulletPuff", int flags = 0, float range = 0, float spread_xy = 2.8125, float spread_z = 0, float lifesteal = 0); 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/strife/acolyte.txt b/wadsrc/static/actors/strife/acolyte.txt index 73b27864b3..1d04147f03 100644 --- a/wadsrc/static/actors/strife/acolyte.txt +++ b/wadsrc/static/actors/strife/acolyte.txt @@ -15,7 +15,7 @@ ACTOR Acolyte : StrifeHumanoid +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 8820f32d05..b24080b1a7 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 f569fb1ed1..e4e50af779 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 dd79c0f19e..fee3eb0f06 100644 --- a/wadsrc/static/actors/strife/loremaster.txt +++ b/wadsrc/static/actors/strife/loremaster.txt @@ -23,7 +23,7 @@ ACTOR Loremaster 12 +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 3666728e07..ac23e4e660 100644 --- a/wadsrc/static/actors/strife/macil.txt +++ b/wadsrc/static/actors/strife/macil.txt @@ -23,7 +23,7 @@ ACTOR Macil1 64 PainSound "macil/pain" ActiveSound "macil/active" CrushPainSound "misc/pcrush" - Tag "MACIL" + Tag "$TAG_MACIL1" Obituary "$OB_MACIL" DropItem "BoxOfBullets" MaxStepHeight 16 @@ -66,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 65f5ac653f..b6a2e09a5e 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 03b80c8946..2e0aff948a 100644 --- a/wadsrc/static/actors/strife/oracle.txt +++ b/wadsrc/static/actors/strife/oracle.txt @@ -15,7 +15,7 @@ ACTOR Oracle 199 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 b06cdfaa84..cf6b98b0b2 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 762aff6593..eaefda35d3 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 ff6faeec2d..3c539bca4f 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/sigil.txt b/wadsrc/static/actors/strife/sigil.txt index ab9c6d21bf..a2cc66c7ba 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 1de16474fd..8017144340 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 2be8d8ad67..2431e9438e 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/strifeitems.txt b/wadsrc/static/actors/strife/strifeitems.txt index 407a91843e..74b957b83e 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 d128cbbd64..b304f83bcb 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 d8ecd9bbb3..a80685c353 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 d287ace08d..5e3a419d56 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 214804b347..c20aed49bd 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 5245611808..957b381bfc 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 a2a4498e28..8bd2ee99a9 100644 --- a/wadsrc/static/animdefs.txt +++ b/wadsrc/static/animdefs.txt @@ -101,6 +101,85 @@ pic PTN1A0 tics 3 pic PTN1B0 tics 3 pic PTN1C0 tics 3 +// Hexen's player portraits +texture optional P_FWALK1 + pic P_FWALK1 tics 8 + pic P_FWALK2 tics 8 + pic P_FWALK3 tics 8 + pic P_FWALK4 tics 8 + +texture optional P_CWALK1 + pic P_CWALK1 tics 8 + pic P_CWALK2 tics 8 + pic P_CWALK3 tics 8 + pic P_CWALK4 tics 8 + +texture optional P_MWALK1 + pic P_MWALK1 tics 8 + pic P_MWALK2 tics 8 + pic P_MWALK3 tics 8 + pic P_MWALK4 tics 8 + +// Heretic's menu skulls +texture optional M_SKL00 + pic M_SKL00 tics 3 + pic M_SKL01 tics 3 + pic M_SKL02 tics 3 + pic M_SKL03 tics 3 + pic M_SKL04 tics 3 + pic M_SKL05 tics 3 + pic M_SKL06 tics 3 + pic M_SKL07 tics 3 + pic M_SKL08 tics 3 + pic M_SKL09 tics 3 + pic M_SKL10 tics 3 + pic M_SKL11 tics 3 + pic M_SKL12 tics 3 + pic M_SKL13 tics 3 + pic M_SKL14 tics 3 + pic M_SKL15 tics 3 + pic M_SKL16 tics 3 + pic M_SKL17 tics 3 + +texture optional M_SKL01 + pic M_SKL17 tics 3 + pic M_SKL16 tics 3 + pic M_SKL15 tics 3 + pic M_SKL14 tics 3 + pic M_SKL13 tics 3 + pic M_SKL12 tics 3 + pic M_SKL11 tics 3 + pic M_SKL10 tics 3 + pic M_SKL09 tics 3 + pic M_SKL08 tics 3 + pic M_SKL07 tics 3 + pic M_SKL06 tics 3 + pic M_SKL05 tics 3 + pic M_SKL04 tics 3 + pic M_SKL03 tics 3 + pic M_SKL02 tics 3 + pic M_SKL01 tics 3 + pic M_SKL00 tics 3 + +// Hexen's Fire Bulls +texture optional FBULA0 + pic FBULA0 tics 5 + pic FBULB0 tics 5 + pic FBULC0 tics 5 + pic FBULD0 tics 5 + pic FBULE0 tics 5 + pic FBULF0 tics 5 + pic FBULG0 tics 5 + +texture optional FBULB0 + pic FBULC0 tics 5 + pic FBULD0 tics 5 + pic FBULE0 tics 5 + pic FBULF0 tics 5 + pic FBULG0 tics 5 + pic FBULA0 tics 5 + pic FBULB0 tics 5 + // 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 09f38a80b6..98528363da 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -21,6 +21,7 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15 2F1F8E27FBB5EF21AFBE1F3B13C03037 // map16 1CE294781A2455DE72C197E0B3DF6212 // map31 { + setslopeoverflow resetplayerspeed } diff --git a/wadsrc/static/graphics/cursor.png b/wadsrc/static/graphics/cursor.png new file mode 100644 index 0000000000..c9ee9b6e31 Binary files /dev/null and b/wadsrc/static/graphics/cursor.png differ diff --git a/wadsrc/static/graphics/m_back_d.png b/wadsrc/static/graphics/m_back_d.png new file mode 100644 index 0000000000..2fb4a827d8 Binary files /dev/null and b/wadsrc/static/graphics/m_back_d.png differ diff --git a/wadsrc/static/graphics/m_back_h.png b/wadsrc/static/graphics/m_back_h.png new file mode 100644 index 0000000000..fc45dcd7b6 Binary files /dev/null and b/wadsrc/static/graphics/m_back_h.png differ diff --git a/wadsrc/static/graphics/m_back_s.png b/wadsrc/static/graphics/m_back_s.png new file mode 100644 index 0000000000..ab63b43105 Binary files /dev/null and b/wadsrc/static/graphics/m_back_s.png differ diff --git a/wadsrc/static/graphics/m_back_x.png b/wadsrc/static/graphics/m_back_x.png new file mode 100644 index 0000000000..d785b6788a Binary files /dev/null and b/wadsrc/static/graphics/m_back_x.png differ diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 2f23edc2e9..5f0eff88e3 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,6 +1479,7 @@ 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: @@ -1310,6 +1559,8 @@ MNU_DELETESG = "Do you really want to delete the savegame\n"; MNU_ONLYREGISTERED = "ONLY AVAILABLE IN THE REGISTERED VERSION"; +MNU_EPISODE = "Select Episode"; + // Bloodbath announcer BBA_BONED = "%k boned %o like a fish"; diff --git a/wadsrc/static/mapinfo/chex.txt b/wadsrc/static/mapinfo/chex.txt index 036d2af895..5a76298cb8 100644 --- a/wadsrc/static/mapinfo/chex.txt +++ b/wadsrc/static/mapinfo/chex.txt @@ -41,6 +41,17 @@ gameinfo defaultdropstyle = 1 endoom = "ENDOOM" player5start = 4001 + drawreadthis = true + pickupcolor = "d6 ba 45" + quitmessages = "$QUITMSG", "$QUITMSG23", "$QUITMSG24", "$QUITMSG25", "$QUITMSG26", "$QUITMSG27", "$QUITMSG28", "$QUITMSG29" + menufontcolor_title = "GREEN" + menufontcolor_label = "UNTRANSLATED" + menufontcolor_value = "GRAY" + menufontcolor_action = "GRAY" + menufontcolor_header = "YELLOW" + menufontcolor_highlight = "BLUE" + menufontcolor_selection = "GOLD" + menubackbutton = "M_BACK_H" } skill baby diff --git a/wadsrc/static/mapinfo/doom1.txt b/wadsrc/static/mapinfo/doom1.txt index b3e6316501..064f3777a1 100644 --- a/wadsrc/static/mapinfo/doom1.txt +++ b/wadsrc/static/mapinfo/doom1.txt @@ -14,6 +14,7 @@ gameinfo borderflat = "FLOOR7_2" drawreadthis = true intermissionmusic = "$MUSIC_INTER" + quitmessages = "$QUITMSG", "$QUITMSG1", "$QUITMSG2", "$QUITMSG3", "$QUITMSG4", "$QUITMSG5", "$QUITMSG6", "$QUITMSG7" } clearepisodes diff --git a/wadsrc/static/mapinfo/doomcommon.txt b/wadsrc/static/mapinfo/doomcommon.txt index 3de2717566..9fd4b96379 100644 --- a/wadsrc/static/mapinfo/doomcommon.txt +++ b/wadsrc/static/mapinfo/doomcommon.txt @@ -40,6 +40,18 @@ gameinfo defaultdropstyle = 1 endoom = "ENDOOM" player5start = 4001 + pickupcolor = "d6 ba 45" + quitmessages = "$QUITMSG", "$QUITMSG1", "$QUITMSG2", "$QUITMSG3", "$QUITMSG4", "$QUITMSG5", "$QUITMSG6", "$QUITMSG7", + "$QUITMSG8", "$QUITMSG9", "$QUITMSG10", "$QUITMSG11", "$QUITMSG12", "$QUITMSG13", "$QUITMSG14" + + menufontcolor_title = "RED" + menufontcolor_label = "UNTRANSLATED" + menufontcolor_value = "GRAY" + menufontcolor_action = "GRAY" + menufontcolor_header = "GOLD" + menufontcolor_highlight = "YELLOW" + menufontcolor_selection = "BRICK" + menubackbutton = "M_BACK_D" } skill baby diff --git a/wadsrc/static/mapinfo/heretic.txt b/wadsrc/static/mapinfo/heretic.txt index 0c78f1184a..b9f88e2b92 100644 --- a/wadsrc/static/mapinfo/heretic.txt +++ b/wadsrc/static/mapinfo/heretic.txt @@ -41,6 +41,16 @@ gameinfo defaultdropstyle = 1 endoom = "ENDTEXT" player5start = 4001 + pickupcolor = "d6 ba 45" + quitmessages = "$*RAVENQUITMSG" + menufontcolor_title = "UNTRANSLATED" + menufontcolor_label = "GREEN" + menufontcolor_value = "UNTRANSLATED" + menufontcolor_action = "UNTRANSLATED" + menufontcolor_header = "GOLD" + menufontcolor_highlight = "YELLOW" + menufontcolor_selection = "DARKGREEN" + menubackbutton = "M_BACK_H" } skill baby diff --git a/wadsrc/static/mapinfo/hexen.txt b/wadsrc/static/mapinfo/hexen.txt index 344f4eb412..8f0962a50e 100644 --- a/wadsrc/static/mapinfo/hexen.txt +++ b/wadsrc/static/mapinfo/hexen.txt @@ -39,6 +39,16 @@ gameinfo defaultrespawntime = 12 defaultdropstyle = 1 player5start = 9100 + pickupcolor = "d6 ba 45" + quitmessages = "$*RAVENQUITMSG" + menufontcolor_title = "UNTRANSLATED" + menufontcolor_label = "RED" + menufontcolor_value = "UNTRANSLATED" + menufontcolor_action = "UNTRANSLATED" + menufontcolor_header = "GOLD" + menufontcolor_highlight = "YELLOW" + menufontcolor_selection = "BRICK" + menubackbutton = "M_BACK_X" } skill baby diff --git a/wadsrc/static/mapinfo/strife.txt b/wadsrc/static/mapinfo/strife.txt index 6c213d81eb..ffb70e6966 100644 --- a/wadsrc/static/mapinfo/strife.txt +++ b/wadsrc/static/mapinfo/strife.txt @@ -42,6 +42,16 @@ gameinfo defaultdropstyle = 2 endoom = "ENDSTRF" player5start = 5 + pickupcolor = "d6 ba 45" + quitmessages = "$QUITMSG", "$QUITMSG15", "$QUITMSG16", "$QUITMSG17", "$QUITMSG18", "$QUITMSG19", "$QUITMSG20", "$QUITMSG21", "$QUITMSG22" + menufontcolor_title = "UNTRANSLATED" + menufontcolor_label = "UNTRANSLATED" + menufontcolor_value = "GRAY" + menufontcolor_action = "GRAY" + menufontcolor_header = "RED" + menufontcolor_highlight = "GREEN" + menufontcolor_selection = "GOLD" + menubackbutton = "M_BACK_S" } skill baby diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt new file mode 100644 index 0000000000..ee624ed751 --- /dev/null +++ b/wadsrc/static/menudef.txt @@ -0,0 +1,1333 @@ +//------------------------------------------------------------------------------------------- +// +// Note: +// Much of the menu structure defined here is accessed internally by CCMDs +// and menu generation code. If you want to design your own menus make sure +// that they are named identically and that links to all important submenus +// are present. +// +//------------------------------------------------------------------------------------------- + +DEFAULTLISTMENU +{ + Font "BigFont", "Untranslated" + IfGame(Doom, Chex) + { + Selector "M_SKULL1", -32, -5 + Linespacing 16 + Font "BigFont", "Red" + } + IfGame(Strife) + { + Selector "M_CURS1", -28, -5 + Linespacing 19 + } + IfGame(Heretic, Hexen) + { + Selector "M_SLCTR1", -28, -1 + Linespacing 20 + } +} + +//------------------------------------------------------------------------------------------- +// +// The main menu. There's a lot of differences here between the games +// +//------------------------------------------------------------------------------------------- + +LISTMENU "MainMenu" +{ + IfGame(Doom, Chex) + { + StaticPatch 94, 2, "M_DOOM" + Position 97, 72 + IfOption(ReadThis) + { + Position 97, 64 + } + } + IfGame(Strife) + { + StaticPatch 84, 2, "M_STRIFE" + Position 97, 45 + } + IfGame(Heretic) + { + StaticPatch 88, 0, "M_HTIC" + StaticPatch 40, 10, "M_SKL01" + StaticPatch 232, 10, "M_SKL00" + Position 110, 56 + } + IfGame(Hexen) + { + StaticPatch 88, 0, "M_HTIC" + StaticPatch 37, 80, "FBULB0" + StaticPatch 278, 80, "FBULA0" + Position 110, 56 + } + + IfGame(Doom, Strife, Chex) + { + PatchItem "M_NGAME", "n", "PlayerclassMenu" + PatchItem "M_LOADG", "l", "LoadGameMenu" + PatchItem "M_SAVEG", "s", "SaveGameMenu" + PatchItem "M_OPTION","o", "OptionsMenu" + ifOption(ReadThis) + { + PatchItem "M_RDTHIS","r", "ReadThisMenu" + } + PatchItem "M_QUITG", "q", "QuitMenu" + } + + IfGame(Heretic, Hexen) + { + TextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + TextItem "$MNU_OPTIONS", "o", "OptionsMenu" + TextItem "$MNU_GAMEFILES", "g", "GameFilesMenu" + TextItem "$MNU_INFO", "i", "ReadThisMenu" + TextItem "$MNU_QUITGAME", "q", "QuitMenu" + } +} + +//------------------------------------------------------------------------------------------- +// +// Important note about the following template menus: +// Don't even think about replacing them with something that's not an empty menu +// with some static elements only. Proper function is not guaranteed then. +// +//------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------- +// +// The player class menu +// The init code will set the first item to 'autoselect' if it's the only one. +// +//------------------------------------------------------------------------------------------- + +ListMenu "PlayerclassMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$NETGAME" + } + IfGame(Chex) + { + NetgameMessage "$CNETGAME" + } + + IfGame(Doom, Strife, Chex) + { + StaticTextCentered 160, 15, "$MNU_CHOOSECLASS" + Position 48, 63 + PlayerDisplay 220, 63, "20 00 00", "80 00 40" + MouseWindow 0, 220 + } + IfGame(Heretic) + { + StaticTextCentered 160, 15, "$MNU_CHOOSECLASS" + Position 80, 50 + PlayerDisplay 220, 50, "20 00 00", "80 00 40" + MouseWindow 0, 220 + } + IfGame(Hexen) + { + StaticText 34, 24, "$MNU_CHOOSECLASS" + Position 66, 58 + PlayerDisplay 174, 8, "00 07 00", "40 53 40" + MouseWindow 0, 174 + } + // The rest of this menu will be set up based on the actual player definitions. +} + +//------------------------------------------------------------------------------------------- +// +// The episode menu +// The init code will set the first item to 'autoselect' if it's the only one. +// +//------------------------------------------------------------------------------------------- + +ListMenu "EpisodeMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$NETGAME" + } + IfGame(Chex) + { + NetgameMessage "$CNETGAME" + } + + IfGame(Doom, Chex) + { + Position 48, 63 + StaticPatch 54, 38, "M_EPISOD" + } + IfGame(Strife) + { + Position 48, 63 + StaticText 54, 38, "$MNU_EPISODE" + } + IfGame(Heretic, Hexen) + { + Position 80, 50 + } + // items will be filled in by MAPINFO +} + +//------------------------------------------------------------------------------------------- +// +// The skill menu +// Most of this will be filled in at runtime +// +//------------------------------------------------------------------------------------------- + +ListMenu "SkillMenu" +{ + + IfGame(Doom, Chex) + { + StaticPatch 96, 14, "M_NEWG" + } + IfGame(Strife) + { + StaticPatch 96, 14, "M_NGAME" + } + IfGame(Doom, Strife, Chex) + { + StaticPatch 54, 38, "M_SKILL" + Position 48, 63 + } + IfGame (Heretic) + { + Position 38, 30 + } + IfGame (Hexen) + { + StaticText 74, 16, "$MNU_CHOOSESKILL" + Position 38, 44 + } +} + +//------------------------------------------------------------------------------------------- +// +// Raven's game files menu +// +//------------------------------------------------------------------------------------------- + +ListMenu "GameFilesMenu" +{ + Position 110, 60 + TextItem "$MNU_LOADGAME", "l", "LoadGameMenu" + TextItem "$MNU_SAVEGAME", "s", "SaveGameMenu" +} + +//------------------------------------------------------------------------------------------- +// +// Base definition for load game menu. Only the configurable part is done here +// +//------------------------------------------------------------------------------------------- + +ListMenu "LoadGameMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$LOADNET" + } + IfGame(Chex) + { + NetgameMessage "$CLOADNET" + } + IfGame(Doom, Strife, Chex) + { + StaticPatchCentered 160, -20, "M_LOADG" + } + IfGame(Heretic, Hexen) + { + StaticTextCentered 160, -10, "$MNU_LOADGAME" + } + Position 80,54 + Class "LoadMenu" // uses its own implementation +} + +//------------------------------------------------------------------------------------------- +// +// Base definition for save game menu. Only the configurable part is done here +// +//------------------------------------------------------------------------------------------- + +ListMenu "SaveGameMenu" +{ + IfGame(Doom, Strife, Chex) + { + StaticPatchCentered 160, -20, "M_SAVEG" + } + IfGame(Heretic, Hexen) + { + StaticTextCentered 160, -10, "$MNU_SAVEGAME" + } + Position 80,54 + Class "SaveMenu" // uses its own implementation +} + +//------------------------------------------------------------------------------------------- +// +// The option menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "YesNo" +{ + 0, "No" + 1, "Yes" +} + +OptionValue "NoYes" +{ + 0, "Yes" + 1, "No" +} + +OptionValue "OnOff" +{ + 0, "Off" + 1, "On" +} + +OptionValue "OffOn" +{ + 0, "On" + 1, "Off" +} + +OptionMenuSettings +{ + // These can be overridden if a different menu fonts requires it. + Linespacing 8 + LabelOffset 0 + IfGame(Heretic, Hexen) + { + Linespacing 9 + LabelOffset 2 + } +} + +DefaultOptionMenu +{ + Position -15 + IfGame(Heretic, Hexen) + { + Position -13 + } +} + +OptionMenu "OptionsMenu" +{ + Title "OPTIONS" + Submenu "Customize Controls", "CustomizeControls" + Submenu "Mouse options", "MouseOptions" + Submenu "Joystick options", "JoystickOptions" + StaticText " " + Submenu "Player Setup", "PlayerMenu" + Submenu "Gameplay Options", "GameplayOptions" + Submenu "Compatibility Options", "CompatibilityOptions" + Submenu "Automap Options", "AutomapOptions" + Submenu "Sound Options", "SoundOptions" + Submenu "Display Options", "VideoOptions" + Submenu "Set video mode", "VideoModeMenu" + StaticText " " + SafeCommand "Reset to defaults", "reset2defaults" + SafeCommand "Reset to last saved", "reset2saved" + Command "Go to console", "menuconsole" +} + +//------------------------------------------------------------------------------------------- +// +// The player menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "Gender" +{ + 0, "Male" + 1, "Female" + 2, "Other" +} + +OptionValue "Autoaim" +{ + 0, "Never" + 1, "Very low" + 2, "Low" + 3, "Medium" + 4, "High" + 5, "Very high" + 6, "Always" +} + + +ListMenu "PlayerMenu" +{ + StaticTextCentered 160, 6, "$MNU_PLAYERSETUP" + Font "SmallFont" + Linespacing 14 + Position 48, 36 + + IfGame (Doom, Strife, Chex) + { + PlayerNameBox "Name", 0, "Playerbox" + Selector "-", -16, -1 + } + IfGame(Heretic, Hexen) + { + PlayerNameBox "Name", 5, "Playerbox" + Selector "-", -16, 1 + } + IfGame(Doom, Heretic, Strife, Chex) + { + MouseWindow 0, 220 + PlayerDisplay 220, 80, "20 00 00", "80 00 40", 1, "PlayerDisplay" + } + IfGame(Hexen) + { + MouseWindow 0, 220 + PlayerDisplay 220, 80, "00 07 00", "40 53 40", 1, "PlayerDisplay" + } + + ValueText "Team", "Team" + ValueText "Color", "Color" + Linespacing 10 + Slider "Red", "Red", 0, 255, 16 + Slider "Green", "Green", 0, 255, 16 + Linespacing 14 + Slider "Blue", "Blue", 0, 255, 16 + ValueText "Class", "Class" + ValueText "Skin", "Skin" + ValueText "Gender", "Gender", "Gender" + ValueText "Autoaim", "Autoaim", "Autoaim" + ValueText "Switch on pickup", "Switch", "OffOn" + ValueText "Always Run", "AlwaysRun", "OnOff" + Class "PlayerMenu" +} + +//------------------------------------------------------------------------------------------- +// +// Controls Menu +// +//------------------------------------------------------------------------------------------- + +OptionMenu "CustomizeControls" +{ + Title "CUSTOMIZE CONTROLS" + ScrollTop 2 + StaticTextSwitchable "ENTER to change, BACKSPACE to clear", "Press new key for control, ESC to cancel", "ControlMessage" + StaticText "" + StaticText "Controls", 1 + Control "Fire", "+attack" + Control "Secondary Fire", "+altattack" + Control "Use / Open", "+use" + Control "Move forward", "+forward" + Control "Move backward", "+back" + Control "Strafe left", "+moveleft" + Control "Strafe right", "+moveright" + Control "Turn left", "+left" + Control "Turn right", "+right" + Control "Jump", "+jump" + Control "Crouch", "+crouch" + Control "Crouch Toggle", "crouch" + Control "Fly / Swim up", "+moveup" + Control "Fly / Swim down", "+movedown" + Control "Stop flying", "land" + Control "Mouse look", "+mlook" + Control "Keyboard look", "+klook" + Control "Look up", "+lookup" + Control "Look down", "+lookdown" + Control "Center view", "centerview" + Control "Run", "+speed" + Control "Strafe", "+strafe" + Control "Show Scoreboard", "+showscores" + StaticText "" + StaticText "Chat", 1 + Control "Say", "messagemode" + Control "Team say", "messagemode2" + StaticText "" + StaticText "Weapons", 1 + Control "Next weapon", "weapnext" + Control "Previous weapon", "weapprev" + StaticText "" + StaticText "Inventory", 1 + Control "Activate item", "invuse" + Control "Activate all items", "invuseall" + Control "Next item", "invnext" + Control "Previous item", "invprev" + Control "Drop item", "invdrop" + Control "Query item", "invquery" + Control "Drop weapon", "weapdrop" + StaticText "" + StaticText "Other", 1 + Control "Toggle automap", "togglemap" + Control "Chasecam", "chase" + Control "Coop spy", "spynext" + Control "Screenshot", "screenshot" + Control "Open console", "toggleconsole" + StaticText "" + StaticText "Strife Popup Screens", 1 + Control "Mission objectives", "showpop 1" + Control "Keys list", "showpop 2" + Control "Weapons/ammo/stats", "showpop 3" +} + +//------------------------------------------------------------------------------------------- +// +// Mouse Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "Corners" +{ + -1, "Off" + 0, "Upper left" + 1, "Upper right" + 2, "Lower left" + 3, "Lower right" +} + +OptionValue "MenuMouse" +{ + 0, "No" + 1, "Yes" + 2, "Touchscreen-like" +} + +OptionMenu "MouseOptions" +{ + Title "MOUSE OPTIONS" + Option "Enable mouse", "use_mouse", "YesNo" + IfOption(Windows) // GUI mouse not operable in SDL interface right now. + { + Option "Enable mouse in menus", "m_use_mouse", "MenuMouse", "use_mouse" + Option "Show back button", "m_show_backbutton", "Corners", "use_mouse" + } + StaticText "" + Slider "Overall sensitivity", "mouse_sensitivity", 0.5, 2.5, 0.1 + Option "Prescale mouse movement", "m_noprescale", "NoYes" + Option "Smooth mouse movement", "smooth_mouse", "YesNo" + StaticText "" + Slider "Turning speed", "m_yaw", 0, 2.5, 0.1 + Slider "Mouselook speed", "m_pitch", 0, 2.5, 0.1 + Slider "Forward/Backward speed", "m_forward", 0, 2.5, 0.1 + Slider "Strafing speed", "m_side", 0, 2.5, 0.1 + StaticText "" + Option "Always Mouselook", "freelook", "OnOff" + Option "Invert Mouse", "invertmouse", "OnOff" + Option "Lookspring", "lookspring", "OnOff" + Option "Lookstrafe", "lookstrafe", "OnOff" +} + + +//------------------------------------------------------------------------------------------- +// +// Joystick Menu +// +//------------------------------------------------------------------------------------------- + +OptionMenu "JoystickOptions" +{ + Title "CONTROLLER OPTIONS" + // Will be filled in by joystick code. +} + +OptionValue "JoyAxisMapNames" +{ + -1, "None" + 0, "Turning" + 1, "Looking Up/Down" + 2, "Moving Forward" + 3, "Strafing" + 4, "Moving Up/Down" +} + +OptionValue "Inversion" +{ + 0, "Not Inverted" + 1, "Inverted" +} + +OptionMenu "JoystickConfigMenu" +{ + Title "CONFIGURE CONTROLLER" + Class "JoystickConfigMenu" + // Will be filled in by joystick code. +} + + +//------------------------------------------------------------------------------------------- +// +// Video Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue ColumnMethods +{ + 0.0, "Original" + 1.0, "Optimized" +} + +OptionValue RocketTrailTypes +{ + 0.0, "Off" + 1.0, "Particles" + 2.0, "Sprites" + 3.0, "Sprites & Particles" +} + +OptionValue BloodTypes +{ + 0.0, "Sprites" + 1.0, "Sprites & Particles" + 2.0, "Particles" +} + +OptionValue PuffTypes +{ + 0.0, "Sprites" + 1.0, "Particles" +} + +OptionValue Wipes +{ + 0.0, "None" + 1.0, "Melt" + 2.0, "Burn" + 3.0, "Crossfade" +} + +OptionValue Endoom +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Only modified" +} + +OptionValue Contrast +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Smooth" +} + +OptionValue DisplayTagsTypes +{ + 0.0, "None" + 1.0, "Items" + 2.0, "Weapons" + 3.0, "Both" +} + +OptionValue TextColors +{ + 0.0, "\cabrick" + 1.0, "\cbtan" + 2.0, "\ccgray" + 3.0, "\cdgreen" + 4.0, "\cebrown" + 5.0, "\cfgold" + 6.0, "\cgred" + 7.0, "\chblue" + 8.0, "\ciorange" + 9.0, "\cjwhite" + 10.0, "\ckyellow" + 11.0, "\cldefault" + 12.0, "\cmblack" + 13.0, "\cnlight blue" + 14.0, "\cocream" + 15.0, "\cpolive" + 16.0, "\cqdark green" + 17.0, "\crdark red" + 18.0, "\csdark brown" + 19.0, "\ctpurple" + 20.0, "\cudark gray" + 21.0, "\cvcyan" +} + +OptionValue Crosshairs +{ + // will be filled in from the XHAIRS lump +} + +OptionMenu "VideoOptions" +{ + Title "DISPLAY OPTIONS" + Submenu "Message Options", "MessageOptions" + Submenu "Scoreboard Options", "ScoreboardOptions" + StaticText " " + Slider "Screen size", "screenblocks", 3.0, 12.0, 1.0 + Slider "Brightness", "Gamma", 1.0, 3.0, 0.1 + Option "Vertical Sync", "vid_vsync", "OnOff" + Option "Column render mode", "r_columnmethod", "ColumnMethods" + + StaticText " " + Option "Crosshair", "crosshair", "Crosshairs" + Option "Display nametags", "displaynametags", "DisplayTagsTypes" + Option "Nametag color", "nametagcolor", "TextColors", "displaynametags" + Option "Stretch status bar", "st_scale", "OnOff" + Option "Alternative HUD", "hud_althud", "OnOff" + Option "Screen wipe style", "wipetype", "Wipes" + + IfOption(Windows) + { + Option "Show ENDOOM screen", "showendoom", "Endoom" + //Option "DirectDraw palette hack", "vid_palettehack", "OnOff" + //Option "Use attached surfaces", "vid_attachedsurfaces", "OnOff" + } + + StaticText " " + Option "Stretch short skies", "r_stretchsky", "OnOff" + Option "Use fuzz effect", "r_drawfuzz", "YesNo" + Option "Use fake contrast", "r_fakecontrast", "Contrast" + Option "Rocket Trails", "cl_rockettrails", "RocketTrailTypes" + Option "Blood Type", "cl_bloodtype", "BloodTypes" + Option "Bullet Puff Type", "cl_pufftype", "PuffTypes" + Option "Interpolate monster movement", "nomonsterinterpolation", "NoYes" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue MapColorTypes +{ + 0, "Custom" + 1, "Traditional Doom" + 2, "Traditional Strife" + 3, "Traditional Raven" +} + +OptionValue SecretTypes +{ + 0, "Never" + 1, "Only when found" + 2, "Always" +} + +OptionValue RotateTypes +{ + 0, "Off" + 1, "On" + 2, "On for overlay only" +} + +OptionValue OverlayTypes +{ + 0, "Off" + 1, "Overlay+Normal" + 2, "Overlay Only" +} + +OptionMenu AutomapOptions +{ + Title "AUTOMAP OPTIONS" + Option "Map color set", "am_colorset", "MapColorTypes" + Submenu "Set custom colors", "MapColorMenu" + Submenu "Customize map controls", "MapControlsMenu" + StaticText " " + Option "Rotate automap", "am_rotate", "RotateTypes" + Option "Overlay automap", "am_overlay", "OverlayTypes" + Option "Enable textured display", "am_textured", "OnOff" + StaticText " " + Option "Show item counts", "am_showitems", "OnOff" + Option "Show monster counts", "am_showmonsters", "OnOff" + Option "Show secret counts", "am_showsecrets", "OnOff" + Option "Show time elapsed", "am_showtime", "OnOff" + Option "Show total time elapsed", "am_showtotaltime", "OnOff" + Option "Show secrets on map", "am_map_secrets", "SecretTypes" + Option "Draw map background", "am_drawmapback", "OnOff" + Option "Show keys (cheat)", "am_showkeys", "OnOff" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Controls +// +//------------------------------------------------------------------------------------------- + +OptionMenu MapControlsMenu +{ + Title "CUSTOMIZE MAP CONTROLS" + ScrollTop 2 + StaticTextSwitchable "ENTER to change, BACKSPACE to clear", "Press new key for control, ESC to cancel", "ControlMessage" + StaticText "" + StaticText "Map Controls", 1 + MapControl "Pan left", "+am_panleft" + MapControl "Pan right", "+am_panright" + MapControl "Pan up", "+am_panup" + MapControl "Pan down", "+am_pandown" + MapControl "Zoom in", "+am_zoomin" + MapControl "Zoom out", "+am_zoomout" + MapControl "Toggle zoom", "am_gobig" + MapControl "Toggle follow", "am_togglefollow" + MapControl "Toggle grid", "am_togglegrid" + MapControl "Toggle texture","am_toggletexture" + MapControl "Set mark", "am_setmark" + MapControl "Clear mark", "am_clearmarks" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Colors +// +//------------------------------------------------------------------------------------------- + +OptionMenu MapColorMenu +{ + Title "CUSTOMIZE MAP COLORS" + SafeCommand "Restore default custom colors", "am_restorecolors" + StaticText " " + ColorPicker "Background", "am_backcolor" + ColorPicker "You", "am_yourcolor" + ColorPicker "1-sided walls", "am_wallcolor" + ColorPicker "2-sided walls with different floors", "am_fdwallcolor" + ColorPicker "2-sided walls with different ceilings", "am_cdwallcolor" + ColorPicker "Map grid", "am_gridcolor" + ColorPicker "Center point", "am_xhaircolor" + ColorPicker "Not-yet-seen walls", "am_notseencolor" + ColorPicker "Locked doors", "am_lockedcolor" + ColorPicker "Teleporter to the same map", "am_intralevelcolor" + ColorPicker "Teleporter to a different map", "am_interlevelcolor" + ColorPicker "Secret sector", "am_secretsectorcolor" + StaticText " " + StaticText "Cheat Mode", 1 + ColorPicker "Invisible 2-sided walls", "am_tswallcolor" + ColorPicker "Secret walls", "am_secretwallcolor" + ColorPicker "Actors", "am_thingcolor" + ColorPicker "Monsters", "am_thingcolor_monster" + ColorPicker "Friends", "am_thingcolor_friend" + ColorPicker "Items", "am_thingcolor_item" + ColorPicker "Count Items", "am_thingcolor_citem" + StaticText " " + StaticText "Overlay Mode", 1 + ColorPicker "You", "am_ovyourcolor" + ColorPicker "1-sided walls", "am_ovwallcolor" + ColorPicker "2-sided walls", "am_ovotherwallscolor" + ColorPicker "Not-yet-seen walls", "am_ovunseencolor" + ColorPicker "Teleporter", "am_ovtelecolor" + ColorPicker "Secret sector", "am_ovsecretsectorcolor" + StaticText " " + StaticText "Overlay Cheat Mode", 1 + ColorPicker "Actors", "am_ovthingcolor" + ColorPicker "Monsters", "am_ovthingcolor_monster" + ColorPicker "Friends", "am_ovthingcolor_friend" + ColorPicker "Items", "am_ovthingcolor_item" + ColorPicker "Count Items", "am_ovthingcolor_citem" +} + +//------------------------------------------------------------------------------------------- +// +// Color Picker +// +//------------------------------------------------------------------------------------------- + +OptionMenu ColorPickerMenu +{ + Title "SELECT COLOR" + // This menu will be created by the calling code +} + +//------------------------------------------------------------------------------------------- +// +// Messages +// +//------------------------------------------------------------------------------------------- + + +OptionValue ScaleValues +{ + 0, "Off" + 1, "On" + 2, "Double" +} + +OptionValue MessageLevels +{ + 0.0, "Item Pickup" + 1.0, "Obituaries" + 2.0, "Critical Messages" +} + +OptionMenu MessageOptions +{ + Title "MESSAGES" + Option "Show messages", "show_messages", "OnOff" + Option "Show obituaries", "show_obituaries", "OnOff" + Option "Scale text in high res", "con_scaletext", "ScaleValues" + Option "Minimum message level", "msg", "MessageLevels" + Option "Center messages", "con_centernotify", "OnOff" + StaticText " " + StaticText "Message Colors", 1 + StaticText " " + Option "Item Pickup", "msg0color", "TextColors" + Option "Obituaries", "msg1color", "TextColors" + Option "Critical Messages", "msg2color", "TextColors" + Option "Chat Messages", "msg3color", "TextColors" + Option "Team Messages", "msg4color", "TextColors" + Option "Centered Messages", "msgmidcolor", "TextColors" + StaticText " " + Option "Screenshot messages", "screenshot_quiet", "OffOn" + Option "Detailed save messages", "longsavemessages", "OnOff" +} + +//------------------------------------------------------------------------------------------- +// +// Scoreboard +// +//------------------------------------------------------------------------------------------- + +OptionMenu ScoreboardOptions +{ + Title "SCOREBOARD OPTIONS" + StaticText "Cooperative Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_cooperative_enable", "YesNo" + Option "Header Color", "sb_cooperative_headingcolor", "TextColors" + Option "Your Player Color", "sb_cooperative_yourplayercolor", "TextColors" + Option "Other Players' Color", "sb_cooperative_otherplayercolor", "TextColors" + StaticText " " + StaticText " " + StaticText "Deathmatch Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_deathmatch_enable", "YesNo" + Option "Header Color", "sb_deathmatch_headingcolor", "TextColors" + Option "Your Player Color", "sb_deathmatch_yourplayercolor", "TextColors" + Option "Other Players' Color", "sb_deathmatch_otherplayercolor", "TextColors" + StaticText " " + StaticText " " + StaticText "Team Deathmatch Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_teamdeathmatch_enable", "YesNo" + Option "Header Color", "sb_teamdeathmatch_headingcolor", "TextColors" +} + +/*======================================= + * + * Gameplay Options (dmflags) Menu + * + *=======================================*/ + +OptionValue SmartAim +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Never friends" + 3.0, "Only monsters" +} + +OptionValue FallingDM +{ + 0, "Off" + 1, "Old" + 2, "Hexen" + 3, "Strife" +} + +OptionValue JumpCrouch +{ + 0, "Default" + 1, "Off" + 2, "On" +} + + +OptionMenu GameplayOptions +{ + Title "GAMEPLAY OPTIONS" + Indent 222 + Option "Teamplay", "teamplay", "OnOff" + Slider "Team damage scalar", "teamdamage", 0, 1, 0.05 + StaticText " " + Option "Smart Autoaim", "sv_smartaim", "SmartAim" + StaticText " " + Option "Falling damage", "sv_fallingdamage", "FallingDM" + Option "Drop weapon", "sv_weapondrop", "YesNo" + Option "Double ammo", "sv_doubleammo", "YesNo" + Option "Infinite ammo", "sv_infiniteammo", "YesNo" + Option "Infinite inventory", "sv_infiniteinventory", "YesNo" + Option "No monsters", "sv_nomonsters", "YesNo" + Option "No monsters to exit", "sv_killallmonsters", "YesNo" + Option "Monsters respawn", "sv_monsterrespawn", "YesNo" + Option "No respawn", "sv_norespawn", "YesNo" + Option "Items respawn", "sv_itemrespawn", "YesNo" + Option "Big powerups respawn", "sv_respawnsuper", "YesNo" + Option "Fast monsters", "sv_fastmonsters", "YesNo" + Option "Degeneration", "sv_degeneration", "YesNo" + Option "Allow Autoaim", "sv_noautoaim", "NoYes" + Option "Allow Suicide", "sv_disallowsuicide", "NoYes" + Option "Allow jump", "sv_jump", "JumpCrouch" + Option "Allow crouch", "sv_crouch", "JumpCrouch" + Option "Allow freelook", "sv_nofreelook", "NoYes" + Option "Allow FOV", "sv_nofov", "NoYes" + Option "Allow BFG aiming", "sv_nobfgaim", "NoYes" + Option "Allow automap", "sv_noautomap", "NoYes" + Option "Automap allies", "sv_noautomapallies", "NoYes" + Option "Allow spying", "sv_disallowspying", "NoYes" + Option "Chasecam cheat", "sv_chasecam", "YesNo" + Option "Check ammo for weapon switch", "sv_dontcheckammo", "NoYes" + Option "Killing Romero kills all his spawns", "sv_killbossmonst", "YesNo" + + StaticText " " + StaticText "Deathmatch Settings",1 + Option "Weapons stay", "sv_weaponstay", "YesNo" + Option "Allow powerups", "sv_noitems", "NoYes" + Option "Allow health", "sv_nohealth", "YesNo" + Option "Allow armor", "sv_noarmor", "YesNo" + Option "Spawn farthest", "sv_spawnfarthest", "YesNo" + Option "Same map", "sv_samelevel", "YesNo" + Option "Force respawn", "sv_forcerespawn", "YesNo" + Option "Allow exit", "sv_noexit", "YesNo" + Option "Barrels respawn", "sv_barrelrespawn", "YesNo" + Option "Respawn protection", "sv_respawnprotect", "YesNo" + Option "Lose frag if fragged", "sv_losefrag", "YesNo" + Option "Keep frags gained", "sv_keepfrags", "YesNo" + Option "No team switching", "sv_noteamswitch", "YesNo" + + StaticText " " + StaticText "Cooperative Settings",1 + Option "Spawn multi. weapons", "sv_noweaponspawn", "NoYes" + Option "Lose entire inventory", "sv_cooploseinventory", "YesNo" + Option "Keep keys", "sv_cooplosekeys", "NoYes" + Option "Keep weapons", "sv_cooploseweapons", "NoYes" + Option "Keep armor", "sv_cooplosearmor", "NoYes" + Option "Keep powerups", "sv_cooplosepowerups", "NoYes" + Option "Keep ammo", "sv_cooploseammo", "NoYes" + Option "Lose half ammo", "sv_coophalveammo", "YesNo" + Option "Spawn where died", "sv_samespawnspot", "YesNo" + Class "GameplayMenu" +} + +/*======================================= + * + * Compatibility Options Menu + * + *=======================================*/ + + +OptionValue CompatModes +{ + 0, "Default" + 1, "Doom" + 2, "Doom (strict)" + 3, "Boom" + 6, "Boom (strict)" + 5, "MBF" + 4, "ZDoom 2.0.63" +} + +OptionMenu "CompatibilityOptions" +{ + Title "COMPATIBILITY OPTIONS" + Option "Compatibility mode", "compatmode", "CompatModes", "", 1 + StaticText " " + Option "Find shortest textures like Doom", "compat_SHORTTEX", "YesNo" + Option "Use buggier stair building", "compat_stairs", "YesNo" + Option "Find neighboring light like Doom", "compat_LIGHT", "YesNo" + Option "Limit Pain Elementals' Lost Souls", "compat_LIMITPAIN", "YesNo" + Option "Don't let others hear your pickups", "compat_SILENTPICKUP", "YesNo" + Option "Actors are infinitely tall", "compat_nopassover", "YesNo" + Option "Enable wall running", "compat_WALLRUN", "YesNo" + Option "Spawn item drops on the floor", "compat_NOTOSSDROPS", "YesNo" + Option "All special lines can block ", "compat_USEBLOCKING", "YesNo" + Option "Disable BOOM door light effect", "compat_NODOORLIGHT", "YesNo" + Option "Raven scrollers use original speed", "compat_RAVENSCROLL", "YesNo" + Option "Use original sound target handling", "compat_SOUNDTARGET", "YesNo" + Option "DEH health settings like Doom2.exe", "compat_DEHHEALTH", "YesNo" + Option "Self ref. sectors don't block shots", "compat_TRACE", "YesNo" + Option "Monsters get stuck over dropoffs", "compat_DROPOFF", "YesNo" + Option "Monsters cannot cross dropoffs", "compat_CROSSDROPOFF", "YesNo" + Option "Monsters see invisible players", "compat_INVISIBILITY", "YesNo" + Option "Boom scrollers are additive", "compat_BOOMSCROLL", "YesNo" + Option "Inst. moving floors are not silent", "compat_silentinstantfloors", "YesNo" + Option "Sector sounds use center as source", "compat_SECTORSOUNDS", "YesNo" + Option "Use Doom heights for missile clipping", "compat_MISSILECLIP", "YesNo" + Option "Allow any bossdeath for level special", "compat_ANYBOSSDEATH", "YesNo" + Option "No Minotaur floor flames in water", "compat_MINOTAUR", "YesNo" + Option "Original A_Mushroom speed in DEH mods", "compat_MUSHROOM", "YesNo" + Option "Monster movement is affected by effects", "compat_MBFMONSTERMOVE", "YesNo" + Option "Crushed monsters can be resurrected", "compat_CORPSEGIBS", "YesNo" + Option "Friendly monsters aren't blocked", "compat_NOBLOCKFRIENDS", "YesNo" + Option "Invert sprite sorting", "compat_SPRITESORT", "YesNo" + Option "Use Doom code for hitscan checks", "compat_HITSCAN", "YesNo" + Option "Cripple sound for silent BFG trick", "compat_soundslots", "YesNo" + Option "Draw polyobjects like Hexen", "compat_POLYOBJ", "YesNo" + + Class "CompatibilityMenu" +} + +/*======================================= + * + * Sound Options Menu + * + *=======================================*/ + +OptionValue SampleRates +{ + 0, "Default" + 4000, "4000 Hz" + 8000, "8000 Hz" + 11025, "11025 Hz" + 22050, "22050 Hz" + 32000, "32000 Hz" + 44100, "44100 Hz" + 48000, "48000 Hz" +} + + +OptionValue BufferSizes +{ + 0, "Default" + 64, "64 samples" + 128, "128 samples" + 256, "256 samples" + 512, "512 samples" + 1024, "1024 samples" + 2048, "2048 samples" + 4096, "4096 samples" +} + + +OptionValue BufferCounts +{ + 0, "Default" + 2, "2" + 3, "3" + 4, "4" + 5, "5" + 6, "6" + 7, "7" + 8, "8" + 9, "9" + 10, "10" + 11, "11" + 12, "12" +} + + +OptionString SoundOutputsWindows +{ + "Default", "Default" + "DirectSound", "DirectSound" + "WASAPI", "Vista WASAPI" + "ASIO", "ASIO" + "WaveOut", "WaveOut" + "OpenAL", "OpenAL (very beta)" + "No sound", "No sound" +} + + +OptionString SoundOutputsUnix +{ + "Default", "Default" + "OSS", "OSS" + "ALSA", "ALSA" + "SDL", "SDL" + "ESD", "ESD" + "No sound", "No sound" +} + +OptionString SoundOutputsMac +{ + "Sound Manager", "Sound Manager" + "Core Audio", "Core Audio" + "No sound", "No sound" +} + +OptionString OutputFormats +{ + "PCM-8", "8-bit" + "PCM-16", "16-bit" + "PCM-24", "24-bit" + "PCM-32", "32-bit" + "PCM-Float", "32-bit float" +} + + +OptionString SpeakerModes +{ + "Auto", "Auto" + "Mono", "Mono" + "Stereo", "Stereo" + "Prologic", "Dolby Prologic Decoder" + "Quad", "Quad" + "Surround", "5 speakers" + "5.1", "5.1 speakers" + "7.1", "7.1 speakers" +} + + +OptionString Resamplers +{ + "NoInterp", "No interpolation" + "Linear", "Linear" + "Cubic", "Cubic" + "Spline", "Spline" +} + +OptionValue MidiDevices +{ + // filled in by the sound code +} + +OptionMenu SoundOptions +{ + Title "SOUND OPTIONS" + Slider "Sounds volume", "snd_sfxvolume", 0, 1, 0.05 + Slider "Menu volume", "snd_menuvolume", 0, 1, 0.05 + Slider "Music volume", "snd_musicvolume", 0, 1, 0.05 + Option "MIDI device", "snd_mididevice", "MidiDevices" + StaticText " " + Option "Underwater reverb", "snd_waterreverb", "OnOff" + Slider "Underwater cutoff", "snd_waterlp", 0, 2000, 50 + Option "Randomize pitches", "snd_pitched", "OnOff" + Slider "Sound channels", "snd_channels", 8, 256, 8 + StaticText " " + Command "Restart sound", "snd_reset" + StaticText " " + IfOption(Windows) + { + Option "Output system", "snd_output", "SoundOutputsWindows" + } + IfOption(Unix) + { + Option "Output system", "snd_output", "SoundOutputsUnix" + } + IfOption(Mac) + { + Option "Output system", "snd_output", "SoundOutputsMac" + } + Option "Output format", "snd_output_format", "OutputFormats" + Option "Speaker mode", "snd_speakermode", "SpeakerModes" + Option "Resampler", "snd_resampler", "Resamplers" + Option "HRTF filter", "snd_hrtf", "OnOff" + + StaticText " " + Submenu "Advanced options", "AdvSoundOptions" + Submenu "Module replayer options", "ModReplayerOptions" +} + +/*======================================= + * + * Advanced Sound Options Menu + * + *=======================================*/ + +OptionMenu AdvSoundOptions +{ + Title "ADVANCED SOUND OPTIONS" + Option "Sample rate", "snd_samplerate", "SampleRates" + Option "Buffer size", "snd_buffersize", "BufferSizes" + Option "Buffer count", "snd_buffercount", "BufferCounts" + StaticText " " + StaticText "OPL Synthesis", 1 + Option "Only emulate one OPL chip", "opl_onechip", "OnOff" +} + +/*======================================= + * + * Module Replayer Options Menu + * + *=======================================*/ + +OptionValue ModReplayers +{ + 0.0, "FMOD" + 1.0, "foo_dumb" +} + + +OptionValue ModInterpolations +{ + 0.0, "None" + 1.0, "Linear" + 2.0, "Cubic" +} + + +OptionValue ModVolumeRamps +{ + 0.0, "None" + 1.0, "Logarithmic" + 2.0, "Linear" + 3.0, "XM=lin, else none" + 4.0, "XM=lin, else log" +} + + +OptionMenu ModReplayerOptions +{ + Title "MODULE REPLAYER OPTIONS" + Option "Replayer engine", "mod_dumb", "ModReplayers" + StaticText " " + Option "Sample rate", "mod_samplerate", "SampleRates" + Option "Interpolation", "mod_interp", "ModInterpolations" + Option "Volume ramping", "mod_volramp", "ModVolumeRamps" + StaticText " " + Option "Chip-o-matic", "mod_autochip", "OnOff" + // TODO if the menu system is ever rewritten: Provide a decent + // mechanism to edit the chip-o-matic settings like you can with + // the foo_dumb preferences in foobar2000. +} + +/*======================================= + * + * Video mode menu + * + *=======================================*/ + +OptionValue ForceRatios +{ + 0.0, "Off" + 3.0, "4:3" + 1.0, "16:9" + 2.0, "16:10" + 4.0, "5:4" +} +OptionValue Ratios +{ + 0.0, "4:3" + 1.0, "16:9" + 2.0, "16:10" + 3.0, "All" +} +OptionValue RatiosTFT +{ + 0.0, "4:3" + 4.0, "5:4" + 1.0, "16:9" + 2.0, "16:10" + 3.0, "All" +} + +OptionMenu VideoModeMenu +{ + Title "VIDEO MODE" + + Option "Fullscreen", "fullscreen", "YesNo" + Option "Aspect ratio", "menu_screenratios", "Ratios" + Option "Force aspect ratio", "vid_aspect", "ForceRatios" + Option "Enable 5:4 aspect ratio","vid_tft", "YesNo" + StaticText " " + ScreenResolution "res_0" + ScreenResolution "res_1" + ScreenResolution "res_2" + ScreenResolution "res_3" + ScreenResolution "res_4" + ScreenResolution "res_5" + ScreenResolution "res_6" + ScreenResolution "res_7" + ScreenResolution "res_8" + ScreenResolution "res_9" + StaticTextSwitchable "Press ENTER to set mode", "", "VMEnterText" + StaticText " " + StaticTextSwitchable "T to test mode for 5 seconds", "Please wait 5 seconds...", "VMTestText" + class VideoModeMenu +} + diff --git a/wadsrc/static/textures.txt b/wadsrc/static/textures.txt new file mode 100644 index 0000000000..19f27e84e7 --- /dev/null +++ b/wadsrc/static/textures.txt @@ -0,0 +1,72 @@ + +Graphic optional P_FWALK1, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK2, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK3, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK4, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK4", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK1, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK2, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK3, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK4, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK4", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK1, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK2, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK3, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK4, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK4", 24, 12 { useoffsets } +} diff --git a/wadsrc/static/xlat/defines.i b/wadsrc/static/xlat/defines.i index b72641d30e..60d582f708 100644 --- a/wadsrc/static/xlat/defines.i +++ b/wadsrc/static/xlat/defines.i @@ -145,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/zdoom.vcproj b/zdoom.vcproj index 0f82621abf..94a7a92dd8 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + @@ -688,18 +692,10 @@ RelativePath=".\src\m_joy.cpp" > - - - - @@ -712,10 +708,6 @@ RelativePath=".\src\md5.cpp" > - - @@ -804,6 +796,10 @@ RelativePath=".\src\p_floor.cpp" > + + @@ -900,6 +896,10 @@ RelativePath=".\src\p_udmf.cpp" > + + @@ -962,7 +962,7 @@ /> + + @@ -1626,6 +1630,16 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -1868,14 +1880,6 @@ Outputs="$(IntDir)/$(InputName).obj" /> - - - @@ -2065,14 +2069,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - @@ -2083,6 +2079,14 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + + + + + + + - - - + + + - - - @@ -5367,6 +5379,14 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> + + + @@ -5425,16 +5445,20 @@ RelativePath=".\src\sound\music_dumb.cpp" > + + + + + + @@ -5645,7 +5677,7 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +