code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
Copyright (C) NoOne

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#ifdef NOONE_EXTENSIONS
#include "common_game.h"
#include "nnexts.h"
#include "compat.h"
#include "build.h"
#include "pragmas.h"
#include "mmulti.h"

#include "actor.h"
#include "ai.h"
#include "aiunicult.h"
#include "blood.h"
#include "db.h"
#include "dude.h"

#include "eventq.h"
#include "globals.h"
#include "levels.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "sound.h"
#include "trig.h"
#include "triggers.h"
#include "endgame.h"
#include "view.h"
#include "tile.h"

#include "gib.h"
#include "aiburn.h"

static void genDudeAttack1(int, int);
static void punchCallback(int, int);
static void ThrowCallback1(int, int);
static void ThrowCallback2(int, int);
static void ThrowThing(int, bool);
static void thinkSearch(spritetype*, XSPRITE*);
static void thinkGoto(spritetype*, XSPRITE*);
static void thinkChase(spritetype*, XSPRITE*);
static void forcePunch(spritetype*, XSPRITE*);

static int nGenDudeAttack1 = seqRegisterClient(genDudeAttack1);
static int nGenDudePunch = seqRegisterClient(punchCallback);
static int nGenDudeThrow1 = seqRegisterClient(ThrowCallback1);
static int nGenDudeThrow2 = seqRegisterClient(ThrowCallback2);

AISTATE genDudeIdleL = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
AISTATE genDudeIdleW = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
// ---------------------
AISTATE genDudeSearchL = { kAiStateSearch, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleL };
AISTATE genDudeSearchW = { kAiStateSearch, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleW };
// ---------------------
AISTATE genDudeSearchShortL = { kAiStateSearch, 9, -1, 200, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleL };
AISTATE genDudeSearchShortW = { kAiStateSearch, 13, -1, 200, NULL, aiGenDudeMoveForward, thinkSearch, &genDudeIdleW };
// ---------------------
AISTATE genDudeSearchNoWalkL = { kAiStateSearch, 0, -1, 600, NULL, aiMoveTurn, thinkSearch, &genDudeIdleL };
AISTATE genDudeSearchNoWalkW = { kAiStateSearch, 13, -1, 600, NULL, aiMoveTurn, thinkSearch, &genDudeIdleW };
// ---------------------
AISTATE genDudeGotoL = { kAiStateMove, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &genDudeIdleL };
AISTATE genDudeGotoW = { kAiStateMove, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &genDudeIdleW };
// ---------------------
AISTATE genDudeDodgeL = { kAiStateMove, 9, -1, 90, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
AISTATE genDudeDodgeD = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
AISTATE genDudeDodgeW = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
// ---------------------
AISTATE genDudeDodgeShortL = { kAiStateMove, 9, -1, 60, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
AISTATE genDudeDodgeShortD = { kAiStateMove, 14, -1, 60, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
AISTATE genDudeDodgeShortW = { kAiStateMove, 13, -1, 60, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
// ---------------------
AISTATE genDudeDodgeShorterL = { kAiStateMove, 9, -1, 20, NULL,	aiMoveDodge,	NULL, &genDudeChaseL };
AISTATE genDudeDodgeShorterD = { kAiStateMove, 14, -1, 20, NULL, aiMoveDodge,	NULL, &genDudeChaseD };
AISTATE genDudeDodgeShorterW = { kAiStateMove, 13, -1, 20, NULL, aiMoveDodge,	NULL, &genDudeChaseW };
// ---------------------
AISTATE genDudeChaseL = { kAiStateChase, 9, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
AISTATE genDudeChaseD = { kAiStateChase, 14, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
AISTATE genDudeChaseW = { kAiStateChase, 13, -1, 0, NULL,	aiGenDudeMoveForward, thinkChase, NULL };
// ---------------------
AISTATE genDudeChaseNoWalkL = { kAiStateChase, 0, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
AISTATE genDudeChaseNoWalkD = { kAiStateChase, 14, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
AISTATE genDudeChaseNoWalkW = { kAiStateChase, 13, -1, 0, NULL,	aiMoveTurn, thinkChase, NULL };
// ---------------------
AISTATE genDudeFireL = { kAiStateChase, 6, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireL };
AISTATE genDudeFireD = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireD };
AISTATE genDudeFireW = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &genDudeFireW };
// ---------------------z
AISTATE genDudeRecoilL = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseL };
AISTATE genDudeRecoilD = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseD };
AISTATE genDudeRecoilW = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseW };
AISTATE genDudeRecoilTesla = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &genDudeDodgeShortL };
// ---------------------
AISTATE genDudeThrow = { kAiStateChase, 7, nGenDudeThrow1, 0, NULL, NULL, NULL, &genDudeChaseL };
AISTATE genDudeThrow2 = { kAiStateChase, 7, nGenDudeThrow2, 0, NULL, NULL, NULL, &genDudeChaseL };
// ---------------------
AISTATE genDudePunch = { kAiStateChase,10, nGenDudePunch, 0, NULL, NULL, forcePunch, &genDudeChaseL };
// ---------------------

GENDUDESND gCustomDudeSnd[] = {
    { 1003, 2, 0, true, false   },      // spot sound
    { 1013, 2, 2, true, true    },      // pain sound
    { 1018, 2, 4, false, true   },      // death sound
    { 1031, 2, 6, true, true    },      // burning state sound
    { 1018, 2, 8, false, true   },      // explosive death or end of burning state sound
    { 4021, 2, 10, true, false  },	    // target of dude is dead
    { 1005, 2, 12, true, false  },	    // chase sound
    { -1, 0, 14, false, true    },	    // weapon attack
    { -1, 0, 15, false, true    },	    // throw attack
    { -1, 0, 16, false, true    },	    // melee attack
    { 9008, 0, 17, false, false },      // transforming in other dude
};

// for kModernThingThrowableRock
short gCustomDudeDebrisPics[6] = {

    2406, 2280, 2185, 2155, 2620, 3135

};

GENDUDEEXTRA gGenDudeExtra[kMaxSprites];

static void forcePunch(spritetype* pSprite, XSPRITE*) {
    if (gGenDudeExtra[pSprite->index].forcePunch && seqGetStatus(3, pSprite->extra) == -1)
        punchCallback(0,pSprite->extra);
}

/*bool sameFamily(spritetype* pDude1, spritetype* pDude2) {

    return true;
}*/

bool genDudeAdjustSlope(spritetype* pSprite, XSPRITE* pXSprite, int dist, int weaponType, int by) {
    if (spriRangeIsFine(pXSprite->target)) {
        int fStart = 0; int fEnd = 0; GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
        unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1;
        for (int i = -8191; i < 8192; i += by) {
            HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, i, clipMask, dist);
            if (!fStart && pXSprite->target == gHitInfo.hitsprite) fStart = i;
            else if (fStart && pXSprite->target != gHitInfo.hitsprite) { fEnd = i; break; }
        }

        if (fStart != fEnd) {

            if (weaponType == kGenDudeWeaponHitscan)
                gDudeSlope[pSprite->extra] = fStart - ((fStart - fEnd) >> 2);
            else if (weaponType == kGenDudeWeaponMissile) {
                MissileType* pMissile = &missileInfo[pExtra->curWeapon - kMissileBase];
                gDudeSlope[pSprite->extra] = (fStart - ((fStart - fEnd) >> 2)) - (pMissile->clipDist << 1);
            }

            //viewSetSystemMessage("!!!! FOUND, SLOPE %d, RANGE %d,%d", gDudeSlope[pSprite->extra], fStart, fEnd);
            return true;
        }
    }

    return false;

}

GENDUDEEXTRA* genDudeExtra(spritetype* pGenDude) {
    return &gGenDudeExtra[pGenDude->index];
}

void genDudeUpdate(spritetype* pSprite) {
    GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
    for (int i = 0; i < kGenDudePropertyMax; i++) {
        if (pExtra->updReq[i]) genDudePrepare(pSprite, i);
    }
}

static void punchCallback(int, int nXIndex) {
    XSPRITE* pXSprite = &xsprite[nXIndex];
    if (pXSprite->target != -1) {
        int nSprite = pXSprite->reference;
        spritetype* pSprite = &sprite[nSprite];

        int nZOffset1 = getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat << 2;
        int nZOffset2 = 0;

        spritetype* pTarget = &sprite[pXSprite->target];
        if(IsDudeSprite(pTarget))
            nZOffset2 = getDudeInfo(pTarget->type)->eyeHeight * pTarget->yrepeat << 2;

        int dx = Cos(pSprite->ang) >> 16;
        int dy = Sin(pSprite->ang) >> 16;
        int dz = nZOffset1 - nZOffset2;

        if (!playGenDudeSound(pSprite, kGenDudeSndAttackMelee))
            sfxPlay3DSound(pSprite, 530, 1, 0);

        actFireVector(pSprite, 0, 0, dx, dy, dz,kVectorGenDudePunch);
    }
}

static void genDudeAttack1(int, int nXIndex) {
    if (!(nXIndex >= 0 && nXIndex < kMaxXSprites)) {
        consoleSysMsg("nXIndex >= 0 && nXIndex < kMaxXSprites");
        return;
    }

    XSPRITE* pXSprite = &xsprite[nXIndex]; int nSprite = pXSprite->reference;
    if (pXSprite->target < 0) return;
    else if (!(nSprite >= 0 && nSprite < kMaxSprites)) {
        consoleSysMsg("nIndex >= 0 && nIndex < kMaxSprites");
        return;
    }

    int dx, dy, dz; spritetype* pSprite = &sprite[nSprite];
    xvel[pSprite->index] = yvel[pSprite->index] = 0;

    GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
    short dispersion = pExtra->baseDispersion;
    if (inDuck(pXSprite->aiState))
        dispersion = ClipLow(dispersion >> 1, kGenDudeMinDispesion);

    if (pExtra->weaponType == kGenDudeWeaponHitscan) {

        dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];
        // dispersal modifiers here in case if non-melee enemy
        if (!dudeIsMelee(pXSprite)) {
            dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion);
        }

        actFireVector(pSprite, 0, 0, dx, dy, dz,(VECTOR_TYPE)pExtra->curWeapon);
        if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
            sfxPlayVectorSound(pSprite, pExtra->curWeapon);

    } else if (pExtra->weaponType == kGenDudeWeaponSummon) {

        spritetype* pSpawned = NULL; int dist = pSprite->clipdist << 4;
        if (pExtra->slaveCount <= gGameOptions.nDifficulty) {
            if ((pSpawned = actSpawnDude(pSprite, pExtra->curWeapon, dist + Random(dist), 0)) != NULL) {
                pSpawned->owner = nSprite;

                if (xspriRangeIsFine(pSpawned->extra)) {
                    xsprite[pSpawned->extra].target = pXSprite->target;
                    if (pXSprite->target > -1)
                        aiActivateDude(pSpawned, &xsprite[pSpawned->extra]);
                }

                gKillMgr.AddCount(pSpawned);
                pExtra->slave[pExtra->slaveCount++] = pSpawned->index;
                if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
                    sfxPlay3DSoundCP(pSprite, 379, 1, 0, 0x10000 - Random3(0x3000));
            }
        }

    } else if (pExtra->weaponType == kGenDudeWeaponMissile) {

        dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];

        // dispersal modifiers here
        dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion >> 1);

        actFireMissile(pSprite, 0, 0, dx, dy, dz, pExtra->curWeapon);
        if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
            sfxPlayMissileSound(pSprite, pExtra->curWeapon);
    }
}

