diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt
index 2478b1d68..f451cfe2c 100644
--- a/man/CMakeLists.txt
+++ b/man/CMakeLists.txt
@@ -1,3 +1,13 @@
+
+if(Python3_EXECUTABLE)
+ add_custom_target(paramsgen
+ COMMAND "${Python3_EXECUTABLE}" docgen -a
+ "${PROJECT_SOURCE_DIR}/src" > "${PROJECT_SOURCE_DIR}/src/params.h"
+ DEPENDS "${PROJECT_SOURCE_DIR}/src"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ )
+endif()
+
configure_file(WoofInstall.cmake.in WoofInstall.cmake ESCAPE_QUOTES @ONLY)
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/WoofInstall.cmake")
diff --git a/man/docgen b/man/docgen
index b90046b36..a8f6b3ca6 100644
--- a/man/docgen
+++ b/man/docgen
@@ -34,18 +34,11 @@ import os
import re
import glob
import getopt
+import textwrap
TEXT_WRAP_WIDTH = 78
INCLUDE_STATEMENT_RE = re.compile("@include\s+(\S+)")
-# Use appropriate stdout function for Python 2 or 3
-
-def stdout(buf):
- if sys.version_info.major < 3:
- sys.stdout.write(buf)
- else:
- sys.stdout.buffer.write(buf)
-
# Find the maximum width of a list of parameters (for plain text output)
def parameter_list_width(params):
@@ -116,6 +109,26 @@ class Category:
return result
+ def help_output(self):
+ result = self.description + ": \n"
+
+ self.params.sort()
+
+ params_help = []
+
+ for p in self.params:
+ if p.should_show() and p.help:
+ params_help.append(p)
+
+ w = parameter_list_width(params_help)
+
+ for p in params_help:
+ result += p.help_output(w)
+
+ result = result.rstrip() + "\n"
+
+ return result
+
def markdown_output(self):
result = "## %s\n\n| Parameter | Description |\n| - | - |\n" % self.description
@@ -142,6 +155,17 @@ class Category:
return result
+ def carray_output(self, check_args):
+ result = ""
+
+ self.params.sort()
+
+ for p in self.params:
+ if p.should_show() and p.args if check_args else not p.args:
+ result += "\"" + p.carray_output() + "\",\n"
+
+ return result
+
def manpage_output(self):
result = ".SH " + self.description.upper() + "\n"
@@ -199,6 +223,7 @@ class Parameter:
self.args = None
self.platform = None
self.category = None
+ self.help = False
self.vanilla_option = False
self.games = None
@@ -223,6 +248,8 @@ class Parameter:
self.platform = data
elif option_type == "category":
self.category = data
+ elif option_type == "help":
+ self.help = True
elif option_type == "vanilla":
self.vanilla_option = True
elif option_type == "game":
@@ -275,6 +302,20 @@ class Parameter:
return result
+ def help_output(self, indent):
+ result = self.name
+
+ if self.args:
+ result += " " + self.args
+
+ result += " " * (indent - len(result))
+
+ result += textwrap.fill(self.text, width = 80 - indent,
+ subsequent_indent = " " * indent)
+ result += "\n"
+
+ return result
+
def markdown_output(self):
if self.args:
name = "%s %s" % (self.name, self.args)
@@ -336,6 +377,12 @@ class Parameter:
return result
+ def carray_output(self):
+
+ result = self.name
+
+ return result
+
# Read list of wiki pages
def read_wikipages():
@@ -483,7 +530,6 @@ def print_template(template_file, substs, content):
for k,v in substs.items():
line = line.replace(k,v)
print(line.rstrip())
- # stdout(line.rstrip().encode('UTF-8') + b'\n')
finally:
f.close()
@@ -503,7 +549,6 @@ def wiki_output(targets, _, template):
for t in targets:
print(st.wiki_output())
- # stdout(t.wiki_output().encode('UTF-8') + b'\n')
def markdown_output(targets, substs, template_file):
content = ""
@@ -531,6 +576,34 @@ def completion_output(targets, substs, template_file):
print_template(template_file, substs, content)
+def carray_output(targets, substs, template):
+
+ content = "\nstatic const char *params[] = {\n"
+
+ for t in targets:
+ content += t.carray_output(False)
+
+ content += "};\n\nstatic const char *params_with_args[] = {\n"
+
+ for t in targets:
+ content += t.carray_output(True)
+
+ content += "};\n\n#define HELP_STRING \""
+
+ c = dict(categories)
+
+ for t in targets:
+ # no video and obscure category
+ if t != c["video"] and t != c["obscure"]:
+ s = t.help_output() + "\n";
+ s = s.replace("\"", "\\\"")
+ s = s.replace("\n", "\\n\\\n")
+ content += s
+
+ content += "\""
+
+ print(content)
+
def usage():
print("Usage: %s [-V] [-c tag] -s project_name [ -z shortname ] ( -M | -m | -w | -p )
..." \
% sys.argv[0])
@@ -544,13 +617,14 @@ def usage():
print(" -w : Wikitext output")
print(" -p : Plaintext output")
print(" -b : Bash-Completion output")
+ print(" -a : C array output")
print(" -V : Don't show Vanilla Doom options")
print(" -g : Only document options for specified game.")
sys.exit(0)
# Parse command line
-opts, args = getopt.getopt(sys.argv[1:], "s:z:M:m:wp:b:c:g:V")
+opts, args = getopt.getopt(sys.argv[1:], "s:z:M:m:wp:b:ap:c:g:V")
output_function = None
template = None
@@ -577,6 +651,8 @@ for opt in opts:
elif opt[0] == "-b":
output_function = completion_output
template = opt[1]
+ elif opt[0] == "-a":
+ output_function = carray_output
elif opt[0] == "-V":
show_vanilla_options = False
elif opt[0] == "-c":
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 000000000..8e69d0489
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1 @@
+params.h
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 80f0f8e1e..30048b399 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -117,8 +117,6 @@ set(WOOF_SOURCES
z_zone.c z_zone.h
../miniz/miniz.c ../miniz/miniz.h)
-list(APPEND WOOF_LIBRARIES textscreen)
-
if(WIN32)
list(APPEND
WOOF_SOURCES
@@ -156,13 +154,18 @@ add_executable(woof WIN32 ${WOOF_SOURCES})
target_woof_settings(woof)
target_include_directories(woof PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../")
+if(Python3_EXECUTABLE)
+ add_dependencies(woof paramsgen)
+ target_compile_definitions(woof PRIVATE HAVE_PARAMS_GEN)
+endif()
+
if (FluidSynth_FOUND)
list(APPEND WOOF_LIBRARIES FluidSynth::FluidSynth)
target_compile_definitions(woof PRIVATE HAVE_FLUIDSYNTH)
endif()
target_link_libraries(woof PRIVATE ${WOOF_LIBRARIES}
- SDL2::SDL2 SDL2::mixer SDL2::net opl)
+ SDL2::SDL2 SDL2::mixer SDL2::net opl textscreen)
if(MSVC)
# MSVC tries to supply a default manifest and complains when it finds ours
diff --git a/src/d_iwad.c b/src/d_iwad.c
index d9a20ba26..ceb30a5d8 100644
--- a/src/d_iwad.c
+++ b/src/d_iwad.c
@@ -656,6 +656,7 @@ char *D_FindIWADFile(GameMode_t *mode, GameMission_t *mission)
// Specify an IWAD file to use.
//
// @arg
+ // @help
//
int iwadparm = M_CheckParmWithArgs("-iwad", 1);
diff --git a/src/d_loop.c b/src/d_loop.c
index e4f76a344..34a9e33d0 100644
--- a/src/d_loop.c
+++ b/src/d_loop.c
@@ -356,7 +356,7 @@ void D_StartNetGame(net_gamesettings_t *settings,
i = M_CheckParmWithArgs("-extratics", 1);
if (i > 0)
- settings->extratics = atoi(myargv[i+1]);
+ settings->extratics = M_ParmArgToInt(i);
else
settings->extratics = 1;
@@ -371,7 +371,7 @@ void D_StartNetGame(net_gamesettings_t *settings,
i = M_CheckParmWithArgs("-dup", 1);
if (i > 0)
- settings->ticdup = atoi(myargv[i+1]);
+ settings->ticdup = M_ParmArgToInt(i);
else
settings->ticdup = 1;
@@ -433,6 +433,7 @@ boolean D_InitNetGame(net_connect_data_t *connect_data)
//!
// @category net
+ // @help
//
// Start a multiplayer server, listening for connections.
//
@@ -453,6 +454,7 @@ boolean D_InitNetGame(net_connect_data_t *connect_data)
{
//!
// @category net
+ // @help
//
// Automatically search the local LAN for a multiplayer
// server and join it.
@@ -473,6 +475,7 @@ boolean D_InitNetGame(net_connect_data_t *connect_data)
//!
// @arg
// @category net
+ // @help
//
// Connect to a multiplayer server running on the given
// address.
diff --git a/src/d_main.c b/src/d_main.c
index deb9dc6dd..900728d0d 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -592,8 +592,8 @@ static boolean D_AddZipFile(const char *file)
memset(&zip_archive, 0, sizeof(zip_archive));
if (!mz_zip_reader_init_file(&zip_archive, file, MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
{
- printf("D_AddZipFile: Failed to open %s\n", file);
- return true;
+ I_Error("D_AddZipFile: Failed to open %s\n", file);
+ return false;
}
str = M_StringJoin("_", PROJECT_SHORTNAME, "_", M_BaseName(file), NULL);
@@ -1089,8 +1089,8 @@ static void InitGameVersion(void)
// @arg
// @category compat
//
- // Emulate a specific version of Doom. Valid values are "1.9",
- // "ultimate", "final", "chex".
+ // Emulate a specific version of Doom. Valid values are "1.9",
+ // "ultimate", "final", "chex". Implies -complevel vanilla.
//
p = M_CheckParm("-gameversion");
@@ -1407,6 +1407,7 @@ static void D_ProcessDehCommandLine(void)
//!
// @arg
// @category mod
+ // @help
//
// Load the given dehacked/bex patch(es).
//
@@ -1783,6 +1784,25 @@ void D_DoomMain(void)
I_AtExitPrio(I_ErrorMsg, true, "I_ErrorMsg", exit_priority_verylast);
+#if defined(HAVE_PARAMS_GEN)
+ // Don't check undocumented options if -devparm is set
+ if (!M_ParmExists("-devparm"))
+ {
+ M_CheckCommandLine();
+ }
+
+ //!
+ //
+ // Print command line help.
+ //
+
+ if (M_ParmExists("-help"))
+ {
+ M_PrintHelpString();
+ I_SafeExit(0);
+ }
+#endif
+
dsdh_InitTables();
#if defined(_WIN32)
@@ -1863,6 +1883,7 @@ void D_DoomMain(void)
//!
// @category game
+ // @help
//
// Enables automatic pistol starts on each level.
//
@@ -2018,8 +2039,8 @@ void D_DoomMain(void)
extern int forwardmove[2];
extern int sidemove[2];
- if (p 400)
@@ -2049,6 +2070,7 @@ void D_DoomMain(void)
//!
// @arg
// @vanilla
+ // @help
//
// Load the specified PWAD files.
//
@@ -2076,6 +2098,7 @@ void D_DoomMain(void)
// @arg
// @category demo
// @vanilla
+ // @help
//
// Play back the demo named demo.lmp.
//
@@ -2127,30 +2150,49 @@ void D_DoomMain(void)
// @category game
// @arg
// @vanilla
+ // @help
//
- // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of
- // 0 disables all monsters.
+ // Set the game skill, 1-5 (1: easiest, 5: hardest). A skill of 0 disables all
+ // monsters only in -complevel vanilla.
//
if ((p = M_CheckParm ("-skill")) && p < myargc-1)
- {
- startskill = myargv[p+1][0]-'1';
- autostart = true;
- }
+ {
+ startskill = M_ParmArgToInt(p);
+ startskill--;
+ if (startskill >= sk_none && startskill <= sk_nightmare)
+ {
+ autostart = true;
+ }
+ else
+ {
+ I_Error("Invalid parameter '%s' for -skill, valid values are 1-5 "
+ "(1: easiest, 5: hardest). "
+ "A skill of 0 disables all monsters.", myargv[p+1]);
+ }
+ }
//!
// @category game
// @arg
// @vanilla
//
- // Start playing on episode n (1-4)
+ // Start playing on episode n (1-99)
//
if ((p = M_CheckParm ("-episode")) && p < myargc-1)
{
- startepisode = myargv[p+1][0]-'0';
- startmap = 1;
- autostart = true;
+ startepisode = M_ParmArgToInt(p);
+ if (startepisode >= 1 && startepisode <= 99)
+ {
+ startmap = 1;
+ autostart = true;
+ }
+ else
+ {
+ I_Error("Invalid parameter '%s' for -episode, valid values are 1-99.",
+ myargv[p+1]);
+ }
}
//!
@@ -2163,9 +2205,8 @@ void D_DoomMain(void)
if ((p = M_CheckParm ("-timer")) && p < myargc-1 && deathmatch)
{
- int time = atoi(myargv[p+1]);
- timelimit = time;
- printf("Levels will end after %d minute%s.\n", time, time>1 ? "s" : "");
+ timelimit = M_ParmArgToInt(p);
+ printf("Levels will end after %d minute%s.\n", timelimit, timelimit>1 ? "s" : "");
}
//!
@@ -2183,11 +2224,11 @@ void D_DoomMain(void)
//!
// @category game
- // @arg [ | ]
+ // @arg |
// @vanilla
+ // @help
//
- // Start a game immediately, warping to ExMy (Doom 1) or MAPxy
- // (Doom 2)
+ // Start a game immediately, warping to ExMy (Doom 1) or MAPxy (Doom 2).
//
if (((p = M_CheckParm ("-warp")) || // killough 5/2/98
@@ -2195,21 +2236,21 @@ void D_DoomMain(void)
{
if (gamemode == commercial)
{
- startmap = atoi(myargv[p+1]);
+ startmap = M_ParmArgToInt(p);
autostart = true;
}
else // 1/25/98 killough: fix -warp xxx from crashing Doom 1 / UD
// [crispy] only if second argument is not another option
if (p < myargc-2 && myargv[p+2][0] != '-')
{
- startepisode = atoi(myargv[++p]);
- startmap = atoi(myargv[p+1]);
+ startepisode = M_ParmArgToInt(p);
+ startmap = M_ParmArg2ToInt(p);
autostart = true;
}
// [crispy] allow second digit without space in between for Doom 1
else
{
- int em = atoi(myargv[++p]);
+ int em = M_ParmArgToInt(p);
startepisode = em / 10;
startmap = em % 10;
autostart = true;
@@ -2379,7 +2420,7 @@ void D_DoomMain(void)
p = M_CheckParmWithArgs("-loadgame", 1);
if (p)
{
- startloadgame = atoi(myargv[p+1]);
+ startloadgame = M_ParmArgToInt(p);
}
else
{
@@ -2466,6 +2507,10 @@ void D_DoomMain(void)
{
demoskip_tics = (int) (sec * TICRATE);
}
+ else
+ {
+ I_Error("Invalid parameter '%s' for -skipsec, should be min:sec", myargv[p+1]);
+ }
demoskip_tics = abs(demoskip_tics);
}
@@ -2494,6 +2539,7 @@ void D_DoomMain(void)
// @arg
// @category demo
// @vanilla
+ // @help
//
// Record a demo named demo.lmp.
//
diff --git a/src/d_net.c b/src/d_net.c
index 292de36c8..2503b596b 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -224,6 +224,7 @@ static void InitConnectData(net_connect_data_t *connect_data)
//!
// @category demo
+ // @help
//
// Play with low turning resolution to emulate demo recording.
//
diff --git a/src/g_game.c b/src/g_game.c
index 312f86956..091ed8dc7 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1144,7 +1144,13 @@ static void G_DoCompleted(void)
{
int i;
- // [crispy] Write level statistics upon exit
+ //!
+ // @category demo
+ // @help
+ //
+ // Write level statistics upon exit to levelstat.txt
+ //
+
if (M_CheckParm("-levelstat"))
{
G_WriteLevelStat();
@@ -2544,7 +2550,7 @@ static int G_GetHelpers(void)
//
j = M_CheckParm ("-dogs");
- return j ? j+1 < myargc ? atoi(myargv[j+1]) : 1 : default_dogs;
+ return j ? j+1 < myargc ? M_ParmArgToInt(j) : 1 : default_dogs;
}
// [FG] support named complevels on the command line, e.g. "-complevel boom",
@@ -2759,8 +2765,9 @@ void G_ReloadDefaults(void)
{
//!
- // @arg
+ // @arg
// @category compat
+ // @help
//
// Emulate a specific version of Doom/Boom/MBF. Valid values are
// "vanilla", "boom", "mbf", "mbf21".
@@ -2773,6 +2780,10 @@ void G_ReloadDefaults(void)
int l = G_GetNamedComplevel(myargv[i+1]);
if (l > -1)
demo_version = l;
+ else
+ I_Error("Invalid parameter '%s' for -complevel, "
+ "valid values are vanilla, boom, mbf, mbf21.",
+ myargv[i+1]);
}
}
@@ -2782,7 +2793,8 @@ void G_ReloadDefaults(void)
strictmode = default_strictmode;
//!
- // @category compat
+ // @category demo
+ // @help
//
// Sets compatibility and cosmetic settings according to DSDA rules.
//
@@ -3083,7 +3095,7 @@ void G_RecordDemo(char *name)
i = M_CheckParm ("-maxdemo");
if (i && i
#include
+#include "i_system.h"
int myargc;
char **myargv;
@@ -69,6 +71,131 @@ boolean M_ParmExists(const char *check)
return M_CheckParm(check) != 0;
}
+static int ArgToInt(int p, int arg)
+{
+ int result;
+
+ if (p + arg >= myargc)
+ I_Error("No parameter for '%s'.", myargv[p]);
+
+ if (sscanf(myargv[p + arg], " %d", &result) != 1)
+ I_Error("Invalid parameter '%s' for %s, must be a number.", myargv[p + arg], myargv[p]);
+
+ return result;
+}
+
+int M_ParmArgToInt(int p)
+{
+ return ArgToInt(p, 1);
+}
+
+int M_ParmArg2ToInt(int p)
+{
+ return ArgToInt(p, 2);
+}
+
+
+#if defined(HAVE_PARAMS_GEN)
+#include "params.h"
+
+static int CheckArgs(int p, int num_args)
+{
+ int i;
+
+ ++p;
+
+ for (i = p; i < p + num_args && i < myargc; ++i)
+ {
+ if (myargv[i][0] == '-')
+ break;
+ }
+
+ if (i > p)
+ return i;
+
+ return 0;
+}
+
+void M_CheckCommandLine(void)
+{
+ int p = 1;
+
+ while (p < myargc)
+ {
+ int i;
+ int args = -1;
+
+ for (i = 0; i < arrlen(params_with_args); ++i)
+ {
+ if (!strcasecmp(myargv[p], "-file") ||
+ !strcasecmp(myargv[p], "-deh") ||
+ !strcasecmp(myargv[p], "-bex"))
+ {
+ args = myargc;
+ break;
+ }
+ else if (!strcasecmp(myargv[p], "-warp") ||
+ !strcasecmp(myargv[p], "-recordfrom"))
+ {
+ args = 2;
+ break;
+ }
+ else if (!strcasecmp(myargv[p], params_with_args[i]))
+ {
+ args = 1;
+ break;
+ }
+ }
+
+ if (args > 0)
+ {
+ int check = CheckArgs(p, args);
+
+ if (!check)
+ {
+ // -turbo has default value
+ if (!strcasecmp(myargv[p], "-turbo"))
+ {
+ ++p;
+ }
+ // -statdump allow "-" parameter
+ else if (!strcasecmp(myargv[p], "-statdump") &&
+ p + 1 < myargc && !strcmp(myargv[p + 1], "-"))
+ {
+ p += 2;
+ }
+ else
+ {
+ I_Error("No parameter for '%s'.", myargv[p]);
+ }
+ }
+ else
+ {
+ p = check;
+ }
+
+ continue;
+ }
+
+ for (i = 0; i < arrlen(params); ++i)
+ {
+ if (!strcasecmp(myargv[p], params[i]))
+ break;
+ }
+
+ if (i == arrlen(params))
+ I_Error("No such option '%s'.", myargv[p]);
+
+ ++p;
+ }
+}
+
+void M_PrintHelpString(void)
+{
+ printf(HELP_STRING);
+}
+#endif
+
//----------------------------------------------------------------------------
//
// $Log: m_argv.c,v $
diff --git a/src/m_argv.h b/src/m_argv.h
index 66ee9d429..3e336e9a3 100644
--- a/src/m_argv.h
+++ b/src/m_argv.h
@@ -49,6 +49,14 @@ int M_CheckParmWithArgs(const char *check, int num_args);
// line arguments, false if not.
boolean M_ParmExists(const char *check);
+#if defined(HAVE_PARAMS_GEN)
+boolean M_CheckCommandLine(void);
+void M_PrintHelpString(void);
+#endif
+
+int M_ParmArgToInt(int p);
+int M_ParmArg2ToInt(int p);
+
#endif
//----------------------------------------------------------------------------
diff --git a/src/m_menu.c b/src/m_menu.c
index ed2642b74..e3f8255aa 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3843,7 +3843,9 @@ static void M_ResetTimeScale(void)
I_SetTimeScale(100);
else
{
- int p, time_scale;
+ int p;
+
+ int time_scale = realtic_clock_rate;
//!
// @arg
@@ -3855,9 +3857,12 @@ static void M_ResetTimeScale(void)
p = M_CheckParmWithArgs("-speed", 1);
if (p)
- time_scale = BETWEEN(10, 1000, atoi(myargv[p+1]));
- else
- time_scale = realtic_clock_rate;
+ {
+ time_scale = M_ParmArgToInt(p);
+ if (time_scale < 10 || time_scale > 1000)
+ I_Error("Invalid parameter '%d' for -speed, valid values are 10-1000.",
+ time_scale);
+ }
I_SetTimeScale(time_scale);
}
diff --git a/src/net_gui.c b/src/net_gui.c
index 632453d8e..4fd3b3a2d 100644
--- a/src/net_gui.c
+++ b/src/net_gui.c
@@ -251,7 +251,7 @@ static void ParseCommandLineArgs(void)
i = M_CheckParmWithArgs("-nodes", 1);
if (i > 0)
{
- expected_nodes = atoi(myargv[i + 1]);
+ expected_nodes = M_ParmArgToInt(i);
}
}
diff --git a/src/net_sdl.c b/src/net_sdl.c
index ba0689df0..4f31a75fd 100644
--- a/src/net_sdl.c
+++ b/src/net_sdl.c
@@ -170,7 +170,7 @@ static boolean NET_SDL_InitClient(void)
p = M_CheckParmWithArgs("-port", 1);
if (p > 0)
- port = atoi(myargv[p+1]);
+ port = M_ParmArgToInt(p);
SDLNet_Init();
diff --git a/src/net_server.c b/src/net_server.c
index cf984851c..afd0f6c65 100644
--- a/src/net_server.c
+++ b/src/net_server.c
@@ -1911,6 +1911,7 @@ void NET_SV_RegisterWithMaster(void)
{
//!
// @category net
+ // @help
//
// When running a server, don't register with the global master server.
// Implies -server.
diff --git a/src/p_map.c b/src/p_map.c
index 214c6b7b4..3b6936010 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2309,7 +2309,8 @@ static void SpechitOverrun(line_t *ld)
if (p > 0)
{
- M_StrToInt(myargv[p+1], (int *) &baseaddr);
+ if (!M_StrToInt(myargv[p+1], (int *) &baseaddr))
+ I_Error("Invalid parameter '%s' for -spechit.", myargv[p+1]);
}
else
{
diff --git a/src/p_spec.c b/src/p_spec.c
index 26ab6b1a8..3d19a49fc 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2392,7 +2392,7 @@ void P_SpawnSpecials (void)
if (i && deathmatch)
{
int frags;
- frags = atoi(myargv[i+1]);
+ frags = M_ParmArgToInt(i);
if (frags <= 0) frags = 10; // default 10 if no count provided
levelFragLimit = true;
levelFragLimitCount = frags;