diff --git a/.circleci/config.yml b/.circleci/config.yml index e7916074..c08a7f6d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,12 +42,12 @@ jobs: paths: - /var/cache/apt/archives - checkout - - run: - name: Compile without network support and BLUA - command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NO_LUA=1 - - run: - name: wipe build - command: make -C src LINUX=1 cleandep + #- run: + # name: Compile without network support and BLUA + # command: make -C src LINUX=1 ERRORMODE=1 -k NONET=1 NO_LUA=1 + #- run: + # name: wipe build + # command: make -C src LINUX=1 cleandep - run: name: rebuild depend command: make -C src LINUX=1 clean diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bd76843..ceaf65f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) # DO NOT CHANGE THIS SRB2 STRING! Some variable names depend on this string. # Version change is fine. project(SRB2 - VERSION 1.4 + VERSION 1.6 LANGUAGES C) if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR}) diff --git a/src/command.c b/src/command.c index 9804dbc0..49bba34e 100644 --- a/src/command.c +++ b/src/command.c @@ -353,6 +353,40 @@ size_t COM_CheckParm(const char *check) return 0; } +/** \brief COM_CheckParm, but checks only the start of each argument. + * E.g. checking for "-no" would match "-noerror" too. + */ +size_t COM_CheckPartialParm(const char *check) +{ + int len; + size_t i; + + len = strlen(check); + + for (i = 1; i < com_argc; i++) + { + if (strncasecmp(check, com_argv[i], len) == 0) + return i; + } + return 0; +} + +/** Find the first argument that starts with a hyphen (-). + * \return The index of the argument, or 0 + * if there are no such arguments. + */ +size_t COM_FirstOption(void) +{ + size_t i; + + for (i = 1; i < com_argc; i++) + { + if (com_argv[i][0] == '-')/* options start with a hyphen */ + return i; + } + return 0; +} + /** Parses a string into command-line tokens. * * \param ptext A null-terminated string. Does not need to be diff --git a/src/command.h b/src/command.h index ae573a16..3fc39c84 100644 --- a/src/command.h +++ b/src/command.h @@ -37,6 +37,8 @@ size_t COM_Argc(void); const char *COM_Argv(size_t arg); // if argv > argc, returns empty string char *COM_Args(void); size_t COM_CheckParm(const char *check); // like M_CheckParm :) +size_t COM_CheckPartialParm(const char *check); +size_t COM_FirstOption(void); // match existing command or NULL const char *COM_CompleteCommand(const char *partial, INT32 skips); diff --git a/src/config.h.in b/src/config.h.in index 0ead749a..5225c8b0 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -40,6 +40,7 @@ * Last updated 2020 / 08 / 30 - Kart v1.3 - patch.kart * Last updated 2022 / 08 / 16 - Kart v1.4 - Main assets * Last updated 2022 / 08 / 19 - Kart v1.5 - gfx.kart + * Last updated 2022 / 11 / 01 - Kart v1.6 - gfx.kart, maps.kart */ // Base SRB2 hashes @@ -49,10 +50,10 @@ #endif // SRB2Kart-specific hashes -#define ASSET_HASH_GFX_KART "30b2d9fb5009f1b3a3d7216a0fe28e51" +#define ASSET_HASH_GFX_KART "06f86ee16136eb8a7043b15001797034" #define ASSET_HASH_TEXTURES_KART "abb53d56aba47c3a8cb0f764da1c8b80" #define ASSET_HASH_CHARS_KART "e2c428347dde52858a3dacd29fc5b964" -#define ASSET_HASH_MAPS_KART "13e273292576b71af0cdb3a98ca066eb" +#define ASSET_HASH_MAPS_KART "d051e55141ba736582228c456953cd98" #ifdef USE_PATCH_KART #define ASSET_HASH_PATCH_KART "00000000000000000000000000000000" #endif diff --git a/src/d_clisrv.c b/src/d_clisrv.c index bb2364c5..727eba4a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1130,6 +1130,7 @@ typedef enum CL_PREPAREHTTPFILES, CL_DOWNLOADHTTPFILES, #endif + CL_LEGACYREQUESTFAILED, } cl_mode_t; static void GetPackets(void); @@ -1227,6 +1228,7 @@ static inline void CL_DrawConnectionStatus(void) #endif case CL_ASKFULLFILELIST: case CL_CONFIRMCONNECT: + case CL_LEGACYREQUESTFAILED: cltext = ""; break; case CL_SETUPFILES: @@ -1333,8 +1335,10 @@ static inline void CL_DrawConnectionStatus(void) strncpy(tempname, filename, sizeof(tempname)-1); } + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-30, 0, + va(M_GetText("%s downloading"), ((cl_mode == CL_DOWNLOADHTTPFILES) ? "\x82""HTTP" : "\x85""Direct"))); V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-22, V_YELLOWMAP, - va(M_GetText("Downloading \"%s\""), tempname)); + va(M_GetText("\"%s\""), tempname)); V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,file->totalsize>>10)); V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-58, V_20TRANS|V_MONOSPACE, @@ -2124,6 +2128,10 @@ static void M_ConfirmConnect(event_t *ev) { cl_mode = CL_DOWNLOADFILES; } + else + { + cl_mode = CL_LEGACYREQUESTFAILED; + } } #ifdef HAVE_CURL else @@ -2279,6 +2287,10 @@ static boolean CL_FinishedFileList(void) { cl_mode = CL_DOWNLOADFILES; } + else + { + cl_mode = CL_LEGACYREQUESTFAILED; + } } #endif } @@ -2465,6 +2477,21 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic cl_mode = CL_LOADFILES; break; + case CL_LEGACYREQUESTFAILED: + { + CONS_Printf(M_GetText("Legacy downloader request packet failed.\n")); + CONS_Printf(M_GetText("Network game synchronization aborted.\n")); + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + M_StartMessage(M_GetText( + "The direct download encountered an error.\n" + "See the logfile for more info.\n" + "\n" + "Press ESC\n" + ), NULL, MM_NOTHING); + return false; + } case CL_LOADFILES: if (CL_LoadServerFiles()) cl_mode = CL_SETUPFILES; @@ -5945,23 +5972,20 @@ boolean TryRunTics(tic_t realtics) if (ticking) { - if (advancedemo) - D_StartTitle(); - else - // run the count * tics - while (neededtic > gametic) - { - DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic)); + // run the count * tics + while (neededtic > gametic) + { + DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic)); - G_Ticker((gametic % NEWTICRATERATIO) == 0); - ExtraDataTicker(); - gametic++; - consistancy[gametic%TICQUEUE] = Consistancy(); + G_Ticker((gametic % NEWTICRATERATIO) == 0); + ExtraDataTicker(); + gametic++; + consistancy[gametic%TICQUEUE] = Consistancy(); - // Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame. - if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value) - break; - } + // Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame. + if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value) + break; + } } else { @@ -5983,53 +6007,77 @@ boolean TryRunTics(tic_t realtics) static INT32 pingtimeout[MAXPLAYERS]; +#define PINGKICK_TICQUEUE 2 +#define PINGKICK_LIMIT 1 + static inline void PingUpdate(void) { INT32 i; - boolean laggers[MAXPLAYERS]; - UINT8 numlaggers = 0; - memset(laggers, 0, sizeof(boolean) * MAXPLAYERS); + UINT8 pingkick[MAXPLAYERS]; + UINT8 nonlaggers = 0; + memset(pingkick, 0, sizeof(pingkick)); netbuffer->packettype = PT_PING; //check for ping limit breakage. - if (cv_maxping.value) + //if (cv_maxping.value) -- always check for TICQUEUE overrun { - for (i = 1; i < MAXPLAYERS; i++) + for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value)) + if (!playeringame[i] || P_IsLocalPlayer(&players[i])) // should be P_IsMachineLocalPlayer for DRRR { - if (players[i].jointime > 30 * TICRATE) - laggers[i] = true; - numlaggers++; + pingtimeout[i] = 0; + continue; + } + + if ((maketic + 5) >= nettics[playernode[i]] + (TICQUEUE-(2*TICRATE))) + { + // Anyone who's gobbled most of the TICQUEUE and is likely to halt the server the next few times this runs has to die *right now*. (See also NetUpdate) + pingkick[i] = PINGKICK_TICQUEUE; + } + else if ((cv_maxping.value) + && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value)) + { + if (players[i].jointime > 10 * TICRATE) + { + pingkick[i] = PINGKICK_LIMIT; + } } else - pingtimeout[i] = 0; + { + nonlaggers++; + + // you aren't lagging, but you aren't free yet. In case you'll keep spiking, we just make the timer go back down. (Very unstable net must still get kicked). + if (pingtimeout[i] > 0) + pingtimeout[i]--; + } } //kick lagging players... unless everyone but the server's ping sucks. //in that case, it is probably the server's fault. - if (numlaggers < D_NumPlayers() - 1) + // Always kick TICQUEUE-overrunners, too. { - for (i = 1; i < MAXPLAYERS; i++) + UINT8 minimumkicklevel = (nonlaggers > 0) ? PINGKICK_LIMIT : PINGKICK_TICQUEUE; + for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i] && laggers[i]) + XBOXSTATIC char buf[2]; + + if (!playeringame[i] || pingkick[i] < minimumkicklevel) + continue; + + if (pingkick[i] == PINGKICK_LIMIT) { - pingtimeout[i]++; - if (pingtimeout[i] > cv_pingtimeout.value) // ok your net has been bad for too long, you deserve to die. - { - XBOXSTATIC char buf[2]; - - pingtimeout[i] = 0; - - buf[0] = (char)i; - buf[1] = KICK_MSG_PING_HIGH; - SendNetXCmd(XD_KICK, &buf, 2); - } + // Don't kick on ping alone if we haven't reached our threshold yet. + if (++pingtimeout[i] < cv_pingtimeout.value) + continue; } - else // you aren't lagging, but you aren't free yet. In case you'll keep spiking, we just make the timer go back down. (Very unstable net must still get kicked). - pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1); + + pingtimeout[i] = 0; + + buf[0] = (char)i; + buf[1] = KICK_MSG_PING_HIGH; + SendNetXCmd(XD_KICK, &buf, 2); } } } @@ -6054,9 +6102,12 @@ static inline void PingUpdate(void) if (nodeingame[i]) HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1)); - pingmeasurecount = 1; //Reset count + pingmeasurecount = 0; //Reset count } +#undef PINGKICK_DANGER +#undef PINGKICK_LIMIT + static tic_t gametime = 0; static void UpdatePingTable(void) @@ -6147,6 +6198,9 @@ FILESTAMP SV_FileSendTicker(); } +// If a tree falls in the forest but nobody is around to hear it, does it make a tic? +#define DEDICATEDIDLETIME (10*TICRATE) + void NetUpdate(void) { static tic_t resptime = 0; @@ -6159,6 +6213,55 @@ void NetUpdate(void) if (realtics <= 0) // nothing new to update return; + +#ifdef DEDICATEDIDLETIME + if (server && dedicated && gamestate == GS_LEVEL) + { + static tic_t dedicatedidle = 0; + + for (i = 1; i < MAXNETNODES; ++i) + if (nodeingame[i]) + { + if (dedicatedidle == DEDICATEDIDLETIME) + { + CONS_Printf("DEDICATED: Awakening from idle (Node %d detected...)\n", i); + dedicatedidle = 0; + } + break; + } + + if (i == MAXNETNODES) + { + if (leveltime == 2) + { + // On next tick... + dedicatedidle = DEDICATEDIDLETIME-1; + } + else if (dedicatedidle == DEDICATEDIDLETIME) + { + if (D_GetExistingTextcmd(gametic, 0) || D_GetExistingTextcmd(gametic+1, 0)) + { + CONS_Printf("DEDICATED: Awakening from idle (Netxcmd detected...)\n"); + dedicatedidle = 0; + } + else + { + realtics = 0; + } + } + else if (++dedicatedidle == DEDICATEDIDLETIME) + { + const char *idlereason = "at round start"; + if (leveltime > 3) + idlereason = va("for %d seconds", dedicatedidle/TICRATE); + + CONS_Printf("DEDICATED: No nodes %s, idling...\n", idlereason); + realtics = 0; + } + } + } +#endif + if (realtics > 5) { if (server) @@ -6201,7 +6304,7 @@ FILESTAMP } else { - if (!demo.playback) + if (!demo.playback && realtics > 0) { INT32 counts; @@ -6225,6 +6328,7 @@ FILESTAMP // Do not make tics while resynching if (counts != -666) { + // See also PingUpdate if (maketic + counts >= firstticstosend + TICQUEUE) counts = firstticstosend+TICQUEUE-maketic-1; diff --git a/src/d_main.c b/src/d_main.c index a08ed77d..b2591b94 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -144,7 +144,6 @@ boolean sound_disabled = false; boolean digital_disabled = false; #endif -boolean advancedemo; #ifdef DEBUGFILE INT32 debugload = 0; #endif @@ -815,15 +814,6 @@ void D_SRB2Loop(void) } } -// -// D_AdvanceDemo -// Called after each demo or intro demosequence finishes -// -void D_AdvanceDemo(void) -{ - advancedemo = true; -} - // ========================================================================= // D_SRB2Main // ========================================================================= @@ -883,7 +873,6 @@ void D_StartTitle(void) //demosequence = -1; gametype = GT_RACE; // SRB2kart paused = false; - advancedemo = false; F_StartTitleScreen(); // Reset the palette -- SRB2Kart: actually never mind let's do this in the middle of every fade @@ -1154,6 +1143,8 @@ void D_SRB2Main(void) { const char *userhome = D_Home(); //Alam: path to home + FILE *tmpfile; + char testfile[MAX_WADPATH]; if (!userhome) { @@ -1203,9 +1194,6 @@ void D_SRB2Main(void) // If config isn't writable, tons of behavior will be broken. // Fail loudly before things get confusing! - FILE *tmpfile; - char testfile[MAX_WADPATH]; - snprintf(testfile, sizeof testfile, "%s" PATHSEP "file.tmp", srb2home); testfile[sizeof testfile - 1] = '\0'; @@ -1258,26 +1246,6 @@ void D_SRB2Main(void) if (M_CheckParm("-server") || dedicated) netgame = server = true; - if (M_CheckParm("-warp") && M_IsNextParm()) - { - const char *word = M_GetNextParm(); - char ch; // use this with sscanf to catch non-digits with - if (fastncmp(word, "MAP", 3)) // MAPxx name - pstartmap = M_MapNumber(word[3], word[4]); - else if (sscanf(word, "%d%c", &pstartmap, &ch) != 1) // a plain number - I_Error("Cannot warp to map %s (invalid map name)\n", word); - // Don't check if lump exists just yet because the wads haven't been loaded! - // Just do a basic range check here. - if (pstartmap < 1 || pstartmap > NUMMAPS) - I_Error("Cannot warp to map %d (out of range)\n", pstartmap); - else - { - if (!M_CheckParm("-server")) - G_SetGameModified(true, true); - autostart = true; - } - } - CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n"); Z_Init(); @@ -1457,6 +1425,23 @@ void D_SRB2Main(void) //------------------------------------------------ COMMAND LINE PARAMS + // this must be done after loading gamedata, + // to avoid setting off the corrupted gamedata code in G_LoadGameData if a SOC with custom gamedata is added + // -- Monster Iestyn 20/02/20 + if (M_CheckParm("-warp") && M_IsNextParm()) + { + const char *word = M_GetNextParm(); + pstartmap = G_FindMapByNameOrCode(word, 0); + if (! pstartmap) + I_Error("Cannot find a map remotely named '%s'\n", word); + else + { + if (!M_CheckParm("-server")) + G_SetGameModified(true, true); + autostart = true; + } + } + // Initialize CD-Audio if (M_CheckParm("-usecd") && !dedicated) I_InitCD(); diff --git a/src/d_main.h b/src/d_main.h index d67a5bb4..af74e7a6 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -17,8 +17,6 @@ #include "d_event.h" #include "w_wad.h" // for MAX_WADFILES -extern boolean advancedemo; - // make sure not to write back the config until it's been correctly loaded extern tic_t rendergametic; @@ -34,7 +32,6 @@ void D_SRB2Loop(void) FUNCNORETURN; // D_SRB2Main() // Not a globally visible function, just included for source reference, // calls all startup code, parses command line options. -// If not overrided by user input, calls D_AdvanceDemo. // void D_SRB2Main(void); @@ -51,7 +48,6 @@ const char *D_Home(void); // // BASE LEVEL // -void D_AdvanceDemo(void); void D_StartTitle(void); #endif //__D_MAIN__ diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 8404295e..b36ab33a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2519,27 +2519,67 @@ void D_PickVote(void) SendNetXCmd(XD_PICKVOTE, &buf, 2); } +static char * +ConcatCommandArgv (int start, int end) +{ + char *final; + + size_t size; + + int i; + char *p; + + size = 0; + + for (i = start; i < end; ++i) + { + /* + one space after each argument, but terminating + character on final argument + */ + size += strlen(COM_Argv(i)) + 1; + } + + final = ZZ_Alloc(size); + p = final; + + --end;/* handle the final argument separately */ + for (i = start; i < end; ++i) + { + p += sprintf(p, "%s ", COM_Argv(i)); + } + /* at this point "end" is actually the last argument's position */ + strcpy(p, COM_Argv(end)); + + return final; +} + +// // Warp to map code. // Called either from map console command, or idclev cheat. // +// Largely rewritten by James. +// static void Command_Map_f(void) { - const char *mapname; - size_t i; - INT32 newmapnum; - boolean newresetplayers, newencoremode; - INT32 newgametype = gametype; + size_t first_option; + size_t option_force; + size_t option_gametype; + size_t option_encore; + const char *gametypename; + boolean newresetplayers; - // max length of command: map map03 -gametype race -noresetplayers -force -encore - // 1 2 3 4 5 6 7 - // = 8 arg max - // i don't know whether this is intrinsic to the system or just someone being weird but - // "noresetplayers" is pretty useless for kart if it turns out this is too close to the limit - if (COM_Argc() < 2 || COM_Argc() > 8) - { - CONS_Printf(M_GetText("map [-gametype [-force]: warp to map\n")); - return; - } + boolean mustmodifygame; + + INT32 newmapnum; + + char * mapname; + char *realmapname = NULL; + + INT32 newgametype = gametype; + boolean newencoremode = cv_kartencore.value; + + INT32 d; if (client && !IsPlayerAdmin(consoleplayer)) { @@ -2547,99 +2587,129 @@ static void Command_Map_f(void) return; } - // internal wad lump always: map command doesn't support external files as in doom legacy - if (W_CheckNumForName(COM_Argv(1)) == LUMPERROR) + option_force = COM_CheckPartialParm("-f"); + option_gametype = COM_CheckPartialParm("-g"); + option_encore = COM_CheckPartialParm("-e"); + newresetplayers = ! COM_CheckParm("-noresetplayers"); + + mustmodifygame = !( netgame || multiplayer || majormods ); + + if (mustmodifygame && !option_force) { - CONS_Alert(CONS_ERROR, M_GetText("Internal game level '%s' not found\n"), COM_Argv(1)); + /* May want to be more descriptive? */ + CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); return; } - if (!(netgame || multiplayer) && !majormods) - { - if (COM_CheckParm("-force")) - { - G_SetGameModified(false, true); - } - else - { - CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); - return; - } - } - - newresetplayers = !COM_CheckParm("-noresetplayers"); - if (!newresetplayers && !cv_debug) { CONS_Printf(M_GetText("DEVMODE must be enabled.\n")); return; } - mapname = COM_Argv(1); - if (strlen(mapname) != 5 - || (newmapnum = M_MapNumber(mapname[3], mapname[4])) == 0) + if (option_gametype) { - CONS_Alert(CONS_ERROR, M_GetText("Invalid level name %s\n"), mapname); + if (!multiplayer) + { + CONS_Printf(M_GetText( + "You can't switch gametypes in single player!\n")); + return; + } + else if (COM_Argc() < option_gametype + 2)/* no argument after? */ + { + CONS_Alert(CONS_ERROR, + "No gametype name follows parameter '%s'.\n", + COM_Argv(option_gametype)); + return; + } + } + + if (!( first_option = COM_FirstOption() )) + first_option = COM_Argc(); + + if (first_option < 2) + { + /* I'm going over the fucking lines and I DON'T CAREEEEE */ + CONS_Printf("map [-gametype ] [-encore] [-force]:\n"); + CONS_Printf(M_GetText( + "Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n" + "All parameters are case-insensitive and may be abbreviated.\n")); return; } + mapname = ConcatCommandArgv(1, first_option); + + newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); + + if (newmapnum == 0) + { + CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); + return; + } + + if (mustmodifygame && option_force) + { + G_SetGameModified(false, true); + } + // new gametype value // use current one by default - i = COM_CheckParm("-gametype"); - if (i) + if (option_gametype) { - if (!multiplayer) - { - CONS_Printf(M_GetText("You can't switch gametypes in single player!\n")); - return; - } + gametypename = COM_Argv(option_gametype + 1); + + newgametype = G_GetGametypeByName(gametypename); - newgametype = G_GetGametypeByName(COM_Argv(i+1)); if (newgametype == -1) // reached end of the list with no match { - INT32 j = atoi(COM_Argv(i+1)); // assume they gave us a gametype number, which is okay too - if (j >= 0 && j < NUMGAMETYPES) - newgametype = (INT16)j; + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) + { + d = atoi(gametypename); + if (d >= 0 && d < NUMGAMETYPES) + newgametype = d; + else + { + CONS_Alert(CONS_ERROR, + "Gametype number %d is out of range. Use a number between" + " 0 and %d inclusive. ...Or just use the name. :v\n", + d, + NUMGAMETYPES-1); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a gametype.\n", + gametypename); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } } - // new encoremode value - // use cvar by default - - newencoremode = (boolean)cv_kartencore.value; - - if (COM_CheckParm("-encore")) - { - if (!M_SecretUnlocked(SECRET_ENCORE) && !newencoremode) - { - CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); - return; - } - newencoremode = !newencoremode; - } - - if (!(i = COM_CheckParm("-force")) && newgametype == gametype) // SRB2Kart + if (!option_force && newgametype == gametype) // SRB2Kart newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cv_debug || i || cv_skipmapcheck.value) - ; // The player wants us to trek on anyway. Do so. + if (cv_debug || option_force || cv_skipmapcheck.value) + fromlevelselect = false; // The player wants us to trek on anyway. Do so. // G_TOLFlag handles both multiplayer gametype and ignores it for !multiplayer - // Alternatively, bail if the map header is completely missing anyway. - else if (!mapheaderinfo[newmapnum-1] - || !(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype))) + else { - char gametypestring[32] = "Single Player"; - - if (multiplayer) + if (!(mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype))) { - if (newgametype >= 0 && newgametype < NUMGAMETYPES - && Gametype_Names[newgametype]) - strcpy(gametypestring, Gametype_Names[newgametype]); + CONS_Alert(CONS_WARNING, M_GetText("Course %s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), + (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); + Z_Free(realmapname); + Z_Free(mapname); + return; } - - CONS_Alert(CONS_WARNING, M_GetText("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring); - return; } // Prevent warping to locked levels @@ -2649,11 +2719,25 @@ static void Command_Map_f(void) if (!dedicated && M_MapLocked(newmapnum)) { CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n")); + Z_Free(realmapname); + Z_Free(mapname); return; } + if (option_encore) + { + newencoremode = ! newencoremode; + if (! M_SecretUnlocked(SECRET_ENCORE) && newencoremode) + { + CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + return; + } + } + fromlevelselect = false; D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); + + Z_Free(realmapname); } /** Receives a map command and changes the map. @@ -2702,7 +2786,9 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) lastgametype = gametype; gametype = READUINT8(*cp); - if (gametype != lastgametype) + if (gametype < 0 || gametype >= NUMGAMETYPES) + gametype = lastgametype; + else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype if (!G_RaceGametype()) diff --git a/src/d_netfil.c b/src/d_netfil.c index 8f46c045..663bcc86 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -299,6 +299,9 @@ boolean CL_CheckDownloadable(void) return false; } +// The following was written and, against all odds, works. +#define MORELEGACYDOWNLOADER + /** Sends requests for files in the ::fileneeded table with a status of * ::FS_NOTFOUND. * @@ -311,42 +314,132 @@ boolean CL_SendRequestFile(void) char *p; INT32 i; INT64 totalfreespaceneeded = 0, availablefreespace; + INT32 skippedafile = -1; +#ifdef MORELEGACYDOWNLOADER + boolean firstloop = true; +#endif #ifdef PARANOIA if (M_CheckParm("-nodownload")) - I_Error("Attempted to download files in -nodownload mode"); + { + CONS_Printf("Direct download - Attempted to download files in -nodownload mode"); + return false; + } +#endif for (i = 0; i < fileneedednum; i++) + { if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2)) { - I_Error("Attempted to download files that were not sendable"); + CONS_Printf("Direct download - attempted to download files that were not sendable\n"); + return false; } + + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK)) + { + // Error check for the first time around. + totalfreespaceneeded += fileneeded[i].totalsize; + } + } + + I_GetDiskFreeSpace(&availablefreespace); + if (totalfreespaceneeded > availablefreespace) + { + CONS_Printf("Direct download -\n" + " To play on this server you must download %s KB,\n" + " but you have only %s KB free space on this drive\n", + sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10))); + return false; + } + +#ifdef MORELEGACYDOWNLOADER +tryagain: + skippedafile = -1; +#endif + +#ifdef VERBOSEREQUESTFILE + CONS_Printf("Preparing packet\n"); #endif 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].status == FS_FALLBACK)) { - totalfreespaceneeded += fileneeded[i].totalsize; + // Pre-prepare. + size_t checklen; nameonly(fileneeded[i].filename); + + // Figure out if we'd overrun our buffer. + checklen = strlen(fileneeded[i].filename)+2; // plus the fileid (and terminator, in case this is last) + if ((UINT8 *)(p + checklen) >= netbuffer->u.textcmd + MAXTEXTCMD) + { + skippedafile = i; + // we might have a shorter file that can fit in the remaining space, and file ID permits out-of-order data + continue; + } + + // Now write. WRITEUINT8(p, i); // fileid WRITESTRINGN(p, fileneeded[i].filename, MAX_WADPATH); + +#ifdef VERBOSEREQUESTFILE + CONS_Printf(" file \"%s\" (id %d)\n", i, fileneeded[i].filename); +#endif + // put it in download dir strcatbf(fileneeded[i].filename, downloaddir, "/"); fileneeded[i].status = FS_REQUESTED; } - WRITEUINT8(p, 0xFF); - I_GetDiskFreeSpace(&availablefreespace); - if (totalfreespaceneeded > availablefreespace) - I_Error("To play on this server you must download %s KB,\n" - "but you have only %s KB free space on this drive\n", - sizeu1((size_t)(totalfreespaceneeded>>10)), sizeu2((size_t)(availablefreespace>>10))); + } - // prepare to download - I_mkdir(downloaddir, 0755); - return HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd); +#ifdef MORELEGACYDOWNLOADER + if (firstloop) +#else + // If we're not trying extralong legacy download requests, gotta bail. + if (skippedafile != -1) + { + CONS_Printf("Direct download - missing files are as follows:\n"); + for (i = 0; i < fileneedednum; i++) + { + if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD || fileneeded[i].status == FS_FALLBACK || fileneeded[i].status == FS_REQUESTED)) // FS_REQUESTED added + CONS_Printf(" %s\n", fileneeded[i].filename); + } + return false; + } +#endif + I_mkdir(downloaddir, 0755); + + // Couldn't fit a single one in? + if (p == (char *)netbuffer->u.textcmd) + { + CONS_Printf("Direct download - fileneeded name for %s (fileneeded[%d]) too long??\n", (skippedafile != -1 ? fileneeded[skippedafile].filename : NULL), skippedafile); + return false; + } + + WRITEUINT8(p, 0xFF); // terminator + if (!HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd)) + { + CONS_Printf("Direct download - unable to send packet.\n"); + return false; + } + +#ifdef MORELEGACYDOWNLOADER + if (skippedafile != -1) + { + firstloop = false; + goto tryagain; + } +#endif + +#ifdef VERBOSEREQUESTFILE + CONS_Printf("Returning true\n"); +#endif + + return true; } // get request filepak and put it on the send queue @@ -356,16 +449,18 @@ boolean Got_RequestFilePak(INT32 node) char wad[MAX_WADPATH+1]; UINT8 *p = netbuffer->u.textcmd; UINT8 id; - while (p < netbuffer->u.textcmd + MAXTEXTCMD-1) // Don't allow hacked client to overflow + while (p < netbuffer->u.textcmd + MAXTEXTCMD) // Don't allow hacked client to overflow { id = READUINT8(p); if (id == 0xFF) break; READSTRINGN(p, wad, MAX_WADPATH); - if (!SV_SendFile(node, wad, id)) + if (p >= netbuffer->u.textcmd + MAXTEXTCMD || !SV_SendFile(node, wad, id)) { + if (cv_noticedownload.value) + CONS_Printf("Bad PT_REQUESTFILE from node %d!\n", node); SV_AbortSendFiles(node); - return false; // don't read the rest of the files + return false; // don't read any more } } return true; // no problems with any files @@ -538,7 +633,7 @@ static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid) char wadfilename[MAX_WADPATH]; if (cv_noticedownload.value) - CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node)); + CONS_Printf("Sending file \"%s\" (id %d) to node %d (%s)\n", filename, fileid, node, I_GetNodeAddress(node)); // Find the last file in the list and set a pointer to its "next" field q = &transfer[node].txlist; @@ -664,7 +759,7 @@ static void SV_EndFileSend(INT32 node) { case SF_FILE: // It's a file, close it and free its filename if (cv_noticedownload.value) - CONS_Printf("Ending file transfer for node %d\n", node); + CONS_Printf("Ending file transfer (id %d) for node %d\n", p->fileid, node); if (transfer[node].currentfile) fclose(transfer[node].currentfile); free(p->id.filename); @@ -1156,6 +1251,7 @@ void CURLGetFile(void) int msgs_left; /* how many messages are left */ const char *easy_handle_error; long response_code = 0; + static char *filename; if (curl_runninghandles) { @@ -1180,6 +1276,8 @@ void CURLGetFile(void) { e = m->easy_handle; easyres = m->data.result; + filename = Z_StrDup(curl_realname); + nameonly(filename); if (easyres != CURLE_OK) { if (easyres == CURLE_HTTP_RETURNED_ERROR) @@ -1192,21 +1290,30 @@ void CURLGetFile(void) curl_failedwebdownload = true; fclose(curl_curfile->file); remove(curl_curfile->filename); - curl_curfile->file = NULL; - //nameonly(curl_curfile->filename); - nameonly(curl_realname); - CONS_Printf(M_GetText("Failed to download %s (%s)\n"), curl_realname, easy_handle_error); + CONS_Printf(M_GetText("Failed to download %s (%s)\n"), filename, easy_handle_error); } else { - nameonly(curl_realname); - CONS_Printf(M_GetText("Finished downloading %s\n"), curl_realname); - downloadcompletednum++; - downloadcompletedsize += curl_curfile->totalsize; - curl_curfile->status = FS_FOUND; fclose(curl_curfile->file); + + if (checkfilemd5(curl_curfile->filename, curl_curfile->md5sum) == FS_MD5SUMBAD) + { + CONS_Alert(CONS_ERROR, M_GetText("HTTP Download of %s finished but is corrupt or has been modified\n"), filename); + curl_curfile->status = FS_FALLBACK; + curl_failedwebdownload = true; + } + else + { + CONS_Printf(M_GetText("Finished HTTP download of %s\n"), filename); + downloadcompletednum++; + downloadcompletedsize += curl_curfile->totalsize; + curl_curfile->status = FS_FOUND; + } } + + Z_Free(filename); + curl_curfile->file = NULL; curl_running = false; curl_transfers--; curl_multi_remove_handle(multi_handle, e); diff --git a/src/doomdef.h b/src/doomdef.h index 012b4926..5ed1e9ed 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -148,9 +148,9 @@ extern char logfilename[1024]; // we use comprevision and compbranch instead. #else #define VERSION 1 // Game version -#define SUBVERSION 4 // more precise version number -#define VERSIONSTRING "v1.5" -#define VERSIONSTRINGW L"v1.5" +#define SUBVERSION 6 // more precise version number +#define VERSIONSTRING "v1.6" +#define VERSIONSTRINGW L"v1.6" // Hey! If you change this, add 1 to the MODVERSION below! Otherwise we can't force updates! // And change CMakeLists.txt (not src/, but in root), for CMake users! // AND appveyor.yml, for the build bots! @@ -532,6 +532,7 @@ extern boolean capslock; // if we ever make our alloc stuff... #define ZZ_Alloc(x) Z_Malloc(x, PU_STATIC, NULL) +#define ZZ_Calloc(x) Z_Calloc(x, PU_STATIC, NULL) // i_system.c, replace getchar() once the keyboard has been appropriated INT32 I_GetKey(void); diff --git a/src/doomtype.h b/src/doomtype.h index ad96b979..4660e876 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -139,6 +139,9 @@ typedef long ssize_t; #define strlwr _strlwr #endif +char *strcasestr(const char *in, const char *what); +#define stristr strcasestr + #if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap #define true 1 #define false 0 diff --git a/src/f_finale.c b/src/f_finale.c index 76152027..1b5310e0 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -431,10 +431,12 @@ static const char *credits[] = { "\"JugadorXEI\"", "\"Kimberly\"", "\"Lighto97\"", + "\"Lonsfor\"", "\"mazmazz\"", "\"minenice\"", "\"Shuffle\"", "\"Snu\"", + "\"X.organic\"", "", "\1Lead Artists", "Desmond \"Blade\" DesJardins", @@ -512,6 +514,7 @@ static const char *credits[] = { "", "\1Testing", "RKH License holders", + "The KCS", "\"CyberIF\"", "\"Dani\"", "Karol \"Fooruman\" D""\x1E""browski", // DÄ…browski, accents in srb2 :ytho: @@ -559,7 +562,7 @@ static struct { // This Tyler52 gag is troublesome // Alignment should be ((spaces+1 * 100) + (headers+1 * 38) + (lines * 15)) // Current max image spacing: (216*17) - {112, (16*100)+(19*38)+(100*15), "TYLER52", SKINCOLOR_NONE}, + {112, (16*100)+(19*38)+(103*15), "TYLER52", SKINCOLOR_NONE}, {0, 0, NULL, SKINCOLOR_NONE} }; diff --git a/src/g_game.c b/src/g_game.c index 972d3c18..c6b633ce 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3339,7 +3339,9 @@ void G_DoReborn(INT32 playernum) // respawn at the start mobj_t *oldmo = NULL; - if (player->starpostnum || ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) && player->laps)) // SRB2kart + if (player->spectator) + ; + else if (player->starpostnum || ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) && player->laps)) // SRB2kart starpost = true; // first dissasociate the corpse @@ -4864,6 +4866,242 @@ char *G_BuildMapTitle(INT32 mapnum) return title; } +static void measurekeywords(mapsearchfreq_t *fr, + struct searchdim **dimp, UINT8 *cuntp, + const char *s, const char *q, boolean wanttable) +{ + char *qp; + char *sp; + if (wanttable) + (*dimp) = Z_Realloc((*dimp), 255 * sizeof (struct searchdim), + PU_STATIC, NULL); + for (qp = strtok(va("%s", q), " "); + qp && fr->total < 255; + qp = strtok(0, " ")) + { + if (( sp = strcasestr(s, qp) )) + { + if (wanttable) + { + (*dimp)[(*cuntp)].pos = sp - s; + (*dimp)[(*cuntp)].siz = strlen(qp); + } + (*cuntp)++; + fr->total++; + } + } + if (wanttable) + (*dimp) = Z_Realloc((*dimp), (*cuntp) * sizeof (struct searchdim), + PU_STATIC, NULL); +} + +static void writesimplefreq(mapsearchfreq_t *fr, INT32 *frc, + INT32 mapnum, UINT8 pos, UINT8 siz) +{ + fr[(*frc)].mapnum = mapnum; + fr[(*frc)].matchd = ZZ_Alloc(sizeof (struct searchdim)); + fr[(*frc)].matchd[0].pos = pos; + fr[(*frc)].matchd[0].siz = siz; + fr[(*frc)].matchc = 1; + fr[(*frc)].total = 1; + (*frc)++; +} + +INT32 G_FindMap(const char *mapname, char **foundmapnamep, + mapsearchfreq_t **freqp, INT32 *freqcp) +{ + INT32 newmapnum = 0; + INT32 mapnum; + INT32 apromapnum = 0; + + size_t mapnamelen; + char *realmapname = NULL; + char *newmapname = NULL; + char *apromapname = NULL; + char *aprop = NULL; + + mapsearchfreq_t *freq; + boolean wanttable; + INT32 freqc; + UINT8 frequ; + + INT32 i; + + mapnamelen = strlen(mapname); + + /* Count available maps; how ugly. */ + for (i = 0, freqc = 0; i < NUMMAPS; ++i) + { + if (mapheaderinfo[i]) + freqc++; + } + + freq = ZZ_Calloc(freqc * sizeof (mapsearchfreq_t)); + + wanttable = !!( freqp ); + + freqc = 0; + for (i = 0, mapnum = 1; i < NUMMAPS; ++i, ++mapnum) + if (mapheaderinfo[i]) + { + if (!( realmapname = G_BuildMapTitle(mapnum) )) + continue; + + aprop = realmapname; + + /* Now that we found a perfect match no need to fucking guess. */ + if (strnicmp(realmapname, mapname, mapnamelen) == 0) + { + if (wanttable) + { + writesimplefreq(freq, &freqc, mapnum, 0, mapnamelen); + } + if (newmapnum == 0) + { + newmapnum = mapnum; + newmapname = realmapname; + realmapname = 0; + Z_Free(apromapname); + if (!wanttable) + break; + } + } + else + if (apromapnum == 0 || wanttable) + { + /* LEVEL 1--match keywords verbatim */ + if (( aprop = strcasestr(realmapname, mapname) )) + { + if (wanttable) + { + writesimplefreq(freq, &freqc, + mapnum, aprop - realmapname, mapnamelen); + } + if (apromapnum == 0) + { + apromapnum = mapnum; + apromapname = realmapname; + realmapname = 0; + } + } + else/* ...match individual keywords */ + { + freq[freqc].mapnum = mapnum; + measurekeywords(&freq[freqc], + &freq[freqc].matchd, &freq[freqc].matchc, + realmapname, mapname, wanttable); + if (freq[freqc].total) + freqc++; + } + } + + Z_Free(realmapname);/* leftover old name */ + } + + if (newmapnum == 0)/* no perfect match--try a substring */ + { + newmapnum = apromapnum; + newmapname = apromapname; + } + + if (newmapnum == 0)/* calculate most queries met! */ + { + frequ = 0; + for (i = 0; i < freqc; ++i) + { + if (freq[i].total > frequ) + { + frequ = freq[i].total; + newmapnum = freq[i].mapnum; + } + } + if (newmapnum) + { + newmapname = G_BuildMapTitle(newmapnum); + } + } + + if (freqp) + (*freqp) = freq; + else + Z_Free(freq); + + if (freqcp) + (*freqcp) = freqc; + + if (foundmapnamep) + (*foundmapnamep) = newmapname; + else + Z_Free(newmapname); + + return newmapnum; +} + +void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc) +{ + INT32 i; + for (i = 0; i < freqc; ++i) + { + Z_Free(freq[i].matchd); + } + Z_Free(freq); +} + +INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep) +{ + boolean usemapcode = false; + + INT32 newmapnum; + + size_t mapnamelen; + + char *p; + + mapnamelen = strlen(mapname); + + if (mapnamelen == 2)/* maybe two digit code */ + { + if (( newmapnum = M_MapNumber(mapname[0], mapname[1]) )) + usemapcode = true; + } + else if (mapnamelen == 5 && strnicmp(mapname, "MAP", 3) == 0) + { + if (( newmapnum = M_MapNumber(mapname[3], mapname[4]) )) + usemapcode = true; + } + + if (!usemapcode) + { + /* Now detect map number in base 10, which no one asked for. */ + newmapnum = strtol(mapname, &p, 10); + if (*p == '\0')/* we got it */ + { + if (newmapnum < 1 || newmapnum > NUMMAPS) + { + CONS_Alert(CONS_ERROR, M_GetText("Invalid map number %d.\n"), newmapnum); + return 0; + } + usemapcode = true; + } + else + { + newmapnum = G_FindMap(mapname, realmapnamep, NULL, NULL); + } + } + + if (usemapcode) + { + /* we can't check mapheaderinfo for this hahahaha */ + if (W_CheckNumForName(G_BuildMapName(newmapnum)) == LUMPERROR) + return 0; + + if (realmapnamep) + (*realmapnamep) = G_BuildMapTitle(newmapnum); + } + + return newmapnum; +} + // // DEMO RECORDING // @@ -6496,7 +6734,7 @@ void G_BeginRecording(void) demoflags |= DF_ENCORE; #ifdef HAVE_BLUA - if (!modeattacking) // Ghosts don't read luavars, and you shouldn't ever need to save Lua in replays, you doof! + if (!modeattacking && gL) // Ghosts don't read luavars, and you shouldn't ever need to save Lua in replays, you doof! // SERIOUSLY THOUGH WHY WOULD YOU LOAD HOSTMOD AND RECORD A GHOST WITH IT !???? demoflags |= DF_LUAVARS; #endif @@ -6531,7 +6769,7 @@ void G_BeginRecording(void) if (wadfiles[i]->important) { nameonly(( filename = va("%s", wadfiles[i]->filename) )); - WRITESTRINGN(demo_p, filename, 64); + WRITESTRINGL(demo_p, filename, MAX_WADPATH); WRITEMEM(demo_p, wadfiles[i]->md5sum, 16); totalfiles++; @@ -6605,7 +6843,7 @@ void G_BeginRecording(void) #ifdef HAVE_BLUA // player lua vars, always saved even if empty... Unless it's record attack. - if (!modeattacking) + if (demoflags & DF_LUAVARS) LUA_ArchiveDemo(); #endif @@ -6776,10 +7014,13 @@ static void G_LoadDemoExtraFiles(UINT8 **pp) } else { - P_AddWadFile(filename); + P_PartialAddWadFile(filename); } } } + + if (P_PartialAddGetStage() >= 0) + P_MultiSetupWadFiles(true); // in case any partial adds were done } static void G_SkipDemoExtraFiles(UINT8 **pp) @@ -8358,7 +8599,7 @@ boolean G_CheckDemoStatus(void) CONS_Printf(M_GetText("timed %u gametics in %d realtics\n%f seconds, %f avg fps\n"), leveltime,demotime,f1/TICRATE,f2/f1); if (restorecv_vidwait != cv_vidwait.value) CV_SetValue(&cv_vidwait, restorecv_vidwait); - D_AdvanceDemo(); + D_StartTitle(); return true; } @@ -8376,7 +8617,7 @@ boolean G_CheckDemoStatus(void) if (modeattacking) M_EndModeAttackRun(); else - D_AdvanceDemo(); + D_StartTitle(); } return true; diff --git a/src/g_game.h b/src/g_game.h index 9f1b8c5c..b258ffd7 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -173,6 +173,30 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, boolean skipprecutscene); char *G_BuildMapTitle(INT32 mapnum); +struct searchdim +{ + UINT8 pos; + UINT8 siz; +}; + +typedef struct +{ + INT16 mapnum; + UINT8 matchc; + struct searchdim *matchd;/* offset that a pattern was matched */ + UINT8 keywhc; + struct searchdim *keywhd;/* ...in KEYWORD */ + UINT8 total;/* total hits */ +} +mapsearchfreq_t; + +INT32 G_FindMap(const char *query, char **foundmapnamep, + mapsearchfreq_t **freqp, INT32 *freqc); +void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc); + +/* Match map name by search + 2 digit map code or map number. */ +INT32 G_FindMapByNameOrCode(const char *query, char **foundmapnamep); + // XMOD spawning mapthing_t *G_FindCTFStart(INT32 playernum); mapthing_t *G_FindMatchStart(INT32 playernum); diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 397ef637..86dd7c34 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3019,12 +3019,44 @@ static void HWR_RotateSpritePolyToAim(gr_vissprite_t *spr, FOutVector *wallVerts { if (cv_grspritebillboarding.value && spr && spr->mobj && !(spr->mobj->frame & FF_PAPERSPRITE) && wallVerts) { - float basey = FIXED_TO_FLOAT(spr->mobj->z); - float lowy = wallVerts[0].y; + // uncapped/interpolation + interpmobjstate_t interp = {0}; + float basey, lowy; + + // do interpolation + if (R_UsingFrameInterpolation() && !paused) + { + if (spr->precip) + { + R_InterpolatePrecipMobjState((precipmobj_t *)spr->mobj, rendertimefrac, &interp); + } + else + { + R_InterpolateMobjState(spr->mobj, rendertimefrac, &interp); + } + } + else + { + if (spr->precip) + { + R_InterpolatePrecipMobjState((precipmobj_t *)spr->mobj, FRACUNIT, &interp); + } + else + { + R_InterpolateMobjState(spr->mobj, FRACUNIT, &interp); + } + } + if (P_MobjFlip(spr->mobj) == -1) { - basey = FIXED_TO_FLOAT(spr->mobj->z + spr->mobj->height); + basey = FIXED_TO_FLOAT(interp.z + spr->mobj->height); } + else + { + basey = FIXED_TO_FLOAT(interp.z); + } + lowy = wallVerts[0].y; + // Rotate sprites to fully billboard with the camera // X, Y, AND Z need to be manipulated for the polys to rotate around the // origin, because of how the origin setting works I believe that should diff --git a/src/k_kart.c b/src/k_kart.c index d040b8d2..7cafa491 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2166,7 +2166,7 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflicto player->kartstuff[k_spinouttype] = type; - if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout + if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout, type 2 is spb { // At spinout, player speed is increased to 1/4 their regular speed, moving them forward if (player->speed < K_GetKartSpeed(player, true)/4) @@ -2332,6 +2332,7 @@ void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor) void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A bit of a hack, we just throw the player up higher here and extend their spinout timer { + fixed_t upgoer; UINT8 scoremultiply = 1; #ifdef HAVE_BLUA boolean force = false; // Used to check if Lua ShouldExplode should get us damaged reguardless of flashtics or heck knows what. @@ -2378,9 +2379,6 @@ void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A b if (source && source != player->mo && source->player) K_PlayHitEmSound(source); - player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! - player->mo->momx = player->mo->momy = 0; - player->kartstuff[k_sneakertimer] = 0; player->kartstuff[k_driftboost] = 0; @@ -2428,19 +2426,34 @@ void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A b K_CheckBumpers(); } - player->kartstuff[k_spinouttype] = 1; - player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2; - player->powers[pw_flashing] = K_GetKartFlashing(player); + upgoer = (18*mapobjectscale*P_MobjFlip(player->mo)); + if (player->mo->eflags & MFE_UNDERWATER) + upgoer = (117 * upgoer) / 200; + +#define EXPLODESPINTIME ((3*TICRATE/2)+2) + if (inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1) { - player->kartstuff[k_spinouttimer] = ((5*player->kartstuff[k_spinouttimer])/2)+1; - player->mo->momz *= 2; + player->kartstuff[k_spinouttype] = 2; + player->kartstuff[k_spinouttimer] = (5*EXPLODESPINTIME/2)+1; + player->mo->momz = upgoer*2; } + else + { + if (player->kartstuff[k_spinouttype] == 2) + { + // We're on to your tricks. But let's not STOP the tech - let's make you have to work extra hard for it to pay off. + indirectitemcooldown = 0; + } + player->kartstuff[k_spinouttype] = 1; + player->kartstuff[k_spinouttimer] = EXPLODESPINTIME; + player->mo->momz = upgoer; + } + player->mo->momx = player->mo->momy = 0; - if (player->mo->eflags & MFE_UNDERWATER) - player->mo->momz = (117 * player->mo->momz) / 200; +#undef SPINTIME if (player->mo->state != &states[S_KART_SPIN]) P_SetPlayerMobjState(player->mo, S_KART_SPIN); @@ -4748,7 +4761,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->kartstuff[k_spinouttimer]) { - if ((P_IsObjectOnGround(player->mo) || player->kartstuff[k_spinouttype] == 1) + if ((P_IsObjectOnGround(player->mo) || ((player->kartstuff[k_spinouttype]+1)/2 == 1)) // spinouttype 1 and 2 - explosion and spb && (player->kartstuff[k_sneakertimer] == 0)) { player->kartstuff[k_spinouttimer]--; diff --git a/src/m_menu.c b/src/m_menu.c index f0170301..4bdc6ee0 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -5603,6 +5603,18 @@ static void DrawReplayHutReplayInfo(void) if (demolist[dir_on[menudepthleft]].gametype == GT_RACE) { V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); + } + else + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); + } + + if (demolist[dir_on[menudepthleft]].standings[0].timeorscore == (UINT32_MAX-1)) + { + V_DrawThinString(x+32, y+40-1, V_SNAPTOTOP, "NO CONTEST"); + } + else if (demolist[dir_on[menudepthleft]].gametype == GT_RACE) + { V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", G_TicsToMinutes(demolist[dir_on[menudepthleft]].standings[0].timeorscore, true), G_TicsToSeconds(demolist[dir_on[menudepthleft]].standings[0].timeorscore), @@ -5611,12 +5623,11 @@ static void DrawReplayHutReplayInfo(void) } else { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demolist[dir_on[menudepthleft]].standings[0].timeorscore)); } // Character face! - if (W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[0].skin].facewant) != LUMPERROR) + if (demolist[dir_on[menudepthleft]].standings[0].skin < numskins && W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[0].skin].facewant) != LUMPERROR) { patch = facewantprefix[demolist[dir_on[menudepthleft]].standings[0].skin]; colormap = R_GetTranslationColormap( @@ -5816,7 +5827,7 @@ static void M_DrawReplayStartMenu(void) V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demolist[dir_on[menudepthleft]].standings[i].timeorscore)); // Character face! - if (W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[i].skin].facerank) != LUMPERROR) + if (demolist[dir_on[menudepthleft]].standings[i].skin < numskins && W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[i].skin].facerank) != LUMPERROR) { patch = facerankprefix[demolist[dir_on[menudepthleft]].standings[i].skin]; colormap = R_GetTranslationColormap( diff --git a/src/p_maputl.c b/src/p_maputl.c index 760e45c4..17df33a9 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -1034,7 +1034,10 @@ boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *)) { P_SetTarget(&bnext, mobj->bnext); // We want to note our reference to bnext here incase it is MF_NOTHINK and gets removed! if (!func(mobj)) + { + P_SetTarget(&bnext, NULL); return false; + } if (P_MobjWasRemoved(tmthing) // func just popped our tmthing, cannot continue. || (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue. { @@ -1042,6 +1045,7 @@ boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *)) return true; } } + P_SetTarget(&bnext, NULL); return true; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 530dd37d..7978d0f3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9893,6 +9893,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) break; } + if (!(mobj->flags & MF_NOTHINK)) + P_AddThinker(&mobj->thinker); // Needs to come before the shadow spawn, or else the shadow's reference gets forgotten + switch (mobj->type) { case MT_PLAYER: @@ -9916,9 +9919,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) break; } - if (!(mobj->flags & MF_NOTHINK)) - P_AddThinker(&mobj->thinker); - // Call action functions when the state is set if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC)) { @@ -10298,21 +10298,41 @@ void P_RemovePrecipMobj(precipmobj_t *mobj) void P_RemoveSavegameMobj(mobj_t *mobj) { // unlink from sector and block lists - P_UnsetThingPosition(mobj); - - // Remove touching_sectorlist from mobj. - if (sector_list) + if (((thinker_t *)mobj)->function.acp1 == (actionf_p1)P_NullPrecipThinker) { - P_DelSeclist(sector_list); - sector_list = NULL; + P_UnsetPrecipThingPosition((precipmobj_t *)mobj); + + if (precipsector_list) + { + P_DelPrecipSeclist(precipsector_list); + precipsector_list = NULL; + } + } + else + { + // unlink from sector and block lists + P_UnsetThingPosition(mobj); + + // Remove touching_sectorlist from mobj. + if (sector_list) + { + P_DelSeclist(sector_list); + sector_list = NULL; + } } // stop any playing sound S_StopSound(mobj); + R_RemoveMobjInterpolator(mobj); // free block - P_RemoveThinker((thinker_t *)mobj); - R_RemoveMobjInterpolator(mobj); + // Here we use the same code as R_RemoveThinkerDelayed, but without reference counting (we're removing everything so it shouldn't matter) and without touching currentthinker since we aren't in P_RunThinkers + { + thinker_t *thinker = (thinker_t *)mobj; + thinker_t *next = thinker->next; + (next->prev = thinker->prev)->next = next; + Z_Free(thinker); + } } static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}}; diff --git a/src/p_saveg.c b/src/p_saveg.c index f79b3f57..f553ddf6 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1564,6 +1564,7 @@ static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type) WRITEINT32(save_p, ht->polyObjNum); WRITEINT32(save_p, ht->speed); WRITEINT32(save_p, ht->distance); + WRITEUINT8(save_p, ht->turnobjs); } // @@ -2166,6 +2167,14 @@ static void LoadMobjThinker(actionf_p1 thinker) mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight; } + if (mobj->type == MT_SKYBOX) + { + if (mobj->spawnpoint->options & MTF_OBJECTSPECIAL) + skyboxmo[1] = mobj; + else + skyboxmo[0] = mobj; + } + P_AddThinker(&mobj->thinker); if (diff2 & MD2_WAYPOINTCAP) @@ -2525,6 +2534,7 @@ static inline void LoadPolyrotatetThinker(actionf_p1 thinker) ht->polyObjNum = READINT32(save_p); ht->speed = READINT32(save_p); ht->distance = READINT32(save_p); + ht->turnobjs = READUINT8(save_p); P_AddThinker(&ht->thinker); } @@ -2666,10 +2676,14 @@ static void P_NetUnArchiveThinkers(void) { next = currentthinker->next; - if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker) + if (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker || currentthinker->function.acp1 == (actionf_p1)P_NullPrecipThinker) P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it else + { + (next->prev = currentthinker->prev)->next = next; + R_DestroyLevelInterpolators(currentthinker); Z_Free(currentthinker); + } } // we don't want the removed mobjs to come back diff --git a/src/p_setup.c b/src/p_setup.c index 54504f4b..5263d1b3 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -3071,6 +3071,10 @@ boolean P_SetupLevel(boolean skipprecip) (fileinfo + ML_REJECT)->size, (fileinfo + ML_REJECT)->name); } + else + { + rejectmatrix = NULL; + } // Important: take care of the ordering of the next functions. if (!loadedbm) @@ -3130,14 +3134,14 @@ boolean P_SetupLevel(boolean skipprecip) if (!playerstarts[numcoopstarts]) break; + globalweather = mapheaderinfo[gamemap-1]->weather; + // set up world state P_SpawnSpecials(fromnetsave); if (loadprecip) // ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame) P_SpawnPrecipitation(); - globalweather = mapheaderinfo[gamemap-1]->weather; - #ifdef HWRENDER // not win32 only 19990829 by Kin if (rendermode != render_soft && rendermode != render_none) { diff --git a/src/p_spec.c b/src/p_spec.c index fd9ef4b7..61ae9c63 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2395,6 +2395,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) camera[i].y += y; camera[i].z += z; camera[i].subsector = R_PointInSubsector(camera[i].x, camera[i].y); + R_RelativeTeleportViewInterpolation(i, x, y, z, 0); break; } } diff --git a/src/p_telept.c b/src/p_telept.c index 41896562..7c9da226 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -17,6 +17,7 @@ #include "r_state.h" #include "s_sound.h" #include "r_main.h" +#include "r_fps.h" /** \brief The P_MixUp function @@ -149,25 +150,17 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle thing->reactiontime = TICRATE/2; // don't move for about half a second // absolute angle position - if (thing == players[consoleplayer].mo) - localangle[0] = angle; - else if (splitscreen) - { - for (i = 1; i <= splitscreen; i++) - { - if (thing == players[displayplayers[i]].mo) - { - localangle[i] = angle; - break; - } - } - } - // move chasecam at new player location for (i = 0; i <= splitscreen; i++) { - if (thing->player == &players[displayplayers[i]] && camera[i].chase) - P_ResetCamera(thing->player, &camera[i]); + if (thing->player == &players[displayplayers[i]]) + { + localangle[i] = angle; + if (camera[i].chase) + P_ResetCamera(thing->player, &camera[i]); + R_ResetViewInterpolation(i + 1); + break; + } } // don't run in place after a teleport diff --git a/src/p_tick.c b/src/p_tick.c index 62acdc16..45fefa81 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -25,6 +25,7 @@ #include "k_kart.h" #include "r_main.h" #include "r_fps.h" +#include "i_video.h" // rendermode // Object place #include "m_cheat.h" @@ -818,15 +819,20 @@ void P_Ticker(boolean run) // Hack: ensure newview is assigned every tic. // Ensures view interpolation is T-1 to T in poor network conditions // We need a better way to assign view state decoupled from game logic - for (i = 0; i <= splitscreen; i++) + if (rendermode != render_none) { - player_t *player = &players[displayplayers[i]]; - boolean skyVisible = skyVisiblePerPlayer[i]; - if (skyVisible && skyboxmo[0] && cv_skybox.value) + for (i = 0; i <= splitscreen; i++) { - R_SkyboxFrame(player); + player_t *player = &players[displayplayers[i]]; + boolean isSkyVisibleForPlayer = skyVisiblePerPlayer[i]; + if (!player->mo) + continue; + if (isSkyVisibleForPlayer && skyboxmo[0] && cv_skybox.value) + { + R_SkyboxFrame(player); + } + R_SetupFrame(player, (skyboxmo[0] && cv_skybox.value)); } - R_SetupFrame(player, (skyboxmo[0] && cv_skybox.value)); } } diff --git a/src/p_user.c b/src/p_user.c index 82e5fa3f..94169754 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -8518,16 +8518,21 @@ void P_PlayerThink(player_t *player) if (netgame && cv_antigrief.value != 0 && G_RaceGametype()) { - if (!player->spectator && !player->exiting && !(player->pflags & PF_TIMEOVER)) + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + if (&players[i] == player) + continue; + break; + } + + if (i < MAXPLAYERS && !player->spectator && !player->exiting && !(player->pflags & PF_TIMEOVER)) { const tic_t griefval = cv_antigrief.value * TICRATE; const UINT8 n = player - players; - if (n != serverplayer -#ifndef DEVELOP - && !IsPlayerAdmin(n) -#endif - ) { if (player->grieftime > griefval) { @@ -8536,7 +8541,11 @@ void P_PlayerThink(player_t *player) if (server) { - if (player->griefstrikes > 2) + if ((player->griefstrikes > 2) +#ifndef DEVELOP + && !IsPlayerAdmin(n) +#endif + && !P_IsLocalPlayer(player)) // P_IsMachineLocalPlayer for DRRR { // Send kick XBOXSTATIC UINT8 buf[2]; diff --git a/src/r_fps.c b/src/r_fps.c index 2ed03979..9cbee9c0 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -210,6 +210,14 @@ void R_ResetViewInterpolation(UINT8 p) } } +void R_RelativeTeleportViewInterpolation(UINT8 p, fixed_t xdiff, fixed_t ydiff, fixed_t zdiff, angle_t angdiff) +{ + pview_old[p].x += xdiff; + pview_old[p].y += ydiff; + pview_old[p].z += zdiff; + pview_old[p].angle += angdiff; +} + void R_SetViewContext(enum viewcontext_e _viewcontext) { UINT8 i = 0; diff --git a/src/r_fps.h b/src/r_fps.h index 7e64683f..2f3f4430 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -115,6 +115,8 @@ void R_InterpolateView(fixed_t frac); void R_UpdateViewInterpolation(void); // Reset the view states (e.g. after level load) so R_InterpolateView doesn't interpolate invalid data void R_ResetViewInterpolation(UINT8 p); +// Update old view for seamless relative teleport +void R_RelativeTeleportViewInterpolation(UINT8 p, fixed_t xdiff, fixed_t ydiff, fixed_t zdiff, angle_t angdiff); // Set the current view context (the viewvars pointed to by newview) void R_SetViewContext(enum viewcontext_e _viewcontext); diff --git a/src/r_main.c b/src/r_main.c index b6643f06..11de07e3 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -1220,7 +1220,7 @@ static void R_PortalFrame(line_t *start, line_t *dest, portal_pair *portal) dest_c.y = (dest->v1->y + dest->v2->y) / 2; // Heights! - newview->z += dest->frontsector->floorheight - start->frontsector->floorheight; + viewz += dest->frontsector->floorheight - start->frontsector->floorheight; // calculate the difference in position and rotation! #ifdef ANGLED_PORTALS diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index cc3a37e6..50204f3f 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -567,14 +567,12 @@ static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co) case VK_TAB: event.data1 = KEY_NULL; break; - case VK_SHIFT: - event.data1 = KEY_LSHIFT; - break; case VK_RETURN: entering_con_command = false; // Fall through. default: - event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char + //event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char + event.data1 = evt.uChar.AsciiChar; } if (co != INVALID_HANDLE_VALUE && GetFileType(co) == FILE_TYPE_CHAR && GetConsoleMode(co, &t)) { @@ -593,18 +591,6 @@ static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co) } } } - else - { - event.type = ev_keyup; - switch (evt.wVirtualKeyCode) - { - case VK_SHIFT: - event.data1 = KEY_LSHIFT; - break; - default: - break; - } - } if (event.data1) D_PostEvent(&event); } diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 05cb385e..30a88737 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -101,8 +101,10 @@ rendermode_t rendermode = render_none; boolean highcolor = false; +static void Impl_SetVsync(void); + // synchronize page flipping with screen refresh -consvar_t cv_vidwait = {"vid_wait", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_vidwait = {"vid_wait", "Off", CV_SAVE|CV_CALL|CV_NOINIT, CV_OnOff, Impl_SetVsync, 0, NULL, NULL, 0, 0, NULL}; static consvar_t cv_stretch = {"stretch", "Off", CV_SAVE|CV_NOSHOWHELP, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; UINT8 graphics_started = 0; // Is used in console.c and screen.c @@ -1782,7 +1784,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen) // "direct3d" driver (D3D9) causes Drmingw exchndl // to not write RPT files. Every other driver // seems fine. - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d11"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); renderer = SDL_CreateRenderer(window, -1, flags); if (renderer == NULL) @@ -2131,4 +2133,11 @@ UINT32 I_GetRefreshRate(void) return refresh_rate; } +static void Impl_SetVsync(void) +{ +#if SDL_VERSION_ATLEAST(2,0,18) + if (renderer) + SDL_RenderSetVSync(renderer, cv_vidwait.value); +#endif +} #endif diff --git a/src/sounds.c b/src/sounds.c index 90b1ae30..d949c27a 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -818,10 +818,10 @@ sfxinfo_t S_sfx[NUMSFX] = {"gemhit", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Opulence gem/coin tumbling {"wrink", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // Some sort of ghoulie? {"bsnipe", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Banana sniping - {"join", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player joined server - {"leave", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player left server - {"requst", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Got a Discord join request - {"syfail", false, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Funny sync failure + {"join", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player joined server + {"leave", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Player left server + {"requst", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Got a Discord join request + {"syfail", true, 96, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Funny sync failure {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR}, // Debug notification diff --git a/src/strcasestr.c b/src/strcasestr.c new file mode 100644 index 00000000..4ff778bf --- /dev/null +++ b/src/strcasestr.c @@ -0,0 +1,111 @@ +/* +strcasestr -- case insensitive substring searching function. +*/ +/* +Copyright 2019 James R. +All rights reserved. + +Redistribution and use in source forms, with or without modification, is +permitted provided that the following condition is met: + +1. Redistributions of source code must retain the above copyright notice, this + condition and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +static inline int +trycmp (char **pp, char *cp, + const char *q, size_t qn) +{ + char *p; + p = (*pp); + if (strncasecmp(p, q, qn) == 0) + return 0; + (*pp) = strchr(&p[1], (*cp)); + return 1; +} + +static inline void +swapp (char ***ppap, char ***ppbp, char **cpap, char **cpbp) +{ + char **pp; + char *p; + + pp = *ppap; + *ppap = *ppbp; + *ppbp = pp; + + p = *cpap; + *cpap = *cpbp; + *cpbp = p; +} + +char * +strcasestr (const char *s, const char *q) +{ + size_t qn; + + char uc; + char lc; + + char *up; + char *lp; + + char **ppa; + char **ppb; + + char *cpa; + char *cpb; + + uc = toupper(*q); + lc = tolower(*q); + + up = strchr(s, uc); + lp = strchr(s, lc); + + if (!( (intptr_t)up|(intptr_t)lp )) + return 0; + + if (!lp || ( up && up < lp )) + { + ppa = &up; + ppb = &lp; + + cpa = &uc; + cpb = &lc; + } + else + { + ppa = &lp; + ppb = &up; + + cpa = &lc; + cpb = &uc; + } + + qn = strlen(q); + + for (;;) + { + if (trycmp(ppa, cpa, q, qn) == 0) + return (*ppa); + + if (!( (intptr_t)up|(intptr_t)lp )) + break; + + if (!(*ppa) || ( (*ppb) && (*ppb) < (*ppa) )) + swapp(&ppa, &ppb, &cpa, &cpb); + } + + return 0; +} diff --git a/src/string.c b/src/string.c index 2a03e872..c415e524 100644 --- a/src/string.c +++ b/src/string.c @@ -2,6 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2006 by Graue. // Copyright (C) 2006-2018 by Sonic Team Junior. +// Copyright (C) 2019 by James R. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -50,3 +51,5 @@ size_t strlcpy(char *dst, const char *src, size_t siz) } #endif + +#include "strcasestr.c"