static void ThrowCallback1(int, int nXIndex) {
    ThrowThing(nXIndex, true);
}

static void ThrowCallback2(int, int nXIndex) {
    ThrowThing(nXIndex, false);
}

static void ThrowThing(int nXIndex, bool impact) {
    XSPRITE* pXSprite = &xsprite[nXIndex]; spritetype* pSprite = &sprite[pXSprite->reference];

    if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
        return;

    spritetype * pTarget = &sprite[pXSprite->target];
    if (!(pTarget->type >= kDudeBase && pTarget->type < kDudeMax))
        return;

    short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
    short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
    if (weaponType != kGenDudeWeaponThrow) return;

    THINGINFO* pThinkInfo = &thingInfo[curWeapon - kThingBase];
    if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return;
    else if (!playGenDudeSound(pSprite, kGenDudeSndAttackThrow))
        sfxPlay3DSound(pSprite, 455, -1, 0);

    int zThrow = 14500;
    int dx = pTarget->x - pSprite->x;
    int dy = pTarget->y - pSprite->y;
    int dz = pTarget->z - pSprite->z;
    int dist = approxDist(dx, dy);

    spritetype* pLeech = leechIsDropped(pSprite);
    XSPRITE* pXLeech = (pLeech != NULL) ? &xsprite[pLeech->extra] : NULL;

    switch (curWeapon) {
        case kModernThingEnemyLifeLeech:
        case kThingDroppedLifeLeech:
            zThrow = 5000;
            // pickup life leech before throw it again
            if (pLeech != NULL) removeLeech(pLeech);
            break;
    }

    spritetype* pThing = NULL;
    if ((pThing = actFireThing(pSprite, 0, 0, (dz / 128) - zThrow, curWeapon, divscale23(dist / 540, 120))) == NULL) return;
    else if (pThinkInfo->picnum < 0 && pThing->type != kModernThingThrowableRock) pThing->picnum = 0;

    pThing->owner = pSprite->index;

    switch (curWeapon) {
        case kThingNapalmBall:
            pThing->xrepeat = pThing->yrepeat = 24;
            xsprite[pThing->extra].data4 = 3 + gGameOptions.nDifficulty;
            impact = true;
            break;
        case kModernThingThrowableRock:
            pThing->picnum  = gCustomDudeDebrisPics[Random(5)];
            pThing->xrepeat = pThing->yrepeat = 24 + Random(42);
            pThing->cstat |= 0x0001;
            pThing->pal = 5;

            if (Chance(0x5000)) pThing->cstat |= 0x0004;
            if (Chance(0x5000)) pThing->cstat |= 0x0008;

            if (pThing->xrepeat > 60) xsprite[pThing->extra].data1 = 43;
            else if (pThing->xrepeat > 40) xsprite[pThing->extra].data1 = 33;
            else if (pThing->xrepeat > 30) xsprite[pThing->extra].data1 = 23;
            else xsprite[pThing->extra].data1 = 12;
            return;
        case kThingTNTBarrel:
        case kThingArmedProxBomb:
        case kThingArmedSpray:
            impact = false;
            break;
        case kModernThingTNTProx:
            xsprite[pThing->extra].state = 0;
            xsprite[pThing->extra].Proximity = true;
            return;
        case kModernThingEnemyLifeLeech:
            XSPRITE* pXThing = &xsprite[pThing->extra];
            if (pLeech != NULL) pXThing->health = pXLeech->health;
            else pXThing->health = ((pThinkInfo->startHealth << 4) * gGameOptions.nDifficulty) >> 1;

            sfxPlay3DSound(pSprite, 490, -1, 0);

            pXThing->data3 = 512 / (gGameOptions.nDifficulty + 1);
            pThing->cstat &= ~CSTAT_SPRITE_BLOCK;
            pThing->pal = 6;
            pThing->clipdist = 0;
            pXThing->target = pTarget->index;
            pXThing->Proximity = true;
            pXThing->stateTimer = 1;

            gGenDudeExtra[pSprite->index].nLifeLeech = pThing->index;
            evPost(pThing->index, 3, 80, kCallbackLeechStateTimer);
            return;
    }

    if (impact == true && dist <= 7680) xsprite[pThing->extra].Impact = true;
    else {
        xsprite[pThing->extra].Impact = false;
        evPost(pThing->index, 3, 120 * Random(2) + 120, kCmdOn, pSprite->index);
    }
}

static void thinkSearch( spritetype* pSprite, XSPRITE* pXSprite ) {

    // TO DO: if can't see the target, but in fireDist range - stop moving and look around

    //viewSetSystemMessage("IN SEARCH");
    aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
    aiLookForTarget(pSprite, pXSprite);
}

static void thinkGoto(spritetype* pSprite, XSPRITE* pXSprite) {

    if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
        consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
        return;
    }

    int dx = pXSprite->targetX - pSprite->x;
    int dy = pXSprite->targetY - pSprite->y;
    int nAngle = getangle(dx, dy);

    aiChooseDirection(pSprite, pXSprite, nAngle);

    // if reached target, change to search mode
    if (approxDist(dx, dy) < 5120 && klabs(pSprite->ang - nAngle) < getDudeInfo(pSprite->type)->periphery) {
        if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
        else aiGenDudeNewState(pSprite, &genDudeSearchL);
    }
    aiThinkTarget(pSprite, pXSprite);
}

