Chase more booleans and enums.
[super-star-trek.git] / src / ai.c
1 #include "sst.h"
2
3 static bool tryexit(coord look, int ienm, int loccom, bool irun) 
4 /* a bad guy attempts to bug out */
5 {
6     int n;
7     coord iq;
8
9     iq.x = game.quadrant.x+(look.x+(QUADSIZE-1))/QUADSIZE - 1;
10     iq.y = game.quadrant.y+(look.y+(QUADSIZE-1))/QUADSIZE - 1;
11     if (!VALID_QUADRANT(iq.x,iq.y) ||
12         game.state.galaxy[iq.x][iq.y].supernova ||
13         game.state.galaxy[iq.x][iq.y].klingons > 8)
14         return false; /* no can do -- neg energy, supernovae, or >8 Klingons */
15     if (ienm == IHR) return false; /* Romulans cannot escape! */
16     if (!irun) {
17         /* avoid intruding on another commander's territory */
18         if (ienm == IHC) {
19             for_commanders(n)
20                 if (same(game.state.kcmdr[n],iq)) return false;
21             /* refuse to leave if currently attacking starbase */
22             if (same(game.battle, game.quadrant)) return false;
23         }
24         /* don't leave if over 1000 units of energy */
25         if (game.kpower[loccom] > 1000.) return false;
26     }
27     /* print escape message and move out of quadrant.
28        We know this if either short or long range sensors are working */
29     if (!damaged(DSRSENS) || !damaged(DLRSENS) ||
30         game.condition == docked) {
31         crmena(true, ienm, sector, game.ks[loccom]);
32         prout(_(" escapes to %s (and regains strength)."),
33               cramlc(quadrant, iq));
34     }
35     /* handle local matters related to escape */
36     game.quad[game.ks[loccom].x][game.ks[loccom].y] = IHDOT;
37     game.ks[loccom] = game.ks[game.nenhere];
38     game.kavgd[loccom] = game.kavgd[game.nenhere];
39     game.kpower[loccom] = game.kpower[game.nenhere];
40     game.kdist[loccom] = game.kdist[game.nenhere];
41     game.klhere--;
42     game.nenhere--;
43     if (game.condition != docked) newcnd();
44     /* Handle global matters related to escape */
45     game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons--;
46     game.state.galaxy[iq.x][iq.y].klingons++;
47     if (ienm==IHS) {
48         game.ishere = false;
49         game.iscate = false;
50         game.ientesc = false;
51         game.isatb = 0;
52         schedule(FSCMOVE, 0.2777);
53         unschedule(FSCDBAS);
54         game.state.kscmdr=iq;
55     }
56     else {
57         for_commanders(n) {
58             if (same(game.state.kcmdr[n], game.quadrant)) {
59                 game.state.kcmdr[n]=iq;
60                 break;
61             }
62         }
63         game.comhere = false;
64     }
65     return true; /* success */
66 }
67
68
69 static void movebaddy(coord com, int loccom, feature ienm)
70 /* tactical movement for the bad guys */
71 {
72     int motion, mdist, nsteps, mx, my, ll;
73     coord next, look;
74     int krawlx, krawly;
75     bool success, irun = false;
76     int attempts;
77     /* This should probably be just game.comhere + game.ishere */
78     int nbaddys = game.skill >= SKILL_EXPERT ?
79         (int)((game.comhere*2 + game.ishere*2+game.klhere*1.23+game.irhere*1.5)/2.0):
80         (game.comhere + game.ishere);
81     double dist1, forces;
82
83     dist1 = game.kdist[loccom];
84     mdist = dist1 + 0.5; /* Nearest integer distance */
85
86     /* If SC, check with spy to see if should hi-tail it */
87     if (ienm==IHS &&
88         (game.kpower[loccom] <= 500.0 || (game.condition==docked && !damaged(DPHOTON)))) {
89         irun = true;
90         motion = -QUADSIZE;
91     }
92     else {
93         /* decide whether to advance, retreat, or hold position */
94 /* Algorithm:
95  * Enterprise has "force" based on condition of phaser and photon torpedoes.
96  If both are operating full strength, force is 1000. If both are damaged,
97  force is -1000. Having shields down subtracts an additional 1000.
98
99  * Enemy has forces equal to the energy of the attacker plus
100  100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
101  346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
102
103  Attacker Initial energy levels (nominal):
104  Klingon   Romulan   Commander   Super-Commander
105  Novice    400        700        1200        
106  Fair      425        750        1250
107  Good      450        800        1300        1750
108  Expert    475        850        1350        1875
109  Emeritus  500        900        1400        2000
110  VARIANCE   75        200         200         200
111
112  Enemy vessels only move prior to their attack. In Novice - Good games
113  only commanders move. In Expert games, all enemy vessels move if there
114  is a commander present. In Emeritus games all enemy vessels move.
115
116  *  If Enterprise is not docked, an agressive action is taken if enemy
117  forces are 1000 greater than Enterprise.
118
119  Agressive action on average cuts the distance between the ship and
120  the enemy to 1/4 the original.
121
122  *  At lower energy advantage, movement units are proportional to the
123  advantage with a 650 advantage being to hold ground, 800 to move forward
124  1, 950 for two, 150 for back 4, etc. Variance of 100.
125
126  If docked, is reduced by roughly 1.75*game.skill, generally forcing a
127  retreat, especially at high skill levels.
128
129  *  Motion is limited to skill level, except for SC hi-tailing it out.
130  */
131
132         forces = game.kpower[loccom]+100.0*game.nenhere+400*(nbaddys-1);
133         if (!game.shldup) forces += 1000; /* Good for enemy if shield is down! */
134         if (!damaged(DPHASER) || !damaged(DPHOTON)) {
135             if (damaged(DPHASER)) /* phasers damaged */
136                 forces += 300.0;
137             else
138                 forces -= 0.2*(game.energy - 2500.0);
139             if (damaged(DPHOTON)) /* photon torpedoes damaged */
140                 forces += 300.0;
141             else
142                 forces -= 50.0*game.torps;
143         }
144         else {
145             /* phasers and photon tubes both out! */
146             forces += 1000.0;
147         }
148         motion = 0;
149         if (forces <= 1000.0 && game.condition != docked) /* Typical situation */
150             motion = ((forces+200.0*Rand())/150.0) - 5.0;
151         else {
152             if (forces > 1000.0) /* Very strong -- move in for kill */
153                 motion = (1.0-square(Rand()))*dist1 + 1.0;
154             if (game.condition==docked && (game.options & OPTION_BASE)) /* protected by base -- back off ! */
155                 motion -= game.skill*(2.0-square(Rand()));
156         }
157         if (idebug)
158             proutn("=== MOTION = %1.2f, FORCES = %1.2f, ", motion, forces);
159         /* don't move if no motion */
160         if (motion==0) return;
161         /* Limit motion according to skill */
162         if (abs(motion) > game.skill) motion = (motion < 0) ? -game.skill : game.skill;
163     }
164     /* calculate preferred number of steps */
165     nsteps = motion < 0 ? -motion : motion;
166     if (motion > 0 && nsteps > mdist) nsteps = mdist; /* don't overshoot */
167     if (nsteps > QUADSIZE) nsteps = QUADSIZE; /* This shouldn't be necessary */
168     if (nsteps < 1) nsteps = 1; /* This shouldn't be necessary */
169     if (idebug) {
170         proutn("NSTEPS = %d:", nsteps);
171     }
172     /* Compute preferred values of delta X and Y */
173     mx = game.sector.x - com.x;
174     my = game.sector.y - com.y;
175     if (2.0 * abs(mx) < abs(my)) mx = 0;
176     if (2.0 * abs(my) < abs(game.sector.x-com.x)) my = 0;
177     if (mx != 0) mx = mx*motion < 0 ? -1 : 1;
178     if (my != 0) my = my*motion < 0 ? -1 : 1;
179     next = com;
180     /* main move loop */
181     for (ll = 0; ll < nsteps; ll++) {
182         if (idebug)
183             proutn(" %d", ll+1);
184         /* Check if preferred position available */
185         look.x = next.x + mx;
186         look.y = next.y + my;
187         krawlx = mx < 0 ? 1 : -1;
188         krawly = my < 0 ? 1 : -1;
189         success = false;
190         attempts = 0; /* Settle mysterious hang problem */
191         while (attempts++ < 20 && !success) {
192             if (look.x < 1 || look.x > QUADSIZE) {
193                 if (motion < 0 && tryexit(look, ienm, loccom, irun))
194                     return;
195                 if (krawlx == mx || my == 0) break;
196                 look.x = next.x + krawlx;
197                 krawlx = -krawlx;
198             }
199             else if (look.y < 1 || look.y > QUADSIZE) {
200                 if (motion < 0 && tryexit(look, ienm, loccom, irun))
201                     return;
202                 if (krawly == my || mx == 0) break;
203                 look.y = next.y + krawly;
204                 krawly = -krawly;
205             }
206             else if ((game.options & OPTION_RAMMING) && game.quad[look.x][look.y] != IHDOT) {
207                 /* See if we should ram ship */
208                 if (game.quad[look.x][look.y] == game.ship &&
209                     (ienm == IHC || ienm == IHS)) {
210                     ram(true, ienm, com);
211                     return;
212                 }
213                 if (krawlx != mx && my != 0) {
214                     look.x = next.x + krawlx;
215                     krawlx = -krawlx;
216                 }
217                 else if (krawly != my && mx != 0) {
218                     look.y = next.y + krawly;
219                     krawly = -krawly;
220                 }
221                 else break; /* we have failed */
222             }
223             else success = true;
224         }
225         if (success) {
226             next = look;
227             if (idebug)
228                 proutn(cramlc(neither, next));
229         }
230         else break; /* done early */
231         
232     }
233     if (idebug)
234         prout("");
235     /* Put commander in place within same quadrant */
236     game.quad[com.x][com.y] = IHDOT;
237     game.quad[next.x][next.y] = ienm;
238     if (!same(next, com)) {
239         /* it moved */
240         game.ks[loccom] = next;
241         game.kdist[loccom] = game.kavgd[loccom] = distance(game.sector, next);
242         if (!damaged(DSRSENS) || game.condition == docked) {
243             proutn("***");
244             cramen(ienm);
245             proutn(_(" from %s"), cramlc(2, com));
246             if (game.kdist[loccom] < dist1) proutn(_(" advances to "));
247             else proutn(_(" retreats to "));
248             prout(cramlc(sector, next));
249         }
250     }
251 }
252
253 void movcom(void) 
254 /* move a commander */
255 {
256     coord w; 
257     int i;
258
259     if (idebug) prout("== MOVCOM");
260
261     /* Figure out which Klingon is the commander (or Supercommander)
262        and do move */
263     if (game.comhere) 
264         for_local_enemies(i) {
265             w = game.ks[i];
266             if (game.quad[w.x][w.y] == IHC) {
267                 movebaddy(w, i, IHC);
268                 break;
269             }
270         }
271     if (game.ishere) 
272         for_local_enemies(i) {
273             w = game.ks[i];
274             if (game.quad[w.x][w.y] == IHS) {
275                 movebaddy(w, i, IHS);
276                 break;
277             }
278         }
279     /* if skill level is high, move other Klingons and Romulans too!
280        Move these last so they can base their actions on what the
281        commander(s) do. */
282     if (game.skill >= SKILL_EXPERT && (game.options & OPTION_MVBADDY)) 
283         for_local_enemies(i) {
284             w = game.ks[i];
285             if (game.quad[w.x][w.y] == IHK || game.quad[w.x][w.y] == IHR)
286                 movebaddy(w, i, game.quad[w.x][w.y]);
287         }
288
289     sortkl();
290 }
291
292 static bool movescom(coord iq, bool flag, bool *ipage) 
293 /* commander movement helper */
294 {
295     int i;
296
297     if (same(iq, game.quadrant) || !VALID_QUADRANT(iq.x, iq.y) ||
298         game.state.galaxy[iq.x][iq.y].supernova ||
299         game.state.galaxy[iq.x][iq.y].klingons > 8) 
300         return 1;
301     if (flag) {
302         /* Avoid quadrants with bases if we want to avoid Enterprise */
303         for_starbases(i)
304             if (game.state.baseq[i].x==iq.x && game.state.baseq[i].y==iq.y) return 1;
305     }
306     if (game.justin && !game.iscate) return true;
307     /* do the move */
308     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons--;
309     game.state.kscmdr = iq;
310     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons++;
311     if (game.ishere) {
312         /* SC has scooted, Remove him from current quadrant */
313         game.iscate=false;
314         game.isatb=0;
315         game.ishere = false;
316         game.ientesc = false;
317         unschedule(FSCDBAS);
318         for_local_enemies(i) 
319             if (game.quad[game.ks[i].x][game.ks[i].y] == IHS) break;
320         game.quad[game.ks[i].x][game.ks[i].y] = IHDOT;
321         game.ks[i] = game.ks[game.nenhere];
322         game.kdist[i] = game.kdist[game.nenhere];
323         game.kavgd[i] = game.kavgd[game.nenhere];
324         game.kpower[i] = game.kpower[game.nenhere];
325         game.klhere--;
326         game.nenhere--;
327         if (game.condition!=docked) newcnd();
328         sortkl();
329     }
330     /* check for a helpful planet */
331     for (i = 0; i < game.inplan; i++) {
332         if (game.state.plnets[i].w.x==game.state.kscmdr.x && game.state.plnets[i].w.y==game.state.kscmdr.y &&
333             game.state.plnets[i].crystals == 1) {
334             /* destroy the planet */
335             DESTROY(&game.state.plnets[i]);
336             game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].planet = NOPLANET;
337             if (!damaged(DRADIO) || game.condition == docked) {
338                 if (!*ipage) pause_game(true);
339                 *ipage = true;
340                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
341                 proutn(_("   a planet in "));
342                 proutn(cramlc(quadrant, game.state.kscmdr));
343                 prout(_(" has been destroyed"));
344                 prout(_("   by the Super-commander.\""));
345             }
346             break;
347         }
348     }
349     return false; /* looks good! */
350 }
351                         
352 void scom(bool *ipage)
353 /* move the Super Commander */
354 {
355     int i, i2, j, ideltax, ideltay, ifindit, iwhichb;
356     coord iq, sc, ibq;
357     int basetbl[BASEMAX+1];
358     double bdist[BASEMAX+1];
359     bool flag;
360
361     if (idebug) prout("== SCOM");
362
363     /* Decide on being active or passive */
364     flag = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) ||
365             (game.state.date-game.indate) < 3.0);
366     if (!game.iscate && flag) {
367         /* compute move away from Enterprise */
368         ideltax = game.state.kscmdr.x-game.quadrant.x;
369         ideltay = game.state.kscmdr.y-game.quadrant.y;
370         if (sqrt(ideltax*(double)ideltax+ideltay*(double)ideltay) > 2.0) {
371             /* circulate in space */
372             ideltax = game.state.kscmdr.y-game.quadrant.y;
373             ideltay = game.quadrant.x-game.state.kscmdr.x;
374         }
375     }
376     else {
377         /* compute distances to starbases */
378         if (game.state.rembase <= 0) {
379             /* nothing left to do */
380             unschedule(FSCMOVE);
381             return;
382         }
383         sc = game.state.kscmdr;
384         for_starbases(i) {
385             basetbl[i] = i;
386             bdist[i] = distance(game.state.baseq[i], sc);
387         }
388         if (game.state.rembase > 1) {
389             /* sort into nearest first order */
390             bool iswitch;
391             do {
392                 iswitch = false;
393                 for (i=1; i < game.state.rembase-1; i++) {
394                     if (bdist[i] > bdist[i+1]) {
395                         int ti = basetbl[i];
396                         double t = bdist[i];
397                         bdist[i] = bdist[i+1];
398                         bdist[i+1] = t;
399                         basetbl[i] = basetbl[i+1];
400                         basetbl[i+1] =ti;
401                         iswitch = true;
402                     }
403                 }
404             } while (iswitch);
405         }
406         /* look for nearest base without a commander, no Enterprise, and
407            without too many Klingons, and not already under attack. */
408         ifindit = iwhichb = 0;
409
410         for_starbases(i2) {
411             i = basetbl[i2];    /* bug in original had it not finding nearest*/
412             ibq = game.state.baseq[i];
413             if (same(ibq, game.quadrant) || same(ibq, game.battle) ||
414                 game.state.galaxy[ibq.x][ibq.y].supernova ||
415                 game.state.galaxy[ibq.x][ibq.y].klingons > 8) 
416                 continue;
417             /* if there is a commander, an no other base is appropriate,
418                we will take the one with the commander */
419             for_commanders (j) {
420                 if (same(ibq, game.state.kcmdr[j]) && ifindit!= 2) {
421                     ifindit = 2;
422                     iwhichb = i;
423                     break;
424                 }
425             }
426             if (j > game.state.remcom) { /* no commander -- use this one */
427                 ifindit = 1;
428                 iwhichb = i;
429                 break;
430             }
431         }
432         if (ifindit==0) return; /* Nothing suitable -- wait until next time*/
433         ibq = game.state.baseq[iwhichb];
434         /* decide how to move toward base */
435         ideltax = ibq.x - game.state.kscmdr.x;
436         ideltay = ibq.y - game.state.kscmdr.y;
437     }
438     /* Maximum movement is 1 quadrant in either or both axis */
439     if (ideltax > 1) ideltax = 1;
440     if (ideltax < -1) ideltax = -1;
441     if (ideltay > 1) ideltay = 1;
442     if (ideltay < -1) ideltay = -1;
443
444     /* try moving in both x and y directions */
445     iq.x = game.state.kscmdr.x + ideltax;
446     iq.y = game.state.kscmdr.y + ideltax;
447     if (movescom(iq, flag, ipage)) {
448         /* failed -- try some other maneuvers */
449         if (ideltax==0 || ideltay==0) {
450             /* attempt angle move */
451             if (ideltax != 0) {
452                 iq.y = game.state.kscmdr.y + 1;
453                 if (movescom(iq, flag, ipage)) {
454                     iq.y = game.state.kscmdr.y - 1;
455                     movescom(iq, flag, ipage);
456                 }
457             }
458             else {
459                 iq.x = game.state.kscmdr.x + 1;
460                 if (movescom(iq, flag, ipage)) {
461                     iq.x = game.state.kscmdr.x - 1;
462                     movescom(iq, flag, ipage);
463                 }
464             }
465         }
466         else {
467             /* try moving just in x or y */
468             iq.y = game.state.kscmdr.y;
469             if (movescom(iq, flag, ipage)) {
470                 iq.y = game.state.kscmdr.y + ideltay;
471                 iq.x = game.state.kscmdr.x;
472                 movescom(iq, flag, ipage);
473             }
474         }
475     }
476     /* check for a base */
477     if (game.state.rembase == 0) {
478         unschedule(FSCMOVE);
479     }
480     else for_starbases(i) {
481         ibq = game.state.baseq[i];
482         if (same(ibq, game.state.kscmdr) && same(game.state.kscmdr, game.battle)) {
483             /* attack the base */
484             if (flag) return; /* no, don't attack base! */
485             game.iseenit = false;
486             game.isatb = 1;
487             schedule(FSCDBAS, 1.0 +2.0*Rand());
488             if (is_scheduled(FCDBAS)) 
489                 postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date);
490             if (damaged(DRADIO) && game.condition != docked)
491                 return; /* no warning */
492             game.iseenit = true;
493             if (!*ipage)  pause_game(true);
494             *ipage = true;
495             proutn(_("Lt. Uhura-  \"Captain, the starbase in "));
496             proutn(cramlc(quadrant, game.state.kscmdr));
497             skip(1);
498             prout(_("   reports that it is under attack from the Klingon Super-commander."));
499             proutn(_("   It can survive until stardate %d.\""),
500                    (int)scheduled(FSCDBAS));
501             if (!game.resting) return;
502             prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""));
503             if (ja() == false) return;
504             game.resting = false;
505             game.optime = 0.0; /* actually finished */
506             return;
507         }
508     }
509     /* Check for intelligence report */
510     if (
511         !idebug &&
512         (Rand() > 0.2 ||
513          (damaged(DRADIO) && game.condition != docked) ||
514          !game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].charted))
515         return;
516     if (!*ipage) pause_game(true);
517     *ipage = true;
518     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
519     proutn(_("   the Super-commander is in "));
520     proutn(cramlc(quadrant, game.state.kscmdr));
521     prout(".\"");
522     return;
523 }
524
525 void movetho(void)
526 /* move the Tholian */
527 {
528     int idx, idy, im, i;
529     /* Move the Tholian */
530     if (!game.ithere || game.justin) return;
531
532     if (game.tholian.x == 1 && game.tholian.y == 1) {
533         idx = 1; idy = QUADSIZE;
534     }
535     else if (game.tholian.x == 1 && game.tholian.y == QUADSIZE) {
536         idx = QUADSIZE; idy = QUADSIZE;
537     }
538     else if (game.tholian.x == QUADSIZE && game.tholian.y == QUADSIZE) {
539         idx = QUADSIZE; idy = 1;
540     }
541     else if (game.tholian.x == QUADSIZE && game.tholian.y == 1) {
542         idx = 1; idy = 1;
543     }
544     else {
545         /* something is wrong! */
546         game.ithere = false;
547         return;
548     }
549
550     /* Do nothing if we are blocked */
551     if (game.quad[idx][idy]!= IHDOT && game.quad[idx][idy]!= IHWEB) return;
552     game.quad[game.tholian.x][game.tholian.y] = IHWEB;
553
554     if (game.tholian.x != idx) {
555         /* move in x axis */
556         im = fabs((double)idx - game.tholian.x)/((double)idx - game.tholian.x);
557         while (game.tholian.x != idx) {
558             game.tholian.x += im;
559             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
560         }
561     }
562     else if (game.tholian.y != idy) {
563         /* move in y axis */
564         im = fabs((double)idy - game.tholian.y)/((double)idy - game.tholian.y);
565         while (game.tholian.y != idy) {
566             game.tholian.y += im;
567             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
568         }
569     }
570     game.quad[game.tholian.x][game.tholian.y] = IHT;
571     game.ks[game.nenhere] = game.tholian;
572
573     /* check to see if all holes plugged */
574     for_sectors(i) {
575         if (game.quad[1][i]!=IHWEB && game.quad[1][i]!=IHT) return;
576         if (game.quad[QUADSIZE][i]!=IHWEB && game.quad[QUADSIZE][i]!=IHT) return;
577         if (game.quad[i][1]!=IHWEB && game.quad[i][1]!=IHT) return;
578         if (game.quad[i][QUADSIZE]!=IHWEB && game.quad[i][QUADSIZE]!=IHT) return;
579     }
580     /* All plugged up -- Tholian splits */
581     game.quad[game.tholian.x][game.tholian.y]=IHWEB;
582     dropin(IHBLANK);
583     crmena(true, IHT, sector, game.tholian);
584     prout(_(" completes web."));
585     game.ithere = false;
586     game.nenhere--;
587     return;
588 }