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