1 /*
2  * Shotgun Debugger
3  * Copyright 2005 Game Creation Society
4  *
5  * Programmed by Matt Sarnoff
6  * http://www.contrib.andrew.cmu.edu/~msarnoff
7  * http://www.gamecreation.org
8  *
9  * enemies.cpp - enemy AI routines
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25 
26 #include "sdb.h"
27 
set(int st,int obj,int tgt,float sigr,float ad,float fov)28 void AI::set(int st, int obj, int tgt,  float sigr, float ad, float fov)
29 {
30   state = st; object = obj; target = tgt;
31   doSound = true;
32   ultimateMoveTarget.set(0,0);
33   moveTarget.set(0,0);
34   hasMoveTarget = movingToNode = false;
35   nodeTarget = -1;
36   targetReacquireTimer = -999;
37   task = AIT_STOP;
38   nodePriorities.clear();
39 
40   signalRadius = sigr;
41   alertDistance = ad;
42   fieldOfVision = fov;
43 
44   for (int i = 0; i < currLevel.node.size(); i++)
45     nodePriorities.push_back(0);
46 }
47 
angleCalculations(bool targetOrNode)48 void AI::angleCalculations(bool targetOrNode)
49 {
50   if (targetOrNode)
51     angleToTarget = atan2(currLevelObjs[target]->PosY()-currLevelObjs[object]->PosY(),
52                         currLevelObjs[target]->PosX()-currLevelObjs[object]->PosX());
53   else
54     angleToTarget = atan2(moveTarget.c[Y]-currLevelObjs[object]->PosY(),
55                         moveTarget.c[X]-currLevelObjs[object]->PosX());
56 
57   angleDiff = currLevelObjs[object]->LookAngle() - angleToTarget;
58   if (angleDiff > PI)
59     angleDiff -= 2*PI;
60 }
61 
distanceCalculation(bool targetOrNode)62 void AI::distanceCalculation(bool targetOrNode)
63 {
64   if (targetOrNode == true)
65     distToTarget = dist(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos());
66   else
67     distToTarget = dist(currLevelObjs[object]->Pos(), moveTarget);
68 }
69 
70 // returns true if the object can see it, false if there's a wall in the way
wallOccludeCalculation()71 bool AI::wallOccludeCalculation()
72 { return hasLineOfSight(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos(), true); }
73 
setNodePriorities(Vector2D pos)74 void AI::setNodePriorities(Vector2D pos)
75 {
76   for (int i = 0; i < currLevel.node.size(); i++)
77     nodePriorities[i] = 0;
78 
79   for (int i = 0; i < currLevel.node.size(); i++)
80   {
81     if (hasLineOfSight(currLevel.node[i].pos, pos, false))
82     {
83       nodePriorities[i] = 10;
84       //printf("%d can see this position\n", i);
85       setChildNodePriorities(currLevel.node[i].visible, 9);
86     }
87   }
88 }
89 
getAngle(Vector2D pos)90 float AI::getAngle(Vector2D pos)
91 {
92   return atan2(pos.c[Y]-currLevelObjs[object]->PosY(),
93                pos.c[X]-currLevelObjs[object]->PosX());
94 }
95 
getAngleDiff(float a1,float a2)96 float AI::getAngleDiff(float a1, float a2)
97 {
98   float diff = a1-a2;
99   if (diff > PI)
100     diff -= 2*PI;
101 
102   return diff;
103 }
104 
getDistance(Vector2D pos)105 float AI::getDistance(Vector2D pos)
106 {
107   return dist(currLevelObjs[object]->Pos(), pos);
108 }
109 
110 
setChildNodePriorities(vector<int> children,int priority)111 void AI::setChildNodePriorities(vector<int> children, int priority)
112 {
113   if (priority == 0)
114     return;
115 
116   for (int i = 0; i < children.size(); i++)
117   {
118     if (priority > nodePriorities[children[i]])
119     {
120       nodePriorities[children[i]] = priority;
121       setChildNodePriorities(currLevel.node[children[i]].visible, priority-1);
122     }
123   }
124 }
125 
setMoveTarget(Vector2D tgtPos,int tsk)126 void AI::setMoveTarget(Vector2D tgtPos, int tsk)
127 {
128   //state = MOVING;
129   ultimateMoveTarget = tgtPos;
130   hasMoveTarget = true;
131   task = tsk;
132 
133   setNodePriorities(tgtPos);
134   updateMoveTarget();
135 }
136 
updateMoveTarget()137 void AI::updateMoveTarget()
138 {
139   //printf("Old target %d: ", nodeTarget);
140   // If we have line-of-sight with the ultimate target, target it
141   if (hasLineOfSight(currLevelObjs[object]->Pos(), ultimateMoveTarget, false))
142   {
143     //printf("LOS\n");
144     moveTarget = ultimateMoveTarget;
145   }
146   // If not, look at the visible nodes. Find the closest node of the highest priority
147   // and set that as the target. If no priority nodes are found, abort the movement.
148   else
149   {
150     float closestDistance = 1e17, distToNode;
151     int closestNode = 0;
152     int highestPriority = 0;
153     vector<int> visibleNodes;
154 
155     // Find visible nodes
156     for (int i = 0; i < currLevel.node.size(); i++)
157     {
158       if (hasLineOfSight(currLevelObjs[object]->Pos(), currLevel.node[i].pos, false) && i != nodeTarget)
159       {
160         visibleNodes.push_back(i);
161         if (nodePriorities[i] > highestPriority) highestPriority = nodePriorities[i];
162       }
163     }
164 
165     // Die if we didn't find any good nodes, or no nodes are visible
166     if (highestPriority == 0)
167     {
168       hasMoveTarget = false;
169       state = AWAKE;
170       return;
171     }
172 
173     // Search nodes of highest priority and find closest "prime" node
174     for (int i = 0; i < visibleNodes.size(); i++)
175     {
176       if (nodePriorities[visibleNodes[i]] == highestPriority)
177       {
178         distToNode = dist(currLevelObjs[object]->Pos(), currLevel.node[visibleNodes[i]].pos);
179         if (distToNode < closestDistance)
180         { closestDistance = distToNode; closestNode = visibleNodes[i]; }
181       }
182     }
183 
184     // We've got it; set this node's position as our new target.
185     moveTarget = currLevel.node[closestNode].pos;
186 
187     nodeTarget = closestNode;
188     //printf("Heading to node %d\n", closestNode);
189   }
190 }
191 
sendAlertSignal(double dst)192 void AI::sendAlertSignal(double dst)
193 {
194   for (int i = 0; i < currLevelEnemies.size(); i++)
195   {
196     if (dist(currLevelObjs[currLevelEnemies[i]->Object()]->Pos(), currLevelObjs[object]->Pos()) < dst)
197     {
198       if (hasLineOfSight(currLevelObjs[object]->Pos(), currLevelObjs[currLevelEnemies[i]->Object()]->Pos(), true))
199         currLevelEnemies[i]->setState(ATTACKING);
200     }
201   }
202 }
203 
move()204 void KamikazeAI::move()
205 {
206   if (!currLevelObjs[object]->Stunned())
207   {
208     currLevelObjs[object]->ev.setFB(1);
209     currLevelObjs[object]->setLookAngle(angleToTarget);
210   }
211 }
212 
idleAnimations()213 void KamikazeAI::idleAnimations()
214 {
215   if (state != ATTACKING && !currLevelObjs[object]->Stunned())
216   {
217     /*idleActionTimer -= timer.dT();
218     if (idleActionTimer <= 0)
219     {
220       currLevelObjs[object]->setAnimation(MA_SPECIAL1+rand()%3, ANIM_TRANSIENT);
221       idleActionTimer = frand()*5.0+2.0;
222     }*/
223   }
224 }
225 
alertCheck()226 void KamikazeAI::alertCheck()
227 {
228   // If we were alerted, turn in that direction //
229   if (currLevelObjs[object]->DirectionAlertType() != ALERT_NONE)
230   {
231     float ang = currLevelObjs[object]->DirectionAlert();
232     currLevelObjs[object]->setLookAngle(ang);
233 
234     if (currLevelObjs[object]->DirectionAlertType() != ALERT_TOUCH && currLevelObjs[object]->Type() != ENT_MIB)
235       if (rand()%5 == 0) playSound(SND_ROBOT_ALERT, 1);
236 
237     currLevelObjs[object]->setDirectionAlert(NO_ALERT, ALERT_NONE);
238   }
239 }
240 
lineOfSightBehavior()241 void KamikazeAI::lineOfSightBehavior()
242 {
243   angleCalculations(true);
244   distanceCalculation(true);
245   hasMoveTarget = true;
246   moveTarget = currLevelObjs[target]->Pos();
247   movingToNode = false;
248 
249   attack();
250 }
251 
attack()252 void KamikazeAI::attack()
253 {
254   if (!currLevelObjs[object]->Stunned())
255   {
256     if (dist(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos()) < 15 &&
257        !currLevelObjs[object]->Dying() && currLevelObjs[object]->Type() != ENT_MIB)
258       currLevelObjs[object]->setAnimation(MA_SHORT, ANIM_TRANSIENT);
259     if (dist(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos()) < 5 &&
260       !currLevelObjs[object]->Dying())
261     {
262       if (currLevelObjs[object]->Type() == ENT_MIB)
263         setTrigger(-3);
264       else
265         currLevelObjs[object]->kill(true);
266     }
267   }
268 }
269 
nodeFollowBehavior()270 void KamikazeAI::nodeFollowBehavior()
271 {
272   if (!movingToNode)
273   {
274     targetReacquireTimer = 2.0;
275     int closest = currLevel.closestNode(currLevelObjs[target]->Pos());
276     setMoveTarget(currLevel.node[closest].pos, AIT_ATTACK);
277 
278     movingToNode = true;
279   }
280   else
281   {
282     targetReacquireTimer -= timer.dT();
283 
284     if (targetReacquireTimer <= 0)
285     {
286       int closest = currLevel.closestNode(currLevelObjs[target]->Pos());
287       setMoveTarget(currLevel.node[closest].pos, AIT_ATTACK);
288       targetReacquireTimer = 2.0;
289     }
290   }
291 
292   // If we hit our destination node, find the next one.
293   // Note that as soon as we have line of sight with the target, we abandon
294   // the node path. If we lose line of sight again, we calculate
295   // another path and start over.
296   distanceCalculation(false);
297   if (distToTarget < 1)
298   {
299     //printf("Got to target node %d\n", nodeTarget);
300     updateMoveTarget();
301   }
302 }
303 
targetDetect()304 void KamikazeAI::targetDetect()
305 {
306   if (currLevelObjs[target]->isActive() && (distToTarget < alertDistance) && (fabs(angleDiff) < torad(fieldOfVision)) && wallOccludeCalculation())
307   {
308     sendAlertSignal(signalRadius);
309     state = ATTACKING;
310   }
311 }
312 
313 
update()314 void KamikazeAI::update()
315 {
316   if (target > -1 && currLevelObjs[object]->isActive() && !currLevelObjs[object]->Stunned())
317   {
318     idleAnimations();
319 
320     // Trigger detection //
321     if (triggers[currLevelObjs[object]->RespondTo1()].hit)
322     {
323       if (state == ASLEEP)
324         state = AWAKE;
325       else
326         state = ATTACKING;
327     }
328 
329     // Make calculations //
330     angleCalculations(true);
331     distanceCalculation(true);
332     if (state != ASLEEP)
333     {
334       if (hasMoveTarget)
335       {
336         angleCalculations(false);
337         distanceCalculation(false);
338         move();
339       }
340       else
341         alertCheck();
342 
343       if (state == ATTACKING)
344       {
345         if (doSound || currLevelObjs[object]->Type() != ENT_MIB)
346           { playSound(SND_ROBOT_SEES, 1); doSound = false; }
347 
348         // If we can see the target, head straight for 'im!
349         if (hasLineOfSight(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos(), true))
350           lineOfSightBehavior();
351         // If we're lost line of sight, calculate a node path
352         else if (currLevel.node.size() > 0)
353           nodeFollowBehavior();
354       }
355       else
356         targetDetect();
357     }
358   }
359 }
360 
setIdleRotation()361 void TurretAI::setIdleRotation()
362 {
363   idleRotateTimer = idleRotateLength;
364   if (!idleRotateDirection) idleRotateTimer *= 0.5;
365   idleRotateDirection = 0.5 * ((idleRotateDirection < 0) ? 1.0 : -1.0);
366 }
367 
update()368 void TurretAI::update()
369 {
370   if (target > -1 && currLevelObjs[object]->isActive() && !currLevelObjs[object]->Stunned())
371   {
372     angleCalculations(true);
373     distanceCalculation(true);
374     if (state == AWAKE)
375     {
376       currLevelObjs[object]->ev.setLR(idleRotateDirection);
377       idleRotateTimer -= timer.dT();
378       if (idleRotateTimer <= 0)
379         setIdleRotation();
380       currLevelObjs[object]->ev.setFire(0);
381       if (!doSound) doSound = true;
382 
383 
384       if (currLevelObjs[target]->isActive() && distToTarget < alertDistance && fabs(angleDiff) < torad(fieldOfVision) && wallOccludeCalculation())
385         state = ATTACKING;
386     }
387 
388     if (currLevelObjs[object]->DirectionAlertType() != ALERT_NONE)
389     {
390       float ang = currLevelObjs[object]->DirectionAlert();
391       setMoveTarget(currLevelObjs[object]->Pos() + Vector2D(cos(ang)*2, sin(ang)*2), AIT_INVESTIGATE);
392       state = ATTACKING;
393       attackTimer = 0;
394       currLevelObjs[object]->setDirectionAlert(NO_ALERT, ALERT_NONE);
395     }
396 
397     if (state == ATTACKING)
398     {
399       if (hasMoveTarget)
400       {
401         float moveTargetAngleDiff = getAngleDiff(currLevelObjs[object]->LookAngle(), getAngle(moveTarget));
402 
403         currLevelObjs[object]->ev.setLR(2*sign(moveTargetAngleDiff));
404 
405         currLevelObjs[object]->ev.setFire(1);
406 
407         attackTimer += timer.dT();
408         if (attackTimer >= 3.0)
409         {
410           if (idleRotateLength < 5) idleRotateLength += 0.75;
411           state = AWAKE;
412           hasMoveTarget = false;
413           attackTimer = 0;
414         }
415         if (currLevelObjs[target]->isActive() && distToTarget < alertDistance && fabs(angleDiff) < torad(fieldOfVision) &&  wallOccludeCalculation())
416           hasMoveTarget = false;
417       }
418       else
419       {
420         if (angleDiff < torad(5))
421           currLevelObjs[object]->ev.setLR(-1);
422         else if (angleDiff > torad(5))
423           currLevelObjs[object]->ev.setLR(1);
424         else
425           currLevelObjs[object]->setLookAngle(angleToTarget);
426         currLevelObjs[object]->ev.setFire(1);
427 
428         if (doSound)
429           { playSound(SND_ROBOT_SEES, 1); doSound = false; }
430 
431         if (!(currLevelObjs[target]->isActive() && distToTarget < alertDistance && fabs(angleDiff) < torad(fieldOfVision) && wallOccludeCalculation()))
432           state = AWAKE;
433       }
434     }
435   }
436 }
437 
update()438 void SlaveTurretAI::update()
439 {
440   if (target > -1 && currLevelObjs[object]->isActive() && !currLevelObjs[object]->Stunned())
441   {
442     if (triggers[currLevelObjs[object]->RespondTo1()].hit)
443     {
444       state = ATTACKING;
445       currLevelObjs[object]->ev.setFire(1);
446     }
447     else
448     {
449       state = AWAKE;
450       currLevelObjs[object]->ev.setFire(0);
451     }
452   }
453 }
454 
alertCheck()455 void HunterAI::alertCheck()
456 {
457   // If we were alerted, turn in that direction //
458   if (currLevelObjs[object]->DirectionAlertType() != ALERT_NONE)
459   {
460     float ang = currLevelObjs[object]->DirectionAlert();
461     currLevelObjs[object]->setLookAngle(ang);
462     anger += 0.03 * currLevelObjs[object]->LastDamageAmount();
463 
464     if (currLevelObjs[object]->DirectionAlertType() == ALERT_SHOT)
465     { if (rand() % 2) playSound(SND_ROBOT_ALERT, 1); }
466 
467     currLevelObjs[object]->setDirectionAlert(NO_ALERT, ALERT_NONE);
468   }
469 }
470 
targetDetect()471 void HunterAI::targetDetect()
472 {
473   if (currLevelObjs[target]->isActive() && (distToTarget < alertDistance) && (fabs(angleDiff) < torad(fieldOfVision)) && wallOccludeCalculation())
474   {
475     sendAlertSignal(signalRadius);
476     state = ATTACKING;
477   }
478 }
479 
update()480 void HunterAI::update()
481 {
482   if (target > -1 && currLevelObjs[object]->isActive() && !currLevelObjs[object]->Stunned())
483   {
484     idleAnimations();
485 
486     // Trigger detection //
487     if (triggers[currLevelObjs[object]->RespondTo1()].hit)
488       state = ATTACKING;
489 
490     // Make calculations //
491     angleCalculations(true);
492     distanceCalculation(true);
493 
494     if (state != ASLEEP)
495     {
496       if (hasMoveTarget)
497       {
498         angleCalculations(false);
499         distanceCalculation(false);
500         move();
501 
502         if (distToTarget <= 1.0 && task == AIT_INVESTIGATE)
503           hasMoveTarget = false;
504       }
505 
506       alertCheck();
507 
508       if (state == ATTACKING)
509       {
510         if (doSound)
511           { playSound(SND_ROBOT_SEES, 1); doSound = false; }
512 
513         // If we can see the target, head straight for 'im!
514         if (hasLineOfSight(currLevelObjs[object]->Pos(), currLevelObjs[target]->Pos(), true))
515           lineOfSightBehavior();
516         // If we're lost line of sight, calculate a node path
517         else if (currLevel.node.size() > 0)
518         {
519           currLevelObjs[object]->ev.setFire(0);
520           nodeFollowBehavior();
521         }
522 
523         inaccuracyTimer -= timer.dT();
524         if (inaccuracyTimer <= 0)
525         {
526           angleInaccuracy = torad(frand()*angleInaccuracyMax)*anger;
527           inaccuracyTimer = (frand()*0.5)/anger;
528         }
529 
530         pauseTimer -= timer.dT();
531         if (pauseTimer <= 0)
532         {
533           pauseLength = (frand()*pauseLengthRand+pauseLengthMin)/anger;
534           pauseTimer = (frand()*pauseDelayRand+pauseDelayMin)/anger;
535         }
536 
537       }
538       else
539         targetDetect();
540 
541 
542       if (anger > 5.0)
543         anger = 5.0;
544 
545       anger -= 0.05*timer.dT();
546 
547       if (anger < 1.0)
548         anger = 1.0;
549     }
550   }
551 }
552 
lineOfSightBehavior()553 void HunterAI::lineOfSightBehavior()
554 {
555   angleCalculations(true);
556   distanceCalculation(true);
557   hasMoveTarget = true;
558   moveTarget = currLevelObjs[target]->Pos();
559   movingToNode = false;
560 
561   shootTimer -= timer.dT();
562   if (shootTimer <= 0)
563   {
564     shootTimer = (frand()*shootDelayRand+shootDelayMin)/anger;
565     shotBurstLength = (frand()*shootLengthRand+shootLengthMin)*anger;
566   }
567 
568   if (shotBurstLength > 0)
569   {
570     currLevelObjs[object]->ev.setFire(1);
571     shotBurstLength -= timer.dT();
572   }
573   else
574   {
575     currLevelObjs[object]->ev.setFire(0);
576     shotBurstLength = 0;
577   }
578 }
579 
move()580 void HunterAI::move()
581 {
582   if (!currLevelObjs[object]->Stunned())
583   {
584     if (pauseLength > 0)
585     {
586       currLevelObjs[object]->ev.setFB(0);
587       currLevelObjs[object]->ev.setStrafe(0);
588       pauseLength -= timer.dT();
589     }
590     else
591     {
592       if (distToTarget >= closestDistance/anger)
593         currLevelObjs[object]->ev.setFB(1*anger);
594       else
595         currLevelObjs[object]->ev.setFB(0);
596 
597       pauseLength = 0;
598     }
599 
600     if (currLevelObjs[object]->Type() == ENT_HUNTER)
601     {
602       if (distToTarget < closestDistance)
603         currLevelObjs[object]->ev.setStrafe(strafePreference);
604     }
605 
606     currLevelObjs[object]->setLookAngle(angleToTarget+angleCompensation+angleInaccuracy);
607   }
608 }
609