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