static void thinkChase( spritetype* pSprite, XSPRITE* pXSprite ) {

    if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax) return;
    else if (pXSprite->target < 0 || pXSprite->target >= kMaxSprites) {
        if(spriteIsUnderwater(pSprite,false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
        else aiGenDudeNewState(pSprite, &genDudeGotoL);
        return;
    } else {

        genDudeUpdate(pSprite);

    }

    spritetype* pTarget = &sprite[pXSprite->target];
    XSPRITE* pXTarget = (!IsDudeSprite(pTarget) || !xspriRangeIsFine(pTarget->extra)) ? NULL : &xsprite[pTarget->extra];

    if (pXTarget == NULL) {  // target lost
        if(spriteIsUnderwater(pSprite,false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
        else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
        pXSprite->target = -1;
        return;

    } else if (pXTarget->health <= 0) { // target is dead
        PLAYER* pPlayer = NULL;
        if ((!IsPlayerSprite(pTarget)) || ((pPlayer = getPlayerById(pTarget->type)) != NULL && pPlayer->fraggerId == pSprite->index)) {
            playGenDudeSound(pSprite, kGenDudeSndTargetDead);
            if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
            else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
        }
        else if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
        else aiGenDudeNewState(pSprite, &genDudeGotoL);
        pXSprite->target = -1;
        return;
    }

    // check target
    int dx = pTarget->x - pSprite->x; int dy = pTarget->y - pSprite->y;
    int dist = ClipLow((int)approxDist(dx, dy), 1);

    // quick hack to prevent spinning around or changing attacker's sprite angle on high movement speeds
    // when attacking the target. It happens because vanilla function takes in account x and y velocity,
    // so i use fake velocity with fixed value and pass it as argument.
    int xvelocity = xvel[pSprite->index]; int yvelocity = yvel[pSprite->index];
    if (inAttack(pXSprite->aiState))
       xvelocity = yvelocity = ClipLow(pSprite->clipdist >> 1, 1);

    //aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
    aiGenDudeChooseDirection(pSprite, pXSprite, getangle(dx, dy), xvelocity, yvelocity);

    GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
    if (!pExtra->canAttack) {
        if (pExtra->canWalk) aiSetTarget(pXSprite, pSprite->index);
        if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeGotoW);
        else aiGenDudeNewState(pSprite, &genDudeGotoL);
        return;
    } else if (IsPlayerSprite(pTarget)) {
        PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
        if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0)  {
            if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
            else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
            pXSprite->target = -1;
            return;
        }
    }

    DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
    int losAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
    int eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;

    if (dist > pDudeInfo->seeDist || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
        pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)) {

        if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
        else aiGenDudeNewState(pSprite, &genDudeSearchL);
        pXSprite->target = -1;
        return;
    }

    // is the target visible?
    if (dist < pDudeInfo->seeDist && klabs(losAngle) <= pDudeInfo->periphery) {

        if (((int)gFrameClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(pSprite, false))
            playGenDudeSound(pSprite, kGenDudeSndChasing);

        gDudeSlope[pSprite->extra] = divscale10(pTarget->z - pSprite->z, dist);

        short curWeapon = gGenDudeExtra[pSprite->index].curWeapon; short weaponType = gGenDudeExtra[pSprite->index].weaponType;
        spritetype* pLeech = leechIsDropped(pSprite); VECTORDATA* meleeVector = &gVectorData[22];
        if (weaponType == kGenDudeWeaponThrow) {
            if (klabs(losAngle) < kAng15) {
                if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) {
                    if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                    else aiGenDudeNewState(pSprite, &genDudeChaseL);
                    return;

                } else if (dist < 12264 && dist > 7680 && !spriteIsUnderwater(pSprite, false) && curWeapon != kModernThingEnemyLifeLeech) {
                    int pHit = HitScan(pSprite, pSprite->z, dx, dy, 0, 16777280, 0);
                    switch (pHit) {
                        case 0:
                        case 4:
                            return;
                        default:
                            aiGenDudeNewState(pSprite, &genDudeThrow);
                            return;
                    }

                } else if (dist > 4072 && dist <= 11072 && !spriteIsUnderwater(pSprite, false) && pSprite->owner != (kMaxSprites - 1)) {
                    switch (curWeapon) {
                        case kModernThingEnemyLifeLeech: {
                            if (pLeech == NULL) {
                                aiGenDudeNewState(pSprite, &genDudeThrow2);
                                genDudeThrow2.nextState = &genDudeDodgeShortL;
                                return;
                            }

                            XSPRITE* pXLeech = &xsprite[pLeech->extra];
                            int ldist = aiFightGetTargetDist(pTarget, pDudeInfo, pLeech);
                            if (ldist > 3 || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
                                pLeech->x, pLeech->y, pLeech->z, pLeech->sectnum) || pXLeech->target == -1) {

                                aiGenDudeNewState(pSprite, &genDudeThrow2);
                                genDudeThrow2.nextState = &genDudeDodgeShortL;

                            } else {

                                genDudeThrow2.nextState = &genDudeChaseL;
                                if (dist > 5072 && Chance(0x5000)) {
                                    if (!canDuck(pSprite) || Chance(0x4000)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL);
                                    else aiGenDudeNewState(pSprite, &genDudeDodgeShortD);
                                } else {
                                    aiGenDudeNewState(pSprite, &genDudeChaseL);
                                }

                            }
                        }
                        return;
                        case kModernThingThrowableRock:
                            if (Chance(0x4000)) aiGenDudeNewState(pSprite, &genDudeThrow2);
                            else playGenDudeSound(pSprite, kGenDudeSndTargetSpot);
                            return;
                        default:
                            aiGenDudeNewState(pSprite, &genDudeThrow2);
                            return;
                    }

                } else if (dist <= meleeVector->maxDist) {

                    if (spriteIsUnderwater(pSprite, false)) {
                        if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
                        else aiGenDudeNewState(pSprite, &genDudeDodgeW);

                    }
                    else if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
                    else aiGenDudeNewState(pSprite, &genDudeDodgeL);
                    return;

                } else {
                    int state = checkAttackState(pSprite, pXSprite);
                    if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
                    else if (state == 2) {
                        if (Chance(0x5000)) aiGenDudeNewState(pSprite, &genDudeChaseD);
                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                    }
                    else  aiGenDudeNewState(pSprite, &genDudeChaseL);
                    return;
                }
            }

        } else {

            int vdist; int mdist; int defDist;
            defDist = vdist = mdist = gGenDudeExtra[pSprite->index].fireDist;

            if (weaponType == kGenDudeWeaponHitscan) {
                if ((vdist = gVectorData[curWeapon].maxDist) <= 0)
                    vdist = defDist;

            } else if (weaponType == kGenDudeWeaponSummon) {

                // don't attack slaves
                if (pXSprite->target >= 0 && sprite[pXSprite->target].owner == pSprite->index) {
                    aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
                    return;
                } else if (gGenDudeExtra[pSprite->index].slaveCount > gGameOptions.nDifficulty || dist < meleeVector->maxDist) {
                    if (dist <= meleeVector->maxDist) {
                        aiGenDudeNewState(pSprite, &genDudePunch);
                        return;
                    } else {
                        int state = checkAttackState(pSprite, pXSprite);
                        if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
                        else if (state == 2) aiGenDudeNewState(pSprite, &genDudeChaseD);
                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                        return;
                    }
                }

            } else if (weaponType == kGenDudeWeaponMissile) {
                // special handling for flame, explosive and life leech missiles
                int state = checkAttackState(pSprite, pXSprite);
                switch (curWeapon) {
                    case kMissileLifeLeechRegular:
                        // pickup life leech if it was thrown previously
                        if (pLeech != NULL) removeLeech(pLeech);
                        mdist = 1500;
                        break;
                    case kMissileFlareAlt:
                        mdist = 2500;
                        fallthrough__;
                    case kMissileFireball:
                    case kMissileFireballNapalm:
                    case kMissileFireballCerberus:
                    case kMissileFireballTchernobog:
                        if (mdist == defDist) mdist = 3000;
                        if (dist > mdist || pXSprite->locked == 1) break;
                        else if (dist <= meleeVector->maxDist && Chance(0x9000))
                            aiGenDudeNewState(pSprite, &genDudePunch);
                        else if (state == 1) aiGenDudeNewState(pSprite, &genDudeChaseW);
                        else if (state == 2) aiGenDudeNewState(pSprite, &genDudeChaseD);
                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                        return;
                    case kMissileFlameSpray:
                    case kMissileFlameHound:
                        //viewSetSystemMessage("%d", pXTarget->burnTime);
                        if (spriteIsUnderwater(pSprite, false)) {
                            if (dist > meleeVector->maxDist) aiGenDudeNewState(pSprite, &genDudeChaseW);
                            else if (Chance(0x8000)) aiGenDudeNewState(pSprite, &genDudePunch);
                            else aiGenDudeNewState(pSprite, &genDudeDodgeShortW);
                            return;
                        } else if (dist <= 4000 && pXTarget->burnTime >= 2000 && pXTarget->burnSource == pSprite->index) {
                            if (dist > meleeVector->maxDist) aiGenDudeNewState(pSprite, &genDudeChaseL);
                            else aiGenDudeNewState(pSprite, &genDudePunch);
                            return;
                        }
                        vdist = 3500 + (gGameOptions.nDifficulty * 400);
                        break;
                }
            } else if (weaponType == kGenDudeWeaponKamikaze) {
                int nType = curWeapon - kTrapExploder; EXPLOSION* pExpl = &explodeInfo[nType];
                if (CheckProximity(pSprite, pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pExpl->radius >> 1)) {
                    xvel[pSprite->index] = zvel[pSprite->index] = yvel[pSprite->index] = 0;
                    if (doExplosion(pSprite, nType) && pXSprite->health > 0)
                            actDamageSprite(pSprite->index, pSprite, kDamageExplode, 65535);
                }
                return;
            }

            int state = checkAttackState(pSprite, pXSprite);
            int kAngle = (dudeIsMelee(pXSprite) || dist <= kGenDudeMaxMeleeDist) ? pDudeInfo->periphery : kGenDudeKlabsAng;

            if (dist < vdist && klabs(losAngle) < kAngle) {
                if (pExtra->canWalk) {
                    int objDist = -1; int targetDist = -1; int hit = -1;
                    if (weaponType == kGenDudeWeaponHitscan)
                        hit = HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], CLIPMASK1, dist);
                    else if (weaponType == kGenDudeWeaponMissile)
                        hit = HitScan(pSprite, pSprite->z, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], CLIPMASK0, dist);

                    if (hit >= 0) {
                        targetDist = dist - (pTarget->clipdist << 2);
                        objDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
                    }

                    if (pXSprite->target != gHitInfo.hitsprite && targetDist > objDist) {
                        walltype* pHWall = NULL; XWALL* pXHWall = NULL;
                        spritetype* pHSprite = NULL; XSPRITE* pXHSprite = NULL;
                        bool hscn = false; bool blck = false; bool failed = false;

                        switch (hit) {
                        case 3:
                            pHSprite = &sprite[gHitInfo.hitsprite];
                            if (xspriRangeIsFine(pHSprite->extra)) pXHSprite = &xsprite[pHSprite->extra];
                            hscn = (pHSprite->cstat & CSTAT_SPRITE_BLOCK_HITSCAN); blck = (pHSprite->cstat & CSTAT_SPRITE_BLOCK);
                            break;
                        case 0:
                        case 4:
                            pHWall = &wall[gHitInfo.hitwall];
                            if (xwallRangeIsFine(pHWall->extra)) pXHWall = &xwall[pHWall->extra];
                            hscn = (pHWall->cstat & CSTAT_WALL_BLOCK_HITSCAN); blck = (pHWall->cstat & CSTAT_WALL_BLOCK);
                            break;
                        }

                        switch (hit) {
                        case 0:
                            //if (hit == 0) viewSetSystemMessage("WALL HIT %d", gHitInfo.hitwall);
                            fallthrough__;
                        case 1:
                            //if (hit == 1) viewSetSystemMessage("CEIL HIT %d", gHitInfo.hitsect);
                            fallthrough__;
                        case 2:
                            //if (hit == 2) viewSetSystemMessage("FLOOR HIT %d", gHitInfo.hitsect);
                            if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(pSprite, pXSprite, dist, weaponType)
                                && dist < (int)(6000 + Random(2000)) && pExtra->baseDispersion < kGenDudeMaxDispersion >> 1) break;

                            else if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                            else aiGenDudeNewState(pSprite, &genDudeChaseL);
                            return;
                        case 3:
                            if (pHSprite->statnum == kStatFX || pHSprite->statnum == kStatProjectile || pHSprite->statnum == kStatDebris)
                                break;
                            if (IsDudeSprite(pHSprite) && (weaponType != kGenDudeWeaponHitscan || hscn)) {
                                // dodge a bit in sides
                                if (pXHSprite->health > 0 && pXHSprite->target != pSprite->index) {
                                    if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile) {
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterW);
                                        else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterD);
                                        else aiGenDudeNewState(pSprite, &genDudeDodgeShorterL);
                                    }
                                    else if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShortW);
                                    else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShortD);
                                    else aiGenDudeNewState(pSprite, &genDudeDodgeShortL);

                                    switch (pHSprite->type) {
                                        case kDudeModernCustom: // and make dude which could be hit to dodge too
                                            if (!dudeIsMelee(pXHSprite) && Chance(dist << 4)) {
                                                if (!inAttack(pXHSprite->aiState)) {
                                                    if (spriteIsUnderwater(pHSprite)) aiGenDudeNewState(pHSprite, &genDudeDodgeShorterW);
                                                    else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pHSprite, &genDudeDodgeShorterD);
                                                    else aiGenDudeNewState(pHSprite, &genDudeDodgeShorterL);

                                                    // preferable in opposite sides
                                                    if (Chance(0x8000)) {
                                                        if (pXSprite->dodgeDir == 1) pXHSprite->dodgeDir = -1;
                                                        else if (pXSprite->dodgeDir == -1) pXHSprite->dodgeDir = 1;
                                                    }
                                                    break;
                                                }
                                                if (pSprite->x < pHSprite->x) {
                                                    if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
                                                    else pXSprite->dodgeDir = 1;
                                                } else {
                                                    if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
                                                    else pXSprite->dodgeDir = -1;
                                                }
                                            }
                                            break;
                                        default:
                                            if (pSprite->x < pHSprite->x) {
                                                if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
                                                else pXSprite->dodgeDir = 1;
                                            } else {
                                                if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
                                                else pXSprite->dodgeDir = -1;
                                            }
                                            break;
                                    }
                                    return;
                                }
                                break;
                            } else if (weaponType == kGenDudeWeaponHitscan && hscn) {
                                if (genDudeAdjustSlope(pSprite, pXSprite, dist, weaponType)) break;
                                VectorScan(pSprite, 0, 0, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], dist, 1);
                                if (pXSprite->target == gHitInfo.hitsprite) break;

                                bool immune = nnExtIsImmune(pHSprite, gVectorData[curWeapon].dmgType);
                                if (!(pXHSprite != NULL && (!immune || (immune && pHSprite->statnum == kStatThing && pXHSprite->Vector)) && !pXHSprite->locked)) {

                                    if ((approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y) <= 1500 && !blck)
                                        || (dist <= (int)(pExtra->fireDist / ClipLow(Random(4), 1)))) {

                                        //viewSetSystemMessage("GO CHASE");
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                                        return;

                                    }

                                    int wd1 = picWidth(pHSprite->picnum, pHSprite->xrepeat);
                                    int wd2 = picWidth(pSprite->picnum, pSprite->xrepeat);
                                    if (wd1 < (wd2 << 3)) {
                                        //viewSetSystemMessage("OBJ SIZE: %d   DUDE SIZE: %d", wd1, wd2);
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterW);
                                        else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeDodgeShorterD);
                                        else aiGenDudeNewState(pSprite, &genDudeDodgeShorterL);

                                        if (pSprite->x < pHSprite->x) {
                                            if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
                                            else pXSprite->dodgeDir = 1;
                                        } else {
                                            if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
                                            else pXSprite->dodgeDir = -1;
                                        }

                                        if (((gSpriteHit[pSprite->extra].hit & 0xc000) == 0x8000) || ((gSpriteHit[pSprite->extra].hit & 0xc000) == 0xc000)) {
                                            if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                            else aiGenDudeNewState(pSprite, &genDudeChaseL);
                                            pXSprite->goalAng = Random(kAng360);
                                            //viewSetSystemMessage("WALL OR SPRITE TOUCH");
                                        }

                                    } else {
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                                        //viewSetSystemMessage("TOO BIG OBJECT TO DODGE!!!!!!!!");
                                    }
                                    return;
                                }
                                break;
                            }
                            fallthrough__;
                        case 4:
                            if (hit == 4 && weaponType == kGenDudeWeaponHitscan && hscn) {
                                bool masked = (pHWall->cstat & CSTAT_WALL_MASKED);
                                if (masked) VectorScan(pSprite, 0, 0, Cos(pSprite->ang) >> 16, Sin(pSprite->ang) >> 16, gDudeSlope[pSprite->extra], dist, 1);

                                //viewSetSystemMessage("WALL VHIT: %d", gHitInfo.hitwall);
                                if ((pXSprite->target != gHitInfo.hitsprite) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked)) {
                                    if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                    else aiGenDudeNewState(pSprite, &genDudeChaseL);
                                    return;
                                }
                            } else if (hit >= 3 && weaponType == kGenDudeWeaponMissile && blck) {
                                switch (curWeapon) {
                                case kMissileLifeLeechRegular:
                                case kMissileTeslaAlt:
                                case kMissileFlareAlt:
                                case kMissileFireball:
                                case kMissileFireballNapalm:
                                case kMissileFireballCerberus:
                                case kMissileFireballTchernobog: {
                                    // allow attack if dude is far from object, but target is close to it
                                    int dudeDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
                                    int targetDist = approxDist(gHitInfo.hitx - pTarget->x, gHitInfo.hity - pTarget->y);
                                    if (dudeDist < mdist) {
                                        //viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist);
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                        else aiGenDudeNewState(pSprite, &genDudeChaseL);
                                        return;
                                    } else if (targetDist <= mdist >> 1) {
                                        //viewSetSystemMessage("TARGET CLOSE TO OBJ: %d, MDIST: %d", targetDist, mdist >> 1);
                                        break;
                                    }
                                    fallthrough__;
                                }
                                default:
                                    //viewSetSystemMessage("DEF HIT: %d, MDIST: %d", hit, mdist);
                                    if (hit == 4) failed = (pHWall->type != kWallGib || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked);
                                    else if (hit == 3 && (failed = (pHSprite->statnum != kStatThing || pXHSprite == NULL || pXHSprite->locked)) == false) {
                                        // check also for damage resistance (all possible damages missile can use)
                                        for (int i = 0; i < kDmgMax; i++) {
                                            if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(pHSprite, i)) == false)
                                                break;
                                        }
                                    }

                                    if (failed) {
                                        if (spriteIsUnderwater(pSprite)) aiGenDudeNewState(pSprite, &genDudeSearchW);
                                        else aiGenDudeNewState(pSprite, &genDudeSearchL);
                                        return;
                                    }
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }

                aiSetTarget(pXSprite, pXSprite->target);
                switch (state) {
                    case 1:
                        aiGenDudeNewState(pSprite, &genDudeFireW);
                        pXSprite->aiState->nextState = &genDudeFireW;
                        break;
                    case 2:
                        aiGenDudeNewState(pSprite, &genDudeFireD);
                        pXSprite->aiState->nextState = &genDudeFireD;
                        break;
                    default:
                        aiGenDudeNewState(pSprite, &genDudeFireL);
                        pXSprite->aiState->nextState = &genDudeFireL;
                        break;
                }

            } else {

                if (seqGetID(3, pSprite->extra) == pXSprite->data2 + ((state < 3) ? 8 : 6)) {
                    if (state == 1) pXSprite->aiState->nextState = &genDudeChaseW;
                    else if (state == 2) pXSprite->aiState->nextState = &genDudeChaseD;
                    else pXSprite->aiState->nextState = &genDudeChaseL;

                } else if (state == 1 && pXSprite->aiState != &genDudeChaseW && pXSprite->aiState != &genDudeFireW) {
                    aiGenDudeNewState(pSprite, &genDudeChaseW);
                    pXSprite->aiState->nextState = &genDudeFireW;

                } else if (state == 2 && pXSprite->aiState != &genDudeChaseD && pXSprite->aiState != &genDudeFireD) {
                    aiGenDudeNewState(pSprite, &genDudeChaseD);
                    pXSprite->aiState->nextState = &genDudeFireD;

                } else if (pXSprite->aiState != &genDudeChaseL && pXSprite->aiState != &genDudeFireL) {
                    aiGenDudeNewState(pSprite, &genDudeChaseL);
                    pXSprite->aiState->nextState = &genDudeFireL;
                }

            }
        }
    }
}

