1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 Nuke.YKT
5 
6 This file is part of NBlood.
7 
8 NBlood is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License version 2
10 as published by the Free Software Foundation.
11 
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 
16 See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 */
22 //-------------------------------------------------------------------------
23 #include "compat.h"
24 #include "build.h"
25 #include "pragmas.h"
26 #include "mmulti.h"
27 #include "common_game.h"
28 
29 #include "actor.h"
30 #include "ai.h"
31 #include "aicerber.h"
32 #include "blood.h"
33 #include "db.h"
34 #include "dude.h"
35 #include "levels.h"
36 #include "player.h"
37 #include "seq.h"
38 #include "sfx.h"
39 #include "trig.h"
40 
41 static void BiteSeqCallback(int, int);
42 static void BurnSeqCallback(int, int);
43 static void BurnSeqCallback2(int, int);
44 static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite);
45 static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite);
46 static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite);
47 static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite);
48 
49 static int nBiteClient = seqRegisterClient(BiteSeqCallback);
50 static int nBurnClient = seqRegisterClient(BurnSeqCallback);
51 static int nBurnClient2 = seqRegisterClient(BurnSeqCallback2);
52 
53 AISTATE cerberusIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
54 AISTATE cerberusSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberusIdle };
55 AISTATE cerberusChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
56 AISTATE cerberusRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberusSearch };
57 AISTATE cerberusTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cerberusSearch };
58 AISTATE cerberusGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberusIdle };
59 AISTATE cerberusBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberusChase };
60 AISTATE cerberusBurn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberusChase };
61 AISTATE cerberus3Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberusChase };
62 AISTATE cerberus2Idle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
63 AISTATE cerberus2Search = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberus2Idle };
64 AISTATE cerberus2Chase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
65 AISTATE cerberus2Recoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberus2Search };
66 AISTATE cerberus2Goto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberus2Idle };
67 AISTATE cerberus2Bite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberus2Chase };
68 AISTATE cerberus2Burn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberus2Chase };
69 AISTATE cerberus4Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberus2Chase };
70 AISTATE cerberus139890 = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
71 AISTATE cerberus1398AC = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
72 
BiteSeqCallback(int,int nXSprite)73 static void BiteSeqCallback(int, int nXSprite)
74 {
75     XSPRITE *pXSprite = &xsprite[nXSprite];
76     int nSprite = pXSprite->reference;
77     spritetype *pSprite = &sprite[nSprite];
78     int dx = Cos(pSprite->ang)>>16;
79     int dy = Sin(pSprite->ang)>>16;
80     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
81     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
82         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
83         return;
84     }
85     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
86     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
87         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
88         return;
89     }
90     spritetype *pTarget = &sprite[pXSprite->target];
91     int dz = pTarget->z-pSprite->z;
92     actFireVector(pSprite, 350, -100, dx, dy, dz, kVectorCerberusHack);
93     actFireVector(pSprite, -350, 0, dx, dy, dz, kVectorCerberusHack);
94     actFireVector(pSprite, 0, 0, dx, dy, dz, kVectorCerberusHack);
95 }
96 
BurnSeqCallback(int,int nXSprite)97 static void BurnSeqCallback(int, int nXSprite)
98 {
99     XSPRITE *pXSprite = &xsprite[nXSprite];
100     int nSprite = pXSprite->reference;
101     spritetype *pSprite = &sprite[nSprite];
102     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
103     int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
104     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
105     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
106         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
107         return;
108     }
109     int x = pSprite->x;
110     int y = pSprite->y;
111     int z = height; // ???
112     TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
113     Aim aim;
114     aim.dx = Cos(pSprite->ang)>>16;
115     aim.dy = Sin(pSprite->ang)>>16;
116     aim.dz = gDudeSlope[nXSprite];
117     int nClosest = 0x7fffffff;
118     for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
119     {
120         spritetype *pSprite2 = &sprite[nSprite2];
121         if (pSprite == pSprite2 || !(pSprite2->flags&8))
122             continue;
123         int x2 = pSprite2->x;
124         int y2 = pSprite2->y;
125         int z2 = pSprite2->z;
126         int nDist = approxDist(x2-x, y2-y);
127         if (nDist == 0 || nDist > 0x2800)
128             continue;
129         if (tt1.at10)
130         {
131             int t = divscale(nDist, tt1.at10, 12);
132             x2 += (xvel[nSprite2]*t)>>12;
133             y2 += (yvel[nSprite2]*t)>>12;
134             z2 += (zvel[nSprite2]*t)>>8;
135         }
136         int tx = x+mulscale30(Cos(pSprite->ang), nDist);
137         int ty = y+mulscale30(Sin(pSprite->ang), nDist);
138         int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
139         int tsr = mulscale(9460, nDist, 10);
140         int top, bottom;
141         GetSpriteExtents(pSprite2, &top, &bottom);
142         if (tz-tsr > bottom || tz+tsr < top)
143             continue;
144         int dx = (tx-x2)>>4;
145         int dy = (ty-y2)>>4;
146         int dz = (tz-z2)>>8;
147         int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
148         if (nDist2 < nClosest)
149         {
150             int nAngle = getangle(x2-x, y2-y);
151             int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
152             if (klabs(nDeltaAngle) <= tt1.at8)
153             {
154                 int tz = pSprite2->z-pSprite->z;
155                 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
156                 {
157                     nClosest = nDist2;
158                     aim.dx = Cos(nAngle)>>16;
159                     aim.dy = Sin(nAngle)>>16;
160                     aim.dz = divscale(tz, nDist, 10);
161                 }
162                 else
163                     aim.dz = tz;
164             }
165         }
166     }
167     switch (pSprite->type) {
168         case kDudeCerberusTwoHead:
169             actFireMissile(pSprite, -350, 0, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
170             actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
171             break;
172         case kDudeCerberusOneHead:
173             actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, kMissileFireballCerberus);
174             break;
175     }
176 }
177 
BurnSeqCallback2(int,int nXSprite)178 static void BurnSeqCallback2(int, int nXSprite)
179 {
180     XSPRITE *pXSprite = &xsprite[nXSprite];
181     int nSprite = pXSprite->reference;
182     spritetype *pSprite = &sprite[nSprite];
183     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
184     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
185         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
186         return;
187     }
188     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
189     int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
190 
191     int x = pSprite->x;
192     int y = pSprite->y;
193     int z = height; // ???
194     TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
195     Aim aim;
196     int ax, ay, az;
197     aim.dx = ax = Cos(pSprite->ang)>>16;
198     aim.dy = ay = Sin(pSprite->ang)>>16;
199     aim.dz = gDudeSlope[nXSprite];
200     az = 0;
201     int nClosest = 0x7fffffff;
202     for (short nSprite2 = headspritestat[kStatDude]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
203     {
204         spritetype *pSprite2 = &sprite[nSprite2];
205         if (pSprite == pSprite2 || !(pSprite2->flags&8))
206             continue;
207         int x2 = pSprite2->x;
208         int y2 = pSprite2->y;
209         int z2 = pSprite2->z;
210         int nDist = approxDist(x2-x, y2-y);
211         if (nDist == 0 || nDist > 0x2800)
212             continue;
213         if (tt1.at10)
214         {
215             int t = divscale(nDist, tt1.at10, 12);
216             x2 += (xvel[nSprite2]*t)>>12;
217             y2 += (yvel[nSprite2]*t)>>12;
218             z2 += (zvel[nSprite2]*t)>>8;
219         }
220         int tx = x+mulscale30(Cos(pSprite->ang), nDist);
221         int ty = y+mulscale30(Sin(pSprite->ang), nDist);
222         int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
223         int tsr = mulscale(9460, nDist, 10);
224         int top, bottom;
225         GetSpriteExtents(pSprite2, &top, &bottom);
226         if (tz-tsr > bottom || tz+tsr < top)
227             continue;
228         int dx = (tx-x2)>>4;
229         int dy = (ty-y2)>>4;
230         int dz = (tz-z2)>>8;
231         int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
232         if (nDist2 < nClosest)
233         {
234             int nAngle = getangle(x2-x, y2-y);
235             int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
236             if (klabs(nDeltaAngle) <= tt1.at8)
237             {
238                 DUDEINFO *pDudeInfo2 = getDudeInfo(pSprite2->type);
239                 int height = (pDudeInfo2->aimHeight*pSprite2->yrepeat)<<2;
240                 int tz = (z2-height)-z;
241                 if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
242                 {
243                     nClosest = nDist2;
244                     aim.dx = Cos(nAngle)>>16;
245                     aim.dy = Sin(nAngle)>>16;
246                     aim.dz = divscale(tz, nDist, 10);
247                 }
248                 else
249                     aim.dz = tz;
250             }
251         }
252     }
253     switch (pSprite->type) {
254         case kDudeCerberusTwoHead:
255             actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, kMissileFlameHound);
256             actFireMissile(pSprite, -350, 0, ax, ay, az, kMissileFlameHound);
257             break;
258         case kDudeCerberusOneHead:
259             actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, kMissileFlameHound);
260             break;
261     }
262 }
263 
thinkSearch(spritetype * pSprite,XSPRITE * pXSprite)264 static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
265 {
266     aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
267     aiThinkTarget(pSprite, pXSprite);
268 }
269 
thinkTarget(spritetype * pSprite,XSPRITE * pXSprite)270 static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
271 {
272     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
273     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
274         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
275         return;
276     }
277     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
278     DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
279     if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
280         pDudeExtraE->at4++;
281     else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
282     {
283         pXSprite->goalAng += 256;
284         POINT3D *pTarget = &baseSprite[pSprite->index];
285         aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
286         if (pSprite->type == kDudeCerberusTwoHead)
287             aiNewState(pSprite, pXSprite, &cerberus139890);
288         else
289             aiNewState(pSprite, pXSprite, &cerberus1398AC);
290         return;
291     }
292     if (Chance(pDudeInfo->alertChance))
293     {
294         for (int p = connecthead; p >= 0; p = connectpoint2[p])
295         {
296             PLAYER *pPlayer = &gPlayer[p];
297             if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
298                 continue;
299             int x = pPlayer->pSprite->x;
300             int y = pPlayer->pSprite->y;
301             int z = pPlayer->pSprite->z;
302             int nSector = pPlayer->pSprite->sectnum;
303             int dx = x-pSprite->x;
304             int dy = y-pSprite->y;
305             int nDist = approxDist(dx, dy);
306             if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
307                 continue;
308             if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
309                 continue;
310             int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
311             if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
312             {
313                 pDudeExtraE->at0 = 0;
314                 aiSetTarget(pXSprite, pPlayer->nSprite);
315                 aiActivateDude(pSprite, pXSprite);
316             }
317             else if (nDist < pDudeInfo->hearDist)
318             {
319                 pDudeExtraE->at0 = 0;
320                 aiSetTarget(pXSprite, x, y, z);
321                 aiActivateDude(pSprite, pXSprite);
322             }
323             else
324                 continue;
325             break;
326         }
327     }
328 }
329 
thinkGoto(spritetype * pSprite,XSPRITE * pXSprite)330 static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
331 {
332     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
333     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
334         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
335         return;
336     }
337     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
338     int dx = pXSprite->targetX-pSprite->x;
339     int dy = pXSprite->targetY-pSprite->y;
340     int nAngle = getangle(dx, dy);
341     int nDist = approxDist(dx, dy);
342     aiChooseDirection(pSprite, pXSprite, nAngle);
343     if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
344     {
345         switch (pSprite->type) {
346             case kDudeCerberusTwoHead:
347                 aiNewState(pSprite, pXSprite, &cerberusSearch);
348                 break;
349             case kDudeCerberusOneHead:
350                 aiNewState(pSprite, pXSprite, &cerberus2Search);
351                 break;
352         }
353     }
354     aiThinkTarget(pSprite, pXSprite);
355 }
356 
thinkChase(spritetype * pSprite,XSPRITE * pXSprite)357 static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
358 {
359     if (pXSprite->target == -1) {
360         switch (pSprite->type) {
361             case kDudeCerberusTwoHead:
362                 aiNewState(pSprite, pXSprite, &cerberusGoto);
363                 break;
364             case kDudeCerberusOneHead:
365                 aiNewState(pSprite, pXSprite, &cerberus2Goto);
366                 break;
367         }
368         return;
369     }
370 
371     ///dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
372     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
373         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
374         return;
375     }
376 
377     DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
378 
379     ///dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
380     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites)) {
381         consoleSysMsg("pXSprite->target >= 0 && pXSprite->target < kMaxSprites");
382         return;
383     }
384     spritetype *pTarget = &sprite[pXSprite->target];
385     XSPRITE *pXTarget = &xsprite[pTarget->extra];
386     int dx = pTarget->x-pSprite->x;
387     int dy = pTarget->y-pSprite->y;
388     aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
389 
390     if (pXTarget->health == 0) {
391         switch (pSprite->type) {
392             case kDudeCerberusTwoHead:
393                 aiNewState(pSprite, pXSprite, &cerberusSearch);
394                 break;
395             case kDudeCerberusOneHead:
396                 aiNewState(pSprite, pXSprite, &cerberus2Search);
397                 break;
398         }
399         return;
400     }
401 
402     if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], kPwUpShadowCloak) > 0) {
403         switch (pSprite->type) {
404             case kDudeCerberusTwoHead:
405                 aiNewState(pSprite, pXSprite, &cerberusSearch);
406                 break;
407             case kDudeCerberusOneHead:
408                 aiNewState(pSprite, pXSprite, &cerberus2Search);
409                 break;
410         }
411         return;
412     }
413 
414     int nDist = approxDist(dx, dy);
415     if (nDist <= pDudeInfo->seeDist)
416     {
417         int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
418         int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
419         if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
420         {
421             if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery) {
422                 aiSetTarget(pXSprite, pXSprite->target);
423 
424                 if (nDist < 0x1b00 && nDist > 0xd00 && klabs(nDeltaAngle) < 85) {
425                     switch (pSprite->type) {
426                         case kDudeCerberusTwoHead:
427                             aiNewState(pSprite, pXSprite, &cerberusBurn);
428                             break;
429                         case kDudeCerberusOneHead:
430                             aiNewState(pSprite, pXSprite, &cerberus2Burn);
431                             break;
432                     }
433                 }
434 
435                 else if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85) {
436                     switch (pSprite->type) {
437                         case kDudeCerberusTwoHead:
438                             aiNewState(pSprite, pXSprite, &cerberus3Burn);
439                             break;
440                         case kDudeCerberusOneHead:
441                             aiNewState(pSprite, pXSprite, &cerberus4Burn);
442                             break;
443                     }
444                 }
445                 else if (nDist < 0x200 && klabs(nDeltaAngle) < 85)
446                 {
447                     int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
448                     switch (pSprite->type) {
449                     case kDudeCerberusTwoHead:
450                         switch (hit) {
451                             case -1:
452                                 aiNewState(pSprite, pXSprite, &cerberusBite);
453                                 break;
454                             case 3:
455                                 if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeHellHound)
456                                     aiNewState(pSprite, pXSprite, &cerberusBite);
457                                 break;
458                             case 0:
459                             case 4:
460                                 break;
461                             default:
462                                 aiNewState(pSprite, pXSprite, &cerberusBite);
463                                 break;
464                         }
465                         break;
466                     case kDudeCerberusOneHead:
467                         switch (hit) {
468                             case -1:
469                                 aiNewState(pSprite, pXSprite, &cerberus2Bite);
470                                 break;
471                             case 3:
472                                 if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != kDudeHellHound)
473                                     aiNewState(pSprite, pXSprite, &cerberus2Bite);
474                                 break;
475                             case 0:
476                             case 4:
477                                 break;
478                             default:
479                                 aiNewState(pSprite, pXSprite, &cerberus2Bite);
480                                 break;
481                         }
482                         break;
483                     }
484                 }
485                 return;
486             }
487         }
488     }
489 
490     switch (pSprite->type) {
491         case kDudeCerberusTwoHead:
492             aiNewState(pSprite, pXSprite, &cerberusGoto);
493             break;
494         case kDudeCerberusOneHead:
495             aiNewState(pSprite, pXSprite, &cerberus2Goto);
496             break;
497     }
498     pXSprite->target = -1;
499 }
500