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