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