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