int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite) {
    UNREFERENCED_PARAMETER(pXSprite);
    if (dudeIsPlayingSeq(pSprite, 14) || spriteIsUnderwater(pSprite,false))
    {
        if ( !dudeIsPlayingSeq(pSprite, 14) || spriteIsUnderwater(pSprite,false))
        {
            if (spriteIsUnderwater(pSprite,false))
            {
                return 1; //water
            }
        }
        else
        {
            return 2; //duck
        }
    }
    else
    {
        return 3; //land
    }
    return 0;
}

///// For gen dude
int getGenDudeMoveSpeed(spritetype* pSprite,int which, bool mul, bool shift) {
    DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    int speed = -1; int step = 2500; int maxSpeed = 146603;
    switch(which){
        case 0:
            speed = pDudeInfo->frontSpeed;
            break;
        case 1:
            speed = pDudeInfo->sideSpeed;
            break;
        case 2:
            speed = pDudeInfo->backSpeed;
            break;
        case 3:
            speed = pDudeInfo->angSpeed;
            break;
        default:
            return -1;
    }
    if (pXSprite->busyTime > 0) speed /=3;
    if (speed > 0 && mul) {

        if (pXSprite->busyTime > 0)
            speed += (step * pXSprite->busyTime);
    }

    if (shift) speed *= 4 >> 4;
    if (speed > maxSpeed) speed = maxSpeed;

    return speed;
}

void aiGenDudeMoveForward(spritetype* pSprite, XSPRITE* pXSprite ) {
    DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
    GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
    int maxTurn = pDudeInfo->angSpeed * 4 >> 4;

    if (pExtra->canFly) {
        int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
        int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
        pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
        int nAccel = pDudeInfo->frontSpeed << 2;
        if (klabs(nAng) > 341)
            return;
        if (pXSprite->target == -1)
            pSprite->ang = (pSprite->ang + 256) & 2047;
        int dx = pXSprite->targetX - pSprite->x;
        int dy = pXSprite->targetY - pSprite->y;
        int UNUSED(nAngle) = getangle(dx, dy);
        int nDist = approxDist(dx, dy);
        if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
            return;
        int nCos = Cos(pSprite->ang);
        int nSin = Sin(pSprite->ang);
        int vx = xvel[pSprite->index];
        int vy = yvel[pSprite->index];
        int t1 = dmulscale30(vx, nCos, vy, nSin);
        int t2 = dmulscale30(vx, nSin, -vy, nCos);
        if (pXSprite->target == -1)
            t1 += nAccel;
        else
            t1 += nAccel >> 1;
        xvel[pSprite->index] = dmulscale30(t1, nCos, t2, nSin);
        yvel[pSprite->index] = dmulscale30(t1, nSin, -t2, nCos);
    } else {
        int dang = ((kAng180 + pXSprite->goalAng - pSprite->ang) & 2047) - kAng180;
        pSprite->ang = ((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & 2047);

        // don't move forward if trying to turn around
        if (klabs(dang) > kAng60)
            return;

        int sin = Sin(pSprite->ang);
        int cos = Cos(pSprite->ang);

        int frontSpeed = gGenDudeExtra[pSprite->index].moveSpeed;
        xvel[pSprite->index] += mulscale30(cos, frontSpeed);
        yvel[pSprite->index] += mulscale30(sin, frontSpeed);
    }
}

void aiGenDudeChooseDirection(spritetype* pSprite, XSPRITE* pXSprite, int a3, int xvel, int yvel) {
    if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
        consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
        return;
    }

    // TO-DO: Take in account if sprite is flip-x, so enemy select correct angle

    int vc = ((a3 + 1024 - pSprite->ang) & 2047) - 1024;
    int t1 = dmulscale30(xvel, Cos(pSprite->ang), yvel, Sin(pSprite->ang));
    int vsi = ((t1 * 15) >> 12) / 2; int v8 = (vc >= 0) ? 341 : -341;

    if (CanMove(pSprite, pXSprite->target, pSprite->ang + vc, vsi))
        pXSprite->goalAng = pSprite->ang + vc;
    else if (CanMove(pSprite, pXSprite->target, pSprite->ang + vc / 2, vsi))
        pXSprite->goalAng = pSprite->ang + vc / 2;
    else if (CanMove(pSprite, pXSprite->target, pSprite->ang - vc / 2, vsi))
        pXSprite->goalAng = pSprite->ang - vc / 2;
    else if (CanMove(pSprite, pXSprite->target, pSprite->ang + v8, vsi))
        pXSprite->goalAng = pSprite->ang + v8;
    else if (CanMove(pSprite, pXSprite->target, pSprite->ang, vsi))
        pXSprite->goalAng = pSprite->ang;
    else if (CanMove(pSprite, pXSprite->target, pSprite->ang - v8, vsi))
        pXSprite->goalAng = pSprite->ang - v8;
    else
        pXSprite->goalAng = pSprite->ang + 341;

    pXSprite->dodgeDir = (Chance(0x8000)) ? 1 : -1;

    if (!CanMove(pSprite, pXSprite->target, pSprite->ang + pXSprite->dodgeDir * 512, 512)) {
        pXSprite->dodgeDir = -pXSprite->dodgeDir;
        if (!CanMove(pSprite, pXSprite->target, pSprite->ang + pXSprite->dodgeDir * 512, 512))
            pXSprite->dodgeDir = 0;
    }
}

