685fe69947d77f8547c34450966ed4dfe6929480
[super-star-trek.git] / src / ai.c
1 #include "sst.h"
2
3 static int tryexit(int lookx, int looky, int ienm, int loccom, int irun) 
4 {
5     int l;
6     coord iq;
7
8     iq.x = game.quadrant.x+(lookx+(QUADSIZE-1))/QUADSIZE - 1;
9     iq.y = game.quadrant.y+(looky+(QUADSIZE-1))/QUADSIZE - 1;
10     if (!VALID_QUADRANT(iq.x,iq.y) ||
11         game.state.galaxy[iq.x][iq.y].supernova ||
12         game.state.galaxy[iq.x][iq.y].klingons > 8)
13         return 0; /* no can do -- neg energy, supernovae, or >8 Klingons */
14     if (ienm == IHR) return 0; /* Romulans cannot escape! */
15     if (irun == 0) {
16         /* avoid intruding on another commander's territory */
17         if (ienm == IHC) {
18             for_commanders(l)
19                 if (same(game.state.kcmdr[l],iq)) return 0;
20             /* refuse to leave if currently attacking starbase */
21             if (same(game.battle, game.quadrant)) return 0;
22         }
23         /* don't leave if over 1000 units of energy */
24         if (game.kpower[loccom] > 1000.) return 0;
25     }
26     /* print escape message and move out of quadrant.
27        We know this if either short or long range sensors are working */
28     if (game.damage[DSRSENS] == 0.0 || game.damage[DLRSENS] == 0.0 ||
29         game.condit == IHDOCKED) {
30         crmena(1, ienm, 2, game.ks[loccom]);
31         prout(_(" escapes to %s (and regains strength)."),
32               cramlc(quadrant, iq));
33     }
34     /* handle local matters related to escape */
35     game.quad[game.ks[loccom].x][game.ks[loccom].y] = IHDOT;
36     game.ks[loccom] = game.ks[game.nenhere];
37     game.kavgd[loccom] = game.kavgd[game.nenhere];
38     game.kpower[loccom] = game.kpower[game.nenhere];
39     game.kdist[loccom] = game.kdist[game.nenhere];
40     game.klhere--;
41     game.nenhere--;
42     if (game.condit != IHDOCKED) newcnd();
43     /* Handle global matters related to escape */
44     game.state.galaxy[game.quadrant.x][game.quadrant.y].klingons--;
45     game.state.galaxy[iq.x][iq.y].klingons++;
46     if (ienm==IHS) {
47         game.ishere=0;
48         game.iscate=0;
49         game.ientesc=0;
50         game.isatb=0;
51         schedule(FSCMOVE, 0.2777);
52         unschedule(FSCDBAS);
53         game.state.kscmdr.x=iq.x;
54         game.state.kscmdr.y=iq.y;
55     }
56     else {
57         for_commanders(l) {
58             if (same(game.state.kcmdr[l], game.quadrant)) {
59                 game.state.kcmdr[l]=iq;
60                 break;
61             }
62         }
63         game.comhere = 0;
64     }
65     return 1; /* success */
66 }
67
68
69 static void movebaddy(coord com, int loccom, int ienm) 
70 {
71     int motion, mdist, nsteps, mx, my, lookx, looky, ll;
72     coord next;
73     int irun = 0;
74     int krawlx, krawly;
75     int success;
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.condit==IHDOCKED && game.damage[DPHOTON]==0))) {
89         irun = 1;
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==0) forces += 1000; /* Good for enemy if shield is down! */
134         if (game.damage[DPHASER] == 0.0 || game.damage[DPHOTON] == 0.0) {
135             if (game.damage[DPHASER] != 0) /* phasers damaged */
136                 forces += 300.0;
137             else
138                 forces -= 0.2*(game.energy - 2500.0);
139             if (game.damage[DPHOTON] != 0) /* 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.condit != IHDOCKED) /* 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.condit==IHDOCKED && (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         lookx = next.x + mx;
186         looky = next.y + my;
187         krawlx = mx < 0 ? 1 : -1;
188         krawly = my < 0 ? 1 : -1;
189         success = 0;
190         attempts = 0; /* Settle mysterious hang problem */
191         while (attempts++ < 20 && !success) {
192             if (lookx < 1 || lookx > QUADSIZE) {
193                 if (motion < 0 && tryexit(lookx, looky, ienm, loccom, irun))
194                     return;
195                 if (krawlx == mx || my == 0) break;
196                 lookx = next.x + krawlx;
197                 krawlx = -krawlx;
198             }
199             else if (looky < 1 || looky > QUADSIZE) {
200                 if (motion < 0 && tryexit(lookx, looky, ienm, loccom, irun))
201                     return;
202                 if (krawly == my || mx == 0) break;
203                 looky = next.y + krawly;
204                 krawly = -krawly;
205             }
206             else if ((game.options & OPTION_RAMMING) && game.quad[lookx][looky] != IHDOT) {
207                 /* See if we should ram ship */
208                 if (game.quad[lookx][looky] == game.ship &&
209                     (ienm == IHC || ienm == IHS)) {
210                     ram(1, ienm, com);
211                     return;
212                 }
213                 if (krawlx != mx && my != 0) {
214                     lookx = next.x + krawlx;
215                     krawlx = -krawlx;
216                 }
217                 else if (krawly != my && mx != 0) {
218                     looky = next.y + krawly;
219                     krawly = -krawly;
220                 }
221                 else break; /* we have failed */
222             }
223             else success = 1;
224         }
225         if (success) {
226             next.x = lookx;
227             next.y = looky;
228             if (idebug)
229                 proutn(cramlc(neither, next));
230         }
231         else break; /* done early */
232         
233     }
234     if (idebug)
235         prout("");
236     /* Put commander in place within same quadrant */
237     game.quad[com.x][com.y] = IHDOT;
238     game.quad[next.x][next.y] = ienm;
239     if (next.x != com.x || next.y != com.y) {
240         /* it moved */
241         game.ks[loccom].x = next.x;
242         game.ks[loccom].y = next.y;
243         game.kdist[loccom] = game.kavgd[loccom] =
244             sqrt(square(game.sector.x-next.x)+square(game.sector.y-next.y));
245         if (game.damage[DSRSENS] == 0 || game.condit == IHDOCKED) {
246             proutn("***");
247             cramen(ienm);
248             proutn(_(" from %s"), cramlc(2, com));
249             if (game.kdist[loccom] < dist1) proutn(_(" advances to "));
250             else proutn(_(" retreats to "));
251             prout(cramlc(sector, next));
252         }
253     }
254 }
255
256 void movcom(void) 
257 {
258     coord w; 
259     int i;
260
261     if (idebug) prout("== MOVCOM");
262
263     /* Figure out which Klingon is the commander (or Supercommander)
264        and do move */
265     if (game.comhere) 
266         for_local_enemies(i) {
267             w = game.ks[i];
268             if (game.quad[w.x][w.y] == IHC) {
269                 movebaddy(w, i, IHC);
270                 break;
271             }
272         }
273     if (game.ishere) 
274         for_local_enemies(i) {
275             w = game.ks[i];
276             if (game.quad[w.x][w.y] == IHS) {
277                 movebaddy(w, i, IHS);
278                 break;
279             }
280         }
281     /* if skill level is high, move other Klingons and Romulans too!
282        Move these last so they can base their actions on what the
283        commander(s) do. */
284     if (game.skill >= SKILL_EXPERT && (game.options & OPTION_MVBADDY)) 
285         for_local_enemies(i) {
286             w = game.ks[i];
287             if (game.quad[w.x][w.y] == IHK || game.quad[w.x][w.y] == IHR)
288                 movebaddy(w, i, game.quad[w.x][w.y]);
289         }
290
291     sortkl();
292 }
293
294 static bool movescom(coord iq, int flag, int *ipage) 
295 {
296     int i;
297
298     if (same(iq, game.quadrant) || !VALID_QUADRANT(iq.x, iq.y) ||
299         game.state.galaxy[iq.x][iq.y].supernova ||
300         game.state.galaxy[iq.x][iq.y].klingons > 8) 
301         return 1;
302     if (flag) {
303         /* Avoid quadrants with bases if we want to avoid Enterprise */
304         for_starbases(i)
305             if (game.state.baseq[i].x==iq.x && game.state.baseq[i].y==iq.y) return 1;
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=0;
315         game.isatb=0;
316         game.ishere=0;
317         game.ientesc=0;
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.condit!=IHDOCKED) newcnd();
329         sortkl();
330     }
331     /* check for a helpful planet */
332     for (i = 0; i < game.inplan; i++) {
333         if (game.state.plnets[i].w.x==game.state.kscmdr.x && game.state.plnets[i].w.y==game.state.kscmdr.y &&
334             game.state.plnets[i].crystals == 1) {
335             /* destroy the planet */
336             DESTROY(&game.state.plnets[i]);
337             game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].planet = NULL;
338             if (game.damage[DRADIO] == 0.0 || game.condit == IHDOCKED) {
339                 if (*ipage==0) pause_game(1);
340                 *ipage = 1;
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(int *ipage)
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     int 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==0 && 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             ibq.x = game.state.baseq[i].x;
387             ibq.y = game.state.baseq[i].y;
388             bdist[i] = sqrt(square(ibq.x-sc.x) + square(ibq.y-sc.y));
389         }
390         if (game.state.rembase > 1) {
391             /* sort into nearest first order */
392             int iswitch;
393             do {
394                 iswitch = 0;
395                 for (i=1; i < game.state.rembase-1; i++) {
396                     if (bdist[i] > bdist[i+1]) {
397                         int ti = basetbl[i];
398                         double t = bdist[i];
399                         bdist[i] = bdist[i+1];
400                         bdist[i+1] = t;
401                         basetbl[i] = basetbl[i+1];
402                         basetbl[i+1] =ti;
403                         iswitch = 1;
404                     }
405                 }
406             } while (iswitch);
407         }
408         /* look for nearest base without a commander, no Enterprise, and
409            without too many Klingons, and not already under attack. */
410         ifindit = iwhichb = 0;
411
412         for_starbases(i2) {
413             i = basetbl[i2];    /* bug in original had it not finding nearest*/
414             ibq = game.state.baseq[i];
415             if (same(ibq, game.quadrant) || same(ibq, game.battle) ||
416                 game.state.galaxy[ibq.x][ibq.y].supernova ||
417                 game.state.galaxy[ibq.x][ibq.y].klingons > 8) 
418                 continue;
419             /* if there is a commander, an no other base is appropriate,
420                we will take the one with the commander */
421             for_commanders (j) {
422                 if (ibq.x==game.state.kcmdr[j].x && ibq.y==game.state.kcmdr[j].y && ifindit!= 2) {
423                     ifindit = 2;
424                     iwhichb = i;
425                     break;
426                 }
427             }
428             if (j > game.state.remcom) { /* no commander -- use this one */
429                 ifindit = 1;
430                 iwhichb = i;
431                 break;
432             }
433         }
434         if (ifindit==0) return; /* Nothing suitable -- wait until next time*/
435         ibq = game.state.baseq[iwhichb];
436         /* decide how to move toward base */
437         ideltax = ibq.x - game.state.kscmdr.x;
438         ideltay = ibq.y - game.state.kscmdr.y;
439     }
440     /* Maximum movement is 1 quadrant in either or both axis */
441     if (ideltax > 1) ideltax = 1;
442     if (ideltax < -1) ideltax = -1;
443     if (ideltay > 1) ideltay = 1;
444     if (ideltay < -1) ideltay = -1;
445
446     /* try moving in both x and y directions */
447     iq.x = game.state.kscmdr.x + ideltax;
448     iq.y = game.state.kscmdr.y + ideltax;
449     if (movescom(iq, flag, ipage)) {
450         /* failed -- try some other maneuvers */
451         if (ideltax==0 || ideltay==0) {
452             /* attempt angle move */
453             if (ideltax != 0) {
454                 iq.y = game.state.kscmdr.y + 1;
455                 if (movescom(iq, flag, ipage)) {
456                     iq.y = game.state.kscmdr.y - 1;
457                     movescom(iq, flag, ipage);
458                 }
459             }
460             else {
461                 iq.x = game.state.kscmdr.x + 1;
462                 if (movescom(iq, flag, ipage)) {
463                     iq.x = game.state.kscmdr.x - 1;
464                     movescom(iq, flag, ipage);
465                 }
466             }
467         }
468         else {
469             /* try moving just in x or y */
470             iq.y = game.state.kscmdr.y;
471             if (movescom(iq, flag, ipage)) {
472                 iq.y = game.state.kscmdr.y + ideltay;
473                 iq.x = game.state.kscmdr.x;
474                 movescom(iq, flag, ipage);
475             }
476         }
477     }
478     /* check for a base */
479     if (game.state.rembase == 0) {
480         unschedule(FSCMOVE);
481     }
482     else for_starbases(i) {
483         ibq = game.state.baseq[i];
484         if (same(ibq, game.state.kscmdr) && same(game.state.kscmdr, game.battle)) {
485             /* attack the base */
486             if (flag) return; /* no, don't attack base! */
487             game.iseenit = 0;
488             game.isatb = 1;
489             schedule(FSCDBAS, 1.0 +2.0*Rand());
490             if (is_scheduled(FCDBAS)) 
491                 postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date);
492             if (game.damage[DRADIO] > 0 && game.condit != IHDOCKED)
493                 return; /* no warning */
494             game.iseenit = 1;
495             if (*ipage == 0)  pause_game(1);
496             *ipage=1;
497             proutn(_("Lt. Uhura-  \"Captain, the starbase in "));
498             proutn(cramlc(quadrant, game.state.kscmdr));
499             skip(1);
500             prout(_("   reports that it is under attack from the Klingon Super-commander."));
501             proutn(_("   It can survive until stardate %d.\""),
502                    (int)scheduled(FSCDBAS));
503             if (game.resting==0) return;
504             prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""));
505             if (ja()==0) return;
506             game.resting = 0;
507             game.optime = 0.0; /* actually finished */
508             return;
509         }
510     }
511     /* Check for intelligence report */
512     if (
513         !idebug &&
514         (Rand() > 0.2 ||
515          (game.damage[DRADIO] > 0.0 && game.condit != IHDOCKED) ||
516          !game.state.galaxy[game.state.kscmdr.x][game.state.kscmdr.y].charted))
517         return;
518     if (*ipage==0) pause_game(1);
519     *ipage = 1;
520     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"));
521     proutn(_("   the Super-commander is in "));
522     proutn(cramlc(quadrant, game.state.kscmdr));
523     prout(".\"");
524     return;
525 }
526
527 void movetho(void)
528 {
529     int idx, idy, im, i;
530     coord dummy;
531     /* Move the Tholian */
532     if (game.ithere==0 || game.justin == 1) return;
533
534     if (game.tholian.x == 1 && game.tholian.y == 1) {
535         idx = 1; idy = QUADSIZE;
536     }
537     else if (game.tholian.x == 1 && game.tholian.y == QUADSIZE) {
538         idx = QUADSIZE; idy = QUADSIZE;
539     }
540     else if (game.tholian.x == QUADSIZE && game.tholian.y == QUADSIZE) {
541         idx = QUADSIZE; idy = 1;
542     }
543     else if (game.tholian.x == QUADSIZE && game.tholian.y == 1) {
544         idx = 1; idy = 1;
545     }
546     else {
547         /* something is wrong! */
548         game.ithere = 0;
549         return;
550     }
551
552     /* Do nothing if we are blocked */
553     if (game.quad[idx][idy]!= IHDOT && game.quad[idx][idy]!= IHWEB) return;
554     game.quad[game.tholian.x][game.tholian.y] = IHWEB;
555
556     if (game.tholian.x != idx) {
557         /* move in x axis */
558         im = fabs((double)idx - game.tholian.x)/((double)idx - game.tholian.x);
559         while (game.tholian.x != idx) {
560             game.tholian.x += im;
561             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
562         }
563     }
564     else if (game.tholian.y != idy) {
565         /* move in y axis */
566         im = fabs((double)idy - game.tholian.y)/((double)idy - game.tholian.y);
567         while (game.tholian.y != idy) {
568             game.tholian.y += im;
569             if (game.quad[game.tholian.x][game.tholian.y]==IHDOT) game.quad[game.tholian.x][game.tholian.y] = IHWEB;
570         }
571     }
572     game.quad[game.tholian.x][game.tholian.y] = IHT;
573     game.ks[game.nenhere] = game.tholian;
574
575     /* check to see if all holes plugged */
576     for_sectors(i) {
577         if (game.quad[1][i]!=IHWEB && game.quad[1][i]!=IHT) return;
578         if (game.quad[QUADSIZE][i]!=IHWEB && game.quad[QUADSIZE][i]!=IHT) return;
579         if (game.quad[i][1]!=IHWEB && game.quad[i][1]!=IHT) return;
580         if (game.quad[i][QUADSIZE]!=IHWEB && game.quad[i][QUADSIZE]!=IHT) return;
581     }
582     /* All plugged up -- Tholian splits */
583     game.quad[game.tholian.x][game.tholian.y]=IHWEB;
584     dropin(IHBLANK, &dummy);
585     crmena(1,IHT, 2, game.tholian);
586     prout(_(" completes web."));
587     game.ithere = game.tholian.x = game.tholian.y = 0;
588     game.nenhere--;
589     return;
590 }