raze/source/blood/src/aiunicult.cpp

1997 lines
88 KiB
C++
Raw Normal View History

2019-09-19 22:42:45 +00:00
//-------------------------------------------------------------------------
/*
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.
*/
//-------------------------------------------------------------------------
#include "ns.h" // Must come before everything else!
2019-09-19 22:42:45 +00:00
#include "compat.h"
#include "build.h"
#include "pragmas.h"
#include "mmulti.h"
#include "common_game.h"
#include "actor.h"
#include "ai.h"
#include "aiunicult.h"
#include "blood.h"
#include "db.h"
#include "dude.h"
2019-09-19 22:42:45 +00:00
#include "eventq.h"
#include "globals.h"
2019-09-19 22:42:45 +00:00
#include "levels.h"
#include "player.h"
#include "seq.h"
#include "sfx.h"
#include "sound.h"
2019-09-19 22:42:45 +00:00
#include "trig.h"
#include "triggers.h"
#include "endgame.h"
#include "view.h"
#include "tile.h"
#include "sound/s_soundinternal.h"
2019-09-19 22:42:45 +00:00
BEGIN_BLD_NS
static void genDudeAttack1(int, int);
2019-09-19 22:42:45 +00:00
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 };
2019-09-19 22:42:45 +00:00
// ---------------------
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
};
2019-09-19 22:42:45 +00:00
GENDUDEEXTRA gGenDudeExtra[kMaxSprites];
2019-09-19 22:42:45 +00:00
static void forcePunch(spritetype* pSprite, XSPRITE* pXSprite) {
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;
}
void genDudeProcess(spritetype* pSprite, XSPRITE* pXSprite) {
GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
if (pExtra->slaveCount > 0)
updateTargetOfSlaves(pSprite);
if (pExtra->nLifeLeech >= 0)
updateTargetOfLeech(pSprite);
if (pXSprite->aiState->moveFunc)
pXSprite->aiState->moveFunc(pSprite, pXSprite);
if (pXSprite->aiState->thinkFunc && (gFrame & 3) == (pSprite->index & 3))
pXSprite->aiState->thinkFunc(pSprite, pXSprite);
if (pXSprite->stateTimer == 0 && pXSprite->aiState->nextState && (pXSprite->aiState->stateTicks > 0 || seqGetStatus(3, pSprite->extra) < 0))
aiGenDudeNewState(pSprite, pXSprite->aiState->nextState);
int hinder = ((pExtra->isMelee) ? 25 : 5) << 4;
if (pXSprite->health > 0 && hinder <= cumulDamage[pSprite->extra]) {
pXSprite->data3 = cumulDamage[pSprite->extra];
RecoilDude(pSprite, pXSprite);
}
2019-09-19 22:42:45 +00:00
}
void genDudeUpdate(spritetype* pSprite) {
for (int i = 0; i < kGenDudePropertyMax; i++) {
if (gGenDudeExtra[pSprite->index].updReq[i])
genDudePrepare(pSprite, i);
}
}
2019-09-19 22:42:45 +00:00
static void punchCallback(int, int nXIndex) {
XSPRITE* pXSprite = &xsprite[nXIndex];
if (pXSprite->target != -1) {
2019-09-19 22:42:45 +00:00
int nSprite = pXSprite->reference;
spritetype* pSprite = &sprite[nSprite];
int nZOffset1 = dudeInfo[pSprite->type - kDudeBase].eyeHeight * pSprite->yrepeat << 2;
2019-09-19 22:42:45 +00:00
int nZOffset2 = 0;
spritetype* pTarget = &sprite[pXSprite->target];
if(IsDudeSprite(pTarget))
nZOffset2 = dudeInfo[pTarget->type - kDudeBase].eyeHeight * pTarget->yrepeat << 2;
int dx = Cos(pSprite->ang) >> 16;
int dy = Sin(pSprite->ang) >> 16;
int dz = nZOffset1 - nZOffset2;
2019-09-19 22:42:45 +00:00
if (!playGenDudeSound(pSprite, kGenDudeSndAttackMelee))
sfxPlay3DSound(pSprite, 530, 1, 0);
actFireVector(pSprite, 0, 0, dx, dy, dz,VECTOR_TYPE_22);
2019-09-19 22:42:45 +00:00
}
}
2019-09-19 22:42:45 +00:00
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);
2019-09-19 22:42:45 +00:00
if (pExtra->weaponType == kGenDudeWeaponHitscan) {
dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];
2019-09-19 22:42:45 +00:00
// dispersal modifiers here in case if non-melee enemy
if (!dudeIsMelee(pXSprite)) {
dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion);
2019-09-19 22:42:45 +00:00
}
actFireVector(pSprite, 0, 0, dx, dy, dz,(VECTOR_TYPE)pExtra->curWeapon);
if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
sfxPlayVectorSound(pSprite, pExtra->curWeapon);
2019-09-19 22:42:45 +00:00
} else if (pExtra->weaponType == kGenDudeWeaponSummon) {
2019-09-19 22:42:45 +00:00
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.sub_263E0(1);
pExtra->slave[pExtra->slaveCount++] = pSpawned->index;
if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
sfxPlay3DSoundCP(pSprite, 379, 1, 0, 0x10000 - Random3(0x3000));
}
2019-09-19 22:42:45 +00:00
}
} else if (pExtra->weaponType == kGenDudeWeaponMissile) {
2019-09-19 22:42:45 +00:00
dx = Cos(pSprite->ang) >> 16; dy = Sin(pSprite->ang) >> 16; dz = gDudeSlope[nXIndex];
2019-09-19 22:42:45 +00:00
// dispersal modifiers here
dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion >> 1);
2019-09-19 22:42:45 +00:00
actFireMissile(pSprite, 0, 0, dx, dy, dz, pExtra->curWeapon);
if (!playGenDudeSound(pSprite, kGenDudeSndAttackNormal))
sfxPlayMissileSound(pSprite, pExtra->curWeapon);
2019-09-19 22:42:45 +00:00
}
}
static void ThrowCallback1(int, int nXIndex) {
ThrowThing(nXIndex, true);
}
static void ThrowCallback2(int, int nXIndex) {
ThrowThing(nXIndex, false);
2019-09-19 22:42:45 +00:00
}
static void ThrowThing(int nXIndex, bool impact) {
XSPRITE* pXSprite = &xsprite[nXIndex]; spritetype* pSprite = &sprite[pXSprite->reference];
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
2019-12-06 23:07:02 +00:00
const THINGINFO* pThinkInfo = &thingInfo[curWeapon - kThingBase];
if (!pThinkInfo->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;
}
2019-09-19 22:42:45 +00:00
spritetype* pThing = NULL;
if ((pThing = actFireThing(pSprite, 0, 0, (dz / 128) - zThrow, curWeapon, divscale(dist / 540, 120, 23))) == NULL) return;
else if (pThinkInfo->picnum < 0 && pThing->type != kModernThingThrowableRock) pThing->picnum = 0;
pThing->owner = pSprite->xvel;
switch (curWeapon) {
case kThingNapalmBall:
pThing->xrepeat = pThing->yrepeat = 24;
xsprite[pThing->extra].data4 = 3 + gGameOptions.nDifficulty;
impact = true;
break;
case kModernThingThrowableRock:
int sPics[6];
sPics[0] = 2406; sPics[1] = 2280;
sPics[2] = 2185; sPics[3] = 2155;
sPics[4] = 2620; sPics[5] = 3135;
pThing->picnum = sPics[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;
2019-09-19 22:42:45 +00:00
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 = 300 * gGameOptions.nDifficulty;
sfxPlay3DSound(pSprite, 490, -1, 0);
if (gGameOptions.nDifficulty <= 2) pXThing->data3 = 32700;
else pXThing->data3 = Random(10);
pThing->cstat &= ~CSTAT_SPRITE_BLOCK;
pThing->pal = 6;
pXThing->target = pTarget->xvel;
pXThing->Proximity = true;
pXThing->stateTimer = 1;
gGenDudeExtra[pSprite->index].nLifeLeech = pThing->index;
evPost(pThing->xvel, 3, 80, kCallbackLeechStateTimer);
return;
}
2019-09-19 22:42:45 +00:00
if (impact == true && dist <= 7680) xsprite[pThing->extra].Impact = true;
else {
xsprite[pThing->extra].Impact = false;
evPost(pThing->xvel, 3, 120 * Random(2) + 120, kCmdOn);
2019-09-19 22:42:45 +00:00
}
}
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);
2019-09-19 22:42:45 +00:00
sub_5F15C(pSprite, pXSprite);
}
static void thinkGoto(spritetype* pSprite, XSPRITE* pXSprite) {
2019-09-19 22:42:45 +00:00
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax)) {
consoleSysMsg("pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
return;
}
2019-09-19 22:42:45 +00:00
int dx = pXSprite->targetX - pSprite->x;
int dy = pXSprite->targetY - pSprite->y;
2019-09-19 22:42:45 +00:00
int nAngle = getangle(dx, dy);
aiChooseDirection(pSprite, pXSprite, nAngle);
2019-09-19 22:42:45 +00:00
// if reached target, change to search mode
if (approxDist(dx, dy) < 5120 && klabs(pSprite->ang - nAngle) < dudeInfo[pSprite->type - kDudeBase].periphery) {
if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
else aiGenDudeNewState(pSprite, &genDudeSearchL);
2019-09-19 22:42:45 +00:00
}
aiThinkTarget(pSprite, pXSprite);
}
static void thinkChase( spritetype* pSprite, XSPRITE* pXSprite ) {
2019-09-19 22:42:45 +00:00
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);
2019-09-19 22:42:45 +00:00
return;
} else {
genDudeUpdate(pSprite);
}
2019-09-19 22:42:45 +00:00
spritetype* pTarget = &sprite[pXSprite->target];
XSPRITE* pXTarget = (!IsDudeSprite(pTarget) || !xspriRangeIsFine(pTarget->extra)) ? NULL : &xsprite[pTarget->extra];
2019-09-19 22:42:45 +00:00
if (pXTarget == NULL) { // target lost
if(spriteIsUnderwater(pSprite,false)) aiGenDudeNewState(pSprite, &genDudeSearchShortW);
else aiGenDudeNewState(pSprite, &genDudeSearchShortL);
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);
2019-09-19 22:42:45 +00:00
return;
}
// check target
int dx = pTarget->x - pSprite->x; int dy = pTarget->y - pSprite->y;
int dist = ClipLow((int)approxDist(dx, dy), 1);
2019-09-19 22:42:45 +00:00
// 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);
2019-09-19 22:42:45 +00:00
return;
}
}
DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
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) {
2019-09-19 22:42:45 +00:00
if (((int)gFrameClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(pSprite, false))
playGenDudeSound(pSprite, kGenDudeSndChasing);
gDudeSlope[pSprite->extra] = divscale(pTarget->z - pSprite->z, dist, 10);
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 (!thingInfo[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;
}
2019-09-19 22:42:45 +00:00
} 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;
2019-09-19 22:42:45 +00:00
return;
}
2019-09-19 22:42:45 +00:00
XSPRITE* pXLeech = &xsprite[pLeech->extra];
int ldist = getTargetDist(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) {
2019-09-19 22:42:45 +00:00
aiGenDudeNewState(pSprite, &genDudeThrow2);
genDudeThrow2.nextState = &genDudeDodgeShortL;
2019-09-19 22:42:45 +00:00
} 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);
}
2019-09-19 22:42:45 +00:00
}
}
return;
case kModernThingThrowableRock:
if (Chance(0x4000)) aiGenDudeNewState(pSprite, &genDudeThrow2);
else playGenDudeSound(pSprite, kGenDudeSndTargetSpot);
return;
default:
aiGenDudeNewState(pSprite, &genDudeThrow2);
return;
2019-09-19 22:42:45 +00:00
}
} else if (dist <= meleeVector->maxDist) {
2019-09-19 22:42:45 +00:00
if (spriteIsUnderwater(pSprite, false)) {
if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
else aiGenDudeNewState(pSprite, &genDudeDodgeW);
2019-09-19 22:42:45 +00:00
}
else if (Chance(0x9000)) aiGenDudeNewState(pSprite, &genDudePunch);
else aiGenDudeNewState(pSprite, &genDudeDodgeL);
return;
2019-09-19 22:42:45 +00:00
} 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;
}
}
2019-09-19 22:42:45 +00:00
} else {
2019-09-19 22:42:45 +00:00
int vdist; int mdist; int defDist;
defDist = vdist = mdist = gGenDudeExtra[pSprite->index].fireDist;
2019-09-19 22:42:45 +00:00
if (weaponType == kGenDudeWeaponHitscan) {
if ((vdist = gVectorData[curWeapon].maxDist) <= 0)
vdist = defDist;
2019-09-19 22:42:45 +00:00
} else if (weaponType == kGenDudeWeaponSummon) {
2019-09-19 22:42:45 +00:00
// 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);
2019-09-19 22:42:45 +00:00
return;
}
}
2019-09-19 22:42:45 +00:00
} 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 kMissileFireballNapam:
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);
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
}
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->xvel] = zvel[pSprite->xvel] = yvel[pSprite->xvel] = 0;
if (doExplosion(pSprite, nType) && pXSprite->health > 0)
actDamageSprite(pSprite->xvel, pSprite, DAMAGE_TYPE_3, 65535);
2019-09-19 22:42:45 +00:00
}
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->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 = isImmune(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 kMissileFireballNapam:
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 (missileInfo[curWeapon - kMissileBase].dmgType[i] && (failed = isImmune(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;
}
}
}
2019-09-19 22:42:45 +00:00
}
}
2019-09-19 22:42:45 +00:00
int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite) {
UNREFERENCED_PARAMETER(pXSprite);
if (sub_5BDA8(pSprite, 14) || spriteIsUnderwater(pSprite,false))
{
if ( !sub_5BDA8(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 = &dudeInfo[pSprite->type - kDudeBase];
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) {
2019-09-19 22:42:45 +00:00
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 = &dudeInfo[pSprite->type - kDudeBase];
GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
2019-09-19 22:42:45 +00:00
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 {
2019-09-19 22:42:45 +00:00
int dang = ((kAng180 + pXSprite->goalAng - pSprite->ang) & 2047) - kAng180;
pSprite->ang = ((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & 2047);
2019-09-19 22:42:45 +00:00
// don't move forward if trying to turn around
if (klabs(dang) > kAng60)
2019-09-19 22:42:45 +00:00
return;
2019-09-19 22:42:45 +00:00
int sin = Sin(pSprite->ang);
int cos = Cos(pSprite->ang);
int frontSpeed = gGenDudeExtra[pSprite->index].moveSpeed;
2019-09-19 22:42:45 +00:00
xvel[pSprite->xvel] += mulscale(cos, frontSpeed, 30);
yvel[pSprite->xvel] += mulscale(sin, frontSpeed, 30);
}
2019-09-19 22:42:45 +00:00
}
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];
// redirect dudes which cannot walk to non-walk states
if (!gGenDudeExtra[pSprite->index].canWalk) {
2019-09-19 22:42:45 +00:00
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) {
#if 0
for (int i = 0; i < 256; i++) {
if (Bonkle[i].atc <= 0) continue;
for (int a = 0; a < rand; a++) {
if (sndId + a == Bonkle[i].atc) {
if (Bonkle[i].at0 <= 0) {
pExtra->sndPlaying = false;
break;
}
return true;
}
}
}
#endif
pExtra->sndPlaying = false;
}
2019-09-19 22:42:45 +00:00
if (sndId < 0) return false;
else if (sndStartId <= 0) { sndId += Random(rand); gotSnd = true; }
2019-09-19 22:42:45 +00:00
else {
2019-09-19 22:42:45 +00:00
// Let's try to get random snd
int maxRetries = 5;
while (maxRetries-- > 0) {
2019-09-19 22:42:45 +00:00
int random = Random(rand);
if (!soundEngine->FindSoundByResID(sndId + random)) continue;
sndId = sndId + random;
gotSnd = true;
break;
2019-09-19 22:42:45 +00:00
}
2019-09-19 22:42:45 +00:00
// If no success in getting random snd, get first existing one
if (gotSnd == false) {
int maxSndId = sndId + rand;
while (sndId++ <= maxSndId) {
if (!soundEngine->FindSoundByResID(sndId)) continue;
gotSnd = true;
break;
2019-09-19 22:42:45 +00:00
}
}
}
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;
2019-09-19 22:42:45 +00:00
}
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)));
2019-09-19 22:42:45 +00:00
}
2019-09-19 22:42:45 +00:00
spritetype* leechIsDropped(spritetype* pSprite) {
short nLeech = gGenDudeExtra[pSprite->index].nLifeLeech;
if (nLeech >= 0 && nLeech < kMaxSprites) return &sprite[nLeech];
2019-09-19 22:42:45 +00:00
return NULL;
2019-09-19 22:42:45 +00:00
}
void removeDudeStuff(spritetype* pSprite) {
for (short nSprite = headspritestat[kStatThing]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
2019-09-19 22:42:45 +00:00
if (sprite[nSprite].owner != pSprite->xvel) continue;
switch (sprite[nSprite].type) {
case kThingArmedProxBomb:
case kThingArmedRemoteBomb:
case kModernThingTNTProx:
sprite[nSprite].type = kSpriteDecoration;
actPostSprite(sprite[nSprite].xvel, kStatFree);
break;
case kModernThingEnemyLifeLeech:
killDudeLeech(&sprite[nSprite]);
break;
2019-09-19 22:42:45 +00:00
}
}
for (short nSprite = headspritestat[kStatDude]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
2019-09-19 22:42:45 +00:00
if (sprite[nSprite].owner != pSprite->xvel) 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;
2019-09-19 22:42:45 +00:00
pEffect->pal = 6;
int repeat = 64 + Random(50);
pEffect->xrepeat = repeat;
pEffect->yrepeat = repeat;
}
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
actPostSprite(pLeech->index, kStatFree);
}
2019-09-19 22:42:45 +00:00
}
}
void killDudeLeech(spritetype* pLeech) {
if (pLeech != NULL) {
actDamageSprite(pLeech->owner, pLeech, DAMAGE_TYPE_3, 65535);
sfxPlay3DSoundCP(pLeech, 522, -1, 0, 60000);
if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
gGenDudeExtra[sprite[pLeech->owner].index].nLifeLeech = -1;
2019-09-19 22:42:45 +00:00
}
}
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;
switch (sprite[rxBucket[i].index].statnum) {
case kStatDude:
case kStatInactive: // inactive (ambush) dudes
if (xsprite[sprite[rxBucket[i].index].extra].health > 0)
return &xsprite[sprite[rxBucket[i].index].extra];
2019-09-19 22:42:45 +00:00
}
2019-09-19 22:42:45 +00:00
}
return NULL;
}
bool dudeIsMelee(XSPRITE* pXSprite) {
return gGenDudeExtra[sprite[pXSprite->reference].index].isMelee;
}
2019-09-19 22:42:45 +00:00
void scaleDamage(XSPRITE* pXSprite) {
2019-09-19 22:42:45 +00:00
short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
unsigned short* curScale = gGenDudeExtra[sprite[pXSprite->reference].index].dmgControl;
for (int i = 0; i < kDmgMax; i++)
curScale[i] = dudeInfo[kDudeModernCustom - kDudeBase].startDamage[i];
2019-09-19 22:42:45 +00:00
// all enemies with vector weapons gets extra resistance to bullet damage
if (weaponType == kGenDudeWeaponHitscan) {
curScale[kDmgBullet] -= 10;
// just copy damage resistance of dude that should be summoned
} else if (weaponType == kGenDudeWeaponSummon) {
for (int i = 0; i < kDmgMax; i++)
curScale[i] = dudeInfo[curWeapon - kDudeBase].startDamage[i];
// these does not like the explosions and burning
} else if (weaponType == kGenDudeWeaponKamikaze) {
2019-09-19 22:42:45 +00:00
curScale[kDmgBurn] = curScale[kDmgExplode] = 512;
} else if (weaponType == kGenDudeWeaponMissile || weaponType == 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;
curScale[kDmgExplode] -= 20;
break;
case kMissileLifeLeechRegular:
case kThingDroppedLifeLeech:
case kModernThingEnemyLifeLeech:
curScale[kDmgSpirit] = 32 + Random(18);
curScale[kDmgBurn] = 60 + Random(4);
for (int i = 2; i < kDmgMax; i++) {
if (Chance(0x1000) && i != kDmgSpirit)
curScale[i] = 48 + Random(32);
}
break;
case kMissileFireball:
case kMissileFireballNapam:
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[kDmgExplode] = 32;
curScale[kDmgFall] = 65 + Random(15);
break;
case kMissileTeslaAlt:
case kMissileTeslaRegular:
curScale[kDmgElectric] = 32 + Random(8);
break;
}
2019-09-19 22:42:45 +00:00
}
// 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);
//int surfType = 4;
switch (surfType) {
case 1: // stone
curScale[kDmgFall] = 0;
curScale[kDmgBullet] -= 128;
curScale[kDmgBurn] -= 50;
curScale[kDmgExplode] -= 40;
curScale[kDmgChoke] += 30;
curScale[kDmgElectric] += 20;
break;
case 2: // metal
curScale[kDmgFall] = 16;
curScale[kDmgBullet] -= 64;
curScale[kDmgBurn] -= 45;
curScale[kDmgExplode] -= 35;
curScale[kDmgChoke] += 20;
curScale[kDmgElectric] += 30;
break;
case 3: // wood
curScale[kDmgBullet] -= 5;
curScale[kDmgBurn] += 50;
curScale[kDmgExplode] += 40;
curScale[kDmgChoke] += 10;
curScale[kDmgElectric] -= 30;
break;
case 5: // water
case 6: // dirt
case 7: // clay
case 13: // goo
curScale[kDmgFall] = 8;
curScale[kDmgBullet] -= 10;
curScale[kDmgBurn] -= 128;
curScale[kDmgExplode] -= 30;
curScale[kDmgChoke] = 0;
curScale[kDmgElectric] += 40;
break;
case 8: // snow
case 9: // ice
curScale[kDmgFall] = 8;
curScale[kDmgBullet] -= 10;
curScale[kDmgBurn] -= 60;
curScale[kDmgExplode] -= 40;
curScale[kDmgChoke] = 0;
curScale[kDmgElectric] += 40;
break;
case 10: // leaves
case 12: // plant
curScale[kDmgFall] = 0;
curScale[kDmgBullet] -= 5;
curScale[kDmgBurn] += 70;
curScale[kDmgExplode] += 50;
break;
case 11: // cloth
curScale[kDmgFall] = 8;
curScale[kDmgBullet] -= 5;
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->at8; 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);
}
2019-09-19 22:42:45 +00:00
int getRecoilChance(spritetype* pSprite) {
XSPRITE* pXSprite = &xsprite[pSprite->extra]; int mass = getSpriteMassBySize(pSprite);
int baseChance = (!dudeIsMelee(pXSprite) ? 0x8000 : 0x4000);
baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
int chance = ((baseChance / mass) << 7);
return chance;
2019-09-19 22:42:45 +00:00
}
void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT event)
2019-09-19 22:42:45 +00:00
{
if (event.cmd == kCmdOff) {
2019-09-19 22:42:45 +00:00
actPostSprite(pSprite->xvel, kStatFree);
return;
}
2019-09-19 22:42:45 +00:00
int nTarget = pXSprite->target;
if (spriRangeIsFine(nTarget) && nTarget != pSprite->owner) {
2019-09-19 22:42:45 +00:00
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;
}
2019-09-19 22:42:45 +00:00
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)) {
2019-09-19 22:42:45 +00:00
int t = divscale(nDist, 0x1aaaaa, 12);
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 = divscale(tz - top - 256, nDist, 10);
int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
2019-09-19 22:42:45 +00:00
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);
2019-09-19 22:42:45 +00:00
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;
2019-09-19 22:42:45 +00:00
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);
2019-09-19 22:42:45 +00:00
return true;
}
2019-09-19 22:42:45 +00:00
void updateTargetOfLeech(spritetype* pSprite) {
if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
return;
2019-09-19 22:42:45 +00:00
}
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;
}
}
2019-09-19 22:42:45 +00:00
}
void updateTargetOfSlaves(spritetype* pSprite) {
if (!xspriRangeIsFine(pSprite->extra)) {
consoleSysMsg("!xspriRangeIsFine(pSprite->extra)");
return;
2019-09-19 22:42:45 +00:00
}
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 (!(pSprite->index >= 0 && pSprite->index < kMaxSprites)) {
consoleSysMsg("pSprite->index >= 0 && pSprite->index < kMaxSprites");
return false;
} else if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
consoleSysMsg("pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
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 19: pExtra->curWeapon = 2; break;
case 310: 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, 1200, 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 = dudeInfo[pSprite->type - kDudeBase].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 = dudeInfo[pSprite->type - kDudeBase].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");
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 (!spriRangeIsFine(pXSprite->target)) {
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 && pSprite->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;
}
END_BLD_NS