More factoring out.
[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 static void makemoves(void) {
268         int i, hitme;
269         clrscr();
270         setwnd(LOWER_WINDOW);
271         while (TRUE) { /* command loop */
272                 drawmaps(1);
273                 while (TRUE)  { /* get a command */
274                         hitme = FALSE;
275                         justin = 0;
276                         Time = 0.0;
277                         i = -1;
278                         chew();
279                         setwnd(BOTTOM_WINDOW);
280                         clrscr();
281                         proutn("COMMAND> ");
282                         if (scan() == IHEOL) {
283 #ifdef SERGEEV
284                             _setcursortype(_NOCURSOR);
285                             setwnd(LOWER_WINDOW);
286                             clrscr();
287                             chart(0);
288                             _setcursortype(_NORMALCURSOR);
289 #endif /* SERGEEV */
290                             continue;
291                         }
292                         ididit=0;
293                         clrscr();
294                         setwnd(LOWER_WINDOW);
295                         clrscr();
296                         for (i=0; i < ABANDON; i++)
297                             if (isit(commands[i].name)) {
298                                 i = commands[i].value;
299                                 break;
300                             }
301                         if (i < ABANDON) break;
302                         for (; i < NUMCOMMANDS; i++)
303                             if (strcasecmp(commands[i].name, citem) == 0) {
304                                     i = commands[i].value;
305                                     break;
306                             }
307                         if (i < NUMCOMMANDS) break;
308
309                         listCommands(TRUE);
310                 }
311                 commandhook(commands[i].name, TRUE);
312                 switch (i) { /* command switch */
313 #ifndef SERGEEV
314                         case SRSCAN:                 // srscan
315                                 srscan(1);
316                                 break;
317                         case LRSCAN:                    // lrscan
318                                 lrscan();
319                                 break;
320 #endif /* SERGEEV */
321                         case PHASERS:                   // phasers
322                                 phasers();
323                                 if (ididit) hitme = TRUE;
324                                 break;
325                         case TORPEDO:                   // photons
326                                 photon();
327                                 if (ididit) hitme = TRUE;
328                                 break;
329                         case MOVE:                      // move
330                                 warp(1);
331                                 break;
332                         case SHIELDS:                   // shields
333                                 doshield(1);
334                                 if (ididit) {
335                                         hitme=TRUE;
336                                         shldchg = 0;
337                                 }
338                                 break;
339                         case DOCK:                      // dock
340                                 dock(1);
341                                 if (ididit) attack(0);
342                                 break;
343                         case DAMAGES:                   // damages
344                                 dreprt();
345                                 break;
346                         case CHART:                     // chart
347                                 chart(0);
348                                 break;
349                         case IMPULSE:                   // impulse
350                                 impuls();
351                                 break;
352                         case REST:              // rest
353                                 wait();
354                                 if (ididit) hitme = TRUE;
355                                 break;
356                         case WARP:              // warp
357                                 setwrp();
358                                 break;
359                         case SCORE:                // score
360                                 score();
361                                 break;
362 #ifndef SERGEEV
363                         case SENSORS:                   // sensors
364                                 sensor();
365                                 break;
366 #endif /* SERGEEV */
367                         case ORBIT:                     // orbit
368                                 orbit();
369                                 if (ididit) hitme = TRUE;
370                                 break;
371                         case TRANSPORT:                 // transport "beam"
372                                 beam();
373                                 break;
374                         case MINE:                      // mine
375                                 mine();
376                                 if (ididit) hitme = TRUE;
377                                 break;
378                         case CRYSTALS:                  // crystals
379                                 usecrystals();
380                                 if (ididit) hitme = TRUE;
381                                 break;
382                         case SHUTTLE:                   // shuttle
383                                 shuttle();
384                                 if (ididit) hitme = TRUE;
385                                 break;
386                         case PLANETS:                   // Planet list
387                                 preport();
388                                 break;
389                         case REPORT:                    // Game Report 
390                                 report();
391                                 break;
392                         case COMPUTER:                  // use COMPUTER!
393                                 eta();
394                                 break;
395                         case COMMANDS:
396                                 listCommands(TRUE);
397                                 break;
398                         case EMEXIT:            // Emergency exit
399                                 clrscr();       // Hide screen
400                                 freeze(TRUE);   // forced save
401                                 exit(1);                // And quick exit
402                                 break;
403                         case PROBE:
404                                 probe();                // Launch probe
405                                 if (ididit) hitme = TRUE;
406                                 break;
407                         case ABANDON:                   // Abandon Ship
408                                 abandn();
409                                 break;
410                         case DESTRUCT:                  // Self Destruct
411                                 dstrct();
412                                 break;
413                         case SAVE:                      // Save Game
414                                 freeze(FALSE);
415                                 clrscr();
416                                 if (skill > 3)
417                                         prout("WARNING--Saved games produce no plaques!");
418                                 break;
419                         case DEATHRAY:          // Try a desparation measure
420                                 deathray();
421                                 if (ididit) hitme = TRUE;
422                                 break;
423                         case DEBUGCMD:          // What do we want for debug???
424 #ifdef DEBUG
425                                 debugme();
426 #endif
427                                 break;
428                         case MAYDAY:            // Call for help
429                                 help();
430                                 if (ididit) hitme = TRUE;
431                                 break;
432                         case QUIT:
433                                 alldone = 1;    // quit the game
434 #ifdef DEBUG
435                                 if (idebug) score();
436 #endif
437                                 break;
438                         case HELP:
439                                 helpme();       // get help
440                                 break;
441                 }
442                 commandhook(commands[i].name, FALSE);
443                 for (;;) {
444                         if (alldone) break;             // Game has ended
445 #ifdef DEBUG
446                         if (idebug) prout("2500");
447 #endif
448                         if (Time != 0.0) {
449                                 events();
450                                 if (alldone) break;             // Events did us in
451                         }
452                         if (game.state.galaxy[quadx][quady] == 1000) { // Galaxy went Nova!
453                                 atover(0);
454                                 continue;
455                         }
456                         if (hitme && justin==0) {
457                                 attack(2);
458                                 if (alldone) break;
459                                 if (game.state.galaxy[quadx][quady] == 1000) {  // went NOVA! 
460                                         atover(0);
461                                         hitme = TRUE;
462                                         continue;
463                                 }
464                         }
465                         break;
466                 }
467                 if (alldone) break;
468         }
469 }
470
471
472 int main(int argc, char **argv) {
473         int i, option, usecurses = TRUE;
474
475         while ((option = getopt(argc, argv, "t")) != -1) {
476             switch (option) {
477             case 't':
478                 usecurses = FALSE;
479                 break;
480             default:
481                 fprintf(stderr, "usage: sst [-t] [startcommand...].\n");
482                 exit(0);
483             }
484         }
485
486         randomize();
487         iostart(usecurses);
488
489         line[0] = '\0';
490         for (i = optind; i < argc;  i++) {
491                 strcat(line, argv[i]);
492                 strcat(line, " ");
493         }
494         while (TRUE) { /* Play a game */
495                 prelim();
496                 setup(line[0] == '\0');
497                 if (alldone) {
498                         score();
499                         alldone = 0;
500                 }
501                 else makemoves();
502                 skip(1);
503                 stars();
504                 skip(1);
505
506                 if (tourn && alldone) {
507                         proutn("Do you want your score recorded?");
508                         if (ja()) {
509                                 chew2();
510                                 freeze(FALSE);
511                         }
512                 }
513                 proutn("Do you want to play again? ");
514                 if (!ja()) break;
515                 setwnd(FULLSCREEN_WINDOW);
516                 clrscr();
517         }
518         skip(1);
519         ioend();
520         prout("May the Great Bird of the Galaxy roost upon your home planet.");
521         return 0;
522 }
523
524
525 void cramen(int i) {
526         /* return an enemy */
527         char *s;
528         
529         switch (i) {
530                 case IHR: s = "Romulan"; break;
531                 case IHK: s = "Klingon"; break;
532                 case IHC: s = "Commander"; break;
533                 case IHS: s = "Super-commander"; break;
534                 case IHSTAR: s = "Star"; break;
535                 case IHP: s = "Planet"; break;
536                 case IHB: s = "Starbase"; break;
537                 case IHBLANK: s = "Black hole"; break;
538                 case IHT: s = "Tholian"; break;
539                 case IHWEB: s = "Tholian web"; break;
540                 case IHQUEST: s = "Stranger"; break;
541                 default: s = "Unknown??"; break;
542         }
543         proutn(s);
544 }
545
546 char *cramlc(enum loctype key, int x, int y) {
547         static char buf[32];
548         buf[0] = '\0';
549         if (key == quadrant) strcpy(buf, "Quadrant ");
550         else if (key == sector) strcpy(buf, "Sector ");
551         sprintf(buf+strlen(buf), "%d - %d", x, y);
552         return buf;
553 }
554
555 void crmena(int i, int enemy, int key, int x, int y) {
556         if (i == 1) proutn("***");
557         cramen(enemy);
558         proutn(" at ");
559         proutn(cramlc(key, x, y));
560 }
561
562 void crmshp(void) {
563         char *s;
564         switch (ship) {
565                 case IHE: s = "Enterprise"; break;
566                 case IHF: s = "Faerie Queene"; break;
567                 default:  s = "Ship???"; break;
568         }
569         proutn(s);
570 }
571
572 void stars(void) {
573         prouts("******************************************************");
574         skip(1);
575 }
576
577 double expran(double avrage) {
578         return -avrage*log(1e-7 + Rand());
579 }
580
581 double Rand(void) {
582         return rand()/(1.0 + (double)RAND_MAX);
583 }
584
585 void iran8(int *i, int *j) {
586         *i = Rand()*8.0 + 1.0;
587         *j = Rand()*8.0 + 1.0;
588 }
589
590 void iran10(int *i, int *j) {
591         *i = Rand()*10.0 + 1.0;
592         *j = Rand()*10.0 + 1.0;
593 }
594
595 void chew(void) {
596         linep = line;
597         *linep = 0;
598 }
599
600 void chew2(void) {
601         /* return IHEOL next time */
602         linep = line+1;
603         *linep = 0;
604 }
605
606 int scan(void) {
607         int i;
608         char *cp;
609
610         // Init result
611         aaitem = 0.0;
612         *citem = 0;
613
614         // Read a line if nothing here
615         if (*linep == 0) {
616                 if (linep != line) {
617                         chew();
618                         return IHEOL;
619                 }
620                 cgetline(line, sizeof(line));
621                 fflush(stdin);
622                 if (curwnd==BOTTOM_WINDOW){
623                    clrscr();
624                    setwnd(LOWER_WINDOW);
625                    clrscr();
626                 }
627                 linep = line;
628         }
629         // Skip leading white space
630         while (*linep == ' ') linep++;
631         // Nothing left
632         if (*linep == 0) {
633                 chew();
634                 return IHEOL;
635         }
636         if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
637                 // treat as a number
638             i = 0;
639             if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
640                 linep = line; // Invalid numbers are ignored
641                 *linep = 0;
642                 return IHEOL;
643             }
644             else {
645                 // skip to end
646                 linep += i;
647                 return IHREAL;
648             }
649         }
650         // Treat as alpha
651         cp = citem;
652         while (*linep && *linep!=' ') {
653                 if ((cp - citem) < 9) *cp++ = tolower(*linep);
654                 linep++;
655         }
656         *cp = 0;
657         return IHALPHA;
658 }
659
660 int ja(void) {
661         chew();
662         while (TRUE) {
663                 scan();
664                 chew();
665                 if (*citem == 'y') return TRUE;
666                 if (*citem == 'n') return FALSE;
667                 proutn("Please answer with \"Y\" or \"N\": ");
668         }
669 }
670
671 void huh(void) {
672         chew();
673         skip(1);
674         prout("Beg your pardon, Captain?");
675 }
676
677 int isit(char *s) {
678         /* New function -- compares s to scaned citem and returns true if it
679            matches to the length of s */
680
681         return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
682
683 }
684
685 #ifdef DEBUG
686 void debugme(void) {
687         proutn("Reset levels? ");
688         if (ja() != 0) {
689                 if (energy < inenrg) energy = inenrg;
690                 shield = inshld;
691                 torps = intorps;
692                 lsupres = inlsr;
693         }
694         proutn("Reset damage? ");
695         if (ja() != 0) {
696                 int i;
697                 for (i=0; i <= NDEVICES; i++) if (damage[i] > 0.0) damage[i] = 0.0;
698                 stdamtim = 1e30;
699         }
700         proutn("Toggle idebug? ");
701         if (ja() != 0) {
702                 idebug = !idebug;
703                 if (idebug) prout("Debug output ON");
704                 else prout("Debug output OFF");
705         }
706         proutn("Cause selective damage? ");
707         if (ja() != 0) {
708                 int i, key;
709                 for (i=1; i <= NDEVICES; i++) {
710                         proutn("Kill ");
711                         proutn(device[i]);
712                         proutn("? ");
713                         chew();
714                         key = scan();
715                         if (key == IHALPHA &&  isit("y")) {
716                                 damage[i] = 10.0;
717                                 if (i == DRADIO) stdamtim = game.state.date;
718                         }
719                 }
720         }
721         proutn("Examine/change events? ");
722         if (ja() != 0) {
723                 int i;
724                 for (i = 1; i < NEVENTS; i++) {
725                         int key;
726                         if (future[i] == 1e30) continue;
727                         switch (i) {
728                                 case FSNOVA:  proutn("Supernova       "); break;
729                                 case FTBEAM:  proutn("T Beam          "); break;
730                                 case FSNAP:   proutn("Snapshot        "); break;
731                                 case FBATTAK: proutn("Base Attack     "); break;
732                                 case FCDBAS:  proutn("Base Destroy    "); break;
733                                 case FSCMOVE: proutn("SC Move         "); break;
734                                 case FSCDBAS: proutn("SC Base Destroy "); break;
735                         }
736                         proutn("%.2f", future[i]-game.state.date);
737                         chew();
738                         proutn("  ?");
739                         key = scan();
740                         if (key == IHREAL) {
741                                 future[i] = game.state.date + aaitem;
742                         }
743                 }
744                 chew();
745         }
746 }
747                         
748
749 #endif