diff --git a/FindFluidSynth.cmake b/FindFluidSynth.cmake new file mode 100644 index 000000000..7d5cb6a8e --- /dev/null +++ b/FindFluidSynth.cmake @@ -0,0 +1,23 @@ +# - Find fluidsynth +# Find the native fluidsynth includes and library +# +# FLUIDSYNTH_INCLUDE_DIR - where to find fluidsynth.h +# FLUIDSYNTH_LIBRARIES - List of libraries when using fluidsynth. +# FLUIDSYNTH_FOUND - True if fluidsynth found. + + +IF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + # Already in cache, be silent + SET(FluidSynth_FIND_QUIETLY TRUE) +ENDIF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + +FIND_PATH(FLUIDSYNTH_INCLUDE_DIR fluidsynth.h) + +FIND_LIBRARY(FLUIDSYNTH_LIBRARIES NAMES fluidsynth ) +MARK_AS_ADVANCED( FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR ) + +# handle the QUIETLY and REQUIRED arguments and set FLUIDSYNTH_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FluidSynth DEFAULT_MSG FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR) + diff --git a/specs/fmod_version.txt b/specs/fmod_version.txt index a65aec918..c285c50b9 100644 --- a/specs/fmod_version.txt +++ b/specs/fmod_version.txt @@ -1 +1,3 @@ This version of ZDoom must be compiled with any version between 4.22 and 4.28 inclusive. +Use of the latest 4.26 is recommended though due to technical issues with 4.28. + diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 5ceb573aa..fe6897d9f 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -163,6 +163,7 @@ Note: All fields default to false unless mentioned otherwise. 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 @@ -180,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. } @@ -268,6 +271,12 @@ Added 'playeruseback' line trigger flag. 1.11 07.08.2010 Added 'soundsequnce' sector property. +1.12 22.08.2010 +Added 'conversation' thing property. + +1.13 29.08.2010 +Added 'hidden' sector property. + =============================================================================== EOF =============================================================================== diff --git a/specs/usdf.txt b/specs/usdf.txt new file mode 100644 index 000000000..fc955f02d --- /dev/null +++ b/specs/usdf.txt @@ -0,0 +1,158 @@ +=============================================================================== +Universal Strife Dialog Format Specification v2.0 - 08/20/10 + +Written by Braden "Blzut3" Obrzut - admin@maniacsvault.net + +Defined with input from: + +CodeImp +Gez +Graf Zahl +Quasar +et al. + + Copyright (c) 2010 Braden Obrzut. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + +The grammar and syntax is similar to that of UDMF. A compliant UDMF parser +should be applyable to the USDF. However, it will need to be capable of +handling sub-blocks. Unknown sub-blocks should be skipped. + +======================================= +II. Implementation Semantics +======================================= + +------------------------------------ +II.A : Storage and Retrieval of Data +------------------------------------ + +This is the same as in UDMF. + +----------------------------------- +II.B : Storage Within Archive Files +----------------------------------- + +There are two options for the USDF lump placement. This can either be a part +of the UDMF lump list or standalone. If used stand alone the lump name +DIALOGXY is used corresponding with MAPXY. For UDMF the lump shall be called +"DIALOGUE". + +-------------------------------- +II.C : Implementation Dependence +-------------------------------- + +USDF also implements the namespace statement. This has all the same +requirements as UDMF. + +======================================= +III. Standardized Fields +======================================= + +The following are required for all USDF complient implementations. Like UDMF, +any unknown field/function should be ignored and not treated as an error. + +NOTE: "mobj" refers to Strife's conversationIDs and not doom editor numbers or + Hexen's spawnids. A valid mobj value is any positive integer greater + than or equal to 1. + +--------------------- +III.A : Conversations +--------------------- + +Conversations are groups of pages that can be assigned to a particular object. +Implementors should preserve the IDs to allow for dynamic reassignment through +scripting although this is not a requirement. + +conversation // Starts a dialog. +{ + actor = ; // mobj for this conversation's actor. If previously + // used, this will override the previous conversation. + + page // Starts a new page. Pages are automatically numbered starting at 0. + { + name = ; // Name that goes in the upper left hand corner + panel = ; // Name of lump to render as the background. + voice = ; // Narration sound lump. + dialog = ; // Dialog of the page. + drop = ; // mobj for the object to drop if the actor is + // killed. + link = ; // Page to jump to if all ifitem conditions are + // satisified. + + // jumps to the specified page if the player has the specified amount + // or more of item in their inventory. This can be repeated as many + // times as the author wants, all conditions must be met for the + // jump to occur. + ifitem + { + item = ; // mobj of item to check. + amount = ; // amount required to be in inventory. + } + + // Choices shall be automatically numbered. + choice + { + text = ; // Name of the choice. + + // The amount of an item needed to successfully pick this option. + // This can be repeated, but only the first will be shown (provided + // diaplaycost is true). All costs must be satisfied for success. + cost + { + item = ; // Item that is required for this option. + amount = ; // Minimum amount of the item needed. + } + + displaycost = ; // Weather the cost should be + // displayed with the option. + // If no cost is specified this should + // be ignored. + yesmessage = ; // Text to add to console when choice + // is accepted. + nomessage = ; // Text to add to console when choice + // is denied. + + log = ; // LOG entry to use on success. + giveitem = ; // Gives the specified item upon + // success. + // The following are the same as the special for linedefs in UDMF. + // They are executed on success. + special = ; + arg0 = ; + arg1 = ; + arg2 = ; + arg3 = ; + arg4 = ; + + nextpage = ; // Sets the next page. + closedialog = ; // Should the dialog be closed upon + // selecting this choice? + // Default: false + } + } +} + +------------------------------- +III.B : Including Other Dialogs +------------------------------- + +Unlike the original Strife dialog format. The lump "SCRIPT00" should not be +included automatically. Instead the user must specify this behavior by using +the include function, which takes the name of a lump to include. Include only +needs to be available in the global scope and for compatibility reasons, must +include the result of the script and not act like a preprocessor statement. + +include = ; + +=============================================================================== +EOF +=============================================================================== diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt new file mode 100644 index 000000000..217354292 --- /dev/null +++ b/specs/usdf_zdoom.txt @@ -0,0 +1,89 @@ +=============================================================================== +ZDoom Strife Dialog Format ZDoom v1.1 - 23.08.2010 +based on Universal Strife Dialog Format v2.0 + + Copyright (c) 2010 Christoph Oelckers. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + + No changes. + +======================================= +II. Implementation Semantics +======================================= + +No changes. + +======================================= +III. Changes to USDF spec +======================================= + +ZDoom Strife Dialogue format implements the USDF base specification as described with one important change: +To take advantage of named actor classes any field specifying an actor type +by a conversationID takes a class name instead. +The following fields are affected by this change: + +conversation +{ + actor = ; + + page + { + drop = ; + ifitem + { + item = ; + } + + choice + { + cost + { + item = ; + } + + giveitem = ; + } + } +} + +It should be noted that this change creates an incompatibility with USDF +so technically speaking the ZDoom format is no longer 'real' USDF. +To accomodate what is needed here this is unavoidable, unfortunately. +Any proper USDF implementation not supporting named actor classes should +either refuse loading dialogues with the 'ZDoom' namespace or if it does not +outright abort on incompatible namespaces fail with a type mismatch error on +one of the specified propeties. + +ZDoom-format dialogues need to start with the line: + +namespace = "ZDoom"; + + +--------------------- +III.A : Conversations +--------------------- + +This block only lists the newly added fields. Currently ZDoom only adds one +field to the specification: + +conversation // Starts a dialog. +{ + id = ; // assigns an ID to a dialogue. IDs are used to dynamically assign + // dialogues to actors. For 'Strife' namespace or binary dialogues + // the standard conversation ID ('actor' property) is used instead + // for this purpose but since 'ZDoom' namespace requires the actor + // to be a class name it needs a separate field for this. +} + +=============================================================================== +EOF +=============================================================================== diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7b4dfcbf..488c0c3b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,8 @@ if( CMAKE_COMPILER_IS_GNUCXX ) 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" ) @@ -31,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" @@ -353,6 +359,10 @@ if( NO_OPENAL ) add_definitions( -DNO_OPENAL=1 ) endif( NO_OPENAL ) +# Search for FluidSynth + +include( ../FindFluidSynth.cmake ) + # Search for NASM if( NOT NO_ASM ) @@ -416,9 +426,9 @@ if( NOT NO_ASM ) if( APPLE ) set( ASM_FLAGS -fmacho -DM_TARGET_MACHO ) else( APPLE ) - set( ASM_FLAGS -felf ) + set( ASM_FLAGS -felf -DM_TARGET_LINUX ) endif( APPLE ) - set( ASM_FLAGS "${ASM_FLAGS}" -DM_TARGET_LINUX -i${CMAKE_CURRENT_SOURCE_DIR}/ ) + set( ASM_FLAGS "${ASM_FLAGS}" -i${CMAKE_CURRENT_SOURCE_DIR}/ ) set( ASM_SOURCE_EXTENSION .asm ) endif( X64 ) else( UNIX ) @@ -487,7 +497,8 @@ endif( SSE_MATTERS ) if( CMAKE_COMPILER_IS_GNUCXX ) if( PROFILE ) - set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg" ) + 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" ) @@ -593,8 +604,16 @@ add_custom_target( revision_check ALL # Libraries ZDoom needs -set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" ) -include_directories( "${ZLIB_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" ) +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}" "${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 @@ -689,6 +708,11 @@ else( SSE_MATTERS ) set( X86_SOURCES ) endif( SSE_MATTERS ) +if( DYN_FLUIDSYNTH ) + add_definitions( -DHAVE_FLUIDSYNTH -DDYN_FLUIDSYNTH ) +elseif( FLUIDSYNTH_FOUND ) + add_definitions( -DHAVE_FLUIDSYNTH ) +endif( DYN_FLUIDSYNTH ) add_executable( zdoom WIN32 autostart.cpp @@ -742,19 +766,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 @@ -772,6 +794,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 @@ -796,6 +819,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 @@ -880,6 +904,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 @@ -903,13 +940,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 sound/oalsound.cpp diff --git a/src/actionspecials.h b/src/actionspecials.h index 7f855147e..c735c7a9d 100644 --- a/src/actionspecials.h +++ b/src/actionspecials.h @@ -77,7 +77,7 @@ 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) diff --git a/src/actor.h b/src/actor.h index 9ae11696b..2a8921b56 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 // @@ -722,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 @@ -766,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. @@ -892,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 9dccdf2c0..73a7f8c98 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -36,6 +36,9 @@ #include "r_translate.h" #include "d_event.h" #include "gi.h" +#include "r_bsp.h" +#include "p_setup.h" +#include "c_bind.h" #include "m_cheat.h" #include "i_system.h" @@ -183,6 +186,14 @@ 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); @@ -225,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 @@ -250,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*/) @@ -417,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) @@ -466,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 @@ -775,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); @@ -1158,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; } @@ -1290,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); @@ -1300,6 +1287,13 @@ void AM_changeWindowScale () AM_maxOutWindowScale(); } +CCMD(am_zoom) +{ + if (argv.argc() >= 2) + { + am_zoomdir = (float)atof(argv[1]); + } +} //============================================================================= // @@ -1334,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 @@ -1361,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 @@ -1622,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 + ); + } +} + //============================================================================= // // @@ -1856,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); } //============================================================================= @@ -2232,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, @@ -2291,6 +2390,9 @@ void AM_Drawer () } AM_activateNewScale(); + if (am_textured && hasglnodes && textured && !viewactive) + AM_drawSubsectors(); + if (grid) AM_drawGrid(GridColor); diff --git a/src/am_map.h b/src/am_map.h index 71740b9a7..20da6b96b 100644 --- a/src/am_map.h +++ b/src/am_map.h @@ -26,7 +26,7 @@ struct event_t; class FArchive; // Called by main loop. -bool AM_Responder (event_t* ev); +bool AM_Responder (event_t* ev, bool last); // Called by main loop. void AM_Ticker (void); diff --git a/src/asm_ia32/a.asm b/src/asm_ia32/a.asm index 07b00fe25..d68fba116 100644 --- a/src/asm_ia32/a.asm +++ b/src/asm_ia32/a.asm @@ -100,8 +100,8 @@ setupvlineasm: %endif %ifdef M_TARGET_MACHO -GLOBAL rtext_a_start -rtext_a_start: +GLOBAL _rtext_a_start +_rtext_a_start: %endif ;eax = xscale @@ -334,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 @@ -549,6 +550,6 @@ mvcase0: jmp beginmvlineasm4 align 16 %ifdef M_TARGET_MACHO -GLOBAL rtext_a_end -rtext_a_end: +GLOBAL _rtext_a_end +_rtext_a_end: %endif diff --git a/src/asm_ia32/tmap.asm b/src/asm_ia32/tmap.asm index 04b86d09d..cbcd9f4f1 100644 --- a/src/asm_ia32/tmap.asm +++ b/src/asm_ia32/tmap.asm @@ -292,8 +292,8 @@ aret: ret %endif %ifdef M_TARGET_MACHO -GLOBAL rtext_tmap_start -rtext_tmap_start: +GLOBAL _rtext_tmap_start +_rtext_tmap_start: %endif rtext_start: @@ -309,6 +309,8 @@ GLOBAL R_DrawSpanP_ASM ; edi: dest ; ebp: scratch ; esi: count +; [esp]: xstep +; [esp+4]: ystep align 16 @@ -324,6 +326,7 @@ R_DrawSpanP_ASM: push edi push ebp push esi + sub esp, 8 mov edi,ecx add edi,[dc_destorg] @@ -335,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 @@ -355,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 @@ -367,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 @@ -392,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 @@ -406,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 @@ -420,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 @@ -448,6 +452,8 @@ GLOBAL R_DrawSpanMaskedP_ASM ; edi: dest ; ebp: scratch ; esi: count +; [esp]: xstep +; [esp+4]: ystep align 16 @@ -463,6 +469,7 @@ R_DrawSpanMaskedP_ASM: push edi push ebp push esi + sub esp,8 mov edi,ecx add edi,[dc_destorg] @@ -474,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 @@ -494,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 @@ -508,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 @@ -537,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 @@ -553,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 @@ -571,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 @@ -580,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 @@ -1748,8 +1756,8 @@ ac4nil: pop edi rtext_end: %ifdef M_TARGET_MACHO -GLOBAL rtext_tmap_end -rtext_tmap_end: +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 63eee0044..e1f166878 100644 --- a/src/asm_ia32/tmap2.asm +++ b/src/asm_ia32/tmap2.asm @@ -220,8 +220,8 @@ SetTiltedSpanSize: SECTION .rtext progbits alloc exec write align=64 %else SECTION .text align=64 -GLOBAL rtext_tmap2_start -rtext_tmap2_start: +GLOBAL _rtext_tmap2_start +_rtext_tmap2_start: %endif rtext_start: @@ -635,6 +635,6 @@ fetch10 mov al,[ebp+esi+SPACEFILLER4] rtext_end: %ifdef M_TARGET_MACHO -GLOBAL rtext_tmap2_end -rtext_tmap2_end: +GLOBAL _rtext_tmap2_end +_rtext_tmap2_end: %endif diff --git a/src/asm_ia32/tmap3.asm b/src/asm_ia32/tmap3.asm index 3791b4ff0..3161ff368 100644 --- a/src/asm_ia32/tmap3.asm +++ b/src/asm_ia32/tmap3.asm @@ -82,8 +82,8 @@ setupvlinetallasm: %ifdef M_TARGET_MACHO SECTION .text align=64 -GLOBAL rtext_tmap3_start -rtext_tmap3_start: +GLOBAL _rtext_tmap3_start +_rtext_tmap3_start: %else SECTION .rtext progbits alloc exec write align=64 %endif @@ -339,6 +339,6 @@ shift12: shr ecx,16 ret %ifdef M_TARGET_MACHO -GLOBAL rtext_tmap3_end -rtext_tmap3_end: +GLOBAL _rtext_tmap3_end +_rtext_tmap3_end: %endif diff --git a/src/basicinlines.h b/src/basicinlines.h index fa6ce822b..80f861c22 100644 --- a/src/basicinlines.h +++ b/src/basicinlines.h @@ -11,8 +11,8 @@ #pragma once #endif -#ifndef _MSC_VER -#define __forceinline inline +#if defined(__GNUC__) && !defined(__forceinline) +#define __forceinline __inline__ __attribute__((always_inline)) #endif static __forceinline SDWORD Scale (SDWORD a, SDWORD b, SDWORD c) diff --git a/src/c_bind.cpp b/src/c_bind.cpp index d96b5a742..cceb8bd35 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 2b46118cf..4c9edb2bb 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 6d21a7922..f3cb40958 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 d4e01128d..7b5387635 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 2fbafd2a9..413b8d64c 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -119,7 +119,9 @@ FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, Button_Forward, Button_Right, Button_Left, Button_MoveDown, Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch, Button_Zoom, Button_Reload, - Button_User1, Button_User2, Button_User3, Button_User4; + Button_User1, Button_User2, Button_User3, Button_User4, + Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp, + Button_AM_ZoomIn, Button_AM_ZoomOut; bool ParsingKeyConf; @@ -131,13 +133,16 @@ bool ParsingKeyConf; FActionMap ActionMaps[] = { + { 0x0d52d67b, &Button_AM_PanLeft, "am_panleft"}, { 0x125f5226, &Button_User2, "user2" }, { 0x1eefa611, &Button_Jump, "jump" }, { 0x201f1c55, &Button_Right, "right" }, { 0x20ccc4d5, &Button_Zoom, "zoom" }, { 0x23a99cd7, &Button_Back, "back" }, + { 0x41df90c2, &Button_AM_ZoomIn, "am_zoomin"}, { 0x426b69e7, &Button_Reload, "reload" }, { 0x4463f43a, &Button_LookDown, "lookdown" }, + { 0x51f7a334, &Button_AM_ZoomOut, "am_zoomout"}, { 0x534c30ee, &Button_User4, "user4" }, { 0x5622bf42, &Button_Attack, "attack" }, { 0x577712d0, &Button_User1, "user1" }, @@ -147,12 +152,15 @@ FActionMap ActionMaps[] = { 0x676885b8, &Button_AltAttack, "altattack" }, { 0x6fa41b84, &Button_MoveLeft, "moveleft" }, { 0x818f08e6, &Button_MoveRight, "moveright" }, + { 0x8197097b, &Button_AM_PanRight, "am_panright"}, + { 0x8d89955e, &Button_AM_PanUp, "am_panup"} , { 0xa2b62d8b, &Button_Mlook, "mlook" }, { 0xab2c3e71, &Button_Crouch, "crouch" }, { 0xb000b483, &Button_Left, "left" }, { 0xb62b1e49, &Button_LookUp, "lookup" }, { 0xb6f8fe92, &Button_User3, "user3" }, { 0xb7e6a54b, &Button_Strafe, "strafe" }, + { 0xce301c81, &Button_AM_PanDown, "am_pandown"}, { 0xd5897c73, &Button_ShowScores, "showscores" }, { 0xe0ccb317, &Button_Speed, "speed" }, { 0xe0cfc260, &Button_Use, "use" }, @@ -160,6 +168,7 @@ FActionMap ActionMaps[] = }; #define NUM_ACTIONS countof(ActionMaps) + // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const char *KeyConfCommands[] = diff --git a/src/c_dispatch.h b/src/c_dispatch.h index a19352d32..d93709c90 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 dd32cdb55..36a900709 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -2,10 +2,14 @@ #ifdef _WIN32 #include +#include #else #include #include #include +#if !defined(__sun) +#include +#endif #endif #include "doomtype.h" #include "cmdlib.h" @@ -90,6 +94,23 @@ char *copystring (const char *s) return b; } +//============================================================================ +// +// ncopystring +// +// If the string has no content, returns NULL. Otherwise, returns a copy. +// +//============================================================================ + +char *ncopystring (const char *string) +{ + if (string == NULL || string[0] == 0) + { + return NULL; + } + return copystring (string); +} + //========================================================================== // // ReplaceString @@ -868,3 +889,153 @@ FString NicePath(const char *path) return where; #endif } + + +#ifdef _WIN32 + +//========================================================================== +// +// ScanDirectory +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + struct _finddata_t fileinfo; + intptr_t handle; + FString dirmatch; + + dirmatch << dirpath << "*"; + + if ((handle = _findfirst(dirmatch, &fileinfo)) == -1) + { + I_Error("Could not scan '%s': %s\n", dirpath, strerror(errno)); + } + else + { + do + { + if (fileinfo.attrib & _A_HIDDEN) + { + // Skip hidden files and directories. (Prevents SVN bookkeeping + // info from being included.) + continue; + } + + if (fileinfo.attrib & _A_SUBDIR) + { + if (fileinfo.name[0] == '.' && + (fileinfo.name[1] == '\0' || + (fileinfo.name[1] == '.' && fileinfo.name[2] == '\0'))) + { + // Do not record . and .. directories. + continue; + } + + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << fileinfo.name; + fl->isDirectory = true; + FString newdir = fl->Filename; + newdir << "/"; + ScanDirectory(list, newdir); + } + else + { + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << fileinfo.name; + fl->isDirectory = false; + } + } + while (_findnext(handle, &fileinfo) == 0); + _findclose(handle); + } +} + +#elif defined(__sun) || defined(linux) + +//========================================================================== +// +// ScanDirectory +// Solaris version +// +// Given NULL-terminated array of directory paths, create trees for them. +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + DIR *directory = opendir(dirpath); + if(directory == NULL) + return; + + struct dirent *file; + while((file = readdir(directory)) != NULL) + { + if(file->d_name[0] == '.') //File is hidden or ./.. directory so ignore it. + continue; + + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename << dirpath << file->d_name; + + struct stat fileStat; + stat(fl->Filename, &fileStat); + fl->isDirectory = S_ISDIR(fileStat.st_mode); + + if(fl->isDirectory) + { + FString newdir = fl->Filename; + newdir += "/"; + ScanDirectory(list, newdir); + continue; + } + } + + closedir(directory); +} + +#else + +//========================================================================== +// +// ScanDirectory +// 4.4BSD version +// +//========================================================================== + +void ScanDirectory(TArray &list, const char *dirpath) +{ + const char **argv[] = {dirpath, NULL }; + FTS *fts; + FTSENT *ent; + + fts = fts_open(argv, FTS_LOGICAL, NULL); + if (fts == NULL) + { + I_Error("Failed to start directory traversal: %s\n", strerror(errno)); + return; + } + while ((ent = fts_read(fts)) != NULL) + { + if (ent->fts_info == FTS_D && ent->fts_name[0] == '.') + { + // Skip hidden directories. (Prevents SVN bookkeeping + // info from being included.) + fts_set(fts, ent, FTS_SKIP); + } + if (ent->fts_info == FTS_D && ent->fts_level == 0) + { + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename = ent->fts_path; + fl->isDirectory = true; + } + if (ent->fts_info == FTS_F) + { + // We're only interested in remembering files. + FFileList *fl = &list[list.Reserve(1)]; + fl->Filename = ent->fts_path; + fl->isDirectory = false; + } + } + fts_close(fts); +} +#endif diff --git a/src/cmdlib.h b/src/cmdlib.h index fe41204ec..7c29644d5 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -36,6 +36,7 @@ int ParseNum (const char *str); bool IsNum (const char *str); // [RH] added char *copystring(const char *s); +char *ncopystring(const char *s); void ReplaceString (char **ptr, const char *str); bool CheckWildcards (const char *pattern, const char *text); @@ -53,4 +54,12 @@ void CreatePath(const char * fn); FString ExpandEnvVars(const char *searchpathstring); FString NicePath(const char *path); +struct FFileList +{ + FString Filename; + bool isDirectory; +}; + +void ScanDirectory(TArray &list, const char *dirpath); + #endif diff --git a/src/compatibility.cpp b/src/compatibility.cpp index e091a7f1e..ab1c98ca7 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 } }; @@ -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_gui.h b/src/d_gui.h index 5e9854f7c..b88041771 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 e5939e1a7..499d7cbd7 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 520127b9b..9dc18f473 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -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 997919963..e08899719 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 28b240c10..2d58e00ee 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/dobjgc.cpp b/src/dobjgc.cpp index 4d7589e9b..4314d0488 100644 --- a/src/dobjgc.cpp +++ b/src/dobjgc.cpp @@ -72,6 +72,7 @@ #include "doomstat.h" #include "m_argv.h" #include "po_man.h" +#include "menu/menu.h" // MACROS ------------------------------------------------------------------ @@ -298,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 cdac25902..f232d81f2 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -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 dd35e7e47..e01929901 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 859246261..9c22b22a6 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 3aa527523..1108e770f 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/f_finale.cpp b/src/f_finale.cpp index c37d1f459..a851fdc31 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 9af7d25ba..533b3920e 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 5705b938a..332bddb1f 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -132,6 +132,8 @@ long FileReader::Seek (long offset, int origin) long FileReader::Read (void *buffer, long len) { + assert(len >= 0); + if (len <= 0) return 0; if (FilePos + len > StartPos + Length) { len = Length - FilePos + StartPos; diff --git a/src/g_game.cpp b/src/g_game.cpp index d68ead9ef..caf43cd6f 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_level.cpp b/src/g_level.cpp index b14959efe..10ed1f83e 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 f316491d5..cf7fd2d48 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); @@ -603,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 1eb6363fd..6b3faa78a 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 a614d3b41..eb5444147 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 3697bcba8..2243d48a4 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 6bed525d8..1475f86d2 100644 --- a/src/g_shared/a_armor.cpp +++ b/src/g_shared/a_armor.cpp @@ -211,16 +211,17 @@ void ABasicArmorPickup::Serialize (FArchive &arc) AInventory *ABasicArmorPickup::CreateCopy (AActor *other) { ABasicArmorPickup *copy = static_cast (Super::CreateCopy (other)); - copy->SavePercent = SavePercent; - copy->SaveAmount = SaveAmount; - copy->MaxAbsorb = MaxAbsorb; - copy->MaxFullAbsorb = MaxFullAbsorb; 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; } @@ -291,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; @@ -299,10 +306,6 @@ AInventory *ABasicArmorBonus::CreateCopy (AActor *other) copy->MaxAbsorb = MaxAbsorb; copy->MaxFullAbsorb = MaxFullAbsorb; - if (!(ItemFlags & IF_IGNORESKILL)) - { - SaveAmount = FixedMul(SaveAmount, G_SkillProperty(SKILLP_ArmorFactor)); - } return copy; } diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index 3bd2903ee..d45a9f2a8 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_pickups.cpp b/src/g_shared/a_pickups.cpp index 194618933..30dcd482c 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 67bd4b84a..4c1b5b964 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/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 6895ce321..eed9d86c7 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 8339a3592..e66d23f79 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -614,6 +614,19 @@ class CommandDrawString : public SBarInfoCommand 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; @@ -2071,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()); } @@ -2090,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)< 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)) diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index ef48d211a..beb459cda 100644 --- a/src/g_shared/shared_sbar.cpp +++ b/src/g_shared/shared_sbar.cpp @@ -1504,7 +1504,9 @@ 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); } if (CPlayer->mo->DamageFade.a != 0) diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 936d4e7f7..acf2bbad1 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -399,29 +399,38 @@ void FGameConfigFile::DoGameSetup (const char *gamename) ReadCVars (0); } - strncpy (subsection, "Bindings", sublen); - if (!SetSection (section)) - { // Config has no bindings for the given game - if (!bMigrating) - { - C_SetDefaultBindings (); - } - } - else + if (!bMigrating) { - C_UnbindAll (); + C_SetDefaultBindings (); + } + + strncpy (subsection, "Bindings", sublen); + if (SetSection (section)) + { + Bindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, false); + Bindings.DoBind (key, value); } } strncpy (subsection, "DoubleBindings", sublen); if (SetSection (section)) { + DoubleBindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, true); + DoubleBindings.DoBind (key, value); + } + } + + strncpy (subsection, "AutomapBindings", sublen); + if (SetSection (section)) + { + AutomapBindings.UnbindAll(); + while (NextInSection (key, value)) + { + AutomapBindings.DoBind (key, value); } } @@ -512,11 +521,15 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) strcpy (subsection, "Bindings"); SetSection (section, true); - C_ArchiveBindings (this, false); + Bindings.ArchiveBindings (this); strncpy (subsection, "DoubleBindings", sublen); SetSection (section, true); - C_ArchiveBindings (this, true); + DoubleBindings.ArchiveBindings (this); + + strncpy (subsection, "AutomapBindings", sublen); + SetSection (section, true); + AutomapBindings.ArchiveBindings (this); } void FGameConfigFile::ArchiveGlobalData () diff --git a/src/gi.cpp b/src/gi.cpp index e0813479a..190d2b031 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 a87098555..ef9777553 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 000000000..3da17fb38 --- /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_cheat.cpp b/src/m_cheat.cpp index 0a7928a91..9da1b1ff5 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 2c939f679..00537dee8 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 10ba3a336..000000000 --- a/src/m_menu.cpp +++ /dev/null @@ -1,4151 +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 M_ChangeSwitchPickup (int choice); -static void PickPlayerClass (); - -// EXTERNAL DATA DECLARATIONS ---------------------------------------------- - -EXTERN_CVAR (String, playerclass) -EXTERN_CVAR (String, name) -EXTERN_CVAR (Int, team) -EXTERN_CVAR(Bool, neverswitchonpickup) - -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}, - { 2,0,'p',NULL,M_ChangeSwitchPickup, 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); - - // Draw Switch on Pickup setting - x = SmallFont->StringWidth ("Switch on Pickup") + 8 + PSetupDef.x; - screen->DrawText (SmallFont, label, PSetupDef.x, PSetupDef.y + LINEHEIGHT*9+yo, "Switch on Pickup", DTA_Clean, true, TAG_DONE); - screen->DrawText (SmallFont, value, x, PSetupDef.y + LINEHEIGHT*9+yo, - neverswitchonpickup == false ? "Yes" : "No", - DTA_Clean, true, TAG_DONE); -} - -// A 32x32 cloud rendered with Photoshop, plus some other filters -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_ChangeSwitchPickup (int choice) -{ - if (!choice) - neverswitchonpickup = (neverswitchonpickup == 1) ? 0 : 1; - else - neverswitchonpickup = (neverswitchonpickup == 0) ? 1 : 0; -} - -static void M_EditPlayerName (int choice) -{ - // we are going to be intercepting all chars - 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 4f45cbed2..000000000 --- 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 324095d5c..d070fdd52 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); // @@ -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 1a7b17a96..23905d765 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 eed3e9d85..000000000 --- a/src/m_options.cpp +++ /dev/null @@ -1,3987 +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 (Bool, am_showkeys) -EXTERN_CVAR (Int, vid_aspect) - -static void CalcIndent (menu_t *menu); - -void M_ChangeMessages (); -void M_SizeDisplay (int diff); - -int M_StringHeight (char *string); - -int BuildALDeviceList(valueenum_t **menulist); - -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} }, - { discrete, "Show keys (cheat)", {&am_showkeys}, {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_thingcolor_citem) -EXTERN_CVAR (Color, am_ovthingcolor_friend) -EXTERN_CVAR (Color, am_ovthingcolor_monster) -EXTERN_CVAR (Color, am_ovthingcolor_item) -EXTERN_CVAR (Color, am_ovthingcolor_citem) - -static menuitem_t MapColorsItems[] = { - { rsafemore, "Restore default custom colors", {NULL}, {0}, {0}, {0}, {(value_t*)DefaultCustomColors} }, - { 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} }, - { colorpicker, "Count Items (for cheat)", {&am_thingcolor_citem}, {0}, {0}, {0}, {0} }, - { redtext, " ", {NULL}, {0}, {0}, {0}, {0} }, - { colorpicker, "You (overlay)", {&am_ovyourcolor}, {0}, {0}, {0}, {0} }, - { colorpicker, "1-sided walls (overlay)", {&am_ovwallcolor}, {0}, {0}, {0}, {0} }, - { 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} }, - { colorpicker, "Count Items (overlay) (for cheat)", {&am_ovthingcolor_citem}, {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 Romero kills all his spawns", {&dmflags2}, {0}, {0}, {0}, {(value_t *)DF2_KILLBOSSMONST} }, - - { redtext, " ", {NULL}, {0}, {0}, {0}, {NULL} }, - { whitetext,"Deathmatch Settings", {NULL}, {0}, {0}, {0}, {NULL} }, - { 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), - 222, - 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 (Float, snd_waterabsorption) -EXTERN_CVAR (Int, snd_mididevice) -EXTERN_CVAR (String, snd_backend) -EXTERN_CVAR (String, snd_aldevice) -EXTERN_CVAR (Bool, snd_efx) - -static void MakeSoundChanges (); -static void FMODSoundOptions (); -static void OpenALSoundOptions (); -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 FMODOutputs[] = -{ - { "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 Backends[] = -{ -#ifndef NO_FMOD - { "fmod", "FMOD" }, -#endif -#ifndef NO_OPENAL - { "openal", "OpenAL" }, -#endif - { "null", "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 FMODSoundItems[] = -{ - { slider, "Underwater cutoff", {&snd_waterlp}, {0.0}, {2000.0},{50.0}, {NULL} }, - { ediscrete,"Output system", {&snd_output}, {countof(FMODOutputs)}, {0.0}, {0.0}, {(value_t *)FMODOutputs} }, - { 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} }, - { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, - { discrete, "Buffer count", {&snd_buffercount}, {12.0},{0.0}, {0.0}, {BufferCounts} }, -}; - -static menu_t FMODSoundMenu = -{ - "FMOD SOUND OPTIONS", - 0, - countof(FMODSoundItems), - 0, - FMODSoundItems, -}; - - -static valueenum_t *ALOutputs = NULL; - -static menuitem_t OpenALSoundItems[] = -{ - { ediscrete, "Playback device", {&snd_aldevice}, {0.0}, {0.0},{0.0}, {(value_t *)ALOutputs} }, - { discrete, "Enable EFX", {&snd_efx}, {2.0}, {0.0},{0.0}, {(value_t *)YesNo} }, - { redtext, " ", {NULL}, {0.0}, {0.0},{0.0}, {NULL} }, - { whitetext, "Requires EFX", {NULL}, {0.0}, {0.0},{0.0}, {NULL} }, - { slider, "Underwater absorption",{&snd_waterabsorption}, {0.0}, {10.0},{0.5},{NULL} }, -}; - -static menu_t OpenALSoundMenu = -{ - "OPENAL SOUND OPTIONS", - 0, - countof(OpenALSoundItems), - 0, - OpenALSoundItems, -}; - -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} }, - { discrete, "Randomize pitches", {&snd_pitched}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Sound channels", {&snd_channels}, {8.0}, {256.0}, {8.0}, {NULL} }, - { ediscrete,"Sound backend", {&snd_backend}, {countof(Backends)}, {0.0}, {0.0}, {(value_t *)Backends} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, -#ifndef NO_FMOD - { more, "FMOD sound options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)FMODSoundOptions} }, -#endif -#ifndef NO_OPENAL - { more, "OpenAL sound options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)OpenALSoundOptions} }, -#endif - { 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} }, - { 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} }, - { 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, "Sound System" }, - { 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 FMODSoundOptions () -{ - M_SwitchMenu (&FMODSoundMenu); -} - -CCMD (menu_fmodsound) -{ -#ifndef NO_FMOD - M_StartControlPanel (true); - OptionsActive = true; - FMODSoundOptions (); -#endif -} - -static void OpenALSoundOptions () -{ - OpenALSoundItems[0].b.numvalues = BuildALDeviceList (&ALOutputs); - OpenALSoundItems[0].e.enumvalues = ALOutputs; - M_SwitchMenu (&OpenALSoundMenu); -} - -CCMD (menu_openalsound) -{ -#ifndef NO_OPENAL - M_StartControlPanel (true); - OptionsActive = true; - OpenALSoundOptions (); -#endif -} - -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 () -{ - if (ALOutputs) - { - for (size_t i = 0;ALOutputs[i].value;i++) - delete[] const_cast(ALOutputs[i].value); - delete[] ALOutputs; - ALOutputs = NULL; - } - - // 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 000000000..d803022bf --- /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 000000000..a29df9596 --- /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 000000000..42164c16b --- /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 000000000..130dd0bd5 --- /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 000000000..f74ac4794 --- /dev/null +++ b/src/menu/menu.cpp @@ -0,0 +1,955 @@ +/* +** 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) +{ + bool res = false; + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_LButtonDown) + { + res = MouseEventBack(MOUSE_Click, ev->data1, ev->data2); + // make the menu's mouse handler believe that the current coordinate is outside the valid range + if (res) ev->data2 = -1; + res |= MouseEvent(MOUSE_Click, ev->data1, ev->data2); + if (res) + { + SetCapture(); + } + + } + else if (ev->subtype == EV_GUI_MouseMove) + { + BackbuttonTime = BACKBUTTON_TIME; + if (mMouseCapture || m_use_mouse == 1) + { + res = MouseEventBack(MOUSE_Move, ev->data1, ev->data2); + if (res) ev->data2 = -1; + res |= MouseEvent(MOUSE_Move, ev->data1, ev->data2); + } + } + else if (ev->subtype == EV_GUI_LButtonUp) + { + if (mMouseCapture) + { + ReleaseCapture(); + res = MouseEventBack(MOUSE_Release, ev->data1, ev->data2); + if (res) ev->data2 = -1; + res |= 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 mBackbuttonSelected; + } + } + 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 000000000..b1073a041 --- /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 000000000..033ad599b --- /dev/null +++ b/src/menu/menudef.cpp @@ -0,0 +1,1393 @@ +/* +** 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 = firstitem + MIN(2u, AllEpisodes.Size()-1); + } + else + { + ld->mAutoselect = -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 000000000..0f35b7e1f --- /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 000000000..bde298894 --- /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 000000000..a64f77431 --- /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 000000000..07a72fb53 --- /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 000000000..f9871e1ca --- /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 000000000..b25b86f1a --- /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 000000000..388dca716 --- /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 000000000..0d1590463 --- /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 63e59e755..2157b4a89 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 6dc75dc03..f1d846927 100644 --- a/src/mus2midi.h +++ b/src/mus2midi.h @@ -75,7 +75,4 @@ typedef struct // WORD UsedInstruments[NumInstruments]; } MUSHeader; -bool ProduceMIDI (const BYTE *musBuf, 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 94e0c97bd..9f7e5a87d 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) @@ -441,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.h b/src/nodebuild.h index d1ed2cb15..da82e26a2 100644 --- a/src/nodebuild.h +++ b/src/nodebuild.h @@ -111,6 +111,12 @@ class FNodeBuilder bool Forward; }; + struct glseg_t : public seg_t + { + DWORD Partner; + }; + + // Like a blockmap, but for vertices instead of lines class IVertexMap { @@ -200,7 +206,7 @@ public: ~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); @@ -282,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); diff --git a/src/nodebuild_extract.cpp b/src/nodebuild_extract.cpp index d8675aa6e..c4546bb5b 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,7 +99,7 @@ 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) { @@ -110,13 +110,19 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, 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; } } } @@ -125,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]; @@ -138,8 +145,6 @@ 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) @@ -194,19 +199,17 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp) if (GLNodes) { + TArray glsegs; for (i = 0; i < Subsectors.Size(); ++i) { - DWORD numsegs = CloseSubsector (bsp->Segs, i, &bsp->Verts[0]); + DWORD numsegs = CloseSubsector (glsegs, i, &bsp->Verts[0]); bsp->Subsectors[i].numlines = numsegs; bsp->Subsectors[i].firstline = &bsp->Segs[bsp->Segs.Size() - numsegs]; } - - for (i = 0; i < Segs.Size(); ++i) + bsp->Segs.Resize(glsegs.Size()); + for (i = 0; i < glsegs.Size(); ++i) { - if (bsp->Segs[i].PartnerSeg != NULL) - { - bsp->Segs[i].PartnerSeg = &bsp->Segs[Segs[(unsigned int)(size_t)bsp->Segs[i].PartnerSeg-1].storedseg]; - } + bsp->Segs[i] = *(seg_t *)&glsegs[i]; } } else @@ -234,8 +237,6 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp) out->linedef = NULL; out->sidedef = NULL; } - out->PartnerSeg = NULL; - out->bPolySeg = false; } for (i = 0; i < bsp->Subsectors.Size(); ++i) { @@ -244,7 +245,7 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp) } } -int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts) +int FNodeBuilder::CloseSubsector (TArray &segs, int subsector, vertex_t *outVerts) { FPrivSeg *seg, *prev; angle_t prevAngle; @@ -406,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; @@ -473,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; @@ -491,14 +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.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; @@ -506,7 +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.Partner = DWORD_MAX; segs.Push (newseg); } diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index 9413ae4bb..ade5b5c12 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -74,25 +74,9 @@ OPLMIDIDevice::OPLMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - FWadLump data = Wads.OpenLumpName("GENMIDI"); OPLloadBank(data); -} - -//========================================================================== -// -// OPLMIDIDevice Destructor -// -//========================================================================== - -OPLMIDIDevice::~OPLMIDIDevice() -{ - Close(); + SampleRate = (int)OPL_SAMPLE_RATE; } //========================================================================== @@ -109,25 +93,14 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo { return 1; } - - Stream = GSnd->CreateStream(FillStream, int(OPL_SAMPLE_RATE / 14) * 4, - SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this); - if (Stream == NULL) + int ret = OpenStream(14, SoundStream::Mono, callback, userdata); + if (ret == 0) { - return 2; + OPLstopMusic(); + OPLplayMusic(100); + DEBUGOUT("========= New song started ==========\n", 0, 0, 0); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - - OPLstopMusic(); - OPLplayMusic(100); - DEBUGOUT("========= New song started ==========\n", 0, 0, 0); - - return 0; + return ret; } //========================================================================== @@ -138,24 +111,8 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo void OPLMIDIDevice::Close() { - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } + SoftSynthMIDIDevice::Close(); io->OPLdeinit(); - Started = false; -} - -//========================================================================== -// -// OPLMIDIDevice :: IsOpen -// -//========================================================================== - -bool OPLMIDIDevice::IsOpen() const -{ - return Stream != NULL; } //========================================================================== @@ -169,34 +126,6 @@ int OPLMIDIDevice::GetTechnology() const return MOD_FMSYNTH; } -//========================================================================== -// -// OPLMIDIDevice :: SetTempo -// -//========================================================================== - -int OPLMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - DEBUGOUT("Tempo changed to %.0f, %.2f samples/tick\n", Tempo, SamplesPerTick, 0); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int OPLMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - DEBUGOUT("Division changed to %.0f, %.2f samples/tick\n", Division, SamplesPerTick, 0); - return 0; -} - //========================================================================== // // OPLMIDIDevice :: CalcTickRate @@ -208,219 +137,22 @@ int OPLMIDIDevice::SetTimeDiv(int timediv) void OPLMIDIDevice::CalcTickRate() { - SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division; - io->SetClockRate(SamplesPerTick); -} - -//========================================================================== -// -// OPLMIDIDevice :: Resume -// -//========================================================================== - -int OPLMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: Stop -// -//========================================================================== - -void OPLMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int OPLMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - ChipAccess.Enter(); - StreamOut(header); - ChipAccess.Leave(); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int OPLMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: PrepareHeader -// -//========================================================================== - -int OPLMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int OPLMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: FakeVolume -// -// Since the OPL output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool OPLMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool OPLMIDIDevice::NeedThreadedCallback() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: Pause -// -//========================================================================== - -bool OPLMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + SoftSynthMIDIDevice::CalcTickRate(); + io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick); } //========================================================================== // // OPLMIDIDevice :: PlayTick // -// event[0] = delta time -// event[1] = unused -// event[2] = event +// We derive from two base classes that both define PlayTick(), so we need +// to be unambiguous about which one to use. // //========================================================================== int OPLMIDIDevice::PlayTick() { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { // Should I handle master volume changes? - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - HandleEvent(status, parm1, parm2); - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + return SoftSynthMIDIDevice::PlayTick(); } //========================================================================== @@ -508,14 +240,35 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2) //========================================================================== // -// OPLMIDIDevice :: FillStream static +// OPLMIDIDevice :: HandleLongEvent // //========================================================================== -bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void OPLMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - OPLMIDIDevice *device = (OPLMIDIDevice *)userdata; - return device->ServiceStream(buff, len); +} + +//========================================================================== +// +// OPLMIDIDevice :: ComputeOutput +// +// We override ServiceStream, so this function is never actually called. +// +//========================================================================== + +void OPLMIDIDevice::ComputeOutput(float *buffer, int len) +{ +} + +//========================================================================== +// +// OPLMIDIDevice :: ServiceStream +// +//========================================================================== + +bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes) +{ + return OPLmusicBlock::ServiceStream(buff, numbytes); } //========================================================================== diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 4aeb0595f..556592991 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -2136,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; @@ -2759,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); @@ -2820,6 +2821,7 @@ int DLevelScript::CheckActorProperty (int tid, int property, int 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: @@ -5097,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]; diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 8e32ee8ab..a2c287a42 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 13658e0f0..d4fa60009 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_enemy.cpp b/src/p_enemy.cpp index 53d87e3dc..1cf28aa5d 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 @@ -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); + } + } } } diff --git a/src/p_enemy.h b/src/p_enemy.h index 1d80e08e3..f996a0c52 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_glnodes.cpp b/src/p_glnodes.cpp new file mode 100644 index 000000000..382f026b2 --- /dev/null +++ b/src/p_glnodes.cpp @@ -0,0 +1,1553 @@ +/* +** gl_nodes.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2005-2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include +#ifdef _MSC_VER +#include // for alloca() +#endif + +#ifndef _WIN32 +#include + +#else +#include + +#define rmdir _rmdir + +// TODO, maybe: stop using DWORD so I don't need to worry about conflicting +// with Windows' typedef. Then I could just include the header file instead +// of declaring everything here. +#define MAX_PATH 260 +#define CSIDL_LOCAL_APPDATA 0x001c +extern "C" __declspec(dllimport) long __stdcall SHGetFolderPathA(void *hwnd, int csidl, void *hToken, unsigned long dwFlags, char *pszPath); + +#endif + +#ifdef __APPLE__ +#include +#endif + +#include "templates.h" +#include "m_alloc.h" +#include "m_argv.h" +#include "c_dispatch.h" +#include "m_swap.h" +#include "g_game.h" +#include "i_system.h" +#include "w_wad.h" +#include "doomdef.h" +#include "p_local.h" +#include "nodebuild.h" +#include "doomstat.h" +#include "vectors.h" +#include "stats.h" +#include "doomerrors.h" +#include "p_setup.h" +#include "x86.h" +#include "version.h" +#include "md5.h" + +void P_GetPolySpots (MapData * lump, TArray &spots, TArray &anchors); + +CVAR(Bool, gl_cachenodes, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Float, gl_cachetime, 0.6f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +void P_LoadZNodes (FileReader &dalump, DWORD id); +static bool CheckCachedNodes(MapData *map); +static void CreateCachedNodes(MapData *map); + + +// fixed 32 bit gl_vert format v2.0+ (glBsp 1.91) +struct mapglvertex_t +{ + fixed_t x,y; +}; + +struct gl3_mapsubsector_t +{ + SDWORD numsegs; + SDWORD firstseg; // Index of first one; segs are stored sequentially. +}; + +struct glseg_t +{ + WORD v1; // start vertex (16 bit) + WORD v2; // end vertex (16 bit) + WORD linedef; // linedef, or -1 for minisegs + WORD side; // side on linedef: 0 for right, 1 for left + WORD partner; // corresponding partner seg, or 0xffff on one-sided walls +}; + +struct glseg3_t +{ + SDWORD v1; + SDWORD v2; + WORD linedef; + WORD side; + SDWORD partner; +}; + +struct gl5_mapnode_t +{ + SWORD x,y,dx,dy; // partition line + SWORD bbox[2][4]; // bounding box for each child + // If NF_SUBSECTOR is or'ed in, it's a subsector, + // else it's a node of another subtree. + DWORD children[2]; +}; + + + +//========================================================================== +// +// Collect all sidedefs which are not entirely covered by segs +// Old ZDBSPs could create such maps. If such a BSP is discovered +// a node rebuild must be done to ensure proper rendering +// +//========================================================================== + +static int CheckForMissingSegs() +{ + float *added_seglen = new float[numsides]; + int missing = 0; + + memset(added_seglen, 0, sizeof(float)*numsides); + for(int i=0;isidedef!=NULL) + { + // check all the segs and calculate the length they occupy on their sidedef + TVector2 vec1(seg->v2->x - seg->v1->x, seg->v2->y - seg->v1->y); + added_seglen[seg->sidedef - sides] += float(vec1.Length()); + } + } + + for(int i=0;ilinedef; + + TVector2 lvec(line->dx, line->dy); + float linelen = float(lvec.Length()); + + missing += (added_seglen[i] < linelen - FRACUNIT); + } + + delete [] added_seglen; + return missing; +} + +//========================================================================== +// +// Checks whether the nodes are suitable for GL rendering +// +//========================================================================== + +bool P_CheckForGLNodes() +{ + int i; + + for(i=0;ifirstline; + seg_t * lastseg = sub->firstline + sub->numlines - 1; + + if (firstseg->v1 != lastseg->v2) + { + // This subsector is incomplete which means that these + // are normal nodes + return false; + } + else + { + for(DWORD j=0;jnumlines;j++) + { + if (segs[j].linedef==NULL) // miniseg + { + // We already have GL nodes. Great! + return true; + } + } + } + } + // all subsectors were closed but there are no minisegs + // Although unlikely this can happen. Such nodes are not a problem. + // all that is left is to check whether the BSP covers all sidedefs completely. + int missing = CheckForMissingSegs(); + if (missing > 0) + { + Printf("%d missing segs counted\nThe BSP needs to be rebuilt", missing); + } + return missing == 0; +} + + +//========================================================================== +// +// LoadGLVertexes +// +// loads GL vertices +// +//========================================================================== + +#define gNd2 MAKE_ID('g','N','d','2') +#define gNd4 MAKE_ID('g','N','d','4') +#define gNd5 MAKE_ID('g','N','d','5') + +#define GL_VERT_OFFSET 4 +static int firstglvertex; +static bool format5; + +static bool LoadGLVertexes(FileReader * f, wadlump_t * lump) +{ + BYTE *gldata; + int i; + + firstglvertex = numvertexes; + + int gllen=lump->Size; + + gldata = new BYTE[gllen]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(gldata, gllen); + + if (*(int *)gldata == gNd5) + { + format5=true; + } + else if (*(int *)gldata != gNd2) + { + // GLNodes V1 and V4 are unsupported. + // V1 because the precision is insufficient and + // V4 due to the missing partner segs + Printf("GL nodes v%d found. This format is not supported by "GAMENAME"\n", + (*(int *)gldata == gNd4)? 4:1); + + delete [] gldata; + return false; + } + else format5=false; + + mapglvertex_t* mgl; + + vertex_t * oldvertexes = vertexes; + numvertexes += (gllen - GL_VERT_OFFSET)/sizeof(mapglvertex_t); + vertexes = new vertex_t[numvertexes]; + mgl = (mapglvertex_t *) (gldata + GL_VERT_OFFSET); + + memcpy(vertexes, oldvertexes, firstglvertex * sizeof(vertex_t)); + for(i=0;ix); + vertexes[i].y = LittleLong(mgl->y); + mgl++; + } + delete[] gldata; + return true; +} + +//========================================================================== +// +// GL Nodes utilities +// +//========================================================================== + +static inline int checkGLVertex(int num) +{ + if (num & 0x8000) + num = (num&0x7FFF)+firstglvertex; + return num; +} + +static inline int checkGLVertex3(int num) +{ + if (num & 0xc0000000) + num = (num&0x3FFFFFFF)+firstglvertex; + return num; +} + +//========================================================================== +// +// LoadGLSegs +// +//========================================================================== + +static bool LoadGLSegs(FileReader * f, wadlump_t * lump) +{ + char *data; + int i; + line_t *ldef=NULL; + + numsegs = lump->Size; + data= new char[numsegs]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(data, lump->Size); + segs=NULL; + +#ifdef _MSC_VER + __try +#endif + { + if (!format5 && memcmp(data, "gNd3", 4)) + { + numsegs/=sizeof(glseg_t); + segs = new seg_t[numsegs]; + memset(segs,0,sizeof(seg_t)*numsegs); + glsegextras = new glsegextra_t[numsegs]; + + glseg_t * ml = (glseg_t*)data; + for(i = 0; i < numsegs; i++) + { // check for gl-vertices + segs[i].v1 = &vertexes[checkGLVertex(LittleShort(ml->v1))]; + segs[i].v2 = &vertexes[checkGLVertex(LittleShort(ml->v2))]; + + glsegextras[i].PartnerSeg = ml->partner == 0xFFFF ? DWORD_MAX : LittleShort(ml->partner); + if(ml->linedef != 0xffff) + { + ldef = &lines[LittleShort(ml->linedef)]; + segs[i].linedef = ldef; + + + ml->side=LittleShort(ml->side); + segs[i].sidedef = ldef->sidedef[ml->side]; + segs[i].frontsector = ldef->sidedef[ml->side]->sector; + if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL) + { + segs[i].backsector = ldef->sidedef[ml->side^1]->sector; + } + else + { + ldef->flags &= ~ML_TWOSIDED; + segs[i].backsector = 0; + } + + } + else + { + segs[i].linedef = NULL; + segs[i].sidedef = NULL; + + segs[i].frontsector = NULL; + segs[i].backsector = NULL; + } + ml++; + } + } + else + { + if (!format5) numsegs-=4; + numsegs/=sizeof(glseg3_t); + segs = new seg_t[numsegs]; + memset(segs,0,sizeof(seg_t)*numsegs); + glsegextras = new glsegextra_t[numsegs]; + + glseg3_t * ml = (glseg3_t*)(data+ (format5? 0:4)); + for(i = 0; i < numsegs; i++) + { // check for gl-vertices + segs[i].v1 = &vertexes[checkGLVertex3(LittleLong(ml->v1))]; + segs[i].v2 = &vertexes[checkGLVertex3(LittleLong(ml->v2))]; + + glsegextras[i].PartnerSeg = LittleLong(ml->partner); + + if(ml->linedef != 0xffff) // skip minisegs + { + ldef = &lines[LittleLong(ml->linedef)]; + segs[i].linedef = ldef; + + + ml->side=LittleShort(ml->side); + segs[i].sidedef = ldef->sidedef[ml->side]; + segs[i].frontsector = ldef->sidedef[ml->side]->sector; + if (ldef->flags & ML_TWOSIDED && ldef->sidedef[ml->side^1] != NULL) + { + segs[i].backsector = ldef->sidedef[ml->side^1]->sector; + } + else + { + ldef->flags &= ~ML_TWOSIDED; + segs[i].backsector = 0; + } + + } + else + { + segs[i].linedef = NULL; + segs[i].sidedef = NULL; + segs[i].frontsector = NULL; + segs[i].backsector = NULL; + } + ml++; + } + } + delete [] data; + return true; + } +#ifdef _MSC_VER + __except(1) + { + // Invalid data has the bad habit of requiring extensive checks here + // so let's just catch anything invalid and output a message. + // (at least under MSVC. GCC can't do SEH even for Windows... :( ) + Printf("Invalid GL segs. The BSP will have to be rebuilt.\n"); + delete [] data; + delete [] segs; + segs = NULL; + return false; + } +#endif +} + + +//========================================================================== +// +// LoadGLSubsectors +// +//========================================================================== + +static bool LoadGLSubsectors(FileReader * f, wadlump_t * lump) +{ + char * datab; + int i; + + numsubsectors = lump->Size; + datab = new char[numsubsectors]; + f->Seek(lump->FilePos, SEEK_SET); + f->Read(datab, lump->Size); + + if (numsubsectors == 0) + { + delete [] datab; + return false; + } + + if (!format5 && memcmp(datab, "gNd3", 4)) + { + mapsubsector_t * data = (mapsubsector_t*) datab; + numsubsectors /= sizeof(mapsubsector_t); + subsectors = new subsector_t[numsubsectors]; + memset(subsectors,0,numsubsectors * sizeof(subsector_t)); + + for (i=0; ilinedef==NULL) seg->frontsector = seg->backsector = subsectors[i].firstline->frontsector; + } + seg_t *firstseg = subsectors[i].firstline; + seg_t *lastseg = subsectors[i].firstline + subsectors[i].numlines - 1; + // The subsector must be closed. If it isn't we can't use these nodes and have to do a rebuild. + if (lastseg->v2 != firstseg->v1) + { + delete [] datab; + return false; + } + + } + delete [] datab; + return true; +} + +//========================================================================== +// +// P_LoadNodes +// +//========================================================================== + +static bool LoadNodes (FileReader * f, wadlump_t * lump) +{ + const int NF_SUBSECTOR = 0x8000; + const int GL5_NF_SUBSECTOR = (1 << 31); + + int i; + int j; + int k; + node_t* no; + WORD* used; + + if (!format5) + { + mapnode_t* mn, * basemn; + numnodes = lump->Size / sizeof(mapnode_t); + + if (numnodes == 0) return false; + + nodes = new node_t[numnodes]; + f->Seek(lump->FilePos, SEEK_SET); + + basemn = mn = new mapnode_t[numnodes]; + f->Read(mn, lump->Size); + + used = (WORD *)alloca (sizeof(WORD)*numnodes); + memset (used, 0, sizeof(WORD)*numnodes); + + no = nodes; + + for (i = 0; i < numnodes; i++, no++, mn++) + { + no->x = LittleShort(mn->x)<y = LittleShort(mn->y)<dx = LittleShort(mn->dx)<dy = LittleShort(mn->dy)<children[j]); + if (child & NF_SUBSECTOR) + { + child &= ~NF_SUBSECTOR; + if (child >= numsubsectors) + { + delete [] basemn; + return false; + } + no->children[j] = (BYTE *)&subsectors[child] + 1; + } + else if (child >= numnodes) + { + delete [] basemn; + return false; + } + else if (used[child]) + { + delete [] basemn; + return false; + } + else + { + no->children[j] = &nodes[child]; + used[child] = j + 1; + } + for (k = 0; k < 4; k++) + { + no->bbox[j][k] = LittleShort(mn->bbox[j][k])<Size / sizeof(gl5_mapnode_t); + + if (numnodes == 0) return false; + + nodes = new node_t[numnodes]; + f->Seek(lump->FilePos, SEEK_SET); + + basemn = mn = new gl5_mapnode_t[numnodes]; + f->Read(mn, lump->Size); + + used = (WORD *)alloca (sizeof(WORD)*numnodes); + memset (used, 0, sizeof(WORD)*numnodes); + + no = nodes; + + for (i = 0; i < numnodes; i++, no++, mn++) + { + no->x = LittleShort(mn->x)<y = LittleShort(mn->y)<dx = LittleShort(mn->dx)<dy = LittleShort(mn->dy)<children[j]); + if (child & GL5_NF_SUBSECTOR) + { + child &= ~GL5_NF_SUBSECTOR; + if (child >= numsubsectors) + { + delete [] basemn; + return false; + } + no->children[j] = (BYTE *)&subsectors[child] + 1; + } + else if (child >= numnodes) + { + delete [] basemn; + return false; + } + else if (used[child]) + { + delete [] basemn; + return false; + } + else + { + no->children[j] = &nodes[child]; + used[child] = j + 1; + } + for (k = 0; k < 4; k++) + { + no->bbox[j][k] = LittleShort(mn->bbox[j][k])<sidedef) + { + Printf("GL nodes contain invalid data. The BSP has to be rebuilt.\n"); + delete [] nodes; + nodes = NULL; + delete [] subsectors; + subsectors = NULL; + delete [] segs; + segs = NULL; + return false; + } + } + + // check whether the BSP covers all sidedefs completely. + int missing = CheckForMissingSegs(); + if (missing > 0) + { + Printf("%d missing segs counted in GL nodes.\nThe BSP has to be rebuilt", missing); + } + return missing == 0; +} + + +//=========================================================================== +// +// MatchHeader +// +// Checks whether a GL_LEVEL header belongs to this level +// +//=========================================================================== + +static bool MatchHeader(const char * label, const char * hdata) +{ + if (!memcmp(hdata, "LEVEL=", 6) == 0) + { + size_t labellen = strlen(label); + + if (strnicmp(hdata+6, label, labellen)==0 && + (hdata[6+labellen]==0xa || hdata[6+labellen]==0xd)) + { + return true; + } + } + return false; +} + +//=========================================================================== +// +// FindGLNodesInWAD +// +// Looks for GL nodes in the same WAD as the level itself +// +//=========================================================================== + +static int FindGLNodesInWAD(int labellump) +{ + int wadfile = Wads.GetLumpFile(labellump); + FString glheader; + + glheader.Format("GL_%s", Wads.GetLumpFullName(labellump)); + if (glheader.Len()<=8) + { + int gllabel = Wads.CheckNumForName(glheader, ns_global, wadfile); + if (gllabel >= 0) return gllabel; + } + else + { + // Before scanning the entire WAD directory let's check first whether + // it is necessary. + int gllabel = Wads.CheckNumForName("GL_LEVEL", ns_global, wadfile); + + if (gllabel >= 0) + { + int lastlump=0; + int lump; + while ((lump=Wads.FindLump("GL_LEVEL", &lastlump))>=0) + { + if (Wads.GetLumpFile(lump)==wadfile) + { + FMemLump mem = Wads.ReadLump(lump); + if (MatchHeader(Wads.GetLumpFullName(labellump), (const char *)mem.GetMem())) return true; + } + } + } + } + return -1; +} + +//=========================================================================== +// +// FindGLNodesInWAD +// +// Looks for GL nodes in the same WAD as the level itself +// When this function returns the file pointer points to +// the directory entry of the GL_VERTS lump +// +//=========================================================================== + +static int FindGLNodesInFile(FileReader * f, const char * label) +{ + FString glheader; + bool mustcheck=false; + DWORD id, dirofs, numentries; + DWORD offset, size; + char lumpname[9]; + + glheader.Format("GL_%.8s", label); + if (glheader.Len()>8) + { + glheader="GL_LEVEL"; + mustcheck=true; + } + + f->Seek(0, SEEK_SET); + (*f) >> id >> numentries >> dirofs; + + if ((id == IWAD_ID || id == PWAD_ID) && numentries > 4) + { + f->Seek(dirofs, SEEK_SET); + for(DWORD i=0;i> offset >> size; + f->Read(lumpname, 8); + if (!strnicmp(lumpname, glheader, 8)) + { + if (mustcheck) + { + char check[16]={0}; + int filepos = f->Tell(); + f->Seek(offset, SEEK_SET); + f->Read(check, 16); + f->Seek(filepos, SEEK_SET); + if (MatchHeader(label, check)) return i; + } + else return i; + } + } + } + return -1; +} + +//========================================================================== +// +// Checks for the presence of GL nodes in the loaded WADs or a .GWA file +// returns true if successful +// +//========================================================================== + +bool P_LoadGLNodes(MapData * map) +{ + if (!CheckCachedNodes(map)) + { + wadlump_t gwalumps[4]; + char path[256]; + int li; + int lumpfile = Wads.GetLumpFile(map->lumpnum); + bool mapinwad = map->file == Wads.GetFileReader(lumpfile); + FileReader * fr = map->file; + FILE * f_gwa = NULL; + + const char * name = Wads.GetWadFullName(lumpfile); + + if (mapinwad) + { + li = FindGLNodesInWAD(map->lumpnum); + + if (li>=0) + { + // GL nodes are loaded with a WAD + for(int i=0;i<4;i++) + { + gwalumps[i].FilePos=Wads.GetLumpOffset(li+i+1); + gwalumps[i].Size=Wads.LumpLength(li+i+1); + } + return DoLoadGLNodes(fr, gwalumps); + } + else + { + strcpy(path, name); + + char * ext = strrchr(path, '.'); + if (ext) + { + strcpy(ext, ".gwa"); + // Todo: Compare file dates + + f_gwa = fopen(path, "rb"); + if (f_gwa==NULL) return false; + + fr = new FileReader(f_gwa); + + strncpy(map->MapLumps[0].Name, Wads.GetLumpFullName(map->lumpnum), 8); + } + } + } + + bool result = false; + li = FindGLNodesInFile(fr, map->MapLumps[0].Name); + if (li!=-1) + { + static const char check[][9]={"GL_VERT","GL_SEGS","GL_SSECT","GL_NODES"}; + result=true; + for(unsigned i=0; i<4;i++) + { + (*fr) >> gwalumps[i].FilePos; + (*fr) >> gwalumps[i].Size; + fr->Read(gwalumps[i].Name, 8); + if (strnicmp(gwalumps[i].Name, check[i], 8)) + { + result=false; + break; + } + } + if (result) result = DoLoadGLNodes(fr, gwalumps); + } + + if (f_gwa) + { + delete fr; + fclose(f_gwa); + } + return result; + } + else return true; +} + +//========================================================================== +// +// Checks whether nodes are GL friendly or not +// +//========================================================================== + +bool P_CheckNodes(MapData * map, bool rebuilt, int buildtime) +{ + bool ret = false; + + // If the map loading code has performed a node rebuild we don't need to check for it again. + if (!rebuilt && !P_CheckForGLNodes()) + { + ret = true; // we are not using the level's original nodes if we get here. + for (int i = 0; i < numsubsectors; i++) + { + gamesubsectors[i].sector = gamesubsectors[i].firstline->sidedef->sector; + } + + nodes = NULL; + numnodes = 0; + subsectors = NULL; + numsubsectors = 0; + if (segs) delete [] segs; + segs = NULL; + numsegs = 0; + + // Try to load GL nodes (cached or GWA) + if (!P_LoadGLNodes(map)) + { + // none found - we have to build new ones! + unsigned int startTime, endTime; + + startTime = I_FPSTime (); + TArray polyspots, anchors; + P_GetPolySpots (map, polyspots, anchors); + FNodeBuilder::FLevel leveldata = + { + vertexes, numvertexes, + sides, numsides, + lines, numlines + }; + leveldata.FindMapBounds (); + FNodeBuilder builder (leveldata, polyspots, anchors, true); + delete[] vertexes; + builder.Extract (nodes, numnodes, + segs, glsegextras, numsegs, + subsectors, numsubsectors, + vertexes, numvertexes); + endTime = I_FPSTime (); + DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs); + buildtime = endTime - startTime; + } + } + +#ifdef DEBUG + // Building nodes in debug is much slower so let's cache them only if cachetime is 0 + buildtime = 0; +#endif + if (gl_cachenodes && buildtime/1000.f >= gl_cachetime) + { + DPrintf("Caching nodes\n"); + CreateCachedNodes(map); + } + else + { + DPrintf("Not caching nodes (time = %f)\n", buildtime/1000.f); + } + + + if (!gamenodes) + { + gamenodes = nodes; + numgamenodes = numnodes; + gamesubsectors = subsectors; + numgamesubsectors = numsubsectors; + } + return ret; +} + +//========================================================================== +// +// Node caching +// +//========================================================================== + +typedef TArray MemFile; + +static FString GetCachePath() +{ + FString path; + +#ifdef _WIN32 + char pathstr[MAX_PATH]; + if (0 != SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, pathstr)) + { // Failed (e.g. On Win9x): use program directory + path = progdir; + } + else + { + path = pathstr; + } + path += "/zdoom/cache"; +#elif defined(__APPLE__) + char pathstr[PATH_MAX]; + FSRef folder; + + if (noErr == FSFindFolder(kLocalDomain, kApplicationSupportFolderType, kCreateFolder, &folder) && + noErr == FSRefMakePath(&folder, (UInt8*)cpath, PATH_MAX)) + { + path = pathstr; + } + else + { + path = progdir; + } + path += "/zdoom/cache"; +#else + // Don't use GAME_DIR and such so that ZDoom and its child ports can share the node cache. + path = NicePath("~/.zdoom/cache"); +#endif + return path; +} + +static FString CreateCacheName(MapData *map, bool create) +{ + FString path = GetCachePath(); + FString lumpname = Wads.GetLumpFullPath(map->lumpnum); + int separator = lumpname.IndexOf(':'); + path << '/' << lumpname.Left(separator); + if (create) CreatePath(path); + + lumpname.ReplaceChars('/', '%'); + path << '/' << lumpname.Right(lumpname.Len() - separator - 1) << ".gzc"; + return path; +} + +static void WriteByte(MemFile &f, BYTE b) +{ + f.Push(b); +} + +static void WriteWord(MemFile &f, WORD b) +{ + int v = f.Reserve(2); + f[v] = (BYTE)b; + f[v+1] = (BYTE)(b>>8); +} + +static void WriteLong(MemFile &f, DWORD b) +{ + int v = f.Reserve(4); + f[v] = (BYTE)b; + f[v+1] = (BYTE)(b>>8); + f[v+2] = (BYTE)(b>>16); + f[v+3] = (BYTE)(b>>24); +} + +static void CreateCachedNodes(MapData *map) +{ + MemFile ZNodes; + + WriteLong(ZNodes, 0); + WriteLong(ZNodes, numvertexes); + for(int i=0;isidedef[0]? 0:1); + } + else + { + WriteLong(ZNodes, 0xffffffffu); + WriteByte(ZNodes, 0); + } + } + + WriteLong(ZNodes, numnodes); + for(int i=0;i> FRACBITS); + WriteWord(ZNodes, nodes[i].y >> FRACBITS); + WriteWord(ZNodes, nodes[i].dx >> FRACBITS); + WriteWord(ZNodes, nodes[i].dy >> FRACBITS); + for (int j = 0; j < 2; ++j) + { + for (int k = 0; k < 4; ++k) + { + WriteWord(ZNodes, nodes[i].bbox[j][k] >> FRACBITS); + } + } + + for (int j = 0; j < 2; ++j) + { + DWORD child; + if ((size_t)nodes[i].children[j] & 1) + { + child = 0x80000000 | DWORD((subsector_t *)((BYTE *)nodes[i].children[j] - 1) - subsectors); + } + else + { + child = DWORD((node_t *)nodes[i].children[j] - nodes); + } + WriteLong(ZNodes, child); + } + } + + uLongf outlen = ZNodes.Size(); + BYTE *compressed; + int offset = numlines * 8 + 12 + 16; + int r; + do + { + compressed = new Bytef[outlen + offset]; + r = compress (compressed + offset, &outlen, &ZNodes[0], ZNodes.Size()); + if (r == Z_BUF_ERROR) + { + delete[] compressed; + outlen += 1024; + } + } + while (r == Z_BUF_ERROR); + + memcpy(compressed, "CACH", 4); + DWORD len = LittleLong(numlines); + memcpy(compressed+4, &len, 4); + map->GetChecksum(compressed+8); + for(int i=0;iGetChecksum(md5map); + if (memcmp(md5, md5map, 16)) goto errorout; + + verts = new DWORD[numlin * 8]; + if (fread(verts, 8, numlin, f) != numlin) goto errorout; + + if (fread(magic, 1, 4, f) != 4) goto errorout; + if (memcmp(magic, "ZGL2", 4)) goto errorout; + + + try + { + long pos = ftell(f); + FileReader fr(f); + fr.Seek(pos, SEEK_SET); + P_LoadZNodes (fr, MAKE_ID('Z','G','L','2')); + } + catch (CRecoverableError &error) + { + Printf ("Error loading nodes: %s\n", error.GetMessage()); + + if (subsectors != NULL) + { + delete[] subsectors; + subsectors = NULL; + } + if (segs != NULL) + { + delete[] segs; + segs = NULL; + } + if (nodes != NULL) + { + delete[] nodes; + nodes = NULL; + } + goto errorout; + } + + for(int i=0;i list; + FString path = GetCachePath(); + path += "/"; + + try + { + ScanDirectory(list, path); + } + catch (CRecoverableError &err) + { + Printf("%s", err.GetMessage()); + return; + } + + // Scan list backwards so that when we reach a directory + // all files within are already deleted. + for(int i = list.Size()-1; i >= 0; i--) + { + if (list[i].isDirectory) + { + rmdir(list[i].Filename); + } + else + { + remove(list[i].Filename); + } + } + + +} + +//========================================================================== +// +// Keep both the original nodes from the WAD and the GL nodes created here. +// The original set is only being used to get the sector for in-game +// positioning of actors but not for rendering. +// +// This is necessary because ZDBSP is much more sensitive +// to sloppy mapping practices that produce overlapping sectors. +// The crane in P:AR E1M3 is a good example that would be broken if +// this wasn't done. +// +//========================================================================== + + +//========================================================================== +// +// P_PointInSubsector +// +//========================================================================== + +subsector_t *P_PointInSubsector (fixed_t x, fixed_t y) +{ + node_t *node; + int side; + + // single subsector is a special case + if (numgamenodes == 0) + return gamesubsectors; + + node = gamenodes + numgamenodes - 1; + + do + { + side = R_PointOnSide (x, y, node); + node = (node_t *)node->children[side]; + } + while (!((size_t)node & 1)); + + return (subsector_t *)((BYTE *)node - 1); +} + + +//========================================================================== +// +// PointOnLine +// +// Same as the one im the node builder, but not part of a specific class +// +//========================================================================== + +static bool PointOnLine (int x, int y, int x1, int y1, int dx, int dy) +{ + const double SIDE_EPSILON = 6.5536; + + // For most cases, a simple dot product is enough. + double d_dx = double(dx); + double d_dy = double(dy); + double d_x = double(x); + double d_y = double(y); + double d_x1 = double(x1); + double d_y1 = double(y1); + + double s_num = (d_y1-d_y)*d_dx - (d_x1-d_x)*d_dy; + + if (fabs(s_num) < 17179869184.0) // 4<<32 + { + // Either the point is very near the line, or the segment defining + // the line is very short: Do a more expensive test to determine + // just how far from the line the point is. + double l = sqrt(d_dx*d_dx+d_dy*d_dy); + double dist = fabs(s_num)/l; + if (dist < SIDE_EPSILON) + { + return true; + } + } + return false; +} + + +//========================================================================== +// +// SetRenderSector +// +// Sets the render sector for each GL subsector so that the proper flat +// information can be retrieved +// +//========================================================================== + +void P_SetRenderSector() +{ + int i; + DWORD j; + TArray undetermined; + subsector_t * ss; + +#if 0 // doesn't work as expected :( + + // hide all sectors on textured automap that only have hidden lines. + bool *hidesec = new bool[numsectors]; + for(i = 0; i < numsectors; i++) + { + hidesec[i] = true; + } + for(i = 0; i < numlines; i++) + { + if (!(lines[i].flags & ML_DONTDRAW)) + { + hidesec[lines[i].frontsector - sectors] = false; + if (lines[i].backsector != NULL) + { + hidesec[lines[i].backsector - sectors] = false; + } + } + } + for(i = 0; i < numsectors; i++) + { + if (hidesec[i]) sectors[i].MoreFlags |= SECF_HIDDEN; + } + delete [] hidesec; +#endif + + // Check for incorrect partner seg info so that the following code does not crash. + if (glsegextras == NULL) + { + // This can be normal nodes, mistakenly identified as GL nodes so we must fill + // in the missing pieces differently. + + for (i = 0; i < numsubsectors; i++) + { + ss = &subsectors[i]; + ss->render_sector = ss->sector; + } + return; + } + + for(i=0;i=numsegs/*eh? || &segs[partner]!=glsegextras[i].PartnerSeg*/) + { + glsegextras[i].PartnerSeg=DWORD_MAX; + } + + // glbsp creates such incorrect references for Strife. + if (segs[i].linedef && glsegextras[i].PartnerSeg != DWORD_MAX && !segs[glsegextras[i].PartnerSeg].linedef) + { + glsegextras[i].PartnerSeg = glsegextras[glsegextras[i].PartnerSeg].PartnerSeg = DWORD_MAX; + } + } + + for(i=0;ifirstline; + + // Check for one-dimensional subsectors. These should be ignored when + // being processed for automap drawinng etc. + ss->flags |= SSECF_DEGENERATE; + for(j=2; jnumlines; j++) + { + if (!PointOnLine(seg[j].v1->x, seg[j].v1->y, seg->v1->x, seg->v1->y, seg->v2->x-seg->v1->x, seg->v2->y-seg->v1->y)) + { + // Not on the same line + ss->flags &= ~SSECF_DEGENERATE; + break; + } + } + + seg = ss->firstline; + for(j=0; jnumlines; j++) + { + if(seg->sidedef && (glsegextras[seg - segs].PartnerSeg == DWORD_MAX || seg->sidedef->sector!=segs[glsegextras[seg - segs].PartnerSeg].sidedef->sector)) + { + ss->render_sector = seg->sidedef->sector; + break; + } + seg++; + } + if(ss->render_sector == NULL) + { + undetermined.Push(ss); + } + } + + // assign a vaild render sector to all subsectors which haven't been processed yet. + while (undetermined.Size()) + { + bool deleted=false; + for(i=undetermined.Size()-1;i>=0;i--) + { + ss=undetermined[i]; + seg_t * seg = ss->firstline; + + for(j=0; jnumlines; j++) + { + DWORD partner = glsegextras[seg - segs].PartnerSeg; + if (partner != DWORD_MAX && glsegextras[partner].Subsector) + { + sector_t * backsec = glsegextras[partner].Subsector->render_sector; + if (backsec) + { + ss->render_sector=backsec; + undetermined.Delete(i); + deleted=1; + break; + } + } + seg++; + } + } + // We still got some left but the loop above was unable to assign them. + // This only happens when a subsector is off the map. + // Don't bother and just assign the real sector for rendering + if (!deleted && undetermined.Size()) + { + for(i=undetermined.Size()-1;i>=0;i--) + { + ss=undetermined[i]; + ss->render_sector=ss->sector; + } + break; + } + } + +#if 0 // may be useful later so let's keep it here for now + // now group the subsectors by sector + subsector_t ** subsectorbuffer = new subsector_t * [numsubsectors]; + + for(i=0, ss=subsectors; irender_sector->subsectorcount++; + } + + for (i=0; irender_sector->subsectors[ss->render_sector->subsectorcount++]=ss; + } +#endif + +} diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 72bea5c49..a53356a8e 100644 --- a/src/p_lnspec.cpp +++ b/src/p_lnspec.cpp @@ -3006,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] = { @@ -3088,7 +3118,7 @@ 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, diff --git a/src/p_lnspec.h b/src/p_lnspec.h index f0942c4fd..050e0fe7b 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 91b134605..2997f3121 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -456,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; } // diff --git a/src/p_map.cpp b/src/p_map.cpp index 2d105c284..f578ec7fe 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -3352,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. @@ -3502,12 +3502,12 @@ AActor *P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, // 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) @@ -3517,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) { @@ -3819,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; diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index 91370c070..9f4ddb39a 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -302,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); } @@ -322,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) ) { @@ -460,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 { diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index f52067ba2..4d6cdc765 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -361,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"); } } @@ -1052,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 || @@ -3482,6 +3441,7 @@ bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash) return false; // we did the splash ourselves } + //========================================================================== // // P_SpawnMobj @@ -3501,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; @@ -4326,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 || @@ -4423,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)) { @@ -4515,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) @@ -4578,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; @@ -4619,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! @@ -4658,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; @@ -4721,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++) { @@ -4839,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) @@ -4965,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)) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 1ee18eeb2..52ebf88d2 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -47,6 +47,7 @@ #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); @@ -542,3 +543,108 @@ void P_SerializePolyobjs (FArchive &arc) } } } + +//========================================================================== +// +// 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 linemap; -bool UsingGLNodes; - // BLOCKMAP // Created from axis aligned bounding box // of the map, a rectangular array of @@ -152,7 +161,6 @@ fixed_t bmaporgx; // origin of block map fixed_t bmaporgy; FBlockNode** blocklinks; // for thing chains - // REJECT @@ -790,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) { @@ -846,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; @@ -887,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; @@ -956,6 +956,7 @@ 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) { @@ -968,6 +969,7 @@ static void LoadZNodes(FileReaderBase &data, int glnodes) } else { + glsegextras = new glsegextra_t[numsegs]; P_LoadGLZSegs (data, glnodes); } @@ -1014,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; @@ -1092,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) { @@ -1157,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); @@ -1225,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; @@ -1249,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"); @@ -1325,8 +1334,8 @@ void P_LoadSubsectors (MapData * map) if ((size_t)subsectors[i].firstline >= maxseg) { Printf ("Subsector %d contains invalid segs %u-%u\n" - "The BSP will be rebuilt.\n", i, (unsigned)subsectors[i].firstline, - (unsigned)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; @@ -1336,7 +1345,7 @@ void P_LoadSubsectors (MapData * map) { Printf ("Subsector %d contains invalid segs %u-%u\n" "The BSP will be rebuilt.\n", i, maxseg, - (unsigned)subsectors[i].firstline + subsectors[i].numlines - 1); + (unsigned)((size_t)subsectors[i].firstline) + subsectors[i].numlines - 1); ForceNodeBuild = true; delete[] nodes; delete[] subsectors; @@ -2128,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) { @@ -2193,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; } @@ -2903,6 +2914,16 @@ static void P_GroupLines (bool buildmap) { subsectors[i].sector = subsectors[i].firstline->sidedef->sector; } + if (glsegextras != NULL) + { + for (i = 0; i < numsubsectors; i++) + { + for (jj = 0; jj < subsectors[i].numlines; ++jj) + { + glsegextras[subsectors[i].firstline - segs + jj].Subsector = &subsectors[i]; + } + } + } times[0].Unclock(); // count number of lines in each sector @@ -3338,6 +3359,11 @@ void P_FreeLevelData () delete[] segs; segs = NULL; } + if (glsegextras != NULL) + { + delete[] glsegextras; + glsegextras = NULL; + } if (sectors != NULL) { delete[] sectors[0].e; @@ -3345,6 +3371,14 @@ 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) @@ -3355,13 +3389,15 @@ void P_FreeLevelData () } } 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; @@ -3479,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(); @@ -3536,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; @@ -3654,7 +3694,7 @@ void P_SetupLevel (char *lumpname, int position) } times[6].Clock(); - P_LoopSidedefs (); + P_LoopSidedefs (true); times[6].Unclock(); linemap.Clear(); @@ -3664,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'); @@ -3755,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; @@ -3770,15 +3825,68 @@ void P_SetupLevel (char *lumpname, int position) lines, numlines }; leveldata.FindMapBounds (); - UsingGLNodes |= genglnodes; - FNodeBuilder builder (leveldata, polyspots, anchors, UsingGLNodes); + // 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(); @@ -3797,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. @@ -3844,9 +3957,7 @@ void P_SetupLevel (char *lumpname, int position) P_SpawnSpecials (); times[16].Clock(); - // The old sidedef looping data is no longer valid if the nodes were rebuilt - // and vertexes merged so it has to be redone before setting up the polyobjects. - if (ForceNodeBuild) P_LoopSidedefs (); + if (reloop) P_LoopSidedefs (false); PO_Init (); // Initialize the polyobjs times[16].Unclock(); @@ -3923,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 80196e317..aa860af0e 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -111,6 +111,12 @@ 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 @@ -133,5 +139,7 @@ struct sidei_t // [RH] Only keep BOOM sidedef init stuff around for init }; }; extern sidei_t *sidetemp; +extern bool hasglnodes; +extern struct glsegextra_t *glsegextras; #endif diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 915d4a508..e37df68b1 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_things.cpp b/src/p_things.cpp index cd1540f4f..6622ac0a7 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 c057c253c..5f2fca941 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -35,7 +35,6 @@ #include "r_data.h" #include "p_setup.h" -#include "sc_man.h" #include "p_lnspec.h" #include "templates.h" #include "i_system.h" @@ -43,6 +42,7 @@ #include "r_sky.h" #include "g_level.h" #include "v_palette.h" +#include "p_udmf.h" //=========================================================================== // @@ -124,6 +124,152 @@ extern TArray linemap; #define CHECK_N(f) if (!(namespace_bits&(f))) break; +//=========================================================================== +// +// Common parsing routines +// +//=========================================================================== + +//=========================================================================== +// +// Skip a key or block +// +//=========================================================================== + +void UDMFParserBase::Skip() +{ + if (developer) sc.ScriptMessage("Ignoring unknown key \"%s\".", sc.String); + if(sc.CheckToken('{')) + { + int level = 1; + while(sc.GetToken()) + { + if (sc.TokenType == '}') + { + level--; + if(level == 0) + { + sc.UnGet(); + break; + } + } + else if (sc.TokenType == '{') + { + level++; + } + } + } + else + { + sc.MustGetToken('='); + do + { + sc.MustGetAnyToken(); + } + while(sc.TokenType != ';'); + } +} + +//=========================================================================== +// +// Parses a 'key = value' line of the map +// +//=========================================================================== + +FName UDMFParserBase::ParseKey(bool checkblock, bool *isblock) +{ + sc.MustGetString(); + FName key = sc.String; + if (checkblock) + { + if (sc.CheckToken('{')) + { + if (isblock) *isblock = true; + return key; + } + else if (isblock) *isblock = false; + } + sc.MustGetToken('='); + + sc.Number = 0; + sc.Float = 0; + sc.MustGetAnyToken(); + + if (sc.TokenType == '+' || sc.TokenType == '-') + { + bool neg = (sc.TokenType == '-'); + sc.MustGetAnyToken(); + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Numeric constant expected"); + } + if (neg) + { + sc.Number = -sc.Number; + sc.Float = -sc.Float; + } + } + if (sc.TokenType == TK_StringConst) + { + parsedString = sc.String; + } + int savedtoken = sc.TokenType; + sc.MustGetToken(';'); + sc.TokenType = savedtoken; + return key; +} + +//=========================================================================== +// +// Syntax checks +// +//=========================================================================== + +int UDMFParserBase::CheckInt(const char *key) +{ + if (sc.TokenType != TK_IntConst) + { + sc.ScriptMessage("Integer value expected for key '%s'", key); + } + return sc.Number; +} + +double UDMFParserBase::CheckFloat(const char *key) +{ + if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) + { + sc.ScriptMessage("Floating point value expected for key '%s'", key); + } + return sc.Float; +} + +fixed_t UDMFParserBase::CheckFixed(const char *key) +{ + return FLOAT2FIXED(CheckFloat(key)); +} + +angle_t UDMFParserBase::CheckAngle(const char *key) +{ + return angle_t(CheckFloat(key) * ANGLE_90 / 90.); +} + +bool UDMFParserBase::CheckBool(const char *key) +{ + if (sc.TokenType == TK_True) return true; + if (sc.TokenType == TK_False) return false; + sc.ScriptMessage("Boolean value expected for key '%s'", key); + return false; +} + +const char *UDMFParserBase::CheckString(const char *key) +{ + if (sc.TokenType != TK_StringConst) + { + sc.ScriptMessage("String value expected for key '%s'", key); + } + return parsedString; +} + //=========================================================================== // // Storage of UDMF user properties @@ -233,15 +379,11 @@ fixed_t GetUDMFFixed(int type, int index, const char *key) // //=========================================================================== -struct UDMFParser +class UDMFParser : public UDMFParserBase { - FScanner sc; - FName namespc; - int namespace_bits; bool isTranslated; bool isExtended; bool floordrop; - FString parsedString; TArray ParsedLines; TArray ParsedSides; @@ -251,113 +393,13 @@ struct UDMFParser FDynamicColormap *fogMap, *normMap; +public: UDMFParser() { linemap.Clear(); fogMap = normMap = NULL; } - //=========================================================================== - // - // Parses a 'key = value' line of the map - // - //=========================================================================== - - FName ParseKey() - { - sc.MustGetString(); - FName key = sc.String; - sc.MustGetToken('='); - - sc.Number = 0; - sc.Float = 0; - sc.MustGetAnyToken(); - - if (sc.TokenType == '+' || sc.TokenType == '-') - { - bool neg = (sc.TokenType == '-'); - sc.MustGetAnyToken(); - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Numeric constant expected"); - } - if (neg) - { - sc.Number = -sc.Number; - sc.Float = -sc.Float; - } - } - if (sc.TokenType == TK_StringConst) - { - parsedString = sc.String; - } - int savedtoken = sc.TokenType; - sc.MustGetToken(';'); - sc.TokenType = savedtoken; - return key; - } - - //=========================================================================== - // - // Syntax checks - // - //=========================================================================== - - int CheckInt(const char *key) - { - if (sc.TokenType != TK_IntConst) - { - sc.ScriptMessage("Integer value expected for key '%s'", key); - } - return sc.Number; - } - - double CheckFloat(const char *key) - { - if (sc.TokenType != TK_IntConst && sc.TokenType != TK_FloatConst) - { - sc.ScriptMessage("Floating point value expected for key '%s'", key); - } - return sc.Float; - } - - fixed_t CheckFixed(const char *key) - { - return FLOAT2FIXED(CheckFloat(key)); - } - - angle_t CheckAngle(const char *key) - { - return angle_t(CheckFloat(key) * ANGLE_90 / 90.); - } - - bool CheckBool(const char *key) - { - if (sc.TokenType == TK_True) return true; - if (sc.TokenType == TK_False) return false; - sc.ScriptMessage("Boolean value expected for key '%s'", key); - return false; - } - - const char *CheckString(const char *key) - { - if (sc.TokenType != TK_StringConst) - { - sc.ScriptMessage("String value expected for key '%s'", key); - } - return parsedString; - } - - template - void Flag(T &value, int mask, const char *key) - { - if (CheckBool(key)) - value |= mask; - else - value &= ~mask; - } - - void AddUserKey(FName key, int kind, int index) { FUDMFKeys &keyarray = UDMFKeys[kind][index]; @@ -451,6 +493,11 @@ struct UDMFParser th->type = (short)CheckInt(key); break; + case NAME_Conversation: + CHECK_N(Zd | Zdt) + th->Conversation = CheckInt(key); + break; + case NAME_Special: CHECK_N(Hx | Zd | Zdt | Va) th->special = CheckInt(key); @@ -1180,6 +1227,10 @@ struct UDMFParser sec->seqType = -1; continue; + case NAME_hidden: + sec->MoreFlags |= SECF_HIDDEN; + break; + default: break; } @@ -1457,6 +1508,10 @@ struct UDMFParser ParseVertex(&vt); ParsedVertices.Push(vt); } + else + { + Skip(); + } } // Create the real vertices diff --git a/src/p_udmf.h b/src/p_udmf.h new file mode 100644 index 000000000..0fc2f5564 --- /dev/null +++ b/src/p_udmf.h @@ -0,0 +1,38 @@ +#ifndef __P_UDMF_H +#define __P_UDMF_H + +#include "sc_man.h" +#include "m_fixed.h" +#include "tables.h" + +class UDMFParserBase +{ +protected: + FScanner sc; + FName namespc; + int namespace_bits; + FString parsedString; + + void Skip(); + FName ParseKey(bool checkblock = false, bool *isblock = NULL); + int CheckInt(const char *key); + double CheckFloat(const char *key); + fixed_t CheckFixed(const char *key); + angle_t CheckAngle(const char *key); + bool CheckBool(const char *key); + const char *CheckString(const char *key); + + template + void Flag(T &value, int mask, const char *key) + { + if (CheckBool(key)) + value |= mask; + else + value &= ~mask; + } + +}; + +#define BLOCK_ID (ENamedName)-1 + +#endif \ No newline at end of file diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp new file mode 100644 index 000000000..b831b4140 --- /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 65909b3d3..eef4f5025 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() @@ -503,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; + } } //=========================================================================== @@ -1966,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 1ca8d9a3a..0f9b0b15a 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -1245,6 +1245,7 @@ bool FPolyObj::CheckMobjBlocking (side_t *sd) bool blocked; ld = sd->linedef; + top = (ld->bbox[BOXTOP]-bmaporgy) >> MAPBLOCKSHIFT; bottom = (ld->bbox[BOXBOTTOM]-bmaporgy) >> MAPBLOCKSHIFT; left = (ld->bbox[BOXLEFT]-bmaporgx) >> MAPBLOCKSHIFT; @@ -1294,6 +1295,17 @@ bool FPolyObj::CheckMobjBlocking (side_t *sd) { continue; } + // 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; } @@ -1752,8 +1764,8 @@ static void TranslateToStartSpot (int tag, int originX, int originY) po->OriginalPts[i].y = po->Vertices[i]->y - po->StartSpot.y; } po->CalcCenter(); - // subsector assignment no longer done here. - // Polyobjects will be sorted into the subsectors each frame before rendering them. + // For compatibility purposes + po->CenterSubsector = R_PointInSubsector(po->CenterSpot.x, po->CenterSpot.y); } //========================================================================== @@ -1827,12 +1839,6 @@ void PO_Init (void) // [RH] Don't need the seg lists anymore KillSideLists (); - // We still need to flag the segs of the polyobj's sidedefs so that they are excluded from rendering. - for(int i=0;iFlags & WALLF_POLYOBJ); - } - for(int i=0;idy; 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; + } + } + } + } //========================================================================== @@ -2193,7 +2216,29 @@ void FPolyObj::CreateSubsectorLinks() seg->v2 = side->V2(); seg->wall = side; } - SplitPoly(node, nodes + numnodes - 1, dummybbox); + 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; + } } //========================================================================== diff --git a/src/po_man.h b/src/po_man.h index d76dff55f..074426d88 100644 --- a/src/po_man.h +++ b/src/po_man.h @@ -53,6 +53,7 @@ struct FPolyObj FPolyVertex StartSpot; FPolyVertex CenterSpot; FBoundingBox Bounds; // Bounds in map coordinates + subsector_t *CenterSubsector; angle_t angle; int tag; // reference tag assigned in HereticEd diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index 71d4f210a..e7ba7c116 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -36,6 +36,7 @@ #include "i_system.h" #include "p_lnspec.h" +#include "p_setup.h" #include "r_main.h" #include "r_plane.h" @@ -112,6 +113,8 @@ 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? @@ -167,10 +170,11 @@ static cliprange_t solidsegs[MAXWIDTH/2+2]; // //========================================================================== -void R_ClipWallSegment (int first, int last, bool solid) +bool R_ClipWallSegment (int first, int last, bool solid) { cliprange_t *next, *start; int i, j; + bool res = false; // Find the first range that touches the range // (adjacent pixels are touching). @@ -180,6 +184,7 @@ void R_ClipWallSegment (int first, int last, bool solid) if (first < start->first) { + res = true; if (last <= start->first) { // Post is entirely visible (above start). @@ -205,7 +210,7 @@ void R_ClipWallSegment (int first, int last, bool solid) next->last = last; } } - return; + return true; } // There is a fragment above *start. @@ -220,7 +225,7 @@ void R_ClipWallSegment (int first, int last, bool solid) // Bottom contained in start? if (last <= start->last) - return; + return res; next = start; while (last >= (next+1)->first) @@ -257,6 +262,31 @@ crunch: newend = start+i; } } + return true; +} + +bool R_CheckClipWallSegment (int first, int last) +{ + cliprange_t *start; + + // Find the first range that touches the range + // (adjacent pixels are touching). + start = solidsegs; + while (start->last < first) + start++; + + if (first < start->first) + { + return true; + } + + // Bottom contained in start? + if (last > start->last) + { + return true; + } + + return false; } @@ -626,6 +656,10 @@ void R_AddLine (seg_t *line) if (line->linedef == NULL) { + if (R_CheckClipWallSegment (WallSX1, WallSX2)) + { + InSubsector->flags |= SSECF_DRAWN; + } return; } @@ -792,6 +826,16 @@ void R_AddLine (seg_t *line) // Reject empty lines used for triggers and special events. // Identical floor and ceiling on both sides, identical light levels // on both sides, and no middle texture. + + // When using GL nodes, do a clipping test for these lines so we can + // mark their subsectors as visible for automap texturing. + if (hasglnodes && !(InSubsector->flags & SSECF_DRAWN)) + { + if (R_CheckClipWallSegment(WallSX1, WallSX2)) + { + InSubsector->flags |= SSECF_DRAWN; + } + } return; } } @@ -827,7 +871,10 @@ void R_AddLine (seg_t *line) #endif } - R_ClipWallSegment (WallSX1, WallSX2, solid); + if (R_ClipWallSegment (WallSX1, WallSX2, solid)) + { + InSubsector->flags |= SSECF_DRAWN; + } } @@ -1096,12 +1143,21 @@ void R_Subsector (subsector_t *sub) sector_t tempsec; // killough 3/7/98: deep water hack int floorlightlevel; // killough 3/16/98: set floor lightlevel int ceilinglightlevel; // killough 4/11/98 + bool outersubsector; + + if (InSubsector != NULL) + { // InSubsector is not NULL. This means we are rendering from a mini-BSP. + outersubsector = false; + } + else + { + outersubsector = true; + InSubsector = sub; + } -#if 0 #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 #endif assert(sub->sector != NULL); @@ -1109,6 +1165,10 @@ void R_Subsector (subsector_t *sub) if (sub->polys) { // Render the polyobjs in the subsector first R_AddPolyobjs(sub); + if (outersubsector) + { + InSubsector = NULL; + } return; } @@ -1190,12 +1250,16 @@ void R_Subsector (subsector_t *sub) while (count--) { - if (!line->bPolySeg) + if (!outersubsector || line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ)) { R_AddLine (line); } line++; } + if (outersubsector) + { + InSubsector = NULL; + } } // diff --git a/src/r_defs.h b/src/r_defs.h index 3c8ab46d7..9e0477ac2 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -319,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 @@ -942,10 +943,12 @@ struct seg_t // Sector references. Could be retrieved from linedef, too. sector_t* frontsector; sector_t* backsector; // NULL for one-sided lines +}; - seg_t* PartnerSeg; - - BITFIELD bPolySeg:1; +struct glsegextra_t +{ + DWORD PartnerSeg; + subsector_t *Subsector; }; // @@ -954,13 +957,23 @@ struct seg_t // Basically, this is a list of LineSegs indicating the visible walls that // define (all or some) sides of a convex BSP leaf. // + +enum +{ + SSECF_DEGENERATE = 1, + SSECF_DRAWN = 2, + SSECF_POLYORG = 4, +}; + struct subsector_t { sector_t *sector; FPolyNode *polys; FMiniBSP *BSP; seg_t *firstline; + sector_t *render_sector; DWORD numlines; + int flags; }; diff --git a/src/r_draw.cpp b/src/r_draw.cpp index ce836f839..ba77ad2d8 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 4d452b49d..96e58bf8d 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -91,8 +91,11 @@ extern void (*R_DrawShadedColumn)(void); // Green/Red/Blue/Indigo shirts. extern void (*R_DrawTranslatedColumn)(void); -// Span drawing for rows, floor/ceiling. No Sepctre effect needed. +// Span drawing for rows, floor/ceiling. No Spectre effect needed. extern void (*R_DrawSpan)(void); +void R_SetupSpanBits(FTexture *tex); +void R_SetSpanColormap(BYTE *colormap); +void R_SetSpanSource(const BYTE *pixels); // Span drawing for masked textures. extern void (*R_DrawSpanMasked)(void); diff --git a/src/r_plane.cpp b/src/r_plane.cpp index b89a2979e..cd0b9f4a5 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -991,22 +991,9 @@ void R_DrawSinglePlane (visplane_t *pl, fixed_t alpha, bool masked) { // Don't waste time on a masked texture if it isn't really masked. masked = false; } - tex->GetWidth (); - ds_xbits = tex->WidthBits; - ds_ybits = tex->HeightBits; - if ((1 << ds_xbits) > tex->GetWidth()) - { - ds_xbits--; - } - if ((1 << ds_ybits) > tex->GetHeight()) - { - ds_ybits--; - } + R_SetupSpanBits(tex); pl->xscale = MulScale16 (pl->xscale, tex->xScale); pl->yscale = MulScale16 (pl->yscale, tex->yScale); -#ifdef X86_ASM - R_SetSpanSize_ASM (ds_xbits, ds_ybits); -#endif ds_source = tex->GetPixels (); basecolormap = pl->colormap; diff --git a/src/r_plane.h b/src/r_plane.h index d52fc6db2..369083bad 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -110,5 +110,4 @@ bool R_PlaneInitData (void); extern visplane_t* floorplane; extern visplane_t* ceilingplane; - #endif // __R_PLANE_H__ diff --git a/src/r_polymost.cpp b/src/r_polymost.cpp index 2cb3e2225..12050440f 100644 --- a/src/r_polymost.cpp +++ b/src/r_polymost.cpp @@ -1485,7 +1485,7 @@ void RP_Subsector (subsector_t *sub) while (count--) { - if (!line->bPolySeg) + if (line->sidedef == NULL || !(line->sidedef->Flags & WALLF_POLYOBJ)) { RP_AddLine (line); } diff --git a/src/r_state.h b/src/r_state.h index 38f8e4c3e..9044da72b 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -77,6 +77,13 @@ extern side_t* sides; extern int numzones; extern zone_t* zones; +extern node_t * gamenodes; +extern int numgamenodes; + +extern subsector_t * gamesubsectors; +extern int numgamesubsectors; + + extern FExtraLight* ExtraLights; extern FLightStack* LightStacks; diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index e121bc2ee..46d4bc813 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -1294,6 +1294,7 @@ static void S_AddSNDINFO (int lump) else if (sc.Compare("standard")) MidiDevices[nm] = MDEV_MMAPI; else if (sc.Compare("opl")) MidiDevices[nm] = MDEV_OPL; else if (sc.Compare("default")) MidiDevices[nm] = MDEV_DEFAULT; + else if (sc.Compare("fluidsynth")) MidiDevices[nm] = MDEV_FLUIDSYNTH; else sc.ScriptError("Unknown MIDI device %s\n", sc.String); } break; diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 40be1b927..8d6424169 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -827,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 8d89d1a0d..05237ba27 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/i_input.cpp b/src/sdl/i_input.cpp index 3b3823e37..3e94ff045 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 01d3673c6..124c2ca85 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_system.cpp b/src/sdl/i_system.cpp index c05ed2027..ff15b1e88 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -778,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 778af53f5..acdae0167 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/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index cdbb70645..8d19ea4fb 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -708,7 +708,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; } @@ -1332,10 +1333,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; } diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index e25d4b5d6..172553df5 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -3,7 +3,7 @@ ** Plays music ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2010 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -84,6 +84,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 4bde02df1..5b238cd4c 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 e5c4771eb..8c6507167 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -95,6 +95,9 @@ public: virtual bool NeedThreadedCallback() = 0; virtual void PrecacheInstruments(const WORD *instruments, int count); virtual void TimidityVolumeChanged(); + virtual void FluidSettingInt(const char *setting, int value); + virtual void FluidSettingNum(const char *setting, double value); + virtual void FluidSettingStr(const char *setting, const char *value); virtual FString GetStats(); }; @@ -136,14 +139,14 @@ protected: }; #endif -// OPL implementation of a MIDI output device ------------------------------- +// Base class for software synthesizer MIDI output devices ------------------ -class OPLMIDIDevice : public MIDIDevice, protected OPLmusicBlock +class SoftSynthMIDIDevice : public MIDIDevice { public: - OPLMIDIDevice(); - ~OPLMIDIDevice(); - int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + SoftSynthMIDIDevice(); + ~SoftSynthMIDIDevice(); + void Close(); bool IsOpen() const; int GetTechnology() const; @@ -158,24 +161,51 @@ public: bool FakeVolume(); bool NeedThreadedCallback(); bool Pause(bool paused); - FString GetStats(); protected: - static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + FCriticalSection CritSec; + SoundStream *Stream; + double Tempo; + double Division; + double SamplesPerTick; + double NextTickIn; + MIDIHDR *Events; + bool Started; + DWORD Position; + int SampleRate; void (*Callback)(unsigned int, void *, DWORD, DWORD); void *CallbackData; - void CalcTickRate(); - void HandleEvent(int status, int parm1, int parm2); + virtual void CalcTickRate(); int PlayTick(); + int OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + virtual bool ServiceStream (void *buff, int numbytes); - SoundStream *Stream; - double Tempo; - double Division; - MIDIHDR *Events; - bool Started; - DWORD Position; + virtual void HandleEvent(int status, int parm1, int parm2) = 0; + virtual void HandleLongEvent(const BYTE *data, int len) = 0; + virtual void ComputeOutput(float *buffer, int len) = 0; +}; + +// OPL implementation of a MIDI output device ------------------------------- + +class OPLMIDIDevice : public SoftSynthMIDIDevice, protected OPLmusicBlock +{ +public: + OPLMIDIDevice(); + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + void Close(); + int GetTechnology() const; + FString GetStats(); + +protected: + void CalcTickRate(); + int PlayTick(); + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); + bool ServiceStream(void *buff, int numbytes); }; // OPL dumper implementation of a MIDI output device ------------------------ @@ -193,52 +223,22 @@ public: namespace Timidity { struct Renderer; } -class TimidityMIDIDevice : public MIDIDevice +class TimidityMIDIDevice : public SoftSynthMIDIDevice { public: TimidityMIDIDevice(); - TimidityMIDIDevice(int rate); ~TimidityMIDIDevice(); int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); - void Close(); - bool IsOpen() const; - int GetTechnology() const; - int SetTempo(int tempo); - int SetTimeDiv(int timediv); - int StreamOut(MIDIHDR *data); - int StreamOutSync(MIDIHDR *data); - int Resume(); - void Stop(); - int PrepareHeader(MIDIHDR *data); - int UnprepareHeader(MIDIHDR *data); - bool FakeVolume(); - bool Pause(bool paused); - bool NeedThreadedCallback(); void PrecacheInstruments(const WORD *instruments, int count); - void TimidityVolumeChanged(); FString GetStats(); protected: - static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); - bool ServiceStream (void *buff, int numbytes); - - void (*Callback)(unsigned int, void *, DWORD, DWORD); - void *CallbackData; - - void CalcTickRate(); - int PlayTick(); - - FCriticalSection CritSec; - SoundStream *Stream; Timidity::Renderer *Renderer; - double Tempo; - double Division; - double SamplesPerTick; - double NextTickIn; - MIDIHDR *Events; - bool Started; - DWORD Position; + + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); }; // Internal TiMidity disk writing version of a MIDI device ------------------ @@ -255,6 +255,77 @@ protected: FILE *File; }; +// FluidSynth implementation of a MIDI device ------------------------------- + +#ifdef HAVE_FLUIDSYNTH +#ifndef DYN_FLUIDSYNTH +#include +#else +struct fluid_settings_t; +struct fluid_synth_t; +#endif + +class FluidSynthMIDIDevice : public SoftSynthMIDIDevice +{ +public: + FluidSynthMIDIDevice(); + ~FluidSynthMIDIDevice(); + + int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata); + FString GetStats(); + void FluidSettingInt(const char *setting, int value); + void FluidSettingNum(const char *setting, double value); + void FluidSettingStr(const char *setting, const char *value); + +protected: + void HandleEvent(int status, int parm1, int parm2); + void HandleLongEvent(const BYTE *data, int len); + void ComputeOutput(float *buffer, int len); + int LoadPatchSets(const char *patches); + + fluid_settings_t *FluidSettings; + fluid_synth_t *FluidSynth; + +#ifdef DYN_FLUIDSYNTH + enum { FLUID_FAILED = 1, FLUID_OK = 0 }; + fluid_settings_t *(STACK_ARGS *new_fluid_settings)(); + fluid_synth_t *(STACK_ARGS *new_fluid_synth)(fluid_settings_t *); + int (STACK_ARGS *delete_fluid_synth)(fluid_synth_t *); + void (STACK_ARGS *delete_fluid_settings)(fluid_settings_t *); + int (STACK_ARGS *fluid_settings_setnum)(fluid_settings_t *, const char *, double); + int (STACK_ARGS *fluid_settings_setstr)(fluid_settings_t *, const char *, const char *); + int (STACK_ARGS *fluid_settings_setint)(fluid_settings_t *, const char *, int); + int (STACK_ARGS *fluid_settings_getstr)(fluid_settings_t *, const char *, char **); + int (STACK_ARGS *fluid_settings_getint)(fluid_settings_t *, const char *, int *); + int (STACK_ARGS *fluid_synth_set_interp_method)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_set_polyphony)(fluid_synth_t *, int); + int (STACK_ARGS *fluid_synth_get_polyphony)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_get_active_voice_count)(fluid_synth_t *); + double (STACK_ARGS *fluid_synth_get_cpu_load)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_system_reset)(fluid_synth_t *); + int (STACK_ARGS *fluid_synth_noteon)(fluid_synth_t *, int, int, int); + int (STACK_ARGS *fluid_synth_noteoff)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_cc)(fluid_synth_t *, int, int, int); + int (STACK_ARGS *fluid_synth_program_change)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_channel_pressure)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_pitch_bend)(fluid_synth_t *, int, int); + int (STACK_ARGS *fluid_synth_write_float)(fluid_synth_t *, int, void *, int, int, void *, int, int); + int (STACK_ARGS *fluid_synth_sfload)(fluid_synth_t *, const char *, int); + void (STACK_ARGS *fluid_synth_set_reverb)(fluid_synth_t *, double, double, double, double); + void (STACK_ARGS *fluid_synth_set_chorus)(fluid_synth_t *, int, double, double, double, int); + int (STACK_ARGS *fluid_synth_sysex)(fluid_synth_t *, const char *, int, char *, int *, int *, int); + +#ifdef _WIN32 + HMODULE FluidSynthDLL; +#else + void *FluidSynthSO; +#endif + bool LoadFluidSynth(); + void UnloadFluidSynth(); +#endif +}; +#endif + // Base class for streaming MUS and MIDI files ------------------------------ // MIDI device selection. @@ -262,6 +333,12 @@ enum EMIDIDevice { MIDI_Win, MIDI_OPL, + MIDI_GUS, + MIDI_Fluid, + + // only used by I_RegisterSong + MIDI_Null, + MIDI_FMOD, MIDI_Timidity }; @@ -282,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 8261d4611..ceaaf0172 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -90,6 +90,7 @@ CVAR (String, snd_backend, "null", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_pitched, false, CVAR_ARCHIVE) SoundRenderer *GSnd; +bool nosfx; void I_CloseSound (); @@ -243,7 +244,8 @@ public: void I_InitSound () { /* Get command line options: */ - bool nosound = !!Args->CheckParm ("-nosfx") || !!Args->CheckParm ("-nosound"); + bool nosound = !!Args->CheckParm ("-nosound"); + nosfx = !!Args->CheckParm ("-nosfx"); if (nosound) { diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index d8cf166fc..e5edcbb40 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -143,6 +143,7 @@ public: }; extern SoundRenderer *GSnd; +extern bool nosfx; void I_InitSound (); void I_ShutdownSound (); diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp new file mode 100644 index 000000000..d50e236b7 --- /dev/null +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -0,0 +1,728 @@ +/* +** music_fluidsynth_mididevice.cpp +** Provides access to FluidSynth as a generic MIDI device. +** +**--------------------------------------------------------------------------- +** Copyright 2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifdef HAVE_FLUIDSYNTH + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +#ifdef DYN_FLUIDSYNTH + +#ifdef _WIN32 +#ifndef _M_X64 +#define FLUIDSYNTHLIB "fluidsynth.dll" +#else +#define FLUIDSYNTHLIB "fluidsynth64.dll" +#endif +#else +#include + +#define FLUIDSYNTHLIB "libfluidsynth.so.1" +#endif + +#define FLUID_REVERB_DEFAULT_ROOMSIZE 0.2f +#define FLUID_REVERB_DEFAULT_DAMP 0.0f +#define FLUID_REVERB_DEFAULT_WIDTH 0.5f +#define FLUID_REVERB_DEFAULT_LEVEL 0.9f + +#define FLUID_CHORUS_MOD_SINE 0 +#define FLUID_CHORUS_MOD_TRIANGLE 1 + +#define FLUID_CHORUS_DEFAULT_N 3 +#define FLUID_CHORUS_DEFAULT_LEVEL 2.0f +#define FLUID_CHORUS_DEFAULT_SPEED 0.3f +#define FLUID_CHORUS_DEFAULT_DEPTH 8.0f +#define FLUID_CHORUS_DEFAULT_TYPE FLUID_CHORUS_MOD_SINE + +#endif + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(String, fluid_patchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +CUSTOM_CVAR(Float, fluid_gain, 0.5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 10) + self = 10; + else if (currSong != NULL) + currSong->FluidSettingNum("synth.gain", self); +} + +CUSTOM_CVAR(Bool, fluid_reverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (currSong != NULL) + currSong->FluidSettingStr("synth.reverb.active", self ? "yes" : "no"); +} + +CUSTOM_CVAR(Bool, fluid_chorus, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (currSong != NULL) + currSong->FluidSettingStr("synth.chorus.active", self ? "yes" : "no"); +} + +CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 16) + self = 16; + else if (self > 4096) + self = 4096; + else if (currSong != NULL) + currSong->FluidSettingInt("synth.polyphony", self); +} + +CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + // Values are: 0 = FLUID_INTERP_NONE + // 1 = FLUID_INTERP_LINEAR + // 4 = FLUID_INTERP_4THORDER (the FluidSynth default) + // 7 = FLUID_INTERP_7THORDER + // (And here I thought it was just a linear list.) + // Round undefined values to the nearest valid one. + if (self < 0) + self = 0; + else if (self == 2) + self = 1; + else if (self == 3 || self == 5) + self = 4; + else if (self == 6 || self > 7) + self = 7; + else if (currSong != NULL) + currSong->FluidSettingInt("synth.interpolation", self); +} + +CVAR(Int, fluid_samplerate, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +// I don't know if this setting even matters for us, since we aren't letting +// FluidSynth drives its own output. +CUSTOM_CVAR(Int, fluid_threads, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 1) + self = 1; + else if (self > 256) + self = 256; +} + +CUSTOM_CVAR(Float, fluid_reverb_roomsize, FLUID_REVERB_DEFAULT_ROOMSIZE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1.2f) + self = 1.2f; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_damping, FLUID_REVERB_DEFAULT_DAMP, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_width, FLUID_REVERB_DEFAULT_WIDTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 100) + self = 100; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_reverb_level, FLUID_REVERB_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.reverb-changed", 0); +} + +CUSTOM_CVAR(Int, fluid_chorus_voices, FLUID_CHORUS_DEFAULT_N, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 99) + self = 99; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_chorus_level, FLUID_CHORUS_DEFAULT_LEVEL, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 1) + self = 1; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Float, fluid_chorus_speed, FLUID_CHORUS_DEFAULT_SPEED, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0.29f) + self = 0.29f; + else if (self > 5) + self = 5; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +// depth is in ms and actual maximum depends on the sample rate +CUSTOM_CVAR(Float, fluid_chorus_depth, FLUID_CHORUS_DEFAULT_DEPTH, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 21) + self = 21; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +CUSTOM_CVAR(Int, fluid_chorus_type, FLUID_CHORUS_DEFAULT_TYPE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self != FLUID_CHORUS_MOD_SINE && self != FLUID_CHORUS_MOD_TRIANGLE) + self = FLUID_CHORUS_DEFAULT_TYPE; + else if (currSong != NULL) + currSong->FluidSettingInt("z.chorus-changed", 0); +} + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FluidSynthMIDIDevice Constructor +// +//========================================================================== + +FluidSynthMIDIDevice::FluidSynthMIDIDevice() +{ + FluidSynth = NULL; + FluidSettings = NULL; +#ifdef DYN_FLUIDSYNTH + if (!LoadFluidSynth()) + { + return; + } +#endif + FluidSettings = new_fluid_settings(); + if (FluidSettings == NULL) + { + printf("Failed to create FluidSettings.\n"); + return; + } + SampleRate = fluid_samplerate; + if (SampleRate < 22050 || SampleRate > 96000) + { // Match sample rate to SFX rate + SampleRate = clamp((int)GSnd->GetOutputRate(), 22050, 96000); + } + fluid_settings_setnum(FluidSettings, "synth.sample-rate", SampleRate); + fluid_settings_setnum(FluidSettings, "synth.gain", fluid_gain); + fluid_settings_setstr(FluidSettings, "synth.reverb.active", fluid_reverb ? "yes" : "no"); + fluid_settings_setstr(FluidSettings, "synth.chorus.active", fluid_chorus ? "yes" : "no"); + fluid_settings_setint(FluidSettings, "synth.polyphony", fluid_voices); + fluid_settings_setint(FluidSettings, "synth.cpu-cores", fluid_threads); + FluidSynth = new_fluid_synth(FluidSettings); + if (FluidSynth == NULL) + { + Printf("Failed to create FluidSynth.\n"); + return; + } + fluid_synth_set_interp_method(FluidSynth, -1, fluid_interp); + fluid_synth_set_reverb(FluidSynth, fluid_reverb_roomsize, fluid_reverb_damping, + fluid_reverb_width, fluid_reverb_level); + fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level, + fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type); + if (0 == LoadPatchSets(fluid_patchset)) + { +#ifdef unix + // This is the standard location on Ubuntu. + if (0 == LoadPatchSets("/usr/share/sounds/sf2/FluidR3_GS.sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2")) + { +#endif +#ifdef _WIN32 + // On Windows, look for the 4 megabyte patch set installed by Creative's drivers as a default. + char sysdir[MAX_PATH+sizeof("\\CT4MGM.SF2")]; + UINT filepart; + if (0 != (filepart = GetSystemDirectoryA(sysdir, MAX_PATH))) + { + strcat(sysdir, "\\CT4MGM.SF2"); + if (0 == LoadPatchSets(sysdir)) + { + // Try again with CT2MGM.SF2 + sysdir[filepart + 3] = '2'; + if (0 == LoadPatchSets(sysdir)) + { +#endif + Printf("Failed to load any MIDI patches.\n"); + delete_fluid_synth(FluidSynth); + FluidSynth = NULL; +#ifdef _WIN32 + } + } + } +#endif +#ifdef unix + } +#endif + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice Destructor +// +//========================================================================== + +FluidSynthMIDIDevice::~FluidSynthMIDIDevice() +{ + Close(); + if (FluidSynth != NULL) + { + delete_fluid_synth(FluidSynth); + } + if (FluidSettings != NULL) + { + delete_fluid_settings(FluidSettings); + } +#ifdef DYN_FLUIDSYNTH + UnloadFluidSynth(); +#endif +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Open +// +// Returns 0 on success. +// +//========================================================================== + +int FluidSynthMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + if (FluidSynth == NULL) + { + return 2; + } + int ret = OpenStream(4, 0, callback, userdata); + if (ret == 0) + { + fluid_synth_system_reset(FluidSynth); + } + return ret; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: HandleEvent +// +// Translates a MIDI event into FluidSynth calls. +// +//========================================================================== + +void FluidSynthMIDIDevice::HandleEvent(int status, int parm1, int parm2) +{ + int command = status & 0xF0; + int channel = status & 0x0F; + + switch (command) + { + case MIDI_NOTEOFF: + fluid_synth_noteoff(FluidSynth, channel, parm1); + break; + + case MIDI_NOTEON: + fluid_synth_noteon(FluidSynth, channel, parm1, parm2); + break; + + case MIDI_POLYPRESS: + break; + + case MIDI_CTRLCHANGE: + fluid_synth_cc(FluidSynth, channel, parm1, parm2); + break; + + case MIDI_PRGMCHANGE: + fluid_synth_program_change(FluidSynth, channel, parm1); + break; + + case MIDI_CHANPRESS: + fluid_synth_channel_pressure(FluidSynth, channel, parm1); + break; + + case MIDI_PITCHBEND: + fluid_synth_pitch_bend(FluidSynth, channel, (parm1 & 0x7f) | ((parm2 & 0x7f) << 7)); + break; + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: HandleLongEvent +// +// Handle SysEx messages. +// +//========================================================================== + +void FluidSynthMIDIDevice::HandleLongEvent(const BYTE *data, int len) +{ + if (len > 1 && (data[0] == 0xF0 || data[0] == 0xF7)) + { + fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - 1, NULL, NULL, NULL, 0); + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: ComputeOutput +// +//========================================================================== + +void FluidSynthMIDIDevice::ComputeOutput(float *buffer, int len) +{ + fluid_synth_write_float(FluidSynth, len, + buffer, 0, 2, + buffer, 1, 2); +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: LoadPatchSets +// +// Loads a delimiter-separated list of patch sets. This delimiter matches +// that of the PATH environment variable. On Windows, it is ';'. On other +// systems, it is ':'. Returns the number of patch sets loaded. +// +//========================================================================== + +int FluidSynthMIDIDevice::LoadPatchSets(const char *patches) +{ + int count; + char *wpatches = strdup(patches); + char *tok; +#ifdef _WIN32 + const char *const delim = ";"; +#else + const char *const delim = ":"; +#endif + + if (wpatches == NULL) + { + return 0; + } + tok = strtok(wpatches, delim); + count = 0; + while (tok != NULL) + { + if (FLUID_FAILED != fluid_synth_sfload(FluidSynth, tok, count == 0)) + { + DPrintf("Loaded patch set %s.\n", tok); + count++; + } + else + { + DPrintf("Failed to load patch set %s.\n", tok); + } + tok = strtok(NULL, delim); + } + free(wpatches); + return count; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingInt +// +// Changes an integer setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingInt(const char *setting, int value) +{ + if (FluidSynth == NULL || FluidSettings == NULL) + { + return; + } + + if (strcmp(setting, "synth.interpolation") == 0) + { + if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value)) + { + Printf("Setting interpolation method %d failed.\n", value); + } + } + else if (strcmp(setting, "synth.polyphony") == 0) + { + if (FLUID_OK != fluid_synth_set_polyphony(FluidSynth, value)) + { + Printf("Setting polyphony to %d failed.\n", value); + } + } + else if (strcmp(setting, "z.reverb-changed") == 0) + { + fluid_synth_set_reverb(FluidSynth, fluid_reverb_roomsize, fluid_reverb_damping, + fluid_reverb_width, fluid_reverb_level); + } + else if (strcmp(setting, "z.chorus-changed") == 0) + { + fluid_synth_set_chorus(FluidSynth, fluid_chorus_voices, fluid_chorus_level, + fluid_chorus_speed, fluid_chorus_depth, fluid_chorus_type); + } + else if (FLUID_OK != fluid_settings_setint(FluidSettings, setting, value)) + { + Printf("Faild to set %s to %d.\n", setting, value); + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingNum +// +// Changes a numeric setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingNum(const char *setting, double value) +{ + if (FluidSettings != NULL) + { + if (!fluid_settings_setnum(FluidSettings, setting, value)) + { + Printf("Failed to set %s to %g.\n", setting, value); + } + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FluidSettingStr +// +// Changes a string setting. +// +//========================================================================== + +void FluidSynthMIDIDevice::FluidSettingStr(const char *setting, const char *value) +{ + if (FluidSettings != NULL) + { + if (!fluid_settings_setstr(FluidSettings, setting, value)) + { + Printf("Failed to set %s to %s.\n", setting, value); + } + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: GetStats +// +//========================================================================== + +FString FluidSynthMIDIDevice::GetStats() +{ + if (FluidSynth == NULL || FluidSettings == NULL) + { + return "FluidSynth is invalid"; + } + FString out; + + CritSec.Enter(); + int polyphony = fluid_synth_get_polyphony(FluidSynth); + int voices = fluid_synth_get_active_voice_count(FluidSynth); + double load = fluid_synth_get_cpu_load(FluidSynth); + char *chorus, *reverb; + int maxpoly; + fluid_settings_getstr(FluidSettings, "synth.chorus.active", &chorus); + fluid_settings_getstr(FluidSettings, "synth.reverb.active", &reverb); + fluid_settings_getint(FluidSettings, "synth.polyphony", &maxpoly); + CritSec.Leave(); + + out.Format("Voices: "TEXTCOLOR_YELLOW"%3d"TEXTCOLOR_NORMAL"/"TEXTCOLOR_ORANGE"%3d"TEXTCOLOR_NORMAL"("TEXTCOLOR_RED"%3d"TEXTCOLOR_NORMAL")" + TEXTCOLOR_YELLOW"%6.2f"TEXTCOLOR_NORMAL"%% CPU " + "Reverb: "TEXTCOLOR_YELLOW"%3s"TEXTCOLOR_NORMAL + " Chorus: "TEXTCOLOR_YELLOW"%3s", + voices, polyphony, maxpoly, load, reverb, chorus); + return out; +} + +#ifdef DYN_FLUIDSYNTH + +struct LibFunc +{ + void **FuncPointer; + const char *FuncName; +}; + +//========================================================================== +// +// FluidSynthMIDIDevice :: LoadFluidSynth +// +// Returns true if the FluidSynth library was successfully loaded. +// +//========================================================================== + +bool FluidSynthMIDIDevice::LoadFluidSynth() +{ + LibFunc imports[] = + { + { (void **)&new_fluid_settings, "new_fluid_settings" }, + { (void **)&new_fluid_synth, "new_fluid_synth" }, + { (void **)&delete_fluid_synth, "delete_fluid_synth" }, + { (void **)&delete_fluid_settings, "delete_fluid_settings" }, + { (void **)&fluid_settings_setnum, "fluid_settings_setnum" }, + { (void **)&fluid_settings_setstr, "fluid_settings_setstr" }, + { (void **)&fluid_settings_setint, "fluid_settings_setint" }, + { (void **)&fluid_settings_getstr, "fluid_settings_getstr" }, + { (void **)&fluid_settings_getint, "fluid_settings_getint" }, + { (void **)&fluid_synth_set_interp_method, "fluid_synth_set_interp_method" }, + { (void **)&fluid_synth_set_polyphony, "fluid_synth_set_polyphony" }, + { (void **)&fluid_synth_get_polyphony, "fluid_synth_get_polyphony" }, + { (void **)&fluid_synth_get_active_voice_count, "fluid_synth_get_active_voice_count" }, + { (void **)&fluid_synth_get_cpu_load, "fluid_synth_get_cpu_load" }, + { (void **)&fluid_synth_system_reset, "fluid_synth_system_reset" }, + { (void **)&fluid_synth_noteon, "fluid_synth_noteon" }, + { (void **)&fluid_synth_noteoff, "fluid_synth_noteoff" }, + { (void **)&fluid_synth_cc, "fluid_synth_cc" }, + { (void **)&fluid_synth_program_change, "fluid_synth_program_change" }, + { (void **)&fluid_synth_channel_pressure, "fluid_synth_channel_pressure" }, + { (void **)&fluid_synth_pitch_bend, "fluid_synth_pitch_bend" }, + { (void **)&fluid_synth_write_float, "fluid_synth_write_float" }, + { (void **)&fluid_synth_sfload, "fluid_synth_sfload" }, + { (void **)&fluid_synth_set_reverb, "fluid_synth_set_reverb" }, + { (void **)&fluid_synth_set_chorus, "fluid_synth_set_chorus" }, + { (void **)&fluid_synth_sysex, "fluid_synth_sysex" }, + }; + int fail = 0; + +#ifdef _WIN32 + FluidSynthDLL = LoadLibrary(FLUIDSYNTHLIB); + if (FluidSynthDLL == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB "\n"); + return false; + } +#else + FluidSynthSO = dlopen(FLUIDSYNTHLIB, RTLD_LAZY); + if (FluidSynthSO == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB ": %s\n", dlerror()); + return false; + } +#endif + + for (size_t i = 0; i < countof(imports); ++i) + { +#ifdef _WIN32 + FARPROC proc = GetProcAddress(FluidSynthDLL, imports[i].FuncName); +#else + void *proc = dlsym(FluidSynthSO, imports[i].FuncName); +#endif + if (proc == NULL) + { + Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, FLUIDSYNTHLIB); + fail++; + } + *imports[i].FuncPointer = (void *)proc; + } + if (fail == 0) + { + return true; + } + else + { +#ifdef _WIN32 + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; +#else + dlclose(FluidSynthSO); + FluidSynthSO = NULL; +#endif + return false; + } + +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: UnloadFluidSynth +// +//========================================================================== + +void FluidSynthMIDIDevice::UnloadFluidSynth() +{ +#ifdef _WIN32 + if (FluidSynthDLL != NULL) + { + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; + } +#else + if (FluidSynthSO != NULL) + { + dlclose(FluidSynthSO); + FluidSynthSO = NULL; + } +#endif +} + +#endif + +#endif + diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp new file mode 100644 index 000000000..c2075bf1a --- /dev/null +++ b/src/sound/music_hmi_midiout.cpp @@ -0,0 +1,880 @@ +/* +** music_midi_midiout.cpp +** Code to let ZDoom play HMI MIDI music through the MIDI streaming API. +** +**--------------------------------------------------------------------------- +** Copyright 2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" + +// MACROS ------------------------------------------------------------------ + +#define SONG_MAGIC "HMI-MIDISONG" +#define TRACK_MAGIC "HMI-MIDITRACK" + +// Used by SendCommand to check for unexpected end-of-track conditions. +#define CHECK_FINISHED \ + if (track->TrackP >= track->MaxTrackP) \ + { \ + track->Finished = true; \ + return events; \ + } + +// In song header +#define TRACK_COUNT_OFFSET 0xE4 +#define TRACK_DIR_PTR_OFFSET 0xE8 + +// In track header +#define TRACK_DATA_PTR_OFFSET 0x57 +#define TRACK_DESIGNATION_OFFSET 0x99 + +#define NUM_DESIGNATIONS 8 + +// MIDI device types for designation +#define HMI_DEV_GM 0xA000 // Generic General MIDI (not a real device) +#define HMI_DEV_MPU401 0xA001 // MPU-401, Roland Sound Canvas, Ensoniq SoundScape, Rolad RAP-10 +#define HMI_DEV_OPL2 0xA002 // SoundBlaster (Pro), ESS AudioDrive +#define HMI_DEV_MT32 0xA004 // MT-32 +#define HMI_DEV_SBAWE32 0xA008 // SoundBlaster AWE32 +#define HMI_DEV_OPL3 0xA009 // SoundBlaster 16, Microsoft Sound System, Pro Audio Spectrum 16 +#define HMI_DEV_GUS 0xA00A // Gravis UltraSound, Gravis UltraSound Max/Ace + + +// Data accessors, since this data is highly likely to be unaligned. +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) +inline int GetShort(const BYTE *foo) +{ + return *(const short *)foo; +} +inline int GetInt(const BYTE *foo) +{ + return *(const int *)foo; +} +#else +inline int GetShort(const BYTE *foo) +{ + return short(foo[0] | (foo[1] << 8)); +} +inline int GetInt(const BYTE *foo) +{ + return int(foo[0] | (foo[1] << 8) | (foo[2] << 16) | (foo[3] << 24)); +} +#endif + +// TYPES ------------------------------------------------------------------- + +struct HMISong::TrackInfo +{ + const BYTE *TrackBegin; + size_t TrackP; + size_t MaxTrackP; + DWORD Delay; + DWORD PlayedTime; + WORD Designation[NUM_DESIGNATIONS]; + bool Enabled; + bool Finished; + BYTE RunningStatus; + + DWORD ReadVarLen (); +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern char MIDI_EventLengths[7]; +extern char MIDI_CommonLengths[15]; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// HMISong Constructor +// +// Buffers the file and does some validation of the HMI header. +// +//========================================================================== + +HMISong::HMISong (FILE *file, BYTE *musiccache, int len, EMIDIDevice type) +: MIDIStreamer(type), MusHeader(0), Tracks(0) +{ + int p; + int i; + +#ifdef _WIN32 + if (ExitEvent == NULL) + { + return; + } +#endif + if (len < 0x100) + { // Way too small to be HMI. + return; + } + MusHeader = new BYTE[len]; + SongLen = len; + if (file != NULL) + { + if (fread(MusHeader, 1, len, file) != (size_t)len) + return; + } + else + { + memcpy(MusHeader, musiccache, len); + } + + // Do some validation of the MIDI file + if (memcmp(MusHeader, SONG_MAGIC, 12) != 0) + return; + + NumTracks = GetShort(MusHeader + TRACK_COUNT_OFFSET); + if (NumTracks <= 0) + { + return; + } + + // The division is the number of pulses per quarter note (PPQN). + Division = 60; + + Tracks = new TrackInfo[NumTracks + 1]; + int track_dir = GetInt(MusHeader + TRACK_DIR_PTR_OFFSET); + + // Gather information about each track + for (i = 0, p = 0; i < NumTracks; ++i) + { + int start = GetInt(MusHeader + track_dir + i*4); + int tracklen, datastart; + + if (start > len - TRACK_DESIGNATION_OFFSET - 4) + { // Track is incomplete. + continue; + } + + // BTW, HMI does not actually check the track header. + if (memcmp(MusHeader + start, TRACK_MAGIC, 13) != 0) + { + continue; + } + + // The track ends where the next one begins. If this is the + // last track, then it ends at the end of the file. + if (i == NumTracks - 1) + { + tracklen = len - start; + } + else + { + tracklen = GetInt(MusHeader + track_dir + i*4 + 4) - start; + } + // Clamp incomplete tracks to the end of the file. + tracklen = MIN(tracklen, len - start); + if (tracklen <= 0) + { + continue; + } + + // Offset to actual MIDI events. + datastart = GetInt(MusHeader + start + TRACK_DATA_PTR_OFFSET); + tracklen -= datastart; + if (tracklen <= 0) + { + continue; + } + + // Store track information + Tracks[p].TrackBegin = MusHeader + start + datastart; + Tracks[p].TrackP = 0; + Tracks[p].MaxTrackP = tracklen; + + // Retrieve track designations. We can't check them yet, since we have not yet + // connected to the MIDI device. + for (int ii = 0; ii < NUM_DESIGNATIONS; ++ii) + { + Tracks[p].Designation[ii] = GetShort(MusHeader + start + TRACK_DESIGNATION_OFFSET + ii*2); + } + + p++; + } + + // In case there were fewer actual chunks in the file than the + // header specified, update NumTracks with the current value of p. + NumTracks = p; + + if (NumTracks == 0) + { // No tracks, so nothing to play + return; + } +} + +//========================================================================== +// +// HMISong Destructor +// +//========================================================================== + +HMISong::~HMISong () +{ + if (Tracks != NULL) + { + delete[] Tracks; + } + if (MusHeader != NULL) + { + delete[] MusHeader; + } +} + +//========================================================================== +// +// HMISong :: CheckCaps +// +// Check track designations and disable tracks that have not been +// designated for the device we will be playing on. +// +//========================================================================== + +void HMISong::CheckCaps(int tech) +{ + // What's the equivalent HMI device for our technology? + if (tech == MOD_FMSYNTH) + { + tech = HMI_DEV_OPL3; + } + else if (tech == MOD_MIDIPORT) + { + tech = HMI_DEV_MPU401; + } + else + { // Good enough? Or should we just say we're GM. + tech = HMI_DEV_SBAWE32; + } + + for (int i = 0; i < NumTracks; ++i) + { + Tracks[i].Enabled = false; + // Track designations are stored in a 0-terminated array. + for (int j = 0; j < NUM_DESIGNATIONS && Tracks[i].Designation[j] != 0; ++j) + { + if (Tracks[i].Designation[j] == tech) + { + Tracks[i].Enabled = true; + } + // If a track is designated for device 0xA000, it will be played by a MIDI + // driver for device types 0xA000, 0xA001, and 0xA008. Why this does not + // include the GUS, I do not know. + else if (Tracks[i].Designation[j] == HMI_DEV_GM) + { + Tracks[i].Enabled = (tech == HMI_DEV_MPU401 || tech == HMI_DEV_SBAWE32); + } + // If a track is designated for device 0xA002, it will be played by a MIDI + // driver for device types 0xA002 or 0xA009. + else if (Tracks[i].Designation[j] == HMI_DEV_OPL2) + { + Tracks[i].Enabled = (tech == HMI_DEV_OPL3); + } + // Any other designation must match the specific MIDI driver device number. + // (Which we handled first above.) + + if (Tracks[i].Enabled) + { // This track's been enabled, so we can stop checking other designations. + break; + } + } + } +} + + +//========================================================================== +// +// HMISong :: DoInitialSetup +// +// Sets the starting channel volumes. +// +//========================================================================== + +void HMISong :: DoInitialSetup() +{ + for (int i = 0; i < 16; ++i) + { + ChannelVolumes[i] = 100; + } +} + +//========================================================================== +// +// HMISong :: DoRestart +// +// Rewinds every track. +// +//========================================================================== + +void HMISong :: DoRestart() +{ + int i; + + // Set initial state. + FakeTrack = &Tracks[NumTracks]; + NoteOffs.Clear(); + for (i = 0; i <= NumTracks; ++i) + { + Tracks[i].TrackP = 0; + Tracks[i].Finished = false; + Tracks[i].RunningStatus = 0; + Tracks[i].PlayedTime = 0; + } + ProcessInitialMetaEvents (); + for (i = 0; i < NumTracks; ++i) + { + Tracks[i].Delay = Tracks[i].ReadVarLen(); + } + Tracks[i].Delay = 0; // for the FakeTrack + Tracks[i].Enabled = true; + TrackDue = Tracks; + TrackDue = FindNextDue(); +} + +//========================================================================== +// +// HMISong :: CheckDone +// +//========================================================================== + +bool HMISong::CheckDone() +{ + return TrackDue == NULL; +} + +//========================================================================== +// +// HMISong :: MakeEvents +// +// Copies MIDI events from the file and puts them into a MIDI stream +// buffer. Returns the new position in the buffer. +// +//========================================================================== + +DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time) +{ + DWORD *start_events; + DWORD tot_time = 0; + DWORD time = 0; + DWORD delay; + + start_events = events; + while (TrackDue && events < max_event_p && tot_time <= max_time) + { + // It's possible that this tick may be nothing but meta-events and + // not generate any real events. Repeat this until we actually + // get some output so we don't send an empty buffer to the MIDI + // device. + do + { + delay = TrackDue->Delay; + time += delay; + // Advance time for all tracks by the amount needed for the one up next. + tot_time += delay * Tempo / Division; + AdvanceTracks(delay); + // Play all events for this tick. + do + { + DWORD *new_events = SendCommand(events, TrackDue, time); + TrackDue = FindNextDue(); + if (new_events != events) + { + time = 0; + } + events = new_events; + } + while (TrackDue && TrackDue->Delay == 0 && events < max_event_p); + } + while (start_events == events && TrackDue); + time = 0; + } + return events; +} + +//========================================================================== +// +// HMISong :: AdvanceTracks +// +// Advaces time for all tracks by the specified amount. +// +//========================================================================== + +void HMISong::AdvanceTracks(DWORD time) +{ + for (int i = 0; i <= NumTracks; ++i) + { + if (Tracks[i].Enabled && !Tracks[i].Finished) + { + Tracks[i].Delay -= time; + Tracks[i].PlayedTime += time; + } + } + NoteOffs.AdvanceTime(time); +} + +//========================================================================== +// +// HMISong :: SendCommand +// +// Places a single MIDIEVENT in the event buffer. +// +//========================================================================== + +DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) +{ + DWORD len; + BYTE event, data1 = 0, data2 = 0; + + // If the next event comes from the fake track, pop an entry off the note-off queue. + if (track == FakeTrack) + { + AutoNoteOff off; + NoteOffs.Pop(off); + events[0] = delay; + events[1] = 0; + events[2] = MIDI_NOTEON | off.Channel | (off.Key << 8); + return events + 3; + } + + CHECK_FINISHED + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + + if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe) + { + // Normal short message + if ((event & 0xF0) == 0xF0) + { + if (MIDI_CommonLengths[event & 15] > 0) + { + data1 = track->TrackBegin[track->TrackP++]; + if (MIDI_CommonLengths[event & 15] > 1) + { + data2 = track->TrackBegin[track->TrackP++]; + } + } + } + else if ((event & 0x80) == 0) + { + data1 = event; + event = track->RunningStatus; + } + else + { + track->RunningStatus = event; + data1 = track->TrackBegin[track->TrackP++]; + } + + CHECK_FINISHED + + if (MIDI_EventLengths[(event&0x70)>>4] == 2) + { + data2 = track->TrackBegin[track->TrackP++]; + } + + // Monitor channel volume controller changes. + if ((event & 0x70) == (MIDI_CTRLCHANGE & 0x70) && data1 == 7) + { + data2 = VolumeControllerChange(event & 15, data2); + } + + events[0] = delay; + events[1] = 0; + if (event != MIDI_META) + { + events[2] = event | (data1<<8) | (data2<<16); + } + else + { + events[2] = MEVT_NOP; + } + events += 3; + + if ((event & 0x70) == (MIDI_NOTEON & 0x70)) + { // HMI note on events include the time until an implied note off event. + NoteOffs.AddNoteOff(track->ReadVarLen(), event & 0x0F, data1); + } + } + else + { + // Skip SysEx events just because I don't want to bother with them. + // The old MIDI player ignored them too, so this won't break + // anything that played before. + if (event == MIDI_SYSEX || event == MIDI_SYSEXEND) + { + len = track->ReadVarLen (); + track->TrackP += len; + } + else if (event == MIDI_META) + { + // It's a meta-event + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + len = track->ReadVarLen (); + CHECK_FINISHED + + if (track->TrackP + len <= track->MaxTrackP) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + Tempo = + (track->TrackBegin[track->TrackP+0]<<16) | + (track->TrackBegin[track->TrackP+1]<<8) | + (track->TrackBegin[track->TrackP+2]); + events[0] = delay; + events[1] = 0; + events[2] = (MEVT_TEMPO << 24) | Tempo; + events += 3; + break; + } + track->TrackP += len; + if (track->TrackP == track->MaxTrackP) + { + track->Finished = true; + } + } + else + { + track->Finished = true; + } + } + else if (event == 0xFE) + { // Skip unknown HMI events. + event = track->TrackBegin[track->TrackP++]; + CHECK_FINISHED + if (event == 0x13 || event == 0x15) + { + track->TrackP += 6; + } + else if (event == 0x12 || event == 0x14) + { + track->TrackP += 2; + } + else if (event == 0x10) + { + track->TrackP += 2; + CHECK_FINISHED + track->TrackP += track->TrackBegin[track->TrackP] + 5; + CHECK_FINISHED + } + else + { // No idea. + track->Finished = true; + } + } + } + if (!track->Finished) + { + track->Delay = track->ReadVarLen(); + } + return events; +} + +//========================================================================== +// +// HMISong :: ProcessInitialMetaEvents +// +// Handle all the meta events at the start of each track. +// +//========================================================================== + +void HMISong::ProcessInitialMetaEvents () +{ + TrackInfo *track; + int i; + BYTE event; + DWORD len; + + for (i = 0; i < NumTracks; ++i) + { + track = &Tracks[i]; + while (!track->Finished && + track->TrackP < track->MaxTrackP - 4 && + track->TrackBegin[track->TrackP] == 0 && + track->TrackBegin[track->TrackP+1] == 0xFF) + { + event = track->TrackBegin[track->TrackP+2]; + track->TrackP += 3; + len = track->ReadVarLen (); + if (track->TrackP + len <= track->MaxTrackP) + { + switch (event) + { + case MIDI_META_EOT: + track->Finished = true; + break; + + case MIDI_META_TEMPO: + SetTempo( + (track->TrackBegin[track->TrackP+0]<<16) | + (track->TrackBegin[track->TrackP+1]<<8) | + (track->TrackBegin[track->TrackP+2]) + ); + break; + } + } + track->TrackP += len; + } + if (track->TrackP >= track->MaxTrackP - 4) + { + track->Finished = true; + } + } +} + +//========================================================================== +// +// HMISong :: TrackInfo :: ReadVarLen +// +// Reads a variable-length SMF number. +// +//========================================================================== + +DWORD HMISong::TrackInfo::ReadVarLen () +{ + DWORD time = 0, t = 0x80; + + while ((t & 0x80) && TrackP < MaxTrackP) + { + t = TrackBegin[TrackP++]; + time = (time << 7) | (t & 127); + } + return time; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: AddNoteOff +// +//========================================================================== + +void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) +{ + unsigned int i = Reserve(1); + while (i > 0 && (*this)[Parent(i)].Delay > delay) + { + (*this)[i] = (*this)[Parent(i)]; + i = Parent(i); + } + (*this)[i].Delay = delay; + (*this)[i].Channel = channel; + (*this)[i].Key = key; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: Pop +// +//========================================================================== + +bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item) +{ + item = (*this)[0]; + if (TArray::Pop((*this)[0])) + { + Heapify(); + return true; + } + return false; +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: AdvanceTime +// +//========================================================================== + +void HMISong::NoteOffQueue::AdvanceTime(DWORD time) +{ + // Because the time is decreasing by the same amount for every entry, + // the heap property is maintained. + for (unsigned int i = 0; i < Size(); ++i) + { + assert((*this)[i].Delay >= time); + (*this)[i].Delay -= time; + } +} + +//========================================================================== +// +// HMISong :: NoteOffQueue :: Heapify +// +//========================================================================== + +void HMISong::NoteOffQueue::Heapify() +{ + unsigned int i = 0; + for (;;) + { + unsigned int l = Left(i); + unsigned int r = Right(i); + unsigned int smallest = i; + if (l < Size() && (*this)[l].Delay < (*this)[i].Delay) + { + smallest = l; + } + if (r < Size() && (*this)[r].Delay < (*this)[smallest].Delay) + { + smallest = r; + } + if (smallest == i) + { + break; + } + swapvalues((*this)[i], (*this)[smallest]); + i = smallest; + } +} + +//========================================================================== +// +// HMISong :: FindNextDue +// +// Scans every track for the next event to play. Returns NULL if all events +// have been consumed. +// +//========================================================================== + +HMISong::TrackInfo *HMISong::FindNextDue () +{ + TrackInfo *track; + DWORD best; + int i; + + if (TrackDue != FakeTrack && !TrackDue->Finished && TrackDue->Delay == 0) + { + return TrackDue; + } + + // Check regular tracks. + track = NULL; + best = 0xFFFFFFFF; + for (i = 0; i < NumTracks; ++i) + { + if (Tracks[i].Enabled && !Tracks[i].Finished && Tracks[i].Delay < best) + { + best = Tracks[i].Delay; + track = &Tracks[i]; + } + } + // Check automatic note-offs. + if (NoteOffs.Size() != 0 && NoteOffs[0].Delay <= best) + { + FakeTrack->Delay = NoteOffs[0].Delay; + return FakeTrack; + } + return track; +} + + +//========================================================================== +// +// HMISong :: SetTempo +// +// Sets the tempo from a track's initial meta events. +// +//========================================================================== + +void HMISong::SetTempo(int new_tempo) +{ + if (0 == MIDI->SetTempo(new_tempo)) + { + Tempo = new_tempo; + } +} + +//========================================================================== +// +// HMISong :: GetOPLDumper +// +//========================================================================== + +MusInfo *HMISong::GetOPLDumper(const char *filename) +{ + return new HMISong(this, filename, MIDI_OPL); +} + +//========================================================================== +// +// HMISong :: GetWaveDumper +// +//========================================================================== + +MusInfo *HMISong::GetWaveDumper(const char *filename, int rate) +{ + return new HMISong(this, filename, MIDI_GUS); +} + +//========================================================================== +// +// HMISong File Dumping Constructor +// +//========================================================================== + +HMISong::HMISong(const HMISong *original, const char *filename, EMIDIDevice type) +: MIDIStreamer(filename, type) +{ + SongLen = original->SongLen; + MusHeader = new BYTE[original->SongLen]; + memcpy(MusHeader, original->MusHeader, original->SongLen); + NumTracks = original->NumTracks; + Division = original->Division; + Tempo = InitialTempo = original->InitialTempo; + Tracks = new TrackInfo[NumTracks]; + for (int i = 0; i < NumTracks; ++i) + { + TrackInfo *newtrack = &Tracks[i]; + const TrackInfo *oldtrack = &original->Tracks[i]; + + newtrack->TrackBegin = MusHeader + (oldtrack->TrackBegin - original->MusHeader); + newtrack->TrackP = 0; + newtrack->MaxTrackP = oldtrack->MaxTrackP; + } +} diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index 77c8dde9c..264349b42 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 = "Sound System"; - 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, "Sound System", 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 = "Sound System"; - 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. Sound System\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index ea725d0da..6e2acb32d 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -207,7 +207,7 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) BYTE *buf; - if (file!=NULL) + if (file != NULL) { buf = new BYTE[len]; fread (buf, 1, len, file); @@ -217,18 +217,8 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) buf = musiccache; } - - // The file type has already been checked before this class instance was - // created, so we only need to check one character to determine if this - // is a MUS or MIDI file and write it to disk as appropriate. - if (buf[1] == 'T') - { - success = (fwrite (buf, 1, len, f) == (size_t)len); - } - else - { - success = ProduceMIDI (buf, 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 5ed94c34c..454ff0d3f 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -49,6 +49,8 @@ // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- +static void WriteVarLen (TArray &file, DWORD value); + // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR(Float, snd_musicvolume) @@ -57,8 +59,21 @@ EXTERN_CVAR(Float, snd_musicvolume) extern UINT mididevice; #endif +extern char MIDI_EventLengths[7]; + // PRIVATE DATA DEFINITIONS ------------------------------------------------ +static const BYTE StaticMIDIhead[] = +{ + 'M','T','h','d', 0, 0, 0, 6, + 0, 0, // format 0: only one track + 0, 1, // yes, there is really only one track + 0, 0, // divisions (filled in) + 'M','T','r','k', 0, 0, 0, 0, + // The first event sets the tempo (filled in) + 0, 255, 81, 3, 0, 0, 0 +}; + // PUBLIC DATA DEFINITIONS ------------------------------------------------- // CODE -------------------------------------------------------------------- @@ -172,7 +187,7 @@ bool MIDIStreamer::IsValid() const // //========================================================================== -void MIDIStreamer::CheckCaps() +void MIDIStreamer::CheckCaps(int tech) { } @@ -200,7 +215,7 @@ void MIDIStreamer::Play(bool looping, int subsong) { MIDI = new OPLDumperMIDIDevice(DumpFilename); } - else if (DeviceType == MIDI_Timidity) + else if (DeviceType == MIDI_GUS) { MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0); } @@ -215,27 +230,38 @@ void MIDIStreamer::Play(bool looping, int subsong) assert(0); // Intentional fall-through for non-Windows systems. - case MIDI_Timidity: +#ifdef HAVE_FLUIDSYNTH + case MIDI_Fluid: + MIDI = new FluidSynthMIDIDevice; + break; +#endif + + case MIDI_GUS: MIDI = new TimidityMIDIDevice; break; case MIDI_OPL: MIDI = new OPLMIDIDevice; break; + + default: + MIDI = NULL; + break; } #ifndef _WIN32 - assert(MIDI->NeedThreadedCallback() == false); + assert(MIDI == NULL || MIDI->NeedThreadedCallback() == false); #endif - if (0 != MIDI->Open(Callback, this)) + if (MIDI == NULL || 0 != MIDI->Open(Callback, this)) { Printf(PRINT_BOLD, "Could not open MIDI out device\n"); return; } - CheckCaps(); + CheckCaps(MIDI->GetTechnology()); Precache(); + IgnoreLoops = false; // Set time division and tempo. if (0 != MIDI->SetTimeDiv(Division) || @@ -435,6 +461,48 @@ void MIDIStreamer::TimidityVolumeChanged() } } +//========================================================================== +// +// MIDIStreamer :: FluidSettingInt +// +//========================================================================== + +void MIDIStreamer::FluidSettingInt(const char *setting, int value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingInt(setting, value); + } +} + +//========================================================================== +// +// MIDIStreamer :: FluidSettingNum +// +//========================================================================== + +void MIDIStreamer::FluidSettingNum(const char *setting, double value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingNum(setting, value); + } +} + +//========================================================================== +// +// MIDIDeviceStreamer :: FluidSettingStr +// +//========================================================================== + +void MIDIStreamer::FluidSettingStr(const char *setting, const char *value) +{ + if (MIDI != NULL) + { + MIDI->FluidSettingStr(setting, value); + } +} + //========================================================================== // @@ -466,7 +534,7 @@ void MIDIStreamer::OutputVolume (DWORD volume) int MIDIStreamer::VolumeControllerChange(int channel, int volume) { ChannelVolumes[channel] = volume; - return ((volume + 1) * Volume) >> 16; + return IgnoreLoops ? volume : ((volume + 1) * Volume) >> 16; } //========================================================================== @@ -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 b89d9fd67..44a0d45e6 100644 --- a/src/sound/music_mus_midiout.cpp +++ b/src/sound/music_mus_midiout.cpp @@ -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 00d816c66..0f01be75b 100644 --- a/src/sound/music_midi_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -86,11 +86,11 @@ struct MIDISong2::TrackInfo // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; -static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // PUBLIC DATA DEFINITIONS ------------------------------------------------- +char MIDI_EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 }; +char MIDI_CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + // CODE -------------------------------------------------------------------- //========================================================================== @@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 () // //========================================================================== -void MIDISong2::CheckCaps() +void MIDISong2::CheckCaps(int tech) { - int tech = MIDI->GetTechnology(); - DesignationMask = 0xFF0F; if (tech == MOD_FMSYNTH) { @@ -389,10 +387,10 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) // Normal short message if ((event & 0xF0) == 0xF0) { - if (CommonLengths[event & 15] > 0) + if (MIDI_CommonLengths[event & 15] > 0) { data1 = track->TrackBegin[track->TrackP++]; - if (CommonLengths[event & 15] > 1) + if (MIDI_CommonLengths[event & 15] > 1) { data2 = track->TrackBegin[track->TrackP++]; } @@ -411,7 +409,7 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) CHECK_FINISHED - if (EventLengths[(event&0x70)>>4] == 2) + if (MIDI_EventLengths[(event&0x70)>>4] == 2) { data2 = track->TrackBegin[track->TrackP++]; } @@ -500,10 +498,13 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) break; case 116: // EMIDI Loop Begin - track->LoopBegin = track->TrackP; - track->LoopDelay = 0; - track->LoopCount = data2; - track->LoopFinished = track->Finished; + if (!IgnoreLoops) + { + track->LoopBegin = track->TrackP; + track->LoopDelay = 0; + track->LoopCount = data2; + track->LoopFinished = track->Finished; + } event = MIDI_META; break; @@ -529,12 +530,15 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay) break; case 118: // EMIDI Global Loop Begin - for (i = 0; i < NumTracks; ++i) + if (!IgnoreLoops) { - Tracks[i].LoopBegin = Tracks[i].TrackP; - Tracks[i].LoopDelay = Tracks[i].Delay; - Tracks[i].LoopCount = data2; - Tracks[i].LoopFinished = Tracks[i].Finished; + for (i = 0; i < NumTracks; ++i) + { + Tracks[i].LoopBegin = Tracks[i].TrackP; + Tracks[i].LoopDelay = Tracks[i].Delay; + Tracks[i].LoopCount = data2; + Tracks[i].LoopFinished = Tracks[i].Finished; + } } event = MIDI_META; break; @@ -709,7 +713,7 @@ DWORD MIDISong2::TrackInfo::ReadVarLen () //========================================================================== // -// MIDISong2 :: TrackInfo :: FindNextDue +// MIDISong2 :: FindNextDue // // Scans every track for the next event to play. Returns NULL if all events // have been consumed. @@ -776,135 +780,6 @@ void MIDISong2::SetTempo(int new_tempo) } } -//========================================================================== -// -// MIDISong2 :: Precache -// -// Scans each track for program change events on normal channels and note on -// events on channel 10. Does not care about bank selects, since they're -// unlikely to appear in a song aimed at Doom. -// -//========================================================================== - -void MIDISong2::Precache() -{ - // This array keeps track of instruments that are used. The first 128 - // entries are for melodic instruments. The second 128 are for - // percussion. - BYTE found_instruments[256] = { 0, }; - BYTE found_banks[256] = { 0, }; - bool multiple_banks = false; - int i, j; - - DoRestart(); - found_banks[0] = true; // Bank 0 is always used. - found_banks[128] = true; - for (i = 0; i < NumTracks; ++i) - { - TrackInfo *track = &Tracks[i]; - BYTE running_status = 0; - BYTE ev, data1, data2, command, channel; - int len; - - data2 = 0; // Silence, GCC - while (track->TrackP < track->MaxTrackP) - { - ev = track->TrackBegin[track->TrackP++]; - command = ev & 0xF0; - - if (ev == MIDI_META) - { - track->TrackP++; - len = track->ReadVarLen(); - track->TrackP += len; - } - else if (ev == MIDI_SYSEX || ev == MIDI_SYSEXEND) - { - len = track->ReadVarLen(); - track->TrackP += len; - } - else if (command == 0xF0) - { - track->TrackP += CommonLengths[ev & 0x0F]; - } - else - { - if ((ev & 0x80) == 0) - { // Use running status. - data1 = ev; - ev = running_status; - } - else - { // Store new running status. - running_status = ev; - data1 = track->TrackBegin[track->TrackP++]; - } - command = ev & 0x70; - channel = ev & 0x0F; - if (EventLengths[command >> 4] == 2) - { - data2 = track->TrackBegin[track->TrackP++]; - } - if (channel != 9 && command == (MIDI_PRGMCHANGE & 0x70)) - { - found_instruments[data1 & 127] = true; - } - else if (channel == 9 && command == (MIDI_PRGMCHANGE & 0x70) && data1 != 0) - { // On a percussion channel, program change also serves as bank select. - multiple_banks = true; - found_banks[data1 | 128] = true; - } - else if (channel == 9 && command == (MIDI_NOTEON & 0x70) && data2 != 0) - { - found_instruments[data1 | 128] = true; - } - else if (command == (MIDI_CTRLCHANGE & 0x70) && data1 == 0 && data2 != 0) - { - multiple_banks = true; - if (channel == 9) - { - found_banks[data2 | 128] = true; - } - else - { - found_banks[data2 & 127] = true; - } - } - } - track->ReadVarLen(); // Skip delay. - } - } - DoRestart(); - - // Now pack everything into a contiguous region for the PrecacheInstruments call(). - TArray packed; - - for (i = 0; i < 256; ++i) - { - if (found_instruments[i]) - { - WORD packnum = (i & 127) | ((i & 128) << 7); - if (!multiple_banks) - { - packed.Push(packnum); - } - else - { // In order to avoid having to multiplex tracks in a type 1 file, - // precache every used instrument in every used bank, even if not - // all combinations are actually used. - for (j = 0; j < 128; ++j) - { - if (found_banks[j + (i & 128)]) - { - packed.Push(packnum | (j << 7)); - } - } - } - } - } - MIDI->PrecacheInstruments(&packed[0], packed.Size()); -} - //========================================================================== // // MIDISong2 :: GetOPLDumper @@ -924,7 +799,7 @@ MusInfo *MIDISong2::GetOPLDumper(const char *filename) MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate) { - return new MIDISong2(this, filename, MIDI_Timidity); + return new MIDISong2(this, filename, MIDI_GUS); } //========================================================================== diff --git a/src/sound/music_softsynth_mididevice.cpp b/src/sound/music_softsynth_mididevice.cpp new file mode 100644 index 000000000..9d2e69298 --- /dev/null +++ b/src/sound/music_softsynth_mididevice.cpp @@ -0,0 +1,498 @@ +/* +** music_softsynth_mididevice.cpp +** Common base clase for software synthesis MIDI devices. +** +**--------------------------------------------------------------------------- +** Copyright 2008-2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(Bool, synth_watch, false, 0) + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// SoftSynthMIDIDevice Constructor +// +//========================================================================== + +SoftSynthMIDIDevice::SoftSynthMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + SampleRate = GSnd != NULL ? (int)GSnd->GetOutputRate() : 44100; +} + +//========================================================================== +// +// SoftSynthMIDIDevice Destructor +// +//========================================================================== + +SoftSynthMIDIDevice::~SoftSynthMIDIDevice() +{ + Close(); +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: OpenStream +// +//========================================================================== + +int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + Stream = GSnd->CreateStream(FillStream, (SampleRate / chunks) * 4, SoundStream::Float | flags, SampleRate, this); + if (Stream == NULL) + { + return 2; + } + + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Close +// +//========================================================================== + +void SoftSynthMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + Started = false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: IsOpen +// +//========================================================================== + +bool SoftSynthMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: GetTechnology +// +//========================================================================== + +int SoftSynthMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTempo +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void SoftSynthMIDIDevice::CalcTickRate() +{ + SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Resume +// +//========================================================================== + +int SoftSynthMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1)) + { + Started = true; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Stop +// +//========================================================================== + +void SoftSynthMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + } +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOutSync +// +// This version is called from the main game thread and needs to +// synchronize with the player thread. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOutSync(MIDIHDR *header) +{ + CritSec.Enter(); + StreamOut(header); + CritSec.Leave(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOut +// +// This version is called from the player thread so does not need to +// arbitrate for access to the Events pointer. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOut(MIDIHDR *header) +{ + header->lpNext = NULL; + if (Events == NULL) + { + Events = header; + NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; + Position = 0; + } + else + { + MIDIHDR **p; + + for (p = &Events; *p != NULL; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PrepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FakeVolume +// +// Since the softsynth output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool SoftSynthMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: NeedThreadedCallabck +// +// We can service the callback directly rather than using a separate +// thread. +// +//========================================================================== + +bool SoftSynthMIDIDevice::NeedThreadedCallback() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Pause +// +//========================================================================== + +bool SoftSynthMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int SoftSynthMIDIDevice::PlayTick() +{ + DWORD delay = 0; + + while (delay == 0 && Events != NULL) + { + DWORD *event = (DWORD *)(Events->lpData + Position); + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + SetTempo(MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { + HandleLongEvent((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { // Short MIDI event + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + HandleEvent(status, parm1, parm2); + + if (synth_watch) + { + static const char *const commands[8] = + { + "Note off", + "Note on", + "Poly press", + "Ctrl change", + "Prgm change", + "Chan press", + "Pitch bend", + "SysEx" + }; + char buffer[128]; + mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); +#ifdef _WIN32 + OutputDebugString(buffer); +#else + fputs(buffer, stderr); +#endif + } + } + + // Advance to next event. + if (event[2] < 0x80000000) + { // Short message + Position += 12; + } + else + { // Long message + Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); + } + + // Did we use up this buffer? + if (Position >= Events->dwBytesRecorded) + { + Events = Events->lpNext; + Position = 0; + + if (Callback != NULL) + { + Callback(MOM_DONE, CallbackData, 0, 0); + } + } + + if (Events == NULL) + { // No more events. Just return something to keep the song playing + // while we wait for more to be submitted. + return int(Division); + } + + delay = *(DWORD *)(Events->lpData + Position); + } + return delay; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: ServiceStream +// +//========================================================================== + +bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes) +{ + float *samples = (float *)buff; + float *samples1; + int numsamples = numbytes / sizeof(float) / 2; + bool prev_ended = false; + bool res = true; + + samples1 = samples; + memset(buff, 0, numbytes); + + CritSec.Enter(); + while (Events != NULL && numsamples > 0) + { + double ticky = NextTickIn; + int tick_in = int(NextTickIn); + int samplesleft = MIN(numsamples, tick_in); + + if (samplesleft > 0) + { + ComputeOutput(samples1, samplesleft); + assert(NextTickIn == ticky); + NextTickIn -= samplesleft; + assert(NextTickIn >= 0); + numsamples -= samplesleft; + samples1 += samplesleft * 2; + } + + if (NextTickIn < 1) + { + int next = PlayTick(); + assert(next >= 0); + if (next == 0) + { // end of song + if (numsamples > 0) + { + ComputeOutput(samples1, numsamples); + } + res = false; + break; + } + else + { + NextTickIn += SamplesPerTick * next; + assert(NextTickIn >= 0); + } + } + } + + if (Events == NULL) + { + res = false; + } + CritSec.Leave(); + return res; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FillStream static +// +//========================================================================== + +bool SoftSynthMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + SoftSynthMIDIDevice *device = (SoftSynthMIDIDevice *)userdata; + return device->ServiceStream(buff, len); +} diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp index a13f46e61..9e5be625b 100644 --- a/src/sound/music_timidity_mididevice.cpp +++ b/src/sound/music_timidity_mididevice.cpp @@ -78,8 +78,6 @@ struct FmtChunk // PUBLIC DATA DEFINITIONS ------------------------------------------------- -CVAR(Bool, timidity_watch, false, 0) - // CODE -------------------------------------------------------------------- //========================================================================== @@ -90,33 +88,8 @@ CVAR(Bool, timidity_watch, false, 0) TimidityMIDIDevice::TimidityMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; Renderer = NULL; - Renderer = new Timidity::Renderer(GSnd->GetOutputRate()); -} - -//========================================================================== -// -// TimidityMIDIDevice Constructor with rate parameter -// -//========================================================================== - -TimidityMIDIDevice::TimidityMIDIDevice(int rate) -{ - // Need to support multiple instances with different playback rates - // before we can use this parameter. - rate = (int)GSnd->GetOutputRate(); - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - Renderer = NULL; - Renderer = new Timidity::Renderer((float)rate); + Renderer = new Timidity::Renderer((float)SampleRate); } //========================================================================== @@ -144,261 +117,12 @@ TimidityMIDIDevice::~TimidityMIDIDevice() int TimidityMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) { - Stream = GSnd->CreateStream(FillStream, int(Renderer->rate / 2) * 4, - SoundStream::Float, int(Renderer->rate), this); - if (Stream == NULL) + int ret = OpenStream(2, 0, callback, userdata); + if (ret == 0) { - return 2; + Renderer->Reset(); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - Renderer->Reset(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Close -// -//========================================================================== - -void TimidityMIDIDevice::Close() -{ - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } - Started = false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: IsOpen -// -//========================================================================== - -bool TimidityMIDIDevice::IsOpen() const -{ - return Stream != NULL; -} - -//========================================================================== -// -// TimidityMIDIDevice :: GetTechnology -// -//========================================================================== - -int TimidityMIDIDevice::GetTechnology() const -{ - return MOD_SWSYNTH; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTempo -// -//========================================================================== - -int TimidityMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int TimidityMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: CalcTickRate -// -// Tempo is the number of microseconds per quarter note. -// Division is the number of ticks per quarter note. -// -//========================================================================== - -void TimidityMIDIDevice::CalcTickRate() -{ - SamplesPerTick = Renderer->rate / (1000000.0 / Tempo) / Division; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Resume -// -//========================================================================== - -int TimidityMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1/*timidity_mastervolume*/)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Stop -// -//========================================================================== - -void TimidityMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - CritSec.Enter(); - StreamOut(header); - CritSec.Leave(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: PrepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: FakeVolume -// -// Since the TiMidity output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool TimidityMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool TimidityMIDIDevice::NeedThreadedCallback() -{ - return false; -} - - -//========================================================================== -// -// TimidityMIDIDevice :: TimidityVolumeChanged -// -//========================================================================== - -void TimidityMIDIDevice::TimidityVolumeChanged() -{ - /* - if (Stream != NULL) - { - Stream->SetVolume(timidity_mastervolume); - } - */ -} - -//========================================================================== -// -// TimidityMIDIDevice :: Pause -// -//========================================================================== - -bool TimidityMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + return ret; } //========================================================================== @@ -423,164 +147,35 @@ void TimidityMIDIDevice::PrecacheInstruments(const WORD *instruments, int count) //========================================================================== // -// TimidityMIDIDevice :: PlayTick -// -// event[0] = delta time -// event[1] = unused -// event[2] = event +// TimidityMIDIDevice :: HandleEvent // //========================================================================== -int TimidityMIDIDevice::PlayTick() +void TimidityMIDIDevice::HandleEvent(int status, int parm1, int parm2) { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { - Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - Renderer->HandleEvent(status, parm1, parm2); - - if (timidity_watch) - { - static const char *const commands[8] = - { - "Note off", - "Note on", - "Poly press", - "Ctrl change", - "Prgm change", - "Chan press", - "Pitch bend", - "SysEx" - }; -#ifdef _WIN32 - char buffer[128]; - mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); - OutputDebugString(buffer); -#else - //fprintf(stderr, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); -#endif - } - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + Renderer->HandleEvent(status, parm1, parm2); } //========================================================================== // -// TimidityMIDIDevice :: ServiceStream +// TimidityMIDIDevice :: HandleLongEvent // //========================================================================== -bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes) +void TimidityMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - float *samples = (float *)buff; - float *samples1; - int numsamples = numbytes / sizeof(float) / 2; - bool prev_ended = false; - bool res = true; - - samples1 = samples; - memset(buff, 0, numbytes); - - CritSec.Enter(); - while (Events != NULL && numsamples > 0) - { - double ticky = NextTickIn; - int tick_in = int(NextTickIn); - int samplesleft = MIN(numsamples, tick_in); - - if (samplesleft > 0) - { - Renderer->ComputeOutput(samples1, samplesleft); - assert(NextTickIn == ticky); - NextTickIn -= samplesleft; - assert(NextTickIn >= 0); - numsamples -= samplesleft; - samples1 += samplesleft * 2; - } - - if (NextTickIn < 1) - { - int next = PlayTick(); - assert(next >= 0); - if (next == 0) - { // end of song - if (numsamples > 0) - { - Renderer->ComputeOutput(samples1, numsamples); - } - res = false; - break; - } - else - { - NextTickIn += SamplesPerTick * next; - assert(NextTickIn >= 0); - } - } - } - if (Events == NULL) - { - res = false; - } - CritSec.Leave(); - return res; + Renderer->HandleLongMessage(data, len); } //========================================================================== // -// TimidityMIDIDevice :: FillStream static +// TimidityMIDIDevice :: ComputeOutput // //========================================================================== -bool TimidityMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void TimidityMIDIDevice::ComputeOutput(float *buffer, int len) { - TimidityMIDIDevice *device = (TimidityMIDIDevice *)userdata; - return device->ServiceStream(buff, len); + Renderer->ComputeOutput(buffer, len); } //========================================================================== diff --git a/src/tarray.h b/src/tarray.h index 9ee5eff2b..3eab7be33 100644 --- a/src/tarray.h +++ b/src/tarray.h @@ -141,6 +141,15 @@ public: ::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/textures/bitmap.h b/src/textures/bitmap.h index 4bd7f32bb..da039fd0f 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 90c52dd63..4debfa24a 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 d1fe3e8f5..4e30802c4 100644 --- a/src/textures/pngtexture.cpp +++ b/src/textures/pngtexture.cpp @@ -202,7 +202,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename { DWORD palette[256]; BYTE pngpal[256][3]; - }; + } p; BYTE trans[256]; bool havetRNS = false; DWORD len, id; @@ -260,14 +260,14 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case MAKE_ID('P','L','T','E'): PaletteSize = MIN (len / 3, 256); - lump.Read (pngpal, PaletteSize * 3); + lump.Read (p.pngpal, PaletteSize * 3); if (PaletteSize * 3 != (int)len) { lump.Seek (len - PaletteSize * 3, SEEK_CUR); } for (i = PaletteSize - 1; i >= 0; --i) { - palette[i] = MAKERGB(pngpal[i][0], pngpal[i][1], pngpal[i][2]); + p.palette[i] = MAKERGB(p.pngpal[i][0], p.pngpal[i][1], p.pngpal[i][2]); } break; @@ -314,7 +314,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case 3: // Paletted PaletteMap = new BYTE[PaletteSize]; - GPalette.MakeRemap (palette, PaletteMap, trans, PaletteSize); + GPalette.MakeRemap (p.palette, PaletteMap, trans, PaletteSize); for (i = 0; i < PaletteSize; ++i) { if (trans[i] == 0) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index aacb45b18..b304e638f 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -996,6 +996,7 @@ enum FB_Flags FBF_NORANDOM = 2, FBF_EXPLICITANGLE = 4, FBF_NOPITCH = 8, + FBF_NOFLASH = 16, }; DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) @@ -1025,7 +1026,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireBullets) if (Range == 0) Range = PLAYERMISSILERANGE; - static_cast(self)->PlayAttacking2 (); + if (!(Flags & FBF_NOFLASH)) static_cast(self)->PlayAttacking2 (); if (!(Flags & FBF_NOPITCH)) bslope = P_BulletSlope(self); bangle = self->angle; diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index ffe287035..d41d79891 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); } //========================================================================== @@ -2185,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/v_draw.cpp b/src/v_draw.cpp index 3c2ed74d0..f22c9c928 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())) @@ -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 ea6feb149..3815c8ae4 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 3149f5df5..7761a3dec 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 */ diff --git a/src/v_video.cpp b/src/v_video.cpp index 1e8993e74..c51f13cfc 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" @@ -63,6 +62,7 @@ #include "colormatcher.h" #include "v_palette.h" #include "r_sky.h" +#include "menu/menu.h" IMPLEMENT_ABSTRACT_CLASS (DCanvas) diff --git a/src/v_video.h b/src/v_video.h index 77a3f8a65..8d492c7b4 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); @@ -406,6 +412,7 @@ public: virtual void WipeEndScreen(); virtual bool WipeDo(int ticks); virtual void WipeCleanup(); + virtual int GetPixelDoubling() const { return 0; } uint32 GetLastFPS() const { return LastCount; } @@ -483,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/w_wad.cpp b/src/w_wad.cpp index 5727c8bcc..13e7ff14a 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -70,6 +70,7 @@ struct FWadCollection::LumpRecord }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- +extern bool nospriterename; // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -759,7 +760,7 @@ void FWadCollection::RenameSprites () } } - renameAll = !!Args->CheckParm ("-oldsprites"); + renameAll = !!Args->CheckParm ("-oldsprites") || nospriterename; for (DWORD i = 0; i < LumpInfo.Size(); i++) { diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index b2c8e1bfa..7453571e5 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; @@ -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; @@ -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; diff --git a/src/win32/fb_d3d9_wipe.cpp b/src/win32/fb_d3d9_wipe.cpp index 9024fa804..3407136aa 100644 --- a/src/win32/fb_d3d9_wipe.cpp +++ b/src/win32/fb_d3d9_wipe.cpp @@ -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/i_crash.cpp b/src/win32/i_crash.cpp index 727e4b7ed..5e4dfb1c9 100644 --- a/src/win32/i_crash.cpp +++ b/src/win32/i_crash.cpp @@ -69,6 +69,54 @@ #include #include +#if defined(_WIN64) && defined(__GNUC__) +struct KNONVOLATILE_CONTEXT_POINTERS { + union { + PDWORD64 IntegerContext[16]; + struct { + PDWORD64 Rax; + PDWORD64 Rcx; + PDWORD64 Rdx; + PDWORD64 Rbx; + PDWORD64 Rsp; + PDWORD64 Rbp; + PDWORD64 Rsi; + PDWORD64 Rdi; + PDWORD64 R8; + PDWORD64 R9; + PDWORD64 R10; + PDWORD64 R11; + PDWORD64 R12; + PDWORD64 R13; + PDWORD64 R14; + PDWORD64 R15; + }; + }; +}; +typedef +EXCEPTION_DISPOSITION +NTAPI +EXCEPTION_ROUTINE ( + struct _EXCEPTION_RECORD *ExceptionRecord, + PVOID EstablisherFrame, + struct _CONTEXT *ContextRecord, + PVOID DispatcherContext + ); +NTSYSAPI +EXCEPTION_ROUTINE * +NTAPI +RtlVirtualUnwind ( + DWORD HandlerType, + DWORD64 ImageBase, + DWORD64 ControlPc, + PRUNTIME_FUNCTION FunctionEntry, + PCONTEXT ContextRecord, + PVOID *HandlerData, + PDWORD64 EstablisherFrame, + KNONVOLATILE_CONTEXT_POINTERS *ContextPointers + ); +#endif + // MACROS ------------------------------------------------------------------ #define REMOTE_HOST "localhost" @@ -1312,7 +1360,7 @@ static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD } // If we reach a RIP of zero, this means we've walked off the end of // the call stack and are done. - if (context.Rip == NULL) + if (context.Rip == 0) { break; } diff --git a/src/win32/i_dijoy.cpp b/src/win32/i_dijoy.cpp index 89ddd1600..b78fea64e 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; diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index d01547dae..49915bf29 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,6 +144,7 @@ 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; @@ -191,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 }; @@ -228,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) { @@ -278,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; @@ -290,11 +304,39 @@ 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; + } + + { + int shift = screen? screen->GetPixelDoubling() : 0; + ev.data1 = LOWORD(lParam) >> shift; + ev.data2 = HIWORD(lParam) >> shift; + } + + 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; @@ -385,6 +427,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: @@ -425,6 +476,10 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 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: diff --git a/src/win32/i_input.h b/src/win32/i_input.h index 1c21d78c3..b0d409844 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_mouse.cpp b/src/win32/i_mouse.cpp index af595dea5..0fa46c1db 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 ------------------------------------------------------------------ @@ -265,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); @@ -485,6 +480,7 @@ static FMouse *CreateRawMouse() FRawMouse::FRawMouse() { Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -591,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; } @@ -678,6 +674,7 @@ FDInputMouse::FDInputMouse() { Device = NULL; Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -804,7 +801,7 @@ void FDInputMouse::ProcessInput() dx = 0; dy = 0; - if (!Grabbed) + if (!Grabbed || !use_mouse) return; event_t ev = { 0 }; @@ -903,6 +900,7 @@ FWin32Mouse::FWin32Mouse() { GetCursorPos(&UngrabbedPointerPos); Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -945,7 +943,7 @@ void FWin32Mouse::ProcessInput() POINT pt; int x, y; - if (!Grabbed || !GetCursorPos(&pt)) + if (!Grabbed || !use_mouse || !GetCursorPos(&pt)) { return; } @@ -1003,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)); diff --git a/src/win32/i_rawps2.cpp b/src/win32/i_rawps2.cpp index 68803fefb..cab2844a6 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 c8b496656..f2178ffbf 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 -------------------------------------------------------------------- //========================================================================== @@ -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 66b2bac7d..1a5e755e3 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 e3ad1eed0..5a02d7af4 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 8fe844f70..1f0838ef2 100644 --- a/src/win32/st_start.cpp +++ b/src/win32/st_start.cpp @@ -656,23 +656,23 @@ FHexenStartupScreen::FHexenStartupScreen(int max_progress, HRESULT &hr) { RGBQUAD color; DWORD quad; - }; + } c; Wads.ReadLump (startup_lump, startup_screen); - color.rgbReserved = 0; + c.color.rgbReserved = 0; StartupBitmap = ST_Util_CreateBitmap (640, 480, 4); // Initialize the bitmap palette. for (int i = 0; i < 16; ++i) { - color.rgbRed = startup_screen[i*3+0]; - color.rgbGreen = startup_screen[i*3+1]; - color.rgbBlue = startup_screen[i*3+2]; + c.color.rgbRed = startup_screen[i*3+0]; + c.color.rgbGreen = startup_screen[i*3+1]; + c.color.rgbBlue = startup_screen[i*3+2]; // Convert from 6-bit per component to 8-bit per component. - quad = (quad << 2) | ((quad >> 4) & 0x03030303); - StartupBitmap->bmiColors[i] = color; + c.quad = (c.quad << 2) | ((c.quad >> 4) & 0x03030303); + StartupBitmap->bmiColors[i] = c.color; } // Fill in the bitmap data. Convert to chunky, because I can't figure out diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h index e46c392c3..e3710b234 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/wadsrc/static/actors/chex/chexweapons.txt b/wadsrc/static/actors/chex/chexweapons.txt index 4fa7eec56..97043f2ca 100644 --- a/wadsrc/static/actors/chex/chexweapons.txt +++ b/wadsrc/static/actors/chex/chexweapons.txt @@ -4,6 +4,7 @@ actor Bootspoon : Fist { game Chex obituary "$OB_MPSPOON" + Tag "$TAG_SPOON" } actor SuperBootspork : Chainsaw 2005 @@ -11,6 +12,7 @@ actor SuperBootspork : Chainsaw 2005 game Chex obituary "$OB_MPBOOTSPORK" Inventory.PickupMessage "$GOTSUPERBOOTSPORK" + Tag "$TAG_SPORK" } actor MiniZorcher : Pistol @@ -18,6 +20,7 @@ actor MiniZorcher : Pistol game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTMINIZORCHER" + Tag "$TAG_MINIZORCHER" states { Spawn: @@ -30,6 +33,7 @@ actor LargeZorcher : Shotgun 2001 game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTLARGEZORCHER" + Tag "$TAG_LARGEZORCHER" } actor SuperLargeZorcher : SuperShotgun 82 @@ -37,6 +41,7 @@ actor SuperLargeZorcher : SuperShotgun 82 game Chex obituary "$OB_MPMEGAZORCH" inventory.pickupmessage "$GOTSUPERLARGEZORCHER" + Tag "$TAG_SUPERLARGEZORCHER" } actor RapidZorcher : Chaingun 2002 @@ -44,6 +49,7 @@ actor RapidZorcher : Chaingun 2002 game Chex obituary "$OB_MPRAPIDZORCH" inventory.pickupmessage "$GOTRAPIDZORCHER" + Tag "$TAG_RAPIDZORCHER" } actor ZorchPropulsor : RocketLauncher 2003 @@ -51,6 +57,7 @@ actor ZorchPropulsor : RocketLauncher 2003 game Chex obituary "" inventory.pickupmessage "$GOTZORCHPROPULSOR" + Tag "$TAG_ZORCHPROPULSOR" States { Fire: @@ -75,6 +82,7 @@ actor PhasingZorcher : PlasmaRifle 2004 game Chex obituary "" inventory.pickupmessage "$GOTPHASINGZORCHER" + Tag "$TAG_PHASINGZORCHER" states { Fire: @@ -104,6 +112,7 @@ actor LAZDevice : BFG9000 2006 game Chex obituary "" inventory.pickupmessage "$GOTLAZDEVICE" + Tag "$TAG_LAZDEVICE" states { Fire: diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 05e77df2a..d6fdd42de 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -24,6 +24,7 @@ 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; diff --git a/wadsrc/static/actors/doom/doomweapons.txt b/wadsrc/static/actors/doom/doomweapons.txt index 076559c0e..b748f8611 100644 --- a/wadsrc/static/actors/doom/doomweapons.txt +++ b/wadsrc/static/actors/doom/doomweapons.txt @@ -21,6 +21,7 @@ ACTOR Fist : Weapon Weapon.SelectionOrder 3700 Weapon.Kickback 100 Obituary "$OB_MPFIST" + Tag "$FIST" +WEAPON.WIMPY_WEAPON +WEAPON.MELEEWEAPON States @@ -61,6 +62,7 @@ ACTOR Pistol : DoomWeapon 5010 Obituary "$OB_MPPISTOL" +WEAPON.WIMPY_WEAPON Inventory.Pickupmessage "$PICKUP_PISTOL_DROPPED" + Tag "$TAG_PISTOL" States { Ready: @@ -105,6 +107,7 @@ ACTOR Chainsaw : Weapon 2005 Weapon.ReadySound "weapons/sawidle" Inventory.PickupMessage "$GOTCHAINSAW" Obituary "$OB_MPCHAINSAW" + Tag "$TAG_CHAINSAW" +WEAPON.MELEEWEAPON States { @@ -144,6 +147,7 @@ ACTOR Shotgun : DoomWeapon 2001 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN" Obituary "$OB_MPSHOTGUN" + Tag "$TAG_SHOTGUN" States { Ready: @@ -190,6 +194,7 @@ ACTOR SuperShotgun : DoomWeapon 82 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN2" Obituary "$OB_MPSSHOTGUN" + Tag "$TAG_SUPERSHOTGUN" States { Ready: @@ -243,6 +248,7 @@ ACTOR Chaingun : DoomWeapon 2002 Weapon.AmmoType "Clip" Inventory.PickupMessage "$GOTCHAINGUN" Obituary "$OB_MPCHAINGUN" + Tag "$TAG_CHAINGUN" States { Ready: @@ -285,6 +291,7 @@ ACTOR RocketLauncher : DoomWeapon 2003 Weapon.AmmoType "RocketAmmo" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTLAUNCHER" + Tag "$TAG_ROCKETLAUNCHER" States { Ready: @@ -406,6 +413,7 @@ ACTOR PlasmaRifle : DoomWeapon 2004 Weapon.AmmoGive 40 Weapon.AmmoType "Cell" Inventory.PickupMessage "$GOTPLASMA" + Tag "$TAG_PLASMARIFLE" States { Ready: @@ -511,6 +519,7 @@ ACTOR BFG9000 : DoomWeapon 2006 Weapon.AmmoType "Cell" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTBFG9000" + Tag "$TAG_BFG9000" States { Ready: diff --git a/wadsrc/static/actors/heretic/hereticartifacts.txt b/wadsrc/static/actors/heretic/hereticartifacts.txt index 0d5ede1a6..67d3946b9 100644 --- a/wadsrc/static/actors/heretic/hereticartifacts.txt +++ b/wadsrc/static/actors/heretic/hereticartifacts.txt @@ -33,6 +33,7 @@ ACTOR ArtiInvisibility : PowerupGiver 75 Inventory.Icon ARTIINVS Powerup.Type Ghost Inventory.PickupMessage "$TXT_ARTIINVISIBILITY" + Tag "$TAG_ARTIINVISIBILITY" States { Spawn: @@ -54,6 +55,7 @@ ACTOR ArtiTomeOfPower : PowerupGiver 86 native Inventory.Icon "ARTIPWBK" Powerup.Type Weaponlevel2 Inventory.PickupMessage "$TXT_ARTITOMEOFPOWER" + Tag "$TAG_ARTITOMEOFPOWER" States { Spawn: @@ -98,6 +100,7 @@ ACTOR ArtiTimeBomb : Inventory 34 native Inventory.Icon "ARTIFBMB" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIFIREBOMB" + Tag "$TAG_ARTIFIREBOMB" Inventory.DefMaxAmount States { diff --git a/wadsrc/static/actors/heretic/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 993da1485..7e2d0a2ee 100644 --- a/wadsrc/static/actors/heretic/hereticweaps.txt +++ b/wadsrc/static/actors/heretic/hereticweaps.txt @@ -15,6 +15,8 @@ ACTOR Staff : HereticWeapon +WIMPY_WEAPON +MELEEWEAPON Weapon.sisterweapon "StaffPowered" + Obituary "$OB_MPSTAFF" + Tag "$TAG_STAFF" action native A_StaffAttack (int damage, class puff); @@ -45,6 +47,8 @@ ACTOR StaffPowered : Staff +WEAPON.POWERED_UP +WEAPON.READYSNDHALF +WEAPON.STAFF2_KICKBACK + Obituary "$OB_MPPSTAFF" + Tag "$TAG_STAFFP" States { Ready: @@ -116,6 +120,8 @@ ACTOR GoldWand : HereticWeapon Weapon.AmmoType "GoldWandAmmo" Weapon.SisterWeapon "GoldWandPowered" Weapon.YAdjust 5 + Obituary "$OB_MPGOLDWAND" + Tag "$TAG_GOLDWAND" action native A_FireGoldWandPL1 (); @@ -145,6 +151,8 @@ ACTOR GoldWandPowered : GoldWand +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "GoldWand" + Obituary "$OB_MPPGOLDWAND" + Tag "$TAG_GOLDWANDP" action native A_FireGoldWandPL2 (); @@ -173,6 +181,7 @@ ACTOR GoldWandFX1 Projectile RenderStyle Add DeathSound "weapons/wandhit" + Obituary "$OB_MPPGOLDWAND" States { Spawn: @@ -209,7 +218,8 @@ ACTOR GoldWandPuff1 +NOGRAVITY +PUFFONACTORS RenderStyle Add - States { + States + { Spawn: PUF2 ABCDE 3 BRIGHT Stop @@ -243,7 +253,8 @@ ACTOR Crossbow : HereticWeapon 2001 Weapon.AmmoType "CrossbowAmmo" Weapon.SisterWeapon "CrossbowPowered" Weapon.YAdjust 15 - Inventory.PickupMessage "$TxT_WPNCROSSBOW" + Inventory.PickupMessage "$TXT_WPNCROSSBOW" + Tag "$TAG_CROSSBOW" action native A_FireCrossbowPL1 (); @@ -277,6 +288,7 @@ ACTOR CrossbowPowered : Crossbow +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "Crossbow" + Tag "$TAG_CROSSBOWP" action native A_FireCrossbowPL2(); @@ -310,6 +322,7 @@ ACTOR CrossbowFX1 RenderStyle Add SeeSound "weapons/bowshoot" DeathSound "weapons/bowhit" + Obituary "$OB_MPCROSSBOW" States { Spawn: @@ -330,6 +343,7 @@ ACTOR CrossbowFX2 : CrossbowFX1 SpawnID 148 Speed 32 Damage 6 + Obituary "$OB_MPPCROSSBOW" States { Spawn: @@ -394,6 +408,8 @@ ACTOR Gauntlets : Weapon 2005 Weapon.UpSound "weapons/gauntletsactivate" Weapon.SisterWeapon "GauntletsPowered" Inventory.PickupMessage "$TXT_WPNGAUNTLETS" + Tag "$TAG_GAUNTLETS" + Obituary "$OB_MPGAUNTLETS" action native A_GauntletAttack (int power); @@ -427,6 +443,8 @@ ACTOR GauntletsPowered : Gauntlets { Game Heretic +POWERED_UP + Tag "$TAG_GAUNTLETSP" + Obituary "$OB_MPPGAUNTLETS" Weapon.SisterWeapon "Gauntlets" States { @@ -469,7 +487,7 @@ ACTOR GauntletPuff1 } } -// Gauntlett puff 2 --------------------------------------------------------- +// Gauntlet puff 2 --------------------------------------------------------- ACTOR GauntletPuff2 : GauntletPuff1 { @@ -494,7 +512,8 @@ ACTOR Mace : HereticWeapon Weapon.YAdjust 15 Weapon.AmmoType "MaceAmmo" Weapon.SisterWeapon "MacePowered" - Inventory.PickupMessage "$TxT_WPNMACE" + Inventory.PickupMessage "$TXT_WPNMACE" + Tag "$TAG_MACE" action native A_FireMacePL1(); @@ -529,6 +548,7 @@ ACTOR MacePowered : Mace Weapon.AmmoUse 5 Weapon.AmmoGive 0 Weapon.SisterWeapon "Mace" + Tag "$TAG_MACEP" action native A_FireMacePL2(); @@ -558,6 +578,7 @@ ACTOR MaceFX1 +THRUGHOST BounceType "HereticCompat" SeeSound "weapons/maceshoot" + Obituary "$OB_MPMACE" action native A_MacePL1Check(); action native A_MaceBallImpact(); @@ -636,6 +657,7 @@ ACTOR MaceFX4 native -NOTELEPORT BounceType "HereticCompat" SeeSound "" + Obituary "$OB_MPPMACE" action native A_DeathBallImpact(); @@ -682,7 +704,9 @@ ACTOR Blaster : HereticWeapon 53 Weapon.YAdjust 15 Weapon.AmmoType "BlasterAmmo" Weapon.SisterWeapon "BlasterPowered" - Inventory.PickupMessage "$TxT_WPNBLASTER" + Inventory.PickupMessage "$TXT_WPNBLASTER" + Tag "$TAG_BLASTER" + Obituary "$OB_MPBLASTER" action native A_FireBlasterPL1(); @@ -740,6 +764,8 @@ ACTOR BlasterFX1 : FastProjectile native SeeSound "weapons/blastershoot" DeathSound "weapons/blasterhit" +SPAWNSOUNDSOURCE + Obituary "$OB_MPPBLASTER" + Tag "$TAG_BLASTERP" action native A_SpawnRippers(); @@ -787,6 +813,7 @@ ACTOR Ripper native Projectile +RIPPER DeathSound "weapons/blasterpowhit" + Obituary "$OB_MPPBLASTER" States { Spawn: @@ -832,7 +859,8 @@ ACTOR SkullRod : HereticWeapon 2004 Weapon.YAdjust 15 Weapon.AmmoType1 "SkullRodAmmo" Weapon.SisterWeapon "SkullRodPowered" - Inventory.PickupMessage "$TxT_WPNSKULLROD" + Inventory.PickupMessage "$TXT_WPNSKULLROD" + Tag "$TAG_SKULLROD" action native A_FireSkullRodPL1(); @@ -864,6 +892,7 @@ ACTOR SkullRodPowered : SkullRod Weapon.AmmoUse1 5 Weapon.AmmoGive1 0 Weapon.SisterWeapon "SkullRod" + Tag "$TAG_SKULLRODP" action native A_FireSkullRodPL2(); @@ -899,6 +928,7 @@ ACTOR HornRodFX1 RenderStyle Add SeeSound "weapons/hornrodshoot" DeathSound "weapons/hornrodhit" + Obituary "$OB_MPSKULLROD" States { Spawn: @@ -926,6 +956,7 @@ ACTOR HornRodFX2 native RenderStyle Add SeeSound "weapons/hornrodpowshoot" DeathSound "weapons/hornrodpowhit" + Obituary "$OB_MPPSKULLROD" action native A_AddPlayerRain(); action native A_HideInCeiling(); @@ -963,6 +994,7 @@ ACTOR RainPillar native -ACTIVATEPCROSS -ACTIVATEIMPACT RenderStyle Add + Obituary "$OB_MPPSKULLROD" action native A_RainImpact(); @@ -1003,7 +1035,8 @@ ACTOR PhoenixRod : Weapon 2003 native Weapon.AmmoGive 2 Weapon.AmmoType "PhoenixRodAmmo" Weapon.Sisterweapon "PhoenixRodPowered" - Inventory.PickupMessage "$TxT_WPNPHOENIxROD" + Inventory.PickupMessage "$TXT_WPNPHOENIxROD" + Tag "$TAG_PHOENIxROD" action native A_FirePhoenixPL1(); @@ -1037,6 +1070,7 @@ ACTOR PhoenixRodPowered : PhoenixRod native +WEAPON.MELEEWEAPON Weapon.SisterWeapon "PhoenixRod" Weapon.AmmoGive 0 + Tag "$TAG_PHOENIxRODP" action native A_InitPhoenixPL2(); action native A_FirePhoenixPL2(); @@ -1071,6 +1105,7 @@ ACTOR PhoenixFX1 native +SPECIALFIREDAMAGE SeeSound "weapons/phoenixshoot" DeathSound "weapons/phoenixhit" + Obituary "$OB_MPPHOENIXROD" action native A_PhoenixPuff(); @@ -1116,6 +1151,7 @@ ACTOR PhoenixFX2 native DamageType Fire Projectile RenderStyle Add + Obituary "$OB_MPPPHOENIXROD" action native A_FlameEnd(); action native A_FloatPuff(); diff --git a/wadsrc/static/actors/hexen/blastradius.txt b/wadsrc/static/actors/hexen/blastradius.txt index 7e4259cf8..ad98d7ecc 100644 --- a/wadsrc/static/actors/hexen/blastradius.txt +++ b/wadsrc/static/actors/hexen/blastradius.txt @@ -10,6 +10,7 @@ ACTOR ArtiBlastRadius : CustomInventory 10110 Inventory.Icon "ARTIBLST" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBLASTRADIUS" + Tag "$TAG_ARTIBLASTRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/boostarmor.txt b/wadsrc/static/actors/hexen/boostarmor.txt index ca3b8384c..607c8d66a 100644 --- a/wadsrc/static/actors/hexen/boostarmor.txt +++ b/wadsrc/static/actors/hexen/boostarmor.txt @@ -13,6 +13,7 @@ ACTOR ArtiBoostArmor : Inventory 8041 native Inventory.Icon "ARTIBRAC" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTARMOR" + Tag "$TAG_ARTIBOOSTARMOR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/clericflame.txt b/wadsrc/static/actors/hexen/clericflame.txt index 29312ca79..5fb4175fd 100644 --- a/wadsrc/static/actors/hexen/clericflame.txt +++ b/wadsrc/static/actors/hexen/clericflame.txt @@ -12,6 +12,7 @@ ACTOR CWeapFlame : ClericWeapon 8009 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C3" + Tag "$TAG_CWEAPFLAME" action native A_CFlameAttack(); @@ -126,6 +127,7 @@ ACTOR CircleFlame -ACTIVATEPCROSS RenderStyle Add DeathSound "ClericFlameCircle" + Obituary "$OB_MPCWEAPFLAME" action native A_CFlameRotate(); @@ -166,6 +168,7 @@ ACTOR CFlameMissile : FastProjectile native DamageType "Fire" +INVISIBLE RenderStyle Add + Obituary "$OB_MPCWEAPFLAME" action native A_CFlamePuff(); action native A_CFlameMissile(); diff --git a/wadsrc/static/actors/hexen/clericholy.txt b/wadsrc/static/actors/hexen/clericholy.txt index 0c441726d..15673ab1b 100644 --- a/wadsrc/static/actors/hexen/clericholy.txt +++ b/wadsrc/static/actors/hexen/clericholy.txt @@ -84,6 +84,7 @@ ACTOR CWeapWraithverge : ClericWeapon native Weapon.AmmoType1 "Mana1" Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C4" + Tag "$TAG_CWEAPWRAITHVERGE" Inventory.PickupSound "WeaponBuild" action native A_CHolyAttack(); @@ -187,6 +188,7 @@ ACTOR HolySpirit native RenderStyle Translucent Alpha 0.4 DeathSound "SpiritDie" + Obituary "$OB_MPCWEAPWRAITHVERGE" action native A_CHolySeek(); action native A_CHolyCheckScream(); diff --git a/wadsrc/static/actors/hexen/clericmace.txt b/wadsrc/static/actors/hexen/clericmace.txt index ae9d3b84a..14ef4b00f 100644 --- a/wadsrc/static/actors/hexen/clericmace.txt +++ b/wadsrc/static/actors/hexen/clericmace.txt @@ -8,6 +8,8 @@ ACTOR CWeapMace : ClericWeapon Weapon.KickBack 150 Weapon.YAdjust -8 +BLOODSPLATTER + Obituary "$OB_MPCWEAPMACE" + Tag "$TAG_CWEAPMACE" action native A_CMaceAttack(); diff --git a/wadsrc/static/actors/hexen/clericplayer.txt b/wadsrc/static/actors/hexen/clericplayer.txt index 4fe6629e2..73276e981 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 3b9cd1699..e55674fa5 100644 --- a/wadsrc/static/actors/hexen/clericstaff.txt +++ b/wadsrc/static/actors/hexen/clericstaff.txt @@ -12,6 +12,8 @@ ACTOR CWeapStaff : ClericWeapon 10 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_C2" + Obituary "$OB_MPCWEAPSTAFFM" + Tag "$TAG_CWEAPSTAFF" action native A_CStaffInitBlink(); action native A_CStaffCheckBlink(); @@ -65,6 +67,7 @@ ACTOR CStaffMissile native RenderStyle Add Projectile DeathSound "ClericCStaffExplode" + Obituary "$OB_MPCWEAPSTAFFR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/fighteraxe.txt b/wadsrc/static/actors/hexen/fighteraxe.txt index 78cbbf0a3..502de5030 100644 --- a/wadsrc/static/actors/hexen/fighteraxe.txt +++ b/wadsrc/static/actors/hexen/fighteraxe.txt @@ -13,6 +13,8 @@ ACTOR FWeapAxe : FighterWeapon 8010 native Weapon.YAdjust -12 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_F2" + Obituary "$OB_MPFWEAPAXE" + Tag "$TAG_FWEAPAXE" action native A_FAxeCheckUp(); action native A_FAxeCheckReady(); diff --git a/wadsrc/static/actors/hexen/fighterfist.txt b/wadsrc/static/actors/hexen/fighterfist.txt index 6693b79bf..0122d7f74 100644 --- a/wadsrc/static/actors/hexen/fighterfist.txt +++ b/wadsrc/static/actors/hexen/fighterfist.txt @@ -8,6 +8,8 @@ ACTOR FWeapFist : FighterWeapon Weapon.SelectionOrder 3400 +WEAPON.MELEEWEAPON Weapon.KickBack 150 + Obituary "$OB_MPFWEAPFIST" + Tag "$TAG_FWEAPFIST" action native A_FPunchAttack(); diff --git a/wadsrc/static/actors/hexen/fighterhammer.txt b/wadsrc/static/actors/hexen/fighterhammer.txt index 63585b1cc..5c7d6dc25 100644 --- a/wadsrc/static/actors/hexen/fighterhammer.txt +++ b/wadsrc/static/actors/hexen/fighterhammer.txt @@ -14,6 +14,8 @@ ACTOR FWeapHammer : FighterWeapon 123 Weapon.YAdjust -10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F3" + Obituary "$OB_MPFWEAPHAMMERM" + Tag "$TAG_FWEAPHAMMER" action native A_FHammerAttack(); action native A_FHammerThrow(); @@ -60,6 +62,7 @@ ACTOR HammerMissile DamageType "Fire" Projectile DeathSound "FighterHammerExplode" + Obituary "$OB_MPFWEAPHAMMERR" States { diff --git a/wadsrc/static/actors/hexen/fighterplayer.txt b/wadsrc/static/actors/hexen/fighterplayer.txt index 2253ba5b6..809b1f9e9 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 3bd5904f7..034914d23 100644 --- a/wadsrc/static/actors/hexen/fighterquietus.txt +++ b/wadsrc/static/actors/hexen/fighterquietus.txt @@ -86,6 +86,7 @@ ACTOR FWeapQuietus : FighterWeapon Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_FWEAPQUIETUS" action native A_FSwordAttack(); @@ -131,6 +132,7 @@ ACTOR FSwordMissile native +EXTREMEDEATH RenderStyle Add DeathSound "FighterSwordExplode" + Obituary "$OB_MPFWEAPQUIETUS" action native A_FSwordFlames(); diff --git a/wadsrc/static/actors/hexen/flame.txt b/wadsrc/static/actors/hexen/flame.txt index c3a222ea6..984c2d90c 100644 --- a/wadsrc/static/actors/hexen/flame.txt +++ b/wadsrc/static/actors/hexen/flame.txt @@ -10,10 +10,10 @@ ACTOR FlameSmallTemp 10500 { Spawn: FFSM AB 3 Bright - FFSM C 2 Bright A_CountdownArg(1) + FFSM C 2 Bright A_CountdownArg(0) FFSM C 2 Bright FFSM D 3 Bright - FFSM E 3 Bright A_CountdownArg(1) + FFSM E 3 Bright A_CountdownArg(0) Loop } } @@ -31,21 +31,21 @@ ACTOR FlameLargeTemp 10502 { Spawn: FFLG A 4 Bright - FFLG B 4 Bright A_CountdownArg(1) + FFLG B 4 Bright A_CountdownArg(0) FFLG C 4 Bright - FFLG D 4 Bright A_CountdownArg(1) + FFLG D 4 Bright A_CountdownArg(0) FFLG E 4 Bright - FFLG F 4 Bright A_CountdownArg(1) + FFLG F 4 Bright A_CountdownArg(0) FFLG G 4 Bright - FFLG H 4 Bright A_CountdownArg(1) + FFLG H 4 Bright A_CountdownArg(0) FFLG I 4 Bright - FFLG J 4 Bright A_CountdownArg(1) + FFLG J 4 Bright A_CountdownArg(0) FFLG K 4 Bright - FFLG L 4 Bright A_CountdownArg(1) + FFLG L 4 Bright A_CountdownArg(0) FFLG M 4 Bright - FFLG N 4 Bright A_CountdownArg(1) + FFLG N 4 Bright A_CountdownArg(0) FFLG O 4 Bright - FFLG P 4 Bright A_CountdownArg(1) + FFLG P 4 Bright A_CountdownArg(0) Goto Spawn+4 } } diff --git a/wadsrc/static/actors/hexen/flechette.txt b/wadsrc/static/actors/hexen/flechette.txt index 4820a706d..b2063a6ec 100644 --- a/wadsrc/static/actors/hexen/flechette.txt +++ b/wadsrc/static/actors/hexen/flechette.txt @@ -105,6 +105,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native Inventory.Icon "ARTIPSBG" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIPOISONBAG" + Tag "$TAG_ARTIPOISONBAG" States { Spawn: @@ -118,6 +119,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native ACTOR ArtiPoisonBag1 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB1" + Tag "$TAG_ARTIPOISONBAG1" } // Poison Bag 2 (The Mage's) ------------------------------------------------ @@ -125,6 +127,7 @@ ACTOR ArtiPoisonBag1 : ArtiPoisonBag native ACTOR ArtiPoisonBag2 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB2" + Tag "$TAG_ARTIPOISONBAG2" } // Poison Bag 3 (The Fighter's) --------------------------------------------- @@ -132,6 +135,7 @@ ACTOR ArtiPoisonBag2 : ArtiPoisonBag native ACTOR ArtiPoisonBag3 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB3" + Tag "$TAG_ARTIPOISONBAG3" } // Poison Cloud ------------------------------------------------------------- diff --git a/wadsrc/static/actors/hexen/healingradius.txt b/wadsrc/static/actors/hexen/healingradius.txt index 8c5b0e53a..e0556915b 100644 --- a/wadsrc/static/actors/hexen/healingradius.txt +++ b/wadsrc/static/actors/hexen/healingradius.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealingRadius : Inventory 10120 native Inventory.Icon "ARTIHRAD" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALINGRADIUS" + Tag "$TAG_ARTIHEALINGRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/hexenspecialdecs.txt b/wadsrc/static/actors/hexen/hexenspecialdecs.txt index 7d4a13a94..e36451e7f 100644 --- a/wadsrc/static/actors/hexen/hexenspecialdecs.txt +++ b/wadsrc/static/actors/hexen/hexenspecialdecs.txt @@ -216,6 +216,7 @@ ACTOR CorpseBloodDrip Gravity 0.125 +MISSILE +NOICEDEATH + DeathSound "Drip" States { Spawn: diff --git a/wadsrc/static/actors/hexen/magecone.txt b/wadsrc/static/actors/hexen/magecone.txt index 17c7ccf4f..b5dfdb546 100644 --- a/wadsrc/static/actors/hexen/magecone.txt +++ b/wadsrc/static/actors/hexen/magecone.txt @@ -13,6 +13,8 @@ ACTOR MWeapFrost : MageWeapon 53 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_M2" + Obituary "$OB_MPMWEAPFROST" + Tag "$TAG_MWEAPFROST" action native A_FireConePL1(); @@ -55,6 +57,7 @@ ACTOR FrostMissile native DamageType "Ice" Projectile DeathSound "MageShardsExplode" + Obituary "$OB_MPMWEAPFROST" action native A_ShedShard(); diff --git a/wadsrc/static/actors/hexen/magelightning.txt b/wadsrc/static/actors/hexen/magelightning.txt index 02658bcba..85402f223 100644 --- a/wadsrc/static/actors/hexen/magelightning.txt +++ b/wadsrc/static/actors/hexen/magelightning.txt @@ -12,6 +12,7 @@ ACTOR MWeapLightning : MageWeapon 8040 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_M3" + Tag "$TAG_MWEAPLIGHTNING" action native A_LightningReady(); action native A_MLightningAttack(class floor = "LightningFloor", class ceiling = "LightningCeiling"); @@ -56,6 +57,7 @@ ACTOR Lightning native MissileType "LightningZap" AttackSound "MageLightningZap" ActiveSound "MageLightningContinuous" + Obituary "$OB_MPMWEAPLIGHTNING" } ACTOR LightningCeiling : Lightning @@ -137,6 +139,7 @@ ACTOR LightningZap native -ACTIVATEIMPACT -ACTIVATEPCROSS RenderStyle Add + Obituary "$OB_MPMWEAPLIGHTNING" action native A_ZapMimic(); diff --git a/wadsrc/static/actors/hexen/mageplayer.txt b/wadsrc/static/actors/hexen/mageplayer.txt index a34698366..d843b00a9 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 e402e57c0..65c0ac893 100644 --- a/wadsrc/static/actors/hexen/magestaff.txt +++ b/wadsrc/static/actors/hexen/magestaff.txt @@ -86,6 +86,7 @@ ACTOR MWeapBloodscourge : MageWeapon native +Inventory.NoAttenPickupSound Inventory.PickupMessage "$TXT_WEAPON_M4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_MWEAPBLOODSCOURGE" action native A_MStaffAttack(); action native A_MStaffPalette(); @@ -128,6 +129,7 @@ ACTOR MageStaffFX2 native +SCREENSEEKER +EXTREMEDEATH DeathSound "MageStaffExplode" + Obituary "$OB_MPMWEAPBLOODSCOURGE" action native A_MStaffTrack(); diff --git a/wadsrc/static/actors/hexen/magewand.txt b/wadsrc/static/actors/hexen/magewand.txt index ebb8e2ca1..aaac9e5da 100644 --- a/wadsrc/static/actors/hexen/magewand.txt +++ b/wadsrc/static/actors/hexen/magewand.txt @@ -7,6 +7,7 @@ ACTOR MWeapWand : MageWeapon Weapon.SelectionOrder 3600 Weapon.KickBack 0 Weapon.YAdjust 9 + Tag "$TAG_MWEAPWAND" States { @@ -56,6 +57,7 @@ ACTOR MageWandMissile : FastProjectile +SPAWNSOUNDSOURCE MissileType "MageWandSmoke" SeeSound "MageWandFire" + Obituary "$OB_MPMWEAPWAND" States { Spawn: diff --git a/wadsrc/static/actors/hexen/mana.txt b/wadsrc/static/actors/hexen/mana.txt index 6130fe962..ee7ddd37c 100644 --- a/wadsrc/static/actors/hexen/mana.txt +++ b/wadsrc/static/actors/hexen/mana.txt @@ -81,6 +81,7 @@ ACTOR ArtiBoostMana : CustomInventory 8003 Inventory.Icon "ARTIBMAN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTMANA" + Tag "$TAG_ARTIBOOSTMANA" States { Spawn: diff --git a/wadsrc/static/actors/hexen/pig.txt b/wadsrc/static/actors/hexen/pig.txt index eaecd692e..45b649597 100644 --- a/wadsrc/static/actors/hexen/pig.txt +++ b/wadsrc/static/actors/hexen/pig.txt @@ -142,7 +142,7 @@ ACTOR Pig : MorphedMonster Death: PIGY E 4 A_Scream PIGY F 3 A_NoBlocking - PIGY G 4 + PIGY G 4 A_QueueCorpse PIGY H 3 PIGY IJK 4 PIGY L -1 diff --git a/wadsrc/static/actors/hexen/puzzleitems.txt b/wadsrc/static/actors/hexen/puzzleitems.txt index c3bdf29ba..dd15fc8fd 100644 --- a/wadsrc/static/actors/hexen/puzzleitems.txt +++ b/wadsrc/static/actors/hexen/puzzleitems.txt @@ -8,6 +8,7 @@ ACTOR PuzzSkull : PuzzleItem 9002 PuzzleItem.Number 0 Inventory.Icon ARTISKLL Inventory.PickupMessage "$TXT_ARTIPUZZSKULL" + Tag "$TAG_ARTIPUZZSKULL" States { Spawn: @@ -26,6 +27,7 @@ ACTOR PuzzGemBig : PuzzleItem 9003 PuzzleItem.Number 1 Inventory.Icon ARTIBGEM Inventory.PickupMessage "$TXT_ARTIPUZZGEMBIG" + Tag "$TAG_ARTIPUZZGEMBIG" States { Spawn: @@ -43,6 +45,7 @@ ACTOR PuzzGemRed : PuzzleItem 9004 PuzzleItem.Number 2 Inventory.Icon ARTIGEMR Inventory.PickupMessage "$TXT_ARTIPUZZGEMRED" + Tag "$TAG_ARTIPUZZGEMRED" States { Spawn: @@ -61,6 +64,7 @@ ACTOR PuzzGemGreen1 : PuzzleItem 9005 PuzzleItem.Number 3 Inventory.Icon ARTIGEMG Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN1" + Tag "$TAG_ARTIPUZZGEMGREEN1" States { Spawn: @@ -79,6 +83,7 @@ ACTOR PuzzGemGreen2 : PuzzleItem 9009 PuzzleItem.Number 4 Inventory.Icon ARTIGMG2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN2" + Tag "$TAG_ARTIPUZZGEMGREEN2" States { Spawn: @@ -97,6 +102,7 @@ ACTOR PuzzGemBlue1 : PuzzleItem 9006 PuzzleItem.Number 5 Inventory.Icon ARTIGEMB Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE1" + Tag "$TAG_ARTIPUZZGEMBLUE1" States { Spawn: @@ -115,6 +121,7 @@ ACTOR PuzzGemBlue2 : PuzzleItem 9010 PuzzleItem.Number 6 Inventory.Icon ARTIGMB2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE2" + Tag "$TAG_ARTIPUZZGEMBLUE2" States { Spawn: @@ -133,6 +140,7 @@ ACTOR PuzzBook1 : PuzzleItem 9007 PuzzleItem.Number 7 Inventory.Icon ARTIBOK1 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK1" + Tag "$TAG_ARTIPUZZBOOK1" States { Spawn: @@ -151,6 +159,7 @@ ACTOR PuzzBook2 : PuzzleItem 9008 PuzzleItem.Number 8 Inventory.Icon ARTIBOK2 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK2" + Tag "$TAG_ARTIPUZZBOOK2" States { Spawn: @@ -169,6 +178,7 @@ ACTOR PuzzFlameMask : PuzzleItem 9014 PuzzleItem.Number 9 Inventory.Icon ARTISKL2 Inventory.PickupMessage "$TXT_ARTIPUZZSKULL2" + Tag "$TAG_ARTIPUZZSKULL2" States { Spawn: @@ -185,6 +195,7 @@ ACTOR PuzzFWeapon : PuzzleItem 9015 PuzzleItem.Number 10 Inventory.Icon ARTIFWEP Inventory.PickupMessage "$TXT_ARTIPUZZFWEAPON" + Tag "$TAG_ARTIPUZZFWEAPON" States { Spawn: @@ -202,6 +213,7 @@ ACTOR PuzzCWeapon : PuzzleItem 9016 PuzzleItem.Number 11 Inventory.Icon ARTICWEP Inventory.PickupMessage "$TXT_ARTIPUZZCWEAPON" + Tag "$TAG_ARTIPUZZCWEAPON" States { Spawn: @@ -219,6 +231,7 @@ ACTOR PuzzMWeapon : PuzzleItem 9017 PuzzleItem.Number 12 Inventory.Icon ARTIMWEP Inventory.PickupMessage "$TXT_ARTIPUZZMWEAPON" + Tag "$TAG_ARTIPUZZMWEAPON" States { Spawn: @@ -235,6 +248,7 @@ ACTOR PuzzGear1 : PuzzleItem 9018 PuzzleItem.Number 13 Inventory.Icon ARTIGEAR Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR1" States { Spawn: @@ -252,6 +266,7 @@ ACTOR PuzzGear2 : PuzzleItem 9019 PuzzleItem.Number 14 Inventory.Icon ARTIGER2 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR2" States { Spawn: @@ -269,6 +284,7 @@ ACTOR PuzzGear3 : PuzzleItem 9020 PuzzleItem.Number 15 Inventory.Icon ARTIGER3 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR3" States { Spawn: @@ -286,6 +302,7 @@ ACTOR PuzzGear4 : PuzzleItem 9021 PuzzleItem.Number 16 Inventory.Icon ARTIGER4 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR4" States { Spawn: diff --git a/wadsrc/static/actors/hexen/speedboots.txt b/wadsrc/static/actors/hexen/speedboots.txt index 17d193d2a..5fd6703b8 100644 --- a/wadsrc/static/actors/hexen/speedboots.txt +++ b/wadsrc/static/actors/hexen/speedboots.txt @@ -9,6 +9,7 @@ ACTOR ArtiSpeedBoots : PowerupGiver 8002 +INVENTORY.PICKUPFLASH Inventory.Icon ARTISPED Inventory.PickupMessage "$TXT_ARTISPEED" + Tag "$TAG_ARTISPEED" Powerup.Type Speed States { diff --git a/wadsrc/static/actors/hexen/summon.txt b/wadsrc/static/actors/hexen/summon.txt index 399c2dd89..a4b3b8584 100644 --- a/wadsrc/static/actors/hexen/summon.txt +++ b/wadsrc/static/actors/hexen/summon.txt @@ -14,6 +14,7 @@ ACTOR ArtiDarkServant : Inventory 86 native Inventory.Icon "ARTISUMN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUMMON" + Tag "$TAG_ARTISUMMON" States { Spawn: diff --git a/wadsrc/static/actors/hexen/teleportother.txt b/wadsrc/static/actors/hexen/teleportother.txt index bcbdfc33c..77a05f6af 100644 --- a/wadsrc/static/actors/hexen/teleportother.txt +++ b/wadsrc/static/actors/hexen/teleportother.txt @@ -14,6 +14,7 @@ ACTOR ArtiTeleportOther : Inventory 10040 native Inventory.Icon "ARTITELO" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORTOTHER" + Tag "$TAG_ARTITELEPORTOTHER" States { Spawn: diff --git a/wadsrc/static/actors/raven/artiegg.txt b/wadsrc/static/actors/raven/artiegg.txt index 6b8de50c9..d94f00151 100644 --- a/wadsrc/static/actors/raven/artiegg.txt +++ b/wadsrc/static/actors/raven/artiegg.txt @@ -38,6 +38,7 @@ ACTOR ArtiEgg : CustomInventory 30 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIEGG" Inventory.DefMaxAmount + Tag "$TAG_ARTIEGG" States { Spawn: @@ -91,10 +92,11 @@ ACTOR ArtiPork : CustomInventory 30 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIEGG2" Inventory.DefMaxAmount + Tag "$TAG_ARTIPORK" States { Spawn: - PORK ABCDEFGH 6 + PORK ABCDEFGH 5 Loop Use: TNT1 A 0 A_FireCustomMissile("PorkFX", -15, 0, 0, 0, 1) diff --git a/wadsrc/static/actors/raven/artitele.txt b/wadsrc/static/actors/raven/artitele.txt index 5c1fa9d8f..1f0fe73d2 100644 --- a/wadsrc/static/actors/raven/artitele.txt +++ b/wadsrc/static/actors/raven/artitele.txt @@ -13,6 +13,7 @@ ACTOR ArtiTeleport : Inventory 36 native Inventory.Icon "ARTIATLP" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORT" + Tag "$TAG_ARTITELEPORT" States { Spawn: diff --git a/wadsrc/static/actors/raven/ravenartifacts.txt b/wadsrc/static/actors/raven/ravenartifacts.txt index 84264764b..c02c19c3b 100644 --- a/wadsrc/static/actors/raven/ravenartifacts.txt +++ b/wadsrc/static/actors/raven/ravenartifacts.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealth : HealthPickup 82 Inventory.Icon ARTIPTN2 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALTH" + Tag "$TAG_ARTIHEALTH" HealthPickup.Autouse 1 States { @@ -36,6 +37,7 @@ ACTOR ArtiSuperHealth : HealthPickup 32 Inventory.Icon ARTISPHL Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUPERHEALTH" + Tag "$TAG_ARTISUPERHEALTH" HealthPickup.Autouse 2 States { @@ -58,6 +60,7 @@ ACTOR ArtiFly : PowerupGiver 83 Inventory.RespawnTics 4230 Inventory.Icon ARTISOAR Inventory.PickupMessage "$TXT_ARTIFLY" + Tag "$TAG_ARTIFLY" Powerup.Type Flight States { @@ -79,6 +82,7 @@ ACTOR ArtiInvulnerability : PowerupGiver 84 Inventory.RespawnTics 4230 Inventory.Icon ARTIINVU Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY" + Tag "$TAG_ARTIINVULNERABILITY" Powerup.Type Invulnerable Powerup.Color GoldMap States @@ -102,6 +106,7 @@ ACTOR ArtiInvulnerability2 : PowerupGiver 84 Inventory.Icon ARTIDEFN Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY2" Powerup.Type Invulnerable + Tag "$TAG_ARTIDEFENDER" States { Spawn: @@ -121,6 +126,7 @@ ACTOR ArtiTorch : PowerupGiver 33 +INVENTORY.PICKUPFLASH Inventory.Icon ARTITRCH Inventory.PickupMessage "$TXT_ARTITORCH" + Tag "$TAG_ARTITORCH" Powerup.Type Torch States { diff --git a/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index a61519df0..49b6b39a2 100644 --- a/wadsrc/static/actors/shared/inventory.txt +++ b/wadsrc/static/actors/shared/inventory.txt @@ -5,6 +5,7 @@ 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); diff --git a/wadsrc/static/actors/strife/acolyte.txt b/wadsrc/static/actors/strife/acolyte.txt index 73b27864b..1d04147f0 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 8820f32d0..b24080b1a 100644 --- a/wadsrc/static/actors/strife/beggars.txt +++ b/wadsrc/static/actors/strife/beggars.txt @@ -13,7 +13,7 @@ ACTOR Beggar : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Beggar" + Tag "$TAG_BEGGAR" MaxStepHeight 16 MaxDropoffHeight 32 HitObituary "$OB_BEGGAR" diff --git a/wadsrc/static/actors/strife/coin.txt b/wadsrc/static/actors/strife/coin.txt index f569fb1ed..e4e50af77 100644 --- a/wadsrc/static/actors/strife/coin.txt +++ b/wadsrc/static/actors/strife/coin.txt @@ -10,7 +10,7 @@ ACTOR Coin : Inventory 93 native +FLOORCLIP Inventory.MaxAmount 0x7fffffff +INVENTORY.INVBAR - Tag "coin" + Tag "$TAG_COIN" Inventory.Icon "I_COIN" Inventory.PickupMessage "$TXT_COIN" States @@ -29,7 +29,7 @@ ACTOR Gold10 : Coin 138 Game Strife ConversationID 169, 162, 166 Inventory.Amount 10 - Tag "10 gold" + Tag "$TAG_10GOLD" Inventory.PickupMessage "$TXT_10GOLD" States { @@ -46,7 +46,7 @@ ACTOR Gold25 : Coin 139 Game Strife ConversationID 170, 163, 167 Inventory.Amount 25 - Tag "25 gold" + Tag "$TAG_25GOLD" Inventory.PickupMessage "$TXT_25GOLD" States { @@ -63,7 +63,7 @@ ACTOR Gold50 : Coin 140 Game Strife ConversationID 171, 164, 168 Inventory.Amount 50 - Tag "50 gold" + Tag "$TAG_50GOLD" Inventory.PickupMessage "$TXT_50GOLD" States { @@ -79,7 +79,7 @@ ACTOR Gold300 : Coin { ConversationID 172, -1, -1 Inventory.Amount 300 - Tag "300 gold" + Tag "$TAG_300GOLD" Inventory.PickupMessage "$TXT_300GOLD" Inventory.GiveQuest 3 +INVENTORY.ALWAYSPICKUP diff --git a/wadsrc/static/actors/strife/loremaster.txt b/wadsrc/static/actors/strife/loremaster.txt index dd79c0f19..fee3eb0f0 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 3666728e0..ac23e4e66 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 65f5ac653..b6a2e09a5 100644 --- a/wadsrc/static/actors/strife/merchants.txt +++ b/wadsrc/static/actors/strife/merchants.txt @@ -59,7 +59,7 @@ ACTOR WeaponSmith : Merchant 116 Game Strife ConversationID 2 PainSound "smith/pain" - Tag "Weapon Smith" + Tag "$TAG_WEAPONSMITH" } @@ -72,7 +72,7 @@ ACTOR BarKeep : Merchant 72 ConversationID 3 PainSound "barkeep/pain" ActiveSound "barkeep/active" - Tag "Bar Keep" + Tag "$TAG_BARKEEP" } @@ -84,7 +84,7 @@ ACTOR Armorer : Merchant 73 Translation 5 ConversationID 4 PainSound "armorer/pain" - Tag "Armorer" + Tag "$TAG_ARMORER" } @@ -96,6 +96,6 @@ ACTOR Medic : Merchant 74 Translation 6 ConversationID 5 PainSound "medic/pain" - Tag "Medic" + Tag "$TAG_MEDIC" } diff --git a/wadsrc/static/actors/strife/oracle.txt b/wadsrc/static/actors/strife/oracle.txt index 03b80c894..2e0aff948 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 b06cdfaa8..cf6b98b0b 100644 --- a/wadsrc/static/actors/strife/questitems.txt +++ b/wadsrc/static/actors/strife/questitems.txt @@ -64,19 +64,19 @@ ACTOR QuestItem3 : QuestItem ACTOR QuestItem4 : QuestItem { ConversationID 315, 296, 313 - Tag "quest4" + Tag "$TAG_QUEST4" } ACTOR QuestItem5 : QuestItem { ConversationID 316, 297, 314 - Tag "quest5" + Tag "$TAG_QUEST5" } ACTOR QuestItem6 : QuestItem { ConversationID 317, 298, 315 - Tag "quest4" + Tag "TAG_QUEST6" } ACTOR QuestItem7 : QuestItem diff --git a/wadsrc/static/actors/strife/ratbuddy.txt b/wadsrc/static/actors/strife/ratbuddy.txt index 762aff659..eaefda35d 100644 --- a/wadsrc/static/actors/strife/ratbuddy.txt +++ b/wadsrc/static/actors/strife/ratbuddy.txt @@ -12,7 +12,7 @@ ACTOR RatBuddy 85 MinMissileChance 150 MaxStepHeight 16 MaxDropoffHeight 32 - Tag "rat buddy" + Tag "$TAG_RATBUDDY" SeeSound "rat/sight" DeathSound "rat/death" ActiveSound "rat/active" diff --git a/wadsrc/static/actors/strife/rebels.txt b/wadsrc/static/actors/strife/rebels.txt index ff6faeec2..3c539bca4 100644 --- a/wadsrc/static/actors/strife/rebels.txt +++ b/wadsrc/static/actors/strife/rebels.txt @@ -14,7 +14,7 @@ ACTOR Rebel : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Rebel" + Tag "$TAG_REBEL" SeeSound "rebel/sight" PainSound "rebel/pain" DeathSound "rebel/death" @@ -124,7 +124,7 @@ ACTOR TeleporterBeacon : Inventory 10 native +DROPPED +INVENTORY.INVBAR Inventory.Icon "I_BEAC" - Tag "Teleporter Beacon" + Tag "$TAG_TELEPORTERBEACON" Inventory.PickupMessage "$TXT_BEACON" action native A_Beacon (); diff --git a/wadsrc/static/actors/strife/sigil.txt b/wadsrc/static/actors/strife/sigil.txt index ab9c6d21b..a2cc66c7b 100644 --- a/wadsrc/static/actors/strife/sigil.txt +++ b/wadsrc/static/actors/strife/sigil.txt @@ -11,7 +11,7 @@ ACTOR Sigil : Weapon native +FLOORCLIP +WEAPON.CHEATNOTWEAPON Inventory.PickupSound "weapons/sigilcharge" - Tag "SIGIL" + Tag "$TAG_SIGIL" Inventory.Icon "I_SGL1" Inventory.PickupMessage "$TXT_SIGIL" diff --git a/wadsrc/static/actors/strife/strifeammo.txt b/wadsrc/static/actors/strife/strifeammo.txt index 1de16474f..801714434 100644 --- a/wadsrc/static/actors/strife/strifeammo.txt +++ b/wadsrc/static/actors/strife/strifeammo.txt @@ -10,7 +10,7 @@ ACTOR HEGrenadeRounds : Ammo 152 Ammo.BackpackAmount 6 Ammo.BackpackMaxAmount 60 Inventory.Icon "I_GRN1" - Tag "HE-Grenade Rounds" + Tag "$TAG_HEGRENADES" Inventory.PickupMessage "$TXT_HEGRENADES" States { @@ -32,7 +32,7 @@ ACTOR PhosphorusGrenadeRounds : Ammo 153 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 32 Inventory.Icon "I_GRN2" - Tag "Phoshorus-Grenade Rounds" // "Fire-Grenade_Rounds" in the Teaser + Tag "$TAG_PHGRENADES" Inventory.PickupMessage "$TXT_PHGRENADES" States { @@ -55,7 +55,7 @@ ACTOR ClipOfBullets : Ammo 2007 Ammo.BackpackAmount 10 Ammo.BackpackMaxAmount 500 Inventory.Icon "I_BLIT" - Tag "Clip of Bullets" // "bullets" in the Teaser + Tag "$TAG_CLIPOFBULLETS" Inventory.PickupMessage "$TXT_CLIPOFBULLETS" States { @@ -73,7 +73,7 @@ ACTOR BoxOfBullets : ClipOfBullets 2048 SpawnID 139 ConversationID 180, 174, 178 Inventory.Amount 50 - Tag "Ammo" + Tag "$TAG_BOXOFBULLETS" Inventory.PickupMessage "$TXT_BOXOFBULLETS" States { @@ -96,7 +96,7 @@ ACTOR MiniMissiles : Ammo 2010 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 200 Inventory.Icon "I_ROKT" - Tag "Mini Missiles" //"rocket" in the Teaser + Tag "$TAG_MINIMISSILES" Inventory.PickupMessage "$TXT_MINIMISSILES" States { @@ -114,7 +114,7 @@ ACTOR CrateOfMissiles : MiniMissiles 2046 SpawnID 141 ConversationID 182, 176, 180 Inventory.Amount 20 - Tag "Crate of Missiles" //"box_of_rockets" in the Teaser + Tag "$TAG_CRATEOFMISSILES" Inventory.PickupMessage "$TXT_CRATEOFMISSILES" States { @@ -138,7 +138,7 @@ ACTOR EnergyPod : Ammo 2047 Ammo.BackpackMaxAmount 800 Ammo.DropAmount 20 Inventory.Icon "I_BRY1" - Tag "Energy Pod" + Tag "$TAG_ENERGYPOD" Inventory.PickupMessage "$TXT_ENERGYPOD" States { @@ -156,7 +156,7 @@ ACTOR EnergyPack : EnergyPod 17 SpawnID 142 ConversationID 184, 178, 182 Inventory.Amount 100 - Tag "Energy Pack" + Tag "$TAG_ENERGYPACK" Inventory.PickupMessage "$TXT_ENERGYPACK" States { @@ -178,7 +178,7 @@ ACTOR PoisonBolts : Ammo 115 Ammo.BackpackAmount 2 Ammo.BackpackMaxAmount 50 Inventory.Icon "I_PQRL" - Tag "Poison Bolts" // "poison_arrows" in the Teaser + Tag "$TAG_POISONBOLTS" Inventory.PickupMessage "$TXT_POISONBOLTS" States { @@ -200,7 +200,7 @@ ACTOR ElectricBolts : Ammo 114 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 100 Inventory.Icon "I_XQRL" - Tag "Electric Bolts" // "electric_arrows" in the Teaser + Tag "$TAG_ELECTRICBOLTS" Inventory.PickupMessage "$TXT_ELECTRICBOLTS" States { @@ -219,7 +219,7 @@ ACTOR AmmoSatchel : BackpackItem 183 ConversationID 187, 181, 184 +FLOORCLIP Inventory.Icon "I_BKPK" - Tag "Ammo Satchel" // "Back_pack" in the Teaser + Tag "$TAG_AMMOSATCHEL" Inventory.PickupMessage "$TXT_AMMOSATCHEL" States { diff --git a/wadsrc/static/actors/strife/strifearmor.txt b/wadsrc/static/actors/strife/strifearmor.txt index 2be8d8ad6..2431e9438 100644 --- a/wadsrc/static/actors/strife/strifearmor.txt +++ b/wadsrc/static/actors/strife/strifearmor.txt @@ -14,7 +14,7 @@ ACTOR MetalArmor : BasicArmorPickup 2019 Inventory.PickupMessage "$TXT_METALARMOR" Armor.SaveAmount 200 Armor.SavePercent 50 - Tag "Metal Armor" + Tag "$TAG_METALARMOR" States { Spawn: @@ -38,7 +38,7 @@ ACTOR LeatherArmor : BasicArmorPickup 2018 Inventory.PickupMessage "$TXT_LEATHERARMOR" Armor.SaveAmount 100 Armor.SavePercent 33.335 - Tag "Leather Armor" + Tag "$TAG_LEATHER" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifeitems.txt b/wadsrc/static/actors/strife/strifeitems.txt index 407a91843..74b957b83 100644 --- a/wadsrc/static/actors/strife/strifeitems.txt +++ b/wadsrc/static/actors/strife/strifeitems.txt @@ -8,7 +8,7 @@ ACTOR MedPatch : HealthPickup 2011 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 20 - Tag "Med patch" + Tag "$TAG_MEDPATCH" Inventory.Icon "I_STMP" Inventory.PickupMessage "$TXT_MEDPATCH" HealthPickup.Autouse 3 @@ -31,7 +31,7 @@ ACTOR MedicalKit : HealthPickup 2012 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 15 - Tag "Medical kit" + Tag "$TAG_MEDICALKIT" Inventory.Icon "I_MDKT" Inventory.PickupMessage "$TXT_MEDICALKIT" HealthPickup.Autouse 3 @@ -54,7 +54,7 @@ ACTOR SurgeryKit : HealthPickup 83 +INVENTORY.INVBAR Health -100 Inventory.MaxAmount 5 - Tag "Surgery Kit" // "full_health" in the Teaser + Tag "$TAG_SURGERYKIT" Inventory.Icon "I_FULL" Inventory.PickupMessage "$TXT_SURGERYKIT" States @@ -75,7 +75,7 @@ ACTOR StrifeMap : MapRevealer 2026 ConversationID 164, 160, 163 +FLOORCLIP Inventory.PickupSound "misc/p_pkup" - Inventory.PickupMessage "$TXT_STRIFEMAP" + Inventory.PickupMessage "$TXT_STRIFEMAP" States { Spawn: @@ -94,7 +94,7 @@ ACTOR BeldinsRing : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 173, 165, 169 - Tag "Ring" + Tag "$TAG_BELDINSRING" Inventory.Icon "I_RING" Inventory.GiveQuest 1 Inventory.PickupMessage "$TXT_BELDINSRING" @@ -118,7 +118,7 @@ ACTOR OfferingChalice : Inventory 205 ConversationID 174, 166, 170 Radius 10 Height 16 - Tag "Offering Chalice" + Tag "$TAG_OFFERINGCHALICE" Inventory.Icon "I_RELC" Inventory.PickupMessage "$TXT_OFFERINGCHALICE" Inventory.GiveQuest 2 @@ -139,7 +139,7 @@ ACTOR Ear : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 175, 167, 171 - Tag "Ear" + Tag "$TAG_EAR" Inventory.Icon "I_EARS" Inventory.PickupMessage "$TXT_EAR" Inventory.GiveQuest 9 @@ -164,7 +164,7 @@ ACTOR BrokenPowerCoupling : Inventory 226 +INVENTORY.INVBAR Radius 16 Height 16 - Tag "Broken Power Coupling" + Tag "$TAG_BROKENCOUPLING" Inventory.MaxAmount 1 Inventory.Icon "I_COUP" Inventory.PickupMessage "$TXT_BROKENCOUPLING" @@ -190,7 +190,7 @@ ACTOR ShadowArmor : PowerupGiver 2024 +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND RenderStyle Translucent - Tag "Shadow Armor" + Tag "$TAG_SHADOWARMOR" Inventory.MaxAmount 2 Powerup.Type "Shadow" Inventory.Icon "I_SHD1" @@ -217,7 +217,7 @@ ACTOR EnvironmentalSuit : PowerupGiver 2025 -INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 5 Powerup.Type "Mask" - Tag "Environmental Suit" + Tag "$TAG_ENVSUIT" Inventory.Icon "I_MASK" Inventory.PickupSound "misc/i_pkup" Inventory.PickupMessage "$TXT_ENVSUIT" @@ -238,7 +238,7 @@ ACTOR GuardUniform : Inventory 90 ConversationID 162, 158, 161 +FLOORCLIP +INVENTORY.INVBAR - Tag "Guard Uniform" + Tag "$TAG_GUARDUNIFORM" Inventory.Icon "I_UNIF" Inventory.PickupMessage "$TXT_GUARDUNIFORM" Inventory.GiveQuest 15 @@ -259,7 +259,7 @@ ACTOR OfficersUniform : Inventory 52 ConversationID 163, 159, 162 +FLOORCLIP +INVENTORY.INVBAR - Tag "Officer's Uniform" + Tag "$TAG_OFFICERSUNIFORM" Inventory.Icon "I_OFIC" Inventory.PickupMessage "$TXT_OFFICERSUNIFORM" States @@ -280,7 +280,7 @@ ACTOR FlameThrowerParts : Inventory +FLOORCLIP +INVENTORY.INVBAR Inventory.Icon "I_BFLM" - Tag "Flame Thrower Parts" + Tag "$TAG_FTHROWERPARTS" Inventory.PickupMessage "$TXT_FTHROWERPARTS" States { @@ -300,7 +300,7 @@ ACTOR InterrogatorReport : Inventory Game Strife ConversationID 308, 289, 306 +FLOORCLIP - Tag "Report" + Tag "$TAG_REPORT" Inventory.PickupMessage "$TXT_REPORT" States { @@ -319,7 +319,7 @@ ACTOR Info : Inventory ConversationID 300, 282, 299 +FLOORCLIP +INVENTORY.INVBAR - Tag "Info" + Tag "$TAG_INFO" Inventory.Icon "I_TOKN" Inventory.PickupMessage "$TXT_INFO" States @@ -340,7 +340,7 @@ ACTOR Targeter : PowerupGiver 207 +FLOORCLIP +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND - Tag "Targeter" + Tag "$TAG_TARGETER" Powerup.Type "Targeter" Inventory.MaxAmount 5 Inventory.Icon "I_TARG" @@ -361,7 +361,7 @@ ACTOR Communicator : Inventory 206 Game Strife ConversationID 176, 168, 172 +NOTDMATCH - Tag "Communicator" + Tag "$TAG_COMMUNICATOR" Inventory.Icon "I_COMM" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_COMMUNICATOR" @@ -389,7 +389,7 @@ ACTOR DegninOre : Inventory 59 native +FLOORCLIP +INCOMBAT +INVENTORY.INVBAR - Tag "Degnin Ore" + Tag "$TAG_DEGNINORE" DeathSound "ore/explode" Inventory.Icon "I_XPRK" Inventory.PickupMessage "$TXT_DEGNINORE" @@ -418,7 +418,7 @@ ACTOR GunTraining : Inventory +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Accuracy" + Tag "$TAG_GUNTRAINING" Inventory.Icon "I_GUNT" States { @@ -438,7 +438,7 @@ ACTOR HealthTraining : Inventory native +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Toughness" + Tag "$TAG_HEALTHTRAINING" Inventory.Icon "I_HELT" States { @@ -459,7 +459,7 @@ ACTOR Scanner : PowerupGiver 2027 native +FLOORCLIP +INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 1 - Tag "Scanner" + Tag "$TAG_SCANNER" Inventory.Icon "I_PMUP" Powerup.Type "Scanner" Inventory.PickupSound "misc/i_pkup" @@ -479,8 +479,8 @@ ACTOR PrisonPass : Key native Game Strife ConversationID 304, 286, 303 Inventory.Icon "I_TOKN" - Tag "Prison Pass" - Inventory.PickupMessage "TXT_PRISONPASS" + Tag "$TAG_PRISONPASS" + Inventory.PickupMessage "$TXT_PRISONPASS" States { Spawn: @@ -509,7 +509,7 @@ ACTOR DummyStrifeItem : Inventory native ACTOR RaiseAlarm : DummyStrifeItem native { ConversationID 301, 283, 300 - Tag "Alarm" + Tag "$TAG_ALARM" } // Open door tag 222 -------------------------------------------------------- @@ -538,7 +538,7 @@ ACTOR OpenDoor224 : DummyStrifeItem native ACTOR AmmoFillup : DummyStrifeItem native { ConversationID 298,280,297 - Tag "Ammo" + Tag "$TAG_AMMOFILLUP" } // Health ------------------------------------------------------------------- @@ -546,7 +546,7 @@ ACTOR AmmoFillup : DummyStrifeItem native ACTOR HealthFillup : DummyStrifeItem native { ConversationID 299,281,298 - Tag "Health" + Tag "$TAG_HEALTHFILLUP" } // Upgrade Stamina ---------------------------------------------------------- diff --git a/wadsrc/static/actors/strife/strifekeys.txt b/wadsrc/static/actors/strife/strifekeys.txt index d128cbbd6..b304f83bc 100644 --- a/wadsrc/static/actors/strife/strifekeys.txt +++ b/wadsrc/static/actors/strife/strifekeys.txt @@ -13,7 +13,7 @@ ACTOR BaseKey : StrifeKey 230 Game Strife ConversationID 133, 129, 132 Inventory.Icon "I_FUSL" - Tag "Base Key" + Tag "$TAG_BASEKEY" Inventory.PickupMessage "$TXT_BASEKEY" States { @@ -31,7 +31,7 @@ ACTOR GovsKey : StrifeKey Game Strife ConversationID 134, 130, 133 Inventory.Icon "I_REBL" - Tag "Govs Key" // "Rebel_Key" in the Teaser + Tag "$TAG_GOVSKEY" Inventory.PickupMessage "$TXT_GOVSKEY" States { @@ -49,7 +49,7 @@ ACTOR Passcard : StrifeKey 185 Game Strife ConversationID 135, 131, 134 Inventory.Icon "I_TPAS" - Tag "Passcard" + Tag "$TAG_PASSCARD" Inventory.PickupMessage "$TXT_PASSCARD" States { @@ -67,7 +67,7 @@ ACTOR IDBadge : StrifeKey 184 Game Strife ConversationID 136, 132, 135 Inventory.Icon "I_CRD1" - Tag "ID Badge" + Tag "$TAG_IDBADGE" Inventory.PickupMessage "$TXT_IDBADGE" States { @@ -85,7 +85,7 @@ ACTOR PrisonKey : StrifeKey Game Strife ConversationID 137, 133, 136 Inventory.Icon "I_PRIS" - Tag "Prison Key" + Tag "$TAG_PRISONKEY" Inventory.GiveQuest 11 Inventory.PickupMessage "$TXT_PRISONKEY" States @@ -104,7 +104,7 @@ ACTOR SeveredHand : StrifeKey 91 Game Strife ConversationID 138, 134, 137 Inventory.Icon "I_HAND" - Tag "Severed Hand" + Tag "$TAG_SEVEREDHAND" Inventory.GiveQuest 12 Inventory.PickupMessage "$TXT_SEVEREDHAND" States @@ -123,7 +123,7 @@ ACTOR Power1Key : StrifeKey Game Strife ConversationID 139, 135, 138 Inventory.Icon "I_PWR1" - Tag "Power1 Key" + Tag "$TAG_POWER1KEY" Inventory.PickupMessage "$TXT_POWER1KEY" States { @@ -141,7 +141,7 @@ ACTOR Power2Key : StrifeKey Game Strife ConversationID 140, 136, 139 Inventory.Icon "I_PWR2" - Tag "Power2 Key" + Tag "$TAG_POWER2KEY" Inventory.PickupMessage "$TXT_POWER2KEY" States { @@ -159,7 +159,7 @@ ACTOR Power3Key : StrifeKey Game Strife ConversationID 141, 137, 140 Inventory.Icon "I_PWR3" - Tag "Power3 Key" + Tag "$TAG_POWER3KEY" Inventory.PickupMessage "$TXT_POWER3KEY" States { @@ -177,7 +177,7 @@ ACTOR GoldKey : StrifeKey 40 Game Strife ConversationID 142, 138, 141 Inventory.Icon "I_KY1G" - Tag "Gold Key" + Tag "$TAG_GOLDKEY" Inventory.PickupMessage "$TXT_GOLDKEY" States { @@ -195,7 +195,7 @@ ACTOR IDCard : StrifeKey 13 Game Strife ConversationID 143, 139, 142 Inventory.Icon "I_CRD2" - Tag "ID Card" + Tag "$TAG_IDCARD" Inventory.PickupMessage "$TXT_IDCARD" States { @@ -213,7 +213,7 @@ ACTOR SilverKey : StrifeKey 38 Game Strife ConversationID 144, 140, 143 Inventory.Icon "I_KY2S" - Tag "Silver Key" + Tag "$TAG_SILVERKEY" Inventory.PickupMessage "$TXT_SILVERKEY" States { @@ -231,7 +231,7 @@ ACTOR OracleKey : StrifeKey 61 Game Strife ConversationID 145, 141, 144 Inventory.Icon "I_ORAC" - Tag "Oracle Key" + Tag "$TAG_ORACLEKEY" Inventory.PickupMessage "$TXT_ORACLEKEY" States { @@ -249,7 +249,7 @@ ACTOR MilitaryID : StrifeKey Game Strife ConversationID 146, 142, 145 Inventory.Icon "I_GYID" - Tag "Military ID" + Tag "$TAG_MILITARYID" Inventory.PickupMessage "$TXT_MILITARYID" States { @@ -267,7 +267,7 @@ ACTOR OrderKey : StrifeKey 86 Game Strife ConversationID 147, 143, 146 Inventory.Icon "I_FUBR" - Tag "Order Key" + Tag "$TAG_ORDERKEY" Inventory.PickupMessage "$TXT_ORDERKEY" States { @@ -285,7 +285,7 @@ ACTOR WarehouseKey : StrifeKey 166 Game Strife ConversationID 148, 144, 147 Inventory.Icon "I_WARE" - Tag "Warehouse Key" + Tag "$TAG_WAREHOUSEKEY" Inventory.PickupMessage "$TXT_WAREHOUSEKEY" States { @@ -303,7 +303,7 @@ ACTOR BrassKey : StrifeKey 39 Game Strife ConversationID 149, 145, 148 Inventory.Icon "I_KY3B" - Tag "Brass Key" + Tag "$TAG_BRASSKEY" Inventory.PickupMessage "$TXT_BRASSKEY" States { @@ -321,7 +321,7 @@ ACTOR RedCrystalKey : StrifeKey 192 Game Strife ConversationID 150, 146, 149 Inventory.Icon "I_RCRY" - Tag "Red Crystal Key" + Tag "$TAG_REDCRYSTALKEY" Inventory.PickupMessage "$TXT_REDCRYSTAL" States { @@ -339,7 +339,7 @@ ACTOR BlueCrystalKey : StrifeKey 193 Game Strife ConversationID 151, 147, 150 Inventory.Icon "I_BCRY" - Tag "Blue Crystal Key" + Tag "$TAG_BLUECRYSTALKEY" Inventory.PickupMessage "$TXT_BLUECRYSTAL" States { @@ -357,7 +357,7 @@ ACTOR ChapelKey : StrifeKey 195 Game Strife ConversationID 152, 148, 151 Inventory.Icon "I_CHAP" - Tag "Chapel Key" + Tag "$TAG_CHAPELKEY" Inventory.PickupMessage "$TXT_CHAPELKEY" States { @@ -375,7 +375,7 @@ ACTOR CatacombKey : StrifeKey Game Strife ConversationID 153, 149, 152 Inventory.Icon "I_TUNL" - Tag "Catacomb Key" // "Tunnel_Key" in the Teaser + Tag "$TAG_CATACOMBKEY" Inventory.GiveQuest 28 Inventory.PickupMessage "$TXT_CATACOMBKEY" States @@ -394,7 +394,7 @@ ACTOR SecurityKey : StrifeKey Game Strife ConversationID 154, 150, 153 Inventory.Icon "I_SECK" - Tag "Security Key" + Tag "$TAG_SECURITYKEY" Inventory.PickupMessage "$TXT_SECURITYKEY" States { @@ -412,7 +412,7 @@ ACTOR CoreKey : StrifeKey 236 Game Strife ConversationID 155, 151, 154 Inventory.Icon "I_GOID" - Tag "Core Key" // "New_Key1" in the Teaser + Tag "$TAG_COREKEY" Inventory.PickupMessage "$TXT_COREKEY" States { @@ -430,7 +430,7 @@ ACTOR MaulerKey : StrifeKey 233 Game Strife ConversationID 156, 152, 155 Inventory.Icon "I_BLTK" - Tag "Mauler Key" // "New_Key2" in the Teaser + Tag "$TAG_MAULERKEY" Inventory.PickupMessage "$TXT_MAULERKEY" States { @@ -448,7 +448,7 @@ ACTOR FactoryKey : StrifeKey 234 Game Strife ConversationID 157, 153, 156 Inventory.Icon "I_PROC" - Tag "Factory Key" // "New_Key3" in the Teaser + Tag "$TAG_FACTORYKEY" Inventory.PickupMessage "$TXT_FACTORYKEY" States { @@ -465,8 +465,8 @@ ACTOR MineKey : StrifeKey 235 { Game Strife ConversationID 158, 154, 157 - Inventory.Icon "I_MINE" // "New_Key4" in the Teaser - Tag "Mine_Key" + Inventory.Icon "I_MINE" + Tag "$TAG_MINEKEY" Inventory.PickupMessage "$TXT_MINEKEY" States { @@ -484,7 +484,7 @@ ACTOR NewKey5 : StrifeKey Game Strife ConversationID 159, 155, 158 Inventory.Icon "I_BLTK" - Tag "New Key5" + Tag "$TAG_NEWKEY5" Inventory.PickupMessage "$TXT_NEWKEY5" States { @@ -505,7 +505,7 @@ ACTOR OraclePass : Inventory Inventory.Icon "I_OTOK" Inventory.GiveQuest 18 Inventory.PickupMessage "$TXT_ORACLEPASS" - Tag "Oracle Pass" + Tag "$TAG_ORACLEPASS" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifestuff.txt b/wadsrc/static/actors/strife/strifestuff.txt index d8ecd9bbb..a80685c35 100644 --- a/wadsrc/static/actors/strife/strifestuff.txt +++ b/wadsrc/static/actors/strife/strifestuff.txt @@ -1662,7 +1662,6 @@ ACTOR TargetPractice 208 ACTOR ForceFieldGuard 25 native { Game Strife - ConversationID 0 Health 10 Radius 2 Height 1 diff --git a/wadsrc/static/actors/strife/strifeweapons.txt b/wadsrc/static/actors/strife/strifeweapons.txt index d287ace08..5e3a419d5 100644 --- a/wadsrc/static/actors/strife/strifeweapons.txt +++ b/wadsrc/static/actors/strife/strifeweapons.txt @@ -48,6 +48,8 @@ ACTOR PunchDagger : StrifeWeapon Game Strife Weapon.SelectionOrder 3900 +WEAPON.NOALERT + Obituary "$OB_MPPUNCHDAGGER" + Tag "$TAG_PUNCHDAGGER" action native A_JabDagger (); @@ -109,6 +111,7 @@ ACTOR ElectricBolt : StrifeZap1 SeeSound "misc/swish" ActiveSound "misc/swish" DeathSound "weapons/xbowhit" + Obituary "$OB_MPELECTRICBOLT" States { Spawn: @@ -132,6 +135,7 @@ ACTOR PoisonBolt native MaxStepHeight 4 SeeSound "misc/swish" ActiveSound "misc/swish" + Obituary "$OB_MPPOISONBOLT" States { Spawn: @@ -158,8 +162,8 @@ ACTOR StrifeCrossbow : StrifeWeapon 2001 Weapon.AmmoType1 "ElectricBolts" Weapon.SisterWeapon "StrifeCrossbow2" Inventory.PickupMessage "$TXT_STRIFECROSSBOW" + Tag "$TAG_STRIFECROSSBOW1" Inventory.Icon "CBOWA0" - Tag "Crossbow" action native A_ClearFlash (); action native A_ShowElectricFlash (); @@ -205,6 +209,7 @@ ACTOR StrifeCrossbow2 : StrifeCrossbow Weapon.AmmoGive1 0 Weapon.AmmoType1 "PoisonBolts" Weapon.SisterWeapon "StrifeCrossbow" + Tag "$TAG_STRIFECROSSBOW2" States { @@ -243,8 +248,9 @@ actor AssaultGun : StrifeWeapon 2002 Weapon.AmmoGive1 20 Weapon.AmmoType1 "ClipOfBullets" Inventory.Icon "RIFLA0" - Tag "Assault Gun" + Tag "$TAG_ASSAULTGUN" Inventory.PickupMessage "$TXT_ASSAULTGUN" + Obituary "$OB_MPASSAULTGUN" States { Spawn: @@ -299,7 +305,7 @@ ACTOR MiniMissileLauncher : StrifeWeapon 2003 Weapon.AmmoGive1 8 Weapon.AmmoType1 "MiniMissiles" Inventory.Icon "MMSLA0" - Tag "Mini Missile Launcher" + Tag "$TAG_MMLAUNCHER" Inventory.PickupMessage "$TXT_MMLAUNCHER" action native A_FireMiniMissile (); @@ -376,6 +382,7 @@ ACTOR MiniMissile MaxStepHeight 4 SeeSound "weapons/minimissile" DeathSound "weapons/minimissilehit" + Obituary "$OB_MPMINIMISSILELAUNCHER" States { Spawn: @@ -407,7 +414,7 @@ ACTOR FlameThrower : StrifeWeapon 2005 Weapon.ReadySound "weapons/flameidle" Weapon.AmmoType1 "EnergyPod" Inventory.Icon "FLAMA0" - Tag "Flame Thrower" + Tag "$TAG_FLAMER" Inventory.PickupMessage "$TXT_FLAMER" action native A_FireFlamer (); @@ -451,6 +458,7 @@ ACTOR FlameMissile MaxStepHeight 4 RenderStyle Add SeeSound "weapons/flamethrower" + Obituary "$OB_MPFLAMETHROWER" action native A_FlameDie (); @@ -483,8 +491,9 @@ ACTOR Mauler : StrifeWeapon 2004 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler2" Inventory.Icon "TRPDA0" - Tag "Mauler" + Tag "$TAG_MAULER1" Inventory.PickupMessage "$TXT_MAULER" + Obituary "$OB_MPMAULER1" action native A_FireMauler1 (); @@ -525,6 +534,8 @@ ACTOR Mauler2 : Mauler Weapon.AmmoGive1 0 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler" + Obituary "$OB_MPMAULER2" + Tag "$TAG_MAULER2" action native A_FireMauler2Pre (); action native A_FireMauler2 (); @@ -586,6 +597,7 @@ ACTOR MaulerTorpedo RenderStyle Add SeeSound "weapons/mauler2fire" DeathSound "weapons/mauler2hit" + Obituary "$OB_MPMAULER" action native A_MaulerTorpedoWave (); @@ -616,6 +628,7 @@ ACTOR MaulerTorpedoWave +STRIFEDAMAGE MaxStepHeight 4 RenderStyle Add + Obituary "$OB_MPMAULER" States { Spawn: @@ -650,6 +663,7 @@ ACTOR HEGrenade BounceCount 2 SeeSound "weapons/hegrenadeshoot" DeathSound "weapons/hegrenadebang" + Obituary "$OB_MPSTRIFEGRENADE" States { Spawn: @@ -687,6 +701,7 @@ ACTOR PhosphorousGrenade BounceCount 2 SeeSound "weapons/phgrenadeshoot" DeathSound "weapons/phgrenadebang" + Obituary "$OB_MPPHOSPHOROUSGRENADE" States { Spawn: @@ -710,6 +725,7 @@ ACTOR PhosphorousFire native +NODAMAGETHRUST +DONTSPLASH RenderStyle Add + Obituary "$OB_MPPHOSPHOROUSGRENADE" action native A_Burnarea (); action native A_Burnination (); @@ -748,7 +764,7 @@ ACTOR StrifeGrenadeLauncher : StrifeWeapon 154 Weapon.AmmoType1 "HEGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher2" Inventory.Icon "GRNDA0" - Tag "Grenade Launcher" + Tag "$TAG_GLAUNCHER1" Inventory.PickupMessage "$TXT_GLAUNCHER" action native A_FireGrenade (class grenadetype, int angleofs, state flash); @@ -794,6 +810,7 @@ ACTOR StrifeGrenadeLauncher2 : StrifeGrenadeLauncher Weapon.AmmoGive1 0 Weapon.AmmoType1 "PhosphorusGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher" + Tag "$TAG_GLAUNCHER2" States { diff --git a/wadsrc/static/actors/strife/templar.txt b/wadsrc/static/actors/strife/templar.txt index 214804b34..c20aed49b 100644 --- a/wadsrc/static/actors/strife/templar.txt +++ b/wadsrc/static/actors/strife/templar.txt @@ -20,7 +20,7 @@ ACTOR Templar 3003 DeathSound "templar/death" ActiveSound "templar/active" CrushPainSound "misc/pcrush" - Tag "TEMPLAR" + Tag "$TAG_TEMPLAR" HitObituary "$OB_TEMPLARHIT" Obituary "$OB_TEMPLAR" DropItem "EnergyPod" diff --git a/wadsrc/static/animdefs.txt b/wadsrc/static/animdefs.txt index a2a4498e2..8bd2ee99a 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 09f38a80b..98528363d 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 000000000..c9ee9b6e3 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 000000000..2fb4a827d 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 000000000..fc45dcd7b 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 000000000..ab63b4310 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 000000000..d785b6788 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 2f23edc2e..5f0eff88e 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 9e313cf1e..5a76298cb 100644 --- a/wadsrc/static/mapinfo/chex.txt +++ b/wadsrc/static/mapinfo/chex.txt @@ -42,6 +42,16 @@ gameinfo 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 b3e631650..064f3777a 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 3de271756..9fd4b9637 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 0c78f1184..b9f88e2b9 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 344f4eb41..8f0962a50 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 6c213d81e..ffb70e696 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 000000000..ee624ed75 --- /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 000000000..19f27e84e --- /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 b72641d30..60d582f70 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 4efe7e185..247eb9d57 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 @@ /> + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -1872,14 +1880,6 @@ Outputs="$(IntDir)/$(InputName).obj" /> - - - @@ -2069,14 +2069,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - @@ -2087,6 +2079,14 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + - - - + + + - - - @@ -5387,6 +5379,14 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> + + + @@ -5457,16 +5457,20 @@ RelativePath=".\src\sound\music_dumb.cpp" > + + + + + + @@ -5677,7 +5689,7 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +