diff --git a/CMakeLists.txt b/CMakeLists.txt index ec96b7030c4622c842bed0a1dc67a070d7160ac1..f2c43d02ceb26c277c94b9440470f90eb9394244 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) project(SRB2 - VERSION 2.1.21 + VERSION 2.1.22 LANGUAGES C) if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR}) diff --git a/appveyor.yml b/appveyor.yml index 061613c4dcb7975848c5b353554514bf59de5539..501aee5b0d4417da5775d179411b40517f42e0a3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.1.21.{branch}-{build} +version: 2.1.22.{branch}-{build} os: MinGW environment: diff --git a/debian/changelog b/debian/changelog index 855c1c1b35a8ff767f25a017ab5bebbb50c2614f..49f4c8b6b2e36f5ab173259768cdbc49ced74910 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,6 +1,6 @@ -srb2 (2.1.21~9) trusty; urgency=high +srb2 (2.1.22~9) trusty; urgency=high - * SRB2 v2.1.21 release + * SRB2 v2.1.22 release -- Marco Zafra <marco.a.zafra@gmail.com> Mon, 27 Nov 2018 16:45:00 -0500 diff --git a/debian/control b/debian/control index ce3b33fbdae1efb642b80591d8920436f18f0a9f..64b96d6b9aac61bd75dfc696b83e65b46f8c1d6c 100644 --- a/debian/control +++ b/debian/control @@ -18,7 +18,7 @@ Homepage: http://www.srb2.org Package: srb2 Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, srb2-data (>= 2.1.15), srb2-data (<= 2.1.21) +Depends: ${shlibs:Depends}, ${misc:Depends}, srb2-data (>= 2.1.15), srb2-data (<= 2.1.22) Description: A cross-platform 3D Sonic fangame Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog fangame built using a modified version of the Doom Legacy @@ -31,7 +31,7 @@ Description: A cross-platform 3D Sonic fangame Package: srb2-dbg Architecture: any # FIXME: should be Depends: ${shlibs:Depends}, ${misc:Depends}, srb2-data (= 2.1.14), srb2 but dh_shlibdeps is being an asshat -Depends: libc6, ${misc:Depends}, srb2-data (>= 2.1.15), srb2-data (<= 2.1.21), srb2 +Depends: libc6, ${misc:Depends}, srb2-data (>= 2.1.15), srb2-data (<= 2.1.22), srb2 Description: A cross-platform 3D Sonic fangame Sonic Robo Blast 2 is a 3D open-source Sonic the Hedgehog fangame built using a modified version of the Doom Legacy diff --git a/src/command.c b/src/command.c index 3183ba70bcb1d80abaead0efabec244781d782f7..ba0095e079b4a6be92a0e9983217d9e6432325fa 100644 --- a/src/command.c +++ b/src/command.c @@ -50,6 +50,7 @@ static void COM_Wait_f(void); static void COM_Help_f(void); static void COM_Toggle_f(void); +static void CV_EnforceExecVersion(void); static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr); static boolean CV_Command(void); static consvar_t *CV_FindVar(const char *name); @@ -64,10 +65,11 @@ CV_PossibleValue_t CV_YesNo[] = {{0, "No"}, {1, "Yes"}, {0, NULL}}; CV_PossibleValue_t CV_Unsigned[] = {{0, "MIN"}, {999999999, "MAX"}, {0, NULL}}; CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}}; -// Filter consvars by MODVERSION +// Filter consvars by EXECVERSION // First implementation is 26 (2.1.21), so earlier configs default at 25 (2.1.20) // Also set CV_HIDEN during runtime, after config is loaded -consvar_t cv_execversion = {"execversion","25",0,CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL}; +static boolean execversion_enabled = false; +consvar_t cv_execversion = {"execversion","25",CV_CALL,CV_Unsigned, CV_EnforceExecVersion, 0, NULL, NULL, 0, 0, NULL}; // for default joyaxis detection static boolean joyaxis_default = false; @@ -1598,10 +1600,21 @@ void CV_InitFilterVar(void) joyaxis_count = joyaxis2_count = 0; } +void CV_ToggleExecVersion(boolean enable) +{ + execversion_enabled = enable; +} + +static void CV_EnforceExecVersion(void) +{ + if (!execversion_enabled) + CV_StealthSetValue(&cv_execversion, EXECVERSION); +} + static boolean CV_FilterJoyAxisVars(consvar_t *v, const char *valstr) { // If ALL axis settings are previous defaults, set them to the new defaults - // MODVERSION < 26 (2.1.21) + // EXECVERSION < 26 (2.1.21) if (joyaxis_default) { @@ -1749,8 +1762,7 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr) if (!(v->flags & CV_SAVE)) return true; - // We go by MODVERSION here - if (cv_execversion.value < 26) // 26 = 2.1.21 + if (GETMAJOREXECVERSION(cv_execversion.value) < 26) // 26 = 2.1.21 { // MOUSE SETTINGS // alwaysfreelook split between first and third person (chasefreelook) diff --git a/src/command.h b/src/command.h index 8dee1174cb2eba85ee9106f652ffd8dae822e482..e6767825c7561c56a0b62995c4ecb66c8399d925 100644 --- a/src/command.h +++ b/src/command.h @@ -130,6 +130,7 @@ extern CV_PossibleValue_t CV_Natural[]; extern consvar_t cv_execversion; void CV_InitFilterVar(void); +void CV_ToggleExecVersion(boolean enable); // register a variable for use at the console void CV_RegisterVar(consvar_t *variable); diff --git a/src/config.h.in b/src/config.h.in index 7452ec80c206d2111b9d9b52c7d308439565ba01..1e6a7f514939491a6428b08c840679a1d14f4d2f 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -27,14 +27,15 @@ #else /* Manually defined asset hashes for non-CMake builds - * Last updated 2015 / 05 / 03 + * Last updated 2015 / 05 / 03 - v2.1.15 - main assets + * Last updated 2018 / 12 / 23 - v2.1.22 - patch.dta */ #define ASSET_HASH_SRB2_SRB "c1b9577687f8a795104aef4600720ea7" #define ASSET_HASH_ZONES_DTA "303838c6c534d9540288360fa49cca60" #define ASSET_HASH_PLAYER_DTA "cfca0f1c73023cbbd8f844f45480f799" #define ASSET_HASH_RINGS_DTA "85901ad4bf94637e5753d2ac2c03ea26" #ifdef USE_PATCH_DTA -#define ASSET_HASH_PATCH_DTA "dbbf8bc6121618ee3be2d5b14650429b" +#define ASSET_HASH_PATCH_DTA "b04fd9624bfd94dc96dcf4f400f7deb4" #endif #endif diff --git a/src/console.c b/src/console.c index 11bf4cb11ab3143f30e7f64fd8bc817f694330cd..5c173e4594cf2d62037779c77af5da27af9716c0 100644 --- a/src/console.c +++ b/src/console.c @@ -33,6 +33,7 @@ #include "i_system.h" #include "d_main.h" #include "m_menu.h" +#include "filesrch.h" #ifdef _WINDOWS #include "win32/win_main.h" @@ -128,10 +129,15 @@ static CV_PossibleValue_t backpic_cons_t[] = {{0, "translucent"}, {1, "picture"} // whether to use console background picture, or translucent mode static consvar_t cons_backpic = {"con_backpic", "translucent", CV_SAVE, backpic_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, {1, "Gray"}, {2, "Brown"}, - {3, "Red"}, {4, "Orange"}, {5, "Yellow"}, - {6, "Green"}, {7, "Blue"}, {8, "Cyan"}, +static CV_PossibleValue_t backcolor_cons_t[] = {{0, "White"}, {1, "Black"}, {2, "Sepia"}, + {3, "Brown"}, {4, "Pink"}, {5, "Raspberry"}, + {6, "Red"}, {7, "Creamsicle"}, {8, "Orange"}, + {9, "Gold"}, {10,"Yellow"}, {11,"Emerald"}, + {12,"Green"}, {13,"Cyan"}, {14,"Steel"}, + {15,"Periwinkle"}, {16,"Blue"}, {17,"Purple"}, + {18,"Lavender"}, {0, NULL}}; + consvar_t cons_backcolor = {"con_backcolor", "Green", CV_CALL|CV_SAVE, backcolor_cons_t, CONS_backcolor_Change, 0, NULL, NULL, 0, 0, NULL}; static void CON_Print(char *msg); @@ -238,29 +244,41 @@ void CON_SetupBackColormap(void) UINT16 i, palsum; UINT8 j, palindex; UINT8 *pal = W_CacheLumpName(GetPalette(), PU_CACHE); + INT32 shift = 6; if (!consolebgmap) consolebgmap = (UINT8 *)Z_Malloc(256, PU_STATIC, NULL); switch (cons_backcolor.value) { - case 0: palindex = 15; break; // White - case 1: palindex = 31; break; // Gray - case 2: palindex = 63; break; // Brown - case 3: palindex = 143; break; // Red - case 4: palindex = 95; break; // Orange - case 5: palindex = 111; break; // Yellow - case 6: palindex = 175; break; // Green - case 7: palindex = 239; break; // Blue - case 8: palindex = 219; break; // Cyan + case 0: palindex = 15; break; // White + case 1: palindex = 31; break; // Gray + case 2: palindex = 47; break; // Sepia + case 3: palindex = 63; break; // Brown + case 4: palindex = 150; shift = 7; break; // Pink + case 5: palindex = 127; shift = 7; break; // Raspberry + case 6: palindex = 143; break; // Red + case 7: palindex = 86; shift = 7; break; // Creamsicle + case 8: palindex = 95; break; // Orange + case 9: palindex = 119; shift = 7; break; // Gold + case 10: palindex = 111; break; // Yellow + case 11: palindex = 191; shift = 7; break; // Emerald + case 12: palindex = 175; break; // Green + case 13: palindex = 219; break; // Cyan + case 14: palindex = 207; shift = 7; break; // Steel + case 15: palindex = 230; shift = 7; break; // Periwinkle + case 16: palindex = 239; break; // Blue + case 17: palindex = 199; shift = 7; break; // Purple + case 18: palindex = 255; shift = 7; break; // Lavender // Default green default: palindex = 175; break; + } // setup background colormap for (i = 0, j = 0; i < 768; i += 3, j++) { - palsum = (pal[i] + pal[i+1] + pal[i+2]) >> 6; + palsum = (pal[i] + pal[i+1] + pal[i+2]) >> shift; consolebgmap[j] = (UINT8)(palindex - palsum); } } @@ -839,7 +857,7 @@ boolean CON_Responder(event_t *ev) // ...why shouldn't it eat the key? if it doesn't, it just means you // can control Sonic from the console, which is silly - return true; //return false; + return true;//return false; } // command completion forward (tab) and backward (shift-tab) @@ -1033,15 +1051,30 @@ boolean CON_Responder(event_t *ev) else if (key == KEY_KPADSLASH) key = '/'; - if (shiftdown) + // capslock + if (key == KEY_CAPSLOCK) // it's a toggle. + { + if (capslock) + capslock = false; + else + capslock = true; + return true; + } + + if (key >= 'a' && key <= 'z') + { + if (capslock ^ shiftdown) + key = shiftxform[key]; + } + else if (shiftdown) key = shiftxform[key]; // enter a char into the command prompt if (key < 32 || key > 127) - return true; // even if key can't be printed, eat it anyway + return true; // add key to cmd line here - if (key >= 'A' && key <= 'Z' && !shiftdown) //this is only really necessary for dedicated servers + if (key >= 'A' && key <= 'Z' && !(shiftdown ^ capslock)) //this is only really necessary for dedicated servers key = key + 'a' - 'A'; if (input_sel != input_cur) @@ -1258,12 +1291,15 @@ void CONS_Alert(alerttype_t level, const char *fmt, ...) switch (level) { case CONS_NOTICE: + // no notice for notices, hehe CONS_Printf("\x83" "%s" "\x80 ", M_GetText("NOTICE:")); break; case CONS_WARNING: + refreshdirmenu |= REFRESHDIR_WARNING; CONS_Printf("\x82" "%s" "\x80 ", M_GetText("WARNING:")); break; case CONS_ERROR: + refreshdirmenu |= REFRESHDIR_ERROR; CONS_Printf("\x85" "%s" "\x80 ", M_GetText("ERROR:")); break; } @@ -1419,8 +1455,8 @@ static void CON_DrawHudlines(void) if (con_hudlines <= 0) return; - if (chat_on) - y = charheight; // leave place for chat input in the first row of text + if (chat_on && OLDCHAT) + y = charheight; // leave place for chat input in the first row of text (only do it if consolechat is on.) else y = 0; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index cd8367af874b0318783017668eb55fec628dd03c..2529b05d0e03b908ce3b3249d48dd4e62776c9f4 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2762,7 +2762,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) msg = KICK_MSG_CON_FAIL; } - CONS_Printf("\x82%s ", player_names[pnum]); + //CONS_Printf("\x82%s ", player_names[pnum]); // If a verified admin banned someone, the server needs to know about it. // If the playernum isn't zero (the server) then the server needs to record the ban. @@ -2779,17 +2779,17 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) switch (msg) { case KICK_MSG_GO_AWAY: - CONS_Printf(M_GetText("has been kicked (Go away)\n")); + HU_AddChatText(va("\x82*%s has been kicked (Go away)", player_names[pnum]), false); kickreason = KR_KICK; break; #ifdef NEWPING case KICK_MSG_PING_HIGH: - CONS_Printf(M_GetText("left the game (Broke ping limit)\n")); + HU_AddChatText(va("\x82*%s left the game (Broke ping limit)", player_names[pnum]), false); kickreason = KR_PINGLIMIT; break; #endif case KICK_MSG_CON_FAIL: - CONS_Printf(M_GetText("left the game (Synch failure)\n")); + HU_AddChatText(va("\x82*%s left the game (Synch Failure)", player_names[pnum]), false); kickreason = KR_SYNCH; if (M_CheckParm("-consisdump")) // Helps debugging some problems @@ -2826,26 +2826,26 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) } break; case KICK_MSG_TIMEOUT: - CONS_Printf(M_GetText("left the game (Connection timeout)\n")); + HU_AddChatText(va("\x82*%s left the game (Connection timeout)", player_names[pnum]), false); kickreason = KR_TIMEOUT; break; case KICK_MSG_PLAYER_QUIT: if (netgame) // not splitscreen/bots - CONS_Printf(M_GetText("left the game\n")); + HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); kickreason = KR_LEAVE; break; case KICK_MSG_BANNED: - CONS_Printf(M_GetText("has been banned (Don't come back)\n")); + HU_AddChatText(va("\x82*%s has been banned (Don't come back)", player_names[pnum]), false); kickreason = KR_BAN; break; case KICK_MSG_CUSTOM_KICK: READSTRINGN(*p, reason, MAX_REASONLENGTH+1); - CONS_Printf(M_GetText("has been kicked (%s)\n"), reason); + HU_AddChatText(va("\x82*%s has been kicked (%s)", player_names[pnum], reason), false); kickreason = KR_KICK; break; case KICK_MSG_CUSTOM_BAN: READSTRINGN(*p, reason, MAX_REASONLENGTH+1); - CONS_Printf(M_GetText("has been banned (%s)\n"), reason); + HU_AddChatText(va("\x82*%s has been banned (%s)", player_names[pnum], reason), false); kickreason = KR_BAN; break; } @@ -3119,9 +3119,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) if (newplayernum+1 > doomcom->numslots) doomcom->numslots = (INT16)(newplayernum+1); - if (netgame) - CONS_Printf(M_GetText("Player %d has joined the game (node %d)\n"), newplayernum+1, node); - // the server is creating my player if (node == mynode) { @@ -3143,11 +3140,17 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) D_SendPlayerConfig(); addedtogame = true; } - else if (server && netgame && cv_showjoinaddress.value) + + if (netgame) { - const char *address; - if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL) - CONS_Printf(M_GetText("Player Address is %s\n"), address); + if (server && cv_showjoinaddress.value) + { + const char *address; + if (I_GetNodeAddress && (address = I_GetNodeAddress(node)) != NULL) + HU_AddChatText(va("\x82*Player %d has joined the game (node %d) (%s)", newplayernum+1, node, address), false); // merge join notification + IP to avoid clogging console/chat. + } + else + HU_AddChatText(va("\x82*Player %d has joined the game (node %d)", newplayernum+1, node), false); // if you don't wanna see the join address. } if (server && multiplayer && motd[0] != '\0') diff --git a/src/d_clisrv.h b/src/d_clisrv.h index dd1aaf4eab0bf9754173a4988c62dde8533f53d7..8443b3fc0e72ca6600daca61244d484e355fe9cf 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -309,6 +309,7 @@ typedef struct } ATTRPACK clientconfig_pak; #define MAXSERVERNAME 32 +#define MAXFILENEEDED 915 // This packet is too large typedef struct { @@ -330,7 +331,7 @@ typedef struct unsigned char mapmd5[16]; UINT8 actnum; UINT8 iszone; - UINT8 fileneeded[915]; // is filled with writexxx (byteptr.h) + UINT8 fileneeded[MAXFILENEEDED]; // is filled with writexxx (byteptr.h) } ATTRPACK serverinfo_pak; typedef struct diff --git a/src/d_main.c b/src/d_main.c index df422232eb1bda8ec0129b30a62c2c7dc190be5b..dd2cfe0e520308efc722ae06315cf60ba6676154 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -74,6 +74,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "m_cond.h" // condition initialization #include "fastcmp.h" #include "keys.h" +#include "filesrch.h" // refreshdirmenu, mainwadstally #ifdef CMAKECONFIG #include "config.h" @@ -178,6 +179,7 @@ void D_PostEvent_end(void) {}; UINT8 shiftdown = 0; // 0x1 left, 0x2 right UINT8 ctrldown = 0; // 0x1 left, 0x2 right UINT8 altdown = 0; // 0x1 left, 0x2 right +boolean capslock = 0; // gee i wonder what this does. // // D_ModifierKeyResponder // Sets global shift/ctrl/alt variables, never actually eats events @@ -582,6 +584,8 @@ void D_SRB2Loop(void) realtics = entertic - oldentertics; oldentertics = entertic; + refreshdirmenu = 0; // not sure where to put this, here as good as any? + #ifdef DEBUGFILE if (!realtics) if (debugload) @@ -846,17 +850,21 @@ static void IdentifyVersion(void) #if !defined (HAVE_SDL) || defined (HAVE_MIXER) { +#define MUSICTEST(str) \ + {\ + const char *musicpath = va(pandf,srb2waddir,str);\ + int ms = W_VerifyNMUSlumps(musicpath); \ + if (ms == 1) \ + D_AddFile(musicpath); \ + else if (ms == 0) \ + I_Error("File "str" has been modified with non-music/sound lumps"); \ + } + #if defined (DC) && 0 - const char *musicfile = "music_dc.dta"; + MUSICTEST("music_dc.dta") #else - const char *musicfile = "music.dta"; + MUSICTEST("music.dta") #endif - const char *musicpath = va(pandf,srb2waddir,musicfile); - int ms = W_VerifyNMUSlumps(musicpath); // Don't forget the music! - if (ms == 1) - D_AddFile(musicpath); - else if (ms == 0) - I_Error("File %s has been modified with non-music lumps",musicfile); } #endif } @@ -1132,25 +1140,36 @@ void D_SRB2Main(void) #endif D_CleanFile(); + mainwads = 0; + #ifndef DEVELOP // md5s last updated 12/14/14 // Check MD5s of autoloaded files - W_VerifyFileMD5(0, ASSET_HASH_SRB2_SRB); // srb2.srb/srb2.wad - W_VerifyFileMD5(1, ASSET_HASH_ZONES_DTA); // zones.dta - W_VerifyFileMD5(2, ASSET_HASH_PLAYER_DTA); // player.dta - W_VerifyFileMD5(3, ASSET_HASH_RINGS_DTA); // rings.dta + W_VerifyFileMD5(mainwads++, ASSET_HASH_SRB2_SRB); // srb2.srb/srb2.wad + W_VerifyFileMD5(mainwads++, ASSET_HASH_ZONES_DTA); // zones.dta + W_VerifyFileMD5(mainwads++, ASSET_HASH_PLAYER_DTA); // player.dta + W_VerifyFileMD5(mainwads++, ASSET_HASH_RINGS_DTA); // rings.dta #ifdef USE_PATCH_DTA - W_VerifyFileMD5(4, ASSET_HASH_PATCH_DTA); // patch.dta + W_VerifyFileMD5(mainwads++, ASSET_HASH_PATCH_DTA); // patch.dta #endif - // don't check music.dta because people like to modify it, and it doesn't matter if they do // ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for. -#endif //ifndef DEVELOP + //mainwads++; // music.dta does not increment mainwads (see <= 2.1.21) - mainwads = 4; // there are 4 wads not to unload +#else + + mainwads++; // srb2.srb/srb2.wad + mainwads++; // zones.dta + mainwads++; // player.dta + mainwads++; // rings.dta #ifdef USE_PATCH_DTA - ++mainwads; // patch.dta adds one more + mainwads++; // patch.dta #endif + //mainwads++; // music.dta does not increment mainwads (see <= 2.1.21) + +#endif //ifndef DEVELOP + + mainwadstally = packetsizetally; cht_Init(); diff --git a/src/d_net.h b/src/d_net.h index 55ea308b38100c638425a1e0ebadaef244a18a2a..61c669dbb93cbcd8cafda1fbc7ca132aac5fe6d9 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -22,6 +22,7 @@ #define MAXNETNODES 32 #define BROADCASTADDR MAXNETNODES #define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer +//#define NETSPLITSCREEN // Kart's splitscreen netgame feature #define STATLENGTH (TICRATE*2) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index fc885601abb4bbf7ca00dbafccca8746132c457f..11b9413a82905cf1e9066a72d4848b44e14d32d8 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -38,6 +38,7 @@ #include "d_main.h" #include "m_random.h" #include "f_finale.h" +#include "filesrch.h" #include "mserv.h" #include "md5.h" #include "z_zone.h" @@ -673,6 +674,14 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_usegamma); // m_menu.c + CV_RegisterVar(&cv_compactscoreboard); + CV_RegisterVar(&cv_chatheight); + CV_RegisterVar(&cv_chatwidth); + CV_RegisterVar(&cv_chattime); + CV_RegisterVar(&cv_chatspamprotection); + CV_RegisterVar(&cv_chatbacktint); + CV_RegisterVar(&cv_consolechat); + CV_RegisterVar(&cv_chatnotifications); CV_RegisterVar(&cv_crosshair); CV_RegisterVar(&cv_crosshair2); CV_RegisterVar(&cv_alwaysfreelook); @@ -698,6 +707,14 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_firenaxis); CV_RegisterVar(&cv_firenaxis2); + // filesrch.c + CV_RegisterVar(&cv_addons_option); + CV_RegisterVar(&cv_addons_folder); + CV_RegisterVar(&cv_addons_md5); + CV_RegisterVar(&cv_addons_showall); + CV_RegisterVar(&cv_addons_search_type); + CV_RegisterVar(&cv_addons_search_case); + // WARNING: the order is important when initialising mouse2 // we need the mouse2port CV_RegisterVar(&cv_mouse2port); @@ -988,8 +1005,8 @@ static void SetPlayerName(INT32 playernum, char *newname) if (strcasecmp(newname, player_names[playernum]) != 0) { if (netgame) - CONS_Printf(M_GetText("%s renamed to %s\n"), - player_names[playernum], newname); + HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false); + strcpy(player_names[playernum], newname); } } @@ -3147,25 +3164,12 @@ static void Command_Addfile(void) break; ++p; // check total packet size and no of files currently loaded + // See W_LoadWadFile in w_wad.c + if ((numwadfiles >= MAX_WADFILES) + || ((packetsizetally + nameonlylength(fn) + 22) > MAXFILENEEDED*sizeof(UINT8))) { - size_t packetsize = 0; - serverinfo_pak *dummycheck = NULL; - - // Shut the compiler up. - (void)dummycheck; - - // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; - - packetsize += nameonlylength(fn) + 22; - - if ((numwadfiles >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) - { - CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn); - return; - } + CONS_Alert(CONS_ERROR, M_GetText("Too many files loaded to add %s\n"), fn); + return; } WRITESTRINGN(buf_p,p,240); @@ -3251,7 +3255,6 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum) boolean kick = false; boolean toomany = false; INT32 i,j; - size_t packetsize = 0; serverinfo_pak *dummycheck = NULL; // Shut the compiler up. @@ -3282,13 +3285,8 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum) } // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; - - packetsize += nameonlylength(filename) + 22; - if ((numwadfiles >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) + || ((packetsizetally + nameonlylength(filename) + 22) > MAXFILENEEDED*sizeof(UINT8))) toomany = true; else ncs = findfile(filename,md5sum,true); @@ -3518,6 +3516,9 @@ static void Command_Playintro_f(void) if (netgame) return; + if (dirmenu) + closefilemenu(true); + F_StartIntro(); } @@ -4075,6 +4076,9 @@ void Command_ExitGame_f(void) cv_debug = 0; emeralds = 0; + if (dirmenu) + closefilemenu(true); + if (!modeattacking) D_StartTitle(); } diff --git a/src/d_netfil.c b/src/d_netfil.c index f73712418ee3ffc70057904ce0d4187489246c9c..deb04fbe14fa5bd64213129a8ce8ed4979317f14 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -104,6 +104,7 @@ INT32 lastfilenum = -1; /** Fills a serverinfo packet with information about wad files loaded. * * \todo Give this function a better name since it is in global scope. + * Used to have size limiting built in - now handed via W_LoadWadFile in w_wad.c * */ UINT8 *PutFileNeeded(void) @@ -112,29 +113,22 @@ UINT8 *PutFileNeeded(void) UINT8 *p = netbuffer->u.serverinfo.fileneeded; char wadfilename[MAX_WADPATH] = ""; UINT8 filestatus; - size_t bytesused = 0; for (i = 0; i < numwadfiles; i++) { - // If it has only music/sound lumps, mark it as unimportant - if (W_VerifyNMUSlumps(wadfiles[i]->filename)) - filestatus = 0; - else - filestatus = 1; // Important + // If it has only music/sound lumps, don't put it in the list + if (!wadfiles[i]->important) + continue; + + filestatus = 1; // Importance - not really used any more, holds 1 by default for backwards compat with MS // Store in the upper four bits if (!cv_downloading.value) filestatus += (2 << 4); // Won't send - else if ((wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024)) - filestatus += (0 << 4); // Won't send - else + else if ((wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)) filestatus += (1 << 4); // Will send if requested - - bytesused += (nameonlylength(wadfilename) + 22); - - // Don't write too far... - if (bytesused > sizeof(netbuffer->u.serverinfo.fileneeded)) - I_Error("Too many wad files added to host a game. (%s, stopped on %s)\n", sizeu1(bytesused), wadfilename); + // else + // filestatus += (0 << 4); -- Won't send, too big WRITEUINT8(p, filestatus); @@ -167,7 +161,6 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr) { fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet filestatus = READUINT8(p); // The first byte is the file status - fileneeded[i].important = (UINT8)(filestatus & 3); fileneeded[i].willsend = (UINT8)(filestatus >> 4); fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size fileneeded[i].file = NULL; // The file isn't open yet @@ -197,7 +190,7 @@ boolean CL_CheckDownloadable(void) UINT8 i,dlstatus = 0; for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important) + if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { if (fileneeded[i].willsend == 1) continue; @@ -218,7 +211,7 @@ boolean CL_CheckDownloadable(void) // not downloadable, put reason in console CONS_Alert(CONS_NOTICE, M_GetText("You need additional files to connect to this server:\n")); for (i = 0; i < fileneedednum; i++) - if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important) + if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN) { CONS_Printf(" * \"%s\" (%dK)", fileneeded[i].filename, fileneeded[i].totalsize >> 10); @@ -271,7 +264,7 @@ boolean CL_SendRequestFile(void) for (i = 0; i < fileneedednum; i++) if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN - && fileneeded[i].important && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) + && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) { I_Error("Attempted to download files that were not sendable"); } @@ -280,8 +273,7 @@ boolean CL_SendRequestFile(void) netbuffer->packettype = PT_REQUESTFILE; p = (char *)netbuffer->u.textcmd; for (i = 0; i < fileneedednum; i++) - if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD) - && fileneeded[i].important) + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)) { totalfreespaceneeded += fileneeded[i].totalsize; nameonly(fileneeded[i].filename); @@ -339,10 +331,6 @@ INT32 CL_CheckFiles(void) INT32 ret = 1; size_t packetsize = 0; size_t filestoget = 0; - serverinfo_pak *dummycheck = NULL; - - // Shut the compiler up. - (void)dummycheck; // if (M_CheckParm("-nofiles")) // return 1; @@ -360,13 +348,7 @@ INT32 CL_CheckFiles(void) CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n"); for (i = 1, j = 1; i < fileneedednum || j < numwadfiles;) { - if (i < fileneedednum && !fileneeded[i].important) - { - // Eh whatever, don't care - ++i; - continue; - } - if (j < numwadfiles && W_VerifyNMUSlumps(wadfiles[j]->filename)) + if (j < numwadfiles && !wadfiles[j]->important) { // Unimportant on our side. still don't care. ++j; @@ -392,8 +374,7 @@ INT32 CL_CheckFiles(void) } // See W_LoadWadFile in w_wad.c - for (i = 0; i < numwadfiles; i++) - packetsize += nameonlylength(wadfiles[i]->filename) + 22; + packetsize = packetsizetally; for (i = 1; i < fileneedednum; i++) { @@ -411,13 +392,13 @@ INT32 CL_CheckFiles(void) break; } } - if (fileneeded[i].status != FS_NOTFOUND || !fileneeded[i].important) + if (fileneeded[i].status != FS_NOTFOUND) continue; packetsize += nameonlylength(fileneeded[i].filename) + 22; if ((numwadfiles+filestoget >= MAX_WADFILES) - || (packetsize > sizeof(dummycheck->fileneeded))) + || (packetsize > MAXFILENEEDED*sizeof(UINT8))) return 3; filestoget++; @@ -449,27 +430,8 @@ void CL_LoadServerFiles(void) fileneeded[i].status = FS_OPEN; } else if (fileneeded[i].status == FS_MD5SUMBAD) - { - // If the file is marked important, don't even bother proceeding. - if (fileneeded[i].important) - I_Error("Wrong version of important file %s", fileneeded[i].filename); - - // If it isn't, no need to worry the user with a console message, - // although it can't hurt to put something in the debug file. - - // ...but wait a second. What if the local version is "important"? - if (!W_VerifyNMUSlumps(fileneeded[i].filename)) - I_Error("File %s should only contain music and sound effects!", - fileneeded[i].filename); - - // Okay, NOW we know it's safe. Whew. - P_AddWadFile(fileneeded[i].filename); - if (fileneeded[i].important) - G_SetGameModified(true); - fileneeded[i].status = FS_OPEN; - DEBFILE(va("File %s found but with different md5sum\n", fileneeded[i].filename)); - } - else if (fileneeded[i].important) + I_Error("Wrong version of file %s", fileneeded[i].filename); + else { const char *s; switch(fileneeded[i].status) @@ -939,10 +901,11 @@ void nameonly(char *s) { ns = &(s[j+1]); len = strlen(ns); - if (false) +#if 0 M_Memcpy(s, ns, len+1); - else +#else memmove(s, ns, len+1); +#endif return; } } diff --git a/src/d_netfil.h b/src/d_netfil.h index a7b692931a3c7d68195150f1e816485ca9665557..3d7c2ed599dafb0b5eb4c772403b42a771d8b236 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -35,7 +35,6 @@ typedef enum typedef struct { - UINT8 important; UINT8 willsend; // Is the server willing to send it? char filename[MAX_WADPATH]; UINT8 md5sum[16]; diff --git a/src/dehacked.c b/src/dehacked.c index 60379df07db95a9a9e0074f0d6128a9be9b13ab6..a726ecbc1fa32b6573b373acc261b75af81ca5ce 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -8338,7 +8338,6 @@ static inline int lib_getenum(lua_State *L) lua_pushinteger(L, token); return 1; } - return 0; } diff --git a/src/doomdef.h b/src/doomdef.h index 796221c91802d4051e1c04a61f50374abc1a0c04..d5ba877c971c9ecf3d8d2304731f09f6e99f97f4 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -150,9 +150,9 @@ extern FILE *logstream; // we use comprevision and compbranch instead. #else #define VERSION 201 // Game version -#define SUBVERSION 21 // more precise version number -#define VERSIONSTRING "v2.1.21" -#define VERSIONSTRINGW L"v2.1.21" +#define SUBVERSION 22 // more precise version number +#define VERSIONSTRING "v2.1.22" +#define VERSIONSTRINGW L"v2.1.22" // Hey! If you change this, add 1 to the MODVERSION below! // Otherwise we can't force updates! #endif @@ -161,6 +161,9 @@ extern FILE *logstream; // Comment or uncomment this as necessary. #define USE_PATCH_DTA +// Use .kart extension addons +//#define USE_KART + // Modification options // If you want to take advantage of the Master Server's ability to force clients to update // to the latest version, fill these out. Otherwise, just comment out UPDATE_ALERT and leave @@ -214,7 +217,21 @@ extern FILE *logstream; // it's only for detection of the version the player is using so the MS can alert them of an update. // Only set it higher, not lower, obviously. // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1". -#define MODVERSION 26 +#define MODVERSION 27 + +// To version config.cfg, MAJOREXECVERSION is set equal to MODVERSION automatically. +// Increment MINOREXECVERSION whenever a config change is needed that does not correspond +// to an increment in MODVERSION. This might never happen in practice. +// If MODVERSION increases, set MINOREXECVERSION to 0. +#define MAJOREXECVERSION MODVERSION +#define MINOREXECVERSION 0 +// (It would have been nice to use VERSION and SUBVERSION but those are zero'd out for DEVELOP builds) + +// Macros +#define GETMAJOREXECVERSION(v) (v & 0xFFFF) +#define GETMINOREXECVERSION(v) (v >> 16) +#define GETEXECVERSION(major,minor) (major + (minor << 16)) +#define EXECVERSION GETEXECVERSION(MAJOREXECVERSION, MINOREXECVERSION) // ========================================================================= @@ -396,6 +413,7 @@ extern INT32 cv_debug; // Modifier key variables, accessible anywhere extern UINT8 shiftdown, ctrldown, altdown; +extern boolean capslock; // if we ever make our alloc stuff... #define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL) @@ -410,6 +428,15 @@ INT32 I_GetKey(void); #define max(x, y) (((x) > (y)) ? (x) : (y)) #endif +// Floating point comparison epsilons from float.h +#ifndef FLT_EPSILON +#define FLT_EPSILON 1.1920928955078125e-7f +#endif + +#ifndef DBL_EPSILON +#define DBL_EPSILON 2.2204460492503131e-16 +#endif + // An assert-type mechanism. #ifdef PARANOIA #define I_Assert(e) ((e) ? (void)0 : I_Error("assert failed: %s, file %s, line %d", #e, __FILE__, __LINE__)) diff --git a/src/f_finale.c b/src/f_finale.c index fa6d6dbadedb4c582753333f0a5b470ea5ad8ea9..64e3712118d23264e3b277591b5ea38fdfb727df 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -997,7 +997,9 @@ static const char *credits[] = { "Gregor \"Oogaland\" Dick", "Louis-Antoine \"LJSonic\" de Moulins", // for fixing 2.1's netcode (de Rochefort doesn't quite fit on the screen sorry lol) "Julio \"Chaos Zero 64\" Guir", + "\"Jimita\"", "\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog + "\"Lat'\"", // SRB2-CHAT, the chat window from Kart "Matthew \"Shuffle\" Marsalko", "Steven \"StroggOnMeth\" McGranahan", "\"Morph\"", // For SRB2Morphed stuff @@ -1006,9 +1008,9 @@ static const char *credits[] = { "Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible "\"Steel Titanium\"", "Ben \"Cue\" Woodford", - // Git contributors with 5+ approved merges, at least a few of substantive quality, may be named - // Everyone else is acknowledged here - "STJr Git Contributors", + // Git contributors with 5+ approved merges of substantive quality, + // or contributors with at least one groundbreaking merge, may be named. + // Everyone else is acknowledged under "Special Thanks > SRB2 Community Contributors". "", "\1Sprite Artists", "Odi \"Iceman404\" Atunzu", @@ -1089,7 +1091,10 @@ static const char *credits[] = { "Pascal \"CodeImp\" vd Heiden", // Doom Builder developer "Randi Heit (<!>)", // For their MSPaint <!> sprite that we nicked "Simon \"sirjuddington\" Judd", // SLADE developer - + // Acknowledged here are the following: + // Minor merge request authors, see guideline above + // Golden - Expanded thin font + "SRB2 Community Contributors", "", "\1Produced By", "Sonic Team Junior", diff --git a/src/filesrch.c b/src/filesrch.c index 2463e71789c29902055eb5f935204f5441d8ff50..0276e1c90c92b00488814768f5503db9fee038f6 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -31,6 +31,8 @@ #include "filesrch.h" #include "d_netfil.h" #include "m_misc.h" +#include "z_zone.h" +#include "m_menu.h" // Addons_option_Onchange #if (defined (_WIN32) && !defined (_WIN32_WCE)) && defined (_MSC_VER) && !defined (_XBOX) @@ -255,6 +257,28 @@ readdir (DIR * dirp) return (struct dirent *) 0; } +/* + * rewinddir + * + * Makes the next readdir start from the beginning. + */ +int +rewinddir (DIR * dirp) +{ + errno = 0; + + /* Check for valid DIR struct. */ + if (!dirp) + { + errno = EFAULT; + return -1; + } + + dirp->dd_stat = 0; + + return 0; +} + /* * closedir * @@ -285,6 +309,41 @@ closedir (DIR * dirp) return rc; } #endif + +static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"}, +#if 1 + {1, "HOME"}, {2, "SRB2"}, +#endif + {3, "CUSTOM"}, {0, NULL}}; + +consvar_t cv_addons_option = {"addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_addons_folder = {"addons_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL}; + +static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}}; +consvar_t cv_addons_md5 = {"addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_addons_showall = {"addons_showall", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_addons_search_case = {"addons_search_case", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; + +static CV_PossibleValue_t addons_search_type_cons_t[] = {{0, "Start"}, {1, "Anywhere"}, {0, NULL}}; +consvar_t cv_addons_search_type = {"addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +char menupath[1024]; +size_t menupathindex[menudepth]; +size_t menudepthleft = menudepth; + +char menusearch[MAXSTRINGLENGTH+1]; + +char **dirmenu, **coredirmenu; // core only local for this file +size_t sizedirmenu, sizecoredirmenu; // ditto +size_t dir_on[menudepth]; +UINT8 refreshdirmenu = 0; +char *refreshdirname = NULL; + +size_t packetsizetally = 0; +size_t mainwadstally = 0; + #if defined (_XBOX) && defined (_MSC_VER) filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) @@ -296,6 +355,25 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want completepath = false; return FS_NOTFOUND; } + +void closefilemenu(boolean validsize) +{ + (void)validsize; + return; +} + +void searchfilemenu(char *tempname) +{ + (void)tempname; + return; +} + +boolean preparefilemenu(boolean samedepth) +{ + (void)samedepth; + return false; +} + #elif defined (_WIN32_WCE) filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) @@ -346,7 +424,27 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want #endif return FS_NOTFOUND; } + +void closefilemenu(boolean validsize) +{ + (void)validsize; + return; +} + +void searchfilemenu(char *tempname) +{ + (void)tempname; + return; +} + +boolean preparefilemenu(boolean samedepth) +{ + (void)samedepth; + return false; +} + #else + filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth) { filestatus_t retval = FS_NOTFOUND; @@ -387,25 +485,29 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want { searchpath[searchpathindex[depthleft]]=0; dent = readdir(dirhandle[depthleft]); - if (dent) - strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); if (!dent) + { closedir(dirhandle[depthleft++]); - else if (dent->d_name[0]=='.' && + continue; + } + + if (dent->d_name[0]=='.' && (dent->d_name[1]=='\0' || (dent->d_name[1]=='.' && dent->d_name[2]=='\0'))) { // we don't want to scan uptree + continue; } - else if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat - { - // was the file (re)moved? can't stat it - } + + // okay, now we actually want searchpath to incorporate d_name + strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); + + if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it else if (S_ISDIR(fsstat.st_mode) && depthleft) { - strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name); searchpathindex[--depthleft] = strlen(searchpath) + 1; dirhandle[depthleft] = opendir(searchpath); if (!dirhandle[depthleft]) @@ -444,6 +546,365 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want free(searchname); free(searchpathindex); free(dirhandle); + return retval; } + +char exttable[NUM_EXT_TABLE][7] = { // maximum extension length (currently 4) plus 3 (null terminator, stop, and length including previous two) + "\5.txt", "\5.cfg", // exec + "\5.wad", +#ifdef USE_KART + "\6.kart", +#endif + "\5.pk3", "\5.soc", "\5.lua"}; // addfile + +char filenamebuf[MAX_WADFILES][MAX_WADPATH]; + + +static boolean filemenucmp(char *haystack, char *needle) +{ + static char localhaystack[128]; + strlcpy(localhaystack, haystack, 128); + if (!cv_addons_search_case.value) + strupr(localhaystack); + if (cv_addons_search_type.value) + return (strstr(localhaystack, needle) != 0); + return (!strncmp(localhaystack, needle, menusearch[0])); +} + +void closefilemenu(boolean validsize) +{ + // search + if (dirmenu) + { + if (dirmenu != coredirmenu) + { + if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS)) + { + Z_Free(dirmenu[0]); + dirmenu[0] = NULL; + } + Z_Free(dirmenu); + } + dirmenu = NULL; + sizedirmenu = 0; + } + + if (coredirmenu) + { + // core + if (validsize) + { + for (; sizecoredirmenu > 0; sizecoredirmenu--) + { + Z_Free(coredirmenu[sizecoredirmenu-1]); + coredirmenu[sizecoredirmenu-1] = NULL; + } + } + else + sizecoredirmenu = 0; + + Z_Free(coredirmenu); + coredirmenu = NULL; + } + + if (refreshdirname) + Z_Free(refreshdirname); + refreshdirname = NULL; +} + +void searchfilemenu(char *tempname) +{ + size_t i, first; + char localmenusearch[MAXSTRINGLENGTH] = ""; + + if (dirmenu) + { + if (dirmenu != coredirmenu) + { + if (dirmenu[0] && ((UINT8)(dirmenu[0][DIR_TYPE]) == EXT_NORESULTS)) + { + Z_Free(dirmenu[0]); + dirmenu[0] = NULL; + } + //Z_Free(dirmenu); -- Z_Realloc later tho... + } + else + dirmenu = NULL; + } + + first = (((UINT8)(coredirmenu[0][DIR_TYPE]) == EXT_UP) ? 1 : 0); // skip UP... + + if (!menusearch[0]) + { + if (dirmenu) + Z_Free(dirmenu); + dirmenu = coredirmenu; + sizedirmenu = sizecoredirmenu; + + if (tempname) + { + for (i = first; i < sizedirmenu; i++) + { + if (!strcmp(dirmenu[i]+DIR_STRING, tempname)) + { + dir_on[menudepthleft] = i; + break; + } + } + + if (i == sizedirmenu) + dir_on[menudepthleft] = first; + + Z_Free(tempname); + } + + return; + } + + strcpy(localmenusearch, menusearch+1); + if (!cv_addons_search_case.value) + strupr(localmenusearch); + + sizedirmenu = 0; + for (i = first; i < sizecoredirmenu; i++) + { + if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + sizedirmenu++; + } + + if (!sizedirmenu) // no results... + { + if ((!(dirmenu = Z_Realloc(dirmenu, sizeof(char *), PU_STATIC, NULL))) + || !(dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS)))) + I_Error("searchfilemenu(): could not create \"No results...\"."); + sizedirmenu = 1; + dir_on[menudepthleft] = 0; + if (tempname) + Z_Free(tempname); + return; + } + + if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL))) + I_Error("searchfilemenu(): could not reallocate dirmenu."); + + sizedirmenu = 0; + for (i = first; i < sizecoredirmenu; i++) + { + if (filemenucmp(coredirmenu[i]+DIR_STRING, localmenusearch)) + { + if (tempname && !strcmp(coredirmenu[i]+DIR_STRING, tempname)) + { + dir_on[menudepthleft] = sizedirmenu; + Z_Free(tempname); + tempname = NULL; + } + dirmenu[sizedirmenu++] = coredirmenu[i]; // pointer reuse + } + } + + if (tempname) + { + dir_on[menudepthleft] = 0; //first; -- can't be first, causes problems + Z_Free(tempname); + } +} + +boolean preparefilemenu(boolean samedepth) +{ + DIR *dirhandle; + struct dirent *dent; + struct stat fsstat; + size_t pos = 0, folderpos = 0, numfolders = 0; + char *tempname = NULL; + + if (samedepth) + { + if (dirmenu && dirmenu[dir_on[menudepthleft]]) + tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL + } + else + menusearch[0] = menusearch[1] = 0; // clear search + + if (!(dirhandle = opendir(menupath))) // get directory + { + closefilemenu(true); + return false; + } + + for (; sizecoredirmenu > 0; sizecoredirmenu--) // clear out existing items + { + Z_Free(coredirmenu[sizecoredirmenu-1]); + coredirmenu[sizecoredirmenu-1] = NULL; + } + + while (true) + { + menupath[menupathindex[menudepthleft]] = 0; + dent = readdir(dirhandle); + + if (!dent) + break; + else if (dent->d_name[0]=='.' && + (dent->d_name[1]=='\0' || + (dent->d_name[1]=='.' && + dent->d_name[2]=='\0'))) + continue; // we don't want to scan uptree + + strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name); + + if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it + else // is a file or directory + { + if (!S_ISDIR(fsstat.st_mode)) // file + { + if (!cv_addons_showall.value) + { + size_t len = strlen(dent->d_name)+1; + UINT8 ext; + for (ext = 0; ext < NUM_EXT_TABLE; ext++) + if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison + if (ext == NUM_EXT_TABLE) continue; // not an addfile-able (or exec-able) file + } + } + else // directory + numfolders++; + + sizecoredirmenu++; + } + } + + if (!sizecoredirmenu) + { + closedir(dirhandle); + closefilemenu(false); + if (tempname) + Z_Free(tempname); + return false; + } + + if (menudepthleft != menudepth-1) // Make room for UP... + { + sizecoredirmenu++; + numfolders++; + folderpos++; + } + + if (dirmenu && dirmenu == coredirmenu) + dirmenu = NULL; + + if (!(coredirmenu = Z_Realloc(coredirmenu, sizecoredirmenu*sizeof(char *), PU_STATIC, NULL))) + { + closedir(dirhandle); // just in case + I_Error("preparefilemenu(): could not reallocate coredirmenu."); + } + + rewinddir(dirhandle); + + while ((pos+folderpos) < sizecoredirmenu) + { + menupath[menupathindex[menudepthleft]] = 0; + dent = readdir(dirhandle); + + if (!dent) + break; + else if (dent->d_name[0]=='.' && + (dent->d_name[1]=='\0' || + (dent->d_name[1]=='.' && + dent->d_name[2]=='\0'))) + continue; // we don't want to scan uptree + + strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name); + + if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat + ; // was the file (re)moved? can't stat it + else // is a file or directory + { + char *temp; + size_t len = strlen(dent->d_name)+1; + UINT8 ext = EXT_FOLDER; + UINT8 folder; + + if (!S_ISDIR(fsstat.st_mode)) // file + { + if (!((numfolders+pos) < sizecoredirmenu)) continue; // crash prevention + for (; ext < NUM_EXT_TABLE; ext++) + if (!strcasecmp(exttable[ext]+1, dent->d_name+len-(exttable[ext][0]))) break; // extension comparison + if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file + ext += EXT_START; // moving to be appropriate position + + if (ext >= EXT_LOADSTART) + { + size_t i; + for (i = 0; i < numwadfiles; i++) + { + if (!filenamebuf[i][0]) + { + strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH); + filenamebuf[i][MAX_WADPATH - 1] = '\0'; + nameonly(filenamebuf[i]); + } + + if (strcmp(dent->d_name, filenamebuf[i])) + continue; + if (cv_addons_md5.value && !checkfilemd5(menupath, wadfiles[i]->md5sum)) + continue; + + ext |= EXT_LOADED; + } + } + else if (ext == EXT_TXT) + { + if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt")) + ext |= EXT_LOADED; + } + + if (!strcmp(dent->d_name, configfile)) + ext |= EXT_LOADED; + + folder = 0; + } + else // directory + len += (folder = 1); + + if (len > 255) + len = 255; + + if (!(temp = Z_Malloc((len+DIR_STRING+folder) * sizeof (char), PU_STATIC, NULL))) + I_Error("preparefilemenu(): could not create file entry."); + temp[DIR_TYPE] = ext; + temp[DIR_LEN] = (UINT8)(len); + strlcpy(temp+DIR_STRING, dent->d_name, len); + if (folder) + { + strcpy(temp+len, PATHSEP); + coredirmenu[folderpos++] = temp; + } + else + coredirmenu[numfolders + pos++] = temp; + } + } + + closedir(dirhandle); + + if ((menudepthleft != menudepth-1) // now for UP... entry + && !(coredirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP)))) + I_Error("preparefilemenu(): could not create \"UP...\"."); + + menupath[menupathindex[menudepthleft]] = 0; + sizecoredirmenu = (numfolders+pos); // just in case things shrink between opening and rewind + + if (!sizecoredirmenu) + { + dir_on[menudepthleft] = 0; + closefilemenu(false); + return false; + } + + searchfilemenu(tempname); + + return true; +} + #endif diff --git a/src/filesrch.h b/src/filesrch.h index 33b148d4b8c0ce668aaf08dd1c3cdcfeec87eb42..4186271b038d23aac0ad9a1f77a0a3b368914ada 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -6,6 +6,9 @@ #include "doomdef.h" #include "d_netfil.h" +#include "m_menu.h" // MAXSTRINGLENGTH + +extern consvar_t cv_addons_option, cv_addons_folder, cv_addons_md5, cv_addons_showall, cv_addons_search_case, cv_addons_search_type; /** \brief The filesearch function @@ -25,4 +28,71 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth); +#define menudepth 20 + +extern char menupath[1024]; +extern size_t menupathindex[menudepth]; +extern size_t menudepthleft; + +extern char menusearch[MAXSTRINGLENGTH+1]; + +extern char **dirmenu; +extern size_t sizedirmenu; +extern size_t dir_on[menudepth]; +extern UINT8 refreshdirmenu; +extern char *refreshdirname; + +extern size_t packetsizetally; +extern size_t mainwadstally; + +typedef enum +{ + EXT_FOLDER = 0, + EXT_UP, + EXT_NORESULTS, + EXT_START, + EXT_TXT = EXT_START, + EXT_CFG, + EXT_LOADSTART, + EXT_WAD = EXT_LOADSTART, +#ifdef USE_KART + EXT_KART, +#endif + EXT_PK3, + EXT_SOC, + EXT_LUA, // allowed even if not HAVE_BLUA so that we can yell on load attempt + NUM_EXT, + NUM_EXT_TABLE = NUM_EXT-EXT_START, + EXT_LOADED = 0x80 + /* + obviously there can only be 0x7F supported extensions in + addons menu because we're cramming this into a char out of + laziness/easy memory allocation (what's the difference?) + and have stolen a bit to show whether it's loaded or not + in practice the size of the data type is probably overkill + toast 02/05/17 + */ +} ext_enum; + +typedef enum +{ + DIR_TYPE = 0, + DIR_LEN, + DIR_STRING +} dirname_enum; + +typedef enum +{ + REFRESHDIR_NORMAL = 1, + REFRESHDIR_ADDFILE = 2, + REFRESHDIR_WARNING = 4, + REFRESHDIR_ERROR = 8, + REFRESHDIR_NOTLOADED = 16, + REFRESHDIR_MAX = 32 +} refreshdir_enum; + +void closefilemenu(boolean validsize); +void searchfilemenu(char *tempname); +boolean preparefilemenu(boolean samedepth); + #endif // __FILESRCH_H__ diff --git a/src/g_game.c b/src/g_game.c index 60a5252cd4721f25d6a70cc71818280de2733ac8..213c3b83eb72575769e91b173dfbc5905219842a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -347,6 +347,37 @@ static CV_PossibleValue_t joyaxis_cons_t[] = {{0, "None"}, #endif #endif +// don't mind me putting these here, I was lazy to figure out where else I could put those without blowing up the compiler. + +// it automatically becomes compact with 20+ players, but if you like it, I guess you can turn that on! +consvar_t cv_compactscoreboard= {"compactscoreboard", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// chat timer thingy +static CV_PossibleValue_t chattime_cons_t[] = {{5, "MIN"}, {999, "MAX"}, {0, NULL}}; +consvar_t cv_chattime = {"chattime", "8", CV_SAVE, chattime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// chatwidth +static CV_PossibleValue_t chatwidth_cons_t[] = {{64, "MIN"}, {150, "MAX"}, {0, NULL}}; +consvar_t cv_chatwidth = {"chatwidth", "128", CV_SAVE, chatwidth_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// chatheight +static CV_PossibleValue_t chatheight_cons_t[] = {{6, "MIN"}, {22, "MAX"}, {0, NULL}}; +consvar_t cv_chatheight= {"chatheight", "8", CV_SAVE, chatheight_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// chat notifications (do you want to hear beeps? I'd understand if you didn't.) +consvar_t cv_chatnotifications= {"chatnotifications", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// chat spam protection (why would you want to disable that???) +consvar_t cv_chatspamprotection= {"chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// minichat text background +consvar_t cv_chatbacktint = {"chatbacktint", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; + +// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.) +static CV_PossibleValue_t consolechat_cons_t[] = {{0, "Window"}, {1, "Console"}, {2, "Window (Hidden)"}, {0, NULL}}; +consvar_t cv_consolechat = {"chatmode", "Window", CV_SAVE, consolechat_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + + consvar_t cv_crosshair = {"crosshair", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_crosshair2 = {"crosshair2", "Cross", CV_SAVE, crosshair_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_invertmouse = {"invertmouse", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -3662,7 +3693,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean unlocktriggers = 0; // clear itemfinder, just in case - if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds + if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds CV_StealthSetValue(&cv_itemfinder, 0); } diff --git a/src/g_game.h b/src/g_game.h index e4d155eb5d67736e1b4dad072aef6ee38a0674a4..5259eacbbf4848ab208e3290b04ffd308fbdaa14 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -54,6 +54,7 @@ extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard di extern INT16 rw_maximums[NUM_WEAPONS]; // used in game menu +extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard; extern consvar_t cv_crosshair, cv_crosshair2; extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_chasefreelook, cv_mousemove; extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_chasefreelook2, cv_mousemove2; diff --git a/src/g_input.c b/src/g_input.c index c0ed736474819725d0d56811e0d2ef931a7e1c4e..79623555909687fc6bf49ec637f59886e49bf3d4 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -98,6 +98,8 @@ void G_MapEventsToControls(event_t *ev) break; case ev_mouse: // buttons are virtual keys + if (menuactive || CON_Ready() || chat_on) + break; mousex = (INT32)(ev->data2*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f)); mousey = (INT32)(ev->data3*((cv_mousesens.value*cv_mousesens.value)/110.0f + 0.1f)); mlooky = (INT32)(ev->data3*((cv_mouseysens.value*cv_mousesens.value)/110.0f + 0.1f)); @@ -105,7 +107,7 @@ void G_MapEventsToControls(event_t *ev) case ev_joystick: // buttons are virtual keys i = ev->data1; - if (i >= JOYAXISSET) + if (i >= JOYAXISSET || menuactive || CON_Ready() || chat_on) break; if (ev->data2 != INT32_MAX) joyxmove[i] = ev->data2; if (ev->data3 != INT32_MAX) joyymove[i] = ev->data3; @@ -113,13 +115,15 @@ void G_MapEventsToControls(event_t *ev) case ev_joystick2: // buttons are virtual keys i = ev->data1; - if (i >= JOYAXISSET) + if (i >= JOYAXISSET || menuactive || CON_Ready() || chat_on) break; if (ev->data2 != INT32_MAX) joy2xmove[i] = ev->data2; if (ev->data3 != INT32_MAX) joy2ymove[i] = ev->data3; break; case ev_mouse2: // buttons are virtual keys + if (menuactive || CON_Ready() || chat_on) + break; mouse2x = (INT32)(ev->data2*((cv_mousesens2.value*cv_mousesens2.value)/110.0f + 0.1f)); mouse2y = (INT32)(ev->data3*((cv_mousesens2.value*cv_mousesens2.value)/110.0f + 0.1f)); mlook2y = (INT32)(ev->data3*((cv_mouseysens2.value*cv_mousesens2.value)/110.0f + 0.1f)); @@ -1204,6 +1208,7 @@ void G_Controldefault(void) gamecontrol[gc_lookup ][0] = KEY_UPARROW; gamecontrol[gc_lookdown ][0] = KEY_DOWNARROW; gamecontrol[gc_centerview ][0] = KEY_END; + gamecontrol[gc_centerview ][1] = KEY_JOY1+9; // Right Stick gamecontrol[gc_talkkey ][0] = 't'; gamecontrol[gc_talkkey ][1] = KEY_HAT1+2; // D-Pad Left gamecontrol[gc_teamkey ][0] = 'y'; @@ -1224,6 +1229,7 @@ void G_Controldefault(void) gamecontrolbis[gc_tossflag ][0] = KEY_2JOY1+0; // A gamecontrolbis[gc_use ][0] = KEY_2JOY1+4; // LB gamecontrolbis[gc_camreset ][0] = KEY_2JOY1+3; // Y + gamecontrolbis[gc_centerview][0] = KEY_2JOY1+9; // Right Stick gamecontrolbis[gc_jump ][0] = KEY_2JOY1+5; // RB //gamecontrolbis[gc_pause ][0] = KEY_2JOY1+6; // Back //gamecontrolbis[gc_systemmenu][0] = KEY_2JOY1+7; // Start @@ -1333,30 +1339,166 @@ void G_SaveKeySetting(FILE *f) } } -void G_CheckDoubleUsage(INT32 keynum) +INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify) { + INT32 result = gc_null; if (cv_controlperkey.value == 1) { INT32 i; for (i = 0; i < num_gamecontrols; i++) { if (gamecontrol[i][0] == keynum) - gamecontrol[i][0] = KEY_NULL; + { + result = i; + if (modify) gamecontrol[i][0] = KEY_NULL; + } if (gamecontrol[i][1] == keynum) - gamecontrol[i][1] = KEY_NULL; + { + result = i; + if (modify) gamecontrol[i][1] = KEY_NULL; + } if (gamecontrolbis[i][0] == keynum) - gamecontrolbis[i][0] = KEY_NULL; + { + result = i; + if (modify) gamecontrolbis[i][0] = KEY_NULL; + } if (gamecontrolbis[i][1] == keynum) - gamecontrolbis[i][1] = KEY_NULL; + { + result = i; + if (modify) gamecontrolbis[i][1] = KEY_NULL; + } + if (result && !modify) + return result; } } + return result; } -static void setcontrol(INT32 (*gc)[2], INT32 na) +static INT32 G_FilterKeyByVersion(INT32 numctrl, INT32 keyidx, INT32 player, INT32 *keynum1, INT32 *keynum2, boolean *nestedoverride) +{ + // Special case: ignore KEY_PAUSE because it's hardcoded + if (keyidx == 0 && *keynum1 == KEY_PAUSE) + { + if (*keynum2 != KEY_PAUSE) + { + *keynum1 = *keynum2; // shift down keynum2 and continue + *keynum2 = 0; + } + else + return -1; // skip setting control + } + else if (keyidx == 1 && *keynum2 == KEY_PAUSE) + return -1; // skip setting control + +#if !defined (DC) && !defined (_PSP) && !defined (GP2X) && !defined (_NDS) && !defined(WMINPUT) && !defined(_WII) + if (GETMAJOREXECVERSION(cv_execversion.value) < 27 && ( // v2.1.22 + numctrl == gc_weaponnext || numctrl == gc_weaponprev || numctrl == gc_tossflag || + numctrl == gc_use || numctrl == gc_camreset || numctrl == gc_jump || + numctrl == gc_pause || numctrl == gc_systemmenu || numctrl == gc_camtoggle || + numctrl == gc_screenshot || numctrl == gc_talkkey || numctrl == gc_scores || + numctrl == gc_centerview + )) + { + INT32 keynum = 0, existingctrl = 0; + INT32 defaultkey; + boolean defaultoverride = false; + + // get the default gamecontrol + if (player == 0 && numctrl == gc_systemmenu) + defaultkey = gamecontrol[numctrl][0]; + else + defaultkey = (player == 1 ? gamecontrolbis[numctrl][0] : gamecontrol[numctrl][1]); + + // Assign joypad button defaults if there is an open slot. + // At this point, gamecontrol/bis should have the default controls + // (unless LOADCONFIG is being run) + // + // If the player runs SETCONTROL in-game, this block should not be reached + // because EXECVERSION is locked onto the latest version. + if (keyidx == 0 && !*keynum1) + { + if (*keynum2) // push keynum2 down; this is an edge case + { + *keynum1 = *keynum2; + *keynum2 = 0; + keynum = *keynum1; + } + else + { + keynum = defaultkey; + defaultoverride = true; + } + } + else if (keyidx == 1 && (!*keynum2 || (!*keynum1 && *keynum2))) // last one is the same edge case as above + { + keynum = defaultkey; + defaultoverride = true; + } + else // default to the specified keynum + keynum = (keyidx == 1 ? *keynum2 : *keynum1); + + // Did our last call override keynum2? + if (*nestedoverride) + { + defaultoverride = true; + *nestedoverride = false; + } + + // Fill keynum2 with the default control + if (keyidx == 0 && !*keynum2) + { + *keynum2 = defaultkey; + // Tell the next call that this is an override + *nestedoverride = true; + + // if keynum2 already matches keynum1, we probably recursed + // so unset it + if (*keynum1 == *keynum2) + { + *keynum2 = 0; + *nestedoverride = false; + } + } + + // check if the key is being used somewhere else before passing it + // pass it through if it's the same numctrl. This is an edge case -- when using + // LOADCONFIG, gamecontrol is not reset with default. + // + // Also, only check if we're actually overriding, to preserve behavior where + // config'd keys overwrite default keys. + if (defaultoverride) + existingctrl = G_CheckDoubleUsage(keynum, false); + + if (keynum && (!existingctrl || existingctrl == numctrl)) + return keynum; + else if (keyidx == 0 && *keynum2) + { + // try it again and push down keynum2 + *keynum1 = *keynum2; + *keynum2 = 0; + return G_FilterKeyByVersion(numctrl, keyidx, player, keynum1, keynum2, nestedoverride); + // recursion *should* be safe because we only assign keynum2 to a joy default + // and then clear it if we find that keynum1 already has the joy default. + } + else + return 0; + } +#endif + + // All's good, so pass the keynum as-is + if (keyidx == 1) + return *keynum2; + else //if (keyidx == 0) + return *keynum1; +} + +static void setcontrol(INT32 (*gc)[2]) { INT32 numctrl; const char *namectrl; - INT32 keynum; + INT32 keynum, keynum1, keynum2; + INT32 player = ((void*)gc == (void*)&gamecontrolbis ? 1 : 0); + boolean nestedoverride = false; namectrl = COM_Argv(1); for (numctrl = 0; numctrl < num_gamecontrols && stricmp(namectrl, gamecontrolname[numctrl]); @@ -1367,31 +1509,38 @@ static void setcontrol(INT32 (*gc)[2], INT32 na) CONS_Printf(M_GetText("Control '%s' unknown\n"), namectrl); return; } - keynum = G_KeyStringtoNum(COM_Argv(2)); + keynum1 = G_KeyStringtoNum(COM_Argv(2)); + keynum2 = G_KeyStringtoNum(COM_Argv(3)); + keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride); - if (keynum == KEY_PAUSE) // fail silently; pause is hardcoded + if (keynum >= 0) { - if (na == 4) + (void)G_CheckDoubleUsage(keynum, true); + + // if keynum was rejected, try it again with keynum2 + if (!keynum && keynum2) { - na--; - keynum = G_KeyStringtoNum(COM_Argv(3)); - if (keynum == KEY_PAUSE) - return; + keynum1 = keynum2; // push down keynum2 + keynum2 = 0; + keynum = G_FilterKeyByVersion(numctrl, 0, player, &keynum1, &keynum2, &nestedoverride); + if (keynum >= 0) + (void)G_CheckDoubleUsage(keynum, true); } - else - return; } - G_CheckDoubleUsage(keynum); - gc[numctrl][0] = keynum; + if (keynum >= 0) + gc[numctrl][0] = keynum; - if (na == 4) + if (keynum2) { - keynum = G_KeyStringtoNum(COM_Argv(3)); - if (keynum != KEY_PAUSE) - gc[numctrl][1] = keynum; - else - gc[numctrl][1] = 0; + keynum = G_FilterKeyByVersion(numctrl, 1, player, &keynum1, &keynum2, &nestedoverride); + if (keynum >= 0) + { + if (keynum != gc[numctrl][0]) + gc[numctrl][1] = keynum; + else + gc[numctrl][1] = 0; + } } else gc[numctrl][1] = 0; @@ -1409,7 +1558,7 @@ void Command_Setcontrol_f(void) return; } - setcontrol(gamecontrol, na); + setcontrol(gamecontrol); } void Command_Setcontrol2_f(void) @@ -1424,5 +1573,5 @@ void Command_Setcontrol2_f(void) return; } - setcontrol(gamecontrolbis, na); + setcontrol(gamecontrolbis); } diff --git a/src/g_input.h b/src/g_input.h index 102809f7ff0997c8d950ee315d5f37066064b939..8f4e9cb097d7c3f46c8f1ba76daf6008fcda2ec0 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -165,6 +165,6 @@ void Command_Setcontrol_f(void); void Command_Setcontrol2_f(void); void G_Controldefault(void); void G_SaveKeySetting(FILE *f); -void G_CheckDoubleUsage(INT32 keynum); +INT32 G_CheckDoubleUsage(INT32 keynum, boolean modify); #endif diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index 40d38d0594b5fb2ee2d5fed00cc6f7e0c85b189b..55bbde45984dd2607c336ddb15a16aad84147830 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -790,6 +790,110 @@ void HWR_drawAMline(const fline_t *fl, INT32 color) HWD.pfnDraw2DLine(&v1, &v2, color_rgba); } +// -------------------+ +// HWR_DrawConsoleFill : draw flat coloured transparent rectangle because that's cool, and hw sucks less than sw for that. +// -------------------+ +void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 options) +{ + FOutVector v[4]; + FSurfaceInfo Surf; + float fx, fy, fw, fh; + + if (w < 0 || h < 0) + return; // consistency w/ software + +// 3--2 +// | /| +// |/ | +// 0--1 + + fx = (float)x; + fy = (float)y; + fw = (float)w; + fh = (float)h; + + if (!(options & V_NOSCALESTART)) + { + float dupx = (float)vid.dupx, dupy = (float)vid.dupy; + + if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) + { + RGBA_t rgbaColour = V_GetColor(color); + FRGBAFloat clearColour; + clearColour.red = (float)rgbaColour.s.red / 255; + clearColour.green = (float)rgbaColour.s.green / 255; + clearColour.blue = (float)rgbaColour.s.blue / 255; + clearColour.alpha = 1; + HWD.pfnClearBuffer(true, false, &clearColour); + return; + } + + fx *= dupx; + fy *= dupy; + fw *= dupx; + fh *= dupy; + + if (fabsf((float)vid.width - ((float)BASEVIDWIDTH * dupx)) > 1.0E-36f) + { + if (options & V_SNAPTORIGHT) + fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)); + else if (!(options & V_SNAPTOLEFT)) + fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2; + } + if (fabsf((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) > 1.0E-36f) + { + // same thing here + if (options & V_SNAPTOBOTTOM) + fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); + else if (!(options & V_SNAPTOTOP)) + fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2; + } + } + + if (fx >= vid.width || fy >= vid.height) + return; + if (fx < 0) + { + fw += fx; + fx = 0; + } + if (fy < 0) + { + fh += fy; + fy = 0; + } + + if (fw <= 0 || fh <= 0) + return; + if (fx + fw > vid.width) + fw = (float)vid.width - fx; + if (fy + fh > vid.height) + fh = (float)vid.height - fy; + + fx = -1 + fx / (vid.width / 2); + fy = 1 - fy / (vid.height / 2); + fw = fw / (vid.width / 2); + fh = fh / (vid.height / 2); + + v[0].x = v[3].x = fx; + v[2].x = v[1].x = fx + fw; + v[0].y = v[1].y = fy; + v[2].y = v[3].y = fy - fh; + + //Hurdler: do we still use this argb color? if not, we should remove it + v[0].argb = v[1].argb = v[2].argb = v[3].argb = 0xff00ff00; //; + v[0].z = v[1].z = v[2].z = v[3].z = 1.0f; + + v[0].sow = v[3].sow = 0.0f; + v[2].sow = v[1].sow = 1.0f; + v[0].tow = v[1].tow = 0.0f; + v[2].tow = v[3].tow = 1.0f; + + Surf.FlatColor.rgba = UINT2RGBA(color); + Surf.FlatColor.s.alpha = 0x80; + + HWD.pfnDrawPolygon(&Surf, v, 4, PF_NoTexture|PF_Modulated|PF_Translucent|PF_NoDepthTest); +} // -----------------+ // HWR_DrawFill : draw flat coloured rectangle, with no texture diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index c72843715d3d09769833f9d5d66faf5495eebaa7..59042cf3b1c4c34f030397a8226385af9b726b9b 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -51,6 +51,7 @@ void HWR_CreatePlanePolygons(INT32 bspnum); void HWR_CreateStaticLightmaps(INT32 bspnum); void HWR_PrepLevelCache(size_t pnumtextures); void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color); +void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 options); // Lat: separate flags from color since color needs to be an uint to work right. void HWR_DrawPic(INT32 x,INT32 y,lumpnum_t lumpnum); void HWR_AddCommands(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index c6f95613fb6c99c6b5c1a0db3598972ad2fbf388..2cecb5e3e8659d2ed3a66466caeef5add38af895 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -73,6 +73,7 @@ patch_t *cred_font[CRED_FONTSIZE]; static player_t *plr; boolean chat_on; // entering a chat message? static char w_chat[HU_MAXMSGLEN]; +static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; boolean hu_showscores; // draw rankings static char hu_tick; @@ -319,6 +320,81 @@ void HU_Start(void) //====================================================================== #ifndef NONET + +// EVERY CHANGE IN THIS SCRIPT IS LOL XD! BY VINCYTM + +static UINT32 chat_nummsg_log = 0; +static UINT32 chat_nummsg_min = 0; +static UINT32 chat_scroll = 0; +static tic_t chat_scrolltime = 0; + +static UINT32 chat_maxscroll = 0; // how far can we scroll? + +//static chatmsg_t chat_mini[CHAT_BUFSIZE]; // Display the last few messages sent. +//static chatmsg_t chat_log[CHAT_BUFSIZE]; // Keep every message sent to us in memory so we can scroll n shit, it's cool. + +static char chat_log[CHAT_BUFSIZE][255]; // hold the last 48 or so messages in that log. +static char chat_mini[8][255]; // display up to 8 messages that will fade away / get overwritten +static tic_t chat_timers[8]; + +static boolean chat_scrollmedown = false; // force instant scroll down on the chat log. Happens when you open it / send a message. + +// remove text from minichat table + +static INT16 addy = 0; // use this to make the messages scroll smoothly when one fades away + +static void HU_removeChatText_Mini(void) +{ + // MPC: Don't create new arrays, just iterate through an existing one + size_t i; + for(i=0;i<chat_nummsg_min-1;i++) { + strcpy(chat_mini[i], chat_mini[i+1]); + chat_timers[i] = chat_timers[i+1]; + } + chat_nummsg_min--; // lost 1 msg. + + // use addy and make shit slide smoothly af. + addy += (vid.width < 640) ? 8 : 6; + +} + +// same but w the log. TODO: optimize this and maybe merge in a single func? im bad at C. +static void HU_removeChatText_Log(void) +{ + // MPC: Don't create new arrays, just iterate through an existing one + size_t i; + for(i=0;i<chat_nummsg_log-1;i++) { + strcpy(chat_log[i], chat_log[i+1]); + } + chat_nummsg_log--; // lost 1 msg. +} + +void HU_AddChatText(const char *text, boolean playsound) +{ + if (playsound && cv_consolechat.value != 2) // Don't play the sound if we're using hidden chat. + S_StartSound(NULL, sfx_radio); + // reguardless of our preferences, put all of this in the chat buffer in case we decide to change from oldchat mid-game. + + if (chat_nummsg_log >= CHAT_BUFSIZE) // too many messages! + HU_removeChatText_Log(); + + strcpy(chat_log[chat_nummsg_log], text); + chat_nummsg_log++; + + if (chat_nummsg_min >= 8) + HU_removeChatText_Mini(); + + strcpy(chat_mini[chat_nummsg_min], text); + chat_timers[chat_nummsg_min] = TICRATE*cv_chattime.value; + chat_nummsg_min++; + + if (OLDCHAT) // if we're using oldchat, print directly in console + CONS_Printf("%s\n", text); + else // if we aren't, still save the message to log.txt + CON_LogMessage(va("%s\n", text)); +} + + /** Runs a say command, sending an ::XD_SAY message. * A say command consists of a signed 8-bit integer for the target, an * unsigned 8-bit flag variable, and then the message itself. @@ -337,6 +413,8 @@ void HU_Start(void) * \sa Command_Say_f, Command_Sayteam_f, Command_Sayto_f, Got_Saycmd * \author Graue <graue@oceanbase.org> */ + + static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) { XBOXSTATIC char buf[254]; @@ -347,14 +425,14 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) numwords = COM_Argc() - usedargs; I_Assert(numwords > 0); - if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) + if (CHAT_MUTE) // TODO: Per Player mute. { - CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n")); + HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false); return; } // Only servers/admins can CSAY. - if(!server && IsPlayerAdmin(consoleplayer)) + if(!server && !(IsPlayerAdmin(consoleplayer))) flags &= ~HU_CSAY; // We handle HU_SERVER_SAY, not the caller. @@ -373,6 +451,57 @@ static void DoSayCommand(SINT8 target, size_t usedargs, UINT8 flags) strlcat(msg, COM_Argv(ix + usedargs), msgspace); } + if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm + { + // what we're gonna do now is check if the node exists + // with that logic, characters 4 and 5 are our numbers: + const char *newmsg; + char *nodenum = (char*) malloc(3); + INT32 spc = 1; // used if nodenum[1] is a space. + + strncpy(nodenum, msg+3, 3); + // check for undesirable characters in our "number" + if (((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9'))) + { + // check if nodenum[1] is a space + if (nodenum[1] == ' ') + spc = 0; + // let it slide + else + { + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false); + free(nodenum); + return; + } + } + // I'm very bad at C, I swear I am, additional checks eww! + if (spc != 0) + { + if (msg[5] != ' ') + { + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false); + free(nodenum); + return; + } + } + + target = atoi((const char*) nodenum); // turn that into a number + free(nodenum); + //CONS_Printf("%d\n", target); + + // check for target player, if it doesn't exist then we can't send the message! + if (target < MAXPLAYERS && playeringame[target]) // player exists + target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work! + else + { + HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same + return; + } + buf[0] = target; + newmsg = msg+5+spc; + strlcpy(msg, newmsg, 252); + } + SendNetXCmd(XD_SAY, buf, strlen(msg) + 1 + msg-buf); } @@ -456,6 +585,7 @@ static void Command_CSay_f(void) DoSayCommand(0, 1, HU_CSAY); } +static tic_t stop_spamming[MAXPLAYERS]; /** Receives a message, processing an ::XD_SAY command. * \sa DoSayCommand @@ -469,6 +599,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) char *msg; boolean action = false; char *ptr; + INT32 spam_eatmsg = 0; CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]); @@ -477,7 +608,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) msg = (char *)*p; SKIPSTRING(*p); - if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !IsPlayerAdmin(playernum)) + if ((cv_mute.value || flags & (HU_CSAY|HU_SERVER_SAY)) && playernum != serverplayer && !(IsPlayerAdmin(playernum))) { CONS_Alert(CONS_WARNING, cv_mute.value ? M_GetText("Illegal say command received from %s while muted\n") : M_GetText("Illegal csay command received from non-admin %s\n"), @@ -515,11 +646,28 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) } } + // before we do anything, let's verify the guy isn't spamming, get this easier on us. + + //if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY)) + if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY)) + { + CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]); + stop_spamming[playernum] = 4; + spam_eatmsg = 1; + } + else + stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you? + + // run the lua hook even if we were supposed to eat the msg, netgame consistency goes first. + #ifdef HAVE_BLUA if (LUAh_PlayerMsg(playernum, target, flags, msg)) return; #endif + if (spam_eatmsg) + return; // don't proceed if we were supposed to eat the message. + // If it's a CSAY, just CECHO and be done with it. if (flags & HU_CSAY) { @@ -559,18 +707,56 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) || target == 0 // To everyone || consoleplayer == target-1) // To you { - const char *cstart = "", *cend = "", *adminchar = "~", *remotechar = "@", *fmt; + const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80"; char *tempchar = NULL; - // In CTF and team match, color the player's name. - if (G_GametypeHasTeams()) + // player is a spectator? + if (players[playernum].spectator) + { + cstart = "\x86"; // grey name + textcolor = "\x86"; + } + else if (target == -1) // say team { - cend = "\x80"; if (players[playernum].ctfteam == 1) // red + { cstart = "\x85"; - else if (players[playernum].ctfteam == 2) // blue + textcolor = "\x85"; + } + else // blue + { cstart = "\x84"; + textcolor = "\x84"; + } } + else + { + const UINT8 color = players[playernum].skincolor; + + cstart = "\x83"; + + if (color <= SKINCOLOR_SILVER) + cstart = "\x80"; // White + else if (color <= SKINCOLOR_BLACK) + cstart = "\x86"; // Grey + else if (color <= SKINCOLOR_BLUE) + cstart = "\x84"; // Blue + else if (color <= SKINCOLOR_PEACH) + cstart = "\x87"; //... Orange??? + else if (color == SKINCOLOR_PINK) + cstart = "\x85"; // Red. + else if (color <= SKINCOLOR_PURPLE) + cstart = "\x81"; // Purple + else if (color <= SKINCOLOR_ROSEWOOD) + cstart = "\x87"; // Orange + else if (color <= SKINCOLOR_DARKRED) + cstart = "\x85"; // Red + else if (color <= SKINCOLOR_OLIVE) + cstart = "\x83"; // green + else if (color <= SKINCOLOR_GOLD) + cstart = "\x82"; // Yellow + } + prefix = cstart; // Give admins and remote admins their symbols. if (playernum == serverplayer) @@ -579,11 +765,11 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL); if (tempchar) { - strcat(tempchar, cstart); if (playernum == serverplayer) strcat(tempchar, adminchar); else strcat(tempchar, remotechar); + strcat(tempchar, cstart); cstart = tempchar; } @@ -592,21 +778,39 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) // name, color end, and the message itself. // '\4' makes the message yellow and beeps; '\3' just beeps. if (action) - fmt = "\4* %s%s%s \x82%s\n"; - else if (target == 0) // To everyone - fmt = "\3<%s%s%s> %s\n"; + fmt2 = "* %s%s%s%s \x82%s%s"; else if (target-1 == consoleplayer) // To you - fmt = "\3*%s%s%s* %s\n"; + { + prefix = "\x82[PM]"; + cstart = "\x82"; + textcolor = "\x82"; + fmt2 = "%s<%s%s>%s\x80 %s%s"; + } else if (target > 0) // By you, to another player { // Use target's name. dispname = player_names[target-1]; - fmt = "\3->*%s%s%s* %s\n"; + prefix = "\x82[TO]"; + cstart = "\x82"; + fmt2 = "%s<%s%s>%s\x80 %s%s"; + } + else if (target == 0) // To everyone + fmt2 = "%s<%s%s%s>\x80 %s%s"; else // To your team - fmt = "\3>>%s%s%s<< (team) %s\n"; + { + if (players[playernum].ctfteam == 1) // red + prefix = "\x85[TEAM]"; + else if (players[playernum].ctfteam == 2) // blue + prefix = "\x84[TEAM]"; + else + prefix = "\x83"; // makes sure this doesn't implode if you sayteam on non-team gamemodes + + fmt2 = "%s<%s%s>\x80%s %s%s"; + } + + HU_AddChatText(va(fmt2, prefix, cstart, dispname, cend, textcolor, msg), cv_chatnotifications.value); // add to chat - CONS_Printf(fmt, cstart, dispname, cend, msg); if (tempchar) Z_Free(tempchar); } @@ -633,19 +837,53 @@ static inline boolean HU_keyInChatString(char *s, char ch) l = strlen(s); if (l < HU_MAXMSGLEN - 1) { - s[l++] = ch; - s[l]=0; + if (c_input >= strlen(s)) // don't do anything complicated + { + s[l++] = ch; + s[l]=0; + } + else + { + + // move everything past c_input for new characters: + size_t m = HU_MAXMSGLEN-1; + while (m>=c_input) + { + if (s[m]) + s[m+1] = (s[m]); + if (m == 0) // prevent overflow + break; + m--; + } + s[c_input] = ch; // and replace this. + } + c_input++; return true; } return false; } else if (ch == KEY_BACKSPACE) { - l = strlen(s); - if (l) - s[--l] = 0; - else + size_t i = c_input; + + if (c_input <= 0) + return false; + + if (!s[i-1]) return false; + + if (i >= strlen(s)-1) + { + s[strlen(s)-1] = 0; + c_input--; + return false; + } + + for (; (i < HU_MAXMSGLEN); i++) + { + s[i-1] = s[i]; + } + c_input--; } else if (ch != KEY_ENTER) return false; // did not eat key @@ -669,28 +907,30 @@ void HU_Ticker(void) hu_showscores = false; } -#define QUEUESIZE 256 - static boolean teamtalk = false; -static char chatchars[QUEUESIZE]; -static INT32 head = 0, tail = 0; +/*static char chatchars[QUEUESIZE]; +static INT32 head = 0, tail = 0;*/ +// WHY DO YOU OVERCOMPLICATE EVERYTHING????????? -// -// HU_dequeueChatChar -// -char HU_dequeueChatChar(void) +// Clear spaces so we don't end up with messages only made out of emptiness +static boolean HU_clearChatSpaces(void) { - char c; + size_t i = 0; // Used to just check our message + char c; // current character we're iterating. + boolean nothingbutspaces = true; - if (head != tail) + for (; i < strlen(w_chat); i++) // iterate through message and eradicate all spaces that don't belong. { - c = chatchars[tail]; - tail = (tail + 1) & (QUEUESIZE-1); - } - else - c = 0; + c = w_chat[i]; + if (!c) + break; // if there's nothing, it's safe to assume our message has ended, so let's not waste any more time here. - return c; + if (c != ' ') // Isn't a space + { + nothingbutspaces = false; + } + } + return nothingbutspaces; } // @@ -701,131 +941,815 @@ static void HU_queueChatChar(char c) if (c == KEY_ENTER) { char buf[2+256]; + char *msg = &buf[2]; + size_t i = 0; size_t ci = 2; + INT32 target = 0; + + if (HU_clearChatSpaces()) // Avoids being able to send empty messages, or something. + return; // If this returns true, that means our message was NOTHING but spaces, so don't send it period. do { - c = HU_dequeueChatChar(); + c = w_chat[-2+ci++]; if (!c || (c >= ' ' && !(c & 0x80))) // copy printable characters and terminating '\0' only. - buf[ci++]=c; + buf[ci-1]=c; } while (c); + for (;(i<HU_MAXMSGLEN);i++) + w_chat[i] = 0; // reset this. + + c_input = 0; + // last minute mute check - if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) + if (CHAT_MUTE) { - CONS_Alert(CONS_NOTICE, M_GetText("The chat is muted. You can't say anything at the moment.\n")); + HU_AddChatText(va("%s>ERROR: The chat is muted. You can't say anything.", "\x85"), false); return; } + if (strlen(msg) > 4 && strnicmp(msg, "/pm", 3) == 0) // used /pm + { + INT32 spc = 1; // used if nodenum[1] is a space. + char *nodenum = (char*) malloc(3); + const char *newmsg; + + // what we're gonna do now is check if the node exists + // with that logic, characters 4 and 5 are our numbers: + + // teamtalk can't send PMs, just don't send it, else everyone would be able to see it, and no one wants to see your sex RP sicko. + if (teamtalk) + { + HU_AddChatText(va("%sCannot send sayto in Say-Team.", "\x85"), false); + return; + } + + strncpy(nodenum, msg+3, 3); + // check for undesirable characters in our "number" + if (((nodenum[0] < '0') || (nodenum[0] > '9')) || ((nodenum[1] < '0') || (nodenum[1] > '9'))) + { + // check if nodenum[1] is a space + if (nodenum[1] == ' ') + spc = 0; + // let it slide + else + { + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false); + free(nodenum); + return; + } + } + // I'm very bad at C, I swear I am, additional checks eww! + if (spc != 0) + { + if (msg[5] != ' ') + { + HU_AddChatText("\x82NOTICE: \x80Invalid command format. Correct format is \'/pm<node> \'.", false); + free(nodenum); + return; + } + } + + target = atoi((const char*) nodenum); // turn that into a number + free(nodenum); + //CONS_Printf("%d\n", target); + + // check for target player, if it doesn't exist then we can't send the message! + if (target < MAXPLAYERS && playeringame[target]) // player exists + target++; // even though playernums are from 0 to 31, target is 1 to 32, so up that by 1 to have it work! + else + { + HU_AddChatText(va("\x82NOTICE: \x80Player %d does not exist.", target), false); // same + return; + } + + // we need to get rid of the /pm<node> + newmsg = msg+5+spc; + strlcpy(msg, newmsg, 255); + } if (ci > 3) // don't send target+flags+empty message. { if (teamtalk) buf[0] = -1; // target else - buf[0] = 0; // target + buf[0] = target; + buf[1] = 0; // flags SendNetXCmd(XD_SAY, buf, 2 + strlen(&buf[2]) + 1); } return; } - - if (((head + 1) & (QUEUESIZE-1)) == tail) - CONS_Printf(M_GetText("[Message unsent]\n")); // message not sent - else - { - if (c == KEY_BACKSPACE) - { - if (tail != head) - head = (head - 1) & (QUEUESIZE-1); - } - else - { - chatchars[head] = c; - head = (head + 1) & (QUEUESIZE-1); - } - } } void HU_clearChatChars(void) { - while (tail != head) - HU_queueChatChar(KEY_BACKSPACE); + size_t i = 0; + for (;i<HU_MAXMSGLEN;i++) + w_chat[i] = 0; // reset this. chat_on = false; + c_input = 0; } +static boolean justscrolleddown; +static boolean justscrolledup; +static INT16 typelines = 1; // number of drawfill lines we need when drawing the chat. it's some weird hack and might be one frame off but I'm lazy to make another loop. +// It's up here since it has to be reset when we open the chat. + + // // Returns true if key eaten // boolean HU_Responder(event_t *ev) { - UINT8 c; + INT32 c=0; if (ev->type != ev_keydown) return false; // only KeyDown events now... + /*// Shoot, to prevent P1 chatting from ruining the game for everyone else, it's either: + // A. completely disallow opening chat entirely in online splitscreen + // or B. iterate through all controls to make sure it's bound to player 1 before eating + // You can see which one I chose. + // (Unless if you're sharing a keyboard, since you probably establish when you start chatting that you have dibs on it...) + // (Ahhh, the good ol days when I was a kid who couldn't afford an extra USB controller...) + + if (ev->data1 >= KEY_MOUSE1) + { + INT32 i; + for (i = 0; i < num_gamecontrols; i++) + { + if (gamecontrol[i][0] == ev->data1 || gamecontrol[i][1] == ev->data1) + break; + } + + if (i == num_gamecontrols) + return false; + }*/ //We don't actually care about that unless we get splitscreen netgames. :V + + c = (INT32)ev->data1; + + // capslock (now handled outside of chat on so that it works everytime......) + if (c && c == KEY_CAPSLOCK) // it's a toggle. + { + if (capslock) + capslock = false; + else + capslock = true; + return true; + } + if (!chat_on) { // enter chat mode if ((ev->data1 == gamecontrol[gc_talkkey][0] || ev->data1 == gamecontrol[gc_talkkey][1]) - && netgame && (!cv_mute.value || server || IsPlayerAdmin(consoleplayer))) + && netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise. { - if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) - return false; chat_on = true; w_chat[0] = 0; teamtalk = false; + chat_scrollmedown = true; + typelines = 1; return true; } if ((ev->data1 == gamecontrol[gc_teamkey][0] || ev->data1 == gamecontrol[gc_teamkey][1]) - && netgame && (!cv_mute.value || server || (IsPlayerAdmin(consoleplayer)))) + && netgame && !OLD_MUTE) { - if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) - return false; chat_on = true; w_chat[0] = 0; - teamtalk = true; + teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams. + chat_scrollmedown = true; + typelines = 1; return true; } } - else // if chat_on - { - // Ignore modifier keys - // Note that we do this here so users can still set - // their chat keys to one of these, if they so desire. - if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT - || ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL - || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT) - return true; - - c = (UINT8)ev->data1; + else // if chat_on + { + + // Ignore modifier keys + // Note that we do this here so users can still set + // their chat keys to one of these, if they so desire. + if (ev->data1 == KEY_LSHIFT || ev->data1 == KEY_RSHIFT + || ev->data1 == KEY_LCTRL || ev->data1 == KEY_RCTRL + || ev->data1 == KEY_LALT || ev->data1 == KEY_RALT) + return true; + + c = (INT32)ev->data1; + + // I know this looks very messy but this works. If it ain't broke, don't fix it! + // shift LETTERS to uppercase if we have capslock or are holding shift + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + { + if (shiftdown ^ capslock) + c = shiftxform[c]; + } + else // if we're holding shift we should still shift non letter symbols + { + if (shiftdown) + c = shiftxform[c]; + } + + // pasting. pasting is cool. chat is a bit limited, though :( + if (((c == 'v' || c == 'V') && ctrldown) && !CHAT_MUTE) + { + const char *paste = I_ClipboardPaste(); + size_t chatlen; + size_t pastelen; + + // create a dummy string real quickly + + if (paste == NULL) + return true; + + chatlen = strlen(w_chat); + pastelen = strlen(paste); + if (chatlen+pastelen > HU_MAXMSGLEN) + return true; // we can't paste this!! + + if (c_input >= strlen(w_chat)) // add it at the end of the string. + { + memcpy(&w_chat[chatlen], paste, pastelen); // copy all of that. + c_input += pastelen; + /*size_t i = 0; + for (;i<pastelen;i++) + { + HU_queueChatChar(paste[i]); // queue it so that it's actually sent. (this chat write thing is REALLY messy.) + }*/ + return true; + } + else // otherwise, we need to shift everything and make space, etc etc + { + size_t i = HU_MAXMSGLEN-1; + while (i >= c_input) + { + if (w_chat[i]) + w_chat[i+pastelen] = w_chat[i]; + if (i == 0) // prevent overflow + break; + i--; + } + memcpy(&w_chat[c_input], paste, pastelen); // copy all of that. + c_input += pastelen; + return true; + } + } + + if (!CHAT_MUTE && HU_keyInChatString(w_chat,c)) + { + HU_queueChatChar(c); + } + if (c == KEY_ENTER) + { + chat_on = false; + c_input = 0; // reset input cursor + chat_scrollmedown = true; // you hit enter, so you might wanna autoscroll to see what you just sent. :) + } + else if (c == KEY_ESCAPE + || ((c == gamecontrol[gc_talkkey][0] || c == gamecontrol[gc_talkkey][1] + || c == gamecontrol[gc_teamkey][0] || c == gamecontrol[gc_teamkey][1]) + && c >= KEY_MOUSE1)) // If it's not a keyboard key, then the chat button is used as a toggle. + { + chat_on = false; + c_input = 0; // reset input cursor + } + else if ((c == KEY_UPARROW || c == KEY_MOUSEWHEELUP) && chat_scroll > 0 && !OLDCHAT) // CHAT SCROLLING YAYS! + { + chat_scroll--; + justscrolledup = true; + chat_scrolltime = 4; + } + else if ((c == KEY_DOWNARROW || c == KEY_MOUSEWHEELDOWN) && chat_scroll < chat_maxscroll && chat_maxscroll > 0 && !OLDCHAT) + { + chat_scroll++; + justscrolleddown = true; + chat_scrolltime = 4; + } + else if (c == KEY_LEFTARROW && c_input != 0 && !OLDCHAT) // i said go back + c_input--; + else if (c == KEY_RIGHTARROW && c_input < strlen(w_chat) && !OLDCHAT) // don't need to check for admin or w/e here since the chat won't ever contain anything if it's muted. + c_input++; + return true; + } + return false; +} + + +//====================================================================== +// HEADS UP DRAWING +//====================================================================== + +// Precompile a wordwrapped string to any given width. +// This is a muuuch better method than V_WORDWRAP. +// again stolen and modified a bit from video.c, don't mind me, will need to rearrange this one day. +// this one is simplified for the chat drawer. +static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) +{ + INT32 c; + size_t chw, i, lastusablespace = 0; + size_t slen; + char *newstring = Z_StrDup(string); + INT32 spacewidth = (vid.width < 640) ? 8 : 4, charwidth = (vid.width < 640) ? 8 : 4; + + slen = strlen(string); + x = 0; + + for (i = 0; i < slen; ++i) + { + c = newstring[i]; + if ((UINT8)c >= 0x80 && (UINT8)c <= 0x89) //color parsing! -Inuyasha 2.16.09 + continue; + + if (c == '\n') + { + x = 0; + lastusablespace = 0; + continue; + } + + if (!(option & V_ALLOWLOWERCASE)) + c = toupper(c); + c -= HU_FONTSTART; + + if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + { + chw = spacewidth; + lastusablespace = i; + } + else + chw = charwidth; + + x += chw; + + if (lastusablespace != 0 && x > w) + { + //CONS_Printf("Wrap at index %d\n", i); + newstring[lastusablespace] = '\n'; + i = lastusablespace+1; + lastusablespace = 0; + x = 0; + } + } + return newstring; +} + + +// 30/7/18: chaty is now the distance at which the lowest point of the chat will be drawn if that makes any sense. + +INT16 chatx = 13, chaty = 169; // let's use this as our coordinates, shh + +// chat stuff by VincyTM LOL XD! + +// HU_DrawMiniChat + +static void HU_drawMiniChat(void) +{ + INT32 x = chatx+2; + INT32 charwidth = 4, charheight = 6; + INT32 boxw = cv_chatwidth.value; + INT32 dx = 0, dy = 0; + size_t i = chat_nummsg_min; + boolean prev_linereturn = false; // a hack to prevent double \n while I have no idea why they happen in the first place. + + INT32 msglines = 0; + // process all messages once without rendering anything or doing anything fancy so that we know how many lines each message has... + INT32 y; + + if (!chat_nummsg_min) + return; // needless to say it's useless to do anything if we don't have anything to draw. + + /*if (splitscreen > 1) + boxw = max(64, boxw/2);*/ + + for (; i>0; i--) + { + const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i-1]); + size_t j = 0; + INT32 linescount = 0; + + while(msg[j]) // iterate through msg + { + if (msg[j] < HU_FONTSTART) // don't draw + { + if (msg[j] == '\n') // get back down. + { + ++j; + if (!prev_linereturn) + { + linescount += 1; + dx = 0; + } + prev_linereturn = true; + continue; + } + else if (msg[j] & 0x80) // stolen from video.c, nice. + { + ++j; + continue; + } + + ++j; + } + else + { + j++; + } + prev_linereturn = false; + dx += charwidth; + if (dx >= boxw) + { + dx = 0; + linescount += 1; + } + } + dy = 0; + dx = 0; + msglines += linescount+1; + } + + y = chaty - charheight*(msglines+1); + + /*if (splitscreen) + { + y -= BASEVIDHEIGHT/2; + if (splitscreen > 1) + y += 16; + }*/ + y -= (G_RingSlingerGametype() ? 16 : 0); + + dx = 0; + dy = 0; + i = 0; + prev_linereturn = false; + + for (; i<=(chat_nummsg_min-1); i++) // iterate through our hot messages + { + INT32 clrflag = 0; + INT32 timer = ((cv_chattime.value*TICRATE)-chat_timers[i]) - cv_chattime.value*TICRATE+9; // see below... + INT32 transflag = (timer >= 0 && timer <= 9) ? (timer*V_10TRANS) : 0; // you can make bad jokes out of this one. + size_t j = 0; + const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_mini[i]); // get the current message, and word wrap it. + UINT8 *colormap = NULL; + + while(msg[j]) // iterate through msg + { + if (msg[j] < HU_FONTSTART) // don't draw + { + if (msg[j] == '\n') // get back down. + { + ++j; + if (!prev_linereturn) + { + dy += charheight; + dx = 0; + } + prev_linereturn = true; + continue; + } + else if (msg[j] & 0x80) // stolen from video.c, nice. + { + clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; + colormap = V_GetStringColormap(clrflag); + ++j; + continue; + } + + ++j; + } + else + { + if (cv_chatbacktint.value) // on request of wolfy + V_DrawFillConsoleMap(x + dx + 2, y+dy, charwidth, charheight, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); + + V_DrawChatCharacter(x + dx + 2, y+dy, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|transflag, !cv_allcaps.value, colormap); + } + + dx += charwidth; + prev_linereturn = false; + if (dx >= boxw) + { + dx = 0; + dy += charheight; + } + } + dy += charheight; + dx = 0; + } + + // decrement addy and make that shit smooth: + addy /= 2; + +} + +// HU_DrawChatLog + +static void HU_drawChatLog(INT32 offset) +{ + INT32 charwidth = 4, charheight = 6; + INT32 boxw = cv_chatwidth.value, boxh = cv_chatheight.value; + INT32 x = chatx+2, y, dx = 0, dy = 0; + UINT32 i = 0; + INT32 chat_topy, chat_bottomy; + boolean atbottom = false; + + // make sure that our scroll position isn't "illegal"; + if (chat_scroll > chat_maxscroll) + chat_scroll = chat_maxscroll; + +#ifdef NETSPLITSCREEN + if (splitscreen) + { + boxh = max(6, boxh/2); + if (splitscreen > 1) + boxw = max(64, boxw/2); + } +#endif + + y = chaty - offset*charheight - (chat_scroll*charheight) - boxh*charheight - 12; + +#ifdef NETSPLITSCREEN + if (splitscreen) + { + y -= BASEVIDHEIGHT/2; + if (splitscreen > 1) + y += 16; + } +#endif + y -= (G_RingSlingerGametype() ? 16 : 0); + + + chat_topy = y + chat_scroll*charheight; + chat_bottomy = chat_topy + boxh*charheight; + + V_DrawFillConsoleMap(chatx, chat_topy, boxw, boxh*charheight +2, 239|V_SNAPTOBOTTOM|V_SNAPTOLEFT); // log box + + for (i=0; i<chat_nummsg_log; i++) // iterate through our chatlog + { + INT32 clrflag = 0; + INT32 j = 0; + const char *msg = CHAT_WordWrap(x+2, boxw-(charwidth*2), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, chat_log[i]); // get the current message, and word wrap it. + UINT8 *colormap = NULL; + while(msg[j]) // iterate through msg + { + if (msg[j] < HU_FONTSTART) // don't draw + { + if (msg[j] == '\n') // get back down. + { + ++j; + dy += charheight; + dx = 0; + continue; + } + else if (msg[j] & 0x80) // stolen from video.c, nice. + { + clrflag = ((msg[j] & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; + colormap = V_GetStringColormap(clrflag); + ++j; + continue; + } + + ++j; + } + else + { + if ((y+dy+2 >= chat_topy) && (y+dy < (chat_bottomy))) + V_DrawChatCharacter(x + dx + 2, y+dy+2, msg[j++] |V_SNAPTOBOTTOM|V_SNAPTOLEFT, !cv_allcaps.value, colormap); + else + j++; // don't forget to increment this or we'll get stuck in the limbo. + } + + dx += charwidth; + if (dx >= boxw-charwidth-2 && i<chat_nummsg_log && msg[j] >= HU_FONTSTART) // end of message shouldn't count, nor should invisible characters!!!! + { + dx = 0; + dy += charheight; + } + } + dy += charheight; + dx = 0; + } + + + if (((chat_scroll >= chat_maxscroll) || (chat_scrollmedown)) && !(justscrolleddown || justscrolledup || chat_scrolltime)) // was already at the bottom of the page before new maxscroll calculation and was NOT scrolling. + { + atbottom = true; // we should scroll + } + chat_scrollmedown = false; + + // getmaxscroll through a lazy hack. We do all these loops, so let's not do more loops that are gonna lag the game more. :P + chat_maxscroll = (dy/charheight); // welcome to C, we don't know what min() and max() are. + if (chat_maxscroll <= (UINT32)cv_chatheight.value) + chat_maxscroll = 0; + else + chat_maxscroll -= cv_chatheight.value; + + // if we're not bound by the time, autoscroll for next frame: + if (atbottom) + chat_scroll = chat_maxscroll; + + // draw arrows to indicate that we can (or not) scroll. + if (chat_scroll > 0) + V_DrawThinString(chatx-8, ((justscrolledup) ? (chat_topy-1) : (chat_topy)), V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1A"); // up arrow + if (chat_scroll < chat_maxscroll) + V_DrawThinString(chatx-8, chat_bottomy-((justscrolleddown) ? 5 : 6), V_SNAPTOBOTTOM | V_SNAPTOLEFT | V_YELLOWMAP, "\x1B"); // down arrow + + justscrolleddown = false; + justscrolledup = false; +} + +// +// HU_DrawChat +// +// Draw chat input +// + +static void HU_DrawChat(void) +{ + INT32 charwidth = 4, charheight = 6; + INT32 boxw = cv_chatwidth.value; + INT32 t = 0, c = 0, y = chaty - (typelines*charheight); + UINT32 i = 0, saylen = strlen(w_chat); // You learn new things everyday! + INT32 cflag = 0; + const char *ntalk = "Say: ", *ttalk = "Team: "; + const char *talk = ntalk; + const char *mute = "Chat has been muted."; + +#ifdef NETSPLITSCREEN + if (splitscreen) + { + y -= BASEVIDHEIGHT/2; + if (splitscreen > 1) + { + y += 16; + boxw = max(64, boxw/2); + } + } +#endif + y -= (G_RingSlingerGametype() ? 16 : 0); + + if (teamtalk) + { + talk = ttalk; +#if 0 + if (players[consoleplayer].ctfteam == 1) + t = 0x500; // Red + else if (players[consoleplayer].ctfteam == 2) + t = 0x400; // Blue +#endif + } + + if (CHAT_MUTE) + { + talk = mute; + typelines = 1; + cflag = V_GRAYMAP; // set text in gray if chat is muted. + } + + V_DrawFillConsoleMap(chatx, y-1, boxw, (typelines*charheight), 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); + + while (talk[i]) + { + if (talk[i] < HU_FONTSTART) + ++i; + else + { + V_DrawChatCharacter(chatx + c + 2, y, talk[i] |V_SNAPTOBOTTOM|V_SNAPTOLEFT|cflag, !cv_allcaps.value, V_GetStringColormap(talk[i]|cflag)); + i++; + } + + c += charwidth; + } + + // if chat is muted, just draw the log and get it over with, no need to draw anything else. + if (CHAT_MUTE) + { + HU_drawChatLog(0); + return; + } + + i = 0; + typelines = 1; + + if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4) + V_DrawChatCharacter(chatx + 2 + c, y+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL); + + while (w_chat[i]) + { + boolean skippedline = false; + if (c_input == (i+1)) + { + INT32 cursorx = (c+charwidth < boxw-charwidth) ? (chatx + 2 + c+charwidth) : (chatx+1); // we may have to go down. + INT32 cursory = (cursorx != chatx+1) ? (y) : (y+charheight); + if (hu_tick < 4) + V_DrawChatCharacter(cursorx, cursory+1, '_' |V_SNAPTOBOTTOM|V_SNAPTOLEFT|t, !cv_allcaps.value, NULL); + + if (cursorx == chatx+1 && saylen == i) // a weirdo hack + { + typelines += 1; + skippedline = true; + } + } + + //Hurdler: isn't it better like that? + if (w_chat[i] < HU_FONTSTART) + ++i; + else + V_DrawChatCharacter(chatx + c + 2, y, w_chat[i++] | V_SNAPTOBOTTOM|V_SNAPTOLEFT | t, !cv_allcaps.value, NULL); + + c += charwidth; + if (c > boxw-(charwidth*2) && !skippedline) + { + c = 0; + y += charheight; + typelines += 1; + } + } + + // handle /pm list. It's messy, horrible and I don't care. + if (strnicmp(w_chat, "/pm", 3) == 0 && vid.width >= 400 && !teamtalk) // 320x200 unsupported kthxbai + { + INT32 count = 0; + INT32 p_dispy = chaty - charheight -1; +#ifdef NETSPLITSCREEN + if (splitscreen) + { + p_dispy -= BASEVIDHEIGHT/2; + if (splitscreen > 1) + p_dispy += 16; + } +#endif + p_dispy -= (G_RingSlingerGametype() ? 16 : 0); + + i = 0; + for(i=0; (i<MAXPLAYERS); i++) + { + + // filter: (code needs optimization pls help I'm bad with C) + if (w_chat[3]) + { + char *nodenum; + UINT32 n; + // right, that's half important: (w_chat[4] may be a space since /pm0 msg is perfectly acceptable!) + if ( ( ((w_chat[3] != 0) && ((w_chat[3] < '0') || (w_chat[3] > '9'))) || ((w_chat[4] != 0) && (((w_chat[4] < '0') || (w_chat[4] > '9'))))) && (w_chat[4] != ' ')) + break; + + + nodenum = (char*) malloc(3); + strncpy(nodenum, w_chat+3, 3); + n = atoi((const char*) nodenum); // turn that into a number + free(nodenum); + // special cases: + + if ((n == 0) && !(w_chat[4] == '0')) + { + if (!(i<10)) + continue; + } + else if ((n == 1) && !(w_chat[3] == '0')) + { + if (!((i == 1) || ((i >= 10) && (i <= 19)))) + continue; + } + else if ((n == 2) && !(w_chat[3] == '0')) + { + if (!((i == 2) || ((i >= 20) && (i <= 29)))) + continue; + } + else if ((n == 3) && !(w_chat[3] == '0')) + { + if (!((i == 3) || ((i >= 30) && (i <= 31)))) + continue; + } + else // general case. + { + if (i != n) + continue; + } + } + + if (playeringame[i]) + { + char name[MAXPLAYERNAME+1]; + strlcpy(name, player_names[i], 7); // shorten name to 7 characters. + V_DrawFillConsoleMap(chatx+ boxw + 2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud. + V_DrawSmallString(chatx+ boxw + 4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, va("\x82%d\x80 - %s", i, name)); + count++; + } + } + if (count == 0) // no results. + { + V_DrawFillConsoleMap(chatx+boxw+2, p_dispy- (6*count), 48, 6, 239 | V_SNAPTOBOTTOM | V_SNAPTOLEFT); // fill it like the chat so the text doesn't become hard to read because of the hud. + V_DrawSmallString(chatx+boxw+4, p_dispy- (6*count), V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_ALLOWLOWERCASE, "NO RESULT."); + } + } - // use console translations - if (shiftdown) - c = shiftxform[c]; - - if (HU_keyInChatString(w_chat,c)) - HU_queueChatChar(c); - if (c == KEY_ENTER) - chat_on = false; - else if (c == KEY_ESCAPE) - chat_on = false; + HU_drawChatLog(typelines-1); // typelines is the # of lines we're typing. If there's more than 1 then the log should scroll up to give us more space. - return true; - } - return false; } -//====================================================================== -// HEADS UP DRAWING -//====================================================================== -// -// HU_DrawChat -// -// Draw chat input -// -static void HU_DrawChat(void) +// For anyone who, for some godforsaken reason, likes oldchat. + +static void HU_DrawChat_Old(void) { INT32 t = 0, c = 0, y = HU_INPUTY; size_t i = 0; @@ -833,7 +1757,6 @@ static void HU_DrawChat(void) const char *talk = ntalk; INT32 charwidth = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor; INT32 charheight = 8 * con_scalefactor; //SHORT(hu_font['A'-HU_FONTSTART]->height) * con_scalefactor; - if (teamtalk) { talk = ttalk; @@ -860,9 +1783,20 @@ static void HU_DrawChat(void) c += charwidth; } + if ((strlen(w_chat) == 0 || c_input == 0) && hu_tick < 4) + V_DrawCharacter(HU_INPUTX+c, y+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value); + i = 0; while (w_chat[i]) { + + if (c_input == (i+1) && hu_tick < 4) + { + INT32 cursorx = (HU_INPUTX+c+charwidth < vid.width) ? (HU_INPUTX + c + charwidth) : (HU_INPUTX); // we may have to go down. + INT32 cursory = (cursorx != HU_INPUTX) ? (y) : (y+charheight); + V_DrawCharacter(cursorx, cursory+2*con_scalefactor, '_' |cv_constextsize.value | V_NOSCALESTART|t, !cv_allcaps.value); + } + //Hurdler: isn't it better like that? if (w_chat[i] < HU_FONTSTART) { @@ -882,9 +1816,6 @@ static void HU_DrawChat(void) y += charheight; } } - - if (hu_tick < 4) - V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value); } @@ -1057,7 +1988,43 @@ void HU_Drawer(void) { // draw chat string plus cursor if (chat_on) - HU_DrawChat(); + { + // count down the scroll timer. + if (chat_scrolltime > 0) + chat_scrolltime--; + if (!OLDCHAT) + HU_DrawChat(); + else + HU_DrawChat_Old(); + } + else + { + typelines = 1; + chat_scrolltime = 0; + if (!OLDCHAT && cv_consolechat.value < 2 && netgame) // Don't display minimized chat if you set the mode to Window (Hidden) + HU_drawMiniChat(); // draw messages in a cool fashion. + } + + if (netgame) // would handle that in hu_drawminichat, but it's actually kinda awkward when you're typing a lot of messages. (only handle that in netgames duh) + { + size_t i = 0; + + // handle spam while we're at it: + for(; (i<MAXPLAYERS); i++) + { + if (stop_spamming[i] > 0) + stop_spamming[i]--; + } + + // handle chat timers + for (i=0; (i<chat_nummsg_min); i++) + { + if (chat_timers[i] > 0) + chat_timers[i]--; + else + HU_removeChatText_Mini(); + } + } if (cechotimer) HU_DrawCEcho(); @@ -1153,9 +2120,9 @@ void HU_Erase(void) // clear the message lines that go away, so use _oldclearlines_ bottomline = oldclearlines; oldclearlines = con_clearlines; - if (chat_on) + if (chat_on && OLDCHAT) if (bottomline < 8) - bottomline = 8; + bottomline = 8; // only do it for consolechat. consolechat is gay. if (automapactive || viewwindowx == 0) // hud msgs don't need to be cleared return; @@ -1191,6 +2158,41 @@ void HU_Erase(void) // IN-LEVEL MULTIPLAYER RANKINGS //====================================================================== +// +// HU_drawPing +// +void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext) +{ + UINT8 numbars = 1; // how many ping bars do we draw? + UINT8 barcolor = 128; // color we use for the bars (green, yellow or red) + SINT8 i = 0; + SINT8 yoffset = 6; + INT32 dx = x+1 - (V_SmallStringWidth(va("%dms", ping), V_ALLOWLOWERCASE)/2); + + if (ping < 128) + { + numbars = 3; + barcolor = 184; + } + else if (ping < 256) + { + numbars = 2; // Apparently ternaries w/ multiple statements don't look good in C so I decided against it. + barcolor = 103; + } + + if (!notext || vid.width >= 640) // how sad, we're using a shit resolution. + V_DrawSmallString(dx, y+4, V_ALLOWLOWERCASE, va("%dms", ping)); + + for (i=0; (i<3); i++) // Draw the ping bar + { + V_DrawFill(x+2 *(i-1), y+yoffset-4, 2, 8-yoffset, 31); + if (i < numbars) + V_DrawFill(x+2 *(i-1), y+yoffset-3, 1, 8-yoffset-1, barcolor); + + yoffset -= 2; + } +} + // // HU_DrawTabRankings // @@ -1209,6 +2211,14 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I if (players[tab[i].num].spectator) continue; //ignore them. + if (!splitscreen) // don't draw it on splitscreen, + { + if (!(tab[i].num == serverplayer)) + HU_drawPing(x+ 253, y+2, playerpingtable[tab[i].num], false); + //else + // V_DrawSmallString(x+ 246, y+4, V_YELLOWMAP, "SERVER"); + } + V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0) | ((players[tab[i].num].health > 0) ? 0 : V_60TRANS) @@ -1285,6 +2295,113 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I } } +// +// HU_Draw32Emeralds +// +static void HU_Draw32Emeralds(INT32 x, INT32 y, INT32 pemeralds) +{ + //Draw the emeralds, in the CORRECT order, using tiny emerald sprites. + if (pemeralds & EMERALD1) + V_DrawSmallScaledPatch(x , y, 0, tinyemeraldpics[0]); + + if (pemeralds & EMERALD2) + V_DrawSmallScaledPatch(x+4, y, 0, tinyemeraldpics[1]); + + if (pemeralds & EMERALD3) + V_DrawSmallScaledPatch(x+8, y, 0, tinyemeraldpics[2]); + + if (pemeralds & EMERALD4) + V_DrawSmallScaledPatch(x+12 , y, 0, tinyemeraldpics[3]); + + if (pemeralds & EMERALD5) + V_DrawSmallScaledPatch(x+16, y, 0, tinyemeraldpics[4]); + + if (pemeralds & EMERALD6) + V_DrawSmallScaledPatch(x+20, y, 0, tinyemeraldpics[5]); + + if (pemeralds & EMERALD7) + V_DrawSmallScaledPatch(x+24, y, 0, tinyemeraldpics[6]); +} + +// +// HU_Draw32TeamTabRankings +// +static void HU_Draw32TeamTabRankings(playersort_t *tab, INT32 whiteplayer) +{ + INT32 i,x,y; + INT32 redplayers = 0, blueplayers = 0; + const UINT8 *colormap; + char name[MAXPLAYERNAME+1]; + + V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams. + V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T. + V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom. + + for (i = 0; i < MAXPLAYERS; i++) + { + if (players[tab[i].num].spectator) + continue; //ignore them. + + if (tab[i].color == skincolor_redteam) //red + { + redplayers++; + x = 14 + (BASEVIDWIDTH/2); + y = (redplayers * 9) + 20; + } + else if (tab[i].color == skincolor_blueteam) //blue + { + blueplayers++; + x = 14; + y = (blueplayers * 9) + 20; + } + else //er? not on red or blue, so ignore them + continue; + + strlcpy(name, tab[i].name, 8); + V_DrawString(x + 10, y, + ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0) + | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT) + | V_ALLOWLOWERCASE, name); + + if (gametype == GT_CTF) + { + if (players[tab[i].num].gotflag & GF_REDFLAG) // Red + V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, rflagico, 0); + else if (players[tab[i].num].gotflag & GF_BLUEFLAG) // Blue + V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, bflagico, 0); + } + + // Draw emeralds + if (!players[tab[i].num].powers[pw_super] + || ((leveltime/7) & 1)) + { + HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds); + } + + if (players[tab[i].num].powers[pw_super]) + { + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE); + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap); + } + else + { + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE); + if (players[tab[i].num].health <= 0) + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap); + else + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap); + } + V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + if (!splitscreen) + { + if (!(tab[i].num == serverplayer)) + HU_drawPing(x+ 135, y+3, playerpingtable[tab[i].num], true); + //else + //V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST"); + } + } +} + // // HU_DrawTeamTabRankings // @@ -1292,13 +2409,50 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) { INT32 i,x,y; INT32 redplayers = 0, blueplayers = 0; + boolean smol = false; const UINT8 *colormap; char name[MAXPLAYERNAME+1]; + // before we draw, we must count how many players are in each team. It makes an additional loop, but we need to know if we have to draw a big or a small ranking. + for (i = 0; i < MAXPLAYERS; i++) + { + if (players[tab[i].num].spectator) + continue; //ignore them. + + if (tab[i].color == skincolor_redteam) //red + { + if (redplayers++ > 8) + { + smol = true; + break; // don't make more loops than we need to. + } + } + else if (tab[i].color == skincolor_blueteam) //blue + { + if (blueplayers++ > 8) + { + smol = true; + break; + } + } + else //er? not on red or blue, so ignore them + continue; + + } + + // I'll be blunt with you, this may add more lines, but I'm not adding weird cases for this, so we're executing a separate function. + if (smol == true || cv_compactscoreboard.value) + { + HU_Draw32TeamTabRankings(tab, whiteplayer); + return; + } + V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams. V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T. V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom. + i=0, redplayers=0, blueplayers=0; + for (i = 0; i < MAXPLAYERS; i++) { if (players[tab[i].num].spectator) @@ -1321,7 +2475,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) else //er? not on red or blue, so ignore them continue; - strlcpy(name, tab[i].name, 9); + strlcpy(name, tab[i].name, 7); V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0) | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT) @@ -1355,7 +2509,14 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer) else V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap); } - V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + V_DrawRightAlignedThinString(x+100, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + if (!splitscreen) + { + if (!(tab[i].num == serverplayer)) + HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false); + //else + // V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER"); + } } } @@ -1377,7 +2538,12 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline if (players[tab[i].num].spectator) continue; //ignore them. - strlcpy(name, tab[i].name, 9); + strlcpy(name, tab[i].name, 7); + if (!(tab[i].num == serverplayer)) + HU_drawPing(x+ 113, y+2, playerpingtable[tab[i].num], false); + //else + // V_DrawSmallString(x+ 94, y+4, V_YELLOWMAP, "SERVER"); + V_DrawString(x + 20, y, ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0) | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT) @@ -1432,15 +2598,15 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline if (circuitmap) { if (players[tab[i].num].exiting) - V_DrawRightAlignedThinString(x+156, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime))); + V_DrawRightAlignedThinString(x+146, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime))); else - V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + V_DrawRightAlignedThinString(x+146, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); } else - V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count))); + V_DrawRightAlignedThinString(x+146, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count))); } else - V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + V_DrawRightAlignedThinString(x+100, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); y += 16; if (y > 160) @@ -1451,6 +2617,107 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline } } +// +// HU_Draw32TabRankings +// +static void HU_Draw32TabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer) +{ + INT32 i; + const UINT8 *colormap; + char name[MAXPLAYERNAME+1]; + + V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two sides. + V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T. + V_DrawFill(1, 180, 318, 1, 0); //And a horizontal line near the bottom. + + for (i = 0; i < scorelines; i++) + { + if (players[tab[i].num].spectator) + continue; //ignore them. + + strlcpy(name, tab[i].name, 7); + if (!splitscreen) // don't draw it on splitscreen, + { + if (!(tab[i].num == serverplayer)) + HU_drawPing(x+ 135, y+3, playerpingtable[tab[i].num], true); + //else + // V_DrawSmallString(x+ 129, y+4, V_YELLOWMAP, "HOST"); + } + + V_DrawString(x + 10, y, + ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0) + | ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT) + | V_ALLOWLOWERCASE, name); + + if (G_GametypeUsesLives()) //show lives + V_DrawRightAlignedThinString(x-1, y, V_ALLOWLOWERCASE, va("%d", players[tab[i].num].lives)); + else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT) + V_DrawFixedPatch((x-10)*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, tagico, 0); + + // Draw emeralds + if (!players[tab[i].num].powers[pw_super] + || ((leveltime/7) & 1)) + { + HU_Draw32Emeralds(x+60, y+2, tab[i].emeralds); + //HU_DrawEmeralds(x-12,y+2,tab[i].emeralds); + } + + //V_DrawSmallScaledPatch (x, y-4, 0, livesback); + if (tab[i].color == 0) + { + colormap = colormaps; + if (players[tab[i].num].powers[pw_super]) + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], 0); + else + { + if (players[tab[i].num].health <= 0) + V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], 0); + else + V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], 0); + } + } + else + { + if (players[tab[i].num].powers[pw_super]) + { + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE); + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/4, 0, superprefix[players[tab[i].num].skin], colormap); + } + else + { + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE); + if (players[tab[i].num].health <= 0) + V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, V_HUDTRANSHALF, faceprefix[players[tab[i].num].skin], colormap); + else + V_DrawFixedPatch(x*FRACUNIT, (y)*FRACUNIT, FRACUNIT/4, 0, faceprefix[players[tab[i].num].skin], colormap); + } + } + + // All data drawn with thin string for space. + if (gametype == GT_RACE) + { + if (circuitmap) + { + if (players[tab[i].num].exiting) + V_DrawRightAlignedThinString(x+128, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime))); + else + V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + } + else + V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count))); + } + else + V_DrawRightAlignedThinString(x+128, y, ((players[tab[i].num].health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count)); + + y += 9; + if (i == 16) + { + y = 32; + x += BASEVIDWIDTH/2; + } + } +} + // // HU_DrawEmeralds // @@ -1697,15 +2964,18 @@ static void HU_DrawRankings(void) scorelines++; } - if (scorelines > 20) - scorelines = 20; //dont draw past bottom of screen, show the best only + //if (scorelines > 20) + // scorelines = 20; //dont draw past bottom of screen, show the best only + // shush, we'll do it anyway. if (G_GametypeHasTeams()) HU_DrawTeamTabRankings(tab, whiteplayer); //separate function for Spazzo's silly request - else if (scorelines <= 9) + else if (scorelines <= 9 && !cv_compactscoreboard.value) HU_DrawTabRankings(40, 32, tab, scorelines, whiteplayer); - else + else if (scorelines <= 20 && !cv_compactscoreboard.value) HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer); + else + HU_Draw32TabRankings(14, 28, tab, scorelines, whiteplayer); // draw spectators in a ticker across the bottom if (!splitscreen && G_GametypeHasSpectators()) diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 23e421076975d0f4a009b504e11010b3d88ac207..b1d3918281852caf2fdd58dce33721971c9d9c2c 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -21,7 +21,7 @@ //------------------------------------ // heads up font //------------------------------------ -#define HU_FONTSTART '\x1F' // the first font character +#define HU_FONTSTART '\x16' // the first font character #define HU_FONTEND '~' #define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) @@ -57,6 +57,20 @@ typedef struct // chat stuff //------------------------------------ #define HU_MAXMSGLEN 224 +#define CHAT_BUFSIZE 64 // that's enough messages, right? We'll delete the older ones when that gets out of hand. +#ifdef NETSPLITSCREEN +#define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640) +#else +#define OLDCHAT (cv_consolechat.value == 1 || dedicated || vid.width < 640 || splitscreen) +#endif +#define CHAT_MUTE (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) // this still allows to open the chat but not to type. That's used for scrolling and whatnot. +#define OLD_MUTE (OLDCHAT && cv_mute.value && !(server || IsPlayerAdmin(consoleplayer))) // this is used to prevent oldchat from opening when muted. + +// some functions +void HU_AddChatText(const char *text, boolean playsound); + +// set true when entering a chat message +extern boolean chat_on; extern patch_t *hu_font[HU_FONTSIZE], *tny_font[HU_FONTSIZE]; extern patch_t *tallnum[10]; @@ -72,9 +86,6 @@ extern patch_t *bmatcico; extern patch_t *tagico; extern patch_t *tallminus; -// set true when entering a chat message -extern boolean chat_on; - // set true whenever the tab rankings are being shown for any reason extern boolean hu_showscores; @@ -87,12 +98,12 @@ void HU_LoadGraphics(void); void HU_Start(void); boolean HU_Responder(event_t *ev); - void HU_Ticker(void); void HU_Drawer(void); char HU_dequeueChatChar(void); void HU_Erase(void); void HU_clearChatChars(void); +void HU_drawPing(INT32 x, INT32 y, INT32 ping, boolean notext); // Lat': Ping drawer for scoreboard. void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer); void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer); void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 4666394edb603cc55449c9194513cf372f450726..fd83cf9552536aebb8310d5167c5fe2174f62396 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -23,7 +23,7 @@ #include "m_random.h" #include "s_sound.h" #include "g_game.h" -#include "hu_stuff.h" +#include "hu_stuff.h" // HU_AddChatText #include "console.h" #include "d_netcmd.h" // IsPlayerAdmin @@ -91,6 +91,51 @@ static int lib_print(lua_State *L) return 0; } +// Print stuff in the chat, or in the console if we can't. +static int lib_chatprint(lua_State *L) +{ + const char *str = luaL_checkstring(L, 1); // retrieve string + boolean sound = lua_optboolean(L, 2); // retrieve sound boolean + int len = strlen(str); + + if (str == NULL) // error if we don't have a string! + return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprint")); + + if (len > 255) // string is too long!!! + return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer."); + + HU_AddChatText(str, sound); + return 0; +} + +// Same as above, but do it for only one player. +static int lib_chatprintf(lua_State *L) +{ + int n = lua_gettop(L); /* number of arguments */ + const char *str = luaL_checkstring(L, 2); // retrieve string + boolean sound = lua_optboolean(L, 3); // sound? + int len = strlen(str); + player_t *plr; + + if (n < 2) + return luaL_error(L, "chatprintf requires at least two arguments: player and text."); + + plr = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); // retrieve player + if (!plr) + return LUA_ErrInvalid(L, "player_t"); + if (plr != &players[consoleplayer]) + return 0; + + if (str == NULL) // error if we don't have a string! + return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("chatprintf")); + + if (len > 255) // string is too long!!! + return luaL_error(L, "String exceeds the 255 characters limit of the chat buffer."); + + HU_AddChatText(str, sound); + return 0; +} + static int lib_evalMath(lua_State *L) { const char *word = luaL_checkstring(L, 1); @@ -1709,7 +1754,7 @@ static int lib_sStartSound(lua_State *L) const void *origin = NULL; sfxenum_t sound_id = luaL_checkinteger(L, 2); player_t *player = NULL; - NOHUD + //NOHUD // kys @whoever did this. if (sound_id >= NUMSFX) return luaL_error(L, "sfx %d out of range (0 - %d)", sound_id, NUMSFX-1); if (!lua_isnil(L, 1)) @@ -1725,7 +1770,12 @@ static int lib_sStartSound(lua_State *L) return LUA_ErrInvalid(L, "player_t"); } if (!player || P_IsLocalPlayer(player)) + { + if (hud_running) + origin = NULL; // HUD rendering startsound shouldn't have an origin, just remove it instead of having a retarded error. + S_StartSound(origin, sound_id); + } return 0; } @@ -2052,6 +2102,8 @@ static int lib_gTicsToMilliseconds(lua_State *L) static luaL_Reg lib[] = { {"print", lib_print}, + {"chatprint", lib_chatprint}, + {"chatprintf", lib_chatprintf}, {"EvalMath", lib_evalMath}, {"IsPlayerAdmin", lib_isPlayerAdmin}, diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 7e544ae99f7b82ff1d042f348b3e982ff3b986a4..948eca84c76bf6fc138dd8d1dea5afcb918dc99b 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -952,7 +952,7 @@ boolean LUAh_LinedefExecute(line_t *line, mobj_t *mo, sector_t *sector) return hooked; } -// Hook for player chat + boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg) { hook_p hookp; @@ -1005,6 +1005,7 @@ boolean LUAh_PlayerMsg(int source, int target, int flags, char *msg) return hooked; } + // Hook for hurt messages boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source) { diff --git a/src/m_menu.c b/src/m_menu.c index 650eac644e75c9e8f3ec7bf46f3d47f640b8e33d..7b49d359e05f19ce3b3e81af3f6436f1b3832dd5 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -33,6 +33,9 @@ #include "s_sound.h" #include "i_system.h" +// Addfile +#include "filesrch.h" + #include "v_video.h" #include "i_video.h" #include "keys.h" @@ -75,7 +78,6 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #define SMALLLINEHEIGHT 8 #define SLIDER_RANGE 10 #define SLIDER_WIDTH (8*SLIDER_RANGE+6) -#define MAXSTRINGLENGTH 32 #define SERVERS_PER_PAGE 11 typedef enum @@ -205,6 +207,8 @@ menu_t MessageDef; menu_t SPauseDef; +#define lsheadingheight 16 + // Sky Room static void M_CustomLevelSelect(INT32 choice); static void M_CustomWarp(INT32 choice); @@ -292,15 +296,22 @@ menu_t OP_SoundOptionsDef; //Misc menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef; -menu_t OP_GameOptionsDef, OP_ServerOptionsDef; +menu_t OP_GameOptionsDef, OP_ChatOptionsDef, OP_ServerOptionsDef; menu_t OP_NetgameOptionsDef, OP_GametypeOptionsDef; menu_t OP_MonitorToggleDef; static void M_ScreenshotOptions(INT32 choice); static void M_EraseData(INT32 choice); +static void M_Addons(INT32 choice); +static void M_AddonsOptions(INT32 choice); +static patch_t *addonsp[NUM_EXT+5]; + +#define numaddonsshown 4 + // Drawing functions static void M_DrawGenericMenu(void); static void M_DrawCenteredMenu(void); +static void M_DrawAddons(void); static void M_DrawSkyRoom(void); static void M_DrawChecklist(void); static void M_DrawEmblemHints(void); @@ -335,6 +346,7 @@ static boolean M_CancelConnect(void); #endif static boolean M_ExitPandorasBox(void); static boolean M_QuitMultiPlayerMenu(void); +static void M_HandleAddons(INT32 choice); static void M_HandleSoundTest(INT32 choice); static void M_HandleImageDef(INT32 choice); static void M_HandleLoadSave(INT32 choice); @@ -442,10 +454,11 @@ static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dum // --------- static menuitem_t MainMenu[] = { - {IT_CALL |IT_STRING, NULL, "Secrets", M_SecretsMenu, 84}, - {IT_CALL |IT_STRING, NULL, "1 player", M_SinglePlayerMenu, 92}, - {IT_SUBMENU|IT_STRING, NULL, "multiplayer", &MP_MainDef, 100}, - {IT_CALL |IT_STRING, NULL, "options", M_Options, 108}, + {IT_CALL |IT_STRING, NULL, "Secrets", M_SecretsMenu, 76}, + {IT_CALL |IT_STRING, NULL, "1 player", M_SinglePlayerMenu, 84}, + {IT_SUBMENU|IT_STRING, NULL, "multiplayer", &MP_MainDef, 92}, + {IT_CALL |IT_STRING, NULL, "options", M_Options, 100}, + {IT_CALL |IT_STRING, NULL, "Addons", M_Addons, 108}, {IT_CALL |IT_STRING, NULL, "quit game", M_QuitSRB2, 116}, }; @@ -455,9 +468,15 @@ typedef enum singleplr, multiplr, options, + addons, quitdoom } main_e; +static menuitem_t MISC_AddonsMenu[] = +{ + {IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleAddons, 0}, // dummy menuitem for the control func +}; + // --------------------------------- // Pause Menu Mode Attacking Edition // --------------------------------- @@ -480,6 +499,7 @@ typedef enum // --------------------- static menuitem_t MPauseMenu[] = { + {IT_STRING | IT_CALL, NULL, "Add-ons...", M_Addons, 8}, {IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...", &MISC_ScrambleTeamDef, 16}, {IT_STRING | IT_CALL, NULL, "Switch Map..." , M_MapChange, 24}, @@ -499,7 +519,8 @@ static menuitem_t MPauseMenu[] = typedef enum { - mpause_scramble = 0, + mpause_addons = 0, + mpause_scramble, mpause_switchmap, mpause_continue, @@ -985,6 +1006,7 @@ static menuitem_t OP_MainMenu[] = {IT_SUBMENU | IT_STRING, NULL, "Game Options...", &OP_GameOptionsDef, 70}, {IT_SUBMENU | IT_STRING, NULL, "Server Options...", &OP_ServerOptionsDef, 80}, + {IT_STRING | IT_CALL, NULL, "Add-on Options...", M_AddonsOptions, 90}, }; static menuitem_t OP_ControlsMenu[] = @@ -1293,28 +1315,60 @@ static menuitem_t OP_EraseDataMenu[] = {IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", M_EraseData, 40}, }; +static menuitem_t OP_AddonsOptionsMenu[] = +{ + {IT_HEADER, NULL, "Menu", NULL, 0}, + {IT_STRING|IT_CVAR, NULL, "Location", &cv_addons_option, 10}, + {IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_addons_folder, 20}, + {IT_STRING|IT_CVAR, NULL, "Identify add-ons via", &cv_addons_md5, 48}, + {IT_STRING|IT_CVAR, NULL, "Show unsupported file types", &cv_addons_showall, 58}, + + {IT_HEADER, NULL, "Search", NULL, 76}, + {IT_STRING|IT_CVAR, NULL, "Matching", &cv_addons_search_type, 86}, + {IT_STRING|IT_CVAR, NULL, "Case-sensitive", &cv_addons_search_case, 96}, +}; + +enum +{ + op_addons_folder = 2, +}; + static menuitem_t OP_GameOptionsMenu[] = { #ifndef NONET {IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server", &cv_masterserver, 10}, #endif - {IT_STRING | IT_CVAR, NULL, "Show HUD", &cv_showhud, 40}, + {IT_STRING | IT_SUBMENU, NULL, "Chat Options...", &OP_ChatOptionsDef, 40}, + {IT_STRING | IT_CVAR, NULL, "Show HUD", &cv_showhud, 50}, {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "HUD Visibility", &cv_translucenthud, 50}, - {IT_STRING | IT_CVAR, NULL, "Timer Display", &cv_timetic, 60}, + NULL, "HUD Visibility", &cv_translucenthud, 60}, + {IT_STRING | IT_CVAR, NULL, "Timer Display", &cv_timetic, 70}, + {IT_STRING | IT_CVAR, NULL, "Always Compact Rankings", &cv_compactscoreboard, 80}, #ifdef SEENAMES - {IT_STRING | IT_CVAR, NULL, "HUD Player Names", &cv_seenames, 80}, + {IT_STRING | IT_CVAR, NULL, "HUD Player Names", &cv_seenames, 90}, #endif - {IT_STRING | IT_CVAR, NULL, "Log Hazard Damage", &cv_hazardlog, 90}, + {IT_STRING | IT_CVAR, NULL, "Log Hazard Damage", &cv_hazardlog, 100}, - {IT_STRING | IT_CVAR, NULL, "Console Back Color", &cons_backcolor, 100}, - {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize,110}, - {IT_STRING | IT_CVAR, NULL, "Uppercase Console", &cv_allcaps, 120}, + {IT_STRING | IT_CVAR, NULL, "Console Back Color", &cons_backcolor, 110}, + {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize,120}, + {IT_STRING | IT_CVAR, NULL, "Uppercase Console", &cv_allcaps, 130}, {IT_STRING | IT_CVAR, NULL, "Title Screen Demos", &cv_rollingdemos, 140}, }; +static menuitem_t OP_ChatOptionsMenu[] = +{ + {IT_STRING | IT_CVAR, NULL, "Chat Mode", &cv_consolechat, 10}, + + {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Width", &cv_chatwidth, 30}, + {IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Chat Box Height", &cv_chatheight, 40}, + {IT_STRING | IT_CVAR, NULL, "Message Fadeout Time", &cv_chattime, 50}, + {IT_STRING | IT_CVAR, NULL, "Chat Notifications", &cv_chatnotifications, 60}, + {IT_STRING | IT_CVAR, NULL, "Spam Protection", &cv_chatspamprotection, 70}, + {IT_STRING | IT_CVAR, NULL, "Chat background tint", &cv_chatbacktint, 80}, +}; + static menuitem_t OP_ServerOptionsMenu[] = { {IT_STRING | IT_SUBMENU, NULL, "General netgame options...", &OP_NetgameOptionsDef, 10}, @@ -1407,6 +1461,18 @@ static menuitem_t OP_MonitorToggleMenu[] = // Main Menu and related menu_t MainDef = CENTERMENUSTYLE(NULL, MainMenu, NULL, 72); +menu_t MISC_AddonsDef = +{ + NULL, + sizeof (MISC_AddonsMenu)/sizeof (menuitem_t), + &MainDef, + MISC_AddonsMenu, + M_DrawAddons, + 50, 28, + 0, + NULL +}; + menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72); menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72); menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72); @@ -1417,6 +1483,9 @@ menu_t MISC_ChangeTeamDef = DEFAULTMENUSTYLE(NULL, MISC_ChangeTeamMenu, &MPauseD menu_t MISC_ChangeLevelDef = MAPICONMENUSTYLE(NULL, MISC_ChangeLevelMenu, &MPauseDef); menu_t MISC_HelpDef = IMAGEDEF(MISC_HelpMenu); +static INT32 highlightflags, recommendedflags, warningflags; + + // Sky Room menu_t SR_PandoraDef = { @@ -1709,6 +1778,7 @@ menu_t OP_ServerOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_ServerOptionsMenu, menu_t OP_NetgameOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_NetgameOptionsMenu, &OP_ServerOptionsDef, 30, 30); menu_t OP_GametypeOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_GametypeOptionsMenu, &OP_ServerOptionsDef, 30, 30); +menu_t OP_ChatOptionsDef = DEFAULTMENUSTYLE("M_GAME", OP_ChatOptionsMenu, &OP_GameOptionsDef, 30, 30); menu_t OP_MonitorToggleDef = { "M_SERVER", @@ -1751,6 +1821,7 @@ menu_t OP_OpenGLColorDef = #endif menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30); menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30); +menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_MainDef, 30, 30); menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30); // ========================================================================== @@ -1976,6 +2047,12 @@ void Moviemode_mode_Onchange(void) OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR; } +void Addons_option_Onchange(void) +{ + OP_AddonsOptionsMenu[op_addons_folder].status = + (cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED); +} + // ========================================================================== // END ORGANIZATION STUFF. // ========================================================================== @@ -2606,6 +2683,7 @@ void M_StartControlPanel(void) else // multiplayer { MPauseMenu[mpause_switchmap].status = IT_DISABLED; + MPauseMenu[mpause_addons].status = IT_DISABLED; MPauseMenu[mpause_scramble].status = IT_DISABLED; MPauseMenu[mpause_psetupsplit].status = IT_DISABLED; MPauseMenu[mpause_psetupsplit2].status = IT_DISABLED; @@ -2617,6 +2695,7 @@ void M_StartControlPanel(void) if ((server || IsPlayerAdmin(consoleplayer))) { MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL; + MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL; if (G_GametypeHasTeams()) MPauseMenu[mpause_scramble].status = IT_STRING | IT_SUBMENU; } @@ -3856,6 +3935,548 @@ static void M_HandleImageDef(INT32 choice) // MISC MAIN MENU OPTIONS // ====================== +static void M_AddonsOptions(INT32 choice) +{ + (void)choice; + Addons_option_Onchange(); + + M_SetupNextMenu(&OP_AddonsOptionsDef); +} + +#define LOCATIONSTRING1 "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!" +//#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make add-ons!" + +static void M_Addons(INT32 choice) +{ + const char *pathname = "."; + + (void)choice; + + // If M_GetGameypeColor() is ever ported from Kart, then remove this. + highlightflags = V_YELLOWMAP; + recommendedflags = V_GREENMAP; + warningflags = V_REDMAP; + +#if 1 + if (cv_addons_option.value == 0) + pathname = usehome ? srb2home : srb2path; + else if (cv_addons_option.value == 1) + pathname = srb2home; + else if (cv_addons_option.value == 2) + pathname = srb2path; + else +#endif + if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0') + pathname = cv_addons_folder.string; + + strlcpy(menupath, pathname, 1024); + menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1; + + if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0]) + { + menupath[menupathindex[menudepthleft]-1] = PATHSEP[0]; + menupath[menupathindex[menudepthleft]] = 0; + } + else + --menupathindex[menudepthleft]; + + if (!preparefilemenu(false)) + { + M_StartMessage(va("No files/folders found.\n\n%s\n\n(Press a key)\n",LOCATIONSTRING1),NULL,MM_NOTHING); + // (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1)) + return; + } + else + dir_on[menudepthleft] = 0; + + if (addonsp[0]) // never going to have some provided but not all, saves individually checking + { + size_t i; + for (i = 0; i < NUM_EXT+5; i++) + W_UnlockCachedPatch(addonsp[i]); + } + + addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC); + addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC); + addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC); + addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC); + addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC); + addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC); +#ifdef USE_KART + addonsp[EXT_KART] = W_CachePatchName("M_FKART", PU_STATIC); +#endif + addonsp[EXT_PK3] = W_CachePatchName("M_FPK3", PU_STATIC); + addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC); + addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC); + addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC); + addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL", PU_STATIC); + addonsp[NUM_EXT+2] = W_CachePatchName("M_FLOAD", PU_STATIC); + addonsp[NUM_EXT+3] = W_CachePatchName("M_FSRCH", PU_STATIC); + addonsp[NUM_EXT+4] = W_CachePatchName("M_FSAVE", PU_STATIC); + + MISC_AddonsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_AddonsDef); +} + +#define width 4 +#define vpadding 27 +#define h (BASEVIDHEIGHT-(2*vpadding)) +#define NUMCOLOURS 8 // when toast's coding it's british english hacker fucker +static void M_DrawTemperature(INT32 x, fixed_t t) +{ + INT32 y; + + // bounds check + if (t > FRACUNIT) + t = FRACUNIT; + /*else if (t < 0) -- not needed + t = 0;*/ + + // scale + if (t > 1) + t = (FixedMul(h<<FRACBITS, t)>>FRACBITS); + + // border + V_DrawFill(x - 1, vpadding, 1, h, 120); + V_DrawFill(x + width, vpadding, 1, h, 120); + V_DrawFill(x - 1, vpadding-1, width+2, 1, 120); + V_DrawFill(x - 1, vpadding+h, width+2, 1, 120); + + // bar itself + y = h; + if (t) + for (t = h - t; y > 0; y--) + { + UINT8 colours[NUMCOLOURS] = {135, 133, 92, 77, 114, 178, 161, 162}; + UINT8 c; + if (y <= t) break; + if (y+vpadding >= BASEVIDHEIGHT/2) + c = 185; + else + c = colours[(NUMCOLOURS*(y-1))/(h/2)]; + V_DrawFill(x, y-1 + vpadding, width, 1, c); + } + + // fill the rest of the backing + if (y) + V_DrawFill(x, vpadding, width, y, 30); +} +#undef width +#undef vpadding +#undef h +#undef NUMCOLOURS + +static char *M_AddonsHeaderPath(void) +{ + UINT32 len; + static char header[1024]; + + strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024); + len = strlen(header); + if (len > 34) + { + len = len-34; + header[len] = header[len+1] = header[len+2] = '.'; + } + else + len = 0; + + return header+len; +} + +#define UNEXIST S_StartSound(NULL, sfx_lose);\ + M_SetupNextMenu(MISC_AddonsDef.prevMenu);\ + M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING) + +#define CLEARNAME Z_Free(refreshdirname);\ + refreshdirname = NULL + +static void M_AddonsClearName(INT32 choice) +{ + CLEARNAME; + M_StopMessage(choice); +} + +// returns whether to do message draw +static boolean M_AddonsRefresh(void) +{ + if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true)) + { + UNEXIST; + return true; + } + + if (refreshdirmenu & REFRESHDIR_ADDFILE) + { + char *message = NULL; + + if (refreshdirmenu & REFRESHDIR_NOTLOADED) + { + S_StartSound(NULL, sfx_lose); + if (refreshdirmenu & REFRESHDIR_MAX) + message = va("%c%s\x80\nMaximum number of add-ons reached.\nA file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + else + message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + } + else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR)) + { + S_StartSound(NULL, sfx_skid); + message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")); + } + + if (message) + { + M_StartMessage(message,M_AddonsClearName,MM_EVENTHANDLER); + return true; + } + + S_StartSound(NULL, sfx_strpst); + CLEARNAME; + } + + return false; +} + +static void M_DrawAddons(void) +{ + INT32 x, y; + ssize_t i, m; + const UINT8 *flashcol = NULL; + UINT8 hilicol; + + // hack - need to refresh at end of frame to handle addfile... + if (refreshdirmenu & M_AddonsRefresh()) + { + M_DrawMessageMenu(); + return; + } + + if (Playing()) + V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems."); + else + V_DrawCenteredString(BASEVIDWIDTH/2, 5, 0, LOCATIONSTRING1); + // (recommendedflags == V_SKYMAP ? LOCATIONSTRING2 : LOCATIONSTRING1) + + if (numwadfiles <= mainwads+1) + y = 0; + else if (numwadfiles >= MAX_WADFILES) + y = FRACUNIT; + else + { + x = FixedDiv(((ssize_t)(numwadfiles) - (ssize_t)(mainwads+1))<<FRACBITS, ((ssize_t)MAX_WADFILES - (ssize_t)(mainwads+1))<<FRACBITS); + y = FixedDiv((((ssize_t)packetsizetally-(ssize_t)mainwadstally)<<FRACBITS), ((((ssize_t)MAXFILENEEDED*sizeof(UINT8)-(ssize_t)mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally + if (x > y) + y = x; + if (y > FRACUNIT) // happens because of how we're shrinkin' it a little + y = FRACUNIT; + } + + M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y); + + // DRAW MENU + x = currentMenu->x; + y = currentMenu->y + 1; + + hilicol = V_GetStringColormap(highlightflags)[120]; + + V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), highlightflags|V_ALLOWLOWERCASE, M_AddonsHeaderPath()); + V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), MAXSTRINGLENGTH*8+6, 1, hilicol); + V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 30); + + m = (BASEVIDHEIGHT - currentMenu->y + 2) - (y - 1); + V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, m, 239); + + // scrollbar! + if (sizedirmenu <= (2*numaddonsshown + 1)) + i = 0; + else + { + ssize_t q = m; + m = ((2*numaddonsshown + 1) * m)/sizedirmenu; + if (dir_on[menudepthleft] <= numaddonsshown) // all the way up + i = 0; + else if (sizedirmenu <= (dir_on[menudepthleft] + numaddonsshown + 1)) // all the way down + i = q-m; + else + i = ((dir_on[menudepthleft] - numaddonsshown) * (q-m))/(sizedirmenu - (2*numaddonsshown + 1)); + } + + V_DrawFill(x + MAXSTRINGLENGTH*8+5 - 21, (y - 1) + i, 1, m, hilicol); + + // get bottom... + m = dir_on[menudepthleft] + numaddonsshown + 1; + if (m > (ssize_t)sizedirmenu) + m = sizedirmenu; + + // then compute top and adjust bottom if needed! + if (m < (2*numaddonsshown + 1)) + { + m = min(sizedirmenu, 2*numaddonsshown + 1); + i = 0; + } + else + i = m - (2*numaddonsshown + 1); + + if (i != 0) + V_DrawString(19, y+4 - (skullAnimCounter/5), highlightflags, "\x1A"); + + if (skullAnimCounter < 4) + flashcol = V_GetStringColormap(highlightflags); + + for (; i < m; i++) + { + UINT32 flags = V_ALLOWLOWERCASE; + if (y > BASEVIDHEIGHT) break; + if (dirmenu[i]) +#define type (UINT8)(dirmenu[i][DIR_TYPE]) + { + if (type & EXT_LOADED) + { + flags |= V_TRANSLUCENT; + V_DrawSmallScaledPatch(x-(16+4), y, V_TRANSLUCENT, addonsp[(type & ~EXT_LOADED)]); + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+2]); + } + else + V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[(type & ~EXT_LOADED)]); + + if ((size_t)i == dir_on[menudepthleft]) + { + V_DrawFixedPatch((x-(16+4))<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, 0, addonsp[NUM_EXT+1], flashcol); + flags = V_ALLOWLOWERCASE|highlightflags; + } + +#define charsonside 14 + if (dirmenu[i][DIR_LEN] > (charsonside*2 + 3)) + V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1))); +#undef charsonside + else + V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING); + } +#undef type + y += 16; + } + + if (m != (ssize_t)sizedirmenu) + V_DrawString(19, y-12 + (skullAnimCounter/5), highlightflags, "\x1B"); + + y = BASEVIDHEIGHT - currentMenu->y + 1; + + M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1); + if (menusearch[0]) + V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE, menusearch+1); + else + V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE|V_TRANSLUCENT, "Type to search..."); + if (skullAnimCounter < 4) + V_DrawCharacter(x - 18 + V_StringWidth(menusearch+1, 0), y + 8, + '_' | 0x80, false); + + x -= (21 + 5 + 16); + V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+3]); + + x = BASEVIDWIDTH - x - 16; + V_DrawSmallScaledPatch(x, y + 4, ((!modifiedgame || savemoddata) ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]); + + if (modifiedgame) + V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+2]); +} + +static void M_AddonExec(INT32 ch) +{ + if (ch != 'y' && ch != KEY_ENTER) + return; + + S_StartSound(NULL, sfx_zoom); + COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); +} + +#define len menusearch[0] +static boolean M_ChangeStringAddons(INT32 choice) +{ + if (shiftdown && choice >= 32 && choice <= 127) + choice = shiftxform[choice]; + + switch (choice) + { + case KEY_DEL: + if (len) + { + len = menusearch[1] = 0; + return true; + } + break; + case KEY_BACKSPACE: + if (len) + { + menusearch[1+--len] = 0; + return true; + } + break; + default: + if (choice >= 32 && choice <= 127) + { + if (len < MAXSTRINGLENGTH - 1) + { + menusearch[1+len++] = (char)choice; + menusearch[1+len] = 0; + return true; + } + } + break; + } + return false; +} +#undef len + +static void M_HandleAddons(INT32 choice) +{ + boolean exitmenu = false; // exit to previous menu + + if (M_ChangeStringAddons(choice)) + { + char *tempname = NULL; + if (dirmenu && dirmenu[dir_on[menudepthleft]]) + tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL +#if 0 // much slower + if (!preparefilemenu(true)) + { + UNEXIST; + return; + } +#else // streamlined + searchfilemenu(tempname); +#endif + } + + switch (choice) + { + case KEY_DOWNARROW: + if (dir_on[menudepthleft] < sizedirmenu-1) + dir_on[menudepthleft]++; + S_StartSound(NULL, sfx_menu1); + break; + case KEY_UPARROW: + if (dir_on[menudepthleft]) + dir_on[menudepthleft]--; + S_StartSound(NULL, sfx_menu1); + break; + case KEY_PGDN: + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--) + dir_on[menudepthleft]++; + } + S_StartSound(NULL, sfx_menu1); + break; + case KEY_PGUP: + { + UINT8 i; + for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--) + dir_on[menudepthleft]--; + } + S_StartSound(NULL, sfx_menu1); + break; + case KEY_ENTER: + { + boolean refresh = true; + if (!dirmenu[dir_on[menudepthleft]]) + S_StartSound(NULL, sfx_lose); + else + { + switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE]) + { + case EXT_FOLDER: + strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING); + if (menudepthleft) + { + menupathindex[--menudepthleft] = strlen(menupath); + menupath[menupathindex[menudepthleft]] = 0; + + if (!preparefilemenu(false)) + { + S_StartSound(NULL, sfx_skid); + M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[++menudepthleft]] = 0; + + if (!preparefilemenu(true)) + { + UNEXIST; + return; + } + } + else + { + S_StartSound(NULL, sfx_menu1); + dir_on[menudepthleft] = 1; + } + refresh = false; + } + else + { + S_StartSound(NULL, sfx_lose); + M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING); + menupath[menupathindex[menudepthleft]] = 0; + } + break; + case EXT_UP: + S_StartSound(NULL, sfx_menu1); + menupath[menupathindex[++menudepthleft]] = 0; + if (!preparefilemenu(false)) + { + UNEXIST; + return; + } + break; + case EXT_TXT: + M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO); + break; + case EXT_CFG: + M_AddonExec(KEY_ENTER); + break; + case EXT_LUA: +#ifndef HAVE_BLUA + S_StartSound(NULL, sfx_lose); + M_StartMessage(va("%c%s\x80\nThis copy of SRB2 was compiled\nwithout support for .lua files.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),NULL,MM_NOTHING); + break; +#endif + // else intentional fallthrough + case EXT_SOC: + case EXT_WAD: +#ifdef USE_KART + case EXT_KART: +#endif + case EXT_PK3: + COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING)); + break; + default: + S_StartSound(NULL, sfx_lose); + } + } + if (refresh) + refreshdirmenu |= REFRESHDIR_NORMAL; + } + break; + + case KEY_ESCAPE: + exitmenu = true; + break; + + default: + break; + } + if (exitmenu) + { + closefilemenu(true); + + // Secret menu! + MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + + if (currentMenu->prevMenu) + M_SetupNextMenu(currentMenu->prevMenu); + else + M_ClearMenus(true); + } +} + static void M_PandorasBox(INT32 choice) { (void)choice; @@ -6960,7 +7581,7 @@ static void M_ChangecontrolResponse(event_t *ev) found = 0; setupcontrols[control][1] = KEY_NULL; //replace key 1,clear key2 } - G_CheckDoubleUsage(ch); + (void)G_CheckDoubleUsage(ch, true); setupcontrols[control][found] = ch; } S_StartSound(NULL, sfx_strpst); diff --git a/src/m_menu.h b/src/m_menu.h index de76a2710dd0b68480f31b9e1b4167603da33911..eb770c19425c2b6730bdfbd8479463dd8f567112 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -124,6 +124,8 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt); #define IT_HEADER (IT_SPACE +IT_HEADERTEXT) #define IT_SECRET (IT_SPACE +IT_QUESTIONMARKS) +#define MAXSTRINGLENGTH 32 + typedef union { struct menu_s *submenu; // IT_SUBMENU @@ -223,6 +225,9 @@ void M_CheatActivationResponder(INT32 ch); void Moviemode_mode_Onchange(void); void Screenshot_option_Onchange(void); +// Addons menu updating +void Addons_option_Onchange(void); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(header, source, prev, x, y)\ {\ diff --git a/src/m_misc.c b/src/m_misc.c index 79fb0c99b6a5a1f0d780a9b079d96aec847077b4..7dd0d822c7ff9bd5b7f6c05b35f17dc7fba742c7 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -447,7 +447,7 @@ void Command_LoadConfig_f(void) G_Controldefault(); // temporarily reset execversion to default - cv_execversion.flags &= ~CV_HIDEN; + CV_ToggleExecVersion(true); COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue)); CV_InitFilterVar(); @@ -455,8 +455,8 @@ void Command_LoadConfig_f(void) COM_BufInsertText(va("exec \"%s\"\n", configfile)); // don't filter anymore vars and don't let this convsvar be changed - COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, MODVERSION)); - cv_execversion.flags |= CV_HIDEN; + COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION)); + CV_ToggleExecVersion(false); } /** Saves the current configuration and loads another. @@ -498,7 +498,7 @@ void M_FirstLoadConfig(void) // temporarily reset execversion to default // we shouldn't need to do this, but JUST in case... - cv_execversion.flags &= ~CV_HIDEN; + CV_ToggleExecVersion(true); COM_BufInsertText(va("%s \"%s\"\n", cv_execversion.name, cv_execversion.defaultvalue)); CV_InitFilterVar(); @@ -507,8 +507,8 @@ void M_FirstLoadConfig(void) // no COM_BufExecute() needed; that does it right away // don't filter anymore vars and don't let this convsvar be changed - COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, MODVERSION)); - cv_execversion.flags |= CV_HIDEN; + COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION)); + CV_ToggleExecVersion(false); // make sure I_Quit() will write back the correct config // (do not write back the config if it crash before) @@ -574,8 +574,8 @@ void M_SaveConfig(const char *filename) fprintf(f, "// SRB2 configuration file.\n"); // print execversion FIRST, because subsequent consvars need to be filtered - // always print current MODVERSION - fprintf(f, "%s \"%d\"\n", cv_execversion.name, MODVERSION); + // always print current EXECVERSION + fprintf(f, "%s \"%d\"\n", cv_execversion.name, EXECVERSION); // FIXME: save key aliases if ever implemented.. diff --git a/src/p_mobj.c b/src/p_mobj.c index 4fea158022f91df67cf8adf515778022897f666b..850ec2987f5de2513e59e00ded0824c9fb0a2dbd 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7274,6 +7274,8 @@ void P_MobjThinker(mobj_t *mobj) // Assumedly in splitscreen players will be on opposing teams if (players[consoleplayer].ctfteam == 1 || splitscreen) S_StartSound(NULL, sfx_hoop1); + else if (players[consoleplayer].ctfteam == 2) + S_StartSound(NULL, sfx_hoop3); redflag = flagmo; } @@ -7285,6 +7287,8 @@ void P_MobjThinker(mobj_t *mobj) // Assumedly in splitscreen players will be on opposing teams if (players[consoleplayer].ctfteam == 2 || splitscreen) S_StartSound(NULL, sfx_hoop1); + else if (players[consoleplayer].ctfteam == 1) + S_StartSound(NULL, sfx_hoop3); blueflag = flagmo; } diff --git a/src/p_setup.c b/src/p_setup.c index 4e66bebbef8075e9e81f6f9d47a83cf96d299777..b1521a53e714d2398ebfb90cb6619bd63fc1aa0a 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -54,6 +54,8 @@ #include "v_video.h" +#include "filesrch.h" // refreshdirmenu + // wipes #include "f_finale.h" @@ -3197,6 +3199,7 @@ boolean P_AddWadFile(const char *wadfilename) if ((numlumps = W_InitFile(wadfilename)) == INT16_MAX) { + refreshdirmenu |= REFRESHDIR_NOTLOADED; CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), wadfilename); return false; } diff --git a/src/r_data.c b/src/r_data.c index e0b9a8147f858bd2c4ae740202da6ebd60175b6d..a21ba49ae9c3ae83efc88cf7376fb50af96a3a35 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -1220,7 +1220,7 @@ INT32 R_CreateColormap(char *p1, char *p2, char *p3) continue; if (maskcolor == extra_colormaps[i].maskcolor && fadecolor == extra_colormaps[i].fadecolor - && fabsf((float)(maskamt - extra_colormaps[i].maskamt)) < 1.0E-36f + && fabs(maskamt - extra_colormaps[i].maskamt) < DBL_EPSILON && fadestart == extra_colormaps[i].fadestart && fadeend == extra_colormaps[i].fadeend && fog == extra_colormaps[i].fog) diff --git a/src/r_segs.c b/src/r_segs.c index 6bdc8eabc988bfd22d31fdee6f12c4cd23f03907..6d6ba1efd1c0dd7a62cfd130df0e2cc08adfa133 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -1126,9 +1126,6 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) // Get data for the column col = (column_t *)((UINT8 *)R_GetColumn(texnum,maskedtexturecol[dc_x]) - 3); - // guess what I just fixed? -monster psychic cat - dc_colormap = colormaps; - // SoM: New code does not rely on R_DrawColumnShadowed_8 which // will (hopefully) put less strain on the stack. if (dc_numlights) diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj index 6eabee56b6d20e57ff793ed589024203e6046f22..6242e0a05756c2e09db05db109501c16060ac2a8 100644 --- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj +++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj @@ -1214,7 +1214,7 @@ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 2.1.21; + CURRENT_PROJECT_VERSION = 2.1.22; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", NORMALSRB2, @@ -1226,7 +1226,7 @@ C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 2.1.21; + CURRENT_PROJECT_VERSION = 2.1.22; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj index 93f63309cab89371035681e58ac686c6733b8c78..081d046039f822034fde3453cd10d0efedc9972f 100644 --- a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj +++ b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj @@ -1214,7 +1214,7 @@ C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 2.1.21; + CURRENT_PROJECT_VERSION = 2.1.22; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", NORMALSRB2, @@ -1226,7 +1226,7 @@ C01FCF4C08A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CURRENT_PROJECT_VERSION = 2.1.21; + CURRENT_PROJECT_VERSION = 2.1.22; GCC_ENABLE_FIX_AND_CONTINUE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/src/v_video.c b/src/v_video.c index 35b848f98d052b7fbe41f64d0e3a9648dc991882..cfe7d036032c773fef71a8ff75c1c5180dce5ae8 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -841,6 +841,145 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) memset(dest, c, w * vid.bpp); } +#ifdef HWRENDER +// This is now a function since it's otherwise repeated 2 times and honestly looks retarded: +static UINT32 V_GetHWConsBackColor(void) +{ + UINT32 hwcolor; + switch (cons_backcolor.value) + { + case 0: hwcolor = 0xffffff00; break; // White + case 1: hwcolor = 0x80808000; break; // Gray + case 2: hwcolor = 0xdeb88700; break; // Sepia + case 3: hwcolor = 0x40201000; break; // Brown + case 4: hwcolor = 0xfa807200; break; // Pink + case 5: hwcolor = 0xff69b400; break; // Raspberry + case 6: hwcolor = 0xff000000; break; // Red + case 7: hwcolor = 0xffd68300; break; // Creamsicle + case 8: hwcolor = 0xff800000; break; // Orange + case 9: hwcolor = 0xdaa52000; break; // Gold + case 10: hwcolor = 0x80800000; break; // Yellow + case 11: hwcolor = 0x00ff0000; break; // Emerald + case 12: hwcolor = 0x00800000; break; // Green + case 13: hwcolor = 0x4080ff00; break; // Cyan + case 14: hwcolor = 0x4682b400; break; // Steel + case 15: hwcolor = 0x1e90ff00; break; // Periwinkle + case 16: hwcolor = 0x0000ff00; break; // Blue + case 17: hwcolor = 0xff00ff00; break; // Purple + case 18: hwcolor = 0xee82ee00; break; // Lavender + // Default green + default: hwcolor = 0x00800000; break; + } + return hwcolor; +} +#endif + + +// THANK YOU MPC!!! + +void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) +{ + UINT8 *dest; + INT32 u, v; + UINT8 *fadetable; + UINT32 alphalevel = 0; + + if (rendermode == render_none) + return; + +#ifdef HWRENDER + if (rendermode != render_soft && rendermode != render_none) + { + UINT32 hwcolor = V_GetHWConsBackColor(); + HWR_DrawConsoleFill(x, y, w, h, hwcolor, c); // we still use the regular color stuff but only for flags. actual draw color is "hwcolor" for this. + return; + } +#endif + + if (!(c & V_NOSCALESTART)) + { + INT32 dupx = vid.dupx, dupy = vid.dupy; + + if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) + { // Clear the entire screen, from dest to deststop. Yes, this really works. + memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp); + return; + } + + x *= dupx; + y *= dupy; + w *= dupx; + h *= dupy; + + // Center it if necessary + if (vid.width != BASEVIDWIDTH * dupx) + { + // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, + // so center this imaginary screen + if (c & V_SNAPTORIGHT) + x += (vid.width - (BASEVIDWIDTH * dupx)); + else if (!(c & V_SNAPTOLEFT)) + x += (vid.width - (BASEVIDWIDTH * dupx)) / 2; + } + if (vid.height != BASEVIDHEIGHT * dupy) + { + // same thing here + if (c & V_SNAPTOBOTTOM) + y += (vid.height - (BASEVIDHEIGHT * dupy)); + else if (!(c & V_SNAPTOTOP)) + y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2; + } + } + + if (x >= vid.width || y >= vid.height) + return; // off the screen + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + + if (w <= 0 || h <= 0) + return; // zero width/height wouldn't draw anything + if (x + w > vid.width) + w = vid.width-x; + if (y + h > vid.height) + h = vid.height-y; + + dest = screens[0] + y*vid.width + x; + + if ((alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT))) + { + if (alphalevel == 13) + alphalevel = hudminusalpha[cv_translucenthud.value]; + else if (alphalevel == 14) + alphalevel = 10 - cv_translucenthud.value; + else if (alphalevel == 15) + alphalevel = hudplusalpha[cv_translucenthud.value]; + + if (alphalevel >= 10) + return; // invis + } + + c &= 255; + + // Jimita (12-04-2018) + w = min(w, vid.width); + h = min(h, vid.height); + fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256)); + for (v = 0; v < h; v++, dest += vid.width) + for (u = 0; u < w; u++) + { + if (!alphalevel) + dest[u] = consolebgmap[dest[u]]; + else + dest[u] = fadetable[consolebgmap[dest[u]]]; + } +} + // // Fills a box of pixels using a flat texture as a pattern, scaled to screen size. // @@ -983,21 +1122,7 @@ void V_DrawFadeConsBack(INT32 plines) #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode != render_soft && rendermode != render_none) { - UINT32 hwcolor; - switch (cons_backcolor.value) - { - case 0: hwcolor = 0xffffff00; break; // White - case 1: hwcolor = 0x80808000; break; // Gray - case 2: hwcolor = 0x40201000; break; // Brown - case 3: hwcolor = 0xff000000; break; // Red - case 4: hwcolor = 0xff800000; break; // Orange - case 5: hwcolor = 0x80800000; break; // Yellow - case 6: hwcolor = 0x00800000; break; // Green - case 7: hwcolor = 0x0000ff00; break; // Blue - case 8: hwcolor = 0x4080ff00; break; // Cyan - // Default green - default: hwcolor = 0x00800000; break; - } + UINT32 hwcolor = V_GetHWConsBackColor(); HWR_DrawConsoleBack(hwcolor, plines); return; } @@ -1012,7 +1137,7 @@ void V_DrawFadeConsBack(INT32 plines) // Gets string colormap, used for 0x80 color codes // -static const UINT8 *V_GetStringColormap(INT32 colorflags) +UINT8 *V_GetStringColormap(INT32 colorflags) { switch ((colorflags & V_CHARCOLORMASK) >> V_CHARCOLORSHIFT) { @@ -1061,6 +1186,32 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed) V_DrawScaledPatch(x, y, flags, hu_font[c]); } +// Writes a single character for the chat. (draw WHITE if bit 7 set) +// Essentially the same as the above but it's small or big depending on what resolution you've chosen to huge.. +// +void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap) +{ + INT32 w, flags; + //const UINT8 *colormap = V_GetStringColormap(c); + + flags = c & ~(V_CHARCOLORMASK | V_PARAMMASK); + c &= 0x7f; + if (lowercaseallowed) + c -= HU_FONTSTART; + else + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + return; + + w = (vid.width < 640 ) ? (SHORT(hu_font[c]->width)/2) : (SHORT(hu_font[c]->width)); // use normal sized characters if we're using a terribly low resolution. + if (x + w > vid.width) + return; + + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, (vid.width < 640) ? (FRACUNIT) : (FRACUNIT/2), flags, hu_font[c], colormap); + + +} + // Precompile a wordwrapped string to any given width. // This is a muuuch better method than V_WORDWRAP. char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) diff --git a/src/v_video.h b/src/v_video.h index a60a08a5bb5349c3477214e0c527a42005ec0efe..877a619b5ac56ad60bb423918f1647b7dbb7d0af 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -139,6 +139,7 @@ void V_DrawScaledPic (INT32 px1, INT32 py1, INT32 scrn, INT32 lumpnum); // fill a box with a single color void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c); +void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c); // fill a box with a flat as a pattern void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum); @@ -149,11 +150,16 @@ void V_DrawFadeConsBack(INT32 plines); // draw a single character void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed); +// draw a single character, but for the chat +void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UINT8 *colormap); + +UINT8 *V_GetStringColormap(INT32 colorflags); void V_DrawLevelTitle(INT32 x, INT32 y, INT32 option, const char *string); // wordwrap a string using the hu_font char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string); +UINT8 *V_GetStringColormap(INT32 colorflags); // draw a string using the hu_font void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string); diff --git a/src/w_wad.c b/src/w_wad.c index e1cd16ae9a58dc22b35f8c7c68459aa8ff2aae54..88e89974aab83c201d7f2b0fcdd88dca39481645 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -34,6 +34,8 @@ #include "z_zone.h" #include "fastcmp.h" +#include "filesrch.h" + #include "i_video.h" // rendermode #include "d_netfil.h" #include "dehacked.h" @@ -647,12 +649,22 @@ UINT16 W_InitFile(const char *filename) restype_t type; UINT16 numlumps = 0; size_t i; - size_t packetsize = 0; - serverinfo_pak *dummycheck = NULL; + size_t packetsize; UINT8 md5sum[16]; + boolean important; + + if (!(refreshdirmenu & REFRESHDIR_ADDFILE)) + refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier - // Shut the compiler up. - (void)dummycheck; + if (refreshdirname) + Z_Free(refreshdirname); + if (dirmenu) + { + refreshdirname = Z_StrDup(filename); + nameonly(refreshdirname); + } + else + refreshdirname = NULL; //CONS_Debug(DBG_SETUP, "Loading %s\n", filename); // @@ -661,6 +673,7 @@ UINT16 W_InitFile(const char *filename) if (numwadfiles >= MAX_WADFILES) { CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); + refreshdirmenu |= REFRESHDIR_MAX; return INT16_MAX; } @@ -670,21 +683,21 @@ UINT16 W_InitFile(const char *filename) // Check if wad files will overflow fileneededbuffer. Only the filename part // is send in the packet; cf. - for (i = 0; i < numwadfiles; i++) + // see PutFileNeeded in d_netfil.c + if ((important = !W_VerifyNMUSlumps(filename))) { - packetsize += nameonlylength(wadfiles[i]->filename); - packetsize += 22; // MD5, etc. - } + packetsize = packetsizetally + nameonlylength(filename) + 22; - packetsize += nameonlylength(filename); - packetsize += 22; + if (packetsize > MAXFILENEEDED*sizeof(UINT8)) + { + CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); + refreshdirmenu |= REFRESHDIR_MAX; + if (handle) + fclose(handle); + return INT16_MAX; + } - if (packetsize > sizeof(dummycheck->fileneeded)) - { - CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n")); - if (handle) - fclose(handle); - return INT16_MAX; + packetsizetally = packetsize; } #ifndef NOMD5 @@ -741,6 +754,7 @@ UINT16 W_InitFile(const char *filename) wadfile->handle = handle; wadfile->numlumps = (UINT16)numlumps; wadfile->lumpinfo = lumpinfo; + wadfile->important = important; fseek(handle, 0, SEEK_END); wadfile->filesize = (unsigned)ftell(handle); wadfile->type = type; diff --git a/src/w_wad.h b/src/w_wad.h index 8baf061adce3982a185ff1c9b35489802aad6d9d..e2e17740f76fc24ed7d02a60fc5f854824888296 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -110,6 +110,7 @@ typedef struct wadfile_s FILE *handle; UINT32 filesize; // for network UINT8 md5sum[16]; + boolean important; } wadfile_t; #define WADFILENUM(lumpnum) (UINT16)((lumpnum)>>16) // wad flumpnum>>16) // wad file number in upper word