void aiGenDudeNewState(spritetype* pSprite, AISTATE* pAIState) {
    if (!xspriRangeIsFine(pSprite->extra)) {
        consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
        return;
    }

    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    if (pXSprite->health <= 0 || pXSprite->sysData1 == kGenDudeTransformStatus)
        return;

    // redirect dudes which cannot walk to non-walk states
    if (!gGenDudeExtra[pSprite->index].canWalk) {

        if (pAIState == &genDudeDodgeL || pAIState == &genDudeDodgeShortL || pAIState == &genDudeDodgeShorterL)
            pAIState = &genDudeRecoilL;

        else if (pAIState == &genDudeDodgeD || pAIState == &genDudeDodgeShortD || pAIState == &genDudeDodgeShorterD)
            pAIState = &genDudeRecoilD;

        else if (pAIState == &genDudeDodgeW || pAIState == &genDudeDodgeShortW || pAIState == &genDudeDodgeShorterW)
            pAIState = &genDudeRecoilW;

        else if (pAIState == &genDudeSearchL || pAIState == &genDudeSearchShortL)
            pAIState = &genDudeSearchNoWalkL;

        else if (pAIState == &genDudeSearchW || pAIState == &genDudeSearchShortW)
            pAIState = &genDudeSearchNoWalkW;

        else if (pAIState == &genDudeGotoL) pAIState = &genDudeIdleL;
        else if (pAIState == &genDudeGotoW) pAIState = &genDudeIdleW;
        else if (pAIState == &genDudeChaseL) pAIState = &genDudeChaseNoWalkL;
        else if (pAIState == &genDudeChaseD) pAIState = &genDudeChaseNoWalkD;
        else if (pAIState == &genDudeChaseW) pAIState = &genDudeChaseNoWalkW;
        else if (pAIState == &genDudeRecoilTesla) {

            if (spriteIsUnderwater(pSprite, false)) pAIState = &genDudeRecoilW;
            else pAIState = &genDudeRecoilL;

        }

    }

    if (!gGenDudeExtra[pSprite->index].canRecoil) {
        if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL;
        else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW;
    }

    pXSprite->stateTimer = pAIState->stateTicks; pXSprite->aiState = pAIState;

    int stateSeq = pXSprite->data2 + pAIState->seqId;
    if (pAIState->seqId >= 0 && gSysRes.Lookup(stateSeq, "SEQ")) {
        seqSpawn(stateSeq, 3, pSprite->extra, pAIState->funcId);
    }

    if (pAIState->enterFunc)
        pAIState->enterFunc(pSprite, pXSprite);
}

bool playGenDudeSound(spritetype* pSprite, int mode) {

    if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax) return false;
    GENDUDESND* sndInfo =& gCustomDudeSnd[mode]; bool gotSnd = false;
    short sndStartId = xsprite[pSprite->extra].sysData1; int rand = sndInfo->randomRange;
    int sndId = (sndStartId <= 0) ? sndInfo->defaultSndId : sndStartId + sndInfo->sndIdOffset;
    GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);

    // let's check if there same sounds already played by other dudes
    // so we won't get a lot of annoying screams in the same time and ensure sound played in it's full length (if not interruptable)
    if (pExtra->sndPlaying && !sndInfo->interruptable) {
        for (int i = 0; i < 256; i++) {
            if (Bonkle[i].sfxId <= 0) continue;
            for (int a = 0; a < rand; a++) {
                if (sndId + a == Bonkle[i].sfxId) {
                    if (Bonkle[i].lChan <= 0) {
                        pExtra->sndPlaying = false;
                        break;
                    }
                    return true;
                }
            }
        }

        pExtra->sndPlaying = false;

    }

    if (sndId < 0) return false;
    else if (sndStartId <= 0) { sndId += Random(rand); gotSnd = true; }
    else {

        // Let's try to get random snd
        int maxRetries = 5;
        while (maxRetries-- > 0) {
            int random = Random(rand);
            if (!gSoundRes.Lookup(sndId + random, "SFX")) continue;
            sndId = sndId + random;
            gotSnd = true;
            break;
        }

        // If no success in getting random snd, get first existing one
        if (gotSnd == false) {
            int maxSndId = sndId + rand;
            while (sndId++ < maxSndId) {
                if (!gSoundRes.Lookup(sndId, "SFX")) continue;
                gotSnd = true;
                break;
            }
        }

    }

    if (gotSnd == false) return false;
    else if (sndInfo->aiPlaySound) aiPlay3DSound(pSprite, sndId, AI_SFX_PRIORITY_2, -1);
    else sfxPlay3DSound(pSprite, sndId, -1, 0);

    pExtra->sndPlaying = true;
    return true;
}

bool spriteIsUnderwater(spritetype* pSprite, bool oldWay) {
    return ((sector[pSprite->sectnum].extra >= 0 && xsector[sector[pSprite->sectnum].extra].Underwater)
        || (oldWay && (xsprite[pSprite->extra].medium == kMediumWater || xsprite[pSprite->extra].medium == kMediumGoo)));
}

spritetype* leechIsDropped(spritetype* pSprite) {
    short nLeech = gGenDudeExtra[pSprite->index].nLifeLeech;
    if (nLeech >= 0 && nLeech < kMaxSprites) return &sprite[nLeech];
    return NULL;

}

void removeDudeStuff(spritetype* pSprite) {
    for (short nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        if (sprite[nSprite].owner != pSprite->index) continue;
        switch (sprite[nSprite].type) {
            case kThingArmedProxBomb:
            case kThingArmedRemoteBomb:
            case kModernThingTNTProx:
                sprite[nSprite].type = kSpriteDecoration;
                actPostSprite(sprite[nSprite].index, kStatFree);
                break;
            case kModernThingEnemyLifeLeech:
                killDudeLeech(&sprite[nSprite]);
                break;
        }
    }

    for (short nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
        if (sprite[nSprite].owner != pSprite->index) continue;
        actDamageSprite(sprite[nSprite].owner, &sprite[nSprite], (DAMAGE_TYPE) 0, 65535);
    }
}

void removeLeech(spritetype* pLeech, bool delSprite) {
    if (pLeech != NULL) {
        spritetype* pEffect = gFX.fxSpawn((FX_ID)52,pLeech->sectnum,pLeech->x,pLeech->y,pLeech->z,pLeech->ang);
        if (pEffect != NULL) {
            pEffect->cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
            pEffect->pal = 6;
            int repeat = 64 + Random(50);
            pEffect->xrepeat = repeat;
            pEffect->yrepeat = repeat;
        }

        sfxPlay3DSoundCP(pLeech, 490, -1, 0,60000);

        if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
            gGenDudeExtra[sprite[pLeech->owner].index].nLifeLeech = -1;

        if (delSprite) {
            pLeech->type = kSpriteDecoration;
            actPostSprite(pLeech->index, kStatFree);
        }

    }
}

void killDudeLeech(spritetype* pLeech) {
    if (pLeech != NULL) {
        actDamageSprite(pLeech->owner, pLeech, kDamageExplode, 65535);
        sfxPlay3DSoundCP(pLeech, 522, -1, 0, 60000);

        if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
            gGenDudeExtra[sprite[pLeech->owner].index].nLifeLeech = -1;
    }
}

XSPRITE* getNextIncarnation(XSPRITE* pXSprite) {
    for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
        if (rxBucket[i].type != 3 || rxBucket[i].index == pXSprite->reference)
            continue;

        if (sprite[rxBucket[i].index].statnum == kStatInactive)
            return &xsprite[sprite[rxBucket[i].index].extra];
    }
    return NULL;
}

bool dudeIsMelee(XSPRITE* pXSprite) {
    return gGenDudeExtra[sprite[pXSprite->reference].index].isMelee;
}

void scaleDamage(XSPRITE* pXSprite) {

    short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
    short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
    signed short* curScale = gGenDudeExtra[sprite[pXSprite->reference].index].dmgControl;
    for (int i = 0; i < kDmgMax; i++)
        curScale[i] = getDudeInfo(kDudeModernCustom)->startDamage[i];

    switch (weaponType) {
        // just copy damage resistance of dude that should be summoned
        case kGenDudeWeaponSummon:
            for (int i = 0; i < kDmgMax; i++)
                curScale[i] = getDudeInfo(curWeapon)->startDamage[i];
            break;
        // these does not like the explosions and burning
        case kGenDudeWeaponKamikaze:
            curScale[kDmgBurn] = curScale[kDmgExplode] = curScale[kDmgElectric] = 1024;
            break;
        case kGenDudeWeaponMissile:
        case kGenDudeWeaponThrow:
            switch (curWeapon) {
                case kMissileButcherKnife:
                    curScale[kDmgBullet] = 100;
                    fallthrough__;
                case kMissileEctoSkull:
                    curScale[kDmgSpirit] = 32;
                    break;
                case kMissileLifeLeechAltNormal:
                case kMissileLifeLeechAltSmall:
                case kMissileArcGargoyle:
                    curScale[kDmgSpirit] -= 32;
                    curScale[kDmgElectric] = 52;
                    break;
                case kMissileFlareRegular:
                case kMissileFlareAlt:
                case kMissileFlameSpray:
                case kMissileFlameHound:
                case kThingArmedSpray:
                case kThingPodFireBall:
                case kThingNapalmBall:
                    curScale[kDmgBurn] = 32;
                    break;
                case kMissileLifeLeechRegular:
                    curScale[kDmgBurn] = 60 + Random(4);
                    fallthrough__;
                case kThingDroppedLifeLeech:
                case kModernThingEnemyLifeLeech:
                    curScale[kDmgSpirit] = 32 + Random(18);
                    break;
                case kMissileFireball:
                case kMissileFireballNapalm:
                case kMissileFireballCerberus:
                case kMissileFireballTchernobog:
                    curScale[kDmgBurn] = 50;
                    curScale[kDmgExplode] -= 32;
                    curScale[kDmgFall] = 65 + Random(15);
                    break;
                case kThingTNTBarrel:
                case kThingArmedProxBomb:
                case kThingArmedRemoteBomb:
                case kThingArmedTNTBundle:
                case kThingArmedTNTStick:
                case kModernThingTNTProx:
                    curScale[kDmgBurn] -= 32;
                    curScale[kDmgExplode] -= 32;
                    curScale[kDmgFall] = 65 + Random(15);
                    break;
                case kMissileTeslaAlt:
                case kMissileTeslaRegular:
                    curScale[kDmgElectric] = 32 + Random(8);
                    break;
            }
            break;

    }

    // add resistance if have an armor item to drop
    if (pXSprite->dropMsg >= kItemArmorAsbest && pXSprite->dropMsg <= kItemArmorSuper) {
        switch (pXSprite->dropMsg) {
            case kItemArmorAsbest:
                curScale[kDmgBurn] = 0;
                curScale[kDmgExplode] -= 30;
                break;
            case kItemArmorBasic:
                curScale[kDmgBurn] -= 15;
                curScale[kDmgExplode] -= 15;
                curScale[kDmgBullet] -= 15;
                curScale[kDmgSpirit] -= 15;
                break;
            case kItemArmorBody:
                curScale[kDmgBullet] -= 30;
                break;
            case kItemArmorFire:
                curScale[kDmgBurn] -= 30;
                curScale[kDmgExplode] -= 30;
                break;
            case kItemArmorSpirit:
                curScale[kDmgSpirit] -= 30;
                break;
            case kItemArmorSuper:
                curScale[kDmgBurn] -= 60;
                curScale[kDmgExplode] -= 60;
                curScale[kDmgBullet] -= 60;
                curScale[kDmgSpirit] -= 60;
                break;
        }
    }

    // take in account yrepeat of sprite
    short yrepeat = sprite[pXSprite->reference].yrepeat;
    if (yrepeat < 64) {
        for (int i = 0; i < kDmgMax; i++) curScale[i] += (64 - yrepeat);
    } else if (yrepeat > 64) {
        for (int i = 0; i < kDmgMax; i++) curScale[i] -= ((yrepeat - 64) >> 2);
    }

    // take surface type into account
    int surfType = tileGetSurfType(sprite[pXSprite->reference].index + 0xc000);
    switch (surfType) {
        case 1:  // stone
            curScale[kDmgFall] = 0;
            curScale[kDmgBullet] -= 200;
            curScale[kDmgBurn] -= 100;
            curScale[kDmgExplode] -= 80;
            curScale[kDmgChoke] += 30;
            curScale[kDmgElectric] += 20;
            break;
        case 2:  // metal
            curScale[kDmgFall] = 16;
            curScale[kDmgBullet] -= 128;
            curScale[kDmgBurn] -= 90;
            curScale[kDmgExplode] -= 55;
            curScale[kDmgChoke] += 20;
            curScale[kDmgElectric] += 30;
            break;
        case 3:  // wood
            curScale[kDmgBullet] -= 10;
            curScale[kDmgBurn] += 50;
            curScale[kDmgExplode] += 40;
            curScale[kDmgChoke] += 10;
            curScale[kDmgElectric] -= 60;
            break;
        case 5:  // water
        case 6:  // dirt
        case 7:  // clay
        case 13: // goo
            curScale[kDmgFall] = 8;
            curScale[kDmgBullet] -= 20;
            curScale[kDmgBurn] -= 200;
            curScale[kDmgExplode] -= 60;
            curScale[kDmgChoke] = 0;
            curScale[kDmgElectric] += 40;
            break;
        case 8:  // snow
        case 9:  // ice
            curScale[kDmgFall] = 8;
            curScale[kDmgBullet] -= 20;
            curScale[kDmgBurn] -= 100;
            curScale[kDmgExplode] -= 50;
            curScale[kDmgChoke] = 0;
            curScale[kDmgElectric] += 40;
            break;
        case 10: // leaves
        case 12: // plant
            curScale[kDmgFall] = 0;
            curScale[kDmgBullet] -= 10;
            curScale[kDmgBurn] += 70;
            curScale[kDmgExplode] += 50;
            break;
        case 11: // cloth
            curScale[kDmgFall] = 8;
            curScale[kDmgBullet] -= 10;
            curScale[kDmgBurn] += 30;
            curScale[kDmgExplode] += 20;
            break;
        case 14: // lava
            curScale[kDmgBurn] = 0;
            curScale[kDmgExplode] = 0;
            curScale[kDmgChoke] += 30;
            break;
    }

    // finally, scale dmg for difficulty
    for (int i = 0; i < kDmgMax; i++)
        curScale[i] = mulscale8(DudeDifficulty[gGameOptions.nDifficulty], ClipLow(curScale[i], 1));

    //short* dc = curScale;
    //if (pXSprite->rxID == 788)
        //viewSetSystemMessage("0: %d, 1: %d, 2: %d, 3: %d, 4: %d, 5: %d, 6: %d", dc[0], dc[1], dc[2], dc[3], dc[4], dc[5], dc[6]);
}

