aicdud.cpp
1 //------------------------------------------------------------------------- 2 /* 3 Copyright (C) 2010-2019 EDuke32 developers and contributors 4 Copyright (C) 2019 Nuke.YKT 5 Copyright (C) NoOne 6 7 ***************************************************************** 8 NoOne: AI code for Custom Dude system. 9 ***************************************************************** 10 11 This file is part of NBlood. 12 13 NBlood is free software; you can redistribute it and/or 14 modify it under the terms of the GNU General Public License version 2 15 as published by the Free Software Foundation. 16 17 This program is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 21 See the GNU General Public License for more details. 22 23 You should have received a copy of the GNU General Public License 24 along with this program; if not, write to the Free Software 25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 26 */ 27 //------------------------------------------------------------------------- 28 #ifdef NOONE_EXTENSIONS 29 #include "nnexts.h" 30 #include "aicdud.h" 31 #include "globals.h" 32 #include "player.h" 33 #include "trig.h" 34 #include "endgame.h" 35 #include "mmulti.h" 36 #include "view.h" 37 38 #define SEQOFFSC(x) (kCdudeDefaultSeq + x) 39 #define SEQOFFSG(x) (kCdudeDefaultSeqF + x) 40 41 #define ATTACK_FAIL -1 42 #define ATTACK_CONTINUE -2 43 44 #pragma pack(push, 1) 45 struct TARGET_INFO 46 { 47 spritetype* pSpr; 48 XSPRITE* pXSpr; 49 unsigned int nDist : 32; 50 unsigned int nAng : 12; 51 unsigned int nDang : 12; 52 int nCode; 53 }; 54 #pragma pack(pop) 55 56 static void resetTarget(spritetype*, XSPRITE* pXSpr) { pXSpr->target = -1; } 57 static void moveForwardStop(spritetype* pSpr, XSPRITE*) { xvel[pSpr->index] = yvel[pSpr->index] = 0; } 58 static int qsSortTargets(TARGET_INFO* ref1, TARGET_INFO* ref2) { return ref1->nDist - ref2->nDist; } 59 static char posObstructed(int x, int y, int z, int nRadius); 60 static char clipFlying(spritetype* pSpr, XSPRITE* pXSpr); 61 void helperPounce(spritetype* pSpr, spritetype* pHSpr, int nDmgType, int nDmg, int kickPow); 62 63 static void thinkSearch(spritetype*, XSPRITE*); 64 static void thinkChase(spritetype*, XSPRITE*); 65 static void thinkFlee(spritetype*, XSPRITE*); 66 static void thinkTarget(spritetype* pSpr, XSPRITE*); 67 static void thinkMorph(spritetype* pSpr, XSPRITE* pXSpr); 68 static void thinkDying(spritetype* pSpr, XSPRITE* pXSpr); 69 static void enterIdle(spritetype* pSpr, XSPRITE* pXSpr); 70 static void enterSearch(spritetype* pSpr, XSPRITE* pXSpr); 71 static void enterFlee(spritetype* pSpr, XSPRITE* pXSpr); 72 static void enterBurnSearchWater(spritetype* pSpr, XSPRITE* pXSpr); 73 static void enterMorph(spritetype* pSpr, XSPRITE* pXSpr); 74 static void enterDying(spritetype* pSpr, XSPRITE* pXSpr); 75 static void enterDeath(spritetype* pSpr, XSPRITE* pXSpr); 76 static void enterSleep(spritetype* pSpr, XSPRITE* pXSpr); 77 static void enterWake(spritetype* pSpr, XSPRITE*); 78 static void enterAttack(spritetype* pSpr, XSPRITE* pXSpr); 79 static void enterKnock(spritetype* pSpr, XSPRITE*); 80 static void exitKnock(spritetype* pSpr, XSPRITE*); 81 static void enterDodgeFly(spritetype* pSpr, XSPRITE*); 82 static int getTargetAng(spritetype* pSpr, XSPRITE* pXSpr); 83 static void turnToTarget(spritetype* pSpr, XSPRITE* pXSpr); 84 static void moveTurn(spritetype* pSpr, XSPRITE* pXSpr); 85 static void moveDodge(spritetype* pSpr, XSPRITE* pXSpr); 86 static void moveNormal(spritetype* pSpr, XSPRITE* pXSpr); 87 static void moveKnockout(spritetype* pSpr, XSPRITE* pXSpr); 88 89 static void weaponShot(int, int); 90 static int nWeaponShot = seqRegisterClient(weaponShot); 91 static int weaponShotDummy(CUSTOMDUDE*, CUSTOMDUDE_WEAPON*, POINT3D*, int, int, int) { return -1; } 92 static int weaponShotHitscan(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 93 static int weaponShotMissile(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 94 static int weaponShotThing(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 95 static int weaponShotSummon(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 96 static int weaponShotKamikaze(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 97 static int weaponShotSpecial(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz); 98 static int (*gWeaponShotFunc[])(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) = 99 { 100 weaponShotDummy, // none 101 weaponShotHitscan, 102 weaponShotMissile, 103 weaponShotThing, 104 weaponShotSummon, // vanilla dude 105 weaponShotSummon, // custom dude 106 weaponShotKamikaze, 107 weaponShotSpecial, 108 }; 109 110 static AISTATE gCdudeStateDeath = { kAiStateOther, -1, -1, 0, enterDeath, NULL, NULL, NULL }; // just turns dude to a gib 111 112 113 114 115 116 // Land, Crouch, Swim, Fly (proper order matters!) 117 AISTATE gCdudeStateTemplate[kCdudeStateNormalMax][kCdudePostureMax] = 118 { 119 // idle (don't change pos or patrol gets broken!) 120 { 121 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterIdle, NULL, thinkTarget, NULL }, 122 { kAiStateIdle, SEQOFFSC(17), -1, 0, enterIdle, NULL, thinkTarget, NULL }, 123 { kAiStateIdle, SEQOFFSC(13), -1, 0, enterIdle, NULL, thinkTarget, NULL }, 124 { kAiStateIdle, SEQOFFSG(0), -1, 0, enterIdle, NULL, thinkTarget, NULL }, 125 }, 126 127 // search (don't change pos or patrol gets broken!) 128 { 129 { kAiStateSearch, SEQOFFSC(9), -1, 800, enterSearch, moveNormal, thinkSearch, &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureL] }, 130 { kAiStateSearch, SEQOFFSC(14), -1, 800, NULL, moveNormal, thinkSearch, &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureC] }, 131 { kAiStateSearch, SEQOFFSC(13), -1, 800, NULL, moveNormal, thinkSearch, &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureW] }, 132 { kAiStateSearch, SEQOFFSG(0), -1, 800, enterSearch, moveNormal, thinkSearch, &gCdudeStateTemplate[kCdudeStateIdle][kCdudePostureF] }, 133 }, 134 135 // dodge 136 { 137 { kAiStateMove, SEQOFFSC(9), -1, 90, NULL, moveDodge, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, 138 { kAiStateMove, SEQOFFSC(14), -1, 90, NULL, moveDodge, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, 139 { kAiStateMove, SEQOFFSC(13), -1, 90, NULL, moveDodge, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, 140 { kAiStateMove, SEQOFFSG(0), -1, 90, enterDodgeFly, moveDodge, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureF] }, 141 }, 142 143 // chase 144 { 145 { kAiStateChase, SEQOFFSC(9), -1, 30, NULL, moveNormal, thinkChase, NULL }, 146 { kAiStateChase, SEQOFFSC(14), -1, 30, NULL, moveNormal, thinkChase, NULL }, 147 { kAiStateChase, SEQOFFSC(13), -1, 30, NULL, moveNormal, thinkChase, NULL }, 148 { kAiStateChase, SEQOFFSG(0), -1, 30, NULL, moveNormal, thinkChase, NULL }, 149 }, 150 151 // flee 152 { 153 { kAiStateMove, SEQOFFSC(9), -1, 256, enterFlee, moveNormal, thinkFlee, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, 154 { kAiStateMove, SEQOFFSC(14), -1, 256, enterFlee, moveNormal, thinkFlee, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, 155 { kAiStateMove, SEQOFFSC(13), -1, 256, enterFlee, moveNormal, thinkFlee, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, 156 { kAiStateMove, SEQOFFSG(0), -1, 256, enterFlee, moveNormal, thinkFlee, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureF] }, 157 }, 158 159 // recoil normal 160 { 161 { kAiStateRecoil, SEQOFFSC(5), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, 162 { kAiStateRecoil, SEQOFFSC(5), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, 163 { kAiStateRecoil, SEQOFFSC(5), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, 164 { kAiStateRecoil, SEQOFFSG(5), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureF] }, 165 }, 166 167 // recoil tesla 168 { 169 { kAiStateRecoil, SEQOFFSC(4), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureL] }, 170 { kAiStateRecoil, SEQOFFSC(4), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureC] }, 171 { kAiStateRecoil, SEQOFFSC(4), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureW] }, 172 { kAiStateRecoil, SEQOFFSG(4), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateChase][kCdudePostureF] }, 173 }, 174 175 // burn search 176 { 177 { kAiStateSearch, SEQOFFSC(3), -1, 3600, enterBurnSearchWater, moveNormal, NULL, &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureL] }, 178 { kAiStateSearch, SEQOFFSC(3), -1, 3600, enterBurnSearchWater, moveNormal, NULL, &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureC] }, 179 { kAiStateSearch, SEQOFFSC(3), -1, 3600, enterBurnSearchWater, moveNormal, NULL, &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureW] }, 180 { kAiStateSearch, SEQOFFSG(3), -1, 3600, enterBurnSearchWater, moveNormal, NULL, &gCdudeStateTemplate[kCdudeBurnStateSearch][kCdudePostureF] }, 181 }, 182 183 // morph (put thinkFunc in moveFunc because it supposed to work fast) 184 { 185 { kAiStateOther, SEQOFFSC(18), -1, 0, enterMorph, thinkMorph, NULL, NULL }, 186 { kAiStateOther, SEQOFFSC(18), -1, 0, enterMorph, thinkMorph, NULL, NULL }, 187 { kAiStateOther, SEQOFFSC(18), -1, 0, enterMorph, thinkMorph, NULL, NULL }, 188 { kAiStateOther, SEQOFFSG(0), -1, 0, enterMorph, thinkMorph, NULL, NULL }, 189 }, 190 191 // knock enter 192 { 193 { kAiStateKnockout, SEQOFFSC(0), -1, 0, enterKnock, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureL] }, 194 { kAiStateKnockout, SEQOFFSC(0), -1, 0, enterKnock, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureC] }, 195 { kAiStateKnockout, SEQOFFSC(0), -1, 0, enterKnock, moveKnockout, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureW] }, 196 { kAiStateKnockout, SEQOFFSG(0), -1, 0, enterKnock, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnock][kCdudePostureF] }, 197 }, 198 199 // knock 200 { 201 { kAiStateKnockout, SEQOFFSC(0), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureL] }, 202 { kAiStateKnockout, SEQOFFSC(0), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureC] }, 203 { kAiStateKnockout, SEQOFFSC(0), -1, 0, NULL, moveKnockout, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureW] }, 204 { kAiStateKnockout, SEQOFFSG(0), -1, 0, NULL, NULL, NULL, &gCdudeStateTemplate[kCdudeStateKnockExit][kCdudePostureF] }, 205 }, 206 207 // knock exit 208 { 209 { kAiStateKnockout, SEQOFFSC(0), -1, 0, exitKnock, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, 210 { kAiStateKnockout, SEQOFFSC(0), -1, 0, exitKnock, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, 211 { kAiStateKnockout, SEQOFFSC(0), -1, 0, exitKnock, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, 212 { kAiStateKnockout, SEQOFFSG(0), -1, 0, exitKnock, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureF] }, 213 }, 214 215 // sleep 216 { 217 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterSleep, NULL, thinkTarget, NULL }, 218 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterSleep, NULL, thinkTarget, NULL }, 219 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterSleep, NULL, thinkTarget, NULL }, 220 { kAiStateIdle, SEQOFFSG(0), -1, 0, enterSleep, NULL, thinkTarget, NULL }, 221 }, 222 223 // wake 224 { 225 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterWake, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureL] }, 226 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterWake, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureC] }, 227 { kAiStateIdle, SEQOFFSC(0), -1, 0, enterWake, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureW] }, 228 { kAiStateIdle, SEQOFFSG(0), -1, 0, enterWake, turnToTarget, NULL, &gCdudeStateTemplate[kCdudeStateSearch][kCdudePostureF] }, 229 }, 230 231 // generic idle (ai fight compat.) 232 { 233 { kAiStateGenIdle, SEQOFFSC(0), -1, 0, resetTarget, NULL, NULL, NULL }, 234 { kAiStateGenIdle, SEQOFFSC(17), -1, 0, resetTarget, NULL, NULL, NULL }, 235 { kAiStateGenIdle, SEQOFFSC(13), -1, 0, resetTarget, NULL, NULL, NULL }, 236 { kAiStateGenIdle, SEQOFFSG(0), -1, 0, resetTarget, NULL, NULL, NULL }, 237 }, 238 }; 239 240 // Land, Crouch, Swim, Fly 241 AISTATE gCdudeStateAttackTemplate[kCdudePostureMax] = 242 { 243 // attack (put thinkFunc in moveFunc because it supposed to work fast) 244 { kAiStateAttack, SEQOFFSC(6), nWeaponShot, 0, enterAttack, thinkChase, NULL, &gCdudeStateAttackTemplate[kCdudePostureL] }, 245 { kAiStateAttack, SEQOFFSC(8), nWeaponShot, 0, enterAttack, thinkChase, NULL, &gCdudeStateAttackTemplate[kCdudePostureC] }, 246 { kAiStateAttack, SEQOFFSC(8), nWeaponShot, 0, enterAttack, thinkChase, NULL, &gCdudeStateAttackTemplate[kCdudePostureW] }, 247 { kAiStateAttack, SEQOFFSG(6), nWeaponShot, 0, enterAttack, thinkChase, NULL, &gCdudeStateAttackTemplate[kCdudePostureF] }, 248 }; 249 250 // Land, Crouch, Swim, Fly or random pick 251 AISTATE gCdudeStateDyingTemplate[kCdudePostureMax] = 252 { 253 // dying 254 { kAiStateOther, SEQOFFSC(1), -1, 0, enterDying, NULL, thinkDying, &gCdudeStateDeath }, 255 { kAiStateOther, SEQOFFSC(1), -1, 0, enterDying, NULL, thinkDying, &gCdudeStateDeath }, 256 { kAiStateOther, SEQOFFSC(1), -1, 0, enterDying, NULL, thinkDying, &gCdudeStateDeath }, 257 { kAiStateOther, SEQOFFSG(1), -1, 0, enterDying, NULL, thinkDying, &gCdudeStateDeath }, 258 }; 259 260 // for kModernThingThrowableRock 261 static short gCdudeDebrisPics[6] = 262 { 263 2406, 2280, 2185, 2155, 2620, 3135 264 }; 265 266 static int weaponShotHitscan(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 267 { 268 VECTORDATA* pVect = &gVectorData[pWeap->id]; 269 spritetype* pSpr = pDude->pSpr; 270 int t; 271 272 // ugly hack to make it fire at required distance 273 t = pVect->maxDist, pVect->maxDist = pWeap->GetDistance(); 274 actFireVector(pSpr, pOffs->x, pOffs->z, dx, dy, dz, (VECTOR_TYPE)pWeap->id); 275 pVect->maxDist = t; 276 277 return kMaxSprites; 278 } 279 280 static int weaponShotMissile(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 281 { 282 spritetype* pSpr = pDude->pSpr, *pShot; 283 XSPRITE* pXSpr = pDude->pXSpr, *pXShot; 284 285 pShot = nnExtFireMissile(pSpr, pOffs->x, pOffs->z, dx, dy, dz, pWeap->id); 286 if (pShot) 287 { 288 pXShot = &xsprite[pShot->extra]; 289 nnExtOffsetSprite(pShot, 0, pOffs->y, 0); 290 291 if (pWeap->shot.clipdist) 292 pShot->clipdist = pWeap->shot.clipdist; 293 294 if (pWeap->HaveVelocity()) 295 { 296 pXShot->target = -1; // have to erase, so vanilla won't set velocity back 297 nnExtScaleVelocity(pShot, pWeap->shot.velocity, dx, dy, dz); 298 } 299 300 pWeap->shot.appearance.Set(pShot, pSpr); 301 302 if (pWeap->shot.targetFollow) 303 { 304 pXShot->goalAng = pWeap->shot.targetFollow; 305 gFlwSpritesList.Add(pShot->index); 306 pXShot->sysData1 = pXSpr->target; 307 pXShot->target = -1; // have own target follow code 308 } 309 310 311 return pShot->index; 312 } 313 314 return ATTACK_FAIL; 315 } 316 317 static int weaponShotThing(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 318 { 319 spritetype* pSpr = pDude->pSpr, *pShot; 320 XSPRITE* pXSpr = pDude->pXSpr, * pXShot; 321 322 int nTime = 120 * Random(2) + 120; 323 int nDist = approxDist(dx, dy), nSlope = 12000, nDiv = 540; 324 int nVel = divscale23(nDist / nDiv, 120); 325 int tx = pSpr->x + dx, ty = pSpr->y + dy; 326 int ta = pSpr->ang; 327 328 nSlope = (pWeap->HaveSlope()) ? pWeap->shot.slope : ((dz / 128) - nSlope); 329 330 pSpr->ang = getangle(pSpr->x - tx, pSpr->y - ty); 331 pShot = actFireThing(pSpr, -pOffs->x, pOffs->z, nSlope, pWeap->id, nVel); 332 pSpr->ang = ta; 333 334 if (pShot) 335 { 336 nnExtOffsetSprite(pShot, 0, pOffs->y, 0); 337 338 THINGINFO* pInfo = &thingInfo[pWeap->id - kThingBase]; 339 pShot->picnum = ClipLow(pInfo->picnum, 0); 340 pXShot = &xsprite[pShot->extra]; 341 pShot->owner = pSpr->index; 342 343 switch (pWeap->id) 344 { 345 case kThingArmedProxBomb: 346 case kModernThingTNTProx: 347 pXShot->state = 0; 348 fallthrough__; 349 case kThingArmedRemoteBomb: 350 case kThingArmedTNTBundle: 351 case kThingArmedTNTStick: 352 case kThingTNTBarrel: 353 if (pWeap->data1 > 0) 354 { 355 nTime = pWeap->data1; 356 if (pWeap->data2 > pWeap->data1) 357 nTime += Random(pWeap->data2 - pWeap->data1); 358 } 359 break; 360 case kModernThingThrowableRock: 361 pShot->picnum = gCdudeDebrisPics[Random(LENGTH(gCdudeDebrisPics))]; 362 pShot->xrepeat = pShot->yrepeat = 24 + Random(42); 363 pShot->cstat |= CSTAT_SPRITE_BLOCK; 364 pShot->pal = 5; 365 366 if (Chance(0x5000)) pShot->cstat |= CSTAT_SPRITE_XFLIP; 367 if (Chance(0x5000)) pShot->cstat |= CSTAT_SPRITE_YFLIP; 368 369 if (pWeap->data1 > 0) 370 { 371 pXShot->data1 = pWeap->data1; 372 if (pWeap->data2 > pWeap->data1) 373 pXShot->data1 += Random(pWeap->data2 - pWeap->data1); 374 } 375 else if (pShot->xrepeat > 60) pXShot->data1 = 43; 376 else if (pShot->xrepeat > 40) pXShot->data1 = 33; 377 else if (pShot->xrepeat > 30) pXShot->data1 = 23; 378 else pXShot->data1 = 12; 379 break; 380 case kThingBloodBits: 381 case kThingBloodChunks: 382 DudeToGibCallback1(pShot->index, pShot->extra); 383 break; 384 case kThingNapalmBall: 385 pShot->xrepeat = pShot->yrepeat = 24; 386 if (pWeap->data1 > 0) 387 { 388 pXShot->data4 = pWeap->data1; 389 if (pWeap->data2 > pWeap->data1) 390 pXShot->data4 += Random(pWeap->data2 - pWeap->data1); 391 } 392 else pXShot->data4 = 3 + Random2(2); 393 break; 394 case kModernThingEnemyLifeLeech: 395 case kThingDroppedLifeLeech: 396 pXShot->health = ((pInfo->startHealth << 4) * ClipLow(gGameOptions.nDifficulty, 1)) >> 1; 397 pShot->cstat &= ~CSTAT_SPRITE_BLOCK; 398 pShot->pal = 6; 399 pShot->clipdist = 0; 400 pXShot->data3 = 512 / (gGameOptions.nDifficulty + 1); 401 pXShot->target = pXSpr->target; 402 pXShot->Proximity = true; 403 pXShot->stateTimer = 1; 404 405 evPost(pShot->index, 3, 80, kCallbackLeechStateTimer); 406 pDude->pXLeech = &xsprite[pShot->extra]; 407 break; 408 } 409 410 if (pWeap->shot.clipdist) pShot->clipdist = pWeap->shot.clipdist; 411 if (pWeap->HaveVelocity()) nnExtScaleVelocity(pShot, -(pWeap->shot.velocity<<3), dx, dy, dz, 0x01); 412 413 pWeap->shot.appearance.Set(pShot, pSpr); 414 415 if (pWeap->shot.targetFollow) 416 { 417 pXShot->goalAng = pWeap->shot.targetFollow; 418 pXShot->sysData1 = pXSpr->target; 419 gFlwSpritesList.Add(pShot->index); 420 } 421 422 pXShot->Impact = pWeap->shot.impact; 423 424 if (!pXShot->Impact) 425 evPost(pShot->index, OBJ_SPRITE, nTime, kCmdOn, pSpr->index); 426 427 return pShot->index; 428 } 429 430 return ATTACK_FAIL; 431 } 432 433 static int weaponShotSummon(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 434 { 435 spritetype* pShot, *pSpr = pDude->pSpr; 436 XSPRITE *pXShot, *pXSpr = pDude->pXSpr; 437 438 int x = pSpr->x+dx, y = pSpr->y+dy, z = pSpr->z+dz, a = 0; 439 440 int nDude = pWeap->id; 441 if (pWeap->type == kCdudeWeaponSummonCdude) 442 nDude = kDudeModernCustom; 443 444 nnExtOffsetPos(pOffs->x, ClipLow(pOffs->y, 800), pOffs->z, pSpr->ang, &x, &y, &z); 445 446 while (a < kAng180) 447 { 448 if (!posObstructed(x, y, z, 32)) 449 { 450 if ((pShot = nnExtSpawnDude(pSpr, nDude, x, y, z)) != NULL) 451 { 452 pXShot = &xsprite[pShot->extra]; 453 if (nDude == kDudeModernCustom) 454 pXShot->data1 = pWeap->id; 455 456 if (pWeap->shot.clipdist) 457 pShot->clipdist = pWeap->shot.clipdist; 458 459 if (pWeap->HaveVelocity()) 460 nnExtScaleVelocity(pShot, pWeap->shot.velocity, dx, dy, dz); 461 462 if (pWeap->data1) 463 { 464 int nHealth = ClipHigh(pWeap->data1, 65535); 465 pXShot->health = ClipHigh(nHealth << 4, 65535); 466 pXShot->data4 = pXShot->sysData2 = nHealth; 467 } 468 469 aiInitSprite(pShot); 470 471 pWeap->shot.appearance.Set(pShot, pSpr); 472 473 pXShot->targetX = pXSpr->targetX; 474 pXShot->targetY = pXSpr->targetY; 475 pXShot->targetZ = pXSpr->targetZ; 476 pXShot->target = pXSpr->target; 477 pShot->ang = pSpr->ang; 478 479 aiActivateDude(pShot, pXShot); 480 481 pDude->slaves.list->Add(pShot->index); 482 gKillMgr.AddCount(pShot); 483 return pShot->index; 484 } 485 } 486 else 487 { 488 RotatePoint(&x, &y, a, pSpr->x, pSpr->y); 489 a += kAng15; 490 continue; 491 } 492 493 break; 494 } 495 496 return ATTACK_FAIL; 497 } 498 499 static int weaponShotKamikaze(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int, int, int) 500 { 501 spritetype* pSpr = pDude->pSpr; 502 spritetype* pShot = actSpawnSprite(pSpr->sectnum, pSpr->x, pSpr->y, pSpr->z, kStatExplosion, true); 503 XSPRITE* pXSpr = pDude->pXSpr; 504 int nShot = -1; 505 506 if (pShot) 507 { 508 int nType = pWeap->id - kTrapExploder; 509 XSPRITE* pXShot = &xsprite[pShot->extra]; 510 EXPLOSION* pExpl = &explodeInfo[nType]; 511 EXPLOSION_EXTRA* pExtra = &gExplodeExtra[nType]; 512 513 pShot->type = nType; 514 pShot->cstat |= CSTAT_SPRITE_INVISIBLE; 515 pShot->owner = pSpr->index; 516 pShot->shade = -127; 517 pShot->yrepeat = pShot->xrepeat = pExpl->repeat; 518 pShot->ang = pSpr->ang; 519 520 pXShot->data1 = pExpl->ticks; 521 pXShot->data2 = pExpl->quakeEffect; 522 pXShot->data3 = pExpl->flashEffect; 523 pXShot->data4 = ClipLow(pWeap->GetDistance() >> 4, pExpl->radius); 524 525 seqSpawn(pExtra->seq, OBJ_SPRITE, pShot->extra, -1); 526 527 if (pExtra->ground) 528 pShot->z = getflorzofslope(pShot->sectnum, pShot->x, pShot->y); 529 530 pWeap->shot.appearance.Set(pShot, pSpr); 531 532 clampSprite(pShot); 533 nnExtOffsetSprite(pShot, pOffs->x, pOffs->y, pOffs->z); // offset after default sprite placement 534 nShot = pShot->index; 535 } 536 537 if (pXSpr->health) 538 { 539 pXSpr->health = 0; // it supposed to attack once 540 pDude->Kill(pSpr->index, kDamageExplode, 0x10000); 541 } 542 543 return nShot; 544 } 545 546 static int weaponShotSpecialBeastStomp(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeapon, POINT3D*, int, int, int) 547 { 548 spritetype* pSpr = pDude->pSpr; 549 550 int i, j; 551 int vc = 400 << 4; 552 int v1c = 7 * gGameOptions.nDifficulty; 553 int v10 = 55 * gGameOptions.nDifficulty; 554 555 char tmp[] = { kStatDude, kStatThing }; 556 for (i = 0; i < LENGTH(tmp); i++) 557 { 558 for (j = headspritestat[tmp[i]]; j >= 0; j = nextspritestat[j]) 559 { 560 spritetype* pSpr2 = &sprite[j]; 561 if (pSpr2->index == pSpr->index || !xsprIsFine(pSpr2) || pSpr2->owner == pSpr->index) 562 continue; 563 564 if (CheckProximity(pSpr2, pSpr->x, pSpr->y, pSpr->z, pSpr->sectnum, pWeapon->GetDistance() << 4)) 565 { 566 int dx = klabs(pSpr->x - pSpr2->x); 567 int dy = klabs(pSpr->y - pSpr2->y); 568 int nDist2 = ksqrt(dx * dx + dy * dy); 569 if (nDist2 <= vc) 570 { 571 int nDamage; 572 if (!nDist2) 573 nDamage = v1c + v10; 574 else 575 nDamage = v1c + ((vc - nDist2) * v10) / vc; 576 577 if (IsPlayerSprite(pSpr2)) 578 { 579 PLAYER* pPlayer = &gPlayer[pSpr2->type - kDudePlayer1]; 580 pPlayer->quakeEffect = ClipHigh(pPlayer->quakeEffect + (nDamage << 2), 1280); 581 } 582 583 actDamageSprite(pSpr->index, pSpr2, kDamageFall, nDamage << 4); 584 } 585 } 586 } 587 } 588 589 return kMaxSprites; 590 } 591 592 static int weaponShotSpecialRam(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 593 { 594 UNREFERENCED_PARAMETER(pOffs); 595 596 spritetype *pSpr = pDude->pSpr, *pHSpr; 597 XSPRITE *pXSpr = pDude->pXSpr, *pXHSpr; 598 SPRITEHIT *pTouch = &gSpriteHit[pSpr->extra]; 599 600 int cz, fz, cf, zt, zb, zv, x, y, z, s, d, k, m; 601 int tx = pXSpr->targetX - pSpr->x; 602 int ty = pXSpr->targetY - pSpr->y; 603 int nDist, nAng, nDAng, nTouch; 604 605 char targetOnly = (pWeap->data1 & 0x001) == 0; 606 char touchOnce = (pWeap->data1 & 0x002) != 0; 607 char absoluteVel = (pWeap->data1 & 0x004) != 0; 608 char scaleDmg = (pWeap->data1 & 0x008) == 0; 609 char checkCF = (pWeap->data1 & 0x010) == 0; 610 char keepDrag = (pWeap->data1 & 0x020) != 0; 611 char stopOnAng = (pWeap->data1 & 0x040) != 0; 612 char scaleKick = (pWeap->data1 & 0x080) != 0; 613 char stopOnDist = (pWeap->data1 & 0x100) == 0; 614 615 nTouch = pXSpr->target; 616 nAng = getangle(tx, ty); 617 618 while ( 1 ) 619 { 620 if ((pTouch->hit & 0xc000) == 0x8000) 621 break; 622 623 if ((pTouch->hit & 0xc000) == 0xc000) 624 { 625 pHSpr = &sprite[pTouch->hit & 0x3fff]; 626 if (pHSpr->extra <= 0) 627 break; 628 629 if (targetOnly && pXSpr->target != pHSpr->index) 630 break; 631 632 pXHSpr = &xsprite[pHSpr->extra]; 633 if ((pXHSpr->physAttr & kPhysDebrisTouch) == 0) 634 { 635 if (pHSpr->statnum != kStatDude && pHSpr->statnum != kStatThing) 636 break; 637 638 if ((pHSpr->flags & (kPhysMove | kPhysGravity)) == 0) 639 break; 640 } 641 642 k = pWeap->data4 << 8; 643 if (scaleKick && k > 0) 644 { 645 if (IsDudeSprite(pHSpr)) m = getDudeInfo(pHSpr->type)->mass; 646 else if (IsCustomDude(pHSpr)) m = cdudeGet(pHSpr->index)->mass; 647 else m = 0; 648 649 if (m) 650 k = divscale4(k, m); 651 } 652 653 d = (scaleDmg) ? mulscale18(pWeap->data3, approxDist(xvel[pSpr->index], yvel[pSpr->index])) : pWeap->data3; 654 helperPounce(pSpr, pHSpr, pWeap->data2, d, k); 655 if (touchOnce) 656 break; 657 658 s = pHSpr->sectnum, x = pHSpr->x, y = pHSpr->y, z = pHSpr->z; 659 ClipMove(&x, &y, &z, &s, xvel[pHSpr->index] >> 12, yvel[pHSpr->index] >> 12, pHSpr->clipdist << 2, 0, 0, CLIPMASK0); 660 if (approxDist(x - pHSpr->x, y - pHSpr->y) <= 0) 661 break; 662 663 pDude->goalZ = pXSpr->targetZ; 664 checkCF = (keepDrag == 0); 665 nTouch = pHSpr->index; 666 } 667 668 nDist = pWeap->GetDistance(); 669 nDAng = klabs(DANGLE(nAng, pXSpr->goalAng)); 670 if (nDAng > kAng90 && (stopOnAng || (stopOnDist && approxDist(tx, ty) >= nDist))) break; 671 else if (!CanMove(pSpr, nTouch, pSpr->ang, pSpr->clipdist << 2)) break; 672 else if (checkCF && pDude->IsFlying()) 673 { 674 cf = pDude->flight.cfDist, zv = zvel[pSpr->index]; 675 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); GetSpriteExtents(pSpr, &zt, &zb); 676 if ((zv > 0 && zb >= fz - cf) || (zv < 0 && zt <= cz + cf)) 677 break; 678 } 679 680 if (pWeap->HaveVelocity()) 681 { 682 if (!pDude->IsFlying() && !pWeap->HaveSlope()) 683 dz = 0; 684 685 if (!absoluteVel && !pDude->IsFlying()) 686 nnExtScaleVelocityRel(pDude->pSpr, pWeap->shot.velocity, dx, dy, dz); 687 else 688 nnExtScaleVelocity(pDude->pSpr, pWeap->shot.velocity, dx, dy, dz); 689 } 690 691 // continue to move 692 return ATTACK_CONTINUE; 693 } 694 695 pXSpr->goalAng = (nAng + Random2(kAng45)) & kAngMask; 696 pDude->NewState(kCdudeStateSearch); 697 return ATTACK_FAIL; 698 } 699 700 static int weaponShotSpecialTeleport(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 701 { 702 spritetype* pSpr = pDude->pSpr; XSPRITE* pXSpr = pDude->pXSpr; 703 int ox, rx, x, oy, ry, y, oz, z, s; 704 int c = 32; 705 706 x = ox = dx, y = oy = dy, z = oz = pXSpr->targetZ - dz; 707 s = pSpr->sectnum; 708 709 nnExtOffsetPos(pOffs, pSpr->ang, &x, &y, &z); 710 rx = ox - x, ry = oy - y; 711 712 while (--c >= 0) 713 { 714 if (!FindSector(x, y, z, &s)) 715 { 716 if (c > 0) 717 { 718 x = ox + Random(rx); 719 y = oy + Random(ry); 720 z = oz; 721 } 722 else 723 { 724 x = pXSpr->targetX; 725 y = pXSpr->targetY; 726 z = pXSpr->targetZ; 727 } 728 729 continue; 730 } 731 732 pSpr->x = x; 733 pSpr->y = y; 734 pSpr->z = z; 735 736 if (s != pSpr->sectnum) 737 ChangeSpriteSect(pSpr->index, s); 738 739 if (pWeap->turnToTarget) 740 pXSpr->goalAng = getTargetAng(pSpr, pXSpr), pSpr->ang = pXSpr->goalAng; 741 742 if (pWeap->HaveVelocity()) 743 nnExtScaleVelocity(pDude->pSpr, pWeap->shot.velocity, dx, dy, dz); 744 745 clampSprite(pSpr); 746 747 if (!pDude->IsFlying() && pSpr->flags & kPhysGravity) 748 pSpr->flags |= kPhysFalling; 749 750 break; 751 } 752 753 return kMaxSprites; 754 } 755 756 static int weaponShotSpecial(CUSTOMDUDE* pDude, CUSTOMDUDE_WEAPON* pWeap, POINT3D* pOffs, int dx, int dy, int dz) 757 { 758 switch (pWeap->id) 759 { 760 case kCdudeWeaponIdSpecialBeastStomp: return weaponShotSpecialBeastStomp(pDude, pWeap, pOffs, dx, dy, dz); 761 case kCdudeWeaponIdSpecialRam: return weaponShotSpecialRam(pDude, pWeap, pOffs, dx, dy, dz); 762 case kCdudeWeaponIdSpecialTeleport: return weaponShotSpecialTeleport(pDude, pWeap, pOffs, dx, dy, dz); 763 default: return ATTACK_FAIL; 764 } 765 } 766 767 static void weaponShot(int, int nXIndex) 768 { 769 if (!xspriRangeIsFine(nXIndex)) 770 return; 771 772 XSPRITE* pXSpr = &xsprite[nXIndex]; 773 CUSTOMDUDE* pDude = cdudeGet(pXSpr->reference); 774 CUSTOMDUDE_WEAPON* pMainWeap = pDude->pWeapon, *pWeap; 775 spritetype* pSpr = pDude->pSpr, *pShot, *pTarg; 776 CUSTOMDUDE_WEAPON::PREDICTION* pPredict; 777 POINT3D shotOffs, *pStyleOffs; 778 779 int nAng, nDang, nShots, nTime, nCode, nDist; 780 int dx1, dy1, dz1, tx, ty, tx2, ty2; 781 int dx2, dy2, dz2=0, dx3=0, dy3=0, dz3; 782 int sx, sy, i, j, t; 783 784 int txof; char hxof=0; 785 int sang=0; int hsht; 786 int tang=0; char styled; 787 788 if (!pMainWeap) 789 return; 790 791 for (i = 0; i < pDude->numWeapons && pDude->IsAttacking(); i++) 792 { 793 pWeap = &pDude->weapons[i]; 794 if (!pWeap->available) 795 continue; 796 797 if (pMainWeap != pWeap) 798 { 799 // check if this weapon could be used in conjunction with current 800 if (!pMainWeap->sharedId || pMainWeap->sharedId != pWeap->sharedId) 801 continue; 802 } 803 804 if (pDude->numAvailWeapons >= 2 // check if weapon must shot on this seq frame index 805 && pWeap->pFrames && !pWeap->pFrames->Exists(seqGetStatus(OBJ_SPRITE, pSpr->extra))) 806 continue; 807 808 nShots = pWeap->GetNumshots(); pWeap->ammo.Dec(nShots); 809 styled = (nShots > 1 && pWeap->style.available); 810 shotOffs = pWeap->shot.offset; 811 pPredict = &pWeap->prediction; 812 813 nAng = pSpr->ang; 814 sx = pSpr->x, sy = pSpr->y; 815 tx = pXSpr->targetX, ty = pXSpr->targetY; 816 nDang = DANGLE(pSpr->ang, getangle(tx - sx, ty - sy)); 817 818 if (klabs(nDang) < pWeap->angle) 819 { 820 nnExtOffsetPos(&shotOffs, nAng, &sx, &sy, NULL); 821 nAng = getangle(tx - sx, ty - sy); 822 } 823 824 if (pPredict->distance && spriRangeIsFine(pXSpr->target)) 825 { 826 // this is similar to TARGETTRACK 827 pTarg = &sprite[pXSpr->target], tx = pTarg->x, ty = pTarg->y; 828 nDist = approxDist(tx-sx, ty-sy); 829 830 if ((xvel[pTarg->index] || yvel[pTarg->index]) && nDist < (int)pPredict->distance) 831 { 832 j = divscale12(nDist, pPredict->accuracy); 833 tx2 = tx + ((xvel[pTarg->index]*j) >> 12); 834 ty2 = ty + ((yvel[pTarg->index]*j) >> 12); 835 j = getangle(tx2 - sx, ty2 - sy); 836 837 nDang = DANGLE(j, nAng); 838 if (klabs(nDang) < pPredict->angle) 839 nAng = j, tx = tx2, ty = ty2; 840 } 841 } 842 843 switch (pWeap->type) 844 { 845 case kCdudeWeaponSummon: 846 case kCdudeWeaponSummonCdude: 847 dx1 = 0; 848 dy1 = 0; 849 break; 850 case kCdudeWeaponThrow: 851 dx1 = sx - tx; 852 dy1 = sy - ty; 853 break; 854 default: 855 switch (pWeap->id) 856 { 857 case kCdudeWeaponIdSpecialTeleport: 858 dx1 = tx; 859 dy1 = ty; 860 break; 861 default: 862 dx1 = Cos(nAng) >> 16; 863 dy1 = Sin(nAng) >> 16; 864 break; 865 } 866 break; 867 } 868 869 if (styled) 870 { 871 t = nShots; 872 pStyleOffs = &pWeap->style.offset; hsht = t >> 1; 873 if (t % 2) 874 t++; 875 876 sang = pWeap->style.angle / t; 877 hxof = 0; 878 tang = 0; 879 } 880 881 dz1 = (pWeap->shot.slope == INT32_MAX) ? 882 pDude->AdjustSlope(pXSpr->target, pWeap->shot.offset.z) : pWeap->shot.slope; 883 884 nCode = -1; 885 for (j = nShots; j > 0 && pDude->IsAttacking(); j--) 886 { 887 if (!styled || j == nShots) 888 { 889 dx3 = Random3(pWeap->dispersion[0]); 890 dy3 = Random3(pWeap->dispersion[0]); 891 dz3 = Random3(pWeap->dispersion[1]); 892 893 dx2 = dx1 + dx3; 894 dy2 = dy1 + dy3; 895 dz2 = dz1 + dz3; 896 } 897 898 nCode = gWeaponShotFunc[pWeap->type](pDude, pWeap, &shotOffs, dx2, dy2, dz2); 899 900 if (nCode >= 0) 901 { 902 pShot = (nCode < kMaxSprites) ? &sprite[nCode] : NULL; 903 if (pShot) 904 { 905 pShot->ang = nAng; 906 if ((pShot->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_WALL) 907 pShot->ang = (pShot->ang + kAng90) & kAngMask; 908 909 // override removal timer 910 if ((nTime = pWeap->shot.remTime) >= 0) 911 { 912 evKill(pShot->index, OBJ_SPRITE, kCallbackRemove); 913 if (nTime) 914 evPost(pShot->index, OBJ_SPRITE, nTime, kCallbackRemove); 915 } 916 } 917 918 // setup style 919 if (styled) 920 { 921 if (pStyleOffs->x) 922 { 923 txof = pStyleOffs->x; 924 if (j <= hsht) 925 { 926 if (!hxof) 927 { 928 shotOffs.x = pWeap->shot.offset.x; 929 hxof = 1; 930 } 931 932 txof = -txof; 933 } 934 935 shotOffs.x += txof; 936 } 937 938 shotOffs.y += pStyleOffs->y; 939 shotOffs.z += pStyleOffs->z; 940 941 if (pWeap->style.angle) 942 { 943 // for sprites 944 if (pShot) 945 { 946 if (j <= hsht && sang > 0) 947 { 948 sang = -sang; 949 tang = sang; 950 } 951 952 RotatePoint(&xvel[pShot->index], &yvel[pShot->index], tang, pSpr->x, pSpr->y); 953 954 pShot->ang = getVelocityAngle(pShot); 955 if ((pShot->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_WALL) 956 pShot->ang = (pShot->ang + kAng90) & kAngMask; 957 958 tang += sang; 959 960 } 961 // for hitscan 962 else 963 { 964 if (j <= hsht && sang > 0) 965 { 966 nnExtCoSin(pSpr->ang, &dx2, &dy2); 967 dx2 += dx3; dy2 += dy3; 968 sang = -sang; 969 } 970 971 RotatePoint(&dx2, &dy2, sang, pSpr->x, pSpr->y); 972 } 973 } 974 } 975 } 976 } 977 978 pWeap->shotSound.Play(pSpr); 979 if (nCode != ATTACK_CONTINUE && pWeap->cooldown.Check()) 980 pWeap->available = 0; 981 } 982 } 983 984 static int checkTarget(CUSTOMDUDE* pDude, spritetype* pTarget, TARGET_INFO* pOut) 985 { 986 spritetype*pSpr = pDude->pSpr; 987 if (!xspriRangeIsFine(pTarget->extra)) 988 return -1; 989 990 XSPRITE* pXTarget = &xsprite[pTarget->extra]; 991 if (pSpr->owner == pTarget->index || pXTarget->health <= 0) 992 return -2; 993 994 if (IsPlayerSprite(pTarget)) 995 { 996 PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1]; 997 if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) 998 return -3; 999 } 1000 1001 int x = pTarget->x; 1002 int y = pTarget->y; 1003 int z = pTarget->z; 1004 int nSector = pTarget->sectnum; 1005 int dx = x - pSpr->x; 1006 int dy = y - pSpr->y; 1007 int nDist = approxDist(dx, dy); 1008 int nHeigh = pDude->eyeHeight; 1009 char s = (nDist < pDude->seeDist); 1010 char h = (nDist < pDude->hearDist); 1011 1012 if (!s && !h) 1013 return -4; 1014 1015 if (pDude->IsFlipped()) nHeigh = -nHeigh; 1016 if (!cansee(x, y, z, nSector, pSpr->x, pSpr->y, pSpr->z - nHeigh, pSpr->sectnum)) 1017 return -5; 1018 1019 int nAng = getangle(dx, dy); 1020 if (s) 1021 { 1022 int nDang = klabs(((nAng + kAng180 - pSpr->ang) & kAngMask) - kAng180); 1023 if (nDang <= pDude->periphery) 1024 { 1025 pOut->pSpr = pTarget; 1026 pOut->pXSpr = pXTarget; 1027 pOut->nDist = nDist; 1028 pOut->nDang = nDang; 1029 pOut->nAng = nAng; 1030 pOut->nCode = 1; 1031 return 1; 1032 } 1033 } 1034 1035 if (h) 1036 { 1037 pOut->pSpr = pTarget; 1038 pOut->pXSpr = pXTarget; 1039 pOut->nDist = nDist; 1040 pOut->nDang = 0; 1041 pOut->nAng = nAng; 1042 pOut->nCode = 2; 1043 return 2; 1044 } 1045 1046 return -255; 1047 } 1048 1049 static void thinkTarget(spritetype* pSpr, XSPRITE* pXSpr) 1050 { 1051 int i; spritetype* pTarget; 1052 TARGET_INFO targets[kMaxPlayers], *pInfo = targets; 1053 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1054 int numTargets = 0; 1055 1056 if (Chance(pDude->pInfo->alertChance)) 1057 { 1058 for (i = connecthead; i >= 0; i = connectpoint2[i]) 1059 { 1060 PLAYER* pPlayer = &gPlayer[i]; 1061 if (checkTarget(pDude, pPlayer->pSprite, &targets[numTargets]) > 0) 1062 numTargets++; 1063 } 1064 1065 #if 0 1066 if (pDude->pExtra->stats.active && Chance(0x3000) && numTargets <= 0) 1067 { 1068 int nClosest = 0x7FFFFF; 1069 int nChance = 0x800; 1070 1071 for (i = headspritestat[kStatDude]; i >= 0; i = nextspritestat[i]) 1072 { 1073 pTarget = &sprite[i]; 1074 if (pTarget->type != kDudeInnocent) 1075 continue; 1076 1077 if (checkTarget(pDude, pTarget, &targets[1]) > 0) 1078 { 1079 if (targets[1].nDist < nClosest) 1080 { 1081 Bmemcpy(pInfo, &targets[1], sizeof(targets[1])); 1082 nClosest = targets[1].nDist; 1083 numTargets = 1; 1084 1085 if (Chance(nChance)) 1086 { 1087 aiActivateDude(pInfo->pSpr, pInfo->pXSpr); 1088 break; 1089 } 1090 1091 nChance += 0x1000; 1092 } 1093 } 1094 } 1095 } 1096 #endif 1097 1098 if (numTargets) 1099 { 1100 if (numTargets > 1) // closest first 1101 qsort(targets, numTargets, sizeof(targets[0]), (int(*)(const void*, const void*))qsSortTargets); 1102 1103 pTarget = pInfo->pSpr; 1104 1105 if (pXSpr->target != pTarget->index || Chance(0x0400)) 1106 pDude->PlaySound(kCdudeSndTargetSpot); 1107 1108 pXSpr->goalAng = pInfo->nAng & kAngMask; 1109 if (pInfo->nCode == 1) aiSetTarget(pXSpr, pTarget->index); 1110 else aiSetTarget(pXSpr, pTarget->x, pTarget->y, pTarget->z); 1111 aiActivateDude(pSpr, pXSpr); 1112 } 1113 } 1114 } 1115 1116 static void enterFlee(spritetype* pSpr, XSPRITE*) 1117 { 1118 CUSTOMDUDE* pDude = cdudeGet(pSpr); 1119 if (pDude->pWeapon && pDude->pWeapon->type == kCdudeWeaponNone) 1120 { 1121 if (pDude->pWeapon->available) 1122 { 1123 pDude->pWeapon->ammo.Dec(); 1124 1125 if (pDude->pWeapon->cooldown.Check()) 1126 pDude->pWeapon->available = 0; 1127 } 1128 } 1129 } 1130 1131 static void thinkFlee(spritetype* pSpr, XSPRITE* pXSpr) 1132 { 1133 int nAng = getangle(pSpr->x - pXSpr->targetX, pSpr->y - pXSpr->targetY); 1134 int nDang = klabs(((nAng + kAng180 - pSpr->ang) & kAngMask) - kAng180); 1135 if (nDang > kAng45) 1136 pXSpr->goalAng = (nAng + (kAng15 * Random2(2))) & kAngMask; 1137 1138 aiChooseDirection(pSpr, pXSpr, pXSpr->goalAng); 1139 1140 } 1141 1142 static void enterIdle(spritetype* pSpr, XSPRITE* pXSpr) 1143 { 1144 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1145 pDude->pExtra->stats.thinkTime = 0; 1146 pDude->pExtra->stats.active = 0; 1147 resetTarget(pSpr, pXSpr); 1148 } 1149 1150 static void enterSearch(spritetype* pSpr, XSPRITE* pXSpr) 1151 { 1152 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1153 spritetype* pTarg; 1154 int dz; 1155 1156 if (!pDude->CanFly()) 1157 return; 1158 1159 if (pDude->IsFlying()) 1160 { 1161 pXSpr->goalAng = (pXSpr->goalAng + Random2(kAng45)) & kAngMask; 1162 return; 1163 } 1164 1165 dz = pXSpr->targetZ - pSpr->z; 1166 if (dz >= 0 || klabs(dz) < perc2val(150, pDude->height)) 1167 { 1168 pTarg = spriRangeIsFine(pXSpr->target) ? &sprite[pXSpr->target] : NULL; 1169 if (!pTarg || cansee(pSpr->x, pSpr->y, pSpr->z, pSpr->sectnum, pTarg->x, pTarg->y, pTarg->z, pTarg->sectnum)) 1170 return; 1171 } 1172 1173 zvel[pSpr->index] = pDude->GetStartFlyVel(); 1174 pDude->ChangePosture(kCdudePostureF); 1175 pDude->timer.fLaunch.Set(); 1176 } 1177 1178 static void thinkSearch(spritetype* pSpr, XSPRITE* pXSpr) 1179 { 1180 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1181 1182 if (pDude->timer.moveDir.Pass()) 1183 aiChooseDirection(pSpr, pXSpr, pXSpr->goalAng); 1184 1185 thinkTarget(pSpr, pXSpr); 1186 } 1187 1188 static void thinkChase(spritetype* pSpr, XSPRITE* pXSpr) 1189 { 1190 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); HITINFO* pHit = &gHitInfo; 1191 CUSTOMDUDE_WEAPON* pWeapon = pDude->pWeapon; 1192 CUSTOMDUDE_FLIGHT::TYPE* pFly; 1193 1194 int nDist, nHeigh, dx, dy, dz, nDAng, nSlope = 0; 1195 char thinkTime = pDude->IsThinkTime(); 1196 char turn2target = 0, interrupt = 0; 1197 char inAttack = pDude->IsAttacking(); 1198 char changePos = 0; 1199 1200 if (!spriRangeIsFine(pXSpr->target)) 1201 { 1202 pDude->NewState(kCdudeStateSearch); 1203 return; 1204 } 1205 1206 spritetype* pTarget = &sprite[pXSpr->target]; 1207 if (pTarget->owner == pSpr->index || !IsDudeSprite(pTarget) || !xsprIsFine(pTarget)) // target lost 1208 { 1209 pDude->NewState(kCdudeStateSearch); 1210 return; 1211 } 1212 1213 XSPRITE* pXTarget = &xsprite[pTarget->extra]; 1214 if (pXTarget->health <= 0) // target is dead 1215 { 1216 PLAYER* pPlayer = NULL; 1217 if ((!IsPlayerSprite(pTarget)) || ((pPlayer = getPlayerById(pTarget->type)) != NULL && pPlayer->fraggerId == pSpr->index)) 1218 pDude->PlaySound(kCdudeSndTargetDead); 1219 1220 if (inAttack) pDude->NextState(kCdudeStateSearch); 1221 else pDude->NewState(kCdudeStateSearch); 1222 return; 1223 } 1224 1225 if (IsPlayerSprite(pTarget)) 1226 { 1227 PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1]; 1228 if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) 1229 { 1230 pDude->NewState(kCdudeStateSearch); 1231 return; 1232 } 1233 } 1234 1235 // check target 1236 dx = pTarget->x - pSpr->x; 1237 dy = pTarget->y - pSpr->y; 1238 dz = pTarget->z - pSpr->z; 1239 1240 if (dx == 0) dx = 1; 1241 if (dy == 0) dy = 1; 1242 1243 nDist = approxDist(dx, dy); 1244 nDAng = klabs(((getangle(dx, dy) + kAng180 - pSpr->ang) & kAngMask) - kAng180); 1245 nHeigh = pDude->eyeHeight; 1246 1247 // is the target visible? 1248 if (pDude->IsFlipped()) nHeigh = -nHeigh; 1249 if (nDist > pDude->seeDist || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSpr->x, pSpr->y, pSpr->z - nHeigh, pSpr->sectnum)) 1250 { 1251 if (inAttack) pDude->NextState(kCdudeStateSearch); 1252 else pDude->NewState(kCdudeStateSearch); 1253 return; 1254 } 1255 else if (nDAng > pDude->periphery) 1256 { 1257 if (inAttack) pDude->NextState(kCdudeStateSearch); 1258 else pDude->NewState(kCdudeStateSearch); 1259 return; 1260 } 1261 1262 if (thinkTime) 1263 { 1264 aiSetTarget(pXSpr, pTarget->index); 1265 1266 if (!inAttack) 1267 { 1268 if (pDude->timer.moveDir.Pass() || !CanMove(pSpr, pXSpr->target, pSpr->ang, pSpr->clipdist<<2)) 1269 { 1270 aiChooseDirection(pSpr, pXSpr, getangle(dx, dy)); 1271 pDude->timer.moveDir.Set(); 1272 } 1273 1274 if (pDude->IsFlying()) 1275 { 1276 pFly = &pDude->flight.type[kCdudeFlyLand]; 1277 if (pDude->timer.fLand.Pass() && Chance(pFly->chance) && irngok(nDist, pFly->distance[0], pFly->distance[1])) 1278 { 1279 if (!pFly->distance[2] || rngok(dz, -pFly->distance[2], pFly->distance[2])) 1280 { 1281 pDude->timer.fLand.Set(); 1282 if (Chance(0x8000)) 1283 return; 1284 } 1285 } 1286 } 1287 else if (pDude->CanFly()) 1288 { 1289 pFly = &pDude->flight.type[kCdudeFlyStart]; 1290 if (pDude->timer.fLaunch.Pass() && Chance(pFly->chance) && irngok(nDist, pFly->distance[0], pFly->distance[1])) 1291 { 1292 if (!pFly->distance[2] || dz <= -(int)pFly->distance[2]) 1293 { 1294 zvel[pSpr->index] = pDude->GetStartFlyVel(); 1295 pDude->ChangePosture(kCdudePostureF); 1296 pDude->timer.fLaunch.Set(); 1297 if (Chance(0x8000)) 1298 return; 1299 } 1300 } 1301 } 1302 } 1303 1304 if (Chance(0x2000)) 1305 pDude->PlaySound(kCdudeSndTargetChase); 1306 } 1307 1308 if (pWeapon) 1309 { 1310 nSlope = pDude->AdjustSlope(pXSpr->target, pWeapon->shot.offset.z); 1311 turn2target = pWeapon->turnToTarget; 1312 interrupt = pWeapon->interruptable; 1313 } 1314 1315 ARG_PICK_WEAPON weapData(pSpr, pXSpr, pTarget, pXTarget, nDist, nDAng, nSlope); 1316 1317 // in attack 1318 if (inAttack) 1319 { 1320 if (turn2target && thinkTime) 1321 { 1322 pXSpr->goalAng = getTargetAng(pSpr, pXSpr); 1323 moveTurn(pSpr, pXSpr); 1324 } 1325 1326 if (pXSpr->aiState->stateTicks) // attack timer set 1327 { 1328 if (!pXSpr->stateTimer) 1329 { 1330 pWeapon = pDude->PickWeapon(&weapData); 1331 if (pWeapon && pWeapon == pDude->pWeapon) 1332 { 1333 pDude->pWeapon = pWeapon; 1334 pDude->NewState(pWeapon->stateID); 1335 } 1336 else 1337 pDude->NewState(kCdudeStateChase); 1338 } 1339 else if (interrupt) 1340 { 1341 pDude->PickWeapon(&weapData); 1342 if (!pWeapon->available) 1343 pDude->NewState(kCdudeStateChase); 1344 } 1345 1346 return; 1347 } 1348 1349 if (!pDude->SeqPlaying()) // final frame 1350 { 1351 pWeapon = pDude->PickWeapon(&weapData); 1352 if (!pWeapon) 1353 { 1354 pDude->NewState(kCdudeStateChase); 1355 return; 1356 } 1357 else 1358 { 1359 pDude->pWeapon = pWeapon; 1360 } 1361 } 1362 else // playing previous animation 1363 { 1364 if (!interrupt) 1365 { 1366 if (!pWeapon) 1367 { 1368 pDude->NextState(kCdudeStateChase); 1369 } 1370 1371 return; 1372 } 1373 else 1374 { 1375 pDude->PickWeapon(&weapData); 1376 if (!pWeapon->available) 1377 { 1378 pDude->NewState(kCdudeStateChase); 1379 return; 1380 } 1381 } 1382 } 1383 } 1384 else 1385 { 1386 // enter attack 1387 pWeapon = pDude->PickWeapon(&weapData); 1388 if (pWeapon) 1389 pDude->pWeapon = pWeapon; 1390 } 1391 1392 if (pWeapon) 1393 { 1394 switch (pWeapon->type) 1395 { 1396 case kCdudeWeaponNone: 1397 if (inAttack) pDude->NextState(pDude->CanMove() ? kCdudeStateFlee : kCdudeStateSearch); 1398 else pDude->NewState(pDude->CanMove() ? kCdudeStateFlee : kCdudeStateSearch); 1399 return; 1400 case kCdudeWeaponHitscan: 1401 case kCdudeWeaponMissile: 1402 case kCdudeWeaponThrow: 1403 case kCdudeWeaponSpecial: 1404 if (pDude->CanMove()) 1405 { 1406 HitScan(pSpr, pSpr->z, dx, dy, nSlope, pWeapon->clipMask, nDist); 1407 if (pHit->hitsprite != pXSpr->target && !pDude->AdjustSlope(nDist, &nSlope)) 1408 { 1409 changePos = 1; 1410 if (spriRangeIsFine(pHit->hitsprite)) 1411 { 1412 spritetype* pHitSpr = &sprite[pHit->hitsprite]; 1413 XSPRITE* pXHitSpr = NULL; 1414 if (xsprIsFine(pHitSpr)) 1415 pXHitSpr = &xsprite[pHitSpr->extra]; 1416 1417 if (IsDudeSprite(pHitSpr)) 1418 { 1419 if (pXHitSpr) 1420 { 1421 if (pXHitSpr->target == pSpr->index) 1422 return; 1423 1424 if (pXHitSpr->dodgeDir > 0) 1425 pXSpr->dodgeDir = -pXHitSpr->dodgeDir; 1426 } 1427 } 1428 else if (pHitSpr->owner == pSpr->index) // projectiles, things, fx etc... 1429 { 1430 if (!pXHitSpr || !pXHitSpr->health) 1431 changePos = 0; 1432 } 1433 1434 if (changePos) 1435 { 1436 // prefer dodge 1437 if (pDude->dodge.onAimMiss.Allow()) 1438 { 1439 pDude->NewState(kCdudeStateDodge, 30 * (Random(2) + 1)); 1440 return; 1441 } 1442 } 1443 } 1444 1445 if (changePos) 1446 { 1447 // prefer chase 1448 pDude->NewState(kCdudeStateChase); 1449 return; 1450 } 1451 } 1452 } 1453 fallthrough__; 1454 default: 1455 pDude->NewState(pWeapon->stateID); 1456 if (pDude->IsAttacking()) pDude->NextState(pWeapon->nextStateID); 1457 return; 1458 } 1459 } 1460 1461 if (!pDude->CanMove()) 1462 pDude->NextState(kCdudeStateSearch); 1463 } 1464 1465 static int getTargetAng(spritetype* pSpr, XSPRITE* pXSpr) 1466 { 1467 int x, y; 1468 if (spriRangeIsFine(pXSpr->target)) 1469 { 1470 spritetype* pTarg = &sprite[pXSpr->target]; 1471 x = pTarg->x; 1472 y = pTarg->y; 1473 } 1474 else 1475 { 1476 x = pXSpr->targetX; 1477 y = pXSpr->targetY; 1478 } 1479 1480 return getangle(x - pSpr->x, y - pSpr->y); 1481 } 1482 1483 static void turnToTarget(spritetype* pSpr, XSPRITE* pXSpr) 1484 { 1485 pSpr->ang = getTargetAng(pSpr, pXSpr); 1486 pXSpr->goalAng = pSpr->ang; 1487 } 1488 1489 static void moveTurn(spritetype* pSpr, XSPRITE* pXSpr) 1490 { 1491 CUSTOMDUDE* pDude = cdudeGet(pSpr); 1492 int nVelTurn = pDude->GetVelocity(kParVelocityTurn); 1493 int nAng = ((kAng180 + pXSpr->goalAng - pSpr->ang) & kAngMask) - kAng180; 1494 pSpr->ang = ((pSpr->ang + ClipRange(nAng, -nVelTurn, nVelTurn)) & kAngMask); 1495 } 1496 1497 static void moveDodge(spritetype* pSpr, XSPRITE* pXSpr) 1498 { 1499 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1500 moveTurn(pSpr, pXSpr); 1501 1502 if (pXSpr->dodgeDir && pDude->CanMove()) 1503 { 1504 int nVelDodge = pDude->GetVelocity(kParVelocityDodge); 1505 int nCos = Cos(pSpr->ang); int nSin = Sin(pSpr->ang); 1506 int dX = xvel[pSpr->index]; int dY = yvel[pSpr->index]; 1507 int t1 = dmulscale30(dX, nCos, dY, nSin); int t2 = dmulscale30(dX, nSin, -dY, nCos); 1508 1509 if (pXSpr->dodgeDir > 0) 1510 { 1511 t2 += nVelDodge; 1512 } 1513 else 1514 { 1515 t2 -= nVelDodge; 1516 } 1517 1518 xvel[pSpr->index] = dmulscale30(t1, nCos, t2, nSin); 1519 yvel[pSpr->index] = dmulscale30(t1, nSin, -t2, nCos); 1520 1521 if (pDude->IsFlying() && pDude->dodge.zDir) 1522 { 1523 int cz, fz, zt, zb, r = 4 + Random(2); 1524 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); 1525 GetSpriteExtents(pSpr, &zt, &zb); 1526 1527 if (Chance(0x8000) && klabs(fz - zb) > pDude->flight.cfDist) zvel[pSpr->index] = +(nVelDodge * r); 1528 else if (klabs(zt - cz) > pDude->flight.cfDist) zvel[pSpr->index] = -(nVelDodge * r); 1529 else zvel[pSpr->index] = 0; 1530 } 1531 } 1532 } 1533 1534 static void moveKnockout(spritetype* pSpr, XSPRITE*) 1535 { 1536 int zv = zvel[pSpr->index]; 1537 zvel[pSpr->index] = ClipRange(zv + mulscale16(zv, 0x3000), 0x1000, 0x40000); 1538 } 1539 1540 static void moveNormalF(spritetype* pSpr, XSPRITE* pXSpr) 1541 { 1542 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1543 CUSTOMDUDE_FLIGHT* pFly = &pDude->flight; 1544 1545 int fwdVel = pDude->GetVelocity(kParVelocityForward); 1546 int flyHg = pDude->GetMaxFlyHeigh(pFly->clipDist); 1547 int cz, fz, z, cf = pFly->cfDist; 1548 char stat; 1549 1550 stat = clipFlying(pSpr, pXSpr); 1551 if (pDude->IsThinkTime()) 1552 { 1553 if (!pDude->timer.fLand.Pass()) 1554 { 1555 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); 1556 pDude->goalZ = ClipRange(pXSpr->targetZ, cz + cf, fz - cf); 1557 } 1558 else if (pDude->IsSearching() && pDude->timer.goalZ.Pass()) 1559 { 1560 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); 1561 z = (pXSpr->targetZ > fz) ? fz : pXSpr->targetZ; 1562 pDude->goalZ = z - (flyHg >> 1) - ((pFly->absGoalZ) ? Random(flyHg>>1) : perc2val(Random(50), flyHg)); 1563 pDude->goalZ = ClipRange(pDude->goalZ, cz + cf, fz - cf); 1564 pDude->timer.goalZ.Set(); 1565 } 1566 else if (pDude->timer.goalZ.Pass() && (stat == 0 || !pFly->mustReach || klabs(pXSpr->targetZ - pDude->goalZ) > flyHg)) 1567 { 1568 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); 1569 pDude->goalZ = pXSpr->targetZ - ((pFly->absGoalZ) ? Random(flyHg) : perc2val(Random(100), flyHg)); 1570 pDude->goalZ = ClipRange(pDude->goalZ, cz + cf, fz - cf); 1571 pDude->timer.goalZ.Set(); 1572 } 1573 } 1574 1575 if (pFly->friction) 1576 nnExtFixDudeDrag(pSpr, pFly->friction); 1577 1578 xvel[pSpr->index] += mulscale30(Cos(pSpr->ang), fwdVel); 1579 yvel[pSpr->index] += mulscale30(Sin(pSpr->ang), fwdVel); 1580 } 1581 1582 static void moveNormalW(spritetype* pSpr, XSPRITE* pXSpr) 1583 { 1584 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1585 int fwdVel, dz = 0; 1586 1587 if (spriRangeIsFine(pXSpr->target)) 1588 { 1589 spritetype* pTarg = &sprite[pXSpr->target]; 1590 if (spriteIsUnderwater(pTarg, true)) 1591 dz = (pTarg->z - pSpr->z) + (10 << Random(12)); 1592 } 1593 else 1594 { 1595 dz = (pXSpr->targetZ - pSpr->z); 1596 } 1597 1598 if (Chance(0x0500)) 1599 dz <<= 1; 1600 1601 fwdVel = pDude->GetVelocity(kParVelocityForward); 1602 xvel[pSpr->index] += mulscale30(Cos(pSpr->ang), fwdVel); 1603 yvel[pSpr->index] += mulscale30(Sin(pSpr->ang), fwdVel); 1604 zvel[pSpr->index] += dz; 1605 } 1606 1607 static void moveNormalL(spritetype* pSpr, XSPRITE* pXSpr) 1608 { 1609 UNREFERENCED_PARAMETER(pXSpr); 1610 1611 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1612 int fwdVel; 1613 1614 fwdVel = pDude->GetVelocity(kParVelocityForward); 1615 xvel[pSpr->index] += mulscale30(Cos(pSpr->ang), fwdVel); 1616 yvel[pSpr->index] += mulscale30(Sin(pSpr->ang), fwdVel); 1617 } 1618 1619 static void moveNormal(spritetype* pSpr, XSPRITE* pXSpr) 1620 { 1621 UNREFERENCED_PARAMETER(pXSpr); 1622 1623 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1624 int nAng = ((kAng180 + pXSpr->goalAng - pSpr->ang) & kAngMask) - kAng180; 1625 1626 moveTurn(pSpr, pXSpr); 1627 1628 // don't move forward if trying to turn around 1629 if (klabs(nAng) > pDude->turnAng) 1630 { 1631 if (pDude->stopMoveOnTurn) 1632 moveForwardStop(pSpr, pXSpr); 1633 1634 return; 1635 } 1636 1637 if (pDude->CanMove()) 1638 { 1639 if (pDude->IsUnderwater()) moveNormalW(pSpr, pXSpr); 1640 else if (pDude->IsFlying()) moveNormalF(pSpr, pXSpr); 1641 else moveNormalL(pSpr, pXSpr); 1642 } 1643 } 1644 1645 static void enterDodgeFly(spritetype* pSpr, XSPRITE*) 1646 { 1647 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1648 pDude->dodge.zDir = Chance(0x8000) ? 1 : 0; 1649 } 1650 1651 static void enterAttack(spritetype* pSpr, XSPRITE* pXSpr) 1652 { 1653 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1654 CUSTOMDUDE_WEAPON* pWeap = pDude->pWeapon; 1655 int tDist, bDist, flyHg, nVel, zt, zb, fz; 1656 1657 if (pXSpr->target >= 0) 1658 aiSetTarget(pXSpr, pXSpr->target); 1659 1660 if (pWeap) 1661 { 1662 pWeap->attackSound.Play(pSpr); 1663 1664 if (pWeap->inertia == 0) 1665 { 1666 moveForwardStop(pSpr, pXSpr); 1667 if (pDude->IsFlying()) 1668 zvel[pSpr->index] = 0; 1669 } 1670 else if (pDude->IsFlying()) 1671 { 1672 GetSpriteExtents(pSpr, &zt, &zb); 1673 flyHg = pDude->GetMaxFlyHeigh(pDude->flight.clipDist); 1674 nVel = perc2val(10, pDude->GetVelocity(kParVelocityZ)); 1675 fz = getflorzofslope(pSpr->sectnum, pSpr->x, pSpr->y); 1676 tDist = klabs(zt - fz - flyHg); 1677 bDist = klabs(fz - zb); 1678 1679 if (Chance(0x8000) && bDist > pDude->flight.cfDist) zvel[pSpr->index] = nVel; 1680 else if (tDist > pDude->flight.cfDist) zvel[pSpr->index] = -nVel; 1681 else zvel[pSpr->index] = 0; 1682 } 1683 } 1684 } 1685 1686 static void enterKnock(spritetype* pSpr, XSPRITE*) 1687 { 1688 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1689 pDude->StatusSet(kCdudeStatusKnocked); 1690 1691 if (pDude->IsFlying()) 1692 { 1693 pSpr->flags |= (kPhysGravity | kPhysFalling); 1694 } 1695 #if 0 1696 else if (pDude->IsFlipped()) 1697 { 1698 pSpr->flags |= (kPhysGravity | kPhysFalling); 1699 pSpr->cstat &= ~CSTAT_SPRITE_YFLIP; 1700 } 1701 #endif 1702 } 1703 1704 static void exitKnock(spritetype* pSpr, XSPRITE*) 1705 { 1706 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1707 pDude->StatusRem(kCdudeStatusKnocked); 1708 1709 if (pDude->IsFlying()) 1710 { 1711 pSpr->flags &= ~(kPhysGravity | kPhysFalling); 1712 if (zvel[pSpr->index] == 0) 1713 zvel[pSpr->index] = -0x100; 1714 } 1715 #if 0 1716 else if (pDude->StatusTest(kCdudeStatusFlipped)) 1717 { 1718 pSpr->flags &= ~(kPhysGravity | kPhysFalling); 1719 pSpr->cstat |= CSTAT_SPRITE_YFLIP; 1720 } 1721 #endif 1722 } 1723 1724 static void enterSleep(spritetype* pSpr, XSPRITE* pXSpr) 1725 { 1726 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1727 pDude->StatusSet(kCdudeStatusSleep); 1728 moveForwardStop(pSpr, pXSpr); 1729 resetTarget(pSpr, pXSpr); 1730 1731 // reduce distances while sleeping 1732 pDude->seeDist = pDude->sleepDist; 1733 pDude->hearDist = pDude->sleepDist>>1; 1734 pDude->periphery = kAng360; 1735 } 1736 1737 static void enterWake(spritetype* pSpr, XSPRITE*) 1738 { 1739 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1740 CUSTOMDUDE* pModel = pDude->pTemplate; 1741 1742 if (pDude->StatusTest(kCdudeStatusSleep)) 1743 { 1744 pDude->StatusRem(kCdudeStatusSleep); 1745 1746 // restore distances when awaked 1747 if (pModel) 1748 { 1749 pDude->seeDist = pModel->seeDist; 1750 pDude->hearDist = pModel->hearDist; 1751 pDude->periphery = pModel->periphery; 1752 } 1753 else 1754 { 1755 pDude->seeDist = pDude->pInfo->seeDist; 1756 pDude->hearDist = pDude->pInfo->hearDist; 1757 pDude->periphery = pDude->pInfo->periphery; 1758 } 1759 } 1760 1761 pDude->PlaySound(kCdudeSndWake); 1762 } 1763 1764 1765 static void enterDying(spritetype* pSpr, XSPRITE*) 1766 { 1767 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1768 if (pDude->mass > 48) 1769 pDude->mass = ClipLow(pDude->mass >> 2, 48); 1770 } 1771 1772 static void thinkDying(spritetype* pSpr, XSPRITE*) 1773 { 1774 SPRITEHIT* pHit = &gSpriteHit[pSpr->extra]; 1775 if (!pHit->florhit && spriteIsUnderwater(pSpr, true)) 1776 zvel[pSpr->index] = ClipLow(zvel[pSpr->index], 0x40000); 1777 } 1778 1779 static void enterDeath(spritetype* pSpr, XSPRITE*) 1780 { 1781 // don't let the data fields gets overwritten! 1782 if (!(pSpr->flags & kHitagRespawn)) 1783 DudeToGibCallback1(pSpr->index, pSpr->extra); 1784 1785 pSpr->type = kThingBloodChunks; 1786 actPostSprite(pSpr->index, kStatThing); 1787 } 1788 1789 static void enterMorph(spritetype* pSpr, XSPRITE* pXSpr) 1790 { 1791 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1792 if (!pDude->IsMorphing()) 1793 { 1794 pDude->PlaySound(kCdudeSndTransforming); 1795 pDude->StatusSet(kCdudeStatusMorph); // set morph status 1796 pXSpr->locked = 1; // lock it while morphing 1797 1798 pSpr->flags &= ~kPhysMove; 1799 moveForwardStop(pSpr, pXSpr); 1800 if (pXSpr->aiState->seqId <= 0) 1801 seqKill(OBJ_SPRITE, pSpr->extra); 1802 } 1803 } 1804 1805 static void thinkMorph(spritetype* pSpr, XSPRITE* pXSpr) 1806 { 1807 int nTarget; char triggerOn, triggerOff; 1808 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1809 spritetype* pNext = NULL; XSPRITE* pXNext = NULL; 1810 int nextDude = pDude->nextDude; 1811 1812 if (pDude->SeqPlaying()) 1813 { 1814 moveForwardStop(pSpr, pXSpr); 1815 return; 1816 } 1817 1818 pDude->ClearEffectCallbacks(); 1819 pDude->StatusRem(kCdudeStatusMorph); // clear morph status 1820 pXSpr->burnSource = -1; 1821 pXSpr->burnTime = 0; 1822 pXSpr->locked = 0; 1823 pXSpr->scale = 0; 1824 1825 if (rngok(nextDude, 0, kMaxSprites)) 1826 { 1827 // classic morphing to already inserted sprite by TX ID 1828 pNext = &sprite[nextDude]; pXNext = &xsprite[pNext->extra]; 1829 1830 pXSpr->key = pXSpr->dropMsg = 0; 1831 1832 // save incarnation's going on and off options 1833 triggerOn = pXNext->triggerOn, triggerOff = pXNext->triggerOff; 1834 1835 // then remove it from incarnation so it won't send the commands 1836 pXNext->triggerOn = pXNext->triggerOff = 0; 1837 1838 // trigger dude death before morphing 1839 trTriggerSprite(pSpr->index, pXSpr, kCmdOff, pSpr->index); 1840 1841 pSpr->type = pSpr->inittype = pNext->type; 1842 pSpr->flags = pNext->flags; 1843 pSpr->pal = pNext->pal; 1844 pSpr->shade = pNext->shade; 1845 pSpr->clipdist = pNext->clipdist; 1846 pSpr->xrepeat = pNext->xrepeat; 1847 pSpr->yrepeat = pNext->yrepeat; 1848 1849 pXSpr->txID = pXNext->txID; 1850 pXSpr->command = pXNext->command; 1851 pXSpr->triggerOn = triggerOn; 1852 pXSpr->triggerOff = triggerOff; 1853 pXSpr->busyTime = pXNext->busyTime; 1854 pXSpr->waitTime = pXNext->waitTime; 1855 1856 // inherit respawn properties 1857 pXSpr->respawn = pXNext->respawn; 1858 pXSpr->respawnPending = pXNext->respawnPending; 1859 1860 pXSpr->data1 = pXNext->data1; // for v1 this is weapon id, v2 - descriptor id 1861 pXSpr->data2 = pXNext->data2; // for v1 this is seqBase id 1862 pXSpr->data3 = pXSpr->sysData1 = pXNext->sysData1; // for v1 this is soundBase id 1863 pXSpr->data4 = pXSpr->sysData2 = pXNext->sysData2; // start hp 1864 1865 // inherit dude flags 1866 pXSpr->dudeGuard = pXNext->dudeGuard; 1867 pXSpr->dudeDeaf = pXNext->dudeDeaf; 1868 pXSpr->dudeAmbush = pXNext->dudeAmbush; 1869 pXSpr->dudeFlag4 = pXNext->dudeFlag4; 1870 pXSpr->unused1 = pXNext->unused1; 1871 1872 pXSpr->dropMsg = pXNext->dropMsg; 1873 pXSpr->key = pXNext->key; 1874 1875 pXSpr->Decoupled = pXNext->Decoupled; 1876 pXSpr->locked = pXNext->locked; 1877 1878 // set health 1879 pXSpr->health = nnExtDudeStartHealth(pSpr, pXSpr->data4); 1880 1881 // restore values for incarnation 1882 pXNext->triggerOn = triggerOn; 1883 pXNext->triggerOff = triggerOff; 1884 } 1885 else 1886 { 1887 // v2 morphing 1888 if (nextDude >= kMaxSprites) 1889 { 1890 // morph to another custom dude 1891 pXSpr->data1 = nextDude - kMaxSprites; 1892 } 1893 else if (nextDude < -1) 1894 { 1895 // morph to some vanilla dude 1896 pSpr->type = klabs(nextDude) - 1; 1897 pSpr->clipdist = getDudeInfo(pSpr->type)->clipdist; 1898 pXSpr->data1 = 0; 1899 } 1900 1901 pSpr->inittype = pSpr->type; 1902 pXSpr->health = nnExtDudeStartHealth(pSpr, 0); 1903 pXSpr->data4 = pXSpr->sysData2 = 0; 1904 pXSpr->data2 = 0; 1905 pXSpr->data3 = 0; 1906 } 1907 1908 // clear init status 1909 pDude->initialized = 0; 1910 1911 nTarget = pXSpr->target; // save target 1912 aiInitSprite(pSpr); // re-init sprite with all new settings 1913 1914 switch (pSpr->type) 1915 { 1916 case kDudePodMother: // fake dude 1917 case kDudeTentacleMother: // fake dude 1918 break; 1919 default: 1920 if (pXSpr->dudeFlag4) break; 1921 else if (spriRangeIsFine(nTarget)) aiSetTarget(pXSpr, nTarget); // try to restore target 1922 else aiSetTarget(pXSpr, pSpr->x, pSpr->y, pSpr->z); 1923 aiActivateDude(pSpr, pXSpr); // finally activate it 1924 break; 1925 } 1926 } 1927 1928 // get closest visible underwater sector it can fall in 1929 static void enterBurnSearchWater(spritetype* pSpr, XSPRITE* pXSpr) 1930 { 1931 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 1932 1933 int i = numsectors; 1934 int nClosest = 0x7FFFFF; 1935 int nDist, s, e; 1936 1937 int x1 = pSpr->x; 1938 int y1 = pSpr->y; 1939 int z1, z2; 1940 int x2, y2; 1941 1942 pXSpr->aiState->thinkFunc = NULL; 1943 if (!pDude->CanSwim() || pDude->IsFlying() || !Chance(0x8000)) 1944 { 1945 pXSpr->aiState->thinkFunc = thinkSearch; // try follow to the target 1946 return; 1947 } 1948 1949 GetSpriteExtents(pSpr, &z1, &z2); 1950 1951 while (--i >= 0) 1952 { 1953 if (gUpperLink[i] < 0) 1954 continue; 1955 1956 spritetype* pUp = &sprite[gUpperLink[i]]; 1957 if (!spriRangeIsFine(pUp->owner)) 1958 continue; 1959 1960 spritetype* pLow = &sprite[pUp->owner]; 1961 if (sectRangeIsFine(pLow->sectnum) && isUnderwaterSector(pLow->sectnum)) 1962 { 1963 s = sector[i].wallptr; 1964 e = s + sector[i].wallnum; 1965 1966 while (--e >= s) 1967 { 1968 x2 = wall[e].x; y2 = wall[e].y; 1969 if (!cansee(x1, y1, z1, pSpr->sectnum, x2, y2, z1, i)) 1970 continue; 1971 1972 if ((nDist = approxDist(x1 - x2, y1 - y2)) < nClosest) 1973 { 1974 x2 = (x2 + wall[wall[e].point2].x) >> 1; 1975 y2 = (y2 + wall[wall[e].point2].y) >> 1; 1976 pXSpr->goalAng = getangle(x2 - x1, y2 - y1) & kAngMask; 1977 nClosest = nDist; 1978 } 1979 } 1980 } 1981 } 1982 1983 if (Chance(0xB000) && spriRangeIsFine(pXSpr->target)) 1984 { 1985 spritetype* pTarget = &sprite[pXSpr->target]; 1986 x2 = pTarget->x; 1987 y2 = pTarget->y; 1988 1989 if (approxDist(x1 - x2, y1 - y2) < nClosest) // water sector is not closer than target 1990 { 1991 pXSpr->goalAng = getangle(x2 - x1, y2 - y1) & kAngMask; 1992 pXSpr->aiState->thinkFunc = thinkSearch; // try follow to the target 1993 return; 1994 } 1995 } 1996 } 1997 1998 void cdudeDoExplosion(CUSTOMDUDE* pDude) 1999 { 2000 CUSTOMDUDE_WEAPON* pWeap = pDude->pWeapon; 2001 if (pWeap && pWeap->type == kCdudeWeaponKamikaze) 2002 weaponShotKamikaze(pDude, pWeap, &pWeap->shot.offset, 0, 0, 0); 2003 } 2004 2005 static char posObstructed(int x, int y, int z, int nRadius) 2006 { 2007 int i = numsectors; 2008 while (--i >= 0 && !inside(x, y, i)); 2009 if (i < 0) 2010 return true; 2011 2012 for (i = 0; i < kMaxSprites; i++) 2013 { 2014 spritetype* pSpr = &sprite[i]; 2015 if ((pSpr->flags & kHitagFree) || (pSpr->flags & kHitagRespawn)) continue; 2016 if ((pSpr->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != CSTAT_SPRITE_ALIGNMENT_FACING) 2017 continue; 2018 2019 if (!(pSpr->cstat & CSTAT_SPRITE_BLOCK)) 2020 { 2021 if (!IsDudeSprite(pSpr) || !dudeIsAlive(pSpr)) 2022 continue; 2023 } 2024 else 2025 { 2026 int w = tilesiz[pSpr->picnum].x; 2027 int h = tilesiz[pSpr->picnum].y; 2028 2029 if (w <= 0 || h <= 0) 2030 continue; 2031 } 2032 2033 if (CheckProximityPoint(pSpr->x, pSpr->y, pSpr->z, x, y, z, nRadius)) 2034 return true; 2035 } 2036 2037 return false; 2038 } 2039 2040 static char clipFlying(spritetype* pSpr, XSPRITE* pXSpr) 2041 { 2042 #define kCdudeMinHG 0x1000 2043 2044 CUSTOMDUDE* pDude = cdudeGet(pSpr->index); 2045 int flyVel = pDude->GetVelocity(kParVelocityZ), flyHg = pDude->GetMaxFlyHeigh(0); 2046 int curz = pSpr->z, goalz = pDude->goalZ, &zv = zvel[pSpr->index]; 2047 int t, zt, zb, cz, fz, zs = klabs(zv) >> 8; 2048 int cf = pDude->flight.cfDist; 2049 2050 getzsofslope(pSpr->sectnum, pSpr->x, pSpr->y, &cz, &fz); 2051 GetSpriteExtents(pSpr, &zt, &zb); 2052 2053 if (pDude->flight.backOnTrackAccel) 2054 { 2055 if ((curz < pXSpr->targetZ && klabs(zb - pXSpr->targetZ) > flyHg) || (curz > pXSpr->targetZ && klabs(zt - pXSpr->targetZ) > flyHg)) 2056 { 2057 flyVel += perc2val(pDude->flight.backOnTrackAccel, flyVel); 2058 } 2059 } 2060 2061 if (pDude->timer.fLand.Pass()) 2062 { 2063 if ((zv > 0 && zb + zs >= fz - cf) || (zv < 0 && zt - zs <= cz + cf)) 2064 { 2065 zv >>= 2; 2066 return 0; 2067 } 2068 } 2069 2070 if (curz < goalz) 2071 { 2072 curz += zs; // Above max height 2073 if (curz < goalz) 2074 { 2075 t = mulscale20(flyVel, ClipLow(klabs(goalz-curz), kCdudeMinHG)); 2076 zv = ClipHigh(zv + t, flyVel); 2077 return 1; 2078 } 2079 2080 zv >>= 1; 2081 } 2082 else if (curz > goalz) 2083 { 2084 curz -= zs; // Below max height 2085 if (curz > goalz) 2086 { 2087 t = mulscale20(flyVel, ClipLow(klabs(goalz-curz), kCdudeMinHG)); 2088 zv = ClipLow(zv - t, -flyVel); 2089 return 2; 2090 } 2091 2092 zv >>= 1; 2093 } 2094 2095 return 0; 2096 } 2097 2098 void helperPounce(spritetype* pSpr, spritetype* pHSpr, int nDmgType, int nDmg, int kickPow) 2099 { 2100 if (kickPow) 2101 { 2102 int nVel = ClipHigh(mulscale16(approxDist(xvel[pSpr->index], yvel[pSpr->index]), kickPow), kickPow); 2103 xvel[pHSpr->index] += mulscale30(nVel, Cos(pSpr->ang + Random2(kAng15))); 2104 yvel[pHSpr->index] += mulscale30(nVel, Sin(pSpr->ang + Random2(kAng15))); 2105 pHSpr->flags |= kPhysFalling; 2106 } 2107 2108 if (nDmg) 2109 actDamageSprite(pSpr->index, pHSpr, (DAMAGE_TYPE)nDmgType, nDmg); 2110 } 2111 #endif