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.
167 /* the input queue */
168 static char line[128], *linep = line;
173 int iscore, iskill; // Common PLAQ
177 int seed; // the random-number seed
178 bool idebug; // debug mode
179 FILE *logfp, *replayfp;
181 char *device[NDEVICES] = {
202 unsigned long option;
207 {"SRSCAN", SRSCAN, OPTION_TTY},
209 {"STATUS", STATUS, OPTION_TTY},
211 {"REQUEST", REQUEST, OPTION_TTY},
213 {"LRSCAN", LRSCAN, OPTION_TTY},
215 {"PHASERS", PHASERS, 0},
217 {"TORPEDO", TORPEDO, 0},
218 {"PHOTONS", TORPEDO, 0},
222 {"SHIELDS", SHIELDS, 0},
226 {"DAMAGES", DAMAGES, 0},
230 {"IMPULSE", IMPULSE, 0},
238 {"SENSORS", SENSORS, OPTION_PLANETS},
240 {"ORBIT", ORBIT, OPTION_PLANETS},
242 {"TRANSPORT", TRANSPORT, OPTION_PLANETS},
244 {"MINE", MINE, OPTION_PLANETS},
246 {"CRYSTALS", CRYSTALS, OPTION_PLANETS},
248 {"SHUTTLE", SHUTTLE, OPTION_PLANETS},
250 {"PLANETS", PLANETS, OPTION_PLANETS},
252 {"REPORT", REPORT, 0},
254 {"COMPUTER", COMPUTER, 0},
256 {"COMMANDS", COMMANDS, 0},
258 {"EMEXIT", EMEXIT, 0},
260 {"PROBE", PROBE, OPTION_PROBE},
265 {"ABANDON", ABANDON, 0},
267 {"DESTRUCT", DESTRUCT, 0},
269 {"DEATHRAY", DEATHRAY, 0},
271 {"DEBUG", DEBUGCMD, 0},
273 {"MAYDAY", MAYDAY, 0},
274 //{"SOS", MAYDAY, 0},
275 //{"CALL", MAYDAY, 0},
284 #define NUMCOMMANDS sizeof(commands)/sizeof(commands[0])
285 #define ACCEPT(i) (!commands[i].option || (commands[i].option & game.options))
287 static void listCommands(void) {
289 proutn("LEGAL COMMANDS ARE:");
290 for (i = 0; i < NUMCOMMANDS; i++) {
295 proutn("%-12s ", commands[i].name);
301 static void helpme(void)
304 char cmdbuf[32], *cp;
307 /* Give help on commands */
312 setwnd(prompt_window);
313 proutn("Help on what command? ");
316 setwnd(message_window);
317 if (key == IHEOL) return;
318 for (i = 0; i < NUMCOMMANDS; i++) {
319 if (ACCEPT(i) && strcasecmp(commands[i].name, citem)==0) {
320 i = commands[i].value;
324 if (i != NUMCOMMANDS) break;
326 prout("Valid commands:");
333 strcpy(cmdbuf, " ABBREV");
336 for (j = 0; commands[i].name[j]; j++)
337 cmdbuf[j] = toupper(commands[i].name[j]);
340 fp = fopen(SSTDOC, "r");
342 fp = fopen(DOC_NAME, "r");
344 prout("Spock- \"Captain, that information is missing from the");
345 prout(" computer. You need to find "DOC_NAME" and put it in the");
346 prout(" current directory or to "SSTDOC".\"");
348 * This used to continue: "You need to find SST.DOC and put
349 * it in the current directory."
354 if (fgets(linebuf, sizeof(linebuf), fp) == NULL) {
355 prout("Spock- \"Captain, there is no information on that command.\"");
359 if (linebuf[0] == '%' && linebuf[1] == '%'&& linebuf[2] == ' ') {
360 for (cp = linebuf+3; isspace(*cp); cp++)
362 linebuf[strlen(linebuf)-1] = '\0';
363 if (strcasecmp(cp, cmdbuf) == 0)
369 prout("Spock- \"Captain, I've found the following information:\"");
372 while (fgets(linebuf, sizeof(linebuf),fp)) {
373 if (strstr(linebuf, "******"))
380 void enqueue(char *s)
385 static void makemoves(void)
390 setwnd(message_window);
391 for(;;) { /* command loop */
393 for(;;) { /* get a command */
399 setwnd(prompt_window);
402 if (scan() == IHEOL) {
408 setwnd(message_window);
410 for (i=0; i < ABANDON; i++)
411 if (ACCEPT(i) && isit(commands[i].name)) {
412 v = commands[i].value;
415 if (i < ABANDON && (!commands[i].option || (commands[i].option & game.options)))
417 for (; i < NUMCOMMANDS; i++)
418 if (ACCEPT(i) && strcasecmp(commands[i].name, citem) == 0) {
419 v = commands[i].value;
422 if (i < NUMCOMMANDS && (!commands[i].option || (commands[i].option & game.options)))
426 commandhook(commands[i].name, true);
427 switch (v) { /* command switch */
428 case SRSCAN: // srscan
431 case STATUS: // status
434 case REQUEST: // status request
435 srscan(SCAN_REQUEST);
437 case LRSCAN: // lrscan
440 case PHASERS: // phasers
442 if (game.ididit) hitme = true;
444 case TORPEDO: // photons
446 if (game.ididit) hitme = true;
451 case SHIELDS: // shields
460 if (game.ididit) attack(0);
462 case DAMAGES: // damages
468 case IMPULSE: // impulse
473 if (game.ididit) hitme = true;
481 case SENSORS: // sensors
486 if (game.ididit) hitme = true;
488 case TRANSPORT: // transport "beam"
493 if (game.ididit) hitme = true;
495 case CRYSTALS: // crystals
497 if (game.ididit) hitme = true;
499 case SHUTTLE: // shuttle
501 if (game.ididit) hitme = true;
503 case PLANETS: // Planet list
506 case REPORT: // Game Report
509 case COMPUTER: // use COMPUTER!
515 case EMEXIT: // Emergency exit
516 clrscr(); // Hide screen
517 freeze(true); // forced save
518 exit(1); // And quick exit
521 probe(); // Launch probe
522 if (game.ididit) hitme = true;
524 case ABANDON: // Abandon Ship
527 case DESTRUCT: // Self Destruct
530 case SAVE: // Save Game
533 if (game.skill > SKILL_GOOD)
534 prout("WARNING--Saved games produce no plaques!");
536 case DEATHRAY: // Try a desparation measure
538 if (game.ididit) hitme = true;
540 case DEBUGCMD: // What do we want for debug???
543 case MAYDAY: // Call for help
545 if (game.ididit) hitme = true;
548 game.alldone = 1; // quit the game
551 helpme(); // get help
553 case SEED: // set random-number seed
559 commandhook(commands[i].name, false);
561 if (game.alldone) break; // Game has ended
562 if (game.optime != 0.0) {
564 if (game.alldone) break; // Events did us in
566 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // Galaxy went Nova!
570 if (hitme && game.justin==0) {
572 if (game.alldone) break;
573 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) { // went NOVA!
581 if (game.alldone) break;
583 if (idebug) prout("=== Ending");
587 int main(int argc, char **argv)
591 game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_SHOWME | OPTION_PLAIN | OPTION_ALMY);
593 game.options |= OPTION_CURSES | OPTION_SHOWME;
595 game.options |= OPTION_TTY;
597 seed = (int)time(NULL);
598 while ((option = getopt(argc, argv, "r:tx")) != -1) {
601 replayfp = fopen(optarg, "r");
602 if (replayfp == NULL) {
603 fprintf(stderr, "sst: can't open replay file %s\n", optarg);
606 if (fscanf(replayfp, "seed %d\n", &seed) != 1) {
607 fprintf(stderr, "sst: replay file %s is ill-formed\n", optarg);
612 game.options |= OPTION_TTY;
613 game.options &=~ OPTION_CURSES;
619 fprintf(stderr, "usage: sst [-t] [-x] [startcommand...].\n");
623 /* where to save the input in case of bugs */
624 logfp = fopen("sst-input.log", "w");
626 fprintf(logfp, "seed %d\n", seed);
633 for (i = optind; i < argc; i++) {
634 strcat(line, argv[i]);
637 for(;;) { /* Play a game */
638 setwnd(fullscreen_window);
641 setup(line[0] == '\0');
651 if (game.tourn && game.alldone) {
652 proutn("Do you want your score recorded?");
658 proutn("Do you want to play again? ");
662 prout("May the Great Bird of the Galaxy roost upon your home planet.");
669 /* return an enemy */
673 case IHR: s = "Romulan"; break;
674 case IHK: s = "Klingon"; break;
675 case IHC: s = "Commander"; break;
676 case IHS: s = "Super-commander"; break;
677 case IHSTAR: s = "Star"; break;
678 case IHP: s = "Planet"; break;
679 case IHB: s = "Starbase"; break;
680 case IHBLANK: s = "Black hole"; break;
681 case IHT: s = "Tholian"; break;
682 case IHWEB: s = "Tholian web"; break;
683 case IHQUEST: s = "Stranger"; break;
684 default: s = "Unknown??"; break;
689 char *cramlc(enum loctype key, coord w)
693 if (key == quadrant) strcpy(buf, "Quadrant ");
694 else if (key == sector) strcpy(buf, "Sector ");
695 sprintf(buf+strlen(buf), "%d - %d", w.x, w.y);
699 void crmena(int i, int enemy, int key, coord w)
701 if (i == 1) proutn("***");
704 proutn(cramlc(key, w));
711 case IHE: s = "Enterprise"; break;
712 case IHF: s = "Faerie Queene"; break;
713 default: s = "Ship???"; break;
720 prouts("******************************************************");
724 double expran(double avrage)
726 return -avrage*log(1e-7 + Rand());
731 return rand()/(1.0 + (double)RAND_MAX);
734 void iran(int size, int *i, int *j)
736 *i = Rand()*(size*1.0) + 1.0;
737 *j = Rand()*(size*1.0) + 1.0;
748 /* return IHEOL next time */
762 // Read a line if nothing here
768 cgetline(line, sizeof(line));
770 if (curwnd==prompt_window){
772 setwnd(message_window);
777 // Skip leading white space
778 while (*linep == ' ') linep++;
784 if (isdigit(*linep) || *linep=='+' || *linep=='-' || *linep=='.') {
787 if (sscanf(linep, "%lf%n", &aaitem, &i) < 1) {
788 linep = line; // Invalid numbers are ignored
800 while (*linep && *linep!=' ') {
801 if ((cp - citem) < 9) *cp++ = tolower(*linep);
814 if (*citem == 'y') return true;
815 if (*citem == 'n') return false;
816 proutn("Please answer with \"Y\" or \"N\": ");
824 prout("Beg your pardon, Captain?");
829 /* New function -- compares s to scanned citem and returns true if it
830 matches to the length of s */
832 return strncasecmp(s, citem, max(1, strlen(citem))) == 0;
838 proutn("Reset levels? ");
840 if (game.energy < game.inenrg) game.energy = game.inenrg;
841 game.shield = game.inshld;
842 game.torps = game.intorps;
843 game.lsupres = game.inlsr;
845 proutn("Reset damage? ");
848 for (i=0; i < NDEVICES; i++)
849 if (game.damage[i] > 0.0)
850 game.damage[i] = 0.0;
852 proutn("Toggle debug flag? ");
855 if (idebug) prout("Debug output ON");
856 else prout("Debug output OFF");
858 proutn("Cause selective damage? ");
861 for (i=0; i < NDEVICES; i++) {
867 if (key == IHALPHA && isit("y")) {
868 game.damage[i] = 10.0;
872 proutn("Examine/change events? ");
877 for (i = 1; i < NEVENTS; i++) {
880 case FSNOVA: proutn("Supernova "); break;
881 case FTBEAM: proutn("T Beam "); break;
882 case FSNAP: proutn("Snapshot "); break;
883 case FBATTAK: proutn("Base Attack "); break;
884 case FCDBAS: proutn("Base Destroy "); break;
885 case FSCMOVE: proutn("SC Move "); break;
886 case FSCDBAS: proutn("SC Base Destroy "); break;
887 case FDSPROB: proutn("Probe Move "); break;
888 case FDISTR: proutn("Distress Call "); break;
889 case FENSLV: proutn("Enlavement "); break;
890 case FREPRO: proutn("Klingon Build "); break;
892 if (is_scheduled(i)) {
893 proutn("%.2f", scheduled(i)-game.state.date);
894 if (i == FENSLV || i == FREPRO) {
896 proutn(" in %d-%d", ev->quadrant.x,ev->quadrant.y);
906 } else if (key == IHREAL) {
907 ev = schedule(i, aaitem);
908 if (i == FENSLV || i == FREPRO) {
910 proutn("In quadrant- ");
912 /* IHEOL says to leave coordinates as they are */
915 prout("Event %d canceled, no x coordinate.", i);
922 prout("Event %d canceled, no y coordinate.", i);
934 proutn("Induce supernova here? ");
936 game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova = true;