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