int getDispersionModifier(spritetype* pSprite, int minDisp, int maxDisp) {
    // the faster fire rate, the less frames = more dispersion
    Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(xsprite[pSprite->extra].data2 + 6, "SEQ"); int disp = 1;
    if (hSeq != NULL && (pSeq = (Seq*)gSysRes.Load(hSeq)) != NULL) {
        int nFrames = pSeq->nFrames; int ticks = pSeq->ticksPerFrame; int shots = 0;
        for (int i = 0; i < pSeq->nFrames; i++) {
            if (pSeq->frames[i].at5_5) shots++;
        }

        disp = (((shots * 1000) / nFrames) / ticks) * 20;
        if (gGameOptions.nDifficulty > 0)
            disp /= gGameOptions.nDifficulty;

        //viewSetSystemMessage("DISP: %d FRAMES: %d SHOTS: %d TICKS %d", disp, nFrames, shots, ticks);

    }

    return ClipRange(disp, minDisp, maxDisp);
}

// the distance counts from sprite size
int getRangeAttackDist(spritetype* pSprite, int minDist, int maxDist) {
    short yrepeat = pSprite->yrepeat; int dist = 0; int seqId = xsprite[pSprite->extra].data2;
    short mul = 550; int picnum = pSprite->picnum;

    if (yrepeat > 0) {
        if (seqId >= 0) {
            Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(seqId, "SEQ");
            if (hSeq) {
                pSeq = (Seq*)gSysRes.Load(hSeq);
                picnum = seqGetTile(&pSeq->frames[0]);
            }
        }

        dist = tilesiz[picnum].y << 8;
        if (yrepeat < 64) dist -= (64 - yrepeat) * mul;
        else if (yrepeat > 64) dist += (yrepeat - 64) * (mul / 3);
    }

    dist = ClipRange(dist, minDist, maxDist);
    //viewSetSystemMessage("DIST: %d, SPRHEIGHT: %d: YREPEAT: %d PIC: %d", dist, tilesiz[pSprite->picnum].y, yrepeat, picnum);
    return dist;
}

int getBaseChanceModifier(int baseChance) {
    return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance);
}

int getRecoilChance(spritetype* pSprite) {
    XSPRITE* pXSprite = &xsprite[pSprite->extra]; int mass = getSpriteMassBySize(pSprite);
    int baseChance = (!dudeIsMelee(pXSprite) ? 0x8000 : 0x4000);
    baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;

    int chance = ((baseChance / mass) << 7);
    return chance;
}

int getDodgeChance(spritetype* pSprite) {
    XSPRITE* pXSprite = &xsprite[pSprite->extra]; int mass = getSpriteMassBySize(pSprite);
    int baseChance = (!dudeIsMelee(pXSprite) ? 0x6000 : 0x1000);
    baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;

    int chance = ((baseChance / mass) << 7);
    return chance;

}

void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT event)
{
    if (event.cmd == kCmdOff) {
        actPostSprite(pSprite->index, kStatFree);
        return;
    }

    int nTarget = pXSprite->target;
    if (spriRangeIsFine(nTarget) && nTarget != pSprite->owner) {
        spritetype* pTarget = &sprite[nTarget];
        if (pTarget->statnum == kStatDude && !(pTarget->flags & 32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites && !pXSprite->stateTimer) {

            if (IsPlayerSprite(pTarget)) {
                PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
                if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return;
            }
            int top, bottom;
            GetSpriteExtents(pSprite, &top, &bottom);
            int nType = pTarget->type - kDudeBase;
            DUDEINFO* pDudeInfo = &dudeInfo[nType];
            int z1 = (top - pSprite->z) - 256;
            int x = pTarget->x; int y = pTarget->y; int z = pTarget->z;
            int nDist = approxDist(x - pSprite->x, y - pSprite->y);

            if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum)) {
                int t = divscale12(nDist, 0x1aaaaa);
                x += (xvel[nTarget] * t) >> 12;
                y += (yvel[nTarget] * t) >> 12;
                int angBak = pSprite->ang;
                pSprite->ang = getangle(x - pSprite->x, y - pSprite->y);
                int dx = Cos(pSprite->ang) >> 16;
                int dy = Sin(pSprite->ang) >> 16;
                int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
                int dz = divscale10(tz - top - 256, nDist);
                int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
                int t2;

                if (!pXSprite->data3) t2 = 120 / 10.0;
                else t2 = (3 * 120) / 10.0;

                spritetype * pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
                if (pMissile)
                {
                    pMissile->owner = pSprite->owner;
                    pXSprite->stateTimer = 1;
                    evPost(pSprite->index, 3, t2, kCallbackLeechStateTimer);
                    pXSprite->data3 = ClipLow(pXSprite->data3 - 1, 0);
                }
                pSprite->ang = angBak;
            }
        }

    }
}

bool doExplosion(spritetype* pSprite, int nType) {
    spritetype* pExplosion = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatExplosion, true);
    if (pExplosion->extra < 0 || pExplosion->extra >= kMaxXSprites)
        return false;

    int nSeq = 4; int nSnd = 304; EXPLOSION* pExpl = &explodeInfo[nType];

    pExplosion->type = nType;
    pExplosion->cstat |= CSTAT_SPRITE_INVISIBLE;
    pExplosion->owner = pSprite->index;
    pExplosion->shade = -127;

    pExplosion->yrepeat = pExplosion->xrepeat = pExpl->repeat;

    xsprite[pExplosion->extra].data1 = pExpl->ticks;
    xsprite[pExplosion->extra].data2 = pExpl->quakeEffect;
    xsprite[pExplosion->extra].data3 = pExpl->flashEffect;

    if (nType == 0) { nSeq = 3; nSnd = 303; }
    else if (nType == 2) { nSeq = 4; nSnd = 305; }
    else if (nType == 3) { nSeq = 9; nSnd = 307; }
    else if (nType == 4) { nSeq = 5; nSnd = 307; }
    else if (nType <= 6) { nSeq = 4; nSnd = 303; }
    else if (nType == 7) { nSeq = 4; nSnd = 303; }

    seqSpawn(nSeq, 3, pExplosion->extra, -1);
    sfxPlay3DSound(pExplosion, nSnd, -1, 0);

    return true;
}

// this function allows to spawn new custom dude and inherit spawner settings,
// so custom dude can have different weapons, hp and so on...
spritetype* genDudeSpawn(XSPRITE* pXSource, spritetype* pSprite, int nDist) {

    spritetype* pSource = &sprite[pXSource->reference];
    spritetype* pDude = actSpawnSprite(pSprite, kStatDude); XSPRITE* pXDude = &xsprite[pDude->extra];

    int x, y, z = pSprite->z, nAngle = pSprite->ang, nType = kDudeModernCustom;

    if (nDist > 0) {

        x = pSprite->x + mulscale30r(Cos(nAngle), nDist);
        y = pSprite->y + mulscale30r(Sin(nAngle), nDist);

    } else {

        x = pSprite->x;
        y = pSprite->y;

    }

    pDude->type = nType; pDude->ang = nAngle;
    vec3_t pos = { x, y, z }; setsprite(pDude->index, &pos);
    pDude->cstat |= 0x1101; pDude->clipdist = dudeInfo[nType - kDudeBase].clipdist;

    // inherit weapon, seq and sound settings.
    pXDude->data1 = pXSource->data1;
    pXDude->data2 = pXSource->data2;
    pXDude->sysData1 = pXSource->data3; // move sndStartId from data3 to sysData1
    pXDude->data3 = 0;

    // spawn seq
    seqSpawn(genDudeSeqStartId(pXDude), 3, pDude->extra, -1);

    // inherit movement speed.
    pXDude->busyTime = pXSource->busyTime;

    // inherit clipdist?
    if (pSource->clipdist > 0)
        pDude->clipdist = pSource->clipdist;

    // inherit custom hp settings
    if (pXSource->data4 <= 0) pXDude->health = dudeInfo[nType - kDudeBase].startHealth << 4;
    else pXDude->health = ClipRange(pXSource->data4 << 4, 1, 65535);

    if (pSource->flags & kModernTypeFlag1) {
        switch (pSource->type) {
            case kModernCustomDudeSpawn:
                //inherit pal?
                if (pDude->pal <= 0) pDude->pal = pSource->pal;

                // inherit spawn sprite trigger settings, so designer can count monsters.
                pXDude->txID = pXSource->txID;
                pXDude->command = pXSource->command;
                pXDude->triggerOn = pXSource->triggerOn;
                pXDude->triggerOff = pXSource->triggerOff;

                // inherit drop items
                pXDude->dropMsg = pXSource->dropMsg;

                // inherit required key so it can be dropped
                pXDude->key = pXSource->key;

                // inherit dude flags
                pXDude->dudeDeaf = pXSource->dudeDeaf;
                pXDude->dudeGuard = pXSource->dudeGuard;
                pXDude->dudeAmbush = pXSource->dudeAmbush;
                pXDude->dudeFlag4 = pXSource->dudeFlag4;
                pXDude->unused1 = pXSource->unused1;
                break;
        }
    }

    // inherit sprite size (useful for seqs with zero repeats)
    if (pSource->flags & kModernTypeFlag2) {
        pDude->xrepeat = pSource->xrepeat;
        pDude->yrepeat = pSource->yrepeat;
    }

    gKillMgr.AddCount(pDude);
    aiInitSprite(pDude);
    return pDude;
}

