1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 Nuke.YKT
5 Copyright (C) NoOne
6 
7 This file is part of NBlood.
8 
9 NBlood is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License version 2
11 as published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22 */
23 //-------------------------------------------------------------------------
24 #ifdef NOONE_EXTENSIONS
25 #include "common_game.h"
26 #include "nnexts.h"
27 #include "compat.h"
28 #include "build.h"
29 #include "pragmas.h"
30 #include "mmulti.h"
31 
32 #include "actor.h"
33 #include "ai.h"
34 #include "aiunicult.h"
35 #include "blood.h"
36 #include "db.h"
37 #include "dude.h"
38 
39 #include "eventq.h"
40 #include "globals.h"
41 #include "levels.h"
42 #include "player.h"
43 #include "seq.h"
44 #include "sfx.h"
45 #include "sound.h"
46 #include "trig.h"
47 #include "triggers.h"
48 #include "endgame.h"
49 #include "view.h"
50 #include "tile.h"
51 
52 #include "gib.h"
53 #include "aiburn.h"
54 
55 static void genDudeAttack1(int, int);
56 static void punchCallback(int, int);
57 static void ThrowCallback1(int, int);
58 static void ThrowCallback2(int, int);
59 static void ThrowThing(int, bool);
60 static void thinkSearch(spritetype*, XSPRITE*);
61 static void thinkGoto(spritetype*, XSPRITE*);
62 static void thinkChase(spritetype*, XSPRITE*);
63 static void forcePunch(spritetype*, XSPRITE*);
64 
65 static int nGenDudeAttack1 = seqRegisterClient(genDudeAttack1);
66 static int nGenDudePunch = seqRegisterClient(punchCallback);
67 static int nGenDudeThrow1 = seqRegisterClient(ThrowCallback1);
68 static int nGenDudeThrow2 = seqRegisterClient(ThrowCallback2);
69 
70 AISTATE genDudeIdleL = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
71 AISTATE genDudeIdleW = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
72 // ---------------------
73 AISTATE genDudeSearchL = { kAiStateSearch, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleL };
74 AISTATE genDudeSearchW = { kAiStateSearch, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleW };
75 // ---------------------
76 AISTATE genDudeSearchShortL = { kAiStateSearch, 9, -1, 200, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleL };
77 AISTATE genDudeSearchShortW = { kAiStateSearch, 13, -1, 200, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleW };
78 // ---------------------
79 AISTATE genDudeSearchNoWalkL = { kAiStateSearch, 0, -1, 600, NULL, aiMoveTurn, thinkSearch, &genDudeIdleL };
80 AISTATE genDudeSearchNoWalkW = { kAiStateSearch, 13, -1, 600, NULL, aiMoveTurn, thinkSearch, &genDudeIdleW };
81 // ---------------------
82 AISTATE genDudeGotoL = { kAiStateMove, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &genDudeIdleL };
83 AISTATE genDudeGotoW = { kAiStateMove, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &genDudeIdleW };
84 // ---------------------
85 AISTATE genDudeDodgeL = { kAiStateMove, 9, -1, 90, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
86 AISTATE genDudeDodgeD = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
87 AISTATE genDudeDodgeW = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
88 // ---------------------
89 AISTATE genDudeDodgeShortL = { kAiStateMove, 9, -1, 60, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
90 AISTATE genDudeDodgeShortD = { kAiStateMove, 14, -1, 60, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
91 AISTATE genDudeDodgeShortW = { kAiStateMove, 13, -1, 60, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
92 // ---------------------
93 AISTATE genDudeDodgeShorterL = { kAiStateMove, 9, -1, 20, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
94 AISTATE genDudeDodgeShorterD = { kAiStateMove, 14, -1, 20, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
95 AISTATE genDudeDodgeShorterW = { kAiStateMove, 13, -1, 20, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
96 // ---------------------
97 AISTATE genDudeChaseL = { kAiStateChase, 9, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
98 AISTATE genDudeChaseD = { kAiStateChase, 14, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
99 AISTATE genDudeChaseW = { kAiStateChase, 13, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
100 // ---------------------
101 AISTATE genDudeChaseNoWalkL = { kAiStateChase, 0, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
102 AISTATE genDudeChaseNoWalkD = { kAiStateChase, 14, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
103 AISTATE genDudeChaseNoWalkW = { kAiStateChase, 13, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
104 // ---------------------
105 AISTATE genDudeFireL = { kAiStateChase, 6, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireL };
106 AISTATE genDudeFireD = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireD };
107 AISTATE genDudeFireW = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireW };
108 // ---------------------z
109 AISTATE genDudeRecoilL = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseL };
110 AISTATE genDudeRecoilD = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseD };
111 AISTATE genDudeRecoilW = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseW };
112 AISTATE genDudeRecoilTesla = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &genDudeDodgeShortL };
113 // ---------------------
114 AISTATE genDudeThrow = { kAiStateChase, 7, nGenDudeThrow1, 0, NULL, NULL, NULL, &genDudeChaseL };
115 AISTATE genDudeThrow2 = { kAiStateChase, 7, nGenDudeThrow2, 0, NULL, NULL, NULL, &genDudeChaseL };
116 // ---------------------
117 AISTATE genDudePunch = { kAiStateChase,10, nGenDudePunch, 0, NULL, NULL, forcePunch, &genDudeChaseL };
118 // ---------------------
119 
120 GENDUDESND gCustomDudeSnd[] = {
121     { 1003, 2, 0, true, false   },      // spot sound
122     { 1013, 2, 2, true, true    },      // pain sound
123     { 1018, 2, 4, false, true   },      // death sound
124     { 1031, 2, 6, true, true    },      // burning state sound
125     { 1018, 2, 8, false, true   },      // explosive death or end of burning state sound
126     { 4021, 2, 10, true, false  },	    // target of dude is dead
127     { 1005, 2, 12, true, false  },	    // chase sound
128     { -1, 0, 14, false, true    },	    // weapon attack
129     { -1, 0, 15, false, true    },	    // throw attack
130     { -1, 0, 16, false, true    },	    // melee attack
131     { 9008, 0, 17, false, false },      // transforming in other dude
132 };
133 
134 GENDUDEEXTRA gGenDudeExtra[kMaxSprites];
135 
forcePunch(spritetype * pSprite,XSPRITE *)136 static void forcePunch(spritetype* pSprite, XSPRITE*) {
137     if (gGenDudeExtra[pSprite->index].forcePunch && seqGetStatus(3, pSprite->extra) == -1)
138         punchCallback(0,pSprite->extra);
139 }
140 
141 /*bool sameFamily(spritetype* pDude1, spritetype* pDude2) {
142 
143     return true;
144 }*/
145 
genDudeAdjustSlope(spritetype * pSprite,XSPRITE * pXSprite,int dist,int weaponType,int by)146 bool genDudeAdjustSlope(spritetype* pSprite, XSPRITE* pXSprite, int dist, int weaponType, int by) {
147     if (spriRangeIsFine(pXSprite->target)) {
148         int fStart = 0; int fEnd = 0; GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
149         unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1;
150         for (int i = -8191; i < 8192; i += by) {
151             HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, i, clipMask, dist);
152             if (!fStart && pXSprite->target == gHitInfo.hitsprite) fStart = i;
153             else if (fStart && pXSprite->target != gHitInfo.hitsprite) { fEnd = i; break; }
154         }
155 
156         if (fStart != fEnd) {
157 
158             if (weaponType == kGenDudeWeaponHitscan)
159                 gDudeSlope[pSprite->extra] = fStart - ((fStart - fEnd) >> 2);
160             else if (weaponType == kGenDudeWeaponMissile) {
161                 MissileType* pMissile = &missileInfo[pExtra->curWeapon - kMissileBase];
162                 gDudeSlope[pSprite->extra] = (fStart - ((fStart - fEnd) >> 2)) - (pMissile->clipDist << 1);
163             }
164 
165             //viewSetSystemMessage("!!!! FOUND, SLOPE %d, RANGE %d,%d", gDudeSlope[pSprite->extra], fStart, fEnd);
166             return true;
167         }
168     }
169 
170     return false;
171 
172 }
173 
genDudeExtra(spritetype * pGenDude)174 GENDUDEEXTRA* genDudeExtra(spritetype* pGenDude) {
175     return &gGenDudeExtra[pGenDude->index];
176 }
177 
genDudeUpdate(spritetype * pSprite)178 void genDudeUpdate(spritetype* pSprite) {
179     GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
180     for (int i = 0; i < kGenDudePropertyMax; i++) {
181         if (pExtra->updReq[i]) genDudePrepare(pSprite, i);
182     }
183 }
184 
punchCallback(int,int nXIndex)185 static void punchCallback(int, int nXIndex) {
186     XSPRITE* pXSprite = &xsprite[nXIndex];
187     if (pXSprite->target != -1) {
188         int nSprite = pXSprite->reference;
189         spritetype* pSprite = &sprite[nSprite];
190 
191         int nZOffset1 = getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat << 2;
192         int nZOffset2 = 0;
193 
194         spritetype* pTarget = &sprite[pXSprite->target];
195         if(IsDudeSprite(pTarget))
196             nZOffset2 = getDudeInfo(pTarget->type)->eyeHeight * pTarget->yrepeat << 2;
197 
198         int dx = Cos(pSprite->ang) >> 16;
199         int dy = Sin(pSprite->ang) >> 16;
200         int dz = nZOffset1 - nZOffset2;
201 
202         if (!playGenDudeSound(pSprite, kGenDudeSndAttackMelee))
203             sfxPlay3DSound(pSprite, 530, 1, 0);
204 
205         actFireVector(pSprite, 0, 0, dx, dy, dz,kVectorGenDudePunch);
206     }
207 }
208 
genDudeAttack1(int,int nXIndex)209 static void genDudeAttack1(int, int nXIndex) {
210     if (!(nXIndex >= 0 && nXIndex < kMaxXSprites)) {
211         consoleSysMsg("nXIndex >= 0 && nXIndex < kMaxXSprites");
212         return;
213     }
214 
215     XSPRITE* pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference;
216     if (pXSprite->target < 0) return;
217     else if (!(nSprite >= 0 && nSprite < kMaxSprites)) {
218         consoleSysMsg("nIndex >= 0 && nIndex < kMaxSprites");
219         return;
220     }
221 
222     int dx, dy, dz; spritetype* pSprite = &sprite[nSprite];
223     xvel[pSprite->index] = yvel[pSprite->index] = 0;
224 
225     GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
226     short dispersion = pExtra->baseDispersion;
227     if (inDuck(pXSprite->aiState))
228         dispersion = ClipLow(dispersion >> 1, kGenDudeMinDispesion);
229 
230     if (pExtra->weaponType == kGenDudeWeaponHitscan) {
231 
232         dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];
233         // dispersal modifiers here in case if non-melee enemy
234         if (!dudeIsMelee(pXSprite)) {
235             dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion);
236         }
237 
238         actFireVector(pSprite, 0, 0, dx, dy, dz,(VECTOR_TYPE)pExtra->curWeapon);
239         if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
240             sfxPlayVectorSound(pSprite, pExtra->curWeapon);
241 
242     } else if (pExtra->weaponType == kGenDudeWeaponSummon) {
243 
244         spritetype* pSpawned = NULL; int dist = pSprite->clipdist << 4;
245         if (pExtra->slaveCount <= gGameOptions.nDifficulty) {
246             if ((pSpawned = actSpawnDude(pSprite, pExtra->curWeapon, dist + Random(dist), 0)) != NULL) {
247                 pSpawned->owner = nSprite;
248 
249                 if (xspriRangeIsFine(pSpawned->extra)) {
250                     xsprite[pSpawned->extra].target = pXSprite->target;
251                     if (pXSprite->target > -1)
252                         aiActivateDude(pSpawned, &xsprite[pSpawned->extra]);
253                 }
254 
255                 gKillMgr.sub_263E0(1);
256                 pExtra->slave[pExtra->slaveCount++] = pSpawned->index;
257                 if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
258                     sfxPlay3DSoundCP(pSprite, 379, 1, 0, 0x10000 - Random3(0x3000));
259             }
260         }
261 
262     } else if (pExtra->weaponType == kGenDudeWeaponMissile) {
263 
264         dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];
265 
266         // dispersal modifiers here
267         dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion >> 1);
268 
269         actFireMissile(pSprite, 0, 0, dx, dy, dz, pExtra->curWeapon);
270         if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
271             sfxPlayMissileSound(pSprite, pExtra->curWeapon);
272     }
273 }
274 
ThrowCallback1(int,int nXIndex)275 static void ThrowCallback1(int, int nXIndex) {
276     ThrowThing(nXIndex, true);
277 }
278 
ThrowCallback2(int,int nXIndex)279 static void ThrowCallback2(int, int nXIndex) {
280     ThrowThing(nXIndex, false);
281 }
282 
ThrowThing(int nXIndex,bool impact)283 static void ThrowThing(int nXIndex, bool impact) {
284     XSPRITE* pXSprite = &xsprite[nXIndex]; spritetype* pSprite = &sprite[pXSprite->reference];
285 
286     if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
287         return;
288 
289     spritetype * pTarget = &sprite[pXSprite->target];
290     if (!(pTarget->type >= kDudeBase && pTarget->type < kDudeMax))
291         return;
292 
293     short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
294     short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
295     if (weaponType != kGenDudeWeaponThrow) return;
296 
297     THINGINFO* pThinkInfo = &thingInfo[curWeapon - kThingBase];
298     if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return;
299     else if (!playGenDudeSound(pSprite, kGenDudeSndAttackThrow))
300         sfxPlay3DSound(pSprite, 455, -1, 0);
301 
302     int zThrow = 14500;
303     int dx = pTarget->x - pSprite->x;
304     int dy = pTarget->y - pSprite->y;
305     int dz = pTarget->z - pSprite->z;
306     int dist = approxDist(dx, dy);
307 
308     spritetype* pLeech = leechIsDropped(pSprite);
309     XSPRITE* pXLeech = (pLeech != NULL) ? &xsprite[pLeech->extra] : NULL;
310 
311     switch (curWeapon) {
312         case kModernThingEnemyLifeLeech:
313         case kThingDroppedLifeLeech:
314             zThrow = 5000;
315             // pickup life leech before throw it again
316             if (pLeech != NULL) removeLeech(pLeech);
317             break;
318     }
319 
320     spritetype* pThing = NULL;
321     if ((pThing = actFireThing(pSprite, 0, 0, (dz / 128) - zThrow, curWeapon, divscale(dist / 540, 120, 23))) == NULL) return;
322     else if (pThinkInfo->picnum < 0 && pThing->type != kModernThingThrowableRock) pThing->picnum = 0;
323 
324     pThing->owner = pSprite->index;
325 
326     switch (curWeapon) {
327         case kThingNapalmBall:
328             pThing->xrepeat = pThing->yrepeat = 24;
329             xsprite[pThing->extra].data4 = 3 + gGameOptions.nDifficulty;
330             impact = true;
331             break;
332         case kModernThingThrowableRock:
333             int sPics[6];
334             sPics[0] = 2406;	sPics[1] = 2280;
335             sPics[2] = 2185;	sPics[3] = 2155;
336             sPics[4] = 2620;	sPics[5] = 3135;
337 
338             pThing->picnum = sPics[Random(5)];
339             pThing->xrepeat = pThing->yrepeat = 24 + Random(42);
340             pThing->cstat |= 0x0001;
341             pThing->pal = 5;
342 
343             if (Chance(0x5000)) pThing->cstat |= 0x0004;
344             if (Chance(0x5000)) pThing->cstat |= 0x0008;
345 
346             if (pThing->xrepeat > 60) xsprite[pThing->extra].data1 = 43;
347             else if (pThing->xrepeat > 40) xsprite[pThing->extra].data1 = 33;
348             else if (pThing->xrepeat > 30) xsprite[pThing->extra].data1 = 23;
349             else xsprite[pThing->extra].data1 = 12;
350             return;
351         case kThingTNTBarrel:
352         case kThingArmedProxBomb:
353         case kThingArmedSpray:
354             impact = false;
355             break;
356         case kModernThingTNTProx:
357             xsprite[pThing->extra].state = 0;
358             xsprite[pThing->extra].Proximity = true;
359             return;
360         case kModernThingEnemyLifeLeech:
361             XSPRITE* pXThing = &xsprite[pThing->extra];
362             if (pLeech != NULL) pXThing->health = pXLeech->health;
363             else pXThing->health = ((pThinkInfo->startHealth << 4) * gGameOptions.nDifficulty) >> 1;
364 
365             sfxPlay3DSound(pSprite, 490, -1, 0);
366 
367             pXThing->data3 = 512 / (gGameOptions.nDifficulty + 1);
368             pThing->cstat &= ~CSTAT_SPRITE_BLOCK;
369             pThing->pal = 6;
370             pThing->clipdist = 0;
371             pXThing->target = pTarget->index;
372             pXThing->Proximity = true;
373             pXThing->stateTimer = 1;
374 
375             gGenDudeExtra[pSprite->index].nLifeLeech = pThing->index;
376             evPost(pThing->index, 3, 80, kCallbackLeechStateTimer);
377             return;
378     }
379 
380     if (impact == true && dist <= 7680) xsprite[pThing->extra].Impact = true;
381     else {
382         xsprite[pThing->extra].Impact = false;
383         evPost(pThing->index, 3, 120 * Random(2) + 120, kCmdOn);
384     }
385 }
386 
thinkSearch(spritetype * pSprite,XSPRITE * pXSprite)387 static void thinkSearch( spritetype* pSprite, XSPRITE* pXSprite ) {
388 
389     // TO DO: if can't see the target, but in fireDist range - stop moving and look around
390 
391     //viewSetSystemMessage("IN SEARCH");
392     aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
393     sub_5F15C(pSprite, pXSprite);
394 }
395 
thinkGoto(spritetype * pSprite,XSPRITE * pXSprite)396 static void thinkGoto(spritetype* pSprite, XSPRITE* pXSprite) {
397 
398     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
399         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
400         return;
401     }
402 
403     int dx = pXSprite->targetX - pSprite->x;
404     int dy = pXSprite->targetY - pSprite->y;
405     int nAngle = getangle(dx, dy);
406 
407     aiChooseDirection(pSprite, pXSprite, nAngle);
408 
409     // if reached target, change to search mode
410     if (approxDist(dx, dy) < 5120 && klabs(pSprite->ang - nAngle) < getDudeInfo(pSprite->type)->periphery) {
411         if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
412         else aiGenDudeNewState(pSprite, &genDudeSearchL);
413     }
414     aiThinkTarget(pSprite, pXSprite);
415 }
416 
thinkChase(spritetype * pSprite,XSPRITE * pXSprite)417 static void thinkChase( spritetype* pSprite, XSPRITE* pXSprite ) {
418 
419     if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax) return;
420     else if (pXSprite->target < 0 || pXSprite->target >= kMaxSprites) {
421         if(spriteIsUnderwater(pSprite,false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
422         else aiGenDudeNewState(pSprite, &genDudeGotoL);
423         return;
424     } else {
425 
426         genDudeUpdate(pSprite);
427 
428     }
429 
430     spritetype* pTarget = &sprite[pXSprite->target];
431     XSPRITE* pXTarget = (!IsDudeSprite(pTarget) || !xspriRangeIsFine(pTarget->extra)) ? NULL : &xsprite[pTarget->extra];
432 
433     if (pXTarget == NULL) {  // target lost
434         if(spriteIsUnderwater(pSprite,false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
435         else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
436         pXSprite->target = -1;
437         return;
438 
439     } else if (pXTarget->health <= 0) { // target is dead
440         PLAYER* pPlayer = NULL;
441         if ((!IsPlayerSprite(pTarget)) || ((pPlayer = getPlayerById(pTarget->type)) != NULL && pPlayer->fraggerId == pSprite->index)) {
442             playGenDudeSound(pSprite, kGenDudeSndTargetDead);
443             if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
444             else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
445         }
446         else if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
447         else aiGenDudeNewState(pSprite, &genDudeGotoL);
448         pXSprite->target = -1;
449         return;
450     }
451 
452     // check target
453     int dx = pTarget->x - pSprite->x; int dy = pTarget->y - pSprite->y;
454     int dist = ClipLow((int)approxDist(dx, dy), 1);
455 
456     // quick hack to prevent spinning around or changing attacker's sprite angle on high movement speeds
457     // when attacking the target. It happens because vanilla function takes in account x and y velocity,
458     // so i use fake velocity with fixed value and pass it as argument.
459     int xvelocity = xvel[pSprite->index]; int yvelocity = yvel[pSprite->index];
460     if (inAttack(pXSprite->aiState))
461        xvelocity = yvelocity = ClipLow(pSprite->clipdist >> 1, 1);
462 
463     //aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
464     aiGenDudeChooseDirection(pSprite, pXSprite, getangle(dx, dy), xvelocity, yvelocity);
465 
466     GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
467     if (!pExtra->canAttack) {
468         if (pExtra->canWalk) aiSetTarget(pXSprite, pSprite->index);
469         if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
470         else aiGenDudeNewState(pSprite, &genDudeGotoL);
471         return;
472     } else if (IsPlayerSprite(pTarget)) {
473         PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
474         if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0)  {
475             if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
476             else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
477             pXSprite->target = -1;
478             return;
479         }
480     }
481 
482     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
483     int losAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
484     int eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;
485 
486     if (dist > pDudeInfo->seeDist || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
487         pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)) {
488 
489         if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
490         else aiGenDudeNewState(pSprite, &genDudeSearchL);
491         pXSprite->target = -1;
492         return;
493     }
494 
495     // is the target visible?
496     if (dist < pDudeInfo->seeDist && klabs(losAngle) <= pDudeInfo->periphery) {
497 
498         if (((int)gFrameClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(pSprite, false))
499             playGenDudeSound(pSprite, kGenDudeSndChasing);
500 
501         gDudeSlope[pSprite->extra] = divscale(pTarget->z - pSprite->z, dist, 10);
502 
503         short curWeapon = gGenDudeExtra[pSprite->index].curWeapon; short weaponType = gGenDudeExtra[pSprite->index].weaponType;
504         spritetype* pLeech = leechIsDropped(pSprite); VECTORDATA* meleeVector = &gVectorData[22];
505         if (weaponType == kGenDudeWeaponThrow) {
506             if (klabs(losAngle) < kAng15) {
507                 if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) {
508                     if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
509                     else aiGenDudeNewState(pSprite, &genDudeChaseL);
510                     return;
511 
512                 } else if (dist < 12264 && dist > 7680 && !spriteIsUnderwater(pSprite, false) && curWeapon != kModernThingEnemyLifeLeech) {
513                     int pHit = HitScan(pSprite, pSprite->z, dx, dy, 0, 16777280, 0);
514                     switch (pHit) {
515                         case 0:
516                         case 4:
517                             return;
518                         default:
519                             aiGenDudeNewState(pSprite, &genDudeThrow);
520                             return;
521                     }
522 
523                 } else if (dist > 4072 && dist <= 11072 && !spriteIsUnderwater(pSprite, false) && pSprite->owner != (kMaxSprites - 1)) {
524                     switch (curWeapon) {
525                         case kModernThingEnemyLifeLeech: {
526                             if (pLeech == NULL) {
527                                 aiGenDudeNewState(pSprite, &genDudeThrow2);
528                                 genDudeThrow2.nextState = &genDudeDodgeShortL;
529                                 return;
530                             }
531 
532                             XSPRITE* pXLeech = &xsprite[pLeech->extra];
533                             int ldist = aiFightGetTargetDist(pTarget, pDudeInfo, pLeech);
534                             if (ldist > 3 || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
535                                 pLeech->x, pLeech->y, pLeech->z, pLeech->sectnum) || pXLeech->target == -1) {
536 
537                                 aiGenDudeNewState(pSprite, &genDudeThrow2);
538                                 genDudeThrow2.nextState = &genDudeDodgeShortL;
539 
540                             } else {
541 
542                                 genDudeThrow2.nextState = &genDudeChaseL;
543                                 if (dist > 5072 && Chance(0x5000)) {
544                                     if (!canDuck(pSprite) || Chance(0x4000)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL);
545                                     else aiGenDudeNewState(pSprite, &genDudeDodgeShortD);
546                                 } else {
547                                     aiGenDudeNewState(pSprite, &genDudeChaseL);
548                                 }
549 
550                             }
551                         }
552                         return;
553                         case kModernThingThrowableRock:
554                             if (Chance(0x4000)) aiGenDudeNewState(pSprite, &genDudeThrow2);
555                             else playGenDudeSound(pSprite, kGenDudeSndTargetSpot);
556                             return;
557                         default:
558                             aiGenDudeNewState(pSprite, &genDudeThrow2);
559                             return;
560                     }
561 
562                 } else if (dist <= meleeVector->maxDist) {
563 
564                     if (spriteIsUnderwater(pSprite, false)) {
565                         if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
566                         else aiGenDudeNewState(pSprite, &genDudeDodgeW);
567 
568                     }
569                     else if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
570                     else aiGenDudeNewState(pSprite, &genDudeDodgeL);
571                     return;
572 
573                 } else {
574                     int state = checkAttackState(pSprite, pXSprite);
575                     if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
576                     else if (state == 2) {
577                         if (Chance(0x5000)) aiGenDudeNewState(pSprite, &genDudeChaseD);
578                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
579                     }
580                     else  aiGenDudeNewState(pSprite, &genDudeChaseL);
581                     return;
582                 }
583             }
584 
585         } else {
586 
587             int vdist; int mdist; int defDist;
588             defDist = vdist = mdist = gGenDudeExtra[pSprite->index].fireDist;
589 
590             if (weaponType == kGenDudeWeaponHitscan) {
591                 if ((vdist = gVectorData[curWeapon].maxDist) <= 0)
592                     vdist = defDist;
593 
594             } else if (weaponType == kGenDudeWeaponSummon) {
595 
596                 // don't attack slaves
597                 if (pXSprite->target >= 0 && sprite[pXSprite->target].owner == pSprite->index) {
598                     aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
599                     return;
600                 } else if (gGenDudeExtra[pSprite->index].slaveCount > gGameOptions.nDifficulty || dist < meleeVector->maxDist) {
601                     if (dist <= meleeVector->maxDist) {
602                         aiGenDudeNewState(pSprite, &genDudePunch);
603                         return;
604                     } else {
605                         int state = checkAttackState(pSprite, pXSprite);
606                         if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
607                         else if (state == 2) aiGenDudeNewState(pSprite, &genDudeChaseD);
608                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
609                         return;
610                     }
611                 }
612 
613             } else if (weaponType == kGenDudeWeaponMissile) {
614                 // special handling for flame, explosive and life leech missiles
615                 int state = checkAttackState(pSprite, pXSprite);
616                 switch (curWeapon) {
617                     case kMissileLifeLeechRegular:
618                         // pickup life leech if it was thrown previously
619                         if (pLeech != NULL) removeLeech(pLeech);
620                         mdist = 1500;
621                         break;
622                     case kMissileFlareAlt:
623                         mdist = 2500;
624                         fallthrough__;
625                     case kMissileFireball:
626                     case kMissileFireballNapam:
627                     case kMissileFireballCerberus:
628                     case kMissileFireballTchernobog:
629                         if (mdist == defDist) mdist = 3000;
630                         if (dist > mdist || pXSprite->locked == 1) break;
631                         else if (dist <= meleeVector->maxDist && Chance(0x9000))
632                             aiGenDudeNewState(pSprite, &genDudePunch);
633                         else if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
634                         else if (state == 2) aiGenDudeNewState(pSprite, &genDudeChaseD);
635                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
636                         return;
637                     case kMissileFlameSpray:
638                     case kMissileFlameHound:
639                         //viewSetSystemMessage("%d", pXTarget->burnTime);
640                         if (spriteIsUnderwater(pSprite, false)) {
641                             if (dist > meleeVector->maxDist) aiGenDudeNewState(pSprite, &genDudeChaseW);
642                             else if (Chance(0x8000)) aiGenDudeNewState(pSprite, &genDudePunch);
643                             else aiGenDudeNewState(pSprite, &genDudeDodgeShortW);
644                             return;
645                         } else if (dist <= 4000 && pXTarget->burnTime >= 2000 && pXTarget->burnSource == pSprite->index) {
646                             if (dist > meleeVector->maxDist) aiGenDudeNewState(pSprite, &genDudeChaseL);
647                             else aiGenDudeNewState(pSprite, &genDudePunch);
648                             return;
649                         }
650                         vdist = 3500 + (gGameOptions.nDifficulty * 400);
651                         break;
652                 }
653             } else if (weaponType == kGenDudeWeaponKamikaze) {
654                 int nType = curWeapon - kTrapExploder; EXPLOSION* pExpl = &explodeInfo[nType];
655                 if (CheckProximity(pSprite, pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pExpl->radius >> 1)) {
656                     xvel[pSprite->index] = zvel[pSprite->index] = yvel[pSprite->index] = 0;
657                     if (doExplosion(pSprite, nType) && pXSprite->health > 0)
658                             actDamageSprite(pSprite->index, pSprite, kDamageExplode, 65535);
659                 }
660                 return;
661             }
662 
663             int state = checkAttackState(pSprite, pXSprite);
664             int kAngle = (dudeIsMelee(pXSprite) || dist <= kGenDudeMaxMeleeDist) ? pDudeInfo->periphery : kGenDudeKlabsAng;
665 
666             if (dist < vdist && klabs(losAngle) < kAngle) {
667                 if (pExtra->canWalk) {
668                     int objDist = -1; int targetDist = -1; int hit = -1;
669                     if (weaponType == kGenDudeWeaponHitscan)
670                         hit = HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], CLIPMASK1, dist);
671                     else if (weaponType == kGenDudeWeaponMissile)
672                         hit = HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], CLIPMASK0, dist);
673 
674                     if (hit >= 0) {
675                         targetDist = dist - (pTarget->clipdist << 2);
676                         objDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
677                     }
678 
679                     if (pXSprite->target != gHitInfo.hitsprite && targetDist > objDist) {
680                         walltype* pHWall = NULL; XWALL* pXHWall = NULL;
681                         spritetype* pHSprite = NULL; XSPRITE* pXHSprite = NULL;
682                         bool hscn = false; bool blck = false; bool failed = false;
683 
684                         switch (hit) {
685                         case 3:
686                             pHSprite = &sprite[gHitInfo.hitsprite];
687                             if (xspriRangeIsFine(pHSprite->extra)) pXHSprite = &xsprite[pHSprite->extra];
688                             hscn = (pHSprite->cstat & CSTAT_SPRITE_BLOCK_HITSCAN); blck = (pHSprite->cstat & CSTAT_SPRITE_BLOCK);
689                             break;
690                         case 0:
691                         case 4:
692                             pHWall = &wall[gHitInfo.hitwall];
693                             if (xwallRangeIsFine(pHWall->extra)) pXHWall = &xwall[pHWall->extra];
694                             hscn = (pHWall->cstat & CSTAT_WALL_BLOCK_HITSCAN); blck = (pHWall->cstat & CSTAT_WALL_BLOCK);
695                             break;
696                         }
697 
698                         switch (hit) {
699                         case 0:
700                             //if (hit == 0) viewSetSystemMessage("WALL HIT %d", gHitInfo.hitwall);
701                             fallthrough__;
702                         case 1:
703                             //if (hit == 1) viewSetSystemMessage("CEIL HIT %d", gHitInfo.hitsect);
704                             fallthrough__;
705                         case 2:
706                             //if (hit == 2) viewSetSystemMessage("FLOOR HIT %d", gHitInfo.hitsect);
707                             if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(pSprite, pXSprite, dist, weaponType)
708                                 && dist < (int)(6000 + Random(2000)) && pExtra->baseDispersion < kGenDudeMaxDispersion >> 1) break;
709 
710                             else if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
711                             else aiGenDudeNewState(pSprite, &genDudeChaseL);
712                             return;
713                         case 3:
714                             if (pHSprite->statnum == kStatFX || pHSprite->statnum == kStatProjectile || pHSprite->statnum == kStatDebris)
715                                 break;
716                             if (IsDudeSprite(pHSprite) && (weaponType != kGenDudeWeaponHitscan || hscn)) {
717                                 // dodge a bit in sides
718                                 if (pXHSprite->target != pSprite->index) {
719                                     if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile) {
720                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterW);
721                                         else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterD);
722                                         else aiGenDudeNewState(pSprite, &genDudeDodgeShorterL);
723                                     }
724                                     else if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShortW);
725                                     else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShortD);
726                                     else aiGenDudeNewState(pSprite, &genDudeDodgeShortL);
727 
728                                     switch (pHSprite->type) {
729                                         case kDudeModernCustom: // and make dude which could be hit to dodge too
730                                             if (!dudeIsMelee(pXHSprite) && Chance(dist << 4)) {
731                                                 if (!inAttack(pXHSprite->aiState)) {
732                                                     if (spriteIsUnderwater(pHSprite)) aiGenDudeNewState(pHSprite, &genDudeDodgeShorterW);
733                                                     else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pHSprite, &genDudeDodgeShorterD);
734                                                     else aiGenDudeNewState(pHSprite, &genDudeDodgeShorterL);
735 
736                                                     // preferable in opposite sides
737                                                     if (Chance(0x8000)) {
738                                                         if (pXSprite->dodgeDir == 1) pXHSprite->dodgeDir = -1;
739                                                         else if (pXSprite->dodgeDir == -1) pXHSprite->dodgeDir = 1;
740                                                     }
741                                                     break;
742                                                 }
743                                                 if (pSprite->x < pHSprite->x) {
744                                                     if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
745                                                     else pXSprite->dodgeDir = 1;
746                                                 } else {
747                                                     if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
748                                                     else pXSprite->dodgeDir = -1;
749                                                 }
750                                             }
751                                             break;
752                                         default:
753                                             if (pSprite->x < pHSprite->x) {
754                                                 if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
755                                                 else pXSprite->dodgeDir = 1;
756                                             } else {
757                                                 if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
758                                                 else pXSprite->dodgeDir = -1;
759                                             }
760                                             break;
761                                     }
762                                     return;
763                                 }
764                                 break;
765                             } else if (weaponType == kGenDudeWeaponHitscan && hscn) {
766                                 if (genDudeAdjustSlope(pSprite, pXSprite, dist, weaponType)) break;
767                                 VectorScan(pSprite, 0, 0, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], dist, 1);
768                                 if (pXSprite->target == gHitInfo.hitsprite) break;
769 
770                                 bool immune = nnExtIsImmune(pHSprite, gVectorData[curWeapon].dmgType);
771                                 if (!(pXHSprite != NULL && (!immune || (immune && pHSprite->statnum == kStatThing && pXHSprite->Vector)) && !pXHSprite->locked)) {
772 
773                                     if ((approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y) <= 1500 && !blck)
774                                         || (dist <= (int)(pExtra->fireDist / ClipLow(Random(4), 1)))) {
775 
776                                         //viewSetSystemMessage("GO CHASE");
777                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
778                                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
779                                         return;
780 
781                                     }
782 
783                                     int wd1 = picWidth(pHSprite->picnum, pHSprite->xrepeat);
784                                     int wd2 = picWidth(pSprite->picnum, pSprite->xrepeat);
785                                     if (wd1 < (wd2 << 3)) {
786                                         //viewSetSystemMessage("OBJ SIZE: %d   DUDE SIZE: %d", wd1, wd2);
787                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterW);
788                                         else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterD);
789                                         else aiGenDudeNewState(pSprite, &genDudeDodgeShorterL);
790 
791                                         if (pSprite->x < pHSprite->x) {
792                                             if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
793                                             else pXSprite->dodgeDir = 1;
794                                         } else {
795                                             if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
796                                             else pXSprite->dodgeDir = -1;
797                                         }
798 
799                                         if (((gSpriteHit[pSprite->extra].hit & 0xc000) == 0x8000) || ((gSpriteHit[pSprite->extra].hit & 0xc000) == 0xc000)) {
800                                             if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
801                                             else aiGenDudeNewState(pSprite, &genDudeChaseL);
802                                             pXSprite->goalAng = Random(kAng360);
803                                             //viewSetSystemMessage("WALL OR SPRITE TOUCH");
804                                         }
805 
806                                     } else {
807                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
808                                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
809                                         //viewSetSystemMessage("TOO BIG OBJECT TO DODGE!!!!!!!!");
810                                     }
811                                     return;
812                                 }
813                                 break;
814                             }
815                             fallthrough__;
816                         case 4:
817                             if (hit == 4 && weaponType == kGenDudeWeaponHitscan && hscn) {
818                                 bool masked = (pHWall->cstat & CSTAT_WALL_MASKED);
819                                 if (masked) VectorScan(pSprite, 0, 0, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], dist, 1);
820 
821                                 //viewSetSystemMessage("WALL VHIT: %d", gHitInfo.hitwall);
822                                 if ((pXSprite->target != gHitInfo.hitsprite) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked)) {
823                                     if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
824                                     else aiGenDudeNewState(pSprite, &genDudeChaseL);
825                                     return;
826                                 }
827                             } else if (hit >= 3 && weaponType == kGenDudeWeaponMissile && blck) {
828                                 switch (curWeapon) {
829                                 case kMissileLifeLeechRegular:
830                                 case kMissileTeslaAlt:
831                                 case kMissileFlareAlt:
832                                 case kMissileFireball:
833                                 case kMissileFireballNapam:
834                                 case kMissileFireballCerberus:
835                                 case kMissileFireballTchernobog: {
836                                     // allow attack if dude is far from object, but target is close to it
837                                     int dudeDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
838                                     int targetDist = approxDist(gHitInfo.hitx - pTarget->x, gHitInfo.hity - pTarget->y);
839                                     if (dudeDist < mdist) {
840                                         //viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist);
841                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
842                                         else aiGenDudeNewState(pSprite, &genDudeChaseL);
843                                         return;
844                                     } else if (targetDist <= mdist >> 1) {
845                                         //viewSetSystemMessage("TARGET CLOSE TO OBJ: %d, MDIST: %d", targetDist, mdist >> 1);
846                                         break;
847                                     }
848                                     fallthrough__;
849                                 }
850                                 default:
851                                     //viewSetSystemMessage("DEF HIT: %d, MDIST: %d", hit, mdist);
852                                     if (hit == 4) failed = (pHWall->type != kWallGib || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked);
853                                     else if (hit == 3 && (failed = (pHSprite->statnum != kStatThing || pXHSprite == NULL || pXHSprite->locked)) == false) {
854                                         // check also for damage resistance (all possible damages missile can use)
855                                         for (int i = 0; i < kDmgMax; i++) {
856                                             if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(pHSprite, i)) == false)
857                                                 break;
858                                         }
859                                     }
860 
861                                     if (failed) {
862                                         if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeSearchW);
863                                         else aiGenDudeNewState(pSprite, &genDudeSearchL);
864                                         return;
865                                     }
866                                     break;
867                                 }
868                             }
869                             break;
870                         }
871                     }
872                 }
873 
874                 aiSetTarget(pXSprite, pXSprite->target);
875                 switch (state) {
876                     case 1:
877                         aiGenDudeNewState(pSprite, &genDudeFireW);
878                         pXSprite->aiState->nextState = &genDudeFireW;
879                         break;
880                     case 2:
881                         aiGenDudeNewState(pSprite, &genDudeFireD);
882                         pXSprite->aiState->nextState = &genDudeFireD;
883                         break;
884                     default:
885                         aiGenDudeNewState(pSprite, &genDudeFireL);
886                         pXSprite->aiState->nextState = &genDudeFireL;
887                         break;
888                 }
889 
890 
891             } else {
892 
893                 if (seqGetID(3, pSprite->extra) == pXSprite->data2 + ((state < 3) ? 8 : 6)) {
894                     if (state == 1) pXSprite->aiState->nextState = &genDudeChaseW;
895                     else if (state == 2) pXSprite->aiState->nextState = &genDudeChaseD;
896                     else pXSprite->aiState->nextState = &genDudeChaseL;
897 
898                 } else if (state == 1 && pXSprite->aiState != &genDudeChaseW && pXSprite->aiState != &genDudeFireW) {
899                     aiGenDudeNewState(pSprite, &genDudeChaseW);
900                     pXSprite->aiState->nextState = &genDudeFireW;
901 
902                 } else if (state == 2 && pXSprite->aiState != &genDudeChaseD && pXSprite->aiState != &genDudeFireD) {
903                     aiGenDudeNewState(pSprite, &genDudeChaseD);
904                     pXSprite->aiState->nextState = &genDudeFireD;
905 
906                 } else if (pXSprite->aiState != &genDudeChaseL && pXSprite->aiState != &genDudeFireL) {
907                     aiGenDudeNewState(pSprite, &genDudeChaseL);
908                     pXSprite->aiState->nextState = &genDudeFireL;
909                 }
910 
911             }
912         }
913     }
914 }
915 
916 
checkAttackState(spritetype * pSprite,XSPRITE * pXSprite)917 int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite) {
918     UNREFERENCED_PARAMETER(pXSprite);
919     if (sub_5BDA8(pSprite, 14) || spriteIsUnderwater(pSprite,false))
920     {
921         if ( !sub_5BDA8(pSprite, 14) || spriteIsUnderwater(pSprite,false))
922         {
923             if (spriteIsUnderwater(pSprite,false))
924             {
925                 return 1; //water
926             }
927         }
928         else
929         {
930             return 2; //duck
931         }
932     }
933     else
934     {
935         return 3; //land
936     }
937     return 0;
938 }
939 
940 ///// For gen dude
getGenDudeMoveSpeed(spritetype * pSprite,int which,bool mul,bool shift)941 int getGenDudeMoveSpeed(spritetype* pSprite,int which, bool mul, bool shift) {
942     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
943     XSPRITE* pXSprite = &xsprite[pSprite->extra];
944     int speed = -1; int step = 2500; int maxSpeed = 146603;
945     switch(which){
946         case 0:
947             speed = pDudeInfo->frontSpeed;
948             break;
949         case 1:
950             speed = pDudeInfo->sideSpeed;
951             break;
952         case 2:
953             speed = pDudeInfo->backSpeed;
954             break;
955         case 3:
956             speed = pDudeInfo->angSpeed;
957             break;
958         default:
959             return -1;
960     }
961     if (pXSprite->busyTime > 0) speed /=3;
962     if (speed > 0 && mul) {
963 
964 
965         if (pXSprite->busyTime > 0)
966             speed += (step * pXSprite->busyTime);
967     }
968 
969     if (shift) speed *= 4 >> 4;
970     if (speed > maxSpeed) speed = maxSpeed;
971 
972     return speed;
973 }
974 
aiGenDudeMoveForward(spritetype * pSprite,XSPRITE * pXSprite)975 void aiGenDudeMoveForward(spritetype* pSprite, XSPRITE* pXSprite ) {
976     DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
977     GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
978     int maxTurn = pDudeInfo->angSpeed * 4 >> 4;
979 
980 
981     if (pExtra->canFly) {
982         int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
983         int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
984         pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
985         int nAccel = pDudeInfo->frontSpeed << 2;
986         if (klabs(nAng) > 341)
987             return;
988         if (pXSprite->target == -1)
989             pSprite->ang = (pSprite->ang + 256) & 2047;
990         int dx = pXSprite->targetX - pSprite->x;
991         int dy = pXSprite->targetY - pSprite->y;
992         int UNUSED(nAngle) = getangle(dx, dy);
993         int nDist = approxDist(dx, dy);
994         if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
995             return;
996         int nCos = Cos(pSprite->ang);
997         int nSin = Sin(pSprite->ang);
998         int vx = xvel[pSprite->index];
999         int vy = yvel[pSprite->index];
1000         int t1 = dmulscale30(vx, nCos, vy, nSin);
1001         int t2 = dmulscale30(vx, nSin, -vy, nCos);
1002         if (pXSprite->target == -1)
1003             t1 += nAccel;
1004         else
1005             t1 += nAccel >> 1;
1006         xvel[pSprite->index] = dmulscale30(t1, nCos, t2, nSin);
1007         yvel[pSprite->index] = dmulscale30(t1, nSin, -t2, nCos);
1008     } else {
1009         int dang = ((kAng180 + pXSprite->goalAng - pSprite->ang) & 2047) - kAng180;
1010         pSprite->ang = ((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & 2047);
1011 
1012         // don't move forward if trying to turn around
1013         if (klabs(dang) > kAng60)
1014             return;
1015 
1016         int sin = Sin(pSprite->ang);
1017         int cos = Cos(pSprite->ang);
1018 
1019         int frontSpeed = gGenDudeExtra[pSprite->index].moveSpeed;
1020         xvel[pSprite->index] += mulscale(cos, frontSpeed, 30);
1021         yvel[pSprite->index] += mulscale(sin, frontSpeed, 30);
1022     }
1023 }
1024 
aiGenDudeChooseDirection(spritetype * pSprite,XSPRITE * pXSprite,int a3,int xvel,int yvel)1025 void aiGenDudeChooseDirection(spritetype* pSprite, XSPRITE* pXSprite, int a3, int xvel, int yvel) {
1026     if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
1027         consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
1028         return;
1029     }
1030 
1031     // TO-DO: Take in account if sprite is flip-x, so enemy select correct angle
1032 
1033     int vc = ((a3 + 1024 - pSprite->ang) & 2047) - 1024;
1034     int t1 = dmulscale30(xvel, Cos(pSprite->ang), yvel, Sin(pSprite->ang));
1035     int vsi = ((t1 * 15) >> 12) / 2; int v8 = (vc >= 0) ? 341 : -341;
1036 
1037     if (CanMove(pSprite, pXSprite->target, pSprite->ang + vc, vsi))
1038         pXSprite->goalAng = pSprite->ang + vc;
1039     else if (CanMove(pSprite, pXSprite->target, pSprite->ang + vc / 2, vsi))
1040         pXSprite->goalAng = pSprite->ang + vc / 2;
1041     else if (CanMove(pSprite, pXSprite->target, pSprite->ang - vc / 2, vsi))
1042         pXSprite->goalAng = pSprite->ang - vc / 2;
1043     else if (CanMove(pSprite, pXSprite->target, pSprite->ang + v8, vsi))
1044         pXSprite->goalAng = pSprite->ang + v8;
1045     else if (CanMove(pSprite, pXSprite->target, pSprite->ang, vsi))
1046         pXSprite->goalAng = pSprite->ang;
1047     else if (CanMove(pSprite, pXSprite->target, pSprite->ang - v8, vsi))
1048         pXSprite->goalAng = pSprite->ang - v8;
1049     else
1050         pXSprite->goalAng = pSprite->ang + 341;
1051 
1052     pXSprite->dodgeDir = (Chance(0x8000)) ? 1 : -1;
1053 
1054     if (!CanMove(pSprite, pXSprite->target, pSprite->ang + pXSprite->dodgeDir * 512, 512)) {
1055         pXSprite->dodgeDir = -pXSprite->dodgeDir;
1056         if (!CanMove(pSprite, pXSprite->target, pSprite->ang + pXSprite->dodgeDir * 512, 512))
1057             pXSprite->dodgeDir = 0;
1058     }
1059 }
1060 
aiGenDudeNewState(spritetype * pSprite,AISTATE * pAIState)1061 void aiGenDudeNewState(spritetype* pSprite, AISTATE* pAIState) {
1062     if (!xspriRangeIsFine(pSprite->extra)) {
1063         consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
1064         return;
1065     }
1066 
1067     XSPRITE* pXSprite = &xsprite[pSprite->extra];
1068 
1069     // redirect dudes which cannot walk to non-walk states
1070     if (!gGenDudeExtra[pSprite->index].canWalk) {
1071 
1072         if (pAIState == &genDudeDodgeL || pAIState == &genDudeDodgeShortL || pAIState == &genDudeDodgeShorterL)
1073             pAIState = &genDudeRecoilL;
1074 
1075         else if (pAIState == &genDudeDodgeD || pAIState == &genDudeDodgeShortD || pAIState == &genDudeDodgeShorterD)
1076             pAIState = &genDudeRecoilD;
1077 
1078         else if (pAIState == &genDudeDodgeW || pAIState == &genDudeDodgeShortW || pAIState == &genDudeDodgeShorterW)
1079             pAIState = &genDudeRecoilW;
1080 
1081         else if (pAIState == &genDudeSearchL || pAIState == &genDudeSearchShortL)
1082             pAIState = &genDudeSearchNoWalkL;
1083 
1084         else if (pAIState == &genDudeSearchW || pAIState == &genDudeSearchShortW)
1085             pAIState = &genDudeSearchNoWalkW;
1086 
1087         else if (pAIState == &genDudeGotoL) pAIState = &genDudeIdleL;
1088         else if (pAIState == &genDudeGotoW) pAIState = &genDudeIdleW;
1089         else if (pAIState == &genDudeChaseL) pAIState = &genDudeChaseNoWalkL;
1090         else if (pAIState == &genDudeChaseD) pAIState = &genDudeChaseNoWalkD;
1091         else if (pAIState == &genDudeChaseW) pAIState = &genDudeChaseNoWalkW;
1092         else if (pAIState == &genDudeRecoilTesla) {
1093 
1094             if (spriteIsUnderwater(pSprite, false)) pAIState = &genDudeRecoilW;
1095             else pAIState = &genDudeRecoilL;
1096 
1097         }
1098 
1099     }
1100 
1101     if (!gGenDudeExtra[pSprite->index].canRecoil) {
1102         if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL;
1103         else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW;
1104     }
1105 
1106     pXSprite->stateTimer = pAIState->stateTicks; pXSprite->aiState = pAIState;
1107 
1108     int stateSeq = pXSprite->data2 + pAIState->seqId;
1109     if (pAIState->seqId >= 0 && gSysRes.Lookup(stateSeq, "SEQ")) {
1110         seqSpawn(stateSeq, 3, pSprite->extra, pAIState->funcId);
1111     }
1112 
1113     if (pAIState->enterFunc)
1114         pAIState->enterFunc(pSprite, pXSprite);
1115 }
1116 
1117 
playGenDudeSound(spritetype * pSprite,int mode)1118 bool playGenDudeSound(spritetype* pSprite, int mode) {
1119 
1120     if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax) return false;
1121     GENDUDESND* sndInfo =& gCustomDudeSnd[mode]; bool gotSnd = false;
1122     short sndStartId = xsprite[pSprite->extra].sysData1; int rand = sndInfo->randomRange;
1123     int sndId = (sndStartId <= 0) ? sndInfo->defaultSndId : sndStartId + sndInfo->sndIdOffset;
1124     GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
1125 
1126     // let's check if there same sounds already played by other dudes
1127     // so we won't get a lot of annoying screams in the same time and ensure sound played in it's full length (if not interruptable)
1128     if (pExtra->sndPlaying && !sndInfo->interruptable) {
1129         for (int i = 0; i < 256; i++) {
1130             if (Bonkle[i].atc <= 0) continue;
1131             for (int a = 0; a < rand; a++) {
1132                 if (sndId + a == Bonkle[i].atc) {
1133                     if (Bonkle[i].at0 <= 0) {
1134                         pExtra->sndPlaying = false;
1135                         break;
1136                     }
1137                     return true;
1138                 }
1139             }
1140         }
1141 
1142         pExtra->sndPlaying = false;
1143 
1144     }
1145 
1146     if (sndId < 0) return false;
1147     else if (sndStartId <= 0) { sndId += Random(rand); gotSnd = true; }
1148     else {
1149 
1150         // Let's try to get random snd
1151         int maxRetries = 5;
1152         while (maxRetries-- > 0) {
1153             int random = Random(rand);
1154             if (!gSoundRes.Lookup(sndId + random, "SFX")) continue;
1155             sndId = sndId + random;
1156             gotSnd = true;
1157             break;
1158         }
1159 
1160         // If no success in getting random snd, get first existing one
1161         if (gotSnd == false) {
1162             int maxSndId = sndId + rand;
1163             while (sndId++ < maxSndId) {
1164                 if (!gSoundRes.Lookup(sndId, "SFX")) continue;
1165                 gotSnd = true;
1166                 break;
1167             }
1168         }
1169 
1170     }
1171 
1172     if (gotSnd == false) return false;
1173     else if (sndInfo->aiPlaySound) aiPlay3DSound(pSprite, sndId, AI_SFX_PRIORITY_2, -1);
1174     else sfxPlay3DSound(pSprite, sndId, -1, 0);
1175 
1176     pExtra->sndPlaying = true;
1177     return true;
1178 }
1179 
1180 
spriteIsUnderwater(spritetype * pSprite,bool oldWay)1181 bool spriteIsUnderwater(spritetype* pSprite, bool oldWay) {
1182     return ((sector[pSprite->sectnum].extra >= 0 && xsector[sector[pSprite->sectnum].extra].Underwater)
1183         || (oldWay && (xsprite[pSprite->extra].medium == kMediumWater || xsprite[pSprite->extra].medium == kMediumGoo)));
1184 }
1185 
leechIsDropped(spritetype * pSprite)1186 spritetype* leechIsDropped(spritetype* pSprite) {
1187     short nLeech = gGenDudeExtra[pSprite->index].nLifeLeech;
1188     if (nLeech >= 0 && nLeech < kMaxSprites) return &sprite[nLeech];
1189     return NULL;
1190 
1191 }
1192 
removeDudeStuff(spritetype * pSprite)1193 void removeDudeStuff(spritetype* pSprite) {
1194     for (short nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
1195         if (sprite[nSprite].owner != pSprite->index) continue;
1196         switch (sprite[nSprite].type) {
1197             case kThingArmedProxBomb:
1198             case kThingArmedRemoteBomb:
1199             case kModernThingTNTProx:
1200                 sprite[nSprite].type = kSpriteDecoration;
1201                 actPostSprite(sprite[nSprite].index, kStatFree);
1202                 break;
1203             case kModernThingEnemyLifeLeech:
1204                 killDudeLeech(&sprite[nSprite]);
1205                 break;
1206         }
1207     }
1208 
1209     for (short nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
1210         if (sprite[nSprite].owner != pSprite->index) continue;
1211         actDamageSprite(sprite[nSprite].owner, &sprite[nSprite], (DAMAGE_TYPE) 0, 65535);
1212     }
1213 }
1214 
removeLeech(spritetype * pLeech,bool delSprite)1215 void removeLeech(spritetype* pLeech, bool delSprite) {
1216     if (pLeech != NULL) {
1217         spritetype* pEffect = gFX.fxSpawn((FX_ID)52,pLeech->sectnum,pLeech->x,pLeech->y,pLeech->z,pLeech->ang);
1218         if (pEffect != NULL) {
1219             pEffect->cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
1220             pEffect->pal = 6;
1221             int repeat = 64 + Random(50);
1222             pEffect->xrepeat = repeat;
1223             pEffect->yrepeat = repeat;
1224         }
1225 
1226         sfxPlay3DSoundCP(pLeech, 490, -1, 0,60000);
1227 
1228         if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
1229             gGenDudeExtra[sprite[pLeech->owner].index].nLifeLeech = -1;
1230 
1231         if (delSprite) {
1232             pLeech->type = kSpriteDecoration;
1233             actPostSprite(pLeech->index, kStatFree);
1234         }
1235 
1236 
1237     }
1238 }
1239 
killDudeLeech(spritetype * pLeech)1240 void killDudeLeech(spritetype* pLeech) {
1241     if (pLeech != NULL) {
1242         actDamageSprite(pLeech->owner, pLeech, kDamageExplode, 65535);
1243         sfxPlay3DSoundCP(pLeech, 522, -1, 0, 60000);
1244 
1245         if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
1246             gGenDudeExtra[sprite[pLeech->owner].index].nLifeLeech = -1;
1247     }
1248 }
1249 
getNextIncarnation(XSPRITE * pXSprite)1250 XSPRITE* getNextIncarnation(XSPRITE* pXSprite) {
1251     for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
1252         if (rxBucket[i].type != 3 || rxBucket[i].index == pXSprite->reference)
1253             continue;
1254 
1255         if (sprite[rxBucket[i].index].statnum == kStatInactive)
1256             return &xsprite[sprite[rxBucket[i].index].extra];
1257     }
1258     return NULL;
1259 }
1260 
dudeIsMelee(XSPRITE * pXSprite)1261 bool dudeIsMelee(XSPRITE* pXSprite) {
1262     return gGenDudeExtra[sprite[pXSprite->reference].index].isMelee;
1263 }
1264 
scaleDamage(XSPRITE * pXSprite)1265 void scaleDamage(XSPRITE* pXSprite) {
1266 
1267     short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
1268     short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
1269     signed short* curScale = gGenDudeExtra[sprite[pXSprite->reference].index].dmgControl;
1270     for (int i = 0; i < kDmgMax; i++)
1271         curScale[i] = getDudeInfo(kDudeModernCustom)->startDamage[i];
1272 
1273     switch (weaponType) {
1274         // just copy damage resistance of dude that should be summoned
1275         case kGenDudeWeaponSummon:
1276             for (int i = 0; i < kDmgMax; i++)
1277                 curScale[i] = getDudeInfo(curWeapon)->startDamage[i];
1278             break;
1279         // these does not like the explosions and burning
1280         case kGenDudeWeaponKamikaze:
1281             curScale[kDmgBurn] = curScale[kDmgExplode] = curScale[kDmgElectric] = 1024;
1282             break;
1283         case kGenDudeWeaponMissile:
1284         case kGenDudeWeaponThrow:
1285             switch (curWeapon) {
1286                 case kMissileButcherKnife:
1287                     curScale[kDmgBullet] = 100;
1288                     fallthrough__;
1289                 case kMissileEctoSkull:
1290                     curScale[kDmgSpirit] = 32;
1291                     break;
1292                 case kMissileLifeLeechAltNormal:
1293                 case kMissileLifeLeechAltSmall:
1294                 case kMissileArcGargoyle:
1295                     curScale[kDmgSpirit] -= 32;
1296                     curScale[kDmgElectric] = 52;
1297                     break;
1298                 case kMissileFlareRegular:
1299                 case kMissileFlareAlt:
1300                 case kMissileFlameSpray:
1301                 case kMissileFlameHound:
1302                 case kThingArmedSpray:
1303                 case kThingPodFireBall:
1304                 case kThingNapalmBall:
1305                     curScale[kDmgBurn] = 32;
1306                     curScale[kDmgExplode] -= 32;
1307                     break;
1308                 case kMissileLifeLeechRegular:
1309                     curScale[kDmgBurn] = 60 + Random(4);
1310                     fallthrough__;
1311                 case kThingDroppedLifeLeech:
1312                 case kModernThingEnemyLifeLeech:
1313                     curScale[kDmgSpirit] = 32 + Random(18);
1314                     break;
1315                 case kMissileFireball:
1316                 case kMissileFireballNapam:
1317                 case kMissileFireballCerberus:
1318                 case kMissileFireballTchernobog:
1319                     curScale[kDmgBurn] = 50;
1320                     curScale[kDmgExplode] = 32;
1321                     curScale[kDmgFall] = 65 + Random(15);
1322                     break;
1323                 case kThingTNTBarrel:
1324                 case kThingArmedProxBomb:
1325                 case kThingArmedRemoteBomb:
1326                 case kThingArmedTNTBundle:
1327                 case kThingArmedTNTStick:
1328                 case kModernThingTNTProx:
1329                     curScale[kDmgBurn] -= 32;
1330                     curScale[kDmgExplode] = 32;
1331                     curScale[kDmgFall] = 65 + Random(15);
1332                     break;
1333                 case kMissileTeslaAlt:
1334                 case kMissileTeslaRegular:
1335                     curScale[kDmgElectric] = 32 + Random(8);
1336                     break;
1337             }
1338             break;
1339 
1340     }
1341 
1342     // add resistance if have an armor item to drop
1343     if (pXSprite->dropMsg >= kItemArmorAsbest && pXSprite->dropMsg <= kItemArmorSuper) {
1344         switch (pXSprite->dropMsg) {
1345             case kItemArmorAsbest:
1346                 curScale[kDmgBurn] = 0;
1347                 curScale[kDmgExplode] -= 30;
1348                 break;
1349             case kItemArmorBasic:
1350                 curScale[kDmgBurn] -= 15;
1351                 curScale[kDmgExplode] -= 15;
1352                 curScale[kDmgBullet] -= 15;
1353                 curScale[kDmgSpirit] -= 15;
1354                 break;
1355             case kItemArmorBody:
1356                 curScale[kDmgBullet] -= 30;
1357                 break;
1358             case kItemArmorFire:
1359                 curScale[kDmgBurn] -= 30;
1360                 curScale[kDmgExplode] -= 30;
1361                 break;
1362             case kItemArmorSpirit:
1363                 curScale[kDmgSpirit] -= 30;
1364                 break;
1365             case kItemArmorSuper:
1366                 curScale[kDmgBurn] -= 60;
1367                 curScale[kDmgExplode] -= 60;
1368                 curScale[kDmgBullet] -= 60;
1369                 curScale[kDmgSpirit] -= 60;
1370                 break;
1371         }
1372     }
1373 
1374     // take in account yrepeat of sprite
1375     short yrepeat = sprite[pXSprite->reference].yrepeat;
1376     if (yrepeat < 64) {
1377         for (int i = 0; i < kDmgMax; i++) curScale[i] += (64 - yrepeat);
1378     } else if (yrepeat > 64) {
1379         for (int i = 0; i < kDmgMax; i++) curScale[i] -= ((yrepeat - 64) >> 2);
1380     }
1381 
1382     // take surface type into account
1383     int surfType = tileGetSurfType(sprite[pXSprite->reference].index + 0xc000);
1384     switch (surfType) {
1385         case 1:  // stone
1386             curScale[kDmgFall] = 0;
1387             curScale[kDmgBullet] -= 200;
1388             curScale[kDmgBurn] -= 100;
1389             curScale[kDmgExplode] -= 80;
1390             curScale[kDmgChoke] += 30;
1391             curScale[kDmgElectric] += 20;
1392             break;
1393         case 2:  // metal
1394             curScale[kDmgFall] = 16;
1395             curScale[kDmgBullet] -= 128;
1396             curScale[kDmgBurn] -= 90;
1397             curScale[kDmgExplode] -= 55;
1398             curScale[kDmgChoke] += 20;
1399             curScale[kDmgElectric] += 30;
1400             break;
1401         case 3:  // wood
1402             curScale[kDmgBullet] -= 10;
1403             curScale[kDmgBurn] += 50;
1404             curScale[kDmgExplode] += 40;
1405             curScale[kDmgChoke] += 10;
1406             curScale[kDmgElectric] -= 60;
1407             break;
1408         case 5:  // water
1409         case 6:  // dirt
1410         case 7:  // clay
1411         case 13: // goo
1412             curScale[kDmgFall] = 8;
1413             curScale[kDmgBullet] -= 20;
1414             curScale[kDmgBurn] -= 200;
1415             curScale[kDmgExplode] -= 60;
1416             curScale[kDmgChoke] = 0;
1417             curScale[kDmgElectric] += 40;
1418             break;
1419         case 8:  // snow
1420         case 9:  // ice
1421             curScale[kDmgFall] = 8;
1422             curScale[kDmgBullet] -= 20;
1423             curScale[kDmgBurn] -= 100;
1424             curScale[kDmgExplode] -= 50;
1425             curScale[kDmgChoke] = 0;
1426             curScale[kDmgElectric] += 40;
1427             break;
1428         case 10: // leaves
1429         case 12: // plant
1430             curScale[kDmgFall] = 0;
1431             curScale[kDmgBullet] -= 10;
1432             curScale[kDmgBurn] += 70;
1433             curScale[kDmgExplode] += 50;
1434             break;
1435         case 11: // cloth
1436             curScale[kDmgFall] = 8;
1437             curScale[kDmgBullet] -= 10;
1438             curScale[kDmgBurn] += 30;
1439             curScale[kDmgExplode] += 20;
1440             break;
1441         case 14: // lava
1442             curScale[kDmgBurn] = 0;
1443             curScale[kDmgExplode] = 0;
1444             curScale[kDmgChoke] += 30;
1445             break;
1446     }
1447 
1448     // finally, scale dmg for difficulty
1449     for (int i = 0; i < kDmgMax; i++)
1450         curScale[i] = mulscale8(DudeDifficulty[gGameOptions.nDifficulty], ClipLow(curScale[i], 1));
1451 
1452     //short* dc = curScale;
1453     //if (pXSprite->rxID == 788)
1454         //viewSetSystemMessage("0: %d, 1: %d, 2: %d, 3: %d, 4: %d, 5: %d, 6: %d", dc[0], dc[1], dc[2], dc[3], dc[4], dc[5], dc[6]);
1455 }
1456 
getDispersionModifier(spritetype * pSprite,int minDisp,int maxDisp)1457 int getDispersionModifier(spritetype* pSprite, int minDisp, int maxDisp) {
1458     // the faster fire rate, the less frames = more dispersion
1459     Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(xsprite[pSprite->extra].data2 + 6, "SEQ"); int disp = 1;
1460     if (hSeq != NULL && (pSeq = (Seq*)gSysRes.Load(hSeq)) != NULL) {
1461         int nFrames = pSeq->nFrames; int ticks = pSeq->ticksPerFrame; int shots = 0;
1462         for (int i = 0; i <= pSeq->nFrames; i++) {
1463             if (pSeq->frames[i].at5_5) shots++;
1464         }
1465 
1466         disp = (((shots * 1000) / nFrames) / ticks) * 20;
1467         if (gGameOptions.nDifficulty > 0)
1468             disp /= gGameOptions.nDifficulty;
1469 
1470         //viewSetSystemMessage("DISP: %d FRAMES: %d SHOTS: %d TICKS %d", disp, nFrames, shots, ticks);
1471 
1472     }
1473 
1474     return ClipRange(disp, minDisp, maxDisp);
1475 }
1476 
1477 // the distance counts from sprite size
getRangeAttackDist(spritetype * pSprite,int minDist,int maxDist)1478 int getRangeAttackDist(spritetype* pSprite, int minDist, int maxDist) {
1479     short yrepeat = pSprite->yrepeat; int dist = 0; int seqId = xsprite[pSprite->extra].data2;
1480     short mul = 550; int picnum = pSprite->picnum;
1481 
1482     if (yrepeat > 0) {
1483         if (seqId >= 0) {
1484             Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(seqId, "SEQ");
1485             if (hSeq) {
1486                 pSeq = (Seq*)gSysRes.Load(hSeq);
1487                 picnum = seqGetTile(&pSeq->frames[0]);
1488             }
1489         }
1490 
1491         dist = tilesiz[picnum].y << 8;
1492         if (yrepeat < 64) dist -= (64 - yrepeat) * mul;
1493         else if (yrepeat > 64) dist += (yrepeat - 64) * (mul / 3);
1494     }
1495 
1496     dist = ClipRange(dist, minDist, maxDist);
1497     //viewSetSystemMessage("DIST: %d, SPRHEIGHT: %d: YREPEAT: %d PIC: %d", dist, tilesiz[pSprite->picnum].y, yrepeat, picnum);
1498     return dist;
1499 }
1500 
getBaseChanceModifier(int baseChance)1501 int getBaseChanceModifier(int baseChance) {
1502     return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance);
1503 }
1504 
getRecoilChance(spritetype * pSprite)1505 int getRecoilChance(spritetype* pSprite) {
1506     XSPRITE* pXSprite = &xsprite[pSprite->extra]; int mass = getSpriteMassBySize(pSprite);
1507     int baseChance = (!dudeIsMelee(pXSprite) ? 0x8000 : 0x4000);
1508     baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
1509 
1510     int chance = ((baseChance / mass) << 7);
1511     return chance;
1512 }
1513 
getDodgeChance(spritetype * pSprite)1514 int getDodgeChance(spritetype* pSprite) {
1515     XSPRITE* pXSprite = &xsprite[pSprite->extra]; int mass = getSpriteMassBySize(pSprite);
1516     int baseChance = (!dudeIsMelee(pXSprite) ? 0x6000 : 0x1000);
1517     baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
1518 
1519     int chance = ((baseChance / mass) << 7);
1520     return chance;
1521 
1522 }
1523 
dudeLeechOperate(spritetype * pSprite,XSPRITE * pXSprite,EVENT event)1524 void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT event)
1525 {
1526     if (event.cmd == kCmdOff) {
1527         actPostSprite(pSprite->index, kStatFree);
1528         return;
1529     }
1530 
1531     int nTarget = pXSprite->target;
1532     if (spriRangeIsFine(nTarget) && nTarget != pSprite->owner) {
1533         spritetype* pTarget = &sprite[nTarget];
1534         if (pTarget->statnum == kStatDude && !(pTarget->flags & 32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites && !pXSprite->stateTimer) {
1535 
1536             if (IsPlayerSprite(pTarget)) {
1537                 PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
1538                 if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return;
1539             }
1540             int top, bottom;
1541             GetSpriteExtents(pSprite, &top, &bottom);
1542             int nType = pTarget->type - kDudeBase;
1543             DUDEINFO* pDudeInfo = &dudeInfo[nType];
1544             int z1 = (top - pSprite->z) - 256;
1545             int x = pTarget->x; int y = pTarget->y; int z = pTarget->z;
1546             int nDist = approxDist(x - pSprite->x, y - pSprite->y);
1547 
1548             if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum)) {
1549                 int t = divscale(nDist, 0x1aaaaa, 12);
1550                 x += (xvel[nTarget] * t) >> 12;
1551                 y += (yvel[nTarget] * t) >> 12;
1552                 int angBak = pSprite->ang;
1553                 pSprite->ang = getangle(x - pSprite->x, y - pSprite->y);
1554                 int dx = Cos(pSprite->ang) >> 16;
1555                 int dy = Sin(pSprite->ang) >> 16;
1556                 int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
1557                 int dz = divscale(tz - top - 256, nDist, 10);
1558                 int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
1559                 int t2;
1560 
1561                 if (!pXSprite->data3) t2 = 120 / 10.0;
1562                 else t2 = (3 * 120) / 10.0;
1563 
1564                 spritetype * pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
1565                 if (pMissile)
1566                 {
1567                     pMissile->owner = pSprite->owner;
1568                     pXSprite->stateTimer = 1;
1569                     evPost(pSprite->index, 3, t2, kCallbackLeechStateTimer);
1570                     pXSprite->data3 = ClipLow(pXSprite->data3 - 1, 0);
1571                 }
1572                 pSprite->ang = angBak;
1573             }
1574         }
1575 
1576     }
1577 }
1578 
doExplosion(spritetype * pSprite,int nType)1579 bool doExplosion(spritetype* pSprite, int nType) {
1580     spritetype* pExplosion = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatExplosion, true);
1581     if (pExplosion->extra < 0 || pExplosion->extra >= kMaxXSprites)
1582         return false;
1583 
1584     int nSeq = 4; int nSnd = 304; EXPLOSION* pExpl = &explodeInfo[nType];
1585 
1586     pExplosion->type = nType;
1587     pExplosion->cstat |= CSTAT_SPRITE_INVISIBLE;
1588     pExplosion->owner = pSprite->index;
1589     pExplosion->shade = -127;
1590 
1591     pExplosion->yrepeat = pExplosion->xrepeat = pExpl->repeat;
1592 
1593     xsprite[pExplosion->extra].data1 = pExpl->ticks;
1594     xsprite[pExplosion->extra].data2 = pExpl->quakeEffect;
1595     xsprite[pExplosion->extra].data3 = pExpl->flashEffect;
1596 
1597     if (nType == 0) { nSeq = 3; nSnd = 303; }
1598     else if (nType == 2) { nSeq = 4; nSnd = 305; }
1599     else if (nType == 3) { nSeq = 9; nSnd = 307; }
1600     else if (nType == 4) { nSeq = 5; nSnd = 307; }
1601     else if (nType <= 6) { nSeq = 4; nSnd = 303; }
1602     else if (nType == 7) { nSeq = 4; nSnd = 303; }
1603 
1604     seqSpawn(nSeq, 3, pExplosion->extra, -1);
1605     sfxPlay3DSound(pExplosion, nSnd, -1, 0);
1606 
1607     return true;
1608 }
1609 
1610 // this function allows to spawn new custom dude and inherit spawner settings,
1611 // so custom dude can have different weapons, hp and so on...
genDudeSpawn(spritetype * pSprite,int nDist)1612 spritetype* genDudeSpawn(spritetype* pSprite, int nDist) {
1613 
1614     spritetype* pSource = pSprite; XSPRITE* pXSource = &xsprite[pSource->extra];
1615     spritetype* pDude = actSpawnSprite(pSprite, 6); XSPRITE* pXDude = &xsprite[pDude->extra];
1616 
1617     int x, y, z = pSprite->z, nAngle = pSprite->ang, nType = kDudeModernCustom;
1618 
1619     if (nDist > 0) {
1620         x = pSprite->x + mulscale30r(Cos(nAngle), nDist);
1621         y = pSprite->y + mulscale30r(Sin(nAngle), nDist);
1622     } else {
1623         x = pSprite->x;
1624         y = pSprite->y;
1625     }
1626 
1627     pDude->type = nType; pDude->ang = nAngle;
1628     vec3_t pos = { x, y, z }; setsprite(pDude->index, &pos);
1629     pDude->cstat |= 0x1101; pDude->clipdist = dudeInfo[nType - kDudeBase].clipdist;
1630 
1631     // inherit weapon, seq and sound settings.
1632     pXDude->data1 = pXSource->data1;
1633     pXDude->data2 = pXSource->data2;
1634     pXDude->sysData1 = pXSource->data3; // move sndStartId from data3 to sysData1
1635     pXDude->data3 = 0;
1636 
1637     // spawn seq
1638     seqSpawn(genDudeSeqStartId(pXDude), 3, pDude->extra, -1);
1639 
1640     // inherit movement speed.
1641     pXDude->busyTime = pXSource->busyTime;
1642 
1643     // inherit clipdist?
1644     if (pSource->clipdist > 0) pDude->clipdist = pSource->clipdist;
1645 
1646     // inherit custom hp settings
1647     if (pXSource->data4 <= 0) pXDude->health = dudeInfo[nType - kDudeBase].startHealth << 4;
1648     else pXDude->health = ClipRange(pXSource->data4 << 4, 1, 65535);
1649 
1650 
1651     if (pSource->flags & kModernTypeFlag1) {
1652         switch (pSource->type) {
1653         case kModernCustomDudeSpawn:
1654             //inherit pal?
1655             if (pDude->pal <= 0) pDude->pal = pSource->pal;
1656 
1657             // inherit spawn sprite trigger settings, so designer can count monsters.
1658             pXDude->txID = pXSource->txID;
1659             pXDude->command = pXSource->command;
1660             pXDude->triggerOn = pXSource->triggerOn;
1661             pXDude->triggerOff = pXSource->triggerOff;
1662 
1663             // inherit drop items
1664             pXDude->dropMsg = pXSource->dropMsg;
1665 
1666             // inherit required key so it can be dropped
1667             pXDude->key = pXSource->key;
1668 
1669             // inherit dude flags
1670             pXDude->dudeDeaf = pXSource->dudeDeaf;
1671             pXDude->dudeGuard = pXSource->dudeGuard;
1672             pXDude->dudeAmbush = pXSource->dudeAmbush;
1673             pXDude->dudeFlag4 = pXSource->dudeFlag4;
1674             pXDude->unused1 = pXSource->unused1;
1675             break;
1676         }
1677     }
1678 
1679     // inherit sprite size (useful for seqs with zero repeats)
1680     if (pSource->flags & kModernTypeFlag2) {
1681         pDude->xrepeat = pSource->xrepeat;
1682         pDude->yrepeat = pSource->yrepeat;
1683     }
1684 
1685     aiInitSprite(pDude);
1686     return pDude;
1687 }
1688 
genDudeTransform(spritetype * pSprite)1689 void genDudeTransform(spritetype* pSprite) {
1690 
1691     if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
1692         consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
1693         return;
1694     }
1695 
1696     XSPRITE* pXSprite = &xsprite[pSprite->extra];
1697     XSPRITE* pXIncarnation = getNextIncarnation(pXSprite);
1698     if (pXIncarnation == NULL) {
1699         if (pXSprite->sysData1 == kGenDudeTransformStatus) pXSprite->sysData1 = 0;
1700         trTriggerSprite(pSprite->index, pXSprite, kCmdOff);
1701         return;
1702     }
1703 
1704     spritetype* pIncarnation = &sprite[pXIncarnation->reference];
1705     pXSprite->key = pXSprite->dropMsg = pXSprite->locked = 0;
1706 
1707     // save incarnation's going on and off options
1708     bool triggerOn = pXIncarnation->triggerOn;
1709     bool triggerOff = pXIncarnation->triggerOff;
1710 
1711     // then remove it from incarnation so it will not send the commands
1712     pXIncarnation->triggerOn = false;
1713     pXIncarnation->triggerOff = false;
1714 
1715     // trigger dude death before transform
1716     trTriggerSprite(pSprite->index, pXSprite, kCmdOff);
1717 
1718     pSprite->type = pSprite->inittype = pIncarnation->type;
1719     pSprite->flags = pIncarnation->flags;
1720     pSprite->pal = pIncarnation->pal;
1721     pSprite->shade = pIncarnation->shade;
1722     pSprite->clipdist = pIncarnation->clipdist;
1723     pSprite->xrepeat = pIncarnation->xrepeat;
1724     pSprite->yrepeat = pIncarnation->yrepeat;
1725 
1726     pXSprite->txID = pXIncarnation->txID;
1727     pXSprite->command = pXIncarnation->command;
1728     pXSprite->triggerOn = triggerOn;
1729     pXSprite->triggerOff = triggerOff;
1730     pXSprite->busyTime = pXIncarnation->busyTime;
1731     pXSprite->waitTime = pXIncarnation->waitTime;
1732 
1733     // inherit respawn properties
1734     pXSprite->respawn = pXIncarnation->respawn;
1735     pXSprite->respawnPending = pXIncarnation->respawnPending;
1736 
1737     pXSprite->burnTime = 0;
1738     pXSprite->burnSource = -1;
1739 
1740     pXSprite->data1 = pXIncarnation->data1;
1741     pXSprite->data2 = pXIncarnation->data2;
1742 
1743     pXSprite->sysData1 = pXIncarnation->data3;  // soundBase id
1744     pXSprite->sysData2 = pXIncarnation->data4;  // start hp
1745 
1746     pXSprite->dudeGuard = pXIncarnation->dudeGuard;
1747     pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
1748     pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
1749     pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;
1750     pXSprite->unused1 = pXIncarnation->unused1;
1751 
1752     pXSprite->dropMsg = pXIncarnation->dropMsg;
1753     pXSprite->key = pXIncarnation->key;
1754 
1755     pXSprite->locked = pXIncarnation->locked;
1756     pXSprite->Decoupled = pXIncarnation->Decoupled;
1757 
1758     // clear drop items of the incarnation
1759     pXIncarnation->key = pXIncarnation->dropMsg = 0;
1760 
1761     // set hp
1762     if (pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
1763     else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);
1764 
1765     int seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
1766     switch (pSprite->type) {
1767         case kDudePodMother: // fake dude
1768         case kDudeTentacleMother: // fake dude
1769             break;
1770         case kDudeModernCustom:
1771         case kDudeModernCustomBurning:
1772             seqId = genDudeSeqStartId(pXSprite);
1773             genDudePrepare(pSprite, kGenDudePropertyMass);
1774             fallthrough__; // go below
1775         default:
1776             seqSpawn(seqId, 3, pSprite->extra, -1);
1777 
1778             // save target
1779             int target = pXSprite->target;
1780 
1781             // re-init sprite
1782             aiInitSprite(pSprite);
1783 
1784             // try to restore target
1785             if (target == -1) aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
1786             else aiSetTarget(pXSprite, target);
1787 
1788             // finally activate it
1789             aiActivateDude(pSprite, pXSprite);
1790 
1791             break;
1792     }
1793     pXIncarnation->triggerOn = triggerOn;
1794     pXIncarnation->triggerOff = triggerOff;
1795 
1796     /*// remove the incarnation in case if non-locked
1797     if (pXIncarnation->locked == 0) {
1798         pXIncarnation->txID = pIncarnation->type = 0;
1799         actPostSprite(pIncarnation->index, kStatFree);
1800         // or restore triggerOn and off options
1801     } else {
1802         pXIncarnation->triggerOn = triggerOn;
1803         pXIncarnation->triggerOff = triggerOff;
1804     }*/
1805 }
1806 
1807 
updateTargetOfLeech(spritetype * pSprite)1808 void updateTargetOfLeech(spritetype* pSprite) {
1809     if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
1810         consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
1811         return;
1812     }
1813 
1814 
1815     spritetype* pLeech = leechIsDropped(pSprite);
1816     if (pLeech == NULL || pLeech->extra < 0) gGenDudeExtra[pSprite->index].nLifeLeech = -1;
1817     else if (xsprite[pSprite->extra].target != xsprite[pLeech->extra].target) {
1818         XSPRITE* pXDude = &xsprite[pSprite->extra]; XSPRITE* pXLeech = &xsprite[pLeech->extra];
1819         if (pXDude->target < 0 && spriRangeIsFine(pXLeech->target)) {
1820             aiSetTarget(pXDude, pXLeech->target);
1821             if (inIdle(pXDude->aiState))
1822                 aiActivateDude(pSprite, pXDude);
1823         } else {
1824             pXLeech->target = pXDude->target;
1825         }
1826     }
1827 }
1828 
updateTargetOfSlaves(spritetype * pSprite)1829 void updateTargetOfSlaves(spritetype* pSprite) {
1830     if (!xspriRangeIsFine(pSprite->extra)) {
1831         consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
1832         return;
1833     }
1834 
1835     XSPRITE* pXSprite = &xsprite[pSprite->extra];
1836     GENDUDEEXTRA* pExtra = genDudeExtra(pSprite); short* slave = pExtra->slave;
1837     spritetype* pTarget = (pXSprite->target >= 0 && IsDudeSprite(&sprite[pXSprite->target])) ? &sprite[pXSprite->target] : NULL;
1838     XSPRITE* pXTarget = (pTarget != NULL && xspriRangeIsFine(pTarget->extra) && xsprite[pTarget->extra].health > 0) ? &xsprite[pTarget->extra] : NULL;
1839 
1840     int newCnt = pExtra->slaveCount;
1841     for (int i = 0; i <= gGameOptions.nDifficulty; i++) {
1842         if (spriRangeIsFine(slave[i])) {
1843             spritetype* pSlave = &sprite[slave[i]];
1844             if (!IsDudeSprite(pSlave) || !xspriRangeIsFine(pSlave->extra) || xsprite[pSlave->extra].health < 0) {
1845                 slave[i] = pSlave->owner = -1; newCnt--;
1846                 continue;
1847             }
1848 
1849             XSPRITE* pXSlave = &xsprite[pSlave->index];
1850             if (pXTarget != NULL) {
1851                 if (pXSprite->target != pXSlave->target) aiSetTarget(pXSlave, pXSprite->target);
1852                 // check if slave have proper target
1853                 if (!spriRangeIsFine(pXSlave->target) || sprite[pXSlave->target].owner == pSprite->index)
1854                     aiSetTarget(pXSlave, pSprite->x, pSprite->y, pSprite->z);
1855             } else {
1856                 aiSetTarget(pXSlave, pSprite->x, pSprite->y, pSprite->z); // try return to master
1857             }
1858         }
1859     }
1860 
1861     pExtra->slaveCount = newCnt;
1862 }
1863 
inDodge(AISTATE * aiState)1864 short inDodge(AISTATE* aiState) {
1865     if (aiState == &genDudeDodgeL) return 1;
1866     else if (aiState == &genDudeDodgeD) return 2;
1867     else if (aiState == &genDudeDodgeW) return 3;
1868     else if (aiState == &genDudeDodgeShortL) return 4;
1869     else if (aiState == &genDudeDodgeShortD) return 5;
1870     else if (aiState == &genDudeDodgeShortW) return 6;
1871     else if (aiState == &genDudeDodgeShorterL) return 7;
1872     else if (aiState == &genDudeDodgeShorterD) return 8;
1873     else if (aiState == &genDudeDodgeShorterW) return 9;
1874     return 0;
1875 
1876 }
1877 
inIdle(AISTATE * aiState)1878 bool inIdle(AISTATE* aiState) {
1879     return (aiState == &genDudeIdleW || aiState == &genDudeIdleL);
1880 }
1881 
inAttack(AISTATE * aiState)1882 bool inAttack(AISTATE* aiState) {
1883     return (aiState == &genDudeFireL || aiState == &genDudeFireW
1884         || aiState == &genDudeFireD || aiState == &genDudeThrow || aiState == &genDudeThrow2 || aiState == &genDudePunch);
1885 }
1886 
inSearch(AISTATE * aiState)1887 short inSearch(AISTATE* aiState) {
1888     if (aiState->stateType == kAiStateSearch) return 1;
1889     return 0;
1890 }
1891 
inChase(AISTATE * aiState)1892 short inChase(AISTATE* aiState) {
1893     if (aiState == &genDudeChaseL) return 1;
1894     else if (aiState == &genDudeChaseD) return 2;
1895     else if (aiState == &genDudeChaseW) return 3;
1896     else if (aiState == &genDudeChaseNoWalkL) return 4;
1897     else if (aiState == &genDudeChaseNoWalkD) return 5;
1898     else if (aiState == &genDudeChaseNoWalkW) return 6;
1899     else return 0;
1900 }
1901 
inRecoil(AISTATE * aiState)1902 short inRecoil(AISTATE* aiState) {
1903     if (aiState == &genDudeRecoilL || aiState == &genDudeRecoilTesla) return 1;
1904     else if (aiState == &genDudeRecoilD) return 2;
1905     else if (aiState == &genDudeRecoilW) return 3;
1906     else return 0;
1907 }
1908 
inDuck(AISTATE * aiState)1909 short inDuck(AISTATE* aiState) {
1910     if (aiState == &genDudeFireD) return 1;
1911     else if (aiState == &genDudeChaseD) return 2;
1912     else if (aiState == &genDudeChaseNoWalkD) return 3;
1913     else if (aiState == &genDudeRecoilD) return 4;
1914     else if (aiState == &genDudeDodgeShortD) return 5;
1915     return 0;
1916 }
1917 
1918 
canSwim(spritetype * pSprite)1919 bool canSwim(spritetype* pSprite) {
1920     return gGenDudeExtra[pSprite->index].canSwim;
1921 }
1922 
canDuck(spritetype * pSprite)1923 bool canDuck(spritetype* pSprite) {
1924     return gGenDudeExtra[pSprite->index].canDuck;
1925 }
1926 
canWalk(spritetype * pSprite)1927 bool canWalk(spritetype* pSprite) {
1928     return gGenDudeExtra[pSprite->index].canWalk;
1929 }
1930 
genDudeSeqStartId(XSPRITE * pXSprite)1931 int genDudeSeqStartId(XSPRITE* pXSprite) {
1932     if (genDudePrepare(&sprite[pXSprite->reference], kGenDudePropertyStates)) return pXSprite->data2;
1933     else return kGenDudeDefaultSeq;
1934 }
1935 
genDudePrepare(spritetype * pSprite,int propId)1936 bool genDudePrepare(spritetype* pSprite, int propId) {
1937     if (!spriRangeIsFine(pSprite->index)) {
1938         consoleSysMsg("!spriRangeIsFine(pSprite->index)");
1939         return false;
1940     } else if (!xspriRangeIsFine(pSprite->extra)) {
1941         consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
1942         return false;
1943     } else if (pSprite->type != kDudeModernCustom) {
1944         consoleSysMsg("pSprite->type != kDudeModernCustom");
1945         return false;
1946     } else if (propId < kGenDudePropertyAll || propId >= kGenDudePropertyMax) {
1947         viewSetSystemMessage("Unknown custom dude #%d property (%d)", pSprite->index, propId);
1948         return false;
1949     }
1950 
1951     XSPRITE* pXSprite = &xsprite[pSprite->extra];
1952     GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index]; pExtra->updReq[propId] = false;
1953 
1954     switch (propId) {
1955         case kGenDudePropertyAll:
1956         case kGenDudePropertyInitVals:
1957             pExtra->moveSpeed = getGenDudeMoveSpeed(pSprite, 0, true, false);
1958             pExtra->initVals[0] = pSprite->xrepeat;
1959             pExtra->initVals[1] = pSprite->yrepeat;
1960             pExtra->initVals[2] = pSprite->clipdist;
1961             if (propId) break;
1962             fallthrough__;
1963 
1964         case kGenDudePropertyWeapon: {
1965             pExtra->curWeapon = pXSprite->data1;
1966             switch (pXSprite->data1) {
1967                 case VECTOR_TYPE_19: pExtra->curWeapon = kVectorBullet; break;
1968                 case kMissileUnused: pExtra->curWeapon = kMissileArcGargoyle; break;
1969                 case kThingDroppedLifeLeech: pExtra->curWeapon = kModernThingEnemyLifeLeech; break;
1970             }
1971 
1972             pExtra->canAttack = false;
1973             if (pExtra->curWeapon > 0 && gSysRes.Lookup(pXSprite->data2 + kGenDudeSeqAttackNormalL, "SEQ"))
1974                 pExtra->canAttack = true;
1975 
1976             pExtra->weaponType = kGenDudeWeaponNone;
1977             if (pExtra->curWeapon > 0 && pExtra->curWeapon < kVectorMax) pExtra->weaponType = kGenDudeWeaponHitscan;
1978             else if (pExtra->curWeapon >= kDudeBase && pExtra->curWeapon < kDudeMax) pExtra->weaponType = kGenDudeWeaponSummon;
1979             else if (pExtra->curWeapon >= kMissileBase && pExtra->curWeapon < kMissileMax) pExtra->weaponType = kGenDudeWeaponMissile;
1980             else if (pExtra->curWeapon >= kThingBase && pExtra->curWeapon < kThingMax) pExtra->weaponType = kGenDudeWeaponThrow;
1981             else if (pExtra->curWeapon >= kTrapExploder && pExtra->curWeapon < (kTrapExploder + kExplodeMax) - 1)
1982                 pExtra->weaponType = kGenDudeWeaponKamikaze;
1983 
1984             pExtra->isMelee = false;
1985             if (pExtra->weaponType == kGenDudeWeaponKamikaze) pExtra->isMelee = true;
1986             else if (pExtra->weaponType == kGenDudeWeaponHitscan) {
1987                 if (gVectorData[pExtra->curWeapon].maxDist > 0 && gVectorData[pExtra->curWeapon].maxDist <= kGenDudeMaxMeleeDist)
1988                     pExtra->isMelee = true;
1989             }
1990 
1991             if (propId) break;
1992             fallthrough__;
1993 
1994         }
1995         case kGenDudePropertyDmgScale:
1996             scaleDamage(pXSprite);
1997             if (propId) break;
1998             fallthrough__;
1999 
2000         case kGenDudePropertyMass: {
2001             // to ensure mass gets updated, let's clear all cache
2002             SPRITEMASS* pMass = &gSpriteMass[pSprite->index];
2003             pMass->seqId = pMass->picnum = pMass->xrepeat = pMass->yrepeat = pMass->clipdist = 0;
2004             pMass->mass = pMass->airVel = pMass->fraction = 0;
2005             getSpriteMassBySize(pSprite);
2006             if (propId) break;
2007             fallthrough__;
2008         }
2009         case kGenDudePropertyAttack:
2010             pExtra->fireDist = getRangeAttackDist(pSprite, 3000, 45000);
2011             pExtra->throwDist = pExtra->fireDist; // temp
2012             pExtra->baseDispersion = getDispersionModifier(pSprite, 200, 3500);
2013             if (propId) break;
2014             fallthrough__;
2015 
2016         case kGenDudePropertyStates: {
2017 
2018             pExtra->canFly = false;
2019 
2020             // check the animation
2021             int seqStartId = -1;
2022             if (pXSprite->data2 <= 0) seqStartId = pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
2023             else seqStartId = pXSprite->data2;
2024 
2025             for (int i = seqStartId; i < seqStartId + kGenDudeSeqMax; i++) {
2026                 switch (i - seqStartId) {
2027                     case kGenDudeSeqIdleL:
2028                     case kGenDudeSeqDeathDefault:
2029                     case kGenDudeSeqAttackNormalL:
2030                     case kGenDudeSeqAttackThrow:
2031                     case kGenDudeSeqAttackPunch:
2032                         if (!gSysRes.Lookup(i, "SEQ")) {
2033                             pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
2034                             viewSetSystemMessage("No SEQ animation id %d found for custom dude #%d!", i, pSprite->index);
2035                             viewSetSystemMessage("SEQ base id: %d", seqStartId);
2036                         } else if ((i - seqStartId) == kGenDudeSeqAttackPunch) {
2037                             pExtra->forcePunch = true; // required for those who don't have fire trigger in punch seq and for default animation
2038                             Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(i, "SEQ");
2039                             pSeq = (Seq*)gSysRes.Load(hSeq);
2040                             for (int i = 0; i < pSeq->nFrames; i++) {
2041                                 if (!pSeq->frames[i].at5_5) continue;
2042                                 pExtra->forcePunch = false;
2043                                 break;
2044                             }
2045                         }
2046                         break;
2047                     case kGenDudeSeqDeathExplode:
2048                         pExtra->availDeaths[kDmgExplode] = (bool)gSysRes.Lookup(i, "SEQ");
2049                         break;
2050                     case kGenDudeSeqBurning:
2051                         pExtra->canBurn = gSysRes.Lookup(i, "SEQ");
2052                         break;
2053                     case kGenDudeSeqElectocuted:
2054                         pExtra->canElectrocute = gSysRes.Lookup(i, "SEQ");
2055                         break;
2056                     case kGenDudeSeqRecoil:
2057                         pExtra->canRecoil = gSysRes.Lookup(i, "SEQ");
2058                         break;
2059                     case kGenDudeSeqMoveL: {
2060                         bool oldStatus = pExtra->canWalk;
2061                         pExtra->canWalk = gSysRes.Lookup(i, "SEQ");
2062                         if (oldStatus != pExtra->canWalk) {
2063                             if (!spriRangeIsFine(pXSprite->target)) {
2064                                 if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeIdleW);
2065                                 else aiGenDudeNewState(pSprite, &genDudeIdleL);
2066                             } else if (pExtra->canWalk) {
2067                                 if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeChaseW);
2068                                 else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeChaseD);
2069                                 else aiGenDudeNewState(pSprite, &genDudeChaseL);
2070                             } else {
2071                                 if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeChaseNoWalkW);
2072                                 else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeChaseNoWalkD);
2073                                 else aiGenDudeNewState(pSprite, &genDudeChaseNoWalkL);
2074                             }
2075                         }
2076                         break;
2077                     }
2078                     case kGenDudeSeqAttackNormalDW:
2079                         pExtra->canDuck = (gSysRes.Lookup(i, "SEQ") && gSysRes.Lookup(seqStartId + 14, "SEQ"));
2080                         pExtra->canSwim = (gSysRes.Lookup(i, "SEQ") && gSysRes.Lookup(seqStartId + 13, "SEQ")
2081                             && gSysRes.Lookup(seqStartId + 17, "SEQ"));
2082                         break;
2083                     case kGenDudeSeqDeathBurn1: {
2084                         bool seq15 = gSysRes.Lookup(i, "SEQ"); bool seq16 = gSysRes.Lookup(seqStartId + 16, "SEQ");
2085                         if (seq15 && seq16) pExtra->availDeaths[kDmgBurn] = 3;
2086                         else if (seq16) pExtra->availDeaths[kDmgBurn] = 2;
2087                         else if (seq15) pExtra->availDeaths[kDmgBurn] = 1;
2088                         else pExtra->availDeaths[kDmgBurn] = 0;
2089                         break;
2090                     }
2091                     case kGenDudeSeqMoveW:
2092                     case kGenDudeSeqMoveD:
2093                     case kGenDudeSeqDeathBurn2:
2094                     case kGenDudeSeqIdleW:
2095                         break;
2096                     case kGenDudeSeqReserved3:
2097                     case kGenDudeSeqReserved4:
2098                     case kGenDudeSeqReserved5:
2099                     case kGenDudeSeqReserved6:
2100                     case kGenDudeSeqReserved7:
2101                     case kGenDudeSeqReserved8:
2102                         /*if (gSysRes.Lookup(i, "SEQ")) {
2103                             viewSetSystemMessage("Found reserved SEQ animation (%d) for custom dude #%d!", i, pSprite->index);
2104                             viewSetSystemMessage("Using reserved animation is not recommended.");
2105                             viewSetSystemMessage("SEQ base id: %d", seqStartId);
2106                         }*/
2107                         break;
2108                 }
2109             }
2110             if (propId) break;
2111             fallthrough__;
2112         }
2113         case kGenDudePropertyLeech:
2114             pExtra->nLifeLeech = -1;
2115             if (pSprite->owner != kMaxSprites - 1) {
2116                 for (int nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
2117                     if (sprite[nSprite].owner == pSprite->index && sprite[nSprite].type == kModernThingEnemyLifeLeech) {
2118                         pExtra->nLifeLeech = nSprite;
2119                         break;
2120                     }
2121                 }
2122             }
2123             if (propId) break;
2124             fallthrough__;
2125 
2126         case kGenDudePropertySlaves:
2127             pExtra->slaveCount = 0; memset(pExtra->slave, -1, sizeof(pExtra->slave));
2128             for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
2129                 if (sprite[nSprite].owner != pSprite->index) continue;
2130                 else if (!IsDudeSprite(&sprite[nSprite]) || !xspriRangeIsFine(sprite[nSprite].extra) || xsprite[sprite[nSprite].extra].health <= 0) {
2131                     sprite[nSprite].owner = -1;
2132                     continue;
2133                 }
2134 
2135                 pExtra->slave[pExtra->slaveCount++] = nSprite;
2136                 if (pExtra->slaveCount > gGameOptions.nDifficulty)
2137                     break;
2138             }
2139             if (propId) break;
2140             fallthrough__;
2141 
2142         case kGenDudePropertySpriteSize: {
2143             if (seqGetStatus(3, pSprite->extra) == -1)
2144                 seqSpawn(pXSprite->data2 + pXSprite->aiState->seqId, 3, pSprite->extra, -1);
2145 
2146             // make sure dudes aren't in the floor or ceiling
2147             int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot);
2148             if (!(sector[pSprite->sectnum].ceilingstat & 0x0001))
2149                 pSprite->z += ClipLow(sector[pSprite->sectnum].ceilingz - zTop, 0);
2150             if (!(sector[pSprite->sectnum].floorstat & 0x0001))
2151                 pSprite->z += ClipHigh(sector[pSprite->sectnum].floorz - zBot, 0);
2152 
2153             pSprite->clipdist = ClipRange((pSprite->xrepeat + pSprite->yrepeat) >> 1, 4, 120);
2154             if (propId) break;
2155         }
2156     }
2157 
2158     return true;
2159 }
2160 
genDudePostDeath(spritetype * pSprite,DAMAGE_TYPE damageType,int damage)2161 void genDudePostDeath(spritetype* pSprite, DAMAGE_TYPE damageType, int damage) {
2162     if (damageType == kDamageExplode) {
2163         DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
2164         for (int i = 0; i < 3; i++)
2165             if (pDudeInfo->nGibType[i] > -1)
2166                 GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
2167 
2168         for (int i = 0; i < 4; i++)
2169             fxSpawnBlood(pSprite, damage);
2170     }
2171 
2172     gKillMgr.AddKill(pSprite);
2173 
2174     pSprite->type = kThingBloodChunks;
2175     actPostSprite(pSprite->index, kStatThing);
2176 }
2177 
aiGenDudeInitSprite(spritetype * pSprite,XSPRITE * pXSprite)2178 void aiGenDudeInitSprite(spritetype* pSprite, XSPRITE* pXSprite) {
2179     switch (pSprite->type) {
2180         case kDudeModernCustom: {
2181             DUDEEXTRA_at6_u1* pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
2182             pDudeExtraE->at8 = pDudeExtraE->at0 = 0;
2183             aiGenDudeNewState(pSprite, &genDudeIdleL);
2184             break;
2185         }
2186         case kDudeModernCustomBurning:
2187             aiGenDudeNewState(pSprite, &genDudeBurnGoto);
2188             pXSprite->burnTime = 1200;
2189             break;
2190     }
2191 
2192     pSprite->flags = 15;
2193     return;
2194 }
2195 
2196 #endif
2197