Rewrote the command dispatcher.
[super-star-trek.git] / sst.c
1 #define INCLUDED        // Define externs here
2 #include <ctype.h>
3 #include <getopt.h>
4 #include <time.h>
5 #include "conio.h"
6 #include "sstlinux.h"
7 #include "sst.h"
8
9 #ifndef SSTDOC
10 #define SSTDOC  "sst.doc"
11 #endif
12         
13 static char line[128], *linep = line;
14
15 /*
16
17 Here are Tom Almy's changes:
18
19  Compared to original version, I've changed the "help" command to
20    "call" and the "terminate" command to "quit" to better match
21    user expectations. The DECUS version apparently made those changes
22    as well as changing "freeze" to "save". However I like "freeze".
23
24    When I got a later version of Super Star Trek that I was converting
25    from, I added the emexit command.
26
27    That later version also mentions srscan and lrscan working when
28    docked (using the starbase's scanners), so I made some changes here
29    to do this (and indicating that fact to the player), and then realized
30    the base would have a subspace radio as well -- doing a Chart when docked
31    updates the star chart, and all radio reports will be heard. The Dock
32    command will also give a report if a base is under attack.
33
34    Movecom no longer reports movement if sensors are damaged so you wouldn't
35    otherwise know it.
36
37    Also added:
38
39    1. Better base positioning at startup
40
41    2. Deathray improvement (but keeping original failure alternatives)
42
43    3. Tholian Web.
44
45    4. Enemies can ram the Enterprise. Regular Klingons and Romulans can
46       move in Expert and Emeritus games. This code could use improvement.
47
48    5. The deep space probe looks interesting! DECUS version
49
50    6. Perhaps cloaking to be added later? BSD version
51
52 Here are Stas Sergeev's changes (controlled by the proprocessor symbol
53 SERGEEV, not yet completely merged):
54
55    1. The Space Thingy can be shoved, if you it ram, and can fire back if 
56       fired upon.
57
58    1 The Tholian can be hit with phasers
59
60    2. When you are docked, base covers you with an almost invincible shields 
61       (a commander can still ram you, or a Romulan can destroy the base,
62       or a SCom can even succeed with direct attack IIRC, but this rarely 
63       happens).
64
65     3. SCom can't escape from you if no more enemies remain (without this, 
66        chasing SCom can take an eternity).
67
68     4. Probe target you enter is now the destination quadrant. Before I don't 
69        remember what it was, but it was something I had difficulty using)
70
71     5. Secret password is now autogenerated.
72
73     6. "Plaque" is adjusted for A4 paper:)
74
75     7. Phasers now tells you how much energy needed, but only if the computer 
76        is alive.
77
78     8. Planets are auto-scanned when you enter the quadrant.
79
80     9. Mining or using crystals in presense of enemy now yields an attack.
81        There are other minor adjustments to what yields an attack
82        and what does not.
83
84     10. Ramming a black hole is no longer instant death.  There is a
85         chance you might get timewarped instead.
86
87     11. "freeze" command reverts to "save", most people will understand this
88         better anyway.
89
90 Eric Raymond's changes:
91
92      1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
93
94    */
95
96 static struct 
97 {
98     char *name;
99     int value;
100 }
101 commands[] = {
102 #ifndef SERGEEV
103 #define SRSCAN  1
104         {"SRSCAN",      SRSCAN},
105         {"STATUS",      SRSCAN},
106 #define LRSCAN  2
107         {"LRSCAN",      LRSCAN},
108 #endif /* SERGEEV */
109 #define PHASERS 3
110         {"PHASERS",     PHASERS},
111 #define TORPEDO 4
112         {"TORPEDO",     TORPEDO},
113         {"PHOTONS",     TORPEDO},
114 #define MOVE    5
115         {"MOVE",        MOVE},
116 #define SHIELDS 6
117         {"SHIELDS",     SHIELDS},
118 #define DOCK    7
119         {"DOCK",        DOCK},
120 #define DAMAGES 8
121         {"DAMAGES",     DAMAGES},
122 #define CHART   9
123         {"CHART",       CHART},
124 #define IMPULSE 10
125         {"IMPULSE",     IMPULSE},
126 #define REST    11
127         {"REST",        REST},
128 #define WARP    12
129         {"WARP",        WARP},
130 #define SCORE   13
131         {"SCORE",       SCORE},
132 #ifndef SERGEEV
133 #define SENSORS 14
134         {"SENSORS",     SENSORS},
135 #endif /* SERGEEV */
136 #define ORBIT   15
137         {"ORBIT",       ORBIT},
138 #define TRANSPORT       17
139         {"TRANSPORT",   TRANSPORT},
140 #define MINE    18
141         {"MINE",        MINE},
142 #define CRYSTALS 19
143         {"CRYSTALS",    CRYSTALS},
144 #define SHUTTLE 20
145         {"SHUTTLE",     SHUTTLE},
146 #define PLANETS 21
147         {"PLANETS",     PLANETS},
148 #ifdef SERGEEV
149 #define REQUEST 22
150         {"REQUEST",     REQUEST},
151 #endif /* SERGEEV */
152 #define REPORT  23
153         {"REPORT",      REPORT},
154 #define COMPUTER        24
155         {"COMPUTER",    COMPUTER},
156 #define COMMANDS        25
157         {"COMMANDS",    COMMANDS},
158 #define EMEXIT  26
159         {"EMEXIT",      EMEXIT},
160 #define PROBE   27
161         {"PROBE",       PROBE},
162 #define SAVE    28
163         {"SAVE",        SAVE},
164         {"FREEZE",      SAVE},
165 #define ABANDON 29
166         {"ABANDON",     ABANDON},
167 #define DESTRUCT 30
168         {"DESTRUCT",    DESTRUCT},
169 #define DEATHRAY 31
170         {"DEATHRAY",    DEATHRAY},
171 #define DEBUGCMD        32
172         {"DEBUG",       DEBUGCMD},
173 #define MAYDAY  33
174         {"MAYDAY",      MAYDAY},
175         {"SOS",         MAYDAY},
176         {"CALL",        MAYDAY},
177 #define QUIT    34
178         {"QUIT",        QUIT},
179 #define HELP    35
180         {"HELP",        HELP},
181 };
182
183 #ifdef SERGEEV
184 wnd wnds[6]={{1,1,80,25},{1,1,25,12},{26,2,80,12},{65,1,80,10},{1,13,80,23},{1,24,80,25}};
185 short curwnd;
186 #endif /* SERGEEV */
187
188 #define NUMCOMMANDS     sizeof(commands)/sizeof(commands[0])
189
190 static void listCommands(int x) {
191     int i;
192     prout("LEGAL COMMANDS ARE:");
193     for (i = 0; i < NUMCOMMANDS; i++) {
194         proutn("%-12s ", commands[i].name);
195         if (i % 5 == 4)
196             skip(1);
197     }
198     skip(1);
199 }
200
201 static void helpme(void) {
202         int i, j;
203         char cmdbuf[32], *cp;
204         char linebuf[132];
205         FILE *fp;
206         /* Give help on commands */
207         int key;
208         key = scan();
209         while (TRUE) {
210                 if (key == IHEOL) {
211 #ifdef SERGEEV
212                         setwnd(5);
213 #endif /* SERGEEV */
214                         proutn("Help on what command? ");
215                         key = scan();
216                 }
217 #ifdef SERGEEV
218                 setwnd(4);
219 #endif /* SERGEEV */
220                 if (key == IHEOL) return;
221                 for (i = 0; i < NUMCOMMANDS; i++) {
222                     if (strcasecmp(commands[i].name, citem)==0) {
223                         i = commands[i].value;
224                         break;
225                     }
226                 }
227                 if (i != NUMCOMMANDS) break;
228                 skip(1);
229                 prout("Valid commands:");
230                 listCommands(FALSE);
231                 key = IHEOL;
232                 chew();
233                 skip(1);
234         }
235         if (i == COMMANDS) {
236                 strcpy(cmdbuf, " ABBREV");
237         }
238         else {
239             for (j = 0; commands[i].name[j]; j++)
240                 cmdbuf[j] = toupper(commands[i].name[j]);
241             cmdbuf[j] = '\0';
242         }
243         fp = fopen(SSTDOC, "r");
244         if (fp == NULL) {
245                 prout("Spock-  \"Captain, that information is missing from the");
246                 prout("   computer.\"");
247                 /*
248                  * This used to continue: "You need to find SST.DOC and put 
249                  * it in the current directory."
250                  */
251                 return;
252         }
253         for (;;) {
254             if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
255                         prout("Spock- \"Captain, there is no information on that command.\"");
256                         fclose(fp);
257                         return;
258                 }
259             if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
260                 for (cp = linebuf+3; isspace(*cp); cp++)
261                         continue;
262                 linebuf[strlen(linebuf)-1] = '\0';
263                 if (strcasecmp(cp, cmdbuf) == 0)
264                     break;
265             }
266         }
267
268         skip(1);
269         prout("Spock- \"Captain, I've found the following information:\"");
270         skip(1);
271
272         while (fgets(linebuf, sizeof(linebuf),fp)) {
273                 if (strstr(linebuf, "******"))
274                         break;
275                 proutc(linebuf);
276         }
277         fclose(fp);
278 }
279
280 void drawmaps(short l) {
281 /* hook to be called after moving to redraw maps */
282 #ifdef SERGEEV
283      _setcursortype(_NOCURSOR);
284      if (l==1) sensor();
285      if (l!=2) setwnd(1);
286      gotoxy(1,1);
287      strcpy(line,"s");
288      srscan(1);
289      if (l!=2){
290         setwnd(2);
291         clrscr();
292         srscan(2);
293         setwnd(3);
294         clrscr();
295         strcpy(line,"l");
296         lrscan();
297         _setcursortype(_NORMALCURSOR);
298      }
299 #endif /* SERGEEV */
300 }
301
302 static void makemoves(void) {
303         int i, hitme;
304 #ifdef SERGEEV
305         clrscr();
306         setwnd(4);
307 #endif /* SERGEEV */
308         while (TRUE) { /* command loop */
309                 drawmaps(1);
310                 while (TRUE)  { /* get a command */
311                         hitme = FALSE;
312                         justin = 0;
313                         Time = 0.0;
314                         i = -1;
315                         chew();
316 #ifdef SERGEEV
317                         setwnd(5);
318                         clrscr();
319 #endif /* SERGEEV */
320                         proutn("COMMAND> ");
321                         if (scan() == IHEOL) {
322 #ifdef SERGEEV
323                             _setcursortype(_NOCURSOR);
324                             setwnd(4);
325                             clrscr();
326                             chart(0);
327                             _setcursortype(_NORMALCURSOR);
328 #endif /* SERGEEV */
329                             continue;
330                         }
331 #ifdef SERGEEV
332                         ididit=0;
333                         clrscr();
334                         setwnd(4);
335                         clrscr();
336 #endif /* SERGEEV */
337                         for (i=0; i < ABANDON; i++)
338                             if (isit(commands[i].name)) {
339                                 i = commands[i].value;
340                                 break;
341                             }
342                         if (i < ABANDON) break;
343                         for (; i < NUMCOMMANDS; i++)
344                             if (strcasecmp(commands[i].name, citem) == 0) {
345                                     i = commands[i].value;
346                                     break;
347                             }
348                         if (i < NUMCOMMANDS) break;
349
350                         listCommands(TRUE);
351                 }
352                 commandhook(commands[i].name, TRUE);
353                 switch (i) { /* command switch */
354 #ifndef SERGEEV
355                         case SRSCAN:                 // srscan
356                                 srscan(1);
357                                 break;
358                         case LRSCAN:                    // lrscan
359                                 lrscan();
360                                 break;
361 #endif /* SERGEEV */
362                         case PHASERS:                   // phasers
363                                 phasers();
364                                 if (ididit) hitme = TRUE;
365                                 break;
366                         case TORPEDO:                   // photons
367                                 photon();
368                                 if (ididit) hitme = TRUE;
369                                 break;
370                         case MOVE:                      // move
371                                 warp(1);
372                                 break;
373                         case SHIELDS:                   // shields
374                                 doshield(1);
375                                 if (ididit) {
376                                         hitme=TRUE;
377                                         shldchg = 0;
378                                 }
379                                 break;
380                         case DOCK:                      // dock
381                                 dock(1);
382                                 if (ididit) attack(0);
383                                 break;
384                         case DAMAGES:                   // damages
385                                 dreprt();
386                                 break;
387                         case CHART:                     // chart
388                                 chart(0);
389                                 break;
390                         case IMPULSE:                   // impulse
391                                 impuls();
392                                 break;
393                         case REST:              // rest
394                                 wait();
395                                 if (ididit) hitme = TRUE;
396                                 break;
397                         case WARP:              // warp
398                                 setwrp();
399                                 break;
400                         case SCORE:                // score
401                                 score();
402                                 break;
403 #ifndef SERGEEV
404                         case SENSORS:                   // sensors
405                                 sensor();
406                                 break;
407 #endif /* SERGEEV */
408                         case ORBIT:                     // orbit
409                                 orbit();
410                                 if (ididit) hitme = TRUE;
411                                 break;
412                         case TRANSPORT:                 // transport "beam"
413                                 beam();
414                                 break;
415                         case MINE:                      // mine
416                                 mine();
417                                 if (ididit) hitme = TRUE;
418                                 break;
419                         case CRYSTALS:                  // crystals
420                                 usecrystals();
421                                 if (ididit) hitme = TRUE;
422                                 break;
423                         case SHUTTLE:                   // shuttle
424                                 shuttle();
425                                 if (ididit) hitme = TRUE;
426                                 break;
427                         case PLANETS:                   // Planet list
428                                 preport();
429                                 break;
430                         case REPORT:                    // Game Report 
431                                 report();
432                                 break;
433                         case COMPUTER:                  // use COMPUTER!
434                                 eta();
435                                 break;
436                         case COMMANDS:
437                                 listCommands(TRUE);
438                                 break;
439                         case EMEXIT:            // Emergency exit
440                                 clrscr();       // Hide screen
441                                 freeze(TRUE);   // forced save
442                                 exit(1);                // And quick exit
443                                 break;
444                         case PROBE:
445                                 probe();                // Launch probe
446                                 if (ididit) hitme = TRUE;
447                                 break;
448                         case ABANDON:                   // Abandon Ship
449                                 abandn();
450                                 break;
451                         case DESTRUCT:                  // Self Destruct
452                                 dstrct();
453                                 break;
454                         case SAVE:                      // Save Game
455                                 freeze(FALSE);
456 #ifdef SERGEEV
457                                 clrscr();
458 #endif /* SERGEEV */
459                                 if (skill > 3)
460                                         prout("WARNING--Saved games produce no plaques!");
461                                 break;
462                         case DEATHRAY:          // Try a desparation measure
463                                 deathray();
464                                 if (ididit) hitme = TRUE;
465                                 break;
466                         case DEBUGCMD:          // What do we want for debug???
467 #ifdef DEBUG
468                                 debugme();
469 #endif
470                                 break;
471                         case MAYDAY:            // Call for help
472                                 help();
473                                 if (ididit) hitme = TRUE;
474                                 break;
475                         case QUIT:
476                                 alldone = 1;    // quit the game
477 #ifdef DEBUG
478                                 if (idebug) score();
479 #endif
480                                 break;
481                         case HELP:
482                                 helpme();       // get help
483                                 break;
484                 }
485                 commandhook(commands[i].name, FALSE);
486                 for (;;) {
487                         if (alldone) break;             // Game has ended
488 #ifdef DEBUG
489                         if (idebug) prout("2500");
490 #endif
491                         if (Time != 0.0) {
492                                 events();
493                                 if (alldone) break;             // Events did us in
494                         }
495                         if (game.state.galaxy[quadx][quady] == 1000) { // Galaxy went Nova!
496                                 atover(0);
497                                 continue;
498                         }
499                         if (hitme && justin==0) {
500                                 attack(2);
501                                 if (alldone) break;
502                                 if (game.state.galaxy[quadx][quady] == 1000) {  // went NOVA! 
503                                         atover(0);
504                                         hitme = TRUE;
505                                         continue;
506                                 }
507                         }
508                         break;
509                 }
510                 if (alldone) break;
511         }
512 }
513
514
515 int main(int argc, char **argv) {
516         int i, option, usecurses = TRUE;
517
518         while ((option = getopt(argc, argv, "t")) != -1) {
519             switch (option) {
520             case 't':
521                 usecurses = FALSE;
522                 break;
523             default:
524                 fprintf(stderr, "usage: sst [-t] [startcommand...].\n");
525                 exit(0);
526             }
527         }
528
529 #ifndef SERGEEV
530         iostart(usecurses);
531 #else
532         randomize();
533         textattr(7);
534         clrscr();
535         setwnd(0);
536 #endif /* SERGEEV */
537         line[0] = '\0';
538         for (i = optind; i < argc;  i++) {
539                 strcat(line, argv[i]);
540                 strcat(line, " ");
541         }
542         while (TRUE) { /* Play a game */
543                 prelim();
544                 setup(line[0] == '\0');
545                 if (alldone) {
546                         score();
547                         alldone = 0;
548                 }
549                 else makemoves();
550                 skip(1);
551                 stars();
552                 skip(1);
553
554                 if (tourn && alldone) {
555                         proutn("Do you want your score recorded?");
556                         if (ja()) {
557                                 chew2();
558                                 freeze(FALSE);
559                         }
560                 }
561                 proutn("Do you want to play again? ");
562                 if (!ja()) break;
563 #ifdef SERGEEV
564                 setwnd(0);
565                 clrscr();
566 #endif /* SERGEEV */
567         }
568         skip(1);
569 #ifndef SERGEEV
570         ioend();
571 #endif /* SERGEEV */
572         prout("May the Great Bird of the Galaxy roost upon your home planet.");
573         return 0;
574 }
575
576
577 void cramen(int i) {
578         /* return an enemy */
579         char *s;
580         
581         switch (i) {
582                 case IHR: s = "Romulan"; break;
583                 case IHK: s = "Klingon"; break;
584                 case IHC: s = "Commander"; break;
585                 case IHS: s = "Super-commander"; break;
586                 case IHSTAR: s = "Star"; break;
587                 case IHP: s = "Planet"; break;
588                 case IHB: s = "Starbase"; break;
589                 case IHBLANK: s = "Black hole"; break;
590                 case IHT: s = "Tholian"; break;
591                 case IHWEB: s = "Tholian web"; break;
592                 case IHQUEST: s = "Stranger"; break;
593                 default: s = "Unknown??"; break;
594         }
595         proutn(s);
596 }
597
598 char *cramlc(enum loctype key, int x, int y) {
599         static char buf[32];
600         buf[0] = '\0';
601         if (key == quadrant) strcpy(buf, "Quadrant ");
602         else if (key == sector) strcpy(buf, "Sector ");
603         sprintf(buf+strlen(buf), "%d - %d", x, y);
604         return buf;
605 }
606
607 void crmena(int i, int enemy, int key, int x, int y) {
608         if (i == 1) proutn("***");
609         cramen(enemy);
610         proutn(" at ");
611         proutn(cramlc(key, x, y));
612 }
613
614 void crmshp(void) {
615         char *s;
616         switch (ship) {
617                 case IHE: s = "Enterprise"; break;
618                 case IHF: s = "Faerie Queene"; break;
619                 default:  s = "Ship???"; break;
620         }
621         proutn(s);
622 }
623
624 void stars(void) {
625         prouts("******************************************************");
626         skip(1);
627 }
628
629 double expran(double avrage) {
630         return -avrage*log(1e-7 + Rand());
631 }
632
633 double Rand(void) {
634         return rand()/(1.0 + (double)RAND_MAX);
635 }
636
637 void iran8(int *i, int *j) {
638         *i = Rand()*8.0 + 1.0;
639         *j = Rand()*8.0 + 1.0;
640 }
641
642 void iran10(int *i, int *j) {
643         *i = Rand()*10.0 + 1.0;
644         *j = Rand()*10.0 + 1.0;
645 }
646
647 void chew(void) {
648         linep = line;
649         *linep = 0;
650 }
651
652 void chew2(void) {
653         /* return IHEOL next time */
654         linep = line+1;
655         *linep = 0;
656 }
657
658 int scan(void) {
659         int i;
660         char *cp;
661
662         // Init result
663         aaitem = 0.0;
664         *citem = 0;
665
666         // Read a line if nothing here
667         if (*linep == 0) {
668                 if (linep != line) {
669                         chew();
670                         return IHEOL;
671                 }
672                 getline(line, sizeof(line));
673 #ifdef SERGEEV
674                 fflush(stdin);
675                 if (curwnd==5){
676                    clrscr();
677                    setwnd(4);
678                    clrscr();
679                 }
680 #endif /* SERGEEV */
681                 linep = line;
682         }
683         // Skip leading white space
684         while (*linep == ' ') linep++;
685         // Nothing left
686         if (*linep == 0) {
687                 chew();
688                 return IHEOL;
689         }
690         if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
691                 // treat as a number
692             i = 0;
693             if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
694                 linep = line; // Invalid numbers are ignored
695                 *linep = 0;
696                 return IHEOL;
697             }
698             else {
699                 // skip to end
700                 linep += i;
701                 return IHREAL;
702             }
703         }
704         // Treat as alpha
705         cp = citem;
706         while (*linep && *linep!=' ') {
707                 if ((cp - citem) < 9) *cp++ = tolower(*linep);
708                 linep++;
709         }
710         *cp = 0;
711         return IHALPHA;
712 }
713
714 int ja(void) {
715         chew();
716         while (TRUE) {
717                 scan();
718                 chew();
719                 if (*citem == 'y') return TRUE;
720                 if (*citem == 'n') return FALSE;
721                 proutn("Please answer with \"Y\" or \"N\": ");
722         }
723 }
724
725 void huh(void) {
726         chew();
727         skip(1);
728         prout("Beg your pardon, Captain?");
729 }
730
731 int isit(char *s) {
732         /* New function -- compares s to scaned citem and returns true if it
733            matches to the length of s */
734
735         return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
736
737 }
738
739 #ifdef DEBUG
740 void debugme(void) {
741         proutn("Reset levels? ");
742         if (ja() != 0) {
743                 if (energy < inenrg) energy = inenrg;
744                 shield = inshld;
745                 torps = intorps;
746                 lsupres = inlsr;
747         }
748         proutn("Reset damage? ");
749         if (ja() != 0) {
750                 int i;
751                 for (i=0; i <= NDEVICES; i++) if (damage[i] > 0.0) damage[i] = 0.0;
752                 stdamtim = 1e30;
753         }
754         proutn("Toggle idebug? ");
755         if (ja() != 0) {
756                 idebug = !idebug;
757                 if (idebug) prout("Debug output ON");
758                 else prout("Debug output OFF");
759         }
760         proutn("Cause selective damage? ");
761         if (ja() != 0) {
762                 int i, key;
763                 for (i=1; i <= NDEVICES; i++) {
764                         proutn("Kill ");
765                         proutn(device[i]);
766                         proutn("? ");
767                         chew();
768                         key = scan();
769                         if (key == IHALPHA &&  isit("y")) {
770                                 damage[i] = 10.0;
771                                 if (i == DRADIO) stdamtim = game.state.date;
772                         }
773                 }
774         }
775         proutn("Examine/change events? ");
776         if (ja() != 0) {
777                 int i;
778                 for (i = 1; i < NEVENTS; i++) {
779                         int key;
780                         if (future[i] == 1e30) continue;
781                         switch (i) {
782                                 case FSNOVA:  proutn("Supernova       "); break;
783                                 case FTBEAM:  proutn("T Beam          "); break;
784                                 case FSNAP:   proutn("Snapshot        "); break;
785                                 case FBATTAK: proutn("Base Attack     "); break;
786                                 case FCDBAS:  proutn("Base Destroy    "); break;
787                                 case FSCMOVE: proutn("SC Move         "); break;
788                                 case FSCDBAS: proutn("SC Base Destroy "); break;
789                         }
790                         proutn("%.2f", future[i]-game.state.date);
791                         chew();
792                         proutn("  ?");
793                         key = scan();
794                         if (key == IHREAL) {
795                                 future[i] = game.state.date + aaitem;
796                         }
797                 }
798                 chew();
799         }
800 }
801                         
802
803 #endif