Merge branch 'map-by-name' into 'master'

New map command

See merge request STJr/SRB2Internal!424
This commit is contained in:
MascaraSnake 2019-11-13 13:45:36 -05:00
commit 510f36fbdf
7 changed files with 581 additions and 67 deletions

View File

@ -1736,25 +1736,151 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
}
}
enum
{
MAP_COMMAND_FORCE_OPTION,
MAP_COMMAND_GAMETYPE_OPTION,
MAP_COMMAND_NORESETPLAYERS_OPTION,
NUM_MAP_COMMAND_OPTIONS
};
static size_t CheckOptions(
int num_options,
size_t *first_argumentp,
size_t *user_options,
const char ***option_names,
int *option_num_arguments
)
{
int arguments_used;
size_t first_argument;
int i;
const char **pp;
const char *name;
size_t n;
arguments_used = 0;
first_argument = COM_Argc();
for (i = 0; i < num_options; ++i)
{
pp = option_names[i];
name = *pp;
do
{
if (( n = COM_CheckParm(name) ))
{
user_options[i] = n;
arguments_used += 1 + option_num_arguments[i];
if (n < first_argument)
first_argument = n;
}
}
while (( name = *++pp )) ;
}
(*first_argumentp) = first_argument;
return arguments_used;
}
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;
const char *force_option_names[] =
{
"-force",
"-f",
NULL
};
const char *gametype_option_names[] =
{
"-gametype",
"-g",
"-gt",
NULL
};
const char *noresetplayers_option_names[] =
{
"-noresetplayers",
NULL
};
const char **option_names[] =
{
force_option_names,
gametype_option_names,
noresetplayers_option_names,
};
int option_num_arguments[] =
{
0,/* -force */
1,/* -gametype */
0,/* -noresetplayers */
};
size_t acceptableargc;/* (this includes the command name itself!) */
size_t first_argument;
size_t user_options [NUM_MAP_COMMAND_OPTIONS] = {0};
const char *arg_gametype;
boolean newresetplayers;
boolean mustmodifygame;
boolean usemapcode = false;
INT32 newmapnum;
char * mapname;
size_t mapnamelen;
char *realmapname = NULL;
INT32 newgametype = gametype;
// max length of command: map map03 -gametype coop -noresetplayers -force
// 1 2 3 4 5 6
// = 8 arg max
if (COM_Argc() < 2 || COM_Argc() > 8)
{
CONS_Printf(M_GetText("map <mapname> [-gametype <type> [-force]: warp to map\n"));
return;
}
INT32 d;
char *p;
if (client && !IsPlayerAdmin(consoleplayer))
{
@ -1762,62 +1888,145 @@ 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)
/* map name + options */
acceptableargc = 2 + CheckOptions(NUM_MAP_COMMAND_OPTIONS,
&first_argument,
user_options, option_names, option_num_arguments);
newresetplayers = !user_options[MAP_COMMAND_NORESETPLAYERS_OPTION];
mustmodifygame =
!( netgame || multiplayer ) &&
(!modifiedgame || savemoddata );
if (mustmodifygame && !user_options[MAP_COMMAND_FORCE_OPTION])
{
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) && (!modifiedgame || savemoddata))
{
if (COM_CheckParm("-force"))
G_SetGameModified(false);
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 (user_options[MAP_COMMAND_GAMETYPE_OPTION] && !multiplayer)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid level name %s\n"), mapname);
CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
return;
}
// Ultimate Mode only in SP via menu
if (netgame || multiplayer)
ultimatemode = false;
/* If the first argument is an option, you fucked up. */
if (COM_Argc() < acceptableargc || first_argument == 1)
{
/* I'm going over the fucking lines and I DON'T CAREEEEE */
CONS_Printf("map <name / [MAP]code / number> [-gametype <type>] [-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.\n"
"* \"-force\" may be shortened to \"-f\".\n"
"* \"-gametype\" may be shortened to \"-g\" or \"-gt\".\n"));
return;
}
mapname = ConcatCommandArgv(1, first_argument);
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]) ) == 0)
{
CONS_Alert(CONS_ERROR, M_GetText("Invalid map code '%s'.\n"), mapname);
Z_Free(mapname);
return;
}
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);
Z_Free(mapname);
return;
}
usemapcode = true;
}
else
{
newmapnum = G_FindMap(mapname, &realmapname, NULL, NULL);
}
}
if (newmapnum == 0 || !mapheaderinfo[newmapnum-1])
{
CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname);
Z_Free(mapname);
return;
}
if (usemapcode)
{
realmapname = G_BuildMapTitle(newmapnum);
}
if (mustmodifygame && user_options[MAP_COMMAND_FORCE_OPTION])
{
G_SetGameModified(false);
}
// new gametype value
// use current one by default
i = COM_CheckParm("-gametype");
if (i)
if (user_options[MAP_COMMAND_GAMETYPE_OPTION])
{
if (!multiplayer)
{
CONS_Printf(M_GetText("You can't switch gametypes in single player!\n"));
return;
}
arg_gametype = COM_Argv(user_options[MAP_COMMAND_GAMETYPE_OPTION] + 1);
newgametype = G_GetGametypeByName(COM_Argv(i+1));
newgametype = G_GetGametypeByName(arg_gametype);
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;
d = atoi(arg_gametype);
// assume they gave us a gametype number, which is okay too
if (d >= 0 && d < NUMGAMETYPES)
newgametype = d;
}
}
// don't use a gametype the map doesn't support
if (cv_debug || user_options[MAP_COMMAND_FORCE_OPTION] || 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
else
{
if (!(
mapheaderinfo[newmapnum-1] &&
mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype)
))
{
CONS_Alert(CONS_WARNING, M_GetText("%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;
}
else
{
fromlevelselect =
( netgame || multiplayer ) &&
newgametype == gametype &&
newgametype == GT_COOP;
}
}
@ -1828,31 +2037,14 @@ 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;
}
// don't use a gametype the map doesn't support
if (cv_debug || COM_CheckParm("-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)))
{
char gametypestring[32] = "Single Player";
if (multiplayer)
{
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);
return;
}
else
fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP)));
// Ultimate Mode only in SP via menu
if (netgame || multiplayer)
ultimatemode = false;
if (tutorialmode && tutorialgcs)
{
@ -1865,7 +2057,10 @@ static void Command_Map_f(void)
tutorialmode = false; // warping takes us out of tutorial mode
D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect);
Z_Free(realmapname);
}
#undef CHECKPARM
/** Receives a map command and changes the map.
*

View File

@ -495,6 +495,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

@ -115,6 +115,9 @@ typedef long ssize_t;
#define strnicmp(x,y,n) strncasecmp(x,y,n)
#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

@ -4047,6 +4047,187 @@ 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);
}
//
// DEMO RECORDING
//

View File

@ -108,6 +108,27 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer,
boolean skipprecutscene, boolean FLS);
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);
// XMOD spawning
mapthing_t *G_FindCTFStart(INT32 playernum);
mapthing_t *G_FindMatchStart(INT32 playernum);

110
src/strcasestr.c Normal file
View File

@ -0,0 +1,110 @@
/*
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.
*/
#define SWAP( a, b ) \
(\
(a) ^= (b),\
(b) ^= (a),\
(a) ^= (b)\
)
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)
{
SWAP(*(intptr_t *)ppap, *(intptr_t *)ppbp);
SWAP(*(intptr_t *)cpap, *(intptr_t *)cpbp);
}
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 < 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"