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