f6b86b1cedb6f876bb9d92e3747696183214e206
[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                         setwnd(BOTTOM_WINDOW);
203                         proutn("Help on what command? ");
204                         key = scan();
205                 }
206                 setwnd(LOWER_WINDOW);
207                 if (key == IHEOL) return;
208                 for (i = 0; i < NUMCOMMANDS; i++) {
209                     if (strcasecmp(commands[i].name, citem)==0) {
210                         i = commands[i].value;
211                         break;
212                     }
213                 }
214                 if (i != NUMCOMMANDS) break;
215                 skip(1);
216                 prout("Valid commands:");
217                 listCommands(FALSE);
218                 key = IHEOL;
219                 chew();
220                 skip(1);
221         }
222         if (i == COMMANDS) {
223                 strcpy(cmdbuf, " ABBREV");
224         }
225         else {
226             for (j = 0; commands[i].name[j]; j++)
227                 cmdbuf[j] = toupper(commands[i].name[j]);
228             cmdbuf[j] = '\0';
229         }
230         fp = fopen(SSTDOC, "r");
231         if (fp == NULL) {
232                 prout("Spock-  \"Captain, that information is missing from the");
233                 prout("   computer.\"");
234                 /*
235                  * This used to continue: "You need to find SST.DOC and put 
236                  * it in the current directory."
237                  */
238                 return;
239         }
240         for (;;) {
241             if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
242                         prout("Spock- \"Captain, there is no information on that command.\"");
243                         fclose(fp);
244                         return;
245                 }
246             if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
247                 for (cp = linebuf+3; isspace(*cp); cp++)
248                         continue;
249                 linebuf[strlen(linebuf)-1] = '\0';
250                 if (strcasecmp(cp, cmdbuf) == 0)
251                     break;
252             }
253         }
254
255         skip(1);
256         prout("Spock- \"Captain, I've found the following information:\"");
257         skip(1);
258
259         while (fgets(linebuf, sizeof(linebuf),fp)) {
260                 if (strstr(linebuf, "******"))
261                         break;
262                 proutc(linebuf);
263         }
264         fclose(fp);
265 }
266
267 void drawmaps(short l) {
268 /* hook to be called after moving to redraw maps */
269 #ifdef SERGEEV
270      _setcursortype(_NOCURSOR);
271      if (l==1) sensor();
272      if (l!=2) setwnd(LEFTUPPER_WINDOW);
273      gotoxy(1,1);
274      strcpy(line,"s");
275      srscan(1);
276      if (l!=2){
277         setwnd(SRSCAN_WINDOW);
278         clrscr();
279         srscan(2);
280         setwnd(LRSCAN_WINDOW);
281         clrscr();
282         strcpy(line,"l");
283         lrscan();
284         _setcursortype(_NORMALCURSOR);
285      }
286 #endif /* SERGEEV */
287 }
288
289 static void makemoves(void) {
290         int i, hitme;
291         clrscr();
292         setwnd(LOWER_WINDOW);
293         while (TRUE) { /* command loop */
294                 drawmaps(1);
295                 while (TRUE)  { /* get a command */
296                         hitme = FALSE;
297                         justin = 0;
298                         Time = 0.0;
299                         i = -1;
300                         chew();
301                         setwnd(BOTTOM_WINDOW);
302                         clrscr();
303                         proutn("COMMAND> ");
304                         if (scan() == IHEOL) {
305 #ifdef SERGEEV
306                             _setcursortype(_NOCURSOR);
307                             setwnd(LOWER_WINDOW);
308                             clrscr();
309                             chart(0);
310                             _setcursortype(_NORMALCURSOR);
311 #endif /* SERGEEV */
312                             continue;
313                         }
314                         ididit=0;
315                         clrscr();
316                         setwnd(LOWER_WINDOW);
317                         clrscr();
318                         for (i=0; i < ABANDON; i++)
319                             if (isit(commands[i].name)) {
320                                 i = commands[i].value;
321                                 break;
322                             }
323                         if (i < ABANDON) break;
324                         for (; i < NUMCOMMANDS; i++)
325                             if (strcasecmp(commands[i].name, citem) == 0) {
326                                     i = commands[i].value;
327                                     break;
328                             }
329                         if (i < NUMCOMMANDS) break;
330
331                         listCommands(TRUE);
332                 }
333                 commandhook(commands[i].name, TRUE);
334                 switch (i) { /* command switch */
335 #ifndef SERGEEV
336                         case SRSCAN:                 // srscan
337                                 srscan(1);
338                                 break;
339                         case LRSCAN:                    // lrscan
340                                 lrscan();
341                                 break;
342 #endif /* SERGEEV */
343                         case PHASERS:                   // phasers
344                                 phasers();
345                                 if (ididit) hitme = TRUE;
346                                 break;
347                         case TORPEDO:                   // photons
348                                 photon();
349                                 if (ididit) hitme = TRUE;
350                                 break;
351                         case MOVE:                      // move
352                                 warp(1);
353                                 break;
354                         case SHIELDS:                   // shields
355                                 doshield(1);
356                                 if (ididit) {
357                                         hitme=TRUE;
358                                         shldchg = 0;
359                                 }
360                                 break;
361                         case DOCK:                      // dock
362                                 dock(1);
363                                 if (ididit) attack(0);
364                                 break;
365                         case DAMAGES:                   // damages
366                                 dreprt();
367                                 break;
368                         case CHART:                     // chart
369                                 chart(0);
370                                 break;
371                         case IMPULSE:                   // impulse
372                                 impuls();
373                                 break;
374                         case REST:              // rest
375                                 wait();
376                                 if (ididit) hitme = TRUE;
377                                 break;
378                         case WARP:              // warp
379                                 setwrp();
380                                 break;
381                         case SCORE:                // score
382                                 score();
383                                 break;
384 #ifndef SERGEEV
385                         case SENSORS:                   // sensors
386                                 sensor();
387                                 break;
388 #endif /* SERGEEV */
389                         case ORBIT:                     // orbit
390                                 orbit();
391                                 if (ididit) hitme = TRUE;
392                                 break;
393                         case TRANSPORT:                 // transport "beam"
394                                 beam();
395                                 break;
396                         case MINE:                      // mine
397                                 mine();
398                                 if (ididit) hitme = TRUE;
399                                 break;
400                         case CRYSTALS:                  // crystals
401                                 usecrystals();
402                                 if (ididit) hitme = TRUE;
403                                 break;
404                         case SHUTTLE:                   // shuttle
405                                 shuttle();
406                                 if (ididit) hitme = TRUE;
407                                 break;
408                         case PLANETS:                   // Planet list
409                                 preport();
410                                 break;
411                         case REPORT:                    // Game Report 
412                                 report();
413                                 break;
414                         case COMPUTER:                  // use COMPUTER!
415                                 eta();
416                                 break;
417                         case COMMANDS:
418                                 listCommands(TRUE);
419                                 break;
420                         case EMEXIT:            // Emergency exit
421                                 clrscr();       // Hide screen
422                                 freeze(TRUE);   // forced save
423                                 exit(1);                // And quick exit
424                                 break;
425                         case PROBE:
426                                 probe();                // Launch probe
427                                 if (ididit) hitme = TRUE;
428                                 break;
429                         case ABANDON:                   // Abandon Ship
430                                 abandn();
431                                 break;
432                         case DESTRUCT:                  // Self Destruct
433                                 dstrct();
434                                 break;
435                         case SAVE:                      // Save Game
436                                 freeze(FALSE);
437                                 clrscr();
438                                 if (skill > 3)
439                                         prout("WARNING--Saved games produce no plaques!");
440                                 break;
441                         case DEATHRAY:          // Try a desparation measure
442                                 deathray();
443                                 if (ididit) hitme = TRUE;
444                                 break;
445                         case DEBUGCMD:          // What do we want for debug???
446 #ifdef DEBUG
447                                 debugme();
448 #endif
449                                 break;
450                         case MAYDAY:            // Call for help
451                                 help();
452                                 if (ididit) hitme = TRUE;
453                                 break;
454                         case QUIT:
455                                 alldone = 1;    // quit the game
456 #ifdef DEBUG
457                                 if (idebug) score();
458 #endif
459                                 break;
460                         case HELP:
461                                 helpme();       // get help
462                                 break;
463                 }
464                 commandhook(commands[i].name, FALSE);
465                 for (;;) {
466                         if (alldone) break;             // Game has ended
467 #ifdef DEBUG
468                         if (idebug) prout("2500");
469 #endif
470                         if (Time != 0.0) {
471                                 events();
472                                 if (alldone) break;             // Events did us in
473                         }
474                         if (game.state.galaxy[quadx][quady] == 1000) { // Galaxy went Nova!
475                                 atover(0);
476                                 continue;
477                         }
478                         if (hitme && justin==0) {
479                                 attack(2);
480                                 if (alldone) break;
481                                 if (game.state.galaxy[quadx][quady] == 1000) {  // went NOVA! 
482                                         atover(0);
483                                         hitme = TRUE;
484                                         continue;
485                                 }
486                         }
487                         break;
488                 }
489                 if (alldone) break;
490         }
491 }
492
493
494 int main(int argc, char **argv) {
495         int i, option, usecurses = TRUE;
496
497         while ((option = getopt(argc, argv, "t")) != -1) {
498             switch (option) {
499             case 't':
500                 usecurses = FALSE;
501                 break;
502             default:
503                 fprintf(stderr, "usage: sst [-t] [startcommand...].\n");
504                 exit(0);
505             }
506         }
507
508 #ifndef SERGEEV
509         iostart(usecurses);
510 #else
511         randomize();
512         textattr(7);
513         clrscr();
514         setwnd(FULLSCREEN_WINDOW);
515 #endif /* SERGEEV */
516         line[0] = '\0';
517         for (i = optind; i < argc;  i++) {
518                 strcat(line, argv[i]);
519                 strcat(line, " ");
520         }
521         while (TRUE) { /* Play a game */
522                 prelim();
523                 setup(line[0] == '\0');
524                 if (alldone) {
525                         score();
526                         alldone = 0;
527                 }
528                 else makemoves();
529                 skip(1);
530                 stars();
531                 skip(1);
532
533                 if (tourn && alldone) {
534                         proutn("Do you want your score recorded?");
535                         if (ja()) {
536                                 chew2();
537                                 freeze(FALSE);
538                         }
539                 }
540                 proutn("Do you want to play again? ");
541                 if (!ja()) break;
542                 setwnd(FULLSCREEN_WINDOW);
543                 clrscr();
544         }
545         skip(1);
546 #ifndef SERGEEV
547         ioend();
548 #endif /* SERGEEV */
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 iran8(int *i, int *j) {
615         *i = Rand()*8.0 + 1.0;
616         *j = Rand()*8.0 + 1.0;
617 }
618
619 void iran10(int *i, int *j) {
620         *i = Rand()*10.0 + 1.0;
621         *j = Rand()*10.0 + 1.0;
622 }
623
624 void chew(void) {
625         linep = line;
626         *linep = 0;
627 }
628
629 void chew2(void) {
630         /* return IHEOL next time */
631         linep = line+1;
632         *linep = 0;
633 }
634
635 int scan(void) {
636         int i;
637         char *cp;
638
639         // Init result
640         aaitem = 0.0;
641         *citem = 0;
642
643         // Read a line if nothing here
644         if (*linep == 0) {
645                 if (linep != line) {
646                         chew();
647                         return IHEOL;
648                 }
649                 cgetline(line, sizeof(line));
650 #ifdef SERGEEV
651                 fflush(stdin);
652                 if (curwnd==BOTTOM_WINDOW){
653                    clrscr();
654                    setwnd(LOWER_WINDOW);
655                    clrscr();
656                 }
657 #endif /* SERGEEV */
658                 linep = line;
659         }
660         // Skip leading white space
661         while (*linep == ' ') linep++;
662         // Nothing left
663         if (*linep == 0) {
664                 chew();
665                 return IHEOL;
666         }
667         if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
668                 // treat as a number
669             i = 0;
670             if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
671                 linep = line; // Invalid numbers are ignored
672                 *linep = 0;
673                 return IHEOL;
674             }
675             else {
676                 // skip to end
677                 linep += i;
678                 return IHREAL;
679             }
680         }
681         // Treat as alpha
682         cp = citem;
683         while (*linep && *linep!=' ') {
684                 if ((cp - citem) < 9) *cp++ = tolower(*linep);
685                 linep++;
686         }
687         *cp = 0;
688         return IHALPHA;
689 }
690
691 int ja(void) {
692         chew();
693         while (TRUE) {
694                 scan();
695                 chew();
696                 if (*citem == 'y') return TRUE;
697                 if (*citem == 'n') return FALSE;
698                 proutn("Please answer with \"Y\" or \"N\": ");
699         }
700 }
701
702 void huh(void) {
703         chew();
704         skip(1);
705         prout("Beg your pardon, Captain?");
706 }
707
708 int isit(char *s) {
709         /* New function -- compares s to scaned citem and returns true if it
710            matches to the length of s */
711
712         return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
713
714 }
715
716 #ifdef DEBUG
717 void debugme(void) {
718         proutn("Reset levels? ");
719         if (ja() != 0) {
720                 if (energy < inenrg) energy = inenrg;
721                 shield = inshld;
722                 torps = intorps;
723                 lsupres = inlsr;
724         }
725         proutn("Reset damage? ");
726         if (ja() != 0) {
727                 int i;
728                 for (i=0; i <= NDEVICES; i++) if (damage[i] > 0.0) damage[i] = 0.0;
729                 stdamtim = 1e30;
730         }
731         proutn("Toggle idebug? ");
732         if (ja() != 0) {
733                 idebug = !idebug;
734                 if (idebug) prout("Debug output ON");
735                 else prout("Debug output OFF");
736         }
737         proutn("Cause selective damage? ");
738         if (ja() != 0) {
739                 int i, key;
740                 for (i=1; i <= NDEVICES; i++) {
741                         proutn("Kill ");
742                         proutn(device[i]);
743                         proutn("? ");
744                         chew();
745                         key = scan();
746                         if (key == IHALPHA &&  isit("y")) {
747                                 damage[i] = 10.0;
748                                 if (i == DRADIO) stdamtim = game.state.date;
749                         }
750                 }
751         }
752         proutn("Examine/change events? ");
753         if (ja() != 0) {
754                 int i;
755                 for (i = 1; i < NEVENTS; i++) {
756                         int key;
757                         if (future[i] == 1e30) continue;
758                         switch (i) {
759                                 case FSNOVA:  proutn("Supernova       "); break;
760                                 case FTBEAM:  proutn("T Beam          "); break;
761                                 case FSNAP:   proutn("Snapshot        "); break;
762                                 case FBATTAK: proutn("Base Attack     "); break;
763                                 case FCDBAS:  proutn("Base Destroy    "); break;
764                                 case FSCMOVE: proutn("SC Move         "); break;
765                                 case FSCDBAS: proutn("SC Base Destroy "); break;
766                         }
767                         proutn("%.2f", future[i]-game.state.date);
768                         chew();
769                         proutn("  ?");
770                         key = scan();
771                         if (key == IHREAL) {
772                                 future[i] = game.state.date + aaitem;
773                         }
774                 }
775                 chew();
776         }
777 }
778                         
779
780 #endif