Merge branch 'master' into next

This commit is contained in:
SteelT 2022-11-04 14:53:51 -04:00
commit 32f88155f3
34 changed files with 1104 additions and 291 deletions

View file

@ -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

View file

@ -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})

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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,9 +5972,6 @@ boolean TryRunTics(tic_t realtics)
if (ticking)
{
if (advancedemo)
D_StartTitle();
else
// run the count * tics
while (neededtic > gametic)
{
@ -5983,44 +6007,72 @@ 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++)
{
if (playeringame[i] && laggers[i])
{
pingtimeout[i]++;
if (pingtimeout[i] > cv_pingtimeout.value) // ok your net has been bad for too long, you deserve to die.
UINT8 minimumkicklevel = (nonlaggers > 0) ? PINGKICK_LIMIT : PINGKICK_TICQUEUE;
for (i = 0; i < MAXPLAYERS; i++)
{
XBOXSTATIC char buf[2];
if (!playeringame[i] || pingkick[i] < minimumkicklevel)
continue;
if (pingkick[i] == PINGKICK_LIMIT)
{
// Don't kick on ping alone if we haven't reached our threshold yet.
if (++pingtimeout[i] < cv_pingtimeout.value)
continue;
}
pingtimeout[i] = 0;
buf[0] = (char)i;
@ -6028,10 +6080,6 @@ static inline void PingUpdate(void)
SendNetXCmd(XD_KICK, &buf, 2);
}
}
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);
}
}
}
//make the ping packet and clear server data for next one
@ -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;

View file

@ -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();

View file

@ -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__

View file

@ -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 <mapname> 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 <mapname> [-gametype <type> [-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,27 +2587,19 @@ 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)
{
CONS_Alert(CONS_ERROR, M_GetText("Internal game level '%s' not found\n"), COM_Argv(1));
return;
}
option_force = COM_CheckPartialParm("-f");
option_gametype = COM_CheckPartialParm("-g");
option_encore = COM_CheckPartialParm("-e");
newresetplayers = ! COM_CheckParm("-noresetplayers");
if (!(netgame || multiplayer) && !majormods)
{
if (COM_CheckParm("-force"))
{
G_SetGameModified(false, true);
}
else
mustmodifygame = !( netgame || multiplayer || majormods );
if (mustmodifygame && !option_force)
{
/* May want to be more descriptive? */
CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n"));
return;
}
}
newresetplayers = !COM_CheckParm("-noresetplayers");
if (!newresetplayers && !cv_debug)
{
@ -2575,72 +2607,110 @@ static void Command_Map_f(void)
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 <name / [MAP]code / number> [-gametype <type>] [-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;
}
}
// new encoremode value
// use cvar by default
newencoremode = (boolean)cv_kartencore.value;
if (COM_CheckParm("-encore"))
/* Did they give us a gametype number? That's okay too! */
if (isdigit(gametypename[0]))
{
if (!M_SecretUnlocked(SECRET_ENCORE) && !newencoremode)
d = atoi(gametypename);
if (d >= 0 && d < NUMGAMETYPES)
newgametype = d;
else
{
CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n"));
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;
}
newencoremode = !newencoremode;
}
else
{
CONS_Alert(CONS_ERROR,
"'%s' is not a gametype.\n",
gametypename);
Z_Free(realmapname);
Z_Free(mapname);
return;
}
}
}
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("%s doesn't support %s mode!\n(Use -force to override)\n"), mapname, gametypestring);
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;
}
}
// Prevent warping to locked levels
// ... unless you're in a dedicated server. Yes, technically this means you can view any level by
@ -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())

View file

@ -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
#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);
return HSendPacket(servernode, true, 0, p - (char *)netbuffer->u.textcmd);
// 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);
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;
fclose(curl_curfile->file);
}
}
Z_Free(filename);
curl_curfile->file = NULL;
curl_running = false;
curl_transfers--;
curl_multi_remove_handle(multi_handle, e);

View file

@ -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);

View file

@ -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

View file

@ -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, <Sryder> 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}
};

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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]--;

View file

@ -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(

View file

@ -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;
}

View file

@ -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))
{
@ -10297,6 +10297,19 @@ void P_RemovePrecipMobj(precipmobj_t *mobj)
// Clearing out stuff for savegames
void P_RemoveSavegameMobj(mobj_t *mobj)
{
// unlink from sector and block lists
if (((thinker_t *)mobj)->function.acp1 == (actionf_p1)P_NullPrecipThinker)
{
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);
@ -10306,13 +10319,20 @@ void P_RemoveSavegameMobj(mobj_t *mobj)
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}};

View file

@ -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,11 +2676,15 @@ 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
iquetail = iquehead = 0;

View file

@ -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)
{

View file

@ -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;
}
}

View file

@ -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)
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

View file

@ -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,17 +819,22 @@ 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
if (rendermode != render_none)
{
for (i = 0; i <= splitscreen; i++)
{
player_t *player = &players[displayplayers[i]];
boolean skyVisible = skyVisiblePerPlayer[i];
if (skyVisible && skyboxmo[0] && cv_skybox.value)
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));
}
}
}
P_MapEnd();

View file

@ -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];

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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

111
src/strcasestr.c Normal file
View file

@ -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;
}

View file

@ -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"