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