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