Another rollup patch.
[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         skip(1);
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 (same(game.state.baseq[i], iq)) 
305                 return true;
306     }
307     if (game.justin && !game.iscate) return true;
308     /* do the move */
309     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons--;
310     game.state.kscmdr = iq;
311     game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].klingons++;
312     if (game.ishere) {
313         /* SC has scooted, Remove him from current quadrant */
314         game.iscate=false;
315         game.isatb=0;
316         game.ishere = false;
317         game.ientesc = false;
318         unschedule(FSCDBAS);
319         for_local_enemies(i) 
320             if (game.quad[game.ks[i].x][game.ks[i].y] == IHS) break;
321         game.quad[game.ks[i].x][game.ks[i].y] = IHDOT;
322         game.ks[i] = game.ks[game.nenhere];
323         game.kdist[i] = game.kdist[game.nenhere];
324         game.kavgd[i] = game.kavgd[game.nenhere];
325         game.kpower[i] = game.kpower[game.nenhere];
326         game.klhere--;
327         game.nenhere--;
328         if (game.condition!=docked) newcnd();
329         sortkl();
330     }
331     /* check for a helpful planet */
332     for (i = 0; i < game.inplan; i++) {
333         if (same(game.state.plnets[i].w, game.state.kscmdr) &&
334             game.state.plnets[i].crystals == present) {
335             /* destroy the planet */
336             DESTROY(&game.state.plnets[i]);
337             game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].planet = NOPLANET;
338             if (!damaged(DRADIO) || game.condition == docked) {
339                 if (!*ipage) pause_game(true);
340                 *ipage = true;
341                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
342                 proutn(_("   a planet in "));
343                 proutn(cramlc(quadrant, game.state.kscmdr));
344                 prout(_(" has been destroyed"));
345                 prout(_("   by the Super-commander.\""));
346             }
347             break;
348         }
349     }
350     return false; /* looks good! */
351 }
352                         
353 void scom(bool *ipage)
354 /* move the Super Commander */
355 {
356     int i, i2, j, ideltax, ideltay, ifindit, iwhichb;
357     coord iq, sc, ibq;
358     int basetbl[BASEMAX+1];
359     double bdist[BASEMAX+1];
360     bool flag;
361
362     if (idebug) prout("== SCOM");
363
364     /* Decide on being active or passive */
365     flag = ((NKILLC+NKILLK)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) ||
366             (game.state.date-game.indate) < 3.0);
367     if (!game.iscate && flag) {
368         /* compute move away from Enterprise */
369         ideltax = game.state.kscmdr.x-game.quadrant.x;
370         ideltay = game.state.kscmdr.y-game.quadrant.y;
371         if (sqrt(ideltax*(double)ideltax+ideltay*(double)ideltay) > 2.0) {
372             /* circulate in space */
373             ideltax = game.state.kscmdr.y-game.quadrant.y;
374             ideltay = game.quadrant.x-game.state.kscmdr.x;
375         }
376     }
377     else {
378         /* compute distances to starbases */
379         if (game.state.rembase <= 0) {
380             /* nothing left to do */
381             unschedule(FSCMOVE);
382             return;
383         }
384         sc = game.state.kscmdr;
385         for_starbases(i) {
386             basetbl[i] = i;
387             bdist[i] = distance(game.state.baseq[i], sc);
388         }
389         if (game.state.rembase > 1) {
390             /* sort into nearest first order */
391             bool iswitch;
392             do {
393                 iswitch = false;
394                 for (i=1; i < game.state.rembase-1; i++) {
395                     if (bdist[i] > bdist[i+1]) {
396                         int ti = basetbl[i];
397                         double t = bdist[i];
398                         bdist[i] = bdist[i+1];
399                         bdist[i+1] = t;
400                         basetbl[i] = basetbl[i+1];
401                         basetbl[i+1] =ti;
402                         iswitch = true;
403                     }
404                 }
405             } while (iswitch);
406         }
407         /* look for nearest base without a commander, no Enterprise, and
408            without too many Klingons, and not already under attack. */
409         ifindit = iwhichb = 0;
410
411         for_starbases(i2) {
412             i = basetbl[i2];    /* bug in original had it not finding nearest*/
413             ibq = game.state.baseq[i];
414             if (same(ibq, game.quadrant) || same(ibq, game.battle) ||
415                 game.state.galaxy[ibq.x][ibq.y].supernova ||
416                 game.state.galaxy[ibq.x][ibq.y].klingons > 8) 
417                 continue;
418             /* if there is a commander, an no other base is appropriate,
419                we will take the one with the commander */
420             for_commanders (j) {
421                 if (same(ibq, game.state.kcmdr[j]) && ifindit!= 2) {
422                     ifindit = 2;
423                     iwhichb = i;
424                     break;
425                 }
426             }
427             if (j > game.state.remcom) { /* no commander -- use this one */
428                 ifindit = 1;
429                 iwhichb = i;
430                 break;
431             }
432         }
433         if (ifindit==0) return; /* Nothing suitable -- wait until next time*/
434         ibq = game.state.baseq[iwhichb];
435         /* decide how to move toward base */
436         ideltax = ibq.x - game.state.kscmdr.x;
437         ideltay = ibq.y - game.state.kscmdr.y;
438     }
439     /* Maximum movement is 1 quadrant in either or both axis */
440     if (ideltax > 1) ideltax = 1;
441     if (ideltax < -1) ideltax = -1;
442     if (ideltay > 1) ideltay = 1;
443     if (ideltay < -1) ideltay = -1;
444
445     /* try moving in both x and y directions */
446     iq.x = game.state.kscmdr.x + ideltax;
447     iq.y = game.state.kscmdr.y + ideltax;
448     if (movescom(iq, flag, ipage)) {
449         /* failed -- try some other maneuvers */
450         if (ideltax==0 || ideltay==0) {
451             /* attempt angle move */
452             if (ideltax != 0) {
453                 iq.y = game.state.kscmdr.y + 1;
454                 if (movescom(iq, flag, ipage)) {
455                     iq.y = game.state.kscmdr.y - 1;
456                     movescom(iq, flag, ipage);
457                 }
458             }
459             else {
460                 iq.x = game.state.kscmdr.x + 1;
461                 if (movescom(iq, flag, ipage)) {
462                     iq.x = game.state.kscmdr.x - 1;
463                     movescom(iq, flag, ipage);
464                 }
465             }
466         }
467         else {
468             /* try moving just in x or y */
469             iq.y = game.state.kscmdr.y;
470             if (movescom(iq, flag, ipage)) {
471                 iq.y = game.state.kscmdr.y + ideltay;
472                 iq.x = game.state.kscmdr.x;
473                 movescom(iq, flag, ipage);
474             }
475         }
476     }
477     /* check for a base */
478     if (game.state.rembase == 0) {
479         unschedule(FSCMOVE);
480     }
481     else for_starbases(i) {
482         ibq = game.state.baseq[i];
483         if (same(ibq, game.state.kscmdr) && same(game.state.kscmdr, game.battle)) {
484             /* attack the base */
485             if (flag) return; /* no, don't attack base! */
486             game.iseenit = false;
487             game.isatb = 1;
488             schedule(FSCDBAS, 1.0 +2.0*Rand());
489             if (is_scheduled(FCDBAS)) 
490                 postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date);
491             if (damaged(DRADIO) && game.condition != docked)
492                 return; /* no warning */
493             game.iseenit = true;
494             if (!*ipage)  pause_game(true);
495             *ipage = true;
496             proutn(_("Lt. Uhura-  \"Captain, the starbase in "));
497             proutn(cramlc(quadrant, game.state.kscmdr));
498             skip(1);
499             prout(_("   reports that it is under attack from the Klingon Super-commander."));
500             proutn(_("   It can survive until stardate %d.\""),
501                    (int)scheduled(FSCDBAS));
502             if (!game.resting) return;
503             prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""));
504             if (ja() == false) return;
505             game.resting = false;
506             game.optime = 0.0; /* actually finished */
507             return;
508         }
509     }
510     /* Check for intelligence report */
511     if (
512         !idebug &&
513         (Rand() > 0.2 ||
514          (damaged(DRADIO) && game.condition != docked) ||
515          !game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].charted))
516         return;
517     if (!*ipage) pause_game(true);
518     *ipage = true;
519     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
520     proutn(_("   the Super-commander is in "));
521     proutn(cramlc(quadrant, game.state.kscmdr));
522     prout(".\"");
523     return;
524 }
525
526 void movetho(void)
527 /* move the Tholian */
528 {
529     int idx, idy, im, i;
530     /* Move the Tholian */
531     if (!game.ithere || game.justin) return;
532
533     if (game.tholian.x == 1 && game.tholian.y == 1) {
534         idx = 1; idy = QUADSIZE;
535     }
536     else if (game.tholian.x == 1 && game.tholian.y == QUADSIZE) {
537         idx = QUADSIZE; idy = QUADSIZE;
538     }
539     else if (game.tholian.x == QUADSIZE && game.tholian.y == QUADSIZE) {
540         idx = QUADSIZE; idy = 1;
541     }
542     else if (game.tholian.x == QUADSIZE && game.tholian.y == 1) {
543         idx = 1; idy = 1;
544     }
545     else {
546         /* something is wrong! */
547         game.ithere = false;
548         return;
549     }
550
551     /* Do nothing if we are blocked */
552     if (game.quad[idx][idy]!= IHDOT && game.quad[idx][idy]!= IHWEB) return;
553     game.quad[game.tholian.x][game.tholian.y] = IHWEB;
554
555     if (game.tholian.x != idx) {
556         /* move in x axis */
557         im = fabs((double)idx - game.tholian.x)/((double)idx - game.tholian.x);
558         while (game.tholian.x != idx) {
559             game.tholian.x += im;
560             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
561         }
562     }
563     else if (game.tholian.y != idy) {
564         /* move in y axis */
565         im = fabs((double)idy - game.tholian.y)/((double)idy - game.tholian.y);
566         while (game.tholian.y != idy) {
567             game.tholian.y += im;
568             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
569         }
570     }
571     game.quad[game.tholian.x][game.tholian.y] = IHT;
572     game.ks[game.nenhere] = game.tholian;
573
574     /* check to see if all holes plugged */
575     for_sectors(i) {
576         if (game.quad[1][i]!=IHWEB && game.quad[1][i]!=IHT) return;
577         if (game.quad[QUADSIZE][i]!=IHWEB && game.quad[QUADSIZE][i]!=IHT) return;
578         if (game.quad[i][1]!=IHWEB && game.quad[i][1]!=IHT) return;
579         if (game.quad[i][QUADSIZE]!=IHWEB && game.quad[i][QUADSIZE]!=IHT) return;
580     }
581     /* All plugged up -- Tholian splits */
582     game.quad[game.tholian.x][game.tholian.y]=IHWEB;
583     dropin(IHBLANK);
584     crmena(true, IHT, sector, game.tholian);
585     prout(_(" completes web."));
586     game.ithere = false;
587     game.nenhere--;
588     return;
589 }