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