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