2019-09-19 22:42:45 +00:00
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
Copyright (C) 2010-2019 EDuke32 developers and contributors
|
|
|
|
Copyright (C) 2019 Nuke.YKT
|
|
|
|
Copyright (C) NoOne
|
|
|
|
|
|
|
|
This file is part of NBlood.
|
|
|
|
|
|
|
|
NBlood is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public License version 2
|
|
|
|
as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
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-12-09 14:56:32 +00:00
|
|
|
#include "raze_sound.h"
|
2020-01-26 11:19:01 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
#include "blood.h"
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2020-12-09 14:56:32 +00:00
|
|
|
#ifdef NOONE_EXTENSIONS
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
|
2019-09-22 06:39:22 +00:00
|
|
|
BEGIN_BLD_NS
|
2020-11-07 14:16:12 +00:00
|
|
|
static void ThrowThing(DBloodActor*, bool);
|
2020-11-06 21:48:22 +00:00
|
|
|
static void unicultThinkSearch(DBloodActor*);
|
|
|
|
static void unicultThinkGoto(DBloodActor*);
|
|
|
|
static void unicultThinkChase(DBloodActor*);
|
|
|
|
static void forcePunch(DBloodActor*);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
AISTATE genDudeIdleL = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
|
|
|
|
AISTATE genDudeIdleW = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
|
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeSearchL = { kAiStateSearch, 9, -1, 600, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleL };
|
|
|
|
AISTATE genDudeSearchW = { kAiStateSearch, 13, -1, 600, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleW };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeSearchShortL = { kAiStateSearch, 9, -1, 200, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleL };
|
|
|
|
AISTATE genDudeSearchShortW = { kAiStateSearch, 13, -1, 200, NULL, aiGenDudeMoveForward, unicultThinkSearch, &genDudeIdleW };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeSearchNoWalkL = { kAiStateSearch, 0, -1, 600, NULL, aiMoveTurn, unicultThinkSearch, &genDudeIdleL };
|
|
|
|
AISTATE genDudeSearchNoWalkW = { kAiStateSearch, 13, -1, 600, NULL, aiMoveTurn, unicultThinkSearch, &genDudeIdleW };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeGotoL = { kAiStateMove, 9, -1, 600, NULL, aiGenDudeMoveForward, unicultThinkGoto, &genDudeIdleL };
|
|
|
|
AISTATE genDudeGotoW = { kAiStateMove, 13, -1, 600, NULL, aiGenDudeMoveForward, unicultThinkGoto, &genDudeIdleW };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
|
|
|
AISTATE genDudeDodgeL = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &genDudeChaseL };
|
|
|
|
AISTATE genDudeDodgeD = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &genDudeChaseD };
|
|
|
|
AISTATE genDudeDodgeW = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &genDudeChaseW };
|
|
|
|
// ---------------------
|
|
|
|
AISTATE genDudeDodgeShortL = { kAiStateMove, 9, -1, 60, NULL, aiMoveDodge, NULL, &genDudeChaseL };
|
|
|
|
AISTATE genDudeDodgeShortD = { kAiStateMove, 14, -1, 60, NULL, aiMoveDodge, NULL, &genDudeChaseD };
|
|
|
|
AISTATE genDudeDodgeShortW = { kAiStateMove, 13, -1, 60, NULL, aiMoveDodge, NULL, &genDudeChaseW };
|
|
|
|
// ---------------------
|
2019-12-05 20:42:35 +00:00
|
|
|
AISTATE genDudeDodgeShorterL = { kAiStateMove, 9, -1, 20, NULL, aiMoveDodge, NULL, &genDudeChaseL };
|
|
|
|
AISTATE genDudeDodgeShorterD = { kAiStateMove, 14, -1, 20, NULL, aiMoveDodge, NULL, &genDudeChaseD };
|
|
|
|
AISTATE genDudeDodgeShorterW = { kAiStateMove, 13, -1, 20, NULL, aiMoveDodge, NULL, &genDudeChaseW };
|
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeChaseL = { kAiStateChase, 9, -1, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL };
|
|
|
|
AISTATE genDudeChaseD = { kAiStateChase, 14, -1, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL };
|
|
|
|
AISTATE genDudeChaseW = { kAiStateChase, 13, -1, 0, NULL, aiGenDudeMoveForward, unicultThinkChase, NULL };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeChaseNoWalkL = { kAiStateChase, 0, -1, 0, NULL, aiMoveTurn, unicultThinkChase, NULL };
|
|
|
|
AISTATE genDudeChaseNoWalkD = { kAiStateChase, 14, -1, 0, NULL, aiMoveTurn, unicultThinkChase, NULL };
|
|
|
|
AISTATE genDudeChaseNoWalkW = { kAiStateChase, 13, -1, 0, NULL, aiMoveTurn, unicultThinkChase, NULL };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------
|
2020-10-11 09:56:27 +00:00
|
|
|
AISTATE genDudeFireL = { kAiStateChase, 6, nGenDudeAttack1, 0, NULL, aiMoveTurn, unicultThinkChase, &genDudeFireL };
|
|
|
|
AISTATE genDudeFireD = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, unicultThinkChase, &genDudeFireD };
|
|
|
|
AISTATE genDudeFireW = { kAiStateChase, 8, nGenDudeAttack1, 0, NULL, aiMoveTurn, unicultThinkChase, &genDudeFireW };
|
2019-11-24 20:53:51 +00:00
|
|
|
// ---------------------z
|
|
|
|
AISTATE genDudeRecoilL = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseL };
|
|
|
|
AISTATE genDudeRecoilD = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseD };
|
|
|
|
AISTATE genDudeRecoilW = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &genDudeChaseW };
|
|
|
|
AISTATE genDudeRecoilTesla = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &genDudeDodgeShortL };
|
|
|
|
// ---------------------
|
|
|
|
AISTATE genDudeThrow = { kAiStateChase, 7, nGenDudeThrow1, 0, NULL, NULL, NULL, &genDudeChaseL };
|
|
|
|
AISTATE genDudeThrow2 = { kAiStateChase, 7, nGenDudeThrow2, 0, NULL, NULL, NULL, &genDudeChaseL };
|
|
|
|
// ---------------------
|
|
|
|
AISTATE genDudePunch = { kAiStateChase,10, nGenDudePunch, 0, NULL, NULL, forcePunch, &genDudeChaseL };
|
2019-09-19 22:42:45 +00:00
|
|
|
// ---------------------
|
|
|
|
|
2020-02-11 23:22:11 +00:00
|
|
|
const GENDUDESND gCustomDudeSnd[] = {
|
2021-12-29 19:03:42 +00:00
|
|
|
{ 1003, 2, 0, true, false }, // spot sound
|
|
|
|
{ 1013, 2, 2, true, true }, // pain sound
|
|
|
|
{ 1018, 2, 4, false, true }, // death sound
|
|
|
|
{ 1031, 2, 6, true, true }, // burning state sound
|
|
|
|
{ 1018, 2, 8, false, true }, // explosive death or end of burning state sound
|
|
|
|
{ 4021, 2, 10, true, false }, // target of dude is dead
|
|
|
|
{ 1005, 2, 12, true, false }, // chase sound
|
|
|
|
{ -1, 0, 14, false, true }, // weapon attack
|
|
|
|
{ -1, 0, 15, false, true }, // throw attack
|
|
|
|
{ -1, 0, 16, false, true }, // melee attack
|
|
|
|
{ 9008, 0, 17, false, false }, // transforming in other dude
|
2019-09-21 11:02:17 +00:00
|
|
|
};
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-07-19 21:15:26 +00:00
|
|
|
// for kModernThingThrowableRock
|
2021-11-16 17:15:56 +00:00
|
|
|
const int16_t gCustomDudeDebrisPics[6] = {
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
2406, 2280, 2185, 2155, 2620, 3135
|
2021-07-19 21:15:26 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
static void forcePunch(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor->genDudeExtra.forcePunch && seqGetStatus(actor) == -1)
|
|
|
|
punchCallback(0, actor);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2022-09-29 10:33:07 +00:00
|
|
|
static bool genDudeAdjustSlope(DBloodActor* actor, double dist, int weaponType, int by = 64)
|
2020-11-07 15:13:03 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor->GetTarget() != nullptr)
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
double fStart = 0;
|
|
|
|
double fEnd = 0;
|
2021-12-29 19:03:42 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1;
|
|
|
|
|
|
|
|
for (int i = -8191; i < 8192; i += by)
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
double ii = i / 16384.;
|
2022-09-29 10:33:07 +00:00
|
|
|
HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.angle.ToVector(), ii), clipMask, dist * 16); // this originally passed a badly scaled 'dist'.
|
2022-09-28 16:20:26 +00:00
|
|
|
if (!fStart && actor->GetTarget() == gHitInfo.actor()) fStart = ii;
|
2021-12-29 19:03:42 +00:00
|
|
|
else if (fStart && actor->GetTarget() != gHitInfo.actor())
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
fEnd = ii;
|
2021-12-29 19:03:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fStart != fEnd)
|
|
|
|
{
|
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
actor->dudeSlope = fStart - ((fStart - fEnd) * 0.25);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
|
|
|
const MissileType* pMissile = &missileInfo[pExtra->curWeapon - kMissileBase];
|
2022-09-28 16:20:26 +00:00
|
|
|
actor->dudeSlope = (fStart - ((fStart - fEnd) * 0.25)) - (pMissile->fClipDist()) / 2048;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2019-12-05 20:42:35 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-08-31 20:01:28 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-08-31 20:01:28 +00:00
|
|
|
void genDudeUpdate(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
for (int i = 0; i < kGenDudePropertyMax; i++) {
|
|
|
|
if (pExtra->updReq[i]) genDudePrepare(actor, i);
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
void punchCallback(int, DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
auto const target = actor->GetTarget();
|
|
|
|
if (target != nullptr)
|
|
|
|
{
|
2022-10-07 21:33:37 +00:00
|
|
|
double nZOffset1 = getDudeInfo(actor->spr.type)->eyeHeight * actor->spr.scale.Y;
|
2022-09-28 13:36:25 +00:00
|
|
|
double nZOffset2 = 0;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-05-05 15:25:37 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
if (target->IsDudeActor())
|
2022-10-07 21:33:37 +00:00
|
|
|
nZOffset2 = getDudeInfo(target->spr.type)->eyeHeight * target->spr.scale.Y;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackMelee))
|
|
|
|
sfxPlay3DSound(actor, 530, 1, 0);
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2022-09-28 13:36:25 +00:00
|
|
|
actFireVector(actor, 0, 0, DVector3(actor->spr.angle.ToVector() * 64, nZOffset1 - nZOffset2), kVectorGenDudePunch);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
void genDudeAttack1(int, DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor->GetTarget() == nullptr) return;
|
|
|
|
|
2022-09-28 13:36:25 +00:00
|
|
|
DVector3 dv;
|
2022-09-03 07:45:23 +00:00
|
|
|
actor->ZeroVelocityXY();
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
int dispersion = pExtra->baseDispersion;
|
|
|
|
if (inDuck(actor->xspr.aiState))
|
|
|
|
dispersion = ClipLow(dispersion >> 1, kGenDudeMinDispesion);
|
|
|
|
|
|
|
|
if (pExtra->weaponType == kGenDudeWeaponHitscan)
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
dv = DVector3(actor->spr.angle.ToVector(), actor->dudeSlope);
|
2021-12-29 19:03:42 +00:00
|
|
|
// dispersal modifiers here in case if non-melee enemy
|
|
|
|
if (!dudeIsMelee(actor))
|
|
|
|
{
|
2022-09-28 13:36:25 +00:00
|
|
|
dv.X += Random3F(dispersion, 14);
|
|
|
|
dv.Y += Random3F(dispersion, 14);
|
|
|
|
dv.Z += Random3F(dispersion, 14);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
2022-09-28 13:36:25 +00:00
|
|
|
actFireVector(actor, 0, 0, dv, (VECTOR_TYPE)pExtra->curWeapon);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
|
|
|
sfxPlayVectorSound(actor, pExtra->curWeapon);
|
|
|
|
}
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponSummon)
|
|
|
|
{
|
|
|
|
DBloodActor* spawned = nullptr;
|
2022-10-04 17:06:49 +00:00
|
|
|
double dist = actor->clipdist * 4;
|
2021-12-29 19:03:42 +00:00
|
|
|
if (pExtra->slaveCount <= gGameOptions.nDifficulty)
|
|
|
|
{
|
2022-09-25 20:33:40 +00:00
|
|
|
if ((spawned = actSpawnDude(actor, pExtra->curWeapon, dist + RandomD(dist, 4))) != NULL)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
spawned->SetOwner(actor);
|
|
|
|
|
|
|
|
if (spawned->hasX())
|
|
|
|
{
|
|
|
|
spawned->SetTarget(actor->GetTarget());
|
|
|
|
if (spawned->GetTarget() != nullptr)
|
|
|
|
aiActivateDude(spawned);
|
|
|
|
}
|
|
|
|
|
2022-05-05 23:08:09 +00:00
|
|
|
gKillMgr.AddKill(spawned);
|
2021-12-29 19:03:42 +00:00
|
|
|
pExtra->slave[pExtra->slaveCount++] = spawned;
|
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
|
|
|
sfxPlay3DSoundCP(actor, 379, 1, 0, 0x10000 - Random3(0x3000));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
2022-09-28 16:20:26 +00:00
|
|
|
dv = DVector3(actor->spr.angle.ToVector(), actor->dudeSlope);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// dispersal modifiers here
|
2022-09-28 13:36:25 +00:00
|
|
|
dv.X += Random3F(dispersion, 14);
|
|
|
|
dv.Y += Random3F(dispersion, 14);
|
|
|
|
dv.Z += Random3F(dispersion >> 1, 14);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-28 13:36:25 +00:00
|
|
|
actFireMissile(actor, 0, 0, dv, pExtra->curWeapon);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
|
|
|
sfxPlayMissileSound(actor, pExtra->curWeapon);
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
void ThrowCallback1(int, DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
ThrowThing(actor, true);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
void ThrowCallback2(int, DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
ThrowThing(actor, false);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
static void ThrowThing(DBloodActor* actor, bool impact)
|
2020-11-07 14:16:12 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
auto target = actor->GetTarget();
|
|
|
|
|
|
|
|
if (target == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!(target->spr.type >= kDudeBase && target->spr.type < kDudeMax))
|
|
|
|
return;
|
|
|
|
|
|
|
|
int curWeapon = actor->genDudeExtra.curWeapon;
|
|
|
|
int weaponType = actor->genDudeExtra.weaponType;
|
|
|
|
if (weaponType != kGenDudeWeaponThrow) return;
|
|
|
|
|
|
|
|
const THINGINFO* pThinkInfo = &thingInfo[curWeapon - kThingBase];
|
|
|
|
if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return;
|
|
|
|
else if (!playGenDudeSound(actor, kGenDudeSndAttackThrow))
|
|
|
|
sfxPlay3DSound(actor, 455, -1, 0);
|
|
|
|
|
2022-09-28 10:54:54 +00:00
|
|
|
double zThrow = 14500 / 65536.;
|
|
|
|
auto dv = target->spr.pos - actor->spr.pos;
|
|
|
|
double dist = dv.Length();
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
|
|
|
|
switch (curWeapon) {
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
case kThingDroppedLifeLeech:
|
2022-09-28 10:54:54 +00:00
|
|
|
zThrow = 5000 / 65536;
|
2021-12-29 19:03:42 +00:00
|
|
|
// pickup life leech before throw it again
|
|
|
|
if (actLeech != NULL) removeLeech(actLeech);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBloodActor* spawned = nullptr;
|
2022-09-28 10:54:54 +00:00
|
|
|
if ((spawned = actFireThing(actor, 0., 0., (dv.Z / 32768.) - zThrow, curWeapon, dist * (2048. / 64800))) == nullptr) return;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
if (pThinkInfo->picnum < 0 && spawned->spr.type != kModernThingThrowableRock) spawned->spr.picnum = 0;
|
|
|
|
|
|
|
|
spawned->SetOwner(actor);
|
|
|
|
|
|
|
|
switch (curWeapon) {
|
|
|
|
case kThingNapalmBall:
|
2022-10-07 21:52:29 +00:00
|
|
|
spawned->spr.scale = DVector2(0.375, 0.375);
|
2021-12-29 19:03:42 +00:00
|
|
|
spawned->xspr.data4 = 3 + gGameOptions.nDifficulty;
|
|
|
|
impact = true;
|
|
|
|
break;
|
|
|
|
case kModernThingThrowableRock:
|
2022-10-06 16:28:42 +00:00
|
|
|
{
|
|
|
|
double s = 0.375 + Random(42) * REPEAT_SCALE;
|
2021-12-29 19:03:42 +00:00
|
|
|
spawned->spr.picnum = gCustomDudeDebrisPics[Random(5)];
|
2022-10-07 21:52:29 +00:00
|
|
|
spawned->spr.scale = DVector2(s, s);
|
2021-12-29 19:03:42 +00:00
|
|
|
spawned->spr.cstat |= CSTAT_SPRITE_BLOCK;
|
|
|
|
spawned->spr.pal = 5;
|
|
|
|
|
|
|
|
if (Chance(0x5000)) spawned->spr.cstat |= CSTAT_SPRITE_XFLIP;
|
|
|
|
if (Chance(0x5000)) spawned->spr.cstat |= CSTAT_SPRITE_YFLIP;
|
|
|
|
|
2022-10-07 21:33:37 +00:00
|
|
|
if (spawned->spr.scale.X > 0.9375 ) spawned->xspr.data1 = 43;
|
|
|
|
else if (spawned->spr.scale.X > 0.625) spawned->xspr.data1 = 33;
|
|
|
|
else if (spawned->spr.scale.X > 0.46875) spawned->xspr.data1 = 23;
|
2021-12-29 19:03:42 +00:00
|
|
|
else spawned->xspr.data1 = 12;
|
|
|
|
return;
|
2022-10-06 16:28:42 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
case kThingTNTBarrel:
|
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedSpray:
|
|
|
|
impact = false;
|
|
|
|
break;
|
|
|
|
case kModernThingTNTProx:
|
|
|
|
spawned->xspr.state = 0;
|
|
|
|
spawned->xspr.Proximity = true;
|
|
|
|
return;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
if (actLeech != nullptr) spawned->xspr.health = actLeech->xspr.health;
|
|
|
|
else spawned->xspr.health = ((pThinkInfo->startHealth << 4) * gGameOptions.nDifficulty) >> 1;
|
|
|
|
|
|
|
|
sfxPlay3DSound(actor, 490, -1, 0);
|
|
|
|
|
|
|
|
spawned->xspr.data3 = 512 / (gGameOptions.nDifficulty + 1);
|
|
|
|
spawned->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
|
|
|
|
spawned->spr.pal = 6;
|
2022-10-04 17:06:49 +00:00
|
|
|
spawned->clipdist = 0;
|
2021-12-29 19:03:42 +00:00
|
|
|
spawned->SetTarget(actor->GetTarget());
|
|
|
|
spawned->xspr.Proximity = true;
|
|
|
|
spawned->xspr.stateTimer = 1;
|
|
|
|
|
|
|
|
actor->genDudeExtra.pLifeLeech = spawned;
|
|
|
|
evPostActor(spawned, 80, kCallbackLeechStateTimer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (impact == true && dist <= 7680) spawned->xspr.Impact = true;
|
|
|
|
else {
|
|
|
|
spawned->xspr.Impact = false;
|
2022-08-10 21:45:29 +00:00
|
|
|
evPostActor(spawned, 120 * Random(2) + 120, kCmdOn, actor);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
static void unicultThinkSearch(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
// TO DO: if can't see the target, but in fireDist range - stop moving and look around
|
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
aiChooseDirection(actor, actor->xspr.goalAng);
|
2021-12-29 19:03:42 +00:00
|
|
|
aiLookForTarget(actor);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
static void unicultThinkGoto(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax))
|
|
|
|
{
|
|
|
|
Printf(PRINT_HIGH, "actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-23 20:29:05 +00:00
|
|
|
auto dvec = actor->xspr.TargetPos.XY() - actor->spr.pos.XY();
|
2022-09-30 11:36:50 +00:00
|
|
|
DAngle nAngle = dvec.Angle();
|
2022-09-27 20:24:30 +00:00
|
|
|
double nDist = dvec.Length();
|
|
|
|
aiChooseDirection(actor, nAngle);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// if reached target, change to search mode
|
2022-09-27 20:24:30 +00:00
|
|
|
if (nDist < 320 && absangle(actor->spr.angle, nAngle) < getDudeInfo(actor->spr.type)->Periphery())
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
|
|
|
}
|
|
|
|
aiThinkTarget(actor);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
static void unicultThinkChase(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor->spr.type < kDudeBase || actor->spr.type >= kDudeMax) return;
|
|
|
|
|
|
|
|
auto const target = actor->GetTarget();
|
|
|
|
if (target == nullptr)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
genDudeUpdate(actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!target || !target->IsDudeActor() || !target->hasX()) // target lost
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
|
|
|
actor->SetTarget(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (target->xspr.health <= 0) // target is dead
|
|
|
|
{
|
|
|
|
PLAYER* pPlayer = NULL;
|
|
|
|
if ((!target->IsPlayerActor()) || ((pPlayer = getPlayerById(target->spr.type)) != NULL && pPlayer->fragger == actor))
|
|
|
|
{
|
|
|
|
playGenDudeSound(actor, kGenDudeSndTargetDead);
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
|
|
|
}
|
|
|
|
else if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
|
|
|
actor->SetTarget(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check target
|
2022-09-27 21:01:17 +00:00
|
|
|
auto dv = target->spr.pos.XY() - actor->spr.pos.XY();
|
|
|
|
double dist = max(dv.Length(), 1 / 256.);
|
2022-09-30 11:36:50 +00:00
|
|
|
DAngle nAngle = dv.Angle();
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// quick hack to prevent spinning around or changing attacker's sprite angle on high movement speeds
|
|
|
|
// when attacking the target. It happens because vanilla function takes in account x and y velocity,
|
|
|
|
// so i use fake velocity with fixed value and pass it as argument.
|
2022-09-03 22:38:26 +00:00
|
|
|
auto velocity = actor->vel;
|
2021-12-29 19:03:42 +00:00
|
|
|
if (inAttack(actor->xspr.aiState))
|
2022-10-04 17:25:06 +00:00
|
|
|
velocity.X = velocity.Y = max(actor->clipdist, 0.5) / 32768;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-27 21:01:17 +00:00
|
|
|
aiGenDudeChooseDirection(actor, nAngle, velocity);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
if (!pExtra->canAttack)
|
|
|
|
{
|
|
|
|
if (pExtra->canWalk) aiSetTarget(actor, actor); // targeting self???
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (target->IsPlayerActor())
|
|
|
|
{
|
|
|
|
PLAYER* pPlayer = &gPlayer[target->spr.type - kDudePlayer1];
|
|
|
|
if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
|
|
|
actor->SetTarget(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
|
2022-09-27 21:01:17 +00:00
|
|
|
DAngle losAngle = absangle(actor->spr.angle, nAngle);
|
2022-10-07 21:33:37 +00:00
|
|
|
double height = (pDudeInfo->eyeHeight * actor->spr.scale.Y);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-27 21:01:17 +00:00
|
|
|
if (dist > pDudeInfo->SeeDist() || !cansee(target->spr.pos, target->sector(),
|
2022-08-22 16:37:46 +00:00
|
|
|
actor->spr.pos.plusZ(-height), actor->sector()))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
|
|
|
actor->SetTarget(nullptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// is the target visible?
|
2022-09-27 21:01:17 +00:00
|
|
|
if (dist < pDudeInfo->SeeDist() && losAngle <= pDudeInfo->Periphery())
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if ((PlayClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(actor, false))
|
|
|
|
playGenDudeSound(actor, kGenDudeSndChasing);
|
|
|
|
|
2022-09-28 16:20:26 +00:00
|
|
|
actor->dudeSlope = dist == 0 ? 0 : target->spr.pos.Z - actor->spr.pos.Z / dist;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
int curWeapon = actor->genDudeExtra.curWeapon;
|
|
|
|
int weaponType = actor->genDudeExtra.weaponType;
|
|
|
|
|
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
|
|
|
|
const VECTORDATA* meleeVector = &gVectorData[22];
|
|
|
|
if (weaponType == kGenDudeWeaponThrow)
|
|
|
|
{
|
2022-09-27 21:01:17 +00:00
|
|
|
if (losAngle < DAngle15)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
2022-09-27 21:01:17 +00:00
|
|
|
else if (dist < 766.5 && dist > 480 && !spriteIsUnderwater(actor, false) && curWeapon != kModernThingEnemyLifeLeech)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-09-27 21:01:17 +00:00
|
|
|
int pHit = HitScan(actor, actor->spr.pos.Z, DVector3(dv, 0), 16777280, 0);
|
2021-12-29 19:03:42 +00:00
|
|
|
switch (pHit) {
|
|
|
|
case 0:
|
|
|
|
case 4:
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
aiGenDudeNewState(actor, &genDudeThrow);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2022-09-27 21:01:17 +00:00
|
|
|
else if (dist > 254.5 && dist <= 692 && !spriteIsUnderwater(actor, false) && !actor->GetSpecialOwner())
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
switch (curWeapon)
|
|
|
|
{
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
{
|
|
|
|
if (actLeech == nullptr)
|
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
|
|
|
genDudeThrow2.nextState = &genDudeDodgeShortL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ldist = aiFightGetTargetDist(target, pDudeInfo, actLeech);
|
2022-08-22 16:37:46 +00:00
|
|
|
if (ldist > 3 || !cansee(target->spr.pos, target->sector(),
|
|
|
|
actLeech->spr.pos, actLeech->sector()) || actLeech->GetTarget() == nullptr)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
|
|
|
genDudeThrow2.nextState = &genDudeDodgeShortL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
genDudeThrow2.nextState = &genDudeChaseL;
|
2022-09-27 21:01:17 +00:00
|
|
|
if (dist > 317 && Chance(0x5000))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (!canDuck(actor) || Chance(0x4000)) aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortD);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
case kModernThingThrowableRock:
|
|
|
|
if (Chance(0x4000)) aiGenDudeNewState(actor, &genDudeThrow2);
|
|
|
|
else playGenDudeSound(actor, kGenDudeSndTargetSpot);
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2022-09-29 11:51:33 +00:00
|
|
|
else if (dist <= meleeVector->fMaxDist())
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
if (spriteIsUnderwater(actor, false))
|
|
|
|
{
|
|
|
|
if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeW);
|
|
|
|
}
|
|
|
|
else if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeL);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int state = checkAttackState(actor);
|
|
|
|
if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (state == 2)
|
|
|
|
{
|
|
|
|
if (Chance(0x5000)) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
}
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-28 12:24:43 +00:00
|
|
|
double vdist;
|
|
|
|
double mdist;
|
|
|
|
double defDist;
|
|
|
|
|
2022-09-29 12:01:59 +00:00
|
|
|
vdist = mdist = defDist = actor->genDudeExtra.fireDist;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
|
|
|
{
|
2022-09-29 11:51:33 +00:00
|
|
|
if ((vdist = gVectorData[curWeapon].fMaxDist()) <= 0)
|
2022-09-28 12:24:43 +00:00
|
|
|
vdist = mdist;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponSummon)
|
|
|
|
{
|
|
|
|
// don't attack slaves
|
|
|
|
if (actor->GetTarget() != nullptr && actor->GetTarget()->GetOwner() == actor)
|
|
|
|
{
|
2022-08-22 16:41:41 +00:00
|
|
|
aiSetTarget(actor, actor->spr.pos);
|
2021-12-29 19:03:42 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-09-29 11:51:33 +00:00
|
|
|
else if (actor->genDudeExtra.slaveCount > gGameOptions.nDifficulty || dist < meleeVector->fMaxDist())
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-09-29 11:51:33 +00:00
|
|
|
if (dist <= meleeVector->fMaxDist())
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int state = checkAttackState(actor);
|
|
|
|
if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
|
|
|
// special handling for flame, explosive and life leech missiles
|
|
|
|
int state = checkAttackState(actor);
|
|
|
|
switch (curWeapon)
|
|
|
|
{
|
|
|
|
case kMissileLifeLeechRegular:
|
|
|
|
// pickup life leech if it was thrown previously
|
|
|
|
if (actLeech != NULL) removeLeech(actLeech);
|
2022-09-28 12:24:43 +00:00
|
|
|
mdist = 1500/16.;
|
2021-12-29 19:03:42 +00:00
|
|
|
break;
|
2022-09-28 12:24:43 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
case kMissileFlareAlt:
|
2022-09-28 12:24:43 +00:00
|
|
|
mdist = 2500/16.;
|
2021-12-29 19:03:42 +00:00
|
|
|
[[fallthrough]];
|
|
|
|
case kMissileFireball:
|
|
|
|
case kMissileFireballNapalm:
|
|
|
|
case kMissileFireballCerberus:
|
|
|
|
case kMissileFireballTchernobog:
|
2022-09-28 12:24:43 +00:00
|
|
|
if (mdist == defDist) mdist = 3000/16.;
|
|
|
|
if (dist > mdist || actor->xspr.locked == 1) break;
|
2022-09-29 11:51:33 +00:00
|
|
|
else if (dist <= meleeVector->fMaxDist() && Chance(0x9000))
|
2021-12-29 19:03:42 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
2022-09-28 12:24:43 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
case kMissileFlameSpray:
|
|
|
|
case kMissileFlameHound:
|
|
|
|
//viewSetSystemMessage("%d", target->xspr.burnTime);
|
|
|
|
if (spriteIsUnderwater(actor, false))
|
|
|
|
{
|
2022-09-29 11:51:33 +00:00
|
|
|
if (dist > meleeVector->fMaxDist()) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-12-29 19:03:42 +00:00
|
|
|
else if (Chance(0x8000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortW);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-27 21:01:17 +00:00
|
|
|
else if (dist <= 250 && target->xspr.burnTime >= 2000 && target->GetBurnSource() == actor)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-09-29 11:51:33 +00:00
|
|
|
if (dist > meleeVector->fMaxDist()) aiGenDudeNewState(actor, &genDudeChaseL);
|
2021-12-29 19:03:42 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-28 12:24:43 +00:00
|
|
|
vdist = 3500/16. + (gGameOptions.nDifficulty * 25);
|
2021-12-29 19:03:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponKamikaze)
|
|
|
|
{
|
|
|
|
int nType = curWeapon - kTrapExploder;
|
|
|
|
const EXPLOSION* pExpl = &explodeInfo[nType];
|
2022-08-22 16:23:36 +00:00
|
|
|
if (CheckProximity(actor, target->spr.pos, target->sector(), pExpl->radius >> 1))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-08-23 19:25:05 +00:00
|
|
|
actor->ZeroVelocity();
|
2021-12-29 19:03:42 +00:00
|
|
|
if (doExplosion(actor, nType) && actor->xspr.health > 0)
|
|
|
|
actDamageSprite(actor, actor, kDamageExplode, 65535);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int state = checkAttackState(actor);
|
2022-09-27 21:01:17 +00:00
|
|
|
DAngle kAngle = (dudeIsMelee(actor) || dist <= 256/* kGenDudeMaxMeleeDist */) ? pDudeInfo->Periphery() : DAngle1 * 10;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-28 12:24:43 +00:00
|
|
|
if (dist < vdist && losAngle < kAngle)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (pExtra->canWalk)
|
|
|
|
{
|
2022-09-27 21:01:17 +00:00
|
|
|
double objDist = -1;
|
|
|
|
double targetDist = -1;
|
|
|
|
int hit = -1;
|
2021-12-29 19:03:42 +00:00
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
2022-09-28 16:20:26 +00:00
|
|
|
hit = HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.angle.ToVector(), actor->dudeSlope), CLIPMASK1, dist);
|
2021-12-29 19:03:42 +00:00
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
2022-09-28 16:20:26 +00:00
|
|
|
hit = HitScan(actor, actor->spr.pos.Z, DVector3(actor->spr.angle.ToVector(), actor->dudeSlope), CLIPMASK0, dist);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
if (hit >= 0)
|
|
|
|
{
|
2022-10-04 17:06:49 +00:00
|
|
|
targetDist = dist - (target->clipdist);
|
2022-09-27 21:01:17 +00:00
|
|
|
objDist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length();
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (actor != gHitInfo.actor() && targetDist > objDist)
|
|
|
|
{
|
|
|
|
DBloodActor* hitactor = nullptr;
|
|
|
|
walltype* pHWall = NULL;
|
|
|
|
XWALL* pXHWall = NULL;
|
|
|
|
bool hscn = false;
|
|
|
|
bool blck = false;
|
|
|
|
bool failed = false;
|
|
|
|
|
|
|
|
switch (hit)
|
|
|
|
{
|
|
|
|
case 3:
|
|
|
|
hitactor = gHitInfo.actor();
|
|
|
|
if (hitactor)
|
|
|
|
{
|
|
|
|
hscn = (hitactor->spr.cstat & CSTAT_SPRITE_BLOCK_HITSCAN); blck = (hitactor->spr.cstat & CSTAT_SPRITE_BLOCK);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
case 4:
|
|
|
|
pHWall = gHitInfo.hitWall;
|
|
|
|
if (pHWall->hasX()) pXHWall = &pHWall->xw();
|
|
|
|
hscn = (pHWall->cstat & CSTAT_WALL_BLOCK_HITSCAN); blck = (pHWall->cstat & CSTAT_WALL_BLOCK);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (hit) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
case 2:
|
2022-09-29 10:33:07 +00:00
|
|
|
if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(actor, dist, weaponType)
|
2022-09-27 21:01:17 +00:00
|
|
|
&& dist < (375 + RandomF(2000, 4)) && pExtra->baseDispersion < kGenDudeMaxDispersion >> 1) break;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
case 3:
|
|
|
|
if (hitactor->spr.statnum == kStatFX || hitactor->spr.statnum == kStatProjectile || hitactor->spr.statnum == kStatDebris)
|
|
|
|
break;
|
|
|
|
if (hitactor->IsDudeActor() && (weaponType != kGenDudeWeaponHitscan || hscn))
|
|
|
|
{
|
|
|
|
// dodge a bit in sides
|
2022-05-05 22:55:50 +00:00
|
|
|
if (hitactor->xspr.health > 0 && hitactor->GetTarget() != actor)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
|
|
|
|
}
|
|
|
|
else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShortW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShortD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
|
|
|
|
|
|
|
switch (hitactor->spr.type)
|
|
|
|
{
|
|
|
|
case kDudeModernCustom: // and make dude which could be hit to dodge too
|
2022-09-27 21:01:17 +00:00
|
|
|
if (!dudeIsMelee(hitactor) && Chance(int(dist * 256)))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (!inAttack(hitactor->xspr.aiState))
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(hitactor)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(hitactor, &genDudeDodgeShorterL);
|
|
|
|
|
|
|
|
// preferable in opposite sides
|
|
|
|
if (Chance(0x8000))
|
|
|
|
{
|
|
|
|
if (actor->xspr.dodgeDir == 1) hitactor->xspr.dodgeDir = -1;
|
|
|
|
else if (actor->xspr.dodgeDir == -1) hitactor->xspr.dodgeDir = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-08-23 20:27:43 +00:00
|
|
|
if (actor->spr.pos.X < hitactor->spr.pos.X)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2022-08-23 20:27:43 +00:00
|
|
|
if (actor->spr.pos.X < hitactor->spr.pos.X)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponHitscan && hscn)
|
|
|
|
{
|
2022-09-29 10:33:07 +00:00
|
|
|
if (genDudeAdjustSlope(actor, dist, weaponType)) break;
|
2022-09-28 16:20:26 +00:00
|
|
|
VectorScan(actor, 0, 0, DVector3(actor->spr.angle.ToVector(), actor->dudeSlope), dist, 1);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor == gHitInfo.actor()) break;
|
|
|
|
|
|
|
|
bool immune = nnExtIsImmune(hitactor, gVectorData[curWeapon].dmgType);
|
|
|
|
if (!(hitactor->hasX() && (!immune || (immune && hitactor->spr.statnum == kStatThing && hitactor->xspr.Vector)) && !hitactor->xspr.locked))
|
|
|
|
{
|
2022-09-26 19:25:13 +00:00
|
|
|
auto hdist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length();
|
|
|
|
if ((hdist <= 93.75 && !blck)
|
2022-09-29 12:01:59 +00:00
|
|
|
|| (dist <= (pExtra->fireDist / max(Random(4), 1u))))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
//viewSetSystemMessage("GO CHASE");
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-10-07 21:33:37 +00:00
|
|
|
double wd1 = tileWidth(hitactor->spr.picnum) * hitactor->spr.scale.X;
|
|
|
|
double wd2 = tileWidth(actor->spr.picnum) * actor->spr.scale.X;
|
2022-10-06 16:28:42 +00:00
|
|
|
if (wd1 < (wd2 * 8))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
//viewSetSystemMessage("OBJ SIZE: %d DUDE SIZE: %d", wd1, wd2);
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
|
|
|
|
|
2022-08-23 20:27:43 +00:00
|
|
|
if (actor->spr.pos.X < hitactor->spr.pos.X)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = -1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-23 20:27:43 +00:00
|
|
|
if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) actor->xspr.dodgeDir = 1;
|
2021-12-29 19:03:42 +00:00
|
|
|
else actor->xspr.dodgeDir = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (actor->hit.hit.type == kHitWall || actor->hit.hit.type == kHitSprite)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2022-09-03 22:38:26 +00:00
|
|
|
actor->xspr.goalAng = RandomAngle();
|
2021-12-29 19:03:42 +00:00
|
|
|
//viewSetSystemMessage("WALL OR SPRITE TOUCH");
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
//viewSetSystemMessage("TOO BIG OBJECT TO DODGE!!!!!!!!");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
[[fallthrough]];
|
|
|
|
case 4:
|
|
|
|
if (hit == 4 && weaponType == kGenDudeWeaponHitscan && hscn)
|
|
|
|
{
|
|
|
|
bool masked = (pHWall->cstat & CSTAT_WALL_MASKED);
|
2022-09-28 16:20:26 +00:00
|
|
|
if (masked) VectorScan(actor, 0, 0, DVector3(actor->spr.angle.ToVector(), actor->dudeSlope), dist, 1);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
if ((actor != gHitInfo.actor()) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked))
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (hit >= 3 && weaponType == kGenDudeWeaponMissile && blck)
|
|
|
|
{
|
|
|
|
switch (curWeapon) {
|
|
|
|
case kMissileLifeLeechRegular:
|
|
|
|
case kMissileTeslaAlt:
|
|
|
|
case kMissileFlareAlt:
|
|
|
|
case kMissileFireball:
|
|
|
|
case kMissileFireballNapalm:
|
|
|
|
case kMissileFireballCerberus:
|
|
|
|
case kMissileFireballTchernobog:
|
|
|
|
{
|
|
|
|
// allow attack if dude is far from object, but target is close to it
|
2022-09-28 12:24:43 +00:00
|
|
|
double dudeDist = (gHitInfo.hitpos.XY() - actor->spr.pos.XY()).Length();
|
|
|
|
double targetDist1 = (gHitInfo.hitpos.XY() - target->spr.pos.XY()).Length();
|
2021-12-29 19:03:42 +00:00
|
|
|
if (dudeDist < mdist)
|
|
|
|
{
|
|
|
|
//viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist);
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-28 12:24:43 +00:00
|
|
|
else if (targetDist1 <= mdist * 0.5)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
//viewSetSystemMessage("TARGET CLOSE TO OBJ: %d, MDIST: %d", targetDist, mdist >> 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
[[fallthrough]];
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
//viewSetSystemMessage("DEF HIT: %d, MDIST: %d", hit, mdist);
|
|
|
|
if (hit == 4) failed = (pHWall->type != kWallGib || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked);
|
|
|
|
else if (hit == 3 && (failed = (hitactor->spr.statnum != kStatThing || !hitactor->hasX() || hitactor->xspr.locked)) == false)
|
|
|
|
{
|
|
|
|
// check also for damage resistance (all possible damages missile can use)
|
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
|
|
|
{
|
|
|
|
if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(hitactor, i)) == false)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (failed)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeSearchW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aiSetTarget(actor, actor->GetTarget());
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case 1:
|
|
|
|
aiGenDudeNewState(actor, &genDudeFireW);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireW;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
aiGenDudeNewState(actor, &genDudeFireD);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireD;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
aiGenDudeNewState(actor, &genDudeFireL);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (seqGetID(actor) == actor->xspr.data2 + ((state < 3) ? 8 : 6))
|
|
|
|
{
|
|
|
|
if (state == 1) actor->xspr.aiState->nextState = &genDudeChaseW;
|
|
|
|
else if (state == 2) actor->xspr.aiState->nextState = &genDudeChaseD;
|
|
|
|
else actor->xspr.aiState->nextState = &genDudeChaseL;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (state == 1 && actor->xspr.aiState != &genDudeChaseW && actor->xspr.aiState != &genDudeFireW)
|
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireW;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (state == 2 && actor->xspr.aiState != &genDudeChaseD && actor->xspr.aiState != &genDudeFireD)
|
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireD;
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (actor->xspr.aiState != &genDudeChaseL && actor->xspr.aiState != &genDudeFireL)
|
|
|
|
{
|
|
|
|
aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
actor->xspr.aiState->nextState = &genDudeFireL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
int checkAttackState(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor, false))
|
|
|
|
{
|
|
|
|
if (!dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor, false))
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false))
|
|
|
|
{
|
|
|
|
return 1; //water
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 2; //duck
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 3; //land
|
|
|
|
}
|
|
|
|
return 0;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-08-31 19:59:32 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
static int getGenDudeMoveSpeed(DBloodActor* actor, int which, bool mul, bool shift)
|
2021-08-31 19:59:32 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
|
|
|
|
int speed = -1; int step = 2500; int maxSpeed = 146603;
|
|
|
|
switch (which)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
speed = pDudeInfo->frontSpeed;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
speed = pDudeInfo->sideSpeed;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
speed = pDudeInfo->backSpeed;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
speed = pDudeInfo->angSpeed;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (actor->xspr.busyTime > 0) speed /= 3;
|
|
|
|
if (speed > 0 && mul)
|
|
|
|
{
|
|
|
|
if (actor->xspr.busyTime > 0)
|
|
|
|
speed += (step * actor->xspr.busyTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shift) speed *= 4 >> 4;
|
|
|
|
if (speed > maxSpeed) speed = maxSpeed;
|
|
|
|
|
|
|
|
return speed;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiGenDudeMoveForward(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
|
|
|
|
if (pExtra->canFly)
|
|
|
|
{
|
2022-09-03 22:38:26 +00:00
|
|
|
auto nAng = deltaangle(actor->spr.angle, actor->xspr.goalAng);
|
2022-09-26 22:20:44 +00:00
|
|
|
auto nTurnRange = pDudeInfo->TurnRange();
|
2022-08-29 00:22:06 +00:00
|
|
|
actor->spr.angle += clamp(nAng, -nTurnRange, nTurnRange);
|
2022-09-26 22:28:50 +00:00
|
|
|
double nAccel = pDudeInfo->FrontSpeed() * 4;
|
2022-08-29 00:22:06 +00:00
|
|
|
if (abs(nAng) > DAngle60)
|
2021-12-29 19:03:42 +00:00
|
|
|
return;
|
|
|
|
if (actor->GetTarget() == nullptr)
|
2022-08-21 17:19:47 +00:00
|
|
|
actor->spr.angle += DAngle45;
|
2022-08-23 20:29:05 +00:00
|
|
|
auto dvec = actor->xspr.TargetPos.XY() - actor->spr.pos.XY();
|
2022-09-26 22:44:51 +00:00
|
|
|
double nDist = dvec.Length();
|
|
|
|
if ((unsigned int)Random(64) < 32 && nDist <= 0x40)
|
2021-12-29 19:03:42 +00:00
|
|
|
return;
|
2022-09-03 09:54:01 +00:00
|
|
|
AdjustVelocity(actor, ADJUSTER{
|
|
|
|
if (actor->GetTarget() == nullptr)
|
2022-09-26 22:28:50 +00:00
|
|
|
t1 += nAccel;
|
2022-09-03 09:54:01 +00:00
|
|
|
else
|
2022-09-26 22:28:50 +00:00
|
|
|
t1 += nAccel * 0.5;
|
2022-09-03 09:54:01 +00:00
|
|
|
});
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-11 12:02:04 +00:00
|
|
|
DAngle maxTurn = mapangle(pDudeInfo->angSpeed * 4 >> 4);
|
2022-09-03 22:38:26 +00:00
|
|
|
|
|
|
|
DAngle dang = actor->xspr.goalAng - actor->spr.angle;
|
|
|
|
actor->spr.angle += clamp(dang, -maxTurn, maxTurn);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// don't move forward if trying to turn around
|
2022-09-03 22:38:26 +00:00
|
|
|
if (abs(dang) > DAngle180 / 3)
|
2021-12-29 19:03:42 +00:00
|
|
|
return;
|
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
double frontSpeed = FixedToFloat(actor->genDudeExtra.moveSpeed);
|
|
|
|
actor->vel += actor->spr.angle.ToVector() * frontSpeed;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
void aiGenDudeChooseDirection(DBloodActor* actor, DAngle direction, const DVector2& vel)
|
2021-08-31 19:59:32 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!(actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax))
|
|
|
|
{
|
|
|
|
Printf(PRINT_HIGH, "actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TO-DO: Take in account if sprite is flip-x, so enemy select correct angle
|
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
DAngle vc = deltaangle(actor->spr.angle, direction);
|
2022-09-27 15:33:18 +00:00
|
|
|
double range = vel.dot(actor->spr.angle.ToVector()) * 120;
|
2022-09-03 22:38:26 +00:00
|
|
|
DAngle v8 = vc > nullAngle ? DAngle180 / 3 : -DAngle180 / 3;
|
|
|
|
|
|
|
|
if (CanMove(actor, actor->GetTarget(), actor->spr.angle + vc, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle + vc;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), actor->spr.angle + vc / 2, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle + vc / 2;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), actor->spr.angle - vc / 2, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle - vc / 2;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), actor->spr.angle + v8, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle + v8;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), actor->spr.angle, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle;
|
|
|
|
else if (CanMove(actor, actor->GetTarget(), actor->spr.angle - v8, range))
|
|
|
|
actor->xspr.goalAng = actor->spr.angle - v8;
|
2021-12-29 19:03:42 +00:00
|
|
|
else
|
2022-09-03 22:38:26 +00:00
|
|
|
actor->xspr.goalAng = actor->spr.angle + DAngle180 / 3;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
actor->xspr.goalAng = actor->xspr.goalAng.Normalized360();
|
2021-12-29 19:03:42 +00:00
|
|
|
actor->xspr.dodgeDir = (Chance(0x8000)) ? 1 : -1;
|
|
|
|
|
2022-09-03 22:38:26 +00:00
|
|
|
if (!CanMove(actor, actor->GetTarget(), actor->spr.angle + DAngle90 * actor->xspr.dodgeDir, 512))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
actor->xspr.dodgeDir = -actor->xspr.dodgeDir;
|
2022-09-03 22:38:26 +00:00
|
|
|
if (!CanMove(actor, actor->GetTarget(), actor->spr.angle + DAngle90 * actor->xspr.dodgeDir, 512))
|
2021-12-29 19:03:42 +00:00
|
|
|
actor->xspr.dodgeDir = 0;
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
void aiGenDudeNewState(DBloodActor* actor, AISTATE* pAIState)
|
2021-08-31 19:52:26 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actor->hasX())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2022-05-05 22:55:50 +00:00
|
|
|
|
|
|
|
if (actor->xspr.health <= 0 || actor->xspr.sysData1 == kGenDudeTransformStatus)
|
|
|
|
return;
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// redirect dudes which cannot walk to non-walk states
|
|
|
|
if (!actor->genDudeExtra.canWalk)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (pAIState == &genDudeDodgeL || pAIState == &genDudeDodgeShortL || pAIState == &genDudeDodgeShorterL)
|
|
|
|
pAIState = &genDudeRecoilL;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeDodgeD || pAIState == &genDudeDodgeShortD || pAIState == &genDudeDodgeShorterD)
|
|
|
|
pAIState = &genDudeRecoilD;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeDodgeW || pAIState == &genDudeDodgeShortW || pAIState == &genDudeDodgeShorterW)
|
|
|
|
pAIState = &genDudeRecoilW;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeSearchL || pAIState == &genDudeSearchShortL)
|
|
|
|
pAIState = &genDudeSearchNoWalkL;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeSearchW || pAIState == &genDudeSearchShortW)
|
|
|
|
pAIState = &genDudeSearchNoWalkW;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeGotoL) pAIState = &genDudeIdleL;
|
|
|
|
else if (pAIState == &genDudeGotoW) pAIState = &genDudeIdleW;
|
|
|
|
else if (pAIState == &genDudeChaseL) pAIState = &genDudeChaseNoWalkL;
|
|
|
|
else if (pAIState == &genDudeChaseD) pAIState = &genDudeChaseNoWalkD;
|
|
|
|
else if (pAIState == &genDudeChaseW) pAIState = &genDudeChaseNoWalkW;
|
|
|
|
else if (pAIState == &genDudeRecoilTesla) {
|
|
|
|
|
|
|
|
if (spriteIsUnderwater(actor, false)) pAIState = &genDudeRecoilW;
|
|
|
|
else pAIState = &genDudeRecoilL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!actor->genDudeExtra.canRecoil)
|
|
|
|
{
|
|
|
|
if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL;
|
|
|
|
else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW;
|
|
|
|
}
|
|
|
|
|
|
|
|
actor->xspr.stateTimer = pAIState->stateTicks; actor->xspr.aiState = pAIState;
|
|
|
|
|
|
|
|
int stateSeq = actor->xspr.data2 + pAIState->seqId;
|
|
|
|
if (pAIState->seqId >= 0 && getSequence(stateSeq))
|
|
|
|
{
|
|
|
|
seqSpawn(stateSeq, actor, pAIState->funcId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pAIState->enterFunc)
|
|
|
|
pAIState->enterFunc(actor);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool playGenDudeSound(DBloodActor* actor, int mode)
|
2021-05-05 17:15:25 +00:00
|
|
|
{
|
2022-11-16 06:53:21 +00:00
|
|
|
if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax || !actor->hasX()) return false;
|
|
|
|
const GENDUDESND* sndInfo = &gCustomDudeSnd[mode];
|
2021-12-29 19:03:42 +00:00
|
|
|
int sndStartId = actor->xspr.sysData1;
|
|
|
|
int rand = sndInfo->randomRange;
|
|
|
|
int sndId = (sndStartId <= 0) ? sndInfo->defaultSndId : sndStartId + sndInfo->sndIdOffset;
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
|
|
|
|
if (sndId < 0) return false;
|
2022-11-16 06:53:21 +00:00
|
|
|
else if (sndStartId <= 0) sndId += Random(rand);
|
2021-12-29 19:03:42 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Let's try to get random snd
|
|
|
|
int maxRetries = 5;
|
|
|
|
while (maxRetries-- > 0) {
|
|
|
|
int random = Random(rand);
|
2022-11-24 16:46:39 +00:00
|
|
|
if (!soundEngine->FindSoundByResID(sndId + random).isvalid()) continue;
|
2021-12-29 19:03:42 +00:00
|
|
|
sndId = sndId + random;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no success in getting random snd, get first existing one
|
2022-11-16 06:53:21 +00:00
|
|
|
if (maxRetries <= 0)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
int maxSndId = sndId + rand;
|
2022-11-24 16:46:39 +00:00
|
|
|
while (sndId < maxSndId && !soundEngine->FindSoundByResID(sndId++).isvalid());
|
2022-11-16 06:53:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// let's check if there same sounds already played by other dudes
|
|
|
|
// so we won't get a lot of annoying screams in the same time and
|
|
|
|
// ensure sound played in it's full length (if not interruptable)
|
|
|
|
if (pExtra->sndPlaying && !sndInfo->interruptable)
|
|
|
|
{
|
|
|
|
if (soundEngine->GetSoundPlayingInfo(SOURCE_Any, nullptr, soundEngine->FindSoundByResID(sndId)))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-11-16 06:53:21 +00:00
|
|
|
return true;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 06:53:21 +00:00
|
|
|
pExtra->sndPlaying = false;
|
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
2022-11-16 06:53:21 +00:00
|
|
|
if (sndInfo->aiPlaySound) aiPlay3DSound(actor, sndId, AI_SFX_PRIORITY_2, -1);
|
2021-12-29 19:03:42 +00:00
|
|
|
else sfxPlay3DSound(actor, sndId, -1, 0);
|
|
|
|
|
|
|
|
pExtra->sndPlaying = true;
|
|
|
|
return true;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-05-05 17:15:25 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool spriteIsUnderwater(DBloodActor* actor, bool oldWay)
|
2021-05-05 18:40:31 +00:00
|
|
|
{
|
2021-12-30 15:51:56 +00:00
|
|
|
return (IsUnderwaterSector(actor->sector())
|
2021-12-29 19:03:42 +00:00
|
|
|
|| (oldWay && (actor->xspr.medium == kMediumWater || actor->xspr.medium == kMediumGoo)));
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
DBloodActor* leechIsDropped(DBloodActor* actor)
|
2021-05-05 19:06:38 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return actor->genDudeExtra.pLifeLeech;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-05-05 19:18:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void removeDudeStuff(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
BloodStatIterator it(kStatThing);
|
|
|
|
while (auto actor2 = it.Next())
|
|
|
|
{
|
|
|
|
if (actor2->GetOwner() != actor) continue;
|
|
|
|
switch (actor2->spr.type) {
|
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedRemoteBomb:
|
|
|
|
case kModernThingTNTProx:
|
|
|
|
actor2->spr.type = kSpriteDecoration;
|
|
|
|
actPostSprite(actor2, kStatFree);
|
|
|
|
break;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
killDudeLeech(actor2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it.Reset(kStatDude);
|
|
|
|
while (auto actor2 = it.Next())
|
|
|
|
{
|
|
|
|
if (actor2->GetOwner() != actor) continue;
|
|
|
|
actDamageSprite(actor2->GetOwner(), actor2, kDamageFall, 65535);
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-05-05 19:18:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
void removeLeech(DBloodActor* actLeech, bool delSprite)
|
2021-05-05 19:18:09 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actLeech != nullptr)
|
|
|
|
{
|
2022-09-28 13:36:25 +00:00
|
|
|
auto effectactor = gFX.fxSpawnActor((FX_ID)52, actLeech->sector(), actLeech->spr.pos, actLeech->spr.angle);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (effectactor != nullptr)
|
|
|
|
{
|
|
|
|
effectactor->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
|
|
|
|
effectactor->spr.pal = 6;
|
2022-10-05 22:01:01 +00:00
|
|
|
double repeat = 1 + Random(50) * REPEAT_SCALE;
|
2022-10-07 21:52:29 +00:00
|
|
|
effectactor->spr.scale = DVector2(repeat, repeat);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sfxPlay3DSoundCP(actLeech, 490, -1, 0, 60000);
|
|
|
|
|
|
|
|
if (actLeech->GetOwner())
|
|
|
|
actLeech->GetOwner()->genDudeExtra.pLifeLeech = nullptr;
|
|
|
|
|
|
|
|
if (delSprite)
|
|
|
|
{
|
|
|
|
actLeech->spr.type = kSpriteDecoration;
|
|
|
|
actPostSprite(actLeech, kStatFree);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
void killDudeLeech(DBloodActor* actLeech)
|
2021-05-05 19:22:09 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actLeech != NULL)
|
|
|
|
{
|
|
|
|
actDamageSprite(actLeech->GetOwner(), actLeech, kDamageExplode, 65535);
|
|
|
|
sfxPlay3DSoundCP(actLeech, 522, -1, 0, 60000);
|
|
|
|
|
|
|
|
if (actLeech->GetOwner() != nullptr)
|
|
|
|
actLeech->GetOwner()->genDudeExtra.pLifeLeech = nullptr;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2021-05-05 19:22:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-05-06 06:39:33 +00:00
|
|
|
DBloodActor* getNextIncarnation(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
for (int i = bucketHead[actor->xspr.txID]; i < bucketHead[actor->xspr.txID + 1]; i++)
|
|
|
|
{
|
|
|
|
if (!rxBucket[i].isActor()) continue;
|
2021-11-23 17:07:34 +00:00
|
|
|
auto rxactor = rxBucket[i].actor();
|
2021-12-29 19:03:42 +00:00
|
|
|
if (actor != rxactor && rxactor->spr.statnum == kStatInactive) return rxactor;
|
|
|
|
}
|
|
|
|
return nullptr;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 06:45:50 +00:00
|
|
|
bool dudeIsMelee(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return actor->genDudeExtra.isMelee;
|
2021-05-06 06:45:50 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 06:39:33 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
static void scaleDamage(DBloodActor* actor)
|
2021-05-06 07:02:49 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
int curWeapon = actor->genDudeExtra.curWeapon;
|
|
|
|
int weaponType = actor->genDudeExtra.weaponType;
|
|
|
|
signed short* curScale = actor->genDudeExtra.dmgControl;
|
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
|
|
|
curScale[i] = getDudeInfo(kDudeModernCustom)->startDamage[i];
|
|
|
|
|
|
|
|
switch (weaponType) {
|
|
|
|
// just copy damage resistance of dude that should be summoned
|
|
|
|
case kGenDudeWeaponSummon:
|
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
|
|
|
curScale[i] = getDudeInfo(curWeapon)->startDamage[i];
|
|
|
|
break;
|
|
|
|
// these does not like the explosions and burning
|
|
|
|
case kGenDudeWeaponKamikaze:
|
|
|
|
curScale[kDmgBurn] = curScale[kDmgExplode] = curScale[kDmgElectric] = 1024;
|
|
|
|
break;
|
|
|
|
case kGenDudeWeaponMissile:
|
|
|
|
case kGenDudeWeaponThrow:
|
|
|
|
switch (curWeapon)
|
|
|
|
{
|
|
|
|
case kMissileButcherKnife:
|
|
|
|
curScale[kDmgBullet] = 100;
|
|
|
|
[[fallthrough]];
|
|
|
|
case kMissileEctoSkull:
|
|
|
|
curScale[kDmgSpirit] = 32;
|
|
|
|
break;
|
|
|
|
case kMissileLifeLeechAltNormal:
|
|
|
|
case kMissileLifeLeechAltSmall:
|
|
|
|
case kMissileArcGargoyle:
|
|
|
|
curScale[kDmgSpirit] -= 32;
|
|
|
|
curScale[kDmgElectric] = 52;
|
|
|
|
break;
|
|
|
|
case kMissileFlareRegular:
|
|
|
|
case kMissileFlareAlt:
|
|
|
|
case kMissileFlameSpray:
|
|
|
|
case kMissileFlameHound:
|
|
|
|
case kThingArmedSpray:
|
|
|
|
case kThingPodFireBall:
|
|
|
|
case kThingNapalmBall:
|
|
|
|
curScale[kDmgBurn] = 32;
|
|
|
|
break;
|
|
|
|
case kMissileLifeLeechRegular:
|
|
|
|
curScale[kDmgBurn] = 60 + Random(4);
|
|
|
|
[[fallthrough]];
|
|
|
|
case kThingDroppedLifeLeech:
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
curScale[kDmgSpirit] = 32 + Random(18);
|
|
|
|
break;
|
|
|
|
case kMissileFireball:
|
|
|
|
case kMissileFireballNapalm:
|
|
|
|
case kMissileFireballCerberus:
|
|
|
|
case kMissileFireballTchernobog:
|
|
|
|
curScale[kDmgBurn] = 50;
|
|
|
|
curScale[kDmgExplode] -= 32;
|
|
|
|
curScale[kDmgFall] = 65 + Random(15);
|
|
|
|
break;
|
|
|
|
case kThingTNTBarrel:
|
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedRemoteBomb:
|
|
|
|
case kThingArmedTNTBundle:
|
|
|
|
case kThingArmedTNTStick:
|
|
|
|
case kModernThingTNTProx:
|
|
|
|
curScale[kDmgBurn] -= 32;
|
|
|
|
curScale[kDmgExplode] -= 32;
|
|
|
|
curScale[kDmgFall] = 65 + Random(15);
|
|
|
|
break;
|
|
|
|
case kMissileTeslaAlt:
|
|
|
|
case kMissileTeslaRegular:
|
|
|
|
curScale[kDmgElectric] = 32 + Random(8);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// add resistance if have an armor item to drop
|
|
|
|
if (actor->xspr.dropMsg >= kItemArmorAsbest && actor->xspr.dropMsg <= kItemArmorSuper)
|
|
|
|
{
|
|
|
|
switch (actor->xspr.dropMsg)
|
|
|
|
{
|
|
|
|
case kItemArmorAsbest:
|
|
|
|
curScale[kDmgBurn] = 0;
|
|
|
|
curScale[kDmgExplode] -= 30;
|
|
|
|
break;
|
|
|
|
case kItemArmorBasic:
|
|
|
|
curScale[kDmgBurn] -= 15;
|
|
|
|
curScale[kDmgExplode] -= 15;
|
|
|
|
curScale[kDmgBullet] -= 15;
|
|
|
|
curScale[kDmgSpirit] -= 15;
|
|
|
|
break;
|
|
|
|
case kItemArmorBody:
|
|
|
|
curScale[kDmgBullet] -= 30;
|
|
|
|
break;
|
|
|
|
case kItemArmorFire:
|
|
|
|
curScale[kDmgBurn] -= 30;
|
|
|
|
curScale[kDmgExplode] -= 30;
|
|
|
|
break;
|
|
|
|
case kItemArmorSpirit:
|
|
|
|
curScale[kDmgSpirit] -= 30;
|
|
|
|
break;
|
|
|
|
case kItemArmorSuper:
|
|
|
|
curScale[kDmgBurn] -= 60;
|
|
|
|
curScale[kDmgExplode] -= 60;
|
|
|
|
curScale[kDmgBullet] -= 60;
|
|
|
|
curScale[kDmgSpirit] -= 60;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-05 22:56:03 +00:00
|
|
|
// take in account scale of sprite
|
2022-10-07 21:33:37 +00:00
|
|
|
int yscale = int(actor->spr.scale.Y * 64);
|
2022-10-05 22:18:06 +00:00
|
|
|
if (yscale < 64)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-10-05 22:18:06 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++) curScale[i] += (64 - yscale);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
2022-10-05 22:18:06 +00:00
|
|
|
else if (yscale > 64)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-10-05 22:18:06 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++) curScale[i] -= ((yscale - 64) >> 2);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// take surface type into account
|
|
|
|
int surfType = tileGetSurfType(actor->spr.picnum);
|
|
|
|
switch (surfType)
|
|
|
|
{
|
|
|
|
case 1: // stone
|
|
|
|
curScale[kDmgFall] = 0;
|
|
|
|
curScale[kDmgBullet] -= 200;
|
|
|
|
curScale[kDmgBurn] -= 100;
|
|
|
|
curScale[kDmgExplode] -= 80;
|
|
|
|
curScale[kDmgChoke] += 30;
|
|
|
|
curScale[kDmgElectric] += 20;
|
|
|
|
break;
|
|
|
|
case 2: // metal
|
|
|
|
curScale[kDmgFall] = 16;
|
|
|
|
curScale[kDmgBullet] -= 128;
|
|
|
|
curScale[kDmgBurn] -= 90;
|
|
|
|
curScale[kDmgExplode] -= 55;
|
|
|
|
curScale[kDmgChoke] += 20;
|
|
|
|
curScale[kDmgElectric] += 30;
|
|
|
|
break;
|
|
|
|
case 3: // wood
|
|
|
|
curScale[kDmgBullet] -= 10;
|
|
|
|
curScale[kDmgBurn] += 50;
|
|
|
|
curScale[kDmgExplode] += 40;
|
|
|
|
curScale[kDmgChoke] += 10;
|
|
|
|
curScale[kDmgElectric] -= 60;
|
|
|
|
break;
|
|
|
|
case 5: // water
|
|
|
|
case 6: // dirt
|
|
|
|
case 7: // clay
|
|
|
|
case 13: // goo
|
|
|
|
curScale[kDmgFall] = 8;
|
|
|
|
curScale[kDmgBullet] -= 20;
|
|
|
|
curScale[kDmgBurn] -= 200;
|
|
|
|
curScale[kDmgExplode] -= 60;
|
|
|
|
curScale[kDmgChoke] = 0;
|
|
|
|
curScale[kDmgElectric] += 40;
|
|
|
|
break;
|
|
|
|
case 8: // snow
|
|
|
|
case 9: // ice
|
|
|
|
curScale[kDmgFall] = 8;
|
|
|
|
curScale[kDmgBullet] -= 20;
|
|
|
|
curScale[kDmgBurn] -= 100;
|
|
|
|
curScale[kDmgExplode] -= 50;
|
|
|
|
curScale[kDmgChoke] = 0;
|
|
|
|
curScale[kDmgElectric] += 40;
|
|
|
|
break;
|
|
|
|
case 10: // leaves
|
|
|
|
case 12: // plant
|
|
|
|
curScale[kDmgFall] = 0;
|
|
|
|
curScale[kDmgBullet] -= 10;
|
|
|
|
curScale[kDmgBurn] += 70;
|
|
|
|
curScale[kDmgExplode] += 50;
|
|
|
|
break;
|
|
|
|
case 11: // cloth
|
|
|
|
curScale[kDmgFall] = 8;
|
|
|
|
curScale[kDmgBullet] -= 10;
|
|
|
|
curScale[kDmgBurn] += 30;
|
|
|
|
curScale[kDmgExplode] += 20;
|
|
|
|
break;
|
|
|
|
case 14: // lava
|
|
|
|
curScale[kDmgBurn] = 0;
|
|
|
|
curScale[kDmgExplode] = 0;
|
|
|
|
curScale[kDmgChoke] += 30;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// finally, scale dmg for difficulty
|
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
|
|
|
curScale[i] = MulScale(DudeDifficulty[gGameOptions.nDifficulty], ClipLow(curScale[i], 1), 8);
|
|
|
|
|
|
|
|
//short* dc = curScale;
|
|
|
|
//if (actor->xspr.rxID == 788)
|
|
|
|
//viewSetSystemMessage("0: %d, 1: %d, 2: %d, 3: %d, 4: %d, 5: %d, 6: %d", dc[0], dc[1], dc[2], dc[3], dc[4], dc[5], dc[6]);
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:04:06 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
static int getDispersionModifier(DBloodActor* actor, int minDisp, int maxDisp)
|
2020-07-25 20:47:46 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
// the faster fire rate, the less frames = more dispersion
|
|
|
|
Seq* pSeq = getSequence(actor->xspr.data2 + 6);
|
|
|
|
int disp = 1;
|
|
|
|
if (pSeq != nullptr)
|
|
|
|
{
|
|
|
|
int nFrames = pSeq->nFrames; int ticks = pSeq->ticksPerFrame; int shots = 0;
|
|
|
|
for (int i = 0; i <= pSeq->nFrames; i++) {
|
|
|
|
if (pSeq->frames[i].trigger) shots++;
|
|
|
|
}
|
|
|
|
|
|
|
|
disp = (((shots * 1000) / nFrames) / ticks) * 20;
|
|
|
|
if (gGameOptions.nDifficulty > 0)
|
|
|
|
disp /= gGameOptions.nDifficulty;
|
|
|
|
|
|
|
|
//viewSetSystemMessage("DISP: %d FRAMES: %d SHOTS: %d TICKS %d", disp, nFrames, shots, ticks);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ClipRange(disp, minDisp, maxDisp);
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:04:06 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2019-11-08 19:57:01 +00:00
|
|
|
// the distance counts from sprite size
|
2021-05-06 07:04:06 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2022-09-29 12:01:59 +00:00
|
|
|
static double getRangeAttackDist(DBloodActor* actor, double minDist, double maxDist)
|
2021-05-06 07:04:06 +00:00
|
|
|
{
|
2022-10-07 21:33:37 +00:00
|
|
|
int yscale = int(actor->spr.scale.Y * 64);
|
2021-12-29 19:03:42 +00:00
|
|
|
int dist = 0;
|
|
|
|
int seqId = actor->xspr.data2;
|
|
|
|
int mul = 550;
|
|
|
|
int picnum = actor->spr.picnum;
|
|
|
|
|
2022-10-05 22:18:06 +00:00
|
|
|
if (yscale > 0)
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
|
|
|
if (seqId >= 0)
|
|
|
|
{
|
|
|
|
Seq* pSeq = getSequence(seqId);
|
|
|
|
if (pSeq)
|
|
|
|
{
|
|
|
|
picnum = seqGetTile(&pSeq->frames[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dist = tileHeight(picnum) << 8;
|
2022-10-05 22:18:06 +00:00
|
|
|
if (yscale < 64) dist -= (64 - yscale) * mul;
|
|
|
|
else if (yscale > 64) dist += (yscale - 64) * (mul / 3);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
2022-09-29 12:01:59 +00:00
|
|
|
return clamp(dist / 16., minDist, maxDist);
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
int getBaseChanceModifier(int baseChance)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
int getRecoilChance(DBloodActor* actor)
|
2021-05-06 07:08:06 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
int mass = getSpriteMassBySize(actor);
|
|
|
|
int baseChance = (!dudeIsMelee(actor) ? 0x8000 : 0x4000);
|
|
|
|
baseChance = getBaseChanceModifier(baseChance) + actor->xspr.data3;
|
|
|
|
|
|
|
|
int chance = ((baseChance / mass) << 7);
|
|
|
|
return chance;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
int getDodgeChance(DBloodActor* actor)
|
2021-05-06 07:08:06 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
int mass = getSpriteMassBySize(actor);
|
|
|
|
int baseChance = (!dudeIsMelee(actor) ? 0x6000 : 0x1000);
|
|
|
|
baseChance = getBaseChanceModifier(baseChance) + actor->xspr.data3;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
int chance = ((baseChance / mass) << 7);
|
|
|
|
return chance;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:20:34 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void dudeLeechOperate(DBloodActor* actor, const EVENT& event)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (event.cmd == kCmdOff)
|
|
|
|
{
|
|
|
|
actPostSprite(actor, kStatFree);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto actTarget = actor->GetTarget();
|
|
|
|
if (actTarget != nullptr && actTarget != actor->GetOwner())
|
|
|
|
{
|
|
|
|
if (actTarget->spr.statnum == kStatDude && !(actTarget->spr.flags & 32) && actTarget->hasX() && !actor->xspr.stateTimer)
|
|
|
|
{
|
|
|
|
if (actTarget->IsPlayerActor())
|
|
|
|
{
|
|
|
|
PLAYER* pPlayer = &gPlayer[actTarget->spr.type - kDudePlayer1];
|
|
|
|
if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return;
|
|
|
|
}
|
2022-09-28 11:06:50 +00:00
|
|
|
double top, bottom;
|
2021-12-29 19:03:42 +00:00
|
|
|
GetActorExtents(actor, &top, &bottom);
|
|
|
|
int nType = actTarget->spr.type - kDudeBase;
|
|
|
|
DUDEINFO* pDudeInfo = &dudeInfo[nType];
|
2022-09-28 11:06:50 +00:00
|
|
|
double z1 = (top - actor->spr.pos.Z) - 1;
|
2022-08-23 20:32:14 +00:00
|
|
|
auto atpos = actTarget->spr.pos;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-09-28 11:06:50 +00:00
|
|
|
double nDist = (atpos.XY() - actor->spr.pos.XY()).Length();
|
|
|
|
|
|
|
|
if (nDist != 0 && cansee(DVector3(actor->spr.pos.XY(), top), actor->sector(), atpos, actTarget->sector()))
|
2021-12-29 19:03:42 +00:00
|
|
|
{
|
2022-09-28 11:06:50 +00:00
|
|
|
atpos.XY() += actTarget->vel.XY() * nDist * 0.0375;
|
|
|
|
|
2022-08-23 20:32:14 +00:00
|
|
|
auto angBak = actor->spr.angle;
|
2022-09-30 11:38:21 +00:00
|
|
|
actor->spr.angle = (atpos - actor->spr.pos.XY()).Angle();
|
2022-09-28 11:06:50 +00:00
|
|
|
DVector3 dv;
|
|
|
|
dv.XY() = actor->spr.angle.ToVector() * 64;
|
2022-10-07 21:33:37 +00:00
|
|
|
double tz = actTarget->spr.pos.Z - (actTarget->spr.scale.Y * pDudeInfo->aimHeight);
|
2022-09-28 11:06:50 +00:00
|
|
|
double dz = (tz - top - 1) / nDist * 4;
|
2021-12-29 19:03:42 +00:00
|
|
|
int nMissileType = kMissileLifeLeechAltNormal + (actor->xspr.data3 ? 1 : 0);
|
|
|
|
int t2;
|
|
|
|
|
|
|
|
if (!actor->xspr.data3) t2 = 120 / 10;
|
|
|
|
else t2 = (3 * 120) / 10;
|
|
|
|
|
2022-09-28 11:06:50 +00:00
|
|
|
auto missile = actFireMissile(actor, 0, z1, dv, nMissileType);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (missile)
|
|
|
|
{
|
|
|
|
missile->SetOwner(actor);
|
|
|
|
actor->xspr.stateTimer = 1;
|
|
|
|
evPostActor(actor, t2, kCallbackLeechStateTimer);
|
|
|
|
actor->xspr.data3 = ClipLow(actor->xspr.data3 - 1, 0);
|
|
|
|
}
|
2022-08-23 20:32:14 +00:00
|
|
|
actor->spr.angle = angBak;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:09:58 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
bool doExplosion(DBloodActor* actor, int nType)
|
2020-12-05 16:24:27 +00:00
|
|
|
{
|
2022-08-22 16:24:09 +00:00
|
|
|
auto actExplosion = actSpawnSprite(actor->sector(), actor->spr.pos, kStatExplosion, true);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actExplosion->hasX())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
int nSeq = 4; int nSnd = 304; const EXPLOSION* pExpl = &explodeInfo[nType];
|
|
|
|
|
|
|
|
actExplosion->spr.type = nType;
|
|
|
|
actExplosion->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
|
|
actExplosion->SetOwner(actor);
|
|
|
|
actExplosion->spr.shade = -127;
|
|
|
|
|
2022-10-07 21:52:29 +00:00
|
|
|
actExplosion->spr.scale = DVector2(pExpl->repeat * REPEAT_SCALE, pExpl->repeat * REPEAT_SCALE);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
actExplosion->xspr.data1 = pExpl->ticks;
|
|
|
|
actExplosion->xspr.data2 = pExpl->quakeEffect;
|
|
|
|
actExplosion->xspr.data3 = pExpl->flashEffect;
|
|
|
|
|
|
|
|
if (nType == 0) { nSeq = 3; nSnd = 303; }
|
|
|
|
else if (nType == 2) { nSeq = 4; nSnd = 305; }
|
|
|
|
else if (nType == 3) { nSeq = 9; nSnd = 307; }
|
|
|
|
else if (nType == 4) { nSeq = 5; nSnd = 307; }
|
|
|
|
else if (nType <= 6) { nSeq = 4; nSnd = 303; }
|
|
|
|
else if (nType == 7) { nSeq = 4; nSnd = 303; }
|
|
|
|
|
|
|
|
seqSpawn(nSeq, actExplosion, -1);
|
|
|
|
sfxPlay3DSound(actExplosion, nSnd, -1, 0);
|
|
|
|
|
|
|
|
return true;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2020-02-07 19:47:43 +00:00
|
|
|
// this function allows to spawn new custom dude and inherit spawner settings,
|
|
|
|
// so custom dude can have different weapons, hp and so on...
|
2021-05-06 07:00:49 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2022-09-28 13:44:18 +00:00
|
|
|
DBloodActor* genDudeSpawn(DBloodActor* source, DBloodActor* actor, double nDist)
|
2021-05-06 07:00:49 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
auto spawned = actSpawnSprite(actor, kStatDude);
|
2022-09-28 13:36:25 +00:00
|
|
|
int nType = kDudeModernCustom;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-08-22 21:57:39 +00:00
|
|
|
auto pos = actor->spr.pos;
|
2021-12-29 19:03:42 +00:00
|
|
|
if (nDist > 0)
|
|
|
|
{
|
2022-09-28 13:44:18 +00:00
|
|
|
pos.XY() += actor->spr.angle.ToVector() * nDist;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
2022-08-22 21:57:39 +00:00
|
|
|
spawned->spr.type = nType;
|
|
|
|
spawned->spr.angle = actor->spr.angle;
|
|
|
|
SetActor(spawned, pos);
|
2021-12-29 19:03:42 +00:00
|
|
|
spawned->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL | CSTAT_SPRITE_BLOOD_BIT1;
|
2022-10-04 17:18:09 +00:00
|
|
|
spawned->clipdist = dudeInfo[nType - kDudeBase].fClipdist();
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// inherit weapon, seq and sound settings.
|
|
|
|
spawned->xspr.data1 = source->xspr.data1;
|
|
|
|
spawned->xspr.data2 = source->xspr.data2;
|
|
|
|
spawned->xspr.sysData1 = source->xspr.data3; // move sndStartId from data3 to sysData1
|
|
|
|
spawned->xspr.data3 = 0;
|
|
|
|
|
|
|
|
// spawn seq
|
|
|
|
seqSpawn(genDudeSeqStartId(spawned), spawned, -1);
|
|
|
|
|
|
|
|
// inherit movement speed.
|
|
|
|
spawned->xspr.busyTime = source->xspr.busyTime;
|
|
|
|
|
|
|
|
// inherit clipdist?
|
2022-10-04 17:25:06 +00:00
|
|
|
if (source->clipdist > 0)
|
2022-09-09 16:28:21 +00:00
|
|
|
spawned->copy_clipdist(source);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
// inherit custom hp settings
|
|
|
|
if (source->xspr.data4 <= 0) spawned->xspr.health = dudeInfo[nType - kDudeBase].startHealth << 4;
|
|
|
|
else spawned->xspr.health = ClipRange(source->xspr.data4 << 4, 1, 65535);
|
|
|
|
|
|
|
|
|
|
|
|
if (source->spr.flags & kModernTypeFlag1)
|
|
|
|
{
|
|
|
|
switch (source->spr.type) {
|
|
|
|
case kModernCustomDudeSpawn:
|
|
|
|
//inherit pal?
|
|
|
|
if (spawned->spr.pal <= 0) spawned->spr.pal = source->spr.pal;
|
|
|
|
|
|
|
|
// inherit spawn sprite trigger settings, so designer can count monsters.
|
|
|
|
spawned->xspr.txID = source->xspr.txID;
|
|
|
|
spawned->xspr.command = source->xspr.command;
|
|
|
|
spawned->xspr.triggerOn = source->xspr.triggerOn;
|
|
|
|
spawned->xspr.triggerOff = source->xspr.triggerOff;
|
|
|
|
|
|
|
|
// inherit drop items
|
|
|
|
spawned->xspr.dropMsg = source->xspr.dropMsg;
|
|
|
|
|
|
|
|
// inherit required key so it can be dropped
|
|
|
|
spawned->xspr.key = source->xspr.key;
|
|
|
|
|
|
|
|
// inherit dude flags
|
|
|
|
spawned->xspr.dudeDeaf = source->xspr.dudeDeaf;
|
|
|
|
spawned->xspr.dudeGuard = source->xspr.dudeGuard;
|
|
|
|
spawned->xspr.dudeAmbush = source->xspr.dudeAmbush;
|
|
|
|
spawned->xspr.dudeFlag4 = source->xspr.dudeFlag4;
|
|
|
|
spawned->xspr.unused1 = source->xspr.unused1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// inherit sprite size (useful for seqs with zero repeats)
|
|
|
|
if (source->spr.flags & kModernTypeFlag2)
|
|
|
|
{
|
2022-10-07 21:41:15 +00:00
|
|
|
spawned->spr.scale = source->spr.scale;
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
2022-05-05 23:08:09 +00:00
|
|
|
gKillMgr.AddKill(spawned);
|
2021-12-29 19:03:42 +00:00
|
|
|
aiInitSprite(spawned);
|
|
|
|
return spawned;
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:43:35 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
void genDudeTransform(DBloodActor* actor)
|
2021-05-06 07:55:56 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actor->hasX()) return;
|
|
|
|
|
|
|
|
auto actIncarnation = getNextIncarnation(actor);
|
|
|
|
if (actIncarnation == NULL)
|
|
|
|
{
|
|
|
|
if (actor->xspr.sysData1 == kGenDudeTransformStatus) actor->xspr.sysData1 = 0;
|
2022-08-10 21:45:29 +00:00
|
|
|
trTriggerSprite(actor, kCmdOff, actor);
|
2021-12-29 19:03:42 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
actor->xspr.key = actor->xspr.dropMsg = actor->xspr.locked = 0;
|
|
|
|
|
|
|
|
// save incarnation's going on and off options
|
|
|
|
bool triggerOn = actIncarnation->xspr.triggerOn;
|
|
|
|
bool triggerOff = actIncarnation->xspr.triggerOff;
|
|
|
|
|
|
|
|
// then remove it from incarnation so it will not send the commands
|
|
|
|
actIncarnation->xspr.triggerOn = false;
|
|
|
|
actIncarnation->xspr.triggerOff = false;
|
|
|
|
|
|
|
|
// trigger dude death before transform
|
2022-08-10 21:45:29 +00:00
|
|
|
trTriggerSprite(actor, kCmdOff, actor);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
actor->spr.type = actor->spr.inittype = actIncarnation->spr.type;
|
|
|
|
actor->spr.flags = actIncarnation->spr.flags;
|
|
|
|
actor->spr.pal = actIncarnation->spr.pal;
|
|
|
|
actor->spr.shade = actIncarnation->spr.shade;
|
2022-09-09 16:28:21 +00:00
|
|
|
actor->copy_clipdist(actIncarnation);
|
2022-10-07 21:41:15 +00:00
|
|
|
actor->spr.scale = actIncarnation->spr.scale;
|
2021-12-29 19:03:42 +00:00
|
|
|
|
|
|
|
actor->xspr.txID = actIncarnation->xspr.txID;
|
|
|
|
actor->xspr.command = actIncarnation->xspr.command;
|
|
|
|
actor->xspr.triggerOn = triggerOn;
|
|
|
|
actor->xspr.triggerOff = triggerOff;
|
|
|
|
actor->xspr.busyTime = actIncarnation->xspr.busyTime;
|
|
|
|
actor->xspr.waitTime = actIncarnation->xspr.waitTime;
|
|
|
|
|
|
|
|
// inherit respawn properties
|
|
|
|
actor->xspr.respawn = actIncarnation->xspr.respawn;
|
|
|
|
actor->xspr.respawnPending = actIncarnation->xspr.respawnPending;
|
|
|
|
|
|
|
|
actor->xspr.burnTime = 0;
|
|
|
|
actor->SetBurnSource(nullptr);
|
|
|
|
|
|
|
|
actor->xspr.data1 = actIncarnation->xspr.data1;
|
|
|
|
actor->xspr.data2 = actIncarnation->xspr.data2;
|
|
|
|
|
|
|
|
actor->xspr.sysData1 = actIncarnation->xspr.data3; // soundBase id
|
|
|
|
actor->xspr.sysData2 = actIncarnation->xspr.data4; // start hp
|
|
|
|
|
|
|
|
actor->xspr.dudeGuard = actIncarnation->xspr.dudeGuard;
|
|
|
|
actor->xspr.dudeDeaf = actIncarnation->xspr.dudeDeaf;
|
|
|
|
actor->xspr.dudeAmbush = actIncarnation->xspr.dudeAmbush;
|
|
|
|
actor->xspr.dudeFlag4 = actIncarnation->xspr.dudeFlag4;
|
|
|
|
actor->xspr.unused1 = actIncarnation->xspr.unused1;
|
|
|
|
|
|
|
|
actor->xspr.dropMsg = actIncarnation->xspr.dropMsg;
|
|
|
|
actor->xspr.key = actIncarnation->xspr.key;
|
|
|
|
|
|
|
|
actor->xspr.locked = actIncarnation->xspr.locked;
|
|
|
|
actor->xspr.Decoupled = actIncarnation->xspr.Decoupled;
|
|
|
|
|
|
|
|
// clear drop items of the incarnation
|
|
|
|
actIncarnation->xspr.key = actIncarnation->xspr.dropMsg = 0;
|
|
|
|
|
|
|
|
// set hp
|
|
|
|
if (actor->xspr.sysData2 <= 0) actor->xspr.health = dudeInfo[actor->spr.type - kDudeBase].startHealth << 4;
|
|
|
|
else actor->xspr.health = ClipRange(actor->xspr.sysData2 << 4, 1, 65535);
|
|
|
|
|
|
|
|
int seqId = dudeInfo[actor->spr.type - kDudeBase].seqStartID;
|
|
|
|
switch (actor->spr.type) {
|
|
|
|
case kDudePodMother: // fake dude
|
|
|
|
case kDudeTentacleMother: // fake dude
|
|
|
|
break;
|
|
|
|
case kDudeModernCustom:
|
|
|
|
case kDudeModernCustomBurning:
|
|
|
|
seqId = genDudeSeqStartId(actor);
|
|
|
|
genDudePrepare(actor, kGenDudePropertyMass);
|
|
|
|
[[fallthrough]]; // go below
|
|
|
|
default:
|
|
|
|
seqSpawn(seqId, actor, -1);
|
|
|
|
|
|
|
|
// save target
|
|
|
|
auto target = actor->GetTarget();
|
|
|
|
|
|
|
|
// re-init sprite
|
|
|
|
aiInitSprite(actor);
|
|
|
|
|
|
|
|
// try to restore target
|
2022-08-22 16:41:41 +00:00
|
|
|
if (target == nullptr) aiSetTarget(actor, actor->spr.pos);
|
2021-12-29 19:03:42 +00:00
|
|
|
else aiSetTarget(actor, target);
|
|
|
|
|
|
|
|
// finally activate it
|
|
|
|
aiActivateDude(actor);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
actIncarnation->xspr.triggerOn = triggerOn;
|
|
|
|
actIncarnation->xspr.triggerOff = triggerOff;
|
|
|
|
|
|
|
|
/*// remove the incarnation in case if non-locked
|
|
|
|
if (actIncarnation->xspr.locked == 0) {
|
|
|
|
actIncarnation->xspr.txID = actIncarnation->spr.type = 0;
|
|
|
|
actPostSprite(pIncarnation, kStatFree);
|
|
|
|
// or restore triggerOn and off options
|
|
|
|
} else {
|
|
|
|
actIncarnation->xspr.triggerOn = triggerOn;
|
|
|
|
actIncarnation->xspr.triggerOff = triggerOff;
|
|
|
|
}*/
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:43:35 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-06 07:55:56 +00:00
|
|
|
void updateTargetOfLeech(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actor->hasX()) return;
|
|
|
|
|
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
if (actLeech == NULL || !actLeech->hasX()) actor->genDudeExtra.pLifeLeech = nullptr;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (actor->GetTarget() != actLeech->GetTarget())
|
|
|
|
{
|
|
|
|
if (actor->GetTarget() == nullptr && actLeech->GetTarget() != nullptr)
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, actLeech->GetTarget());
|
|
|
|
if (inIdle(actor->xspr.aiState))
|
|
|
|
aiActivateDude(actor);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
actLeech->SetTarget(actor->GetTarget());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 07:43:35 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-09-01 10:04:27 +00:00
|
|
|
void updateTargetOfSlaves(DBloodActor* actor)
|
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actor->hasX()) return;
|
|
|
|
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
auto slave = pExtra->slave;
|
|
|
|
auto actTarget = actor->GetTarget();
|
|
|
|
if (!actTarget || !actTarget->IsDudeActor() || !actTarget->hasX() || actTarget->xspr.health <= 0) actTarget = nullptr;
|
|
|
|
|
|
|
|
for (int i = 0; i <= gGameOptions.nDifficulty; i++)
|
|
|
|
{
|
|
|
|
if (slave[i] != nullptr)
|
|
|
|
{
|
|
|
|
if (!slave[i]->IsDudeActor() || !slave[i]->hasX() || slave[i]->xspr.health <= 0)
|
|
|
|
{
|
|
|
|
slave[i]->SetOwner(nullptr);
|
|
|
|
slave[i] = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (actTarget != nullptr)
|
|
|
|
{
|
|
|
|
if (actTarget != slave[i]->GetTarget()) aiSetTarget(slave[i], actTarget);
|
|
|
|
// check if slave have proper target
|
|
|
|
if (slave[i]->GetTarget() == nullptr || slave[i]->GetTarget()->GetOwner() == actor)
|
2022-08-22 16:41:41 +00:00
|
|
|
aiSetTarget(slave[i], actor->spr.pos);
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-08-22 16:41:41 +00:00
|
|
|
aiSetTarget(slave[i], actor->spr.pos); // try return to master
|
2021-12-29 19:03:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// compact the array after processing.
|
|
|
|
int writeindex = 0;
|
|
|
|
for (int i = 0; i <= gGameOptions.nDifficulty; i++)
|
|
|
|
{
|
|
|
|
if (slave[i] != nullptr)
|
|
|
|
{
|
|
|
|
slave[writeindex++] = slave[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pExtra->slaveCount = writeindex;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 08:24:29 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-11-16 17:24:01 +00:00
|
|
|
int inDodge(AISTATE* aiState)
|
2021-05-06 08:24:29 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (aiState == &genDudeDodgeL) return 1;
|
|
|
|
else if (aiState == &genDudeDodgeD) return 2;
|
|
|
|
else if (aiState == &genDudeDodgeW) return 3;
|
|
|
|
else if (aiState == &genDudeDodgeShortL) return 4;
|
|
|
|
else if (aiState == &genDudeDodgeShortD) return 5;
|
|
|
|
else if (aiState == &genDudeDodgeShortW) return 6;
|
|
|
|
else if (aiState == &genDudeDodgeShorterL) return 7;
|
|
|
|
else if (aiState == &genDudeDodgeShorterD) return 8;
|
|
|
|
else if (aiState == &genDudeDodgeShorterW) return 9;
|
|
|
|
return 0;
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool inIdle(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
return (aiState == &genDudeIdleW || aiState == &genDudeIdleL);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
bool inAttack(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
return (aiState == &genDudeFireL || aiState == &genDudeFireW
|
|
|
|
|| aiState == &genDudeFireD || aiState == &genDudeThrow || aiState == &genDudeThrow2 || aiState == &genDudePunch);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-11-16 17:24:01 +00:00
|
|
|
bool inSearch(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
return (aiState->stateType == kAiStateSearch);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-11-16 17:24:01 +00:00
|
|
|
int inChase(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
if (aiState == &genDudeChaseL) return 1;
|
|
|
|
else if (aiState == &genDudeChaseD) return 2;
|
|
|
|
else if (aiState == &genDudeChaseW) return 3;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkL) return 4;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkD) return 5;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkW) return 6;
|
|
|
|
else return 0;
|
2019-12-05 20:42:35 +00:00
|
|
|
}
|
|
|
|
|
2021-11-16 17:24:01 +00:00
|
|
|
int inRecoil(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
if (aiState == &genDudeRecoilL || aiState == &genDudeRecoilTesla) return 1;
|
|
|
|
else if (aiState == &genDudeRecoilD) return 2;
|
|
|
|
else if (aiState == &genDudeRecoilW) return 3;
|
|
|
|
else return 0;
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-11-16 17:24:01 +00:00
|
|
|
int inDuck(AISTATE* aiState) {
|
2021-12-29 19:03:42 +00:00
|
|
|
if (aiState == &genDudeFireD) return 1;
|
|
|
|
else if (aiState == &genDudeChaseD) return 2;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkD) return 3;
|
|
|
|
else if (aiState == &genDudeRecoilD) return 4;
|
|
|
|
else if (aiState == &genDudeDodgeShortD) return 5;
|
|
|
|
return 0;
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-06 08:24:29 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool canSwim(DBloodActor* actor)
|
2021-05-06 08:24:29 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return actor->genDudeExtra.canSwim;
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool canDuck(DBloodActor* actor)
|
2021-05-06 08:24:29 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return actor->genDudeExtra.canDuck;
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool canWalk(DBloodActor* actor)
|
2021-05-06 08:24:29 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
return actor->genDudeExtra.canWalk;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-06 08:24:29 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
int genDudeSeqStartId(DBloodActor* actor)
|
2021-05-06 08:51:39 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (genDudePrepare(actor, kGenDudePropertyStates)) return actor->xspr.data2;
|
|
|
|
else return kGenDudeDefaultSeq;
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
bool genDudePrepare(DBloodActor* actor, int propId)
|
2021-05-06 08:51:39 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (!actor || !actor->hasX()) return false;
|
|
|
|
|
|
|
|
if (actor->spr.type != kDudeModernCustom) {
|
|
|
|
Printf(PRINT_HIGH, "actor->spr.type != kDudeModernCustom");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (propId < kGenDudePropertyAll || propId >= kGenDudePropertyMax) {
|
|
|
|
viewSetSystemMessage("Unknown custom dude #%d property (%d)", actor->GetIndex(), propId);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
|
|
|
|
pExtra->updReq[propId] = false;
|
|
|
|
|
|
|
|
switch (propId) {
|
|
|
|
case kGenDudePropertyAll:
|
|
|
|
case kGenDudePropertyInitVals:
|
|
|
|
pExtra->moveSpeed = getGenDudeMoveSpeed(actor, 0, true, false);
|
2022-10-06 16:28:42 +00:00
|
|
|
pExtra->clipdist = actor->spr.clipdist;
|
2021-12-29 19:03:42 +00:00
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
case kGenDudePropertyWeapon: {
|
|
|
|
pExtra->curWeapon = actor->xspr.data1;
|
|
|
|
switch (actor->xspr.data1) {
|
|
|
|
case VECTOR_TYPE_19: pExtra->curWeapon = kVectorBullet; break;
|
|
|
|
case kMissileUnused: pExtra->curWeapon = kMissileArcGargoyle; break;
|
|
|
|
case kThingDroppedLifeLeech: pExtra->curWeapon = kModernThingEnemyLifeLeech; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pExtra->canAttack = false;
|
|
|
|
if (pExtra->curWeapon > 0 && getSequence(actor->xspr.data2 + kGenDudeSeqAttackNormalL))
|
|
|
|
pExtra->canAttack = true;
|
|
|
|
|
|
|
|
pExtra->weaponType = kGenDudeWeaponNone;
|
|
|
|
if (pExtra->curWeapon > 0 && pExtra->curWeapon < kVectorMax) pExtra->weaponType = kGenDudeWeaponHitscan;
|
|
|
|
else if (pExtra->curWeapon >= kDudeBase && pExtra->curWeapon < kDudeMax) pExtra->weaponType = kGenDudeWeaponSummon;
|
|
|
|
else if (pExtra->curWeapon >= kMissileBase && pExtra->curWeapon < kMissileMax) pExtra->weaponType = kGenDudeWeaponMissile;
|
|
|
|
else if (pExtra->curWeapon >= kThingBase && pExtra->curWeapon < kThingMax) pExtra->weaponType = kGenDudeWeaponThrow;
|
|
|
|
else if (pExtra->curWeapon >= kTrapExploder && pExtra->curWeapon < (kTrapExploder + kExplodeMax) - 1)
|
|
|
|
pExtra->weaponType = kGenDudeWeaponKamikaze;
|
|
|
|
|
|
|
|
pExtra->isMelee = false;
|
|
|
|
if (pExtra->weaponType == kGenDudeWeaponKamikaze) pExtra->isMelee = true;
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponHitscan) {
|
2022-09-29 11:51:33 +00:00
|
|
|
if (gVectorData[pExtra->curWeapon].fMaxDist() > 0 && gVectorData[pExtra->curWeapon].fMaxDist() <= kGenDudeMaxMeleeDistf)
|
2021-12-29 19:03:42 +00:00
|
|
|
pExtra->isMelee = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
}
|
|
|
|
case kGenDudePropertyDmgScale:
|
|
|
|
scaleDamage(actor);
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
case kGenDudePropertyMass: {
|
|
|
|
// to ensure mass gets updated, let's clear all cache
|
|
|
|
SPRITEMASS* pMass = &actor->spriteMass;
|
2022-10-05 16:55:33 +00:00
|
|
|
pMass->seqId = pMass->picnum = 0;
|
2022-10-06 17:07:45 +00:00
|
|
|
pMass->scale.Zero();
|
2022-10-04 17:35:00 +00:00
|
|
|
pMass->clipDist = 0;
|
2021-12-29 19:03:42 +00:00
|
|
|
pMass->mass = pMass->airVel = pMass->fraction = 0;
|
|
|
|
getSpriteMassBySize(actor);
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
}
|
|
|
|
case kGenDudePropertyAttack:
|
2022-09-29 12:01:59 +00:00
|
|
|
pExtra->fireDist = getRangeAttackDist(actor, 187.5, 2812.5);
|
2021-12-29 19:03:42 +00:00
|
|
|
pExtra->throwDist = pExtra->fireDist; // temp
|
|
|
|
pExtra->baseDispersion = getDispersionModifier(actor, 200, 3500);
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
case kGenDudePropertyStates: {
|
|
|
|
|
|
|
|
pExtra->canFly = false;
|
|
|
|
|
|
|
|
// check the animation
|
|
|
|
int seqStartId = -1;
|
|
|
|
if (actor->xspr.data2 <= 0) seqStartId = actor->xspr.data2 = getDudeInfo(actor->spr.type)->seqStartID;
|
|
|
|
else seqStartId = actor->xspr.data2;
|
|
|
|
|
|
|
|
for (int i = seqStartId; i < seqStartId + kGenDudeSeqMax; i++) {
|
|
|
|
switch (i - seqStartId) {
|
|
|
|
case kGenDudeSeqIdleL:
|
|
|
|
case kGenDudeSeqDeathDefault:
|
|
|
|
case kGenDudeSeqAttackNormalL:
|
|
|
|
case kGenDudeSeqAttackThrow:
|
|
|
|
case kGenDudeSeqAttackPunch:
|
|
|
|
{
|
|
|
|
Seq* pSeq = getSequence(i);
|
|
|
|
if (!pSeq)
|
|
|
|
{
|
|
|
|
actor->xspr.data2 = getDudeInfo(actor->spr.type)->seqStartID;
|
|
|
|
viewSetSystemMessage("No SEQ animation id %d found for custom dude #%d!", i, actor->GetIndex());
|
|
|
|
viewSetSystemMessage("SEQ base id: %d", seqStartId);
|
|
|
|
}
|
|
|
|
else if ((i - seqStartId) == kGenDudeSeqAttackPunch)
|
|
|
|
{
|
|
|
|
pExtra->forcePunch = true; // required for those who don't have fire trigger in punch seq and for default animation
|
|
|
|
for (int ii = 0; ii < pSeq->nFrames; ii++) {
|
|
|
|
if (!pSeq->frames[ii].trigger) continue;
|
|
|
|
pExtra->forcePunch = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kGenDudeSeqDeathExplode:
|
|
|
|
pExtra->availDeaths[kDmgExplode] = !!getSequence(i);
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqBurning:
|
|
|
|
pExtra->canBurn = !!getSequence(i);
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqElectocuted:
|
|
|
|
pExtra->canElectrocute = !!getSequence(i);
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqRecoil:
|
|
|
|
pExtra->canRecoil = !!getSequence(i);
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqMoveL: {
|
|
|
|
bool oldStatus = pExtra->canWalk;
|
|
|
|
pExtra->canWalk = !!getSequence(i);
|
|
|
|
if (oldStatus != pExtra->canWalk) {
|
|
|
|
if (actor->GetTarget() == nullptr)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeIdleW);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeIdleL);
|
|
|
|
}
|
|
|
|
else if (pExtra->canWalk)
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseNoWalkW);
|
|
|
|
else if (inDuck(actor->xspr.aiState)) aiGenDudeNewState(actor, &genDudeChaseNoWalkD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseNoWalkL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kGenDudeSeqAttackNormalDW:
|
|
|
|
pExtra->canDuck = (getSequence(i) && getSequence(seqStartId + 14));
|
|
|
|
pExtra->canSwim = (getSequence(i) && getSequence(seqStartId + 13)
|
|
|
|
&& getSequence(seqStartId + 17));
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqDeathBurn1: {
|
|
|
|
bool seq15 = getSequence(i);
|
|
|
|
bool seq16 = getSequence(seqStartId + 16);
|
|
|
|
if (seq15 && seq16) pExtra->availDeaths[kDmgBurn] = 3;
|
|
|
|
else if (seq16) pExtra->availDeaths[kDmgBurn] = 2;
|
|
|
|
else if (seq15) pExtra->availDeaths[kDmgBurn] = 1;
|
|
|
|
else pExtra->availDeaths[kDmgBurn] = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kGenDudeSeqMoveW:
|
|
|
|
case kGenDudeSeqMoveD:
|
|
|
|
case kGenDudeSeqDeathBurn2:
|
|
|
|
case kGenDudeSeqIdleW:
|
|
|
|
break;
|
|
|
|
case kGenDudeSeqReserved3:
|
|
|
|
case kGenDudeSeqReserved4:
|
|
|
|
case kGenDudeSeqReserved5:
|
|
|
|
case kGenDudeSeqReserved6:
|
|
|
|
case kGenDudeSeqReserved7:
|
|
|
|
case kGenDudeSeqReserved8:
|
|
|
|
/*if (getSequence(i)) {
|
|
|
|
viewSetSystemMessage("Found reserved SEQ animation (%d) for custom dude #%d!", i, actor->GetIndex());
|
|
|
|
viewSetSystemMessage("Using reserved animation is not recommended.");
|
|
|
|
viewSetSystemMessage("SEQ base id: %d", seqStartId);
|
|
|
|
}*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
}
|
|
|
|
case kGenDudePropertyLeech:
|
|
|
|
pExtra->pLifeLeech = nullptr;
|
|
|
|
if (!actor->GetSpecialOwner())
|
|
|
|
{
|
|
|
|
BloodStatIterator it(kStatThing);
|
|
|
|
while (auto actor2 = it.Next())
|
|
|
|
{
|
|
|
|
if (actor2->GetOwner() == actor && actor2->spr.type == kModernThingEnemyLifeLeech) {
|
|
|
|
pExtra->pLifeLeech = actor2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
|
|
|
|
case kGenDudePropertySlaves:
|
|
|
|
{
|
|
|
|
pExtra->slaveCount = 0;
|
|
|
|
for (auto i = 0; i < kGenDudeMaxSlaves; i++)
|
|
|
|
{
|
|
|
|
pExtra->slave[i] = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
BloodStatIterator it(kStatDude);
|
|
|
|
while (auto actor2 = it.Next())
|
|
|
|
{
|
|
|
|
if (actor2->GetOwner() != actor) continue;
|
|
|
|
else if (!actor2->IsDudeActor() || !actor2->hasX() || actor2->xspr.health <= 0) {
|
|
|
|
actor2->SetOwner(nullptr);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pExtra->slave[pExtra->slaveCount++] = actor2;
|
|
|
|
if (pExtra->slaveCount > gGameOptions.nDifficulty)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (propId) break;
|
|
|
|
[[fallthrough]];
|
|
|
|
}
|
|
|
|
case kGenDudePropertySpriteSize: {
|
|
|
|
if (seqGetStatus(actor) == -1)
|
|
|
|
seqSpawn(actor->xspr.data2 + actor->xspr.aiState->seqId, actor, -1);
|
|
|
|
|
|
|
|
// make sure dudes aren't in the floor or ceiling
|
2022-09-16 17:02:37 +00:00
|
|
|
double zTop, zBot;
|
|
|
|
GetActorExtents(actor, &zTop, &zBot);
|
2021-12-30 15:51:56 +00:00
|
|
|
if (!(actor->sector()->ceilingstat & CSTAT_SECTOR_SKY))
|
2022-09-16 17:02:37 +00:00
|
|
|
actor->spr.pos.Z += max(actor->sector()->ceilingz - zTop, 0.);
|
2021-12-30 15:51:56 +00:00
|
|
|
if (!(actor->sector()->floorstat & CSTAT_SECTOR_SKY))
|
2022-09-16 17:02:37 +00:00
|
|
|
actor->spr.pos.Z += min(actor->sector()->floorz - zBot, 0.);
|
2021-12-29 19:03:42 +00:00
|
|
|
|
2022-10-07 21:33:37 +00:00
|
|
|
actor->clipdist = clamp((actor->spr.scale.X + actor->spr.scale.Y) * 8, 1., 30.);
|
2021-12-29 19:03:42 +00:00
|
|
|
if (propId) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 08:55:56 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-12-29 19:03:42 +00:00
|
|
|
void genDudePostDeath(DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
|
2021-05-06 08:55:56 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
if (damageType == kDamageExplode)
|
|
|
|
{
|
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(actor->spr.type);
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
if (pDudeInfo->nGibType[i] > -1)
|
|
|
|
GibSprite(actor, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
fxSpawnBlood(actor, damage);
|
|
|
|
}
|
|
|
|
|
|
|
|
gKillMgr.AddKill(actor);
|
|
|
|
|
|
|
|
actor->spr.type = kThingBloodChunks;
|
|
|
|
actPostSprite(actor, kStatThing);
|
2020-04-04 19:48:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-06 08:55:56 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void aiGenDudeInitSprite(DBloodActor* actor)
|
2020-12-04 22:21:42 +00:00
|
|
|
{
|
2021-12-29 19:03:42 +00:00
|
|
|
switch (actor->spr.type)
|
|
|
|
{
|
|
|
|
case kDudeModernCustom:
|
|
|
|
{
|
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
2022-05-05 23:11:44 +00:00
|
|
|
pDudeExtraE->active = pDudeExtraE->thinkTime = 0;
|
2021-12-29 19:03:42 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeIdleL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeModernCustomBurning:
|
|
|
|
aiGenDudeNewState(actor, &genDudeBurnGoto);
|
|
|
|
actor->xspr.burnTime = 1200;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
actor->spr.flags = 15;
|
|
|
|
return;
|
2020-04-04 19:48:23 +00:00
|
|
|
}
|
|
|
|
|
2020-01-26 20:01:22 +00:00
|
|
|
END_BLD_NS
|
2020-08-02 16:12:43 +00:00
|
|
|
#endif
|