7 #define DOC_NAME "sst.doc"
13 SRSCAN, MOVE, PHASERS, CALL, STATUS, IMPULSE, PHOTONS, ABANDON,
14 LRSCAN, WARP, SHIELDS, DESTRUCT, CHART, REST, DOCK, QUIT, and DAMAGE
15 were in the original non-"super" version of UT FORTRAN Star Trek.
17 Tholians weren't in the original. Dave is dubious about their merits.
18 (They are now controlled by OPTION_THOLIAN and turned off if the game
21 Planets and dilithium crystals weren't in the original. Dave is OK
22 with this idea. (It's now controlled by OPTION_PLANETS and turned
23 off if the game type is "plain".)
25 Dave says the bit about the Galileo getting turned into a
26 McDonald's is "consistant with our original vision". (This has been
27 left permanently enabled, as it can only happen if OPTION_PLANETS
30 Dave also says the Space Thingy should not be preserved across saved
31 games, so you can't prove to others that you've seen it. He says it
32 shouldn't fire back, either. It should do nothing except scream and
33 disappear when hit by photon torpedos. It's OK that it may move
34 when attacked, but it didn't in the original. (Whether the Thingy
35 can fire back is now controlled by OPTION_THINGY and turned off if the
36 game type is "plain" or "almy". The no-save behavior has been restored.)
38 The Faerie Queen, black holes, and time warping were in the original.
40 Here are Tom Almy's changes:
42 In early 1997, I got the bright idea to look for references to
43 "Super Star Trek" on the World Wide Web. There weren't many hits,
44 but there was one that came up with 1979 Fortran sources! This
45 version had a few additional features that mine didn't have,
46 however mine had some feature it didn't have. So I merged its
47 features that I liked. I also took a peek at the DECUS version (a
48 port, less sources, to the PDP-10), and some other variations.
50 1, Compared to the original UT version, I've changed the "help" command to
51 "call" and the "terminate" command to "quit" to better match
52 user expectations. The DECUS version apparently made those changes
53 as well as changing "freeze" to "save". However I like "freeze".
54 (Both "freeze" and "save" work in SST2K.)
56 2. The experimental deathray originally had only a 5% chance of
57 success, but could be used repeatedly. I guess after a couple
58 years of use, it was less "experimental" because the 1979
59 version had a 70% success rate. However it was prone to breaking
60 after use. I upgraded the deathray, but kept the original set of
61 failure modes (great humor!). (Now controlled by OPTION_DEATHRAY
62 and turned off if game type is "plain".)
64 3. The 1979 version also mentions srscan and lrscan working when
65 docked (using the starbase's scanners), so I made some changes here
66 to do this (and indicating that fact to the player), and then realized
67 the base would have a subspace radio as well -- doing a Chart when docked
68 updates the star chart, and all radio reports will be heard. The Dock
69 command will also give a report if a base is under attack.
71 4. Tholian Web from the 1979 version. (Now controlled by
72 OPTION_THOLIAN and turned off if game type is "plain".)
74 5. Enemies can ram the Enterprise. (Now controlled by OPTION_RAMMING
75 and turned off if game type is "plain".)
77 6. Regular Klingons and Romulans can move in Expert and Emeritus games.
78 This code could use improvement. (Now controlled by OPTION_MVBADDY
79 and turned off if game type is "plain".)
81 7. The deep-space probe feature from the DECUS version. (Now controlled
82 by OPTION_PROBE and turned off if game type is "plain").
84 8. 'emexit' command from the 1979 version.
86 9. Bugfix: Klingon commander movements are no longer reported if long-range
89 10. Bugfix: Better base positioning at startup (more spread out).
90 That made sense to add because most people abort games with
93 In June 2002, I fixed two known bugs and a documentation typo.
94 In June 2004 I fixed a number of bugs involving: 1) parsing invalid
95 numbers, 2) manual phasers when SR scan is damaged and commander is
96 present, 3) time warping into the future, 4) hang when moving
97 klingons in crowded quadrants. (These fixes are in SST2K.)
99 Here are Stas Sergeev's changes:
101 1. The Space Thingy can be shoved, if you ram it, and can fire back if
102 fired upon. (Now controlled by OPTION_THINGY and turned off if game
103 type is "plain" or "almy".)
105 2. When you are docked, base covers you with an almost invincible shield.
106 (A commander can still ram you, or a Romulan can destroy the base,
107 or a SCom can even succeed with direct attack IIRC, but this rarely
108 happens.) (Now controlled by OPTION_BASE and turned off if game
109 type is "plain" or "almy".)
111 3. Ramming a black hole is no longer instant death. There is a
112 chance you might get timewarped instead. (Now controlled by
113 OPTION_BLKHOLE and turned off if game type is "plain" or "almy".)
115 4. The Tholian can be hit with phasers.
117 5. SCom can't escape from you if no more enemies remain
118 (without this, chasing SCom can take an eternity).
120 6. Probe target you enter is now the destination quadrant. Before I don't
121 remember what it was, but it was something I had difficulty using.
123 7. Secret password is now autogenerated.
125 8. "Plaque" is adjusted for A4 paper :-)
127 9. Phasers now tells you how much energy needed, but only if the computer
130 10. Planets are auto-scanned when you enter the quadrant.
132 11. Mining or using crystals in presense of enemy now yields an attack.
133 There are other minor adjustments to what yields an attack
136 12. "freeze" command reverts to "save", most people will understand this
137 better anyway. (SST2K recognizes both.)
139 13. Screen-oriented interface, with sensor scans always up. (SST2K
140 supports both screen-oriented and TTY modes.)
142 Eric Raymond's changes:
144 Mainly, I translated this C code out of FORTRAN into C -- created #defines
145 for a lot of magic numbers and refactored the heck out of it.
147 1. "sos" and "call" becomes "mayday", "freeze" and "save" are both good.
149 2. Status report now indicates when dilithium crystals are on board.
151 3. Per Dave Matuszek's remarks, Thingy state is never saved across games.
153 4. Added game option selection so you can play a close (but not bug-for-
154 bug identical) approximation of older versions.
156 5. Half the quadrants now have inhabited planets, from which one
157 cannot mine dilithium (there will still be the same additional number
158 of dilithium-bearing planets). Torpedoing an inhabited world is *bad*.
159 There is BSD-Trek-like logic for Klingons to attack and enslave
160 inhabited worlds, producing more ships (only is skill is 'good' or
161 better). (Controlled by OPTION_WORLDS and turned off if game
162 type is "plain" or "almy".)
164 6. User input is now logged so we can do regression testing.
166 7. More BSD-Trek features: You can now lose if your entire crew
167 dies in battle. When abandoning ship in a game with inhabited
168 worlds enabled, they must have one in the quadrant to beam down
169 to; otherwise they die in space and this counts heavily against
170 your score. Docking at a starbase replenishes your crew.
172 8. Still more BSD-Trek: we now have a weighted damage table.
173 Also, the nav subsystem (enabling automatic course
174 setting) can be damaged separately from the main computer (which
175 handles weapons targeting, ETA calculation, and self-destruct).
178 /* the input queue */
179 static char line[128], *linep = line;
183 bool iqhere, iqengry;
184 int iscore, iskill; // Common PLAQ
188 int seed; // the random-number seed
189 bool idebug; // debug mode
190 FILE *logfp, *replayfp;
192 char *systnames[NINHAB + 1];
193 char *device[NDEVICES];
199 unsigned long option;
204 {"SRSCAN", SRSCAN, OPTION_TTY},
206 {"STATUS", STATUS, OPTION_TTY},
208 {"REQUEST", REQUEST, OPTION_TTY},
210 {"LRSCAN", LRSCAN, OPTION_TTY},
212 {"PHASERS", PHASERS, 0},
214 {"TORPEDO", TORPEDO, 0},
215 {"PHOTONS", TORPEDO, 0},
219 {"SHIELDS", SHIELDS, 0},
223 {"DAMAGES", DAMAGES, 0},
227 {"IMPULSE", IMPULSE, 0},
235 {"SENSORS", SENSORS, OPTION_PLANETS},
237 {"ORBIT", ORBIT, OPTION_PLANETS},
239 {"TRANSPORT", TRANSPORT, OPTION_PLANETS},
241 {"MINE", MINE, OPTION_PLANETS},
243 {"CRYSTALS", CRYSTALS, OPTION_PLANETS},
245 {"SHUTTLE", SHUTTLE, OPTION_PLANETS},
247 {"PLANETS", PLANETS, OPTION_PLANETS},
249 {"REPORT", REPORT, 0},
251 {"COMPUTER", COMPUTER, 0},
253 {"COMMANDS", COMMANDS, 0},
255 {"EMEXIT", EMEXIT, 0},
257 {"PROBE", PROBE, OPTION_PROBE},
262 {"ABANDON", ABANDON, 0},
264 {"DESTRUCT", DESTRUCT, 0},
266 {"DEATHRAY", DEATHRAY, 0},
268 {"DEBUG", DEBUGCMD, 0},
270 {"MAYDAY", MAYDAY, 0},
271 //{"SOS", MAYDAY, 0},
272 //{"CALL", MAYDAY, 0},
281 {"VISUAL", VISUAL, 0},
285 #define NUMCOMMANDS sizeof(commands)/sizeof(commands[0])
286 #define ACCEPT(i) (!commands[i].option || (commands[i].option & game.options))
288 static void listCommands(void)
289 /* generate a list of legal commands */
292 proutn(_("LEGAL COMMANDS ARE:"));
293 for (i = 0; i < NUMCOMMANDS; i++) {
298 proutn("%-12s ", commands[i].name);
304 static void helpme(void)
305 /* browse on-line help */
308 char cmdbuf[32], *cp;
311 /* Give help on commands */
316 setwnd(prompt_window);
317 proutn(_("Help on what command? "));
320 setwnd(message_window);
321 if (key == IHEOL) return;
322 for (i = 0; i < NUMCOMMANDS; i++) {
323 if (ACCEPT(i) && strcasecmp(commands[i].name, citem)==0) {
324 i = commands[i].value;
328 if (i != NUMCOMMANDS) break;
336 strcpy(cmdbuf, " ABBREV");
339 for (j = 0; commands[i].name[j]; j++)
340 cmdbuf[j] = toupper(commands[i].name[j]);
343 fp = fopen(SSTDOC, "r");
345 fp = fopen(DOC_NAME, "r");
347 prout(_("Spock- \"Captain, that information is missing from the"));
348 proutn(_(" computer. You need to find "));
350 prout(_(" and put it in the"));
351 proutn(_(" current directory or to "));
355 * This used to continue: "You need to find SST.DOC and put
356 * it in the current directory."
361 if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
362 prout(_("Spock- \"Captain, there is no information on that command.\""));
366 if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
367 for (cp = linebuf+3; isspace(*cp); cp++)
369 linebuf[strlen(linebuf)-1] = '\0';
370 if (strcasecmp(cp, cmdbuf) == 0)
376 prout(_("Spock- \"Captain, I've found the following information:\""));
379 while (fgets(linebuf, sizeof(linebuf), fp)) {
381 if (strstr(linebuf, "******"))
383 if ((eol = strpbrk(linebuf, "\r\n")))
390 void enqueue(char *s)
391 /* enqueue input for the command parser */
397 static void makemoves(void)
398 /* command-interpretation loop */
403 setwnd(message_window);
404 for(;;) { /* command loop */
406 for(;;) { /* get a command */
412 setwnd(prompt_window);
415 if (scan() == IHEOL) {
421 setwnd(message_window);
423 for (i=0; i < ABANDON; i++)
424 if (ACCEPT(i) && isit(commands[i].name)) {
425 v = commands[i].value;
428 if (i < ABANDON && (!commands[i].option || (commands[i].option & game.options)))
430 for (; i < NUMCOMMANDS; i++)
431 if (ACCEPT(i) && strcasecmp(commands[i].name, citem) == 0) {
432 v = commands[i].value;
435 if (i < NUMCOMMANDS && (!commands[i].option || (commands[i].option & game.options)))
439 commandhook(commands[i].name, true);
440 switch (v) { /* command switch */
441 case SRSCAN: // srscan
444 case STATUS: // status
447 case REQUEST: // status request
448 srscan(SCAN_REQUEST);
450 case LRSCAN: // lrscan
453 case PHASERS: // phasers
455 if (game.ididit) hitme = true;
457 case TORPEDO: // photons
459 if (game.ididit) hitme = true;
464 case SHIELDS: // shields
468 game.shldchg = false;
473 if (game.ididit) attack(false);
475 case DAMAGES: // damages
481 case IMPULSE: // impulse
486 if (game.ididit) hitme = true;
494 case SENSORS: // sensors
499 if (game.ididit) hitme = true;
501 case TRANSPORT: // transport "beam"
506 if (game.ididit) hitme = true;
508 case CRYSTALS: // crystals
510 if (game.ididit) hitme = true;
512 case SHUTTLE: // shuttle
514 if (game.ididit) hitme = true;
516 case PLANETS: // Planet list
519 case REPORT: // Game Report
522 case COMPUTER: // use COMPUTER!
528 case EMEXIT: // Emergency exit
529 clrscr(); // Hide screen
530 freeze(true); // forced save
531 exit(1); // And quick exit
534 probe(); // Launch probe
535 if (game.ididit) hitme = true;
537 case ABANDON: // Abandon Ship
540 case DESTRUCT: // Self Destruct
543 case SAVE: // Save Game
546 if (game.skill > SKILL_GOOD)
547 prout(_("WARNING--Saved games produce no plaques!"));
549 case DEATHRAY: // Try a desparation measure
551 if (game.ididit) hitme = true;
553 case DEBUGCMD: // What do we want for debug???
556 case MAYDAY: // Call for help
558 if (game.ididit) hitme = true;
561 game.alldone = true; // quit the game
564 helpme(); // get help
566 case SEED: // set random-number seed
573 visual(); // perform visual scan
577 commandhook(commands[i].name, false);
579 if (game.alldone) break; // Game has ended
580 if (game.optime != 0.0) {
582 if (game.alldone) break; // Events did us in
584 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
588 if (hitme && !game.justin) {
590 if (game.alldone) break;
591 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // went NOVA!
599 if (game.alldone) break;
601 if (idebug) prout("=== Ending");
605 int main(int argc, char **argv)
609 game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY);
611 game.options |= OPTION_CURSES | OPTION_SHOWME;
613 game.options |= OPTION_TTY;
615 seed = (int)time(NULL);
616 while ((option = getopt(argc, argv, "r:tx")) != -1) {
619 replayfp = fopen(optarg, "r");
620 if (replayfp == NULL) {
621 fprintf(stderr, "sst: can't open replay file %s\n", optarg);
624 if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
625 fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
630 game.options |= OPTION_TTY;
631 game.options &=~ OPTION_CURSES;
637 fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
641 /* where to save the input in case of bugs */
642 logfp = fopen("sst-input.log", "w");
644 fprintf(logfp, "seed %d\n", seed);
651 for (i = optind; i < argc; i++) {
652 strcat(line, argv[i]);
655 for(;;) { /* Play a game */
656 setwnd(fullscreen_window);
659 setup(line[0] == '\0');
662 game.alldone = false;
669 if (game.tourn && game.alldone) {
670 proutn(_("Do you want your score recorded?"));
676 proutn(_("Do you want to play again? "));
680 prout(_("May the Great Bird of the Galaxy roost upon your home planet."));
685 void cramen(feature i)
686 /* print the name of an enemy */
688 /* return an enemy */
692 case IHR: s = _("Romulan"); break;
693 case IHK: s = _("Klingon"); break;
694 case IHC: s = _("Commander"); break;
695 case IHS: s = _("Super-commander"); break;
696 case IHSTAR: s = _("Star"); break;
697 case IHP: s = _("Planet"); break;
698 case IHB: s = _("Starbase"); break;
699 case IHBLANK: s = _("Black hole"); break;
700 case IHT: s = _("Tholian"); break;
701 case IHWEB: s = _("Tholian web"); break;
702 case IHQUEST: s = _("Stranger"); break;
703 case IHW: s = _("Inhabited World"); break;
704 default: s = "Unknown??"; break;
709 char *cramlc(enum loctype key, coord w)
710 /* name a location */
714 if (key == quadrant) strcpy(buf, _("Quadrant "));
715 else if (key == sector) strcpy(buf, _("Sector "));
716 sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
720 void crmena(bool stars, feature enemy, enum loctype key, coord w)
721 /* print an enemy and his location */
723 if (stars) proutn("***");
726 proutn(cramlc(key, w));
730 /* print our ship name */
734 case IHE: s = _("Enterprise"); break;
735 case IHF: s = _("Faerie Queene"); break;
736 default: s = "Ship???"; break;
742 /* print a line of stars */
744 prouts("******************************************************");
748 double expran(double avrage)
750 return -avrage*log(1e-7 + Rand());
755 return rand()/(1.0 + (double)RAND_MAX);
758 coord randplace(int size)
759 /* choose a random location */
762 w.x = Rand()*(size*1.0) + 1.0;
763 w.y = Rand()*(size*1.0) + 1.0;
775 /* return IHEOL next time */
789 // Read a line if nothing here
795 cgetline(line, sizeof(line));
797 if (curwnd==prompt_window){
799 setwnd(message_window);
804 // Skip leading white space
805 while (*linep == ' ') linep++;
811 if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
814 if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
815 linep = line; // Invalid numbers are ignored
827 while (*linep && *linep!=' ') {
828 if ((cp - citem) < 9) *cp++ = tolower(*linep);
836 /* yes-or-no confirmation */
842 if (*citem == 'y') return true;
843 if (*citem == 'n') return false;
844 proutn(_("Please answer with \"y\" or \"n\": "));
849 /* complain about unparseable input */
853 prout(_("Beg your pardon, Captain?"));
857 /* compares s to citem and returns true if it matches to the length of s */
859 return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
863 /* access to the internals for debugging */
865 proutn("Reset levels? ");
867 if (game.energy < game.inenrg) game.energy = game.inenrg;
868 game.shield = game.inshld;
869 game.torps = game.intorps;
870 game.lsupres = game.inlsr;
872 proutn("Reset damage? ");
875 for (i=0; i < NDEVICES; i++)
876 if (game.damage[i] > 0.0)
877 game.damage[i] = 0.0;
879 proutn("Toggle debug flag? ");
882 if (idebug) prout("Debug output ON");
883 else prout("Debug output OFF");
885 proutn("Cause selective damage? ");
888 for (i=0; i < NDEVICES; i++) {
894 if (key == IHALPHA && isit("y")) {
895 game.damage[i] = 10.0;
899 proutn("Examine/change events? ");
904 for (i = 1; i < NEVENTS; i++) {
907 case FSNOVA: proutn("Supernova "); break;
908 case FTBEAM: proutn("T Beam "); break;
909 case FSNAP: proutn("Snapshot "); break;
910 case FBATTAK: proutn("Base Attack "); break;
911 case FCDBAS: proutn("Base Destroy "); break;
912 case FSCMOVE: proutn("SC Move "); break;
913 case FSCDBAS: proutn("SC Base Destroy "); break;
914 case FDSPROB: proutn("Probe Move "); break;
915 case FDISTR: proutn("Distress Call "); break;
916 case FENSLV: proutn("Enlavement "); break;
917 case FREPRO: proutn("Klingon Build "); break;
919 if (is_scheduled(i)) {
920 proutn("%.2f", scheduled(i)-game.state.date);
921 if (i == FENSLV || i == FREPRO) {
923 proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
933 } else if (key == IHREAL) {
934 ev = schedule(i, aaitem);
935 if (i == FENSLV || i == FREPRO) {
937 proutn("In quadrant- ");
939 /* IHEOL says to leave coordinates as they are */
942 prout("Event %d canceled, no x coordinate.", i);
949 prout("Event %d canceled, no y coordinate.", i);
961 proutn("Induce supernova here? ");
963 game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;