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 "compat.h"
|
|
|
|
#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[] = {
|
2019-11-24 20:53:51 +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
|
|
|
|
short gCustomDudeDebrisPics[6] = {
|
|
|
|
|
|
|
|
2406, 2280, 2185, 2155, 2620, 3135
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2020-02-11 23:22:11 +00:00
|
|
|
GENDUDEEXTRA gGenDudeExtra[kMaxSprites]; // savegame handling in ai.cpp
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
static void forcePunch(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
2021-08-31 19:38:51 +00:00
|
|
|
if (actor->genDudeExtra().forcePunch && seqGetStatus(3, pSprite->extra) == -1)
|
2020-11-07 14:16:12 +00:00
|
|
|
punchCallback(0, actor);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
/*bool sameFamily(spritetype* pDude1, spritetype* pDude2) {
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}*/
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
static bool genDudeAdjustSlope(DBloodActor* actor, int dist, int weaponType, int by = 64)
|
2020-11-07 15:13:03 +00:00
|
|
|
{
|
|
|
|
spritetype* pSprite = &actor->s();
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
2021-08-31 19:52:26 +00:00
|
|
|
if (actor->GetTarget() != nullptr)
|
2020-11-07 15:13:03 +00:00
|
|
|
{
|
|
|
|
int fStart = 0;
|
|
|
|
int fEnd = 0;
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2019-12-06 19:34:19 +00:00
|
|
|
unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1;
|
2020-11-07 15:13:03 +00:00
|
|
|
|
|
|
|
for (int i = -8191; i < 8192; i += by)
|
|
|
|
{
|
2020-09-01 13:00:35 +00:00
|
|
|
HitScan(pSprite, pSprite->z, CosScale16(pSprite->ang), SinScale16(pSprite->ang), i, clipMask, dist);
|
2021-05-05 14:43:42 +00:00
|
|
|
if (!fStart && actor->GetTarget() == gHitInfo.hitactor) fStart = i;
|
|
|
|
else if (fStart && actor->GetTarget() != gHitInfo.hitactor)
|
2020-11-07 15:13:03 +00:00
|
|
|
{
|
|
|
|
fEnd = i;
|
|
|
|
break;
|
|
|
|
}
|
2019-12-05 20:42:35 +00:00
|
|
|
}
|
|
|
|
|
2020-11-07 15:13:03 +00:00
|
|
|
if (fStart != fEnd)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
2020-11-07 15:13:03 +00:00
|
|
|
{
|
2020-11-07 15:26:14 +00:00
|
|
|
actor->dudeSlope = fStart - ((fStart - fEnd) >> 2);
|
2020-11-07 15:13:03 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
2020-02-11 22:15:25 +00:00
|
|
|
const MissileType* pMissile = &missileInfo[pExtra->curWeapon - kMissileBase];
|
2020-11-07 15:26:14 +00:00
|
|
|
actor->dudeSlope = (fStart - ((fStart - fEnd) >> 2)) - (pMissile->clipDist << 1);
|
2019-12-05 20:42:35 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2019-11-08 19:57:01 +00:00
|
|
|
for (int i = 0; i < kGenDudePropertyMax; i++) {
|
2021-08-31 20:01:28 +00:00
|
|
|
if (pExtra->updReq[i]) genDudePrepare(&actor->s(), 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-09-20 18:16:53 +00:00
|
|
|
auto const pXSprite = &actor->x();
|
|
|
|
auto const pSprite = &actor->s();
|
2021-05-05 15:25:37 +00:00
|
|
|
auto const target = actor->GetTarget();
|
|
|
|
if (target != nullptr)
|
2020-11-07 14:16:12 +00:00
|
|
|
{
|
2020-02-05 21:17:05 +00:00
|
|
|
int nZOffset1 = getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat << 2;
|
2019-09-19 22:42:45 +00:00
|
|
|
int nZOffset2 = 0;
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-05-05 15:25:37 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
auto const pTarget = &target->s();
|
2021-05-05 15:25:37 +00:00
|
|
|
if(target->IsDudeActor())
|
2020-02-05 21:17:05 +00:00
|
|
|
nZOffset2 = getDudeInfo(pTarget->type)->eyeHeight * pTarget->yrepeat << 2;
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2020-09-01 13:00:35 +00:00
|
|
|
int dx = CosScale16(pSprite->ang);
|
|
|
|
int dy = SinScale16(pSprite->ang);
|
2019-09-21 11:02:17 +00:00
|
|
|
int dz = nZOffset1 - nZOffset2;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-05 17:15:25 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackMelee))
|
2021-09-16 19:34:16 +00:00
|
|
|
sfxPlay3DSound(actor, 530, 1, 0);
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-08-30 18:28:24 +00:00
|
|
|
actFireVector(actor, 0, 0, dx, dy, dz,kVectorGenDudePunch);
|
2019-09-19 22:42:45 +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-09-20 18:16:53 +00:00
|
|
|
auto const pXSprite = &actor->x();
|
|
|
|
auto const pSprite = &actor->s();
|
2020-11-07 14:16:12 +00:00
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
if (actor->GetTarget() == nullptr) return;
|
2019-12-11 19:48:42 +00:00
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
int dx, dy, dz;
|
2021-09-16 17:30:35 +00:00
|
|
|
actor->xvel() = actor->yvel() = 0;
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2019-12-11 19:48:42 +00:00
|
|
|
short dispersion = pExtra->baseDispersion;
|
2019-12-05 20:42:35 +00:00
|
|
|
if (inDuck(pXSprite->aiState))
|
|
|
|
dispersion = ClipLow(dispersion >> 1, kGenDudeMinDispesion);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
if (pExtra->weaponType == kGenDudeWeaponHitscan)
|
|
|
|
{
|
2020-11-07 15:26:14 +00:00
|
|
|
dx = CosScale16(pSprite->ang); dy = SinScale16(pSprite->ang); dz = actor->dudeSlope;
|
2019-09-19 22:42:45 +00:00
|
|
|
// dispersal modifiers here in case if non-melee enemy
|
2021-05-06 06:45:50 +00:00
|
|
|
if (!dudeIsMelee(actor))
|
2021-09-20 18:16:53 +00:00
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-08-30 18:28:24 +00:00
|
|
|
actFireVector(actor, 0, 0, dx, dy, dz,(VECTOR_TYPE)pExtra->curWeapon);
|
2021-05-05 17:15:25 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
2019-12-11 19:48:42 +00:00
|
|
|
sfxPlayVectorSound(pSprite, pExtra->curWeapon);
|
2021-09-20 18:16:53 +00:00
|
|
|
}
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponSummon)
|
|
|
|
{
|
2020-12-05 17:32:49 +00:00
|
|
|
DBloodActor* spawned = nullptr;
|
|
|
|
int dist = pSprite->clipdist << 4;
|
2021-09-20 18:16:53 +00:00
|
|
|
if (pExtra->slaveCount <= gGameOptions.nDifficulty)
|
|
|
|
{
|
2020-12-05 17:32:49 +00:00
|
|
|
if ((spawned = actSpawnDude(actor, pExtra->curWeapon, dist + Random(dist), 0)) != NULL)
|
|
|
|
{
|
|
|
|
spritetype* pSpawned = &spawned->s();
|
2020-11-07 14:16:12 +00:00
|
|
|
pSpawned->owner = pSprite->index;
|
2019-12-11 19:48:42 +00:00
|
|
|
|
2021-08-31 20:03:03 +00:00
|
|
|
if (xspriRangeIsFine(pSpawned->extra))
|
|
|
|
{
|
|
|
|
spawned->SetTarget(actor->GetTarget());
|
|
|
|
if (spawned->GetTarget() != nullptr)
|
|
|
|
aiActivateDude(spawned);
|
2019-12-11 19:48:42 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2020-09-01 19:27:32 +00:00
|
|
|
gKillMgr.AddNewKill(1);
|
2019-12-11 19:48:42 +00:00
|
|
|
pExtra->slave[pExtra->slaveCount++] = pSpawned->index;
|
2021-05-05 17:15:25 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
2021-08-31 20:03:03 +00:00
|
|
|
sfxPlay3DSoundCP(actor, 379, 1, 0, 0x10000 - Random3(0x3000));
|
2019-12-11 19:48:42 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2021-09-20 18:16:53 +00:00
|
|
|
}
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
2020-11-07 15:26:14 +00:00
|
|
|
dx = CosScale16(pSprite->ang); dy = SinScale16(pSprite->ang); dz = actor->dudeSlope;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
|
|
|
// dispersal modifiers here
|
2019-12-05 20:42:35 +00:00
|
|
|
dx += Random3(dispersion); dy += Random3(dispersion); dz += Random3(dispersion >> 1);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-12-05 20:23:53 +00:00
|
|
|
actFireMissile(actor, 0, 0, dx, dy, dz, pExtra->curWeapon);
|
2021-05-05 17:15:25 +00:00
|
|
|
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
|
2019-12-11 19:48:42 +00:00
|
|
|
sfxPlayMissileSound(pSprite, 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)
|
|
|
|
{
|
|
|
|
ThrowThing(actor, true);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
void ThrowCallback2(int, DBloodActor* actor)
|
|
|
|
{
|
|
|
|
ThrowThing(actor, false);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-07 14:16:12 +00:00
|
|
|
static void ThrowThing(DBloodActor* actor, bool impact)
|
|
|
|
{
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
spritetype* pSprite = &actor->s();
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
spritetype * pTarget = &actor->GetTarget()->s();
|
2019-09-19 22:42:45 +00:00
|
|
|
if (!(pTarget->type >= kDudeBase && pTarget->type < kDudeMax))
|
|
|
|
return;
|
|
|
|
|
2021-08-31 19:38:51 +00:00
|
|
|
int curWeapon = actor->genDudeExtra().curWeapon;
|
|
|
|
int weaponType = actor->genDudeExtra().weaponType;
|
2019-12-05 20:42:35 +00:00
|
|
|
if (weaponType != kGenDudeWeaponThrow) return;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-12-06 23:07:02 +00:00
|
|
|
const THINGINFO* pThinkInfo = &thingInfo[curWeapon - kThingBase];
|
2020-01-26 11:19:01 +00:00
|
|
|
if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return;
|
2021-05-05 17:15:25 +00:00
|
|
|
else if (!playGenDudeSound(actor, kGenDudeSndAttackThrow))
|
2021-09-16 19:34:16 +00:00
|
|
|
sfxPlay3DSound(actor, 455, -1, 0);
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
int zThrow = 14500;
|
|
|
|
int dx = pTarget->x - pSprite->x;
|
|
|
|
int dy = pTarget->y - pSprite->y;
|
|
|
|
int dz = pTarget->z - pSprite->z;
|
|
|
|
int dist = approxDist(dx, dy);
|
|
|
|
|
2021-05-05 19:06:38 +00:00
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
spritetype* pLeech = actLeech? &actLeech->s() : nullptr;
|
|
|
|
XSPRITE* pXLeech = actLeech && actLeech->hasX()? &actLeech->x() : nullptr;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
switch (curWeapon) {
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
case kThingDroppedLifeLeech:
|
|
|
|
zThrow = 5000;
|
|
|
|
// pickup life leech before throw it again
|
2021-05-05 19:18:09 +00:00
|
|
|
if (actLeech != NULL) removeLeech(actLeech);
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-12-05 19:22:29 +00:00
|
|
|
DBloodActor* spawned = nullptr;
|
|
|
|
if ((spawned = actFireThing(actor, 0, 0, (dz / 128) - zThrow, curWeapon, DivScale(dist / 540, 120, 23))) == nullptr) return;
|
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
auto const pSpawned = &spawned->s();
|
|
|
|
auto const pXSpawned = &spawned->x();
|
|
|
|
if (pThinkInfo->picnum < 0 && pSpawned->type != kModernThingThrowableRock) pSpawned->picnum = 0;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
pSpawned->owner = pSprite->index;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
switch (curWeapon) {
|
|
|
|
case kThingNapalmBall:
|
2021-08-31 19:56:50 +00:00
|
|
|
pSpawned->xrepeat = pSpawned->yrepeat = 24;
|
|
|
|
pXSpawned->data4 = 3 + gGameOptions.nDifficulty;
|
2019-11-08 19:57:01 +00:00
|
|
|
impact = true;
|
|
|
|
break;
|
|
|
|
case kModernThingThrowableRock:
|
2021-08-31 19:56:50 +00:00
|
|
|
pSpawned->picnum = gCustomDudeDebrisPics[Random(5)];
|
|
|
|
pSpawned->xrepeat = pSpawned->yrepeat = 24 + Random(42);
|
|
|
|
pSpawned->cstat |= 0x0001;
|
|
|
|
pSpawned->pal = 5;
|
|
|
|
|
|
|
|
if (Chance(0x5000)) pSpawned->cstat |= 0x0004;
|
|
|
|
if (Chance(0x5000)) pSpawned->cstat |= 0x0008;
|
|
|
|
|
|
|
|
if (pSpawned->xrepeat > 60) pXSpawned->data1 = 43;
|
|
|
|
else if (pSpawned->xrepeat > 40) pXSpawned->data1 = 33;
|
|
|
|
else if (pSpawned->xrepeat > 30) pXSpawned->data1 = 23;
|
|
|
|
else pXSpawned->data1 = 12;
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2019-11-08 19:57:01 +00:00
|
|
|
case kThingTNTBarrel:
|
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedSpray:
|
|
|
|
impact = false;
|
|
|
|
break;
|
|
|
|
case kModernThingTNTProx:
|
2021-08-31 19:56:50 +00:00
|
|
|
pXSpawned->state = 0;
|
|
|
|
pXSpawned->Proximity = true;
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
2021-08-31 19:56:50 +00:00
|
|
|
if (pLeech != NULL) pXSpawned->health = pXLeech->health;
|
|
|
|
else pXSpawned->health = ((pThinkInfo->startHealth << 4) * gGameOptions.nDifficulty) >> 1;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-09-16 19:34:16 +00:00
|
|
|
sfxPlay3DSound(actor, 490, -1, 0);
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
pXSpawned->data3 = 512 / (gGameOptions.nDifficulty + 1);
|
|
|
|
pSpawned->cstat &= ~CSTAT_SPRITE_BLOCK;
|
|
|
|
pSpawned->pal = 6;
|
|
|
|
pSpawned->clipdist = 0;
|
2021-05-06 07:00:49 +00:00
|
|
|
spawned->SetTarget(actor->GetTarget());
|
2021-08-31 19:56:50 +00:00
|
|
|
pXSpawned->Proximity = true;
|
|
|
|
pXSpawned->stateTimer = 1;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-05 19:06:38 +00:00
|
|
|
actor->genDudeExtra().pLifeLeech = spawned;
|
2021-08-31 19:56:50 +00:00
|
|
|
evPost(spawned, 80, kCallbackLeechStateTimer);
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-08-31 19:56:50 +00:00
|
|
|
if (impact == true && dist <= 7680) pXSpawned->Impact = true;
|
2019-11-08 19:57:01 +00:00
|
|
|
else {
|
2021-08-31 19:56:50 +00:00
|
|
|
pXSpawned->Impact = false;
|
|
|
|
evPost(spawned, 120 * Random(2) + 120, kCmdOn);
|
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)
|
|
|
|
{
|
|
|
|
auto pXSprite = &actor->x();
|
2019-12-05 20:42:35 +00:00
|
|
|
// TO DO: if can't see the target, but in fireDist range - stop moving and look around
|
|
|
|
|
|
|
|
//viewSetSystemMessage("IN SEARCH");
|
2021-09-15 22:17:37 +00:00
|
|
|
aiChooseDirection(actor,pXSprite->goalAng);
|
2021-09-16 18:49:25 +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-09-20 18:16:53 +00:00
|
|
|
auto const pXSprite = &actor->x();
|
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
|
|
|
|
{
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
int dx = pXSprite->targetX - pSprite->x;
|
|
|
|
int dy = pXSprite->targetY - pSprite->y;
|
2019-09-19 22:42:45 +00:00
|
|
|
int nAngle = getangle(dx, dy);
|
|
|
|
|
2021-09-15 22:17:37 +00:00
|
|
|
aiChooseDirection(actor,nAngle);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
|
|
|
// if reached target, change to search mode
|
2021-09-20 18:42:23 +00:00
|
|
|
if (approxDist(dx, dy) < 5120 && abs(pSprite->ang - nAngle) < getDudeInfo(pSprite->type)->periphery)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2020-11-06 21:48:22 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-11-07 15:13:03 +00:00
|
|
|
auto const pXSprite = &actor->x();
|
|
|
|
auto const pSprite = &actor->s();
|
2019-11-08 19:57:01 +00:00
|
|
|
if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax) return;
|
2021-08-31 19:52:26 +00:00
|
|
|
|
|
|
|
auto const targetactor = actor->GetTarget();
|
|
|
|
if (targetactor == nullptr)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if(spriteIsUnderwater(actor,false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2021-08-31 20:01:28 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
genDudeUpdate(actor);
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-09-10 20:36:55 +00:00
|
|
|
XSPRITE* pXTarget = !targetactor || !targetactor->IsDudeActor() || !targetactor->hasX() ? nullptr : &targetactor->x();
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-05 14:43:42 +00:00
|
|
|
if (pXTarget == nullptr) // target lost
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if(spriteIsUnderwater(actor,false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
2021-05-05 07:47:08 +00:00
|
|
|
actor->SetTarget(nullptr);
|
2019-11-24 20:53:51 +00:00
|
|
|
return;
|
2021-05-05 14:43:42 +00:00
|
|
|
}
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-05-05 14:43:42 +00:00
|
|
|
auto const pTarget = &targetactor->s();
|
|
|
|
if (pXTarget->health <= 0) // target is dead
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
PLAYER* pPlayer = NULL;
|
2021-09-20 18:42:23 +00:00
|
|
|
if ((!IsPlayerSprite(pTarget)) || ((pPlayer = getPlayerById(pTarget->type)) != NULL && pPlayer->fraggerId == pSprite->index))
|
|
|
|
{
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor, kGenDudeSndTargetDead);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
2021-05-05 07:47:08 +00:00
|
|
|
actor->SetTarget(nullptr);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
// check target
|
2021-08-31 20:03:03 +00:00
|
|
|
int dx = pTarget->x - pSprite->x;
|
|
|
|
int dy = pTarget->y - pSprite->y;
|
2019-11-24 20:53:51 +00:00
|
|
|
int dist = ClipLow((int)approxDist(dx, dy), 1);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-08 19:57:01 +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.
|
2021-08-31 20:03:03 +00:00
|
|
|
int xvelocity = actor->xvel();
|
|
|
|
int yvelocity = actor->yvel();
|
2019-11-24 20:53:51 +00:00
|
|
|
if (inAttack(pXSprite->aiState))
|
|
|
|
xvelocity = yvelocity = ClipLow(pSprite->clipdist >> 1, 1);
|
|
|
|
|
2021-09-15 22:17:37 +00:00
|
|
|
//aiChooseDirection(actor,getangle(dx, dy));
|
2021-08-31 19:59:32 +00:00
|
|
|
aiGenDudeChooseDirection(actor, getangle(dx, dy), xvelocity, yvelocity);
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2021-09-20 18:16:53 +00:00
|
|
|
if (!pExtra->canAttack)
|
|
|
|
{
|
2021-09-16 18:38:46 +00:00
|
|
|
if (pExtra->canWalk) aiSetTarget(actor, actor); // targeting self???
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeGotoL);
|
2019-11-24 20:53:51 +00:00
|
|
|
return;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (IsPlayerSprite(pTarget))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
|
2021-09-20 18:42:23 +00:00
|
|
|
if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchShortL);
|
2021-05-05 07:47:08 +00:00
|
|
|
actor->SetTarget(nullptr);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 21:17:05 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2019-12-05 20:42:35 +00:00
|
|
|
int losAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
|
|
|
|
int eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;
|
|
|
|
|
2019-09-21 11:02:17 +00:00
|
|
|
if (dist > pDudeInfo->seeDist || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
|
2021-09-20 18:42:23 +00:00
|
|
|
pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum))
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
2021-05-05 07:47:08 +00:00
|
|
|
actor->SetTarget(nullptr);
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// is the target visible?
|
2021-01-04 12:02:00 +00:00
|
|
|
if (dist < pDudeInfo->seeDist && abs(losAngle) <= pDudeInfo->periphery) {
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
if ((PlayClock & 64) == 0 && Chance(0x3000) && !spriteIsUnderwater(actor, false))
|
2021-05-05 17:15:25 +00:00
|
|
|
playGenDudeSound(actor, kGenDudeSndChasing);
|
2019-09-07 13:15:39 +00:00
|
|
|
|
2021-01-04 11:59:00 +00:00
|
|
|
actor->dudeSlope = DivScale(pTarget->z - pSprite->z, dist, 10);
|
2019-09-07 13:15:39 +00:00
|
|
|
|
2021-08-31 19:38:51 +00:00
|
|
|
int curWeapon = actor->genDudeExtra().curWeapon;
|
|
|
|
int weaponType = actor->genDudeExtra().weaponType;
|
2021-05-05 19:06:38 +00:00
|
|
|
|
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
spritetype* pLeech = actLeech? &actLeech->s() : nullptr;
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
const VECTORDATA* meleeVector = &gVectorData[22];
|
|
|
|
if (weaponType == kGenDudeWeaponThrow)
|
|
|
|
{
|
|
|
|
if (abs(losAngle) < kAng15)
|
|
|
|
{
|
|
|
|
if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (dist < 12264 && dist > 7680 && !spriteIsUnderwater(actor, false) && curWeapon != kModernThingEnemyLifeLeech)
|
2021-09-20 18:42:23 +00:00
|
|
|
{
|
2019-09-21 11:02:17 +00:00
|
|
|
int pHit = HitScan(pSprite, pSprite->z, dx, dy, 0, 16777280, 0);
|
|
|
|
switch (pHit) {
|
2019-11-08 19:57:01 +00:00
|
|
|
case 0:
|
|
|
|
case 4:
|
|
|
|
return;
|
|
|
|
default:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeThrow);
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
}
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (dist > 4072 && dist <= 11072 && !spriteIsUnderwater(actor, false) && pSprite->owner != (kMaxSprites - 1))
|
2021-09-20 18:16:53 +00:00
|
|
|
{
|
|
|
|
switch (curWeapon)
|
|
|
|
{
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
{
|
|
|
|
if (pLeech == NULL)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
2019-11-24 20:53:51 +00:00
|
|
|
genDudeThrow2.nextState = &genDudeDodgeShortL;
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-09-21 11:02:17 +00:00
|
|
|
XSPRITE* pXLeech = &xsprite[pLeech->extra];
|
2020-02-07 19:47:43 +00:00
|
|
|
int ldist = aiFightGetTargetDist(pTarget, pDudeInfo, pLeech);
|
2019-09-21 11:02:17 +00:00
|
|
|
if (ldist > 3 || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
|
2021-09-20 18:42:23 +00:00
|
|
|
pLeech->x, pLeech->y, pLeech->z, pLeech->sectnum) || pXLeech->target_i == -1)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
2019-11-24 20:53:51 +00:00
|
|
|
genDudeThrow2.nextState = &genDudeDodgeShortL;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
genDudeThrow2.nextState = &genDudeChaseL;
|
2021-09-20 18:42:23 +00:00
|
|
|
if (dist > 5072 && Chance(0x5000))
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (!canDuck(pSprite) || Chance(0x4000)) aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortD);
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
2019-10-11 21:59:39 +00:00
|
|
|
case kModernThingThrowableRock:
|
2021-08-31 19:52:26 +00:00
|
|
|
if (Chance(0x4000)) aiGenDudeNewState(actor, &genDudeThrow2);
|
2021-05-05 17:15:25 +00:00
|
|
|
else playGenDudeSound(actor, kGenDudeSndTargetSpot);
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
|
|
|
default:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeThrow2);
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
} else if (dist <= meleeVector->maxDist)
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false))
|
2021-09-20 18:42:23 +00:00
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeW);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeL);
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-06 07:00:49 +00:00
|
|
|
int state = checkAttackState(actor);
|
2021-08-31 19:52:26 +00:00
|
|
|
if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-09-20 18:42:23 +00:00
|
|
|
else if (state == 2)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (Chance(0x5000)) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-09-21 11:02:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
int vdist; int mdist; int defDist;
|
2021-08-31 19:38:51 +00:00
|
|
|
defDist = vdist = mdist = actor->genDudeExtra().fireDist;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if ((vdist = gVectorData[curWeapon].maxDist) <= 0)
|
2019-09-21 11:02:17 +00:00
|
|
|
vdist = defDist;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponSummon)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
// don't attack slaves
|
2021-09-16 18:38:46 +00:00
|
|
|
if (actor->GetTarget() != nullptr && actor->GetTarget()->GetOwner() == actor)
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, pSprite->x, pSprite->y, pSprite->z);
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2021-09-20 18:16:53 +00:00
|
|
|
}
|
2021-08-31 19:38:51 +00:00
|
|
|
else if (actor->genDudeExtra().slaveCount > gGameOptions.nDifficulty || dist < meleeVector->maxDist)
|
2021-09-20 18:16:53 +00:00
|
|
|
{
|
|
|
|
if (dist <= meleeVector->maxDist)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudePunch);
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
int state = checkAttackState(actor);
|
|
|
|
if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
|
|
|
{
|
2019-09-21 11:02:17 +00:00
|
|
|
// special handling for flame, explosive and life leech missiles
|
2020-11-06 21:48:22 +00:00
|
|
|
int state = checkAttackState(&bloodActors[pXSprite->reference]);
|
2021-09-20 18:42:23 +00:00
|
|
|
switch (curWeapon)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
case kMissileLifeLeechRegular:
|
|
|
|
// pickup life leech if it was thrown previously
|
2021-05-05 19:18:09 +00:00
|
|
|
if (actLeech != NULL) removeLeech(actLeech);
|
2019-12-05 20:42:35 +00:00
|
|
|
mdist = 1500;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kMissileFlareAlt:
|
2019-12-05 20:42:35 +00:00
|
|
|
mdist = 2500;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-11-08 19:57:01 +00:00
|
|
|
case kMissileFireball:
|
2021-08-22 07:49:02 +00:00
|
|
|
case kMissileFireballNapalm:
|
2019-11-08 19:57:01 +00:00
|
|
|
case kMissileFireballCerberus:
|
|
|
|
case kMissileFireballTchernobog:
|
2019-12-05 20:42:35 +00:00
|
|
|
if (mdist == defDist) mdist = 3000;
|
2019-11-08 19:57:01 +00:00
|
|
|
if (dist > mdist || pXSprite->locked == 1) break;
|
|
|
|
else if (dist <= meleeVector->maxDist && Chance(0x9000))
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else if (state == 1) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (state == 2) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2019-11-08 19:57:01 +00:00
|
|
|
case kMissileFlameSpray:
|
|
|
|
case kMissileFlameHound:
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("%d", pXTarget->burnTime);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false))
|
2021-09-20 18:42:23 +00:00
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (dist > meleeVector->maxDist) aiGenDudeNewState(actor, &genDudeChaseW);
|
|
|
|
else if (Chance(0x8000)) aiGenDudeNewState(actor, &genDudePunch);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortW);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (dist <= 4000 && pXTarget->burnTime >= 2000 && pXTarget->burnSource == pSprite->index)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
if (dist > meleeVector->maxDist) aiGenDudeNewState(actor, &genDudeChaseL);
|
|
|
|
else aiGenDudeNewState(actor, &genDudePunch);
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-12-05 20:42:35 +00:00
|
|
|
vdist = 3500 + (gGameOptions.nDifficulty * 400);
|
2019-09-21 11:02:17 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponKamikaze)
|
|
|
|
{
|
2020-02-11 22:15:25 +00:00
|
|
|
int nType = curWeapon - kTrapExploder; const EXPLOSION* pExpl = &explodeInfo[nType];
|
2021-08-31 20:03:03 +00:00
|
|
|
if (CheckProximity(pSprite, pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pExpl->radius >> 1))
|
|
|
|
{
|
|
|
|
actor->xvel() = actor->yvel() = actor->zvel() = 0;
|
2019-11-08 19:57:01 +00:00
|
|
|
if (doExplosion(pSprite, nType) && pXSprite->health > 0)
|
2021-05-06 07:00:49 +00:00
|
|
|
actDamageSprite(actor, actor, kDamageExplode, 65535);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
return;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
int state = checkAttackState(actor);
|
2021-05-06 06:45:50 +00:00
|
|
|
int kAngle = (dudeIsMelee(actor) || dist <= kGenDudeMaxMeleeDist) ? pDudeInfo->periphery : kGenDudeKlabsAng;
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (dist < vdist && abs(losAngle) < kAngle)
|
|
|
|
{
|
|
|
|
if (pExtra->canWalk)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
int objDist = -1; int targetDist = -1; int hit = -1;
|
|
|
|
if (weaponType == kGenDudeWeaponHitscan)
|
2020-11-07 15:26:14 +00:00
|
|
|
hit = HitScan(pSprite, pSprite->z, CosScale16(pSprite->ang), SinScale16(pSprite->ang), actor->dudeSlope, CLIPMASK1, dist);
|
2019-12-05 20:42:35 +00:00
|
|
|
else if (weaponType == kGenDudeWeaponMissile)
|
2020-11-07 15:26:14 +00:00
|
|
|
hit = HitScan(pSprite, pSprite->z, CosScale16(pSprite->ang), SinScale16(pSprite->ang), actor->dudeSlope, CLIPMASK0, dist);
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (hit >= 0)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
targetDist = dist - (pTarget->clipdist << 2);
|
|
|
|
objDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
|
|
|
|
}
|
|
|
|
|
2021-05-05 14:43:42 +00:00
|
|
|
if (actor != gHitInfo.hitactor && targetDist > objDist)
|
|
|
|
{
|
|
|
|
DBloodActor* hitactor = nullptr;
|
|
|
|
walltype* pHWall = NULL;
|
|
|
|
XWALL* pXHWall = NULL;
|
|
|
|
spritetype* pHSprite = NULL;
|
|
|
|
XSPRITE* pXHSprite = NULL;
|
|
|
|
bool hscn = false;
|
|
|
|
bool blck = false;
|
|
|
|
bool failed = false;
|
|
|
|
|
|
|
|
switch (hit)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
case 3:
|
2021-05-05 14:43:42 +00:00
|
|
|
hitactor = gHitInfo.hitactor;
|
|
|
|
if (hitactor)
|
|
|
|
{
|
|
|
|
pHSprite = &hitactor->s();
|
|
|
|
pXHSprite = &hitactor->x();
|
|
|
|
hscn = (pHSprite->cstat & CSTAT_SPRITE_BLOCK_HITSCAN); blck = (pHSprite->cstat & CSTAT_SPRITE_BLOCK);
|
|
|
|
}
|
2019-12-05 20:42:35 +00:00
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
case 4:
|
|
|
|
pHWall = &wall[gHitInfo.hitwall];
|
|
|
|
if (xwallRangeIsFine(pHWall->extra)) pXHWall = &xwall[pHWall->extra];
|
|
|
|
hscn = (pHWall->cstat & CSTAT_WALL_BLOCK_HITSCAN); blck = (pHWall->cstat & CSTAT_WALL_BLOCK);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (hit) {
|
|
|
|
case 0:
|
|
|
|
//if (hit == 0) viewSetSystemMessage("WALL HIT %d", gHitInfo.hitwall);
|
2021-09-20 18:42:23 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
case 1:
|
|
|
|
//if (hit == 1) viewSetSystemMessage("CEIL HIT %d", gHitInfo.hitsect);
|
2021-09-20 18:42:23 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
case 2:
|
2019-12-06 19:34:19 +00:00
|
|
|
//if (hit == 2) viewSetSystemMessage("FLOOR HIT %d", gHitInfo.hitsect);
|
2020-11-07 15:13:03 +00:00
|
|
|
if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(actor, dist, weaponType)
|
2019-12-05 20:42:35 +00:00
|
|
|
&& dist < (int)(6000 + Random(2000)) && pExtra->baseDispersion < kGenDudeMaxDispersion >> 1) break;
|
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
|
|
|
case 3:
|
|
|
|
if (pHSprite->statnum == kStatFX || pHSprite->statnum == kStatProjectile || pHSprite->statnum == kStatDebris)
|
|
|
|
break;
|
2021-09-20 18:42:23 +00:00
|
|
|
if (IsDudeSprite(pHSprite) && (weaponType != kGenDudeWeaponHitscan || hscn))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
// dodge a bit in sides
|
2021-09-20 18:42:23 +00:00
|
|
|
if (pXHSprite->target_i != pSprite->index)
|
|
|
|
{
|
|
|
|
if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
|
2019-12-05 20:42:35 +00:00
|
|
|
}
|
2021-05-05 18:40:31 +00:00
|
|
|
else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShortW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShortD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShortL);
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
switch (pHSprite->type)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
case kDudeModernCustom: // and make dude which could be hit to dodge too
|
2021-05-06 06:45:50 +00:00
|
|
|
if (!dudeIsMelee(hitactor) && Chance(dist << 4))
|
2021-09-20 18:42:23 +00:00
|
|
|
{
|
|
|
|
if (!inAttack(pXHSprite->aiState))
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(hitactor)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(hitactor, &genDudeDodgeShorterL);
|
2019-12-05 20:42:35 +00:00
|
|
|
|
|
|
|
// preferable in opposite sides
|
2021-09-20 18:42:23 +00:00
|
|
|
if (Chance(0x8000))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (pXSprite->dodgeDir == 1) pXHSprite->dodgeDir = -1;
|
|
|
|
else if (pXSprite->dodgeDir == -1) pXHSprite->dodgeDir = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-11-07 15:13:03 +00:00
|
|
|
if (pSprite->x < pHSprite->x)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
|
|
|
|
else pXSprite->dodgeDir = 1;
|
2020-11-07 15:13:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
|
|
|
|
else pXSprite->dodgeDir = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2020-11-07 15:13:03 +00:00
|
|
|
if (pSprite->x < pHSprite->x)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
|
|
|
|
else pXSprite->dodgeDir = 1;
|
2020-11-07 15:13:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x9000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
|
|
|
|
else pXSprite->dodgeDir = -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
2020-11-07 15:13:03 +00:00
|
|
|
}
|
|
|
|
else if (weaponType == kGenDudeWeaponHitscan && hscn)
|
|
|
|
{
|
|
|
|
if (genDudeAdjustSlope(actor, dist, weaponType)) break;
|
2020-11-07 15:26:14 +00:00
|
|
|
VectorScan(pSprite, 0, 0, CosScale16(pSprite->ang), SinScale16(pSprite->ang), actor->dudeSlope, dist, 1);
|
2021-05-05 14:43:42 +00:00
|
|
|
if (actor == gHitInfo.hitactor) break;
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2020-02-15 21:53:21 +00:00
|
|
|
bool immune = nnExtIsImmune(pHSprite, gVectorData[curWeapon].dmgType);
|
2020-11-07 15:13:03 +00:00
|
|
|
if (!(pXHSprite != NULL && (!immune || (immune && pHSprite->statnum == kStatThing && pXHSprite->Vector)) && !pXHSprite->locked))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if ((approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y) <= 1500 && !blck)
|
2020-11-07 15:13:03 +00:00
|
|
|
|| (dist <= (int)(pExtra->fireDist / ClipLow(Random(4), 1))))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("GO CHASE");
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int wd1 = picWidth(pHSprite->picnum, pHSprite->xrepeat);
|
|
|
|
int wd2 = picWidth(pSprite->picnum, pSprite->xrepeat);
|
2021-09-20 18:42:23 +00:00
|
|
|
if (wd1 < (wd2 << 3))
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("OBJ SIZE: %d DUDE SIZE: %d", wd1, wd2);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (pSprite->x < pHSprite->x)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = -1;
|
|
|
|
else pXSprite->dodgeDir = 1;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (Chance(0x3000) && pTarget->x > pHSprite->x) pXSprite->dodgeDir = 1;
|
|
|
|
else pXSprite->dodgeDir = -1;
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (((gSpriteHit[pSprite->extra].hit & 0xc000) == 0x8000) || ((gSpriteHit[pSprite->extra].hit & 0xc000) == 0xc000))
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
pXSprite->goalAng = Random(kAng360);
|
|
|
|
//viewSetSystemMessage("WALL OR SPRITE TOUCH");
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("TOO BIG OBJECT TO DODGE!!!!!!!!");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
case 4:
|
2021-09-20 18:42:23 +00:00
|
|
|
if (hit == 4 && weaponType == kGenDudeWeaponHitscan && hscn)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
bool masked = (pHWall->cstat & CSTAT_WALL_MASKED);
|
2020-11-07 15:26:14 +00:00
|
|
|
if (masked) VectorScan(pSprite, 0, 0, CosScale16(pSprite->ang), SinScale16(pSprite->ang), actor->dudeSlope, dist, 1);
|
2019-12-05 20:42:35 +00:00
|
|
|
|
|
|
|
//viewSetSystemMessage("WALL VHIT: %d", gHitInfo.hitwall);
|
2021-05-05 14:43:42 +00:00
|
|
|
if ((actor != gHitInfo.hitactor) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked))
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (hit >= 3 && weaponType == kGenDudeWeaponMissile && blck)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
switch (curWeapon) {
|
|
|
|
case kMissileLifeLeechRegular:
|
|
|
|
case kMissileTeslaAlt:
|
|
|
|
case kMissileFlareAlt:
|
|
|
|
case kMissileFireball:
|
2021-08-22 07:49:02 +00:00
|
|
|
case kMissileFireballNapalm:
|
2019-12-05 20:42:35 +00:00
|
|
|
case kMissileFireballCerberus:
|
2021-09-20 18:42:23 +00:00
|
|
|
case kMissileFireballTchernobog:
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
// allow attack if dude is far from object, but target is close to it
|
|
|
|
int dudeDist = approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y);
|
|
|
|
int targetDist = approxDist(gHitInfo.hitx - pTarget->x, gHitInfo.hity - pTarget->y);
|
2021-09-20 18:42:23 +00:00
|
|
|
if (dudeDist < mdist)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist);
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (targetDist <= mdist >> 1)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
//viewSetSystemMessage("TARGET CLOSE TO OBJ: %d, MDIST: %d", targetDist, mdist >> 1);
|
|
|
|
break;
|
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
//viewSetSystemMessage("DEF HIT: %d, MDIST: %d", hit, mdist);
|
|
|
|
if (hit == 4) failed = (pHWall->type != kWallGib || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked);
|
2021-09-20 18:42:23 +00:00
|
|
|
else if (hit == 3 && (failed = (pHSprite->statnum != kStatThing || pXHSprite == NULL || pXHSprite->locked)) == false)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
// check also for damage resistance (all possible damages missile can use)
|
2021-09-20 18:42:23 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
|
|
|
{
|
2020-02-15 21:53:21 +00:00
|
|
|
if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(pHSprite, i)) == false)
|
2019-12-05 20:42:35 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
if (failed)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeSearchW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeSearchL);
|
2019-12-05 20:42:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 19:34:19 +00:00
|
|
|
|
2021-05-05 07:47:08 +00:00
|
|
|
aiSetTarget(actor, actor->GetTarget());
|
2021-09-20 18:42:23 +00:00
|
|
|
switch (state)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
case 1:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeFireW);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireW;
|
2019-12-05 20:42:35 +00:00
|
|
|
break;
|
2019-11-08 19:57:01 +00:00
|
|
|
case 2:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeFireD);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireD;
|
2019-12-05 20:42:35 +00:00
|
|
|
break;
|
2019-11-08 19:57:01 +00:00
|
|
|
default:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeFireL);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireL;
|
2019-12-05 20:42:35 +00:00
|
|
|
break;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (seqGetID(3, pSprite->extra) == pXSprite->data2 + ((state < 3) ? 8 : 6))
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
if (state == 1) pXSprite->aiState->nextState = &genDudeChaseW;
|
|
|
|
else if (state == 2) pXSprite->aiState->nextState = &genDudeChaseD;
|
|
|
|
else pXSprite->aiState->nextState = &genDudeChaseL;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (state == 1 && pXSprite->aiState != &genDudeChaseW && pXSprite->aiState != &genDudeFireW)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeChaseW);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireW;
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (state == 2 && pXSprite->aiState != &genDudeChaseD && pXSprite->aiState != &genDudeFireD)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeChaseD);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireD;
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
}
|
|
|
|
else if (pXSprite->aiState != &genDudeChaseL && pXSprite->aiState != &genDudeFireL)
|
|
|
|
{
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeChaseL);
|
2019-11-24 20:53:51 +00:00
|
|
|
pXSprite->aiState->nextState = &genDudeFireL;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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)
|
|
|
|
{
|
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
2021-05-05 18:40:31 +00:00
|
|
|
if (dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor,false))
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if ( !dudeIsPlayingSeq(actor, 14) || spriteIsUnderwater(actor,false))
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor,false))
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
|
|
|
return 1; //water
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 2; //duck
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return 3; //land
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-08-31 19:59:32 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
static int getGenDudeMoveSpeed(DBloodActor *actor,int which, bool mul, bool shift)
|
|
|
|
{
|
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
auto const pXSprite = &actor->x();
|
2020-02-05 21:17:05 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2019-09-19 22:42:45 +00:00
|
|
|
int speed = -1; int step = 2500; int maxSpeed = 146603;
|
2021-08-31 19:59:32 +00:00
|
|
|
switch(which)
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
case 0:
|
|
|
|
speed = pDudeInfo->frontSpeed;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
speed = pDudeInfo->sideSpeed;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
speed = pDudeInfo->backSpeed;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
speed = pDudeInfo->angSpeed;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (pXSprite->busyTime > 0) speed /=3;
|
2021-08-31 19:59:32 +00:00
|
|
|
if (speed > 0 && mul)
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
if (pXSprite->busyTime > 0)
|
|
|
|
speed += (step * pXSprite->busyTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shift) speed *= 4 >> 4;
|
|
|
|
if (speed > maxSpeed) speed = maxSpeed;
|
|
|
|
|
|
|
|
return speed;
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2020-11-06 21:48:22 +00:00
|
|
|
void aiGenDudeMoveForward(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
auto pXSprite = &actor->x();
|
|
|
|
auto pSprite = &actor->s();
|
2020-02-05 21:17:05 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2019-09-19 22:42:45 +00:00
|
|
|
int maxTurn = pDudeInfo->angSpeed * 4 >> 4;
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
if (pExtra->canFly)
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
int nAng = ((pXSprite->goalAng + 1024 - pSprite->ang) & 2047) - 1024;
|
|
|
|
int nTurnRange = (pDudeInfo->angSpeed << 2) >> 4;
|
|
|
|
pSprite->ang = (pSprite->ang + ClipRange(nAng, -nTurnRange, nTurnRange)) & 2047;
|
|
|
|
int nAccel = pDudeInfo->frontSpeed << 2;
|
2021-01-04 12:02:00 +00:00
|
|
|
if (abs(nAng) > 341)
|
2019-11-24 20:53:51 +00:00
|
|
|
return;
|
2021-05-05 07:47:08 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2019-11-24 20:53:51 +00:00
|
|
|
pSprite->ang = (pSprite->ang + 256) & 2047;
|
|
|
|
int dx = pXSprite->targetX - pSprite->x;
|
|
|
|
int dy = pXSprite->targetY - pSprite->y;
|
2020-08-28 09:01:39 +00:00
|
|
|
int nDist = approxDist(dx, dy);
|
2019-11-24 20:53:51 +00:00
|
|
|
if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
|
|
|
|
return;
|
|
|
|
int nCos = Cos(pSprite->ang);
|
|
|
|
int nSin = Sin(pSprite->ang);
|
2021-05-06 07:00:49 +00:00
|
|
|
int vx = actor->xvel();
|
|
|
|
int vy = actor->yvel();
|
2021-01-04 10:40:08 +00:00
|
|
|
int t1 = DMulScale(vx, nCos, vy, nSin, 30);
|
|
|
|
int t2 = DMulScale(vx, nSin, -vy, nCos, 30);
|
2021-05-05 07:47:08 +00:00
|
|
|
if (actor->GetTarget() == nullptr)
|
2019-11-24 20:53:51 +00:00
|
|
|
t1 += nAccel;
|
|
|
|
else
|
|
|
|
t1 += nAccel >> 1;
|
2021-05-06 07:00:49 +00:00
|
|
|
actor->xvel() = DMulScale(t1, nCos, t2, nSin, 30);
|
|
|
|
actor->yvel() = DMulScale(t1, nSin, -t2, nCos, 30);
|
2021-09-20 18:16:53 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
int dang = ((kAng180 + pXSprite->goalAng - pSprite->ang) & 2047) - kAng180;
|
|
|
|
pSprite->ang = ((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & 2047);
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
// don't move forward if trying to turn around
|
2021-01-04 12:02:00 +00:00
|
|
|
if (abs(dang) > kAng60)
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
int sin = Sin(pSprite->ang);
|
|
|
|
int cos = Cos(pSprite->ang);
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
int frontSpeed = gGenDudeExtra[pSprite->index].moveSpeed;
|
2021-05-06 07:00:49 +00:00
|
|
|
actor->xvel() += MulScale(cos, frontSpeed, 30);
|
|
|
|
actor->yvel() += MulScale(sin, frontSpeed, 30);
|
2019-11-24 20:53:51 +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
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-08-31 19:59:32 +00:00
|
|
|
void aiGenDudeChooseDirection(DBloodActor* actor, int a3, int xvel, int yvel)
|
|
|
|
{
|
|
|
|
auto const pXSprite = &actor->x();
|
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
|
|
|
|
{
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-09-15 22:17:37 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
// TO-DO: Take in account if sprite is flip-x, so enemy select correct angle
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
int vc = ((a3 + 1024 - pSprite->ang) & 2047) - 1024;
|
2021-01-04 10:40:08 +00:00
|
|
|
int t1 = DMulScale(xvel, Cos(pSprite->ang), yvel, Sin(pSprite->ang), 30);
|
2019-11-08 19:57:01 +00:00
|
|
|
int vsi = ((t1 * 15) >> 12) / 2; int v8 = (vc >= 0) ? 341 : -341;
|
|
|
|
|
2021-09-15 22:52:54 +00:00
|
|
|
if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang + vc;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc / 2, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang + vc / 2;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - vc / 2, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang - vc / 2;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + v8, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang + v8;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang;
|
2021-09-15 22:52:54 +00:00
|
|
|
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - v8, vsi))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->goalAng = pSprite->ang - v8;
|
|
|
|
else
|
|
|
|
pXSprite->goalAng = pSprite->ang + 341;
|
|
|
|
|
|
|
|
pXSprite->dodgeDir = (Chance(0x8000)) ? 1 : -1;
|
|
|
|
|
2021-08-31 19:59:32 +00:00
|
|
|
if (!CanMove(actor, actor->GetTarget(), pSprite->ang + pXSprite->dodgeDir * 512, 512))
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->dodgeDir = -pXSprite->dodgeDir;
|
2021-09-15 22:52:54 +00:00
|
|
|
if (!CanMove(actor, actor->GetTarget(), pSprite->ang + pXSprite->dodgeDir * 512, 512))
|
2019-11-08 19:57:01 +00:00
|
|
|
pXSprite->dodgeDir = 0;
|
|
|
|
}
|
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-20 18:42:23 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
void aiGenDudeNewState(DBloodActor* actor, AISTATE* pAIState)
|
|
|
|
{
|
|
|
|
if (!actor->hasX())
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
auto const pXSprite = &actor->x();
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
// redirect dudes which cannot walk to non-walk states
|
2021-08-31 19:38:51 +00:00
|
|
|
if (!actor->genDudeExtra().canWalk)
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
if (pAIState == &genDudeDodgeL || pAIState == &genDudeDodgeShortL || pAIState == &genDudeDodgeShorterL)
|
2019-11-24 20:53:51 +00:00
|
|
|
pAIState = &genDudeRecoilL;
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
else if (pAIState == &genDudeDodgeD || pAIState == &genDudeDodgeShortD || pAIState == &genDudeDodgeShorterD)
|
2019-11-24 20:53:51 +00:00
|
|
|
pAIState = &genDudeRecoilD;
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
else if (pAIState == &genDudeDodgeW || pAIState == &genDudeDodgeShortW || pAIState == &genDudeDodgeShorterW)
|
2019-11-24 20:53:51 +00:00
|
|
|
pAIState = &genDudeRecoilW;
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
else if (pAIState == &genDudeSearchL || pAIState == &genDudeSearchShortL)
|
|
|
|
pAIState = &genDudeSearchNoWalkL;
|
|
|
|
|
|
|
|
else if (pAIState == &genDudeSearchW || pAIState == &genDudeSearchShortW)
|
|
|
|
pAIState = &genDudeSearchNoWalkW;
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
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) {
|
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) pAIState = &genDudeRecoilW;
|
2019-11-24 20:53:51 +00:00
|
|
|
else pAIState = &genDudeRecoilL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
if (!gGenDudeExtra[pSprite->index].canRecoil)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL;
|
|
|
|
else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW;
|
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
pXSprite->stateTimer = pAIState->stateTicks; pXSprite->aiState = pAIState;
|
2019-12-11 19:48:42 +00:00
|
|
|
|
|
|
|
int stateSeq = pXSprite->data2 + pAIState->seqId;
|
2021-09-20 18:16:53 +00:00
|
|
|
if (pAIState->seqId >= 0 && getSequence(stateSeq))
|
|
|
|
{
|
2019-12-11 19:48:42 +00:00
|
|
|
seqSpawn(stateSeq, 3, pSprite->extra, pAIState->funcId);
|
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
if (pAIState->enterFunc)
|
2021-05-06 07:00:49 +00:00
|
|
|
pAIState->enterFunc(actor);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 18:16:53 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-05-05 17:15:25 +00:00
|
|
|
bool playGenDudeSound(DBloodActor* actor, int mode)
|
|
|
|
{
|
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
auto const pXSprite = &actor->x();
|
2019-11-08 19:57:01 +00:00
|
|
|
if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax) return false;
|
2021-05-05 17:15:25 +00:00
|
|
|
const GENDUDESND* sndInfo = &gCustomDudeSnd[mode]; bool gotSnd = false;
|
|
|
|
short sndStartId = pXSprite->sysData1;
|
|
|
|
int rand = sndInfo->randomRange;
|
2019-09-21 11:02:17 +00:00
|
|
|
int sndId = (sndStartId <= 0) ? sndInfo->defaultSndId : sndStartId + sndInfo->sndIdOffset;
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
2019-11-24 20:53:51 +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) {
|
2019-12-17 18:37:05 +00:00
|
|
|
#if 0
|
2019-11-24 20:53:51 +00:00
|
|
|
for (int i = 0; i < 256; i++) {
|
|
|
|
if (Bonkle[i].atc <= 0) continue;
|
|
|
|
for (int a = 0; a < rand; a++) {
|
|
|
|
if (sndId + a == Bonkle[i].atc) {
|
|
|
|
if (Bonkle[i].at0 <= 0) {
|
|
|
|
pExtra->sndPlaying = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-17 18:37:05 +00:00
|
|
|
#endif
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
pExtra->sndPlaying = false;
|
|
|
|
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
if (sndId < 0) return false;
|
2019-09-21 11:02:17 +00:00
|
|
|
else if (sndStartId <= 0) { sndId += Random(rand); gotSnd = true; }
|
2021-05-05 17:15:25 +00:00
|
|
|
else
|
|
|
|
{
|
2019-09-19 22:42:45 +00:00
|
|
|
// Let's try to get random snd
|
2019-09-21 11:02:17 +00:00
|
|
|
int maxRetries = 5;
|
|
|
|
while (maxRetries-- > 0) {
|
2019-09-19 22:42:45 +00:00
|
|
|
int random = Random(rand);
|
2019-12-17 18:37:05 +00:00
|
|
|
if (!soundEngine->FindSoundByResID(sndId + random)) continue;
|
2019-09-21 11:02:17 +00:00
|
|
|
sndId = sndId + random;
|
|
|
|
gotSnd = true;
|
|
|
|
break;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
// If no success in getting random snd, get first existing one
|
2021-05-05 17:15:25 +00:00
|
|
|
if (gotSnd == false)
|
|
|
|
{
|
2019-09-21 11:02:17 +00:00
|
|
|
int maxSndId = sndId + rand;
|
2021-05-05 17:15:25 +00:00
|
|
|
while (sndId++ < maxSndId)
|
|
|
|
{
|
2019-12-17 18:37:05 +00:00
|
|
|
if (!soundEngine->FindSoundByResID(sndId)) continue;
|
2019-09-21 11:02:17 +00:00
|
|
|
gotSnd = true;
|
|
|
|
break;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
|
|
|
if (gotSnd == false) return false;
|
2021-05-05 09:51:10 +00:00
|
|
|
else if (sndInfo->aiPlaySound) aiPlay3DSound(actor, sndId, AI_SFX_PRIORITY_2, -1);
|
2021-05-05 17:15:25 +00:00
|
|
|
else sfxPlay3DSound(actor, sndId, -1, 0);
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
pExtra->sndPlaying = true;
|
2019-09-21 11:02:17 +00:00
|
|
|
return true;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-05 17:15:25 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-05-05 18:40:31 +00:00
|
|
|
bool spriteIsUnderwater(DBloodActor* actor, bool oldWay)
|
|
|
|
{
|
|
|
|
auto const pSprite = &actor->s();
|
|
|
|
auto const pXSprite = &actor->x();
|
2019-11-24 20:53:51 +00:00
|
|
|
return ((sector[pSprite->sectnum].extra >= 0 && xsector[sector[pSprite->sectnum].extra].Underwater)
|
2021-05-05 18:40:31 +00:00
|
|
|
|| (oldWay && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)));
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-05 19:06:38 +00:00
|
|
|
DBloodActor* leechIsDropped(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
return actor->genDudeExtra().pLifeLeech;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2021-05-05 19:18:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void removeDudeStuff(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
auto pSprite = &actor->s();
|
|
|
|
BloodStatIterator it(kStatThing);
|
|
|
|
while (auto actor2 = it.Next())
|
2020-10-15 15:15:45 +00:00
|
|
|
{
|
2021-05-05 19:18:09 +00:00
|
|
|
if ( actor2->GetOwner() != actor) continue;
|
|
|
|
auto pSprite2 = &actor2->s();
|
|
|
|
switch (pSprite2->type) {
|
2019-10-11 21:59:39 +00:00
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedRemoteBomb:
|
|
|
|
case kModernThingTNTProx:
|
2021-05-05 19:18:09 +00:00
|
|
|
pSprite2->type = kSpriteDecoration;
|
|
|
|
actPostSprite(actor2, kStatFree);
|
2019-10-11 21:59:39 +00:00
|
|
|
break;
|
|
|
|
case kModernThingEnemyLifeLeech:
|
2021-05-05 19:22:09 +00:00
|
|
|
killDudeLeech(actor2);
|
2019-10-11 21:59:39 +00:00
|
|
|
break;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-15 15:15:45 +00:00
|
|
|
it.Reset(kStatDude);
|
2021-05-05 19:18:09 +00:00
|
|
|
while (auto actor2 = it.Next())
|
2020-10-15 15:15:45 +00:00
|
|
|
{
|
2021-05-05 19:18:09 +00:00
|
|
|
if (actor2->GetOwner() != actor) continue;
|
|
|
|
actDamageSprite(actor2->GetOwner(), actor2, kDamageFall, 65535);
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 19:18:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void removeLeech(DBloodActor* actLeech, bool delSprite)
|
|
|
|
{
|
|
|
|
if (actLeech != NULL)
|
|
|
|
{
|
|
|
|
auto const pLeech = &actLeech->s();
|
|
|
|
spritetype* pEffect = gFX.fxSpawn((FX_ID)52, pLeech->sectnum, pLeech->x, pLeech->y, pLeech->z, pLeech->ang);
|
|
|
|
if (pEffect != NULL)
|
|
|
|
{
|
2019-09-21 11:02:17 +00:00
|
|
|
pEffect->cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
|
2019-09-19 22:42:45 +00:00
|
|
|
pEffect->pal = 6;
|
|
|
|
int repeat = 64 + Random(50);
|
|
|
|
pEffect->xrepeat = repeat;
|
|
|
|
pEffect->yrepeat = repeat;
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
sfxPlay3DSoundCP(pLeech, 490, -1, 0,60000);
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
if (pLeech->owner >= 0 && pLeech->owner < kMaxSprites)
|
2021-05-05 19:06:38 +00:00
|
|
|
gGenDudeExtra[sprite[pLeech->owner].index].pLifeLeech = nullptr;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-05 19:18:09 +00:00
|
|
|
if (delSprite)
|
|
|
|
{
|
2019-10-19 19:11:39 +00:00
|
|
|
pLeech->type = kSpriteDecoration;
|
2019-09-19 22:42:45 +00:00
|
|
|
actPostSprite(pLeech->index, kStatFree);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-05 19:22:09 +00:00
|
|
|
void killDudeLeech(DBloodActor* actLeech)
|
|
|
|
{
|
|
|
|
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-05-05 19:22:09 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2021-05-06 06:39:33 +00:00
|
|
|
DBloodActor* getNextIncarnation(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
XSPRITE* pXSprite = &actor->x();
|
|
|
|
for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++)
|
|
|
|
{
|
|
|
|
if (rxBucket[i].type != OBJ_SPRITE || rxBucket[i].index == pXSprite->reference)
|
2019-09-21 11:02:17 +00:00
|
|
|
continue;
|
2021-05-06 06:39:33 +00:00
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
if (sprite[rxBucket[i].index].statnum == kStatInactive)
|
2021-05-06 06:39:33 +00:00
|
|
|
return &bloodActors[sprite[rxBucket[i].index].index];
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2021-05-06 06:45:50 +00:00
|
|
|
bool dudeIsMelee(DBloodActor* actor)
|
|
|
|
{
|
|
|
|
return actor->genDudeExtra().isMelee;
|
|
|
|
}
|
|
|
|
|
2021-05-06 06:39:33 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
void scaleDamage(XSPRITE* pXSprite) {
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
short curWeapon = gGenDudeExtra[sprite[pXSprite->reference].index].curWeapon;
|
2019-12-05 20:42:35 +00:00
|
|
|
short weaponType = gGenDudeExtra[sprite[pXSprite->reference].index].weaponType;
|
2020-03-01 20:36:28 +00:00
|
|
|
signed short* curScale = gGenDudeExtra[sprite[pXSprite->reference].index].dmgControl;
|
2019-11-08 19:57:01 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
2020-02-05 21:17:05 +00:00
|
|
|
curScale[i] = getDudeInfo(kDudeModernCustom)->startDamage[i];
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-03-01 20:36:28 +00:00
|
|
|
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:
|
2021-05-06 07:00:49 +00:00
|
|
|
switch (curWeapon)
|
|
|
|
{
|
2020-03-01 20:36:28 +00:00
|
|
|
case kMissileButcherKnife:
|
|
|
|
curScale[kDmgBullet] = 100;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2020-03-01 20:36:28 +00:00
|
|
|
case kMissileEctoSkull:
|
|
|
|
curScale[kDmgSpirit] = 32;
|
|
|
|
break;
|
|
|
|
case kMissileLifeLeechAltNormal:
|
|
|
|
case kMissileLifeLeechAltSmall:
|
|
|
|
case kMissileArcGargoyle:
|
2020-03-19 19:35:31 +00:00
|
|
|
curScale[kDmgSpirit] -= 32;
|
2020-03-01 20:36:28 +00:00
|
|
|
curScale[kDmgElectric] = 52;
|
|
|
|
break;
|
|
|
|
case kMissileFlareRegular:
|
|
|
|
case kMissileFlareAlt:
|
|
|
|
case kMissileFlameSpray:
|
|
|
|
case kMissileFlameHound:
|
|
|
|
case kThingArmedSpray:
|
|
|
|
case kThingPodFireBall:
|
|
|
|
case kThingNapalmBall:
|
|
|
|
curScale[kDmgBurn] = 32;
|
|
|
|
break;
|
|
|
|
case kMissileLifeLeechRegular:
|
2020-03-19 19:35:31 +00:00
|
|
|
curScale[kDmgBurn] = 60 + Random(4);
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2020-03-01 20:36:28 +00:00
|
|
|
case kThingDroppedLifeLeech:
|
|
|
|
case kModernThingEnemyLifeLeech:
|
|
|
|
curScale[kDmgSpirit] = 32 + Random(18);
|
|
|
|
break;
|
|
|
|
case kMissileFireball:
|
2021-08-22 07:49:02 +00:00
|
|
|
case kMissileFireballNapalm:
|
2020-03-01 20:36:28 +00:00
|
|
|
case kMissileFireballCerberus:
|
|
|
|
case kMissileFireballTchernobog:
|
|
|
|
curScale[kDmgBurn] = 50;
|
2021-09-19 14:34:01 +00:00
|
|
|
curScale[kDmgExplode] -= 32;
|
2020-03-01 20:36:28 +00:00
|
|
|
curScale[kDmgFall] = 65 + Random(15);
|
|
|
|
break;
|
|
|
|
case kThingTNTBarrel:
|
|
|
|
case kThingArmedProxBomb:
|
|
|
|
case kThingArmedRemoteBomb:
|
|
|
|
case kThingArmedTNTBundle:
|
|
|
|
case kThingArmedTNTStick:
|
|
|
|
case kModernThingTNTProx:
|
2020-05-05 18:50:14 +00:00
|
|
|
curScale[kDmgBurn] -= 32;
|
2021-09-19 14:34:01 +00:00
|
|
|
curScale[kDmgExplode] -= 32;
|
2020-03-01 20:36:28 +00:00
|
|
|
curScale[kDmgFall] = 65 + Random(15);
|
|
|
|
break;
|
|
|
|
case kMissileTeslaAlt:
|
|
|
|
case kMissileTeslaRegular:
|
|
|
|
curScale[kDmgElectric] = 32 + Random(8);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
// add resistance if have an armor item to drop
|
2021-05-06 07:00:49 +00:00
|
|
|
if (pXSprite->dropMsg >= kItemArmorAsbest && pXSprite->dropMsg <= kItemArmorSuper)
|
|
|
|
{
|
|
|
|
switch (pXSprite->dropMsg)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
case kItemArmorAsbest:
|
|
|
|
curScale[kDmgBurn] = 0;
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgExplode] -= 30;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kItemArmorBasic:
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgBurn] -= 15;
|
|
|
|
curScale[kDmgExplode] -= 15;
|
|
|
|
curScale[kDmgBullet] -= 15;
|
|
|
|
curScale[kDmgSpirit] -= 15;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kItemArmorBody:
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgBullet] -= 30;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kItemArmorFire:
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgBurn] -= 30;
|
|
|
|
curScale[kDmgExplode] -= 30;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kItemArmorSpirit:
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgSpirit] -= 30;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
case kItemArmorSuper:
|
2019-12-05 20:42:35 +00:00
|
|
|
curScale[kDmgBurn] -= 60;
|
|
|
|
curScale[kDmgExplode] -= 60;
|
|
|
|
curScale[kDmgBullet] -= 60;
|
|
|
|
curScale[kDmgSpirit] -= 60;
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// take in account yrepeat of sprite
|
|
|
|
short yrepeat = sprite[pXSprite->reference].yrepeat;
|
2021-05-06 07:00:49 +00:00
|
|
|
if (yrepeat < 64)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++) curScale[i] += (64 - yrepeat);
|
2021-05-06 07:00:49 +00:00
|
|
|
}
|
|
|
|
else if (yrepeat > 64)
|
|
|
|
{
|
2020-03-19 19:35:31 +00:00
|
|
|
for (int i = 0; i < kDmgMax; i++) curScale[i] -= ((yrepeat - 64) >> 2);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// take surface type into account
|
|
|
|
int surfType = tileGetSurfType(sprite[pXSprite->reference].index + 0xc000);
|
2021-05-06 07:00:49 +00:00
|
|
|
switch (surfType)
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
case 1: // stone
|
|
|
|
curScale[kDmgFall] = 0;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 200;
|
|
|
|
curScale[kDmgBurn] -= 100;
|
|
|
|
curScale[kDmgExplode] -= 80;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgChoke] += 30;
|
|
|
|
curScale[kDmgElectric] += 20;
|
|
|
|
break;
|
|
|
|
case 2: // metal
|
|
|
|
curScale[kDmgFall] = 16;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 128;
|
|
|
|
curScale[kDmgBurn] -= 90;
|
|
|
|
curScale[kDmgExplode] -= 55;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgChoke] += 20;
|
|
|
|
curScale[kDmgElectric] += 30;
|
|
|
|
break;
|
|
|
|
case 3: // wood
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 10;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgBurn] += 50;
|
|
|
|
curScale[kDmgExplode] += 40;
|
|
|
|
curScale[kDmgChoke] += 10;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgElectric] -= 60;
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case 5: // water
|
|
|
|
case 6: // dirt
|
|
|
|
case 7: // clay
|
|
|
|
case 13: // goo
|
|
|
|
curScale[kDmgFall] = 8;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 20;
|
|
|
|
curScale[kDmgBurn] -= 200;
|
|
|
|
curScale[kDmgExplode] -= 60;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgChoke] = 0;
|
|
|
|
curScale[kDmgElectric] += 40;
|
|
|
|
break;
|
|
|
|
case 8: // snow
|
|
|
|
case 9: // ice
|
|
|
|
curScale[kDmgFall] = 8;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 20;
|
|
|
|
curScale[kDmgBurn] -= 100;
|
|
|
|
curScale[kDmgExplode] -= 50;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgChoke] = 0;
|
|
|
|
curScale[kDmgElectric] += 40;
|
|
|
|
break;
|
|
|
|
case 10: // leaves
|
|
|
|
case 12: // plant
|
|
|
|
curScale[kDmgFall] = 0;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 10;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgBurn] += 70;
|
|
|
|
curScale[kDmgExplode] += 50;
|
|
|
|
break;
|
|
|
|
case 11: // cloth
|
|
|
|
curScale[kDmgFall] = 8;
|
2020-05-22 16:28:03 +00:00
|
|
|
curScale[kDmgBullet] -= 10;
|
2019-11-24 20:53:51 +00:00
|
|
|
curScale[kDmgBurn] += 30;
|
|
|
|
curScale[kDmgExplode] += 20;
|
|
|
|
break;
|
|
|
|
case 14: // lava
|
|
|
|
curScale[kDmgBurn] = 0;
|
|
|
|
curScale[kDmgExplode] = 0;
|
|
|
|
curScale[kDmgChoke] += 30;
|
|
|
|
break;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// finally, scale dmg for difficulty
|
|
|
|
for (int i = 0; i < kDmgMax; i++)
|
2021-01-04 11:36:54 +00:00
|
|
|
curScale[i] = MulScale(DudeDifficulty[gGameOptions.nDifficulty], ClipLow(curScale[i], 1), 8);
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
//short* dc = curScale;
|
|
|
|
//if (pXSprite->rxID == 788)
|
|
|
|
//viewSetSystemMessage("0: %d, 1: %d, 2: %d, 3: %d, 4: %d, 5: %d, 6: %d", dc[0], dc[1], dc[2], dc[3], dc[4], dc[5], dc[6]);
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2020-07-25 20:47:46 +00:00
|
|
|
int getDispersionModifier(spritetype* pSprite, int minDisp, int maxDisp)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
// the faster fire rate, the less frames = more dispersion
|
2020-07-25 20:47:46 +00:00
|
|
|
Seq* pSeq = getSequence(xsprite[pSprite->extra].data2 + 6);
|
|
|
|
int disp = 1;
|
|
|
|
if (pSeq != nullptr)
|
|
|
|
{
|
2020-11-21 17:12:51 +00:00
|
|
|
int nFrames = pSeq->nFrames; int ticks = pSeq->ticksPerFrame; int shots = 0;
|
2019-11-08 19:57:01 +00:00
|
|
|
for (int i = 0; i <= pSeq->nFrames; i++) {
|
2020-11-21 17:12:51 +00:00
|
|
|
if (pSeq->frames[i].trigger) shots++;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
disp = (((shots * 1000) / nFrames) / ticks) * 20;
|
|
|
|
if (gGameOptions.nDifficulty > 0)
|
2019-12-05 20:42:35 +00:00
|
|
|
disp /= gGameOptions.nDifficulty;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
|
|
|
//viewSetSystemMessage("DISP: %d FRAMES: %d SHOTS: %d TICKS %d", disp, nFrames, shots, ticks);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ClipRange(disp, minDisp, maxDisp);
|
|
|
|
}
|
|
|
|
|
|
|
|
// the distance counts from sprite size
|
|
|
|
int getRangeAttackDist(spritetype* pSprite, int minDist, int maxDist) {
|
|
|
|
short yrepeat = pSprite->yrepeat; int dist = 0; int seqId = xsprite[pSprite->extra].data2;
|
2021-05-06 07:00:49 +00:00
|
|
|
int mul = 550;
|
|
|
|
int picnum = pSprite->picnum;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
if (yrepeat > 0)
|
|
|
|
{
|
|
|
|
if (seqId >= 0)
|
|
|
|
{
|
2020-07-25 20:47:46 +00:00
|
|
|
Seq* pSeq = getSequence(seqId);
|
2021-05-06 07:00:49 +00:00
|
|
|
if (pSeq)
|
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
picnum = seqGetTile(&pSeq->frames[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 22:27:24 +00:00
|
|
|
dist = tileHeight(picnum) << 8;
|
2019-11-08 19:57:01 +00:00
|
|
|
if (yrepeat < 64) dist -= (64 - yrepeat) * mul;
|
|
|
|
else if (yrepeat > 64) dist += (yrepeat - 64) * (mul / 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
dist = ClipRange(dist, minDist, maxDist);
|
2020-11-22 23:18:07 +00:00
|
|
|
//viewSetSystemMessage("DIST: %d, SPRHEIGHT: %d: YREPEAT: %d PIC: %d", dist, tileHeight(pSprite->picnum), yrepeat, picnum);
|
2019-11-08 19:57:01 +00:00
|
|
|
return dist;
|
|
|
|
}
|
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
int getBaseChanceModifier(int baseChance)
|
|
|
|
{
|
2019-11-24 20:53:51 +00:00
|
|
|
return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance);
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
int getRecoilChance(spritetype* pSprite) {
|
2021-05-06 06:45:50 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
|
|
|
int mass = getSpriteMassBySize(pSprite);
|
|
|
|
int baseChance = (!dudeIsMelee(actor) ? 0x8000 : 0x4000);
|
2019-11-24 20:53:51 +00:00
|
|
|
baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
|
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
int chance = ((baseChance / mass) << 7);
|
|
|
|
return chance;
|
|
|
|
}
|
|
|
|
|
|
|
|
int getDodgeChance(spritetype* pSprite) {
|
2021-05-06 06:45:50 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
|
|
|
int mass = getSpriteMassBySize(pSprite);
|
|
|
|
int baseChance = (!dudeIsMelee(actor) ? 0x6000 : 0x1000);
|
2019-12-05 20:42:35 +00:00
|
|
|
baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
|
|
|
int chance = ((baseChance / mass) << 7);
|
|
|
|
return chance;
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 21:59:39 +00:00
|
|
|
void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT event)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2019-10-11 21:59:39 +00:00
|
|
|
if (event.cmd == kCmdOff) {
|
2020-02-07 19:47:43 +00:00
|
|
|
actPostSprite(pSprite->index, kStatFree);
|
2019-09-19 22:42:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-12-05 20:23:53 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-09-15 22:40:09 +00:00
|
|
|
int nTarget = pXSprite->target_i;
|
2019-12-05 20:42:35 +00:00
|
|
|
if (spriRangeIsFine(nTarget) && nTarget != pSprite->owner) {
|
2019-09-19 22:42:45 +00:00
|
|
|
spritetype* pTarget = &sprite[nTarget];
|
2019-11-24 20:53:51 +00:00
|
|
|
if (pTarget->statnum == kStatDude && !(pTarget->flags & 32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites && !pXSprite->stateTimer) {
|
|
|
|
|
|
|
|
if (IsPlayerSprite(pTarget)) {
|
|
|
|
PLAYER* pPlayer = &gPlayer[pTarget->type - kDudePlayer1];
|
|
|
|
if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return;
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
int top, bottom;
|
|
|
|
GetSpriteExtents(pSprite, &top, &bottom);
|
|
|
|
int nType = pTarget->type - kDudeBase;
|
|
|
|
DUDEINFO* pDudeInfo = &dudeInfo[nType];
|
|
|
|
int z1 = (top - pSprite->z) - 256;
|
|
|
|
int x = pTarget->x; int y = pTarget->y; int z = pTarget->z;
|
|
|
|
int nDist = approxDist(x - pSprite->x, y - pSprite->y);
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum)) {
|
2021-01-04 11:59:00 +00:00
|
|
|
int t = DivScale(nDist, 0x1aaaaa, 12);
|
2019-09-19 22:42:45 +00:00
|
|
|
x += (xvel[nTarget] * t) >> 12;
|
|
|
|
y += (yvel[nTarget] * t) >> 12;
|
|
|
|
int angBak = pSprite->ang;
|
|
|
|
pSprite->ang = getangle(x - pSprite->x, y - pSprite->y);
|
2020-09-01 13:00:35 +00:00
|
|
|
int dx = CosScale16(pSprite->ang);
|
|
|
|
int dy = SinScale16(pSprite->ang);
|
2019-09-19 22:42:45 +00:00
|
|
|
int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
|
2021-01-04 11:59:00 +00:00
|
|
|
int dz = DivScale(tz - top - 256, nDist, 10);
|
2019-10-11 21:59:39 +00:00
|
|
|
int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
|
2019-09-19 22:42:45 +00:00
|
|
|
int t2;
|
|
|
|
|
2020-09-26 09:59:24 +00:00
|
|
|
if (!pXSprite->data3) t2 = 120 / 10;
|
|
|
|
else t2 = (3 * 120) / 10;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-12-05 20:23:53 +00:00
|
|
|
auto missile = actFireMissile(actor, 0, z1, dx, dy, dz, nMissileType);
|
|
|
|
if (missile)
|
2019-09-19 22:42:45 +00:00
|
|
|
{
|
2020-12-05 20:23:53 +00:00
|
|
|
missile->SetOwner(actor);
|
2019-09-19 22:42:45 +00:00
|
|
|
pXSprite->stateTimer = 1;
|
2019-10-11 21:59:39 +00:00
|
|
|
evPost(pSprite->index, 3, t2, kCallbackLeechStateTimer);
|
2019-09-19 22:42:45 +00:00
|
|
|
pXSprite->data3 = ClipLow(pXSprite->data3 - 1, 0);
|
|
|
|
}
|
|
|
|
pSprite->ang = angBak;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-05 16:24:27 +00:00
|
|
|
bool doExplosion(spritetype* pSprite, int nType)
|
|
|
|
{
|
|
|
|
auto actor = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, kStatExplosion, true);
|
|
|
|
spritetype* pExplosion = &actor->s();
|
2019-11-08 19:57:01 +00:00
|
|
|
if (pExplosion->extra < 0 || pExplosion->extra >= kMaxXSprites)
|
|
|
|
return false;
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2020-02-11 22:15:25 +00:00
|
|
|
int nSeq = 4; int nSnd = 304; const EXPLOSION* pExpl = &explodeInfo[nType];
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-09-30 07:18:01 +00:00
|
|
|
pExplosion->type = nType;
|
2019-11-08 19:57:01 +00:00
|
|
|
pExplosion->cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
|
|
pExplosion->owner = pSprite->index;
|
|
|
|
pExplosion->shade = -127;
|
|
|
|
|
|
|
|
pExplosion->yrepeat = pExplosion->xrepeat = pExpl->repeat;
|
|
|
|
|
|
|
|
xsprite[pExplosion->extra].data1 = pExpl->ticks;
|
|
|
|
xsprite[pExplosion->extra].data2 = pExpl->quakeEffect;
|
|
|
|
xsprite[pExplosion->extra].data3 = pExpl->flashEffect;
|
|
|
|
|
|
|
|
if (nType == 0) { nSeq = 3; nSnd = 303; }
|
|
|
|
else if (nType == 2) { nSeq = 4; nSnd = 305; }
|
|
|
|
else if (nType == 3) { nSeq = 9; nSnd = 307; }
|
|
|
|
else if (nType == 4) { nSeq = 5; nSnd = 307; }
|
|
|
|
else if (nType <= 6) { nSeq = 4; nSnd = 303; }
|
|
|
|
else if (nType == 7) { nSeq = 4; nSnd = 303; }
|
|
|
|
|
|
|
|
seqSpawn(nSeq, 3, pExplosion->extra, -1);
|
|
|
|
sfxPlay3DSound(pExplosion, nSnd, -1, 0);
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
return true;
|
|
|
|
}
|
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
|
|
|
|
2021-05-06 07:00:49 +00:00
|
|
|
spritetype* genDudeSpawn(XSPRITE* pXSource, spritetype* pSprite, int nDist)
|
|
|
|
{
|
2020-12-05 17:32:49 +00:00
|
|
|
DBloodActor* actor = &bloodActors[pSprite->index];
|
|
|
|
spritetype* pSource = &sprite[pXSource->reference];
|
|
|
|
auto spawned = actSpawnSprite(actor, kStatDude);
|
|
|
|
spritetype* pDude = &spawned->s();
|
|
|
|
XSPRITE* pXDude = &spawned->x();
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
int x, y, z = pSprite->z, nAngle = pSprite->ang, nType = kDudeModernCustom;
|
|
|
|
|
|
|
|
if (nDist > 0) {
|
2021-07-19 21:15:26 +00:00
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
x = pSprite->x + mulscale30r(Cos(nAngle), nDist);
|
|
|
|
y = pSprite->y + mulscale30r(Sin(nAngle), nDist);
|
2021-07-19 21:15:26 +00:00
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
} else {
|
2021-07-19 21:15:26 +00:00
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
x = pSprite->x;
|
|
|
|
y = pSprite->y;
|
2021-07-19 21:15:26 +00:00
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pDude->type = nType; pDude->ang = nAngle;
|
|
|
|
vec3_t pos = { x, y, z }; setsprite(pDude->index, &pos);
|
|
|
|
pDude->cstat |= 0x1101; pDude->clipdist = dudeInfo[nType - kDudeBase].clipdist;
|
|
|
|
|
|
|
|
// inherit weapon, seq and sound settings.
|
|
|
|
pXDude->data1 = pXSource->data1;
|
|
|
|
pXDude->data2 = pXSource->data2;
|
|
|
|
pXDude->sysData1 = pXSource->data3; // move sndStartId from data3 to sysData1
|
|
|
|
pXDude->data3 = 0;
|
|
|
|
|
|
|
|
// spawn seq
|
|
|
|
seqSpawn(genDudeSeqStartId(pXDude), 3, pDude->extra, -1);
|
|
|
|
|
|
|
|
// inherit movement speed.
|
|
|
|
pXDude->busyTime = pXSource->busyTime;
|
|
|
|
|
|
|
|
// inherit clipdist?
|
2021-07-19 21:15:26 +00:00
|
|
|
if (pSource->clipdist > 0)
|
|
|
|
pDude->clipdist = pSource->clipdist;
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
// inherit custom hp settings
|
2020-04-07 20:30:00 +00:00
|
|
|
if (pXSource->data4 <= 0) pXDude->health = dudeInfo[nType - kDudeBase].startHealth << 4;
|
|
|
|
else pXDude->health = ClipRange(pXSource->data4 << 4, 1, 65535);
|
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
if (pSource->flags & kModernTypeFlag1) {
|
|
|
|
switch (pSource->type) {
|
2021-07-19 21:15:26 +00:00
|
|
|
case kModernCustomDudeSpawn:
|
|
|
|
//inherit pal?
|
|
|
|
if (pDude->pal <= 0) pDude->pal = pSource->pal;
|
|
|
|
|
|
|
|
// inherit spawn sprite trigger settings, so designer can count monsters.
|
|
|
|
pXDude->txID = pXSource->txID;
|
|
|
|
pXDude->command = pXSource->command;
|
|
|
|
pXDude->triggerOn = pXSource->triggerOn;
|
|
|
|
pXDude->triggerOff = pXSource->triggerOff;
|
|
|
|
|
|
|
|
// inherit drop items
|
|
|
|
pXDude->dropMsg = pXSource->dropMsg;
|
|
|
|
|
|
|
|
// inherit required key so it can be dropped
|
|
|
|
pXDude->key = pXSource->key;
|
|
|
|
|
|
|
|
// inherit dude flags
|
|
|
|
pXDude->dudeDeaf = pXSource->dudeDeaf;
|
|
|
|
pXDude->dudeGuard = pXSource->dudeGuard;
|
|
|
|
pXDude->dudeAmbush = pXSource->dudeAmbush;
|
|
|
|
pXDude->dudeFlag4 = pXSource->dudeFlag4;
|
|
|
|
pXDude->unused1 = pXSource->unused1;
|
|
|
|
break;
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// inherit sprite size (useful for seqs with zero repeats)
|
|
|
|
if (pSource->flags & kModernTypeFlag2) {
|
|
|
|
pDude->xrepeat = pSource->xrepeat;
|
|
|
|
pDude->yrepeat = pSource->yrepeat;
|
|
|
|
}
|
|
|
|
|
2021-07-19 21:15:26 +00:00
|
|
|
gKillMgr.AddNewKill(1);
|
2021-05-05 10:55:52 +00:00
|
|
|
aiInitSprite(spawned);
|
2020-02-07 19:47:43 +00:00
|
|
|
return pDude;
|
|
|
|
}
|
|
|
|
|
|
|
|
void genDudeTransform(spritetype* pSprite) {
|
|
|
|
|
|
|
|
if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
|
2020-02-07 19:47:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
2021-09-16 18:38:46 +00:00
|
|
|
auto actor = &bloodActors[pXSprite->reference];
|
2021-05-06 06:39:33 +00:00
|
|
|
auto actIncarnation = getNextIncarnation(actor);
|
|
|
|
if (actIncarnation == NULL) {
|
2020-02-07 19:47:43 +00:00
|
|
|
if (pXSprite->sysData1 == kGenDudeTransformStatus) pXSprite->sysData1 = 0;
|
|
|
|
trTriggerSprite(pSprite->index, pXSprite, kCmdOff);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-06 06:39:33 +00:00
|
|
|
auto pXIncarnation = &actIncarnation->x();
|
|
|
|
spritetype* pIncarnation = &actIncarnation->s();
|
2020-02-07 19:47:43 +00:00
|
|
|
pXSprite->key = pXSprite->dropMsg = pXSprite->locked = 0;
|
|
|
|
|
|
|
|
// save incarnation's going on and off options
|
|
|
|
bool triggerOn = pXIncarnation->triggerOn;
|
|
|
|
bool triggerOff = pXIncarnation->triggerOff;
|
|
|
|
|
|
|
|
// then remove it from incarnation so it will not send the commands
|
|
|
|
pXIncarnation->triggerOn = false;
|
|
|
|
pXIncarnation->triggerOff = false;
|
|
|
|
|
|
|
|
// trigger dude death before transform
|
|
|
|
trTriggerSprite(pSprite->index, pXSprite, kCmdOff);
|
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
pSprite->type = pSprite->inittype = pIncarnation->type;
|
2020-02-07 19:47:43 +00:00
|
|
|
pSprite->flags = pIncarnation->flags;
|
|
|
|
pSprite->pal = pIncarnation->pal;
|
|
|
|
pSprite->shade = pIncarnation->shade;
|
|
|
|
pSprite->clipdist = pIncarnation->clipdist;
|
|
|
|
pSprite->xrepeat = pIncarnation->xrepeat;
|
|
|
|
pSprite->yrepeat = pIncarnation->yrepeat;
|
|
|
|
|
|
|
|
pXSprite->txID = pXIncarnation->txID;
|
|
|
|
pXSprite->command = pXIncarnation->command;
|
|
|
|
pXSprite->triggerOn = triggerOn;
|
|
|
|
pXSprite->triggerOff = triggerOff;
|
|
|
|
pXSprite->busyTime = pXIncarnation->busyTime;
|
|
|
|
pXSprite->waitTime = pXIncarnation->waitTime;
|
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
// inherit respawn properties
|
|
|
|
pXSprite->respawn = pXIncarnation->respawn;
|
|
|
|
pXSprite->respawnPending = pXIncarnation->respawnPending;
|
|
|
|
|
2020-02-07 19:47:43 +00:00
|
|
|
pXSprite->burnTime = 0;
|
|
|
|
pXSprite->burnSource = -1;
|
|
|
|
|
|
|
|
pXSprite->data1 = pXIncarnation->data1;
|
|
|
|
pXSprite->data2 = pXIncarnation->data2;
|
|
|
|
|
2020-04-07 20:30:00 +00:00
|
|
|
pXSprite->sysData1 = pXIncarnation->data3; // soundBase id
|
|
|
|
pXSprite->sysData2 = pXIncarnation->data4; // start hp
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
pXSprite->dudeGuard = pXIncarnation->dudeGuard;
|
|
|
|
pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
|
|
|
|
pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
|
|
|
|
pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;
|
2021-01-07 12:33:20 +00:00
|
|
|
pXSprite->unused1 = pXIncarnation->unused1;
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
pXSprite->dropMsg = pXIncarnation->dropMsg;
|
|
|
|
pXSprite->key = pXIncarnation->key;
|
|
|
|
|
|
|
|
pXSprite->locked = pXIncarnation->locked;
|
|
|
|
pXSprite->Decoupled = pXIncarnation->Decoupled;
|
|
|
|
|
|
|
|
// clear drop items of the incarnation
|
|
|
|
pXIncarnation->key = pXIncarnation->dropMsg = 0;
|
|
|
|
|
|
|
|
// set hp
|
2020-04-07 20:30:00 +00:00
|
|
|
if (pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
|
|
|
|
else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
int seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
|
|
|
|
switch (pSprite->type) {
|
|
|
|
case kDudePodMother: // fake dude
|
|
|
|
case kDudeTentacleMother: // fake dude
|
|
|
|
break;
|
|
|
|
case kDudeModernCustom:
|
|
|
|
case kDudeModernCustomBurning:
|
|
|
|
seqId = genDudeSeqStartId(pXSprite);
|
|
|
|
genDudePrepare(pSprite, kGenDudePropertyMass);
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]]; // go below
|
2020-02-07 19:47:43 +00:00
|
|
|
default:
|
|
|
|
seqSpawn(seqId, 3, pSprite->extra, -1);
|
|
|
|
|
|
|
|
// save target
|
2021-09-15 22:40:09 +00:00
|
|
|
int target = pXSprite->target_i;
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
// re-init sprite
|
2021-05-05 10:55:52 +00:00
|
|
|
aiInitSprite(actor);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
// try to restore target
|
2021-09-16 18:38:46 +00:00
|
|
|
if (target == -1) aiSetTarget(actor, pSprite->x, pSprite->y, pSprite->z);
|
2021-09-16 16:57:25 +00:00
|
|
|
else aiSetTarget_(pXSprite, target);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
// finally activate it
|
2021-09-16 18:38:46 +00:00
|
|
|
aiActivateDude(actor);
|
2020-02-07 19:47:43 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2020-04-04 19:48:23 +00:00
|
|
|
pXIncarnation->triggerOn = triggerOn;
|
|
|
|
pXIncarnation->triggerOff = triggerOff;
|
2020-02-07 19:47:43 +00:00
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
/*// remove the incarnation in case if non-locked
|
2020-02-07 19:47:43 +00:00
|
|
|
if (pXIncarnation->locked == 0) {
|
|
|
|
pXIncarnation->txID = pIncarnation->type = 0;
|
|
|
|
actPostSprite(pIncarnation->index, kStatFree);
|
|
|
|
// or restore triggerOn and off options
|
|
|
|
} else {
|
|
|
|
pXIncarnation->triggerOn = triggerOn;
|
|
|
|
pXIncarnation->triggerOff = triggerOff;
|
2020-04-04 19:48:23 +00:00
|
|
|
}*/
|
2020-02-07 19:47:43 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 22:42:45 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
void updateTargetOfLeech(spritetype* pSprite) {
|
|
|
|
if (!(pSprite->extra >= 0 && pSprite->extra < kMaxXSprites)) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "pSprite->extra >= 0 && pSprite->extra < kMaxXSprites");
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-05-05 19:06:38 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
2019-11-24 20:53:51 +00:00
|
|
|
|
2021-05-05 19:06:38 +00:00
|
|
|
auto actLeech = leechIsDropped(actor);
|
|
|
|
if (actLeech == NULL || !actLeech->hasX()) actor->genDudeExtra().pLifeLeech = nullptr;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
XSPRITE* pXDude = &actor->x();
|
|
|
|
if (actor->GetTarget() != actLeech->GetTarget())
|
|
|
|
{
|
|
|
|
if (actor->GetTarget() == nullptr && actLeech->GetTarget() != nullptr)
|
|
|
|
{
|
|
|
|
aiSetTarget(actor, actLeech->GetTarget());
|
2019-11-24 20:53:51 +00:00
|
|
|
if (inIdle(pXDude->aiState))
|
2021-05-05 19:06:38 +00:00
|
|
|
aiActivateDude(actor);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
actLeech->SetTarget(actor->GetTarget());
|
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void updateTargetOfSlaves(spritetype* pSprite) {
|
2019-12-11 19:48:42 +00:00
|
|
|
if (!xspriRangeIsFine(pSprite->extra)) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "!xspriRangeIsFine(pSprite->extra)");
|
2019-11-08 19:57:01 +00:00
|
|
|
return;
|
2019-09-19 22:42:45 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2021-08-31 19:38:51 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
2019-12-11 19:48:42 +00:00
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
2021-08-31 19:38:51 +00:00
|
|
|
GENDUDEEXTRA* pExtra = &actor->genDudeExtra();
|
|
|
|
short* slave = pExtra->slave;
|
2021-09-15 22:40:09 +00:00
|
|
|
spritetype* pTarget = (pXSprite->target_i >= 0 && IsDudeSprite(&sprite[pXSprite->target_i])) ? &sprite[pXSprite->target_i] : NULL;
|
2019-12-11 19:48:42 +00:00
|
|
|
XSPRITE* pXTarget = (pTarget != NULL && xspriRangeIsFine(pTarget->extra) && xsprite[pTarget->extra].health > 0) ? &xsprite[pTarget->extra] : NULL;
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-12-11 19:48:42 +00:00
|
|
|
int newCnt = pExtra->slaveCount;
|
2019-11-08 19:57:01 +00:00
|
|
|
for (int i = 0; i <= gGameOptions.nDifficulty; i++) {
|
2019-12-11 19:48:42 +00:00
|
|
|
if (spriRangeIsFine(slave[i])) {
|
2019-11-08 19:57:01 +00:00
|
|
|
spritetype* pSlave = &sprite[slave[i]];
|
2019-12-11 19:48:42 +00:00
|
|
|
if (!IsDudeSprite(pSlave) || !xspriRangeIsFine(pSlave->extra) || xsprite[pSlave->extra].health < 0) {
|
|
|
|
slave[i] = pSlave->owner = -1; newCnt--;
|
2019-11-08 19:57:01 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-11 19:48:42 +00:00
|
|
|
XSPRITE* pXSlave = &xsprite[pSlave->index];
|
|
|
|
if (pXTarget != NULL) {
|
2021-09-16 16:57:25 +00:00
|
|
|
if (pXSprite->target_i != pXSlave->target_i) aiSetTarget_(pXSlave, pXSprite->target_i);
|
2019-12-11 19:48:42 +00:00
|
|
|
// check if slave have proper target
|
2021-09-15 22:40:09 +00:00
|
|
|
if (!spriRangeIsFine(pXSlave->target_i) || sprite[pXSlave->target_i].owner == pSprite->index)
|
2021-09-16 16:57:25 +00:00
|
|
|
aiSetTarget_(pXSlave, pSprite->x, pSprite->y, pSprite->z);
|
2019-11-08 19:57:01 +00:00
|
|
|
} else {
|
2021-09-16 16:57:25 +00:00
|
|
|
aiSetTarget_(pXSlave, pSprite->x, pSprite->y, pSprite->z); // try return to master
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-12-11 19:48:42 +00:00
|
|
|
|
|
|
|
pExtra->slaveCount = newCnt;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
short inDodge(AISTATE* aiState) {
|
|
|
|
if (aiState == &genDudeDodgeL) return 1;
|
|
|
|
else if (aiState == &genDudeDodgeD) return 2;
|
|
|
|
else if (aiState == &genDudeDodgeW) return 3;
|
|
|
|
else if (aiState == &genDudeDodgeShortL) return 4;
|
|
|
|
else if (aiState == &genDudeDodgeShortD) return 5;
|
|
|
|
else if (aiState == &genDudeDodgeShortW) return 6;
|
|
|
|
else if (aiState == &genDudeDodgeShorterL) return 7;
|
|
|
|
else if (aiState == &genDudeDodgeShorterD) return 8;
|
|
|
|
else if (aiState == &genDudeDodgeShorterW) return 9;
|
|
|
|
return 0;
|
|
|
|
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool inIdle(AISTATE* aiState) {
|
2019-11-24 20:53:51 +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) {
|
|
|
|
return (aiState == &genDudeFireL || aiState == &genDudeFireW
|
|
|
|
|| aiState == &genDudeFireD || aiState == &genDudeThrow || aiState == &genDudeThrow2 || aiState == &genDudePunch);
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
short inSearch(AISTATE* aiState) {
|
|
|
|
if (aiState->stateType == kAiStateSearch) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
short inChase(AISTATE* aiState) {
|
|
|
|
if (aiState == &genDudeChaseL) return 1;
|
|
|
|
else if (aiState == &genDudeChaseD) return 2;
|
|
|
|
else if (aiState == &genDudeChaseW) return 3;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkL) return 4;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkD) return 5;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkW) return 6;
|
|
|
|
else return 0;
|
|
|
|
}
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
short inRecoil(AISTATE* aiState) {
|
|
|
|
if (aiState == &genDudeRecoilL || aiState == &genDudeRecoilTesla) return 1;
|
|
|
|
else if (aiState == &genDudeRecoilD) return 2;
|
|
|
|
else if (aiState == &genDudeRecoilW) return 3;
|
|
|
|
else return 0;
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
short inDuck(AISTATE* aiState) {
|
|
|
|
if (aiState == &genDudeFireD) return 1;
|
|
|
|
else if (aiState == &genDudeChaseD) return 2;
|
|
|
|
else if (aiState == &genDudeChaseNoWalkD) return 3;
|
|
|
|
else if (aiState == &genDudeRecoilD) return 4;
|
|
|
|
else if (aiState == &genDudeDodgeShortD) return 5;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool canSwim(spritetype* pSprite) {
|
|
|
|
return gGenDudeExtra[pSprite->index].canSwim;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool canDuck(spritetype* pSprite) {
|
|
|
|
return gGenDudeExtra[pSprite->index].canDuck;
|
|
|
|
}
|
2019-09-21 11:02:17 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
bool canWalk(spritetype* pSprite) {
|
|
|
|
return gGenDudeExtra[pSprite->index].canWalk;
|
2019-09-21 11:02:17 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
int genDudeSeqStartId(XSPRITE* pXSprite) {
|
|
|
|
if (genDudePrepare(&sprite[pXSprite->reference], kGenDudePropertyStates)) return pXSprite->data2;
|
|
|
|
else return kGenDudeDefaultSeq;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool genDudePrepare(spritetype* pSprite, int propId) {
|
2020-03-01 20:36:28 +00:00
|
|
|
if (!spriRangeIsFine(pSprite->index)) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "!spriRangeIsFine(pSprite->index)");
|
2019-11-24 20:53:51 +00:00
|
|
|
return false;
|
2020-03-01 20:36:28 +00:00
|
|
|
} else if (!xspriRangeIsFine(pSprite->extra)) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "!xspriRangeIsFine(pSprite->extra)");
|
2019-11-24 20:53:51 +00:00
|
|
|
return false;
|
2019-11-08 19:57:01 +00:00
|
|
|
} else if (pSprite->type != kDudeModernCustom) {
|
2020-10-11 10:38:17 +00:00
|
|
|
Printf(PRINT_HIGH, "pSprite->type != kDudeModernCustom");
|
2019-11-24 20:53:51 +00:00
|
|
|
return false;
|
|
|
|
} else if (propId < kGenDudePropertyAll || propId >= kGenDudePropertyMax) {
|
|
|
|
viewSetSystemMessage("Unknown custom dude #%d property (%d)", pSprite->index, propId);
|
|
|
|
return false;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2021-08-31 19:52:26 +00:00
|
|
|
auto actor = &bloodActors[pSprite->index];
|
2019-11-24 20:53:51 +00:00
|
|
|
XSPRITE* pXSprite = &xsprite[pSprite->extra];
|
|
|
|
GENDUDEEXTRA* pExtra = &gGenDudeExtra[pSprite->index]; pExtra->updReq[propId] = false;
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
switch (propId) {
|
|
|
|
case kGenDudePropertyAll:
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudePropertyInitVals:
|
2021-08-31 19:59:32 +00:00
|
|
|
pExtra->moveSpeed = getGenDudeMoveSpeed(actor, 0, true, false);
|
2019-11-24 20:53:51 +00:00
|
|
|
pExtra->initVals[0] = pSprite->xrepeat;
|
|
|
|
pExtra->initVals[1] = pSprite->yrepeat;
|
|
|
|
pExtra->initVals[2] = pSprite->clipdist;
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudePropertyWeapon: {
|
2019-11-08 19:57:01 +00:00
|
|
|
pExtra->curWeapon = pXSprite->data1;
|
|
|
|
switch (pXSprite->data1) {
|
2021-06-16 19:13:04 +00:00
|
|
|
case VECTOR_TYPE_19: pExtra->curWeapon = kVectorBullet; break;
|
2020-03-01 20:36:28 +00:00
|
|
|
case kMissileUnused: pExtra->curWeapon = kMissileArcGargoyle; break;
|
2019-11-24 20:53:51 +00:00
|
|
|
case kThingDroppedLifeLeech: pExtra->curWeapon = kModernThingEnemyLifeLeech; break;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
pExtra->canAttack = false;
|
2020-07-25 20:47:46 +00:00
|
|
|
if (pExtra->curWeapon > 0 && getSequence(pXSprite->data2 + kGenDudeSeqAttackNormalL))
|
2019-11-24 20:53:51 +00:00
|
|
|
pExtra->canAttack = true;
|
|
|
|
|
2019-12-05 20:42:35 +00:00
|
|
|
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;
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
pExtra->isMelee = false;
|
2019-12-05 20:42:35 +00:00
|
|
|
if (pExtra->weaponType == kGenDudeWeaponKamikaze) pExtra->isMelee = true;
|
|
|
|
else if (pExtra->weaponType == kGenDudeWeaponHitscan) {
|
2019-11-24 20:53:51 +00:00
|
|
|
if (gVectorData[pExtra->curWeapon].maxDist > 0 && gVectorData[pExtra->curWeapon].maxDist <= kGenDudeMaxMeleeDist)
|
|
|
|
pExtra->isMelee = true;
|
|
|
|
}
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
case kGenDudePropertyDmgScale:
|
2019-11-08 19:57:01 +00:00
|
|
|
scaleDamage(pXSprite);
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
case kGenDudePropertyMass: {
|
2019-12-05 20:42:35 +00:00
|
|
|
// to ensure mass gets updated, let's clear all cache
|
2020-11-22 11:48:01 +00:00
|
|
|
SPRITEMASS* pMass = &gSpriteMass[pSprite->extra];
|
2019-11-08 19:57:01 +00:00
|
|
|
pMass->seqId = pMass->picnum = pMass->xrepeat = pMass->yrepeat = pMass->clipdist = 0;
|
|
|
|
pMass->mass = pMass->airVel = pMass->fraction = 0;
|
|
|
|
getSpriteMassBySize(pSprite);
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
case kGenDudePropertyAttack:
|
2020-05-05 18:50:14 +00:00
|
|
|
pExtra->fireDist = getRangeAttackDist(pSprite, 3000, 45000);
|
2019-11-24 20:53:51 +00:00
|
|
|
pExtra->throwDist = pExtra->fireDist; // temp
|
2019-11-08 19:57:01 +00:00
|
|
|
pExtra->baseDispersion = getDispersionModifier(pSprite, 200, 3500);
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudePropertyStates: {
|
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
pExtra->canFly = false;
|
2019-11-24 20:53:51 +00:00
|
|
|
|
|
|
|
// check the animation
|
2019-12-05 20:42:35 +00:00
|
|
|
int seqStartId = -1;
|
2020-02-05 21:17:05 +00:00
|
|
|
if (pXSprite->data2 <= 0) seqStartId = pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
|
2019-11-24 20:53:51 +00:00
|
|
|
else seqStartId = pXSprite->data2;
|
|
|
|
|
|
|
|
for (int i = seqStartId; i < seqStartId + kGenDudeSeqMax; i++) {
|
|
|
|
switch (i - seqStartId) {
|
|
|
|
case kGenDudeSeqIdleL:
|
|
|
|
case kGenDudeSeqDeathDefault:
|
2019-12-05 20:42:35 +00:00
|
|
|
case kGenDudeSeqAttackNormalL:
|
|
|
|
case kGenDudeSeqAttackThrow:
|
|
|
|
case kGenDudeSeqAttackPunch:
|
2020-07-25 20:47:46 +00:00
|
|
|
{
|
|
|
|
Seq* pSeq = getSequence(i);
|
|
|
|
if (!pSeq)
|
|
|
|
{
|
2020-02-05 21:17:05 +00:00
|
|
|
pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
|
2019-11-24 20:53:51 +00:00
|
|
|
viewSetSystemMessage("No SEQ animation id %d found for custom dude #%d!", i, pSprite->index);
|
|
|
|
viewSetSystemMessage("SEQ base id: %d", seqStartId);
|
2020-07-25 20:47:46 +00:00
|
|
|
}
|
|
|
|
else if ((i - seqStartId) == kGenDudeSeqAttackPunch)
|
|
|
|
{
|
2019-12-05 20:42:35 +00:00
|
|
|
pExtra->forcePunch = true; // required for those who don't have fire trigger in punch seq and for default animation
|
|
|
|
for (int i = 0; i < pSeq->nFrames; i++) {
|
2020-11-21 17:12:51 +00:00
|
|
|
if (!pSeq->frames[i].trigger) continue;
|
2019-12-05 20:42:35 +00:00
|
|
|
pExtra->forcePunch = false;
|
|
|
|
break;
|
2020-07-25 20:47:46 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-07-25 20:47:46 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudeSeqDeathExplode:
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->availDeaths[kDmgExplode] = !!getSequence(i);
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case kGenDudeSeqBurning:
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->canBurn = !!getSequence(i);
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case kGenDudeSeqElectocuted:
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->canElectrocute = !!getSequence(i);
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case kGenDudeSeqRecoil:
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->canRecoil = !!getSequence(i);
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case kGenDudeSeqMoveL: {
|
|
|
|
bool oldStatus = pExtra->canWalk;
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->canWalk = !!getSequence(i);
|
2019-11-24 20:53:51 +00:00
|
|
|
if (oldStatus != pExtra->canWalk) {
|
2021-09-15 22:40:09 +00:00
|
|
|
if (!spriRangeIsFine(pXSprite->target_i))
|
2020-07-25 20:47:46 +00:00
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeIdleW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else aiGenDudeNewState(actor, &genDudeIdleL);
|
2020-07-25 20:47:46 +00:00
|
|
|
}
|
|
|
|
else if (pExtra->canWalk)
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeChaseD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseL);
|
2020-07-25 20:47:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-05 18:40:31 +00:00
|
|
|
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseNoWalkW);
|
2021-08-31 19:52:26 +00:00
|
|
|
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeChaseNoWalkD);
|
|
|
|
else aiGenDudeNewState(actor, &genDudeChaseNoWalkL);
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kGenDudeSeqAttackNormalDW:
|
2020-07-25 20:47:46 +00:00
|
|
|
pExtra->canDuck = (getSequence(i) && getSequence(seqStartId + 14));
|
|
|
|
pExtra->canSwim = (getSequence(i) && getSequence(seqStartId + 13)
|
|
|
|
&& getSequence(seqStartId + 17));
|
2019-11-24 20:53:51 +00:00
|
|
|
break;
|
|
|
|
case kGenDudeSeqDeathBurn1: {
|
2020-07-25 20:47:46 +00:00
|
|
|
bool seq15 = getSequence(i);
|
|
|
|
bool seq16 = getSequence(seqStartId + 16);
|
2019-11-24 20:53:51 +00:00
|
|
|
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:
|
2019-12-11 19:48:42 +00:00
|
|
|
break;
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudeSeqReserved3:
|
|
|
|
case kGenDudeSeqReserved4:
|
|
|
|
case kGenDudeSeqReserved5:
|
|
|
|
case kGenDudeSeqReserved6:
|
|
|
|
case kGenDudeSeqReserved7:
|
|
|
|
case kGenDudeSeqReserved8:
|
2020-07-25 20:47:46 +00:00
|
|
|
/*if (getSequence(i)) {
|
2019-11-24 20:53:51 +00:00
|
|
|
viewSetSystemMessage("Found reserved SEQ animation (%d) for custom dude #%d!", i, pSprite->index);
|
|
|
|
viewSetSystemMessage("Using reserved animation is not recommended.");
|
|
|
|
viewSetSystemMessage("SEQ base id: %d", seqStartId);
|
|
|
|
}*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-11-24 20:53:51 +00:00
|
|
|
}
|
2019-11-08 19:57:01 +00:00
|
|
|
case kGenDudePropertyLeech:
|
2021-05-05 19:06:38 +00:00
|
|
|
pExtra->pLifeLeech = nullptr;
|
2019-11-08 19:57:01 +00:00
|
|
|
if (pSprite->owner != kMaxSprites - 1) {
|
2020-10-15 15:15:45 +00:00
|
|
|
int nSprite;
|
|
|
|
StatIterator it(kStatThing);
|
|
|
|
while ((nSprite = it.NextIndex()) >= 0)
|
|
|
|
{
|
2020-05-22 16:28:03 +00:00
|
|
|
if (sprite[nSprite].owner == pSprite->index && sprite[nSprite].type == kModernThingEnemyLifeLeech) {
|
2021-05-05 19:06:38 +00:00
|
|
|
pExtra->pLifeLeech = &bloodActors[nSprite];
|
2019-11-08 19:57:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2019-12-05 20:42:35 +00:00
|
|
|
|
2019-11-08 19:57:01 +00:00
|
|
|
case kGenDudePropertySlaves:
|
2020-10-15 15:15:45 +00:00
|
|
|
{
|
2019-11-08 19:57:01 +00:00
|
|
|
pExtra->slaveCount = 0; memset(pExtra->slave, -1, sizeof(pExtra->slave));
|
2020-10-15 15:15:45 +00:00
|
|
|
int nSprite;
|
|
|
|
StatIterator it(kStatDude);
|
|
|
|
while ((nSprite = it.NextIndex()) >= 0)
|
|
|
|
{
|
2019-12-11 19:48:42 +00:00
|
|
|
if (sprite[nSprite].owner != pSprite->index) continue;
|
|
|
|
else if (!IsDudeSprite(&sprite[nSprite]) || !xspriRangeIsFine(sprite[nSprite].extra) || xsprite[sprite[nSprite].extra].health <= 0) {
|
2019-11-08 19:57:01 +00:00
|
|
|
sprite[nSprite].owner = -1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
pExtra->slave[pExtra->slaveCount++] = nSprite;
|
|
|
|
if (pExtra->slaveCount > gGameOptions.nDifficulty)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (propId) break;
|
2021-05-06 07:00:49 +00:00
|
|
|
[[fallthrough]];
|
2020-10-15 15:15:45 +00:00
|
|
|
}
|
2019-11-24 20:53:51 +00:00
|
|
|
case kGenDudePropertySpriteSize: {
|
|
|
|
if (seqGetStatus(3, pSprite->extra) == -1)
|
|
|
|
seqSpawn(pXSprite->data2 + pXSprite->aiState->seqId, 3, pSprite->extra, -1);
|
|
|
|
|
|
|
|
// make sure dudes aren't in the floor or ceiling
|
|
|
|
int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot);
|
|
|
|
if (!(sector[pSprite->sectnum].ceilingstat & 0x0001))
|
|
|
|
pSprite->z += ClipLow(sector[pSprite->sectnum].ceilingz - zTop, 0);
|
|
|
|
if (!(sector[pSprite->sectnum].floorstat & 0x0001))
|
|
|
|
pSprite->z += ClipHigh(sector[pSprite->sectnum].floorz - zBot, 0);
|
|
|
|
|
2020-01-01 20:53:29 +00:00
|
|
|
pSprite->clipdist = ClipRange((pSprite->xrepeat + pSprite->yrepeat) >> 1, 4, 120);
|
2019-11-24 20:53:51 +00:00
|
|
|
if (propId) break;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-24 20:53:51 +00:00
|
|
|
return true;
|
2019-11-08 19:57:01 +00:00
|
|
|
}
|
|
|
|
|
2020-04-04 19:48:23 +00:00
|
|
|
void genDudePostDeath(spritetype* pSprite, DAMAGE_TYPE damageType, int damage) {
|
2021-06-16 19:13:04 +00:00
|
|
|
if (damageType == kDamageExplode) {
|
2020-04-04 19:48:23 +00:00
|
|
|
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
if (pDudeInfo->nGibType[i] > -1)
|
|
|
|
GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
fxSpawnBlood(pSprite, damage);
|
|
|
|
}
|
|
|
|
|
|
|
|
gKillMgr.AddKill(pSprite);
|
|
|
|
|
|
|
|
pSprite->type = kThingBloodChunks;
|
|
|
|
actPostSprite(pSprite->index, kStatThing);
|
|
|
|
}
|
|
|
|
|
2020-12-04 22:21:42 +00:00
|
|
|
void aiGenDudeInitSprite(spritetype* pSprite, XSPRITE* pXSprite)
|
|
|
|
{
|
|
|
|
auto actor = &bloodActors[pSprite->index];
|
2020-04-04 19:48:23 +00:00
|
|
|
switch (pSprite->type) {
|
|
|
|
case kDudeModernCustom: {
|
2021-10-06 20:20:57 +00:00
|
|
|
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
|
|
|
|
pDudeExtraE->active = 0;
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeIdleL);
|
2020-04-04 19:48:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case kDudeModernCustomBurning:
|
2021-08-31 19:52:26 +00:00
|
|
|
aiGenDudeNewState(actor, &genDudeBurnGoto);
|
2020-04-04 19:48:23 +00:00
|
|
|
pXSprite->burnTime = 1200;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pSprite->flags = 15;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-26 20:01:22 +00:00
|
|
|
END_BLD_NS
|
2020-08-02 16:12:43 +00:00
|
|
|
#endif
|