7fdf27157544123fc86abd57e31ab597d2e4f206
[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, %s in %s reports it is under attack",
448                       systemname(q->planet), cramlc(quadrant, w));
449                 prout("by a Klingon invasion fleet.");
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 %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             if (game.state.remkl >=MAXKLGAME)
489                 break;          /* full right now */
490             /* reproduce one Klingon */
491             w = ev->quadrant;
492             if (game.klhere >= MAXKLQUAD) {
493                 /* this quadrant not ok, pick an adjacent one */
494                 for (i = w.x - 1; i <= w.x + 1; i++)
495                 {
496                     for (j = w.y - 1; j <= w.y + 1; j++)
497                     {
498                         if (!VALID_QUADRANT(i, j))
499                             continue;
500                         q = &game.state.galaxy[w.x][w.y];
501                         /* check for this quad ok (not full & no snova) */
502                         if (q->klingons >= MAXKLQUAD || q->supernova)
503                             continue;
504                         goto foundit;
505                     }
506                 }
507                 break;  /* search for eligible quadrant failed */
508             foundit:
509                 w.x = i;
510                 w.y = j;
511             }
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             /* report the disaster if we can */
522             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED)
523             {
524                 if (same(game.quadrant, w)) {
525                     prout("Spock- sensors indicate the Klingons have");
526                     prout("launched a warship from %s.",systemname(q->planet));
527                 } else {
528                     prout("Uhura- Starfleet reports increased Klingon activity");
529                     if (q->planet != NOPLANET)
530                         proutn("near %s", systemname(q->planet));
531                     prout("in %s.\n", cramlc(quadrant, w));
532                 }
533             }
534             break;
535         }
536     }
537 }
538
539                                 
540 void wait(void) 
541 {
542     int key;
543     double temp, delay, origTime;
544
545     game.ididit = 0;
546     for (;;) {
547         key = scan();
548         if (key  != IHEOL) break;
549         proutn(_("How long? "));
550     }
551     chew();
552     if (key != IHREAL) {
553         huh();
554         return;
555     }
556     origTime = delay = aaitem;
557     if (delay <= 0.0) return;
558     if (delay >= game.state.remtime || game.nenhere != 0) {
559         proutn(_("Are you sure? "));
560         if (ja() == 0) return;
561     }
562
563     /* Alternate resting periods (events) with attacks */
564
565     game.resting = true;
566     do {
567         if (delay <= 0) game.resting = false;
568         if (!game.resting) {
569             prout(_("%d stardates left."), (int)game.state.remtime);
570             return;
571         }
572         temp = game.optime = delay;
573
574         if (game.nenhere) {
575             double rtime = 1.0 + Rand();
576             if (rtime < temp) temp = rtime;
577             game.optime = temp;
578         }
579         if (game.optime < delay) attack(0);
580         if (game.alldone) return;
581         events();
582         game.ididit = 1;
583         if (game.alldone) return;
584         delay -= temp;
585         /* Repair Deathray if long rest at starbase */
586         if (origTime-delay >= 9.99 && game.condit == IHDOCKED)
587             game.damage[DDRAY] = 0.0;
588     } while 
589         // leave if quadrant supernovas
590         (!game.state.galaxy[game.quadrant.x][game.quadrant.y].supernova);
591
592     game.resting = false;
593     game.optime = 0;
594 }
595
596 void nova(int ix, int iy) 
597 {
598     static double course[] =
599         {0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5};
600     int bot, top, top2, hits[QUADSIZE+1][3], kount, icx, icy, mm, nn, j;
601     int iquad, iquad1, i, ll;
602     coord newc, nov, scratch;
603
604     nov.x = ix; nov.y = iy;
605     if (Rand() < 0.05) {
606         /* Wow! We've supernova'ed */
607         snova(ix, iy);
608         return;
609     }
610
611     /* handle initial nova */
612     game.quad[ix][iy] = IHDOT;
613     crmena(1, IHSTAR, 2, nov);
614     prout(_(" novas."));
615     game.state.galaxy[game.quadrant.x][game.quadrant.y].stars--;
616     game.state.starkl++;
617         
618     /* Set up stack to recursively trigger adjacent stars */
619     bot = top = top2 = 1;
620     kount = 0;
621     icx = icy = 0;
622     hits[1][1] = ix;
623     hits[1][2] = iy;
624     while (1) {
625         for (mm = bot; mm <= top; mm++) 
626             for (nn = 1; nn <= 3; nn++)  /* nn,j represents coordinates around current */
627                 for (j = 1; j <= 3; j++) {
628                     if (j==2 && nn== 2) continue;
629                     scratch.x = hits[mm][1]+nn-2;
630                     scratch.y = hits[mm][2]+j-2;
631                     if (!VALID_SECTOR(scratch.y, scratch.x)) continue;
632                     iquad = game.quad[scratch.x][scratch.y];
633                     switch (iquad) {
634                     // case IHDOT:      /* Empty space ends reaction
635                     // case IHQUEST:
636                     // case IHBLANK:
637                     // case IHT:
638                     // case IHWEB:
639                     default:
640                         break;
641                     case IHSTAR: /* Affect another star */
642                         if (Rand() < 0.05) {
643                             /* This star supernovas */
644                             snova(scratch.x,scratch.y);
645                             return;
646                         }
647                         top2++;
648                         hits[top2][1]=scratch.x;
649                         hits[top2][2]=scratch.y;
650                         game.state.galaxy[game.quadrant.x][game.quadrant.y].stars -= 1;
651                         game.state.starkl++;
652                         crmena(1, IHSTAR, 2, scratch);
653                         prout(_(" novas."));
654                         game.quad[scratch.x][scratch.y] = IHDOT;
655                         break;
656                     case IHP: /* Destroy planet */
657                         game.state.galaxy[game.quadrant.x][game.quadrant.y].planet = NOPLANET;
658                         game.state.nplankl++;
659                         crmena(1, IHP, 2, scratch);
660                         prout(_(" destroyed."));
661                         DESTROY(&game.state.plnets[game.iplnet]);
662                         game.iplnet = game.plnet.x = game.plnet.y = 0;
663                         if (game.landed == 1) {
664                             finish(FPNOVA);
665                             return;
666                         }
667                         game.quad[scratch.x][scratch.y] = IHDOT;
668                         break;
669                     case IHB: /* Destroy base */
670                         game.state.galaxy[game.quadrant.x][game.quadrant.y].starbase = false;
671                         for_starbases(i)
672                             if (game.state.baseq[i].x==game.quadrant.x && game.state.baseq[i].y==game.quadrant.y) 
673                                 break;
674                         game.state.baseq[i] = game.state.baseq[game.state.rembase];
675                         game.state.rembase--;
676                         game.base.x = game.base.y = 0;
677                         game.state.basekl++;
678                         newcnd();
679                         crmena(1, IHB, 2, scratch);
680                         prout(_(" destroyed."));
681                         game.quad[scratch.x][scratch.y] = IHDOT;
682                         break;
683                     case IHE: /* Buffet ship */
684                     case IHF:
685                         prout(_("***Starship buffeted by nova."));
686                         if (game.shldup) {
687                             if (game.shield >= 2000.0) game.shield -= 2000.0;
688                             else {
689                                 double diff = 2000.0 - game.shield;
690                                 game.energy -= diff;
691                                 game.shield = 0.0;
692                                 game.shldup = false;
693                                 prout(_("***Shields knocked out."));
694                                 game.damage[DSHIELD] += 0.005*game.damfac*Rand()*diff;
695                             }
696                         }
697                         else game.energy -= 2000.0;
698                         if (game.energy <= 0) {
699                             finish(FNOVA);
700                             return;
701                         }
702                         /* add in course nova contributes to kicking starship*/
703                         icx += game.sector.x-hits[mm][1];
704                         icy += game.sector.y-hits[mm][2];
705                         kount++;
706                         break;
707                     case IHK: /* kill klingon */
708                         deadkl(scratch,iquad, scratch.x, scratch.y);
709                         break;
710                     case IHC: /* Damage/destroy big enemies */
711                     case IHS:
712                     case IHR:
713                         for_local_enemies(ll)
714                             if (game.ks[ll].x==scratch.x && game.ks[ll].y==scratch.y) break;
715                         game.kpower[ll] -= 800.0; /* If firepower is lost, die */
716                         if (game.kpower[ll] <= 0.0) {
717                             deadkl(scratch, iquad, scratch.x, scratch.y);
718                             break;
719                         }
720                         newc.x = scratch.x + scratch.x - hits[mm][1];
721                         newc.y = scratch.y + scratch.y - hits[mm][2];
722                         crmena(1, iquad, 2, scratch);
723                         proutn(_(" damaged"));
724                         if (!VALID_SECTOR(newc.x, newc.y)) {
725                             /* can't leave quadrant */
726                             skip(1);
727                             break;
728                         }
729                         iquad1 = game.quad[newc.x][newc.y];
730                         if (iquad1 == IHBLANK) {
731                             proutn(_(", blasted into "));
732                             crmena(0, IHBLANK, 2, newc);
733                             skip(1);
734                             deadkl(scratch, iquad, newc.x, newc.y);
735                             break;
736                         }
737                         if (iquad1 != IHDOT) {
738                             /* can't move into something else */
739                             skip(1);
740                             break;
741                         }
742                         proutn(_(", buffeted to "));
743                         proutn(cramlc(sector, newc));
744                         game.quad[scratch.x][scratch.y] = IHDOT;
745                         game.quad[newc.x][newc.y] = iquad;
746                         game.ks[ll].x = newc.x;
747                         game.ks[ll].y = newc.y;
748                         game.kavgd[ll] = sqrt(square(game.sector.x-newc.x)+square(game.sector.y-newc.y));
749                         game.kdist[ll] = game.kavgd[ll];
750                         skip(1);
751                         break;
752                     }
753                 }
754         if (top == top2) 
755             break;
756         bot = top + 1;
757         top = top2;
758     }
759     if (kount==0) 
760         return;
761
762     /* Starship affected by nova -- kick it away. */
763     game.dist = kount*0.1;
764     if (icx) icx = (icx < 0 ? -1 : 1);
765     if (icy) icy = (icy < 0 ? -1 : 1);
766     game.direc = course[3*(icx+1)+icy+2];
767     if (game.direc == 0.0) game.dist = 0.0;
768     if (game.dist == 0.0) return;
769     game.optime = 10.0*game.dist/16.0;
770     skip(1);
771     prout(_("Force of nova displaces starship."));
772     game.iattak=2;      /* Eliminates recursion problem */
773     imove();
774     game.optime = 10.0*game.dist/16.0;
775     return;
776 }
777         
778         
779 void snova(int insx, int insy) 
780 {
781     int comdead, nsx, nsy, num=0, kldead, iscdead;
782     int nrmdead, npdead;
783     int incipient=0;
784     coord nq;
785
786     nq.x = nq.y = 0;
787     nsx = insy;
788     nsy = insy;
789
790     if (insy== 0) {
791         if (insx == 1)
792             /* NOVAMAX being used */
793             nq = game.probec;
794         else {
795             int stars = 0;
796             /* Scheduled supernova -- select star */
797             /* logic changed here so that we won't favor quadrants in top
798                left of universe */
799             for_quadrants(nq.x) {
800                 for_quadrants(nq.y) {
801                     stars += game.state.galaxy[nq.x][nq.y].stars;
802                 }
803             }
804             if (stars == 0) return; /* nothing to supernova exists */
805             num = Rand()*stars + 1;
806             for_quadrants(nq.x) {
807                 for_quadrants(nq.y) {
808                     num -= game.state.galaxy[nq.x][nq.y].stars;
809                     if (num <= 0) break;
810                 }
811                 if (num <=0) break;
812             }
813             if (idebug) {
814                 proutn("=== Super nova here?");
815                 if (ja()==1) {
816                     nq.x = game.quadrant.x;
817                     nq.y = game.quadrant.y;
818                 }
819             }
820         }
821
822         if (nq.x != game.quadrant.y || nq.y != game.quadrant.y || game.justin != 0) {
823             /* it isn't here, or we just entered (treat as inroute) */
824             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED) {
825                 skip(1);
826                 prout(_("Message from Starfleet Command       Stardate %.2f"), game.state.date);
827                 prout(_("     Supernova in %s; caution advised."),
828                       cramlc(quadrant, nq));
829             }
830         }
831         else {
832             /* we are in the quadrant! */
833             incipient = 1;
834             num = Rand()* game.state.galaxy[nq.x][nq.y].stars + 1;
835             for_sectors(nsx) {
836                 for_sectors(nsy) {
837                     if (game.quad[nsx][nsy]==IHSTAR) {
838                         num--;
839                         if (num==0) break;
840                     }
841                 }
842                 if (num==0) break;
843             }
844         }
845     }
846     else {
847         incipient = 1;
848     }
849
850     if (incipient) {
851         coord nd;
852         skip(1);
853         prouts(_("***RED ALERT!  RED ALERT!"));
854         skip(1);
855         nd.x = nsx; nd.y = nsy;
856         prout(_("***Incipient supernova detected at "), cramlc(sector, nd));
857         nq = game.quadrant;
858         if (square(nsx-game.sector.x) + square(nsy-game.sector.y) <= 2.1) {
859             proutn(_("Emergency override attempts t"));
860             prouts("***************");
861             skip(1);
862             stars();
863             game.alldone=1;
864         }
865     }
866     /* destroy any Klingons in supernovaed quadrant */
867     kldead = game.state.galaxy[nq.x][nq.y].klingons;
868     game.state.galaxy[nq.x][nq.y].klingons = 0;
869     comdead = iscdead = 0;
870     if (same(nq, game.state.kscmdr)) {
871         /* did in the Supercommander! */
872         game.state.nscrem = game.state.kscmdr.x = game.state.kscmdr.y = game.isatb = game.iscate = 0;
873         iscdead = 1;
874         unschedule(FSCMOVE);
875         unschedule(FSCDBAS);
876     }
877     if (game.state.remcom) {
878         int maxloop = game.state.remcom, l;
879         for (l = 1; l <= maxloop; l++) {
880             if (same(game.state.kcmdr[l], nq)) {
881                 game.state.kcmdr[l] = game.state.kcmdr[game.state.remcom];
882                 game.state.kcmdr[game.state.remcom].x = game.state.kcmdr[game.state.remcom].y = 0;
883                 game.state.remcom--;
884                 kldead--;
885                 comdead++;
886                 if (game.state.remcom==0) unschedule(FTBEAM);
887                 break;
888             }
889         }
890     }
891     game.state.remkl -= kldead;
892     /* destroy Romulans and planets in supernovaed quadrant */
893     nrmdead = game.state.galaxy[nq.x][nq.y].romulans;
894     game.state.galaxy[nq.x][nq.y].romulans = 0;
895     game.state.nromrem -= nrmdead;
896     npdead = num - nrmdead*10;
897     if (npdead) {
898         int l;
899         for (l = 0; l < game.inplan; l++)
900             if (same(game.state.plnets[l].w, nq)) {
901                 DESTROY(&game.state.plnets[l]);
902             }
903     }
904     /* Destroy any base in supernovaed quadrant */
905     if (game.state.rembase) {
906         int maxloop = game.state.rembase, l;
907         for (l = 1; l <= maxloop; l++)
908             if (same(game.state.baseq[l], nq)) {
909                 game.state.baseq[l] = game.state.baseq[game.state.rembase];
910                 game.state.baseq[game.state.rembase].x = game.state.baseq[game.state.rembase].y = 0;
911                 game.state.rembase--;
912                 break;
913             }
914     }
915     /* If starship caused supernova, tally up destruction */
916     if (insx) {
917         game.state.starkl += game.state.galaxy[nq.x][nq.y].stars;
918         game.state.basekl += game.state.galaxy[nq.x][nq.y].starbase;
919         game.state.nplankl += npdead;
920     }
921     /* mark supernova in galaxy and in star chart */
922     if (same(game.quadrant, nq) || game.damage[DRADIO] == 0 || game.condit == IHDOCKED)
923         game.state.galaxy[nq.x][nq.y].supernova = true;
924     /* If supernova destroys last Klingons give special message */
925     if (KLINGREM==0 && (nq.x != game.quadrant.x || nq.y != game.quadrant.y)) {
926         skip(2);
927         if (insx == 0) prout(_("Lucky you!"));
928         proutn(_("A supernova in %s has just destroyed the last Klingons."),
929                cramlc(quadrant, nq));
930         finish(FWON);
931         return;
932     }
933     /* if some Klingons remain, continue or die in supernova */
934     if (game.alldone) finish(FSNOVAED);
935     return;
936 }
937                 
938