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

2607 lines
100 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[] = {
{ 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] = {
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
2406, 2280, 2185, 2155, 2620, 3135
};
2021-09-20 18:16:53 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void forcePunch(DBloodActor* actor)
{
2021-09-04 21:27:21 +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
{
spritetype* pSprite = &actor->s();
if (actor->GetTarget() != nullptr)
2020-11-07 15:13:03 +00:00
{
int fStart = 0;
int fEnd = 0;
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
unsigned int clipMask = (weaponType == kGenDudeWeaponMissile) ? CLIPMASK0 : CLIPMASK1;
2020-11-07 15:13:03 +00:00
for (int i = -8191; i < 8192; i += by)
{
HitScan(actor, pSprite->pos.Z, bcos(pSprite->ang), bsin(pSprite->ang), i, clipMask, dist);
if (!fStart && actor->GetTarget() == gHitInfo.actor()) fStart = i;
else if (fStart && actor->GetTarget() != gHitInfo.actor())
2020-11-07 15:13:03 +00:00
{
fEnd = i;
break;
}
}
2020-11-07 15:13:03 +00:00
if (fStart != fEnd)
{
if (weaponType == kGenDudeWeaponHitscan)
2020-11-07 15:13:03 +00:00
{
actor->dudeSlope = fStart - ((fStart - fEnd) >> 2);
2020-11-07 15:13:03 +00:00
}
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)
{
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
for (int i = 0; i < kGenDudePropertyMax; i++) {
2021-05-06 08:51:39 +00:00
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-09-20 18:16:53 +00:00
auto const pSprite = &actor->s();
auto const target = actor->GetTarget();
if (target != nullptr)
{
int nZOffset1 = getDudeInfo(pSprite->type)->eyeHeight * pSprite->yrepeat << 2;
2019-09-19 22:42:45 +00:00
int nZOffset2 = 0;
if(target->IsDudeActor())
nZOffset2 = getDudeInfo(target->spr.type)->eyeHeight * target->spr.yrepeat << 2;
int dx = bcos(pSprite->ang);
int dy = bsin(pSprite->ang);
int dz = nZOffset1 - nZOffset2;
2019-09-19 22:42:45 +00:00
2021-05-05 17:15:25 +00:00
if (!playGenDudeSound(actor, kGenDudeSndAttackMelee))
sfxPlay3DSound(actor, 530, 1, 0);
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-19 22:42:45 +00:00
2021-09-20 18:16:53 +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();
if (actor->GetTarget() == nullptr) return;
int dx, dy, dz;
actor->xvel = actor->yvel = 0;
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
int dispersion = pExtra->baseDispersion;
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)
{
dx = bcos(pSprite->ang); dy = bsin(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
{
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))
2021-08-27 17:12:22 +00:00
sfxPlayVectorSound(actor, 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)
{
spawned->SetOwner(actor);
2021-08-29 07:44:08 +00:00
if (spawned->hasX())
{
spawned->SetTarget(actor->GetTarget());
if (spawned->GetTarget() != nullptr)
aiActivateDude(spawned);
}
gKillMgr.AddNewKill(1);
pExtra->slave[pExtra->slaveCount++] = spawned;
2021-05-05 17:15:25 +00:00
if (!playGenDudeSound(actor, kGenDudeSndAttackNormal))
sfxPlay3DSoundCP(actor, 379, 1, 0, 0x10000 - Random3(0x3000));
}
2019-09-19 22:42:45 +00:00
}
2021-09-20 18:16:53 +00:00
}
else if (pExtra->weaponType == kGenDudeWeaponMissile)
{
dx = bcos(pSprite->ang); dy = bsin(pSprite->ang); dz = actor->dudeSlope;
2019-09-19 22:42:45 +00:00
// dispersal modifiers here
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))
2021-08-27 17:12:22 +00:00
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)
{
ThrowThing(actor, true);
2019-09-19 22:42:45 +00:00
}
void ThrowCallback2(int, DBloodActor* actor)
{
ThrowThing(actor, false);
2019-09-19 22:42:45 +00:00
}
static void ThrowThing(DBloodActor* actor, bool impact)
{
spritetype* pSprite = &actor->s();
auto target = actor->GetTarget();
2019-09-19 22:42:45 +00:00
if (target == nullptr)
2019-09-19 22:42:45 +00:00
return;
if (!(target->spr.type >= kDudeBase && target->spr.type < kDudeMax))
2019-09-19 22:42:45 +00:00
return;
int curWeapon = actor->genDudeExtra.curWeapon;
int weaponType = actor->genDudeExtra.weaponType;
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];
if (!gThingInfoExtra[curWeapon - kThingBase].allowThrow) return;
2021-05-05 17:15:25 +00:00
else if (!playGenDudeSound(actor, kGenDudeSndAttackThrow))
sfxPlay3DSound(actor, 455, -1, 0);
int zThrow = 14500;
int dx = target->spr.pos.X - pSprite->pos.X;
int dy = target->spr.pos.Y - pSprite->pos.Y;
int dz = target->spr.pos.Z - pSprite->pos.Z;
int dist = approxDist(dx, dy);
2021-05-05 19:06:38 +00:00
auto actLeech = leechIsDropped(actor);
XSPRITE* pXLeech = actLeech && actLeech->hasX()? &actLeech->x() : nullptr;
switch (curWeapon) {
case kModernThingEnemyLifeLeech:
case kThingDroppedLifeLeech:
zThrow = 5000;
// pickup life leech before throw it again
if (actLeech != NULL) removeLeech(actLeech);
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;
auto const pXSpawned = &spawned->x();
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;
pXSpawned->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) pXSpawned->data1 = 43;
else if (spawned->spr.xrepeat > 40) pXSpawned->data1 = 33;
else if (spawned->spr.xrepeat > 30) pXSpawned->data1 = 23;
else pXSpawned->data1 = 12;
2019-09-19 22:42:45 +00:00
return;
case kThingTNTBarrel:
case kThingArmedProxBomb:
case kThingArmedSpray:
impact = false;
break;
case kModernThingTNTProx:
pXSpawned->state = 0;
pXSpawned->Proximity = true;
return;
case kModernThingEnemyLifeLeech:
if (actLeech != nullptr) pXSpawned->health = pXLeech->health;
else pXSpawned->health = ((pThinkInfo->startHealth << 4) * gGameOptions.nDifficulty) >> 1;
sfxPlay3DSound(actor, 490, -1, 0);
pXSpawned->data3 = 512 / (gGameOptions.nDifficulty + 1);
spawned->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
spawned->spr.pal = 6;
spawned->spr.clipdist = 0;
spawned->SetTarget(actor->GetTarget());
pXSpawned->Proximity = true;
pXSpawned->stateTimer = 1;
actor->genDudeExtra.pLifeLeech = spawned;
evPostActor(spawned, 80, kCallbackLeechStateTimer);
return;
}
2019-09-19 22:42:45 +00:00
if (impact == true && dist <= 7680) pXSpawned->Impact = true;
else {
pXSpawned->Impact = false;
evPostActor(spawned, 120 * Random(2) + 120, kCmdOn);
2019-09-19 22:42:45 +00:00
}
}
2021-09-20 18:16:53 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void unicultThinkSearch(DBloodActor* actor)
{
auto pXSprite = &actor->x();
// TO DO: if can't see the target, but in fireDist range - stop moving and look around
//viewSetSystemMessage("IN SEARCH");
aiChooseDirection(actor,pXSprite->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-09-20 18:16:53 +00:00
auto const pXSprite = &actor->x();
auto const pSprite = &actor->s();
if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
{
Printf(PRINT_HIGH, "pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
return;
}
2019-09-19 22:42:45 +00:00
int dx = pXSprite->targetX - pSprite->pos.X;
int dy = pXSprite->targetY - pSprite->pos.Y;
2019-09-19 22:42:45 +00:00
int nAngle = getangle(dx, dy);
aiChooseDirection(actor,nAngle);
2019-09-19 22:42:45 +00:00
// if reached target, change to search mode
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);
else aiGenDudeNewState(actor, &genDudeSearchL);
2019-09-19 22:42:45 +00:00
}
aiThinkTarget(actor);
2019-09-19 22:42:45 +00:00
}
2021-09-20 18:16:53 +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();
if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax) return;
auto const target = actor->GetTarget();
if (target == nullptr)
{
2021-05-05 18:40:31 +00:00
if(spriteIsUnderwater(actor,false)) aiGenDudeNewState(actor, &genDudeGotoW);
else aiGenDudeNewState(actor, &genDudeGotoL);
2019-09-19 22:42:45 +00:00
return;
2021-08-31 20:01:28 +00:00
}
else
{
genDudeUpdate(actor);
}
if (!target || !target->IsDudeActor() || !target->hasX()) // target lost
{
2021-05-05 18:40:31 +00:00
if(spriteIsUnderwater(actor,false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
else aiGenDudeNewState(actor, &genDudeSearchShortL);
actor->SetTarget(nullptr);
return;
}
XSPRITE* pXTarget = &target->x();
if (pXTarget->health <= 0) // target is dead
{
PLAYER* pPlayer = NULL;
if ((!target->IsPlayerActor()) || ((pPlayer = getPlayerById(target->spr.type)) != NULL && pPlayer->fragger == actor))
{
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);
else aiGenDudeNewState(actor, &genDudeSearchShortL);
}
2021-05-05 18:40:31 +00:00
else if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeGotoW);
else aiGenDudeNewState(actor, &genDudeGotoL);
actor->SetTarget(nullptr);
2019-09-19 22:42:45 +00:00
return;
}
// check target
int dx = target->spr.pos.X - pSprite->pos.X;
int dy = target->spr.pos.Y - pSprite->pos.Y;
int dist = ClipLow((int)approxDist(dx, dy), 1);
2019-09-19 22:42:45 +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.
int xvelocity = actor->xvel;
int yvelocity = actor->yvel;
if (inAttack(pXSprite->aiState))
xvelocity = yvelocity = ClipLow(pSprite->clipdist >> 1, 1);
//aiChooseDirection(actor,getangle(dx, dy));
aiGenDudeChooseDirection(actor, getangle(dx, dy), xvelocity, yvelocity);
GENDUDEEXTRA* pExtra = &actor->genDudeExtra;
2021-09-20 18:16:53 +00:00
if (!pExtra->canAttack)
{
if (pExtra->canWalk) aiSetTarget(actor, actor); // targeting self???
2021-05-05 18:40:31 +00:00
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)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeSearchShortW);
else aiGenDudeNewState(actor, &genDudeSearchShortL);
actor->SetTarget(nullptr);
2019-09-19 22:42:45 +00:00
return;
}
}
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
int losAngle = ((getangle(dx, dy) + 1024 - pSprite->ang) & 2047) - 1024;
int eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;
if (dist > pDudeInfo->seeDist || !cansee(target->spr.pos.X, target->spr.pos.Y, target->spr.pos.Z, target->spr.sector(),
pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z - eyeAboveZ, pSprite->sector()))
{
2021-05-05 18:40:31 +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) {
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);
actor->dudeSlope = dist == 0 ? 0 : DivScale(target->spr.pos.Z - pSprite->pos.Z, dist, 10);
int curWeapon = actor->genDudeExtra.curWeapon;
int weaponType = actor->genDudeExtra.weaponType;
2021-05-05 19:06:38 +00:00
auto actLeech = leechIsDropped(actor);
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);
else aiGenDudeNewState(actor, &genDudeChaseL);
return;
}
2021-05-05 18:40:31 +00:00
else if (dist < 12264 && dist > 7680 && !spriteIsUnderwater(actor, false) && curWeapon != kModernThingEnemyLifeLeech)
{
int pHit = HitScan(actor, pSprite->pos.Z, dx, dy, 0, 16777280, 0);
switch (pHit) {
case 0:
case 4:
return;
default:
aiGenDudeNewState(actor, &genDudeThrow);
return;
}
2019-09-19 22:42:45 +00:00
2021-09-20 18:16:53 +00:00
}
else if (dist > 4072 && dist <= 11072 && !spriteIsUnderwater(actor, false) && !actor->GetSpecialOwner())
2021-09-20 18:16:53 +00:00
{
switch (curWeapon)
{
case kModernThingEnemyLifeLeech:
{
if (actLeech == nullptr)
2021-09-20 18:16:53 +00:00
{
aiGenDudeNewState(actor, &genDudeThrow2);
genDudeThrow2.nextState = &genDudeDodgeShortL;
2019-09-19 22:42:45 +00:00
return;
}
2019-09-19 22:42:45 +00:00
int ldist = aiFightGetTargetDist(target, pDudeInfo, actLeech);
if (ldist > 3 || !cansee(target->spr.pos.X, target->spr.pos.Y, target->spr.pos.Z, target->spr.sector(),
actLeech->spr.pos.X, actLeech->spr.pos.Y, actLeech->spr.pos.Z, actLeech->spr.sector()) || actLeech->GetTarget() == nullptr)
{
aiGenDudeNewState(actor, &genDudeThrow2);
genDudeThrow2.nextState = &genDudeDodgeShortL;
}
else
{
genDudeThrow2.nextState = &genDudeChaseL;
if (dist > 5072 && Chance(0x5000))
{
2021-05-06 08:24:29 +00:00
if (!canDuck(actor) || Chance(0x4000)) aiGenDudeNewState(actor, &genDudeDodgeShortL);
else aiGenDudeNewState(actor, &genDudeDodgeShortD);
}
else
{
aiGenDudeNewState(actor, &genDudeChaseL);
}
2019-09-19 22:42:45 +00:00
}
}
return;
case kModernThingThrowableRock:
if (Chance(0x4000)) aiGenDudeNewState(actor, &genDudeThrow2);
2021-05-05 17:15:25 +00:00
else playGenDudeSound(actor, kGenDudeSndTargetSpot);
return;
default:
aiGenDudeNewState(actor, &genDudeThrow2);
return;
2019-09-19 22:42:45 +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))
{
if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
else aiGenDudeNewState(actor, &genDudeDodgeW);
}
else if (Chance(0x9000)) aiGenDudeNewState(actor, &genDudePunch);
else aiGenDudeNewState(actor, &genDudeDodgeL);
return;
2019-09-19 22:42:45 +00:00
}
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;
2019-09-19 22:42:45 +00:00
if (weaponType == kGenDudeWeaponHitscan)
{
if ((vdist = gVectorData[curWeapon].maxDist) <= 0)
vdist = defDist;
2019-09-19 22:42:45 +00:00
}
else if (weaponType == kGenDudeWeaponSummon)
{
// don't attack slaves
if (actor->GetTarget() != nullptr && actor->GetTarget()->GetOwner() == actor)
{
aiSetTarget(actor, pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z);
return;
2021-09-20 18:16:53 +00:00
}
else if (actor->genDudeExtra.slaveCount > gGameOptions.nDifficulty || dist < meleeVector->maxDist)
2021-09-20 18:16:53 +00:00
{
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);
2019-09-19 22:42:45 +00:00
return;
}
}
2019-09-19 22:42:45 +00:00
}
else if (weaponType == kGenDudeWeaponMissile)
{
// special handling for flame, explosive and life leech missiles
2021-05-06 07:09:58 +00:00
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 || pXSprite->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);
2019-09-19 22:42:45 +00:00
return;
case kMissileFlameSpray:
case kMissileFlameHound:
//viewSetSystemMessage("%d", pXTarget->burnTime);
2021-05-05 18:40:31 +00:00
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 && pXTarget->burnTime >= 2000 && target->GetBurnSource() == actor)
{
if (dist > meleeVector->maxDist) aiGenDudeNewState(actor, &genDudeChaseL);
else aiGenDudeNewState(actor, &genDudePunch);
return;
2019-09-19 22:42:45 +00:00
}
vdist = 3500 + (gGameOptions.nDifficulty * 400);
break;
}
}
else if (weaponType == kGenDudeWeaponKamikaze)
{
int nType = curWeapon - kTrapExploder;
const EXPLOSION* pExpl = &explodeInfo[nType];
if (CheckProximity(actor, target->spr.pos.X, target->spr.pos.Y, target->spr.pos.Z, target->spr.sector(), pExpl->radius >> 1))
{
actor->xvel = actor->yvel = actor->zvel = 0;
2021-05-06 07:09:58 +00:00
if (doExplosion(actor, nType) && pXSprite->health > 0)
actDamageSprite(actor, actor, kDamageExplode, 65535);
2019-09-19 22:42:45 +00:00
}
return;
}
int state = checkAttackState(actor);
2021-05-06 06:45:50 +00:00
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)
hit = HitScan(actor, pSprite->pos.Z, bcos(pSprite->ang), bsin(pSprite->ang), actor->dudeSlope, CLIPMASK1, dist);
else if (weaponType == kGenDudeWeaponMissile)
hit = HitScan(actor, pSprite->pos.Z, bcos(pSprite->ang), bsin(pSprite->ang), actor->dudeSlope, CLIPMASK0, dist);
if (hit >= 0)
{
targetDist = dist - (target->spr.clipdist << 2);
objDist = approxDist(gHitInfo.hitpos.X - pSprite->pos.X, gHitInfo.hitpos.Y - pSprite->pos.Y);
}
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;
2021-11-19 00:13:33 +00:00
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:
2020-11-07 15:13:03 +00:00
if (weaponType != kGenDudeWeaponMissile && genDudeAdjustSlope(actor, dist, weaponType)
&& 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);
else aiGenDudeNewState(actor, &genDudeChaseL);
return;
case 3:
if (hitactor->spr.statnum == kStatFX || hitactor->spr.statnum == kStatProjectile || hitactor->spr.statnum == kStatDebris)
break;
2021-09-01 19:52:36 +00:00
if (hitactor->IsDudeActor() && (weaponType != kGenDudeWeaponHitscan || hscn))
{
// dodge a bit in sides
2021-05-06 07:43:35 +00:00
if (hitactor->GetTarget() != actor)
{
if (pExtra->baseDispersion < 1024 && weaponType != kGenDudeWeaponMissile)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
}
2021-05-05 18:40:31 +00:00
else if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShortW);
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShortD);
else aiGenDudeNewState(actor, &genDudeDodgeShortL);
switch (hitactor->spr.type)
{
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))
{
if (!inAttack(hitactor->xspr.aiState))
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(hitactor)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterW);
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(hitactor, &genDudeDodgeShorterD);
else aiGenDudeNewState(hitactor, &genDudeDodgeShorterL);
// preferable in opposite sides
if (Chance(0x8000))
{
if (pXSprite->dodgeDir == 1) hitactor->xspr.dodgeDir = -1;
else if (pXSprite->dodgeDir == -1) hitactor->xspr.dodgeDir = 1;
}
break;
}
if (pSprite->pos.X < hitactor->spr.pos.X)
2020-11-07 15:13:03 +00:00
{
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) pXSprite->dodgeDir = -1;
else pXSprite->dodgeDir = 1;
2020-11-07 15:13:03 +00:00
}
else
{
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) pXSprite->dodgeDir = 1;
else pXSprite->dodgeDir = -1;
}
}
break;
default:
if (pSprite->pos.X < hitactor->spr.pos.X)
2020-11-07 15:13:03 +00:00
{
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.X) pXSprite->dodgeDir = -1;
else pXSprite->dodgeDir = 1;
2020-11-07 15:13:03 +00:00
}
else
{
if (Chance(0x9000) && target->spr.pos.X > hitactor->spr.pos.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;
2021-09-04 18:12:05 +00:00
VectorScan(actor, 0, 0, bcos(pSprite->ang), bsin(pSprite->ang), actor->dudeSlope, dist, 1);
if (actor == gHitInfo.actor()) break;
2021-08-27 12:01:51 +00:00
bool immune = nnExtIsImmune(hitactor, gVectorData[curWeapon].dmgType);
if (!(hitactor->hasX() && (!immune || (immune && hitactor->spr.statnum == kStatThing && hitactor->xspr.Vector)) && !hitactor->xspr.locked))
2020-11-07 15:13:03 +00:00
{
if ((approxDist(gHitInfo.hitpos.X - pSprite->pos.X, gHitInfo.hitpos.Y - pSprite->pos.Y) <= 1500 && !blck)
2020-11-07 15:13:03 +00:00
|| (dist <= (int)(pExtra->fireDist / ClipLow(Random(4), 1))))
{
//viewSetSystemMessage("GO CHASE");
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
else aiGenDudeNewState(actor, &genDudeChaseL);
return;
}
int wd1 = picWidth(hitactor->spr.picnum, hitactor->spr.xrepeat);
int wd2 = picWidth(pSprite->picnum, pSprite->xrepeat);
if (wd1 < (wd2 << 3))
{
//viewSetSystemMessage("OBJ SIZE: %d DUDE SIZE: %d", wd1, wd2);
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeDodgeShorterW);
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeDodgeShorterD);
else aiGenDudeNewState(actor, &genDudeDodgeShorterL);
if (pSprite->pos.X < hitactor->spr.pos.X)
{
if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) pXSprite->dodgeDir = -1;
else pXSprite->dodgeDir = 1;
}
else
{
if (Chance(0x3000) && target->spr.pos.X > hitactor->spr.pos.X) pXSprite->dodgeDir = 1;
else pXSprite->dodgeDir = -1;
}
2021-08-29 17:09:29 +00:00
if (actor->hit.hit.type == kHitWall || actor->hit.hit.type == kHitSprite)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
else aiGenDudeNewState(actor, &genDudeChaseL);
pXSprite->goalAng = Random(kAng360);
//viewSetSystemMessage("WALL OR SPRITE TOUCH");
}
}
else
{
2021-05-05 18:40:31 +00:00
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);
2021-09-04 18:12:05 +00:00
if (masked) VectorScan(actor, 0, 0, bcos(pSprite->ang), bsin(pSprite->ang), actor->dudeSlope, dist, 1);
if ((actor != gHitInfo.actor()) && (pHWall->type != kWallGib || !masked || pXHWall == NULL || !pXHWall->triggerVector || pXHWall->locked))
{
2021-05-05 18:40:31 +00:00
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 - pSprite->pos.X, gHitInfo.hitpos.Y - pSprite->pos.Y);
int targetDist = approxDist(gHitInfo.hitpos.X - target->spr.pos.X, gHitInfo.hitpos.Y - target->spr.pos.Y);
if (dudeDist < mdist)
{
//viewSetSystemMessage("DUDE CLOSE TO OBJ: %d, MDIST: %d", dudeDist, mdist);
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeChaseW);
else aiGenDudeNewState(actor, &genDudeChaseL);
return;
}
else if (targetDist <= 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++)
{
2021-08-27 12:01:51 +00:00
if (gMissileInfoExtra[curWeapon - kMissileBase].dmgType[i] && (failed = nnExtIsImmune(hitactor, i)) == false)
break;
}
}
if (failed)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor)) aiGenDudeNewState(actor, &genDudeSearchW);
else aiGenDudeNewState(actor, &genDudeSearchL);
return;
}
break;
}
}
break;
}
}
}
aiSetTarget(actor, actor->GetTarget());
switch (state)
{
case 1:
aiGenDudeNewState(actor, &genDudeFireW);
pXSprite->aiState->nextState = &genDudeFireW;
break;
case 2:
aiGenDudeNewState(actor, &genDudeFireD);
pXSprite->aiState->nextState = &genDudeFireD;
break;
default:
aiGenDudeNewState(actor, &genDudeFireL);
pXSprite->aiState->nextState = &genDudeFireL;
break;
}
}
else
{
2021-09-04 21:27:21 +00:00
if (seqGetID(actor) == pXSprite->data2 + ((state < 3) ? 8 : 6))
{
if (state == 1) pXSprite->aiState->nextState = &genDudeChaseW;
else if (state == 2) pXSprite->aiState->nextState = &genDudeChaseD;
else pXSprite->aiState->nextState = &genDudeChaseL;
}
else if (state == 1 && pXSprite->aiState != &genDudeChaseW && pXSprite->aiState != &genDudeFireW)
{
aiGenDudeNewState(actor, &genDudeChaseW);
pXSprite->aiState->nextState = &genDudeFireW;
}
else if (state == 2 && pXSprite->aiState != &genDudeChaseD && pXSprite->aiState != &genDudeFireD)
{
aiGenDudeNewState(actor, &genDudeChaseD);
pXSprite->aiState->nextState = &genDudeFireD;
}
else if (pXSprite->aiState != &genDudeChaseL && pXSprite->aiState != &genDudeFireL)
{
aiGenDudeNewState(actor, &genDudeChaseL);
pXSprite->aiState->nextState = &genDudeFireL;
}
}
}
2019-09-19 22:42:45 +00:00
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int checkAttackState(DBloodActor* actor)
{
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;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static int getGenDudeMoveSpeed(DBloodActor *actor,int which, bool mul, bool shift)
{
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
2019-09-19 22:42:45 +00:00
int speed = -1; int step = 2500; int maxSpeed = 146603;
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;
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;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiGenDudeMoveForward(DBloodActor* actor)
{
auto pXSprite = &actor->x();
auto pSprite = &actor->s();
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
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)
{
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;
if (abs(nAng) > 341)
return;
if (actor->GetTarget() == nullptr)
pSprite->ang = (pSprite->ang + 256) & 2047;
int dx = pXSprite->targetX - pSprite->pos.X;
int dy = pXSprite->targetY - pSprite->pos.Y;
int nDist = approxDist(dx, dy);
if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
return;
int nCos = Cos(pSprite->ang);
int nSin = Sin(pSprite->ang);
int vx = actor->xvel;
int vy = actor->yvel;
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->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-09-19 22:42:45 +00:00
// don't move forward if trying to turn around
if (abs(dang) > kAng60)
2019-09-19 22:42:45 +00:00
return;
2019-09-19 22:42:45 +00:00
int sin = Sin(pSprite->ang);
int cos = Cos(pSprite->ang);
int frontSpeed = actor->genDudeExtra.moveSpeed;
actor->xvel += MulScale(cos, frontSpeed, 30);
actor->yvel += MulScale(sin, frontSpeed, 30);
}
2019-09-19 22:42:45 +00:00
}
2021-09-20 18:16:53 +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))
{
Printf(PRINT_HIGH, "pSprite->type >= kDudeBase && pSprite->type < kDudeMax");
return;
}
// TO-DO: Take in account if sprite is flip-x, so enemy select correct angle
int vc = ((a3 + 1024 - pSprite->ang) & 2047) - 1024;
int t1 = DMulScale(xvel, Cos(pSprite->ang), yvel, Sin(pSprite->ang), 30);
int vsi = ((t1 * 15) >> 12) / 2; int v8 = (vc >= 0) ? 341 : -341;
if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc, vsi))
pXSprite->goalAng = pSprite->ang + vc;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + vc / 2, vsi))
pXSprite->goalAng = pSprite->ang + vc / 2;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - vc / 2, vsi))
pXSprite->goalAng = pSprite->ang - vc / 2;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang + v8, vsi))
pXSprite->goalAng = pSprite->ang + v8;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang, vsi))
pXSprite->goalAng = pSprite->ang;
else if (CanMove(actor, actor->GetTarget(), pSprite->ang - v8, vsi))
pXSprite->goalAng = pSprite->ang - v8;
else
pXSprite->goalAng = pSprite->ang + 341;
pXSprite->dodgeDir = (Chance(0x8000)) ? 1 : -1;
if (!CanMove(actor, actor->GetTarget(), pSprite->ang + pXSprite->dodgeDir * 512, 512))
{
pXSprite->dodgeDir = -pXSprite->dodgeDir;
if (!CanMove(actor, actor->GetTarget(), pSprite->ang + pXSprite->dodgeDir * 512, 512))
pXSprite->dodgeDir = 0;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiGenDudeNewState(DBloodActor* actor, AISTATE* pAIState)
{
if (!actor->hasX())
{
return;
}
auto const pXSprite = &actor->x();
// redirect dudes which cannot walk to non-walk states
if (!actor->genDudeExtra.canWalk)
{
2019-09-19 22:42:45 +00:00
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) {
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor, false)) pAIState = &genDudeRecoilW;
else pAIState = &genDudeRecoilL;
}
}
if (!actor->genDudeExtra.canRecoil)
2021-09-20 18:16:53 +00:00
{
if (pAIState == &genDudeRecoilL || pAIState == &genDudeRecoilD) pAIState = &genDudeIdleL;
else if (pAIState == &genDudeRecoilW) pAIState = &genDudeIdleW;
}
pXSprite->stateTimer = pAIState->stateTicks; pXSprite->aiState = pAIState;
int stateSeq = pXSprite->data2 + pAIState->seqId;
2021-09-20 18:16:53 +00:00
if (pAIState->seqId >= 0 && getSequence(stateSeq))
{
2021-09-04 21:27:21 +00:00
seqSpawn(stateSeq, actor, pAIState->funcId);
}
if (pAIState->enterFunc)
pAIState->enterFunc(actor);
}
2021-09-20 18:16:53 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-05-05 17:15:25 +00:00
bool playGenDudeSound(DBloodActor* actor, int mode)
{
auto const pXSprite = &actor->x();
if (mode < kGenDudeSndTargetSpot || mode >= kGenDudeSndMax) return false;
2021-05-05 17:15:25 +00:00
const GENDUDESND* sndInfo = &gCustomDudeSnd[mode]; bool gotSnd = false;
int sndStartId = pXSprite->sysData1;
2021-05-05 17:15:25 +00:00
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
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
pExtra->sndPlaying = false;
}
2019-09-19 22:42:45 +00:00
if (sndId < 0) return false;
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
int maxRetries = 5;
while (maxRetries-- > 0) {
2019-09-19 22:42:45 +00:00
int random = Random(rand);
if (!soundEngine->FindSoundByResID(sndId + random)) continue;
sndId = sndId + random;
gotSnd = true;
break;
2019-09-19 22:42:45 +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)
{
int maxSndId = sndId + rand;
2021-05-05 17:15:25 +00:00
while (sndId++ < maxSndId)
{
if (!soundEngine->FindSoundByResID(sndId)) continue;
gotSnd = true;
break;
2019-09-19 22:42:45 +00:00
}
}
}
if (gotSnd == false) return false;
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);
pExtra->sndPlaying = true;
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();
return (IsUnderwaterSector(pSprite->sector())
2021-05-05 18:40:31 +00:00
|| (oldWay && (pXSprite->medium == kMediumWater || pXSprite->medium == kMediumGoo)));
2019-09-19 22:42:45 +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
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void removeDudeStuff(DBloodActor* actor)
{
BloodStatIterator it(kStatThing);
while (auto actor2 = it.Next())
2020-10-15 15:15:45 +00:00
{
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:
2021-05-05 19:22:09 +00:00
killDudeLeech(actor2);
break;
2019-09-19 22:42:45 +00:00
}
}
2020-10-15 15:15:45 +00:00
it.Reset(kStatDude);
while (auto actor2 = it.Next())
2020-10-15 15:15:45 +00:00
{
if (actor2->GetOwner() != actor) continue;
actDamageSprite(actor2->GetOwner(), actor2, kDamageFall, 65535);
2019-09-19 22:42:45 +00:00
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void removeLeech(DBloodActor* actLeech, bool delSprite)
{
if (actLeech != nullptr)
{
auto effectactor = gFX.fxSpawnActor((FX_ID)52, actLeech->spr.sector(), actLeech->spr.pos.X, actLeech->spr.pos.Y, actLeech->spr.pos.Z, actLeech->spr.ang);
if (effectactor != nullptr)
{
effectactor->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FACING;
effectactor->spr.pal = 6;
2019-09-19 22:42:45 +00:00
int repeat = 64 + Random(50);
effectactor->spr.xrepeat = repeat;
effectactor->spr.yrepeat = repeat;
2019-09-19 22:42:45 +00:00
}
sfxPlay3DSoundCP(actLeech, 490, -1, 0,60000);
2021-05-06 08:24:29 +00:00
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-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
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DBloodActor* getNextIncarnation(DBloodActor* actor)
{
XSPRITE* pXSprite = &actor->x();
for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++)
{
if (!rxBucket[i].isActor()) continue;
auto rxactor = rxBucket[i].actor();
if (actor != rxactor && rxactor->spr.statnum == kStatInactive) return rxactor;
}
2021-09-01 17:48:33 +00:00
return nullptr;
2019-09-19 22:42:45 +00:00
}
2021-05-06 06:45:50 +00:00
bool dudeIsMelee(DBloodActor* actor)
{
return actor->genDudeExtra.isMelee;
2021-05-06 06:45:50 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-05-06 07:02:49 +00:00
static void scaleDamage(DBloodActor* actor)
{
auto const pXSprite = &actor->x();
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];
2019-09-19 22:42:45 +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:
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:
- Don't change statnum of thing converted to debris back to thing on impact trigger when firing with vector weapon. - Change damage scale and min firedist for Custom Dude. - Remove unnecessary checks in callback of tracking condition. - Fix picWidth() function. - Better initialization of modern stuff. - kModernSeqSpawner: disable all other spawners with same TX ID when enabling current. - Fix: sceneQav was not playing if resurrected with COUSTEAU cheat. - kModernPictureChanger: remove kModernFlag01 feature (deprecated). - kModernSectorFXChanger: add flags to control where exactly light effect should appear. - kModernCondition: - add delay before sending command if condition is true. - take in account state, so kCmdState and kCmdNotState is useful. - fix wrong comparison result in some conditions. - add new various conditions. - kModernPlayerControl: - fix start / stop playing qav scene when triggered with event command converted to sprite command. - add a way to resurrect / heal player. - add event commands to toggle inventory item status via trigger. - fix that Remote and Proximity detonators cannot be given. - add clear all screen effects option. - proper percents for changing movement / jumping. - kModernRandomTX, kModernSequentialTX: change event redirection mode activation from kModernTypeFlag02 to kCmdLink. - kModernSpriteDamager: treat damage value as percents by default, take in account god mode. - kModernEffectGen: fix wrong cstat for effects. - kModernPropertiesChanger: proper underwater status for sectors and players or enemies in it. - Players: assign or update current player's sprite index for all conditions. # Conflicts: # source/blood/src/nnexts.cpp
2020-05-05 18:50:14 +00:00
curScale[kDmgBurn] -= 32;
curScale[kDmgExplode] -= 32;
curScale[kDmgFall] = 65 + Random(15);
break;
case kMissileTeslaAlt:
case kMissileTeslaRegular:
curScale[kDmgElectric] = 32 + Random(8);
break;
}
break;
2019-09-19 22:42:45 +00:00
}
// add resistance if have an armor item to drop
if (pXSprite->dropMsg >= kItemArmorAsbest && pXSprite->dropMsg <= kItemArmorSuper)
{
switch (pXSprite->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 (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]);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static int getDispersionModifier(DBloodActor* actor, int minDisp, int maxDisp)
{
auto const pXSprite = &actor->x();
// the faster fire rate, the less frames = more dispersion
Seq* pSeq = getSequence(pXSprite->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
//
//---------------------------------------------------------------------------
static int getRangeAttackDist(DBloodActor* actor, int minDist, int maxDist)
{
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
int yrepeat = pSprite->yrepeat;
int dist = 0;
int seqId = pXSprite->data2;
int mul = 550;
int picnum = pSprite->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(pSprite->picnum), yrepeat, picnum);
return dist;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int getBaseChanceModifier(int baseChance)
{
return ((gGameOptions.nDifficulty > 0) ? baseChance - (0x0500 * gGameOptions.nDifficulty) : baseChance);
}
2021-05-06 07:08:06 +00:00
int getRecoilChance(DBloodActor* actor)
{
auto const pXSprite = &actor->x();
2021-08-27 17:12:22 +00:00
int mass = getSpriteMassBySize(actor);
2021-05-06 06:45:50 +00:00
int baseChance = (!dudeIsMelee(actor) ? 0x8000 : 0x4000);
baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
2019-09-19 22:42:45 +00:00
int chance = ((baseChance / mass) << 7);
return chance;
}
2021-05-06 07:08:06 +00:00
int getDodgeChance(DBloodActor* actor)
{
auto const pXSprite = &actor->x();
2021-08-27 17:12:22 +00:00
int mass = getSpriteMassBySize(actor);
2021-05-06 06:45:50 +00:00
int baseChance = (!dudeIsMelee(actor) ? 0x6000 : 0x1000);
baseChance = getBaseChanceModifier(baseChance) + pXSprite->data3;
2019-09-19 22:42:45 +00:00
int chance = ((baseChance / mass) << 7);
return chance;
}
2021-05-06 07:20:34 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void dudeLeechOperate(DBloodActor* actor, const EVENT& event)
2019-09-19 22:42:45 +00:00
{
2021-05-06 07:20:34 +00:00
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
if (event.cmd == kCmdOff)
{
actPostSprite(actor, kStatFree);
2019-09-19 22:42:45 +00:00
return;
}
2021-05-06 07:20:34 +00:00
auto actTarget = actor->GetTarget();
if (actTarget != nullptr && actTarget != actor->GetOwner())
{
if (actTarget->spr.statnum == kStatDude && !(actTarget->spr.flags & 32) && actTarget->hasX() && !pXSprite->stateTimer)
2021-05-06 07:20:34 +00:00
{
if (actTarget->IsPlayerActor())
2021-05-06 07:20:34 +00:00
{
PLAYER* pPlayer = &gPlayer[actTarget->spr.type - kDudePlayer1];
if (powerupCheck(pPlayer, kPwUpShadowCloak) > 0) return;
}
2019-09-19 22:42:45 +00:00
int top, bottom;
2021-05-06 07:20:34 +00:00
GetActorExtents(actor, &top, &bottom);
int nType = actTarget->spr.type - kDudeBase;
2019-09-19 22:42:45 +00:00
DUDEINFO* pDudeInfo = &dudeInfo[nType];
int z1 = (top - pSprite->pos.Z) - 256;
int x = actTarget->spr.pos.X; int y = actTarget->spr.pos.Y; int z = actTarget->spr.pos.Z;
int nDist = approxDist(x - pSprite->pos.X, y - pSprite->pos.Y);
2019-09-19 22:42:45 +00:00
if (nDist != 0 && cansee(pSprite->pos.X, pSprite->pos.Y, top, pSprite->sector(), x, y, z, actTarget->spr.sector()))
2021-05-06 07:20:34 +00:00
{
int t = DivScale(nDist, 0x1aaaaa, 12);
x += (actTarget->xvel * t) >> 12;
y += (actTarget->yvel * t) >> 12;
2019-09-19 22:42:45 +00:00
int angBak = pSprite->ang;
pSprite->ang = getangle(x - pSprite->pos.X, y - pSprite->pos.Y);
int dx = bcos(pSprite->ang);
int dy = bsin(pSprite->ang);
int tz = actTarget->spr.pos.Z - (actTarget->spr.yrepeat * pDudeInfo->aimHeight) * 4;
int dz = DivScale(tz - top - 256, nDist, 10);
int nMissileType = kMissileLifeLeechAltNormal + (pXSprite->data3 ? 1 : 0);
2019-09-19 22:42:45 +00:00
int t2;
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;
evPostActor(actor, t2, kCallbackLeechStateTimer);
2019-09-19 22:42:45 +00:00
pXSprite->data3 = ClipLow(pXSprite->data3 - 1, 0);
}
pSprite->ang = angBak;
}
}
}
}
2021-05-06 07:09:58 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool doExplosion(DBloodActor* actor, int nType)
{
2021-05-06 07:09:58 +00:00
auto const pSprite = &actor->s();
auto actExplosion = actSpawnSprite(pSprite->sector(), pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z, kStatExplosion, true);
2021-05-06 07:09:58 +00:00
auto const pXExplosion = &actExplosion->x();
if (!actExplosion->hasX())
return false;
2019-09-19 22:42:45 +00:00
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;
2021-05-06 07:09:58 +00:00
pXExplosion->data1 = pExpl->ticks;
pXExplosion->data2 = pExpl->quakeEffect;
pXExplosion->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; }
2021-09-04 21:27:21 +00:00
seqSpawn(nSeq, actExplosion, -1);
2021-05-06 07:09:58 +00:00
sfxPlay3DSound(actExplosion, nSnd, -1, 0);
2019-09-19 22:42:45 +00:00
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...
//
//---------------------------------------------------------------------------
DBloodActor* genDudeSpawn(DBloodActor* source, DBloodActor* actor, int nDist)
{
spritetype* pSprite = &actor->s();
auto pXSource = &source->x();
2021-05-06 07:43:35 +00:00
auto spawned = actSpawnSprite(actor, kStatDude);
2020-12-05 17:32:49 +00:00
XSPRITE* pXDude = &spawned->x();
int x, y, z = pSprite->pos.Z, nAngle = pSprite->ang, nType = kDudeModernCustom;
2021-05-06 07:43:35 +00:00
if (nDist > 0)
{
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
x = pSprite->pos.X + mulscale30r(Cos(nAngle), nDist);
y = pSprite->pos.Y + mulscale30r(Sin(nAngle), nDist);
2021-05-06 07:43:35 +00:00
}
else
{
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
x = pSprite->pos.X;
y = pSprite->pos.Y;
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
}
spawned->spr.type = nType; spawned->spr.ang = nAngle;
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.
pXDude->data1 = pXSource->data1;
pXDude->data2 = pXSource->data2;
pXDude->sysData1 = pXSource->data3; // move sndStartId from data3 to sysData1
pXDude->data3 = 0;
// spawn seq
2021-09-04 21:27:21 +00:00
seqSpawn(genDudeSeqStartId(spawned), spawned, -1);
// inherit movement speed.
pXDude->busyTime = pXSource->busyTime;
// inherit clipdist?
if (source->spr.clipdist > 0)
spawned->spr.clipdist = source->spr.clipdist;
// inherit custom hp settings
if (pXSource->data4 <= 0) pXDude->health = dudeInfo[nType - kDudeBase].startHealth << 4;
else pXDude->health = ClipRange(pXSource->data4 << 4, 1, 65535);
if (source->spr.flags & kModernTypeFlag1)
2021-05-06 07:43:35 +00:00
{
switch (source->spr.type) {
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
case kModernCustomDudeSpawn:
//inherit pal?
if (spawned->spr.pal <= 0) spawned->spr.pal = source->spr.pal;
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
// 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;
}
}
// inherit sprite size (useful for seqs with zero repeats)
if (source->spr.flags & kModernTypeFlag2)
2021-05-06 07:43:35 +00:00
{
spawned->spr.xrepeat = source->spr.xrepeat;
spawned->spr.yrepeat = source->spr.yrepeat;
}
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
gKillMgr.AddNewKill(1);
aiInitSprite(spawned);
return spawned;
}
2021-05-06 07:43:35 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void genDudeTransform(DBloodActor* actor)
{
if (!actor->hasX()) return;
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
auto actIncarnation = getNextIncarnation(actor);
if (actIncarnation == NULL)
{
if (pXSprite->sysData1 == kGenDudeTransformStatus) pXSprite->sysData1 = 0;
2021-08-27 14:47:46 +00:00
trTriggerSprite(actor, kCmdOff);
return;
}
auto pXIncarnation = &actIncarnation->x();
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
2021-08-27 14:47:46 +00:00
trTriggerSprite(actor, kCmdOff);
pSprite->type = pSprite->inittype = actIncarnation->spr.type;
pSprite->flags = actIncarnation->spr.flags;
pSprite->pal = actIncarnation->spr.pal;
pSprite->shade = actIncarnation->spr.shade;
pSprite->clipdist = actIncarnation->spr.clipdist;
pSprite->xrepeat = actIncarnation->spr.xrepeat;
pSprite->yrepeat = actIncarnation->spr.yrepeat;
pXSprite->txID = pXIncarnation->txID;
pXSprite->command = pXIncarnation->command;
pXSprite->triggerOn = triggerOn;
pXSprite->triggerOff = triggerOff;
pXSprite->busyTime = pXIncarnation->busyTime;
pXSprite->waitTime = pXIncarnation->waitTime;
// inherit respawn properties
pXSprite->respawn = pXIncarnation->respawn;
pXSprite->respawnPending = pXIncarnation->respawnPending;
pXSprite->burnTime = 0;
actor->SetBurnSource(nullptr);
pXSprite->data1 = pXIncarnation->data1;
pXSprite->data2 = pXIncarnation->data2;
pXSprite->sysData1 = pXIncarnation->data3; // soundBase id
pXSprite->sysData2 = pXIncarnation->data4; // start hp
pXSprite->dudeGuard = pXIncarnation->dudeGuard;
pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;
pXSprite->unused1 = pXIncarnation->unused1;
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
if (pXSprite->sysData2 <= 0) pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
else pXSprite->health = ClipRange(pXSprite->sysData2 << 4, 1, 65535);
int seqId = dudeInfo[pSprite->type - kDudeBase].seqStartID;
switch (pSprite->type) {
case kDudePodMother: // fake dude
case kDudeTentacleMother: // fake dude
break;
case kDudeModernCustom:
case kDudeModernCustomBurning:
2021-05-06 08:51:39 +00:00
seqId = genDudeSeqStartId(actor);
genDudePrepare(actor, kGenDudePropertyMass);
[[fallthrough]]; // go below
default:
2021-09-04 21:27:21 +00:00
seqSpawn(seqId, actor, -1);
// save target
2021-05-06 07:43:35 +00:00
auto target = actor->GetTarget();
// re-init sprite
aiInitSprite(actor);
// try to restore target
if (target == nullptr) aiSetTarget(actor, pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z);
2021-05-06 07:43:35 +00:00
else aiSetTarget(actor, target);
// finally activate it
aiActivateDude(actor);
break;
}
pXIncarnation->triggerOn = triggerOn;
pXIncarnation->triggerOff = triggerOff;
/*// remove the incarnation in case if non-locked
if (pXIncarnation->locked == 0) {
pXIncarnation->txID = actIncarnation->spr.type = 0;
2021-10-13 21:56:03 +00:00
actPostSprite(pIncarnation, kStatFree);
// or restore triggerOn and off options
} else {
pXIncarnation->triggerOn = triggerOn;
pXIncarnation->triggerOff = triggerOff;
}*/
}
2021-05-06 07:43:35 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2019-09-19 22:42:45 +00:00
void updateTargetOfLeech(DBloodActor* actor)
{
if (!actor->hasX()) return;
2021-05-05 19:06:38 +00:00
auto actLeech = leechIsDropped(actor);
if (actLeech == NULL || !actLeech->hasX()) actor->genDudeExtra.pLifeLeech = nullptr;
2021-05-05 19:06:38 +00:00
else
{
XSPRITE* pXDude = &actor->x();
if (actor->GetTarget() != actLeech->GetTarget())
{
if (actor->GetTarget() == nullptr && actLeech->GetTarget() != nullptr)
{
aiSetTarget(actor, actLeech->GetTarget());
if (inIdle(pXDude->aiState))
2021-05-05 19:06:38 +00:00
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)
{
if (!actor->hasX()) return;
auto const pSprite = &actor->s();
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], pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z);
}
else
{
aiSetTarget(slave[i], pSprite->pos.X, pSprite->pos.Y, pSprite->pos.Z); // try return to master
}
}
}
// 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
{
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) {
return (aiState == &genDudeIdleW || aiState == &genDudeIdleL);
}
bool inAttack(AISTATE* aiState) {
return (aiState == &genDudeFireL || aiState == &genDudeFireW
|| aiState == &genDudeFireD || aiState == &genDudeThrow || aiState == &genDudeThrow2 || aiState == &genDudePunch);
}
bool inSearch(AISTATE* aiState) {
return (aiState->stateType == kAiStateSearch);
}
int 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;
}
int 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;
}
int 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;
}
2021-05-06 08:24:29 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool canSwim(DBloodActor* actor)
{
return actor->genDudeExtra.canSwim;
}
2021-05-06 08:24:29 +00:00
bool canDuck(DBloodActor* actor)
{
return actor->genDudeExtra.canDuck;
}
2021-05-06 08:24:29 +00:00
bool canWalk(DBloodActor* actor)
{
return actor->genDudeExtra.canWalk;
}
2021-05-06 08:24:29 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-05-06 08:51:39 +00:00
int genDudeSeqStartId(DBloodActor* actor)
{
if (genDudePrepare(actor, kGenDudePropertyStates)) return actor->xspr.data2;
else return kGenDudeDefaultSeq;
}
2021-05-06 08:51:39 +00:00
bool genDudePrepare(DBloodActor* actor, int propId)
{
if (!actor || !actor->hasX()) return false;
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
if (pSprite->type != kDudeModernCustom) {
Printf(PRINT_HIGH, "pSprite->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;
2021-05-06 08:51:39 +00:00
pExtra->updReq[propId] = false;
switch (propId) {
case kGenDudePropertyAll:
case kGenDudePropertyInitVals:
pExtra->moveSpeed = getGenDudeMoveSpeed(actor, 0, true, false);
pExtra->initVals[0] = pSprite->xrepeat;
pExtra->initVals[1] = pSprite->yrepeat;
pExtra->initVals[2] = pSprite->clipdist;
if (propId) break;
[[fallthrough]];
case kGenDudePropertyWeapon: {
pExtra->curWeapon = pXSprite->data1;
switch (pXSprite->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(pXSprite->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:
2021-05-06 07:02:49 +00:00
scaleDamage(actor);
if (propId) break;
[[fallthrough]];
case kGenDudePropertyMass: {
// to ensure mass gets updated, let's clear all cache
2021-09-01 19:53:33 +00:00
SPRITEMASS* pMass = &actor->spriteMass;
pMass->seqId = pMass->picnum = pMass->xrepeat = pMass->yrepeat = pMass->clipdist = 0;
pMass->mass = pMass->airVel = pMass->fraction = 0;
2021-08-27 17:12:22 +00:00
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 (pXSprite->data2 <= 0) seqStartId = pXSprite->data2 = getDudeInfo(pSprite->type)->seqStartID;
else seqStartId = pXSprite->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)
{
pXSprite->data2 = getDudeInfo(pSprite->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 i = 0; i < pSeq->nFrames; i++) {
if (!pSeq->frames[i].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)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeIdleW);
else aiGenDudeNewState(actor, &genDudeIdleL);
}
else if (pExtra->canWalk)
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseW);
else if (inDuck(pXSprite->aiState)) aiGenDudeNewState(actor, &genDudeChaseD);
else aiGenDudeNewState(actor, &genDudeChaseL);
}
else
{
2021-05-05 18:40:31 +00:00
if (spriteIsUnderwater(actor, false)) aiGenDudeNewState(actor, &genDudeChaseNoWalkW);
else if (inDuck(pXSprite->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:
2021-05-05 19:06:38 +00:00
pExtra->pLifeLeech = nullptr;
if (!actor->GetSpecialOwner())
{
2021-09-01 19:52:36 +00:00
BloodStatIterator it(kStatThing);
while (auto actor2 = it.Next())
2020-10-15 15:15:45 +00:00
{
if (actor2->GetOwner() == actor && actor2->spr.type == kModernThingEnemyLifeLeech) {
2021-09-01 19:52:36 +00:00
pExtra->pLifeLeech = actor2;
break;
}
}
}
if (propId) break;
[[fallthrough]];
case kGenDudePropertySlaves:
2020-10-15 15:15:45 +00:00
{
pExtra->slaveCount = 0;
for (auto i = 0; i < kGenDudeMaxSlaves; i++)
{
pExtra->slave[i] = nullptr;
}
2021-09-01 19:52:36 +00:00
BloodStatIterator it(kStatDude);
while (auto actor2 = it.Next())
2020-10-15 15:15:45 +00:00
{
2021-09-01 19:52:36 +00:00
if (actor2->GetOwner() != actor) continue;
else if (!actor2->IsDudeActor() || !actor2->hasX() || actor2->xspr.health <= 0) {
2021-09-01 19:52:36 +00:00
actor2->SetOwner(nullptr);
continue;
}
2021-09-01 19:52:36 +00:00
pExtra->slave[pExtra->slaveCount++] = actor2;
if (pExtra->slaveCount > gGameOptions.nDifficulty)
break;
}
if (propId) break;
[[fallthrough]];
2020-10-15 15:15:45 +00:00
}
case kGenDudePropertySpriteSize: {
2021-09-01 19:52:36 +00:00
if (seqGetStatus(actor) == -1)
2021-09-04 21:27:21 +00:00
seqSpawn(pXSprite->data2 + pXSprite->aiState->seqId, actor, -1);
// make sure dudes aren't in the floor or ceiling
int zTop, zBot; GetSpriteExtents(pSprite, &zTop, &zBot);
if (!(pSprite->sector()->ceilingstat & CSTAT_SECTOR_SKY))
pSprite->pos.Z += ClipLow(pSprite->sector()->ceilingz - zTop, 0);
if (!(pSprite->sector()->floorstat & CSTAT_SECTOR_SKY))
pSprite->pos.Z += ClipHigh(pSprite->sector()->floorz - zBot, 0);
pSprite->clipdist = ClipRange((pSprite->xrepeat + pSprite->yrepeat) >> 1, 4, 120);
if (propId) break;
}
}
return true;
}
2021-05-06 08:55:56 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void genDudePostDeath(DBloodActor* actor, DAMAGE_TYPE damageType, int damage)
{
auto const pSprite = &actor->s();
if (damageType == kDamageExplode)
{
DUDEINFO* pDudeInfo = getDudeInfo(pSprite->type);
for (int i = 0; i < 3; i++)
if (pDudeInfo->nGibType[i] > -1)
2021-08-29 20:22:16 +00:00
GibSprite(actor, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
for (int i = 0; i < 4; i++)
2021-08-26 22:07:33 +00:00
fxSpawnBlood(actor, damage);
}
2021-12-22 15:43:59 +00:00
gKillMgr.AddKill(actor);
pSprite->type = kThingBloodChunks;
2021-08-28 12:14:18 +00:00
actPostSprite(actor, kStatThing);
}
2021-05-06 08:55:56 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void aiGenDudeInitSprite(DBloodActor* actor)
{
2021-05-06 08:55:56 +00:00
auto const pSprite = &actor->s();
auto const pXSprite = &actor->x();
switch (pSprite->type)
{
case kDudeModernCustom:
{
DUDEEXTRA_STATS* pDudeExtraE = &actor->dudeExtra.stats;
pDudeExtraE->active = 0;
aiGenDudeNewState(actor, &genDudeIdleL);
break;
}
case kDudeModernCustomBurning:
aiGenDudeNewState(actor, &genDudeBurnGoto);
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