void genDudeTransform(spritetype* pSprite) {

    if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
        consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
        return;
    }

    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    XSPRITE* pXIncarnation = getNextIncarnation(pXSprite);
    if (pXIncarnation == NULL) {
        if (pXSprite->sysData1 == kGenDudeTransformStatus) pXSprite->sysData1 = 0;
        trTriggerSprite(pSprite->index, pXSprite, kCmdOff, pSprite->index);
        return;
    }

    spritetype* pIncarnation = &sprite[pXIncarnation->reference];
    pXSprite->key = pXSprite->dropMsg = pXSprite->locked = 0;

    // save incarnation's going on and off options
    bool triggerOn = pXIncarnation->triggerOn;
    bool triggerOff = pXIncarnation->triggerOff;

    // then remove it from incarnation so it will not send the commands
    pXIncarnation->triggerOn = false;
    pXIncarnation->triggerOff = false;

    // trigger dude death before transform
    trTriggerSprite(pSprite->index, pXSprite, kCmdOff, pSprite->index);

    pSprite->type = pSprite->inittype = pIncarnation->type;
    pSprite->flags = pIncarnation->flags;
    pSprite->pal = pIncarnation->pal;
    pSprite->shade = pIncarnation->shade;
    pSprite->clipdist = pIncarnation->clipdist;
    pSprite->xrepeat = pIncarnation->xrepeat;
    pSprite->yrepeat = pIncarnation->yrepeat;

    pXSprite->txID = pXIncarnation->txID;
    pXSprite->command = pXIncarnation->command;
    pXSprite->triggerOn = triggerOn;
    pXSprite->triggerOff = triggerOff;
    pXSprite->busyTime = pXIncarnation->busyTime;
    pXSprite->waitTime = pXIncarnation->waitTime;

    // inherit respawn properties
    pXSprite->respawn = pXIncarnation->respawn;
    pXSprite->respawnPending = pXIncarnation->respawnPending;

    pXSprite->burnTime = 0;
    pXSprite->burnSource = -1;

    pXSprite->data1 = pXIncarnation->data1;
    pXSprite->data2 = pXIncarnation->data2;

    pXSprite->sysData1 = pXIncarnation->data3;  // soundBase id
    pXSprite->sysData2 = pXIncarnation->data4;  // start hp

    pXSprite->dudeGuard = pXIncarnation->dudeGuard;
    pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
    pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
    pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;
    pXSprite->unused1 = pXIncarnation->unused1;

    pXSprite->dropMsg = pXIncarnation->dropMsg;
    pXSprite->key = pXIncarnation->key;

    pXSprite->locked = pXIncarnation->locked;
    pXSprite->Decoupled = pXIncarnation->Decoupled;

    // clear drop items of the incarnation
    pXIncarnation->key = pXIncarnation->dropMsg = 0;

    // set hp
    if (pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
    else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);

    int seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
    switch (pSprite->type) {
        case kDudePodMother: // fake dude
        case kDudeTentacleMother: // fake dude
            break;
        case kDudeModernCustom:
        case kDudeModernCustomBurning:
            seqId = genDudeSeqStartId(pXSprite);
            genDudePrepare(pSprite, kGenDudePropertyMass);
            fallthrough__; // go below
        default:
            seqSpawn(seqId, 3, pSprite->extra, -1);

            // save target
            int target = pXSprite->target;

            // re-init sprite
            aiInitSprite(pSprite);

            // try to restore target
            if (target == -1) aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
            else aiSetTarget(pXSprite, target);

            // finally activate it
            aiActivateDude(pSprite, pXSprite);

            break;
    }
    pXIncarnation->triggerOn = triggerOn;
    pXIncarnation->triggerOff = triggerOff;

    /*// remove the incarnation in case if non-locked
    if (pXIncarnation->locked == 0) {
        pXIncarnation->txID = pIncarnation->type = 0;
        actPostSprite(pIncarnation->index, kStatFree);
        // or restore triggerOn and off options
    } else {
        pXIncarnation->triggerOn = triggerOn;
        pXIncarnation->triggerOff = triggerOff;
    }*/
}

void updateTargetOfLeech(spritetype* pSprite) {
    if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
        consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
        return;
    }

    spritetype* pLeech = leechIsDropped(pSprite);
    if (pLeech == NULL || pLeech->extra < 0) gGenDudeExtra[pSprite->index].nLifeLeech = -1;
    else if (xsprite[pSprite->extra].target != xsprite[pLeech->extra].target) {
        XSPRITE* pXDude = &xsprite[pSprite->extra]; XSPRITE* pXLeech = &xsprite[pLeech->extra];
        if (pXDude->target < 0 && spriRangeIsFine(pXLeech->target)) {
            aiSetTarget(pXDude, pXLeech->target);
            if (inIdle(pXDude->aiState))
                aiActivateDude(pSprite, pXDude);
        } else {
            pXLeech->target = pXDude->target;
        }
    }
}

void updateTargetOfSlaves(spritetype* pSprite) {
    if (!xspriRangeIsFine(pSprite->extra)) {
        consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
        return;
    }

    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    GENDUDEEXTRA* pExtra = genDudeExtra(pSprite); short* slave = pExtra->slave;
    spritetype* pTarget = (pXSprite->target >= 0 && IsDudeSprite(&sprite[pXSprite->target])) ? &sprite[pXSprite->target] : NULL;
    XSPRITE* pXTarget = (pTarget != NULL && xspriRangeIsFine(pTarget->extra) && xsprite[pTarget->extra].health > 0) ? &xsprite[pTarget->extra] : NULL;

    int newCnt = pExtra->slaveCount;
    for (int i = 0; i <= gGameOptions.nDifficulty; i++) {
        if (spriRangeIsFine(slave[i])) {
            spritetype* pSlave = &sprite[slave[i]];
            if (!IsDudeSprite(pSlave) || !xspriRangeIsFine(pSlave->extra) || xsprite[pSlave->extra].health < 0) {
                slave[i] = pSlave->owner = -1; newCnt--;
                continue;
            }

            XSPRITE* pXSlave = &xsprite[pSlave->index];
            if (pXTarget != NULL) {
                if (pXSprite->target != pXSlave->target) aiSetTarget(pXSlave, pXSprite->target);
                // check if slave have proper target
                if (!spriRangeIsFine(pXSlave->target) || sprite[pXSlave->target].owner == pSprite->index)
                    aiSetTarget(pXSlave, pSprite->x, pSprite->y, pSprite->z);
            } else {
                aiSetTarget(pXSlave, pSprite->x, pSprite->y, pSprite->z); // try return to master
            }
        }
    }

    pExtra->slaveCount = newCnt;
}

short inDodge(AISTATE* aiState) {
    if (aiState == &genDudeDodgeL) return 1;
    else if (aiState == &genDudeDodgeD) return 2;
    else if (aiState == &genDudeDodgeW) return 3;
    else if (aiState == &genDudeDodgeShortL) return 4;
    else if (aiState == &genDudeDodgeShortD) return 5;
    else if (aiState == &genDudeDodgeShortW) return 6;
    else if (aiState == &genDudeDodgeShorterL) return 7;
    else if (aiState == &genDudeDodgeShorterD) return 8;
    else if (aiState == &genDudeDodgeShorterW) return 9;
    return 0;

}

bool inIdle(AISTATE* aiState) {
    return (aiState == &genDudeIdleW || aiState == &genDudeIdleL);
}

bool inAttack(AISTATE* aiState) {
    return (aiState == &genDudeFireL || aiState == &genDudeFireW
        || aiState == &genDudeFireD || aiState == &genDudeThrow || aiState == &genDudeThrow2 || aiState == &genDudePunch);
}

short inSearch(AISTATE* aiState) {
    if (aiState->stateType == kAiStateSearch) return 1;
    return 0;
}

short inChase(AISTATE* aiState) {
    if (aiState == &genDudeChaseL) return 1;
    else if (aiState == &genDudeChaseD) return 2;
    else if (aiState == &genDudeChaseW) return 3;
    else if (aiState == &genDudeChaseNoWalkL) return 4;
    else if (aiState == &genDudeChaseNoWalkD) return 5;
    else if (aiState == &genDudeChaseNoWalkW) return 6;
    else return 0;
}

short inRecoil(AISTATE* aiState) {
    if (aiState == &genDudeRecoilL || aiState == &genDudeRecoilTesla) return 1;
    else if (aiState == &genDudeRecoilD) return 2;
    else if (aiState == &genDudeRecoilW) return 3;
    else return 0;
}

short inDuck(AISTATE* aiState) {
    if (aiState == &genDudeFireD) return 1;
    else if (aiState == &genDudeChaseD) return 2;
    else if (aiState == &genDudeChaseNoWalkD) return 3;
    else if (aiState == &genDudeRecoilD) return 4;
    else if (aiState == &genDudeDodgeShortD) return 5;
    return 0;
}

bool canSwim(spritetype* pSprite) {
    return gGenDudeExtra[pSprite->index].canSwim;
}

bool canDuck(spritetype* pSprite) {
    return gGenDudeExtra[pSprite->index].canDuck;
}

bool canWalk(spritetype* pSprite) {
    return gGenDudeExtra[pSprite->index].canWalk;
}

int genDudeSeqStartId(XSPRITE* pXSprite) {
    if (genDudePrepare(&sprite[pXSprite->reference], kGenDudePropertyStates)) return pXSprite->data2;
    else return kGenDudeDefaultSeq;
}

