1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2016 EDuke32 developers and contributors
4
5 This file is part of EDuke32.
6
7 EDuke32 is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License version 2
9 as published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21 //-------------------------------------------------------------------------
22
23 #include "duke3d.h"
24 #include "demo.h"
25 #include "screens.h"
26 #include "renderlayer.h"
27 #include "cmdline.h"
28
29 int32_t g_commandSetup = 0;
30 int32_t g_noSetup = 0;
31 int32_t g_noAutoLoad = 0;
32 int32_t g_noSound = 0;
33 int32_t g_noMusic = 0;
34 const char *CommandMap = NULL;
35 const char *CommandName = NULL;
36 int32_t g_forceWeaponChoice = 0;
37 int32_t g_fakeMultiMode = 0;
38
G_ShowParameterHelp(void)39 void G_ShowParameterHelp(void)
40 {
41 static char const s[] = "Usage: " APPBASENAME " [files] [options]\n"
42 "Example: " APPBASENAME " -usecwd -cfg myconfig.cfg -map nukeland.map\n\n"
43 "Files can be of type [grp|zip|map|con|def]\n"
44 "\n"
45 "-cfg [file.cfg]\tUse an alternate configuration file\n"
46 #ifdef HAVE_CLIPSHAPE_FEATURE
47 "-clipmap [file.map]\tLoad an additional clipping map for use with clipshape\n"
48 #endif
49 "-connect [host]\tConnect to a multiplayer game\n"
50 "-c#\t\tMultiplayer mode #, 1 = DM, 2 = Co-op, 3 = DM(no spawn)\n"
51 "-d [file.edm or #]\tPlay a demo\n"
52 "-g [file.grp]\tLoad additional game data\n"
53 "-h [file.def]\tLoad an alternate definitions file\n"
54 "-j [dir]\t\tAdd a directory to " APPNAME "'s search list\n"
55 "-l#\t\tStart game on level #, see -v\n"
56 "-map [file.map]\tLoad an external map file\n"
57 "-mh [file.def]\tInclude an additional definitions module\n"
58 "-mx [file.con]\tInclude an additional CON script module\n"
59 "-m\t\tDisable enemies\n"
60 "-rts [file.rts]\tLoad a custom Remote Ridicule sound bank\n"
61 "-r\t\tRecord demo\n"
62 "-s#\t\tStart game on skill level #\n"
63 "-server\t\tStart a multiplayer server\n"
64 #ifdef STARTUP_SETUP_WINDOW
65 "-setup/nosetup\tEnable or disable startup window\n"
66 #endif
67 "-t#\t\tRespawn mode: 1 = enemies, 2 = weapons, 3 = items, x = all\n"
68 "-usecwd\t\tRead data and configuration from current directory\n"
69 "-u#########\tUser's favorite weapon order (default: 3425689071)\n"
70 "-v#\t\tStart game on episode #, see -l\n"
71 "-x [game.con]\tLoad custom CON script\n"
72 "-#\t\tLoad and run a game from slot # (0-9)\n"
73 // "\n-?/--help\tDisplay this help message and exit\n"
74 "\nSee " APPBASENAME " -debughelp for additional parameters for debugging"
75 ;
76 #ifdef WM_MSGBOX_WINDOW
77 Bsnprintf(tempbuf, sizeof(tempbuf), HEAD2 " %s", s_buildRev);
78 wm_msgbox(tempbuf, s);
79 #else
80 initprintf("%s\n", s);
81 #endif
82 }
83
G_ShowDebugHelp(void)84 void G_ShowDebugHelp(void)
85 {
86 static char const s[] = "Usage: " APPBASENAME " [files] [options]\n"
87 "\n"
88 #if 0
89 "-a\t\tUse fake player AI (fake multiplayer only)\n"
90 #endif
91 "-cachesize #\tSet cache size in kB\n"
92 "-game_dir [dir]\tSpecify game data directory\n"
93 "-gamegrp \tSelect main grp file\n"
94 "-name [name]\tPlayer name in multiplayer\n"
95 "-noautoload\tDisable loading from autoload directory\n"
96 #if defined RENDERTYPEWIN
97 "-nodinput\t\tDisable DirectInput (joystick) support\n"
98 #endif
99 "-nologo\t\tSkip intro anim\n"
100 "-ns\t\tDisable sound\n"
101 "-nm\t\tDisable music\n"
102 "-q#\t\tFake multiplayer with # players\n"
103 "-z#/-condebug\tEnable line-by-line CON compile debugging at level #\n"
104 "-conversion YYYYMMDD\tSelects CON script version for compatibility with older mods\n"
105 "-rotatesprite-no-widescreen\tStretch screen drawing from scripts to fullscreen\n"
106 ;
107 #ifdef WM_MSGBOX_WINDOW
108 Bsnprintf(tempbuf, sizeof(tempbuf), HEAD2 " %s", s_buildRev);
109 wm_msgbox(tempbuf, s);
110 #else
111 initprintf("%s\n", s);
112 #endif
113 }
114
G_AddDemo(const char * param)115 static void G_AddDemo(const char* param)
116 {
117 Bstrncpy(tempbuf, param, sizeof(tempbuf));
118 char * colon = (char *) Bstrchr(tempbuf, ':');
119 int32_t framespertic=-1, numrepeats=1;
120
121 if (colon && colon != tempbuf)
122 {
123 // -d<filename>:<num>[,<num>]
124 // profiling options
125 *(colon++) = 0;
126 Bsscanf(colon, "%d,%d", &framespertic, &numrepeats);
127 }
128
129 Demo_SetFirst(tempbuf);
130
131 if (framespertic < 0)
132 {
133 initprintf("Play demo %s.\n", g_firstDemoFile);
134 }
135 else
136 {
137 framespertic = clamp(framespertic, 0, 8)+1;
138 // TODO: repeat count and gathering statistics.
139 initprintf("Profile demo %s, %d frames/gametic, repeated 1x.\n", g_firstDemoFile,
140 framespertic-1);
141 Demo_PlayFirst(framespertic, 1);
142 g_noLogo = 1;
143 }
144 }
145
G_CheckCommandLine(int32_t argc,char const * const * argv)146 void G_CheckCommandLine(int32_t argc, char const * const * argv)
147 {
148 int16_t i = 1, j;
149 const char *c, *k;
150
151 ud.fta_on = 1;
152 ud.god = 0;
153 ud.m_respawn_items = 0;
154 ud.m_respawn_monsters = 0;
155 ud.m_respawn_inventory = 0;
156 ud.warp_on = 0;
157 ud.cashman = 0;
158 ud.m_player_skill = ud.player_skill = 2;
159 g_player[0].wchoice[0] = 3;
160 g_player[0].wchoice[1] = 4;
161 g_player[0].wchoice[2] = 5;
162 g_player[0].wchoice[3] = 7;
163 g_player[0].wchoice[4] = 8;
164 g_player[0].wchoice[5] = 6;
165 g_player[0].wchoice[6] = 0;
166 g_player[0].wchoice[7] = 2;
167 g_player[0].wchoice[8] = 9;
168 g_player[0].wchoice[9] = 1;
169 Bsprintf(ud.wchoice, "3457860291");
170
171 #ifdef HAVE_CLIPSHAPE_FEATURE
172 // pre-form the default 10 clipmaps
173 for (j = '0'; j<='9'; ++j)
174 {
175 char clipshape[16] = "_clipshape0.map";
176
177 clipshape[10] = j;
178 g_clipMapFiles.append(Xstrdup(clipshape));
179 }
180 #endif
181
182 if (argc > 1)
183 {
184 initprintf("Application parameters: ");
185 while (i < argc)
186 initprintf("%s ", argv[i++]);
187 initprintf("\n");
188
189 i = 1;
190 do
191 {
192 const char *const oc = argv[i];
193 int32_t shortopt = 0, ignored_short_opt = 0;
194
195 c = oc;
196
197 if ((*c == '-')
198 #ifdef _WIN32
199 || (*c == '/')
200 #endif
201 )
202 {
203 shortopt = 0;
204
205 if (!Bstrcasecmp(c+1, "?") || !Bstrcasecmp(c+1, "help") || !Bstrcasecmp(c+1, "-help"))
206 {
207 G_ShowParameterHelp();
208 Bexit(0);
209 }
210 if (!Bstrcasecmp(c+1, "addon"))
211 {
212 if (argc > i+1)
213 {
214 g_addonNum = Batoi(argv[i+1]);
215
216 if (g_addonNum > ADDON_NONE && g_addonNum < NUMADDONS)
217 g_noSetup = 1;
218 else g_addonNum = ADDON_NONE;
219
220 i++;
221 }
222 i++;
223 continue;
224 }
225 if (!Bstrcasecmp(c+1, "debughelp") || !Bstrcasecmp(c+1, "-debughelp"))
226 {
227 G_ShowDebugHelp();
228 Bexit(0);
229 }
230 if (!Bstrcasecmp(c+1, "grp") || !Bstrcasecmp(c+1, "g"))
231 {
232 if (argc > i+1)
233 {
234 G_AddGroup(argv[i+1]);
235 i++;
236 }
237 i++;
238 continue;
239 }
240 if (!Bstrcasecmp(c+1, "game_dir"))
241 {
242 if (argc > i+1)
243 {
244 Bstrncpyz(g_modDir, argv[i+1], sizeof(g_modDir));
245 G_AddPath(argv[i+1]);
246 i++;
247 }
248 i++;
249 continue;
250 }
251 if (!Bstrcasecmp(c+1, "cfg"))
252 {
253 if (argc > i+1)
254 {
255 Bstrcpy(g_setupFileName, argv[i+1]);
256 i++;
257 }
258 i++;
259 continue;
260 }
261 if (!Bstrcasecmp(c+1, "gamegrp"))
262 {
263 if (argc > i+1)
264 {
265 clearGrpNamePtr();
266 g_grpNamePtr = dup_filename(argv[i+1]);
267 i++;
268 }
269 i++;
270 continue;
271 }
272 if (!Bstrcasecmp(c+1, "nam"))
273 {
274 g_gameType = GAMEFLAG_NAM;
275 i++;
276 continue;
277 }
278 if (!Bstrcasecmp(c+1, "napalm"))
279 {
280 g_gameType = GAMEFLAG_NAM|GAMEFLAG_NAPALM;
281 i++;
282 continue;
283 }
284 if (!Bstrcasecmp(c+1, "setup"))
285 {
286 g_commandSetup = TRUE;
287 i++;
288 continue;
289 }
290 if (!Bstrcasecmp(c+1, "nosetup"))
291 {
292 g_noSetup = 1;
293 g_commandSetup = 0;
294 i++;
295 continue;
296 }
297 #if defined RENDERTYPEWIN
298 if (!Bstrcasecmp(c+1, "nodinput"))
299 {
300 initprintf("DirectInput (joystick) support disabled\n");
301 di_disabled = 1;
302 i++;
303 continue;
304 }
305 #endif
306 if (!Bstrcasecmp(c+1, "noautoload"))
307 {
308 initprintf("Autoload disabled\n");
309 g_noAutoLoad = 1;
310 i++;
311 continue;
312 }
313 #ifndef NETCODE_DISABLE
314 if (!Bstrcasecmp(c+1, "net"))
315 {
316 G_GameExit("EDuke32 no longer supports legacy networking.\n\n"
317 "If using YANG or other launchers that only support legacy netplay, download an older build of EDuke32. "
318 "Otherwise, run the following:\n\n"
319 "eduke32 -server\n\n"
320 "Other clients can then connect by typing \"connect [host]\" in the console.\n\n"
321 "EDuke32 will now close.");
322 }
323 #endif
324 if (!Bstrcasecmp(c+1, "port"))
325 {
326 if (argc > i+1)
327 {
328 g_netPort = Batoi(argv[i+1]);
329 i++;
330 }
331 i++;
332 continue;
333 }
334 #ifndef NETCODE_DISABLE
335 if (!Bstrcasecmp(c+1, "server"))
336 {
337 g_networkMode = NET_SERVER;
338 g_noSetup = g_noLogo = TRUE;
339 i++;
340 continue;
341 }
342 //if (!Bstrcasecmp(c+1, "dedicated"))
343 //{
344 // g_networkMode = NET_DEDICATED_SERVER;
345 // g_noSetup = g_noLogo = TRUE;
346 // i++;
347 // continue;
348 //}
349 if (!Bstrcasecmp(c+1, "connect"))
350 {
351 if (argc > i+1)
352 {
353 Net_Connect(argv[i+1]);
354 g_noSetup = g_noLogo = TRUE;
355 i++;
356 }
357 i++;
358 continue;
359 }
360 if (!Bstrcasecmp(c+1, "password"))
361 {
362 if (argc > i+1)
363 {
364 Bstrncpyz(g_netPassword, argv[i+1], sizeof(g_netPassword));
365 i++;
366 }
367 i++;
368 continue;
369 }
370 #endif
371 if (!Bstrcasecmp(c+1, "name"))
372 {
373 if (argc > i+1)
374 {
375 CommandName = argv[i+1];
376 i++;
377 }
378 i++;
379 continue;
380 }
381 if (!Bstrcasecmp(c+1, "map"))
382 {
383 if (argc > i+1)
384 {
385 CommandMap = argv[i+1];
386 i++;
387 }
388 i++;
389 continue;
390 }
391 if (!Bstrcasecmp(c+1, "rts"))
392 {
393 if (argc > i+1)
394 {
395 free(g_rtsNamePtr);
396 g_rtsNamePtr = dup_filename(argv[i+1]);
397 initprintf("Using RTS file \"%s\".\n", g_rtsNamePtr);
398 i++;
399 }
400 i++;
401 continue;
402 }
403 if (!Bstrcasecmp(c+1, "x"))
404 {
405 if (argc > i+1)
406 {
407 G_AddCon(argv[i+1]);
408 i++;
409 }
410 i++;
411 continue;
412 }
413 if (!Bstrcasecmp(c+1, "mx"))
414 {
415 if (argc > i+1)
416 {
417 G_AddConModule(argv[i+1]);
418 i++;
419 }
420 i++;
421 continue;
422 }
423 if (!Bstrcasecmp(c+1, "h"))
424 {
425 if (argc > i+1)
426 {
427 G_AddDef(argv[i+1]);
428 i++;
429 }
430 i++;
431 continue;
432 }
433 if (!Bstrcasecmp(c+1, "mh"))
434 {
435 if (argc > i+1)
436 {
437 G_AddDefModule(argv[i+1]);
438 i++;
439 }
440 i++;
441 continue;
442 }
443 if (!Bstrcasecmp(c+1, "j"))
444 {
445 if (argc > i+1)
446 {
447 G_AddPath(argv[i+1]);
448 i++;
449 }
450 i++;
451 continue;
452 }
453 if (!Bstrcasecmp(c+1, "d"))
454 {
455 if (argc > i+1)
456 {
457 G_AddDemo(argv[i+1]);
458 i++;
459 }
460 i++;
461 continue;
462 }
463 #ifdef HAVE_CLIPSHAPE_FEATURE
464 if (!Bstrcasecmp(c+1, "clipmap"))
465 {
466 if (argc > i+1)
467 {
468 G_AddClipMap(argv[i+1]);
469 i++;
470 }
471 i++;
472 continue;
473 }
474 #endif
475 if (!Bstrcasecmp(c+1, "condebug"))
476 {
477 g_scriptDebug = 1;
478 i++;
479 continue;
480 }
481 if (!Bstrcasecmp(c+1, "nologo"))
482 {
483 g_noLogo = 1;
484 i++;
485 continue;
486 }
487 if (!Bstrcasecmp(c+1, "rotatesprite-no-widescreen"))
488 {
489 g_rotatespriteNoWidescreen = 1;
490 i++;
491 continue;
492 }
493 if (!Bstrcasecmp(c+1, "usecwd"))
494 {
495 g_useCwd = 1;
496 i++;
497 continue;
498 }
499 if (!Bstrcasecmp(c+1, "cachesize"))
500 {
501 if (argc > i+1)
502 {
503 uint32_t j = Batol(argv[i+1]);
504 MAXCACHE1DSIZE = j<<10;
505 initprintf("Cache size: %dkB\n", j);
506 i++;
507 }
508 i++;
509 continue;
510 }
511 if (!Bstrcasecmp(c+1, "noinstancechecking"))
512 {
513 i++;
514 continue;
515 }
516 #if defined(RENDERTYPEWIN) && defined(USE_OPENGL)
517 if (!Bstrcasecmp(c+1, "forcegl"))
518 {
519 forcegl = 1;
520 i++;
521 continue;
522 }
523 #endif
524 // the following two dummy entries allow us to serve as a drop-in replacement for NAM on Steam
525 if (!Bstrcasecmp(c+1, "noconsole"))
526 {
527 i++;
528 continue;
529 }
530 if (!Bstrcasecmp(c+1, "conf"))
531 {
532 if (argc > i+1)
533 i++;
534 i++;
535 continue;
536 }
537 }
538
539 if ((*c == '-')
540 #ifdef _WIN32
541 || (*c == '/')
542 #endif
543 )
544 {
545 shortopt = 1;
546
547 c++;
548 switch (Btolower(*c))
549 {
550 case 'a':
551 ud.playerai = 1;
552 initprintf("Other player AI.\n");
553 break;
554 case 'c':
555 c++;
556 ud.m_coop = 0;
557 while ((*c >= '0')&&(*c <= '9'))
558 {
559 ud.m_coop *= 10;
560 ud.m_coop += *c - '0';
561 c++;
562 }
563 ud.m_coop--;
564 break;
565 case 'd':
566 {
567 c++;
568 if (*c)
569 G_AddDemo(c);
570 break;
571 }
572 case 'g':
573 c++;
574 if (*c)
575 G_AddGroup(c);
576 break;
577 case 'h':
578 c++;
579 if (*c)
580 G_AddDef(c);
581 break;
582 case 'j':
583 c++;
584 if (*c)
585 G_AddPath(c);
586 break;
587 case 'l':
588 // NOTE: Overlaid with -Lopts=... options for Lunatic, hence the check.
589 if (Bisdigit(c[1]))
590 {
591 ud.warp_on = 1;
592 c++;
593 ud.m_level_number = ud.level_number = ((unsigned) (Batoi(c)-1))%MAXLEVELS;
594 }
595 break;
596 case 'm':
597 if (*(c+1) != 'a' && *(c+1) != 'A')
598 {
599 ud.m_monsters_off = 1;
600 ud.m_player_skill = ud.player_skill = 0;
601 initprintf("Monsters off.\n");
602 }
603 break;
604 case 'n':
605 c++;
606 if (*c == 's' || *c == 'S')
607 {
608 g_noSound = 2;
609 initprintf("Sound off.\n");
610 }
611 else if (*c == 'm' || *c == 'M')
612 {
613 g_noMusic = 1;
614 initprintf("Music off.\n");
615 }
616 else
617 {
618 G_ShowParameterHelp();
619 exit(-1);
620 }
621 break;
622 case 'q':
623 if (*(++c) == 0)
624 {
625 ud.multimode = 1;
626 initprintf("Fake multiplayer mode: expected number after -q, falling back to 1 player.\n");
627 }
628 else
629 {
630 int32_t numpl = Batoi(c);
631
632 if (numpl < 2 || numpl > MAXPLAYERS)
633 {
634 initprintf("Fake multiplayer mode: expected 2-%d players, falling back to 1.\n",
635 MAXPLAYERS);
636 }
637 else
638 {
639 ud.multimode = numpl;
640 initprintf("Fake multiplayer mode: %d players.\n", ud.multimode);
641
642 g_fakeMultiMode = numpl;
643 }
644 }
645
646 ud.m_coop = ud.coop = 0;
647 ud.m_marker = ud.marker = 1;
648 ud.m_respawn_monsters = ud.respawn_monsters = 1;
649 ud.m_respawn_items = ud.respawn_items = 1;
650 ud.m_respawn_inventory = ud.respawn_inventory = 1;
651 break;
652 case 'r':
653 ud.m_recstat = 1;
654 initprintf("Demo record mode on.\n");
655 break;
656 case 's':
657 c++;
658 ud.m_player_skill = ud.player_skill = (Batoi(c)%5);
659 if (ud.m_player_skill == 4)
660 ud.m_respawn_monsters = ud.respawn_monsters = 1;
661 break;
662 case 't':
663 c++;
664 if (*c == '1') ud.m_respawn_monsters = 1;
665 else if (*c == '2') ud.m_respawn_items = 1;
666 else if (*c == '3') ud.m_respawn_inventory = 1;
667 else
668 {
669 ud.m_respawn_monsters = 1;
670 ud.m_respawn_items = 1;
671 ud.m_respawn_inventory = 1;
672 }
673 initprintf("Respawn on.\n");
674 break;
675 case 'u':
676 g_forceWeaponChoice = 1;
677 c++;
678 j = 0;
679 if (*c)
680 {
681 initprintf("Using favorite weapon order(s).\n");
682 while (*c)
683 {
684 g_player[0].wchoice[j] = *c-'0';
685 ud.wchoice[j] = *c;
686 c++;
687 j++;
688 }
689
690 while (j < 10)
691 {
692 if (j == 9)
693 {
694 g_player[0].wchoice[9] = 1;
695 ud.wchoice[9] = '1';
696 }
697 else
698 {
699 g_player[0].wchoice[j] = 2;
700 ud.wchoice[j] = '2';
701 }
702
703 j++;
704 }
705 }
706 else
707 {
708 initprintf("Using default weapon orders.\n");
709 g_player[0].wchoice[0] = 3;
710 g_player[0].wchoice[1] = 4;
711 g_player[0].wchoice[2] = 5;
712 g_player[0].wchoice[3] = 7;
713 g_player[0].wchoice[4] = 8;
714 g_player[0].wchoice[5] = 6;
715 g_player[0].wchoice[6] = 0;
716 g_player[0].wchoice[7] = 2;
717 g_player[0].wchoice[8] = 9;
718 g_player[0].wchoice[9] = 1;
719
720 Bsprintf(ud.wchoice, "3457860291");
721 }
722 break;
723 case 'v':
724 c++;
725 ud.warp_on = 1;
726 ud.m_volume_number = ud.volume_number = ((unsigned) (Batoi(c)-1))%MAXVOLUMES;
727 break;
728 case 'w':
729 ud.coords = 1;
730 break;
731 case 'x':
732 c++;
733 if (*c)
734 G_AddCon(c);
735 break;
736 case 'z':
737 c++;
738 g_scriptDebug = Batoi(c);
739 if (!g_scriptDebug)
740 g_scriptDebug = 1;
741 break;
742 default:
743 ignored_short_opt = 1;
744 break;
745 }
746 }
747 else
748 {
749 shortopt = 0;
750
751 k = Bstrrchr(c, '.');
752 if (k)
753 {
754 if (!Bstrcasecmp(k, ".map"))
755 {
756 CommandMap = argv[i++];
757 continue;
758 }
759 if (!Bstrcasecmp(k, ".grp") || !Bstrcasecmp(k, ".zip") || !Bstrcasecmp(k, ".pk3") || !Bstrcasecmp(k, ".pk4"))
760 {
761 G_AddGroup(argv[i++]);
762 continue;
763 }
764 if (!Bstrcasecmp(k, ".con"))
765 {
766 clearScriptNamePtr();
767 g_scriptNamePtr = dup_filename(argv[i++]);
768 initprintf("Using CON file \"%s\".\n", g_scriptNamePtr);
769 continue;
770 }
771 if (!Bstrcasecmp(k, ".def"))
772 {
773 clearDefNamePtr();
774 g_defNamePtr = dup_filename(argv[i++]);
775 initprintf("Using DEF file \"%s\".\n", g_defNamePtr);
776 continue;
777 }
778 if (!Bstrcasecmp(k, ".rts"))
779 {
780 free(g_rtsNamePtr);
781 g_rtsNamePtr = dup_filename(argv[i++]);
782 initprintf("Using RTS file \"%s\".\n", g_rtsNamePtr);
783 continue;
784 }
785 }
786 }
787
788 if (!shortopt || ignored_short_opt)
789 initprintf("Warning: ignored application parameter \"%s\".\n", oc);
790
791 i++;
792 } while (i < argc);
793 }
794 }
795