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