bool genDudePrepare(spritetype* pSprite, int propId) {
    if (!spriRangeIsFine(pSprite->index)) {
        consoleSysMsg("!spriRangeIsFine(pSprite->index)");
        return false;
    } else if (!xspriRangeIsFine(pSprite->extra)) {
        consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
        return false;
    } else if (pSprite->type != kDudeModernCustom) {
        consoleSysMsg("pSprite->type != kDudeModernCustom");
        return false;
    } else if (propId < kGenDudePropertyAll || propId >= kGenDudePropertyMax) {
        viewSetSystemMessage("Unknown custom dude #%d property (%d)", pSprite->index, propId);
        return false;
    }

    XSPRITE* pXSprite = &xsprite[pSprite->extra];
    GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index]; pExtra->updReq[propId] = false;

    switch (propId) {
        case kGenDudePropertyAll:
        case kGenDudePropertyInitVals:
            pExtra->moveSpeed = getGenDudeMoveSpeed(pSprite, 0, true, false);
            pExtra->initVals[0] = pSprite->xrepeat;
            pExtra->initVals[1] = pSprite->yrepeat;
            pExtra->initVals[2] = pSprite->clipdist;
            if (propId) break;
            fallthrough__;

        case kGenDudePropertyWeapon: {
            pExtra->curWeapon = pXSprite->data1;
            switch (pXSprite->data1) {
                case VECTOR_TYPE_19: pExtra->curWeapon = kVectorBullet; break;
                case kMissileUnused: pExtra->curWeapon = kMissileArcGargoyle; break;
                case kThingDroppedLifeLeech: pExtra->curWeapon = kModernThingEnemyLifeLeech; break;
            }

            pExtra->canAttack = false;
            if (pExtra->curWeapon > 0 && gSysRes.Lookup(pXSprite->data2 + kGenDudeSeqAttackNormalL, "SEQ"))
                pExtra->canAttack = true;

            pExtra->weaponType = kGenDudeWeaponNone;
            if (pExtra->curWeapon > 0 && pExtra->curWeapon < kVectorMax) pExtra->weaponType = kGenDudeWeaponHitscan;
            else if (pExtra->curWeapon >= kDudeBase && pExtra->curWeapon < kDudeMax) pExtra->weaponType = kGenDudeWeaponSummon;
            else if (pExtra->curWeapon >= kMissileBase && pExtra->curWeapon < kMissileMax) pExtra->weaponType = kGenDudeWeaponMissile;
            else if (pExtra->curWeapon >= kThingBase && pExtra->curWeapon < kThingMax) pExtra->weaponType = kGenDudeWeaponThrow;
            else if (pExtra->curWeapon >= kTrapExploder && pExtra->curWeapon < (kTrapExploder + kExplodeMax) - 1)
                pExtra->weaponType = kGenDudeWeaponKamikaze;

            pExtra->isMelee = false;
            if (pExtra->weaponType == kGenDudeWeaponKamikaze) pExtra->isMelee = true;
            else if (pExtra->weaponType == kGenDudeWeaponHitscan) {
                if (gVectorData[pExtra->curWeapon].maxDist > 0 && gVectorData[pExtra->curWeapon].maxDist <= kGenDudeMaxMeleeDist)
                    pExtra->isMelee = true;
            }

            if (propId) break;
            fallthrough__;

        }
        case kGenDudePropertyDmgScale:
            scaleDamage(pXSprite);
            if (propId) break;
            fallthrough__;

        case kGenDudePropertyMass: {
            // to ensure mass gets updated, let's clear all cache
            SPRITEMASS* pMass = &gSpriteMass[pSprite->index];
            pMass->seqId = pMass->picnum = pMass->xrepeat = pMass->yrepeat = pMass->clipdist = 0;
            pMass->mass = pMass->airVel = pMass->fraction = 0;
            getSpriteMassBySize(pSprite);
            if (propId) break;
            fallthrough__;
        }
        case kGenDudePropertyAttack:
            pExtra->fireDist = getRangeAttackDist(pSprite, 3000, 45000);
            pExtra->throwDist = pExtra->fireDist; // temp
            pExtra->baseDispersion = getDispersionModifier(pSprite, 200, 3500);
            if (propId) break;
            fallthrough__;

        case kGenDudePropertyStates: {

            pExtra->canFly = false;

            // check the animation
            int seqStartId = -1;
            if (pXSprite->data2 <= 0) seqStartId = pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
            else seqStartId = pXSprite->data2;

            for (int i = seqStartId; i < seqStartId + kGenDudeSeqMax; i++) {
                switch (i - seqStartId) {
                    case kGenDudeSeqIdleL:
                    case kGenDudeSeqDeathDefault:
                    case kGenDudeSeqAttackNormalL:
                    case kGenDudeSeqAttackThrow:
                    case kGenDudeSeqAttackPunch:
                        if (!gSysRes.Lookup(i, "SEQ")) {
                            pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
                            viewSetSystemMessage("No SEQ animation id %d found for custom dude #%d!", i, pSprite->index);
                            viewSetSystemMessage("SEQ base id: %d", seqStartId);
                        } else if ((i - seqStartId) == kGenDudeSeqAttackPunch) {
                            pExtra->forcePunch = true; // required for those who don't have fire trigger in punch seq and for default animation
                            Seq* pSeq = NULL; DICTNODE* hSeq = gSysRes.Lookup(i, "SEQ");
                            if (hSeq) {

                                pSeq = (Seq*)gSysRes.Load(hSeq);
                                for (int i = 0; i < pSeq->nFrames; i++) {
                                    if (!pSeq->frames[i].at5_5) continue;
                                    pExtra->forcePunch = false;
                                    break;
                                }
                            }
                        }
                        break;
                    case kGenDudeSeqDeathExplode:
                        pExtra->availDeaths[kDmgExplode] = (bool)gSysRes.Lookup(i, "SEQ");
                        break;
                    case kGenDudeSeqBurning:
                        pExtra->canBurn = gSysRes.Lookup(i, "SEQ");
                        break;
                    case kGenDudeSeqElectocuted:
                        pExtra->canElectrocute = gSysRes.Lookup(i, "SEQ");
                        break;
                    case kGenDudeSeqRecoil:
                        pExtra->canRecoil = gSysRes.Lookup(i, "SEQ");
                        break;
                    case kGenDudeSeqMoveL: {
                        bool oldStatus = pExtra->canWalk;
                        pExtra->canWalk = gSysRes.Lookup(i, "SEQ");
                        if (oldStatus != pExtra->canWalk) {
                            if (pXSprite->target <= 0 || pXSprite->target >= kMaxSprites) {
                                if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeIdleW);
                                else aiGenDudeNewState(pSprite, &genDudeIdleL);
                            } else if (pExtra->canWalk) {
                                if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeChaseW);
                                else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeChaseD);
                                else aiGenDudeNewState(pSprite, &genDudeChaseL);
                            } else {
                                if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeChaseNoWalkW);
                                else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(pSprite, &genDudeChaseNoWalkD);
                                else aiGenDudeNewState(pSprite, &genDudeChaseNoWalkL);
                            }
                        }
                        break;
                    }
                    case kGenDudeSeqAttackNormalDW:
                        pExtra->canDuck = (gSysRes.Lookup(i, "SEQ") && gSysRes.Lookup(seqStartId + 14, "SEQ"));
                        pExtra->canSwim = (gSysRes.Lookup(i, "SEQ") && gSysRes.Lookup(seqStartId + 13, "SEQ")
                            && gSysRes.Lookup(seqStartId + 17, "SEQ"));
                        break;
                    case kGenDudeSeqDeathBurn1: {
                        bool seq15 = gSysRes.Lookup(i, "SEQ"); bool seq16 = gSysRes.Lookup(seqStartId + 16, "SEQ");
                        if (seq15 && seq16) pExtra->availDeaths[kDmgBurn] = 3;
                        else if (seq16) pExtra->availDeaths[kDmgBurn] = 2;
                        else if (seq15) pExtra->availDeaths[kDmgBurn] = 1;
                        else pExtra->availDeaths[kDmgBurn] = 0;
                        break;
                    }
                    case kGenDudeSeqMoveW:
                    case kGenDudeSeqMoveD:
                    case kGenDudeSeqDeathBurn2:
                    case kGenDudeSeqIdleW:
                        break;
                    case kGenDudeSeqReserved3:
                    case kGenDudeSeqReserved4:
                    case kGenDudeSeqReserved5:
                    case kGenDudeSeqReserved6:
                    case kGenDudeSeqReserved7:
                    case kGenDudeSeqReserved8:
                        /*if (gSysRes.Lookup(i, "SEQ")) {
                            viewSetSystemMessage("Found reserved SEQ animation (%d) for custom dude #%d!", i, pSprite->index);
                            viewSetSystemMessage("Using reserved animation is not recommended.");
                            viewSetSystemMessage("SEQ base id: %d", seqStartId);
                        }*/
                        break;
                }
            }
            if (propId) break;
            fallthrough__;
        }
        case kGenDudePropertyLeech:
            pExtra->nLifeLeech = -1;
            if (pSprite->owner != kMaxSprites - 1) {
                for (int nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
                    if (sprite[nSprite].owner == pSprite->index && sprite[nSprite].type == kModernThingEnemyLifeLeech) {
                        pExtra->nLifeLeech = nSprite;
                        break;
                    }
                }
            }
            if (propId) break;
            fallthrough__;

        case kGenDudePropertySlaves:
            pExtra->slaveCount = 0; memset(pExtra->slave, -1, sizeof(pExtra->slave));
            for (int nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
                if (sprite[nSprite].owner != pSprite->index) continue;
                else if (!IsDudeSprite(&sprite[nSprite]) || !xspriRangeIsFine(sprite[nSprite].extra) || xsprite[sprite[nSprite].extra].health <= 0) {
                    sprite[nSprite].owner = -1;
                    continue;
                }

                pExtra->slave[pExtra->slaveCount++] = nSprite;
                if (pExtra->slaveCount > gGameOptions.nDifficulty)
                    break;
            }
            if (propId) break;
            fallthrough__;

        case kGenDudePropertySpriteSize: {
            if (seqGetStatus(3, pSprite->extra) == -1)
                seqSpawn(pXSprite->data2 + pXSprite->aiState->seqId, 3, pSprite->extra, -1);

            // make sure dudes aren't in the floor or ceiling
            int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot);
            if (!(sector[pSprite->sectnum].ceilingstat & 0x0001))
                pSprite->z += ClipLow(sector[pSprite->sectnum].ceilingz - zTop, 0);
            if (!(sector[pSprite->sectnum].floorstat & 0x0001))
                pSprite->z += ClipHigh(sector[pSprite->sectnum].floorz - zBot, 0);

            pSprite->clipdist = ClipRange((pSprite->xrepeat + pSprite->yrepeat) >> 1, 4, 120);
            if (propId) break;
        }
    }

    return true;
}

void genDudePostDeath(spritetype* pSprite, DAMAGE_TYPE damageType, int damage) {
    if (damageType == kDamageExplode) {
        DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
        for (int i = 0; i < 3; i++)
            if (pDudeInfo->nGibType[i] > -1)
                GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);

        for (int i = 0; i < 4; i++)
            fxSpawnBlood(pSprite, damage);
    }

    gKillMgr.AddKill(pSprite);

    pSprite->type = kThingBloodChunks;
    actPostSprite(pSprite->index, kStatThing);
}

void aiGenDudeInitSprite(spritetype* pSprite, XSPRITE* pXSprite) {
    switch (pSprite->type) {
        case kDudeModernCustom: {
            DUDEEXTRA_STATS* pDudeExtraE = &gDudeExtra[pSprite->extra].stats;
            pDudeExtraE->active = pDudeExtraE->thinkTime = 0;
            aiGenDudeNewState(pSprite, &genDudeIdleL);
            break;
        }
        case kDudeModernCustomBurning:
            aiGenDudeNewState(pSprite, &genDudeBurnGoto);
            pXSprite->burnTime = 1200;
            break;
    }

    pSprite->flags = 15;
    return;
}

#endif