raze/source/games/blood/src/ai.cpp

1990 lines
67 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
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 "build.h"
2020-11-21 22:40:33 +00:00
#include "savegamehelp.h"
2019-09-19 22:42:45 +00:00
#include "blood.h"
BEGIN_BLD_NS
void RecoilDude(DBloodActor* actor);
2019-09-19 22:42:45 +00:00
AISTATE genIdle = {kAiStateGenIdle, 0, -1, 0, NULL, NULL, NULL, NULL };
AISTATE genRecoil = {kAiStateRecoil, 5, -1, 20, NULL, NULL, NULL, &genIdle };
const int dword_138BB0[5] = {0x2000, 0x4000, 0x8000, 0xa000, 0xe000};
2019-09-19 22:42:45 +00:00
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-15 22:12:28 +00:00
bool dudeIsPlayingSeq(DBloodActor *actor, int nSeq)
2019-09-19 22:42:45 +00:00
{
2021-09-15 22:12:28 +00:00
auto pSprite = &actor->s();
if (pSprite->statnum == kStatDude && pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
2019-09-19 22:42:45 +00:00
{
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
if (seqGetID(3, pSprite->extra) == pDudeInfo->seqStartID + nSeq && seqGetStatus(3, pSprite->extra) >= 0)
return true;
}
return false;
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-15 22:12:28 +00:00
void aiPlay3DSound(DBloodActor *actor, int a2, AI_SFX_PRIORITY a3, int a4)
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA *pDudeExtra = &actor->dudeExtra;
2019-09-19 22:42:45 +00:00
if (a3 == AI_SFX_PRIORITY_0)
2021-09-15 22:12:28 +00:00
sfxPlay3DSound(actor, a2, a4, 2);
else if (a3 > pDudeExtra->prio || pDudeExtra->time <= PlayClock)
2019-09-19 22:42:45 +00:00
{
2021-09-15 22:12:28 +00:00
sfxKill3DSound(actor, -1, -1);
sfxPlay3DSound(actor, a2, a4, 0);
2020-11-21 22:40:33 +00:00
pDudeExtra->prio = a3;
pDudeExtra->time = PlayClock+120;
2019-09-19 22:42:45 +00:00
}
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiNewState(DBloodActor* actor, AISTATE *pAIState)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
pXSprite->stateTimer = pAIState->stateTicks;
2019-09-19 22:42:45 +00:00
pXSprite->aiState = pAIState;
int seqStartId = pDudeInfo->seqStartID;
2021-09-15 22:01:50 +00:00
if (pAIState->seqId >= 0)
{
seqStartId += pAIState->seqId;
if (getSequence(seqStartId))
seqSpawn(seqStartId, actor, pAIState->funcId);
2019-09-19 22:42:45 +00:00
}
if (pAIState->enterFunc)
pAIState->enterFunc(actor);
2019-09-19 22:42:45 +00:00
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static bool isImmune(DBloodActor* actor, int dmgType, int minScale)
{
if (dmgType >= kDmgFall && dmgType < kDmgMax && actor->hasX() && actor->x().locked != 1)
{
int type = actor->s().type;
if (type >= kThingBase && type < kThingMax)
return (thingInfo[type - kThingBase].dmgControl[dmgType] <= minScale);
else if (actor->IsDudeActor())
{
if (actor->IsPlayerActor()) return (gPlayer[type - kDudePlayer1].godMode || gPlayer[type - kDudePlayer1].damageControl[dmgType] <= minScale);
else return (dudeInfo[type - kDudeBase].damageVal[dmgType] <= minScale);
}
}
return true;
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool CanMove(DBloodActor *actor, DBloodActor* target, int nAngle, int nRange)
2019-09-19 22:42:45 +00:00
{
auto pSprite = &actor->s();
2019-09-19 22:42:45 +00:00
int top, bottom;
GetSpriteExtents(pSprite, &top, &bottom);
int x = pSprite->x;
int y = pSprite->y;
int z = pSprite->z;
HitScan(pSprite, z, CosScale16(nAngle), SinScale16(nAngle), 0, CLIPMASK0, nRange);
2019-09-19 22:42:45 +00:00
int nDist = approxDist(x-gHitInfo.hitx, y-gHitInfo.hity);
if (nDist - (pSprite->clipdist << 2) < nRange)
{
if (gHitInfo.hitsprite < 0 || target == nullptr || target->s().index != gHitInfo.hitsprite)
2019-09-19 22:42:45 +00:00
return false;
return true;
}
x += MulScale(nRange, Cos(nAngle), 30);
y += MulScale(nRange, Sin(nAngle), 30);
2019-09-19 22:42:45 +00:00
int nSector = pSprite->sectnum;
assert(nSector >= 0 && nSector < kMaxSectors);
2019-09-19 22:42:45 +00:00
if (!FindSector(x, y, z, &nSector))
return false;
int floorZ = getflorzofslope(nSector, x, y);
int nXSector = sector[nSector].extra;
char Underwater = 0; char Water = 0; char Depth = 0; char Crusher = 0;
XSECTOR* pXSector = NULL;
if (nXSector > 0)
{
pXSector = &xsector[nXSector];
if (pXSector->Underwater)
Underwater = 1;
if (pXSector->Depth)
Depth = 1;
if (sector[nSector].type == kSectorDamage || pXSector->damageType > 0)
Crusher = 1;
2019-09-19 22:42:45 +00:00
}
int nUpper = gUpperLink[nSector];
int nLower = gLowerLink[nSector];
if (nUpper >= 0)
{
if (sprite[nUpper].type == kMarkerUpWater || sprite[nUpper].type == kMarkerUpGoo)
Water = Depth = 1;
}
if (nLower >= 0)
{
if (sprite[nLower].type == kMarkerLowWater || sprite[nLower].type == kMarkerLowGoo)
Depth = 1;
}
switch (pSprite->type) {
case kDudeGargoyleFlesh:
case kDudeGargoyleStone:
case kDudeBat:
2019-09-19 22:42:45 +00:00
if (pSprite->clipdist > nDist)
return 0;
if (Depth)
{
// Ouch...
if (Depth)
return false;
if (Crusher)
return false;
}
break;
case kDudeBoneEel:
2019-09-19 22:42:45 +00:00
if (Water)
return false;
if (!Underwater)
return false;
if (Underwater)
return true;
break;
case kDudeCerberusTwoHead:
case kDudeCerberusOneHead:
// by NoOne: a quick fix for Cerberus spinning in E3M7-like maps, where damage sectors is used.
// It makes ignore danger if enemy immune to N damageType. As result Cerberus start acting like
// in Blood 1.0 so it can move normally to player. It's up to you for adding rest of enemies here as
// i don't think it will broke something in game.
if (!cl_bloodvanillaenemies && !VanillaMode() && Crusher && isImmune(actor, pXSector->damageType, 16)) return true;
2021-09-15 22:01:50 +00:00
[[fallthrough]];
case kDudeZombieButcher:
case kDudeSpiderBrown:
case kDudeSpiderRed:
case kDudeSpiderBlack:
case kDudeSpiderMother:
case kDudeHellHound:
case kDudeRat:
case kDudeInnocent:
2019-09-19 22:42:45 +00:00
if (Crusher)
return false;
if (Depth || Underwater)
return false;
if (floorZ - bottom > 0x2000)
return false;
break;
#ifdef NOONE_EXTENSIONS
case kDudeModernCustom:
case kDudeModernCustomBurning:
2020-02-15 21:53:21 +00:00
if ((Crusher && !nnExtIsImmune(pSprite, pXSector->damageType)) || ((Water || Underwater) && !canSwim(pSprite))) return false;
2019-09-19 22:42:45 +00:00
return true;
2021-09-15 22:01:50 +00:00
[[fallthrough]];
#endif
case kDudeZombieAxeNormal:
case kDudePhantasm:
case kDudeGillBeast:
2019-09-19 22:42:45 +00:00
default:
if (Crusher)
return false;
if ((nXSector < 0 || (!xsector[nXSector].Underwater && !xsector[nXSector].Depth)) && floorZ - bottom > 0x2000)
return false;
break;
}
return 1;
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiChooseDirection(DBloodActor* actor, int a3)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
2019-09-19 22:42:45 +00:00
int vc = ((a3+1024-pSprite->ang)&2047)-1024;
int nCos = Cos(pSprite->ang);
int nSin = Sin(pSprite->ang);
int dx = actor->xvel();
int dy = actor->yvel();
int t1 = DMulScale(dx, nCos, dy, nSin, 30);
2019-09-19 22:42:45 +00:00
int vsi = ((t1*15)>>12) / 2;
int v8 = 341;
if (vc < 0)
v8 = -341;
if (CanMove(actor, actor->GetTarget(), pSprite->ang+vc, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang+vc;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang+vc/2, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang+vc/2;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang-vc/2, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang-vc/2;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang+v8, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang+v8;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang-v8, vsi))
2019-09-19 22:42:45 +00:00
pXSprite->goalAng = pSprite->ang-v8;
//else if (pSprite->flags&2)
//pXSprite->goalAng = pSprite->ang+341;
2019-09-19 22:42:45 +00:00
else // Weird..
pXSprite->goalAng = pSprite->ang+341;
if (Chance(0x8000))
pXSprite->dodgeDir = 1;
else
pXSprite->dodgeDir = -1;
if (!CanMove(actor, actor->GetTarget(), pSprite->ang+pXSprite->dodgeDir*512, 512))
2019-09-19 22:42:45 +00:00
{
pXSprite->dodgeDir = -pXSprite->dodgeDir;
if (!CanMove(actor, actor->GetTarget(), pSprite->ang+pXSprite->dodgeDir*512, 512))
2019-09-19 22:42:45 +00:00
pXSprite->dodgeDir = 0;
}
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiMoveForward(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
if (abs(nAng) > 341)
2019-09-19 22:42:45 +00:00
return;
actor->xvel() += MulScale(pDudeInfo->frontSpeed, Cos(pSprite->ang), 30);
actor->yvel() += MulScale(pDudeInfo->frontSpeed, Sin(pSprite->ang), 30);
2019-09-19 22:42:45 +00:00
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiMoveTurn(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiMoveDodge(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
if (pXSprite->dodgeDir)
{
int nCos = Cos(pSprite->ang);
int nSin = Sin(pSprite->ang);
int dx = actor->xvel();
int dy = actor->yvel();
int t1 = DMulScale(dx, nCos, dy, nSin, 30);
int t2 = DMulScale(dx, nSin, -dy, nCos, 30);
2019-09-19 22:42:45 +00:00
if (pXSprite->dodgeDir > 0)
t2 += pDudeInfo->sideSpeed;
else
t2 -= pDudeInfo->sideSpeed;
actor->xvel() = DMulScale(t1, nCos, t2, nSin, 30);
actor->yvel() = DMulScale(t1, nSin, -t2, nCos, 30);
2019-09-19 22:42:45 +00:00
}
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
// todo: split this up.
//
//---------------------------------------------------------------------------
void aiActivateDude(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
2021-09-15 22:01:50 +00:00
if (!pXSprite->state)
{
aiChooseDirection(actor, getangle(pXSprite->targetX-pSprite->x, pXSprite->targetY-pSprite->y));
2019-09-19 22:42:45 +00:00
pXSprite->state = 1;
}
2021-09-15 22:01:50 +00:00
switch (pSprite->type)
{
case kDudePhantasm:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &ghostSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1600, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &ghostChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeCultistTommy:
case kDudeCultistShotgun:
case kDudeCultistTesla:
case kDudeCultistTNT:
case kDudeCultistBeast:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
2021-09-15 22:01:50 +00:00
{
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &cultistSearch);
2021-09-15 22:01:50 +00:00
if (Chance(0x8000))
{
2021-09-15 22:12:28 +00:00
if (pSprite->type == kDudeCultistTommy) aiPlay3DSound(actor, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
else aiPlay3DSound(actor, 1008+Random(5), AI_SFX_PRIORITY_1, -1);
}
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimSearch);
break;
2019-09-19 22:42:45 +00:00
}
2021-09-15 22:01:50 +00:00
}
else
{
if (Chance(0x8000))
{
2021-09-15 22:12:28 +00:00
if (pSprite->type == kDudeCultistTommy) aiPlay3DSound(actor, 4003+Random(4), AI_SFX_PRIORITY_1, -1);
else aiPlay3DSound(actor, 1003+Random(4), AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
}
2021-09-15 22:01:50 +00:00
switch (pXSprite->medium)
{
case kMediumNormal:
if (pSprite->type == kDudeCultistTommy) aiNewState(actor, &fanaticChase);
else aiNewState(actor, &cultistChase);
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimChase);
break;
2019-09-19 22:42:45 +00:00
}
}
break;
}
#ifdef NOONE_EXTENSIONS
case kDudeModernCustom:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
{
if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeSearchW);
else aiGenDudeNewState(pSprite, &genDudeSearchL);
}
else
{
if (Chance(0x4000)) playGenDudeSound(pSprite, kGenDudeSndTargetSpot);
if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeChaseW);
else aiGenDudeNewState(pSprite, &genDudeChaseL);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeModernCustomBurning:
if (actor->GetTarget() == nullptr) aiGenDudeNewState(pSprite, &genDudeBurnSearch);
else aiGenDudeNewState(pSprite, &genDudeBurnChase);
2019-09-19 22:42:45 +00:00
break;
#endif
2021-09-15 22:01:50 +00:00
case kDudeCultistTommyProne:
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 1;
pSprite->type = kDudeCultistTommy;
if (actor->GetTarget() == nullptr)
2021-09-15 22:01:50 +00:00
{
switch (pXSprite->medium)
{
case 0:
aiNewState(actor, &cultistSearch);
2021-09-15 22:12:28 +00:00
if (Chance(0x8000)) aiPlay3DSound(actor, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimSearch);
break;
2019-09-19 22:42:45 +00:00
}
2021-09-15 22:01:50 +00:00
}
else
{
2019-09-19 22:42:45 +00:00
if (Chance(0x8000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
2021-09-15 22:01:50 +00:00
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &cultistProneChase);
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimChase);
break;
2019-09-19 22:42:45 +00:00
}
}
break;
}
case kDudeCultistShotgunProne:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 1;
pSprite->type = kDudeCultistShotgun;
if (actor->GetTarget() == nullptr)
2019-09-19 22:42:45 +00:00
{
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &cultistSearch);
2019-09-19 22:42:45 +00:00
if (Chance(0x8000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1008+Random(5), AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimSearch);
2019-09-19 22:42:45 +00:00
break;
}
}
else
{
if (Chance(0x8000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1003+Random(4), AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &cultistProneChase);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &cultistSwimChase);
2019-09-19 22:42:45 +00:00
break;
}
}
break;
}
case kDudeBurningCultist:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &cultistBurnSearch);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &cultistBurnChase);
2019-09-19 22:42:45 +00:00
break;
case kDudeBat:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (!pSprite->flags)
pSprite->flags = 9;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &batSearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0xa000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2000, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &batChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeBoneEel:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &eelSearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0x8000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1501, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1500, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &eelChase);
2019-09-19 22:42:45 +00:00
}
break;
}
2021-09-15 22:01:50 +00:00
case kDudeGillBeast:
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
2019-09-19 22:42:45 +00:00
XSECTOR *pXSector = NULL;
if (sector[pSprite->sectnum].extra > 0)
pXSector = &xsector[sector[pSprite->sectnum].extra];
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
2019-09-19 22:42:45 +00:00
{
if (pXSector && pXSector->Underwater)
aiNewState(actor, &gillBeastSwimSearch);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &gillBeastSearch);
2019-09-19 22:42:45 +00:00
}
else
{
if (Chance(0x4000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1701, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1700, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
if (pXSector && pXSector->Underwater)
aiNewState(actor, &gillBeastSwimChase);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &gillBeastChase);
2019-09-19 22:42:45 +00:00
}
break;
}
2021-09-15 22:01:50 +00:00
case kDudeZombieAxeNormal:
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &zombieASearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0xa000))
{
switch (Random(3))
{
default:
case 0:
case 3:
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1103, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
break;
case 1:
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1104, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
break;
case 2:
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1105, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
break;
}
}
aiNewState(actor, &zombieAChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeZombieAxeBuried:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
2021-09-15 22:12:28 +00:00
if (pXSprite->aiState == &zombieEIdle) aiNewState(actor, &zombieEUp);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeZombieAxeLaying:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
2021-09-15 22:12:28 +00:00
if (pXSprite->aiState == &zombieSIdle) aiNewState(actor, &zombie13AC2C);
2019-09-19 22:42:45 +00:00
break;
}
2021-09-15 22:12:28 +00:00
case kDudeZombieButcher:
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &zombieFSearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0x4000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1201, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1200, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &zombieFChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeBurningZombieAxe:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &zombieABurnSearch);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &zombieABurnChase);
2019-09-19 22:42:45 +00:00
break;
case kDudeBurningZombieButcher:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &zombieFBurnSearch);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &zombieFBurnChase);
2019-09-19 22:42:45 +00:00
break;
case kDudeGargoyleFlesh: {
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &gargoyleFSearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0x4000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1401, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1400, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &gargoyleFChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeGargoyleStone:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 1;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &gargoyleFSearch);
2019-09-19 22:42:45 +00:00
else
{
if (Chance(0x4000))
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1451, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1450, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &gargoyleFChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeGargoyleStatueFlesh:
case kDudeGargoyleStatueStone:
#ifdef NOONE_EXTENSIONS
// play gargoyle statue breaking animation if data1 = 1.
2021-09-15 22:01:50 +00:00
if (gModernMap && pXSprite->data1 == 1)
{
if (pSprite->type == kDudeGargoyleStatueFlesh) aiNewState(actor, &statueFBreakSEQ);
else aiNewState(actor, &statueSBreakSEQ);
2021-09-15 22:01:50 +00:00
}
else
{
2021-09-15 22:12:28 +00:00
if (Chance(0x4000)) aiPlay3DSound(actor, 1401, AI_SFX_PRIORITY_1, -1);
else aiPlay3DSound(actor, 1400, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
if (pSprite->type == kDudeGargoyleStatueFlesh) aiNewState(actor, &gargoyleFMorph);
else aiNewState(actor, &gargoyleSMorph);
2019-09-19 22:42:45 +00:00
}
#else
2021-09-15 22:12:28 +00:00
if (Chance(0x4000)) aiPlay3DSound(actor, 1401, AI_SFX_PRIORITY_1, -1);
else aiPlay3DSound(actor, 1400, AI_SFX_PRIORITY_1, -1);
if (pSprite->type == kDudeGargoyleStatueFlesh) aiNewState(actor, &gargoyleFMorph);
else aiNewState(actor, &gargoyleSMorph);
#endif
2019-09-19 22:42:45 +00:00
break;
case kDudeCerberusTwoHead:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &cerberusSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2300, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &cerberusChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeCerberusOneHead:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &cerberus2Search);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2300, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &cerberus2Chase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeHellHound:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &houndSearch);
2019-09-19 22:42:45 +00:00
else
{
aiPlay3DSound(actor, 1300, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &houndChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeHand:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &handSearch);
2019-09-19 22:42:45 +00:00
else
{
aiPlay3DSound(actor, 1900, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &handChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeRat:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &ratSearch);
2019-09-19 22:42:45 +00:00
else
{
aiPlay3DSound(actor, 2100, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &ratChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeInnocent:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &innocentSearch);
2019-09-19 22:42:45 +00:00
else
{
if (pXSprite->health > 0)
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 7000+Random(6), AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &innocentChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeTchernobog:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &tchernobogSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2350+Random(7), AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &tchernobogChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeSpiderBrown:
case kDudeSpiderRed:
case kDudeSpiderBlack:
pSprite->flags |= 2;
2019-09-19 22:42:45 +00:00
pSprite->cstat &= ~8;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &spidSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1800, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &spidChase);
2019-09-19 22:42:45 +00:00
}
break;
2021-09-15 22:01:50 +00:00
case kDudeSpiderMother:
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 1;
pSprite->flags |= 2;
2019-09-19 22:42:45 +00:00
pSprite->cstat &= ~8;
if (actor->GetTarget() == nullptr)
aiNewState(actor, &spidSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 1853+Random(1), AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &spidChase);
2019-09-19 22:42:45 +00:00
}
break;
}
case kDudeTinyCaleb:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
if (actor->GetTarget() == nullptr)
2019-09-19 22:42:45 +00:00
{
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &tinycalebSearch);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &tinycalebSwimSearch);
2019-09-19 22:42:45 +00:00
break;
}
}
else
{
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &tinycalebChase);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &tinycalebSwimChase);
2019-09-19 22:42:45 +00:00
break;
}
}
break;
}
case kDudeBeast:
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->thinkTime = 1;
if (actor->GetTarget() == nullptr)
2019-09-19 22:42:45 +00:00
{
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &beastSearch);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &beastSwimSearch);
2019-09-19 22:42:45 +00:00
break;
}
}
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 9009+Random(2), AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
switch (pXSprite->medium)
{
case kMediumNormal:
aiNewState(actor, &beastChase);
2019-09-19 22:42:45 +00:00
break;
case kMediumWater:
case kMediumGoo:
aiNewState(actor, &beastSwimChase);
2019-09-19 22:42:45 +00:00
break;
}
}
break;
}
case kDudePodGreen:
case kDudePodFire:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &podSearch);
2019-09-19 22:42:45 +00:00
else
{
if (pSprite->type == kDudePodFire)
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2453, AI_SFX_PRIORITY_1, -1);
2019-09-19 22:42:45 +00:00
else
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2473, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &podChase);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeTentacleGreen:
case kDudeTentacleFire:
if (actor->GetTarget() == nullptr)
aiNewState(actor, &tentacleSearch);
2019-09-19 22:42:45 +00:00
else
{
2021-09-15 22:12:28 +00:00
aiPlay3DSound(actor, 2503, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &tentacleChase);
2019-09-19 22:42:45 +00:00
}
break;
}
}
2021-09-15 22:01:50 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-09-16 17:32:46 +00:00
void aiSetTarget(DBloodActor*actor, int x, int y, int z)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:32:46 +00:00
auto pXSprite = &actor->x();
actor->SetTarget(nullptr);
2019-09-19 22:42:45 +00:00
pXSprite->targetX = x;
pXSprite->targetY = y;
pXSprite->targetZ = z;
}
2021-09-16 17:32:46 +00:00
void aiSetTarget(DBloodActor* actor, DBloodActor* target)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:32:46 +00:00
if (target == nullptr)
{
actor->SetTarget(nullptr);
return;
}
auto pXSprite = &actor->x();
spritetype* pTarget = &target->s();
2019-09-19 22:42:45 +00:00
if (pTarget->type >= kDudeBase && pTarget->type < kDudeMax)
{
2021-09-16 17:32:46 +00:00
if (actor->GetOwner() != target)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:32:46 +00:00
actor->SetTarget(target);
DUDEINFO* pDudeInfo = getDudeInfo(pTarget->type);
2019-09-19 22:42:45 +00:00
pXSprite->targetX = pTarget->x;
pXSprite->targetY = pTarget->y;
2021-09-16 17:32:46 +00:00
pXSprite->targetZ = pTarget->z - ((pDudeInfo->eyeHeight * pTarget->yrepeat) << 2);
2019-09-19 22:42:45 +00:00
}
}
}
//---------------------------------------------------------------------------
//
// todo: split up and put most of its content in tables.
//
//---------------------------------------------------------------------------
2020-12-02 23:30:19 +00:00
int aiDamageSprite(DBloodActor* source, DBloodActor* actor, DAMAGE_TYPE nDmgType, int nDamage)
2019-09-19 22:42:45 +00:00
{
2020-12-02 23:30:19 +00:00
auto pSprite = &actor->s();
XSPRITE* pXSprite = &actor->x();
2019-09-19 22:42:45 +00:00
if (!pXSprite->health)
return 0;
pXSprite->health = ClipLow(pXSprite->health - nDamage, 0);
actor->cumulDamage += nDamage;
DUDEINFO *pDudeInfo = getDudeInfo(pSprite->type);
2021-09-16 17:42:54 +00:00
2020-12-02 23:30:19 +00:00
if (source)
2019-09-19 22:42:45 +00:00
{
2020-12-02 23:30:19 +00:00
spritetype *pSource = &source->s();
int nSource = pSource->index;
2020-12-06 20:56:09 +00:00
if (pSprite == pSource) return 0;
else if (actor->GetTarget() == nullptr) // if no target, give the dude a target
2019-09-19 22:42:45 +00:00
{
aiSetTarget(actor, source);
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
}
2021-09-16 17:42:54 +00:00
else if (source != actor->GetTarget()) // if found a new target, retarget
{
int nThresh = nDamage;
if (pSprite->type == pSource->type)
nThresh *= pDudeInfo->changeTargetKin;
else
nThresh *= pDudeInfo->changeTarget;
if (Chance(nThresh))
{
2021-09-16 17:32:46 +00:00
aiSetTarget(actor, source);
2021-09-16 17:42:54 +00:00
aiActivateDude(actor);
}
}
2020-12-06 20:56:09 +00:00
#ifdef NOONE_EXTENSIONS
if (gModernMap) {
// for enemies in patrol mode
if (aiInPatrolState(pXSprite->aiState))
{
2020-12-06 20:56:09 +00:00
aiPatrolStop(pSprite, pSource->index, pXSprite->dudeAmbush);
PLAYER* pPlayer = getPlayerById(pSource->type);
if (!pPlayer) return nDamage;
if (powerupCheck(pPlayer, kPwUpShadowCloak)) pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
if (readyForCrit(pSource, pSprite))
{
2021-06-02 19:00:39 +00:00
nDamage += aiDamageSprite(actor, source, nDmgType, nDamage * (10 - gGameOptions.nDifficulty));
if (pXSprite->health > 0)
{
2020-12-06 20:56:09 +00:00
int fullHp = (pXSprite->sysData2 > 0) ? ClipRange(pXSprite->sysData2 << 4, 1, 65535) : getDudeInfo(pSprite->type)->startHealth << 4;
if (((100 * pXSprite->health) / fullHp) <= 75)
{
actor->cumulDamage += nDamage << 4; // to be sure any enemy will play the recoil animation
RecoilDude(&bloodActors[pXSprite->reference]);
2020-12-06 20:56:09 +00:00
}
}
DPrintf(DMSG_SPAMMY, "Player #%d does the critical damage to patrol dude #%d!", pPlayer->nPlayer + 1, pSprite->index);
2020-12-06 20:56:09 +00:00
}
return nDamage;
}
if (pSprite->type == kDudeModernCustomBurning)
{
if (Chance(0x2000) && actor->dudeExtra.time < PlayClock) {
2020-12-06 20:56:09 +00:00
playGenDudeSound(pSprite, kGenDudeSndBurning);
actor->dudeExtra.time = PlayClock + 360;
2020-12-06 20:56:09 +00:00
}
if (pXSprite->burnTime == 0) pXSprite->burnTime = 2400;
if (spriteIsUnderwater(pSprite, false))
{
2020-12-06 20:56:09 +00:00
pSprite->type = kDudeModernCustom;
pXSprite->burnTime = 0;
pXSprite->health = 1; // so it can be killed with flame weapons while underwater and if already was burning dude before.
aiGenDudeNewState(pSprite, &genDudeGotoW);
}
return nDamage;
}
if (pSprite->type == kDudeModernCustom)
{
2020-12-06 20:56:09 +00:00
GENDUDEEXTRA* pExtra = genDudeExtra(pSprite);
if (nDmgType == kDamageBurn)
{
2021-06-02 19:00:39 +00:00
if (pXSprite->health > (uint32_t)pDudeInfo->fleeHealth) return nDamage;
else if (pXSprite->txID <= 0 || getNextIncarnation(pXSprite) == nullptr)
{
2020-12-06 20:56:09 +00:00
removeDudeStuff(pSprite);
if (pExtra->weaponType == kGenDudeWeaponKamikaze)
doExplosion(pSprite, pXSprite->data1 - kTrapExploder);
if (spriteIsUnderwater(pSprite))
{
2020-12-06 20:56:09 +00:00
pXSprite->health = 0;
return nDamage;
}
if (pXSprite->burnTime <= 0)
pXSprite->burnTime = 1200;
if (pExtra->canBurn && pExtra->availDeaths[kDamageBurn] > 0) {
2020-12-06 20:56:09 +00:00
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
2020-12-06 20:56:09 +00:00
playGenDudeSound(pSprite, kGenDudeSndBurning);
pSprite->type = kDudeModernCustomBurning;
if (pXSprite->data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation
pSprite->pal = 0;
aiGenDudeNewState(pSprite, &genDudeBurnGoto);
actHealDude(actor, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
actor->dudeExtra.time = PlayClock + 360;
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2020-12-06 20:56:09 +00:00
}
}
else
{
actKillDude(actor, actor, kDamageFall, 65535);
2020-12-06 20:56:09 +00:00
}
}
else if (canWalk(pSprite) && !inDodge(pXSprite->aiState) && !inRecoil(pXSprite->aiState))
{
if (!dudeIsMelee(pXSprite))
{
if (inIdle(pXSprite->aiState) || Chance(getDodgeChance(pSprite)))
{
if (!spriteIsUnderwater(pSprite))
{
if (!canDuck(pSprite) || !dudeIsPlayingSeq(actor, 14)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL);
2020-12-06 20:56:09 +00:00
else aiGenDudeNewState(pSprite, &genDudeDodgeShortD);
if (Chance(0x0200))
playGenDudeSound(pSprite, kGenDudeSndGotHit);
2021-09-16 17:42:54 +00:00
}
else if (dudeIsPlayingSeq(actor, 13))
{
2020-12-06 20:56:09 +00:00
aiGenDudeNewState(pSprite, &genDudeDodgeShortW);
}
}
}
else if (Chance(0x0200))
{
2020-12-06 20:56:09 +00:00
playGenDudeSound(pSprite, kGenDudeSndGotHit);
}
}
return nDamage;
}
}
#endif
if (nDmgType == kDamageTesla)
2019-09-19 22:42:45 +00:00
{
DUDEEXTRA *pDudeExtra = &actor->dudeExtra;
pDudeExtra->teslaHit = 1;
}
else if (!VanillaMode()) // reset tesla hit state if received different type of damage
{
DUDEEXTRA *pDudeExtra = &actor->dudeExtra;
pDudeExtra->teslaHit = 0;
2019-09-19 22:42:45 +00:00
}
const bool fixRandomCultist = !cl_bloodvanillaenemies && (pSprite->inittype >= kDudeBase) && (pSprite->inittype < kDudeMax) && !VanillaMode(); // fix burning cultists randomly switching types underwater
2019-09-19 22:42:45 +00:00
switch (pSprite->type)
{
case kDudeCultistTommy:
case kDudeCultistShotgun:
case kDudeCultistTesla:
case kDudeCultistTNT:
if (nDmgType != kDamageBurn)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:42:54 +00:00
if (!dudeIsPlayingSeq(actor, 14) && !pXSprite->medium)
aiNewState(actor, &cultistDodge);
2021-09-16 17:42:54 +00:00
else if (dudeIsPlayingSeq(actor, 14) && !pXSprite->medium)
aiNewState(actor, &cultistProneDodge);
2021-09-16 17:42:54 +00:00
else if (dudeIsPlayingSeq(actor, 13) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
aiNewState(actor, &cultistSwimDodge);
2019-09-19 22:42:45 +00:00
}
else if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
2019-09-19 22:42:45 +00:00
{
pSprite->type = kDudeBurningCultist;
aiNewState(actor, &cultistBurnGoto);
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
aiPlay3DSound(actor, 1031+Random(2), AI_SFX_PRIORITY_2, -1);
actor->dudeExtra.time = PlayClock+360;
actHealDude(actor, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2019-09-19 22:42:45 +00:00
}
break;
2019-12-21 02:15:17 +00:00
case kDudeInnocent:
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
2019-09-19 22:42:45 +00:00
{
pSprite->type = kDudeBurningInnocent;
aiNewState(actor, &cultistBurnGoto);
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
actor->dudeExtra.time = PlayClock+360;
actHealDude(actor, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeBurningCultist:
if (Chance(0x4000) && actor->dudeExtra.time < PlayClock)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 1031+Random(2), AI_SFX_PRIORITY_2, -1);
actor->dudeExtra.time = PlayClock+360;
2019-09-19 22:42:45 +00:00
}
if (Chance(0x600) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
2019-09-19 22:42:45 +00:00
{
pSprite->type = kDudeCultistTommy;
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
pSprite->type = pSprite->inittype; // restore back to spawned cultist type
2019-09-19 22:42:45 +00:00
pXSprite->burnTime = 0;
aiNewState(actor, &cultistSwimGoto);
2019-09-19 22:42:45 +00:00
}
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
2019-09-19 22:42:45 +00:00
{
pSprite->type = kDudeCultistShotgun;
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
pSprite->type = pSprite->inittype; // restore back to spawned cultist type
2019-09-19 22:42:45 +00:00
pXSprite->burnTime = 0;
aiNewState(actor, &cultistSwimGoto);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeGargoyleFlesh:
aiNewState(actor, &gargoyleFChase);
2019-09-19 22:42:45 +00:00
break;
case kDudeZombieButcher:
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth) {
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
aiPlay3DSound(actor, 1202, AI_SFX_PRIORITY_2, -1);
pSprite->type = kDudeBurningZombieButcher;
aiNewState(actor, &zombieFBurnGoto);
actHealDude(actor, dudeInfo[42].startHealth, dudeInfo[42].startHealth);
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2019-09-19 22:42:45 +00:00
}
break;
2019-12-21 02:15:17 +00:00
case kDudeTinyCaleb:
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
2019-09-19 22:42:45 +00:00
{
if (!cl_bloodvanillaenemies && !VanillaMode()) // fix burning sprite for tiny caleb
{
pSprite->type = kDudeBurningTinyCaleb;
aiNewState(actor, &tinycalebBurnGoto);
}
else
{
pSprite->type = kDudeBurningInnocent;
aiNewState(actor, &cultistBurnGoto);
}
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
actor->dudeExtra.time = PlayClock+360;
actHealDude(actor, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeCultistBeast:
2019-09-19 22:42:45 +00:00
if (pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth)
{
pSprite->type = kDudeBeast;
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 9008, AI_SFX_PRIORITY_1, -1);
aiNewState(actor, &beastMorphFromCultist);
actHealDude(actor, dudeInfo[51].startHealth, dudeInfo[51].startHealth);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeZombieAxeNormal:
case kDudeZombieAxeBuried:
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth)
2019-09-19 22:42:45 +00:00
{
2021-09-16 17:42:54 +00:00
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
aiPlay3DSound(actor, 1106, AI_SFX_PRIORITY_2, -1);
pSprite->type = kDudeBurningZombieAxe;
aiNewState(actor, &zombieABurnGoto);
actHealDude(actor, dudeInfo[41].startHealth, dudeInfo[41].startHealth);
2021-09-16 17:42:54 +00:00
evKill(actor, kCallbackFXFlameLick);
2019-09-19 22:42:45 +00:00
}
break;
}
}
return nDamage;
}
//---------------------------------------------------------------------------
//
// todo: split up and put most of its content in tables.
//
//---------------------------------------------------------------------------
void RecoilDude(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
2019-09-19 22:42:45 +00:00
char v4 = Chance(0x8000);
DUDEEXTRA* pDudeExtra = &actor->dudeExtra;
if (pSprite->statnum == kStatDude && (pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
{
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
switch (pSprite->type)
{
#ifdef NOONE_EXTENSIONS
case kDudeModernCustom:
{
GENDUDEEXTRA* pExtra = genDudeExtra(pSprite); int rChance = getRecoilChance(pSprite);
if (pExtra->canElectrocute && pDudeExtra->teslaHit && !spriteIsUnderwater(pSprite, false))
{
2019-09-19 22:42:45 +00:00
if (Chance(rChance << 3) || (dudeIsMelee(pXSprite) && Chance(rChance << 4))) aiGenDudeNewState(pSprite, &genDudeRecoilTesla);
else if (pExtra->canRecoil && Chance(rChance)) aiGenDudeNewState(pSprite, &genDudeRecoilL);
else if (canWalk(pSprite))
{
if (Chance(rChance >> 2)) aiGenDudeNewState(pSprite, &genDudeDodgeL);
else if (Chance(rChance >> 1)) aiGenDudeNewState(pSprite, &genDudeDodgeShortL);
}
2019-09-19 22:42:45 +00:00
}
else if (pExtra->canRecoil && Chance(rChance))
{
if (inDuck(pXSprite->aiState) && Chance(rChance >> 2)) aiGenDudeNewState(pSprite, &genDudeRecoilD);
else if (spriteIsUnderwater(pSprite, false)) aiGenDudeNewState(pSprite, &genDudeRecoilW);
else aiGenDudeNewState(pSprite, &genDudeRecoilL);
}
short rState = inRecoil(pXSprite->aiState);
if (rState > 0)
{
if (!canWalk(pSprite))
{
if (rState == 1) pXSprite->aiState->nextState = &genDudeChaseNoWalkL;
else if (rState == 2) pXSprite->aiState->nextState = &genDudeChaseNoWalkD;
else pXSprite->aiState->nextState = &genDudeChaseNoWalkW;
2019-09-19 22:42:45 +00:00
}
else if (!dudeIsMelee(pXSprite) || Chance(rChance >> 2))
{
if (rState == 1) pXSprite->aiState->nextState = (Chance(rChance) ? &genDudeDodgeL : &genDudeDodgeShortL);
else if (rState == 2) pXSprite->aiState->nextState = (Chance(rChance) ? &genDudeDodgeD : &genDudeDodgeShortD);
else if (rState == 3) pXSprite->aiState->nextState = (Chance(rChance) ? &genDudeDodgeW : &genDudeDodgeShortW);
}
else if (rState == 1) pXSprite->aiState->nextState = &genDudeChaseL;
else if (rState == 2) pXSprite->aiState->nextState = &genDudeChaseD;
else pXSprite->aiState->nextState = &genDudeChaseW;
2019-09-19 22:42:45 +00:00
playGenDudeSound(pSprite, kGenDudeSndGotHit);
2019-09-19 22:42:45 +00:00
}
pDudeExtra->teslaHit = 0;
2019-09-19 22:42:45 +00:00
break;
}
#endif
case kDudeCultistTommy:
case kDudeCultistShotgun:
case kDudeCultistTesla:
case kDudeCultistTNT:
case kDudeCultistBeast:
if (pSprite->type == kDudeCultistTommy) aiPlay3DSound(actor, 4013 + Random(2), AI_SFX_PRIORITY_2, -1);
else aiPlay3DSound(actor, 1013 + Random(2), AI_SFX_PRIORITY_2, -1);
if (!v4 && pXSprite->medium == kMediumNormal)
{
if (pDudeExtra->teslaHit) aiNewState(actor, &cultistTeslaRecoil);
else aiNewState(actor, &cultistRecoil);
}
else if (v4 && pXSprite->medium == kMediumNormal)
{
if (pDudeExtra->teslaHit) aiNewState(actor, &cultistTeslaRecoil);
else if (gGameOptions.nDifficulty > 0) aiNewState(actor, &cultistProneRecoil);
else aiNewState(actor, &cultistRecoil);
2019-09-19 22:42:45 +00:00
}
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
aiNewState(actor, &cultistSwimRecoil);
2019-09-19 22:42:45 +00:00
else
{
if (pDudeExtra->teslaHit)
aiNewState(actor, &cultistTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &cultistRecoil);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeBurningCultist:
aiNewState(actor, &cultistBurnGoto);
2019-09-19 22:42:45 +00:00
break;
#ifdef NOONE_EXTENSIONS
case kDudeModernCustomBurning:
aiGenDudeNewState(pSprite, &genDudeBurnGoto);
2019-09-19 22:42:45 +00:00
break;
#endif
case kDudeZombieButcher:
aiPlay3DSound(actor, 1202, AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit)
aiNewState(actor, &zombieFTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &zombieFRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeZombieAxeNormal:
case kDudeZombieAxeBuried:
aiPlay3DSound(actor, 1106, AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit && pXSprite->data3 > pDudeInfo->startHealth/3)
aiNewState(actor, &zombieATeslaRecoil);
else if (pXSprite->data3 > pDudeInfo->startHealth / 3)
aiNewState(actor, &zombieARecoil2);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &zombieARecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeBurningZombieAxe:
aiPlay3DSound(actor, 1106, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &zombieABurnGoto);
2019-09-19 22:42:45 +00:00
break;
case kDudeBurningZombieButcher:
aiPlay3DSound(actor, 1202, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &zombieFBurnGoto);
2019-09-19 22:42:45 +00:00
break;
case kDudeGargoyleFlesh:
case kDudeGargoyleStone:
aiPlay3DSound(actor, 1402, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &gargoyleFRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeCerberusTwoHead:
aiPlay3DSound(actor, 2302+Random(2), AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit && pXSprite->data3 > pDudeInfo->startHealth/3)
aiNewState(actor, &cerberusTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &cerberusRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeCerberusOneHead:
aiPlay3DSound(actor, 2302 + Random(2), AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &cerberus2Recoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeHellHound:
aiPlay3DSound(actor, 1302, AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit)
aiNewState(actor, &houndTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &houndRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeTchernobog:
aiPlay3DSound(actor, 2370 + Random(2), AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &tchernobogRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeHand:
aiPlay3DSound(actor, 1902, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &handRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeRat:
aiPlay3DSound(actor, 2102, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &ratRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeBat:
aiPlay3DSound(actor, 2002, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &batRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeBoneEel:
aiPlay3DSound(actor, 1502, AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &eelRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeGillBeast: {
XSECTOR* pXSector = NULL;
2019-09-19 22:42:45 +00:00
if (sector[pSprite->sectnum].extra > 0)
pXSector = &xsector[sector[pSprite->sectnum].extra];
aiPlay3DSound(actor, 1702, AI_SFX_PRIORITY_2, -1);
2019-09-19 22:42:45 +00:00
if (pXSector && pXSector->Underwater)
aiNewState(actor, &gillBeastSwimRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &gillBeastRecoil);
2019-09-19 22:42:45 +00:00
break;
}
case kDudePhantasm:
aiPlay3DSound(actor, 1602, AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit)
aiNewState(actor, &ghostTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &ghostRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeSpiderBrown:
case kDudeSpiderRed:
case kDudeSpiderBlack:
aiPlay3DSound(actor, 1802 + Random(1), AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &spidDodge);
2019-09-19 22:42:45 +00:00
break;
case kDudeSpiderMother:
aiPlay3DSound(actor, 1851 + Random(1), AI_SFX_PRIORITY_2, -1);
aiNewState(actor, &spidDodge);
2019-09-19 22:42:45 +00:00
break;
case kDudeInnocent:
aiPlay3DSound(actor, 7007 + Random(2), AI_SFX_PRIORITY_2, -1);
if (pDudeExtra->teslaHit)
aiNewState(actor, &innocentTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &innocentRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeTinyCaleb:
if (pXSprite->medium == kMediumNormal)
2019-09-19 22:42:45 +00:00
{
if (pDudeExtra->teslaHit)
aiNewState(actor, &tinycalebTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &tinycalebRecoil);
2019-09-19 22:42:45 +00:00
}
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
aiNewState(actor, &tinycalebSwimRecoil);
2019-09-19 22:42:45 +00:00
else
{
if (pDudeExtra->teslaHit)
aiNewState(actor, &tinycalebTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &tinycalebRecoil);
2019-09-19 22:42:45 +00:00
}
break;
case kDudeBeast:
aiPlay3DSound(actor, 9004 + Random(2), AI_SFX_PRIORITY_2, -1);
if (pXSprite->medium == kMediumNormal)
2019-09-19 22:42:45 +00:00
{
if (pDudeExtra->teslaHit)
aiNewState(actor, &beastTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &beastRecoil);
2019-09-19 22:42:45 +00:00
}
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
aiNewState(actor, &beastSwimRecoil);
2019-09-19 22:42:45 +00:00
else
{
if (pDudeExtra->teslaHit)
aiNewState(actor, &beastTeslaRecoil);
2019-09-19 22:42:45 +00:00
else
aiNewState(actor, &beastRecoil);
2019-09-19 22:42:45 +00:00
}
break;
case kDudePodGreen:
case kDudePodFire:
aiNewState(actor, &podRecoil);
2019-09-19 22:42:45 +00:00
break;
case kDudeTentacleGreen:
case kDudeTentacleFire:
aiNewState(actor, &tentacleRecoil);
2019-09-19 22:42:45 +00:00
break;
default:
aiNewState(actor, &genRecoil);
2019-09-19 22:42:45 +00:00
break;
}
pDudeExtra->teslaHit = 0;
2019-09-19 22:42:45 +00:00
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiThinkTarget(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
if (Chance(pDudeInfo->alertChance))
{
for (int p = connecthead; p >= 0; p = connectpoint2[p])
{
PLAYER* pPlayer = &gPlayer[p];
if (pSprite->owner == pPlayer->nSprite || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
2019-09-19 22:42:45 +00:00
continue;
int x = pPlayer->pSprite->x;
int y = pPlayer->pSprite->y;
int z = pPlayer->pSprite->z;
int nSector = pPlayer->pSprite->sectnum;
int dx = x - pSprite->x;
int dy = y - pSprite->y;
2019-09-19 22:42:45 +00:00
int nDist = approxDist(dx, dy);
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
continue;
if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - ((pDudeInfo->eyeHeight * pSprite->yrepeat) << 2), pSprite->sectnum))
2019-09-19 22:42:45 +00:00
continue;
int nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
if (nDist < pDudeInfo->seeDist && abs(nDeltaAngle) <= pDudeInfo->periphery)
2019-09-19 22:42:45 +00:00
{
aiSetTarget(actor, pPlayer->actor());
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
return;
}
else if (nDist < pDudeInfo->hearDist)
{
aiSetTarget(actor, x, y, z);
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
return;
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiLookForTarget(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pSprite = &actor->s();
auto pXSprite = &actor->x();
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
if (Chance(pDudeInfo->alertChance))
{
for (int p = connecthead; p >= 0; p = connectpoint2[p])
{
PLAYER* pPlayer = &gPlayer[p];
if (pSprite->owner == pPlayer->nSprite || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
2019-09-19 22:42:45 +00:00
continue;
int x = pPlayer->pSprite->x;
int y = pPlayer->pSprite->y;
int z = pPlayer->pSprite->z;
int nSector = pPlayer->pSprite->sectnum;
int dx = x - pSprite->x;
int dy = y - pSprite->y;
2019-09-19 22:42:45 +00:00
int nDist = approxDist(dx, dy);
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
continue;
if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - ((pDudeInfo->eyeHeight * pSprite->yrepeat) << 2), pSprite->sectnum))
2019-09-19 22:42:45 +00:00
continue;
int nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
if (nDist < pDudeInfo->seeDist && abs(nDeltaAngle) <= pDudeInfo->periphery)
2019-09-19 22:42:45 +00:00
{
aiSetTarget(actor, pPlayer->actor());
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
return;
}
else if (nDist < pDudeInfo->hearDist)
{
aiSetTarget(actor, x, y, z);
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
return;
}
}
if (pXSprite->state)
{
uint8_t sectmap[(kMaxSectors+7)>>3];
const bool newSectCheckMethod = !cl_bloodvanillaenemies && !VanillaMode(); // use new sector checking logic
GetClosestSpriteSectors(pSprite->sectnum, pSprite->x, pSprite->y, 400, sectmap, nullptr, newSectCheckMethod);
2020-10-15 15:15:45 +00:00
BloodStatIterator it(kStatDude);
while (DBloodActor* actor2 = it.Next())
2019-09-19 22:42:45 +00:00
{
spritetype* pSprite2 = &actor2->s();
int dx = pSprite2->x - pSprite->x;
int dy = pSprite2->y - pSprite->y;
2019-09-19 22:42:45 +00:00
int nDist = approxDist(dx, dy);
if (pSprite2->type == kDudeInnocent)
2019-09-19 22:42:45 +00:00
{
DUDEINFO* pDudeInfo = getDudeInfo(pSprite2->type);
2019-09-19 22:42:45 +00:00
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
continue;
aiSetTarget(actor, actor2);
aiActivateDude(actor);
2019-09-19 22:42:45 +00:00
return;
}
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiProcessDudes(void)
{
BloodStatIterator it(kStatDude);
while (auto actor = it.Next())
2020-10-15 15:15:45 +00:00
{
auto pSprite = &actor->s();
if (pSprite->flags & 32) continue;
auto pXSprite = &actor->x();
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
if (IsPlayerSprite(pSprite) || pXSprite->health == 0) continue;
pXSprite->stateTimer = ClipLow(pXSprite->stateTimer - 4, 0);
if (pXSprite->aiState && pXSprite->aiState->moveFunc)
pXSprite->aiState->moveFunc(actor);
if (pXSprite->aiState->thinkFunc && (gFrameCount & 3) == (pSprite->index & 3)) // ouch, ouch! :(
pXSprite->aiState->thinkFunc(actor);
switch (pSprite->type) {
#ifdef NOONE_EXTENSIONS
case kDudeModernCustom:
case kDudeModernCustomBurning: {
GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index];
if (pExtra->slaveCount > 0) updateTargetOfSlaves(pSprite);
if (pExtra->nLifeLeech >= 0) updateTargetOfLeech(pSprite);
if (pXSprite->stateTimer == 0 && pXSprite->aiState && 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 > actor->cumulDamage) break;
pXSprite->data3 = actor->cumulDamage;
RecoilDude(actor);
break;
}
#endif
default:
if (pXSprite->stateTimer == 0 && pXSprite->aiState && pXSprite->aiState->nextState) {
if (pXSprite->aiState->stateTicks > 0)
aiNewState(actor, pXSprite->aiState->nextState);
else if (seqGetStatus(actor) < 0)
aiNewState(actor, pXSprite->aiState->nextState);
}
if (pXSprite->health > 0 && ((pDudeInfo->hinderDamage << 4) <= actor->cumulDamage))
{
pXSprite->data3 = actor->cumulDamage;
RecoilDude(actor);
}
break;
}
2019-09-19 22:42:45 +00:00
}
it.Reset(kStatDude);
while (auto actor = it.Next())
{
actor->cumulDamage = 0;
}
2019-09-19 22:42:45 +00:00
}
void aiInit(void)
{
BloodStatIterator it(kStatDude);
while (auto actor = it.Next())
2019-09-19 22:42:45 +00:00
{
aiInitSprite(actor);
2019-09-19 22:42:45 +00:00
}
}
void aiInitSprite(DBloodActor* actor)
2019-09-19 22:42:45 +00:00
{
auto pSprite = &actor->s();
auto pXSprite = &actor->x();
2019-09-19 22:42:45 +00:00
int nSector = pSprite->sectnum;
int nXSector = sector[nSector].extra;
XSECTOR* pXSector = NULL;
2019-09-19 22:42:45 +00:00
if (nXSector > 0)
pXSector = &xsector[nXSector];
DUDEEXTRA* pDudeExtra = &actor->dudeExtra;
DUDEEXTRA_STATS *pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtra->teslaHit = 0;
2020-11-21 22:40:33 +00:00
pDudeExtra->time = 0;
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 0;
2020-12-06 20:56:09 +00:00
#ifdef NOONE_EXTENSIONS
2020-12-06 20:56:09 +00:00
int stateTimer = -1, targetMarker = -1;
int targetX = 0, targetY = 0, targetZ = 0;
2020-12-06 20:56:09 +00:00
// dude patrol init
if (gModernMap)
{
2020-12-06 20:56:09 +00:00
// must keep it in case of loading save
if (pXSprite->dudeFlag4 && spriRangeIsFine(pXSprite->target_i) && sprite[pXSprite->target_i].type == kMarkerPath) {
stateTimer = pXSprite->stateTimer; targetMarker = pXSprite->target_i;
2020-12-06 20:56:09 +00:00
targetX = pXSprite->targetX; targetY = pXSprite->targetY;
targetZ = pXSprite->targetZ;
}
}
#endif
2020-12-06 20:56:09 +00:00
switch (pSprite->type)
{
case kDudeCultistTommy:
case kDudeCultistShotgun:
case kDudeCultistTesla:
case kDudeCultistTNT:
case kDudeCultistBeast:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
aiNewState(actor, &cultistIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeCultistTommyProne:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
aiNewState(actor, &fanaticProneIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeCultistShotgunProne:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
aiNewState(actor, &cultistProneIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeZombieButcher: {
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &zombieFIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeZombieAxeNormal: {
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &zombieAIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeZombieAxeLaying:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &zombieSIdle);
pSprite->flags &= ~1;
2019-09-19 22:42:45 +00:00
break;
}
case kDudeZombieAxeBuried: {
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &zombieEIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeGargoyleFlesh:
case kDudeGargoyleStone: {
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 0;
aiNewState(actor, &gargoyleFIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeGargoyleStatueFlesh:
case kDudeGargoyleStatueStone:
aiNewState(actor, &gargoyleStatueIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeCerberusTwoHead: {
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &cerberusIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeCerberusOneHead: {
if (!VanillaMode()) {
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &cerberus2Idle);
break;
}
aiNewState(actor, &genIdle);
break;
}
case kDudeHellHound:
aiNewState(actor, &houndIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeHand:
aiNewState(actor, &handIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudePhantasm:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 0;
aiNewState(actor, &ghostIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeInnocent:
aiNewState(actor, &innocentIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeRat:
aiNewState(actor, &ratIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeBoneEel:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 0;
aiNewState(actor, &eelIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeGillBeast:
aiNewState(actor, &gillBeastIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeBat:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->thinkTime = 0;
pDudeExtraE->active = 0;
aiNewState(actor, &batIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeSpiderBrown:
case kDudeSpiderRed:
case kDudeSpiderBlack:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &spidIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeSpiderMother:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
pDudeExtraE->birthCounter = 0;
aiNewState(actor, &spidIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeTchernobog:
2019-09-19 22:42:45 +00:00
{
pDudeExtraE->active = 0;
pDudeExtraE->thinkTime = 0;
aiNewState(actor, &tchernobogIdle);
2019-09-19 22:42:45 +00:00
break;
}
case kDudeTinyCaleb:
aiNewState(actor, &tinycalebIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeBeast:
aiNewState(actor, &beastIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudePodGreen:
case kDudePodFire:
aiNewState(actor, &podIdle);
2019-09-19 22:42:45 +00:00
break;
case kDudeTentacleGreen:
case kDudeTentacleFire:
aiNewState(actor, &tentacleIdle);
2019-09-19 22:42:45 +00:00
break;
#ifdef NOONE_EXTENSIONS
case kDudeModernCustom:
case kDudeModernCustomBurning:
if (!gModernMap) break;
aiGenDudeInitSprite(pSprite, pXSprite);
genDudePrepare(pSprite, kGenDudePropertyAll);
break;
#endif
2019-09-19 22:42:45 +00:00
default:
aiNewState(actor, &genIdle);
2019-09-19 22:42:45 +00:00
break;
}
aiSetTarget(actor, 0, 0, 0);
2019-09-19 22:42:45 +00:00
pXSprite->stateTimer = 0;
switch (pSprite->type)
{
case kDudeSpiderBrown:
case kDudeSpiderRed:
case kDudeSpiderBlack:
if (pSprite->cstat & 8) pSprite->flags |= 9;
else pSprite->flags = 15;
break;
case kDudeGargoyleFlesh:
case kDudeGargoyleStone:
case kDudePhantasm:
case kDudeBoneEel:
case kDudeBat:
pSprite->flags |= 9;
2019-09-19 22:42:45 +00:00
break;
case kDudeGillBeast:
if (pXSector && pXSector->Underwater) pSprite->flags |= 9;
else pSprite->flags = 15;
2019-09-19 22:42:45 +00:00
break;
case kDudeZombieAxeBuried:
case kDudeZombieAxeLaying:
pSprite->flags = 7;
2019-09-19 22:42:45 +00:00
break;
#ifdef NOONE_EXTENSIONS
case kDudePodMother: // FakeDude type
if (gModernMap) break;
2021-09-15 22:01:50 +00:00
[[fallthrough]];
// Allow put pods and tentacles on ceilings if sprite is y-flipped.
case kDudePodGreen:
case kDudeTentacleGreen:
case kDudePodFire:
case kDudeTentacleFire:
case kDudeTentacleMother:
if (gModernMap && (pSprite->cstat & CSTAT_SPRITE_YFLIP)) {
if (!(pSprite->flags & kModernTypeFlag1)) // don't add autoaim for player if hitag 1 specified in editor.
pSprite->flags = kHitagAutoAim;
2019-09-19 22:42:45 +00:00
break;
}
2021-09-15 22:01:50 +00:00
[[fallthrough]];
2019-09-19 22:42:45 +00:00
// go default
#endif
2019-09-19 22:42:45 +00:00
default:
pSprite->flags = 15;
2019-09-19 22:42:45 +00:00
break;
}
2020-12-06 20:56:09 +00:00
#ifdef NOONE_EXTENSIONS
if (gModernMap)
{
if (pXSprite->dudeFlag4)
{
2020-12-06 20:56:09 +00:00
// restore dude's path
if (spriRangeIsFine(targetMarker))
{
pXSprite->target_i = targetMarker;
2020-12-06 20:56:09 +00:00
pXSprite->targetX = targetX;
pXSprite->targetY = targetY;
pXSprite->targetZ = targetZ;
}
// reset target spot progress
pXSprite->data3 = 0;
// make dude follow the markers
bool uwater = spriteIsUnderwater(pSprite);
if (pXSprite->target_i <= 0 || sprite[pXSprite->target_i].type != kMarkerPath) {
pXSprite->target_i = -1; aiPatrolSetMarker(pSprite, pXSprite);
Increase kMaxSuperXSprites from 128 to 512. Fix mirror (ROR) intialization so it won't crash if more than 1024 sectors used. Fix random item generator so items that inherits TX ID won't send command at respawn. Fix for things (400 - 433) that affected by modern physics so it won't return to vanilla physics after getting damage. Fix kTeleportTarget so teleported sprites won't stuck in floors or ceilings. Corpses won't gib as gargoyles anymore (gModernMap). kModernCondition: - remove bool comparison (condCmpb). - remove no extra comparison (condCmpne). - remove "else if" search at level start. - add global (game) conditions type. - add more conditions. - make error report a bit more informative. Add more options and damage effects for kModernSpriteDamager. Add more options for kModernMissileGen and allow to spawn projectile on TX ID sprites location. Add more options and vertical wind processing for kModernWindGen. Add more options and effects for kModernEffectGen. Allow kMarkerDudeSpawn to spawn enemies on TX ID sprites location (gModernMap). Allow kModernCustomDudeSpawn to spawn dude on TX ID sprites location. Add Screen and Aim trigger flags for sprites that can be triggered with Sight (gModernMap). Patrolling enemies: - add turn AI state. - add "return back" option for path markers. - add "turning while waiting" option for markers. - make enemies to hear some sounds assuming that player generates and hears it too. - add kModernStealthRegion type to affect current spot progress velocity. - replace AI's CanMove and aiChooseDirection to a better versions. - make flying enemies to not spin around the marker. - treat Phantasm as flying enemy! - allow to continue patrol when falling in water. Fix compile warnings Various minor fixes / cleanup.
2021-07-19 21:15:26 +00:00
}
2020-12-06 20:56:09 +00:00
if (stateTimer > 0) {
if (uwater) aiPatrolState(pSprite, kAiStatePatrolWaitW);
else if (pXSprite->unused1 & kDudeFlagCrouch) aiPatrolState(pSprite, kAiStatePatrolWaitC);
2020-12-06 20:56:09 +00:00
else aiPatrolState(pSprite, kAiStatePatrolWaitL);
pXSprite->stateTimer = stateTimer; // restore state timer
}
else if (uwater) aiPatrolState(pSprite, kAiStatePatrolMoveW);
else if (pXSprite->unused1 & kDudeFlagCrouch) aiPatrolState(pSprite, kAiStatePatrolMoveC);
2020-12-06 20:56:09 +00:00
else aiPatrolState(pSprite, kAiStatePatrolMoveL);
}
}
#endif
2019-09-19 22:42:45 +00:00
}
END_BLD_NS