/ source / blood / src / aicdud.cpp
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