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