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;