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