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.
|
|
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
2019-09-21 18:59:54 +00:00
|
|
|
|
|
|
|
#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"
|
2020-12-09 14:56:32 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
#include "blood.h"
|
|
|
|
|
2019-09-22 06:39:22 +00:00
|
|
|
BEGIN_BLD_NS
|
|
|
|
|
2021-05-03 22:03:01 +00:00
|
|
|
void RecoilDude(DBloodActor* actor);
|
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
AISTATE genIdle = { kAiStateGenIdle, 0, -1, 0, NULL, NULL, NULL, NULL };
|
|
|
|
AISTATE genRecoil = { kAiStateRecoil, 5, -1, 20, NULL, NULL, NULL, &genIdle };
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
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-16 19:59:39 +00:00
|
|
|
bool dudeIsPlayingSeq(DBloodActor* actor, int nSeq)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
if (pSprite->statnum == kStatDude && pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
|
|
|
|
{
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2021-09-04 21:27:21 +00:00
|
|
|
if (seqGetID(actor) == pDudeInfo->seqStartID + nSeq && seqGetStatus(actor) >= 0)
|
2021-09-16 19:59:39 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
void aiPlay3DSound(DBloodActor* actor, int a2, AI_SFX_PRIORITY a3, int a4)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
DUDEEXTRA* pDudeExtra = &actor->dudeExtra;
|
|
|
|
if (a3 == AI_SFX_PRIORITY_0)
|
|
|
|
sfxPlay3DSound(actor, a2, a4, 2);
|
|
|
|
else if (a3 > pDudeExtra->prio || pDudeExtra->time <= PlayClock)
|
|
|
|
{
|
|
|
|
sfxKill3DSound(actor, -1, -1);
|
|
|
|
sfxPlay3DSound(actor, a2, a4, 0);
|
|
|
|
pDudeExtra->prio = a3;
|
|
|
|
pDudeExtra->time = PlayClock + 120;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
void aiNewState(DBloodActor* actor, AISTATE* pAIState)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
pXSprite->stateTimer = pAIState->stateTicks;
|
|
|
|
pXSprite->aiState = pAIState;
|
|
|
|
int seqStartId = pDudeInfo->seqStartID;
|
|
|
|
|
|
|
|
if (pAIState->seqId >= 0)
|
|
|
|
{
|
|
|
|
seqStartId += pAIState->seqId;
|
|
|
|
if (getSequence(seqStartId))
|
|
|
|
seqSpawn(seqStartId, actor, pAIState->funcId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pAIState->enterFunc)
|
|
|
|
pAIState->enterFunc(actor);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-15 22:17:37 +00:00
|
|
|
static bool isImmune(DBloodActor* actor, int dmgType, int minScale)
|
2020-12-06 07:36:54 +00:00
|
|
|
{
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
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;
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
bool CanMove(DBloodActor* actor, DBloodActor* target, int nAngle, int nRange)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
int top, bottom;
|
2021-05-05 11:39:16 +00:00
|
|
|
GetActorExtents(actor, &top, &bottom);
|
2021-09-16 19:59:39 +00:00
|
|
|
int x = pSprite->x;
|
|
|
|
int y = pSprite->y;
|
|
|
|
int z = pSprite->z;
|
2021-09-04 18:09:56 +00:00
|
|
|
HitScan(actor, z, bcos(nAngle), bsin(nAngle), 0, CLIPMASK0, nRange);
|
2021-09-16 19:59:39 +00:00
|
|
|
int nDist = approxDist(x - gHitInfo.hitx, y - gHitInfo.hity);
|
|
|
|
if (nDist - (pSprite->clipdist << 2) < nRange)
|
|
|
|
{
|
2021-05-05 14:43:42 +00:00
|
|
|
if (gHitInfo.hitactor == nullptr || target == nullptr || target != gHitInfo.hitactor)
|
2021-09-16 19:59:39 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
x += MulScale(nRange, Cos(nAngle), 30);
|
|
|
|
y += MulScale(nRange, Sin(nAngle), 30);
|
|
|
|
int nSector = pSprite->sectnum;
|
|
|
|
assert(nSector >= 0 && nSector < kMaxSectors);
|
|
|
|
if (!FindSector(x, y, z, &nSector))
|
|
|
|
return false;
|
|
|
|
int floorZ = getflorzofslope(nSector, x, y);
|
|
|
|
int nXSector = sector[nSector].extra;
|
2021-09-01 19:54:23 +00:00
|
|
|
bool Underwater = 0;
|
|
|
|
bool Water = 0;
|
|
|
|
bool Depth = 0;
|
|
|
|
bool Crusher = 0;
|
2021-09-16 19:59:39 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-05-05 14:06:35 +00:00
|
|
|
auto Upper = getUpperLink(nSector);
|
|
|
|
auto Lower = getLowerLink(nSector);
|
|
|
|
if (Upper != nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
2021-05-05 14:06:35 +00:00
|
|
|
if (Upper->s().type == kMarkerUpWater || Upper->s().type == kMarkerUpGoo)
|
2021-09-16 19:59:39 +00:00
|
|
|
Water = Depth = 1;
|
|
|
|
}
|
2021-05-05 14:06:35 +00:00
|
|
|
if (Lower != nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
2021-05-05 14:06:35 +00:00
|
|
|
if (Lower->s().type == kMarkerLowWater || Lower->s().type == kMarkerLowGoo)
|
2021-09-16 19:59:39 +00:00
|
|
|
Depth = 1;
|
|
|
|
}
|
|
|
|
switch (pSprite->type) {
|
|
|
|
case kDudeGargoyleFlesh:
|
|
|
|
case kDudeGargoyleStone:
|
|
|
|
case kDudeBat:
|
|
|
|
if (pSprite->clipdist > nDist)
|
|
|
|
return 0;
|
|
|
|
if (Depth)
|
|
|
|
{
|
|
|
|
// Ouch...
|
|
|
|
if (Depth)
|
|
|
|
return false;
|
|
|
|
if (Crusher)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeBoneEel:
|
|
|
|
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;
|
|
|
|
[[fallthrough]];
|
|
|
|
case kDudeZombieButcher:
|
|
|
|
case kDudeSpiderBrown:
|
|
|
|
case kDudeSpiderRed:
|
|
|
|
case kDudeSpiderBlack:
|
|
|
|
case kDudeSpiderMother:
|
|
|
|
case kDudeHellHound:
|
|
|
|
case kDudeRat:
|
|
|
|
case kDudeInnocent:
|
|
|
|
if (Crusher)
|
|
|
|
return false;
|
|
|
|
if (Depth || Underwater)
|
|
|
|
return false;
|
|
|
|
if (floorZ - bottom > 0x2000)
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
|
|
case kDudeModernCustom:
|
|
|
|
case kDudeModernCustomBurning:
|
2021-08-27 12:01:51 +00:00
|
|
|
if ((Crusher && !nnExtIsImmune(actor, pXSector->damageType)) || ((Water || Underwater) && !canSwim(actor))) return false;
|
2021-09-16 19:59:39 +00:00
|
|
|
return true;
|
|
|
|
[[fallthrough]];
|
|
|
|
#endif
|
|
|
|
case kDudeZombieAxeNormal:
|
|
|
|
case kDudePhantasm:
|
|
|
|
case kDudeGillBeast:
|
|
|
|
default:
|
|
|
|
if (Crusher)
|
|
|
|
return false;
|
|
|
|
if ((nXSector < 0 || (!xsector[nXSector].Underwater && !xsector[nXSector].Depth)) && floorZ - bottom > 0x2000)
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 1;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-15 22:17:37 +00:00
|
|
|
void aiChooseDirection(DBloodActor* actor, int a3)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
2021-09-15 22:17:37 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
|
|
|
int vc = ((a3 + 1024 - pSprite->ang) & 2047) - 1024;
|
|
|
|
int nCos = Cos(pSprite->ang);
|
|
|
|
int nSin = Sin(pSprite->ang);
|
2021-09-04 10:52:02 +00:00
|
|
|
int dx = actor->xvel;
|
|
|
|
int dy = actor->yvel;
|
2021-09-16 19:59:39 +00:00
|
|
|
int t1 = DMulScale(dx, nCos, dy, nSin, 30);
|
|
|
|
int vsi = ((t1 * 15) >> 12) / 2;
|
|
|
|
int v8 = 341;
|
|
|
|
if (vc < 0)
|
|
|
|
v8 = -341;
|
|
|
|
if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc, vsi))
|
|
|
|
pXSprite->goalAng = pSprite->ang + vc;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc / 2, vsi))
|
|
|
|
pXSprite->goalAng = pSprite->ang + vc / 2;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - vc / 2, vsi))
|
|
|
|
pXSprite->goalAng = pSprite->ang - vc / 2;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + v8, vsi))
|
|
|
|
pXSprite->goalAng = pSprite->ang + v8;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang, vsi))
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - v8, vsi))
|
|
|
|
pXSprite->goalAng = pSprite->ang - v8;
|
|
|
|
//else if (pSprite->flags&2)
|
|
|
|
//pXSprite->goalAng = pSprite->ang+341;
|
|
|
|
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))
|
|
|
|
{
|
|
|
|
pXSprite->dodgeDir = -pXSprite->dodgeDir;
|
|
|
|
if (!CanMove(actor, actor->GetTarget(), pSprite->ang + pXSprite->dodgeDir * 512, 512))
|
|
|
|
pXSprite->dodgeDir = 0;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiMoveForward(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
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)
|
|
|
|
return;
|
2021-09-04 10:52:02 +00:00
|
|
|
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
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiMoveTurn(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
|
|
|
|
int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
|
|
|
|
pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiMoveDodge(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
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);
|
2021-09-04 10:52:02 +00:00
|
|
|
int dx = actor->xvel;
|
|
|
|
int dy = actor->yvel;
|
2021-09-16 19:59:39 +00:00
|
|
|
int t1 = DMulScale(dx, nCos, dy, nSin, 30);
|
|
|
|
int t2 = DMulScale(dx, nSin, -dy, nCos, 30);
|
|
|
|
if (pXSprite->dodgeDir > 0)
|
|
|
|
t2 += pDudeInfo->sideSpeed;
|
|
|
|
else
|
|
|
|
t2 -= pDudeInfo->sideSpeed;
|
|
|
|
|
2021-09-04 10:52:02 +00:00
|
|
|
actor->xvel = DMulScale(t1, nCos, t2, nSin, 30);
|
|
|
|
actor->yvel = DMulScale(t1, nSin, -t2, nCos, 30);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// todo: split this up.
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiActivateDude(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
|
|
|
if (!pXSprite->state)
|
|
|
|
{
|
|
|
|
aiChooseDirection(actor, getangle(pXSprite->targetX - pSprite->x, pXSprite->targetY - pSprite->y));
|
|
|
|
pXSprite->state = 1;
|
|
|
|
}
|
|
|
|
switch (pSprite->type)
|
|
|
|
{
|
|
|
|
case kDudePhantasm:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &ghostSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 1600, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &ghostChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeCultistTommy:
|
|
|
|
case kDudeCultistShotgun:
|
|
|
|
case kDudeCultistTesla:
|
|
|
|
case kDudeCultistTNT:
|
|
|
|
case kDudeCultistBeast:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &cultistSearch);
|
|
|
|
if (Chance(0x8000))
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x8000))
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-01-26 11:19:01 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeModernCustom:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 1;
|
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-15 22:52:54 +00:00
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
2021-09-15 22:52:54 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 17:15:25 +00:00
|
|
|
if (Chance(0x4000)) playGenDudeSound(actor,kGenDudeSndTargetSpot);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeModernCustomBurning:
|
2021-08-31 19:52:26 +00:00
|
|
|
if (actor->GetTarget() == nullptr) aiGenDudeNewState(actor, &genDudeBurnSearch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeBurnChase);
|
2021-09-16 19:59:39 +00:00
|
|
|
break;
|
2020-01-26 11:19:01 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeCultistTommyProne:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 1;
|
|
|
|
pSprite->type = kDudeCultistTommy;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
aiNewState(actor, &cultistSearch);
|
|
|
|
if (Chance(0x8000)) aiPlay3DSound(actor, 4008 + Random(5), AI_SFX_PRIORITY_1, -1);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &cultistSwimSearch);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x8000))
|
|
|
|
aiPlay3DSound(actor, 4008 + Random(5), AI_SFX_PRIORITY_1, -1);
|
|
|
|
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &cultistProneChase);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &cultistSwimChase);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeCultistShotgunProne:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 1;
|
|
|
|
pSprite->type = kDudeCultistShotgun;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &cultistSearch);
|
|
|
|
if (Chance(0x8000))
|
|
|
|
aiPlay3DSound(actor, 1008 + Random(5), AI_SFX_PRIORITY_1, -1);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &cultistSwimSearch);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x8000))
|
|
|
|
aiPlay3DSound(actor, 1003 + Random(4), AI_SFX_PRIORITY_1, -1);
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &cultistProneChase);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &cultistSwimChase);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeBurningCultist:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cultistBurnSearch);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &cultistBurnChase);
|
|
|
|
break;
|
|
|
|
case kDudeBat:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
|
|
|
if (!pSprite->flags)
|
|
|
|
pSprite->flags = 9;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &batSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0xa000))
|
|
|
|
aiPlay3DSound(actor, 2000, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &batChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeBoneEel:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &eelSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x8000))
|
|
|
|
aiPlay3DSound(actor, 1501, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 1500, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &eelChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGillBeast:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
XSECTOR* pXSector = NULL;
|
|
|
|
if (sector[pSprite->sectnum].extra > 0)
|
|
|
|
pXSector = &xsector[sector[pSprite->sectnum].extra];
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
if (pXSector && pXSector->Underwater)
|
|
|
|
aiNewState(actor, &gillBeastSwimSearch);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &gillBeastSearch);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x4000))
|
|
|
|
aiPlay3DSound(actor, 1701, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 1700, AI_SFX_PRIORITY_1, -1);
|
|
|
|
if (pXSector && pXSector->Underwater)
|
|
|
|
aiNewState(actor, &gillBeastSwimChase);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &gillBeastChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeNormal:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &zombieASearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0xa000))
|
|
|
|
{
|
|
|
|
switch (Random(3))
|
|
|
|
{
|
|
|
|
default:
|
|
|
|
case 0:
|
|
|
|
case 3:
|
|
|
|
aiPlay3DSound(actor, 1103, AI_SFX_PRIORITY_1, -1);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
aiPlay3DSound(actor, 1104, AI_SFX_PRIORITY_1, -1);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
aiPlay3DSound(actor, 1105, AI_SFX_PRIORITY_1, -1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
aiNewState(actor, &zombieAChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeBuried:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
|
|
|
if (pXSprite->aiState == &zombieEIdle) aiNewState(actor, &zombieEUp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeLaying:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
|
|
|
if (pXSprite->aiState == &zombieSIdle) aiNewState(actor, &zombie13AC2C);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieButcher:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &zombieFSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x4000))
|
|
|
|
aiPlay3DSound(actor, 1201, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 1200, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &zombieFChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeBurningZombieAxe:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &zombieABurnSearch);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &zombieABurnChase);
|
|
|
|
break;
|
|
|
|
case kDudeBurningZombieButcher:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &zombieFBurnSearch);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &zombieFBurnChase);
|
|
|
|
break;
|
|
|
|
case kDudeGargoyleFlesh: {
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &gargoyleFSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x4000))
|
|
|
|
aiPlay3DSound(actor, 1401, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 1400, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &gargoyleFChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGargoyleStone:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &gargoyleFSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Chance(0x4000))
|
|
|
|
aiPlay3DSound(actor, 1451, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 1450, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &gargoyleFChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGargoyleStatueFlesh:
|
|
|
|
case kDudeGargoyleStatueStone:
|
|
|
|
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
|
|
// play gargoyle statue breaking animation if data1 = 1.
|
|
|
|
if (gModernMap && pXSprite->data1 == 1)
|
|
|
|
{
|
|
|
|
if (pSprite->type == kDudeGargoyleStatueFlesh) aiNewState(actor, &statueFBreakSEQ);
|
|
|
|
else aiNewState(actor, &statueSBreakSEQ);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
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
|
|
|
|
break;
|
|
|
|
case kDudeCerberusTwoHead:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cerberusSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 2300, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &cerberusChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeCerberusOneHead:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cerberus2Search);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 2300, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &cerberus2Chase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeHellHound:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &houndSearch);
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 09:51:10 +00:00
|
|
|
aiPlay3DSound(actor, 1300, AI_SFX_PRIORITY_1, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &houndChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeHand:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &handSearch);
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 09:51:10 +00:00
|
|
|
aiPlay3DSound(actor, 1900, AI_SFX_PRIORITY_1, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &handChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeRat:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &ratSearch);
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 09:51:10 +00:00
|
|
|
aiPlay3DSound(actor, 2100, AI_SFX_PRIORITY_1, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &ratChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeInnocent:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &innocentSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (pXSprite->health > 0)
|
|
|
|
aiPlay3DSound(actor, 7000 + Random(6), AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &innocentChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeTchernobog:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &tchernobogSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 2350 + Random(7), AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &tchernobogChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeSpiderBrown:
|
|
|
|
case kDudeSpiderRed:
|
|
|
|
case kDudeSpiderBlack:
|
|
|
|
pSprite->flags |= 2;
|
|
|
|
pSprite->cstat &= ~8;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &spidSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 1800, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &spidChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeSpiderMother:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 1;
|
|
|
|
pSprite->flags |= 2;
|
|
|
|
pSprite->cstat &= ~8;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &spidSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 1853 + Random(1), AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &spidChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeTinyCaleb:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &tinycalebSearch);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &tinycalebSwimSearch);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &tinycalebChase);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &tinycalebSwimChase);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeBeast:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->thinkTime = 1;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &beastSearch);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &beastSwimSearch);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 9009 + Random(2), AI_SFX_PRIORITY_1, -1);
|
|
|
|
switch (pXSprite->medium)
|
|
|
|
{
|
|
|
|
case kMediumNormal:
|
|
|
|
aiNewState(actor, &beastChase);
|
|
|
|
break;
|
|
|
|
case kMediumWater:
|
|
|
|
case kMediumGoo:
|
|
|
|
aiNewState(actor, &beastSwimChase);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudePodGreen:
|
|
|
|
case kDudePodFire:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &podSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (pSprite->type == kDudePodFire)
|
|
|
|
aiPlay3DSound(actor, 2453, AI_SFX_PRIORITY_1, -1);
|
|
|
|
else
|
|
|
|
aiPlay3DSound(actor, 2473, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &podChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeTentacleGreen:
|
|
|
|
case kDudeTentacleFire:
|
2021-09-15 22:52:54 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &tentacleSearch);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 2503, AI_SFX_PRIORITY_1, -1);
|
|
|
|
aiNewState(actor, &tentacleChase);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-15 22:01:50 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-16 19:59:39 +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);
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->targetX = x;
|
|
|
|
pXSprite->targetY = y;
|
|
|
|
pXSprite->targetZ = z;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
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();
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pTarget->type >= kDudeBase && pTarget->type < kDudeMax)
|
|
|
|
{
|
2021-09-16 17:32:46 +00:00
|
|
|
if (actor->GetOwner() != target)
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
2021-09-16 17:32:46 +00:00
|
|
|
actor->SetTarget(target);
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pTarget->type);
|
2021-09-16 19:59:39 +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);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 17:41:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2021-09-16 18:43:58 +00:00
|
|
|
// todo: split up and put most of its content in tables.
|
2021-09-16 17:41:23 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
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
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
2020-12-02 23:30:19 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (!pXSprite->health)
|
|
|
|
return 0;
|
|
|
|
pXSprite->health = ClipLow(pXSprite->health - nDamage, 0);
|
2021-09-16 18:49:25 +00:00
|
|
|
actor->cumulDamage += nDamage;
|
2021-09-16 19:59:39 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
|
|
|
|
if (source)
|
|
|
|
{
|
|
|
|
spritetype* pSource = &source->s();
|
|
|
|
if (pSprite == pSource) return 0;
|
|
|
|
else if (actor->GetTarget() == nullptr) // if no target, give the dude a target
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, source);
|
2021-09-16 18:38:46 +00:00
|
|
|
aiActivateDude(actor);
|
2021-09-16 19:59:39 +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))
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, source);
|
|
|
|
aiActivateDude(actor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
|
|
if (gModernMap) {
|
|
|
|
|
|
|
|
// for enemies in patrol mode
|
|
|
|
if (aiInPatrolState(pXSprite->aiState))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-05 17:21:18 +00:00
|
|
|
aiPatrolStop(actor, source, pXSprite->dudeAmbush);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
PLAYER* pPlayer = getPlayerById(pSource->type);
|
|
|
|
if (!pPlayer) return nDamage;
|
|
|
|
if (powerupCheck(pPlayer, kPwUpShadowCloak)) pPlayer->pwUpTime[kPwUpShadowCloak] = 0;
|
2021-08-29 15:16:38 +00:00
|
|
|
if (readyForCrit(source, actor))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:34:16 +00:00
|
|
|
nDamage += aiDamageSprite(actor, source, nDmgType, nDamage * (10 - gGameOptions.nDifficulty));
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->health > 0)
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
int fullHp = (pXSprite->sysData2 > 0) ? ClipRange(pXSprite->sysData2 << 4, 1, 65535) : getDudeInfo(pSprite->type)->startHealth << 4;
|
|
|
|
if (((100 * pXSprite->health) / fullHp) <= 75)
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 18:49:25 +00:00
|
|
|
actor->cumulDamage += nDamage << 4; // to be sure any enemy will play the recoil animation
|
2021-08-31 23:28:19 +00:00
|
|
|
RecoilDude(actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-06 20:47:10 +00:00
|
|
|
DPrintf(DMSG_SPAMMY, "Player #%d does the critical damage to patrol dude #%d!", pPlayer->nPlayer + 1, actor->GetIndex());
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
return nDamage;
|
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pSprite->type == kDudeModernCustomBurning)
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
if (Chance(0x2000) && actor->dudeExtra.time < PlayClock) {
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor,kGenDudeSndBurning);
|
2021-09-16 19:59:39 +00:00
|
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->burnTime == 0) pXSprite->burnTime = 2400;
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +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.
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeGotoW);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
return nDamage;
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pSprite->type == kDudeModernCustom)
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-08-29 07:27:03 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
2021-09-16 19:59:39 +00:00
|
|
|
if (nDmgType == kDamageBurn)
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->health > (uint32_t)pDudeInfo->fleeHealth) return nDamage;
|
2021-05-06 06:39:33 +00:00
|
|
|
else if (pXSprite->txID <= 0 || getNextIncarnation(actor) == nullptr)
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-05 19:18:09 +00:00
|
|
|
removeDudeStuff(actor);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pExtra->weaponType == kGenDudeWeaponKamikaze)
|
2021-05-06 07:09:58 +00:00
|
|
|
doExplosion(actor, pXSprite->data1 - kTrapExploder);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->health = 0;
|
|
|
|
return nDamage;
|
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->burnTime <= 0)
|
|
|
|
pXSprite->burnTime = 1200;
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pExtra->canBurn && pExtra->availDeaths[kDamageBurn] > 0) {
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-05-05 09:51:10 +00:00
|
|
|
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor,kGenDudeSndBurning);
|
2021-09-16 19:59:39 +00:00
|
|
|
pSprite->type = kDudeModernCustomBurning;
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->data2 == kGenDudeDefaultSeq) // don't inherit palette for burning if using default animation
|
|
|
|
pSprite->pal = 0;
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeBurnGoto);
|
2021-09-16 19:59:39 +00:00
|
|
|
actHealDude(actor, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
|
|
|
|
actor->dudeExtra.time = PlayClock + 360;
|
2021-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2021-09-16 17:41:23 +00:00
|
|
|
}
|
2021-09-16 19:59:39 +00:00
|
|
|
else
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
actKillDude(actor, actor, kDamageFall, 65535);
|
|
|
|
}
|
2021-05-06 08:24:29 +00:00
|
|
|
}
|
|
|
|
else if (canWalk(actor) && !inDodge(pXSprite->aiState) && !inRecoil(pXSprite->aiState))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-06 06:45:50 +00:00
|
|
|
if (!dudeIsMelee(actor))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-06 07:08:06 +00:00
|
|
|
if (inIdle(pXSprite->aiState) || Chance(getDodgeChance(actor)))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (!spriteIsUnderwater(actor))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-06 08:24:29 +00:00
|
|
|
if (!canDuck(actor) || !dudeIsPlayingSeq(actor, 14)) aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortD);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
if (Chance(0x0200))
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor,kGenDudeSndGotHit);
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
else if (dudeIsPlayingSeq(actor, 13))
|
2021-09-16 17:42:54 +00:00
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeDodgeShortW);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-16 17:41:23 +00:00
|
|
|
}
|
2021-09-16 19:59:39 +00:00
|
|
|
else if (Chance(0x0200))
|
2021-09-16 17:41:23 +00:00
|
|
|
{
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor,kGenDudeSndGotHit);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nDamage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (nDmgType == kDamageTesla)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
const bool fixRandomCultist = !cl_bloodvanillaenemies && (pSprite->inittype >= kDudeBase) && (pSprite->inittype < kDudeMax) && !VanillaMode(); // fix burning cultists randomly switching types underwater
|
|
|
|
switch (pSprite->type)
|
|
|
|
{
|
|
|
|
case kDudeCultistTommy:
|
|
|
|
case kDudeCultistShotgun:
|
|
|
|
case kDudeCultistTesla:
|
|
|
|
case kDudeCultistTNT:
|
|
|
|
if (nDmgType != kDamageBurn)
|
|
|
|
{
|
2021-09-16 17:42:54 +00:00
|
|
|
if (!dudeIsPlayingSeq(actor, 14) && !pXSprite->medium)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cultistDodge);
|
2021-09-16 17:42:54 +00:00
|
|
|
else if (dudeIsPlayingSeq(actor, 14) && !pXSprite->medium)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cultistProneDodge);
|
2021-09-16 17:42:54 +00:00
|
|
|
else if (dudeIsPlayingSeq(actor, 13) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &cultistSwimDodge);
|
|
|
|
}
|
|
|
|
else if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
|
|
|
|
{
|
|
|
|
pSprite->type = kDudeBurningCultist;
|
|
|
|
aiNewState(actor, &cultistBurnGoto);
|
2021-09-16 17:42:54 +00:00
|
|
|
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiPlay3DSound(actor, 1031 + Random(2), AI_SFX_PRIORITY_2, -1);
|
|
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
|
|
actHealDude(actor, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
|
2021-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeInnocent:
|
|
|
|
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
|
|
|
|
{
|
|
|
|
pSprite->type = kDudeBurningInnocent;
|
|
|
|
aiNewState(actor, &cultistBurnGoto);
|
2021-09-16 17:42:54 +00:00
|
|
|
aiPlay3DSound(actor, 361, AI_SFX_PRIORITY_0, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
|
|
actHealDude(actor, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
|
2021-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeBurningCultist:
|
|
|
|
if (Chance(0x4000) && actor->dudeExtra.time < PlayClock)
|
|
|
|
{
|
|
|
|
aiPlay3DSound(actor, 1031 + Random(2), AI_SFX_PRIORITY_2, -1);
|
|
|
|
actor->dudeExtra.time = PlayClock + 360;
|
|
|
|
}
|
|
|
|
if (Chance(0x600) && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo))
|
|
|
|
{
|
|
|
|
pSprite->type = kDudeCultistTommy;
|
|
|
|
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
|
|
|
|
pSprite->type = pSprite->inittype; // restore back to spawned cultist type
|
|
|
|
pXSprite->burnTime = 0;
|
|
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
|
|
}
|
|
|
|
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
|
|
|
|
{
|
|
|
|
pSprite->type = kDudeCultistShotgun;
|
|
|
|
if (fixRandomCultist) // fix burning cultists randomly switching types underwater
|
|
|
|
pSprite->type = pSprite->inittype; // restore back to spawned cultist type
|
|
|
|
pXSprite->burnTime = 0;
|
|
|
|
aiNewState(actor, &cultistSwimGoto);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeGargoyleFlesh:
|
|
|
|
aiNewState(actor, &gargoyleFChase);
|
|
|
|
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);
|
2021-09-16 19:59:39 +00:00
|
|
|
pSprite->type = kDudeBurningZombieButcher;
|
|
|
|
aiNewState(actor, &zombieFBurnGoto);
|
|
|
|
actHealDude(actor, dudeInfo[42].startHealth, dudeInfo[42].startHealth);
|
2021-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeTinyCaleb:
|
|
|
|
if (nDmgType == kDamageBurn && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
|
|
|
|
{
|
|
|
|
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-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeCultistBeast:
|
|
|
|
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);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &beastMorphFromCultist);
|
|
|
|
actHealDude(actor, dudeInfo[51].startHealth, dudeInfo[51].startHealth);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeZombieAxeNormal:
|
|
|
|
case kDudeZombieAxeBuried:
|
|
|
|
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, 1106, AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
pSprite->type = kDudeBurningZombieAxe;
|
|
|
|
aiNewState(actor, &zombieABurnGoto);
|
|
|
|
actHealDude(actor, dudeInfo[41].startHealth, dudeInfo[41].startHealth);
|
2021-08-27 07:44:47 +00:00
|
|
|
evKillActor(actor, kCallbackFXFlameLick);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nDamage;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// todo: split up and put most of its content in tables.
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void RecoilDude(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
char v4 = Chance(0x8000);
|
2021-09-16 18:43:58 +00:00
|
|
|
DUDEEXTRA* pDudeExtra = &actor->dudeExtra;
|
|
|
|
if (pSprite->statnum == kStatDude && (pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
|
|
|
|
{
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
switch (pSprite->type)
|
|
|
|
{
|
2020-01-26 11:19:01 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 18:43:58 +00:00
|
|
|
case kDudeModernCustom:
|
|
|
|
{
|
2021-08-29 07:27:03 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
2021-05-06 07:08:06 +00:00
|
|
|
int rChance = getRecoilChance(actor);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (pExtra->canElectrocute && pDudeExtra->teslaHit && !spriteIsUnderwater(actor, false))
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
2021-05-06 06:45:50 +00:00
|
|
|
if (Chance(rChance << 3) || (dudeIsMelee(actor) && Chance(rChance << 4))) aiGenDudeNewState(actor, &genDudeRecoilTesla);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (pExtra->canRecoil && Chance(rChance)) aiGenDudeNewState(actor, &genDudeRecoilL);
|
2021-05-06 08:24:29 +00:00
|
|
|
else if (canWalk(actor))
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
if (Chance(rChance >> 2)) aiGenDudeNewState(actor, &genDudeDodgeL);
|
|
|
|
else if (Chance(rChance >> 1)) aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
}
|
|
|
|
else if (pExtra->canRecoil && Chance(rChance))
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (inDuck(pXSprite->aiState) && Chance(rChance >> 2)) aiGenDudeNewState(actor, &genDudeRecoilD);
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeRecoilW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeRecoilL);
|
2021-09-16 18:43:58 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
short rState = inRecoil(pXSprite->aiState);
|
2021-09-16 18:43:58 +00:00
|
|
|
if (rState > 0)
|
|
|
|
{
|
2021-05-06 08:24:29 +00:00
|
|
|
if (!canWalk(actor))
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
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
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
}
|
2021-05-06 06:45:50 +00:00
|
|
|
else if (!dudeIsMelee(actor) || Chance(rChance >> 2))
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
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
|
|
|
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor,kGenDudeSndGotHit);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
pDudeExtra->teslaHit = 0;
|
|
|
|
break;
|
|
|
|
}
|
2020-01-26 11:19:01 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeCultistTommy:
|
|
|
|
case kDudeCultistShotgun:
|
|
|
|
case kDudeCultistTesla:
|
|
|
|
case kDudeCultistTNT:
|
|
|
|
case kDudeCultistBeast:
|
2021-09-16 18:43:58 +00:00
|
|
|
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)
|
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pDudeExtra->teslaHit) aiNewState(actor, &cultistTeslaRecoil);
|
|
|
|
else aiNewState(actor, &cultistRecoil);
|
2019-10-11 21:59:39 +00:00
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
}
|
|
|
|
else if (v4 && pXSprite->medium == kMediumNormal)
|
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pDudeExtra->teslaHit) aiNewState(actor, &cultistTeslaRecoil);
|
|
|
|
else if (gGameOptions.nDifficulty > 0) aiNewState(actor, &cultistProneRecoil);
|
|
|
|
else aiNewState(actor, &cultistRecoil);
|
|
|
|
}
|
|
|
|
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
|
|
|
|
aiNewState(actor, &cultistSwimRecoil);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &cultistTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &cultistRecoil);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeBurningCultist:
|
|
|
|
aiNewState(actor, &cultistBurnGoto);
|
|
|
|
break;
|
2020-01-26 11:19:01 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeModernCustomBurning:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeBurnGoto);
|
2021-09-16 19:59:39 +00:00
|
|
|
break;
|
2020-01-26 11:19:01 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeZombieButcher:
|
|
|
|
aiPlay3DSound(actor, 1202, AI_SFX_PRIORITY_2, -1);
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &zombieFTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &zombieFRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeZombieAxeNormal:
|
|
|
|
case kDudeZombieAxeBuried:
|
|
|
|
aiPlay3DSound(actor, 1106, AI_SFX_PRIORITY_2, -1);
|
|
|
|
if (pDudeExtra->teslaHit && pXSprite->data3 > pDudeInfo->startHealth / 3)
|
|
|
|
aiNewState(actor, &zombieATeslaRecoil);
|
2021-09-16 18:43:58 +00:00
|
|
|
else if (pXSprite->data3 > pDudeInfo->startHealth / 3)
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &zombieARecoil2);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &zombieARecoil);
|
|
|
|
break;
|
|
|
|
case kDudeBurningZombieAxe:
|
|
|
|
aiPlay3DSound(actor, 1106, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &zombieABurnGoto);
|
|
|
|
break;
|
|
|
|
case kDudeBurningZombieButcher:
|
|
|
|
aiPlay3DSound(actor, 1202, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &zombieFBurnGoto);
|
|
|
|
break;
|
|
|
|
case kDudeGargoyleFlesh:
|
|
|
|
case kDudeGargoyleStone:
|
|
|
|
aiPlay3DSound(actor, 1402, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &gargoyleFRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeCerberusTwoHead:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 2302 + Random(2), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pDudeExtra->teslaHit && pXSprite->data3 > pDudeInfo->startHealth / 3)
|
|
|
|
aiNewState(actor, &cerberusTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &cerberusRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeCerberusOneHead:
|
|
|
|
aiPlay3DSound(actor, 2302 + Random(2), AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &cerberus2Recoil);
|
|
|
|
break;
|
|
|
|
case kDudeHellHound:
|
|
|
|
aiPlay3DSound(actor, 1302, AI_SFX_PRIORITY_2, -1);
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &houndTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &houndRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeTchernobog:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 2370 + Random(2), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &tchernobogRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeHand:
|
|
|
|
aiPlay3DSound(actor, 1902, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &handRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeRat:
|
|
|
|
aiPlay3DSound(actor, 2102, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &ratRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeBat:
|
|
|
|
aiPlay3DSound(actor, 2002, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &batRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeBoneEel:
|
|
|
|
aiPlay3DSound(actor, 1502, AI_SFX_PRIORITY_2, -1);
|
|
|
|
aiNewState(actor, &eelRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeGillBeast: {
|
2021-09-16 18:43:58 +00:00
|
|
|
XSECTOR* pXSector = NULL;
|
2021-09-16 19:59:39 +00:00
|
|
|
if (sector[pSprite->sectnum].extra > 0)
|
|
|
|
pXSector = &xsector[sector[pSprite->sectnum].extra];
|
|
|
|
aiPlay3DSound(actor, 1702, AI_SFX_PRIORITY_2, -1);
|
|
|
|
if (pXSector && pXSector->Underwater)
|
|
|
|
aiNewState(actor, &gillBeastSwimRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &gillBeastRecoil);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudePhantasm:
|
|
|
|
aiPlay3DSound(actor, 1602, AI_SFX_PRIORITY_2, -1);
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &ghostTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &ghostRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeSpiderBrown:
|
|
|
|
case kDudeSpiderRed:
|
|
|
|
case kDudeSpiderBlack:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 1802 + Random(1), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &spidDodge);
|
|
|
|
break;
|
|
|
|
case kDudeSpiderMother:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 1851 + Random(1), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiNewState(actor, &spidDodge);
|
|
|
|
break;
|
|
|
|
case kDudeInnocent:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 7007 + Random(2), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &innocentTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &innocentRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeTinyCaleb:
|
|
|
|
if (pXSprite->medium == kMediumNormal)
|
|
|
|
{
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &tinycalebTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &tinycalebRecoil);
|
|
|
|
}
|
|
|
|
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
|
|
|
|
aiNewState(actor, &tinycalebSwimRecoil);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &tinycalebTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &tinycalebRecoil);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudeBeast:
|
2021-09-16 18:43:58 +00:00
|
|
|
aiPlay3DSound(actor, 9004 + Random(2), AI_SFX_PRIORITY_2, -1);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->medium == kMediumNormal)
|
|
|
|
{
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &beastTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &beastRecoil);
|
|
|
|
}
|
|
|
|
else if (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)
|
|
|
|
aiNewState(actor, &beastSwimRecoil);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (pDudeExtra->teslaHit)
|
|
|
|
aiNewState(actor, &beastTeslaRecoil);
|
|
|
|
else
|
|
|
|
aiNewState(actor, &beastRecoil);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case kDudePodGreen:
|
|
|
|
case kDudePodFire:
|
|
|
|
aiNewState(actor, &podRecoil);
|
|
|
|
break;
|
|
|
|
case kDudeTentacleGreen:
|
|
|
|
case kDudeTentacleFire:
|
|
|
|
aiNewState(actor, &tentacleRecoil);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
aiNewState(actor, &genRecoil);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pDudeExtra->teslaHit = 0;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiThinkTarget(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
2021-09-16 18:43:58 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (Chance(pDudeInfo->alertChance))
|
|
|
|
{
|
|
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
|
|
{
|
2021-09-16 18:43:58 +00:00
|
|
|
PLAYER* pPlayer = &gPlayer[p];
|
2021-09-05 07:53:06 +00:00
|
|
|
if (actor->GetOwner() == pPlayer->actor || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
|
2021-09-16 19:59:39 +00:00
|
|
|
continue;
|
|
|
|
int x = pPlayer->pSprite->x;
|
|
|
|
int y = pPlayer->pSprite->y;
|
|
|
|
int z = pPlayer->pSprite->z;
|
|
|
|
int nSector = pPlayer->pSprite->sectnum;
|
2021-09-16 18:43:58 +00:00
|
|
|
int dx = x - pSprite->x;
|
|
|
|
int dy = y - pSprite->y;
|
2021-09-16 19:59:39 +00:00
|
|
|
int nDist = approxDist(dx, dy);
|
|
|
|
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
|
|
|
|
continue;
|
2021-09-16 18:43:58 +00:00
|
|
|
if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - ((pDudeInfo->eyeHeight * pSprite->yrepeat) << 2), pSprite->sectnum))
|
2021-09-16 19:59:39 +00:00
|
|
|
continue;
|
2021-09-16 18:43:58 +00:00
|
|
|
|
|
|
|
int nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
|
2021-09-16 19:59:39 +00:00
|
|
|
if (nDist < pDudeInfo->seeDist && abs(nDeltaAngle) <= pDudeInfo->periphery)
|
|
|
|
{
|
2021-09-05 07:53:06 +00:00
|
|
|
aiSetTarget(actor, pPlayer->actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiActivateDude(actor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (nDist < pDudeInfo->hearDist)
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, x, y, z);
|
|
|
|
aiActivateDude(actor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-16 18:49:25 +00:00
|
|
|
void aiLookForTarget(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-09-16 18:49:25 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
auto pXSprite = &actor->x();
|
2021-09-16 19:59:39 +00:00
|
|
|
assert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
|
2021-09-16 18:43:58 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (Chance(pDudeInfo->alertChance))
|
|
|
|
{
|
|
|
|
for (int p = connecthead; p >= 0; p = connectpoint2[p])
|
|
|
|
{
|
2021-09-16 18:43:58 +00:00
|
|
|
PLAYER* pPlayer = &gPlayer[p];
|
2021-09-05 07:53:06 +00:00
|
|
|
if (actor->GetOwner() == pPlayer->actor || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
|
2021-09-16 19:59:39 +00:00
|
|
|
continue;
|
|
|
|
int x = pPlayer->pSprite->x;
|
|
|
|
int y = pPlayer->pSprite->y;
|
|
|
|
int z = pPlayer->pSprite->z;
|
|
|
|
int nSector = pPlayer->pSprite->sectnum;
|
2021-09-16 18:43:58 +00:00
|
|
|
int dx = x - pSprite->x;
|
|
|
|
int dy = y - pSprite->y;
|
2021-09-16 19:59:39 +00:00
|
|
|
int nDist = approxDist(dx, dy);
|
|
|
|
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
|
|
|
|
continue;
|
2021-09-16 18:43:58 +00:00
|
|
|
if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z - ((pDudeInfo->eyeHeight * pSprite->yrepeat) << 2), pSprite->sectnum))
|
2021-09-16 19:59:39 +00:00
|
|
|
continue;
|
2021-09-16 18:43:58 +00:00
|
|
|
int nDeltaAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
|
2021-09-16 19:59:39 +00:00
|
|
|
if (nDist < pDudeInfo->seeDist && abs(nDeltaAngle) <= pDudeInfo->periphery)
|
|
|
|
{
|
2021-09-05 07:53:06 +00:00
|
|
|
aiSetTarget(actor, pPlayer->actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
aiActivateDude(actor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (nDist < pDudeInfo->hearDist)
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, x, y, z);
|
|
|
|
aiActivateDude(actor);
|
|
|
|
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);
|
|
|
|
|
|
|
|
BloodStatIterator it(kStatDude);
|
|
|
|
while (DBloodActor* actor2 = it.Next())
|
|
|
|
{
|
2021-09-16 18:38:46 +00:00
|
|
|
spritetype* pSprite2 = &actor2->s();
|
2021-09-16 18:43:58 +00:00
|
|
|
int dx = pSprite2->x - pSprite->x;
|
|
|
|
int dy = pSprite2->y - pSprite->y;
|
2021-09-16 19:59:39 +00:00
|
|
|
int nDist = approxDist(dx, dy);
|
|
|
|
if (pSprite2->type == kDudeInnocent)
|
|
|
|
{
|
2021-09-16 18:43:58 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite2->type);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
|
|
|
|
continue;
|
|
|
|
aiSetTarget(actor, actor2);
|
|
|
|
aiActivateDude(actor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 18:43:58 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
void aiProcessDudes(void)
|
2021-09-16 18:43:58 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
BloodStatIterator it(kStatDude);
|
|
|
|
while (auto actor = it.Next())
|
|
|
|
{
|
2021-09-16 18:49:25 +00:00
|
|
|
auto pSprite = &actor->s();
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pSprite->flags & 32) continue;
|
2021-09-16 18:49:25 +00:00
|
|
|
auto pXSprite = &actor->x();
|
2021-05-05 10:55:52 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (IsPlayerSprite(pSprite) || pXSprite->health == 0) continue;
|
2021-09-16 18:49:25 +00:00
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
pXSprite->stateTimer = ClipLow(pXSprite->stateTimer - 4, 0);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2021-09-10 21:36:18 +00:00
|
|
|
if (pXSprite->aiState)
|
|
|
|
{
|
|
|
|
if (pXSprite->aiState->moveFunc)
|
|
|
|
pXSprite->aiState->moveFunc(actor);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2021-11-10 23:32:15 +00:00
|
|
|
if (pXSprite->aiState->thinkFunc && (gFrameCount & 3) == (actor->GetIndex() & 3))
|
2021-09-10 21:36:18 +00:00
|
|
|
pXSprite->aiState->thinkFunc(actor);
|
|
|
|
}
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
switch (pSprite->type) {
|
2021-05-05 10:55:52 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeModernCustom:
|
|
|
|
case kDudeModernCustomBurning: {
|
2021-08-29 07:27:03 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
2021-09-01 10:04:27 +00:00
|
|
|
if (pExtra->slaveCount > 0) updateTargetOfSlaves(actor);
|
2021-05-06 07:55:56 +00:00
|
|
|
if (pExtra->pLifeLeech != nullptr) updateTargetOfLeech(actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
if (pXSprite->stateTimer == 0 && pXSprite->aiState && pXSprite->aiState->nextState
|
2021-09-04 21:27:21 +00:00
|
|
|
&& (pXSprite->aiState->stateTicks > 0 || seqGetStatus(actor) < 0))
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, pXSprite->aiState->nextState);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
int hinder = ((pExtra->isMelee) ? 25 : 5) << 4;
|
|
|
|
if (pXSprite->health <= 0 || hinder > actor->cumulDamage) break;
|
|
|
|
pXSprite->data3 = actor->cumulDamage;
|
|
|
|
RecoilDude(actor);
|
|
|
|
break;
|
|
|
|
}
|
2021-05-05 10:55:52 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
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);
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
if (pXSprite->health > 0 && ((pDudeInfo->hinderDamage << 4) <= actor->cumulDamage))
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
|
|
|
pXSprite->data3 = actor->cumulDamage;
|
|
|
|
RecoilDude(actor);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-09-16 18:49:25 +00:00
|
|
|
|
|
|
|
it.Reset(kStatDude);
|
|
|
|
while (auto actor = it.Next())
|
|
|
|
{
|
|
|
|
actor->cumulDamage = 0;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void aiInit(void)
|
|
|
|
{
|
2021-05-05 10:55:52 +00:00
|
|
|
BloodStatIterator it(kStatDude);
|
|
|
|
while (auto actor = it.Next())
|
2021-09-16 19:59:39 +00:00
|
|
|
{
|
2021-05-05 10:55:52 +00:00
|
|
|
aiInitSprite(actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
void aiInitSprite(DBloodActor* actor)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-05-05 10:55:52 +00:00
|
|
|
auto pSprite = &actor->s();
|
|
|
|
auto pXSprite = &actor->x();
|
2021-09-16 19:59:39 +00:00
|
|
|
int nSector = pSprite->sectnum;
|
|
|
|
int nXSector = sector[nSector].extra;
|
2021-05-05 10:55:52 +00:00
|
|
|
XSECTOR* pXSector = NULL;
|
2021-09-16 19:59:39 +00:00
|
|
|
if (nXSector > 0)
|
|
|
|
pXSector = &xsector[nXSector];
|
2021-05-05 10:55:52 +00:00
|
|
|
DUDEEXTRA* pDudeExtra = &actor->dudeExtra;
|
2021-09-16 19:59:39 +00:00
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtra->teslaHit = 0;
|
|
|
|
pDudeExtra->time = 0;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
|
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:11:26 +00:00
|
|
|
int stateTimer = -1;
|
2021-09-16 19:59:39 +00:00
|
|
|
int targetX = 0, targetY = 0, targetZ = 0;
|
2021-09-16 19:11:26 +00:00
|
|
|
DBloodActor* pTargetMarker = nullptr;
|
2021-05-05 10:55:52 +00:00
|
|
|
|
2021-09-16 19:59:39 +00:00
|
|
|
// dude patrol init
|
2021-05-05 10:55:52 +00:00
|
|
|
if (gModernMap)
|
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
// must keep it in case of loading save
|
|
|
|
if (pXSprite->dudeFlag4 && actor->GetTarget() && actor->GetTarget()->s().type == kMarkerPath)
|
2021-09-16 19:11:26 +00:00
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
stateTimer = pXSprite->stateTimer;
|
2021-09-16 19:11:26 +00:00
|
|
|
pTargetMarker = actor->GetTarget();
|
2021-09-16 19:59:39 +00:00
|
|
|
targetX = pXSprite->targetX;
|
2021-09-16 19:11:26 +00:00
|
|
|
targetY = pXSprite->targetY;
|
2021-09-16 19:59:39 +00:00
|
|
|
targetZ = pXSprite->targetZ;
|
|
|
|
}
|
|
|
|
}
|
2021-05-05 10:55:52 +00:00
|
|
|
#endif
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
switch (pSprite->type)
|
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeCultistTommy:
|
|
|
|
case kDudeCultistShotgun:
|
|
|
|
case kDudeCultistTesla:
|
|
|
|
case kDudeCultistTNT:
|
|
|
|
case kDudeCultistBeast:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &cultistIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeCultistTommyProne:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &fanaticProneIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeCultistShotgunProne:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &cultistProneIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieButcher: {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &zombieFIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeNormal: {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &zombieAIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeLaying:
|
|
|
|
{
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &zombieSIdle);
|
|
|
|
pSprite->flags &= ~1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeZombieAxeBuried: {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &zombieEIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGargoyleFlesh:
|
|
|
|
case kDudeGargoyleStone: {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &gargoyleFIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGargoyleStatueFlesh:
|
|
|
|
case kDudeGargoyleStatueStone:
|
|
|
|
aiNewState(actor, &gargoyleStatueIdle);
|
|
|
|
break;
|
|
|
|
case kDudeCerberusTwoHead: {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &cerberusIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeCerberusOneHead: {
|
|
|
|
if (!VanillaMode()) {
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &cerberus2Idle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
aiNewState(actor, &genIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeHellHound:
|
|
|
|
aiNewState(actor, &houndIdle);
|
|
|
|
break;
|
|
|
|
case kDudeHand:
|
|
|
|
aiNewState(actor, &handIdle);
|
|
|
|
break;
|
|
|
|
case kDudePhantasm:
|
|
|
|
{
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &ghostIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeInnocent:
|
|
|
|
aiNewState(actor, &innocentIdle);
|
|
|
|
break;
|
|
|
|
case kDudeRat:
|
|
|
|
aiNewState(actor, &ratIdle);
|
|
|
|
break;
|
|
|
|
case kDudeBoneEel:
|
|
|
|
{
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &eelIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeGillBeast:
|
|
|
|
aiNewState(actor, &gillBeastIdle);
|
|
|
|
break;
|
|
|
|
case kDudeBat:
|
|
|
|
{
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
aiNewState(actor, &batIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeSpiderBrown:
|
|
|
|
case kDudeSpiderRed:
|
|
|
|
case kDudeSpiderBlack:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &spidIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeSpiderMother:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
pDudeExtraE->birthCounter = 0;
|
|
|
|
aiNewState(actor, &spidIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeTchernobog:
|
|
|
|
{
|
|
|
|
pDudeExtraE->active = 0;
|
|
|
|
pDudeExtraE->thinkTime = 0;
|
|
|
|
aiNewState(actor, &tchernobogIdle);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeTinyCaleb:
|
|
|
|
aiNewState(actor, &tinycalebIdle);
|
|
|
|
break;
|
|
|
|
case kDudeBeast:
|
|
|
|
aiNewState(actor, &beastIdle);
|
|
|
|
break;
|
|
|
|
case kDudePodGreen:
|
|
|
|
case kDudePodFire:
|
|
|
|
aiNewState(actor, &podIdle);
|
|
|
|
break;
|
|
|
|
case kDudeTentacleGreen:
|
|
|
|
case kDudeTentacleFire:
|
|
|
|
aiNewState(actor, &tentacleIdle);
|
|
|
|
break;
|
2021-09-19 14:34:01 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudeModernCustom:
|
|
|
|
case kDudeModernCustomBurning:
|
|
|
|
if (!gModernMap) break;
|
2021-05-06 08:55:56 +00:00
|
|
|
aiGenDudeInitSprite(actor);
|
2021-05-06 08:51:39 +00:00
|
|
|
genDudePrepare(actor, kGenDudePropertyAll);
|
2021-09-16 19:59:39 +00:00
|
|
|
break;
|
2021-09-19 14:34:01 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
default:
|
|
|
|
aiNewState(actor, &genIdle);
|
|
|
|
break;
|
|
|
|
}
|
2021-05-05 10:55:52 +00:00
|
|
|
aiSetTarget(actor, 0, 0, 0);
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->stateTimer = 0;
|
|
|
|
switch (pSprite->type)
|
|
|
|
{
|
|
|
|
case kDudeSpiderBrown:
|
|
|
|
case kDudeSpiderRed:
|
|
|
|
case kDudeSpiderBlack:
|
2021-05-05 10:55:52 +00:00
|
|
|
if (pSprite->cstat & 8) pSprite->flags |= 9;
|
2021-09-16 19:59:39 +00:00
|
|
|
else pSprite->flags = 15;
|
|
|
|
break;
|
|
|
|
case kDudeGargoyleFlesh:
|
|
|
|
case kDudeGargoyleStone:
|
|
|
|
case kDudePhantasm:
|
|
|
|
case kDudeBoneEel:
|
|
|
|
case kDudeBat:
|
|
|
|
pSprite->flags |= 9;
|
|
|
|
break;
|
|
|
|
case kDudeGillBeast:
|
|
|
|
if (pXSector && pXSector->Underwater) pSprite->flags |= 9;
|
|
|
|
else pSprite->flags = 15;
|
|
|
|
break;
|
|
|
|
case kDudeZombieAxeBuried:
|
|
|
|
case kDudeZombieAxeLaying:
|
|
|
|
pSprite->flags = 7;
|
|
|
|
break;
|
2021-05-05 10:55:52 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2021-09-16 19:59:39 +00:00
|
|
|
case kDudePodMother: // FakeDude type
|
|
|
|
if (gModernMap) break;
|
|
|
|
[[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;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
[[fallthrough]];
|
|
|
|
// go default
|
2021-05-05 10:55:52 +00:00
|
|
|
#endif
|
2021-09-16 19:59:39 +00:00
|
|
|
default:
|
|
|
|
pSprite->flags = 15;
|
|
|
|
break;
|
|
|
|
}
|
2020-12-06 20:56:09 +00:00
|
|
|
|
2021-05-05 10:55:52 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
|
|
|
if (gModernMap)
|
|
|
|
{
|
|
|
|
if (pXSprite->dudeFlag4)
|
|
|
|
{
|
2021-09-16 19:59:39 +00:00
|
|
|
// restore dude's path
|
2021-09-16 19:11:26 +00:00
|
|
|
if (pTargetMarker)
|
2021-05-05 10:55:52 +00:00
|
|
|
{
|
2021-09-16 19:11:26 +00:00
|
|
|
actor->SetTarget(pTargetMarker);
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->targetX = targetX;
|
|
|
|
pXSprite->targetY = targetY;
|
|
|
|
pXSprite->targetZ = targetZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset target spot progress
|
|
|
|
pXSprite->data3 = 0;
|
|
|
|
|
|
|
|
// make dude follow the markers
|
2021-05-05 18:40:31 +00:00
|
|
|
bool uwater = spriteIsUnderwater(actor);
|
2021-08-29 12:36:40 +00:00
|
|
|
if (actor->GetTarget() == nullptr || actor->GetTarget()->s().type != kMarkerPath)
|
2021-09-01 19:52:54 +00:00
|
|
|
{
|
|
|
|
actor->SetTarget(nullptr);
|
2021-08-29 13:57:42 +00:00
|
|
|
aiPatrolSetMarker(actor);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
|
2021-08-29 12:36:40 +00:00
|
|
|
if (stateTimer > 0)
|
|
|
|
{
|
|
|
|
if (uwater) aiPatrolState(actor, kAiStatePatrolWaitW);
|
|
|
|
else if (pXSprite->unused1 & kDudeFlagCrouch) aiPatrolState(actor, kAiStatePatrolWaitC);
|
|
|
|
else aiPatrolState(actor, kAiStatePatrolWaitL);
|
2021-09-16 19:59:39 +00:00
|
|
|
pXSprite->stateTimer = stateTimer; // restore state timer
|
|
|
|
}
|
2021-08-29 12:36:40 +00:00
|
|
|
else if (uwater) aiPatrolState(actor, kAiStatePatrolMoveW);
|
|
|
|
else if (pXSprite->unused1 & kDudeFlagCrouch) aiPatrolState(actor, kAiStatePatrolMoveC);
|
|
|
|
else aiPatrolState(actor, kAiStatePatrolMoveL);
|
2021-09-16 19:59:39 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-05 10:55:52 +00:00
|
|
|
#endif
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2019-09-22 06:39:22 +00:00
|
|
|
END_BLD_NS
|