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

2555 lines
80 KiB
C++
Raw Normal View History

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