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