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