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