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