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