Make the "crystals' member into an enumeration.
[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                     game.battle.x = game.battle.y = 0;
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                 game.battle.x = game.battle.y = 0;
363             }
364             break;
365         case FSCMOVE: /* Supercommander moves */
366             schedule(FSCMOVE, 0.2777);
367             if (!game.ientesc && !istract && game.isatb != 1 &&
368                         (!game.iscate || !game.justin)) 
369                 scom(&ipage);
370             break;
371         case FDSPROB: /* Move deep space probe */
372             schedule(FDSPROB, 0.01);
373             game.probex += game.probeinx;
374             game.probey += game.probeiny;
375             i = (int)(game.probex/QUADSIZE +0.05);
376             j = (int)(game.probey/QUADSIZE + 0.05);
377             if (game.probec.x != i || game.probec.y != j) {
378                 game.probec.x = i;
379                 game.probec.y = j;
380                 if (!VALID_QUADRANT(i, j) ||
381                     game.state.galaxy[game.probec.x][game.probec.y].supernova) {
382                     // Left galaxy or ran into supernova
383                     if (!damaged(DRADIO) || game.condition == docked) {
384                         if (!ipage) pause_game(true);
385                         ipage = true;
386                         skip(1);
387                         proutn(_("Lt. Uhura-  \"The deep space probe "));
388                         if (!VALID_QUADRANT(j, i))
389                             proutn(_("has left the galaxy"));
390                         else
391                             proutn(_("is no longer transmitting"));
392                         prout(".\"");
393                     }
394                     unschedule(FDSPROB);
395                     break;
396                 }
397                 if (!damaged(DRADIO) || game.condition == docked) {
398                     if (!ipage) pause_game(true);
399                     ipage = true;
400                     skip(1);
401                     proutn(_("Lt. Uhura-  \"The deep space probe is now in "));
402                     proutn(cramlc(quadrant, game.probec));
403                     prout(".\"");
404                 }
405             }
406             pdest = &game.state.galaxy[game.probec.x][game.probec.y];
407             /* Update star chart if Radio is working or have access to
408                radio. */
409             if (!damaged(DRADIO) || game.condition == docked) {
410                 struct page *chp = &game.state.chart[game.probec.x][game.probec.y];
411
412                 chp->klingons = pdest->klingons;
413                 chp->starbase = pdest->starbase;
414                 chp->stars = pdest->stars;
415                 pdest->charted = true;
416             }
417             game.proben--; // One less to travel
418             if (game.proben == 0 && game.isarmed && pdest->stars) {
419                 /* lets blow the sucker! */
420                 snova(true, &game.probec);
421                 unschedule(FDSPROB);
422                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) 
423                     return;
424             }
425             break;
426         case FDISTR: /* inhabited system issues distress call */
427             unschedule(FDISTR);
428             /* try a whole bunch of times to find something suitable */
429             i = 100;
430             do {
431                 /* need a quadrant which is not the current one,
432                    which has some stars which are inhabited and
433                    not already under attack, which is not
434                    supernova'ed, and which has some Klingons in it */
435                 w = randplace(GALSIZE);
436                 q = &game.state.galaxy[w.x][w.y];
437             } while (--i &&
438                      (same(game.quadrant, w) || q->planet == NOPLANET ||
439                       q->supernova || q->status!=secure || q->klingons<=0));
440             if (i == 0) {
441                 /* can't seem to find one; ignore this call */
442                 if (idebug)
443                     prout("=== Couldn't find location for distress event.");
444                 break;
445             }
446
447             /* got one!!  Schedule its enslavement */
448             ev = schedule(FENSLV, expran(game.intime));
449             ev->quadrant = w;
450             q->status = distressed;
451
452             /* tell the captain about it if we can */
453             if (!damaged(DRADIO) || game.condition == docked)
454             {
455                 prout("Uhura- Captain, %s in %s reports it is under attack",
456                       systnames[q->planet], cramlc(quadrant, w));
457                 prout("by a Klingon invasion fleet.");
458                 if (cancelrest())
459                     return;
460             }
461             break;
462         case FENSLV:            /* starsystem is enslaved */
463             ev = unschedule(FENSLV);
464             /* see if current distress call still active */
465             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
466             if (q->klingons <= 0) {
467                 q->status = secure;
468                 break;
469             }
470             q->status = enslaved;
471
472             /* play stork and schedule the first baby */
473             ev2 = schedule(FREPRO, expran(2.0 * game.intime));
474             ev2->quadrant = ev->quadrant;
475
476             /* report the disaster if we can */
477             if (!damaged(DRADIO) || game.condition == docked)
478             {
479                 prout("Uhura- We've lost contact with starsystem %s",
480                       systnames[q->planet]);
481                 prout("in %s.\n", cramlc(quadrant, ev->quadrant));
482             }
483             break;
484         case FREPRO:            /* Klingon reproduces */
485             /*
486              * If we ever switch to a real event queue, we'll need to
487              * explicitly retrieve and restore the x and y.
488              */
489             ev = schedule(FREPRO, expran(1.0 * game.intime));
490             /* see if current distress call still active */
491             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
492             if (q->klingons <= 0) {
493                 q->status = secure;
494                 break;
495             }
496             if (game.state.remkl >=MAXKLGAME)
497                 break;          /* full right now */
498             /* reproduce one Klingon */
499             w = ev->quadrant;
500             if (game.klhere >= MAXKLQUAD) {
501                 /* this quadrant not ok, pick an adjacent one */
502                 for (i = w.x - 1; i <= w.x + 1; i++)
503                 {
504                     for (j = w.y - 1; j <= w.y + 1; j++)
505                     {
506                         if (!VALID_QUADRANT(i, j))
507                             continue;
508                         q = &game.state.galaxy[w.x][w.y];
509                         /* check for this quad ok (not full & no snova) */
510                         if (q->klingons >= MAXKLQUAD || q->supernova)
511                             continue;
512                         goto foundit;
513                     }
514                 }
515                 break;  /* search for eligible quadrant failed */
516             foundit:
517                 w.x = i;
518                 w.y = j;
519             }
520
521             /* deliver the child */
522             game.state.remkl++;
523             q->klingons++;
524             if (same(game.quadrant, w))
525                 newkling(++game.klhere);
526
527             /* recompute time left */
528             game.state.remtime = game.state.remres/(game.state.remkl+4*game.state.remcom);
529             /* report the disaster if we can */
530             if (!damaged(DRADIO) || game.condition == docked)
531             {
532                 if (same(game.quadrant, w)) {
533                     prout("Spock- sensors indicate the Klingons have");
534                     prout("launched a warship from %s.", systnames[q->planet]);
535                 } else {
536                     prout("Uhura- Starfleet reports increased Klingon activity");
537                     if (q->planet != NOPLANET)
538                         proutn("near %s", systnames[q->planet]);
539                     prout("in %s.\n", cramlc(quadrant, w));
540                 }
541             }
542             break;
543         }
544     }
545 }
546
547                                 
548 void wait(void) 
549 /* wait on events */
550 {
551     int key;
552     double temp, delay, origTime;
553
554     game.ididit = false;
555     for (;;) {
556         key = scan();
557         if (key  != IHEOL) break;
558         proutn(_("How long? "));
559     }
560     chew();
561     if (key != IHREAL) {
562         huh();
563         return;
564     }
565     origTime = delay = aaitem;
566     if (delay <= 0.0) return;
567     if (delay >= game.state.remtime || game.nenhere != 0) {
568         proutn(_("Are you sure? "));
569         if (ja() == false) return;
570     }
571
572     /* Alternate resting periods (events) with attacks */
573
574     game.resting = true;
575     do {
576         if (delay <= 0) game.resting = false;
577         if (!game.resting) {
578             prout(_("%d stardates left."), (int)game.state.remtime);
579             return;
580         }
581         temp = game.optime = delay;
582
583         if (game.nenhere) {
584             double rtime = 1.0 + Rand();
585             if (rtime < temp) temp = rtime;
586             game.optime = temp;
587         }
588         if (game.optime < delay) attack(false);
589         if (game.alldone) return;
590         events();
591         game.ididit = true;
592         if (game.alldone) return;
593         delay -= temp;
594         /* Repair Deathray if long rest at starbase */
595         if (origTime-delay >= 9.99 && game.condition == docked)
596             game.damage[DDRAY] = 0.0;
597     } while 
598         // leave if quadrant supernovas
599         (!game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova);
600
601     game.resting = false;
602     game.optime = 0;
603 }
604
605 /*
606  *      A nova occurs.  It is the result of having a star hit with a
607  *      photon torpedo, or possibly of a probe warhead going off.
608  *      Stars that go nova cause stars which surround them to undergo
609  *      the same probabilistic process.  Klingons next to them are
610  *      destroyed.  And if the starship is next to it, it gets zapped.
611  *      If the zap is too much, it gets destroyed.
612  */
613 void nova(coord nov) 
614 /* star goes nova */
615 {
616     static double course[] =
617         {0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5};
618     int bot, top, top2, hits[QUADSIZE+1][3], kount, icx, icy, mm, nn, j;
619     int iquad, iquad1, i, ll;
620     coord newc, scratch;
621
622     if (Rand() < 0.05) {
623         /* Wow! We've supernova'ed */
624         snova(false, &nov);
625         return;
626     }
627
628     /* handle initial nova */
629     game.quad[nov.x][nov.y] = IHDOT;
630     crmena(false, IHSTAR, sector, nov);
631     prout(_(" novas."));
632     game.state.galaxy[game.quadrant.x][game.quadrant.y].stars--;
633     game.state.starkl++;
634         
635     /* Set up stack to recursively trigger adjacent stars */
636     bot = top = top2 = 1;
637     kount = 0;
638     icx = icy = 0;
639     hits[1][1] = nov.x;
640     hits[1][2] = nov.y;
641     while (1) {
642         for (mm = bot; mm <= top; mm++) 
643             for (nn = 1; nn <= 3; nn++)  /* nn,j represents coordinates around current */
644                 for (j = 1; j <= 3; j++) {
645                     if (j==2 && nn== 2) continue;
646                     scratch.x = hits[mm][1]+nn-2;
647                     scratch.y = hits[mm][2]+j-2;
648                     if (!VALID_SECTOR(scratch.y, scratch.x)) continue;
649                     iquad = game.quad[scratch.x][scratch.y];
650                     switch (iquad) {
651                     // case IHDOT:      /* Empty space ends reaction
652                     // case IHQUEST:
653                     // case IHBLANK:
654                     // case IHT:
655                     // case IHWEB:
656                     default:
657                         break;
658                     case IHSTAR: /* Affect another star */
659                         if (Rand() < 0.05) {
660                             /* This star supernovas */
661                             snova(false, &scratch);
662                             return;
663                         }
664                         top2++;
665                         hits[top2][1]=scratch.x;
666                         hits[top2][2]=scratch.y;
667                         game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1;
668                         game.state.starkl++;
669                         crmena(true, IHSTAR, sector, scratch);
670                         prout(_(" novas."));
671                         game.quad[scratch.x][scratch.y] = IHDOT;
672                         break;
673                     case IHP: /* Destroy planet */
674                         game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET;
675                         game.state.nplankl++;
676                         crmena(true, IHP, sector, scratch);
677                         prout(_(" destroyed."));
678                         DESTROY(&game.state.plnets[game.iplnet]);
679                         game.iplnet = game.plnet.x = game.plnet.y = 0;
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                         game.base.x = game.base.y = 0;
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 "), 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                 game.state.kcmdr[game.state.remcom].x = game.state.kcmdr[game.state.remcom].y = 0;
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                 game.state.baseq[game.state.rembase].x = game.state.baseq[game.state.rembase].y = 0;
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 }