Clean up ugly FORTRANISH names.
[super-star-trek.git] / src / events.c
1 /*
2  * events.c -- event-queue handling
3  *
4  * This isn't a real event queue a la BSD Trek yet -- you can only have one 
5  * event of each type active at any given time.  Mostly these means we can 
6  * only have one FDISTR/FENSLV/FREPRO sequence going at any given time;
7  * BSD Trek, from which we swiped the idea, can have up to 5.
8  */
9 #include "sst.h"
10 #include <math.h>
11
12 event *unschedule(int evtype)
13 /* remove an event from the schedule */
14 {
15     game.future[evtype].date = FOREVER;
16     return &game.future[evtype];
17 }
18
19 int is_scheduled(int evtype)
20 /* is an event of specified type scheduled */
21 {
22     return game.future[evtype].date != FOREVER;
23 }
24
25 extern double scheduled(int evtype)
26 /* when will this event happen? */
27 {
28     return game.future[evtype].date;
29 }
30
31 event *schedule(int evtype, double offset)
32 /* schedule an event of specified type */
33 {
34     game.future[evtype].date = game.state.date + offset;
35     return &game.future[evtype];
36 }
37
38 void postpone(int evtype, double offset)
39 /* poistpone a scheduled event */
40 {
41     game.future[evtype].date += offset;
42 }
43
44 static bool cancelrest(void)
45 /* rest period is interrupted by event */
46 {
47     if (game.resting) {
48         skip(1);
49         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""));
50         if (ja() == true) {
51             game.resting = false;
52             game.optime = 0.0;
53             return true;
54         }
55     }
56
57     return false;
58 }
59
60 void events(void) 
61 /* run through the event queue looking for things to do */
62 {
63     int evcode, i=0, j, k, l;
64     double fintim = game.state.date + game.optime, datemin, xtime, repair, yank=0;
65     bool radio_was_broken, ictbeam = false, istract = false;
66     struct quadrant *pdest, *q;
67     coord w, hold;
68     event *ev, *ev2;
69
70     pause_reset();
71
72     if (idebug) {
73         prout("=== EVENTS from %.2f to %.2f:", game.state.date, fintim);
74         for (i = 1; i < NEVENTS; i++) {
75             switch (i) {
76             case FSNOVA:  proutn("=== Supernova       "); break;
77             case FTBEAM:  proutn("=== T Beam          "); break;
78             case FSNAP:   proutn("=== Snapshot        "); break;
79             case FBATTAK: proutn("=== Base Attack     "); break;
80             case FCDBAS:  proutn("=== Base Destroy    "); break;
81             case FSCMOVE: proutn("=== SC Move         "); break;
82             case FSCDBAS: proutn("=== SC Base Destroy "); break;
83             case FDSPROB: proutn("=== Probe Move      "); break;
84             case FDISTR:  proutn("=== Distress Call   "); break;
85             case FENSLV:  proutn("=== Enlavement      "); break;
86             case FREPRO:  proutn("=== Klingon Build   "); break;
87             }
88             if (is_scheduled(i))
89                 prout("%.2f", scheduled(i));
90             else
91                 prout("never");
92
93         }
94     }
95
96     radio_was_broken = damaged(DRADIO);
97
98     hold.x = hold.y = 0;
99     for (;;) {
100         /* Select earliest extraneous event, evcode==0 if no events */
101         evcode = FSPY;
102         if (game.alldone)
103             return;
104         datemin = fintim;
105         for (l = 1; l < NEVENTS; l++)
106             if (game.future[l].date < datemin) {
107                 evcode = l;
108                 if (idebug)
109                     prout("== Event %d fires", evcode);
110                 datemin = game.future[l].date;
111             }
112         xtime = datemin-game.state.date;
113         game.state.date = datemin;
114         /* Decrement Federation resources and recompute remaining time */
115         game.state.remres -= (game.state.remkl+4*game.state.remcom)*xtime;
116         game.state.remtime = game.state.remres/(game.state.remkl+4*game.state.remcom);
117         if (game.state.remtime <=0) {
118             finish(FDEPLETE);
119             return;
120         }
121         /* Any crew left alive? */
122         if (game.state.crew <=0) {
123             finish(FCREW);
124             return;
125         }
126         /* Is life support adequate? */
127         if (damaged(DLIFSUP) && game.condition != docked) {
128             if (game.lsupres < xtime && game.damage[DLIFSUP] > game.lsupres) {
129                 finish(FLIFESUP);
130                 return;
131             }
132             game.lsupres -= xtime;
133             if (game.damage[DLIFSUP] <= xtime)
134                 game.lsupres = game.inlsr;
135         }
136         /* Fix devices */
137         repair = xtime;
138         if (game.condition == docked)
139             repair /= game.docfac;
140         /* Don't fix Deathray here */
141         for (l=0; l<NDEVICES; l++)
142             if (game.damage[l] > 0.0 && l != DDRAY)
143                 game.damage[l] -= (game.damage[l]-repair > 0.0 ? repair : game.damage[l]);
144         /* If radio repaired, update star chart and attack reports */
145         if (radio_was_broken && !damaged(DRADIO)) {
146             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"));
147             prout(_("   surveillance reports are coming in."));
148             skip(1);
149             if (!game.iseenit) {
150                 attackreport(false);
151                 game.iseenit = true;
152             }
153             rechart();
154             prout(_("   The star chart is now up to date.\""));
155             skip(1);
156         }
157         /* Cause extraneous event EVCODE to occur */
158         game.optime -= xtime;
159         switch (evcode) {
160         case FSNOVA: /* Supernova */
161             pause_game(true);
162             supernova(false, NULL);
163             schedule(FSNOVA, expran(0.5*game.intime));
164             if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova)
165                 return;
166             break;
167         case FSPY: /* Check with spy to see if S.C. should tractor beam */
168             if (game.state.nscrem == 0 ||
169                 ictbeam || istract ||
170                 game.condition==docked || game.isatb==1 || game.iscate) return;
171             if (game.ientesc ||
172                 (game.energy < 2000 && game.torps < 4 && game.shield < 1250) ||
173                 (damaged(DPHASER) && (damaged(DPHOTON) || game.torps < 4)) ||
174                 (damaged(DSHIELD) &&
175                  (game.energy < 2500 || damaged(DPHASER)) &&
176                  (game.torps < 5 || damaged(DPHOTON)))) {
177                 /* Tractor-beam her! */
178                 istract = true;
179                 yank = distance(game.state.kscmdr, game.quadrant);
180                 /********* fall through to FTBEAM code ***********/
181             }
182             else
183                 return;
184         case FTBEAM: /* Tractor beam */
185             if (evcode==FTBEAM) {
186                 if (game.state.remcom == 0) {
187                     unschedule(FTBEAM);
188                     break;
189                 }
190                 i = Rand()*game.state.remcom+1.0;
191                 yank = square(game.state.kcmdr[i].x-game.quadrant.x) + square(game.state.kcmdr[i].y-game.quadrant.y);
192                 if (istract || game.condition == docked || yank == 0) {
193                     /* Drats! Have to reschedule */
194                     schedule(FTBEAM, 
195                              game.optime + expran(1.5*game.intime/game.state.remcom));
196                     break;
197                 }
198             }
199             /* tractor beaming cases merge here */
200             yank = sqrt(yank);
201             pause_game(true);
202             game.optime = (10.0/(7.5*7.5))*yank; /* 7.5 is yank rate (warp 7.5) */
203             ictbeam = true;
204             skip(1);
205             proutn("***");
206             crmshp();
207             prout(_(" caught in long range tractor beam--"));
208             /* If Kirk & Co. screwing around on planet, handle */
209             atover(true); /* atover(true) is Grab */
210             if (game.alldone)
211                 return;
212             if (game.icraft) { /* Caught in Galileo? */
213                 finish(FSTRACTOR);
214                 return;
215             }
216             /* Check to see if shuttle is aboard */
217             if (game.iscraft == offship) {
218                 skip(1);
219                 if (Rand() > 0.5) {
220                     prout(_("Galileo, left on the planet surface, is captured"));
221                     prout(_("by aliens and made into a flying McDonald's."));
222                     game.damage[DSHUTTL] = -10;
223                     game.iscraft = removed;
224                 }
225                 else {
226                     prout(_("Galileo, left on the planet surface, is well hidden."));
227                 }
228             }
229             if (evcode==0)
230                 game.quadrant = game.state.kscmdr;
231             else
232                 game.quadrant = game.state.kcmdr[i];
233             game.sector = randplace(QUADSIZE);
234             crmshp();
235             proutn(_(" is pulled to "));
236             proutn(cramlc(quadrant, game.quadrant));
237             proutn(", ");
238             prout(cramlc(sector, game.sector));
239             if (game.resting) {
240                 prout(_("(Remainder of rest/repair period cancelled.)"));
241                 game.resting = false;
242             }
243             if (!game.shldup) {
244                 if (!damaged(DSHIELD) && game.shield > 0) {
245                     doshield(true); /* raise shields */
246                     game.shldchg=false;
247                 }
248                 else
249                     prout(_("(Shields not currently useable.)"));
250             }
251             newqad(false);
252             /* Adjust finish time to time of tractor beaming */
253             fintim = game.state.date+game.optime;
254             attack(false);
255             if (game.state.remcom <= 0)
256                 unschedule(FTBEAM);
257             else 
258                 schedule(FTBEAM, game.optime+expran(1.5*game.intime/game.state.remcom));
259             break;
260         case FSNAP: /* Snapshot of the universe (for time warp) */
261             game.snapsht = game.state;
262             game.state.snap = true;
263             schedule(FSNAP, expran(0.5 * game.intime));
264             break;
265         case FBATTAK: /* Commander attacks starbase */
266             if (game.state.remcom==0 || game.state.rembase==0) {
267                 /* no can do */
268                 unschedule(FBATTAK);
269                 unschedule(FCDBAS);
270                 break;
271             }
272             i = 0;
273             for_starbases(j) {
274                 for_commanders(k)
275                     if (same(game.state.baseq[j], game.state.kcmdr[k]) &&
276                         !same(game.state.baseq[j], game.quadrant) &&
277                         !same(game.state.baseq[j], game.state.kscmdr)) {
278                         i = 1;
279                         break;
280                     }
281                 if (i == 1)
282                     break;
283             }
284             if (j>game.state.rembase) {
285                 /* no match found -- try later */
286                 schedule(FBATTAK, expran(0.3*game.intime));
287                 unschedule(FCDBAS);
288                 break;
289             }
290             /* commander + starbase combination found -- launch attack */
291             game.battle = game.state.baseq[j];
292             schedule(FCDBAS, 1.0+3.0*Rand());
293             if (game.isatb) /* extra time if SC already attacking */
294                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date);
295             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime);
296             game.iseenit = false;
297             if (!damaged(DRADIO) && game.condition != docked) 
298                 break; /* No warning :-( */
299             game.iseenit = true;
300             pause_game(true);
301             skip(1);
302             proutn(_("Lt. Uhura-  \"Captain, the starbase in "));
303             prout(cramlc(quadrant, game.battle));
304             prout(_("   reports that it is under attack and that it can"));
305             proutn(_("   hold out only until stardate %d"),
306                    (int)scheduled(FCDBAS));
307             prout(".\"");
308             if (cancelrest())
309                 return;
310             break;
311         case FSCDBAS: /* Supercommander destroys base */
312             unschedule(FSCDBAS);
313             game.isatb = 2;
314             if (!game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].starbase) 
315                 break; /* WAS RETURN! */
316             hold = game.battle;
317             game.battle = game.state.kscmdr;
318             /* FALL THROUGH */
319         case FCDBAS: /* Commander succeeds in destroying base */
320             if (evcode==FCDBAS) {
321                 unschedule(FCDBAS);
322                 /* find the lucky pair */
323                 for_commanders(i)
324                     if (same(game.state.kcmdr[i], game.battle)) 
325                         break;
326                 if (i > game.state.remcom || game.state.rembase == 0 ||
327                     !game.state.galaxy[game.battle.x][game.battle.y].starbase) {
328                     /* No action to take after all */
329                     invalidate(game.battle);
330                     break;
331                 }
332             }
333             /* Code merges here for any commander destroying base */
334             /* Not perfect, but will have to do */
335             /* Handle case where base is in same quadrant as starship */
336             if (same(game.battle, game.quadrant)) {
337                 game.state.chart[game.battle.x][game.battle.y].starbase = false;
338                 game.quad[game.base.x][game.base.y] = IHDOT;
339                 game.base.x=game.base.y=0;
340                 newcnd();
341                 skip(1);
342                 prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""));
343             }
344             else if (game.state.rembase != 1 &&
345                      (!damaged(DRADIO) || game.condition == docked)) {
346                 /* Get word via subspace radio */
347                 pause_game(true);                   
348                 skip(1);
349                 prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"));
350                 proutn(_("   the starbase in "));
351                 proutn(cramlc(quadrant, game.battle));
352                 prout(_(" has been destroyed by"));
353                 if (game.isatb == 2) 
354                     prout(_("the Klingon Super-Commander"));
355                 else
356                     prout(_("a Klingon Commander"));
357                 game.state.chart[game.battle.x][game.battle.y].starbase = false;
358             }
359             /* Remove Starbase from galaxy */
360             game.state.galaxy[game.battle.x][game.battle.y].starbase = false;
361             for_starbases(i)
362                 if (same(game.state.baseq[i], game.battle))
363                     game.state.baseq[i] = game.state.baseq[game.state.rembase];
364             game.state.rembase--;
365             if (game.isatb == 2) {
366                 /* reinstate a commander's base attack */
367                 game.battle = hold;
368                 game.isatb = 0;
369             }
370             else
371                 invalidate(game.battle);
372             break;
373         case FSCMOVE: /* Supercommander moves */
374             schedule(FSCMOVE, 0.2777);
375             if (!game.ientesc && !istract && game.isatb != 1 &&
376                         (!game.iscate || !game.justin)) 
377                 supercommander();
378             break;
379         case FDSPROB: /* Move deep space probe */
380             schedule(FDSPROB, 0.01);
381             game.probex += game.probeinx;
382             game.probey += game.probeiny;
383             i = (int)(game.probex/QUADSIZE +0.05);
384             j = (int)(game.probey/QUADSIZE + 0.05);
385             if (game.probec.x != i || game.probec.y != j) {
386                 game.probec.x = i;
387                 game.probec.y = j;
388                 if (!VALID_QUADRANT(i, j) ||
389                     game.state.galaxy[game.probec.x][game.probec.y].supernova) {
390                     // Left galaxy or ran into supernova
391                     if (!damaged(DRADIO) || game.condition == docked) {
392                         pause_game(true);
393                         skip(1);
394                         proutn(_("Lt. Uhura-  \"The deep space probe "));
395                         if (!VALID_QUADRANT(j, i))
396                             proutn(_("has left the galaxy"));
397                         else
398                             proutn(_("is no longer transmitting"));
399                         prout(".\"");
400                     }
401                     unschedule(FDSPROB);
402                     break;
403                 }
404                 if (!damaged(DRADIO) || game.condition == docked) {
405                     pause_game(true);
406                     skip(1);
407                     proutn(_("Lt. Uhura-  \"The deep space probe is now in "));
408                     proutn(cramlc(quadrant, game.probec));
409                     prout(".\"");
410                 }
411             }
412             pdest = &game.state.galaxy[game.probec.x][game.probec.y];
413             /* Update star chart if Radio is working or have access to
414                radio. */
415             if (!damaged(DRADIO) || game.condition == docked) {
416                 struct page *chp = &game.state.chart[game.probec.x][game.probec.y];
417
418                 chp->klingons = pdest->klingons;
419                 chp->starbase = pdest->starbase;
420                 chp->stars = pdest->stars;
421                 pdest->charted = true;
422             }
423             game.proben--; // One less to travel
424             if (game.proben == 0 && game.isarmed && pdest->stars) {
425                 /* lets blow the sucker! */
426                 supernova(true, &game.probec);
427                 unschedule(FDSPROB);
428                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) 
429                     return;
430             }
431             break;
432         case FDISTR: /* inhabited system issues distress call */
433             unschedule(FDISTR);
434             /* try a whole bunch of times to find something suitable */
435             i = 100;
436             do {
437                 /* need a quadrant which is not the current one,
438                    which has some stars which are inhabited and
439                    not already under attack, which is not
440                    supernova'ed, and which has some Klingons in it */
441                 w = randplace(GALSIZE);
442                 q = &game.state.galaxy[w.x][w.y];
443             } while (--i &&
444                      (same(game.quadrant, w) || q->planet == NOPLANET ||
445                       q->supernova || q->status!=secure || q->klingons<=0));
446             if (i == 0) {
447                 /* can't seem to find one; ignore this call */
448                 if (idebug)
449                     prout("=== Couldn't find location for distress event.");
450                 break;
451             }
452
453             /* got one!!  Schedule its enslavement */
454             ev = schedule(FENSLV, expran(game.intime));
455             ev->quadrant = w;
456             q->status = distressed;
457
458             /* tell the captain about it if we can */
459             if (!damaged(DRADIO) || game.condition == docked)
460             {
461                 prout("Uhura- Captain, %s in %s reports it is under attack",
462                       systnames[q->planet], cramlc(quadrant, w));
463                 prout("by a Klingon invasion fleet.");
464                 if (cancelrest())
465                     return;
466             }
467             break;
468         case FENSLV:            /* starsystem is enslaved */
469             ev = unschedule(FENSLV);
470             /* see if current distress call still active */
471             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
472             if (q->klingons <= 0) {
473                 q->status = secure;
474                 break;
475             }
476             q->status = enslaved;
477
478             /* play stork and schedule the first baby */
479             ev2 = schedule(FREPRO, expran(2.0 * game.intime));
480             ev2->quadrant = ev->quadrant;
481
482             /* report the disaster if we can */
483             if (!damaged(DRADIO) || game.condition == docked)
484             {
485                 prout("Uhura- We've lost contact with starsystem %s",
486                       systnames[q->planet]);
487                 prout("in %s.\n", cramlc(quadrant, ev->quadrant));
488             }
489             break;
490         case FREPRO:            /* Klingon reproduces */
491             /*
492              * If we ever switch to a real event queue, we'll need to
493              * explicitly retrieve and restore the x and y.
494              */
495             ev = schedule(FREPRO, expran(1.0 * game.intime));
496             /* see if current distress call still active */
497             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
498             if (q->klingons <= 0) {
499                 q->status = secure;
500                 break;
501             }
502             if (game.state.remkl >=MAXKLGAME)
503                 break;          /* full right now */
504             /* reproduce one Klingon */
505             w = ev->quadrant;
506             if (game.klhere >= MAXKLQUAD) {
507                 /* this quadrant not ok, pick an adjacent one */
508                 for (i = w.x - 1; i <= w.x + 1; i++)
509                 {
510                     for (j = w.y - 1; j <= w.y + 1; j++)
511                     {
512                         if (!VALID_QUADRANT(i, j))
513                             continue;
514                         q = &game.state.galaxy[w.x][w.y];
515                         /* check for this quad ok (not full & no snova) */
516                         if (q->klingons >= MAXKLQUAD || q->supernova)
517                             continue;
518                         goto foundit;
519                     }
520                 }
521                 break;  /* search for eligible quadrant failed */
522             foundit:
523                 w.x = i;
524                 w.y = j;
525             }
526
527             /* deliver the child */
528             game.state.remkl++;
529             q->klingons++;
530             if (same(game.quadrant, w))
531                 newkling(++game.klhere);
532
533             /* recompute time left */
534             game.state.remtime = game.state.remres/(game.state.remkl+4*game.state.remcom);
535             /* report the disaster if we can */
536             if (!damaged(DRADIO) || game.condition == docked)
537             {
538                 if (same(game.quadrant, w)) {
539                     prout("Spock- sensors indicate the Klingons have");
540                     prout("launched a warship from %s.", systnames[q->planet]);
541                 } else {
542                     prout("Uhura- Starfleet reports increased Klingon activity");
543                     if (q->planet != NOPLANET)
544                         proutn("near %s", systnames[q->planet]);
545                     prout("in %s.\n", cramlc(quadrant, w));
546                 }
547             }
548             break;
549         }
550     }
551 }
552
553                                 
554 void wait(void) 
555 /* wait on events */
556 {
557     int key;
558     double temp, delay, origTime;
559
560     game.ididit = false;
561     for (;;) {
562         key = scan();
563         if (key  != IHEOL)
564             break;
565         proutn(_("How long? "));
566     }
567     chew();
568     if (key != IHREAL) {
569         huh();
570         return;
571     }
572     origTime = delay = aaitem;
573     if (delay <= 0.0)
574         return;
575     if (delay >= game.state.remtime || game.nenhere != 0) {
576         proutn(_("Are you sure? "));
577         if (ja() == false)
578             return;
579     }
580
581     /* Alternate resting periods (events) with attacks */
582
583     game.resting = true;
584     do {
585         if (delay <= 0)
586             game.resting = false;
587         if (!game.resting) {
588             prout(_("%d stardates left."), (int)game.state.remtime);
589             return;
590         }
591         temp = game.optime = delay;
592
593         if (game.nenhere) {
594             double rtime = 1.0 + Rand();
595             if (rtime < temp)
596                 temp = rtime;
597             game.optime = temp;
598         }
599         if (game.optime < delay)
600             attack(false);
601         if (game.alldone)
602             return;
603         events();
604         game.ididit = true;
605         if (game.alldone)
606             return;
607         delay -= temp;
608         /* Repair Deathray if long rest at starbase */
609         if (origTime-delay >= 9.99 && game.condition == docked)
610             game.damage[DDRAY] = 0.0;
611     } while 
612         // leave if quadrant supernovas
613         (!game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova);
614
615     game.resting = false;
616     game.optime = 0;
617 }
618
619 /*
620  *      A nova occurs.  It is the result of having a star hit with a
621  *      photon torpedo, or possibly of a probe warhead going off.
622  *      Stars that go nova cause stars which surround them to undergo
623  *      the same probabilistic process.  Klingons next to them are
624  *      destroyed.  And if the starship is next to it, it gets zapped.
625  *      If the zap is too much, it gets destroyed.
626  */
627 void nova(coord nov) 
628 /* star goes nova */
629 {
630     static double course[] =
631         {0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5};
632     int bot, top, top2, hits[QUADSIZE+1][3], kount, icx, icy, mm, nn, j;
633     int iquad, iquad1, i, ll;
634     coord newc, scratch;
635
636     if (Rand() < 0.05) {
637         /* Wow! We've supernova'ed */
638         supernova(false, &nov);
639         return;
640     }
641
642     /* handle initial nova */
643     game.quad[nov.x][nov.y] = IHDOT;
644     crmena(false, IHSTAR, sector, nov);
645     prout(_(" novas."));
646     game.state.galaxy[game.quadrant.x][game.quadrant.y].stars--;
647     game.state.starkl++;
648         
649     /* Set up stack to recursively trigger adjacent stars */
650     bot = top = top2 = 1;
651     kount = 0;
652     icx = icy = 0;
653     hits[1][1] = nov.x;
654     hits[1][2] = nov.y;
655     while (1) {
656         for (mm = bot; mm <= top; mm++) 
657             for (nn = 1; nn <= 3; nn++)  /* nn,j represents coordinates around current */
658                 for (j = 1; j <= 3; j++) {
659                     if (j==2 && nn== 2)
660                         continue;
661                     scratch.x = hits[mm][1]+nn-2;
662                     scratch.y = hits[mm][2]+j-2;
663                     if (!VALID_SECTOR(scratch.y, scratch.x))
664                         continue;
665                     iquad = game.quad[scratch.x][scratch.y];
666                     switch (iquad) {
667                     // case IHDOT:      /* Empty space ends reaction
668                     // case IHQUEST:
669                     // case IHBLANK:
670                     // case IHT:
671                     // case IHWEB:
672                     default:
673                         break;
674                     case IHSTAR: /* Affect another star */
675                         if (Rand() < 0.05) {
676                             /* This star supernovas */
677                             supernova(false, &scratch);
678                             return;
679                         }
680                         top2++;
681                         hits[top2][1]=scratch.x;
682                         hits[top2][2]=scratch.y;
683                         game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1;
684                         game.state.starkl++;
685                         crmena(true, IHSTAR, sector, scratch);
686                         prout(_(" novas."));
687                         game.quad[scratch.x][scratch.y] = IHDOT;
688                         break;
689                     case IHP: /* Destroy planet */
690                         game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET;
691                         game.state.nplankl++;
692                         crmena(true, IHP, sector, scratch);
693                         prout(_(" destroyed."));
694                         DESTROY(&game.state.planets[game.iplnet]);
695                         game.iplnet = 0;
696                         invalidate(game.plnet);
697                         if (game.landed) {
698                             finish(FPNOVA);
699                             return;
700                         }
701                         game.quad[scratch.x][scratch.y] = IHDOT;
702                         break;
703                     case IHB: /* Destroy base */
704                         game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase = false;
705                         for_starbases(i)
706                             if (same(game.state.baseq[i], game.quadrant)) 
707                                 break;
708                         game.state.baseq[i] = game.state.baseq[game.state.rembase];
709                         game.state.rembase--;
710                         invalidate(game.base);
711                         game.state.basekl++;
712                         newcnd();
713                         crmena(true, IHB, sector, scratch);
714                         prout(_(" destroyed."));
715                         game.quad[scratch.x][scratch.y] = IHDOT;
716                         break;
717                     case IHE: /* Buffet ship */
718                     case IHF:
719                         prout(_("***Starship buffeted by nova."));
720                         if (game.shldup) {
721                             if (game.shield >= 2000.0)
722                                 game.shield -= 2000.0;
723                             else {
724                                 double diff = 2000.0 - game.shield;
725                                 game.energy -= diff;
726                                 game.shield = 0.0;
727                                 game.shldup = false;
728                                 prout(_("***Shields knocked out."));
729                                 game.damage[DSHIELD] += 0.005*game.damfac*Rand()*diff;
730                             }
731                         }
732                         else
733                             game.energy -= 2000.0;
734                         if (game.energy <= 0) {
735                             finish(FNOVA);
736                             return;
737                         }
738                         /* add in course nova contributes to kicking starship*/
739                         icx += game.sector.x-hits[mm][1];
740                         icy += game.sector.y-hits[mm][2];
741                         kount++;
742                         break;
743                     case IHK: /* kill klingon */
744                         deadkl(scratch,iquad, scratch);
745                         break;
746                     case IHC: /* Damage/destroy big enemies */
747                     case IHS:
748                     case IHR:
749                         for_local_enemies(ll)
750                             if (same(game.ks[ll], scratch))
751                                 break;
752                         game.kpower[ll] -= 800.0; /* If firepower is lost, die */
753                         if (game.kpower[ll] <= 0.0) {
754                             deadkl(scratch, iquad, scratch);
755                             break;
756                         }
757                         newc.x = scratch.x + scratch.x - hits[mm][1];
758                         newc.y = scratch.y + scratch.y - hits[mm][2];
759                         crmena(true, iquad, sector, scratch);
760                         proutn(_(" damaged"));
761                         if (!VALID_SECTOR(newc.x, newc.y)) {
762                             /* can't leave quadrant */
763                             skip(1);
764                             break;
765                         }
766                         iquad1 = game.quad[newc.x][newc.y];
767                         if (iquad1 == IHBLANK) {
768                             proutn(_(", blasted into "));
769                             crmena(false, IHBLANK, sector, newc);
770                             skip(1);
771                             deadkl(scratch, iquad, newc);
772                             break;
773                         }
774                         if (iquad1 != IHDOT) {
775                             /* can't move into something else */
776                             skip(1);
777                             break;
778                         }
779                         proutn(_(", buffeted to "));
780                         proutn(cramlc(sector, newc));
781                         game.quad[scratch.x][scratch.y] = IHDOT;
782                         game.quad[newc.x][newc.y] = iquad;
783                         game.ks[ll] = newc;
784                         game.kdist[ll] = game.kavgd[ll] = distance(game.sector, newc);
785                         skip(1);
786                         break;
787                     }
788                 }
789         if (top == top2) 
790             break;
791         bot = top + 1;
792         top = top2;
793     }
794     if (kount==0) 
795         return;
796
797     /* Starship affected by nova -- kick it away. */
798     game.dist = kount*0.1;
799     if (icx)
800         icx = (icx < 0 ? -1 : 1);
801     if (icy)
802         icy = (icy < 0 ? -1 : 1);
803     game.direc = course[3*(icx+1)+icy+2];
804     if (game.direc == 0.0)
805         game.dist = 0.0;
806     if (game.dist == 0.0)
807         return;
808     game.optime = 10.0*game.dist/16.0;
809     skip(1);
810     prout(_("Force of nova displaces starship."));
811     imove(true);
812     game.optime = 10.0*game.dist/16.0;
813     return;
814 }
815         
816         
817 void supernova(bool induced, coord *w) 
818 /* star goes supernova */
819 {
820     int num = 0, nrmdead, npdead, kldead;
821     coord nq;
822
823     if (w != NULL) 
824         nq = *w;
825     else {
826         int stars = 0;
827         /* Scheduled supernova -- select star */
828         /* logic changed here so that we won't favor quadrants in top
829            left of universe */
830         for_quadrants(nq.x) {
831             for_quadrants(nq.y) {
832                 stars += game.state.galaxy[nq.x][nq.y].stars;
833             }
834         }
835         if (stars == 0)
836             return; /* nothing to supernova exists */
837         num = Rand()*stars + 1;
838         for_quadrants(nq.x) {
839             for_quadrants(nq.y) {
840                 num -= game.state.galaxy[nq.x][nq.y].stars;
841                 if (num <= 0)
842                     break;
843             }
844             if (num <=0)
845                 break;
846         }
847         if (idebug) {
848             proutn("=== Super nova here?");
849             if (ja() == true)
850                 nq = game.quadrant;
851         }
852     }
853
854     if (!same(nq, game.quadrant) || game.justin) {
855         /* it isn't here, or we just entered (treat as enroute) */
856         if (!damaged(DRADIO) || game.condition == docked) {
857             skip(1);
858             prout(_("Message from Starfleet Command       Stardate %.2f"), game.state.date);
859             prout(_("     Supernova in %s; caution advised."),
860                   cramlc(quadrant, nq));
861         }
862     }
863     else {
864         coord ns;
865         /* we are in the quadrant! */
866         num = Rand()* game.state.galaxy[nq.x][nq.y].stars + 1;
867         for_sectors(ns.x) {
868             for_sectors(ns.y) {
869                 if (game.quad[ns.x][ns.y]==IHSTAR) {
870                     num--;
871                     if (num==0)
872                         break;
873                 }
874             }
875             if (num==0)
876                 break;
877         }
878
879         skip(1);
880         prouts(_("***RED ALERT!  RED ALERT!"));
881         skip(1);
882         prout(_("***Incipient supernova detected at %s"), cramlc(sector, ns));
883         if (square(ns.x-game.sector.x) + square(ns.y-game.sector.y) <= 2.1) {
884             proutn(_("Emergency override attempts t"));
885             prouts("***************");
886             skip(1);
887             stars();
888             game.alldone = true;
889         }
890     }
891
892     /* destroy any Klingons in supernovaed quadrant */
893     kldead = game.state.galaxy[nq.x][nq.y].klingons;
894     game.state.galaxy[nq.x][nq.y].klingons = 0;
895     if (same(nq, game.state.kscmdr)) {
896         /* did in the Supercommander! */
897         game.state.nscrem = game.state.kscmdr.x = game.state.kscmdr.y = game.isatb =  0;
898         game.iscate = false;
899         unschedule(FSCMOVE);
900         unschedule(FSCDBAS);
901     }
902     if (game.state.remcom) {
903         int maxloop = game.state.remcom, l;
904         for (l = 1; l <= maxloop; l++) {
905             if (same(game.state.kcmdr[l], nq)) {
906                 game.state.kcmdr[l] = game.state.kcmdr[game.state.remcom];
907                 invalidate(game.state.kcmdr[game.state.remcom]);
908                 game.state.remcom--;
909                 kldead--;
910                 if (game.state.remcom==0)
911                     unschedule(FTBEAM);
912                 break;
913             }
914         }
915     }
916     game.state.remkl -= kldead;
917     /* destroy Romulans and planets in supernovaed quadrant */
918     nrmdead = game.state.galaxy[nq.x][nq.y].romulans;
919     game.state.galaxy[nq.x][nq.y].romulans = 0;
920     game.state.nromrem -= nrmdead;
921     npdead = num - nrmdead*10;
922     if (npdead) {
923         int loop;
924         for (loop = 0; loop < game.inplan; loop++)
925             if (same(game.state.planets[loop].w, nq)) {
926                 DESTROY(&game.state.planets[loop]);
927             }
928     }
929     /* Destroy any base in supernovaed quadrant */
930     if (game.state.rembase) {
931         int maxloop = game.state.rembase, loop;
932         for (loop = 1; loop <= maxloop; loop++)
933             if (same(game.state.baseq[loop], nq)) {
934                 game.state.baseq[loop] = game.state.baseq[game.state.rembase];
935                 invalidate(game.state.baseq[game.state.rembase]);
936                 game.state.rembase--;
937                 break;
938             }
939     }
940     /* If starship caused supernova, tally up destruction */
941     if (induced) {
942         game.state.starkl += game.state.galaxy[nq.x][nq.y].stars;
943         game.state.basekl += game.state.galaxy[nq.x][nq.y].starbase;
944         game.state.nplankl += npdead;
945     }
946     /* mark supernova in galaxy and in star chart */
947     if (same(game.quadrant, nq) || !damaged(DRADIO) || game.condition == docked)
948         game.state.galaxy[nq.x][nq.y].supernova = true;
949     /* If supernova destroys last Klingons give special message */
950     if (KLINGREM==0 && !same(nq, game.quadrant)) {
951         skip(2);
952         if (!induced)
953             prout(_("Lucky you!"));
954         proutn(_("A supernova in %s has just destroyed the last Klingons."),
955                cramlc(quadrant, nq));
956         finish(FWON);
957         return;
958     }
959     /* if some Klingons remain, continue or die in supernova */
960     if (game.alldone)
961         finish(FSNOVAED);
962     return;
963 }