This completely merges the line-oriented and screen I/O managers,
[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 "sstlinux.h"
6 #include "sst.h"
7
8 #ifndef SSTDOC
9 #define SSTDOC  "sst.doc"
10 #endif
11         
12 /*
13
14 Here are Tom Almy's changes:
15
16  Compared to original version, I've changed the "help" command to
17    "call" and the "terminate" command to "quit" to better match
18    user expectations. The DECUS version apparently made those changes
19    as well as changing "freeze" to "save". However I like "freeze".
20
21    When I got a later version of Super Star Trek that I was converting
22    from, I added the emexit command.
23
24    That later version also mentions srscan and lrscan working when
25    docked (using the starbase's scanners), so I made some changes here
26    to do this (and indicating that fact to the player), and then realized
27    the base would have a subspace radio as well -- doing a Chart when docked
28    updates the star chart, and all radio reports will be heard. The Dock
29    command will also give a report if a base is under attack.
30
31    Movecom no longer reports movement if sensors are damaged so you wouldn't
32    otherwise know it.
33
34    Also added:
35
36    1. Better base positioning at startup
37
38    2. Deathray improvement (but keeping original failure alternatives)
39
40    3. Tholian Web.
41
42    4. Enemies can ram the Enterprise. Regular Klingons and Romulans can
43       move in Expert and Emeritus games. This code could use improvement.
44
45    5. The deep space probe looks interesting! DECUS version
46
47    6. Perhaps cloaking to be added later? BSD version
48
49 Here are Stas Sergeev's changes:
50
51    1. The Space Thingy can be shoved, if you it ram, and can fire back if 
52       fired upon.
53
54    2. The Tholian can be hit with phasers
55
56    3. When you are docked, base covers you with an almost invincible shields 
57       (a commander can still ram you, or a Romulan can destroy the base,
58       or a SCom can even succeed with direct attack IIRC, but this rarely 
59       happens).
60
61    4. SCom can't escape from you if no more enemies remain (without this, 
62       chasing SCom can take an eternity).
63
64    5. Probe target you enter is now the destination quadrant. Before I don't 
65       remember what it was, but it was something I had difficulty using)
66
67    6. Secret password is now autogenerated.
68
69    7. "Plaque" is adjusted for A4 paper :-)
70
71    8. Phasers now tells you how much energy needed, but only if the computer 
72        is alive.
73
74    9. Planets are auto-scanned when you enter the quadrant.
75
76    10. Mining or using crystals in presense of enemy now yields an attack.
77        There are other minor adjustments to what yields an attack
78        and what does not.
79
80    11. Ramming a black hole is no longer instant death.  There is a
81        chance you might get timewarped instead.
82
83    12. "freeze" command reverts to "save", most people will understand this
84         better anyway.
85
86    13. Screen-oriented interface, with sensor scans always up.
87
88 Eric Raymond's changes:
89
90    1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
91
92    */
93
94 /* the input queue */
95 static char line[128], *linep = line;
96
97 static struct 
98 {
99     char *name;
100     int value;
101 }
102 commands[] = {
103 #define SRSCAN  1
104         {"SRSCAN",      SRSCAN},
105 #define STATUS  2
106         {"STATUS",      STATUS},
107 #define REQUEST 3
108         {"REQUEST",     REQUEST},
109 #define LRSCAN  4
110         {"LRSCAN",      LRSCAN},
111 #define PHASERS 5
112         {"PHASERS",     PHASERS},
113 #define TORPEDO 6
114         {"TORPEDO",     TORPEDO},
115         {"PHOTONS",     TORPEDO},
116 #define MOVE    7
117         {"MOVE",        MOVE},
118 #define SHIELDS 8
119         {"SHIELDS",     SHIELDS},
120 #define DOCK    9
121         {"DOCK",        DOCK},
122 #define DAMAGES 10
123         {"DAMAGES",     DAMAGES},
124 #define CHART   11
125         {"CHART",       CHART},
126 #define IMPULSE 12
127         {"IMPULSE",     IMPULSE},
128 #define REST    13
129         {"REST",        REST},
130 #define WARP    14
131         {"WARP",        WARP},
132 #define SCORE   15
133         {"SCORE",       SCORE},
134 #define SENSORS 16
135         {"SENSORS",     SENSORS},
136 #define ORBIT   17
137         {"ORBIT",       ORBIT},
138 #define TRANSPORT       18
139         {"TRANSPORT",   TRANSPORT},
140 #define MINE    19
141         {"MINE",        MINE},
142 #define CRYSTALS        20
143         {"CRYSTALS",    CRYSTALS},
144 #define SHUTTLE 21
145         {"SHUTTLE",     SHUTTLE},
146 #define PLANETS 22
147         {"PLANETS",     PLANETS},
148 #define REPORT  23
149         {"REPORT",      REPORT},
150 #define COMPUTER        24
151         {"COMPUTER",    COMPUTER},
152 #define COMMANDS        25
153         {"COMMANDS",    COMMANDS},
154 #define EMEXIT  26
155         {"EMEXIT",      EMEXIT},
156 #define PROBE   27
157         {"PROBE",       PROBE},
158 #define SAVE    28
159         {"SAVE",        SAVE},
160         {"FREEZE",      SAVE},
161 #define ABANDON 29
162         {"ABANDON",     ABANDON},
163 #define DESTRUCT        30
164         {"DESTRUCT",    DESTRUCT},
165 #define DEATHRAY        31
166         {"DEATHRAY",    DEATHRAY},
167 #define DEBUGCMD        32
168         {"DEBUG",       DEBUGCMD},
169 #define MAYDAY  33
170         {"MAYDAY",      MAYDAY},
171         {"SOS",         MAYDAY},
172         {"CALL",        MAYDAY},
173 #define QUIT    34
174         {"QUIT",        QUIT},
175 #define HELP    35
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                 proutn(linebuf);
263         }
264         fclose(fp);
265 }
266
267 void enqueue(char *s) {
268     strcpy(line, s);
269 }
270
271 static void makemoves(void) {
272         int i, hitme;
273         clrscr();
274         setwnd(LOWER_WINDOW);
275         while (TRUE) { /* command loop */
276                 drawmaps(1);
277                 while (TRUE)  { /* get a command */
278                         hitme = FALSE;
279                         justin = 0;
280                         Time = 0.0;
281                         i = -1;
282                         chew();
283                         setwnd(BOTTOM_WINDOW);
284                         clrscr();
285                         proutn("COMMAND> ");
286                         if (scan() == IHEOL) {
287                             makechart();
288                             continue;
289                         }
290                         ididit=0;
291                         clrscr();
292                         setwnd(LOWER_WINDOW);
293                         clrscr();
294                         for (i=0; i < ABANDON; i++)
295                             if (isit(commands[i].name)) {
296                                 i = commands[i].value;
297                                 break;
298                             }
299                         if (i < ABANDON) break;
300                         for (; i < NUMCOMMANDS; i++)
301                             if (strcasecmp(commands[i].name, citem) == 0) {
302                                     i = commands[i].value;
303                                     break;
304                             }
305                         if (i < NUMCOMMANDS) break;
306
307                         listCommands(TRUE);
308                 }
309                 commandhook(commands[i].name, TRUE);
310                 switch (i) { /* command switch */
311                         case SRSCAN:                 // srscan
312                                 srscan(SCAN_FULL);
313                                 break;
314                         case STATUS:                 // status
315                                 srscan(SCAN_STATUS);
316                                 break;
317                         case REQUEST:                   // status request 
318                                 srscan(SCAN_REQUEST);
319                                 break;
320                         case LRSCAN:                    // lrscan
321                                 lrscan();
322                                 break;
323                         case PHASERS:                   // phasers
324                                 phasers();
325                                 if (ididit) hitme = TRUE;
326                                 break;
327                         case TORPEDO:                   // photons
328                                 photon();
329                                 if (ididit) hitme = TRUE;
330                                 break;
331                         case MOVE:                      // move
332                                 warp(1);
333                                 break;
334                         case SHIELDS:                   // shields
335                                 doshield(1);
336                                 if (ididit) {
337                                         hitme=TRUE;
338                                         shldchg = 0;
339                                 }
340                                 break;
341                         case DOCK:                      // dock
342                                 dock(1);
343                                 if (ididit) attack(0);
344                                 break;
345                         case DAMAGES:                   // damages
346                                 dreprt();
347                                 break;
348                         case CHART:                     // chart
349                                 chart(0);
350                                 break;
351                         case IMPULSE:                   // impulse
352                                 impuls();
353                                 break;
354                         case REST:              // rest
355                                 wait();
356                                 if (ididit) hitme = TRUE;
357                                 break;
358                         case WARP:              // warp
359                                 setwrp();
360                                 break;
361                         case SCORE:                // score
362                                 score();
363                                 break;
364                         case SENSORS:                   // sensors
365                                 sensor();
366                                 break;
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                 setwnd(FULLSCREEN_WINDOW);
496                 clrscr();
497                 prelim();
498                 setup(line[0] == '\0');
499                 if (alldone) {
500                         score();
501                         alldone = 0;
502                 }
503                 else makemoves();
504                 skip(1);
505                 stars();
506                 skip(1);
507
508                 if (tourn && alldone) {
509                         proutn("Do you want your score recorded?");
510                         if (ja()) {
511                                 chew2();
512                                 freeze(FALSE);
513                         }
514                 }
515                 proutn("Do you want to play again? ");
516                 if (!ja()) break;
517         }
518         skip(1);
519         prout("May the Great Bird of the Galaxy roost upon your home planet.");
520         return 0;
521 }
522
523
524 void cramen(int i) {
525         /* return an enemy */
526         char *s;
527         
528         switch (i) {
529                 case IHR: s = "Romulan"; break;
530                 case IHK: s = "Klingon"; break;
531                 case IHC: s = "Commander"; break;
532                 case IHS: s = "Super-commander"; break;
533                 case IHSTAR: s = "Star"; break;
534                 case IHP: s = "Planet"; break;
535                 case IHB: s = "Starbase"; break;
536                 case IHBLANK: s = "Black hole"; break;
537                 case IHT: s = "Tholian"; break;
538                 case IHWEB: s = "Tholian web"; break;
539                 case IHQUEST: s = "Stranger"; break;
540                 default: s = "Unknown??"; break;
541         }
542         proutn(s);
543 }
544
545 char *cramlc(enum loctype key, int x, int y) {
546         static char buf[32];
547         buf[0] = '\0';
548         if (key == quadrant) strcpy(buf, "Quadrant ");
549         else if (key == sector) strcpy(buf, "Sector ");
550         sprintf(buf+strlen(buf), "%d - %d", x, y);
551         return buf;
552 }
553
554 void crmena(int i, int enemy, int key, int x, int y) {
555         if (i == 1) proutn("***");
556         cramen(enemy);
557         proutn(" at ");
558         proutn(cramlc(key, x, y));
559 }
560
561 void crmshp(void) {
562         char *s;
563         switch (ship) {
564                 case IHE: s = "Enterprise"; break;
565                 case IHF: s = "Faerie Queene"; break;
566                 default:  s = "Ship???"; break;
567         }
568         proutn(s);
569 }
570
571 void stars(void) {
572         prouts("******************************************************");
573         skip(1);
574 }
575
576 double expran(double avrage) {
577         return -avrage*log(1e-7 + Rand());
578 }
579
580 double Rand(void) {
581         return rand()/(1.0 + (double)RAND_MAX);
582 }
583
584 void iran8(int *i, int *j) {
585         *i = Rand()*8.0 + 1.0;
586         *j = Rand()*8.0 + 1.0;
587 }
588
589 void iran10(int *i, int *j) {
590         *i = Rand()*10.0 + 1.0;
591         *j = Rand()*10.0 + 1.0;
592 }
593
594 void chew(void) {
595         linep = line;
596         *linep = 0;
597 }
598
599 void chew2(void) {
600         /* return IHEOL next time */
601         linep = line+1;
602         *linep = 0;
603 }
604
605 int scan(void) {
606         int i;
607         char *cp;
608
609         // Init result
610         aaitem = 0.0;
611         *citem = 0;
612
613         // Read a line if nothing here
614         if (*linep == 0) {
615                 if (linep != line) {
616                         chew();
617                         return IHEOL;
618                 }
619                 cgetline(line, sizeof(line));
620                 fflush(stdin);
621                 if (curwnd==BOTTOM_WINDOW){
622                    clrscr();
623                    setwnd(LOWER_WINDOW);
624                    clrscr();
625                 }
626                 linep = line;
627         }
628         // Skip leading white space
629         while (*linep == ' ') linep++;
630         // Nothing left
631         if (*linep == 0) {
632                 chew();
633                 return IHEOL;
634         }
635         if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
636                 // treat as a number
637             i = 0;
638             if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
639                 linep = line; // Invalid numbers are ignored
640                 *linep = 0;
641                 return IHEOL;
642             }
643             else {
644                 // skip to end
645                 linep += i;
646                 return IHREAL;
647             }
648         }
649         // Treat as alpha
650         cp = citem;
651         while (*linep && *linep!=' ') {
652                 if ((cp - citem) < 9) *cp++ = tolower(*linep);
653                 linep++;
654         }
655         *cp = 0;
656         return IHALPHA;
657 }
658
659 int ja(void) {
660         chew();
661         while (TRUE) {
662                 scan();
663                 chew();
664                 if (*citem == 'y') return TRUE;
665                 if (*citem == 'n') return FALSE;
666                 proutn("Please answer with \"Y\" or \"N\": ");
667         }
668 }
669
670 void huh(void) {
671         chew();
672         skip(1);
673         prout("Beg your pardon, Captain?");
674 }
675
676 int isit(char *s) {
677         /* New function -- compares s to scaned citem and returns true if it
678            matches to the length of s */
679
680         return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
681
682 }
683
684 #ifdef DEBUG
685 void debugme(void) {
686         proutn("Reset levels? ");
687         if (ja() != 0) {
688                 if (energy < inenrg) energy = inenrg;
689                 shield = inshld;
690                 torps = intorps;
691                 lsupres = inlsr;
692         }
693         proutn("Reset damage? ");
694         if (ja() != 0) {
695                 int i;
696                 for (i=0; i <= NDEVICES; i++) if (damage[i] > 0.0) damage[i] = 0.0;
697                 stdamtim = 1e30;
698         }
699         proutn("Toggle idebug? ");
700         if (ja() != 0) {
701                 idebug = !idebug;
702                 if (idebug) prout("Debug output ON");
703                 else prout("Debug output OFF");
704         }
705         proutn("Cause selective damage? ");
706         if (ja() != 0) {
707                 int i, key;
708                 for (i=1; i <= NDEVICES; i++) {
709                         proutn("Kill ");
710                         proutn(device[i]);
711                         proutn("? ");
712                         chew();
713                         key = scan();
714                         if (key == IHALPHA &&  isit("y")) {
715                                 damage[i] = 10.0;
716                                 if (i == DRADIO) stdamtim = game.state.date;
717                         }
718                 }
719         }
720         proutn("Examine/change events? ");
721         if (ja() != 0) {
722                 int i;
723                 for (i = 1; i < NEVENTS; i++) {
724                         int key;
725                         if (future[i] == 1e30) continue;
726                         switch (i) {
727                                 case FSNOVA:  proutn("Supernova       "); break;
728                                 case FTBEAM:  proutn("T Beam          "); break;
729                                 case FSNAP:   proutn("Snapshot        "); break;
730                                 case FBATTAK: proutn("Base Attack     "); break;
731                                 case FCDBAS:  proutn("Base Destroy    "); break;
732                                 case FSCMOVE: proutn("SC Move         "); break;
733                                 case FSCDBAS: proutn("SC Base Destroy "); break;
734                         }
735                         proutn("%.2f", future[i]-game.state.date);
736                         chew();
737                         proutn("  ?");
738                         key = scan();
739                         if (key == IHREAL) {
740                                 future[i] = game.state.date + aaitem;
741                         }
742                 }
743                 chew();
744         }
745 }
746                         
747
748 #endif