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