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