More booleans cleanup.
[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 = (game.damage[DRADIO] != 0.0);
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         /* 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 EVCODE to occur */
145         game.optime -= xtime;
146         switch (evcode) {
147         case FSNOVA: /* Supernova */
148             if (!ipage) pause_game(1);
149             ipage=true;
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 (evcode==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) pause_game(1);
188             ipage=true;
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 (evcode==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) pause_game(1);
283             ipage = true;
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             hold = game.battle;
300             game.battle = game.state.kscmdr;
301             /* FALL THROUGH */
302         case FCDBAS: /* Commander succeeds in destroying base */
303             if (evcode==FCDBAS) {
304                 unschedule(FCDBAS);
305                 /* find the lucky pair */
306                 for_commanders(i)
307                     if (same(game.state.kcmdr[i], game.battle)) 
308                         break;
309                 if (i > game.state.remcom || game.state.rembase == 0 ||
310                     !game.state.galaxy[game.battle.x][game.battle.y].starbase) {
311                     /* No action to take after all */
312                     game.battle.x = game.battle.y = 0;
313                     break;
314                 }
315             }
316             /* Code merges here for any commander destroying base */
317             /* Not perfect, but will have to do */
318             /* Handle case where base is in same quadrant as starship */
319             if (same(game.battle, game.quadrant)) {
320                 game.state.chart[game.battle.x][game.battle.y].starbase = false;
321                 game.quad[game.base.x][game.base.y] = IHDOT;
322                 game.base.x=game.base.y=0;
323                 newcnd();
324                 skip(1);
325                 prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""));
326             }
327             else if (game.state.rembase != 1 &&
328                      (game.damage[DRADIO] <= 0.0 || game.condit == IHDOCKED)) {
329                 /* Get word via subspace radio */
330                 if (!ipage) pause_game(1);
331                 ipage = true;
332                 skip(1);
333                 prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"));
334                 proutn(_("   the starbase in "));
335                 proutn(cramlc(quadrant, game.battle));
336                 prout(_(" has been destroyed by"));
337                 if (game.isatb == 2) 
338                     prout(_("the Klingon Super-Commander"));
339                 else prout(_("a Klingon Commander"));
340                 game.state.chart[game.battle.x][game.battle.y].starbase = false;
341             }
342             /* Remove Starbase from galaxy */
343             game.state.galaxy[game.battle.x][game.battle.y].starbase = false;
344             for_starbases(i)
345                 if (same(game.state.baseq[i], game.battle))
346                     game.state.baseq[i] = game.state.baseq[game.state.rembase];
347             game.state.rembase--;
348             if (game.isatb == 2) {
349                 /* reinstate a commander's base attack */
350                 game.battle = hold;
351                 game.isatb = 0;
352             }
353             else {
354                 game.battle.x = game.battle.y = 0;
355             }
356             break;
357         case FSCMOVE: /* Supercommander moves */
358             schedule(FSCMOVE, 0.2777);
359             if (game.ientesc+istract==0 &&
360                 game.isatb != 1 &&
361                 (game.iscate != 1 || !game.justin)) scom(&ipage);
362             break;
363         case FDSPROB: /* Move deep space probe */
364             schedule(FDSPROB, 0.01);
365             game.probex += game.probeinx;
366             game.probey += game.probeiny;
367             i = (int)(game.probex/QUADSIZE +0.05);
368             j = (int)(game.probey/QUADSIZE + 0.05);
369             if (game.probec.x != i || game.probec.y != j) {
370                 game.probec.x = i;
371                 game.probec.y = j;
372                 if (!VALID_QUADRANT(i, j) ||
373                     game.state.galaxy[game.probec.x][game.probec.y].supernova) {
374                     // Left galaxy or ran into supernova
375                     if (game.damage[DRADIO]==0.0 || game.condit == IHDOCKED) {
376                         if (ipage==0) pause_game(1);
377                         ipage = 1;
378                         skip(1);
379                         proutn(_("Lt. Uhura-  \"The deep space probe "));
380                         if (!VALID_QUADRANT(j, i))
381                             proutn(_("has left the galaxy"));
382                         else
383                             proutn(_("is no longer transmitting"));
384                         prout(".\"");
385                     }
386                     unschedule(FDSPROB);
387                     break;
388                 }
389                 if (game.damage[DRADIO]==0.0   || game.condit == IHDOCKED) {
390                     if (ipage==0) pause_game(1);
391                     ipage = 1;
392                     skip(1);
393                     proutn(_("Lt. Uhura-  \"The deep space probe is now in "));
394                     proutn(cramlc(quadrant, game.probec));
395                     prout(".\"");
396                 }
397             }
398             pdest = &game.state.galaxy[game.probec.x][game.probec.y];
399             /* Update star chart if Radio is working or have access to
400                radio. */
401             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED) {
402                 struct page *chp = &game.state.chart[game.probec.x][game.probec.y];
403
404                 chp->klingons = pdest->klingons;
405                 chp->starbase = pdest->starbase;
406                 chp->stars = pdest->stars;
407                 pdest->charted = true;
408             }
409             game.proben--; // One less to travel
410             if (game.proben == 0 && game.isarmed && pdest->stars) {
411                 /* lets blow the sucker! */
412                 snova(1,0);
413                 unschedule(FDSPROB);
414                 if (game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova) 
415                     return;
416             }
417             break;
418         case FDISTR: /* inhabited system issues distress call */
419             unschedule(FDISTR);
420             /* try a whole bunch of times to find something suitable */
421             i = 100;
422             do {
423                 /* need a quadrant which is not the current one,
424                    which has some stars which are inhabited and
425                    not already under attack, which is not
426                    supernova'ed, and which has some Klingons in it */
427                 iran(GALSIZE, &w.x, &w.y);
428                 q = &game.state.galaxy[w.x][w.y];
429             } while (--i &&
430                      (same(game.quadrant, w) || q->planet == NOPLANET ||
431                       q->supernova || q->status!=secure || q->klingons<=0));
432             if (i == 0) {
433                 /* can't seem to find one; ignore this call */
434                 if (idebug)
435                     prout("=== Couldn't find location for distress event.");
436                 break;
437             }
438
439             /* got one!!  Schedule its enslavement */
440             ev = schedule(FENSLV, expran(game.intime));
441             ev->quadrant = w;
442             q->status = distressed;
443
444             /* tell the captain about it if we can */
445             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED)
446             {
447                 prout("Uhura- Captain, starsystem %s in quadrant %s",
448                       systemname(q->planet), cramlc(quadrant, w));
449                 prout("is under attack.");
450                 if (cancelrest())
451                     return;
452             }
453             break;
454         case FENSLV:            /* starsystem is enslaved */
455             ev = unschedule(FENSLV);
456             /* see if current distress call still active */
457             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
458             if (q->klingons <= 0) {
459                 q->status = secure;
460                 break;
461             }
462             q->status = enslaved;
463
464             /* play stork and schedule the first baby */
465             ev2 = schedule(FREPRO, expran(2.0 * game.intime));
466             ev2->quadrant = ev->quadrant;
467
468             /* report the disaster if we can */
469             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED)
470             {
471                 prout("Uhura- We've lost contact with starsystem %s",
472                       systemname(q->planet));
473                 prout("in quadrant %s.\n", cramlc(quadrant, ev->quadrant));
474             }
475             break;
476         case FREPRO:            /* Klingon reproduces */
477             /*
478              * If we ever switch to a real event queue, we'll need to
479              * explicitly retrieve and restore the x and y.
480              */
481             ev = schedule(FREPRO, expran(1.0 * game.intime));
482             /* see if current distress call still active */
483             q = &game.state.galaxy[ev->quadrant.x][ev->quadrant.y];
484             if (q->klingons <= 0) {
485                 q->status = secure;
486                 break;
487             }
488             /* reproduce one Klingon */
489             w = ev->quadrant;
490             if (game.state.remkl >=MAXKLGAME)
491                 break;          /* full right now */
492             /* this quadrant not ok, pick an adjacent one */
493             for (i = w.x - 1; i <= w.x + 1; i++)
494             {
495                 for (j = w.y - 1; j <= w.y + 1; j++)
496                 {
497                     if (!VALID_QUADRANT(i, j))
498                         continue;
499                     q = &game.state.galaxy[w.x][w.y];
500                     /* check for this quad ok (not full & no snova) */
501                     if (q->klingons >= MAXKLQUAD || !q->supernova)
502                         continue;
503                     goto foundit;
504                 }
505             }
506             break;      /* search for eligible quadrant failed */
507         foundit:
508             w.x = i;
509             w.y = j;
510
511             /* deliver the child */
512             game.state.remkl++;
513             q->klingons++;
514             if (same(game.quadrant, w))
515                 newkling(++game.klhere, &hold);
516
517             /* recompute time left */
518             game.state.remtime = game.state.remres/(game.state.remkl+4*game.state.remcom);
519             break;
520         }
521     }
522 }
523
524                                 
525 void wait(void) 
526 {
527     int key;
528     double temp, delay, origTime;
529
530     game.ididit = 0;
531     for (;;) {
532         key = scan();
533         if (key  != IHEOL) break;
534         proutn(_("How long? "));
535     }
536     chew();
537     if (key != IHREAL) {
538         huh();
539         return;
540     }
541     origTime = delay = aaitem;
542     if (delay <= 0.0) return;
543     if (delay >= game.state.remtime || game.nenhere != 0) {
544         proutn(_("Are you sure? "));
545         if (ja() == 0) return;
546     }
547
548     /* Alternate resting periods (events) with attacks */
549
550     game.resting = true;
551     do {
552         if (delay <= 0) game.resting = false;
553         if (!game.resting) {
554             prout(_("%d stardates left."), (int)game.state.remtime);
555             return;
556         }
557         temp = game.optime = delay;
558
559         if (game.nenhere) {
560             double rtime = 1.0 + Rand();
561             if (rtime < temp) temp = rtime;
562             game.optime = temp;
563         }
564         if (game.optime < delay) attack(0);
565         if (game.alldone) return;
566         events();
567         game.ididit = 1;
568         if (game.alldone) return;
569         delay -= temp;
570         /* Repair Deathray if long rest at starbase */
571         if (origTime-delay >= 9.99 && game.condit == IHDOCKED)
572             game.damage[DDRAY] = 0.0;
573     } while 
574         // leave if quadrant supernovas
575         (!game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova);
576
577     game.resting = false;
578     game.optime = 0;
579 }
580
581 void nova(int ix, int iy) 
582 {
583     static double course[] =
584         {0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5};
585     int bot, top, top2, hits[QUADSIZE+1][3], kount, icx, icy, mm, nn, j;
586     int iquad, iquad1, i, ll;
587     coord newc, nov, scratch;
588
589     nov.x = ix; nov.y = iy;
590     if (Rand() < 0.05) {
591         /* Wow! We've supernova'ed */
592         snova(ix, iy);
593         return;
594     }
595
596     /* handle initial nova */
597     game.quad[ix][iy] = IHDOT;
598     crmena(1, IHSTAR, 2, nov);
599     prout(_(" novas."));
600     game.state.galaxy[game.quadrant.x][game.quadrant.y].stars--;
601     game.state.starkl++;
602         
603     /* Set up stack to recursively trigger adjacent stars */
604     bot = top = top2 = 1;
605     kount = 0;
606     icx = icy = 0;
607     hits[1][1] = ix;
608     hits[1][2] = iy;
609     while (1) {
610         for (mm = bot; mm <= top; mm++) 
611             for (nn = 1; nn <= 3; nn++)  /* nn,j represents coordinates around current */
612                 for (j = 1; j <= 3; j++) {
613                     if (j==2 && nn== 2) continue;
614                     scratch.x = hits[mm][1]+nn-2;
615                     scratch.y = hits[mm][2]+j-2;
616                     if (!VALID_SECTOR(scratch.y, scratch.x)) continue;
617                     iquad = game.quad[scratch.x][scratch.y];
618                     switch (iquad) {
619                     // case IHDOT:      /* Empty space ends reaction
620                     // case IHQUEST:
621                     // case IHBLANK:
622                     // case IHT:
623                     // case IHWEB:
624                     default:
625                         break;
626                     case IHSTAR: /* Affect another star */
627                         if (Rand() < 0.05) {
628                             /* This star supernovas */
629                             snova(scratch.x,scratch.y);
630                             return;
631                         }
632                         top2++;
633                         hits[top2][1]=scratch.x;
634                         hits[top2][2]=scratch.y;
635                         game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1;
636                         game.state.starkl++;
637                         crmena(1, IHSTAR, 2, scratch);
638                         prout(_(" novas."));
639                         game.quad[scratch.x][scratch.y] = IHDOT;
640                         break;
641                     case IHP: /* Destroy planet */
642                         game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET;
643                         game.state.nplankl++;
644                         crmena(1, IHP, 2, scratch);
645                         prout(_(" destroyed."));
646                         DESTROY(&game.state.plnets[game.iplnet]);
647                         game.iplnet = game.plnet.x = game.plnet.y = 0;
648                         if (game.landed == 1) {
649                             finish(FPNOVA);
650                             return;
651                         }
652                         game.quad[scratch.x][scratch.y] = IHDOT;
653                         break;
654                     case IHB: /* Destroy base */
655                         game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase = false;
656                         for_starbases(i)
657                             if (game.state.baseq[i].x==game.quadrant.x && game.state.baseq[i].y==game.quadrant.y) 
658                                 break;
659                         game.state.baseq[i] = game.state.baseq[game.state.rembase];
660                         game.state.rembase--;
661                         game.base.x = game.base.y = 0;
662                         game.state.basekl++;
663                         newcnd();
664                         crmena(1, IHB, 2, scratch);
665                         prout(_(" destroyed."));
666                         game.quad[scratch.x][scratch.y] = IHDOT;
667                         break;
668                     case IHE: /* Buffet ship */
669                     case IHF:
670                         prout(_("***Starship buffeted by nova."));
671                         if (game.shldup) {
672                             if (game.shield >= 2000.0) game.shield -= 2000.0;
673                             else {
674                                 double diff = 2000.0 - game.shield;
675                                 game.energy -= diff;
676                                 game.shield = 0.0;
677                                 game.shldup = false;
678                                 prout(_("***Shields knocked out."));
679                                 game.damage[DSHIELD] += 0.005*game.damfac*Rand()*diff;
680                             }
681                         }
682                         else game.energy -= 2000.0;
683                         if (game.energy <= 0) {
684                             finish(FNOVA);
685                             return;
686                         }
687                         /* add in course nova contributes to kicking starship*/
688                         icx += game.sector.x-hits[mm][1];
689                         icy += game.sector.y-hits[mm][2];
690                         kount++;
691                         break;
692                     case IHK: /* kill klingon */
693                         deadkl(scratch,iquad, scratch.x, scratch.y);
694                         break;
695                     case IHC: /* Damage/destroy big enemies */
696                     case IHS:
697                     case IHR:
698                         for_local_enemies(ll)
699                             if (game.ks[ll].x==scratch.x && game.ks[ll].y==scratch.y) break;
700                         game.kpower[ll] -= 800.0; /* If firepower is lost, die */
701                         if (game.kpower[ll] <= 0.0) {
702                             deadkl(scratch, iquad, scratch.x, scratch.y);
703                             break;
704                         }
705                         newc.x = scratch.x + scratch.x - hits[mm][1];
706                         newc.y = scratch.y + scratch.y - hits[mm][2];
707                         crmena(1, iquad, 2, scratch);
708                         proutn(_(" damaged"));
709                         if (!VALID_SECTOR(newc.x, newc.y)) {
710                             /* can't leave quadrant */
711                             skip(1);
712                             break;
713                         }
714                         iquad1 = game.quad[newc.x][newc.y];
715                         if (iquad1 == IHBLANK) {
716                             proutn(_(", blasted into "));
717                             crmena(0, IHBLANK, 2, newc);
718                             skip(1);
719                             deadkl(scratch, iquad, newc.x, newc.y);
720                             break;
721                         }
722                         if (iquad1 != IHDOT) {
723                             /* can't move into something else */
724                             skip(1);
725                             break;
726                         }
727                         proutn(_(", buffeted to "));
728                         proutn(cramlc(sector, newc));
729                         game.quad[scratch.x][scratch.y] = IHDOT;
730                         game.quad[newc.x][newc.y] = iquad;
731                         game.ks[ll].x = newc.x;
732                         game.ks[ll].y = newc.y;
733                         game.kavgd[ll] = sqrt(square(game.sector.x-newc.x)+square(game.sector.y-newc.y));
734                         game.kdist[ll] = game.kavgd[ll];
735                         skip(1);
736                         break;
737                     }
738                 }
739         if (top == top2) 
740             break;
741         bot = top + 1;
742         top = top2;
743     }
744     if (kount==0) 
745         return;
746
747     /* Starship affected by nova -- kick it away. */
748     game.dist = kount*0.1;
749     if (icx) icx = (icx < 0 ? -1 : 1);
750     if (icy) icy = (icy < 0 ? -1 : 1);
751     game.direc = course[3*(icx+1)+icy+2];
752     if (game.direc == 0.0) game.dist = 0.0;
753     if (game.dist == 0.0) return;
754     game.optime = 10.0*game.dist/16.0;
755     skip(1);
756     prout(_("Force of nova displaces starship."));
757     game.iattak=2;      /* Eliminates recursion problem */
758     imove();
759     game.optime = 10.0*game.dist/16.0;
760     return;
761 }
762         
763         
764 void snova(int insx, int insy) 
765 {
766     int comdead, nsx, nsy, num=0, kldead, iscdead;
767     int nrmdead, npdead;
768     int incipient=0;
769     coord nq;
770
771     nq.x = nq.y = 0;
772     nsx = insy;
773     nsy = insy;
774
775     if (insy== 0) {
776         if (insx == 1)
777             /* NOVAMAX being used */
778             nq = game.probec;
779         else {
780             int stars = 0;
781             /* Scheduled supernova -- select star */
782             /* logic changed here so that we won't favor quadrants in top
783                left of universe */
784             for_quadrants(nq.x) {
785                 for_quadrants(nq.y) {
786                     stars += game.state.galaxy[nq.x][nq.y].stars;
787                 }
788             }
789             if (stars == 0) return; /* nothing to supernova exists */
790             num = Rand()*stars + 1;
791             for_quadrants(nq.x) {
792                 for_quadrants(nq.y) {
793                     num -= game.state.galaxy[nq.x][nq.y].stars;
794                     if (num <= 0) break;
795                 }
796                 if (num <=0) break;
797             }
798             if (idebug) {
799                 proutn("=== Super nova here?");
800                 if (ja()==1) {
801                     nq.x = game.quadrant.x;
802                     nq.y = game.quadrant.y;
803                 }
804             }
805         }
806
807         if (nq.x != game.quadrant.y || nq.y != game.quadrant.y || game.justin != 0) {
808             /* it isn't here, or we just entered (treat as inroute) */
809             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED) {
810                 skip(1);
811                 prout(_("Message from Starfleet Command       Stardate %.2f"), game.state.date);
812                 prout(_("     Supernova in %s; caution advised."),
813                       cramlc(quadrant, nq));
814             }
815         }
816         else {
817             /* we are in the quadrant! */
818             incipient = 1;
819             num = Rand()* game.state.galaxy[nq.x][nq.y].stars + 1;
820             for_sectors(nsx) {
821                 for_sectors(nsy) {
822                     if (game.quad[nsx][nsy]==IHSTAR) {
823                         num--;
824                         if (num==0) break;
825                     }
826                 }
827                 if (num==0) break;
828             }
829         }
830     }
831     else {
832         incipient = 1;
833     }
834
835     if (incipient) {
836         coord nd;
837         skip(1);
838         prouts(_("***RED ALERT!  RED ALERT!"));
839         skip(1);
840         nd.x = nsx; nd.y = nsy;
841         prout(_("***Incipient supernova detected at "), cramlc(sector, nd));
842         nq = game.quadrant;
843         if (square(nsx-game.sector.x) + square(nsy-game.sector.y) <= 2.1) {
844             proutn(_("Emergency override attempts t"));
845             prouts("***************");
846             skip(1);
847             stars();
848             game.alldone=1;
849         }
850     }
851     /* destroy any Klingons in supernovaed quadrant */
852     kldead = game.state.galaxy[nq.x][nq.y].klingons;
853     game.state.galaxy[nq.x][nq.y].klingons = 0;
854     comdead = iscdead = 0;
855     if (same(nq, game.state.kscmdr)) {
856         /* did in the Supercommander! */
857         game.state.nscrem = game.state.kscmdr.x = game.state.kscmdr.y = game.isatb = game.iscate = 0;
858         iscdead = 1;
859         unschedule(FSCMOVE);
860         unschedule(FSCDBAS);
861     }
862     if (game.state.remcom) {
863         int maxloop = game.state.remcom, l;
864         for (l = 1; l <= maxloop; l++) {
865             if (same(game.state.kcmdr[l], nq)) {
866                 game.state.kcmdr[l] = game.state.kcmdr[game.state.remcom];
867                 game.state.kcmdr[game.state.remcom].x = game.state.kcmdr[game.state.remcom].y = 0;
868                 game.state.remcom--;
869                 kldead--;
870                 comdead++;
871                 if (game.state.remcom==0) unschedule(FTBEAM);
872                 break;
873             }
874         }
875     }
876     game.state.remkl -= kldead;
877     /* destroy Romulans and planets in supernovaed quadrant */
878     nrmdead = game.state.galaxy[nq.x][nq.y].romulans;
879     game.state.galaxy[nq.x][nq.y].romulans = 0;
880     game.state.nromrem -= nrmdead;
881     npdead = num - nrmdead*10;
882     if (npdead) {
883         int l;
884         for (l = 0; l < game.inplan; l++)
885             if (same(game.state.plnets[l].w, nq)) {
886                 DESTROY(&game.state.plnets[l]);
887             }
888     }
889     /* Destroy any base in supernovaed quadrant */
890     if (game.state.rembase) {
891         int maxloop = game.state.rembase, l;
892         for (l = 1; l <= maxloop; l++)
893             if (same(game.state.baseq[l], nq)) {
894                 game.state.baseq[l] = game.state.baseq[game.state.rembase];
895                 game.state.baseq[game.state.rembase].x = game.state.baseq[game.state.rembase].y = 0;
896                 game.state.rembase--;
897                 break;
898             }
899     }
900     /* If starship caused supernova, tally up destruction */
901     if (insx) {
902         game.state.starkl += game.state.galaxy[nq.x][nq.y].stars;
903         game.state.basekl += game.state.galaxy[nq.x][nq.y].starbase;
904         game.state.nplankl += npdead;
905     }
906     /* mark supernova in galaxy and in star chart */
907     if (same(game.quadrant, nq) || game.damage[DRADIO] == 0 || game.condit == IHDOCKED)
908         game.state.galaxy[nq.x][nq.y].supernova = true;
909     /* If supernova destroys last Klingons give special message */
910     if (KLINGREM==0 && (nq.x != game.quadrant.x || nq.y != game.quadrant.y)) {
911         skip(2);
912         if (insx == 0) prout(_("Lucky you!"));
913         proutn(_("A supernova in %s has just destroyed the last Klingons."),
914                cramlc(quadrant, nq));
915         finish(FWON);
916         return;
917     }
918     /* if some Klingons remain, continue or die in supernova */
919     if (game.alldone) finish(FSNOVAED);
920     return;
921 }
922                 
923