From 1063a98c47c3272138090546a4de59e147562812 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 13 Aug 2010 22:12:42 +0000 Subject: [PATCH 01/84] - Maybe allow assembly on Intel Macs? - attempt 2 SVN r2537 (trunk) --- src/CMakeLists.txt | 4 ++-- src/asm_ia32/a.asm | 9 +++++---- src/asm_ia32/tmap.asm | 8 ++++---- src/asm_ia32/tmap2.asm | 8 ++++---- src/asm_ia32/tmap3.asm | 8 ++++---- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b763dee33..80d5e38004 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -299,9 +299,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 ) diff --git a/src/asm_ia32/a.asm b/src/asm_ia32/a.asm index 07b00fe255..d68fba116f 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 04b86d09d6..e9713131f8 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: @@ -1748,8 +1748,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 63eee0044d..e1f166878a 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 3791b4ff09..3161ff3689 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 From b288a7a416375c960aa64e979776bfd8258aeb80 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 14 Aug 2010 06:25:38 +0000 Subject: [PATCH 02/84] - added option to set item pickup color through MAPINFO's GAMEINFO block. SVN r2539 (trunk) --- src/g_shared/shared_sbar.cpp | 4 +++- src/gi.cpp | 1 + src/gi.h | 1 + wadsrc/static/mapinfo/chex.txt | 1 + wadsrc/static/mapinfo/doomcommon.txt | 1 + wadsrc/static/mapinfo/heretic.txt | 1 + wadsrc/static/mapinfo/hexen.txt | 1 + wadsrc/static/mapinfo/strife.txt | 1 + 8 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/g_shared/shared_sbar.cpp b/src/g_shared/shared_sbar.cpp index ef48d211a5..beb459cdab 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/gi.cpp b/src/gi.cpp index e0813479ab..03fea70a93 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -266,6 +266,7 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_INT(defKickback, "defKickback") GAMEINFOKEY_CSTRING(SkyFlatName, "SkyFlatName", 8) GAMEINFOKEY_STRING(translator, "translator") + GAMEINFOKEY_COLOR(pickupcolor, "pickupcolor") GAMEINFOKEY_COLOR(defaultbloodcolor, "defaultbloodcolor") GAMEINFOKEY_COLOR(defaultbloodparticlecolor, "defaultbloodparticlecolor") GAMEINFOKEY_STRING(backpacktype, "backpacktype") diff --git a/src/gi.h b/src/gi.h index a87098555b..09a9d37a84 100644 --- a/src/gi.h +++ b/src/gi.h @@ -106,6 +106,7 @@ struct gameinfo_t int defaultrespawntime; int defaultdropstyle; int player5start; + DWORD pickupcolor; const char *GetFinalePage(unsigned int num) const; }; diff --git a/wadsrc/static/mapinfo/chex.txt b/wadsrc/static/mapinfo/chex.txt index 9e313cf1e2..deefea4d3b 100644 --- a/wadsrc/static/mapinfo/chex.txt +++ b/wadsrc/static/mapinfo/chex.txt @@ -42,6 +42,7 @@ gameinfo endoom = "ENDOOM" player5start = 4001 drawreadthis = true + pickupcolor = "d6 ba 45" } skill baby diff --git a/wadsrc/static/mapinfo/doomcommon.txt b/wadsrc/static/mapinfo/doomcommon.txt index 3de2717566..f677835093 100644 --- a/wadsrc/static/mapinfo/doomcommon.txt +++ b/wadsrc/static/mapinfo/doomcommon.txt @@ -40,6 +40,7 @@ gameinfo defaultdropstyle = 1 endoom = "ENDOOM" player5start = 4001 + pickupcolor = "d6 ba 45" } skill baby diff --git a/wadsrc/static/mapinfo/heretic.txt b/wadsrc/static/mapinfo/heretic.txt index 0c78f1184a..4ca7272725 100644 --- a/wadsrc/static/mapinfo/heretic.txt +++ b/wadsrc/static/mapinfo/heretic.txt @@ -41,6 +41,7 @@ gameinfo defaultdropstyle = 1 endoom = "ENDTEXT" player5start = 4001 + pickupcolor = "d6 ba 45" } skill baby diff --git a/wadsrc/static/mapinfo/hexen.txt b/wadsrc/static/mapinfo/hexen.txt index 344f4eb412..16ebcf5b48 100644 --- a/wadsrc/static/mapinfo/hexen.txt +++ b/wadsrc/static/mapinfo/hexen.txt @@ -39,6 +39,7 @@ gameinfo defaultrespawntime = 12 defaultdropstyle = 1 player5start = 9100 + pickupcolor = "d6 ba 45" } skill baby diff --git a/wadsrc/static/mapinfo/strife.txt b/wadsrc/static/mapinfo/strife.txt index 6c213d81eb..e5c9043ee8 100644 --- a/wadsrc/static/mapinfo/strife.txt +++ b/wadsrc/static/mapinfo/strife.txt @@ -42,6 +42,7 @@ gameinfo defaultdropstyle = 2 endoom = "ENDSTRF" player5start = 5 + pickupcolor = "d6 ba 45" } skill baby From 1fda9421ac1f310e61aaca76c5975722feef9960 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 14 Aug 2010 20:00:47 +0000 Subject: [PATCH 03/84] Backported from GZDoom: - Fixed: P_LoopSidedefs() needs to clean out sidetemp[] because when it's called a second time, the maximum of the number of vertices and that of sides may have increased compared to when P_AllocateSideDefs() created the array, which led to access violations. SVN r2541 (trunk) --- src/p_setup.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 636d4d69f4..4ddb79e3c5 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -2132,10 +2132,11 @@ static void P_LoopSidedefs () { 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) { From a0d16bc99a884b172a0f6894be1a6f0809cffb7b Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Sat, 14 Aug 2010 21:01:49 +0000 Subject: [PATCH 04/84] - Fixed: The center flag for drawimage didn't work. - Fixed: GCC didn't like casting from seg_t* to unsigned int on 64-bit systems. SVN r2542 (trunk) --- src/g_shared/sbarinfo.cpp | 18 +++++++++--------- src/p_setup.cpp | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 6895ce321c..83f9e9bef7 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. @@ -1343,7 +1343,7 @@ 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') { diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 4ddb79e3c5..332e1d0774 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -1325,8 +1325,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 +1336,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; From 66f6115c86db05471ff9aa4a9f1616c27bb98b6d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 15 Aug 2010 10:02:10 +0000 Subject: [PATCH 05/84] - added a compatibility option to render all segs of a polyobject in the center's subsector and automatically set it for Hexen MAP36 and HEXDD MAP47. SVN r2543 (trunk) --- src/compatibility.cpp | 14 ++++++++++++++ src/d_iwad.cpp | 4 ++-- src/d_main.cpp | 7 +++++++ src/doomdef.h | 1 + src/g_mapinfo.cpp | 1 + src/gi.h | 4 +++- src/m_options.cpp | 1 + src/po_man.cpp | 28 +++++++++++++++++++++++++--- src/po_man.h | 1 + 9 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/compatibility.cpp b/src/compatibility.cpp index e091a7f1eb..ab1c98ca7d 100644 --- a/src/compatibility.cpp +++ b/src/compatibility.cpp @@ -118,6 +118,7 @@ static FCompatOption Options[] = { "spritesort", COMPATF_SPRITESORT, 0 }, { "hitscan", COMPATF_HITSCAN, 0 }, { "lightlevel", COMPATF_LIGHT, 0 }, + { "polyobj", COMPATF_POLYOBJ, 0 }, { NULL, 0, 0 } }; @@ -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_iwad.cpp b/src/d_iwad.cpp index e5939e1a7e..9a49c533b2 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 }, diff --git a/src/d_main.cpp b/src/d_main.cpp index 520127b9bc..deef1d6c72 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -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) @@ -480,7 +481,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 +565,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); //========================================================================== // diff --git a/src/doomdef.h b/src/doomdef.h index 3aa527523b..1108e770f3 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -329,6 +329,7 @@ enum COMPATF_SPRITESORT = 1 << 27, // Invert sprite sorting order for sprites of equal distance COMPATF_HITSCAN = 1 << 28, // Hitscans use original blockmap anf hit check code. COMPATF_LIGHT = 1 << 29, // Find neighboring light level like Doom + COMPATF_POLYOBJ = 1 << 30, // Draw polyobjects the old fashioned way }; // Emulate old bugs for select maps. These are not exposed by a cvar diff --git a/src/g_mapinfo.cpp b/src/g_mapinfo.cpp index 1eb6363fdd..9378db768d 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -1404,6 +1404,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 }, diff --git a/src/gi.h b/src/gi.h index 09a9d37a84..cbb1a02bbf 100644 --- a/src/gi.h +++ b/src/gi.h @@ -43,7 +43,9 @@ #define GI_MENUHACK_EXTENDED 0x00000004 // (Heretic) #define GI_TEASER2 0x00000008 // Alternate version of the Strife Teaser #define GI_COMPATSHORTTEX 0x00000010 // always force COMPAT_SHORTTEX for IWAD maps. -#define GI_COMPATSTAIRS 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 #include "gametype.h" diff --git a/src/m_options.cpp b/src/m_options.cpp index 7e17ef7986..2357c930af 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -1133,6 +1133,7 @@ static menuitem_t CompatibilityItems[] = { { bitflag, "Invert sprite sorting", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_SPRITESORT} }, { bitflag, "Use Doom code for hitscan checks", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_HITSCAN} }, { bitflag, "Cripple sound for silent BFG trick", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_MAGICSILENCE} }, + { bitflag, "Draw polyobjects like Hexen", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_POLYOBJ} }, { discrete, "Interpolate monster movement", {&nomonsterinterpolation}, {2.0}, {0.0}, {0.0}, {NoYes} }, }; diff --git a/src/po_man.cpp b/src/po_man.cpp index 1ca8d9a3aa..1aa065c19c 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -1752,8 +1752,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); } //========================================================================== @@ -2193,7 +2193,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 d76dff55ff..074426d889 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 From 0657121847ade656974155459021977a3347d182 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 15 Aug 2010 14:09:17 +0000 Subject: [PATCH 06/84] - Backport from GZDoom: P_LoopSidedefs() needed another fix as it shouldn't try to report errors found on the second loop. SVN r2544 (trunk) --- src/p_setup.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 332e1d0774..35ec40a43f 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -2128,7 +2128,7 @@ 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; @@ -2193,7 +2193,7 @@ static void P_LoopSidedefs () right = sidetemp[right].b.first; - if (right == NO_SIDE) + if (firstloop && right == NO_SIDE) { // There is no right side! Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); continue; @@ -3655,7 +3655,7 @@ void P_SetupLevel (char *lumpname, int position) } times[6].Clock(); - P_LoopSidedefs (); + P_LoopSidedefs (true); times[6].Unclock(); linemap.Clear(); @@ -3845,9 +3845,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 (ForceNodeBuild) P_LoopSidedefs (false); PO_Init (); // Initialize the polyobjs times[16].Unclock(); From 1fa4bbf69cf7d76ef0d2e8c9d78bf9aca01f8cfc Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 15 Aug 2010 19:54:59 +0000 Subject: [PATCH 07/84] - Added FluidSynth support as snd_mididevice -5. Only tested with Linux. I will have to try compiling it myself on Windows to see if it's really that slow or if Ubuntu just ships an unoptimized version, because performance is pretty pathetic when compared to the other options. (I understand that it's a complete SoundFont2 renderer, so it is understandably slower than something like TiMidity++, but still. Does it really need to be around 10x slower? I played with the chorus, reverb, and interpolation settings, and none of them seemed to make much difference in performance.) SVN r2545 (trunk) --- FindFluidSynth.cmake | 23 ++++++++++ src/CMakeLists.txt | 21 +++++++-- src/sound/fmodsound.cpp | 31 +++++++++---- src/sound/fmodsound.h | 3 +- src/sound/i_music.cpp | 18 ++++++++ src/sound/i_music.h | 3 ++ src/sound/i_musicinterns.h | 67 ++++++++++++++++++++++++++- src/sound/music_midi_base.cpp | 9 ++-- src/sound/music_midistream.cpp | 82 +++++++++++++++++++++++++++++++++- 9 files changed, 239 insertions(+), 18 deletions(-) create mode 100644 FindFluidSynth.cmake diff --git a/FindFluidSynth.cmake b/FindFluidSynth.cmake new file mode 100644 index 0000000000..7d5cb6a8ed --- /dev/null +++ b/FindFluidSynth.cmake @@ -0,0 +1,23 @@ +# - Find fluidsynth +# Find the native fluidsynth includes and library +# +# FLUIDSYNTH_INCLUDE_DIR - where to find fluidsynth.h +# FLUIDSYNTH_LIBRARIES - List of libraries when using fluidsynth. +# FLUIDSYNTH_FOUND - True if fluidsynth found. + + +IF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + # Already in cache, be silent + SET(FluidSynth_FIND_QUIETLY TRUE) +ENDIF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + +FIND_PATH(FLUIDSYNTH_INCLUDE_DIR fluidsynth.h) + +FIND_LIBRARY(FLUIDSYNTH_LIBRARIES NAMES fluidsynth ) +MARK_AS_ADVANCED( FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR ) + +# handle the QUIETLY and REQUIRED arguments and set FLUIDSYNTH_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FluidSynth DEFAULT_MSG FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR) + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80d5e38004..d6c0aa3821 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,6 +31,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" @@ -236,6 +240,10 @@ else( FMOD_LIBRARY ) endif( FMOD_LIBRARY ) +# Search for FluidSynth + +include( ../FindFluidSynth.cmake ) + # Search for NASM if( NOT NO_ASM ) @@ -370,7 +378,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" ) @@ -476,8 +485,10 @@ add_custom_target( revision_check ALL # Libraries ZDoom needs -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}" ) + +message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) +set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${FMOD_LIBRARY}" "${FLUIDSYNTH_LIBRARIES}" ) +include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${FLUIDSYNTH_INCLUDE_DIR}" ) # Start defining source files for ZDoom @@ -572,6 +583,9 @@ else( SSE_MATTERS ) set( X86_SOURCES ) endif( SSE_MATTERS ) +if( FLUIDSYNTH_FOUND ) + add_definitions( -DHAVE_FLUIDSYNTH ) +endif( FLUIDSYNTH_FOUND ) add_executable( zdoom WIN32 autostart.cpp @@ -793,6 +807,7 @@ add_executable( zdoom WIN32 sound/music_mus_midiout.cpp sound/music_mus_opl.cpp sound/music_stream.cpp + sound/music_fluidsynth_mididevice.cpp sound/music_timidity_mididevice.cpp sound/music_win_mididevice.cpp textures/automaptexture.cpp diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 30efe22d9c..bcd84b9fd0 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -101,6 +101,7 @@ EXTERN_CVAR (Int, snd_buffersize) EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Bool, snd_pitched) EXTERN_CVAR (Int, snd_channels) +EXTERN_CVAR (String, snd_midipatchset) extern int sfx_empty; @@ -115,7 +116,6 @@ CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_profile, false, 0) // Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter. @@ -644,6 +644,7 @@ bool FMODSoundRenderer::Init() ChannelGroupTargetUnit = NULL; SfxReverbHooked = false; SfxReverbPlaceholder = NULL; + SfxHeadMixer = NULL; OutputPlugin = 0; Printf("I_InitSound: Initializing FMOD\n"); @@ -706,7 +707,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; } @@ -1014,6 +1016,12 @@ bool FMODSoundRenderer::Init() result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxReverbPlaceholder); if (result == FMOD_OK) { + result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxHeadMixer); + result = sfx_head->addInput(SfxHeadMixer, &SfxConnection); + result = sfx_head->disconnectFrom(pausable_head); + sfx_head = SfxHeadMixer; + SfxHeadMixer->setActive(true); + SfxHeadMixer->setBypass(false); // Replace the PausableSFX->SFX connection with // PausableSFX->ReverbPlaceholder->SFX. result = SfxReverbPlaceholder->addInput(pausable_head, NULL); @@ -1023,13 +1031,13 @@ bool FMODSoundRenderer::Init() result = sfx_head->addInput(SfxReverbPlaceholder, &connection); if (result == FMOD_OK) { - sfx_head->disconnectFrom(pausable_head); +// sfx_head->disconnectFrom(pausable_head); SfxReverbPlaceholder->setActive(true); SfxReverbPlaceholder->setBypass(true); // The placeholder now takes the place of the pausable_head // for the following connections. pausable_head = SfxReverbPlaceholder; - SfxConnection = connection; + // SfxConnection = connection; } } else @@ -1038,6 +1046,7 @@ bool FMODSoundRenderer::Init() SfxReverbPlaceholder = NULL; } } +#if 1 result = WaterLP->addInput(pausable_head, NULL); WaterLP->setActive(false); WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp); @@ -1069,6 +1078,7 @@ bool FMODSoundRenderer::Init() { result = sfx_head->addInput(WaterLP, NULL); } +#endif } } } @@ -1147,6 +1157,11 @@ void FMODSoundRenderer::Shutdown() SfxReverbPlaceholder->release(); SfxReverbPlaceholder = NULL; } + if (SfxHeadMixer != NULL) + { + SfxHeadMixer->release(); + SfxHeadMixer = NULL; + } Sys->close(); if (OutputPlugin != 0) @@ -1330,10 +1345,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/fmodsound.h b/src/sound/fmodsound.h index 1460b697b8..f15629cb4b 100644 --- a/src/sound/fmodsound.h +++ b/src/sound/fmodsound.h @@ -103,7 +103,8 @@ private: FMOD::DSP *WaterLP, *WaterReverb; FMOD::DSPConnection *SfxConnection; FMOD::DSP *ChannelGroupTargetUnit; - FMOD::DSP *SfxReverbPlaceholder; + FMOD::DSP *SfxReverbPlaceholder; + FMOD::DSP *SfxHeadMixer; bool SfxReverbHooked; float LastWaterLP; unsigned int OutputPlugin; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index e25d4b5d62..51640b7470 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -162,6 +162,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"; @@ -428,6 +440,12 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int { info = new MUSSong2(file, musiccache, len, MIDI_Timidity); } +#ifdef HAVE_FLUIDSYNTH + else if (snd_mididevice == -5 && device == MDEV_DEFAULT) + { + info = new MUSSong2(file, musiccache, len, MIDI_Fluid); + } +#endif if (info != NULL && !info->IsValid()) { delete info; diff --git a/src/sound/i_music.h b/src/sound/i_music.h index 4bde02df1d..88e6f61c06 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -98,6 +98,9 @@ public: virtual FString GetStats(); virtual MusInfo *GetOPLDumper(const char *filename); virtual MusInfo *GetWaveDumper(const char *filename, int rate); + virtual void FluidSettingInt(const char *setting, int value); // FluidSynth settings + virtual void FluidSettingNum(const char *setting, double value); // " + virtual void FluidSettingStr(const char *setting, const char *value); // " enum EState { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index e5c4771eb8..4deec4f558 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(); }; @@ -255,6 +258,64 @@ protected: FILE *File; }; +// FluidSynth implementation of a MIDI device ------------------------------- + +#ifdef HAVE_FLUIDSYNTH +#include + +class FluidSynthMIDIDevice : public MIDIDevice +{ +public: + FluidSynthMIDIDevice(); + ~FluidSynthMIDIDevice(); + + 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); + 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: + static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); + bool ServiceStream(void *buff, int numbytes); + void HandleEvent(int status, int parm1, int parm2); + int LoadPatchSets(const char *patches); + + void (*Callback)(unsigned int, void *, DWORD, DWORD); + void *CallbackData; + + void CalcTickRate(); + int PlayTick(); + + FCriticalSection CritSec; + SoundStream *Stream; + fluid_settings_t *FluidSettings; + fluid_synth_t *FluidSynth; + double Tempo; + double Division; + double SamplesPerTick; + double NextTickIn; + MIDIHDR *Events; + bool Started; + DWORD Position; +}; +#endif + // Base class for streaming MUS and MIDI files ------------------------------ // MIDI device selection. @@ -262,7 +323,8 @@ enum EMIDIDevice { MIDI_Win, MIDI_OPL, - MIDI_Timidity + MIDI_Timidity, + MIDI_Fluid }; class MIDIStreamer : public MusInfo @@ -282,6 +344,9 @@ 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); protected: MIDIStreamer(const char *dumpname, EMIDIDevice type); diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index d7983a70f3..0184c6a10b 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -9,6 +9,9 @@ static DWORD nummididevices; static bool nummididevicesset; + +CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); + #ifdef _WIN32 UINT mididevice; @@ -19,7 +22,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; @@ -177,8 +180,8 @@ CCMD (snd_listmididevices) CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { - if (self < -3) - self = -3; + if (self < -5) + self = -5; else if (self > -1) self = -1; } diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 5ed94c34c3..d9449a657f 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -215,6 +215,12 @@ void MIDIStreamer::Play(bool looping, int subsong) assert(0); // Intentional fall-through for non-Windows systems. +#ifdef HAVE_FLUIDSYNTH + case MIDI_Fluid: + MIDI = new FluidSynthMIDIDevice; + break; +#endif + case MIDI_Timidity: MIDI = new TimidityMIDIDevice; break; @@ -225,10 +231,10 @@ void MIDIStreamer::Play(bool looping, int subsong) } #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; @@ -435,6 +441,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); + } +} + //========================================================================== // @@ -840,6 +888,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 From ea4ac390d98f99de27fa710c1709e4a0e104aaae Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 15 Aug 2010 19:55:40 +0000 Subject: [PATCH 08/84] - Forgot to include this music_fluidsynth_mididevice.cpp. SVN r2546 (trunk) --- src/sound/music_fluidsynth_mididevice.cpp | 799 ++++++++++++++++++++++ 1 file changed, 799 insertions(+) create mode 100644 src/sound/music_fluidsynth_mididevice.cpp diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp new file mode 100644 index 0000000000..302166ad15 --- /dev/null +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -0,0 +1,799 @@ +/* +** 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 ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +EXTERN_CVAR(String, snd_midipatchset) + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +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, 32, 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, FLUID_INTERP_LINEAR, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + // Values are: 0 = FLUID_INTERP_NONE + // 1 = FLUID_INTERP_LINEAR + // 2 = FLUID_INTERP_4THORDER (the FluidSynth default) + // 3 = FLUID_INTERP_7THORDER + if (self < FLUID_INTERP_NONE) + self = FLUID_INTERP_NONE; + else if (self > FLUID_INTERP_HIGHEST) + self = FLUID_INTERP_HIGHEST; + else if (currSong != NULL) + currSong->FluidSettingInt("synth.interpolation", self); +} + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FluidSynthMIDIDevice Constructor +// +//========================================================================== + +FluidSynthMIDIDevice::FluidSynthMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + FluidSynth = NULL; + FluidSettings = new_fluid_settings(); + if (FluidSettings == NULL) + { + printf("Failed to create FluidSettings.\n"); + return; + } + 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); + FluidSynth = new_fluid_synth(FluidSettings); + if (FluidSynth == NULL) + { + Printf("Failed to create FluidSynth.\n"); + return; + } + if (FLUID_FAILED == fluid_synth_set_interp_method(FluidSynth, -1, fluid_interp)) + { + Printf("Failed to set interpolation method %d.\n", *fluid_interp); + } + if (0 == LoadPatchSets(snd_midipatchset)) + { +#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 + Printf("Failed to load any MIDI patches.\n"); + delete_fluid_synth(FluidSynth); + FluidSynth = NULL; +#ifdef unix + } +#endif + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice Destructor +// +//========================================================================== + +FluidSynthMIDIDevice::~FluidSynthMIDIDevice() +{ + Close(); + if (FluidSynth != NULL) + { + delete_fluid_synth(FluidSynth); + } + if (FluidSettings != NULL) + { + delete_fluid_settings(FluidSettings); + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Open +// +// Returns 0 on success. +// +//========================================================================== + +int FluidSynthMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + if (FluidSynth == NULL) + { + return 2; + } + Stream = GSnd->CreateStream(FillStream, int(44100 / 4) * 4, + SoundStream::Float, 44100, this); + if (Stream == NULL) + { + return 2; + } + + fluid_synth_system_reset(FluidSynth); + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Close +// +//========================================================================== + +void FluidSynthMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + Started = false; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: IsOpen +// +//========================================================================== + +bool FluidSynthMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: GetTechnology +// +//========================================================================== + +int FluidSynthMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: SetTempo +// +//========================================================================== + +int FluidSynthMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int FluidSynthMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void FluidSynthMIDIDevice::CalcTickRate() +{ + SamplesPerTick = 44100 / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Resume +// +//========================================================================== + +int FluidSynthMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1)) + { + Started = true; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Stop +// +//========================================================================== + +void FluidSynthMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + } +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: StreamOutSync +// +// This version is called from the main game thread and needs to +// synchronize with the player thread. +// +//========================================================================== + +int FluidSynthMIDIDevice::StreamOutSync(MIDIHDR *header) +{ + CritSec.Enter(); + StreamOut(header); + CritSec.Leave(); + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: StreamOut +// +// This version is called from the player thread so does not need to +// arbitrate for access to the Events pointer. +// +//========================================================================== + +int FluidSynthMIDIDevice::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; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: PrepareHeader +// +//========================================================================== + +int FluidSynthMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int FluidSynthMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: FakeVolume +// +// Since the FluidSynth output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool FluidSynthMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: NeedThreadedCallabck +// +// FluidSynth can service the callback directly rather than using a separate +// thread. +// +//========================================================================== + +bool FluidSynthMIDIDevice::NeedThreadedCallback() +{ + return false; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: Pause +// +//========================================================================== + +bool FluidSynthMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: PrecacheInstruments +// +// Each entry is packed as follows: +// Bits 0- 6: Instrument number +// Bits 7-13: Bank number +// Bit 14: Select drum set if 1, tone bank if 0 +// +//========================================================================== + +void FluidSynthMIDIDevice::PrecacheInstruments(const WORD *instruments, int count) +{ +#if 0 + for (int i = 0; i < count; ++i) + { + Renderer->MarkInstrument((instruments[i] >> 7) & 127, instruments[i] >> 14, instruments[i] & 127); + } + Renderer->load_missing_instruments(); +#endif +} + +//========================================================================== +// +// 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 :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int FluidSynthMIDIDevice::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) + { +#if 0 + Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); +#endif + } + 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; +} + +//========================================================================== +// +// FluidSynthtMIDIDevice :: ServiceStream +// +//========================================================================== + +bool FluidSynthMIDIDevice::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) + { + fluid_synth_write_float(FluidSynth, samplesleft, + samples1, 0, 2, + samples1, 1, 2); + 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) + { + fluid_synth_write_float(FluidSynth, numsamples, + samples1, 0, 2, + samples1, 1, 2); + } + res = false; + break; + } + else + { + NextTickIn += SamplesPerTick * next; + assert(NextTickIn >= 0); + } + } + } + if (Events == NULL) + { + res = false; + } + CritSec.Leave(); + return res; +} + +//========================================================================== +// +// 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 (strcmp(setting, "synth.interpolation") == 0) + { + if (FluidSynth != NULL) + { + if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value)) + { + Printf("Setting interpolation method %d failed.\n", value); + } + } + } + else if (FluidSynth != NULL && strcmp(setting, "synth.polyphony") == 0) + { + if (FLUID_OK != fluid_synth_set_polyphony(FluidSynth, value)) + { + Printf("Setting polyphony to %d failed.\n", value); + } + } + else if (FluidSettings != NULL) + { + if (!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 :: FillStream static +// +//========================================================================== + +bool FluidSynthMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + FluidSynthMIDIDevice *device = (FluidSynthMIDIDevice *)userdata; + return device->ServiceStream(buff, len); +} + +//========================================================================== +// +// 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; +} + +#endif + From d53ef3f38a41776fd7abba5920be03e5dd43ee3d Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 15 Aug 2010 20:05:35 +0000 Subject: [PATCH 09/84] - Oops. These changes didn't belong in the repository. SVN r2547 (trunk) --- src/sound/fmodsound.cpp | 18 ++---------------- src/sound/fmodsound.h | 3 +-- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index bcd84b9fd0..ff99d516e6 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -644,7 +644,6 @@ bool FMODSoundRenderer::Init() ChannelGroupTargetUnit = NULL; SfxReverbHooked = false; SfxReverbPlaceholder = NULL; - SfxHeadMixer = NULL; OutputPlugin = 0; Printf("I_InitSound: Initializing FMOD\n"); @@ -1016,12 +1015,6 @@ bool FMODSoundRenderer::Init() result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxReverbPlaceholder); if (result == FMOD_OK) { - result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxHeadMixer); - result = sfx_head->addInput(SfxHeadMixer, &SfxConnection); - result = sfx_head->disconnectFrom(pausable_head); - sfx_head = SfxHeadMixer; - SfxHeadMixer->setActive(true); - SfxHeadMixer->setBypass(false); // Replace the PausableSFX->SFX connection with // PausableSFX->ReverbPlaceholder->SFX. result = SfxReverbPlaceholder->addInput(pausable_head, NULL); @@ -1031,13 +1024,13 @@ bool FMODSoundRenderer::Init() result = sfx_head->addInput(SfxReverbPlaceholder, &connection); if (result == FMOD_OK) { -// sfx_head->disconnectFrom(pausable_head); + sfx_head->disconnectFrom(pausable_head); SfxReverbPlaceholder->setActive(true); SfxReverbPlaceholder->setBypass(true); // The placeholder now takes the place of the pausable_head // for the following connections. pausable_head = SfxReverbPlaceholder; - // SfxConnection = connection; + SfxConnection = connection; } } else @@ -1046,7 +1039,6 @@ bool FMODSoundRenderer::Init() SfxReverbPlaceholder = NULL; } } -#if 1 result = WaterLP->addInput(pausable_head, NULL); WaterLP->setActive(false); WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp); @@ -1078,7 +1070,6 @@ bool FMODSoundRenderer::Init() { result = sfx_head->addInput(WaterLP, NULL); } -#endif } } } @@ -1157,11 +1148,6 @@ void FMODSoundRenderer::Shutdown() SfxReverbPlaceholder->release(); SfxReverbPlaceholder = NULL; } - if (SfxHeadMixer != NULL) - { - SfxHeadMixer->release(); - SfxHeadMixer = NULL; - } Sys->close(); if (OutputPlugin != 0) diff --git a/src/sound/fmodsound.h b/src/sound/fmodsound.h index f15629cb4b..1460b697b8 100644 --- a/src/sound/fmodsound.h +++ b/src/sound/fmodsound.h @@ -103,8 +103,7 @@ private: FMOD::DSP *WaterLP, *WaterReverb; FMOD::DSPConnection *SfxConnection; FMOD::DSP *ChannelGroupTargetUnit; - FMOD::DSP *SfxReverbPlaceholder; - FMOD::DSP *SfxHeadMixer; + FMOD::DSP *SfxReverbPlaceholder; bool SfxReverbHooked; float LastWaterLP; unsigned int OutputPlugin; From 050b82f543f2286f21f521ccb4bf872b298de26c Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Tue, 17 Aug 2010 04:22:17 +0000 Subject: [PATCH 10/84] - Fixed: ZDoom wouldn't compile without FluidSynth. SVN r2548 (trunk) --- src/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d6c0aa3821..03cedcdedb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -485,10 +485,14 @@ add_custom_target( revision_check ALL # Libraries ZDoom needs - message( STATUS "Fluid synth libs: ${FLUIDSYNTH_LIBRARIES}" ) -set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${FMOD_LIBRARY}" "${FLUIDSYNTH_LIBRARIES}" ) -include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" "${FLUIDSYNTH_INCLUDE_DIR}" ) +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 ) + set( ZDOOM_LIBS "${FLUIDSYNTH_LIBRARIES}" ) + include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) +endif( FLUIDSYNTH_FOUND ) # Start defining source files for ZDoom From f9523a01e37b6fc8c44d5e299405864b509bf13b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 18 Aug 2010 20:26:25 +0000 Subject: [PATCH 11/84] - Added Gez's patch for moving tag strings into the language file and adding tags for all weapons and inventory items. SVN r2552 (trunk) --- src/g_game.cpp | 32 ++- src/g_shared/a_pickups.cpp | 1 + src/m_options.cpp | 11 +- wadsrc/static/actors/chex/chexweapons.txt | 9 + wadsrc/static/actors/doom/doomweapons.txt | 9 + .../actors/heretic/hereticartifacts.txt | 3 + wadsrc/static/actors/heretic/hereticweaps.txt | 50 +++- wadsrc/static/actors/hexen/blastradius.txt | 1 + wadsrc/static/actors/hexen/boostarmor.txt | 1 + wadsrc/static/actors/hexen/clericflame.txt | 3 + wadsrc/static/actors/hexen/clericholy.txt | 2 + wadsrc/static/actors/hexen/clericmace.txt | 2 + wadsrc/static/actors/hexen/clericstaff.txt | 3 + wadsrc/static/actors/hexen/fighteraxe.txt | 2 + wadsrc/static/actors/hexen/fighterfist.txt | 2 + wadsrc/static/actors/hexen/fighterhammer.txt | 3 + wadsrc/static/actors/hexen/fighterquietus.txt | 2 + wadsrc/static/actors/hexen/flechette.txt | 4 + wadsrc/static/actors/hexen/healingradius.txt | 1 + wadsrc/static/actors/hexen/magecone.txt | 3 + wadsrc/static/actors/hexen/magelightning.txt | 3 + wadsrc/static/actors/hexen/magestaff.txt | 2 + wadsrc/static/actors/hexen/magewand.txt | 2 + wadsrc/static/actors/hexen/mana.txt | 1 + wadsrc/static/actors/hexen/puzzleitems.txt | 17 ++ wadsrc/static/actors/hexen/speedboots.txt | 1 + wadsrc/static/actors/hexen/summon.txt | 1 + wadsrc/static/actors/hexen/teleportother.txt | 1 + wadsrc/static/actors/raven/artiegg.txt | 2 + wadsrc/static/actors/raven/artitele.txt | 1 + wadsrc/static/actors/raven/ravenartifacts.txt | 6 + wadsrc/static/actors/strife/acolyte.txt | 2 +- wadsrc/static/actors/strife/beggars.txt | 2 +- wadsrc/static/actors/strife/coin.txt | 10 +- wadsrc/static/actors/strife/loremaster.txt | 2 +- wadsrc/static/actors/strife/macil.txt | 3 +- wadsrc/static/actors/strife/merchants.txt | 8 +- wadsrc/static/actors/strife/oracle.txt | 2 +- wadsrc/static/actors/strife/questitems.txt | 6 +- wadsrc/static/actors/strife/ratbuddy.txt | 2 +- wadsrc/static/actors/strife/rebels.txt | 4 +- wadsrc/static/actors/strife/sigil.txt | 2 +- wadsrc/static/actors/strife/strifeammo.txt | 22 +- wadsrc/static/actors/strife/strifearmor.txt | 4 +- wadsrc/static/actors/strife/strifeitems.txt | 52 ++-- wadsrc/static/actors/strife/strifekeys.txt | 58 ++--- wadsrc/static/actors/strife/strifeweapons.txt | 29 ++- wadsrc/static/actors/strife/templar.txt | 2 +- wadsrc/static/language.enu | 245 ++++++++++++++++++ 49 files changed, 520 insertions(+), 116 deletions(-) diff --git a/src/g_game.cpp b/src/g_game.cpp index d68ead9ef4..fadfb6c551 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -116,13 +116,13 @@ EXTERN_CVAR (Float, con_midtime); // // CVAR displaynametags // -// Selects whether to display name tags or not when changing weapons +// Selects whether to display name tags or not when changing weapons/items // //========================================================================== -CUSTOM_CVAR (Bool, displaynametags, 0, CVAR_ARCHIVE) +CUSTOM_CVAR (Int, displaynametags, 0, CVAR_ARCHIVE) { - if (self != 0 && self != 1) + if (self < 0 || self > 3) { self = 0; } @@ -322,11 +322,23 @@ CCMD (turn180) CCMD (weapnext) { SendItemUse = players[consoleplayer].weapons.PickNextWeapon (&players[consoleplayer]); + // [BC] Option to display the name of the weapon being cycled to. + if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) + { + StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), + 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + } } CCMD (weapprev) { SendItemUse = players[consoleplayer].weapons.PickPrevWeapon (&players[consoleplayer]); + // [BC] Option to display the name of the weapon being cycled to. + if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) + { + StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), + 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + } } CCMD (invnext) @@ -354,10 +366,9 @@ CCMD (invnext) who->InvSel = who->Inventory; } } - if (displaynametags && StatusBar && SmallFont - && gamestate == GS_LEVEL && level.time > con_midtime && who->InvSel) - StatusBar->AttachMessage (new DHUDMessage (SmallFont, who->InvSel->GetTag(), - 2.5f, 0.375f, 0, 0, CR_YELLOW, con_midtime), MAKE_ID('S','I','N','V')); + if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), + 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -385,10 +396,9 @@ CCMD (invprev) } who->InvSel = item; } - if (displaynametags && StatusBar && SmallFont - && gamestate == GS_LEVEL && level.time > con_midtime && who->InvSel) - StatusBar->AttachMessage (new DHUDMessage (SmallFont, who->InvSel->GetTag(), - 2.5f, 0.375f, 0, 0, CR_YELLOW, con_midtime), MAKE_ID('S','I','N','V')); + if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) + StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), + 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index 1946189337..3802bd4778 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; diff --git a/src/m_options.cpp b/src/m_options.cpp index 2357c930af..4f822befef 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -93,7 +93,7 @@ EXTERN_CVAR(Int, showendoom) EXTERN_CVAR(Bool, hud_althud) EXTERN_CVAR(Int, compatmode) EXTERN_CVAR (Bool, vid_vsync) -EXTERN_CVAR(Bool, displaynametags) +EXTERN_CVAR(Int, displaynametags) EXTERN_CVAR (Int, snd_channels) // @@ -485,6 +485,13 @@ static value_t Contrast[] = { { 2.0, "Smooth" } }; +static value_t DisplayTagsTypes[] = { + { 0.0, "None" }, + { 1.0, "Items" }, + { 2.0, "Weapons" }, + { 3.0, "Both" } +}; + static menuitem_t VideoItems[] = { { more, "Message Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartMessagesMenu} }, { more, "Automap Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartAutomapMenu} }, @@ -510,7 +517,7 @@ static menuitem_t VideoItems[] = { { discrete, "Rocket Trails", {&cl_rockettrails}, {4.0}, {0.0}, {0.0}, {RocketTrailTypes} }, { discrete, "Blood Type", {&cl_bloodtype}, {3.0}, {0.0}, {0.0}, {BloodTypes} }, { discrete, "Bullet Puff Type", {&cl_pufftype}, {2.0}, {0.0}, {0.0}, {PuffTypes} }, - { discrete, "Display nametags", {&displaynametags}, {2.0}, {0.0}, {0.0}, {YesNo} }, + { discrete, "Display nametags", {&displaynametags}, {4.0}, {0.0}, {0.0}, {DisplayTagsTypes} }, }; #define CROSSHAIR_INDEX 7 diff --git a/wadsrc/static/actors/chex/chexweapons.txt b/wadsrc/static/actors/chex/chexweapons.txt index 4fa7eec560..97043f2ca2 100644 --- a/wadsrc/static/actors/chex/chexweapons.txt +++ b/wadsrc/static/actors/chex/chexweapons.txt @@ -4,6 +4,7 @@ actor Bootspoon : Fist { game Chex obituary "$OB_MPSPOON" + Tag "$TAG_SPOON" } actor SuperBootspork : Chainsaw 2005 @@ -11,6 +12,7 @@ actor SuperBootspork : Chainsaw 2005 game Chex obituary "$OB_MPBOOTSPORK" Inventory.PickupMessage "$GOTSUPERBOOTSPORK" + Tag "$TAG_SPORK" } actor MiniZorcher : Pistol @@ -18,6 +20,7 @@ actor MiniZorcher : Pistol game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTMINIZORCHER" + Tag "$TAG_MINIZORCHER" states { Spawn: @@ -30,6 +33,7 @@ actor LargeZorcher : Shotgun 2001 game Chex obituary "$OP_MPZORCH" inventory.pickupmessage "$GOTLARGEZORCHER" + Tag "$TAG_LARGEZORCHER" } actor SuperLargeZorcher : SuperShotgun 82 @@ -37,6 +41,7 @@ actor SuperLargeZorcher : SuperShotgun 82 game Chex obituary "$OB_MPMEGAZORCH" inventory.pickupmessage "$GOTSUPERLARGEZORCHER" + Tag "$TAG_SUPERLARGEZORCHER" } actor RapidZorcher : Chaingun 2002 @@ -44,6 +49,7 @@ actor RapidZorcher : Chaingun 2002 game Chex obituary "$OB_MPRAPIDZORCH" inventory.pickupmessage "$GOTRAPIDZORCHER" + Tag "$TAG_RAPIDZORCHER" } actor ZorchPropulsor : RocketLauncher 2003 @@ -51,6 +57,7 @@ actor ZorchPropulsor : RocketLauncher 2003 game Chex obituary "" inventory.pickupmessage "$GOTZORCHPROPULSOR" + Tag "$TAG_ZORCHPROPULSOR" States { Fire: @@ -75,6 +82,7 @@ actor PhasingZorcher : PlasmaRifle 2004 game Chex obituary "" inventory.pickupmessage "$GOTPHASINGZORCHER" + Tag "$TAG_PHASINGZORCHER" states { Fire: @@ -104,6 +112,7 @@ actor LAZDevice : BFG9000 2006 game Chex obituary "" inventory.pickupmessage "$GOTLAZDEVICE" + Tag "$TAG_LAZDEVICE" states { Fire: diff --git a/wadsrc/static/actors/doom/doomweapons.txt b/wadsrc/static/actors/doom/doomweapons.txt index 076559c0ee..b748f86115 100644 --- a/wadsrc/static/actors/doom/doomweapons.txt +++ b/wadsrc/static/actors/doom/doomweapons.txt @@ -21,6 +21,7 @@ ACTOR Fist : Weapon Weapon.SelectionOrder 3700 Weapon.Kickback 100 Obituary "$OB_MPFIST" + Tag "$FIST" +WEAPON.WIMPY_WEAPON +WEAPON.MELEEWEAPON States @@ -61,6 +62,7 @@ ACTOR Pistol : DoomWeapon 5010 Obituary "$OB_MPPISTOL" +WEAPON.WIMPY_WEAPON Inventory.Pickupmessage "$PICKUP_PISTOL_DROPPED" + Tag "$TAG_PISTOL" States { Ready: @@ -105,6 +107,7 @@ ACTOR Chainsaw : Weapon 2005 Weapon.ReadySound "weapons/sawidle" Inventory.PickupMessage "$GOTCHAINSAW" Obituary "$OB_MPCHAINSAW" + Tag "$TAG_CHAINSAW" +WEAPON.MELEEWEAPON States { @@ -144,6 +147,7 @@ ACTOR Shotgun : DoomWeapon 2001 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN" Obituary "$OB_MPSHOTGUN" + Tag "$TAG_SHOTGUN" States { Ready: @@ -190,6 +194,7 @@ ACTOR SuperShotgun : DoomWeapon 82 Weapon.AmmoType "Shell" Inventory.PickupMessage "$GOTSHOTGUN2" Obituary "$OB_MPSSHOTGUN" + Tag "$TAG_SUPERSHOTGUN" States { Ready: @@ -243,6 +248,7 @@ ACTOR Chaingun : DoomWeapon 2002 Weapon.AmmoType "Clip" Inventory.PickupMessage "$GOTCHAINGUN" Obituary "$OB_MPCHAINGUN" + Tag "$TAG_CHAINGUN" States { Ready: @@ -285,6 +291,7 @@ ACTOR RocketLauncher : DoomWeapon 2003 Weapon.AmmoType "RocketAmmo" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTLAUNCHER" + Tag "$TAG_ROCKETLAUNCHER" States { Ready: @@ -406,6 +413,7 @@ ACTOR PlasmaRifle : DoomWeapon 2004 Weapon.AmmoGive 40 Weapon.AmmoType "Cell" Inventory.PickupMessage "$GOTPLASMA" + Tag "$TAG_PLASMARIFLE" States { Ready: @@ -511,6 +519,7 @@ ACTOR BFG9000 : DoomWeapon 2006 Weapon.AmmoType "Cell" +WEAPON.NOAUTOFIRE Inventory.PickupMessage "$GOTBFG9000" + Tag "$TAG_BFG9000" States { Ready: diff --git a/wadsrc/static/actors/heretic/hereticartifacts.txt b/wadsrc/static/actors/heretic/hereticartifacts.txt index 0d5ede1a63..67d3946b94 100644 --- a/wadsrc/static/actors/heretic/hereticartifacts.txt +++ b/wadsrc/static/actors/heretic/hereticartifacts.txt @@ -33,6 +33,7 @@ ACTOR ArtiInvisibility : PowerupGiver 75 Inventory.Icon ARTIINVS Powerup.Type Ghost Inventory.PickupMessage "$TXT_ARTIINVISIBILITY" + Tag "$TAG_ARTIINVISIBILITY" States { Spawn: @@ -54,6 +55,7 @@ ACTOR ArtiTomeOfPower : PowerupGiver 86 native Inventory.Icon "ARTIPWBK" Powerup.Type Weaponlevel2 Inventory.PickupMessage "$TXT_ARTITOMEOFPOWER" + Tag "$TAG_ARTITOMEOFPOWER" States { Spawn: @@ -98,6 +100,7 @@ ACTOR ArtiTimeBomb : Inventory 34 native Inventory.Icon "ARTIFBMB" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIFIREBOMB" + Tag "$TAG_ARTIFIREBOMB" Inventory.DefMaxAmount States { diff --git a/wadsrc/static/actors/heretic/hereticweaps.txt b/wadsrc/static/actors/heretic/hereticweaps.txt index 993da14859..7e2d0a2eeb 100644 --- a/wadsrc/static/actors/heretic/hereticweaps.txt +++ b/wadsrc/static/actors/heretic/hereticweaps.txt @@ -15,6 +15,8 @@ ACTOR Staff : HereticWeapon +WIMPY_WEAPON +MELEEWEAPON Weapon.sisterweapon "StaffPowered" + Obituary "$OB_MPSTAFF" + Tag "$TAG_STAFF" action native A_StaffAttack (int damage, class puff); @@ -45,6 +47,8 @@ ACTOR StaffPowered : Staff +WEAPON.POWERED_UP +WEAPON.READYSNDHALF +WEAPON.STAFF2_KICKBACK + Obituary "$OB_MPPSTAFF" + Tag "$TAG_STAFFP" States { Ready: @@ -116,6 +120,8 @@ ACTOR GoldWand : HereticWeapon Weapon.AmmoType "GoldWandAmmo" Weapon.SisterWeapon "GoldWandPowered" Weapon.YAdjust 5 + Obituary "$OB_MPGOLDWAND" + Tag "$TAG_GOLDWAND" action native A_FireGoldWandPL1 (); @@ -145,6 +151,8 @@ ACTOR GoldWandPowered : GoldWand +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "GoldWand" + Obituary "$OB_MPPGOLDWAND" + Tag "$TAG_GOLDWANDP" action native A_FireGoldWandPL2 (); @@ -173,6 +181,7 @@ ACTOR GoldWandFX1 Projectile RenderStyle Add DeathSound "weapons/wandhit" + Obituary "$OB_MPPGOLDWAND" States { Spawn: @@ -209,7 +218,8 @@ ACTOR GoldWandPuff1 +NOGRAVITY +PUFFONACTORS RenderStyle Add - States { + States + { Spawn: PUF2 ABCDE 3 BRIGHT Stop @@ -243,7 +253,8 @@ ACTOR Crossbow : HereticWeapon 2001 Weapon.AmmoType "CrossbowAmmo" Weapon.SisterWeapon "CrossbowPowered" Weapon.YAdjust 15 - Inventory.PickupMessage "$TxT_WPNCROSSBOW" + Inventory.PickupMessage "$TXT_WPNCROSSBOW" + Tag "$TAG_CROSSBOW" action native A_FireCrossbowPL1 (); @@ -277,6 +288,7 @@ ACTOR CrossbowPowered : Crossbow +WEAPON.POWERED_UP Weapon.AmmoGive 0 Weapon.SisterWeapon "Crossbow" + Tag "$TAG_CROSSBOWP" action native A_FireCrossbowPL2(); @@ -310,6 +322,7 @@ ACTOR CrossbowFX1 RenderStyle Add SeeSound "weapons/bowshoot" DeathSound "weapons/bowhit" + Obituary "$OB_MPCROSSBOW" States { Spawn: @@ -330,6 +343,7 @@ ACTOR CrossbowFX2 : CrossbowFX1 SpawnID 148 Speed 32 Damage 6 + Obituary "$OB_MPPCROSSBOW" States { Spawn: @@ -394,6 +408,8 @@ ACTOR Gauntlets : Weapon 2005 Weapon.UpSound "weapons/gauntletsactivate" Weapon.SisterWeapon "GauntletsPowered" Inventory.PickupMessage "$TXT_WPNGAUNTLETS" + Tag "$TAG_GAUNTLETS" + Obituary "$OB_MPGAUNTLETS" action native A_GauntletAttack (int power); @@ -427,6 +443,8 @@ ACTOR GauntletsPowered : Gauntlets { Game Heretic +POWERED_UP + Tag "$TAG_GAUNTLETSP" + Obituary "$OB_MPPGAUNTLETS" Weapon.SisterWeapon "Gauntlets" States { @@ -469,7 +487,7 @@ ACTOR GauntletPuff1 } } -// Gauntlett puff 2 --------------------------------------------------------- +// Gauntlet puff 2 --------------------------------------------------------- ACTOR GauntletPuff2 : GauntletPuff1 { @@ -494,7 +512,8 @@ ACTOR Mace : HereticWeapon Weapon.YAdjust 15 Weapon.AmmoType "MaceAmmo" Weapon.SisterWeapon "MacePowered" - Inventory.PickupMessage "$TxT_WPNMACE" + Inventory.PickupMessage "$TXT_WPNMACE" + Tag "$TAG_MACE" action native A_FireMacePL1(); @@ -529,6 +548,7 @@ ACTOR MacePowered : Mace Weapon.AmmoUse 5 Weapon.AmmoGive 0 Weapon.SisterWeapon "Mace" + Tag "$TAG_MACEP" action native A_FireMacePL2(); @@ -558,6 +578,7 @@ ACTOR MaceFX1 +THRUGHOST BounceType "HereticCompat" SeeSound "weapons/maceshoot" + Obituary "$OB_MPMACE" action native A_MacePL1Check(); action native A_MaceBallImpact(); @@ -636,6 +657,7 @@ ACTOR MaceFX4 native -NOTELEPORT BounceType "HereticCompat" SeeSound "" + Obituary "$OB_MPPMACE" action native A_DeathBallImpact(); @@ -682,7 +704,9 @@ ACTOR Blaster : HereticWeapon 53 Weapon.YAdjust 15 Weapon.AmmoType "BlasterAmmo" Weapon.SisterWeapon "BlasterPowered" - Inventory.PickupMessage "$TxT_WPNBLASTER" + Inventory.PickupMessage "$TXT_WPNBLASTER" + Tag "$TAG_BLASTER" + Obituary "$OB_MPBLASTER" action native A_FireBlasterPL1(); @@ -740,6 +764,8 @@ ACTOR BlasterFX1 : FastProjectile native SeeSound "weapons/blastershoot" DeathSound "weapons/blasterhit" +SPAWNSOUNDSOURCE + Obituary "$OB_MPPBLASTER" + Tag "$TAG_BLASTERP" action native A_SpawnRippers(); @@ -787,6 +813,7 @@ ACTOR Ripper native Projectile +RIPPER DeathSound "weapons/blasterpowhit" + Obituary "$OB_MPPBLASTER" States { Spawn: @@ -832,7 +859,8 @@ ACTOR SkullRod : HereticWeapon 2004 Weapon.YAdjust 15 Weapon.AmmoType1 "SkullRodAmmo" Weapon.SisterWeapon "SkullRodPowered" - Inventory.PickupMessage "$TxT_WPNSKULLROD" + Inventory.PickupMessage "$TXT_WPNSKULLROD" + Tag "$TAG_SKULLROD" action native A_FireSkullRodPL1(); @@ -864,6 +892,7 @@ ACTOR SkullRodPowered : SkullRod Weapon.AmmoUse1 5 Weapon.AmmoGive1 0 Weapon.SisterWeapon "SkullRod" + Tag "$TAG_SKULLRODP" action native A_FireSkullRodPL2(); @@ -899,6 +928,7 @@ ACTOR HornRodFX1 RenderStyle Add SeeSound "weapons/hornrodshoot" DeathSound "weapons/hornrodhit" + Obituary "$OB_MPSKULLROD" States { Spawn: @@ -926,6 +956,7 @@ ACTOR HornRodFX2 native RenderStyle Add SeeSound "weapons/hornrodpowshoot" DeathSound "weapons/hornrodpowhit" + Obituary "$OB_MPPSKULLROD" action native A_AddPlayerRain(); action native A_HideInCeiling(); @@ -963,6 +994,7 @@ ACTOR RainPillar native -ACTIVATEPCROSS -ACTIVATEIMPACT RenderStyle Add + Obituary "$OB_MPPSKULLROD" action native A_RainImpact(); @@ -1003,7 +1035,8 @@ ACTOR PhoenixRod : Weapon 2003 native Weapon.AmmoGive 2 Weapon.AmmoType "PhoenixRodAmmo" Weapon.Sisterweapon "PhoenixRodPowered" - Inventory.PickupMessage "$TxT_WPNPHOENIxROD" + Inventory.PickupMessage "$TXT_WPNPHOENIxROD" + Tag "$TAG_PHOENIxROD" action native A_FirePhoenixPL1(); @@ -1037,6 +1070,7 @@ ACTOR PhoenixRodPowered : PhoenixRod native +WEAPON.MELEEWEAPON Weapon.SisterWeapon "PhoenixRod" Weapon.AmmoGive 0 + Tag "$TAG_PHOENIxRODP" action native A_InitPhoenixPL2(); action native A_FirePhoenixPL2(); @@ -1071,6 +1105,7 @@ ACTOR PhoenixFX1 native +SPECIALFIREDAMAGE SeeSound "weapons/phoenixshoot" DeathSound "weapons/phoenixhit" + Obituary "$OB_MPPHOENIXROD" action native A_PhoenixPuff(); @@ -1116,6 +1151,7 @@ ACTOR PhoenixFX2 native DamageType Fire Projectile RenderStyle Add + Obituary "$OB_MPPPHOENIXROD" action native A_FlameEnd(); action native A_FloatPuff(); diff --git a/wadsrc/static/actors/hexen/blastradius.txt b/wadsrc/static/actors/hexen/blastradius.txt index 7e4259cf8a..ad98d7ecc7 100644 --- a/wadsrc/static/actors/hexen/blastradius.txt +++ b/wadsrc/static/actors/hexen/blastradius.txt @@ -10,6 +10,7 @@ ACTOR ArtiBlastRadius : CustomInventory 10110 Inventory.Icon "ARTIBLST" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBLASTRADIUS" + Tag "$TAG_ARTIBLASTRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/boostarmor.txt b/wadsrc/static/actors/hexen/boostarmor.txt index ca3b8384c7..607c8d66a3 100644 --- a/wadsrc/static/actors/hexen/boostarmor.txt +++ b/wadsrc/static/actors/hexen/boostarmor.txt @@ -13,6 +13,7 @@ ACTOR ArtiBoostArmor : Inventory 8041 native Inventory.Icon "ARTIBRAC" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTARMOR" + Tag "$TAG_ARTIBOOSTARMOR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/clericflame.txt b/wadsrc/static/actors/hexen/clericflame.txt index 29312ca79d..5fb4175fd4 100644 --- a/wadsrc/static/actors/hexen/clericflame.txt +++ b/wadsrc/static/actors/hexen/clericflame.txt @@ -12,6 +12,7 @@ ACTOR CWeapFlame : ClericWeapon 8009 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C3" + Tag "$TAG_CWEAPFLAME" action native A_CFlameAttack(); @@ -126,6 +127,7 @@ ACTOR CircleFlame -ACTIVATEPCROSS RenderStyle Add DeathSound "ClericFlameCircle" + Obituary "$OB_MPCWEAPFLAME" action native A_CFlameRotate(); @@ -166,6 +168,7 @@ ACTOR CFlameMissile : FastProjectile native DamageType "Fire" +INVISIBLE RenderStyle Add + Obituary "$OB_MPCWEAPFLAME" action native A_CFlamePuff(); action native A_CFlameMissile(); diff --git a/wadsrc/static/actors/hexen/clericholy.txt b/wadsrc/static/actors/hexen/clericholy.txt index 0c441726da..15673ab1b5 100644 --- a/wadsrc/static/actors/hexen/clericholy.txt +++ b/wadsrc/static/actors/hexen/clericholy.txt @@ -84,6 +84,7 @@ ACTOR CWeapWraithverge : ClericWeapon native Weapon.AmmoType1 "Mana1" Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_C4" + Tag "$TAG_CWEAPWRAITHVERGE" Inventory.PickupSound "WeaponBuild" action native A_CHolyAttack(); @@ -187,6 +188,7 @@ ACTOR HolySpirit native RenderStyle Translucent Alpha 0.4 DeathSound "SpiritDie" + Obituary "$OB_MPCWEAPWRAITHVERGE" action native A_CHolySeek(); action native A_CHolyCheckScream(); diff --git a/wadsrc/static/actors/hexen/clericmace.txt b/wadsrc/static/actors/hexen/clericmace.txt index ae9d3b84ae..14ef4b00fe 100644 --- a/wadsrc/static/actors/hexen/clericmace.txt +++ b/wadsrc/static/actors/hexen/clericmace.txt @@ -8,6 +8,8 @@ ACTOR CWeapMace : ClericWeapon Weapon.KickBack 150 Weapon.YAdjust -8 +BLOODSPLATTER + Obituary "$OB_MPCWEAPMACE" + Tag "$TAG_CWEAPMACE" action native A_CMaceAttack(); diff --git a/wadsrc/static/actors/hexen/clericstaff.txt b/wadsrc/static/actors/hexen/clericstaff.txt index 3b9cd16995..e55674fa5d 100644 --- a/wadsrc/static/actors/hexen/clericstaff.txt +++ b/wadsrc/static/actors/hexen/clericstaff.txt @@ -12,6 +12,8 @@ ACTOR CWeapStaff : ClericWeapon 10 Weapon.YAdjust 10 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_C2" + Obituary "$OB_MPCWEAPSTAFFM" + Tag "$TAG_CWEAPSTAFF" action native A_CStaffInitBlink(); action native A_CStaffCheckBlink(); @@ -65,6 +67,7 @@ ACTOR CStaffMissile native RenderStyle Add Projectile DeathSound "ClericCStaffExplode" + Obituary "$OB_MPCWEAPSTAFFR" States { Spawn: diff --git a/wadsrc/static/actors/hexen/fighteraxe.txt b/wadsrc/static/actors/hexen/fighteraxe.txt index 78cbbf0a3b..502de50307 100644 --- a/wadsrc/static/actors/hexen/fighteraxe.txt +++ b/wadsrc/static/actors/hexen/fighteraxe.txt @@ -13,6 +13,8 @@ ACTOR FWeapAxe : FighterWeapon 8010 native Weapon.YAdjust -12 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_F2" + Obituary "$OB_MPFWEAPAXE" + Tag "$TAG_FWEAPAXE" action native A_FAxeCheckUp(); action native A_FAxeCheckReady(); diff --git a/wadsrc/static/actors/hexen/fighterfist.txt b/wadsrc/static/actors/hexen/fighterfist.txt index 6693b79bf1..0122d7f740 100644 --- a/wadsrc/static/actors/hexen/fighterfist.txt +++ b/wadsrc/static/actors/hexen/fighterfist.txt @@ -8,6 +8,8 @@ ACTOR FWeapFist : FighterWeapon Weapon.SelectionOrder 3400 +WEAPON.MELEEWEAPON Weapon.KickBack 150 + Obituary "$OB_MPFWEAPFIST" + Tag "$TAG_FWEAPFIST" action native A_FPunchAttack(); diff --git a/wadsrc/static/actors/hexen/fighterhammer.txt b/wadsrc/static/actors/hexen/fighterhammer.txt index 63585b1cc5..5c7d6dc256 100644 --- a/wadsrc/static/actors/hexen/fighterhammer.txt +++ b/wadsrc/static/actors/hexen/fighterhammer.txt @@ -14,6 +14,8 @@ ACTOR FWeapHammer : FighterWeapon 123 Weapon.YAdjust -10 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F3" + Obituary "$OB_MPFWEAPHAMMERM" + Tag "$TAG_FWEAPHAMMER" action native A_FHammerAttack(); action native A_FHammerThrow(); @@ -60,6 +62,7 @@ ACTOR HammerMissile DamageType "Fire" Projectile DeathSound "FighterHammerExplode" + Obituary "$OB_MPFWEAPHAMMERR" States { diff --git a/wadsrc/static/actors/hexen/fighterquietus.txt b/wadsrc/static/actors/hexen/fighterquietus.txt index 3bd5904f78..034914d23d 100644 --- a/wadsrc/static/actors/hexen/fighterquietus.txt +++ b/wadsrc/static/actors/hexen/fighterquietus.txt @@ -86,6 +86,7 @@ ACTOR FWeapQuietus : FighterWeapon Weapon.AmmoType2 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_F4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_FWEAPQUIETUS" action native A_FSwordAttack(); @@ -131,6 +132,7 @@ ACTOR FSwordMissile native +EXTREMEDEATH RenderStyle Add DeathSound "FighterSwordExplode" + Obituary "$OB_MPFWEAPQUIETUS" action native A_FSwordFlames(); diff --git a/wadsrc/static/actors/hexen/flechette.txt b/wadsrc/static/actors/hexen/flechette.txt index 4820a706db..b2063a6ec0 100644 --- a/wadsrc/static/actors/hexen/flechette.txt +++ b/wadsrc/static/actors/hexen/flechette.txt @@ -105,6 +105,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native Inventory.Icon "ARTIPSBG" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIPOISONBAG" + Tag "$TAG_ARTIPOISONBAG" States { Spawn: @@ -118,6 +119,7 @@ ACTOR ArtiPoisonBag : Inventory 8000 native ACTOR ArtiPoisonBag1 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB1" + Tag "$TAG_ARTIPOISONBAG1" } // Poison Bag 2 (The Mage's) ------------------------------------------------ @@ -125,6 +127,7 @@ ACTOR ArtiPoisonBag1 : ArtiPoisonBag native ACTOR ArtiPoisonBag2 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB2" + Tag "$TAG_ARTIPOISONBAG2" } // Poison Bag 3 (The Fighter's) --------------------------------------------- @@ -132,6 +135,7 @@ ACTOR ArtiPoisonBag2 : ArtiPoisonBag native ACTOR ArtiPoisonBag3 : ArtiPoisonBag native { Inventory.Icon "ARTIPSB3" + Tag "$TAG_ARTIPOISONBAG3" } // Poison Cloud ------------------------------------------------------------- diff --git a/wadsrc/static/actors/hexen/healingradius.txt b/wadsrc/static/actors/hexen/healingradius.txt index 8c5b0e53ab..e0556915bc 100644 --- a/wadsrc/static/actors/hexen/healingradius.txt +++ b/wadsrc/static/actors/hexen/healingradius.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealingRadius : Inventory 10120 native Inventory.Icon "ARTIHRAD" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALINGRADIUS" + Tag "$TAG_ARTIHEALINGRADIUS" States { Spawn: diff --git a/wadsrc/static/actors/hexen/magecone.txt b/wadsrc/static/actors/hexen/magecone.txt index 17c7ccf4f9..b5dfdb5462 100644 --- a/wadsrc/static/actors/hexen/magecone.txt +++ b/wadsrc/static/actors/hexen/magecone.txt @@ -13,6 +13,8 @@ ACTOR MWeapFrost : MageWeapon 53 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana1" Inventory.PickupMessage "$TXT_WEAPON_M2" + Obituary "$OB_MPMWEAPFROST" + Tag "$TAG_MWEAPFROST" action native A_FireConePL1(); @@ -55,6 +57,7 @@ ACTOR FrostMissile native DamageType "Ice" Projectile DeathSound "MageShardsExplode" + Obituary "$OB_MPMWEAPFROST" action native A_ShedShard(); diff --git a/wadsrc/static/actors/hexen/magelightning.txt b/wadsrc/static/actors/hexen/magelightning.txt index 02658bcba5..85402f223c 100644 --- a/wadsrc/static/actors/hexen/magelightning.txt +++ b/wadsrc/static/actors/hexen/magelightning.txt @@ -12,6 +12,7 @@ ACTOR MWeapLightning : MageWeapon 8040 Weapon.YAdjust 20 Weapon.AmmoType1 "Mana2" Inventory.PickupMessage "$TXT_WEAPON_M3" + Tag "$TAG_MWEAPLIGHTNING" action native A_LightningReady(); action native A_MLightningAttack(class floor = "LightningFloor", class ceiling = "LightningCeiling"); @@ -56,6 +57,7 @@ ACTOR Lightning native MissileType "LightningZap" AttackSound "MageLightningZap" ActiveSound "MageLightningContinuous" + Obituary "$OB_MPMWEAPLIGHTNING" } ACTOR LightningCeiling : Lightning @@ -137,6 +139,7 @@ ACTOR LightningZap native -ACTIVATEIMPACT -ACTIVATEPCROSS RenderStyle Add + Obituary "$OB_MPMWEAPLIGHTNING" action native A_ZapMimic(); diff --git a/wadsrc/static/actors/hexen/magestaff.txt b/wadsrc/static/actors/hexen/magestaff.txt index e402e57c0b..65c0ac8931 100644 --- a/wadsrc/static/actors/hexen/magestaff.txt +++ b/wadsrc/static/actors/hexen/magestaff.txt @@ -86,6 +86,7 @@ ACTOR MWeapBloodscourge : MageWeapon native +Inventory.NoAttenPickupSound Inventory.PickupMessage "$TXT_WEAPON_M4" Inventory.PickupSound "WeaponBuild" + Tag "$TAG_MWEAPBLOODSCOURGE" action native A_MStaffAttack(); action native A_MStaffPalette(); @@ -128,6 +129,7 @@ ACTOR MageStaffFX2 native +SCREENSEEKER +EXTREMEDEATH DeathSound "MageStaffExplode" + Obituary "$OB_MPMWEAPBLOODSCOURGE" action native A_MStaffTrack(); diff --git a/wadsrc/static/actors/hexen/magewand.txt b/wadsrc/static/actors/hexen/magewand.txt index ebb8e2ca12..aaac9e5da0 100644 --- a/wadsrc/static/actors/hexen/magewand.txt +++ b/wadsrc/static/actors/hexen/magewand.txt @@ -7,6 +7,7 @@ ACTOR MWeapWand : MageWeapon Weapon.SelectionOrder 3600 Weapon.KickBack 0 Weapon.YAdjust 9 + Tag "$TAG_MWEAPWAND" States { @@ -56,6 +57,7 @@ ACTOR MageWandMissile : FastProjectile +SPAWNSOUNDSOURCE MissileType "MageWandSmoke" SeeSound "MageWandFire" + Obituary "$OB_MPMWEAPWAND" States { Spawn: diff --git a/wadsrc/static/actors/hexen/mana.txt b/wadsrc/static/actors/hexen/mana.txt index 6130fe9624..ee7ddd37c6 100644 --- a/wadsrc/static/actors/hexen/mana.txt +++ b/wadsrc/static/actors/hexen/mana.txt @@ -81,6 +81,7 @@ ACTOR ArtiBoostMana : CustomInventory 8003 Inventory.Icon "ARTIBMAN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIBOOSTMANA" + Tag "$TAG_ARTIBOOSTMANA" States { Spawn: diff --git a/wadsrc/static/actors/hexen/puzzleitems.txt b/wadsrc/static/actors/hexen/puzzleitems.txt index c3bdf29ba5..dd15fc8fd9 100644 --- a/wadsrc/static/actors/hexen/puzzleitems.txt +++ b/wadsrc/static/actors/hexen/puzzleitems.txt @@ -8,6 +8,7 @@ ACTOR PuzzSkull : PuzzleItem 9002 PuzzleItem.Number 0 Inventory.Icon ARTISKLL Inventory.PickupMessage "$TXT_ARTIPUZZSKULL" + Tag "$TAG_ARTIPUZZSKULL" States { Spawn: @@ -26,6 +27,7 @@ ACTOR PuzzGemBig : PuzzleItem 9003 PuzzleItem.Number 1 Inventory.Icon ARTIBGEM Inventory.PickupMessage "$TXT_ARTIPUZZGEMBIG" + Tag "$TAG_ARTIPUZZGEMBIG" States { Spawn: @@ -43,6 +45,7 @@ ACTOR PuzzGemRed : PuzzleItem 9004 PuzzleItem.Number 2 Inventory.Icon ARTIGEMR Inventory.PickupMessage "$TXT_ARTIPUZZGEMRED" + Tag "$TAG_ARTIPUZZGEMRED" States { Spawn: @@ -61,6 +64,7 @@ ACTOR PuzzGemGreen1 : PuzzleItem 9005 PuzzleItem.Number 3 Inventory.Icon ARTIGEMG Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN1" + Tag "$TAG_ARTIPUZZGEMGREEN1" States { Spawn: @@ -79,6 +83,7 @@ ACTOR PuzzGemGreen2 : PuzzleItem 9009 PuzzleItem.Number 4 Inventory.Icon ARTIGMG2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMGREEN2" + Tag "$TAG_ARTIPUZZGEMGREEN2" States { Spawn: @@ -97,6 +102,7 @@ ACTOR PuzzGemBlue1 : PuzzleItem 9006 PuzzleItem.Number 5 Inventory.Icon ARTIGEMB Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE1" + Tag "$TAG_ARTIPUZZGEMBLUE1" States { Spawn: @@ -115,6 +121,7 @@ ACTOR PuzzGemBlue2 : PuzzleItem 9010 PuzzleItem.Number 6 Inventory.Icon ARTIGMB2 Inventory.PickupMessage "$TXT_ARTIPUZZGEMBLUE2" + Tag "$TAG_ARTIPUZZGEMBLUE2" States { Spawn: @@ -133,6 +140,7 @@ ACTOR PuzzBook1 : PuzzleItem 9007 PuzzleItem.Number 7 Inventory.Icon ARTIBOK1 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK1" + Tag "$TAG_ARTIPUZZBOOK1" States { Spawn: @@ -151,6 +159,7 @@ ACTOR PuzzBook2 : PuzzleItem 9008 PuzzleItem.Number 8 Inventory.Icon ARTIBOK2 Inventory.PickupMessage "$TXT_ARTIPUZZBOOK2" + Tag "$TAG_ARTIPUZZBOOK2" States { Spawn: @@ -169,6 +178,7 @@ ACTOR PuzzFlameMask : PuzzleItem 9014 PuzzleItem.Number 9 Inventory.Icon ARTISKL2 Inventory.PickupMessage "$TXT_ARTIPUZZSKULL2" + Tag "$TAG_ARTIPUZZSKULL2" States { Spawn: @@ -185,6 +195,7 @@ ACTOR PuzzFWeapon : PuzzleItem 9015 PuzzleItem.Number 10 Inventory.Icon ARTIFWEP Inventory.PickupMessage "$TXT_ARTIPUZZFWEAPON" + Tag "$TAG_ARTIPUZZFWEAPON" States { Spawn: @@ -202,6 +213,7 @@ ACTOR PuzzCWeapon : PuzzleItem 9016 PuzzleItem.Number 11 Inventory.Icon ARTICWEP Inventory.PickupMessage "$TXT_ARTIPUZZCWEAPON" + Tag "$TAG_ARTIPUZZCWEAPON" States { Spawn: @@ -219,6 +231,7 @@ ACTOR PuzzMWeapon : PuzzleItem 9017 PuzzleItem.Number 12 Inventory.Icon ARTIMWEP Inventory.PickupMessage "$TXT_ARTIPUZZMWEAPON" + Tag "$TAG_ARTIPUZZMWEAPON" States { Spawn: @@ -235,6 +248,7 @@ ACTOR PuzzGear1 : PuzzleItem 9018 PuzzleItem.Number 13 Inventory.Icon ARTIGEAR Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR1" States { Spawn: @@ -252,6 +266,7 @@ ACTOR PuzzGear2 : PuzzleItem 9019 PuzzleItem.Number 14 Inventory.Icon ARTIGER2 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR2" States { Spawn: @@ -269,6 +284,7 @@ ACTOR PuzzGear3 : PuzzleItem 9020 PuzzleItem.Number 15 Inventory.Icon ARTIGER3 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR3" States { Spawn: @@ -286,6 +302,7 @@ ACTOR PuzzGear4 : PuzzleItem 9021 PuzzleItem.Number 16 Inventory.Icon ARTIGER4 Inventory.PickupMessage "$TXT_ARTIPUZZGEAR" + Tag "$TAG_ARTIPUZZGEAR4" States { Spawn: diff --git a/wadsrc/static/actors/hexen/speedboots.txt b/wadsrc/static/actors/hexen/speedboots.txt index 17d193d2ac..5fd6703b87 100644 --- a/wadsrc/static/actors/hexen/speedboots.txt +++ b/wadsrc/static/actors/hexen/speedboots.txt @@ -9,6 +9,7 @@ ACTOR ArtiSpeedBoots : PowerupGiver 8002 +INVENTORY.PICKUPFLASH Inventory.Icon ARTISPED Inventory.PickupMessage "$TXT_ARTISPEED" + Tag "$TAG_ARTISPEED" Powerup.Type Speed States { diff --git a/wadsrc/static/actors/hexen/summon.txt b/wadsrc/static/actors/hexen/summon.txt index 399c2dd89b..a4b3b85844 100644 --- a/wadsrc/static/actors/hexen/summon.txt +++ b/wadsrc/static/actors/hexen/summon.txt @@ -14,6 +14,7 @@ ACTOR ArtiDarkServant : Inventory 86 native Inventory.Icon "ARTISUMN" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUMMON" + Tag "$TAG_ARTISUMMON" States { Spawn: diff --git a/wadsrc/static/actors/hexen/teleportother.txt b/wadsrc/static/actors/hexen/teleportother.txt index bcbdfc33cb..77a05f6afe 100644 --- a/wadsrc/static/actors/hexen/teleportother.txt +++ b/wadsrc/static/actors/hexen/teleportother.txt @@ -14,6 +14,7 @@ ACTOR ArtiTeleportOther : Inventory 10040 native Inventory.Icon "ARTITELO" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORTOTHER" + Tag "$TAG_ARTITELEPORTOTHER" States { Spawn: diff --git a/wadsrc/static/actors/raven/artiegg.txt b/wadsrc/static/actors/raven/artiegg.txt index 6b8de50c9b..4662355882 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,6 +92,7 @@ ACTOR ArtiPork : CustomInventory 30 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIEGG2" Inventory.DefMaxAmount + Tag "$TAG_ARTIPORK" States { Spawn: diff --git a/wadsrc/static/actors/raven/artitele.txt b/wadsrc/static/actors/raven/artitele.txt index 5c1fa9d8fc..1f0fe73d22 100644 --- a/wadsrc/static/actors/raven/artitele.txt +++ b/wadsrc/static/actors/raven/artitele.txt @@ -13,6 +13,7 @@ ACTOR ArtiTeleport : Inventory 36 native Inventory.Icon "ARTIATLP" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTITELEPORT" + Tag "$TAG_ARTITELEPORT" States { Spawn: diff --git a/wadsrc/static/actors/raven/ravenartifacts.txt b/wadsrc/static/actors/raven/ravenartifacts.txt index 84264764b5..c02c19c3be 100644 --- a/wadsrc/static/actors/raven/ravenartifacts.txt +++ b/wadsrc/static/actors/raven/ravenartifacts.txt @@ -13,6 +13,7 @@ ACTOR ArtiHealth : HealthPickup 82 Inventory.Icon ARTIPTN2 Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTIHEALTH" + Tag "$TAG_ARTIHEALTH" HealthPickup.Autouse 1 States { @@ -36,6 +37,7 @@ ACTOR ArtiSuperHealth : HealthPickup 32 Inventory.Icon ARTISPHL Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_ARTISUPERHEALTH" + Tag "$TAG_ARTISUPERHEALTH" HealthPickup.Autouse 2 States { @@ -58,6 +60,7 @@ ACTOR ArtiFly : PowerupGiver 83 Inventory.RespawnTics 4230 Inventory.Icon ARTISOAR Inventory.PickupMessage "$TXT_ARTIFLY" + Tag "$TAG_ARTIFLY" Powerup.Type Flight States { @@ -79,6 +82,7 @@ ACTOR ArtiInvulnerability : PowerupGiver 84 Inventory.RespawnTics 4230 Inventory.Icon ARTIINVU Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY" + Tag "$TAG_ARTIINVULNERABILITY" Powerup.Type Invulnerable Powerup.Color GoldMap States @@ -102,6 +106,7 @@ ACTOR ArtiInvulnerability2 : PowerupGiver 84 Inventory.Icon ARTIDEFN Inventory.PickupMessage "$TXT_ARTIINVULNERABILITY2" Powerup.Type Invulnerable + Tag "$TAG_ARTIDEFENDER" States { Spawn: @@ -121,6 +126,7 @@ ACTOR ArtiTorch : PowerupGiver 33 +INVENTORY.PICKUPFLASH Inventory.Icon ARTITRCH Inventory.PickupMessage "$TXT_ARTITORCH" + Tag "$TAG_ARTITORCH" Powerup.Type Torch States { diff --git a/wadsrc/static/actors/strife/acolyte.txt b/wadsrc/static/actors/strife/acolyte.txt index 73b27864b3..1d04147f03 100644 --- a/wadsrc/static/actors/strife/acolyte.txt +++ b/wadsrc/static/actors/strife/acolyte.txt @@ -15,7 +15,7 @@ ACTOR Acolyte : StrifeHumanoid +FLOORCLIP +NEVERRESPAWN MinMissileChance 150 - Tag "ACOLYTE" + Tag "$TAG_ACOLYTE" SeeSound "acolyte/sight" PainSound "acolyte/pain" AttackSound "acolyte/rifle" diff --git a/wadsrc/static/actors/strife/beggars.txt b/wadsrc/static/actors/strife/beggars.txt index 8820f32d05..b24080b1a7 100644 --- a/wadsrc/static/actors/strife/beggars.txt +++ b/wadsrc/static/actors/strife/beggars.txt @@ -13,7 +13,7 @@ ACTOR Beggar : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Beggar" + Tag "$TAG_BEGGAR" MaxStepHeight 16 MaxDropoffHeight 32 HitObituary "$OB_BEGGAR" diff --git a/wadsrc/static/actors/strife/coin.txt b/wadsrc/static/actors/strife/coin.txt index f569fb1ed1..e4e50af779 100644 --- a/wadsrc/static/actors/strife/coin.txt +++ b/wadsrc/static/actors/strife/coin.txt @@ -10,7 +10,7 @@ ACTOR Coin : Inventory 93 native +FLOORCLIP Inventory.MaxAmount 0x7fffffff +INVENTORY.INVBAR - Tag "coin" + Tag "$TAG_COIN" Inventory.Icon "I_COIN" Inventory.PickupMessage "$TXT_COIN" States @@ -29,7 +29,7 @@ ACTOR Gold10 : Coin 138 Game Strife ConversationID 169, 162, 166 Inventory.Amount 10 - Tag "10 gold" + Tag "$TAG_10GOLD" Inventory.PickupMessage "$TXT_10GOLD" States { @@ -46,7 +46,7 @@ ACTOR Gold25 : Coin 139 Game Strife ConversationID 170, 163, 167 Inventory.Amount 25 - Tag "25 gold" + Tag "$TAG_25GOLD" Inventory.PickupMessage "$TXT_25GOLD" States { @@ -63,7 +63,7 @@ ACTOR Gold50 : Coin 140 Game Strife ConversationID 171, 164, 168 Inventory.Amount 50 - Tag "50 gold" + Tag "$TAG_50GOLD" Inventory.PickupMessage "$TXT_50GOLD" States { @@ -79,7 +79,7 @@ ACTOR Gold300 : Coin { ConversationID 172, -1, -1 Inventory.Amount 300 - Tag "300 gold" + Tag "$TAG_300GOLD" Inventory.PickupMessage "$TXT_300GOLD" Inventory.GiveQuest 3 +INVENTORY.ALWAYSPICKUP diff --git a/wadsrc/static/actors/strife/loremaster.txt b/wadsrc/static/actors/strife/loremaster.txt index dd79c0f19e..fee3eb0f06 100644 --- a/wadsrc/static/actors/strife/loremaster.txt +++ b/wadsrc/static/actors/strife/loremaster.txt @@ -23,7 +23,7 @@ ACTOR Loremaster 12 +NEVERRESPAWN DamageFactor "Fire", 0.5 MinMissileChance 150 - Tag "PRIEST" + Tag "$TAG_PRIEST" SeeSound "loremaster/sight" AttackSound "loremaster/attack" PainSound "loremaster/pain" diff --git a/wadsrc/static/actors/strife/macil.txt b/wadsrc/static/actors/strife/macil.txt index 3666728e07..ac23e4e660 100644 --- a/wadsrc/static/actors/strife/macil.txt +++ b/wadsrc/static/actors/strife/macil.txt @@ -23,7 +23,7 @@ ACTOR Macil1 64 PainSound "macil/pain" ActiveSound "macil/active" CrushPainSound "misc/pcrush" - Tag "MACIL" + Tag "$TAG_MACIL1" Obituary "$OB_MACIL" DropItem "BoxOfBullets" MaxStepHeight 16 @@ -66,6 +66,7 @@ ACTOR Macil2 : Macil1 200 +COUNTKILL +SPECTRAL -NODAMAGE + Tag "$TAG_MACIL2" DeathSound "macil/slop" DropItem "None" DamageFactor "SpectralLow", 0 diff --git a/wadsrc/static/actors/strife/merchants.txt b/wadsrc/static/actors/strife/merchants.txt index 65f5ac653f..b6a2e09a5e 100644 --- a/wadsrc/static/actors/strife/merchants.txt +++ b/wadsrc/static/actors/strife/merchants.txt @@ -59,7 +59,7 @@ ACTOR WeaponSmith : Merchant 116 Game Strife ConversationID 2 PainSound "smith/pain" - Tag "Weapon Smith" + Tag "$TAG_WEAPONSMITH" } @@ -72,7 +72,7 @@ ACTOR BarKeep : Merchant 72 ConversationID 3 PainSound "barkeep/pain" ActiveSound "barkeep/active" - Tag "Bar Keep" + Tag "$TAG_BARKEEP" } @@ -84,7 +84,7 @@ ACTOR Armorer : Merchant 73 Translation 5 ConversationID 4 PainSound "armorer/pain" - Tag "Armorer" + Tag "$TAG_ARMORER" } @@ -96,6 +96,6 @@ ACTOR Medic : Merchant 74 Translation 6 ConversationID 5 PainSound "medic/pain" - Tag "Medic" + Tag "$TAG_MEDIC" } diff --git a/wadsrc/static/actors/strife/oracle.txt b/wadsrc/static/actors/strife/oracle.txt index 03b80c8946..2e0aff948a 100644 --- a/wadsrc/static/actors/strife/oracle.txt +++ b/wadsrc/static/actors/strife/oracle.txt @@ -15,7 +15,7 @@ ACTOR Oracle 199 DamageFactor "Fire", 0.5 DamageFactor "SpectralLow", 0 MaxDropoffHeight 32 - Tag "Oracle" + Tag "$TAG_ORACLE" DropItem "Meat" action native A_WakeOracleSpectre (); diff --git a/wadsrc/static/actors/strife/questitems.txt b/wadsrc/static/actors/strife/questitems.txt index b06cdfaa84..cf6b98b0b2 100644 --- a/wadsrc/static/actors/strife/questitems.txt +++ b/wadsrc/static/actors/strife/questitems.txt @@ -64,19 +64,19 @@ ACTOR QuestItem3 : QuestItem ACTOR QuestItem4 : QuestItem { ConversationID 315, 296, 313 - Tag "quest4" + Tag "$TAG_QUEST4" } ACTOR QuestItem5 : QuestItem { ConversationID 316, 297, 314 - Tag "quest5" + Tag "$TAG_QUEST5" } ACTOR QuestItem6 : QuestItem { ConversationID 317, 298, 315 - Tag "quest4" + Tag "TAG_QUEST6" } ACTOR QuestItem7 : QuestItem diff --git a/wadsrc/static/actors/strife/ratbuddy.txt b/wadsrc/static/actors/strife/ratbuddy.txt index 762aff6593..eaefda35d3 100644 --- a/wadsrc/static/actors/strife/ratbuddy.txt +++ b/wadsrc/static/actors/strife/ratbuddy.txt @@ -12,7 +12,7 @@ ACTOR RatBuddy 85 MinMissileChance 150 MaxStepHeight 16 MaxDropoffHeight 32 - Tag "rat buddy" + Tag "$TAG_RATBUDDY" SeeSound "rat/sight" DeathSound "rat/death" ActiveSound "rat/active" diff --git a/wadsrc/static/actors/strife/rebels.txt b/wadsrc/static/actors/strife/rebels.txt index ff6faeec2d..3c539bca4f 100644 --- a/wadsrc/static/actors/strife/rebels.txt +++ b/wadsrc/static/actors/strife/rebels.txt @@ -14,7 +14,7 @@ ACTOR Rebel : StrifeHumanoid -COUNTKILL +NOSPLASHALERT MinMissileChance 150 - Tag "Rebel" + Tag "$TAG_REBEL" SeeSound "rebel/sight" PainSound "rebel/pain" DeathSound "rebel/death" @@ -124,7 +124,7 @@ ACTOR TeleporterBeacon : Inventory 10 native +DROPPED +INVENTORY.INVBAR Inventory.Icon "I_BEAC" - Tag "Teleporter Beacon" + Tag "$TAG_TELEPORTERBEACON" Inventory.PickupMessage "$TXT_BEACON" action native A_Beacon (); diff --git a/wadsrc/static/actors/strife/sigil.txt b/wadsrc/static/actors/strife/sigil.txt index ab9c6d21bf..a2cc66c7ba 100644 --- a/wadsrc/static/actors/strife/sigil.txt +++ b/wadsrc/static/actors/strife/sigil.txt @@ -11,7 +11,7 @@ ACTOR Sigil : Weapon native +FLOORCLIP +WEAPON.CHEATNOTWEAPON Inventory.PickupSound "weapons/sigilcharge" - Tag "SIGIL" + Tag "$TAG_SIGIL" Inventory.Icon "I_SGL1" Inventory.PickupMessage "$TXT_SIGIL" diff --git a/wadsrc/static/actors/strife/strifeammo.txt b/wadsrc/static/actors/strife/strifeammo.txt index 1de16474fd..8017144340 100644 --- a/wadsrc/static/actors/strife/strifeammo.txt +++ b/wadsrc/static/actors/strife/strifeammo.txt @@ -10,7 +10,7 @@ ACTOR HEGrenadeRounds : Ammo 152 Ammo.BackpackAmount 6 Ammo.BackpackMaxAmount 60 Inventory.Icon "I_GRN1" - Tag "HE-Grenade Rounds" + Tag "$TAG_HEGRENADES" Inventory.PickupMessage "$TXT_HEGRENADES" States { @@ -32,7 +32,7 @@ ACTOR PhosphorusGrenadeRounds : Ammo 153 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 32 Inventory.Icon "I_GRN2" - Tag "Phoshorus-Grenade Rounds" // "Fire-Grenade_Rounds" in the Teaser + Tag "$TAG_PHGRENADES" Inventory.PickupMessage "$TXT_PHGRENADES" States { @@ -55,7 +55,7 @@ ACTOR ClipOfBullets : Ammo 2007 Ammo.BackpackAmount 10 Ammo.BackpackMaxAmount 500 Inventory.Icon "I_BLIT" - Tag "Clip of Bullets" // "bullets" in the Teaser + Tag "$TAG_CLIPOFBULLETS" Inventory.PickupMessage "$TXT_CLIPOFBULLETS" States { @@ -73,7 +73,7 @@ ACTOR BoxOfBullets : ClipOfBullets 2048 SpawnID 139 ConversationID 180, 174, 178 Inventory.Amount 50 - Tag "Ammo" + Tag "$TAG_BOXOFBULLETS" Inventory.PickupMessage "$TXT_BOXOFBULLETS" States { @@ -96,7 +96,7 @@ ACTOR MiniMissiles : Ammo 2010 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 200 Inventory.Icon "I_ROKT" - Tag "Mini Missiles" //"rocket" in the Teaser + Tag "$TAG_MINIMISSILES" Inventory.PickupMessage "$TXT_MINIMISSILES" States { @@ -114,7 +114,7 @@ ACTOR CrateOfMissiles : MiniMissiles 2046 SpawnID 141 ConversationID 182, 176, 180 Inventory.Amount 20 - Tag "Crate of Missiles" //"box_of_rockets" in the Teaser + Tag "$TAG_CRATEOFMISSILES" Inventory.PickupMessage "$TXT_CRATEOFMISSILES" States { @@ -138,7 +138,7 @@ ACTOR EnergyPod : Ammo 2047 Ammo.BackpackMaxAmount 800 Ammo.DropAmount 20 Inventory.Icon "I_BRY1" - Tag "Energy Pod" + Tag "$TAG_ENERGYPOD" Inventory.PickupMessage "$TXT_ENERGYPOD" States { @@ -156,7 +156,7 @@ ACTOR EnergyPack : EnergyPod 17 SpawnID 142 ConversationID 184, 178, 182 Inventory.Amount 100 - Tag "Energy Pack" + Tag "$TAG_ENERGYPACK" Inventory.PickupMessage "$TXT_ENERGYPACK" States { @@ -178,7 +178,7 @@ ACTOR PoisonBolts : Ammo 115 Ammo.BackpackAmount 2 Ammo.BackpackMaxAmount 50 Inventory.Icon "I_PQRL" - Tag "Poison Bolts" // "poison_arrows" in the Teaser + Tag "$TAG_POISONBOLTS" Inventory.PickupMessage "$TXT_POISONBOLTS" States { @@ -200,7 +200,7 @@ ACTOR ElectricBolts : Ammo 114 Ammo.BackpackAmount 4 Ammo.BackpackMaxAmount 100 Inventory.Icon "I_XQRL" - Tag "Electric Bolts" // "electric_arrows" in the Teaser + Tag "$TAG_ELECTRICBOLTS" Inventory.PickupMessage "$TXT_ELECTRICBOLTS" States { @@ -219,7 +219,7 @@ ACTOR AmmoSatchel : BackpackItem 183 ConversationID 187, 181, 184 +FLOORCLIP Inventory.Icon "I_BKPK" - Tag "Ammo Satchel" // "Back_pack" in the Teaser + Tag "$TAG_AMMOSATCHEL" Inventory.PickupMessage "$TXT_AMMOSATCHEL" States { diff --git a/wadsrc/static/actors/strife/strifearmor.txt b/wadsrc/static/actors/strife/strifearmor.txt index 2be8d8ad67..2431e9438e 100644 --- a/wadsrc/static/actors/strife/strifearmor.txt +++ b/wadsrc/static/actors/strife/strifearmor.txt @@ -14,7 +14,7 @@ ACTOR MetalArmor : BasicArmorPickup 2019 Inventory.PickupMessage "$TXT_METALARMOR" Armor.SaveAmount 200 Armor.SavePercent 50 - Tag "Metal Armor" + Tag "$TAG_METALARMOR" States { Spawn: @@ -38,7 +38,7 @@ ACTOR LeatherArmor : BasicArmorPickup 2018 Inventory.PickupMessage "$TXT_LEATHERARMOR" Armor.SaveAmount 100 Armor.SavePercent 33.335 - Tag "Leather Armor" + Tag "$TAG_LEATHER" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifeitems.txt b/wadsrc/static/actors/strife/strifeitems.txt index 407a91843e..74b957b83e 100644 --- a/wadsrc/static/actors/strife/strifeitems.txt +++ b/wadsrc/static/actors/strife/strifeitems.txt @@ -8,7 +8,7 @@ ACTOR MedPatch : HealthPickup 2011 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 20 - Tag "Med patch" + Tag "$TAG_MEDPATCH" Inventory.Icon "I_STMP" Inventory.PickupMessage "$TXT_MEDPATCH" HealthPickup.Autouse 3 @@ -31,7 +31,7 @@ ACTOR MedicalKit : HealthPickup 2012 +FLOORCLIP +INVENTORY.INVBAR Inventory.MaxAmount 15 - Tag "Medical kit" + Tag "$TAG_MEDICALKIT" Inventory.Icon "I_MDKT" Inventory.PickupMessage "$TXT_MEDICALKIT" HealthPickup.Autouse 3 @@ -54,7 +54,7 @@ ACTOR SurgeryKit : HealthPickup 83 +INVENTORY.INVBAR Health -100 Inventory.MaxAmount 5 - Tag "Surgery Kit" // "full_health" in the Teaser + Tag "$TAG_SURGERYKIT" Inventory.Icon "I_FULL" Inventory.PickupMessage "$TXT_SURGERYKIT" States @@ -75,7 +75,7 @@ ACTOR StrifeMap : MapRevealer 2026 ConversationID 164, 160, 163 +FLOORCLIP Inventory.PickupSound "misc/p_pkup" - Inventory.PickupMessage "$TXT_STRIFEMAP" + Inventory.PickupMessage "$TXT_STRIFEMAP" States { Spawn: @@ -94,7 +94,7 @@ ACTOR BeldinsRing : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 173, 165, 169 - Tag "Ring" + Tag "$TAG_BELDINSRING" Inventory.Icon "I_RING" Inventory.GiveQuest 1 Inventory.PickupMessage "$TXT_BELDINSRING" @@ -118,7 +118,7 @@ ACTOR OfferingChalice : Inventory 205 ConversationID 174, 166, 170 Radius 10 Height 16 - Tag "Offering Chalice" + Tag "$TAG_OFFERINGCHALICE" Inventory.Icon "I_RELC" Inventory.PickupMessage "$TXT_OFFERINGCHALICE" Inventory.GiveQuest 2 @@ -139,7 +139,7 @@ ACTOR Ear : Inventory +FLOORCLIP +INVENTORY.INVBAR ConversationID 175, 167, 171 - Tag "Ear" + Tag "$TAG_EAR" Inventory.Icon "I_EARS" Inventory.PickupMessage "$TXT_EAR" Inventory.GiveQuest 9 @@ -164,7 +164,7 @@ ACTOR BrokenPowerCoupling : Inventory 226 +INVENTORY.INVBAR Radius 16 Height 16 - Tag "Broken Power Coupling" + Tag "$TAG_BROKENCOUPLING" Inventory.MaxAmount 1 Inventory.Icon "I_COUP" Inventory.PickupMessage "$TXT_BROKENCOUPLING" @@ -190,7 +190,7 @@ ACTOR ShadowArmor : PowerupGiver 2024 +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND RenderStyle Translucent - Tag "Shadow Armor" + Tag "$TAG_SHADOWARMOR" Inventory.MaxAmount 2 Powerup.Type "Shadow" Inventory.Icon "I_SHD1" @@ -217,7 +217,7 @@ ACTOR EnvironmentalSuit : PowerupGiver 2025 -INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 5 Powerup.Type "Mask" - Tag "Environmental Suit" + Tag "$TAG_ENVSUIT" Inventory.Icon "I_MASK" Inventory.PickupSound "misc/i_pkup" Inventory.PickupMessage "$TXT_ENVSUIT" @@ -238,7 +238,7 @@ ACTOR GuardUniform : Inventory 90 ConversationID 162, 158, 161 +FLOORCLIP +INVENTORY.INVBAR - Tag "Guard Uniform" + Tag "$TAG_GUARDUNIFORM" Inventory.Icon "I_UNIF" Inventory.PickupMessage "$TXT_GUARDUNIFORM" Inventory.GiveQuest 15 @@ -259,7 +259,7 @@ ACTOR OfficersUniform : Inventory 52 ConversationID 163, 159, 162 +FLOORCLIP +INVENTORY.INVBAR - Tag "Officer's Uniform" + Tag "$TAG_OFFICERSUNIFORM" Inventory.Icon "I_OFIC" Inventory.PickupMessage "$TXT_OFFICERSUNIFORM" States @@ -280,7 +280,7 @@ ACTOR FlameThrowerParts : Inventory +FLOORCLIP +INVENTORY.INVBAR Inventory.Icon "I_BFLM" - Tag "Flame Thrower Parts" + Tag "$TAG_FTHROWERPARTS" Inventory.PickupMessage "$TXT_FTHROWERPARTS" States { @@ -300,7 +300,7 @@ ACTOR InterrogatorReport : Inventory Game Strife ConversationID 308, 289, 306 +FLOORCLIP - Tag "Report" + Tag "$TAG_REPORT" Inventory.PickupMessage "$TXT_REPORT" States { @@ -319,7 +319,7 @@ ACTOR Info : Inventory ConversationID 300, 282, 299 +FLOORCLIP +INVENTORY.INVBAR - Tag "Info" + Tag "$TAG_INFO" Inventory.Icon "I_TOKN" Inventory.PickupMessage "$TXT_INFO" States @@ -340,7 +340,7 @@ ACTOR Targeter : PowerupGiver 207 +FLOORCLIP +INVENTORY.INVBAR -INVENTORY.FANCYPICKUPSOUND - Tag "Targeter" + Tag "$TAG_TARGETER" Powerup.Type "Targeter" Inventory.MaxAmount 5 Inventory.Icon "I_TARG" @@ -361,7 +361,7 @@ ACTOR Communicator : Inventory 206 Game Strife ConversationID 176, 168, 172 +NOTDMATCH - Tag "Communicator" + Tag "$TAG_COMMUNICATOR" Inventory.Icon "I_COMM" Inventory.PickupSound "misc/p_pkup" Inventory.PickupMessage "$TXT_COMMUNICATOR" @@ -389,7 +389,7 @@ ACTOR DegninOre : Inventory 59 native +FLOORCLIP +INCOMBAT +INVENTORY.INVBAR - Tag "Degnin Ore" + Tag "$TAG_DEGNINORE" DeathSound "ore/explode" Inventory.Icon "I_XPRK" Inventory.PickupMessage "$TXT_DEGNINORE" @@ -418,7 +418,7 @@ ACTOR GunTraining : Inventory +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Accuracy" + Tag "$TAG_GUNTRAINING" Inventory.Icon "I_GUNT" States { @@ -438,7 +438,7 @@ ACTOR HealthTraining : Inventory native +INVENTORY.INVBAR +INVENTORY.UNDROPPABLE Inventory.MaxAmount 100 - Tag "Toughness" + Tag "$TAG_HEALTHTRAINING" Inventory.Icon "I_HELT" States { @@ -459,7 +459,7 @@ ACTOR Scanner : PowerupGiver 2027 native +FLOORCLIP +INVENTORY.FANCYPICKUPSOUND Inventory.MaxAmount 1 - Tag "Scanner" + Tag "$TAG_SCANNER" Inventory.Icon "I_PMUP" Powerup.Type "Scanner" Inventory.PickupSound "misc/i_pkup" @@ -479,8 +479,8 @@ ACTOR PrisonPass : Key native Game Strife ConversationID 304, 286, 303 Inventory.Icon "I_TOKN" - Tag "Prison Pass" - Inventory.PickupMessage "TXT_PRISONPASS" + Tag "$TAG_PRISONPASS" + Inventory.PickupMessage "$TXT_PRISONPASS" States { Spawn: @@ -509,7 +509,7 @@ ACTOR DummyStrifeItem : Inventory native ACTOR RaiseAlarm : DummyStrifeItem native { ConversationID 301, 283, 300 - Tag "Alarm" + Tag "$TAG_ALARM" } // Open door tag 222 -------------------------------------------------------- @@ -538,7 +538,7 @@ ACTOR OpenDoor224 : DummyStrifeItem native ACTOR AmmoFillup : DummyStrifeItem native { ConversationID 298,280,297 - Tag "Ammo" + Tag "$TAG_AMMOFILLUP" } // Health ------------------------------------------------------------------- @@ -546,7 +546,7 @@ ACTOR AmmoFillup : DummyStrifeItem native ACTOR HealthFillup : DummyStrifeItem native { ConversationID 299,281,298 - Tag "Health" + Tag "$TAG_HEALTHFILLUP" } // Upgrade Stamina ---------------------------------------------------------- diff --git a/wadsrc/static/actors/strife/strifekeys.txt b/wadsrc/static/actors/strife/strifekeys.txt index d128cbbd64..b304f83bcb 100644 --- a/wadsrc/static/actors/strife/strifekeys.txt +++ b/wadsrc/static/actors/strife/strifekeys.txt @@ -13,7 +13,7 @@ ACTOR BaseKey : StrifeKey 230 Game Strife ConversationID 133, 129, 132 Inventory.Icon "I_FUSL" - Tag "Base Key" + Tag "$TAG_BASEKEY" Inventory.PickupMessage "$TXT_BASEKEY" States { @@ -31,7 +31,7 @@ ACTOR GovsKey : StrifeKey Game Strife ConversationID 134, 130, 133 Inventory.Icon "I_REBL" - Tag "Govs Key" // "Rebel_Key" in the Teaser + Tag "$TAG_GOVSKEY" Inventory.PickupMessage "$TXT_GOVSKEY" States { @@ -49,7 +49,7 @@ ACTOR Passcard : StrifeKey 185 Game Strife ConversationID 135, 131, 134 Inventory.Icon "I_TPAS" - Tag "Passcard" + Tag "$TAG_PASSCARD" Inventory.PickupMessage "$TXT_PASSCARD" States { @@ -67,7 +67,7 @@ ACTOR IDBadge : StrifeKey 184 Game Strife ConversationID 136, 132, 135 Inventory.Icon "I_CRD1" - Tag "ID Badge" + Tag "$TAG_IDBADGE" Inventory.PickupMessage "$TXT_IDBADGE" States { @@ -85,7 +85,7 @@ ACTOR PrisonKey : StrifeKey Game Strife ConversationID 137, 133, 136 Inventory.Icon "I_PRIS" - Tag "Prison Key" + Tag "$TAG_PRISONKEY" Inventory.GiveQuest 11 Inventory.PickupMessage "$TXT_PRISONKEY" States @@ -104,7 +104,7 @@ ACTOR SeveredHand : StrifeKey 91 Game Strife ConversationID 138, 134, 137 Inventory.Icon "I_HAND" - Tag "Severed Hand" + Tag "$TAG_SEVEREDHAND" Inventory.GiveQuest 12 Inventory.PickupMessage "$TXT_SEVEREDHAND" States @@ -123,7 +123,7 @@ ACTOR Power1Key : StrifeKey Game Strife ConversationID 139, 135, 138 Inventory.Icon "I_PWR1" - Tag "Power1 Key" + Tag "$TAG_POWER1KEY" Inventory.PickupMessage "$TXT_POWER1KEY" States { @@ -141,7 +141,7 @@ ACTOR Power2Key : StrifeKey Game Strife ConversationID 140, 136, 139 Inventory.Icon "I_PWR2" - Tag "Power2 Key" + Tag "$TAG_POWER2KEY" Inventory.PickupMessage "$TXT_POWER2KEY" States { @@ -159,7 +159,7 @@ ACTOR Power3Key : StrifeKey Game Strife ConversationID 141, 137, 140 Inventory.Icon "I_PWR3" - Tag "Power3 Key" + Tag "$TAG_POWER3KEY" Inventory.PickupMessage "$TXT_POWER3KEY" States { @@ -177,7 +177,7 @@ ACTOR GoldKey : StrifeKey 40 Game Strife ConversationID 142, 138, 141 Inventory.Icon "I_KY1G" - Tag "Gold Key" + Tag "$TAG_GOLDKEY" Inventory.PickupMessage "$TXT_GOLDKEY" States { @@ -195,7 +195,7 @@ ACTOR IDCard : StrifeKey 13 Game Strife ConversationID 143, 139, 142 Inventory.Icon "I_CRD2" - Tag "ID Card" + Tag "$TAG_IDCARD" Inventory.PickupMessage "$TXT_IDCARD" States { @@ -213,7 +213,7 @@ ACTOR SilverKey : StrifeKey 38 Game Strife ConversationID 144, 140, 143 Inventory.Icon "I_KY2S" - Tag "Silver Key" + Tag "$TAG_SILVERKEY" Inventory.PickupMessage "$TXT_SILVERKEY" States { @@ -231,7 +231,7 @@ ACTOR OracleKey : StrifeKey 61 Game Strife ConversationID 145, 141, 144 Inventory.Icon "I_ORAC" - Tag "Oracle Key" + Tag "$TAG_ORACLEKEY" Inventory.PickupMessage "$TXT_ORACLEKEY" States { @@ -249,7 +249,7 @@ ACTOR MilitaryID : StrifeKey Game Strife ConversationID 146, 142, 145 Inventory.Icon "I_GYID" - Tag "Military ID" + Tag "$TAG_MILITARYID" Inventory.PickupMessage "$TXT_MILITARYID" States { @@ -267,7 +267,7 @@ ACTOR OrderKey : StrifeKey 86 Game Strife ConversationID 147, 143, 146 Inventory.Icon "I_FUBR" - Tag "Order Key" + Tag "$TAG_ORDERKEY" Inventory.PickupMessage "$TXT_ORDERKEY" States { @@ -285,7 +285,7 @@ ACTOR WarehouseKey : StrifeKey 166 Game Strife ConversationID 148, 144, 147 Inventory.Icon "I_WARE" - Tag "Warehouse Key" + Tag "$TAG_WAREHOUSEKEY" Inventory.PickupMessage "$TXT_WAREHOUSEKEY" States { @@ -303,7 +303,7 @@ ACTOR BrassKey : StrifeKey 39 Game Strife ConversationID 149, 145, 148 Inventory.Icon "I_KY3B" - Tag "Brass Key" + Tag "$TAG_BRASSKEY" Inventory.PickupMessage "$TXT_BRASSKEY" States { @@ -321,7 +321,7 @@ ACTOR RedCrystalKey : StrifeKey 192 Game Strife ConversationID 150, 146, 149 Inventory.Icon "I_RCRY" - Tag "Red Crystal Key" + Tag "$TAG_REDCRYSTALKEY" Inventory.PickupMessage "$TXT_REDCRYSTAL" States { @@ -339,7 +339,7 @@ ACTOR BlueCrystalKey : StrifeKey 193 Game Strife ConversationID 151, 147, 150 Inventory.Icon "I_BCRY" - Tag "Blue Crystal Key" + Tag "$TAG_BLUECRYSTALKEY" Inventory.PickupMessage "$TXT_BLUECRYSTAL" States { @@ -357,7 +357,7 @@ ACTOR ChapelKey : StrifeKey 195 Game Strife ConversationID 152, 148, 151 Inventory.Icon "I_CHAP" - Tag "Chapel Key" + Tag "$TAG_CHAPELKEY" Inventory.PickupMessage "$TXT_CHAPELKEY" States { @@ -375,7 +375,7 @@ ACTOR CatacombKey : StrifeKey Game Strife ConversationID 153, 149, 152 Inventory.Icon "I_TUNL" - Tag "Catacomb Key" // "Tunnel_Key" in the Teaser + Tag "$TAG_CATACOMBKEY" Inventory.GiveQuest 28 Inventory.PickupMessage "$TXT_CATACOMBKEY" States @@ -394,7 +394,7 @@ ACTOR SecurityKey : StrifeKey Game Strife ConversationID 154, 150, 153 Inventory.Icon "I_SECK" - Tag "Security Key" + Tag "$TAG_SECURITYKEY" Inventory.PickupMessage "$TXT_SECURITYKEY" States { @@ -412,7 +412,7 @@ ACTOR CoreKey : StrifeKey 236 Game Strife ConversationID 155, 151, 154 Inventory.Icon "I_GOID" - Tag "Core Key" // "New_Key1" in the Teaser + Tag "$TAG_COREKEY" Inventory.PickupMessage "$TXT_COREKEY" States { @@ -430,7 +430,7 @@ ACTOR MaulerKey : StrifeKey 233 Game Strife ConversationID 156, 152, 155 Inventory.Icon "I_BLTK" - Tag "Mauler Key" // "New_Key2" in the Teaser + Tag "$TAG_MAULERKEY" Inventory.PickupMessage "$TXT_MAULERKEY" States { @@ -448,7 +448,7 @@ ACTOR FactoryKey : StrifeKey 234 Game Strife ConversationID 157, 153, 156 Inventory.Icon "I_PROC" - Tag "Factory Key" // "New_Key3" in the Teaser + Tag "$TAG_FACTORYKEY" Inventory.PickupMessage "$TXT_FACTORYKEY" States { @@ -465,8 +465,8 @@ ACTOR MineKey : StrifeKey 235 { Game Strife ConversationID 158, 154, 157 - Inventory.Icon "I_MINE" // "New_Key4" in the Teaser - Tag "Mine_Key" + Inventory.Icon "I_MINE" + Tag "$TAG_MINEKEY" Inventory.PickupMessage "$TXT_MINEKEY" States { @@ -484,7 +484,7 @@ ACTOR NewKey5 : StrifeKey Game Strife ConversationID 159, 155, 158 Inventory.Icon "I_BLTK" - Tag "New Key5" + Tag "$TAG_NEWKEY5" Inventory.PickupMessage "$TXT_NEWKEY5" States { @@ -505,7 +505,7 @@ ACTOR OraclePass : Inventory Inventory.Icon "I_OTOK" Inventory.GiveQuest 18 Inventory.PickupMessage "$TXT_ORACLEPASS" - Tag "Oracle Pass" + Tag "$TAG_ORACLEPASS" States { Spawn: diff --git a/wadsrc/static/actors/strife/strifeweapons.txt b/wadsrc/static/actors/strife/strifeweapons.txt index d287ace08d..5e3a419d56 100644 --- a/wadsrc/static/actors/strife/strifeweapons.txt +++ b/wadsrc/static/actors/strife/strifeweapons.txt @@ -48,6 +48,8 @@ ACTOR PunchDagger : StrifeWeapon Game Strife Weapon.SelectionOrder 3900 +WEAPON.NOALERT + Obituary "$OB_MPPUNCHDAGGER" + Tag "$TAG_PUNCHDAGGER" action native A_JabDagger (); @@ -109,6 +111,7 @@ ACTOR ElectricBolt : StrifeZap1 SeeSound "misc/swish" ActiveSound "misc/swish" DeathSound "weapons/xbowhit" + Obituary "$OB_MPELECTRICBOLT" States { Spawn: @@ -132,6 +135,7 @@ ACTOR PoisonBolt native MaxStepHeight 4 SeeSound "misc/swish" ActiveSound "misc/swish" + Obituary "$OB_MPPOISONBOLT" States { Spawn: @@ -158,8 +162,8 @@ ACTOR StrifeCrossbow : StrifeWeapon 2001 Weapon.AmmoType1 "ElectricBolts" Weapon.SisterWeapon "StrifeCrossbow2" Inventory.PickupMessage "$TXT_STRIFECROSSBOW" + Tag "$TAG_STRIFECROSSBOW1" Inventory.Icon "CBOWA0" - Tag "Crossbow" action native A_ClearFlash (); action native A_ShowElectricFlash (); @@ -205,6 +209,7 @@ ACTOR StrifeCrossbow2 : StrifeCrossbow Weapon.AmmoGive1 0 Weapon.AmmoType1 "PoisonBolts" Weapon.SisterWeapon "StrifeCrossbow" + Tag "$TAG_STRIFECROSSBOW2" States { @@ -243,8 +248,9 @@ actor AssaultGun : StrifeWeapon 2002 Weapon.AmmoGive1 20 Weapon.AmmoType1 "ClipOfBullets" Inventory.Icon "RIFLA0" - Tag "Assault Gun" + Tag "$TAG_ASSAULTGUN" Inventory.PickupMessage "$TXT_ASSAULTGUN" + Obituary "$OB_MPASSAULTGUN" States { Spawn: @@ -299,7 +305,7 @@ ACTOR MiniMissileLauncher : StrifeWeapon 2003 Weapon.AmmoGive1 8 Weapon.AmmoType1 "MiniMissiles" Inventory.Icon "MMSLA0" - Tag "Mini Missile Launcher" + Tag "$TAG_MMLAUNCHER" Inventory.PickupMessage "$TXT_MMLAUNCHER" action native A_FireMiniMissile (); @@ -376,6 +382,7 @@ ACTOR MiniMissile MaxStepHeight 4 SeeSound "weapons/minimissile" DeathSound "weapons/minimissilehit" + Obituary "$OB_MPMINIMISSILELAUNCHER" States { Spawn: @@ -407,7 +414,7 @@ ACTOR FlameThrower : StrifeWeapon 2005 Weapon.ReadySound "weapons/flameidle" Weapon.AmmoType1 "EnergyPod" Inventory.Icon "FLAMA0" - Tag "Flame Thrower" + Tag "$TAG_FLAMER" Inventory.PickupMessage "$TXT_FLAMER" action native A_FireFlamer (); @@ -451,6 +458,7 @@ ACTOR FlameMissile MaxStepHeight 4 RenderStyle Add SeeSound "weapons/flamethrower" + Obituary "$OB_MPFLAMETHROWER" action native A_FlameDie (); @@ -483,8 +491,9 @@ ACTOR Mauler : StrifeWeapon 2004 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler2" Inventory.Icon "TRPDA0" - Tag "Mauler" + Tag "$TAG_MAULER1" Inventory.PickupMessage "$TXT_MAULER" + Obituary "$OB_MPMAULER1" action native A_FireMauler1 (); @@ -525,6 +534,8 @@ ACTOR Mauler2 : Mauler Weapon.AmmoGive1 0 Weapon.AmmoType1 "EnergyPod" Weapon.SisterWeapon "Mauler" + Obituary "$OB_MPMAULER2" + Tag "$TAG_MAULER2" action native A_FireMauler2Pre (); action native A_FireMauler2 (); @@ -586,6 +597,7 @@ ACTOR MaulerTorpedo RenderStyle Add SeeSound "weapons/mauler2fire" DeathSound "weapons/mauler2hit" + Obituary "$OB_MPMAULER" action native A_MaulerTorpedoWave (); @@ -616,6 +628,7 @@ ACTOR MaulerTorpedoWave +STRIFEDAMAGE MaxStepHeight 4 RenderStyle Add + Obituary "$OB_MPMAULER" States { Spawn: @@ -650,6 +663,7 @@ ACTOR HEGrenade BounceCount 2 SeeSound "weapons/hegrenadeshoot" DeathSound "weapons/hegrenadebang" + Obituary "$OB_MPSTRIFEGRENADE" States { Spawn: @@ -687,6 +701,7 @@ ACTOR PhosphorousGrenade BounceCount 2 SeeSound "weapons/phgrenadeshoot" DeathSound "weapons/phgrenadebang" + Obituary "$OB_MPPHOSPHOROUSGRENADE" States { Spawn: @@ -710,6 +725,7 @@ ACTOR PhosphorousFire native +NODAMAGETHRUST +DONTSPLASH RenderStyle Add + Obituary "$OB_MPPHOSPHOROUSGRENADE" action native A_Burnarea (); action native A_Burnination (); @@ -748,7 +764,7 @@ ACTOR StrifeGrenadeLauncher : StrifeWeapon 154 Weapon.AmmoType1 "HEGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher2" Inventory.Icon "GRNDA0" - Tag "Grenade Launcher" + Tag "$TAG_GLAUNCHER1" Inventory.PickupMessage "$TXT_GLAUNCHER" action native A_FireGrenade (class grenadetype, int angleofs, state flash); @@ -794,6 +810,7 @@ ACTOR StrifeGrenadeLauncher2 : StrifeGrenadeLauncher Weapon.AmmoGive1 0 Weapon.AmmoType1 "PhosphorusGrenadeRounds" Weapon.SisterWeapon "StrifeGrenadeLauncher" + Tag "$TAG_GLAUNCHER2" States { diff --git a/wadsrc/static/actors/strife/templar.txt b/wadsrc/static/actors/strife/templar.txt index 214804b347..c20aed49bd 100644 --- a/wadsrc/static/actors/strife/templar.txt +++ b/wadsrc/static/actors/strife/templar.txt @@ -20,7 +20,7 @@ ACTOR Templar 3003 DeathSound "templar/death" ActiveSound "templar/active" CrushPainSound "misc/pcrush" - Tag "TEMPLAR" + Tag "$TAG_TEMPLAR" HitObituary "$OB_TEMPLARHIT" Obituary "$OB_TEMPLAR" DropItem "EnergyPod" diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 2f23edc2e9..5f05ba447d 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -727,6 +727,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 +787,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" From f6edfd1300fb681119086c1814a4f28b7d384e18 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 19 Aug 2010 07:59:32 +0000 Subject: [PATCH 12/84] - The Wiki seems to imply that FMod 4.28 is not the best choice for ZDoom so mention this in fmod_version.txt. SVN r2553 (trunk) --- specs/fmod_version.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/fmod_version.txt b/specs/fmod_version.txt index a65aec9184..c285c50b98 100644 --- a/specs/fmod_version.txt +++ b/specs/fmod_version.txt @@ -1 +1,3 @@ This version of ZDoom must be compiled with any version between 4.22 and 4.28 inclusive. +Use of the latest 4.26 is recommended though due to technical issues with 4.28. + From 576e597d2b71fd03a07972623e1476396f74fceb Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 15:31:44 +0000 Subject: [PATCH 13/84] - Increase the fluid_voices default to 128. It apparently has a poor voice cutting algorithm which is painfully apparent on hexen.wad MAP01's music, which hits around 90 voices. - Patch from Chris: * Add FluidSynth to the menu. * Enable FluidSynth for MIDI as well as MUS. * Fix CmakeLists.txt. SVN r2554 (trunk) --- src/CMakeLists.txt | 2 +- src/sound/i_music.cpp | 6 +++ src/sound/music_fluidsynth_mididevice.cpp | 2 +- src/sound/music_midi_base.cpp | 61 ++++++++++++++++------- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03cedcdedb..f8ebfa9f2e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -490,7 +490,7 @@ set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_L include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" ) if( FLUIDSYNTH_FOUND ) - set( ZDOOM_LIBS "${FLUIDSYNTH_LIBRARIES}" ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FLUIDSYNTH_LIBRARIES}" ) include_directories( "${FLUIDSYNTH_INCLUDE_DIR}" ) endif( FLUIDSYNTH_FOUND ) diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 51640b7470..8594223871 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -527,6 +527,12 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int { info = new MIDISong2(file, musiccache, len, MIDI_Timidity); } +#ifdef HAVE_FLUIDSYNTH + else if (snd_mididevice == -5 && device == MDEV_DEFAULT) + { + info = new MIDISong2(file, musiccache, len, MIDI_Fluid); + } +#endif if (info != NULL && !info->IsValid()) { delete info; diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 302166ad15..36a38d630a 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -83,7 +83,7 @@ CUSTOM_CVAR(Bool, fluid_chorus, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) currSong->FluidSettingStr("synth.chorus.active", self ? "yes" : "no"); } -CUSTOM_CVAR(Int, fluid_voices, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 16) self = 16; diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index 0184c6a10b..dd3cc95d8f 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -77,25 +77,34 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) { if (*outValues == NULL) { - int count = 3 + nummididevices; + int count = 4 + nummididevices; value_t *values; UINT id; - int p; + int p = 0; *outValues = values = new value_t[count]; - values[0].name = "OPL Synth Emulation"; - values[0].value = -3.0; - values[1].name = "TiMidity++"; - values[1].value = -2.0; - values[2].name = "FMOD"; - values[2].value = -1.0; - for (id = 0, p = 3; id < nummididevices; ++id) +#ifdef HAVE_FLUIDSYNTH + values[p].name = "FluidSynth"; + values[p].value = -5.0; + ++p; +#endif + values[p].name = "OPL Synth Emulation"; + values[p].value = -3.0; + ++p; + values[p].name = "TiMidity++"; + values[p].value = -2.0; + ++p; + values[p].name = "FMOD"; + values[p].value = -1.0; + ++p; + for (id = 0; id < nummididevices; ++id) { MIDIOUTCAPS caps; MMRESULT res; res = midiOutGetDevCaps (id, &caps, sizeof(caps)); + assert(res == MMSYSERR_NOERROR); if (res == MMSYSERR_NOERROR) { size_t len = strlen (caps.szPname) + 1; @@ -107,8 +116,7 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) ++p; } } - assert(p == count); - *numValues = float(count); + *numValues = float(p); } } @@ -154,6 +162,9 @@ CCMD (snd_listmididevices) MIDIOUTCAPS caps; MMRESULT res; +#ifdef HAVE_FLUIDSYNTH + PrintMidiDevice (-5, "FluidSynth", MOD_SWSYNTH, 0); +#endif PrintMidiDevice (-3, "Emulated OPL FM Synth", MOD_FMSYNTH, 0); PrintMidiDevice (-2, "TiMidity++", 0, MOD_SWSYNTH); PrintMidiDevice (-1, "FMOD", 0, MOD_SWSYNTH); @@ -191,21 +202,33 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) if (*outValues == NULL) { value_t *values; + int p = 0; - *outValues = values = new value_t[3]; + *outValues = values = new value_t[4]; - values[0].name = "OPL Synth Emulation"; - values[0].value = -3.0; - values[1].name = "TiMidity++"; - values[1].value = -2.0; - values[2].name = "FMOD"; - values[2].value = -1.0; - *numValues = 3.f; +#ifdef HAVE_FLUIDSYNTH + values[p].name = "FluidSynth"; + values[p].value = -5.0; + ++p; +#endif + values[p].name = "OPL Synth Emulation"; + values[p].value = -3.0; + ++p; + values[p].name = "TiMidity++"; + values[p].value = -2.0; + ++p; + values[p].name = "FMOD"; + values[p].value = -1.0; + ++p; + *numValues = float(p); } } CCMD (snd_listmididevices) { +#ifdef HAVE_FLUIDSYNTH + Printf("%s-5. FluidSynth\n", -5 == snd_mididevice ? TEXTCOLOR_BOLD : ""); +#endif Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-1. FMOD\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); From 2c397b1aaccb9e47ce7e0424626daf9706562407 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 15:46:49 +0000 Subject: [PATCH 14/84] - Add a default search for Creative's CT4MGM.SF2 on Windows if fluid_patchset is unspecified. - Use fluid_patchset to specify the SoundFont(s) for FluidSynth instead of doubling up with snd_midipatchset, which is already used by FMOD. SVN r2555 (trunk) --- src/sound/fmodsound.cpp | 2 +- src/sound/music_fluidsynth_mididevice.cpp | 25 +++++++++++++++++------ src/sound/music_midi_base.cpp | 2 -- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index ff99d516e6..23e374a7bd 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -101,7 +101,6 @@ EXTERN_CVAR (Int, snd_buffersize) EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Bool, snd_pitched) EXTERN_CVAR (Int, snd_channels) -EXTERN_CVAR (String, snd_midipatchset) extern int sfx_empty; @@ -117,6 +116,7 @@ CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_profile, false, 0) +CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); // Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter. CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 36a38d630a..fba167b710 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -55,12 +55,12 @@ // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -EXTERN_CVAR(String, snd_midipatchset) - // 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) @@ -143,16 +143,29 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice() { Printf("Failed to set interpolation method %d.\n", *fluid_interp); } - if (0 == LoadPatchSets(snd_midipatchset)) + 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 - Printf("Failed to load any MIDI patches.\n"); - delete_fluid_synth(FluidSynth); - FluidSynth = NULL; +#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")]; + if (0 != GetSystemDirectoryA(sysdir, MAX_PATH)) + { + strcat(sysdir, "\\CT4MGM.SF2"); + 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 diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index dd3cc95d8f..39f42cc35a 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -10,8 +10,6 @@ static DWORD nummididevices; static bool nummididevicesset; -CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); - #ifdef _WIN32 UINT mididevice; From 2136ef2abafff34142a52f2a13a5c04ad95fdf79 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 15:51:07 +0000 Subject: [PATCH 15/84] - Also check for CT2MGM.SF2 if CT4MGM.SF2 is not found. SVN r2556 (trunk) --- src/sound/music_fluidsynth_mididevice.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index fba167b710..d151cd86f0 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -153,16 +153,22 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice() #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")]; - if (0 != GetSystemDirectoryA(sysdir, MAX_PATH)) + 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; + Printf("Failed to load any MIDI patches.\n"); + delete_fluid_synth(FluidSynth); + FluidSynth = NULL; #ifdef _WIN32 + } } } #endif From f389cd8b0fb60404c7daded316175ae49cedead1 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 16:49:43 +0000 Subject: [PATCH 16/84] - Add on-demand loading for fluidsynth.dll. This also lets you build with FluidSynth support without actually having the FluidSynth development files installed. SVN r2557 (trunk) --- src/sound/i_musicinterns.h | 38 ++++++ src/sound/music_fluidsynth_mididevice.cpp | 145 ++++++++++++++++++++-- zdoom.vcproj | 12 +- 3 files changed, 184 insertions(+), 11 deletions(-) diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 4deec4f558..041e3db00e 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -261,7 +261,12 @@ protected: // 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 MIDIDevice { @@ -313,6 +318,39 @@ protected: MIDIHDR *Events; bool Started; DWORD Position; + +#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); + +#ifdef _WIN32 + HMODULE FluidSynthDLL; +#endif + bool LoadFluidSynth(); + void UnloadFluidSynth(); +#endif }; #endif diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index d151cd86f0..6cb576b187 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -45,6 +45,20 @@ // MACROS ------------------------------------------------------------------ +#ifdef DYN_FLUIDSYNTH + +#ifdef _WIN32 +#ifndef _M_X64 +#define FLUIDSYNTHLIB "fluidsynth.dll" +#else +#define FLUIDSYNTHLIB "fluidsynth64.dll" +#endif +#else +#error "TODO: Write a dlopen() version of this code." +#endif + +#endif + // TYPES ------------------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -93,16 +107,22 @@ CUSTOM_CVAR(Int, fluid_voices, 128, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) currSong->FluidSettingInt("synth.polyphony", self); } -CUSTOM_CVAR(Int, fluid_interp, FLUID_INTERP_LINEAR, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { // Values are: 0 = FLUID_INTERP_NONE // 1 = FLUID_INTERP_LINEAR - // 2 = FLUID_INTERP_4THORDER (the FluidSynth default) - // 3 = FLUID_INTERP_7THORDER - if (self < FLUID_INTERP_NONE) - self = FLUID_INTERP_NONE; - else if (self > FLUID_INTERP_HIGHEST) - self = FLUID_INTERP_HIGHEST; + // 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); } @@ -123,6 +143,13 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice() Events = NULL; Started = false; FluidSynth = NULL; + FluidSettings = NULL; +#ifdef DYN_FLUIDSYNTH + if (!LoadFluidSynth()) + { + return; + } +#endif FluidSettings = new_fluid_settings(); if (FluidSettings == NULL) { @@ -195,6 +222,9 @@ FluidSynthMIDIDevice::~FluidSynthMIDIDevice() { delete_fluid_settings(FluidSettings); } +#ifdef DYN_FLUIDSYNTH + UnloadFluidSynth(); +#endif } //========================================================================== @@ -814,5 +844,106 @@ FString FluidSynthMIDIDevice::GetStats() 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" } + }; + int fail = 0; + +#ifdef _WIN32 + FluidSynthDLL = LoadLibrary(FLUIDSYNTHLIB); +#endif + if (FluidSynthDLL == NULL) + { + Printf(TEXTCOLOR_RED"Could not load " FLUIDSYNTHLIB "\n"); + return false; + } + + for (int i = 0; i < countof(imports); ++i) + { +#ifdef _WIN32 + FARPROC proc = GetProcAddress(FluidSynthDLL, imports[i].FuncName); + if (proc == NULL) + { + Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, FLUIDSYNTHLIB); + fail++; + } + *imports[i].FuncPointer = proc; +#endif + } + if (fail == 0) + { + return true; + } + else + { +#ifdef _WIN32 + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; +#endif + return false; + } + +} + +//========================================================================== +// +// FluidSynthMIDIDevice :: UnloadFluidSynth +// +//========================================================================== + +void FluidSynthMIDIDevice::UnloadFluidSynth() +{ +#ifdef _WIN32 + if (FluidSynthDLL != NULL) + { + FreeLibrary(FluidSynthDLL); + FluidSynthDLL = NULL; + } +#endif +} + +#endif + #endif diff --git a/zdoom.vcproj b/zdoom.vcproj index 913cff4a99..3edc5d20e9 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -56,8 +56,8 @@ FavorSizeOrSpeed="1" OmitFramePointers="true" WholeProgramOptimization="false" - AdditionalIncludeDirectories="src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;"jpeg-6b";game-music-emu\gme;gdtoa;bzip2;lzma\C" - PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY,BACKPATCH" + AdditionalIncludeDirectories="src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;"jpeg-6b";"game-music-emu\gme";gdtoa;bzip2;lzma\C" + PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,HAVE_STRUPR,HAVE_FILELENGTH;NO_VA_COPY,BACKPATCH,HAVE_FLUIDSYNTH,DYN_FLUIDSYNTH" StringPooling="true" ExceptionHandling="1" RuntimeLibrary="0" @@ -286,8 +286,8 @@ + + From a68e9c8fa61a3a32e986d4c8d5e574d139df29c9 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 20:46:19 +0000 Subject: [PATCH 17/84] - Added a dlopen() version of on-demand FluidSynth loading and exposed it to CMake through the DYN_FLUIDSYNTH option. SVN r2558 (trunk) --- src/CMakeLists.txt | 10 +++++++-- src/sound/i_musicinterns.h | 2 ++ src/sound/music_fluidsynth_mididevice.cpp | 26 ++++++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8ebfa9f2e..281be10b92 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" ) @@ -490,8 +492,10 @@ set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_L 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 @@ -587,9 +591,11 @@ else( SSE_MATTERS ) set( X86_SOURCES ) endif( SSE_MATTERS ) -if( FLUIDSYNTH_FOUND ) +if( DYN_FLUIDSYNTH ) + add_definitions( -DHAVE_FLUIDSYNTH -DDYN_FLUIDSYNTH ) +elseif( FLUIDSYNTH_FOUND ) add_definitions( -DHAVE_FLUIDSYNTH ) -endif( FLUIDSYNTH_FOUND ) +endif( DYN_FLUIDSYNTH ) add_executable( zdoom WIN32 autostart.cpp diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 041e3db00e..18b6937dfc 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -347,6 +347,8 @@ protected: #ifdef _WIN32 HMODULE FluidSynthDLL; +#else + void *FluidSynthSO; #endif bool LoadFluidSynth(); void UnloadFluidSynth(); diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 6cb576b187..fb573f2fb9 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -54,7 +54,9 @@ #define FLUIDSYNTHLIB "fluidsynth64.dll" #endif #else -#error "TODO: Write a dlopen() version of this code." +#include + +#define FLUIDSYNTHLIB "libfluidsynth.so.1" #endif #endif @@ -892,24 +894,33 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() #ifdef _WIN32 FluidSynthDLL = LoadLibrary(FLUIDSYNTHLIB); -#endif 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 (int 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 = proc; -#endif } if (fail == 0) { @@ -920,6 +931,9 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() #ifdef _WIN32 FreeLibrary(FluidSynthDLL); FluidSynthDLL = NULL; +#else + dlclose(FluidSynthSO); + FluidSynthSO = NULL; #endif return false; } @@ -940,6 +954,12 @@ void FluidSynthMIDIDevice::UnloadFluidSynth() FreeLibrary(FluidSynthDLL); FluidSynthDLL = NULL; } +#else + if (FluidSynthSO != NULL) + { + dlclose(FluidSynthSO); + FluidSynthSO = NULL; + } #endif } From eafb0ab7cfd835d9c156848dc4dad13fd80ffddc Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 19 Aug 2010 23:11:16 +0000 Subject: [PATCH 18/84] - Expose more FluidSynth settings through these cvars: * fluid_samplerate * fluid_threads * fluid_reverb_roomsize * fluid_reverb_damping * fluid_reverb_width * fluid_reverb_level * fluid_chorus_voices * fluid_chorus_level * fluid_chorus_speed * fluid_chorus_depth * fluid_chorus_type SVN r2559 (trunk) --- src/sound/i_musicinterns.h | 3 + src/sound/music_fluidsynth_mididevice.cpp | 172 +++++++++++++++++++--- 2 files changed, 156 insertions(+), 19 deletions(-) diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 18b6937dfc..2f7a83b221 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -318,6 +318,7 @@ protected: MIDIHDR *Events; bool Started; DWORD Position; + int SampleRate; #ifdef DYN_FLUIDSYNTH enum { FLUID_FAILED = 1, FLUID_OK = 0 }; @@ -344,6 +345,8 @@ protected: 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); #ifdef _WIN32 HMODULE FluidSynthDLL; diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index fb573f2fb9..32cd0da468 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -59,6 +59,20 @@ #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 ------------------------------------------------------------------- @@ -129,6 +143,107 @@ CUSTOM_CVAR(Int, fluid_interp, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) 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 -------------------------------------------------------------------- //========================================================================== @@ -158,20 +273,28 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice() 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; } - if (FLUID_FAILED == fluid_synth_set_interp_method(FluidSynth, -1, fluid_interp)) - { - Printf("Failed to set interpolation method %d.\n", *fluid_interp); - } + 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 @@ -243,8 +366,8 @@ int FluidSynthMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWO { return 2; } - Stream = GSnd->CreateStream(FillStream, int(44100 / 4) * 4, - SoundStream::Float, 44100, this); + Stream = GSnd->CreateStream(FillStream, (SampleRate / 4) * 4, + SoundStream::Float, SampleRate, this); if (Stream == NULL) { return 2; @@ -334,7 +457,7 @@ int FluidSynthMIDIDevice::SetTimeDiv(int timediv) void FluidSynthMIDIDevice::CalcTickRate() { - SamplesPerTick = 44100 / (1000000.0 / Tempo) / Division; + SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division; } //========================================================================== @@ -737,29 +860,38 @@ int FluidSynthMIDIDevice::LoadPatchSets(const char *patches) void FluidSynthMIDIDevice::FluidSettingInt(const char *setting, int value) { + if (FluidSynth == NULL || FluidSettings == NULL) + { + return; + } + if (strcmp(setting, "synth.interpolation") == 0) { - if (FluidSynth != NULL) + if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value)) { - if (FLUID_OK != fluid_synth_set_interp_method(FluidSynth, -1, value)) - { - Printf("Setting interpolation method %d failed.\n", value); - } + Printf("Setting interpolation method %d failed.\n", value); } } - else if (FluidSynth != NULL && strcmp(setting, "synth.polyphony") == 0) + 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 (FluidSettings != NULL) + else if (strcmp(setting, "z.reverb-changed") == 0) { - if (!fluid_settings_setint(FluidSettings, setting, value)) - { - Printf("Faild to set %s to %d.\n", setting, value); - } + 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); } } @@ -888,7 +1020,9 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() { (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_sfload, "fluid_synth_sfload" }, + { (void **)&fluid_synth_set_reverb, "fluid_synth_set_reverb" }, + { (void **)&fluid_synth_set_chorus, "fluid_synth_set_chorus" }, }; int fail = 0; From 6f82db47b857218d147dd8fbddfc42ce371f6461 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 20 Aug 2010 04:21:53 +0000 Subject: [PATCH 19/84] - Merge the shared code for the softsynths into a common base class. SVN r2560 (trunk) --- src/CMakeLists.txt | 1 + src/oplsynth/music_opl_mididevice.cpp | 323 ++------------ src/sound/i_music.cpp | 2 +- src/sound/i_musicinterns.h | 135 +++--- src/sound/music_fluidsynth_mididevice.cpp | 421 +----------------- src/sound/music_softsynth_mididevice.cpp | 498 ++++++++++++++++++++++ src/sound/music_timidity_mididevice.cpp | 433 +------------------ zdoom.vcproj | 4 + 8 files changed, 630 insertions(+), 1187 deletions(-) create mode 100644 src/sound/music_softsynth_mididevice.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 281be10b92..9fb4cab099 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -818,6 +818,7 @@ add_executable( zdoom WIN32 sound/music_mus_opl.cpp sound/music_stream.cpp sound/music_fluidsynth_mididevice.cpp + sound/music_softsynth_mididevice.cpp sound/music_timidity_mididevice.cpp sound/music_win_mididevice.cpp textures/automaptexture.cpp diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp index 9413ae4bbf..ade5b5c122 100644 --- a/src/oplsynth/music_opl_mididevice.cpp +++ b/src/oplsynth/music_opl_mididevice.cpp @@ -74,25 +74,9 @@ OPLMIDIDevice::OPLMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - FWadLump data = Wads.OpenLumpName("GENMIDI"); OPLloadBank(data); -} - -//========================================================================== -// -// OPLMIDIDevice Destructor -// -//========================================================================== - -OPLMIDIDevice::~OPLMIDIDevice() -{ - Close(); + SampleRate = (int)OPL_SAMPLE_RATE; } //========================================================================== @@ -109,25 +93,14 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo { return 1; } - - Stream = GSnd->CreateStream(FillStream, int(OPL_SAMPLE_RATE / 14) * 4, - SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this); - if (Stream == NULL) + int ret = OpenStream(14, SoundStream::Mono, callback, userdata); + if (ret == 0) { - return 2; + OPLstopMusic(); + OPLplayMusic(100); + DEBUGOUT("========= New song started ==========\n", 0, 0, 0); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - - OPLstopMusic(); - OPLplayMusic(100); - DEBUGOUT("========= New song started ==========\n", 0, 0, 0); - - return 0; + return ret; } //========================================================================== @@ -138,24 +111,8 @@ int OPLMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), vo void OPLMIDIDevice::Close() { - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } + SoftSynthMIDIDevice::Close(); io->OPLdeinit(); - Started = false; -} - -//========================================================================== -// -// OPLMIDIDevice :: IsOpen -// -//========================================================================== - -bool OPLMIDIDevice::IsOpen() const -{ - return Stream != NULL; } //========================================================================== @@ -169,34 +126,6 @@ int OPLMIDIDevice::GetTechnology() const return MOD_FMSYNTH; } -//========================================================================== -// -// OPLMIDIDevice :: SetTempo -// -//========================================================================== - -int OPLMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - DEBUGOUT("Tempo changed to %.0f, %.2f samples/tick\n", Tempo, SamplesPerTick, 0); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int OPLMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - DEBUGOUT("Division changed to %.0f, %.2f samples/tick\n", Division, SamplesPerTick, 0); - return 0; -} - //========================================================================== // // OPLMIDIDevice :: CalcTickRate @@ -208,219 +137,22 @@ int OPLMIDIDevice::SetTimeDiv(int timediv) void OPLMIDIDevice::CalcTickRate() { - SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division; - io->SetClockRate(SamplesPerTick); -} - -//========================================================================== -// -// OPLMIDIDevice :: Resume -// -//========================================================================== - -int OPLMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: Stop -// -//========================================================================== - -void OPLMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int OPLMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - ChipAccess.Enter(); - StreamOut(header); - ChipAccess.Leave(); - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int OPLMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: PrepareHeader -// -//========================================================================== - -int OPLMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int OPLMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// OPLMIDIDevice :: FakeVolume -// -// Since the OPL output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool OPLMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool OPLMIDIDevice::NeedThreadedCallback() -{ - return false; -} - -//========================================================================== -// -// OPLMIDIDevice :: Pause -// -//========================================================================== - -bool OPLMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + SoftSynthMIDIDevice::CalcTickRate(); + io->SetClockRate(OPLmusicBlock::SamplesPerTick = SoftSynthMIDIDevice::SamplesPerTick); } //========================================================================== // // OPLMIDIDevice :: PlayTick // -// event[0] = delta time -// event[1] = unused -// event[2] = event +// We derive from two base classes that both define PlayTick(), so we need +// to be unambiguous about which one to use. // //========================================================================== int OPLMIDIDevice::PlayTick() { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { // Should I handle master volume changes? - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - HandleEvent(status, parm1, parm2); - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + return SoftSynthMIDIDevice::PlayTick(); } //========================================================================== @@ -508,14 +240,35 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2) //========================================================================== // -// OPLMIDIDevice :: FillStream static +// OPLMIDIDevice :: HandleLongEvent // //========================================================================== -bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void OPLMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - OPLMIDIDevice *device = (OPLMIDIDevice *)userdata; - return device->ServiceStream(buff, len); +} + +//========================================================================== +// +// OPLMIDIDevice :: ComputeOutput +// +// We override ServiceStream, so this function is never actually called. +// +//========================================================================== + +void OPLMIDIDevice::ComputeOutput(float *buffer, int len) +{ +} + +//========================================================================== +// +// OPLMIDIDevice :: ServiceStream +// +//========================================================================== + +bool OPLMIDIDevice::ServiceStream(void *buff, int numbytes) +{ + return OPLmusicBlock::ServiceStream(buff, numbytes); } //========================================================================== diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 8594223871..60a87ea67d 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -810,7 +810,7 @@ CCMD (writeopl) } else { - Printf ("Usage: writeopl "); + Printf ("Usage: writeopl \n"); } } diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 2f7a83b221..334002963f 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -139,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; @@ -161,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 ------------------------ @@ -196,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 ------------------ @@ -268,57 +265,26 @@ struct fluid_settings_t; struct fluid_synth_t; #endif -class FluidSynthMIDIDevice : public MIDIDevice +class FluidSynthMIDIDevice : public SoftSynthMIDIDevice { public: FluidSynthMIDIDevice(); ~FluidSynthMIDIDevice(); 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); 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: - static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata); - bool ServiceStream(void *buff, int numbytes); 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); - void (*Callback)(unsigned int, void *, DWORD, DWORD); - void *CallbackData; - - void CalcTickRate(); - int PlayTick(); - - FCriticalSection CritSec; - SoundStream *Stream; fluid_settings_t *FluidSettings; fluid_synth_t *FluidSynth; - double Tempo; - double Division; - double SamplesPerTick; - double NextTickIn; - MIDIHDR *Events; - bool Started; - DWORD Position; - int SampleRate; #ifdef DYN_FLUIDSYNTH enum { FLUID_FAILED = 1, FLUID_OK = 0 }; @@ -347,6 +313,7 @@ protected: 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; diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 32cd0da468..1e88af055a 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -59,18 +59,18 @@ #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_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_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 @@ -254,11 +254,6 @@ CUSTOM_CVAR(Int, fluid_chorus_type, FLUID_CHORUS_DEFAULT_TYPE, CVAR_ARCHIVE|CVAR FluidSynthMIDIDevice::FluidSynthMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; FluidSynth = NULL; FluidSettings = NULL; #ifdef DYN_FLUIDSYNTH @@ -366,266 +361,12 @@ int FluidSynthMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWO { return 2; } - Stream = GSnd->CreateStream(FillStream, (SampleRate / 4) * 4, - SoundStream::Float, SampleRate, this); - if (Stream == NULL) + int ret = OpenStream(4, 0, callback, userdata); + if (ret == 0) { - return 2; + fluid_synth_system_reset(FluidSynth); } - - fluid_synth_system_reset(FluidSynth); - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: Close -// -//========================================================================== - -void FluidSynthMIDIDevice::Close() -{ - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } - Started = false; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: IsOpen -// -//========================================================================== - -bool FluidSynthMIDIDevice::IsOpen() const -{ - return Stream != NULL; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: GetTechnology -// -//========================================================================== - -int FluidSynthMIDIDevice::GetTechnology() const -{ - return MOD_SWSYNTH; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: SetTempo -// -//========================================================================== - -int FluidSynthMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int FluidSynthMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: CalcTickRate -// -// Tempo is the number of microseconds per quarter note. -// Division is the number of ticks per quarter note. -// -//========================================================================== - -void FluidSynthMIDIDevice::CalcTickRate() -{ - SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: Resume -// -//========================================================================== - -int FluidSynthMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: Stop -// -//========================================================================== - -void FluidSynthMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int FluidSynthMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - CritSec.Enter(); - StreamOut(header); - CritSec.Leave(); - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int FluidSynthMIDIDevice::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; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: PrepareHeader -// -//========================================================================== - -int FluidSynthMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int FluidSynthMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: FakeVolume -// -// Since the FluidSynth output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool FluidSynthMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: NeedThreadedCallabck -// -// FluidSynth can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool FluidSynthMIDIDevice::NeedThreadedCallback() -{ - return false; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: Pause -// -//========================================================================== - -bool FluidSynthMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; -} - -//========================================================================== -// -// FluidSynthMIDIDevice :: PrecacheInstruments -// -// Each entry is packed as follows: -// Bits 0- 6: Instrument number -// Bits 7-13: Bank number -// Bit 14: Select drum set if 1, tone bank if 0 -// -//========================================================================== - -void FluidSynthMIDIDevice::PrecacheInstruments(const WORD *instruments, int count) -{ -#if 0 - for (int i = 0; i < count; ++i) - { - Renderer->MarkInstrument((instruments[i] >> 7) & 127, instruments[i] >> 14, instruments[i] & 127); - } - Renderer->load_missing_instruments(); -#endif + return ret; } //========================================================================== @@ -674,136 +415,31 @@ void FluidSynthMIDIDevice::HandleEvent(int status, int parm1, int parm2) //========================================================================== // -// FluidSynthMIDIDevice :: PlayTick +// FluidSynthMIDIDevice :: HandleLongEvent // -// event[0] = delta time -// event[1] = unused -// event[2] = event +// Handle SysEx messages. // //========================================================================== -int FluidSynthMIDIDevice::PlayTick() +void FluidSynthMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) + if (len > 1 && (data[0] == 0xF0 || data[0] == 0xF7)) { - 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) - { -#if 0 - Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); -#endif - } - 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); + fluid_synth_sysex(FluidSynth, (const char *)data + 1, len - 1, NULL, NULL, NULL, 0); } - return delay; } //========================================================================== // -// FluidSynthtMIDIDevice :: ServiceStream +// FluidSynthMIDIDevice :: ComputeOutput // //========================================================================== -bool FluidSynthMIDIDevice::ServiceStream (void *buff, int numbytes) +void FluidSynthMIDIDevice::ComputeOutput(float *buffer, 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) - { - fluid_synth_write_float(FluidSynth, samplesleft, - samples1, 0, 2, - samples1, 1, 2); - 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) - { - fluid_synth_write_float(FluidSynth, numsamples, - samples1, 0, 2, - samples1, 1, 2); - } - res = false; - break; - } - else - { - NextTickIn += SamplesPerTick * next; - assert(NextTickIn >= 0); - } - } - } - if (Events == NULL) - { - res = false; - } - CritSec.Leave(); - return res; + fluid_synth_write_float(FluidSynth, len, + buffer, 0, 2, + buffer, 1, 2); } //========================================================================== @@ -933,18 +569,6 @@ void FluidSynthMIDIDevice::FluidSettingStr(const char *setting, const char *valu } } -//========================================================================== -// -// FluidSynthMIDIDevice :: FillStream static -// -//========================================================================== - -bool FluidSynthMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) -{ - FluidSynthMIDIDevice *device = (FluidSynthMIDIDevice *)userdata; - return device->ServiceStream(buff, len); -} - //========================================================================== // // FluidSynthMIDIDevice :: GetStats @@ -1023,6 +647,7 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() { (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; diff --git a/src/sound/music_softsynth_mididevice.cpp b/src/sound/music_softsynth_mididevice.cpp new file mode 100644 index 0000000000..9d2e692980 --- /dev/null +++ b/src/sound/music_softsynth_mididevice.cpp @@ -0,0 +1,498 @@ +/* +** music_softsynth_mididevice.cpp +** Common base clase for software synthesis MIDI devices. +** +**--------------------------------------------------------------------------- +** Copyright 2008-2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include "i_musicinterns.h" +#include "templates.h" +#include "doomdef.h" +#include "m_swap.h" +#include "w_wad.h" +#include "v_text.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +CVAR(Bool, synth_watch, false, 0) + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// SoftSynthMIDIDevice Constructor +// +//========================================================================== + +SoftSynthMIDIDevice::SoftSynthMIDIDevice() +{ + Stream = NULL; + Tempo = 0; + Division = 0; + Events = NULL; + Started = false; + SampleRate = GSnd != NULL ? (int)GSnd->GetOutputRate() : 44100; +} + +//========================================================================== +// +// SoftSynthMIDIDevice Destructor +// +//========================================================================== + +SoftSynthMIDIDevice::~SoftSynthMIDIDevice() +{ + Close(); +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: OpenStream +// +//========================================================================== + +int SoftSynthMIDIDevice::OpenStream(int chunks, int flags, void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) +{ + Stream = GSnd->CreateStream(FillStream, (SampleRate / chunks) * 4, SoundStream::Float | flags, SampleRate, this); + if (Stream == NULL) + { + return 2; + } + + Callback = callback; + CallbackData = userdata; + Tempo = 500000; + Division = 100; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Close +// +//========================================================================== + +void SoftSynthMIDIDevice::Close() +{ + if (Stream != NULL) + { + delete Stream; + Stream = NULL; + } + Started = false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: IsOpen +// +//========================================================================== + +bool SoftSynthMIDIDevice::IsOpen() const +{ + return Stream != NULL; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: GetTechnology +// +//========================================================================== + +int SoftSynthMIDIDevice::GetTechnology() const +{ + return MOD_SWSYNTH; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTempo +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTempo(int tempo) +{ + Tempo = tempo; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: SetTimeDiv +// +//========================================================================== + +int SoftSynthMIDIDevice::SetTimeDiv(int timediv) +{ + Division = timediv; + CalcTickRate(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: CalcTickRate +// +// Tempo is the number of microseconds per quarter note. +// Division is the number of ticks per quarter note. +// +//========================================================================== + +void SoftSynthMIDIDevice::CalcTickRate() +{ + SamplesPerTick = SampleRate / (1000000.0 / Tempo) / Division; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Resume +// +//========================================================================== + +int SoftSynthMIDIDevice::Resume() +{ + if (!Started) + { + if (Stream->Play(true, 1)) + { + Started = true; + return 0; + } + return 1; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Stop +// +//========================================================================== + +void SoftSynthMIDIDevice::Stop() +{ + if (Started) + { + Stream->Stop(); + Started = false; + } +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOutSync +// +// This version is called from the main game thread and needs to +// synchronize with the player thread. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOutSync(MIDIHDR *header) +{ + CritSec.Enter(); + StreamOut(header); + CritSec.Leave(); + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: StreamOut +// +// This version is called from the player thread so does not need to +// arbitrate for access to the Events pointer. +// +//========================================================================== + +int SoftSynthMIDIDevice::StreamOut(MIDIHDR *header) +{ + header->lpNext = NULL; + if (Events == NULL) + { + Events = header; + NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; + Position = 0; + } + else + { + MIDIHDR **p; + + for (p = &Events; *p != NULL; p = &(*p)->lpNext) + { } + *p = header; + } + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PrepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::PrepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: UnprepareHeader +// +//========================================================================== + +int SoftSynthMIDIDevice::UnprepareHeader(MIDIHDR *header) +{ + return 0; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FakeVolume +// +// Since the softsynth output is rendered as a normal stream, its volume is +// controlled through the GSnd interface, not here. +// +//========================================================================== + +bool SoftSynthMIDIDevice::FakeVolume() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: NeedThreadedCallabck +// +// We can service the callback directly rather than using a separate +// thread. +// +//========================================================================== + +bool SoftSynthMIDIDevice::NeedThreadedCallback() +{ + return false; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: Pause +// +//========================================================================== + +bool SoftSynthMIDIDevice::Pause(bool paused) +{ + if (Stream != NULL) + { + return Stream->SetPaused(paused); + } + return true; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: PlayTick +// +// event[0] = delta time +// event[1] = unused +// event[2] = event +// +//========================================================================== + +int SoftSynthMIDIDevice::PlayTick() +{ + DWORD delay = 0; + + while (delay == 0 && Events != NULL) + { + DWORD *event = (DWORD *)(Events->lpData + Position); + if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) + { + SetTempo(MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) + { + HandleLongEvent((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); + } + else if (MEVT_EVENTTYPE(event[2]) == 0) + { // Short MIDI event + int status = event[2] & 0xff; + int parm1 = (event[2] >> 8) & 0x7f; + int parm2 = (event[2] >> 16) & 0x7f; + HandleEvent(status, parm1, parm2); + + if (synth_watch) + { + static const char *const commands[8] = + { + "Note off", + "Note on", + "Poly press", + "Ctrl change", + "Prgm change", + "Chan press", + "Pitch bend", + "SysEx" + }; + char buffer[128]; + mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); +#ifdef _WIN32 + OutputDebugString(buffer); +#else + fputs(buffer, stderr); +#endif + } + } + + // Advance to next event. + if (event[2] < 0x80000000) + { // Short message + Position += 12; + } + else + { // Long message + Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); + } + + // Did we use up this buffer? + if (Position >= Events->dwBytesRecorded) + { + Events = Events->lpNext; + Position = 0; + + if (Callback != NULL) + { + Callback(MOM_DONE, CallbackData, 0, 0); + } + } + + if (Events == NULL) + { // No more events. Just return something to keep the song playing + // while we wait for more to be submitted. + return int(Division); + } + + delay = *(DWORD *)(Events->lpData + Position); + } + return delay; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: ServiceStream +// +//========================================================================== + +bool SoftSynthMIDIDevice::ServiceStream (void *buff, int numbytes) +{ + float *samples = (float *)buff; + float *samples1; + int numsamples = numbytes / sizeof(float) / 2; + bool prev_ended = false; + bool res = true; + + samples1 = samples; + memset(buff, 0, numbytes); + + CritSec.Enter(); + while (Events != NULL && numsamples > 0) + { + double ticky = NextTickIn; + int tick_in = int(NextTickIn); + int samplesleft = MIN(numsamples, tick_in); + + if (samplesleft > 0) + { + ComputeOutput(samples1, samplesleft); + assert(NextTickIn == ticky); + NextTickIn -= samplesleft; + assert(NextTickIn >= 0); + numsamples -= samplesleft; + samples1 += samplesleft * 2; + } + + if (NextTickIn < 1) + { + int next = PlayTick(); + assert(next >= 0); + if (next == 0) + { // end of song + if (numsamples > 0) + { + ComputeOutput(samples1, numsamples); + } + res = false; + break; + } + else + { + NextTickIn += SamplesPerTick * next; + assert(NextTickIn >= 0); + } + } + } + + if (Events == NULL) + { + res = false; + } + CritSec.Leave(); + return res; +} + +//========================================================================== +// +// SoftSynthMIDIDevice :: FillStream static +// +//========================================================================== + +bool SoftSynthMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +{ + SoftSynthMIDIDevice *device = (SoftSynthMIDIDevice *)userdata; + return device->ServiceStream(buff, len); +} diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp index a13f46e61f..9e5be625b6 100644 --- a/src/sound/music_timidity_mididevice.cpp +++ b/src/sound/music_timidity_mididevice.cpp @@ -78,8 +78,6 @@ struct FmtChunk // PUBLIC DATA DEFINITIONS ------------------------------------------------- -CVAR(Bool, timidity_watch, false, 0) - // CODE -------------------------------------------------------------------- //========================================================================== @@ -90,33 +88,8 @@ CVAR(Bool, timidity_watch, false, 0) TimidityMIDIDevice::TimidityMIDIDevice() { - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; Renderer = NULL; - Renderer = new Timidity::Renderer(GSnd->GetOutputRate()); -} - -//========================================================================== -// -// TimidityMIDIDevice Constructor with rate parameter -// -//========================================================================== - -TimidityMIDIDevice::TimidityMIDIDevice(int rate) -{ - // Need to support multiple instances with different playback rates - // before we can use this parameter. - rate = (int)GSnd->GetOutputRate(); - Stream = NULL; - Tempo = 0; - Division = 0; - Events = NULL; - Started = false; - Renderer = NULL; - Renderer = new Timidity::Renderer((float)rate); + Renderer = new Timidity::Renderer((float)SampleRate); } //========================================================================== @@ -144,261 +117,12 @@ TimidityMIDIDevice::~TimidityMIDIDevice() int TimidityMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata) { - Stream = GSnd->CreateStream(FillStream, int(Renderer->rate / 2) * 4, - SoundStream::Float, int(Renderer->rate), this); - if (Stream == NULL) + int ret = OpenStream(2, 0, callback, userdata); + if (ret == 0) { - return 2; + Renderer->Reset(); } - - Callback = callback; - CallbackData = userdata; - Tempo = 500000; - Division = 100; - CalcTickRate(); - Renderer->Reset(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Close -// -//========================================================================== - -void TimidityMIDIDevice::Close() -{ - if (Stream != NULL) - { - delete Stream; - Stream = NULL; - } - Started = false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: IsOpen -// -//========================================================================== - -bool TimidityMIDIDevice::IsOpen() const -{ - return Stream != NULL; -} - -//========================================================================== -// -// TimidityMIDIDevice :: GetTechnology -// -//========================================================================== - -int TimidityMIDIDevice::GetTechnology() const -{ - return MOD_SWSYNTH; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTempo -// -//========================================================================== - -int TimidityMIDIDevice::SetTempo(int tempo) -{ - Tempo = tempo; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: SetTimeDiv -// -//========================================================================== - -int TimidityMIDIDevice::SetTimeDiv(int timediv) -{ - Division = timediv; - CalcTickRate(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: CalcTickRate -// -// Tempo is the number of microseconds per quarter note. -// Division is the number of ticks per quarter note. -// -//========================================================================== - -void TimidityMIDIDevice::CalcTickRate() -{ - SamplesPerTick = Renderer->rate / (1000000.0 / Tempo) / Division; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Resume -// -//========================================================================== - -int TimidityMIDIDevice::Resume() -{ - if (!Started) - { - if (Stream->Play(true, 1/*timidity_mastervolume*/)) - { - Started = true; - return 0; - } - return 1; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: Stop -// -//========================================================================== - -void TimidityMIDIDevice::Stop() -{ - if (Started) - { - Stream->Stop(); - Started = false; - } -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOutSync -// -// This version is called from the main game thread and needs to -// synchronize with the player thread. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOutSync(MIDIHDR *header) -{ - CritSec.Enter(); - StreamOut(header); - CritSec.Leave(); - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: StreamOut -// -// This version is called from the player thread so does not need to -// arbitrate for access to the Events pointer. -// -//========================================================================== - -int TimidityMIDIDevice::StreamOut(MIDIHDR *header) -{ - header->lpNext = NULL; - if (Events == NULL) - { - Events = header; - NextTickIn = SamplesPerTick * *(DWORD *)header->lpData; - Position = 0; - } - else - { - MIDIHDR **p; - - for (p = &Events; *p != NULL; p = &(*p)->lpNext) - { } - *p = header; - } - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: PrepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::PrepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: UnprepareHeader -// -//========================================================================== - -int TimidityMIDIDevice::UnprepareHeader(MIDIHDR *header) -{ - return 0; -} - -//========================================================================== -// -// TimidityMIDIDevice :: FakeVolume -// -// Since the TiMidity output is rendered as a normal stream, its volume is -// controlled through the GSnd interface, not here. -// -//========================================================================== - -bool TimidityMIDIDevice::FakeVolume() -{ - return false; -} - -//========================================================================== -// -// TimidityMIDIDevice :: NeedThreadedCallabck -// -// OPL can service the callback directly rather than using a separate -// thread. -// -//========================================================================== - -bool TimidityMIDIDevice::NeedThreadedCallback() -{ - return false; -} - - -//========================================================================== -// -// TimidityMIDIDevice :: TimidityVolumeChanged -// -//========================================================================== - -void TimidityMIDIDevice::TimidityVolumeChanged() -{ - /* - if (Stream != NULL) - { - Stream->SetVolume(timidity_mastervolume); - } - */ -} - -//========================================================================== -// -// TimidityMIDIDevice :: Pause -// -//========================================================================== - -bool TimidityMIDIDevice::Pause(bool paused) -{ - if (Stream != NULL) - { - return Stream->SetPaused(paused); - } - return true; + return ret; } //========================================================================== @@ -423,164 +147,35 @@ void TimidityMIDIDevice::PrecacheInstruments(const WORD *instruments, int count) //========================================================================== // -// TimidityMIDIDevice :: PlayTick -// -// event[0] = delta time -// event[1] = unused -// event[2] = event +// TimidityMIDIDevice :: HandleEvent // //========================================================================== -int TimidityMIDIDevice::PlayTick() +void TimidityMIDIDevice::HandleEvent(int status, int parm1, int parm2) { - DWORD delay = 0; - - while (delay == 0 && Events != NULL) - { - DWORD *event = (DWORD *)(Events->lpData + Position); - if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO) - { - SetTempo(MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG) - { - Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2])); - } - else if (MEVT_EVENTTYPE(event[2]) == 0) - { // Short MIDI event - int status = event[2] & 0xff; - int parm1 = (event[2] >> 8) & 0x7f; - int parm2 = (event[2] >> 16) & 0x7f; - Renderer->HandleEvent(status, parm1, parm2); - - if (timidity_watch) - { - static const char *const commands[8] = - { - "Note off", - "Note on", - "Poly press", - "Ctrl change", - "Prgm change", - "Chan press", - "Pitch bend", - "SysEx" - }; -#ifdef _WIN32 - char buffer[128]; - mysnprintf(buffer, countof(buffer), "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); - OutputDebugString(buffer); -#else - //fprintf(stderr, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2); -#endif - } - } - - // Advance to next event. - if (event[2] < 0x80000000) - { // Short message - Position += 12; - } - else - { // Long message - Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3); - } - - // Did we use up this buffer? - if (Position >= Events->dwBytesRecorded) - { - Events = Events->lpNext; - Position = 0; - - if (Callback != NULL) - { - Callback(MOM_DONE, CallbackData, 0, 0); - } - } - - if (Events == NULL) - { // No more events. Just return something to keep the song playing - // while we wait for more to be submitted. - return int(Division); - } - - delay = *(DWORD *)(Events->lpData + Position); - } - return delay; + Renderer->HandleEvent(status, parm1, parm2); } //========================================================================== // -// TimidityMIDIDevice :: ServiceStream +// TimidityMIDIDevice :: HandleLongEvent // //========================================================================== -bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes) +void TimidityMIDIDevice::HandleLongEvent(const BYTE *data, int len) { - float *samples = (float *)buff; - float *samples1; - int numsamples = numbytes / sizeof(float) / 2; - bool prev_ended = false; - bool res = true; - - samples1 = samples; - memset(buff, 0, numbytes); - - CritSec.Enter(); - while (Events != NULL && numsamples > 0) - { - double ticky = NextTickIn; - int tick_in = int(NextTickIn); - int samplesleft = MIN(numsamples, tick_in); - - if (samplesleft > 0) - { - Renderer->ComputeOutput(samples1, samplesleft); - assert(NextTickIn == ticky); - NextTickIn -= samplesleft; - assert(NextTickIn >= 0); - numsamples -= samplesleft; - samples1 += samplesleft * 2; - } - - if (NextTickIn < 1) - { - int next = PlayTick(); - assert(next >= 0); - if (next == 0) - { // end of song - if (numsamples > 0) - { - Renderer->ComputeOutput(samples1, numsamples); - } - res = false; - break; - } - else - { - NextTickIn += SamplesPerTick * next; - assert(NextTickIn >= 0); - } - } - } - if (Events == NULL) - { - res = false; - } - CritSec.Leave(); - return res; + Renderer->HandleLongMessage(data, len); } //========================================================================== // -// TimidityMIDIDevice :: FillStream static +// TimidityMIDIDevice :: ComputeOutput // //========================================================================== -bool TimidityMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata) +void TimidityMIDIDevice::ComputeOutput(float *buffer, int len) { - TimidityMIDIDevice *device = (TimidityMIDIDevice *)userdata; - return device->ServiceStream(buff, len); + Renderer->ComputeOutput(buffer, len); } //========================================================================== diff --git a/zdoom.vcproj b/zdoom.vcproj index 3edc5d20e9..7885a5174b 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -5485,6 +5485,10 @@ RelativePath=".\src\oplsynth\music_opldumper_mididevice.cpp" > + + From b452bec0eef75723406e952ddf304e51e2ea81cc Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 20 Aug 2010 12:20:51 +0000 Subject: [PATCH 20/84] - merge USDF branch into trunk. - add USDF spexs. SVN r2561 (trunk) --- specs/usdf.txt | 158 ++++++ specs/usdf_zdoom.txt | 56 +++ src/CMakeLists.txt | 1 + src/actionspecials.h | 2 +- src/actor.h | 5 +- src/cmdlib.cpp | 17 + src/cmdlib.h | 1 + src/dobjtype.cpp | 2 + src/farchive.h | 4 + src/g_shared/a_action.cpp | 2 +- src/info.h | 1 + src/namedef.h | 20 + src/p_conversation.cpp | 267 +++++++---- src/p_conversation.h | 38 +- src/p_lnspec.cpp | 32 +- src/p_mobj.cpp | 67 +-- src/p_udmf.cpp | 260 +++++----- src/p_udmf.h | 38 ++ src/p_usdf.cpp | 505 ++++++++++++++++++++ src/p_user.cpp | 10 +- src/thingdef/thingdef_properties.cpp | 10 +- wadsrc/static/actors/strife/strifestuff.txt | 1 - zdoom.vcproj | 4 + 23 files changed, 1227 insertions(+), 274 deletions(-) create mode 100644 specs/usdf.txt create mode 100644 specs/usdf_zdoom.txt create mode 100644 src/p_udmf.h create mode 100644 src/p_usdf.cpp diff --git a/specs/usdf.txt b/specs/usdf.txt new file mode 100644 index 0000000000..fc955f02d5 --- /dev/null +++ b/specs/usdf.txt @@ -0,0 +1,158 @@ +=============================================================================== +Universal Strife Dialog Format Specification v2.0 - 08/20/10 + +Written by Braden "Blzut3" Obrzut - admin@maniacsvault.net + +Defined with input from: + +CodeImp +Gez +Graf Zahl +Quasar +et al. + + Copyright (c) 2010 Braden Obrzut. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +======================================= +I. Grammar / Syntax +======================================= + +The grammar and syntax is similar to that of UDMF. A compliant UDMF parser +should be applyable to the USDF. However, it will need to be capable of +handling sub-blocks. Unknown sub-blocks should be skipped. + +======================================= +II. Implementation Semantics +======================================= + +------------------------------------ +II.A : Storage and Retrieval of Data +------------------------------------ + +This is the same as in UDMF. + +----------------------------------- +II.B : Storage Within Archive Files +----------------------------------- + +There are two options for the USDF lump placement. This can either be a part +of the UDMF lump list or standalone. If used stand alone the lump name +DIALOGXY is used corresponding with MAPXY. For UDMF the lump shall be called +"DIALOGUE". + +-------------------------------- +II.C : Implementation Dependence +-------------------------------- + +USDF also implements the namespace statement. This has all the same +requirements as UDMF. + +======================================= +III. Standardized Fields +======================================= + +The following are required for all USDF complient implementations. Like UDMF, +any unknown field/function should be ignored and not treated as an error. + +NOTE: "mobj" refers to Strife's conversationIDs and not doom editor numbers or + Hexen's spawnids. A valid mobj value is any positive integer greater + than or equal to 1. + +--------------------- +III.A : Conversations +--------------------- + +Conversations are groups of pages that can be assigned to a particular object. +Implementors should preserve the IDs to allow for dynamic reassignment through +scripting although this is not a requirement. + +conversation // Starts a dialog. +{ + actor = ; // mobj for this conversation's actor. If previously + // used, this will override the previous conversation. + + page // Starts a new page. Pages are automatically numbered starting at 0. + { + name = ; // Name that goes in the upper left hand corner + panel = ; // Name of lump to render as the background. + voice = ; // Narration sound lump. + dialog = ; // Dialog of the page. + drop = ; // mobj for the object to drop if the actor is + // killed. + link = ; // Page to jump to if all ifitem conditions are + // satisified. + + // jumps to the specified page if the player has the specified amount + // or more of item in their inventory. This can be repeated as many + // times as the author wants, all conditions must be met for the + // jump to occur. + ifitem + { + item = ; // mobj of item to check. + amount = ; // amount required to be in inventory. + } + + // Choices shall be automatically numbered. + choice + { + text = ; // Name of the choice. + + // The amount of an item needed to successfully pick this option. + // This can be repeated, but only the first will be shown (provided + // diaplaycost is true). All costs must be satisfied for success. + cost + { + item = ; // Item that is required for this option. + amount = ; // Minimum amount of the item needed. + } + + displaycost = ; // Weather the cost should be + // displayed with the option. + // If no cost is specified this should + // be ignored. + yesmessage = ; // Text to add to console when choice + // is accepted. + nomessage = ; // Text to add to console when choice + // is denied. + + log = ; // LOG entry to use on success. + giveitem = ; // Gives the specified item upon + // success. + // The following are the same as the special for linedefs in UDMF. + // They are executed on success. + special = ; + arg0 = ; + arg1 = ; + arg2 = ; + arg3 = ; + arg4 = ; + + nextpage = ; // Sets the next page. + closedialog = ; // Should the dialog be closed upon + // selecting this choice? + // Default: false + } + } +} + +------------------------------- +III.B : Including Other Dialogs +------------------------------- + +Unlike the original Strife dialog format. The lump "SCRIPT00" should not be +included automatically. Instead the user must specify this behavior by using +the include function, which takes the name of a lump to include. Include only +needs to be available in the global scope and for compatibility reasons, must +include the result of the script and not act like a preprocessor statement. + +include = ; + +=============================================================================== +EOF +=============================================================================== diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt new file mode 100644 index 0000000000..8fd8fec406 --- /dev/null +++ b/specs/usdf_zdoom.txt @@ -0,0 +1,56 @@ +=============================================================================== +Universal Strife Dialog Format ZDoom extensions v1.0 - 14.08.2010 + + 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. Standardized Fields +======================================= + +ZDoom implements the 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. +This means that ZDoom dialogues are not forward-compatible with the 'Strife' +namespace. Other ports should be aware of this. +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 9fb4cab099..5fadf0b2dd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -703,6 +703,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 diff --git a/src/actionspecials.h b/src/actionspecials.h index 7f855147ef..c735c7a9d6 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 9ae11696b9..84aab5fed9 100644 --- a/src/actor.h +++ b/src/actor.h @@ -892,8 +892,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/cmdlib.cpp b/src/cmdlib.cpp index dd32cdb55d..9c06b6714c 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -90,6 +90,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 diff --git a/src/cmdlib.h b/src/cmdlib.h index fe41204ec8..f9d7fae703 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); diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index cdac259025..0d443bb695 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -317,6 +317,7 @@ PClass *PClass::CreateDerivedClass (FName name, unsigned int size) info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; + info->ConversationID = -1; m_RuntimeActors.Push (type); } return type; @@ -411,6 +412,7 @@ void PClass::InitializeActorInfo () info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; + info->ConversationID = -1; m_RuntimeActors.Push (this); } diff --git a/src/farchive.h b/src/farchive.h index 9af7d25baa..533b3920eb 100644 --- a/src/farchive.h +++ b/src/farchive.h @@ -290,6 +290,10 @@ template<> inline FArchive &operator<< (FArchive &arc, FFont* &font) return SerializeFFontPtr (arc, font); } +struct FStrifeDialogueNode; +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node); + + template inline FArchive &operator<< (FArchive &arc, TArray &self) diff --git a/src/g_shared/a_action.cpp b/src/g_shared/a_action.cpp index 3697bcba86..2243d48a4d 100644 --- a/src/g_shared/a_action.cpp +++ b/src/g_shared/a_action.cpp @@ -68,7 +68,7 @@ void A_Unblock(AActor *self, bool drop) self->flags &= ~MF_SOLID; - // If the self has a conversation that sets an item to drop, drop that. + // If the actor has a conversation that sets an item to drop, drop that. if (self->Conversation != NULL && self->Conversation->DropType != NULL) { P_DropItem (self, self->Conversation->DropType, -1, 256); diff --git a/src/info.h b/src/info.h index 9480f5f59f..d7d939c0b0 100644 --- a/src/info.h +++ b/src/info.h @@ -203,6 +203,7 @@ struct FActorInfo BYTE GameFilter; BYTE SpawnID; SWORD DoomEdNum; + int ConversationID; FStateLabels *StateList; DmgFactors *DamageFactors; PainChanceList *PainChances; diff --git a/src/namedef.h b/src/namedef.h index 94e0c97bd9..b91781b862 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -443,3 +443,23 @@ xx(blockprojectiles) xx(blockuse) xx(Renderstyle) + +// USDF keywords +xx(Amount) +xx(Text) +xx(Displaycost) +xx(Yesmessage) +xx(Nomessage) +xx(Log) +xx(Giveitem) +xx(Nextpage) +xx(Closedialog) +xx(Cost) +xx(Page) +xx(Count) +xx(Name) +xx(Panel) +xx(Dialog) +xx(Ifitem) +xx(Choice) +xx(Link) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 8e32ee8abd..37739a43d2 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -59,6 +59,8 @@ #include "doomstat.h" #include "c_console.h" #include "sbar.h" +#include "farchive.h" +#include "p_lnspec.h" // The conversations as they exist inside a SCRIPTxx lump. struct Response @@ -103,19 +105,20 @@ void GiveSpawner (player_t *player, const PClass *type); TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -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 menu_t ConversationMenu; static TArray ConversationItems; static int ConversationPauseTic; static bool ShowGold; -static void LoadScriptFile (const char *name); -static void LoadScriptFile(FileReader *lump, int numnodes); +static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type); static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeakerType); static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeakerType); static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses); @@ -139,13 +142,42 @@ static FBrokenLines *DialogueLines; // //============================================================================ -static 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 +190,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 +202,13 @@ 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)) + { + LoadScriptFile (scriptname_b, false, 1); + } } } @@ -192,36 +227,13 @@ void P_FreeStrifeConversations () delete node; } - for (int i = 0; i < 344; ++i) - { - if (StrifeTypes[i] != NULL) - { - AActor * ac = GetDefaultByType (StrifeTypes[i]); - if (ac != NULL) ac->Conversation = NULL; - } - } + DialogueRoots.Clear(); + ClassRoots.Clear(); CurNode = NULL; PrevNode = NULL; } -//============================================================================ -// -// ncopystring -// -// If the string has no content, returns NULL. Otherwise, returns a copy. -// -//============================================================================ - -static char *ncopystring (const char *string) -{ - if (string == NULL || string[0] == 0) - { - return NULL; - } - return copystring (string); -} - //============================================================================ // // LoadScriptFile @@ -230,61 +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(0, SEEK_SET); + + // 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,15 +524,18 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) // The message to record in the log for this reply. reply->LogNumber = rsp->Log; + reply->LogString = NULL; // The item to receive when this reply is used. reply->GiveType = 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 @@ -517,7 +568,7 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) { reply->QuickYes = ncopystring (rsp->Yes); } - if (reply->ItemCheck[0] != 0) + if (reply->ItemCheck[0].Item != 0) { reply->QuickNo = ncopystring (rsp->No); } @@ -713,13 +764,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 @@ -1063,9 +1121,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,12 +1190,18 @@ 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; } @@ -1147,7 +1211,11 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply } // Update the quest log, if needed. - if (reply->LogNumber != 0) + if (reply->LogString != NULL) + { + player->SetLogText(reply->LogString); + } + else if (reply->LogNumber != 0) { player->SetLogNumber(reply->LogNumber); } @@ -1162,7 +1230,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]; @@ -1316,3 +1384,26 @@ static void TerminalResponse (const char *str) } } } + + +template<> FArchive &operator<< (FArchive &arc, FStrifeDialogueNode *&node) +{ + DWORD convnum; + if (arc.IsStoring()) + { + arc.WriteCount (node == NULL? ~0u : node->ThisNodeNum); + } + else + { + convnum = arc.ReadCount(); + if (convnum >= StrifeDialogues.Size()) + { + node = NULL; + } + else + { + node = StrifeDialogues[convnum]; + } + } + return arc; +} diff --git a/src/p_conversation.h b/src/p_conversation.h index 13658e0f08..61c0ef8f4b 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,12 +38,14 @@ 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; @@ -50,14 +54,16 @@ struct FStrifeDialogueReply extern TArray StrifeDialogues; -// There were 344 types in Strife, and Strife conversations refer -// to their index in the mobjinfo table. This table indexes all -// the Strife actor types in the order Strife had them and is -// initialized as part of the actor's setup in infodefaults.cpp. -extern 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 +72,8 @@ void P_ResumeConversation (); void P_ConversationCommand (int netcode, int player, BYTE **stream); +class FileReader; +bool P_ParseUSDF(int lumpnum, FileReader *lump, int lumplen); + + #endif diff --git a/src/p_lnspec.cpp b/src/p_lnspec.cpp index 72bea5c499..a53356a8ef 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_mobj.cpp b/src/p_mobj.cpp index f52067ba27..7b7e25888e 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 > 2500) // adjust after merging into trunk! { - 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 (convnum == 0) { 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"); } } @@ -3482,6 +3441,7 @@ bool AActor::UpdateWaterLevel (fixed_t oldz, bool dosplash) return false; // we did the splash ourselves } + //========================================================================== // // P_SpawnMobj @@ -3508,6 +3468,17 @@ AActor *AActor::StaticSpawn (const PClass *type, fixed_t ix, fixed_t iy, fixed_t actor = static_cast(const_cast(type)->CreateNew ()); + // Set default dialogue + actor->ConversationRoot = GetConversation(actor->GetClass()->TypeName); + if (actor->ConversationRoot != -1) + { + actor->Conversation = StrifeDialogues[actor->ConversationRoot]; + } + else + { + actor->Conversation = NULL; + } + actor->x = actor->PrevX = ix; actor->y = actor->PrevY = iy; actor->z = actor->PrevZ = iz; diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index c057c253cd..ef7f991ffc 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]; @@ -1457,6 +1499,10 @@ struct UDMFParser ParseVertex(&vt); ParsedVertices.Push(vt); } + else + { + Skip(); + } } // Create the real vertices diff --git a/src/p_udmf.h b/src/p_udmf.h new file mode 100644 index 0000000000..0fc2f5564d --- /dev/null +++ b/src/p_udmf.h @@ -0,0 +1,38 @@ +#ifndef __P_UDMF_H +#define __P_UDMF_H + +#include "sc_man.h" +#include "m_fixed.h" +#include "tables.h" + +class UDMFParserBase +{ +protected: + FScanner sc; + FName namespc; + int namespace_bits; + FString parsedString; + + void Skip(); + FName ParseKey(bool checkblock = false, bool *isblock = NULL); + int CheckInt(const char *key); + double CheckFloat(const char *key); + fixed_t CheckFixed(const char *key); + angle_t CheckAngle(const char *key); + bool CheckBool(const char *key); + const char *CheckString(const char *key); + + template + void Flag(T &value, int mask, const char *key) + { + if (CheckBool(key)) + value |= mask; + else + value &= ~mask; + } + +}; + +#define BLOCK_ID (ENamedName)-1 + +#endif \ No newline at end of file diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp new file mode 100644 index 0000000000..21bd8eeb3d --- /dev/null +++ b/src/p_usdf.cpp @@ -0,0 +1,505 @@ +// +// 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 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) + { + 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 65909b3d34..cf80d3819f 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() diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index ffe2870357..1e8f51f97f 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -255,13 +255,11 @@ 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; + + bag.Info->ConversationID = convid; + if (convid <= 0) return; // 0 is not usable because the dialogue scripts use it as 'no object'. + SetStrifeType(convid, info->Class); } //========================================================================== diff --git a/wadsrc/static/actors/strife/strifestuff.txt b/wadsrc/static/actors/strife/strifestuff.txt index d8ecd9bbb3..a80685c353 100644 --- a/wadsrc/static/actors/strife/strifestuff.txt +++ b/wadsrc/static/actors/strife/strifestuff.txt @@ -1662,7 +1662,6 @@ ACTOR TargetPractice 208 ACTOR ForceFieldGuard 25 native { Game Strife - ConversationID 0 Health 10 Radius 2 Height 1 diff --git a/zdoom.vcproj b/zdoom.vcproj index 7885a5174b..21247821f1 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -900,6 +900,10 @@ RelativePath=".\src\p_udmf.cpp" > + + From 1460b8feeda6c853f0fc68c165bd71799247f461 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 20 Aug 2010 19:10:02 +0000 Subject: [PATCH 21/84] - fixed: The check for old incompatible savegames with dialogues was wrong. SVN r2562 (trunk) --- src/p_mobj.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 7b7e25888e..4d3af4568b 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -370,7 +370,7 @@ void AActor::Serialize (FArchive &arc) int convnum; convnum = arc.ReadCount(); - if (convnum == 0) + if (GetConversation(GetClass()->TypeName) == -1) { Conversation = NULL; ConversationRoot = -1; From 96a8f1ceee566ca50343c6cade593c1eaae2fa51 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 21 Aug 2010 19:50:07 +0000 Subject: [PATCH 22/84] - fixed: The SaveVersion check for the USDF related changes for the current dialogue did not have the correct version number. - fixed: All blood spawning functions checked the ALLOWPARTICLES flag on the class defaults before actor replacement instead of after. SVN r2563 (trunk) --- src/actor.h | 13 ++++++++++--- src/p_mobj.cpp | 10 +++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/actor.h b/src/actor.h index 84aab5fed9..68eda132b6 100644 --- a/src/actor.h +++ b/src/actor.h @@ -722,19 +722,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->ActorInfo->GetReplacement()->Class; + } + return bloodcls; } // Calculate amount of missile damage diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 4d3af4568b..c6011d4eae 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -361,7 +361,7 @@ void AActor::Serialize (FArchive &arc) arc << foo; } - if (SaveVersion > 2500) // adjust after merging into trunk! + if (SaveVersion > 2560) { arc << ConversationRoot << Conversation; } @@ -4486,7 +4486,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) @@ -4549,7 +4549,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; @@ -4590,7 +4590,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! @@ -4629,7 +4629,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; From 883510efe2eb5dabc2de06338d6e625edf387bcf Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 22 Aug 2010 17:18:46 +0000 Subject: [PATCH 23/84] - added UMDF property to assign conversation dialogues to mapthings. SVN r2567 (trunk) --- specs/udmf_zdoom.txt | 5 +++++ src/doomdata.h | 1 + src/namedef.h | 1 + src/p_mobj.cpp | 11 +++++++++++ src/p_udmf.cpp | 5 +++++ 5 files changed, 23 insertions(+) diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 5ceb573aa6..9f91c748e0 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -180,6 +180,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 +270,9 @@ Added 'playeruseback' line trigger flag. 1.11 07.08.2010 Added 'soundsequnce' sector property. +1.12 22.08.2010 +Added 'conversation' thing property. + =============================================================================== EOF =============================================================================== diff --git a/src/doomdata.h b/src/doomdata.h index 8592462611..9c22b22a60 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -340,6 +340,7 @@ struct FMapThing DWORD flags; int special; int args[5]; + int Conversation; void Serialize (FArchive &); }; diff --git a/src/namedef.h b/src/namedef.h index b91781b862..cb3e78ea22 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) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index c6011d4eae..2b9da7a549 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -4394,6 +4394,17 @@ 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) + { + mobj->ConversationRoot = GetConversation(mthing->Conversation); + if (mobj->ConversationRoot != -1) + { + mobj->Conversation = StrifeDialogues[mobj->ConversationRoot]; + } + } + mobj->BeginPlay (); if (!(mobj->ObjectFlags & OF_EuthanizeMe)) { diff --git a/src/p_udmf.cpp b/src/p_udmf.cpp index ef7f991ffc..e75d324df8 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -493,6 +493,11 @@ public: 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); From ec71635ca13ace99068e0a664af9aa35e1566e9b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 22 Aug 2010 17:31:42 +0000 Subject: [PATCH 24/84] - fixed: The USDF CheckActorType function did not return the class object for ZDoom namespace. - fixed: The binary check for dialogue lumps must only rewind the file by 4 bytes, not completely to the start. SVN r2568 (trunk) --- src/p_conversation.cpp | 2 +- src/p_usdf.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 37739a43d2..96b00b1a5d 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -266,7 +266,7 @@ static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool inc char buffer[4]; lump->Read(buffer, 4); - lump->Seek(0, SEEK_SET); + 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); diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp index 21bd8eeb3d..7bcca22043 100644 --- a/src/p_usdf.cpp +++ b/src/p_usdf.cpp @@ -71,6 +71,7 @@ class USDFParser : public UDMFParserBase sc.ScriptMessage("'%s' is not an actor type", key); return NULL; } + return cls; } return NULL; } From 9073b8cdeaa363db696b000f60ca312456d89bd9 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 22 Aug 2010 17:49:28 +0000 Subject: [PATCH 25/84] - removed FActorInfo::ConversationID which was development garbage of the USDF branch. SVN r2569 (trunk) --- src/dobjtype.cpp | 2 -- src/info.h | 1 - src/thingdef/thingdef_properties.cpp | 1 - 3 files changed, 4 deletions(-) diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index 0d443bb695..cdac259025 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -317,7 +317,6 @@ PClass *PClass::CreateDerivedClass (FName name, unsigned int size) info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; - info->ConversationID = -1; m_RuntimeActors.Push (type); } return type; @@ -412,7 +411,6 @@ void PClass::InitializeActorInfo () info->DamageFactors = NULL; info->PainChances = NULL; info->ColorSets = NULL; - info->ConversationID = -1; m_RuntimeActors.Push (this); } diff --git a/src/info.h b/src/info.h index d7d939c0b0..9480f5f59f 100644 --- a/src/info.h +++ b/src/info.h @@ -203,7 +203,6 @@ struct FActorInfo BYTE GameFilter; BYTE SpawnID; SWORD DoomEdNum; - int ConversationID; FStateLabels *StateList; DmgFactors *DamageFactors; PainChanceList *PainChances; diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 1e8f51f97f..1e47dfd869 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -257,7 +257,6 @@ DEFINE_INFO_PROPERTY(conversationid, IiI, Actor) } - bag.Info->ConversationID = convid; if (convid <= 0) return; // 0 is not usable because the dialogue scripts use it as 'no object'. SetStrifeType(convid, info->Class); } From 3662ed4f271b0b83d5771eb5d328f9bdd8a4fac5 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 23 Aug 2010 06:18:17 +0000 Subject: [PATCH 26/84] - Changed wording of usdf_zdoom.txt because technically this format is not USDF anymore due to the required changes to allow specifying classes by name. SVN r2571 (trunk) --- specs/usdf_zdoom.txt | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt index 8fd8fec406..2173542922 100644 --- a/specs/usdf_zdoom.txt +++ b/specs/usdf_zdoom.txt @@ -1,5 +1,6 @@ =============================================================================== -Universal Strife Dialog Format ZDoom extensions v1.0 - 14.08.2010 +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 @@ -22,14 +23,46 @@ II. Implementation Semantics No changes. ======================================= -III. Standardized Fields +III. Changes to USDF spec ======================================= -ZDoom implements the base specification as described with one important change: +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. -This means that ZDoom dialogues are not forward-compatible with the 'Strife' -namespace. Other ports should be aware of this. +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"; From c058c644a416c121928c7a9d0a98c37e1d986af5 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Tue, 24 Aug 2010 02:35:10 +0000 Subject: [PATCH 27/84] - MinGW did not like this assignment in the FluidSynth loader. SVN r2573 (trunk) --- src/sound/music_fluidsynth_mididevice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sound/music_fluidsynth_mididevice.cpp b/src/sound/music_fluidsynth_mididevice.cpp index 1e88af055a..d50e236b7c 100644 --- a/src/sound/music_fluidsynth_mididevice.cpp +++ b/src/sound/music_fluidsynth_mididevice.cpp @@ -667,7 +667,7 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() } #endif - for (int i = 0; i < countof(imports); ++i) + for (size_t i = 0; i < countof(imports); ++i) { #ifdef _WIN32 FARPROC proc = GetProcAddress(FluidSynthDLL, imports[i].FuncName); @@ -679,7 +679,7 @@ bool FluidSynthMIDIDevice::LoadFluidSynth() Printf(TEXTCOLOR_RED"Failed to find %s in %s\n", imports[i].FuncName, FLUIDSYNTHLIB); fail++; } - *imports[i].FuncPointer = proc; + *imports[i].FuncPointer = (void *)proc; } if (fail == 0) { From f08c66d664fbb0cd72cfab04802ba8a460a3c0f3 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 24 Aug 2010 09:53:10 +0000 Subject: [PATCH 28/84] - externalized the "You seem to have enough" string to language lump. - allow all texts in conversations to reference the string table. - fixed: If no dialogue is found for the current map, SCRIPT00 should still be loaded. SVN r2577 (trunk) --- src/p_conversation.cpp | 39 +++++++++++++++++++++++++++++++++++--- wadsrc/static/language.enu | 1 + 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 96b00b1a5d..0d7d311b4c 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -207,7 +207,10 @@ void P_LoadStrifeConversations (MapData *map, const char *mapname) if (!LoadScriptFile(scriptname_t, false, 2)) { - LoadScriptFile (scriptname_b, false, 1); + if (!LoadScriptFile (scriptname_b, false, 1)) + { + LoadScriptFile ("SCRIPT00", false, 1); + } } } } @@ -813,6 +816,18 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang toSay = "Go away!"; // Ok, it's lame - but it doesn't look like an error to the player. ;) } } + else + { + // handle string table replacement + if (toSay[0] == '$') + { + toSay = GStrings(toSay + 1); + } + } + if (toSay == NULL) + { + toSay = "."; + } DialogueLines = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay); // Fill out the possible choices @@ -830,6 +845,12 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang for (j = 0; reply->ReplyLines[j].Width >= 0; ++j) { item.label = reply->ReplyLines[j].Text.LockBuffer(); + // handle string table replacement + if (item.label[0] == '$') + { + item.label = GStrings(item.label + 1); + } + item.b.position = j == 0 ? i : 0; item.c.extra = reply; ConversationItems.Push (item); @@ -1207,13 +1228,19 @@ static void HandleReply(player_t *player, bool isconsole, int nodenum, int reply } else { - replyText = "You seem to have enough!"; + replyText = "$txt_haveenough"; } // Update the quest log, if needed. if (reply->LogString != NULL) { - player->SetLogText(reply->LogString); + const char *log = reply->LogString; + if (log[0] == '$') + { + log = GStrings(log + 1); + } + + player->SetLogText(log); } else if (reply->LogNumber != 0) { @@ -1366,6 +1393,12 @@ static void TerminalResponse (const char *str) { if (str != NULL) { + // handle string table replacement + if (str[0] == '$') + { + str = GStrings(str + 1); + } + if (StatusBar != NULL) { AddToConsole(-1, str); diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 5f05ba447d..883b0e3255 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1476,6 +1476,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: From 6c57441bcdaa701658900f46b4e0a2b984e28662 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 24 Aug 2010 13:57:17 +0000 Subject: [PATCH 29/84] - fixed: The UDMF check for dialogues without owning class needs to be relaxed. A dialogue with an id and no class is a valid construct. - fixed: Trying to assign a non-existent dialogue to an actor in UDMF partially overwrote the default dialogue. SVN r2579 (trunk) --- src/p_mobj.cpp | 6 ++++-- src/p_usdf.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 2b9da7a549..85ca7662e8 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -4398,9 +4398,11 @@ AActor *P_SpawnMapThing (FMapThing *mthing, int position) // Check if this actor's mapthing has a conversation defined if (mthing->Conversation > 0) { - mobj->ConversationRoot = GetConversation(mthing->Conversation); - if (mobj->ConversationRoot != -1) + // 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]; } } diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp index 7bcca22043..b831b4140f 100644 --- a/src/p_usdf.cpp +++ b/src/p_usdf.cpp @@ -407,7 +407,7 @@ class USDFParser : public UDMFParserBase } } } - if (type == NULL) + if (type == NULL && dlgid == 0) { sc.ScriptMessage("No valid actor type defined in conversation."); return false; From eed581279986f2853f261900031445a5e3137697 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 26 Aug 2010 16:46:01 +0000 Subject: [PATCH 30/84] - added 'fluidsynth' option for $mididevice command. SVN r2598 (trunk) --- src/s_advsound.cpp | 1 + src/s_sound.h | 1 + src/sound/i_music.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/s_advsound.cpp b/src/s_advsound.cpp index e121bc2ee1..46d4bc813e 100644 --- a/src/s_advsound.cpp +++ b/src/s_advsound.cpp @@ -1294,6 +1294,7 @@ static void S_AddSNDINFO (int lump) else if (sc.Compare("standard")) MidiDevices[nm] = MDEV_MMAPI; else if (sc.Compare("opl")) MidiDevices[nm] = MDEV_OPL; else if (sc.Compare("default")) MidiDevices[nm] = MDEV_DEFAULT; + else if (sc.Compare("fluidsynth")) MidiDevices[nm] = MDEV_FLUIDSYNTH; else sc.ScriptError("Unknown MIDI device %s\n", sc.String); } break; diff --git a/src/s_sound.h b/src/s_sound.h index 8d89d1a0de..05237ba271 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -378,6 +378,7 @@ enum EMidiDevice MDEV_OPL = 1, MDEV_FMOD = 2, MDEV_TIMIDITY = 3, + MDEV_FLUIDSYNTH = 4, }; typedef TMap MidiDeviceMap; diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 60a87ea67d..eb68d290e4 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -441,7 +441,7 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int info = new MUSSong2(file, musiccache, len, MIDI_Timidity); } #ifdef HAVE_FLUIDSYNTH - else if (snd_mididevice == -5 && device == MDEV_DEFAULT) + else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) { info = new MUSSong2(file, musiccache, len, MIDI_Fluid); } @@ -528,7 +528,7 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int info = new MIDISong2(file, musiccache, len, MIDI_Timidity); } #ifdef HAVE_FLUIDSYNTH - else if (snd_mididevice == -5 && device == MDEV_DEFAULT) + else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) { info = new MIDISong2(file, musiccache, len, MIDI_Fluid); } From 06a35dea11f9028398b90197907703bf28ea323d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 26 Aug 2010 18:03:15 +0000 Subject: [PATCH 31/84] - added: offset the trail actors spawned by AFastProjectile by missileheight. SVN r2599 (trunk) --- src/g_shared/a_fastprojectile.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index 3bd2903eeb..161323b2b4 100644 --- a/src/g_shared/a_fastprojectile.cpp +++ b/src/g_shared/a_fastprojectile.cpp @@ -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) From ffa58aadbe99984b7b5a5c8fe05645784bbe55ca Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 26 Aug 2010 18:08:09 +0000 Subject: [PATCH 32/84] - added Aroenai's snd_menuvolume submission. SVN r2600 (trunk) --- src/m_menu.cpp | 43 ++++++++++++++-------------- src/m_options.cpp | 72 ++++++++++++++++++++++++----------------------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/m_menu.cpp b/src/m_menu.cpp index 10ba3a3362..b3e28b88e1 100644 --- a/src/m_menu.cpp +++ b/src/m_menu.cpp @@ -205,6 +205,7 @@ EXTERN_CVAR (String, playerclass) EXTERN_CVAR (String, name) EXTERN_CVAR (Int, team) EXTERN_CVAR(Bool, neverswitchonpickup) +EXTERN_CVAR(Float, snd_menuvolume) extern bool sendpause; extern int flagsvar; @@ -663,28 +664,28 @@ CCMD (menu_help) CCMD (quicksave) { // F6 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuickSave(); } CCMD (quickload) { // F9 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuickLoad(); } CCMD (menu_endgame) { // F7 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_EndGame(0); } CCMD (menu_quit) { // F10 //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); M_QuitGame(0); } @@ -1439,7 +1440,7 @@ void M_QuickSaveResponse (int ch) if (ch == 'y') { M_DoSave (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } } @@ -1447,7 +1448,7 @@ void M_QuickSave () { if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -1478,7 +1479,7 @@ void M_QuickLoadResponse (int ch) if (ch == 'y') { M_LoadSelect (quickSaveSlot); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } } @@ -1981,7 +1982,7 @@ void M_EndGame(int choice) choice = 0; if (!usergame) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -2042,7 +2043,7 @@ void M_QuitResponse(int ch) { if (gameinfo.quitSound.IsNotEmpty()) { - S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); I_WaitVBL (105); } } @@ -2879,7 +2880,7 @@ void M_StartMessage (const char *string, void (*routine)(int)) if (messageRoutine != NULL) { S_StopSound (CHAN_VOICE); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", snd_menuvolume, ATTN_NONE); } return; } @@ -2898,7 +2899,7 @@ void M_EndMessage(int key) } SB_state = screen->GetPageCount(); // refresh the status bar BorderNeedRefresh = screen->GetPageCount(); - S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", 1, ATTN_NONE); + S_Sound(CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); } @@ -3101,7 +3102,7 @@ bool M_Responder (event_t *ev) if (currentMenu->menuitems[i].alphaKey == ch) { itemOn = i; - S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); return true; } } @@ -3337,7 +3338,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (itemOn + 1 >= currentMenu->numitems) itemOn = 0; else itemOn++; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; @@ -3347,7 +3348,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (itemOn == 0) itemOn = currentMenu->numitems - 1; else itemOn--; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } while (currentMenu->menuitems[itemOn].status == -1); break; @@ -3355,7 +3356,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); currentMenu->menuitems[itemOn].routine(0); } break; @@ -3364,7 +3365,7 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].routine && currentMenu->menuitems[itemOn].status == 2) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); currentMenu->menuitems[itemOn].routine(1); } break; @@ -3377,12 +3378,12 @@ void M_ButtonHandler(EMenuKey key, bool repeat) if (currentMenu->menuitems[itemOn].status == 2) { currentMenu->menuitems[itemOn].routine(1); // right arrow - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else { currentMenu->menuitems[itemOn].routine(itemOn); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); } } break; @@ -3602,7 +3603,7 @@ void M_StartControlPanel (bool makeSound, bool wantTop) if (makeSound) { - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); } } @@ -3946,12 +3947,12 @@ void M_PopMenuStack (void) } drawSkull = MenuStack[MenuStackDepth].drawSkull; ++MenuStackDepth; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/backup", snd_menuvolume, ATTN_NONE); } else { M_ClearMenus (); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, ATTN_NONE); } } diff --git a/src/m_options.cpp b/src/m_options.cpp index 4f822befef..06c3017bfd 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -100,6 +100,7 @@ EXTERN_CVAR (Int, snd_channels) // defaulted values // CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) // Show messages has default, 0 = off, 1 = on CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -1274,6 +1275,7 @@ static valueenum_t Resamplers[] = static menuitem_t SoundItems[] = { { slider, "Sounds volume", {&snd_sfxvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, + { slider, "Menu volume", {&snd_menuvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, { slider, "Music volume", {&snd_musicvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, { discrete, "MIDI device", {&snd_mididevice}, {0.0}, {0.0}, {0.0}, {NULL} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, @@ -1304,7 +1306,7 @@ static menu_t SoundMenu = SoundItems, }; -#define MIDI_DEVICE_ITEM 2 +#define MIDI_DEVICE_ITEM 3 /*======================================= * @@ -1506,13 +1508,13 @@ void M_SizeDisplay (int diff) CCMD (sizedown) { M_SizeDisplay (-1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } CCMD (sizeup) { M_SizeDisplay (1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } // Draws a string in the console font, scaled to the 8x8 cells @@ -2186,7 +2188,7 @@ void M_OptResponder(event_t *ev) NewBits = BitTranslate[DummyDepthCvar]; setmodeneeded = true; testingmode = I_GetTime(false) + 5 * TICRATE; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); SetModesMenu (NewWidth, NewHeight, NewBits); } else if (ev->data1 >= '0' && ev->data1 <= '9') @@ -2291,7 +2293,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) CurrentMenu->items[CurrentItem].a.selmode = modecol; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2361,7 +2363,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (CurrentMenu->items[CurrentItem].type == screenres) CurrentMenu->items[CurrentItem].a.selmode = modecol; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2383,7 +2385,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { ++CurrentItem; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2406,7 +2408,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { ++CurrentItem; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); } break; @@ -2444,7 +2446,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else item->a.cvar->SetGenericRep (newval, CVAR_Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_sens: @@ -2452,7 +2454,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (value.Float < item->b.min) value.Float = item->b.min; SELECTED_JOYSTICK->SetSensitivity(value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_slider: @@ -2485,12 +2487,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case palettegrid: SelColorIndex = (SelColorIndex - 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; case discretes: @@ -2535,14 +2537,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case ediscrete: value = item->a.cvar->GetGenericRep(CVAR_String); value.String = const_cast(M_FindPrevVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case bitmask: @@ -2561,21 +2563,21 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value.Int = (value.Int & ~bmask) | int(item->e.values[cur].value); item->a.cvar->SetGenericRep (value, CVAR_Int); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case inverter: value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_inverter: assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case screenres: @@ -2599,7 +2601,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) item->a.selmode = col; } } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; default: @@ -2641,7 +2643,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else item->a.cvar->SetGenericRep (newval, CVAR_Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_sens: @@ -2649,7 +2651,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (value.Float > item->c.max) value.Float = item->c.max; SELECTED_JOYSTICK->SetSensitivity(value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_slider: @@ -2682,12 +2684,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) { SELECTED_JOYSTICK->SetAxisDeadZone(item->a.joyselection, value.Float); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case palettegrid: SelColorIndex = (SelColorIndex + 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; case discretes: @@ -2732,14 +2734,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case ediscrete: value = item->a.cvar->GetGenericRep(CVAR_String); value.String = const_cast(M_FindNextVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case bitmask: @@ -2758,21 +2760,21 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value.Int = (value.Int & ~bmask) | int(item->e.values[cur].value); item->a.cvar->SetGenericRep (value, CVAR_Int); } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case inverter: value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case joy_inverter: assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); break; case screenres: @@ -2799,7 +2801,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) item->a.selmode = col; } } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); break; default: @@ -2829,7 +2831,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) setmodeneeded = true; NewBits = BitTranslate[DummyDepthCvar]; } - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); SetModesMenu (NewWidth, NewHeight, NewBits); } else if ((item->type == more || @@ -2841,7 +2843,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) && item->e.mfunc) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); if (item->type == safemore || item->type == rsafemore) { ActivateConfirm (item->label, item->e.mfunc); @@ -2876,7 +2878,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) if (item->e.values == Depths) BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else if (item->type == control) { @@ -2889,7 +2891,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else if (item->type == listelement) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); item->e.lfunc (CurrentItem); } else if (item->type == inverter) @@ -2897,14 +2899,14 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) value = item->a.cvar->GetGenericRep (CVAR_Float); value.Float = -value.Float; item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else if (item->type == joy_inverter) { assert(item->e.joyslidernum == 0); value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } else if (item->type == screenres) { @@ -2912,7 +2914,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) else if (item->type == colorpicker) { CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", 1, ATTN_NONE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); StartColorPickerMenu (item->label, item->a.colorcvar); } else if (item->type == palettegrid) From 9102200771e16e7adc94cc9af973f3b53b49c704 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 26 Aug 2010 20:59:15 +0000 Subject: [PATCH 33/84] - added: Let the kill CCMD also kill replacements of the monster that is specified. - add a GetReplacement method to PClass to clean up some really ugly code - Who wrote the 'kill' CCMD? The way it checked if two classes were identical was horrendously overcomplicated. SVN r2601 (trunk) --- src/actor.h | 2 +- src/d_net.cpp | 29 ++++++++++++++++++++++------- src/dobjtype.cpp | 5 +++++ src/dobjtype.h | 1 + src/g_shared/a_randomspawner.cpp | 2 +- src/m_cheat.cpp | 4 ++-- src/p_acs.cpp | 2 +- src/p_map.cpp | 4 ++-- src/p_mobj.cpp | 6 +++--- src/p_things.cpp | 4 ++-- 10 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/actor.h b/src/actor.h index 68eda132b6..15439e7676 100644 --- a/src/actor.h +++ b/src/actor.h @@ -739,7 +739,7 @@ public: if (bloodcls != NULL) { - bloodcls = bloodcls->ActorInfo->GetReplacement()->Class; + bloodcls = bloodcls->GetReplacement(); } return bloodcls; } diff --git a/src/d_net.cpp b/src/d_net.cpp index 9979199634..be592b238a 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -1917,6 +1917,22 @@ BYTE *FDynamicBuffer::GetData (int *len) } +static int KillAll(const PClass *cls) +{ + AActor *actor; + int killcount = 0; + TThinkerIterator iterator(cls); + while ( (actor = iterator.Next ()) ) + { + if (actor->IsA(cls)) + { + if (!(actor->flags2 & MF2_DORMANT) && (actor->flags3 & MF3_ISMONSTER)) + killcount += actor->Massacre (); + } + } + return killcount; + +} // [RH] Execute a special "ticcmd". The type byte should // have already been read, and the stream is positioned // at the beginning of the command's actual data. @@ -2348,18 +2364,17 @@ 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 (classname != 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); } } diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index cdac259025..f232d81f2b 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 dd35e7e47d..e019299012 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -174,6 +174,7 @@ struct PClass static const PClass *FindClass (ENamedName name) { return FindClass (FName (name)); } static const PClass *FindClass (FName name); const PClass *FindClassTentative (FName name); // not static! + PClass *GetReplacement() const; static TArray m_Types; static TArray m_RuntimeActors; diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp index 67bd4b84a2..4c1b5b964c 100644 --- a/src/g_shared/a_randomspawner.cpp +++ b/src/g_shared/a_randomspawner.cpp @@ -71,7 +71,7 @@ class ARandomSpawner : public AActor cls = PClass::FindClass(di->Name); if (cls != NULL) { - const PClass *rep = cls->ActorInfo->GetReplacement()->Class; + const PClass *rep = cls->GetReplacement(); if (rep != NULL) { cls = rep; diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 0a7928a912..9da1b1ff5f 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -754,8 +754,8 @@ void cht_Give (player_t *player, const char *name, int amount) // Don't give replaced weapons unless the replacement was done by Dehacked. if (type != RUNTIME_CLASS(AWeapon) && type->IsDescendantOf (RUNTIME_CLASS(AWeapon)) && - (type->ActorInfo->GetReplacement() == type->ActorInfo || - type->ActorInfo->GetReplacement()->Class->IsDescendantOf(RUNTIME_CLASS(ADehackedPickup)))) + (type->GetReplacement() == type || + type->GetReplacement()->IsDescendantOf(RUNTIME_CLASS(ADehackedPickup)))) { // Give the weapon only if it belongs to the current game or diff --git a/src/p_acs.cpp b/src/p_acs.cpp index 4aeb0595fd..f04cfbdffe 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; diff --git a/src/p_map.cpp b/src/p_map.cpp index 2d105c284c..06483c44e8 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. @@ -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_mobj.cpp b/src/p_mobj.cpp index 85ca7662e8..9dfa8d5ca2 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -1011,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 || @@ -3461,7 +3461,7 @@ 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; @@ -4297,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 || diff --git a/src/p_things.cpp b/src/p_things.cpp index cd1540f4fc..6622ac0a78 100644 --- a/src/p_things.cpp +++ b/src/p_things.cpp @@ -65,7 +65,7 @@ bool P_Thing_Spawn (int tid, AActor *source, int type, angle_t angle, bool fog, return false; // Handle decorate replacements. - kind = kind->ActorInfo->GetReplacement()->Class; + kind = kind->GetReplacement(); if ((GetDefaultByType (kind)->flags3 & MF3_ISMONSTER) && ((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS))) @@ -200,7 +200,7 @@ bool P_Thing_Projectile (int tid, AActor *source, int type, const char *type_nam // Handle decorate replacements. - kind = kind->ActorInfo->GetReplacement()->Class; + kind = kind->GetReplacement(); defflags3 = GetDefaultByType (kind)->flags3; if ((defflags3 & MF3_ISMONSTER) && From 9a4abe0915c4146a572256fe0df3b6d00e5c4174 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 27 Aug 2010 15:20:05 +0000 Subject: [PATCH 34/84] - merged automap branch into trunk. SVN r2609 (trunk) --- src/CMakeLists.txt | 1 + src/actor.h | 2 + src/am_map.cpp | 369 +++++---- src/am_map.h | 2 +- src/asm_ia32/tmap.asm | 76 +- src/c_bind.cpp | 932 ++++++++++++---------- src/c_bind.h | 60 +- src/c_dispatch.cpp | 11 +- src/c_dispatch.h | 5 +- src/cmdlib.cpp | 154 ++++ src/cmdlib.h | 8 + src/d_main.cpp | 26 + src/f_finale.cpp | 2 +- src/g_game.cpp | 14 +- src/g_level.cpp | 2 +- src/gameconfigfile.cpp | 41 +- src/m_menu.h | 1 + src/m_options.cpp | 80 +- src/namedef.h | 1 + src/nodebuild.h | 16 +- src/nodebuild_extract.cpp | 47 +- src/p_acs.cpp | 2 +- src/p_glnodes.cpp | 1537 ++++++++++++++++++++++++++++++++++++ src/p_local.h | 3 +- src/p_maputl.cpp | 5 +- src/p_mobj.cpp | 16 +- src/p_saveg.cpp | 106 +++ src/p_saveg.h | 1 + src/p_setup.cpp | 133 +++- src/p_setup.h | 8 + src/p_udmf.cpp | 4 + src/po_man.cpp | 6 - src/r_bsp.cpp | 80 +- src/r_defs.h | 18 +- src/r_draw.cpp | 71 ++ src/r_draw.h | 5 +- src/r_plane.cpp | 15 +- src/r_plane.h | 1 - src/r_polymost.cpp | 2 +- src/r_state.h | 7 + src/v_draw.cpp | 172 +++- src/v_video.h | 6 + src/win32/fb_d3d9.cpp | 177 ++++- src/win32/fb_d3d9_wipe.cpp | 4 +- src/win32/win32iface.h | 13 +- wadsrc/static/language.enu | 2 + zdoom.vcproj | 4 + 47 files changed, 3497 insertions(+), 751 deletions(-) create mode 100644 src/p_glnodes.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5fadf0b2dd..6906bec5b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -679,6 +679,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 diff --git a/src/actor.h b/src/actor.h index 15439e7676..2a8921b56e 100644 --- a/src/actor.h +++ b/src/actor.h @@ -40,6 +40,7 @@ #include "r_blend.h" #include "s_sound.h" +struct subsector_t; // // NOTES: AActor // @@ -773,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. diff --git a/src/am_map.cpp b/src/am_map.cpp index 9dccdf2c03..63d328c647 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 @@ -417,7 +413,6 @@ 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 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 +461,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 +832,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 +1223,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 +1256,11 @@ bool AM_Responder (event_t *ev) void AM_changeWindowScale () { + int mtof_zoommul; + + if (Button_AM_ZoomIn.bDown) mtof_zoommul = M_ZOOMIN; + else if (Button_AM_ZoomOut.bDown) mtof_zoommul = M_ZOOMOUT; + // Change the scaling multipliers scale_mtof = MapMul(scale_mtof, mtof_zoommul); scale_ftom = MapDiv(MAPUNIT, scale_mtof); @@ -1334,19 +1305,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 +1319,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_changeWindowScale(); // Change x,y location @@ -1622,6 +1590,92 @@ 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_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 + ); + } +} + //============================================================================= // // @@ -2232,11 +2286,19 @@ void AM_drawAuthorMarkers () while (marked != NULL) { - if (mark->args[1] == 0 || (mark->args[1] == 1 && marked->Sector->MoreFlags & SECF_DRAWN)) + if (mark->args[1] == 0 || (mark->args[1] == 1)) { - DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0, - flip, mark->scaleX, mark->scaleY, mark->Translation, - mark->alpha, mark->fillcolor, mark->RenderStyle); + // Use more correct info if we have GL nodes available + INTBOOL drawn = hasglnodes? + marked->subsector->flags & SSECF_DRAWN : + marked->Sector->MoreFlags & SECF_DRAWN; + + if (drawn) + { + DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0, + flip, mark->scaleX, mark->scaleY, mark->Translation, + mark->alpha, mark->fillcolor, mark->RenderStyle); + } } marked = mark->args[0] != 0 ? it.Next() : NULL; } @@ -2291,6 +2353,9 @@ void AM_Drawer () } AM_activateNewScale(); + if (am_textured && hasglnodes && textured) + AM_drawSubsectors(); + if (grid) AM_drawGrid(GridColor); diff --git a/src/am_map.h b/src/am_map.h index 71740b9a7d..20da6b96be 100644 --- a/src/am_map.h +++ b/src/am_map.h @@ -26,7 +26,7 @@ struct event_t; class FArchive; // Called by main loop. -bool AM_Responder (event_t* ev); +bool AM_Responder (event_t* ev, bool last); // Called by main loop. void AM_Ticker (void); diff --git a/src/asm_ia32/tmap.asm b/src/asm_ia32/tmap.asm index e9713131f8..cbcd9f4f15 100644 --- a/src/asm_ia32/tmap.asm +++ b/src/asm_ia32/tmap.asm @@ -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 diff --git a/src/c_bind.cpp b/src/c_bind.cpp index d96b5a7426..6afb0c6775 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,27 @@ static const FBinding DefStrifeBindings[] = // h - use health }; +static const FBinding DefAutomapBindings[] = +{ + { "f", "am_togglefollow" }, + { "g", "am_togglegrid" }, + { "t", "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" }, + { NULL } +}; + + + const char *KeyNames[NUM_KEYS] = { // This array is dependant on the particular keyboard input @@ -278,11 +293,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 +325,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 +353,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 +425,471 @@ void C_NameKeys (char *str, int first, int second) *str = '\0'; } -void C_UnbindACommand (char *str) +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::DoBind (const char *key, const char *bind) +{ + int keynum = GetConfigKeyFromName (key); + if (keynum != 0) + { + Binds[keynum] = bind; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::SetBinds(const FBinding *binds) +{ + while (binds->Key) + { + DoBind (binds->Key, binds->Bind); + binds++; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindAll () +{ + for (int i = 0; i < NUM_KEYS; ++i) + { + Binds[i] = ""; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindKey(const char *key) +{ + int i; + + if ( (i = GetKeyFromName (key)) ) + { + Binds[i] = ""; + } + else + { + Printf ("Unknown key \"%s\"\n", key); + return; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::PerformBind(FCommandLine &argv, const char *msg) +{ + int i; + + if (argv.argc() > 1) + { + i = GetKeyFromName (argv[1]); + if (!i) + { + Printf ("Unknown key \"%s\"\n", argv[1]); + return; + } + if (argv.argc() == 2) + { + Printf ("\"%s\" = \"%s\"\n", argv[1], Binds[i].GetChars()); + } + else + { + Binds[i] = argv[2]; + } + } + else + { + Printf ("%s:\n", msg); + + for (i = 0; i < NUM_KEYS; i++) + { + if (!Binds[i].IsEmpty()) + Printf ("%s \"%s\"\n", KeyName (i), Binds[i].GetChars()); + } + } +} + + +//============================================================================= +// +// This function is first called for functions in custom key sections. +// In this case, matchcmd is non-NULL, and only keys bound to that command +// are stored. If a match is found, its binding is set to "\1". +// After all custom key sections are saved, it is called one more for the +// normal Bindings and DoubleBindings sections for this game. In this case +// matchcmd is NULL and all keys will be stored. The config section was not +// previously cleared, so all old bindings are still in place. If the binding +// for a key is empty, the corresponding key in the config is removed as well. +// If a binding is "\1", then the binding itself is cleared, but nothing +// happens to the entry in the config. +// +//============================================================================= + +void FKeyBindings::ArchiveBindings(FConfigFile *f, const char *matchcmd) { int i; for (i = 0; i < NUM_KEYS; i++) { - if (!stricmp (str, Bindings[i])) + if (Binds[i].IsEmpty()) { - Bindings[i] = ""; + if (matchcmd == NULL) + { + f->ClearKey(ConfigKeyName(i)); + } + } + else if (matchcmd == NULL || stricmp(Binds[i], matchcmd) == 0) + { + if (Binds[i][0] == '\1') + { + Binds[i] = ""; + continue; + } + f->SetValueForKey(ConfigKeyName(i), Binds[i]); + if (matchcmd != NULL) + { // If saving a specific command, set a marker so that + // it does not get saved in the general binding list. + Binds[i] = "\1"; + } } } } -void C_ChangeBinding (const char *str, int newone) +//============================================================================= +// +// +// +//============================================================================= + +int FKeyBindings::GetKeysForCommand (char *cmd, int *first, int *second) { - if ((unsigned int)newone < NUM_KEYS) + int c, i; + + *first = *second = c = i = 0; + + while (i < NUM_KEYS && c < 2) { - Bindings[newone] = str; + if (stricmp (cmd, Binds[i]) == 0) + { + if (c++ == 0) + *first = i; + else + *second = i; + } + i++; + } + return c; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::UnbindACommand (char *str) +{ + int i; + + for (i = 0; i < NUM_KEYS; i++) + { + if (!stricmp (str, Binds[i])) + { + Binds[i] = ""; + } } } -const char *C_GetBinding (int key) +//============================================================================= +// +// +// +//============================================================================= + +void FKeyBindings::DefaultBind(const char *keyname, const char *cmd) { - return (unsigned int)key < NUM_KEYS ? Bindings[key].GetChars() : NULL; + int key = GetKeyFromName (keyname); + if (key == 0) + { + Printf ("Unknown key \"%s\"\n", keyname); + return; + } + if (!Binds[key].IsEmpty()) + { // This key is already bound. + return; + } + for (int i = 0; i < NUM_KEYS; ++i) + { + if (!Binds[i].IsEmpty() && stricmp (Binds[i], cmd) == 0) + { // This command is already bound to a key. + return; + } + } + // It is safe to do the bind, so do it. + Binds[key] = cmd; } + +//============================================================================= +// +// +// +//============================================================================= + +void C_UnbindAll () +{ + Bindings.UnbindAll(); + DoubleBindings.UnbindAll(); + AutomapBindings.UnbindAll(); +} + +CCMD (unbindall) +{ + C_UnbindAll (); +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (unbind) +{ + if (argv.argc() > 1) + { + Bindings.UnbindKey(argv[1]); + } +} + +CCMD (undoublebind) +{ + if (argv.argc() > 1) + { + DoubleBindings.UnbindKey(argv[1]); + } +} + +CCMD (unmapbind) +{ + if (argv.argc() > 1) + { + AutomapBindings.UnbindKey(argv[1]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (bind) +{ + Bindings.PerformBind(argv, "Current key bindings"); +} + +CCMD (doublebind) +{ + DoubleBindings.PerformBind(argv, "Current key doublebindings"); +} + +CCMD (mapbind) +{ + AutomapBindings.PerformBind(argv, "Current automap key bindings"); +} + +//========================================================================== +// +// CCMD defaultbind +// +// Binds a command to a key if that key is not already bound and if +// that command is not already bound to another key. +// +//========================================================================== + +CCMD (defaultbind) +{ + if (argv.argc() < 3) + { + Printf ("Usage: defaultbind \n"); + } + else + { + Bindings.DefaultBind(argv[1], argv[2]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (rebind) +{ + FKeyBindings *bindings; + + if (key == 0) + { + Printf ("Rebind cannot be used from the console\n"); + return; + } + + if (key & KEY_DBLCLICKED) + { + bindings = &DoubleBindings; + key &= KEY_DBLCLICKED-1; + } + else + { + bindings = &Bindings; + } + + if (argv.argc() > 1) + { + bindings->SetBind(key, argv[1]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void C_BindDefaults () +{ + Bindings.SetBinds (DefBindings); + + if (gameinfo.gametype & (GAME_Raven|GAME_Strife)) + { + Bindings.SetBinds (DefRavenBindings); + } + + if (gameinfo.gametype == GAME_Heretic) + { + Bindings.SetBinds (DefHereticBindings); + } + + if (gameinfo.gametype == GAME_Hexen) + { + Bindings.SetBinds (DefHexenBindings); + } + + if (gameinfo.gametype == GAME_Strife) + { + Bindings.SetBinds (DefStrifeBindings); + } + + AutomapBindings.SetBinds(DefAutomapBindings); +} + +CCMD(binddefaults) +{ + C_BindDefaults (); +} + +void C_SetDefaultBindings () +{ + C_UnbindAll (); + C_BindDefaults (); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds) +{ + FString binding; + bool dclick; + int dclickspot; + BYTE dclickmask; + + if (ev->type != EV_KeyDown && ev->type != EV_KeyUp) + return false; + + if ((unsigned int)ev->data1 >= NUM_KEYS) + return false; + + dclickspot = ev->data1 >> 3; + dclickmask = 1 << (ev->data1 & 7); + dclick = false; + + // This used level.time which didn't work outside a level. + if (DClickTime[ev->data1] > I_MSTime() && ev->type == EV_KeyDown) + { + // Key pressed for a double click + if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1); + DClicked[dclickspot] |= dclickmask; + dclick = true; + } + else + { + if (ev->type == EV_KeyDown) + { // Key pressed for a normal press + binding = binds->GetBinding(ev->data1); + DClickTime[ev->data1] = I_MSTime() + 571; + } + else if (DClicked[dclickspot] & dclickmask) + { // Key released from a double click + if (doublebinds != NULL) binding = doublebinds->GetBinding(ev->data1); + DClicked[dclickspot] &= ~dclickmask; + DClickTime[ev->data1] = 0; + dclick = true; + } + else + { // Key released from a normal press + binding = binds->GetBinding(ev->data1); + } + } + + + if (binding.IsEmpty()) + { + binding = binds->GetBinding(ev->data1); + dclick = false; + } + + if (!binding.IsEmpty() && (chatmodeon == 0 || ev->data1 < 256)) + { + if (ev->type == EV_KeyUp && binding[0] != '+') + { + return false; + } + + char *copy = binding.LockBuffer(); + + if (ev->type == EV_KeyUp) + { + copy[0] = '-'; + } + + AddCommandString (copy, dclick ? ev->data1 | KEY_DBLCLICKED : ev->data1); + return true; + } + return false; +} + diff --git a/src/c_bind.h b/src/c_bind.h index 2b46118cf3..7d71b462a3 100644 --- a/src/c_bind.h +++ b/src/c_bind.h @@ -34,25 +34,65 @@ #ifndef __C_BINDINGS_H__ #define __C_BINDINGS_H__ +#include "doomdef.h" struct event_t; class FConfigFile; +class FCommandLine; -bool C_DoKey (event_t *ev); -void C_ArchiveBindings (FConfigFile *f, bool dodouble, const char *matchcmd=NULL); +void C_NameKeys (char *str, int first, int second); + +struct FBinding +{ + const char *Key; + const char *Bind; +}; + +class FKeyBindings +{ + FString Binds[NUM_KEYS]; + +public: + void PerformBind(FCommandLine &argv, const char *msg); + void SetBinds(const FBinding *binds); + bool DoKey(event_t *ev); + void ArchiveBindings(FConfigFile *F, const char *matchcmd = NULL); + int GetKeysForCommand (char *cmd, int *first, int *second); + void UnbindACommand (char *str); + void UnbindAll (); + void UnbindKey(const char *key); + void DoBind (const char *key, const char *bind); + void DefaultBind(const char *keyname, const char *cmd); + + void SetBind(unsigned int key, const char *bind) + { + if (key < NUM_KEYS) Binds[key] = bind; + } + + const FString &GetBinding(unsigned int index) const + { + return Binds[index]; + } + + const char *GetBind(unsigned int index) const + { + if (index < NUM_KEYS) return Binds[index]; + else return NULL; + } + +}; + +extern FKeyBindings Bindings; +extern FKeyBindings DoubleBindings; +extern FKeyBindings AutomapBindings; + + +bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds); // Stuff used by the customize controls menu -int C_GetKeysForCommand (char *cmd, int *first, int *second); -void C_NameKeys (char *str, int first, int second); -void C_UnbindACommand (char *str); -void C_ChangeBinding (const char *str, int newone); -void C_DoBind (const char *key, const char *bind, bool doublebind); void C_SetDefaultBindings (); void C_UnbindAll (); -// Returns string bound to given key (NULL if none) -const char *C_GetBinding (int key); - extern const char *KeyNames[]; #endif //__C_BINDINGS_H__ diff --git a/src/c_dispatch.cpp b/src/c_dispatch.cpp index 2fbafd2a95..413b8d64c4 100644 --- a/src/c_dispatch.cpp +++ b/src/c_dispatch.cpp @@ -119,7 +119,9 @@ FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, Button_Forward, Button_Right, Button_Left, Button_MoveDown, Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch, Button_Zoom, Button_Reload, - Button_User1, Button_User2, Button_User3, Button_User4; + Button_User1, Button_User2, Button_User3, Button_User4, + Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp, + Button_AM_ZoomIn, Button_AM_ZoomOut; bool ParsingKeyConf; @@ -131,13 +133,16 @@ bool ParsingKeyConf; FActionMap ActionMaps[] = { + { 0x0d52d67b, &Button_AM_PanLeft, "am_panleft"}, { 0x125f5226, &Button_User2, "user2" }, { 0x1eefa611, &Button_Jump, "jump" }, { 0x201f1c55, &Button_Right, "right" }, { 0x20ccc4d5, &Button_Zoom, "zoom" }, { 0x23a99cd7, &Button_Back, "back" }, + { 0x41df90c2, &Button_AM_ZoomIn, "am_zoomin"}, { 0x426b69e7, &Button_Reload, "reload" }, { 0x4463f43a, &Button_LookDown, "lookdown" }, + { 0x51f7a334, &Button_AM_ZoomOut, "am_zoomout"}, { 0x534c30ee, &Button_User4, "user4" }, { 0x5622bf42, &Button_Attack, "attack" }, { 0x577712d0, &Button_User1, "user1" }, @@ -147,12 +152,15 @@ FActionMap ActionMaps[] = { 0x676885b8, &Button_AltAttack, "altattack" }, { 0x6fa41b84, &Button_MoveLeft, "moveleft" }, { 0x818f08e6, &Button_MoveRight, "moveright" }, + { 0x8197097b, &Button_AM_PanRight, "am_panright"}, + { 0x8d89955e, &Button_AM_PanUp, "am_panup"} , { 0xa2b62d8b, &Button_Mlook, "mlook" }, { 0xab2c3e71, &Button_Crouch, "crouch" }, { 0xb000b483, &Button_Left, "left" }, { 0xb62b1e49, &Button_LookUp, "lookup" }, { 0xb6f8fe92, &Button_User3, "user3" }, { 0xb7e6a54b, &Button_Strafe, "strafe" }, + { 0xce301c81, &Button_AM_PanDown, "am_pandown"}, { 0xd5897c73, &Button_ShowScores, "showscores" }, { 0xe0ccb317, &Button_Speed, "speed" }, { 0xe0cfc260, &Button_Use, "use" }, @@ -160,6 +168,7 @@ FActionMap ActionMaps[] = }; #define NUM_ACTIONS countof(ActionMaps) + // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const char *KeyConfCommands[] = diff --git a/src/c_dispatch.h b/src/c_dispatch.h index a19352d32b..d93709c90a 100644 --- a/src/c_dispatch.h +++ b/src/c_dispatch.h @@ -146,6 +146,7 @@ struct FButtonStatus bool PressKey (int keynum); // Returns true if this key caused the button to be pressed. bool ReleaseKey (int keynum); // Returns true if this key is no longer pressed. void ResetTriggers () { bWentDown = bWentUp = false; } + void Reset () { bDown = bWentDown = bWentUp = false; } }; extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, @@ -154,7 +155,9 @@ extern FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack, Button_Forward, Button_Right, Button_Left, Button_MoveDown, Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch, Button_Zoom, Button_Reload, - Button_User1, Button_User2, Button_User3, Button_User4; + Button_User1, Button_User2, Button_User3, Button_User4, + Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp, + Button_AM_ZoomIn, Button_AM_ZoomOut; extern bool ParsingKeyConf; void ResetButtonTriggers (); // Call ResetTriggers for all buttons diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp index 9c06b6714c..36a9007094 100644 --- a/src/cmdlib.cpp +++ b/src/cmdlib.cpp @@ -2,10 +2,14 @@ #ifdef _WIN32 #include +#include #else #include #include #include +#if !defined(__sun) +#include +#endif #endif #include "doomtype.h" #include "cmdlib.h" @@ -885,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 f9d7fae703..7c29644d55 100644 --- a/src/cmdlib.h +++ b/src/cmdlib.h @@ -54,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/d_main.cpp b/src/d_main.cpp index deef1d6c72..20ba2c4675 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -2294,6 +2294,14 @@ void FStartupScreen::AppendStatusLine(const char *status) // //========================================================================== +//========================================================================== +// +// STAT fps +// +// Displays statistics about rendering times +// +//========================================================================== + ADD_STAT (fps) { FString out; @@ -2302,6 +2310,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/f_finale.cpp b/src/f_finale.cpp index c37d1f4593..a851fdc319 100644 --- a/src/f_finale.cpp +++ b/src/f_finale.cpp @@ -723,7 +723,7 @@ bool F_CastResponder (event_t* ev) if (ev->type != EV_KeyDown) return false; - const char *cmd = C_GetBinding (ev->data1); + const char *cmd = Bindings.GetBind (ev->data1); if (cmd != NULL && !stricmp (cmd, "toggleconsole")) return false; diff --git a/src/g_game.cpp b/src/g_game.cpp index fadfb6c551..f85601c6e1 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -879,7 +879,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) { @@ -902,11 +902,11 @@ bool G_Responder (event_t *ev) } else { - return C_DoKey (ev); + return C_DoKey (ev, &Bindings, &DoubleBindings); } } if (cmd && cmd[0] == '+') - return C_DoKey (ev); + return C_DoKey (ev, &Bindings, &DoubleBindings); return false; } @@ -918,7 +918,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) @@ -930,12 +930,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 @@ -949,7 +949,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); diff --git a/src/g_level.cpp b/src/g_level.cpp index b14959efe7..8f475d3f31 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1464,8 +1464,8 @@ void G_SerializeLevel (FArchive &arc, bool hubLoad) P_SerializeThinkers (arc, hubLoad); P_SerializeWorld (arc); P_SerializePolyobjs (arc); + P_SerializeSubsectors(arc); StatusBar->Serialize (arc); - //SerializeInterpolations (arc); arc << level.total_monsters << level.total_items << level.total_secrets; diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 936d4e7f75..acf2bbad18 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -399,29 +399,38 @@ void FGameConfigFile::DoGameSetup (const char *gamename) ReadCVars (0); } - strncpy (subsection, "Bindings", sublen); - if (!SetSection (section)) - { // Config has no bindings for the given game - if (!bMigrating) - { - C_SetDefaultBindings (); - } - } - else + if (!bMigrating) { - C_UnbindAll (); + C_SetDefaultBindings (); + } + + strncpy (subsection, "Bindings", sublen); + if (SetSection (section)) + { + Bindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, false); + Bindings.DoBind (key, value); } } strncpy (subsection, "DoubleBindings", sublen); if (SetSection (section)) { + DoubleBindings.UnbindAll(); while (NextInSection (key, value)) { - C_DoBind (key, value, true); + DoubleBindings.DoBind (key, value); + } + } + + strncpy (subsection, "AutomapBindings", sublen); + if (SetSection (section)) + { + AutomapBindings.UnbindAll(); + while (NextInSection (key, value)) + { + AutomapBindings.DoBind (key, value); } } @@ -512,11 +521,15 @@ void FGameConfigFile::ArchiveGameData (const char *gamename) strcpy (subsection, "Bindings"); SetSection (section, true); - C_ArchiveBindings (this, false); + Bindings.ArchiveBindings (this); strncpy (subsection, "DoubleBindings", sublen); SetSection (section, true); - C_ArchiveBindings (this, true); + DoubleBindings.ArchiveBindings (this); + + strncpy (subsection, "AutomapBindings", sublen); + SetSection (section, true); + AutomapBindings.ArchiveBindings (this); } void FGameConfigFile::ArchiveGlobalData () diff --git a/src/m_menu.h b/src/m_menu.h index 4f45cbed23..5653b694a5 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -116,6 +116,7 @@ typedef enum { joy_slider, joy_map, joy_inverter, + mapcontrol, } itemtype; struct IJoystickConfig; diff --git a/src/m_options.cpp b/src/m_options.cpp index 06c3017bfd..dae45c994c 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -204,6 +204,7 @@ static menu_t ConfirmMenu = { * *=======================================*/ +static void StartAutomapMenu (void); static void CustomizeControls (void); static void GameplayOptions (void); static void CompatibilityOptions (void); @@ -228,6 +229,7 @@ static menuitem_t OptionItems[] = { more, "Player Setup", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)M_PlayerSetup} }, { more, "Gameplay Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)GameplayOptions} }, { more, "Compatibility Options",{NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)CompatibilityOptions} }, + { more, "Automap Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartAutomapMenu} }, { more, "Sound Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SoundOptions} }, { more, "Display Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)VideoOptions} }, { more, "Set video mode", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SetVidMode} }, @@ -419,13 +421,13 @@ menu_t ControlsMenu = 2, }; + /*======================================= * * Display Options Menu * *=======================================*/ static void StartMessagesMenu (void); -static void StartAutomapMenu (void); static void StartScoreboardMenu (void); static void InitCrosshairsList(); @@ -495,7 +497,6 @@ static value_t DisplayTagsTypes[] = { 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} }, @@ -538,6 +539,7 @@ menu_t VideoMenu = * *=======================================*/ static void StartMapColorsMenu (void); +static void StartMapControlsMenu (void); EXTERN_CVAR (Int, am_rotate) EXTERN_CVAR (Int, am_overlay) @@ -548,6 +550,7 @@ EXTERN_CVAR (Bool, am_showtime) EXTERN_CVAR (Int, am_map_secrets) EXTERN_CVAR (Bool, am_showtotaltime) EXTERN_CVAR (Bool, am_drawmapback) +EXTERN_CVAR (Bool, am_textured) static value_t MapColorTypes[] = { { 0, "Custom" }, @@ -577,9 +580,11 @@ static value_t OverlayTypes[] = { static menuitem_t AutomapItems[] = { { discrete, "Map color set", {&am_colorset}, {4.0}, {0.0}, {0.0}, {MapColorTypes} }, { more, "Set custom colors", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t*)StartMapColorsMenu} }, + { more, "Customize map controls", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t*)StartMapControlsMenu} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Rotate automap", {&am_rotate}, {3.0}, {0.0}, {0.0}, {RotateTypes} }, { discrete, "Overlay automap", {&am_overlay}, {3.0}, {0.0}, {0.0}, {OverlayTypes} }, + { discrete, "Enable textured display", {&am_textured}, {3.0}, {0.0}, {0.0}, {OnOff} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Show item counts", {&am_showitems}, {2.0}, {0.0}, {0.0}, {OnOff} }, { discrete, "Show monster counts", {&am_showmonsters}, {2.0}, {0.0}, {0.0}, {OnOff} }, @@ -600,6 +605,37 @@ menu_t AutomapMenu = AutomapItems, }; +menuitem_t MapControlsItems[] = +{ + { redtext,"ENTER to change, BACKSPACE to clear", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { whitetext,"Map Controls", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { mapcontrol, "Pan left", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panleft"} }, + { mapcontrol, "Pan right", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panright"} }, + { mapcontrol, "Pan up", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panup"} }, + { mapcontrol, "Pan down", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_pandown"} }, + { mapcontrol, "Zoom in", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomin"} }, + { mapcontrol, "Zoom out", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomout"} }, + { mapcontrol, "Toggle zoom", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_gobig"} }, + { mapcontrol, "Toggle follow", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglefollow"} }, + { mapcontrol, "Toggle grid", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglegrid"} }, + { mapcontrol, "Toggle texture", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_toggletexture"} }, + { mapcontrol, "Set mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_setmark"} }, + { mapcontrol, "Clear mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_clearmarks"} }, +}; + +menu_t MapControlsMenu = +{ + "CUSTOMIZE MAP CONTROLS", + 3, + countof(MapControlsItems), + 0, + MapControlsItems, + 2, +}; + + + /*======================================= * * Map Colors Menu @@ -1536,7 +1572,9 @@ void M_BuildKeyList (menuitem_t *item, int numitems) for (i = 0; i < numitems; i++, item++) { if (item->type == control) - C_GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); + Bindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); + else if (item->type == mapcontrol) + AutomapBindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); } } @@ -1825,7 +1863,7 @@ void M_OptDrawer () default: x = indent - width; - color = (item->type == control && menuactive == MENU_WaitKey && i == CurrentItem) + color = ((item->type == control || item->type == mapcontrol) && menuactive == MENU_WaitKey && i == CurrentItem) ? CR_YELLOW : LabelColor; break; } @@ -1983,6 +2021,7 @@ void M_OptDrawer () break; case control: + case mapcontrol: { char description[64]; @@ -2165,7 +2204,14 @@ void M_OptResponder(event_t *ev) { if (ev->data1 != KEY_ESCAPE) { - C_ChangeBinding(item->e.command, ev->data1); + if (item->type == control) + { + Bindings.SetBind(ev->data1, item->e.command); + } + else if (item->type == mapcontrol) + { + AutomapBindings.SetBind(ev->data1, item->e.command); + } M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems); } menuactive = MENU_On; @@ -2812,7 +2858,12 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) case MKEY_Clear: if (item->type == control) { - C_UnbindACommand (item->e.command); + Bindings.UnbindACommand (item->e.command); + item->b.key1 = item->c.key2 = 0; + } + else if (item->type == mapcontrol) + { + AutomapBindings.UnbindACommand (item->e.command); item->b.key1 = item->c.key2 = 0; } break; @@ -2880,7 +2931,7 @@ void M_OptButtonHandler(EMenuKey key, bool repeat) S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); } - else if (item->type == control) + else if (item->type == control || item->type == mapcontrol) { menuactive = MENU_WaitKey; OldMessage = CurrentMenu->items[0].label; @@ -3000,6 +3051,12 @@ static void StartMapColorsMenu (void) M_SwitchMenu (&MapColorsMenu); } +static void StartMapControlsMenu (void) +{ + M_BuildKeyList (MapControlsMenu.items, MapControlsMenu.numitems); + M_SwitchMenu (&MapControlsMenu); +} + CCMD (menu_mapcolors) { M_StartControlPanel (true); @@ -3644,12 +3701,14 @@ void M_LoadKeys (const char *modname, bool dbl) mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname, dbl ? ".Double" : "."); + + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; if (GameConfig->SetSection (section)) { const char *key, *value; while (GameConfig->NextInSection (key, value)) { - C_DoBind (key, value, dbl); + bindings->DoBind (key, value); } } } @@ -3660,12 +3719,13 @@ int M_DoSaveKeys (FConfigFile *config, char *section, int i, bool dbl) config->SetSection (section, true); config->ClearCurrentSection (); + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; for (++i; i < most; ++i) { menuitem_t *item = &CustomControlsItems[i]; if (item->type == control) { - C_ArchiveBindings (config, dbl, item->e.command); + bindings->ArchiveBindings (config, item->e.command); continue; } break; @@ -3711,7 +3771,7 @@ void FreeKeySections() for (i = numStdControls; i < CustomControlsItems.Size(); ++i) { menuitem_t *item = &CustomControlsItems[i]; - if (item->type == whitetext || item->type == control) + if (item->type == whitetext || item->type == control || item->type == mapcontrol) { if (item->label != NULL) { diff --git a/src/namedef.h b/src/namedef.h index cb3e78ea22..99737eda50 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -442,6 +442,7 @@ xx(nofakecontrast) xx(smoothlighting) xx(blockprojectiles) xx(blockuse) +xx(hidden) xx(Renderstyle) diff --git a/src/nodebuild.h b/src/nodebuild.h index d1ed2cb15b..da82e26a23 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 d8675aa6eb..5355179ea3 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,14 +110,12 @@ 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].PartnerSeg = &outSegs[Segs[(unsigned int)(size_t)outSegs[i].PartnerSeg-1].storedseg]; - } + outSegs[i] = *(seg_t *)&segs[i]; + outSegExtras[i].PartnerSeg = segs[i].Partner; } } else @@ -125,6 +123,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 +137,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 +191,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 +229,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 +237,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 +399,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 +466,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 +484,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 +498,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/p_acs.cpp b/src/p_acs.cpp index f04cfbdffe..bf480fe73e 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -5097,7 +5097,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_glnodes.cpp b/src/p_glnodes.cpp new file mode 100644 index 0000000000..426552c37d --- /dev/null +++ b/src/p_glnodes.cpp @@ -0,0 +1,1537 @@ +/* +** 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() +#include +#endif + +#ifndef _WIN32 +#include + +#else + +#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; + + // 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; + + // Check for incorrect partner seg info so that the following code does not crash. + 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_local.h b/src/p_local.h index 91b134605a..2997f31216 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_maputl.cpp b/src/p_maputl.cpp index 91370c0707..9f4ddb39a0 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 9dfa8d5ca2..98f0801bc6 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -4705,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++) { @@ -4823,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) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 1ee18eeb27..1b44426497 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; @@ -1162,7 +1164,6 @@ void P_LoadSegs (MapData * map) li->v1 = &vertexes[vnum1]; li->v2 = &vertexes[vnum2]; - li->PartnerSeg = NULL; segangle = (WORD)LittleShort(ml->angle); @@ -2904,6 +2905,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 @@ -3339,6 +3350,11 @@ void P_FreeLevelData () delete[] segs; segs = NULL; } + if (glsegextras != NULL) + { + delete[] glsegextras; + glsegextras = NULL; + } if (sectors != NULL) { delete[] sectors[0].e; @@ -3346,6 +3362,18 @@ void P_FreeLevelData () sectors = NULL; numsectors = 0; // needed for the pointer cleanup code } + if (gamenodes && gamenodes!=nodes) + { + delete [] gamenodes; + gamenodes = NULL; + numgamenodes = 0; + } + if (gamesubsectors && gamesubsectors!=subsectors) + { + delete [] gamesubsectors; + gamesubsectors = NULL; + numgamesubsectors = 0; + } if (subsectors != NULL) { for (int i = 0; i < numsubsectors; ++i) @@ -3480,6 +3508,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(); @@ -3537,6 +3568,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; @@ -3665,23 +3697,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'); @@ -3756,10 +3788,23 @@ 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; + if (ForceNodeBuild) { - unsigned int startTime, endTime; startTime = I_FPSTime (); TArray polyspots, anchors; @@ -3771,15 +3816,46 @@ 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. + bool BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes; + 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; + } + + // 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, ForceNodeBuild, endTime - startTime); + hasglnodes = true; + } + else + { + hasglnodes = P_CheckForGLNodes(); } times[10].Clock(); @@ -3798,6 +3874,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. @@ -3845,7 +3926,7 @@ void P_SetupLevel (char *lumpname, int position) P_SpawnSpecials (); times[16].Clock(); - if (ForceNodeBuild) P_LoopSidedefs (false); + if (reloop) P_LoopSidedefs (false); PO_Init (); // Initialize the polyobjs times[16].Unclock(); @@ -3922,6 +4003,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 80196e3173..aa860af0e3 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_udmf.cpp b/src/p_udmf.cpp index e75d324df8..5f2fca9411 100644 --- a/src/p_udmf.cpp +++ b/src/p_udmf.cpp @@ -1227,6 +1227,10 @@ public: sec->seqType = -1; continue; + case NAME_hidden: + sec->MoreFlags |= SECF_HIDDEN; + break; + default: break; } diff --git a/src/po_man.cpp b/src/po_man.cpp index 1aa065c19c..7619bd06af 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -1827,12 +1827,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;i 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 3c8ab46d7b..55b896a80d 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,22 @@ 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, +}; + 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 ce836f839b..4334f6f79e 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -1000,6 +1000,77 @@ const BYTE* ds_source; // just for profiling int dscount; + +#ifdef X86_ASM +extern "C" void R_SetSpanSource_ASM (const BYTE *flat); +extern "C" void STACK_ARGS R_SetSpanSize_ASM (int xbits, int ybits); +extern "C" void R_SetSpanColormap_ASM (BYTE *colormap); +extern "C" BYTE *ds_curcolormap, *ds_cursource, *ds_curtiltedsource; +#endif +} + +//========================================================================== +// +// R_SetSpanSource +// +// Sets the source bitmap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanSource(const BYTE *pixels) +{ + ds_source = pixels; +#ifdef X86_ASM + if (ds_cursource != ds_source) + { + R_SetSpanSource_ASM(pixels); + } +#endif +} + +//========================================================================== +// +// R_SetSpanColormap +// +// Sets the colormap for the span drawing routines. +// +//========================================================================== + +void R_SetSpanColormap(BYTE *colormap) +{ + ds_colormap = colormap; +#ifdef X86_ASM + if (ds_colormap != ds_curcolormap) + { + R_SetSpanColormap_ASM (ds_colormap); + } +#endif +} + +//========================================================================== +// +// R_SetupSpanBits +// +// Sets the texture size for the span drawing routines. +// +//========================================================================== + +void R_SetupSpanBits(FTexture *tex) +{ + tex->GetWidth (); + ds_xbits = tex->WidthBits; + ds_ybits = tex->HeightBits; + if ((1 << ds_xbits) > tex->GetWidth()) + { + ds_xbits--; + } + if ((1 << ds_ybits) > tex->GetHeight()) + { + ds_ybits--; + } +#ifdef X86_ASM + R_SetSpanSize_ASM (ds_xbits, ds_ybits); +#endif } // diff --git a/src/r_draw.h b/src/r_draw.h index 4d452b49de..96e58bf8da 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 b89a2979e0..cd0b9f4a5e 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 d52fc6db2e..369083badb 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 2cb3e22256..12050440f6 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 38f8e4c3e8..9044da72be 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -77,6 +77,13 @@ extern side_t* sides; extern int numzones; extern zone_t* zones; +extern node_t * gamenodes; +extern int numgamenodes; + +extern subsector_t * gamesubsectors; +extern int numgamesubsectors; + + extern FExtraLight* ExtraLights; extern FLightStack* LightStacks; diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 3c2ed74d05..af6b804d13 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -62,6 +62,9 @@ int CleanWidth, CleanHeight; // Above minus 1 (or 1, if they are already 1) int CleanXfac_1, CleanYfac_1, CleanWidth_1, CleanHeight_1; +// FillSimplePoly uses this +extern "C" short spanend[MAXHEIGHT]; + CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); // For routines that take RGB colors, cache the previous lookup in case there @@ -69,6 +72,7 @@ CVAR (Bool, hud_scale, false, CVAR_ARCHIVE); static int LastPal = -1; static uint32 LastRGB; + static int PalFromRGB(uint32 rgb) { if (LastPal >= 0 && LastRGB == rgb) @@ -186,7 +190,7 @@ void STACK_ARGS DCanvas::DrawTextureV(FTexture *img, double x, double y, uint32 double iyscale = 1 / yscale; spryscale = FLOAT2FIXED(yscale); - + assert(spryscale > 2); #if 0 // Fix precision errors that are noticeable at some resolutions if ((y0 + parms.destheight) > (y0 + yscale * img->GetHeight())) @@ -1091,6 +1095,172 @@ void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uin } } +//========================================================================== +// +// DCanvas :: FillSimplePoly +// +// Fills a simple polygon with a texture. Here, "simple" means that a +// horizontal scanline at any vertical position within the polygon will +// not cross it more than twice. +// +// The originx, originy, scale, and rotation parameters specify +// transformation of the filling texture, not of the points. +// +// The points must be specified in clockwise order. +// +//========================================================================== + +void DCanvas::FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, + double originx, double originy, double scalex, double scaley, angle_t rotation, + FDynamicColormap *colormap, int lightlevel) +{ + // Use an equation similar to player sprites to determine shade + fixed_t shade = LIGHT2SHADE(lightlevel) - 12*FRACUNIT; + float topy, boty, leftx, rightx; + int toppt, botpt, pt1, pt2; + int i; + int y1, y2, y; + fixed_t x; + double rot = rotation * M_PI / double(1u << 31); + bool dorotate = rot != 0; + double cosrot, sinrot; + + if (--npoints < 2 || Buffer == NULL) + { // not a polygon or we're not locked + return; + } + + // Find the extents of the polygon, in particular the highest and lowest points. + for (botpt = toppt = 0, boty = topy = points[0].Y, leftx = rightx = points[0].X, i = 1; i <= npoints; ++i) + { + if (points[i].Y < topy) + { + topy = points[i].Y; + toppt = i; + } + if (points[i].Y > boty) + { + boty = points[i].Y; + botpt = i; + } + if (points[i].X < leftx) + { + leftx = points[i].X; + } + if (points[i].X > rightx) + { + rightx = points[i].X; + } + } + if (topy >= Height || // off the bottom of the screen + boty <= 0 || // off the top of the screen + leftx >= Width || // off the right of the screen + rightx <= 0) // off the left of the screen + { + return; + } + + cosrot = cos(rot); + sinrot = sin(rot); + + // Setup constant texture mapping parameters. + R_SetupSpanBits(tex); + R_SetSpanColormap(colormap != NULL ? &colormap->Maps[clamp(shade >> FRACBITS, 0, NUMCOLORMAPS-1) * 256] : identitymap); + R_SetSpanSource(tex->GetPixels()); + scalex = double(1u << (32 - ds_xbits)) / scalex; + scaley = double(1u << (32 - ds_ybits)) / scaley; + ds_xstep = xs_RoundToInt(cosrot * scalex); + ds_ystep = xs_RoundToInt(sinrot * scaley); + + // Travel down the right edge and create an outline of that edge. + pt1 = toppt; + pt2 = toppt + 1; if (pt2 > npoints) pt2 = 0; + y1 = xs_RoundToInt(points[pt1].Y + 0.5f); + do + { + x = FLOAT2FIXED(points[pt1].X + 0.5f); + y2 = xs_RoundToInt(points[pt2].Y + 0.5f); + if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height)) + { + } + else + { + fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y)); + int y3 = MIN(y2, Height); + if (y1 < 0) + { + x += xinc * -y1; + y1 = 0; + } + for (y = y1; y < y3; ++y) + { + spanend[y] = clamp(x >> FRACBITS, -1, Width); + x += xinc; + } + } + y1 = y2; + pt1 = pt2; + pt2++; if (pt2 > npoints) pt2 = 0; + } while (pt1 != botpt); + + // Travel down the left edge and fill it in. + pt1 = toppt; + pt2 = toppt - 1; if (pt2 < 0) pt2 = npoints; + y1 = xs_RoundToInt(points[pt1].Y + 0.5f); + do + { + x = FLOAT2FIXED(points[pt1].X + 0.5f); + y2 = xs_RoundToInt(points[pt2].Y + 0.5f); + if (y1 >= y2 || (y1 < 0 && y2 < 0) || (y1 >= Height && y2 >= Height)) + { + } + else + { + fixed_t xinc = FLOAT2FIXED((points[pt2].X - points[pt1].X) / (points[pt2].Y - points[pt1].Y)); + int y3 = MIN(y2, Height); + if (y1 < 0) + { + x += xinc * -y1; + y1 = 0; + } + for (y = y1; y < y3; ++y) + { + int x1 = x >> FRACBITS; + int x2 = spanend[y]; + if (x2 > x1 && x2 > 0 && x1 < Width) + { + x1 = MAX(x1, 0); + x2 = MIN(x2, Width); +#if 0 + memset(this->Buffer + y * this->Pitch + x1, (int)tex, x2 - x1); +#else + ds_y = y; + ds_x1 = x1; + ds_x2 = x2 - 1; + + TVector2 tex(x1 - originx, y - originy); + if (dorotate) + { + double t = tex.X; + tex.X = t * cosrot - tex.Y * sinrot; + tex.Y = tex.Y * cosrot + t * sinrot; + } + ds_xfrac = xs_RoundToInt(tex.X * scalex); + ds_yfrac = xs_RoundToInt(tex.Y * scaley); + + R_DrawSpan(); +#endif + } + x += xinc; + } + } + y1 = y2; + pt1 = pt2; + pt2--; if (pt2 < 0) pt2 = npoints; + } while (pt1 != botpt); +} + + /********************************/ /* */ /* Other miscellaneous routines */ diff --git a/src/v_video.h b/src/v_video.h index 77a3f8a655..b1ba7a8631 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); diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index b2c8e1bfaa..020be2de1a 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->GetWidth() * scalex)); + vscale = float(1.f / (texture->GetHeight() * 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 9024fa8049..3407136aa7 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/win32iface.h b/src/win32/win32iface.h index e46c392c3b..62f06fa99b 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,12 +360,12 @@ private: void DrawPackedTextures(int packnum); void DrawLetterbox(); void Draw3DPart(bool copy3d); - bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedQuad &quad); + bool SetStyle(D3DTex *tex, DCanvas::DrawParms &parms, D3DCOLOR &color0, D3DCOLOR &color1, BufferedTris &quad); static D3DBLEND GetStyleAlpha(int type); static void SetColorOverlay(DWORD color, float alpha, D3DCOLOR &color0, D3DCOLOR &color1); void DoWindowedGamma(); void AddColorOnlyQuad(int left, int top, int width, int height, D3DCOLOR color); - void CheckQuadBatch(); + void CheckQuadBatch(int numtris=2, int numverts=4); void BeginQuadBatch(); void EndQuadBatch(); void BeginLineBatch(); @@ -433,7 +438,7 @@ private: FBVERTEX *VertexData; IDirect3DIndexBuffer9 *IndexBuffer; WORD *IndexData; - BufferedQuad *QuadExtra; + BufferedTris *QuadExtra; int VertexPos; int IndexPos; int QuadBatchPos; diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 883b0e3255..6a7580fbfb 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"; diff --git a/zdoom.vcproj b/zdoom.vcproj index 21247821f1..b2a3f4fe38 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -804,6 +804,10 @@ RelativePath=".\src\p_floor.cpp" > + + From a11e70bf5f8d97ec6893673fc6d668bbae1e058a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 27 Aug 2010 15:22:21 +0000 Subject: [PATCH 35/84] - changed savegame version check for automap stuff. SVN r2610 (trunk) --- src/p_saveg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 1b44426497..52ebf88d28 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -606,7 +606,7 @@ void P_SerializeSubsectors(FArchive &arc) } else { - if (SaveVersion < 2500) + if (SaveVersion < 2609) { if (hasglnodes) { From 2152bc88d604e5895d6150b8a71ace996ef8fd12 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 27 Aug 2010 16:06:10 +0000 Subject: [PATCH 36/84] - fixed CROSSHAIR_INDEX in m_options.cpp. SVN r2611 (trunk) --- src/m_options.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/m_options.cpp b/src/m_options.cpp index dae45c994c..fc5f1b7bb4 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -522,7 +522,7 @@ static menuitem_t VideoItems[] = { { discrete, "Display nametags", {&displaynametags}, {4.0}, {0.0}, {0.0}, {DisplayTagsTypes} }, }; -#define CROSSHAIR_INDEX 7 +#define CROSSHAIR_INDEX 6 menu_t VideoMenu = { From 3afa8149deaa0bbe1968ed80f56ed5095fa69534 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 27 Aug 2010 16:53:11 +0000 Subject: [PATCH 37/84] - added a fix from GZDoom to handle levels with compressed sidedefs that were processed by older ZDBSPs. SVN r2612 (trunk) --- src/p_setup.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index b0a92b2c45..3f7f91454f 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -3830,6 +3830,28 @@ void P_SetupLevel (char *lumpname, int position) DPrintf ("BSP generation took %.3f sec (%d segs)\n", (endTime - startTime) * 0.001, numsegs); reloop = true; } + else + { + // 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) From ece980d372741046cb0253777ff2ee95e15bf6cd Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 27 Aug 2010 17:34:25 +0000 Subject: [PATCH 38/84] - fixed check for GL nodebuild SVN r2613 (trunk) --- src/p_setup.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 3f7f91454f..d8e86a9cda 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -3803,8 +3803,10 @@ void P_SetupLevel (char *lumpname, int position) unsigned int startTime=0, endTime=0; + bool BuildGLNodes; if (ForceNodeBuild) { + BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes; startTime = I_FPSTime (); TArray polyspots, anchors; @@ -3819,7 +3821,6 @@ void P_SetupLevel (char *lumpname, int position) // 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. - bool BuildGLNodes = am_textured || multiplayer || demoplayback || demorecording || genglnodes; FNodeBuilder builder (leveldata, polyspots, anchors, BuildGLNodes); delete[] vertexes; builder.Extract (nodes, numnodes, @@ -3832,6 +3833,7 @@ void P_SetupLevel (char *lumpname, int position) } 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;i Date: Fri, 27 Aug 2010 17:49:27 +0000 Subject: [PATCH 39/84] - disable check for hidden sectors because it does more harm than good. - don't draw a textured automap in overlay mode. SVN r2614 (trunk) --- src/am_map.cpp | 2 +- src/p_glnodes.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index 63d328c647..fd3f4373fc 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -2353,7 +2353,7 @@ void AM_Drawer () } AM_activateNewScale(); - if (am_textured && hasglnodes && textured) + if (am_textured && hasglnodes && textured && !viewactive) AM_drawSubsectors(); if (grid) diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp index 426552c37d..f08ef55ed6 100644 --- a/src/p_glnodes.cpp +++ b/src/p_glnodes.cpp @@ -1387,6 +1387,8 @@ void P_SetRenderSector() 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++) @@ -1409,6 +1411,7 @@ void P_SetRenderSector() 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. for(i=0;i Date: Fri, 27 Aug 2010 20:03:40 +0000 Subject: [PATCH 40/84] - fixed Hexen's flame calls to A_CountdownArg. SVN r2615 (trunk) --- wadsrc/static/actors/hexen/flame.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wadsrc/static/actors/hexen/flame.txt b/wadsrc/static/actors/hexen/flame.txt index c3a222ea63..984c2d90ce 100644 --- a/wadsrc/static/actors/hexen/flame.txt +++ b/wadsrc/static/actors/hexen/flame.txt @@ -10,10 +10,10 @@ ACTOR FlameSmallTemp 10500 { Spawn: FFSM AB 3 Bright - FFSM C 2 Bright A_CountdownArg(1) + FFSM C 2 Bright A_CountdownArg(0) FFSM C 2 Bright FFSM D 3 Bright - FFSM E 3 Bright A_CountdownArg(1) + FFSM E 3 Bright A_CountdownArg(0) Loop } } @@ -31,21 +31,21 @@ ACTOR FlameLargeTemp 10502 { Spawn: FFLG A 4 Bright - FFLG B 4 Bright A_CountdownArg(1) + FFLG B 4 Bright A_CountdownArg(0) FFLG C 4 Bright - FFLG D 4 Bright A_CountdownArg(1) + FFLG D 4 Bright A_CountdownArg(0) FFLG E 4 Bright - FFLG F 4 Bright A_CountdownArg(1) + FFLG F 4 Bright A_CountdownArg(0) FFLG G 4 Bright - FFLG H 4 Bright A_CountdownArg(1) + FFLG H 4 Bright A_CountdownArg(0) FFLG I 4 Bright - FFLG J 4 Bright A_CountdownArg(1) + FFLG J 4 Bright A_CountdownArg(0) FFLG K 4 Bright - FFLG L 4 Bright A_CountdownArg(1) + FFLG L 4 Bright A_CountdownArg(0) FFLG M 4 Bright - FFLG N 4 Bright A_CountdownArg(1) + FFLG N 4 Bright A_CountdownArg(0) FFLG O 4 Bright - FFLG P 4 Bright A_CountdownArg(1) + FFLG P 4 Bright A_CountdownArg(0) Goto Spawn+4 } } From 0d5386740901d9b9152a1d09af76523ece19ffd2 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 28 Aug 2010 01:46:26 +0000 Subject: [PATCH 41/84] - Changed the default key for am_toggletexture to P so as not to conflict with the default chat key. SVN r2616 (trunk) --- src/c_bind.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c_bind.cpp b/src/c_bind.cpp index 6afb0c6775..471959f6c4 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -176,7 +176,7 @@ static const FBinding DefAutomapBindings[] = { { "f", "am_togglefollow" }, { "g", "am_togglegrid" }, - { "t", "am_toggletexture" }, + { "p", "am_toggletexture" }, { "m", "am_setmark" }, { "c", "am_clearmarks" }, { "0", "am_gobig" }, From 5a9151d7d2631182dbfd59398dc6170cc2ec649c Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 11:17:25 +0000 Subject: [PATCH 42/84] - fixed: Extraction of partner segs was broken in the internal node builder. SVN r2617 (trunk) --- src/nodebuild_extract.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nodebuild_extract.cpp b/src/nodebuild_extract.cpp index 5355179ea3..c4546bb5be 100644 --- a/src/nodebuild_extract.cpp +++ b/src/nodebuild_extract.cpp @@ -115,7 +115,15 @@ void FNodeBuilder::Extract (node_t *&outNodes, int &nodeCount, for (i = 0; i < segCount; ++i) { outSegs[i] = *(seg_t *)&segs[i]; - outSegExtras[i].PartnerSeg = segs[i].Partner; + + if (segs[i].Partner != DWORD_MAX) + { + outSegExtras[i].PartnerSeg = Segs[segs[i].Partner].storedseg; + } + else + { + outSegExtras[i].PartnerSeg = DWORD_MAX; + } } } else From 80f1a63cc9638d204f43f4f0c1ecb94a6bd50b48 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 12:00:42 +0000 Subject: [PATCH 43/84] - fixed: The textured automap node initialization code could crash on maps that only have single isolated sectors. SVN r2618 (trunk) --- src/p_glnodes.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp index f08ef55ed6..d7332def21 100644 --- a/src/p_glnodes.cpp +++ b/src/p_glnodes.cpp @@ -1414,6 +1414,19 @@ void P_SetRenderSector() #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 Date: Sat, 28 Aug 2010 12:23:20 +0000 Subject: [PATCH 44/84] - fixed: Hexen's CorpseBloodDrip was missing its death sound. SVN r2619 (trunk) --- wadsrc/static/actors/hexen/hexenspecialdecs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wadsrc/static/actors/hexen/hexenspecialdecs.txt b/wadsrc/static/actors/hexen/hexenspecialdecs.txt index 7d4a13a949..e36451e7f5 100644 --- a/wadsrc/static/actors/hexen/hexenspecialdecs.txt +++ b/wadsrc/static/actors/hexen/hexenspecialdecs.txt @@ -216,6 +216,7 @@ ACTOR CorpseBloodDrip Gravity 0.125 +MISSILE +NOICEDEATH + DeathSound "Drip" States { Spawn: From 5647fed0cff45865a57d7c4e85be58ff1fad71b1 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 12:57:23 +0000 Subject: [PATCH 45/84] - fixed: armor factor application was done wrong. - fixed: APROP_Invulnerable could only be set and unset but not checked. - fixed: Two sided polyobjects applied thrust to sctors in a way that did not work. SVN r2620 (trunk) --- src/g_shared/a_armor.cpp | 19 +++++++++++-------- src/p_acs.cpp | 2 ++ src/po_man.cpp | 12 ++++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/g_shared/a_armor.cpp b/src/g_shared/a_armor.cpp index 6bed525d83..1475f86d24 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/p_acs.cpp b/src/p_acs.cpp index bf480fe73e..5565929914 100644 --- a/src/p_acs.cpp +++ b/src/p_acs.cpp @@ -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: diff --git a/src/po_man.cpp b/src/po_man.cpp index 7619bd06af..da8399b3d0 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; } From 1dd3ecd6e2ab9ea5db9b42acaa1ad4fa8e9c49d2 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 13:36:41 +0000 Subject: [PATCH 46/84] - fixed: -nosfx deactivated the entire sound system which also made music inoperable. Changed it so that all it does is block sound effects from being started. SVN r2621 (trunk) --- src/s_sound.cpp | 2 +- src/sound/i_sound.cpp | 4 +++- src/sound/i_sound.h | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/s_sound.cpp b/src/s_sound.cpp index 40be1b927c..8d64241690 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/sound/i_sound.cpp b/src/sound/i_sound.cpp index b25bbd6fbf..0a946b3c18 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -81,6 +81,7 @@ CVAR (String, snd_output, "default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Bool, snd_pitched, false, CVAR_ARCHIVE) SoundRenderer *GSnd; +bool nosfx; void I_CloseSound (); @@ -234,7 +235,8 @@ public: void I_InitSound () { /* Get command line options: */ - bool nosound = !!Args->CheckParm ("-nosfx") || !!Args->CheckParm ("-nosound"); + bool nosound = !!Args->CheckParm ("-nosound"); + nosfx = !!Args->CheckParm ("-nosfx"); if (nosound) { diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index d8cf166fc5..e5edcbb400 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -143,6 +143,7 @@ public: }; extern SoundRenderer *GSnd; +extern bool nosfx; void I_InitSound (); void I_ShutdownSound (); From 4e1a5144551473293594550ef1ceeec306f60492 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 16:51:41 +0000 Subject: [PATCH 47/84] - fixed: Subsectors which are the origin of polyobjects should not be drawn on the textured automap because the vertices no longer are where they'd be expected to be. SVN r2622 (trunk) --- src/am_map.cpp | 5 +++++ src/po_man.cpp | 17 +++++++++++++++++ src/r_defs.h | 1 + 3 files changed, 23 insertions(+) diff --git a/src/am_map.cpp b/src/am_map.cpp index fd3f4373fc..ea5e9c3b42 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -1609,6 +1609,11 @@ void AM_drawSubsectors() 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; diff --git a/src/po_man.cpp b/src/po_man.cpp index da8399b3d0..0f9b0b15a1 100644 --- a/src/po_man.cpp +++ b/src/po_man.cpp @@ -1846,6 +1846,23 @@ void PO_Init (void) double fdy = (double)no->dy; no->len = (float)sqrt(fdx * fdx + fdy * fdy); } + + // mark all subsectors which have a seg belonging to a polyobj + // These ones should not be rendered on the textured automap. + for (int i = 0; i < numsubsectors; i++) + { + subsector_t *ss = &subsectors[i]; + for(DWORD j=0;jnumlines; j++) + { + if (ss->firstline[j].sidedef != NULL && + ss->firstline[j].sidedef->Flags & WALLF_POLYOBJ) + { + ss->flags |= SSECF_POLYORG; + break; + } + } + } + } //========================================================================== diff --git a/src/r_defs.h b/src/r_defs.h index 55b896a80d..9e0477ac27 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -962,6 +962,7 @@ enum { SSECF_DEGENERATE = 1, SSECF_DRAWN = 2, + SSECF_POLYORG = 4, }; struct subsector_t From 387bfc0260566d3c4cbcf96d0be1aab45a7a3896 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 17:19:48 +0000 Subject: [PATCH 48/84] - added 'nospriterename' key to GAMEINFO lump so that PWADs have a means to disable this feature without having to specify a command line switch. SVN r2623 (trunk) --- src/d_main.cpp | 5 +++++ src/w_wad.cpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index 20ba2c4675..19ed60437c 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -201,6 +201,7 @@ gamestate_t wipegamestate = GS_DEMOSCREEN; // can be -1 to force a wipe bool PageBlank; FTexture *Page; FTexture *Advisory; +bool nospriterename; cycle_t FrameCycles; @@ -1678,6 +1679,10 @@ static FString ParseGameInfo(TArray &pwads, const char *fn, const char } while (sc.CheckToken(',')); } + else if (!nextKey.CompareNoCase("NOSPRITERENAME")) + { + nospriterename = true; + } } return iwad; } diff --git a/src/w_wad.cpp b/src/w_wad.cpp index 5727c8bcc7..13e7ff14a2 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -70,6 +70,7 @@ struct FWadCollection::LumpRecord }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- +extern bool nospriterename; // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- @@ -759,7 +760,7 @@ void FWadCollection::RenameSprites () } } - renameAll = !!Args->CheckParm ("-oldsprites"); + renameAll = !!Args->CheckParm ("-oldsprites") || nospriterename; for (DWORD i = 0; i < LumpInfo.Size(); i++) { From 56cc0569bbbe730a326c4c356176428a3110ad90 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 17:24:17 +0000 Subject: [PATCH 49/84] - fixed: ArtiPork had wrong frame durations. SVN r2624 (trunk) --- wadsrc/static/actors/raven/artiegg.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wadsrc/static/actors/raven/artiegg.txt b/wadsrc/static/actors/raven/artiegg.txt index 4662355882..d94f001515 100644 --- a/wadsrc/static/actors/raven/artiegg.txt +++ b/wadsrc/static/actors/raven/artiegg.txt @@ -96,7 +96,7 @@ ACTOR ArtiPork : CustomInventory 30 States { Spawn: - PORK ABCDEFGH 6 + PORK ABCDEFGH 5 Loop Use: TNT1 A 0 A_FireCustomMissile("PorkFX", -15, 0, 0, 0, 1) From eb3340e8725401e162bd3938a77fd864c702c9de Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 19:20:14 +0000 Subject: [PATCH 50/84] - fixed: Clearing a pickup message for inventory items was not possible. Changed it so that "You got a pickup" is AInventory's pickup message and not a default returned when nothing valid is set. SVN r2625 (trunk) --- src/g_shared/a_pickups.cpp | 6 ++---- wadsrc/static/actors/shared/inventory.txt | 1 + wadsrc/static/language.enu | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/g_shared/a_pickups.cpp b/src/g_shared/a_pickups.cpp index 3802bd4778..30dcd482cf 100644 --- a/src/g_shared/a_pickups.cpp +++ b/src/g_shared/a_pickups.cpp @@ -890,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; @@ -960,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/wadsrc/static/actors/shared/inventory.txt b/wadsrc/static/actors/shared/inventory.txt index a61519df0a..49b6b39a24 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/language.enu b/wadsrc/static/language.enu index 6a7580fbfb..58cc870490 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -286,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" From cbff41f481da548251199db6bfacdb6406fbb6f8 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 20:22:35 +0000 Subject: [PATCH 51/84] - assws PinkSilver's LOF_NOJUMP submission for A_LookEx. SVN r2626 (trunk) --- src/p_enemy.cpp | 19 +++++++++++-------- src/p_enemy.h | 1 + 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 53d87e3dc5..01c31794ec 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -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 1d80e08e37..f996a0c52e 100644 --- a/src/p_enemy.h +++ b/src/p_enemy.h @@ -32,6 +32,7 @@ enum LO_Flags LOF_DONTCHASEGOAL = 4, LOF_NOSEESOUND = 8, LOF_FULLVOLSEESOUND = 16, + LOF_NOJUMP = 32, }; struct FLookExParams From a3c8e0c0428f093c96c01d0e2f7b6ce7a4584da4 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 28 Aug 2010 20:25:52 +0000 Subject: [PATCH 52/84] - fixed: P_LineAttack mixed up two flags variables. SVN r2627 (trunk) --- src/p_map.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_map.cpp b/src/p_map.cpp index 06483c44e8..f578ec7feb 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -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) { From f9691a24ffab4bdbcce3dfd283009e75e0f44773 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 29 Aug 2010 03:37:41 +0000 Subject: [PATCH 53/84] - Fixed: The automap was too aggressive about hiding markers. SVN r2628 (trunk) --- src/am_map.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index ea5e9c3b42..fd46c80185 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -2291,19 +2291,15 @@ void AM_drawAuthorMarkers () while (marked != NULL) { - if (mark->args[1] == 0 || (mark->args[1] == 1)) + // 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))) { - // Use more correct info if we have GL nodes available - INTBOOL drawn = hasglnodes? - marked->subsector->flags & SSECF_DRAWN : - marked->Sector->MoreFlags & SECF_DRAWN; - - if (drawn) - { - DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0, - flip, mark->scaleX, mark->scaleY, mark->Translation, - mark->alpha, mark->fillcolor, mark->RenderStyle); - } + DrawMarker (tex, marked->x >> FRACTOMAPBITS, marked->y >> FRACTOMAPBITS, 0, + flip, mark->scaleX, mark->scaleY, mark->Translation, + mark->alpha, mark->fillcolor, mark->RenderStyle); } marked = mark->args[0] != 0 ? it.Next() : NULL; } From d4b03e2b773e91ab71233c17bf18183137a587af Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 29 Aug 2010 03:52:02 +0000 Subject: [PATCH 54/84] - SVN r2629 (trunk) --- src/p_setup.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index d8e86a9cda..236a52137e 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -1094,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) { @@ -1159,7 +1167,7 @@ void P_LoadSegs (MapData * map) if (vnum1 >= numvertexes || vnum2 >= numvertexes) { - throw i * 4; + throw badseg(0, i, MAX(vnum1, vnum2)); } li->v1 = &vertexes[vnum1]; @@ -1226,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; @@ -1250,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"); From c304b39ecc656dbdcfd2014bdb8b35b761a72559 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 29 Aug 2010 12:20:35 +0000 Subject: [PATCH 55/84] - added new sector special 195 to set the 'hidden' sector flag in non-UDMF maps. SVN r2637 (trunk) --- specs/udmf_zdoom.txt | 4 ++++ src/p_lnspec.h | 5 +++-- src/p_spec.cpp | 5 +++++ wadsrc/static/xlat/defines.i | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 9f91c748e0..fe6897d9ff 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 @@ -273,6 +274,9 @@ 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/src/p_lnspec.h b/src/p_lnspec.h index f0942c4fde..050e0fe7bf 100644 --- a/src/p_lnspec.h +++ b/src/p_lnspec.h @@ -114,8 +114,9 @@ typedef enum { sDamage_SuperHellslime = 116, Scroll_StrifeCurrent = 118, - // Caverns of Darkness healing sector - Sector_Heal = 196, + + Sector_Hidden = 195, + Sector_Heal = 196, // Caverns of Darkness healing sector Light_OutdoorLightning = 197, Light_IndoorLightning1 = 198, diff --git a/src/p_spec.cpp b/src/p_spec.cpp index 915d4a508c..e37df68b1a 100644 --- a/src/p_spec.cpp +++ b/src/p_spec.cpp @@ -1104,6 +1104,11 @@ void P_SpawnSpecials (void) 0, -1, int(sector-sectors), 0); break; + case Sector_Hidden: + sector->MoreFlags |= SECF_HIDDEN; + sector->special &= 0xff00; + break; + default: if ((sector->special & 0xff) >= Scroll_North_Slow && (sector->special & 0xff) <= Scroll_SouthWest_Fast) diff --git a/wadsrc/static/xlat/defines.i b/wadsrc/static/xlat/defines.i index b72641d30e..60d582f708 100644 --- a/wadsrc/static/xlat/defines.i +++ b/wadsrc/static/xlat/defines.i @@ -145,8 +145,8 @@ enum sDamage_SuperHellslime = 116, Scroll_StrifeCurrent = 118, - // Caverns of Darkness healing sector - Sector_Heal = 196, + Sector_Hidden = 195, + Sector_Heal = 196, // Caverns of Darkness healing sector Light_OutdoorLightning = 197, Light_IndoorLightning1 = 198, From 08a40b22be6fd42bdbd00bd8dcaf0d917ae9bae7 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sun, 29 Aug 2010 17:57:10 +0000 Subject: [PATCH 56/84] - BOOM compatibility fix: Allow voodoo dolls to spawn inside narrow pits. SVN r2641 (trunk) --- src/p_user.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/p_user.cpp b/src/p_user.cpp index cf80d3819f..cea9c9b9f5 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -503,6 +503,15 @@ void APlayerPawn::Tick() void APlayerPawn::PostBeginPlay() { SetupWeaponSlots(); + + // Voodoo dolls: restore original floorz/ceilingz logic + if (player->mo != this) + { + dropoffz = floorz = Sector->floorplane.ZatPoint(x, y); + ceilingz = Sector->ceilingplane.ZatPoint(x, y); + P_FindFloorCeiling(this, true); + z = floorz; + } } //=========================================================================== From 39daeb25e3918979d832539c9eb8fa41b0dfd27a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 30 Aug 2010 15:11:11 +0000 Subject: [PATCH 57/84] - fixed: Hexen's pig was missing an A_QueueCorpse call. SVN r2644 (trunk) --- wadsrc/static/actors/hexen/pig.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wadsrc/static/actors/hexen/pig.txt b/wadsrc/static/actors/hexen/pig.txt index eaecd692e2..45b649597c 100644 --- a/wadsrc/static/actors/hexen/pig.txt +++ b/wadsrc/static/actors/hexen/pig.txt @@ -142,7 +142,7 @@ ACTOR Pig : MorphedMonster Death: PIGY E 4 A_Scream PIGY F 3 A_NoBlocking - PIGY G 4 + PIGY G 4 A_QueueCorpse PIGY H 3 PIGY IJK 4 PIGY L -1 From 399cfc4890983d7f65f2016c621910cbdb9f9a48 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Tue, 31 Aug 2010 04:35:13 +0000 Subject: [PATCH 58/84] - Fixd MinGW compilation of p_glnodes.cpp. SVN r2650 (trunk) --- src/p_glnodes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/p_glnodes.cpp b/src/p_glnodes.cpp index d7332def21..382f026b28 100644 --- a/src/p_glnodes.cpp +++ b/src/p_glnodes.cpp @@ -33,13 +33,13 @@ #include #ifdef _MSC_VER #include // for alloca() -#include #endif #ifndef _WIN32 #include #else +#include #define rmdir _rmdir @@ -1201,7 +1201,7 @@ static bool CheckCachedNodes(MapData *map) if (fread(&numlin, 4, 1, f) != 1) goto errorout; numlin = LittleLong(numlin); - if (numlin != numlines) goto errorout; + if ((int)numlin != numlines) goto errorout; if (fread(md5, 1, 16, f) != 16) goto errorout; map->GetChecksum(md5map); @@ -1445,7 +1445,7 @@ void P_SetRenderSector() for(i=0;i Date: Tue, 31 Aug 2010 21:24:03 +0000 Subject: [PATCH 59/84] - added kgsws-cz's FBF_NOFLASH submission. SVN r2655 (trunk) --- src/thingdef/thingdef_codeptr.cpp | 3 ++- wadsrc/static/actors/constants.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index aacb45b18f..b304e638fe 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/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 05e77df2ac..d6fdd42de0 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; From 230178bf98949803de8072fe2f646be74b07a060 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Tue, 31 Aug 2010 21:36:30 +0000 Subject: [PATCH 60/84] - Added drawshadow flag to drawstring. SVN r2656 (trunk) --- src/g_shared/sbarinfo_commands.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index 8339a35927..e4413faf0b 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; From 4b817cfd8b28148594cf30a8cb7a18fa8ef2c352 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 1 Sep 2010 03:22:43 +0000 Subject: [PATCH 61/84] - Fix an ICE when compiling with GCC 4.5.0. SVN r2659 (trunk) --- src/win32/st_start.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/win32/st_start.cpp b/src/win32/st_start.cpp index 8fe844f700..1f0838ef22 100644 --- a/src/win32/st_start.cpp +++ b/src/win32/st_start.cpp @@ -656,23 +656,23 @@ FHexenStartupScreen::FHexenStartupScreen(int max_progress, HRESULT &hr) { RGBQUAD color; DWORD quad; - }; + } c; Wads.ReadLump (startup_lump, startup_screen); - color.rgbReserved = 0; + c.color.rgbReserved = 0; StartupBitmap = ST_Util_CreateBitmap (640, 480, 4); // Initialize the bitmap palette. for (int i = 0; i < 16; ++i) { - color.rgbRed = startup_screen[i*3+0]; - color.rgbGreen = startup_screen[i*3+1]; - color.rgbBlue = startup_screen[i*3+2]; + c.color.rgbRed = startup_screen[i*3+0]; + c.color.rgbGreen = startup_screen[i*3+1]; + c.color.rgbBlue = startup_screen[i*3+2]; // Convert from 6-bit per component to 8-bit per component. - quad = (quad << 2) | ((quad >> 4) & 0x03030303); - StartupBitmap->bmiColors[i] = color; + c.quad = (c.quad << 2) | ((c.quad >> 4) & 0x03030303); + StartupBitmap->bmiColors[i] = c.color; } // Fill in the bitmap data. Convert to chunky, because I can't figure out From a2573e4bb30dd9f01510a3630a67dd7c46cb7e2c Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 1 Sep 2010 03:30:18 +0000 Subject: [PATCH 62/84] - Fixes to compile with GCC 4.5.0. SVN r2660 (trunk) --- src/basicinlines.h | 4 +-- src/g_raven/a_minotaur.cpp | 4 +-- src/g_shared/a_fastprojectile.cpp | 2 +- src/p_enemy.cpp | 4 +-- src/p_mobj.cpp | 2 +- src/p_user.cpp | 2 +- src/textures/pngtexture.cpp | 8 ++--- src/win32/i_crash.cpp | 50 ++++++++++++++++++++++++++++++- src/win32/i_rawps2.cpp | 5 ++-- 9 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/basicinlines.h b/src/basicinlines.h index fa6ce822b3..80f861c22d 100644 --- a/src/basicinlines.h +++ b/src/basicinlines.h @@ -11,8 +11,8 @@ #pragma once #endif -#ifndef _MSC_VER -#define __forceinline inline +#if defined(__GNUC__) && !defined(__forceinline) +#define __forceinline __inline__ __attribute__((always_inline)) #endif static __forceinline SDWORD Scale (SDWORD a, SDWORD b, SDWORD c) diff --git a/src/g_raven/a_minotaur.cpp b/src/g_raven/a_minotaur.cpp index a614d3b41f..eb54441470 100644 --- a/src/g_raven/a_minotaur.cpp +++ b/src/g_raven/a_minotaur.cpp @@ -448,9 +448,9 @@ DEFINE_ACTION_FUNCTION(AActor, A_MinotaurRoam) { // Turn if (pr_minotaurroam() & 1) - self->movedir = (++self->movedir)%8; + self->movedir = (self->movedir + 1) % 8; else - self->movedir = (self->movedir+7)%8; + self->movedir = (self->movedir + 7) % 8; FaceMovementDirection (self); } } diff --git a/src/g_shared/a_fastprojectile.cpp b/src/g_shared/a_fastprojectile.cpp index 161323b2b4..d45a9f2a82 100644 --- a/src/g_shared/a_fastprojectile.cpp +++ b/src/g_shared/a_fastprojectile.cpp @@ -72,7 +72,7 @@ void AFastProjectile::Tick () tm.LastRipped = NULL; // [RH] Do rip damage each step, like Hexen } - if (!P_TryMove (this, x + xfrac,y + yfrac, true, false, tm)) + if (!P_TryMove (this, x + xfrac,y + yfrac, true, NULL, tm)) { // Blocked move if (!(flags3 & MF3_SKYEXPLODE)) { diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index 01c31794ec..1cf28aa5d9 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -512,12 +512,12 @@ bool P_Move (AActor *actor) try_ok = true; for(int i=1; i < steps; i++) { - try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), dropoff, false, tm); + try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), dropoff, NULL, tm); if (!try_ok) break; } // killough 3/15/98: don't jump over dropoffs: - if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, dropoff, false, tm); + if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, dropoff, NULL, tm); // [GrafZahl] Interpolating monster movement as it is done here just looks bad // so make it switchable diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 98f0801bc6..4d6cdc765a 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -4963,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_user.cpp b/src/p_user.cpp index cea9c9b9f5..a00843d841 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -1975,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/textures/pngtexture.cpp b/src/textures/pngtexture.cpp index d1fe3e8f54..4e30802c42 100644 --- a/src/textures/pngtexture.cpp +++ b/src/textures/pngtexture.cpp @@ -202,7 +202,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename { DWORD palette[256]; BYTE pngpal[256][3]; - }; + } p; BYTE trans[256]; bool havetRNS = false; DWORD len, id; @@ -260,14 +260,14 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case MAKE_ID('P','L','T','E'): PaletteSize = MIN (len / 3, 256); - lump.Read (pngpal, PaletteSize * 3); + lump.Read (p.pngpal, PaletteSize * 3); if (PaletteSize * 3 != (int)len) { lump.Seek (len - PaletteSize * 3, SEEK_CUR); } for (i = PaletteSize - 1; i >= 0; --i) { - palette[i] = MAKERGB(pngpal[i][0], pngpal[i][1], pngpal[i][2]); + p.palette[i] = MAKERGB(p.pngpal[i][0], p.pngpal[i][1], p.pngpal[i][2]); } break; @@ -314,7 +314,7 @@ FPNGTexture::FPNGTexture (FileReader &lump, int lumpnum, const FString &filename case 3: // Paletted PaletteMap = new BYTE[PaletteSize]; - GPalette.MakeRemap (palette, PaletteMap, trans, PaletteSize); + GPalette.MakeRemap (p.palette, PaletteMap, trans, PaletteSize); for (i = 0; i < PaletteSize; ++i) { if (trans[i] == 0) diff --git a/src/win32/i_crash.cpp b/src/win32/i_crash.cpp index 727e4b7edf..5e4dfb1c9f 100644 --- a/src/win32/i_crash.cpp +++ b/src/win32/i_crash.cpp @@ -69,6 +69,54 @@ #include #include +#if defined(_WIN64) && defined(__GNUC__) +struct KNONVOLATILE_CONTEXT_POINTERS { + union { + PDWORD64 IntegerContext[16]; + struct { + PDWORD64 Rax; + PDWORD64 Rcx; + PDWORD64 Rdx; + PDWORD64 Rbx; + PDWORD64 Rsp; + PDWORD64 Rbp; + PDWORD64 Rsi; + PDWORD64 Rdi; + PDWORD64 R8; + PDWORD64 R9; + PDWORD64 R10; + PDWORD64 R11; + PDWORD64 R12; + PDWORD64 R13; + PDWORD64 R14; + PDWORD64 R15; + }; + }; +}; +typedef +EXCEPTION_DISPOSITION +NTAPI +EXCEPTION_ROUTINE ( + struct _EXCEPTION_RECORD *ExceptionRecord, + PVOID EstablisherFrame, + struct _CONTEXT *ContextRecord, + PVOID DispatcherContext + ); +NTSYSAPI +EXCEPTION_ROUTINE * +NTAPI +RtlVirtualUnwind ( + DWORD HandlerType, + DWORD64 ImageBase, + DWORD64 ControlPc, + PRUNTIME_FUNCTION FunctionEntry, + PCONTEXT ContextRecord, + PVOID *HandlerData, + PDWORD64 EstablisherFrame, + KNONVOLATILE_CONTEXT_POINTERS *ContextPointers + ); +#endif + // MACROS ------------------------------------------------------------------ #define REMOTE_HOST "localhost" @@ -1312,7 +1360,7 @@ static void StackWalk (HANDLE file, void *dumpaddress, DWORD *topOfStack, DWORD } // If we reach a RIP of zero, this means we've walked off the end of // the call stack and are done. - if (context.Rip == NULL) + if (context.Rip == 0) { break; } diff --git a/src/win32/i_rawps2.cpp b/src/win32/i_rawps2.cpp index 68803fefb0..d060b869d1 100644 --- a/src/win32/i_rawps2.cpp +++ b/src/win32/i_rawps2.cpp @@ -388,8 +388,9 @@ FRawPS2Controller::~FRawPS2Controller() bool FRawPS2Controller::ProcessInput(RAWHID *raw, int code) { - // w32api has an incompatible definition of bRawData -#if __GNUC__ + // w32api has an incompatible definition of bRawData. + // (But the version that comes with MinGW64 is fine.) +#if defined(__GNUC__) && !defined(_WIN64) BYTE *rawdata = &raw->bRawData; #else BYTE *rawdata = raw->bRawData; From c25f206fbbe0305efb1eb64fa2f377f9c898b7e7 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 1 Sep 2010 04:13:36 +0000 Subject: [PATCH 63/84] - Disable framebuffer debug spew. All those OutputDebugString messages from DCanvas::DrawLine()'s Lock and Unlock calls were slowing things down extremely when looking at a software-drawn automap. SVN r2661 (trunk) --- src/win32/win32iface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h index 62f06fa99b..6193c641c2 100644 --- a/src/win32/win32iface.h +++ b/src/win32/win32iface.h @@ -504,7 +504,7 @@ enum #define LOG4(x,y,z,a,b) do { if (dbg) { fprintf (dbg, x, y, z, a, b); fflush (dbg); } } while(0) #define LOG5(x,y,z,a,b,c) do { if (dbg) { fprintf (dbg, x, y, z, a, b, c); fflush (dbg); } } while(0) FILE *dbg; -#elif _DEBUG +#elif _DEBUG && 0 #define STARTLOG #define STOPLOG #define LOG(x) { OutputDebugString(x); } From 7bf0cd13a6fd3803a2f21681ffea2fdaef1664bb Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 1 Sep 2010 04:24:56 +0000 Subject: [PATCH 64/84] - Fixed: The mouse pointer stayed hidden on startup because the CursorState variable was not set until the mouse was grabbed. SVN r2662 (trunk) --- src/win32/i_mouse.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/win32/i_mouse.cpp b/src/win32/i_mouse.cpp index af595dea52..d347a4d72a 100644 --- a/src/win32/i_mouse.cpp +++ b/src/win32/i_mouse.cpp @@ -485,6 +485,7 @@ static FMouse *CreateRawMouse() FRawMouse::FRawMouse() { Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -678,6 +679,7 @@ FDInputMouse::FDInputMouse() { Device = NULL; Grabbed = false; + SetCursorState(true); } //========================================================================== @@ -903,6 +905,7 @@ FWin32Mouse::FWin32Mouse() { GetCursorPos(&UngrabbedPointerPos); Grabbed = false; + SetCursorState(true); } //========================================================================== From 0202c0c3a98a4c50b2b40cf48d50110ce1def4cc Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Wed, 1 Sep 2010 05:03:17 +0000 Subject: [PATCH 65/84] - Added the am_zoom command to zoom the automap by a specific step and set default mouse wheel bindings for it. I'm not sure how this should be exposed through the menu, however. Technically, it's different from the pan keys, but from an end user's point of view, they both zoom the automap, so they should both be listed under the Zoom in and out controls. But the menu code can't handle that. SVN r2663 (trunk) --- src/am_map.cpp | 33 ++++++++++++++++++++++++++++----- src/c_bind.cpp | 2 ++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index fd46c80185..0a0dafd66e 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -246,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*/) @@ -413,6 +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 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) @@ -1258,8 +1259,23 @@ void AM_changeWindowScale () { int mtof_zoommul; - if (Button_AM_ZoomIn.bDown) mtof_zoommul = M_ZOOMIN; - else if (Button_AM_ZoomOut.bDown) mtof_zoommul = M_ZOOMOUT; + 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); @@ -1271,6 +1287,13 @@ void AM_changeWindowScale () AM_maxOutWindowScale(); } +CCMD(am_zoom) +{ + if (argv.argc() >= 2) + { + am_zoomdir = (float)atof(argv[1]); + } +} //============================================================================= // @@ -1332,7 +1355,7 @@ void AM_Ticker () } // Change the zoom if necessary - if (Button_AM_ZoomIn.bDown || Button_AM_ZoomOut.bDown) + if (Button_AM_ZoomIn.bDown || Button_AM_ZoomOut.bDown || am_zoomdir != 0) AM_changeWindowScale(); // Change x,y location diff --git a/src/c_bind.cpp b/src/c_bind.cpp index 471959f6c4..b9489ca65d 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -188,6 +188,8 @@ static const FBinding DefAutomapBindings[] = { "=", "+am_zoomin" }, { "kp-", "+am_zoomout" }, { "kp+", "+am_zoomin" }, + { "mwheelup", "am_zoom 1.2" }, + { "mwheeldown", "am_zoom -1.2" }, { NULL } }; From 241e09c2717a925fc8aa1c4303961b13b9b7dff4 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Wed, 1 Sep 2010 22:26:07 +0000 Subject: [PATCH 66/84] - Fixed: font monospacing didn't apply to the space character. SVN r2666 (trunk) --- src/g_shared/sbarinfo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 83f9e9bef7..29ea9774b1 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -1349,7 +1349,10 @@ public: { if(*str == ' ') { - ax += font->GetSpaceWidth(); + if(script->spacingCharacter == '\0') + ax += font->GetSpaceWidth(); + else + ax += font->GetCharWidth((int) script->spacingCharacter); str++; continue; } From 77ca7f7a87117fd3b74d2c91afe14427b22d6e95 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 2 Sep 2010 22:33:34 +0000 Subject: [PATCH 67/84] - set 'setslopeoverflow' compatibility flag for all maps in Massmouth2. SVN r2674 (trunk) --- wadsrc/static/compatibility.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index 09f38a80b6..98528363da 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -21,6 +21,7 @@ A80E7EE40E0D0C76A6FBD242BE29FE27 // map15 2F1F8E27FBB5EF21AFBE1F3B13C03037 // map16 1CE294781A2455DE72C197E0B3DF6212 // map31 { + setslopeoverflow resetplayerspeed } From 81e21b0688b43a91becd0cad079bab331f587913 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Thu, 2 Sep 2010 23:17:58 +0000 Subject: [PATCH 68/84] - Renamed music_midi_midiout.cpp to music_smf_midiout.cpp. - Moved MIDI precaching logic into MIDIStreamer so that SMF and HMI files can both use the same implementation. - Added a player for HMI midi files. SVN r2675 (trunk) --- src/CMakeLists.txt | 5 +- src/sound/i_music.cpp | 30 +- src/sound/i_musicinterns.h | 62 +- src/sound/music_hmi_midiout.cpp | 882 ++++++++++++++++++ src/sound/music_midistream.cpp | 106 ++- ...midi_midiout.cpp => music_smf_midiout.cpp} | 167 +--- zdoom.vcproj | 8 +- 7 files changed, 1106 insertions(+), 154 deletions(-) create mode 100644 src/sound/music_hmi_midiout.cpp rename src/sound/{music_midi_midiout.cpp => music_smf_midiout.cpp} (80%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6906bec5b0..18f13abd6f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -812,11 +812,12 @@ 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 diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index eb68d290e4..717f98eaa5 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -489,10 +489,36 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int } #endif // _WIN32 } - // Check for MIDI format else { - if (id[0] == MAKE_ID('M','T','h','d')) + // Check for HMI format + if (id[0] == MAKE_ID('H','M','I','-') && + id[1] == MAKE_ID('M','I','D','I') && + id[2] == MAKE_ID('S','O','N','G')) + { + if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL) + { + info = new HMISong(file, musiccache, len, MIDI_OPL); + } + else if (snd_mididevice == -4 && device == MDEV_DEFAULT) + { + info = new HMISong(file, musiccache, len, MIDI_Timidity); + } +#ifdef HAVE_FLUIDSYNTH + else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) + { + info = new HMISong(file, musiccache, len, MIDI_Fluid); + } +#endif +#ifdef _WIN32 + else + { + info = new HMISong(file, musiccache, len, MIDI_Win); + } +#endif + } + // Check for MIDI format + else if (id[0] == MAKE_ID('M','T','h','d')) { // This is a midi file diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 334002963f..6bfe9c2f41 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -373,7 +373,7 @@ protected: 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 @@ -413,6 +413,7 @@ protected: DWORD Volume; EMIDIDevice DeviceType; bool CallbackIsThreaded; + bool IgnoreLoops; FString DumpFilename; }; @@ -460,7 +461,6 @@ protected: void DoInitialSetup(); void DoRestart(); bool CheckDone(); - void Precache(); DWORD *MakeEvents(DWORD *events, DWORD *max_events_p, DWORD max_time); void AdvanceTracks(DWORD time); @@ -480,6 +480,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(); + 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/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp new file mode 100644 index 0000000000..078e98c944 --- /dev/null +++ b/src/sound/music_hmi_midiout.cpp @@ -0,0 +1,882 @@ +/* +** 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 = MIDI->GetTechnology(); + + // 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_Timidity); +} + +//========================================================================== +// +// 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_midistream.cpp b/src/sound/music_midistream.cpp index d9449a657f..ddba36006e 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -242,6 +242,7 @@ void MIDIStreamer::Play(bool looping, int subsong) CheckCaps(); Precache(); + IgnoreLoops = true; // Set time division and tempo. if (0 != MIDI->SetTimeDiv(Division) || @@ -734,7 +735,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. // @@ -829,6 +830,109 @@ 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 them 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 :: GetStats diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_smf_midiout.cpp similarity index 80% rename from src/sound/music_midi_midiout.cpp rename to src/sound/music_smf_midiout.cpp index 00d816c663..2607de3414 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 -------------------------------------------------------------------- //========================================================================== @@ -389,10 +389,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 +411,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 +500,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 +532,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 +715,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 +782,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 diff --git a/zdoom.vcproj b/zdoom.vcproj index b2a3f4fe38..c61cfa9d72 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -5462,11 +5462,11 @@ > + + From 092cbfd55b46dfd83201b142b48cb0bacc4c8a29 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 3 Sep 2010 02:11:35 +0000 Subject: [PATCH 69/84] - Fixed: When the game nodes were the same as the render nodes, their pointers would not be NULLed. SVN r2676 (trunk) --- src/p_setup.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 236a52137e..02329bdb96 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -3370,17 +3370,13 @@ void P_FreeLevelData () sectors = NULL; numsectors = 0; // needed for the pointer cleanup code } - if (gamenodes && gamenodes!=nodes) + if (gamenodes != NULL && gamenodes != nodes) { - delete [] gamenodes; - gamenodes = NULL; - numgamenodes = 0; + delete[] gamenodes; } - if (gamesubsectors && gamesubsectors!=subsectors) + if (gamesubsectors != NULL && gamesubsectors != subsectors) { - delete [] gamesubsectors; - gamesubsectors = NULL; - numgamesubsectors = 0; + delete[] gamesubsectors; } if (subsectors != NULL) { @@ -3392,13 +3388,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; From 070ec7578548d97e61d9754572ce36107cd75cab Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Fri, 3 Sep 2010 05:08:05 +0000 Subject: [PATCH 70/84] - Cleaned up the ugly MIDI song creating code a little. - Added a generic Standard MIDI File creator that works with any of the sequencers. mus2midi.cpp is no longer used but is kept around as a reference. SVN r2677 (trunk) --- src/CMakeLists.txt | 1 - src/mus2midi.cpp | 4 +- src/mus2midi.h | 3 - src/sound/i_music.cpp | 306 +++++++++++++++--------------- src/sound/i_musicinterns.h | 16 +- src/sound/music_hmi_midiout.cpp | 6 +- src/sound/music_midi_timidity.cpp | 16 +- src/sound/music_midistream.cpp | 169 ++++++++++++++++- src/sound/music_mus_midiout.cpp | 6 +- src/sound/music_smf_midiout.cpp | 6 +- src/tarray.h | 9 + zdoom.vcproj | 4 - 12 files changed, 344 insertions(+), 202 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 18f13abd6f..16a658a10b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -661,7 +661,6 @@ add_executable( zdoom WIN32 m_png.cpp m_random.cpp md5.cpp - mus2midi.cpp name.cpp nodebuild.cpp nodebuild_classify_nosse2.cpp diff --git a/src/mus2midi.cpp b/src/mus2midi.cpp index 63e59e755a..2157b4a89a 100644 --- a/src/mus2midi.cpp +++ b/src/mus2midi.cpp @@ -195,9 +195,9 @@ bool ProduceMIDI (const BYTE *musBuf, int len, TArray &outFile) switch (event & 0x70) { case MUS_NOTEOFF: - midStatus |= MIDI_NOTEOFF; + midStatus |= MIDI_NOTEON; mid1 = t & 127; - mid2 = 64; + mid2 = 0; break; case MUS_NOTEON: diff --git a/src/mus2midi.h b/src/mus2midi.h index 6dc75dc039..f1d8469274 100644 --- a/src/mus2midi.h +++ b/src/mus2midi.h @@ -75,7 +75,4 @@ typedef struct // WORD UsedInstruments[NumInstruments]; } MUSHeader; -bool ProduceMIDI (const BYTE *musBuf, int len, TArray &outFile); -bool ProduceMIDI (const BYTE *musBuf, int len, FILE *outFile); - #endif //__MUS2MIDI_H__ diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index 717f98eaa5..172553df5d 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -3,7 +3,7 @@ ** Plays music ** **--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit +** Copyright 1998-2010 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -84,6 +84,14 @@ extern void ChildSigHandler (int signum); #define GZIP_FNAME 8 #define GZIP_FCOMMENT 16 +enum EMIDIType +{ + MIDI_NOTMIDI, + MIDI_MIDI, + MIDI_HMI, + MIDI_MUS +}; + extern int MUSHeaderSearch(const BYTE *head, int len); EXTERN_CVAR (Int, snd_samplerate) @@ -305,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; @@ -405,191 +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)) { - info = new MUSSong2(file, musiccache, len, MIDI_Fluid); + 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 } - 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 { - // Check for HMI format - if (id[0] == MAKE_ID('H','M','I','-') && - id[1] == MAKE_ID('M','I','D','I') && - id[2] == MAKE_ID('S','O','N','G')) - { - if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL) - { - info = new HMISong(file, musiccache, len, MIDI_OPL); - } - else if (snd_mididevice == -4 && device == MDEV_DEFAULT) - { - info = new HMISong(file, musiccache, len, MIDI_Timidity); - } -#ifdef HAVE_FLUIDSYNTH - else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) - { - info = new HMISong(file, musiccache, len, MIDI_Fluid); - } -#endif -#ifdef _WIN32 - else - { - info = new HMISong(file, musiccache, len, MIDI_Win); - } -#endif - } - // Check for MIDI format - else 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); - } -#ifdef HAVE_FLUIDSYNTH - else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT)) - { - info = new MIDISong2(file, musiccache, len, MIDI_Fluid); - } -#endif - 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) diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index 6bfe9c2f41..8c65071674 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -333,8 +333,13 @@ enum EMIDIDevice { MIDI_Win, MIDI_OPL, - MIDI_Timidity, - MIDI_Fluid + MIDI_GUS, + MIDI_Fluid, + + // only used by I_RegisterSong + MIDI_Null, + MIDI_FMOD, + MIDI_Timidity }; class MIDIStreamer : public MusInfo @@ -357,6 +362,7 @@ public: 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); @@ -369,7 +375,7 @@ 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; @@ -457,7 +463,7 @@ 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(); @@ -494,7 +500,7 @@ public: protected: HMISong(const HMISong *original, const char *filename, EMIDIDevice type); // file dump constructor - void CheckCaps(); + void CheckCaps(int tech); void DoInitialSetup(); void DoRestart(); bool CheckDone(); diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index 078e98c944..c73c9f6197 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -273,10 +273,8 @@ HMISong::~HMISong () // //========================================================================== -void HMISong::CheckCaps() +void HMISong::CheckCaps(int tech) { - int tech = MIDI->GetTechnology(); - // What's the equivalent HMI device for our technology? if (tech == MOD_FMSYNTH) { @@ -851,7 +849,7 @@ MusInfo *HMISong::GetOPLDumper(const char *filename) MusInfo *HMISong::GetWaveDumper(const char *filename, int rate) { - return new HMISong(this, filename, MIDI_Timidity); + return new HMISong(this, filename, MIDI_GUS); } //========================================================================== diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp index ea725d0da0..6e2acb32d3 100644 --- a/src/sound/music_midi_timidity.cpp +++ b/src/sound/music_midi_timidity.cpp @@ -207,7 +207,7 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) BYTE *buf; - if (file!=NULL) + if (file != NULL) { buf = new BYTE[len]; fread (buf, 1, len, file); @@ -217,18 +217,8 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len) buf = musiccache; } - - // The file type has already been checked before this class instance was - // created, so we only need to check one character to determine if this - // is a MUS or MIDI file and write it to disk as appropriate. - if (buf[1] == 'T') - { - success = (fwrite (buf, 1, len, f) == (size_t)len); - } - else - { - success = ProduceMIDI (buf, len, f); - } + // Write to temporary file + success = (fwrite (buf, 1, len, f) == (size_t)len); fclose (f); if (file != NULL) { diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index ddba36006e..085c996e25 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); } @@ -221,13 +236,17 @@ void MIDIStreamer::Play(bool looping, int subsong) break; #endif - case MIDI_Timidity: + case MIDI_GUS: MIDI = new TimidityMIDIDevice; break; case MIDI_OPL: MIDI = new OPLMIDIDevice; break; + + default: + MIDI = NULL; + break; } #ifndef _WIN32 @@ -240,9 +259,9 @@ void MIDIStreamer::Play(bool looping, int subsong) return; } - CheckCaps(); + CheckCaps(MIDI->GetTechnology()); Precache(); - IgnoreLoops = true; + IgnoreLoops = false; // Set time division and tempo. if (0 != MIDI->SetTimeDiv(Division) || @@ -515,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; } //========================================================================== @@ -834,9 +853,9 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time) // // MIDIStreamer :: Precache // -// Generates a list of instruments this song uses them 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 +// 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. // //========================================================================== @@ -933,6 +952,138 @@ void MIDIStreamer::Precache() 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(); + + 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 diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp index b89d9fd67d..44a0d45e6e 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_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp index 2607de3414..0f01be75b4 100644 --- a/src/sound/music_smf_midiout.cpp +++ b/src/sound/music_smf_midiout.cpp @@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 () // //========================================================================== -void MIDISong2::CheckCaps() +void MIDISong2::CheckCaps(int tech) { - int tech = MIDI->GetTechnology(); - DesignationMask = 0xFF0F; if (tech == MOD_FMSYNTH) { @@ -801,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/tarray.h b/src/tarray.h index 9ee5eff2b4..3eab7be33f 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/zdoom.vcproj b/zdoom.vcproj index c61cfa9d72..4df675804d 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -712,10 +712,6 @@ RelativePath=".\src\md5.cpp" > - - From 84b9de8c14bbe7563eb8174e0c31d2bcf94445e4 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 4 Sep 2010 02:57:36 +0000 Subject: [PATCH 71/84] - Make sure Tempo is initialized before creating SMFs. SVN r2684 (trunk) --- src/sound/music_midistream.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp index 085c996e25..454ff0d3f1 100644 --- a/src/sound/music_midistream.cpp +++ b/src/sound/music_midistream.cpp @@ -969,6 +969,7 @@ void MIDIStreamer::CreateSMF(TArray &file) CheckCaps(MOD_MIDIPORT); IgnoreLoops = true; DoRestart(); + Tempo = InitialTempo; file.Reserve(sizeof(StaticMIDIhead)); memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead)); From 61d438e1eb1c7ab4ddcfb2e47af088bd28d799ca Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 4 Sep 2010 03:02:13 +0000 Subject: [PATCH 72/84] - Don't call M_NotifyNewSave() before closing the new savegame. - Disallow negative read lengths in FileReader::Read(). SVN r2685 (trunk) --- src/files.cpp | 2 ++ src/g_game.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/files.cpp b/src/files.cpp index 5705b938a3..332bddb1fa 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -132,6 +132,8 @@ long FileReader::Seek (long offset, int origin) long FileReader::Read (void *buffer, long len) { + assert(len >= 0); + if (len <= 0) return 0; if (FilePos + len > StartPos + Length) { len = Length - FilePos + StartPos; diff --git a/src/g_game.cpp b/src/g_game.cpp index f85601c6e1..39a424097f 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -2060,11 +2060,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"); From 3dbf807345fc23ea256c7b5feaea43efcd9d1dd9 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Sat, 4 Sep 2010 18:21:51 +0000 Subject: [PATCH 73/84] - Applied Chris's patch to fix hmi compilation error on Linux. SVN r2689 (trunk) --- src/sound/music_hmi_midiout.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp index c73c9f6197..c2075bf1a8 100644 --- a/src/sound/music_hmi_midiout.cpp +++ b/src/sound/music_hmi_midiout.cpp @@ -717,7 +717,7 @@ void HMISong::NoteOffQueue::AddNoteOff(DWORD delay, BYTE channel, BYTE key) bool HMISong::NoteOffQueue::Pop(AutoNoteOff &item) { item = (*this)[0]; - if (TArray::Pop((*this)[0])) + if (TArray::Pop((*this)[0])) { Heapify(); return true; From e9211aaad32e2f025ed9ee3cd691cb697381852c Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 5 Sep 2010 20:51:13 +0000 Subject: [PATCH 74/84] - added some NULL pointer checks to the kill CCMD and APlayerPawn::PostBeginPlay. SVN r2697 (trunk) --- src/d_net.cpp | 8 ++++++-- src/p_user.cpp | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/d_net.cpp b/src/d_net.cpp index be592b238a..73d800b51f 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2368,7 +2368,7 @@ void Net_DoCommand (int type, BYTE **stream, int player) int killcount = 0; const PClass *cls = PClass::FindClass(classname); - if (classname != NULL) + if (cls != NULL && cls->ActorInfo != NULL) { killcount = KillAll(cls); const PClass *cls_rep = cls->GetReplacement(); @@ -2376,9 +2376,13 @@ void Net_DoCommand (int type, BYTE **stream, int player) { 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/p_user.cpp b/src/p_user.cpp index a00843d841..eef4f5025f 100644 --- a/src/p_user.cpp +++ b/src/p_user.cpp @@ -505,7 +505,7 @@ void APlayerPawn::PostBeginPlay() SetupWeaponSlots(); // Voodoo dolls: restore original floorz/ceilingz logic - if (player->mo != this) + if (player == NULL || player->mo != this) { dropoffz = floorz = Sector->floorplane.ZatPoint(x, y); ceilingz = Sector->ceilingplane.ZatPoint(x, y); From 31754a582d11aaa77ab9af5808cf8ac85b2caef0 Mon Sep 17 00:00:00 2001 From: Braden Obrzut Date: Mon, 6 Sep 2010 20:12:44 +0000 Subject: [PATCH 75/84] - Fixed: when using the border property of drawbar, interpolation didn't work quite right. SVN r2705 (trunk) --- src/g_shared/sbarinfo.cpp | 2 +- src/g_shared/sbarinfo_commands.cpp | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/g_shared/sbarinfo.cpp b/src/g_shared/sbarinfo.cpp index 29ea9774b1..eed9d86c79 100644 --- a/src/g_shared/sbarinfo.cpp +++ b/src/g_shared/sbarinfo.cpp @@ -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) diff --git a/src/g_shared/sbarinfo_commands.cpp b/src/g_shared/sbarinfo_commands.cpp index e4413faf0b..e66d23f79e 100644 --- a/src/g_shared/sbarinfo_commands.cpp +++ b/src/g_shared/sbarinfo_commands.cpp @@ -2084,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()); } @@ -2103,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)) From ce2c2bd825a0783154a4c345f96a5398e492d384 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 7 Sep 2010 20:23:44 +0000 Subject: [PATCH 76/84] - replaced AM_Rotate with a more precise floating point version posted by Entryway. SVN r2713 (trunk) --- src/am_map.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index 0a0dafd66e..f3f7d7a390 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -1938,14 +1938,19 @@ 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; + double x = FIXED2FLOAT(*xp); + double y = FIXED2FLOAT(*yp); + double rot = (double)a / (double)(1u << 31) * (double)M_PI; + double sinrot = sin(rot); + double cosrot = cos(rot); - a >>= ANGLETOFINESHIFT; - tmpx = DMulScale16 (*x,finecosine[a],*y,-finesine[a]); - *y = DMulScale16 (*x,finesine[a],*y,finecosine[a]); - *x = tmpx; + double tmpx = (x * cosrot) - (y * sinrot); + y = (x * sinrot) + (y * cosrot); + x = tmpx; + *xp = FLOAT2FIXED(x); + *yp = FLOAT2FIXED(y); } //============================================================================= From 610ff3956e658c4ac3defc9131aeed80aa5afc16 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 8 Sep 2010 08:53:39 +0000 Subject: [PATCH 77/84] - fixed P_LoopSideDefs could crash on the second P_LoopSidedefs call if there were unconnected linedefs in the map. SVN r2716 (trunk) --- src/p_setup.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 02329bdb96..09d2d2e8db 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -2202,9 +2202,10 @@ static void P_LoopSidedefs (bool firstloop) right = sidetemp[right].b.first; - if (firstloop && right == NO_SIDE) - { // There is no right side! - Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); + if (right == NO_SIDE) + { + // There is no right side! + if (firstloop) Printf ("Line %d's right edge is unconnected\n", linemap[unsigned(line-lines)]); continue; } From ec0b07b5e2e2eef417c10bfec3a1cdfad581d06f Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 10 Sep 2010 13:49:08 +0000 Subject: [PATCH 78/84] - added Entryway's AM_Rotate optimization. SVN r2739 (trunk) --- src/am_map.cpp | 16 ++++++++++++---- src/win32/fb_d3d9.cpp | 4 ++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/am_map.cpp b/src/am_map.cpp index f3f7d7a390..73a7f8c989 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -1940,12 +1940,20 @@ void AM_drawWalls (bool allmap) void AM_rotate(fixed_t *xp, fixed_t *yp, angle_t a) { + static angle_t angle_saved = 0; + static double sinrot = 0; + static double cosrot = 1; + + 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 rot = (double)a / (double)(1u << 31) * (double)M_PI; - double sinrot = sin(rot); - double cosrot = cos(rot); - double tmpx = (x * cosrot) - (y * sinrot); y = (x * sinrot) + (y * cosrot); x = tmpx; diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp index 020be2de1a..7453571e5d 100644 --- a/src/win32/fb_d3d9.cpp +++ b/src/win32/fb_d3d9.cpp @@ -3311,8 +3311,8 @@ void D3DFB::FillSimplePoly(FTexture *texture, FVector2 *points, int npoints, quad->NumTris = npoints - 2; yoffs = GatheringWipeScreen ? 0 : LBOffset; - uscale = float(1.f / (texture->GetWidth() * scalex)); - vscale = float(1.f / (texture->GetHeight() * scaley)); + uscale = float(1.f / (texture->GetScaledWidth() * scalex)); + vscale = float(1.f / (texture->GetScaledHeight() * scaley)); ox = float(originx); oy = float(originy); From 579502ab74a9b7db95f0840b50da1e0832619d5a Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 14 Sep 2010 17:28:18 +0000 Subject: [PATCH 79/84] - merged menu branch back into trunk. SVN r2768 (trunk) --- src/CMakeLists.txt | 15 +- src/c_bind.cpp | 4 +- src/c_bind.h | 19 +- src/c_cmds.cpp | 1 + src/c_cvars.cpp | 110 +- src/c_cvars.h | 24 + src/d_gui.h | 39 +- src/d_iwad.cpp | 2 +- src/d_main.cpp | 20 +- src/d_net.cpp | 2 +- src/d_player.h | 1 + src/dobjgc.cpp | 2 + src/g_game.cpp | 15 +- src/g_level.cpp | 12 +- src/g_level.h | 12 + src/g_mapinfo.cpp | 75 +- src/gi.cpp | 15 +- src/gi.h | 10 + src/keysections.cpp | 146 + src/m_joy.h | 1 + src/m_menu.cpp | 4152 ------------------ src/m_menu.h | 280 -- src/m_misc.cpp | 3 - src/m_misc.h | 1 + src/m_options.cpp | 3958 ----------------- src/menu/colorpickermenu.cpp | 357 ++ src/menu/joystickmenu.cpp | 420 ++ src/menu/listmenu.cpp | 511 +++ src/menu/loadsavemenu.cpp | 1165 +++++ src/menu/menu.cpp | 949 ++++ src/menu/menu.h | 652 +++ src/menu/menudef.cpp | 1389 ++++++ src/menu/menuinput.cpp | 366 ++ src/menu/messagebox.cpp | 733 ++++ src/menu/optionmenu.cpp | 591 +++ src/menu/optionmenuitems.h | 916 ++++ src/menu/playerdisplay.cpp | 561 +++ src/menu/playermenu.cpp | 1142 +++++ src/menu/readthis.cpp | 154 + src/menu/videomenu.cpp | 442 ++ src/namedef.h | 61 + src/p_conversation.cpp | 688 +-- src/p_conversation.h | 2 - src/r_draw.cpp | 4 +- src/sdl/i_input.cpp | 8 + src/sdl/i_input.h | 2 + src/sdl/i_system.cpp | 5 + src/sdl/i_system.h | 2 + src/sound/i_music.h | 3 +- src/sound/music_midi_base.cpp | 118 +- src/textures/bitmap.h | 1 + src/textures/multipatchtexture.cpp | 29 +- src/thingdef/thingdef_properties.cpp | 9 + src/v_draw.cpp | 33 + src/v_font.cpp | 9 + src/v_palette.cpp | 15 + src/v_video.cpp | 2 +- src/v_video.h | 2 + src/win32/i_dijoy.cpp | 4 - src/win32/i_input.cpp | 99 +- src/win32/i_input.h | 3 + src/win32/i_mouse.cpp | 26 +- src/win32/i_rawps2.cpp | 1 - src/win32/i_system.cpp | 217 + src/win32/i_system.h | 4 + src/win32/i_xinput.cpp | 1 - src/win32/win32iface.h | 1 + wadsrc/static/actors/hexen/clericplayer.txt | 1 + wadsrc/static/actors/hexen/fighterplayer.txt | 1 + wadsrc/static/actors/hexen/mageplayer.txt | 1 + wadsrc/static/animdefs.txt | 79 + wadsrc/static/graphics/cursor.png | Bin 0 -> 264 bytes wadsrc/static/graphics/m_back_d.png | Bin 0 -> 204 bytes wadsrc/static/graphics/m_back_h.png | Bin 0 -> 213 bytes wadsrc/static/graphics/m_back_s.png | Bin 0 -> 235 bytes wadsrc/static/graphics/m_back_x.png | Bin 0 -> 169 bytes wadsrc/static/language.enu | 2 + wadsrc/static/mapinfo/chex.txt | 9 + wadsrc/static/mapinfo/doom1.txt | 1 + wadsrc/static/mapinfo/doomcommon.txt | 11 + wadsrc/static/mapinfo/heretic.txt | 9 + wadsrc/static/mapinfo/hexen.txt | 9 + wadsrc/static/mapinfo/strife.txt | 9 + wadsrc/static/menudef.txt | 1333 ++++++ wadsrc/static/textures.txt | 72 + zdoom.vcproj | 826 ++-- 86 files changed, 13680 insertions(+), 9299 deletions(-) create mode 100644 src/keysections.cpp delete mode 100644 src/m_menu.cpp create mode 100644 src/menu/colorpickermenu.cpp create mode 100644 src/menu/joystickmenu.cpp create mode 100644 src/menu/listmenu.cpp create mode 100644 src/menu/loadsavemenu.cpp create mode 100644 src/menu/menu.cpp create mode 100644 src/menu/menu.h create mode 100644 src/menu/menudef.cpp create mode 100644 src/menu/menuinput.cpp create mode 100644 src/menu/messagebox.cpp create mode 100644 src/menu/optionmenu.cpp create mode 100644 src/menu/optionmenuitems.h create mode 100644 src/menu/playerdisplay.cpp create mode 100644 src/menu/playermenu.cpp create mode 100644 src/menu/readthis.cpp create mode 100644 src/menu/videomenu.cpp create mode 100644 wadsrc/static/graphics/cursor.png create mode 100644 wadsrc/static/graphics/m_back_d.png create mode 100644 wadsrc/static/graphics/m_back_h.png create mode 100644 wadsrc/static/graphics/m_back_s.png create mode 100644 wadsrc/static/graphics/m_back_x.png create mode 100644 wadsrc/static/menudef.txt create mode 100644 wadsrc/static/textures.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16a658a10b..b811e8a594 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -649,13 +649,13 @@ 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 @@ -788,6 +788,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 diff --git a/src/c_bind.cpp b/src/c_bind.cpp index b9489ca65d..cceb8bd352 100644 --- a/src/c_bind.cpp +++ b/src/c_bind.cpp @@ -583,7 +583,7 @@ void FKeyBindings::ArchiveBindings(FConfigFile *f, const char *matchcmd) // //============================================================================= -int FKeyBindings::GetKeysForCommand (char *cmd, int *first, int *second) +int FKeyBindings::GetKeysForCommand (const char *cmd, int *first, int *second) { int c, i; @@ -609,7 +609,7 @@ int FKeyBindings::GetKeysForCommand (char *cmd, int *first, int *second) // //============================================================================= -void FKeyBindings::UnbindACommand (char *str) +void FKeyBindings::UnbindACommand (const char *str) { int i; diff --git a/src/c_bind.h b/src/c_bind.h index 7d71b462a3..4c9edb2bb4 100644 --- a/src/c_bind.h +++ b/src/c_bind.h @@ -57,8 +57,8 @@ public: void SetBinds(const FBinding *binds); bool DoKey(event_t *ev); void ArchiveBindings(FConfigFile *F, const char *matchcmd = NULL); - int GetKeysForCommand (char *cmd, int *first, int *second); - void UnbindACommand (char *str); + 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); @@ -85,6 +85,7 @@ public: extern FKeyBindings Bindings; extern FKeyBindings DoubleBindings; extern FKeyBindings AutomapBindings; +extern FKeyBindings MenuBindings; bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds); @@ -95,4 +96,18 @@ void C_UnbindAll (); extern const char *KeyNames[]; +struct FKeyAction +{ + FString mTitle; + FString mAction; +}; + +struct FKeySection +{ + FString mTitle; + FString mSection; + TArray mActions; +}; +extern TArray KeySections; + #endif //__C_BINDINGS_H__ diff --git a/src/c_cmds.cpp b/src/c_cmds.cpp index 6d21a79225..f3cb409587 100644 --- a/src/c_cmds.cpp +++ b/src/c_cmds.cpp @@ -936,3 +936,4 @@ CCMD(currentpos) FIXED2FLOAT(mo->x), FIXED2FLOAT(mo->y), FIXED2FLOAT(mo->z), mo->angle/float(ANGLE_1), FIXED2FLOAT(mo->floorz), mo->Sector->sectornum, mo->Sector->lightlevel); } + diff --git a/src/c_cvars.cpp b/src/c_cvars.cpp index d4e01128d9..7b53876354 100644 --- a/src/c_cvars.cpp +++ b/src/c_cvars.cpp @@ -1119,7 +1119,7 @@ void FFlagCVar::DoSet (UCVarValue value, ECVarType type) // exec scripts because all flags will base their changes off of the value of // the "master" cvar at the time the script was run, overriding any changes // another flag might have made to the same cvar earlier in the script. - if ((ValueVar.Flags & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) + if ((ValueVar.GetFlags() & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) { if (netgame && !players[consoleplayer].settings_controller) { @@ -1139,6 +1139,114 @@ void FFlagCVar::DoSet (UCVarValue value, ECVarType type) } } +// +// Mask cvar implementation +// +// Similar to FFlagCVar but can have multiple bits +// + +FMaskCVar::FMaskCVar (const char *name, FIntCVar &realvar, DWORD bitval) +: FBaseCVar (name, 0, NULL), +ValueVar (realvar), +BitVal (bitval) +{ + int bit; + + Flags &= ~CVAR_ISDEFAULT; + + assert (bitval != 0); + + bit = 0; + while ((bitval & 1) == 0) + { + ++bit; + bitval >>= 1; + } + BitNum = bit; +} + +ECVarType FMaskCVar::GetRealType () const +{ + return CVAR_Dummy; +} + +UCVarValue FMaskCVar::GetGenericRep (ECVarType type) const +{ + return FromInt ((ValueVar & BitVal) >> BitNum, type); +} + +UCVarValue FMaskCVar::GetFavoriteRep (ECVarType *type) const +{ + UCVarValue ret; + *type = CVAR_Int; + ret.Int = (ValueVar & BitVal) >> BitNum; + return ret; +} + +UCVarValue FMaskCVar::GetGenericRepDefault (ECVarType type) const +{ + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + return FromInt ((def.Int & BitVal) >> BitNum, type); +} + +UCVarValue FMaskCVar::GetFavoriteRepDefault (ECVarType *type) const +{ + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + def.Int = (def.Int & BitVal) >> BitNum; + *type = CVAR_Int; + return def; +} + +void FMaskCVar::SetGenericRepDefault (UCVarValue value, ECVarType type) +{ + int val = ToInt(value, type) << BitNum; + ECVarType dummy; + UCVarValue def; + def = ValueVar.GetFavoriteRepDefault (&dummy); + def.Int &= ~BitVal; + def.Int |= val; + ValueVar.SetGenericRepDefault (def, CVAR_Int); +} + +void FMaskCVar::DoSet (UCVarValue value, ECVarType type) +{ + int val = ToInt(value, type) << BitNum; + + // Server cvars that get changed by this need to use a special message, because + // changes are not processed until the next net update. This is a problem with + // exec scripts because all flags will base their changes off of the value of + // the "master" cvar at the time the script was run, overriding any changes + // another flag might have made to the same cvar earlier in the script. + if ((ValueVar.GetFlags() & CVAR_SERVERINFO) && gamestate != GS_STARTUP && !demoplayback) + { + if (netgame && !players[consoleplayer].settings_controller) + { + Printf ("Only setting controllers can change %s\n", Name); + return; + } + // Ugh... + for(int i = 0; i < 32; i++) + { + if (BitVal & (1<> BitNum; } + inline int operator *() const { return (ValueVar & BitVal) >> BitNum; } + +protected: + virtual void DoSet (UCVarValue value, ECVarType type); + + FIntCVar &ValueVar; + uint32 BitVal; + int BitNum; +}; + class FGUIDCVar : public FBaseCVar { public: diff --git a/src/d_gui.h b/src/d_gui.h index 5e9854f7cd..b88041771b 100644 --- a/src/d_gui.h +++ b/src/d_gui.h @@ -43,27 +43,34 @@ enum EGUIEvent EV_GUI_KeyRepeat, // same EV_GUI_KeyUp, // same EV_GUI_Char, // data1: translated character (for user text input), data2: alt down? - EV_GUI_MouseMove, - EV_GUI_LButtonDown, - EV_GUI_LButtonUp, - EV_GUI_LButtonDblClick, - EV_GUI_MButtonDown, - EV_GUI_MButtonUp, - EV_GUI_MButtonDblClick, - EV_GUI_RButtonDown, - EV_GUI_RButtonUp, - EV_GUI_RButtonDblClick, - EV_GUI_WheelUp, // data3: shift/ctrl/alt - EV_GUI_WheelDown, // " - EV_GUI_WheelRight, // " - EV_GUI_WheelLeft, // " + EV_GUI_FirstMouseEvent, + EV_GUI_MouseMove, + EV_GUI_LButtonDown, + EV_GUI_LButtonUp, + EV_GUI_LButtonDblClick, + EV_GUI_MButtonDown, + EV_GUI_MButtonUp, + EV_GUI_MButtonDblClick, + EV_GUI_RButtonDown, + EV_GUI_RButtonUp, + EV_GUI_RButtonDblClick, + EV_GUI_WheelUp, // data3: shift/ctrl/alt + EV_GUI_WheelDown, // " + EV_GUI_WheelRight, // " + EV_GUI_WheelLeft, // " + EV_GUI_BackButtonDown, + EV_GUI_BackButtonUp, + EV_GUI_FwdButtonDown, + EV_GUI_FwdButtonUp, + EV_GUI_LastMouseEvent, }; enum GUIKeyModifiers { GKM_SHIFT = 1, GKM_CTRL = 2, - GKM_ALT = 4 + GKM_ALT = 4, + GKM_LBUTTON = 8 }; // Special codes for some GUI keys, including a few real ASCII codes. @@ -100,7 +107,7 @@ enum ESpecialGUIKeys GK_ESCAPE = 27, // ASCII GK_FREE1 = 28, GK_FREE2 = 29, - GK_FREE3 = 30, + GK_BACK = 30, // browser back key GK_CESCAPE = 31 // color escape }; diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 9a49c533b2..499d7cbd7a 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -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 19ed60437c..9dc18f473d 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" @@ -392,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); //========================================================================== // @@ -462,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 @@ -888,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 @@ -2077,6 +2090,7 @@ void D_DoomMain (void) // [GRB] Initialize player class list SetupPlayerClasses (); + // [RH] Load custom key and weapon settings from WADs D_LoadWadSettings (); @@ -2146,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"); diff --git a/src/d_net.cpp b/src/d_net.cpp index 73d800b51f..e088997198 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -25,7 +25,7 @@ #include #include "version.h" -#include "m_menu.h" +#include "menu/menu.h" #include "m_random.h" #include "i_system.h" #include "i_video.h" diff --git a/src/d_player.h b/src/d_player.h index 28b240c105..2d58e00ee4 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -54,6 +54,7 @@ enum APMETA_ColorRange, // skin color range APMETA_InvulMode, APMETA_HealingRadius, + APMETA_Portrait, APMETA_Hexenarmor0, APMETA_Hexenarmor1, APMETA_Hexenarmor2, diff --git a/src/dobjgc.cpp b/src/dobjgc.cpp index 4d7589e9bb..4314d0488a 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/g_game.cpp b/src/g_game.cpp index 39a424097f..caf43cd6f0 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -40,7 +40,7 @@ #include "f_finale.h" #include "m_argv.h" #include "m_misc.h" -#include "m_menu.h" +#include "menu/menu.h" #include "m_random.h" #include "m_crc32.h" #include "i_system.h" @@ -128,6 +128,8 @@ CUSTOM_CVAR (Int, displaynametags, 0, CVAR_ARCHIVE) } } +CVAR(Int, nametagcolor, CR_GOLD, CVAR_ARCHIVE) + gameaction_t gameaction; gamestate_t gamestate = GS_STARTUP; @@ -326,7 +328,7 @@ CCMD (weapnext) if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) { StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), - 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + 1.5f, 0.90f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); } } @@ -337,7 +339,7 @@ CCMD (weapprev) if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse) { StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(), - 1.5f, 0.90f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); + 1.5f, 0.90f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' )); } } @@ -368,7 +370,7 @@ CCMD (invnext) } if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), - 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); + 1.5f, 0.80f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -398,7 +400,7 @@ CCMD (invprev) } if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel) StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(), - 1.5f, 0.80f, 0, 0, CR_GOLD, 2.f, 0.35f), MAKE_ID('S','I','N','V')); + 1.5f, 0.80f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V')); } who->player->inventorytics = 5*TICRATE; } @@ -897,7 +899,8 @@ 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 diff --git a/src/g_level.cpp b/src/g_level.cpp index 8f475d3f31..10ed1f83e9 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -68,7 +68,6 @@ #include "m_png.h" #include "m_random.h" #include "version.h" -#include "m_menu.h" #include "statnums.h" #include "sbarinfo.h" #include "r_translate.h" @@ -78,6 +77,7 @@ #include "d_net.h" #include "d_netinf.h" #include "v_palette.h" +#include "menu/menu.h" #include "gi.h" @@ -99,6 +99,7 @@ void STAT_WRITE(FILE *f); EXTERN_CVAR (Float, sv_gravity) EXTERN_CVAR (Float, sv_aircontrol) EXTERN_CVAR (Int, disableautosave) +EXTERN_CVAR (String, playerclass) #define SNAP_ID MAKE_ID('s','n','A','p') #define DSNP_ID MAKE_ID('d','s','N','p') @@ -230,6 +231,15 @@ void G_DeferedInitNew (const char *mapname, int newskill) gameaction = ga_newgame2; } +void G_DeferedInitNew (FGameStartup *gs) +{ + playerclass = gs->PlayerClass; + d_mapname = AllEpisodes[gs->Episode].mEpisodeMap; + d_skill = gs->Skill; + CheckWarpTransMap (d_mapname, true); + gameaction = ga_newgame2; +} + //========================================================================== // // diff --git a/src/g_level.h b/src/g_level.h index f316491d5a..cf7fd2d482 100644 --- a/src/g_level.h +++ b/src/g_level.h @@ -486,6 +486,8 @@ void G_InitNew (const char *mapname, bool bTitleLevel); // A normal game starts at map 1, // but a warp test can start elsewhere void G_DeferedInitNew (const char *mapname, int skill = -1); +struct FGameStartup; +void G_DeferedInitNew (FGameStartup *gs); void G_ExitLevel (int position, bool keepFacing); void G_SecretExitLevel (int position); @@ -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 9378db768d..6b3faa78a5 100644 --- a/src/g_mapinfo.cpp +++ b/src/g_mapinfo.cpp @@ -38,7 +38,6 @@ #include "g_level.h" #include "sc_man.h" #include "w_wad.h" -#include "m_menu.h" #include "cmdlib.h" #include "v_video.h" #include "p_lnspec.h" @@ -63,6 +62,8 @@ TArray wadlevelinfos; level_info_t TheDefaultLevelInfo; static cluster_info_t TheDefaultClusterInfo; +TArray AllEpisodes; + //========================================================================== // // @@ -1655,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; @@ -1697,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")) { @@ -1751,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; } @@ -1762,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; } } @@ -1818,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(); } //========================================================================== @@ -2004,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/gi.cpp b/src/gi.cpp index 03fea70a93..190d2b0311 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -103,12 +103,11 @@ const char* GameInfoBoarders[] = do \ { \ sc.MustGetToken(TK_StringConst); \ - if(strlen(sc.String) > length) \ + if(length > 0 && strlen(sc.String) > length) \ { \ sc.ScriptError("Value for '%s' can not be longer than %d characters.", #key, length); \ } \ - FName val = sc.String; \ - gameinfo.key.Push(val); \ + gameinfo.key[gameinfo.key.Reserve(1)] = sc.String; \ } \ while (sc.CheckToken(',')); \ } @@ -283,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 cbb1a02bbf..ef9777553c 100644 --- a/src/gi.h +++ b/src/gi.h @@ -46,6 +46,7 @@ #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" @@ -109,6 +110,15 @@ struct gameinfo_t int defaultdropstyle; int player5start; DWORD pickupcolor; + TArray quitmessages; + FName mTitleColor; + FName mFontColor; + FName mFontColorValue; + FName mFontColorMore; + FName mFontColorHeader; + FName mFontColorHighlight; + FName mFontColorSelection; + char mBackButton[9]; const char *GetFinalePage(unsigned int num) const; }; diff --git a/src/keysections.cpp b/src/keysections.cpp new file mode 100644 index 0000000000..3da17fb380 --- /dev/null +++ b/src/keysections.cpp @@ -0,0 +1,146 @@ +/* +** keysections.cpp +** Custom key bindings +** +**--------------------------------------------------------------------------- +** Copyright 1998-2009 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +#include "menu/menu.h" +#include "g_level.h" +#include "d_player.h" +#include "gi.h" +#include "c_bind.h" +#include "c_dispatch.h" +#include "gameconfigfile.h" + +TArray KeySections; + +static void LoadKeys (const char *modname, bool dbl) +{ + char section[64]; + + if (GameNames[gameinfo.gametype] == NULL) + return; + + mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname, + dbl ? ".Double" : "."); + + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; + if (GameConfig->SetSection (section)) + { + const char *key, *value; + while (GameConfig->NextInSection (key, value)) + { + bindings->DoBind (key, value); + } + } +} + +static void DoSaveKeys (FConfigFile *config, const char *section, FKeySection *keysection, bool dbl) +{ + config->SetSection (section, true); + config->ClearCurrentSection (); + FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; + for (unsigned i = 0; i < keysection->mActions.Size(); ++i) + { + bindings->ArchiveBindings (config, keysection->mActions[i].mAction); + } +} + +void M_SaveCustomKeys (FConfigFile *config, char *section, char *subsection, size_t sublen) +{ + for (unsigned i=0; i \n"); + return; + } + + // Limit the ini name to 32 chars + if (strlen (argv[2]) > 32) + argv[2][32] = 0; + + for (unsigned i = 0; i < KeySections.Size(); i++) + { + if (KeySections[i].mTitle.CompareNoCase(argv[2] == 0)) + { + CurrentKeySection = i; + return; + } + } + + CurrentKeySection = KeySections.Reserve(1); + KeySections[CurrentKeySection].mTitle = argv[1]; + KeySections[CurrentKeySection].mSection = argv[2]; + // Load bindings for this section from the ini + LoadKeys (argv[2], 0); + LoadKeys (argv[2], 1); + } +} + +CCMD (addmenukey) +{ + if (ParsingKeyConf) + { + if (argv.argc() != 3) + { + Printf ("Usage: addmenukey \n"); + return; + } + if (CurrentKeySection == -1 || CurrentKeySection >= (int)KeySections.Size()) + { + Printf ("You must use addkeysection first.\n"); + return; + } + + FKeySection *sect = &KeySections[CurrentKeySection]; + + FKeyAction *act = §->mActions[sect->mActions.Reserve(1)]; + act->mTitle = argv[1]; + act->mAction = argv[2]; + } +} + diff --git a/src/m_joy.h b/src/m_joy.h index 2c939f679f..00537dee8a 100644 --- a/src/m_joy.h +++ b/src/m_joy.h @@ -60,5 +60,6 @@ double Joy_RemoveDeadZone(double axisval, double deadzone, BYTE *buttons); void I_GetAxes(float axes[NUM_JOYAXIS]); void I_GetJoysticks(TArray &sticks); IJoystickConfig *I_UpdateDeviceList(); +extern void UpdateJoystickMenu(IJoystickConfig *); #endif diff --git a/src/m_menu.cpp b/src/m_menu.cpp deleted file mode 100644 index b3e28b88e1..0000000000 --- a/src/m_menu.cpp +++ /dev/null @@ -1,4152 +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_CVAR(Float, snd_menuvolume) - -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", snd_menuvolume, ATTN_NONE); - M_QuickSave(); -} - -CCMD (quickload) -{ // F9 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); - M_QuickLoad(); -} - -CCMD (menu_endgame) -{ // F7 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); - M_EndGame(0); -} - -CCMD (menu_quit) -{ // F10 - //M_StartControlPanel (true); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - } -} - -void M_QuickSave () -{ - 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; - - 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", snd_menuvolume, 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", snd_menuvolume, 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, snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - } - else - { - currentMenu->menuitems[itemOn].routine(itemOn); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - } - else - { - M_ClearMenus (); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, 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 index 5653b694a5..e69de29bb2 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -1,280 +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, - mapcontrol, -} 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 324095d5c8..d070fdd524 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -85,8 +85,6 @@ CVAR(String, screenshot_type, "png", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); CVAR(String, screenshot_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG); EXTERN_CVAR(Bool, longsavemessages); -extern void FreeKeySections(); - static long ParseCommandLine (const char *args, int *argc, char **argv); // @@ -420,7 +418,6 @@ void M_LoadDefaults () { GameConfig = new FGameConfigFile; GameConfig->DoGlobalSetup (); - atterm (FreeKeySections); atterm (M_SaveDefaultsFinal); } diff --git a/src/m_misc.h b/src/m_misc.h index 1a7b17a965..23905d7653 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -43,6 +43,7 @@ void M_LoadDefaults (); bool M_SaveDefaults (const char *filename); void M_SaveCustomKeys (FConfigFile *config, char *section, char *subsection, size_t sublen); + // Prepends ~/.zdoom to path FString GetUserFile (const char *path); diff --git a/src/m_options.cpp b/src/m_options.cpp index fc5f1b7bb4..e69de29bb2 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -1,3958 +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(Int, displaynametags) -EXTERN_CVAR (Int, snd_channels) - -// -// defaulted values -// -CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) - -// Show messages has default, 0 = off, 1 = on -CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -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); - -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 StartAutomapMenu (void); -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, "Automap Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartAutomapMenu} }, - { more, "Sound Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SoundOptions} }, - { more, "Display Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)VideoOptions} }, - { more, "Set video mode", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)SetVidMode} }, - { 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 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 value_t DisplayTagsTypes[] = { - { 0.0, "None" }, - { 1.0, "Items" }, - { 2.0, "Weapons" }, - { 3.0, "Both" } -}; - -static menuitem_t VideoItems[] = { - { more, "Message Options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)StartMessagesMenu} }, - { more, "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}, {4.0}, {0.0}, {0.0}, {DisplayTagsTypes} }, -}; - -#define CROSSHAIR_INDEX 6 - -menu_t VideoMenu = -{ - "DISPLAY OPTIONS", - 0, - countof(VideoItems), - 0, - VideoItems, -}; - -/*======================================= - * - * Automap Menu - * - *=======================================*/ -static void StartMapColorsMenu (void); -static void StartMapControlsMenu (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) -EXTERN_CVAR (Bool, am_textured) - -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} }, - { more, "Customize map controls", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t*)StartMapControlsMenu} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Rotate automap", {&am_rotate}, {3.0}, {0.0}, {0.0}, {RotateTypes} }, - { discrete, "Overlay automap", {&am_overlay}, {3.0}, {0.0}, {0.0}, {OverlayTypes} }, - { discrete, "Enable textured display", {&am_textured}, {3.0}, {0.0}, {0.0}, {OnOff} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Show item counts", {&am_showitems}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { discrete, "Show monster counts", {&am_showmonsters}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { 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, -}; - -menuitem_t MapControlsItems[] = -{ - { redtext,"ENTER to change, BACKSPACE to clear", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"Map Controls", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { mapcontrol, "Pan left", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panleft"} }, - { mapcontrol, "Pan right", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panright"} }, - { mapcontrol, "Pan up", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_panup"} }, - { mapcontrol, "Pan down", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_pandown"} }, - { mapcontrol, "Zoom in", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomin"} }, - { mapcontrol, "Zoom out", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"+am_zoomout"} }, - { mapcontrol, "Toggle zoom", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_gobig"} }, - { mapcontrol, "Toggle follow", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglefollow"} }, - { mapcontrol, "Toggle grid", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_togglegrid"} }, - { mapcontrol, "Toggle texture", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_toggletexture"} }, - { mapcontrol, "Set mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_setmark"} }, - { mapcontrol, "Clear mark", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)"am_clearmarks"} }, -}; - -menu_t MapControlsMenu = -{ - "CUSTOMIZE MAP CONTROLS", - 3, - countof(MapControlsItems), - 0, - MapControlsItems, - 2, -}; - - - -/*======================================= - * - * Map Colors Menu - * - *=======================================*/ -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} }, - { bitflag, "Draw polyobjects like Hexen", {&compatflags}, {0}, {0}, {0}, {(value_t *)COMPATF_POLYOBJ} }, - - { discrete, "Interpolate monster movement", {&nomonsterinterpolation}, {2.0}, {0.0}, {0.0}, {NoYes} }, -}; - -static menu_t CompatibilityMenu = -{ - "COMPATIBILITY OPTIONS", - 0, - countof(CompatibilityItems), - 0, - CompatibilityItems, -}; - -/*======================================= - * - * Sound Options Menu - * - *=======================================*/ - -#ifdef _WIN32 -EXTERN_CVAR (Float, snd_movievolume) -#endif -EXTERN_CVAR (Bool, snd_flipstereo) -EXTERN_CVAR (Bool, snd_pitched) -EXTERN_CVAR (String, snd_output_format) -EXTERN_CVAR (String, snd_speakermode) -EXTERN_CVAR (String, snd_resampler) -EXTERN_CVAR (String, snd_output) -EXTERN_CVAR (Int, snd_buffersize) -EXTERN_CVAR (Int, snd_buffercount) -EXTERN_CVAR (Int, snd_samplerate) -EXTERN_CVAR (Bool, snd_hrtf) -EXTERN_CVAR (Bool, snd_waterreverb) -EXTERN_CVAR (Float, snd_waterlp) -EXTERN_CVAR (Int, snd_mididevice) - -static void MakeSoundChanges (); -static void AdvSoundOptions (); -static void ModReplayerOptions (); - -static value_t SampleRates[] = -{ - { 0.f, "Default" }, - { 4000.f, "4000 Hz" }, - { 8000.f, "8000 Hz" }, - { 11025.f, "11025 Hz" }, - { 22050.f, "22050 Hz" }, - { 32000.f, "32000 Hz" }, - { 44100.f, "44100 Hz" }, - { 48000.f, "48000 Hz" } -}; - -static value_t BufferSizes[] = -{ - { 0.f, "Default" }, - { 64.f, "64 samples" }, - { 128.f, "128 samples" }, - { 256.f, "256 samples" }, - { 512.f, "512 samples" }, - { 1024.f, "1024 samples" }, - { 2048.f, "2048 samples" }, - { 4096.f, "4096 samples" } -}; - -static value_t BufferCounts[] = -{ - { 0.f, "Default" }, - { 2.f, "2" }, - { 3.f, "3" }, - { 4.f, "4" }, - { 5.f, "5" }, - { 6.f, "6" }, - { 7.f, "7" }, - { 8.f, "8" }, - { 9.f, "9" }, - { 10.f, "10" }, - { 11.f, "11" }, - { 12.f, "12" } -}; - -static valueenum_t Outputs[] = -{ - { "Default", "Default" }, -#if defined(_WIN32) - { "DirectSound", "DirectSound" }, - { "WASAPI", "Vista WASAPI" }, - { "ASIO", "ASIO" }, - { "WaveOut", "WaveOut" }, - { "OpenAL", "OpenAL (very beta)" }, -#elif defined(unix) - { "OSS", "OSS" }, - { "ALSA", "ALSA" }, - { "SDL", "SDL" }, - { "ESD", "ESD" }, -#elif defined(__APPLE__) - { "Sound Manager", "Sound Manager" }, - { "Core Audio", "Core Audio" }, -#endif - { "No sound", "No sound" } -}; - -static valueenum_t OutputFormats[] = -{ - { "PCM-8", "8-bit" }, - { "PCM-16", "16-bit" }, - { "PCM-24", "24-bit" }, - { "PCM-32", "32-bit" }, - { "PCM-Float", "32-bit float" } -}; - -static valueenum_t SpeakerModes[] = -{ - { "Auto", "Auto" }, - { "Mono", "Mono" }, - { "Stereo", "Stereo" }, - { "Prologic", "Dolby Prologic Decoder" }, - { "Quad", "Quad" }, - { "Surround", "5 speakers" }, - { "5.1", "5.1 speakers" }, - { "7.1", "7.1 speakers" } -}; - -static valueenum_t Resamplers[] = -{ - { "NoInterp", "No interpolation" }, - { "Linear", "Linear" }, - { "Cubic", "Cubic" }, - { "Spline", "Spline" } -}; - -static menuitem_t SoundItems[] = -{ - { slider, "Sounds volume", {&snd_sfxvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, - { slider, "Menu volume", {&snd_menuvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, - { slider, "Music volume", {&snd_musicvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, - { discrete, "MIDI device", {&snd_mididevice}, {0.0}, {0.0}, {0.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Underwater reverb", {&snd_waterreverb}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Underwater cutoff", {&snd_waterlp}, {0.0}, {2000.0},{50.0}, {NULL} }, - { discrete, "Randomize pitches", {&snd_pitched}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Sound channels", {&snd_channels}, {8.0}, {256.0}, {8.0}, {NULL} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { more, "Restart sound", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)MakeSoundChanges} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { ediscrete,"Output system", {&snd_output}, {countof(Outputs)}, {0.0}, {0.0}, {(value_t *)Outputs} }, - { ediscrete,"Output format", {&snd_output_format}, {5.0}, {0.0}, {0.0}, {(value_t *)OutputFormats} }, - { ediscrete,"Speaker mode", {&snd_speakermode}, {8.0}, {0.0}, {0.0}, {(value_t *)SpeakerModes} }, - { ediscrete,"Resampler", {&snd_resampler}, {4.0}, {0.0}, {0.0}, {(value_t *)Resamplers} }, - { discrete, "HRTF filter", {&snd_hrtf}, {2.0}, {0.0}, {0.0}, {(value_t *)OnOff} }, - - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { more, "Advanced options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)AdvSoundOptions} }, - { more, "Module replayer options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)ModReplayerOptions} }, -}; - -static menu_t SoundMenu = -{ - "SOUND OPTIONS", - 0, - countof(SoundItems), - 0, - SoundItems, -}; - -#define MIDI_DEVICE_ITEM 3 - -/*======================================= - * - * Advanced Sound Options Menu - * - *=======================================*/ - -EXTERN_CVAR (Bool, opl_onechip) - -static menuitem_t AdvSoundItems[] = -{ - { discrete, "Sample rate", {&snd_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, - { discrete, "Buffer count", {&snd_buffercount}, {12.0}, {0.0}, {0.0}, {BufferCounts} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} }, -}; - -static menu_t AdvSoundMenu = -{ - "ADVANCED SOUND OPTIONS", - 0, - countof(AdvSoundItems), - 0, - AdvSoundItems, -}; - -/*======================================= - * - * Module Replayer Options Menu - * - *=======================================*/ - -EXTERN_CVAR(Bool, mod_dumb) -EXTERN_CVAR(Int, mod_samplerate) -EXTERN_CVAR(Int, mod_volramp) -EXTERN_CVAR(Int, mod_interp) -EXTERN_CVAR(Bool, mod_autochip) -EXTERN_CVAR(Int, mod_autochip_size_force) -EXTERN_CVAR(Int, mod_autochip_size_scan) -EXTERN_CVAR(Int, mod_autochip_scan_threshold) - -static value_t ModReplayers[] = -{ - { 0.0, "FMOD" }, - { 1.0, "foo_dumb" } -}; - -static value_t ModInterpolations[] = -{ - { 0.0, "None" }, - { 1.0, "Linear" }, - { 2.0, "Cubic" } -}; - -static value_t ModVolumeRamps[] = -{ - { 0.0, "None" }, - { 1.0, "Logarithmic" }, - { 2.0, "Linear" }, - { 3.0, "XM=lin, else none" }, - { 4.0, "XM=lin, else log" } -}; - -static menuitem_t ModReplayerItems[] = -{ - { discrete, "Replayer engine", {&mod_dumb}, {2.0}, {0.0}, {0.0}, {ModReplayers} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Sample rate", {&mod_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Interpolation", {&mod_interp}, {3.0}, {0.0}, {0.0}, {ModInterpolations} }, - { discrete, "Volume ramping", {&mod_volramp}, {5.0}, {0.0}, {0.0}, {ModVolumeRamps} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { discrete, "Chip-o-matic", {&mod_autochip}, {2.0}, {0.0}, {0.0}, {OnOff} }, - // TODO if the menu system is ever rewritten: Provide a decent - // mechanism to edit the chip-o-matic settings like you can with - // the foo_dumb preferences in foobar2000. -}; - -static menu_t ModReplayerMenu = -{ - "MODULE REPLAYER OPTIONS", - 0, - countof(ModReplayerItems), - 0, - ModReplayerItems, -}; - - -//=========================================================================== -static void ActivateConfirm (const char *text, void (*func)()) -{ - ConfirmItems[0].label = text; - ConfirmItems[0].e.mfunc = func; - ConfirmMenu.lastOn = 3; - M_SwitchMenu (&ConfirmMenu); -} - -static void ConfirmIsAGo () -{ - M_PopMenuStack (); - ConfirmItems[0].e.mfunc (); -} - -// -// Set some stuff up for the video modes menu -// -static BYTE BitTranslate[32]; - -void M_OptInit (void) -{ - if (gameinfo.gametype & GAME_DoomChex) - { - LabelColor = CR_UNTRANSLATED; - ValueColor = CR_GRAY; - MoreColor = CR_GRAY; - } - else if (gameinfo.gametype == GAME_Heretic) - { - LabelColor = CR_GREEN; - ValueColor = CR_UNTRANSLATED; - MoreColor = CR_UNTRANSLATED; - } - else // Hexen - { - LabelColor = CR_RED; - ValueColor = CR_UNTRANSLATED; - MoreColor = CR_UNTRANSLATED; - } -} - -void M_InitVideoModesMenu () -{ - int dummy1, dummy2; - size_t currval = 0; - - M_RefreshModesList(); - - for (unsigned int i = 1; i <= 32 && currval < countof(Depths); i++) - { - Video->StartModeIterator (i, screen->IsFullscreen()); - if (Video->NextMode (&dummy1, &dummy2, NULL)) - { - /* - Depths[currval].value = currval; - mysnprintf (name, countof(name), "%d bit", i); - Depths[currval].name = copystring (name); - */ - BitTranslate[currval++] = i; - } - } - - //ModesItems[VM_DEPTHITEM].b.min = (float)currval; - - switch (Video->GetDisplayType ()) - { - case DISPLAY_FullscreenOnly: - ModesItems[2].type = nochoice; - ModesItems[2].b.min = 1.f; - break; - case DISPLAY_WindowOnly: - ModesItems[2].type = nochoice; - ModesItems[2].b.min = 0.f; - break; - default: - break; - } -} - - -// -// Toggle messages on/off -// -void M_ChangeMessages () -{ - if (show_messages) - { - Printf (128, "%s\n", GStrings("MSGOFF")); - show_messages = false; - } - else - { - Printf (128, "%s\n", GStrings("MSGON")); - show_messages = true; - } -} - -CCMD (togglemessages) -{ - M_ChangeMessages (); -} - -void M_SizeDisplay (int diff) -{ - // changing screenblocks automatically resizes the display - screenblocks = screenblocks + diff; -} - -CCMD (sizedown) -{ - M_SizeDisplay (-1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); -} - -CCMD (sizeup) -{ - M_SizeDisplay (1); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, 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) - Bindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); - else if (item->type == mapcontrol) - AutomapBindings.GetKeysForCommand (item->e.command, &item->b.key1, &item->c.key2); - } -} - -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 || item->type == mapcontrol) && 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: - case mapcontrol: - { - 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) - { - if (item->type == control) - { - Bindings.SetBind(ev->data1, item->e.command); - } - else if (item->type == mapcontrol) - { - AutomapBindings.SetBind(ev->data1, item->e.command); - } - M_BuildKeyList(CurrentMenu->items, CurrentMenu->numitems); - } - menuactive = MENU_On; - 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case palettegrid: - SelColorIndex = (SelColorIndex - 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case ediscrete: - value = item->a.cvar->GetGenericRep(CVAR_String); - value.String = const_cast(M_FindPrevVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); - item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case inverter: - value = item->a.cvar->GetGenericRep (CVAR_Float); - value.Float = -value.Float; - item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); - break; - - case joy_inverter: - assert(item->e.joyslidernum == 0); - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case palettegrid: - SelColorIndex = (SelColorIndex + 1) & 15; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case ediscrete: - value = item->a.cvar->GetGenericRep(CVAR_String); - value.String = const_cast(M_FindNextVal(value.String, item->e.enumvalues, (int)item->b.numvalues)); - item->a.cvar->SetGenericRep(value, CVAR_String); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - case inverter: - value = item->a.cvar->GetGenericRep (CVAR_Float); - value.Float = -value.Float; - item->a.cvar->SetGenericRep (value, CVAR_Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); - break; - - case joy_inverter: - assert(item->e.joyslidernum == 0); - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - break; - - default: - break; - } - break; - - case MKEY_Clear: - if (item->type == control) - { - Bindings.UnbindACommand (item->e.command); - item->b.key1 = item->c.key2 = 0; - } - else if (item->type == mapcontrol) - { - AutomapBindings.UnbindACommand (item->e.command); - item->b.key1 = item->c.key2 = 0; - } - break; - - 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", snd_menuvolume, 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - } - else if (item->type == control || item->type == mapcontrol) - { - 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", snd_menuvolume, 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", snd_menuvolume, ATTN_NONE); - } - else if (item->type == joy_inverter) - { - assert(item->e.joyslidernum == 0); - value.Float = SELECTED_JOYSTICK->GetAxisScale(item->a.joyselection); - SELECTED_JOYSTICK->SetAxisScale(item->a.joyselection, -value.Float); - S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); - } - else if (item->type == screenres) - { - } - else if (item->type == colorpicker) - { - CurrentMenu->lastOn = CurrentItem; - S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, 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); -} - -static void StartMapControlsMenu (void) -{ - M_BuildKeyList (MapControlsMenu.items, MapControlsMenu.numitems); - M_SwitchMenu (&MapControlsMenu); -} - -CCMD (menu_mapcolors) -{ - M_StartControlPanel (true); - OptionsActive = true; - StartMapColorsMenu (); -} - -static void DefaultCustomColors () -{ - // Find the color cvars by scanning the MapColors menu. - for (int i = 0; i < MapColorsMenu.numitems; ++i) - { - if (MapColorsItems[i].type == colorpicker) - { - MapColorsItems[i].a.colorcvar->ResetToDefault (); - } - } -} - -static bool ColorPickerDrawer () -{ - DWORD newColor = MAKEARGB(255, - int(ColorPickerItems[2].a.fval), - int(ColorPickerItems[3].a.fval), - int(ColorPickerItems[4].a.fval)); - DWORD oldColor = DWORD(*ColorPickerItems[0].a.colorcvar) | 0xFF000000; - - int x = screen->GetWidth()*2/3; - int y = (15 + BigFont->GetHeight() + SmallFont->GetHeight()*5 - 10) * CleanYfac_1; - - screen->Clear (x, y, x + 48*CleanXfac_1, y + 48*CleanYfac_1, -1, oldColor); - screen->Clear (x + 48*CleanXfac_1, y, x + 48*2*CleanXfac_1, y + 48*CleanYfac_1, -1, newColor); - - y += 49*CleanYfac_1; - screen->DrawText (SmallFont, CR_GRAY, x+(24-SmallFont->StringWidth("Old")/2)*CleanXfac_1, y, - "Old", DTA_CleanNoMove_1, true, TAG_DONE); - screen->DrawText (SmallFont, CR_WHITE, x+(48+24-SmallFont->StringWidth("New")/2)*CleanXfac_1, y, - "New", DTA_CleanNoMove_1, true, TAG_DONE); - return false; -} - -static void SetColorPickerSliders () -{ - FColorCVar *cvar = ColorPickerItems[0].a.colorcvar; - ColorPickerItems[2].a.fval = float(RPART(DWORD(*cvar))); - ColorPickerItems[3].a.fval = float(GPART(DWORD(*cvar))); - ColorPickerItems[4].a.fval = float(BPART(DWORD(*cvar))); - CurrColorIndex = cvar->GetIndex(); -} - -static void UpdateSelColor (int index) -{ - ColorPickerItems[2].a.fval = GPalette.BaseColors[index].r; - ColorPickerItems[3].a.fval = GPalette.BaseColors[index].g; - ColorPickerItems[4].a.fval = GPalette.BaseColors[index].b; -} - -static void ColorPickerReset () -{ - SetColorPickerSliders (); -} - -static void ActivateColorChoice () -{ - UCVarValue val; - val.Int = MAKERGB - (int(ColorPickerItems[2].a.fval), - int(ColorPickerItems[3].a.fval), - int(ColorPickerItems[4].a.fval)); - ColorPickerItems[0].a.colorcvar->SetGenericRep (val, CVAR_Int); -} - -static void StartColorPickerMenu (const char *colorname, FColorCVar *cvar) -{ - ColorPickerMenu.PreDraw = ColorPickerDrawer; - ColorPickerMenu.EscapeHandler = ActivateColorChoice; - ColorPickerItems[0].label = colorname; - ColorPickerItems[0].a.colorcvar = cvar; - SetColorPickerSliders (); - M_SwitchMenu (&ColorPickerMenu); -} - -static void CustomizeControls (void) -{ - M_BuildKeyList (ControlsMenu.items, ControlsMenu.numitems); - M_SwitchMenu (&ControlsMenu); -} - -CCMD (menu_keys) -{ - M_StartControlPanel (true); - OptionsActive = true; - CustomizeControls (); -} - -EXTERN_CVAR (Int, dmflags) - -static void GameplayOptions (void) -{ - M_SwitchMenu (&DMFlagsMenu); - flagsvar = SHOW_DMFlags | SHOW_DMFlags2; -} - -CCMD (menu_gameplay) -{ - M_StartControlPanel (true); - OptionsActive = true; - GameplayOptions (); -} - -static void CompatibilityOptions (void) -{ - M_SwitchMenu (&CompatibilityMenu); - flagsvar = SHOW_CompatFlags; -} - -CCMD (menu_compatibility) -{ - M_StartControlPanel (true); - OptionsActive = true; - CompatibilityOptions (); -} - -static void MouseOptions () -{ - M_SwitchMenu (&MouseMenu); -} - -CCMD (menu_mouse) -{ - M_StartControlPanel (true); - OptionsActive = true; - MouseOptions (); -} - -static bool DrawJoystickConfigMenuHeader() -{ - FString joyname = SELECTED_JOYSTICK->GetName(); - screen->DrawText(BigFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - (screen->GetWidth() - BigFont->StringWidth(CurrentMenu->texttitle) * CleanXfac_1) / 2, - 5 * CleanYfac_1, - CurrentMenu->texttitle, DTA_CleanNoMove_1, true, TAG_DONE); - screen->DrawText(SmallFont, gameinfo.gametype & GAME_DoomChex ? CR_RED : CR_UNTRANSLATED, - (screen->GetWidth() - SmallFont->StringWidth(joyname) * CleanXfac_1) / 2, (8 + BigFont->GetHeight()) * CleanYfac_1, - joyname, DTA_CleanNoMove_1, true, TAG_DONE); - return false; -} - -static void UpdateJoystickConfigMenu(IJoystickConfig *joy) -{ - int i; - menuitem_t item = { whitetext }; - - JoystickConfigItems.Clear(); - if (joy == NULL) - { - item.type = redtext; - item.label = "Invalid controller specified for menu"; - JoystickConfigItems.Push(item); - } - else - { - SELECTED_JOYSTICK = joy; - - item.type = joy_sens; - item.label = "Overall sensitivity"; - item.b.min = 0; - item.c.max = 2; - item.d.step = 0.1f; - JoystickConfigItems.Push(item); - - item.type = redtext; - item.label = " "; - JoystickConfigItems.Push(item); - - item.type = whitetext; - if (joy->GetNumAxes() > 0) - { - item.label = "Axis Configuration"; - JoystickConfigItems.Push(item); - - for (i = 0; i < joy->GetNumAxes(); ++i) - { - item.type = redtext; - item.label = " "; - JoystickConfigItems.Push(item); - - item.type = joy_map; - item.label = joy->GetAxisName(i); - item.a.joyselection = i; - item.b.numvalues = countof(JoyAxisMapNames); - item.e.values = JoyAxisMapNames; - JoystickConfigItems.Push(item); - - item.type = joy_slider; - item.label = "Sensitivity"; - item.b.min = 0; - item.c.max = 4; - item.d.step = 0.1f; - item.e.joyslidernum = 0; - JoystickConfigItems.Push(item); - - item.type = joy_inverter; - item.label = "Invert"; - JoystickConfigItems.Push(item); - - item.type = joy_slider; - item.label = "Dead Zone"; - item.b.position = 1; - item.c.max = 0.9f; - item.d.step = 0.05f; - item.e.joyslidernum = 1; - JoystickConfigItems.Push(item); - } - } - else - { - item.label = "No configurable axes"; - JoystickConfigItems.Push(item); - } - } - JoystickConfigMenu.items = &JoystickConfigItems[0]; - JoystickConfigMenu.numitems = JoystickConfigItems.Size(); - JoystickConfigMenu.lastOn = 0; - JoystickConfigMenu.scrollpos = 0; - JoystickConfigMenu.y = 25 + BigFont->GetHeight(); - JoystickConfigMenu.PreDraw = DrawJoystickConfigMenuHeader; - if (screen != NULL) - { - CalcIndent(&JoystickConfigMenu); - } -} - -static void StartJoystickConfigMenu() -{ - UpdateJoystickConfigMenu(Joysticks[JoystickItems[JoystickMenu.lastOn].a.joyselection]); - M_SwitchMenu(&JoystickConfigMenu); -} - -void UpdateJoystickMenu(IJoystickConfig *selected) -{ - int i; - menuitem_t item = { whitetext }; - int itemnum = -1; - - JoystickItems.Clear(); - I_GetJoysticks(Joysticks); - if ((unsigned)itemnum >= Joysticks.Size()) - { - itemnum = Joysticks.Size() - 1; - } - if (selected != NULL) - { - for (i = 0; (unsigned)i < Joysticks.Size(); ++i) - { - if (Joysticks[i] == selected) - { - itemnum = i; - break; - } - } - } - item.type = discrete; - item.label = "Enable controller support"; - item.a.cvar = &use_joystick; - item.b.numvalues = 2; - item.c.discretecenter = 2; - item.e.values = YesNo; - JoystickItems.Push(item); - -#ifdef _WIN32 - item.label = "Enable DirectInput controllers"; - item.a.cvar = &joy_dinput; - JoystickItems.Push(item); - - item.label = "Enable XInput controllers"; - item.a.cvar = &joy_xinput; - JoystickItems.Push(item); - - item.label = "Enable raw PlayStation 2 adapters"; - item.a.cvar = &joy_ps2raw; - JoystickItems.Push(item); -#endif - - item.type = redtext; - item.label = " "; - item.c.discretecenter = 0; - JoystickItems.Push(item); - - if (Joysticks.Size() == 0) - { - item.type = redtext; - item.label = "No controllers detected"; - JoystickItems.Push(item); - if (!use_joystick) - { - item.type = whitetext; - item.label = "Controller support must be"; - JoystickItems.Push(item); - - item.label = "enabled to detect any"; - JoystickItems.Push(item); - } - } - else - { - item.label = "Configure controllers:"; - JoystickItems.Push(item); - - item.type = joymore; - item.e.mfunc = StartJoystickConfigMenu; - for (int i = 0; i < (int)Joysticks.Size(); ++i) - { - item.a.joyselection = i; - if (i == itemnum) - { - JoystickMenu.lastOn = JoystickItems.Size(); - } - JoystickItems.Push(item); - } - } - JoystickMenu.items = &JoystickItems[0]; - JoystickMenu.numitems = JoystickItems.Size(); - if (JoystickMenu.lastOn >= JoystickMenu.numitems) - { - JoystickMenu.lastOn = JoystickMenu.numitems - 1; - } - if (CurrentMenu == &JoystickMenu && CurrentItem >= JoystickMenu.numitems) - { - CurrentItem = JoystickMenu.lastOn; - } - if (screen != NULL) - { - CalcIndent(&JoystickMenu); - } - - // If the joystick config menu is open, close it if the device it's - // open for is gone. - for (i = 0; (unsigned)i < Joysticks.Size(); ++i) - { - if (Joysticks[i] == SELECTED_JOYSTICK) - { - break; - } - } - if (i == (int)Joysticks.Size()) - { - SELECTED_JOYSTICK = NULL; - if (CurrentMenu == &JoystickConfigMenu) - { - M_PopMenuStack(); - } - } -} - -static void JoystickOptions () -{ - UpdateJoystickMenu (NULL); - M_SwitchMenu (&JoystickMenu); -} - -CCMD (menu_joystick) -{ - M_StartControlPanel (true); - OptionsActive = true; - JoystickOptions (); -} - -static void FreeMIDIMenuList() -{ - if (SoundItems[MIDI_DEVICE_ITEM].e.values != NULL) - { - delete[] SoundItems[MIDI_DEVICE_ITEM].e.values; - } -} - -static void SoundOptions () -{ - I_BuildMIDIMenuList(&SoundItems[MIDI_DEVICE_ITEM].e.values, &SoundItems[MIDI_DEVICE_ITEM].b.min); - atterm(FreeMIDIMenuList); - M_SwitchMenu(&SoundMenu); -} - -CCMD (menu_sound) -{ - M_StartControlPanel (true); - OptionsActive = true; - SoundOptions (); -} - -static void AdvSoundOptions () -{ - M_SwitchMenu (&AdvSoundMenu); -} - -CCMD (menu_advsound) -{ - M_StartControlPanel (true); - OptionsActive = true; - AdvSoundOptions (); -} - -static void MakeSoundChanges (void) -{ - static char snd_reset[] = "snd_reset"; - AddCommandString (snd_reset); -} - -static void ModReplayerOptions() -{ - for (size_t i = 2; i < countof(ModReplayerItems); ++i) - { - if (ModReplayerItems[i].type == discrete) - { - ModReplayerItems[i].d.graycheck = &mod_dumb; - } - } - M_SwitchMenu(&ModReplayerMenu); -} - -CCMD (menu_modreplayer) -{ - M_StartControlPanel(true); - OptionsActive = true; - ModReplayerOptions(); -} - -static void VideoOptions (void) -{ - InitCrosshairsList(); - M_SwitchMenu (&VideoMenu); -} - -CCMD (menu_display) -{ - M_StartControlPanel (true); - OptionsActive = true; - InitCrosshairsList(); - M_SwitchMenu (&VideoMenu); -} - -static void BuildModesList (int hiwidth, int hiheight, int hi_bits) -{ - char strtemp[32], **str; - int i, c; - int width, height, showbits; - bool letterbox=false; - int ratiomatch; - - if (menu_screenratios >= 0 && menu_screenratios <= 4 && menu_screenratios != 3) - { - ratiomatch = menu_screenratios; - } - else - { - ratiomatch = -1; - } - showbits = BitTranslate[DummyDepthCvar]; - - if (Video != NULL) - { - Video->StartModeIterator (showbits, screen->IsFullscreen()); - } - - for (i = VM_RESSTART; ModesItems[i].type == screenres; i++) - { - ModesItems[i].e.highlight = -1; - for (c = 0; c < 3; c++) - { - bool haveMode = false; - - switch (c) - { - default: str = &ModesItems[i].b.res1; break; - case 1: str = &ModesItems[i].c.res2; break; - case 2: str = &ModesItems[i].d.res3; break; - } - if (Video != NULL) - { - while ((haveMode = Video->NextMode (&width, &height, &letterbox)) && - (ratiomatch >= 0 && CheckRatio (width, height) != ratiomatch)) - { - } - } - - if (haveMode) - { - if (/* hi_bits == showbits && */ width == hiwidth && height == hiheight) - ModesItems[i].e.highlight = ModesItems[i].a.selmode = c; - - mysnprintf (strtemp, countof(strtemp), "%dx%d%s", width, height, letterbox?TEXTCOLOR_BROWN" LB":""); - ReplaceString (str, strtemp); - } - else - { - if (*str) - { - delete[] *str; - *str = NULL; - } - } - } - } -} - -void M_RefreshModesList () -{ - BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); -} - -void M_FreeModesList () -{ - for (int i = VM_RESSTART; ModesItems[i].type == screenres; ++i) - { - for (int c = 0; c < 3; ++c) - { - char **str; - - switch (c) - { - default: str = &ModesItems[i].b.res1; break; - case 1: str = &ModesItems[i].c.res2; break; - case 2: str = &ModesItems[i].d.res3; break; - } - if (str != NULL) - { - delete[] *str; - *str = NULL; - } - } - } -} - -static bool GetSelectedSize (int line, int *width, int *height) -{ - if (ModesItems[line].type != screenres) - { - return false; - } - else - { - char *res, *breakpt; - long x, y; - - switch (ModesItems[line].a.selmode) - { - default: res = ModesItems[line].b.res1; break; - case 1: res = ModesItems[line].c.res2; break; - case 2: res = ModesItems[line].d.res3; break; - } - x = strtol (res, &breakpt, 10); - y = strtol (breakpt+1, NULL, 10); - - *width = x; - *height = y; - return true; - } -} - -static int FindBits (int bits) -{ - int i; - - for (i = 0; i < 22; i++) - { - if (BitTranslate[i] == bits) - return i; - } - - return 0; -} - -static void SetModesMenu (int w, int h, int bits) -{ - DummyDepthCvar = FindBits (bits); - - if (testingmode <= 1) - { - if (ModesItems[VM_ENTERLINE].label != VMEnterText) - free (const_cast(ModesItems[VM_ENTERLINE].label)); - ModesItems[VM_ENTERLINE].label = VMEnterText; - ModesItems[VM_TESTLINE].label = VMTestText; - } - else - { - char strtemp[64]; - - mysnprintf (strtemp, countof(strtemp), "TESTING %dx%dx%d", w, h, bits); - ModesItems[VM_ENTERLINE].label = copystring (strtemp); - ModesItems[VM_TESTLINE].label = "Please wait 5 seconds..."; - } - - BuildModesList (w, h, bits); -} - -void M_RestoreMode () -{ - NewWidth = OldWidth; - NewHeight = OldHeight; - NewBits = OldBits; - setmodeneeded = true; - testingmode = 0; - SetModesMenu (OldWidth, OldHeight, OldBits); -} - -void M_SetDefaultMode () -{ - // Make current resolution the default - vid_defwidth = SCREENWIDTH; - vid_defheight = SCREENHEIGHT; - vid_defbits = DisplayBits; - testingmode = 0; - SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); -} - -static void SetVidMode () -{ - SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); - if (ModesMenu.items[ModesMenu.lastOn].type == screenres) - { - if (ModesMenu.items[ModesMenu.lastOn].a.selmode == -1) - { - ModesMenu.items[ModesMenu.lastOn].a.selmode++; - } - } - M_SwitchMenu (&ModesMenu); -} - -CCMD (menu_video) -{ - M_StartControlPanel (true); - OptionsActive = true; - SetVidMode (); -} - -void M_LoadKeys (const char *modname, bool dbl) -{ - char section[64]; - - if (GameNames[gameinfo.gametype] == NULL) - return; - - mysnprintf (section, countof(section), "%s.%s%sBindings", GameNames[gameinfo.gametype], modname, - dbl ? ".Double" : "."); - - FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; - if (GameConfig->SetSection (section)) - { - const char *key, *value; - while (GameConfig->NextInSection (key, value)) - { - bindings->DoBind (key, value); - } - } -} - -int M_DoSaveKeys (FConfigFile *config, char *section, int i, bool dbl) -{ - int most = (int)CustomControlsItems.Size(); - - config->SetSection (section, true); - config->ClearCurrentSection (); - FKeyBindings *bindings = dbl? &DoubleBindings : &Bindings; - for (++i; i < most; ++i) - { - menuitem_t *item = &CustomControlsItems[i]; - if (item->type == control) - { - bindings->ArchiveBindings (config, 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 || item->type == mapcontrol) - { - if (item->label != NULL) - { - delete[] item->label; - item->label = NULL; - } - if (item->e.command != NULL) - { - delete[] item->e.command; - item->e.command = NULL; - } - } - } -} - -CCMD (addkeysection) -{ - if (argv.argc() != 3) - { - Printf ("Usage: addkeysection \n"); - return; - } - - const int numStdControls = countof(ControlsItems); - int i; - - if (ControlsMenu.items == ControlsItems) - { // No custom controls have been defined yet. - for (i = 0; i < numStdControls; ++i) - { - CustomControlsItems.Push (ControlsItems[i]); - } - } - - // See if this section already exists - int last = (int)CustomControlsItems.Size(); - for (i = numStdControls; i < last; ++i) - { - menuitem_t *item = &CustomControlsItems[i]; - - if (item->type == whitetext && - stricmp (item->label, argv[1]) == 0) - { // found it - break; - } - } - - if (i == last) - { // Add the new section - // Limit the ini name to 32 chars - if (strlen (argv[2]) > 32) - argv[2][32] = 0; - - menuitem_t tempItem = { redtext, " " }; - - // Add a blank line to the menu - CustomControlsItems.Push (tempItem); - - // Add the section name to the menu - tempItem.type = whitetext; - tempItem.label = copystring (argv[1]); - tempItem.e.command = copystring (argv[2]); // Record ini section name in command field - CustomControlsItems.Push (tempItem); - ControlsMenu.items = &CustomControlsItems[0]; - - // Load bindings for this section from the ini - M_LoadKeys (argv[2], 0); - M_LoadKeys (argv[2], 1); - - AddKeySpot = 0; - } - else - { // Add new keys to the end of this section - do - { - i++; - } while (i < last && CustomControlsItems[i].type == control); - if (i < last) - { - AddKeySpot = i; - } - else - { - AddKeySpot = 0; - } - } -} - -CCMD (addmenukey) -{ - if (argv.argc() != 3) - { - Printf ("Usage: addmenukey \n"); - return; - } - if (ControlsMenu.items == ControlsItems) - { - Printf ("You must use addkeysection first.\n"); - return; - } - - menuitem_t newItem = { control, }; - newItem.label = copystring (argv[1]); - newItem.e.command = copystring (argv[2]); - if (AddKeySpot == 0) - { // Just add to the end of the menu - CustomControlsItems.Push (newItem); - } - else - { // Add somewhere in the middle of the menu - size_t movecount = CustomControlsItems.Size() - AddKeySpot; - CustomControlsItems.Reserve (1); - memmove (&CustomControlsItems[AddKeySpot+1], - &CustomControlsItems[AddKeySpot], - sizeof(menuitem_t)*movecount); - CustomControlsItems[AddKeySpot++] = newItem; - } - ControlsMenu.items = &CustomControlsItems[0]; - ControlsMenu.numitems = (int)CustomControlsItems.Size(); -} - -void M_Deinit () -{ - // Free bitdepth names for the modes menu. - for (size_t i = 0; i < countof(Depths); ++i) - { - if (Depths[i].name != NULL) - { - delete[] Depths[i].name; - Depths[i].name = NULL; - } - } - - // Free resolutions from the modes menu. - M_FreeModesList(); -} - -// Reads any XHAIRS lumps for the names of crosshairs and -// adds them to the display options menu. -void InitCrosshairsList() -{ - int lastlump, lump; - valuestring_t value; - - lastlump = 0; - - Crosshairs.Clear(); - value.value = 0; - value.name = "None"; - Crosshairs.Push(value); - - while ((lump = Wads.FindLump("XHAIRS", &lastlump)) != -1) - { - FScanner sc(lump); - while (sc.GetNumber()) - { - value.value = float(sc.Number); - sc.MustGetString(); - value.name = sc.String; - if (value.value != 0) - { // Check if it already exists. If not, add it. - unsigned int i; - - for (i = 1; i < Crosshairs.Size(); ++i) - { - if (Crosshairs[i].value == value.value) - { - break; - } - } - if (i < Crosshairs.Size()) - { - Crosshairs[i].name = value.name; - } - else - { - Crosshairs.Push(value); - } - } - } - } - VideoItems[CROSSHAIR_INDEX].b.numvalues = float(Crosshairs.Size()); - VideoItems[CROSSHAIR_INDEX].e.valuestrings = &Crosshairs[0]; -} diff --git a/src/menu/colorpickermenu.cpp b/src/menu/colorpickermenu.cpp new file mode 100644 index 0000000000..d803022bf9 --- /dev/null +++ b/src/menu/colorpickermenu.cpp @@ -0,0 +1,357 @@ +/* +** colorpickermenu.cpp +** The color picker menu +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" + +#define NO_IMP +#include "menu/optionmenuitems.h" + +class DColorPickerMenu : public DOptionMenu +{ + DECLARE_CLASS(DColorPickerMenu, DOptionMenu) + + float mRed; + float mGreen; + float mBlue; + + int mGridPosX; + int mGridPosY; + + int mStartItem; + + FColorCVar *mCVar; + +public: + + DColorPickerMenu(DMenu *parent, const char *name, FOptionMenuDescriptor *desc, FColorCVar *cvar) + { + mStartItem = desc->mItems.Size(); + mRed = (float)RPART(DWORD(*cvar)); + mGreen = (float)GPART(DWORD(*cvar)); + mBlue = (float)BPART(DWORD(*cvar)); + mGridPosX = 0; + mGridPosY = 0; + mCVar = cvar; + + // This menu uses some featurs that are hard to implement in an external control lump + // so it creates its own list of menu items. + desc->mItems.Resize(mStartItem+8); + desc->mItems[mStartItem+0] = new FOptionMenuItemStaticText(name, false); + desc->mItems[mStartItem+1] = new FOptionMenuItemStaticText(" ", false); + desc->mItems[mStartItem+2] = new FOptionMenuSliderVar("Red", &mRed, 0, 255, 15, 0); + desc->mItems[mStartItem+3] = new FOptionMenuSliderVar("Green", &mGreen, 0, 255, 15, 0); + desc->mItems[mStartItem+4] = new FOptionMenuSliderVar("Blue", &mBlue, 0, 255, 15, 0); + desc->mItems[mStartItem+5] = new FOptionMenuItemStaticText(" ", false); + desc->mItems[mStartItem+6] = new FOptionMenuItemCommand("Undo changes", "undocolorpic"); + desc->mItems[mStartItem+7] = new FOptionMenuItemStaticText(" ", false); + desc->mSelectedItem = mStartItem + 2; + Init(parent, desc); + desc->mIndent = 0; + desc->CalcIndent(); + } + + void Destroy() + { + if (mStartItem >= 0) + { + for(unsigned i=0;i<8;i++) + { + delete mDesc->mItems[mStartItem+i]; + mDesc->mItems.Resize(mStartItem); + } + UCVarValue val; + val.Int = MAKERGB(int(mRed), int(mGreen), int(mBlue)); + if (mCVar != NULL) mCVar->SetGenericRep (val, CVAR_Int); + mStartItem = -1; + } + } + + void Reset() + { + mRed = (float)RPART(DWORD(*mCVar)); + mGreen = (float)GPART(DWORD(*mCVar)); + mBlue = (float)BPART(DWORD(*mCVar)); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MenuEvent (int mkey, bool fromcontroller) + { + int &mSelectedItem = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Down: + if (mSelectedItem == mStartItem+6) // last valid item + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY = 0; + // let it point to the last static item so that the super class code still has a valid item + mSelectedItem = mStartItem+7; + return true; + } + else if (mSelectedItem == mStartItem+7) + { + if (mGridPosY < 15) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY++; + } + return true; + } + break; + + case MKEY_Up: + if (mSelectedItem == mStartItem+7) + { + if (mGridPosY > 0) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mGridPosY--; + } + else + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + mSelectedItem = mStartItem+6; + } + return true; + } + break; + + case MKEY_Left: + if (mSelectedItem == mStartItem+7) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + if (--mGridPosX < 0) mGridPosX = 15; + return true; + } + break; + + case MKEY_Right: + if (mSelectedItem == mStartItem+7) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + if (++mGridPosX > 15) mGridPosX = 0; + return true; + } + break; + + case MKEY_Enter: + if (mSelectedItem == mStartItem+7) + { + // Choose selected palette entry + int index = mGridPosX + mGridPosY * 16; + mRed = GPalette.BaseColors[index].r; + mGreen = GPalette.BaseColors[index].g; + mBlue = GPalette.BaseColors[index].b; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + return true; + } + break; + } + if (mSelectedItem >= 0 && mSelectedItem < mStartItem+7) + { + if (mDesc->mItems[mDesc->mSelectedItem]->MenuEvent(mkey, fromcontroller)) return true; + } + return Super::MenuEvent(mkey, fromcontroller); + } + + //============================================================================= + // + // + // + //============================================================================= + + bool MouseEvent(int type, int mx, int my) + { + int olditem = mDesc->mSelectedItem; + bool res = Super::MouseEvent(type, mx, my); + + if (mDesc->mSelectedItem == -1 || mDesc->mSelectedItem == mStartItem+7) + { + int y = (-mDesc->mPosition + BigFont->GetHeight() + mDesc->mItems.Size() * OptionSettings.mLinespacing) * CleanYfac_1; + int h = (screen->GetHeight() - y) / 16; + int fh = OptionSettings.mLinespacing * CleanYfac_1; + int w = fh; + int yy = y + 2 * CleanYfac_1; + int indent = (screen->GetWidth() / 2); + + if (h > fh) h = fh; + else if (h < 4) return res; // no space to draw it. + + int box_y = y - 2 * CleanYfac_1; + int box_x = indent - 16*w; + + if (mx >= box_x && mx < box_x + 16*w && my >= box_y && my < box_y + 16*h) + { + int cell_x = (mx - box_x) / w; + int cell_y = (my - box_y) / h; + + if (olditem != mStartItem+7 || cell_x != mGridPosX || cell_y != mGridPosY) + { + mGridPosX = cell_x; + mGridPosY = cell_y; + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mSelectedItem = mStartItem+7; + if (type == MOUSE_Release) + { + MenuEvent(MKEY_Enter, true); + if (m_use_mouse == 2) mDesc->mSelectedItem = -1; + } + res = true; + } + } + return res; + } + + //============================================================================= + // + // + // + //============================================================================= + + void Drawer() + { + Super::Drawer(); + + if (mCVar == NULL) return; + int y = (-mDesc->mPosition + BigFont->GetHeight() + mDesc->mItems.Size() * OptionSettings.mLinespacing) * CleanYfac_1; + int h = (screen->GetHeight() - y) / 16; + int fh = OptionSettings.mLinespacing * CleanYfac_1; + int w = fh; + int yy = y; + + if (h > fh) h = fh; + else if (h < 4) return; // no space to draw it. + + int indent = (screen->GetWidth() / 2); + int p = 0; + + for(int i = 0; i < 16; i++, y += h) + { + int box_x, box_y; + int x1; + + box_y = y - 2 * CleanYfac_1; + box_x = indent - 16*w; + for (x1 = 0; x1 < 16; ++x1, p++) + { + screen->Clear (box_x, box_y, box_x + w, box_y + h, p, 0); + if ((mDesc->mSelectedItem == mStartItem+7) && + (/*p == CurrColorIndex ||*/ (i == mGridPosY && x1 == mGridPosX))) + { + int r, g, b; + DWORD col; + double blinky; + if (i == mGridPosY && x1 == mGridPosX) + { + r = 255, g = 128, b = 0; + } + else + { + r = 200, g = 200, b = 255; + } + // Make sure the cursors stand out against similar colors + // by pulsing them. + blinky = fabs(sin(I_MSTime()/1000.0)) * 0.5 + 0.5; + col = MAKEARGB(255,int(r*blinky),int(g*blinky),int(b*blinky)); + + screen->Clear (box_x, box_y, box_x + w, box_y + 1, -1, col); + screen->Clear (box_x, box_y + h-1, box_x + w, box_y + h, -1, col); + screen->Clear (box_x, box_y, box_x + 1, box_y + h, -1, col); + screen->Clear (box_x + w - 1, box_y, box_x + w, box_y + h, -1, col); + } + box_x += w; + } + } + y = yy; + DWORD newColor = MAKEARGB(255, int(mRed), int(mGreen), int(mBlue)); + DWORD oldColor = DWORD(*mCVar) | 0xFF000000; + + int x = screen->GetWidth()*2/3; + + screen->Clear (x, y, x + 48*CleanXfac_1, y + 48*CleanYfac_1, -1, oldColor); + screen->Clear (x + 48*CleanXfac_1, y, x + 48*2*CleanXfac_1, y + 48*CleanYfac_1, -1, newColor); + + y += 49*CleanYfac_1; + screen->DrawText (SmallFont, CR_GRAY, x+(24-SmallFont->StringWidth("Old")/2)*CleanXfac_1, y, + "Old", DTA_CleanNoMove_1, true, TAG_DONE); + screen->DrawText (SmallFont, CR_WHITE, x+(48+24-SmallFont->StringWidth("New")/2)*CleanXfac_1, y, + "New", DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_ABSTRACT_CLASS(DColorPickerMenu) + +CCMD(undocolorpic) +{ + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DColorPickerMenu))) + { + static_cast(DMenu::CurrentMenu)->Reset(); + } +} + + +DMenu *StartPickerMenu(DMenu *parent, const char *name, FColorCVar *cvar) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Colorpickermenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + return new DColorPickerMenu(parent, name, (FOptionMenuDescriptor*)(*desc), cvar); + } + else + { + return NULL; + } +} + diff --git a/src/menu/joystickmenu.cpp b/src/menu/joystickmenu.cpp new file mode 100644 index 0000000000..a29df9596f --- /dev/null +++ b/src/menu/joystickmenu.cpp @@ -0,0 +1,420 @@ +/* +** joystickmenu.cpp +** The joystick configuration menus +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" + +#define NO_IMP +#include "optionmenuitems.h" + + +static TArray Joysticks; +IJoystickConfig *SELECTED_JOYSTICK; + +FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy); + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoySensitivity : public FOptionMenuSliderBase +{ +public: + FOptionMenuSliderJoySensitivity(const char *label, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + } + + double GetValue() + { + return SELECTED_JOYSTICK->GetSensitivity(); + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetSensitivity(float(val)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoyScale : public FOptionMenuSliderBase +{ + int mAxis; + int mNeg; + +public: + FOptionMenuSliderJoyScale(const char *label, int axis, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mAxis = axis; + mNeg = 1; + } + + double GetValue() + { + double d = SELECTED_JOYSTICK->GetAxisScale(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetAxisScale(mAxis, float(val * mNeg)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderJoyDeadZone : public FOptionMenuSliderBase +{ + int mAxis; + int mNeg; + +public: + FOptionMenuSliderJoyDeadZone(const char *label, int axis, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mAxis = axis; + mNeg = 1; + } + + double GetValue() + { + double d = SELECTED_JOYSTICK->GetAxisDeadZone(mAxis); + mNeg = d < 0? -1:1; + return d; + } + + void SetValue(double val) + { + SELECTED_JOYSTICK->SetAxisDeadZone(mAxis, float(val * mNeg)); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemJoyMap : public FOptionMenuItemOptionBase +{ + int mAxis; +public: + + FOptionMenuItemJoyMap(const char *label, int axis, const char *values, int center) + : FOptionMenuItemOptionBase(label, "none", values, NULL, center) + { + mAxis = axis; + } + + int GetSelection() + { + float f = (float)(int)SELECTED_JOYSTICK->GetAxisMap(mAxis); + for(unsigned i=0;imValues.Size(); i++) + { + if (fabs(f - mValues->mValues[i].Value) < FLT_EPSILON) + { + return i; + } + } + return -1; + } + + void SetSelection(int Selection) + { + SELECTED_JOYSTICK->SetAxisMap(mAxis, (EJoyAxis)Selection); + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemInverter : public FOptionMenuItemOptionBase +{ + int mAxis; +public: + + FOptionMenuItemInverter(const char *label, int axis, int center) + : FOptionMenuItemOptionBase(label, "none", "YesNo", NULL, center) + { + mAxis = axis; + } + + int GetSelection() + { + float f = SELECTED_JOYSTICK->GetAxisScale(mAxis); + return f > 0? 0:1; + } + + void SetSelection(int Selection) + { + float f = fabs(SELECTED_JOYSTICK->GetAxisScale(mAxis)); + if (Selection) f*=-1; + SELECTED_JOYSTICK->SetAxisScale(mAxis, f); + } +}; + +class DJoystickConfigMenu : public DOptionMenu +{ + DECLARE_CLASS(DJoystickConfigMenu, DOptionMenu) +}; + +IMPLEMENT_CLASS(DJoystickConfigMenu) + +//============================================================================= +// +// Executes a CCMD, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemJoyConfigMenu : public FOptionMenuItemSubmenu +{ + IJoystickConfig *mJoy; +public: + FOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy) + : FOptionMenuItemSubmenu(label, "JoystickConfigMenu") + { + mJoy = joy; + } + + bool Activate() + { + UpdateJoystickConfigMenu(mJoy); + return FOptionMenuItemSubmenu::Activate(); + } +}; + + +/*======================================= + * + * Joystick Menu + * + *=======================================*/ + +FOptionMenuDescriptor *UpdateJoystickConfigMenu(IJoystickConfig *joy) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickConfigMenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; + FOptionMenuItem *it; + for(unsigned i=0;imItems.Size();i++) + { + delete opt->mItems[i]; + opt->mItems.Clear(); + } + opt->mTitle.Format("Configure %s", joy->GetName().GetChars()); + + if (joy == NULL) + { + it = new FOptionMenuItemStaticText("Invalid controller specified for menu", false); + opt->mItems.Push(it); + } + else + { + SELECTED_JOYSTICK = joy; + + it = new FOptionMenuSliderJoySensitivity("Overall sensitivity", 0, 2, 0.1, 3); + opt->mItems.Push(it); + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + if (joy->GetNumAxes() > 0) + { + it = new FOptionMenuItemStaticText("Axis Configuration", true); + opt->mItems.Push(it); + + for (int i = 0; i < joy->GetNumAxes(); ++i) + { + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + it = new FOptionMenuItemJoyMap(joy->GetAxisName(i), i, "JoyAxisMapNames", false); + opt->mItems.Push(it); + it = new FOptionMenuSliderJoyScale("Overall sensitivity", i, 0, 4, 0.1, 3); + opt->mItems.Push(it); + it = new FOptionMenuItemInverter("Invert", i, false); + opt->mItems.Push(it); + it = new FOptionMenuSliderJoyDeadZone("Dead Zone", i, 0, 0.9, 0.05, 3); + opt->mItems.Push(it); + } + } + else + { + it = new FOptionMenuItemStaticText("No configurable axes", false); + opt->mItems.Push(it); + } + } + opt->mScrollPos = 0; + opt->mSelectedItem = -1; + opt->mIndent = 0; + opt->mPosition = -25; + opt->CalcIndent(); + return opt; + } + return NULL; +} + + + +void UpdateJoystickMenu(IJoystickConfig *selected) +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_JoystickOptions); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *opt = (FOptionMenuDescriptor *)*desc; + FOptionMenuItem *it; + for(unsigned i=0;imItems.Size();i++) + { + delete opt->mItems[i]; + opt->mItems.Clear(); + } + + int i; + int itemnum = -1; + + I_GetJoysticks(Joysticks); + if ((unsigned)itemnum >= Joysticks.Size()) + { + itemnum = Joysticks.Size() - 1; + } + if (selected != NULL) + { + for (i = 0; (unsigned)i < Joysticks.Size(); ++i) + { + if (Joysticks[i] == selected) + { + itemnum = i; + break; + } + } + } + + // Todo: Block joystick for changing this one. + it = new FOptionMenuItemOption("Enable controller support", "use_joystick", "YesNo", NULL, false); + opt->mItems.Push(it); + #ifdef _WIN32 + it = new FOptionMenuItemOption("Enable DirectInput controllers", "joy_dinput", "YesNo", NULL, false); + opt->mItems.Push(it); + it = new FOptionMenuItemOption("Enable XInput controllers", "joy_xinput", "YesNo", NULL, false); + opt->mItems.Push(it); + it = new FOptionMenuItemOption("Enable raw PlayStation 2 adapters", "joy_ps2raw", "YesNo", NULL, false); + opt->mItems.Push(it); + #endif + + it = new FOptionMenuItemStaticText(" ", false); + opt->mItems.Push(it); + + if (Joysticks.Size() == 0) + { + it = new FOptionMenuItemStaticText("No controllers detected", false); + opt->mItems.Push(it); + if (!use_joystick) + { + it = new FOptionMenuItemStaticText("Controller support must be", false); + opt->mItems.Push(it); + it = new FOptionMenuItemStaticText("enabled to detect any", false); + opt->mItems.Push(it); + } + } + else + { + it = new FOptionMenuItemStaticText("Configure controllers:", false); + opt->mItems.Push(it); + + for (int i = 0; i < (int)Joysticks.Size(); ++i) + { + it = new FOptionMenuItemJoyConfigMenu(Joysticks[i]->GetName(), Joysticks[i]); + opt->mItems.Push(it); + if (i == itemnum) opt->mSelectedItem = opt->mItems.Size(); + } + } + if (opt->mSelectedItem >= (int)opt->mItems.Size()) + { + opt->mSelectedItem = opt->mItems.Size() - 1; + } + + opt->CalcIndent(); + + // If the joystick config menu is open, close it if the device it's + // open for is gone. + for (i = 0; (unsigned)i < Joysticks.Size(); ++i) + { + if (Joysticks[i] == SELECTED_JOYSTICK) + { + break; + } + } + if (i == (int)Joysticks.Size()) + { + SELECTED_JOYSTICK = NULL; + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DJoystickConfigMenu))) + { + DMenu::CurrentMenu->Close(); + } + } + } +} + diff --git a/src/menu/listmenu.cpp b/src/menu/listmenu.cpp new file mode 100644 index 0000000000..42164c16b3 --- /dev/null +++ b/src/menu/listmenu.cpp @@ -0,0 +1,511 @@ +/* +** listmenu.cpp +** A simple menu consisting of a list of items +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "v_video.h" +#include "v_font.h" +#include "cmdlib.h" +#include "gstrings.h" +#include "g_level.h" +#include "gi.h" +#include "d_gui.h" +#include "d_event.h" +#include "menu/menu.h" + +IMPLEMENT_CLASS(DListMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DListMenu::DListMenu(DMenu *parent, FListMenuDescriptor *desc) +: DMenu(parent) +{ + mDesc = desc; + mFocusControl = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Init(DMenu *parent, FListMenuDescriptor *desc) +{ + mParentMenu = parent; + GC::WriteBarrier(this, parent); + mDesc = desc; +} + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItem *DListMenu::GetItem(FName name) +{ + for(unsigned i=0;imItems.Size(); i++) + { + FName nm = mDesc->mItems[i]->GetAction(NULL); + if (nm == name) return mDesc->mItems[i]; + } + return NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_KeyDown) + { + int ch = tolower (ev->data1); + + for(unsigned i = mDesc->mSelectedItem + 1; i < mDesc->mItems.Size(); i++) + { + if (mDesc->mItems[i]->CheckHotkey(ch)) + { + mDesc->mSelectedItem = i; + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + } + } + for(int i = 0; i < mDesc->mSelectedItem; i++) + { + if (mDesc->mItems[i]->CheckHotkey(ch)) + { + mDesc->mSelectedItem = i; + S_Sound(CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + } + } + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int startedAt = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + do + { + if (--mDesc->mSelectedItem < 0) mDesc->mSelectedItem = mDesc->mItems.Size()-1; + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + + case MKEY_Down: + do + { + if (++mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) mDesc->mSelectedItem = 0; + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + return true; + + case MKEY_Enter: + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + } + return true; + + default: + return Super::MenuEvent(mkey, fromcontroller); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DListMenu::MouseEvent(int type, int x, int y) +{ + int sel = -1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; + + if (mFocusControl != NULL) + { + mFocusControl->MouseEvent(type, x, y); + return true; + } + else + { + if ((mDesc->mWLeft <= 0 || x > mDesc->mWLeft) && + (mDesc->mWRight <= 0 || x < mDesc->mWRight)) + { + for(unsigned i=0;imItems.Size(); i++) + { + if (mDesc->mItems[i]->CheckCoordinate(x, y)) + { + if (i != mDesc->mSelectedItem) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mSelectedItem = i; + mDesc->mItems[i]->MouseEvent(type, x, y); + return true; + } + } + } + } + mDesc->mSelectedItem = -1; + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Ticker () +{ + Super::Ticker(); + for(unsigned i=0;imItems.Size(); i++) + { + mDesc->mItems[i]->Ticker(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DListMenu::Drawer () +{ + for(unsigned i=0;imItems.Size(); i++) + { + if (mDesc->mItems[i]->mEnabled) mDesc->mItems[i]->Drawer(mDesc->mSelectedItem == i); + } + if (mDesc->mSelectedItem >= 0 && mDesc->mSelectedItem < (int)mDesc->mItems.Size()) + mDesc->mItems[mDesc->mSelectedItem]->DrawSelector(mDesc->mSelectOfsX, mDesc->mSelectOfsY, mDesc->mSelector); + Super::Drawer(); +} + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +FListMenuItem::~FListMenuItem() +{ +} + +bool FListMenuItem::CheckCoordinate(int x, int y) +{ + return false; +} + +void FListMenuItem::Ticker() +{ +} + +void FListMenuItem::Drawer(bool selected) +{ +} + +bool FListMenuItem::Selectable() +{ + return false; +} + +void FListMenuItem::DrawSelector(int xofs, int yofs, FTextureID tex) +{ + if (tex.isNull()) + { + if ((DMenu::MenuTime%8) < 6) + { + screen->DrawText(ConFont, OptionSettings.mFontColorSelection, + mXpos + xofs, mYpos + yofs, "\xd", DTA_Clean, true, TAG_DONE); + } + } + else + { + screen->DrawTexture (TexMan(tex), mXpos + xofs, mYpos + yofs, DTA_Clean, true, TAG_DONE); + } +} + +bool FListMenuItem::Activate() +{ + return false; // cannot be activated +} + +FName FListMenuItem::GetAction(int *pparam) +{ + return mAction; +} + +bool FListMenuItem::SetString(int i, const char *s) +{ + return false; +} + +bool FListMenuItem::GetString(int i, char *s, int len) +{ + return false; +} + +bool FListMenuItem::SetValue(int i, int value) +{ + return false; +} + +bool FListMenuItem::GetValue(int i, int *pvalue) +{ + return false; +} + +void FListMenuItem::Enable(bool on) +{ + mEnabled = on; +} + +bool FListMenuItem::MenuEvent(int mkey, bool fromcontroller) +{ + return false; +} + +bool FListMenuItem::MouseEvent(int type, int x, int y) +{ + return false; +} + +bool FListMenuItem::CheckHotkey(int c) +{ + return false; +} + + +//============================================================================= +// +// static patch +// +//============================================================================= + +FListMenuItemStaticPatch::FListMenuItemStaticPatch(int x, int y, FTextureID patch, bool centered) +: FListMenuItem(x, y) +{ + mTexture = patch; + mCentered = centered; +} + +void FListMenuItemStaticPatch::Drawer(bool selected) +{ + int x = mXpos; + FTexture *tex = TexMan(mTexture); + if (mYpos >= 0) + { + if (mCentered) x -= tex->GetScaledWidth()/2; + screen->DrawTexture (tex, x, mYpos, DTA_Clean, true, TAG_DONE); + } + else + { + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + if (mCentered) x -= (tex->GetScaledWidth()*CleanXfac)/2; + screen->DrawTexture (tex, x, -mYpos*CleanYfac, DTA_CleanNoMove, true, TAG_DONE); + } +} + +//============================================================================= +// +// static text +// +//============================================================================= + +FListMenuItemStaticText::FListMenuItemStaticText(int x, int y, const char *text, FFont *font, EColorRange color, bool centered) +: FListMenuItem(x, y) +{ + mText = ncopystring(text); + mFont = font; + mColor = color; + mCentered = centered; +} + +void FListMenuItemStaticText::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + if (mYpos >= 0) + { + int x = mXpos; + if (mCentered) x -= mFont->StringWidth(text)/2; + screen->DrawText(mFont, mColor, x, mYpos, text, DTA_Clean, true, TAG_DONE); + } + else + { + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + if (mCentered) x -= (mFont->StringWidth(text)*CleanXfac)/2; + screen->DrawText (mFont, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true, TAG_DONE); + } + } +} + +FListMenuItemStaticText::~FListMenuItemStaticText() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// base class for selectable items +// +//============================================================================= + +FListMenuItemSelectable::FListMenuItemSelectable(int x, int y, int height, FName action, int param) +: FListMenuItem(x, y, action) +{ + mHeight = height; + mParam = param; + mHotkey = 0; +} + +bool FListMenuItemSelectable::CheckCoordinate(int x, int y) +{ + return mEnabled && y >= mYpos && y < mYpos + mHeight; // no x check here +} + +bool FListMenuItemSelectable::Selectable() +{ + return mEnabled; +} + +bool FListMenuItemSelectable::Activate() +{ + M_SetMenu(mAction, mParam); + return true; +} + +FName FListMenuItemSelectable::GetAction(int *pparam) +{ + if (pparam != NULL) *pparam = mParam; + return mAction; +} + +bool FListMenuItemSelectable::CheckHotkey(int c) +{ + return c == tolower(mHotkey); +} + +bool FListMenuItemSelectable::MouseEvent(int type, int x, int y) +{ + if (type == DMenu::MOUSE_Release) + { + if (DMenu::CurrentMenu->MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + return false; +} + +//============================================================================= +// +// text item +// +//============================================================================= + +FListMenuItemText::FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param) +: FListMenuItemSelectable(x, y, height, child, param) +{ + mText = ncopystring(text); + mFont = font; + mColor = color; + mHotkey = hotkey; +} + +FListMenuItemText::~FListMenuItemText() +{ + if (mText != NULL) + { + delete [] mText; + } +} + +void FListMenuItemText::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, mColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + } +} + +//============================================================================= +// +// patch item +// +//============================================================================= + +FListMenuItemPatch::FListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID patch, FName child, int param) +: FListMenuItemSelectable(x, y, height, child, param) +{ + mHotkey = hotkey; + mTexture = patch; +} + +void FListMenuItemPatch::Drawer(bool selected) +{ + screen->DrawTexture (TexMan(mTexture), mXpos, mYpos, DTA_Clean, true, TAG_DONE); +} diff --git a/src/menu/loadsavemenu.cpp b/src/menu/loadsavemenu.cpp new file mode 100644 index 0000000000..130dd0bd52 --- /dev/null +++ b/src/menu/loadsavemenu.cpp @@ -0,0 +1,1165 @@ +/* +** loadsavemenu.cpp +** The load game and save game menus +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "i_system.h" +#include "lists.h" +#include "version.h" +#include "g_game.h" +#include "m_png.h" +#include "w_wad.h" +#include "v_text.h" +#include "d_event.h" +#include "gstrings.h" +#include "v_palette.h" +#include "doomstat.h" +#include "gi.h" +#include "d_gui.h" + + + + +class DLoadSaveMenu : public DListMenu +{ + DECLARE_CLASS(DLoadSaveMenu, DListMenu) + +protected: + static List SaveGames; + static FSaveGameNode *TopSaveGame; + static FSaveGameNode *lastSaveSlot; + static FSaveGameNode *SelSaveGame; + + + friend void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); + + static FSaveGameNode *RemoveSaveSlot (FSaveGameNode *file); + static void UnloadSaveStrings(); + static void InsertSaveNode (FSaveGameNode *node); + static void ReadSaveStrings (); + static void NotifyNewSave (const char *file, const char *title, bool okForQuicksave); + + + FTexture *SavePic; + FBrokenLines *SaveComment; + bool mEntering; + char savegamestring[SAVESTRINGSIZE]; + bool mWheelScrolled; + + DLoadSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + void Destroy(); + + void UnloadSaveData (); + void ClearSaveStuff (); + void ExtractSaveData (const FSaveGameNode *node); + void Drawer (); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + bool Responder(event_t *ev); + +}; + +IMPLEMENT_CLASS(DLoadSaveMenu) + +List DLoadSaveMenu::SaveGames; +FSaveGameNode *DLoadSaveMenu::TopSaveGame; +FSaveGameNode *DLoadSaveMenu::lastSaveSlot; +FSaveGameNode *DLoadSaveMenu::SelSaveGame; + +FSaveGameNode *quickSaveSlot; + +//============================================================================= +// +// Save data maintenance (stored statically) +// +//============================================================================= + +FSaveGameNode *DLoadSaveMenu::RemoveSaveSlot (FSaveGameNode *file) +{ + FSaveGameNode *next = static_cast(file->Succ); + + if (file == TopSaveGame) + { + TopSaveGame = next; + } + if (quickSaveSlot == file) + { + quickSaveSlot = NULL; + } + if (lastSaveSlot == file) + { + lastSaveSlot = NULL; + } + file->Remove (); + if (!file->bNoDelete) delete file; + return next; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::UnloadSaveStrings() +{ + while (!SaveGames.IsEmpty()) + { + RemoveSaveSlot (static_cast(SaveGames.Head)); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::InsertSaveNode (FSaveGameNode *node) +{ + FSaveGameNode *probe; + + if (SaveGames.IsEmpty ()) + { + SaveGames.AddHead (node); + return; + } + + if (node->bOldVersion) + { // Add node at bottom of list + probe = static_cast(SaveGames.TailPred); + while (probe->Pred != NULL && probe->bOldVersion && + stricmp (node->Title, probe->Title) < 0) + { + probe = static_cast(probe->Pred); + } + node->Insert (probe); + } + else + { // Add node at top of list + probe = static_cast(SaveGames.Head); + while (probe->Succ != NULL && !probe->bOldVersion && + stricmp (node->Title, probe->Title) > 0) + { + probe = static_cast(probe->Succ); + } + node->InsertBefore (probe); + } +} + + +//============================================================================= +// +// M_ReadSaveStrings +// +// Find savegames and read their titles +// +//============================================================================= + +void DLoadSaveMenu::ReadSaveStrings () +{ + if (SaveGames.IsEmpty ()) + { + void *filefirst; + findstate_t c_file; + FString filter; + + atterm (UnloadSaveStrings); + + filter = G_BuildSaveName ("*.zds", -1); + filefirst = I_FindFirst (filter.GetChars(), &c_file); + if (filefirst != ((void *)(-1))) + { + do + { + // I_FindName only returns the file's name and not its full path + FString filepath = G_BuildSaveName (I_FindName(&c_file), -1); + FILE *file = fopen (filepath, "rb"); + + if (file != NULL) + { + PNGHandle *png; + char sig[16]; + char title[SAVESTRINGSIZE+1]; + bool oldVer = true; + bool addIt = false; + bool missing = false; + + // ZDoom 1.23 betas 21-33 have the savesig first. + // Earlier versions have the savesig second. + // Later versions have the savegame encapsulated inside a PNG. + // + // Old savegame versions are always added to the menu so + // the user can easily delete them if desired. + + title[SAVESTRINGSIZE] = 0; + + if (NULL != (png = M_VerifyPNG (file))) + { + char *ver = M_GetPNGText (png, "ZDoom Save Version"); + char *engine = M_GetPNGText (png, "Engine"); + if (ver != NULL) + { + if (!M_GetPNGText (png, "Title", title, SAVESTRINGSIZE)) + { + strncpy (title, I_FindName(&c_file), SAVESTRINGSIZE); + } + if (strncmp (ver, SAVESIG, 9) == 0 && + atoi (ver+9) >= MINSAVEVER && + engine != NULL) + { + // Was saved with a compatible ZDoom version, + // so check if it's for the current game. + // If it is, add it. Otherwise, ignore it. + char *iwad = M_GetPNGText (png, "Game WAD"); + if (iwad != NULL) + { + if (stricmp (iwad, Wads.GetWadName (FWadCollection::IWAD_FILENUM)) == 0) + { + addIt = true; + oldVer = false; + missing = !G_CheckSaveGameWads (png, false); + } + delete[] iwad; + } + } + else + { // An old version + addIt = true; + } + delete[] ver; + } + if (engine != NULL) + { + delete[] engine; + } + delete png; + } + else + { + fseek (file, 0, SEEK_SET); + if (fread (sig, 1, 16, file) == 16) + { + + if (strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + if (fread (title, 1, SAVESTRINGSIZE, file) == SAVESTRINGSIZE) + { + addIt = true; + } + } + else + { + memcpy (title, sig, 16); + if (fread (title + 16, 1, SAVESTRINGSIZE-16, file) == SAVESTRINGSIZE-16 && + fread (sig, 1, 16, file) == 16 && + strncmp (sig, "ZDOOMSAVE", 9) == 0) + { + addIt = true; + } + } + } + } + + if (addIt) + { + FSaveGameNode *node = new FSaveGameNode; + node->Filename = filepath; + node->bOldVersion = oldVer; + node->bMissingWads = missing; + memcpy (node->Title, title, SAVESTRINGSIZE); + InsertSaveNode (node); + } + fclose (file); + } + } while (I_FindNext (filefirst, &c_file) == 0); + I_FindClose (filefirst); + } + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + SelSaveGame = static_cast(SaveGames.Head); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::NotifyNewSave (const char *file, const char *title, bool okForQuicksave) +{ + FSaveGameNode *node; + + if (file == NULL) + return; + + ReadSaveStrings (); + + // See if the file is already in our list + for (node = static_cast(SaveGames.Head); + node->Succ != NULL; + node = static_cast(node->Succ)) + { +#ifdef unix + if (node->Filename.Compare (file) == 0) +#else + if (node->Filename.CompareNoCase (file) == 0) +#endif + { + strcpy (node->Title, title); + node->bOldVersion = false; + node->bMissingWads = false; + break; + } + } + + if (node->Succ == NULL) + { + node = new FSaveGameNode; + strcpy (node->Title, title); + node->Filename = file; + node->bOldVersion = false; + node->bMissingWads = false; + InsertSaveNode (node); + SelSaveGame = node; + } + + if (okForQuicksave) + { + if (quickSaveSlot == NULL) quickSaveSlot = node; + lastSaveSlot = node; + } +} + +void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave) +{ + DLoadSaveMenu::NotifyNewSave(file, title, okForQuicksave); +} + +//============================================================================= +// +// End of static savegame maintenance code +// +//============================================================================= + +DLoadSaveMenu::DLoadSaveMenu(DMenu *parent, FListMenuDescriptor *desc) +: DListMenu(parent, desc) +{ + ReadSaveStrings(); + mWheelScrolled = false; +} + +void DLoadSaveMenu::Destroy() +{ + ClearSaveStuff (); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::UnloadSaveData () +{ + if (SavePic != NULL) + { + delete SavePic; + } + if (SaveComment != NULL) + { + V_FreeBrokenLines (SaveComment); + } + + SavePic = NULL; + SaveComment = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::ClearSaveStuff () +{ + UnloadSaveData(); + if (quickSaveSlot == (FSaveGameNode *)1) + { + quickSaveSlot = NULL; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::ExtractSaveData (const FSaveGameNode *node) +{ + FILE *file; + PNGHandle *png; + + UnloadSaveData (); + + if (node != NULL && + node->Succ != NULL && + !node->Filename.IsEmpty() && + !node->bOldVersion && + (file = fopen (node->Filename.GetChars(), "rb")) != NULL) + { + if (NULL != (png = M_VerifyPNG (file))) + { + char *time, *pcomment, *comment; + size_t commentlen, totallen, timelen; + + // Extract comment + time = M_GetPNGText (png, "Creation Time"); + pcomment = M_GetPNGText (png, "Comment"); + if (pcomment != NULL) + { + commentlen = strlen (pcomment); + } + else + { + commentlen = 0; + } + if (time != NULL) + { + timelen = strlen (time); + totallen = timelen + commentlen + 3; + } + else + { + timelen = 0; + totallen = commentlen + 1; + } + if (totallen != 0) + { + comment = new char[totallen]; + + if (timelen) + { + memcpy (comment, time, timelen); + comment[timelen] = '\n'; + comment[timelen+1] = '\n'; + timelen += 2; + } + if (commentlen) + { + memcpy (comment + timelen, pcomment, commentlen); + } + comment[timelen+commentlen] = 0; + SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, comment); + delete[] comment; + delete[] time; + delete[] pcomment; + } + + // Extract pic + SavePic = PNGTexture_CreateFromFile(png, node->Filename); + + delete png; + } + fclose (file); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DLoadSaveMenu::Drawer () +{ + Super::Drawer(); + + const int savepicLeft = 10; + const int savepicTop = 54*CleanYfac; + const int savepicWidth = 216*screen->GetWidth()/640; + const int savepicHeight = 135*screen->GetHeight()/400; + + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxLeft = savepicLeft + savepicWidth + 14; + const int listboxTop = savepicTop; + const int listboxWidth = screen->GetWidth() - listboxLeft - 10; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + const int listboxHeight = listboxRows * rowHeight + 1; + const int listboxRight = listboxLeft + listboxWidth; + const int listboxBottom = listboxTop + listboxHeight; + + const int commentLeft = savepicLeft; + const int commentTop = savepicTop + savepicHeight + 16; + const int commentWidth = savepicWidth; + const int commentHeight = (51+(screen->GetHeight()>200?10:0))*CleanYfac; + const int commentRight = commentLeft + commentWidth; + const int commentBottom = commentTop + commentHeight; + + FSaveGameNode *node; + int i; + bool didSeeSelected = false; + + // Draw picture area + if (gameaction == ga_loadgame || gameaction == ga_savegame) + { + return; + } + + V_DrawFrame (savepicLeft, savepicTop, savepicWidth, savepicHeight); + if (SavePic != NULL) + { + screen->DrawTexture(SavePic, savepicLeft, savepicTop, + DTA_DestWidth, savepicWidth, + DTA_DestHeight, savepicHeight, + DTA_Masked, false, + TAG_DONE); + } + else + { + screen->Clear (savepicLeft, savepicTop, + savepicLeft+savepicWidth, savepicTop+savepicHeight, 0, 0); + + if (!SaveGames.IsEmpty ()) + { + const char *text = + (SelSaveGame == NULL || !SelSaveGame->bOldVersion) + ? GStrings("MNU_NOPICTURE") : GStrings("MNU_DIFFVERSION"); + const int textlen = SmallFont->StringWidth (text)*CleanXfac; + + screen->DrawText (SmallFont, CR_GOLD, savepicLeft+(savepicWidth-textlen)/2, + savepicTop+(savepicHeight-rowHeight)/2, text, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // Draw comment area + V_DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); + screen->Clear (commentLeft, commentTop, commentRight, commentBottom, 0, 0); + if (SaveComment != NULL) + { + // I'm not sure why SaveComment would go NULL in this loop, but I got + // a crash report where it was NULL when i reached 1, so now I check + // for that. + for (i = 0; SaveComment != NULL && SaveComment[i].Width >= 0 && i < 6; ++i) + { + screen->DrawText (SmallFont, CR_GOLD, commentLeft, commentTop + + SmallFont->GetHeight()*i*CleanYfac, SaveComment[i].Text, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // Draw file area + do + { + V_DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); + screen->Clear (listboxLeft, listboxTop, listboxRight, listboxBottom, 0, 0); + + if (SaveGames.IsEmpty ()) + { + const char * text = GStrings("MNU_NOFILES"); + const int textlen = SmallFont->StringWidth (text)*CleanXfac; + + screen->DrawText (SmallFont, CR_GOLD, listboxLeft+(listboxWidth-textlen)/2, + listboxTop+(listboxHeight-rowHeight)/2, text, + DTA_CleanNoMove, true, TAG_DONE); + return; + } + + for (i = 0, node = TopSaveGame; + i < listboxRows && node->Succ != NULL; + ++i, node = static_cast(node->Succ)) + { + int color; + if (node->bOldVersion) + { + color = CR_BLUE; + } + else if (node->bMissingWads) + { + color = CR_ORANGE; + } + else if (node == SelSaveGame) + { + color = CR_WHITE; + } + else + { + color = CR_TAN; + } + if (node == SelSaveGame) + { + screen->Clear (listboxLeft, listboxTop+rowHeight*i, + listboxRight, listboxTop+rowHeight*(i+1), -1, + mEntering ? MAKEARGB(255,255,0,0) : MAKEARGB(255,0,0,255)); + didSeeSelected = true; + if (!mEntering) + { + screen->DrawText (SmallFont, color, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, + DTA_CleanNoMove, true, TAG_DONE); + } + else + { + screen->DrawText (SmallFont, CR_WHITE, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, savegamestring, + DTA_CleanNoMove, true, TAG_DONE); + + screen->DrawText (SmallFont, CR_WHITE, + listboxLeft+1+SmallFont->StringWidth (savegamestring)*CleanXfac, + listboxTop+rowHeight*i+CleanYfac, + (gameinfo.gametype & (GAME_DoomStrifeChex)) ? "_" : "[", + DTA_CleanNoMove, true, TAG_DONE); + } + } + else + { + screen->DrawText (SmallFont, color, + listboxLeft+1, listboxTop+rowHeight*i+CleanYfac, node->Title, + DTA_CleanNoMove, true, TAG_DONE); + } + } + + // This is dumb: If the selected node was not visible, + // scroll down and redraw. M_SaveLoadResponder() + // guarantees that if the node is not visible, it will + // always be below the visible list instead of above it. + // This should not really be done here, but I don't care. + + if (!didSeeSelected) + { + // no, this shouldn't be here - and that's why there's now another hack in here + // so that the mouse scrolling does not get screwed by this code... + if (mWheelScrolled) + { + didSeeSelected = true; + SelSaveGame = NULL; + mWheelScrolled = false; + } + for (i = 1; node->Succ != NULL && node != SelSaveGame; ++i) + { + node = static_cast(node->Succ); + } + if (node->Succ == NULL) + { // SelSaveGame is invalid + didSeeSelected = true; + } + else + { + do + { + TopSaveGame = static_cast(TopSaveGame->Succ); + } while (--i); + } + } + } while (!didSeeSelected); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::MenuEvent (int mkey, bool fromcontroller) +{ + switch (mkey) + { + case MKEY_Up: + if (SelSaveGame == NULL) + { + SelSaveGame = TopSaveGame; + } + else if (SelSaveGame->Succ != NULL) + { + if (SelSaveGame != SaveGames.Head) + { + if (SelSaveGame == TopSaveGame) + { + TopSaveGame = static_cast(TopSaveGame->Pred); + } + SelSaveGame = static_cast(SelSaveGame->Pred); + } + else + { + SelSaveGame = static_cast(SaveGames.TailPred); + } + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + } + return true; + + case MKEY_Down: + if (SelSaveGame == NULL) + { + SelSaveGame = TopSaveGame; + } + else if (SelSaveGame->Succ != NULL) + { + if (SelSaveGame != SaveGames.TailPred) + { + SelSaveGame = static_cast(SelSaveGame->Succ); + } + else + { + SelSaveGame = TopSaveGame = + static_cast(SaveGames.Head); + } + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + } + return true; + + case MKEY_Enter: + return false; // This event will be handled by the subclasses + + case MKEY_MBYes: + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + FSaveGameNode *next = static_cast(SelSaveGame->Succ); + if (next->Succ == NULL) + { + next = static_cast(SelSaveGame->Pred); + if (next->Pred == NULL) + { + next = NULL; + } + } + + remove (SelSaveGame->Filename.GetChars()); + UnloadSaveData (); + SelSaveGame = RemoveSaveSlot (SelSaveGame); + ExtractSaveData (SelSaveGame); + } + return true; + } + + default: + return Super::MenuEvent(mkey, fromcontroller); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::MouseEvent(int type, int x, int y) +{ + const int savepicLeft = 10; + const int savepicTop = 54*CleanYfac; + const int savepicWidth = 216*screen->GetWidth()/640; + + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxLeft = savepicLeft + savepicWidth + 14; + const int listboxTop = savepicTop; + const int listboxWidth = screen->GetWidth() - listboxLeft - 10; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + const int listboxHeight = listboxRows * rowHeight + 1; + const int listboxRight = listboxLeft + listboxWidth; + const int listboxBottom = listboxTop + listboxHeight; + + if (x >= listboxLeft && x < listboxLeft + listboxWidth && + y >= listboxTop && y < listboxTop + listboxHeight) + { + int lineno = (y - listboxTop) / rowHeight; + FSaveGameNode *top = TopSaveGame; + while (lineno > 0 && top->Succ != NULL) + { + lineno--; + top = (FSaveGameNode *)top->Succ; + } + if (lineno == 0) + { + if (SelSaveGame != top) + { + SelSaveGame = top; + UnloadSaveData (); + ExtractSaveData (SelSaveGame); + // Sound? + } + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + } + else SelSaveGame = NULL; + } + else + { + SelSaveGame = NULL; + } + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadSaveMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_KeyDown) + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + switch (ev->data1) + { + case GK_F1: + if (!SelSaveGame->Filename.IsEmpty()) + { + char workbuf[512]; + + mysnprintf (workbuf, countof(workbuf), "File on disk:\n%s", SelSaveGame->Filename.GetChars()); + if (SaveComment != NULL) + { + V_FreeBrokenLines (SaveComment); + } + SaveComment = V_BreakLines (SmallFont, 216*screen->GetWidth()/640/CleanXfac, workbuf); + } + return true; + + case GK_DEL: + case '\b': + { + FString EndString; + EndString.Format("%s" TEXTCOLOR_WHITE "%s" TEXTCOLOR_NORMAL "?\n\n%s", + GStrings("MNU_DELETESG"), SelSaveGame->Title, GStrings("PRESSYN")); + M_StartMessage (EndString, 0); + } + return true; + } + } + } + else if (ev->subtype == EV_GUI_WheelUp) + { + if (TopSaveGame != SaveGames.Head && TopSaveGame != NULL) + { + TopSaveGame = static_cast(TopSaveGame->Pred); + mWheelScrolled = true; + } + return true; + } + else if (ev->subtype == EV_GUI_WheelDown) + { + const int savepicTop = 54*CleanYfac; + const int listboxTop = savepicTop; + const int rowHeight = (SmallFont->GetHeight() + 1) * CleanYfac; + const int listboxHeight1 = screen->GetHeight() - listboxTop - 10; + const int listboxRows = (listboxHeight1 - 1) / rowHeight; + + FSaveGameNode *node = TopSaveGame; + if (node != NULL) + { + int count = 1; + while (node != NULL && node != SaveGames.TailPred) + { + node = (FSaveGameNode*)node->Succ; + count++; + } + if (count > listboxRows) + { + TopSaveGame = (FSaveGameNode*)TopSaveGame->Succ; + mWheelScrolled = true; + } + } + } + } + return Super::Responder(ev); +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DSaveMenu : public DLoadSaveMenu +{ + DECLARE_CLASS(DSaveMenu, DLoadSaveMenu) + + FSaveGameNode NewSaveNode; + +public: + + DSaveMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + void Destroy(); + void DoSave (FSaveGameNode *node); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + +}; + +IMPLEMENT_CLASS(DSaveMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +DSaveMenu::DSaveMenu(DMenu *parent, FListMenuDescriptor *desc) +: DLoadSaveMenu(parent, desc) +{ + strcpy (NewSaveNode.Title, ""); + NewSaveNode.bNoDelete = true; + SaveGames.AddHead (&NewSaveNode); + TopSaveGame = static_cast(SaveGames.Head); + if (lastSaveSlot == NULL) + { + SelSaveGame = &NewSaveNode; + } + else + { + SelSaveGame = lastSaveSlot; + } + ExtractSaveData (SelSaveGame); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DSaveMenu::Destroy() +{ + if (SaveGames.Head == &NewSaveNode) + { + SaveGames.RemHead (); + if (SelSaveGame == &NewSaveNode) + { + SelSaveGame = static_cast(SaveGames.Head); + } + if (TopSaveGame == &NewSaveNode) + { + TopSaveGame = static_cast(SaveGames.Head); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DSaveMenu::DoSave (FSaveGameNode *node) +{ + if (node != &NewSaveNode) + { + G_SaveGame (node->Filename.GetChars(), savegamestring); + } + else + { + // Find an unused filename and save as that + FString filename; + int i; + FILE *test; + + for (i = 0;; ++i) + { + filename = G_BuildSaveName ("save", i); + test = fopen (filename, "rb"); + if (test == NULL) + { + break; + } + fclose (test); + } + G_SaveGame (filename, savegamestring); + } + M_ClearMenus(); + BorderNeedRefresh = screen->GetPageCount (); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DSaveMenu::MenuEvent (int mkey, bool fromcontroller) +{ + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + return false; + } + + if (mkey == MKEY_Enter) + { + if (SelSaveGame != &NewSaveNode) + { + strcpy (savegamestring, SelSaveGame->Title); + } + else + { + savegamestring[0] = 0; + } + DMenu *input = new DTextEnterMenu(this, savegamestring, SAVESTRINGSIZE, 1, fromcontroller); + M_ActivateMenu(input); + mEntering = true; + } + else if (mkey == MKEY_Input) + { + mEntering = false; + DoSave(SelSaveGame); + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DSaveMenu::Responder (event_t *ev) +{ + if (ev->subtype == EV_GUI_KeyDown) + { + if (SelSaveGame != NULL && SelSaveGame->Succ != NULL) + { + switch (ev->data1) + { + case GK_DEL: + case '\b': + // cannot delete 'new save game' item + if (SelSaveGame == &NewSaveNode) return true; + break; + + case 'N': + SelSaveGame = TopSaveGame = &NewSaveNode; + UnloadSaveData (); + return true; + } + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +class DLoadMenu : public DLoadSaveMenu +{ + DECLARE_CLASS(DLoadMenu, DLoadSaveMenu) + +public: + + DLoadMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + + bool MenuEvent (int mkey, bool fromcontroller); +}; + +IMPLEMENT_CLASS(DLoadMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +DLoadMenu::DLoadMenu(DMenu *parent, FListMenuDescriptor *desc) +: DLoadSaveMenu(parent, desc) +{ + TopSaveGame = static_cast(SaveGames.Head); + ExtractSaveData (SelSaveGame); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DLoadMenu::MenuEvent (int mkey, bool fromcontroller) +{ + if (Super::MenuEvent(mkey, fromcontroller)) + { + return true; + } + if (SelSaveGame == NULL || SelSaveGame->Succ == NULL) + { + return false; + } + + if (mkey == MKEY_Enter) + { + G_LoadGame (SelSaveGame->Filename.GetChars()); + if (gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + } + if (quickSaveSlot == (FSaveGameNode *)1) + { + quickSaveSlot = SelSaveGame; + } + M_ClearMenus(); + BorderNeedRefresh = screen->GetPageCount (); + return true; + } + return false; +} + diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp new file mode 100644 index 0000000000..cb557512fd --- /dev/null +++ b/src/menu/menu.cpp @@ -0,0 +1,949 @@ +/* +** menu.cpp +** Menu base class and global interface +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "doomdef.h" +#include "doomstat.h" +#include "c_dispatch.h" +#include "d_gui.h" +#include "d_player.h" +#include "g_level.h" +#include "c_console.h" +#include "c_bind.h" +#include "s_sound.h" +#include "p_tick.h" +#include "g_game.h" +#include "c_cvars.h" +#include "d_event.h" +#include "v_video.h" +#include "hu_stuff.h" +#include "gi.h" +#include "i_input.h" +#include "gameconfigfile.h" +#include "gstrings.h" +#include "r_main.h" +#include "menu/menu.h" +#include "textures/textures.h" + +// +// Todo: Move these elsewhere +// +CVAR (Float, mouse_sensitivity, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, show_messages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, show_obituaries, true, CVAR_ARCHIVE) + + +CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) +CVAR(Int, m_use_mouse, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR(Int, m_show_backbutton, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +DMenu *DMenu::CurrentMenu; +int DMenu::MenuTime; + +FListMenuDescriptor *MainMenu; +FGameStartup GameStartupInfo; +EMenuState menuactive; +bool M_DemoNoPlay; +FButtonStatus MenuButtons[NUM_MKEYS]; +int MenuButtonTickers[NUM_MKEYS]; +bool MenuButtonOrigin[NUM_MKEYS]; +int BackbuttonTime; +fixed_t BackbuttonAlpha; + + +#define KEY_REPEAT_DELAY (TICRATE*5/12) +#define KEY_REPEAT_RATE (3) + +//============================================================================ +// +// DMenu base class +// +//============================================================================ + +IMPLEMENT_POINTY_CLASS (DMenu) + DECLARE_POINTER(mParentMenu) +END_POINTERS + +DMenu::DMenu(DMenu *parent) +{ + mParentMenu = parent; + mMouseCapture = false; + mBackbuttonSelected = false; + GC::WriteBarrier(this, parent); +} + +bool DMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_LButtonDown) + { + MouseEventBack(MOUSE_Click, ev->data1, ev->data2); + if (MouseEvent(MOUSE_Click, ev->data1, ev->data2)) + { + SetCapture(); + } + + } + else if (ev->subtype == EV_GUI_MouseMove) + { + BackbuttonTime = BACKBUTTON_TIME; + if (mMouseCapture || m_use_mouse == 1) + { + MouseEventBack(MOUSE_Move, ev->data1, ev->data2); + return MouseEvent(MOUSE_Move, ev->data1, ev->data2); + } + } + else if (ev->subtype == EV_GUI_LButtonUp) + { + if (mMouseCapture) + { + ReleaseCapture(); + MouseEventBack(MOUSE_Release, ev->data1, ev->data2); + return MouseEvent(MOUSE_Release, ev->data1, ev->data2); + } + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MenuEvent (int mkey, bool fromcontroller) +{ + switch (mkey) + { + case MKEY_Back: + { + Close(); + S_Sound (CHAN_VOICE | CHAN_UI, + DMenu::CurrentMenu != NULL? "menu/backup" : "menu/clear", snd_menuvolume, ATTN_NONE); + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::Close () +{ + assert(DMenu::CurrentMenu == this); + DMenu::CurrentMenu = mParentMenu; + Destroy(); + if (DMenu::CurrentMenu != NULL) + { + GC::WriteBarrier(DMenu::CurrentMenu); + } + else + { + M_ClearMenus (); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MouseEvent(int type, int x, int y) +{ + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMenu::MouseEventBack(int type, int x, int y) +{ + if (m_show_backbutton >= 0) + { + FTexture *tex = TexMan[gameinfo.mBackButton]; + if (tex != NULL) + { + if (m_show_backbutton&1) x -= screen->GetWidth() - tex->GetScaledWidth() * CleanXfac; + if (m_show_backbutton&2) y -= screen->GetHeight() - tex->GetScaledHeight() * CleanYfac; + mBackbuttonSelected = (x >= 0 && x < tex->GetScaledWidth() * CleanXfac && y < tex->GetScaledHeight() * CleanYfac); + if (mBackbuttonSelected && type == MOUSE_Release) + { + if (m_use_mouse == 2) mBackbuttonSelected = false; + MenuEvent(MKEY_Back, true); + } + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::SetCapture() +{ + if (!mMouseCapture) + { + mMouseCapture = true; + I_SetMouseCapture(); + } +} + +void DMenu::ReleaseCapture() +{ + if (mMouseCapture) + { + mMouseCapture = false; + I_ReleaseMouseCapture(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMenu::Ticker () +{ +} + +void DMenu::Drawer () +{ + if (this == DMenu::CurrentMenu && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) + { + FTexture *tex = TexMan[gameinfo.mBackButton]; + int w = tex->GetScaledWidth() * CleanXfac; + int h = tex->GetScaledHeight() * CleanYfac; + int x = (!(m_show_backbutton&1))? 0:screen->GetWidth() - w; + int y = (!(m_show_backbutton&2))? 0:screen->GetHeight() - h; + if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) + { + screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, MAKEARGB(40, 255,255,255), TAG_DONE); + } + else + { + screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha, TAG_DONE); + } + } +} + +bool DMenu::DimAllowed() +{ + return true; +} + +bool DMenu::TranslateKeyboardEvents() +{ + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_StartControlPanel (bool makeSound) +{ + // intro might call this repeatedly + if (DMenu::CurrentMenu != NULL) + return; + + ResetButtonStates (); + for (int i = 0; i < NUM_MKEYS; ++i) + { + MenuButtons[i].ReleaseKey(0); + } + + C_HideConsole (); // [RH] Make sure console goes bye bye. + menuactive = MENU_On; + // Pause sound effects before we play the menu switch sound. + // That way, it won't be paused. + P_CheckTickerPaused (); + + if (makeSound) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + } + BackbuttonTime = 0; + BackbuttonAlpha = 0; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_ActivateMenu(DMenu *menu) +{ + if (menuactive == MENU_Off) menuactive = MENU_On; + if (DMenu::CurrentMenu != NULL) DMenu::CurrentMenu->ReleaseCapture(); + DMenu::CurrentMenu = menu; + GC::WriteBarrier(DMenu::CurrentMenu); +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_SetMenu(FName menu, int param) +{ + // some menus need some special treatment + switch (menu) + { + case NAME_Episodemenu: + // sent from the player class menu + GameStartupInfo.Skill = -1; + GameStartupInfo.Episode = -1; + GameStartupInfo.PlayerClass = + param == -1? "Random" : PlayerClasses[param].Type->Meta.GetMetaString (APMETA_DisplayName); + break; + + case NAME_Skillmenu: + // sent from the episode menu + + if ((gameinfo.flags & GI_SHAREWARE) && param > 0) + { + // Only Doom and Heretic have multi-episode shareware versions. + if (gameinfo.gametype == GAME_Doom) + { + M_StartMessage(GStrings("SWSTRING"), 1); + } + else + { + M_StartMessage(GStrings("MNU_ONLYREGISTERED"), 1); + } + return; + } + + GameStartupInfo.Episode = param; + M_StartupSkillMenu(&GameStartupInfo); // needs player class name from class menu (later) + break; + + case NAME_StartgameConfirm: + { + // sent from the skill menu for a skill that needs to be confirmed + GameStartupInfo.Skill = param; + + const char *msg = AllSkills[param].MustConfirmText; + if (*msg==0) msg = GStrings("NIGHTMARE"); + M_StartMessage (msg, 0, NAME_Startgame); + return; + } + + case NAME_Startgame: + // sent either from skill menu or confirmation screen. Skill gets only set if sent from skill menu + // Now we can finally start the game. Ugh... + if (GameStartupInfo.Skill == -1) GameStartupInfo.Skill = param; + + G_DeferedInitNew (&GameStartupInfo); + if (gamestate == GS_FULLCONSOLE) + { + gamestate = GS_HIDECONSOLE; + gameaction = ga_newgame; + } + M_ClearMenus (); + return; + + case NAME_Savegamemenu: + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) + { + // cannot save outside the game. + M_StartMessage (GStrings("SAVEDEAD"), 1); + return; + } + } + + // End of special checks + + FMenuDescriptor **desc = MenuDescriptors.CheckKey(menu); + if (desc != NULL) + { + if ((*desc)->mNetgameMessage.IsNotEmpty() && netgame) + { + M_StartMessage((*desc)->mNetgameMessage, 1); + return; + } + + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + if (ld->mAutoselect >= 0 && ld->mAutoselect < (int)ld->mItems.Size()) + { + // recursively activate the autoselected item without ever creating this menu. + ld->mItems[ld->mAutoselect]->Activate(); + } + else + { + const PClass *cls = ld->mClass == NULL? RUNTIME_CLASS(DListMenu) : ld->mClass; + + DListMenu *newmenu = (DListMenu *)cls->CreateNew(); + newmenu->Init(DMenu::CurrentMenu, ld); + M_ActivateMenu(newmenu); + } + } + else if ((*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *ld = static_cast(*desc); + const PClass *cls = ld->mClass == NULL? RUNTIME_CLASS(DOptionMenu) : ld->mClass; + + DOptionMenu *newmenu = (DOptionMenu *)cls->CreateNew(); + newmenu->Init(DMenu::CurrentMenu, ld); + M_ActivateMenu(newmenu); + } + return; + } + else + { + const PClass *menuclass = PClass::FindClass(menu); + if (menuclass != NULL) + { + if (menuclass->IsDescendantOf(RUNTIME_CLASS(DMenu))) + { + DMenu *newmenu = (DMenu*)menuclass->CreateNew(); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); + return; + } + } + } + Printf("Attempting to open menu of unknown type '%s'\n", menu.GetChars()); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool M_Responder (event_t *ev) +{ + int ch = 0; + bool keyup = false; + int mkey = NUM_MKEYS; + bool fromcontroller = true; + + if (chatmodeon) + { + return false; + } + + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + { + // There are a few input sources we are interested in: + // + // EV_KeyDown / EV_KeyUp : joysticks/gamepads/controllers + // EV_GUI_KeyDown / EV_GUI_KeyUp : the keyboard + // EV_GUI_Char : printable characters, which we want in string input mode + // + // This code previously listened for EV_GUI_KeyRepeat to handle repeating + // in the menus, but that doesn't work with gamepads, so now we combine + // the multiple inputs into buttons and handle the repetition manually. + if (ev->type == EV_GUI_Event) + { + fromcontroller = false; + if (ev->subtype == EV_GUI_KeyRepeat) + { + // We do our own key repeat handling but still want to eat the + // OS's repeated keys. + return true; + } + else if (ev->subtype == EV_GUI_BackButtonDown || ev->subtype == EV_GUI_BackButtonUp) + { + mkey = MKEY_Back; + keyup = ev->subtype == EV_GUI_BackButtonUp; + } + else if (ev->subtype != EV_GUI_KeyDown && ev->subtype != EV_GUI_KeyUp) + { + // do we want mouse input? + if (ev->subtype >= EV_GUI_FirstMouseEvent && ev->subtype <= EV_GUI_LastMouseEvent) + { + // FIXME: Mouse events in SDL code are mostly useless so mouse is + // disabled until that code is fixed + #ifdef _WIN32 + if (!m_use_mouse) + #endif + return true; + } + + // pass everything else on to the current menu + return DMenu::CurrentMenu->Responder(ev); + } + else if (DMenu::CurrentMenu->TranslateKeyboardEvents()) + { + ch = ev->data1; + keyup = ev->subtype == EV_GUI_KeyUp; + switch (ch) + { + case GK_BACK: mkey = MKEY_Back; break; + case GK_ESCAPE: mkey = MKEY_Back; break; + case GK_RETURN: mkey = MKEY_Enter; break; + case GK_UP: mkey = MKEY_Up; break; + case GK_DOWN: mkey = MKEY_Down; break; + case GK_LEFT: mkey = MKEY_Left; break; + case GK_RIGHT: mkey = MKEY_Right; break; + case GK_BACKSPACE: mkey = MKEY_Clear; break; + case GK_PGUP: mkey = MKEY_PageUp; break; + case GK_PGDN: mkey = MKEY_PageDown; break; + default: + if (!keyup) + { + return DMenu::CurrentMenu->Responder(ev); + } + break; + } + } + } + else if (ev->type == EV_KeyDown || ev->type == EV_KeyUp) + { + keyup = ev->type == EV_KeyUp; + + ch = ev->data1; + switch (ch) + { + case KEY_JOY1: + case KEY_PAD_A: + mkey = MKEY_Enter; + break; + + case KEY_JOY2: + case KEY_PAD_B: + mkey = MKEY_Back; + break; + + case KEY_JOY3: + case KEY_PAD_X: + mkey = MKEY_Clear; + break; + + case KEY_JOY5: + case KEY_PAD_LSHOULDER: + mkey = MKEY_PageUp; + break; + + case KEY_JOY6: + case KEY_PAD_RSHOULDER: + mkey = MKEY_PageDown; + break; + + case KEY_PAD_DPAD_UP: + case KEY_PAD_LTHUMB_UP: + case KEY_JOYAXIS1MINUS: + case KEY_JOYPOV1_UP: + mkey = MKEY_Up; + break; + + case KEY_PAD_DPAD_DOWN: + case KEY_PAD_LTHUMB_DOWN: + case KEY_JOYAXIS1PLUS: + case KEY_JOYPOV1_DOWN: + mkey = MKEY_Down; + break; + + case KEY_PAD_DPAD_LEFT: + case KEY_PAD_LTHUMB_LEFT: + case KEY_JOYAXIS2MINUS: + case KEY_JOYPOV1_LEFT: + mkey = MKEY_Left; + break; + + case KEY_PAD_DPAD_RIGHT: + case KEY_PAD_LTHUMB_RIGHT: + case KEY_JOYAXIS2PLUS: + case KEY_JOYPOV1_RIGHT: + mkey = MKEY_Right; + break; + } + } + + if (mkey != NUM_MKEYS) + { + if (keyup) + { + MenuButtons[mkey].ReleaseKey(ch); + return false; + } + else + { + MenuButtons[mkey].PressKey(ch); + MenuButtonOrigin[mkey] = fromcontroller; + if (mkey <= MKEY_PageDown) + { + MenuButtonTickers[mkey] = KEY_REPEAT_DELAY; + } + DMenu::CurrentMenu->MenuEvent(mkey, fromcontroller); + return true; + } + } + return DMenu::CurrentMenu->Responder(ev) || !keyup; + } + else + { + if (ev->type == EV_KeyDown) + { + // Pop-up menu? + if (ev->data1 == KEY_ESCAPE) + { + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); + return true; + } + // If devparm is set, pressing F1 always takes a screenshot no matter + // what it's bound to. (for those who don't bother to read the docs) + if (devparm && ev->data1 == KEY_F1) + { + G_ScreenShot(NULL); + return true; + } + return false; + } + else if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_LButtonDown && + ConsoleState != c_down && m_use_mouse) + { + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Ticker (void) +{ + DMenu::MenuTime++; + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + DMenu::CurrentMenu->Ticker(); + + for (int i = 0; i < NUM_MKEYS; ++i) + { + if (MenuButtons[i].bDown) + { + if (MenuButtonTickers[i] > 0 && --MenuButtonTickers[i] <= 0) + { + MenuButtonTickers[i] = KEY_REPEAT_RATE; + DMenu::CurrentMenu->MenuEvent(i, MenuButtonOrigin[i]); + } + } + } + + if (BackbuttonTime > 0) + { + if (BackbuttonAlpha < FRACUNIT) BackbuttonAlpha += FRACUNIT/10; + BackbuttonTime--; + } + else + { + if (BackbuttonAlpha > 0) BackbuttonAlpha -= FRACUNIT/10; + if (BackbuttonAlpha < 0) BackbuttonAlpha = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Drawer (void) +{ + player_t *player = &players[consoleplayer]; + AActor *camera = player->camera; + PalEntry fade = 0; + + if (!screen->Accel2D && camera != NULL && (gamestate == GS_LEVEL || gamestate == GS_TITLELEVEL)) + { + if (camera->player != NULL) + { + player = camera->player; + } + fade = PalEntry (BYTE(player->BlendA*255), BYTE(player->BlendR*255), BYTE(player->BlendG*255), BYTE(player->BlendB*255)); + } + + + if (DMenu::CurrentMenu != NULL && menuactive != MENU_Off) + { + if (DMenu::CurrentMenu->DimAllowed()) screen->Dim(fade); + DMenu::CurrentMenu->Drawer(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_ClearMenus () +{ + M_DemoNoPlay = false; + if (DMenu::CurrentMenu != NULL) + { + DMenu::CurrentMenu->Destroy(); + DMenu::CurrentMenu = NULL; + } + BorderNeedRefresh = screen->GetPageCount (); + menuactive = MENU_Off; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_Init (void) +{ + M_ParseMenuDefs(); + M_CreateMenus(); +} + + +//============================================================================= +// +// [RH] Most menus can now be accessed directly +// through console commands. +// +//============================================================================= + +CCMD (menu_main) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_Mainmenu, -1); +} + +CCMD (menu_load) +{ // F3 + M_StartControlPanel (true); + M_SetMenu(NAME_Loadgamemenu, -1); +} + +CCMD (menu_save) +{ // F2 + M_StartControlPanel (true); + M_SetMenu(NAME_Savegamemenu, -1); +} + +CCMD (menu_help) +{ // F1 + M_StartControlPanel (true); + M_SetMenu(NAME_Readthismenu, -1); +} + +CCMD (menu_game) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playerclassmenu, -1); // The playerclass menu is the first in the 'start game' chain +} + +CCMD (menu_options) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Optionsmenu, -1); +} + +CCMD (menu_player) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_Playermenu, -1); +} + +CCMD (menu_messages) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MessageOptions, -1); +} + +CCMD (menu_automap) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AutomapOptions, -1); +} + +CCMD (menu_scoreboard) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_ScoreboardOptions, -1); +} + +CCMD (menu_mapcolors) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MapColorMenu, -1); +} + +CCMD (menu_keys) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CustomizeControls, -1); +} + +CCMD (menu_gameplay) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_GameplayOptions, -1); +} + +CCMD (menu_compatibility) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_CompatibilityOptions, -1); +} + +CCMD (menu_mouse) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_MouseOptions, -1); +} + +CCMD (menu_joystick) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_JoystickOptions, -1); +} + +CCMD (menu_sound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_SoundOptions, -1); +} + +CCMD (menu_advsound) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_AdvSoundOptions, -1); +} + +CCMD (menu_modreplayer) +{ + M_StartControlPanel(true); + M_SetMenu(NAME_ModReplayerOptions, -1); +} + +CCMD (menu_display) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoOptions, -1); +} + +CCMD (menu_video) +{ + M_StartControlPanel (true); + M_SetMenu(NAME_VideoModeMenu, -1); +} + + + +CCMD (openmenu) +{ + if (argv.argc() < 2) + { + Printf("Usage: openmenu \"menu_name\""); + return; + } + M_StartControlPanel (true); + M_SetMenu(argv[1], -1); +} + +// +// Toggle messages on/off +// +CCMD (togglemessages) +{ + if (show_messages) + { + Printf (128, "%s\n", GStrings("MSGOFF")); + show_messages = false; + } + else + { + Printf (128, "%s\n", GStrings("MSGON")); + show_messages = true; + } +} + +EXTERN_CVAR (Int, screenblocks) + +CCMD (sizedown) +{ + screenblocks = screenblocks - 1; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD (sizeup) +{ + screenblocks = screenblocks + 1; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); +} + +CCMD(menuconsole) +{ + M_ClearMenus(); + C_ToggleConsole(); +} + +CCMD(reset2defaults) +{ + C_SetDefaultBindings (); + C_SetCVarsToDefaults (); + R_SetViewSize (screenblocks); +} + +CCMD(reset2saved) +{ + GameConfig->DoGlobalSetup (); + GameConfig->DoGameSetup (GameNames[gameinfo.gametype]); + R_SetViewSize (screenblocks); +} diff --git a/src/menu/menu.h b/src/menu/menu.h new file mode 100644 index 0000000000..b1073a0410 --- /dev/null +++ b/src/menu/menu.h @@ -0,0 +1,652 @@ +#ifndef __M_MENU_MENU_H__ +#define __M_MENU_MENU_H__ + + + + +#include "dobject.h" +#include "lists.h" +#include "d_player.h" +#include "r_translate.h" +#include "c_cvars.h" +#include "v_font.h" +#include "version.h" +#include "textures/textures.h" + +EXTERN_CVAR(Float, snd_menuvolume) +EXTERN_CVAR(Int, m_use_mouse); + + +struct event_t; +class FTexture; +class FFont; +enum EColorRange; +class FPlayerClass; +class FKeyBindings; + +enum EMenuKey +{ + MKEY_Up, + MKEY_Down, + MKEY_Left, + MKEY_Right, + MKEY_PageUp, + MKEY_PageDown, + //----------------- Keys past here do not repeat. + MKEY_Enter, + MKEY_Back, // Back to previous menu + MKEY_Clear, // Clear keybinding/flip player sprite preview + NUM_MKEYS, + + // These are not buttons but events sent from other menus + + MKEY_Input, // Sent when input is confirmed + MKEY_Abort, // Input aborted + MKEY_MBYes, + MKEY_MBNo, +}; + + +struct FGameStartup +{ + const char *PlayerClass; + int Episode; + int Skill; +}; + +extern FGameStartup GameStartupInfo; + +struct FSaveGameNode : public Node +{ + char Title[SAVESTRINGSIZE]; + FString Filename; + bool bOldVersion; + bool bMissingWads; + bool bNoDelete; + + FSaveGameNode() { bNoDelete = false; } +}; + + + +//============================================================================= +// +// menu descriptor. This is created from the menu definition lump +// Items must be inserted in the order they are cycled through with the cursor +// +//============================================================================= + +enum EMenuDescriptorType +{ + MDESC_ListMenu, + MDESC_OptionsMenu, +}; + +struct FMenuDescriptor +{ + FName mMenuName; + FString mNetgameMessage; + int mType; + + virtual ~FMenuDescriptor() {} +}; + +class FListMenuItem; +class FOptionMenuItem; + +struct FListMenuDescriptor : public FMenuDescriptor +{ + TDeletingArray mItems; + int mSelectedItem; + int mSelectOfsX; + int mSelectOfsY; + FTextureID mSelector; + int mDisplayTop; + int mXpos, mYpos; + int mWLeft, mWRight; + int mLinespacing; // needs to be stored for dynamically created menus + int mAutoselect; // this can only be set by internal menu creation functions + FFont *mFont; + EColorRange mFontColor; + EColorRange mFontColor2; + const PClass *mClass; + FMenuDescriptor *mRedirect; // used to redirect overlong skill and episode menus to option menu based alternatives +}; + +struct FOptionMenuSettings +{ + EColorRange mTitleColor; + EColorRange mFontColor; + EColorRange mFontColorValue; + EColorRange mFontColorMore; + EColorRange mFontColorHeader; + EColorRange mFontColorHighlight; + EColorRange mFontColorSelection; + int mLinespacing; + int mLabelOffset; +}; + +struct FOptionMenuDescriptor : public FMenuDescriptor +{ + TDeletingArray mItems; + FString mTitle; + int mSelectedItem; + int mDrawTop; + int mScrollTop; + int mScrollPos; + int mIndent; + int mPosition; + bool mDontDim; + const PClass *mClass; + + void CalcIndent(); + FOptionMenuItem *GetItem(FName name); + +}; + + +typedef TMap MenuDescriptorList; + +extern FOptionMenuSettings OptionSettings; +extern MenuDescriptorList MenuDescriptors; + +#define CURSORSPACE (14 * CleanXfac_1) + +//============================================================================= +// +// +// +//============================================================================= + +struct FMenuRect +{ + int x, y; + int width, height; + + void set(int _x, int _y, int _w, int _h) + { + x = _x; + y = _y; + width = _w; + height = _h; + } + + bool inside(int _x, int _y) + { + return _x >= x && _x < x+width && _y >= y && _y < y+height; + } + +}; + + +class DMenu : public DObject +{ + DECLARE_CLASS (DMenu, DObject) + HAS_OBJECT_POINTERS + +protected: + bool mMouseCapture; + bool mBackbuttonSelected; + +public: + enum + { + MOUSE_Click, + MOUSE_Move, + MOUSE_Release + }; + + enum + { + BACKBUTTON_TIME = 4*TICRATE + }; + + static DMenu *CurrentMenu; + static int MenuTime; + + TObjPtr mParentMenu; + + DMenu(DMenu *parent = NULL); + virtual bool Responder (event_t *ev); + virtual bool MenuEvent (int mkey, bool fromcontroller); + virtual void Ticker (); + virtual void Drawer (); + virtual bool DimAllowed (); + virtual bool TranslateKeyboardEvents(); + virtual void Close(); + virtual bool MouseEvent(int type, int x, int y); + bool MouseEventBack(int type, int x, int y); + void SetCapture(); + void ReleaseCapture(); + bool HasCapture() + { + return mMouseCapture; + } +}; + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +class FListMenuItem +{ +protected: + int mXpos, mYpos; + FName mAction; + +public: + bool mEnabled; + + FListMenuItem(int xpos = 0, int ypos = 0, FName action = NAME_None) + { + mXpos = xpos; + mYpos = ypos; + mAction = action; + mEnabled = true; + } + + virtual ~FListMenuItem(); + + virtual bool CheckCoordinate(int x, int y); + virtual void Ticker(); + virtual void Drawer(bool selected); + virtual bool Selectable(); + virtual bool Activate(); + virtual FName GetAction(int *pparam); + virtual bool SetString(int i, const char *s); + virtual bool GetString(int i, char *s, int len); + virtual bool SetValue(int i, int value); + virtual bool GetValue(int i, int *pvalue); + virtual void Enable(bool on); + virtual bool MenuEvent (int mkey, bool fromcontroller); + virtual bool MouseEvent(int type, int x, int y); + virtual bool CheckHotkey(int c); + void DrawSelector(int xofs, int yofs, FTextureID tex); + void OffsetPositionY(int ydelta) { mYpos += ydelta; } + int GetY() { return mYpos; } +}; + +class FListMenuItemStaticPatch : public FListMenuItem +{ +protected: + FTextureID mTexture; + bool mCentered; + +public: + FListMenuItemStaticPatch(int x, int y, FTextureID patch, bool centered); + void Drawer(bool selected); +}; + +class FListMenuItemStaticText : public FListMenuItem +{ +protected: + const char *mText; + FFont *mFont; + EColorRange mColor; + bool mCentered; + +public: + FListMenuItemStaticText(int x, int y, const char *text, FFont *font, EColorRange color, bool centered); + ~FListMenuItemStaticText(); + void Drawer(bool selected); +}; + +//============================================================================= +// +// the player sprite window +// +//============================================================================= + +class FListMenuItemPlayerDisplay : public FListMenuItem +{ + FListMenuDescriptor *mOwner; + FTexture *mBackdrop; + FRemapTable mRemap; + FPlayerClass *mPlayerClass; + FState *mPlayerState; + int mPlayerTics; + bool mNoportrait; + BYTE mRotation; + BYTE mMode; // 0: automatic (used by class selection), 1: manual (used by player setup) + BYTE mTranslate; + int mSkin; + int mRandomClass; + int mRandomTimer; + int mClassNum; + + void SetPlayerClass(int classnum, bool force = false); + bool UpdatePlayerClass(); + void UpdateRandomClass(); + +public: + + enum + { + PDF_ROTATION = 0x10001, + PDF_SKIN = 0x10002, + PDF_CLASS = 0x10003, + PDF_MODE = 0x10004, + PDF_TRANSLATE = 0x10005, + }; + + FListMenuItemPlayerDisplay(FListMenuDescriptor *menu, int x, int y, PalEntry c1, PalEntry c2, bool np, FName action); + ~FListMenuItemPlayerDisplay(); + virtual void Ticker(); + virtual void Drawer(bool selected); + bool SetValue(int i, int value); +}; + + +//============================================================================= +// +// selectable items +// +//============================================================================= + +class FListMenuItemSelectable : public FListMenuItem +{ +protected: + int mHotkey; + int mHeight; + int mParam; + +public: + FListMenuItemSelectable(int x, int y, int height, FName childmenu, int mParam = -1); + bool CheckCoordinate(int x, int y); + bool Selectable(); + bool CheckHotkey(int c); + bool Activate(); + bool MouseEvent(int type, int x, int y); + FName GetAction(int *pparam); +}; + +class FListMenuItemText : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mColor; +public: + FListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, EColorRange color, FName child, int param = 0); + ~FListMenuItemText(); + void Drawer(bool selected); +}; + +class FListMenuItemPatch : public FListMenuItemSelectable +{ + FTextureID mTexture; +public: + FListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID patch, FName child, int param = 0); + void Drawer(bool selected); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FPlayerNameBox : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mFontColor; + int mFrameSize; + char mPlayerName[MAXPLAYERNAME+1]; + char mEditName[MAXPLAYERNAME+2]; + bool mEntering; + + void DrawBorder (int x, int y, int len); + +public: + + FPlayerNameBox(int x, int y, int height, int frameofs, const char *text, FFont *font, EColorRange color, FName action); + ~FPlayerNameBox(); + bool SetString(int i, const char *s); + bool GetString(int i, char *s, int len); + void Drawer(bool selected); + bool MenuEvent (int mkey, bool fromcontroller); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FValueTextItem : public FListMenuItemSelectable +{ + TArray mSelections; + const char *mText; + int mSelection; + FFont *mFont; + EColorRange mFontColor; + EColorRange mFontColor2; + +public: + + FValueTextItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, EColorRange valuecolor, FName action, FName values); + ~FValueTextItem(); + bool SetString(int i, const char *s); + bool SetValue(int i, int value); + bool GetValue(int i, int *pvalue); + bool MenuEvent (int mkey, bool fromcontroller); + void Drawer(bool selected); +}; + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +class FSliderItem : public FListMenuItemSelectable +{ + const char *mText; + FFont *mFont; + EColorRange mFontColor; + int mMinrange, mMaxrange; + int mStep; + int mSelection; + + void DrawSlider (int x, int y); + +public: + + FSliderItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, FName action, int min, int max, int step); + ~FSliderItem(); + bool SetValue(int i, int value); + bool GetValue(int i, int *pvalue); + bool MenuEvent (int mkey, bool fromcontroller); + void Drawer(bool selected); + bool MouseEvent(int type, int x, int y); +}; + +//============================================================================= +// +// list menu class runs a menu described by a FListMenuDescriptor +// +//============================================================================= + +class DListMenu : public DMenu +{ + DECLARE_CLASS(DListMenu, DMenu) + +protected: + FListMenuDescriptor *mDesc; + FListMenuItem *mFocusControl; + +public: + DListMenu(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + virtual void Init(DMenu *parent = NULL, FListMenuDescriptor *desc = NULL); + FListMenuItem *GetItem(FName name); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); + void SetFocus(FListMenuItem *fc) + { + mFocusControl = fc; + } + bool CheckFocus(FListMenuItem *fc) + { + return mFocusControl == fc; + } + void ReleaseFocus() + { + mFocusControl = NULL; + } +}; + + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +class FOptionMenuItem : public FListMenuItem +{ +protected: + char *mLabel; + bool mCentered; + + void drawLabel(int indent, int y, EColorRange color, bool grayed = false); +public: + + FOptionMenuItem(const char *text, FName action = NAME_None, bool center = false) + : FListMenuItem(0, 0, action) + { + mLabel = copystring(text); + mCentered = center; + } + + ~FOptionMenuItem(); + virtual bool CheckCoordinate(FOptionMenuDescriptor *desc, int x, int y); + virtual int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected); + virtual bool Selectable(); + virtual int GetIndent(); + virtual bool MouseEvent(int type, int x, int y); +}; + +//============================================================================= +// +// +// +//============================================================================= +struct FOptionValues +{ + struct Pair + { + double Value; + FString TextValue; + FString Text; + }; + + TArray mValues; +}; + +typedef TMap< FName, FOptionValues* > FOptionMap; + +extern FOptionMap OptionValues; + + +//============================================================================= +// +// Option menu class runs a menu described by a FOptionMenuDescriptor +// +//============================================================================= + +class DOptionMenu : public DMenu +{ + DECLARE_CLASS(DOptionMenu, DMenu) + + bool CanScrollUp; + bool CanScrollDown; + int VisBottom; + FOptionMenuItem *mFocusControl; + +protected: + FOptionMenuDescriptor *mDesc; + +public: + FOptionMenuItem *GetItem(FName name); + DOptionMenu(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); + virtual void Init(DMenu *parent = NULL, FOptionMenuDescriptor *desc = NULL); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); + const FOptionMenuDescriptor *GetDescriptor() const { return mDesc; } + void SetFocus(FOptionMenuItem *fc) + { + mFocusControl = fc; + } + bool CheckFocus(FOptionMenuItem *fc) + { + return mFocusControl == fc; + } + void ReleaseFocus() + { + mFocusControl = NULL; + } +}; + + +//============================================================================= +// +// Input some text +// +//============================================================================= + +class DTextEnterMenu : public DMenu +{ + DECLARE_ABSTRACT_CLASS(DTextEnterMenu, DMenu) + + char *mEnterString; + unsigned int mEnterSize; + unsigned int mEnterPos; + int mSizeMode; // 1: size is length in chars. 2: also check string width + bool mInputGridOkay; + + int InputGridX; + int InputGridY; + +public: + + DTextEnterMenu(DMenu *parent, char *textbuffer, int maxlen, int sizemode, bool showgrid); + + void Drawer (); + bool MenuEvent (int mkey, bool fromcontroller); + bool Responder(event_t *ev); + bool TranslateKeyboardEvents(); + bool MouseEvent(int type, int x, int y); + +}; + + + + +struct event_t; +bool M_Responder (event_t *ev); +void M_Ticker (void); +void M_Drawer (void); +void M_Init (void); +void M_CreateMenus(); +void M_ActivateMenu(DMenu *menu); +void M_ClearMenus (); +void M_ParseMenuDefs(); +void M_StartupSkillMenu(FGameStartup *gs); +void M_StartControlPanel (bool makeSound); +void M_SetMenu(FName menu, int param = -1); +void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); +void M_StartMessage(const char *message, int messagemode, FName action = NAME_None); +DMenu *StartPickerMenu(DMenu *parent, const char *name, FColorCVar *cvar); +void M_RefreshModesList (); +void M_InitVideoModesMenu (); + + +#endif \ No newline at end of file diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp new file mode 100644 index 0000000000..31fe0fd3b5 --- /dev/null +++ b/src/menu/menudef.cpp @@ -0,0 +1,1389 @@ +/* +** menudef.cpp +** MENUDEF parser amd menu generation code +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ +#include + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" +#include "gi.h" + +#include "optionmenuitems.h" + +MenuDescriptorList MenuDescriptors; +static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus +static FOptionMenuDescriptor DefaultOptionMenuSettings; // contains common settings for all Option menus +FOptionMenuSettings OptionSettings; +FOptionMap OptionValues; + +static void DeinitMenus() +{ + { + MenuDescriptorList::Iterator it(MenuDescriptors); + + MenuDescriptorList::Pair *pair; + + while (it.NextPair(pair)) + { + delete pair->Value; + pair->Value = NULL; + } + } + + { + FOptionMap::Iterator it(OptionValues); + + FOptionMap::Pair *pair; + + while (it.NextPair(pair)) + { + delete pair->Value; + pair->Value = NULL; + } + } + DMenu::CurrentMenu = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +static void SkipSubBlock(FScanner &sc) +{ + sc.MustGetStringName("{"); + int depth = 1; + while (depth > 0) + { + sc.MustGetString(); + if (sc.Compare("{")) depth++; + if (sc.Compare("}")) depth--; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool CheckSkipGameBlock(FScanner &sc) +{ + int filter = 0; + sc.MustGetStringName("("); + do + { + sc.MustGetString(); + if (sc.Compare("Doom")) filter |= GAME_Doom; + if (sc.Compare("Heretic")) filter |= GAME_Heretic; + if (sc.Compare("Hexen")) filter |= GAME_Hexen; + if (sc.Compare("Strife")) filter |= GAME_Strife; + if (sc.Compare("Chex")) filter |= GAME_Chex; + } + while (sc.CheckString(",")); + sc.MustGetStringName(")"); + if (!(gameinfo.gametype & filter)) + { + SkipSubBlock(sc); + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool CheckSkipOptionBlock(FScanner &sc) +{ + bool filter = false; + sc.MustGetStringName("("); + do + { + sc.MustGetString(); + if (sc.Compare("ReadThis")) filter |= gameinfo.drawreadthis; + else if (sc.Compare("Windows")) + { + #ifdef _WIN32 + filter = true; + #endif + } + else if (sc.Compare("unix")) + { + #ifdef unix + filter = true; + #endif + } + else if (sc.Compare("Mac")) + { + #ifdef __APPLE__ + filter = true; + #endif + } + } + while (sc.CheckString(",")); + sc.MustGetStringName(")"); + if (!filter) + { + SkipSubBlock(sc); + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseListMenuBody(FScanner &sc, FListMenuDescriptor *desc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseListMenuBody(sc, desc); + } + } + else if (sc.Compare("ifoption")) + { + if (!CheckSkipOptionBlock(sc)) + { + // recursively parse sub-block + ParseListMenuBody(sc, desc); + } + } + else if (sc.Compare("Class")) + { + sc.MustGetString(); + const PClass *cls = PClass::FindClass(sc.String); + if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DListMenu))) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; + } + else if (sc.Compare("Selector")) + { + sc.MustGetString(); + desc->mSelector = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mSelectOfsX = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mSelectOfsY = sc.Number; + } + else if (sc.Compare("Linespacing")) + { + sc.MustGetNumber(); + desc->mLinespacing = sc.Number; + } + else if (sc.Compare("Position")) + { + sc.MustGetNumber(); + desc->mXpos = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mYpos = sc.Number; + } + else if (sc.Compare("MouseWindow")) + { + sc.MustGetNumber(); + desc->mWLeft = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + desc->mWRight = sc.Number; + } + else if (sc.Compare("StaticPatch") || sc.Compare("StaticPatchCentered")) + { + bool centered = sc.Compare("StaticPatchCentered"); + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + + FListMenuItem *it = new FListMenuItemStaticPatch(x, y, tex, centered); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticText") || sc.Compare("StaticTextCentered")) + { + bool centered = sc.Compare("StaticTextCentered"); + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FListMenuItem *it = new FListMenuItemStaticText(x, y, sc.String, desc->mFont, desc->mFontColor, centered); + desc->mItems.Push(it); + } + else if (sc.Compare("PatchItem")) + { + sc.MustGetString(); + FTextureID tex = TexMan.CheckForTexture(sc.String, FTexture::TEX_MiscPatch); + sc.MustGetStringName(","); + sc.MustGetString(); + int hotkey = sc.String[0]; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + int param = 0; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + param = sc.Number; + } + + FListMenuItem *it = new FListMenuItemPatch(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, tex, action, param); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("TextItem")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + int hotkey = sc.String[0]; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + int param = 0; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + param = sc.Number; + } + + FListMenuItem *it = new FListMenuItemText(desc->mXpos, desc->mYpos, desc->mLinespacing, hotkey, text, desc->mFont, desc->mFontColor, action, param); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + + } + else if (sc.Compare("Font")) + { + sc.MustGetString(); + FFont *newfont = V_GetFont(sc.String); + if (newfont != NULL) desc->mFont = newfont; + if (sc.CheckString(",")) + { + sc.MustGetString(); + desc->mFontColor2 = desc->mFontColor = V_FindFontColor((FName)sc.String); + if (sc.CheckString(",")) + { + sc.MustGetString(); + desc->mFontColor2 = V_FindFontColor((FName)sc.String); + } + } + else + { + desc->mFontColor = OptionSettings.mFontColor; + desc->mFontColor2 = OptionSettings.mFontColorValue; + } + } + else if (sc.Compare("NetgameMessage")) + { + sc.MustGetString(); + desc->mNetgameMessage = sc.String; + } + else if (sc.Compare("PlayerDisplay")) + { + bool noportrait = false; + FName action = NAME_None; + sc.MustGetNumber(); + int x = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int y = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + PalEntry c1 = V_GetColor(NULL, sc.String); + sc.MustGetStringName(","); + sc.MustGetString(); + PalEntry c2 = V_GetColor(NULL, sc.String); + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + noportrait = !!sc.Number; + if (sc.CheckString(",")) + { + sc.MustGetString(); + action = sc.String; + } + } + FListMenuItemPlayerDisplay *it = new FListMenuItemPlayerDisplay(desc, x, y, c1, c2, noportrait, action); + desc->mItems.Push(it); + } + else if (sc.Compare("PlayerNameBox")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int ofs = sc.Number; + sc.MustGetStringName(","); + sc.MustGetString(); + FListMenuItem *it = new FPlayerNameBox(desc->mXpos, desc->mYpos, desc->mLinespacing, ofs, text, desc->mFont, desc->mFontColor, sc.String); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("ValueText")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + FName values; + if (sc.CheckString(",")) + { + sc.MustGetString(); + values = sc.String; + } + FListMenuItem *it = new FValueTextItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, desc->mFontColor2, action, values); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else if (sc.Compare("Slider")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString action = sc.String; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int min = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int max = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + int step = sc.Number; + FListMenuItem *it = new FSliderItem(desc->mXpos, desc->mYpos, desc->mLinespacing, text, desc->mFont, desc->mFontColor, action, min, max, step); + desc->mItems.Push(it); + desc->mYpos += desc->mLinespacing; + if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size()-1; + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseListMenu(FScanner &sc) +{ + sc.MustGetString(); + + FListMenuDescriptor *desc = new FListMenuDescriptor; + desc->mType = MDESC_ListMenu; + desc->mMenuName = sc.String; + desc->mSelectedItem = -1; + desc->mAutoselect = -1; + desc->mSelectOfsX = DefaultListMenuSettings.mSelectOfsX; + desc->mSelectOfsY = DefaultListMenuSettings.mSelectOfsY; + desc->mSelector = DefaultListMenuSettings.mSelector; + desc->mDisplayTop = DefaultListMenuSettings.mDisplayTop; + desc->mXpos = DefaultListMenuSettings.mXpos; + desc->mYpos = DefaultListMenuSettings.mYpos; + desc->mLinespacing = DefaultListMenuSettings.mLinespacing; + desc->mNetgameMessage = DefaultListMenuSettings.mNetgameMessage; + desc->mFont = DefaultListMenuSettings.mFont; + desc->mFontColor = DefaultListMenuSettings.mFontColor; + desc->mFontColor2 = DefaultListMenuSettings.mFontColor2; + desc->mClass = NULL; + desc->mRedirect = NULL; + desc->mWLeft = 0; + desc->mWRight = 0; + + FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); + if (pOld != NULL && *pOld != NULL) delete *pOld; + MenuDescriptors[desc->mMenuName] = desc; + + ParseListMenuBody(sc, desc); +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionValue(FScanner &sc) +{ + FName optname; + + FOptionValues *val = new FOptionValues; + sc.MustGetString(); + optname = sc.String; + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)]; + sc.MustGetFloat(); + pair.Value = sc.Float; + sc.MustGetStringName(","); + sc.MustGetString(); + pair.Text = strbin1(sc.String); + } + FOptionValues **pOld = OptionValues.CheckKey(optname); + if (pOld != NULL && *pOld != NULL) delete *pOld; + OptionValues[optname] = val; +} + + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionString(FScanner &sc) +{ + FName optname; + + FOptionValues *val = new FOptionValues; + sc.MustGetString(); + optname = sc.String; + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + FOptionValues::Pair &pair = val->mValues[val->mValues.Reserve(1)]; + sc.MustGetString(); + pair.Value = DBL_MAX; + pair.TextValue = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + pair.Text = strbin1(sc.String); + } + FOptionValues **pOld = OptionValues.CheckKey(optname); + if (pOld != NULL && *pOld != NULL) delete *pOld; + OptionValues[optname] = val; +} + + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionSettings(FScanner &sc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseOptionSettings(sc); + } + } + else if (sc.Compare("Linespacing")) + { + sc.MustGetNumber(); + OptionSettings.mLinespacing = sc.Number; + } + else if (sc.Compare("LabelOffset")) + { + sc.MustGetNumber(); + OptionSettings.mLabelOffset = sc.Number; + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionMenuBody(FScanner &sc, FOptionMenuDescriptor *desc) +{ + sc.MustGetStringName("{"); + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("ifgame")) + { + if (!CheckSkipGameBlock(sc)) + { + // recursively parse sub-block + ParseOptionMenuBody(sc, desc); + } + } + else if (sc.Compare("ifoption")) + { + if (!CheckSkipOptionBlock(sc)) + { + // recursively parse sub-block + ParseOptionMenuBody(sc, desc); + } + } + else if (sc.Compare("Class")) + { + sc.MustGetString(); + const PClass *cls = PClass::FindClass(sc.String); + if (cls == NULL || !cls->IsDescendantOf(RUNTIME_CLASS(DOptionMenu))) + { + sc.ScriptError("Unknown menu class '%s'", sc.String); + } + desc->mClass = cls; + } + else if (sc.Compare("Title")) + { + sc.MustGetString(); + desc->mTitle = sc.String; + } + else if (sc.Compare("Position")) + { + sc.MustGetNumber(); + desc->mPosition = sc.Number; + } + else if (sc.Compare("DefaultSelection")) + { + sc.MustGetNumber(); + desc->mSelectedItem = sc.Number; + } + else if (sc.Compare("ScrollTop")) + { + sc.MustGetNumber(); + desc->mScrollTop = sc.Number; + } + else if (sc.Compare("Indent")) + { + sc.MustGetNumber(); + desc->mIndent = sc.Number; + } + else if (sc.Compare("Submenu")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemSubmenu(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("Option")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString cvar = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString values = sc.String; + FString check; + int center = 0; + if (sc.CheckString(",")) + { + sc.MustGetString(); + if (*sc.String != 0) check = sc.String; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + center = sc.Number; + } + } + FOptionMenuItem *it = new FOptionMenuItemOption(label, cvar, values, check, center); + desc->mItems.Push(it); + } + else if (sc.Compare("Command")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemCommand(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("SafeCommand")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemSafeCommand(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("Control") || sc.Compare("MapControl")) + { + bool map = sc.Compare("MapControl"); + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemControl(label, sc.String, map? &AutomapBindings : &Bindings); + desc->mItems.Push(it); + } + else if (sc.Compare("ColorPicker")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuItemColorPicker(label, sc.String); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticText")) + { + sc.MustGetString(); + FString label = sc.String; + bool cr = false; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + cr = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuItemStaticText(label, cr); + desc->mItems.Push(it); + } + else if (sc.Compare("StaticTextSwitchable")) + { + sc.MustGetString(); + FString label = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString label2 = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FName action = sc.String; + bool cr = false; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + cr = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuItemStaticTextSwitchable(label, label2, action, cr); + desc->mItems.Push(it); + } + else if (sc.Compare("Slider")) + { + sc.MustGetString(); + FString text = sc.String; + sc.MustGetStringName(","); + sc.MustGetString(); + FString action = sc.String; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double min = sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double max = sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + double step = sc.Float; + bool showvalue = true; + if (sc.CheckString(",")) + { + sc.MustGetNumber(); + showvalue = !!sc.Number; + } + FOptionMenuItem *it = new FOptionMenuSliderCVar(text, action, min, max, step, showvalue? 1:-1); + desc->mItems.Push(it); + } + else if (sc.Compare("screenresolution")) + { + sc.MustGetString(); + FOptionMenuItem *it = new FOptionMenuScreenResolutionLine(sc.String); + desc->mItems.Push(it); + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void ParseOptionMenu(FScanner &sc) +{ + sc.MustGetString(); + + FOptionMenuDescriptor *desc = new FOptionMenuDescriptor; + desc->mType = MDESC_OptionsMenu; + desc->mMenuName = sc.String; + desc->mSelectedItem = -1; + desc->mScrollPos = 0; + desc->mClass = NULL; + desc->mPosition = DefaultOptionMenuSettings.mPosition; + desc->mScrollTop = DefaultOptionMenuSettings.mScrollTop; + desc->mIndent = DefaultOptionMenuSettings.mIndent; + desc->mDontDim = DefaultOptionMenuSettings.mDontDim; + + FMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); + if (pOld != NULL && *pOld != NULL) delete *pOld; + MenuDescriptors[desc->mMenuName] = desc; + + ParseOptionMenuBody(sc, desc); + + if (desc->mIndent == 0) desc->CalcIndent(); +} + + +//============================================================================= +// +// +// +//============================================================================= + +void M_ParseMenuDefs() +{ + int lump, lastlump = 0; + + OptionSettings.mTitleColor = V_FindFontColor(gameinfo.mTitleColor); + OptionSettings.mFontColor = V_FindFontColor(gameinfo.mFontColor); + OptionSettings.mFontColorValue = V_FindFontColor(gameinfo.mFontColorValue); + OptionSettings.mFontColorMore = V_FindFontColor(gameinfo.mFontColorMore); + OptionSettings.mFontColorHeader = V_FindFontColor(gameinfo.mFontColorHeader); + OptionSettings.mFontColorHighlight = V_FindFontColor(gameinfo.mFontColorHighlight); + OptionSettings.mFontColorSelection = V_FindFontColor(gameinfo.mFontColorSelection); + + atterm( DeinitMenus); + while ((lump = Wads.FindLump ("MENUDEF", &lastlump)) != -1) + { + FScanner sc(lump); + + sc.SetCMode(true); + while (sc.GetString()) + { + if (sc.Compare("LISTMENU")) + { + ParseListMenu(sc); + } + else if (sc.Compare("DEFAULTLISTMENU")) + { + ParseListMenuBody(sc, &DefaultListMenuSettings); + } + else if (sc.Compare("OPTIONVALUE")) + { + ParseOptionValue(sc); + } + else if (sc.Compare("OPTIONSTRING")) + { + ParseOptionString(sc); + } + else if (sc.Compare("OPTIONMENUSETTINGS")) + { + ParseOptionSettings(sc); + } + else if (sc.Compare("OPTIONMENU")) + { + ParseOptionMenu(sc); + } + else if (sc.Compare("DEFAULTOPTIONMENU")) + { + ParseOptionMenuBody(sc, &DefaultOptionMenuSettings); + } + else + { + sc.ScriptError("Unknown keyword '%s'", sc.String); + } + } + } +} + + +//============================================================================= +// +// Creates the episode menu +// Falls back on an option menu if there's not enough screen space to show all episodes +// +//============================================================================= + +static void BuildEpisodeMenu() +{ + // Build episode menu + bool success = false; + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Episodemenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + int posy = ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + AllEpisodes.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || AllEpisodes.Size() == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy -= topdelta; + } + + ld->mSelectedItem = ld->mItems.Size(); + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + FListMenuItem *it; + if (AllEpisodes[i].mPicName.IsNotEmpty()) + { + FTextureID tex = TexMan.CheckForTexture(AllEpisodes[i].mPicName, FTexture::TEX_MiscPatch); + it = new FListMenuItemPatch(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, + tex, NAME_Skillmenu, i); + } + else + { + it = new FListMenuItemText(ld->mXpos, posy, ld->mLinespacing, AllEpisodes[i].mShortcut, + AllEpisodes[i].mEpisodeName, ld->mFont, ld->mFontColor, NAME_Skillmenu, i); + } + ld->mItems.Push(it); + posy += ld->mLinespacing; + } + if (AllEpisodes.Size() == 1) + { + ld->mAutoselect = ld->mSelectedItem; + } + success = true; + } + } + } + if (!success) + { + // Couldn't create the episode menu, either because there's too many episodes or some error occured + // Create an option menu for episode selection instead. + FOptionMenuDescriptor *od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Episodemenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Episodemenu; + od->mTitle = "$MNU_EPISODE"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + for(unsigned i = 0; i < AllEpisodes.Size(); i++) + { + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(AllEpisodes[i].mEpisodeName, "Skillmenu", i); + od->mItems.Push(it); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +static void BuildPlayerclassMenu() +{ + bool success = false; + + // Build player class menu + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Playerclassmenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + // add player display + ld->mSelectedItem = ld->mItems.Size(); + + int posy = ld->mYpos; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // Count the number of items this menu will show + int numclassitems = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + numclassitems++; + } + } + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + (numclassitems+1) * ld->mLinespacing - topy; + + if (totalheight <= 190 || numclassitems == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + posy -= topdelta; + } + + int n = 0; + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, i); + ld->mItems.Push(it); + ld->mYpos += ld->mLinespacing; + n++; + } + } + } + if (n > 1) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, 'r', + "$MNU_RANDOM", ld->mFont,ld->mFontColor, NAME_Episodemenu, -1); + ld->mItems.Push(it); + } + if (n == 0) + { + const char *pname = PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FListMenuItemText *it = new FListMenuItemText(ld->mXpos, ld->mYpos, ld->mLinespacing, *pname, + pname, ld->mFont,ld->mFontColor, NAME_Episodemenu, 0); + ld->mItems.Push(it); + } + } + if (n < 2) + { + ld->mAutoselect = ld->mSelectedItem; + } + success = true; + } + } + } + if (!success) + { + // Couldn't create the playerclass menu, either because there's too many episodes or some error occured + // Create an option menu for class selection instead. + FOptionMenuDescriptor *od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Playerclassmenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Playerclassmenu; + od->mTitle = "$MNU_CHOOSECLASS"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + od->mNetgameMessage = "$NETGAME"; + + for (unsigned i = 0; i < PlayerClasses.Size (); i++) + { + if (!(PlayerClasses[i].Flags & PCF_NOMENU)) + { + const char *pname = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + if (pname != NULL) + { + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu(pname, "Episodemenu", i); + od->mItems.Push(it); + } + } + } + FOptionMenuItemSubmenu *it = new FOptionMenuItemSubmenu("Random", "Episodemenu", -1); + od->mItems.Push(it); + } +} + +//============================================================================= +// +// Reads any XHAIRS lumps for the names of crosshairs and +// adds them to the display options menu. +// +//============================================================================= + +static void InitCrosshairsList() +{ + int lastlump, lump; + + lastlump = 0; + + FOptionValues **opt = OptionValues.CheckKey(NAME_Crosshairs); + if (opt == NULL) + { + return; // no crosshair value list present. No need to go on. + } + + FOptionValues::Pair *pair = &(*opt)->mValues[(*opt)->mValues.Reserve(1)]; + pair->Value = 0; + pair->Text = "None"; + + while ((lump = Wads.FindLump("XHAIRS", &lastlump)) != -1) + { + FScanner sc(lump); + while (sc.GetNumber()) + { + FOptionValues::Pair value; + value.Value = sc.Number; + sc.MustGetString(); + value.Text = sc.String; + if (value.Value != 0) + { // Check if it already exists. If not, add it. + unsigned int i; + + for (i = 1; i < (*opt)->mValues.Size(); ++i) + { + if ((*opt)->mValues[i].Value == value.Value) + { + break; + } + } + if (i < (*opt)->mValues.Size()) + { + (*opt)->mValues[i].Text = value.Text; + } + else + { + (*opt)->mValues.Push(value); + } + } + } + } +} + +//============================================================================= +// +// With the current workings of the menu system this cannot be done any longer +// from within the respective CCMDs. +// +//============================================================================= + +static void InitKeySections() +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_CustomizeControls); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_OptionsMenu) + { + FOptionMenuDescriptor *menu = static_cast(*desc); + + for (unsigned i = 0; i < KeySections.Size(); i++) + { + FKeySection *sect = &KeySections[i]; + FOptionMenuItem *item = new FOptionMenuItemStaticText(" ", false); + menu->mItems.Push(item); + item = new FOptionMenuItemStaticText(sect->mTitle, true); + menu->mItems.Push(item); + for (unsigned j = 0; j < sect->mActions.Size(); j++) + { + FKeyAction *act = §->mActions[j]; + item = new FOptionMenuItemControl(act->mTitle, act->mAction, &Bindings); + menu->mItems.Push(item); + } + } + } + } +} + +//============================================================================= +// +// Special menus will be created once all engine data is loaded +// +//============================================================================= + +void M_CreateMenus() +{ + BuildEpisodeMenu(); + BuildPlayerclassMenu(); + InitCrosshairsList(); + InitKeySections(); + UpdateJoystickMenu(NULL); + + FOptionValues **opt = OptionValues.CheckKey(NAME_Mididevices); + if (opt != NULL) + { + I_BuildMIDIMenuList(*opt); + } +} + +//============================================================================= +// +// THe skill menu must be refeshed each time it starts up +// +//============================================================================= + +void M_StartupSkillMenu(FGameStartup *gs) +{ + static bool done = false; + bool success = false; + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu); + if (desc != NULL) + { + if ((*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor *ld = static_cast(*desc); + int x = ld->mXpos; + int y = ld->mYpos; + if (gameinfo.gametype == GAME_Hexen) + { + // THere really needs to be a better way to do this... :( + if (gs->PlayerClass != NULL) + { + if (!stricmp(gs->PlayerClass, "fighter")) x = 120; + else if (!stricmp(gs->PlayerClass, "cleric")) x = 116; + else if (!stricmp(gs->PlayerClass, "mage")) x = 112; + } + } + + // Delete previous contents + for(unsigned i=0; imItems.Size(); i++) + { + FName n = ld->mItems[i]->GetAction(NULL); + if (n == NAME_Startgame || n == NAME_StartgameConfirm) + { + for(unsigned j=i; jmItems.Size(); j++) + { + delete ld->mItems[j]; + } + ld->mItems.Resize(i); + break; + } + } + + if (!done) + { + done = true; + int defskill = DefaultSkill; + if ((unsigned int)defskill >= AllSkills.Size()) + { + defskill = (AllSkills.Size() - 1) / 2; + } + ld->mSelectedItem = ld->mItems.Size() + defskill; + + int posy = y; + int topy = posy; + + // Get lowest y coordinate of any static item in the menu + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + int y = ld->mItems[i]->GetY(); + if (y < topy) topy = y; + } + + // center the menu on the screen if the top space is larger than the bottom space + int totalheight = posy + AllSkills.Size() * ld->mLinespacing - topy; + + if (totalheight < 190 || AllSkills.Size() == 1) + { + int newtop = (200 - totalheight + topy) / 2; + int topdelta = newtop - topy; + if (topdelta < 0) + { + for(unsigned i = 0; i < ld->mItems.Size(); i++) + { + ld->mItems[i]->OffsetPositionY(topdelta); + } + y = ld->mYpos = posy - topdelta; + } + } + else + { + // too large + delete ld; + desc = NULL; + done = false; + goto fail; + } + } + + unsigned firstitem = ld->mItems.Size(); + for(unsigned int i = 0; i < AllSkills.Size(); i++) + { + FSkillInfo &skill = AllSkills[i]; + FListMenuItem *li; + // Using a different name for skills that must be confirmed makes handling this easier. + FName action = skill.MustConfirm? NAME_StartgameConfirm : NAME_Startgame; + + FString *pItemText = NULL; + if (gs->PlayerClass != NULL) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + + if (skill.PicName.Len() != 0 && pItemText == NULL) + { + FTextureID tex = TexMan.CheckForTexture(skill.PicName, FTexture::TEX_MiscPatch); + li = new FListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i); + } + else + { + EColorRange color = (EColorRange)skill.GetTextColor(); + if (color == CR_UNTRANSLATED) color = ld->mFontColor; + li = new FListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut, + pItemText? *pItemText : skill.MenuName, ld->mFont, color, action, i); + } + ld->mItems.Push(li); + y += ld->mLinespacing; + } + if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1) + { + ld->mAutoselect = MIN(2u, AllEpisodes.Size()-1); + } + success = true; + } + } + if (success) return; +fail: + // Option menu fallback for overlong skill lists + FOptionMenuDescriptor *od; + if (desc == NULL) + { + od = new FOptionMenuDescriptor; + if (desc != NULL) delete *desc; + MenuDescriptors[NAME_Skillmenu] = od; + od->mType = MDESC_OptionsMenu; + od->mMenuName = NAME_Skillmenu; + od->mTitle = "$MNU_CHOOSESKILL"; + od->mSelectedItem = 0; + od->mScrollPos = 0; + od->mClass = NULL; + od->mPosition = -15; + od->mScrollTop = 0; + od->mIndent = 160; + od->mDontDim = false; + } + else + { + od = static_cast(*desc); + for(unsigned i=0;imItems.Size(); i++) + { + delete od->mItems[i]; + } + od->mItems.Clear(); + } + for(unsigned int i = 0; i < AllSkills.Size(); i++) + { + FSkillInfo &skill = AllSkills[i]; + FOptionMenuItem *li; + // Using a different name for skills that must be confirmed makes handling this easier. + const char *action = skill.MustConfirm? "StartgameConfirm" : "Startgame"; + + FString *pItemText = NULL; + if (gs->PlayerClass != NULL) + { + pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); + } + li = new FOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i); + od->mItems.Push(li); + if (!done) + { + done = true; + int defskill = DefaultSkill; + if ((unsigned int)defskill >= AllSkills.Size()) + { + defskill = (AllSkills.Size() - 1) / 2; + } + od->mSelectedItem = defskill; + } + } +} diff --git a/src/menu/menuinput.cpp b/src/menu/menuinput.cpp new file mode 100644 index 0000000000..0f35b7e1f5 --- /dev/null +++ b/src/menu/menuinput.cpp @@ -0,0 +1,366 @@ +/* +** menuinput.cpp +** The string input code +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "v_video.h" +#include "c_cvars.h" +#include "d_event.h" +#include "d_gui.h" +#include "v_font.h" +#include "v_palette.h" + +IMPLEMENT_ABSTRACT_CLASS(DTextEnterMenu) + +#define INPUTGRID_WIDTH 13 +#define INPUTGRID_HEIGHT 5 + +// Heretic and Hexen do not, by default, come with glyphs for all of these +// characters. Oh well. Doom and Strife do. +static const char InputGridChars[INPUTGRID_WIDTH * INPUTGRID_HEIGHT] = + "ABCDEFGHIJKLM" + "NOPQRSTUVWXYZ" + "0123456789+-=" + ".,!?@'\":;[]()" + "<>^#$%&*/_ \b"; + + +CVAR(Bool, m_showinputgrid, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + +//============================================================================= +// +// +// +//============================================================================= + +DTextEnterMenu::DTextEnterMenu(DMenu *parent, char *textbuffer, int maxlen, int sizemode, bool showgrid) +: DMenu(parent) +{ + mEnterString = textbuffer; + mEnterSize = maxlen; + mEnterPos = (unsigned)strlen(textbuffer); + mSizeMode = sizemode; + mInputGridOkay = showgrid || m_showinputgrid; + if (mEnterPos > 0) + { + InputGridX = INPUTGRID_WIDTH - 1; + InputGridY = INPUTGRID_HEIGHT - 1; + } + else + { + // If we are naming a new save, don't start the cursor on "end". + InputGridX = 0; + InputGridY = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::TranslateKeyboardEvents() +{ + return mInputGridOkay; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::Responder(event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + // Save game and player name string input + if (ev->subtype == EV_GUI_Char) + { + mInputGridOkay = false; + if (mEnterPos < mEnterSize && + (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) + { + mEnterString[mEnterPos] = (char)ev->data1; + mEnterString[++mEnterPos] = 0; + } + return true; + } + char ch = (char)ev->data1; + if ((ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) && ch == '\b') + { + if (mEnterPos > 0) + { + mEnterPos--; + mEnterString[mEnterPos] = 0; + } + } + else if (ev->subtype == EV_GUI_KeyDown) + { + if (ch == GK_ESCAPE) + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Abort, false); + return true; + } + else if (ch == '\r') + { + if (mEnterString[0]) + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Input, false); + return true; + } + } + } + if (ev->subtype == EV_GUI_KeyDown || ev->subtype == EV_GUI_KeyRepeat) + { + return true; + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::MouseEvent(int type, int x, int y) +{ + const int cell_width = 18 * CleanXfac; + const int cell_height = 12 * CleanYfac; + const int screen_y = screen->GetHeight() - INPUTGRID_HEIGHT * cell_height; + const int screen_x = (screen->GetWidth() - INPUTGRID_WIDTH * cell_width) / 2; + + if (x >= screen_x && x < screen_x + INPUTGRID_WIDTH * cell_width && y >= screen_y) + { + InputGridX = (x - screen_x) / cell_width; + InputGridY = (y - screen_y) / cell_height; + if (type == DMenu::MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + if (m_use_mouse == 2) InputGridX = InputGridY = -1; + return true; + } + } + } + else + { + InputGridX = InputGridY = -1; + } + return Super::MouseEvent(type, x, y); +} + + + +//============================================================================= +// +// +// +//============================================================================= + +bool DTextEnterMenu::MenuEvent (int key, bool fromcontroller) +{ + if (key == MKEY_Back) + { + mParentMenu->MenuEvent(MKEY_Abort, false); + return Super::MenuEvent(key, fromcontroller); + } + if (fromcontroller) + { + mInputGridOkay = true; + } + + if (mInputGridOkay) + { + int ch; + + if (InputGridX == -1 || InputGridY == -1) + { + InputGridX = InputGridY = 0; + } + switch (key) + { + case MKEY_Down: + InputGridY = (InputGridY + 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Up: + InputGridY = (InputGridY + INPUTGRID_HEIGHT - 1) % INPUTGRID_HEIGHT; + return true; + + case MKEY_Right: + InputGridX = (InputGridX + 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Left: + InputGridX = (InputGridX + INPUTGRID_WIDTH - 1) % INPUTGRID_WIDTH; + return true; + + case MKEY_Clear: + if (mEnterPos > 0) + { + mEnterString[--mEnterPos] = 0; + } + return true; + + case MKEY_Enter: + assert(unsigned(InputGridX) < INPUTGRID_WIDTH && unsigned(InputGridY) < INPUTGRID_HEIGHT); + if (mInputGridOkay) + { + ch = InputGridChars[InputGridX + InputGridY * INPUTGRID_WIDTH]; + if (ch == 0) // end + { + if (mEnterString[0] != '\0') + { + DMenu *parent = mParentMenu; + Close(); + parent->MenuEvent(MKEY_Input, false); + return true; + } + } + else if (ch == '\b') // bs + { + if (mEnterPos > 0) + { + mEnterString[--mEnterPos] = 0; + } + } + else if (mEnterPos < mEnterSize && + (mSizeMode == 2/*entering player name*/ || (size_t)SmallFont->StringWidth(mEnterString) < (mEnterSize-1)*8)) + { + mEnterString[mEnterPos] = ch; + mEnterString[++mEnterPos] = 0; + } + } + return true; + + default: + break; // Keep GCC quiet + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DTextEnterMenu::Drawer () +{ + mParentMenu->Drawer(); + if (mInputGridOkay) + { + const int cell_width = 18 * CleanXfac; + const int cell_height = 12 * CleanYfac; + const int top_padding = cell_height / 2 - SmallFont->GetHeight() * CleanYfac / 2; + + // Darken the background behind the character grid. + // Unless we frame it with a border, I think it looks better to extend the + // background across the full width of the screen. + screen->Dim(0, 0.8f, + 0 /*screen->GetWidth()/2 - 13 * cell_width / 2*/, + screen->GetHeight() - INPUTGRID_HEIGHT * cell_height, + screen->GetWidth() /*13 * cell_width*/, + INPUTGRID_HEIGHT * cell_height); + + if (InputGridX >= 0 && InputGridY >= 0) + { + // Highlight the background behind the selected character. + screen->Dim(MAKERGB(255,248,220), 0.6f, + InputGridX * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2, + InputGridY * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(), + cell_width, cell_height); + } + + for (int y = 0; y < INPUTGRID_HEIGHT; ++y) + { + const int yy = y * cell_height - INPUTGRID_HEIGHT * cell_height + screen->GetHeight(); + for (int x = 0; x < INPUTGRID_WIDTH; ++x) + { + int width; + const int xx = x * cell_width - INPUTGRID_WIDTH * cell_width / 2 + screen->GetWidth() / 2; + const int ch = InputGridChars[y * INPUTGRID_WIDTH + x]; + FTexture *pic = SmallFont->GetChar(ch, &width); + EColorRange color; + FRemapTable *remap; + + // The highlighted character is yellow; the rest are dark gray. + color = (x == InputGridX && y == InputGridY) ? CR_YELLOW : CR_DARKGRAY; + remap = SmallFont->GetColorTranslation(color); + + if (pic != NULL) + { + // Draw a normal character. + screen->DrawTexture(pic, xx + cell_width/2 - width*CleanXfac/2, yy + top_padding, + DTA_Translation, remap, + DTA_CleanNoMove, true, + TAG_DONE); + } + else if (ch == ' ') + { + // Draw the space as a box outline. We also draw it 50% wider than it really is. + const int x1 = xx + cell_width/2 - width * CleanXfac * 3 / 4; + const int x2 = x1 + width * 3 * CleanXfac / 2; + const int y1 = yy + top_padding; + const int y2 = y1 + SmallFont->GetHeight() * CleanYfac; + const int palentry = remap->Remap[remap->NumEntries*2/3]; + const uint32 palcolor = remap->Palette[remap->NumEntries*2/3]; + screen->Clear(x1, y1, x2, y1+CleanYfac, palentry, palcolor); // top + screen->Clear(x1, y2, x2, y2+CleanYfac, palentry, palcolor); // bottom + screen->Clear(x1, y1+CleanYfac, x1+CleanXfac, y2, palentry, palcolor); // left + screen->Clear(x2-CleanXfac, y1+CleanYfac, x2, y2, palentry, palcolor); // right + } + else if (ch == '\b' || ch == 0) + { + // Draw the backspace and end "characters". + const char *const str = ch == '\b' ? "BS" : "ED"; + screen->DrawText(SmallFont, color, + xx + cell_width/2 - SmallFont->StringWidth(str)*CleanXfac/2, + yy + top_padding, str, DTA_CleanNoMove, true, TAG_DONE); + } + } + } + } + Super::Drawer(); +} \ No newline at end of file diff --git a/src/menu/messagebox.cpp b/src/menu/messagebox.cpp new file mode 100644 index 0000000000..bde298894d --- /dev/null +++ b/src/menu/messagebox.cpp @@ -0,0 +1,733 @@ +/* +** messagebox.cpp +** Confirmation, notification screns +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "d_event.h" +#include "d_gui.h" +#include "v_video.h" +#include "v_text.h" +#include "d_main.h" +#include "gstrings.h" +#include "gi.h" +#include "i_video.h" +#include "st_start.h" +#include "c_dispatch.h" +#include "g_game.h" + + +extern FSaveGameNode *quickSaveSlot; + +class DMessageBoxMenu : public DMenu +{ + DECLARE_CLASS(DMessageBoxMenu, DMenu) + + FBrokenLines *mMessage; + int mMessageMode; + int messageSelection; + int mMouseLeft, mMouseRight, mMouseY; + FName mAction; + +public: + + DMessageBoxMenu(DMenu *parent = NULL, const char *message = NULL, int messagemode = 0, bool playsound = false, FName action = NAME_None); + void Destroy(); + void Init(DMenu *parent, const char *message, int messagemode, bool playsound = false); + void Drawer(); + bool Responder(event_t *ev); + bool MenuEvent(int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void CloseSound(); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DMessageBoxMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DMessageBoxMenu::DMessageBoxMenu(DMenu *parent, const char *message, int messagemode, bool playsound, FName action) +: DMenu(parent) +{ + mAction = action; + messageSelection = 0; + mMouseLeft = 140; + mMouseY = INT_MIN; + int mr1 = 170 + SmallFont->StringWidth(GStrings["TXT_YES"]); + int mr2 = 170 + SmallFont->StringWidth(GStrings["TXT_NO"]); + mMouseRight = MAX(mr1, mr2); + + Init(parent, message, messagemode, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Init(DMenu *parent, const char *message, int messagemode, bool playsound) +{ + mParentMenu = parent; + if (message != NULL) + { + if (*message == '$') message = GStrings(message+1); + mMessage = V_BreakLines(SmallFont, 300, message); + } + else mMessage = NULL; + mMessageMode = messagemode; + if (playsound) + { + S_StopSound (CHAN_VOICE); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/prompt", snd_menuvolume, ATTN_NONE); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Destroy() +{ + if (mMessage != NULL) V_FreeBrokenLines(mMessage); + mMessage = NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::CloseSound() +{ + S_Sound (CHAN_VOICE | CHAN_UI, + DMenu::CurrentMenu != NULL? "menu/backup" : "menu/dismiss", snd_menuvolume, ATTN_NONE); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::HandleResult(bool res) +{ + if (mParentMenu != NULL) + { + if (mMessageMode == 0) + { + if (mAction == NAME_None) + { + mParentMenu->MenuEvent(res? MKEY_MBYes : MKEY_MBNo, false); + Close(); + } + else + { + Close(); + if (res) M_SetMenu(mAction, -1); + } + CloseSound(); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DMessageBoxMenu::Drawer () +{ + int i, y; + PalEntry fade = 0; + + int fontheight = SmallFont->GetHeight(); + //BorderNeedRefresh = screen->GetPageCount (); + //SB_state = screen->GetPageCount (); + + y = 100; + + if (mMessage != NULL) + { + for (i = 0; mMessage[i].Width >= 0; i++) + y -= SmallFont->GetHeight () / 2; + + for (i = 0; mMessage[i].Width >= 0; i++) + { + screen->DrawText (SmallFont, CR_UNTRANSLATED, 160 - mMessage[i].Width/2, y, mMessage[i].Text, + DTA_Clean, true, TAG_DONE); + y += fontheight; + } + } + + if (mMessageMode == 0) + { + y += fontheight; + mMouseY = y; + screen->DrawText(SmallFont, + messageSelection == 0? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, + 160, y, GStrings["TXT_YES"], DTA_Clean, true, TAG_DONE); + screen->DrawText(SmallFont, + messageSelection == 1? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, + 160, y + fontheight + 1, GStrings["TXT_NO"], DTA_Clean, true, TAG_DONE); + + if (messageSelection >= 0) + { + if ((DMenu::MenuTime%8) < 6) + { + screen->DrawText(ConFont, OptionSettings.mFontColorSelection, + (150 - 160) * CleanXfac + screen->GetWidth() / 2, + (y + (fontheight + 1) * messageSelection - 100) * CleanYfac + screen->GetHeight() / 2, + "\xd", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); + } + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::Responder(event_t *ev) +{ + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown) + { + if (mMessageMode == 0) + { + int ch = tolower(ev->data1); + if (ch == 'n' || ch == ' ') + { + HandleResult(false); + return true; + } + else if (ch == 'y') + { + HandleResult(true); + return true; + } + } + else + { + Close(); + return true; + } + return false; + } + else if (ev->type == EV_KeyDown) + { + Close(); + return true; + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::MenuEvent(int mkey, bool fromcontroller) +{ + if (mMessageMode == 0) + { + if (mkey == MKEY_Up || mkey == MKEY_Down) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + messageSelection = !messageSelection; + return true; + } + else if (mkey == MKEY_Enter) + { + // 0 is yes, 1 is no + HandleResult(!messageSelection); + return true; + } + else if (mkey == MKEY_Back) + { + HandleResult(false); + return true; + } + return false; + } + else + { + Close(); + CloseSound(); + return true; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DMessageBoxMenu::MouseEvent(int type, int x, int y) +{ + if (mMessageMode == 1) + { + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; + } + else + { + int sel = -1; + int fh = SmallFont->GetHeight() + 1; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; + + if (x >= mMouseLeft && x <= mMouseRight && y >= mMouseY && y < mMouseY + 2 * fh) + { + sel = y >= mMouseY + fh; + } + if (sel != -1 && sel != messageSelection) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + messageSelection = sel; + if (type == MOUSE_Release) + { + return MenuEvent(MKEY_Enter, true); + } + return true; + } +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuitMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuitMenu, DMessageBoxMenu) + +public: + + DQuitMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuitMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuitMenu::DQuitMenu(bool playsound) +{ + int messageindex = gametic % gameinfo.quitmessages.Size(); + FString EndString; + const char *msg = gameinfo.quitmessages[messageindex]; + if (msg[0] == '$') + { + if (msg[1] == '*') + { + EndString = GStrings(msg+2); + } + else + { + EndString.Format("%s\n\n%s", GStrings(msg+1), GStrings("DOSY")); + } + } + else EndString = gameinfo.quitmessages[messageindex]; + + Init(NULL, EndString, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuitMenu::HandleResult(bool res) +{ + if (res) + { + if (!netgame) + { + if (gameinfo.quitSound.IsNotEmpty()) + { + S_Sound (CHAN_VOICE | CHAN_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); + I_WaitVBL (105); + } + } + ST_Endoom(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (menu_quit) +{ // F10 + M_StartControlPanel (true); + DMenu *newmenu = new DQuitMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + + + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DEndGameMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DEndGameMenu, DMessageBoxMenu) + +public: + + DEndGameMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DEndGameMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DEndGameMenu::DEndGameMenu(bool playsound) +{ + int messageindex = gametic % gameinfo.quitmessages.Size(); + FString EndString = gameinfo.quitmessages[messageindex]; + + if (netgame) + { + if(gameinfo.gametype == GAME_Chex) + EndString = GStrings("CNETEND"); + else + EndString = GStrings("NETEND"); + return; + } + + if(gameinfo.gametype == GAME_Chex) + EndString = GStrings("CENDGAME"); + else + EndString = GStrings("ENDGAME"); + + + Init(NULL, EndString, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DEndGameMenu::HandleResult(bool res) +{ + if (res) + { + M_ClearMenus (); + D_StartTitle (); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (menu_endgame) +{ // F7 + if (!usergame) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + //M_StartControlPanel (true); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + DMenu *newmenu = new DEndGameMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuickSaveMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuickSaveMenu, DMessageBoxMenu) + +public: + + DQuickSaveMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuickSaveMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuickSaveMenu::DQuickSaveMenu(bool playsound) +{ + FString tempstring; + + if(gameinfo.gametype == GAME_Chex) + tempstring.Format(GStrings("CQSPROMPT"), quickSaveSlot->Title); + else + tempstring.Format(GStrings("QSPROMPT"), quickSaveSlot->Title); + + Init(NULL, tempstring, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuickSaveMenu::HandleResult(bool res) +{ + if (res) + { + G_SaveGame (quickSaveSlot->Filename.GetChars(), quickSaveSlot->Title); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quicksave) +{ // F6 + if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + return; + } + + if (gamestate != GS_LEVEL) + return; + + S_Sound (CHAN_VOICE | CHAN_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + if (quickSaveSlot == NULL) + { + M_StartControlPanel(false); + M_SetMenu(NAME_Savegamemenu); + return; + } + DMenu *newmenu = new DQuickSaveMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= +//============================================================================= +// +// +// +//============================================================================= + +class DQuickLoadMenu : public DMessageBoxMenu +{ + DECLARE_CLASS(DQuickLoadMenu, DMessageBoxMenu) + +public: + + DQuickLoadMenu(bool playsound = false); + virtual void HandleResult(bool res); +}; + +IMPLEMENT_CLASS(DQuickLoadMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DQuickLoadMenu::DQuickLoadMenu(bool playsound) +{ + FString tempstring; + + if(gameinfo.gametype == GAME_Chex) + tempstring.Format(GStrings("CQLPROMPT"), quickSaveSlot->Title); + else + tempstring.Format(GStrings("QLPROMPT"), quickSaveSlot->Title); + + Init(NULL, tempstring, 0, playsound); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DQuickLoadMenu::HandleResult(bool res) +{ + if (res) + { + G_LoadGame (quickSaveSlot->Filename.GetChars()); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + M_ClearMenus(); + } + else + { + Close(); + CloseSound(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +CCMD (quickload) +{ // F9 + M_StartControlPanel (true); + + if (netgame) + { + if(gameinfo.gametype == GAME_Chex) + M_StartMessage (GStrings("CQLOADNET"), NULL); + else + M_StartMessage (GStrings("QLOADNET"), NULL); + return; + } + + if (quickSaveSlot == NULL) + { + M_StartControlPanel(false); + // signal that whatever gets loaded should be the new quicksave + quickSaveSlot = (FSaveGameNode *)1; + M_SetMenu(NAME_Loadgamemenu); + return; + } + DMenu *newmenu = new DQuickLoadMenu(false); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_StartMessage(const char *message, int messagemode, FName action) +{ + if (DMenu::CurrentMenu == NULL) + { + // only play a sound if no menu was active before + M_StartControlPanel(menuactive == MENU_Off); + } + DMenu *newmenu = new DMessageBoxMenu(DMenu::CurrentMenu, message, messagemode, false, action); + newmenu->mParentMenu = DMenu::CurrentMenu; + M_ActivateMenu(newmenu); +} + diff --git a/src/menu/optionmenu.cpp b/src/menu/optionmenu.cpp new file mode 100644 index 0000000000..735b674ea6 --- /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) + { + M_DrawConText(CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12"); + M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 78) / range)) * CleanXfac_1), y, "\x13"); + + if (fracdigits >= 0) + { + char textbuf[16]; + mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); + screen->DrawText(SmallFont, CR_DARKGRAY, x + (12*8 + 4) * CleanXfac_1, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE); + } + } + else + { + // On 320x200 we need a shorter slider + M_DrawConText(CR_WHITE, x, y, "\x10\x11\x11\x11\x11\x11\x12"); + M_DrawConText(CR_ORANGE, x + int((5 + ((ccur * 38) / range)) * CleanXfac_1), y, "\x13"); + + if (fracdigits >= 0) + { + char textbuf[16]; + mysnprintf(textbuf, countof(textbuf), "%.*f", fracdigits, cur); + screen->DrawText(SmallFont, CR_DARKGRAY, x + (7*8 + 4) * CleanXfac_1, y, textbuf, DTA_CleanNoMove_1, true, TAG_DONE); + } + } +} + + + +IMPLEMENT_CLASS(DOptionMenu) + +//============================================================================= +// +// +// +//============================================================================= + +DOptionMenu::DOptionMenu(DMenu *parent, FOptionMenuDescriptor *desc) +: DMenu(parent) +{ + CanScrollUp = false; + CanScrollDown = false; + VisBottom = 0; + mFocusControl = NULL; + Init(parent, desc); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Init(DMenu *parent, FOptionMenuDescriptor *desc) +{ + mParentMenu = parent; + GC::WriteBarrier(this, parent); + mDesc = desc; + if (mDesc != NULL && mDesc->mSelectedItem < 0) + { + // Go down to the first selectable item + int i = -1; + mDesc->mSelectedItem = -1; + do + { + i++; + } + while (!mDesc->mItems[i]->Selectable() && i < (int)mDesc->mItems.Size()); + if (i>=0) mDesc->mSelectedItem = i; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuItem *DOptionMenu::GetItem(FName name) +{ + for(unsigned i=0;imItems.Size(); i++) + { + FName nm = mDesc->mItems[i]->GetAction(NULL); + if (nm == name) return mDesc->mItems[i]; + } + return NULL; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event) + { + if (ev->subtype == EV_GUI_WheelUp) + { + if (mDesc->mScrollPos > 0) + { + mDesc->mScrollPos--; + } + return true; + } + else if (ev->subtype == EV_GUI_WheelDown) + { + if (CanScrollDown) + { + mDesc->mScrollPos++; + VisBottom++; + } + return true; + } + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int startedAt = mDesc->mSelectedItem; + + switch (mkey) + { + case MKEY_Up: + do + { + --mDesc->mSelectedItem; + + if (mDesc->mScrollPos > 0 && + mDesc->mSelectedItem == mDesc->mScrollTop + mDesc->mScrollPos) + { + mDesc->mScrollPos--; + } + + if (mDesc->mSelectedItem < 0) + { + // Figure out how many lines of text fit on the menu + int y = mDesc->mPosition; + + if (y <= 0) + { + if (BigFont && mDesc->mTitle.IsNotEmpty()) + { + y = -y + BigFont->GetHeight(); + } + else + { + y = -y; + } + } + y *= CleanYfac_1; + int rowheight = OptionSettings.mLinespacing * CleanYfac_1; + int maxitems = (screen->GetHeight() - rowheight - y) / rowheight + 1; + + mDesc->mScrollPos = MAX (0, (int)mDesc->mItems.Size() - maxitems + mDesc->mScrollTop); + mDesc->mSelectedItem = mDesc->mItems.Size()-1; + } + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + break; + + case MKEY_Down: + do + { + ++mDesc->mSelectedItem; + + if (CanScrollDown && mDesc->mSelectedItem == VisBottom) + { + mDesc->mScrollPos++; + VisBottom++; + } + if (mDesc->mSelectedItem >= (int)mDesc->mItems.Size()) + { + mDesc->mSelectedItem = 0; + mDesc->mScrollPos = 0; + } + } + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable() && mDesc->mSelectedItem != startedAt); + break; + + case MKEY_PageUp: + if (mDesc->mScrollPos > 0) + { + mDesc->mScrollPos -= VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; + if (mDesc->mScrollPos < 0) + { + mDesc->mScrollPos = 0; + } + mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos + 1; + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) + { + ++mDesc->mSelectedItem; + } + } + break; + + case MKEY_PageDown: + if (CanScrollDown) + { + int pagesize = VisBottom - mDesc->mScrollPos - mDesc->mScrollTop; + mDesc->mScrollPos += pagesize; + if (mDesc->mScrollPos + mDesc->mScrollTop + pagesize > (int)mDesc->mItems.Size()) + { + mDesc->mScrollPos = mDesc->mItems.Size() - mDesc->mScrollTop - pagesize; + } + mDesc->mSelectedItem = mDesc->mScrollTop + mDesc->mScrollPos; + while (!mDesc->mItems[mDesc->mSelectedItem]->Selectable()) + { + ++mDesc->mSelectedItem; + } + } + break; + + case MKEY_Enter: + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + { + return true; + } + // fall through to default + default: + if (mDesc->mSelectedItem >= 0 && + mDesc->mItems[mDesc->mSelectedItem]->MenuEvent(mkey, fromcontroller)) return true; + return Super::MenuEvent(mkey, fromcontroller); + } + + if (mDesc->mSelectedItem != startedAt) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DOptionMenu::MouseEvent(int type, int x, int y) +{ + y = (y / CleanYfac_1) - mDesc->mDrawTop; + + if (mFocusControl) + { + mFocusControl->MouseEvent(type, x, y); + return true; + } + else + { + int yline = (y / OptionSettings.mLinespacing); + if (yline >= mDesc->mScrollTop) + { + yline += mDesc->mScrollPos; + } + if ((unsigned)yline < mDesc->mItems.Size() && mDesc->mItems[yline]->Selectable()) + { + if (yline != mDesc->mSelectedItem) + { + mDesc->mSelectedItem = yline; + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mDesc->mItems[yline]->MouseEvent(type, x, y); + return true; + } + } + mDesc->mSelectedItem = -1; + return Super::MouseEvent(type, x, y); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Ticker () +{ + Super::Ticker(); + for(unsigned i=0;imItems.Size(); i++) + { + mDesc->mItems[i]->Ticker(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DOptionMenu::Drawer () +{ + int y = mDesc->mPosition; + + if (y <= 0) + { + if (BigFont && mDesc->mTitle.IsNotEmpty()) + { + const char *tt = mDesc->mTitle; + if (*tt == '$') tt = GStrings(tt+1); + screen->DrawText (BigFont, OptionSettings.mTitleColor, + (screen->GetWidth() - BigFont->StringWidth(tt) * CleanXfac_1) / 2, 10*CleanYfac_1, + tt, DTA_CleanNoMove_1, true, TAG_DONE); + y = -y + BigFont->GetHeight(); + } + else + { + y = -y; + } + } + mDesc->mDrawTop = y; + //int labelofs = OptionSettings.mLabelOffset * CleanXfac_1; + //int cursorspace = 14 * CleanXfac_1; + int fontheight = OptionSettings.mLinespacing * CleanYfac_1; + y *= CleanYfac_1; + + int indent = mDesc->mIndent; + if (indent > 280) + { // kludge for the compatibility options with their extremely long labels + if (indent + 40 <= CleanWidth_1) + { + indent = (screen->GetWidth() - ((indent + 40) * CleanXfac_1)) / 2 + indent * CleanXfac_1; + } + else + { + indent = screen->GetWidth() - 40 * CleanXfac_1; + } + } + else + { + indent = (indent - 160) * CleanXfac_1 + screen->GetWidth() / 2; + } + + int ytop = y + mDesc->mScrollTop * 8 * CleanYfac_1; + int lastrow = screen->GetHeight() - SmallFont->GetHeight() * CleanYfac_1; + + unsigned i; + for (i = 0; i < mDesc->mItems.Size() && y <= lastrow; i++, y += fontheight) + { + // Don't scroll the uppermost items + if (i == mDesc->mScrollTop) + { + i += mDesc->mScrollPos; + if (i >= mDesc->mItems.Size()) break; // skipped beyond end of menu + } + int cur_indent = mDesc->mItems[i]->Draw(mDesc, y, indent, mDesc->mSelectedItem == i); + if (cur_indent >= 0 && mDesc->mSelectedItem == i && mDesc->mItems[i]->Selectable()) + { + if (((DMenu::MenuTime%8) < 6) || DMenu::CurrentMenu != this) + { + M_DrawConText(OptionSettings.mFontColorSelection, cur_indent + 3 * CleanXfac_1, y-CleanYfac_1+OptionSettings.mLabelOffset, "\xd"); + } + } + } + + CanScrollUp = (mDesc->mScrollPos > 0); + CanScrollDown = (i < mDesc->mItems.Size()); + VisBottom = i - 1; + + if (CanScrollUp) + { + M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, ytop + OptionSettings.mLabelOffset, "\x1a"); + } + if (CanScrollDown) + { + M_DrawConText(CR_ORANGE, 3 * CleanXfac_1, y - 8*CleanYfac_1 + OptionSettings.mLabelOffset, "\x1b"); + } + Super::Drawer(); +} + + +//============================================================================= +// +// base class for menu items +// +//============================================================================= + +FOptionMenuItem::~FOptionMenuItem() +{ + if (mLabel != NULL) delete [] mLabel; +} + +bool FOptionMenuItem::CheckCoordinate(FOptionMenuDescriptor *desc, int x, int y) +{ + return false; +} + +int FOptionMenuItem::Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) +{ + return indent; +} + +bool FOptionMenuItem::Selectable() +{ + return true; +} + +bool FOptionMenuItem::MouseEvent(int type, int x, int y) +{ + if (Selectable() && type == DMenu::MOUSE_Release) + { + return DMenu::CurrentMenu->MenuEvent(MKEY_Enter, true); + } + return false; +} + +int FOptionMenuItem::GetIndent() +{ + return mCentered? 0 : SmallFont->StringWidth(mLabel); +} + +void FOptionMenuItem::drawLabel(int indent, int y, EColorRange color, bool grayed) +{ + const char *label = mLabel; + if (*label == '$') label = GStrings(label+1); + + int overlay = grayed? MAKEARGB(96,48,0,0) : 0; + + int x; + int w = SmallFont->StringWidth(label) * CleanXfac_1; + if (!mCentered) x = indent - w; + else x = (screen->GetWidth() - w) / 2; + screen->DrawText (SmallFont, color, x, y, label, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); +} + + + +void FOptionMenuDescriptor::CalcIndent() +{ + // calculate the menu indent + int widest = 0, thiswidth; + + for (unsigned i = 0; i < mItems.Size(); i++) + { + thiswidth = mItems[i]->GetIndent(); + if (thiswidth > widest) widest = thiswidth; + } + mIndent = widest + 4; +} + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuItem *FOptionMenuDescriptor::GetItem(FName name) +{ + for(unsigned i=0;iGetAction(NULL); + if (nm == name) return mItems[i]; + } + return NULL; +} + + + + +class DGameplayMenu : public DOptionMenu +{ + DECLARE_CLASS(DGameplayMenu, DOptionMenu) + +public: + DGameplayMenu() + {} + + void Drawer () + { + Super::Drawer(); + + char text[64]; + mysnprintf(text, 64, "dmflags = %d dmflags2 = %d", *dmflags, *dmflags2); + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, + (screen->GetWidth() - SmallFont->StringWidth (text) * CleanXfac_1) / 2, 0, text, + DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_CLASS(DGameplayMenu) + +class DCompatibilityMenu : public DOptionMenu +{ + DECLARE_CLASS(DCompatibilityMenu, DOptionMenu) + +public: + DCompatibilityMenu() + {} + + void Drawer () + { + Super::Drawer(); + + char text[64]; + mysnprintf(text, 64, "compatflags = %d", *compatflags); + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, + (screen->GetWidth() - SmallFont->StringWidth (text) * CleanXfac_1) / 2, 0, text, + DTA_CleanNoMove_1, true, TAG_DONE); + } +}; + +IMPLEMENT_CLASS(DCompatibilityMenu) diff --git a/src/menu/optionmenuitems.h b/src/menu/optionmenuitems.h new file mode 100644 index 0000000000..07a72fb539 --- /dev/null +++ b/src/menu/optionmenuitems.h @@ -0,0 +1,916 @@ +/* +** optionmenuitems.h +** Control items for option menus +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +void M_DrawConText (int color, int x, int y, const char *str); +void M_DrawSlider (int x, int y, double min, double max, double cur,int fracdigits); +void M_SetVideoMode(); + + + +//============================================================================= +// +// opens a submenu, action is a submenu name +// +//============================================================================= + +class FOptionMenuItemSubmenu : public FOptionMenuItem +{ + int mParam; +public: + FOptionMenuItemSubmenu(const char *label, const char *menu, int param = 0) + : FOptionMenuItem(label, menu) + { + mParam = param; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColorMore); + return indent; + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + M_SetMenu(mAction, mParam); + return true; + } +}; + + +//============================================================================= +// +// Executes a CCMD, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemCommand : public FOptionMenuItemSubmenu +{ +public: + FOptionMenuItemCommand(const char *label, const char *menu) + : FOptionMenuItemSubmenu(label, menu) + { + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + C_DoCommand(mAction); + return true; + } + +}; + +//============================================================================= +// +// Executes a CCMD after confirmation, action is a CCMD name +// +//============================================================================= + +class FOptionMenuItemSafeCommand : public FOptionMenuItemCommand +{ + // action is a CCMD +public: + FOptionMenuItemSafeCommand(const char *label, const char *menu) + : FOptionMenuItemCommand(label, menu) + { + } + + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == MKEY_MBYes) + { + C_DoCommand(mAction); + return true; + } + return FOptionMenuItemCommand::MenuEvent(mkey, fromcontroller); + } + + bool Activate() + { + M_StartMessage("Do you really want to do this?", 0); + return true; + } +}; + +//============================================================================= +// +// Base class for option lists +// +//============================================================================= + +class FOptionMenuItemOptionBase : public FOptionMenuItem +{ +protected: + // action is a CVAR + FOptionValues *mValues; + FBaseCVar *mGrayCheck; + int mCenter; +public: + + enum + { + OP_VALUES = 0x11001 + }; + + FOptionMenuItemOptionBase(const char *label, const char *menu, const char *values, const char *graycheck, int center) + : FOptionMenuItem(label, menu) + { + FOptionValues **opt = OptionValues.CheckKey(values); + if (opt != NULL) + { + mValues = *opt; + } + else + { + mValues = NULL; + } + mGrayCheck = (FBoolCVar*)FindCVar(graycheck, NULL); + mCenter = center; + } + + bool SetString(int i, const char *newtext) + { + if (i == OP_VALUES) + { + FOptionValues **opt = OptionValues.CheckKey(newtext); + if (opt != NULL) + { + mValues = *opt; + int s = GetSelection(); + if (s >= (int)mValues->mValues.Size()) s = 0; + SetSelection(s); // readjust the CVAR if its value is outside the range now + return true; + } + } + return false; + } + + + + //============================================================================= + virtual int GetSelection() = 0; + virtual void SetSelection(int Selection) = 0; + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + bool grayed = mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool); + + if (mCenter) + { + indent = (screen->GetWidth() / 2); + } + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor, grayed); + + int overlay = grayed? MAKEARGB(96,48,0,0) : 0; + const char *text; + int Selection = GetSelection(); + if (Selection < 0) + { + text = "Unknown"; + } + else + { + text = mValues->mValues[Selection].Text; + } + screen->DrawText (SmallFont, OptionSettings.mFontColorValue, indent + CURSORSPACE, y, + text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, TAG_DONE); + return indent; + } + + //============================================================================= + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mValues->mValues.Size() > 0) + { + int Selection = GetSelection(); + if (mkey == MKEY_Left) + { + if (Selection == -1) Selection = 0; + else if (--Selection < 0) Selection = mValues->mValues.Size()-1; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + if (++Selection >= (int)mValues->mValues.Size()) Selection = 0; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + SetSelection(Selection); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + return true; + } + + bool Selectable() + { + return !(mGrayCheck != NULL && !(mGrayCheck->GetGenericRep(CVAR_Bool).Bool)); + } +}; + +//============================================================================= +// +// Change a CVAR, action is the CVAR name +// +//============================================================================= + +class FOptionMenuItemOption : public FOptionMenuItemOptionBase +{ + // action is a CVAR + FBaseCVar *mCVar; +public: + + FOptionMenuItemOption(const char *label, const char *menu, const char *values, const char *graycheck, int center) + : FOptionMenuItemOptionBase(label, menu, values, graycheck, center) + { + mCVar = FindCVar(mAction, NULL); + } + + //============================================================================= + int GetSelection() + { + int Selection = -1; + if (mValues != NULL && mCVar != NULL && mValues->mValues.Size() > 0) + { + if (mValues->mValues[0].TextValue.IsEmpty()) + { + UCVarValue cv = mCVar->GetGenericRep(CVAR_Float); + for(unsigned i=0;imValues.Size(); i++) + { + if (fabs(cv.Float - mValues->mValues[i].Value) < FLT_EPSILON) + { + Selection = i; + break; + } + } + } + else + { + UCVarValue cv = mCVar->GetGenericRep(CVAR_String); + for(unsigned i=0;imValues.Size(); i++) + { + if (mValues->mValues[i].TextValue.CompareNoCase(cv.String) == 0) + { + Selection = i; + break; + } + } + } + } + return Selection; + } + + void SetSelection(int Selection) + { + UCVarValue value; + if (mValues != NULL && mCVar != NULL && mValues->mValues.Size() > 0) + { + if (mValues->mValues[0].TextValue.IsEmpty()) + { + value.Float = (float)mValues->mValues[Selection].Value; + mCVar->SetGenericRep (value, CVAR_Float); + } + else + { + value.String = mValues->mValues[Selection].TextValue.LockBuffer(); + mCVar->SetGenericRep (value, CVAR_String); + mValues->mValues[Selection].TextValue.UnlockBuffer(); + } + } + } +}; + +//============================================================================= +// +// This class is used to capture the key to be used as the new key binding +// for a control item +// +//============================================================================= + +class DEnterKey : public DMenu +{ + DECLARE_CLASS(DEnterKey, DMenu) + + int *pKey; + +public: + DEnterKey(DMenu *parent, int *keyptr) + : DMenu(parent) + { + pKey = keyptr; + SetMenuMessage(1); + menuactive = MENU_WaitKey; // There should be a better way to disable GUI capture... + } + + bool TranslateKeyboardEvents() + { + return false; + } + + void SetMenuMessage(int which) + { + if (mParentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu))) + { + DOptionMenu *m = barrier_cast(mParentMenu); + FListMenuItem *it = m->GetItem(NAME_Controlmessage); + if (it != NULL) + { + it->SetValue(0, which); + } + } + } + + bool Responder(event_t *ev) + { + if (ev->type == EV_KeyDown) + { + *pKey = ev->data1; + menuactive = MENU_On; + SetMenuMessage(0); + Close(); + mParentMenu->MenuEvent((ev->data1 == KEY_ESCAPE)? MKEY_Abort : MKEY_Input, 0); + return true; + } + return false; + } + + void Drawer() + { + mParentMenu->Drawer(); + } +}; + +#ifndef NO_IMP +IMPLEMENT_ABSTRACT_CLASS(DEnterKey) +#endif + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class FOptionMenuItemControl : public FOptionMenuItem +{ + FKeyBindings *mBindings; + int mInput; + bool mWaiting; +public: + + FOptionMenuItemControl(const char *label, const char *menu, FKeyBindings *bindings) + : FOptionMenuItem(label, menu) + { + mBindings = bindings; + mWaiting = false; + } + + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mWaiting? OptionSettings.mFontColorHighlight: + (selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor)); + + char description[64]; + int Key1, Key2; + + mBindings->GetKeysForCommand(mAction, &Key1, &Key2); + C_NameKeys (description, Key1, Key2); + if (description[0]) + { + M_DrawConText(CR_WHITE, indent + CURSORSPACE, y-1+OptionSettings.mLabelOffset, description); + } + else + { + screen->DrawText(SmallFont, CR_BLACK, indent + CURSORSPACE, y + OptionSettings.mLabelOffset, "---", + DTA_CleanNoMove_1, true, TAG_DONE); + } + return indent; + } + + //============================================================================= + bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == MKEY_Input) + { + mWaiting = false; + mBindings->SetBind(mInput, mAction); + return true; + } + else if (mkey == MKEY_Clear) + { + mBindings->UnbindACommand(mAction); + return true; + } + else if (mkey == MKEY_Abort) + { + mWaiting = false; + return true; + } + return false; + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + mWaiting = true; + DMenu *input = new DEnterKey(DMenu::CurrentMenu, &mInput); + M_ActivateMenu(input); + return true; + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemStaticText : public FOptionMenuItem +{ + EColorRange mColor; +public: + FOptionMenuItemStaticText(const char *label, bool header) + : FOptionMenuItem(label, NAME_None, true) + { + mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, mColor); + return -1; + } + + bool Selectable() + { + return false; + } + +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuItemStaticTextSwitchable : public FOptionMenuItem +{ + EColorRange mColor; + FString mAltText; + int mCurrent; + +public: + FOptionMenuItemStaticTextSwitchable(const char *label, const char *label2, FName action, bool header) + : FOptionMenuItem(label, action, true) + { + mColor = header? OptionSettings.mFontColorHeader : OptionSettings.mFontColor; + mAltText = label2; + mCurrent = 0; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + const char *txt = mCurrent? (const char*)mAltText : mLabel; + int w = SmallFont->StringWidth(txt) * CleanXfac_1; + int x = (screen->GetWidth() - w) / 2; + screen->DrawText (SmallFont, mColor, x, y, txt, DTA_CleanNoMove_1, true, TAG_DONE); + return -1; + } + + bool SetValue(int i, int val) + { + if (i == 0) + { + mCurrent = val; + return true; + } + return false; + } + + bool SetString(int i, const char *newtext) + { + if (i == 0) + { + mAltText = newtext; + return true; + } + return false; + } + + bool Selectable() + { + return false; + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderBase : public FOptionMenuItem +{ + // action is a CVAR + double mMin, mMax, mStep; + int mShowValue; + int mDrawX; +public: + FOptionMenuSliderBase(const char *label, double min, double max, double step, int showval) + : FOptionMenuItem(label, NAME_None) + { + mMin = min; + mMax = max; + mStep = step; + mShowValue = showval; + mDrawX = 0; + } + + virtual double GetValue() = 0; + virtual void SetValue(double val) = 0; + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); + mDrawX = indent + CURSORSPACE; + M_DrawSlider (mDrawX, y + OptionSettings.mLabelOffset, mMin, mMax, GetValue(), mShowValue); + return indent; + } + + //============================================================================= + bool MenuEvent (int mkey, bool fromcontroller) + { + double value = GetValue(); + + if (mkey == MKEY_Left) + { + value -= mStep; + } + else if (mkey == MKEY_Right) + { + value += mStep; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + SetValue(clamp(value, mMin, mMax)); + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + + bool MouseEvent(int type, int x, int y) + { + DOptionMenu *lm = static_cast(DMenu::CurrentMenu); + if (type != DMenu::MOUSE_Click) + { + if (!lm->CheckFocus(this)) return false; + } + if (type == DMenu::MOUSE_Release) + { + lm->ReleaseFocus(); + } + + int slide_left = mDrawX+8*CleanXfac_1; + int slide_right = slide_left + 10*8*CleanXfac_1; // 12 char cells with 8 pixels each. + + if (type == DMenu::MOUSE_Click) + { + if (x < slide_left || x >= slide_right) return true; + } + + x = clamp(x, slide_left, slide_right); + double v = mMin + ((x - slide_left) * (mMax - mMin)) / (slide_right - slide_left); + if (v != GetValue()) + { + SetValue(v); + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + if (type == DMenu::MOUSE_Click) + { + lm->SetFocus(this); + } + return true; + } + +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderCVar : public FOptionMenuSliderBase +{ + FBaseCVar *mCVar; +public: + FOptionMenuSliderCVar(const char *label, const char *menu, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mCVar = FindCVar(menu, NULL); + } + + double GetValue() + { + if (mCVar != NULL) + { + return mCVar->GetGenericRep(CVAR_Float).Float; + } + else + { + return 0; + } + } + + void SetValue(double val) + { + if (mCVar != NULL) + { + UCVarValue value; + value.Float = (float)val; + mCVar->SetGenericRep(value, CVAR_Float); + } + } +}; + +//============================================================================= +// +// +// +//============================================================================= + +class FOptionMenuSliderVar : public FOptionMenuSliderBase +{ + float *mPVal; +public: + + FOptionMenuSliderVar(const char *label, float *pVal, double min, double max, double step, int showval) + : FOptionMenuSliderBase(label, min, max, step, showval) + { + mPVal = pVal; + } + + double GetValue() + { + return *mPVal; + } + + void SetValue(double val) + { + *mPVal = (float)val; + } +}; + +//============================================================================= +// +// // Edit a key binding, Action is the CCMD to bind +// +//============================================================================= + +class FOptionMenuItemColorPicker : public FOptionMenuItem +{ + FColorCVar *mCVar; +public: + + enum + { + CPF_RESET = 0x20001, + }; + + FOptionMenuItemColorPicker(const char *label, const char *menu) + : FOptionMenuItem(label, menu) + { + FBaseCVar *cv = FindCVar(menu, NULL); + if (cv->GetRealType() == CVAR_Color) + { + mCVar = (FColorCVar*)cv; + } + else mCVar = NULL; + } + + //============================================================================= + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + drawLabel(indent, y, selected? OptionSettings.mFontColorSelection : OptionSettings.mFontColor); + + if (mCVar != NULL) + { + int box_x = indent + CURSORSPACE; + int box_y = y + OptionSettings.mLabelOffset * CleanYfac_1 / 2; + screen->Clear (box_x, box_y, box_x + 32*CleanXfac_1, box_y + (SmallFont->GetHeight() - 1) * CleanYfac_1, + -1, (uint32)*mCVar | 0xff000000); + } + return indent; + } + + bool SetValue(int i, int v) + { + if (i == CPF_RESET && mCVar != NULL) + { + mCVar->ResetToDefault(); + return true; + } + return false; + } + + bool Activate() + { + if (mCVar != NULL) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + DMenu *picker = StartPickerMenu(DMenu::CurrentMenu, mLabel, mCVar); + if (picker != NULL) + { + M_ActivateMenu(picker); + return true; + } + } + return false; + } +}; + +class FOptionMenuScreenResolutionLine : public FOptionMenuItem +{ + FString mResTexts[3]; + int mSelection; + int mHighlight; + int mMaxValid; +public: + + enum + { + SRL_INDEX = 0x30000, + SRL_SELECTION = 0x30003, + SRL_HIGHLIGHT = 0x30004, + }; + + FOptionMenuScreenResolutionLine(const char *action) + : FOptionMenuItem("", action) + { + mSelection = 0; + mHighlight = -1; + } + + bool SetValue(int i, int v) + { + if (i == SRL_SELECTION) + { + mSelection = v; + return true; + } + else if (i == SRL_HIGHLIGHT) + { + mHighlight = v; + return true; + } + return false; + } + + bool GetValue(int i, int *v) + { + if (i == SRL_SELECTION) + { + *v = mSelection; + return true; + } + return false; + } + + bool SetString(int i, const char *newtext) + { + if (i >= SRL_INDEX && i <= SRL_INDEX+2) + { + mResTexts[i-SRL_INDEX] = newtext; + if (mResTexts[0].IsEmpty()) mMaxValid = -1; + else if (mResTexts[1].IsEmpty()) mMaxValid = 0; + else if (mResTexts[2].IsEmpty()) mMaxValid = 1; + else mMaxValid = 2; + return true; + } + return false; + } + + bool GetString(int i, char *s, int len) + { + if (i >= SRL_INDEX && i <= SRL_INDEX+2) + { + strncpy(s, mResTexts[i-SRL_INDEX], len-1); + s[len-1] = 0; + return true; + } + return false; + } + + bool MenuEvent (int mkey, bool fromcontroller) + { + if (mkey == MKEY_Left) + { + if (--mSelection < 0) mSelection = mMaxValid; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + else if (mkey == MKEY_Right) + { + if (++mSelection > mMaxValid) mSelection = 0; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + return true; + } + else + { + return FOptionMenuItem::MenuEvent(mkey, fromcontroller); + } + return false; + } + + bool MouseEvent(int type, int x, int y) + { + int colwidth = screen->GetWidth() / 3; + mSelection = x / colwidth; + return FOptionMenuItem::MouseEvent(type, x, y); + } + + bool Activate() + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + M_SetVideoMode(); + return true; + } + + int Draw(FOptionMenuDescriptor *desc, int y, int indent, bool selected) + { + int colwidth = screen->GetWidth() / 3; + EColorRange color; + + for (int x = 0; x < 3; x++) + { + if (selected && mSelection == x) + color = OptionSettings.mFontColorSelection; + else if (x == mHighlight) + color = OptionSettings.mFontColorHighlight; + else + color = OptionSettings.mFontColorValue; + + screen->DrawText (SmallFont, color, colwidth * x + 20 * CleanXfac_1, y, mResTexts[x], DTA_CleanNoMove_1, true, TAG_DONE); + } + return colwidth * mSelection + 20 * CleanXfac_1 - CURSORSPACE; + } + + bool Selectable() + { + return mMaxValid >= 0; + } +}; + +#ifndef NO_IMP +CCMD(am_restorecolors) +{ + if (DMenu::CurrentMenu != NULL && DMenu::CurrentMenu->IsKindOf(RUNTIME_CLASS(DOptionMenu))) + { + DOptionMenu *m = (DOptionMenu*)DMenu::CurrentMenu; + const FOptionMenuDescriptor *desc = m->GetDescriptor(); + // Find the color cvars by scanning the MapColors menu. + for (unsigned i = 0; i < desc->mItems.Size(); ++i) + { + desc->mItems[i]->SetValue(FOptionMenuItemColorPicker::CPF_RESET, 0); + } + } +} +#endif diff --git a/src/menu/playerdisplay.cpp b/src/menu/playerdisplay.cpp new file mode 100644 index 0000000000..f9871e1ca6 --- /dev/null +++ b/src/menu/playerdisplay.cpp @@ -0,0 +1,561 @@ +/* +** playerdisplay.cpp +** The player display for the player setup and class selection screen +** +**--------------------------------------------------------------------------- +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "doomtype.h" +#include "doomstat.h" +#include "d_player.h" +#include "tables.h" +#include "m_fixed.h" +#include "templates.h" +#include "menu/menu.h" +#include "colormatcher.h" +#include "textures/textures.h" +#include "w_wad.h" +#include "v_font.h" +#include "v_video.h" +#include "g_level.h" +#include "gi.h" +#include "r_defs.h" +#include "r_state.h" + + +//============================================================================= +// +// Used by the player display +// +//============================================================================= + +struct FBackdropTexture : public FTexture +{ +public: + FBackdropTexture(); + + const BYTE *GetColumn(unsigned int column, const Span **spans_out); + const BYTE *GetPixels(); + void Unload(); + bool CheckModified(); + +protected: + BYTE Pixels[144*160]; + static const Span DummySpan[2]; + int LastRenderTic; + + angle_t time1, time2, time3, time4; + angle_t t1ang, t2ang, z1ang, z2ang; + + void Render(); +}; + + + +// A 32x32 cloud rendered with Photoshop, plus some other filters +static BYTE pattern1[1024] = +{ + 5, 9, 7,10, 9,15, 9, 7, 8,10, 5, 3, 5, 7, 9, 8,14, 8, 4, 7, 8, 9, 5, 7,14, 7, 0, 7,13,13, 9, 6, + 2, 7, 9, 7, 7,10, 8, 8,11,10, 6, 7,10, 7, 5, 6, 6, 4, 7,13,15,16,11,15,11, 8, 0, 4,13,22,17,11, + 5, 9, 9, 7, 9,10, 4, 3, 6, 7, 8, 6, 5, 4, 2, 2, 1, 4, 6,11,15,15,14,13,17, 9, 5, 9,11,12,17,20, + 9,16, 9, 8,12,13, 7, 3, 7, 9, 5, 4, 2, 5, 5, 5, 7,11, 6, 7, 6,13,17,10,10, 9,12,17,14,12,16,15, + 15,13, 5, 3, 9,10, 4,10,12,12, 7, 9, 8, 8, 8,10, 7, 6, 5, 5, 5, 6,11, 9, 3,13,16,18,21,16,23,18, + 23,13, 0, 0, 0, 0, 0,12,18,14,15,16,13, 7, 7, 5, 9, 6, 6, 8, 4, 0, 0, 0, 0,14,19,17,14,20,21,25, + 19,20,14,13, 7, 5,13,19,14,13,17,15,14, 7, 3, 5, 6,11, 7, 7, 8, 8,10, 9, 9,18,17,15,14,15,18,16, + 16,29,24,23,18, 9,17,20,11, 5,12,15,15,12, 6, 3, 4, 6, 7,10,13,18,18,19,16,12,17,19,23,16,14,14, + 9,18,20,26,19, 5,18,18,10, 5,12,15,14,17,11, 6,11, 9,10,13,10,20,24,20,21,20,14,18,15,22,20,19, + 0, 6,16,18, 8, 7,15,18,10,13,17,17,13,11,15,11,19,12,13,10, 4,15,19,21,21,24,14, 9,17,20,24,17, + 18,17, 7, 7,16,21,22,15, 5,14,20,14,13,21,13, 8,12,14, 7, 8,11,15,13,11,16,17, 7, 5,12,17,19,14, + 25,23,17,16,23,18,15, 7, 0, 6,11, 6,11,15,11, 7,12, 7, 4,10,16,13, 7, 7,15,13, 9,15,21,14, 5, 0, + 18,22,21,21,21,22,12, 6,14,20,15, 6,10,19,13, 8, 7, 3, 7,12,14,16, 9,12,22,15,12,18,24,19,17, 9, + 0,15,18,21,17,25,14,13,19,21,21,11, 6,13,16,16,12,10,12,11,13,20,14,13,18,13, 9,15,16,25,31,20, + 5,20,24,16, 7,14,14,11,18,19,19, 6, 0, 5,11,14,17,16,19,14,15,21,19,15,14,14, 8, 0, 7,24,18,16, + 9,17,15, 7, 6,14,12, 7,14,16,11, 4, 7, 6,13,16,15,13,12,20,21,20,21,17,18,26,14, 0,13,23,21,11, + 9,12,18,11,15,21,13, 8,13,13,10, 7,13, 8, 8,19,13, 7, 4,15,19,18,14,12,14,15, 8, 6,16,22,22,15, + 9,17,14,19,15,14,15, 9,11, 9, 6, 8,14,13,13,12, 5, 0, 0, 6,12,13, 7, 7, 9, 7, 0,12,21,16,15,18, + 15,16,18,11, 6, 8,15, 9, 2, 0, 5,10,10,16, 9, 0, 4,12,15, 9,12, 9, 7, 7,12, 7, 0, 6,12, 6, 9,13, + 12,19,15,14,11, 7, 8, 9,12,10, 5, 5, 7,12,12,10,14,16,16,11, 8,12,10,12,10, 8,10,10,14,12,16,16, + 16,17,20,22,12,15,12,14,19,11, 6, 5,10,13,17,17,21,19,15, 9, 6, 9,15,18,10,10,18,14,20,15,16,17, + 11,19,19,18,19,14,17,13,12,12, 7,11,18,17,16,15,19,19,10, 2, 0, 8,15,12, 8,11,12,10,19,20,19,19, + 6,14,18,13,13,16,16,12, 5, 8,10,12,10,13,18,12, 9,10, 7, 6, 5,11, 8, 6, 7,13,16,13,10,15,20,14, + 0, 5,12,12, 4, 0, 9,16, 9,10,12, 8, 0, 9,13, 9, 0, 2, 4, 7,10, 6, 7, 3, 4,11,16,18,10,11,21,21, + 16,13,11,15, 8, 0, 5, 9, 8, 7, 6, 3, 0, 9,17, 9, 0, 0, 0, 3, 5, 4, 3, 5, 7,15,16,16,17,14,22,22, + 24,14,15,12, 9, 0, 5,10, 8, 4, 7,12,10,11,12, 7, 6, 8, 6, 5, 7, 8, 8,11,13,10,15,14,12,18,20,16, + 16,17,17,18,12, 9,12,16,10, 5, 6,20,13,15, 8, 4, 8, 9, 8, 7, 9,11,12,17,16,16,11,10, 9,10, 5, 0, + 0,14,18,18,15,16,14, 9,10, 9, 9,15,14,10, 4, 6,10, 8, 8, 7,10, 9,10,16,18,10, 0, 0, 7,12,10, 8, + 0,14,19,14, 9,11,11, 8, 8,10,15, 9,10, 7, 4,10,13, 9, 7, 5, 5, 7, 7, 7,13,13, 5, 5,14,22,18,16, + 0,10,14,10, 3, 6, 5, 6, 8, 9, 8, 9, 5, 9, 8, 9, 6, 8, 8, 8, 1, 0, 0, 0, 9,17,12,12,17,19,20,13, + 6,11,17,11, 5, 5, 8,10, 6, 5, 6, 6, 3, 7, 9, 7, 6, 8,12,10, 4, 8, 6, 6,11,16,16,15,16,17,17,16, + 11, 9,10,10, 5, 6,12,10, 5, 1, 6,10, 5, 3, 3, 5, 4, 7,15,10, 7,13, 7, 8,15,11,15,15,15, 8,11,15, +}; + +// Just a 32x32 cloud rendered with the standard Photoshop filter +static BYTE pattern2[1024] = +{ + 9, 9, 8, 8, 8, 8, 6, 6,13,13,11,21,19,21,23,18,23,24,19,19,24,17,18,12, 9,14, 8,12,12, 5, 8, 6, + 11,10, 6, 7, 8, 8, 9,13,10,11,17,15,23,22,23,22,20,26,27,26,17,21,20,14,12, 8,11, 8,11, 7, 8, 7, + 6, 9,13,13,10, 9,13, 7,12,13,16,19,16,20,22,25,22,25,27,22,21,23,15,10,14,14,15,13,12, 8,12, 6, + 6, 7,12,12,12,16, 9,12,12,15,16,11,21,24,19,24,23,26,28,27,26,21,14,15, 7, 7,10,15,12,11,10, 9, + 7,14,11,16,12,18,16,14,16,14,11,14,15,21,23,17,20,18,26,24,27,18,20,11,11,14,10,17,17,10, 6,10, + 13, 9,14,10,13,11,14,15,18,15,15,12,19,19,20,18,22,20,19,22,19,19,19,20,17,15,15,11,16,14,10, 8, + 13,16,12,16,17,19,17,18,15,19,14,18,15,14,15,17,21,19,23,18,23,22,18,18,17,15,15,16,12,12,15,10, + 10,12,14,10,16,11,18,15,21,20,20,17,18,19,16,19,14,20,19,14,19,25,22,21,22,24,18,12, 9, 9, 8, 6, + 10,10,13, 9,15,13,20,19,22,18,18,17,17,21,21,13,13,12,19,18,16,17,27,26,22,23,20,17,12,11, 8, 9, + 7,13,14,15,11,13,18,22,19,23,23,20,22,24,21,14,12,16,17,19,18,18,22,18,24,23,19,17,16,14, 8, 7, + 12,12, 8, 8,16,20,26,25,28,28,22,29,23,22,21,18,13,16,15,15,20,17,25,24,19,17,17,17,15,10, 8, 9, + 7,12,15,11,17,20,25,25,25,29,30,31,28,26,18,16,17,18,20,21,22,20,23,19,18,19,10,16,16,11,11, 8, + 5, 6, 8,14,14,17,17,21,27,23,27,31,27,22,23,21,19,19,21,19,20,19,17,22,13,17,12,15,10,10,12, 6, + 8, 9, 8,14,15,16,15,18,27,26,23,25,23,22,18,21,20,17,19,20,20,16,20,14,15,13,12, 8, 8, 7,11,13, + 7, 6,11,11,11,13,15,22,25,24,26,22,24,26,23,18,24,24,20,18,20,16,17,12,12,12,10, 8,11, 9, 6, 8, + 9,10, 9, 6, 5,14,16,19,17,21,26,20,23,19,19,17,20,21,26,25,23,21,17,13,12, 5,13,11, 7,12,10,12, + 6, 5, 4,10,11, 9,10,13,17,20,20,18,23,26,27,20,21,24,20,19,24,20,18,10,11, 3, 6,13, 9, 6, 8, 8, + 1, 2, 2,11,13,13,11,16,16,16,19,21,20,23,22,28,21,20,19,18,23,16,18, 7, 5, 9, 7, 6, 5,10, 8, 8, + 0, 0, 6, 9,11,15,12,12,19,18,19,26,22,24,26,30,23,22,22,16,20,19,12,12, 3, 4, 6, 5, 4, 7, 2, 4, + 2, 0, 0, 7,11, 8,14,13,15,21,26,28,25,24,27,26,23,24,22,22,15,17,12, 8,10, 7, 7, 4, 0, 5, 0, 1, + 1, 2, 0, 1, 9,14,13,10,19,24,22,29,30,28,30,30,31,23,24,19,17,14,13, 8, 8, 8, 1, 4, 0, 0, 0, 3, + 5, 2, 4, 2, 9, 8, 8, 8,18,23,20,27,30,27,31,25,28,30,28,24,24,15,11,14,10, 3, 4, 3, 0, 0, 1, 3, + 9, 3, 4, 3, 5, 6, 8,13,14,23,21,27,28,27,28,27,27,29,30,24,22,23,13,15, 8, 6, 2, 0, 4, 3, 4, 1, + 6, 5, 5, 3, 9, 3, 6,14,13,16,23,26,28,23,30,31,28,29,26,27,21,20,15,15,13, 9, 1, 0, 2, 0, 5, 8, + 8, 4, 3, 7, 2, 0,10, 7,10,14,21,21,29,28,25,27,30,28,25,24,27,22,19,13,10, 5, 0, 0, 0, 0, 0, 7, + 7, 6, 7, 0, 2, 2, 5, 6,15,11,19,24,22,29,27,31,30,30,31,28,23,18,14,14, 7, 5, 0, 0, 1, 0, 1, 0, + 5, 5, 5, 0, 0, 4, 5,11, 7,10,13,20,21,21,28,31,28,30,26,28,25,21, 9,12, 3, 3, 0, 2, 2, 2, 0, 1, + 3, 3, 0, 2, 0, 3, 5, 3,11,11,16,19,19,27,26,26,30,27,28,26,23,22,16, 6, 2, 2, 3, 2, 0, 2, 4, 0, + 0, 0, 0, 3, 3, 1, 0, 4, 5, 9,11,16,24,20,28,26,28,24,28,25,22,21,16, 5, 7, 5, 7, 3, 2, 3, 3, 6, + 0, 0, 2, 0, 2, 0, 4, 3, 8,12, 9,17,16,23,23,27,27,22,26,22,21,21,13,14, 5, 3, 7, 3, 2, 4, 6, 1, + 2, 5, 6, 4, 0, 1, 5, 8, 7, 6,15,17,22,20,24,28,23,25,20,21,18,16,13,15,13,10, 8, 5, 5, 9, 3, 7, + 7, 7, 0, 5, 1, 6, 7, 9,12, 9,12,21,22,25,24,22,23,25,24,18,24,22,17,13,10, 9,10, 9, 6,11, 6, 5, +}; + +const FTexture::Span FBackdropTexture::DummySpan[2] = { { 0, 160 }, { 0, 0 } }; + +//============================================================================= +// +// +// +//============================================================================= + +FBackdropTexture::FBackdropTexture() +{ + Width = 144; + Height = 160; + WidthBits = 8; + HeightBits = 8; + WidthMask = 255; + LastRenderTic = 0; + + time1 = ANGLE_1*180; + time2 = ANGLE_1*56; + time3 = ANGLE_1*99; + time4 = ANGLE_1*1; + t1ang = ANGLE_90; + t2ang = 0; + z1ang = 0; + z2ang = ANGLE_90/2; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FBackdropTexture::CheckModified() +{ + return LastRenderTic != gametic; +} + +void FBackdropTexture::Unload() +{ +} + +//============================================================================= +// +// +// +//============================================================================= + +const BYTE *FBackdropTexture::GetColumn(unsigned int column, const Span **spans_out) +{ + if (LastRenderTic != gametic) + { + Render(); + } + column = clamp(column, 0u, 143u); + if (spans_out != NULL) + { + *spans_out = DummySpan; + } + return Pixels + column*160; +} + +//============================================================================= +// +// +// +//============================================================================= + +const BYTE *FBackdropTexture::GetPixels() +{ + if (LastRenderTic != gametic) + { + Render(); + } + return Pixels; +} + +//============================================================================= +// +// This is one plasma and two rotozoomers. I think it turned out quite awesome. +// +//============================================================================= + +void FBackdropTexture::Render() +{ + BYTE *from; + int width, height, pitch; + + width = 160; + height = 144; + pitch = width; + + int x, y; + + const angle_t a1add = ANGLE_1/2; + const angle_t a2add = ANGLE_MAX-ANGLE_1; + const angle_t a3add = ANGLE_1*5/7; + const angle_t a4add = ANGLE_MAX-ANGLE_1*4/3; + + const angle_t t1add = ANGLE_MAX-ANGLE_1*2; + const angle_t t2add = ANGLE_MAX-ANGLE_1*3+ANGLE_1/6; + const angle_t t3add = ANGLE_1*16/7; + const angle_t t4add = ANGLE_MAX-ANGLE_1*2/3; + const angle_t x1add = 5<>ANGLETOFINESHIFT]>>2)+FRACUNIT/2; + fixed_t z2 = (finecosine[z1ang>>ANGLETOFINESHIFT]>>2)+FRACUNIT*3/4; + + tc = MulScale5 (finecosine[t1ang>>ANGLETOFINESHIFT], z1); + ts = MulScale5 (finesine[t1ang>>ANGLETOFINESHIFT], z1); + uc = MulScale5 (finecosine[t2ang>>ANGLETOFINESHIFT], z2); + us = MulScale5 (finesine[t2ang>>ANGLETOFINESHIFT], z2); + + ltx = -width/2*tc; + lty = -width/2*ts; + lux = -width/2*uc; + luy = -width/2*us; + + for (y = 0; y < height; ++y) + { + a1 = time1; + a2 = time2; + c3 = finecosine[a3>>ANGLETOFINESHIFT]; + c4 = finecosine[a4>>ANGLETOFINESHIFT]; + tx = ltx - (y-height/2)*ts; + ty = lty + (y-height/2)*tc; + ux = lux - (y-height/2)*us; + uy = luy + (y-height/2)*uc; + for (x = 0; x < width; ++x) + { + c1 = finecosine[a1>>ANGLETOFINESHIFT]; + c2 = finecosine[a2>>ANGLETOFINESHIFT]; + from[x] = ((c1 + c2 + c3 + c4) >> (FRACBITS+3-7)) + 128 // plasma + + pattern1[(tx>>27)+((ty>>22)&992)] // rotozoomer 1 + + pattern2[(ux>>27)+((uy>>22)&992)]; // rotozoomer 2 + tx += tc; + ty += ts; + ux += uc; + uy += us; + a1 += a1add; + a2 += a2add; + } + a3 += a3add; + a4 += a4add; + from += pitch; + } + + time1 += t1add; + time2 += t2add; + time3 += t3add; + time4 += t4add; + t1ang += x1add; + t2ang += x2add; + z1ang += z1add; + z2ang += z2add; + + LastRenderTic = gametic; +} + + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItemPlayerDisplay::FListMenuItemPlayerDisplay(FListMenuDescriptor *menu, int x, int y, PalEntry c1, PalEntry c2, bool np, FName action) +: FListMenuItem(x, y, action) +{ + mOwner = menu; + + for (int i = 0; i < 256; i++) + { + int r = c1.r + c2.r * i / 255; + int g = c1.g + c2.g * i / 255; + int b = c1.b + c2.b * i / 255; + mRemap.Remap[i] = ColorMatcher.Pick (r, g, b); + mRemap.Palette[i] = PalEntry(255, r, g, b); + } + mBackdrop = new FBackdropTexture; + mPlayerClass = NULL; + mPlayerState = NULL; + mNoportrait = np; + mMode = 0; + mRotation = 0; + mTranslate = false; + mSkin = 0; + mRandomClass = 0; + mRandomTimer = 0; + mClassNum = -1; +} + + +//============================================================================= +// +// +// +//============================================================================= + +FListMenuItemPlayerDisplay::~FListMenuItemPlayerDisplay() +{ + delete mBackdrop; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::UpdateRandomClass() +{ + if (--mRandomTimer < 0) + { + if (++mRandomClass >= (int)PlayerClasses.Size ()) mRandomClass = 0; + mPlayerClass = &PlayerClasses[mRandomClass]; + mPlayerState = GetDefaultByType (mPlayerClass->Type)->SeeState; + mPlayerTics = mPlayerState->GetTics(); + mRandomTimer = 6; + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::SetPlayerClass(int classnum, bool force) +{ + if (classnum < 0 || classnum >= (int)PlayerClasses.Size ()) + { + if (mClassNum != -1) + { + mClassNum = -1; + mRandomTimer = 0; + UpdateRandomClass(); + } + } + else if (mPlayerClass != &PlayerClasses[classnum] || force) + { + mPlayerClass = &PlayerClasses[classnum]; + mPlayerState = GetDefaultByType (mPlayerClass->Type)->SeeState; + mPlayerTics = mPlayerState->GetTics(); + mClassNum = classnum; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FListMenuItemPlayerDisplay::UpdatePlayerClass() +{ + int classnum; + FName seltype = mOwner->mItems[mOwner->mSelectedItem]->GetAction(&classnum); + + if (seltype != NAME_Episodemenu) return false; + if (PlayerClasses.Size() == 0) return false; + + SetPlayerClass(classnum); + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FListMenuItemPlayerDisplay::SetValue(int i, int value) +{ + switch (i) + { + case PDF_MODE: + mMode = value; + return true; + + case PDF_ROTATION: + mRotation = value; + return true; + + case PDF_TRANSLATE: + mTranslate = value; + + case PDF_CLASS: + SetPlayerClass(value, true); + break; + + case PDF_SKIN: + mSkin = value; + break; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::Ticker() +{ + if (mClassNum < 0) UpdateRandomClass(); + + if (mPlayerState != NULL && mPlayerState->GetTics () != -1 && mPlayerState->GetNextState () != NULL) + { + if (--mPlayerTics <= 0) + { + mPlayerState = mPlayerState->GetNextState(); + mPlayerTics = mPlayerState->GetTics(); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FListMenuItemPlayerDisplay::Drawer(bool selected) +{ + if (mMode == 0 && !UpdatePlayerClass()) + { + return; + } + + const char *portrait = mPlayerClass->Type->Meta.GetMetaString(APMETA_Portrait); + + if (portrait != NULL && !mNoportrait) + { + FTextureID texid = TexMan.CheckForTexture(portrait, FTexture::TEX_MiscPatch); + if (texid.isValid()) + { + FTexture *tex = TexMan(texid); + if (tex != NULL) + { + screen->DrawTexture (tex, mXpos, mYpos, DTA_Clean, true, TAG_DONE); + return; + } + } + } + int x = (mXpos - 160) * CleanXfac + (SCREENWIDTH>>1); + int y = (mYpos - 100) * CleanYfac + (SCREENHEIGHT>>1); + + screen->DrawTexture (mBackdrop, x, y - 1, + DTA_DestWidth, 72 * CleanXfac, + DTA_DestHeight, 80 * CleanYfac, + DTA_Translation, &mRemap, + DTA_Masked, true, + TAG_DONE); + + V_DrawFrame (x, y, 72*CleanXfac, 80*CleanYfac-1); + + spriteframe_t *sprframe; + fixed_t scaleX, scaleY; + + if (mSkin == 0) + { + sprframe = &SpriteFrames[sprites[mPlayerState->sprite].spriteframes + mPlayerState->GetFrame()]; + scaleX = GetDefaultByType(mPlayerClass->Type)->scaleX; + scaleY = GetDefaultByType(mPlayerClass->Type)->scaleY; + } + else + { + sprframe = &SpriteFrames[sprites[skins[mSkin].sprite].spriteframes + mPlayerState->GetFrame()]; + scaleX = skins[mSkin].ScaleX; + scaleY = skins[mSkin].ScaleY; + } + + if (sprframe != NULL) + { + FTexture *tex = TexMan(sprframe->Texture[mRotation]); + if (tex != NULL && tex->UseType != FTexture::TEX_Null) + { + FRemapTable *trans = NULL; + if (mTranslate) trans = translationtables[TRANSLATION_Players](MAXPLAYERS); + screen->DrawTexture (tex, + x + 36*CleanXfac, y + 71*CleanYfac, + DTA_DestWidth, MulScale16 (tex->GetWidth() * CleanXfac, scaleX), + DTA_DestHeight, MulScale16 (tex->GetHeight() * CleanYfac, scaleY), + DTA_Translation, trans, + TAG_DONE); + } + } +} + diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp new file mode 100644 index 0000000000..b25b86f1a6 --- /dev/null +++ b/src/menu/playermenu.cpp @@ -0,0 +1,1142 @@ +/* +** playermenu.cpp +** The player setup menu +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "v_video.h" +#include "v_font.h" +#include "gi.h" +#include "gstrings.h" +#include "d_player.h" +#include "d_event.h" +#include "d_gui.h" +#include "c_dispatch.h" +#include "teaminfo.h" +#include "v_palette.h" +#include "r_state.h" +#include "r_translate.h" +#include "v_text.h" + +EXTERN_CVAR (String, playerclass) +EXTERN_CVAR (String, name) +EXTERN_CVAR (Int, team) +EXTERN_CVAR (Float, autoaim) +EXTERN_CVAR(Bool, neverswitchonpickup) +EXTERN_CVAR (Bool, cl_run) + +void R_GetPlayerTranslation (int color, const FPlayerColorSet *colorset, FPlayerSkin *skin, FRemapTable *table); + +//============================================================================= +// +// Player's name +// +//============================================================================= + +FPlayerNameBox::FPlayerNameBox(int x, int y, int height, int frameofs, const char *text, FFont *font, EColorRange color, FName action) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mFrameSize = frameofs; + mPlayerName[0] = 0; + mEntering = false; +} + +FPlayerNameBox::~FPlayerNameBox() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FPlayerNameBox::SetString(int i, const char *s) +{ + if (i == 0) + { + strncpy(mPlayerName, s, MAXPLAYERNAME); + mPlayerName[MAXPLAYERNAME] = 0; + return true; + } + return false; +} + +bool FPlayerNameBox::GetString(int i, char *s, int len) +{ + if (i == 0) + { + strncpy(s, mPlayerName, len); + s[len] = 0; + return true; + } + return false; +} + +//============================================================================= +// +// [RH] Width of the border is variable +// +//============================================================================= + +void FPlayerNameBox::DrawBorder (int x, int y, int len) +{ + if (gameinfo.gametype & (GAME_DoomStrifeChex)) + { + int i; + + screen->DrawTexture (TexMan["M_LSLEFT"], x-8, y+7, DTA_Clean, true, TAG_DONE); + + for (i = 0; i < len; i++) + { + screen->DrawTexture (TexMan["M_LSCNTR"], x, y+7, DTA_Clean, true, TAG_DONE); + x += 8; + } + + screen->DrawTexture (TexMan["M_LSRGHT"], x, y+7, DTA_Clean, true, TAG_DONE); + } + else + { + screen->DrawTexture (TexMan["M_FSLOT"], x, y+1, DTA_Clean, true, TAG_DONE); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void FPlayerNameBox::Drawer(bool selected) +{ + const char *text = mText; + if (text != NULL) + { + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + } + + // Draw player name box + int x = mXpos + mFont->StringWidth(text) + 16 + mFrameSize; + DrawBorder (x, mYpos - mFrameSize, MAXPLAYERNAME+1); + if (!mEntering) + { + screen->DrawText (SmallFont, CR_UNTRANSLATED, x + mFrameSize, mYpos, mPlayerName, + DTA_Clean, true, TAG_DONE); + } + else + { + size_t l = strlen(mEditName); + mEditName[l] = (gameinfo.gametype & (GAME_DoomStrifeChex)) ? '_' : '['; + + screen->DrawText (SmallFont, CR_UNTRANSLATED, x + mFrameSize, mYpos, mEditName, + DTA_Clean, true, TAG_DONE); + + mEditName[l] = 0; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FPlayerNameBox::MenuEvent(int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + strcpy(mEditName, mPlayerName); + mEntering = true; + DMenu *input = new DTextEnterMenu(DMenu::CurrentMenu, mEditName, MAXPLAYERNAME, 2, fromcontroller); + M_ActivateMenu(input); + return true; + } + else if (mkey == MKEY_Input) + { + strcpy(mPlayerName, mEditName); + mEntering = false; + return true; + } + else if (mkey == MKEY_Abort) + { + mEntering = false; + return true; + } + return false; +} + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +FValueTextItem::FValueTextItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, EColorRange valuecolor, FName action, FName values) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mFontColor2 = valuecolor; + mSelection = 0; + if (values != NAME_None) + { + FOptionValues **opt = OptionValues.CheckKey(values); + if (opt != NULL) + { + for(unsigned i=0;i<(*opt)->mValues.Size(); i++) + { + SetString(i, (*opt)->mValues[i].Text); + } + } + } +} + +FValueTextItem::~FValueTextItem() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::SetString(int i, const char *s) +{ + // should actually use the index... + FString str = s; + if (i==0) mSelections.Clear(); + mSelections.Push(str); + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::SetValue(int i, int value) +{ + if (i == 0) + { + mSelection = value; + return true; + } + return false; +} + +bool FValueTextItem::GetValue(int i, int *pvalue) +{ + if (i == 0) + { + *pvalue = mSelection; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FValueTextItem::MenuEvent (int mkey, bool fromcontroller) +{ + if (mSelections.Size() > 1) + { + if (mkey == MKEY_Left) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if (--mSelection < 0) mSelection = mSelections.Size() - 1; + return true; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if (++mSelection >= (int)mSelections.Size()) mSelection = 0; + return true; + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FValueTextItem::Drawer(bool selected) +{ + const char *text = mText; + + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + + int x = mXpos + mFont->StringWidth(text) + 8; + if (mSelections.Size() > 0) screen->DrawText(mFont, mFontColor2, x, mYpos, mSelections[mSelection], DTA_Clean, true, TAG_DONE); +} + +//============================================================================= +// +// items for the player menu +// +//============================================================================= + +FSliderItem::FSliderItem(int x, int y, int height, const char *text, FFont *font, EColorRange color, FName action, int min, int max, int step) +: FListMenuItemSelectable(x, y, height, action) +{ + mText = copystring(text); + mFont = font; + mFontColor = color; + mSelection = 0; + mMinrange = min; + mMaxrange = max; + mStep = step; +} + +FSliderItem::~FSliderItem() +{ + if (mText != NULL) delete [] mText; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::SetValue(int i, int value) +{ + if (i == 0) + { + mSelection = value; + return true; + } + return false; +} + +bool FSliderItem::GetValue(int i, int *pvalue) +{ + if (i == 0) + { + *pvalue = mSelection; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::MenuEvent (int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Left) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if ((mSelection -= mStep) < mMinrange) mSelection = mMinrange; + return true; + } + else if (mkey == MKEY_Right || mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + if ((mSelection += mStep) > mMaxrange) mSelection = mMaxrange; + return true; + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +bool FSliderItem::MouseEvent(int type, int x, int y) +{ + DListMenu *lm = static_cast(DMenu::CurrentMenu); + if (type != DMenu::MOUSE_Click) + { + if (!lm->CheckFocus(this)) return false; + } + if (type == DMenu::MOUSE_Release) + { + lm->ReleaseFocus(); + } + + int slide_left = SmallFont->StringWidth ("Green") + 8 + mXpos; + int slide_right = slide_left + 12*8; // 12 char cells with 8 pixels each. + + if (type == DMenu::MOUSE_Click) + { + if (x < slide_left || x >= slide_right) return true; + } + + x = clamp(x, slide_left, slide_right); + int v = mMinrange + Scale(x - slide_left, mMaxrange - mMinrange, slide_right - slide_left); + if (v != mSelection) + { + mSelection = v; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/change", snd_menuvolume, ATTN_NONE); + } + if (type == DMenu::MOUSE_Click) + { + lm->SetFocus(this); + } + return true; +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSliderItem::DrawSlider (int x, int y) +{ + int range = mMaxrange - mMinrange; + int cur = mSelection - mMinrange; + + x = (x - 160) * CleanXfac + screen->GetWidth() / 2; + y = (y - 100) * CleanYfac + screen->GetHeight() / 2; + + screen->DrawText (ConFont, CR_WHITE, x, y, + "\x10\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x12", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); + screen->DrawText (ConFont, CR_ORANGE, x + (5 + (int)((cur * 78) / range)) * CleanXfac, y, + "\x13", + DTA_CellX, 8 * CleanXfac, + DTA_CellY, 8 * CleanYfac, + TAG_DONE); +} + +//============================================================================= +// +// +// +//============================================================================= + +void FSliderItem::Drawer(bool selected) +{ + const char *text = mText; + + if (*text == '$') text = GStrings(text+1); + screen->DrawText(mFont, selected? OptionSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true, TAG_DONE); + + int x = SmallFont->StringWidth ("Green") + 8 + mXpos; + DrawSlider (x, mYpos); +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DPlayerMenu : public DListMenu +{ + DECLARE_CLASS(DPlayerMenu, DListMenu) + + int PlayerClassIndex; + FPlayerClass *PlayerClass; + TArray PlayerColorSets; + TArray PlayerSkins; + int mRotation; + + void PickPlayerClass (); + void UpdateColorsets(); + void UpdateSkins(); + void UpdateTranslation(); + void SendNewColor (int red, int green, int blue); + + void PlayerNameChanged(FListMenuItem *li); + void ColorSetChanged (FListMenuItem *li); + void ClassChanged (FListMenuItem *li); + void AutoaimChanged (FListMenuItem *li); + void SkinChanged (FListMenuItem *li); + + +public: + + DPlayerMenu() {} + void Init(DMenu *parent, FListMenuDescriptor *desc); + bool Responder (event_t *ev); + bool MenuEvent (int mkey, bool fromcontroller); + bool MouseEvent(int type, int x, int y); + void Ticker (); + void Drawer (); +}; + +IMPLEMENT_CLASS(DPlayerMenu) + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Init(DMenu *parent, FListMenuDescriptor *desc) +{ + FListMenuItem *li; + + Super::Init(parent, desc); + PickPlayerClass(); + mRotation = 0; + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_ROTATION, 0); + li->SetValue(FListMenuItemPlayerDisplay::PDF_MODE, 1); + li->SetValue(FListMenuItemPlayerDisplay::PDF_TRANSLATE, 1); + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + if (PlayerClass != NULL && !(GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN) && + players[consoleplayer].userinfo.PlayerClass != -1) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, players[consoleplayer].userinfo.skin); + } + } + + li = GetItem(NAME_Playerbox); + if (li != NULL) + { + li->SetString(0, name); + } + + li = GetItem(NAME_Team); + if (li != NULL) + { + li->SetString(0, "None"); + for(unsigned i=0;iSetString(i+1, Teams[i].GetName()); + } + li->SetValue(0, team == TEAM_NONE? 0 : team + 1); + } + + int mycolorset = players[consoleplayer].userinfo.colorset; + int color = players[consoleplayer].userinfo.color; + + UpdateColorsets(); + + li = GetItem(NAME_Red); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, RPART(color)); + } + + li = GetItem(NAME_Green); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, GPART(color)); + } + + li = GetItem(NAME_Blue); + if (li != NULL) + { + li->Enable(mycolorset == -1); + li->SetValue(0, BPART(color)); + } + + li = GetItem(NAME_Class); + if (li != NULL) + { + if (PlayerClasses.Size() == 1) + { + li->SetString(0, PlayerClasses[0].Type->Meta.GetMetaString (APMETA_DisplayName)); + li->SetValue(0, 0); + } + else + { + li->SetString(0, "Random"); + for(unsigned i=0; i< PlayerClasses.Size(); i++) + { + const char *cls = PlayerClasses[i].Type->Meta.GetMetaString (APMETA_DisplayName); + li->SetString(i+1, cls); + } + li->SetValue(0, players[consoleplayer].userinfo.PlayerClass + 1); + } + } + + UpdateSkins(); + + li = GetItem(NAME_Gender); + if (li != NULL) + { + li->SetValue(0, players[consoleplayer].userinfo.gender); + } + + li = GetItem(NAME_Autoaim); + if (li != NULL) + { + int sel = + autoaim == 0 ? 0 : + autoaim <= 0.25 ? 1 : + autoaim <= 0.5 ? 2 : + autoaim <= 1 ? 3 : + autoaim <= 2 ? 4 : + autoaim <= 3 ? 5:6; + li->SetValue(0, sel); + } + + li = GetItem(NAME_Switch); + if (li != NULL) + { + li->SetValue(0, neverswitchonpickup); + } + + li = GetItem(NAME_AlwaysRun); + if (li != NULL) + { + li->SetValue(0, cl_run); + } + + if (mDesc->mSelectedItem < 0) mDesc->mSelectedItem = 1; + +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DPlayerMenu::Responder (event_t *ev) +{ + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_Char && ev->data1 == ' ') + { + // turn the player sprite around + mRotation = 8 - mRotation; + FListMenuItem *li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_ROTATION, mRotation); + } + return true; + } + return Super::Responder(ev); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateTranslation() +{ + int PlayerColor = players[consoleplayer].userinfo.color; + int PlayerSkin = players[consoleplayer].userinfo.skin; + int PlayerColorset = players[consoleplayer].userinfo.colorset; + + if (PlayerClass != NULL) + { + PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(PlayerClass - &PlayerClasses[0])); + R_GetPlayerTranslation(PlayerColor, + P_GetPlayerColorSet(PlayerClass->Type->TypeName, PlayerColorset), + &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::PickPlayerClass() +{ + + /* + // What's the point of this? Aren't we supposed to edit the + // userinfo? + if (players[consoleplayer].mo != NULL) + { + PlayerClassIndex = players[consoleplayer].CurrentPlayerClass; + } + else + */ + { + int pclass = 0; + // [GRB] Pick a class from player class list + if (PlayerClasses.Size () > 1) + { + pclass = players[consoleplayer].userinfo.PlayerClass; + + if (pclass < 0) + { + pclass = (MenuTime>>7) % PlayerClasses.Size (); + } + } + PlayerClassIndex = pclass; + } + PlayerClass = &PlayerClasses[PlayerClassIndex]; + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::SendNewColor (int red, int green, int blue) +{ + char command[24]; + + players[consoleplayer].userinfo.color = MAKERGB(red, green, blue); + mysnprintf (command, countof(command), "color \"%02x %02x %02x\"", red, green, blue); + C_DoCommand (command); + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateColorsets() +{ + FListMenuItem *li = GetItem(NAME_Color); + if (li != NULL) + { + int sel = 0; + P_EnumPlayerColorSets(PlayerClass->Type->TypeName, &PlayerColorSets); + li->SetString(0, "Custom"); + for(unsigned i=0;iType->TypeName, PlayerColorSets[i]); + li->SetString(i+1, colorset->Name); + } + int mycolorset = players[consoleplayer].userinfo.colorset; + if (mycolorset != -1) + { + for(unsigned i=0;iSetValue(0, sel); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::UpdateSkins() +{ + int sel = 0; + int skin; + FListMenuItem *li = GetItem(NAME_Skin); + if (li != NULL) + { + if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || + players[consoleplayer].userinfo.PlayerClass == -1) + { + li->SetString(0, "Base"); + li->SetValue(0, 0); + skin = 0; + } + else + { + PlayerSkins.Clear(); + for(unsigned i=0;i<(unsigned)numskins; i++) + { + if (PlayerClass->CheckSkin(i)) + { + int j = PlayerSkins.Push(i); + li->SetString(j, skins[i].name); + if (players[consoleplayer].userinfo.skin == i) + { + sel = j; + } + } + } + li->SetValue(0, sel); + skin = PlayerSkins[sel]; + } + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, skin); + } + } + UpdateTranslation(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::PlayerNameChanged(FListMenuItem *li) +{ + char pp[MAXPLAYERNAME+1]; + const char *p; + if (li->GetString(0, pp, MAXPLAYERNAME)) + { + FString command("name \""); + + // Escape any backslashes or quotation marks before sending the name to the console. + for (p = pp; *p != '\0'; ++p) + { + if (*p == '"' || *p == '\\') + { + command << '\\'; + } + command << *p; + } + command << '"'; + C_DoCommand (command); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::ColorSetChanged (FListMenuItem *li) +{ + int sel; + + if (li->GetValue(0, &sel)) + { + int mycolorset = -1; + + if (sel > 0) mycolorset = PlayerColorSets[sel-1]; + + FListMenuItem *red = GetItem(NAME_Red); + FListMenuItem *green = GetItem(NAME_Green); + FListMenuItem *blue = GetItem(NAME_Blue); + + // disable the sliders if a valid colorset is selected + if (red != NULL) red->Enable(mycolorset == -1); + if (green != NULL) green->Enable(mycolorset == -1); + if (blue != NULL) blue->Enable(mycolorset == -1); + + char command[24]; + players[consoleplayer].userinfo.colorset = mycolorset; + mysnprintf(command, countof(command), "colorset %d", mycolorset); + C_DoCommand(command); + UpdateTranslation(); + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::ClassChanged (FListMenuItem *li) +{ + if (PlayerClasses.Size () == 1) + { + return; + } + + int sel; + + if (li->GetValue(0, &sel)) + { + players[consoleplayer].userinfo.PlayerClass = sel-1; + + cvar_set ("playerclass", + sel == 0 ? "Random" : PlayerClass->Type->Meta.GetMetaString (APMETA_DisplayName)); + + PickPlayerClass(); + UpdateSkins(); + UpdateColorsets(); + UpdateTranslation(); + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_CLASS, players[consoleplayer].userinfo.PlayerClass); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::SkinChanged (FListMenuItem *li) +{ + if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || + players[consoleplayer].userinfo.PlayerClass == -1) + { + return; + } + + int sel; + + if (li->GetValue(0, &sel)) + { + sel = PlayerSkins[sel]; + players[consoleplayer].userinfo.skin = sel; + UpdateTranslation(); + cvar_set ("skin", skins[sel].name); + + li = GetItem(NAME_Playerdisplay); + if (li != NULL) + { + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, sel); + } + } +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::AutoaimChanged (FListMenuItem *li) +{ + static const float ranges[] = { 0, 0.25, 0.5, 1, 2, 3, 5000 }; + + int sel; + + if (li->GetValue(0, &sel)) + { + autoaim = ranges[sel]; + } +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DPlayerMenu::MenuEvent (int mkey, bool fromcontroller) +{ + int v; + if (mDesc->mSelectedItem >= 0) + { + FListMenuItem *li = mDesc->mItems[mDesc->mSelectedItem]; + if (li->MenuEvent(mkey, fromcontroller)) + { + FName current = li->GetAction(NULL); + switch(current) + { + // item specific handling comes here + + case NAME_Playerbox: + PlayerNameChanged(li); + break; + + case NAME_Team: + if (li->GetValue(0, &v)) + { + team = v==0? TEAM_NONE : v-1; + } + break; + + case NAME_Color: + ColorSetChanged(li); + break; + + case NAME_Red: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (v, GPART(color), BPART(color)); + } + break; + + case NAME_Green: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), v, BPART(color)); + } + break; + + case NAME_Blue: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), GPART(color), v); + } + break; + + case NAME_Class: + ClassChanged(li); + break; + + case NAME_Skin: + SkinChanged(li); + break; + + case NAME_Gender: + if (li->GetValue(0, &v)) + { + cvar_set ("gender", v==0? "male" : v==1? "female" : "other"); + } + break; + + case NAME_Autoaim: + AutoaimChanged(li); + break; + + case NAME_Switch: + if (li->GetValue(0, &v)) + { + neverswitchonpickup = !!v; + } + break; + + case NAME_AlwaysRun: + if (li->GetValue(0, &v)) + { + cl_run = !!v; + } + break; + + default: + break; + } + return true; + } + } + return Super::MenuEvent(mkey, fromcontroller); +} + + +bool DPlayerMenu::MouseEvent(int type, int x, int y) +{ + int v; + FListMenuItem *li = mFocusControl; + bool res = Super::MouseEvent(type, x, y); + if (li == NULL) li = mFocusControl; + if (li != NULL) + { + // Check if the colors have changed + FName current = li->GetAction(NULL); + switch(current) + { + case NAME_Red: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (v, GPART(color), BPART(color)); + } + break; + + case NAME_Green: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), v, BPART(color)); + } + break; + + case NAME_Blue: + if (li->GetValue(0, &v)) + { + int color = players[consoleplayer].userinfo.color; + SendNewColor (RPART(color), GPART(color), v); + } + break; + } + } + return res; +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Ticker () +{ + + Super::Ticker(); +} + +//============================================================================= +// +// +// +//============================================================================= + +void DPlayerMenu::Drawer () +{ + + Super::Drawer(); + + const char *str = "PRESS " TEXTCOLOR_WHITE "SPACE"; + screen->DrawText (SmallFont, CR_GOLD, 320 - 32 - 32 - + SmallFont->StringWidth (str)/2, + 50 + 48 + 70, str, + DTA_Clean, true, TAG_DONE); + str = mRotation ? "TO SEE FRONT" : "TO SEE BACK"; + screen->DrawText (SmallFont, CR_GOLD, 320 - 32 - 32 - + SmallFont->StringWidth (str)/2, + 50 + 48 + 70 + SmallFont->GetHeight (), str, + DTA_Clean, true, TAG_DONE); + +} diff --git a/src/menu/readthis.cpp b/src/menu/readthis.cpp new file mode 100644 index 0000000000..388dca716a --- /dev/null +++ b/src/menu/readthis.cpp @@ -0,0 +1,154 @@ +/* +** readthis.cpp +** Help screens +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "menu/menu.h" +#include "v_video.h" +#include "g_level.h" +#include "gi.h" +#include "textures/textures.h" + +class DReadThisMenu : public DMenu +{ + DECLARE_CLASS(DReadThisMenu, DMenu) + int mScreen; + int mInfoTic; + +public: + + DReadThisMenu(DMenu *parent = NULL); + void Drawer(); + bool MenuEvent(int mkey, bool fromcontroller); + bool DimAllowed () { return false; } + bool MouseEvent(int type, int x, int y); +}; + +IMPLEMENT_CLASS(DReadThisMenu) + +//============================================================================= +// +// Read This Menus +// +//============================================================================= + +DReadThisMenu::DReadThisMenu(DMenu *parent) +: DMenu(parent) +{ + mScreen = 1; + mInfoTic = gametic; +} + + +//============================================================================= +// +// +// +//============================================================================= + +void DReadThisMenu::Drawer() +{ + FTexture *tex = NULL, *prevpic = NULL; + fixed_t alpha; + + // Did the mapper choose a custom help page via MAPINFO? + if ((level.info != NULL) && level.info->f1[0] != 0) + { + tex = TexMan.FindTexture(level.info->f1); + mScreen = 1; + } + + if (tex == NULL) + { + tex = TexMan[gameinfo.infoPages[mScreen-1].GetChars()]; + } + + if (mScreen > 1) + { + prevpic = TexMan[gameinfo.infoPages[mScreen-2].GetChars()]; + } + + alpha = MIN (Scale (gametic - mInfoTic, OPAQUE, TICRATE/3), OPAQUE); + if (alpha < OPAQUE && prevpic != NULL) + { + screen->DrawTexture (prevpic, 0, 0, + DTA_DestWidth, screen->GetWidth(), + DTA_DestHeight, screen->GetHeight(), + TAG_DONE); + } + screen->DrawTexture (tex, 0, 0, + DTA_DestWidth, screen->GetWidth(), + DTA_DestHeight, screen->GetHeight(), + DTA_Alpha, alpha, + TAG_DONE); + +} + + +//============================================================================= +// +// +// +//============================================================================= + +bool DReadThisMenu::MenuEvent(int mkey, bool fromcontroller) +{ + if (mkey == MKEY_Enter) + { + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + mScreen++; + mInfoTic = gametic; + if ((level.info != NULL && level.info->f1[0] != 0) || mScreen > int(gameinfo.infoPages.Size())) + { + Close(); + } + return true; + } + else return Super::MenuEvent(mkey, fromcontroller); +} + +//============================================================================= +// +// +// +//============================================================================= + +bool DReadThisMenu::MouseEvent(int type, int x, int y) +{ + if (type == MOUSE_Click) + { + return MenuEvent(MKEY_Enter, true); + } + return false; +} + diff --git a/src/menu/videomenu.cpp b/src/menu/videomenu.cpp new file mode 100644 index 0000000000..0d15904635 --- /dev/null +++ b/src/menu/videomenu.cpp @@ -0,0 +1,442 @@ +/* +** videomenu.cpp +** The video modes menu +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include + +#include "menu/menu.h" +#include "c_dispatch.h" +#include "w_wad.h" +#include "sc_man.h" +#include "v_font.h" +#include "g_level.h" +#include "d_player.h" +#include "v_video.h" +#include "gi.h" +#include "i_system.h" +#include "c_bind.h" +#include "v_palette.h" +#include "d_event.h" +#include "d_gui.h" +#include "i_music.h" +#include "m_joy.h" +#include "sbar.h" +#include "hardware.h" + +#define NO_IMP +#include "optionmenuitems.h" + + +/*======================================= + * + * Video Modes Menu + * + *=======================================*/ +static void BuildModesList (int hiwidth, int hiheight, int hi_id); +static bool GetSelectedSize (int *width, int *height); +static void SetModesMenu (int w, int h, int bits); +FOptionMenuDescriptor *GetVideoModeMenu(); + +extern bool setmodeneeded; +extern int NewWidth, NewHeight, NewBits; +extern int DisplayBits; + +EXTERN_CVAR (Int, vid_defwidth) +EXTERN_CVAR (Int, vid_defheight) +EXTERN_CVAR (Int, vid_defbits) +EXTERN_CVAR (Bool, fullscreen) +EXTERN_CVAR (Bool, vid_tft) // Defined below + +int testingmode; // Holds time to revert to old mode +int OldWidth, OldHeight, OldBits; +static FIntCVar DummyDepthCvar (NULL, 0, 0); +static BYTE BitTranslate[32]; + +CUSTOM_CVAR (Int, menu_screenratios, 0, CVAR_ARCHIVE) +{ + if (self < 0 || self > 4) + { + self = 3; + } + else if (self == 4 && !vid_tft) + { + self = 3; + } + else + { + BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); + } +} + +CUSTOM_CVAR (Bool, vid_tft, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + FOptionMenuItem *it = opt->GetItem("menu_screenratios"); + if (it != NULL) + { + if (self) + { + it->SetString(FOptionMenuItemOptionBase::OP_VALUES, "RatiosTFT"); + } + else + { + it->SetString(FOptionMenuItemOptionBase::OP_VALUES, "Ratios"); + } + } + } + setsizeneeded = true; + if (StatusBar != NULL) + { + StatusBar->ScreenSizeChanged(); + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +class DVideoModeMenu : public DOptionMenu +{ + DECLARE_CLASS(DVideoModeMenu, DOptionMenu) + +public: + + DVideoModeMenu() + { + SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); + } + + bool MenuEvent(int mkey, bool fromcontroller) + { + if ((mkey == MKEY_Up || mkey == MKEY_Down) && mDesc->mSelectedItem >= 0 && + mDesc->mSelectedItem < (int)mDesc->mItems.Size()) + { + int sel; + bool selected = mDesc->mItems[mDesc->mSelectedItem]->GetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, &sel); + bool res = Super::MenuEvent(mkey, fromcontroller); + if (selected) mDesc->mItems[mDesc->mSelectedItem]->SetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, sel); + return res; + } + return Super::MenuEvent(mkey, fromcontroller); + } + + bool Responder(event_t *ev) + { + if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown && + (ev->data1 == 't' || ev->data1 == 'T')) + { + if (!GetSelectedSize (&NewWidth, &NewHeight)) + { + NewWidth = SCREENWIDTH; + NewHeight = SCREENHEIGHT; + } + OldWidth = SCREENWIDTH; + OldHeight = SCREENHEIGHT; + OldBits = DisplayBits; + NewBits = BitTranslate[DummyDepthCvar]; + setmodeneeded = true; + testingmode = I_GetTime(false) + 5 * TICRATE; + S_Sound (CHAN_VOICE | CHAN_UI, "menu/choose", snd_menuvolume, ATTN_NONE); + SetModesMenu (NewWidth, NewHeight, NewBits); + return true; + } + return Super::Responder(ev); + } +}; + +IMPLEMENT_CLASS(DVideoModeMenu) + + +//============================================================================= +// +// +// +//============================================================================= + +FOptionMenuDescriptor *GetVideoModeMenu() +{ + FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_VideoModeMenu); + if (desc != NULL && (*desc)->mType == MDESC_OptionsMenu) + { + return (FOptionMenuDescriptor *)*desc; + } + return NULL; +} + +//============================================================================= +// +// Set some stuff up for the video modes menu +// +//============================================================================= + +static void BuildModesList (int hiwidth, int hiheight, int hi_bits) +{ + char strtemp[32]; + int i, c; + int width, height, showbits; + bool letterbox=false; + int ratiomatch; + + if (menu_screenratios >= 0 && menu_screenratios <= 4 && menu_screenratios != 3) + { + ratiomatch = menu_screenratios; + } + else + { + ratiomatch = -1; + } + showbits = BitTranslate[DummyDepthCvar]; + + if (Video != NULL) + { + Video->StartModeIterator (showbits, screen->IsFullscreen()); + } + + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + for (i = NAME_res_0; i<= NAME_res_9; i++) + { + FOptionMenuItem *it = opt->GetItem((ENamedName)i); + if (it != NULL) + { + it->SetValue(FOptionMenuScreenResolutionLine::SRL_HIGHLIGHT, -1); + for (c = 0; c < 3; c++) + { + bool haveMode = false; + + if (Video != NULL) + { + while ((haveMode = Video->NextMode (&width, &height, &letterbox)) && + (ratiomatch >= 0 && CheckRatio (width, height) != ratiomatch)) + { + } + } + + if (haveMode) + { + if (width == hiwidth && height == hiheight) + { + it->SetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, c); + it->SetValue(FOptionMenuScreenResolutionLine::SRL_HIGHLIGHT, c); + } + + mysnprintf (strtemp, countof(strtemp), "%dx%d%s", width, height, letterbox?TEXTCOLOR_BROWN" LB":""); + it->SetString(FOptionMenuScreenResolutionLine::SRL_INDEX+c, strtemp); + } + else + { + it->SetString(FOptionMenuScreenResolutionLine::SRL_INDEX+c, ""); + } + } + } + } + } +} + + +//============================================================================= +// +// +// +//============================================================================= + +void M_RestoreMode () +{ + NewWidth = OldWidth; + NewHeight = OldHeight; + NewBits = OldBits; + setmodeneeded = true; + testingmode = 0; + SetModesMenu (OldWidth, OldHeight, OldBits); +} + +void M_SetDefaultMode () +{ + // Make current resolution the default + vid_defwidth = SCREENWIDTH; + vid_defheight = SCREENHEIGHT; + vid_defbits = DisplayBits; + testingmode = 0; + SetModesMenu (SCREENWIDTH, SCREENHEIGHT, DisplayBits); +} + + + +//============================================================================= +// +// +// +//============================================================================= + +void M_RefreshModesList () +{ + BuildModesList (SCREENWIDTH, SCREENHEIGHT, DisplayBits); +} + +void M_InitVideoModesMenu () +{ + int dummy1, dummy2; + size_t currval = 0; + + M_RefreshModesList(); + + for (unsigned int i = 1; i <= 32 && currval < countof(BitTranslate); i++) + { + Video->StartModeIterator (i, screen->IsFullscreen()); + if (Video->NextMode (&dummy1, &dummy2, NULL)) + { + BitTranslate[currval++] = i; + } + } + + /* It doesn't look like this can be anything but DISPLAY_Both, regardless of any other settings. + switch (Video->GetDisplayType ()) + { + case DISPLAY_FullscreenOnly: + case DISPLAY_WindowOnly: + // todo: gray out fullscreen option + default: + break; + } + */ +} + +//============================================================================= +// +// +// +//============================================================================= + +static bool GetSelectedSize (int *width, int *height) +{ + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + int line = opt->mSelectedItem; + int hsel; + FOptionMenuItem *it = opt->mItems[line]; + if (it->GetValue(FOptionMenuScreenResolutionLine::SRL_SELECTION, &hsel)) + { + char buffer[32]; + char *breakpt; + if (it->GetString(FOptionMenuScreenResolutionLine::SRL_INDEX+hsel, buffer, sizeof(buffer))) + { + *width = strtol (buffer, &breakpt, 10); + *height = strtol (breakpt+1, NULL, 10); + return true; + } + } + } + return false; +} + +//============================================================================= +// +// +// +//============================================================================= + +void M_SetVideoMode() +{ + if (!GetSelectedSize (&NewWidth, &NewHeight)) + { + NewWidth = SCREENWIDTH; + NewHeight = SCREENHEIGHT; + } + else + { + testingmode = 1; + setmodeneeded = true; + NewBits = BitTranslate[DummyDepthCvar]; + } + SetModesMenu (NewWidth, NewHeight, NewBits); +} + +//============================================================================= +// +// +// +//============================================================================= + +static int FindBits (int bits) +{ + int i; + + for (i = 0; i < 22; i++) + { + if (BitTranslate[i] == bits) + return i; + } + + return 0; +} + +static void SetModesMenu (int w, int h, int bits) +{ + DummyDepthCvar = FindBits (bits); + + FOptionMenuDescriptor *opt = GetVideoModeMenu(); + if (opt != NULL) + { + FOptionMenuItem *it; + if (testingmode <= 1) + { + it = opt->GetItem(NAME_VMEnterText); + if (it != NULL) it->SetValue(0, 0); + it = opt->GetItem(NAME_VMTestText); + if (it != NULL) it->SetValue(0, 0); + } + else + { + + it = opt->GetItem(NAME_VMTestText); + if (it != NULL) it->SetValue(0, 1); + it = opt->GetItem(NAME_VMEnterText); + if (it != NULL) + { + char strtemp[64]; + mysnprintf (strtemp, countof(strtemp), "TESTING %dx%dx%d", w, h, bits); + it->SetValue(0, 1); + it->SetString(0, strtemp); + } + } + } + BuildModesList (w, h, bits); +} diff --git a/src/namedef.h b/src/namedef.h index 99737eda50..9f7e5a87d1 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -465,3 +465,64 @@ 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/p_conversation.cpp b/src/p_conversation.cpp index 0d7d311b4c..a2c287a422 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -41,7 +41,6 @@ #include "w_wad.h" #include "cmdlib.h" #include "s_sound.h" -#include "m_menu.h" #include "v_text.h" #include "v_video.h" #include "m_random.h" @@ -56,11 +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 @@ -112,9 +113,8 @@ typedef TMap FDialogueMap; // maps actor class names to dialogue 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; @@ -123,13 +123,11 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker 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 @@ -233,7 +231,6 @@ void P_FreeStrifeConversations () DialogueRoots.Clear(); ClassRoots.Clear(); - CurNode = NULL; PrevNode = NULL; } @@ -541,10 +538,6 @@ static void ParseReplies (FStrifeDialogueReply **replyptr, Response *responses) 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) @@ -615,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); } //============================================================================ @@ -703,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 @@ -714,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; @@ -798,102 +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. ;) - } - } - else - { - // handle string table replacement - if (toSay[0] == '$') - { - toSay = GStrings(toSay + 1); - } - } - if (toSay == NULL) - { - toSay = "."; - } - DialogueLines = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay); - - // Fill out the possible choices - 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(); - // handle string table replacement - if (item.label[0] == '$') - { - item.label = GStrings(item.label + 1); - } - - item.b.position = j == 0 ? i : 0; - item.c.extra = reply; - ConversationItems.Push (item); - } - ++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); } } @@ -921,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 @@ -1307,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); } //============================================================================ @@ -1408,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 diff --git a/src/p_conversation.h b/src/p_conversation.h index 61c0ef8f4b..d4fa600098 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -48,8 +48,6 @@ struct FStrifeDialogueReply char *LogString; char *QuickNo; bool NeedsGold; - - FBrokenLines *ReplyLines; }; extern TArray StrifeDialogues; diff --git a/src/r_draw.cpp b/src/r_draw.cpp index 4334f6f79e..ba77ad2d8c 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -1961,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) { @@ -1982,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/sdl/i_input.cpp b/src/sdl/i_input.cpp index 3b3823e376..3e94ff045b 100644 --- a/src/sdl/i_input.cpp +++ b/src/sdl/i_input.cpp @@ -147,6 +147,14 @@ static void I_CheckGUICapture () } } +void I_SetMouseCapture() +{ +} + +void I_ReleaseMouseCapture() +{ +} + static void CenterMouse () { SDL_WarpMouse (screen->GetWidth()/2, screen->GetHeight()/2); diff --git a/src/sdl/i_input.h b/src/sdl/i_input.h index 01d3673c6c..124c2ca853 100644 --- a/src/sdl/i_input.h +++ b/src/sdl/i_input.h @@ -3,6 +3,8 @@ void I_PutInClipboard (const char *str); FString I_GetFromClipboard (bool use_primary_selection); +void I_SetMouseCapture(); +void I_ReleaseMouseCapture(); #endif diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index c05ed2027b..ff15b1e888 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 778af53f54..acdae0167d 100644 --- a/src/sdl/i_system.h +++ b/src/sdl/i_system.h @@ -128,6 +128,8 @@ bool I_WriteIniFailed (); unsigned int I_MSTime (void); unsigned int I_FPSTime(); +class FTexture; +bool I_SetCursor(FTexture *); // Directory searching routines diff --git a/src/sound/i_music.h b/src/sound/i_music.h index 88e6f61c06..5b238cd4c9 100644 --- a/src/sound/i_music.h +++ b/src/sound/i_music.h @@ -37,13 +37,14 @@ #include "doomdef.h" class FileReader; +struct FOptionValues; // // MUSIC I/O // void I_InitMusic (); void I_ShutdownMusic (); -void I_BuildMIDIMenuList (struct value_t **values, float *numValues); +void I_BuildMIDIMenuList (FOptionValues *); void I_UpdateMusic (); // Volume. diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index 39f42cc35a..97eb887083 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -5,7 +5,7 @@ #include "templates.h" #include "v_text.h" -#include "m_menu.h" +#include "menu/menu.h" static DWORD nummididevices; static bool nummididevicesset; @@ -71,50 +71,43 @@ void I_ShutdownMusicWin32 () } } -void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) -{ - if (*outValues == NULL) - { - int count = 4 + nummididevices; - value_t *values; - UINT id; - int p = 0; - - *outValues = values = new value_t[count]; - #ifdef HAVE_FLUIDSYNTH - values[p].name = "FluidSynth"; - values[p].value = -5.0; - ++p; +#define NUM_DEF_DEVICES 4 +#else +#define NUM_DEF_DEVICES 3 #endif - values[p].name = "OPL Synth Emulation"; - values[p].value = -3.0; - ++p; - values[p].name = "TiMidity++"; - values[p].value = -2.0; - ++p; - values[p].name = "FMOD"; - values[p].value = -1.0; - ++p; - for (id = 0; id < nummididevices; ++id) +void I_BuildMIDIMenuList (FOptionValues *opt) +{ + 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) + { + MIDIOUTCAPS caps; + MMRESULT res; + + res = midiOutGetDevCaps (id, &caps, sizeof(caps)); + assert(res == MMSYSERR_NOERROR); + if (res == MMSYSERR_NOERROR) { - MIDIOUTCAPS caps; - MMRESULT res; - - res = midiOutGetDevCaps (id, &caps, sizeof(caps)); - assert(res == MMSYSERR_NOERROR); - if (res == MMSYSERR_NOERROR) - { - size_t len = strlen (caps.szPname) + 1; - 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; } - *numValues = float(p); } } @@ -195,31 +188,28 @@ CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) self = -1; } -void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) -{ - if (*outValues == NULL) - { - value_t *values; - int p = 0; - - *outValues = values = new value_t[4]; - #ifdef HAVE_FLUIDSYNTH - values[p].name = "FluidSynth"; - values[p].value = -5.0; - ++p; +#define NUM_DEF_DEVICES 4 +#else +#define NUM_DEF_DEVICES 3 #endif - values[p].name = "OPL Synth Emulation"; - values[p].value = -3.0; - ++p; - values[p].name = "TiMidity++"; - values[p].value = -2.0; - ++p; - values[p].name = "FMOD"; - values[p].value = -1.0; - ++p; - *numValues = float(p); - } +void I_BuildMIDIMenuList (FOptionValues *opt) +{ + 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) diff --git a/src/textures/bitmap.h b/src/textures/bitmap.h index 4bd7f32bb5..da039fd0f8 100644 --- a/src/textures/bitmap.h +++ b/src/textures/bitmap.h @@ -100,6 +100,7 @@ public: Width = w; Height = h; data = new BYTE[4*w*h]; + memset(data, 0, 4*w*h); FreeBuffer = true; ClipRect.x = ClipRect.y = 0; ClipRect.width = w; diff --git a/src/textures/multipatchtexture.cpp b/src/textures/multipatchtexture.cpp index 90c52dd63c..4debfa24a0 100644 --- a/src/textures/multipatchtexture.cpp +++ b/src/textures/multipatchtexture.cpp @@ -189,7 +189,7 @@ protected: private: void CheckForHacks (); - void ParsePatch(FScanner &sc, TexPart & part); + void ParsePatch(FScanner &sc, TexPart & part, bool silent); }; //========================================================================== @@ -970,7 +970,7 @@ void FTextureManager::AddTexturesLumps (int lump1, int lump2, int patcheslump) // //========================================================================== -void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) +void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part, bool silent) { FString patchname; sc.MustGetString(); @@ -1011,7 +1011,7 @@ void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) } if (part.Texture == NULL) { - Printf("Unknown patch '%s' in texture '%s'\n", sc.String, Name); + if (!silent) Printf("Unknown patch '%s' in texture '%s'\n", sc.String, Name); } sc.MustGetStringName(","); sc.MustGetNumber(); @@ -1164,6 +1164,14 @@ void FMultiPatchTexture::ParsePatch(FScanner &sc, TexPart & part) bComplex |= (part.op != OP_COPY); bTranslucentPatches = bComplex; } + else if (sc.Compare("useoffsets")) + { + if (part.Texture != NULL) + { + part.OriginX -= part.Texture->LeftOffset; + part.OriginY -= part.Texture->TopOffset; + } + } } } if (Mirror & 2) @@ -1187,10 +1195,23 @@ FMultiPatchTexture::FMultiPatchTexture (FScanner &sc, int usetype) : Pixels (0), Spans(0), Parts(0), bRedirect(false), bTranslucentPatches(false) { TArray parts; + bool bSilent = false; bMultiPatch = true; sc.SetCMode(true); sc.MustGetString(); + if (sc.Compare("optional")) + { + bSilent = true; + sc.MustGetString(); + if (sc.Compare(",")) + { + // this is not right. Apparently a texture named 'optional' is being defined right now... + sc.UnGet(); + sc.String = "optional"; + bSilent = false; + } + } uppercopy(Name, sc.String); Name[8] = 0; sc.MustGetStringName(","); @@ -1231,7 +1252,7 @@ FMultiPatchTexture::FMultiPatchTexture (FScanner &sc, int usetype) else if (sc.Compare("Patch")) { TexPart part; - ParsePatch(sc, part); + ParsePatch(sc, part, bSilent); if (part.Texture != NULL) parts.Push(part); part.Texture = NULL; part.Translation = NULL; diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 1e47dfd869..d41d79891a 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -2182,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 af6b804d13..f22c9c9284 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -45,6 +45,7 @@ #include "r_translate.h" #include "doomstat.h" #include "v_palette.h" +#include "gi.h" #include "i_system.h" #include "i_video.h" @@ -1351,3 +1352,35 @@ bool DCanvas::ClipBox (int &x, int &y, int &w, int &h, const BYTE *&src, const i } return false; } + +// Draw a frame around the specified area using the view border +// frame graphics. The border is drawn outside the area, not in it. +void V_DrawFrame (int left, int top, int width, int height) +{ + FTexture *p; + const gameborder_t *border = gameinfo.border; + // Sanity check for incomplete gameinfo + if (border == NULL) + return; + int offset = border->offset; + int right = left + width; + int bottom = top + height; + + // Draw top and bottom sides. + p = TexMan[border->t]; + screen->FlatFill(left, top - p->GetHeight(), right, top, p, true); + p = TexMan[border->b]; + screen->FlatFill(left, bottom, right, bottom + p->GetHeight(), p, true); + + // Draw left and right sides. + p = TexMan[border->l]; + screen->FlatFill(left - p->GetWidth(), top, left, bottom, p, true); + p = TexMan[border->r]; + screen->FlatFill(right, top, right + p->GetWidth(), bottom, p, true); + + // Draw beveled corners. + screen->DrawTexture (TexMan[border->tl], left-offset, top-offset, TAG_DONE); + screen->DrawTexture (TexMan[border->tr], left+width, top-offset, TAG_DONE); + screen->DrawTexture (TexMan[border->bl], left-offset, top+height, TAG_DONE); + screen->DrawTexture (TexMan[border->br], left+width, top+height, TAG_DONE); +} \ No newline at end of file diff --git a/src/v_font.cpp b/src/v_font.cpp index ea6feb1493..3815c8ae49 100644 --- a/src/v_font.cpp +++ b/src/v_font.cpp @@ -2043,6 +2043,15 @@ void V_InitFontColors () while ((lump = Wads.FindLump ("TEXTCOLO", &lastlump)) != -1) { + if (gameinfo.flags & GI_NOTEXTCOLOR) + { + // Chex3 contains a bad TEXTCOLO lump, probably to force all text to be green. + // This renders the Gray, Gold, Red and Yellow color range inoperable, some of + // which are used by the menu. So we have no choice but to skip this lump so that + // all colors work properly. + // The text colors should be the end user's choice anyway. + if (Wads.GetLumpFile(lump) == 1) continue; + } FScanner sc(lump); while (sc.GetString()) { diff --git a/src/v_palette.cpp b/src/v_palette.cpp index 3149f5df5a..7761a3dece 100644 --- a/src/v_palette.cpp +++ b/src/v_palette.cpp @@ -118,6 +118,21 @@ CUSTOM_CVAR (Float, Gamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) } } +CCMD (bumpgamma) +{ + // [RH] Gamma correction tables are now generated + // on the fly for *any* gamma level. + // Q: What are reasonable limits to use here? + + float newgamma = Gamma + 0.1f; + + if (newgamma > 3.0) + newgamma = 1.0; + + Gamma = newgamma; + Printf ("Gamma correction level %g\n", *Gamma); +} + /****************************/ /* Palette management stuff */ diff --git a/src/v_video.cpp b/src/v_video.cpp index 1e8993e748..c51f13cfca 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -42,7 +42,6 @@ #include "m_argv.h" #include "m_bbox.h" #include "m_swap.h" -#include "m_menu.h" #include "i_video.h" #include "v_video.h" @@ -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 b1ba7a8631..14782519cc 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -412,6 +412,7 @@ public: virtual void WipeEndScreen(); virtual bool WipeDo(int ticks); virtual void WipeCleanup(); + virtual int GetPixelDoubling() const { return 1; } uint32 GetLastFPS() const { return LastCount; } @@ -489,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/win32/i_dijoy.cpp b/src/win32/i_dijoy.cpp index 89ddd16002..b78fea64e4 100644 --- a/src/win32/i_dijoy.cpp +++ b/src/win32/i_dijoy.cpp @@ -21,7 +21,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" @@ -236,8 +235,6 @@ protected: // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- -extern void UpdateJoystickMenu(); - // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- @@ -246,7 +243,6 @@ static void MapAxis(FIntCVar &var, int num); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- -extern menu_t JoystickMenu; extern LPDIRECTINPUT8 g_pdi; extern HWND Window; diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index d01547daef..f302d807d0 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -86,7 +86,6 @@ #include "i_input.h" #include "v_video.h" #include "i_sound.h" -#include "m_menu.h" #include "g_game.h" #include "d_main.h" #include "d_gui.h" @@ -145,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,35 @@ bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESU { ev.subtype = message - WM_MBUTTONDOWN + EV_GUI_MButtonDown; } - D_PostEvent(&ev); + else if (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONUP) + { + ev.subtype = message - WM_XBUTTONDOWN + EV_GUI_BackButtonDown; + if (GET_XBUTTON_WPARAM(wParam) == 2) + { + ev.subtype += EV_GUI_FwdButtonDown - EV_GUI_BackButtonDown; + } + else if (GET_XBUTTON_WPARAM(wParam) != 1) + { + break; + } + } + else if (message == WM_MOUSEMOVE) + { + ev.subtype = EV_GUI_MouseMove; + } + ev.data1 = LOWORD(lParam) >> screen->GetPixelDoubling(); + ev.data2 = HIWORD(lParam) >> screen->GetPixelDoubling(); + + if (wParam & MK_SHIFT) ev.data3 |= GKM_SHIFT; + if (wParam & MK_CONTROL) ev.data3 |= GKM_CTRL; + if (GetKeyState(VK_MENU) & 0x8000) ev.data3 |= GKM_ALT; + + if (use_mouse) D_PostEvent(&ev); return true; // Note: If the mouse is grabbed, it sends the mouse wheel events itself. case WM_MOUSEWHEEL: + if (!use_mouse) return false; if (wParam & MK_SHIFT) ev.data3 |= GKM_SHIFT; if (wParam & MK_CONTROL) ev.data3 |= GKM_CTRL; if (GetKeyState(VK_MENU) & 0x8000) ev.data3 |= GKM_ALT; @@ -385,6 +423,15 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return result; } + if ((gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL) && message == WM_LBUTTONDOWN) + { + if (GUIWndProcHook(hWnd, message, wParam, lParam, &result)) + { + return result; + } + } + + switch (message) { case WM_DESTROY: @@ -425,6 +472,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 1c21d78c35..b0d409844d 100644 --- a/src/win32/i_input.h +++ b/src/win32/i_input.h @@ -37,6 +37,9 @@ #include "doomtype.h" #include "doomdef.h" +void I_SetMouseCapture(); +void I_ReleaseMouseCapture(); + bool I_InitInput (void *hwnd); void I_ShutdownInput (); void I_PutInClipboard (const char *str); diff --git a/src/win32/i_mouse.cpp b/src/win32/i_mouse.cpp index d347a4d72a..0fa46c1db1 100644 --- a/src/win32/i_mouse.cpp +++ b/src/win32/i_mouse.cpp @@ -1,8 +1,3 @@ -#ifdef _MSC_VER -// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" -// generated by SetClassLongPtr(). -#pragma warning(disable:4244) -#endif // HEADER FILES ------------------------------------------------------------ @@ -22,6 +17,7 @@ #include "doomstat.h" #include "win32iface.h" #include "rawinput.h" +#include "menu/menu.h" // MACROS ------------------------------------------------------------------ @@ -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); @@ -592,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; } @@ -806,7 +801,7 @@ void FDInputMouse::ProcessInput() dx = 0; dy = 0; - if (!Grabbed) + if (!Grabbed || !use_mouse) return; event_t ev = { 0 }; @@ -948,7 +943,7 @@ void FWin32Mouse::ProcessInput() POINT pt; int x, y; - if (!Grabbed || !GetCursorPos(&pt)) + if (!Grabbed || !use_mouse || !GetCursorPos(&pt)) { return; } @@ -1006,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 d060b869d1..cab2844a61 100644 --- a/src/win32/i_rawps2.cpp +++ b/src/win32/i_rawps2.cpp @@ -16,7 +16,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" diff --git a/src/win32/i_system.cpp b/src/win32/i_system.cpp index c8b496656e..f2178ffbfa 100644 --- a/src/win32/i_system.cpp +++ b/src/win32/i_system.cpp @@ -83,9 +83,17 @@ #include "doomstat.h" #include "v_palette.h" #include "stats.h" +#include "r_data.h" +#include "textures/bitmap.h" // MACROS ------------------------------------------------------------------ +#ifdef _MSC_VER +// Turn off "conversion from 'LONG_PTR' to 'LONG', possible loss of data" +// generated by SetClassLongPtr(). +#pragma warning(disable:4244) +#endif + // TYPES ------------------------------------------------------------------- // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -108,6 +116,11 @@ static int I_WaitForTicEvent(int prevtic); static void I_FreezeTimeEventDriven(bool frozen); static void CALLBACK TimerTicked(UINT id, UINT msg, DWORD_PTR user, DWORD_PTR dw1, DWORD_PTR dw2); +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic); +static HCURSOR CreateAlphaCursor(FTexture *cursorpic); +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask); +static void DestroyCustomCursor(); + // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR(String, language); @@ -158,6 +171,8 @@ static WadStuff *WadList; static int NumWads; static int DefaultWad; +static HCURSOR CustomCursor; + // CODE -------------------------------------------------------------------- //========================================================================== @@ -1175,6 +1190,208 @@ int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad) return defaultiwad; } +//========================================================================== +// +// I_SetCursor +// +// Returns true if the cursor was successfully changed. +// +//========================================================================== + +bool I_SetCursor(FTexture *cursorpic) +{ + HCURSOR cursor; + + // Must be no larger than 32x32. + if (cursorpic->GetWidth() > 32 || cursorpic->GetHeight() > 32) + { + return false; + } + + cursor = CreateAlphaCursor(cursorpic); + if (cursor == NULL) + { + cursor = CreateCompatibleCursor(cursorpic); + } + if (cursor == NULL) + { + return false; + } + // Replace the existing cursor with the new one. + if (CustomCursor != NULL) + { + DestroyCursor(CustomCursor); + } + CustomCursor = cursor; + atterm(DestroyCustomCursor); + SetClassLongPtr(Window, GCLP_HCURSOR, (LONG_PTR)cursor); + return true; +} + +//========================================================================== +// +// CreateCompatibleCursor +// +// Creates a cursor with a 1-bit alpha channel. +// +//========================================================================== + +static HCURSOR CreateCompatibleCursor(FTexture *cursorpic) +{ + int picwidth = cursorpic->GetWidth(); + int picheight = cursorpic->GetHeight(); + + // Create bitmap masks for the cursor from the texture. + HDC dc = GetDC(NULL); + if (dc == NULL) + { + return false; + } + HDC and_mask_dc = CreateCompatibleDC(dc); + HDC xor_mask_dc = CreateCompatibleDC(dc); + HBITMAP and_mask = CreateCompatibleBitmap(dc, 32, 32); + HBITMAP xor_mask = CreateCompatibleBitmap(dc, 32, 32); + ReleaseDC(NULL, dc); + + SelectObject(and_mask_dc, and_mask); + SelectObject(xor_mask_dc, xor_mask); + + // Initialize with an invisible cursor. + SelectObject(and_mask_dc, GetStockObject(WHITE_PEN)); + SelectObject(and_mask_dc, GetStockObject(WHITE_BRUSH)); + Rectangle(and_mask_dc, 0, 0, 32, 32); + SelectObject(xor_mask_dc, GetStockObject(BLACK_PEN)); + SelectObject(xor_mask_dc, GetStockObject(BLACK_BRUSH)); + Rectangle(xor_mask_dc, 0, 0, 32, 32); + + FBitmap bmp; + const BYTE *pixels; + + bmp.Create(picwidth, picheight); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + pixels = bmp.GetPixels(); + + // Copy color data from the source texture to the cursor bitmaps. + for (int y = 0; y < picheight; ++y) + { + for (int x = 0; x < picwidth; ++x) + { + const BYTE *bgra = &pixels[x*4 + y*bmp.GetPitch()]; + if (bgra[3] != 0) + { + SetPixelV(and_mask_dc, x, y, RGB(0,0,0)); + SetPixelV(xor_mask_dc, x, y, RGB(bgra[2], bgra[1], bgra[0])); + } + } + } + DeleteDC(and_mask_dc); + DeleteDC(xor_mask_dc); + + // Create the cursor from the bitmaps. + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, and_mask, xor_mask); +} + +//========================================================================== +// +// CreateAlphaCursor +// +// Creates a cursor with a full alpha channel. +// +//========================================================================== + +static HCURSOR CreateAlphaCursor(FTexture *cursorpic) +{ + HDC dc; + BITMAPV5HEADER bi; + HBITMAP color, mono; + void *bits; + + memset(&bi, 0, sizeof(bi)); + bi.bV5Size = sizeof(bi); + bi.bV5Width = 32; + bi.bV5Height = 32; + bi.bV5Planes = 1; + bi.bV5BitCount = 32; + bi.bV5Compression = BI_BITFIELDS; + bi.bV5RedMask = 0x00FF0000; + bi.bV5GreenMask = 0x0000FF00; + bi.bV5BlueMask = 0x000000FF; + bi.bV5AlphaMask = 0xFF000000; + + dc = GetDC(NULL); + if (dc == NULL) + { + return NULL; + } + + // Create the DIB section with an alpha channel. + color = CreateDIBSection(dc, (BITMAPINFO *)&bi, DIB_RGB_COLORS, &bits, NULL, 0); + ReleaseDC(NULL, dc); + + if (color == NULL) + { + return NULL; + } + + // Create an empty mask bitmap, since CreateIconIndirect requires this. + mono = CreateBitmap(32, 32, 1, 1, NULL); + if (mono == NULL) + { + DeleteObject(color); + return NULL; + } + + // Copy cursor to the color bitmap. Note that GDI bitmaps are upside down compared + // to normal conventions, so we create the FBitmap pointing at the last row and use + // a negative pitch so that CopyTrueColorPixels will use GDI's orientation. + FBitmap bmp((BYTE *)bits + 31*32*4, -32*4, 32, 32); + cursorpic->CopyTrueColorPixels(&bmp, 0, 0); + + return CreateBitmapCursor(cursorpic->LeftOffset, cursorpic->TopOffset, mono, color); +} + +//========================================================================== +// +// CreateBitmapCursor +// +// Create the cursor from the bitmaps. Deletes the bitmaps before returning. +// +//========================================================================== + +static HCURSOR CreateBitmapCursor(int xhot, int yhot, HBITMAP and_mask, HBITMAP color_mask) +{ + ICONINFO iconinfo = + { + FALSE, // fIcon + xhot, // xHotspot + yhot, // yHotspot + and_mask, // hbmMask + color_mask // hbmColor + }; + HCURSOR cursor = CreateIconIndirect(&iconinfo); + + // Delete the bitmaps. + DeleteObject(and_mask); + DeleteObject(color_mask); + + return cursor; +} + +//========================================================================== +// +// DestroyCustomCursor +// +//========================================================================== + +static void DestroyCustomCursor() +{ + if (CustomCursor != NULL) + { + DestroyCursor(CustomCursor); + CustomCursor = NULL; + } +} + //========================================================================== // // I_WriteIniFailed diff --git a/src/win32/i_system.h b/src/win32/i_system.h index 66b2bac7d8..1a5e755e30 100644 --- a/src/win32/i_system.h +++ b/src/win32/i_system.h @@ -114,6 +114,10 @@ void STACK_ARGS I_FatalError (const char *error, ...) GCCPRINTF(1,2); void atterm (void (*func)(void)); void popterm (); +// Set the mouse cursor. The texture must be 32x32. +class FTexture; +bool I_SetCursor(FTexture *cursor); + // Repaint the pre-game console void I_PaintConsole (void); diff --git a/src/win32/i_xinput.cpp b/src/win32/i_xinput.cpp index e3ad1eed0d..5a02d7af4b 100644 --- a/src/win32/i_xinput.cpp +++ b/src/win32/i_xinput.cpp @@ -19,7 +19,6 @@ #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" -#include "m_menu.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" diff --git a/src/win32/win32iface.h b/src/win32/win32iface.h index 6193c641c2..e3710b2343 100644 --- a/src/win32/win32iface.h +++ b/src/win32/win32iface.h @@ -372,6 +372,7 @@ private: void EndLineBatch(); void EndBatch(); void CopyNextFrontBuffer(); + int GetPixelDoubling() const { return PixelDoubling; } D3DCAPS9 DeviceCaps; diff --git a/wadsrc/static/actors/hexen/clericplayer.txt b/wadsrc/static/actors/hexen/clericplayer.txt index 4fe6629e28..73276e9815 100644 --- a/wadsrc/static/actors/hexen/clericplayer.txt +++ b/wadsrc/static/actors/hexen/clericplayer.txt @@ -23,6 +23,7 @@ ACTOR ClericPlayer : PlayerPawn Player.HealRadiusType "Health" Player.Hexenarmor 10, 10, 25, 5, 20 Player.StartItem "CWeapMace" + Player.Portrait "P_CWALK1" Player.WeaponSlot 1, CWeapMace Player.WeaponSlot 2, CWeapStaff Player.WeaponSlot 3, CWeapFlame diff --git a/wadsrc/static/actors/hexen/fighterplayer.txt b/wadsrc/static/actors/hexen/fighterplayer.txt index 2253ba5b64..809b1f9e98 100644 --- a/wadsrc/static/actors/hexen/fighterplayer.txt +++ b/wadsrc/static/actors/hexen/fighterplayer.txt @@ -23,6 +23,7 @@ ACTOR FighterPlayer : PlayerPawn Player.StartItem "FWeapFist" Player.ForwardMove 1.08, 1.2 Player.SideMove 1.125, 1.475 + Player.Portrait "P_FWALK1" Player.WeaponSlot 1, FWeapFist Player.WeaponSlot 2, FWeapAxe Player.WeaponSlot 3, FWeapHammer diff --git a/wadsrc/static/actors/hexen/mageplayer.txt b/wadsrc/static/actors/hexen/mageplayer.txt index a34698366d..d843b00a9c 100644 --- a/wadsrc/static/actors/hexen/mageplayer.txt +++ b/wadsrc/static/actors/hexen/mageplayer.txt @@ -25,6 +25,7 @@ ACTOR MagePlayer : PlayerPawn Player.StartItem "MWeapWand" Player.ForwardMove 0.88, 0.92 Player.SideMove 0.875, 0.925 + Player.Portrait "P_MWALK1" Player.WeaponSlot 1, MWeapWand Player.WeaponSlot 2, MWeapFrost Player.WeaponSlot 3, MWeapLightning diff --git a/wadsrc/static/animdefs.txt b/wadsrc/static/animdefs.txt index a2a4498e28..8bd2ee99a9 100644 --- a/wadsrc/static/animdefs.txt +++ b/wadsrc/static/animdefs.txt @@ -101,6 +101,85 @@ pic PTN1A0 tics 3 pic PTN1B0 tics 3 pic PTN1C0 tics 3 +// Hexen's player portraits +texture optional P_FWALK1 + pic P_FWALK1 tics 8 + pic P_FWALK2 tics 8 + pic P_FWALK3 tics 8 + pic P_FWALK4 tics 8 + +texture optional P_CWALK1 + pic P_CWALK1 tics 8 + pic P_CWALK2 tics 8 + pic P_CWALK3 tics 8 + pic P_CWALK4 tics 8 + +texture optional P_MWALK1 + pic P_MWALK1 tics 8 + pic P_MWALK2 tics 8 + pic P_MWALK3 tics 8 + pic P_MWALK4 tics 8 + +// Heretic's menu skulls +texture optional M_SKL00 + pic M_SKL00 tics 3 + pic M_SKL01 tics 3 + pic M_SKL02 tics 3 + pic M_SKL03 tics 3 + pic M_SKL04 tics 3 + pic M_SKL05 tics 3 + pic M_SKL06 tics 3 + pic M_SKL07 tics 3 + pic M_SKL08 tics 3 + pic M_SKL09 tics 3 + pic M_SKL10 tics 3 + pic M_SKL11 tics 3 + pic M_SKL12 tics 3 + pic M_SKL13 tics 3 + pic M_SKL14 tics 3 + pic M_SKL15 tics 3 + pic M_SKL16 tics 3 + pic M_SKL17 tics 3 + +texture optional M_SKL01 + pic M_SKL17 tics 3 + pic M_SKL16 tics 3 + pic M_SKL15 tics 3 + pic M_SKL14 tics 3 + pic M_SKL13 tics 3 + pic M_SKL12 tics 3 + pic M_SKL11 tics 3 + pic M_SKL10 tics 3 + pic M_SKL09 tics 3 + pic M_SKL08 tics 3 + pic M_SKL07 tics 3 + pic M_SKL06 tics 3 + pic M_SKL05 tics 3 + pic M_SKL04 tics 3 + pic M_SKL03 tics 3 + pic M_SKL02 tics 3 + pic M_SKL01 tics 3 + pic M_SKL00 tics 3 + +// Hexen's Fire Bulls +texture optional FBULA0 + pic FBULA0 tics 5 + pic FBULB0 tics 5 + pic FBULC0 tics 5 + pic FBULD0 tics 5 + pic FBULE0 tics 5 + pic FBULF0 tics 5 + pic FBULG0 tics 5 + +texture optional FBULB0 + pic FBULC0 tics 5 + pic FBULD0 tics 5 + pic FBULE0 tics 5 + pic FBULF0 tics 5 + pic FBULG0 tics 5 + pic FBULA0 tics 5 + pic FBULB0 tics 5 + // The Wings of Wrath are not included, because they stop spinning when // you stop flying, so they can't be a simple animation. diff --git a/wadsrc/static/graphics/cursor.png b/wadsrc/static/graphics/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..c9ee9b6e3125e54fdca9d5e972b087500da7c9f9 GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0vp^JU}eO!3HGrSK5O(9O*@lNkEzrh?xQ}sr~}eyFFbT zLn`8?UNYo5WFX`A@y)yg+AG3Ou*PiOvGW$Q#rEYL7417JnoG*oxMzHK`u0Q0MJDD|;l>-PzICnhYrTfRr}D&w=qX>T7z9dI_#JHU9!a7q9A9@nQExA1eDt`I(-zuz0^4F*qF KKbLh*2~7ZAXKRH3 literal 0 HcmV?d00001 diff --git a/wadsrc/static/graphics/m_back_d.png b/wadsrc/static/graphics/m_back_d.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb4a827d8340232ed00a5749b83122152187b6b GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XEGms3L`dJ)ENeB3ZxU&5FW6j9)nu+Zk6I&!B z^F}7t$xJL|jLdS3jQR>^^?=G4OM?7@862M7NCR@>JzX3_DsGvcb>wPL5Mj9xS=Ai% zT+rzSLt~u7u7)3b9c~0^A51paZP_mM?+p8bDs_hQn*^EP3NvoEJmI)@(w3H{v=*5Q zH4B*bD1DipvnR(!@~X}eWz$smn03E>B=*~#+b5UuP593>pj8Z>u6{1-oD!M~mo>t^* zFyLu@IDNqbkvUxx6ACr%sBmUQ$b9xarn4q#`eRd-`5FJVF(}Mt+!fsET;-l5P~P!a zOLty_+B93Sw%zNFTn;noxb@w%a?=#kC-$NK)7siC65hRJR*YAdXLIK~477^D)78&q Iol`;+0MC|1wEzGB literal 0 HcmV?d00001 diff --git a/wadsrc/static/graphics/m_back_s.png b/wadsrc/static/graphics/m_back_s.png new file mode 100644 index 0000000000000000000000000000000000000000..ab63b43105985e3022e75825332debffdc6fd026 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^0w6XEGms3L`dJ)EX$AO%xU&5FQ^Lj5k{9E{#FE3t zGN&u2r!K{Yi6w@OMTLo_B*|yVtP&F@77;F< zdimYd9bs{^FVdQ&MBb@02aSGqW}N^ literal 0 HcmV?d00001 diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 58cc870490..5f0eff88e3 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -1559,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 deefea4d3b..5a76298cb8 100644 --- a/wadsrc/static/mapinfo/chex.txt +++ b/wadsrc/static/mapinfo/chex.txt @@ -43,6 +43,15 @@ gameinfo player5start = 4001 drawreadthis = true pickupcolor = "d6 ba 45" + quitmessages = "$QUITMSG", "$QUITMSG23", "$QUITMSG24", "$QUITMSG25", "$QUITMSG26", "$QUITMSG27", "$QUITMSG28", "$QUITMSG29" + menufontcolor_title = "GREEN" + menufontcolor_label = "UNTRANSLATED" + menufontcolor_value = "GRAY" + menufontcolor_action = "GRAY" + menufontcolor_header = "YELLOW" + menufontcolor_highlight = "BLUE" + menufontcolor_selection = "GOLD" + menubackbutton = "M_BACK_H" } skill baby diff --git a/wadsrc/static/mapinfo/doom1.txt b/wadsrc/static/mapinfo/doom1.txt index b3e6316501..064f3777a1 100644 --- a/wadsrc/static/mapinfo/doom1.txt +++ b/wadsrc/static/mapinfo/doom1.txt @@ -14,6 +14,7 @@ gameinfo borderflat = "FLOOR7_2" drawreadthis = true intermissionmusic = "$MUSIC_INTER" + quitmessages = "$QUITMSG", "$QUITMSG1", "$QUITMSG2", "$QUITMSG3", "$QUITMSG4", "$QUITMSG5", "$QUITMSG6", "$QUITMSG7" } clearepisodes diff --git a/wadsrc/static/mapinfo/doomcommon.txt b/wadsrc/static/mapinfo/doomcommon.txt index f677835093..9fd4b96379 100644 --- a/wadsrc/static/mapinfo/doomcommon.txt +++ b/wadsrc/static/mapinfo/doomcommon.txt @@ -41,6 +41,17 @@ gameinfo 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 4ca7272725..b9f88e2b92 100644 --- a/wadsrc/static/mapinfo/heretic.txt +++ b/wadsrc/static/mapinfo/heretic.txt @@ -42,6 +42,15 @@ gameinfo 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 16ebcf5b48..8f0962a50e 100644 --- a/wadsrc/static/mapinfo/hexen.txt +++ b/wadsrc/static/mapinfo/hexen.txt @@ -40,6 +40,15 @@ gameinfo 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 e5c9043ee8..ffb70e6966 100644 --- a/wadsrc/static/mapinfo/strife.txt +++ b/wadsrc/static/mapinfo/strife.txt @@ -43,6 +43,15 @@ gameinfo endoom = "ENDSTRF" player5start = 5 pickupcolor = "d6 ba 45" + quitmessages = "$QUITMSG", "$QUITMSG15", "$QUITMSG16", "$QUITMSG17", "$QUITMSG18", "$QUITMSG19", "$QUITMSG20", "$QUITMSG21", "$QUITMSG22" + menufontcolor_title = "UNTRANSLATED" + menufontcolor_label = "UNTRANSLATED" + menufontcolor_value = "GRAY" + menufontcolor_action = "GRAY" + menufontcolor_header = "RED" + menufontcolor_highlight = "GREEN" + menufontcolor_selection = "GOLD" + menubackbutton = "M_BACK_S" } skill baby diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt new file mode 100644 index 0000000000..ee624ed751 --- /dev/null +++ b/wadsrc/static/menudef.txt @@ -0,0 +1,1333 @@ +//------------------------------------------------------------------------------------------- +// +// Note: +// Much of the menu structure defined here is accessed internally by CCMDs +// and menu generation code. If you want to design your own menus make sure +// that they are named identically and that links to all important submenus +// are present. +// +//------------------------------------------------------------------------------------------- + +DEFAULTLISTMENU +{ + Font "BigFont", "Untranslated" + IfGame(Doom, Chex) + { + Selector "M_SKULL1", -32, -5 + Linespacing 16 + Font "BigFont", "Red" + } + IfGame(Strife) + { + Selector "M_CURS1", -28, -5 + Linespacing 19 + } + IfGame(Heretic, Hexen) + { + Selector "M_SLCTR1", -28, -1 + Linespacing 20 + } +} + +//------------------------------------------------------------------------------------------- +// +// The main menu. There's a lot of differences here between the games +// +//------------------------------------------------------------------------------------------- + +LISTMENU "MainMenu" +{ + IfGame(Doom, Chex) + { + StaticPatch 94, 2, "M_DOOM" + Position 97, 72 + IfOption(ReadThis) + { + Position 97, 64 + } + } + IfGame(Strife) + { + StaticPatch 84, 2, "M_STRIFE" + Position 97, 45 + } + IfGame(Heretic) + { + StaticPatch 88, 0, "M_HTIC" + StaticPatch 40, 10, "M_SKL01" + StaticPatch 232, 10, "M_SKL00" + Position 110, 56 + } + IfGame(Hexen) + { + StaticPatch 88, 0, "M_HTIC" + StaticPatch 37, 80, "FBULB0" + StaticPatch 278, 80, "FBULA0" + Position 110, 56 + } + + IfGame(Doom, Strife, Chex) + { + PatchItem "M_NGAME", "n", "PlayerclassMenu" + PatchItem "M_LOADG", "l", "LoadGameMenu" + PatchItem "M_SAVEG", "s", "SaveGameMenu" + PatchItem "M_OPTION","o", "OptionsMenu" + ifOption(ReadThis) + { + PatchItem "M_RDTHIS","r", "ReadThisMenu" + } + PatchItem "M_QUITG", "q", "QuitMenu" + } + + IfGame(Heretic, Hexen) + { + TextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + TextItem "$MNU_OPTIONS", "o", "OptionsMenu" + TextItem "$MNU_GAMEFILES", "g", "GameFilesMenu" + TextItem "$MNU_INFO", "i", "ReadThisMenu" + TextItem "$MNU_QUITGAME", "q", "QuitMenu" + } +} + +//------------------------------------------------------------------------------------------- +// +// Important note about the following template menus: +// Don't even think about replacing them with something that's not an empty menu +// with some static elements only. Proper function is not guaranteed then. +// +//------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------- +// +// The player class menu +// The init code will set the first item to 'autoselect' if it's the only one. +// +//------------------------------------------------------------------------------------------- + +ListMenu "PlayerclassMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$NETGAME" + } + IfGame(Chex) + { + NetgameMessage "$CNETGAME" + } + + IfGame(Doom, Strife, Chex) + { + StaticTextCentered 160, 15, "$MNU_CHOOSECLASS" + Position 48, 63 + PlayerDisplay 220, 63, "20 00 00", "80 00 40" + MouseWindow 0, 220 + } + IfGame(Heretic) + { + StaticTextCentered 160, 15, "$MNU_CHOOSECLASS" + Position 80, 50 + PlayerDisplay 220, 50, "20 00 00", "80 00 40" + MouseWindow 0, 220 + } + IfGame(Hexen) + { + StaticText 34, 24, "$MNU_CHOOSECLASS" + Position 66, 58 + PlayerDisplay 174, 8, "00 07 00", "40 53 40" + MouseWindow 0, 174 + } + // The rest of this menu will be set up based on the actual player definitions. +} + +//------------------------------------------------------------------------------------------- +// +// The episode menu +// The init code will set the first item to 'autoselect' if it's the only one. +// +//------------------------------------------------------------------------------------------- + +ListMenu "EpisodeMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$NETGAME" + } + IfGame(Chex) + { + NetgameMessage "$CNETGAME" + } + + IfGame(Doom, Chex) + { + Position 48, 63 + StaticPatch 54, 38, "M_EPISOD" + } + IfGame(Strife) + { + Position 48, 63 + StaticText 54, 38, "$MNU_EPISODE" + } + IfGame(Heretic, Hexen) + { + Position 80, 50 + } + // items will be filled in by MAPINFO +} + +//------------------------------------------------------------------------------------------- +// +// The skill menu +// Most of this will be filled in at runtime +// +//------------------------------------------------------------------------------------------- + +ListMenu "SkillMenu" +{ + + IfGame(Doom, Chex) + { + StaticPatch 96, 14, "M_NEWG" + } + IfGame(Strife) + { + StaticPatch 96, 14, "M_NGAME" + } + IfGame(Doom, Strife, Chex) + { + StaticPatch 54, 38, "M_SKILL" + Position 48, 63 + } + IfGame (Heretic) + { + Position 38, 30 + } + IfGame (Hexen) + { + StaticText 74, 16, "$MNU_CHOOSESKILL" + Position 38, 44 + } +} + +//------------------------------------------------------------------------------------------- +// +// Raven's game files menu +// +//------------------------------------------------------------------------------------------- + +ListMenu "GameFilesMenu" +{ + Position 110, 60 + TextItem "$MNU_LOADGAME", "l", "LoadGameMenu" + TextItem "$MNU_SAVEGAME", "s", "SaveGameMenu" +} + +//------------------------------------------------------------------------------------------- +// +// Base definition for load game menu. Only the configurable part is done here +// +//------------------------------------------------------------------------------------------- + +ListMenu "LoadGameMenu" +{ + IfGame(Doom, Heretic, Hexen, Strife) + { + NetgameMessage "$LOADNET" + } + IfGame(Chex) + { + NetgameMessage "$CLOADNET" + } + IfGame(Doom, Strife, Chex) + { + StaticPatchCentered 160, -20, "M_LOADG" + } + IfGame(Heretic, Hexen) + { + StaticTextCentered 160, -10, "$MNU_LOADGAME" + } + Position 80,54 + Class "LoadMenu" // uses its own implementation +} + +//------------------------------------------------------------------------------------------- +// +// Base definition for save game menu. Only the configurable part is done here +// +//------------------------------------------------------------------------------------------- + +ListMenu "SaveGameMenu" +{ + IfGame(Doom, Strife, Chex) + { + StaticPatchCentered 160, -20, "M_SAVEG" + } + IfGame(Heretic, Hexen) + { + StaticTextCentered 160, -10, "$MNU_SAVEGAME" + } + Position 80,54 + Class "SaveMenu" // uses its own implementation +} + +//------------------------------------------------------------------------------------------- +// +// The option menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "YesNo" +{ + 0, "No" + 1, "Yes" +} + +OptionValue "NoYes" +{ + 0, "Yes" + 1, "No" +} + +OptionValue "OnOff" +{ + 0, "Off" + 1, "On" +} + +OptionValue "OffOn" +{ + 0, "On" + 1, "Off" +} + +OptionMenuSettings +{ + // These can be overridden if a different menu fonts requires it. + Linespacing 8 + LabelOffset 0 + IfGame(Heretic, Hexen) + { + Linespacing 9 + LabelOffset 2 + } +} + +DefaultOptionMenu +{ + Position -15 + IfGame(Heretic, Hexen) + { + Position -13 + } +} + +OptionMenu "OptionsMenu" +{ + Title "OPTIONS" + Submenu "Customize Controls", "CustomizeControls" + Submenu "Mouse options", "MouseOptions" + Submenu "Joystick options", "JoystickOptions" + StaticText " " + Submenu "Player Setup", "PlayerMenu" + Submenu "Gameplay Options", "GameplayOptions" + Submenu "Compatibility Options", "CompatibilityOptions" + Submenu "Automap Options", "AutomapOptions" + Submenu "Sound Options", "SoundOptions" + Submenu "Display Options", "VideoOptions" + Submenu "Set video mode", "VideoModeMenu" + StaticText " " + SafeCommand "Reset to defaults", "reset2defaults" + SafeCommand "Reset to last saved", "reset2saved" + Command "Go to console", "menuconsole" +} + +//------------------------------------------------------------------------------------------- +// +// The player menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "Gender" +{ + 0, "Male" + 1, "Female" + 2, "Other" +} + +OptionValue "Autoaim" +{ + 0, "Never" + 1, "Very low" + 2, "Low" + 3, "Medium" + 4, "High" + 5, "Very high" + 6, "Always" +} + + +ListMenu "PlayerMenu" +{ + StaticTextCentered 160, 6, "$MNU_PLAYERSETUP" + Font "SmallFont" + Linespacing 14 + Position 48, 36 + + IfGame (Doom, Strife, Chex) + { + PlayerNameBox "Name", 0, "Playerbox" + Selector "-", -16, -1 + } + IfGame(Heretic, Hexen) + { + PlayerNameBox "Name", 5, "Playerbox" + Selector "-", -16, 1 + } + IfGame(Doom, Heretic, Strife, Chex) + { + MouseWindow 0, 220 + PlayerDisplay 220, 80, "20 00 00", "80 00 40", 1, "PlayerDisplay" + } + IfGame(Hexen) + { + MouseWindow 0, 220 + PlayerDisplay 220, 80, "00 07 00", "40 53 40", 1, "PlayerDisplay" + } + + ValueText "Team", "Team" + ValueText "Color", "Color" + Linespacing 10 + Slider "Red", "Red", 0, 255, 16 + Slider "Green", "Green", 0, 255, 16 + Linespacing 14 + Slider "Blue", "Blue", 0, 255, 16 + ValueText "Class", "Class" + ValueText "Skin", "Skin" + ValueText "Gender", "Gender", "Gender" + ValueText "Autoaim", "Autoaim", "Autoaim" + ValueText "Switch on pickup", "Switch", "OffOn" + ValueText "Always Run", "AlwaysRun", "OnOff" + Class "PlayerMenu" +} + +//------------------------------------------------------------------------------------------- +// +// Controls Menu +// +//------------------------------------------------------------------------------------------- + +OptionMenu "CustomizeControls" +{ + Title "CUSTOMIZE CONTROLS" + ScrollTop 2 + StaticTextSwitchable "ENTER to change, BACKSPACE to clear", "Press new key for control, ESC to cancel", "ControlMessage" + StaticText "" + StaticText "Controls", 1 + Control "Fire", "+attack" + Control "Secondary Fire", "+altattack" + Control "Use / Open", "+use" + Control "Move forward", "+forward" + Control "Move backward", "+back" + Control "Strafe left", "+moveleft" + Control "Strafe right", "+moveright" + Control "Turn left", "+left" + Control "Turn right", "+right" + Control "Jump", "+jump" + Control "Crouch", "+crouch" + Control "Crouch Toggle", "crouch" + Control "Fly / Swim up", "+moveup" + Control "Fly / Swim down", "+movedown" + Control "Stop flying", "land" + Control "Mouse look", "+mlook" + Control "Keyboard look", "+klook" + Control "Look up", "+lookup" + Control "Look down", "+lookdown" + Control "Center view", "centerview" + Control "Run", "+speed" + Control "Strafe", "+strafe" + Control "Show Scoreboard", "+showscores" + StaticText "" + StaticText "Chat", 1 + Control "Say", "messagemode" + Control "Team say", "messagemode2" + StaticText "" + StaticText "Weapons", 1 + Control "Next weapon", "weapnext" + Control "Previous weapon", "weapprev" + StaticText "" + StaticText "Inventory", 1 + Control "Activate item", "invuse" + Control "Activate all items", "invuseall" + Control "Next item", "invnext" + Control "Previous item", "invprev" + Control "Drop item", "invdrop" + Control "Query item", "invquery" + Control "Drop weapon", "weapdrop" + StaticText "" + StaticText "Other", 1 + Control "Toggle automap", "togglemap" + Control "Chasecam", "chase" + Control "Coop spy", "spynext" + Control "Screenshot", "screenshot" + Control "Open console", "toggleconsole" + StaticText "" + StaticText "Strife Popup Screens", 1 + Control "Mission objectives", "showpop 1" + Control "Keys list", "showpop 2" + Control "Weapons/ammo/stats", "showpop 3" +} + +//------------------------------------------------------------------------------------------- +// +// Mouse Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue "Corners" +{ + -1, "Off" + 0, "Upper left" + 1, "Upper right" + 2, "Lower left" + 3, "Lower right" +} + +OptionValue "MenuMouse" +{ + 0, "No" + 1, "Yes" + 2, "Touchscreen-like" +} + +OptionMenu "MouseOptions" +{ + Title "MOUSE OPTIONS" + Option "Enable mouse", "use_mouse", "YesNo" + IfOption(Windows) // GUI mouse not operable in SDL interface right now. + { + Option "Enable mouse in menus", "m_use_mouse", "MenuMouse", "use_mouse" + Option "Show back button", "m_show_backbutton", "Corners", "use_mouse" + } + StaticText "" + Slider "Overall sensitivity", "mouse_sensitivity", 0.5, 2.5, 0.1 + Option "Prescale mouse movement", "m_noprescale", "NoYes" + Option "Smooth mouse movement", "smooth_mouse", "YesNo" + StaticText "" + Slider "Turning speed", "m_yaw", 0, 2.5, 0.1 + Slider "Mouselook speed", "m_pitch", 0, 2.5, 0.1 + Slider "Forward/Backward speed", "m_forward", 0, 2.5, 0.1 + Slider "Strafing speed", "m_side", 0, 2.5, 0.1 + StaticText "" + Option "Always Mouselook", "freelook", "OnOff" + Option "Invert Mouse", "invertmouse", "OnOff" + Option "Lookspring", "lookspring", "OnOff" + Option "Lookstrafe", "lookstrafe", "OnOff" +} + + +//------------------------------------------------------------------------------------------- +// +// Joystick Menu +// +//------------------------------------------------------------------------------------------- + +OptionMenu "JoystickOptions" +{ + Title "CONTROLLER OPTIONS" + // Will be filled in by joystick code. +} + +OptionValue "JoyAxisMapNames" +{ + -1, "None" + 0, "Turning" + 1, "Looking Up/Down" + 2, "Moving Forward" + 3, "Strafing" + 4, "Moving Up/Down" +} + +OptionValue "Inversion" +{ + 0, "Not Inverted" + 1, "Inverted" +} + +OptionMenu "JoystickConfigMenu" +{ + Title "CONFIGURE CONTROLLER" + Class "JoystickConfigMenu" + // Will be filled in by joystick code. +} + + +//------------------------------------------------------------------------------------------- +// +// Video Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue ColumnMethods +{ + 0.0, "Original" + 1.0, "Optimized" +} + +OptionValue RocketTrailTypes +{ + 0.0, "Off" + 1.0, "Particles" + 2.0, "Sprites" + 3.0, "Sprites & Particles" +} + +OptionValue BloodTypes +{ + 0.0, "Sprites" + 1.0, "Sprites & Particles" + 2.0, "Particles" +} + +OptionValue PuffTypes +{ + 0.0, "Sprites" + 1.0, "Particles" +} + +OptionValue Wipes +{ + 0.0, "None" + 1.0, "Melt" + 2.0, "Burn" + 3.0, "Crossfade" +} + +OptionValue Endoom +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Only modified" +} + +OptionValue Contrast +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Smooth" +} + +OptionValue DisplayTagsTypes +{ + 0.0, "None" + 1.0, "Items" + 2.0, "Weapons" + 3.0, "Both" +} + +OptionValue TextColors +{ + 0.0, "\cabrick" + 1.0, "\cbtan" + 2.0, "\ccgray" + 3.0, "\cdgreen" + 4.0, "\cebrown" + 5.0, "\cfgold" + 6.0, "\cgred" + 7.0, "\chblue" + 8.0, "\ciorange" + 9.0, "\cjwhite" + 10.0, "\ckyellow" + 11.0, "\cldefault" + 12.0, "\cmblack" + 13.0, "\cnlight blue" + 14.0, "\cocream" + 15.0, "\cpolive" + 16.0, "\cqdark green" + 17.0, "\crdark red" + 18.0, "\csdark brown" + 19.0, "\ctpurple" + 20.0, "\cudark gray" + 21.0, "\cvcyan" +} + +OptionValue Crosshairs +{ + // will be filled in from the XHAIRS lump +} + +OptionMenu "VideoOptions" +{ + Title "DISPLAY OPTIONS" + Submenu "Message Options", "MessageOptions" + Submenu "Scoreboard Options", "ScoreboardOptions" + StaticText " " + Slider "Screen size", "screenblocks", 3.0, 12.0, 1.0 + Slider "Brightness", "Gamma", 1.0, 3.0, 0.1 + Option "Vertical Sync", "vid_vsync", "OnOff" + Option "Column render mode", "r_columnmethod", "ColumnMethods" + + StaticText " " + Option "Crosshair", "crosshair", "Crosshairs" + Option "Display nametags", "displaynametags", "DisplayTagsTypes" + Option "Nametag color", "nametagcolor", "TextColors", "displaynametags" + Option "Stretch status bar", "st_scale", "OnOff" + Option "Alternative HUD", "hud_althud", "OnOff" + Option "Screen wipe style", "wipetype", "Wipes" + + IfOption(Windows) + { + Option "Show ENDOOM screen", "showendoom", "Endoom" + //Option "DirectDraw palette hack", "vid_palettehack", "OnOff" + //Option "Use attached surfaces", "vid_attachedsurfaces", "OnOff" + } + + StaticText " " + Option "Stretch short skies", "r_stretchsky", "OnOff" + Option "Use fuzz effect", "r_drawfuzz", "YesNo" + Option "Use fake contrast", "r_fakecontrast", "Contrast" + Option "Rocket Trails", "cl_rockettrails", "RocketTrailTypes" + Option "Blood Type", "cl_bloodtype", "BloodTypes" + Option "Bullet Puff Type", "cl_pufftype", "PuffTypes" + Option "Interpolate monster movement", "nomonsterinterpolation", "NoYes" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Menu +// +//------------------------------------------------------------------------------------------- + +OptionValue MapColorTypes +{ + 0, "Custom" + 1, "Traditional Doom" + 2, "Traditional Strife" + 3, "Traditional Raven" +} + +OptionValue SecretTypes +{ + 0, "Never" + 1, "Only when found" + 2, "Always" +} + +OptionValue RotateTypes +{ + 0, "Off" + 1, "On" + 2, "On for overlay only" +} + +OptionValue OverlayTypes +{ + 0, "Off" + 1, "Overlay+Normal" + 2, "Overlay Only" +} + +OptionMenu AutomapOptions +{ + Title "AUTOMAP OPTIONS" + Option "Map color set", "am_colorset", "MapColorTypes" + Submenu "Set custom colors", "MapColorMenu" + Submenu "Customize map controls", "MapControlsMenu" + StaticText " " + Option "Rotate automap", "am_rotate", "RotateTypes" + Option "Overlay automap", "am_overlay", "OverlayTypes" + Option "Enable textured display", "am_textured", "OnOff" + StaticText " " + Option "Show item counts", "am_showitems", "OnOff" + Option "Show monster counts", "am_showmonsters", "OnOff" + Option "Show secret counts", "am_showsecrets", "OnOff" + Option "Show time elapsed", "am_showtime", "OnOff" + Option "Show total time elapsed", "am_showtotaltime", "OnOff" + Option "Show secrets on map", "am_map_secrets", "SecretTypes" + Option "Draw map background", "am_drawmapback", "OnOff" + Option "Show keys (cheat)", "am_showkeys", "OnOff" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Controls +// +//------------------------------------------------------------------------------------------- + +OptionMenu MapControlsMenu +{ + Title "CUSTOMIZE MAP CONTROLS" + ScrollTop 2 + StaticTextSwitchable "ENTER to change, BACKSPACE to clear", "Press new key for control, ESC to cancel", "ControlMessage" + StaticText "" + StaticText "Map Controls", 1 + MapControl "Pan left", "+am_panleft" + MapControl "Pan right", "+am_panright" + MapControl "Pan up", "+am_panup" + MapControl "Pan down", "+am_pandown" + MapControl "Zoom in", "+am_zoomin" + MapControl "Zoom out", "+am_zoomout" + MapControl "Toggle zoom", "am_gobig" + MapControl "Toggle follow", "am_togglefollow" + MapControl "Toggle grid", "am_togglegrid" + MapControl "Toggle texture","am_toggletexture" + MapControl "Set mark", "am_setmark" + MapControl "Clear mark", "am_clearmarks" +} + +//------------------------------------------------------------------------------------------- +// +// Automap Colors +// +//------------------------------------------------------------------------------------------- + +OptionMenu MapColorMenu +{ + Title "CUSTOMIZE MAP COLORS" + SafeCommand "Restore default custom colors", "am_restorecolors" + StaticText " " + ColorPicker "Background", "am_backcolor" + ColorPicker "You", "am_yourcolor" + ColorPicker "1-sided walls", "am_wallcolor" + ColorPicker "2-sided walls with different floors", "am_fdwallcolor" + ColorPicker "2-sided walls with different ceilings", "am_cdwallcolor" + ColorPicker "Map grid", "am_gridcolor" + ColorPicker "Center point", "am_xhaircolor" + ColorPicker "Not-yet-seen walls", "am_notseencolor" + ColorPicker "Locked doors", "am_lockedcolor" + ColorPicker "Teleporter to the same map", "am_intralevelcolor" + ColorPicker "Teleporter to a different map", "am_interlevelcolor" + ColorPicker "Secret sector", "am_secretsectorcolor" + StaticText " " + StaticText "Cheat Mode", 1 + ColorPicker "Invisible 2-sided walls", "am_tswallcolor" + ColorPicker "Secret walls", "am_secretwallcolor" + ColorPicker "Actors", "am_thingcolor" + ColorPicker "Monsters", "am_thingcolor_monster" + ColorPicker "Friends", "am_thingcolor_friend" + ColorPicker "Items", "am_thingcolor_item" + ColorPicker "Count Items", "am_thingcolor_citem" + StaticText " " + StaticText "Overlay Mode", 1 + ColorPicker "You", "am_ovyourcolor" + ColorPicker "1-sided walls", "am_ovwallcolor" + ColorPicker "2-sided walls", "am_ovotherwallscolor" + ColorPicker "Not-yet-seen walls", "am_ovunseencolor" + ColorPicker "Teleporter", "am_ovtelecolor" + ColorPicker "Secret sector", "am_ovsecretsectorcolor" + StaticText " " + StaticText "Overlay Cheat Mode", 1 + ColorPicker "Actors", "am_ovthingcolor" + ColorPicker "Monsters", "am_ovthingcolor_monster" + ColorPicker "Friends", "am_ovthingcolor_friend" + ColorPicker "Items", "am_ovthingcolor_item" + ColorPicker "Count Items", "am_ovthingcolor_citem" +} + +//------------------------------------------------------------------------------------------- +// +// Color Picker +// +//------------------------------------------------------------------------------------------- + +OptionMenu ColorPickerMenu +{ + Title "SELECT COLOR" + // This menu will be created by the calling code +} + +//------------------------------------------------------------------------------------------- +// +// Messages +// +//------------------------------------------------------------------------------------------- + + +OptionValue ScaleValues +{ + 0, "Off" + 1, "On" + 2, "Double" +} + +OptionValue MessageLevels +{ + 0.0, "Item Pickup" + 1.0, "Obituaries" + 2.0, "Critical Messages" +} + +OptionMenu MessageOptions +{ + Title "MESSAGES" + Option "Show messages", "show_messages", "OnOff" + Option "Show obituaries", "show_obituaries", "OnOff" + Option "Scale text in high res", "con_scaletext", "ScaleValues" + Option "Minimum message level", "msg", "MessageLevels" + Option "Center messages", "con_centernotify", "OnOff" + StaticText " " + StaticText "Message Colors", 1 + StaticText " " + Option "Item Pickup", "msg0color", "TextColors" + Option "Obituaries", "msg1color", "TextColors" + Option "Critical Messages", "msg2color", "TextColors" + Option "Chat Messages", "msg3color", "TextColors" + Option "Team Messages", "msg4color", "TextColors" + Option "Centered Messages", "msgmidcolor", "TextColors" + StaticText " " + Option "Screenshot messages", "screenshot_quiet", "OffOn" + Option "Detailed save messages", "longsavemessages", "OnOff" +} + +//------------------------------------------------------------------------------------------- +// +// Scoreboard +// +//------------------------------------------------------------------------------------------- + +OptionMenu ScoreboardOptions +{ + Title "SCOREBOARD OPTIONS" + StaticText "Cooperative Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_cooperative_enable", "YesNo" + Option "Header Color", "sb_cooperative_headingcolor", "TextColors" + Option "Your Player Color", "sb_cooperative_yourplayercolor", "TextColors" + Option "Other Players' Color", "sb_cooperative_otherplayercolor", "TextColors" + StaticText " " + StaticText " " + StaticText "Deathmatch Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_deathmatch_enable", "YesNo" + Option "Header Color", "sb_deathmatch_headingcolor", "TextColors" + Option "Your Player Color", "sb_deathmatch_yourplayercolor", "TextColors" + Option "Other Players' Color", "sb_deathmatch_otherplayercolor", "TextColors" + StaticText " " + StaticText " " + StaticText "Team Deathmatch Options", 1 + StaticText " " + Option "Enable Scoreboard", "sb_teamdeathmatch_enable", "YesNo" + Option "Header Color", "sb_teamdeathmatch_headingcolor", "TextColors" +} + +/*======================================= + * + * Gameplay Options (dmflags) Menu + * + *=======================================*/ + +OptionValue SmartAim +{ + 0.0, "Off" + 1.0, "On" + 2.0, "Never friends" + 3.0, "Only monsters" +} + +OptionValue FallingDM +{ + 0, "Off" + 1, "Old" + 2, "Hexen" + 3, "Strife" +} + +OptionValue JumpCrouch +{ + 0, "Default" + 1, "Off" + 2, "On" +} + + +OptionMenu GameplayOptions +{ + Title "GAMEPLAY OPTIONS" + Indent 222 + Option "Teamplay", "teamplay", "OnOff" + Slider "Team damage scalar", "teamdamage", 0, 1, 0.05 + StaticText " " + Option "Smart Autoaim", "sv_smartaim", "SmartAim" + StaticText " " + Option "Falling damage", "sv_fallingdamage", "FallingDM" + Option "Drop weapon", "sv_weapondrop", "YesNo" + Option "Double ammo", "sv_doubleammo", "YesNo" + Option "Infinite ammo", "sv_infiniteammo", "YesNo" + Option "Infinite inventory", "sv_infiniteinventory", "YesNo" + Option "No monsters", "sv_nomonsters", "YesNo" + Option "No monsters to exit", "sv_killallmonsters", "YesNo" + Option "Monsters respawn", "sv_monsterrespawn", "YesNo" + Option "No respawn", "sv_norespawn", "YesNo" + Option "Items respawn", "sv_itemrespawn", "YesNo" + Option "Big powerups respawn", "sv_respawnsuper", "YesNo" + Option "Fast monsters", "sv_fastmonsters", "YesNo" + Option "Degeneration", "sv_degeneration", "YesNo" + Option "Allow Autoaim", "sv_noautoaim", "NoYes" + Option "Allow Suicide", "sv_disallowsuicide", "NoYes" + Option "Allow jump", "sv_jump", "JumpCrouch" + Option "Allow crouch", "sv_crouch", "JumpCrouch" + Option "Allow freelook", "sv_nofreelook", "NoYes" + Option "Allow FOV", "sv_nofov", "NoYes" + Option "Allow BFG aiming", "sv_nobfgaim", "NoYes" + Option "Allow automap", "sv_noautomap", "NoYes" + Option "Automap allies", "sv_noautomapallies", "NoYes" + Option "Allow spying", "sv_disallowspying", "NoYes" + Option "Chasecam cheat", "sv_chasecam", "YesNo" + Option "Check ammo for weapon switch", "sv_dontcheckammo", "NoYes" + Option "Killing Romero kills all his spawns", "sv_killbossmonst", "YesNo" + + StaticText " " + StaticText "Deathmatch Settings",1 + Option "Weapons stay", "sv_weaponstay", "YesNo" + Option "Allow powerups", "sv_noitems", "NoYes" + Option "Allow health", "sv_nohealth", "YesNo" + Option "Allow armor", "sv_noarmor", "YesNo" + Option "Spawn farthest", "sv_spawnfarthest", "YesNo" + Option "Same map", "sv_samelevel", "YesNo" + Option "Force respawn", "sv_forcerespawn", "YesNo" + Option "Allow exit", "sv_noexit", "YesNo" + Option "Barrels respawn", "sv_barrelrespawn", "YesNo" + Option "Respawn protection", "sv_respawnprotect", "YesNo" + Option "Lose frag if fragged", "sv_losefrag", "YesNo" + Option "Keep frags gained", "sv_keepfrags", "YesNo" + Option "No team switching", "sv_noteamswitch", "YesNo" + + StaticText " " + StaticText "Cooperative Settings",1 + Option "Spawn multi. weapons", "sv_noweaponspawn", "NoYes" + Option "Lose entire inventory", "sv_cooploseinventory", "YesNo" + Option "Keep keys", "sv_cooplosekeys", "NoYes" + Option "Keep weapons", "sv_cooploseweapons", "NoYes" + Option "Keep armor", "sv_cooplosearmor", "NoYes" + Option "Keep powerups", "sv_cooplosepowerups", "NoYes" + Option "Keep ammo", "sv_cooploseammo", "NoYes" + Option "Lose half ammo", "sv_coophalveammo", "YesNo" + Option "Spawn where died", "sv_samespawnspot", "YesNo" + Class "GameplayMenu" +} + +/*======================================= + * + * Compatibility Options Menu + * + *=======================================*/ + + +OptionValue CompatModes +{ + 0, "Default" + 1, "Doom" + 2, "Doom (strict)" + 3, "Boom" + 6, "Boom (strict)" + 5, "MBF" + 4, "ZDoom 2.0.63" +} + +OptionMenu "CompatibilityOptions" +{ + Title "COMPATIBILITY OPTIONS" + Option "Compatibility mode", "compatmode", "CompatModes", "", 1 + StaticText " " + Option "Find shortest textures like Doom", "compat_SHORTTEX", "YesNo" + Option "Use buggier stair building", "compat_stairs", "YesNo" + Option "Find neighboring light like Doom", "compat_LIGHT", "YesNo" + Option "Limit Pain Elementals' Lost Souls", "compat_LIMITPAIN", "YesNo" + Option "Don't let others hear your pickups", "compat_SILENTPICKUP", "YesNo" + Option "Actors are infinitely tall", "compat_nopassover", "YesNo" + Option "Enable wall running", "compat_WALLRUN", "YesNo" + Option "Spawn item drops on the floor", "compat_NOTOSSDROPS", "YesNo" + Option "All special lines can block ", "compat_USEBLOCKING", "YesNo" + Option "Disable BOOM door light effect", "compat_NODOORLIGHT", "YesNo" + Option "Raven scrollers use original speed", "compat_RAVENSCROLL", "YesNo" + Option "Use original sound target handling", "compat_SOUNDTARGET", "YesNo" + Option "DEH health settings like Doom2.exe", "compat_DEHHEALTH", "YesNo" + Option "Self ref. sectors don't block shots", "compat_TRACE", "YesNo" + Option "Monsters get stuck over dropoffs", "compat_DROPOFF", "YesNo" + Option "Monsters cannot cross dropoffs", "compat_CROSSDROPOFF", "YesNo" + Option "Monsters see invisible players", "compat_INVISIBILITY", "YesNo" + Option "Boom scrollers are additive", "compat_BOOMSCROLL", "YesNo" + Option "Inst. moving floors are not silent", "compat_silentinstantfloors", "YesNo" + Option "Sector sounds use center as source", "compat_SECTORSOUNDS", "YesNo" + Option "Use Doom heights for missile clipping", "compat_MISSILECLIP", "YesNo" + Option "Allow any bossdeath for level special", "compat_ANYBOSSDEATH", "YesNo" + Option "No Minotaur floor flames in water", "compat_MINOTAUR", "YesNo" + Option "Original A_Mushroom speed in DEH mods", "compat_MUSHROOM", "YesNo" + Option "Monster movement is affected by effects", "compat_MBFMONSTERMOVE", "YesNo" + Option "Crushed monsters can be resurrected", "compat_CORPSEGIBS", "YesNo" + Option "Friendly monsters aren't blocked", "compat_NOBLOCKFRIENDS", "YesNo" + Option "Invert sprite sorting", "compat_SPRITESORT", "YesNo" + Option "Use Doom code for hitscan checks", "compat_HITSCAN", "YesNo" + Option "Cripple sound for silent BFG trick", "compat_soundslots", "YesNo" + Option "Draw polyobjects like Hexen", "compat_POLYOBJ", "YesNo" + + Class "CompatibilityMenu" +} + +/*======================================= + * + * Sound Options Menu + * + *=======================================*/ + +OptionValue SampleRates +{ + 0, "Default" + 4000, "4000 Hz" + 8000, "8000 Hz" + 11025, "11025 Hz" + 22050, "22050 Hz" + 32000, "32000 Hz" + 44100, "44100 Hz" + 48000, "48000 Hz" +} + + +OptionValue BufferSizes +{ + 0, "Default" + 64, "64 samples" + 128, "128 samples" + 256, "256 samples" + 512, "512 samples" + 1024, "1024 samples" + 2048, "2048 samples" + 4096, "4096 samples" +} + + +OptionValue BufferCounts +{ + 0, "Default" + 2, "2" + 3, "3" + 4, "4" + 5, "5" + 6, "6" + 7, "7" + 8, "8" + 9, "9" + 10, "10" + 11, "11" + 12, "12" +} + + +OptionString SoundOutputsWindows +{ + "Default", "Default" + "DirectSound", "DirectSound" + "WASAPI", "Vista WASAPI" + "ASIO", "ASIO" + "WaveOut", "WaveOut" + "OpenAL", "OpenAL (very beta)" + "No sound", "No sound" +} + + +OptionString SoundOutputsUnix +{ + "Default", "Default" + "OSS", "OSS" + "ALSA", "ALSA" + "SDL", "SDL" + "ESD", "ESD" + "No sound", "No sound" +} + +OptionString SoundOutputsMac +{ + "Sound Manager", "Sound Manager" + "Core Audio", "Core Audio" + "No sound", "No sound" +} + +OptionString OutputFormats +{ + "PCM-8", "8-bit" + "PCM-16", "16-bit" + "PCM-24", "24-bit" + "PCM-32", "32-bit" + "PCM-Float", "32-bit float" +} + + +OptionString SpeakerModes +{ + "Auto", "Auto" + "Mono", "Mono" + "Stereo", "Stereo" + "Prologic", "Dolby Prologic Decoder" + "Quad", "Quad" + "Surround", "5 speakers" + "5.1", "5.1 speakers" + "7.1", "7.1 speakers" +} + + +OptionString Resamplers +{ + "NoInterp", "No interpolation" + "Linear", "Linear" + "Cubic", "Cubic" + "Spline", "Spline" +} + +OptionValue MidiDevices +{ + // filled in by the sound code +} + +OptionMenu SoundOptions +{ + Title "SOUND OPTIONS" + Slider "Sounds volume", "snd_sfxvolume", 0, 1, 0.05 + Slider "Menu volume", "snd_menuvolume", 0, 1, 0.05 + Slider "Music volume", "snd_musicvolume", 0, 1, 0.05 + Option "MIDI device", "snd_mididevice", "MidiDevices" + StaticText " " + Option "Underwater reverb", "snd_waterreverb", "OnOff" + Slider "Underwater cutoff", "snd_waterlp", 0, 2000, 50 + Option "Randomize pitches", "snd_pitched", "OnOff" + Slider "Sound channels", "snd_channels", 8, 256, 8 + StaticText " " + Command "Restart sound", "snd_reset" + StaticText " " + IfOption(Windows) + { + Option "Output system", "snd_output", "SoundOutputsWindows" + } + IfOption(Unix) + { + Option "Output system", "snd_output", "SoundOutputsUnix" + } + IfOption(Mac) + { + Option "Output system", "snd_output", "SoundOutputsMac" + } + Option "Output format", "snd_output_format", "OutputFormats" + Option "Speaker mode", "snd_speakermode", "SpeakerModes" + Option "Resampler", "snd_resampler", "Resamplers" + Option "HRTF filter", "snd_hrtf", "OnOff" + + StaticText " " + Submenu "Advanced options", "AdvSoundOptions" + Submenu "Module replayer options", "ModReplayerOptions" +} + +/*======================================= + * + * Advanced Sound Options Menu + * + *=======================================*/ + +OptionMenu AdvSoundOptions +{ + Title "ADVANCED SOUND OPTIONS" + Option "Sample rate", "snd_samplerate", "SampleRates" + Option "Buffer size", "snd_buffersize", "BufferSizes" + Option "Buffer count", "snd_buffercount", "BufferCounts" + StaticText " " + StaticText "OPL Synthesis", 1 + Option "Only emulate one OPL chip", "opl_onechip", "OnOff" +} + +/*======================================= + * + * Module Replayer Options Menu + * + *=======================================*/ + +OptionValue ModReplayers +{ + 0.0, "FMOD" + 1.0, "foo_dumb" +} + + +OptionValue ModInterpolations +{ + 0.0, "None" + 1.0, "Linear" + 2.0, "Cubic" +} + + +OptionValue ModVolumeRamps +{ + 0.0, "None" + 1.0, "Logarithmic" + 2.0, "Linear" + 3.0, "XM=lin, else none" + 4.0, "XM=lin, else log" +} + + +OptionMenu ModReplayerOptions +{ + Title "MODULE REPLAYER OPTIONS" + Option "Replayer engine", "mod_dumb", "ModReplayers" + StaticText " " + Option "Sample rate", "mod_samplerate", "SampleRates" + Option "Interpolation", "mod_interp", "ModInterpolations" + Option "Volume ramping", "mod_volramp", "ModVolumeRamps" + StaticText " " + Option "Chip-o-matic", "mod_autochip", "OnOff" + // TODO if the menu system is ever rewritten: Provide a decent + // mechanism to edit the chip-o-matic settings like you can with + // the foo_dumb preferences in foobar2000. +} + +/*======================================= + * + * Video mode menu + * + *=======================================*/ + +OptionValue ForceRatios +{ + 0.0, "Off" + 3.0, "4:3" + 1.0, "16:9" + 2.0, "16:10" + 4.0, "5:4" +} +OptionValue Ratios +{ + 0.0, "4:3" + 1.0, "16:9" + 2.0, "16:10" + 3.0, "All" +} +OptionValue RatiosTFT +{ + 0.0, "4:3" + 4.0, "5:4" + 1.0, "16:9" + 2.0, "16:10" + 3.0, "All" +} + +OptionMenu VideoModeMenu +{ + Title "VIDEO MODE" + + Option "Fullscreen", "fullscreen", "YesNo" + Option "Aspect ratio", "menu_screenratios", "Ratios" + Option "Force aspect ratio", "vid_aspect", "ForceRatios" + Option "Enable 5:4 aspect ratio","vid_tft", "YesNo" + StaticText " " + ScreenResolution "res_0" + ScreenResolution "res_1" + ScreenResolution "res_2" + ScreenResolution "res_3" + ScreenResolution "res_4" + ScreenResolution "res_5" + ScreenResolution "res_6" + ScreenResolution "res_7" + ScreenResolution "res_8" + ScreenResolution "res_9" + StaticTextSwitchable "Press ENTER to set mode", "", "VMEnterText" + StaticText " " + StaticTextSwitchable "T to test mode for 5 seconds", "Please wait 5 seconds...", "VMTestText" + class VideoModeMenu +} + diff --git a/wadsrc/static/textures.txt b/wadsrc/static/textures.txt new file mode 100644 index 0000000000..19f27e84e7 --- /dev/null +++ b/wadsrc/static/textures.txt @@ -0,0 +1,72 @@ + +Graphic optional P_FWALK1, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK2, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK3, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_FWALK4, 112, 136 +{ + Patch "M_FBOX", 0, 0 + Patch "M_FWALK4", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK1, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK2, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK3, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_CWALK4, 112, 136 +{ + Patch "M_CBOX", 0, 0 + Patch "M_CWALK4", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK1, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK1", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK2, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK2", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK3, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK3", 24, 12 { useoffsets } +} + +Graphic optional P_MWALK4, 112, 136 +{ + Patch "M_MBOX", 0, 0 + Patch "M_MWALK4", 24, 12 { useoffsets } +} diff --git a/zdoom.vcproj b/zdoom.vcproj index 4df675804d..94a7a92dd8 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + @@ -688,18 +692,10 @@ RelativePath=".\src\m_joy.cpp" > - - - - @@ -966,7 +962,7 @@ /> + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -1876,14 +1880,6 @@ Outputs="$(IntDir)/$(InputName).obj" /> - - - @@ -2073,14 +2069,6 @@ Outputs="$(IntDir)\$(InputName).obj" /> - - - @@ -2091,6 +2079,14 @@ Outputs="$(IntDir)\$(InputName).obj" /> + + + - - - + + + - - - @@ -5391,6 +5379,14 @@ AdditionalIncludeDirectories="src\win32;$(NoInherit)" /> + + + @@ -5681,7 +5677,7 @@ /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8308a35345d6282b2df7d60bbfba63b36a49a241 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 14 Sep 2010 17:29:54 +0000 Subject: [PATCH 80/84] - deleted empty old files. SVN r2769 (trunk) --- src/CMakeLists.txt | 1 - src/m_menu.h | 0 src/m_options.cpp | 0 3 files changed, 1 deletion(-) delete mode 100644 src/m_menu.h delete mode 100644 src/m_options.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b811e8a594..e34d8ea3dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -657,7 +657,6 @@ add_executable( zdoom WIN32 m_cheat.cpp m_joy.cpp m_misc.cpp - m_options.cpp m_png.cpp m_random.cpp md5.cpp diff --git a/src/m_menu.h b/src/m_menu.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/m_options.cpp b/src/m_options.cpp deleted file mode 100644 index e69de29bb2..0000000000 From ecb34b8302bc5598238ab4c359ce4b75d0c34913 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 14 Sep 2010 19:08:21 +0000 Subject: [PATCH 81/84] - relaxed conditions for shortening the sliders so that they remain at full size at 800x600. SVN r2771 (trunk) --- src/menu/optionmenu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menu/optionmenu.cpp b/src/menu/optionmenu.cpp index 735b674ea6..a64f77431c 100644 --- a/src/menu/optionmenu.cpp +++ b/src/menu/optionmenu.cpp @@ -79,7 +79,7 @@ void M_DrawSlider (int x, int y, double min, double max, double cur,int fracdigi range = max - min; double ccur = clamp(cur, min, max) - min; - if (CleanXfac > CleanXfac_1) + 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"); From 5fcac9b5f1848aa7f90a274e67ac06dab69b3175 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 14 Sep 2010 20:53:12 +0000 Subject: [PATCH 82/84] - fixed: The default GetPixelDoubling function in DFrameBuffer returned 1 instead of 0. - copied a NULL pointer check for screen from GZDoom. SVN r2776 (trunk) --- src/v_video.h | 2 +- src/win32/i_input.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/v_video.h b/src/v_video.h index 14782519cc..8d492c7b4f 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -412,7 +412,7 @@ public: virtual void WipeEndScreen(); virtual bool WipeDo(int ticks); virtual void WipeCleanup(); - virtual int GetPixelDoubling() const { return 1; } + virtual int GetPixelDoubling() const { return 0; } uint32 GetLastFPS() const { return LastCount; } diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index f302d807d0..49915bf29a 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -320,8 +320,12 @@ bool GUIWndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESU { ev.subtype = EV_GUI_MouseMove; } - ev.data1 = LOWORD(lParam) >> screen->GetPixelDoubling(); - ev.data2 = HIWORD(lParam) >> screen->GetPixelDoubling(); + + { + 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; From 087979d6cc42d04ad915893e778f4551d43e660b Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 14 Sep 2010 22:00:02 +0000 Subject: [PATCH 83/84] - fixed: skipping the skill menu locked up the menu sequence for starting a game. SVN r2777 (trunk) --- src/menu/menudef.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index 31fe0fd3b5..033ad599ba 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -1327,7 +1327,11 @@ void M_StartupSkillMenu(FGameStartup *gs) } if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1) { - ld->mAutoselect = MIN(2u, AllEpisodes.Size()-1); + ld->mAutoselect = firstitem + MIN(2u, AllEpisodes.Size()-1); + } + else + { + ld->mAutoselect = -1; } success = true; } From d5f9b0874ed1c20b06accef1355b53dde1e75fc6 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 15 Sep 2010 10:22:38 +0000 Subject: [PATCH 84/84] - fixed: Lines in the option menu that covered the same vertical space as the back button rendered the back button inoperable. SVN r2779 (trunk) --- src/menu/menu.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index cb557512fd..f74ac47949 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -104,12 +104,16 @@ DMenu::DMenu(DMenu *parent) bool DMenu::Responder (event_t *ev) { + bool res = false; if (ev->type == EV_GUI_Event) { if (ev->subtype == EV_GUI_LButtonDown) { - MouseEventBack(MOUSE_Click, ev->data1, ev->data2); - if (MouseEvent(MOUSE_Click, ev->data1, ev->data2)) + 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(); } @@ -120,8 +124,9 @@ bool DMenu::Responder (event_t *ev) BackbuttonTime = BACKBUTTON_TIME; if (mMouseCapture || m_use_mouse == 1) { - MouseEventBack(MOUSE_Move, ev->data1, ev->data2); - return MouseEvent(MOUSE_Move, ev->data1, ev->data2); + 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) @@ -129,8 +134,9 @@ bool DMenu::Responder (event_t *ev) if (mMouseCapture) { ReleaseCapture(); - MouseEventBack(MOUSE_Release, ev->data1, ev->data2); - return MouseEvent(MOUSE_Release, ev->data1, ev->data2); + res = MouseEventBack(MOUSE_Release, ev->data1, ev->data2); + if (res) ev->data2 = -1; + res |= MouseEvent(MOUSE_Release, ev->data1, ev->data2); } } } @@ -211,7 +217,7 @@ bool DMenu::MouseEventBack(int type, int x, int y) if (m_use_mouse == 2) mBackbuttonSelected = false; MenuEvent(MKEY_Back, true); } - return true; + return mBackbuttonSelected; } } return false;